Merge branch 'import/ss/graphite' into ss/graphite
authorFlorian Forster <octo@collectd.org>
Thu, 2 Feb 2012 17:19:15 +0000 (18:19 +0100)
committerFlorian Forster <octo@collectd.org>
Thu, 2 Feb 2012 17:19:15 +0000 (18:19 +0100)
334 files changed:
.gitignore [new file with mode: 0644]
.mailmap [new file with mode: 0644]
AUTHORS [new file with mode: 0644]
COPYING [new file with mode: 0644]
ChangeLog [new file with mode: 0644]
Makefile.am [new file with mode: 0644]
NEWS [new file with mode: 0644]
README [new file with mode: 0644]
TODO [new file with mode: 0644]
bindings/Makefile.am [new file with mode: 0644]
bindings/java/Makefile.am [new file with mode: 0644]
bindings/java/org/collectd/api/Collectd.java [new file with mode: 0644]
bindings/java/org/collectd/api/CollectdConfigInterface.java [new file with mode: 0644]
bindings/java/org/collectd/api/CollectdFlushInterface.java [new file with mode: 0644]
bindings/java/org/collectd/api/CollectdInitInterface.java [new file with mode: 0644]
bindings/java/org/collectd/api/CollectdLogInterface.java [new file with mode: 0644]
bindings/java/org/collectd/api/CollectdMatchFactoryInterface.java [new file with mode: 0644]
bindings/java/org/collectd/api/CollectdMatchInterface.java [new file with mode: 0644]
bindings/java/org/collectd/api/CollectdNotificationInterface.java [new file with mode: 0644]
bindings/java/org/collectd/api/CollectdReadInterface.java [new file with mode: 0644]
bindings/java/org/collectd/api/CollectdShutdownInterface.java [new file with mode: 0644]
bindings/java/org/collectd/api/CollectdTargetFactoryInterface.java [new file with mode: 0644]
bindings/java/org/collectd/api/CollectdTargetInterface.java [new file with mode: 0644]
bindings/java/org/collectd/api/CollectdWriteInterface.java [new file with mode: 0644]
bindings/java/org/collectd/api/DataSet.java [new file with mode: 0644]
bindings/java/org/collectd/api/DataSource.java [new file with mode: 0644]
bindings/java/org/collectd/api/Notification.java [new file with mode: 0644]
bindings/java/org/collectd/api/OConfigItem.java [new file with mode: 0644]
bindings/java/org/collectd/api/OConfigValue.java [new file with mode: 0644]
bindings/java/org/collectd/api/PluginData.java [new file with mode: 0644]
bindings/java/org/collectd/api/ValueList.java [new file with mode: 0644]
bindings/java/org/collectd/java/GenericJMX.java [new file with mode: 0644]
bindings/java/org/collectd/java/GenericJMXConfConnection.java [new file with mode: 0644]
bindings/java/org/collectd/java/GenericJMXConfMBean.java [new file with mode: 0644]
bindings/java/org/collectd/java/GenericJMXConfValue.java [new file with mode: 0644]
bindings/java/org/collectd/java/JMXMemory.java [new file with mode: 0644]
bindings/perl/Makefile.PL [new file with mode: 0644]
bindings/perl/lib/Collectd.pm [new file with mode: 0644]
bindings/perl/lib/Collectd/Plugins/Monitorus.pm [new file with mode: 0644]
bindings/perl/lib/Collectd/Plugins/OpenVZ.pm [new file with mode: 0644]
bindings/perl/lib/Collectd/Unixsock.pm [new file with mode: 0644]
build.sh [new file with mode: 0755]
clean.sh [new file with mode: 0755]
configure.in [new file with mode: 0644]
contrib/GenericJMX.conf [new file with mode: 0644]
contrib/README [new file with mode: 0644]
contrib/SpamAssassin/Collectd.pm [new file with mode: 0644]
contrib/SpamAssassin/example.cf [new file with mode: 0644]
contrib/add_rra.sh [new file with mode: 0755]
contrib/aix/collectd.spec [new file with mode: 0644]
contrib/aix/init.d-collectd [new file with mode: 0755]
contrib/collectd2html.pl [new file with mode: 0644]
contrib/collectd_network.py [new file with mode: 0644]
contrib/collectd_unix_sock.rb [new file with mode: 0644]
contrib/collectd_unixsock.py [new file with mode: 0644]
contrib/collection.cgi [new file with mode: 0755]
contrib/collection.conf [new file with mode: 0644]
contrib/collection3/README [new file with mode: 0644]
contrib/collection3/bin/.htaccess [new file with mode: 0644]
contrib/collection3/bin/graph.cgi [new file with mode: 0755]
contrib/collection3/bin/index.cgi [new file with mode: 0755]
contrib/collection3/bin/json.cgi [new file with mode: 0755]
contrib/collection3/etc/.htaccess [new file with mode: 0644]
contrib/collection3/etc/collection.conf [new file with mode: 0644]
contrib/collection3/lib/.htaccess [new file with mode: 0644]
contrib/collection3/lib/Collectd/Config.pm [new file with mode: 0644]
contrib/collection3/lib/Collectd/Graph/Common.pm [new file with mode: 0644]
contrib/collection3/lib/Collectd/Graph/Config.pm [new file with mode: 0644]
contrib/collection3/lib/Collectd/Graph/Type.pm [new file with mode: 0644]
contrib/collection3/lib/Collectd/Graph/Type/ArcCounts.pm [new file with mode: 0644]
contrib/collection3/lib/Collectd/Graph/Type/Df.pm [new file with mode: 0644]
contrib/collection3/lib/Collectd/Graph/Type/GenericIO.pm [new file with mode: 0644]
contrib/collection3/lib/Collectd/Graph/Type/GenericStacked.pm [new file with mode: 0644]
contrib/collection3/lib/Collectd/Graph/Type/JavaMemory.pm [new file with mode: 0644]
contrib/collection3/lib/Collectd/Graph/Type/Load.pm [new file with mode: 0644]
contrib/collection3/lib/Collectd/Graph/Type/PsCputime.pm [new file with mode: 0644]
contrib/collection3/lib/Collectd/Graph/Type/TableSize.pm [new file with mode: 0644]
contrib/collection3/lib/Collectd/Graph/Type/Wirkleistung.pm [new file with mode: 0644]
contrib/collection3/lib/Collectd/Graph/TypeLoader.pm [new file with mode: 0644]
contrib/collection3/share/.htaccess [new file with mode: 0644]
contrib/collection3/share/navigate.js [new file with mode: 0644]
contrib/collection3/share/shortcut-icon.png [new file with mode: 0644]
contrib/collection3/share/style.css [new file with mode: 0644]
contrib/cussh.pl [new file with mode: 0755]
contrib/examples/MyPlugin.pm [new file with mode: 0644]
contrib/examples/myplugin.c [new file with mode: 0644]
contrib/exec-munin.conf [new file with mode: 0644]
contrib/exec-munin.px [new file with mode: 0755]
contrib/exec-nagios.conf [new file with mode: 0644]
contrib/exec-nagios.px [new file with mode: 0755]
contrib/exec-smartctl [new file with mode: 0755]
contrib/fedora/collectd.spec [new file with mode: 0644]
contrib/fedora/init.d-collectd [new file with mode: 0644]
contrib/iptables/accounting.sh [new file with mode: 0755]
contrib/migrate-3-4.px [new file with mode: 0755]
contrib/migrate-4-5.px [new file with mode: 0755]
contrib/network-proxy.py [new file with mode: 0644]
contrib/oracle/create_schema.ddl [new file with mode: 0644]
contrib/oracle/db_systat.sql [new file with mode: 0644]
contrib/php-collection/browser.js [new file with mode: 0644]
contrib/php-collection/config.php [new file with mode: 0644]
contrib/php-collection/definitions.local.php [new file with mode: 0644]
contrib/php-collection/definitions.php [new file with mode: 0644]
contrib/php-collection/functions.php [new file with mode: 0644]
contrib/php-collection/graph.php [new file with mode: 0644]
contrib/php-collection/index.php [new file with mode: 0644]
contrib/python/getsigchld.py [new file with mode: 0644]
contrib/redhat/apache.conf [new file with mode: 0644]
contrib/redhat/collectd.conf [new file with mode: 0644]
contrib/redhat/collectd.spec [new file with mode: 0644]
contrib/redhat/email.conf [new file with mode: 0644]
contrib/redhat/init.d-collectd [new file with mode: 0644]
contrib/redhat/mysql.conf [new file with mode: 0644]
contrib/redhat/nginx.conf [new file with mode: 0644]
contrib/redhat/sensors.conf [new file with mode: 0644]
contrib/redhat/snmp.conf [new file with mode: 0644]
contrib/rrd_filter.px [new file with mode: 0755]
contrib/sles10.1/collectd.spec [new file with mode: 0644]
contrib/sles10.1/init.d-collectd [new file with mode: 0755]
contrib/snmp-data.conf [new file with mode: 0644]
contrib/snmp-probe-host.px [new file with mode: 0755]
contrib/solaris-smf/README [new file with mode: 0644]
contrib/solaris-smf/collectd [new file with mode: 0755]
contrib/solaris-smf/collectd.xml [new file with mode: 0644]
src/Makefile.am [new file with mode: 0644]
src/amqp.c [new file with mode: 0644]
src/apache.c [new file with mode: 0644]
src/apcups.c [new file with mode: 0644]
src/apple_sensors.c [new file with mode: 0644]
src/ascent.c [new file with mode: 0644]
src/battery.c [new file with mode: 0644]
src/bind.c [new file with mode: 0644]
src/collectd-email.pod [new file with mode: 0644]
src/collectd-exec.pod [new file with mode: 0644]
src/collectd-java.pod [new file with mode: 0644]
src/collectd-nagios.c [new file with mode: 0644]
src/collectd-nagios.pod [new file with mode: 0644]
src/collectd-perl.pod [new file with mode: 0644]
src/collectd-python.pod [new file with mode: 0644]
src/collectd-snmp.pod [new file with mode: 0644]
src/collectd-threshold.pod [new file with mode: 0644]
src/collectd-unixsock.pod [new file with mode: 0644]
src/collectd.c [new file with mode: 0644]
src/collectd.conf.in [new file with mode: 0644]
src/collectd.conf.pod [new file with mode: 0644]
src/collectd.h [new file with mode: 0644]
src/collectd.pod [new file with mode: 0644]
src/collectdctl.c [new file with mode: 0644]
src/collectdctl.pod [new file with mode: 0644]
src/collectdmon.c [new file with mode: 0644]
src/collectdmon.pod [new file with mode: 0644]
src/common.c [new file with mode: 0644]
src/common.h [new file with mode: 0644]
src/configfile.c [new file with mode: 0644]
src/configfile.h [new file with mode: 0644]
src/conntrack.c [new file with mode: 0644]
src/contextswitch.c [new file with mode: 0644]
src/cpu.c [new file with mode: 0644]
src/cpufreq.c [new file with mode: 0644]
src/cpython.h [new file with mode: 0644]
src/csv.c [new file with mode: 0644]
src/curl.c [new file with mode: 0644]
src/curl_json.c [new file with mode: 0644]
src/curl_xml.c [new file with mode: 0644]
src/dbi.c [new file with mode: 0644]
src/df.c [new file with mode: 0644]
src/disk.c [new file with mode: 0644]
src/dns.c [new file with mode: 0644]
src/email.c [new file with mode: 0644]
src/entropy.c [new file with mode: 0644]
src/exec.c [new file with mode: 0644]
src/filecount.c [new file with mode: 0644]
src/filter_chain.c [new file with mode: 0644]
src/filter_chain.h [new file with mode: 0644]
src/fscache.c [new file with mode: 0644]
src/gmond.c [new file with mode: 0644]
src/hddtemp.c [new file with mode: 0644]
src/interface.c [new file with mode: 0644]
src/ipmi.c [new file with mode: 0644]
src/iptables.c [new file with mode: 0644]
src/ipvs.c [new file with mode: 0644]
src/irq.c [new file with mode: 0644]
src/java.c [new file with mode: 0644]
src/libcollectdclient/Makefile.am [new file with mode: 0644]
src/libcollectdclient/client.c [new file with mode: 0644]
src/libcollectdclient/client.h [new file with mode: 0644]
src/libcollectdclient/lcc_features.h.in [new file with mode: 0644]
src/libcollectdclient/libcollectdclient.pc.in [new file with mode: 0644]
src/liboconfig/AUTHORS [new file with mode: 0644]
src/liboconfig/COPYING [new file with mode: 0644]
src/liboconfig/ChangeLog [new file with mode: 0644]
src/liboconfig/Makefile.am [new file with mode: 0644]
src/liboconfig/aux_types.h [new file with mode: 0644]
src/liboconfig/oconfig.c [new file with mode: 0644]
src/liboconfig/oconfig.h [new file with mode: 0644]
src/liboconfig/parser.y [new file with mode: 0644]
src/liboconfig/scanner.l [new file with mode: 0644]
src/libvirt.c [new file with mode: 0644]
src/load.c [new file with mode: 0644]
src/logfile.c [new file with mode: 0644]
src/lpar.c [new file with mode: 0644]
src/madwifi.c [new file with mode: 0644]
src/madwifi.h [new file with mode: 0644]
src/match_empty_counter.c [new file with mode: 0644]
src/match_hashed.c [new file with mode: 0644]
src/match_regex.c [new file with mode: 0644]
src/match_timediff.c [new file with mode: 0644]
src/match_value.c [new file with mode: 0644]
src/mbmon.c [new file with mode: 0644]
src/memcachec.c [new file with mode: 0644]
src/memcached.c [new file with mode: 0644]
src/memory.c [new file with mode: 0644]
src/meta_data.c [new file with mode: 0644]
src/meta_data.h [new file with mode: 0644]
src/modbus.c [new file with mode: 0644]
src/multimeter.c [new file with mode: 0644]
src/mysql.c [new file with mode: 0644]
src/netapp.c [new file with mode: 0644]
src/netlink.c [new file with mode: 0644]
src/network.c [new file with mode: 0644]
src/network.h [new file with mode: 0644]
src/nfs.c [new file with mode: 0644]
src/nginx.c [new file with mode: 0644]
src/notify_desktop.c [new file with mode: 0644]
src/notify_email.c [new file with mode: 0644]
src/ntpd.c [new file with mode: 0644]
src/nut.c [new file with mode: 0644]
src/olsrd.c [new file with mode: 0644]
src/onewire.c [new file with mode: 0644]
src/openvpn.c [new file with mode: 0644]
src/oracle.c [new file with mode: 0644]
src/perl.c [new file with mode: 0644]
src/pinba.c [new file with mode: 0644]
src/pinba.proto [new file with mode: 0644]
src/ping.c [new file with mode: 0644]
src/plugin.c [new file with mode: 0644]
src/plugin.h [new file with mode: 0644]
src/postgresql.c [new file with mode: 0644]
src/postgresql_default.conf [new file with mode: 0644]
src/powerdns.c [new file with mode: 0644]
src/processes.c [new file with mode: 0644]
src/protocols.c [new file with mode: 0644]
src/pyconfig.c [new file with mode: 0644]
src/python.c [new file with mode: 0644]
src/pyvalues.c [new file with mode: 0644]
src/redis.c [new file with mode: 0644]
src/routeros.c [new file with mode: 0644]
src/rrdcached.c [new file with mode: 0644]
src/rrdtool.c [new file with mode: 0644]
src/sensors.c [new file with mode: 0644]
src/serial.c [new file with mode: 0644]
src/snmp.c [new file with mode: 0644]
src/swap.c [new file with mode: 0644]
src/syslog.c [new file with mode: 0644]
src/table.c [new file with mode: 0644]
src/tail.c [new file with mode: 0644]
src/tape.c [new file with mode: 0644]
src/target_notification.c [new file with mode: 0644]
src/target_replace.c [new file with mode: 0644]
src/target_scale.c [new file with mode: 0644]
src/target_set.c [new file with mode: 0644]
src/target_v5upgrade.c [new file with mode: 0644]
src/tcpconns.c [new file with mode: 0644]
src/teamspeak2.c [new file with mode: 0644]
src/ted.c [new file with mode: 0644]
src/thermal.c [new file with mode: 0644]
src/threshold.c [new file with mode: 0644]
src/tokyotyrant.c [new file with mode: 0644]
src/types.db [new file with mode: 0644]
src/types.db.pod [new file with mode: 0644]
src/types_list.c [new file with mode: 0644]
src/types_list.h [new file with mode: 0644]
src/unixsock.c [new file with mode: 0644]
src/uptime.c [new file with mode: 0644]
src/users.c [new file with mode: 0644]
src/utils_avltree.c [new file with mode: 0644]
src/utils_avltree.h [new file with mode: 0644]
src/utils_cache.c [new file with mode: 0644]
src/utils_cache.h [new file with mode: 0644]
src/utils_cmd_flush.c [new file with mode: 0644]
src/utils_cmd_flush.h [new file with mode: 0644]
src/utils_cmd_getthreshold.c [new file with mode: 0644]
src/utils_cmd_getthreshold.h [new file with mode: 0644]
src/utils_cmd_getval.c [new file with mode: 0644]
src/utils_cmd_getval.h [new file with mode: 0644]
src/utils_cmd_listval.c [new file with mode: 0644]
src/utils_cmd_listval.h [new file with mode: 0644]
src/utils_cmd_putnotif.c [new file with mode: 0644]
src/utils_cmd_putnotif.h [new file with mode: 0644]
src/utils_cmd_putval.c [new file with mode: 0644]
src/utils_cmd_putval.h [new file with mode: 0644]
src/utils_complain.c [new file with mode: 0644]
src/utils_complain.h [new file with mode: 0644]
src/utils_db_query.c [new file with mode: 0644]
src/utils_db_query.h [new file with mode: 0644]
src/utils_dns.c [new file with mode: 0644]
src/utils_dns.h [new file with mode: 0644]
src/utils_fbhash.c [new file with mode: 0644]
src/utils_fbhash.h [new file with mode: 0644]
src/utils_format_json.c [new file with mode: 0644]
src/utils_format_json.h [new file with mode: 0644]
src/utils_heap.c [new file with mode: 0644]
src/utils_heap.h [new file with mode: 0644]
src/utils_ignorelist.c [new file with mode: 0644]
src/utils_ignorelist.h [new file with mode: 0644]
src/utils_llist.c [new file with mode: 0644]
src/utils_llist.h [new file with mode: 0644]
src/utils_match.c [new file with mode: 0644]
src/utils_match.h [new file with mode: 0644]
src/utils_mount.c [new file with mode: 0644]
src/utils_mount.h [new file with mode: 0644]
src/utils_parse_option.c [new file with mode: 0644]
src/utils_parse_option.h [new file with mode: 0644]
src/utils_rrdcreate.c [new file with mode: 0644]
src/utils_rrdcreate.h [new file with mode: 0644]
src/utils_subst.c [new file with mode: 0644]
src/utils_subst.h [new file with mode: 0644]
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_time.c [new file with mode: 0644]
src/utils_time.h [new file with mode: 0644]
src/uuid.c [new file with mode: 0644]
src/varnish.c [new file with mode: 0644]
src/vmem.c [new file with mode: 0644]
src/vserver.c [new file with mode: 0644]
src/wireless.c [new file with mode: 0644]
src/write_http.c [new file with mode: 0644]
src/write_mongodb.c [new file with mode: 0644]
src/write_redis.c [new file with mode: 0644]
src/xmms.c [new file with mode: 0644]
src/zfs_arc.c [new file with mode: 0644]
version-gen.sh [new file with mode: 0755]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..75cb307
--- /dev/null
@@ -0,0 +1,74 @@
+# build.sh stuff:
+Makefile.in
+/INSTALL
+/aclocal.m4
+/autom4te.cache
+/autom4te.cache
+/compile
+/config.guess
+/config.sub
+/configure
+/depcomp
+/install-sh
+/libltdl/
+/ltmain.sh
+/missing
+src/config.h.in
+
+# configure stuff:
+Makefile
+config.log
+config.status
+libtool
+src/.deps
+src/collectd.conf
+src/config.h
+src/libcollectdclient/libcollectdclient.pc
+src/stamp-h1
+
+# make stuff:
+*.la
+*.lo
+*.o
+.libs/
+src/collectd
+src/collectd-nagios
+src/collectdctl
+src/collectdmon
+src/*.1
+src/*.5
+src/libcollectdclient/lcc_features.h
+
+# patch stuff
+*.rej
+*.orig
+
+# lex / yacc stuff:
+ylwrap
+src/liboconfig/parser.c
+src/liboconfig/parser.h
+src/liboconfig/scanner.c
+
+# make dist stuff:
+/collectd-*.tar.gz
+/collectd-*.tar.bz2
+
+# perl stuff:
+bindings/.perl-directory-stamp
+bindings/perl/Collectd/pm_to_blib
+bindings/perl/blib/
+bindings/perl/pm_to_blib
+
+# java stuff
+bindings/java/java-build-stamp
+bindings/java/org/collectd/api/*.class
+bindings/java/org/collectd/java/*.class
+
+# python stuff
+*.pyc
+
+# tag stuff
+src/tags
+
+# backup stuff
+*~
diff --git a/.mailmap b/.mailmap
new file mode 100644 (file)
index 0000000..a39a1d2
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,9 @@
+Anthony <anthony>
+Florian Forster <octo>
+Florian Forster <octo@dev4.office.noris.de>
+Luboš Staněk <kolektor@atlas.cz>
+Luboš Staněk <lubek@users.sourceforge.net>
+Niki W. Waibel <niki>
+Sebastian Harl <tokkee>
+Rodolphe Quiedeville <rquiedeville@bearstech.com>
+
diff --git a/AUTHORS b/AUTHORS
new file mode 100644 (file)
index 0000000..14e96be
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,206 @@
+Permanent project members
+=========================
+
+Florian "octo" Forster <octo at verplant.org>
+ - Initial author.
+
+Sebastian "tokkee" Harl <sh at tokkee.org>
+ - Bugfixes and enhancments in many places all around the project.
+ - perl plugin.
+ - users plugin.
+ - vserver plugin.
+ - Debian package.
+
+
+Contributors (sorted alphabetically)
+====================================
+
+Akkarit Sangpetch <asangpet at andrew.cmu.edu>
+ - write_mongodb plugin.
+
+Alessandro Iurlano <alessandro.iurlano at gmail.com>
+ - Initial filecount plugin.
+
+Alvaro Barcellos <alvaro.barcellos at gmail.com>
+ - Don't-fork patch.
+
+Amit Gupta <amit.gupta221 at gmail.com>
+ - Multiple servers in the apache plugin.
+ - curl_xml plugin.
+
+Anthony Dewhurst <dewhurst at gmail.com>
+ - zfs_arc plugin.
+
+Anthony Gialluca <tonyabg at charter.net>
+ - apcups plugin.
+
+Antony Dovgal <tony at daylessday.org>
+ - memcached plugin.
+
+Aurélien Reynaud <collectd at wattapower.net>
+ - LPAR plugin.
+ - Various fixes for AIX, HP-UX and Solaris.
+
+Bruno Prémont <bonbons at linux-vserver.org>
+ - BIND plugin.
+ - Many bugreports and -fixes in various plugins,
+   especially a nasty bug in the network plugin.
+ - Wireshark dissector.
+
+Chris Lundquist <clundquist at bluebox.net>
+ - Improvements to the write_mongodb plugin.
+
+Christophe Kalt <collectd at klb.taranis.org>
+ - The version 3 `log' mode.
+ - Many Solaris related hints and fixes.
+
+Dan Berrange <berrange at redhat.com>
+ - uuid plugin.
+
+David Bacher <drbacher at gmail.com>
+ - serial plugin.
+
+Doug MacEachern <dougm at hyperic.com>
+ - The `-T' option (config testing mode).
+ - OpenVPN plugin.
+ - jcollectd (two-way JMX integration).
+ - A few other patches to various plugins.
+ - curl_json plugin.
+
+Edward “Koko” Konetzko <konetzed at quixoticagony.com>
+ - fscache plugin.
+
+Fabian Linzberger <e at lefant.net>
+ - Percentage aggregation for `collectd-nagios'.
+
+Fabien Wernli <cpan at faxm0dem.org>
+ - Solaris improvements in the memory and interfaces plugin.
+
+Flavio Stanchina <flavio at stanchina.net>
+ - mbmon plugin.
+
+Franck Lombardi
+ - UNIX socket code for the memcached plugin.
+
+Jason Pepas <cell at ices.utexas.edu>
+ - nfs plugin.
+
+Jérôme Renard <jerome.renard at gmail.com>
+ - varnish plugin.
+
+Luboš Staněk <kolektor at atlas.cz>
+ - sensors plugin improvements.
+ - Time and effort to find a nasty bug in the ntpd-plugin.
+
+Luke Herberling <collectd at c-ware.com>
+ - powerdns plugin.
+ - Initial `tail' subsystem by:
+
+Lyonel Vincent <lyonel at ezix.org>
+ - processes plugin.
+
+Manuel Sanmartin
+ - AIX port of the following plugins:
+   + cpu
+   + disk
+   + interface
+   + load
+   + memory
+   + processes
+   + swap
+ - Various AIX-related fixes and hacks.
+
+Marco Chiappero <marco at absence.it>
+ - uptime plugin.
+ - ip6tables support in the iptables plugin.
+ - openvpn plugin (support for more status file formats)
+
+Michael Stapelberg <michael+git at stapelberg.de>
+ - OpenBSD port of the tcpconns plugin.
+
+Michał Mirosław <mirq-linux at rere.qmqm.pl>
+ - thermal plugin.
+ - Streamlines recursive directory traversion.
+
+Mirko Buffoni <briareos at eswat.org>
+ - Port/Socket selection in the MySQL plugin.
+
+Niki W. Waibel <niki.waibel at newlogic.com>
+ - Initial autotools fixes.
+ - libltdl code.
+ - getmnt-wizardry.
+
+Oleg King <king2 at kaluga.ru>
+ - Added support for the statgrab library to
+   + the cpu plugin,
+   + the disk plugin, and
+   + the users plugin.
+
+Ondrej Zajicek <santiago at crfreenet.org>
+ - madwifi plugin.
+
+Patrik Weiskircher <weiskircher at inqnet.at>
+ - Contextswitch plugin.
+ - Forkrate counter in the processes plugin.
+ - INode count in the DF plugin.
+
+Paul Sadauskas <psadauskas at gmail.com>
+ - tokyotyrant plugin.
+ - `ReportByDevice' option of the df plugin.
+ - write_http plugin.
+
+Peter Holik <peter at holik.at>
+ - cpufreq plugin.
+ - multimeter plugin.
+ - irq plugin.
+ - Some bugfixes in the exec plugin.
+ - Notifications in the ipmi plugin.
+
+Phoenix Kayo <kayo.k11.4 at gmail.com>
+ - pinba plugin.
+
+Piotr Hosowicz <the55 at wp.pl>
+ - SMF manifest for collectd.
+
+Richard W. M. Jones <rjones at redhat.com>
+ - libvirt plugin.
+ - uuid plugin.
+
+Roman Klesel <roman.klesel at noris.de>
+ - Oracle schema and sample SQL statements to be used with the Oracle plugin.
+
+Rodolphe Quiédeville <rquiedeville at bearstech.com>
+ - Lock statistics in the mysql plugin.
+
+Scott Garrett <sgarrett at technomancer.com>
+ - tape plugin.
+
+Sebastien Pahl <sebastien.pahl at dotcloud.com>
+ - AMQP plugin.
+
+Simon Kuhnle <simon at blarzwurst.de>
+ - OpenBSD code for the cpu and memory plugins.
+
+Sjoerd van der Berg <harekiet at gmail.com>
+ - iptables plugin.
+
+Stefan Hacker <stefan.hacker at web.de>
+ - teamspeak2 plugin.
+
+Sven Trenkel <collectd at semidefinite.de>
+ - netapp plugin.
+ - python plugin.
+
+Tomasz Pala <gotar at pld-linux.org>
+ - conntrack plugin.
+
+Tommie Gannert <d00-tga at d.kth.se>
+ - PID-file patch.
+
+Vincent Stehlé <vincent.stehle at free.fr>
+ - hddtemp plugin.
+
+collectd is available at:
+  <http://collectd.org/>
+
+Enjoy :)
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..d511905
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/ChangeLog b/ChangeLog
new file mode 100644 (file)
index 0000000..2e6b6e9
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,1824 @@
+2012-01-21, Version 5.0.2
+       * curl_xml plugin: Fix handling of file:// and other URLs (which don't
+         follow HTTP status codes). Thanks to Fabien Wernli for his patch!
+       * df plugin: Fix handling of negative "available" counts. This can
+         occur with some file systems, for example UFS. Thanks to Toni Ylenius
+         for his patch.
+       * interface plugin: "mac" interfaces are now ignored on Solaris. These
+         pseudo-interfaces occur multiple times, causing warnings. Also switch
+         to 64-bit counters on Solaris, improving overflow behavior for
+         high-speed interfaces. Thanks to Eddy Geez and Fabien Wernli for
+         their patches.
+       * memory plugin: Account kernel and unused memory under Solaris. Thanks
+         to Fabien Wernli for his patch.
+       * network plugin: A bug in the interaction between the Network plugin
+         and filter chains has been fixed: When a filter modified a field such
+         as the hostname, subsequent values in the same network packets could
+         have ended up using the modified name rather than the original name.
+         Thanks to Sebastian Harl for identifying the problem.
+       * oracle plugin: A memory leak has been fixed in the parameter handling.
+       * python plugin: A memory leak has been fixed. Thanks to Sven Trenkel
+         for fixing this bug!
+
+2011-10-07, Version 5.0.1
+       * collectd: A mutex leak has been fixed in the meta data code. Thanks
+         to Rafal Lesniak for his patch.
+       * collectd: Compatibility fixes for GCC 4.6 have been applied. Thanks
+         to Peter Green for his patch.
+       * csv plugin: The line buffer size has been increased. Thanks to Colin
+         McCabe for the patch.
+       * curl_json plugin: Don't use the "parent" node to build the type
+         instance, if it is empty. Compatibility with libyajl 2 has been
+         added. Thanks to "spupykin" of the Arch Linux project for the initial
+         code. Formatting of time has been fixed in the JSON module.
+       * exec plugin: Fix the timestamp value passed to notification scripts.
+         Thanks to Alexander Kovalenko for fixing this.
+       * iptables plugin: Fix linking with some versions of libiptc.
+       * irq plugin: Fix support for interrupts under Linux. The old code
+         assumed that interrupts have a numeric value -- this is no longer
+         true for Linux. Thanks to Bostjan Skufca for implementing this.
+       * notify_desktop plugin: Compatibility with libnotify 0.7 has been
+         added. Thanks to Samuli Suominen for his patch.
+       * processes plugin: Fix handling of regular expressions containing
+         spaces. Thanks for Sebastian Harl for fixing this.
+       * rrdtool, rrdcached plugins: Improve precision of the XFF parameter.
+         Previously, values like 0.999 would have been rounded to 1.0. Thanks
+         to Francois-Xavier Bourlet for fixing this.
+       * varnish plugin: Fix data type handling of some metrics. Some values
+         were submitted as gauge even though they were derives.
+       * Various plugin: Set a multi-threading flag in libcurl. Thanks to Mike
+         Flisher for the fix.
+
+2011-03-28, Version 5.0.0
+       * collectd: The "FQDNLookup" option is now enabled by default.
+       * collectd: The internal representation of time has been changed to
+         allow a higher accuracy than one second.
+       * collectdcmd: This new command line utility can send various commands
+         to collectd using the UnixSock plugin. Thanks to Håkon Dugstad
+         Johnsen and Sebastian Harl for their code.
+       * collectd-nagios: The "-m" option has been implemented (treat NaNs as
+         critical).
+       * collectd-tg: Traffic generator creating bogus network traffic
+         compatible to the Network plugin. This utility can be used to
+         stress-test new write plugins and collectd in general.
+       * libcollectdclient: Creating and sending network packets has been
+         added to the collectd client library.
+       * All data sets: The data source name of all data sets with exactly
+         one data source has been changed to "value".
+       * All plugins: All "counter" data sources have been converted to
+         "derive" data sources. All plugins now use "derive" by default, but
+         plugins such as the network plugin can still handle "counter", of
+         course. The minimum value of all derive data sources is zero, the
+         maximum value is unspecified.
+       * amqp plugin: The new AMQP plugin can send data to and receive data
+         from an AMQP broker. Thanks to Sebastien Pahl for his code.
+       * apache plugin: Backwards compatibility code has been removed.
+         Support for the IBM HTTP Server has been added. Thanks to Manuel
+         Luis Sanmartín Rozada for his patch.
+       * contextswitch plugin: Support for sysctlbyname(3) has been added.
+         Thanks to Kimo Rosenbaum for his patch.
+       * df plugin: The default behavior has been changed to be equivalent to
+         the "ReportReserved" behavior of v4.
+       * dns plugin: Improved RFC 1035 name parsing has been imported from
+         "dnstop".
+       * exec plugin: Backwards compatibility code has been removed.
+       * GenericJMX plugin: The "InstancePrefix" option has been added to
+         "Connection" blocks.
+       * hddtemp plugin: The "TranslateDevicename" config option has been
+         removed.
+       * interface plugin: Use the "plugin instance" to store the interface
+         value.
+       * libvirt plugin: The "InterfaceFormat" option has been added. Thanks
+         to Ruben Kerkhof for his patch.
+       * lpar plugin: New plugins for "logical partitions", a virtualization
+         technique of POWER CPUs. Thanks to Aurélien Reynaud for his code and
+         patience.
+       * modbus plugin: Support for libmodbus 2.9.2 has been added and the
+         license has been changed to LGPLv2.1.
+       * mysql plugin: Backwards compatibility code has been removed. The
+         data sets used have been improved.
+       * network plugin: The default buffer size has been increased to
+         1452 bytes.
+       * perl plugin: Backwards compatibility code has been removed.
+       * postgresql plugin: Backwards compatibility code has been removed.
+       * redis plugin: Plugin for collecting statistics from Redis, a key-
+         value store, has been added. Thanks to Andres J. Diaz for his code.
+       * swap plugin: Implement collection of physical and virtual memory
+         statistics under Solaris. The new default is collecting physical
+         memory. Thanks to Aurélien Reynaud for his patches.
+       * threshold plugin: The threshold configuration has been moved into
+         this separate plugin.
+       * unixsock plugin: The "DeleteSocket" option has been added.
+       * varnish plugin: The new Varnish plugin reads statistics from
+         Varnish, a web accelerator. Thanks to Jérôme Renard and Marc
+         Fournier for their contributions.
+       * write_redis: New plugin for writing data to Redis, a key-value
+         store.
+       * zfs_arc plugin: The data sets have been replaced by more elegant
+         alternatives.
+       * v5upgrade target: Target for converting v4 data sets to the v5
+         schema.
+
+2012-01-21, Version 4.10.5
+       * curl_xml plugin: Fix handling of file:// and other URLs (which don't
+         follow HTTP status codes). Thanks to Fabien Wernli for his patch!
+       * df plugin: Fix handling of negative "available" counts. This can
+         occur with some file systems, for example UFS. Thanks to Toni Ylenius
+         for his patch.
+       * interface plugin: "mac" interfaces are now ignored on Solaris. These
+         pseudo-interfaces occur multiple times, causing warnings. Also switch
+         to 64-bit counters on Solaris, improving overflow behavior for
+         high-speed interfaces. Thanks to Eddy Geez and Fabien Wernli for
+         their patches.
+       * memory plugin: Account kernel and unused memory under Solaris. Thanks
+         to Fabien Wernli for his patch.
+       * network plugin: A bug in the interaction between the Network plugin
+         and filter chains has been fixed: When a filter modified a field such
+         as the hostname, subsequent values in the same network packets could
+         have ended up using the modified name rather than the original name.
+         Thanks to Sebastian Harl for identifying the problem.
+       * oracle plugin: A memory leak has been fixed in the parameter handling.
+       * python plugin: A memory leak has been fixed. Thanks to Sven Trenkel
+         for fixing this bug!
+
+2011-10-14, Version 4.10.4
+       * collectd: A mutex leak has been fixed in the meta data code. Thanks
+         to Rafal Lesniak for his patch.
+       * collectd: Compatibility fixes for GCC 4.6 have been applied. Thanks
+         to Peter Green for his patch.
+       * csv plugin: The line buffer size has been increased. Thanks to Colin
+         McCabe for the patch.
+       * curl_json plugin: Don't use the "parent" node to build the type
+         instance, if it is empty. Compatibility with libyajl 2 has been
+         added. Thanks to "spupykin" of the Arch Linux project for the initial
+         code.
+       * iptables plugin: Fix linking with some versions of libiptc.
+       * irq plugin: Fix support for interrupts under Linux. The old code
+         assumed that interrupts have a numeric value -- this is no longer
+         true for Linux. Thanks to Bostjan Skufca for implementing this.
+       * notify_desktop plugin: Compatibility with libnotify 0.7 has been
+         added. Thanks to Samuli Suominen for his patch.
+       * processes plugin: Fix handling of regular expressions containing
+         spaces. Thanks for Sebastian Harl for fixing this.
+       * rrdtool, rrdcached plugins: Improve precision of the XFF parameter.
+         Previously, values like 0.999 would have been rounded to 1.0. Thanks
+         to Francois-Xavier Bourlet for fixing this.
+       * Various plugin: Set a multi-threading flag in libcurl. Thanks to Mike
+         Flisher for the fix.
+
+2011-03-26, Version 4.10.3
+       * Documentation: Several updates and additions. Thanks to Sebastian Harl.
+       * collectd: Build issues (compiler warnings) have been fixed. Thanks to
+         Bruno Prémont.
+       * collectd: Threshold subsection: Handling of NAN values in the
+         percentage calculation has been fixed.
+       * collectd, java plugin, ntpd plugin: Several diagnostic messages have
+         been improved.
+       * curl_json plugin: Handling of arrays has been fixed.
+       * libvirt plugin: A bug in reading the virtual CPU statistics has been
+         fixed. Thanks to “JLPC” for reporting this problem.
+       * modbus plugin: Compatibility with libmodbus 2.0.3 has been restored.
+       * processes plugin: Potentially erroneous behavior has been fixed in an
+         error handling case.
+       * python plugin: Fix dispatching of values from Python scripts to
+         collectd. Thanks to Gregory Szorc for finding and fixing this
+         problem.
+
+2010-11-27, Version 4.10.2
+       * Documentation: Various documentation fixes.
+       * collectd: If including one configuration file fails, continue with
+         the rest of the configuration if possible.
+       * collectd: Fix a bug in the read function scheduling. In rare cases
+         read functions may not have been called as often as requested.
+       * collectd: Concurrency issues with errno(3) under AIX have been
+         fixed: A thread-safe version of errno has to be requested under AIX.
+         Thanks to Aurélien Reynaud for his patch.
+       * collectd: A left-over hard-coded 2 has been replaced by the
+         configurable timeout value.
+       * curl, memcachec, tail plugins: Fix handling of "DERIVE" data
+         sources. Matching the end of a string has been improved; thanks to
+         Sebastian Harl for the patch.
+       * curl_json plugin: Fix a problem when parsing 64bit integers. Reading
+         JSON data from non-HTTP sources has been fixed.
+       * netapp plugin: Pass the interval setting to the dispatch function.
+         Restore compatibility to NetApp Release 7.3. Thanks to Sven Trenkel
+         for the patch.
+       * network plugin: Be less verbose about unchecked signatures, in order
+         to prevent spamming the logs.
+       * notify_email plugin: Concurrency problems have been fixed.
+       * python plugin: Set "sys.argv", since many scripts don't expect that
+         it may not be set. Thanks to Sven Trenkel for the patch.
+       * rrdtool, rrdcached plugin: Fix a too strict assertion when creating
+         RRD files.
+       * swap plugin: A bug which lead to incorrect I/O values has been
+         fixed.
+       * value match: A minor memory leak has been fixed. Thanks to Sven
+         Trenkel for the patch.
+
+2010-07-09, Version 4.10.1
+       * Build system: Checking for "strtok_r" under Solaris has been fixed.
+       * Portability: Fixes for Solaris 8 have been applied. Thanks to
+         Alexander Wuerstlein for his patch.
+       * collectd: The shutdown speed when terminating the read threads has
+         been improved.
+       * libcollectdclient: A format error in the PUTVAL command has been
+         removed. Thanks to Johan Van den Brande for fixing this.
+       * df plugin: An error message shown when "cu_mount_getlist" fails has
+         been added.
+       * processes plugin: Missing initialization code for IO members of a
+         struct has been added. Thanks to Aurélien Reynaud for fixing this.
+       * python plugin: Memory leaks in the write and notification callbacks
+         have been fixed. A possible crash when the plugin was loaded but not
+         configured has been fixed. Thanks to Sven Trenkel for his patches.
+       * snmp plugin: Verbosity with regard to unknown ASN types has been
+         increased. A build problem on PowerPC and ARM processors has been
+         fixed by Aurélien Reynaud; thanks!
+       * powerdns plugin: Compatibility changes for PowerDNS 2.9.22 and above
+         have been applied. Thanks to Luke Heberling for his changes.
+
+2010-05-01, Version 4.10.0
+       * collectd: JSON output now includes the "dstypes" and "dsnames"
+         fields. This makes it easier for external applications to interpret
+         the data. Thanks to Chris Buben for his work.
+       * collectd: The new "Timeout" option can be used to specify a
+         "timeout" for missing values. This is used in the threshold checking
+         code to detect missing values. Thanks to Andrés J. Díaz for the
+         patch.
+       * apache plugin: Support for "IdleWorkers" (Apache 1.*: "IdleServers")
+         has been added.
+       * curl plugin: The new "ExcludeRegex" allows to easily exclude certain
+         lines from the match.
+       * curl_xml plugin: This new plugin allows to read XML files using cURL
+         and extract metrics included in the files. Thanks to Amit Gupta for
+         his work.
+       * filecount plugin: The new "IncludeHidden" option allows to include
+         "hidden" files and directories in the statistics. Thanks to Vaclav
+         Malek for the patch.
+       * logfile plugin: The new "PrintSeverity" option allows to include the
+         severity of a message in the output. Thanks to Clément Stenac for
+         his patch.
+       * memcachec plugin: The new "ExcludeRegex" allows to easily exclude
+         certain lines from the match.
+       * modbus plugin: This new plugin allows to read registers from
+         Modbus-TCP enabled devices.
+       * network plugin: The new "Interface" option allows to set the
+         interface to be used for multicast and, if supported, unicast
+         traffic. Thanks to Max Henkel for his work.
+       * openvpn plugin: The "CollectUserCount" and "CollectIndividualUsers"
+         options allow more detailed control over how to report sessions of
+         multiple users. Thanks to Fabian Schuh for his work.
+       * pinba plugin: This new plugin receives timing information from the
+         Pinba PHP extension, which can be used for profiling PHP code and
+         webserver performance. Thanks to Phoenix Kayo for his work.
+       * ping plugin: The new "MaxMissed" allows to re-resolve a hosts
+         address when it doesn't reply to a number of ping requests. Thanks
+         to Stefan Völkel for the patch.
+       * postgresql plugin: The "Interval" config option has been added. The
+         plugin has been relicensed under the 2-clause BSD license. Thanks to
+         Sebastian Harl for his work.
+       * processes plugin: Support for "code" and "data" virtual memory sizes
+         has been added. Thanks to Clément Stenac for his patch.
+       * python plugin: Support for Python 3 has been implemented. Thanks to
+         Sven Trenkel for his work.
+       * routeros plugin: Support for collecting CPU load, memory usage, used
+         and free disk space, sectors written and number of bad blocks from
+         MikroTik devices has been added.
+       * swap plugin: Support for Linux < 2.6 has been added. Thanks to Lorin
+         Scraba for his patch.
+       * tail plugin: The new "ExcludeRegex" allows to easily exclude certain
+         lines from the match. Thanks to Peter Warasin for his patch.
+       * write_http plugin: The "StoreRates" option has been added. Thanks to
+         Paul Sadauskas for his patch.
+       * regex match: The "Invert" option has been added. Thanks to Julien
+         Ammous for his patch.
+
+2011-03-26, Version 4.9.5
+       * Documentation: Several updates and additions. Thanks to Sebastian Harl.
+       * collectd: Build issues (compiler warnings) have been fixed. Thanks to
+         Bruno Prémont.
+       * collectd: Threshold subsection: Handling of NAN values in the
+         percentage calculation has been fixed.
+       * collectd, java plugin, ntpd plugin: Several diagnostic messages have
+         been improved.
+       * libvirt plugin: A bug in reading the virtual CPU statistics has been
+         fixed. Thanks to “JLPC” for reporting this problem.
+       * processes plugin: Potentially erroneous behavior has been fixed in an
+         error handling case.
+       * python plugin: Fix dispatching of values from Python scripts to
+         collectd. Thanks to Gregory Szorc for finding and fixing this
+         problem.
+
+2010-11-27, Version 4.9.4
+       * Documentation: Various documentation fixes.
+       * collectd: If including one configuration file fails, continue with
+         the rest of the configuration if possible.
+       * collectd: Fix a bug in the read function scheduling. In rare cases
+         read functions may not have been called as often as requested.
+       * collectd: Concurrency issues with errno(3) under AIX have been
+         fixed: A thread-safe version of errno has to be requested under AIX.
+         Thanks to Aurélien Reynaud for his patch.
+       * curl, memcachec, tail plugins: Fix handling of "DERIVE" data
+         sources. Matching the end of a string has been improved; thanks to
+         Sebastian Harl for the patch.
+       * curl_json plugin: Fix a problem when parsing 64bit integers. Reading
+         JSON data from non-HTTP sources has been fixed.
+       * netapp plugin: Pass the interval setting to the dispatch function.
+         Restore compatibility to NetApp Release 7.3. Thanks to Sven Trenkel
+         for the patch.
+       * network plugin: Be less verbose about unchecked signatures, in order
+         to prevent spamming the logs.
+       * notify_email plugin: Concurrency problems have been fixed.
+       * python plugin: Set "sys.argv", since many scripts don't expect that
+         it may not be set. Thanks to Sven Trenkel for the patch.
+       * rrdtool, rrdcached plugin: Fix a too strict assertion when creating
+         RRD files.
+       * value match: A minor memory leak has been fixed. Thanks to Sven
+         Trenkel for the patch.
+
+2010-07-09, Version 4.9.3
+       * Build system: Checking for "strtok_r" under Solaris has been fixed.
+       * Portability: Fixes for Solaris 8 have been applied. Thanks to
+         Aurélien Reynaud and Alexander Wuerstlein for their patches.
+       * collectd: The shutdown speed when terminating the read threads has
+         been improved.
+       * collectd-nagios: The format of the performance data has been fixed.
+       * libcollectdclient: A format error in the PUTVAL command has been
+         removed. Thanks to Johan Van den Brande for fixing this.
+       * df plugin: An error message shown when "cu_mount_getlist" fails has
+         been added.
+       * processes plugin: Missing initialization code for IO members of a
+         struct has been added. Thanks to Aurélien Reynaud for fixing this.
+       * python plugin: Memory leaks in the write and notification callbacks
+         have been fixed. A possible crash when the plugin was loaded but not
+         configured has been fixed. Thanks to Sven Trenkel for his patches.
+       * rrdcached plugin: A build issue has been resolved. Thanks to
+         Thorsten von Eicken for the patch.
+       * snmp plugin: Verbosity with regard to unknown ASN types has been
+         increased. A build problem on PowerPC and ARM processors has been
+         fixed by Aurélien Reynaud; thanks!
+       * powerdns plugin: Compatibility changes for PowerDNS 2.9.22 and above
+         have been applied. Thanks to Luke Heberling for his changes.
+
+2010-04-22, Version 4.9.2
+       * Build system, various plugins: Fixes for AIX compatibility have been
+         added. Thanks to Manuel Sanmartin for his patches.
+       * Build system: Checking for "nanosleep" on old Solaris machines has
+         been fixed. Thanks to Vincent McIntyre and Sebastian Harl for
+         figuring out a way to make this work.
+       * collectd: Append a newline to messages written to STDERR.
+       * collectd: Serialization of NANs in JSON format has been fixed.
+         Thanks to Chris Buben for pointing out the resulting syntax error.
+       * collectd: Checks whether a "sleep" returned early have been added;
+         the cases are now handled correctly. Thanks to Michael Stapelberg
+         for the patch.
+       * collectd: Continue reading files in a directory when parsing one
+         file fails.
+       * apache plugin: Collection of the number of active connections has
+         been fixed for Apache 2.*.
+       * contextswitch plugin: Handle large counter/derive values correctly.
+         Thanks to Martin Merkel for reporting the bug.
+       * exec plugin: Error messages have been improved. The "running" flag
+         is now cleared correctly when forking a child fails.
+       * iptables plugin: Fix a violation of aliasing rules. This resolves a
+         warning / error with new GCC versions. Thanks to Jan Engelhardt for
+         the work-around.
+       * java plugin: The Java API files are now packaged into a .jar file.
+         Thanks to Amit Gupta for his patch.
+       * network plugin: Fix a segmentation fault when receiving packets with
+         an unknown data source type.
+       * network plugin: A memory leak when receiving encrypted network
+         packets has been fixed.
+       * openvpn plugin: Fix naming schema when reading "MULTI1" type status
+         files.
+       * oracle plugin: Fix checking for lost connections and reconnect in
+         this case. Thanks to Sven Trenkel for pointing out the problem.
+       * unixsock plugin: A memory leak in the "LISTVAL" command has been
+         fixed. Thanks to Peter Warasin for pointing it out.
+       * write_http plugin: Use the "any" authentication schema. This used to
+         be "digest". Thanks to Paul Sadauskas for the patch.
+
+2010-01-14, Version 4.9.1
+       * Documentation: Some manpage fixes.
+       * Default config: Added sample configuration for missing plugins.
+       * apache plugin: Fix a segmentation fault in the config handling of
+         VerifyPeer / VerifyHost. Thanks to "plazmus" for his or her patch.
+       * processes plugin: Fix handling of derive data sources.
+       * rrdtool plugin: Fix a bug with random write timeouts. Due to an
+         incorrect initialization some files may be suspended basically
+         indefinitely. After flushing the files they were written regularly
+         again.
+       * routeros plugin: Use the node name for the "host" field.
+       * Monitorus.pm: Put the plugin into the "Collectd::Plugins" namespace.
+       * Perl bindings: Fix a warning that was printed when building
+         debugging output.
+
+2009-12-21, Version 4.9.0
+       * contextswitch plugin: The new ContextSwitch plugin gathers the
+         number of context switches done by the CPU. Thanks to Patrik
+         Weiskircher for the patch.
+       * cpu plugin: Support for SMP (multiple processors) under FreeBSD has
+         been added. Thanks to Doug MacEachern for the patch.
+       * curl plugin: The “MeasureResponseTime” option has been added. Thanks
+         to Aman Gupta for the patch.
+       * df plugin: Collecting the inode count and reserved space has been
+         added. Thanks to Patrik Weiskircher for the patch.
+       * exec plugin: The environment variables “COLLECTD_INTERVAL” and
+         “COLLECTD_HOSTNAME” are now set before executing the application.
+       * Monitorus plugin: This Perl-based plugin to query statistics from
+         mon.itor.us has been added. Thanks to Jeff Green for the patch.
+       * netapp plugin: New plugin to collect statistics from NetApp filers.
+         Thanks to Sven Trenkel of the noris network AG for the patch.
+       * network plugin: Statistics collection about the plugin itself has
+         been implemented.
+       * openvpn plugin: Add support for more versions of the “status file”.
+         Thanks to Marco Chiappero for the patch.
+       * OpenVZ plugin: This Perl-based plugin to gather OpenVZ statistics
+         has been added. Thanks to Jonathan Kolb for the patch.
+       * ping plugin: The config options "SourceAddress" and "Device"
+         have been added. Thanks to Sebastian Harl for the patch.
+       * processes plugin: Collection of IO-metrics has been added. Thanks to
+         Andrés J. Díaz for the patch.
+       * python plugin: The new Python plugin integrates a Python interpreter
+         into collectd and allows to execute plugins written in the scripting
+         language. Thanks to Sven Trenkel for his work.
+       * routeros plugin: The new RouterOS plugin queries interface and
+         wireless registration statistics from RouterOS.
+       * Various plugins: AIX support has been added to the cpu, disk,
+         interface, load, memory, processes, and swap plugins. Thanks to
+         Manuel Sanmartin for his patches.
+       * hashed match: This match for simple load balancing and redundant
+         storage has been added.
+       * scale target: This target to scale (multiply) values by an arbitrary
+         value has been added.
+
+2010-04-22, Version 4.8.5
+       * collectd: Append a newline to messages written to STDERR.
+       * network plugin: Fix a segmentation fault when receiving packets with
+         an unknown data source type.
+
+2010-04-07, Version 4.8.4
+       * Build system, various plugins: Fixes for AIX compatibility have been
+         added. Thanks to Manuel Sanmartin for his patches.
+       * Build system: Checking for "nanosleep" on old Solaris machines has
+         been fixed. Thanks to Vincent McIntyre and Sebastian Harl for
+         figuring out a way to make this work.
+       * collectd: Serialization of NANs in JSON format has been fixed.
+         Thanks to Chris Buben for pointing out the resulting syntax error.
+       * collectd: Checks whether a "sleep" returned early have been added;
+         the cases are now handled correctly. Thanks to Michael Stapelberg
+         for the patch.
+       * collectd: Continue reading files in a directory when parsing one
+         file fails.
+       * apache plugin: Collection of the number of active connections has
+         been fixed for Apache 2.*.
+       * exec plugin: Error messages have been improved. The "running" flag
+         is now cleared correctly when forking a child fails.
+       * iptables plugin: Fix a violation of aliasing rules. This resolves a
+         warning / error with new GCC versions. Thanks to Jan Engelhardt for
+         the work-around.
+       * java plugin: The Java API files are now packaged into a .jar file.
+         Thanks to Amit Gupta for his patch.
+       * network plugin: A memory leak when receiving encrypted network
+         packets has been fixed.
+       * oracle plugin: Fix checking for lost connections and reconnect in
+         this case. Thanks to Sven Trenkel for pointing out the problem.
+       * unixsock plugin: A memory leak in the "LISTVAL" command has been
+         fixed. Thanks to Peter Warasin for pointing it out.
+       * write_http plugin: Use the "any" authentication schema. This used to
+         be "digest". Thanks to Paul Sadauskas for the patch.
+
+2010-01-14, Version 4.8.3
+       * Documentation: Some manpage fixes.
+       * rrdtool plugin: Fix a bug with random write timeouts. Due to an
+         incorrect initialization some files may be suspended basically
+         indefinitely. After flushing the files they were written regularly
+         again.
+
+2009-12-18, Version 4.8.2
+       * Build system, java plugin: Don't use “find -L” to search for Java
+         headers, because it's a GNU extension.
+       * Build system: Support for parallel builds has been improved. Thanks
+         Sebastian Harl and Stefan Völkel for looking into this.
+       * collectd: Print error messages to STDERR if no log plugin has been
+         loaded.
+       * genericjmx plugin: Close and re-open the connection upon I/O-errors.
+       * gmond plugin: Fix typos which caused syntax errors.
+       * memory plugin: Handling of >4 Gbyte of memory has been fixed.
+       * network plugin: The license has been changed to LGPL 2.1.
+       * oracle plugin: Reconnect to the database if the connection dies.
+       * rrdcached plugin: Work-around for a bug in RRDtool 1.4rc2 has been
+         added.
+       * snmp plugin: Handling of negative values has been fixed. Strings
+         containing control characters are now interpreted as hex-strings.
+       * unixsock plugin: A memory leak in the LISTVAL command has been
+         fixed. Thanks to Ben Knight for his patch.
+
+2009-10-04, Version 4.8.1
+       * Build system: Issues when building the iptables plugin have been
+         fixed.
+       * exec plugin: Clear the signal block mask before calling exec(2).
+       * perl plugin: Declare the “environ” variable. This solves build
+         issues on some platforms.
+       * processes plugin: Remove unnecessary call of realloc(3). Thanks to
+         Andrés J. Díaz for the patch.
+       * unixsock plugin: Fix a (well hidden) race condition related to file
+         descriptor handling.
+
+2009-09-13, Version 4.8.0
+       * collectd: Two new data source types, “DERIVE” and “ABSOLUTE”, have
+         been added. “DERIVE” can be used for counters that are reset
+         occasionally. Thanks to Mariusz Gronczewski for implementing this.
+       * thresholds: The advanced threshold options “Percentage”, “Hits”, and
+         “Hysteresis” have been added. Thanks to Andrés J. Díaz for his
+         patches.
+       * curl_json plugin: The new cURL-JSON plugin reads JSON files using
+         the cURL library and parses the contents according to user
+         specification. Among other things, this allows to read statistics
+         from a CouchDB instance. Thanks to Doug MacEachern for the patch.
+       * df plugin: Using the new “ReportByDevice” option the device rather
+         than the mount point can be used to identify partitions. Thanks to
+         Paul Sadauskas for the patch.
+       * dns plugin: The possibility to ignore numeric QTypes has been added.
+         Thanks to Mirko Buffoni for the patch.
+       * GenericJMX plugin: The new, Java-based GenericJMX plugin allows to
+         query arbitrary data from a Java process using the “Java Management
+         Extensions” (JMX).
+       * madwifi plugin: The new MadWifi plugin collects information about
+         Atheros wireless LAN chipsets from the MadWifi driver. Thanks to
+         Ondrej Zajicek for his patches.
+       * network plugin: The receive- and send-buffer-sizes have been made
+         configurable, allowing for bigger and smaller packets. Thanks to
+         Aman Gupta for the patch.
+       * olsrd plugin: The new OLSRd plugin queries routing information from
+         the “Optimized Link State Routing” daemon.
+       * rrdtool plugin: A new configuration option allows to define a random
+         write delay when writing RRD files. This spreads the load created by
+         writing RRD files more evenly. Thanks to Mariusz Gronczewski for the
+         patch.
+       * swap plugin: The possibility to collect swapped in/out pages has
+         been added to the Swap plugin. Thanks to Stefan Völkel for the
+         patch.
+       * tokyotyrant plugin: The new TokyoTyrant plugin reads the number of
+         records and file size from a running Tokyo Tyrant server. Thanks to
+         Paul Sadauskas for the patch.
+       * unixsock plugin: Add the “GETTHRESHOLD” command. This command can be
+         used to query the thresholds configured for a particular identifier.
+       * write_http plugin: The new Write HTTP plugin sends the values
+         collected by collectd to a web-server using HTTP POST requests.
+         Thanks to Paul Sadauskas for the patch.
+       * zfs_arc plugin: The new ZFS ARC plugin collects information about
+         the “Adaptive Replacement Cache” (ARC) of the “Zeta File-System”
+         (ZFS). Thanks to Anthony Dewhurst for the patch.
+       * empty_counter match: The new Empty Counter match matches value
+         lists, where at least one data source is of type COUNTER and the
+         counter value of all counter data sources is zero.
+
+2009-12-18, Version 4.7.5
+       * Build system, java plugin: Don't use “find -L” to search for Java
+         headers, because it's a GNU extension.
+       * Build system: Support for parallel builds has been improved. Thanks
+         Sebastian Harl and Stefan Völkel for looking into this.
+       * collectd: Print error messages to STDERR if no log plugin has been
+         loaded.
+       * memory plugin: Handling of >4 Gbyte of memory has been fixed.
+       * network plugin: The license has been changed to LGPL 2.1.
+       * oracle plugin: Reconnect to the database if the connection dies.
+       * rrdcached plugin: Work-around for a bug in RRDtool 1.4rc2 has been
+         added.
+       * snmp plugin: Handling of negative values has been fixed. Strings
+         containing control characters are now interpreted as hex-strings.
+       * unixsock plugin: A memory leak in the LISTVAL command has been
+         fixed. Thanks to Ben Knight for his patch.
+
+2009-10-03, Version 4.7.4
+       * Build system: Issues when building the iptables plugin have been
+         fixed.
+       * exec plugin: Clear the signal block mask before calling exec(2).
+       * perl plugin: Declare the “environ” variable. This solves build
+         issues on some platforms.
+       * processes plugin: Remove unnecessary call of realloc(3). Thanks to
+         Andrés J. Díaz for the patch.
+       * unixsock plugin: Fix a (well hidden) race condition related to file
+         descriptor handling.
+
+2009-09-13, Version 4.7.3
+       * collectd: Fix a possible but very rare invalid “free” in the caching
+         code. Thanks to Sebastian Harl for the patch.
+       * collectd: Remove old values when a cache entry is marked as missing.
+         This way the “GETVAL” command of the UnixSock plugin doesn't return
+         old, no longer valid values when this happens. Thanks to Andrés J.
+         Díaz for the patch.
+       * collectd: The “plugin_unregister_read” function has been fixed.
+       * apache, ascent, bind, curl, nginx plugins: Advise the cURL library
+         to follow redirects. Thanks to Joey Hess for reporting this bug.
+       * df plugin: Check the ignorelist before stating the file system,
+         possibly reducing the number of stats considerably. Thanks to Joey
+         Hess for reporting this bug.
+       * iptables plugin: Support for the new libiptc API has been added.
+         Thanks to Sebastian Harl for the patch. The build system has been
+         updated to the plugin only includes the shipped header files when it
+         is linked with the shipped library, too.
+       * java plugin: Delay creating the JVM until after the daemon has
+         forked. The JVM internally creates threads that are lost when
+         forking. This means that Java-based plugins are now configured
+         during the init-phase, i. e. later than other plugins.
+       * libvirt plugin: Re-connect to libvirtd if connecting fails. Thanks
+         to Alan Pevec for the patch.
+       * network plugin: Fix the handling of the “CacheFlush” option: The
+         value was assigned to a wrong variable. The initialization of the
+         gcrypt library, which is used for signing / encrypting traffic, has
+         been fixed. Thanks to Luke Heberling for the patch.
+       * powerdns plugin: Set a timeout when reading data from the datagram
+         socket. Handling of the “LocalSocket” option has been fixed.  An
+         incorrectly used “type” has been corrected. Thanks to Luke Heberling
+         for his patches.
+
+2009-07-19, Version 4.7.2
+       * Build system: Support for `DESTDIR' has been fixed in the Java
+         bindings.
+       * collectd: Okay-notifications have been fixed. Thanks to Andrés J.
+         Díaz for fixing this bug.
+       * collectd: A programming error has been fixed in the notification
+         code. The bug may result in an assertion failure.
+       * memcached plugin: Portability fix for Solaris. Thanks to Amit Gupta
+         for reporting the bug.
+       * ping plugin: Link the plugin with libm.
+
+2009-06-02, Version 4.7.1
+       * Build system: Detection of Java has been improved and missing
+         details have been added to the configuration summary. Support for
+         libtool 2.2 has been added.
+       * collectd: Two bugs with the threshold checking have been fixed. The
+         first one prevented thresholds to be checked at all, the second one
+         caused wrong behavior with the persistency option. Thanks to Andrés
+         J. Díaz for fixing these problems.
+       * collectd: Handling of the `Include' configuration option has been
+         fixed.
+       * rrdtool plugin: Make sure initialization is run only once. This
+         resolves problems under Solaris and potentially other systems.
+         Thanks to Amit Gupta for reporting this bug.
+       * java plugin: Make it possible to use dots ('.') instead of slashes
+         ('/') as the class separator. Thanks to Randy Rizun for pointing
+         this out.
+       * swap plugin: A work-around for 32-bit Solaris has been added. Thanks
+         to Doug MacEachern for the patch.
+
+2009-05-11, Version 4.7.0
+       * apache plugin: Support to query multiple servers has been added.
+         Thanks to Amit Gupta for the patch.
+       * apache plugin: Handling of lighttpd's scoreboard statistics has been
+         improved. Thanks to Amit Gupta for the patch.
+       * conntrack plugin: The new conntrack plugin collects the connection
+         tracking table size. Thanks to Tomasz Pala for the patch.
+       * fscache plugin: The new fscache plugin collects statistics about
+         Linux' file-system based caching framework. Thanks to Edward
+         Konetzko for the patch.
+       * gmond plugin: The new gmond plugin can receive and interpret
+         multicast traffic from Ganglia's gmond daemon.
+       * java plugin: The new java plugin exports the collectd API to Java,
+         making it possible to write extensions to collectd in Java.
+       * memcachec plugin: The new memcachec plugin queries data from a
+         memcached daemon and parses it similar to the cURL plugin. Thanks to
+         Doug MacEachern for the initial code.
+       * memcached plugin: Support for connections over UNIX domain sockets
+         has been added. Thanks to Franck Lombardi for the patch.
+       * memory plugin: Support for OpenBSD and possibly other *BSDs has been
+         added. Thanks to Simon Kuhnle for the patch.
+       * mysql plugin: Support to query multiple databases has been added.
+         Thanks to Doug MacEachern for the patch.
+       * mysql plugin: Master/slave statistics have been added.
+       * mysql plugin: Lock statistics have been added. Thanks to Rodolphe
+         Quiédeville for the patch.
+       * network plugin: The possibility to sign or encrypt network traffic
+         has been added.
+       * protocols plugin: The new protocols plugin provides information
+         about network protocols, such as IP, TCP and UDP.
+       * snmp plugin: The intervals given in the configuration of the SNMP
+         plugin must no longer be a multiple of the global interval.
+       * table plugin: The new Table plugin provides parsing for table-like
+         structured files, such as many files beneath /proc.
+       * ted plugin: The new TED plugin reads power consumption measurements
+         from “The Energy Detective” (TED). Thanks to Eric Reed for this
+         plugin.
+       * onewire plugin: The new `Interval' option allows collecting
+         information from OneWire sensors at arbitrary intervals.
+       * ping plugin: Support for collecting the drop rate and standard
+         deviation of round-trip times has been added.
+       * uptime plugin: The new uptime plugin can collect the server's
+         uptime. Thanks to Marco Chiappero for the patch.
+
+2009-09-10, Version 4.6.5
+       * collectd: Remove old values when a cache entry is marked as missing.
+         This way the “GETVAL” command of the UnixSock plugin doesn't return
+         old, no longer valid values when this happens. Thanks to Andrés J.
+         Díaz for the patch.
+       * apache, ascent, bind, curl, nginx plugins: Advise the cURL library
+         to follow redirects. Thanks to Joey Hess for reporting this bug.
+       * df plugin: Check the ignorelist before stating the file system,
+         possibly reducing the number of stats considerably. Thanks to Joey
+         Hess for reporting this bug.
+       * iptables plugin: Support for the new libiptc API has been added.
+         Thanks to Sebastian Harl for the patch. The build system has been
+         updated to the plugin only includes the shipped header files when it
+         is linked with the shipped library, too.
+       * libvirt plugin: Re-connect to libvirtd if connecting fails. Thanks
+         to Alan Pevec for the patch.
+       * powerdns plugin: Set a timeout when reading data from the datagram
+         socket. Handling of the “LocalSocket” option has been fixed.  An
+         incorrectly used “type” has been corrected. Thanks to Luke Heberling
+         for his patches.
+
+2009-07-18, Version 4.6.4
+       * collectd: Okay-notifications have been fixed. Thanks to Andrés J.
+         Díaz for fixing this bug.
+       * collectd: A programming error has been fixed in the notification
+         code. The bug may result in an assertion failure.
+       * memcached plugin: Portability fix for Solaris. Thanks to Amit Gupta
+         for reporting the bug.
+
+2009-06-02, Version 4.6.3
+       * Build system, various plugins: Many build fixes for FreeBSD,
+         OpenBSD, NetBSD, Solaris and Mac OS X. Big thanks to Doug MacEachern
+         for many fixes and providing a build system for many platforms,
+         Ulf Zimmermann for providing a FreeBSD system and Simon Kuhnle for
+         providing an OpenBSD system.
+       * collectd: Two bugs with the threshold checking have been fixed. The
+         first one prevented thresholds to be checked at all, the second one
+         caused wrong behavior with the persistency option. Thanks to Andrés
+         J. Díaz for fixing these problems.
+       * collectd: Handling of the `Include' configuration option has been
+         fixed.
+       * battery plugin: Don't complain about a missing directory every
+         interval.
+       * exec plugin: Allow executed programs to close STDERR. Thanks to
+         Thorsten von Eicken for reporting this problem.
+       * irq plugin: Fix handling of overflowing 32-bit counters. Thanks to
+         Tomasz Pala for the patch.
+       * perl plugin: Portability build-fixes. Thanks to Doug MacEachern for
+         the patch.
+       * memory plugin: Fix a potential problem under Solaris.
+       * swap plugin: A work-around for 32-bit Solaris has been added. Thanks
+         to Doug MacEachern for the patch.
+
+2009-03-18, Version 4.6.2
+       * collectd: Some Solaris utility code has been improved.
+       * filter subsystem: Allow `Chains' without default targets.
+       * liboping: A patch to comply with strict aliasing rules has been
+         added.
+       * timediff match: Fix a typo: The match was registered with a wrong
+         name which prevented this match to be used as documented. Thanks to
+         Bruno Prémont for finding this problem.
+       * bind plugin: Fix collection of the cached RR sets. The number of RR
+         sets currently in the cache was collected as a counter value, which
+         is nonsense. Thanks to Bruno Prémont for implementing this.
+       * dns plugin: Don't pass NULL to `pcap_open_live': Some systems,
+         primarily BSDs, don't take it well and crash.
+       * oracle plugin: Portability to 64 bit systems has been improved.
+       * postgresql plugin: The default configuration has been improved.
+       * rrdtool plugin: Fix a possible race condition: If the network plugin
+         is brought and dispatches a value before the rrdtool plugin is
+         initialized, the daemon may crash.
+
+2009-02-22, Version 4.6.1
+       * collectd: Many documentation fixes.
+       * Collectd::Unixsock: Error handling has been improved.
+       * regex match: Don't link with the PCRE library.
+       * bind plugin: Various bugs have been fixed. Thanks to Bruno Prémont
+         for finding and fixing most of them.
+       * ipmi plugin: Fix an off-by-one error which could cause segmentation
+         faults. Thanks to Peter Holik for his patch.
+
+2009-02-16, Version 4.6.0
+       * collectd: Added the `filter chain' infrastructure, which allows the
+         user to use `matches' and `targets' to control value processing.
+       * collectd: The new `-T' command line argument allows more in-depth
+         testing of a configuration. Thanks to Doug MacEachern for the patch.
+       * collectd-nagios: The Nagios integration command has been updated to
+         use libcollectdclient. The `percentage' aggregation function has
+         been added. Thanks to Fabian Linzberger for the patch.
+       * libcollectdclient: A library which abstracts communication with the
+         unixsock plugin for clients has been added.
+       * regex match: Match values by their identifies using regular
+         expressions.
+       * timediff match: Match for values with an invalid timestamp.
+       * value match: Select values by their data sources' values.
+       * notification target: Create and dispatch a notification.
+       * replace target: Replace parts of an identifier using regular
+         expressions.
+       * set target: Set (overwrite) entire parts of an identifier.
+       * bind plugin: This new plugin uses the new HTTP/XML interface to BIND
+         statistics, allowing very detailed name server statistics. Thanks to
+         Bruno Prémont for this plugin.
+       * cpu plugin: Report `interrupt' separately when using
+         sysctlbyname(3) (used under *BSD). Support for sysctl(3), for
+         example for native OpenBSD support, has been added. Thanks to Simon
+         Kuhnle for the patch.
+       * csv plugin: Make it possible to write values to STDOUT instead of
+         files. This is meant for testing purposes mostly. The output written
+         to STDOUT is compatible with the exec plugin. Thanks to Doug
+         MacEachern for the patch.
+       * curl plugin: This new plugin can be used to read web pages and parse
+         them using the same mechanism that's used in the tail plugin.
+       * dbi plugin: This new plugin allows you to connect to a variety of
+         relational databases and use SQL to gather custom statistics from
+         it. It is similar to the already existing PostgreSQL plugin but uses
+         libdbi to communicate with the database(s).
+       * interface plugin: Use the ignorelist framework when selecting /
+         ignoring interfaces. This allows one to use regular expressions to
+         select interfaces, too.
+       * ipmi plugin: Handle temporary IPMI error conditions more gracefully.
+         Thanks to Bruno Prémont for this patch.
+       * memcached plugin: Add hit-ratio metric. Thanks to Doug MacEachern
+         for the patch.
+       * mysql plugin: Allow connecting to a database via the UNIX domain
+         socket, too. Thanks to Mirko Buffoni for the patch.
+       * network plugin: Further performance improvements for the receive
+         code. This hopefully will help very large setups.
+       * openvpn plugin: This new plugin collects statistics provided by the
+         OpenVPN daemon. Thanks to Doug MacEachern for the patch.
+       * oracle plugin: This new plugin allows you to connect to an Oracle
+         database and use SQL to gather custom statistics from it. It is
+         similar to the already existing PostgreSQL plugin.
+       * perl plugin: Compatibility fixes for broken versions of Perl 5.10
+         have been added.
+       * perl plugin: Export the newly added plugin_write() to Perl plugins.
+       * perl plugin: Added support for `notification meta data'.
+       * perl plugin: Added support for the `filter chain' infrastructure by
+         allowing plugins to register `matches' and `targets'.
+       * postgresql plugin: The preferred configuration syntax has been
+         updated to be in line with the syntax used by the new dbi and oracle
+         plugins. The compatibility code for the old syntax is present.
+         Support for the new `Result' blocks and the interval parameter has
+         been added.
+       * processes plugin: Stacksize and virtual memory usage statistics have
+         been added. Portability fixes.
+       * rrdcached plugin: This new plugin uses the (still in development)
+         RRD accelerator daemon, rrdcached. This daemon works very similar to
+         the original rrdtool plugin of collectd, but adds some more nice
+         features.
+       * swap plugin: Code for OpenBSD (and possibly other *BSDs) has been
+         added.
+
+2009-05-09, Version 4.5.4
+       * Build system, various plugins: Many build fixes for FreeBSD,
+         OpenBSD, NetBSD, Solaris and Mac OS X. Big thanks to Doug MacEachern
+         for many fixes and providing a build system for many platforms,
+         Ulf Zimmermann for providing a FreeBSD system and Simon Kuhnle for
+         providing an OpenBSD system.
+       * collectd: Fix a potential race condition when creating directories.
+       * battery plugin: Don't complain about a missing directory every
+         interval.
+       * dns plugin: Slight portability fixes.
+       * exec plugin: Allow executed programs to close STDERR. Thanks to
+         Thorsten von Eicken for reporting this problem.
+       * irq plugin: Fix handling of overflowing 32-bit counters. Thanks to
+         Tomasz Pala for the patch.
+       * perl plugin: Portability build-fixes. Thanks to Doug MacEachern for
+         the patch.
+       * rrdtool plugin: Fix a possible race condition: If the network plugin
+         is initialized and dispatches a value before the rrdtool plugin is
+         initialized, the daemon may crash.
+       * memory plugin: Fix a potential problem under Solaris.
+
+2009-02-22, Version 4.5.3
+       * build system: The check for libupsclient even when `pkg-config' is
+         not available.
+       * collectd: Fix error handling in the global cache.
+       * Collectd::Unixsock: Error handling has been improved.
+       * ascent plugin: Fix a memory leak. Thanks to Bruno Prémont for his
+         patch.
+       * ipmi plugin: Fix an off-by-one error which could cause segmentation
+         faults. Thanks to Peter Holik for his patch.
+       * tcpconns plugin: An endianness problem has been fixed in the *BSD
+         code. Thanks to "thated" for reporting this.
+
+2009-01-02, Version 4.5.2
+       * build system: Check for `mysql.h' and `mysql/mysql.h', since the
+         file may be in both locations, especially when the database was
+         installed in a non-standard path. Thanks to Dusty Doris for
+         reporting this.
+       * build system: Handle the _POSIX_PTHREAD_SEMANTICS defined, needed by
+         Solaris, in the configure script automatically.
+       * build system, tcpconns plugin: Check for `kvm_nlist' and
+         `kvm_openfiles' before enabling the plugin: Solaris provides a KVM
+         library with similar functions to the BSD variant, but doesn't
+         provide these necessary functions.
+       * collectd.conf(5): Various fixes and clarifications.
+       * collectd: Remove a GNUism (unnamed unions), thus improving
+         portability.
+       * collectd, apcups plugin: Include "collectd.h" before <stdlib.h>.
+         This solves portability problems, especially for Solaris.
+       * dns plugin: Fix a portability problem with NetBSD.
+       * filecount plugin: Fix an off-by-one error. This error may cause a
+         segmentation fault.
+       * network plugin: Fix the handling of `type' in the network protocol.
+         Due to a programming mistake, only 4 or 8 bytes would be copied to a
+         much larger buffer. This caused the `type' to be transferred much
+         more often than necessary. In some cases, e. g. the `cpu' and
+         `cpufreq' plugins being used at the same time, data may be corrupted
+         in those files. Thanks to Bruno Prémont for debugging and reporting
+         this issue.
+       * processes plugin: Fix a possible segmentation fault when specifying
+         invalid configuration options.
+       * unixsock plugin: Make sure the initialization function is run only
+         once. This resolves a file descriptor leak under systems which run
+         the initialization more than once, such as Solaris.
+
+2008-10-16, Version 4.5.1
+       * build system: Change `--enable-<plugin>' to abort with an error if
+         dependencies are not met. Thanks to Bruno Prémont for the patch.
+         Also, the poisoning of various string functions has been restricted
+         to debug builds.
+       * collectd: Fix a memory leak in the global value cache. With every
+         *missing* value a couple of bytes would be leaked. Another memory
+         leak in the configuration handling code has been fixed. Thanks to
+         Niraj Tolia for reporting these issues.
+       * collectd: Fix an off-by-one error in the ignorelist functionality.
+         When using regular expressions, the last character would be missing,
+         possibly matching differently from what one would expect.
+       * collectdmon: Don't block SIGCHLD. This fixes a potential portability
+         problem.
+       * collectd-nagios: Fix handling of the `-d' option. Thanks to Fabian
+         Linzberger for reporting the bug.
+       * iptables plugin: Fix an off-by-one error. If a string was just one
+         character too long, it was truncated instead of reporting an error.
+       * network plugin: Fix a memory leak in the configuration handling
+         code. Thanks to Niraj Tolia for reporting this issue.
+       * perl plugin: Log an error message if bootstrapping `Collectd' fails.
+       * postgresql plugin: Don't reopen connection during reinitialization.
+         This fixes a bug under Solaris and potentially other platforms.
+         Missing calls to `PQclear' have been added, too. This fixes memory
+         leaks. Thanks to ``Admin'' for reporting these bugs.
+       * snmp plugin: Don't expect null-terminated strings from the Net-SNMP
+         library.
+       * tail plugin: Call `clearerr(3)' after reading an EOF. This fixes
+         problems with some `libc's. Thanks to Matthias Lay for reporting the
+         bug.
+
+2008-09-04, Version 4.5.0
+       * collectd: Added the ability to flush certain identifiers.
+       * collectd: The concept of `notification meta data' has been
+         introduced.
+       * filecount plugin: The new filecount plugin counts the number of
+         files in a directory and its subdirectories.
+       * ipmi plugin: Sensor names have been changed to ensure unique names.
+         Notifications upon added and removed sensors can now be generated.
+       * notify_desktop plugin: This new plugin sends notifications to the
+         X desktop using the structure defined in the `Desktop Notification
+         Specification'.
+       * notify_email plugin: This new plugin sends out notifications via
+         email, using the `esmtp' library.
+       * onewire plugin: The new experimental(!) onewire plugin reads values,
+         such as temperatures, from sensors connected to the computer via the
+         onewire bus.
+       * perl plugin: Improved synchronized access to internal data structures
+         and fixed a possible dead-lock.
+       * perl plugin: Added the ability to flush certain identifiers and marked
+         plugin_flush_all() and plugin_flush_one() as deprecated in favor of
+         plugin_flush().
+       * perl plugin: Added the ability to configure Perl plugins.
+       * postgresql plugin: The new postgresql plugin collects statistics
+         about or from a PostgreSQL database.
+       * processes plugin: The `ProcessMatch' option has been added.
+       * rrdtool plugin: Implement throttling of the `update queue' to lessen
+         IO load.
+       * tcpconns plugin: This plugin has been ported to OpenBSD.
+       * thermal plugin: The new thermal plugin collects system temperatures
+         using Linux ACPI thermal zone data.
+
+2009-01-02, Version 4.4.5
+       * build system: Check for `mysql.h' and `mysql/mysql.h', since the
+         file may be in both locations, especially when the database was
+         installed in a non-standard path. Thanks to Dusty Doris for
+         reporting this.
+       * build system: Handle the _POSIX_PTHREAD_SEMANTICS defined, needed by
+         Solaris, in the configure script automatically.
+       * collectd.conf(5): Various fixes and clarifications.
+       * apcups plugin: Include "collectd.h" before <stdlib.h>. This solves
+         portability problems, especially for Solaris.
+       * dns plugin: Fix a portability problem with NetBSD.
+       * network plugin: Fix the handling of `type' in the network protocol.
+         Due to a programming mistake, only 4 or 8 bytes would be copied to a
+         much larger buffer. This caused the `type' to be transferred much
+         more often than necessary. In some cases, e. g. the `cpu' and
+         `cpufreq' plugins being used at the same time, data may be corrupted
+         in those files. Thanks to Bruno Prémont for debugging and reporting
+         this issue.
+       * unixsock plugin: Make sure the initialization function is run only
+         once. This resolves a file descriptor leak under systems which run
+         the initialization more than once, such as Solaris.
+
+2008-10-16, Version 4.4.4
+       * build system: Change `--enable-<plugin>' to abort with an error if
+         dependencies are not met. Thanks to Bruno Prémont for the patch.
+         Also, the poisoning of various string functions has been restricted
+         to debug builds.
+       * collectd: Fix a memory leak in the global value cache. With every
+         *missing* value a couple of bytes would be leaked. Another memory
+         leak in the configuration handling code has been fixed. Thanks to
+         Niraj Tolia for reporting these issues.
+       * collectd: Fix an off-by-one error in the ignorelist functionality.
+         When using regular expressions, the last character would be missing,
+         possibly matching differently from what one would expect.
+       * collectdmon: Don't block SIGCHLD. This fixes a potential portability
+         problem.
+       * collectd-nagios: Fix handling of the `-d' option. Thanks to Fabian
+         Linzberger for reporting the bug.
+       * network plugin: Fix a memory leak in the configuration handling
+         code. Thanks to Niraj Tolia for reporting this issue.
+       * perl plugin: Log an error message if bootstrapping `Collectd' fails.
+       * tail plugin: Call `clearerr(3)' after reading an EOF. This fixes
+         problems with some `libc's. Thanks to Matthias Lay for reporting the
+         bug.
+
+2008-09-01, Version 4.4.3
+       * collectd: Fix a memory leak in the threshold checking code.
+       * memcached plugin: Fix a too short timeout and a related file
+         descriptor leak.
+       * memory plugin: A typo in the libstatgrab code has been fixed.
+       * snmp plugin: Fix a possible memory leak.
+
+2008-07-15, Version 4.4.2
+       * build system: Use pkg-config to detect the upsclient library.
+       * collectd: Try even harder to determine the endianess of the
+         architecture collectd is being built on.
+       * disk plugin: Fix for Linux 2.4: A wrong field was used as the name
+         of disks.
+       * dns plugin: Fix compilation errors with BIND versions 19991001
+         through 19991005.
+       * network plugin: Bugfix in the init routine: The init function
+         cleared a buffer regardless of its contents. This could lead to lost
+         values under Solaris.
+       * nginx plugin: Remove usage of the thread-unsafe `strtok' function.
+       * vserver plugin: Remove usage of the thread-unsafe `readdir'
+         function.
+       * wireless plugin: Work around incorrect noise and power values
+         returned by some broken drivers.
+
+2008-06-03, Version 4.4.1
+       * collectd: Fix the `DataSource' option within `Type' blocks. Thanks
+         to kyrone for reporting this.
+       * collectd: Fixed min/max output in notifications generated by
+         threshold checking.
+       * collectd-nagios: Fix the protocol used to communicate with the
+         daemon.
+       * perl plugin: Fail noisily, but don't shutdown the daemon, if
+         initialization has errors. An issue with Perl 5.10 has been fixed.
+       * teamspeak2 plugin: Fixed an out of bound array access. Thanks to
+         René Rebe and Siegmund Gorr for reporting this.
+
+2008-05-06, Version 4.4.0
+       * collectd: Internal code cleanups.
+       * collectd: Added support for a `Flush' command in the unixsock and
+         exec plugins. This command can be used to force a plugin (or all) to
+         flush its values to disk.
+       * collectd: Thresholds can now be configured to apply to one data
+         source only, making it possible to configure different thresholds
+         for each data source.
+       * apache, nginx plugins: Added the possibility to disable host and/or
+         peer verification.
+       * ascent plugin: The new ascent plugin reads and parses the statistics
+         page of an Ascent server.
+       * cpu plugin: Support for the statgrab library has been added.
+       * disk plugin: The possibility to ignore certain disks or collect only
+         specific disks has been added.
+       * disk plugin: Support for the statgrab library has been added.
+       * ipmi plugin: The new ipmi plugin uses the OpenIPMI library to read
+         sensor values via IPMI, the intelligent platform management
+         interface.
+       * iptables plugin: The iptc library that is used by the iptables
+         plugin has been added to the distribution, because it is not
+         provided by all distributions and removed from at least one.
+       * powerdns plugin: The new powerdns plugin reads statistics from an
+         authoritative or a recursing PowerDNS name server.
+       * rrdtool plugin: The size of the files generated with the default
+         configuration has been decreased.
+       * tail plugin: The new tail plugin can be used to gather statistics by
+         continuously reading from log files.
+       * teamspeak2 plugin: The new teamspeak2 plugin connects to a
+         TeamSpeak2 server and collects statistics about the number of users
+         and number of channels.
+       * users plugin: Support for the statgrab library has been added.
+       * vmem plugin: The new vmem plugin collects very detailed statistics
+         about the virtual memory subsystem of Linux.
+
+2008-08-30, Version 4.3.4
+       * Build system: Improved detection of and linking with the statgrab
+         library.
+       * collectd: Portability fixes, especially to determine endianess more
+         reliable.
+       * Various plugins: Fix format strings.
+       * disk plugin: A fix for giving disks under Linux 2.4 the right names
+         again has been applied.
+       * memcached plugin: Fix a too short timeout and a related file
+         descriptor leak.
+       * memory plugin: A typo in the libstatgrab code has been fixed.
+       * network plugin: A fix in the initialization function solves problems
+         under Solaris.
+       * nginx plugin: A thread-unsafe function has been replaced.
+       * vserver plugin: A thread-unsafe function has been replaced.
+       * wireless plugin: A work-around for broken wireless drivers has been
+         added.
+
+2008-04-22, Version 4.3.3
+       * build system: Improved detection of several libraries, especially if
+         they are in non-standard paths.
+       * build system: Portability fixes: Automatically define "_REENTRANT"
+         if the libc expects it.
+       * collectd: Error and warning messages have been improved.
+       * collectd: Check for the BYTE_ORDER and BIG_ENDIAN defines before
+         using them.
+       * apache plugin: Allocate new memory when reading a webpage instead of
+         using a buffer of static size.
+       * exec plugin: Close (almost) all filedescriptors before exec(2)ing
+         the program.
+       * hddtemp plugin: Error and warning messages have been improved.
+       * sensors plugin: Fix sensor collection for some chip types.
+
+2008-03-29, Version 4.3.2
+       * collectd: Fix configuration of the `FailureMax', `WarningMax', and
+         `Persist' threshold options.
+       * collectd: Fix handling of missing values in the global value cache.
+       * collectd: Improved error messages when parsing the configuration.
+       * sensors plugin: Fix temperature collection with libsensors4.
+       * unixsock plugin: Fix mixed input and output operation on streams.
+       * wireless plugin: Fix reading noise value.
+
+2008-03-05, Version 4.3.1
+       * exec plugin: Set supplementary group IDs.
+       * network plugin:
+         + Use `memcpy' when constructing/parsing a package to avoid
+           alignment problems on weird architectures, such as Sparc.
+         + Translate doubles to/from the x86 byte representation to ensure
+           cross-platform compatibility.
+       * ping plugin: Correct the handling of the `TTL' setting.
+       * swap plugin: Reapply a patch for Solaris.
+       * tcpconns plugin: Portability improvements.
+
+2008-02-18, Version 4.3.0
+       * collectd: Notifications have been added to the daemon. Notifications
+         are status messages that may be associated with a data instance.
+       * collectd: Threshold checking has been added to the daemon. This
+         means that you can configure threshold values for each data
+         instance. If this threshold is exceeded a notification will be
+         created.
+       * collectd: The new `FQDNLookup' option tells the daemon to use the
+         full qualified domain name as the hostname, not just the host part
+         es returned by `gethostname(2)'.
+       * collectd: Support for more than one `TypesDB' file has been added.
+         This is useful when one such file is included in a package but one
+         wants to add custom type definitions.
+       * collectd: The `Include' config option has been expanded to handle
+         entire directories and shell wildcards.
+       * collectdmon: The new `collectdmon' binary detects when collectd
+         terminates and automatically restarts it again.
+       * csv plugin: The CSV plugin is now able to store counter values as a
+         rate, using the `StoreRates' configuration option.
+       * exec plugin: Handling of notifications has been added and the
+         ability to pass arguments to the executed programs has been added.
+       * hddtemp plugin: The new `TranslateDevicename' option lets you
+         disable the translation from device names to major-minor-numbers.
+       * logfile plugin: Handling of notifications has been added.
+       * ntpd plugin: The new `ReverseLookups' can be used to disable reverse
+         domain name lookups in this plugin.
+       * perl plugin: Many internal changes added support for handling multiple
+         threads making the plugin reasonably usable inside collectd. The API has
+         been extended to support notifications and export global variables to
+         Perl plugins; callbacks now have to be identified by name rather than a
+         pointer to a subroutine. The plugin is no longer experimental.
+       * uuid plugin: The new UUID plugin sets the hostname to an unique
+         identifier for this host. This is meant for setups where each client
+         may migrate to another physical host, possibly going through one or
+         more name changes in the process. Thanks to Richard Jones from
+         Red Hat's Emerging Technology group for this plugin.
+       * libvirt: The new libvirt plugin uses the `libvirt' library to query
+         CPU, disk and network statistics about guest systems on the same
+         physical server. Thanks to Richard Jones from Red Hat's Emerging
+         Technology group for this plugin.
+
+2008-04-22, Version 4.2.7
+       * build system: Improved detection of several libraries, especially if
+         they are in non-standard paths.
+       * build system: Portability fixes: Automatically define "_REENTRANT"
+         if the libc expects it.
+       * collectd: Error and warning messages have been improved.
+       * collectd: Check for the BYTE_ORDER and BIG_ENDIAN defines before
+         using them.
+       * apache plugin: Allocate new memory when reading a webpage instead of
+         using a buffer of static size.
+       * exec plugin: Close (almost) all filedescriptors before exec(2)ing
+         the program.
+       * hddtemp plugin: Error and warning messages have been improved.
+       * sensors plugin: Fix sensor collection for some chip types.
+
+2008-03-29, Version 4.2.6
+       * collectd: Improved error messages when parsing the configuration.
+       * sensors plugin: Fix temperature collection with libsensors4.
+       * unixsock plugin: Fix mixed input and output operation on streams.
+       * wireless plugin: Fix reading noise value.
+
+2008-03-04, Version 4.2.5
+       * apache plugin: Improved initialization and error messages.
+       * exec plugin: Set supplementary group IDs.
+       * network plugin:
+         + Create separate threads for reading from the socket and parsing
+           and dispatching incoming packets. Versions prior to this may have
+           problems in high-load situations, where the socket receive buffers
+           overflows, resulting in gaps in the data.
+         + Use `memcpy' when constructing/parsing a package to avoid
+           alignment problems on weird architectures, such as Sparc.
+         + Translate doubles to/from the x86 byte representation to ensure
+           cross-platform compatibility.
+       * ping plugin: Correct the handling of the `TTL' setting.
+       * rrdtool plugin: Ensure correct handling of the `RRATimespan' option.
+       * swap plugin: Reapply a patch for Solaris.
+       * tcpconns plugin: Portability improvements.
+
+2008-01-21, Version 4.2.4
+       * unixsock plugin: A bug in the unixsock plugin caused it not to set
+         the permission on the socket as documented in the manpage. Thanks to
+         Evgeny Chukreev for fixing this issue.
+       * collectd: The documentation has been improved.
+
+2007-12-28, Version 4.2.3
+       * sensors plugin: Updated the plugin to build and work with version 3
+         of the libsensors library.
+
+2007-12-15, Version 4.2.2
+       * nginx plugin: Incorrect comparison of strings lead to a segfault
+         when using the plugin. Thanks to Saulius Grigaliunas for fixing
+         this.
+       * logfile plugin: The config option `Timestamp' was handled
+         incorrectly and basically always active. Thanks to Luke Heberling
+         for fixing this.
+
+2007-11-08, Version 4.2.1
+       * tcpconns plugin: Don't complain about a missing file if IPv6 is not
+         enabled on the host.
+       * snmp plugin: Fix a memory leak.
+
+2007-10-27, Version 4.2.0
+       * collectd: The new config option `Include' lets you include other
+         configfiles and thus split up your config into smaller parts. This
+         may be especially interesting for the snmp plugin to keep the data
+         definitions separate from the host definitions.
+       * ipvs plugin: The new `ipvs' plugin collects IPVS connection statistics
+         (number of connections, octets and packets for each service and
+         destination). Thanks to Sebastian Harl for this plugin.
+       * memcached plugin: The new `memcached' plugin connects to a memcached
+         daemon process and collects statistics of this distributed caching
+         system. Thanks to Antony Dovgal for contributing this plugin.
+       * nginx plugin: The new `nginx' plugin reads the status page of an
+         nginx daemon and saves the handled connections and requests.
+       * perl plugin: Many changes, including the added `EnableDebugger'
+         config option which lets you debug your Perl plugins more easily.
+       * rrdtool plugin: Use the thread-safe RRD-library if available. Try to
+         be more thread-safe otherwise by locking calls to the library.
+       * snmp plugin: Added the options `Scale' and `Shift' to Data-blocks to
+         correct the values returned by SNMP-agents. If a <data> block is
+         defined as `table' the instance is now optional. The sequence number
+         is used as the type-instance in this case. The new `InstancePrefix'
+         option allows to add arbitrary prefixes to the type-instance.
+       * tcpconns plugin: The new `tcpconns' plugin collects the number of
+         certain TCP connections and what state they're in. This can be used
+         to see how many connections your FTP server has to handle or how
+         many outgoing connections your mailserver has open.
+
+2008-01-11, Version 4.1.6
+       * unixsock plugin: A bug in the unixsock plugin caused it not to set
+         the permission on the socket as documented in the manpage. Thanks to
+         Evgeny Chukreev for fixing this issue.
+       * collectd: The documentation has been improved.
+
+2007-12-27, Version 4.1.5
+       * rrdtool plugin: Fix a memory leak that only occurred in very-low-
+         memory situations.
+       * sensors plugin: Updated the plugin to build and work with version 3
+         of the libsensors library.
+
+2007-11-08, Version 4.1.4
+       * Build system: Improve detection of the rrd library, especially if
+         it's in a non-standard location.
+       * Build system: A bug when parsing the argument for
+         `--with-libnetsnmp' has been fixed.
+       * collectd: Implement `strerror_r' if the libc doesn't provide it.
+       * rrdtool plugin: Fix a bug in the shutdown sequence that might cause
+         a deadlock or delay when shutting down the daemon.
+       * snmp plugin: Fix a memory leak.
+
+2007-10-24, Version 4.1.3
+       * collectd: A build issue under Solaris has been resolved by renaming
+         data types.
+       * rrdtool plugin: Use the thread-safe RRD-library if available. Try to
+         be more thread-safe otherwise by locking calls to the library.
+
+2007-09-28, Version 4.1.2
+       * apcups plugin: Fix reporting of the `load percent' data.
+       * wireless plugin: Correct the handling of cards returning signal and
+         noise quality as percentage.
+       * perl plugin: Fix a possible buffer overflow in get_module_name().
+       * build system: Further improve the detection of libraries.
+       * netlink plugin: Build issues under some older versions of the Linux
+         includes (i. e. Debian Sarge) have been fixed.
+       * snmp plugin: Fix a potential segfault when a host times out. Add
+         support for the `timeticks' type. 
+
+2007-09-12, Version 4.1.1
+       * Build system: The detection of `libnetlink' has been improved.
+       * collectd: The documentation has been fixed in numerous places.
+       * exec plugin: Setting the group under which to run a program has been
+         fixed.
+       * collectd: The `sstrerror' function was improved to work correctly
+         with the broken GNU version of `strerror_r'.
+       * collectd: Write an error message to STDERR when loading of a plugin
+         fails.
+       * apcups plugin: Fix the `types' used to submit the values: They still
+         has an `apcups_' prefix which doesn't work anymore.
+       * rrdtool plugin: Create new RRD-files with the `begin' time set to
+         whatever the client thinks is `now'..
+
+2007-09-01, Version 4.1.0
+       * Build system: The build system has been changed to automatically
+         disable all plugins, which are missing dependencies. The dependency
+         checking has been removed from the plugins themselves to remove
+         redundancy.
+       * Flexible interval: The interval of collected data is now sent along
+         with the data itself over the network, so that the interval-settings
+         of server and clients no longer needs to match.
+       * netlink plugin: The new `netlink' plugin connects to the Linux
+         kernel using a netlink socket and uses it to query information about
+         interfaces, qdiscs and classes.
+       * rrdtool plugin: The cache is now dumped to disk in an extra thread
+         to not block data collection.
+       * snmp plugin: The new `snmp' plugin can read values from SNMP enabled
+         network devices, such as switches, routers, thermometers, rack
+         monitoring servers, etc. The collectd-snmp(5) manpage documents this
+         plugin.
+       * unixsock plugin: Added the `LISTVAL' command.
+       * xmms plugin: The new `xmms' plugin graphs the bitrate and frequency
+         of music played with xmms.
+
+2007-09-28, Version 4.0.9
+       * apcups plugin: Fix reporting of the `load percent' data.
+       * wireless plugin: Correct the handling of cards returning signal and
+         noise quality as percentage.
+       * perl plugin: Fix a possible buffer overflow in get_module_name().
+
+2007-09-12, Version 4.0.8
+       * collectd: The `sstrerror' function was improved to work correctly
+         with the broken GNU version of `strerror_r'.
+       * collectd: Write an error message to STDERR when loading of a plugin
+         fails.
+       * apcups plugin: Fix the `types' used to submit the values: They still
+         has an `apcups_' prefix which doesn't work anymore.
+       * rrdtool plugin: Create new RRD-files with the `begin' time set to
+         whatever the client thinks is `now'..
+
+2007-08-26, Version 4.0.7
+       * documentation: Some typos have been fixed and some information has
+         been improved.
+       * build system: Many fixes for detecting libraries in unusual places,
+         such as on RedHat systems. The affected libraries are `libcurl',
+         `libmysql', and `libupsclient'.
+       * network plugin: Allow the `Port' option to be specified as a number
+         (i. e. without quotes).
+       * nut plugin: A fix allows linking the nut plugin against
+         libupsclient, version >= 2.2.0.
+       * processes plugin: Fix a potential segmentation fault.
+
+2007-07-30, Version 4.0.6
+       * sensors plugin: Fix the ignorelist functionality: Only the `type
+         instance' was used to match against the list, but the documentation
+         told otherwise. This release fixes the code, so it complies with the
+         documentation.
+       * syslog plugin: Call `openlog' right when the plugin is loaded, so
+         configuration messages will end up in the logging facility.
+       * conrtib/fedora: The contributed specfile for Fedora has been
+         updated.
+
+2007-07-05, Version 4.0.5
+       * Portability: More fixes for OpenBSD have been included.
+
+2007-06-24, Version 4.0.4
+       * cpu plugin: Fixed the Solaris code.
+       * dns plugin: Fixed a build issue for OpenBSD.
+       * interface plugin: Fixed the Solaris code.
+       * load plugin: Fixed the alternative `/proc' Linux code.
+       * memory plugin: Fixed the Solaris code.
+       * oconfig: Don't require `-lfl' anymore.
+
+2007-06-19, Version 4.0.3
+       * cpu plugin: Fix the Darwin / Mac OS X code.
+       * ping plugin: Use the return value of `getpid', not its address.
+       * csv, rrdtool plugin: Fixed a bug that prevented an buffer to be
+         initialized correctly.
+       * configure: Added `--with-nan-emulation' to aid cross compilation.
+
+2007-06-12, Version 4.0.2
+       * hddtemp and ntpd plugin: Corrected the parsing of port numbers when
+         they're given in numerically form.
+
+2007-06-07, Version 4.0.1
+       * iptables plugin: A bug in the configuration routine has been fixed.
+         Setting a comment in the configfile will no longer cause a
+         segmentation fault.
+
+2007-06-03, Version 4.0.0
+       * collectd: The plugin-infrastructure has been changed to allow for
+         more types of plugins, namely `write' and `log' plugins.
+       * collectd: The read-function has been changed to read many plugins in
+         parallel, using threads. Thus, plugins generally need to use
+         thread-safe functions from now on.
+       * collectd: The '-t' command line options allows to perform syntax tests
+         of the configuration file and exit immediately.
+       * csv plugin: The new `csv' plugin handles output to `comma separated
+         values'-files.
+       * rrdtool plugin: The new `rrdtool' plugin handles output to
+         RRD-files. Data can be cached to combine multiple updates into one
+         write to increase IO-performance.
+       * network plugin: The new `network' plugin handles IO via the network.
+         It implements a different, much more extensible protocol which can
+         combine many values in one packet, decreasing the number of UDP-
+         packets being sent. It can read from and send to the network and
+         with the appropriate configuration even forward packets to other
+         networks.
+       * unixsock plugin: The new `unixsock' plugin provides an interface to
+         communicate with the daemon while it is running. Right now the
+         commands `GETVAL' and `PUTVAL' are implemented, but more are to
+         come.
+       * perl plugin: The new `perl' plugin allows you to write extensions
+         for collectd in the scripting-language Perl.
+       * logfile plugin: The new `logfile' plugin writes logmessages to files
+         or STDOUT or STDERR.
+       * syslog plugin: The new `syslog' plugin sends logmessages to the
+         system's syslog daemon.
+       * entropy plugin: The new `entropy' plugin collects the amount of
+         entropy currently being available to the system.
+       * exec plugin: The new `exec' plugin forks child processes and reads
+         back values provided by the forked processes.
+       * iptables plugin: The new `iptables' plugin reads counters from
+         iptables rules. Thanks to Sjoerd van der Berg for contributing this
+         plugin.
+       * irq plugin: The new `irq' plugin collects the IRQ-counters. Thanks
+         to Peter Holik for contributing this plugin.
+       * nut plugin: The new `nut' plugin connects the upsd of the `network
+         ups tools' and reads information about the connected UPS.
+       * apache plugin: Support for lighttpd's `BusyServers' (aka.
+         connections) field was added by Florent Monbillard.
+       * collectd-nagios: The new `collectd-nagios' binary queries values
+         from collectd, parses them and exits according to Nagios-standards.
+       * manpages: The manpages have been improved a lot.
+
+2007-09-28, Version 3.11.7
+       * wireless plugin: Correct the handling of cards returning signal and
+         noise quality as percentage.
+
+2007-08-31, Version 3.11.6
+       * processes plugin: Fix a potential segmentation fault.
+
+2007-05-29, Version 3.11.5
+       * configure: Added `AC_SYS_LARGEFILE' for LFS.
+       * ntpd plugin: Fix a potential buffer overflow.
+       * processes plugin: Fix a bug when run under Linux 2.4. All processes
+         were accounted as `zombies'.
+
+2007-04-10, Version 3.11.4
+       * dns plugin: Change the order of includes to make the plugin compile
+         under FreeBSD.
+
+2007-03-30, Version 3.11.3
+       * configure: Have the configure-script define `HAVE_LIBKSTAT' instead
+         of the unused `COLLECT_KSTAT'.
+
+2007-02-11, Version 3.11.2
+       * plugin: Catch NULL-pointer and try to fix them. Otherwise the
+         NULL-pointer may have been passed to `printf' which causes a
+         segfault with some libcs.
+
+2007-02-10, Version 3.11.1
+       * df plugin: Some wrong defines have been fixed so the plugin works
+         under Solaris again.
+       * dns plugin: The usage of a struct has been fixed to work with
+         non-GNU libcs.
+       * processes plugin: Some missing defines have been added so the plugin
+         compiles cleanly under FreeBSD and presumably other UNIXes.
+
+2006-12-22, Version 3.11.0
+       * collectd: The new command line option `-P' makes it easier for
+         distributors to change the location of PID-files.
+       * collectd: The daemon shuts down faster now which makes it easier to
+         write init.d-scripts for it.
+       * apache plugin: Increase the buffersize to 16k, because the 4k buffer
+         caused problems every now and then.
+       * df plugin: New config options allow to ignore certain mountpoints,
+         filesystem types or devices.
+       * dns plugin: The new dns plugin uses `libpcap' to capture DNS traffic
+         and interprets it. It collects traffic as well as qtype, opcode and
+         rcode counts.
+       * email plugin: Sebastian Harl has contributed this plugin which
+         counts received mails in categories (e. g. ham, spam, virus), spam
+         score (as given by SpamAssassin) and check types.
+       * mbmon plugin: Flavio Stanchina has contributed this plugin which
+         uses `mbmon' to gather information from sensors on the motherboard.
+       * processes plugin: Collect detailed statistics for configured
+         processes, that's process and thread count, CPU usage, resident
+         segment size and pagefaults.
+       * multimeter plugin: Peter Holik contributed a new plugin which
+         queries multimeters.
+       * sensors plugin: Lubos Stanek has put much effort into improving this
+         plugin, including `extended naming', collection of voltage values
+         and the possibility to ignore certain values.
+
+2006-12-21, Version 3.10.4
+       * Max Kellermann has identified a bug in the server routine: When
+         opening a socket fails the daemon will (re)try opening the socket in
+         an endless loop, ultimately leading to a `EMFILE' error.
+
+2006-11-04, Version 3.10.3
+       * Lubos Stanek has identified a bug in the ntpd-plugin: When the
+         ntpd's reply was sent in more than one packet, the buffer size was
+         calculated incorrectly, resulting in the reading of uninitialized or
+         freed memory.
+
+2006-11-01, Version 3.10.2
+       * The sample config file has been improved.
+       * Errors in the manpages have been corrected.
+       * The ping-plugin now adds hosts during initialization, not during
+         startup. This speeds up startup when no network connectivity is
+         available. Also, the hosts are being added later when the network is
+         available.
+       * Improved BSD-support for the df-plugin.
+       * Fixed syntax errors in the swap-plugin for Mac OS X.
+       * Fix a wrong structure being passed to `getnameinfo' in the ntpd-
+         plugin.
+       * Don't disable the mysql-plugin if connecting to the database fails
+         during initialization. Instead, try again in increasing intervals.
+
+2006-07-19, Version 3.10.1
+       * A bug in the apcups plugin was fixed: Is the plugin is loaded, but
+         the apcups cannot be reached, unconnected sockets will pile up and
+         eventually lead to `Too many open files' errors.
+
+2006-07-09, Version 3.10.0
+       * The `disk' plugin has been ported to Darwin.
+       * The `battery' plugin should work on many Apple computers now.
+       * The `traffic' plugin can now ignore certain interfaces. Also,
+         statistics for sent/received packets and errors have been added.
+       * A plugin to monitor APC UPSes using `apcupsd' has been added. Thanks
+         to Anthony Gialluca for contributing this plugin and providing me
+         with a test environment :)
+       * A plugin for monitoring an NTP instance and the local clock drift
+         has been added.
+
+2006-06-25, Version 3.9.4
+       * The Solaris code in the `swap' plugin has been changed to reflect
+         the numbers returned by `swap -s'. Thanks to Christophe Kalt for
+         working this out.
+       * The debugging system has been fixed to work with the Sun libc.
+       * When built without librrd the variable `operating_mode' could be
+         uninitialized. Thanks to David Elliot for reporting the bug.
+
+2006-06-01, Version 3.9.3
+       * Fixed the ping-plugin under FreeBSD and Mac OS X. Potentially other
+         operating systems also profit from the changes, but I wasn't able to
+         check that.
+       * Changed the build system to find the netinet-includes under FreeBSD
+         and therefore successfully build the `liboping' library there.
+
+2006-05-09, Version 3.9.2
+       * Applied a patch to the `liboping' library. Due to a bug in the
+         sequence checking the `ping' plugin stopped working after
+         approximately 7.6 days.
+
+2006-05-09, Version 3.8.5
+       * Applied a patch to the `liboping' library. Due to a bug in the
+         sequence checking the `ping' plugin stopped working after
+         approximately 7.6 days.
+
+2006-04-21, Version 3.9.1
+       * Build issues with Solaris and possible other architectures have been
+         resolved.
+       * Problems when building the `apache'-plugin without `libcurl' have
+         been resolved.
+       * A bug in the `ping' plugin has been fixed. Sorry folks.
+
+2006-04-02, Version 3.9.0
+       * A plugin to monitor the Apache webserver has been added.
+         <http://httpd.apache.org/>
+       * A plugin to collect statistics about virtual servers using VServer.
+         <http://linux-vserver.org/> Thanks to Sebastian Harl for writing
+         this plugin :)
+       * A plugin for wireless LAN cards has been added. It monitors signal
+         strength, link quality and noise ratio..
+       * A plugin for Apple hardware sensors has been added.
+       * An option to compile collectd with different `step' and `heartbeat'
+         settings has been added. The size of RRAs is no longer static but
+         calculated based on the settings for `step' and `width'.
+       * The `ping' plugin can now be configured to use a certain TTL.
+       * A plugin to monitor the hardware sensors of Apple computers has been
+         added.
+       * The plugins `cpu', `memory', `processes' and `traffic' have been
+         ported to Mach/Darwin (Mac OS X).
+       * The `log mode' has been contributed by Christophe Kalt. It writes
+         the data into text files rather than RRD files.
+
+2006-04-09, Version 3.8.4
+       * Applied patch by Vincent Stehlé which improves the disk-name
+         resolution in the `hddtemp' plugin for Linux systems.
+
+2006-04-02, Version 3.8.3
+       * Applied a patch by James Byers: The MySQL plugin was not working
+         with MySQL 5.0.2 or later.
+
+2006-03-14, Version 3.8.2
+       * `utils_mount.c' has been changed to not use the `MNTTAB' defined by
+         the GNU libc, because it points to `/etc/fstab' rather than
+         `/etc/mtab'.
+
+2006-03-13, Version 3.8.1
+       * Fixes for building collectd under FreeBSD, Mac OS X and Solaris.
+       * Fixes in the debian `postinst' and `init.d' scripts.
+
+2006-03-09, Version 3.8.0
+       * The `ping' plugin no longer uses `libping' but a self written
+         library named `liboping'. With this library it's possible to ping
+         multiple IPv4 and IPv6 addresses and hostnames - in parallel.
+
+2006-02-18, Version 3.7.2
+       * A simple bug in the `battery' plugin has been fixed. It should now
+         work with ACPI based batteries as well. Thanks to Sebastian for
+         fixing this.
+       * Fixing a bug that prevented collectd to be built without librrd.
+         Thanks to Werner Heuser for reporting it.
+
+2006-02-04, Version 3.7.1
+       * The new network code has been improved to build with older versions
+         of glibc.
+       * Fix in `libping' sets the ICMP sequence on outgoing packets. Thanks
+         to Tommie Gannert for this patch.
+
+2006-01-30, Version 3.7.0
+       * The `battery' plugin has been added. It collects information about
+         laptop batteries..
+       * The MySQL plugin has been improved: It now writes two more RRD
+         files, `mysql_qcache.rrd' and `mysql_threads.rrd'.
+       * The `cpufreq' plugin now reads another file since the file it did
+         read so far causes much overhead in the kernel. Also, you need root
+         to read the old file, but not to read the new one.
+       * The `hddtemp' plugin can now be configured to connect to another
+         address and/or port than localhost.
+       * The `df' plugin now prefers `statvfs' over `statfs'.
+       * The network code has been rewritten. collectd now supports unicast
+         and multicast, and IPv4 and IPv6. Also, the TTL of sent packages can
+         be set in the configfile.
+
+2006-01-24, Version 3.6.2
+       * Due to a bug in the configfile handling collectd wouldn't start in
+         client mode. This released fixes this.
+
+2006-01-20, Version 3.6.1
+       * Due to a bug in `configure.in' all modules and the binary were
+         linked against `libmysqlclient'. This issue is solved by this
+         release.
+
+2006-01-17, Version 3.6.0
+       * A config file has been added. This allows for loading only specific
+         plugins.
+       * A `df' plugin has been added.
+       * A `mysql' plugin has been added.
+       * The `ping' plugin doesn't entirely give up hope when a socket error
+         occurred, but will back of and increase the intervals between tries.
+
+2006-01-21, Version 3.5.2
+       * Fixed yet another bug in the signal handling.. Stupid typo..
+       * Improved the ping plugin to not give up on socket errors (backport
+         from 3.6.0).
+
+2005-12-18, Version 3.5.1
+       * The PID-file is now deleted correctly when shutting down the daemon.
+       * SIGINT and SIGTERM are now handled correctly.
+
+2005-12-16, Version 3.5.0 (Revision 326)
+       * A bug in the `load' module under Solaris has been fixed.
+       * The `users' module has been contributed by Sebastian Harl. It counts
+         currently logged in users.
+       * The CPU module now works under FreeBSD without the use of
+         `libstatgrab', however SMP support is missing.
+       * The default directories for the RRD files and the PID file now
+         depend on the compile time setting of `localstatedir'.
+
+2005-11-15, Version 3.4.0 (Revision 236)
+       * A PID-file is written to /var/run upon startup. Thanks to `Tommie'
+         from gentoo's bugzilla for writing the patch.
+       * The build dependency for librrd has been removed. Binaries built
+         without librrd are client-only and will multicast their value as
+         with the `-c' argument.
+       * A patch by Peter Holik adds a module for monitoring CPU frequencies.
+       * The newly introduced `-f' switch prevents daemon initialization
+         (forking, closing standard filehandles, etc.) Thanks to Alvaro
+         Barcellos for this patch.
+
+2005-11-04, Version 3.3.0 (Revision 216)
+       * New modules have been added:
+         - `serial', for monitoring traffic on the serial interfaces
+         - `nfs', for graphing NFS procedure calls
+         - `tape', traffic from/to tape devices
+       * The memory.rrd now accepts more than 4Gig of memory.
+
+2005-10-26, Version 3.2.0 (Revision 200)
+       * Support for graphing the processes has been added (thanks to Lyonel
+         Vincent)
+       * If reading from hddtemp fails collectd will increase the time
+         between polls up to one day.
+       * The init.d files have been improved.
+       * Problems with the spec file have been fixed.
+
+2005-10-16, Version 3.1.0 (Revision 194)
+       * Added the `setsid' syscall to the startup code.
+       * Support for hddtemp has been added (thanks to Vincent Stehlé)
+
+2005-09-30, Version 3.0.0 (Revision 184)
+       * The ability to send/receive data to/from the network (think
+         multicast) has been added.
+       * Modules have been split up into shared libraries can be loaded at
+         runtime. The biggest advantage is that the core program doesn't need
+         to be linked against an external library.
+       * A patch by George Kargiotakis has been applied: It fixes the sensors
+         behaviour then more than one sensor is being queried.
+
+2005-09-16, Version 2.1.0 (Revision 172)
+       * A module for swap statistics has been added.
+
+2005-09-09, Version 2.0.0 (Revision 135)
+       * Filenames can no longer be configured at program startup. The only
+         options as of this version are the directory and ping hosts.
+       * CPU statistics now include Wait-IO. If provided under Linux IRQ and
+         Soft-IRQ statistics are added to `System'. 
+       * Diskstats now collect read and write bytes, not sectors.
+       * Ping statistics can now be collected for more than one host. There
+         is no default any more: If no host is given no host will be pinged.
+       * A self-written patch for libping has been applied so it builds
+         cleanly.
+
+2005-09-01, Version 1.8.1 (Revision 123)
+       * Much improved configure-script: libraries and features may now be
+         disabled.
+       * More detailed warnings/error messages when RRD update fails.
+
+2005-08-29, Version 1.8.0:
+       * Support for collecting disk statistics under Solaris.
+
+2005-08-25, Version 1.7.0:
+       * Support for libstatgrab[1] for load, memory usage and network
+         traffic. CPU- and disk-usage are not (yet) supported, since
+         libstatgrab returns insufficient information. I will contact the
+         authors.
+       * Improved the CPU-initialization code for Solaris. Apparently CPUs
+         aren't necessarily counted linear which is now handled correctly.
+       [1]: http://www.i-scream.org/libstatgrab/
+
+2005-08-21, Version 1.6.0:
+       * Basic support for Solaris: System load and cpu-usage can be
+         collected under Solaris, too. Other stats will follow later.
+       * Many fixes in the autoconf-script
+       * Collection/Museum scripts have been added under contrib/museum
+       * collectd may now be started in unprivileged mode, though ping
+         statistics will not work.
+
+2005-07-17, Version 1.5.1:
+       * Diskstats-RRDs now use major/minor for naming. Some systems have
+         weird strings as disk-names..
+
+2005-07-17, Version 1.5:
+       * A new module, diskstats, has been added. It collects information
+         about the disks and partitions.
+
+2005-07-11, Version 1.4.2:
+       * The meminfo module has been changed to work with more platforms
+         and/or kernel versions.
+
+2005-07-10, Version 1.4.1: Correct traffic stats
+       * The traffic rrd-file is now created with DS-type `COUNTER' which I
+         forgot to correct when I changed that module.
+
+2005-07-09, Version 1.4: More traffic stats
+       * Traffic is now collected for all interfaces that can be found
+       * Temperature-statistics are read from lm-sensors if available
+
+2005-07-08, Version 1.3: CPU stats
+       * Collecting CPU statistics now
+
+2005-07-12, Version 1.2: Using syslog
+       * collectd is now using the syslog facility to report errors, warnings
+         and the like..
+       * The default directory is now /var/db/collectd
+
+2005-07-10, Version 1.1: Minor changes
+       * Nothing really useful to say ;)
+
+2005-07-09, Version 1.0: Initial Version
+       * The following modules are provided:
+         * Load average
+         * Ping time
+         * Traffic
+         * Memory info
diff --git a/Makefile.am b/Makefile.am
new file mode 100644 (file)
index 0000000..9e3feac
--- /dev/null
@@ -0,0 +1,12 @@
+ACLOCAL_AMFLAGS = -I libltdl/m4
+
+SUBDIRS = libltdl src bindings
+
+INCLUDES = $(LTDLINCL)
+
+EXTRA_DIST = contrib version-gen.sh
+
+install-exec-hook:
+       $(mkinstalldirs) $(DESTDIR)$(localstatedir)/run
+       $(mkinstalldirs) $(DESTDIR)$(localstatedir)/lib/$(PACKAGE_NAME)
+       $(mkinstalldirs) $(DESTDIR)$(localstatedir)/log
diff --git a/NEWS b/NEWS
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..ac79ce5
--- /dev/null
+++ b/README
@@ -0,0 +1,811 @@
+ collectd - System information collection daemon
+=================================================
+http://collectd.org/
+
+About
+-----
+
+  collectd is a small daemon which collects system information periodically
+  and provides mechanisms to store and monitor the values in a variety of
+  ways.
+
+
+Features
+--------
+
+  * collectd is able to collect the following data:
+
+    - apache
+      Apache server utilization: Number of bytes transfered, number of
+      requests handled and detailed scoreboard statistics
+
+    - apcups
+      APC UPS Daemon: UPS charge, load, input/output/battery voltage, etc.
+
+    - apple_sensors
+      Sensors in Macs running Mac OS X / Darwin: Temperature, fanspeed and
+      voltage sensors.
+
+    - ascent
+      Statistics about Ascent, a free server for the game `World of Warcraft'.
+
+    - battery
+      Batterycharge, -current and voltage of ACPI and PMU based laptop
+      batteries.
+
+    - bind
+      Name server and resolver statistics from the `statistics-channel'
+      interface of BIND 9.5, 9,6 and later.
+
+    - conntrack
+      Number of nf_conntrack entries.
+
+    - contextswitch
+      Number of context switches done by the operating system.
+
+    - cpu
+      CPU utilization: Time spent in the system, user, nice, idle, and related
+      states.
+
+    - cpufreq
+      CPU frequency (For laptops with speed step or a similar technology)
+
+    - curl
+      Parse statistics from websites using regular expressions.
+
+    - curl_json
+      Retrieves JSON data via cURL and parses it according to user
+      configuration.
+
+    - curl_xml
+      Retrieves XML data via cURL and parses it according to user
+      configuration.
+
+    - dbi
+      Executes SQL statements on various databases and interprets the returned
+      data.
+
+    - df
+      Mountpoint usage (Basically the values `df(1)' delivers)
+
+    - disk
+      Disk utilization: Sectors read/written, number of read/write actions,
+      average time an IO-operation took to complete.
+
+    - dns
+      DNS traffic: Query types, response codes, opcodes and traffic/octets
+      transfered.
+
+    - email
+      Email statistics: Count, traffic, spam scores and checks.
+      See collectd-email(5).
+
+    - entropy
+      Amount of entropy available to the system.
+
+    - exec
+      Values gathered by a custom program or script.
+      See collectd-exec(5).
+
+    - filecount
+      Count the number of files in directories.
+
+    - fscache
+      Linux file-system based caching framework statistics.
+
+    - gmond
+      Receive multicast traffic from Ganglia instances.
+
+    - hddtemp
+      Harddisk temperatures using hddtempd.
+
+    - interface
+      Interface traffic: Number of octets, packets and errors for each
+      interface.
+
+    - iptables
+      Iptables' counters: Number of bytes that were matched by a certain
+      iptables rule.
+
+    - ipmi
+      IPMI (Intelligent Platform Management Interface) sensors information.
+
+    - ipvs
+      IPVS connection statistics (number of connections, octets and packets
+      for each service and destination).
+      See http://www.linuxvirtualserver.org/software/index.html.
+
+    - irq
+      IRQ counters: Frequency in which certain interrupts occur.
+
+    - java
+      Integrates a `Java Virtual Machine' (JVM) to execute plugins in Java
+      bytecode. See “Configuring with libjvm” below.
+
+    - load
+      System load average over the last 1, 5 and 15 minutes.
+
+    - lpar
+      Detailed CPU statistics of the “Logical Partitions” virtualization
+      technique built into IBM's POWER processors.
+
+    - libvirt
+      CPU, disk and network I/O statistics from virtual machines.
+
+    - madwifi
+      Queries very detailed usage statistics from wireless LAN adapters and
+      interfaces that use the Atheros chipset and the MadWifi driver.
+
+    - mbmon
+      Motherboard sensors: temperature, fanspeed and voltage information,
+      using mbmon(1).
+
+    - memcachec
+      Query and parse data from a memcache daemon (memcached).
+
+    - memcached
+      Statistics of the memcached distributed caching system.
+      <http://www.danga.com/memcached/>
+
+    - memory
+      Memory utilization: Memory occupied by running processes, page cache,
+      buffer cache and free.
+
+    - modbus
+      Reads values from Modbus/TCP enabled devices. Supports reading values
+      from multiple "slaves" so gateway devices can be used.
+
+    - multimeter
+      Information provided by serial multimeters, such as the `Metex
+      M-4650CR'.
+
+    - mysql
+      MySQL server statistics: Commands issued, handlers triggered, thread
+      usage, query cache utilization and traffic/octets sent and received.
+
+    - netapp
+      Plugin to query performance values from a NetApp storage system using the
+      “Manage ONTAP” SDK provided by NetApp.
+
+    - netlink
+      Very detailed Linux network interface and routing statistics. You can get
+      (detailed) information on interfaces, qdiscs, classes, and, if you can
+      make use of it, filters.
+
+    - network
+      Receive values that were collected by other hosts. Large setups will
+      want to collect the data on one dedicated machine, and this is the
+      plugin of choice for that.
+
+    - nfs
+      NFS Procedures: Which NFS command were called how often. Only NFSv2 and
+      NFSv3 right now.
+
+    - nginx
+      Collects statistics from `nginx' (speak: engine X), a HTTP and mail
+      server/proxy.
+
+    - ntpd
+      NTP daemon statistics: Local clock drift, offset to peers, etc.
+
+    - nut
+      Network UPS tools: UPS current, voltage, power, charge, utilisation,
+      temperature, etc. See upsd(8).
+
+    - olsrd
+      Queries routing information from the “Optimized Link State Routing”
+      daemon.
+
+    - onewire (EXPERIMENTAL!)
+      Read onewire sensors using the owcapu library of the owfs project.
+      Please read in collectd.conf(5) why this plugin is experimental.
+
+    - openvpn
+      RX and TX of each client in openvpn-status.log (status-version 2).
+      <http://openvpn.net/index.php/documentation/howto.html>
+
+    - oracle
+      Query data from an Oracle database.
+
+    - perl
+      The perl plugin implements a Perl-interpreter into collectd. You can
+      write your own plugins in Perl and return arbitrary values using this
+      API. See collectd-perl(5).
+
+    - pinba
+      Receive and dispatch timing values from Pinba, a profiling extension for
+      PHP.
+
+    - ping
+      Network latency: Time to reach the default gateway or another given
+      host.
+
+    - postgresql
+      PostgreSQL database statistics: active server connections, transaction
+      numbers, block IO, table row manipulations.
+
+    - powerdns
+      PowerDNS name server statistics.
+
+    - processes
+      Process counts: Number of running, sleeping, zombie, ... processes.
+
+    - protocols
+      Counts various aspects of network protocols such as IP, TCP, UDP, etc.
+
+    - python
+      The python plugin implements a Python interpreter into collectd. This
+      makes it possible to write plugins in Python which are executed by
+      collectd without the need to start a heavy interpreter every interval.
+      See collectd-python(5) for details.
+
+    - redis
+      The redis plugin gathers information from a redis server, including:
+      uptime, used memory, total connections etc.
+
+    - routeros
+      Query interface and wireless registration statistics from RouterOS.
+
+    - rrdcached
+      RRDtool caching daemon (RRDcacheD) statistics.
+
+    - sensors
+      System sensors, accessed using lm_sensors: Voltages, temperatures and
+      fan rotation speeds.
+
+    - serial
+      RX and TX of serial interfaces. Linux only; needs root privileges.
+
+    - snmp
+      Read values from SNMP (Simple Network Management Protocol) enabled
+      network devices such as switches, routers, thermometers, rack monitoring
+      servers, etc. See collectd-snmp(5).
+
+    - swap
+      Pages swapped out onto harddisk or whatever is called `swap' by the OS..
+
+    - table
+      Parse table-like structured files.
+
+    - tail
+      Follows (tails) logfiles, parses them by lines and submits matched
+      values.
+
+    - tape
+      Bytes and operations read and written on tape devices. Solaris only.
+
+    - tcpconns
+      Number of TCP connections to specific local and remote ports.
+
+    - teamspeak2
+      TeamSpeak2 server statistics.
+
+    - ted
+      Plugin to read values from `The Energy Detective' (TED).
+
+    - thermal
+      Linux ACPI thermal zone information.
+
+    - tokyotyrant
+      Reads the number of records and file size from a running Tokyo Tyrant
+      server.
+
+    - uptime
+      System uptime statistics.
+
+    - users
+      Users currently logged in.
+
+    - varnish
+      Various statistics from Varnish, an HTTP accelerator.
+
+    - vmem
+      Virtual memory statistics, e. g. the number of page-ins/-outs or the
+      number of pagefaults.
+
+    - vserver
+      System resources used by Linux VServers.
+      See <http://linux-vserver.org/>.
+
+    - wireless
+      Link quality of wireless cards. Linux only.
+
+    - xmms
+      Bitrate and frequency of music played with XMMS.
+
+    - zfs_arc
+      Statistics for ZFS' “Adaptive Replacement Cache” (ARC).
+
+  * Output can be written or sent to various destinations by the following
+    plugins:
+
+    - amqp
+      Sends JSON-encoded data to an Advanced Message Queuing Protocol (AMQP)
+      server, such as RabbitMQ.
+
+    - csv
+      Write to comma separated values (CSV) files. This needs lots of
+      diskspace but is extremely portable and can be analysed with almost
+      every program that can analyse anything. Even Microsoft's Excel..
+
+    - 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.
+
+    - perl
+      Of course the values are propagated to plugins written in Perl, too, so
+      you can easily do weird stuff with the plugins we didn't dare think of
+      ;) See collectd-perl(5).
+
+    - python
+      It's possible to implement write plugins in Python using the python
+      plugin. See collectd-python(5) for details.
+
+    - rrdcached
+      Output to round-robin-database (RRD) files using the RRDtool caching
+      daemon (RRDcacheD) - see rrdcached(1). That daemon provides a general
+      implementation of the caching done by the `rrdtool' plugin.
+
+    - rrdtool
+      Output to round-robin-database (RRD) files using librrd. See rrdtool(1).
+      This is likely the most popular destination for such values. Since
+      updates to RRD-files are somewhat expensive this plugin can cache
+      updates to the files and write a bunch of updates at once, which lessens
+      system load a lot.
+
+    - unixsock
+      One can query the values from the unixsock plugin whenever they're
+      needed. Please read collectd-unixsock(5) for a description on how that's
+      done.
+
+    - write_http
+      Sends the values collected by collectd to a web-server using HTTP POST
+      requests. The transmitted data is either in a form understood by the
+      Exec plugin or formatted in JSON.
+
+    - write_redis
+      Sends the values to a Redis key-value database server.
+
+  * Logging is, as everything in collectd, provided by plugins. The following
+    plugins keep up informed about what's going on:
+
+    - logfile
+      Writes logmessages to a file or STDOUT/STDERR.
+
+    - perl
+      Log messages are propagated to plugins written in Perl as well.
+      See collectd-perl(5).
+
+    - python
+      It's possible to implement log plugins in Python using the python plugin.
+      See collectd-python(5) for details.
+
+    - syslog
+      Logs to the standard UNIX logging mechanism, syslog.
+
+  * Notifications can be handled by the following plugins:
+
+    - notify_desktop
+      Send a desktop notification to a notification daemon, as defined in
+      the Desktop Notification Specification. To actually display the
+      notifications, notification-daemon is required.
+      See http://www.galago-project.org/specs/notification/.
+
+    - notify_email
+      Send an E-mail with the notification message to the configured
+      recipients.
+
+    - exec
+      Execute a program or script to handle the notification.
+      See collectd-exec(5).
+
+    - logfile
+      Writes the notification message to a file or STDOUT/STDERR.
+
+    - network
+      Send the notification to a remote host to handle it somehow.
+
+    - perl
+      Notifications are propagated to plugins written in Perl as well.
+      See collectd-perl(5).
+
+    - python
+      It's possible to implement notification plugins in Python using the
+      python plugin. See collectd-python(5) for details.
+
+  * Value processing can be controlled using the "filter chain" infrastructure
+    and "matches" and "targets". The following plugins are available:
+
+    - match_empty_counter
+      Match counter values which are currently zero.
+
+    - match_hashed
+      Match values using a hash function of the hostname.
+
+    - match_regex
+      Match values by their identifier based on regular expressions.
+
+    - match_timediff
+      Match values with an invalid timestamp.
+
+    - match_value
+      Select values by their data sources' values.
+
+    - target_notification
+      Create and dispatch a notification.
+
+    - target_replace
+      Replace parts of an identifier using regular expressions.
+
+    - target_scale
+      Scale (multiply) values by an arbitrary value.
+
+    - target_set
+      Set (overwrite) entire parts of an identifier.
+
+  * Miscellaneous plugins:
+
+    - threshold
+      Checks values against configured thresholds and creates notifications if
+      values are out of bounds. See collectd-threshold(5) for details.
+
+    - uuid
+      Sets the hostname to an unique identifier. This is meant for setups
+      where each client may migrate to another physical host, possibly going
+      through one or more name changes in the process.
+
+  * Performance: Since collectd is running as a daemon it doesn't spend much
+    time starting up again and again. With the exception of the exec plugin no
+    processes are forked. Caching in output plugins, such as the rrdtool and
+    network plugins, makes sure your resources are used efficiently. Also,
+    since collectd is programmed multithreaded it benefits from hyperthreading
+    and multicore processors and makes sure that the daemon isn't idle if only
+    one plugin waits for an IO-operation to complete.
+
+  * Once set up, hardly any maintenance is necessary. Setup is kept as easy
+    as possible and the default values should be okay for most users.
+
+
+Operation
+---------
+
+  * collectd's configuration file can be found at `sysconfdir'/collectd.conf.
+    Run `collectd -h' for a list of builtin defaults. See `collectd.conf(5)'
+    for a list of options and a syntax description.
+
+  * When the `csv' or `rrdtool' plugins are loaded they'll write the values to
+    files. The usual place for these files is beneath `/var/lib/collectd'.
+
+  * When using some of the plugins, collectd needs to run as user root, since
+    only root can do certain things, such as craft ICMP packages needed to ping
+    other hosts. collectd should NOT be installed setuid root since it can be
+    used to overwrite valuable files!
+
+  * Sample scripts to generate graphs reside in `contrib/' in the source
+    package or somewhere near `/usr/share/doc/collectd' in most distributions.
+    Please be aware that those script are meant as a starting point for your
+    own experiments.. Some of them require the `RRDs' Perl module.
+    (`librrds-perl' on Debian) If you have written a more sophisticated
+    solution please share it with us.
+
+  * The RRAs of the automatically created RRD files depend on the `step'
+    and `heartbeat' settings given. If change these settings you may need to
+    re-create the files, losing all data. Please be aware of that when changing
+    the values and read the rrdtool(1) manpage thoroughly.
+
+
+collectd and chkrootkit
+-----------------------
+
+  If you are using the `dns' plugin chkrootkit(1) will report collectd as a
+  packet sniffer ("<iface>: PACKET SNIFFER(/usr/sbin/collectd[<pid>])"). The
+  plugin captures all UDP packets on port 53 to analyze the DNS traffic. In
+  this case, collectd is a legitimate sniffer and the report should be
+  considered to be a false positive. However, you might want to check that
+  this really is collectd and not some other, illegitimate sniffer.
+
+
+Prerequisites
+-------------
+
+  To compile collectd from source you will need:
+
+  * Usual suspects: C compiler, linker, preprocessor, make, ...
+
+  * A POSIX-threads (pthread) implementation.
+    Since gathering some statistics is slow (network connections, slow devices,
+    etc) the collectd is parallelized. The POSIX threads interface is being
+    used and should be found in various implementations for hopefully all
+    platforms.
+
+  * CoreFoundation.framework and IOKit.framework (optional)
+    For compiling on Darwin in general and the `apple_sensors' plugin in
+    particular.
+    <http://developer.apple.com/corefoundation/>
+
+  * libclntsh (optional)
+    Used by the `oracle' plugin.
+
+  * libcredis (optional)
+    Used by the redis plugin. Please note that you require a 0.2.2 version
+    or higher. <http://code.google.com/p/credis/>
+
+  * libcurl (optional)
+    If you want to use the `apache', `ascent', `curl', `nginx', or `write_http'
+    plugin.
+    <http://curl.haxx.se/>
+
+  * libdbi (optional)
+    Used by the `dbi' plugin to connect to various databases.
+    <http://libdbi.sourceforge.net/>
+
+  * libesmtp (optional)
+    For the `notify_email' plugin.
+    <http://www.stafford.uklinux.net/libesmtp/>
+
+  * libganglia (optional)
+    Used by the `gmond' plugin to process data received from Ganglia.
+    <http://ganglia.info/>
+
+  * libgcrypt (optional)
+    Used by the `network' plugin for encryption and authentication.
+    <http://www.gnupg.org/>
+
+  * libhal (optional)
+    If present, the uuid plugin will check for UUID from HAL.
+    <http://hal.freedesktop.org/>
+
+  * libiptc (optional)
+    For querying iptables counters.
+    <http://netfilter.org/>
+
+    If not found on the system, a version shipped with this distribution can
+    be used. It requires some Linux headers in /usr/include/linux. You can
+    force the build system to use the shipped version by specifying
+      --with-libiptc=shipped
+    when running the configure script.
+
+  * 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.
+    <http://openjdk.java.net/> (and others)
+
+  * libmemcached (optional)
+    Used by the `memcachec' plugin to connect to a memcache daemon.
+    <http://tangent.org/552/libmemcached.html>
+
+  * libmodbus (optional)
+    Used by the “modbus” plugin to communicate with Modbus/TCP devices. The
+    “modbus” plugin works with version 2.0.3 of the library – due to frequent
+    API changes other versions may or may not compile cleanly.
+    <http://www.libmodbus.org/>
+
+  * libmysqlclient (optional)
+    Unsurprisingly used by the `mysql' plugin.
+    <http://dev.mysql.com/>
+
+  * libnetapp (optional)
+    Required for the “netapp” plugin.
+    This library is part of the “Manage ONTAP SDK” published by NetApp.
+
+  * libnetlink (optional)
+    Used, obviously, for the `netlink' plugin.
+    <http://www.linuxfoundation.org/en/Net:Iproute2>
+
+  * libnetsnmp (optional)
+    For the `snmp' plugin.
+    <http://www.net-snmp.org/>
+
+  * libnotify (optional)
+    For the `notify_desktop' plugin.
+    <http://www.galago-project.org/>
+
+  * liboping (optional)
+    Used by the `ping' plugin to send and receive ICMP packets.
+    <http://verplant.org/liboping/>
+
+  * libowcapi (optional)
+    Used by the `onewire' plugin to read values from onewire sensors (or the
+    owserver(1) daemon).
+    <http://www.owfs.org/>
+
+  * libpcap (optional)
+    Used to capture packets by the `dns' plugin.
+    <http://www.tcpdump.org/>
+
+  * libperfstat (optional)
+    Used by various plugins to gather statistics under AIX.
+
+  * libperl (optional)
+    Obviously used by the `perl' plugin. The library has to be compiled with
+    ithread support (introduced in Perl 5.6.0).
+    <http://www.perl.org/>
+
+  * libpq (optional)
+    The PostgreSQL C client library used by the `postgresql' plugin.
+    <http://www.postgresql.org/>
+
+  * libprotobuf-c, protoc-c (optional)
+    Used by the `pinba' plugin to generate a parser for the network packets
+    sent by the Pinba PHP extension.
+    <http://code.google.com/p/protobuf-c/>
+
+  * libpython (optional)
+    Used by the `python' plugin. Currently, Python 2.3 and later and Python 3
+    are supported.
+    <http://www.python.org/>
+
+  * librabbitmq (optional; also called “rabbitmq-c”)
+    Used by the AMQP plugin for AMQP connections, for example to RabbitMQ.
+    <http://hg.rabbitmq.com/rabbitmq-c/>
+
+  * librouteros (optional)
+    Used by the `routeros' plugin to connect to a device running `RouterOS'.
+    <http://verplant.org/librouteros/>
+
+  * librrd (optional)
+    Used by the `rrdtool' and `rrdcached' plugins. The latter requires RRDtool
+    client support which was added after version 1.3 of RRDtool. Versions 1.0,
+    1.2 and 1.3 are known to work with the `rrdtool' plugin.
+    <http://oss.oetiker.ch/rrdtool/>
+
+  * librt, libsocket, libkstat, libdevinfo (optional)
+    Various standard Solaris libraries which provide system functions.
+    <http://developers.sun.com/solaris/>
+
+  * libsensors (optional)
+    To read from `lm_sensors', see the `sensors' plugin.
+    <http://www.lm-sensors.org/>
+
+  * libstatgrab (optional)
+    Used by various plugins to collect statistics on systems other than Linux
+    and/or Solaris.
+    <http://www.i-scream.org/libstatgrab/>
+
+  * libtokyotyrant (optional)
+    Used by the tokyotyrant plugin.
+    <http://1978th.net/tokyotyrant/>
+
+  * libupsclient/nut (optional)
+    For the `nut' plugin which queries nut's `upsd'.
+    <http://networkupstools.org/>
+
+  * libvirt (optional)
+    Collect statistics from virtual machines.
+    <http://libvirt.org/>
+
+  * libxml2 (optional)
+    Parse XML data. This is needed for the `ascent' and `libvirt' plugins.
+    <http://xmlsoft.org/>
+
+  * libxmms (optional)
+    <http://www.xmms.org/>
+
+  * libyajl (optional)
+    Parse JSON data. This is needed for the `curl_json' plugin.
+    <http://github.com/lloyd/yajl>
+
+  * libvarnish (optional)
+     Fetches statistics from a Varnish instance. This is needed for the Varnish plugin
+     <http://varnish-cache.org>
+
+Configuring / Compiling / Installing
+------------------------------------
+
+  To configure, build and install collectd with the default settings, run
+  `./configure && make && make install'.  For detailed, generic instructions
+  see INSTALL. For a complete list of configure options and their description,
+  run `./configure --help'.
+
+  By default, the configure script will check for all build dependencies and
+  disable all plugins whose requirements cannot be fulfilled (any other plugin
+  will be enabled). To enable a plugin, install missing dependencies (see
+  section `Prerequisites' above) and rerun `configure'. If you specify the
+  `--enable-<plugin>' configure option, the script will fail if the depen-
+  dencies for the specified plugin are not met. In that case you can force the
+  plugin to be built using the `--enable-<plugin>=force' configure option.
+  This will most likely fail though unless you're working in a very unusual
+  setup and you really know what you're doing. If you specify the
+  `--disable-<plugin>' configure option, the plugin will not be built. If you
+  specify the `--enable-all-plugins' or `--disable-all-plugins' configure
+  options, all plugins will be enabled or disabled respectively by default.
+  Explicitly enabling or disabling a plugin overwrites the default for the
+  specified plugin. These options are meant for package maintainers and should
+  not be used in everyday situations.
+
+  By default, collectd will be installed into `/opt/collectd'. You can adjust
+  this setting by specifying the `--prefix' configure option - see INSTALL for
+  details. If you pass DESTDIR=<path> to `make install', <path> will be
+  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 and LD-flags for the ‘Java’ plugin by hand, using the following three
+  (environment) variables:
+
+    - JAVA_CPPFLAGS
+    - JAVA_CFLAGS
+    - JAVA_LDFLAGS
+
+  For example (shortened for demonstration purposes):
+
+    ./configure JAVA_CPPFLAGS="-I$JAVA_HOME/include -I$JAVA_HOME/include/linux"
+
+  Adding "-ljvm" to the JAVA_LDFLAGS is done automatically, you don't have to
+  do that.
+
+Crosscompiling
+--------------
+
+  To compile correctly collectd needs to be able to initialize static
+  variables to NAN (Not A Number). Some C libraries, especially the GNU
+  libc, have a problem with that.
+
+  Luckily, with GCC it's possible to work around that problem: One can define
+  NAN as being (0.0 / 0.0) and `isnan' as `f != f'. However, to test this
+  ``implementation'' the configure script needs to compile and run a short
+  test program. Obviously running a test program when doing a cross-
+  compilation is, well, challenging.
+
+  If you run into this problem, you can use the `--with-nan-emulation'
+  configure option to force the use of this implementation. We can't promise
+  that the compiled binary actually behaves as it should, but since NANs
+  are likely never passed to the libm you have a good chance to be lucky.
+
+  Likewise, collectd needs to know the layout of doubles in memory, in order
+  to craft uniform network packets over different architectures. For this, it
+  needs to know how to convert doubles into the memory layout used by x86. The
+  configure script tries to figure this out by compiling and running a few
+  small test programs. This is of course not possible when cross-compiling.
+  You can use the `--with-fp-layout' option to tell the configure script which
+  conversion method to assume. Valid arguments are:
+
+    * `nothing'    (12345678 -> 12345678)
+    * `endianflip' (12345678 -> 87654321)
+    * `intswap'    (12345678 -> 56781234)
+
+
+Contact
+-------
+
+  For questions, bug reports, development information and basically all other
+  concerns please send an email to collectd's mailing list at
+  <collectd at verplant.org>.
+
+  For live discussion and more personal contact visit us in IRC, we're in
+  channel #collectd on freenode.
+
+
+Author
+------
+
+  Florian octo Forster <octo at verplant.org>,
+  Sebastian tokkee Harl <sh at tokkee.org>,
+  and many contributors (see `AUTHORS').
+
+  Please send bug reports and patches to the mailing list, see `Contact'
+  above.
+
diff --git a/TODO b/TODO
new file mode 100644 (file)
index 0000000..009eb7f
--- /dev/null
+++ b/TODO
@@ -0,0 +1,21 @@
+* 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/
diff --git a/bindings/Makefile.am b/bindings/Makefile.am
new file mode 100644 (file)
index 0000000..f39e9bb
--- /dev/null
@@ -0,0 +1,39 @@
+SUBDIRS =
+
+if BUILD_WITH_JAVA
+SUBDIRS += java
+endif
+
+EXTRA_DIST = perl/Makefile.PL \
+            perl/lib/Collectd.pm \
+            perl/lib/Collectd/Unixsock.pm \
+            perl/lib/Collectd/Plugins/Monitorus.pm \
+            perl/lib/Collectd/Plugins/OpenVZ.pm
+
+all-local: @PERL_BINDINGS@
+
+install-exec-local:
+       [ ! -f perl/Makefile ] || ( cd perl && $(MAKE) install )
+
+clean-local:
+       [ ! -f perl/Makefile ] || ( cd perl && $(MAKE) realclean )
+
+perl: perl/Makefile
+       cd perl && $(MAKE)
+
+perl/Makefile: .perl-directory-stamp perl/Makefile.PL \
+       $(top_builddir)/config.status
+       cd perl && @PERL@ Makefile.PL PREFIX=$(prefix) @PERL_BINDINGS_OPTIONS@
+
+.perl-directory-stamp:
+       if test ! -d perl; then \
+         mkdir -p perl/Collectd/Plugins; \
+         cp $(srcdir)/perl/Collectd.pm perl/; \
+         cp $(srcdir)/perl/Makefile.PL perl/; \
+         cp $(srcdir)/perl/Collectd/Unixsock.pm perl/Collectd/; \
+         cp $(srcdir)/perl/Collectd/Plugins/OpenVZ.pm perl/Collectd/Plugins/; \
+       fi
+       touch $@
+
+.PHONY: perl
+
diff --git a/bindings/java/Makefile.am b/bindings/java/Makefile.am
new file mode 100644 (file)
index 0000000..f8e936a
--- /dev/null
@@ -0,0 +1,48 @@
+EXTRA_DIST = org/collectd/api/CollectdConfigInterface.java \
+            org/collectd/api/CollectdFlushInterface.java \
+            org/collectd/api/CollectdInitInterface.java \
+            org/collectd/api/Collectd.java \
+            org/collectd/api/CollectdLogInterface.java \
+            org/collectd/api/CollectdMatchFactoryInterface.java \
+            org/collectd/api/CollectdMatchInterface.java \
+            org/collectd/api/CollectdNotificationInterface.java \
+            org/collectd/api/CollectdReadInterface.java \
+            org/collectd/api/CollectdShutdownInterface.java \
+            org/collectd/api/CollectdTargetFactoryInterface.java \
+            org/collectd/api/CollectdTargetInterface.java \
+            org/collectd/api/CollectdWriteInterface.java \
+            org/collectd/api/DataSet.java \
+            org/collectd/api/DataSource.java \
+            org/collectd/api/Notification.java \
+            org/collectd/api/OConfigItem.java \
+            org/collectd/api/OConfigValue.java \
+            org/collectd/api/PluginData.java \
+            org/collectd/api/ValueList.java \
+            org/collectd/java/GenericJMXConfConnection.java \
+            org/collectd/java/GenericJMXConfMBean.java \
+            org/collectd/java/GenericJMXConfValue.java \
+            org/collectd/java/GenericJMX.java \
+            org/collectd/java/JMXMemory.java
+
+java-build-stamp: org/collectd/api/*.java org/collectd/java/*.java
+       $(JAVAC) -d "." "$(srcdir)/org/collectd/api"/*.java
+       $(JAVAC) -d "." "$(srcdir)/org/collectd/java"/*.java
+       mkdir -p .libs
+       $(JAR) cf .libs/collectd-api.jar "org/collectd/api"/*.class
+       $(JAR) cf .libs/generic-jmx.jar "org/collectd/java"/*.class
+       touch "$@"
+
+all-local: java-build-stamp
+
+install-exec-local: java-build-stamp
+       mkdir -p "$(DESTDIR)$(pkgdatadir)/java"
+       $(INSTALL) -m 644 .libs/collectd-api.jar \
+               "$(DESTDIR)$(pkgdatadir)/java"
+       $(INSTALL) -m 644 .libs/generic-jmx.jar \
+               "$(DESTDIR)$(pkgdatadir)/java"
+
+clean-local:
+       rm -f "org/collectd/api"/*.class
+       rm -f "org/collectd/java"/*.class
+       rm -f .libs
+       rm -f "java-build-stamp"
diff --git a/bindings/java/org/collectd/api/Collectd.java b/bindings/java/org/collectd/api/Collectd.java
new file mode 100644 (file)
index 0000000..84e6592
--- /dev/null
@@ -0,0 +1,295 @@
+/*
+ * collectd/java - org/collectd/api/Collectd.java
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ */
+
+package org.collectd.api;
+
+/**
+ * Java API to internal functions of collectd.
+ *
+ * All functions in this class are {@code static}. You don't need to create an
+ * object of this class (in fact, you can't). Just call these functions
+ * directly.
+ *
+ * @author Florian Forster &lt;octo at verplant.org&gt;
+ */
+public class Collectd
+{
+
+  /**
+   * Constant for severity (log level) "error".
+   *
+   * @see CollectdLogInterface
+   */
+  public static final int LOG_ERR     = 3;
+
+  /**
+   * Constant for severity (log level) "warning".
+   *
+   * @see CollectdLogInterface
+   */
+  public static final int LOG_WARNING = 4;
+
+  /**
+   * Constant for severity (log level) "notice".
+   *
+   * @see CollectdLogInterface
+   */
+  public static final int LOG_NOTICE  = 5;
+
+  /**
+   * Constant for severity (log level) "info".
+   *
+   * @see CollectdLogInterface
+   */
+  public static final int LOG_INFO    = 6;
+
+  /**
+   * Constant for severity (log level) "debug".
+   *
+   * @see CollectdLogInterface
+   */
+  public static final int LOG_DEBUG   = 7;
+
+  /**
+   * Return value of match methods: No match.
+   *
+   * This is one of two valid return values from match callbacks, indicating
+   * that the passed {@link DataSet} and {@link ValueList} did not match.
+   *
+   * Do not use the numeric value directly, it is subject to change without
+   * notice!
+   *
+   * @see CollectdMatchInterface
+   */
+  public static final int FC_MATCH_NO_MATCH  = 0;
+
+  /**
+   * Return value of match methods: Match.
+   *
+   * This is one of two valid return values from match callbacks, indicating
+   * that the passed {@link DataSet} and {@link ValueList} did match.
+   *
+   * Do not use the numeric value directly, it is subject to change without
+   * notice!
+   *
+   * @see CollectdMatchInterface
+   */
+  public static final int FC_MATCH_MATCHES   = 1;
+
+  /**
+   * Return value of target methods: Continue.
+   *
+   * This is one of three valid return values from target callbacks, indicating
+   * that processing of the {@link ValueList} should continue.
+   *
+   * Do not use the numeric value directly, it is subject to change without
+   * notice!
+   *
+   * @see CollectdTargetInterface
+   */
+  public static final int FC_TARGET_CONTINUE = 0;
+
+  /**
+   * Return value of target methods: Stop.
+   *
+   * This is one of three valid return values from target callbacks, indicating
+   * that processing of the {@link ValueList} should stop immediately.
+   *
+   * Do not use the numeric value directly, it is subject to change without
+   * notice!
+   *
+   * @see CollectdTargetInterface
+   */
+  public static final int FC_TARGET_STOP     = 1;
+
+  /**
+   * Return value of target methods: Return.
+   *
+   * This is one of three valid return values from target callbacks, indicating
+   * that processing of the current chain should be stopped and processing of
+   * the {@link ValueList} should continue in the calling chain.
+   *
+   * Do not use the numeric value directly, it is subject to change without
+   * notice!
+   *
+   * @see CollectdTargetInterface
+   */
+  public static final int FC_TARGET_RETURN   = 2;
+
+  /**
+   * Java representation of collectd/src/plugin.h:plugin_register_config
+   *
+   * @return Zero when successful, non-zero otherwise.
+   * @see CollectdConfigInterface
+   */
+  native public static int registerConfig (String name,
+      CollectdConfigInterface object);
+
+  /**
+   * Java representation of collectd/src/plugin.h:plugin_register_init
+   *
+   * @return Zero when successful, non-zero otherwise.
+   * @see CollectdInitInterface
+   */
+  native public static int registerInit (String name,
+      CollectdInitInterface object);
+
+  /**
+   * Java representation of collectd/src/plugin.h:plugin_register_read
+   *
+   * @return Zero when successful, non-zero otherwise.
+   * @see CollectdReadInterface
+   */
+  native public static int registerRead (String name,
+      CollectdReadInterface object);
+
+  /**
+   * Java representation of collectd/src/plugin.h:plugin_register_write
+   *
+   * @return Zero when successful, non-zero otherwise.
+   * @see CollectdWriteInterface
+   */
+  native public static int registerWrite (String name,
+      CollectdWriteInterface object);
+
+  /**
+   * Java representation of collectd/src/plugin.h:plugin_register_flush
+   *
+   * @return Zero when successful, non-zero otherwise.
+   * @see CollectdFlushInterface
+   */
+  native public static int registerFlush (String name,
+      CollectdFlushInterface object);
+
+  /**
+   * Java representation of collectd/src/plugin.h:plugin_register_shutdown
+   *
+   * @return Zero when successful, non-zero otherwise.
+   * @see CollectdShutdownInterface
+   */
+  native public static int registerShutdown (String name,
+      CollectdShutdownInterface object);
+
+  /**
+   * Java representation of collectd/src/plugin.h:plugin_register_log
+   *
+   * @return Zero when successful, non-zero otherwise.
+   * @see CollectdLogInterface
+   */
+  native public static int registerLog (String name,
+      CollectdLogInterface object);
+
+  /**
+   * Java representation of collectd/src/plugin.h:plugin_register_notification
+   *
+   * @return Zero when successful, non-zero otherwise.
+   * @see CollectdNotificationInterface
+   */
+  native public static int registerNotification (String name,
+      CollectdNotificationInterface object);
+
+  /**
+   * Java representation of collectd/src/filter_chain.h:fc_register_match
+   *
+   * @return Zero when successful, non-zero otherwise.
+   * @see CollectdMatchFactoryInterface
+   */
+  native public static int registerMatch (String name,
+      CollectdMatchFactoryInterface object);
+
+  /**
+   * Java representation of collectd/src/filter_chain.h:fc_register_target
+   *
+   * @return Zero when successful, non-zero otherwise.
+   * @see CollectdTargetFactoryInterface
+   */
+  native public static int registerTarget (String name,
+      CollectdTargetFactoryInterface object);
+
+  /**
+   * Java representation of collectd/src/plugin.h:plugin_dispatch_values
+   *
+   * @return Zero when successful, non-zero otherwise.
+   */
+  native public static int dispatchValues (ValueList vl);
+
+  /**
+   * Java representation of collectd/src/plugin.h:plugin_dispatch_notification
+   *
+   * @return Zero when successful, non-zero otherwise.
+   */
+  native public static int dispatchNotification (Notification n);
+
+  /**
+   * Java representation of collectd/src/plugin.h:plugin_get_ds
+   *
+   * @return The appropriate {@link DataSet} object or {@code null} if no such
+   * type is registered.
+   */
+  native public static DataSet getDS (String type);
+
+  /**
+   * Java representation of collectd/src/plugin.h:plugin_log
+   */
+  native private static void log (int severity, String message);
+
+  /**
+   * Prints an error message.
+   */
+  public static void logError (String message)
+  {
+    log (LOG_ERR, message);
+  } /* void logError */
+
+  /**
+   * Prints a warning message.
+   */
+  public static void logWarning (String message)
+  {
+    log (LOG_WARNING, message);
+  } /* void logWarning */
+
+  /**
+   * Prints a notice.
+   */
+  public static void logNotice (String message)
+  {
+    log (LOG_NOTICE, message);
+  } /* void logNotice */
+
+  /**
+   * Prints an info message.
+   */
+  public static void logInfo (String message)
+  {
+    log (LOG_INFO, message);
+  } /* void logInfo */
+
+  /**
+   * Prints a debug message.
+   */
+  public static void logDebug (String message)
+  {
+    log (LOG_DEBUG, message);
+  } /* void logDebug */
+} /* class Collectd */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/bindings/java/org/collectd/api/CollectdConfigInterface.java b/bindings/java/org/collectd/api/CollectdConfigInterface.java
new file mode 100644 (file)
index 0000000..060f944
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * collectd/java - org/collectd/api/CollectdConfigInterface.java
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ */
+
+package org.collectd.api;
+
+/**
+ * Interface for objects implementing a config method.
+ *
+ * @author Florian Forster &lt;octo at verplant.org&gt;
+ * @see Collectd#registerConfig(String, CollectdConfigInterface)
+ */
+public interface CollectdConfigInterface
+{
+       public int config (OConfigItem ci);
+}
diff --git a/bindings/java/org/collectd/api/CollectdFlushInterface.java b/bindings/java/org/collectd/api/CollectdFlushInterface.java
new file mode 100644 (file)
index 0000000..410c61c
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * collectd/java - org/collectd/api/CollectdFlushInterface.java
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ */
+
+package org.collectd.api;
+
+/**
+ * Interface for objects implementing a flush method.
+ *
+ * @author Florian Forster &lt;octo at verplant.org&gt;
+ * @see Collectd#registerFlush
+ */
+public interface CollectdFlushInterface
+{
+       public int flush (Number timeout, String identifier);
+}
diff --git a/bindings/java/org/collectd/api/CollectdInitInterface.java b/bindings/java/org/collectd/api/CollectdInitInterface.java
new file mode 100644 (file)
index 0000000..fbfd306
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * collectd/java - org/collectd/api/CollectdInitInterface.java
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ */
+
+package org.collectd.api;
+
+/**
+ * Interface for objects implementing an init method.
+ *
+ * @author Florian Forster &lt;octo at verplant.org&gt;
+ * @see Collectd#registerInit
+ */
+public interface CollectdInitInterface
+{
+       public int init ();
+}
diff --git a/bindings/java/org/collectd/api/CollectdLogInterface.java b/bindings/java/org/collectd/api/CollectdLogInterface.java
new file mode 100644 (file)
index 0000000..ba0350a
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * collectd/java - org/collectd/api/CollectdLogInterface.java
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ */
+
+package org.collectd.api;
+
+/**
+ * Interface for objects implementing a log method.
+ *
+ * @author Florian Forster &lt;octo at verplant.org&gt;
+ * @see Collectd#registerLog
+ */
+public interface CollectdLogInterface
+{
+       public void log (int severity, String message);
+}
diff --git a/bindings/java/org/collectd/api/CollectdMatchFactoryInterface.java b/bindings/java/org/collectd/api/CollectdMatchFactoryInterface.java
new file mode 100644 (file)
index 0000000..7b1c71a
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * collectd/java - org/collectd/api/CollectdMatchFactoryInterface.java
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ */
+
+package org.collectd.api;
+
+/**
+ * Interface for objects implementing a "match factory".
+ *
+ * Objects implementing this interface are used to create objects implementing
+ * the CollectdMatchInterface interface.
+ *
+ * @author Florian Forster &lt;octo at verplant.org&gt;
+ * @see CollectdMatchInterface
+ * @see Collectd#registerMatch
+ */
+public interface CollectdMatchFactoryInterface
+{
+       /**
+        * Create a new "match" object.
+        *
+        * This method uses the configuration provided as argument to create a
+        * new object which must implement the {@link CollectdMatchInterface}
+        * interface.
+        *
+        * This function corresponds to the <code>create</code> member of the
+        * <code>src/filter_chain.h:match_proc_t</code> struct.
+        *
+        * @return New {@link CollectdMatchInterface} object.
+        */
+       public CollectdMatchInterface createMatch (OConfigItem ci);
+}
diff --git a/bindings/java/org/collectd/api/CollectdMatchInterface.java b/bindings/java/org/collectd/api/CollectdMatchInterface.java
new file mode 100644 (file)
index 0000000..cc8a99e
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * collectd/java - org/collectd/api/CollectdMatchInterface.java
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ */
+
+package org.collectd.api;
+
+/**
+ * Interface for objects implementing a match method.
+ *
+ * These objects are instantiated using objects which implement the
+ * CollectdMatchFactoryInterface interface. They are not instantiated by the
+ * daemon directly!
+ *
+ * @author Florian Forster &lt;octo at verplant.org&gt;
+ * @see CollectdMatchFactoryInterface
+ * @see Collectd#registerMatch
+ */
+public interface CollectdMatchInterface
+{
+       /**
+        * Callback method for matches.
+        *
+        * This method is called to decide whether or not a given ValueList
+        * matches or not. How this is determined is the is the main part of
+        * this function.
+        *
+        * @return One of {@link Collectd#FC_MATCH_NO_MATCH} and {@link Collectd#FC_MATCH_MATCHES}.
+        * @see CollectdMatchFactoryInterface
+        */
+       public int match (DataSet ds, ValueList vl);
+} /* public interface CollectdMatchInterface */
diff --git a/bindings/java/org/collectd/api/CollectdNotificationInterface.java b/bindings/java/org/collectd/api/CollectdNotificationInterface.java
new file mode 100644 (file)
index 0000000..d278fe2
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * collectd/java - org/collectd/api/CollectdNotificationInterface.java
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ */
+
+package org.collectd.api;
+
+/**
+ * Interface for objects implementing a notification method.
+ *
+ * @author Florian Forster &lt;octo at verplant.org&gt;
+ * @see Collectd#registerNotification
+ */
+public interface CollectdNotificationInterface
+{
+       public int notification (Notification n);
+}
diff --git a/bindings/java/org/collectd/api/CollectdReadInterface.java b/bindings/java/org/collectd/api/CollectdReadInterface.java
new file mode 100644 (file)
index 0000000..67f1898
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * collectd/java - org/collectd/api/CollectdReadInterface.java
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ */
+
+package org.collectd.api;
+
+/**
+ * Interface for objects implementing a read method.
+ *
+ * Objects implementing this interface can be registered with the daemon. Their
+ * read method is then called periodically to acquire and submit values.
+ *
+ * @author Florian Forster &lt;octo at verplant.org&gt;
+ * @see Collectd#registerRead
+ */
+public interface CollectdReadInterface
+{
+       /**
+        * Callback method for read plugins.
+        *
+        * This method is called once every few seconds (depends on the
+        * configuration of the daemon). It is supposed to gather values in
+        * some way and submit them to the daemon using
+        * {@link Collectd#dispatchValues}.
+        *
+        * @return zero when successful, non-zero when an error occurred.
+        * @see Collectd#dispatchValues
+        */
+       public int read ();
+}
diff --git a/bindings/java/org/collectd/api/CollectdShutdownInterface.java b/bindings/java/org/collectd/api/CollectdShutdownInterface.java
new file mode 100644 (file)
index 0000000..108c54e
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * collectd/java - org/collectd/api/CollectdShutdownInterface.java
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ */
+
+package org.collectd.api;
+
+/**
+ * Interface for objects implementing a shutdown method.
+ *
+ * @author Florian Forster &lt;octo at verplant.org&gt;
+ * @see Collectd#registerShutdown
+ */
+public interface CollectdShutdownInterface
+{
+       public int shutdown ();
+}
diff --git a/bindings/java/org/collectd/api/CollectdTargetFactoryInterface.java b/bindings/java/org/collectd/api/CollectdTargetFactoryInterface.java
new file mode 100644 (file)
index 0000000..65f6181
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * collectd/java - org/collectd/api/CollectdTargetFactoryInterface.java
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ */
+
+package org.collectd.api;
+
+/**
+ * Interface for objects implementing a "target factory".
+ *
+ * Objects implementing this interface are used to create objects implementing
+ * the CollectdTargetInterface interface.
+ *
+ * @author Florian Forster &lt;octo at verplant.org&gt;
+ * @see CollectdTargetInterface
+ * @see Collectd#registerTarget
+ */
+public interface CollectdTargetFactoryInterface
+{
+       /**
+        * Create a new "target" object.
+        *
+        * This method uses the configuration provided as argument to create a
+        * new object which must implement the {@link CollectdTargetInterface}
+        * interface.
+        *
+        * This function corresponds to the {@code create} member of the
+        * {@code src/filter_chain.h:target_proc_t} struct.
+        *
+        * @return New {@link CollectdTargetInterface} object.
+        */
+       public CollectdTargetInterface createTarget (OConfigItem ci);
+}
diff --git a/bindings/java/org/collectd/api/CollectdTargetInterface.java b/bindings/java/org/collectd/api/CollectdTargetInterface.java
new file mode 100644 (file)
index 0000000..74412a3
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * collectd/java - org/collectd/api/CollectdTargetInterface.java
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ */
+
+package org.collectd.api;
+
+/**
+ * Interface for objects implementing a target method.
+ *
+ * These objects are instantiated using objects which implement the
+ * CollectdTargetFactoryInterface interface. They are not instantiated by the
+ * daemon directly!
+ *
+ * @author Florian Forster &lt;octo at verplant.org&gt;
+ * @see CollectdTargetFactoryInterface
+ * @see Collectd#registerTarget
+ */
+public interface CollectdTargetInterface
+{
+       /**
+        * Callback method for targets.
+        *
+        * This method is called to perform some action on the given ValueList.
+        * What precisely is done depends entirely on the implementing class.
+        *
+        * @return One of: {@link Collectd#FC_TARGET_CONTINUE},
+        * {@link Collectd#FC_TARGET_STOP}, {@link Collectd#FC_TARGET_RETURN}
+        * @see CollectdTargetFactoryInterface
+        */
+       public int invoke (DataSet ds, ValueList vl);
+} /* public interface CollectdTargetInterface */
diff --git a/bindings/java/org/collectd/api/CollectdWriteInterface.java b/bindings/java/org/collectd/api/CollectdWriteInterface.java
new file mode 100644 (file)
index 0000000..28e0230
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * collectd/java - org/collectd/api/CollectdWriteInterface.java
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ */
+
+package org.collectd.api;
+
+/**
+ * Interface for objects implementing a write method.
+ *
+ * @author Florian Forster &lt;octo at verplant.org&gt;
+ * @see Collectd#registerWrite
+ */
+public interface CollectdWriteInterface
+{
+       public int write (ValueList vl);
+}
diff --git a/bindings/java/org/collectd/api/DataSet.java b/bindings/java/org/collectd/api/DataSet.java
new file mode 100644 (file)
index 0000000..9823073
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * collectd/java - org/collectd/api/OConfigItem.java
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ */
+
+package org.collectd.api;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Java representation of collectd/src/plugin.h:data_set_t structure.
+ *
+ * @author Florian Forster &lt;octo at verplant.org&gt;
+ */
+public class DataSet
+{
+    private String _type;
+    private List<DataSource> _ds;
+
+    private DataSet ()
+    {
+        this._type = null;
+        this._ds = new ArrayList<DataSource> ();
+    }
+
+    public DataSet (String type)
+    {
+        this._type = type;
+        this._ds = new ArrayList<DataSource> ();
+    }
+
+    public DataSet (String type, DataSource dsrc)
+    {
+        this._type = type;
+        this._ds = new ArrayList<DataSource> ();
+        this._ds.add (dsrc);
+    }
+
+    public DataSet (String type, List<DataSource> ds)
+    {
+        this._type = type;
+        this._ds = ds;
+    }
+
+    public void setType (String type)
+    {
+        this._type = type;
+    }
+
+    public String getType ()
+    {
+        return (this._type);
+    }
+
+    public void addDataSource (DataSource dsrc)
+    {
+        this._ds.add (dsrc);
+    }
+
+    public List<DataSource> getDataSources ()
+    {
+        return (this._ds);
+    }
+
+    public String toString ()
+    {
+        StringBuffer sb = new StringBuffer ();
+        int i;
+
+        sb.append (this._type);
+        for (i = 0; i < this._ds.size (); i++)
+        {
+            if (i == 0)
+                sb.append ("\t");
+            else
+                sb.append (", ");
+            sb.append (this._ds.get (i).toString ());
+        }
+
+        return (sb.toString ());
+    }
+
+    static public DataSet parseDataSet (String str)
+    {
+        DataSet ds = new DataSet ();
+        String[] fields;
+        int i;
+
+        str = str.trim();
+        if (str.length() == 0) {
+            return (null);
+        }
+        if (str.charAt(0) == '#') {
+            return (null);
+        }
+
+        fields = str.split ("\\s+");
+        if (fields.length < 2)
+            return (null);
+
+        ds._type = fields[0];
+
+        for (i = 1; i < fields.length; i++) {
+            DataSource dsrc;
+
+            dsrc = DataSource.parseDataSource (fields[i]);
+            if (dsrc == null)
+                break;
+
+            ds._ds.add (dsrc);
+        }
+
+        if (i < fields.length)
+            return (null);
+
+        return (ds);
+    } /* DataSet parseDataSet */
+} /* class DataSet */
+
+/* vim: set sw=4 sts=4 et : */
diff --git a/bindings/java/org/collectd/api/DataSource.java b/bindings/java/org/collectd/api/DataSource.java
new file mode 100644 (file)
index 0000000..ba132d3
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * jcollectd
+ * Copyright (C) 2009 Hyperic, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+package org.collectd.api;
+
+/**
+ * Java representation of collectd/src/plugin.h:data_source_t structure. 
+ */
+public class DataSource {
+    public static final int TYPE_COUNTER  = 0;
+    public static final int TYPE_GAUGE    = 1;
+    public static final int TYPE_DERIVE   = 2;
+    public static final int TYPE_ABSOLUTE = 3;
+
+    static final String COUNTER  = "COUNTER";
+    static final String GAUGE    = "GAUGE";
+    static final String DERIVE   = "DERIVE";
+    static final String ABSOLUTE = "ABSOLUTE";
+
+    static final String NAN = "U";
+    private static final String[] TYPES = { COUNTER, GAUGE, DERIVE, ABSOLUTE };
+
+    String _name;
+    int _type;
+    double _min;
+    double _max;
+
+    public DataSource (String name, int type, double min, double max) {
+        this._name = name;
+        this._type = TYPE_GAUGE;
+        if (type == TYPE_COUNTER)
+            this._type = TYPE_COUNTER;
+        else if (type == TYPE_DERIVE)
+            this._type = TYPE_DERIVE;
+        else if (type == TYPE_ABSOLUTE)
+            this._type = TYPE_ABSOLUTE;
+        this._min = min;
+        this._max = max;
+    }
+
+    /* Needed in parseDataSource below. Other code should use the above
+     * constructor or `parseDataSource'. */
+    private DataSource () {
+        this._type = TYPE_GAUGE;
+    }
+
+    public String getName() {
+        return _name;
+    }
+
+    public void setName(String name) {
+        _name = name;
+    }
+
+    public int getType() {
+        return _type;
+    }
+
+    public void setType(int type) {
+        _type = type;
+    }
+
+    public double getMin() {
+        return _min;
+    }
+
+    public void setMin(double min) {
+        _min = min;
+    }
+
+    public double getMax() {
+        return _max;
+    }
+
+    public void setMax(double max) {
+        _max = max;
+    }
+
+    static double toDouble(String val) {
+        if (val.equals(NAN)) {
+            return Double.NaN;
+        }
+        else {
+            return Double.parseDouble(val);
+        }
+    }
+
+    private String asString(double val) {
+        if (Double.isNaN(val)) {
+            return NAN;
+        }
+        else {
+            return String.valueOf(val);
+        }
+    }
+
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        final char DLM = ':';
+        sb.append(_name).append(DLM);
+        sb.append(TYPES[_type]).append(DLM);
+        sb.append(asString(_min)).append(DLM);
+        sb.append(asString(_max));
+        return sb.toString();
+    }
+
+    static public DataSource parseDataSource (String str)
+    {
+        String[] fields;
+        int str_len = str.length ();
+        DataSource dsrc = new DataSource ();
+
+        /* Ignore trailing commas. This makes it easier for parsing code. */
+        if (str.charAt (str_len - 1) == ',') {
+            str = str.substring (0, str_len - 1);
+        }
+
+        fields = str.split(":");
+        if (fields.length != 4)
+            return (null);
+
+        dsrc._name = fields[0];
+
+        if (fields[1].equals (DataSource.GAUGE)) {
+            dsrc._type  = TYPE_GAUGE;
+        }
+        else {
+            dsrc._type  = TYPE_COUNTER;
+        }
+
+        dsrc._min =  toDouble (fields[2]);
+        dsrc._max =  toDouble (fields[3]);
+
+        return (dsrc);
+    } /* DataSource parseDataSource */
+}
+
+/* vim: set sw=4 sts=4 et : */
diff --git a/bindings/java/org/collectd/api/Notification.java b/bindings/java/org/collectd/api/Notification.java
new file mode 100644 (file)
index 0000000..cfc2186
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * jcollectd
+ * Copyright (C) 2009 Hyperic, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+package org.collectd.api;
+
+/**
+ * Java representation of collectd/src/plugin.h:notfication_t structure.
+ */
+public class Notification extends PluginData {
+    public static final int FAILURE = 1;
+    public static final int WARNING = 2;
+    public static final int OKAY    = 4;
+
+    public static String[] SEVERITY = {
+        "FAILURE",
+        "WARNING",
+        "OKAY",
+        "UNKNOWN"
+    };
+
+    private int _severity;
+    private String _message;
+
+    public Notification () {
+        _severity = 0;
+        _message = "Initial notification message";
+    }
+
+    public Notification (PluginData pd) {
+        super (pd);
+        _severity = 0;
+        _message = "Initial notification message";
+    }
+
+    public void setSeverity (int severity) {
+        if ((severity == FAILURE)
+                || (severity == WARNING)
+                || (severity == OKAY))
+            this._severity = severity;
+    }
+
+    public int getSeverity() {
+        return _severity;
+    }
+
+    public String getSeverityString() {
+        switch (_severity) {
+            case FAILURE:
+                return SEVERITY[0];
+            case WARNING:
+                return SEVERITY[1];
+            case OKAY:
+                return SEVERITY[2];
+            default:
+                return SEVERITY[3];
+        }
+    }
+
+    public void setMessage (String message) {
+        this._message = message;
+    }
+
+    public String getMessage() {
+        return _message;
+    }
+
+    public String toString() {
+        StringBuffer sb = new StringBuffer(super.toString());
+        sb.append(" [").append(getSeverityString()).append("] ");
+        sb.append(_message);
+        return sb.toString();
+    }
+}
diff --git a/bindings/java/org/collectd/api/OConfigItem.java b/bindings/java/org/collectd/api/OConfigItem.java
new file mode 100644 (file)
index 0000000..4c6a778
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * collectd/java - org/collectd/api/OConfigItem.java
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ */
+
+package org.collectd.api;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Java representation of collectd/src/liboconfig/oconfig.h:oconfig_item_t structure.
+ *
+ * @author Florian Forster &lt;octo at verplant.org&gt;
+ */
+public class OConfigItem
+{
+  private String _key = null;
+  private List<OConfigValue> _values   = new ArrayList<OConfigValue> ();
+  private List<OConfigItem>  _children = new ArrayList<OConfigItem> ();
+
+  public OConfigItem (String key)
+  {
+    _key = key;
+  } /* OConfigItem (String key) */
+
+  public String getKey ()
+  {
+    return (_key);
+  } /* String getKey () */
+
+  public void addValue (OConfigValue cv)
+  {
+    _values.add (cv);
+  } /* void addValue (OConfigValue cv) */
+
+  public void addValue (String s)
+  {
+    _values.add (new OConfigValue (s));
+  } /* void addValue (String s) */
+
+  public void addValue (Number n)
+  {
+    _values.add (new OConfigValue (n));
+  } /* void addValue (String s) */
+
+  public void addValue (boolean b)
+  {
+    _values.add (new OConfigValue (b));
+  } /* void addValue (String s) */
+
+  public List<OConfigValue> getValues ()
+  {
+    return (_values);
+  } /* List<OConfigValue> getValues () */
+
+  public void addChild (OConfigItem ci)
+  {
+    _children.add (ci);
+  } /* void addChild (OConfigItem ci) */
+
+  public List<OConfigItem> getChildren ()
+  {
+    return (_children);
+  } /* List<OConfigItem> getChildren () */
+
+  public String toString ()
+  {
+    return (new String ("{ key: " + _key + "; "
+          + "values: " + _values.toString () + "; "
+          + "children: " + _children.toString () + "; }"));
+  } /* String toString () */
+} /* class OConfigItem */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/bindings/java/org/collectd/api/OConfigValue.java b/bindings/java/org/collectd/api/OConfigValue.java
new file mode 100644 (file)
index 0000000..1ebafff
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * collectd/java - org/collectd/api/OConfigValue.java
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ */
+
+package org.collectd.api;
+
+/**
+ * Java representation of collectd/src/liboconfig/oconfig.h:oconfig_value_t structure.
+ *
+ * @author Florian Forster &lt;octo at verplant.org&gt;
+ */
+public class OConfigValue
+{
+  public static final int OCONFIG_TYPE_STRING  = 0;
+  public static final int OCONFIG_TYPE_NUMBER  = 1;
+  public static final int OCONFIG_TYPE_BOOLEAN = 2;
+
+  private int     _type;
+  private String  _value_string;
+  private Number  _value_number;
+  private boolean _value_boolean;
+
+  public OConfigValue (String s)
+  {
+    _type = OCONFIG_TYPE_STRING;
+    _value_string  = s;
+    _value_number  = null;
+    _value_boolean = false;
+  } /* OConfigValue (String s) */
+
+  public OConfigValue (Number n)
+  {
+    _type = OCONFIG_TYPE_NUMBER;
+    _value_string  = null;
+    _value_number  = n;
+    _value_boolean = false;
+  } /* OConfigValue (String s) */
+
+  public OConfigValue (boolean b)
+  {
+    _type = OCONFIG_TYPE_BOOLEAN;
+    _value_string  = null;
+    _value_number  = null;
+    _value_boolean = b;
+  } /* OConfigValue (String s) */
+
+  public int getType ()
+  {
+    return (_type);
+  } /* int getType */
+
+  public String getString ()
+  {
+    return (_value_string);
+  } /* String getString */
+
+  public Number getNumber ()
+  {
+    return (_value_number);
+  } /* String getString */
+
+  public boolean getBoolean ()
+  {
+    return (_value_boolean);
+  } /* String getString */
+
+  public String toString ()
+  {
+    if (_type == OCONFIG_TYPE_STRING)
+      return (_value_string);
+    else if (_type == OCONFIG_TYPE_NUMBER)
+      return (_value_number.toString ());
+    else if (_type == OCONFIG_TYPE_BOOLEAN)
+      return (Boolean.toString (_value_boolean));
+    return (null);
+  } /* String toString () */
+} /* class OConfigValue */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/bindings/java/org/collectd/api/PluginData.java b/bindings/java/org/collectd/api/PluginData.java
new file mode 100644 (file)
index 0000000..26b0206
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * jcollectd
+ * Copyright (C) 2009 Hyperic, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+package org.collectd.api;
+
+import java.util.Date;
+
+/**
+ * Shared members of value_list_t and notification_t structures.
+ */
+public class PluginData {
+
+    protected long _time = 0;
+    protected String _host;
+    protected String _plugin;
+    protected String _pluginInstance = "";
+    protected String _type = "";
+    protected String _typeInstance = "";
+
+    public PluginData() {
+        
+    }
+
+    public PluginData(PluginData pd) {
+        _time = pd._time;
+        _host = pd._host;
+        _plugin = pd._plugin;
+        _pluginInstance = pd._pluginInstance;
+        _type = pd._type;
+        _typeInstance = pd._typeInstance;
+    }
+
+    public long getTime() {
+        return _time;
+    }
+
+    public void setTime(long time) {
+        _time = time;
+    }
+
+    public String getHost() {
+        return _host;
+    }
+
+    public void setHost(String host) {
+        _host = host;
+    }
+
+    public String getPlugin() {
+        return _plugin;
+    }
+
+    public void setPlugin(String plugin) {
+        _plugin = plugin;
+    }
+
+    public String getPluginInstance() {
+        return _pluginInstance;
+    }
+
+    public void setPluginInstance(String pluginInstance) {
+        _pluginInstance = pluginInstance;
+    }
+
+    public String getType() {
+        return _type;
+    }
+
+    public void setType(String type) {
+        _type = type;
+    }
+
+    public String getTypeInstance() {
+        return _typeInstance;
+    }
+
+    public void setTypeInstance(String typeInstance) {
+        _typeInstance = typeInstance;
+    }
+
+    public boolean defined(String val) {
+        return (val != null) && (val.length() > 0);
+    }
+
+    public String getSource() {
+        final char DLM = '/';
+        StringBuffer sb = new StringBuffer();
+        if (defined(_host)) {
+            sb.append(_host);
+        }
+        if (defined(_plugin)) {
+            sb.append(DLM).append(_plugin);
+        }
+        if (defined(_pluginInstance)) {
+            sb.append(DLM).append(_pluginInstance);
+        }
+        if (defined(_type)) {
+            sb.append(DLM).append(_type);
+        }
+        if (defined(_typeInstance)) {
+            sb.append(DLM).append(_typeInstance);
+        }
+        return sb.toString();        
+    }
+
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        sb.append('[').append(new Date(_time)).append("] ");
+        sb.append(getSource());
+        return sb.toString();
+    }
+}
diff --git a/bindings/java/org/collectd/api/ValueList.java b/bindings/java/org/collectd/api/ValueList.java
new file mode 100644 (file)
index 0000000..b8d6f40
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * jcollectd
+ * Copyright (C) 2009 Hyperic, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+package org.collectd.api;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Java representation of collectd/src/plugin.h:value_list_t structure.
+ */
+public class ValueList extends PluginData {
+
+    private List<Number> _values = new ArrayList<Number>();
+    private DataSet _ds;
+
+    private long _interval = 0;
+
+    public ValueList() {
+        
+    }
+
+    public ValueList(PluginData pd) {
+        super(pd);
+    }
+
+    public ValueList(ValueList vl) {
+        this((PluginData)vl);
+        _interval = vl._interval;
+        _values.addAll(vl.getValues());
+       _ds = vl._ds;
+    }
+
+    public List<Number> getValues() {
+        return _values;
+    }
+
+    public void setValues(List<Number> values) {
+        _values = values;
+    }
+
+    public void addValue(Number value) {
+        _values.add(value);
+    }
+
+    /* Used by the network parsing code */
+    public void clearValues () {
+        _values.clear ();
+    }
+
+    /**
+     * @deprecated Use {@link #getDataSet()} instead.
+     */
+    public List<DataSource> getDataSource() {
+        if (_ds == null)
+            return null;
+        return _ds.getDataSources ();
+    }
+
+    public DataSet getDataSet () {
+        return _ds;
+    }
+
+    public void setDataSet (DataSet ds) {
+        _ds = ds;
+    }
+
+    /**
+     * @deprecated Use {@link #setDataSet(DataSet)} instead.
+     */
+    public void setDataSource(List<DataSource> dsrc) {
+        _ds = new DataSet (_type, dsrc);
+    }
+
+    /**
+     * Returns the interval (in milliseconds) of the value list.
+     */
+    public long getInterval() {
+        return _interval;
+    }
+
+    /**
+     * Sets the interval (in milliseconds) of the value list.
+     */
+    public void setInterval(long interval) {
+        _interval = interval;
+    }
+
+    public String toString() {
+        StringBuffer sb = new StringBuffer(super.toString());
+        sb.append("=[");
+        List<DataSource> ds = getDataSource();
+        int size = _values.size();
+        for (int i=0; i<size; i++) {
+            Number val = _values.get(i);
+            String name;
+            if (ds == null) {
+                name = "unknown" + i;
+            }
+            else {
+                name = ds.get(i).getName();
+            }
+            sb.append(name).append('=').append(val);
+            if (i < size-1) {
+                sb.append(',');
+            }
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+}
+
+/* vim: set sw=4 sts=4 et : */
diff --git a/bindings/java/org/collectd/java/GenericJMX.java b/bindings/java/org/collectd/java/GenericJMX.java
new file mode 100644 (file)
index 0000000..319615c
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * collectd/java - org/collectd/java/GenericJMX.java
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ */
+
+package org.collectd.java;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.collectd.api.Collectd;
+import org.collectd.api.CollectdConfigInterface;
+import org.collectd.api.CollectdInitInterface;
+import org.collectd.api.CollectdReadInterface;
+import org.collectd.api.CollectdShutdownInterface;
+import org.collectd.api.OConfigValue;
+import org.collectd.api.OConfigItem;
+
+public class GenericJMX implements CollectdConfigInterface,
+       CollectdReadInterface,
+       CollectdShutdownInterface
+{
+  static private Map<String,GenericJMXConfMBean> _mbeans
+    = new TreeMap<String,GenericJMXConfMBean> ();
+
+  private List<GenericJMXConfConnection> _connections = null;
+
+  public GenericJMX ()
+  {
+    Collectd.registerConfig   ("GenericJMX", this);
+    Collectd.registerRead     ("GenericJMX", this);
+    Collectd.registerShutdown ("GenericJMX", this);
+
+    this._connections = new ArrayList<GenericJMXConfConnection> ();
+  }
+
+  public int config (OConfigItem ci) /* {{{ */
+  {
+    List<OConfigItem> children;
+    int i;
+
+    Collectd.logDebug ("GenericJMX plugin: config: ci = " + ci + ";");
+
+    children = ci.getChildren ();
+    for (i = 0; i < children.size (); i++)
+    {
+      OConfigItem child;
+      String key;
+
+      child = children.get (i);
+      key = child.getKey ();
+      if (key.equalsIgnoreCase ("MBean"))
+      {
+        try
+        {
+          GenericJMXConfMBean mbean = new GenericJMXConfMBean (child);
+          putMBean (mbean);
+        }
+        catch (IllegalArgumentException e)
+        {
+          Collectd.logError ("GenericJMX plugin: "
+              + "Evaluating `MBean' block failed: " + e);
+        }
+      }
+      else if (key.equalsIgnoreCase ("Connection"))
+      {
+        try
+        {
+          GenericJMXConfConnection conn = new GenericJMXConfConnection (child);
+          this._connections.add (conn);
+        }
+        catch (IllegalArgumentException e)
+        {
+          Collectd.logError ("GenericJMX plugin: "
+              + "Evaluating `Connection' block failed: " + e);
+        }
+      }
+      else
+      {
+        Collectd.logError ("GenericJMX plugin: Unknown config option: " + key);
+      }
+    } /* for (i = 0; i < children.size (); i++) */
+
+    return (0);
+  } /* }}} int config */
+
+  public int read () /* {{{ */
+  {
+    for (int i = 0; i < this._connections.size (); i++)
+    {
+      try
+      {
+        this._connections.get (i).query ();
+      }
+      catch (Exception e)
+      {
+        Collectd.logError ("GenericJMX: Caught unexpected exception: " + e);
+        e.printStackTrace ();
+      }
+    }
+
+    return (0);
+  } /* }}} int read */
+
+  public int shutdown () /* {{{ */
+  {
+    System.out.print ("org.collectd.java.GenericJMX.Shutdown ();\n");
+    this._connections = null;
+    return (0);
+  } /* }}} int shutdown */
+
+  /*
+   * static functions
+   */
+  static public GenericJMXConfMBean getMBean (String alias)
+  {
+    return (_mbeans.get (alias));
+  }
+
+  static private void putMBean (GenericJMXConfMBean mbean)
+  {
+    Collectd.logDebug ("GenericJMX.putMBean: Adding " + mbean.getName ());
+    _mbeans.put (mbean.getName (), mbean);
+  }
+} /* class GenericJMX */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/bindings/java/org/collectd/java/GenericJMXConfConnection.java b/bindings/java/org/collectd/java/GenericJMXConfConnection.java
new file mode 100644 (file)
index 0000000..0c81bc9
--- /dev/null
@@ -0,0 +1,238 @@
+/*
+ * collectd/java - org/collectd/java/GenericJMXConfConnection.java
+ * Copyright (C) 2009,2010  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ */
+
+package org.collectd.java;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Iterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import javax.management.MBeanServerConnection;
+import javax.management.ObjectName;
+import javax.management.MalformedObjectNameException;
+
+import javax.management.remote.JMXServiceURL;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+
+import org.collectd.api.Collectd;
+import org.collectd.api.PluginData;
+import org.collectd.api.OConfigValue;
+import org.collectd.api.OConfigItem;
+
+class GenericJMXConfConnection
+{
+  private String _username = null;
+  private String _password = null;
+  private String _host = null;
+  private String _instance_prefix = null;
+  private String _service_url = null;
+  private MBeanServerConnection _jmx_connection = null;
+  private List<GenericJMXConfMBean> _mbeans = null;
+
+  /*
+   * private methods
+   */
+  private String getConfigString (OConfigItem ci) /* {{{ */
+  {
+    List<OConfigValue> values;
+    OConfigValue v;
+
+    values = ci.getValues ();
+    if (values.size () != 1)
+    {
+      Collectd.logError ("GenericJMXConfConnection: The " + ci.getKey ()
+          + " configuration option needs exactly one string argument.");
+      return (null);
+    }
+
+    v = values.get (0);
+    if (v.getType () != OConfigValue.OCONFIG_TYPE_STRING)
+    {
+      Collectd.logError ("GenericJMXConfConnection: The " + ci.getKey ()
+          + " configuration option needs exactly one string argument.");
+      return (null);
+    }
+
+    return (v.getString ());
+  } /* }}} String getConfigString */
+
+private void connect () /* {{{ */
+{
+  JMXServiceURL service_url;
+  JMXConnector connector;
+  Map environment;
+
+  if (_jmx_connection != null)
+    return;
+
+  environment = null;
+  if (this._password != null)
+  {
+    String[] credentials;
+
+    if (this._username == null)
+      this._username = new String ("monitorRole");
+
+    credentials = new String[] { this._username, this._password };
+
+    environment = new HashMap ();
+    environment.put (JMXConnector.CREDENTIALS, credentials);
+  }
+
+  try
+  {
+    service_url = new JMXServiceURL (this._service_url);
+    connector = JMXConnectorFactory.connect (service_url, environment);
+    _jmx_connection = connector.getMBeanServerConnection ();
+  }
+  catch (Exception e)
+  {
+    Collectd.logError ("GenericJMXConfConnection: "
+        + "Creating MBean server connection failed: " + e);
+    return;
+  }
+} /* }}} void connect */
+
+/*
+ * public methods
+ *
+ * <Connection>
+ *   Host "tomcat0.mycompany"
+ *   ServiceURL "service:jmx:rmi:///jndi/rmi://localhost:17264/jmxrmi"
+ *   Collect "java.lang:type=GarbageCollector,name=Copy"
+ *   Collect "java.lang:type=Memory"
+ * </Connection>
+ *
+ */
+  public GenericJMXConfConnection (OConfigItem ci) /* {{{ */
+    throws IllegalArgumentException
+  {
+    List<OConfigItem> children;
+    Iterator<OConfigItem> iter;
+
+    this._mbeans = new ArrayList<GenericJMXConfMBean> ();
+
+    children = ci.getChildren ();
+    iter = children.iterator ();
+    while (iter.hasNext ())
+    {
+      OConfigItem child = iter.next ();
+
+      if (child.getKey ().equalsIgnoreCase ("Host"))
+      {
+        String tmp = getConfigString (child);
+        if (tmp != null)
+          this._host = tmp;
+      }
+      else if (child.getKey ().equalsIgnoreCase ("User"))
+      {
+        String tmp = getConfigString (child);
+        if (tmp != null)
+          this._username = tmp;
+      }
+      else if (child.getKey ().equalsIgnoreCase ("Password"))
+      {
+        String tmp = getConfigString (child);
+        if (tmp != null)
+          this._password = tmp;
+      }
+      else if (child.getKey ().equalsIgnoreCase ("ServiceURL"))
+      {
+        String tmp = getConfigString (child);
+        if (tmp != null)
+          this._service_url = tmp;
+      }
+      else if (child.getKey ().equalsIgnoreCase ("InstancePrefix"))
+      {
+        String tmp = getConfigString (child);
+        if (tmp != null)
+          this._instance_prefix = tmp;
+      }
+      else if (child.getKey ().equalsIgnoreCase ("Collect"))
+      {
+        String tmp = getConfigString (child);
+        if (tmp != null)
+        {
+          GenericJMXConfMBean mbean;
+
+          mbean = GenericJMX.getMBean (tmp);
+          if (mbean == null)
+            throw (new IllegalArgumentException ("No such MBean defined: "
+                  + tmp + ". Please make sure all `MBean' blocks appear "
+                  + "before (above) all `Connection' blocks."));
+          Collectd.logDebug ("GenericJMXConfConnection: " + this._host + ": Add " + tmp);
+          this._mbeans.add (mbean);
+        }
+      }
+      else
+        throw (new IllegalArgumentException ("Unknown option: "
+              + child.getKey ()));
+    }
+
+    if (this._service_url == null)
+      throw (new IllegalArgumentException ("No service URL was defined."));
+    if (this._mbeans.size () == 0)
+      throw (new IllegalArgumentException ("No valid collect statement "
+            + "present."));
+  } /* }}} GenericJMXConfConnection (OConfigItem ci) */
+
+  public void query () /* {{{ */
+  {
+    PluginData pd;
+
+    connect ();
+
+    if (this._jmx_connection == null)
+      return;
+
+    Collectd.logDebug ("GenericJMXConfConnection.query: "
+        + "Reading " + this._mbeans.size () + " mbeans from "
+        + ((this._host != null) ? this._host : "(null)"));
+
+    pd = new PluginData ();
+    pd.setHost ((this._host != null) ? this._host : "localhost");
+    pd.setPlugin ("GenericJMX");
+
+    for (int i = 0; i < this._mbeans.size (); i++)
+    {
+      int status;
+
+      status = this._mbeans.get (i).query (this._jmx_connection, pd,
+          this._instance_prefix);
+      if (status != 0)
+      {
+        this._jmx_connection = null;
+        return;
+      }
+    } /* for */
+  } /* }}} void query */
+
+  public String toString ()
+  {
+    return (new String ("host = " + this._host + "; "
+          + "url = " + this._service_url));
+  }
+}
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/bindings/java/org/collectd/java/GenericJMXConfMBean.java b/bindings/java/org/collectd/java/GenericJMXConfMBean.java
new file mode 100644 (file)
index 0000000..b1fbfb3
--- /dev/null
@@ -0,0 +1,238 @@
+/*
+ * collectd/java - org/collectd/java/GenericJMXConfMBean.java
+ * Copyright (C) 2009,2010  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ */
+
+package org.collectd.java;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.ArrayList;
+
+import javax.management.MBeanServerConnection;
+import javax.management.ObjectName;
+import javax.management.MalformedObjectNameException;
+
+import org.collectd.api.Collectd;
+import org.collectd.api.PluginData;
+import org.collectd.api.OConfigValue;
+import org.collectd.api.OConfigItem;
+
+class GenericJMXConfMBean
+{
+  private String _name; /* name by which this mapping is referenced */
+  private ObjectName _obj_name;
+  private String _instance_prefix;
+  private List<String> _instance_from;
+  private List<GenericJMXConfValue> _values;
+
+  private String getConfigString (OConfigItem ci) /* {{{ */
+  {
+    List<OConfigValue> values;
+    OConfigValue v;
+
+    values = ci.getValues ();
+    if (values.size () != 1)
+    {
+      Collectd.logError ("GenericJMXConfMBean: The " + ci.getKey ()
+          + " configuration option needs exactly one string argument.");
+      return (null);
+    }
+
+    v = values.get (0);
+    if (v.getType () != OConfigValue.OCONFIG_TYPE_STRING)
+    {
+      Collectd.logError ("GenericJMXConfMBean: The " + ci.getKey ()
+          + " configuration option needs exactly one string argument.");
+      return (null);
+    }
+
+    return (v.getString ());
+  } /* }}} String getConfigString */
+
+/*
+ * <MBean "alias name">
+ *   ObjectName "object name"
+ *   InstancePrefix "foobar"
+ *   InstanceFrom "name"
+ *   <Value />
+ *   <Value />
+ *   :
+ * </MBean>
+ */
+  public GenericJMXConfMBean (OConfigItem ci) /* {{{ */
+    throws IllegalArgumentException
+  {
+    List<OConfigItem> children;
+    Iterator<OConfigItem> iter;
+
+    this._name = getConfigString (ci);
+    if (this._name == null)
+      throw (new IllegalArgumentException ("No alias name was defined. "
+            + "MBean blocks need exactly one string argument."));
+
+    this._obj_name = null;
+    this._instance_prefix = null;
+    this._instance_from = new ArrayList<String> ();
+    this._values = new ArrayList<GenericJMXConfValue> ();
+
+    children = ci.getChildren ();
+    iter = children.iterator ();
+    while (iter.hasNext ())
+    {
+      OConfigItem child = iter.next ();
+
+      Collectd.logDebug ("GenericJMXConfMBean: child.getKey () = "
+          + child.getKey ());
+      if (child.getKey ().equalsIgnoreCase ("ObjectName"))
+      {
+        String tmp = getConfigString (child);
+        if (tmp == null)
+          continue;
+
+        try
+        {
+          this._obj_name = new ObjectName (tmp);
+        }
+        catch (MalformedObjectNameException e)
+        {
+          throw (new IllegalArgumentException ("Not a valid object name: "
+                + tmp, e));
+        }
+      }
+      else if (child.getKey ().equalsIgnoreCase ("InstancePrefix"))
+      {
+        String tmp = getConfigString (child);
+        if (tmp != null)
+          this._instance_prefix = tmp;
+      }
+      else if (child.getKey ().equalsIgnoreCase ("InstanceFrom"))
+      {
+        String tmp = getConfigString (child);
+        if (tmp != null)
+          this._instance_from.add (tmp);
+      }
+      else if (child.getKey ().equalsIgnoreCase ("Value"))
+      {
+        GenericJMXConfValue cv;
+
+        cv = new GenericJMXConfValue (child);
+        this._values.add (cv);
+      }
+      else
+        throw (new IllegalArgumentException ("Unknown option: "
+              + child.getKey ()));
+    }
+
+    if (this._obj_name == null)
+      throw (new IllegalArgumentException ("No object name was defined."));
+
+    if (this._values.size () == 0)
+      throw (new IllegalArgumentException ("No value block was defined."));
+
+  } /* }}} GenericJMXConfMBean (OConfigItem ci) */
+
+  public String getName () /* {{{ */
+  {
+    return (this._name);
+  } /* }}} */
+
+  public int query (MBeanServerConnection conn, PluginData pd, /* {{{ */
+      String instance_prefix)
+  {
+    Set<ObjectName> names;
+    Iterator<ObjectName> iter;
+
+    try
+    {
+      names = conn.queryNames (this._obj_name, /* query = */ null);
+    }
+    catch (Exception e)
+    {
+      Collectd.logError ("GenericJMXConfMBean: queryNames failed: " + e);
+      return (-1);
+    }
+
+    if (names.size () == 0)
+    {
+      Collectd.logWarning ("GenericJMXConfMBean: No MBean matched "
+          + "the ObjectName " + this._obj_name);
+    }
+
+    iter = names.iterator ();
+    while (iter.hasNext ())
+    {
+      ObjectName   objName;
+      PluginData   pd_tmp;
+      List<String> instanceList;
+      StringBuffer instance;
+
+      objName      = iter.next ();
+      pd_tmp       = new PluginData (pd);
+      instanceList = new ArrayList<String> ();
+      instance     = new StringBuffer ();
+
+      Collectd.logDebug ("GenericJMXConfMBean: objName = "
+          + objName.toString ());
+
+      for (int i = 0; i < this._instance_from.size (); i++)
+      {
+        String propertyName;
+        String propertyValue;
+
+        propertyName = this._instance_from.get (i);
+        propertyValue = objName.getKeyProperty (propertyName);
+        if (propertyValue == null)
+        {
+          Collectd.logError ("GenericJMXConfMBean: "
+              + "No such property in object name: " + propertyName);
+        }
+        else
+        {
+          instanceList.add (propertyValue);
+        }
+      }
+
+      if (instance_prefix != null)
+        instance.append (instance_prefix);
+
+      if (this._instance_prefix != null)
+        instance.append (this._instance_prefix);
+
+      for (int i = 0; i < instanceList.size (); i++)
+      {
+        if (i > 0)
+          instance.append ("-");
+        instance.append (instanceList.get (i));
+      }
+
+      pd_tmp.setPluginInstance (instance.toString ());
+
+      Collectd.logDebug ("GenericJMXConfMBean: instance = " + instance.toString ());
+
+      for (int i = 0; i < this._values.size (); i++)
+        this._values.get (i).query (conn, objName, pd_tmp);
+    }
+
+    return (0);
+  } /* }}} void query */
+}
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/bindings/java/org/collectd/java/GenericJMXConfValue.java b/bindings/java/org/collectd/java/GenericJMXConfValue.java
new file mode 100644 (file)
index 0000000..9fb0fc2
--- /dev/null
@@ -0,0 +1,594 @@
+/*
+ * collectd/java - org/collectd/java/GenericJMXConfValue.java
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ */
+
+package org.collectd.java;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.Iterator;
+import java.util.ArrayList;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import javax.management.MBeanServerConnection;
+import javax.management.ObjectName;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.InvalidKeyException;
+
+import org.collectd.api.Collectd;
+import org.collectd.api.DataSet;
+import org.collectd.api.DataSource;
+import org.collectd.api.ValueList;
+import org.collectd.api.PluginData;
+import org.collectd.api.OConfigValue;
+import org.collectd.api.OConfigItem;
+
+/**
+ * Representation of a &lt;value&nbsp;/&gt; block and query functionality.
+ *
+ * This class represents a &lt;value&nbsp;/&gt; block in the configuration. As
+ * such, the constructor takes an {@link org.collectd.api.OConfigValue} to
+ * construct an object of this class.
+ *
+ * The object can then be asked to query data from JMX and dispatch it to
+ * collectd.
+ *
+ * @see GenericJMXConfMBean
+ */
+class GenericJMXConfValue
+{
+  private String _ds_name;
+  private DataSet _ds;
+  private List<String> _attributes;
+  private String _instance_prefix;
+  private List<String> _instance_from;
+  private boolean _is_table;
+
+  /**
+   * Converts a generic (OpenType) object to a number.
+   *
+   * Returns null if a conversion is not possible or not implemented.
+   */
+  private Number genericObjectToNumber (Object obj, int ds_type) /* {{{ */
+  {
+    if (obj instanceof String)
+    {
+      String str = (String) obj;
+      
+      try
+      {
+        if (ds_type == DataSource.TYPE_GAUGE)
+          return (new Double (str));
+        else
+          return (new Long (str));
+      }
+      catch (NumberFormatException e)
+      {
+        return (null);
+      }
+    }
+    else if (obj instanceof Byte)
+    {
+      return (new Byte ((Byte) obj));
+    }
+    else if (obj instanceof Short)
+    {
+      return (new Short ((Short) obj));
+    }
+    else if (obj instanceof Integer)
+    {
+      return (new Integer ((Integer) obj));
+    }
+    else if (obj instanceof Long)
+    {
+      return (new Long ((Long) obj));
+    }
+    else if (obj instanceof Float)
+    {
+      return (new Float ((Float) obj));
+    }
+    else if (obj instanceof Double)
+    {
+      return (new Double ((Double) obj));
+    }
+    else if (obj instanceof BigDecimal)
+    {
+      return (BigDecimal.ZERO.add ((BigDecimal) obj));
+    }
+    else if (obj instanceof BigInteger)
+    {
+      return (BigInteger.ZERO.add ((BigInteger) obj));
+    }
+
+    return (null);
+  } /* }}} Number genericObjectToNumber */
+
+  /**
+   * Converts a generic list to a list of numbers.
+   *
+   * Returns null if one or more objects could not be converted.
+   */
+  private List<Number> genericListToNumber (List<Object> objects) /* {{{ */
+  {
+    List<Number> ret = new ArrayList<Number> ();
+    List<DataSource> dsrc = this._ds.getDataSources ();
+
+    assert (objects.size () == dsrc.size ());
+
+    for (int i = 0; i < objects.size (); i++)
+    {
+      Number n;
+
+      n = genericObjectToNumber (objects.get (i), dsrc.get (i).getType ());
+      if (n == null)
+        return (null);
+      ret.add (n);
+    }
+
+    return (ret);
+  } /* }}} List<Number> genericListToNumber */
+
+  /**
+   * Converts a list of CompositeData to a list of numbers.
+   *
+   * From each <em>CompositeData </em> the key <em>key</em> is received and all
+   * those values are converted to a number. If one of the
+   * <em>CompositeData</em> doesn't have the specified key or one returned
+   * object cannot converted to a number then the function will return null.
+   */
+  private List<Number> genericCompositeToNumber (List<CompositeData> cdlist, /* {{{ */
+      String key)
+  {
+    List<Object> objects = new ArrayList<Object> ();
+
+    for (int i = 0; i < cdlist.size (); i++)
+    {
+      CompositeData cd;
+      Object value;
+
+      cd = cdlist.get (i);
+      try
+      {
+        value = cd.get (key);
+      }
+      catch (InvalidKeyException e)
+      {
+        return (null);
+      }
+      objects.add (value);
+    }
+
+    return (genericListToNumber (objects));
+  } /* }}} List<Number> genericCompositeToNumber */
+
+  private void submitTable (List<Object> objects, ValueList vl, /* {{{ */
+      String instancePrefix)
+  {
+    List<CompositeData> cdlist;
+    Set<String> keySet = null;
+    Iterator<String> keyIter;
+
+    cdlist = new ArrayList<CompositeData> ();
+    for (int i = 0; i < objects.size (); i++)
+    {
+      Object obj;
+
+      obj = objects.get (i);
+      if (obj instanceof CompositeData)
+      {
+        CompositeData cd;
+
+        cd = (CompositeData) obj;
+
+        if (i == 0)
+          keySet = cd.getCompositeType ().keySet ();
+
+        cdlist.add (cd);
+      }
+      else
+      {
+        Collectd.logError ("GenericJMXConfValue: At least one of the "
+            + "attributes was not of type `CompositeData', as required "
+            + "when table is set to `true'.");
+        return;
+      }
+    }
+
+    assert (keySet != null);
+
+    keyIter = keySet.iterator ();
+    while (keyIter.hasNext ())
+    {
+      String key;
+      List<Number> values;
+
+      key = keyIter.next ();
+      values = genericCompositeToNumber (cdlist, key);
+      if (values == null)
+      {
+        Collectd.logError ("GenericJMXConfValue: Cannot build a list of "
+            + "numbers for key " + key + ". Most likely not all attributes "
+            + "have this key.");
+        continue;
+      }
+
+      if (instancePrefix == null)
+        vl.setTypeInstance (key);
+      else
+        vl.setTypeInstance (instancePrefix + key);
+      vl.setValues (values);
+
+      Collectd.dispatchValues (vl);
+    }
+  } /* }}} void submitTable */
+
+  private void submitScalar (List<Object> objects, ValueList vl, /* {{{ */
+      String instancePrefix)
+  {
+    List<Number> values;
+
+    values = genericListToNumber (objects);
+    if (values == null)
+    {
+      Collectd.logError ("GenericJMXConfValue: Cannot convert list of "
+          + "objects to numbers.");
+      return;
+    }
+
+    if (instancePrefix == null)
+      vl.setTypeInstance ("");
+    else
+      vl.setTypeInstance (instancePrefix);
+    vl.setValues (values);
+
+    Collectd.dispatchValues (vl);
+  } /* }}} void submitScalar */
+
+  private Object queryAttributeRecursive (CompositeData parent, /* {{{ */
+      List<String> attrName)
+  {
+    String key;
+    Object value;
+
+    key = attrName.remove (0);
+
+    try
+    {
+      value = parent.get (key);
+    }
+    catch (InvalidKeyException e)
+    {
+      return (null);
+    }
+
+    if (attrName.size () == 0)
+    {
+      return (value);
+    }
+    else
+    {
+      if (value instanceof CompositeData)
+        return (queryAttributeRecursive ((CompositeData) value, attrName));
+      else
+        return (null);
+    }
+  } /* }}} queryAttributeRecursive */
+
+  private Object queryAttribute (MBeanServerConnection conn, /* {{{ */
+      ObjectName objName, String attrName)
+  {
+    List<String> attrNameList;
+    String key;
+    Object value;
+    String[] attrNameArray;
+
+    attrNameList = new ArrayList<String> ();
+
+    attrNameArray = attrName.split ("\\.");
+    key = attrNameArray[0];
+    for (int i = 1; i < attrNameArray.length; i++)
+      attrNameList.add (attrNameArray[i]);
+
+    try
+    {
+      try
+      {
+        value = conn.getAttribute (objName, key);
+      }
+      catch (javax.management.AttributeNotFoundException e)
+      {
+        value = conn.invoke (objName, key, /* args = */ null, /* types = */ null);
+      }
+    }
+    catch (Exception e)
+    {
+      Collectd.logError ("GenericJMXConfValue.query: getAttribute failed: "
+          + e);
+      return (null);
+    }
+
+    if (attrNameList.size () == 0)
+    {
+      return (value);
+    }
+    else
+    {
+      if (value instanceof CompositeData)
+        return (queryAttributeRecursive((CompositeData) value, attrNameList));
+      else if (value instanceof OpenType)
+      {
+        OpenType ot = (OpenType) value;
+        Collectd.logNotice ("GenericJMXConfValue: Handling of OpenType \""
+            + ot.getTypeName () + "\" is not yet implemented.");
+        return (null);
+      }
+      else
+      {
+        Collectd.logError ("GenericJMXConfValue: Received object of "
+            + "unknown class.");
+        return (null);
+      }
+    }
+  } /* }}} Object queryAttribute */
+
+  private String join (String separator, List<String> list) /* {{{ */
+  {
+    StringBuffer sb;
+
+    sb = new StringBuffer ();
+
+    for (int i = 0; i < list.size (); i++)
+    {
+      if (i > 0)
+        sb.append ("-");
+      sb.append (list.get (i));
+    }
+
+    return (sb.toString ());
+  } /* }}} String join */
+
+  private String getConfigString (OConfigItem ci) /* {{{ */
+  {
+    List<OConfigValue> values;
+    OConfigValue v;
+
+    values = ci.getValues ();
+    if (values.size () != 1)
+    {
+      Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
+          + " configuration option needs exactly one string argument.");
+      return (null);
+    }
+
+    v = values.get (0);
+    if (v.getType () != OConfigValue.OCONFIG_TYPE_STRING)
+    {
+      Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
+          + " configuration option needs exactly one string argument.");
+      return (null);
+    }
+
+    return (v.getString ());
+  } /* }}} String getConfigString */
+
+  private Boolean getConfigBoolean (OConfigItem ci) /* {{{ */
+  {
+    List<OConfigValue> values;
+    OConfigValue v;
+    Boolean b;
+
+    values = ci.getValues ();
+    if (values.size () != 1)
+    {
+      Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
+          + " configuration option needs exactly one boolean argument.");
+      return (null);
+    }
+
+    v = values.get (0);
+    if (v.getType () != OConfigValue.OCONFIG_TYPE_BOOLEAN)
+    {
+      Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
+          + " configuration option needs exactly one boolean argument.");
+      return (null);
+    }
+
+    return (new Boolean (v.getBoolean ()));
+  } /* }}} String getConfigBoolean */
+
+  /**
+   * Constructs a new value with the configured properties.
+   */
+  public GenericJMXConfValue (OConfigItem ci) /* {{{ */
+    throws IllegalArgumentException
+  {
+    List<OConfigItem> children;
+    Iterator<OConfigItem> iter;
+
+    this._ds_name = null;
+    this._ds = null;
+    this._attributes = new ArrayList<String> ();
+    this._instance_prefix = null;
+    this._instance_from = new ArrayList<String> ();
+    this._is_table = false;
+
+    /*
+     * <Value>
+     *   Type "memory"
+     *   Table true|false
+     *   Attribute "HeapMemoryUsage"
+     *   Attribute "..."
+     *   :
+     *   # Type instance:
+     *   InstancePrefix "heap-"
+     * </Value>
+     */
+    children = ci.getChildren ();
+    iter = children.iterator ();
+    while (iter.hasNext ())
+    {
+      OConfigItem child = iter.next ();
+
+      if (child.getKey ().equalsIgnoreCase ("Type"))
+      {
+        String tmp = getConfigString (child);
+        if (tmp != null)
+          this._ds_name = tmp;
+      }
+      else if (child.getKey ().equalsIgnoreCase ("Table"))
+      {
+        Boolean tmp = getConfigBoolean (child);
+        if (tmp != null)
+          this._is_table = tmp.booleanValue ();
+      }
+      else if (child.getKey ().equalsIgnoreCase ("Attribute"))
+      {
+        String tmp = getConfigString (child);
+        if (tmp != null)
+          this._attributes.add (tmp);
+      }
+      else if (child.getKey ().equalsIgnoreCase ("InstancePrefix"))
+      {
+        String tmp = getConfigString (child);
+        if (tmp != null)
+          this._instance_prefix = tmp;
+      }
+      else if (child.getKey ().equalsIgnoreCase ("InstanceFrom"))
+      {
+        String tmp = getConfigString (child);
+        if (tmp != null)
+          this._instance_from.add (tmp);
+      }
+      else
+        throw (new IllegalArgumentException ("Unknown option: "
+              + child.getKey ()));
+    }
+
+    if (this._ds_name == null)
+      throw (new IllegalArgumentException ("No data set was defined."));
+    else if (this._attributes.size () == 0)
+      throw (new IllegalArgumentException ("No attribute was defined."));
+  } /* }}} GenericJMXConfValue (OConfigItem ci) */
+
+  /**
+   * Query values via JMX according to the object's configuration and dispatch
+   * them to collectd.
+   *
+   * @param conn    Connection to the MBeanServer.
+   * @param objName Object name of the MBean to query.
+   * @param pd      Preset naming components. The members host, plugin and
+   *                plugin instance will be used.
+   */
+  public void query (MBeanServerConnection conn, ObjectName objName, /* {{{ */
+      PluginData pd)
+  {
+    ValueList vl;
+    List<DataSource> dsrc;
+    List<Object> values;
+    List<String> instanceList;
+    String instancePrefix;
+
+    if (this._ds == null)
+    {
+      this._ds = Collectd.getDS (this._ds_name);
+      if (this._ds == null)
+      {
+        Collectd.logError ("GenericJMXConfValue: Unknown type: "
+            + this._ds_name);
+        return;
+      }
+    }
+
+    dsrc = this._ds.getDataSources ();
+    if (dsrc.size () != this._attributes.size ())
+    {
+      Collectd.logError ("GenericJMXConfValue.query: The data set "
+          + this._ds_name + " has " + this._ds.getDataSources ().size ()
+          + " data sources, but there were " + this._attributes.size ()
+          + " attributes configured. This doesn't match!");
+      this._ds = null;
+      return;
+    }
+
+    vl = new ValueList (pd);
+    vl.setType (this._ds_name);
+
+    /*
+     * Build the instnace prefix from the fixed string prefix and the
+     * properties of the objName.
+     */
+    instanceList = new ArrayList<String> ();
+    for (int i = 0; i < this._instance_from.size (); i++)
+    {
+      String propertyName;
+      String propertyValue;
+
+      propertyName = this._instance_from.get (i);
+      propertyValue = objName.getKeyProperty (propertyName);
+      if (propertyValue == null)
+      {
+        Collectd.logError ("GenericJMXConfMBean: "
+            + "No such property in object name: " + propertyName);
+      }
+      else
+      {
+        instanceList.add (propertyValue);
+      }
+    }
+
+    if (this._instance_prefix != null)
+      instancePrefix = new String (this._instance_prefix
+          + join ("-", instanceList));
+    else
+      instancePrefix = join ("-", instanceList);
+
+    /*
+     * Build a list of `Object's which is then passed to `submitTable' and
+     * `submitScalar'.
+     */
+    values = new ArrayList<Object> ();
+    assert (dsrc.size () == this._attributes.size ());
+    for (int i = 0; i < this._attributes.size (); i++)
+    {
+      Object v;
+
+      v = queryAttribute (conn, objName, this._attributes.get (i));
+      if (v == null)
+      {
+        Collectd.logError ("GenericJMXConfValue.query: "
+            + "Querying attribute " + this._attributes.get (i) + " failed.");
+        return;
+      }
+
+      values.add (v);
+    }
+
+    if (this._is_table)
+      submitTable (values, vl, instancePrefix);
+    else
+      submitScalar (values, vl, instancePrefix);
+  } /* }}} void query */
+} /* class GenericJMXConfValue */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/bindings/java/org/collectd/java/JMXMemory.java b/bindings/java/org/collectd/java/JMXMemory.java
new file mode 100644 (file)
index 0000000..6e6a2fb
--- /dev/null
@@ -0,0 +1,232 @@
+/*
+ * collectd/java - org/collectd/java/JMXMemory.java
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ */
+
+package org.collectd.java;
+
+import java.util.List;
+import java.util.Date;
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryUsage;
+import java.lang.management.MemoryMXBean;
+
+import javax.management.MBeanServerConnection;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXServiceURL;
+
+import org.collectd.api.Collectd;
+import org.collectd.api.DataSet;
+import org.collectd.api.ValueList;
+import org.collectd.api.Notification;
+import org.collectd.api.OConfigItem;
+
+import org.collectd.api.CollectdConfigInterface;
+import org.collectd.api.CollectdInitInterface;
+import org.collectd.api.CollectdReadInterface;
+import org.collectd.api.CollectdShutdownInterface;
+
+import org.collectd.api.OConfigValue;
+import org.collectd.api.OConfigItem;
+
+public class JMXMemory implements CollectdConfigInterface,
+       CollectdInitInterface,
+       CollectdReadInterface,
+       CollectdShutdownInterface
+{
+  private String _jmx_service_url = null;
+  private MemoryMXBean _mbean = null;
+
+  public JMXMemory ()
+  {
+    Collectd.registerConfig   ("JMXMemory", this);
+    Collectd.registerInit     ("JMXMemory", this);
+    Collectd.registerRead     ("JMXMemory", this);
+    Collectd.registerShutdown ("JMXMemory", this);
+  }
+
+  private void submit (String plugin_instance, MemoryUsage usage) /* {{{ */
+  {
+    ValueList vl;
+
+    long mem_init;
+    long mem_used;
+    long mem_committed;
+    long mem_max;
+
+    mem_init = usage.getInit ();
+    mem_used = usage.getUsed ();
+    mem_committed = usage.getCommitted ();
+    mem_max = usage.getMax ();
+
+    Collectd.logDebug ("JMXMemory plugin: plugin_instance = " + plugin_instance + "; "
+        + "mem_init = " + mem_init + "; "
+        + "mem_used = " + mem_used + "; "
+        + "mem_committed = " + mem_committed + "; "
+        + "mem_max = " + mem_max + ";");
+
+    vl = new ValueList ();
+
+    vl.setHost ("localhost");
+    vl.setPlugin ("JMXMemory");
+    vl.setPluginInstance (plugin_instance);
+    vl.setType ("memory");
+
+    if (mem_init >= 0)
+    {
+      vl.addValue (mem_init);
+      vl.setTypeInstance ("init");
+      Collectd.dispatchValues (vl);
+      vl.clearValues ();
+    }
+
+    if (mem_used >= 0)
+    {
+      vl.addValue (mem_used);
+      vl.setTypeInstance ("used");
+      Collectd.dispatchValues (vl);
+      vl.clearValues ();
+    }
+
+    if (mem_committed >= 0)
+    {
+      vl.addValue (mem_committed);
+      vl.setTypeInstance ("committed");
+      Collectd.dispatchValues (vl);
+      vl.clearValues ();
+    }
+
+    if (mem_max >= 0)
+    {
+      vl.addValue (mem_max);
+      vl.setTypeInstance ("max");
+      Collectd.dispatchValues (vl);
+      vl.clearValues ();
+    }
+  } /* }}} void submit */
+
+  private int configServiceURL (OConfigItem ci) /* {{{ */
+  {
+    List<OConfigValue> values;
+    OConfigValue cv;
+
+    values = ci.getValues ();
+    if (values.size () != 1)
+    {
+      Collectd.logError ("JMXMemory plugin: The JMXServiceURL option needs "
+          + "exactly one string argument.");
+      return (-1);
+    }
+
+    cv = values.get (0);
+    if (cv.getType () != OConfigValue.OCONFIG_TYPE_STRING)
+    {
+      Collectd.logError ("JMXMemory plugin: The JMXServiceURL option needs "
+          + "exactly one string argument.");
+      return (-1);
+    }
+
+    _jmx_service_url = cv.getString ();
+    return (0);
+  } /* }}} int configServiceURL */
+
+  public int config (OConfigItem ci) /* {{{ */
+  {
+    List<OConfigItem> children;
+    int i;
+
+    Collectd.logDebug ("JMXMemory plugin: config: ci = " + ci + ";");
+
+    children = ci.getChildren ();
+    for (i = 0; i < children.size (); i++)
+    {
+      OConfigItem child;
+      String key;
+
+      child = children.get (i);
+      key = child.getKey ();
+      if (key.equalsIgnoreCase ("JMXServiceURL"))
+      {
+        configServiceURL (child);
+      }
+      else
+      {
+        Collectd.logError ("JMXMemory plugin: Unknown config option: " + key);
+      }
+    }
+
+    return (0);
+  } /* }}} int config */
+
+  public int init () /* {{{ */
+  {
+    JMXServiceURL service_url;
+    JMXConnector connector;
+    MBeanServerConnection connection;
+
+    if (_jmx_service_url == null)
+    {
+      Collectd.logError ("JMXMemory: _jmx_service_url == null");
+      return (-1);
+    }
+
+    try
+    {
+      service_url = new JMXServiceURL (_jmx_service_url);
+      connector = JMXConnectorFactory.connect (service_url);
+      connection = connector.getMBeanServerConnection ();
+      _mbean = ManagementFactory.newPlatformMXBeanProxy (connection,
+          ManagementFactory.MEMORY_MXBEAN_NAME,
+          MemoryMXBean.class);
+    }
+    catch (Exception e)
+    {
+      Collectd.logError ("JMXMemory: Creating MBean failed: " + e);
+      return (-1);
+    }
+
+    return (0);
+  } /* }}} int init */
+
+  public int read () /* {{{ */
+  {
+    if (_mbean == null)
+    {
+      Collectd.logError ("JMXMemory: _mbean == null");
+      return (-1);
+    }
+
+    submit ("heap", _mbean.getHeapMemoryUsage ());
+    submit ("non_heap", _mbean.getNonHeapMemoryUsage ());
+
+    return (0);
+  } /* }}} int read */
+
+  public int shutdown () /* {{{ */
+  {
+    System.out.print ("org.collectd.java.JMXMemory.Shutdown ();\n");
+    _jmx_service_url = null;
+    _mbean = null;
+    return (0);
+  } /* }}} int shutdown */
+} /* class JMXMemory */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/bindings/perl/Makefile.PL b/bindings/perl/Makefile.PL
new file mode 100644 (file)
index 0000000..f2ef2fd
--- /dev/null
@@ -0,0 +1,8 @@
+use ExtUtils::MakeMaker;
+
+WriteMakefile(
+       'NAME'          => 'Collectd',
+       'AUTHOR'        => 'Sebastian Harl <sh@tokkee.org>',
+);
+
+# vim: set sw=4 ts=4 tw=78 noexpandtab :
diff --git a/bindings/perl/lib/Collectd.pm b/bindings/perl/lib/Collectd.pm
new file mode 100644 (file)
index 0000000..ca3b5d2
--- /dev/null
@@ -0,0 +1,633 @@
+# collectd - Collectd.pm
+# Copyright (C) 2007-2009  Sebastian Harl
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; only version 2 of the License is applicable.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+#
+# Author:
+#   Sebastian Harl <sh at tokkee.org>
+
+package Collectd;
+
+use strict;
+use warnings;
+
+use Config;
+
+use threads;
+use threads::shared;
+
+BEGIN {
+       if (! $Config{'useithreads'}) {
+               die "Perl does not support ithreads!";
+       }
+}
+
+require Exporter;
+
+our @ISA = qw( Exporter );
+
+our %EXPORT_TAGS = (
+       'plugin' => [ qw(
+                       plugin_register
+                       plugin_unregister
+                       plugin_dispatch_values
+                       plugin_write
+                       plugin_flush
+                       plugin_flush_one
+                       plugin_flush_all
+                       plugin_dispatch_notification
+                       plugin_log
+       ) ],
+       'types' => [ qw(
+                       TYPE_INIT
+                       TYPE_READ
+                       TYPE_WRITE
+                       TYPE_SHUTDOWN
+                       TYPE_LOG
+                       TYPE_NOTIF
+                       TYPE_FLUSH
+                       TYPE_CONFIG
+                       TYPE_DATASET
+       ) ],
+       'ds_types' => [ qw(
+                       DS_TYPE_COUNTER
+                       DS_TYPE_GAUGE
+       ) ],
+       'log' => [ qw(
+                       ERROR
+                       WARNING
+                       NOTICE
+                       INFO
+                       DEBUG
+                       LOG_ERR
+                       LOG_WARNING
+                       LOG_NOTICE
+                       LOG_INFO
+                       LOG_DEBUG
+       ) ],
+       'filter_chain' => [ qw(
+                       fc_register
+                       FC_MATCH_NO_MATCH
+                       FC_MATCH_MATCHES
+                       FC_TARGET_CONTINUE
+                       FC_TARGET_STOP
+                       FC_TARGET_RETURN
+       ) ],
+       'fc_types' => [ qw(
+                       FC_MATCH
+                       FC_TARGET
+       ) ],
+       'notif' => [ qw(
+                       NOTIF_FAILURE
+                       NOTIF_WARNING
+                       NOTIF_OKAY
+       ) ],
+       'globals' => [ qw(
+                       $hostname_g
+                       $interval_g
+       ) ],
+);
+
+{
+       my %seen;
+       push @{$EXPORT_TAGS{'all'}}, grep {! $seen{$_}++ } @{$EXPORT_TAGS{$_}}
+               foreach keys %EXPORT_TAGS;
+}
+
+# global variables
+our $hostname_g;
+our $interval_g;
+
+Exporter::export_ok_tags ('all');
+
+my @plugins : shared = ();
+my @fc_plugins : shared = ();
+my %cf_callbacks : shared = ();
+
+my %types = (
+       TYPE_CONFIG,   "config",
+       TYPE_INIT,     "init",
+       TYPE_READ,     "read",
+       TYPE_WRITE,    "write",
+       TYPE_SHUTDOWN, "shutdown",
+       TYPE_LOG,      "log",
+       TYPE_NOTIF,    "notify",
+       TYPE_FLUSH,    "flush"
+);
+
+my %fc_types = (
+       FC_MATCH,  "match",
+       FC_TARGET, "target"
+);
+
+my %fc_exec_names = (
+       FC_MATCH,  "match",
+       FC_TARGET, "invoke"
+);
+
+my %fc_cb_types = (
+       FC_CB_EXEC, "exec",
+       FC_CB_CREATE, "create",
+       FC_CB_DESTROY, "destroy"
+);
+
+foreach my $type (keys %types) {
+       $plugins[$type] = &share ({});
+}
+
+foreach my $type (keys %fc_types) {
+       $fc_plugins[$type] = &share ({});
+}
+
+sub _log {
+       my $caller = shift;
+       my $lvl    = shift;
+       my $msg    = shift;
+
+       if ("Collectd" eq $caller) {
+               $msg = "perl: $msg";
+       }
+       return plugin_log ($lvl, $msg);
+}
+
+sub ERROR   { _log (scalar caller, LOG_ERR,     shift); }
+sub WARNING { _log (scalar caller, LOG_WARNING, shift); }
+sub NOTICE  { _log (scalar caller, LOG_NOTICE,  shift); }
+sub INFO    { _log (scalar caller, LOG_INFO,    shift); }
+sub DEBUG   { _log (scalar caller, LOG_DEBUG,   shift); }
+
+sub plugin_call_all {
+       my $type = shift;
+
+       my %plugins;
+
+       our $cb_name = undef;
+
+       if (! defined $type) {
+               return;
+       }
+
+       if (TYPE_LOG != $type) {
+               DEBUG ("Collectd::plugin_call: type = \"$type\" ("
+                       . $types{$type} . "), args=\""
+                       . join(', ', map { defined($_) ? $_ : '<undef>' } @_) . "\"");
+       }
+
+       if (! defined $plugins[$type]) {
+               ERROR ("Collectd::plugin_call: unknown type \"$type\"");
+               return;
+       }
+
+       {
+               lock %{$plugins[$type]};
+               %plugins = %{$plugins[$type]};
+       }
+
+       foreach my $plugin (keys %plugins) {
+               my $p = $plugins{$plugin};
+
+               my $status = 0;
+
+               if ($p->{'wait_left'} > 0) {
+                       $p->{'wait_left'} -= $interval_g;
+               }
+
+               next if ($p->{'wait_left'} > 0);
+
+               $cb_name = $p->{'cb_name'};
+               $status = call_by_name (@_);
+
+               if (! $status) {
+                       my $err = undef;
+
+                       if ($@) {
+                               $err = $@;
+                       }
+                       else {
+                               $err = "callback returned false";
+                       }
+
+                       if (TYPE_LOG != $type) {
+                               ERROR ("Execution of callback \"$cb_name\" failed: $err");
+                       }
+
+                       $status = 0;
+               }
+
+               if ($status) {
+                       $p->{'wait_left'} = 0;
+                       $p->{'wait_time'} = $interval_g;
+               }
+               elsif (TYPE_READ == $type) {
+                       if ($p->{'wait_time'} < $interval_g) {
+                               $p->{'wait_time'} = $interval_g;
+                       }
+
+                       $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.");
+               }
+               elsif (TYPE_INIT == $type) {
+                       ERROR ("${plugin}->init() failed with status $status. "
+                               . "Plugin will be disabled.");
+
+                       foreach my $type (keys %types) {
+                               plugin_unregister ($type, $plugin);
+                       }
+               }
+               elsif (TYPE_LOG != $type) {
+                       WARNING ("${plugin}->$types{$type}() failed with status $status.");
+               }
+       }
+       return 1;
+}
+
+# Collectd::plugin_register (type, name, data).
+#
+# type:
+#   init, read, write, shutdown, data set
+#
+# name:
+#   name of the plugin
+#
+# data:
+#   reference to the plugin's subroutine that does the work or the data set
+#   definition
+sub plugin_register {
+       my $type = shift;
+       my $name = shift;
+       my $data = shift;
+
+       DEBUG ("Collectd::plugin_register: "
+               . "type = \"$type\" (" . $types{$type}
+               . "), name = \"$name\", data = \"$data\"");
+
+       if (! ((defined $type) && (defined $name) && (defined $data))) {
+               ERROR ("Usage: Collectd::plugin_register (type, name, data)");
+               return;
+       }
+
+       if ((! defined $plugins[$type]) && (TYPE_DATASET != $type)
+                       && (TYPE_CONFIG != $type)) {
+               ERROR ("Collectd::plugin_register: Invalid type \"$type\"");
+               return;
+       }
+
+       if ((TYPE_DATASET == $type) && ("ARRAY" eq ref $data)) {
+               return plugin_register_data_set ($name, $data);
+       }
+       elsif ((TYPE_CONFIG == $type) && (! ref $data)) {
+               my $pkg = scalar caller;
+
+               if ($data !~ m/^$pkg\:\:/) {
+                       $data = $pkg . "::" . $data;
+               }
+
+               lock %cf_callbacks;
+               $cf_callbacks{$name} = $data;
+       }
+       elsif ((TYPE_DATASET != $type) && (! ref $data)) {
+               my $pkg = scalar caller;
+
+               my %p : shared;
+
+               if ($data !~ m/^$pkg\:\:/) {
+                       $data = $pkg . "::" . $data;
+               }
+
+               %p = (
+                       wait_time => $interval_g,
+                       wait_left => 0,
+                       cb_name   => $data,
+               );
+
+               lock %{$plugins[$type]};
+               $plugins[$type]->{$name} = \%p;
+       }
+       else {
+               ERROR ("Collectd::plugin_register: Invalid data.");
+               return;
+       }
+       return 1;
+}
+
+sub plugin_unregister {
+       my $type = shift;
+       my $name = shift;
+
+       DEBUG ("Collectd::plugin_unregister: type = \"$type\" ("
+               . $types{$type} . "), name = \"$name\"");
+
+       if (! ((defined $type) && (defined $name))) {
+               ERROR ("Usage: Collectd::plugin_unregister (type, name)");
+               return;
+       }
+
+       if (TYPE_DATASET == $type) {
+               return plugin_unregister_data_set ($name);
+       }
+       elsif (TYPE_CONFIG == $type) {
+               lock %cf_callbacks;
+               delete $cf_callbacks{$name};
+       }
+       elsif (defined $plugins[$type]) {
+               lock %{$plugins[$type]};
+               delete $plugins[$type]->{$name};
+       }
+       else {
+               ERROR ("Collectd::plugin_unregister: Invalid type.");
+               return;
+       }
+}
+
+sub plugin_write {
+       my %args = @_;
+
+       my @plugins    = ();
+       my @datasets   = ();
+       my @valuelists = ();
+
+       if (! defined $args{'valuelists'}) {
+               ERROR ("Collectd::plugin_write: Missing 'valuelists' argument.");
+               return;
+       }
+
+       DEBUG ("Collectd::plugin_write:"
+               . (defined ($args{'plugins'}) ? " plugins = $args{'plugins'}" : "")
+               . (defined ($args{'datasets'}) ? " datasets = $args{'datasets'}" : "")
+               . " valueslists = $args{'valuelists'}");
+
+       if (defined ($args{'plugins'})) {
+               if ("ARRAY" eq ref ($args{'plugins'})) {
+                       @plugins = @{$args{'plugins'}};
+               }
+               else {
+                       @plugins = ($args{'plugins'});
+               }
+       }
+       else {
+               @plugins = (undef);
+       }
+
+       if ("ARRAY" eq ref ($args{'valuelists'})) {
+               @valuelists = @{$args{'valuelists'}};
+       }
+       else {
+               @valuelists = ($args{'valuelists'});
+       }
+
+       if (defined ($args{'datasets'})) {
+               if ("ARRAY" eq ref ($args{'datasets'})) {
+                       @datasets = @{$args{'datasets'}};
+               }
+               else {
+                       @datasets = ($args{'datasets'});
+               }
+       }
+       else {
+               @datasets = (undef) x scalar (@valuelists);
+       }
+
+       if ($#datasets != $#valuelists) {
+               ERROR ("Collectd::plugin_write: Invalid number of datasets.");
+               return;
+       }
+
+       foreach my $plugin (@plugins) {
+               for (my $i = 0; $i < scalar (@valuelists); ++$i) {
+                       _plugin_write ($plugin, $datasets[$i], $valuelists[$i]);
+               }
+       }
+}
+
+sub plugin_flush {
+       my %args = @_;
+
+       my $timeout = -1;
+       my @plugins = ();
+       my @ids     = ();
+
+       DEBUG ("Collectd::plugin_flush:"
+               . (defined ($args{'timeout'}) ? " timeout = $args{'timeout'}" : "")
+               . (defined ($args{'plugins'}) ? " plugins = $args{'plugins'}" : "")
+               . (defined ($args{'identifiers'})
+                       ? " identifiers = $args{'identifiers'}" : ""));
+
+       if (defined ($args{'timeout'}) && ($args{'timeout'} > 0)) {
+               $timeout = $args{'timeout'};
+       }
+
+       if (defined ($args{'plugins'})) {
+               if ("ARRAY" eq ref ($args{'plugins'})) {
+                       @plugins = @{$args{'plugins'}};
+               }
+               else {
+                       @plugins = ($args{'plugins'});
+               }
+       }
+       else {
+               @plugins = (undef);
+       }
+
+       if (defined ($args{'identifiers'})) {
+               if ("ARRAY" eq ref ($args{'identifiers'})) {
+                       @ids = @{$args{'identifiers'}};
+               }
+               else {
+                       @ids = ($args{'identifiers'});
+               }
+       }
+       else {
+               @ids = (undef);
+       }
+
+       foreach my $plugin (@plugins) {
+               foreach my $id (@ids) {
+                       _plugin_flush($plugin, $timeout, $id);
+               }
+       }
+}
+
+sub fc_call {
+       my $type    = shift;
+       my $name    = shift;
+       my $cb_type = shift;
+
+       my %proc;
+
+       our $cb_name = undef;
+       my  $status;
+
+       if (! ((defined $type) && (defined $name) && (defined $cb_type))) {
+               ERROR ("Usage: Collectd::fc_call(type, name, cb_type, ...)");
+               return;
+       }
+
+       if (! defined $fc_plugins[$type]) {
+               ERROR ("Collectd::fc_call: Invalid type \"$type\"");
+               return;
+       }
+
+       if (! defined $fc_plugins[$type]->{$name}) {
+               ERROR ("Collectd::fc_call: Unknown "
+                       . ($type == FC_MATCH ? "match" : "target")
+                       . " \"$name\"");
+               return;
+       }
+
+       DEBUG ("Collectd::fc_call: "
+               . "type = \"$type\" (" . $fc_types{$type}
+               . "), name = \"$name\", cb_type = \"$cb_type\" ("
+               . $fc_cb_types{$cb_type} . ")");
+
+       {
+               lock %{$fc_plugins[$type]};
+               %proc = %{$fc_plugins[$type]->{$name}};
+       }
+
+       if (FC_CB_EXEC == $cb_type) {
+               $cb_name = $proc{$fc_exec_names{$type}};
+       }
+       elsif (FC_CB_CREATE == $cb_type) {
+               if (defined $proc{'create'}) {
+                       $cb_name = $proc{'create'};
+               }
+               else {
+                       return 1;
+               }
+       }
+       elsif (FC_CB_DESTROY == $cb_type) {
+               if (defined $proc{'destroy'}) {
+                       $cb_name = $proc{'destroy'};
+               }
+               else {
+                       return 1;
+               }
+       }
+
+       $status = call_by_name (@_);
+
+       if ($status < 0) {
+               my $err = undef;
+
+               if ($@) {
+                       $err = $@;
+               }
+               else {
+                       $err = "callback returned false";
+               }
+
+               ERROR ("Execution of fc callback \"$cb_name\" failed: $err");
+               return;
+       }
+       return $status;
+}
+
+sub fc_register {
+       my $type = shift;
+       my $name = shift;
+       my $proc = shift;
+
+       my %fc : shared;
+
+       DEBUG ("Collectd::fc_register: "
+               . "type = \"$type\" (" . $fc_types{$type}
+               . "), name = \"$name\", proc = \"$proc\"");
+
+       if (! ((defined $type) && (defined $name) && (defined $proc))) {
+               ERROR ("Usage: Collectd::fc_register(type, name, proc)");
+               return;
+       }
+
+       if (! defined $fc_plugins[$type]) {
+               ERROR ("Collectd::fc_register: Invalid type \"$type\"");
+               return;
+       }
+
+       if (("HASH" ne ref ($proc)) || (! defined $proc->{$fc_exec_names{$type}})
+                       || ("" ne ref ($proc->{$fc_exec_names{$type}}))) {
+               ERROR ("Collectd::fc_register: Invalid proc.");
+               return;
+       }
+
+       for my $p (qw( create destroy )) {
+               if ((defined $proc->{$p}) && ("" ne ref ($proc->{$p}))) {
+                       ERROR ("Collectd::fc_register: Invalid proc.");
+                       return;
+               }
+       }
+
+       %fc = %$proc;
+
+       foreach my $p (keys %fc) {
+               my $pkg = scalar caller;
+
+               if ($p !~ m/^(create|destroy|$fc_exec_names{$type})$/) {
+                       next;
+               }
+
+               if ($fc{$p} !~ m/^$pkg\:\:/) {
+                       $fc{$p} = $pkg . "::" . $fc{$p};
+               }
+       }
+
+       lock %{$fc_plugins[$type]};
+       if (defined $fc_plugins[$type]->{$name}) {
+               WARNING ("Collectd::fc_register: Overwriting previous "
+                       . "definition of match \"$name\".");
+       }
+
+       if (! _fc_register ($type, $name)) {
+               ERROR ("Collectd::fc_register: Failed to register \"$name\".");
+               return;
+       }
+
+       $fc_plugins[$type]->{$name} = \%fc;
+       return 1;
+}
+
+sub _plugin_dispatch_config {
+       my $plugin = shift;
+       my $config = shift;
+
+       our $cb_name = undef;
+
+       if (! (defined ($plugin) && defined ($config))) {
+               return;
+       }
+
+       if (! defined $cf_callbacks{$plugin}) {
+               WARNING ("Found a configuration for the \"$plugin\" plugin, but "
+                       . "the plugin isn't loaded or didn't register "
+                       . "a configuration callback.");
+               return;
+       }
+
+       {
+               lock %cf_callbacks;
+               $cb_name = $cf_callbacks{$plugin};
+       }
+       call_by_name ($config);
+}
+
+1;
+
+# vim: set sw=4 ts=4 tw=78 noexpandtab :
+
diff --git a/bindings/perl/lib/Collectd/Plugins/Monitorus.pm b/bindings/perl/lib/Collectd/Plugins/Monitorus.pm
new file mode 100644 (file)
index 0000000..7054fbf
--- /dev/null
@@ -0,0 +1,104 @@
+#
+# collectd - mon.itor.us collectd plugin
+# Copyright (C) 2009  Jeff Green
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; only version 2 of the License is applicable.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+#
+# Authors:
+#   Jeff Green <jeff at kikisoso.org>
+#
+
+package Collectd::Plugins::Monitorus;
+
+use strict;
+use warnings;
+
+use Collectd qw( :all );
+use LWP;
+use threads::shared;
+
+use constant NUM_OF_INTERVALS => 90;
+
+my $intervalcnt :shared;
+$intervalcnt=NUM_OF_INTERVALS;
+my $prev_value :shared;
+$prev_value=0;
+
+plugin_register (TYPE_READ, "monitorus", "monitorus_read");
+
+sub monitorus_read
+{
+        my $vl = { plugin => 'monitorus', type => 'gauge' };
+
+        # Only retrieve a value occasionally in order to not overload mon.itor.us
+        if (++$intervalcnt<NUM_OF_INTERVALS) { # e.g. 180 * 10 secs / 60 seconds/min = 30 minutes
+                $vl->{'values'} = [ $prev_value ];
+                plugin_dispatch_values ($vl);
+                return 1;
+        }
+
+        $intervalcnt=0;
+
+        my $site = 'http://mon.itor.us';
+        my $username = 'me@example.org';
+        my $target = $site.'/user/api/'.$username.'/secretpassword';
+
+        my $ua = LWP::UserAgent->new;
+        my $req = HTTP::Request->new(GET => "$target");
+        $req->header('Accept' => 'text/html');          #Accept HTML Page
+
+        my $key;
+        my $res = $ua->get($target);
+        if ($res->is_success) {# Success....all content of page has been received
+                $res->content() =~ m/\[CDATA\[(.*)\]\]/;
+                $key = $1;
+        } else {
+                INFO("monitorus: Error in retrieving login page.");
+        }
+
+        $target = $site.'/test/api/'.$key.'/testNames';
+        my $testid;
+        $res = $ua->get($target);
+        if ($res->is_success) {# Success....all content of page has been received
+                $res->content() =~ m/<test id='(.*)'><!\[CDATA\[sitetest_http\]\]/;
+                $testid = $1;
+        } else {
+                INFO("monitorus: Error in retrieving testNames page.");
+        }
+
+        #$target = $site.'/test/api/'.$key.'/testinfo/'.$testid.'/-240';
+        #$target = $site.'/test/api/'.$key.'/test/'.$testid.'/27/5/2009/1/3/-240';
+        $target = $site.'/test/api/'.$key.'/testsLastValues/1/3';
+
+        my $result;
+        my $value;
+        $res = $ua->get($target);
+        if ($res->is_success) {# Success....all content of page has been received
+                $res->content() =~ m/\<\/row\>\s*(\<row\>.*?sitetest_http.*?\<\/row\>)/s;
+                $result = $1;
+                $result =~ s/\<cell\>.*?CDATA.*?\<\/cell\>//g;
+                $result =~ m|\<cell\>([0-9]*)\<\/cell\>|;
+                $value = $1;
+        } else {
+                INFO("monitorus: Error in retrieving testsLastValues page.");
+        }
+
+        $prev_value = $value;
+        $vl->{'values'} = [ $value ];
+        plugin_dispatch_values ($vl);
+
+        return 1;
+}
+
+1;
diff --git a/bindings/perl/lib/Collectd/Plugins/OpenVZ.pm b/bindings/perl/lib/Collectd/Plugins/OpenVZ.pm
new file mode 100644 (file)
index 0000000..2944157
--- /dev/null
@@ -0,0 +1,190 @@
+#
+# collectd - OpenVZ collectd plugin
+# Copyright (C) 2009  Jonathan Kolb
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+#
+# Author:
+#   Jonathan Kolb <jon at b0g.us>
+#
+
+package Collectd::Plugins::OpenVZ;
+
+use strict;
+use warnings;
+
+use Collectd qw( :all );
+
+my @cpu_instances = ('user', 'nice', 'system', 'idle', 'wait', 'interrupt', 'softirq', 'steal');
+my @if_instances = ('if_octets', 'if_packets', 'if_errors');
+my $vzctl = '/usr/sbin/vzctl';
+my $vzlist = '/usr/sbin/vzlist';
+
+my $last_stat = {};
+
+sub openvz_read
+{
+    my %v = (time => time(), interval => $interval_g);
+    my (@veids, $veid, $name, $key, $val, $i, @lines, @parts, @counters);
+
+    @veids = map { s/ //g; $_; } split(/\n/, `$vzlist -Ho veid`);
+
+    foreach $veid (@veids)
+    {
+        ($name = `$vzlist -Ho name $veid`) =~ s/^\s*(.*?)\s*$/$1/;
+        $name = $veid if ($name =~ /^-$/);
+
+        $v{'host'} = $name;
+
+        #####################################################################
+        # interface
+
+        $v{'plugin'} = 'interface';
+        delete $v{'plugin_instance'};
+
+        @lines = split(/\n/, `$vzctl exec $veid cat /proc/net/dev`);
+        foreach (@lines)
+        {
+            next if (!/:/);                
+
+            @parts = split(/:/);
+            ($key = $parts[0]) =~ s/^\s*(.*?)\s*$/$1/;
+            ($val = $parts[1]) =~ s/^\s*(.*?)\s*$/$1/;
+            @counters = split(/ +/, $val);
+
+            $v{'type_instance'} = $key;
+            for ($key = 0; $key <= $#if_instances; ++$key)
+            {
+                $v{'type'} = $if_instances[$key];
+                $v{'values'} = [ $counters[$key], $counters[$key + 8] ];
+                plugin_dispatch_values(\%v);
+            }
+        }
+
+        #####################################################################
+        # cpu
+
+        $v{'plugin'} = 'cpu';
+        $v{'type'} = 'cpu';
+
+        $i = 0;
+        @lines = split(/\n/, `$vzctl exec $veid cat /proc/stat`);
+        foreach (@lines)
+        {
+            next if (!/^cpu[0-9]/);
+
+            @counters = split(/ +/);
+            shift(@counters);
+
+            # Remove once OpenVZ bug 1376 is resolved
+            if (48485 == $counters[3])
+            {
+                $counters[3] = $last_stat->{"$veid-$i-idle"};
+                $counters[4] = $last_stat->{"$veid-$i-wait"};
+            }
+            else
+            {
+                $last_stat->{"$veid-$i-idle"} = $counters[3];
+                $last_stat->{"$veid-$i-wait"} = $counters[4];
+            }
+
+            $v{'plugin_instance'} = $i++;
+            for ($key = 0; $key <= $#counters; ++$key)
+            {
+                $v{'type_instance'} = $cpu_instances[$key];
+                $v{'values'} = [ $counters[$key] ];
+                plugin_dispatch_values(\%v);
+            }
+        }
+
+        #####################################################################
+        # df
+
+        $v{'plugin'} = 'df';
+        delete $v{'plugin_instance'};
+        $v{'type'} = 'df';
+
+        $val = join(' ', map { (split)[1] } split(/\n/, `$vzctl exec $veid cat /proc/mounts`));
+        @lines = split(/\n/, `$vzctl exec $veid stat -tf $val`);
+        foreach (@lines)
+        {
+            @parts = split(/ /);
+            next if (0 == $parts[7]);
+
+            $val = substr($parts[0], 1);
+            $val = 'root' if ($val =~ /^$/);
+            $val =~ s#/#-#g;
+
+            $v{'type_instance'} = $val;
+            $v{'values'} = [ $parts[5] * ($parts[6] - $parts[7]), $parts[5] * $parts[7] ];
+            plugin_dispatch_values(\%v);
+        }
+
+        #####################################################################
+        # load
+
+        $v{'plugin'} = 'load';
+        delete $v{'plugin_instance'};
+        $v{'type'} = 'load';
+        delete $v{'type_instance'};
+
+        @parts = split(/ +/, `$vzctl exec $veid cat /proc/loadavg`);
+        $v{'values'} = [ $parts[0], $parts[1], $parts[2] ];
+        plugin_dispatch_values(\%v);
+
+        #####################################################################
+        # processes
+
+        my $ps_states = { 'paging' => 0, 'blocked' => 0, 'zombies' => 0, 'stopped' => 0,
+            'running' => 0, 'sleeping' => 0 };
+        my $state_map = { 'R' => 'running', 'S' => 'sleeping', 'D' => 'blocked',
+            'Z' => 'zombies', 'T' => 'stopped', 'W' => 'paging' };
+
+        $v{'plugin'} = 'processes';
+        delete $v{'plugin_instance'};
+        $v{'type'} = 'ps_state';
+
+        @lines = map { (split)[2] } split(/\n/, `$vzctl exec $veid cat '/proc/[0-9]*/stat'`);
+        foreach $key (@lines)
+        {
+            ++$ps_states->{$state_map->{$key}};
+        }
+
+        foreach $key (keys %{$ps_states})
+        {
+            $v{'type_instance'} = $key;
+            $v{'values'} = [ $ps_states->{$key} ];
+            plugin_dispatch_values(\%v);
+        }
+
+        #####################################################################
+        # users
+
+        $v{'plugin'} = 'users';
+        delete $v{'plugin_instance'};
+        $v{'type'} = 'users';
+        delete $v{'type_instance'};
+
+        @lines = split(/\n/, `$vzctl exec $veid w -h`);
+        $v{'values'} = [ scalar(@lines) ];
+        plugin_dispatch_values(\%v);
+    }
+
+    return 1;
+}
+
+plugin_register(TYPE_READ, 'OpenVZ', 'openvz_read');
+
+return 1;
diff --git a/bindings/perl/lib/Collectd/Unixsock.pm b/bindings/perl/lib/Collectd/Unixsock.pm
new file mode 100644 (file)
index 0000000..199a47c
--- /dev/null
@@ -0,0 +1,656 @@
+#
+# collectd - Collectd::Unixsock
+# Copyright (C) 2007,2008  Florian octo Forster
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; only version 2 of the License is applicable.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+#
+# Author:
+#   Florian octo Forster <octo at verplant.org>
+#
+
+package Collectd::Unixsock;
+
+=head1 NAME
+
+Collectd::Unixsock - Abstraction layer for accessing the functionality by
+collectd's unixsock plugin.
+
+=head1 SYNOPSIS
+
+  use Collectd::Unixsock ();
+
+  my $sock = Collectd::Unixsock->new ($path);
+
+  my $value = $sock->getval (%identifier);
+  $sock->putval (%identifier,
+                 time => time (),
+                values => [123, 234, 345]);
+
+  $sock->destroy ();
+
+=head1 DESCRIPTION
+
+collectd's unixsock plugin allows external programs to access the values it has
+collected or received and to submit own values. This Perl-module is simply a
+little abstraction layer over this interface to make it even easier for
+programmers to interact with the daemon.
+
+=cut
+
+use strict;
+use warnings;
+
+#use constant { NOTIF_FAILURE => 1, NOTIF_WARNING => 2, NOTIF_OKAY => 4 };
+
+use Carp (qw(cluck confess));
+use IO::Socket::UNIX;
+use Regexp::Common (qw(number));
+
+our $Debug = 0;
+
+return (1);
+
+sub _debug
+{
+       if (!$Debug)
+       {
+               return;
+       }
+       print @_;
+}
+
+sub _create_socket
+{
+       my $path = shift;
+       my $sock = IO::Socket::UNIX->new (Type => SOCK_STREAM, Peer => $path);
+       if (!$sock)
+       {
+               cluck ("Cannot open UNIX-socket $path: $!");
+               return;
+       }
+       return ($sock);
+} # _create_socket
+
+=head1 VALUE IDENTIFIERS
+
+The values in the collectd are identified using an five-tuple (host, plugin,
+plugin-instance, type, type-instance) where only plugin-instance and
+type-instance may be NULL (or undefined). Many functions expect an
+I<%identifier> hash that has at least the members B<host>, B<plugin>, and
+B<type>, possibly completed by B<plugin_instance> and B<type_instance>.
+
+Usually you can pass this hash as follows:
+
+  $obj->method (host => $host, plugin => $plugin, type => $type, %other_args);
+
+=cut
+
+sub _create_identifier
+{
+       my $args = shift;
+       my $host;
+       my $plugin;
+       my $type;
+
+       if (!$args->{'host'} || !$args->{'plugin'} || !$args->{'type'})
+       {
+               cluck ("Need `host', `plugin' and `type'");
+               return;
+       }
+
+       $host = $args->{'host'};
+       $plugin = $args->{'plugin'};
+       $plugin .= '-' . $args->{'plugin_instance'} if (defined ($args->{'plugin_instance'}));
+       $type = $args->{'type'};
+       $type .= '-' . $args->{'type_instance'} if (defined ($args->{'type_instance'}));
+
+       return ("$host/$plugin/$type");
+} # _create_identifier
+
+sub _parse_identifier
+{
+       my $string = shift;
+       my $host;
+       my $plugin;
+       my $plugin_instance;
+       my $type;
+       my $type_instance;
+       my $ident;
+
+       ($host, $plugin, $type) = split ('/', $string);
+
+       ($plugin, $plugin_instance) = split ('-', $plugin, 2);
+       ($type, $type_instance) = split ('-', $type, 2);
+
+       $ident =
+       {
+               host => $host,
+               plugin => $plugin,
+               type => $type
+       };
+       $ident->{'plugin_instance'} = $plugin_instance if (defined ($plugin_instance));
+       $ident->{'type_instance'} = $type_instance if (defined ($type_instance));
+
+       return ($ident);
+} # _parse_identifier
+
+sub _escape_argument
+{
+       my $string = shift;
+
+       if ($string =~ m/^\w+$/)
+       {
+               return ("$string");
+       }
+
+       $string =~ s#\\#\\\\#g;
+       $string =~ s#"#\\"#g;
+       $string = "\"$string\"";
+
+       return ($string);
+}
+
+=head1 PUBLIC METHODS
+
+=over 4
+
+=item I<$obj> = Collectd::Unixsock->B<new> ([I<$path>]);
+
+Creates a new connection to the daemon. The optional I<$path> argument gives
+the path to the UNIX socket of the C<unixsock plugin> and defaults to
+F</var/run/collectd-unixsock>. Returns the newly created object on success and
+false on error.
+
+=cut
+
+sub new
+{
+       my $pkg = shift;
+       my $path = @_ ? shift : '/var/run/collectd-unixsock';
+       my $sock = _create_socket ($path) or return;
+       my $obj = bless (
+               {
+                       path => $path,
+                       sock => $sock,
+                       error => 'No error'
+               }, $pkg);
+       return ($obj);
+} # new
+
+=item I<$res> = I<$obj>-E<gt>B<getval> (I<%identifier>);
+
+Requests a value-list from the daemon. On success a hash-ref is returned with
+the name of each data-source as the key and the according value as, well, the
+value. On error false is returned.
+
+=cut
+
+sub getval # {{{
+{
+       my $obj = shift;
+       my %args = @_;
+
+       my $status;
+       my $fh = $obj->{'sock'} or confess ('object has no filehandle');
+       my $msg;
+       my $identifier;
+
+       my $ret = {};
+
+       $identifier = _create_identifier (\%args) or return;
+
+       $msg = 'GETVAL ' . _escape_argument ($identifier) . "\n";
+       _debug "-> $msg";
+       print $fh $msg;
+
+       $msg = <$fh>;
+       chomp ($msg);
+       _debug "<- $msg\n";
+
+       ($status, $msg) = split (' ', $msg, 2);
+       if ($status <= 0)
+       {
+               $obj->{'error'} = $msg;
+               return;
+       }
+
+       for (my $i = 0; $i < $status; $i++)
+       {
+               my $entry = <$fh>;
+               chomp ($entry);
+               _debug "<- $entry\n";
+
+               if ($entry =~ m/^(\w+)=NaN$/)
+               {
+                       $ret->{$1} = undef;
+               }
+               elsif ($entry =~ m/^(\w+)=($RE{num}{real})$/)
+               {
+                       $ret->{$1} = 0.0 + $2;
+               }
+       }
+
+       return ($ret);
+} # }}} sub getval
+
+=item I<$res> = I<$obj>-E<gt>B<getthreshold> (I<%identifier>);
+
+Requests a threshold from the daemon. On success a hash-ref is returned with
+the threshold data. On error false is returned.
+
+=cut
+
+sub getthreshold # {{{
+{
+       my $obj = shift;
+       my %args = @_;
+
+       my $status;
+       my $fh = $obj->{'sock'} or confess ('object has no filehandle');
+       my $msg;
+       my $identifier;
+
+       my $ret = {};
+
+       $identifier = _create_identifier (\%args) or return;
+
+       $msg = 'GETTHRESHOLD ' . _escape_argument ($identifier) . "\n";
+       _debug "-> $msg";
+       print $fh $msg;
+
+       $msg = <$fh>;
+       chomp ($msg);
+       _debug "<- $msg\n";
+
+       ($status, $msg) = split (' ', $msg, 2);
+       if ($status <= 0)
+       {
+               $obj->{'error'} = $msg;
+               return;
+       }
+
+       for (my $i = 0; $i < $status; $i++)
+       {
+               my $entry = <$fh>;
+               chomp ($entry);
+               _debug "<- $entry\n";
+
+               if ($entry =~ m/^([^:]+):\s*(\S.*)$/)
+               {
+                       my $key = $1;
+                       my $value = $2;
+
+                       $key =~ s/^\s+//;
+                       $key =~ s/\s+$//;
+
+                       $ret->{$key} = $value;
+               }
+       }
+
+       return ($ret);
+} # }}} sub getthreshold
+
+=item I<$obj>-E<gt>B<putval> (I<%identifier>, B<time> =E<gt> I<$time>, B<values> =E<gt> [...]);
+
+Submits a value-list to the daemon. If the B<time> argument is omitted
+C<time()> is used. The required argument B<values> is a reference to an array
+of values that is to be submitted. The number of values must match the number
+of values expected for the given B<type> (see L<VALUE IDENTIFIERS>), though
+this is checked by the daemon, not the Perl module. Also, gauge data-sources
+(e.E<nbsp>g. system-load) may be C<undef>. Returns true upon success and false
+otherwise.
+
+=cut
+
+sub putval
+{
+       my $obj = shift;
+       my %args = @_;
+
+       my $status;
+       my $fh = $obj->{'sock'} or confess;
+       my $msg;
+       my $identifier;
+       my $values;
+       my $interval = "";
+
+       if (defined $args{'interval'})
+       {
+               $interval = ' interval='
+               . _escape_argument ($args{'interval'});
+       }
+
+       $identifier = _create_identifier (\%args) or return;
+       if (!$args{'values'})
+       {
+               cluck ("Need argument `values'");
+               return;
+       }
+
+       if (!ref ($args{'values'}))
+       {
+               $values = $args{'values'};
+       }
+       else
+       {
+               my $time;
+
+               if ("ARRAY" ne ref ($args{'values'}))
+               {
+                       cluck ("Invalid `values' argument (expected an array ref)");
+                       return;
+               }
+
+               if (! scalar @{$args{'values'}})
+               {
+                       cluck ("Empty `values' array");
+                       return;
+               }
+
+               $time = $args{'time'} ? $args{'time'} : time ();
+               $values = join (':', $time, map { defined ($_) ? $_ : 'U' } (@{$args{'values'}}));
+       }
+
+       $msg = 'PUTVAL '
+       . _escape_argument ($identifier)
+       . $interval
+       . ' ' . _escape_argument ($values) . "\n";
+       _debug "-> $msg";
+       print $fh $msg;
+
+       $msg = <$fh>;
+       chomp ($msg);
+       _debug "<- $msg\n";
+
+       ($status, $msg) = split (' ', $msg, 2);
+       return (1) if ($status == 0);
+
+       $obj->{'error'} = $msg;
+       return;
+} # putval
+
+=item I<$res> = I<$obj>-E<gt>B<listval> ()
+
+Queries a list of values from the daemon. The list is returned as an array of
+hash references, where each hash reference is a valid identifier. The C<time>
+member of each hash holds the epoch value of the last update of that value.
+
+=cut
+
+sub listval
+{
+       my $obj = shift;
+       my $msg;
+       my @ret = ();
+       my $status;
+       my $fh = $obj->{'sock'} or confess;
+
+       _debug "LISTVAL\n";
+       print $fh "LISTVAL\n";
+
+       $msg = <$fh>;
+       chomp ($msg);
+       _debug "<- $msg\n";
+       ($status, $msg) = split (' ', $msg, 2);
+       if ($status < 0)
+       {
+               $obj->{'error'} = $msg;
+               return;
+       }
+
+       for (my $i = 0; $i < $status; $i++)
+       {
+               my $time;
+               my $ident;
+
+               $msg = <$fh>;
+               chomp ($msg);
+               _debug "<- $msg\n";
+
+               ($time, $ident) = split (' ', $msg, 2);
+
+               $ident = _parse_identifier ($ident);
+               $ident->{'time'} = int ($time);
+
+               push (@ret, $ident);
+       } # for (i = 0 .. $status)
+
+       return (@ret);
+} # listval
+
+=item I<$res> = I<$obj>-E<gt>B<putnotif> (B<severity> =E<gt> I<$severity>, B<message> =E<gt> I<$message>, ...);
+
+Submits a notification to the daemon.
+
+Valid options are:
+
+=over 4
+
+=item B<severity>
+
+Sets the severity of the notification. The value must be one of the following
+strings: C<failure>, C<warning>, or C<okay>. Case does not matter. This option
+is mandatory.
+
+=item B<message>
+
+Sets the message of the notification. This option is mandatory.
+
+=item B<time>
+
+Sets the time. If omitted, C<time()> is used.
+
+=item I<Value identifier>
+
+All the other fields of the value identifiers, B<host>, B<plugin>,
+B<plugin_instance>, B<type>, and B<type_instance>, are optional. When given,
+the notification is associated with the performance data of that identifier.
+For more details, please see L<collectd-unixsock(5)>.
+
+=back
+
+=cut
+
+sub putnotif
+{
+       my $obj = shift;
+       my %args = @_;
+
+       my $status;
+       my $fh = $obj->{'sock'} or confess;
+
+       my $msg; # message sent to the socket
+       
+       if (!$args{'message'})
+       {
+               cluck ("Need argument `message'");
+               return;
+       }
+       if (!$args{'severity'})
+       {
+               cluck ("Need argument `severity'");
+               return;
+       }
+       $args{'severity'} = lc ($args{'severity'});
+       if (($args{'severity'} ne 'failure')
+               && ($args{'severity'} ne 'warning')
+               && ($args{'severity'} ne 'okay'))
+       {
+               cluck ("Invalid `severity: " . $args{'severity'});
+               return;
+       }
+
+       if (!$args{'time'})
+       {
+               $args{'time'} = time ();
+       }
+       
+       $msg = 'PUTNOTIF '
+       . join (' ', map { $_ . '=' . _escape_argument ($args{$_}) } (keys %args))
+       . "\n";
+
+       _debug "-> $msg";
+       print $fh $msg;
+
+       $msg = <$fh>;
+       chomp ($msg);
+       _debug "<- $msg\n";
+
+       ($status, $msg) = split (' ', $msg, 2);
+       return (1) if ($status == 0);
+
+       $obj->{'error'} = $msg;
+       return;
+} # putnotif
+
+=item I<$obj>-E<gt>B<flush> (B<timeout> =E<gt> I<$timeout>, B<plugins> =E<gt> [...], B<identifier>  =E<gt> [...]);
+
+Flush cached data.
+
+Valid options are:
+
+=over 4
+
+=item B<timeout>
+
+If this option is specified, only data older than I<$timeout> seconds is
+flushed.
+
+=item B<plugins>
+
+If this option is specified, only the selected plugins will be flushed. The
+argument is a reference to an array of strings.
+
+=item B<identifier>
+
+If this option is specified, only the given identifier(s) will be flushed. The
+argument is a reference to an array of identifiers. Identifiers, in this case,
+are hash references and have the members as outlined in L<VALUE IDENTIFIERS>.
+
+=back
+
+=cut
+
+sub flush
+{
+       my $obj  = shift;
+       my %args = @_;
+
+       my $fh = $obj->{'sock'} or confess;
+
+       my $status = 0;
+       my $msg    = "FLUSH";
+
+       if (defined ($args{'timeout'}))
+       {
+               $msg .= " timeout=" . $args{'timeout'};
+       }
+
+       if ($args{'plugins'})
+       {
+               foreach my $plugin (@{$args{'plugins'}})
+               {
+                       $msg .= " plugin=" . $plugin;
+               }
+       }
+
+       if ($args{'identifier'})
+       {
+               for (@{$args{'identifier'}})
+               {
+                       my $identifier = $_;
+                       my $ident_str;
+
+                       if (ref ($identifier) ne 'HASH')
+                       {
+                               cluck ("The argument of the `identifier' "
+                                       . "option must be an array reference "
+                                       . "of hash references.");
+                               return;
+                       }
+
+                       $ident_str = _create_identifier ($identifier);
+                       if (!$ident_str)
+                       {
+                               return;
+                       }
+
+                       $msg .= ' identifier=' . _escape_argument ($ident_str);
+               }
+       }
+
+       $msg .= "\n";
+
+       _debug "-> $msg";
+       print $fh $msg;
+
+       $msg = <$fh>;
+       chomp ($msg);
+       _debug "<- $msg\n";
+
+       ($status, $msg) = split (' ', $msg, 2);
+       return (1) if ($status == 0);
+
+       $obj->{'error'} = $msg;
+       return;
+}
+
+sub error
+{
+       my $obj = shift;
+       if ($obj->{'error'})
+       {
+               return ($obj->{'error'});
+       }
+       return;
+}
+
+=item I<$obj>-E<gt>destroy ();
+
+Closes the socket before the object is destroyed. This function is also
+automatically called then the object goes out of scope.
+
+=back
+
+=cut
+
+sub destroy
+{
+       my $obj = shift;
+       if ($obj->{'sock'})
+       {
+               close ($obj->{'sock'});
+               delete ($obj->{'sock'});
+       }
+}
+
+sub DESTROY
+{
+       my $obj = shift;
+       $obj->destroy ();
+}
+
+=head1 SEE ALSO
+
+L<collectd(1)>,
+L<collectd.conf(5)>,
+L<collectd-unixsock(5)>
+
+=head1 AUTHOR
+
+Florian octo Forster E<lt>octo@verplant.orgE<gt>
+
+=cut
+
+# vim: set fdm=marker :
diff --git a/build.sh b/build.sh
new file mode 100755 (executable)
index 0000000..20854b1
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,59 @@
+#! /bin/sh
+
+GLOBAL_ERROR_INDICATOR=0
+
+check_for_application ()
+{
+       for PROG in "$@"
+       do
+               which "$PROG" >/dev/null 2>&1
+               if test $? -ne 0; then
+                       cat >&2 <<EOF
+WARNING: \`$PROG' not found!
+    Please make sure that \`$PROG' is installed and is in one of the
+    directories listed in the PATH environment variable.
+EOF
+                       GLOBAL_ERROR_INDICATOR=1
+               fi
+       done
+}
+
+check_for_application lex yacc autoheader aclocal automake autoconf
+
+# Actually we don't need the pkg-config executable, but we need the M4 macros.
+# We check for `pkg-config' here and hope that M4 macros will then be
+# available, too.
+check_for_application pkg-config
+
+libtoolize=""
+libtoolize --version >/dev/null 2>/dev/null
+if test $? -eq 0
+then
+       libtoolize=libtoolize
+else
+       glibtoolize --version >/dev/null 2>/dev/null
+       if test $? -eq 0
+       then
+               libtoolize=glibtoolize
+       else
+               cat >&2 <<EOF
+WARNING: Neither \`libtoolize' nor \`glibtoolize' have been found!
+    Please make sure that one of them is installed and is in one of the
+    directories listed in the PATH environment variable.
+EOF
+               GLOBAL_ERROR_INDICATOR=1
+       fi
+ fi
+
+if test "$GLOBAL_ERROR_INDICATOR" != "0"
+then
+       exit 1
+fi
+
+set -x
+
+autoheader \
+&& aclocal \
+&& $libtoolize --ltdl --copy --force \
+&& automake --add-missing --copy \
+&& autoconf
diff --git a/clean.sh b/clean.sh
new file mode 100755 (executable)
index 0000000..098669d
--- /dev/null
+++ b/clean.sh
@@ -0,0 +1,50 @@
+#! /bin/sh
+
+set -x
+
+true \
+&& rm -f aclocal.m4 \
+&& rm -f -r autom4te.cache \
+&& rm -f collectd-*.tar.bz2 \
+&& rm -f collectd-*.tar.gz \
+&& rm -f compile \
+&& rm -f config.guess \
+&& rm -f config.log \
+&& rm -f config.status \
+&& rm -f config.sub \
+&& rm -f configure \
+&& rm -f depcomp \
+&& rm -f install-sh \
+&& rm -f -r libltdl \
+&& rm -f libtool \
+&& rm -f ltmain.sh \
+&& rm -f Makefile \
+&& rm -f Makefile.in \
+&& rm -f missing \
+&& rm -f -r src/.deps \
+&& rm -f -r src/.libs \
+&& rm -f src/*.o \
+&& rm -f src/*.la \
+&& rm -f src/*.lo \
+&& rm -f src/collectd \
+&& rm -f src/collectd.1 \
+&& rm -f src/config.h \
+&& rm -f src/config.h.in \
+&& rm -f src/config.h.in~ \
+&& rm -f src/Makefile \
+&& rm -f src/Makefile.in \
+&& rm -f src/stamp-h1 \
+&& rm -f src/stamp-h1.in \
+&& rm -f -r src/libping/.libs \
+&& rm -f src/libping/*.o \
+&& rm -f src/libping/*.la \
+&& rm -f src/libping/*.lo \
+&& rm -f src/libping/config.h \
+&& rm -f src/libping/config.h.in \
+&& rm -f src/libping/Makefile \
+&& rm -f src/libping/Makefile.in \
+&& rm -f src/libping/stamp-h2 \
+&& rm -f -r src/libcollectdclient/.libs \
+&& rm -f src/libcollectdclient/*.o \
+&& rm -f src/libcollectdclient/*.la \
+&& rm -f src/libcollectdclient/*.lo
diff --git a/configure.in b/configure.in
new file mode 100644 (file)
index 0000000..b3ab7c9
--- /dev/null
@@ -0,0 +1,5105 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_INIT(collectd, m4_esyscmd(./version-gen.sh))
+AC_CONFIG_SRCDIR(src/collectd.c)
+AC_CONFIG_HEADERS(src/config.h)
+AC_CONFIG_AUX_DIR([libltdl/config])
+
+m4_ifdef([LT_PACKAGE_VERSION],
+       # libtool >= 2.2
+       [
+        LT_CONFIG_LTDL_DIR([libltdl])
+        LT_INIT([dlopen])
+        LTDL_INIT([convenience])
+        AC_DEFINE(LIBTOOL_VERSION, 2, [Define to used libtool version.])
+       ]
+,
+       # libtool <= 1.5
+       [
+        AC_LIBLTDL_CONVENIENCE
+        AC_SUBST(LTDLINCL)
+        AC_SUBST(LIBLTDL)
+        AC_LIBTOOL_DLOPEN
+        AC_CONFIG_SUBDIRS(libltdl)
+        AC_DEFINE(LIBTOOL_VERSION, 1, [Define to used libtool version.])
+       ]
+)
+
+AM_INIT_AUTOMAKE(dist-bzip2)
+AC_LANG(C)
+
+AC_PREFIX_DEFAULT("/opt/collectd")
+
+AC_SYS_LARGEFILE
+
+#
+# Checks for programs.
+#
+AC_PROG_CC
+AC_PROG_CPP
+AC_PROG_INSTALL
+AC_PROG_LN_S
+AC_PROG_MAKE_SET
+AM_PROG_CC_C_O
+AM_CONDITIONAL(COMPILER_IS_GCC, test "x$GCC" = "xyes")
+
+AC_DISABLE_STATIC
+AC_PROG_LIBTOOL
+AC_PROG_LEX
+AC_PROG_YACC
+PKG_PROG_PKG_CONFIG
+
+AC_CHECK_PROG([have_protoc_c], [protoc-c], [yes], [no])
+AM_CONDITIONAL(HAVE_PROTOC_C, test "x$have_protoc_c" = "xyes")
+
+AC_MSG_CHECKING([for kernel type ($host_os)])
+case $host_os in
+       *linux*)
+       AC_DEFINE([KERNEL_LINUX], 1, [True if program is to be compiled for a Linux kernel])
+       ac_system="Linux"
+       ;;
+       *solaris*)
+       AC_DEFINE([KERNEL_SOLARIS], 1, [True if program is to be compiled for a Solaris kernel])
+       ac_system="Solaris"
+       ;;
+       *darwin*)
+       ac_system="Darwin"
+       ;;
+       *openbsd*)
+       ac_system="OpenBSD"
+       ;;
+       *aix*)
+       AC_DEFINE([KERNEL_AIX], 1, [True if program is to be compiled for a AIX kernel])
+       ac_system="AIX"
+       ;;
+       *)
+       ac_system="unknown"
+esac
+AC_MSG_RESULT([$ac_system])
+
+if test "x$ac_system" = "xLinux"
+then
+       AC_ARG_VAR([KERNEL_DIR], [path to Linux kernel sources])
+       if test -z "$KERNEL_DIR"
+       then
+               KERNEL_DIR="/lib/modules/`uname -r`/source"
+       fi
+
+       KERNEL_CFLAGS="-I$KERNEL_DIR/include"
+       AC_SUBST(KERNEL_CFLAGS)
+fi
+
+if test "x$ac_system" = "xSolaris"
+then
+       AC_DEFINE(_POSIX_PTHREAD_SEMANTICS, 1, [Define to enforce POSIX thread semantics under Solaris.])
+fi
+if test "x$ac_system" = "xAIX"
+then
+       AC_DEFINE(_THREAD_SAFE_ERRNO, 1, [Define to use the thread-safe version of errno under AIX.])
+fi
+
+# Where to install .pc files.
+pkgconfigdir="${libdir}/pkgconfig"
+AC_SUBST(pkgconfigdir)
+
+# Check for standards compliance mode
+AC_ARG_ENABLE(standards,
+             AS_HELP_STRING([--enable-standards], [Enable standards compliance mode]),
+             [enable_standards="$enableval"],
+             [enable_standards="no"])
+if test "x$enable_standards" = "xyes"
+then
+       AC_DEFINE(_ISOC99_SOURCE,        1, [Define to enforce ISO C99 compliance.])
+       AC_DEFINE(_POSIX_C_SOURCE, 200809L, [Define to enforce POSIX.1-2008 compliance.])
+       AC_DEFINE(_XOPEN_SOURCE,       700, [Define to enforce X/Open 7 (XSI) compliance.])
+       AC_DEFINE(_REENTRANT,            1, [Define to enable reentrancy interfaces.])
+       if test "x$GCC" = "xyes"
+       then
+               CFLAGS="$CFLAGS -std=c99"
+       fi
+fi
+AM_CONDITIONAL(BUILD_FEATURE_STANDARDS, test "x$enable_standards" = "xyes")
+
+#
+# Checks for header files.
+#
+AC_HEADER_STDC
+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)
+
+# For ping library
+AC_CHECK_HEADERS(netinet/in_systm.h, [], [],
+[#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#if HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+])
+AC_CHECK_HEADERS(netinet/in.h, [], [],
+[#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#if HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#if HAVE_NETINET_IN_SYSTM_H
+# include <netinet/in_systm.h>
+#endif
+])
+AC_CHECK_HEADERS(netinet/ip.h, [], [],
+[#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#if HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#if HAVE_NETINET_IN_SYSTM_H
+# include <netinet/in_systm.h>
+#endif
+#if HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+])
+AC_CHECK_HEADERS(netinet/ip_icmp.h, [], [],
+[#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#if HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#if HAVE_NETINET_IN_SYSTM_H
+# include <netinet/in_systm.h>
+#endif
+#if HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+#if HAVE_NETINET_IP_H
+# include <netinet/ip.h>
+#endif
+])
+AC_CHECK_HEADERS(netinet/ip_var.h, [], [],
+[#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#if HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#if HAVE_NETINET_IN_SYSTM_H
+# include <netinet/in_systm.h>
+#endif
+#if HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+#if HAVE_NETINET_IP_H
+# include <netinet/ip.h>
+#endif
+])
+AC_CHECK_HEADERS(netinet/ip6.h, [], [],
+[#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#if HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#if HAVE_NETINET_IN_SYSTM_H
+# include <netinet/in_systm.h>
+#endif
+#if HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+])
+AC_CHECK_HEADERS(netinet/icmp6.h, [], [],
+[#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#if HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#if HAVE_NETINET_IN_SYSTM_H
+# include <netinet/in_systm.h>
+#endif
+#if HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+#if HAVE_NETINET_IP6_H
+# include <netinet/ip6.h>
+#endif
+])
+AC_CHECK_HEADERS(netinet/tcp.h, [], [],
+[#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#if HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#if HAVE_NETINET_IN_SYSTM_H
+# include <netinet/in_systm.h>
+#endif
+#if HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+#if HAVE_NETINET_IP_H
+# include <netinet/ip.h>
+#endif
+])
+AC_CHECK_HEADERS(netinet/udp.h, [], [],
+[#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#if HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#if HAVE_NETINET_IN_SYSTM_H
+# include <netinet/in_systm.h>
+#endif
+#if HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+#if HAVE_NETINET_IP_H
+# include <netinet/ip.h>
+#endif
+])
+
+# For cpu modules
+AC_CHECK_HEADERS(sys/dkstat.h)
+if test "x$ac_system" = "xDarwin"
+then
+       AC_CHECK_HEADERS(mach/mach_init.h mach/host_priv.h mach/mach_error.h mach/mach_host.h mach/mach_port.h mach/mach_types.h mach/message.h mach/processor_set.h mach/processor.h mach/processor_info.h mach/task.h mach/thread_act.h mach/vm_region.h mach/vm_map.h mach/vm_prot.h mach/vm_statistics.h mach/kern_return.h)
+       AC_CHECK_HEADERS(CoreFoundation/CoreFoundation.h IOKit/IOKitLib.h IOKit/IOTypes.h IOKit/ps/IOPSKeys.h IOKit/IOBSD.h IOKit/storage/IOBlockStorageDriver.h)
+fi
+AC_CHECK_HEADERS(sys/sysctl.h, [], [],
+[
+#if HAVE_SYS_TYPES_H
+#  include <sys/types.h>
+#endif
+#if HAVE_SYS_PARAM_H
+# include <sys/param.h>
+#endif
+])
+
+AC_MSG_CHECKING([for sysctl kern.cp_times])
+if test -x /sbin/sysctl
+then
+       /sbin/sysctl kern.cp_times 2>/dev/null
+       if test $? -eq 0
+       then
+               AC_MSG_RESULT([yes])
+               AC_DEFINE(HAVE_SYSCTL_KERN_CP_TIMES, 1,
+               [Define if sysctl supports kern.cp_times])
+       else
+               AC_MSG_RESULT([no])
+       fi
+else
+       AC_MSG_RESULT([no])
+fi
+
+# For hddtemp module
+AC_CHECK_HEADERS(linux/major.h libgen.h)
+
+# For the battery plugin
+AC_CHECK_HEADERS(IOKit/ps/IOPowerSources.h, [], [],
+[
+#if HAVE_IOKIT_IOKITLIB_H
+#  include <IOKit/IOKitLib.h>
+#endif
+#if HAVE_IOKIT_IOTYPES_H
+#  include <IOKit/IOTypes.h>
+#endif
+])
+
+# For the swap module
+have_linux_wireless_h="no"
+if test "x$ac_system" = "xLinux"
+then
+  AC_CHECK_HEADERS(linux/wireless.h,
+                  [have_linux_wireless_h="yes"],
+                  [have_linux_wireless_h="no"],
+[
+#include <dirent.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+])
+fi
+
+# For the swap module
+have_sys_swap_h="yes"
+AC_CHECK_HEADERS(sys/swap.h vm/anon.h, [], [have_sys_swap_h="no"],
+[
+#undef _FILE_OFFSET_BITS
+#undef _LARGEFILE64_SOURCE
+#if HAVE_SYS_TYPES_H
+#  include <sys/types.h>
+#endif
+#if HAVE_SYS_PARAM_H
+# include <sys/param.h>
+#endif
+])
+
+if test "x$have_sys_swap_h$ac_system" = "xnoSolaris"
+then
+       hint_64=""
+       if test "x$GCC" = "xyes"
+       then
+               hint_64="CFLAGS='-m64'"
+       else
+               hint_64="CFLAGS='-xarch=v9'"
+       fi
+       AC_MSG_NOTICE([Solaris detected and sys/swap.h not usable. Try building a 64-bit binary ($hint_64 ./configure).])
+fi
+
+# For load module
+# For the processes plugin
+# For users module
+AC_CHECK_HEADERS(sys/loadavg.h linux/config.h utmp.h utmpx.h)
+
+# For interface plugin
+AC_CHECK_HEADERS(ifaddrs.h)
+AC_CHECK_HEADERS(net/if.h, [], [],
+[
+#if HAVE_SYS_TYPES_H
+#  include <sys/types.h>
+#endif
+#if HAVE_SYS_SOCKET_H
+#  include <sys/socket.h>
+#endif
+])
+AC_CHECK_HEADERS(linux/if.h, [], [],
+[
+#if HAVE_SYS_TYPES_H
+#  include <sys/types.h>
+#endif
+#if HAVE_SYS_SOCKET_H
+#  include <sys/socket.h>
+#endif
+])
+AC_CHECK_HEADERS(linux/netdevice.h, [], [],
+[
+#if HAVE_SYS_TYPES_H
+#  include <sys/types.h>
+#endif
+#if HAVE_SYS_SOCKET_H
+#  include <sys/socket.h>
+#endif
+#if HAVE_LINUX_IF_H
+# include <linux/if.h>
+#endif
+])
+
+# For ipvs module
+have_linux_ip_vs_h="no"
+have_net_ip_vs_h="no"
+have_ip_vs_h="no"
+ip_vs_h_needs_kernel_cflags="no"
+if test "x$ac_system" = "xLinux"
+then
+       AC_CHECK_HEADERS(linux/ip_vs.h, [have_linux_ip_vs_h="yes"])
+       AC_CHECK_HEADERS(net/ip_vs.h, [have_net_ip_vs_h="yes"])
+       AC_CHECK_HEADERS(ip_vs.h, [have_ip_vs_h="yes"])
+
+       if test "x$have_linux_ip_vs_h$have_net_ip_vs_h$have_ip_vs_h" = "xnonono" && test -d "$KERNEL_DIR"
+       then
+               SAVE_CFLAGS="$CFLAGS"
+               CFLAGS="$CFLAGS $KERNEL_CFLAGS"
+
+               AC_MSG_NOTICE([Did not find ip_vs.h. Trying again using headers from $KERNEL_DIR.])
+
+               AC_CHECK_HEADERS(linux/ip_vs.h, [have_linux_ip_vs_h="yes"])
+               AC_CHECK_HEADERS(net/ip_vs.h, [have_net_ip_vs_h="yes"])
+               AC_CHECK_HEADERS(ip_vs.h, [have_ip_vs_h="yes"])
+
+               if test "x$have_linux_ip_vs_h" = "xyes" || test "x$have_net_ip_vs_h" = "xyes" || test "x$have_ip_vs_h" = "xyes"
+               then
+                       ip_vs_h_needs_kernel_cflags="yes"
+               fi
+
+               CFLAGS="$SAVE_CFLAGS"
+       fi
+fi
+AM_CONDITIONAL(IP_VS_H_NEEDS_KERNEL_CFLAGS, test "x$ip_vs_h_needs_kernel_cflags" = "xyes")
+
+# For quota module
+AC_CHECK_HEADERS(sys/ucred.h, [], [],
+[
+#if HAVE_SYS_TYPES_H
+#  include <sys/types.h>
+#endif
+#if HAVE_SYS_PARAM_H
+# include <sys/param.h>
+#endif
+])
+
+# For mount interface
+AC_CHECK_HEADERS(sys/mount.h, [], [],
+[
+#if HAVE_SYS_TYPES_H
+#  include <sys/types.h>
+#endif
+#if HAVE_SYS_PARAM_H
+# include <sys/param.h>
+#endif
+])
+
+# For the email plugin
+AC_CHECK_HEADERS(linux/un.h, [], [],
+[
+#if HAVE_SYS_SOCKET_H
+#      include <sys/socket.h>
+#endif
+])
+
+AC_CHECK_HEADERS(pwd.h grp.h sys/un.h ctype.h limits.h xfs/xqm.h fs_info.h fshelp.h paths.h mntent.h mnttab.h sys/fstyp.h sys/fs_types.h sys/mntent.h sys/mnttab.h sys/statfs.h sys/statvfs.h sys/vfs.h sys/vfstab.h kvm.h wordexp.h)
+
+# For the dns plugin
+AC_CHECK_HEADERS(arpa/nameser.h)
+AC_CHECK_HEADERS(arpa/nameser_compat.h, [], [],
+[
+#if HAVE_ARPA_NAMESER_H
+# include <arpa/nameser.h>
+#endif
+])
+
+AC_CHECK_HEADERS(net/if_arp.h, [], [],
+[#if HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif
+])
+AC_CHECK_HEADERS(net/ppp_defs.h)
+AC_CHECK_HEADERS(net/if_ppp.h, [], [],
+[#if HAVE_NET_PPP_DEFS_H
+# include <net/ppp_defs.h>
+#endif
+])
+AC_CHECK_HEADERS(netinet/if_ether.h, [], [],
+[#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#if HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#if HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif
+#if HAVE_NET_IF_H
+# include <net/if.h>
+#endif
+#if HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+])
+
+# For the multimeter plugin
+have_termios_h="no"
+AC_CHECK_HEADERS(termios.h, [have_termios_h="yes"])
+
+#
+# Checks for typedefs, structures, and compiler characteristics.
+#
+AC_C_CONST
+AC_TYPE_PID_T
+AC_TYPE_SIZE_T
+AC_TYPE_UID_T
+AC_HEADER_TIME
+
+#
+# Checks for library functions.
+#
+AC_PROG_GCC_TRADITIONAL
+AC_CHECK_FUNCS(gettimeofday select strdup strtol getaddrinfo getnameinfo strchr memcpy strstr strcmp strncmp strncpy strlen strncasecmp strcasecmp openlog closelog sysconf setenv if_indextoname)
+
+AC_FUNC_STRERROR_R
+
+SAVE_CFLAGS="$CFLAGS"
+# Emulate behavior of src/Makefile.am
+if test "x$GCC" = "xyes"
+then
+       CFLAGS="$CFLAGS -Wall -Werror"
+fi
+
+AC_CACHE_CHECK([for strtok_r],
+  [c_cv_have_strtok_r_default],
+  AC_LINK_IFELSE(
+    AC_LANG_PROGRAM(
+    [[[[
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+    ]]]],
+    [[[[
+      char buffer[] = "foo,bar,baz";
+      char *token;
+      char *dummy;
+      char *saveptr;
+
+      dummy = buffer;
+      saveptr = NULL;
+      while ((token = strtok_r (dummy, ",", &saveptr)) != NULL)
+      {
+       dummy = NULL;
+        printf ("token = %s;\n", token);
+      }
+    ]]]]),
+    [c_cv_have_strtok_r_default="yes"],
+    [c_cv_have_strtok_r_default="no"]
+  )
+)
+
+if test "x$c_cv_have_strtok_r_default" = "xno"
+then
+  CFLAGS="$CFLAGS -D_REENTRANT=1"
+
+  AC_CACHE_CHECK([if strtok_r needs _REENTRANT],
+    [c_cv_have_strtok_r_reentrant],
+    AC_LINK_IFELSE(
+      AC_LANG_PROGRAM(
+      [[[[
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+      ]]]],
+      [[[[
+        char buffer[] = "foo,bar,baz";
+        char *token;
+        char *dummy;
+        char *saveptr;
+
+        dummy = buffer;
+        saveptr = NULL;
+        while ((token = strtok_r (dummy, ",", &saveptr)) != NULL)
+        {
+         dummy = NULL;
+          printf ("token = %s;\n", token);
+        }
+      ]]]]),
+      [c_cv_have_strtok_r_reentrant="yes"],
+      [AC_MSG_FAILURE([strtok_r isn't available. Please file a bugreport!])]
+    )
+  )
+fi
+
+CFLAGS="$SAVE_CFLAGS"
+if test "x$c_cv_have_strtok_r_reentrant" = "xyes"
+then
+       CFLAGS="$CFLAGS -D_REENTRANT=1"
+fi
+
+AC_CHECK_FUNCS(getpwnam_r getgrnam_r setgroups regcomp regerror regexec regfree)
+
+socket_needs_socket="no"
+AC_CHECK_FUNCS(socket, [], AC_CHECK_LIB(socket, socket, [socket_needs_socket="yes"], AC_MSG_ERROR(cannot find socket)))
+AM_CONDITIONAL(BUILD_WITH_LIBSOCKET, test "x$socket_needs_socket" = "xyes")
+
+clock_gettime_needs_rt="no"
+clock_gettime_needs_posix4="no"
+have_clock_gettime="no"
+AC_CHECK_FUNCS(clock_gettime, [have_clock_gettime="yes"])
+if test "x$have_clock_gettime" = "xno"
+then
+       AC_CHECK_LIB(rt, clock_gettime, [clock_gettime_needs_rt="yes"
+                                        have_clock_gettime="yes"])
+fi
+if test "x$have_clock_gettime" = "xno"
+then
+       AC_CHECK_LIB(posix4, clock_gettime, [clock_gettime_needs_posix4="yes"
+                                            have_clock_gettime="yes"])
+fi
+if test "x$have_clock_gettime" = "xyes"
+then
+       AC_DEFINE(HAVE_CLOCK_GETTIME, 1, [Define if the clock_gettime(2) function is available.])
+else
+       AC_MSG_WARN(cannot find clock_gettime)
+fi
+
+nanosleep_needs_rt="no"
+nanosleep_needs_posix4="no"
+AC_CHECK_FUNCS(nanosleep,
+    [],
+    AC_CHECK_LIB(rt, nanosleep,
+        [nanosleep_needs_rt="yes"],
+        AC_CHECK_LIB(posix4, nanosleep,
+            [nanosleep_needs_posix4="yes"],
+            AC_MSG_ERROR(cannot find nanosleep))))
+
+AM_CONDITIONAL(BUILD_WITH_LIBRT, test "x$clock_gettime_needs_rt" = "xyes" || test "x$nanosleep_needs_rt" = "xyes")
+AM_CONDITIONAL(BUILD_WITH_LIBPOSIX4, test "x$clock_gettime_needs_posix4" = "xyes" || test "x$nanosleep_needs_posix4" = "xyes")
+
+AC_CHECK_FUNCS(sysctl, [have_sysctl="yes"], [have_sysctl="no"])
+AC_CHECK_FUNCS(sysctlbyname, [have_sysctlbyname="yes"], [have_sysctlbyname="no"])
+AC_CHECK_FUNCS(host_statistics, [have_host_statistics="yes"], [have_host_statistics="no"])
+AC_CHECK_FUNCS(processor_info, [have_processor_info="yes"], [have_processor_info="no"])
+AC_CHECK_FUNCS(thread_info, [have_thread_info="yes"], [have_thread_info="no"])
+AC_CHECK_FUNCS(statfs, [have_statfs="yes"], [have_statfs="no"])
+AC_CHECK_FUNCS(statvfs, [have_statvfs="yes"], [have_statvfs="no"])
+AC_CHECK_FUNCS(getifaddrs, [have_getifaddrs="yes"], [have_getifaddrs="no"])
+AC_CHECK_FUNCS(getloadavg, [have_getloadavg="yes"], [have_getloadavg="no"])
+AC_CHECK_FUNCS(syslog, [have_syslog="yes"], [have_syslog="no"])
+AC_CHECK_FUNCS(getutent, [have_getutent="yes"], [have_getutent="no"])
+AC_CHECK_FUNCS(getutxent, [have_getutxent="yes"], [have_getutxent="no"])
+
+# Check for strptime {{{
+if test "x$GCC" = "xyes"
+then
+       SAVE_CFLAGS="$CFLAGS"
+       CFLAGS="$CFLAGS -Wall -Wextra -Werror"
+fi
+
+AC_CHECK_FUNCS(strptime, [have_strptime="yes"], [have_strptime="no"])
+if test "x$have_strptime" = "xyes"
+then
+       AC_CACHE_CHECK([whether strptime is exported by default],
+                      [c_cv_have_strptime_default],
+                      AC_COMPILE_IFELSE(
+AC_LANG_PROGRAM(
+[[
+AC_INCLUDES_DEFAULT
+#include <time.h>
+]],
+[[
+ struct tm stm;
+ (void) strptime ("2010-12-30%13:42:42", "%Y-%m-%dT%T", &stm);
+]]),
+                      [c_cv_have_strptime_default="yes"],
+                      [c_cv_have_strptime_default="no"]))
+fi
+if test "x$have_strptime" = "xyes" && test "x$c_cv_have_strptime_default" = "xno"
+then
+       AC_CACHE_CHECK([whether strptime needs standards mode],
+                      [c_cv_have_strptime_standards],
+                      AC_COMPILE_IFELSE(
+AC_LANG_PROGRAM(
+[[
+#ifndef _ISOC99_SOURCE
+# define _ISOC99_SOURCE 1
+#endif
+#ifndef _POSIX_C_SOURCE
+# define _POSIX_C_SOURCE 200112L
+#endif
+#ifndef _XOPEN_SOURCE
+# define _XOPEN_SOURCE 500
+#endif
+AC_INCLUDES_DEFAULT
+#include <time.h>
+]],
+[[
+ struct tm stm;
+ (void) strptime ("2010-12-30%13:42:42", "%Y-%m-%dT%T", &stm);
+]]),
+                      [c_cv_have_strptime_standards="yes"],
+                      [c_cv_have_strptime_standards="no"]))
+
+       if test "x$c_cv_have_strptime_standards" = "xyes"
+       then
+               AC_DEFINE([STRPTIME_NEEDS_STANDARDS], 1, [Set to true if strptime is only exported in X/Open mode (GNU libc).])
+       else
+               have_strptime="no"
+       fi
+fi
+
+if test "x$GCC" = "xyes"
+then
+       CFLAGS="$SAVE_CFLAGS"
+fi
+
+# }}} Check for strptime
+
+AC_CHECK_FUNCS(swapctl, [have_swapctl="yes"], [have_swapctl="no"])
+if test "x$have_swapctl" = "xyes"; then
+        AC_CACHE_CHECK([whether swapctl takes two arguments],
+                [c_cv_have_swapctl_two_args],
+                AC_COMPILE_IFELSE(
+                        AC_LANG_PROGRAM([[AC_INCLUDES_DEFAULT
+#if HAVE_SYS_SWAP_H && !defined(_LP64) && _FILE_OFFSET_BITS == 64
+#  undef _FILE_OFFSET_BITS
+#  undef _LARGEFILE64_SOURCE
+#endif
+#include <sys/stat.h>
+#include <sys/swap.h>]],
+                                [[
+                                int num = swapctl(0, NULL);
+                                ]]
+                        ),
+                        [c_cv_have_swapctl_two_args="yes"],
+                        [c_cv_have_swapctl_two_args="no"]
+                )
+        )
+        AC_CACHE_CHECK([whether swapctl takes three arguments],
+                [c_cv_have_swapctl_three_args],
+                AC_COMPILE_IFELSE(
+                        AC_LANG_PROGRAM([[AC_INCLUDES_DEFAULT
+#if HAVE_SYS_SWAP_H && !defined(_LP64) && _FILE_OFFSET_BITS == 64
+#  undef _FILE_OFFSET_BITS
+#  undef _LARGEFILE64_SOURCE
+#endif
+#include <sys/stat.h>
+#include <sys/swap.h>]],
+                                [[
+                                int num = swapctl(0, NULL,0);
+                                ]]
+                        ),
+                        [c_cv_have_swapctl_three_args="yes"],
+                        [c_cv_have_swapctl_three_args="no"]
+                )
+        )
+fi
+# Check for different versions of `swapctl' here..
+if test "x$have_swapctl" = "xyes"; then
+        if test "x$c_cv_have_swapctl_two_args" = "xyes"; then
+                AC_DEFINE(HAVE_SWAPCTL_TWO_ARGS, 1,
+                          [Define if the function swapctl exists and takes two arguments.])
+        fi
+        if test "x$c_cv_have_swapctl_three_args" = "xyes"; then
+                AC_DEFINE(HAVE_SWAPCTL_THREE_ARGS, 1,
+                          [Define if the function swapctl exists and takes three arguments.])
+        fi
+fi
+
+# Check for NAN
+AC_ARG_WITH(nan-emulation, [AS_HELP_STRING([--with-nan-emulation], [use emulated NAN. For crosscompiling only.])],
+[
+ if test "x$withval" = "xno"; then
+        nan_type="none"
+ else if test "x$withval" = "xyes"; then
+        nan_type="zero"
+ else
+        nan_type="$withval"
+ fi; fi
+],
+[nan_type="none"])
+if test "x$nan_type" = "xnone"; then
+  AC_CACHE_CHECK([whether NAN is defined by default],
+    [c_cv_have_nan_default],
+    AC_COMPILE_IFELSE(
+      AC_LANG_PROGRAM(
+      [[
+#include <stdlib.h>
+#include <math.h>
+static double foo = NAN;
+      ]],
+      [[
+       if (isnan (foo))
+        return 0;
+       else
+       return 1;
+      ]]),
+      [c_cv_have_nan_default="yes"],
+      [c_cv_have_nan_default="no"]
+    )
+  )
+  if test "x$c_cv_have_nan_default" = "xyes"
+  then
+    nan_type="default"
+  fi
+fi
+if test "x$nan_type" = "xnone"; then
+  AC_CACHE_CHECK([whether NAN is defined by __USE_ISOC99],
+    [c_cv_have_nan_isoc],
+    AC_COMPILE_IFELSE(
+      AC_LANG_PROGRAM(
+      [[
+#include <stdlib.h>
+#define __USE_ISOC99 1
+#include <math.h>
+static double foo = NAN;
+      ]],
+      [[
+       if (isnan (foo))
+        return 0;
+       else
+       return 1;
+      ]]),
+      [c_cv_have_nan_isoc="yes"],
+      [c_cv_have_nan_isoc="no"]
+    )
+  )
+  if test "x$c_cv_have_nan_isoc" = "xyes"
+  then
+    nan_type="isoc99"
+  fi
+fi
+if test "x$nan_type" = "xnone"; then
+  SAVE_LDFLAGS=$LDFLAGS
+  LDFLAGS="$LDFLAGS -lm"
+  AC_CACHE_CHECK([whether NAN can be defined by 0/0],
+    [c_cv_have_nan_zero],
+    AC_RUN_IFELSE(
+      AC_LANG_PROGRAM(
+      [[
+#include <stdlib.h>
+#include <math.h>
+#ifdef NAN
+# undef NAN
+#endif
+#define NAN (0.0 / 0.0)
+#ifndef isnan
+# define isnan(f) ((f) != (f))
+#endif
+static double foo = NAN;
+      ]],
+      [[
+       if (isnan (foo))
+        return 0;
+       else
+       return 1;
+      ]]),
+      [c_cv_have_nan_zero="yes"],
+      [c_cv_have_nan_zero="no"]
+    )
+  )
+  LDFLAGS=$SAVE_LDFLAGS
+  if test "x$c_cv_have_nan_zero" = "xyes"
+  then
+    nan_type="zero"
+  fi
+fi
+
+if test "x$nan_type" = "xdefault"; then
+  AC_DEFINE(NAN_STATIC_DEFAULT, 1,
+    [Define if NAN is defined by default and can initialize static variables.])
+else if test "x$nan_type" = "xisoc99"; then
+  AC_DEFINE(NAN_STATIC_ISOC, 1,
+    [Define if NAN is defined by __USE_ISOC99 and can initialize static variables.])
+else if test "x$nan_type" = "xzero"; then
+  AC_DEFINE(NAN_ZERO_ZERO, 1,
+    [Define if NAN can be defined as (0.0 / 0.0)])
+else
+  AC_MSG_ERROR([Didn't find out how to statically initialize variables to NAN. Sorry.])
+fi; fi; fi
+
+AC_ARG_WITH(fp-layout, [AS_HELP_STRING([--with-fp-layout], [set the memory layout of doubles. For crosscompiling only.])],
+[
+ if test "x$withval" = "xnothing"; then
+       fp_layout_type="nothing"
+ else if test "x$withval" = "xendianflip"; then
+       fp_layout_type="endianflip"
+ else if test "x$withval" = "xintswap"; then
+       fp_layout_type="intswap"
+ else
+       AC_MSG_ERROR([Invalid argument for --with-fp-layout. Valid arguments are: nothing, endianflip, intswap]);
+fi; fi; fi
+],
+[fp_layout_type="unknown"])
+
+if test "x$fp_layout_type" = "xunknown"; then
+  AC_CACHE_CHECK([if doubles are stored in x86 representation],
+    [c_cv_fp_layout_need_nothing],
+    AC_RUN_IFELSE(
+      AC_LANG_PROGRAM(
+      [[[[
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#if HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+#if HAVE_STDBOOL_H
+# include <stdbool.h>
+#endif
+      ]]]],
+      [[[[
+       uint64_t i0;
+       uint64_t i1;
+       uint8_t c[8];
+       double d;
+
+       d = 8.642135e130; 
+       memcpy ((void *) &i0, (void *) &d, 8);
+
+       i1 = i0;
+       memcpy ((void *) c, (void *) &i1, 8);
+
+       if ((c[0] == 0x2f) && (c[1] == 0x25)
+                       && (c[2] == 0xc0) && (c[3] == 0xc7)
+                       && (c[4] == 0x43) && (c[5] == 0x2b)
+                       && (c[6] == 0x1f) && (c[7] == 0x5b))
+               return (0);
+       else
+               return (1);
+      ]]]]),
+      [c_cv_fp_layout_need_nothing="yes"],
+      [c_cv_fp_layout_need_nothing="no"]
+    )
+  )
+  if test "x$c_cv_fp_layout_need_nothing" = "xyes"; then
+    fp_layout_type="nothing"
+  fi
+fi
+if test "x$fp_layout_type" = "xunknown"; then
+  AC_CACHE_CHECK([if endianflip converts to x86 representation],
+    [c_cv_fp_layout_need_endianflip],
+    AC_RUN_IFELSE(
+      AC_LANG_PROGRAM(
+      [[[[
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#if HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+#if HAVE_STDBOOL_H
+# include <stdbool.h>
+#endif
+#define endianflip(A) ((((uint64_t)(A) & 0xff00000000000000LL) >> 56) | \
+                       (((uint64_t)(A) & 0x00ff000000000000LL) >> 40) | \
+                       (((uint64_t)(A) & 0x0000ff0000000000LL) >> 24) | \
+                       (((uint64_t)(A) & 0x000000ff00000000LL) >> 8)  | \
+                       (((uint64_t)(A) & 0x00000000ff000000LL) << 8)  | \
+                       (((uint64_t)(A) & 0x0000000000ff0000LL) << 24) | \
+                       (((uint64_t)(A) & 0x000000000000ff00LL) << 40) | \
+                       (((uint64_t)(A) & 0x00000000000000ffLL) << 56))
+      ]]]],
+      [[[[
+       uint64_t i0;
+       uint64_t i1;
+       uint8_t c[8];
+       double d;
+
+       d = 8.642135e130; 
+       memcpy ((void *) &i0, (void *) &d, 8);
+
+       i1 = endianflip (i0);
+       memcpy ((void *) c, (void *) &i1, 8);
+
+       if ((c[0] == 0x2f) && (c[1] == 0x25)
+                       && (c[2] == 0xc0) && (c[3] == 0xc7)
+                       && (c[4] == 0x43) && (c[5] == 0x2b)
+                       && (c[6] == 0x1f) && (c[7] == 0x5b))
+               return (0);
+       else
+               return (1);
+      ]]]]),
+      [c_cv_fp_layout_need_endianflip="yes"],
+      [c_cv_fp_layout_need_endianflip="no"]
+    )
+  )
+  if test "x$c_cv_fp_layout_need_endianflip" = "xyes"; then
+    fp_layout_type="endianflip"
+  fi
+fi
+if test "x$fp_layout_type" = "xunknown"; then
+  AC_CACHE_CHECK([if intswap converts to x86 representation],
+    [c_cv_fp_layout_need_intswap],
+    AC_RUN_IFELSE(
+      AC_LANG_PROGRAM(
+      [[[[
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#if HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+#if HAVE_STDBOOL_H
+# include <stdbool.h>
+#endif
+#define intswap(A)    ((((uint64_t)(A) & 0xffffffff00000000LL) >> 32) | \
+                       (((uint64_t)(A) & 0x00000000ffffffffLL) << 32))
+      ]]]],
+      [[[[
+       uint64_t i0;
+       uint64_t i1;
+       uint8_t c[8];
+       double d;
+
+       d = 8.642135e130; 
+       memcpy ((void *) &i0, (void *) &d, 8);
+
+       i1 = intswap (i0);
+       memcpy ((void *) c, (void *) &i1, 8);
+
+       if ((c[0] == 0x2f) && (c[1] == 0x25)
+                       && (c[2] == 0xc0) && (c[3] == 0xc7)
+                       && (c[4] == 0x43) && (c[5] == 0x2b)
+                       && (c[6] == 0x1f) && (c[7] == 0x5b))
+               return (0);
+       else
+               return (1);
+      ]]]]),
+      [c_cv_fp_layout_need_intswap="yes"],
+      [c_cv_fp_layout_need_intswap="no"]
+    )
+  )
+  if test "x$c_cv_fp_layout_need_intswap" = "xyes"; then
+    fp_layout_type="intswap"
+  fi
+fi
+
+if test "x$fp_layout_type" = "xnothing"; then
+  AC_DEFINE(FP_LAYOUT_NEED_NOTHING, 1,
+  [Define if doubles are stored in x86 representation.])
+else if test "x$fp_layout_type" = "xendianflip"; then
+  AC_DEFINE(FP_LAYOUT_NEED_ENDIANFLIP, 1,
+  [Define if endianflip is needed to convert to x86 representation.])
+else if test "x$fp_layout_type" = "xintswap"; then
+  AC_DEFINE(FP_LAYOUT_NEED_INTSWAP, 1,
+  [Define if intswap is needed to convert to x86 representation.])
+else
+  AC_MSG_ERROR([Didn't find out how doubles are stored in memory. Sorry.])
+fi; fi; fi
+
+have_getfsstat="no"
+AC_CHECK_FUNCS(getfsstat, [have_getfsstat="yes"])
+have_getvfsstat="no"
+AC_CHECK_FUNCS(getvfsstat, [have_getvfsstat="yes"])
+have_listmntent="no"
+AC_CHECK_FUNCS(listmntent, [have_listmntent="yes"])
+
+have_getmntent="no"
+AC_CHECK_FUNCS(getmntent, [have_getmntent="c"])
+if test "x$have_getmntent" = "xno"; then
+       AC_CHECK_LIB(sun, getmntent, [have_getmntent="sun"])
+fi
+if test "x$have_getmntent" = "xno"; then
+       AC_CHECK_LIB(seq, getmntent, [have_getmntent="seq"])
+fi
+if test "x$have_getmntent" = "xno"; then
+       AC_CHECK_LIB(gen, getmntent, [have_getmntent="gen"])
+fi
+
+if test "x$have_getmntent" = "xc"; then
+       AC_CACHE_CHECK([whether getmntent takes one argument],
+               [c_cv_have_one_getmntent],
+               AC_COMPILE_IFELSE(
+                       AC_LANG_PROGRAM([[AC_INCLUDES_DEFAULT
+#include "$srcdir/src/utils_mount.h"]],
+                               [[
+                                FILE *fh;
+                                struct mntent *me;
+                                fh = setmntent ("/etc/mtab", "r");
+                                me = getmntent (fh);
+                               ]]
+                       ),
+                       [c_cv_have_one_getmntent="yes"],
+                       [c_cv_have_one_getmntent="no"]
+               )
+       )
+       AC_CACHE_CHECK([whether getmntent takes two arguments],
+               [c_cv_have_two_getmntent],
+               AC_COMPILE_IFELSE(
+                       AC_LANG_PROGRAM([[AC_INCLUDES_DEFAULT
+#include "$srcdir/src/utils_mount.h"]],
+                               [[
+                                FILE *fh;
+                                struct mnttab mt;
+                                int status;
+                                fh = fopen ("/etc/mnttab", "r");
+                                status = getmntent (fh, &mt);
+                               ]]
+                       ),
+                       [c_cv_have_two_getmntent="yes"],
+                       [c_cv_have_two_getmntent="no"]
+               )
+       )
+fi
+
+# Check for different versions of `getmntent' here..
+
+if test "x$have_getmntent" = "xc"; then
+       if test "x$c_cv_have_one_getmntent" = "xyes"; then
+               AC_DEFINE(HAVE_ONE_GETMNTENT, 1,
+                         [Define if the function getmntent exists and takes one argument.])
+       fi
+       if test "x$c_cv_have_two_getmntent" = "xyes"; then
+               AC_DEFINE(HAVE_TWO_GETMNTENT, 1,
+                         [Define if the function getmntent exists and takes two arguments.])
+       fi
+fi
+if test "x$have_getmntent" = "xsun"; then
+       AC_DEFINE(HAVE_SUN_GETMNTENT, 1,
+                 [Define if the function getmntent exists. It's the version from libsun.])
+fi
+if test "x$have_getmntent" = "xseq"; then
+       AC_DEFINE(HAVE_SEQ_GETMNTENT, 1,
+                 [Define if the function getmntent exists. It's the version from libseq.])
+fi
+if test "x$have_getmntent" = "xgen"; then
+       AC_DEFINE(HAVE_GEN_GETMNTENT, 1,
+                 [Define if the function getmntent exists. It's the version from libgen.])
+fi
+
+# Check for htonll
+AC_MSG_CHECKING([if have htonll defined])
+
+    have_htonll="no"
+    AC_LINK_IFELSE([
+       AC_LANG_PROGRAM([
+#include <sys/types.h>
+#include <netinet/in.h>
+#if HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+       ], [
+          return htonll(0);
+       ])
+    ], [
+      have_htonll="yes"
+      AC_DEFINE(HAVE_HTONLL, 1, [Define if the function htonll exists.])
+    ])
+AC_MSG_RESULT([$have_htonll])
+
+# Check for structures
+AC_CHECK_MEMBERS([struct if_data.ifi_ibytes, struct if_data.ifi_opackets, struct if_data.ifi_ierrors],
+       [AC_DEFINE(HAVE_STRUCT_IF_DATA, 1, [Define if struct if_data exists and is usable.])],
+       [],
+       [
+       #include <sys/types.h>
+       #include <sys/socket.h>
+       #include <net/if.h>
+       ])
+AC_CHECK_MEMBERS([struct net_device_stats.rx_bytes, struct net_device_stats.tx_packets, struct net_device_stats.rx_errors],
+       [AC_DEFINE(HAVE_STRUCT_NET_DEVICE_STATS, 1, [Define if struct net_device_stats exists and is usable.])],
+       [],
+       [
+       #include <sys/types.h>
+       #include <sys/socket.h>
+       #include <linux/if.h>
+       #include <linux/netdevice.h>
+       ])
+
+AC_CHECK_MEMBERS([struct ip_mreqn.imr_ifindex], [],
+       [],
+       [
+       #include <netinet/in.h>
+       #include <net/if.h>
+       ])
+
+AC_CHECK_MEMBERS([struct kinfo_proc.ki_pid, struct kinfo_proc.ki_rssize, struct kinfo_proc.ki_rusage],
+       [
+               AC_DEFINE(HAVE_STRUCT_KINFO_PROC_FREEBSD, 1,
+                       [Define if struct kinfo_proc exists in the FreeBSD variant.])
+               have_struct_kinfo_proc_freebsd="yes"
+       ],
+       [
+               have_struct_kinfo_proc_freebsd="no"
+       ],
+       [
+#include <kvm.h>
+#include <sys/param.h>
+#include <sys/sysctl.h>
+#include <sys/user.h>
+       ])
+
+AC_CHECK_MEMBERS([struct kinfo_proc.kp_proc, struct kinfo_proc.kp_eproc],
+       [
+               AC_DEFINE(HAVE_STRUCT_KINFO_PROC_OPENBSD, 1,
+                       [Define if struct kinfo_proc exists in the OpenBSD variant.])
+               have_struct_kinfo_proc_openbsd="yes"
+       ],
+       [
+               have_struct_kinfo_proc_openbsd="no"
+       ],
+       [
+#include <sys/param.h>
+#include <sys/sysctl.h>
+#include <kvm.h>
+       ])
+
+AC_CHECK_MEMBERS([struct udphdr.uh_dport, struct udphdr.uh_sport], [], [],
+[#define _BSD_SOURCE
+#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#if HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#if HAVE_NETINET_IN_SYSTM_H
+# include <netinet/in_systm.h>
+#endif
+#if HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+#if HAVE_NETINET_IP_H
+# include <netinet/ip.h>
+#endif
+#if HAVE_NETINET_UDP_H
+# include <netinet/udp.h>
+#endif
+])
+AC_CHECK_MEMBERS([struct udphdr.dest, struct udphdr.source], [], [],
+[#define _BSD_SOURCE
+#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#if HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#if HAVE_NETINET_IN_SYSTM_H
+# include <netinet/in_systm.h>
+#endif
+#if HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+#if HAVE_NETINET_IP_H
+# include <netinet/ip.h>
+#endif
+#if HAVE_NETINET_UDP_H
+# include <netinet/udp.h>
+#endif
+])
+
+AC_CHECK_MEMBERS([kstat_io_t.nwritten, kstat_io_t.writes, kstat_io_t.nwrites, kstat_io_t.wtime],
+       [],
+       [],
+       [
+#if HAVE_KSTAT_H
+# include <kstat.h>
+#endif
+       ])
+
+#
+# Checks for libraries begin here
+#
+
+with_libresolv="yes"
+AC_CHECK_LIB(resolv, res_search,
+[
+       AC_DEFINE(HAVE_LIBRESOLV, 1, [Define to 1 if you have the 'resolv' library (-lresolv).])
+],
+[with_libresolv="no"])
+AM_CONDITIONAL(BUILD_WITH_LIBRESOLV, test "x$with_libresolv" = "xyes")
+
+dnl Check for HAL (hardware abstraction library)
+with_libhal="yes"
+AC_CHECK_LIB(hal,libhal_device_property_exists,
+            [AC_DEFINE(HAVE_LIBHAL, 1, [Define to 1 if you have 'hal' library])],
+            [with_libhal="no"])
+if test "x$with_libhal" = "xyes"; then
+       if test "x$PKG_CONFIG" != "x"; then
+               BUILD_WITH_LIBHAL_CFLAGS="`pkg-config --cflags hal`"
+               BUILD_WITH_LIBHAL_LIBS="`pkg-config --libs hal`"
+               AC_SUBST(BUILD_WITH_LIBHAL_CFLAGS)
+               AC_SUBST(BUILD_WITH_LIBHAL_LIBS)
+       fi
+fi
+
+m4_divert_once([HELP_WITH], [
+collectd additional packages:])
+
+AM_CONDITIONAL([BUILD_AIX],[test "x$x$ac_system" = "xAIX"]) 
+
+if test "x$ac_system" = "xAIX"
+then
+       with_perfstat="yes"
+       with_procinfo="yes"
+else
+       with_perfstat="no (AIX only)"
+       with_procinfo="no (AIX only)"
+fi
+
+if test "x$with_perfstat" = "xyes"
+then
+       AC_CHECK_LIB(perfstat, perfstat_reset, [with_perfstat="yes"], [with_perfstat="no (perfstat not found)"], [])
+#      AC_CHECK_HEADERS(sys/protosw.h libperfstat.h,, [with_perfstat="no (perfstat not found)"])
+fi
+if test "x$with_perfstat" = "xyes"
+then
+        AC_DEFINE(HAVE_PERFSTAT, 1, [Define to 1 if you have the 'perfstat' library (-lperfstat)])
+        # struct members pertaining to donation have been added to libperfstat somewhere between AIX5.3ML5 and AIX5.3ML9
+        AC_CHECK_MEMBER([perfstat_partition_type_t.b.donate_enabled], [], [], [[#include <libperfstat.h]])
+        if test "x$av_cv_member_perfstat_partition_type_t_b_donate_enabled" = "xyes"
+        then
+               AC_DEFINE(PERFSTAT_SUPPORTS_DONATION, 1, [Define to 1 if your version of the 'perfstat' library supports donation])
+        fi
+fi
+AM_CONDITIONAL(BUILD_WITH_PERFSTAT, test "x$with_perfstat" = "xyes")
+
+# Processes plugin under AIX.
+if test "x$with_procinfo" = "xyes"
+then
+       AC_CHECK_HEADERS(procinfo.h,, [with_procinfo="no (procinfo.h not found)"])
+fi
+if test "x$with_procinfo" = "xyes"
+then
+        AC_DEFINE(HAVE_PROCINFO_H, 1, [Define to 1 if you have the procinfo.h])
+fi
+
+if test "x$ac_system" = "xSolaris"
+then
+       with_kstat="yes"
+       with_devinfo="yes"
+else
+       with_kstat="no (Solaris only)"
+       with_devinfo="no (Solaris only)"
+fi
+
+if test "x$with_kstat" = "xyes"
+then
+       AC_CHECK_LIB(kstat, kstat_open, [with_kstat="yes"], [with_kstat="no (libkstat not found)"], [])
+fi
+if test "x$with_kstat" = "xyes"
+then
+       AC_CHECK_LIB(devinfo, di_init, [with_devinfo="yes"], [with_devinfo="no (not found)"], [])
+       AC_CHECK_HEADERS(kstat.h,, [with_kstat="no (kstat.h not found)"])
+fi
+if test "x$with_kstat" = "xyes"
+then
+       AC_DEFINE(HAVE_LIBKSTAT, 1,
+                 [Define to 1 if you have the 'kstat' library (-lkstat)])
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBKSTAT, test "x$with_kstat" = "xyes")
+AM_CONDITIONAL(BUILD_WITH_LIBDEVINFO, test "x$with_devinfo" = "xyes")
+
+with_libiokit="no"
+AC_CHECK_LIB(IOKit, IOServiceGetMatchingServices,
+[
+       with_libiokit="yes"
+], 
+[
+       with_libiokit="no"
+])
+AM_CONDITIONAL(BUILD_WITH_LIBIOKIT, test "x$with_libiokit" = "xyes")
+
+with_libkvm="no"
+AC_CHECK_LIB(kvm, kvm_getprocs, [with_kvm_getprocs="yes"], [with_kvm_getprocs="no"])
+if test "x$with_kvm_getprocs" = "xyes"
+then
+       AC_DEFINE(HAVE_LIBKVM_GETPROCS, 1,
+                 [Define to 1 if you have the 'kvm' library with the 'kvm_getprocs' symbol (-lkvm)])
+       with_libkvm="yes"
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBKVM_GETPROCS, test "x$with_kvm_getprocs" = "xyes")
+
+AC_CHECK_LIB(kvm, kvm_getswapinfo, [with_kvm_getswapinfo="yes"], [with_kvm_getswapinfo="no"])
+if test "x$with_kvm_getswapinfo" = "xyes"
+then
+       AC_DEFINE(HAVE_LIBKVM_GETSWAPINFO, 1,
+                 [Define to 1 if you have the 'kvm' library with the 'kvm_getswapinfo' symbol (-lkvm)])
+       with_libkvm="yes"
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBKVM_GETSWAPINFO, test "x$with_kvm_getswapinfo" = "xyes")
+
+AC_CHECK_LIB(kvm, kvm_nlist, [with_kvm_nlist="yes"], [with_kvm_nlist="no"])
+if test "x$with_kvm_nlist" = "xyes"
+then
+       AC_DEFINE(HAVE_LIBKVM_NLIST, 1,
+                 [Define to 1 if you have the 'kvm' library with the 'kvm_nlist' symbol (-lkvm)])
+       with_libkvm="yes"
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBKVM_NLIST, test "x$with_kvm_nlist" = "xyes")
+
+AC_CHECK_LIB(kvm, kvm_openfiles, [with_kvm_openfiles="yes"], [with_kvm_openfiles="no"])
+if test "x$with_kvm_openfiles" = "xyes"
+then
+       AC_DEFINE(HAVE_LIBKVM_NLIST, 1,
+                 [Define to 1 if you have the 'kvm' library with the 'kvm_openfiles' symbol (-lkvm)])
+       with_libkvm="yes"
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBKVM_OPENFILES, test "x$with_kvm_openfiles" = "xyes")
+
+# --with-libcredis {{{
+AC_ARG_WITH(libcredis, [AS_HELP_STRING([--with-libcredis@<:@=PREFIX@:>@], [Path to libcredis.])],
+[
+ if test "x$withval" = "xyes"
+ then
+        with_libcredis="yes"
+ else if test "x$withval" = "xno"
+ then
+        with_libcredis="no"
+ else
+        with_libcredis="yes"
+        LIBCREDIS_CPPFLAGS="$LIBCREDIS_CPPFLAGS -I$withval/include"
+        LIBCREDIS_LDFLAGS="$LIBCREDIS_LDFLAGS -L$withval/lib"
+ fi; fi
+],
+[with_libcredis="yes"])
+
+SAVE_CPPFLAGS="$CPPFLAGS"
+SAVE_LDFLAGS="$LDFLAGS"
+
+CPPFLAGS="$CPPFLAGS $LIBCREDIS_CPPFLAGS"
+LDFLAGS="$LDFLAGS $LIBCREDIS_LDFLAGS"
+
+if test "x$with_libcredis" = "xyes"
+then
+       if test "x$LIBCREDIS_CPPFLAGS" != "x"
+       then
+               AC_MSG_NOTICE([libcredis CPPFLAGS: $LIBCREDIS_CPPFLAGS])
+       fi
+       AC_CHECK_HEADERS(credis.h,
+       [with_libcredis="yes"],
+       [with_libcredis="no (credis.h not found)"])
+fi
+if test "x$with_libcredis" = "xyes"
+then
+       if test "x$LIBCREDIS_LDFLAGS" != "x"
+       then
+               AC_MSG_NOTICE([libcredis LDFLAGS: $LIBCREDIS_LDFLAGS])
+       fi
+       AC_CHECK_LIB(credis, credis_info,
+       [with_libcredis="yes"],
+       [with_libcredis="no (symbol 'credis_info' not found)"])
+
+fi
+
+CPPFLAGS="$SAVE_CPPFLAGS"
+LDFLAGS="$SAVE_LDFLAGS"
+
+if test "x$with_libcredis" = "xyes"
+then
+       BUILD_WITH_LIBCREDIS_CPPFLAGS="$LIBCREDIS_CPPFLAGS"
+       BUILD_WITH_LIBCREDIS_LDFLAGS="$LIBCREDIS_LDFLAGS"
+       AC_SUBST(BUILD_WITH_LIBCREDIS_CPPFLAGS)
+       AC_SUBST(BUILD_WITH_LIBCREDIS_LDFLAGS)
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBCREDIS, test "x$with_libcredis" = "xyes")
+# }}}
+
+# --with-libcurl {{{
+with_curl_config="curl-config"
+with_curl_cflags=""
+with_curl_libs=""
+AC_ARG_WITH(libcurl, [AS_HELP_STRING([--with-libcurl@<:@=PREFIX@:>@], [Path to libcurl.])],
+[
+       if test "x$withval" = "xno"
+       then
+               with_libcurl="no"
+       else if test "x$withval" = "xyes"
+       then
+               with_libcurl="yes"
+       else
+               if test -f "$withval" && test -x "$withval"
+               then
+                       with_curl_config="$withval"
+                       with_libcurl="yes"
+               else if test -x "$withval/bin/curl-config"
+               then
+                       with_curl_config="$withval/bin/curl-config"
+                       with_libcurl="yes"
+               fi; fi
+               with_libcurl="yes"
+       fi; fi
+],
+[
+       with_libcurl="yes"
+])
+if test "x$with_libcurl" = "xyes"
+then
+       with_curl_cflags=`$with_curl_config --cflags 2>/dev/null`
+       curl_config_status=$?
+
+       if test $curl_config_status -ne 0
+       then
+               with_libcurl="no ($with_curl_config failed)"
+       else
+               SAVE_CPPFLAGS="$CPPFLAGS"
+               CPPFLAGS="$CPPFLAGS $with_curl_cflags"
+
+               AC_CHECK_HEADERS(curl/curl.h, [], [with_libcurl="no (curl/curl.h not found)"], [])
+
+               CPPFLAGS="$SAVE_CPPFLAGS"
+       fi
+fi
+if test "x$with_libcurl" = "xyes"
+then
+       with_curl_libs=`$with_curl_config --libs 2>/dev/null`
+       curl_config_status=$?
+
+       if test $curl_config_status -ne 0
+       then
+               with_libcurl="no ($with_curl_config failed)"
+       else
+               AC_CHECK_LIB(curl, curl_easy_init,
+                [with_libcurl="yes"],
+                [with_libcurl="no (symbol 'curl_easy_init' not found)"],
+                [$with_curl_libs])
+       fi
+fi
+if test "x$with_libcurl" = "xyes"
+then
+       BUILD_WITH_LIBCURL_CFLAGS="$with_curl_cflags"
+       BUILD_WITH_LIBCURL_LIBS="$with_curl_libs"
+       AC_SUBST(BUILD_WITH_LIBCURL_CFLAGS)
+       AC_SUBST(BUILD_WITH_LIBCURL_LIBS)
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBCURL, test "x$with_libcurl" = "xyes")
+# }}}
+
+# --with-libdbi {{{
+with_libdbi_cppflags=""
+with_libdbi_ldflags=""
+AC_ARG_WITH(libdbi, [AS_HELP_STRING([--with-libdbi@<:@=PREFIX@:>@], [Path to libdbi.])],
+[
+       if test "x$withval" != "xno" && test "x$withval" != "xyes"
+       then
+               with_libdbi_cppflags="-I$withval/include"
+               with_libdbi_ldflags="-L$withval/lib"
+               with_libdbi="yes"
+       else
+               with_libdbi="$withval"
+       fi
+],
+[
+       with_libdbi="yes"
+])
+if test "x$with_libdbi" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libdbi_cppflags"
+
+       AC_CHECK_HEADERS(dbi/dbi.h, [with_libdbi="yes"], [with_libdbi="no (dbi/dbi.h not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+if test "x$with_libdbi" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       SAVE_LDFLAGS="$LDFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libdbi_cppflags"
+       LDFLAGS="$LDFLAGS $with_libdbi_ldflags"
+
+       AC_CHECK_LIB(dbi, dbi_initialize, [with_libdbi="yes"], [with_libdbi="no (Symbol 'dbi_initialize' not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+       LDFLAGS="$SAVE_LDFLAGS"
+fi
+if test "x$with_libdbi" = "xyes"
+then
+       BUILD_WITH_LIBDBI_CPPFLAGS="$with_libdbi_cppflags"
+       BUILD_WITH_LIBDBI_LDFLAGS="$with_libdbi_ldflags"
+       BUILD_WITH_LIBDBI_LIBS="-ldbi"
+       AC_SUBST(BUILD_WITH_LIBDBI_CPPFLAGS)
+       AC_SUBST(BUILD_WITH_LIBDBI_LDFLAGS)
+       AC_SUBST(BUILD_WITH_LIBDBI_LIBS)
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBDBI, test "x$with_libdbi" = "xyes")
+# }}}
+
+# --with-libesmtp {{{
+AC_ARG_WITH(libesmtp, [AS_HELP_STRING([--with-libesmtp@<:@=PREFIX@:>@], [Path to libesmtp.])],
+[
+       if test "x$withval" != "xno" && test "x$withval" != "xyes"
+       then
+               LDFLAGS="$LDFLAGS -L$withval/lib"
+               CPPFLAGS="$CPPFLAGS -I$withval/include -D_THREAD_SAFE"
+               with_libesmtp="yes"
+       else
+               with_libesmtp="$withval"
+       fi
+],
+[
+       with_libesmtp="yes"
+])
+if test "x$with_libesmtp" = "xyes"
+then
+       AC_CHECK_LIB(esmtp, smtp_create_session,
+       [
+               AC_DEFINE(HAVE_LIBESMTP, 1, [Define to 1 if you have the esmtp library (-lesmtp).])
+       ], [with_libesmtp="no (libesmtp not found)"])
+fi
+if test "x$with_libesmtp" = "xyes"
+then
+       AC_CHECK_HEADERS(libesmtp.h,
+       [
+               AC_DEFINE(HAVE_LIBESMTP_H, 1, [Define to 1 if you have the <libesmtp.h> header file.])
+       ], [with_libesmtp="no (libesmtp.h not found)"])
+fi
+if test "x$with_libesmtp" = "xyes"
+then
+       collect_libesmtp=1
+else
+       collect_libesmtp=0
+fi
+AC_DEFINE_UNQUOTED(COLLECT_LIBESMTP, [$collect_libesmtp],
+       [Wether or not to use the esmtp library])
+AM_CONDITIONAL(BUILD_WITH_LIBESMTP, test "x$with_libesmtp" = "xyes")
+# }}}
+
+# --with-libganglia {{{
+AC_ARG_WITH(libganglia, [AS_HELP_STRING([--with-libganglia@<:@=PREFIX@:>@], [Path to libganglia.])],
+[
+ if test -f "$withval" && test -x "$withval"
+ then
+        with_libganglia_config="$withval"
+        with_libganglia="yes"
+ else if test -f "$withval/bin/ganglia-config" && test -x "$withval/bin/ganglia-config"
+ then
+        with_libganglia_config="$withval/bin/ganglia-config"
+        with_libganglia="yes"
+ else if test -d "$withval"
+ then
+        GANGLIA_CPPFLAGS="-I$withval/include"
+        GANGLIA_LDFLAGS="-L$withval/lib"
+        with_libganglia="yes"
+ else
+        with_libganglia_config="ganglia-config"
+        with_libganglia="$withval"
+ fi; fi; fi
+],
+[
+ with_libganglia_config="ganglia-config"
+ with_libganglia="yes"
+])
+
+if test "x$with_libganglia" = "xyes" && test "x$with_libganglia_config" != "x"
+then
+       if test "x$GANGLIA_CPPFLAGS" = "x"
+       then
+               GANGLIA_CPPFLAGS=`"$with_libganglia_config" --cflags 2>/dev/null`
+       fi
+
+       if test "x$GANGLIA_LDFLAGS" = "x"
+       then
+               GANGLIA_LDFLAGS=`"$with_libganglia_config" --ldflags 2>/dev/null`
+       fi
+
+       if test "x$GANGLIA_LIBS" = "x"
+       then
+               GANGLIA_LIBS=`"$with_libganglia_config" --libs 2>/dev/null`
+       fi
+fi
+
+SAVE_CPPFLAGS="$CPPFLAGS"
+SAVE_LDFLAGS="$LDFLAGS"
+CPPFLAGS="$CPPFLAGS $GANGLIA_CPPFLAGS"
+LDFLAGS="$LDFLAGS $GANGLIA_LDFLAGS"
+
+if test "x$with_libganglia" = "xyes"
+then
+       AC_CHECK_HEADERS(gm_protocol.h,
+       [
+               AC_DEFINE(HAVE_GM_PROTOCOL_H, 1,
+                         [Define to 1 if you have the <gm_protocol.h> header file.])
+       ], [with_libganglia="no (gm_protocol.h not found)"])
+fi
+
+if test "x$with_libganglia" = "xyes"
+then
+       AC_CHECK_LIB(ganglia, xdr_Ganglia_value_msg,
+       [
+               AC_DEFINE(HAVE_LIBGANGLIA, 1,
+                         [Define to 1 if you have the ganglia library (-lganglia).])
+       ], [with_libganglia="no (symbol xdr_Ganglia_value_msg not found)"])
+fi
+
+CPPFLAGS="$SAVE_CPPFLAGS"
+LDFLAGS="$SAVE_LDFLAGS"
+
+AC_SUBST(GANGLIA_CPPFLAGS)
+AC_SUBST(GANGLIA_LDFLAGS)
+AC_SUBST(GANGLIA_LIBS)
+AM_CONDITIONAL(BUILD_WITH_LIBGANGLIA, test "x$with_libganglia" = "xyes")
+# }}}
+
+# --with-libgcrypt {{{
+GCRYPT_CPPFLAGS="$GCRYPT_CPPFLAGS"
+GCRYPT_LDFLAGS="$GCRYPT_LDFLAGS"
+GCRYPT_LIBS="$GCRYPT_LIBS"
+AC_ARG_WITH(libgcrypt, [AS_HELP_STRING([--with-libgcrypt@<:@=PREFIX@:>@], [Path to libgcrypt.])],
+[
+ if test -f "$withval" && test -x "$withval"
+ then
+        with_libgcrypt_config="$withval"
+        with_libgcrypt="yes"
+ else if test -f "$withval/bin/gcrypt-config" && test -x "$withval/bin/gcrypt-config"
+ then
+        with_libgcrypt_config="$withval/bin/gcrypt-config"
+        with_libgcrypt="yes"
+ else if test -d "$withval"
+ then
+        GCRYPT_CPPFLAGS="$GCRYPT_CPPFLAGS -I$withval/include"
+        GCRYPT_LDFLAGS="$GCRYPT_LDFLAGS -L$withval/lib"
+        with_libgcrypt="yes"
+ else
+        with_libgcrypt_config="gcrypt-config"
+        with_libgcrypt="$withval"
+ fi; fi; fi
+],
+[
+ with_libgcrypt_config="libgcrypt-config"
+ with_libgcrypt="yes"
+])
+
+if test "x$with_libgcrypt" = "xyes" && test "x$with_libgcrypt_config" != "x"
+then
+       if test "x$GCRYPT_CPPFLAGS" = "x"
+       then
+               GCRYPT_CPPFLAGS=`"$with_libgcrypt_config" --cflags 2>/dev/null`
+       fi
+
+       if test "x$GCRYPT_LDFLAGS" = "x"
+       then
+               gcrypt_exec_prefix=`"$with_libgcrypt_config" --exec-prefix 2>/dev/null`
+               GCRYPT_LDFLAGS="-L$gcrypt_exec_prefix/lib"
+       fi
+
+       if test "x$GCRYPT_LIBS" = "x"
+       then
+               GCRYPT_LIBS=`"$with_libgcrypt_config" --libs 2>/dev/null`
+       fi
+fi
+
+SAVE_CPPFLAGS="$CPPFLAGS"
+SAVE_LDFLAGS="$LDFLAGS"
+CPPFLAGS="$CPPFLAGS $GCRYPT_CPPFLAGS"
+LDFLAGS="$LDFLAGS $GCRYPT_LDFLAGS"
+
+if test "x$with_libgcrypt" = "xyes"
+then
+       if test "x$GCRYPT_CPPFLAGS" != "x"
+       then
+               AC_MSG_NOTICE([gcrypt CPPFLAGS: $GCRYPT_CPPFLAGS])
+       fi
+       AC_CHECK_HEADERS(gcrypt.h,
+               [with_libgcrypt="yes"],
+               [with_libgcrypt="no (gcrypt.h not found)"])
+fi
+
+if test "x$with_libgcrypt" = "xyes"
+then
+       if test "x$GCRYPT_LDFLAGS" != "x"
+       then
+               AC_MSG_NOTICE([gcrypt LDFLAGS: $GCRYPT_LDFLAGS])
+       fi
+       AC_CHECK_LIB(gcrypt, gcry_md_hash_buffer,
+               [with_libgcrypt="yes"],
+               [with_libgcrypt="no (symbol gcry_md_hash_buffer not found)"])
+
+       if test "$with_libgcrypt" != "no"; then
+               AM_PATH_LIBGCRYPT(1:1.2.0,,with_libgcrypt="no (version 1.2.0+ required)")
+       fi
+fi
+
+CPPFLAGS="$SAVE_CPPFLAGS"
+LDFLAGS="$SAVE_LDFLAGS"
+
+if test "x$with_libgcrypt" = "xyes"
+then
+       AC_DEFINE(HAVE_LIBGCRYPT, 1, [Define to 1 if you have the gcrypt library (-lgcrypt).])
+fi
+
+AC_SUBST(GCRYPT_CPPFLAGS)
+AC_SUBST(GCRYPT_LDFLAGS)
+AC_SUBST(GCRYPT_LIBS)
+AM_CONDITIONAL(BUILD_WITH_LIBGCRYPT, test "x$with_libgcrypt" = "xyes")
+# }}}
+
+# --with-libiptc {{{
+AC_ARG_WITH(libiptc, [AS_HELP_STRING([--with-libiptc@<:@=PREFIX@:>@], [Path to libiptc.])],
+[
+       if test "x$withval" != "xno" && test "x$withval" != "xyes"
+       then
+               LIBIPTC_CPPFLAGS="$LIBIPTC_CPPFLAGS -I$withval/include"
+               LIBIPTC_LDFLAGS="$LIBIPTC_LDFLAGS -L$withval/lib"
+               with_libiptc="yes"
+       else
+               with_libiptc="$withval"
+       fi
+],
+[
+       if test "x$ac_system" = "xLinux"
+       then
+               with_libiptc="yes"
+       else
+               with_libiptc="no (Linux only)"
+       fi
+])
+SAVE_CPPFLAGS="$CPPFLAGS"
+SAVE_LDFLAGS="$LDFLAGS"
+CPPFLAGS="$CPPFLAGS $LIBIPTC_CPPFLAGS"
+LDFLAGS="$LDFLAGS $LIBIPTC_LDFLAGS"
+# check whether the header file for libiptc is available.
+if test "x$with_libiptc" = "xyes"
+then
+       AC_CHECK_HEADERS(libiptc/libiptc.h,
+                        [with_libiptc="yes"],
+                        [with_libiptc="no (libiptc/libiptc.h not found)"])
+fi
+if test "x$with_libiptc" = "xyes"
+then
+       AC_CHECK_HEADERS(libiptc/libip6tc.h,
+                        [with_libiptc="yes"],
+                        [with_libiptc="no (libiptc/libip6tc.h not found)"])
+fi
+# If the header file is available, check for the required type declaractions.
+# They may be missing in old versions of libiptc. In that case, they will be
+# declared in the iptables plugin.
+if test "x$with_libiptc" = "xyes"
+then
+       AC_CHECK_TYPES([iptc_handle_t, ip6tc_handle_t], [], [],
+       [
+#include <libiptc/libiptc.h>
+#include <libiptc/libip6tc.h>
+       ])
+fi
+# Check for the iptc_init symbol in the library.
+if test "x$with_libiptc" = "xyes"
+then
+       AC_CHECK_LIB(iptc, iptc_init,
+                    [with_libiptc="yes"],
+                    [with_libiptc="no (symbol 'iptc_init' not found)"])
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBIPTC, test "x$with_libiptc" = "xyes")
+if test "x$with_libiptc" = "xyes"
+then
+       BUILD_WITH_LIBIPTC_CPPFLAGS="$LIBIPTC_CPPFLAGS"
+       BUILD_WITH_LIBIPTC_LDFLAGS="$LIBIPTC_LDFLAGS"
+       AC_SUBST(BUILD_WITH_LIBIPTC_CPPFLAGS)
+       AC_SUBST(BUILD_WITH_LIBIPTC_LDFLAGS)
+fi
+CPPFLAGS="$SAVE_CPPFLAGS"
+LDFLAGS="$SAVE_LDFLAGS"
+# }}}
+
+# --with-java {{{
+with_java_home="$JAVA_HOME"
+with_java_vmtype="client"
+with_java_cflags=""
+with_java_libs=""
+JAVAC="$JAVAC"
+JAR="$JAR"
+AC_ARG_WITH(java, [AS_HELP_STRING([--with-java@<:@=PREFIX@:>@], [Path to Java home.])],
+[
+       if test "x$withval" = "xno"
+       then
+               with_java="no"
+       else if test "x$withval" = "xyes"
+       then
+               with_java="yes"
+       else
+               with_java_home="$withval"
+               with_java="yes"
+       fi; fi
+],
+[with_java="yes"])
+if test "x$with_java" = "xyes"
+then
+       if test -d "$with_java_home"
+       then
+               AC_MSG_CHECKING([for jni.h])
+               TMPDIR=`find "$with_java_home" -name jni.h -type f -exec 'dirname' '{}' ';' | head -n 1`
+               if test "x$TMPDIR" != "x"
+               then
+                       AC_MSG_RESULT([found in $TMPDIR])
+                       JAVA_CPPFLAGS="$JAVA_CPPFLAGS -I$TMPDIR"
+               else
+                       AC_MSG_RESULT([not found])
+               fi
+
+               AC_MSG_CHECKING([for jni_md.h])
+               TMPDIR=`find "$with_java_home" -name jni_md.h -type f -exec 'dirname' '{}' ';' | head -n 1`
+               if test "x$TMPDIR" != "x"
+               then
+                       AC_MSG_RESULT([found in $TMPDIR])
+                       JAVA_CPPFLAGS="$JAVA_CPPFLAGS -I$TMPDIR"
+               else
+                       AC_MSG_RESULT([not found])
+               fi
+
+               AC_MSG_CHECKING([for libjvm.so])
+               TMPDIR=`find "$with_java_home" -name libjvm.so -type f -exec 'dirname' '{}' ';' | head -n 1`
+               if test "x$TMPDIR" != "x"
+               then
+                       AC_MSG_RESULT([found in $TMPDIR])
+                       JAVA_LDFLAGS="$JAVA_LDFLAGS -L$TMPDIR -Wl,-rpath -Wl,$TMPDIR"
+               else
+                       AC_MSG_RESULT([not found])
+               fi
+
+               if test "x$JAVAC" = "x"
+               then
+                       AC_MSG_CHECKING([for javac])
+                       TMPDIR=`find "$with_java_home" -name javac -type f | head -n 1`
+                       if test "x$TMPDIR" != "x"
+                       then
+                               JAVAC="$TMPDIR"
+                               AC_MSG_RESULT([$JAVAC])
+                       else
+                               AC_MSG_RESULT([not found])
+                       fi
+               fi
+               if test "x$JAR" = "x"
+               then
+                       AC_MSG_CHECKING([for jar])
+                       TMPDIR=`find "$with_java_home" -name jar -type f | head -n 1`
+                       if test "x$TMPDIR" != "x"
+                       then
+                               JAR="$TMPDIR"
+                               AC_MSG_RESULT([$JAR])
+                       else
+                               AC_MSG_RESULT([not found])
+                       fi
+               fi
+       else if test "x$with_java_home" != "x"
+       then
+               AC_MSG_WARN([JAVA_HOME: No such directory: $with_java_home])
+       fi; fi
+fi
+
+if test "x$JAVA_CPPFLAGS" != "x"
+then
+       AC_MSG_NOTICE([Building with JAVA_CPPFLAGS set to: $JAVA_CPPFLAGS])
+fi
+if test "x$JAVA_CFLAGS" != "x"
+then
+       AC_MSG_NOTICE([Building with JAVA_CFLAGS set to: $JAVA_CFLAGS])
+fi
+if test "x$JAVA_LDFLAGS" != "x"
+then
+       AC_MSG_NOTICE([Building with JAVA_LDFLAGS set to: $JAVA_LDFLAGS])
+fi
+if test "x$JAVAC" = "x"
+then
+       with_javac_path="$PATH"
+       if test "x$with_java_home" != "x"
+       then
+               with_javac_path="$with_java_home:with_javac_path"
+               if test -d "$with_java_home/bin"
+               then
+                       with_javac_path="$with_java_home/bin:with_javac_path"
+               fi
+       fi
+
+       AC_PATH_PROG(JAVAC, javac, [], "$with_javac_path")
+fi
+if test "x$JAVAC" = "x"
+then
+       with_java="no (javac not found)"
+fi
+if test "x$JAR" = "x"
+then
+       with_jar_path="$PATH"
+       if test "x$with_java_home" != "x"
+       then
+               with_jar_path="$with_java_home:$with_jar_path"
+               if test -d "$with_java_home/bin"
+               then
+                       with_jar_path="$with_java_home/bin:$with_jar_path"
+               fi
+       fi
+
+       AC_PATH_PROG(JAR, jar, [], "$with_jar_path")
+fi
+if test "x$JAR" = "x"
+then
+       with_java="no (jar not found)"
+fi
+
+SAVE_CPPFLAGS="$CPPFLAGS"
+SAVE_CFLAGS="$CFLAGS"
+SAVE_LDFLAGS="$LDFLAGS"
+CPPFLAGS="$CPPFLAGS $JAVA_CPPFLAGS"
+CFLAGS="$CFLAGS $JAVA_CFLAGS"
+LDFLAGS="$LDFLAGS $JAVA_LDFLAGS"
+
+if test "x$with_java" = "xyes"
+then
+       AC_CHECK_HEADERS(jni.h, [], [with_java="no (jni.h not found)"])
+fi
+if test "x$with_java" = "xyes"
+then
+       AC_CHECK_LIB(jvm, JNI_CreateJavaVM,
+       [with_java="yes"],
+       [with_java="no (libjvm not found)"],
+       [$JAVA_LIBS])
+fi
+if test "x$with_java" = "xyes"
+then
+       JAVA_LIBS="$JAVA_LIBS -ljvm"
+       AC_MSG_NOTICE([Building with JAVA_LIBS set to: $JAVA_LIBS])
+fi
+
+CPPFLAGS="$SAVE_CPPFLAGS"
+CFLAGS="$SAVE_CFLAGS"
+LDFLAGS="$SAVE_LDFLAGS"
+
+AC_SUBST(JAVA_CPPFLAGS)
+AC_SUBST(JAVA_CFLAGS)
+AC_SUBST(JAVA_LDFLAGS)
+AC_SUBST(JAVA_LIBS)
+AM_CONDITIONAL(BUILD_WITH_JAVA, test "x$with_java" = "xyes")
+# }}}
+
+# --with-libmemcached {{{
+with_libmemcached_cppflags=""
+with_libmemcached_ldflags=""
+AC_ARG_WITH(libmemcached, [AS_HELP_STRING([--with-libmemcached@<:@=PREFIX@:>@], [Path to libmemcached.])],
+[
+       if test "x$withval" != "xno" && test "x$withval" != "xyes"
+       then
+               with_libmemcached_cppflags="-I$withval/include"
+               with_libmemcached_ldflags="-L$withval/lib"
+               with_libmemcached="yes"
+       else
+               with_libmemcached="$withval"
+       fi
+],
+[
+       with_libmemcached="yes"
+])
+if test "x$with_libmemcached" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libmemcached_cppflags"
+
+       AC_CHECK_HEADERS(libmemcached/memcached.h, [with_libmemcached="yes"], [with_libmemcached="no (libmemcached/memcached.h not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+if test "x$with_libmemcached" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       SAVE_LDFLAGS="$LDFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libmemcached_cppflags"
+       LDFLAGS="$LDFLAGS $with_libmemcached_ldflags"
+
+       AC_CHECK_LIB(memcached, memcached_create, [with_libmemcached="yes"], [with_libmemcached="no (Symbol 'memcached_create' not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+       LDFLAGS="$SAVE_LDFLAGS"
+fi
+if test "x$with_libmemcached" = "xyes"
+then
+       BUILD_WITH_LIBMEMCACHED_CPPFLAGS="$with_libmemcached_cppflags"
+       BUILD_WITH_LIBMEMCACHED_LDFLAGS="$with_libmemcached_ldflags"
+       BUILD_WITH_LIBMEMCACHED_LIBS="-lmemcached"
+       AC_SUBST(BUILD_WITH_LIBMEMCACHED_CPPFLAGS)
+       AC_SUBST(BUILD_WITH_LIBMEMCACHED_LDFLAGS)
+       AC_SUBST(BUILD_WITH_LIBMEMCACHED_LIBS)
+       AC_DEFINE(HAVE_LIBMEMCACHED, 1, [Define if libmemcached is present and usable.])
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBMEMCACHED, test "x$with_libmemcached" = "xyes")
+# }}}
+
+# --with-libmodbus {{{
+with_libmodbus_config=""
+with_libmodbus_cflags=""
+with_libmodbus_libs=""
+AC_ARG_WITH(libmodbus, [AS_HELP_STRING([--with-libmodbus@<:@=PREFIX@:>@], [Path to the modbus library.])],
+[
+       if test "x$withval" = "xno"
+       then
+               with_libmodbus="no"
+       else if test "x$withval" = "xyes"
+       then
+               with_libmodbus="use_pkgconfig"
+       else if test -d "$with_libmodbus/lib"
+       then
+               AC_MSG_NOTICE([Not checking for libmodbus: Manually configured])
+               with_libmodbus_cflags="-I$withval/include"
+               with_libmodbus_libs="-L$withval/lib -lmodbus"
+               with_libmodbus="yes"
+       fi; fi; fi
+],
+[with_libmodbus="use_pkgconfig"])
+
+# configure using pkg-config
+if test "x$with_libmodbus" = "xuse_pkgconfig"
+then
+       if test "x$PKG_CONFIG" = "x"
+       then
+               with_libmodbus="no (Don't have pkg-config)"
+       fi
+fi
+if test "x$with_libmodbus" = "xuse_pkgconfig"
+then
+       AC_MSG_NOTICE([Checking for libmodbus using $PKG_CONFIG])
+       $PKG_CONFIG --exists 'libmodbus' 2>/dev/null
+       if test $? -ne 0
+       then
+               with_libmodbus="no (pkg-config doesn't know libmodbus)"
+       fi
+fi
+if test "x$with_libmodbus" = "xuse_pkgconfig"
+then
+       with_libmodbus_cflags="`$PKG_CONFIG --cflags 'libmodbus'`"
+       if test $? -ne 0
+       then
+               with_libmodbus="no ($PKG_CONFIG failed)"
+       fi
+       with_libmodbus_libs="`$PKG_CONFIG --libs 'libmodbus'`"
+       if test $? -ne 0
+       then
+               with_libmodbus="no ($PKG_CONFIG failed)"
+       fi
+fi
+if test "x$with_libmodbus" = "xuse_pkgconfig"
+then
+       with_libmodbus="yes"
+fi
+
+# with_libmodbus_cflags and with_libmodbus_libs are set up now, let's do
+# the actual checks.
+if test "x$with_libmodbus" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libmodbus_cflags"
+
+       AC_CHECK_HEADERS(modbus/modbus.h, [], [with_libmodbus="no (modbus/modbus.h not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+if test "x$with_libmodbus" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       SAVE_LDFLAGS="$LDFLAGS"
+
+       CPPFLAGS="$CPPFLAGS $with_libmodbus_cflags"
+       LDFLAGS="$LDFLAGS $with_libmodbus_libs"
+
+       AC_CHECK_LIB(modbus, modbus_connect,
+                    [with_libmodbus="yes"],
+                    [with_libmodbus="no (symbol modbus_connect not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+       LDFLAGS="$SAVE_LDFLAGS"
+fi
+if test "x$with_libmodbus" = "xyes"
+then
+       BUILD_WITH_LIBMODBUS_CFLAGS="$with_libmodbus_cflags"
+       BUILD_WITH_LIBMODBUS_LIBS="$with_libmodbus_libs"
+       AC_SUBST(BUILD_WITH_LIBMODBUS_CFLAGS)
+       AC_SUBST(BUILD_WITH_LIBMODBUS_LIBS)
+fi
+# }}}
+
+# --with-libmongoc {{{
+AC_ARG_WITH(libmongoc, [AS_HELP_STRING([--with-libmongoc@<:@=PREFIX@:>@], [Path to libmongoc.])],
+[
+ if test "x$withval" = "xyes"
+ then
+        with_libmongoc="yes"
+ else if test "x$withval" = "xno"
+ then
+        with_libmongoc="no"
+ else
+        with_libmongoc="yes"
+        LIBMONGOC_CPPFLAGS="$LIBMONGOC_CPPFLAGS -I$withval/include"
+        LIBMONGOC_LDFLAGS="$LIBMONGOC_LDFLAGS -L$withval/lib"
+ fi; fi
+],
+[with_libmongoc="yes"])
+
+SAVE_CPPFLAGS="$CPPFLAGS"
+SAVE_LDFLAGS="$LDFLAGS"
+
+CPPFLAGS="$CPPFLAGS $LIBMONGOC_CPPFLAGS"
+LDFLAGS="$LDFLAGS $LIBMONGOC_LDFLAGS"
+
+if test "x$with_libmongoc" = "xyes"
+then
+       if test "x$LIBMONGOC_CPPFLAGS" != "x"
+       then
+               AC_MSG_NOTICE([libmongoc CPPFLAGS: $LIBMONGOC_CPPFLAGS])
+       fi
+       AC_CHECK_HEADERS(mongo.h,
+       [with_libmongoc="yes"],
+       [with_libmongoc="no ('mongo.h' not found)"],
+[#if HAVE_STDINT_H
+# define MONGO_HAVE_STDINT 1
+#else
+# define MONGO_USE_LONG_LONG_INT 1
+#endif
+])
+fi
+if test "x$with_libmongoc" = "xyes"
+then
+       if test "x$LIBMONGOC_LDFLAGS" != "x"
+       then
+               AC_MSG_NOTICE([libmongoc LDFLAGS: $LIBMONGOC_LDFLAGS])
+       fi
+       AC_CHECK_LIB(mongoc, mongo_run_command,
+       [with_libmongoc="yes"],
+       [with_libmongoc="no (symbol 'mongo_run_command' not found)"])
+fi
+
+CPPFLAGS="$SAVE_CPPFLAGS"
+LDFLAGS="$SAVE_LDFLAGS"
+
+if test "x$with_libmongoc" = "xyes"
+then
+       BUILD_WITH_LIBMONGOC_CPPFLAGS="$LIBMONGOC_CPPFLAGS"
+       BUILD_WITH_LIBMONGOC_LDFLAGS="$LIBMONGOC_LDFLAGS"
+       AC_SUBST(BUILD_WITH_LIBMONGOC_CPPFLAGS)
+       AC_SUBST(BUILD_WITH_LIBMONGOC_LDFLAGS)
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBMONGOC, test "x$with_libmongoc" = "xyes")
+# }}}
+
+# --with-libmysql {{{
+with_mysql_config="mysql_config"
+with_mysql_cflags=""
+with_mysql_libs=""
+AC_ARG_WITH(libmysql, [AS_HELP_STRING([--with-libmysql@<:@=PREFIX@:>@], [Path to libmysql.])],
+[
+       if test "x$withval" = "xno"
+       then
+               with_libmysql="no"
+       else if test "x$withval" = "xyes"
+       then
+               with_libmysql="yes"
+       else
+               if test -f "$withval" && test -x "$withval";
+               then
+                       with_mysql_config="$withval"
+               else if test -x "$withval/bin/mysql_config"
+               then
+                       with_mysql_config="$withval/bin/mysql_config"
+               fi; fi
+               with_libmysql="yes"
+       fi; fi
+],
+[
+       with_libmysql="yes"
+])
+if test "x$with_libmysql" = "xyes"
+then
+       with_mysql_cflags=`$with_mysql_config --cflags 2>/dev/null`
+       mysql_config_status=$?
+
+       if test $mysql_config_status -ne 0
+       then
+               with_libmysql="no ($with_mysql_config failed)"
+       else
+               SAVE_CPPFLAGS="$CPPFLAGS"
+               CPPFLAGS="$CPPFLAGS $with_mysql_cflags"
+
+               have_mysql_h="no"
+               have_mysql_mysql_h="no"
+               AC_CHECK_HEADERS(mysql.h, [have_mysql_h="yes"])
+
+               if test "x$have_mysql_h" = "xno"
+               then
+                       AC_CHECK_HEADERS(mysql/mysql.h, [have_mysql_mysql_h="yes"])
+               fi
+
+               if test "x$have_mysql_h$have_mysql_mysql_h" = "xnono"
+               then
+                       with_libmysql="no (mysql.h not found)"
+               fi
+
+               CPPFLAGS="$SAVE_CPPFLAGS"
+       fi
+fi
+if test "x$with_libmysql" = "xyes"
+then
+       with_mysql_libs=`$with_mysql_config --libs 2>/dev/null`
+       mysql_config_status=$?
+
+       if test $mysql_config_status -ne 0
+       then
+               with_libmysql="no ($with_mysql_config failed)"
+       else
+               AC_CHECK_LIB(mysqlclient, mysql_init,
+                [with_libmysql="yes"],
+                [with_libmysql="no (symbol 'mysql_init' not found)"],
+                [$with_mysql_libs])
+
+               AC_CHECK_LIB(mysqlclient, mysql_get_server_version,
+                [with_libmysql="yes"],
+                [with_libmysql="no (symbol 'mysql_get_server_version' not found)"],
+                [$with_mysql_libs])
+       fi
+fi
+if test "x$with_libmysql" = "xyes"
+then
+       BUILD_WITH_LIBMYSQL_CFLAGS="$with_mysql_cflags"
+       BUILD_WITH_LIBMYSQL_LIBS="$with_mysql_libs"
+       AC_SUBST(BUILD_WITH_LIBMYSQL_CFLAGS)
+       AC_SUBST(BUILD_WITH_LIBMYSQL_LIBS)
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBMYSQL, test "x$with_libmysql" = "xyes")
+# }}}
+
+# --with-libnetlink {{{
+with_libnetlink_cflags=""
+with_libnetlink_libs="-lnetlink"
+AC_ARG_WITH(libnetlink, [AS_HELP_STRING([--with-libnetlink@<:@=PREFIX@:>@], [Path to libnetlink.])],
+[
+ echo "libnetlink: withval = $withval"
+ if test "x$withval" = "xyes"
+ then
+        with_libnetlink="yes"
+ else if test "x$withval" = "xno"
+ then
+        with_libnetlink="no"
+ else
+        if test -d "$withval/include"
+        then
+                with_libnetlink_cflags="-I$withval/include"
+                with_libnetlink_libs="-L$withval/lib -lnetlink"
+                with_libnetlink="yes"
+        else
+                AC_MSG_ERROR("no such directory: $withval/include")
+        fi
+ fi; fi
+],
+[
+ if test "x$ac_system" = "xLinux"
+ then
+        with_libnetlink="yes"
+ else
+        with_libnetlink="no (Linux only library)"
+ fi
+])
+if test "x$with_libnetlink" = "xyes"
+then
+       SAVE_CFLAGS="$CFLAGS"
+       CFLAGS="$CFLAGS $with_libnetlink_cflags"
+
+       with_libnetlink="no (libnetlink.h not found)"
+
+       AC_CHECK_HEADERS(libnetlink.h iproute/libnetlink.h linux/libnetlink.h,
+       [
+        with_libnetlink="yes"
+        break
+       ], [],
+[#include <stdio.h>
+#include <sys/types.h>
+#include <asm/types.h>
+#include <sys/socket.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>])
+       AC_CHECK_HEADERS(linux/gen_stats.h linux/pkt_sched.h, [], [],
+[#include <stdio.h>
+#include <sys/types.h>
+#include <asm/types.h>
+#include <sys/socket.h>])
+
+       AC_COMPILE_IFELSE(
+[#include <stdio.h>
+#include <sys/types.h>
+#include <asm/types.h>
+#include <sys/socket.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+int main (void)
+{
+       int retval = TCA_STATS2;
+       return (retval);
+}],
+       [AC_DEFINE([HAVE_TCA_STATS2], 1, [True if the enum-member TCA_STATS2 exists])]
+       []);
+
+       AC_COMPILE_IFELSE(
+[#include <stdio.h>
+#include <sys/types.h>
+#include <asm/types.h>
+#include <sys/socket.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+int main (void)
+{
+       int retval = TCA_STATS;
+       return (retval);
+}],
+       [AC_DEFINE([HAVE_TCA_STATS], 1, [True if the enum-member TCA_STATS exists])]
+       []);
+
+       CFLAGS="$SAVE_CFLAGS"
+fi
+if test "x$with_libnetlink" = "xyes"
+then
+       AC_CHECK_LIB(netlink, rtnl_open,
+                    [with_libnetlink="yes"],
+                    [with_libnetlink="no (symbol 'rtnl_open' not found)"],
+                    [$with_libnetlink_libs])
+fi
+if test "x$with_libnetlink" = "xyes"
+then
+       BUILD_WITH_LIBNETLINK_CFLAGS="$with_libnetlink_cflags"
+       BUILD_WITH_LIBNETLINK_LIBS="$with_libnetlink_libs"
+       AC_SUBST(BUILD_WITH_LIBNETLINK_CFLAGS)
+       AC_SUBST(BUILD_WITH_LIBNETLINK_LIBS)
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBNETLINK, test "x$with_libnetlink" = "xyes")
+# }}}
+
+# --with-libnetapp {{{
+AC_ARG_VAR([LIBNETAPP_CPPFLAGS], [C preprocessor flags required to build with libnetapp])
+AC_ARG_VAR([LIBNETAPP_LDFLAGS],  [Linker flags required to build with libnetapp])
+AC_ARG_VAR([LIBNETAPP_LIBS],     [Other libraries required to link against libnetapp])
+LIBNETAPP_CPPFLAGS="$LIBNETAPP_CPPFLAGS"
+LIBNETAPP_LDFLAGS="$LIBNETAPP_LDFLAGS"
+LIBNETAPP_LIBS="$LIBNETAPP_LIBS"
+AC_ARG_WITH(libnetapp, [AS_HELP_STRING([--with-libnetapp@<:@=PREFIX@:>@], [Path to libnetapp.])],
+[
+ if test -d "$withval"
+ then
+        LIBNETAPP_CPPFLAGS="$LIBNETAPP_CPPFLAGS -I$withval/include"
+        LIBNETAPP_LDFLAGS="$LIBNETAPP_LDFLAGS -L$withval/lib"
+        with_libnetapp="yes"
+ else
+        with_libnetapp="$withval"
+ fi
+],
+[
+ with_libnetapp="yes"
+])
+
+SAVE_CPPFLAGS="$CPPFLAGS"
+SAVE_LDFLAGS="$LDFLAGS"
+CPPFLAGS="$CPPFLAGS $LIBNETAPP_CPPFLAGS"
+LDFLAGS="$LDFLAGS $LIBNETAPP_LDFLAGS"
+
+if test "x$with_libnetapp" = "xyes"
+then
+       if test "x$LIBNETAPP_CPPFLAGS" != "x"
+       then
+               AC_MSG_NOTICE([netapp CPPFLAGS: $LIBNETAPP_CPPFLAGS])
+       fi
+       AC_CHECK_HEADERS(netapp_api.h,
+               [with_libnetapp="yes"],
+               [with_libnetapp="no (netapp_api.h not found)"])
+fi
+
+if test "x$with_libnetapp" = "xyes"
+then
+       if test "x$LIBNETAPP_LDFLAGS" != "x"
+       then
+               AC_MSG_NOTICE([netapp LDFLAGS: $LIBNETAPP_LDFLAGS])
+       fi
+
+       if test "x$LIBNETAPP_LIBS" = "x"
+       then
+               LIBNETAPP_LIBS="-lpthread -lxml -ladt -lssl -lm -lcrypto -lz"
+       fi
+       AC_MSG_NOTICE([netapp LIBS: $LIBNETAPP_LIBS])
+
+       AC_CHECK_LIB(netapp, na_server_invoke_elem,
+               [with_libnetapp="yes"],
+               [with_libnetapp="no (symbol na_server_invoke_elem not found)"],
+               [$LIBNETAPP_LIBS])
+       LIBNETAPP_LIBS="-lnetapp $LIBNETAPP_LIBS"
+fi
+
+CPPFLAGS="$SAVE_CPPFLAGS"
+LDFLAGS="$SAVE_LDFLAGS"
+
+if test "x$with_libnetapp" = "xyes"
+then
+       AC_DEFINE(HAVE_LIBNETAPP, 1, [Define to 1 if you have the netapp library (-lnetapp).])
+fi
+
+AC_SUBST(LIBNETAPP_CPPFLAGS)
+AC_SUBST(LIBNETAPP_LDFLAGS)
+AC_SUBST(LIBNETAPP_LIBS)
+AM_CONDITIONAL(BUILD_WITH_LIBNETAPP, test "x$with_libnetapp" = "xyes")
+# }}}
+
+# --with-libnetsnmp {{{
+with_snmp_config="net-snmp-config"
+with_snmp_cflags=""
+with_snmp_libs=""
+AC_ARG_WITH(libnetsnmp, [AS_HELP_STRING([--with-libnetsnmp@<:@=PREFIX@:>@], [Path to the Net-SNMPD library.])],
+[
+       if test "x$withval" = "xno"
+       then
+               with_libnetsnmp="no"
+       else if test "x$withval" = "xyes"
+       then
+               with_libnetsnmp="yes"
+       else
+               if test -x "$withval"
+               then
+                       with_snmp_config="$withval"
+                       with_libnetsnmp="yes"
+               else
+                       with_snmp_config="$withval/bin/net-snmp-config"
+                       with_libnetsnmp="yes"
+               fi
+       fi; fi
+],
+[with_libnetsnmp="yes"])
+if test "x$with_libnetsnmp" = "xyes"
+then
+       with_snmp_cflags=`$with_snmp_config --cflags 2>/dev/null`
+       snmp_config_status=$?
+
+       if test $snmp_config_status -ne 0
+       then
+               with_libnetsnmp="no ($with_snmp_config failed)"
+       else
+               SAVE_CPPFLAGS="$CPPFLAGS"
+               CPPFLAGS="$CPPFLAGS $with_snmp_cflags"
+               
+               AC_CHECK_HEADERS(net-snmp/net-snmp-config.h, [], [with_libnetsnmp="no (net-snmp/net-snmp-config.h not found)"])
+
+               CPPFLAGS="$SAVE_CPPFLAGS"
+       fi
+fi
+if test "x$with_libnetsnmp" = "xyes"
+then
+       with_snmp_libs=`$with_snmp_config --libs 2>/dev/null`
+       snmp_config_status=$?
+
+       if test $snmp_config_status -ne 0
+       then
+               with_libnetsnmp="no ($with_snmp_config failed)"
+       else
+               AC_CHECK_LIB(netsnmp, init_snmp,
+               [with_libnetsnmp="yes"],
+               [with_libnetsnmp="no (libnetsnmp not found)"],
+               [$with_snmp_libs])
+       fi
+fi
+if test "x$with_libnetsnmp" = "xyes"
+then
+       BUILD_WITH_LIBSNMP_CFLAGS="$with_snmp_cflags"
+       BUILD_WITH_LIBSNMP_LIBS="$with_snmp_libs"
+       AC_SUBST(BUILD_WITH_LIBSNMP_CFLAGS)
+       AC_SUBST(BUILD_WITH_LIBSNMP_LIBS)
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBNETSNMP, test "x$with_libnetsnmp" = "xyes")
+# }}}
+
+# --with-liboconfig {{{
+with_own_liboconfig="no"
+liboconfig_LDFLAGS="$LDFLAGS"
+liboconfig_CPPFLAGS="$CPPFLAGS"
+AC_ARG_WITH(liboconfig, [AS_HELP_STRING([--with-liboconfig@<:@=PREFIX@:>@], [Path to liboconfig.])],
+[
+       if test "x$withval" != "xno" && test "x$withval" != "xyes"
+       then
+               if test -d "$withval/lib"
+               then
+                       liboconfig_LDFLAGS="$LDFLAGS -L$withval/lib"
+               fi
+               if test -d "$withval/include"
+               then
+                       liboconfig_CPPFLAGS="$CPPFLAGS -I$withval/include"
+               fi
+       fi
+       if test "x$withval" = "xno"
+       then
+               AC_MSG_ERROR("liboconfig is required")
+       fi
+],
+[
+       with_liboconfig="yes"
+])
+
+save_LDFLAGS="$LDFLAGS"
+save_CPPFLAGS="$CPPFLAGS"
+LDFLAGS="$liboconfig_LDFLAGS"
+CPPFLAGS="$liboconfig_CPPFLAGS"
+AC_CHECK_LIB(oconfig, oconfig_parse_fh,
+[
+       with_liboconfig="yes"
+       with_own_liboconfig="no"
+],
+[
+       with_liboconfig="yes"
+       with_own_liboconfig="yes"
+       LDFLAGS="$save_LDFLAGS"
+       CPPFLAGS="$save_CPPFLAGS"
+])
+
+AM_CONDITIONAL(BUILD_WITH_OWN_LIBOCONFIG, test "x$with_own_liboconfig" = "xyes")
+if test "x$with_own_liboconfig" = "xyes"
+then
+       with_liboconfig="yes (shipped version)"
+fi
+# }}}
+
+# --with-liboping {{{
+AC_ARG_WITH(liboping, [AS_HELP_STRING([--with-liboping@<:@=PREFIX@:>@], [Path to liboping.])],
+[
+ if test "x$withval" = "xyes"
+ then
+        with_liboping="yes"
+ else if test "x$withval" = "xno"
+ then
+        with_liboping="no"
+ else
+        with_liboping="yes"
+        LIBOPING_CPPFLAGS="$LIBOPING_CPPFLAGS -I$withval/include"
+        LIBOPING_LDFLAGS="$LIBOPING_LDFLAGS -L$withval/lib"
+ fi; fi
+],
+[with_liboping="yes"])
+
+SAVE_CPPFLAGS="$CPPFLAGS"
+SAVE_LDFLAGS="$LDFLAGS"
+
+CPPFLAGS="$CPPFLAGS $LIBOPING_CPPFLAGS"
+LDFLAGS="$LDFLAGS $LIBOPING_LDFLAGS"
+
+if test "x$with_liboping" = "xyes"
+then
+       if test "x$LIBOPING_CPPFLAGS" != "x"
+       then
+               AC_MSG_NOTICE([liboping CPPFLAGS: $LIBOPING_CPPFLAGS])
+       fi
+       AC_CHECK_HEADERS(oping.h,
+       [with_liboping="yes"],
+       [with_liboping="no (oping.h not found)"])
+fi
+if test "x$with_liboping" = "xyes"
+then
+       if test "x$LIBOPING_LDFLAGS" != "x"
+       then
+               AC_MSG_NOTICE([liboping LDFLAGS: $LIBOPING_LDFLAGS])
+       fi
+       AC_CHECK_LIB(oping, ping_construct,
+       [with_liboping="yes"],
+       [with_liboping="no (symbol 'ping_construct' not found)"])
+fi
+
+CPPFLAGS="$SAVE_CPPFLAGS"
+LDFLAGS="$SAVE_LDFLAGS"
+
+if test "x$with_liboping" = "xyes"
+then
+       BUILD_WITH_LIBOPING_CPPFLAGS="$LIBOPING_CPPFLAGS"
+       BUILD_WITH_LIBOPING_LDFLAGS="$LIBOPING_LDFLAGS"
+       AC_SUBST(BUILD_WITH_LIBOPING_CPPFLAGS)
+       AC_SUBST(BUILD_WITH_LIBOPING_LDFLAGS)
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBOPING, test "x$with_liboping" = "xyes")
+# }}}
+
+# --with-oracle {{{
+with_oracle_cppflags=""
+with_oracle_libs=""
+AC_ARG_WITH(oracle, [AS_HELP_STRING([--with-oracle@<:@=ORACLE_HOME@:>@], [Path to Oracle.])],
+[
+       if test "x$withval" = "xyes"
+       then
+               if test "x$ORACLE_HOME" = "x"
+               then
+                       AC_MSG_WARN([Use of the Oracle library has been forced, but the environment variable ORACLE_HOME is not set.])
+               fi
+               with_oracle="yes"
+       else if test "x$withval" = "xno"
+       then
+               with_oracle="no"
+       else
+               with_oracle="yes"
+               ORACLE_HOME="$withval"
+       fi; fi
+],
+[
+       if test "x$ORACLE_HOME" = "x"
+       then
+               with_oracle="no (ORACLE_HOME is not set)"
+       else
+               with_oracle="yes"
+       fi
+])
+if test "x$ORACLE_HOME" != "x"
+then
+       with_oracle_cppflags="-I$ORACLE_HOME/rdbms/public"
+
+       if test -e "$ORACLE_HOME/lib/ldflags"
+       then
+               with_oracle_libs=`cat "$ORACLE_HOME/lib/ldflags"`
+       fi
+       #with_oracle_libs="-L$ORACLE_HOME/lib $with_oracle_libs -lclntsh"
+       with_oracle_libs="-L$ORACLE_HOME/lib -lclntsh"
+fi
+if test "x$with_oracle" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_oracle_cppflags"
+
+       AC_CHECK_HEADERS(oci.h, [with_oracle="yes"], [with_oracle="no (oci.h not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+if test "x$with_oracle" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       SAVE_LDFLAGS="$LDFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_oracle_cppflags"
+       LDFLAGS="$LDFLAGS $with_oracle_libs"
+
+       AC_CHECK_FUNC(OCIEnvCreate, [with_oracle="yes"], [with_oracle="no (Symbol 'OCIEnvCreate' not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+       LDFLAGS="$SAVE_LDFLAGS"
+fi
+if test "x$with_oracle" = "xyes"
+then
+       BUILD_WITH_ORACLE_CFLAGS="$with_oracle_cppflags"
+       BUILD_WITH_ORACLE_LIBS="$with_oracle_libs"
+       AC_SUBST(BUILD_WITH_ORACLE_CFLAGS)
+       AC_SUBST(BUILD_WITH_ORACLE_LIBS)
+fi
+# }}}
+
+# --with-libowcapi {{{
+with_libowcapi_cppflags=""
+with_libowcapi_libs="-lowcapi"
+AC_ARG_WITH(libowcapi, [AS_HELP_STRING([--with-libowcapi@<:@=PREFIX@:>@], [Path to libowcapi.])],
+[
+       if test "x$withval" != "xno" && test "x$withval" != "xyes"
+       then
+               with_libowcapi_cppflags="-I$withval/include"
+               with_libowcapi_libs="-L$withval/lib -lowcapi"
+               with_libowcapi="yes"
+       else
+               with_libowcapi="$withval"
+       fi
+],
+[
+       with_libowcapi="yes"
+])
+if test "x$with_libowcapi" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$with_libowcapi_cppflags"
+       
+       AC_CHECK_HEADERS(owcapi.h, [with_libowcapi="yes"], [with_libowcapi="no (owcapi.h not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+if test "x$with_libowcapi" = "xyes"
+then
+       SAVE_LDFLAGS="$LDFLAGS"
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       LDFLAGS="$with_libowcapi_libs"
+       CPPFLAGS="$with_libowcapi_cppflags"
+       
+       AC_CHECK_LIB(owcapi, OW_get, [with_libowcapi="yes"], [with_libowcapi="no (libowcapi not found)"])
+
+       LDFLAGS="$SAVE_LDFLAGS"
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+if test "x$with_libowcapi" = "xyes"
+then
+       BUILD_WITH_LIBOWCAPI_CPPFLAGS="$with_libowcapi_cppflags"
+       BUILD_WITH_LIBOWCAPI_LIBS="$with_libowcapi_libs"
+       AC_SUBST(BUILD_WITH_LIBOWCAPI_CPPFLAGS)
+       AC_SUBST(BUILD_WITH_LIBOWCAPI_LIBS)
+fi
+# }}}
+
+# --with-libpcap {{{
+AC_ARG_WITH(libpcap, [AS_HELP_STRING([--with-libpcap@<:@=PREFIX@:>@], [Path to libpcap.])],
+[
+       if test "x$withval" != "xno" && test "x$withval" != "xyes"
+       then
+               LDFLAGS="$LDFLAGS -L$withval/lib"
+               CPPFLAGS="$CPPFLAGS -I$withval/include"
+               with_libpcap="yes"
+       else
+               with_libpcap="$withval"
+       fi
+],
+[
+       with_libpcap="yes"
+])
+if test "x$with_libpcap" = "xyes"
+then
+       AC_CHECK_LIB(pcap, pcap_open_live,
+       [
+               AC_DEFINE(HAVE_LIBPCAP, 1, [Define to 1 if you have the pcap library (-lpcap).])
+       ], [with_libpcap="no (libpcap not found)"])
+fi
+if test "x$with_libpcap" = "xyes"
+then
+       AC_CHECK_HEADERS(pcap.h,,
+                        [with_libpcap="no (pcap.h not found)"])
+fi
+if test "x$with_libpcap" = "xyes"
+then
+       AC_CHECK_HEADERS(pcap-bpf.h,,
+                        [with_libpcap="no (pcap-bpf.h not found)"])
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBPCAP, test "x$with_libpcap" = "xyes")
+# }}}
+
+# --with-libperl {{{
+perl_interpreter="perl"
+AC_ARG_WITH(libperl, [AS_HELP_STRING([--with-libperl@<:@=PREFIX@:>@], [Path to libperl.])],
+[
+       if test -x "$withval"
+       then
+               perl_interpreter="$withval"
+               with_libperl="yes"
+       else if test "x$withval" != "xno" && test "x$withval" != "xyes"
+       then
+               LDFLAGS="$LDFLAGS -L$withval/lib"
+               CPPFLAGS="$CPPFLAGS -I$withval/include"
+               perl_interpreter="$withval/bin/perl"
+               with_libperl="yes"
+       else
+               with_libperl="$withval"
+       fi; fi
+],
+[
+       with_libperl="yes"
+])
+
+AC_MSG_CHECKING([for perl])
+perl_interpreter=`which "$perl_interpreter" 2> /dev/null`
+if test -x "$perl_interpreter"
+then
+       AC_MSG_RESULT([yes ($perl_interpreter)])
+else
+       perl_interpreter=""
+       AC_MSG_RESULT([no])
+fi
+
+AC_SUBST(PERL, "$perl_interpreter")
+
+if test "x$with_libperl" = "xyes" \
+       && test -n "$perl_interpreter"
+then
+  SAVE_CFLAGS="$CFLAGS"
+  SAVE_LDFLAGS="$LDFLAGS"
+dnl ARCHFLAGS="" -> disable multi -arch on OSX (see Config_heavy.pl:fetch_string)
+  PERL_CFLAGS=`ARCHFLAGS="" $perl_interpreter -MExtUtils::Embed -e ccopts`
+  PERL_LDFLAGS=`ARCHFLAGS="" $perl_interpreter -MExtUtils::Embed -e ldopts`
+  CFLAGS="$CFLAGS $PERL_CFLAGS"
+  LDFLAGS="$LDFLAGS $PERL_LDFLAGS"
+
+  AC_CACHE_CHECK([for libperl],
+    [c_cv_have_libperl],
+    AC_LINK_IFELSE(
+      AC_LANG_PROGRAM(
+      [[
+#define PERL_NO_GET_CONTEXT
+#include <EXTERN.h>
+#include <perl.h>
+#include <XSUB.h>
+      ]],
+      [[
+       dTHX;
+       load_module (PERL_LOADMOD_NOIMPORT,
+                        newSVpv ("Collectd::Plugin::FooBar", 24),
+                        Nullsv);
+      ]]),
+      [c_cv_have_libperl="yes"],
+      [c_cv_have_libperl="no"]
+    )
+  )
+
+  if test "x$c_cv_have_libperl" = "xyes"
+  then
+         AC_DEFINE(HAVE_LIBPERL, 1, [Define if libperl is present and usable.])
+         AC_SUBST(PERL_CFLAGS)
+         AC_SUBST(PERL_LDFLAGS)
+  else
+         with_libperl="no"
+  fi
+
+  CFLAGS="$SAVE_CFLAGS"
+  LDFLAGS="$SAVE_LDFLAGS"
+else if test -z "$perl_interpreter"; then
+  with_libperl="no (no perl interpreter found)"
+  c_cv_have_libperl="no"
+fi; fi
+AM_CONDITIONAL(BUILD_WITH_LIBPERL, test "x$with_libperl" = "xyes")
+
+if test "x$with_libperl" = "xyes"
+then
+       SAVE_CFLAGS="$CFLAGS"
+       SAVE_LDFLAGS="$LDFLAGS"
+       CFLAGS="$CFLAGS $PERL_CFLAGS"
+       LDFLAGS="$LDFLAGS $PERL_LDFLAGS"
+
+       AC_CACHE_CHECK([if perl supports ithreads],
+               [c_cv_have_perl_ithreads],
+               AC_LINK_IFELSE(
+                       AC_LANG_PROGRAM(
+                       [[
+#include <EXTERN.h>
+#include <perl.h>
+#include <XSUB.h>
+
+#if !defined(USE_ITHREADS)
+# error "Perl does not support ithreads!"
+#endif /* !defined(USE_ITHREADS) */
+                       ]],
+                       [[ ]]),
+                       [c_cv_have_perl_ithreads="yes"],
+                       [c_cv_have_perl_ithreads="no"]
+               )
+       )
+
+       if test "x$c_cv_have_perl_ithreads" = "xyes"
+       then
+               AC_DEFINE(HAVE_PERL_ITHREADS, 1, [Define if Perl supports ithreads.])
+       fi
+
+       CFLAGS="$SAVE_CFLAGS"
+       LDFLAGS="$SAVE_LDFLAGS"
+fi
+
+if test "x$with_libperl" = "xyes"
+then
+       SAVE_CFLAGS="$CFLAGS"
+       SAVE_LDFLAGS="$LDFLAGS"
+       # trigger an error if Perl_load_module*() uses __attribute__nonnull__(3)
+       # (see issues #41 and #42)
+       CFLAGS="$CFLAGS $PERL_CFLAGS -Wall -Werror"
+       LDFLAGS="$LDFLAGS $PERL_LDFLAGS"
+
+       AC_CACHE_CHECK([for broken Perl_load_module()],
+               [c_cv_have_broken_perl_load_module],
+               AC_LINK_IFELSE(
+                       AC_LANG_PROGRAM(
+                       [[
+#define PERL_NO_GET_CONTEXT
+#include <EXTERN.h>
+#include <perl.h>
+#include <XSUB.h>
+                       ]],
+                       [[
+                        dTHX;
+                        load_module (PERL_LOADMOD_NOIMPORT,
+                            newSVpv ("Collectd::Plugin::FooBar", 24),
+                            Nullsv);
+                       ]]),
+                       [c_cv_have_broken_perl_load_module="no"],
+                       [c_cv_have_broken_perl_load_module="yes"]
+               )
+       )
+
+       CFLAGS="$SAVE_CFLAGS"
+       LDFLAGS="$SAVE_LDFLAGS"
+fi
+AM_CONDITIONAL(HAVE_BROKEN_PERL_LOAD_MODULE,
+               test "x$c_cv_have_broken_perl_load_module" = "xyes")
+
+if test "x$with_libperl" = "xyes"
+then
+       SAVE_CFLAGS="$CFLAGS"
+       SAVE_LDFLAGS="$LDFLAGS"
+       CFLAGS="$CFLAGS $PERL_CFLAGS"
+       LDFLAGS="$LDFLAGS $PERL_LDFLAGS"
+
+       AC_CHECK_MEMBER(
+               [struct mgvtbl.svt_local],
+               [have_struct_mgvtbl_svt_local="yes"],
+               [have_struct_mgvtbl_svt_local="no"],
+               [
+#include <EXTERN.h>
+#include <perl.h>
+#include <XSUB.h>
+               ])
+
+       if test "x$have_struct_mgvtbl_svt_local" = "xyes"
+       then
+               AC_DEFINE(HAVE_PERL_STRUCT_MGVTBL_SVT_LOCAL, 1,
+                                 [Define if Perl's struct mgvtbl has member svt_local.])
+       fi
+
+       CFLAGS="$SAVE_CFLAGS"
+       LDFLAGS="$SAVE_LDFLAGS"
+fi
+# }}}
+
+# --with-libpq {{{
+with_pg_config="pg_config"
+with_libpq_includedir=""
+with_libpq_libdir=""
+with_libpq_cppflags=""
+with_libpq_ldflags=""
+AC_ARG_WITH(libpq, [AS_HELP_STRING([--with-libpq@<:@=PREFIX@:>@],
+       [Path to libpq.])],
+[
+       if test "x$withval" = "xno"
+       then
+               with_libpq="no"
+       else if test "x$withval" = "xyes"
+       then
+               with_libpq="yes"
+       else
+               if test -f "$withval" && test -x "$withval";
+               then
+                       with_pg_config="$withval"
+               else if test -x "$withval/bin/pg_config"
+               then
+                       with_pg_config="$withval/bin/pg_config"
+               fi; fi
+               with_libpq="yes"
+       fi; fi
+],
+[
+       with_libpq="yes"
+])
+if test "x$with_libpq" = "xyes"
+then
+       with_libpq_includedir=`$with_pg_config --includedir 2> /dev/null`
+       pg_config_status=$?
+
+       if test $pg_config_status -eq 0
+       then
+               if test -n "$with_libpq_includedir"; then
+                       for dir in $with_libpq_includedir; do
+                               with_libpq_cppflags="$with_libpq_cppflags -I$dir"
+                       done
+               fi
+       else
+               AC_MSG_WARN([$with_pg_config returned with status $pg_config_status])
+       fi
+
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libpq_cppflags"
+
+       AC_CHECK_HEADERS(libpq-fe.h, [],
+               [with_libpq="no (libpq-fe.h not found)"], [])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+if test "x$with_libpq" = "xyes"
+then
+       with_libpq_libdir=`$with_pg_config --libdir 2> /dev/null`
+       pg_config_status=$?
+
+       if test $pg_config_status -eq 0
+       then
+               if test -n "$with_libpq_libdir"; then
+                       for dir in $with_libpq_libdir; do
+                               with_libpq_ldflags="$with_libpq_ldflags -L$dir"
+                       done
+               fi
+       else
+               AC_MSG_WARN([$with_pg_config returned with status $pg_config_status])
+       fi
+
+       SAVE_LDFLAGS="$LDFLAGS"
+       LDFLAGS="$LDFLAGS $with_libpq_ldflags"
+
+       AC_CHECK_LIB(pq, PQconnectdb,
+               [with_libpq="yes"],
+               [with_libpq="no (symbol 'PQconnectdb' not found)"])
+
+       AC_CHECK_LIB(pq, PQserverVersion,
+               [with_libpq="yes"],
+               [with_libpq="no (symbol 'PQserverVersion' not found)"])
+
+       LDFLAGS="$SAVE_LDFLAGS"
+fi
+if test "x$with_libpq" = "xyes"
+then
+       BUILD_WITH_LIBPQ_CPPFLAGS="$with_libpq_cppflags"
+       BUILD_WITH_LIBPQ_LDFLAGS="$with_libpq_ldflags"
+       AC_SUBST(BUILD_WITH_LIBPQ_CPPFLAGS)
+       AC_SUBST(BUILD_WITH_LIBPQ_LDFLAGS)
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBPQ, test "x$with_libpq" = "xyes")
+# }}}
+
+# --with-libpthread {{{
+AC_ARG_WITH(libpthread, [AS_HELP_STRING([--with-libpthread=@<:@=PREFIX@:>@], [Path to libpthread.])],
+[      if test "x$withval" != "xno" \
+               && test "x$withval" != "xyes"
+       then
+               LDFLAGS="$LDFLAGS -L$withval/lib"
+               CPPFLAGS="$CPPFLAGS -I$withval/include"
+               with_libpthread="yes"
+       else
+               if test "x$withval" = "xno"
+               then
+                       with_libpthread="no (disabled)"
+               fi
+       fi
+], [with_libpthread="yes"])
+if test "x$with_libpthread" = "xyes"
+then
+       AC_CHECK_LIB(pthread, pthread_create, [with_libpthread="yes"], [with_libpthread="no (libpthread not found)"], [])
+fi
+
+if test "x$with_libpthread" = "xyes"
+then
+       AC_CHECK_HEADERS(pthread.h,, [with_libpthread="no (pthread.h not found)"])
+fi
+if test "x$with_libpthread" = "xyes"
+then
+       collect_pthread=1
+else
+       collect_pthread=0
+fi
+AC_DEFINE_UNQUOTED(HAVE_LIBPTHREAD, [$collect_pthread],
+       [Wether or not to use pthread (POSIX threads) library])
+AM_CONDITIONAL(BUILD_WITH_LIBPTHREAD, test "x$with_libpthread" = "xyes")
+# }}}
+
+# --with-python {{{
+with_python_prog=""
+with_python_path="$PATH"
+AC_ARG_WITH(python, [AS_HELP_STRING([--with-python@<:@=PREFIX@:>@], [Path to the python interpreter.])],
+[
+ if test "x$withval" = "xyes" || test "x$withval" = "xno"
+ then
+        with_python="$withval"
+ else if test -x "$withval"
+ then
+        with_python_prog="$withval"
+        with_python_path="`dirname \"$withval\"`$PATH_SEPARATOR$with_python_path"
+        with_python="yes"
+ else if test -d "$withval"
+ then
+        with_python_path="$withval$PATH_SEPARATOR$with_python_path"
+        with_python="yes"
+ else
+        AC_MSG_WARN([Argument not recognized: $withval])
+ fi; fi; fi
+], [with_python="yes"])
+
+SAVE_PATH="$PATH"
+SAVE_CPPFLAGS="$CPPFLAGS"
+SAVE_LDFLAGS="$LDFLAGS"
+SAVE_LIBS="$LIBS"
+
+PATH="$with_python_path"
+
+if test "x$with_python" = "xyes" && test "x$with_python_prog" = "x"
+then
+       AC_MSG_CHECKING([for python])
+       with_python_prog="`which python 2>/dev/null`"
+       if test "x$with_python_prog" = "x"
+       then
+               AC_MSG_RESULT([not found])
+               with_python="no (interpreter not found)"
+       else
+               AC_MSG_RESULT([$with_python_prog])
+       fi
+fi
+
+if test "x$with_python" = "xyes"
+then
+       AC_MSG_CHECKING([for Python CPPFLAGS])
+       python_include_path=`echo "import distutils.sysconfig;import sys;sys.stdout.write(distutils.sysconfig.get_python_inc())" | "$with_python_prog" 2>&1`
+       python_config_status=$?
+
+       if test "$python_config_status" -ne 0 || test "x$python_include_path" = "x"
+       then
+               AC_MSG_RESULT([failed with status $python_config_status (output: $python_include_path)])
+               with_python="no"
+       else
+               AC_MSG_RESULT([$python_include_path])
+       fi
+fi
+
+if test "x$with_python" = "xyes"
+then
+       CPPFLAGS="-I$python_include_path $CPPFLAGS"
+       AC_CHECK_HEADERS(Python.h,
+                        [with_python="yes"],
+                        [with_python="no ('Python.h' not found)"])
+fi
+
+if test "x$with_python" = "xyes"
+then
+       AC_MSG_CHECKING([for Python LDFLAGS])
+       python_library_path=`echo "import distutils.sysconfig;import sys;sys.stdout.write(distutils.sysconfig.get_config_vars(\"LIBDIR\").__getitem__(0))" | "$with_python_prog" 2>&1`
+       python_config_status=$?
+
+       if test "$python_config_status" -ne 0 || test "x$python_library_path" = "x"
+       then
+               AC_MSG_RESULT([failed with status $python_config_status (output: $python_library_path)])
+               with_python="no"
+       else
+               AC_MSG_RESULT([$python_library_path])
+       fi
+fi
+
+if test "x$with_python" = "xyes"
+then
+       AC_MSG_CHECKING([for Python LIBS])
+       python_library_flags=`echo "import distutils.sysconfig;import sys;sys.stdout.write(distutils.sysconfig.get_config_vars(\"BLDLIBRARY\").__getitem__(0))" | "$with_python_prog" 2>&1`
+       python_config_status=$?
+
+       if test "$python_config_status" -ne 0 || test "x$python_library_flags" = "x"
+       then
+               AC_MSG_RESULT([failed with status $python_config_status (output: $python_library_flags)])
+               with_python="no"
+       else
+               AC_MSG_RESULT([$python_library_flags])
+       fi
+fi
+
+if test "x$with_python" = "xyes"
+then
+       LDFLAGS="-L$python_library_path $LDFLAGS"
+       LIBS="$python_library_flags $LIBS"
+
+       AC_CHECK_FUNC(PyObject_CallFunction,
+                     [with_python="yes"],
+                     [with_python="no (Symbol 'PyObject_CallFunction' not found)"])
+fi
+
+PATH="$SAVE_PATH"
+CPPFLAGS="$SAVE_CPPFLAGS"
+LDFLAGS="$SAVE_LDFLAGS"
+LIBS="$SAVE_LIBS"
+
+if test "x$with_python" = "xyes"
+then
+       BUILD_WITH_PYTHON_CPPFLAGS="-I$python_include_path"
+       BUILD_WITH_PYTHON_LDFLAGS="-L$python_library_path"
+       BUILD_WITH_PYTHON_LIBS="$python_library_flags"
+       AC_SUBST(BUILD_WITH_PYTHON_CPPFLAGS)
+       AC_SUBST(BUILD_WITH_PYTHON_LDFLAGS)
+       AC_SUBST(BUILD_WITH_PYTHON_LIBS)
+fi
+# }}} --with-python
+
+# --with-librabbitmq {{{
+with_librabbitmq_cppflags=""
+with_librabbitmq_ldflags=""
+AC_ARG_WITH(librabbitmq, [AS_HELP_STRING([--with-librabbitmq@<:@=PREFIX@:>@], [Path to librabbitmq.])],
+[
+       if test "x$withval" != "xno" && test "x$withval" != "xyes"
+       then
+               with_librabbitmq_cppflags="-I$withval/include"
+               with_librabbitmq_ldflags="-L$withval/lib"
+               with_librabbitmq="yes"
+       else
+               with_librabbitmq="$withval"
+       fi
+],
+[
+       with_librabbitmq="yes"
+])
+if test "x$with_librabbitmq" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_librabbitmq_cppflags"
+
+       AC_CHECK_HEADERS(amqp.h, [with_librabbitmq="yes"], [with_librabbitmq="no (amqp.h not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+if test "x$with_librabbitmq" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       SAVE_LDFLAGS="$LDFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_librabbitmq_cppflags"
+       LDFLAGS="$LDFLAGS $with_librabbitmq_ldflags"
+
+       AC_CHECK_LIB(rabbitmq, amqp_basic_publish, [with_librabbitmq="yes"], [with_librabbitmq="no (Symbol 'amqp_basic_publish' not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+       LDFLAGS="$SAVE_LDFLAGS"
+fi
+if test "x$with_librabbitmq" = "xyes"
+then
+       BUILD_WITH_LIBRABBITMQ_CPPFLAGS="$with_librabbitmq_cppflags"
+       BUILD_WITH_LIBRABBITMQ_LDFLAGS="$with_librabbitmq_ldflags"
+       BUILD_WITH_LIBRABBITMQ_LIBS="-lrabbitmq"
+       AC_SUBST(BUILD_WITH_LIBRABBITMQ_CPPFLAGS)
+       AC_SUBST(BUILD_WITH_LIBRABBITMQ_LDFLAGS)
+       AC_SUBST(BUILD_WITH_LIBRABBITMQ_LIBS)
+       AC_DEFINE(HAVE_LIBRABBITMQ, 1, [Define if librabbitmq is present and usable.])
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBRABBITMQ, test "x$with_librabbitmq" = "xyes")
+# }}}
+
+# --with-librouteros {{{
+AC_ARG_WITH(librouteros, [AS_HELP_STRING([--with-librouteros@<:@=PREFIX@:>@], [Path to librouteros.])],
+[
+ if test "x$withval" = "xyes"
+ then
+        with_librouteros="yes"
+ else if test "x$withval" = "xno"
+ then
+        with_librouteros="no"
+ else
+        with_librouteros="yes"
+        LIBROUTEROS_CPPFLAGS="$LIBROUTEROS_CPPFLAGS -I$withval/include"
+        LIBROUTEROS_LDFLAGS="$LIBROUTEROS_LDFLAGS -L$withval/lib"
+ fi; fi
+],
+[with_librouteros="yes"])
+
+SAVE_CPPFLAGS="$CPPFLAGS"
+SAVE_LDFLAGS="$LDFLAGS"
+
+CPPFLAGS="$CPPFLAGS $LIBROUTEROS_CPPFLAGS"
+LDFLAGS="$LDFLAGS $LIBROUTEROS_LDFLAGS"
+
+if test "x$with_librouteros" = "xyes"
+then
+       if test "x$LIBROUTEROS_CPPFLAGS" != "x"
+       then
+               AC_MSG_NOTICE([librouteros CPPFLAGS: $LIBROUTEROS_CPPFLAGS])
+       fi
+       AC_CHECK_HEADERS(routeros_api.h,
+       [with_librouteros="yes"],
+       [with_librouteros="no (routeros_api.h not found)"])
+fi
+if test "x$with_librouteros" = "xyes"
+then
+       if test "x$LIBROUTEROS_LDFLAGS" != "x"
+       then
+               AC_MSG_NOTICE([librouteros LDFLAGS: $LIBROUTEROS_LDFLAGS])
+       fi
+       AC_CHECK_LIB(routeros, ros_interface,
+       [with_librouteros="yes"],
+       [with_librouteros="no (symbol 'ros_interface' not found)"])
+fi
+
+CPPFLAGS="$SAVE_CPPFLAGS"
+LDFLAGS="$SAVE_LDFLAGS"
+
+if test "x$with_librouteros" = "xyes"
+then
+       BUILD_WITH_LIBROUTEROS_CPPFLAGS="$LIBROUTEROS_CPPFLAGS"
+       BUILD_WITH_LIBROUTEROS_LDFLAGS="$LIBROUTEROS_LDFLAGS"
+       AC_SUBST(BUILD_WITH_LIBROUTEROS_CPPFLAGS)
+       AC_SUBST(BUILD_WITH_LIBROUTEROS_LDFLAGS)
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBROUTEROS, test "x$with_librouteros" = "xyes")
+# }}}
+
+# --with-librrd {{{
+# AC_ARG_WITH (package, help-string, [action-if-given], [action-if-not-given])
+librrd_cflags=""
+librrd_ldflags=""
+librrd_threadsafe="yes"
+librrd_rrdc_update="no"
+AC_ARG_WITH(librrd, [AS_HELP_STRING([--with-librrd@<:@=PREFIX@:>@], [Path to rrdtool.])],
+[      if test "x$withval" != "xno" && test "x$withval" != "xyes"
+       then
+               librrd_cflags="-I$withval/include"
+               librrd_ldflags="-L$withval/lib"
+               with_librrd="yes"
+       else
+               with_librrd="$withval"
+       fi
+], [with_librrd="yes"])
+if test "x$with_librrd" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       SAVE_LDFLAGS="$LDFLAGS"
+
+       CPPFLAGS="$CPPFLAGS $librrd_cflags"
+       LDFLAGS="$LDFLAGS $librrd_ldflags"
+
+       AC_CHECK_HEADERS(rrd.h,, [with_librrd="no (rrd.h not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+       LDFLAGS="$SAVE_LDFLAGS"
+fi
+if test "x$with_librrd" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       SAVE_LDFLAGS="$LDFLAGS"
+
+       CPPFLAGS="$CPPFLAGS $librrd_cflags"
+       LDFLAGS="$LDFLAGS $librrd_ldflags"
+
+       AC_CHECK_LIB(rrd_th, rrd_update_r,
+       [with_librrd="yes"
+        librrd_ldflags="$librrd_ldflags -lrrd_th -lm"
+       ],
+       [librrd_threadsafe="no"
+        AC_CHECK_LIB(rrd, rrd_update,
+        [with_librrd="yes"
+         librrd_ldflags="$librrd_ldflags -lrrd -lm"
+        ],
+        [with_librrd="no (symbol 'rrd_update' not found)"],
+        [-lm])
+       ],
+       [-lm])
+
+       if test "x$librrd_threadsafe" = "xyes"
+       then
+               AC_CHECK_LIB(rrd_th, rrdc_update, [librrd_rrdc_update="yes"], [librrd_rrdc_update="no"])
+       else
+               AC_CHECK_LIB(rrd, rrdc_update, [librrd_rrdc_update="yes"], [librrd_rrdc_update="no"])
+       fi
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+       LDFLAGS="$SAVE_LDFLAGS"
+fi
+if test "x$with_librrd" = "xyes"
+then
+       BUILD_WITH_LIBRRD_CFLAGS="$librrd_cflags"
+       BUILD_WITH_LIBRRD_LDFLAGS="$librrd_ldflags"
+       AC_SUBST(BUILD_WITH_LIBRRD_CFLAGS)
+       AC_SUBST(BUILD_WITH_LIBRRD_LDFLAGS)
+fi
+if test "x$librrd_threadsafe" = "xyes"
+then
+       AC_DEFINE(HAVE_THREADSAFE_LIBRRD, 1, [Define to 1 if you have the threadsafe rrd library (-lrrd_th).])
+fi
+# }}}
+
+# --with-libsensors {{{
+with_sensors_cflags=""
+with_sensors_ldflags=""
+AC_ARG_WITH(libsensors, [AS_HELP_STRING([--with-libsensors@<:@=PREFIX@:>@], [Path to lm_sensors.])],
+[
+       if test "x$withval" = "xno"
+       then
+               with_libsensors="no"
+       else
+               with_libsensors="yes"
+               if test "x$withval" != "xyes"
+               then
+                       with_sensors_cflags="-I$withval/include"
+                       with_sensors_ldflags="-L$withval/lib"
+                       with_libsensors="yes"
+               fi
+       fi
+],
+[
+       if test "x$ac_system" = "xLinux"
+       then
+               with_libsensors="yes"
+       else
+               with_libsensors="no (Linux only library)"
+       fi
+])
+if test "x$with_libsensors" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_sensors_cflags"
+
+#      AC_CHECK_HEADERS(sensors/sensors.h,
+#      [
+#              AC_DEFINE(HAVE_SENSORS_SENSORS_H, 1, [Define to 1 if you have the <sensors/sensors.h> header file.])
+#      ],
+#      [with_libsensors="no (sensors/sensors.h not found)"])
+       AC_CHECK_HEADERS(sensors/sensors.h, [], [with_libsensors="no (sensors/sensors.h not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+if test "x$with_libsensors" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       SAVE_LDFLAGS="$LDFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_sensors_cflags"
+       LDFLAGS="$LDFLAGS $with_sensors_ldflags"
+
+       AC_CHECK_LIB(sensors, sensors_init,
+       [
+               AC_DEFINE(HAVE_LIBSENSORS, 1, [Define to 1 if you have the sensors library (-lsensors).])
+       ],
+       [with_libsensors="no (libsensors not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+       LDFLAGS="$SAVE_LDFLAGS"
+fi
+if test "x$with_libsensors" = "xyes"
+then
+       BUILD_WITH_LIBSENSORS_CFLAGS="$with_sensors_cflags"
+       BUILD_WITH_LIBSENSORS_LDFLAGS="$with_sensors_ldflags"
+       AC_SUBST(BUILD_WITH_LIBSENSORS_CFLAGS)
+       AC_SUBST(BUILD_WITH_LIBSENSORS_LDFLAGS)
+fi
+AM_CONDITIONAL(BUILD_WITH_LM_SENSORS, test "x$with_libsensors" = "xyes")
+# }}}
+
+# --with-libstatgrab {{{
+with_libstatgrab_cflags=""
+with_libstatgrab_ldflags=""
+AC_ARG_WITH(libstatgrab, [AS_HELP_STRING([--with-libstatgrab@<:@=PREFIX@:>@], [Path to libstatgrab.])],
+[
+ if test "x$withval" != "xno" \
+   && test "x$withval" != "xyes"
+ then
+   with_libstatgrab_cflags="-I$withval/include"
+   with_libstatgrab_ldflags="-L$withval/lib -lstatgrab"
+   with_libstatgrab="yes"
+   with_libstatgrab_pkg_config="no"
+ else
+   with_libstatgrab="$withval"
+   with_libstatgrab_pkg_config="yes"
+ fi
+ ],
+[
+ with_libstatgrab="yes"
+ with_libstatgrab_pkg_config="yes"
+])
+
+if test "x$with_libstatgrab" = "xyes" \
+  && test "x$with_libstatgrab_pkg_config" = "xyes"
+then
+  if test "x$PKG_CONFIG" != "x"
+  then
+    AC_MSG_CHECKING([pkg-config for libstatgrab])
+    temp_result="found"
+    $PKG_CONFIG --exists libstatgrab 2>/dev/null
+    if test "$?" != "0"
+    then
+      with_libstatgrab_pkg_config="no"
+      with_libstatgrab="no (pkg-config doesn't know libstatgrab)"
+      temp_result="not found"
+    fi
+    AC_MSG_RESULT([$temp_result])
+  else
+    AC_MSG_NOTICE([pkg-config not available, trying to guess flags for the statgrab library.])
+    with_libstatgrab_pkg_config="no"
+    with_libstatgrab_ldflags="$with_libstatgrab_ldflags -lstatgrab"
+  fi
+fi
+
+if test "x$with_libstatgrab" = "xyes" \
+  && test "x$with_libstatgrab_pkg_config" = "xyes" \
+  && test "x$with_libstatgrab_cflags" = "x"
+then
+  AC_MSG_CHECKING([for libstatgrab CFLAGS])
+  temp_result="`$PKG_CONFIG --cflags libstatgrab`"
+  if test "$?" = "0"
+  then
+    with_libstatgrab_cflags="$temp_result"
+  else
+    with_libstatgrab="no ($PKG_CONFIG --cflags libstatgrab failed)"
+    temp_result="$PKG_CONFIG --cflags libstatgrab failed"
+  fi
+  AC_MSG_RESULT([$temp_result])
+fi
+
+if test "x$with_libstatgrab" = "xyes" \
+  && test "x$with_libstatgrab_pkg_config" = "xyes" \
+  && test "x$with_libstatgrab_ldflags" = "x"
+then
+  AC_MSG_CHECKING([for libstatgrab LDFLAGS])
+  temp_result="`$PKG_CONFIG --libs libstatgrab`"
+  if test "$?" = "0"
+  then
+    with_libstatgrab_ldflags="$temp_result"
+  else
+    with_libstatgrab="no ($PKG_CONFIG --libs libstatgrab failed)"
+    temp_result="$PKG_CONFIG --libs libstatgrab failed"
+  fi
+  AC_MSG_RESULT([$temp_result])
+fi
+
+if test "x$with_libstatgrab" = "xyes"
+then
+  SAVE_CPPFLAGS="$CPPFLAGS"
+  CPPFLAGS="$CPPFLAGS $with_libstatgrab_cflags"
+
+  AC_CHECK_HEADERS(statgrab.h,
+                  [with_libstatgrab="yes"],
+                  [with_libstatgrab="no (statgrab.h not found)"])
+
+  CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+
+if test "x$with_libstatgrab" = "xyes"
+then
+  SAVE_CFLAGS="$CFLAGS"
+  SAVE_LDFLAGS="$LDFLAGS"
+
+  CFLAGS="$CFLAGS $with_libstatgrab_cflags"
+  LDFLAGS="$LDFLAGS $with_libstatgrab_ldflags"
+
+  AC_CHECK_LIB(statgrab, sg_init,
+              [with_libstatgrab="yes"],
+              [with_libstatgrab="no (symbol sg_init not found)"])
+
+  CFLAGS="$SAVE_CFLAGS"
+  LDFLAGS="$SAVE_LDFLAGS"
+fi
+
+AM_CONDITIONAL(BUILD_WITH_LIBSTATGRAB, test "x$with_libstatgrab" = "xyes")
+if test "x$with_libstatgrab" = "xyes"
+then
+  AC_DEFINE(HAVE_LIBSTATGRAB, 1, [Define to 1 if you have the 'statgrab' library (-lstatgrab)])
+  BUILD_WITH_LIBSTATGRAB_CFLAGS="$with_libstatgrab_cflags"
+  BUILD_WITH_LIBSTATGRAB_LDFLAGS="$with_libstatgrab_ldflags"
+  AC_SUBST(BUILD_WITH_LIBSTATGRAB_CFLAGS)
+  AC_SUBST(BUILD_WITH_LIBSTATGRAB_LDFLAGS)
+fi
+# }}}
+
+# --with-libtokyotyrant {{{
+with_libtokyotyrant_cppflags=""
+with_libtokyotyrant_ldflags=""
+with_libtokyotyrant_libs=""
+AC_ARG_WITH(libtokyotyrant, [AS_HELP_STRING([--with-libtokyotyrant@<:@=PREFIX@:>@], [Path to libtokyotyrant.])],
+[
+  if test "x$withval" = "xno"
+  then
+    with_libtokyotyrant="no"
+  else if test "x$withval" = "xyes"
+  then
+    with_libtokyotyrant="yes"
+  else
+    with_libtokyotyrant_cppflags="-I$withval/include"
+    with_libtokyotyrant_ldflags="-L$withval/include"
+    with_libtokyotyrant_libs="-ltokyotyrant"
+    with_libtokyotyrant="yes"
+  fi; fi
+],
+[
+  with_libtokyotyrant="yes"
+])
+
+if test "x$with_libtokyotyrant" = "xyes"
+then
+  if $PKG_CONFIG --exists tokyotyrant
+  then
+    with_libtokyotyrant_cppflags="$with_libtokyotyrant_cppflags `$PKG_CONFIG --cflags tokyotyrant`"
+    with_libtokyotyrant_ldflags="$with_libtokyotyrant_ldflags `pkg-config --libs-only-L tokyotyrant`"
+    with_libtokyotyrant_libs="$with_libtokyotyrant_libs `pkg-config --libs-only-l tokyotyrant`"
+  fi
+fi
+
+SAVE_CPPFLAGS="$CPPFLAGS"
+SAVE_LDFLAGS="$LDFLAGS"
+CPPFLAGS="$CPPFLAGS $with_libtokyotyrant_cppflags"
+LDFLAGS="$LDFLAGS $with_libtokyotyrant_ldflags"
+
+if test "x$with_libtokyotyrant" = "xyes"
+then
+  AC_CHECK_HEADERS(tcrdb.h,
+  [
+          AC_DEFINE(HAVE_TCRDB_H, 1,
+                    [Define to 1 if you have the <tcrdb.h> header file.])
+  ], [with_libtokyotyrant="no (tcrdb.h not found)"])
+fi
+
+if test "x$with_libtokyotyrant" = "xyes"
+then
+  AC_CHECK_LIB(tokyotyrant, tcrdbrnum,
+  [
+          AC_DEFINE(HAVE_LIBTOKYOTYRANT, 1,
+                    [Define to 1 if you have the tokyotyrant library (-ltokyotyrant).])
+  ],
+  [with_libtokyotyrant="no (symbol tcrdbrnum not found)"],
+  [$with_libtokyotyrant_libs])
+fi
+
+CPPFLAGS="$SAVE_CPPFLAGS"
+LDFLAGS="$SAVE_LDFLAGS"
+
+if test "x$with_libtokyotyrant" = "xyes"
+then 
+  BUILD_WITH_LIBTOKYOTYRANT_CPPFLAGS="$with_libtokyotyrant_cppflags"
+  BUILD_WITH_LIBTOKYOTYRANT_LDFLAGS="$with_libtokyotyrant_ldflags"
+  BUILD_WITH_LIBTOKYOTYRANT_LIBS="$with_libtokyotyrant_libs"
+  AC_SUBST(BUILD_WITH_LIBTOKYOTYRANT_CPPFLAGS)
+  AC_SUBST(BUILD_WITH_LIBTOKYOTYRANT_LDFLAGS)
+  AC_SUBST(BUILD_WITH_LIBTOKYOTYRANT_LIBS)
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBTOKYOTYRANT, test "x$with_libtokyotyrant" = "xyes")
+# }}}
+
+# --with-libupsclient {{{
+with_libupsclient_config=""
+with_libupsclient_cflags=""
+with_libupsclient_libs=""
+AC_ARG_WITH(libupsclient, [AS_HELP_STRING([--with-libupsclient@<:@=PREFIX@:>@], [Path to the upsclient library.])],
+[
+       if test "x$withval" = "xno"
+       then
+               with_libupsclient="no"
+       else if test "x$withval" = "xyes"
+       then
+               with_libupsclient="use_pkgconfig"
+       else
+               if test -x "$withval"
+               then
+                       with_libupsclient_config="$withval"
+                       with_libupsclient="use_libupsclient_config"
+               else if test -x "$withval/bin/libupsclient-config"
+               then
+                       with_libupsclient_config="$withval/bin/libupsclient-config"
+                       with_libupsclient="use_libupsclient_config"
+               else
+                       AC_MSG_NOTICE([Not checking for libupsclient: Manually configured])
+                       with_libupsclient_cflags="-I$withval/include"
+                       with_libupsclient_libs="-L$withval/lib -lupsclient"
+                       with_libupsclient="yes"
+               fi; fi
+       fi; fi
+],
+[with_libupsclient="use_pkgconfig"])
+
+# configure using libupsclient-config
+if test "x$with_libupsclient" = "xuse_libupsclient_config"
+then
+       AC_MSG_NOTICE([Checking for libupsclient using $with_libupsclient_config])
+       with_libupsclient_cflags="`$with_libupsclient_config --cflags`"
+       if test $? -ne 0
+       then
+               with_libupsclient="no ($with_libupsclient_config failed)"
+       fi
+       with_libupsclient_libs="`$with_libupsclient_config --libs`"
+       if test $? -ne 0
+       then
+               with_libupsclient="no ($with_libupsclient_config failed)"
+       fi
+fi
+if test "x$with_libupsclient" = "xuse_libupsclient_config"
+then
+       with_libupsclient="yes"
+fi
+
+# configure using pkg-config
+if test "x$with_libupsclient" = "xuse_pkgconfig"
+then
+       if test "x$PKG_CONFIG" = "x"
+       then
+               with_libupsclient="no (Don't have pkg-config)"
+       fi
+fi
+if test "x$with_libupsclient" = "xuse_pkgconfig"
+then
+       AC_MSG_NOTICE([Checking for libupsclient using $PKG_CONFIG])
+       $PKG_CONFIG --exists 'libupsclient' 2>/dev/null
+       if test $? -ne 0
+       then
+               with_libupsclient="no (pkg-config doesn't know libupsclient)"
+       fi
+fi
+if test "x$with_libupsclient" = "xuse_pkgconfig"
+then
+       with_libupsclient_cflags="`$PKG_CONFIG --cflags 'libupsclient'`"
+       if test $? -ne 0
+       then
+               with_libupsclient="no ($PKG_CONFIG failed)"
+       fi
+       with_libupsclient_libs="`$PKG_CONFIG --libs 'libupsclient'`"
+       if test $? -ne 0
+       then
+               with_libupsclient="no ($PKG_CONFIG failed)"
+       fi
+fi
+if test "x$with_libupsclient" = "xuse_pkgconfig"
+then
+       with_libupsclient="yes"
+fi
+
+# with_libupsclient_cflags and with_libupsclient_libs are set up now, let's do
+# the actual checks.
+if test "x$with_libupsclient" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libupsclient_cflags"
+
+       AC_CHECK_HEADERS(upsclient.h, [], [with_libupsclient="no (upsclient.h not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+if test "x$with_libupsclient" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       SAVE_LDFLAGS="$LDFLAGS"
+
+       CPPFLAGS="$CPPFLAGS $with_libupsclient_cflags"
+       LDFLAGS="$LDFLAGS $with_libupsclient_libs"
+
+       AC_CHECK_LIB(upsclient, upscli_connect,
+                    [with_libupsclient="yes"],
+                    [with_libupsclient="no (symbol upscli_connect not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+       LDFLAGS="$SAVE_LDFLAGS"
+fi
+if test "x$with_libupsclient" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libupsclient_cflags"
+
+       AC_CHECK_TYPES([UPSCONN_t, UPSCONN], [], [],
+[#include <stdlib.h>
+#include <stdio.h>
+#include <upsclient.h>])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+if test "x$with_libupsclient" = "xyes"
+then
+       BUILD_WITH_LIBUPSCLIENT_CFLAGS="$with_libupsclient_cflags"
+       BUILD_WITH_LIBUPSCLIENT_LIBS="$with_libupsclient_libs"
+       AC_SUBST(BUILD_WITH_LIBUPSCLIENT_CFLAGS)
+       AC_SUBST(BUILD_WITH_LIBUPSCLIENT_LIBS)
+fi
+# }}}
+
+# --with-libxmms {{{
+with_xmms_config="xmms-config"
+with_xmms_cflags=""
+with_xmms_libs=""
+AC_ARG_WITH(libxmms, [AS_HELP_STRING([--with-libxmms@<:@=PREFIX@:>@], [Path to libxmms.])],
+[
+       if test "x$withval" != "xno" \
+               && test "x$withval" != "xyes"
+       then
+               if test -f "$withval" && test -x "$withval";
+               then
+                       with_xmms_config="$withval"
+               else if test -x "$withval/bin/xmms-config"
+               then
+                       with_xmms_config="$withval/bin/xmms-config"
+               fi; fi
+               with_libxmms="yes"
+       else if test "x$withval" = "xno"
+       then
+               with_libxmms="no"
+       else
+               with_libxmms="yes"
+       fi; fi
+],
+[
+       with_libxmms="yes"
+])
+if test "x$with_libxmms" = "xyes"
+then
+       with_xmms_cflags=`$with_xmms_config --cflags 2>/dev/null`
+       xmms_config_status=$?
+
+       if test $xmms_config_status -ne 0
+       then
+               with_libxmms="no"
+       fi
+fi
+if test "x$with_libxmms" = "xyes"
+then
+       with_xmms_libs=`$with_xmms_config --libs 2>/dev/null`
+       xmms_config_status=$?
+
+       if test $xmms_config_status -ne 0
+       then
+               with_libxmms="no"
+       fi
+fi
+if test "x$with_libxmms" = "xyes"
+then
+       AC_CHECK_LIB(xmms, xmms_remote_get_info,
+       [
+               BUILD_WITH_LIBXMMS_CFLAGS="$with_xmms_cflags"
+               BUILD_WITH_LIBXMMS_LIBS="$with_xmms_libs"
+               AC_SUBST(BUILD_WITH_LIBXMMS_CFLAGS)
+               AC_SUBST(BUILD_WITH_LIBXMMS_LIBS)
+       ],
+       [
+               with_libxmms="no"
+       ],
+       [$with_xmms_libs])
+fi
+with_libxmms_numeric=0
+if test "x$with_libxmms" = "xyes"
+then
+       with_libxmms_numeric=1
+fi
+AC_DEFINE_UNQUOTED(HAVE_LIBXMMS, [$with_libxmms_numeric], [Define to 1 if you have the 'xmms' library (-lxmms).])
+AM_CONDITIONAL(BUILD_WITH_LIBXMMS, test "x$with_libxmms" = "xyes")
+# }}}
+
+# --with-libyajl {{{
+with_libyajl_cppflags=""
+with_libyajl_ldflags=""
+AC_ARG_WITH(libyajl, [AS_HELP_STRING([--with-libyajl@<:@=PREFIX@:>@], [Path to libyajl.])],
+[
+       if test "x$withval" != "xno" && test "x$withval" != "xyes"
+       then
+               with_libyajl_cppflags="-I$withval/include"
+               with_libyajl_ldflags="-L$withval/lib"
+               with_libyajl="yes"
+       else
+               with_libyajl="$withval"
+       fi
+],
+[
+       with_libyajl="yes"
+])
+if test "x$with_libyajl" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libyajl_cppflags"
+
+       AC_CHECK_HEADERS(yajl/yajl_parse.h, [with_libyajl="yes"], [with_libyajl="no (yajl/yajl_parse.h not found)"])
+       AC_CHECK_HEADERS(yajl/yajl_version.h)
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+if test "x$with_libyajl" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       SAVE_LDFLAGS="$LDFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libyajl_cppflags"
+       LDFLAGS="$LDFLAGS $with_libyajl_ldflags"
+
+       AC_CHECK_LIB(yajl, yajl_alloc, [with_libyajl="yes"], [with_libyajl="no (Symbol 'yajl_alloc' not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+       LDFLAGS="$SAVE_LDFLAGS"
+fi
+if test "x$with_libyajl" = "xyes"
+then
+       BUILD_WITH_LIBYAJL_CPPFLAGS="$with_libyajl_cppflags"
+       BUILD_WITH_LIBYAJL_LDFLAGS="$with_libyajl_ldflags"
+       BUILD_WITH_LIBYAJL_LIBS="-lyajl"
+       AC_SUBST(BUILD_WITH_LIBYAJL_CPPFLAGS)
+       AC_SUBST(BUILD_WITH_LIBYAJL_LDFLAGS)
+       AC_SUBST(BUILD_WITH_LIBYAJL_LIBS)
+       AC_DEFINE(HAVE_LIBYAJL, 1, [Define if libyajl is present and usable.])
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBYAJL, test "x$with_libyajl" = "xyes")
+# }}}
+
+# --with-libvarnish {{{
+with_libvarnish_cppflags=""
+with_libvarnish_cflags=""
+with_libvarnish_libs=""
+AC_ARG_WITH(libvarnish, [AS_HELP_STRING([--with-libvarnish@<:@=PREFIX@:>@], [Path to libvarnish.])],
+[
+       if test "x$withval" = "xno"
+       then
+               with_libvarnish="no"
+       else if test "x$withval" = "xyes"
+       then
+               with_libvarnish="use_pkgconfig"
+       else if test -d "$with_libvarnish/lib"
+       then
+               AC_MSG_NOTICE([Not checking for libvarnish: Manually configured])
+               with_libvarnish_cflags="-I$withval/include"
+               with_libvarnish_libs="-L$withval/lib -lvarnish -lvarnishcompat -lvarnishapi"
+               with_libvarnish="yes"
+       fi; fi; fi
+],
+[with_libvarnish="use_pkgconfig"])
+
+# configure using pkg-config
+if test "x$with_libvarnish" = "xuse_pkgconfig"
+then
+       if test "x$PKG_CONFIG" = "x"
+       then
+               with_libvarnish="no (Don't have pkg-config)"
+       fi
+fi
+if test "x$with_libvarnish" = "xuse_pkgconfig"
+then
+       AC_MSG_NOTICE([Checking for varnishapi using $PKG_CONFIG])
+       $PKG_CONFIG --exists 'varnishapi' 2>/dev/null
+       if test $? -ne 0
+       then
+               with_libvarnish="no (pkg-config doesn't know varnishapi)"
+       fi
+fi
+if test "x$with_libvarnish" = "xuse_pkgconfig"
+then
+       with_libvarnish_cflags="`$PKG_CONFIG --cflags 'varnishapi'`"
+       if test $? -ne 0
+       then
+               with_libvarnish="no ($PKG_CONFIG failed)"
+       fi
+       with_libvarnish_libs="`$PKG_CONFIG --libs 'varnishapi'`"
+       if test $? -ne 0
+       then
+               with_libvarnish="no ($PKG_CONFIG failed)"
+       fi
+fi
+if test "x$with_libvarnish" = "xuse_pkgconfig"
+then
+       with_libvarnish="yes"
+fi
+
+# with_libvarnish_cflags and with_libvarnish_libs are set up now, let's do
+# the actual checks.
+if test "x$with_libvarnish" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libvarnish_cflags"
+       AC_CHECK_HEADERS(varnish/varnishapi.h, [], [with_libvarnish="no (varnish/varnishapi.h not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+if test "x$with_libvarnish" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       #SAVE_LDFLAGS="$LDFLAGS"
+
+       CPPFLAGS="$CPPFLAGS $with_libvarnish_cflags"
+       #LDFLAGS="$LDFLAGS $with_libvarnish_libs"
+
+       AC_CHECK_LIB(varnishapi, VSL_OpenStats,
+                    [with_libvarnish="yes"],
+                    [with_libvarnish="no (symbol VSL_OpenStats not found)"],
+                    [$with_libvarnish_libs])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+       #LDFLAGS="$SAVE_LDFLAGS"
+fi
+if test "x$with_libvarnish" = "xyes"
+then
+       BUILD_WITH_LIBVARNISH_CFLAGS="$with_libvarnish_cflags"
+       BUILD_WITH_LIBVARNISH_LIBS="$with_libvarnish_libs"
+       AC_SUBST(BUILD_WITH_LIBVARNISH_CFLAGS)
+       AC_SUBST(BUILD_WITH_LIBVARNISH_LIBS)
+fi
+# }}}
+
+# pkg-config --exists 'libxml-2.0'; pkg-config --exists libvirt {{{
+with_libxml2="no (pkg-config isn't available)"
+with_libxml2_cflags=""
+with_libxml2_ldflags=""
+with_libvirt="no (pkg-config isn't available)"
+with_libvirt_cflags=""
+with_libvirt_ldflags=""
+if test "x$PKG_CONFIG" != "x"
+then
+       pkg-config --exists 'libxml-2.0' 2>/dev/null
+       if test "$?" = "0"
+       then
+               with_libxml2="yes"
+       else
+               with_libxml2="no (pkg-config doesn't know libxml-2.0)"
+       fi
+
+       pkg-config --exists libvirt 2>/dev/null
+       if test "$?" = "0"
+       then
+               with_libvirt="yes"
+       else
+               with_libvirt="no (pkg-config doesn't know libvirt)"
+       fi
+fi
+if test "x$with_libxml2" = "xyes"
+then
+       with_libxml2_cflags="`pkg-config --cflags libxml-2.0`"
+       if test $? -ne 0
+       then
+               with_libxml2="no"
+       fi
+       with_libxml2_ldflags="`pkg-config --libs libxml-2.0`"
+       if test $? -ne 0
+       then
+               with_libxml2="no"
+       fi
+fi
+if test "x$with_libxml2" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libxml2_cflags"
+
+       AC_CHECK_HEADERS(libxml/parser.h, [],
+                     [with_libxml2="no (libxml/parser.h not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+if test "x$with_libxml2" = "xyes"
+then
+       SAVE_CFLAGS="$CFLAGS"
+       SAVE_LDFLAGS="$LDFLAGS"
+
+       CFLAGS="$CFLAGS $with_libxml2_cflags"
+       LDFLAGS="$LDFLAGS $with_libxml2_ldflags"
+
+       AC_CHECK_LIB(xml2, xmlXPathEval,
+                    [with_libxml2="yes"],
+                    [with_libxml2="no (symbol xmlXPathEval not found)"])
+
+       CFLAGS="$SAVE_CFLAGS"
+       LDFLAGS="$SAVE_LDFLAGS"
+fi
+dnl Add the right compiler flags and libraries.
+if test "x$with_libxml2" = "xyes"; then
+       BUILD_WITH_LIBXML2_CFLAGS="$with_libxml2_cflags"
+       BUILD_WITH_LIBXML2_LIBS="$with_libxml2_ldflags"
+       AC_SUBST(BUILD_WITH_LIBXML2_CFLAGS)
+       AC_SUBST(BUILD_WITH_LIBXML2_LIBS)
+fi
+if test "x$with_libvirt" = "xyes"
+then
+       with_libvirt_cflags="`pkg-config --cflags libvirt`"
+       if test $? -ne 0
+       then
+               with_libvirt="no"
+       fi
+       with_libvirt_ldflags="`pkg-config --libs libvirt`"
+       if test $? -ne 0
+       then
+               with_libvirt="no"
+       fi
+fi
+if test "x$with_libvirt" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libvirt_cflags"
+
+       AC_CHECK_HEADERS(libvirt/libvirt.h, [],
+                     [with_libvirt="no (libvirt/libvirt.h not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+if test "x$with_libvirt" = "xyes"
+then
+       SAVE_CFLAGS="$CFLAGS"
+       SAVE_LDFLAGS="$LDFLAGS"
+
+       CFLAGS="$CFLAGS $with_libvirt_cflags"
+       LDFLAGS="$LDFLAGS $with_libvirt_ldflags"
+
+       AC_CHECK_LIB(virt, virDomainBlockStats,
+                    [with_libvirt="yes"],
+                    [with_libvirt="no (symbol virDomainBlockStats not found)"])
+
+       CFLAGS="$SAVE_CFLAGS"
+       LDFLAGS="$SAVE_LDFLAGS"
+fi
+dnl Add the right compiler flags and libraries.
+if test "x$with_libvirt" = "xyes"; then
+       BUILD_WITH_LIBVIRT_CFLAGS="$with_libvirt_cflags"
+       BUILD_WITH_LIBVIRT_LIBS="$with_libvirt_ldflags"
+       AC_SUBST(BUILD_WITH_LIBVIRT_CFLAGS)
+       AC_SUBST(BUILD_WITH_LIBVIRT_LIBS)
+fi
+# }}}
+
+# $PKG_CONFIG --exists OpenIPMIpthread {{{
+with_libopenipmipthread="yes"
+with_libopenipmipthread_cflags=""
+with_libopenipmipthread_libs=""
+
+AC_MSG_CHECKING([for pkg-config])
+temp_result="no"
+if test "x$PKG_CONFIG" = "x"
+then
+       with_libopenipmipthread="no"
+       temp_result="no"
+else
+       temp_result="$PKG_CONFIG"
+fi
+AC_MSG_RESULT([$temp_result])
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+       AC_MSG_CHECKING([for libOpenIPMIpthread])
+       $PKG_CONFIG --exists OpenIPMIpthread 2>/dev/null
+       if test "$?" != "0"
+       then
+               with_libopenipmipthread="no (pkg-config doesn't know OpenIPMIpthread)"
+       fi
+       AC_MSG_RESULT([$with_libopenipmipthread])
+fi
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+       AC_MSG_CHECKING([for libOpenIPMIpthread CFLAGS])
+       temp_result="`$PKG_CONFIG --cflags OpenIPMIpthread`"
+       if test "$?" = "0"
+       then
+               with_libopenipmipthread_cflags="$temp_result"
+       else
+               with_libopenipmipthread="no ($PKG_CONFIG --cflags OpenIPMIpthread failed)"
+               temp_result="$PKG_CONFIG --cflags OpenIPMIpthread failed"
+       fi
+       AC_MSG_RESULT([$temp_result])
+fi
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+       AC_MSG_CHECKING([for libOpenIPMIpthread LDFLAGS])
+       temp_result="`$PKG_CONFIG --libs OpenIPMIpthread`"
+       if test "$?" = "0"
+       then
+               with_libopenipmipthread_ldflags="$temp_result"
+       else
+               with_libopenipmipthread="no ($PKG_CONFIG --libs OpenIPMIpthread failed)"
+               temp_result="$PKG_CONFIG --libs OpenIPMIpthread failed"
+       fi
+       AC_MSG_RESULT([$temp_result])
+fi
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libopenipmipthread_cflags"
+
+       AC_CHECK_HEADERS(OpenIPMI/ipmi_smi.h,
+                        [with_libopenipmipthread="yes"],
+                        [with_libopenipmipthread="no (OpenIPMI/ipmi_smi.h not found)"],
+[#include <OpenIPMI/ipmiif.h>
+#include <OpenIPMI/ipmi_err.h>
+#include <OpenIPMI/ipmi_posix.h>
+#include <OpenIPMI/ipmi_conn.h>
+])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+       BUILD_WITH_OPENIPMI_CFLAGS="$with_libopenipmipthread_cflags"
+       BUILD_WITH_OPENIPMI_LIBS="$with_libopenipmipthread_ldflags"
+       AC_SUBST(BUILD_WITH_OPENIPMI_CFLAGS)
+       AC_SUBST(BUILD_WITH_OPENIPMI_LIBS)
+fi
+# }}}
+
+PKG_CHECK_MODULES([LIBNOTIFY], [libnotify],
+               [with_libnotify="yes"],
+               [if test "x$LIBNOTIFY_PKG_ERRORS" = "x"; then
+                        with_libnotify="no"
+                else
+                        with_libnotify="no ($LIBNOTIFY_PKG_ERRORS)"
+                fi])
+
+# Check for enabled/disabled features
+#
+
+# AC_COLLECTD(name, enable/disable, info-text, feature/module)
+# ------------------------------------------------------------
+dnl
+m4_define([my_toupper], [m4_translit([$1], m4_defn([m4_cr_letters]), m4_defn([m4_cr_LETTERS]))])
+dnl
+AC_DEFUN(
+       [AC_COLLECTD],
+       [
+       m4_if([$1], [], [AC_FATAL([AC_COLLECTD([$1], [$2], [$3], [$4]): 1st argument must not be empty])])dnl
+       m4_if(
+               [$2],
+               [enable],
+               [dnl
+               m4_define([EnDis],[disabled])dnl
+               m4_define([YesNo],[no])dnl
+               ],dnl
+               [m4_if(
+                       [$2],
+                       [disable],
+                       [dnl
+                       m4_define([EnDis],[enabled])dnl
+                       m4_define([YesNo],[yes])dnl
+                       ],
+                       [dnl
+                       AC_FATAL([AC_COLLECTD([$1], [$2], [$3], [$4]): 2nd argument must be either enable or disable])dnl
+                       ]dnl
+               )]dnl
+       )dnl
+       m4_if([$3], [feature], [],
+               [m4_if(
+                       [$3], [module], [],
+                       [dnl
+                       AC_FATAL([AC_COLLECTD([$1], [$2], [$3], [$4]): 3rd argument must be either feature or disable])dnl
+                       ]dnl
+               )]dnl
+       )dnl
+       AC_ARG_ENABLE(
+               [$1],
+               AS_HELP_STRING([--$2-$1], [$2 $4 (EnDis by def)]),
+               [],
+               enable_$1='[YesNo]'dnl
+       )# AC_ARG_ENABLE
+if test "x$enable_$1" = "xno"
+then
+       collectd_$1=0
+else
+       if test "x$enable_$1" = "xyes"
+       then
+               collectd_$1=1
+       else
+               AC_MSG_NOTICE([please specify either --enable-$1 or --disable-$1; enabling $1.])
+               collectd_$1=1
+               enable_$1='yes'
+       fi
+fi
+       AC_DEFINE_UNQUOTED([COLLECT_]my_toupper([$1]), [$collectd_$1], [wether or not to enable $3 $4])
+       AM_CONDITIONAL([BUILD_]my_toupper([$3])[_]my_toupper([$1]), [test "x$enable_$1" = "xyes"])dnl
+       ]dnl
+)# AC_COLLECTD(name, enable/disable, info-text, feature/module)
+
+# AC_PLUGIN(name, default, info)
+# ------------------------------------------------------------
+dnl
+AC_DEFUN(
+  [AC_PLUGIN],
+  [
+    enable_plugin="no"
+    force="no"
+    AC_ARG_ENABLE([$1], AC_HELP_STRING([--enable-$1], [$3]),
+    [
+     if test "x$enableval" = "xyes"
+     then
+            enable_plugin="yes"
+     else if test "x$enableval" = "xforce"
+     then
+            enable_plugin="yes"
+            force="yes"
+     else
+            enable_plugin="no (disabled on command line)"
+     fi; fi
+    ],
+    [
+        if test "x$enable_all_plugins" = "xauto"
+        then
+            if test "x$2" = "xyes"
+            then
+                    enable_plugin="yes"
+            else
+                    enable_plugin="no"
+            fi
+        else
+            enable_plugin="$enable_all_plugins"
+        fi
+    ])
+    if test "x$enable_plugin" = "xyes"
+    then
+           if test "x$2" = "xyes" || test "x$force" = "xyes"
+           then
+                   AC_DEFINE([HAVE_PLUGIN_]my_toupper([$1]), 1, [Define to 1 if the $1 plugin is enabled.])
+                   if test "x$2" != "xyes"
+                   then
+                           dependency_warning="yes"
+                   fi
+           else # User passed "yes" but dependency checking yielded "no" => Dependency problem.
+                   dependency_error="yes"
+                   enable_plugin="no (dependency error)"
+           fi
+    fi
+    AM_CONDITIONAL([BUILD_PLUGIN_]my_toupper([$1]), test "x$enable_plugin" = "xyes")
+    enable_$1="$enable_plugin"
+  ]
+)# AC_PLUGIN(name, default, info)
+
+m4_divert_once([HELP_ENABLE], [
+collectd features:])
+# FIXME: Remove these calls to `AC_COLLECTD' and then remove that macro.
+AC_COLLECTD([debug],     [enable],  [feature], [debugging])
+AC_COLLECTD([daemon],    [disable], [feature], [daemon mode])
+AC_COLLECTD([getifaddrs],[enable],  [feature], [getifaddrs under Linux])
+
+dependency_warning="no"
+dependency_error="no"
+
+plugin_ascent="no"
+plugin_battery="no"
+plugin_bind="no"
+plugin_conntrack="no"
+plugin_contextswitch="no"
+plugin_cpu="no"
+plugin_cpufreq="no"
+plugin_curl_json="no"
+plugin_curl_xml="no"
+plugin_df="no"
+plugin_disk="no"
+plugin_entropy="no"
+plugin_interface="no"
+plugin_ipmi="no"
+plugin_ipvs="no"
+plugin_irq="no"
+plugin_libvirt="no"
+plugin_load="no"
+plugin_memory="no"
+plugin_multimeter="no"
+plugin_nfs="no"
+plugin_fscache="no"
+plugin_perl="no"
+plugin_processes="no"
+plugin_protocols="no"
+plugin_serial="no"
+plugin_swap="no"
+plugin_tape="no"
+plugin_tcpconns="no"
+plugin_ted="no"
+plugin_thermal="no"
+plugin_users="no"
+plugin_uptime="no"
+plugin_vmem="no"
+plugin_vserver="no"
+plugin_wireless="no"
+plugin_zfs_arc="no"
+
+# Linux
+if test "x$ac_system" = "xLinux"
+then
+       plugin_battery="yes"
+       plugin_conntrack="yes"
+       plugin_contextswitch="yes"
+       plugin_cpu="yes"
+       plugin_cpufreq="yes"
+       plugin_disk="yes"
+       plugin_entropy="yes"
+       plugin_interface="yes"
+       plugin_irq="yes"
+       plugin_load="yes"
+       plugin_memory="yes"
+       plugin_nfs="yes"
+       plugin_fscache="yes"
+       plugin_processes="yes"
+       plugin_protocols="yes"
+       plugin_serial="yes"
+       plugin_swap="yes"
+       plugin_tcpconns="yes"
+       plugin_thermal="yes"
+       plugin_uptime="yes"
+       plugin_vmem="yes"
+       plugin_vserver="yes"
+       plugin_wireless="yes"
+
+       if test "x$have_linux_ip_vs_h" = "xyes" || test "x$have_net_ip_vs_h" = "xyes" || test "x$have_ip_vs_h" = "xyes"
+       then
+               plugin_ipvs="yes"
+       fi
+fi
+
+if test "x$ac_system" = "xOpenBSD"
+then
+       plugin_tcpconns="yes"
+fi
+
+# Mac OS X devices
+if test "x$with_libiokit" = "xyes"
+then
+       plugin_battery="yes"
+       plugin_disk="yes"
+fi
+
+# AIX
+
+if test "x$ac_system" = "xAIX"
+then
+        plugin_tcpconns="yes"
+fi
+
+if test "x$with_perfstat" = "xyes"
+then
+       plugin_cpu="yes"
+       plugin_disk="yes"
+       plugin_memory="yes"
+       plugin_swap="yes"
+       plugin_interface="yes"
+       plugin_load="yes"
+fi
+
+if test "x$with_procinfo" = "xyes"
+then
+       plugin_processes="yes"
+fi
+
+# Solaris
+if test "x$with_kstat" = "xyes"
+then
+       plugin_uptime="yes"
+       plugin_zfs_arc="yes"
+fi
+
+if test "x$with_devinfo$with_kstat" = "xyesyes"
+then
+       plugin_cpu="yes"
+       plugin_disk="yes"
+       plugin_interface="yes"
+       plugin_memory="yes"
+       plugin_tape="yes"
+fi
+
+# libstatgrab
+if test "x$with_libstatgrab" = "xyes"
+then
+       plugin_cpu="yes"
+       plugin_disk="yes"
+       plugin_interface="yes"
+       plugin_load="yes"
+       plugin_memory="yes"
+       plugin_swap="yes"
+       plugin_users="yes"
+fi
+
+if test "x$with_libcurl" = "xyes" && test "x$with_libxml2" = "xyes"
+then
+       plugin_ascent="yes"
+       if test "x$have_strptime" = "xyes"
+       then
+               plugin_bind="yes"
+       fi
+fi
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+       plugin_ipmi="yes"
+fi
+
+if test "x$with_libcurl" = "xyes" && test "x$with_libyajl" = "xyes"
+then
+       plugin_curl_json="yes"
+fi
+
+if test "x$with_libcurl" = "xyes" && test "x$with_libxml2" = "xyes"
+then
+       plugin_curl_xml="yes"
+fi
+
+if test "x$have_processor_info" = "xyes"
+then
+       plugin_cpu="yes"
+fi
+if test "x$have_sysctl" = "xyes"
+then
+       plugin_cpu="yes"
+       plugin_memory="yes"
+       plugin_uptime="yes"
+       if test "x$ac_system" = "xDarwin"
+       then
+               plugin_swap="yes"
+       fi
+fi
+if test "x$have_sysctlbyname" = "xyes"
+then
+       plugin_contextswitch="yes"
+       plugin_cpu="yes"
+       plugin_memory="yes"
+       plugin_tcpconns="yes"
+fi
+
+# Df plugin: Check if we know how to determine mount points first.
+#if test "x$have_listmntent" = "xyes"; then
+#      plugin_df="yes"
+#fi
+if test "x$have_getvfsstat" = "xyes" || test "x$have_getfsstat" = "xyes"
+then
+       plugin_df="yes"
+fi
+if test "x$c_cv_have_two_getmntent" = "xyes" || test "x$have_getmntent" = "xgen" || test "x$have_getmntent" = "xsun"
+then
+       plugin_df="yes"
+fi
+#if test "x$have_getmntent" = "xseq"
+#then
+#      plugin_df="yes"
+#fi
+if test "x$c_cv_have_one_getmntent" = "xyes"
+then
+       plugin_df="yes"
+fi
+
+# Df plugin: Check if we have either `statfs' or `statvfs' second.
+if test "x$plugin_df" = "xyes"
+then
+       plugin_df="no"
+       if test "x$have_statfs" = "xyes"
+       then
+               plugin_df="yes"
+       fi
+       if test "x$have_statvfs" = "xyes"
+       then
+               plugin_df="yes"
+       fi
+fi
+
+if test "x$have_getifaddrs" = "xyes"
+then
+       plugin_interface="yes"
+fi
+
+if test "x$with_libxml2" = "xyes" && test "x$with_libvirt" = "xyes"
+then
+       plugin_libvirt="yes"
+fi
+
+if test "x$have_getloadavg" = "xyes"
+then
+       plugin_load="yes"
+fi
+
+if test "x$c_cv_have_libperl$c_cv_have_perl_ithreads" = "xyesyes"
+then
+       plugin_perl="yes"
+fi
+
+# Mac OS X memory interface
+if test "x$have_host_statistics" = "xyes"
+then
+       plugin_memory="yes"
+fi
+
+if test "x$have_termios_h" = "xyes"
+then
+       plugin_multimeter="yes"
+       plugin_ted="yes"
+fi
+
+if test "x$have_thread_info" = "xyes"
+then
+       plugin_processes="yes"
+fi
+
+if test "x$with_kvm_getprocs" = "xyes" && test "x$have_struct_kinfo_proc_freebsd" = "xyes"
+then
+       plugin_processes="yes"
+fi
+
+if test "x$with_kvm_getswapinfo" = "xyes"
+then
+       plugin_swap="yes"
+fi
+
+if test "x$have_swapctl" = "xyes" && test "x$c_cv_have_swapctl_two_args" = "xyes"
+then
+       plugin_swap="yes"
+fi
+
+if test "x$with_kvm_openfiles$with_kvm_nlist" = "xyesyes"
+then
+       plugin_tcpconns="yes"
+fi
+
+if test "x$have_getutent" = "xyes"
+then
+       plugin_users="yes"
+fi
+if test "x$have_getutxent" = "xyes"
+then
+       plugin_users="yes"
+fi
+
+m4_divert_once([HELP_ENABLE], [
+collectd plugins:])
+
+AC_ARG_ENABLE([all-plugins],
+               AC_HELP_STRING([--enable-all-plugins],
+                               [enable all plugins (auto by def)]),
+               [
+                if test "x$enableval" = "xyes"
+                then
+                        enable_all_plugins="yes"
+                else if test "x$enableval" = "xauto"
+                then
+                        enable_all_plugins="auto"
+                else
+                        enable_all_plugins="no"
+                fi; fi
+               ],
+               [enable_all_plugins="auto"])
+
+m4_divert_once([HELP_ENABLE], [])
+
+AC_PLUGIN([amqp],        [$with_librabbitmq],  [AMQP output plugin])
+AC_PLUGIN([apache],      [$with_libcurl],      [Apache httpd statistics])
+AC_PLUGIN([apcups],      [yes],                [Statistics of UPSes by APC])
+AC_PLUGIN([apple_sensors], [$with_libiokit],   [Apple's hardware sensors])
+AC_PLUGIN([ascent],      [$plugin_ascent],     [AscentEmu player statistics])
+AC_PLUGIN([battery],     [$plugin_battery],    [Battery statistics])
+AC_PLUGIN([bind],        [$plugin_bind],       [ISC Bind nameserver statistics])
+AC_PLUGIN([conntrack],   [$plugin_conntrack],  [nf_conntrack statistics])
+AC_PLUGIN([contextswitch], [$plugin_contextswitch], [context switch statistics])
+AC_PLUGIN([cpufreq],     [$plugin_cpufreq],    [CPU frequency statistics])
+AC_PLUGIN([cpu],         [$plugin_cpu],        [CPU usage statistics])
+AC_PLUGIN([csv],         [yes],                [CSV output plugin])
+AC_PLUGIN([curl],        [$with_libcurl],      [CURL generic web statistics])
+AC_PLUGIN([curl_json],   [$plugin_curl_json],    [CouchDB statistics])
+AC_PLUGIN([curl_xml],   [$plugin_curl_xml],    [CURL generic xml statistics])
+AC_PLUGIN([dbi],         [$with_libdbi],       [General database statistics])
+AC_PLUGIN([df],          [$plugin_df],         [Filesystem usage statistics])
+AC_PLUGIN([disk],        [$plugin_disk],       [Disk usage statistics])
+AC_PLUGIN([dns],         [$with_libpcap],      [DNS traffic analysis])
+AC_PLUGIN([email],       [yes],                [EMail statistics])
+AC_PLUGIN([entropy],     [$plugin_entropy],    [Entropy statistics])
+AC_PLUGIN([exec],        [yes],                [Execution of external programs])
+AC_PLUGIN([filecount],   [yes],                [Count files in directories])
+AC_PLUGIN([fscache],     [$plugin_fscache],    [fscache statistics])
+AC_PLUGIN([gmond],       [$with_libganglia],   [Ganglia plugin])
+AC_PLUGIN([hddtemp],     [yes],                [Query hddtempd])
+AC_PLUGIN([interface],   [$plugin_interface],  [Interface traffic statistics])
+AC_PLUGIN([ipmi],        [$plugin_ipmi],       [IPMI sensor statistics])
+AC_PLUGIN([iptables],    [$with_libiptc],      [IPTables rule counters])
+AC_PLUGIN([ipvs],        [$plugin_ipvs],       [IPVS connection statistics])
+AC_PLUGIN([irq],         [$plugin_irq],        [IRQ statistics])
+AC_PLUGIN([java],        [$with_java],         [Embed the Java Virtual Machine])
+AC_PLUGIN([libvirt],     [$plugin_libvirt],    [Virtual machine statistics])
+AC_PLUGIN([load],        [$plugin_load],       [System load])
+AC_PLUGIN([logfile],     [yes],                [File logging plugin])
+AC_PLUGIN([lpar],        [$with_perfstat],     [AIX logical partitions statistics])
+AC_PLUGIN([madwifi],     [$have_linux_wireless_h], [Madwifi wireless statistics])
+AC_PLUGIN([match_empty_counter], [yes],        [The empty counter match])
+AC_PLUGIN([match_hashed], [yes],               [The hashed match])
+AC_PLUGIN([match_regex], [yes],                [The regex match])
+AC_PLUGIN([match_timediff], [yes],             [The timediff match])
+AC_PLUGIN([match_value], [yes],                [The value match])
+AC_PLUGIN([mbmon],       [yes],                [Query mbmond])
+AC_PLUGIN([memcachec],   [$with_libmemcached], [memcachec statistics])
+AC_PLUGIN([memcached],   [yes],                [memcached statistics])
+AC_PLUGIN([memory],      [$plugin_memory],     [Memory usage])
+AC_PLUGIN([modbus],      [$with_libmodbus],    [Modbus plugin])
+AC_PLUGIN([multimeter],  [$plugin_multimeter], [Read multimeter values])
+AC_PLUGIN([mysql],       [$with_libmysql],     [MySQL statistics])
+AC_PLUGIN([netapp],      [$with_libnetapp],    [NetApp plugin])
+AC_PLUGIN([netlink],     [$with_libnetlink],   [Enhanced Linux network statistics])
+AC_PLUGIN([network],     [yes],                [Network communication plugin])
+AC_PLUGIN([nfs],         [$plugin_nfs],        [NFS statistics])
+AC_PLUGIN([nginx],       [$with_libcurl],      [nginx statistics])
+AC_PLUGIN([notify_desktop], [$with_libnotify], [Desktop notifications])
+AC_PLUGIN([notify_email], [$with_libesmtp],    [Email notifier])
+AC_PLUGIN([ntpd],        [yes],                [NTPd statistics])
+AC_PLUGIN([nut],         [$with_libupsclient], [Network UPS tools statistics])
+AC_PLUGIN([olsrd],       [yes],                [olsrd statistics])
+AC_PLUGIN([onewire],     [$with_libowcapi],    [OneWire sensor statistics])
+AC_PLUGIN([openvpn],     [yes],                [OpenVPN client statistics])
+AC_PLUGIN([oracle],      [$with_oracle],       [Oracle plugin])
+AC_PLUGIN([perl],        [$plugin_perl],       [Embed a Perl interpreter])
+# FIXME: Check for libevent, too.
+AC_PLUGIN([pinba],       [$have_protoc_c],     [Pinba statistics])
+AC_PLUGIN([ping],        [$with_liboping],     [Network latency statistics])
+AC_PLUGIN([postgresql],  [$with_libpq],        [PostgreSQL database statistics])
+AC_PLUGIN([powerdns],    [yes],                [PowerDNS statistics])
+AC_PLUGIN([processes],   [$plugin_processes],  [Process statistics])
+AC_PLUGIN([protocols],   [$plugin_protocols],  [Protocol (IP, TCP, ...) statistics])
+AC_PLUGIN([python],      [$with_python],       [Embed a Python interpreter])
+AC_PLUGIN([redis],       [$with_libcredis],    [Redis plugin])
+AC_PLUGIN([routeros],    [$with_librouteros],  [RouterOS plugin])
+AC_PLUGIN([rrdcached],   [$librrd_rrdc_update], [RRDTool output plugin])
+AC_PLUGIN([rrdtool],     [$with_librrd],       [RRDTool output plugin])
+AC_PLUGIN([sensors],     [$with_libsensors],   [lm_sensors statistics])
+AC_PLUGIN([serial],      [$plugin_serial],     [serial port traffic])
+AC_PLUGIN([snmp],        [$with_libnetsnmp],   [SNMP querying plugin])
+AC_PLUGIN([swap],        [$plugin_swap],       [Swap usage statistics])
+AC_PLUGIN([syslog],      [$have_syslog],       [Syslog logging plugin])
+AC_PLUGIN([table],       [yes],                [Parsing of tabular data])
+AC_PLUGIN([tail],        [yes],                [Parsing of logfiles])
+AC_PLUGIN([tape],        [$plugin_tape],       [Tape drive statistics])
+AC_PLUGIN([target_notification], [yes],        [The notification target])
+AC_PLUGIN([target_replace], [yes],             [The replace target])
+AC_PLUGIN([target_scale],[yes],                [The scale target])
+AC_PLUGIN([target_set],  [yes],                [The set target])
+AC_PLUGIN([target_v5upgrade], [yes],           [The v5upgrade target])
+AC_PLUGIN([tcpconns],    [$plugin_tcpconns],   [TCP connection statistics])
+AC_PLUGIN([teamspeak2],  [yes],                [TeamSpeak2 server statistics])
+AC_PLUGIN([ted],         [$plugin_ted],        [Read The Energy Detective values])
+AC_PLUGIN([thermal],     [$plugin_thermal],    [Linux ACPI thermal zone statistics])
+AC_PLUGIN([threshold],   [yes],                [Threshold checking plugin])
+AC_PLUGIN([tokyotyrant], [$with_libtokyotyrant],  [TokyoTyrant database statistics])
+AC_PLUGIN([unixsock],    [yes],                [Unixsock communication plugin])
+AC_PLUGIN([uptime],      [$plugin_uptime],     [Uptime statistics])
+AC_PLUGIN([users],       [$plugin_users],      [User statistics])
+AC_PLUGIN([uuid],        [yes],                [UUID as hostname plugin])
+AC_PLUGIN([varnish],     [$with_libvarnish],   [Varnish cache statistics])
+AC_PLUGIN([vmem],        [$plugin_vmem],       [Virtual memory statistics])
+AC_PLUGIN([vserver],     [$plugin_vserver],    [Linux VServer statistics])
+AC_PLUGIN([wireless],    [$plugin_wireless],   [Wireless statistics])
+AC_PLUGIN([write_http],  [$with_libcurl],      [HTTP output plugin])
+AC_PLUGIN([write_redis], [$with_libcredis],    [Redis output plugin])
+AC_PLUGIN([write_mongodb], [$with_libmongoc],  [MongoDB output plugin])
+AC_PLUGIN([xmms],        [$with_libxmms],      [XMMS statistics])
+AC_PLUGIN([zfs_arc],     [$plugin_zfs_arc],    [ZFS ARC statistics])
+
+dnl Default configuration file
+# Load either syslog or logfile
+LOAD_PLUGIN_SYSLOG=""
+LOAD_PLUGIN_LOGFILE=""
+
+AC_MSG_CHECKING([which default log plugin to load])
+default_log_plugin="none"
+if test "x$enable_syslog" = "xyes"
+then
+       default_log_plugin="syslog"
+else
+       LOAD_PLUGIN_SYSLOG="##"
+fi
+
+if test "x$enable_logfile" = "xyes"
+then
+       if test "x$default_log_plugin" = "xnone"
+       then
+               default_log_plugin="logfile"
+       else
+               LOAD_PLUGIN_LOGFILE="#"
+       fi
+else
+       LOAD_PLUGIN_LOGFILE="##"
+fi
+AC_MSG_RESULT([$default_log_plugin])
+
+AC_SUBST(LOAD_PLUGIN_SYSLOG)
+AC_SUBST(LOAD_PLUGIN_LOGFILE)
+
+DEFAULT_LOG_LEVEL="info"
+if test "x$enable_debug" = "xyes"
+then
+       DEFAULT_LOG_LEVEL="debug"
+fi
+AC_SUBST(DEFAULT_LOG_LEVEL)
+
+# Load only one of rrdtool, network, csv in the default config.
+LOAD_PLUGIN_RRDTOOL=""
+LOAD_PLUGIN_NETWORK=""
+LOAD_PLUGIN_CSV=""
+
+AC_MSG_CHECKING([which default write plugin to load])
+default_write_plugin="none"
+if test "x$enable_rrdtool" = "xyes"
+then
+       default_write_plugin="rrdtool"
+else
+       LOAD_PLUGIN_RRDTOOL="##"
+fi
+
+if test "x$enable_network" = "xyes"
+then
+       if test "x$default_write_plugin" = "xnone"
+       then
+               default_write_plugin="network"
+       else
+               LOAD_PLUGIN_NETWORK="#"
+       fi
+else
+       LOAD_PLUGIN_NETWORK="##"
+fi
+
+if test "x$enable_csv" = "xyes"
+then
+       if test "x$default_write_plugin" = "xnone"
+       then
+               default_write_plugin="csv"
+       else
+               LOAD_PLUGIN_CSV="#"
+       fi
+else
+       LOAD_PLUGIN_CSV="##"
+fi
+AC_MSG_RESULT([$default_write_plugin])
+
+AC_SUBST(LOAD_PLUGIN_RRDTOOL)
+AC_SUBST(LOAD_PLUGIN_NETWORK)
+AC_SUBST(LOAD_PLUGIN_CSV)
+
+dnl ip_vs.h
+if test "x$ac_system" = "xLinux" \
+       && test "x$have_linux_ip_vs_h$have_net_ip_vs_h$have_ip_vs_h" = "xnonono"
+then
+       enable_ipvs="$enable_ipvs (ip_vs.h not found)"
+fi
+
+if test "x$ip_vs_h_needs_kernel_cflags" = "xyes"
+then
+       enable_ipvs="$enable_ipvs (needs $KERNEL_CFLAGS)"
+fi
+
+dnl Perl bindings
+AC_ARG_WITH(perl-bindings, [AS_HELP_STRING([--with-perl-bindings@<:@=OPTIONS@:>@], [Options passed to "perl Makefile.PL".])],
+[
+       if test "x$withval" != "xno" && test "x$withval" != "xyes"
+       then
+               PERL_BINDINGS_OPTIONS="$withval"
+               with_perl_bindings="yes"
+       else
+               PERL_BINDINGS_OPTIONS=""
+               with_perl_bindings="$withval"
+       fi
+],
+[
+       PERL_BINDINGS_OPTIONS=""
+       if test -n "$perl_interpreter"
+       then
+               with_perl_bindings="yes"
+       else
+               with_perl_bindings="no (no perl interpreter found)"
+       fi
+])
+if test "x$with_perl_bindings" = "xyes"
+then
+       PERL_BINDINGS="perl"
+else
+       PERL_BINDINGS=""
+fi
+AC_SUBST(PERL_BINDINGS)
+AC_SUBST(PERL_BINDINGS_OPTIONS)
+
+dnl libcollectdclient
+LCC_VERSION_MAJOR=`echo $PACKAGE_VERSION | cut -d'.' -f1`
+LCC_VERSION_MINOR=`echo $PACKAGE_VERSION | cut -d'.' -f2`
+LCC_VERSION_PATCH=`echo $PACKAGE_VERSION | cut -d'.' -f3`
+
+LCC_VERSION_EXTRA=`echo $PACKAGE_VERSION | cut -d'.' -f4-`
+
+LCC_VERSION_STRING="$LCC_VERSION_MAJOR.$LCC_VERSION_MINOR.$LCC_VERSION_PATCH"
+
+AC_SUBST(LCC_VERSION_MAJOR)
+AC_SUBST(LCC_VERSION_MINOR)
+AC_SUBST(LCC_VERSION_PATCH)
+AC_SUBST(LCC_VERSION_EXTRA)
+AC_SUBST(LCC_VERSION_STRING)
+
+AC_CONFIG_FILES(src/libcollectdclient/lcc_features.h)
+
+AC_OUTPUT(Makefile src/Makefile src/collectd.conf src/libcollectdclient/Makefile src/libcollectdclient/libcollectdclient.pc src/liboconfig/Makefile bindings/Makefile bindings/java/Makefile)
+
+if test "x$with_librrd" = "xyes" \
+       && test "x$librrd_threadsafe" != "xyes"
+then
+       with_librrd="yes (warning: librrd is not thread-safe)"
+fi
+
+if test "x$with_libperl" = "xyes"
+then
+       with_libperl="yes (version `$perl_interpreter -MConfig -e 'print $Config{version};'`)"
+else
+       enable_perl="no (needs libperl)"
+fi
+
+if test "x$enable_perl" = "xno" && test "x$c_cv_have_perl_ithreads" = "xno"
+then
+       enable_perl="no (libperl doesn't support ithreads)"
+fi
+
+if test "x$with_perl_bindings" = "xyes" \
+       && test "x$PERL_BINDINGS_OPTIONS" != "x"
+then
+       with_perl_bindings="yes ($PERL_BINDINGS_OPTIONS)"
+fi
+
+cat <<EOF;
+
+Configuration:
+  Libraries:
+    libcurl . . . . . . . $with_libcurl
+    libdbi  . . . . . . . $with_libdbi
+    libcredis . . . . . . $with_libcredis
+    libesmtp  . . . . . . $with_libesmtp
+    libganglia  . . . . . $with_libganglia
+    libgcrypt . . . . . . $with_libgcrypt
+    libiokit  . . . . . . $with_libiokit
+    libiptc . . . . . . . $with_libiptc
+    libjvm  . . . . . . . $with_java
+    libkstat  . . . . . . $with_kstat
+    libkvm  . . . . . . . $with_libkvm
+    libmemcached  . . . . $with_libmemcached
+    libmodbus . . . . . . $with_libmodbus
+    libmysql  . . . . . . $with_libmysql
+    libnetapp . . . . . . $with_libnetapp
+    libnetlink  . . . . . $with_libnetlink
+    libnetsnmp  . . . . . $with_libnetsnmp
+    libnotify . . . . . . $with_libnotify
+    liboconfig  . . . . . $with_liboconfig
+    libopenipmi . . . . . $with_libopenipmipthread
+    liboping  . . . . . . $with_liboping
+    libpcap . . . . . . . $with_libpcap
+    libperfstat . . . . . $with_perfstat
+    libperl . . . . . . . $with_libperl
+    libpq . . . . . . . . $with_libpq
+    libpthread  . . . . . $with_libpthread
+    librabbitmq . . . . . $with_librabbitmq
+    librouteros . . . . . $with_librouteros
+    librrd  . . . . . . . $with_librrd
+    libsensors  . . . . . $with_libsensors
+    libstatgrab . . . . . $with_libstatgrab
+    libtokyotyrant  . . . $with_libtokyotyrant
+    libupsclient  . . . . $with_libupsclient
+    libvarnish  . . . . . $with_libvarnish
+    libvirt . . . . . . . $with_libvirt
+    libxml2 . . . . . . . $with_libxml2
+    libxmms . . . . . . . $with_libxmms
+    libyajl . . . . . . . $with_libyajl
+    libevent  . . . . . . $with_libevent
+    protobuf-c  . . . . . $have_protoc_c
+    oracle  . . . . . . . $with_oracle
+    python  . . . . . . . $with_python
+
+  Features:
+    daemon mode . . . . . $enable_daemon
+    debug . . . . . . . . $enable_debug
+
+  Bindings:
+    perl  . . . . . . . . $with_perl_bindings
+
+  Modules:
+    amqp    . . . . . . . $enable_amqp
+    apache  . . . . . . . $enable_apache
+    apcups  . . . . . . . $enable_apcups
+    apple_sensors . . . . $enable_apple_sensors
+    ascent  . . . . . . . $enable_ascent
+    battery . . . . . . . $enable_battery
+    bind  . . . . . . . . $enable_bind
+    conntrack . . . . . . $enable_conntrack
+    contextswitch . . . . $enable_contextswitch
+    cpu . . . . . . . . . $enable_cpu
+    cpufreq . . . . . . . $enable_cpufreq
+    csv . . . . . . . . . $enable_csv
+    curl  . . . . . . . . $enable_curl
+    curl_json . . . . . . $enable_curl_json
+    curl_xml  . . . . . . $enable_curl_xml
+    dbi . . . . . . . . . $enable_dbi
+    df  . . . . . . . . . $enable_df
+    disk  . . . . . . . . $enable_disk
+    dns . . . . . . . . . $enable_dns
+    email . . . . . . . . $enable_email
+    entropy . . . . . . . $enable_entropy
+    exec  . . . . . . . . $enable_exec
+    filecount . . . . . . $enable_filecount
+    fscache . . . . . . . $enable_fscache
+    gmond . . . . . . . . $enable_gmond
+    hddtemp . . . . . . . $enable_hddtemp
+    interface . . . . . . $enable_interface
+    ipmi  . . . . . . . . $enable_ipmi
+    iptables  . . . . . . $enable_iptables
+    ipvs  . . . . . . . . $enable_ipvs
+    irq . . . . . . . . . $enable_irq
+    java  . . . . . . . . $enable_java
+    libvirt . . . . . . . $enable_libvirt
+    load  . . . . . . . . $enable_load
+    logfile . . . . . . . $enable_logfile
+    lpar... . . . . . . . $enable_lpar
+    madwifi . . . . . . . $enable_madwifi
+    match_empty_counter . $enable_match_empty_counter
+    match_hashed  . . . . $enable_match_hashed
+    match_regex . . . . . $enable_match_regex
+    match_timediff  . . . $enable_match_timediff
+    match_value . . . . . $enable_match_value
+    mbmon . . . . . . . . $enable_mbmon
+    memcachec . . . . . . $enable_memcachec
+    memcached . . . . . . $enable_memcached
+    memory  . . . . . . . $enable_memory
+    modbus  . . . . . . . $enable_modbus
+    multimeter  . . . . . $enable_multimeter
+    mysql . . . . . . . . $enable_mysql
+    netapp  . . . . . . . $enable_netapp
+    netlink . . . . . . . $enable_netlink
+    network . . . . . . . $enable_network
+    nfs . . . . . . . . . $enable_nfs
+    nginx . . . . . . . . $enable_nginx
+    notify_desktop  . . . $enable_notify_desktop
+    notify_email  . . . . $enable_notify_email
+    ntpd  . . . . . . . . $enable_ntpd
+    nut . . . . . . . . . $enable_nut
+    olsrd . . . . . . . . $enable_olsrd
+    onewire . . . . . . . $enable_onewire
+    openvpn . . . . . . . $enable_openvpn
+    oracle  . . . . . . . $enable_oracle
+    perl  . . . . . . . . $enable_perl
+    pinba . . . . . . . . $enable_pinba
+    ping  . . . . . . . . $enable_ping
+    postgresql  . . . . . $enable_postgresql
+    powerdns  . . . . . . $enable_powerdns
+    processes . . . . . . $enable_processes
+    protocols . . . . . . $enable_protocols
+    python  . . . . . . . $enable_python
+    redis . . . . . . . . $enable_redis
+    routeros  . . . . . . $enable_routeros
+    rrdcached . . . . . . $enable_rrdcached
+    rrdtool . . . . . . . $enable_rrdtool
+    sensors . . . . . . . $enable_sensors
+    serial  . . . . . . . $enable_serial
+    snmp  . . . . . . . . $enable_snmp
+    swap  . . . . . . . . $enable_swap
+    syslog  . . . . . . . $enable_syslog
+    table . . . . . . . . $enable_table
+    tail  . . . . . . . . $enable_tail
+    tape  . . . . . . . . $enable_tape
+    target_notification . $enable_target_notification
+    target_replace  . . . $enable_target_replace
+    target_scale  . . . . $enable_target_scale
+    target_set  . . . . . $enable_target_set
+    target_v5upgrade  . . $enable_target_v5upgrade
+    tcpconns  . . . . . . $enable_tcpconns
+    teamspeak2  . . . . . $enable_teamspeak2
+    ted . . . . . . . . . $enable_ted
+    thermal . . . . . . . $enable_thermal
+    threshold . . . . . . $enable_threshold
+    tokyotyrant . . . . . $enable_tokyotyrant
+    unixsock  . . . . . . $enable_unixsock
+    uptime  . . . . . . . $enable_uptime
+    users . . . . . . . . $enable_users
+    uuid  . . . . . . . . $enable_uuid
+    varnish . . . . . . . $enable_varnish
+    vmem  . . . . . . . . $enable_vmem
+    vserver . . . . . . . $enable_vserver
+    wireless  . . . . . . $enable_wireless
+    write_http  . . . . . $enable_write_http
+    write_redis . . . . . $enable_write_redis
+    write_mongodb . . . . $enable_write_mongodb
+    xmms  . . . . . . . . $enable_xmms
+    zfs_arc . . . . . . . $enable_zfs_arc
+
+EOF
+
+if test "x$dependency_error" = "xyes"; then
+       AC_MSG_ERROR("Some plugins are missing dependencies - see the summary above for details")
+fi
+
+if test "x$dependency_warning" = "xyes"; then
+       AC_MSG_WARN("Some plugins seem to have missing dependencies but have been enabled forcibly - see the summary above for details")
+fi
+
+# vim: set fdm=marker :
diff --git a/contrib/GenericJMX.conf b/contrib/GenericJMX.conf
new file mode 100644 (file)
index 0000000..1d3fe56
--- /dev/null
@@ -0,0 +1,245 @@
+# contrib/GenericJMX.conf
+# -----------------------
+#
+# This is an example config file for the ‘GenericJMX’ plugin, a plugin written
+# in Java to receive values via the “Java Management Extensions” (JMX). The
+# plugin can be found in the
+#   bindings/java/org/collectd/java/
+# directory of the source distribution.
+#
+# This sample config defines a couple of <MBean /> blocks which query MBeans
+# provided by the JVM itself, i. e. which should be available for all Java
+# processes. The following MBean blocks are defined:
+#
+#   +-------------------+------------------------------------------------+
+#   ! Name              ! Description                                    !
+#   +-------------------+------------------------------------------------+
+#   ! classes           ! Number of classes being loaded.                !
+#   ! compilation       ! Time spent by the JVM compiling or optimizing. !
+#   ! garbage_collector ! Number of garbage collections and time spent.  !
+#   ! memory            ! Generic heap/nonheap memory usage.             !
+#   ! memory_pool       ! Memory usage by memory pool.                   !
+#   +-------------------+------------------------------------------------+
+#
+<Plugin "java">
+  LoadPlugin "org.collectd.java.GenericJMX"
+
+  <Plugin "GenericJMX">
+    ################
+    # MBean blocks #
+    ################
+    # Number of classes being loaded.
+    <MBean "classes">
+      ObjectName "java.lang:type=ClassLoading"
+      #InstancePrefix ""
+      #InstanceFrom ""
+
+      <Value>
+        Type "gauge"
+        InstancePrefix "loaded_classes"
+        #InstanceFrom ""
+        Table false
+        Attribute "LoadedClassCount"
+      </Value>
+    </MBean>
+
+    # Time spent by the JVM compiling or optimizing.
+    <MBean "compilation">
+      ObjectName "java.lang:type=Compilation"
+      #InstancePrefix ""
+      #InstanceFrom ""
+
+      <Value>
+        Type "total_time_in_ms"
+        InstancePrefix "compilation_time"
+        #InstanceFrom ""
+        Table false
+        Attribute "TotalCompilationTime"
+      </Value>
+    </MBean>
+
+    # Garbage collector information
+    <MBean "garbage_collector">
+      ObjectName "java.lang:type=GarbageCollector,*"
+      InstancePrefix "gc-"
+      InstanceFrom "name"
+
+      <Value>
+        Type "invocations"
+        #InstancePrefix ""
+        #InstanceFrom ""
+        Table false
+        Attribute "CollectionCount"
+      </Value>
+
+      <Value>
+        Type "total_time_in_ms"
+        InstancePrefix "collection_time"
+        #InstanceFrom ""
+        Table false
+        Attribute "CollectionTime"
+      </Value>
+
+#      # Not that useful, therefore commented out.
+#      <Value>
+#        Type "threads"
+#        #InstancePrefix ""
+#        #InstanceFrom ""
+#        Table false
+#        # Demonstration how to access composite types
+#        Attribute "LastGcInfo.GcThreadCount"
+#      </Value>
+    </MBean>
+
+    ######################################
+    # Define the "jmx_memory" type as:   #
+    #   jmx_memory  value:GAUGE:0:U      #
+    # See types.db(5) for details.       #
+    ######################################
+
+    # Generic heap/nonheap memory usage.
+    <MBean "memory">
+      ObjectName "java.lang:type=Memory"
+      #InstanceFrom ""
+      InstancePrefix "memory"
+
+      # Creates four values: committed, init, max, used
+      <Value>
+        Type "jmx_memory"
+        #InstancePrefix ""
+        #InstanceFrom ""
+        Table true
+        Attribute "HeapMemoryUsage"
+        InstancePrefix "heap-"
+      </Value>
+
+      # Creates four values: committed, init, max, used
+      <Value>
+        Type "jmx_memory"
+        #InstancePrefix ""
+        #InstanceFrom ""
+        Table true
+        Attribute "NonHeapMemoryUsage"
+        InstancePrefix "nonheap-"
+      </Value>
+    </MBean>
+
+    # Memory usage by memory pool.
+    <MBean "memory_pool">
+      ObjectName "java.lang:type=MemoryPool,*"
+      InstancePrefix "memory_pool-"
+      InstanceFrom "name"
+
+      <Value>
+        Type "jmx_memory"
+        #InstancePrefix ""
+        #InstanceFrom ""
+        Table true
+        Attribute "Usage"
+      </Value>
+    </MBean>
+
+    ### MBeans by Catalina / Tomcat ###
+    # The global request processor (summary for each request processor)
+    <MBean "catalina/global_request_processor">
+      ObjectName "Catalina:type=GlobalRequestProcessor,*"
+      InstancePrefix "request_processor-"
+      InstanceFrom "name"
+
+      <Value>
+        Type "io_octets"
+        InstancePrefix "global"
+        #InstanceFrom ""
+        Table false
+        Attribute "bytesReceived"
+        Attribute "bytesSent"
+      </Value>
+
+      <Value>
+        Type "total_requests"
+        InstancePrefix "global"
+        #InstanceFrom ""
+        Table false
+        Attribute "requestCount"
+      </Value>
+
+      <Value>
+        Type "total_time_in_ms"
+        InstancePrefix "global-processing"
+        #InstanceFrom ""
+        Table false
+        Attribute "processingTime"
+      </Value>
+    </MBean>
+
+    # Details for each  request processor
+    <MBean "catalina/detailed_request_processor">
+      ObjectName "Catalina:type=RequestProcessor,*"
+      InstancePrefix "request_processor-"
+      InstanceFrom "worker"
+
+      <Value>
+        Type "io_octets"
+        #InstancePrefix ""
+        InstanceFrom "name"
+        Table false
+        Attribute "bytesReceived"
+        Attribute "bytesSent"
+      </Value>
+
+      <Value>
+        Type "total_requests"
+        #InstancePrefix ""
+        InstanceFrom "name"
+        Table false
+        Attribute "requestCount"
+      </Value>
+
+      <Value>
+        Type "total_time_in_ms"
+        InstancePrefix "processing-"
+        InstanceFrom "name"
+        Table false
+        Attribute "processingTime"
+      </Value>
+    </MBean>
+
+    # Thread pool
+    <MBean "catalina/thread_pool">
+      ObjectName "Catalina:type=ThreadPool,*"
+      InstancePrefix "request_processor-"
+      InstanceFrom "name"
+
+      <Value>
+        Type "threads"
+        InstancePrefix "total"
+        #InstanceFrom ""
+        Table false
+        Attribute "currentThreadCount"
+      </Value>
+
+      <Value>
+        Type "threads"
+        InstancePrefix "running"
+        #InstanceFrom ""
+        Table false
+        Attribute "currentThreadsBusy"
+      </Value>
+    </MBean>
+
+    #####################
+    # Connection blocks #
+    #####################
+    <Connection>
+      ServiceURL "service:jmx:rmi:///jndi/rmi://localhost:17264/jmxrmi"
+      User "monitorRole"
+      Password "queeZie1"
+      Host "localhost"
+      Collect "classes"
+      Collect "compilation"
+      Collect "garbage_collector"
+      Collect "memory"
+      Collect "memory_pool"
+    </Connection>
+  </Plugin>
+</Plugin>
diff --git a/contrib/README b/contrib/README
new file mode 100644 (file)
index 0000000..bc1fe9f
--- /dev/null
@@ -0,0 +1,103 @@
+The files in this directory may be used to perform common tasks that aren't
+exactly `collectd's job. They may or may not require in-depth knowledge of RRD
+files and/or `collectd's inner workings. Use at your own risk.
+
+add_rra.sh
+----------
+  Before version 3.9.0 collectd used to create a different set of RRAs. The
+most detailed of these old RRAs had a one minute resolution. This script can
+be used to add three more RRAs: minimum, maximum and average with a ten second
+resolution and 2200 rows (~6 hours). This will make hourly statistics much more
+interesting. Please note that no sanity- checking whatsoever is performed. You
+can seriously fuck up your RRD files if you don't know what you're doing.
+
+collectd-network.py
+-------------------
+  This Python module by Adrian Perez implements the collectd network protocol
+in pure Python. It currently supports to receive data and notifications from
+collectd.
+
+collectd-unixsock.py
+--------------------
+  This Python module by Clay Loveless provides an interface to collect's
+unixsock plugin.
+
+collectd2html.pl
+----------------
+  This script by Vincent Stehlé will search for RRD files in
+`/var/lib/collectd/' and generate an HTML file and a directory containing
+several PNG files which are graphs of the RRD files found.
+
+collection.cgi
+--------------
+  Sample CGI script that creates graphs on the fly. The Perl modules `RRDs'
+(Debian package `librrds-perl'), `URI:Escape' (package liburi-perl),
+`HTML::Entities' (package libhtml-parser-perl) and a CGI capable web server
+(e.g. apache2 or boa) are needed. Simply install the script to a place where
+the webserver will treat it as a CGI script (/usr/lib/cgi-bin/ by default) and
+visit that page in a browser (http://localhost/cgi-bin/collection.cgi by
+default). Please refer to your webserver's documentation for more details.
+
+  Starting with version 4, collection.cgi requires a small config file, which
+should look something like this:
+
+  datadir: "/var/lib/collectd/rrd/"
+  libdir: "/usr/lib/collectd/"
+
+exec-munin.px
+-------------
+  Script to be used with the exec-plugin (see collectd-exec(5) for details)
+which executes munin plugins, parses the output and translates it to a format
+the exec-plugin understands. The features are limited - changing the munin
+plugins to use the output format understood by the exec-plugin is recommended.
+See the embedded POD documentation for more details:
+ $ perldoc contrib/exec-munin.px
+
+exec-smartctl
+-------------
+  Sample script for the exec plugin. Please refer to the documentation in the
+file - you will have to adapt it to your needs anyway.
+
+extractDS.px
+------------
+  Creates a new RRD-file with only one data-source (DS) of the source-RRD-
+file. That is very handy when you realise that you have bundled up DSes in one
+RRD-file that should have been in multiple RRD-files instead. Is is used by
+`migrate-3-4.px' to split up the cpu-, nfs-, swap-files and possibly others.
+
+fedora/
+-------
+  Init-script and Spec-file that can be used when creating RPM-packages for
+Fedora.
+
+GenericJMX.conf
+---------------
+  Example configuration file for the ‘GenericJMX’ Java plugin. Please read the
+documentation at the beginning of the file for more details.
+
+migrate-3-4.px
+--------------
+  Migration-script to ease the switch from version 3 to version 4. Many
+RRD-files are expected in a different place, some have been changed (DSes have
+been renamed) and others have bee split up into multiple files. This script
+prints a bash-script to STDOUT which should do most of the work for you. You
+may still need to do some things by hand, read `README.migration' for more
+details.
+
+redhat/
+-------
+  Spec-file and affiliated files to build an RedHat RPM package of collectd.
+
+snmp-data.conf
+--------------
+  Sample configuration for the SNMP plugin. This config includes a few standard
+<Data ..> definitions that you can include in your own config using the
+`Include' statement (available since version 4.2.0). The config includes some
+data that is defined in the IF-MIB, e. g. octet or packet counters, UPS-MIB and
+whatever people have send in. If you have some more definitions please send
+them in, so others can profit from it.
+
+solaris-smf
+-----------
+  Manifest file for the Solaris SMF system and detailed information on how to
+register collectd as a service with this system.
diff --git a/contrib/SpamAssassin/Collectd.pm b/contrib/SpamAssassin/Collectd.pm
new file mode 100644 (file)
index 0000000..1edcfc6
--- /dev/null
@@ -0,0 +1,218 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+Collectd - plugin for filling collectd with stats 
+
+=head1 INSTALLATION
+
+Just copy Collectd.pm into your SpamAssassin Plugin path 
+(e.g /usr/share/perl5/Mail/SpamAssassin/Plugin/) and
+add a loadplugin call into your init.pre file. 
+
+=head1 SYNOPSIS
+
+  loadplugin    Mail::SpamAssassin::Plugin::Collectd
+
+=head1 USER SETTINGS
+
+=over 4
+
+=item collectd_socket [ socket path ]      (default: /var/run/collectd-email)
+
+Where the collectd socket is
+
+=cut 
+
+=item collectd_buffersize [ size ] (default: 256) 
+
+the email plugin uses a fixed buffer, if a line exceeds this size
+it has to be continued in another line. (This is of course handled internally)
+If you have changed this setting please get it in sync with the SA Plugin
+config. 
+
+=cut 
+
+=item collectd_timeout [ sec ] (default: 2) 
+
+if sending data to to collectd takes too long the connection will be aborted. 
+
+=cut
+
+=item collectd_retries [ tries ] (default: 3)
+
+the collectd plugin uses a tread pool, if this is empty the connection fails,
+the SA Plugin then tries to reconnect. With this variable you can indicate how
+often it should try. 
+
+=cut
+
+=head1 DESCRIPTION
+
+This modules uses the email plugin of collectd from Sebastian Harl to
+collect statistical informations in rrd files to create some nice looking
+graphs with rrdtool. They communicate over a unix socket that the collectd
+plugin creates. The generated graphs will be placed in /var/lib/collectd/email
+
+=head1 AUTHOR
+
+Alexander Wirt <formorer@formorer.de>
+
+=head1 COPYRIGHT
+
+ Copyright 2006 Alexander Wirt <formorer@formorer.de> 
+ This program is free software; you can redistribute it and/or modify 
+ it under the the terms of either: 
+
+ a) the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+
+ or
+
+ b) the GPL (http://www.gnu.org/copyleft/gpl.html)  
+
+ use whatever you like more. 
+
+=cut
+
+package Mail::SpamAssassin::Plugin::Collectd;
+
+use Mail::SpamAssassin::Plugin;
+use Mail::SpamAssassin::Logger;
+use strict;
+use bytes; 
+use warnings;
+use Time::HiRes qw(usleep);
+use IO::Socket;
+
+use vars qw(@ISA);
+@ISA = qw(Mail::SpamAssassin::Plugin);
+
+sub new {
+    my ($class, $mailsa) = @_;
+
+    # the usual perlobj boilerplate to create a subclass object
+    $class = ref($class) || $class;
+    my $self = $class->SUPER::new($mailsa);
+    bless ($self, $class);
+
+    # register our config options
+    $self->set_config($mailsa->{conf});
+
+    # and return the new plugin object
+    return $self;
+}
+
+sub set_config {
+    my ($self, $conf) = @_;
+    my @cmds = ();
+
+    push (@cmds, {
+           setting => 'collectd_buffersize',
+           default => 256,
+           type =>
+           $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
+       });
+
+    push (@cmds, {
+           setting => 'collectd_socket', 
+           default => '/var/run/collectd-email',
+           type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
+    });
+
+       push (@cmds, {
+                       setting => 'collectd_timeout',
+                       default => 2,
+                       type =>
+                       $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
+       });
+
+       push (@cmds, {
+                       setting => 'collectd_retries',
+                       default => 3,
+                       type =>
+                       $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
+       });
+
+
+    $conf->{parser}->register_commands(\@cmds);
+}
+
+sub check_end {
+    my ($self, $params) = @_;
+    my $message_status = $params->{permsgstatus};
+       #create  new connection to our socket
+       eval {
+               local $SIG{ALRM} = sub { die "Sending to collectd timed out.\n" }; # NB: \n required
+
+               #generate a timeout
+               alarm $self->{main}->{conf}->{collectd_timeout};
+
+               my $sock;
+               #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});
+                       #sleep a random value between 0 and 50 microsecs to try for a new
+                       #thread
+                       usleep(int(rand(50))); 
+               }
+
+               die("could not connect to " .
+                               $self->{main}->{conf}->{collectd_socket} . ": $! - collectd plugin disabled") unless $sock; 
+
+               $sock->autoflush(1);
+
+               my $score = $message_status->{score};
+               #get the size of the message 
+               my $body = $message_status->{msg}->{pristine_body};
+
+               my $len = length($body);
+
+               if ($message_status->{score} >= $self->{main}->{conf}->{required_score} ) {
+                       #hey we have spam
+                       print $sock "e:spam:$len\n";
+               } else {
+                       print $sock "e:ham:$len\n";
+               }
+               print $sock "s:$score\n";
+               my @tmp_array; 
+               my @tests = @{$message_status->{test_names_hit}};
+
+               my $buffersize = $self->{main}->{conf}->{collectd_buffersize}; 
+               dbg("collectd: buffersize: $buffersize"); 
+
+               while  (scalar(@tests) > 0) {
+               push (@tmp_array, pop(@tests)); 
+                       if (length(join(',', @tmp_array) . '\n') > $buffersize) {
+                               push (@tests, pop(@tmp_array)); 
+                                       if (length(join(',', @tmp_array) . '\n') > $buffersize or scalar(@tmp_array) == 0) {
+                                               dbg("collectd: this shouldn't happen. Do you have tests"
+                                                       ." with names that have more than ~ $buffersize Bytes?");
+                                               return 1; 
+                                       } else {
+                                               dbg ( "collectd: c:" . join(',', @tmp_array) . "\n" ); 
+                                               print $sock "c:" . join(',', @tmp_array) . "\n"; 
+                                               #clean the array
+                                               @tmp_array = ();
+                                       } 
+                       } elsif ( scalar(@tests) == 0 ) {
+                               dbg ( "collectd: c:" . join(',', @tmp_array) . '\n' );
+                               print $sock "c:" . join(',', @tmp_array) . "\n";
+                       }
+               }
+               close($sock); 
+               alarm 0; 
+       };
+       if ($@) {
+               my $message = $@; 
+               chomp($message); 
+               info("collectd: $message");
+               return -1; 
+       }
+}
+
+1;
+
+# vim: syntax=perl sw=4 ts=4 noet shiftround
diff --git a/contrib/SpamAssassin/example.cf b/contrib/SpamAssassin/example.cf
new file mode 100644 (file)
index 0000000..7ffd708
--- /dev/null
@@ -0,0 +1,5 @@
+collectd_buffersize 256
+collectd_socket /var/run/collectd-email
+collectd_timeout 2
+collectd_retries 3 
+
diff --git a/contrib/add_rra.sh b/contrib/add_rra.sh
new file mode 100755 (executable)
index 0000000..c9306b0
--- /dev/null
@@ -0,0 +1,67 @@
+#!/bin/bash
+
+INPUT=$1
+OUTPUT=$2
+
+if [ -z "$INPUT" -o -z "$OUTPUT" ]
+then
+       cat <<USAGE
+Usage: $0 <input> <output>
+USAGE
+       exit 1
+fi
+
+if [ ! -e "$INPUT" ]
+then
+       echo "No such file: $INPUT"
+       exit 1
+fi
+
+if [ -e "$OUTPUT" ]
+then
+       echo "File exists: $OUTPUT"
+       exit 1
+fi
+
+NUM_DS=0
+rrdtool dump "$INPUT" | while read LINE
+do
+       echo "$LINE"
+
+       if [ "$LINE" = "<ds>" ]
+       then
+               NUM_DS=$(($NUM_DS + 1))
+       fi
+       
+       if [ "$LINE" = "<!-- Round Robin Archives -->" ]
+       then
+               for CF in MIN MAX AVERAGE
+               do
+                       cat <<RRA
+       <rra>
+               <cf> $CF </cf>
+               <pdp_per_row> 1 </pdp_per_row>
+               <xff> 0.0000000000e+00 </xff>
+
+               <cdp_prep>
+RRA
+                       for ((i=0; i < $NUM_DS; i++))
+                       do
+                               echo "                  <ds><value> NaN </value>  <unknown_datapoints> 1 </unknown_datapoints></ds>"
+                       done
+                       echo "          </cdp_prep>"
+                       echo "          <database>"
+
+                       DS_VALUES=`for ((i=0; i < $NUM_DS; i++)); do echo -n "<v> NaN </v>"; done`
+                       for ((i=0; i < 2200; i++))
+                       do
+                               echo "                  <!-- $i --> <row>$DS_VALUES</row>"
+                       done
+                       echo "          </database>"
+                       echo "  </rra>"
+               done
+       fi
+done >"$OUTPUT.xml"
+
+rrdtool restore "$OUTPUT.xml" "$OUTPUT" -r >/dev/null
+rm -f "$OUTPUT.xml"
diff --git a/contrib/aix/collectd.spec b/contrib/aix/collectd.spec
new file mode 100644 (file)
index 0000000..c148d79
--- /dev/null
@@ -0,0 +1,75 @@
+
+%define name    collectd
+%define version 4.10.1
+%define release 1
+
+Name:           %{name}
+Summary:        Statistics collection daemon for filling RRD files.
+Version:        %{version}
+Release:        %{release}
+#Source:         http://collectd.org/files/%{name}-%{version}.tar.gz
+Source0:        %{name}-%{version}.tar.gz
+Group:          System Environment/Daemons
+BuildRoot:      %{_tmppath}/%{name}-%{version}-buildroot
+License:        GPL
+BuildPrereq:    rrdtool-devel,net-snmp-devel
+Requires:       rrdtool,net-snmp
+Packager:       Aurelien Reynaud <collectd@wattapower.net>
+Vendor:         collectd development team <collectd@verplant.org>
+
+%description
+collectd is a small daemon which collects system information periodically and
+provides mechanisms to monitor and store the values in a variety of ways. It
+is written in C for performance. Since the daemon doesn't need to startup
+every time it wants to update the values it's very fast and easy on the
+system. Also, the statistics are very fine grained since the files are updated
+every 10 seconds.
+
+%prep
+%setup
+
+%build
+# The RM variable in the RPM environment conflicts with that of the build environment,
+# at least when building on AIX 6.1. This is definitely a bug in one of the tools but
+# for now we work around it by unsetting the variable below.
+[ -n "$RM" ] && unset RM
+./configure LDFLAGS="-Wl,-brtl" --prefix=/opt/freeware --mandir=/opt/freeware/man --disable-dns --with-libnetsnmp=/opt/freeware/bin/net-snmp-config
+make
+
+%install
+make install DESTDIR=$RPM_BUILD_ROOT
+mkdir -p $RPM_BUILD_ROOT/%{_localstatedir}/lib/%{name}
+mkdir -p $RPM_BUILD_ROOT/%{_localstatedir}/run
+mkdir -p $RPM_BUILD_ROOT/etc/rc.d/init.d
+cp contrib/aix/init.d-collectd $RPM_BUILD_ROOT/etc/rc.d/init.d/collectd
+
+%clean
+[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf "$RPM_BUILD_ROOT"
+
+%files
+%defattr(-,root,system)
+%doc AUTHORS COPYING ChangeLog INSTALL NEWS README
+%config(noreplace) %attr(0644,root,system) %{_sysconfdir}/collectd.conf
+%attr(0755,root,system) /etc/rc.d/init.d/collectd
+%attr(0755,root,system) %{_sbindir}/collectd
+%attr(0755,root,system) %{_bindir}/collectd-nagios
+%attr(0755,root,system) %{_sbindir}/collectdmon
+%attr(0644,root,system) %{_mandir}/man1/*
+%attr(0644,root,system) %{_mandir}/man5/*
+
+# client
+%attr(0644,root,system) %{_includedir}/%{name}/client.h
+%attr(0644,root,system) %{_includedir}/%{name}/lcc_features.h
+
+%attr(0644,root,system) %{_libdir}/libcollectdclient.*
+%attr(0644,root,system) %{_libdir}/pkgconfig/libcollectdclient.pc
+
+%attr(0444,root,system) %{_libdir}/%{name}/*.so
+%attr(0444,root,system) %{_libdir}/%{name}/*.a
+%attr(0444,root,system) %{_libdir}/%{name}/*.la
+
+%attr(0644,root,system) %{_datadir}/%{name}/types.db
+
+%dir %{_localstatedir}/lib/%{name}
+%dir %{_localstatedir}/run
+
diff --git a/contrib/aix/init.d-collectd b/contrib/aix/init.d-collectd
new file mode 100755 (executable)
index 0000000..d893153
--- /dev/null
@@ -0,0 +1,79 @@
+#!/bin/sh
+#
+# description: collectd startup script
+#
+# March 2010, Aurelien Reynaud <collectd@wattapower.net>
+#
+
+# Some plugins need libs in non-standard paths
+case `uname` in
+       SunOS)
+               LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/usr/local/lib:/usr/local/ssl/lib"
+               export LD_LIBRARY_PATH
+               ;;
+       *)
+               ;;
+esac
+
+# Set umask
+umask 022
+
+COLLECTD_BIN=/opt/freeware/sbin/collectd
+PIDFILE=/opt/freeware/var/run/collectd.pid
+
+# Check for missing binaries (stale symlinks should not happen)
+if [ ! -x $COLLECTD_BIN ]; then
+       echo "$COLLECTD_BIN not installed"
+       [ "$1" = "stop" ] && exit 0
+       exit 5
+fi
+
+# Check for existence of needed config file and read it
+COLLECTD_CONFIG=/opt/freeware/etc/collectd.conf
+if [ ! -r $COLLECTD_CONFIG ]; then
+       echo "$COLLECTD_CONFIG not existing"
+       [ "$1" = "stop" ] && exit 0
+       exit 6
+fi
+
+case "$1" in
+    start)
+       if [ -r $PIDFILE ]; then
+           echo "collectd daemon is already running with PID `cat $PIDFILE`."
+           exit 1
+       fi
+       echo "Starting collectd..."
+
+       ## Start daemon
+       $COLLECTD_BIN
+       ;;
+    stop)
+       echo "Shutting down collectd daemon... "
+       ## Stop daemon.
+       if [ -r $PIDFILE ]; then
+           pid=`cat $PIDFILE`
+           kill -15 $pid
+           while ps -p $pid >/dev/null; do
+               sleep 1
+           done
+           rm -f $PIDFILE
+       fi
+       ;;
+    status)
+       if [ -r $PIDFILE ]; then
+           echo "collectd daemon is running with PID `cat $PIDFILE`."
+       else
+           echo "collectd daemon is not running."
+       fi
+       ;;
+    restart)
+       ## Stop the service and regardless of whether it was
+       ## running or not, start it again.
+       $0 stop
+       $0 start
+       ;;
+    *)
+       echo "Usage: $0 {start|stop|status|restart}"
+       exit 1
+       ;;
+esac
diff --git a/contrib/collectd2html.pl b/contrib/collectd2html.pl
new file mode 100644 (file)
index 0000000..fe4e2bd
--- /dev/null
@@ -0,0 +1,258 @@
+#!/usr/bin/perl
+
+################################################################################
+#
+# collectd2html.pl
+#
+# Description:
+#   Generate an html page with all rrd data gathered by collectd.
+#
+# Usage:
+#   collectd2html.pl
+#
+#   When run on <host>, it generated <host>.html and <host>.dir, the latter
+#   containing all necessary images.
+#
+#
+# Copyright 2006 Vincent Stehlé <vincent.stehle@free.fr>
+#
+# Patch to configure the data directory and hostname by Eddy Petrisor
+# <eddy.petrisor@gmail.com>.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+#
+################################################################################
+
+use warnings;
+use strict;
+use Fatal qw(open close);
+use File::Basename;
+use Getopt::Long qw(:config no_ignore_case bundling pass_through);
+
+my $DIR       = "/var/lib/collectd";
+my $HOST      = undef;
+my $IMG_FMT   = "PNG";
+my $RECURSIVE = 1;
+
+GetOptions (
+    "host=s"         => \$HOST,
+    "data-dir=s"     => \$DIR,
+    "image-format=s" => \$IMG_FMT,
+    "recursive"      => \$RECURSIVE
+);
+
+if (($DIR !~ m/\/rrd\/?$/) && (-d "$DIR/rrd")) {
+       $DIR .= "/rrd";
+}
+
+if (defined($HOST) && ($DIR !~ m/\/$HOST\/?$/) && (-d "$DIR/$HOST")) {
+       $DIR .= "/$HOST";
+}
+
+my @COLORS = (0xff7777, 0x7777ff, 0x55ff55, 0xffcc77, 0xff77ff, 0x77ffff,
+       0xffff77, 0x55aaff);
+my @tmp = `/bin/hostname -f`; chomp(@tmp);
+$HOST = $tmp[0] if (! defined $HOST);
+my $svg_p = ($IMG_FMT eq "SVG");
+my $IMG_SFX = $svg_p ? ".svg" : ".png";
+my $IMG_DIR = "${HOST}.dir";
+my $HTML = "${HOST}.xhtml";
+
+################################################################################
+#
+# fade_component
+#
+# Description:
+#   Fade a color's component to the white.
+#
+################################################################################
+sub fade_component($)
+{
+       my($component) = @_;
+       return (($component + 255 * 5) / 6);
+}
+
+################################################################################
+#
+# fade_color
+#
+# Description:
+#   Fade a color to the white.
+#
+################################################################################
+sub fade_color($)
+{
+       my($color) = @_;
+       my $r = 0;
+
+       for my $i (0 .. 2){
+               my $shft = ($i * 8);
+               my $component = (($color >> $shft) & 255);
+               $r |= (fade_component($component) << $shft);
+       }
+
+       return $r;
+}
+
+################################################################################
+#
+# main
+#
+################################################################################
+system("rm -fR $IMG_DIR");
+system("mkdir -p $IMG_DIR");
+local *OUT;
+open(OUT, ">$HTML");
+my $title="Rrd plot for $HOST";
+
+print OUT <<END;
+<!DOCTYPE html PUBLIC
+  "-//W3C//DTD XHTML 1.1//EN"
+  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style type="text/css" media="screen">
+.graph { text-align: center; }
+object.graph { width: 670; height: 179; }
+</style>
+<title>$title</title>
+<meta http-equiv="Content-Type"
+      content="application/xhtml+xml; charset=us-ascii" />
+</head>
+<body>
+END
+
+# list interesting rrd
+my @rrds;
+my @list;
+
+if ($RECURSIVE) {
+       @list = `find $DIR -type f -name '*.rrd'`;
+}
+else {
+       @list = `ls $DIR/*.rrd`;
+}
+chomp(@list);
+
+@list = sort @list;
+foreach my $rrd (@list){
+       $rrd =~ m/^$DIR\/(.*)\.rrd$/;
+       push(@rrds, $1);
+}
+
+# table of contents
+print OUT <<END;
+<h1><a id="top">$title</a></h1>
+<p>
+END
+
+foreach my $bn (@rrds){
+       my $cleaned_bn = $bn;
+       $cleaned_bn =~ tr/%\//__/;
+       print OUT <<END;
+<a href="#$cleaned_bn">$bn</a>
+END
+}
+
+print OUT <<END;
+</p>
+END
+
+# graph interesting rrd
+for (my $i = 0; $i < scalar(@rrds); ++$i) {
+       my $bn = $rrds[$i];
+       print "$bn\n";
+
+       my $rrd = $list[$i];
+       my $cmd = "rrdtool info $rrd |grep 'ds\\[' |sed 's/^ds\\[//'" 
+               ." |sed 's/\\].*//' |sort |uniq";
+       my @dss = `$cmd`; chomp(@dss);
+
+       # all DEF
+       my $j = 0;
+       my $defs = "";
+
+       foreach my $ds (@dss){
+               $defs .= " DEF:${ds}_avg=$rrd:$ds:AVERAGE"
+                       ." DEF:${ds}_max=$rrd:$ds:MAX ";
+       }
+
+       # all AREA
+       $j = 0;
+
+       foreach my $ds (@dss){
+               my $color = $COLORS[$j % scalar(@COLORS)]; $j++;
+               my $faded_color = fade_color($color);
+               $defs .= sprintf(" AREA:${ds}_max#%06x ", $faded_color);
+       }
+
+       # all LINE      
+       $j = 0;
+
+       foreach my $ds (@dss){
+               my $color = $COLORS[$j % scalar(@COLORS)]; $j++;
+               $defs .= sprintf(" LINE2:${ds}_avg#%06x:$ds"
+                       ." GPRINT:${ds}_avg:AVERAGE:%%5.1lf%%sAvg"
+                       ." GPRINT:${ds}_max:MAX:%%5.1lf%%sMax"
+                       , $color);
+       }
+
+       my $cleaned_bn = $bn;
+       $cleaned_bn =~ tr/%\//__/;
+       print OUT <<END;
+<h2><a id="$cleaned_bn">$bn</a></h2>
+END
+
+       # graph various ranges
+       foreach my $span qw(1hour 1day 1week 1month){
+               system("mkdir -p $IMG_DIR/" . dirname($bn));
+               my $img = "$IMG_DIR/${bn}-$span$IMG_SFX";
+
+               my $cmd = "rrdtool graph $img"
+                       ." -t \"$bn $span\" --imgformat $IMG_FMT --width 600 --height 100"
+                       ." --start now-$span --end now --interlaced"
+                       ." $defs >/dev/null 2>&1";
+               system($cmd);
+
+               my $cleaned_img = $img; $cleaned_img =~ s/%/%25/g;
+               if (! $svg_p) {
+                       print OUT <<END;
+<p class="graph"><img src="$cleaned_img" alt="${bn} $span" /></p>
+END
+               } else {
+                       print OUT <<END;
+<p class="graph"><object data="$cleaned_img" type="image/svg+xml">
+  ${bn} $span</object></p>
+END
+               }
+       }
+
+       print OUT <<END;
+<p><a href="#top">[top]</a></p>
+END
+}
+
+print OUT <<END;
+<hr />
+<p>
+  <a href="http://validator.w3.org/check?uri=referer"><img
+     src="http://www.w3.org/Icons/valid-xhtml10"
+     alt="Valid XHTML 1.0 Strict" height="31" width="88" /></a>
+</p>
+</body>
+</html>
+END
+
+close(OUT);
diff --git a/contrib/collectd_network.py b/contrib/collectd_network.py
new file mode 100644 (file)
index 0000000..445b183
--- /dev/null
@@ -0,0 +1,318 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+# vim: fileencoding=utf-8
+#
+# Copyright © 2009 Adrian Perez <aperez@igalia.com>
+#
+# Distributed under terms of the GPLv2 license.
+
+"""
+Collectd network protocol implementation.
+"""
+
+import socket
+import struct
+
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+
+from datetime import datetime
+from copy import deepcopy
+
+
+DEFAULT_PORT = 25826
+"""Default port"""
+
+DEFAULT_IPv4_GROUP = "239.192.74.66"
+"""Default IPv4 multicast group"""
+
+DEFAULT_IPv6_GROUP = "ff18::efc0:4a42"
+"""Default IPv6 multicast group"""
+
+
+
+# Message kinds
+TYPE_HOST            = 0x0000
+TYPE_TIME            = 0x0001
+TYPE_PLUGIN          = 0x0002
+TYPE_PLUGIN_INSTANCE = 0x0003
+TYPE_TYPE            = 0x0004
+TYPE_TYPE_INSTANCE   = 0x0005
+TYPE_VALUES          = 0x0006
+TYPE_INTERVAL        = 0x0007
+
+# For notifications
+TYPE_MESSAGE         = 0x0100
+TYPE_SEVERITY        = 0x0101
+
+# DS kinds
+DS_TYPE_COUNTER      = 0
+DS_TYPE_GAUGE        = 1
+
+
+header = struct.Struct("!2H")
+number = struct.Struct("!Q")
+short  = struct.Struct("!H")
+double = struct.Struct("<d")
+
+
+def decode_network_values(ptype, plen, buf):
+    """Decodes a list of DS values in collectd network format
+    """
+    nvalues = short.unpack_from(buf, header.size)[0]
+    off = header.size + short.size + nvalues
+    valskip = double.size
+
+    # Check whether our expected packet size is the reported one
+    assert ((valskip + 1) * nvalues + short.size + header.size) == plen
+    assert double.size == number.size
+
+    result = []
+    for dstype in map(ord, buf[header.size+short.size:off]):
+        if dstype == DS_TYPE_COUNTER:
+            result.append((dstype, number.unpack_from(buf, off)[0]))
+            off += valskip
+        elif dstype == DS_TYPE_GAUGE:
+            result.append((dstype, double.unpack_from(buf, off)[0]))
+            off += valskip
+        else:
+            raise ValueError("DS type %i unsupported" % dstype)
+
+    return result
+
+
+def decode_network_number(ptype, plen, buf):
+    """Decodes a number (64-bit unsigned) in collectd network format.
+    """
+    return number.unpack_from(buf, header.size)[0]
+
+
+def decode_network_string(msgtype, plen, buf):
+    """Decodes a floating point number (64-bit) in collectd network format.
+    """
+    return buf[header.size:plen-1]
+
+
+# Mapping of message types to decoding functions.
+_decoders = {
+    TYPE_VALUES         : decode_network_values,
+    TYPE_TIME           : decode_network_number,
+    TYPE_INTERVAL       : decode_network_number,
+    TYPE_HOST           : decode_network_string,
+    TYPE_PLUGIN         : decode_network_string,
+    TYPE_PLUGIN_INSTANCE: decode_network_string,
+    TYPE_TYPE           : decode_network_string,
+    TYPE_TYPE_INSTANCE  : decode_network_string,
+    TYPE_MESSAGE        : decode_network_string,
+    TYPE_SEVERITY       : decode_network_number,
+}
+
+
+def decode_network_packet(buf):
+    """Decodes a network packet in collectd format.
+    """
+    off = 0
+    blen = len(buf)
+    while off < blen:
+        ptype, plen = header.unpack_from(buf, off)
+
+        if plen > blen - off:
+            raise ValueError("Packet longer than amount of data in buffer")
+
+        if ptype not in _decoders:
+            raise ValueError("Message type %i not recognized" % ptype)
+
+        yield ptype, _decoders[ptype](ptype, plen, buf[off:])
+        off += plen
+
+
+
+
+
+class Data(object):
+    time = 0
+    host = None
+    plugin = None
+    plugininstance = None
+    type = None
+    typeinstance = None
+
+    def __init__(self, **kw):
+        [setattr(self, k, v) for k, v in kw.iteritems()]
+
+    @property
+    def datetime(self):
+        return datetime.fromtimestamp(self.time)
+
+    @property
+    def source(self):
+        buf = StringIO()
+        if self.host:
+            buf.write(self.host)
+        if self.plugin:
+            buf.write("/")
+            buf.write(self.plugin)
+        if self.plugininstance:
+            buf.write("/")
+            buf.write(self.plugininstance)
+        if self.type:
+            buf.write("/")
+            buf.write(self.type)
+        if self.typeinstance:
+            buf.write("/")
+            buf.write(self.typeinstance)
+        return buf.getvalue()
+
+    def __str__(self):
+        return "[%i] %s" % (self.time, self.source)
+
+
+
+class Notification(Data):
+    FAILURE  = 1
+    WARNING  = 2
+    OKAY     = 4
+
+    SEVERITY = {
+        FAILURE: "FAILURE",
+        WARNING: "WARNING",
+        OKAY   : "OKAY",
+    }
+
+    __severity = 0
+    message  = ""
+
+    def __set_severity(self, value):
+        if value in (self.FAILURE, self.WARNING, self.OKAY):
+            self.__severity = value
+
+    severity = property(lambda self: self.__severity, __set_severity)
+
+    @property
+    def severitystring(self):
+        return self.SEVERITY.get(self.severity, "UNKNOWN")
+
+    def __str__(self):
+        return "%s [%s] %s" % (
+                super(Notification, self).__str__(),
+                self.severitystring,
+                self.message)
+
+
+
+class Values(Data, list):
+    def __str__(self):
+        return "%s %s" % (Data.__str__(self), list.__str__(self))
+
+
+
+def interpret_opcodes(iterable):
+    vl = Values()
+    nt = Notification()
+
+    for kind, data in iterable:
+        if kind == TYPE_TIME:
+            vl.time = nt.time = data
+        elif kind == TYPE_INTERVAL:
+            vl.interval = data
+        elif kind == TYPE_HOST:
+            vl.host = nt.host = data
+        elif kind == TYPE_PLUGIN:
+            vl.plugin = nt.plugin = data
+        elif kind == TYPE_PLUGIN_INSTANCE:
+            vl.plugininstance = nt.plugininstance = data
+        elif kind == TYPE_TYPE:
+            vl.type = nt.type = data
+        elif kind == TYPE_TYPE_INSTANCE:
+            vl.typeinstance = nt.typeinstance = data
+        elif kind == TYPE_SEVERITY:
+            nt.severity = data
+        elif kind == TYPE_MESSAGE:
+            nt.message = data
+            yield deepcopy(nt)
+        elif kind == TYPE_VALUES:
+            vl[:] = data
+            yield deepcopy(vl)
+
+
+
+class Reader(object):
+    """Network reader for collectd data.
+
+    Listens on the network in a given address, which can be a multicast
+    group address, and handles reading data when it arrives.
+    """
+    addr = None
+    host = None
+    port = DEFAULT_PORT
+
+    BUFFER_SIZE = 1024
+
+
+    def __init__(self, host=None, port=DEFAULT_PORT, multicast=False):
+        if host is None:
+            multicast = True
+            host = DEFAULT_IPv4_GROUP
+
+        self.host, self.port = host, port
+        self.ipv6 = ":" in self.host
+
+        family, socktype, proto, canonname, sockaddr = socket.getaddrinfo(
+                None if multicast else self.host, self.port,
+                socket.AF_INET6 if self.ipv6 else socket.AF_UNSPEC,
+                socket.SOCK_DGRAM, 0, socket.AI_PASSIVE)[0]
+
+        self._sock = socket.socket(family, socktype, proto)
+        self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        self._sock.bind(sockaddr)
+
+        if multicast:
+            if hasattr(socket, "SO_REUSEPORT"):
+                self._sock.setsockopt(
+                        socket.SOL_SOCKET,
+                        socket.SO_REUSEPORT, 1)
+
+            val = None
+            if family == socket.AF_INET:
+                assert "." in self.host
+                val = struct.pack("4sl",
+                        socket.inet_aton(self.host), socket.INADDR_ANY)
+            elif family == socket.AF_INET6:
+                raise NotImplementedError("IPv6 support not ready yet")
+            else:
+                raise ValueError("Unsupported network address family")
+
+            self._sock.setsockopt(
+                    socket.IPPROTO_IPV6 if self.ipv6 else socket.IPPROTO_IP,
+                    socket.IP_ADD_MEMBERSHIP, val)
+            self._sock.setsockopt(
+                    socket.IPPROTO_IPV6 if self.ipv6 else socket.IPPROTO_IP,
+                    socket.IP_MULTICAST_LOOP, 0)
+
+
+    def receive(self):
+        """Receives a single raw collect network packet.
+        """
+        return self._sock.recv(self.BUFFER_SIZE)
+
+
+    def decode(self, buf=None):
+        """Decodes a given buffer or the next received packet.
+        """
+        if buf is None:
+            buf = self.receive()
+        return decode_network_packet(buf)
+
+
+    def interpret(self, iterable=None):
+        """Interprets a sequence
+        """
+        if iterable is None:
+            iterable = self.decode()
+        if isinstance(iterable, basestring):
+            iterable = self.decode(iterable)
+        return interpret_opcodes(iterable)
+
+
diff --git a/contrib/collectd_unix_sock.rb b/contrib/collectd_unix_sock.rb
new file mode 100644 (file)
index 0000000..f473361
--- /dev/null
@@ -0,0 +1,135 @@
+# Ruby class to access collectd daemon through the UNIX socket
+# plugin.
+#
+# Requires collectd to be configured with the unixsock plugin, like so:
+#
+# LoadPlugin unixsock
+# <Plugin unixsock>
+#   SocketFile "/var/run/collectd-unixsock"
+#   SocketPerms "0775"
+# </Plugin>
+#
+# Copyright (C) 2009 Novell Inc.
+# Author: Duncan Mac-Vicar P. <dmacvicar@suse.de>
+#
+# Inspired in python version:
+# Copyright (C) 2008 Clay Loveless <clay@killersoft.com>
+#
+# This software is provided 'as-is', without any express or implied
+# warranty.  In no event will the author be held liable for any damages
+# arising from the use of this software.
+#
+# Permission is granted to anyone to use this software for any purpose,
+# including commercial applications, and to alter it and redistribute it
+# freely, subject to the following restrictions:
+#
+# 1. The origin of this software must not be misrepresented; you must not
+#    claim that you wrote the original software. If you use this software
+#    in a product, an acknowledgment in the product documentation would be
+#    appreciated but is not required.
+# 2. Altered source versions must be plainly marked as such, and must not be
+#    misrepresented as being the original software.
+# 3. This notice may not be removed or altered from any source distribution.
+#
+require 'socket'
+
+# Access to collectd data using the unix socket
+# interface
+#
+# see http://collectd.org/wiki/index.php/Plugin:UnixSock
+#
+class CollectdUnixSock
+  include Socket::Constants
+
+  # initializes the collectd interface
+  # path is the location of the collectd
+  # unix socket
+  #
+  # collectd = CollectdUnixSock.new
+  #
+  def initialize(path='/var/run/collectd-unixsock')
+    @socket = UNIXSocket.open(path)
+    # @socket = Socket.new(AF_UNIX, SOCK_STREAM, 0)
+    # @socket.connect(path)
+    @path = path
+  end
+
+  # iterates over available values, passing the
+  # identifier to the block and the time
+  # the data for this identifier was last
+  # updated
+  #
+  # collectd.each_value do |time, identifier|
+  #   ...
+  # end
+  def each_value
+    n_lines = cmd("LISTVAL")
+    n_lines.times do
+      line = @socket.readline
+      time_s, identifier = line.split(' ', 2)
+      time = Time.at(time_s.to_i)
+      yield time, identifier
+    end
+  end
+
+  # iterates over each value current data
+  #
+  # collectd.each_value_data('myhost/swap/swap-free') { |col, val| }
+  #
+  # each iteration gives the column name and the value for it.
+  #
+  # You can also disable flushing by specifying it as an option:
+  #
+  # client.each_value_data('tarro/swap/swap-free',
+  #                   :flush => false ) do |col, val|
+  #    # .. do something with col and val
+  # end
+  #
+  # :flush option is by default true
+  #
+  def each_value_data(identifier, opts={})
+    n_lines = cmd("GETVAL \"#{identifier}\"")
+    n_lines.times do
+      line = @socket.readline
+      col, val = line.split('=', 2)
+      yield col, val
+    end
+
+    # unless the user explicitly disabled
+    # flush...
+    unless opts[:flush] == false
+      cmd("FLUSH identifier=\"#{identifier}\"")
+    end
+    
+  end
+  
+  private
+  
+  # internal command execution
+  def cmd(c)
+    @socket.write("#{c}\n")
+    line = @socket.readline
+    status_string, message = line.split(' ', 2)
+    status = status_string.to_i
+    raise message if status < 0
+    status  
+  end
+  
+end
+
+if __FILE__ == $0
+
+  client = CollectdUnixSock.new
+  client.each_value do |time, id|
+    puts "#{time.to_i} - #{id}"
+  end
+
+  client.each_value_data("tarro/cpu-0/cpu-user") do |col, val|
+    puts "#{col} -> #{val}"
+  end
+  
+  client.each_value_data("tarro/interface/if_packets-eth0") do |col, val|
+    puts "#{col} -> #{val}"
+  end
+  
+end
diff --git a/contrib/collectd_unixsock.py b/contrib/collectd_unixsock.py
new file mode 100644 (file)
index 0000000..1b8e6b1
--- /dev/null
@@ -0,0 +1,243 @@
+#-*- coding: ISO-8859-1 -*-
+# collect.py: the python collectd-unixsock module.
+#
+# Requires collectd to be configured with the unixsock plugin, like so:
+#
+# LoadPlugin unixsock
+# <Plugin unixsock>
+#   SocketFile "/var/run/collectd-unixsock"
+#   SocketPerms "0775"
+# </Plugin>
+#
+# Copyright (C) 2008 Clay Loveless <clay@killersoft.com>
+#
+# This software is provided 'as-is', without any express or implied
+# warranty.  In no event will the author be held liable for any damages
+# arising from the use of this software.
+#
+# Permission is granted to anyone to use this software for any purpose,
+# including commercial applications, and to alter it and redistribute it
+# freely, subject to the following restrictions:
+#
+# 1. The origin of this software must not be misrepresented; you must not
+#    claim that you wrote the original software. If you use this software
+#    in a product, an acknowledgment in the product documentation would be
+#    appreciated but is not required.
+# 2. Altered source versions must be plainly marked as such, and must not be
+#    misrepresented as being the original software.
+# 3. This notice may not be removed or altered from any source distribution.
+
+import socket
+import sys
+
+
+class Collectd():
+
+    def __init__(self, path='/var/run/collectd-unixsock', noisy=False):
+        self.noisy = noisy
+        self.path = path
+        self._sock = self._connect()
+
+    def flush(self, timeout=None, plugins=[], identifiers=[]):
+        """Send a FLUSH command.
+
+        Full documentation:
+            http://collectd.org/wiki/index.php/Plain_text_protocol#FLUSH
+
+        """
+        # have to pass at least one plugin or identifier
+        if not plugins and not identifiers:
+            return None
+        args = []
+        if timeout:
+            args.append("timeout=%s" % timeout)
+        if plugins:
+            plugin_args = map(lambda x: "plugin=%s" % x, plugins)
+            args.extend(plugin_args)
+        if identifiers:
+            identifier_args = map(lambda x: "identifier=%s" % x, identifiers)
+            args.extend(identifier_args)
+        return self._cmd('FLUSH %s' % ' '.join(args))
+
+    def getthreshold(self, identifier):
+        """Send a GETTHRESHOLD command.
+
+        Full documentation:
+            http://collectd.org/wiki/index.php/Plain_text_protocol#GETTHRESHOLD
+
+        """
+        numvalues = self._cmd('GETTHRESHOLD "%s"' % identifier)
+        lines = []
+        if not numvalues or numvalues < 0:
+            raise KeyError("Identifier '%s' not found" % identifier)
+        lines = self._readlines(numvalues)
+        return lines
+
+    def getval(self, identifier, flush_after=True):
+        """Send a GETVAL command.
+
+        Also flushes the identifier if flush_after is True.
+
+        Full documentation:
+            http://collectd.org/wiki/index.php/Plain_text_protocol#GETVAL
+
+        """
+        numvalues = self._cmd('GETVAL "%s"' % identifier)
+        lines = []
+        if not numvalues or numvalues < 0:
+            raise KeyError("Identifier '%s' not found" % identifier)
+        lines = self._readlines(numvalues)
+        if flush_after:
+            self.flush(identifiers=[identifier])
+        return lines
+
+    def listval(self):
+        """Send a LISTVAL command.
+
+        Full documentation:
+            http://collectd.org/wiki/index.php/Plain_text_protocol#LISTVAL
+
+        """
+        numvalues = self._cmd('LISTVAL')
+        lines = []
+        if numvalues:
+            lines = self._readlines(numvalues)
+        return lines
+
+    def putnotif(self, message, options={}):
+        """Send a PUTNOTIF command.
+
+        Options must be passed as a Python dictionary. Example:
+          options={'severity': 'failure', 'host': 'example.com'}
+
+        Full documentation:
+            http://collectd.org/wiki/index.php/Plain_text_protocol#PUTNOTIF
+
+        """
+        args = []
+        if options:
+            options_args = map(lambda x: "%s=%s" % (x, options[x]), options)
+            args.extend(options_args)
+        args.append('message="%s"' % message)
+        return self._cmd('PUTNOTIF %s' % ' '.join(args))
+
+    def putval(self, identifier, values, options={}):
+        """Send a PUTVAL command.
+
+        Options must be passed as a Python dictionary. Example:
+          options={'interval': 10}
+
+        Full documentation:
+            http://collectd.org/wiki/index.php/Plain_text_protocol#PUTVAL
+
+        """
+        args = []
+        args.append('"%s"' % identifier)
+        if options:
+            options_args = map(lambda x: "%s=%s" % (x, options[x]), options)
+            args.extend(options_args)
+        values = map(str, values)
+        args.append(':'.join(values))
+        return self._cmd('PUTVAL %s' % ' '.join(args))
+
+    def _cmd(self, c):
+        try:
+            return self._cmdattempt(c)
+        except socket.error, (errno, errstr):
+            sys.stderr.write("[error] Sending to socket failed: [%d] %s\n"
+                             % (errno, errstr))
+            self._sock = self._connect()
+            return self._cmdattempt(c)
+
+    def _cmdattempt(self, c):
+        if self.noisy:
+            print "[send] %s" % c
+        if not self._sock:
+            sys.stderr.write("[error] Socket unavailable. Can not send.")
+            return False
+        self._sock.send(c + "\n")
+        status_message = self._readline()
+        if self.noisy:
+            print "[recive] %s" % status_message
+        if not status_message:
+            return None
+        code, message = status_message.split(' ', 1)
+        if int(code):
+            return int(code)
+        return False
+
+    def _connect(self):
+        try:
+            sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+            sock.connect(self.path)
+            if self.noisy:
+                print "[socket] connected to %s" % self.path
+            return sock
+        except socket.error, (errno, errstr):
+            sys.stderr.write("[error] Connecting to socket failed: [%d] %s"
+                             % (errno, errstr))
+            return None
+
+    def _readline(self):
+        """Read single line from socket"""
+        if not self._sock:
+            sys.stderr.write("[error] Socket unavailable. Can not read.")
+            return None
+        try:
+            data = ''
+            buf = []
+            recv = self._sock.recv
+            while data != "\n":
+                data = recv(1)
+                if not data:
+                    break
+                if data != "\n":
+                    buf.append(data)
+            return ''.join(buf)
+        except socket.error, (errno, errstr):
+            sys.stderr.write("[error] Reading from socket failed: [%d] %s"
+                             % (errno, errstr))
+            self._sock = self._connect()
+            return None
+
+    def _readlines(self, sizehint=0):
+        """Read multiple lines from socket"""
+        total = 0
+        list = []
+        while True:
+            line = self._readline()
+            if not line:
+                break
+            list.append(line)
+            total = len(list)
+            if sizehint and total >= sizehint:
+                break
+        return list
+
+    def __del__(self):
+        if not self._sock:
+            return
+        try:
+            self._sock.close()
+        except socket.error, (errno, errstr):
+            sys.stderr.write("[error] Closing socket failed: [%d] %s"
+                             % (errno, errstr))
+
+
+if __name__ == '__main__':
+    """Collect values from socket and dump to STDOUT"""
+
+    c = Collectd('/var/run/collectd-unixsock', noisy=True)
+    list = c.listval()
+    for val in list:
+        stamp, identifier = val.split()
+        print "\n%s" % identifier
+        print "\tUpdate time: %s" % stamp
+
+        values = c.getval(identifier)
+        print "\tValue list: %s" % ', '.join(values)
+
+        # don't fetch thresholds by default because collectd will crash
+        # if there is no treshold for the given identifier
+        #thresholds = c.getthreshold(identifier)
+        #print "\tThresholds: %s" % ', '.join(thresholds)
diff --git a/contrib/collection.cgi b/contrib/collection.cgi
new file mode 100755 (executable)
index 0000000..af64fb1
--- /dev/null
@@ -0,0 +1,3420 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Carp (qw(cluck confess));
+use CGI (':cgi');
+use CGI::Carp ('fatalsToBrowser');
+use HTML::Entities ('encode_entities');
+use URI::Escape ('uri_escape');
+use RRDs ();
+use Data::Dumper ();
+
+our $Config = "/etc/collection.conf";
+our @DataDirs = ();
+our @DontShowTypes = ();
+our $LibDir;
+
+our $ValidTimespan =
+{
+  hour => 3600,
+  day => 86400,
+  week => 7 * 86400,
+  month => 31 * 86400,
+  year => 366 * 86400
+};
+
+our @RRDDefaultArgs = ('-w', '400');
+
+our $Args = {};
+
+our $GraphDefs;
+our $MetaGraphDefs = {};
+load_graph_definitions ();
+
+for (qw(action host plugin plugin_instance type type_instance timespan))
+{
+       $Args->{$_} = param ($_);
+}
+
+exit (main ());
+
+sub read_config
+{
+       my $fh;
+       open ($fh, "< $Config") or confess ("open ($Config): $!");
+       while (my $line = <$fh>)
+       {
+               chomp ($line);
+               next if (!$line);
+               next if ($line =~ m/^\s*#/);
+               next if ($line =~ m/^\s*$/);
+
+               my $key;
+               my $value;
+
+               if ($line =~ m/^([A-Za-z]+):\s*"((?:[^"\\]+|\\.)*)"$/)
+               {
+                       $key = lc ($1); $value = $2;
+                       $value =~ s/\\(.)/$1/g;
+               }
+               elsif ($line =~ m/([A-Za-z]+):\s*([0-9]+)$/)
+               {
+                       $key = lc ($1); $value = 0 + $2;
+               }
+               else
+               {
+                       print STDERR "Cannot parse line: $line\n";
+                       next;
+               }
+
+               if ($key eq 'datadir')
+               {
+                       $value =~ s#/*$##;
+                       push (@DataDirs, $value);
+               }
+               elsif ($key eq 'libdir')
+               {
+                       $value =~ s#/*$##;
+                       $LibDir = $value;
+               }
+               elsif ($key eq 'dontshowtype')
+               {
+                 push (@DontShowTypes, $value);
+               }
+               else
+               {
+                       print STDERR "Unknown key: $key\n";
+               }
+       }
+       close ($fh);
+} # read_config
+
+sub validate_args
+{
+       if ($Args->{'action'} && ($Args->{'action'} =~ m/^(overview|show_host|show_plugin|show_type|show_graph)$/))
+       {
+               $Args->{'action'} = $1;
+       }
+       else
+       {
+               $Args->{'action'} = 'overview';
+       }
+
+       if ($Args->{'host'} && ($Args->{'host'} =~ m#/#))
+       {
+               delete ($Args->{'host'});
+       }
+
+       if ($Args->{'plugin'} && ($Args->{'plugin'} =~ m#/#))
+       {
+               delete ($Args->{'plugin'});
+       }
+
+       if ($Args->{'type'} && ($Args->{'type'} =~ m#/#))
+       {
+               delete ($Args->{'type'});
+       }
+
+       if (!$Args->{'plugin'} || ($Args->{'plugin_instance'}
+               && ($Args->{'plugin_instance'} =~ m#/#)))
+       {
+               delete ($Args->{'plugin_instance'});
+       }
+
+       if (!$Args->{'type'} || ($Args->{'type_instance'}
+               && ($Args->{'type_instance'} =~ m#/#)))
+       {
+               delete ($Args->{'type_instance'});
+       }
+
+       if (defined ($Args->{'timespan'})
+         && ($Args->{'timespan'} =~ m/^(hour|day|week|month|year)$/))
+       {
+         $Args->{'timespan'} = $1;
+       }
+       else
+       {
+         $Args->{'timespan'} = 'day';
+       }
+} # validate_args
+
+{
+  my $hosts;
+  sub _find_hosts
+  {
+    if (defined ($hosts))
+    {
+      return (keys %$hosts);
+    }
+
+    $hosts = {};
+
+    for (my $i = 0; $i < @DataDirs; $i++)
+    {
+      my @tmp;
+      my $dh;
+
+      opendir ($dh, $DataDirs[$i]) or next;
+      @tmp = grep { ($_ !~ m/^\./) && (-d $DataDirs[$i] . '/' . $_) } (readdir ($dh));
+      closedir ($dh);
+
+      $hosts->{$_} = 1 for (@tmp);
+    } # for (@DataDirs)
+
+    return (keys %$hosts);
+  } # _find_hosts
+}
+
+sub _get_param_host
+{
+  my %all_hosts = map { $_ => 1 } (_find_hosts ());
+  my @selected_hosts = ();
+  for (param ('host'))
+  {
+    if (defined ($all_hosts{$_}))
+    {
+      push (@selected_hosts, "$_");
+    }
+  }
+  return (@selected_hosts);
+} # _get_param_host
+
+sub _get_param_timespan
+{
+  my $timespan = param ('timespan');
+
+  $timespan ||= 'day';
+  $timespan = lc ($timespan);
+
+  if (!defined ($ValidTimespan->{$timespan}))
+  {
+    $timespan = 'day';
+  }
+
+  return ($timespan);
+} # _get_param_timespan
+
+sub _find_plugins
+{
+  my $host = shift;
+  my %plugins = ();
+
+  for (my $i = 0; $i < @DataDirs; $i++)
+  {
+    my $dir = $DataDirs[$i] . "/$host";
+    my @tmp;
+    my $dh;
+
+    opendir ($dh, $dir) or next;
+    @tmp = grep { ($_ !~ m/^\./) && (-d "$dir/$_") } (readdir ($dh));
+    closedir ($dh);
+
+    for (@tmp)
+    {
+      my ($plugin, $instance) = split (m/-/, $_, 2);
+      $plugins{$plugin} = [] if (!exists $plugins{$plugin});
+      push (@{$plugins{$plugin}}, $instance);
+    }
+  } # for (@DataDirs)
+
+  return (%plugins);
+} # _find_plugins
+
+sub _find_types
+{
+  my $host = shift;
+  my $plugin = shift;
+  my $plugin_instance = shift;
+  my %types = ();
+
+  for (my $i = 0; $i < @DataDirs; $i++)
+  {
+    my $dir = $DataDirs[$i] . "/$host/$plugin" . (defined ($plugin_instance) ? "-$plugin_instance" : '');
+    my @tmp;
+    my $dh;
+
+    opendir ($dh, $dir) or next;
+    @tmp = grep { ($_ !~ m/^\./) && ($_ =~ m/\.rrd$/i) && (-f "$dir/$_") } (readdir ($dh));
+    closedir ($dh);
+
+    for (@tmp)
+    {
+      my $name = "$_";
+      $name =~ s/\.rrd$//i;
+      my ($type, $instance) = split (m/-/, $name, 2);
+      if (grep { $_ eq $type } @DontShowTypes) { next; }
+      $types{$type} = [] if (!$types{$type});
+      push (@{$types{$type}}, $instance) if (defined ($instance));
+    }
+  } # for (@DataDirs)
+
+  return (%types);
+} # _find_types
+
+sub _find_files_for_host
+{
+  my $host = shift;
+  my $ret = {};
+
+  my %plugins = _find_plugins ($host);
+  for (keys %plugins)
+  {
+    my $plugin = $_;
+    my $plugin_instances = $plugins{$plugin};
+
+    if (!$plugin_instances || !@$plugin_instances)
+    {
+      $plugin_instances = ['-'];
+    }
+
+    $ret->{$plugin} = {};
+
+    for (@$plugin_instances)
+    {
+      my $plugin_instance = defined ($_) ? $_ : '-';
+      my %types = _find_types ($host, $plugin,
+       ($plugin_instance ne '-')
+       ? $plugin_instance
+       : undef);
+
+      $ret->{$plugin}{$plugin_instance} = {};
+
+      for (keys %types)
+      {
+       my $type = $_;
+       my $type_instances = $types{$type};
+
+       $ret->{$plugin}{$plugin_instance}{$type} = {};
+
+       for (@$type_instances)
+       {
+         $ret->{$plugin}{$plugin_instance}{$type}{$_} = 1;
+       }
+
+       if (!@$type_instances)
+       {
+         $ret->{$plugin}{$plugin_instance}{$type}{'-'} = 1;
+       }
+      } # for (keys %types)
+    } # for (@$plugin_instances)
+  } # for (keys %plugins)
+
+  return ($ret);
+} # _find_files_for_host
+
+sub _find_files_for_hosts
+{
+  my @hosts = @_;
+  my $all_plugins = {};
+
+  for (my $i = 0; $i < @hosts; $i++)
+  {
+    my $tmp = _find_files_for_host ($hosts[$i]);
+    _files_union ($all_plugins, $tmp);
+  }
+
+  return ($all_plugins);
+} # _find_files_for_hosts
+
+sub _files_union
+{
+  my $dest = shift;
+  my $src = shift;
+
+  for (keys %$src)
+  {
+    my $plugin = $_;
+    $dest->{$plugin} ||= {};
+
+    for (keys %{$src->{$plugin}})
+    {
+      my $pinst = $_;
+      $dest->{$plugin}{$pinst} ||= {};
+
+      for (keys %{$src->{$plugin}{$pinst}})
+      {
+       my $type = $_;
+       $dest->{$plugin}{$pinst}{$type} ||= {};
+
+       for (keys %{$src->{$plugin}{$pinst}{$type}})
+       {
+         my $tinst = $_;
+         $dest->{$plugin}{$pinst}{$type}{$tinst} = 1;
+       }
+      }
+    }
+  }
+} # _files_union
+
+sub _files_plugin_inst_count
+{
+  my $src = shift;
+  my $i = 0;
+
+  for (keys %$src)
+  {
+    if (exists ($MetaGraphDefs->{$_}))
+    {
+      $i++;
+    }
+    else
+    {
+      $i = $i + keys %{$src->{$_}};
+    }
+  }
+  return ($i);
+} # _files_plugin_count
+
+sub list_hosts
+{
+  my @hosts = _find_hosts ();
+  @hosts = sort (@hosts);
+
+  print "<ul>\n";
+  for (my $i = 0; $i < @hosts; $i++)
+  {
+    my $host_html = encode_entities ($hosts[$i]);
+    my $host_url = uri_escape ($hosts[$i]);
+
+    print qq(  <li><a href="${\script_name ()}?action=show_host;host=$host_url">$host_html</a></li>\n);
+  }
+  print "</ul>\n";
+} # list_hosts
+
+sub _string_to_color
+{
+  my $color = shift;
+  if ($color =~ m/([0-9A-Fa-f][0-9A-Fa-f])([0-9A-Fa-f][0-9A-Fa-f])([0-9A-Fa-f][0-9A-Fa-f])/)
+  {
+    return ([hex ($1) / 255.0, hex ($2) / 255.0, hex ($3) / 255.0]);
+  }
+  return;
+} # _string_to_color
+
+sub _color_to_string
+{
+  confess ("Wrong number of arguments") if (@_ != 1);
+  return (sprintf ('%02hx%02hx%02hx', map { int (255.0 * $_) } @{$_[0]}));
+} # _color_to_string
+
+sub _get_random_color
+{
+  my ($r, $g, $b) = (rand (), rand ());
+  my $min = 0.0;
+  my $max = 1.0;
+
+  if (($r + $g) < 1.0)
+  {
+    $min = 1.0 - ($r + $g);
+  }
+  else
+  {
+    $max = 2.0 - ($r + $g);
+  }
+
+  $b = $min + (rand () * ($max - $min));
+
+  return ([$r, $g, $b]);
+} # _get_random_color
+
+sub _get_n_colors
+{
+       my $instances = shift;
+       my $num = scalar @$instances;
+       my $ret = {};
+
+       for (my $i = 0; $i < $num; $i++)
+       {
+               my $pos = 6 * $i / $num;
+               my $n = int ($pos);
+               my $p = $pos - $n;
+               my $q = 1 - $p;
+
+               my $red   = 0;
+               my $green = 0;
+               my $blue  = 0;
+
+               my $color;
+
+               if ($n == 0)
+               {
+                       $red  = 255;
+                       $blue = 255 * $p;
+               }
+               elsif ($n == 1)
+               {
+                       $red  = 255 * $q;
+                       $blue = 255;
+               }
+               elsif ($n == 2)
+               {
+                       $green = 255 * $p;
+                       $blue  = 255;
+               }
+               elsif ($n == 3)
+               {
+                       $green = 255;
+                       $blue  = 255 * $q;
+               }
+               elsif ($n == 4)
+               {
+                       $red   = 255 * $p;
+                       $green = 255;
+               }
+               elsif ($n == 5)
+               {
+                       $red   = 255;
+                       $green = 255 * $q;
+               }
+               else { die; }
+
+               $color = sprintf ("%02x%02x%02x", $red, $green, $blue);
+               $ret->{$instances->[$i]} = $color;
+       }
+
+       return ($ret);
+} # _get_n_colors
+
+sub _get_faded_color
+{
+  my $fg = shift;
+  my $bg;
+  my %opts = @_;
+  my $ret = [undef, undef, undef];
+
+  $opts{'background'} ||= [1.0, 1.0, 1.0];
+  $opts{'alpha'} ||= 0.25;
+
+  if (!ref ($opts{'background'}))
+  {
+    $opts{'background'} = _string_to_color ($opts{'background'})
+      or confess ("Cannot parse background color " . $opts{'background'});
+  }
+  $bg = $opts{'background'};
+
+  for (my $i = 0; $i < 3; $i++)
+  {
+    $ret->[$i] = ($opts{'alpha'} * $fg->[$i])
+       + ((1.0 - $opts{'alpha'}) * $bg->[$i]);
+  }
+
+  return ($ret);
+} # _get_faded_color
+
+sub _custom_sort_arrayref
+{
+  my $array_ref = shift;
+  my $array_sort = shift;
+
+  my %elements = map { $_ => 1 } (@$array_ref);
+  splice (@$array_ref, 0);
+
+  for (@$array_sort)
+  {
+    next if (!exists ($elements{$_}));
+    push (@$array_ref, $_);
+    delete ($elements{$_});
+  }
+  push (@$array_ref, sort (keys %elements));
+} # _custom_sort_arrayref
+
+sub action_show_host
+{
+  my @hosts = _get_param_host ();
+  @hosts = sort (@hosts);
+
+  my $timespan = _get_param_timespan ();
+  my $all_plugins = _find_files_for_hosts (@hosts);
+
+  my $url_prefix = script_name () . '?action=show_plugin'
+  . join ('', map { ';host=' . uri_escape ($_) } (@hosts))
+  . ';timespan=' . uri_escape ($timespan);
+
+  print qq(    <div><a href="${\script_name ()}?action=overview">Back to list of hosts</a></div>\n);
+
+  print "    <p>Available plugins:</p>\n"
+  . "    <ul>\n";
+  for (sort (keys %$all_plugins))
+  {
+    my $plugin = $_;
+    my $plugin_html = encode_entities ($plugin);
+    my $url_plugin = $url_prefix . ';plugin=' . uri_escape ($plugin);
+    print qq(      <li><a href="$url_plugin">$plugin_html</a></li>\n);
+  }
+  print "   </ul>\n";
+} # action_show_host
+
+sub action_show_plugin
+{
+  my @hosts = _get_param_host ();
+  my $plugin = shift;
+  my $plugin_instance = shift;
+  my $timespan = _get_param_timespan ();
+
+  my $hosts_url = join (';', map { 'host=' . uri_escape ($_) } (@hosts));
+  my $url_prefix = script_name () . "?$hosts_url";
+
+  my $all_plugins = {};
+  my $plugins_per_host = {};
+  my $selected_plugins = {};
+
+  for (my $i = 0; $i < @hosts; $i++)
+  {
+    $plugins_per_host->{$hosts[$i]} = _find_files_for_host ($hosts[$i]);
+    _files_union ($all_plugins, $plugins_per_host->{$hosts[$i]});
+  }
+
+  for (param ('plugin'))
+  {
+    if (defined ($all_plugins->{$_}))
+    {
+      $selected_plugins->{$_} = 1;
+    }
+  }
+
+  print qq(    <div><a href="${\script_name ()}?action=show_host;$hosts_url">Back to list of plugins</a></div>\n);
+
+  # Print table header
+  print <<HTML;
+    <table class="graphs">
+      <tr>
+        <th>Plugins</th>
+HTML
+  for (@hosts)
+  {
+    print "\t<th>", encode_entities ($_), "</th>\n";
+  }
+  print "      </tr>\n";
+
+  for (sort (keys %$selected_plugins))
+  {
+    my $plugin = $_;
+    my $plugin_html = encode_entities ($plugin);
+    my $plugin_url = "$url_prefix;plugin=" . uri_escape ($plugin);
+    my $all_pinst = $all_plugins->{$plugin};
+
+    for (sort (keys %$all_pinst))
+    {
+      my $pinst = $_;
+      my $pinst_html = '';
+      my $pinst_url = $plugin_url;
+
+      if ($pinst ne '-')
+      {
+       $pinst_html = encode_entities ($pinst);
+       $pinst_url .= ';plugin_instance=' . uri_escape ($pinst);
+      }
+
+      my $files_printed = 0;
+      my $files_num = _files_plugin_inst_count ($all_pinst->{$pinst});
+      if ($files_num < 1)
+      {
+       next;
+      }
+      my $rowspan = ($files_num == 1) ? '' : qq( rowspan="$files_num");
+
+      for (sort (keys %{$all_plugins->{$plugin}{$pinst}}))
+      {
+       my $type = $_;
+       my $type_html = encode_entities ($type);
+       my $type_url = "$pinst_url;type=" . uri_escape ($type);
+
+       if ($files_printed == 0)
+       {
+         my $title = $plugin_html;
+         if ($pinst ne '-')
+         {
+           $title .= " ($pinst_html)";
+         }
+         print "      <tr>\n";
+         print "\t<td$rowspan>$title</td>\n";
+       }
+
+       if (exists ($MetaGraphDefs->{$type}))
+       {
+         my $graph_url = script_name () . '?action=show_graph'
+         . ';plugin=' . uri_escape ($plugin)
+         . ';type=' . uri_escape ($type)
+         . ';timespan=' . uri_escape ($timespan);
+         if ($pinst ne '-')
+         {
+           $graph_url .= ';plugin_instance=' . uri_escape ($pinst);
+         }
+
+         if ($files_printed != 0)
+         {
+           print "      <tr>\n";
+         }
+
+         for (@hosts)
+         {
+           my $host = $_;
+           my $host_graph_url = $graph_url . ';host=' . uri_escape ($host);
+
+           print "\t<td>";
+           if (exists $plugins_per_host->{$host}{$plugin}{$pinst}{$type})
+           {
+             print qq(<img src="$host_graph_url" />);
+             #print encode_entities (qq(<img src="${\script_name ()}?action=show_graph;host=$host_esc;$param_plugin;$param_type;timespan=$timespan" />));
+           }
+           print "</td>\n";
+         } # for (my $k = 0; $k < @hosts; $k++)
+
+         print "      </tr>\n";
+
+         $files_printed++;
+         next; # pinst
+       } # if (exists ($MetaGraphDefs->{$type}))
+
+       for (sort (keys %{$all_plugins->{$plugin}{$pinst}{$type}}))
+       {
+         my $tinst = $_;
+         my $tinst_esc = encode_entities ($tinst);
+         my $graph_url = script_name () . '?action=show_graph'
+         . ';plugin=' . uri_escape ($plugin)
+         . ';type=' . uri_escape ($type)
+         . ';timespan=' . uri_escape ($timespan);
+         if ($pinst ne '-')
+         {
+           $graph_url .= ';plugin_instance=' . uri_escape ($pinst);
+         }
+         if ($tinst ne '-')
+         {
+           $graph_url .= ';type_instance=' . uri_escape ($tinst);
+         }
+
+         if ($files_printed != 0)
+         {
+           print "      <tr>\n";
+         }
+
+         for (my $k = 0; $k < @hosts; $k++)
+         {
+           my $host = $hosts[$k];
+           my $host_graph_url = $graph_url . ';host=' . uri_escape ($host);
+
+           print "\t<td>";
+           if ($plugins_per_host->{$host}{$plugin}{$pinst}{$type}{$tinst})
+           {
+             print qq(<img src="$host_graph_url" />);
+             #print encode_entities (qq(<img src="${\script_name ()}?action=show_graph;host=$host_esc;$param_plugin;$param_type;timespan=$timespan" />));
+           }
+           print "</td>\n";
+         } # for (my $k = 0; $k < @hosts; $k++)
+
+         print "      </tr>\n";
+
+         $files_printed++;
+       } # for ($tinst)
+      } # for ($type)
+    } # for ($pinst)
+  } # for ($plugin)
+  print "   </table>\n";
+} # action_show_plugin
+
+sub action_show_type
+{
+  my $host = shift;
+  my $plugin = shift;
+  my $plugin_instance = shift;
+  my $type = shift;
+  my $type_instance = shift;
+
+  my $host_url = uri_escape ($host);
+  my $plugin_url = uri_escape ($plugin);
+  my $plugin_html = encode_entities ($plugin);
+  my $plugin_instance_url = defined ($plugin_instance) ? uri_escape ($plugin_instance) : undef;
+  my $type_url = uri_escape ($type);
+  my $type_instance_url = defined ($type_instance) ? uri_escape ($type_instance) : undef;
+
+  my $url_prefix = script_name () . "?action=show_plugin;host=$host_url;plugin=$plugin_url";
+  $url_prefix .= ";plugin_instance=$plugin_instance_url" if (defined ($plugin_instance));
+
+  print qq(    <div><a href="$url_prefix">Back to plugin &quot;$plugin_html&quot;</a></div>\n);
+
+  $url_prefix = script_name () . "?action=show_graph;host=$host_url;plugin=$plugin_url";
+  $url_prefix .= ";plugin_instance=$plugin_instance_url" if (defined ($plugin_instance));
+  $url_prefix .= ";type=$type_url";
+  $url_prefix .= ";type_instance=$type_instance_url" if (defined ($type_instance));
+
+  for (qw(hour day week month year))
+  {
+    my $timespan = $_;
+
+    print qq#  <div><img src="$url_prefix;timespan=$timespan" /></div>\n#;
+  }
+} # action_show_type
+
+sub action_show_graph
+{
+  my $host = shift;
+  my $plugin = shift;
+  my $plugin_instance = shift;
+  my $type = shift;
+  my $type_instance = shift;
+  my @rrd_args;
+  my $title;
+  
+  my %times = (hour => -3600, day => -86400, week => 7 * -86400, month => 31 * -86400, year => 366 * -86400);
+  my $start_time = $times{$Args->{'timespan'}} || -86400;
+
+  #print STDERR Data::Dumper->Dump ([$Args], ['Args']);
+
+  # FIXME
+  if (exists ($MetaGraphDefs->{$type}))
+  {
+    my %types = _find_types ($host, $plugin, $plugin_instance);
+    return $MetaGraphDefs->{$type}->($host, $plugin, $plugin_instance, $type, $types{$type});
+  }
+
+  return if (!defined ($GraphDefs->{$type}));
+  @rrd_args = @{$GraphDefs->{$type}};
+
+  $title = "$host/$plugin" . (defined ($plugin_instance) ? "-$plugin_instance" : '')
+  . "/$type" . (defined ($type_instance) ? "-$type_instance" : '');
+
+  for (my $i = 0; $i < @DataDirs; $i++)
+  {
+    my $file = $DataDirs[$i] . "/$title.rrd";
+    next if (!-f $file);
+
+    $file =~ s/:/\\:/g;
+    s/{file}/$file/ for (@rrd_args);
+
+    RRDs::graph ('-', '-a', 'PNG', '-s', $start_time, '-t', $title, @RRDDefaultArgs, @rrd_args);
+    if (my $err = RRDs::error ())
+    {
+      die ("RRDs::graph: $err");
+    }
+  }
+} # action_show_graph
+
+sub print_selector
+{
+  my @hosts = _find_hosts ();
+  @hosts = sort (@hosts);
+
+  my %selected_hosts = map { $_ => 1 } (_get_param_host ());
+  my $timespan_selected = _get_param_timespan ();
+
+  print <<HTML;
+    <form action="${\script_name ()}" method="get">
+      <fieldset>
+       <legend>Selector</legend>
+       <select name="host" multiple="multiple" size="10">
+HTML
+  for (my $i = 0; $i < @hosts; $i++)
+  {
+    my $host = encode_entities ($hosts[$i]);
+    my $selected = defined ($selected_hosts{$hosts[$i]}) ? ' selected="selected"' : '';
+    print qq(\t  <option value="$host"$selected>$host</option>\n);
+  }
+  print "\t</select>\n";
+
+  if (keys %selected_hosts)
+  {
+    my $all_plugins = _find_files_for_hosts (keys %selected_hosts);
+    my %selected_plugins = map { $_ => 1 } (param ('plugin'));
+
+    print qq(\t<select name="plugin" multiple="multiple" size="10">\n);
+    for (sort (keys %$all_plugins))
+    {
+      my $plugin = $_;
+      my $plugin_html = encode_entities ($plugin);
+      my $selected = (defined ($selected_plugins{$plugin})
+       ? ' selected="selected"' : '');
+      print qq(\t  <option value="$plugin_html"$selected>$plugin</option>\n);
+    }
+    print "</select>\n";
+  } # if (keys %selected_hosts)
+
+  print qq(\t<select name="timespan">\n);
+  for (qw(Hour Day Week Month Year))
+  {
+    my $timespan_uc = $_;
+    my $timespan_lc = lc ($_);
+    my $selected = ($timespan_selected eq $timespan_lc)
+      ? ' selected="selected"' : '';
+    print qq(\t  <option value="$timespan_lc"$selected>$timespan_uc</option>\n);
+  }
+  print <<HTML;
+       </select>
+       <input type="submit" name="button" value="Ok" />
+      </fieldset>
+    </form>
+HTML
+}
+
+sub print_header
+{
+  print <<HEAD;
+Content-Type: application/xhtml+xml; charset=utf-8
+Cache-Control: no-cache
+
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+  <head>
+    <title>collection.cgi, Version 2</title>
+    <style type="text/css">
+      img
+      {
+       border: none;
+      }
+      table.graphs
+      {
+       border-collapse: collapse;
+      }
+      table.graphs td,
+      table.graphs th
+      {
+       border: 1px solid black;
+       empty-cells: hide;
+      }
+    </style>
+  </head>
+
+  <body>
+HEAD
+  print_selector ();
+} # print_header
+
+sub print_footer
+{
+  print <<FOOT;
+  </body>
+</html>
+FOOT
+} # print_footer
+
+sub main
+{
+       read_config ();
+       validate_args ();
+
+       if (defined ($Args->{'host'})
+         && defined ($Args->{'plugin'})
+         && defined ($Args->{'type'})
+         && ($Args->{'action'} eq 'show_graph'))
+       {
+         $| = 1;
+         print STDOUT header (-Content_Type => 'image/png');
+         action_show_graph ($Args->{'host'},
+           $Args->{'plugin'}, $Args->{'plugin_instance'},
+           $Args->{'type'}, $Args->{'type_instance'});
+         return (0);
+       }
+
+       print_header ();
+
+       if (!$Args->{'host'})
+       {
+         list_hosts ();
+       }
+       elsif (!$Args->{'plugin'})
+       {
+         action_show_host ($Args->{'host'});
+       }
+       elsif (!$Args->{'type'})
+       {
+         action_show_plugin ($Args->{'plugin'}, $Args->{'plugin_instance'});
+       }
+       else
+       {
+         action_show_type ($Args->{'host'},
+           $Args->{'plugin'}, $Args->{'plugin_instance'},
+           $Args->{'type'}, $Args->{'type_instance'});
+       }
+
+       print_footer ();
+
+       return (0);
+}
+
+sub load_graph_definitions
+{
+  my $Canvas = 'FFFFFF';
+
+  my $FullRed    = 'FF0000';
+  my $FullGreen  = '00E000';
+  my $FullBlue   = '0000FF';
+  my $FullYellow = 'F0A000';
+  my $FullCyan   = '00A0FF';
+  my $FullMagenta= 'A000FF';
+
+  my $HalfRed    = 'F7B7B7';
+  my $HalfGreen  = 'B7EFB7';
+  my $HalfBlue   = 'B7B7F7';
+  my $HalfYellow = 'F3DFB7';
+  my $HalfCyan   = 'B7DFF7';
+  my $HalfMagenta= 'DFB7F7';
+
+  my $HalfBlueGreen = '89B3C9';
+
+  $GraphDefs =
+  {
+    apache_bytes => ['DEF:min_raw={file}:count:MIN',
+    'DEF:avg_raw={file}:count:AVERAGE',
+    'DEF:max_raw={file}:count:MAX',
+    'CDEF:min=min_raw,8,*',
+    'CDEF:avg=avg_raw,8,*',
+    'CDEF:max=max_raw,8,*',
+    'CDEF:mytime=avg_raw,TIME,TIME,IF',
+    'CDEF:sample_len_raw=mytime,PREV(mytime),-',
+    'CDEF:sample_len=sample_len_raw,UN,0,sample_len_raw,IF',
+    'CDEF:avg_sample=avg_raw,UN,0,avg_raw,IF,sample_len,*',
+    'CDEF:avg_sum=PREV,UN,0,PREV,IF,avg_sample,+',
+    "AREA:avg#$HalfBlue",
+    "LINE1:avg#$FullBlue:Bit/s",
+    'GPRINT:min:MIN:%5.1lf%s Min,',
+    'GPRINT:avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:max:MAX:%5.1lf%s Max,',
+    'GPRINT:avg:LAST:%5.1lf%s Last',
+    'GPRINT:avg_sum:LAST:(ca. %5.1lf%sB Total)\l'
+    ],
+   apache_connections => ['DEF:min={file}:count:MIN',
+    'DEF:avg={file}:count:AVERAGE',
+    'DEF:max={file}:count:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Connections",
+    'GPRINT:min:MIN:%6.2lf Min,',
+    'GPRINT:avg:AVERAGE:%6.2lf Avg,',
+    'GPRINT:max:MAX:%6.2lf Max,',
+    'GPRINT:avg:LAST:%6.2lf Last'
+    ],
+    apache_idle_workers => ['DEF:min={file}:count:MIN',
+    'DEF:avg={file}:count:AVERAGE',
+    'DEF:max={file}:count:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Idle Workers",
+    'GPRINT:min:MIN:%6.2lf Min,',
+    'GPRINT:avg:AVERAGE:%6.2lf Avg,',
+    'GPRINT:max:MAX:%6.2lf Max,',
+    'GPRINT:avg:LAST:%6.2lf Last'
+    ],
+    apache_requests => ['DEF:min={file}:count:MIN',
+    'DEF:avg={file}:count:AVERAGE',
+    'DEF:max={file}:count:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Requests/s",
+    'GPRINT:min:MIN:%6.2lf Min,',
+    'GPRINT:avg:AVERAGE:%6.2lf Avg,',
+    'GPRINT:max:MAX:%6.2lf Max,',
+    'GPRINT:avg:LAST:%6.2lf Last'
+    ],
+    apache_scoreboard => ['DEF:min={file}:count:MIN',
+    'DEF:avg={file}:count:AVERAGE',
+    'DEF:max={file}:count:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Processes",
+    'GPRINT:min:MIN:%6.2lf Min,',
+    'GPRINT:avg:AVERAGE:%6.2lf Avg,',
+    'GPRINT:max:MAX:%6.2lf Max,',
+    'GPRINT:avg:LAST:%6.2lf Last'
+    ],
+    bitrate => ['-v', 'Bits/s',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Bits/s",
+    'GPRINT:min:MIN:%5.1lf%s Min,',
+    'GPRINT:avg:AVERAGE:%5.1lf%s Average,',
+    'GPRINT:max:MAX:%5.1lf%s Max,',
+    'GPRINT:avg:LAST:%5.1lf%s Last\l'
+    ],
+    charge => ['-v', 'Ah',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Charge",
+    'GPRINT:min:MIN:%5.1lf%sAh Min,',
+    'GPRINT:avg:AVERAGE:%5.1lf%sAh Avg,',
+    'GPRINT:max:MAX:%5.1lf%sAh Max,',
+    'GPRINT:avg:LAST:%5.1lf%sAh Last\l'
+    ],
+    connections => ['-v', 'Connections',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Connections",
+    'GPRINT:min:MIN:%4.1lf Min,',
+    'GPRINT:avg:AVERAGE:%4.1lf Avg,',
+    'GPRINT:max:MAX:%4.1lf Max,',
+    'GPRINT:avg:LAST:%4.1lf Last\l'
+    ],
+    cpu => ['-v', 'CPU load',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Percent",
+    'GPRINT:min:MIN:%6.2lf%% Min,',
+    'GPRINT:avg:AVERAGE:%6.2lf%% Avg,',
+    'GPRINT:max:MAX:%6.2lf%% Max,',
+    'GPRINT:avg:LAST:%6.2lf%% Last\l'
+    ],
+    current => ['-v', 'Ampere',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Current",
+    'GPRINT:min:MIN:%5.1lf%sA Min,',
+    'GPRINT:avg:AVERAGE:%5.1lf%sA Avg,',
+    'GPRINT:max:MAX:%5.1lf%sA Max,',
+    'GPRINT:avg:LAST:%5.1lf%sA Last\l'
+    ],
+    df => ['-v', 'Percent', '-l', '0',
+    'DEF:free_avg={file}:free:AVERAGE',
+    'DEF:free_min={file}:free:MIN',
+    'DEF:free_max={file}:free:MAX',
+    'DEF:used_avg={file}:used:AVERAGE',
+    'DEF:used_min={file}:used:MIN',
+    'DEF:used_max={file}:used:MAX',
+    'CDEF:total=free_avg,used_avg,+',
+    'CDEF:free_pct=100,free_avg,*,total,/',
+    'CDEF:used_pct=100,used_avg,*,total,/',
+    'CDEF:free_acc=free_pct,used_pct,+',
+    'CDEF:used_acc=used_pct',
+    "AREA:free_acc#$HalfGreen",
+    "AREA:used_acc#$HalfRed",
+    "LINE1:free_acc#$FullGreen:Free",
+    'GPRINT:free_min:MIN:%5.1lf%sB Min,',
+    'GPRINT:free_avg:AVERAGE:%5.1lf%sB Avg,',
+    'GPRINT:free_max:MAX:%5.1lf%sB Max,',
+    'GPRINT:free_avg:LAST:%5.1lf%sB Last\l',
+    "LINE1:used_acc#$FullRed:Used",
+    'GPRINT:used_min:MIN:%5.1lf%sB Min,',
+    'GPRINT:used_avg:AVERAGE:%5.1lf%sB Avg,',
+    'GPRINT:used_max:MAX:%5.1lf%sB Max,',
+    'GPRINT:used_avg:LAST:%5.1lf%sB Last\l'
+    ],
+    disk => [
+    'DEF:rtime_avg={file}:rtime:AVERAGE',
+    'DEF:rtime_min={file}:rtime:MIN',
+    'DEF:rtime_max={file}:rtime:MAX',
+    'DEF:wtime_avg={file}:wtime:AVERAGE',
+    'DEF:wtime_min={file}:wtime:MIN',
+    'DEF:wtime_max={file}:wtime:MAX',
+    'CDEF:rtime_avg_ms=rtime_avg,1000,/',
+    'CDEF:rtime_min_ms=rtime_min,1000,/',
+    'CDEF:rtime_max_ms=rtime_max,1000,/',
+    'CDEF:wtime_avg_ms=wtime_avg,1000,/',
+    'CDEF:wtime_min_ms=wtime_min,1000,/',
+    'CDEF:wtime_max_ms=wtime_max,1000,/',
+    'CDEF:total_avg_ms=rtime_avg_ms,wtime_avg_ms,+',
+    'CDEF:total_min_ms=rtime_min_ms,wtime_min_ms,+',
+    'CDEF:total_max_ms=rtime_max_ms,wtime_max_ms,+',
+    "AREA:total_max_ms#$HalfRed",
+    "AREA:total_min_ms#$Canvas",
+    "LINE1:wtime_avg_ms#$FullGreen:Write",
+    'GPRINT:wtime_min_ms:MIN:%5.1lf%s Min,',
+    'GPRINT:wtime_avg_ms:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:wtime_max_ms:MAX:%5.1lf%s Max,',
+    'GPRINT:wtime_avg_ms:LAST:%5.1lf%s Last\n',
+    "LINE1:rtime_avg_ms#$FullBlue:Read ",
+    'GPRINT:rtime_min_ms:MIN:%5.1lf%s Min,',
+    'GPRINT:rtime_avg_ms:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:rtime_max_ms:MAX:%5.1lf%s Max,',
+    'GPRINT:rtime_avg_ms:LAST:%5.1lf%s Last\n',
+    "LINE1:total_avg_ms#$FullRed:Total",
+    'GPRINT:total_min_ms:MIN:%5.1lf%s Min,',
+    'GPRINT:total_avg_ms:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:total_max_ms:MAX:%5.1lf%s Max,',
+    'GPRINT:total_avg_ms:LAST:%5.1lf%s Last'
+    ],
+    disk_octets => ['-v', 'Bytes/s',
+    'DEF:out_min={file}:write:MIN',
+    'DEF:out_avg={file}:write:AVERAGE',
+    'DEF:out_max={file}:write:MAX',
+    'DEF:inc_min={file}:read:MIN',
+    'DEF:inc_avg={file}:read:AVERAGE',
+    'DEF:inc_max={file}:read:MAX',
+    'CDEF:overlap=out_avg,inc_avg,GT,inc_avg,out_avg,IF',
+    'CDEF:mytime=out_avg,TIME,TIME,IF',
+    'CDEF:sample_len_raw=mytime,PREV(mytime),-',
+    'CDEF:sample_len=sample_len_raw,UN,0,sample_len_raw,IF',
+    'CDEF:out_avg_sample=out_avg,UN,0,out_avg,IF,sample_len,*',
+    'CDEF:out_avg_sum=PREV,UN,0,PREV,IF,out_avg_sample,+',
+    'CDEF:inc_avg_sample=inc_avg,UN,0,inc_avg,IF,sample_len,*',
+    'CDEF:inc_avg_sum=PREV,UN,0,PREV,IF,inc_avg_sample,+',
+    "AREA:out_avg#$HalfGreen",
+    "AREA:inc_avg#$HalfBlue",
+    "AREA:overlap#$HalfBlueGreen",
+    "LINE1:out_avg#$FullGreen:Written",
+    'GPRINT:out_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:out_max:MAX:%5.1lf%s Max,',
+    'GPRINT:out_avg:LAST:%5.1lf%s Last',
+    'GPRINT:out_avg_sum:LAST:(ca. %5.1lf%sB Total)\l',
+    "LINE1:inc_avg#$FullBlue:Read   ",
+    'GPRINT:inc_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:inc_max:MAX:%5.1lf%s Max,',
+    'GPRINT:inc_avg:LAST:%5.1lf%s Last',
+    'GPRINT:inc_avg_sum:LAST:(ca. %5.1lf%sB Total)\l'
+    ],
+    disk_merged => ['-v', 'Merged Ops/s',
+    'DEF:out_min={file}:write:MIN',
+    'DEF:out_avg={file}:write:AVERAGE',
+    'DEF:out_max={file}:write:MAX',
+    'DEF:inc_min={file}:read:MIN',
+    'DEF:inc_avg={file}:read:AVERAGE',
+    'DEF:inc_max={file}:read:MAX',
+    'CDEF:overlap=out_avg,inc_avg,GT,inc_avg,out_avg,IF',
+    "AREA:out_avg#$HalfGreen",
+    "AREA:inc_avg#$HalfBlue",
+    "AREA:overlap#$HalfBlueGreen",
+    "LINE1:out_avg#$FullGreen:Written",
+    'GPRINT:out_avg:AVERAGE:%6.2lf Avg,',
+    'GPRINT:out_max:MAX:%6.2lf Max,',
+    'GPRINT:out_avg:LAST:%6.2lf Last\l',
+    "LINE1:inc_avg#$FullBlue:Read   ",
+    'GPRINT:inc_avg:AVERAGE:%6.2lf Avg,',
+    'GPRINT:inc_max:MAX:%6.2lf Max,',
+    'GPRINT:inc_avg:LAST:%6.2lf Last\l'
+    ],
+    disk_ops => ['-v', 'Ops/s',
+    'DEF:out_min={file}:write:MIN',
+    'DEF:out_avg={file}:write:AVERAGE',
+    'DEF:out_max={file}:write:MAX',
+    'DEF:inc_min={file}:read:MIN',
+    'DEF:inc_avg={file}:read:AVERAGE',
+    'DEF:inc_max={file}:read:MAX',
+    'CDEF:overlap=out_avg,inc_avg,GT,inc_avg,out_avg,IF',
+    "AREA:out_avg#$HalfGreen",
+    "AREA:inc_avg#$HalfBlue",
+    "AREA:overlap#$HalfBlueGreen",
+    "LINE1:out_avg#$FullGreen:Written",
+    'GPRINT:out_avg:AVERAGE:%6.2lf Avg,',
+    'GPRINT:out_max:MAX:%6.2lf Max,',
+    'GPRINT:out_avg:LAST:%6.2lf Last\l',
+    "LINE1:inc_avg#$FullBlue:Read   ",
+    'GPRINT:inc_avg:AVERAGE:%6.2lf Avg,',
+    'GPRINT:inc_max:MAX:%6.2lf Max,',
+    'GPRINT:inc_avg:LAST:%6.2lf Last\l'
+    ],
+    disk_time => ['-v', 'Seconds/s',
+    'DEF:out_min_raw={file}:write:MIN',
+    'DEF:out_avg_raw={file}:write:AVERAGE',
+    'DEF:out_max_raw={file}:write:MAX',
+    'DEF:inc_min_raw={file}:read:MIN',
+    'DEF:inc_avg_raw={file}:read:AVERAGE',
+    'DEF:inc_max_raw={file}:read:MAX',
+    'CDEF:out_min=out_min_raw,1000,/',
+    'CDEF:out_avg=out_avg_raw,1000,/',
+    'CDEF:out_max=out_max_raw,1000,/',
+    'CDEF:inc_min=inc_min_raw,1000,/',
+    'CDEF:inc_avg=inc_avg_raw,1000,/',
+    'CDEF:inc_max=inc_max_raw,1000,/',
+    'CDEF:overlap=out_avg,inc_avg,GT,inc_avg,out_avg,IF',
+    "AREA:out_avg#$HalfGreen",
+    "AREA:inc_avg#$HalfBlue",
+    "AREA:overlap#$HalfBlueGreen",
+    "LINE1:out_avg#$FullGreen:Written",
+    'GPRINT:out_avg:AVERAGE:%5.1lf%ss Avg,',
+    'GPRINT:out_max:MAX:%5.1lf%ss Max,',
+    'GPRINT:out_avg:LAST:%5.1lf%ss Last\l',
+    "LINE1:inc_avg#$FullBlue:Read   ",
+    'GPRINT:inc_avg:AVERAGE:%5.1lf%ss Avg,',
+    'GPRINT:inc_max:MAX:%5.1lf%ss Max,',
+    'GPRINT:inc_avg:LAST:%5.1lf%ss Last\l'
+    ],
+    dns_octets => ['DEF:rsp_min_raw={file}:responses:MIN',
+    'DEF:rsp_avg_raw={file}:responses:AVERAGE',
+    'DEF:rsp_max_raw={file}:responses:MAX',
+    'DEF:qry_min_raw={file}:queries:MIN',
+    'DEF:qry_avg_raw={file}:queries:AVERAGE',
+    'DEF:qry_max_raw={file}:queries:MAX',
+    'CDEF:rsp_min=rsp_min_raw,8,*',
+    'CDEF:rsp_avg=rsp_avg_raw,8,*',
+    'CDEF:rsp_max=rsp_max_raw,8,*',
+    'CDEF:qry_min=qry_min_raw,8,*',
+    'CDEF:qry_avg=qry_avg_raw,8,*',
+    'CDEF:qry_max=qry_max_raw,8,*',
+    'CDEF:overlap=rsp_avg,qry_avg,GT,qry_avg,rsp_avg,IF',
+    'CDEF:mytime=rsp_avg_raw,TIME,TIME,IF',
+    'CDEF:sample_len_raw=mytime,PREV(mytime),-',
+    'CDEF:sample_len=sample_len_raw,UN,0,sample_len_raw,IF',
+    'CDEF:rsp_avg_sample=rsp_avg_raw,UN,0,rsp_avg_raw,IF,sample_len,*',
+    'CDEF:rsp_avg_sum=PREV,UN,0,PREV,IF,rsp_avg_sample,+',
+    'CDEF:qry_avg_sample=qry_avg_raw,UN,0,qry_avg_raw,IF,sample_len,*',
+    'CDEF:qry_avg_sum=PREV,UN,0,PREV,IF,qry_avg_sample,+',
+    "AREA:rsp_avg#$HalfGreen",
+    "AREA:qry_avg#$HalfBlue",
+    "AREA:overlap#$HalfBlueGreen",
+    "LINE1:rsp_avg#$FullGreen:Responses",
+    'GPRINT:rsp_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:rsp_max:MAX:%5.1lf%s Max,',
+    'GPRINT:rsp_avg:LAST:%5.1lf%s Last',
+    'GPRINT:rsp_avg_sum:LAST:(ca. %5.1lf%sB Total)\l',
+    "LINE1:qry_avg#$FullBlue:Queries  ",
+    #'GPRINT:qry_min:MIN:%5.1lf %s Min,',
+    'GPRINT:qry_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:qry_max:MAX:%5.1lf%s Max,',
+    'GPRINT:qry_avg:LAST:%5.1lf%s Last',
+    'GPRINT:qry_avg_sum:LAST:(ca. %5.1lf%sB Total)\l'
+    ],
+    dns_opcode => [
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Queries/s",
+    'GPRINT:min:MIN:%9.3lf Min,',
+    'GPRINT:avg:AVERAGE:%9.3lf Average,',
+    'GPRINT:max:MAX:%9.3lf Max,',
+    'GPRINT:avg:LAST:%9.3lf Last\l'
+    ],
+    email_count => ['-v', 'Mails',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfMagenta",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullMagenta:Count ",
+    'GPRINT:min:MIN:%4.1lf Min,',
+    'GPRINT:avg:AVERAGE:%4.1lf Avg,',
+    'GPRINT:max:MAX:%4.1lf Max,',
+    'GPRINT:avg:LAST:%4.1lf Last\l'
+    ],
+    email_size => ['-v', 'Bytes',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfMagenta",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullMagenta:Count ",
+    'GPRINT:min:MIN:%4.1lf Min,',
+    'GPRINT:avg:AVERAGE:%4.1lf Avg,',
+    'GPRINT:max:MAX:%4.1lf Max,',
+    'GPRINT:avg:LAST:%4.1lf Last\l'
+    ],
+    spam_score => ['-v', 'Score',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Score ",
+    'GPRINT:min:MIN:%4.1lf Min,',
+    'GPRINT:avg:AVERAGE:%4.1lf Avg,',
+    'GPRINT:max:MAX:%4.1lf Max,',
+    'GPRINT:avg:LAST:%4.1lf Last\l'
+    ],
+    spam_check => [
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfMagenta",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullMagenta:Count ",
+    'GPRINT:min:MIN:%4.1lf Min,',
+    'GPRINT:avg:AVERAGE:%4.1lf Avg,',
+    'GPRINT:max:MAX:%4.1lf Max,',
+    'GPRINT:avg:LAST:%4.1lf Last\l'
+    ],
+    conntrack => ['-v', 'Entries',
+    'DEF:avg={file}:entropy:AVERAGE',
+    'DEF:min={file}:entropy:MIN',
+    'DEF:max={file}:entropy:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Count",
+    'GPRINT:min:MIN:%4.0lf Min,',
+    'GPRINT:avg:AVERAGE:%4.0lf Avg,',
+    'GPRINT:max:MAX:%4.0lf Max,',
+    'GPRINT:avg:LAST:%4.0lf Last\l'
+    ],
+    entropy => ['-v', 'Bits',
+    'DEF:avg={file}:entropy:AVERAGE',
+    'DEF:min={file}:entropy:MIN',
+    'DEF:max={file}:entropy:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Bits",
+    'GPRINT:min:MIN:%4.0lfbit Min,',
+    'GPRINT:avg:AVERAGE:%4.0lfbit Avg,',
+    'GPRINT:max:MAX:%4.0lfbit Max,',
+    'GPRINT:avg:LAST:%4.0lfbit Last\l'
+    ],
+    fanspeed => ['-v', 'RPM',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfMagenta",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullMagenta:RPM",
+    'GPRINT:min:MIN:%4.1lf Min,',
+    'GPRINT:avg:AVERAGE:%4.1lf Avg,',
+    'GPRINT:max:MAX:%4.1lf Max,',
+    'GPRINT:avg:LAST:%4.1lf Last\l'
+    ],
+    frequency => ['-v', 'Hertz',
+    'DEF:avg={file}:frequency:AVERAGE',
+    'DEF:min={file}:frequency:MIN',
+    'DEF:max={file}:frequency:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Frequency [Hz]",
+    'GPRINT:min:MIN:%4.1lf Min,',
+    'GPRINT:avg:AVERAGE:%4.1lf Avg,',
+    'GPRINT:max:MAX:%4.1lf Max,',
+    'GPRINT:avg:LAST:%4.1lf Last\l'
+    ],
+    frequency_offset => [ # NTPd
+    'DEF:ppm_avg={file}:ppm:AVERAGE',
+    'DEF:ppm_min={file}:ppm:MIN',
+    'DEF:ppm_max={file}:ppm:MAX',
+    "AREA:ppm_max#$HalfBlue",
+    "AREA:ppm_min#$Canvas",
+    "LINE1:ppm_avg#$FullBlue:{inst}",
+    'GPRINT:ppm_min:MIN:%5.2lf Min,',
+    'GPRINT:ppm_avg:AVERAGE:%5.2lf Avg,',
+    'GPRINT:ppm_max:MAX:%5.2lf Max,',
+    'GPRINT:ppm_avg:LAST:%5.2lf Last'
+    ],
+    gauge => ['-v', 'Exec value',
+    'DEF:temp_avg={file}:value:AVERAGE',
+    'DEF:temp_min={file}:value:MIN',
+    'DEF:temp_max={file}:value:MAX',
+    "AREA:temp_max#$HalfBlue",
+    "AREA:temp_min#$Canvas",
+    "LINE1:temp_avg#$FullBlue:Exec value",
+    'GPRINT:temp_min:MIN:%6.2lf Min,',
+    'GPRINT:temp_avg:AVERAGE:%6.2lf Avg,',
+    'GPRINT:temp_max:MAX:%6.2lf Max,',
+    'GPRINT:temp_avg:LAST:%6.2lf Last\l'
+    ],
+    hddtemp => [
+    'DEF:temp_avg={file}:value:AVERAGE',
+    'DEF:temp_min={file}:value:MIN',
+    'DEF:temp_max={file}:value:MAX',
+    "AREA:temp_max#$HalfRed",
+    "AREA:temp_min#$Canvas",
+    "LINE1:temp_avg#$FullRed:Temperature",
+    'GPRINT:temp_min:MIN:%4.1lf Min,',
+    'GPRINT:temp_avg:AVERAGE:%4.1lf Avg,',
+    'GPRINT:temp_max:MAX:%4.1lf Max,',
+    'GPRINT:temp_avg:LAST:%4.1lf Last\l'
+    ],
+    humidity => ['-v', 'Percent',
+    'DEF:temp_avg={file}:value:AVERAGE',
+    'DEF:temp_min={file}:value:MIN',
+    'DEF:temp_max={file}:value:MAX',
+    "AREA:temp_max#$HalfGreen",
+    "AREA:temp_min#$Canvas",
+    "LINE1:temp_avg#$FullGreen:Temperature",
+    'GPRINT:temp_min:MIN:%4.1lf%% Min,',
+    'GPRINT:temp_avg:AVERAGE:%4.1lf%% Avg,',
+    'GPRINT:temp_max:MAX:%4.1lf%% Max,',
+    'GPRINT:temp_avg:LAST:%4.1lf%% Last\l'
+    ],
+    if_errors => ['-v', 'Errors/s',
+    'DEF:tx_min={file}:tx:MIN',
+    'DEF:tx_avg={file}:tx:AVERAGE',
+    'DEF:tx_max={file}:tx:MAX',
+    'DEF:rx_min={file}:rx:MIN',
+    'DEF:rx_avg={file}:rx:AVERAGE',
+    'DEF:rx_max={file}:rx:MAX',
+    'CDEF:overlap=tx_avg,rx_avg,GT,rx_avg,tx_avg,IF',
+    'CDEF:mytime=tx_avg,TIME,TIME,IF',
+    'CDEF:sample_len_raw=mytime,PREV(mytime),-',
+    'CDEF:sample_len=sample_len_raw,UN,0,sample_len_raw,IF',
+    'CDEF:tx_avg_sample=tx_avg,UN,0,tx_avg,IF,sample_len,*',
+    'CDEF:tx_avg_sum=PREV,UN,0,PREV,IF,tx_avg_sample,+',
+    'CDEF:rx_avg_sample=rx_avg,UN,0,rx_avg,IF,sample_len,*',
+    'CDEF:rx_avg_sum=PREV,UN,0,PREV,IF,rx_avg_sample,+',
+    "AREA:tx_avg#$HalfGreen",
+    "AREA:rx_avg#$HalfBlue",
+    "AREA:overlap#$HalfBlueGreen",
+    "LINE1:tx_avg#$FullGreen:TX",
+    'GPRINT:tx_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:tx_max:MAX:%5.1lf%s Max,',
+    'GPRINT:tx_avg:LAST:%5.1lf%s Last',
+    'GPRINT:tx_avg_sum:LAST:(ca. %4.0lf%s Total)\l',
+    "LINE1:rx_avg#$FullBlue:RX",
+    #'GPRINT:rx_min:MIN:%5.1lf %s Min,',
+    'GPRINT:rx_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:rx_max:MAX:%5.1lf%s Max,',
+    'GPRINT:rx_avg:LAST:%5.1lf%s Last',
+    'GPRINT:rx_avg_sum:LAST:(ca. %4.0lf%s Total)\l'
+    ],
+    if_collisions => ['-v', 'Collisions/s',
+    'DEF:min_raw={file}:value:MIN',
+    'DEF:avg_raw={file}:value:AVERAGE',
+    'DEF:max_raw={file}:value:MAX',
+    'CDEF:min=min_raw,8,*',
+    'CDEF:avg=avg_raw,8,*',
+    'CDEF:max=max_raw,8,*',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Collisions/s",
+    'GPRINT:min:MIN:%5.1lf %s Min,',
+    'GPRINT:avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:max:MAX:%5.1lf%s Max,',
+    'GPRINT:avg:LAST:%5.1lf%s Last\l'
+    ],
+    if_dropped => ['-v', 'Packets/s',
+    'DEF:tx_min={file}:tx:MIN',
+    'DEF:tx_avg={file}:tx:AVERAGE',
+    'DEF:tx_max={file}:tx:MAX',
+    'DEF:rx_min={file}:rx:MIN',
+    'DEF:rx_avg={file}:rx:AVERAGE',
+    'DEF:rx_max={file}:rx:MAX',
+    'CDEF:overlap=tx_avg,rx_avg,GT,rx_avg,tx_avg,IF',
+    'CDEF:mytime=tx_avg,TIME,TIME,IF',
+    'CDEF:sample_len_raw=mytime,PREV(mytime),-',
+    'CDEF:sample_len=sample_len_raw,UN,0,sample_len_raw,IF',
+    'CDEF:tx_avg_sample=tx_avg,UN,0,tx_avg,IF,sample_len,*',
+    'CDEF:tx_avg_sum=PREV,UN,0,PREV,IF,tx_avg_sample,+',
+    'CDEF:rx_avg_sample=rx_avg,UN,0,rx_avg,IF,sample_len,*',
+    'CDEF:rx_avg_sum=PREV,UN,0,PREV,IF,rx_avg_sample,+',
+    "AREA:tx_avg#$HalfGreen",
+    "AREA:rx_avg#$HalfBlue",
+    "AREA:overlap#$HalfBlueGreen",
+    "LINE1:tx_avg#$FullGreen:TX",
+    'GPRINT:tx_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:tx_max:MAX:%5.1lf%s Max,',
+    'GPRINT:tx_avg:LAST:%5.1lf%s Last',
+    'GPRINT:tx_avg_sum:LAST:(ca. %4.0lf%s Total)\l',
+    "LINE1:rx_avg#$FullBlue:RX",
+    #'GPRINT:rx_min:MIN:%5.1lf %s Min,',
+    'GPRINT:rx_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:rx_max:MAX:%5.1lf%s Max,',
+    'GPRINT:rx_avg:LAST:%5.1lf%s Last',
+    'GPRINT:rx_avg_sum:LAST:(ca. %4.0lf%s Total)\l'
+    ],
+    if_packets => ['-v', 'Packets/s',
+    'DEF:tx_min={file}:tx:MIN',
+    'DEF:tx_avg={file}:tx:AVERAGE',
+    'DEF:tx_max={file}:tx:MAX',
+    'DEF:rx_min={file}:rx:MIN',
+    'DEF:rx_avg={file}:rx:AVERAGE',
+    'DEF:rx_max={file}:rx:MAX',
+    'CDEF:overlap=tx_avg,rx_avg,GT,rx_avg,tx_avg,IF',
+    'CDEF:mytime=tx_avg,TIME,TIME,IF',
+    'CDEF:sample_len_raw=mytime,PREV(mytime),-',
+    'CDEF:sample_len=sample_len_raw,UN,0,sample_len_raw,IF',
+    'CDEF:tx_avg_sample=tx_avg,UN,0,tx_avg,IF,sample_len,*',
+    'CDEF:tx_avg_sum=PREV,UN,0,PREV,IF,tx_avg_sample,+',
+    'CDEF:rx_avg_sample=rx_avg,UN,0,rx_avg,IF,sample_len,*',
+    'CDEF:rx_avg_sum=PREV,UN,0,PREV,IF,rx_avg_sample,+',
+    "AREA:tx_avg#$HalfGreen",
+    "AREA:rx_avg#$HalfBlue",
+    "AREA:overlap#$HalfBlueGreen",
+    "LINE1:tx_avg#$FullGreen:TX",
+    'GPRINT:tx_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:tx_max:MAX:%5.1lf%s Max,',
+    'GPRINT:tx_avg:LAST:%5.1lf%s Last',
+    'GPRINT:tx_avg_sum:LAST:(ca. %4.0lf%s Total)\l',
+    "LINE1:rx_avg#$FullBlue:RX",
+    #'GPRINT:rx_min:MIN:%5.1lf %s Min,',
+    'GPRINT:rx_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:rx_max:MAX:%5.1lf%s Max,',
+    'GPRINT:rx_avg:LAST:%5.1lf%s Last',
+    'GPRINT:rx_avg_sum:LAST:(ca. %4.0lf%s Total)\l'
+    ],
+    if_rx_errors => ['-v', 'Errors/s',
+    'DEF:min={file}:value:MIN',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:max={file}:value:MAX',
+    'CDEF:mytime=avg,TIME,TIME,IF',
+    'CDEF:sample_len_raw=mytime,PREV(mytime),-',
+    'CDEF:sample_len=sample_len_raw,UN,0,sample_len_raw,IF',
+    'CDEF:avg_sample=avg,UN,0,avg,IF,sample_len,*',
+    'CDEF:avg_sum=PREV,UN,0,PREV,IF,avg_sample,+',
+    "AREA:avg#$HalfBlue",
+    "LINE1:avg#$FullBlue:Errors/s",
+    'GPRINT:avg:AVERAGE:%3.1lf%s Avg,',
+    'GPRINT:max:MAX:%3.1lf%s Max,',
+    'GPRINT:avg:LAST:%3.1lf%s Last',
+    'GPRINT:avg_sum:LAST:(ca. %2.0lf%s Total)\l'
+    ],
+    ipt_bytes => ['-v', 'Bits/s',
+    'DEF:min_raw={file}:value:MIN',
+    'DEF:avg_raw={file}:value:AVERAGE',
+    'DEF:max_raw={file}:value:MAX',
+    'CDEF:min=min_raw,8,*',
+    'CDEF:avg=avg_raw,8,*',
+    'CDEF:max=max_raw,8,*',
+    'CDEF:mytime=avg_raw,TIME,TIME,IF',
+    'CDEF:sample_len_raw=mytime,PREV(mytime),-',
+    'CDEF:sample_len=sample_len_raw,UN,0,sample_len_raw,IF',
+    'CDEF:avg_sample=avg_raw,UN,0,avg_raw,IF,sample_len,*',
+    'CDEF:avg_sum=PREV,UN,0,PREV,IF,avg_sample,+',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Bits/s",
+    #'GPRINT:min:MIN:%5.1lf %s Min,',
+    'GPRINT:avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:max:MAX:%5.1lf%s Max,',
+    'GPRINT:avg:LAST:%5.1lf%s Last',
+    'GPRINT:avg_sum:LAST:(ca. %5.1lf%sB Total)\l'
+    ],
+    ipt_packets => ['-v', 'Packets/s',
+    'DEF:min_raw={file}:value:MIN',
+    'DEF:avg_raw={file}:value:AVERAGE',
+    'DEF:max_raw={file}:value:MAX',
+    'CDEF:min=min_raw,8,*',
+    'CDEF:avg=avg_raw,8,*',
+    'CDEF:max=max_raw,8,*',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Packets/s",
+    'GPRINT:min:MIN:%5.1lf %s Min,',
+    'GPRINT:avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:max:MAX:%5.1lf%s Max,',
+    'GPRINT:avg:LAST:%5.1lf%s Last\l'
+    ],
+    irq => ['-v', 'Issues/s',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Issues/s",
+    'GPRINT:min:MIN:%6.2lf Min,',
+    'GPRINT:avg:AVERAGE:%6.2lf Avg,',
+    'GPRINT:max:MAX:%6.2lf Max,',
+    'GPRINT:avg:LAST:%6.2lf Last\l'
+    ],
+    load => ['-v', 'System load',
+    'DEF:s_avg={file}:shortterm:AVERAGE',
+    'DEF:s_min={file}:shortterm:MIN',
+    'DEF:s_max={file}:shortterm:MAX',
+    'DEF:m_avg={file}:midterm:AVERAGE',
+    'DEF:m_min={file}:midterm:MIN',
+    'DEF:m_max={file}:midterm:MAX',
+    'DEF:l_avg={file}:longterm:AVERAGE',
+    'DEF:l_min={file}:longterm:MIN',
+    'DEF:l_max={file}:longterm:MAX',
+    "AREA:s_max#$HalfGreen",
+    "AREA:s_min#$Canvas",
+    "LINE1:s_avg#$FullGreen: 1m average",
+    'GPRINT:s_min:MIN:%4.2lf Min,',
+    'GPRINT:s_avg:AVERAGE:%4.2lf Avg,',
+    'GPRINT:s_max:MAX:%4.2lf Max,',
+    'GPRINT:s_avg:LAST:%4.2lf Last\n',
+    "LINE1:m_avg#$FullBlue: 5m average",
+    'GPRINT:m_min:MIN:%4.2lf Min,',
+    'GPRINT:m_avg:AVERAGE:%4.2lf Avg,',
+    'GPRINT:m_max:MAX:%4.2lf Max,',
+    'GPRINT:m_avg:LAST:%4.2lf Last\n',
+    "LINE1:l_avg#$FullRed:15m average",
+    'GPRINT:l_min:MIN:%4.2lf Min,',
+    'GPRINT:l_avg:AVERAGE:%4.2lf Avg,',
+    'GPRINT:l_max:MAX:%4.2lf Max,',
+    'GPRINT:l_avg:LAST:%4.2lf Last'
+    ],
+    load_percent => [
+    'DEF:avg={file}:percent:AVERAGE',
+    'DEF:min={file}:percent:MIN',
+    'DEF:max={file}:percent:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Load",
+    'GPRINT:min:MIN:%5.1lf%s%% Min,',
+    'GPRINT:avg:AVERAGE:%5.1lf%s%% Avg,',
+    'GPRINT:max:MAX:%5.1lf%s%% Max,',
+    'GPRINT:avg:LAST:%5.1lf%s%% Last\l'
+    ],
+    mails => ['DEF:rawgood={file}:good:AVERAGE',
+    'DEF:rawspam={file}:spam:AVERAGE',
+    'CDEF:good=rawgood,UN,0,rawgood,IF',
+    'CDEF:spam=rawspam,UN,0,rawspam,IF',
+    'CDEF:negspam=spam,-1,*',
+    "AREA:good#$HalfGreen",
+    "LINE1:good#$FullGreen:Good mails",
+    'GPRINT:good:AVERAGE:%4.1lf Avg,',
+    'GPRINT:good:MAX:%4.1lf Max,',
+    'GPRINT:good:LAST:%4.1lf Last\n',
+    "AREA:negspam#$HalfRed",
+    "LINE1:negspam#$FullRed:Spam mails",
+    'GPRINT:spam:AVERAGE:%4.1lf Avg,',
+    'GPRINT:spam:MAX:%4.1lf Max,',
+    'GPRINT:spam:LAST:%4.1lf Last',
+    'HRULE:0#000000'
+    ],
+    memcached_command => ['-v', 'Commands',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Commands",
+    'GPRINT:min:MIN:%5.1lf%s Min,',
+    'GPRINT:avg:AVERAGE:%5.1lf Avg,',
+    'GPRINT:max:MAX:%5.1lf Max,',
+    'GPRINT:avg:LAST:%5.1lf Last\l'
+    ],
+    memcached_connections => ['-v', 'Connections',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Connections",
+    'GPRINT:min:MIN:%4.1lf Min,',
+    'GPRINT:avg:AVERAGE:%4.1lf Avg,',
+    'GPRINT:max:MAX:%4.1lf Max,',
+    'GPRINT:avg:LAST:%4.1lf Last\l'
+    ],
+    memcached_items => ['-v', 'Items',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Items",
+    'GPRINT:min:MIN:%4.1lf Min,',
+    'GPRINT:avg:AVERAGE:%4.1lf Avg,',
+    'GPRINT:max:MAX:%4.1lf Max,',
+    'GPRINT:avg:LAST:%4.1lf Last\l'
+    ],
+    memcached_octets => ['-v', 'Bits/s',
+    'DEF:out_min={file}:tx:MIN',
+    'DEF:out_avg={file}:tx:AVERAGE',
+    'DEF:out_max={file}:tx:MAX',
+    'DEF:inc_min={file}:rx:MIN',
+    'DEF:inc_avg={file}:rx:AVERAGE',
+    'DEF:inc_max={file}:rx:MAX',
+    'CDEF:mytime=out_avg,TIME,TIME,IF',
+    'CDEF:sample_len_raw=mytime,PREV(mytime),-',
+    'CDEF:sample_len=sample_len_raw,UN,0,sample_len_raw,IF',
+    'CDEF:out_avg_sample=out_avg,UN,0,out_avg,IF,sample_len,*',
+    'CDEF:out_avg_sum=PREV,UN,0,PREV,IF,out_avg_sample,+',
+    'CDEF:inc_avg_sample=inc_avg,UN,0,inc_avg,IF,sample_len,*',
+    'CDEF:inc_avg_sum=PREV,UN,0,PREV,IF,inc_avg_sample,+',
+    'CDEF:out_bit_min=out_min,8,*',
+    'CDEF:out_bit_avg=out_avg,8,*',
+    'CDEF:out_bit_max=out_max,8,*',
+    'CDEF:inc_bit_min=inc_min,8,*',
+    'CDEF:inc_bit_avg=inc_avg,8,*',
+    'CDEF:inc_bit_max=inc_max,8,*',
+    'CDEF:overlap=out_bit_avg,inc_bit_avg,GT,inc_bit_avg,out_bit_avg,IF',
+    "AREA:out_bit_avg#$HalfGreen",
+    "AREA:inc_bit_avg#$HalfBlue",
+    "AREA:overlap#$HalfBlueGreen",
+    "LINE1:out_bit_avg#$FullGreen:Written",
+    'GPRINT:out_bit_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:out_bit_max:MAX:%5.1lf%s Max,',
+    'GPRINT:out_bit_avg:LAST:%5.1lf%s Last',
+    'GPRINT:out_avg_sum:LAST:(ca. %5.1lf%sB Total)\l',
+    "LINE1:inc_bit_avg#$FullBlue:Read   ",
+    'GPRINT:inc_bit_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:inc_bit_max:MAX:%5.1lf%s Max,',
+    'GPRINT:inc_bit_avg:LAST:%5.1lf%s Last',
+    'GPRINT:inc_avg_sum:LAST:(ca. %5.1lf%sB Total)\l'
+    ],
+    memcached_ops => ['-v', 'Ops',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Ops",
+    'GPRINT:min:MIN:%4.1lf Min,',
+    'GPRINT:avg:AVERAGE:%4.1lf Avg,',
+    'GPRINT:max:MAX:%4.1lf Max,',
+    'GPRINT:avg:LAST:%4.1lf Last\l'
+    ],
+    memory => ['-b', '1024', '-v', 'Bytes',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Memory",
+    'GPRINT:min:MIN:%5.1lf%sbyte Min,',
+    'GPRINT:avg:AVERAGE:%5.1lf%sbyte Avg,',
+    'GPRINT:max:MAX:%5.1lf%sbyte Max,',
+    'GPRINT:avg:LAST:%5.1lf%sbyte Last\l'
+    ],
+    old_memory => [
+    'DEF:used_avg={file}:used:AVERAGE',
+    'DEF:free_avg={file}:free:AVERAGE',
+    'DEF:buffers_avg={file}:buffers:AVERAGE',
+    'DEF:cached_avg={file}:cached:AVERAGE',
+    'DEF:used_min={file}:used:MIN',
+    'DEF:free_min={file}:free:MIN',
+    'DEF:buffers_min={file}:buffers:MIN',
+    'DEF:cached_min={file}:cached:MIN',
+    'DEF:used_max={file}:used:MAX',
+    'DEF:free_max={file}:free:MAX',
+    'DEF:buffers_max={file}:buffers:MAX',
+    'DEF:cached_max={file}:cached:MAX',
+    'CDEF:cached_avg_nn=cached_avg,UN,0,cached_avg,IF',
+    'CDEF:buffers_avg_nn=buffers_avg,UN,0,buffers_avg,IF',
+    'CDEF:free_cached_buffers_used=free_avg,cached_avg_nn,+,buffers_avg_nn,+,used_avg,+',
+    'CDEF:cached_buffers_used=cached_avg,buffers_avg_nn,+,used_avg,+',
+    'CDEF:buffers_used=buffers_avg,used_avg,+',
+    "AREA:free_cached_buffers_used#$HalfGreen",
+    "AREA:cached_buffers_used#$HalfBlue",
+    "AREA:buffers_used#$HalfYellow",
+    "AREA:used_avg#$HalfRed",
+    "LINE1:free_cached_buffers_used#$FullGreen:Free        ",
+    'GPRINT:free_min:MIN:%5.1lf%s Min,',
+    'GPRINT:free_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:free_max:MAX:%5.1lf%s Max,',
+    'GPRINT:free_avg:LAST:%5.1lf%s Last\n',
+    "LINE1:cached_buffers_used#$FullBlue:Page cache  ",
+    'GPRINT:cached_min:MIN:%5.1lf%s Min,',
+    'GPRINT:cached_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:cached_max:MAX:%5.1lf%s Max,',
+    'GPRINT:cached_avg:LAST:%5.1lf%s Last\n',
+    "LINE1:buffers_used#$FullYellow:Buffer cache",
+    'GPRINT:buffers_min:MIN:%5.1lf%s Min,',
+    'GPRINT:buffers_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:buffers_max:MAX:%5.1lf%s Max,',
+    'GPRINT:buffers_avg:LAST:%5.1lf%s Last\n',
+    "LINE1:used_avg#$FullRed:Used        ",
+    'GPRINT:used_min:MIN:%5.1lf%s Min,',
+    'GPRINT:used_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:used_max:MAX:%5.1lf%s Max,',
+    'GPRINT:used_avg:LAST:%5.1lf%s Last'
+    ],
+    mysql_commands => ['-v', 'Issues/s',
+    "DEF:val_avg={file}:value:AVERAGE",
+    "DEF:val_min={file}:value:MIN",
+    "DEF:val_max={file}:value:MAX",
+    "AREA:val_max#$HalfBlue",
+    "AREA:val_min#$Canvas",
+    "LINE1:val_avg#$FullBlue:Issues/s",
+    'GPRINT:val_min:MIN:%5.2lf Min,',
+    'GPRINT:val_avg:AVERAGE:%5.2lf Avg,',
+    'GPRINT:val_max:MAX:%5.2lf Max,',
+    'GPRINT:val_avg:LAST:%5.2lf Last'
+    ],
+    mysql_handler => ['-v', 'Issues/s',
+    "DEF:val_avg={file}:value:AVERAGE",
+    "DEF:val_min={file}:value:MIN",
+    "DEF:val_max={file}:value:MAX",
+    "AREA:val_max#$HalfBlue",
+    "AREA:val_min#$Canvas",
+    "LINE1:val_avg#$FullBlue:Issues/s",
+    'GPRINT:val_min:MIN:%5.2lf Min,',
+    'GPRINT:val_avg:AVERAGE:%5.2lf Avg,',
+    'GPRINT:val_max:MAX:%5.2lf Max,',
+    'GPRINT:val_avg:LAST:%5.2lf Last'
+    ],
+    mysql_octets => ['-v', 'Bits/s',
+    'DEF:out_min={file}:tx:MIN',
+    'DEF:out_avg={file}:tx:AVERAGE',
+    'DEF:out_max={file}:tx:MAX',
+    'DEF:inc_min={file}:rx:MIN',
+    'DEF:inc_avg={file}:rx:AVERAGE',
+    'DEF:inc_max={file}:rx:MAX',
+    'CDEF:mytime=out_avg,TIME,TIME,IF',
+    'CDEF:sample_len_raw=mytime,PREV(mytime),-',
+    'CDEF:sample_len=sample_len_raw,UN,0,sample_len_raw,IF',
+    'CDEF:out_avg_sample=out_avg,UN,0,out_avg,IF,sample_len,*',
+    'CDEF:out_avg_sum=PREV,UN,0,PREV,IF,out_avg_sample,+',
+    'CDEF:inc_avg_sample=inc_avg,UN,0,inc_avg,IF,sample_len,*',
+    'CDEF:inc_avg_sum=PREV,UN,0,PREV,IF,inc_avg_sample,+',
+    'CDEF:out_bit_min=out_min,8,*',
+    'CDEF:out_bit_avg=out_avg,8,*',
+    'CDEF:out_bit_max=out_max,8,*',
+    'CDEF:inc_bit_min=inc_min,8,*',
+    'CDEF:inc_bit_avg=inc_avg,8,*',
+    'CDEF:inc_bit_max=inc_max,8,*',
+    'CDEF:overlap=out_bit_avg,inc_bit_avg,GT,inc_bit_avg,out_bit_avg,IF',
+    "AREA:out_bit_avg#$HalfGreen",
+    "AREA:inc_bit_avg#$HalfBlue",
+    "AREA:overlap#$HalfBlueGreen",
+    "LINE1:out_bit_avg#$FullGreen:Written",
+    'GPRINT:out_bit_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:out_bit_max:MAX:%5.1lf%s Max,',
+    'GPRINT:out_bit_avg:LAST:%5.1lf%s Last',
+    'GPRINT:out_avg_sum:LAST:(ca. %5.1lf%sB Total)\l',
+    "LINE1:inc_bit_avg#$FullBlue:Read   ",
+    'GPRINT:inc_bit_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:inc_bit_max:MAX:%5.1lf%s Max,',
+    'GPRINT:inc_bit_avg:LAST:%5.1lf%s Last',
+    'GPRINT:inc_avg_sum:LAST:(ca. %5.1lf%sB Total)\l'
+    ],
+    mysql_qcache => ['-v', 'Queries/s',
+    "DEF:hits_min={file}:hits:MIN",
+    "DEF:hits_avg={file}:hits:AVERAGE",
+    "DEF:hits_max={file}:hits:MAX",
+    "DEF:inserts_min={file}:inserts:MIN",
+    "DEF:inserts_avg={file}:inserts:AVERAGE",
+    "DEF:inserts_max={file}:inserts:MAX",
+    "DEF:not_cached_min={file}:not_cached:MIN",
+    "DEF:not_cached_avg={file}:not_cached:AVERAGE",
+    "DEF:not_cached_max={file}:not_cached:MAX",
+    "DEF:lowmem_prunes_min={file}:lowmem_prunes:MIN",
+    "DEF:lowmem_prunes_avg={file}:lowmem_prunes:AVERAGE",
+    "DEF:lowmem_prunes_max={file}:lowmem_prunes:MAX",
+    "DEF:queries_min={file}:queries_in_cache:MIN",
+    "DEF:queries_avg={file}:queries_in_cache:AVERAGE",
+    "DEF:queries_max={file}:queries_in_cache:MAX",
+    "CDEF:unknown=queries_avg,UNKN,+",
+    "CDEF:not_cached_agg=hits_avg,inserts_avg,+,not_cached_avg,+",
+    "CDEF:inserts_agg=hits_avg,inserts_avg,+",
+    "CDEF:hits_agg=hits_avg",
+    "AREA:not_cached_agg#$HalfYellow",
+    "AREA:inserts_agg#$HalfBlue",
+    "AREA:hits_agg#$HalfGreen",
+    "LINE1:not_cached_agg#$FullYellow:Not Cached      ",
+    'GPRINT:not_cached_min:MIN:%5.2lf Min,',
+    'GPRINT:not_cached_avg:AVERAGE:%5.2lf Avg,',
+    'GPRINT:not_cached_max:MAX:%5.2lf Max,',
+    'GPRINT:not_cached_avg:LAST:%5.2lf Last\l',
+    "LINE1:inserts_agg#$FullBlue:Inserts         ",
+    'GPRINT:inserts_min:MIN:%5.2lf Min,',
+    'GPRINT:inserts_avg:AVERAGE:%5.2lf Avg,',
+    'GPRINT:inserts_max:MAX:%5.2lf Max,',
+    'GPRINT:inserts_avg:LAST:%5.2lf Last\l',
+    "LINE1:hits_agg#$FullGreen:Hits            ",
+    'GPRINT:hits_min:MIN:%5.2lf Min,',
+    'GPRINT:hits_avg:AVERAGE:%5.2lf Avg,',
+    'GPRINT:hits_max:MAX:%5.2lf Max,',
+    'GPRINT:hits_avg:LAST:%5.2lf Last\l',
+    "LINE1:lowmem_prunes_avg#$FullRed:Lowmem Prunes   ",
+    'GPRINT:lowmem_prunes_min:MIN:%5.2lf Min,',
+    'GPRINT:lowmem_prunes_avg:AVERAGE:%5.2lf Avg,',
+    'GPRINT:lowmem_prunes_max:MAX:%5.2lf Max,',
+    'GPRINT:lowmem_prunes_avg:LAST:%5.2lf Last\l',
+    "LINE1:unknown#$Canvas:Queries in cache",
+    'GPRINT:queries_min:MIN:%5.0lf Min,',
+    'GPRINT:queries_avg:AVERAGE:%5.0lf Avg,',
+    'GPRINT:queries_max:MAX:%5.0lf Max,',
+    'GPRINT:queries_avg:LAST:%5.0lf Last\l'
+    ],
+    mysql_threads => ['-v', 'Threads',
+    "DEF:running_min={file}:running:MIN",
+    "DEF:running_avg={file}:running:AVERAGE",
+    "DEF:running_max={file}:running:MAX",
+    "DEF:connected_min={file}:connected:MIN",
+    "DEF:connected_avg={file}:connected:AVERAGE",
+    "DEF:connected_max={file}:connected:MAX",
+    "DEF:cached_min={file}:cached:MIN",
+    "DEF:cached_avg={file}:cached:AVERAGE",
+    "DEF:cached_max={file}:cached:MAX",
+    "DEF:created_min={file}:created:MIN",
+    "DEF:created_avg={file}:created:AVERAGE",
+    "DEF:created_max={file}:created:MAX",
+    "CDEF:unknown=created_avg,UNKN,+",
+    "CDEF:cached_agg=connected_avg,cached_avg,+",
+    "AREA:cached_agg#$HalfGreen",
+    "AREA:connected_avg#$HalfBlue",
+    "AREA:running_avg#$HalfRed",
+    "LINE1:cached_agg#$FullGreen:Cached   ",
+    'GPRINT:cached_min:MIN:%5.1lf Min,',
+    'GPRINT:cached_avg:AVERAGE:%5.1lf Avg,',
+    'GPRINT:cached_max:MAX:%5.1lf Max,',
+    'GPRINT:cached_avg:LAST:%5.1lf Last\l',
+    "LINE1:connected_avg#$FullBlue:Connected",
+    'GPRINT:connected_min:MIN:%5.1lf Min,',
+    'GPRINT:connected_avg:AVERAGE:%5.1lf Avg,',
+    'GPRINT:connected_max:MAX:%5.1lf Max,',
+    'GPRINT:connected_avg:LAST:%5.1lf Last\l',
+    "LINE1:running_avg#$FullRed:Running  ",
+    'GPRINT:running_min:MIN:%5.1lf Min,',
+    'GPRINT:running_avg:AVERAGE:%5.1lf Avg,',
+    'GPRINT:running_max:MAX:%5.1lf Max,',
+    'GPRINT:running_avg:LAST:%5.1lf Last\l',
+    "LINE1:unknown#$Canvas:Created  ",
+    'GPRINT:created_min:MIN:%5.0lf Min,',
+    'GPRINT:created_avg:AVERAGE:%5.0lf Avg,',
+    'GPRINT:created_max:MAX:%5.0lf Max,',
+    'GPRINT:created_avg:LAST:%5.0lf Last\l'
+    ],
+    nfs_procedure => ['-v', 'Issues/s',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Issues/s",
+    'GPRINT:min:MIN:%6.2lf Min,',
+    'GPRINT:avg:AVERAGE:%6.2lf Avg,',
+    'GPRINT:max:MAX:%6.2lf Max,',
+    'GPRINT:avg:LAST:%6.2lf Last\l'
+    ],
+    nfs3_procedures => [
+    "DEF:null_avg={file}:null:AVERAGE",
+    "DEF:getattr_avg={file}:getattr:AVERAGE",
+    "DEF:setattr_avg={file}:setattr:AVERAGE",
+    "DEF:lookup_avg={file}:lookup:AVERAGE",
+    "DEF:access_avg={file}:access:AVERAGE",
+    "DEF:readlink_avg={file}:readlink:AVERAGE",
+    "DEF:read_avg={file}:read:AVERAGE",
+    "DEF:write_avg={file}:write:AVERAGE",
+    "DEF:create_avg={file}:create:AVERAGE",
+    "DEF:mkdir_avg={file}:mkdir:AVERAGE",
+    "DEF:symlink_avg={file}:symlink:AVERAGE",
+    "DEF:mknod_avg={file}:mknod:AVERAGE",
+    "DEF:remove_avg={file}:remove:AVERAGE",
+    "DEF:rmdir_avg={file}:rmdir:AVERAGE",
+    "DEF:rename_avg={file}:rename:AVERAGE",
+    "DEF:link_avg={file}:link:AVERAGE",
+    "DEF:readdir_avg={file}:readdir:AVERAGE",
+    "DEF:readdirplus_avg={file}:readdirplus:AVERAGE",
+    "DEF:fsstat_avg={file}:fsstat:AVERAGE",
+    "DEF:fsinfo_avg={file}:fsinfo:AVERAGE",
+    "DEF:pathconf_avg={file}:pathconf:AVERAGE",
+    "DEF:commit_avg={file}:commit:AVERAGE",
+    "DEF:null_max={file}:null:MAX",
+    "DEF:getattr_max={file}:getattr:MAX",
+    "DEF:setattr_max={file}:setattr:MAX",
+    "DEF:lookup_max={file}:lookup:MAX",
+    "DEF:access_max={file}:access:MAX",
+    "DEF:readlink_max={file}:readlink:MAX",
+    "DEF:read_max={file}:read:MAX",
+    "DEF:write_max={file}:write:MAX",
+    "DEF:create_max={file}:create:MAX",
+    "DEF:mkdir_max={file}:mkdir:MAX",
+    "DEF:symlink_max={file}:symlink:MAX",
+    "DEF:mknod_max={file}:mknod:MAX",
+    "DEF:remove_max={file}:remove:MAX",
+    "DEF:rmdir_max={file}:rmdir:MAX",
+    "DEF:rename_max={file}:rename:MAX",
+    "DEF:link_max={file}:link:MAX",
+    "DEF:readdir_max={file}:readdir:MAX",
+    "DEF:readdirplus_max={file}:readdirplus:MAX",
+    "DEF:fsstat_max={file}:fsstat:MAX",
+    "DEF:fsinfo_max={file}:fsinfo:MAX",
+    "DEF:pathconf_max={file}:pathconf:MAX",
+    "DEF:commit_max={file}:commit:MAX",
+    "CDEF:other_avg=null_avg,readlink_avg,create_avg,mkdir_avg,symlink_avg,mknod_avg,remove_avg,rmdir_avg,rename_avg,link_avg,readdir_avg,readdirplus_avg,fsstat_avg,fsinfo_avg,pathconf_avg,+,+,+,+,+,+,+,+,+,+,+,+,+,+",
+    "CDEF:other_max=null_max,readlink_max,create_max,mkdir_max,symlink_max,mknod_max,remove_max,rmdir_max,rename_max,link_max,readdir_max,readdirplus_max,fsstat_max,fsinfo_max,pathconf_max,+,+,+,+,+,+,+,+,+,+,+,+,+,+",
+    "CDEF:stack_read=read_avg",
+    "CDEF:stack_getattr=stack_read,getattr_avg,+",
+    "CDEF:stack_access=stack_getattr,access_avg,+",
+    "CDEF:stack_lookup=stack_access,lookup_avg,+",
+    "CDEF:stack_write=stack_lookup,write_avg,+",
+    "CDEF:stack_commit=stack_write,commit_avg,+",
+    "CDEF:stack_setattr=stack_commit,setattr_avg,+",
+    "CDEF:stack_other=stack_setattr,other_avg,+",
+    "AREA:stack_other#$HalfRed",
+    "AREA:stack_setattr#$HalfGreen",
+    "AREA:stack_commit#$HalfYellow",
+    "AREA:stack_write#$HalfGreen",
+    "AREA:stack_lookup#$HalfBlue",
+    "AREA:stack_access#$HalfMagenta",
+    "AREA:stack_getattr#$HalfCyan",
+    "AREA:stack_read#$HalfBlue",
+    "LINE1:stack_other#$FullRed:Other  ",
+    'GPRINT:other_max:MAX:%5.1lf Max,',
+    'GPRINT:other_avg:AVERAGE:%5.1lf Avg,',
+    'GPRINT:other_avg:LAST:%5.1lf Last\l',
+    "LINE1:stack_setattr#$FullGreen:setattr",
+    'GPRINT:setattr_max:MAX:%5.1lf Max,',
+    'GPRINT:setattr_avg:AVERAGE:%5.1lf Avg,',
+    'GPRINT:setattr_avg:LAST:%5.1lf Last\l',
+    "LINE1:stack_commit#$FullYellow:commit ",
+    'GPRINT:commit_max:MAX:%5.1lf Max,',
+    'GPRINT:commit_avg:AVERAGE:%5.1lf Avg,',
+    'GPRINT:commit_avg:LAST:%5.1lf Last\l',
+    "LINE1:stack_write#$FullGreen:write  ",
+    'GPRINT:write_max:MAX:%5.1lf Max,',
+    'GPRINT:write_avg:AVERAGE:%5.1lf Avg,',
+    'GPRINT:write_avg:LAST:%5.1lf Last\l',
+    "LINE1:stack_lookup#$FullBlue:lookup ",
+    'GPRINT:lookup_max:MAX:%5.1lf Max,',
+    'GPRINT:lookup_avg:AVERAGE:%5.1lf Avg,',
+    'GPRINT:lookup_avg:LAST:%5.1lf Last\l',
+    "LINE1:stack_access#$FullMagenta:access ",
+    'GPRINT:access_max:MAX:%5.1lf Max,',
+    'GPRINT:access_avg:AVERAGE:%5.1lf Avg,',
+    'GPRINT:access_avg:LAST:%5.1lf Last\l',
+    "LINE1:stack_getattr#$FullCyan:getattr",
+    'GPRINT:getattr_max:MAX:%5.1lf Max,',
+    'GPRINT:getattr_avg:AVERAGE:%5.1lf Avg,',
+    'GPRINT:getattr_avg:LAST:%5.1lf Last\l',
+    "LINE1:stack_read#$FullBlue:read   ",
+    'GPRINT:read_max:MAX:%5.1lf Max,',
+    'GPRINT:read_avg:AVERAGE:%5.1lf Avg,',
+    'GPRINT:read_avg:LAST:%5.1lf Last\l'
+    ],
+    partition => [
+    "DEF:rbyte_avg={file}:rbytes:AVERAGE",
+    "DEF:rbyte_min={file}:rbytes:MIN",
+    "DEF:rbyte_max={file}:rbytes:MAX",
+    "DEF:wbyte_avg={file}:wbytes:AVERAGE",
+    "DEF:wbyte_min={file}:wbytes:MIN",
+    "DEF:wbyte_max={file}:wbytes:MAX",
+    'CDEF:overlap=wbyte_avg,rbyte_avg,GT,rbyte_avg,wbyte_avg,IF',
+    "AREA:wbyte_avg#$HalfGreen",
+    "AREA:rbyte_avg#$HalfBlue",
+    "AREA:overlap#$HalfBlueGreen",
+    "LINE1:wbyte_avg#$FullGreen:Write",
+    'GPRINT:wbyte_min:MIN:%5.1lf%s Min,',
+    'GPRINT:wbyte_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:wbyte_max:MAX:%5.1lf%s Max,',
+    'GPRINT:wbyte_avg:LAST:%5.1lf%s Last\l',
+    "LINE1:rbyte_avg#$FullBlue:Read ",
+    'GPRINT:rbyte_min:MIN:%5.1lf%s Min,',
+    'GPRINT:rbyte_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:rbyte_max:MAX:%5.1lf%s Max,',
+    'GPRINT:rbyte_avg:LAST:%5.1lf%s Last\l'
+    ],
+    percent => ['-v', 'Percent',
+    'DEF:avg={file}:percent:AVERAGE',
+    'DEF:min={file}:percent:MIN',
+    'DEF:max={file}:percent:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Percent",
+    'GPRINT:min:MIN:%5.1lf%% Min,',
+    'GPRINT:avg:AVERAGE:%5.1lf%% Avg,',
+    'GPRINT:max:MAX:%5.1lf%% Max,',
+    'GPRINT:avg:LAST:%5.1lf%% Last\l'
+    ],
+    ping => ['DEF:ping_avg={file}:ping:AVERAGE',
+    'DEF:ping_min={file}:ping:MIN',
+    'DEF:ping_max={file}:ping:MAX',
+    "AREA:ping_max#$HalfBlue",
+    "AREA:ping_min#$Canvas",
+    "LINE1:ping_avg#$FullBlue:Ping",
+    'GPRINT:ping_min:MIN:%4.1lf ms Min,',
+    'GPRINT:ping_avg:AVERAGE:%4.1lf ms Avg,',
+    'GPRINT:ping_max:MAX:%4.1lf ms Max,',
+    'GPRINT:ping_avg:LAST:%4.1lf ms Last'],
+    pg_blks => ['DEF:pg_blks_avg={file}:value:AVERAGE',
+    'DEF:pg_blks_min={file}:value:MIN',
+    'DEF:pg_blks_max={file}:value:MAX',
+    "AREA:pg_blks_max#$HalfBlue",
+    "AREA:pg_blks_min#$Canvas",
+    "LINE1:pg_blks_avg#$FullBlue:Blocks",
+    'GPRINT:pg_blks_min:MIN:%4.1lf%s Min,',
+    'GPRINT:pg_blks_avg:AVERAGE:%4.1lf%s Avg,',
+    'GPRINT:pg_blks_max:MAX:%4.1lf%s Max,',
+    'GPRINT:pg_blks_avg:LAST:%4.1lf%s Last'],
+    pg_db_size => ['DEF:pg_db_size_avg={file}:value:AVERAGE',
+    'DEF:pg_db_size_min={file}:value:MIN',
+    'DEF:pg_db_size_max={file}:value:MAX',
+    "AREA:pg_db_size_max#$HalfBlue",
+    "AREA:pg_db_size_min#$Canvas",
+    "LINE1:pg_db_size_avg#$FullBlue:Bytes",
+    'GPRINT:pg_db_size_min:MIN:%4.1lf%s Min,',
+    'GPRINT:pg_db_size_avg:AVERAGE:%4.1lf%s Avg,',
+    'GPRINT:pg_db_size_max:MAX:%4.1lf%s Max,',
+    'GPRINT:pg_db_size_avg:LAST:%4.1lf%s Last'],
+    pg_n_tup_c => ['DEF:pg_n_tup_avg={file}:value:AVERAGE',
+    'DEF:pg_n_tup_min={file}:value:MIN',
+    'DEF:pg_n_tup_max={file}:value:MAX',
+    "AREA:pg_n_tup_max#$HalfBlue",
+    "AREA:pg_n_tup_min#$Canvas",
+    "LINE1:pg_n_tup_avg#$FullBlue:Tuples",
+    'GPRINT:pg_n_tup_min:MIN:%4.1lf%s Min,',
+    'GPRINT:pg_n_tup_avg:AVERAGE:%4.1lf%s Avg,',
+    'GPRINT:pg_n_tup_max:MAX:%4.1lf%s Max,',
+    'GPRINT:pg_n_tup_avg:LAST:%4.1lf%s Last'],
+    pg_n_tup_g => ['DEF:pg_n_tup_avg={file}:value:AVERAGE',
+    'DEF:pg_n_tup_min={file}:value:MIN',
+    'DEF:pg_n_tup_max={file}:value:MAX',
+    "AREA:pg_n_tup_max#$HalfBlue",
+    "AREA:pg_n_tup_min#$Canvas",
+    "LINE1:pg_n_tup_avg#$FullBlue:Tuples",
+    'GPRINT:pg_n_tup_min:MIN:%4.1lf%s Min,',
+    'GPRINT:pg_n_tup_avg:AVERAGE:%4.1lf%s Avg,',
+    'GPRINT:pg_n_tup_max:MAX:%4.1lf%s Max,',
+    'GPRINT:pg_n_tup_avg:LAST:%4.1lf%s Last'],
+    pg_numbackends => ['DEF:pg_numbackends_avg={file}:value:AVERAGE',
+    'DEF:pg_numbackends_min={file}:value:MIN',
+    'DEF:pg_numbackends_max={file}:value:MAX',
+    "AREA:pg_numbackends_max#$HalfBlue",
+    "AREA:pg_numbackends_min#$Canvas",
+    "LINE1:pg_numbackends_avg#$FullBlue:Backends",
+    'GPRINT:pg_numbackends_min:MIN:%4.1lf%s Min,',
+    'GPRINT:pg_numbackends_avg:AVERAGE:%4.1lf%s Avg,',
+    'GPRINT:pg_numbackends_max:MAX:%4.1lf%s Max,',
+    'GPRINT:pg_numbackends_avg:LAST:%4.1lf%s Last'],
+    pg_scan => ['DEF:pg_scan_avg={file}:value:AVERAGE',
+    'DEF:pg_scan_min={file}:value:MIN',
+    'DEF:pg_scan_max={file}:value:MAX',
+    "AREA:pg_scan_max#$HalfBlue",
+    "AREA:pg_scan_min#$Canvas",
+    "LINE1:pg_scan_avg#$FullBlue:Scans",
+    'GPRINT:pg_scan_min:MIN:%4.1lf%s Min,',
+    'GPRINT:pg_scan_avg:AVERAGE:%4.1lf%s Avg,',
+    'GPRINT:pg_scan_max:MAX:%4.1lf%s Max,',
+    'GPRINT:pg_scan_avg:LAST:%4.1lf%s Last'],
+    pg_xact => ['DEF:pg_xact_avg={file}:value:AVERAGE',
+    'DEF:pg_xact_min={file}:value:MIN',
+    'DEF:pg_xact_max={file}:value:MAX',
+    "AREA:pg_xact_max#$HalfBlue",
+    "AREA:pg_xact_min#$Canvas",
+    "LINE1:pg_xact_avg#$FullBlue:Transactions",
+    'GPRINT:pg_xact_min:MIN:%4.1lf%s Min,',
+    'GPRINT:pg_xact_avg:AVERAGE:%4.1lf%s Avg,',
+    'GPRINT:pg_xact_max:MAX:%4.1lf%s Max,',
+    'GPRINT:pg_xact_avg:LAST:%4.1lf%s Last'],
+    power => ['-v', 'Watt',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Watt",
+    'GPRINT:min:MIN:%5.1lf%sW Min,',
+    'GPRINT:avg:AVERAGE:%5.1lf%sW Avg,',
+    'GPRINT:max:MAX:%5.1lf%sW Max,',
+    'GPRINT:avg:LAST:%5.1lf%sW Last\l'
+    ],
+    processes => [
+    "DEF:running_avg={file}:running:AVERAGE",
+    "DEF:running_min={file}:running:MIN",
+    "DEF:running_max={file}:running:MAX",
+    "DEF:sleeping_avg={file}:sleeping:AVERAGE",
+    "DEF:sleeping_min={file}:sleeping:MIN",
+    "DEF:sleeping_max={file}:sleeping:MAX",
+    "DEF:zombies_avg={file}:zombies:AVERAGE",
+    "DEF:zombies_min={file}:zombies:MIN",
+    "DEF:zombies_max={file}:zombies:MAX",
+    "DEF:stopped_avg={file}:stopped:AVERAGE",
+    "DEF:stopped_min={file}:stopped:MIN",
+    "DEF:stopped_max={file}:stopped:MAX",
+    "DEF:paging_avg={file}:paging:AVERAGE",
+    "DEF:paging_min={file}:paging:MIN",
+    "DEF:paging_max={file}:paging:MAX",
+    "DEF:blocked_avg={file}:blocked:AVERAGE",
+    "DEF:blocked_min={file}:blocked:MIN",
+    "DEF:blocked_max={file}:blocked:MAX",
+    'CDEF:paging_acc=sleeping_avg,running_avg,stopped_avg,zombies_avg,blocked_avg,paging_avg,+,+,+,+,+',
+    'CDEF:blocked_acc=sleeping_avg,running_avg,stopped_avg,zombies_avg,blocked_avg,+,+,+,+',
+    'CDEF:zombies_acc=sleeping_avg,running_avg,stopped_avg,zombies_avg,+,+,+',
+    'CDEF:stopped_acc=sleeping_avg,running_avg,stopped_avg,+,+',
+    'CDEF:running_acc=sleeping_avg,running_avg,+',
+    'CDEF:sleeping_acc=sleeping_avg',
+    "AREA:paging_acc#$HalfYellow",
+    "AREA:blocked_acc#$HalfCyan",
+    "AREA:zombies_acc#$HalfRed",
+    "AREA:stopped_acc#$HalfMagenta",
+    "AREA:running_acc#$HalfGreen",
+    "AREA:sleeping_acc#$HalfBlue",
+    "LINE1:paging_acc#$FullYellow:Paging  ",
+    'GPRINT:paging_min:MIN:%5.1lf Min,',
+    'GPRINT:paging_avg:AVERAGE:%5.1lf Average,',
+    'GPRINT:paging_max:MAX:%5.1lf Max,',
+    'GPRINT:paging_avg:LAST:%5.1lf Last\l',
+    "LINE1:blocked_acc#$FullCyan:Blocked ",
+    'GPRINT:blocked_min:MIN:%5.1lf Min,',
+    'GPRINT:blocked_avg:AVERAGE:%5.1lf Average,',
+    'GPRINT:blocked_max:MAX:%5.1lf Max,',
+    'GPRINT:blocked_avg:LAST:%5.1lf Last\l',
+    "LINE1:zombies_acc#$FullRed:Zombies ",
+    'GPRINT:zombies_min:MIN:%5.1lf Min,',
+    'GPRINT:zombies_avg:AVERAGE:%5.1lf Average,',
+    'GPRINT:zombies_max:MAX:%5.1lf Max,',
+    'GPRINT:zombies_avg:LAST:%5.1lf Last\l',
+    "LINE1:stopped_acc#$FullMagenta:Stopped ",
+    'GPRINT:stopped_min:MIN:%5.1lf Min,',
+    'GPRINT:stopped_avg:AVERAGE:%5.1lf Average,',
+    'GPRINT:stopped_max:MAX:%5.1lf Max,',
+    'GPRINT:stopped_avg:LAST:%5.1lf Last\l',
+    "LINE1:running_acc#$FullGreen:Running ",
+    'GPRINT:running_min:MIN:%5.1lf Min,',
+    'GPRINT:running_avg:AVERAGE:%5.1lf Average,',
+    'GPRINT:running_max:MAX:%5.1lf Max,',
+    'GPRINT:running_avg:LAST:%5.1lf Last\l',
+    "LINE1:sleeping_acc#$FullBlue:Sleeping",
+    'GPRINT:sleeping_min:MIN:%5.1lf Min,',
+    'GPRINT:sleeping_avg:AVERAGE:%5.1lf Average,',
+    'GPRINT:sleeping_max:MAX:%5.1lf Max,',
+    'GPRINT:sleeping_avg:LAST:%5.1lf Last\l'
+    ],
+    ps_count => ['-v', 'Processes',
+    'DEF:procs_avg={file}:processes:AVERAGE',
+    'DEF:procs_min={file}:processes:MIN',
+    'DEF:procs_max={file}:processes:MAX',
+    'DEF:thrds_avg={file}:threads:AVERAGE',
+    'DEF:thrds_min={file}:threads:MIN',
+    'DEF:thrds_max={file}:threads:MAX',
+    "AREA:thrds_avg#$HalfBlue",
+    "AREA:procs_avg#$HalfRed",
+    "LINE1:thrds_avg#$FullBlue:Threads  ",
+    'GPRINT:thrds_min:MIN:%5.1lf Min,',
+    'GPRINT:thrds_avg:AVERAGE:%5.1lf Avg,',
+    'GPRINT:thrds_max:MAX:%5.1lf Max,',
+    'GPRINT:thrds_avg:LAST:%5.1lf Last\l',
+    "LINE1:procs_avg#$FullRed:Processes",
+    'GPRINT:procs_min:MIN:%5.1lf Min,',
+    'GPRINT:procs_avg:AVERAGE:%5.1lf Avg,',
+    'GPRINT:procs_max:MAX:%5.1lf Max,',
+    'GPRINT:procs_avg:LAST:%5.1lf Last\l'
+    ],
+    ps_cputime => ['-v', 'Jiffies',
+    'DEF:user_avg_raw={file}:user:AVERAGE',
+    'DEF:user_min_raw={file}:user:MIN',
+    'DEF:user_max_raw={file}:user:MAX',
+    'DEF:syst_avg_raw={file}:syst:AVERAGE',
+    'DEF:syst_min_raw={file}:syst:MIN',
+    'DEF:syst_max_raw={file}:syst:MAX',
+    'CDEF:user_avg=user_avg_raw,1000000,/',
+    'CDEF:user_min=user_min_raw,1000000,/',
+    'CDEF:user_max=user_max_raw,1000000,/',
+    'CDEF:syst_avg=syst_avg_raw,1000000,/',
+    'CDEF:syst_min=syst_min_raw,1000000,/',
+    'CDEF:syst_max=syst_max_raw,1000000,/',
+    'CDEF:user_syst=syst_avg,UN,0,syst_avg,IF,user_avg,+',
+    "AREA:user_syst#$HalfBlue",
+    "AREA:syst_avg#$HalfRed",
+    "LINE1:user_syst#$FullBlue:User  ",
+    'GPRINT:user_min:MIN:%5.1lf%s Min,',
+    'GPRINT:user_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:user_max:MAX:%5.1lf%s Max,',
+    'GPRINT:user_avg:LAST:%5.1lf%s Last\l',
+    "LINE1:syst_avg#$FullRed:System",
+    'GPRINT:syst_min:MIN:%5.1lf%s Min,',
+    'GPRINT:syst_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:syst_max:MAX:%5.1lf%s Max,',
+    'GPRINT:syst_avg:LAST:%5.1lf%s Last\l'
+    ],
+    ps_pagefaults => ['-v', 'Pagefaults/s',
+    'DEF:minor_avg={file}:minflt:AVERAGE',
+    'DEF:minor_min={file}:minflt:MIN',
+    'DEF:minor_max={file}:minflt:MAX',
+    'DEF:major_avg={file}:majflt:AVERAGE',
+    'DEF:major_min={file}:majflt:MIN',
+    'DEF:major_max={file}:majflt:MAX',
+    'CDEF:minor_major=major_avg,UN,0,major_avg,IF,minor_avg,+',
+    "AREA:minor_major#$HalfBlue",
+    "AREA:major_avg#$HalfRed",
+    "LINE1:minor_major#$FullBlue:Minor",
+    'GPRINT:minor_min:MIN:%5.1lf%s Min,',
+    'GPRINT:minor_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:minor_max:MAX:%5.1lf%s Max,',
+    'GPRINT:minor_avg:LAST:%5.1lf%s Last\l',
+    "LINE1:major_avg#$FullRed:Major",
+    'GPRINT:major_min:MIN:%5.1lf%s Min,',
+    'GPRINT:major_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:major_max:MAX:%5.1lf%s Max,',
+    'GPRINT:major_avg:LAST:%5.1lf%s Last\l'
+    ],
+    ps_rss => ['-v', 'Bytes',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:avg#$HalfBlue",
+    "LINE1:avg#$FullBlue:RSS",
+    'GPRINT:min:MIN:%5.1lf%s Min,',
+    'GPRINT:avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:max:MAX:%5.1lf%s Max,',
+    'GPRINT:avg:LAST:%5.1lf%s Last\l'
+    ],
+    ps_state => ['-v', 'Processes',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Processes",
+    'GPRINT:min:MIN:%6.2lf Min,',
+    'GPRINT:avg:AVERAGE:%6.2lf Avg,',
+    'GPRINT:max:MAX:%6.2lf Max,',
+    'GPRINT:avg:LAST:%6.2lf Last\l'
+    ],
+    signal_noise => ['-v', 'dBm',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Noise",
+    'GPRINT:min:MIN:%5.1lf%sdBm Min,',
+    'GPRINT:avg:AVERAGE:%5.1lf%sdBm Avg,',
+    'GPRINT:max:MAX:%5.1lf%sdBm Max,',
+    'GPRINT:avg:LAST:%5.1lf%sdBm Last\l'
+    ],
+    signal_power => ['-v', 'dBm',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Power",
+    'GPRINT:min:MIN:%5.1lf%sdBm Min,',
+    'GPRINT:avg:AVERAGE:%5.1lf%sdBm Avg,',
+    'GPRINT:max:MAX:%5.1lf%sdBm Max,',
+    'GPRINT:avg:LAST:%5.1lf%sdBm Last\l'
+    ],
+    signal_quality => ['-v', '%',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Quality",
+    'GPRINT:min:MIN:%5.1lf%s%% Min,',
+    'GPRINT:avg:AVERAGE:%5.1lf%s%% Avg,',
+    'GPRINT:max:MAX:%5.1lf%s%% Max,',
+    'GPRINT:avg:LAST:%5.1lf%s%% Last\l'
+    ],
+    swap => ['-v', 'Bytes', '-b', '1024',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Bytes",
+    'GPRINT:min:MIN:%6.2lf%sByte Min,',
+    'GPRINT:avg:AVERAGE:%6.2lf%sByte Avg,',
+    'GPRINT:max:MAX:%6.2lf%sByte Max,',
+    'GPRINT:avg:LAST:%6.2lf%sByte Last\l'
+    ],
+    old_swap => [
+    'DEF:used_avg={file}:used:AVERAGE',
+    'DEF:used_min={file}:used:MIN',
+    'DEF:used_max={file}:used:MAX',
+    'DEF:free_avg={file}:free:AVERAGE',
+    'DEF:free_min={file}:free:MIN',
+    'DEF:free_max={file}:free:MAX',
+    'DEF:cach_avg={file}:cached:AVERAGE',
+    'DEF:cach_min={file}:cached:MIN',
+    'DEF:cach_max={file}:cached:MAX',
+    'DEF:resv_avg={file}:resv:AVERAGE',
+    'DEF:resv_min={file}:resv:MIN',
+    'DEF:resv_max={file}:resv:MAX',
+    'CDEF:cach_avg_notnull=cach_avg,UN,0,cach_avg,IF',
+    'CDEF:resv_avg_notnull=resv_avg,UN,0,resv_avg,IF',
+    'CDEF:used_acc=used_avg',
+    'CDEF:resv_acc=used_acc,resv_avg_notnull,+',
+    'CDEF:cach_acc=resv_acc,cach_avg_notnull,+',
+    'CDEF:free_acc=cach_acc,free_avg,+',
+    "AREA:free_acc#$HalfGreen",
+    "AREA:cach_acc#$HalfBlue",
+    "AREA:resv_acc#$HalfYellow",
+    "AREA:used_acc#$HalfRed",
+    "LINE1:free_acc#$FullGreen:Free    ",
+    'GPRINT:free_min:MIN:%5.1lf%s Min,',
+    'GPRINT:free_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:free_max:MAX:%5.1lf%s Max,',
+    'GPRINT:free_avg:LAST:%5.1lf%s Last\n',
+    "LINE1:cach_acc#$FullBlue:Cached  ",
+    'GPRINT:cach_min:MIN:%5.1lf%s Min,',
+    'GPRINT:cach_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:cach_max:MAX:%5.1lf%s Max,',
+    'GPRINT:cach_avg:LAST:%5.1lf%s Last\l',
+    "LINE1:resv_acc#$FullYellow:Reserved",
+    'GPRINT:resv_min:MIN:%5.1lf%s Min,',
+    'GPRINT:resv_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:resv_max:MAX:%5.1lf%s Max,',
+    'GPRINT:resv_avg:LAST:%5.1lf%s Last\n',
+    "LINE1:used_acc#$FullRed:Used    ",
+    'GPRINT:used_min:MIN:%5.1lf%s Min,',
+    'GPRINT:used_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:used_max:MAX:%5.1lf%s Max,',
+    'GPRINT:used_avg:LAST:%5.1lf%s Last\l'
+    ],
+    tcp_connections => ['-v', 'Connections',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Connections",
+    'GPRINT:min:MIN:%4.1lf Min,',
+    'GPRINT:avg:AVERAGE:%4.1lf Avg,',
+    'GPRINT:max:MAX:%4.1lf Max,',
+    'GPRINT:avg:LAST:%4.1lf Last\l'
+    ],
+    temperature => ['-v', 'Celsius',
+    'DEF:temp_avg={file}:value:AVERAGE',
+    'DEF:temp_min={file}:value:MIN',
+    'DEF:temp_max={file}:value:MAX',
+    'CDEF:average=temp_avg,0.2,*,PREV,UN,temp_avg,PREV,IF,0.8,*,+',
+    "AREA:temp_max#$HalfRed",
+    "AREA:temp_min#$Canvas",
+    "LINE1:temp_avg#$FullRed:Temperature",
+    'GPRINT:temp_min:MIN:%4.1lf Min,',
+    'GPRINT:temp_avg:AVERAGE:%4.1lf Avg,',
+    'GPRINT:temp_max:MAX:%4.1lf Max,',
+    'GPRINT:temp_avg:LAST:%4.1lf Last\l'
+    ],
+    timeleft => ['-v', 'Minutes',
+    'DEF:avg={file}:timeleft:AVERAGE',
+    'DEF:min={file}:timeleft:MIN',
+    'DEF:max={file}:timeleft:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Time left [min]",
+    'GPRINT:min:MIN:%5.1lf%s Min,',
+    'GPRINT:avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:max:MAX:%5.1lf%s Max,',
+    'GPRINT:avg:LAST:%5.1lf%s Last\l'
+    ],
+    time_offset => [ # NTPd
+    'DEF:s_avg={file}:seconds:AVERAGE',
+    'DEF:s_min={file}:seconds:MIN',
+    'DEF:s_max={file}:seconds:MAX',
+    "AREA:s_max#$HalfBlue",
+    "AREA:s_min#$Canvas",
+    "LINE1:s_avg#$FullBlue:{inst}",
+    'GPRINT:s_min:MIN:%7.3lf%s Min,',
+    'GPRINT:s_avg:AVERAGE:%7.3lf%s Avg,',
+    'GPRINT:s_max:MAX:%7.3lf%s Max,',
+    'GPRINT:s_avg:LAST:%7.3lf%s Last'
+    ],
+    if_octets => ['-v', 'Bits/s', '-l', '0',
+    'DEF:out_min_raw={file}:tx:MIN',
+    'DEF:out_avg_raw={file}:tx:AVERAGE',
+    'DEF:out_max_raw={file}:tx:MAX',
+    'DEF:inc_min_raw={file}:rx:MIN',
+    'DEF:inc_avg_raw={file}:rx:AVERAGE',
+    'DEF:inc_max_raw={file}:rx:MAX',
+    'CDEF:out_min=out_min_raw,8,*',
+    'CDEF:out_avg=out_avg_raw,8,*',
+    'CDEF:out_max=out_max_raw,8,*',
+    'CDEF:inc_min=inc_min_raw,8,*',
+    'CDEF:inc_avg=inc_avg_raw,8,*',
+    'CDEF:inc_max=inc_max_raw,8,*',
+    'CDEF:overlap=out_avg,inc_avg,GT,inc_avg,out_avg,IF',
+    'CDEF:mytime=out_avg_raw,TIME,TIME,IF',
+    'CDEF:sample_len_raw=mytime,PREV(mytime),-',
+    'CDEF:sample_len=sample_len_raw,UN,0,sample_len_raw,IF',
+    'CDEF:out_avg_sample=out_avg_raw,UN,0,out_avg_raw,IF,sample_len,*',
+    'CDEF:out_avg_sum=PREV,UN,0,PREV,IF,out_avg_sample,+',
+    'CDEF:inc_avg_sample=inc_avg_raw,UN,0,inc_avg_raw,IF,sample_len,*',
+    'CDEF:inc_avg_sum=PREV,UN,0,PREV,IF,inc_avg_sample,+',
+    "AREA:out_avg#$HalfGreen",
+    "AREA:inc_avg#$HalfBlue",
+    "AREA:overlap#$HalfBlueGreen",
+    "LINE1:out_avg#$FullGreen:Outgoing",
+    'GPRINT:out_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:out_max:MAX:%5.1lf%s Max,',
+    'GPRINT:out_avg:LAST:%5.1lf%s Last',
+    'GPRINT:out_avg_sum:LAST:(ca. %5.1lf%sB Total)\l',
+    "LINE1:inc_avg#$FullBlue:Incoming",
+    #'GPRINT:inc_min:MIN:%5.1lf %s Min,',
+    'GPRINT:inc_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:inc_max:MAX:%5.1lf%s Max,',
+    'GPRINT:inc_avg:LAST:%5.1lf%s Last',
+    'GPRINT:inc_avg_sum:LAST:(ca. %5.1lf%sB Total)\l'
+    ],
+    cpufreq => [
+    'DEF:cpufreq_avg={file}:value:AVERAGE',
+    'DEF:cpufreq_min={file}:value:MIN',
+    'DEF:cpufreq_max={file}:value:MAX',
+    "AREA:cpufreq_max#$HalfBlue",
+    "AREA:cpufreq_min#$Canvas",
+    "LINE1:cpufreq_avg#$FullBlue:Frequency",
+    'GPRINT:cpufreq_min:MIN:%5.1lf%s Min,',
+    'GPRINT:cpufreq_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:cpufreq_max:MAX:%5.1lf%s Max,',
+    'GPRINT:cpufreq_avg:LAST:%5.1lf%s Last\l'
+    ],
+    multimeter => [
+    'DEF:multimeter_avg={file}:value:AVERAGE',
+    'DEF:multimeter_min={file}:value:MIN',
+    'DEF:multimeter_max={file}:value:MAX',
+    "AREA:multimeter_max#$HalfBlue",
+    "AREA:multimeter_min#$Canvas",
+    "LINE1:multimeter_avg#$FullBlue:Multimeter",
+    'GPRINT:multimeter_min:MIN:%4.1lf Min,',
+    'GPRINT:multimeter_avg:AVERAGE:%4.1lf Average,',
+    'GPRINT:multimeter_max:MAX:%4.1lf Max,',
+    'GPRINT:multimeter_avg:LAST:%4.1lf Last\l'
+    ],
+    users => ['-v', 'Users',
+    'DEF:users_avg={file}:users:AVERAGE',
+    'DEF:users_min={file}:users:MIN',
+    'DEF:users_max={file}:users:MAX',
+    "AREA:users_max#$HalfBlue",
+    "AREA:users_min#$Canvas",
+    "LINE1:users_avg#$FullBlue:Users",
+    'GPRINT:users_min:MIN:%4.1lf Min,',
+    'GPRINT:users_avg:AVERAGE:%4.1lf Average,',
+    'GPRINT:users_max:MAX:%4.1lf Max,',
+    'GPRINT:users_avg:LAST:%4.1lf Last\l'
+    ],
+    voltage => ['-v', 'Voltage',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Voltage",
+    'GPRINT:min:MIN:%5.1lf%sV Min,',
+    'GPRINT:avg:AVERAGE:%5.1lf%sV Avg,',
+    'GPRINT:max:MAX:%5.1lf%sV Max,',
+    'GPRINT:avg:LAST:%5.1lf%sV Last\l'
+    ],
+    vs_threads => [
+    "DEF:avg={file}:value:AVERAGE",
+    "DEF:min={file}:value:MIN",
+    "DEF:max={file}:value:MAX",
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Threads",
+    'GPRINT:min:MIN:%5.1lf Min,',
+    'GPRINT:avg:AVERAGE:%5.1lf Avg.,',
+    'GPRINT:max:MAX:%5.1lf Max,',
+    'GPRINT:avg:LAST:%5.1lf Last\l',
+    ],
+    vs_memory => ['-b', '1024', '-v', 'Bytes',
+    "DEF:avg={file}:value:AVERAGE",
+    "DEF:min={file}:value:MIN",
+    "DEF:max={file}:value:MAX",
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:",
+    'GPRINT:min:MIN:%5.1lf%sbytes Min,',
+    'GPRINT:avg:AVERAGE:%5.1lf%sbytes Avg.,',
+    'GPRINT:max:MAX:%5.1lf%sbytes Max,',
+    'GPRINT:avg:LAST:%5.1lf%sbytes Last\l',
+    ],
+    vs_processes => [
+    "DEF:avg={file}:value:AVERAGE",
+    "DEF:min={file}:value:MIN",
+    "DEF:max={file}:value:MAX",
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Processes",
+    'GPRINT:min:MIN:%5.1lf Min,',
+    'GPRINT:avg:AVERAGE:%5.1lf Avg.,',
+    'GPRINT:max:MAX:%5.1lf Max,',
+    'GPRINT:avg:LAST:%5.1lf Last\l',
+    ],
+    vmpage_number => ['-v', 'Pages',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Number",
+    'GPRINT:min:MIN:%4.1lf Min,',
+    'GPRINT:avg:AVERAGE:%4.1lf Avg,',
+    'GPRINT:max:MAX:%4.1lf Max,',
+    'GPRINT:avg:LAST:%4.1lf Last\l'
+    ],
+    vmpage_faults => [
+    "DEF:minf_avg={file}:minflt:AVERAGE",
+    "DEF:minf_min={file}:minflt:MIN",
+    "DEF:minf_max={file}:minflt:MAX",
+    "DEF:majf_avg={file}:majflt:AVERAGE",
+    "DEF:majf_min={file}:majflt:MIN",
+    "DEF:majf_max={file}:majflt:MAX",
+    'CDEF:overlap=majf_avg,minf_avg,GT,minf_avg,majf_avg,IF',
+    "AREA:majf_avg#$HalfGreen",
+    "AREA:minf_avg#$HalfBlue",
+    "AREA:overlap#$HalfBlueGreen",
+    "LINE1:majf_avg#$FullGreen:Major",
+    'GPRINT:majf_min:MIN:%5.1lf%s Min,',
+    'GPRINT:majf_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:majf_max:MAX:%5.1lf%s Max,',
+    'GPRINT:majf_avg:LAST:%5.1lf%s Last\l',
+    "LINE1:minf_avg#$FullBlue:Minor",
+    'GPRINT:minf_min:MIN:%5.1lf%s Min,',
+    'GPRINT:minf_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:minf_max:MAX:%5.1lf%s Max,',
+    'GPRINT:minf_avg:LAST:%5.1lf%s Last\l'
+    ],
+    vmpage_io => [
+    "DEF:rpag_avg={file}:in:AVERAGE",
+    "DEF:rpag_min={file}:in:MIN",
+    "DEF:rpag_max={file}:in:MAX",
+    "DEF:wpag_avg={file}:out:AVERAGE",
+    "DEF:wpag_min={file}:out:MIN",
+    "DEF:wpag_max={file}:out:MAX",
+    'CDEF:overlap=wpag_avg,rpag_avg,GT,rpag_avg,wpag_avg,IF',
+    "AREA:wpag_avg#$HalfGreen",
+    "AREA:rpag_avg#$HalfBlue",
+    "AREA:overlap#$HalfBlueGreen",
+    "LINE1:wpag_avg#$FullGreen:OUT",
+    'GPRINT:wpag_min:MIN:%5.1lf%s Min,',
+    'GPRINT:wpag_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:wpag_max:MAX:%5.1lf%s Max,',
+    'GPRINT:wpag_avg:LAST:%5.1lf%s Last\l',
+    "LINE1:rpag_avg#$FullBlue:IN ",
+    'GPRINT:rpag_min:MIN:%5.1lf%s Min,',
+    'GPRINT:rpag_avg:AVERAGE:%5.1lf%s Avg,',
+    'GPRINT:rpag_max:MAX:%5.1lf%s Max,',
+    'GPRINT:rpag_avg:LAST:%5.1lf%s Last\l'
+    ],
+    vmpage_action => ['-v', 'Pages',
+    'DEF:avg={file}:value:AVERAGE',
+    'DEF:min={file}:value:MIN',
+    'DEF:max={file}:value:MAX',
+    "AREA:max#$HalfBlue",
+    "AREA:min#$Canvas",
+    "LINE1:avg#$FullBlue:Number",
+    'GPRINT:min:MIN:%4.1lf Min,',
+    'GPRINT:avg:AVERAGE:%4.1lf Avg,',
+    'GPRINT:max:MAX:%4.1lf Max,',
+    'GPRINT:avg:LAST:%4.1lf Last\l'
+    ],
+    virt_cpu_total => ['-v', 'Milliseconds',
+    'DEF:avg_raw={file}:ns:AVERAGE',
+    'DEF:min_raw={file}:ns:MIN',
+    'DEF:max_raw={file}:ns:MAX',
+    'CDEF:avg=avg_raw,1000000,/',
+    'CDEF:min=min_raw,1000000,/',
+    'CDEF:max=max_raw,1000000,/',
+    "AREA:avg#$HalfBlue",
+    "LINE1:avg#$FullBlue:CPU time",
+    'GPRINT:min:MIN:%4.1lf Min,',
+    'GPRINT:avg:AVERAGE:%4.1lf Avg,',
+    'GPRINT:max:MAX:%4.1lf Max,',
+    'GPRINT:avg:LAST:%4.1lf Last\l'
+    ],
+  };
+  $GraphDefs->{'if_multicast'} = $GraphDefs->{'ipt_packets'};
+  $GraphDefs->{'if_tx_errors'} = $GraphDefs->{'if_rx_errors'};
+  $GraphDefs->{'dns_qtype'} = $GraphDefs->{'dns_opcode'};
+  $GraphDefs->{'dns_rcode'} = $GraphDefs->{'dns_opcode'};
+  $GraphDefs->{'vmpage_io-memory'} = $GraphDefs->{'vmpage_io'};
+  $GraphDefs->{'vmpage_io-swap'} = $GraphDefs->{'vmpage_io'};
+  $GraphDefs->{'virt_cpu_total'} = $GraphDefs->{'virt_cpu_total'};
+
+  $MetaGraphDefs->{'cpu'} = \&meta_graph_cpu;
+  $MetaGraphDefs->{'dns_qtype'} = \&meta_graph_dns;
+  $MetaGraphDefs->{'dns_rcode'} = \&meta_graph_dns;
+  $MetaGraphDefs->{'if_rx_errors'} = \&meta_graph_if_rx_errors;
+  $MetaGraphDefs->{'if_tx_errors'} = \&meta_graph_if_rx_errors;
+  $MetaGraphDefs->{'memory'} = \&meta_graph_memory;
+  $MetaGraphDefs->{'nfs_procedure'} = \&meta_graph_nfs_procedure;
+  $MetaGraphDefs->{'ps_state'} = \&meta_graph_ps_state;
+  $MetaGraphDefs->{'swap'} = \&meta_graph_swap;
+  $MetaGraphDefs->{'mysql_commands'} = \&meta_graph_mysql_commands;
+  $MetaGraphDefs->{'mysql_handler'} = \&meta_graph_mysql_commands;
+  $MetaGraphDefs->{'tcp_connections'} = \&meta_graph_tcp_connections;
+  $MetaGraphDefs->{'vmpage_number'} = \&meta_graph_vmpage_number;
+  $MetaGraphDefs->{'vmpage_action'} = \&meta_graph_vmpage_action;
+} # load_graph_definitions
+
+sub meta_graph_generic_stack
+{
+  confess ("Wrong number of arguments") if (@_ != 2);
+
+  my $opts = shift;
+  my $sources = shift;
+  my $i;
+
+  my $timespan_str = _get_param_timespan ();
+  my $timespan_int = (-1) * $ValidTimespan->{$timespan_str};
+
+  $opts->{'title'} ||= 'Unknown title';
+  $opts->{'rrd_opts'} ||= [];
+  $opts->{'colors'} ||= {};
+
+  my @cmd = ('-', '-a', 'PNG', '-s', $timespan_int,
+    '-t', $opts->{'title'} || 'Unknown title',
+    @RRDDefaultArgs, @{$opts->{'rrd_opts'}});
+
+  my $max_inst_name = 0;
+  my @vnames = ();
+
+  for ($i = 0; $i < @$sources; $i++)
+  {
+    my $tmp = $sources->[$i]->{'name'};
+    $tmp =~ tr/A-Za-z0-9\-_/_/c;
+    $vnames[$i] = $i . $tmp;
+  }
+
+  for ($i = 0; $i < @$sources; $i++)
+  {
+    my $inst_data = $sources->[$i];
+    my $inst_name = $inst_data->{'name'} || confess;
+    my $file = $inst_data->{'file'} || confess;
+    my $vname = $vnames[$i];
+
+    if (length ($inst_name) > $max_inst_name)
+    {
+      $max_inst_name = length ($inst_name);
+    }
+
+    confess ("No such file: $file") if (!-e $file);
+
+    push (@cmd,
+      qq#DEF:${vname}_min=$file:value:MIN#,
+      qq#DEF:${vname}_avg=$file:value:AVERAGE#,
+      qq#DEF:${vname}_max=$file:value:MAX#,
+      qq#CDEF:${vname}_nnl=${vname}_avg,UN,0,${vname}_avg,IF#);
+  }
+
+  {
+    my $vname = $vnames[@vnames - 1];
+
+    push (@cmd, qq#CDEF:${vname}_stk=${vname}_nnl#);
+  }
+  for (my $i = 1; $i < @$sources; $i++)
+  {
+    my $vname0 = $vnames[@vnames - ($i + 1)];
+    my $vname1 = $vnames[@vnames - $i];
+
+    push (@cmd, qq#CDEF:${vname0}_stk=${vname0}_nnl,${vname1}_stk,+#);
+  }
+
+  for (my $i = 0; $i < @$sources; $i++)
+  {
+    my $inst_data = $sources->[$i];
+    my $inst_name = $inst_data->{'name'};
+
+    my $vname = $vnames[$i];
+
+    my $legend = sprintf ('%-*s', $max_inst_name, $inst_name);
+
+    my $line_color;
+    my $area_color;
+
+    my $number_format = $opts->{'number_format'} || '%6.1lf';
+
+    if (exists ($opts->{'colors'}{$inst_name}))
+    {
+      $line_color = $opts->{'colors'}{$inst_name};
+      $area_color = _string_to_color ($line_color);
+    }
+    else
+    {
+      $area_color = _get_random_color ();
+      $line_color = _color_to_string ($area_color);
+    }
+    $area_color = _color_to_string (_get_faded_color ($area_color));
+
+    push (@cmd, qq(AREA:${vname}_stk#$area_color),
+      qq(LINE1:${vname}_stk#$line_color:$legend),
+      qq(GPRINT:${vname}_min:MIN:$number_format Min,),
+      qq(GPRINT:${vname}_avg:AVERAGE:$number_format Avg,),
+      qq(GPRINT:${vname}_max:MAX:$number_format Max,),
+      qq(GPRINT:${vname}_avg:LAST:$number_format Last\\l),
+    );
+  }
+
+  RRDs::graph (@cmd);
+  if (my $errmsg = RRDs::error ())
+  {
+    confess ("RRDs::graph: $errmsg");
+  }
+} # meta_graph_generic_stack
+
+sub meta_graph_cpu
+{
+  confess ("Wrong number of arguments") if (@_ != 5);
+
+  my $host = shift;
+  my $plugin = shift;
+  my $plugin_instance = shift;
+  my $type = shift;
+  my $type_instances = shift;
+
+  my $opts = {};
+  my $sources = [];
+
+  $opts->{'title'} = "$host/$plugin"
+  . (defined ($plugin_instance) ? "-$plugin_instance" : '') . "/$type";
+
+  $opts->{'rrd_opts'} = ['-v', 'Percent'];
+
+  my @files = ();
+
+  $opts->{'colors'} =
+  {
+    'idle'      => 'ffffff',
+    'nice'      => '00e000',
+    'user'      => '0000ff',
+    'wait'      => 'ffb000',
+    'system'    => 'ff0000',
+    'softirq'   => 'ff00ff',
+    'interrupt' => 'a000a0',
+    'steal'     => '000000'
+  };
+
+  _custom_sort_arrayref ($type_instances,
+    [qw(idle nice user wait system softirq interrupt steal)]);
+
+  for (@$type_instances)
+  {
+    my $inst = $_;
+    my $file = '';
+    my $title = $opts->{'title'};
+
+    for (@DataDirs)
+    {
+      if (-e "$_/$title-$inst.rrd")
+      {
+       $file = "$_/$title-$inst.rrd";
+       last;
+      }
+    }
+    confess ("No file found for $title") if ($file eq '');
+
+    push (@$sources,
+      {
+       name => $inst,
+       file => $file
+      }
+    );
+  } # for (@$type_instances)
+
+  return (meta_graph_generic_stack ($opts, $sources));
+} # meta_graph_cpu
+
+sub meta_graph_dns
+{
+  confess ("Wrong number of arguments") if (@_ != 5);
+
+  my $host = shift;
+  my $plugin = shift;
+  my $plugin_instance = shift;
+  my $type = shift;
+  my $type_instances = shift;
+
+  my $opts = {};
+  my $sources = [];
+
+  $opts->{'title'} = "$host/$plugin"
+  . (defined ($plugin_instance) ? "-$plugin_instance" : '') . "/$type";
+
+  $opts->{'rrd_opts'} = ['-v', 'Queries/s'];
+
+  my @files = ();
+
+  @$type_instances = sort @$type_instances;
+
+  $opts->{'colors'} = _get_n_colors ($type_instances);
+
+  for (@$type_instances)
+  {
+    my $inst = $_;
+    my $file = '';
+    my $title = $opts->{'title'};
+
+    for (@DataDirs)
+    {
+      if (-e "$_/$title-$inst.rrd")
+      {
+       $file = "$_/$title-$inst.rrd";
+       last;
+      }
+    }
+    confess ("No file found for $title") if ($file eq '');
+
+    push (@$sources,
+      {
+       name => $inst,
+       file => $file
+      }
+    );
+  } # for (@$type_instances)
+
+  return (meta_graph_generic_stack ($opts, $sources));
+} # meta_graph_dns
+
+sub meta_graph_memory
+{
+  confess ("Wrong number of arguments") if (@_ != 5);
+
+  my $host = shift;
+  my $plugin = shift;
+  my $plugin_instance = shift;
+  my $type = shift;
+  my $type_instances = shift;
+
+  my $opts = {};
+  my $sources = [];
+
+  $opts->{'title'} = "$host/$plugin"
+  . (defined ($plugin_instance) ? "-$plugin_instance" : '') . "/$type";
+  $opts->{'number_format'} = '%5.1lf%s';
+
+  $opts->{'rrd_opts'} = ['-b', '1024', '-v', 'Bytes'];
+
+  my @files = ();
+
+  $opts->{'colors'} =
+  {
+    'free'     => '00e000',
+    'cached'   => '0000ff',
+    'buffered' => 'ffb000',
+    'used'     => 'ff0000'
+  };
+
+  _custom_sort_arrayref ($type_instances,
+    [qw(free cached buffered used)]);
+
+  for (@$type_instances)
+  {
+    my $inst = $_;
+    my $file = '';
+    my $title = $opts->{'title'};
+
+    for (@DataDirs)
+    {
+      if (-e "$_/$title-$inst.rrd")
+      {
+       $file = "$_/$title-$inst.rrd";
+       last;
+      }
+    }
+    confess ("No file found for $title") if ($file eq '');
+
+    push (@$sources,
+      {
+       name => $inst,
+       file => $file
+      }
+    );
+  } # for (@$type_instances)
+
+  return (meta_graph_generic_stack ($opts, $sources));
+} # meta_graph_memory
+
+sub meta_graph_if_rx_errors
+{
+  confess ("Wrong number of arguments") if (@_ != 5);
+
+  my $host = shift;
+  my $plugin = shift;
+  my $plugin_instance = shift;
+  my $type = shift;
+  my $type_instances = shift;
+
+  my $opts = {};
+  my $sources = [];
+
+  $opts->{'title'} = "$host/$plugin"
+  . (defined ($plugin_instance) ? "-$plugin_instance" : '') . "/$type";
+  $opts->{'number_format'} = '%5.2lf';
+  $opts->{'rrd_opts'} = ['-v', 'Errors/s'];
+
+  my @files = ();
+
+  for (sort @$type_instances)
+  {
+    my $inst = $_;
+    my $file = '';
+    my $title = $opts->{'title'};
+
+    for (@DataDirs)
+    {
+      if (-e "$_/$title-$inst.rrd")
+      {
+       $file = "$_/$title-$inst.rrd";
+       last;
+      }
+    }
+    confess ("No file found for $title") if ($file eq '');
+
+    push (@$sources,
+      {
+       name => $inst,
+       file => $file
+      }
+    );
+  } # for (@$type_instances)
+
+  return (meta_graph_generic_stack ($opts, $sources));
+} # meta_graph_if_rx_errors
+
+sub meta_graph_mysql_commands
+{
+  confess ("Wrong number of arguments") if (@_ != 5);
+
+  my $host = shift;
+  my $plugin = shift;
+  my $plugin_instance = shift;
+  my $type = shift;
+  my $type_instances = shift;
+
+  my $opts = {};
+  my $sources = [];
+
+  $opts->{'title'} = "$host/$plugin"
+  . (defined ($plugin_instance) ? "-$plugin_instance" : '') . "/$type";
+  $opts->{'number_format'} = '%5.2lf';
+
+  my @files = ();
+
+  for (sort @$type_instances)
+  {
+    my $inst = $_;
+    my $file = '';
+    my $title = $opts->{'title'};
+
+    for (@DataDirs)
+    {
+      if (-e "$_/$title-$inst.rrd")
+      {
+       $file = "$_/$title-$inst.rrd";
+       last;
+      }
+    }
+    confess ("No file found for $title") if ($file eq '');
+
+    push (@$sources,
+      {
+       name => $inst,
+       file => $file
+      }
+    );
+  } # for (@$type_instances)
+
+  return (meta_graph_generic_stack ($opts, $sources));
+} # meta_graph_mysql_commands
+
+sub meta_graph_nfs_procedure
+{
+  confess ("Wrong number of arguments") if (@_ != 5);
+
+  my $host = shift;
+  my $plugin = shift;
+  my $plugin_instance = shift;
+  my $type = shift;
+  my $type_instances = shift;
+
+  my $opts = {};
+  my $sources = [];
+
+  $opts->{'title'} = "$host/$plugin"
+  . (defined ($plugin_instance) ? "-$plugin_instance" : '') . "/$type";
+  $opts->{'number_format'} = '%5.1lf%s';
+
+  my @files = ();
+
+  for (sort @$type_instances)
+  {
+    my $inst = $_;
+    my $file = '';
+    my $title = $opts->{'title'};
+
+    for (@DataDirs)
+    {
+      if (-e "$_/$title-$inst.rrd")
+      {
+       $file = "$_/$title-$inst.rrd";
+       last;
+      }
+    }
+    confess ("No file found for $title") if ($file eq '');
+
+    push (@$sources,
+      {
+       name => $inst,
+       file => $file
+      }
+    );
+  } # for (@$type_instances)
+
+  return (meta_graph_generic_stack ($opts, $sources));
+} # meta_graph_nfs_procedure
+
+sub meta_graph_ps_state
+{
+  confess ("Wrong number of arguments") if (@_ != 5);
+
+  my $host = shift;
+  my $plugin = shift;
+  my $plugin_instance = shift;
+  my $type = shift;
+  my $type_instances = shift;
+
+  my $opts = {};
+  my $sources = [];
+
+  $opts->{'title'} = "$host/$plugin"
+  . (defined ($plugin_instance) ? "-$plugin_instance" : '') . "/$type";
+  $opts->{'rrd_opts'} = ['-v', 'Processes'];
+
+  my @files = ();
+
+  $opts->{'colors'} =
+  {
+    'Running'      => '00e000',
+    'Sleeping'  => '0000ff',
+    'Paging'      => 'ffb000',
+    'Zombies'   => 'ff0000',
+    'Blocked'   => 'ff00ff',
+    'Stopped' => 'a000a0'
+  };
+
+  _custom_sort_arrayref ($type_instances,
+    [qw(paging blocked zombies stopped running sleeping)]);
+
+  for (@$type_instances)
+  {
+    my $inst = $_;
+    my $file = '';
+    my $title = $opts->{'title'};
+
+    for (@DataDirs)
+    {
+      if (-e "$_/$title-$inst.rrd")
+      {
+       $file = "$_/$title-$inst.rrd";
+       last;
+      }
+    }
+    confess ("No file found for $title") if ($file eq '');
+
+    push (@$sources,
+      {
+       name => ucfirst ($inst),
+       file => $file
+      }
+    );
+  } # for (@$type_instances)
+
+  return (meta_graph_generic_stack ($opts, $sources));
+} # meta_graph_ps_state
+
+sub meta_graph_swap
+{
+  confess ("Wrong number of arguments") if (@_ != 5);
+
+  my $host = shift;
+  my $plugin = shift;
+  my $plugin_instance = shift;
+  my $type = shift;
+  my $type_instances = shift;
+
+  my $opts = {};
+  my $sources = [];
+
+  $opts->{'title'} = "$host/$plugin"
+  . (defined ($plugin_instance) ? "-$plugin_instance" : '') . "/$type";
+  $opts->{'number_format'} = '%5.1lf%s';
+  $opts->{'rrd_opts'} = ['-v', 'Bytes'];
+
+  my @files = ();
+
+  $opts->{'colors'} =
+  {
+    'Free'     => '00e000',
+    'Cached'   => '0000ff',
+    'Reserved' => 'ffb000',
+    'Used'     => 'ff0000'
+  };
+
+  _custom_sort_arrayref ($type_instances,
+    [qw(free cached reserved used)]);
+
+  for (@$type_instances)
+  {
+    my $inst = $_;
+    my $file = '';
+    my $title = $opts->{'title'};
+
+    for (@DataDirs)
+    {
+      if (-e "$_/$title-$inst.rrd")
+      {
+       $file = "$_/$title-$inst.rrd";
+       last;
+      }
+    }
+    confess ("No file found for $title") if ($file eq '');
+
+    push (@$sources,
+      {
+       name => ucfirst ($inst),
+       file => $file
+      }
+    );
+  } # for (@$type_instances)
+
+  return (meta_graph_generic_stack ($opts, $sources));
+} # meta_graph_swap
+
+sub meta_graph_tcp_connections
+{
+  confess ("Wrong number of arguments") if (@_ != 5);
+
+  my $host = shift;
+  my $plugin = shift;
+  my $plugin_instance = shift;
+  my $type = shift;
+  my $type_instances = shift;
+
+  my $opts = {};
+  my $sources = [];
+
+  $opts->{'title'} = "$host/$plugin"
+  . (defined ($plugin_instance) ? "-$plugin_instance" : '') . "/$type";
+  $opts->{'number_format'} = '%6.2lf';
+
+  $opts->{'rrd_opts'} = ['-v', 'Connections'];
+
+  my @files = ();
+
+  $opts->{'colors'} =
+  {
+    ESTABLISHED          => '00e000',
+    SYN_SENT     => '00e0ff',
+    SYN_RECV     => '00e0a0',
+    FIN_WAIT1    => 'f000f0',
+    FIN_WAIT2    => 'f000a0',
+    TIME_WAIT    => 'ffb000',
+    CLOSE        => '0000f0',
+    CLOSE_WAIT   => '0000a0',
+    LAST_ACK     => '000080',
+    LISTEN       => 'ff0000',
+    CLOSING      => '000000'
+  };
+
+  _custom_sort_arrayref ($type_instances,
+    [reverse qw(ESTABLISHED SYN_SENT SYN_RECV FIN_WAIT1 FIN_WAIT2 TIME_WAIT CLOSE
+    CLOSE_WAIT LAST_ACK CLOSING LISTEN)]);
+
+  for (@$type_instances)
+  {
+    my $inst = $_;
+    my $file = '';
+    my $title = $opts->{'title'};
+
+    for (@DataDirs)
+    {
+      if (-e "$_/$title-$inst.rrd")
+      {
+       $file = "$_/$title-$inst.rrd";
+       last;
+      }
+    }
+    confess ("No file found for $title") if ($file eq '');
+
+    push (@$sources,
+      {
+       name => $inst,
+       file => $file
+      }
+    );
+  } # for (@$type_instances)
+
+  return (meta_graph_generic_stack ($opts, $sources));
+} # meta_graph_tcp_connections
+
+sub meta_graph_vmpage_number
+{
+  confess ("Wrong number of arguments") if (@_ != 5);
+
+  my $host = shift;
+  my $plugin = shift;
+  my $plugin_instance = shift;
+  my $type = shift;
+  my $type_instances = shift;
+
+  my $opts = {};
+  my $sources = [];
+
+  $opts->{'title'} = "$host/$plugin"
+  . (defined ($plugin_instance) ? "-$plugin_instance" : '') . "/$type";
+  $opts->{'number_format'} = '%6.2lf';
+
+  $opts->{'rrd_opts'} = ['-v', 'Pages'];
+
+  my @files = ();
+
+  $opts->{'colors'} =
+  {
+    anon_pages   => '00e000',
+    bounce       => '00e0ff',
+    dirty        => '00e0a0',
+    file_pages   => 'f000f0',
+    mapped       => 'f000a0',
+    page_table_pages     => 'ffb000',
+    slab         => '0000f0',
+    unstable     => '0000a0',
+    writeback    => 'ff0000',
+  };
+
+  _custom_sort_arrayref ($type_instances,
+    [reverse qw(anon_pages bounce dirty file_pages mapped page_table_pages slab unstable writeback)]);
+
+  for (@$type_instances)
+  {
+    my $inst = $_;
+    my $file = '';
+    my $title = $opts->{'title'};
+
+    for (@DataDirs)
+    {
+      if (-e "$_/$title-$inst.rrd")
+      {
+       $file = "$_/$title-$inst.rrd";
+       last;
+      }
+    }
+    confess ("No file found for $title") if ($file eq '');
+
+    push (@$sources,
+      {
+       name => $inst,
+       file => $file
+      }
+    );
+  } # for (@$type_instances)
+
+  return (meta_graph_generic_stack ($opts, $sources));
+} # meta_graph_vmpage_number
+
+sub meta_graph_vmpage_action
+{
+  confess ("Wrong number of arguments") if (@_ != 5);
+
+  my $host = shift;
+  my $plugin = shift;
+  my $plugin_instance = shift;
+  my $type = shift;
+  my $type_instances = shift;
+
+  my $opts = {};
+  my $sources = [];
+
+  $opts->{'title'} = "$host/$plugin"
+  . (defined ($plugin_instance) ? "-$plugin_instance" : '') . "/$type";
+  $opts->{'number_format'} = '%6.2lf';
+
+  $opts->{'rrd_opts'} = ['-v', 'Pages'];
+
+  my @files = ();
+
+  $opts->{'colors'} =
+  {
+    activate     => '00e000',
+    deactivate   => '00e0ff',
+    free         => '00e0a0',
+    alloc        => 'f000f0',
+    refill       => 'f000a0',
+    scan_direct          => 'ffb000',
+    scan_kswapd          => '0000f0',
+    steal        => '0000a0',
+  };
+
+  _custom_sort_arrayref ($type_instances,
+    [reverse qw(activate deactivate alloc free refill scan_direct scan_kswapd steal)]);
+
+  for (@$type_instances)
+  {
+    my $inst = $_;
+    my $file = '';
+    my $title = $opts->{'title'};
+
+    for (@DataDirs)
+    {
+      if (-e "$_/$title-$inst.rrd")
+      {
+       $file = "$_/$title-$inst.rrd";
+       last;
+      }
+    }
+    confess ("No file found for $title") if ($file eq '');
+
+    push (@$sources,
+      {
+       name => $inst,
+       file => $file
+      }
+    );
+  } # for (@$type_instances)
+
+  return (meta_graph_generic_stack ($opts, $sources));
+} # meta_graph_vmpage_action
+# vim: shiftwidth=2:softtabstop=2:tabstop=8
diff --git a/contrib/collection.conf b/contrib/collection.conf
new file mode 100644 (file)
index 0000000..e8444f5
--- /dev/null
@@ -0,0 +1,3 @@
+datadir: "/opt/collectd/var/lib/collectd/rrd/"
+libdir: "/opt/collectd/lib/collectd/"
+
diff --git a/contrib/collection3/README b/contrib/collection3/README
new file mode 100644 (file)
index 0000000..c890042
--- /dev/null
@@ -0,0 +1,43 @@
+ collection3 - Web frontend for collectd
+=========================================
+http://collectd.org/
+
+About
+-----
+
+  collection3 is a graphing front-end for the RRD files created by and filled
+  with collectd. It is written in Perl and should be run as an CGI-script.
+  Graphs are generated on-the-fly, so no cron job or similar is necessary.
+
+Layout
+------
+
+  The files used by collection3 are organized in a typical UNIX fashion: The
+  configuration resides in etc/, executable scripts are in bin/, supplementary
+  Perl modules are in lib/ and static data for displaying the web page are in
+  share/.
+
+  All files in all subdirectories except bin/ should NOT be executable.
+  Ideally, the webserver should not serve them either. Consider using
+  `.htaccess' files or other means to configure the web server to deny access
+  to these directories.
+
+Dependencies
+------------
+
+  collection3 depends on the following Perl modules not included in the Perl
+  distribution itself:
+
+  * Config::General
+  * Regexp::Common
+  * HTML::Entities
+  * RRDs
+
+Copyright and License
+---------------------
+
+  Copyright (C) 2008  Florian octo Forster <octo at verplant.org>
+
+  collection3 is provided under the terms of the GNU General Public License,
+  version 2 (GPLv2).
+
diff --git a/contrib/collection3/bin/.htaccess b/contrib/collection3/bin/.htaccess
new file mode 100644 (file)
index 0000000..1695f50
--- /dev/null
@@ -0,0 +1,2 @@
+Options +ExecCGI
+AddHandler cgi-script .cgi
diff --git a/contrib/collection3/bin/graph.cgi b/contrib/collection3/bin/graph.cgi
new file mode 100755 (executable)
index 0000000..2b3f2fe
--- /dev/null
@@ -0,0 +1,334 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2008-2011  Florian Forster
+# Copyright (C) 2011       noris network AG
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; only version 2 of the License is applicable.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+#
+# Authors:
+#   Florian "octo" Forster <octo at collectd.org>
+
+use strict;
+use warnings;
+use utf8;
+use vars (qw($BASE_DIR));
+
+BEGIN
+{
+  if (defined $ENV{'SCRIPT_FILENAME'})
+  {
+    if ($ENV{'SCRIPT_FILENAME'} =~ m{^(/.+)/bin/[^/]+$})
+    {
+      $::BASE_DIR = $1;
+      unshift (@::INC, "$::BASE_DIR/lib");
+    }
+  }
+}
+
+use Carp (qw(confess cluck));
+use CGI (':cgi');
+use RRDs ();
+use File::Temp (':POSIX');
+
+use Collectd::Graph::Config (qw(gc_read_config gc_get_scalar));
+use Collectd::Graph::TypeLoader (qw(tl_load_type));
+
+use Collectd::Graph::Common (qw(sanitize_type get_selected_files
+      epoch_to_rfc1123 flush_files));
+use Collectd::Graph::Type ();
+
+sub base_dir
+{
+  if (defined $::BASE_DIR)
+  {
+    return ($::BASE_DIR);
+  }
+
+  if (!defined ($ENV{'SCRIPT_FILENAME'}))
+  {
+    return;
+  }
+
+  if ($ENV{'SCRIPT_FILENAME'} =~ m{^(/.+)/bin/[^/]+$})
+  {
+    $::BASE_DIR = $1;
+    return ($::BASE_DIR);
+  }
+
+  return;
+}
+
+sub lib_dir
+{
+  my $base = base_dir ();
+
+  if ($base)
+  {
+    return "$base/lib";
+  }
+  else
+  {
+    return "../lib";
+  }
+}
+
+sub sysconf_dir
+{
+  my $base = base_dir ();
+
+  if ($base)
+  {
+    return "$base/etc";
+  }
+  else
+  {
+    return "../etc";
+  }
+}
+
+sub init
+{
+  my $lib_dir = lib_dir ();
+  my $sysconf_dir = sysconf_dir ();
+
+  if (!grep { $lib_dir eq $_ } (@::INC))
+  {
+    unshift (@::INC, $lib_dir);
+  }
+
+  gc_read_config ("$sysconf_dir/collection.conf");
+}
+
+sub main
+{
+  my $Begin = param ('begin');
+  my $End = param ('end');
+  my $GraphWidth = param ('width');
+  my $GraphHeight = param ('height');
+  my $Index = param ('index') || 0;
+  my $OutputFormat = 'PNG';
+  my $ContentType = 'image/png';
+
+  init ();
+
+  if (param ('format'))
+  {
+    my $temp = param ('format') || '';
+    $temp = uc ($temp);
+
+    if ($temp =~ m/^(PNG|SVG|EPS|PDF)$/)
+    {
+      $OutputFormat = $temp;
+
+      if ($OutputFormat eq 'SVG') { $ContentType = 'image/svg+xml'; }
+      elsif ($OutputFormat eq 'EPS') { $ContentType = 'image/eps'; }
+      elsif ($OutputFormat eq 'PDF') { $ContentType = 'application/pdf'; }
+    }
+  }
+
+  if (param ('debug'))
+  {
+    print <<HTTP;
+Content-Type: text/plain
+
+HTTP
+    $ContentType = 'text/plain';
+  }
+
+  if ($GraphWidth)
+  {
+    $GraphWidth =~ s/\D//g;
+  }
+
+  if (!$GraphWidth)
+  {
+    $GraphWidth = gc_get_scalar ('GraphWidth', 400);
+  }
+
+  if ($GraphHeight)
+  {
+    $GraphHeight =~ s/\D//g;
+  }
+
+  if (!$GraphHeight)
+  {
+    $GraphHeight = gc_get_scalar ('GraphHeight', 100);
+  }
+
+  { # Sanitize begin and end times
+    $End ||= 0;
+    $Begin ||= 0;
+
+    if ($End =~ m/\D/)
+    {
+      $End = 0;
+    }
+
+    if (!$Begin || !($Begin =~ m/^-?([1-9][0-9]*)$/))
+    {
+      $Begin = -86400;
+    }
+
+    if ($Begin < 0)
+    {
+      if ($End)
+      {
+        $Begin = $End + $Begin;
+      }
+      else
+      {
+        $Begin = time () + $Begin;
+      }
+    }
+
+    if ($Begin < 0)
+    {
+      $Begin = time () - 86400;
+    }
+
+    if (($End > 0) && ($Begin > $End))
+    {
+      my $temp = $End;
+      $End = $Begin;
+      $Begin = $temp;
+    }
+  }
+
+  my $type = param ('type') or die;
+  my $obj;
+
+  $obj = tl_load_type ($type);
+  if (!$obj)
+  {
+    confess ("tl_load_type ($type) failed");
+  }
+
+  $type = ucfirst (lc ($type));
+  $type =~ s/_([A-Za-z])/\U$1\E/g;
+  $type = sanitize_type ($type);
+
+  my $files = get_selected_files ();
+  if (param ('debug'))
+  {
+    require Data::Dumper;
+    print Data::Dumper->Dump ([$files], ['files']);
+  }
+  for (@$files)
+  {
+    $obj->addFiles ($_);
+  }
+
+  my $expires = time ();
+# IF (End is `now')
+#    OR (Begin is before `now' AND End is after `now')
+  if (($End == 0) || (($Begin <= $expires) && ($End >= $expires)))
+  {
+    # 400 == width in pixels
+    my $timespan;
+
+    if ($End == 0)
+    {
+      $timespan = $expires - $Begin;
+    }
+    else
+    {
+      $timespan = $End - $Begin;
+    }
+    $expires += int ($timespan / 400.0);
+  }
+# IF (End is not `now')
+#    AND (End is before `now')
+# ==> Graph will never change again!
+  elsif (($End > 0) && ($End < $expires))
+  {
+    $expires += (366 * 86400);
+  }
+  elsif ($Begin > $expires)
+  {
+    $expires = $Begin;
+  }
+
+# Send FLUSH command to the daemon if necessary and possible.
+  flush_files ($files,
+    begin => $Begin,
+    end => $End,
+    addr => gc_get_scalar ('UnixSockAddr', undef),
+    interval => gc_get_scalar ('Interval', 10));
+
+  print header (-Content_type => $ContentType,
+    -Last_Modified => epoch_to_rfc1123 ($obj->getLastModified ()),
+    -Expires => epoch_to_rfc1123 ($expires));
+
+  if (param ('debug'))
+  {
+    print "\$expires = $expires;\n";
+  }
+
+  my $args = $obj->getRRDArgs (0 + $Index);
+  if (param ('debug'))
+  {
+    require Data::Dumper;
+    print Data::Dumper->Dump ([$obj], ['obj']);
+    print join (",\n", @$args) . "\n";
+    print "Last-Modified: " . epoch_to_rfc1123 ($obj->getLastModified ()) . "\n";
+  }
+  else
+  {
+    my @timesel = ();
+    my $tmpfile = tmpnam ();
+    my $status;
+
+    if ($End) # $Begin is always true
+    {
+      @timesel = ('-s', $Begin, '-e', $End);
+    }
+    else
+    {
+      @timesel = ('-s', $Begin); # End is implicitely `now'.
+    }
+
+    if (-S "/var/run/rrdcached.sock" && -w "/var/run/rrdcached.sock")
+    {
+      $ENV{"RRDCACHED_ADDRESS"} = "/var/run/rrdcached.sock";
+    }
+    unlink ($tmpfile);
+    RRDs::graph ($tmpfile, '-a', $OutputFormat, '--width', $GraphWidth, '--height', $GraphHeight, @timesel, @$args);
+    if (my $err = RRDs::error ())
+    {
+      print STDERR "RRDs::graph failed: $err\n";
+      exit (1);
+    }
+
+    $status = open (IMG, '<', $tmpfile) or die ("open ($tmpfile): $!");
+    if (!$status)
+    {
+      print STDERR "graph.cgi: Unable to open temporary file \"$tmpfile\" for reading: $!\n";
+    }
+    else
+    {
+      local $/ = undef;
+      while (my $data = <IMG>)
+      {
+        print STDOUT $data;
+      }
+
+      close (IMG);
+      unlink ($tmpfile);
+    }
+  }
+} # sub main
+
+main ();
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
diff --git a/contrib/collection3/bin/index.cgi b/contrib/collection3/bin/index.cgi
new file mode 100755 (executable)
index 0000000..027961f
--- /dev/null
@@ -0,0 +1,476 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2008-2011  Florian Forster
+# Copyright (C) 2011       noris network AG
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; only version 2 of the License is applicable.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+#
+# Authors:
+#   Florian "octo" Forster <octo at collectd.org>
+
+use strict;
+use warnings;
+use utf8;
+use vars (qw($BASE_DIR));
+
+BEGIN
+{
+  if (defined $ENV{'SCRIPT_FILENAME'})
+  {
+    if ($ENV{'SCRIPT_FILENAME'} =~ m{^(/.+)/bin/[^/]+$})
+    {
+      $::BASE_DIR = $1;
+      unshift (@::INC, "$::BASE_DIR/lib");
+    }
+  }
+}
+
+use Carp (qw(cluck confess));
+use CGI (':cgi');
+use CGI::Carp ('fatalsToBrowser');
+use HTML::Entities ('encode_entities');
+
+use Data::Dumper;
+
+use Collectd::Graph::Config (qw(gc_read_config gc_get_scalar));
+use Collectd::Graph::TypeLoader (qw(tl_load_type));
+use Collectd::Graph::Common (qw(get_files_from_directory get_all_hosts
+      get_timespan_selection get_selected_files get_host_selection
+      get_plugin_selection flush_files));
+use Collectd::Graph::Type ();
+
+our $TimeSpans =
+{
+  Hour  =>        3600,
+  Day   =>       86400,
+  Week  =>   7 * 86400,
+  Month =>  31 * 86400,
+  Year  => 366 * 86400
+};
+
+my %Actions =
+(
+  list_hosts => \&action_list_hosts,
+  show_selection => \&action_show_selection
+);
+
+sub base_dir
+{
+  if (defined $::BASE_DIR)
+  {
+    return ($::BASE_DIR);
+  }
+
+  if (!defined ($ENV{'SCRIPT_FILENAME'}))
+  {
+    return;
+  }
+
+  if ($ENV{'SCRIPT_FILENAME'} =~ m{^(/.+)/bin/[^/]+$})
+  {
+    $::BASE_DIR = $1;
+    return ($::BASE_DIR);
+  }
+
+  return;
+}
+
+sub lib_dir
+{
+  my $base = base_dir ();
+
+  if ($base)
+  {
+    return "$base/lib";
+  }
+  else
+  {
+    return "../lib";
+  }
+}
+
+sub sysconf_dir
+{
+  my $base = base_dir ();
+
+  if ($base)
+  {
+    return "$base/etc";
+  }
+  else
+  {
+    return "../etc";
+  }
+}
+
+sub init
+{
+  my $lib_dir = lib_dir ();
+  my $sysconf_dir = sysconf_dir ();
+
+  if (!grep { $lib_dir eq $_ } (@::INC))
+  {
+    unshift (@::INC, $lib_dir);
+  }
+
+  gc_read_config ("$sysconf_dir/collection.conf");
+}
+
+sub main
+{
+  my $Debug = param ('debug') ? 1 : 0;
+  my $action = param ('action') || 'list_hosts';
+
+  if (!exists ($Actions{$action}))
+  {
+    print STDERR "No such action: $action\n";
+    return (1);
+  }
+
+  init ();
+
+  $Actions{$action}->();
+  return (1);
+} # sub main
+
+sub can_handle_xhtml
+{
+  my %types = ();
+
+  if (!defined $ENV{'HTTP_ACCEPT'})
+  {
+    return;
+  }
+
+  for (split (',', $ENV{'HTTP_ACCEPT'}))
+  {
+    my $type = lc ($_);
+    my $q = 1.0;
+
+    if ($type =~ m#^([^;]+);q=([0-9\.]+)$#)
+    {
+      $type = $1;
+      $q = 0.0 + $2;
+    }
+    $types{$type} = $q;
+  }
+
+  if (!defined ($types{'application/xhtml+xml'}))
+  {
+    return;
+  }
+  elsif (!defined ($types{'text/html'}))
+  {
+    return (1);
+  }
+  elsif ($types{'application/xhtml+xml'} < $types{'text/html'})
+  {
+    return;
+  }
+  else
+  {
+    return (1);
+  }
+} # can_handle_xhtml
+
+my $html_started;
+sub start_html
+{
+  return if ($html_started);
+
+  my $end;
+  my $begin;
+  my $timespan;
+
+  $end = time ();
+  $timespan = get_timespan_selection ();
+  $begin = $end - $timespan;
+
+  if (can_handle_xhtml ())
+  {
+    print header (-Content_Type => 'application/xhtml+xml; charset=UTF-8');
+    print <<HTML;
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.w3.org/MarkUp/SCHEMA/xhtml11.xsd"
+    xml:lang="en">
+HTML
+  }
+  else
+  {
+    print header (-Content_Type => 'text/html; charset=UTF-8');
+    print <<HTML;
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+    "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+HTML
+  }
+  print <<HTML;
+  <head>
+    <title>collection.cgi, Version 3</title>
+    <link rel="icon" href="../share/shortcut-icon.png" type="image/png" />
+    <link rel="stylesheet" href="../share/style.css" type="text/css" />
+    <script type="text/javascript" src="../share/navigate.js"></script>
+  </head>
+  <body onload="nav_init ($begin, $end);">
+HTML
+  $html_started = 1;
+}
+
+sub end_html
+{
+  print <<HTML;
+  </body>
+</html>
+HTML
+  $html_started = 0;
+}
+
+sub show_selector
+{
+  my $timespan_selection = get_timespan_selection ();
+  my $host_selection = get_host_selection ();
+  my $plugin_selection = get_plugin_selection ();
+
+  print <<HTML;
+    <form action="${\script_name ()}" method="get">
+      <fieldset>
+        <legend>Data selection</legend>
+        <select name="hostname" multiple="multiple" size="15">
+HTML
+  for (sort (keys %$host_selection))
+  {
+    my $host = encode_entities ($_);
+    my $selected = $host_selection->{$_}
+    ? ' selected="selected"'
+    : '';
+    print qq#          <option value="$host"$selected>$host</option>\n#;
+  }
+  print <<HTML;
+        </select>
+       <select name="plugin" multiple="multiple" size="15">
+HTML
+  for (sort (keys %$plugin_selection))
+  {
+    my $plugin = encode_entities ($_);
+    my $selected = $plugin_selection->{$_}
+    ? ' selected="selected"'
+    : '';
+    print qq#          <option value="$plugin"$selected>$plugin</option>\n#;
+  }
+  print <<HTML;
+       </select>
+       <select name="timespan">
+HTML
+  for (sort { $TimeSpans->{$a} <=> $TimeSpans->{$b} } (keys (%$TimeSpans)))
+  {
+    my $name = encode_entities ($_);
+    my $value = $TimeSpans->{$_};
+    my $selected = ($value == $timespan_selection)
+    ? ' selected="selected"'
+    : '';
+    print qq#          <option value="$value"$selected>$name</option>\n#;
+  }
+  print <<HTML;
+        </select>
+       <input type="hidden" name="action" value="show_selection" />
+       <input type="submit" name="ok_button" value="OK" />
+      </fieldset>
+    </form>
+HTML
+} # show_selector
+
+sub action_list_hosts
+{
+  start_html ();
+  show_selector ();
+
+  my @hosts = get_all_hosts ();
+  print "    <ul>\n";
+  for (sort @hosts)
+  {
+    my $url = encode_entities (script_name () . "?action=show_selection;hostname=$_");
+    my $name = encode_entities ($_);
+    print qq#      <li><a href="$url">$name</a></li>\n#;
+  }
+  print "    </ul>\n";
+
+  end_html ();
+} # action_list_hosts
+
+=head1 MODULE LOADING
+
+This script makes use of the various B<Collectd::Graph::Type::*> modules. If a
+file like C<foo.rrd> is encountered it tries to load the
+B<Collectd::Graph::Type::Foo> module and, if that fails, falls back to the
+B<Collectd::Graph::Type> base class.
+
+If you want to create a specialized graph for a certain type, you have to
+create a new module which inherits from the B<Collectd::Graph::Type> base
+class. A description of provided (and used) methods can be found in the inline
+documentation of the B<Collectd::Graph::Type> module.
+
+There are other, more specialized, "abstract" classes that possibly better fit
+your need. Unfortunately they are not yet documented.
+
+=over 4
+
+=item B<Collectd::Graph::Type::GenericStacked>
+
+Specialized class that groups files by their plugin instance and stacks them on
+top of each other. Example types that inherit from this class are
+B<Collectd::Graph::Type::Cpu> and B<Collectd::Graph::Type::Memory>.
+
+=item B<Collectd::Graph::Type::GenericIO>
+
+Specialized class for input/output graphs. This class can only handle files
+with exactly two data sources, input and output. Example types that inherit
+from this class are B<Collectd::Graph::Type::DiskOctets> and
+B<Collectd::Graph::Type::IfOctets>.
+
+=back
+
+=cut
+
+sub action_show_selection
+{
+  start_html ();
+  show_selector ();
+
+  my $all_files;
+  my $timespan;
+
+  my $types = {};
+
+  my $id_counter = 0;
+
+  $all_files = get_selected_files ();
+  $timespan = get_timespan_selection ();
+
+  if (param ('debug'))
+  {
+    print "<pre>", Data::Dumper->Dump ([$all_files], ['all_files']), "</pre>\n";
+  }
+
+  # Send FLUSH command to the daemon if necessary and possible.
+  flush_files ($all_files,
+      begin => time () - $timespan,
+      end => time (),
+      addr => gc_get_scalar ('UnixSockAddr', undef),
+      interval => gc_get_scalar ('Interval', 10));
+
+  for (@$all_files)
+  {
+    my $file = $_;
+    my $type = ucfirst (lc ($file->{'type'}));
+
+    $type =~ s/[^A-Za-z0-9_]//g;
+    $type =~ s/_([A-Za-z0-9])/\U$1\E/g;
+
+    if (!defined ($types->{$type}))
+    {
+      $types->{$type} = tl_load_type ($file->{'type'});
+      if (!$types->{$type})
+      {
+        warn ("tl_load_type (" . $file->{'type'} . ") failed");
+        next;
+      }
+    }
+
+    $types->{$type}->addFiles ($file);
+  }
+#print STDOUT Data::Dumper->Dump ([$types], ['types']);
+
+  print qq#    <table>\n#;
+  for (sort (keys %$types))
+  {
+    my $type = $_;
+
+    if (!defined ($types->{$type}))
+    {
+      next;
+    }
+
+    my $graphs_num = $types->{$type}->getGraphsNum ();
+
+    for (my $i = 0; $i < $graphs_num; $i++)
+    {
+      my $args = $types->{$type}->getGraphArgs ($i);
+      my $url = encode_entities ("graph.cgi?$args;begin=-$timespan");
+      my $id = sprintf ("graph%04i", $id_counter++);
+
+      print "      <tr>\n";
+      print "        <td rowspan=\"$graphs_num\">$type</td>\n" if ($i == 0);
+      print <<EOF;
+        <td>
+          <div class="graph_canvas">
+            <div class="graph_float">
+              <img id="${id}" class="graph_image"
+                alt="A graph"
+                src="$url" />
+              <div class="controls zoom">
+                <div title="Earlier"
+                  onclick="nav_move_earlier ('${id}');">&#x2190;</div>
+                <div title="Zoom out"
+                  onclick="nav_zoom_out ('${id}');">-</div>
+                <div title="Zoom in"
+                  onclick="nav_zoom_in ('${id}');">+</div>
+                <div title="Later"
+                  onclick="nav_move_later ('${id}');">&#x2192;</div>
+              </div>
+              <div class="controls preset">
+                <div title="Show current hour"
+                  onclick="nav_time_reset ('${id}', 3600);">H</div>
+                <div title="Show current day"
+                  onclick="nav_time_reset ('${id}', 86400);">D</div>
+                <div title="Show current week"
+                  onclick="nav_time_reset ('${id}', 7 * 86400);">W</div>
+                <div title="Show current month"
+                  onclick="nav_time_reset ('${id}', 31 * 86400);">M</div>
+                <div title="Show current year"
+                  onclick="nav_time_reset ('${id}', 366 * 86400);">Y</div>
+                <div title="Set all images to this timespan"
+                  onclick="nav_set_reference ('${id}');">!</div>
+              </div>
+            </div>
+          </div>
+       </td>
+EOF
+      # print qq#        <td><img src="$url" /></td>\n#;
+      print "      </tr>\n";
+    }
+  }
+
+  print "    </table>\n";
+  end_html ();
+}
+
+main ();
+
+=head1 SEE ALSO
+
+L<Collectd::Graph::Type>
+
+=head1 AUTHOR AND LICENSE
+
+Copyright (c) 2008 by Florian Forster
+E<lt>octoE<nbsp>atE<nbsp>verplant.orgE<gt>. Licensed under the terms of the GNU
+General Public License, VersionE<nbsp>2 (GPLv2).
+
+=cut
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
diff --git a/contrib/collection3/bin/json.cgi b/contrib/collection3/bin/json.cgi
new file mode 100755 (executable)
index 0000000..99e2703
--- /dev/null
@@ -0,0 +1,108 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2008  Florian octo Forster <octo at verplant.org>
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; only version 2 of the License is applicable.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+use strict;
+use warnings;
+use lib ('../lib');
+use utf8;
+
+use FindBin ('$RealBin');
+use CGI (':cgi');
+use CGI::Carp ('fatalsToBrowser');
+use URI::Escape ('uri_escape');
+use JSON ('objToJson');
+
+use Data::Dumper;
+
+use Collectd::Graph::Config (qw(gc_read_config));
+use Collectd::Graph::TypeLoader (qw(tl_load_type));
+use Collectd::Graph::Common (qw(get_all_hosts get_files_for_host type_to_module_name));
+use Collectd::Graph::Type ();
+
+our $Debug = param ('debug') ? 1 : 0;
+our $ServerName = 'collect.noris.net';
+
+gc_read_config ("$RealBin/../etc/collection.conf");
+
+if ($Debug)
+{
+  print "Content-Type: text/plain; charset=utf-8\n\n";
+}
+else
+{
+  print "Content-Type: application/json; charset=utf-8\n\n";
+}
+
+my $obj = {};
+my @hosts = get_all_hosts ();
+for (my $i = 0; $i < @hosts; $i++)
+{
+  my $host_obj = {};
+  my $host = $hosts[$i];
+  my $files = get_files_for_host ($host);
+  my %graphs = ();
+  my @graphs = ();
+
+  # Group files by graphs
+  for (@$files)
+  {
+    my $file = $_;
+    my $type = $file->{'type'};
+
+    # Create a new graph object if this is the first of this type.
+    if (!defined ($graphs{$type}))
+    {
+      $graphs{$type} = tl_load_type ($file->{'type'});
+      if (!$graphs{$type})
+      {
+        cluck ("tl_load_type (" . $file->{'type'} . ") failed");
+        next;
+      }
+    }
+
+    $graphs{$type}->addFiles ($file);
+  } # for (@$files)
+
+  #print qq(  ") . objToJson ({ foo => 123 }) . qq(":\n  {\n);
+
+  @graphs = keys %graphs;
+  for (my $j = 0; $j < @graphs; $j++)
+  {
+    my $type = $graphs[$j];
+    my $graphs_num = $graphs{$type}->getGraphsNum ();
+
+    if (!defined ($host_obj->{$type}))
+    {
+      $host_obj->{$type} = [];
+    }
+
+    for (my $k = 0; $k < $graphs_num; $k++)
+    {
+      my $args = $graphs{$type}->getGraphArgs ($k);
+      my $url = "http://$ServerName/cgi-bin/collection3/bin/graph.cgi?" . $args;
+      push (@{$host_obj->{$type}}, $url);
+    }
+  } # for (keys %graphs)
+
+  $obj->{$host} = $host_obj;
+} # for (my $i = 0; $i < @hosts; $i++)
+
+print STDOUT objToJson ($obj, { pretty => 1, indent => 2 });
+
+exit (0);
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
diff --git a/contrib/collection3/etc/.htaccess b/contrib/collection3/etc/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/contrib/collection3/etc/collection.conf b/contrib/collection3/etc/collection.conf
new file mode 100644 (file)
index 0000000..3bb3d8b
--- /dev/null
@@ -0,0 +1,726 @@
+#DataDir "/var/lib/collectd/rrd"
+GraphWidth 400
+#UnixSockAddr "/var/run/collectd-unixsock"
+<Type apache_bytes>
+  DataSources count
+  DSName "count Bytes/s"
+  RRDTitle "Apache Traffic"
+  RRDVerticalLabel "Bytes/s"
+  RRDFormat "%5.1lf%s"
+  Color count 0000ff
+</Type>
+<Type apache_requests>
+  DataSources count
+  DSName "count Requests/s"
+  RRDTitle "Apache Traffic"
+  RRDVerticalLabel "Requests/s"
+  RRDFormat "%5.2lf"
+  Color count 00d000
+</Type>
+<Type apache_scoreboard>
+  Module GenericStacked
+  DataSources count
+  RRDTitle "Apache scoreboard on {hostname}"
+  RRDVerticalLabel "Slots"
+  RRDFormat "%6.2lf"
+  DSName closing Closing
+  DSName dnslookup DNS lookup
+  DSName finishing Finishing
+  DSName idle_cleanup Idle cleanup
+  DSName keepalive Keep alive
+  DSName logging Logging
+  DSName open Open (empty)
+  DSName reading Reading
+  DSName sending Sending
+  DSName starting Starting
+  DSName waiting Waiting
+  Order open closing dnslookup finishing idle_cleanup keepalive logging open reading sending starting waiting
+  Color closing      000080
+  Color dnslookup    ff0000
+  Color finishing    008080
+  Color idle_cleanup ffff00
+  Color keepalive    0080ff
+  Color logging      a000a0
+  Color open         e0e0e0
+  Color reading      0000ff
+  Color sending      00e000
+  Color starting     ff00ff
+  Color waiting      ffb000
+</Type>
+<Type arc_counts>
+  Module ArcCounts
+  RRDTitle "ARC {type_instance} on {hostname}"
+# RRDOptions ...
+</Type>
+<Type arc_l2_bytes>
+  Module GenericIO
+  DataSources read write
+  DSName  "read Read   "
+  DSName "write Written"
+  RRDTitle "L2ARC traffic"
+  RRDVerticalLabel "Bytes per second"
+# RRDOptions ...
+  RRDFormat "%5.1lf%s"
+</Type>
+<Type arc_l2_size>
+  RRDTitle "L2ARC size on {hostname}"
+  RRDVerticalLabel "Size"
+  RRDFormat "%4.0lf%s"
+  RRDOptions -b 1024
+  DSName "value Current size"
+  Color value  00e000
+</Type>
+<Type arc_size>
+  DataSources "current target minlimit maxlimit"
+  RRDTitle "ARC size on {hostname}"
+  RRDVerticalLabel "Size"
+  RRDFormat "%4.0lf%s"
+  RRDOptions -b 1024
+  DSName  "current Current size"
+  DSName   "target Target size "
+  DSName "maxlimit Max size    "
+  DSName "minlimit Min size    "
+  Color current  00e000
+  Color target   0000ff
+  Color minlimit ff0000
+  Color maxlimit ff00ff
+</Type>
+<Type arc_ratio>
+  DataSources value
+  RRDTitle "{type_instance}ARC ratio on {hostname}"
+  RRDVerticalLabel "Ratio"
+  RRDFormat "%4.1lf"
+  RRDOptions -l 0
+  DSName "value Hit ratio"
+</Type>
+<Type bitrate>
+  DataSources value
+  RRDTitle "Bitrate ({instance})"
+  RRDVerticalLabel "Bit/s"
+  RRDFormat "%5.1lf%s"
+  DSName "value Bitrate"
+</Type>
+<Type cache_ratio>
+  DataSources value
+  DSName value Percent
+  RRDTitle "Cache hit ratio for {plugin_instance} {type_instance}"
+  RRDVerticalLabel "Percent"
+  RRDFormat "%5.1lf %%"
+</Type>
+<Type cpu>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "CPU {plugin_instance} usage"
+  RRDVerticalLabel "Jiffies"
+  RRDFormat "%5.2lf"
+  DSName idle Idle
+  DSName nice Nice
+  DSName user User
+  DSName wait Wait-IO
+  DSName system System
+  DSName softirq SoftIRQ
+  DSName interrupt IRQ
+  DSName steal Steal
+  Order idle nice user wait system softirq interrupt steal
+  Color idle      e8e8e8
+  Color nice      00e000
+  Color user      0000ff
+  Color wait      ffb000
+  Color system    ff0000
+  Color softirq   ff00ff
+  Color interrupt a000a0
+  Color steal     000000
+</Type>
+<Type current>
+  DataSources value
+  DSName value Current
+  RRDTitle "Current ({type_instance})"
+  RRDVerticalLabel "Ampere"
+  RRDFormat "%4.1lfA"
+  Color value ffb000
+</Type>
+<Type df>
+  Module Df
+  DataSources free used
+</Type>
+<Type df_complex>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "Disk/Volume usage on {plugin_instance}"
+  RRDVerticalLabel "Byte"
+  RRDFormat "%5.1lf%s"
+  DSName "sis_saved         SIS saved         "
+  DSName "reserved          Reserved          "
+  DSName "free              Free              "
+  DSName "used              Used              "
+  DSName "snap_normal_used  Snap used (normal)"
+  DSName "snap_reserved     Snap reserved     "
+  DSName "snap_reserve_used Snap used (resv)  "
+  Order sis_saved reserved free used snap_normal_used snap_reserved snap_reserve_used
+  Color sis_saved 00e0e0
+  Color reserved ffb000
+  Color free  00ff00
+  Color snap_reverse   ff8000
+  Color used  ff0000
+  Color snap_normal_used  c10640
+  Color snap_reserved     f15aef
+  Color snap_reserve_used 820c81
+</Type>
+<Type disk_latency>
+  Module GenericIO
+  DataSources read write
+  DSName "read Read "
+  DSName write Write
+  RRDTitle "Disk Latency for {plugin_instance}"
+  RRDVerticalLabel "seconds"
+  Scale 0.000001
+  RRDFormat "%5.1lf %ss"
+</Type>
+<Type disk_octets>
+  Module GenericIO
+  DataSources read write
+  DSName "read Read   "
+  DSName write Written
+  RRDTitle "Disk Traffic ({instance})"
+  RRDVerticalLabel "Bytes per second"
+# RRDOptions ...
+  RRDFormat "%5.1lf%s"
+</Type>
+<Type disk_ops>
+  Module GenericIO
+  DataSources read write
+  DSName "read Read   "
+  DSName write Written
+  RRDTitle "Disk Operations ({instance})"
+  RRDVerticalLabel "Operations per second"
+# RRDOptions ...
+  RRDFormat "%5.1lf"
+</Type>
+<Type disk_ops_complex>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "Netapp disc ops on {plugin_instance}"
+  RRDVerticalLabel "Ops"
+  RRDFormat "%6.2lf"
+  DSName fcp_ops   FCP-Ops
+  DSName nfs_ops   NFS-Ops
+  DSName http_ops  HTTP-Ops
+  DSName cifs_ops  CIFS-Ops
+  DSName dafs_ops  DAFS-Ops
+  DSName iscsi_ops iSCSI-Ops
+  Order fcp_ops nfs_ops http_ops cifs_ops dafs_ops iscsi_ops
+  Color fcp_ops    000080
+  Color nfs_ops    ff0000
+  Color http_ops   ffb000
+  Color cifs_ops   00e0a0
+  Color dafs_ops   00e000
+  Color iscsi_ops  00e0ff
+</Type>
+<Type disk_merged>
+  Module GenericIO
+  DataSources read write
+  DSName "read Read   "
+  DSName write Written
+  RRDTitle "Disk Merged Operations ({instance})"
+  RRDVerticalLabel "Merged operations/s"
+# RRDOptions ...
+  RRDFormat "%5.1lf"
+</Type>
+<Type disk_time>
+  Module GenericIO
+  DataSources read write
+  DSName "read Read   "
+  DSName write Written
+  RRDTitle "Disk time per operation ({instance})"
+  RRDVerticalLabel "Avg. Time/Op"
+# RRDOptions ...
+  RRDFormat "%5.1lf%ss"
+  Scale 0.001
+</Type>
+<Type dns_opcode>
+  DataSources value
+  DSName "value Queries/s"
+  RRDTitle "DNS Opcode {type_instance}"
+  RRDVerticalLabel "Queries/s"
+  RRDFormat "%6.1lf"
+</Type>
+<Type conntrack>
+  DataSources conntrack
+  DSName conntrack Conntrack count
+  RRDTitle "nf_conntrack connections on {hostname}"
+  RRDVerticalLabel "Count"
+  RRDFormat "%4.0lf"
+</Type>
+<Type entropy>
+  DataSources entropy
+  DSName entropy Entropy bits
+  RRDTitle "Available entropy on {hostname}"
+  RRDVerticalLabel "Bits"
+  RRDFormat "%4.0lf"
+</Type>
+<Type fanspeed>
+  DataSources value
+  DSName value RPM
+  RRDTitle "Fanspeed ({instance})"
+  RRDVerticalLabel "RPM"
+  RRDFormat "%6.1lf"
+  Color value 00b000
+</Type>
+<Type frequency>
+  DataSources frequency
+  DSName frequency Frequency
+  RRDTitle "Frequency ({type_instance})"
+  RRDVerticalLabel "Hertz"
+  RRDFormat "%4.1lfHz"
+  Color frequency a000a0
+</Type>
+<Type humidity>
+  DataSources value
+  DSName value Humitidy
+  RRDTitle "Humitidy ({instance})"
+  RRDVerticalLabel "Percent"
+  RRDFormat "%4.1lf%%"
+  Color value 00e000
+</Type>
+<Type if_errors>
+  Module GenericIO
+  DataSources rx tx
+  DSName rx RX
+  DSName tx TX
+  RRDTitle "Interface Errors ({type_instance})"
+  RRDVerticalLabel "Errors per second"
+# RRDOptions ...
+  RRDFormat "%.3lf"
+</Type>
+<Type if_rx_errors>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "Interface receive errors ({plugin_instance})"
+  RRDVerticalLabel "Erorrs/s"
+  RRDFormat "%.1lf"
+  Color length  f00000
+  Color over    00e0ff
+  Color crc     00e000
+  Color frame   ffb000
+  Color fifo    f000c0
+  Color missed  0000f0
+</Type>
+<Type if_tx_errors>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "Interface transmit errors ({plugin_instance})"
+  RRDVerticalLabel "Erorrs/s"
+  RRDFormat "%.1lf"
+  Color aborted   f00000
+  Color carrier   00e0ff
+  Color fifo      00e000
+  Color heartbeat ffb000
+  Color window    f000c0
+</Type>
+<Type if_octets>
+  Module GenericIO
+  DataSources rx tx
+  DSName rx RX
+  DSName tx TX
+  RRDTitle "Interface Traffic ({instance})"
+  RRDVerticalLabel "Bits per second"
+# RRDOptions ...
+  RRDFormat "%5.1lf%s"
+  Scale 8
+</Type>
+<Type if_packets>
+  Module GenericIO
+  DataSources rx tx
+  DSName rx RX
+  DSName tx TX
+  RRDTitle "Interface Packets ({type_instance})"
+  RRDVerticalLabel "Packets per second"
+# RRDOptions ...
+  RRDFormat "%5.1lf%s"
+</Type>
+<Type invocations>
+  DataSources value
+  DSName "value Invocations/s"
+  RRDTitle "Invocations ({instance})"
+  RRDVerticalLabel "Invocations/s"
+  RRDFormat "%5.1lf"
+</Type>
+<Type io_octets>
+  Module GenericIO
+  DataSources rx tx
+  DSName "rx Read   "
+  DSName "tx Written"
+  RRDTitle "IO Traffic ({instance})"
+  RRDVerticalLabel "Bytes per second"
+# RRDOptions ...
+  RRDFormat "%5.1lf%s"
+</Type>
+<Type ipt_bytes>
+  DataSources value
+  DSName value Bytes/s
+  RRDTitle "Traffic ({type_instance})"
+  RRDVerticalLabel "Bytes per second"
+# RRDOptions ...
+  RRDFormat "%5.1lf%s"
+</Type>
+<Type ipt_packets>
+  DataSources value
+  DSName value Packets/s
+  RRDTitle "Packets ({type_instance})"
+  RRDVerticalLabel "Packets per second"
+# RRDOptions ...
+  RRDFormat "%5.1lf"
+</Type>
+<Type irq>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "Interrupts on {hostname}"
+  RRDVerticalLabel "IRQs/s"
+  RRDFormat "%5.1lf"
+</Type>
+<Type load>
+  Module Load
+</Type>
+<Type java_memory>
+  Module JavaMemory
+  DataSources value
+</Type>
+<Type memory>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "Physical memory utilization on {hostname}"
+  RRDVerticalLabel "Bytes"
+  RRDFormat "%5.1lf%s"
+  RRDOptions -b 1024 -l 0
+  DSName     "free Free    "
+  DSName   "cached Cached  "
+  DSName "buffered Buffered"
+  DSName   "locked Locked  "
+  DSName     "used Used    "
+  DSName     "available Available    "
+  DSName  "system_cache System Cache "
+  DSName    "pool_paged Paged Pool   "
+  DSName "pool_nonpaged Nonpaged Pool"
+  DSName   "working_set Working Set  "
+  DSName   "system_code System Code  "
+  DSName "system_driver System Driver"
+  #Order used buffered cached free
+  Order free cached buffered used available system_cache system_driver system_code pool_paged pool_nonpaged working_set
+  Color free      00e000
+  Color cached    0000ff
+  Color buffered  ffb000
+  Color locked    ff00ff
+  Color used      ff0000
+  Color available      00e000
+  Color system_cache   0000ff
+  Color system_driver  ff00ff
+  Color system_code    a000a0
+  Color pool_paged     ffb000
+  Color pool_nonpaged  ff8000
+  Color working_set    ff0000
+</Type>
+<Type mysql_commands>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "MySQL commands ({plugin_instance})"
+  RRDVerticalLabel "Invocations"
+  RRDFormat "%6.2lf"
+
+
+  DSName admin_commands admin_commands
+  DSName alter_table alter_table
+  DSName begin begin
+  DSName change_db change_db
+  DSName check check
+  DSName commit commit
+  DSName create_db create_db
+  DSName create_table create_table
+  DSName delete delete
+  DSName drop_db drop_db
+  DSName drop_table drop_table
+  DSName flush flush
+  DSName grant grant
+  DSName insert insert
+  DSName insert_select insert_select
+  DSName lock_tables lock_tables
+  DSName optimize optimize
+  DSName rename_table rename_table
+  DSName replace replace
+  DSName revoke revoke
+  DSName select select
+  DSName set_option set_option
+  DSName show_create_table show_create_table
+  DSName show_databases show_databases
+  DSName show_fields show_fields
+  DSName show_keys show_keys
+  DSName show_master_status show_master_status
+  DSName show_processlist show_processlist
+  DSName show_slave_hosts show_slave_hosts
+  DSName show_status show_status
+  DSName show_tables show_tables
+  DSName show_triggers show_triggers
+  DSName show_variables show_variables
+  DSName unlock_tables unlock_tables
+  DSName update update
+  DSName update_multi update_multi
+
+  Order admin_commands alter_table begin change_db check commit create_db create_table delete drop_db drop_table flush grant insert insert_select lock_tables optimize rename_table replace revoke select set_option show_create_table show_databases show_fields show_keys show_master_status show_processlist show_slave_hosts show_status show_tables show_triggers show_variables unlock_tables update update_multi
+
+  Color admin_commands ff0000
+  Color alter_table ff002a
+  Color begin ff0055
+  Color change_db ff007f
+  Color check ff00aa
+  Color commit ff00d4
+  Color create_db ff00ff
+  Color create_table d400ff
+  Color delete aa00ff
+  Color drop_db 7f00ff
+  Color drop_table 5400ff
+  Color flush 2a00ff
+  Color grant 0000ff
+  Color insert 002aff
+  Color insert_select 0055ff
+  Color lock_tables 007fff
+  Color optimize 00a9ff
+  Color rename_table 00d4ff
+  Color replace 00ffff
+  Color revoke 00ffd4
+  Color select 00ffa9
+  Color set_option 00ff7f
+  Color show_create_table 00ff55
+  Color show_databases 00ff2a
+  Color show_fields 00ff00
+  Color show_keys 2aff00
+  Color show_master_status 54ff00
+  Color show_processlist 7fff00
+  Color show_slave_hosts aaff00
+  Color show_status d4ff00
+  Color show_tables ffff00
+  Color show_triggers ffd400
+  Color show_variables ffaa00
+  Color unlock_tables ff7f00
+  Color update ff5400
+  Color update_multi ff2a00
+</Type>
+<Type mysql_handler>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "MySQL handler ({plugin_instance})"
+  RRDVerticalLabel "Invocations"
+  RRDFormat "%6.2lf"
+  DSName commit commit
+  DSName delete delete
+  DSName read_first read_first
+  DSName read_key read_key
+  DSName read_next read_next
+  DSName read_prev read_prev
+  DSName read_rnd read_rnd
+  DSName read_rnd_next read_rnd_next
+  DSName update update
+  DSName write write
+  Order commit delete read_first read_key read_next read_prev read_rnd read_rnd_next update write
+  Color commit ff0000
+  Color delete ff0099
+  Color read_first cc00ff
+  Color read_key 3200ff
+  Color read_next 0065ff
+  Color read_prev 00ffff
+  Color read_rnd 00ff65
+  Color read_rnd_next 33ff00
+  Color update cbff00
+  Color write ff9800
+</Type>
+<Type mysql_octets>
+  Module GenericIO
+  DataSources rx tx
+  DSName rx RX
+  DSName tx TX
+  RRDTitle "MySQL Traffic ({plugin_instance})"
+  RRDVerticalLabel "Bits per second"
+  RRDFormat "%5.1lf%s"
+  Scale 8
+</Type>
+<Type percent>
+  DataSources percent
+  DSName percent Percent
+  RRDTitle "Percent ({type_instance})"
+  RRDVerticalLabel "Percent"
+  RRDFormat "%4.1lf%%"
+  Color percent 0000ff
+</Type>
+<Type ping>
+  DataSources ping
+  DSName "ping Latency"
+  RRDTitle "Network latency ({type_instance})"
+  RRDVerticalLabel "Milliseconds"
+  RRDFormat "%5.2lfms"
+</Type>
+<Type power>
+  DataSources value
+  DSName value Watts
+  RRDTitle "Power ({type_instance})"
+  RRDVerticalLabel "Watts"
+  RRDFormat "%6.2lf%sW"
+  Color value 008080
+</Type>
+<Type ps_cputime>
+  Module PsCputime
+</Type>
+<Type ps_disk_octets>
+  Module GenericIO
+  DataSources read write
+  DSName "read Read   "
+  DSName write Written
+  RRDTitle "Process disk traffic ({instance})"
+  RRDVerticalLabel "Bytes per second"
+# RRDOptions ...
+  RRDFormat "%5.1lf%s"
+</Type>
+<Type ps_rss>
+  DataSources value
+  DSName value RSS
+  RRDTitle "Resident Segment Size ({instance})"
+  RRDVerticalLabel "Bytes"
+  RRDFormat "%6.2lf%s"
+</Type>
+<Type ps_state>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "Processes on {hostname}"
+  RRDVerticalLabel "Processes"
+  RRDFormat "%5.1lf"
+  DSName running  Running
+  DSName sleeping Sleeping
+  DSName paging   Paging
+  DSName zombies  Zombies
+  DSName blocked  Blocked
+  DSName stopped  Stopped
+  Order paging blocked zombies stopped running sleeping
+  Color running  00e000
+  Color sleeping 0000ff
+  Color paging   ffb000
+  Color zombies  ff0000
+  Color blocked  ff00ff
+  Color stopped  a000a0
+</Type>
+<Type signal_power>
+  DataSources value
+  RRDTitle "Signal power ({instance})"
+  RRDVerticalLabel "dB"
+  RRDFormat "%5.1lf"
+  DSName "value Signal power"
+</Type>
+<Type signal_quality>
+  DataSources value
+  RRDTitle "Signal quality ({instance})"
+  RRDVerticalLabel "Percent"
+  RRDFormat "%5.1lf%%"
+  DSName "value Signal quality"
+</Type>
+<Type snr>
+  DataSources value
+  RRDTitle "Signal / noise ratio ({instance})"
+  RRDVerticalLabel "dBm"
+  RRDFormat "%5.1lf"
+  DSName "value S/N"
+</Type>
+<Type swap>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "Swap utilization on {hostname}"
+  RRDVerticalLabel "Bytes"
+  RRDFormat "%5.1lf%s"
+  RRDOptions -b 1024 -l 0
+  DSName     "free Free    "
+  DSName   "cached Cached  "
+  DSName     "used Used    "
+  #Order used cached free
+  Order free cached used
+  Color free      00e000
+  Color cached    0000ff
+  Color used      ff0000
+</Type>
+<Type table_size>
+  Module TableSize
+  DataSources value
+  DSName value Bytes
+  RRDTitle "Table size ({instance})"
+  RRDVerticalLabel "Size [Bytes]"
+# RRDOptions ...
+  RRDFormat "%5.1lf%s"
+</Type>
+<Type tcp_connections>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "TCP connections ({plugin_instance})"
+  RRDVerticalLabel "Connections"
+  RRDFormat "%5.1lf"
+  Order LISTEN CLOSING LAST_ACK CLOSE_WAIT CLOSE TIME_WAIT FIN_WAIT2 FIN_WAIT1 SYN_RECV SYN_SENT ESTABLISHED CLOSED
+  Color ESTABLISHED 00e000
+  Color SYN_SENT   00e0ff
+  Color SYN_RECV   00e0a0
+  Color FIN_WAIT1  f000f0
+  Color FIN_WAIT2  f000a0
+  Color TIME_WAIT  ffb000
+  Color CLOSE      0000f0
+  Color CLOSE_WAIT 0000a0
+  Color LAST_ACK   000080
+  Color LISTEN     ff0000
+  Color CLOSING    000000
+  Color CLOSED     0000f0
+</Type>
+<Type temperature>
+  DataSources value
+  DSName value Temp
+  RRDTitle "Temperature ({instance})"
+  RRDVerticalLabel "°Celsius"
+  RRDFormat "%4.1lf°C"
+</Type>
+<Type threads>
+  DataSources value
+  DSName "value Threads"
+  RRDTitle "Threads ({instance})"
+  RRDVerticalLabel "Threads"
+  RRDFormat "%5.2lf"
+</Type>
+<Type total_requests>
+  DataSources value
+  DSName "value Requests/s"
+  RRDTitle "Requests ({instance})"
+  RRDVerticalLabel "Requests/s"
+  RRDFormat "%6.2lf"
+</Type>
+<Type total_time_in_ms>
+  DataSources value
+  DSName "value Time"
+  RRDTitle "Time {instance}"
+  RRDVerticalLabel "Seconds"
+  RRDFormat "%6.2lf %ss"
+  Scale 0.001
+</Type>
+<Type users>
+  DataSources users
+  DSName users Users
+  RRDTitle "Users ({type_instance}) on {hostname}"
+  RRDVerticalLabel "Users"
+  RRDFormat "%.1lf"
+  Color users 0000f0
+</Type>
+<Type voltage>
+  DataSources value
+  DSName value Volts
+  RRDTitle "Voltage ({type_instance})"
+  RRDVerticalLabel "Volts"
+  RRDFormat "%4.1lfV"
+  Color value f00000
+</Type>
+<Type wirkleistung>
+  Module Wirkleistung
+  DataSources kWh
+  DSName value Wh
+  RRDTitle "Watt"
+  RRDVerticalLabel "W"
+  RRDFormat "%4.1lfW"
+</Type>
+# vim: set sw=2 sts=2 et syntax=apache fileencoding=utf-8 :
diff --git a/contrib/collection3/lib/.htaccess b/contrib/collection3/lib/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/contrib/collection3/lib/Collectd/Config.pm b/contrib/collection3/lib/Collectd/Config.pm
new file mode 100644 (file)
index 0000000..d20be35
--- /dev/null
@@ -0,0 +1,144 @@
+package Collectd::Graph::Config;
+
+=head1 NAME
+
+Collectd::Graph::Config - Parse the collection3 config file.
+
+=cut
+
+# Copyright (C) 2008  Florian octo Forster <octo at verplant.org>
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; only version 2 of the License is applicable.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+use strict;
+use warnings;
+
+use Carp (qw(cluck confess));
+use Exporter ();
+use Config::General ('ParseConfig');
+use Collectd::Graph::Type ();
+
+@Collectd::Graph::Config::ISA = ('Exporter');
+@Collectd::Graph::Config::EXPORT_OK = (qw(gc_read_config gc_get_config
+  gc_get_scalar));
+
+our $Configuration = undef;
+
+return (1);
+
+=head1 EXPORTED FUNCTIONS
+
+=over 4
+
+=item B<gc_read_config> (I<$file>)
+
+Reads the configuration from the file located at I<$file>. Returns B<true> when
+successfull and B<false> otherwise.
+
+=cut
+
+sub gc_read_config
+{
+  my $file = shift;
+  my %conf;
+
+  if ($Configuration)
+  {
+    return (1);
+  }
+
+  $file ||= "etc/collection.conf";
+
+  %conf = ParseConfig (-ConfigFile => $file,
+    -LowerCaseNames => 1,
+    -UseApacheInclude => 1,
+    -IncludeDirectories => 1,
+    ($Config::General::VERSION >= 2.38) ? (-IncludeAgain => 0) : (),
+    -MergeDuplicateBlocks => 1,
+    -CComments => 0);
+  if (!%conf)
+  {
+    return;
+  }
+
+  $Configuration = \%conf;
+  return (1);
+} # gc_read_config
+
+=item B<gc_get_config> ()
+
+Returns the hash as provided by L<Config::General>. The hash is returned as a
+hash reference. Don't change it!
+
+=cut
+
+sub gc_get_config
+{
+  return ($Configuration);
+} # gc_get_config
+
+=item B<gc_get_config> (I<$key>, [I<$default>])
+
+Returns the scalar value I<$key> from the config file. If the key does not
+exist, I<$default> will be returned. If no default is given, B<undef> will be
+used in this case.
+
+=cut
+
+sub gc_get_scalar
+{
+  my $key = shift;
+  my $default = (@_ != 0) ? shift : undef;
+  my $value;
+
+  if (!$Configuration)
+  {
+    return ($default);
+  }
+
+  $value = $Configuration->{lc ($key)};
+  if (!defined ($value))
+  {
+    return ($default);
+  }
+
+  if (ref ($value) ne '')
+  {
+    cluck ("Value for `$key' should be scalar, but actually is "
+      . ref ($value));
+    return ($default);
+  }
+
+  return ($value);
+} # gc_get_config
+
+=back
+
+=head1 DEPENDS ON
+
+L<Config::General>
+
+=head1 SEE ALSO
+
+L<Collectd::Graph::Type>
+
+=head1 AUTHOR AND LICENSE
+
+Copyright (c) 2008 by Florian Forster
+E<lt>octoE<nbsp>atE<nbsp>verplant.orgE<gt>. Licensed under the terms of the GNU
+General Public License, VersionE<nbsp>2 (GPLv2).
+
+=cut
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 et fdm=marker :
diff --git a/contrib/collection3/lib/Collectd/Graph/Common.pm b/contrib/collection3/lib/Collectd/Graph/Common.pm
new file mode 100644 (file)
index 0000000..cc7e141
--- /dev/null
@@ -0,0 +1,825 @@
+package Collectd::Graph::Common;
+
+# Copyright (C) 2008-2011  Florian Forster
+# Copyright (C) 2011       noris network AG
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; only version 2 of the License is applicable.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+#
+# Authors:
+#   Florian "octo" Forster <octo at collectd.org>
+
+use strict;
+use warnings;
+
+use vars (qw($ColorCanvas $ColorFullBlue $ColorHalfBlue));
+
+use Collectd::Unixsock ();
+use Carp (qw(confess cluck));
+use CGI (':cgi');
+use Exporter;
+use Collectd::Graph::Config (qw(gc_get_scalar));
+
+our $Cache = {};
+
+$ColorCanvas   = 'FFFFFF';
+$ColorFullBlue = '0000FF';
+$ColorHalfBlue = 'B7B7F7';
+
+@Collectd::Graph::Common::ISA = ('Exporter');
+@Collectd::Graph::Common::EXPORT_OK = (qw(
+  $ColorCanvas
+  $ColorFullBlue
+  $ColorHalfBlue
+
+  sanitize_hostname
+  sanitize_plugin sanitize_plugin_instance
+  sanitize_type sanitize_type_instance
+  group_files_by_plugin_instance
+  get_files_from_directory
+  filename_to_ident
+  ident_to_filename
+  ident_to_string
+  get_all_hosts
+  get_files_for_host
+  get_files_by_ident
+  get_selected_files
+  get_timespan_selection
+  get_host_selection
+  get_plugin_selection
+  get_random_color
+  get_faded_color
+  sort_idents_by_type_instance
+  type_to_module_name
+  epoch_to_rfc1123
+  flush_files
+));
+
+our $DefaultDataDir = '/var/lib/collectd/rrd';
+
+return (1);
+
+sub _sanitize_generic_allow_minus
+{
+  my $str = "" . shift;
+
+  # remove all slashes
+  $str =~ s#/##g;
+
+  # remove all dots and dashes at the beginning and at the end.
+  $str =~ s#^[\.-]+##;
+  $str =~ s#[\.-]+$##;
+
+  return ($str);
+}
+
+sub _sanitize_generic_no_minus
+{
+  # Do everything the allow-minus variant does..
+  my $str = _sanitize_generic_allow_minus (@_);
+
+  # .. and remove the dashes, too
+  $str =~ s#/##g;
+
+  return ($str);
+} # _sanitize_generic_no_minus
+
+sub sanitize_hostname
+{
+  return (_sanitize_generic_allow_minus (@_));
+}
+
+sub sanitize_plugin
+{
+  return (_sanitize_generic_no_minus (@_));
+}
+
+sub sanitize_plugin_instance
+{
+  return (_sanitize_generic_allow_minus (@_));
+}
+
+sub sanitize_type
+{
+  return (_sanitize_generic_no_minus (@_));
+}
+
+sub sanitize_type_instance
+{
+  return (_sanitize_generic_allow_minus (@_));
+}
+
+sub group_files_by_plugin_instance
+{
+  my @files = @_;
+  my $data = {};
+
+  for (my $i = 0; $i < @files; $i++)
+  {
+    my $file = $files[$i];
+    my $key1 = $file->{'hostname'} || '';
+    my $key2 = $file->{'plugin_instance'} || '';
+    my $key = "$key1-$key2";
+
+    $data->{$key} ||= [];
+    push (@{$data->{$key}}, $file);
+  }
+
+  return ($data);
+}
+
+sub filename_to_ident
+{
+  my $file = shift;
+  my $ret;
+
+  if ($file =~ m#([^/]+)/([^/\-]+)(?:-([^/]+))?/([^/\-]+)(?:-([^/]+))?\.rrd$#)
+  {
+    $ret = {hostname => $1, plugin => $2, type => $4};
+    if (defined ($3))
+    {
+      $ret->{'plugin_instance'} = $3;
+    }
+    if (defined ($5))
+    {
+      $ret->{'type_instance'} = $5;
+    }
+    if ($`)
+    {
+      $ret->{'_prefix'} = $`;
+    }
+  }
+  else
+  {
+    return;
+  }
+
+  return ($ret);
+} # filename_to_ident
+
+sub ident_to_filename
+{
+  my $ident = shift;
+  my $data_dir = gc_get_scalar ('DataDir', $DefaultDataDir);
+
+  my $ret = '';
+
+  if (defined ($ident->{'_prefix'}))
+  {
+    $ret .= $ident->{'_prefix'};
+  }
+  else
+  {
+    $ret .= "$data_dir/";
+  }
+
+  if (!$ident->{'hostname'})
+  {
+    cluck ("hostname is undefined")
+  }
+  if (!$ident->{'plugin'})
+  {
+    cluck ("plugin is undefined")
+  }
+  if (!$ident->{'type'})
+  {
+    cluck ("type is undefined")
+  }
+
+  $ret .= $ident->{'hostname'} . '/' . $ident->{'plugin'};
+  if (defined ($ident->{'plugin_instance'}))
+  {
+    $ret .= '-' . $ident->{'plugin_instance'};
+  }
+
+  $ret .= '/' . $ident->{'type'};
+  if (defined ($ident->{'type_instance'}))
+  {
+    $ret .= '-' . $ident->{'type_instance'};
+  }
+  $ret .= '.rrd';
+
+  return ($ret);
+} # ident_to_filename
+
+sub _part_to_string
+{
+  my $part = shift;
+
+  if (!defined ($part))
+  {
+    return ("(UNDEF)");
+  }
+  if (ref ($part) eq 'ARRAY')
+  {
+    if (1 == @$part)
+    {
+      return ($part->[0]);
+    }
+    else
+    {
+      return ('(' . join (',', @$part) . ')');
+    }
+  }
+  else
+  {
+    return ($part);
+  }
+} # _part_to_string
+
+sub ident_to_string
+{
+  my $ident = shift;
+
+  my $ret = '';
+
+  $ret .= _part_to_string ($ident->{'hostname'})
+  . '/' . _part_to_string ($ident->{'plugin'});
+  if (defined ($ident->{'plugin_instance'}))
+  {
+    $ret .= '-' . _part_to_string ($ident->{'plugin_instance'});
+  }
+
+  $ret .= '/' . _part_to_string ($ident->{'type'});
+  if (defined ($ident->{'type_instance'}))
+  {
+    $ret .= '-' . _part_to_string ($ident->{'type_instance'});
+  }
+
+  return ($ret);
+} # ident_to_string
+
+sub get_files_from_directory
+{
+  my $dir = shift;
+  my $recursive = @_ ? shift : 0;
+  my $dh;
+  my @directories = ();
+  my @files = ();
+  my $ret = [];
+
+  opendir ($dh, $dir) or die ("opendir ($dir): $!");
+  while (my $entry = readdir ($dh))
+  {
+    next if ($entry =~ m/^\./);
+
+    $entry = "$dir/$entry";
+
+    if (-d $entry)
+    {
+      push (@directories, $entry);
+    }
+    elsif (-f $entry)
+    {
+      push (@files, $entry);
+    }
+  }
+  closedir ($dh);
+
+  push (@$ret, map { filename_to_ident ($_) } sort (@files));
+
+  if ($recursive > 0)
+  {
+    for (@directories)
+    {
+      my $temp = get_files_from_directory ($_, $recursive - 1);
+      if ($temp && @$temp)
+      {
+        push (@$ret, @$temp);
+      }
+    }
+  }
+
+  return ($ret);
+} # get_files_from_directory
+
+sub get_all_hosts
+{
+  my $ret = [];
+
+  if (defined ($Cache->{'get_all_hosts'}))
+  {
+    $ret = $Cache->{'get_all_hosts'};
+  }
+  else
+  {
+    my $dh;
+    my $data_dir = gc_get_scalar ('DataDir', $DefaultDataDir);
+
+    opendir ($dh, "$data_dir") or confess ("opendir ($data_dir): $!");
+    while (my $entry = readdir ($dh))
+    {
+      next if ($entry =~ m/^\./);
+      next if (!-d "$data_dir/$entry");
+      push (@$ret, sanitize_hostname ($entry));
+    }
+    closedir ($dh);
+
+    $Cache->{'get_all_hosts'} = $ret;
+  }
+
+  if (wantarray ())
+  {
+    return (@$ret);
+  }
+  elsif (@$ret)
+  {
+    return ($ret);
+  }
+  else
+  {
+    return;
+  }
+} # get_all_hosts
+
+sub get_all_plugins
+{
+  my @hosts = @_;
+  my $ret = {};
+  my $dh;
+  my $data_dir = gc_get_scalar ('DataDir', $DefaultDataDir);
+  my $cache_key;
+
+  if (@hosts)
+  {
+    $cache_key = join (';', @hosts);
+  }
+  else
+  {
+    $cache_key = "/*/";
+    @hosts = get_all_hosts ();
+  }
+
+  if (defined ($Cache->{'get_all_plugins'}{$cache_key}))
+  {
+    $ret = $Cache->{'get_all_plugins'}{$cache_key};
+
+    if (wantarray ())
+    {
+      return (sort (keys %$ret));
+    }
+    else
+    {
+      return ($ret);
+    }
+  }
+
+  for (@hosts)
+  {
+    my $host = $_;
+    opendir ($dh, "$data_dir/$host") or next;
+    while (my $entry = readdir ($dh))
+    {
+      my $plugin;
+      my $plugin_instance = '';
+
+      next if ($entry =~ m/^\./);
+      next if (!-d "$data_dir/$host/$entry");
+
+      if ($entry =~ m#^([^-]+)-(.+)$#)
+      {
+       $plugin = $1;
+       $plugin_instance = $2;
+      }
+      elsif ($entry =~ m#^([^-]+)$#)
+      {
+       $plugin = $1;
+       $plugin_instance = '';
+      }
+      else
+      {
+       next;
+      }
+
+      $ret->{$plugin} ||= {};
+      $ret->{$plugin}{$plugin_instance} = 1;
+    } # while (readdir)
+    closedir ($dh);
+  } # for (@hosts)
+
+  $Cache->{'get_all_plugins'}{$cache_key} = $ret;
+  if (wantarray ())
+  {
+    return (sort (keys %$ret));
+  }
+  else
+  {
+    return ($ret);
+  }
+} # get_all_plugins
+
+sub get_files_for_host
+{
+  my $host = sanitize_hostname (shift);
+  my $data_dir = gc_get_scalar ('DataDir', $DefaultDataDir);
+  return (get_files_from_directory ("$data_dir/$host", 2));
+} # get_files_for_host
+
+sub _filter_ident
+{
+  my $filter = shift;
+  my $ident = shift;
+
+  for (qw(hostname plugin plugin_instance type type_instance))
+  {
+    my $part = $_;
+    my $tmp;
+
+    if (!defined ($filter->{$part}))
+    {
+      next;
+    }
+    if (!defined ($ident->{$part}))
+    {
+      return (1);
+    }
+
+    if (ref $filter->{$part})
+    {
+      if (!grep { $ident->{$part} eq $_ } (@{$filter->{$part}}))
+      {
+       return (1);
+      }
+    }
+    else
+    {
+      if ($ident->{$part} ne $filter->{$part})
+      {
+       return (1);
+      }
+    }
+  }
+
+  return (0);
+} # _filter_ident
+
+sub _get_all_files
+{
+  my $ret;
+
+  if (defined ($Cache->{'_get_all_files'}))
+  {
+    $ret = $Cache->{'_get_all_files'};
+  }
+  else
+  {
+    my $data_dir = gc_get_scalar ('DataDir', $DefaultDataDir);
+
+    $ret = get_files_from_directory ($data_dir, 3);
+    $Cache->{'_get_all_files'} = $ret;
+  }
+
+  return ($ret);
+} # _get_all_files
+
+sub get_files_by_ident
+{
+  my $ident = shift;
+  my $all_files;
+  my @ret = ();
+
+  my $cache_key = ident_to_string ($ident);
+  if (defined ($Cache->{'get_files_by_ident'}{$cache_key}))
+  {
+    my $ret = $Cache->{'get_files_by_ident'}{$cache_key};
+
+    return ($ret)
+  }
+
+  $all_files = _get_all_files ();
+
+  @ret = grep { _filter_ident ($ident, $_) == 0 } (@$all_files);
+
+  $Cache->{'get_files_by_ident'}{$cache_key} = \@ret;
+  return (\@ret);
+} # get_files_by_ident
+
+sub get_selected_files
+{
+  my $ident = {};
+  
+  for (qw(hostname plugin plugin_instance type type_instance))
+  {
+    my $part = $_;
+    my @temp = param ($part);
+    if (!@temp)
+    {
+      next;
+    }
+    elsif (($part eq 'plugin') || ($part eq 'type'))
+    {
+      $ident->{$part} = [map { _sanitize_generic_no_minus ($_) } (@temp)];
+    }
+    else
+    {
+      $ident->{$part} = [map { _sanitize_generic_allow_minus ($_) } (@temp)];
+    }
+  }
+
+  return (get_files_by_ident ($ident));
+} # get_selected_files
+
+sub get_timespan_selection
+{
+  my $ret = 86400;
+  if (param ('timespan'))
+  {
+    my $temp = int (param ('timespan'));
+    if ($temp && ($temp > 0))
+    {
+      $ret = $temp;
+    }
+  }
+
+  return ($ret);
+} # get_timespan_selection
+
+sub get_host_selection
+{
+  my %ret = ();
+
+  for (get_all_hosts ())
+  {
+    $ret{$_} = 0;
+  }
+
+  for (param ('hostname'))
+  {
+    my $host = _sanitize_generic_allow_minus ($_);
+    if (defined ($ret{$host}))
+    {
+      $ret{$host} = 1;
+    }
+  }
+
+  if (wantarray ())
+  {
+    return (grep { $ret{$_} > 0 } (sort (keys %ret)));
+  }
+  else
+  {
+    return (\%ret);
+  }
+} # get_host_selection
+
+sub get_plugin_selection
+{
+  my %ret = ();
+  my @hosts = get_host_selection ();
+
+  for (get_all_plugins (@hosts))
+  {
+    $ret{$_} = 0;
+  }
+
+  for (param ('plugin'))
+  {
+    if (defined ($ret{$_}))
+    {
+      $ret{$_} = 1;
+    }
+  }
+
+  if (wantarray ())
+  {
+    return (grep { $ret{$_} > 0 } (sort (keys %ret)));
+  }
+  else
+  {
+    return (\%ret);
+  }
+} # get_plugin_selection
+
+sub _string_to_color
+{
+  my $color = shift;
+  if ($color =~ m/([0-9A-Fa-f][0-9A-Fa-f])([0-9A-Fa-f][0-9A-Fa-f])([0-9A-Fa-f][0-9A-Fa-f])/)
+  {
+    return ([hex ($1) / 255.0, hex ($2) / 255.0, hex ($3) / 255.0]);
+  }
+  return;
+} # _string_to_color
+
+sub _color_to_string
+{
+  confess ("Wrong number of arguments") if (@_ != 1);
+  return (sprintf ('%02hx%02hx%02hx', map { int (255.0 * $_) } @{$_[0]}));
+} # _color_to_string
+
+sub get_random_color
+{
+  my ($r, $g, $b) = (rand (), rand ());
+  my $min = 0.0;
+  my $max = 1.0;
+
+  if (($r + $g) < 1.0)
+  {
+    $min = 1.0 - ($r + $g);
+  }
+  else
+  {
+    $max = 2.0 - ($r + $g);
+  }
+
+  $b = $min + (rand () * ($max - $min));
+
+  return (_color_to_string ([$r, $g, $b]));
+} # get_random_color
+
+sub get_faded_color
+{
+  my $fg = shift;
+  my $bg;
+  my %opts = @_;
+  my $ret = [undef, undef, undef];
+
+  $opts{'background'} ||= [1.0, 1.0, 1.0];
+  $opts{'alpha'} ||= 0.25;
+
+  if (!ref ($fg))
+  {
+    $fg = _string_to_color ($fg)
+      or confess ("Cannot parse foreground color $fg");
+  }
+
+  if (!ref ($opts{'background'}))
+  {
+    $opts{'background'} = _string_to_color ($opts{'background'})
+      or confess ("Cannot parse background color " . $opts{'background'});
+  }
+  $bg = $opts{'background'};
+
+  for (my $i = 0; $i < 3; $i++)
+  {
+    $ret->[$i] = ($opts{'alpha'} * $fg->[$i])
+       + ((1.0 - $opts{'alpha'}) * $bg->[$i]);
+  }
+
+  return (_color_to_string ($ret));
+} # get_faded_color
+
+sub sort_idents_by_type_instance
+{
+  my $idents = shift;
+  my $array_sort = shift;
+
+  my %elements = map { $_->{'type_instance'} => $_ } (@$idents);
+  splice (@$idents, 0);
+
+  for (@$array_sort)
+  {
+    next if (!exists ($elements{$_}));
+    push (@$idents, $elements{$_});
+    delete ($elements{$_});
+  }
+  push (@$idents, map { $elements{$_} } (sort (keys %elements)));
+} # sort_idents_by_type_instance
+
+sub type_to_module_name
+{
+  my $type = shift;
+  my $ret;
+  
+  $ret = ucfirst (lc ($type));
+
+  $ret =~ s/[^A-Za-z_]//g;
+  $ret =~ s/_([A-Za-z])/\U$1\E/g;
+
+  return ("Collectd::Graph::Type::$ret");
+} # type_to_module_name
+
+sub epoch_to_rfc1123
+{
+  my @days = (qw(Sun Mon Tue Wed Thu Fri Sat));
+  my @months = (qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec));
+
+  my $epoch = @_ ? shift : time ();
+  my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch);
+  my $string = sprintf ('%s, %02d %s %4d %02d:%02d:%02d GMT', $days[$wday], $mday,
+      $months[$mon], 1900 + $year, $hour ,$min, $sec);
+  return ($string);
+}
+
+sub flush_files
+{
+  my $all_files = shift;
+  my %opts = @_;
+
+  my $begin;
+  my $end;
+  my $addr;
+  my $interval;
+  my $sock;
+  my $now;
+  my $files_to_flush = [];
+  my $status;
+
+  if (!defined $opts{'begin'})
+  {
+    cluck ("begin is not defined");
+    return;
+  }
+  $begin = $opts{'begin'};
+
+  if (!defined $opts{'end'})
+  {
+    cluck ("end is not defined");
+    return;
+  }
+  $end = $opts{'end'};
+
+  if (!$opts{'addr'})
+  {
+    return (1);
+  }
+
+  $interval = $opts{'interval'} || 10;
+
+  if (ref ($all_files) eq 'HASH')
+  {
+    my @tmp = ($all_files);
+    $all_files = \@tmp;
+  }
+
+  $now = time ();
+  # Don't flush anything if the timespan is in the future.
+  if (($end > $now) && ($begin > $now))
+  {
+    return (1);
+  }
+
+  for (@$all_files)
+  {
+    my $file_orig = $_;
+    my $file_name = ident_to_filename ($file_orig);
+    my $file_copy = {};
+    my @statbuf;
+    my $mtime;
+
+    @statbuf = stat ($file_name);
+    if (!@statbuf)
+    {
+      next;
+    }
+    $mtime = $statbuf[9];
+
+    # Skip if file is fresh
+    if (($now - $mtime) <= $interval)
+    {
+      next;
+    }
+    # or $end is before $mtime
+    elsif (($end != 0) && (($end - $mtime) <= 0))
+    {
+      next;
+    }
+
+    $file_copy->{'host'} = $file_orig->{'hostname'};
+    $file_copy->{'plugin'} = $file_orig->{'plugin'};
+    if (exists $file_orig->{'plugin_instance'})
+    {
+      $file_copy->{'plugin_instance'} = $file_orig->{'plugin_instance'}
+    }
+    $file_copy->{'type'} = $file_orig->{'type'};
+    if (exists $file_orig->{'type_instance'})
+    {
+      $file_copy->{'type_instance'} = $file_orig->{'type_instance'}
+    }
+
+    push (@$files_to_flush, $file_copy);
+  } # for (@$all_files)
+
+  if (!@$files_to_flush)
+  {
+    return (1);
+  }
+
+  $sock = Collectd::Unixsock->new ($opts{'addr'});
+  if (!$sock)
+  {
+    return;
+  }
+
+  $status = $sock->flush (plugins => ['rrdtool'], identifier => $files_to_flush);
+  if (!$status)
+  {
+    cluck ("FLUSH failed: " . $sock->{'error'});
+    $sock->destroy ();
+    return;
+  }
+
+  $sock->destroy ();
+  return (1);
+} # flush_files
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
diff --git a/contrib/collection3/lib/Collectd/Graph/Config.pm b/contrib/collection3/lib/Collectd/Graph/Config.pm
new file mode 100644 (file)
index 0000000..42582a7
--- /dev/null
@@ -0,0 +1,143 @@
+package Collectd::Graph::Config;
+
+=head1 NAME
+
+Collectd::Graph::Config - Parse the collection3 config file.
+
+=cut
+
+# Copyright (C) 2008,2009  Florian octo Forster <octo at verplant.org>
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; only version 2 of the License is applicable.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+use strict;
+use warnings;
+
+use Carp (qw(cluck confess));
+use Exporter ();
+use Config::General ('ParseConfig');
+
+@Collectd::Graph::Config::ISA = ('Exporter');
+@Collectd::Graph::Config::EXPORT_OK = (qw(gc_read_config gc_get_config
+  gc_get_scalar));
+
+our $Configuration = undef;
+
+return (1);
+
+=head1 EXPORTED FUNCTIONS
+
+=over 4
+
+=item B<gc_read_config> (I<$file>)
+
+Reads the configuration from the file located at I<$file>. Returns B<true> when
+successfull and B<false> otherwise.
+
+=cut
+
+sub gc_read_config
+{
+  my $file = shift;
+  my %conf;
+
+  if ($Configuration)
+  {
+    return (1);
+  }
+
+  $file ||= "etc/collection.conf";
+
+  %conf = ParseConfig (-ConfigFile => $file,
+    -LowerCaseNames => 1,
+    -UseApacheInclude => 1,
+    -IncludeDirectories => 1,
+    ($Config::General::VERSION >= 2.38) ? (-IncludeAgain => 0) : (),
+    -MergeDuplicateBlocks => 1,
+    -CComments => 0);
+  if (!%conf)
+  {
+    return;
+  }
+
+  $Configuration = \%conf;
+  return (1);
+} # gc_read_config
+
+=item B<gc_get_config> ()
+
+Returns the hash as provided by L<Config::General>. The hash is returned as a
+hash reference. Don't change it!
+
+=cut
+
+sub gc_get_config
+{
+  return ($Configuration);
+} # gc_get_config
+
+=item B<gc_get_config> (I<$key>, [I<$default>])
+
+Returns the scalar value I<$key> from the config file. If the key does not
+exist, I<$default> will be returned. If no default is given, B<undef> will be
+used in this case.
+
+=cut
+
+sub gc_get_scalar
+{
+  my $key = shift;
+  my $default = (@_ != 0) ? shift : undef;
+  my $value;
+
+  if (!$Configuration)
+  {
+    return ($default);
+  }
+
+  $value = $Configuration->{lc ($key)};
+  if (!defined ($value))
+  {
+    return ($default);
+  }
+
+  if (ref ($value) ne '')
+  {
+    cluck ("Value for `$key' should be scalar, but actually is "
+      . ref ($value));
+    return ($default);
+  }
+
+  return ($value);
+} # gc_get_config
+
+=back
+
+=head1 DEPENDS ON
+
+L<Config::General>
+
+=head1 SEE ALSO
+
+L<Collectd::Graph::Type>
+
+=head1 AUTHOR AND LICENSE
+
+Copyright (c) 2008 by Florian Forster
+E<lt>octoE<nbsp>atE<nbsp>verplant.orgE<gt>. Licensed under the terms of the GNU
+General Public License, VersionE<nbsp>2 (GPLv2).
+
+=cut
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 et fdm=marker :
diff --git a/contrib/collection3/lib/Collectd/Graph/Type.pm b/contrib/collection3/lib/Collectd/Graph/Type.pm
new file mode 100644 (file)
index 0000000..81add72
--- /dev/null
@@ -0,0 +1,513 @@
+package Collectd::Graph::Type;
+
+=head1 NAME
+
+Collectd::Graph::Type - Base class for the collectd graphing infrastructure
+
+=cut
+
+# Copyright (C) 2008  Florian octo Forster <octo at verplant.org>
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; only version 2 of the License is applicable.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+use strict;
+use warnings;
+
+use Carp (qw(confess cluck));
+use RRDs ();
+use URI::Escape (qw(uri_escape));
+
+use Collectd::Graph::Common (qw($ColorCanvas $ColorFullBlue $ColorHalfBlue
+  ident_to_filename
+  ident_to_string
+  get_faded_color));
+
+return (1);
+
+=head1 DESCRIPTION
+
+This module serves as base class for more specialized classes realizing
+specific "types".
+
+=head1 MEMBER VARIABLES
+
+As typical in Perl, a Collectd::Graph::Type object is a blessed hash reference.
+Member variables are entries in that hash. Inheriting classes are free to add
+additional entries. To set the member variable B<foo> to B<42>, do:
+
+ $obj->{'foo'} = 42;
+
+The following members control the behavior of Collectd::Graph::Type.
+
+=over 4
+
+=item B<files> (array reference)
+
+List of RRD files. Each file is passed as "ident", i.E<nbsp>e. broken up into
+"hostname", "plugin", "type" and optionally "plugin_instance" and
+"type_instance". Use the B<addFiles> method rather than setting this directly.
+
+=item B<data_sources> (array reference)
+
+List of data sources in the RRD files. If this is not given, the default
+implementation of B<getDataSources> will use B<RRDs::info> to find out which
+data sources are contained in the files.
+
+=item B<ds_names> (array reference)
+
+Names of the data sources as printed in the graph. Should be in the same order
+as the data sources are returned by B<getDataSources>.
+
+=item B<rrd_title> (string)
+
+Title of the RRD graph. The title can contain "{hostname}", "{plugin}" and so
+on which are replaced with their actual value. See the B<getTitle> method
+below.
+
+=item B<rrd_opts> (array reference)
+
+List of options directly passed to B<RRDs::graph>.
+
+=item B<rrd_format> (string)
+
+Format to use with B<GPRINT>. Defaults to C<%5.1lf>.
+
+=item B<rrd_colors> (hash reference)
+
+Mapping of data source names to colors, used when graphing the different data
+sources.  Colors are given in the typical hexadecimal RGB form, but without
+leading "#", e.E<nbsp>g.:
+
+ $obj->{'rrd_colors'} = {foo => 'ff0000', bar => '00ff00'};
+
+=back
+
+=head1 METHODS
+
+The following methods are used by the graphing front end and may be overwritten
+to customize their behavior.
+
+=over 4
+
+=cut
+
+sub _get_ds_from_file
+{
+  my $file = shift;
+  my $info = RRDs::info ($file);
+  my %ds = ();
+  my @ds = ();
+
+  if (!$info || (ref ($info) ne 'HASH'))
+  {
+    return;
+  }
+
+  for (keys %$info)
+  {
+    if (m/^ds\[([^\]]+)\]/)
+    {
+      $ds{$1} = 1;
+    }
+  }
+
+  @ds = (keys %ds);
+  if (wantarray ())
+  {
+    return (@ds);
+  }
+  elsif (@ds)
+  {
+    return (\@ds);
+  }
+  else
+  {
+    return;
+  }
+} # _get_ds_from_file
+
+sub new
+{
+  my $pkg = shift;
+  my $obj = bless ({files => []}, $pkg);
+
+  if (@_)
+  {
+    $obj->addFiles (@_);
+  }
+
+  return ($obj);
+}
+
+=item B<addFiles> ({ I<ident> }, [...])
+
+Adds the given idents (which are hash references) to the B<files> member
+variable, see above.
+
+=cut
+
+sub addFiles
+{
+  my $obj = shift;
+  push (@{$obj->{'files'}}, @_);
+}
+
+=item B<getGraphsNum> ()
+
+Returns the number of graphs that can be generated from the added files. By
+default this number equals the number of files.
+
+=cut
+
+sub getGraphsNum
+{
+  my $obj = shift;
+  return (scalar @{$obj->{'files'}});
+}
+
+=item B<getDataSources> ()
+
+Returns the names of the data sources. If the B<data_sources> member variable
+is unset B<RRDs::info> is used to read that information from the first file.
+Set the B<data_sources> member variable instead of overloading this method!
+
+=cut
+
+sub getDataSources
+{
+  my $obj = shift;
+
+  if (!defined $obj->{'data_sources'})
+  {
+    my $ident;
+    my $filename;
+
+    if (!@{$obj->{'files'}})
+    {
+      return;
+    }
+
+    $ident = $obj->{'files'}[0];
+    $filename = ident_to_filename ($ident);
+
+    $obj->{'data_sources'} = _get_ds_from_file ($filename);
+    if (!$obj->{'data_sources'})
+    {
+      cluck ("_get_ds_from_file ($filename) failed.");
+    }
+  }
+
+  if (!defined $obj->{'data_sources'})
+  {
+    return;
+  }
+  elsif (wantarray ())
+  {
+    return (@{$obj->{'data_sources'}})
+  }
+  else
+  {
+    $obj->{'data_sources'};
+  }
+} # getDataSources
+
+
+=item B<getTitle> (I<$index>)
+
+Returns the title of the I<$index>th B<graph> (not necessarily file!). If the
+B<rrd_title> member variable is unset, a generic title is generated from the
+ident. Otherwise the substrings "{hostname}", "{plugin}", "{plugin_instance}",
+"{type}", and "{type_instance}" are replaced by their respective values.
+
+=cut
+
+sub getTitle
+{
+  my $obj = shift;
+  my $ident = shift;
+  my $title = $obj->{'rrd_title'};
+
+  if (!$title)
+  {
+    return (ident_to_string ($ident));
+  }
+
+  my $hostname = $ident->{'hostname'};
+  my $plugin = $ident->{'plugin'};
+  my $plugin_instance = $ident->{'plugin_instance'};
+  my $type = $ident->{'type'};
+  my $type_instance = $ident->{'type_instance'};
+  my $instance;
+
+  if ((defined $type_instance) && (defined $plugin_instance))
+  {
+    $instance = "$plugin_instance/$type_instance";
+  }
+  elsif (defined $type_instance)
+  {
+    $instance = $type_instance;
+  }
+  elsif (defined $plugin_instance)
+  {
+    $instance = $plugin_instance;
+  }
+  else
+  {
+    $instance = 'no instance';
+  }
+
+  if (!defined $plugin_instance)
+  {
+    $plugin_instance = 'no instance';
+  }
+
+  if (!defined $type_instance)
+  {
+    $type_instance = 'no instance';
+  }
+
+  $title =~ s#{hostname}#$hostname#g;
+  $title =~ s#{plugin}#$plugin#g;
+  $title =~ s#{plugin_instance}#$plugin_instance#g;
+  $title =~ s#{type}#$type#g;
+  $title =~ s#{type_instance}#$type_instance#g;
+  $title =~ s#{instance}#$instance#g;
+
+  return ($title);
+}
+
+=item B<getRRDArgs> (I<$index>)
+
+Return the arguments needed to generate the graph from the RRD file(s). If the
+file has only one data source, this default implementation will generate that
+typical min, average, max graph you probably know from temperatures and such.
+If the RRD files have multiple data sources, the average of each data source is
+printes as simple line.
+
+=cut
+
+sub getRRDArgs
+{
+  my $obj = shift;
+  my $index = shift;
+
+  my $ident = $obj->{'files'}[$index];
+  if (!$ident)
+  {
+    cluck ("Invalid index: $index");
+    return;
+  }
+  my $filename = ident_to_filename ($ident);
+
+  my $rrd_opts = $obj->{'rrd_opts'} || [];
+  my $rrd_title = $obj->getTitle ($ident);
+  my $format = $obj->{'rrd_format'} || '%5.1lf';
+
+  my $rrd_colors = $obj->{'rrd_colors'};
+  my @ret = ('-t', $rrd_title, @$rrd_opts);
+
+  if (defined $obj->{'rrd_vertical'})
+  {
+    push (@ret, '-v', $obj->{'rrd_vertical'});
+  }
+
+  my $ds_names = $obj->{'ds_names'};
+  if (!$ds_names)
+  {
+    $ds_names = {};
+  }
+
+  my $ds = $obj->getDataSources ();
+  if (!$ds)
+  {
+    confess ("obj->getDataSources failed.");
+  }
+
+  if (!$rrd_colors)
+  {
+    my @tmp = ('0000ff', 'ff0000', '00ff00', 'ff00ff', '00ffff', 'ffff00');
+
+    for (my $i = 0; $i < @$ds; $i++)
+    {
+      $rrd_colors->{$ds->[$i]} = $tmp[$i % @tmp];
+    }
+  }
+
+  for (my $i = 0; $i < @$ds; $i++)
+  {
+    my $f = $filename;
+    my $ds_name = $ds->[$i];
+
+    # We need to escape colons for RRDTool..
+    $f =~ s#:#\\:#g;
+    $ds_name =~ s#:#\\:#g;
+
+    if (exists ($obj->{'scale'}))
+    {
+      my $scale = 0.0 + $obj->{'scale'};
+      push (@ret,
+       "DEF:min${i}_raw=${f}:${ds_name}:MIN",
+       "DEF:avg${i}_raw=${f}:${ds_name}:AVERAGE",
+       "DEF:max${i}_raw=${f}:${ds_name}:MAX",
+       "CDEF:max${i}=max${i}_raw,$scale,*",
+       "CDEF:avg${i}=avg${i}_raw,$scale,*",
+       "CDEF:min${i}=min${i}_raw,$scale,*");
+    }
+    else
+    {
+      push (@ret,
+       "DEF:min${i}=${f}:${ds_name}:MIN",
+       "DEF:avg${i}=${f}:${ds_name}:AVERAGE",
+       "DEF:max${i}=${f}:${ds_name}:MAX");
+    }
+  }
+
+  if (@$ds == 1)
+  {
+    my $ds_name = $ds->[0];
+    my $color_fg = $rrd_colors->{$ds_name} || '000000';
+    my $color_bg = get_faded_color ($color_fg);
+
+    if ($ds_names->{$ds_name})
+    {
+      $ds_name = $ds_names->{$ds_name};
+    }
+    $ds_name =~ s#:#\\:#g;
+
+    push (@ret, 
+      "AREA:max0#${color_bg}",
+      "AREA:min0#${ColorCanvas}",
+      "LINE1:avg0#${color_fg}:${ds_name}",
+      "GPRINT:min0:MIN:${format} Min,",
+      "GPRINT:avg0:AVERAGE:${format} Avg,",
+      "GPRINT:max0:MAX:${format} Max,",
+      "GPRINT:avg0:LAST:${format} Last\\l");
+  }
+  else
+  {
+    for (my $i = 0; $i < @$ds; $i++)
+    {
+      my $ds_name = $ds->[$i];
+      my $color = $rrd_colors->{$ds_name} || '000000';
+
+      if ($ds_names->{$ds_name})
+      {
+       $ds_name = $ds_names->{$ds_name};
+      }
+
+      push (@ret, 
+       "LINE1:avg${i}#${color}:${ds_name}",
+       "GPRINT:min${i}:MIN:${format} Min,",
+       "GPRINT:avg${i}:AVERAGE:${format} Avg,",
+       "GPRINT:max${i}:MAX:${format} Max,",
+       "GPRINT:avg${i}:LAST:${format} Last\\l");
+    }
+  }
+
+  return (\@ret);
+} # getRRDArgs
+
+=item B<getGraphArgs> (I<$index>)
+
+Returns the parameters that should be passed to the CGI script to generate the
+I<$index>th graph. The returned string is already URI-encoded and will possibly
+set the "hostname", "plugin", "plugin_instance", "type", and "type_instance"
+parameters.
+
+The default implementation simply uses the ident of the I<$index>th file to
+fill this.
+
+=cut
+
+sub getGraphArgs
+{
+  my $obj = shift;
+  my $index = shift;
+  my $ident = $obj->{'files'}[$index];
+
+  my @args = ();
+  for (qw(hostname plugin plugin_instance type type_instance))
+  {
+    if (defined ($ident->{$_}))
+    {
+      push (@args, uri_escape ($_) . '=' . uri_escape ($ident->{$_}));
+    }
+  }
+
+  return (join (';', @args));
+}
+
+=item B<getLastModified> ([I<$index>])
+
+If I<$index> is not given, the modification time of all files is scanned and the most recent modification is returned. If I<$index> is given, only the files belonging to the I<$index>th graph will be considered.
+
+=cut
+
+sub getLastModified
+{
+  my $obj = shift;
+  my $index = @_ ? shift : -1;
+
+  my $mtime = 0;
+
+  if ($index == -1)
+  {
+    for (@{$obj->{'files'}})
+    {
+      my $ident = $_;
+      my $filename = ident_to_filename ($ident);
+      my @statbuf = stat ($filename);
+
+      if (!@statbuf)
+      {
+       next;
+      }
+
+      if ($mtime < $statbuf[9])
+      {
+       $mtime = $statbuf[9];
+      }
+    }
+  }
+  else
+  {
+    my $ident = $obj->{'files'}[$index];
+    my $filename = ident_to_filename ($ident);
+    my @statbuf = stat ($filename);
+
+    $mtime = $statbuf[9];
+  }
+
+  if (!$mtime)
+  {
+    return;
+  }
+  return ($mtime);
+} # getLastModified
+
+=back
+
+=head1 SEE ALSO
+
+L<Collectd::Graph::Type::GenericStacked>
+
+=head1 AUTHOR AND LICENSE
+
+Copyright (c) 2008 by Florian Forster
+E<lt>octoE<nbsp>atE<nbsp>verplant.orgE<gt>. Licensed under the terms of the GNU
+General Public License, VersionE<nbsp>2 (GPLv2).
+
+=cut
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
diff --git a/contrib/collection3/lib/Collectd/Graph/Type/ArcCounts.pm b/contrib/collection3/lib/Collectd/Graph/Type/ArcCounts.pm
new file mode 100644 (file)
index 0000000..7a8946e
--- /dev/null
@@ -0,0 +1,110 @@
+package Collectd::Graph::Type::ArcCounts;
+
+# Copyright (C) 2009  Anthony Dewhurst <dewhurst at gmail>
+#
+# This program is available software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the available Software
+# Foundation; only version 2 of the License is applicable.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the available Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+use strict;
+use warnings;
+use base ('Collectd::Graph::Type');
+
+use Collectd::Graph::Common (qw(ident_to_filename get_faded_color));
+
+return (1);
+
+sub getDataSources
+{
+  return ([qw(available usedbydatasetbydataset usedbysnapshots usedbyrefres usedbychildren)]);
+} # getDataSources
+
+sub new
+{
+  my $pkg = shift;
+  my $obj = Collectd::Graph::Type->new (@_);
+  $obj->{'data_sources'} = [qw(demand_data demand_metadata prefetch_data prefetch_metadata)];
+  $obj->{'rrd_opts'} = [];
+  $obj->{'rrd_title'} = 'ARC {type_instance} on {hostname}';
+
+  return (bless ($obj, $pkg));
+} # new
+
+sub getRRDArgs
+{
+  my $obj = shift;
+  my $index = shift;
+
+  my $ident = $obj->{'files'}[$index];
+  if (!$ident)
+  {
+    cluck ("Invalid index: $index");
+    return;
+  }
+  my $filename = ident_to_filename ($ident);
+  $filename =~ s#:#\\:#g;
+
+  my $legend = $ident->{'type_instance'};
+
+
+  my $faded_green = get_faded_color ('00ff00');
+  my $faded_blue = get_faded_color ('0000ff');
+  my $faded_red = get_faded_color ('ff0000');
+  my $faded_cyan = get_faded_color ('00ffff');
+
+  my @ret = @{$obj->{'rrd_opts'}};
+
+  push @ret, '-t', $obj->getTitle($ident);
+  push @ret, '-v', ucfirst($ident->{'type_instance'});
+
+  my $ds = {
+    demand_data       => { legend => "Demand data    ", color => "00ff00" },
+    demand_metadata   => { legend => "Demand metadata", color => "0000ff" },
+    prefetch_data     => { legend => "Prefetch data  ", color => "ff0000" },
+    prefetch_metadata => { legend => "Prefetch meta  ", color => "ff00ff" },
+  };
+
+  foreach (qw(demand_data demand_metadata prefetch_data prefetch_metadata))
+  {
+    push @ret,
+      "DEF:${_}_min=${filename}:${_}:MIN",
+      "DEF:${_}_avg=${filename}:${_}:AVERAGE",
+      "DEF:${_}_max=${filename}:${_}:MAX";
+  }
+
+  {
+    push @ret,
+      "CDEF:stack_prefetch_metadata=prefetch_metadata_avg",
+      "CDEF:stack_prefetch_data=prefetch_data_avg,stack_prefetch_metadata,+",
+      "CDEF:stack_demand_metadata=demand_metadata_avg,stack_prefetch_data,+",
+      "CDEF:stack_demand_data=demand_data_avg,stack_demand_metadata,+",
+      "AREA:stack_demand_data#${faded_green}",
+      "AREA:stack_demand_metadata#${faded_blue}",
+      "AREA:stack_prefetch_data#${faded_red}",
+      "AREA:stack_prefetch_metadata#${faded_cyan}",
+  }
+
+  foreach (qw(demand_data demand_metadata prefetch_data prefetch_metadata))
+  {
+    push @ret,
+      "LINE1:stack_${_}#" . $ds->{$_}->{color} . ":" . $ds->{$_}->{legend},
+      "GPRINT:${_}_min:MIN:%5.1lf Min,",
+      "GPRINT:${_}_avg:AVERAGE:%5.1lf Avg,",
+      "GPRINT:${_}_max:MAX:%5.1lf Max,",
+      "GPRINT:${_}_avg:LAST:%5.1lf Last\l";
+  }
+
+  return \@ret;
+
+} # getRRDArgs
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
diff --git a/contrib/collection3/lib/Collectd/Graph/Type/Df.pm b/contrib/collection3/lib/Collectd/Graph/Type/Df.pm
new file mode 100644 (file)
index 0000000..0fbd0d3
--- /dev/null
@@ -0,0 +1,83 @@
+package Collectd::Graph::Type::Df;
+
+# Copyright (C) 2008,2009  Florian octo Forster <octo at verplant.org>
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; only version 2 of the License is applicable.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+use strict;
+use warnings;
+use base ('Collectd::Graph::Type');
+
+use Collectd::Graph::Common (qw(ident_to_filename get_faded_color));
+
+return (1);
+
+sub getDataSources
+{
+  return ([qw(free used)]);
+} # getDataSources
+
+sub new
+{
+  my $pkg = shift;
+  my $obj = Collectd::Graph::Type->new (@_);
+  $obj->{'data_sources'} = [qw(free used)];
+  $obj->{'rrd_opts'} = ['-v', 'Bytes'];
+  $obj->{'rrd_title'} = 'Disk space ({type_instance})';
+  $obj->{'rrd_format'} = '%5.1lf%sB';
+  $obj->{'colors'} = [qw(00b000 ff0000)];
+
+  return (bless ($obj, $pkg));
+} # new
+
+sub getRRDArgs
+{
+  my $obj = shift;
+  my $index = shift;
+
+  my $ident = $obj->{'files'}[$index];
+  if (!$ident)
+  {
+    cluck ("Invalid index: $index");
+    return;
+  }
+  my $filename = ident_to_filename ($ident);
+  $filename =~ s#:#\\:#g;
+
+  my $faded_green = get_faded_color ('00ff00');
+  my $faded_red = get_faded_color ('ff0000');
+
+  return (['-t', 'Diskspace (' . $ident->{'type_instance'} . ')', '-v', 'Bytes', '-l', '0',
+    "DEF:free_min=${filename}:free:MIN",
+    "DEF:free_avg=${filename}:free:AVERAGE",
+    "DEF:free_max=${filename}:free:MAX",
+    "DEF:used_min=${filename}:used:MIN",
+    "DEF:used_avg=${filename}:used:AVERAGE",
+    "DEF:used_max=${filename}:used:MAX",
+    "CDEF:both_avg=free_avg,used_avg,+",
+    "AREA:both_avg#${faded_green}",
+    "AREA:used_avg#${faded_red}",
+    'LINE1:both_avg#00ff00:Free',
+    'GPRINT:free_min:MIN:%5.1lf%sB Min,',
+    'GPRINT:free_avg:AVERAGE:%5.1lf%sB Avg,',
+    'GPRINT:free_max:MAX:%5.1lf%sB Max,',
+    'GPRINT:free_avg:LAST:%5.1lf%sB Last\l',
+    'LINE1:used_avg#ff0000:Used',
+    'GPRINT:used_min:MIN:%5.1lf%sB Min,',
+    'GPRINT:used_avg:AVERAGE:%5.1lf%sB Avg,',
+    'GPRINT:used_max:MAX:%5.1lf%sB Max,',
+    'GPRINT:used_avg:LAST:%5.1lf%sB Last\l']);
+} # getRRDArgs
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
diff --git a/contrib/collection3/lib/Collectd/Graph/Type/GenericIO.pm b/contrib/collection3/lib/Collectd/Graph/Type/GenericIO.pm
new file mode 100644 (file)
index 0000000..7ed915b
--- /dev/null
@@ -0,0 +1,140 @@
+package Collectd::Graph::Type::GenericIO;
+
+# Copyright (C) 2008,2009  Florian octo Forster <octo at verplant.org>
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; only version 2 of the License is applicable.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+use strict;
+use warnings;
+use base ('Collectd::Graph::Type');
+
+use Carp ('confess');
+
+use Collectd::Graph::Common (qw($ColorCanvas ident_to_filename get_faded_color));
+
+return (1);
+
+sub getRRDArgs
+{
+  my $obj = shift;
+  my $index = shift;
+
+  my $ident = $obj->{'files'}[$index] || confess ("Unknown index $index");
+  my $filename = ident_to_filename ($ident);
+
+  my $rrd_opts = $obj->{'rrd_opts'} || [];
+  my $rrd_title = $obj->getTitle ($ident);
+  my $format = $obj->{'rrd_format'} || '%5.1lf%s';
+
+  my $ds_list = $obj->getDataSources ();
+  my $ds_names = $obj->{'ds_names'};
+  if (!$ds_names)
+  {
+    $ds_names = {};
+  }
+
+  my $colors = $obj->{'rrd_colors'} || {};
+  my @ret = ('-t', $rrd_title, @$rrd_opts);
+
+  if (defined $obj->{'rrd_vertical'})
+  {
+    push (@ret, '-v', $obj->{'rrd_vertical'});
+  }
+
+  if (@$ds_list != 2)
+  {
+    my $num = 0 + @$ds_list;
+    confess ("Expected two data sources, but there is/are $num");
+  }
+
+  my $rx_ds = $ds_list->[0];
+  my $tx_ds = $ds_list->[1];
+
+  my $rx_ds_name = $ds_names->{$rx_ds} || $rx_ds;
+  my $tx_ds_name = $ds_names->{$tx_ds} || $tx_ds;
+
+  my $rx_color_fg = $colors->{$rx_ds} || '0000ff';
+  my $tx_color_fg = $colors->{$tx_ds} || '00b000';
+
+  my $rx_color_bg = get_faded_color ($rx_color_fg);
+  my $tx_color_bg = get_faded_color ($tx_color_fg);
+  my $overlap_color = get_faded_color ($rx_color_bg, background => $tx_color_bg);
+
+  $filename =~ s#:#\\:#g;
+  $rx_ds =~ s#:#\\:#g;
+  $tx_ds =~ s#:#\\:#g;
+  $rx_ds_name =~ s#:#\\:#g;
+  $tx_ds_name =~ s#:#\\:#g;
+
+  if ($obj->{'scale'})
+  {
+    my $factor = $obj->{'scale'};
+
+    push (@ret,
+       "DEF:min_rx_raw=${filename}:${rx_ds}:MIN",
+       "DEF:avg_rx_raw=${filename}:${rx_ds}:AVERAGE",
+       "DEF:max_rx_raw=${filename}:${rx_ds}:MAX",
+       "DEF:min_tx_raw=${filename}:${tx_ds}:MIN",
+       "DEF:avg_tx_raw=${filename}:${tx_ds}:AVERAGE",
+       "DEF:max_tx_raw=${filename}:${tx_ds}:MAX",
+       "CDEF:min_rx=min_rx_raw,${factor},*",
+       "CDEF:avg_rx=avg_rx_raw,${factor},*",
+       "CDEF:max_rx=max_rx_raw,${factor},*",
+       "CDEF:min_tx=min_tx_raw,${factor},*",
+       "CDEF:avg_tx=avg_tx_raw,${factor},*",
+       "CDEF:max_tx=max_tx_raw,${factor},*");
+  }
+  else # (!$obj->{'scale'})
+  {
+    push (@ret,
+       "DEF:min_rx=${filename}:${rx_ds}:MIN",
+       "DEF:avg_rx=${filename}:${rx_ds}:AVERAGE",
+       "DEF:max_rx=${filename}:${rx_ds}:MAX",
+       "DEF:min_tx=${filename}:${tx_ds}:MIN",
+       "DEF:avg_tx=${filename}:${tx_ds}:AVERAGE",
+       "DEF:max_tx=${filename}:${tx_ds}:MAX");
+  }
+
+  push (@ret,
+    "CDEF:avg_rx_bytes=avg_rx,8,/",
+    "VDEF:global_min_rx=min_rx,MINIMUM",
+    "VDEF:global_avg_rx=avg_rx,AVERAGE",
+    "VDEF:global_max_rx=max_rx,MAXIMUM",
+    "VDEF:global_tot_rx=avg_rx_bytes,TOTAL",
+    "CDEF:avg_tx_bytes=avg_tx,8,/",
+    "VDEF:global_min_tx=min_tx,MINIMUM",
+    "VDEF:global_avg_tx=avg_tx,AVERAGE",
+    "VDEF:global_max_tx=max_tx,MAXIMUM",
+    "VDEF:global_tot_tx=avg_tx_bytes,TOTAL");
+
+  push (@ret,
+      "CDEF:overlap=avg_rx,avg_tx,LT,avg_rx,avg_tx,IF",
+      "AREA:avg_rx#${rx_color_bg}",
+      "AREA:avg_tx#${tx_color_bg}",
+      "AREA:overlap#${overlap_color}",
+      "LINE1:avg_rx#${rx_color_fg}:${rx_ds_name}",
+      "GPRINT:global_min_rx:${format} Min,",
+      "GPRINT:global_avg_rx:${format} Avg,",
+      "GPRINT:global_max_rx:${format} Max,",
+      "GPRINT:global_tot_rx:ca. ${format} Total\\l",
+      "LINE1:avg_tx#${tx_color_fg}:${tx_ds_name}",
+      "GPRINT:global_min_tx:${format} Min,",
+      "GPRINT:global_avg_tx:${format} Avg,",
+      "GPRINT:global_max_tx:${format} Max,",
+      "GPRINT:global_tot_tx:ca. ${format} Total\\l");
+
+  return (\@ret);
+} # getRRDArgs
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
diff --git a/contrib/collection3/lib/Collectd/Graph/Type/GenericStacked.pm b/contrib/collection3/lib/Collectd/Graph/Type/GenericStacked.pm
new file mode 100644 (file)
index 0000000..c5114a8
--- /dev/null
@@ -0,0 +1,230 @@
+package Collectd::Graph::Type::GenericStacked;
+
+# Copyright (C) 2008,2009  Florian octo Forster <octo at verplant.org>
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; only version 2 of the License is applicable.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+use strict;
+use warnings;
+use base ('Collectd::Graph::Type');
+
+use Collectd::Graph::Common (qw($ColorCanvas $ColorFullBlue $ColorHalfBlue
+  group_files_by_plugin_instance ident_to_filename sanitize_type_instance
+  get_faded_color sort_idents_by_type_instance));
+
+return (1);
+
+sub getGraphsNum
+{
+  my $obj = shift;
+  my $group = group_files_by_plugin_instance (@{$obj->{'files'}});
+
+  return (scalar (keys %$group));
+}
+
+sub getRRDArgs
+{
+  my $obj = shift;
+  my $index = shift;
+
+  my $group = group_files_by_plugin_instance (@{$obj->{'files'}});
+  my @group = sort (keys %$group);
+
+  my $rrd_opts = $obj->{'rrd_opts'} || [];
+  my $format = $obj->{'rrd_format'} || '%5.1lf';
+
+  my $idents = $group->{$group[$index]};
+  my $ds_name_len = 0;
+
+  my $ds = $obj->getDataSources ();
+  if (!$ds)
+  {
+    confess ("obj->getDataSources failed.");
+  }
+  if (@$ds != 1)
+  {
+    confess ("I can only work with RRD files that have "
+      . "exactly one data source!");
+  }
+  my $data_source = $ds->[0];
+
+  my $rrd_title = $obj->getTitle ($idents->[0]);
+
+  my $colors = $obj->{'rrd_colors'} || {};
+  my @ret = ('-t', $rrd_title, @$rrd_opts);
+
+  my $ignore_unknown = $obj->{'ignore_unknown'} || 0;
+  if ($ignore_unknown)
+  {
+    if ($ignore_unknown =~ m/^(yes|true|on)$/i)
+    {
+      $ignore_unknown = 1;
+    }
+    else
+    {
+      $ignore_unknown = 0;
+    }
+  }
+
+  my $stacking = $obj->{'stacking'};
+  if ($stacking)
+  {
+    if ($stacking =~ m/^(no|false|off|none)$/i)
+    {
+      $stacking = 0;
+    }
+    else
+    {
+      $stacking = 1;
+    }
+  }
+  else # if (!$stacking)
+  {
+    $stacking = 1;
+  }
+
+  if (defined $obj->{'rrd_vertical'})
+  {
+    push (@ret, '-v', $obj->{'rrd_vertical'});
+  }
+
+  if ($obj->{'custom_order'})
+  {
+    sort_idents_by_type_instance ($idents, $obj->{'custom_order'});
+  }
+
+  if ($ignore_unknown)
+  {
+    my $new_idents = [];
+    for (@$idents)
+    {
+      if (exists ($obj->{'ds_names'}{$_->{'type_instance'}}))
+      {
+       push (@$new_idents, $_);
+      }
+    }
+
+    if (@$new_idents)
+    {
+      $idents = $new_idents;
+    }
+  }
+
+  $obj->{'ds_names'} ||= {};
+  my @names = map { $obj->{'ds_names'}{$_->{'type_instance'}} || $_->{'type_instance'} } (@$idents);
+
+  for (my $i = 0; $i < @$idents; $i++)
+  {
+    my $ident = $idents->[$i];
+    my $filename = ident_to_filename ($ident);
+
+    if ($ds_name_len < length ($names[$i]))
+    {
+      $ds_name_len = length ($names[$i]);
+    }
+
+    # Escape colons _after_ the length has been checked.
+    $names[$i] =~ s/:/\\:/g;
+
+    push (@ret,
+      "DEF:min${i}=${filename}:${data_source}:MIN",
+      "DEF:avg${i}=${filename}:${data_source}:AVERAGE",
+      "DEF:max${i}=${filename}:${data_source}:MAX");
+  }
+
+  if ($stacking)
+  {
+    for (my $i = @$idents - 1; $i >= 0; $i--)
+    {
+      if ($i == (@$idents - 1))
+      {
+        push (@ret,
+         "CDEF:cdef${i}=avg${i},UN,0,avg${i},IF");
+      }
+      else
+      {
+        my $j = $i + 1;
+        push (@ret,
+          "CDEF:cdef${i}=avg${i},UN,0,avg${i},IF,cdef${j},+");
+      }
+    }
+
+    for (my $i = 0; $i < @$idents; $i++)
+    {
+      my $type_instance = $idents->[$i]{'type_instance'};
+      my $color = '000000';
+      if (exists $colors->{$type_instance})
+      {
+        $color = $colors->{$type_instance};
+      }
+
+      $color = get_faded_color ($color);
+
+      push (@ret,
+        "AREA:cdef${i}#${color}");
+    }
+  }
+  else # if (!$stacking)
+  {
+    for (my $i = @$idents - 1; $i >= 0; $i--)
+    {
+      push (@ret,
+        "CDEF:cdef${i}=avg${i}");
+    }
+  }
+
+  for (my $i = 0; $i < @$idents; $i++)
+  {
+    my $type_instance = $idents->[$i]{'type_instance'};
+    my $ds_name = sprintf ("%-*s", $ds_name_len, $names[$i]);
+    my $color = '000000';
+    if (exists $colors->{$type_instance})
+    {
+      $color = $colors->{$type_instance};
+    }
+    push (@ret,
+      "LINE1:cdef${i}#${color}:${ds_name}",
+      "GPRINT:min${i}:MIN:${format} Min,",
+      "GPRINT:avg${i}:AVERAGE:${format} Avg,",
+      "GPRINT:max${i}:MAX:${format} Max,",
+      "GPRINT:avg${i}:LAST:${format} Last\\l");
+  }
+
+  return (\@ret);
+}
+
+sub getGraphArgs
+{
+  my $obj = shift;
+  my $index = shift;
+
+  my $group = group_files_by_plugin_instance (@{$obj->{'files'}});
+  my @group = sort (keys %$group);
+
+  my $idents = $group->{$group[$index]};
+
+  my @args = ();
+  for (qw(hostname plugin plugin_instance type))
+  {
+    if (defined ($idents->[0]{$_}))
+    {
+      push (@args, $_ . '=' . $idents->[0]{$_});
+    }
+  }
+
+  return (join (';', @args));
+} # getGraphArgs
+
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
diff --git a/contrib/collection3/lib/Collectd/Graph/Type/JavaMemory.pm b/contrib/collection3/lib/Collectd/Graph/Type/JavaMemory.pm
new file mode 100644 (file)
index 0000000..0da2988
--- /dev/null
@@ -0,0 +1,162 @@
+package Collectd::Graph::Type::JavaMemory;
+
+# Copyright (C) 2008,2009  Florian octo Forster <octo at verplant.org>
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; only version 2 of the License is applicable.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+use strict;
+use warnings;
+use base ('Collectd::Graph::Type');
+
+use Collectd::Graph::Common (qw($ColorCanvas $ColorFullBlue $ColorHalfBlue
+  group_files_by_plugin_instance ident_to_filename sanitize_type_instance
+  get_faded_color sort_idents_by_type_instance));
+
+return (1);
+
+sub getGraphsNum
+{
+  my $obj = shift;
+  my $group = group_files_by_plugin_instance (@{$obj->{'files'}});
+
+  return (scalar (keys %$group));
+}
+
+sub getRRDArgs
+{
+  my $obj = shift;
+  my $index = shift;
+
+  my $group = group_files_by_plugin_instance (@{$obj->{'files'}});
+  my @group = sort (keys %$group);
+
+  my $rrd_opts = $obj->{'rrd_opts'} || [];
+  my $format = $obj->{'rrd_format'} || '%5.1lf';
+
+  my $idents = $group->{$group[$index]};
+  my %type_instance = ();
+
+  my $ds = $obj->getDataSources ();
+  if (!$ds)
+  {
+    confess ("obj->getDataSources failed.");
+  }
+  if (@$ds != 1)
+  {
+    confess ("I can only work with RRD files that have "
+      . "exactly one data source!");
+  }
+  my $data_source = $ds->[0];
+
+  my $rrd_title = $idents->[0]{'plugin_instance'};
+  $rrd_title =~ s/^memory_pool-//;
+  $rrd_title = "Memory pool \"$rrd_title\"";
+
+  my $ds_names =
+  {
+    max       => 'Max      ',
+    committed => 'Committed',
+    used      => 'Used     ',
+    init      => 'Init     '
+  };
+
+  my $colors =
+  {
+    max       => '00ff00',
+    committed => 'ff8000',
+    used      => 'ff0000',
+    init      => '0000f0',
+    'head-committed'    => '000000',
+    'head-init'         => '000000',
+    'head-max'          => '000000',
+    'head-used'         => '000000',
+    'nonhead-committed' => '000000',
+    'nonhead-init'      => '000000',
+    'nonhead-max'       => '000000',
+    'nonhead-used'      => '000000'
+  };
+  my @ret = ('-t', $rrd_title, @$rrd_opts);
+
+  if (defined $obj->{'rrd_vertical'})
+  {
+    push (@ret, '-v', $obj->{'rrd_vertical'});
+  }
+  else
+  {
+    push (@ret, '-v', "Bytes");
+  }
+
+  for (@$idents)
+  {
+    my $ident = $_;
+    if ($ident->{'type_instance'})
+    {
+      $type_instance{$ident->{'type_instance'}} = $ident;
+    }
+  }
+
+  if (exists ($type_instance{'committed'})
+    && exists ($type_instance{'init'})
+    && exists ($type_instance{'max'})
+    && exists ($type_instance{'used'}))
+  {
+    for (qw(max committed init used))
+    {
+      my $inst = $_;
+      my $file = ident_to_filename ($type_instance{$inst});
+      my $color = $colors->{$inst};
+      my $name = $ds_names->{$inst};
+      push (@ret,
+       "DEF:${inst}_min=${file}:value:MIN",
+       "DEF:${inst}_avg=${file}:value:AVERAGE",
+       "DEF:${inst}_max=${file}:value:MAX",
+       "AREA:${inst}_avg#${color}10",
+       "LINE1:${inst}_avg#${color}:${name}",
+       "GPRINT:${inst}_min:MIN:%5.1lf\%sB Min,",
+       "GPRINT:${inst}_avg:AVERAGE:%5.1lf\%sB Avg,",
+       "GPRINT:${inst}_max:MAX:%5.1lf\%sB Max,",
+       "GPRINT:${inst}_avg:LAST:%5.1lf\%sB Last\\l");
+    }
+    return (\@ret);
+  }
+  else
+  {
+    require Collectd::Graph::Type::GenericStacked;
+    return (Collectd::Graph::Type::GenericStacked::getRRDArgs ($obj, $index));
+  }
+} # getRRDArgs
+
+sub getGraphArgs
+{
+  my $obj = shift;
+  my $index = shift;
+
+  my $group = group_files_by_plugin_instance (@{$obj->{'files'}});
+  my @group = sort (keys %$group);
+
+  my $idents = $group->{$group[$index]};
+
+  my @args = ();
+  for (qw(hostname plugin plugin_instance type))
+  {
+    if (defined ($idents->[0]{$_}))
+    {
+      push (@args, $_ . '=' . $idents->[0]{$_});
+    }
+  }
+
+  return (join (';', @args));
+} # getGraphArgs
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
diff --git a/contrib/collection3/lib/Collectd/Graph/Type/Load.pm b/contrib/collection3/lib/Collectd/Graph/Type/Load.pm
new file mode 100644 (file)
index 0000000..b6ca4a6
--- /dev/null
@@ -0,0 +1,69 @@
+package Collectd::Graph::Type::Load;
+
+use strict;
+use warnings;
+use base ('Collectd::Graph::Type');
+
+use Collectd::Graph::Common (qw($ColorCanvas ident_to_filename get_faded_color));
+
+return (1);
+
+sub new
+{
+  my $pkg = shift;
+  my $obj = Collectd::Graph::Type->new (@_);
+  $obj->{'data_sources'} = [qw(shortterm midterm longterm)];
+  $obj->{'rrd_opts'} = ['-v', 'System load'];
+  $obj->{'rrd_title'} = 'System load';
+  $obj->{'rrd_format'} = '%.2lf';
+  $obj->{'colors'} = [qw(00ff00 0000ff ff0000)];
+
+  return (bless ($obj, $pkg));
+} # new
+
+sub getRRDArgs
+{
+  my $obj = shift;
+  my $index = shift;
+
+  my $ident = $obj->{'files'}[$index];
+  if (!$ident)
+  {
+    cluck ("Invalid index: $index");
+    return;
+  }
+  my $filename = ident_to_filename ($ident);
+  $filename =~ s#:#\\:#g;
+
+  my $faded_green = get_faded_color ('00ff00');
+
+  return (['-t', 'System load', '-v', 'System load',
+    "DEF:s_min=${filename}:shortterm:MIN",
+    "DEF:s_avg=${filename}:shortterm:AVERAGE",
+    "DEF:s_max=${filename}:shortterm:MAX",
+    "DEF:m_min=${filename}:midterm:MIN",
+    "DEF:m_avg=${filename}:midterm:AVERAGE",
+    "DEF:m_max=${filename}:midterm:MAX",
+    "DEF:l_min=${filename}:longterm:MIN",
+    "DEF:l_avg=${filename}:longterm:AVERAGE",
+    "DEF:l_max=${filename}:longterm:MAX",
+    "AREA:s_max#${faded_green}",
+    "AREA:s_min#${ColorCanvas}",
+    "LINE1:s_avg#00ff00: 1 min",
+    "GPRINT:s_min:MIN:%.2lf Min,",
+    "GPRINT:s_avg:AVERAGE:%.2lf Avg,",
+    "GPRINT:s_max:MAX:%.2lf Max,",
+    "GPRINT:s_avg:LAST:%.2lf Last\\l",
+    "LINE1:m_avg#0000ff: 5 min",
+    "GPRINT:m_min:MIN:%.2lf Min,",
+    "GPRINT:m_avg:AVERAGE:%.2lf Avg,",
+    "GPRINT:m_max:MAX:%.2lf Max,",
+    "GPRINT:m_avg:LAST:%.2lf Last\\l",
+    "LINE1:l_avg#ff0000:15 min",
+    "GPRINT:l_min:MIN:%.2lf Min,",
+    "GPRINT:l_avg:AVERAGE:%.2lf Avg,",
+    "GPRINT:l_max:MAX:%.2lf Max,",
+    "GPRINT:l_avg:LAST:%.2lf Last\\l"]);
+} # sub getRRDArgs
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
diff --git a/contrib/collection3/lib/Collectd/Graph/Type/PsCputime.pm b/contrib/collection3/lib/Collectd/Graph/Type/PsCputime.pm
new file mode 100644 (file)
index 0000000..b0a8f9a
--- /dev/null
@@ -0,0 +1,97 @@
+package Collectd::Graph::Type::PsCputime;
+
+# Copyright (C) 2008,2009  Florian octo Forster <octo at verplant.org>
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; only version 2 of the License is applicable.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+use strict;
+use warnings;
+use base ('Collectd::Graph::Type');
+
+use Collectd::Graph::Common (qw(ident_to_filename get_faded_color));
+
+return (1);
+
+sub getDataSources
+{
+  return ([qw(syst user)]);
+} # getDataSources
+
+sub new
+{
+  my $pkg = shift;
+  my $obj = Collectd::Graph::Type->new (@_);
+  $obj->{'data_sources'} = [qw(syst user)];
+  $obj->{'rrd_opts'} = ['-v', 'CPU time [s]', '-l', '0'];
+  $obj->{'rrd_title'} = 'CPU time used by {plugin_instance}';
+  $obj->{'rrd_format'} = '%5.1lf';
+  $obj->{'colors'} = [qw(00b000 ff0000)];
+
+  return (bless ($obj, $pkg));
+} # new
+
+sub getRRDArgs
+{
+  my $obj = shift;
+  my $index = shift;
+
+  my $ident = $obj->{'files'}[$index];
+  if (!$ident)
+  {
+    cluck ("Invalid index: $index");
+    return;
+  }
+  my $filename = ident_to_filename ($ident);
+  $filename =~ s#:#\\:#g;
+
+  my $faded_blue = get_faded_color ('0000ff');
+  my $faded_red = get_faded_color ('ff0000');
+
+  return (['-t', $obj->getTitle (), @{$obj->{'rrd_opts'}},
+    "DEF:user_min_raw=${filename}:user:MIN",
+    "DEF:user_avg_raw=${filename}:user:AVERAGE",
+    "DEF:user_max_raw=${filename}:user:MAX",
+    "DEF:syst_min_raw=${filename}:syst:MIN",
+    "DEF:syst_avg_raw=${filename}:syst:AVERAGE",
+    "DEF:syst_max_raw=${filename}:syst:MAX",
+    "CDEF:user_min=0.000001,user_min_raw,*",
+    "CDEF:user_avg=0.000001,user_avg_raw,*",
+    "CDEF:user_max=0.000001,user_max_raw,*",
+    "CDEF:syst_min=0.000001,syst_min_raw,*",
+    "CDEF:syst_avg=0.000001,syst_avg_raw,*",
+    "CDEF:syst_max=0.000001,syst_max_raw,*",
+    "VDEF:v_user_min=user_min,MINIMUM",
+    "VDEF:v_user_avg=user_avg,AVERAGE",
+    "VDEF:v_user_max=user_max,MAXIMUM",
+    "VDEF:v_user_lst=syst_avg,LAST",
+    "VDEF:v_syst_min=syst_min,MINIMUM",
+    "VDEF:v_syst_avg=syst_avg,AVERAGE",
+    "VDEF:v_syst_max=syst_max,MAXIMUM",
+    "VDEF:v_syst_lst=syst_avg,LAST",
+    "CDEF:user_stack=syst_avg,user_avg,+",
+    "AREA:user_stack#${faded_blue}",
+    'LINE1:user_stack#0000ff:User  ',
+    'GPRINT:v_user_min:%5.1lf%ss Min,',
+    'GPRINT:v_user_avg:%5.1lf%ss Avg,',
+    'GPRINT:v_user_max:%5.1lf%ss Max,',
+    'GPRINT:v_user_lst:%5.1lf%ss Last\l',
+    "AREA:syst_avg#${faded_red}",
+    'LINE1:syst_avg#ff0000:System',
+    'GPRINT:v_syst_min:%5.1lf%ss Min,',
+    'GPRINT:v_syst_avg:%5.1lf%ss Avg,',
+    'GPRINT:v_syst_max:%5.1lf%ss Max,',
+    'GPRINT:v_syst_lst:%5.1lf%ss Last\l']);
+} # getRRDArgs
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
diff --git a/contrib/collection3/lib/Collectd/Graph/Type/TableSize.pm b/contrib/collection3/lib/Collectd/Graph/Type/TableSize.pm
new file mode 100644 (file)
index 0000000..5ad8ae0
--- /dev/null
@@ -0,0 +1,236 @@
+package Collectd::Graph::Type::TableSize;
+
+# Copyright (C) 2008,2009  Florian octo Forster <octo at verplant.org>
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; only version 2 of the License is applicable.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+use strict;
+use warnings;
+use base ('Collectd::Graph::Type');
+
+use Collectd::Graph::Common (qw($ColorCanvas $ColorFullBlue $ColorHalfBlue
+  ident_to_filename sanitize_type_instance
+  get_random_color sort_idents_by_type_instance));
+
+use RRDs ();
+
+return (1);
+
+sub _get_last_value
+{
+  my $ident = shift;
+  my $value = undef;
+  my $filename = ident_to_filename ($ident);
+  my ($start ,$step ,$names ,$data) = RRDs::fetch ($filename, 'AVERAGE', '-s', '-3600');
+  if (my $errmsg = RRDs::error ())
+  {
+    print STDERR "RRDs::fetch ($filename) failed: $errmsg\n";
+    return;
+  }
+
+  for (@$data)
+  {
+    my $line = $_;
+
+    for (@$line)
+    {
+      my $ds = $_;
+
+      if (defined ($ds))
+      {
+       $value = $ds;
+      }
+    }
+  } # for (@$data)
+
+  return ($value);
+} # _get_last_value
+
+sub getLastValues
+{
+  my $obj = shift;
+  my $last_value = {};
+
+  if (exists ($obj->{'last_value'}))
+  {
+    return ($obj->{'last_value'});
+  }
+
+  for (@{$obj->{'files'}})
+  {
+    my $file = $_;
+
+    $last_value->{$file} = _get_last_value ($file);
+  }
+  $obj->{'last_value'} = $last_value;
+  return ($obj->{'last_value'});
+} # getLastValues
+
+sub _group_files
+{
+  my $obj = shift;
+  my $data = [];
+  my @files;
+  my $last_values;
+
+  $last_values = $obj->getLastValues ();
+
+  @files = sort
+  { 
+    if (!defined ($last_values->{$a}) && !defined ($last_values->{$b}))
+    {
+      return (0);
+    }
+    elsif (!defined ($last_values->{$a}))
+    {
+      return (1);
+    }
+    elsif (!defined ($last_values->{$b}))
+    {
+      return (-1);
+    }
+    else
+    {
+      return ($last_values->{$a} <=> $last_values->{$b});
+    }
+  } (@{$obj->{'files'}});
+
+  for (my $i = 0; $i < @files; $i++)
+  {
+    my $file = $files[$i];
+    my $j = int ($i / 10);
+
+    $data->[$j] ||= [];
+    push (@{$data->[$j]}, $file);
+  }
+
+  return ($data);
+} # _group_files
+
+sub getGraphsNum
+{
+  my $obj = shift;
+  my $group = _group_files ($obj);
+
+  return (0 + @$group);
+}
+
+sub getRRDArgs
+{
+  my $obj = shift;
+  my $index = shift;
+
+  my $group = _group_files ($obj);
+
+  my $rrd_opts = $obj->{'rrd_opts'} || [];
+  my $format = $obj->{'rrd_format'} || '%5.1lf';
+
+  my $idents = $group->[$index];
+  my $ds_name_len = 0;
+
+  my $ds = $obj->getDataSources ();
+  if (!$ds)
+  {
+    confess ("obj->getDataSources failed.");
+  }
+  if (@$ds != 1)
+  {
+    confess ("I can only work with RRD files that have "
+      . "exactly one data source!");
+  }
+  my $data_source = $ds->[0];
+
+  my $rrd_title = $obj->getTitle ($idents->[0]);
+
+  my $colors = $obj->{'rrd_colors'} || {};
+  my @ret = ('-t', $rrd_title, @$rrd_opts);
+
+  if (defined $obj->{'rrd_vertical'})
+  {
+    push (@ret, '-v', $obj->{'rrd_vertical'});
+  }
+
+  if ($obj->{'custom_order'})
+  {
+    sort_idents_by_type_instance ($idents, $obj->{'custom_order'});
+  }
+
+  $obj->{'ds_names'} ||= {};
+  my @names = map { $obj->{'ds_names'}{$_->{'type_instance'}} || $_->{'type_instance'} } (@$idents);
+
+  for (my $i = 0; $i < @$idents; $i++)
+  {
+    my $ident = $idents->[$i];
+    my $filename = ident_to_filename ($ident);
+
+    if ($ds_name_len < length ($names[$i]))
+    {
+      $ds_name_len = length ($names[$i]);
+    }
+    
+    # Escape colons _after_ the length has been checked.
+    $names[$i] =~ s/:/\\:/g;
+
+    push (@ret,
+      "DEF:min${i}=${filename}:${data_source}:MIN",
+      "DEF:avg${i}=${filename}:${data_source}:AVERAGE",
+      "DEF:max${i}=${filename}:${data_source}:MAX");
+  }
+
+  for (my $i = 0; $i < @$idents; $i++)
+  {
+    my $type_instance = $idents->[$i]{'type_instance'};
+    my $ds_name = sprintf ("%-*s", $ds_name_len, $names[$i]);
+    my $color = '000000';
+    if (exists $colors->{$type_instance})
+    {
+      $color = $colors->{$type_instance};
+    }
+    else
+    {
+      $color = get_random_color ();
+    }
+    push (@ret,
+      "LINE1:avg${i}#${color}:${ds_name}",
+      "GPRINT:min${i}:MIN:${format} Min,",
+      "GPRINT:avg${i}:AVERAGE:${format} Avg,",
+      "GPRINT:max${i}:MAX:${format} Max,",
+      "GPRINT:avg${i}:LAST:${format} Last\\l");
+  }
+
+  return (\@ret);
+}
+
+sub getGraphArgs
+{
+  my $obj = shift;
+  my $index = shift;
+
+  my $group = _group_files ($obj);
+  my $idents = $group->[$index];
+
+  my @args = ();
+  for (qw(hostname plugin plugin_instance type))
+  {
+    if (defined ($idents->[0]{$_}))
+    {
+      push (@args, $_ . '=' . $idents->[0]{$_});
+    }
+  }
+  push (@args, "index=$index");
+
+  return (join (';', @args));
+} # getGraphArgs
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
diff --git a/contrib/collection3/lib/Collectd/Graph/Type/Wirkleistung.pm b/contrib/collection3/lib/Collectd/Graph/Type/Wirkleistung.pm
new file mode 100644 (file)
index 0000000..6d5234f
--- /dev/null
@@ -0,0 +1,90 @@
+package Collectd::Graph::Type::Wirkleistung;
+
+# Copyright (C) 2009  Stefan Pfab <spfab at noris.net>
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; only version 2 of the License is applicable.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+use strict;
+use warnings;
+use base ('Collectd::Graph::Type');
+
+use Collectd::Graph::Common (qw(ident_to_filename get_faded_color));
+
+return (1);
+
+sub getDataSources
+{
+  return ([qw(wirkleistung)]);
+} # getDataSources
+
+sub new
+{
+  my $pkg = shift;
+  my $obj = Collectd::Graph::Type->new (@_);
+  $obj->{'data_sources'} = [qw(wirkleistung)];
+  $obj->{'rrd_opts'} = ['-v', 'Watt'];
+  $obj->{'rrd_title'} = 'Wirkleistung ({type_instance})';
+  $obj->{'rrd_format'} = '%5.1lf%s W';
+  $obj->{'colors'} = [qw(000000 f00000)];
+
+  return (bless ($obj, $pkg));
+} # new
+
+sub getRRDArgs
+{
+  my $obj = shift;
+  my $index = shift;
+
+  my $ident = $obj->{'files'}[$index];
+  if (!$ident)
+  {
+    cluck ("Invalid index: $index");
+    return;
+  }
+  my $filename = ident_to_filename ($ident);
+  $filename =~ s#:#\\:#g;
+
+  my $faded_green = get_faded_color ('00ff00');
+  my $faded_red = get_faded_color ('ff0000');
+
+  return (['-t', 'Wirkleistung (' . $ident->{'type_instance'} . ')', '-v', 'Watt', '-l', '0',
+    "DEF:min0=${filename}:kWh:MIN",
+    "DEF:avg0=${filename}:kWh:AVERAGE",
+    "DEF:max0=${filename}:kWh:MAX",
+    'AREA:max0#bfbfbf',
+    'AREA:min0#FFFFFF',
+    'CDEF:watt_avg0=avg0,36000,*,',
+    'CDEF:watt_min0=min0,36000,*,',
+    'CDEF:watt_max0=max0,36000,*,',
+    'CDEF:watt_total=avg0,10,*,',
+    'VDEF:total=watt_total,TOTAL',
+    'VDEF:first=watt_total,FIRST',
+    'VDEF:last=watt_total,LAST',
+    #'CDEF:first_value=first,POP',
+    #'CDEF:first_time=first,POP',
+    'LINE1:watt_avg0#000000:W',
+    'HRULE:190#ff0000',
+    'GPRINT:watt_min0:MIN:%4.1lfW Min,',
+    'GPRINT:watt_avg0:AVERAGE:%4.1lfW Avg,',
+    'GPRINT:watt_max0:MAX:%4.1lfW Max,',
+    'GPRINT:watt_avg0:LAST:%4.1lfW Last\l',
+    'GPRINT:total:%4.1lf%sWh Gesamtverbrauch im angezeigten Zeitraum\l',
+    'GPRINT:first:erster Wert %c:strftime',
+    'GPRINT:last:letzter Wert %c:strftime']);
+
+    # HRULE:190\ ff0000    
+
+} # getRRDArgs
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
diff --git a/contrib/collection3/lib/Collectd/Graph/TypeLoader.pm b/contrib/collection3/lib/Collectd/Graph/TypeLoader.pm
new file mode 100644 (file)
index 0000000..5a0b522
--- /dev/null
@@ -0,0 +1,270 @@
+package Collectd::Graph::TypeLoader;
+
+=head1 NAME
+
+Collectd::Graph::TypeLoader - Load a module according to the "type"
+
+=cut
+
+# Copyright (C) 2008,2009  Florian octo Forster <octo at verplant.org>
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; only version 2 of the License is applicable.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+use strict;
+use warnings;
+
+use Carp (qw(cluck confess));
+use Exporter ();
+use Config::General ('ParseConfig');
+use Collectd::Graph::Config ('gc_get_config');
+use Collectd::Graph::Type ();
+
+@Collectd::Graph::TypeLoader::ISA = ('Exporter');
+@Collectd::Graph::TypeLoader::EXPORT_OK = ('tl_load_type');
+
+our @ArrayMembers = (qw(data_sources rrd_opts custom_order));
+our @ScalarMembers = (qw(rrd_title rrd_format rrd_vertical scale ignore_unknown stacking));
+our @DSMappedMembers = (qw(ds_names rrd_colors));
+
+our %MemberToConfigMap =
+(
+  data_sources => 'datasources',
+  ds_names => 'dsname',
+  rrd_title => 'rrdtitle',
+  rrd_opts => 'rrdoptions',
+  rrd_format => 'rrdformat',
+  rrd_vertical => 'rrdverticallabel',
+  rrd_colors => 'color',
+  scale => 'scale', # GenericIO only
+  custom_order => 'order', # GenericStacked only
+  stacking => 'stacking', # GenericStacked only
+  ignore_unknown => 'ignoreunknown' # GenericStacked only
+);
+
+return (1);
+
+sub _create_object
+{
+  my $module = shift;
+  my $obj;
+
+  # Surpress warnings and error messages caused by the eval.
+  local $SIG{__WARN__} = sub { return (1); print STDERR "WARNING: " . join (', ', @_) . "\n"; };
+  local $SIG{__DIE__}  = sub { return (1); print STDERR "FATAL: "   . join (', ', @_) . "\n"; };
+
+  eval <<PERL;
+  require $module;
+  \$obj = ${module}->new ();
+PERL
+  if (!$obj)
+  {
+    return;
+  }
+
+  return ($obj);
+} # _create_object
+
+sub _load_module_from_config
+{
+  my $conf = shift;
+
+  my $module = $conf->{'module'};
+  my $obj;
+  
+  if ($module && !($module =~ m/::/))
+  {
+    $module = "Collectd::Graph::Type::$module";
+  }
+
+  if ($module)
+  {
+    $obj = _create_object ($module);
+    if (!$obj)
+    {
+      #cluck ("Creating an $module object failed");
+      warn ("Creating an $module object failed");
+      return;
+    }
+  }
+  else
+  {
+    $obj = Collectd::Graph::Type->new ();
+    if (!$obj)
+    {
+      cluck ("Creating an Collectd::Graph::Type object failed");
+      return;
+    }
+  }
+
+  for (@ScalarMembers) # {{{
+  {
+    my $member = $_;
+    my $key = $MemberToConfigMap{$member};
+    my $val;
+
+    if (!defined $conf->{$key})
+    {
+      next;
+    }
+    $val = $conf->{$key};
+    
+    if (ref ($val) ne '')
+    {
+      cluck ("Invalid value type for $key: " . ref ($val));
+      next;
+    }
+
+    $obj->{$member} = $val;
+  } # }}}
+
+  for (@ArrayMembers) # {{{
+  {
+    my $member = $_;
+    my $key = $MemberToConfigMap{$member};
+    my $val;
+
+    if (!defined $conf->{$key})
+    {
+      next;
+    }
+    $val = $conf->{$key};
+    
+    if (ref ($val) eq 'ARRAY')
+    {
+      $obj->{$member} = $val;
+    }
+    elsif (ref ($val) eq '')
+    {
+      $obj->{$member} = [split (' ', $val)];
+    }
+    else
+    {
+      cluck ("Invalid value type for $key: " . ref ($val));
+    }
+  } # }}}
+
+  for (@DSMappedMembers) # {{{
+  {
+    my $member = $_;
+    my $key = $MemberToConfigMap{$member};
+    my @val_list;
+
+    if (!defined $conf->{$key})
+    {
+      next;
+    }
+
+    if (ref ($conf->{$key}) eq 'ARRAY')
+    {
+      @val_list = @{$conf->{$key}};
+    }
+    elsif (ref ($conf->{$key}) eq '')
+    {
+      @val_list = ($conf->{$key});
+    }
+    else
+    {
+      cluck ("Invalid value type for $key: " . ref ($conf->{$key}));
+      next;
+    }
+
+    for (@val_list)
+    {
+      my $line = $_;
+      my $ds;
+      my $val;
+
+      if (!defined ($line) || (ref ($line) ne ''))
+      {
+        next;
+      }
+
+      ($ds, $val) = split (' ', $line, 2);
+      if (!$ds || !$val)
+      {
+        next;
+      }
+
+      $obj->{$member} ||= {};
+      $obj->{$member}{$ds} = $val;
+    } # for (@val_list)
+  } # }}} for (@DSMappedMembers)
+
+  return ($obj);
+} # _load_module_from_config
+
+sub _load_module_generic
+{
+  my $type = shift;
+  my $module = ucfirst (lc ($type));
+  my $obj;
+
+  $module =~ s/[^A-Za-z_]//g;
+  $module =~ s/_([A-Za-z])/\U$1\E/g;
+
+  $obj = _create_object ($module);
+  if (!$obj)
+  {
+    $obj = Collectd::Graph::Type->new ();
+    if (!$obj)
+    {
+      cluck ("Creating an Collectd::Graph::Type object failed");
+      return;
+    }
+  }
+
+  return ($obj);
+} # _load_module_generic
+
+=head1 EXPORTED FUNCTIONS
+
+=over 4
+
+=item B<tl_load_type> (I<$type>)
+
+Does whatever is necessary to get an object with which to graph RRD files of
+type I<$type>.
+
+=cut
+
+sub tl_load_type
+{
+  my $type = shift;
+  my $conf = gc_get_config ();
+
+  if (defined ($conf) && defined ($conf->{'type'}{$type}))
+  {
+    return (_load_module_from_config ($conf->{'type'}{$type}));
+  }
+  else
+  {
+    return (_load_module_generic ($type));
+  }
+} # tl_load_type
+
+=back
+
+=head1 SEE ALSO
+
+L<Collectd::Graph::Type::GenericStacked>
+
+=head1 AUTHOR AND LICENSE
+
+Copyright (c) 2008 by Florian Forster
+E<lt>octoE<nbsp>atE<nbsp>verplant.orgE<gt>. Licensed under the terms of the GNU
+General Public License, VersionE<nbsp>2 (GPLv2).
+
+=cut
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 et fdm=marker :
diff --git a/contrib/collection3/share/.htaccess b/contrib/collection3/share/.htaccess
new file mode 100644 (file)
index 0000000..e139ace
--- /dev/null
@@ -0,0 +1,2 @@
+Options -ExecCGI
+SetHandler none
diff --git a/contrib/collection3/share/navigate.js b/contrib/collection3/share/navigate.js
new file mode 100644 (file)
index 0000000..3bfe56e
--- /dev/null
@@ -0,0 +1,300 @@
+function nav_init (time_begin, time_end)
+{
+  var all_images;
+  var i;
+
+  all_images = document.getElementsByTagName ("img");
+  for (i = 0; i < all_images.length; i++)
+  {
+    if (all_images[i].className != "graph_image")
+      continue;
+
+    all_images[i].navTimeBegin = new Number (time_begin);
+    all_images[i].navTimeEnd   = new Number (time_end);
+
+    all_images[i].navBaseURL = all_images[i].src.replace (/;(begin|end)=[^;]*/g, '');
+
+    if (all_images[i].addEventListener) /* Mozilla */
+    {
+      all_images[i].addEventListener ('dblclick', nav_handle_dblclick,
+          false /* == bubbling */);
+      all_images[i].addEventListener ('DOMMouseScroll', nav_handle_wheel,
+          false /* == bubbling */);
+    }
+    else
+    {
+      all_images[i].ondblclick = nav_handle_dblclick;
+      all_images[i].onmousewheel = nav_handle_wheel;
+    }
+  }
+
+  return (true);
+} /* nav_init */
+
+function nav_image_repaint (img)
+{
+  if (!img || !img.navBaseURL
+      || !img.navTimeBegin || !img.navTimeEnd)
+    return;
+
+  img.src = img.navBaseURL + ";"
+    + "begin=" + img.navTimeBegin.toFixed (0) + ";"
+    + "end=" + img.navTimeEnd.toFixed (0);
+} /* nav_image_repaint */
+
+function nav_time_reset (img_id ,diff)
+{
+  var img;
+
+  img = document.getElementById (img_id);
+  if (!img)
+    return (false);
+
+  img.navTimeEnd = new Number ((new Date ()).getTime () / 1000);
+  img.navTimeBegin = new Number (img.navTimeEnd - diff);
+
+  nav_image_repaint (img);
+
+  return (true);
+}
+
+function nav_time_change_obj (img, factor_begin, factor_end)
+{
+  var diff;
+
+  if (!img || !img.navBaseURL
+      || !img.navTimeBegin || !img.navTimeEnd)
+    return (false);
+
+  diff = img.navTimeEnd - img.navTimeBegin;
+
+  /* Prevent zooming in if diff is less than five minutes */
+  if ((diff <= 300) && (factor_begin > 0.0) && (factor_end < 0.0))
+    return (true);
+
+  img.navTimeBegin += (diff * factor_begin);
+  img.navTimeEnd   += (diff * factor_end);
+
+  nav_image_repaint (img);
+
+  return (true);
+} /* nav_time_change */
+
+function nav_time_change (img_id, factor_begin, factor_end)
+{
+  var diff;
+
+  if (img_id == '*')
+  {
+    var all_images;
+    var i;
+
+    all_images = document.getElementsByTagName ("img");
+    for (i = 0; i < all_images.length; i++)
+    {
+      if (all_images[i].className != "graph_image")
+        continue;
+    
+      nav_time_change_obj (all_images[i], factor_begin, factor_end);
+    }
+  }
+  else
+  {
+    var img;
+
+    img = document.getElementById (img_id);
+    if (!img)
+      return (false);
+
+    nav_time_change_obj (img, factor_begin, factor_end);
+  }
+
+  return (true);
+} /* nav_time_change */
+
+function nav_move_earlier (img_id)
+{
+  return (nav_time_change (img_id, -0.2, -0.2));
+} /* nav_move_earlier */
+
+function nav_move_later (img_id)
+{
+  return (nav_time_change (img_id, +0.2, +0.2));
+} /* nav_move_later */
+
+function nav_zoom_in (img_id)
+{
+  return (nav_time_change (img_id, +0.2, -0.2));
+} /* nav_zoom_in */
+
+function nav_zoom_out (img_id)
+{
+  return (nav_time_change (img_id, (-1.0 / 3.0), (1.0 / 3.0)));
+} /* nav_zoom_in */
+
+function nav_set_reference (img_id)
+{
+  var img;
+  var all_images;
+  var tmp;
+  var i;
+
+  img = document.getElementById (img_id);
+  if (!img || (img.className != "graph_image")
+      || !img.navTimeBegin || !img.navTimeEnd)
+    return;
+
+  all_images = document.getElementsByTagName ("img");
+  for (i = 0; i < all_images.length; i++)
+  {
+    tmp = all_images[i];
+    if (!tmp || (tmp.className != "graph_image")
+        || !tmp.navTimeBegin || !tmp.navTimeEnd)
+      continue;
+
+    if (tmp.id == img_id)
+      continue;
+
+    tmp.navTimeBegin = img.navTimeBegin;
+    tmp.navTimeEnd = img.navTimeEnd;
+
+    nav_image_repaint (tmp);
+  }
+} /* nav_set_reference */
+
+/* 
+ * TODO: calculate the mouse position relative to the image in a cross-browser
+ * manner.
+ */
+function nav_calculate_offset_x (obj)
+{
+  var offset = 0;
+
+  if (!obj)
+    return (offset);
+
+  offset = obj.offsetLeft;
+  if (obj.offsetParent)
+    offset += nav_calculate_offset_x (obj.offsetParent);
+
+  return (offset);
+} /* nav_calculate_offset_x */
+
+function nav_calculate_event_x (e)
+{
+  var pos = 0;
+  var off = 0;
+
+  if (!e || !e.target)
+    return;
+  
+  off = nav_calculate_offset_x (e.target);
+
+  if (e.pageX || e.pageY)
+  {
+    pos = e.pageX;
+  }
+  else if (e.clientX || e.clientY)
+  {
+    pos = e.clientX + document.body.scrollLeft
+      + document.documentElement.scrollLeft;
+  }
+
+  return (pos);
+} /* nav_calculate_event_x */
+
+function nav_recenter (e)
+{
+  var x;
+  var y;
+  var img;
+  var diff;
+  var time_old_center;
+  var time_new_center;
+  var width;
+
+  img = e.target;
+  if (!img || (img.className != "graph_image")
+      || !img.navTimeBegin || !img.navTimeEnd)
+    return;
+
+  width = img.width - 97;
+
+  x = e.layerX - 70;
+  if (!x || (x < 0) || (x > width))
+    return;
+
+  y = e.layerY;
+  if (!y || (y < 35) || (y > 135))
+    return;
+
+  diff = img.navTimeEnd - img.navTimeBegin;
+
+  time_old_center = img.navTimeBegin + (diff / 2.0);
+  time_new_center = img.navTimeBegin + (x * diff / width);
+
+  img.navTimeBegin += (time_new_center - time_old_center);
+  img.navTimeEnd   += (time_new_center - time_old_center);
+} /* nav_recenter */
+
+function nav_handle_dblclick (e)
+{
+  var img;
+
+  /* M$IE */
+  if (!e)
+    e = window.event;
+
+  img = e.target;
+  if (!img || (img.className != "graph_image")
+      || !img.navTimeBegin || !img.navTimeEnd)
+    return;
+
+  nav_recenter (e);
+  nav_image_repaint (img);
+
+  // e.returnValue = false;
+} /* nav_handle_dblclick */
+
+/* Taken from <http://adomas.org/javascript-mouse-wheel/> */
+function nav_handle_wheel (e)
+{
+  var delta = 0;
+  var img;
+  
+  /* M$IE */
+  if (!e)
+    e = window.event;
+
+  img = e.target;
+  if (!img || (img.className != "graph_image")
+      || !img.navTimeBegin || !img.navTimeEnd)
+    return;
+
+  /* Opera and M$IE */
+  if (e.wheelDelta)
+  {
+    delta = e.wheelDelta; 
+    if (window.opera)
+      delta = delta * (-1);
+  }
+  else if (e.detail)
+  {
+    delta = e.detail * (-1);
+  }
+
+  if (!delta)
+    return;
+
+  nav_recenter (e);
+  if (delta > 0)
+    nav_zoom_in (img.id);
+  else
+    nav_zoom_out (img.id);
+
+  if (e.preventDefault)
+    e.preventDefault ();
+  e.returnValue = false;
+} /* function nav_handle_wheel */
+
+/* vim: set sw=2 sts=2 et : */
diff --git a/contrib/collection3/share/shortcut-icon.png b/contrib/collection3/share/shortcut-icon.png
new file mode 100644 (file)
index 0000000..6af57e5
Binary files /dev/null and b/contrib/collection3/share/shortcut-icon.png differ
diff --git a/contrib/collection3/share/style.css b/contrib/collection3/share/style.css
new file mode 100644 (file)
index 0000000..8c12951
--- /dev/null
@@ -0,0 +1,79 @@
+div.graph
+{
+}
+
+div.graph_canvas div.graph_float
+{
+  float: left;
+  position: relative;
+}
+
+div.graph_float div.controls
+{
+  display: none;
+  position: absolute;
+}
+
+div.graph_float:hover div.controls
+{
+  display: block;
+}
+
+div.graph_float div.controls.zoom
+{
+  right: 5px;
+  bottom: 10px;
+}
+
+div.graph_float div.controls.preset
+{
+  right: 5px;
+  top: 5px;
+}
+
+div.graph_float div.controls div
+{
+  display: block;
+
+  color: gray;
+  background: white;
+
+  text-decoration: none;
+  text-align: center;
+  font-size: small;
+
+  cursor: pointer;
+
+  border: 1px solid gray;
+  width: 1em;
+  height: 1em;
+  padding: 1px;
+  margin: 0px;
+}
+
+div.graph_float div.controls div:hover
+{
+  color: black;
+  border-color: black;
+}
+
+div.graph_float div.controls.preset div
+{
+  margin: 1px 0px 1px 0px;
+}
+
+div.graph_float div.controls.zoom div
+{
+  float: left;
+  margin: 0px 1px 0px 1px;
+}
+
+table
+{
+  border-collapse: collapse;
+}
+td, th
+{
+  border: 1px solid gray;
+}
+/* vim: set sw=2 sts=2 et : */
diff --git a/contrib/cussh.pl b/contrib/cussh.pl
new file mode 100755 (executable)
index 0000000..ae758d1
--- /dev/null
@@ -0,0 +1,512 @@
+#!/usr/bin/perl
+#
+# collectd - contrib/cussh.pl
+# Copyright (C) 2007-2009  Sebastian Harl
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; only version 2 of the License is applicable.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+#
+# Author:
+#   Sebastian Harl <sh at tokkee.org>
+#
+
+=head1 NAME
+
+cussh - collectd UNIX socket shell
+
+=head1 SYNOPSIS
+
+B<cussh> [I<E<lt>pathE<gt>>]
+
+=head1 DESCRIPTION
+
+B<collectd>'s unixsock plugin allows external programs to access the values it
+has collected or received and to submit own values. This is a little
+interactive frontend for this plugin.
+
+=head1 OPTIONS
+
+=over 4
+
+=item I<E<lt>pathE<gt>>
+
+The path to the UNIX socket provided by collectd's unixsock plugin. (Default:
+F</var/run/collectd-unixsock>)
+
+=back
+
+=cut
+
+use strict;
+use warnings;
+
+use Collectd::Unixsock();
+
+{ # main
+       my $path = $ARGV[0] || "/var/run/collectd-unixsock";
+       my $sock = Collectd::Unixsock->new($path);
+
+       my $cmds = {
+               HELP    => \&cmd_help,
+               PUTVAL  => \&putval,
+               GETVAL  => \&getval,
+               GETTHRESHOLD  => \&getthreshold,
+               FLUSH   => \&flush,
+               LISTVAL => \&listval,
+               PUTNOTIF => \&putnotif,
+       };
+
+       if (! $sock) {
+               print STDERR "Unable to connect to $path!\n";
+               exit 1;
+       }
+
+       print "cussh version 0.2, Copyright (C) 2007-2008 Sebastian Harl\n"
+               . "cussh comes with ABSOLUTELY NO WARRANTY. This is free software,\n"
+               . "and you are welcome to redistribute it under certain conditions.\n"
+               . "See the GNU General Public License 2 for more details.\n\n";
+
+       while (1) {
+               print "cussh> ";
+               my $line = <STDIN>;
+
+               last if (! $line);
+
+               chomp $line;
+
+               last if ($line =~ m/^quit$/i);
+
+               my ($cmd) = $line =~ m/^(\w+)\s*/;
+               $line = $';
+
+               next if (! $cmd);
+               $cmd = uc $cmd;
+
+               my $f = undef;
+               if (defined $cmds->{$cmd}) {
+                       $f = $cmds->{$cmd};
+               }
+               else {
+                       print STDERR "ERROR: Unknown command $cmd!\n";
+                       next;
+               }
+
+               if (! $f->($sock, $line)) {
+                       print STDERR "ERROR: Command failed!\n";
+                       next;
+               }
+       }
+
+       $sock->destroy();
+       exit 0;
+}
+
+sub tokenize {
+       my $line     = shift || return;
+       my $line_ptr = $line;
+       my @line     = ();
+
+       my $token_pattern = qr/[^"\s]+|"[^"]+"/;
+
+       while (my ($token) = $line_ptr =~ m/^($token_pattern)\s+/) {
+               $line_ptr = $';
+               push @line, $token;
+       }
+
+       if ($line_ptr =~ m/^$token_pattern$/) {
+               push @line, $line_ptr;
+       }
+       else {
+               my ($token) = split m/ /, $line_ptr, 1;
+               print STDERR "Failed to parse line: $line\n";
+               print STDERR "Parse error near token \"$token\".\n";
+               return;
+       }
+
+       foreach my $l (@line) {
+               if ($l =~ m/^"(.*)"$/) {
+                       $l = $1;
+               }
+       }
+       return @line;
+}
+
+sub getid {
+       my $string = shift || return;
+
+       my ($h, $p, $pi, $t, $ti) =
+               $string =~ m#^([^/]+)/([^/-]+)(?:-([^/]+))?/([^/-]+)(?:-([^/]+))?\s*#;
+       $string = $';
+
+       return if ((! $h) || (! $p) || (! $t));
+
+       my %id = ();
+
+       ($id{'host'}, $id{'plugin'}, $id{'type'}) = ($h, $p, $t);
+
+       $id{'plugin_instance'} = $pi if defined ($pi);
+       $id{'type_instance'} = $ti if defined ($ti);
+       return \%id;
+}
+
+sub putid {
+       my $ident = shift || return;
+
+       my $string;
+
+       $string = $ident->{'host'} . "/" . $ident->{'plugin'};
+
+       if (defined $ident->{'plugin_instance'}) {
+               $string .= "-" . $ident->{'plugin_instance'};
+       }
+
+       $string .= "/" . $ident->{'type'};
+
+       if (defined $ident->{'type_instance'}) {
+               $string .= "-" . $ident->{'type_instance'};
+       }
+       return $string;
+}
+
+=head1 COMMANDS
+
+=over 4
+
+=item B<HELP>
+
+=cut
+
+sub cmd_help {
+       my $sock = shift;
+       my $line = shift || '';
+
+       my @line = tokenize($line);
+       my $cmd = shift (@line);
+
+       my %text = (
+               help => <<HELP,
+Available commands:
+  HELP
+  PUTVAL
+  GETVAL
+  GETTHRESHOLD
+  FLUSH
+  LISTVAL
+  PUTNOTIF
+
+See the embedded Perldoc documentation for details. To do that, run:
+  perldoc $0
+HELP
+               putval => <<HELP,
+PUTVAL <id> <value0> [<value1> ...]
+
+Submits a value to the daemon.
+HELP
+               getval => <<HELP,
+GETVAL <id>
+
+Retrieves the current value or values from the daemon.
+HELP
+               flush => <<HELP,
+FLUSH [plugin=<plugin>] [timeout=<timeout>] [identifier=<id>] [...]
+
+Sends a FLUSH command to the daemon.
+HELP
+               listval => <<HELP,
+LISTVAL
+
+Prints a list of available values.
+HELP
+               putnotif => <<HELP
+PUTNOTIF severity=<severity> [...] message=<message>
+
+Sends a notifications message to the daemon.
+HELP
+       );
+
+       if (!$cmd)
+       {
+               $cmd = 'help';
+       }
+       if (!exists ($text{$cmd}))
+       {
+               print STDOUT "Unknown command: " . uc ($cmd) . "\n\n";
+               $cmd = 'help';
+       }
+
+       print STDOUT $text{$cmd};
+
+       return 1;
+} # cmd_help
+
+=item B<PUTVAL> I<Identifier> I<Valuelist>
+
+=cut
+
+sub putval {
+       my $sock = shift || return;
+       my $line = shift || return;
+
+       my @line = tokenize($line);
+
+       my $id;
+       my $ret;
+
+       if (! @line) {
+               return;
+       }
+
+       if (scalar(@line) < 2) {
+               print STDERR "Synopsis: PUTVAL <id> <value0> [<value1> ...]" . $/;
+               return;
+       }
+
+       $id = getid($line[0]);
+
+       if (! $id) {
+               print STDERR "Invalid id \"$line[0]\"." . $/;
+               return;
+       }
+
+       my ($time, @values) = split m/:/, $line;
+       $ret = $sock->putval(%$id, time => $time, values => \@values);
+
+       if (! $ret) {
+               print STDERR "socket error: " . $sock->{'error'} . $/;
+       }
+       return $ret;
+}
+
+=item B<GETVAL> I<Identifier>
+
+=cut
+
+sub getval {
+       my $sock = shift || return;
+       my $line = shift || return;
+
+       my @line = tokenize($line);
+
+       my $id;
+       my $vals;
+
+       if (! @line) {
+               return;
+       }
+
+       if (scalar(@line) < 1) {
+               print STDERR "Synopsis: GETVAL <id>" . $/;
+               return;
+       }
+
+       $id = getid($line[0]);
+
+       if (! $id) {
+               print STDERR "Invalid id \"$line[0]\"." . $/;
+               return;
+       }
+
+       $vals = $sock->getval(%$id);
+
+       if (! $vals) {
+               print STDERR "socket error: " . $sock->{'error'} . $/;
+               return;
+       }
+
+       foreach my $key (keys %$vals) {
+               print "\t$key: $vals->{$key}\n";
+       }
+       return 1;
+}
+
+=item B<GETTHRESHOLD> I<Identifier>
+
+=cut
+
+sub getthreshold {
+       my $sock = shift || return;
+       my $line = shift || return;
+
+       my @line = tokenize($line);
+
+       my $id;
+       my $vals;
+
+       if (! @line) {
+               return;
+       }
+
+       if (scalar(@line) < 1) {
+               print STDERR "Synopsis: GETTHRESHOLD <id>" . $/;
+               return;
+       }
+
+       $id = getid($line[0]);
+
+       if (! $id) {
+               print STDERR "Invalid id \"$line[0]\"." . $/;
+               return;
+       }
+
+       $vals = $sock->getthreshold(%$id);
+
+       if (! $vals) {
+               print STDERR "socket error: " . $sock->{'error'} . $/;
+               return;
+       }
+
+       foreach my $key (keys %$vals) {
+               print "\t$key: $vals->{$key}\n";
+       }
+       return 1;
+}
+
+=item B<FLUSH> [B<timeout>=I<$timeout>] [B<plugin>=I<$plugin>[ ...]]
+
+=cut
+
+sub flush {
+       my $sock = shift || return;
+       my $line = shift;
+
+       my @line = tokenize($line);
+
+       my $res;
+
+       if (! $line) {
+               $res = $sock->flush();
+       }
+       else {
+               my %args = ();
+
+               foreach my $i (@line) {
+                       my ($option, $value) = $i =~ m/^([^=]+)=(.+)$/;
+                       next if (! ($option && $value));
+
+                       if ($option eq "plugin") {
+                               push @{$args{"plugins"}}, $value;
+                       }
+                       elsif ($option eq "timeout") {
+                               $args{"timeout"} = $value;
+                       }
+                       elsif ($option eq "identifier") {
+                               my $id = getid ($value);
+                               if (!$id)
+                               {
+                                       print STDERR "Not a valid identifier: \"$value\"\n";
+                                       next;
+                               }
+                               push @{$args{"identifier"}}, $id;
+                       }
+                       else {
+                               print STDERR "Invalid option \"$option\".\n";
+                               return;
+                       }
+               }
+
+               $res = $sock->flush(%args);
+       }
+
+       if (! $res) {
+               print STDERR "socket error: " . $sock->{'error'} . $/;
+       }
+       return $res;
+}
+
+=item B<LISTVAL>
+
+=cut
+
+sub listval {
+       my $sock = shift || return;
+       my $line = shift;
+
+       my @res;
+
+       if ($line ne "") {
+               print STDERR "Synopsis: LISTVAL" . $/;
+               return;
+       }
+
+       @res = $sock->listval();
+
+       if (! @res) {
+               print STDERR "socket error: " . $sock->{'error'} . $/;
+               return;
+       }
+
+       foreach my $ident (@res) {
+               print $ident->{'time'} . " " . putid($ident) . $/;
+       }
+       return 1;
+}
+
+=item B<PUTNOTIF> [[B<severity>=I<$severity>] [B<message>=I<$message>] [ ...]]
+
+=cut
+
+sub putnotif {
+       my $sock = shift || return;
+       my $line = shift || return;
+
+       my @line = tokenize($line);
+
+       my $ret;
+
+       my (%values) = ();
+       foreach my $i (@line) {
+               my ($key, $val) = split m/=/, $i, 2;
+               if ($key && $val) {
+                       $values{$key} = $val;
+               }
+               else {
+                       $values{'message'} = defined($values{'message'})
+                               ? ($values{'message'} . ' ' . $key)
+                               : $key;
+               }
+       }
+       $values{'time'} ||= time();
+
+       $ret = $sock->putnotif(%values);
+       if (! $ret) {
+               print STDERR "socket error: " . $sock->{'error'} . $/;
+       }
+       return $ret;
+}
+
+=back
+
+These commands follow the exact same syntax as described in
+L<collectd-unixsock(5)>.
+
+=head1 SEE ALSO
+
+L<collectd(1)>, L<collectd-unisock(5)>
+
+=head1 AUTHOR
+
+Written by Sebastian Harl E<lt>sh@tokkee.orgE<gt>.
+
+B<collectd> has been written by Florian Forster and others.
+
+=head1 COPYRIGHT
+
+Copyright (C) 2007 Sebastian Harl.
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free Software
+Foundation; only version 2 of the License is applicable.
+
+=cut
+
+# vim: set sw=4 ts=4 tw=78 noexpandtab :
diff --git a/contrib/examples/MyPlugin.pm b/contrib/examples/MyPlugin.pm
new file mode 100644 (file)
index 0000000..b1a8a6d
--- /dev/null
@@ -0,0 +1,170 @@
+# /usr/share/doc/collectd/examples/MyPlugin.pm
+#
+# A Perl plugin template for collectd.
+#
+# Written by Sebastian Harl <sh@tokkee.org>
+#
+# This is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation; only version 2 of the License is applicable.
+
+# Notes:
+# - each of the functions below (and the corresponding plugin_register call)
+#   is optional
+
+package Collectd::Plugin::MyPlugin;
+
+use strict;
+use warnings;
+
+use Collectd qw( :all );
+
+# data set definition:
+# see section "DATA TYPES" in collectd-perl(5) for details
+#
+# NOTE: If you're defining a custom data-set, you have to make that known to
+# any servers as well. Else, the server is not able to store values using the
+# type defined by that data-set.
+# It is strongly recommended to use one of the types and data-sets pre-defined
+# in the types.db file.
+my $dataset =
+[
+       {
+               name => 'my_ds',
+               type => DS_TYPE_GAUGE,
+               min  => 0,
+               max  => 65535,
+       },
+];
+
+# This code is executed after loading the plugin to register it with collectd.
+plugin_register (TYPE_LOG, 'myplugin', 'my_log');
+plugin_register (TYPE_NOTIF, 'myplugin', 'my_notify');
+plugin_register (TYPE_DATASET, 'mytype', $dataset);
+plugin_register (TYPE_INIT, 'myplugin', 'my_init');
+plugin_register (TYPE_READ, 'myplugin', 'my_read');
+plugin_register (TYPE_WRITE, 'myplugin', 'my_write');
+plugin_register (TYPE_SHUTDOWN, 'myplugin', 'my_shutdown');
+
+# For each of the functions below see collectd-perl(5) for details about
+# arguments and the like.
+
+# This function is called once upon startup to initialize the plugin.
+sub my_init
+{
+       # open sockets, initialize data structures, ...
+
+       # A false return value indicates an error and causes the plugin to be
+       # disabled.
+       return 1;
+} # my_init ()
+
+# This function is called in regular intervals to collectd the data.
+sub my_read
+{
+       # value list to dispatch to collectd:
+       # see section "DATA TYPES" in collectd-perl(5) for details
+       my $vl = {};
+
+       # do the magic to read the data:
+       # the number of values has to match the number of data sources defined in
+       # the registered data set. The type used here (in this example:
+       # "mytype") must be defined in the types.db, see types.db(5) for
+       # details, or registered as "TYPE_DATASET".
+       $vl->{'values'} = [ rand(65535) ];
+       $vl->{'plugin'} = 'myplugin';
+       $vl->{'type'}   = 'mytype';
+       # any other elements are optional
+
+       # dispatch the values to collectd which passes them on to all registered
+       # write functions
+       plugin_dispatch_values ($vl);
+
+       # A false return value indicates an error and the plugin will be skipped
+       # for an increasing amount of time.
+       return 1;
+} # my_read ()
+
+# This function is called after values have been dispatched to collectd.
+sub my_write
+{
+       my $type = shift;
+       my $ds   = shift;
+       my $vl   = shift;
+
+       if (scalar (@$ds) != scalar (@{$vl->{'values'}})) {
+               plugin_log (LOG_WARNING, "DS number does not match values length");
+               return;
+       }
+
+       for (my $i = 0; $i < scalar (@$ds); ++$i) {
+               # do the magic to output the data
+               print "$vl->{'host'}: $vl->{'plugin'}: ";
+
+               if (defined $vl->{'plugin_instance'}) {
+                       print "$vl->{'plugin_instance'}: ";
+               }
+
+               print "$type: ";
+
+               if (defined $vl->{'type_instance'}) {
+                       print "$vl->{'type_instance'}: ";
+               }
+
+               print "$vl->{'values'}->[$i]\n";
+       }
+       return 1;
+} # my_write()
+
+# This function is called before shutting down collectd.
+sub my_shutdown
+{
+       # close sockets, ...
+       return 1;
+} # my_shutdown ()
+
+# This function is called when plugin_log () has been used.
+sub my_log
+{
+       my $level = shift;
+       my $msg   = shift;
+
+       print "LOG: $level - $msg\n";
+       return 1;
+} # my_log ()
+
+# This function is called when plugin_dispatch_notification () has been used
+sub my_notify
+{
+       my $notif = shift;
+
+       my ($sec, $min, $hour, $mday, $mon, $year) = localtime ($notif->{'time'});
+
+       printf "NOTIF (%04d-%02d-%02d %02d:%02d:%02d): %d - ",
+                       $year + 1900, $mon + 1, $mday, $hour, $min, $sec,
+                       $notif->{'severity'};
+
+       if (defined $notif->{'host'}) {
+               print "$notif->{'host'}: ";
+       }
+
+       if (defined $notif->{'plugin'}) {
+               print "$notif->{'plugin'}: ";
+       }
+
+       if (defined $notif->{'plugin_instance'}) {
+               print "$notif->{'plugin_instance'}: ";
+       }
+
+       if (defined $notif->{'type'}) {
+               print "$notif->{'type'}: ";
+       }
+
+       if (defined $notif->{'type_instance'}) {
+               print "$notif->{'type_instance'}: ";
+       }
+
+       print "$notif->{'message'}\n";
+       return 1;
+} # my_notify ()
+
diff --git a/contrib/examples/myplugin.c b/contrib/examples/myplugin.c
new file mode 100644 (file)
index 0000000..f68cc1a
--- /dev/null
@@ -0,0 +1,222 @@
+/*
+ * /usr/share/doc/collectd/examples/myplugin.c
+ *
+ * A plugin template for collectd.
+ *
+ * Written by Sebastian Harl <sh@tokkee.org>
+ *
+ * This is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation; only version 2 of the License is applicable.
+ */
+
+/*
+ * Notes:
+ * - plugins are executed in parallel, thus, thread-safe
+ *   functions need to be used
+ * - each of the functions below (except module_register)
+ *   is optional
+ */
+
+#if ! HAVE_CONFIG_H
+
+#include <stdlib.h>
+
+#include <string.h>
+
+#ifndef __USE_ISOC99 /* required for NAN */
+# define DISABLE_ISOC99 1
+# define __USE_ISOC99 1
+#endif /* !defined(__USE_ISOC99) */
+#include <math.h>
+#if DISABLE_ISOC99
+# undef DISABLE_ISOC99
+# undef __USE_ISOC99
+#endif /* DISABLE_ISOC99 */
+
+#include <time.h>
+
+#endif /* ! HAVE_CONFIG */
+
+#include <collectd/collectd.h>
+#include <collectd/common.h>
+#include <collectd/plugin.h>
+
+/*
+ * data source definition:
+ * - name of the data source
+ * - type of the data source (DS_TYPE_GAUGE, DS_TYPE_COUNTER)
+ * - minimum allowed value
+ * - maximum allowed value
+ */
+static data_source_t dsrc[1] =
+{
+       { "my_ds", DS_TYPE_GAUGE, 0, NAN }
+};
+
+/*
+ * data set definition:
+ * - name of the data set
+ * - number of data sources
+ * - list of data sources
+ *
+ * NOTE: If you're defining a custom data-set, you have to make that known to
+ * any servers as well. Else, the server is not able to store values using the
+ * type defined by that data-set.
+ * It is strongly recommended to use one of the types and data-sets
+ * pre-defined in the types.db file.
+ */
+static data_set_t ds =
+{
+       "myplugin", STATIC_ARRAY_SIZE (dsrc), dsrc
+};
+
+/*
+ * This function is called once upon startup to initialize the plugin.
+ */
+static int my_init (void)
+{
+       /* open sockets, initialize data structures, ... */
+
+       /* A return value != 0 indicates an error and causes the plugin to be
+          disabled. */
+    return 0;
+} /* static int my_init (void) */
+
+/*
+ * This function is called in regular intervalls to collect the data.
+ */
+static int my_read (void)
+{
+       value_t values[1]; /* the size of this list should equal the number of
+                                                 data sources */
+       value_list_t vl = VALUE_LIST_INIT;
+
+       /* do the magic to read the data */
+       values[0].gauge = random ();
+
+       vl.values     = values;
+       vl.values_len = 1;
+       vl.time       = time (NULL);
+       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+       sstrncpy (vl.plugin, "myplugin", sizeof (vl.plugin));
+       /* optionally set vl.plugin_instance and vl.type_instance to reasonable
+        * values (default: "") */
+
+       /* dispatch the values to collectd which passes them on to all registered
+        * write functions - the first argument is used to lookup the data set
+        * definition (it is strongly recommended to use a type defined in the
+        * types.db file) */
+       plugin_dispatch_values ("myplugin", &vl);
+
+       /* A return value != 0 indicates an error and the plugin will be skipped
+        * for an increasing amount of time. */
+    return 0;
+} /* static int my_read (void) */
+
+/*
+ * This function is called after values have been dispatched to collectd.
+ */
+static int my_write (const data_set_t *ds, const value_list_t *vl)
+{
+       char name[1024] = "";
+       int i = 0;
+
+       if (ds->ds_num != vl->values_len) {
+               plugin_log (LOG_WARNING, "DS number does not match values length");
+               return -1;
+       }
+
+       /* get the default base filename for the output file - depending on the
+        * provided values this will be something like
+        * <host>/<plugin>[-<plugin_type>]/<instance>[-<instance_type>] */
+       if (0 != format_name (name, 1024, vl->host, vl->plugin,
+                       vl->plugin_instance, ds->type, vl->type_instance))
+               return -1;
+
+       for (i = 0; i < ds->ds_num; ++i) {
+               /* do the magic to output the data */
+               printf ("%s (%s) at %i: ", name,
+                               (ds->ds->type == DS_TYPE_GAUGE) ? "GAUGE" : "COUNTER",
+                               (int)vl->time);
+
+               if (ds->ds->type == DS_TYPE_GAUGE)
+                       printf ("%f\n", vl->values[i].gauge);
+               else
+                       printf ("%lld\n", vl->values[i].counter);
+       }
+       return 0;
+} /* static int my_write (data_set_t *, value_list_t *) */
+
+/*
+ * This function is called when plugin_log () has been used.
+ */
+static void my_log (int severity, const char *msg)
+{
+       printf ("LOG: %i - %s\n", severity, msg);
+       return;
+} /* static void my_log (int, const char *) */
+
+/*
+ * This function is called when plugin_dispatch_notification () has been used.
+ */
+static int my_notify (const notification_t *notif)
+{
+       char time_str[32] = "";
+       struct tm *tm = NULL;
+
+       int n = 0;
+
+       if (NULL == (tm = localtime (&notif->time)))
+               time_str[0] = '\0';
+
+       n = strftime (time_str, 32, "%F %T", tm);
+       if (n >= 32) n = 31;
+       time_str[n] = '\0';
+
+       printf ("NOTIF (%s): %i - ", time_str, notif->severity);
+
+       if ('\0' != *notif->host)
+               printf ("%s: ", notif->host);
+
+       if ('\0' != *notif->plugin)
+               printf ("%s: ", notif->plugin);
+
+       if ('\0' != *notif->plugin_instance)
+               printf ("%s: ", notif->plugin_instance);
+
+       if ('\0' != *notif->type)
+               printf ("%s: ", notif->type);
+
+       if ('\0' != *notif->type_instance)
+               printf ("%s: ", notif->type_instance);
+
+       printf ("%s\n", notif->message);
+       return 0;
+} /* static int my_notify (notification_t *) */
+
+/*
+ * This function is called before shutting down collectd.
+ */
+static int my_shutdown (void)
+{
+       /* close sockets, free data structures, ... */
+       return 0;
+} /* static int my_shutdown (void) */
+
+/*
+ * This function is called after loading the plugin to register it with
+ * collectd.
+ */
+void module_register (void)
+{
+       plugin_register_log ("myplugin", my_log);
+       plugin_register_notification ("myplugin", my_notify);
+       plugin_register_data_set (&ds);
+       plugin_register_read ("myplugin", my_read);
+       plugin_register_init ("myplugin", my_init);
+       plugin_register_write ("myplugin", my_write);
+       plugin_register_shutdown ("myplugin", my_shutdown);
+    return;
+} /* void module_register (void) */
+
diff --git a/contrib/exec-munin.conf b/contrib/exec-munin.conf
new file mode 100644 (file)
index 0000000..d7c31a4
--- /dev/null
@@ -0,0 +1,6 @@
+AddType temperature temperature
+
+Interval 300
+
+Script /tmp/ipmisens2
+Script /tmp/munin-sensors.pl
diff --git a/contrib/exec-munin.px b/contrib/exec-munin.px
new file mode 100755 (executable)
index 0000000..3e62ce0
--- /dev/null
@@ -0,0 +1,272 @@
+#!/usr/bin/perl
+
+#
+# collectd - contrib/exec-munin.px
+# Copyright (C) 2007,2008  Florian Forster
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; only version 2 of the License is applicable.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+#
+# Authors:
+#   Florian octo Forster <octo at verplant.org>
+#
+
+use strict;
+use warnings;
+
+=head1 NAME
+
+exec-munin.px
+
+=head1 DESCRIPTION
+
+This script allows you to use plugins that were written for Munin with
+collectd's C<exec-plugin>. Since the data models of Munin and collectd are
+quite different rewriting the plugins should be preferred over using this
+transition layer. Having more than one "data source" for one "data set" doesn't
+work with this script, for example.
+
+=cut
+
+use Sys::Hostname ('hostname');
+use File::Basename ('basename');
+use Config::General ('ParseConfig');
+use Regexp::Common ('number');
+
+our $ConfigFile = '/etc/exec-munin.conf';
+our $TypeMap = {};
+our $Scripts = [];
+our $Interval = defined ($ENV{'COLLECTD_INTERVAL'}) ? (0 + $ENV{'COLLECTD_INTERVAL'}) : 300;
+our $Hostname = defined ($ENV{'COLLECTD_HOSTNAME'}) ? $ENV{'COLLECTD_HOSTNAME'} : '';
+
+main ();
+exit (0);
+
+# Configuration {{{
+
+=head1 CONFIGURATION
+
+This script reads it's configuration from F</etc/exec-munin.conf>. The
+configuration is read using C<Config::General> which understands a Apache-like
+config syntax, so it's very similar to the F<collectd.conf> syntax, too.
+
+Here's a short sample config:
+
+  AddType voltage-in in
+  AddType voltage-out out
+  Interval 300
+  Script /usr/lib/munin/plugins/nut
+
+The options have the following semantic (i.E<nbsp>e. meaning):
+
+=over 4
+
+=item B<AddType> I<to> I<from> [I<from> ...]
+
+collectd uses B<types> to specify how data is structured. In Munin all data is
+structured the same way, so some way of telling collectd how to handle the data
+is needed. This option translates the so called "field names" of Munin to the
+"types" of collectd. If more than one field are of the same type, e.E<nbsp>g.
+the C<nut> plugin above provides C<in> and C<out> which are both voltages, you
+can use a hyphen to add a "type instance" to the type.
+
+For a list of already defined "types" look at the F<types.db> file in
+collectd's shared data directory, e.E<nbsp>g. F</usr/share/collectd/>.
+
+=item B<Interval> I<Seconds>
+
+Sets the interval in which the plugins are executed. This doesn't need to match
+the interval setting of the collectd daemon. Usually, you want to execute the
+Munin plugins much less often, e.E<nbsp>g. every 300 seconds versus every 10
+seconds.
+
+=item B<Script> I<File>
+
+Adds a script to the list of scripts to be executed once per I<Interval>
+seconds.
+
+=back
+
+=cut
+
+sub handle_config_addtype
+{
+  my $list = shift;
+
+  for (my $i = 0; $i < @$list; $i++)
+  {
+    my ($to, @from) = split (' ', $list->[$i]);
+    for (my $j = 0; $j < @from; $j++)
+    {
+      $TypeMap->{$from[$j]} = $to;
+    }
+  }
+} # handle_config_addtype
+
+sub handle_config_script
+{
+  my $scripts = shift;
+
+  for (my $i = 0; $i < @$scripts; $i++)
+  {
+    my $script = $scripts->[$i];
+    if (!-e $script)
+    {
+      print STDERR "Script `$script' doesn't exist.\n";
+    }
+    elsif (!-x $script)
+    {
+      print STDERR "Script `$script' exists but is not executable.\n";
+    }
+    else
+    {
+      push (@$Scripts, $script);
+    }
+  } # for $i
+} # handle_config_script
+
+sub handle_config
+{
+  my $config = shift;
+
+  if (defined ($config->{'addtype'}))
+  {
+    if (ref ($config->{'addtype'}) eq 'ARRAY')
+    {
+      handle_config_addtype ($config->{'addtype'});
+    }
+    elsif (ref ($config->{'addtype'}) eq '')
+    {
+      handle_config_addtype ([$config->{'addtype'}]);
+    }
+    else
+    {
+      print STDERR "Cannot handle ref type '"
+      . ref ($config->{'addtype'}) . "' for option 'AddType'.\n";
+    }
+  }
+
+  if (defined ($config->{'script'}))
+  {
+    if (ref ($config->{'script'}) eq 'ARRAY')
+    {
+      handle_config_script ($config->{'script'});
+    }
+    elsif (ref ($config->{'script'}) eq '')
+    {
+      handle_config_script ([$config->{'script'}]);
+    }
+    else
+    {
+      print STDERR "Cannot handle ref type '"
+      . ref ($config->{'script'}) . "' for option 'Script'.\n";
+    }
+  }
+
+  if (defined ($config->{'interval'})
+    && (ref ($config->{'interval'}) eq ''))
+  {
+    my $num = int ($config->{'interval'});
+    if ($num > 0)
+    {
+      $Interval = $num;
+    }
+  }
+} # handle_config }}}
+
+sub execute_script
+{
+  my $fh;
+  my $pinst;
+  my $time = time ();
+  my $script = shift;
+  my $host = $Hostname || hostname () || 'localhost';
+  if (!open ($fh, '-|', $script))
+  {
+    print STDERR "Cannot execute $script: $!";
+    return;
+  }
+
+  $pinst = basename ($script);
+
+  while (my $line = <$fh>)
+  {
+    chomp ($line);
+    if ($line =~ m#^([^\.\-/]+)\.value\s+($RE{num}{real})#)
+    {
+      my $field = $1;
+      my $value = $2;
+      my $type = (defined ($TypeMap->{$field})) ? $TypeMap->{$field} : $field;
+      my $ident = "$host/munin-$pinst/$type";
+
+      $ident =~ s/"/\\"/g;
+
+      print qq(PUTVAL "$ident" interval=$Interval $time:$value\n);
+    }
+  }
+
+  close ($fh);
+} # execute_script
+
+sub main
+{
+  my $last_run;
+  my $next_run;
+
+  my %config = ParseConfig (-ConfigFile => $ConfigFile,
+    -AutoTrue => 1,
+    -LowerCaseNames => 1);
+  handle_config (\%config);
+
+  while (42)
+  {
+    $last_run = time ();
+    $next_run = $last_run + $Interval;
+
+    for (@$Scripts)
+    {
+      execute_script ($_);
+    }
+
+    while ((my $timeleft = ($next_run - time ())) > 0)
+    {
+      sleep ($timeleft);
+    }
+  }
+} # main
+
+=head1 REQUIREMENTS
+
+This script requires the following Perl modules to be installed:
+
+=over 4
+
+=item C<Config::General>
+
+=item C<Regexp::Common>
+
+=back
+
+=head1 SEE ALSO
+
+L<http://munin.projects.linpro.no/>,
+L<http://collectd.org/>,
+L<collectd-exec(5)>
+
+=head1 AUTHOR
+
+Florian octo Forster E<lt>octo at verplant.orgE<gt>
+
+=cut
+
+# vim: set sw=2 sts=2 ts=8 fdm=marker :
diff --git a/contrib/exec-nagios.conf b/contrib/exec-nagios.conf
new file mode 100644 (file)
index 0000000..26dd621
--- /dev/null
@@ -0,0 +1,13 @@
+# Run `perldoc exec-nagios.px' for details on this config file.
+
+Interval 300
+
+<Script /usr/lib/nagios/check_tcp>
+       Arguments -H alice -p 22
+       Type delay
+</Script>
+
+<Script /usr/lib/nagios/check_dns>
+       Arguments -H alice
+       Type delay
+</Script>
diff --git a/contrib/exec-nagios.px b/contrib/exec-nagios.px
new file mode 100755 (executable)
index 0000000..4b112f9
--- /dev/null
@@ -0,0 +1,413 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+=head1 NAME
+
+exec-nagios.px
+
+=head1 DESCRIPTION
+
+This script allows you to use plugins that were written for Nagios with
+collectd's C<exec-plugin>. If the plugin checks some kind of threshold, please
+consider configuring the threshold using collectd's own facilities instead of
+using this transition layer.
+
+=cut
+
+use Sys::Hostname ('hostname');
+use File::Basename ('basename');
+use Config::General ('ParseConfig');
+use Regexp::Common ('number');
+
+our $ConfigFile = '/etc/exec-nagios.conf';
+our $TypeMap = {};
+our $Scripts = [];
+our $Interval = defined ($ENV{'COLLECTD_INTERVAL'}) ? (0 + $ENV{'COLLECTD_INTERVAL'}) : 300;
+our $Hostname = defined ($ENV{'COLLECTD_HOSTNAME'}) ? $ENV{'COLLECTD_HOSTNAME'} : '';
+
+main ();
+exit (0);
+
+# Configuration
+# {{{
+
+=head1 CONFIGURATION
+
+This script reads it's configuration from F</etc/exec-nagios.conf>. The
+configuration is read using C<Config::General> which understands a Apache-like
+config syntax, so it's very similar to the F<collectd.conf> syntax, too.
+
+Here's a short sample config:
+
+  Interval 300
+  <Script /usr/lib/nagios/check_tcp>
+    Arguments -H alice -p 22
+    Type delay
+  </Script>
+  <Script /usr/lib/nagios/check_dns>
+    Arguments -H alice
+    Type delay
+  </Script>
+
+The options have the following semantic (i.E<nbsp>e. meaning):
+
+=over 4
+
+=item B<Interval> I<Seconds>
+
+Sets the interval in which the plugins are executed. This doesn't need to match
+the interval setting of the collectd daemon. Usually, you want to execute the
+Nagios plugins much less often, e.E<nbsp>g. every 300 seconds versus every 10
+seconds.
+
+=item E<lt>B<Script> I<File>E<gt>
+
+Adds a script to the list of scripts to be executed once per I<Interval>
+seconds. You can use the following optional arguments to specify the operation
+further:
+
+=over 4
+
+=item B<Arguments> I<Arguments>
+
+Pass the arguments I<Arguments> to the script. This is often needed with Nagios
+plugins, because much of the logic is implemented in the plugins, not in the
+daemon. If you need to specify a warning and/or critical range here, please
+consider using collectd's own threshold mechanism, which is by far the more
+elegant solution than this transition layer.
+
+=item B<Type> I<Type>
+
+If the plugin provides "performance data" the performance data is dispatched to
+collectd with this type. If no type is configured the data is ignored. Please
+note that this is limited to types that take exactly one value, such as the
+type C<delay> in the example above. If you need more complex performance data,
+rewrite the plugin as a collectd plugin (or at least port it do run directly
+with the C<exec-plugin>).
+
+=back
+
+=back
+
+=cut
+
+sub handle_config_addtype
+{
+  my $list = shift;
+
+  for (my $i = 0; $i < @$list; $i++)
+  {
+    my ($to, @from) = split (' ', $list->[$i]);
+    for (my $j = 0; $j < @from; $j++)
+    {
+      $TypeMap->{$from[$j]} = $to;
+    }
+  }
+} # handle_config_addtype
+
+sub handle_config_script
+{
+  my $scripts = shift;
+
+  for (keys %$scripts)
+  {
+    my $script = $_;
+    my $opts = $scripts->{$script};
+
+    if (!-e $script)
+    {
+      print STDERR "Script `$script' doesn't exist.\n";
+    }
+    elsif (!-x $script)
+    {
+      print STDERR "Script `$script' exists but is not executable.\n";
+    }
+    else
+    {
+      if (ref ($opts) eq 'ARRAY')
+        {
+          for (@$opts)
+            {
+              my $opt = $_;
+              $opt->{'script'} = $script;
+              push (@$Scripts, $opt);
+            }
+        }
+          else
+        {
+          $opts->{'script'} = $script;
+          push (@$Scripts, $opts);
+        }
+    }
+  } # for (keys %$scripts)
+} # handle_config_script
+
+sub handle_config
+{
+  my $config = shift;
+
+  if (defined ($config->{'addtype'}))
+  {
+    if (ref ($config->{'addtype'}) eq 'ARRAY')
+    {
+      handle_config_addtype ($config->{'addtype'});
+    }
+    elsif (ref ($config->{'addtype'}) eq '')
+    {
+      handle_config_addtype ([$config->{'addtype'}]);
+    }
+    else
+    {
+      print STDERR "Cannot handle ref type '"
+      . ref ($config->{'addtype'}) . "' for option 'AddType'.\n";
+    }
+  }
+
+  if (defined ($config->{'script'}))
+  {
+    if (ref ($config->{'script'}) eq 'HASH')
+    {
+      handle_config_script ($config->{'script'});
+    }
+    else
+    {
+      print STDERR "Cannot handle ref type '"
+      . ref ($config->{'script'}) . "' for option 'Script'.\n";
+    }
+  }
+
+  if (defined ($config->{'interval'})
+    && (ref ($config->{'interval'}) eq ''))
+  {
+    my $num = int ($config->{'interval'});
+    if ($num > 0)
+    {
+      $Interval = $num;
+    }
+  }
+} # handle_config }}}
+
+sub scale_value
+{
+  my $value = shift;
+  my $unit = shift;
+
+  if (!$unit)
+  {
+    return ($value);
+  }
+
+  if (($unit =~ m/^mb(yte)?$/i) || ($unit eq 'M'))
+  {
+    return ($value * 1000000);
+  }
+  elsif ($unit =~ m/^k(b(yte)?)?$/i)
+  {
+    return ($value * 1000);
+  }
+
+  return ($value);
+}
+
+sub sanitize_instance
+{
+  my $inst = shift;
+
+  if ($inst eq '/')
+  {
+    return ('root');
+  }
+
+  $inst =~ s/[^A-Za-z_-]/_/g;
+  $inst =~ s/__+/_/g;
+  $inst =~ s/^_//;
+  $inst =~ s/_$//;
+
+  return ($inst);
+}
+
+sub handle_performance_data
+{
+  my $host = shift;
+  my $plugin = shift;
+  my $pinst = shift;
+  my $type = shift;
+  my $time = shift;
+  my $line = shift;
+  my $ident = "$host/$plugin-$pinst/$type-$tinst";
+
+  my $tinst;
+  my $value;
+  my $unit;
+
+  if ($line =~ m/^([^=]+)=($RE{num}{real})([^;]*)/)
+  {
+    $tinst = sanitize_instance ($1);
+    $value = scale_value ($2, $3);
+  }
+  else
+  {
+    return;
+  }
+
+  $ident =~ s/"/\\"/g;
+
+  print qq(PUTVAL "$ident" interval=$Interval ${time}:$value\n);
+}
+
+sub execute_script
+{
+  my $fh;
+  my $pinst;
+  my $time = time ();
+  my $script = shift;
+  my @args = ();
+  my $host = $Hostname || hostname () || 'localhost';
+
+  my $state = 0;
+  my $serviceoutput;
+  my @serviceperfdata;
+  my @longserviceoutput;
+
+  my $script_name = $script->{'script'};
+  
+  if ($script->{'arguments'})
+  {
+    @args = split (' ', $script->{'arguments'});
+  }
+
+  if (!open ($fh, '-|', $script_name, @args))
+  {
+    print STDERR "Cannot execute $script_name: $!";
+    return;
+  }
+
+  $pinst = sanitize_instance (basename ($script_name));
+
+  # Parse the output of the plugin. The format is seriously fucked up, because
+  # it got extended way beyond what it could handle.
+  while (my $line = <$fh>)
+  {
+    chomp ($line);
+
+    if ($state == 0)
+    {
+      my $perfdata;
+      ($serviceoutput, $perfdata) = split (m/\s*\|\s*/, $line, 2);
+      
+      if ($perfdata)
+      {
+        push (@serviceperfdata, split (' ', $perfdata));
+      }
+
+      $state = 1;
+    }
+    elsif ($state == 1)
+    {
+      my $longoutput;
+      my $perfdata;
+      ($longoutput, $perfdata) = split (m/\s*\|\s*/, $line, 2);
+
+      push (@longserviceoutput, $longoutput);
+
+      if ($perfdata)
+      {
+        push (@serviceperfdata, split (' ', $perfdata));
+        $state = 2;
+      }
+    }
+    else # ($state == 2)
+    {
+      push (@serviceperfdata, split (' ', $line));
+    }
+  }
+
+  close ($fh);
+  # Save the exit status of the check in $state
+  $state = $?;
+
+  if ($state == 0)
+  {
+    $state = 'okay';
+  }
+  elsif ($state == 1)
+  {
+    $state = 'warning';
+  }
+  else
+  {
+    $state = 'failure';
+  }
+
+  {
+    my $type = $script->{'type'} || 'nagios_check';
+
+    print "PUTNOTIF time=$time severity=$state host=$host plugin=nagios "
+    . "plugin_instance=$pinst type=$type message=$serviceoutput\n";
+  }
+
+  if ($script->{'type'})
+  {
+    for (@serviceperfdata)
+    {
+      handle_performance_data ($host, 'nagios', $pinst, $script->{'type'},
+        $time, $_);
+    }
+  }
+} # execute_script
+
+sub main
+{
+  my $last_run;
+  my $next_run;
+
+  my %config = ParseConfig (-ConfigFile => $ConfigFile,
+    -AutoTrue => 1,
+    -LowerCaseNames => 1);
+  handle_config (\%config);
+
+  while (42)
+  {
+    $last_run = time ();
+    $next_run = $last_run + $Interval;
+
+    for (@$Scripts)
+    {
+      execute_script ($_);
+    }
+
+    while ((my $timeleft = ($next_run - time ())) > 0)
+    {
+      sleep ($timeleft);
+    }
+  }
+} # main
+
+=head1 REQUIREMENTS
+
+This script requires the following Perl modules to be installed:
+
+=over 4
+
+=item C<Config::General>
+
+=item C<Regexp::Common>
+
+=back
+
+=head1 SEE ALSO
+
+L<http://www.nagios.org/>,
+L<http://nagiosplugins.org/>,
+L<http://collectd.org/>,
+L<collectd-exec(5)>
+
+=head1 AUTHOR
+
+Florian octo Forster E<lt>octo at verplant.orgE<gt>
+
+=cut
+
+# vim: set sw=2 sts=2 ts=8 fdm=marker :
diff --git a/contrib/exec-smartctl b/contrib/exec-smartctl
new file mode 100755 (executable)
index 0000000..99b6986
--- /dev/null
@@ -0,0 +1,46 @@
+#!/bin/bash
+
+# Sample script for the exec plugin (collectd-exec(5))
+#
+# This script uses smartctl(8) to read HDD temperatures. The drives are
+# attached to a 3ware RAID controller which hddtempd can't handle.
+# Unfortunately the smartmontools don't have a library so we can't write a
+# C-plugin, at least not easily.
+# Please note that only root can read the SMART attributes from harddrives,
+# because special ``capabilities'' are necessary. However, the exec plugin will
+# refuse to run scripts as root, which is why `sudo' is used here for
+# fine-grained root privileges for the user `smart'. This isn't as straigt
+# forward as one might hope, but we think that the gained security is worth it.
+
+# The sudo configuration looks something like this:
+# -- 8< --
+# Cmnd_Alias      SMARTCTL = /usr/sbin/smartctl -d 3ware\,0 -A /dev/twe0, /usr/sbin/smartctl -d 3ware\,1 -A /dev/twe0, /usr/sbin/smartctl -d ata -A /dev/sda
+# smart   ALL = (root) NOPASSWD: SMARTCTL
+# -- >8 --
+
+HOSTNAME="${COLLECTD_HOSTNAME:-`hostname -f`}"
+INTERVAL="${COLLECTD_INTERVAL:-60}"
+
+while sleep "$INTERVAL"
+do
+       TEMP=$((sudo smartctl -d 3ware,0 -A /dev/twe0 | grep Temperature_Celsius | awk '{ print $10; }') 2>/dev/null);
+       if [ $? -ne 0 ]
+       then
+               TEMP="U"
+       fi
+       echo "PUTVAL $HOSTNAME/exec-smart/temperature-3ware_0 interval=$INTERVAL N:$TEMP"
+
+       TEMP=$((sudo smartctl -d 3ware,1 -A /dev/twe0 | grep Temperature_Celsius | awk '{ print $10; }') 2>/dev/null);
+       if [ $? -ne 0 ]
+       then
+               TEMP="U"
+       fi
+       echo "PUTVAL $HOSTNAME/exec-smart/temperature-3ware_1 interval=$INTERVAL N:$TEMP"
+
+       TEMP=$((sudo smartctl -d ata -A /dev/sda | grep Temperature_Celsius | awk '{ print $10; }') 2>/dev/null);
+       if [ $? -ne 0 ]
+       then
+               TEMP="U"
+       fi
+       echo "PUTVAL $HOSTNAME/exec-smart/temperature-sata_0 interval=$INTERVAL N:$TEMP"
+done
diff --git a/contrib/fedora/collectd.spec b/contrib/fedora/collectd.spec
new file mode 100644 (file)
index 0000000..a35923c
--- /dev/null
@@ -0,0 +1,376 @@
+Summary:       Statistics collection daemon for filling RRD files.
+Name:           collectd
+Version:       4.2.0
+Release:       1.fc6
+Source:                http://collectd.org/files/%{name}-%{version}.tar.gz
+License:       GPL
+Group:         System Environment/Daemons
+BuildRoot:     %{_tmppath}/%{name}-%{version}-root
+BuildPrereq:   lm_sensors-devel
+BuildPrereq:   mysql-devel
+BuildPrereq:   rrdtool-devel
+BuildPrereq:   net-snmp-devel
+Requires:      rrdtool
+Requires:      perl-Regexp-Common
+Packager:      Florian octo Forster <octo@verplant.org>
+Vendor:                Florian octo Forster <octo@verplant.org>
+
+%description
+collectd is a small daemon written in C for performance.  It reads various
+system  statistics  and updates  RRD files,  creating  them if neccessary.
+Since the daemon doesn't need to startup every time it wants to update the
+files it's very fast and easy on the system. Also, the statistics are very
+fine grained since the files are updated every 10 seconds.
+
+%package apache
+Summary:       apache-plugin for collectd.
+Group:         System Environment/Daemons
+Requires:      collectd = %{version}, curl
+%description apache
+This plugin collectd data provided by Apache's `mod_status'.
+
+%package email
+Summary:       email-plugin for collectd.
+Group:         System Environment/Daemons
+Requires:      collectd = %{version}, spamassassin
+%description email
+This plugin collectd data provided by spamassassin.
+
+%package mysql
+Summary:       mysql-module for collectd.
+Group:         System Environment/Daemons
+Requires:      collectd = %{version}, mysql
+%description mysql
+MySQL  querying  plugin.  This plugins  provides data of  issued commands,
+called handlers and database traffic.
+
+%package sensors
+Summary:       libsensors-module for collectd.
+Group:         System Environment/Daemons
+Requires:      collectd = %{version}, lm_sensors
+%description sensors
+This  plugin  for  collectd  provides  querying  of sensors  supported  by
+lm_sensors.
+
+%prep
+rm -rf $RPM_BUILD_ROOT
+%setup
+
+%build
+./configure --prefix=%{_prefix} --sbindir=%{_sbindir} --mandir=%{_mandir} --libdir=%{_libdir} --sysconfdir=%{_sysconfdir}
+make
+
+%install
+make install DESTDIR=$RPM_BUILD_ROOT
+mkdir -p $RPM_BUILD_ROOT/etc/rc.d/init.d
+mkdir -p $RPM_BUILD_ROOT/var/www/cgi-bin
+cp src/collectd.conf $RPM_BUILD_ROOT/etc/collectd.conf
+cp contrib/fedora/init.d-collectd $RPM_BUILD_ROOT/etc/rc.d/init.d/collectd
+cp contrib/collection.cgi $RPM_BUILD_ROOT/var/www/cgi-bin
+cp contrib/collection.conf $RPM_BUILD_ROOT/etc/collection.conf
+mkdir -p $RPM_BUILD_ROOT/var/lib/collectd
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%post
+/sbin/chkconfig --add collectd
+/sbin/chkconfig collectd on
+
+%preun
+if [ "$1" = 0 ]; then
+   /sbin/chkconfig collectd off
+   /etc/init.d/collectd stop
+   /sbin/chkconfig --del collectd
+fi
+exit 0
+
+%postun
+if [ "$1" -ge 1 ]; then
+    /etc/init.d/collectd restart
+fi
+exit 0
+
+%files
+%defattr(-,root,root)
+%doc AUTHORS COPYING ChangeLog INSTALL NEWS README
+%attr(0644,root,root) %config(noreplace) /etc/collectd.conf
+%attr(0644,root,root) %config(noreplace) /etc/collection.conf
+%attr(0755,root,root) /etc/rc.d/init.d/collectd
+%attr(0755,root,root) /var/www/cgi-bin/collection.cgi
+%attr(0755,root,root) %{_sbindir}/collectd
+%attr(0755,root,root) %{_bindir}/collectd-nagios
+%attr(0644,root,root) %{_mandir}/man1/*
+%attr(0644,root,root) %{_mandir}/man5/*
+
+%attr(0644,root,root) /usr/lib/perl5/5.8.8/i386-linux-thread-multi/perllocal.pod
+%attr(0644,root,root) /usr/lib/perl5/site_perl/5.8.8/Collectd.pm
+%attr(0644,root,root) /usr/lib/perl5/site_perl/5.8.8/Collectd/Unixsock.pm
+%attr(0644,root,root) /usr/lib/perl5/site_perl/5.8.8/i386-linux-thread-multi/auto/Collectd/.packlist
+%attr(0644,root,root) %{_mandir}/man3/Collectd::Unixsock.3pm.gz
+
+%attr(0644,root,root) %{_libdir}/%{name}/apcups.so*
+%attr(0644,root,root) %{_libdir}/%{name}/apcups.la
+
+# FIXME!!!
+#%attr(0644,root,root) %{_libdir}/%{name}/apple_sensors.so*
+#%attr(0644,root,root) %{_libdir}/%{name}/apple_sensors.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/battery.so*
+%attr(0644,root,root) %{_libdir}/%{name}/battery.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/conntrack.so*
+%attr(0644,root,root) %{_libdir}/%{name}/conntrack.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/cpufreq.so*
+%attr(0644,root,root) %{_libdir}/%{name}/cpufreq.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/cpu.so*
+%attr(0644,root,root) %{_libdir}/%{name}/cpu.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/csv.so*
+%attr(0644,root,root) %{_libdir}/%{name}/csv.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/df.so*
+%attr(0644,root,root) %{_libdir}/%{name}/df.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/disk.so*
+%attr(0644,root,root) %{_libdir}/%{name}/disk.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/dns.so*
+%attr(0644,root,root) %{_libdir}/%{name}/dns.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/entropy.so*
+%attr(0644,root,root) %{_libdir}/%{name}/entropy.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/exec.so*
+%attr(0644,root,root) %{_libdir}/%{name}/exec.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/hddtemp.so*
+%attr(0644,root,root) %{_libdir}/%{name}/hddtemp.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/interface.so*
+%attr(0644,root,root) %{_libdir}/%{name}/interface.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/iptables.so*
+%attr(0644,root,root) %{_libdir}/%{name}/iptables.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/irq.so*
+%attr(0644,root,root) %{_libdir}/%{name}/irq.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/load.so*
+%attr(0644,root,root) %{_libdir}/%{name}/load.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/logfile.so*
+%attr(0644,root,root) %{_libdir}/%{name}/logfile.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/mbmon.so*
+%attr(0644,root,root) %{_libdir}/%{name}/mbmon.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/memcached.so*
+%attr(0644,root,root) %{_libdir}/%{name}/memcached.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/memory.so*
+%attr(0644,root,root) %{_libdir}/%{name}/memory.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/multimeter.so*
+%attr(0644,root,root) %{_libdir}/%{name}/multimeter.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/network.so*
+%attr(0644,root,root) %{_libdir}/%{name}/network.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/nfs.so*
+%attr(0644,root,root) %{_libdir}/%{name}/nfs.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/nginx.so*
+%attr(0644,root,root) %{_libdir}/%{name}/nginx.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/ntpd.so*
+%attr(0644,root,root) %{_libdir}/%{name}/ntpd.la
+
+# FIXME!!!
+#%attr(0644,root,root) %{_libdir}/%{name}/nut.so*
+#%attr(0644,root,root) %{_libdir}/%{name}/nut.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/perl.so*
+%attr(0644,root,root) %{_libdir}/%{name}/perl.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/ping.so*
+%attr(0644,root,root) %{_libdir}/%{name}/ping.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/processes.so*
+%attr(0644,root,root) %{_libdir}/%{name}/processes.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/rrdtool.so*
+%attr(0644,root,root) %{_libdir}/%{name}/rrdtool.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/serial.so*
+%attr(0644,root,root) %{_libdir}/%{name}/serial.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/swap.so*
+%attr(0644,root,root) %{_libdir}/%{name}/swap.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/snmp.so*
+%attr(0644,root,root) %{_libdir}/%{name}/snmp.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/syslog.so*
+%attr(0644,root,root) %{_libdir}/%{name}/syslog.la
+
+# FIXME!!!
+#%attr(0644,root,root) %{_libdir}/%{name}/tape.so*
+#%attr(0644,root,root) %{_libdir}/%{name}/tape.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/tcpconns.so*
+%attr(0644,root,root) %{_libdir}/%{name}/tcpconns.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/unixsock.so*
+%attr(0644,root,root) %{_libdir}/%{name}/unixsock.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/users.so*
+%attr(0644,root,root) %{_libdir}/%{name}/users.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/vserver.so*
+%attr(0644,root,root) %{_libdir}/%{name}/vserver.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/wireless.so*
+%attr(0644,root,root) %{_libdir}/%{name}/wireless.la
+
+%attr(0644,root,root) %{_datadir}/%{name}/types.db
+
+%dir /var/lib/collectd
+
+%files apache
+%attr(0644,root,root) %{_libdir}/%{name}/apache.so*
+%attr(0644,root,root) %{_libdir}/%{name}/apache.la
+
+%files email
+%attr(0644,root,root) %{_libdir}/%{name}/email.so*
+%attr(0644,root,root) %{_libdir}/%{name}/email.la
+
+%files mysql
+%attr(0644,root,root) %{_libdir}/%{name}/mysql.so*
+%attr(0644,root,root) %{_libdir}/%{name}/mysql.la
+
+%files sensors
+%attr(0644,root,root) %{_libdir}/%{name}/sensors.so*
+%attr(0644,root,root) %{_libdir}/%{name}/sensors.la
+
+%changelog
+* Wed Oct 31 2007 Iain Lea <iain@bricbrac.de> 4.2.0
+- New major release
+- Changes to support 4.2.0 (ie. contrib/collection.conf)
+
+* Mon Aug 06 2007 Kjell Randa <Kjell.Randa@broadpark.no> 4.0.6
+- New upstream version
+
+* Wed Jul 25 2007 Kjell Randa <Kjell.Randa@broadpark.no> 4.0.5
+- New major release
+- Changes to support 4.0.5 
+
+* Wed Jan 11 2007 Iain Lea <iain@bricbrac.de> 3.11.0-0
+- fixed spec file to build correctly on fedora core
+- added improved init.d script to work with chkconfig
+- added %post and %postun to call chkconfig automatically
+
+* Sun Jul 09 2006 Florian octo Forster <octo@verplant.org> 3.10.0-1
+- New upstream version
+
+* Tue Jun 25 2006 Florian octo Forster <octo@verplant.org> 3.9.4-1
+- New upstream version
+
+* Tue Jun 01 2006 Florian octo Forster <octo@verplant.org> 3.9.3-1
+- New upstream version
+
+* Tue May 09 2006 Florian octo Forster <octo@verplant.org> 3.9.2-1
+- New upstream version
+
+* Tue May 09 2006 Florian octo Forster <octo@verplant.org> 3.8.5-1
+- New upstream version
+
+* Fri Apr 21 2006 Florian octo Forster <octo@verplant.org> 3.9.1-1
+- New upstream version
+
+* Fri Apr 14 2006 Florian octo Forster <octo@verplant.org> 3.9.0-1
+- New upstream version
+- Added the `apache' package.
+
+* Thu Mar 14 2006 Florian octo Forster <octo@verplant.org> 3.8.2-1
+- New upstream version
+
+* Thu Mar 13 2006 Florian octo Forster <octo@verplant.org> 3.8.1-1
+- New upstream version
+
+* Thu Mar 09 2006 Florian octo Forster <octo@verplant.org> 3.8.0-1
+- New upstream version
+
+* Sat Feb 18 2006 Florian octo Forster <octo@verplant.org> 3.7.2-1
+- Include `tape.so' so the build doesn't terminate because of missing files..
+- New upstream version
+
+* Sat Feb 04 2006 Florian octo Forster <octo@verplant.org> 3.7.1-1
+- New upstream version
+
+* Mon Jan 30 2006 Florian octo Forster <octo@verplant.org> 3.7.0-1
+- New upstream version
+- Removed the extra `hddtemp' package
+
+* Tue Jan 24 2006 Florian octo Forster <octo@verplant.org> 3.6.2-1
+- New upstream version
+
+* Fri Jan 20 2006 Florian octo Forster <octo@verplant.org> 3.6.1-1
+- New upstream version
+
+* Fri Jan 20 2006 Florian octo Forster <octo@verplant.org> 3.6.0-1
+- New upstream version
+- Added config file, `collectd.conf(5)', `df.so'
+- Added package `collectd-mysql', dependency on `mysqlclient10 | mysql'
+
+* Wed Dec 07 2005 Florian octo Forster <octo@verplant.org> 3.5.0-1
+- New upstream version
+
+* Sat Nov 26 2005 Florian octo Forster <octo@verplant.org> 3.4.0-1
+- New upstream version
+
+* Sat Nov 05 2005 Florian octo Forster <octo@verplant.org> 3.3.0-1
+- New upstream version
+
+* Tue Oct 26 2005 Florian octo Forster <octo@verplant.org> 3.2.0-1
+- New upstream version
+- Added statement to remove the `*.la' files. This fixes a problem when
+  `Unpackaged files terminate build' is in effect.
+- Added `processes.so*' to the main package
+
+* Fri Oct 14 2005 Florian octo Forster <octo@verplant.org> 3.1.0-1
+- New upstream version
+- Added package `collectd-hddtemp'
+
+* Fri Sep 30 2005 Florian octo Forster <octo@verplant.org> 3.0.0-1
+- New upstream version
+- Split the package into `collectd' and `collectd-sensors'
+
+* Fri Sep 16 2005 Florian octo Forster <octo@verplant.org> 2.1.0-1
+- New upstream version
+
+* Mon Sep 10 2005 Florian octo Forster <octo@verplant.org> 2.0.0-1
+- New upstream version
+
+* Mon Aug 29 2005 Florian octo Forster <octo@verplant.org> 1.8.0-1
+- New upstream version
+
+* Sun Aug 25 2005 Florian octo Forster <octo@verplant.org> 1.7.0-1
+- New upstream version
+
+* Sun Aug 21 2005 Florian octo Forster <octo@verplant.org> 1.6.0-1
+- New upstream version
+
+* Sun Jul 17 2005 Florian octo Forster <octo@verplant.org> 1.5.1-1
+- New upstream version
+
+* Sun Jul 17 2005 Florian octo Forster <octo@verplant.org> 1.5-1
+- New upstream version
+
+* Mon Jul 11 2005 Florian octo Forster <octo@verplant.org> 1.4.2-1
+- New upstream version
+
+* Sat Jul 09 2005 Florian octo Forster <octo@verplant.org> 1.4-1
+- Built on RedHat 7.3
diff --git a/contrib/fedora/init.d-collectd b/contrib/fedora/init.d-collectd
new file mode 100644 (file)
index 0000000..ea8662a
--- /dev/null
@@ -0,0 +1,66 @@
+#!/bin/bash
+#
+# collectd    Startup script for the Collectd statistics gathering daemon
+# chkconfig: - 86 15
+# description: Collectd is a statistics gathering daemon used to collect \
+#   system information ie. cpu, memory, disk, network
+# processname: collectd
+# config: /etc/collectd.conf
+# config: /etc/sysconfig/collectd
+# pidfile: /var/run/collectd.pid
+
+# Source function library.
+. /etc/init.d/functions
+
+RETVAL=0
+ARGS=""
+prog="collectd"
+CONFIG=/etc/collectd.conf
+
+if [ -r /etc/default/$prog ]; then
+       . /etc/default/$prog
+fi
+
+start () {
+       echo -n $"Starting $prog: "
+       if [ -r "$CONFIG" ]
+       then
+               daemon /usr/sbin/collectd -C "$CONFIG"
+               RETVAL=$?
+               echo
+               [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$prog
+       fi
+}
+stop () {
+       echo -n $"Stopping $prog: "
+       killproc $prog
+       RETVAL=$?
+       echo
+       [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$prog
+}
+# See how we were called.
+case "$1" in
+  start)
+       start
+       ;;
+  stop)
+       stop
+       ;;
+  status)
+       status $prog
+       ;;
+  restart|reload)
+       stop
+       start
+       ;;
+  condrestart)
+       [ -f /var/lock/subsys/$prog ] && stop && start || :
+       ;;
+  *)
+       echo $"Usage: $0 {start|stop|status|restart|reload|condrestart}"
+       exit 1
+esac
+
+exit $?
+
+# vim:syntax=sh
diff --git a/contrib/iptables/accounting.sh b/contrib/iptables/accounting.sh
new file mode 100755 (executable)
index 0000000..a9a3be9
--- /dev/null
@@ -0,0 +1,29 @@
+#!/bin/bash
+#Simple script that sets up some chains in mangle table to do global logging of all 
+#traffic going in and out of an interface
+#Could also use the regular input/output tree but this also catches the forwarded nat traffic
+
+IT="iptables -t mangle"
+
+#First clear the old stuff
+$IT -F incoming
+$IT -F outgoing
+$IT -N incoming
+$IT -N outgoing
+
+$IT -D PREROUTING -i eth0 -j incoming
+$IT -D POSTROUTING -o eth0 -j outgoing
+
+#should add some arg == stop exit here...
+
+$IT -A PREROUTING -i eth0 -j incoming
+$IT -A POSTROUTING -o eth0 -j outgoing
+
+$IT -A incoming -p tcp -m comment --comment "tcp"
+$IT -A incoming -p udp -m comment --comment "udp"
+$IT -A incoming -p icmp -m comment --comment "icmp"
+
+$IT -A outgoing -p tcp -m comment --comment "tcp"
+$IT -A outgoing -p udp -m comment --comment "udp"
+$IT -A outgoing -p icmp -m comment --comment "icmp"
+
diff --git a/contrib/migrate-3-4.px b/contrib/migrate-3-4.px
new file mode 100755 (executable)
index 0000000..ed19a7b
--- /dev/null
@@ -0,0 +1,387 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Getopt::Long ('GetOptions');
+use Data::Dumper ();
+use File::Basename ('dirname');
+
+our $InDir = '/var/lib/collectd';
+our $OutDir = '/tmp/collectd-4';
+our $Hostname = 'localhost';
+
+# Types:
+# +------------+----------------------+----+----+----+
+# ! Subdir     ! Type                 ! ti ! pi ! ex !
+# +------------+----------------------+----+----+----+
+# ! apache     ! apache_bytes         !    !    !    !
+# ! apache     ! apache_requests      !    !    !    !
+# ! apache     ! apache_scoreboard    ! x  !    !    !
+# ! battery    ! charge               !    ! x  !    !
+# ! apcups     ! charge_percent       !    !    !    !
+# !            ! cpu                  ! x  !    ! x  !
+# !            ! cpufreq              ! x  !    !    !
+# ! battery    ! current              !    ! x  !    !
+# ! ntpd       ! delay                ! x  !    !    !
+# !            ! df                   ! x  !    !    !
+# !            ! disk                 ! x  !    !    !
+# ! dns        ! dns_traffic          !    !    !    !
+# ! apple_se.. ! fanspeed             ! x  !    !    !
+# ! mbmon      ! fanspeed             ! x  !    !    !
+# ! apcups     ! frequency            ! x  !    !    !
+# ! ntpd       ! frequency_offset     ! x  !    !    !
+# !            ! hddtemp              ! x  !    !    !
+# ! interface  ! if_errors            !    ! x  !    !
+# ! interface  ! if_packets           !    ! x  !    !
+# !            ! lm_sensors           !    !    !    !
+# !            ! load                 !    !    !    !
+# ! apcups     ! load_percent         !    !    !    !
+# !            ! memory               !    !    !    !
+# !            ! multimeter           !    !    !    !
+# ! mysql      ! mysql_commands       ! x  !    !    !
+# ! mysql      ! mysql_handler        ! x  !    !    !
+# ! mysql      ! mysql_qcache         !    !    !    !
+# ! mysql      ! mysql_threads        !    !    !    !
+# !            ! nfs2_procedures      ! x  !    ! x  !
+# !            ! nfs3_procedures      ! x  !    ! x  !
+# ! dns        ! opcode               ! x  !    !    !
+# !            ! partition            ! x  !    !    !
+# !            ! ping                 ! x  !    !    !
+# !            ! processes            !    !    !    !
+# ! processes  ! ps_count             ! x  !    !    !
+# ! processes  ! ps_cputime           ! x  !    !    !
+# ! processes  ! ps_pagefaults        ! x  !    !    !
+# ! processes  ! ps_rss               ! x  !    !    !
+# ! dns        ! qtype                ! x  !    !    !
+# ! dns        ! rcode                ! x  !    !    !
+# ! (*)        ! sensors              ! x  !    !    !
+# !            ! serial               ! x  !    !    !
+# !            ! swap                 !    !    !    !
+# !            ! tape                 ! x  !    !    !
+# ! apple_se.. ! temperature          ! x  !    !    !
+# ! mbmon      ! temperature          ! x  !    !    !
+# ! ntpd       ! time_dispersion      ! x  !    !    !
+# ! ntpd       ! time_offset          ! x  !    !    !
+# ! apcups     ! timeleft             !    !    !    !
+# !            ! traffic              ! x  !    !    ! ->rx,tx
+# ! vserver    ! traffic              ! x  ! x  !    ! ->rx.tx
+# !            ! users                !    !    !    !
+# ! apucups    ! voltage              ! x  !    !    !
+# ! battery    ! voltage              !    ! x  !    !
+# ! mbmon      ! voltage              ! x  !    !    !
+# ! vserver    ! vs_memory            !    ! x  !    !
+# ! vserver    ! vs_processes         !    ! x  !    !
+# ! vserver    ! vs_threads           !    ! x  !    !
+# !            ! wireless             ! x  !    !    !
+# +------------+----------------------+----+----+----+
+
+our %Subdirs =
+(
+       apache => 0,
+       apcups => 0,
+       apple_sensors => 0,
+       battery => 1,
+       dns => 0,
+       interface => 1,
+       mbmon => 0,
+       mysql => 0,
+       ntpd => 0,
+       processes => 0,
+       sensors => 1,
+       vserver => 1
+);
+
+our %TypeTranslate =
+(
+       cpu => sub { $_ = shift; $_->{'plugin_instance'} = $_->{'type_instance'}; $_->{'type_instance'} = undef; $_; },
+       hddtemp => sub { $_ = shift; $_->{'plugin'} = 'hddtemp'; $_->{'type'} = 'temperature'; $_->{'type_instance'} = $_->{'type_instance'}; $_; },
+       if_errors => sub { $_ = shift; $_->{'type_instance'} = $_->{'plugin_instance'}; $_->{'plugin_instance'} = undef; $_; },
+       if_packets => sub { $_ = shift; $_->{'type_instance'} = $_->{'plugin_instance'}; $_->{'plugin_instance'} = undef; $_; },
+       nfs2_procedures => sub { $_ = shift; @$_{qw(plugin plugin_instance type type_instance)} = ('nfs', 'v2' . $_->{'type_instance'}, 'nfs_procedure', undef); $_; },
+       nfs3_procedures => sub { $_ = shift; @$_{qw(plugin plugin_instance type type_instance)} = ('nfs', 'v3' . $_->{'type_instance'}, 'nfs_procedure', undef); $_; },
+       partition => sub { $_ = shift; $_->{'plugin'} = 'disk'; $_; },
+       processes => sub { $_ = shift; $_->{'type'} = 'ps_state'; $_; },
+       traffic => sub { $_ = shift; $_->{'plugin'} =~ s/^traffic$/interface/; @$_{qw(plugin_instance type)} = (undef, 'if_octets'); $_; }
+);
+
+our %TypeSplit =
+(
+       cpu => { from => [qw(user nice syst idle wait)], to => 'value', type_instance => [qw(user nice system idle wait)] },
+       memory => { from => [qw(used free buffers cached)], to => 'value', type_instance => [qw(used free buffered cached)] },
+       nfs3_procedures => { from => [qw(null getattr lookup access readlink
+               read write create mkdir symlink mknod remove rmdir rename link
+               readdir readdirplus fsstat fsinfo pathconf commit)], to => 'value' },
+       nfs2_procedures => { from => [qw(create fsstat getattr link lookup
+               mkdir null read readdir readlink remove rename rmdir root
+               setattr symlink wrcache write)], to => 'value' },
+       processes => { from => [qw(running sleeping zombies stopped paging blocked)], to => 'value' },
+       swap => { from => [qw(cached free used resv)], to => 'value', type_instance => [qw(cached free used reserved)] }
+);
+
+our %TypeRename =
+(
+       traffic => { from => [qw(incoming outgoing)], to => [qw(rx tx)] },
+       vs_processes => { from => [qw(total)], to => [qw(value)] },
+);
+
+GetOptions ("indir|i=s" => \$InDir,
+       "outdir|o=s" => \$OutDir,
+       "hostname=s" => \$Hostname) or exit_usage ();
+
+die "No such directory: $InDir" if (!-d $InDir);
+
+our @Files = ();
+our %OutDirs = ();
+
+@Files = find_files ();
+
+for (@Files)
+{
+       my $orig_filename = $_;
+       my $orig = parse_file ($orig_filename);
+       my $dest = translate_file ($orig);
+       my $dest_filename = get_filename ($dest);
+
+       my $dest_directory = dirname ($dest_filename);
+       if (!exists ($OutDirs{$dest_directory}))
+       {
+               print "[ -d '$OutDir/$dest_directory' ] || mkdir -p '$OutDir/$dest_directory'\n";
+               $OutDirs{$dest_directory} = 1;
+       }
+
+       if (($orig->{'type'} eq 'disk') || ($orig->{'type'} eq 'partition'))
+       {
+               special_disk ($orig_filename, $orig, $dest_filename, $dest);
+       }
+       elsif (exists ($TypeSplit{$orig->{'type'}}))
+       {
+               my $src_dses = $TypeSplit{$orig->{'type'}}->{'from'};
+               my $dst_ds = $TypeSplit{$orig->{'type'}}->{'to'};
+               my $type_instances = exists ($TypeSplit{$orig->{'type'}}->{'type_instance'})
+                       ? $TypeSplit{$orig->{'type'}}->{'type_instance'}
+                       : $TypeSplit{$orig->{'type'}}->{'from'};
+
+               for (my $i = 0; $i < @$src_dses; $i++)
+               {
+                       my $src_ds = $src_dses->[$i];
+                       $dest->{'type_instance'} = $type_instances->[$i];
+                       $dest_filename = get_filename ($dest);
+                       print "./rrd_filter.px -i '$InDir/$orig_filename' -m '${src_ds}:${dst_ds}' -o '$OutDir/$dest_filename'\n";
+               }
+       }
+       else
+       {
+               print "cp '$InDir/$orig_filename' '$OutDir/$dest_filename'\n";
+       }
+
+       if (exists ($TypeRename{$orig->{'type'}}))
+       {
+               my $src_dses = $TypeRename{$orig->{'type'}}->{'from'};
+               my $dst_dses = $TypeRename{$orig->{'type'}}->{'to'};
+
+               print "rrdtool tune '$OutDir/$dest_filename'";
+               for (my $i = 0; $i < @$src_dses; $i++)
+               {
+                       print " --data-source-rename "
+                               . $src_dses->[$i] . ':' . $dst_dses->[$i];
+               }
+               print "\n";
+       }
+}
+
+exit (0);
+
+sub translate_file
+{
+       my $orig = shift;
+       my $dest = {};
+       %$dest = %$orig;
+
+       if (defined ($TypeTranslate{$orig->{'type'}}))
+       {
+               $TypeTranslate{$orig->{'type'}}->($dest);
+       }
+
+       return ($dest);
+} # translate_file
+
+sub get_filename
+{
+       my $args = shift;
+       my $filename = $args->{'host'}
+       . '/' . $args->{'plugin'} . (defined ($args->{'plugin_instance'}) ? '-'.$args->{'plugin_instance'} : '')
+       . '/' . $args->{'type'} . (defined ($args->{'type_instance'}) ? '-'.$args->{'type_instance'} : '') . '.rrd';
+
+       return ($filename);
+}
+
+sub parse_file
+{
+       my $fullname = shift;
+       my @parts = split ('/', $fullname);
+
+       my $filename;
+
+       my $host;
+       my $plugin;
+       my $plugin_instance;
+       my $type;
+       my $type_instance;
+
+       $filename = pop (@parts);
+
+       if ($filename =~ m/^([^-]+)(?:-(.*))?\.rrd$/)
+       {
+               $type = $1;
+               $type_instance = $2;
+       }
+       else
+       {
+               return;
+       }
+
+       if (@parts)
+       {
+               my $dirname = pop (@parts);
+               my $regex_str = join ('|', keys (%Subdirs));
+               if ($dirname =~ m/^($regex_str)(?:-(.*))?$/)
+               {
+                       $plugin = $1;
+                       $plugin_instance = $2;
+               }
+               else
+               {
+                       push (@parts, $dirname);
+               }
+       }
+       if (!$plugin)
+       {
+               $plugin = $type;
+       }
+
+       if (@parts)
+       {
+               $host = pop (@parts);
+       }
+       else
+       {
+               $host = $Hostname;
+       }
+
+       return
+       ({
+               host => $host,
+               plugin => $plugin,
+               plugin_instance => $plugin_instance,
+               type => $type,
+               type_instance => $type_instance
+       });
+} # parse_file
+
+sub find_files
+{
+       my $reldir = @_ ? shift : '';
+       my $absdir = $InDir . ($reldir ? "/$reldir" : '');
+
+       my $dh;
+
+       my @files = ();
+       my @dirs = ();
+
+       opendir ($dh, $absdir) or die ("opendir ($absdir): $!");
+       while (my $file = readdir ($dh))
+       {
+               next if ($file =~ m/^\./);
+               next if (-l "$absdir/$file");
+               if (-d "$absdir/$file")
+               {
+                       push (@dirs, ($reldir ? "$reldir/" : '') . $file);
+               }
+               elsif ($file =~ m/\.rrd$/)
+               {
+                       push (@files, ($reldir ? "$reldir/" : '') . $file);
+               }
+       }
+       closedir ($dh);
+
+       for (my $i = 0; $i < @dirs; $i++)
+       {
+               push (@files, find_files ($dirs[$i]));
+       }
+
+       return (@files);
+} # find_files
+
+{my $cache;
+sub _special_disk_instance
+{
+       my $orig_instance = shift;
+
+       if (!defined ($cache))
+       {
+               my $fh;
+               open ($fh, "< /proc/diskstats") or die ("open (/proc/diststats): $!");
+
+               $cache = {};
+               while (my $line = <$fh>)
+               {
+                       chomp ($line);
+                       my @fields = split (' ', $line);
+                       $cache->{$fields[0] . '-' . $fields[1]} = $fields[2];
+               }
+               close ($fh);
+       }
+
+       return (defined ($cache->{$orig_instance})
+               ? $cache->{$orig_instance}
+               : $orig_instance);
+}}
+
+sub special_disk
+{
+       my $orig_filename = shift;
+       my $orig = shift;
+       my $dest_filename = shift;
+       my $dest = shift;
+       my $dest_directory;
+
+       $dest->{'type_instance'} = undef;
+       $dest->{'plugin_instance'} = _special_disk_instance ($orig->{'type_instance'});
+       if ($dest->{'plugin_instance'} eq $orig->{'type_instance'})
+       {
+               print qq(echo "You may need to rename these files" >&2\n);
+       }
+
+       $dest->{'type'} = 'disk_merged';
+       $dest_filename = get_filename ($dest);
+
+       $dest_directory = dirname ($dest_filename);
+       if (!exists ($OutDirs{$dest_directory}))
+       {
+               print "[ -d '$OutDir/$dest_directory' ] || mkdir -p '$OutDir/$dest_directory'\n";
+               $OutDirs{$dest_directory} = 1;
+       }
+
+       print "./rrd_filter.px -i '$InDir/$orig_filename' -m 'rmerged:read' -m 'wmerged:write' -o '$OutDir/$dest_filename'\n";
+
+       $dest->{'type'} = 'disk_octets';
+       $dest_filename = get_filename ($dest);
+       print "./rrd_filter.px -i '$InDir/$orig_filename' -m 'rbytes:read' -m 'wbytes:write' -o '$OutDir/$dest_filename'\n";
+
+       $dest->{'type'} = 'disk_ops';
+       $dest_filename = get_filename ($dest);
+       print "./rrd_filter.px -i '$InDir/$orig_filename' -m 'rcount:read' -m 'wcount:write' -o '$OutDir/$dest_filename'\n";
+
+       $dest->{'type'} = 'disk_time';
+       $dest_filename = get_filename ($dest);
+       print "./rrd_filter.px -i '$InDir/$orig_filename' -m 'rtime:read' -m 'wtime:write' -o '$OutDir/$dest_filename'\n";
+}
+
+sub exit_usage
+{
+       print <<EOF;
+Usage: $0 [-i indir] [-o outdir] [--hostname myhostname]
+EOF
+       exit (1);
+}
diff --git a/contrib/migrate-4-5.px b/contrib/migrate-4-5.px
new file mode 100755 (executable)
index 0000000..d3ff796
--- /dev/null
@@ -0,0 +1,241 @@
+#!/usr/bin/perl
+
+# collectd - contrib/migrate-4-5.px
+# Copyright (C) 2010  Florian Forster
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+#
+# Authors:
+#   Florian Forster <octo at collectd.org>
+
+use strict;
+use warnings;
+
+use Getopt::Long ('GetOptions');
+use Data::Dumper ();
+use File::Basename ('dirname');
+
+our $InDir = '/var/lib/collectd';
+our $RRDtool = 'rrdtool';
+
+our %TypesCounterToDerive = # {{{
+(
+  apache_bytes => ["count"],
+  apache_requests => ["count"],
+  arc_counts => ["demand_data", "demand_metadata", "prefetch_data", "prefetch_metadata"],
+  arc_l2_bytes => ["read", "write"],
+  ath_stat => ["value"],
+  compression => ["uncompressed", "compressed"],
+  connections => ["value"],
+  cpu => ["value"],
+  current => ["value"],
+  disk_merged => ["read", "write"],
+  disk_octets => ["read", "write"],
+  disk_ops => ["read", "write"],
+  disk_ops_complex => ["value"],
+  disk_time => ["read", "write"],
+  dns_answer => ["value"],
+  dns_notify => ["value"],
+  dns_octets => ["queries", "responses"],
+  dns_opcode => ["value"],
+  dns_qtype => ["value"],
+  dns_query => ["value"],
+  dns_question => ["value"],
+  dns_rcode => ["value"],
+  dns_reject => ["value"],
+  dns_request => ["value"],
+  dns_resolver => ["value"],
+  dns_response => ["value"],
+  dns_transfer => ["value"],
+  dns_update => ["value"],
+  dns_zops => ["value"],
+  fscache_stat => ["value"],
+  fork_rate => ["value"],
+  http_request_methods => ["count"],
+  http_requests => ["count"],
+  http_response_codes => ["count"],
+  if_collisions => ["value"],
+  if_dropped => ["rx", "tx"],
+  if_errors => ["rx", "tx"],
+  if_multicast => ["value"],
+  if_octets => ["rx", "tx"],
+  if_packets => ["rx", "tx"],
+  if_rx_errors => ["value"],
+  if_tx_errors => ["value"],
+  io_octets => ["rx", "tx"],
+  io_packets => ["rx", "tx"],
+  ipt_bytes => ["value"],
+  ipt_packets => ["value"],
+  irq => ["value"],
+  memcached_command => ["value"],
+  memcached_octets => ["rx", "tx"],
+  memcached_ops => ["value"],
+  mysql_commands => ["value"],
+  mysql_handler => ["value"],
+  mysql_locks => ["value"],
+  mysql_log_position => ["value"],
+  mysql_octets => ["rx", "tx"],
+  nfs_procedure => ["value"],
+  nginx_requests => ["value"],
+  node_octets => ["rx", "tx"],
+  node_stat => ["value"],
+  operations => ["value"],
+  pg_blks => ["value"],
+  pg_n_tup_c => ["value"],
+  pg_scan => ["value"],
+  pg_xact => ["value"],
+  protocol_counter => ["value"],
+  ps_cputime => ["user", "syst"],
+  ps_pagefaults => ["minflt", "majflt"],
+  ps_code => ["value"],
+  ps_data => ["value"],
+  serial_octets => ["rx", "tx"],
+  swap_io => ["value"],
+  virt_cpu_total => ["ns"],
+  virt_vcpu => ["ns"],
+  vmpage_action => ["value"],
+  vmpage_faults => ["minflt", "majflt"],
+  vmpage_io => ["in", "out"],
+); # }}} %TypesCounterToDerive
+
+our %TypesRenameDataSource = # {{{
+(
+  absolute => "count",
+  apache_bytes => "count",
+  apache_connections => "count",
+  apache_idle_workers => "count",
+  apache_requests => "count",
+  apache_scoreboard => "count",
+  conntrack => "entropy",
+  contextswitch => "contextswitches",
+  delay => "seconds",
+  entropy => "entropy",
+  file_size => "bytes",
+  frequency => "frequency",
+  frequency_offset => "ppm",
+  http_request_methods => "count",
+  http_requests => "count",
+  http_response_codes => "count",
+  percent => "percent",
+  ping => "ping",
+  records => "count",
+  time_dispersion => "seconds",
+  timeleft => "timeleft",
+  time_offset => "seconds",
+  users => "users",
+  virt_cpu_total => "ns",
+  virt_vcpu => "ns",
+); # }}} %TypesRenameDataSource
+
+sub handle_file # {{{
+{
+  my @path = @_;
+  my $path = join ('/', @path);
+
+  if (!($path =~ m/\.rrd$/))
+  {
+    return;
+  }
+
+  my $tmp = pop (@path);
+  $tmp =~ s/\.rrd$//;
+  my ($type, $type_inst) = split (m/-/, $tmp, 2);
+  $type_inst ||= '';
+
+  $tmp = pop (@path);
+  my ($plugin, $plugin_inst) = split (m/-/, $tmp, 2);
+  $plugin_inst ||= '';
+
+  if ($TypesRenameDataSource{$type})
+  {
+    my $old_ds = $TypesRenameDataSource{$type};
+    print "$RRDtool tune \"$path\" --data-source-rename ${old_ds}:value\n";
+  }
+
+  if ($TypesCounterToDerive{$type})
+  {
+    my $ds_names = $TypesCounterToDerive{$type};
+    
+    for (@$ds_names)
+    {
+      my $name = $_;
+      print "$RRDtool tune \"$path\" --data-source-type ${name}:DERIVE --minimum ${name}:0 --maximum ${name}:U\n";
+    }
+  }
+
+  if ((($plugin eq 'df') || ($plugin eq 'interface'))
+    && (!$plugin_inst) && ($type_inst))
+  {
+    my $dir = join ('/', @path);
+    print "mkdir -p \"$dir/$plugin-$type_inst\"\n";
+    print "mv \"$path\" \"$dir/$plugin-$type_inst/$type.rrd\"\n";
+  }
+} # }}} sub handle_file
+
+sub scan_dir # {{{
+{
+  my @dir_parts = @_;
+  my $dir_str = join ('/', @dir_parts);
+  my $dh;
+
+  opendir ($dh, $dir_str) || die;
+  while (my $entry = readdir ($dh))
+  {
+    my $entry_path = "$dir_str/$entry";
+
+    if ($entry =~ m/^\./)
+    {
+      next;
+    }
+
+    if (-d $entry_path)
+    {
+      scan_dir (@dir_parts, $entry);
+    }
+    elsif (-f $entry_path)
+    {
+      handle_file (@dir_parts, $entry);
+    }
+  }
+  closedir ($dh);
+} # }}} sub scan_dir
+
+sub exit_usage # {{{
+{
+  print STDERR <<EOF;
+migrate-4-5.px [OPTIONS]
+
+Valid options are:
+
+  --indir <dir>      Source directory
+                     Default: $InDir
+  --rrdtool <path>   Path to the RRDtool binary
+                     Default: $RRDtool
+
+EOF
+  exit (1);
+} # }}} sub exit_usage
+
+GetOptions ("indir|i=s" => \$InDir,
+        "rrdtool=s" => \$RRDtool,
+        "help|h" => \&exit_usage) or exit_usage ();
+
+scan_dir ($InDir);
+
+# vim: set sw=2 sts=2 et fdm=marker :
diff --git a/contrib/network-proxy.py b/contrib/network-proxy.py
new file mode 100644 (file)
index 0000000..98a4ad8
--- /dev/null
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+# vim: sts=4 sw=4 et
+
+# Simple unicast proxy to send collectd traffic to another host/port.
+# Copyright (C) 2007  Pavel Shramov <shramov at mexmat.net>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; only version 2 of the License is applicable.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+# Place, Suite 330, Boston, MA  02111-1307  USA
+
+"""
+Simple unicast proxy for collectd (>= 4.0).
+Binds to 'local' address and forwards all traffic to 'remote'.
+"""
+
+import socket
+import struct
+
+""" Local multicast group/port"""
+local  = ("239.192.74.66", 25826)
+""" Address to send packets """
+remote = ("grid.pp.ru", 35826)
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
+mreq = struct.pack("4sl", socket.inet_aton(local[0]), socket.INADDR_ANY)
+
+sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 1)
+sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
+sock.bind(local)
+
+out = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
+
+if __name__ == "__main__":
+    while True:
+        (buf, addr) = sock.recvfrom(2048)
+        sock.sendto(buf, remote)
diff --git a/contrib/oracle/create_schema.ddl b/contrib/oracle/create_schema.ddl
new file mode 100644 (file)
index 0000000..86b1e83
--- /dev/null
@@ -0,0 +1,264 @@
+-- collectd - contrib/oracle/create_schema.ddl
+-- Copyright (C) 2008,2009  Roman Klesel
+--
+-- This program is free software; you can redistribute it and/or modify it
+-- under the terms of the GNU General Public License as published by the
+-- Free Software Foundation; only version 2 of the License is applicable.
+--
+-- This program is distributed in the hope that it will be useful, but
+-- WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+-- General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along
+-- with this program; if not, write to the Free Software Foundation, Inc.,
+-- 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+--
+-- Authors:
+--   Roman Klesel <roman.klesel at noris.de>
+
+-- Description
+--------------
+-- This will create a schema to provide collectd with the required permissions
+-- and space for statistic data.
+-- The idea is to store the output of some expensive queries in static tables
+-- and fill these tables with dbms_scheduler jobs as often as necessary.
+-- collectd will then just read from the static tables. This will reduces the
+-- chance that your system will be killed by excessive monitoring queries and
+-- gives the dba control on the interval the information provided to collectd
+-- will be refreshed. You have to create a dbms_scheduler job for each of the
+-- schemas you what to monitor for object-space-usage. See the example below.
+--
+-- Requirements
+---------------
+-- make sure you have: 
+--             write permission in $PWD
+--             you have GID of oracle software owner
+--             set $ORACLE_HOME 
+--             set $ORACLE_SID
+--             DB is up an running in RW mode
+-- execute like this:
+-- sqlplus /nolog @ create_collectd-schema.dll
+
+spool create_collectd-schema.log
+connect / as sysdba
+
+-- Create user, tablespace and permissions
+CREATE TABLESPACE "COLLECTD-TBS" 
+       DATAFILE SIZE 30M 
+       AUTOEXTEND ON 
+       NEXT 10M 
+       MAXSIZE 300M
+       LOGGING 
+       EXTENT MANAGEMENT LOCAL 
+       SEGMENT SPACE MANAGEMENT AUTO 
+       DEFAULT NOCOMPRESS;
+
+CREATE ROLE "CREATE_COLLECTD_SCHEMA" NOT IDENTIFIED;
+GRANT CREATE JOB TO "CREATE_COLLECTD_SCHEMA";
+GRANT CREATE SEQUENCE TO "CREATE_COLLECTD_SCHEMA";
+GRANT CREATE SYNONYM TO "CREATE_COLLECTD_SCHEMA";
+GRANT CREATE TABLE TO "CREATE_COLLECTD_SCHEMA";
+GRANT CREATE VIEW TO "CREATE_COLLECTD_SCHEMA";
+GRANT CREATE PROCEDURE TO "CREATE_COLLECTD_SCHEMA";
+
+CREATE USER "COLLECTDU" 
+       PROFILE "DEFAULT" 
+       IDENTIFIED BY "Change_me-1st" 
+       PASSWORD EXPIRE 
+       DEFAULT TABLESPACE "COLLECTD-TBS"
+       TEMPORARY TABLESPACE "TEMP"
+       QUOTA UNLIMITED ON "COLLECTD-TBS"
+       ACCOUNT UNLOCK;
+
+GRANT "CONNECT" TO "COLLECTDU";
+GRANT "SELECT_CATALOG_ROLE" TO "COLLECTDU";
+GRANT "CREATE_COLLECTD_SCHEMA" TO "COLLECTDU";
+GRANT analyze any TO "COLLECTDU";
+GRANT select on dba_tables TO "COLLECTDU";
+GRANT select on dba_lobs TO "COLLECTDU";
+GRANT select on dba_indexes TO "COLLECTDU";
+GRANT select on dba_segments TO "COLLECTDU";
+GRANT select on dba_tab_columns TO "COLLECTDU";
+GRANT select on dba_free_space TO "COLLECTDU";
+GRANT select on dba_data_files TO "COLLECTDU";
+-- Create tables and indexes
+
+alter session set current_schema=collectdu;
+
+create table c_tbs_usage (
+       tablespace_name varchar2(30),
+       bytes_free number,
+    bytes_used  number,
+        CONSTRAINT "C_TBS_USAGE_UK1" UNIQUE ("TABLESPACE_NAME") USING INDEX
+        TABLESPACE "COLLECTD-TBS"  ENABLE)
+        TABLESPACE "COLLECTD-TBS";
+
+CREATE TABLE "COLLECTDU"."C_TBL_SIZE" (
+    "OWNER" VARCHAR2(30 BYTE), 
+       "TABLE_NAME" VARCHAR2(30 BYTE), 
+       "BYTES" NUMBER, 
+        CONSTRAINT "C_TBL_SIZE_UK1" UNIQUE ("OWNER", "TABLE_NAME")
+         USING INDEX TABLESPACE "COLLECTD-TBS"  ENABLE)
+         TABLESPACE "COLLECTD-TBS" ;
+
+create or replace PROCEDURE get_object_size(owner IN VARCHAR2) AS
+
+v_owner VARCHAR2(30) := owner;
+
+l_free_blks NUMBER;
+l_total_blocks NUMBER;
+l_total_bytes NUMBER;
+l_unused_blocks NUMBER;
+l_unused_bytes NUMBER;
+l_lastusedextfileid NUMBER;
+l_lastusedextblockid NUMBER;
+l_last_used_block NUMBER;
+
+CURSOR cur_tbl IS
+SELECT owner,
+  TABLE_NAME
+FROM dba_tables
+WHERE owner = v_owner;
+
+CURSOR cur_idx IS
+SELECT owner,
+  index_name,
+  TABLE_NAME
+FROM dba_indexes
+WHERE owner = v_owner;
+
+CURSOR cur_lob IS
+SELECT owner,
+  segment_name,
+  TABLE_NAME
+FROM dba_lobs
+WHERE owner = v_owner;
+
+BEGIN
+
+  DELETE FROM c_tbl_size
+  WHERE owner = v_owner;
+  COMMIT;
+
+  FOR r_tbl IN cur_tbl
+  LOOP
+    BEGIN
+      dbms_space.unused_space(segment_owner => r_tbl.owner,   segment_name => r_tbl.TABLE_NAME,   segment_type => 'TABLE',   total_blocks => l_total_blocks,   total_bytes => l_total_bytes,   unused_blocks => l_unused_blocks,   unused_bytes => l_unused_bytes,   last_used_extent_file_id => l_lastusedextfileid,   last_used_extent_block_id => l_lastusedextblockid,   last_used_block => l_last_used_block);
+
+    EXCEPTION
+    WHEN others THEN
+      DBMS_OUTPUT.PUT_LINE('tbl_name: ' || r_tbl.TABLE_NAME);
+    END;
+    INSERT
+    INTO c_tbl_size
+    VALUES(r_tbl.owner,   r_tbl.TABLE_NAME,   l_total_bytes -l_unused_bytes);
+  END LOOP;
+
+  COMMIT;
+
+  FOR r_idx IN cur_idx
+  LOOP
+    BEGIN
+      dbms_space.unused_space(segment_owner => r_idx.owner,   segment_name => r_idx.index_name,   segment_type => 'INDEX',   total_blocks => l_total_blocks,   total_bytes => l_total_bytes,   unused_blocks => l_unused_blocks,   unused_bytes => l_unused_bytes,   last_used_extent_file_id => l_lastusedextfileid,   last_used_extent_block_id => l_lastusedextblockid,   last_used_block => l_last_used_block);
+
+    EXCEPTION
+    WHEN others THEN
+      DBMS_OUTPUT.PUT_LINE('idx_name: ' || r_idx.index_name);
+    END;
+
+    UPDATE c_tbl_size
+    SET bytes = bytes + l_total_bytes -l_unused_bytes
+    WHERE owner = r_idx.owner
+     AND TABLE_NAME = r_idx.TABLE_NAME;
+  END LOOP;
+
+  COMMIT;
+
+  FOR r_lob IN cur_lob
+  LOOP
+    BEGIN
+      dbms_space.unused_space(segment_owner => r_lob.owner,   segment_name => r_lob.segment_name,   segment_type => 'LOB',   total_blocks => l_total_blocks,   total_bytes => l_total_bytes,   unused_blocks => l_unused_blocks,   unused_bytes => l_unused_bytes,   last_used_extent_file_id => l_lastusedextfileid,   last_used_extent_block_id => l_lastusedextblockid,   last_used_block => l_last_used_block);
+
+    EXCEPTION
+    WHEN others THEN
+      DBMS_OUTPUT.PUT_LINE('lob_name: ' || r_lob.segment_name);
+    END;
+
+    UPDATE c_tbl_size
+    SET bytes = bytes + l_total_bytes -l_unused_bytes
+    WHERE owner = r_lob.owner
+     AND TABLE_NAME = r_lob.TABLE_NAME;
+  END LOOP;
+
+  COMMIT;
+
+END get_object_size;
+/
+
+create or replace PROCEDURE get_tbs_size AS
+BEGIN
+
+execute immediate 'truncate table c_tbs_usage';
+
+insert into c_tbs_usage (
+select df.tablespace_name as tablespace_name, 
+       decode(df.maxbytes,
+               0,
+               sum(fs.bytes),
+               (df.maxbytes-(df.bytes-sum(fs.bytes)))) as bytes_free,
+       decode(df.maxbytes,
+               0,
+               round((df.bytes-sum(fs.bytes))),
+               round(df.maxbytes-(df.maxbytes-(df.bytes-sum(fs.bytes))))) as bytes_used
+from dba_free_space fs inner join 
+       (select 
+               tablespace_name, 
+               sum(bytes) bytes, 
+               sum(decode(maxbytes,0,bytes,maxbytes))  maxbytes
+        from dba_data_files
+        group by tablespace_name ) df          
+on fs.tablespace_name = df.tablespace_name
+group by df.tablespace_name,df.maxbytes,df.bytes);
+
+COMMIT;
+
+END get_tbs_size;
+/
+
+BEGIN
+sys.dbms_scheduler.create_job(
+job_name => '"COLLECTDU"."C_TBSSIZE_JOB"',
+job_type => 'PLSQL_BLOCK',
+job_action => 'begin
+   get_tbs_size();
+end;',
+repeat_interval => 'FREQ=MINUTELY;INTERVAL=5',
+start_date => systimestamp at time zone 'Europe/Berlin',
+job_class => '"DEFAULT_JOB_CLASS"',
+auto_drop => FALSE,
+enabled => TRUE);
+END;
+/
+
+BEGIN
+sys.dbms_scheduler.create_job(
+job_name => '"COLLECTDU"."C_TBLSIZE_COLLECTDU_JOB"',
+job_type => 'PLSQL_BLOCK',
+job_action => 'begin
+   get_object_size( owner => ''COLLECTDU'' );
+end;',
+repeat_interval => 'FREQ=HOURLY;INTERVAL=12',
+start_date => systimestamp at time zone 'Europe/Berlin',
+job_class => '"DEFAULT_JOB_CLASS"',
+auto_drop => FALSE,
+enabled => TRUE);
+END;
+/
+
+spool off
+quit
+
+-- vim: set syntax=sql :
diff --git a/contrib/oracle/db_systat.sql b/contrib/oracle/db_systat.sql
new file mode 100644 (file)
index 0000000..cf2a3c4
--- /dev/null
@@ -0,0 +1,55 @@
+-- Table sizes
+SELECT owner,
+  TABLE_NAME,
+  bytes
+FROM collectdu.c_tbl_size;
+
+-- Tablespace sizes
+SELECT tablespace_name,
+  bytes_free,
+  bytes_used
+FROM collectdu.c_tbs_usage;
+
+-- IO per Tablespace
+SELECT SUM(vf.phyblkrd) *8192 AS
+phy_blk_r,
+  SUM(vf.phyblkwrt) *8192 AS
+phy_blk_w,
+  'tablespace' AS
+i_prefix,
+  dt.tablespace_name
+FROM((dba_data_files dd JOIN v$filestat vf ON dd.file_id = vf.file#) JOIN dba_tablespaces dt ON dd.tablespace_name = dt.tablespace_name)
+GROUP BY dt.tablespace_name;
+
+-- Buffer Pool Hit Ratio:
+SELECT DISTINCT 100 *ROUND(1 -((MAX(decode(name,   'physical reads cache',   VALUE))) /(MAX(decode(name,   'db block gets from cache',   VALUE)) + MAX(decode(name,   'consistent gets from cache',   VALUE)))),   4) AS
+VALUE,
+  'BUFFER_CACHE_HIT_RATIO' AS
+buffer_cache_hit_ratio
+FROM v$sysstat;
+
+-- Shared Pool Hit Ratio:
+SELECT 
+  100.0 * sum(PINHITS) / sum(pins) as VALUE,
+  'SHAREDPOOL_HIT_RATIO' AS SHAREDPOOL_HIT_RATIO
+FROM V$LIBRARYCACHE;
+
+-- PGA Hit Ratio:
+SELECT VALUE,
+  'PGA_HIT_RATIO' AS
+pga_hit_ratio
+FROM v$pgastat
+WHERE name = 'cache hit percentage';
+
+-- DB Efficiency
+SELECT ROUND(SUM(decode(metric_name,   'Database Wait Time Ratio',   VALUE)),   2) AS
+database_wait_time_ratio,
+  ROUND(SUM(decode(metric_name,   'Database CPU Time Ratio',   VALUE)),   2) AS
+database_cpu_time_ratio,
+  'DB_EFFICIENCY' AS
+db_efficiency
+FROM sys.v_$sysmetric
+WHERE metric_name IN('Database CPU Time Ratio',   'Database Wait Time Ratio')
+ AND intsize_csec =
+  (SELECT MAX(intsize_csec)
+   FROM sys.v_$sysmetric);
diff --git a/contrib/php-collection/browser.js b/contrib/php-collection/browser.js
new file mode 100644 (file)
index 0000000..4ddc424
--- /dev/null
@@ -0,0 +1,790 @@
+/*
+ * Copyright (C) 2009  Bruno Prémont <bonbons AT linux-vserver.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+// Toggle visibility of a div
+function toggleDiv(divID) {
+       var div   = document.getElementById(divID);
+       var label = document.getElementById(divID+'_sw');
+       var label_txt = null;
+       if (div) {
+               if (div.style.display == 'none') {
+                       div.style.display = 'block';
+                       label_txt = 'Hide';
+               } else {
+                       div.style.display = 'none';
+                       label_txt = 'Show';
+               }
+       }
+       if (label_txt && label) {
+               var childCnt = label.childNodes.length;
+               while (childCnt > 0)
+                       label.removeChild(label.childNodes[--childCnt]);
+               label.appendChild(document.createTextNode(label_txt));
+       }
+       GraphPositionToolbox(null);
+}
+
+var req = null;
+
+// DHTML helper code to asynchronous loading of content
+function loadXMLDoc(url, query) {
+       if (window.XMLHttpRequest) {
+               req = new XMLHttpRequest();
+               req.onreadystatechange = processReqChange;
+               req.open('POST', url, true);
+               req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8'); 
+               req.send(query);
+       } else if (window.ActiveXObject) {
+               req = new ActiveXObject("Microsoft.XMLHTTP");
+               if (req) {
+                       req.onreadystatechange = processReqChange;
+                       req.open('POST', url, true);
+                       req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8'); 
+                       req.send(query);
+               }
+       }
+}
+
+// DHTML new-content dispatcher
+function processReqChange(evt) {
+       if (req.readyState == 4) {
+               if (req.status == 200) {
+                       var response = req.responseXML.documentElement;
+                       var method = response.getElementsByTagName('method')[0].firstChild.data;
+                       var result = response.getElementsByTagName('result')[0];
+                       req = null;
+                       eval(method + '(result)');
+               }
+       }
+}
+
+// Update contents of a <select> drop-down list
+function refillSelect(options, select) {
+       if (!select)
+               return -1;
+
+       var childCnt = select.childNodes.length;
+       var oldValue = select.selectedIndex > 0 ? select.options[select.selectedIndex].value : '/';
+       while (childCnt > 0)
+               select.removeChild(select.childNodes[--childCnt]);
+
+       var optCnt = options ? options.length : 0;
+       if (optCnt == 0) {
+               select.setAttribute('disabled', 'disabled');
+               return -1;
+       } else {
+               select.removeAttribute('disabled');
+               var keepSelection = false;
+               if (optCnt == 1) {
+                       keepSelection = true;
+                       oldValue = options[0].firstChild ? options[0].firstChild.data : '';
+               } else if (oldValue != '/') {
+                       for (i = 0; i < optCnt && !keepSelection; i++)
+                               if (oldValue == (options[i].firstChild ? options[i].firstChild.data : ''))
+                                       keepSelection = true;
+               }
+               newOption = document.createElement("option");
+               newOption.value = '/';
+               if (keepSelection)
+                       newOption.setAttribute('disabled', 'disabled');
+               else
+                       newOption.setAttribute('selected', 'selected');
+               newOption.setAttribute('style', 'font-style: italic');
+               newOption.appendChild(document.createTextNode('- please select -'));
+               select.appendChild(newOption);
+               for (i = 0; i < optCnt; i++) {
+                       newOption = document.createElement("option");
+                       newOption.value = options[i].firstChild ? options[i].firstChild.data : '';
+                       if (keepSelection && newOption.value == oldValue)
+                               newOption.setAttribute('selected', 'selected');
+                       if (newOption.value[0] == '@') {
+                               newOption.setAttribute('style', 'font-style: italic');
+                               if (newOption.value == '@' || newOption.value == '@merge')
+                                       newOption.appendChild(document.createTextNode('Meta graph'));
+                               else if (newOption.value == '@all')
+                                       newOption.appendChild(document.createTextNode('All entries'));
+                               else if (newOption.value == '@merge_sum')
+                                       newOption.appendChild(document.createTextNode('Meta summed graph'));
+                               else if (newOption.value == '@merge_avg')
+                                       newOption.appendChild(document.createTextNode('Meta averaged graph'));
+                               else if (newOption.value == '@merge_stack')
+                                       newOption.appendChild(document.createTextNode('Meta stacked graph'));
+                               else if (newOption.value == '@merge_line')
+                                       newOption.appendChild(document.createTextNode('Meta lines graph'));
+                               else
+                                       newOption.appendChild(document.createTextNode(newOption.value));
+                       } else
+                               newOption.appendChild(document.createTextNode(newOption.value));
+                       select.appendChild(newOption);
+               }
+               return keepSelection ? select.selectedIndex : -1;
+       }
+}
+
+// Request refresh of host list
+function ListRefreshHost() {
+       var query = 'action=list_hosts';
+       loadXMLDoc(dhtml_url, query);
+}
+
+// Handle update to host list
+function ListOfHost(response) {
+       var select = document.getElementById('host_list');
+       var idx = refillSelect(response ? response.getElementsByTagName('option') : null, select);
+       if (idx > 0) {
+               ListRefreshPlugin();
+       } else
+               ListOfPlugin(null);
+}
+
+// Request refresh of plugin list
+function ListRefreshPlugin() {
+       var host_list = document.getElementById('host_list');
+       var host      = host_list.selectedIndex >= 0 ? host_list.options[host_list.selectedIndex].value : '/';
+       if (host != '/') {
+               var query = 'action=list_plugins&host='+encodeURIComponent(host);
+               loadXMLDoc(dhtml_url, query);
+       } else {
+               ListOfPlugin(null);
+       }
+}
+
+// Handle update to plugin list
+function ListOfPlugin(response) {
+       var select = document.getElementById('plugin_list');
+       var idx = refillSelect(response ? response.getElementsByTagName('option') : null, select);
+       if (idx > 0) {
+               ListRefreshPluginInstance();
+       } else
+               ListOfPluginInstance(null);
+}
+
+// Request refresh of plugin instance list
+function ListRefreshPluginInstance() {
+       var host_list   = document.getElementById('host_list');
+       var host        = host_list.selectedIndex >= 0 ? host_list.options[host_list.selectedIndex].value : '/';
+       var plugin_list = document.getElementById('plugin_list');
+       var plugin      = plugin_list.selectedIndex >= 0 ? plugin_list.options[plugin_list.selectedIndex].value : '/';
+       if (host != '/' && plugin != '/') {
+               var query = 'action=list_pinsts&host='+encodeURIComponent(host)+'&plugin='+encodeURIComponent(plugin);
+               loadXMLDoc(dhtml_url, query);
+       } else {
+               ListOfPluginInstance(null);
+       }
+}
+
+// Handle update of plugin instance list
+function ListOfPluginInstance(response) {
+       var select = document.getElementById('pinst_list');
+       var idx = refillSelect(response ? response.getElementsByTagName('option') : null, select);
+       if (idx > 0) {
+               ListRefreshType();
+       } else
+               ListOfType(null);
+}
+
+// Request refresh of type list
+function ListRefreshType() {
+       var host_list   = document.getElementById('host_list');
+       var host        = host_list.selectedIndex >= 0 ? host_list.options[host_list.selectedIndex].value : '/';
+       var plugin_list = document.getElementById('plugin_list');
+       var plugin      = plugin_list.selectedIndex >= 0 ? plugin_list.options[plugin_list.selectedIndex].value : '/';
+       var pinst_list  = document.getElementById('pinst_list');
+       var pinst       = pinst_list.selectedIndex >= 0 ? pinst_list.options[pinst_list.selectedIndex].value : '/';
+       if (host != '/' && plugin != '/' && pinst != '/') {
+               var query = 'action=list_types&host='+encodeURIComponent(host)+'&plugin='+encodeURIComponent(plugin)+'&plugin_instance='+encodeURIComponent(pinst);
+               loadXMLDoc(dhtml_url, query);
+       } else {
+               ListOfType(null);
+       }
+}
+
+// Handle update of type list
+function ListOfType(response) {
+       var select = document.getElementById('type_list');
+       var idx = refillSelect(response ? response.getElementsByTagName('option') : null, select);
+       if (idx > 0) {
+               ListRefreshTypeInstance();
+       } else
+               ListOfTypeInstance(null);
+}
+
+// Request refresh of type instance list
+function ListRefreshTypeInstance() {
+       var host_list   = document.getElementById('host_list');
+       var host        = host_list.selectedIndex >= 0 ? host_list.options[host_list.selectedIndex].value : '/';
+       var plugin_list = document.getElementById('plugin_list');
+       var plugin      = plugin_list.selectedIndex >= 0 ? plugin_list.options[plugin_list.selectedIndex].value : '/';
+       var pinst_list  = document.getElementById('pinst_list');
+       var pinst       = pinst_list.selectedIndex >= 0 ? pinst_list.options[pinst_list.selectedIndex].value : '/';
+       var type_list   = document.getElementById('type_list');
+       var type        = type_list.selectedIndex >= 0 ? type_list.options[type_list.selectedIndex].value : '/';
+       if (host != '/' && plugin != '/' && pinst != '/' && type != '/') {
+               var query = 'action=list_tinsts&host='+encodeURIComponent(host)+'&plugin='+encodeURIComponent(plugin)+'&plugin_instance='+encodeURIComponent(pinst)+'&type='+encodeURIComponent(type);
+               loadXMLDoc(dhtml_url, query);
+       } else {
+               ListOfTypeInstance(null);
+       }
+}
+
+// Handle update of type instance list
+function ListOfTypeInstance(response) {
+       var select = document.getElementById('tinst_list');
+       var idx = refillSelect(response ? response.getElementsByTagName('option') : null, select);
+       if (idx > 0) {
+               // Enable add button
+               RefreshButtons();
+       } else {
+               // Disable add button
+               RefreshButtons();
+       }
+}
+
+function RefreshButtons() {
+       var host_list   = document.getElementById('host_list');
+       var host        = host_list.selectedIndex >= 0 ? host_list.options[host_list.selectedIndex].value : '/';
+       var plugin_list = document.getElementById('plugin_list');
+       var plugin      = plugin_list.selectedIndex >= 0 ? plugin_list.options[plugin_list.selectedIndex].value : '/';
+       var pinst_list  = document.getElementById('pinst_list');
+       var pinst       = pinst_list.selectedIndex >= 0 ? pinst_list.options[pinst_list.selectedIndex].value : '/';
+       var type_list   = document.getElementById('type_list');
+       var type        = type_list.selectedIndex >= 0 ? type_list.options[type_list.selectedIndex].value : '/';
+       var tinst_list  = document.getElementById('tinst_list');
+       var tinst       = tinst_list.selectedIndex >= 0 ? tinst_list.options[tinst_list.selectedIndex].value : '/';
+       if (host != '/' && plugin != '/' && pinst != '/' && type != '/' && tinst != '/') {
+               document.getElementById('btnAdd').removeAttribute('disabled');
+       } else {
+               document.getElementById('btnAdd').setAttribute('disabled', 'disabled');
+       }
+
+       var graphs = document.getElementById('graphs');
+       if (graphs.getElementsByTagName('div').length > 1) {
+               document.getElementById('btnClear').removeAttribute('disabled');
+               document.getElementById('btnRefresh').removeAttribute('disabled');
+       } else {
+               document.getElementById('btnClear').setAttribute('disabled', 'disabled');
+               document.getElementById('btnRefresh').setAttribute('disabled', 'disabled');
+       }
+}
+
+var nextGraphId = 1;
+var graphList = new Array();
+
+function GraphAppend() {
+       var host_list   = document.getElementById('host_list');
+       var host        = host_list.selectedIndex >= 0 ? host_list.options[host_list.selectedIndex].value : '/';
+       var plugin_list = document.getElementById('plugin_list');
+       var plugin      = plugin_list.selectedIndex >= 0 ? plugin_list.options[plugin_list.selectedIndex].value : '/';
+       var pinst_list  = document.getElementById('pinst_list');
+       var pinst       = pinst_list.selectedIndex >= 0 ? pinst_list.options[pinst_list.selectedIndex].value : '/';
+       var type_list   = document.getElementById('type_list');
+       var type        = type_list.selectedIndex >= 0 ? type_list.options[type_list.selectedIndex].value : '/';
+       var tinst_list  = document.getElementById('tinst_list');
+       var tinst       = tinst_list.selectedIndex >= 0 ? tinst_list.options[tinst_list.selectedIndex].value : '/';
+       var time_list   = document.getElementById('timespan');
+       var timespan    = time_list.selectedIndex >= 0 ? time_list.options[time_list.selectedIndex].value : '';
+       var tinyLegend  = document.getElementById('tinylegend').checked;
+       var logarithmic = document.getElementById('logarithmic').checked;
+       if (host[0] == '@' || plugin[0] == '@' || pinst[0] == '@' || type[0] == '@' || (tinst[0] == '@' && tinst.substr(0, 5) != '@merge')) {
+               var query = 'action=list_graphs&host='+encodeURIComponent(host)+'&plugin='+encodeURIComponent(plugin)+'&plugin_instance='+encodeURIComponent(pinst);
+               query = query+'&type='+encodeURIComponent(type)+'&type_instance='+encodeURIComponent(tinst)+'&timespan='+encodeURIComponent(timespan);
+               query = query+(logarithmic ? '&logarithmic=1' : '')+(tinyLegend ? '&tinylegend=1' : '');
+               loadXMLDoc(dhtml_url, query);
+       } else
+               GraphDoAppend(host, plugin, pinst, type, tinst, timespan, tinyLegend, logarithmic);
+}
+
+function ListOfGraph(response) {
+       var graphs = response ? response.getElementsByTagName('graph') : null;
+       if (graphs && graphs.length > 0) {
+               for (i = 0; i < graphs.length; i++)
+                       GraphDoAppend(graphs[i].getAttribute('host'), graphs[i].getAttribute('plugin'), graphs[i].getAttribute('plugin_instance'),
+                                     graphs[i].getAttribute('type'), graphs[i].getAttribute('type_instance'), graphs[i].getAttribute('timespan'),
+                                     graphs[i].getAttribute('tinyLegend') == '1', graphs[i].getAttribute('logarithmic') == '1');
+       } else
+               alert('No graph found for adding');
+}
+
+function GraphDoAppend(host, plugin, pinst, type, tinst, timespan, tinyLegend, logarithmic) {
+       var graphs      = document.getElementById('graphs');
+
+       if (host != '/' && plugin != '/' && pinst != '/' && type != '/') {
+               var graph_id   = 'graph_'+nextGraphId++;
+               var graph_src  = graph_url+'?host='+encodeURIComponent(host)+'&plugin='+encodeURIComponent(plugin)+'&plugin_instance='+encodeURIComponent(pinst)+'&type='+encodeURIComponent(type);
+               var graph_alt  = '';
+               var grap_title = '';
+               if (tinst == '@') {
+                       graph_alt   = host+'/'+plugin+(pinst.length > 0 ? '-'+pinst : '')+'/'+type;
+                       graph_title = type+' of '+plugin+(pinst.length > 0 ? '-'+pinst : '')+' plugin for '+host;
+               } else {
+                       graph_alt   = host+'/'+plugin+(pinst.length > 0 ? '-'+pinst : '')+'/'+type+(tinst.length > 0 ? '-'+tinst : '');
+                       graph_title = type+(tinst.length > 0 ? '-'+tinst : '')+' of '+plugin+(pinst.length > 0 ? '-'+pinst : '')+' plugin for '+host;
+                       graph_src  += '&type_instance='+encodeURIComponent(tinst);
+               }
+               if (logarithmic)
+                       graph_src += '&logarithmic=1';
+               if (tinyLegend)
+                       graph_src += '&tinylegend=1';
+               if (timespan)
+                       graph_src += '&timespan='+encodeURIComponent(timespan);
+               var now    = new Date();
+               graph_src += '&ts='+now.getTime();
+               graphList.push(graph_id+' '+encodeURIComponent(graph_alt)+(logarithmic ? '&logarithmic=1' : '')+(tinyLegend ? '&tinylegend=1' : '')+'&timespan='+encodeURIComponent(timespan));
+
+               // Graph container
+               newGraph = document.createElement('div');
+               newGraph.setAttribute('class', 'graph');
+               newGraph.setAttribute('id', graph_id);
+               // Graph cell + graph
+               newImg = document.createElement('img');
+               newImg.setAttribute('src', graph_src);
+               newImg.setAttribute('alt', graph_alt);
+               newImg.setAttribute('title', graph_title);
+               newImg.setAttribute('onclick', 'GraphToggleTools("'+graph_id+'")');
+               newGraph.appendChild(newImg);
+               graphs.appendChild(newGraph);
+       }
+       document.getElementById('nograph').style.display = 'none';
+       RefreshButtons();
+}
+
+function GraphDropAll() {
+       var graphs = document.getElementById('graphs');
+       var childCnt = graphs.childNodes.length;
+       while (childCnt > 0)
+               if (graphs.childNodes[--childCnt].id != 'nograph' && (graphs.childNodes[childCnt].nodeName == 'div' || graphs.childNodes[childCnt].nodeName == 'DIV'))
+                       graphs.removeChild(graphs.childNodes[childCnt]);
+               else if (graphs.childNodes[childCnt].id == 'nograph')
+                       graphs.childNodes[childCnt].style.display = 'block';
+       graphList = new Array();
+       RefreshButtons();
+}
+
+function GraphToggleTools(graph) {
+       var graphId = document.getElementById('ge_graphid').value;
+       var ref_img = null;
+       if (graphId == graph || graph == '') {
+               ref_img = null;
+       } else {
+               var graphDiv = document.getElementById(graph);
+               var imgs     = graphDiv ? graphDiv.getElementsByTagName('img') : null;
+               var imgCnt   = imgs ? imgs.length : 0;
+               while (imgCnt > 0)
+                       if (imgs[--imgCnt].parentNode.getAttribute('class') == 'graph')
+                               ref_img = imgs[imgCnt];
+       }
+       if (ref_img) {
+               var ts_sel  =  document.getElementById('ge_timespan');
+               var src_url = ref_img.src;
+               var ge      = document.getElementById('ge');
+               // Fix field values
+               var ts = src_url.match(/&timespan=[^&]*/);
+               ts = ts ? ts[0].substr(10) : '';
+               document.getElementById('ge_graphid').value = graph;
+               document.getElementById('ge_tinylegend').checked = src_url.match(/&tinylegend=1/);
+               document.getElementById('ge_logarithmic').checked = src_url.match(/&logarithmic=1/);
+               for (i = 0; i < ts_sel.options.length; i++)
+                       if (ts_sel.options[i].value == ts) {
+                               ts_sel.selectedIndex = i;
+                               break;
+                       }
+               // show tools box and position it properly
+               ge.style.display = 'table';
+               GraphPositionToolbox(ref_img);
+       } else {
+               // hide tools box
+               document.getElementById('ge').style.display = 'none';
+               document.getElementById('ge_graphid').value = '';
+       }
+}
+
+function GraphPositionToolbox(ref_img) {
+       var ge      = document.getElementById('ge');
+       if (ge.style.display != 'none') {
+               var wl = 0; var wt = 0;
+               var x = ref_img;
+               if (ref_img == null) {
+                       var graphDiv = document.getElementById(document.getElementById('ge_graphid').value);
+                       var imgs     = graphDiv ? graphDiv.getElementsByTagName('img') : null;
+                       var imgCnt   = imgs ? imgs.length : 0;
+                       while (imgCnt > 0)
+                               if (imgs[--imgCnt].parentNode.getAttribute('class') == 'graph')
+                                       ref_img = imgs[imgCnt];
+
+                       if (ref_img == null) {
+                               document.getElementById('ge_graphid').value = '';
+                               ge.style.display = 'none';
+                               return;
+                       } else
+                               x = ref_img;
+               }
+               while (x != null) {
+                       wl += x.offsetLeft;
+                       wt += x.offsetTop;
+                       x = x.offsetParent;
+               }
+               ge.style.left    = (wl + (ref_img.offsetWidth - ge.offsetWidth) / 2)+'px';
+               ge.style.top     = (wt + (ref_img.offsetHeight - ge.offsetHeight) / 2)+'px';
+       }
+}
+
+function GraphRefreshAll() {
+       var imgs   = document.getElementById('graphs').getElementsByTagName('img');
+       var imgCnt = imgs.length;
+       var now    = new Date();
+       var newTS  = '&ts='+now.getTime();
+       while (imgCnt > 0)
+               if (imgs[--imgCnt].parentNode.getAttribute('class') == 'graph') {
+                       var oldSrc = imgs[imgCnt].src;
+                       var newSrc = oldSrc.replace(/&ts=[0-9]+/, newTS);
+                       if (newSrc == oldSrc)
+                               newSrc = newSrc + newTS;
+                       imgs[imgCnt].setAttribute('src', newSrc);
+               }
+}
+
+function GraphRefresh(graph) {
+       var graphElement = null;
+       if (graph == null) {
+               var graphId = document.getElementById('ge_graphid').value;
+               if (graphId != '')
+                       graphElement = document.getElementById(graphId);
+       } else 
+               graphElement = document.getElementById(graph);
+       if (graphElement != null) {
+               var imgs = graphElement.getElementsByTagName('img');
+               var imgCnt = imgs.length;
+               while (imgCnt > 0)
+                       if (imgs[--imgCnt].parentNode.getAttribute('class') == 'graph') {
+                               var now    = new Date();
+                               var newTS  = '&ts='+now.getTime();
+                               var oldSrc = imgs[imgCnt].src;
+                               var newSrc = oldSrc.replace(/&ts=[0-9]+/, newTS);
+                               if (newSrc == oldSrc)
+                                       newSrc = newSrc+newTS;
+                               imgs[imgCnt].setAttribute('src', newSrc);
+                               break;
+                       }
+       }
+}
+
+function GraphAdjust(graph) {
+       var graphId = graph == null ? document.getElementById('ge_graphid').value : graph;
+       var graphElement = document.getElementById(graphId);
+       if (graphElement != null) {
+               var time_list   = document.getElementById('ge_timespan');
+               var timespan    = time_list.selectedIndex >= 0 ? time_list.options[time_list.selectedIndex].value : '';
+               var tinyLegend  = document.getElementById('ge_tinylegend').checked;
+               var logarithmic = document.getElementById('ge_logarithmic').checked
+               var imgs = graphElement.getElementsByTagName('img');
+               var imgCnt = imgs.length;
+               var ref_img     = null;
+               while (imgCnt > 0)
+                       if (imgs[--imgCnt].parentNode.getAttribute('class') == 'graph') {
+                               var now    = new Date();
+                               var newTS  = '&ts='+now.getTime();
+                               var oldSrc = imgs[imgCnt].src;
+                               var newSrc = oldSrc.replace(/&ts=[^&]*/, newTS);
+                               if (newSrc == oldSrc)
+                                       newSrc = newSrc+newTS;
+                               newSrc     = newSrc.replace(/&logarithmic=[^&]*/, '');
+                               if (logarithmic)
+                                       newSrc += '&logarithmic=1';
+                               newSrc     = newSrc.replace(/&tinylegend=[^&]*/, '');
+                               if (tinyLegend)
+                                       newSrc += '&tinylegend=1';
+                               newSrc     = newSrc.replace(/&timespan=[^&]*/, '');
+                               if (timespan)
+                                       newSrc += '&timespan='+encodeURIComponent(timespan);
+                               imgs[imgCnt].setAttribute('src', newSrc);
+
+                               var myList = Array();
+                               for (i = 0; i < graphList.length; i++)
+                                       if (graphList[i].substring(0, graphId.length) == graphId && graphList[i].charAt(graphId.length) == ' ') {
+                                               newSrc = graphList[i];
+                                               newSrc = newSrc.replace(/&logarithmic=[^&]*/, '');
+                                               newSrc = newSrc.replace(/&tinylegend=[^&]*/, '');
+                                               newSrc = newSrc.replace(/&timespan=[^&]*/, '');
+                                               newSrc = newSrc+(logarithmic ? '&logarithmic=1' : '')+(tinyLegend ? '&tinylegend=1' : '')+'&timespan='+encodeURIComponent(timespan);
+                                               myList.push(newSrc);
+                                               continue;
+                                       } else
+                                               myList.push(graphList[i]);
+                               graphList = myList;
+                               window.setTimeout("GraphPositionToolbox(null)", 10);
+                               // GraphPositionToolbox(imgs[imgCnt]);
+                               break;
+                       }
+       }
+}
+
+function GraphRemove(graph) {
+       var graphs = document.getElementById('graphs');
+       var graphId = graph == null ? document.getElementById('ge_graphid').value : graph;
+       var graphElement = document.getElementById(graphId);
+       if (graphElement) {
+               GraphToggleTools('');
+               graphs.removeChild(graphElement);
+               RefreshButtons();
+               if (graphs.getElementsByTagName('div').length == 1)
+                       document.getElementById('nograph').style.display = 'block';
+
+               var myList = Array();
+               for (i = 0; i < graphList.length; i++)
+                       if (graphList[i].substring(0, graphId.length) == graphId && graphList[i].charAt(graphId.length) == ' ')
+                               continue;
+                       else
+                               myList.push(graphList[i]);
+               graphList = myList;
+       }
+}
+
+function GraphMoveUp(graph) {
+       var graphs    = document.getElementById('graphs');
+       var graphId   = graph == null ? document.getElementById('ge_graphid').value : graph;
+       var childCnt  = graphs.childNodes.length;
+       var prevGraph = null;
+       for (i = 0; i < childCnt; i++)
+               if (graphs.childNodes[i].nodeName == 'div' || graphs.childNodes[i].nodeName == 'DIV') {
+                       if (graphs.childNodes[i].id == 'nograph') {
+                               // Skip
+                       } else if (graphs.childNodes[i].id == graphId) {
+                               var myGraph = graphs.childNodes[i];
+                               if (prevGraph) {
+                                       graphs.removeChild(myGraph);
+                                       graphs.insertBefore(myGraph, prevGraph);
+                               }
+                               break;
+                       } else
+                               prevGraph = graphs.childNodes[i];
+               }
+       for (i = 0; i < graphList.length; i++)
+               if (graphList[i].substring(0, graphId.length) == graphId && graphList[i].charAt(graphId.length) == ' ') {
+                       if (i > 0) {
+                               var tmp = graphList[i-1];
+                               graphList[i-1] = graphList[i];
+                               graphList[i]   = tmp;
+                       }
+                       break;
+               }
+       GraphPositionToolbox(null);
+}
+
+function GraphMoveDown(graph) {
+       var graphs    = document.getElementById('graphs');
+       var graphId   = graph == null ? document.getElementById('ge_graphid').value : graph;
+       var childCnt  = graphs.childNodes.length;
+       var nextGraph = null;
+       var myGraph   = null;
+       for (i = 0; i < childCnt; i++)
+               if (graphs.childNodes[i].nodeName == 'div' || graphs.childNodes[i].nodeName == 'DIV') {
+                       if (graphs.childNodes[i].id == 'nograph') {
+                               // Skip
+                       } else if (graphs.childNodes[i].id == graphId) {
+                               myGraph = graphs.childNodes[i];
+                       } else if (myGraph) {
+                               nextGraph = graphs.childNodes[i];
+                               graphs.removeChild(nextGraph);
+                               graphs.insertBefore(nextGraph, myGraph);
+                               break;
+                       }
+               }
+       for (i = 0; i < graphList.length; i++)
+               if (graphList[i].substring(0, graphId.length) == graphId && graphList[i].charAt(graphId.length) == ' ') {
+                       if (i+1 < graphList.length) {
+                               var tmp = graphList[i+1];
+                               graphList[i+1] = graphList[i];
+                               graphList[i]   = tmp;
+                       }
+                       break;
+               }
+       GraphPositionToolbox(null);
+}
+
+function GraphListFromCookie(lname) {
+       if (document.cookie.length > 0) {
+               var cname= 'graphLst'+lname+'=';
+               var cookies = document.cookie.split('; ');
+               for (i = 0; i < cookies.length; i++)
+                       if (cookies[i].substring(0, cname.length) == cname)
+                               return cookies[i].substring(cname.length).split('/');
+       }
+       return new Array();
+}
+
+function GraphListNameSort(a, b) {
+       if (a[0] > b[0])
+               return 1
+       else if (a[0] < b[0])
+               return -1;
+       else
+               return 0;
+}
+
+function GraphListRefresh() {
+       var select   = document.getElementById('GraphList');
+       var childCnt = select.childNodes.length;
+       var oldValue = select.selectedIndex > 0 ? select.options[select.selectedIndex].value : '/';
+       while (childCnt > 0)
+               select.removeChild(select.childNodes[--childCnt]);
+
+       // Determine available names
+       var options = new Array();
+       if (document.cookie.length > 0) {
+               var cookies = document.cookie.split('; ');
+               for (i = 0; i < cookies.length; i++)
+                       if (cookies[i].substring(0, 8) == 'graphLst') {
+                               var p = cookies[i].indexOf('=');
+                               if (p < 0)
+                                       continue;
+                               options.push(new Array(cookies[i].substring(8, p), cookies[i].substring(p+1).split('/').length));
+                       }
+       }
+       options.sort(GraphListNameSort);
+
+       var optCnt  = options ? options.length : 0;
+       if (optCnt == 0) {
+               select.setAttribute('disabled', 'disabled');
+               return -1;
+       } else {
+               select.removeAttribute('disabled');
+               for (i = 0; i < optCnt; i++) {
+                       newOption = document.createElement("option");
+                       newOption.value = options[i][0];
+                       if (newOption.value == oldValue)
+                               newOption.setAttribute('selected', 'selected');
+                       if (options[i][1] == 1)
+                               newOption.appendChild(document.createTextNode(newOption.value+' (1 graph)'));
+                       else
+                               newOption.appendChild(document.createTextNode(newOption.value+' ('+options[i][1]+' graphs)'));
+                       select.appendChild(newOption);
+               }
+               return select.selectedIndex;
+       }
+}
+
+function GraphListCheckName(doalert) {
+       var lname = document.getElementById('GraphListName');
+       if (lname) {
+               if (lname.value.match(/^[a-zA-Z0-9_-]+$/)) {
+                       lname.style.backgroundColor = '';
+                       return lname.value;
+               } else {
+                       lname.style.backgroundColor = '#ffdddd';
+                       if (doalert && lname.value.length == 0)
+                               alert('Graph list name is empty.\n\n'+
+                                     'Please fill in a name and try again.');
+                       else if (doalert)
+                               alert('Graph list name contains non-permitted character.\n\n'+
+                                     'Only anlphanumerical characters (a-z, A-Z, 0-9), hyphen (-) and underscore (_) are permitted.\n'+
+                                     'Please correct and try again.');
+                       lname.focus();
+               }
+       }
+       return '';
+}
+
+function GraphSave() {
+       var lstName = GraphListCheckName(true);
+       if (lstName.length == 0)
+               return;
+       if (graphList.length > 0) {
+               // Save graph list to cookie
+               var str = '';
+               for (i = 0; i < graphList.length; i++) {
+                       var g = graphList[i].indexOf(' ');
+                       if (i > 0)
+                               str += '/';
+                       str += graphList[i].substring(g+1);
+               }
+
+               document.cookie = 'graphLst'+lstName+'='+str;
+               if (GraphListFromCookie(lstName).length == 0)
+                       alert("Failed to save graph list '"+lstName+"' to cookie.");
+               else
+                       alert("Successfully saved current graph list.");
+       } else {
+               document.cookie = 'graphLst'+lstName+'=; expires='+new Date().toGMTString();
+               alert("Cleared saved graph list.");
+       }
+       GraphListRefresh();
+}
+
+function GraphDrop() {
+       var cname = document.getElementById('GraphList');
+       if (cname && cname.selectedIndex >= 0) {
+               cname = cname.options[cname.selectedIndex].value;
+               document.cookie = 'graphLst'+cname+'=; expires='+new Date().toGMTString();
+               GraphListRefresh();
+       } else
+               return;
+}
+
+function GraphLoad() {
+       var cname = document.getElementById('GraphList');
+       if (cname && cname.selectedIndex >= 0)
+               cname = cname.options[cname.selectedIndex].value;
+       else
+               return;
+       // Load graph list from cookie
+       var grLst = GraphListFromCookie(cname);
+       var oldLength = graphList.length;
+       for (i = 0; i < grLst.length; i++) {
+               var host        = '';
+               var plugin      = '';
+               var pinst       = '';
+               var type        = '';
+               var tinst       = '';
+               var timespan    = '';
+               var logarithmic = false;
+               var tinyLegend  = false;
+               var graph = grLst[i].split('&');
+               for (j = 0; j < graph.length; j++)
+                       if (graph[j] == 'logarithmic=1')
+                               logarithmic = true;
+                       else if (graph[j] == 'tinylegend=1')
+                               tinyLegend = true;
+                       else if (graph[j].substring(0, 9) == 'timespan=')
+                               timespan = decodeURIComponent(graph[j].substring(9));
+               graph = decodeURIComponent(graph[0]).split('/');
+               host = graph[0];
+               if (graph.length > 1) {
+                       var g = graph[1].indexOf('-');
+                       if (g >= 0) {
+                               plugin = graph[1].substring(0, g);
+                               pinst  = graph[1].substring(g+1);
+                       } else
+                               plugin = graph[1];
+               }
+               if (graph.length > 2) {
+                       var g = graph[2].indexOf('-');
+                       if (g >= 0) {
+                               type  = graph[2].substring(0, g);
+                               tinst = graph[2].substring(g+1);
+                       } else
+                               type  = graph[2];
+               }
+
+               if (host && plugin && type)
+                       GraphDoAppend(host, plugin, pinst, type, tinst, timespan, tinyLegend, logarithmic);
+       }
+       if (grLst.length == 0)
+               alert("No list '"+cname+"' found for loading.");
+       else if (grLst.length + oldLength != graphList.length)
+               alert("Could not load all graphs, probably damaged cookie.");
+}
+
diff --git a/contrib/php-collection/config.php b/contrib/php-collection/config.php
new file mode 100644 (file)
index 0000000..dbd0aab
--- /dev/null
@@ -0,0 +1,58 @@
+<?php // vim:fenc=utf-8:filetype=php:ts=4
+/**
+ * Configuration file for Collectd graph browser
+ */
+
+// Array of paths when collectd's rrdtool plugin writes RRDs
+$config['datadirs']   = array('/var/lib/collectd/rrd/');
+// Width of graph to be generated by rrdgraph
+$config['rrd_width']  = 600;
+// Height of graph to be generated by rrdgraph
+$config['rrd_height'] = 120;
+// List of supported timespans (used for period drop-down list)
+$config['timespan']   = array(
+       array('name'=>'hour',  'label'=>'past hour',  'seconds'=>3600),
+       array('name'=>'day',   'label'=>'past day',   'seconds'=>86400),
+       array('name'=>'week',  'label'=>'past week',  'seconds'=>604800),
+       array('name'=>'month', 'label'=>'past month', 'seconds'=>2678400),
+       array('name'=>'year',  'label'=>'past year',  'seconds'=>31622400));
+// Interval at which values are collectd (currently ignored)
+$config['rrd_interval']  = 10;
+// Average rows/rra (currently ignored)
+$config['rrd_rows']      = 2400;
+// Additional options to pass to rrdgraph
+$config['rrd_opts']      = array();
+// Predefined set of colors for use by collectd_draw_rrd()
+$config['rrd_colors']    = array(
+                'h_1'=>'F7B7B7',  'f_1'=>'FF0000', // Red
+                'h_2'=>'B7EFB7',  'f_2'=>'00E000', // Green
+                'h_3'=>'B7B7F7',  'f_3'=>'0000FF', // Blue
+                'h_4'=>'F3DFB7',  'f_4'=>'F0A000', // Yellow
+                'h_5'=>'B7DFF7',  'f_5'=>'00A0FF', // Cyan
+                'h_6'=>'DFB7F7',  'f_6'=>'A000FF', // Magenta
+                'h_7'=>'FFC782',  'f_7'=>'FF8C00', // Orange
+                'h_8'=>'DCFF96',  'f_8'=>'AAFF00', // Lime
+                'h_9'=>'83FFCD',  'f_9'=>'00FF99',
+               'h_10'=>'81D9FF', 'f_10'=>'00B2FF',
+               'h_11'=>'FF89F5', 'f_11'=>'FF00EA',
+               'h_12'=>'FF89AE', 'f_12'=>'FF0051',
+               'h_13'=>'BBBBBB', 'f_13'=>'555555',
+       );
+/*
+ * URL to collectd's unix socket (unixsock plugin)
+ *  enabled:  'unix:///var/run/collectd/collectd-unixsock'
+ *  disabled: null
+ */
+$config['collectd_sock'] = null;
+/*
+ * Path to TTF font file to use in error images
+ * (fallback when file does not exist is GD fixed font)
+ */
+$config['error_font']    = '/usr/share/fonts/corefonts/arial.ttf';
+
+/*
+ * Constant defining full path to rrdtool
+ */
+define('RRDTOOL', '/usr/bin/rrdtool');
+
+?>
diff --git a/contrib/php-collection/definitions.local.php b/contrib/php-collection/definitions.local.php
new file mode 100644 (file)
index 0000000..4218ae4
--- /dev/null
@@ -0,0 +1,79 @@
+<?php // vim:fenc=utf-8:filetype=php:ts=4
+/*
+ * Copyright (C) 2009  Bruno Prémont <bonbons AT linux-vserver.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+function load_graph_definitions_local($logarithmic = false, $tinylegend = false) {
+       global $GraphDefs, $MetaGraphDefs;
+
+       // Define 1-rrd Graph definitions here
+       $GraphDefs['local_type'] = array(
+               '-v', 'Commits',
+               'DEF:avg={file}:value:AVERAGE',
+               'DEF:min={file}:value:MIN',
+               'DEF:max={file}:value:MAX',
+               "AREA:max#B7B7F7",
+               "AREA:min#FFFFFF",
+               "LINE1:avg#0000FF:Commits",
+               'GPRINT:min:MIN:%6.1lf Min,',
+               'GPRINT:avg:AVERAGE:%6.1lf Avg,',
+               'GPRINT:max:MAX:%6.1lf Max,',
+               'GPRINT:avg:LAST:%6.1lf Last\l');
+
+       // Define MetaGraph definition type -> function mappings here
+       $MetaGraphDefs['local_meta'] = 'meta_graph_local';
+}
+
+function meta_graph_local($host, $plugin, $plugin_instance, $type, $type_instances, $opts = array()) {
+       global $config;
+       $sources = array();
+
+       $title = "$host/$plugin".(!is_null($plugin_instance) ? "-$plugin_instance" : '')."/$type";
+       if (!isset($opts['title']))
+               $opts['title'] = $title;
+       $opts['rrd_opts'] = array('-v', 'Events');
+
+       $files = array();
+/*     $opts['colors'] = array(
+               'ham'     => '00e000',
+               'spam'    => '0000ff',
+               'malware' => '990000',
+
+               'sent'     => '00e000',
+               'deferred' => 'a0e000',
+               'reject'   => 'ff0000',
+               'bounced'  => 'a00050'
+       );
+
+       $type_instances = array('ham', 'spam', 'malware',  'sent', 'deferred', 'reject', 'bounced'); */
+       foreach ($type_instances as $inst) {
+               $file  = '';
+               foreach ($config['datadirs'] as $datadir)
+                       if (is_file($datadir.'/'.$title.'-'.$inst.'.rrd')) {
+                               $file = $datadir.'/'.$title.'-'.$inst.'.rrd';
+                               break;
+                       }
+               if ($file == '')
+                       continue;
+
+               $sources[] = array('name'=>$inst, 'file'=>$file);
+       }
+
+//     return collectd_draw_meta_stack($opts, $sources);
+       return collectd_draw_meta_line($opts, $sources);
+}
+
+?>
diff --git a/contrib/php-collection/definitions.php b/contrib/php-collection/definitions.php
new file mode 100644 (file)
index 0000000..c84aabe
--- /dev/null
@@ -0,0 +1,2105 @@
+<?php // vim:fenc=utf-8:filetype=php:ts=4
+/*
+ * Copyright (C) 2009  Bruno Prémont <bonbons AT linux-vserver.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ *
+ * Most RRD Graph definitions copied from collection.cgi
+ */
+$GraphDefs     = array();
+$MetaGraphDefs = array();
+
+if (is_file('definitions.local.php'))
+       require_once('definitions.local.php');
+
+function load_graph_definitions($logarithmic = false, $tinylegend = false) {
+       global $GraphDefs, $MetaGraphDefs;
+
+       $Canvas   = 'FFFFFF';
+
+       $FullRed    = 'FF0000';
+       $FullGreen  = '00E000';
+       $FullBlue   = '0000FF';
+       $FullYellow = 'F0A000';
+       $FullCyan   = '00A0FF';
+       $FullMagenta= 'A000FF';
+
+       $HalfRed    = 'F7B7B7';
+       $HalfGreen  = 'B7EFB7';
+       $HalfBlue   = 'B7B7F7';
+       $HalfYellow = 'F3DFB7';
+       $HalfCyan   = 'B7DFF7';
+       $HalfMagenta= 'DFB7F7';
+
+       $HalfBlueGreen = '89B3C9';
+
+       $GraphDefs = array();
+       $GraphDefs['apache_bytes'] = array(
+               '-v', 'Bits/s',
+               'DEF:min_raw={file}:count:MIN',
+               'DEF:avg_raw={file}:count:AVERAGE',
+               'DEF:max_raw={file}:count:MAX',
+               'CDEF:min=min_raw,8,*',
+               'CDEF:avg=avg_raw,8,*',
+               'CDEF:max=max_raw,8,*',
+               'CDEF:mytime=avg_raw,TIME,TIME,IF',
+               'CDEF:sample_len_raw=mytime,PREV(mytime),-',
+               'CDEF:sample_len=sample_len_raw,UN,0,sample_len_raw,IF',
+               'CDEF:avg_sample=avg_raw,UN,0,avg_raw,IF,sample_len,*',
+               'CDEF:avg_sum=PREV,UN,0,PREV,IF,avg_sample,+',
+               "AREA:avg#$HalfBlue",
+               "LINE1:avg#$FullBlue:Bit/s",
+               'GPRINT:min:MIN:%5.1lf%s Min,',
+               'GPRINT:avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:max:MAX:%5.1lf%s Max,',
+               'GPRINT:avg:LAST:%5.1lf%s Last',
+               'GPRINT:avg_sum:LAST:(ca. %5.1lf%sB Total)\l');
+       $GraphDefs['apache_requests'] = array(
+               '-v', 'Requests/s',
+               'DEF:min={file}:count:MIN',
+               'DEF:avg={file}:count:AVERAGE',
+               'DEF:max={file}:count:MAX',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Requests/s",
+               'GPRINT:min:MIN:%6.2lf Min,',
+               'GPRINT:avg:AVERAGE:%6.2lf Avg,',
+               'GPRINT:max:MAX:%6.2lf Max,',
+               'GPRINT:avg:LAST:%6.2lf Last');
+       $GraphDefs['apache_scoreboard'] = array(
+               'DEF:min={file}:count:MIN',
+               'DEF:avg={file}:count:AVERAGE',
+               'DEF:max={file}:count:MAX',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Processes",
+               'GPRINT:min:MIN:%6.2lf Min,',
+               'GPRINT:avg:AVERAGE:%6.2lf Avg,',
+               'GPRINT:max:MAX:%6.2lf Max,',
+               'GPRINT:avg:LAST:%6.2lf Last');
+       $GraphDefs['bitrate'] = array(
+               '-v', 'Bits/s',
+               'DEF:avg={file}:value:AVERAGE',
+               'DEF:min={file}:value:MIN',
+               'DEF:max={file}:value:MAX',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Bits/s",
+               'GPRINT:min:MIN:%5.1lf%s Min,',
+               'GPRINT:avg:AVERAGE:%5.1lf%s Average,',
+               'GPRINT:max:MAX:%5.1lf%s Max,',
+               'GPRINT:avg:LAST:%5.1lf%s Last\l');
+       $GraphDefs['charge'] = array(
+               '-v', 'Ah',
+               'DEF:avg={file}:value:AVERAGE',
+               'DEF:min={file}:value:MIN',
+               'DEF:max={file}:value:MAX',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Charge",
+               'GPRINT:min:MIN:%5.1lf%sAh Min,',
+               'GPRINT:avg:AVERAGE:%5.1lf%sAh Avg,',
+               'GPRINT:max:MAX:%5.1lf%sAh Max,',
+               'GPRINT:avg:LAST:%5.1lf%sAh Last\l');
+       $GraphDefs['counter'] = array(
+               '-v', 'Events',
+               'DEF:avg={file}:value:AVERAGE',
+               'DEF:min={file}:value:MIN',
+               'DEF:max={file}:value:MAX',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Percent",
+               'GPRINT:min:MIN:%6.2lf%% Min,',
+               'GPRINT:avg:AVERAGE:%6.2lf%% Avg,',
+               'GPRINT:max:MAX:%6.2lf%% Max,',
+               'GPRINT:avg:LAST:%6.2lf%% Last\l');
+               $GraphDefs['cpu'] = array(
+               '-v', 'CPU load',
+               'DEF:avg={file}:value:AVERAGE',
+               'DEF:min={file}:value:MIN',
+               'DEF:max={file}:value:MAX',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Percent",
+               'GPRINT:min:MIN:%6.2lf%% Min,',
+               'GPRINT:avg:AVERAGE:%6.2lf%% Avg,',
+               'GPRINT:max:MAX:%6.2lf%% Max,',
+               'GPRINT:avg:LAST:%6.2lf%% Last\l');
+       $GraphDefs['current'] = array(
+               '-v', 'Ampere',
+               'DEF:avg={file}:value:AVERAGE',
+               'DEF:min={file}:value:MIN',
+               'DEF:max={file}:value:MAX',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Current",
+               'GPRINT:min:MIN:%5.1lf%sA Min,',
+               'GPRINT:avg:AVERAGE:%5.1lf%sA Avg,',
+               'GPRINT:max:MAX:%5.1lf%sA Max,',
+               'GPRINT:avg:LAST:%5.1lf%sA Last\l');
+       $GraphDefs['df'] = array(
+               '-v', 'Percent', '-l', '0',
+               'DEF:free_avg={file}:free:AVERAGE',
+               'DEF:free_min={file}:free:MIN',
+               'DEF:free_max={file}:free:MAX',
+               'DEF:used_avg={file}:used:AVERAGE',
+               'DEF:used_min={file}:used:MIN',
+               'DEF:used_max={file}:used:MAX',
+               'CDEF:total=free_avg,used_avg,+',
+               'CDEF:free_pct=100,free_avg,*,total,/',
+               'CDEF:used_pct=100,used_avg,*,total,/',
+               'CDEF:free_acc=free_pct,used_pct,+',
+               'CDEF:used_acc=used_pct',
+               "AREA:free_acc#$HalfGreen",
+               "AREA:used_acc#$HalfRed",
+               "LINE1:free_acc#$FullGreen:Free",
+               'GPRINT:free_min:MIN:%5.1lf%sB Min,',
+               'GPRINT:free_avg:AVERAGE:%5.1lf%sB Avg,',
+               'GPRINT:free_max:MAX:%5.1lf%sB Max,',
+               'GPRINT:free_avg:LAST:%5.1lf%sB Last\l',
+               "LINE1:used_acc#$FullRed:Used",
+               'GPRINT:used_min:MIN:%5.1lf%sB Min,',
+               'GPRINT:used_avg:AVERAGE:%5.1lf%sB Avg,',
+               'GPRINT:used_max:MAX:%5.1lf%sB Max,',
+               'GPRINT:used_avg:LAST:%5.1lf%sB Last\l');
+       $GraphDefs['disk'] = array(
+               'DEF:rtime_avg={file}:rtime:AVERAGE',
+               'DEF:rtime_min={file}:rtime:MIN',
+               'DEF:rtime_max={file}:rtime:MAX',
+               'DEF:wtime_avg={file}:wtime:AVERAGE',
+               'DEF:wtime_min={file}:wtime:MIN',
+               'DEF:wtime_max={file}:wtime:MAX',
+               'CDEF:rtime_avg_ms=rtime_avg,1000,/',
+               'CDEF:rtime_min_ms=rtime_min,1000,/',
+               'CDEF:rtime_max_ms=rtime_max,1000,/',
+               'CDEF:wtime_avg_ms=wtime_avg,1000,/',
+               'CDEF:wtime_min_ms=wtime_min,1000,/',
+               'CDEF:wtime_max_ms=wtime_max,1000,/',
+               'CDEF:total_avg_ms=rtime_avg_ms,wtime_avg_ms,+',
+               'CDEF:total_min_ms=rtime_min_ms,wtime_min_ms,+',
+               'CDEF:total_max_ms=rtime_max_ms,wtime_max_ms,+',
+               "AREA:total_max_ms#$HalfRed",
+               "AREA:total_min_ms#$Canvas",
+               "LINE1:wtime_avg_ms#$FullGreen:Write",
+               'GPRINT:wtime_min_ms:MIN:%5.1lf%s Min,',
+               'GPRINT:wtime_avg_ms:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:wtime_max_ms:MAX:%5.1lf%s Max,',
+               'GPRINT:wtime_avg_ms:LAST:%5.1lf%s Last\n',
+               "LINE1:rtime_avg_ms#$FullBlue:Read ",
+               'GPRINT:rtime_min_ms:MIN:%5.1lf%s Min,',
+               'GPRINT:rtime_avg_ms:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:rtime_max_ms:MAX:%5.1lf%s Max,',
+               'GPRINT:rtime_avg_ms:LAST:%5.1lf%s Last\n',
+               "LINE1:total_avg_ms#$FullRed:Total",
+               'GPRINT:total_min_ms:MIN:%5.1lf%s Min,',
+               'GPRINT:total_avg_ms:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:total_max_ms:MAX:%5.1lf%s Max,',
+               'GPRINT:total_avg_ms:LAST:%5.1lf%s Last');
+       $GraphDefs['disk_octets'] = array(
+               '-v', 'Bytes/s', '--units=si',
+               'DEF:out_min={file}:write:MIN',
+               'DEF:out_avg={file}:write:AVERAGE',
+               'DEF:out_max={file}:write:MAX',
+               'DEF:inc_min={file}:read:MIN',
+               'DEF:inc_avg={file}:read:AVERAGE',
+               'DEF:inc_max={file}:read:MAX',
+               'CDEF:overlap=out_avg,inc_avg,GT,inc_avg,out_avg,IF',
+               'CDEF:mytime=out_avg,TIME,TIME,IF',
+               'CDEF:sample_len_raw=mytime,PREV(mytime),-',
+               'CDEF:sample_len=sample_len_raw,UN,0,sample_len_raw,IF',
+               'CDEF:out_avg_sample=out_avg,UN,0,out_avg,IF,sample_len,*',
+               'CDEF:out_avg_sum=PREV,UN,0,PREV,IF,out_avg_sample,+',
+               'CDEF:inc_avg_sample=inc_avg,UN,0,inc_avg,IF,sample_len,*',
+               'CDEF:inc_avg_sum=PREV,UN,0,PREV,IF,inc_avg_sample,+',
+               "AREA:out_avg#$HalfGreen",
+               "AREA:inc_avg#$HalfBlue",
+               "AREA:overlap#$HalfBlueGreen",
+               "LINE1:out_avg#$FullGreen:Written",
+               'GPRINT:out_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:out_max:MAX:%5.1lf%s Max,',
+               'GPRINT:out_avg:LAST:%5.1lf%s Last',
+               'GPRINT:out_avg_sum:LAST:(ca. %5.1lf%sB Total)\l',
+               "LINE1:inc_avg#$FullBlue:Read   ",
+               'GPRINT:inc_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:inc_max:MAX:%5.1lf%s Max,',
+               'GPRINT:inc_avg:LAST:%5.1lf%s Last',
+               'GPRINT:inc_avg_sum:LAST:(ca. %5.1lf%sB Total)\l');
+       $GraphDefs['disk_merged'] = array(
+               '-v', 'Merged Ops/s', '--units=si',
+               'DEF:out_min={file}:write:MIN',
+               'DEF:out_avg={file}:write:AVERAGE',
+               'DEF:out_max={file}:write:MAX',
+               'DEF:inc_min={file}:read:MIN',
+               'DEF:inc_avg={file}:read:AVERAGE',
+               'DEF:inc_max={file}:read:MAX',
+               'CDEF:overlap=out_avg,inc_avg,GT,inc_avg,out_avg,IF',
+               "AREA:out_avg#$HalfGreen",
+               "AREA:inc_avg#$HalfBlue",
+               "AREA:overlap#$HalfBlueGreen",
+               "LINE1:out_avg#$FullGreen:Written",
+               'GPRINT:out_avg:AVERAGE:%6.2lf Avg,',
+               'GPRINT:out_max:MAX:%6.2lf Max,',
+               'GPRINT:out_avg:LAST:%6.2lf Last\l',
+               "LINE1:inc_avg#$FullBlue:Read   ",
+               'GPRINT:inc_avg:AVERAGE:%6.2lf Avg,',
+               'GPRINT:inc_max:MAX:%6.2lf Max,',
+               'GPRINT:inc_avg:LAST:%6.2lf Last\l');
+               $GraphDefs['disk_ops'] = array(
+               '-v', 'Ops/s', '--units=si',
+               'DEF:out_min={file}:write:MIN',
+               'DEF:out_avg={file}:write:AVERAGE',
+               'DEF:out_max={file}:write:MAX',
+               'DEF:inc_min={file}:read:MIN',
+               'DEF:inc_avg={file}:read:AVERAGE',
+               'DEF:inc_max={file}:read:MAX',
+               'CDEF:overlap=out_avg,inc_avg,GT,inc_avg,out_avg,IF',
+               "AREA:out_avg#$HalfGreen",
+               "AREA:inc_avg#$HalfBlue",
+               "AREA:overlap#$HalfBlueGreen",
+               "LINE1:out_avg#$FullGreen:Written",
+               'GPRINT:out_avg:AVERAGE:%6.2lf Avg,',
+               'GPRINT:out_max:MAX:%6.2lf Max,',
+               'GPRINT:out_avg:LAST:%6.2lf Last\l',
+               "LINE1:inc_avg#$FullBlue:Read   ",
+               'GPRINT:inc_avg:AVERAGE:%6.2lf Avg,',
+               'GPRINT:inc_max:MAX:%6.2lf Max,',
+               'GPRINT:inc_avg:LAST:%6.2lf Last\l');
+       $GraphDefs['disk_time'] = array(
+               '-v', 'Seconds/s',
+               'DEF:out_min_raw={file}:write:MIN',
+               'DEF:out_avg_raw={file}:write:AVERAGE',
+               'DEF:out_max_raw={file}:write:MAX',
+               'DEF:inc_min_raw={file}:read:MIN',
+               'DEF:inc_avg_raw={file}:read:AVERAGE',
+               'DEF:inc_max_raw={file}:read:MAX',
+               'CDEF:out_min=out_min_raw,1000,/',
+               'CDEF:out_avg=out_avg_raw,1000,/',
+               'CDEF:out_max=out_max_raw,1000,/',
+               'CDEF:inc_min=inc_min_raw,1000,/',
+               'CDEF:inc_avg=inc_avg_raw,1000,/',
+               'CDEF:inc_max=inc_max_raw,1000,/',
+               'CDEF:overlap=out_avg,inc_avg,GT,inc_avg,out_avg,IF',
+               "AREA:out_avg#$HalfGreen",
+               "AREA:inc_avg#$HalfBlue",
+               "AREA:overlap#$HalfBlueGreen",
+               "LINE1:out_avg#$FullGreen:Written",
+               'GPRINT:out_avg:AVERAGE:%5.1lf%ss Avg,',
+               'GPRINT:out_max:MAX:%5.1lf%ss Max,',
+               'GPRINT:out_avg:LAST:%5.1lf%ss Last\l',
+               "LINE1:inc_avg#$FullBlue:Read   ",
+               'GPRINT:inc_avg:AVERAGE:%5.1lf%ss Avg,',
+               'GPRINT:inc_max:MAX:%5.1lf%ss Max,',
+               'GPRINT:inc_avg:LAST:%5.1lf%ss Last\l');
+       $GraphDefs['dns_traffic'] = array(
+               'DEF:rsp_min_raw={file}:responses:MIN',
+               'DEF:rsp_avg_raw={file}:responses:AVERAGE',
+               'DEF:rsp_max_raw={file}:responses:MAX',
+               'DEF:qry_min_raw={file}:queries:MIN',
+               'DEF:qry_avg_raw={file}:queries:AVERAGE',
+               'DEF:qry_max_raw={file}:queries:MAX',
+               'CDEF:rsp_min=rsp_min_raw,8,*',
+               'CDEF:rsp_avg=rsp_avg_raw,8,*',
+               'CDEF:rsp_max=rsp_max_raw,8,*',
+               'CDEF:qry_min=qry_min_raw,8,*',
+               'CDEF:qry_avg=qry_avg_raw,8,*',
+               'CDEF:qry_max=qry_max_raw,8,*',
+               'CDEF:overlap=rsp_avg,qry_avg,GT,qry_avg,rsp_avg,IF',
+               'CDEF:mytime=rsp_avg_raw,TIME,TIME,IF',
+               'CDEF:sample_len_raw=mytime,PREV(mytime),-',
+               'CDEF:sample_len=sample_len_raw,UN,0,sample_len_raw,IF',
+               'CDEF:rsp_avg_sample=rsp_avg_raw,UN,0,rsp_avg_raw,IF,sample_len,*',
+               'CDEF:rsp_avg_sum=PREV,UN,0,PREV,IF,rsp_avg_sample,+',
+               'CDEF:qry_avg_sample=qry_avg_raw,UN,0,qry_avg_raw,IF,sample_len,*',
+               'CDEF:qry_avg_sum=PREV,UN,0,PREV,IF,qry_avg_sample,+',
+               "AREA:rsp_avg#$HalfGreen",
+               "AREA:qry_avg#$HalfBlue",
+               "AREA:overlap#$HalfBlueGreen",
+               "LINE1:rsp_avg#$FullGreen:Responses",
+               'GPRINT:rsp_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:rsp_max:MAX:%5.1lf%s Max,',
+               'GPRINT:rsp_avg:LAST:%5.1lf%s Last',
+               'GPRINT:rsp_avg_sum:LAST:(ca. %5.1lf%sB Total)\l',
+               "LINE1:qry_avg#$FullBlue:Queries  ",
+//                     'GPRINT:qry_min:MIN:%5.1lf %s Min,',
+               'GPRINT:qry_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:qry_max:MAX:%5.1lf%s Max,',
+               'GPRINT:qry_avg:LAST:%5.1lf%s Last',
+               'GPRINT:qry_avg_sum:LAST:(ca. %5.1lf%sB Total)\l');
+       $GraphDefs['email_count'] = array(
+               '-v', 'Mails',
+               'DEF:avg={file}:value:AVERAGE',
+               'DEF:min={file}:value:MIN',
+               'DEF:max={file}:value:MAX',
+               "AREA:max#$HalfMagenta",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullMagenta:Count ",
+               'GPRINT:min:MIN:%4.1lf Min,',
+               'GPRINT:avg:AVERAGE:%4.1lf Avg,',
+               'GPRINT:max:MAX:%4.1lf Max,',
+               'GPRINT:avg:LAST:%4.1lf Last\l');
+       $GraphDefs['files'] = $GraphDefs['email_count'];
+       $GraphDefs['email_size'] = array(
+               '-v', 'Bytes',
+               'DEF:avg={file}:value:AVERAGE',
+               'DEF:min={file}:value:MIN',
+               'DEF:max={file}:value:MAX',
+               "AREA:max#$HalfMagenta",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullMagenta:Count ",
+               'GPRINT:min:MIN:%4.1lf Min,',
+               'GPRINT:avg:AVERAGE:%4.1lf Avg,',
+               'GPRINT:max:MAX:%4.1lf Max,',
+               'GPRINT:avg:LAST:%4.1lf Last\l');
+       $GraphDefs['bytes'] = $GraphDefs['email_size'];
+       $GraphDefs['spam_score'] = array(
+               '-v', 'Score',
+               'DEF:avg={file}:value:AVERAGE',
+               'DEF:min={file}:value:MIN',
+               'DEF:max={file}:value:MAX',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Score ",
+               'GPRINT:min:MIN:%4.1lf Min,',
+               'GPRINT:avg:AVERAGE:%4.1lf Avg,',
+               'GPRINT:max:MAX:%4.1lf Max,',
+               'GPRINT:avg:LAST:%4.1lf Last\l');
+       $GraphDefs['spam_check'] = array(
+               'DEF:avg={file}:hits:AVERAGE',
+               'DEF:min={file}:hits:MIN',
+               'DEF:max={file}:hits:MAX',
+               "AREA:max#$HalfMagenta",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullMagenta:Count ",
+               'GPRINT:min:MIN:%4.1lf Min,',
+               'GPRINT:avg:AVERAGE:%4.1lf Avg,',
+               'GPRINT:max:MAX:%4.1lf Max,',
+               'GPRINT:avg:LAST:%4.1lf Last\l');
+       $GraphDefs['conntrack'] = array(
+               '-v', 'Entries',
+               'DEF:avg={file}:entropy:AVERAGE',
+               'DEF:min={file}:entropy:MIN',
+               'DEF:max={file}:entropy:MAX',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Count",
+               'GPRINT:min:MIN:%4.0lf Min,',
+               'GPRINT:avg:AVERAGE:%4.0lf Avg,',
+               'GPRINT:max:MAX:%4.0lf Max,',
+               'GPRINT:avg:LAST:%4.0lf Last\l');
+       $GraphDefs['entropy'] = array(
+               '-v', 'Bits',
+               'DEF:avg={file}:entropy:AVERAGE',
+               'DEF:min={file}:entropy:MIN',
+               'DEF:max={file}:entropy:MAX',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Bits",
+               'GPRINT:min:MIN:%4.0lfbit Min,',
+               'GPRINT:avg:AVERAGE:%4.0lfbit Avg,',
+               'GPRINT:max:MAX:%4.0lfbit Max,',
+               'GPRINT:avg:LAST:%4.0lfbit Last\l');
+       $GraphDefs['fanspeed'] = array(
+               '-v', 'RPM',
+               'DEF:avg={file}:value:AVERAGE',
+               'DEF:min={file}:value:MIN',
+               'DEF:max={file}:value:MAX',
+               "AREA:max#$HalfMagenta",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullMagenta:RPM",
+               'GPRINT:min:MIN:%4.1lf Min,',
+               'GPRINT:avg:AVERAGE:%4.1lf Avg,',
+               'GPRINT:max:MAX:%4.1lf Max,',
+               'GPRINT:avg:LAST:%4.1lf Last\l');
+       $GraphDefs['frequency'] = array(
+               '-v', 'Hertz',
+               'DEF:avg={file}:frequency:AVERAGE',
+               'DEF:min={file}:frequency:MIN',
+               'DEF:max={file}:frequency:MAX',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Frequency [Hz]",
+               'GPRINT:min:MIN:%4.1lf Min,',
+               'GPRINT:avg:AVERAGE:%4.1lf Avg,',
+               'GPRINT:max:MAX:%4.1lf Max,',
+               'GPRINT:avg:LAST:%4.1lf Last\l');
+       $GraphDefs['frequency_offset'] = array( // NTPd
+               'DEF:ppm_avg={file}:ppm:AVERAGE',
+               'DEF:ppm_min={file}:ppm:MIN',
+               'DEF:ppm_max={file}:ppm:MAX',
+               "AREA:ppm_max#$HalfBlue",
+               "AREA:ppm_min#$Canvas",
+               "LINE1:ppm_avg#$FullBlue:{inst}",
+               'GPRINT:ppm_min:MIN:%5.2lf Min,',
+               'GPRINT:ppm_avg:AVERAGE:%5.2lf Avg,',
+               'GPRINT:ppm_max:MAX:%5.2lf Max,',
+               'GPRINT:ppm_avg:LAST:%5.2lf Last');
+       $GraphDefs['gauge'] = array(
+               '-v', 'Exec value',
+               'DEF:temp_avg={file}:value:AVERAGE',
+               'DEF:temp_min={file}:value:MIN',
+               'DEF:temp_max={file}:value:MAX',
+               "AREA:temp_max#$HalfBlue",
+               "AREA:temp_min#$Canvas",
+               "LINE1:temp_avg#$FullBlue:Exec value",
+               'GPRINT:temp_min:MIN:%6.2lf Min,',
+               'GPRINT:temp_avg:AVERAGE:%6.2lf Avg,',
+               'GPRINT:temp_max:MAX:%6.2lf Max,',
+               'GPRINT:temp_avg:LAST:%6.2lf Last\l');
+       $GraphDefs['hddtemp'] = array(
+               '-v', '°C',
+               'DEF:temp_avg={file}:value:AVERAGE',
+               'DEF:temp_min={file}:value:MIN',
+               'DEF:temp_max={file}:value:MAX',
+               "AREA:temp_max#$HalfRed",
+               "AREA:temp_min#$Canvas",
+               "LINE1:temp_avg#$FullRed:Temperature",
+               'GPRINT:temp_min:MIN:%4.1lf Min,',
+               'GPRINT:temp_avg:AVERAGE:%4.1lf Avg,',
+               'GPRINT:temp_max:MAX:%4.1lf Max,',
+               'GPRINT:temp_avg:LAST:%4.1lf Last\l');
+       $GraphDefs['humidity'] = array(
+               '-v', 'Percent',
+               'DEF:temp_avg={file}:value:AVERAGE',
+               'DEF:temp_min={file}:value:MIN',
+               'DEF:temp_max={file}:value:MAX',
+               "AREA:temp_max#$HalfGreen",
+               "AREA:temp_min#$Canvas",
+               "LINE1:temp_avg#$FullGreen:Temperature",
+               'GPRINT:temp_min:MIN:%4.1lf%% Min,',
+               'GPRINT:temp_avg:AVERAGE:%4.1lf%% Avg,',
+               'GPRINT:temp_max:MAX:%4.1lf%% Max,',
+               'GPRINT:temp_avg:LAST:%4.1lf%% Last\l');
+       $GraphDefs['if_errors'] = array(
+               '-v', 'Errors/s', '--units=si',
+               'DEF:tx_min={file}:tx:MIN',
+               'DEF:tx_avg={file}:tx:AVERAGE',
+               'DEF:tx_max={file}:tx:MAX',
+               'DEF:rx_min={file}:rx:MIN',
+               'DEF:rx_avg={file}:rx:AVERAGE',
+               'DEF:rx_max={file}:rx:MAX',
+               'CDEF:overlap=tx_avg,rx_avg,GT,rx_avg,tx_avg,IF',
+               'CDEF:mytime=tx_avg,TIME,TIME,IF',
+               'CDEF:sample_len_raw=mytime,PREV(mytime),-',
+               'CDEF:sample_len=sample_len_raw,UN,0,sample_len_raw,IF',
+               'CDEF:tx_avg_sample=tx_avg,UN,0,tx_avg,IF,sample_len,*',
+               'CDEF:tx_avg_sum=PREV,UN,0,PREV,IF,tx_avg_sample,+',
+               'CDEF:rx_avg_sample=rx_avg,UN,0,rx_avg,IF,sample_len,*',
+               'CDEF:rx_avg_sum=PREV,UN,0,PREV,IF,rx_avg_sample,+',
+               "AREA:tx_avg#$HalfGreen",
+               "AREA:rx_avg#$HalfBlue",
+               "AREA:overlap#$HalfBlueGreen",
+               "LINE1:tx_avg#$FullGreen:TX",
+               'GPRINT:tx_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:tx_max:MAX:%5.1lf%s Max,',
+               'GPRINT:tx_avg:LAST:%5.1lf%s Last',
+               'GPRINT:tx_avg_sum:LAST:(ca. %4.0lf%s Total)\l',
+               "LINE1:rx_avg#$FullBlue:RX",
+//                     'GPRINT:rx_min:MIN:%5.1lf %s Min,',
+               'GPRINT:rx_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:rx_max:MAX:%5.1lf%s Max,',
+               'GPRINT:rx_avg:LAST:%5.1lf%s Last',
+               'GPRINT:rx_avg_sum:LAST:(ca. %4.0lf%s Total)\l');
+       $GraphDefs['if_collisions'] = array(
+               '-v', 'Collisions/s', '--units=si',
+               'DEF:min_raw={file}:value:MIN',
+               'DEF:avg_raw={file}:value:AVERAGE',
+               'DEF:max_raw={file}:value:MAX',
+               'CDEF:min=min_raw,8,*',
+               'CDEF:avg=avg_raw,8,*',
+               'CDEF:max=max_raw,8,*',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Collisions/s",
+               'GPRINT:min:MIN:%5.1lf %s Min,',
+               'GPRINT:avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:max:MAX:%5.1lf%s Max,',
+               'GPRINT:avg:LAST:%5.1lf%s Last\l');
+       $GraphDefs['if_dropped'] = array(
+               '-v', 'Packets/s', '--units=si',
+               'DEF:tx_min={file}:tx:MIN',
+               'DEF:tx_avg={file}:tx:AVERAGE',
+               'DEF:tx_max={file}:tx:MAX',
+               'DEF:rx_min={file}:rx:MIN',
+               'DEF:rx_avg={file}:rx:AVERAGE',
+               'DEF:rx_max={file}:rx:MAX',
+               'CDEF:overlap=tx_avg,rx_avg,GT,rx_avg,tx_avg,IF',
+               'CDEF:mytime=tx_avg,TIME,TIME,IF',
+               'CDEF:sample_len_raw=mytime,PREV(mytime),-',
+               'CDEF:sample_len=sample_len_raw,UN,0,sample_len_raw,IF',
+               'CDEF:tx_avg_sample=tx_avg,UN,0,tx_avg,IF,sample_len,*',
+               'CDEF:tx_avg_sum=PREV,UN,0,PREV,IF,tx_avg_sample,+',
+               'CDEF:rx_avg_sample=rx_avg,UN,0,rx_avg,IF,sample_len,*',
+               'CDEF:rx_avg_sum=PREV,UN,0,PREV,IF,rx_avg_sample,+',
+               "AREA:tx_avg#$HalfGreen",
+               "AREA:rx_avg#$HalfBlue",
+               "AREA:overlap#$HalfBlueGreen",
+               "LINE1:tx_avg#$FullGreen:TX",
+               'GPRINT:tx_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:tx_max:MAX:%5.1lf%s Max,',
+               'GPRINT:tx_avg:LAST:%5.1lf%s Last',
+               'GPRINT:tx_avg_sum:LAST:(ca. %4.0lf%s Total)\l',
+               "LINE1:rx_avg#$FullBlue:RX",
+//                     'GPRINT:rx_min:MIN:%5.1lf %s Min,',
+               'GPRINT:rx_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:rx_max:MAX:%5.1lf%s Max,',
+               'GPRINT:rx_avg:LAST:%5.1lf%s Last',
+               'GPRINT:rx_avg_sum:LAST:(ca. %4.0lf%s Total)\l');
+       $GraphDefs['if_packets'] = array(
+               '-v', 'Packets/s', '--units=si',
+               'DEF:tx_min={file}:tx:MIN',
+               'DEF:tx_avg={file}:tx:AVERAGE',
+               'DEF:tx_max={file}:tx:MAX',
+               'DEF:rx_min={file}:rx:MIN',
+               'DEF:rx_avg={file}:rx:AVERAGE',
+               'DEF:rx_max={file}:rx:MAX',
+               'CDEF:overlap=tx_avg,rx_avg,GT,rx_avg,tx_avg,IF',
+               'CDEF:mytime=tx_avg,TIME,TIME,IF',
+               'CDEF:sample_len_raw=mytime,PREV(mytime),-',
+               'CDEF:sample_len=sample_len_raw,UN,0,sample_len_raw,IF',
+               'CDEF:tx_avg_sample=tx_avg,UN,0,tx_avg,IF,sample_len,*',
+               'CDEF:tx_avg_sum=PREV,UN,0,PREV,IF,tx_avg_sample,+',
+               'CDEF:rx_avg_sample=rx_avg,UN,0,rx_avg,IF,sample_len,*',
+               'CDEF:rx_avg_sum=PREV,UN,0,PREV,IF,rx_avg_sample,+',
+               "AREA:tx_avg#$HalfGreen",
+               "AREA:rx_avg#$HalfBlue",
+               "AREA:overlap#$HalfBlueGreen",
+               "LINE1:tx_avg#$FullGreen:TX",
+               'GPRINT:tx_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:tx_max:MAX:%5.1lf%s Max,',
+               'GPRINT:tx_avg:LAST:%5.1lf%s Last',
+               'GPRINT:tx_avg_sum:LAST:(ca. %4.0lf%s Total)\l',
+               "LINE1:rx_avg#$FullBlue:RX",
+//                     'GPRINT:rx_min:MIN:%5.1lf %s Min,',
+               'GPRINT:rx_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:rx_max:MAX:%5.1lf%s Max,',
+               'GPRINT:rx_avg:LAST:%5.1lf%s Last',
+               'GPRINT:rx_avg_sum:LAST:(ca. %4.0lf%s Total)\l');
+       $GraphDefs['if_rx_errors'] = array(
+               '-v', 'Errors/s', '--units=si',
+               'DEF:min={file}:value:MIN',
+               'DEF:avg={file}:value:AVERAGE',
+               'DEF:max={file}:value:MAX',
+               'CDEF:mytime=avg,TIME,TIME,IF',
+               'CDEF:sample_len_raw=mytime,PREV(mytime),-',
+               'CDEF:sample_len=sample_len_raw,UN,0,sample_len_raw,IF',
+               'CDEF:avg_sample=avg,UN,0,avg,IF,sample_len,*',
+               'CDEF:avg_sum=PREV,UN,0,PREV,IF,avg_sample,+',
+               "AREA:avg#$HalfBlue",
+               "LINE1:avg#$FullBlue:Errors/s",
+               'GPRINT:avg:AVERAGE:%3.1lf%s Avg,',
+               'GPRINT:max:MAX:%3.1lf%s Max,',
+               'GPRINT:avg:LAST:%3.1lf%s Last',
+               'GPRINT:avg_sum:LAST:(ca. %2.0lf%s Total)\l');
+       $GraphDefs['ipt_bytes'] = array(
+               '-v', 'Bits/s',
+               'DEF:min_raw={file}:value:MIN',
+               'DEF:avg_raw={file}:value:AVERAGE',
+               'DEF:max_raw={file}:value:MAX',
+               'CDEF:min=min_raw,8,*',
+               'CDEF:avg=avg_raw,8,*',
+               'CDEF:max=max_raw,8,*',
+               'CDEF:mytime=avg_raw,TIME,TIME,IF',
+               'CDEF:sample_len_raw=mytime,PREV(mytime),-',
+               'CDEF:sample_len=sample_len_raw,UN,0,sample_len_raw,IF',
+               'CDEF:avg_sample=avg_raw,UN,0,avg_raw,IF,sample_len,*',
+               'CDEF:avg_sum=PREV,UN,0,PREV,IF,avg_sample,+',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Bits/s",
+//                     'GPRINT:min:MIN:%5.1lf %s Min,',
+               'GPRINT:avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:max:MAX:%5.1lf%s Max,',
+               'GPRINT:avg:LAST:%5.1lf%s Last',
+               'GPRINT:avg_sum:LAST:(ca. %5.1lf%sB Total)\l');
+       $GraphDefs['ipt_packets'] = array(
+               '-v', 'Packets/s',
+               'DEF:min_raw={file}:value:MIN',
+               'DEF:avg_raw={file}:value:AVERAGE',
+               'DEF:max_raw={file}:value:MAX',
+               'CDEF:min=min_raw,8,*',
+               'CDEF:avg=avg_raw,8,*',
+               'CDEF:max=max_raw,8,*',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Packets/s",
+               'GPRINT:min:MIN:%5.1lf %s Min,',
+               'GPRINT:avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:max:MAX:%5.1lf%s Max,',
+               'GPRINT:avg:LAST:%5.1lf%s Last\l');
+       $GraphDefs['irq'] = array(
+               '-v', 'Issues/s',
+               'DEF:avg={file}:value:AVERAGE',
+               'DEF:min={file}:value:MIN',
+               'DEF:max={file}:value:MAX',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Issues/s",
+               'GPRINT:min:MIN:%6.2lf Min,',
+               'GPRINT:avg:AVERAGE:%6.2lf Avg,',
+               'GPRINT:max:MAX:%6.2lf Max,',
+               'GPRINT:avg:LAST:%6.2lf Last\l');
+       $GraphDefs['load'] = array(
+               '-v', 'System load',
+               'DEF:s_avg={file}:shortterm:AVERAGE',
+               'DEF:s_min={file}:shortterm:MIN',
+               'DEF:s_max={file}:shortterm:MAX',
+               'DEF:m_avg={file}:midterm:AVERAGE',
+               'DEF:m_min={file}:midterm:MIN',
+               'DEF:m_max={file}:midterm:MAX',
+               'DEF:l_avg={file}:longterm:AVERAGE',
+               'DEF:l_min={file}:longterm:MIN',
+               'DEF:l_max={file}:longterm:MAX',
+               "AREA:s_max#$HalfGreen",
+               "AREA:s_min#$Canvas",
+               "LINE1:s_avg#$FullGreen: 1m average",
+               'GPRINT:s_min:MIN:%4.2lf Min,',
+               'GPRINT:s_avg:AVERAGE:%4.2lf Avg,',
+               'GPRINT:s_max:MAX:%4.2lf Max,',
+               'GPRINT:s_avg:LAST:%4.2lf Last\n',
+               "LINE1:m_avg#$FullBlue: 5m average",
+               'GPRINT:m_min:MIN:%4.2lf Min,',
+               'GPRINT:m_avg:AVERAGE:%4.2lf Avg,',
+               'GPRINT:m_max:MAX:%4.2lf Max,',
+               'GPRINT:m_avg:LAST:%4.2lf Last\n',
+               "LINE1:l_avg#$FullRed:15m average",
+               'GPRINT:l_min:MIN:%4.2lf Min,',
+               'GPRINT:l_avg:AVERAGE:%4.2lf Avg,',
+               'GPRINT:l_max:MAX:%4.2lf Max,',
+               'GPRINT:l_avg:LAST:%4.2lf Last');
+       $GraphDefs['load_percent'] = array(
+               'DEF:avg={file}:percent:AVERAGE',
+               'DEF:min={file}:percent:MIN',
+               'DEF:max={file}:percent:MAX',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Load",
+               'GPRINT:min:MIN:%5.1lf%s%% Min,',
+               'GPRINT:avg:AVERAGE:%5.1lf%s%% Avg,',
+               'GPRINT:max:MAX:%5.1lf%s%% Max,',
+               'GPRINT:avg:LAST:%5.1lf%s%% Last\l');
+       $GraphDefs['mails'] = array(
+               'DEF:rawgood={file}:good:AVERAGE',
+               'DEF:rawspam={file}:spam:AVERAGE',
+               'CDEF:good=rawgood,UN,0,rawgood,IF',
+               'CDEF:spam=rawspam,UN,0,rawspam,IF',
+               'CDEF:negspam=spam,-1,*',
+               "AREA:good#$HalfGreen",
+               "LINE1:good#$FullGreen:Good mails",
+               'GPRINT:good:AVERAGE:%4.1lf Avg,',
+               'GPRINT:good:MAX:%4.1lf Max,',
+               'GPRINT:good:LAST:%4.1lf Last\n',
+               "AREA:negspam#$HalfRed",
+               "LINE1:negspam#$FullRed:Spam mails",
+               'GPRINT:spam:AVERAGE:%4.1lf Avg,',
+               'GPRINT:spam:MAX:%4.1lf Max,',
+               'GPRINT:spam:LAST:%4.1lf Last',
+               'HRULE:0#000000');
+       $GraphDefs['memory'] = array(
+               '-b', '1024', '-v', 'Bytes',
+               'DEF:avg={file}:value:AVERAGE',
+               'DEF:min={file}:value:MIN',
+               'DEF:max={file}:value:MAX',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Memory",
+               'GPRINT:min:MIN:%5.1lf%sbyte Min,',
+               'GPRINT:avg:AVERAGE:%5.1lf%sbyte Avg,',
+               'GPRINT:max:MAX:%5.1lf%sbyte Max,',
+               'GPRINT:avg:LAST:%5.1lf%sbyte Last\l');
+       $GraphDefs['old_memory'] = array(
+               'DEF:used_avg={file}:used:AVERAGE',
+               'DEF:free_avg={file}:free:AVERAGE',
+               'DEF:buffers_avg={file}:buffers:AVERAGE',
+               'DEF:cached_avg={file}:cached:AVERAGE',
+               'DEF:used_min={file}:used:MIN',
+               'DEF:free_min={file}:free:MIN',
+               'DEF:buffers_min={file}:buffers:MIN',
+               'DEF:cached_min={file}:cached:MIN',
+               'DEF:used_max={file}:used:MAX',
+               'DEF:free_max={file}:free:MAX',
+               'DEF:buffers_max={file}:buffers:MAX',
+               'DEF:cached_max={file}:cached:MAX',
+               'CDEF:cached_avg_nn=cached_avg,UN,0,cached_avg,IF',
+               'CDEF:buffers_avg_nn=buffers_avg,UN,0,buffers_avg,IF',
+               'CDEF:free_cached_buffers_used=free_avg,cached_avg_nn,+,buffers_avg_nn,+,used_avg,+',
+               'CDEF:cached_buffers_used=cached_avg,buffers_avg_nn,+,used_avg,+',
+               'CDEF:buffers_used=buffers_avg,used_avg,+',
+               "AREA:free_cached_buffers_used#$HalfGreen",
+               "AREA:cached_buffers_used#$HalfBlue",
+               "AREA:buffers_used#$HalfYellow",
+               "AREA:used_avg#$HalfRed",
+               "LINE1:free_cached_buffers_used#$FullGreen:Free        ",
+               'GPRINT:free_min:MIN:%5.1lf%s Min,',
+               'GPRINT:free_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:free_max:MAX:%5.1lf%s Max,',
+               'GPRINT:free_avg:LAST:%5.1lf%s Last\n',
+               "LINE1:cached_buffers_used#$FullBlue:Page cache  ",
+               'GPRINT:cached_min:MIN:%5.1lf%s Min,',
+               'GPRINT:cached_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:cached_max:MAX:%5.1lf%s Max,',
+               'GPRINT:cached_avg:LAST:%5.1lf%s Last\n',
+               "LINE1:buffers_used#$FullYellow:Buffer cache",
+               'GPRINT:buffers_min:MIN:%5.1lf%s Min,',
+               'GPRINT:buffers_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:buffers_max:MAX:%5.1lf%s Max,',
+               'GPRINT:buffers_avg:LAST:%5.1lf%s Last\n',
+               "LINE1:used_avg#$FullRed:Used        ",
+               'GPRINT:used_min:MIN:%5.1lf%s Min,',
+               'GPRINT:used_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:used_max:MAX:%5.1lf%s Max,',
+               'GPRINT:used_avg:LAST:%5.1lf%s Last');
+       $GraphDefs['mysql_commands'] = array(
+               '-v', 'Issues/s',
+               "DEF:val_avg={file}:value:AVERAGE",
+               "DEF:val_min={file}:value:MIN",
+               "DEF:val_max={file}:value:MAX",
+               "AREA:val_max#$HalfBlue",
+               "AREA:val_min#$Canvas",
+               "LINE1:val_avg#$FullBlue:Issues/s",
+               'GPRINT:val_min:MIN:%5.2lf Min,',
+               'GPRINT:val_avg:AVERAGE:%5.2lf Avg,',
+               'GPRINT:val_max:MAX:%5.2lf Max,',
+               'GPRINT:val_avg:LAST:%5.2lf Last');
+       $GraphDefs['mysql_handler'] = array(
+               '-v', 'Issues/s',
+               "DEF:val_avg={file}:value:AVERAGE",
+               "DEF:val_min={file}:value:MIN",
+               "DEF:val_max={file}:value:MAX",
+               "AREA:val_max#$HalfBlue",
+               "AREA:val_min#$Canvas",
+               "LINE1:val_avg#$FullBlue:Issues/s",
+               'GPRINT:val_min:MIN:%5.2lf Min,',
+               'GPRINT:val_avg:AVERAGE:%5.2lf Avg,',
+               'GPRINT:val_max:MAX:%5.2lf Max,',
+               'GPRINT:val_avg:LAST:%5.2lf Last');
+       $GraphDefs['mysql_octets'] = array(
+               '-v', 'Bits/s',
+               'DEF:out_min={file}:tx:MIN',
+               'DEF:out_avg={file}:tx:AVERAGE',
+               'DEF:out_max={file}:tx:MAX',
+               'DEF:inc_min={file}:rx:MIN',
+               'DEF:inc_avg={file}:rx:AVERAGE',
+               'DEF:inc_max={file}:rx:MAX',
+               'CDEF:mytime=out_avg,TIME,TIME,IF',
+               'CDEF:sample_len_raw=mytime,PREV(mytime),-',
+               'CDEF:sample_len=sample_len_raw,UN,0,sample_len_raw,IF',
+               'CDEF:out_avg_sample=out_avg,UN,0,out_avg,IF,sample_len,*',
+               'CDEF:out_avg_sum=PREV,UN,0,PREV,IF,out_avg_sample,+',
+               'CDEF:inc_avg_sample=inc_avg,UN,0,inc_avg,IF,sample_len,*',
+               'CDEF:inc_avg_sum=PREV,UN,0,PREV,IF,inc_avg_sample,+',
+               'CDEF:out_bit_min=out_min,8,*',
+               'CDEF:out_bit_avg=out_avg,8,*',
+               'CDEF:out_bit_max=out_max,8,*',
+               'CDEF:inc_bit_min=inc_min,8,*',
+               'CDEF:inc_bit_avg=inc_avg,8,*',
+               'CDEF:inc_bit_max=inc_max,8,*',
+               'CDEF:overlap=out_bit_avg,inc_bit_avg,GT,inc_bit_avg,out_bit_avg,IF',
+               "AREA:out_bit_avg#$HalfGreen",
+               "AREA:inc_bit_avg#$HalfBlue",
+               "AREA:overlap#$HalfBlueGreen",
+               "LINE1:out_bit_avg#$FullGreen:Written",
+               'GPRINT:out_bit_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:out_bit_max:MAX:%5.1lf%s Max,',
+               'GPRINT:out_bit_avg:LAST:%5.1lf%s Last',
+               'GPRINT:out_avg_sum:LAST:(ca. %5.1lf%sB Total)\l',
+               "LINE1:inc_bit_avg#$FullBlue:Read   ",
+               'GPRINT:inc_bit_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:inc_bit_max:MAX:%5.1lf%s Max,',
+               'GPRINT:inc_bit_avg:LAST:%5.1lf%s Last',
+               'GPRINT:inc_avg_sum:LAST:(ca. %5.1lf%sB Total)\l');
+       $GraphDefs['mysql_qcache'] = array(
+               '-v', 'Queries/s',
+               "DEF:hits_min={file}:hits:MIN",
+               "DEF:hits_avg={file}:hits:AVERAGE",
+               "DEF:hits_max={file}:hits:MAX",
+               "DEF:inserts_min={file}:inserts:MIN",
+               "DEF:inserts_avg={file}:inserts:AVERAGE",
+               "DEF:inserts_max={file}:inserts:MAX",
+               "DEF:not_cached_min={file}:not_cached:MIN",
+               "DEF:not_cached_avg={file}:not_cached:AVERAGE",
+               "DEF:not_cached_max={file}:not_cached:MAX",
+               "DEF:lowmem_prunes_min={file}:lowmem_prunes:MIN",
+               "DEF:lowmem_prunes_avg={file}:lowmem_prunes:AVERAGE",
+               "DEF:lowmem_prunes_max={file}:lowmem_prunes:MAX",
+               "DEF:queries_min={file}:queries_in_cache:MIN",
+               "DEF:queries_avg={file}:queries_in_cache:AVERAGE",
+               "DEF:queries_max={file}:queries_in_cache:MAX",
+               "CDEF:unknown=queries_avg,UNKN,+",
+               "CDEF:not_cached_agg=hits_avg,inserts_avg,+,not_cached_avg,+",
+               "CDEF:inserts_agg=hits_avg,inserts_avg,+",
+               "CDEF:hits_agg=hits_avg",
+               "AREA:not_cached_agg#$HalfYellow",
+               "AREA:inserts_agg#$HalfBlue",
+               "AREA:hits_agg#$HalfGreen",
+               "LINE1:not_cached_agg#$FullYellow:Not Cached      ",
+               'GPRINT:not_cached_min:MIN:%5.2lf Min,',
+               'GPRINT:not_cached_avg:AVERAGE:%5.2lf Avg,',
+               'GPRINT:not_cached_max:MAX:%5.2lf Max,',
+               'GPRINT:not_cached_avg:LAST:%5.2lf Last\l',
+               "LINE1:inserts_agg#$FullBlue:Inserts         ",
+               'GPRINT:inserts_min:MIN:%5.2lf Min,',
+               'GPRINT:inserts_avg:AVERAGE:%5.2lf Avg,',
+               'GPRINT:inserts_max:MAX:%5.2lf Max,',
+               'GPRINT:inserts_avg:LAST:%5.2lf Last\l',
+               "LINE1:hits_agg#$FullGreen:Hits            ",
+               'GPRINT:hits_min:MIN:%5.2lf Min,',
+               'GPRINT:hits_avg:AVERAGE:%5.2lf Avg,',
+               'GPRINT:hits_max:MAX:%5.2lf Max,',
+               'GPRINT:hits_avg:LAST:%5.2lf Last\l',
+               "LINE1:lowmem_prunes_avg#$FullRed:Lowmem Prunes   ",
+               'GPRINT:lowmem_prunes_min:MIN:%5.2lf Min,',
+               'GPRINT:lowmem_prunes_avg:AVERAGE:%5.2lf Avg,',
+               'GPRINT:lowmem_prunes_max:MAX:%5.2lf Max,',
+               'GPRINT:lowmem_prunes_avg:LAST:%5.2lf Last\l',
+               "LINE1:unknown#$Canvas:Queries in cache",
+               'GPRINT:queries_min:MIN:%5.0lf Min,',
+               'GPRINT:queries_avg:AVERAGE:%5.0lf Avg,',
+               'GPRINT:queries_max:MAX:%5.0lf Max,',
+               'GPRINT:queries_avg:LAST:%5.0lf Last\l');
+       $GraphDefs['mysql_threads'] = array(
+               '-v', 'Threads',
+               "DEF:running_min={file}:running:MIN",
+               "DEF:running_avg={file}:running:AVERAGE",
+               "DEF:running_max={file}:running:MAX",
+               "DEF:connected_min={file}:connected:MIN",
+               "DEF:connected_avg={file}:connected:AVERAGE",
+               "DEF:connected_max={file}:connected:MAX",
+               "DEF:cached_min={file}:cached:MIN",
+               "DEF:cached_avg={file}:cached:AVERAGE",
+               "DEF:cached_max={file}:cached:MAX",
+               "DEF:created_min={file}:created:MIN",
+               "DEF:created_avg={file}:created:AVERAGE",
+               "DEF:created_max={file}:created:MAX",
+               "CDEF:unknown=created_avg,UNKN,+",
+               "CDEF:cached_agg=connected_avg,cached_avg,+",
+               "AREA:cached_agg#$HalfGreen",
+               "AREA:connected_avg#$HalfBlue",
+               "AREA:running_avg#$HalfRed",
+               "LINE1:cached_agg#$FullGreen:Cached   ",
+               'GPRINT:cached_min:MIN:%5.1lf Min,',
+               'GPRINT:cached_avg:AVERAGE:%5.1lf Avg,',
+               'GPRINT:cached_max:MAX:%5.1lf Max,',
+               'GPRINT:cached_avg:LAST:%5.1lf Last\l',
+               "LINE1:connected_avg#$FullBlue:Connected",
+               'GPRINT:connected_min:MIN:%5.1lf Min,',
+               'GPRINT:connected_avg:AVERAGE:%5.1lf Avg,',
+               'GPRINT:connected_max:MAX:%5.1lf Max,',
+               'GPRINT:connected_avg:LAST:%5.1lf Last\l',
+               "LINE1:running_avg#$FullRed:Running  ",
+               'GPRINT:running_min:MIN:%5.1lf Min,',
+               'GPRINT:running_avg:AVERAGE:%5.1lf Avg,',
+               'GPRINT:running_max:MAX:%5.1lf Max,',
+               'GPRINT:running_avg:LAST:%5.1lf Last\l',
+               "LINE1:unknown#$Canvas:Created  ",
+               'GPRINT:created_min:MIN:%5.0lf Min,',
+               'GPRINT:created_avg:AVERAGE:%5.0lf Avg,',
+               'GPRINT:created_max:MAX:%5.0lf Max,',
+               'GPRINT:created_avg:LAST:%5.0lf Last\l');
+       $GraphDefs['nfs_procedure'] = array(
+               '-v', 'Issues/s',
+               'DEF:avg={file}:value:AVERAGE',
+               'DEF:min={file}:value:MIN',
+               'DEF:max={file}:value:MAX',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Issues/s",
+               'GPRINT:min:MIN:%6.2lf Min,',
+               'GPRINT:avg:AVERAGE:%6.2lf Avg,',
+               'GPRINT:max:MAX:%6.2lf Max,',
+               'GPRINT:avg:LAST:%6.2lf Last\l');
+       $GraphDefs['nfs3_procedures'] = array(
+               "DEF:null_avg={file}:null:AVERAGE",
+               "DEF:getattr_avg={file}:getattr:AVERAGE",
+               "DEF:setattr_avg={file}:setattr:AVERAGE",
+               "DEF:lookup_avg={file}:lookup:AVERAGE",
+               "DEF:access_avg={file}:access:AVERAGE",
+               "DEF:readlink_avg={file}:readlink:AVERAGE",
+               "DEF:read_avg={file}:read:AVERAGE",
+               "DEF:write_avg={file}:write:AVERAGE",
+               "DEF:create_avg={file}:create:AVERAGE",
+               "DEF:mkdir_avg={file}:mkdir:AVERAGE",
+               "DEF:symlink_avg={file}:symlink:AVERAGE",
+               "DEF:mknod_avg={file}:mknod:AVERAGE",
+               "DEF:remove_avg={file}:remove:AVERAGE",
+               "DEF:rmdir_avg={file}:rmdir:AVERAGE",
+               "DEF:rename_avg={file}:rename:AVERAGE",
+               "DEF:link_avg={file}:link:AVERAGE",
+               "DEF:readdir_avg={file}:readdir:AVERAGE",
+               "DEF:readdirplus_avg={file}:readdirplus:AVERAGE",
+               "DEF:fsstat_avg={file}:fsstat:AVERAGE",
+               "DEF:fsinfo_avg={file}:fsinfo:AVERAGE",
+               "DEF:pathconf_avg={file}:pathconf:AVERAGE",
+               "DEF:commit_avg={file}:commit:AVERAGE",
+               "DEF:null_max={file}:null:MAX",
+               "DEF:getattr_max={file}:getattr:MAX",
+               "DEF:setattr_max={file}:setattr:MAX",
+               "DEF:lookup_max={file}:lookup:MAX",
+               "DEF:access_max={file}:access:MAX",
+               "DEF:readlink_max={file}:readlink:MAX",
+               "DEF:read_max={file}:read:MAX",
+               "DEF:write_max={file}:write:MAX",
+               "DEF:create_max={file}:create:MAX",
+               "DEF:mkdir_max={file}:mkdir:MAX",
+               "DEF:symlink_max={file}:symlink:MAX",
+               "DEF:mknod_max={file}:mknod:MAX",
+               "DEF:remove_max={file}:remove:MAX",
+               "DEF:rmdir_max={file}:rmdir:MAX",
+               "DEF:rename_max={file}:rename:MAX",
+               "DEF:link_max={file}:link:MAX",
+               "DEF:readdir_max={file}:readdir:MAX",
+               "DEF:readdirplus_max={file}:readdirplus:MAX",
+               "DEF:fsstat_max={file}:fsstat:MAX",
+               "DEF:fsinfo_max={file}:fsinfo:MAX",
+               "DEF:pathconf_max={file}:pathconf:MAX",
+               "DEF:commit_max={file}:commit:MAX",
+               "CDEF:other_avg=null_avg,readlink_avg,create_avg,mkdir_avg,symlink_avg,mknod_avg,remove_avg,rmdir_avg,rename_avg,link_avg,readdir_avg,readdirplus_avg,fsstat_avg,fsinfo_avg,pathconf_avg,+,+,+,+,+,+,+,+,+,+,+,+,+,+",
+               "CDEF:other_max=null_max,readlink_max,create_max,mkdir_max,symlink_max,mknod_max,remove_max,rmdir_max,rename_max,link_max,readdir_max,readdirplus_max,fsstat_max,fsinfo_max,pathconf_max,+,+,+,+,+,+,+,+,+,+,+,+,+,+",
+               "CDEF:stack_read=read_avg",
+               "CDEF:stack_getattr=stack_read,getattr_avg,+",
+               "CDEF:stack_access=stack_getattr,access_avg,+",
+               "CDEF:stack_lookup=stack_access,lookup_avg,+",
+               "CDEF:stack_write=stack_lookup,write_avg,+",
+               "CDEF:stack_commit=stack_write,commit_avg,+",
+               "CDEF:stack_setattr=stack_commit,setattr_avg,+",
+               "CDEF:stack_other=stack_setattr,other_avg,+",
+               "AREA:stack_other#$HalfRed",
+               "AREA:stack_setattr#$HalfGreen",
+               "AREA:stack_commit#$HalfYellow",
+               "AREA:stack_write#$HalfGreen",
+               "AREA:stack_lookup#$HalfBlue",
+               "AREA:stack_access#$HalfMagenta",
+               "AREA:stack_getattr#$HalfCyan",
+               "AREA:stack_read#$HalfBlue",
+               "LINE1:stack_other#$FullRed:Other  ",
+               'GPRINT:other_max:MAX:%5.1lf Max,',
+               'GPRINT:other_avg:AVERAGE:%5.1lf Avg,',
+               'GPRINT:other_avg:LAST:%5.1lf Last\l',
+               "LINE1:stack_setattr#$FullGreen:setattr",
+               'GPRINT:setattr_max:MAX:%5.1lf Max,',
+               'GPRINT:setattr_avg:AVERAGE:%5.1lf Avg,',
+               'GPRINT:setattr_avg:LAST:%5.1lf Last\l',
+               "LINE1:stack_commit#$FullYellow:commit ",
+               'GPRINT:commit_max:MAX:%5.1lf Max,',
+               'GPRINT:commit_avg:AVERAGE:%5.1lf Avg,',
+               'GPRINT:commit_avg:LAST:%5.1lf Last\l',
+               "LINE1:stack_write#$FullGreen:write  ",
+               'GPRINT:write_max:MAX:%5.1lf Max,',
+               'GPRINT:write_avg:AVERAGE:%5.1lf Avg,',
+               'GPRINT:write_avg:LAST:%5.1lf Last\l',
+               "LINE1:stack_lookup#$FullBlue:lookup ",
+               'GPRINT:lookup_max:MAX:%5.1lf Max,',
+               'GPRINT:lookup_avg:AVERAGE:%5.1lf Avg,',
+               'GPRINT:lookup_avg:LAST:%5.1lf Last\l',
+               "LINE1:stack_access#$FullMagenta:access ",
+               'GPRINT:access_max:MAX:%5.1lf Max,',
+               'GPRINT:access_avg:AVERAGE:%5.1lf Avg,',
+               'GPRINT:access_avg:LAST:%5.1lf Last\l',
+               "LINE1:stack_getattr#$FullCyan:getattr",
+               'GPRINT:getattr_max:MAX:%5.1lf Max,',
+               'GPRINT:getattr_avg:AVERAGE:%5.1lf Avg,',
+               'GPRINT:getattr_avg:LAST:%5.1lf Last\l',
+               "LINE1:stack_read#$FullBlue:read   ",
+               'GPRINT:read_max:MAX:%5.1lf Max,',
+               'GPRINT:read_avg:AVERAGE:%5.1lf Avg,',
+               'GPRINT:read_avg:LAST:%5.1lf Last\l');
+       $GraphDefs['opcode'] = array(
+               'DEF:avg={file}:value:AVERAGE',
+               'DEF:min={file}:value:MIN',
+               'DEF:max={file}:value:MAX',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Queries/s",
+               'GPRINT:min:MIN:%9.3lf Min,',
+               'GPRINT:avg:AVERAGE:%9.3lf Average,',
+               'GPRINT:max:MAX:%9.3lf Max,',
+               'GPRINT:avg:LAST:%9.3lf Last\l');
+       $GraphDefs['partition'] = array(
+               "DEF:rbyte_avg={file}:rbytes:AVERAGE",
+               "DEF:rbyte_min={file}:rbytes:MIN",
+               "DEF:rbyte_max={file}:rbytes:MAX",
+               "DEF:wbyte_avg={file}:wbytes:AVERAGE",
+               "DEF:wbyte_min={file}:wbytes:MIN",
+               "DEF:wbyte_max={file}:wbytes:MAX",
+               'CDEF:overlap=wbyte_avg,rbyte_avg,GT,rbyte_avg,wbyte_avg,IF',
+               "AREA:wbyte_avg#$HalfGreen",
+               "AREA:rbyte_avg#$HalfBlue",
+               "AREA:overlap#$HalfBlueGreen",    "LINE1:wbyte_avg#$FullGreen:Write",
+               'GPRINT:wbyte_min:MIN:%5.1lf%s Min,',
+               'GPRINT:wbyte_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:wbyte_max:MAX:%5.1lf%s Max,',
+               'GPRINT:wbyte_avg:LAST:%5.1lf%s Last\l',
+               "LINE1:rbyte_avg#$FullBlue:Read ",
+               'GPRINT:rbyte_min:MIN:%5.1lf%s Min,',
+               'GPRINT:rbyte_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:rbyte_max:MAX:%5.1lf%s Max,',
+               'GPRINT:rbyte_avg:LAST:%5.1lf%s Last\l');
+       $GraphDefs['percent'] = array(
+               '-v', 'Percent',
+               'DEF:avg={file}:percent:AVERAGE',
+               'DEF:min={file}:percent:MIN',
+               'DEF:max={file}:percent:MAX',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Percent",
+               'GPRINT:min:MIN:%5.1lf%% Min,',
+               'GPRINT:avg:AVERAGE:%5.1lf%% Avg,',
+               'GPRINT:max:MAX:%5.1lf%% Max,',
+               'GPRINT:avg:LAST:%5.1lf%% Last\l');
+       $GraphDefs['ping'] = array(
+               'DEF:ping_avg={file}:ping:AVERAGE',
+               'DEF:ping_min={file}:ping:MIN',
+               'DEF:ping_max={file}:ping:MAX',
+               "AREA:ping_max#$HalfBlue",
+               "AREA:ping_min#$Canvas",
+               "LINE1:ping_avg#$FullBlue:Ping",
+               'GPRINT:ping_min:MIN:%4.1lf ms Min,',
+               'GPRINT:ping_avg:AVERAGE:%4.1lf ms Avg,',
+               'GPRINT:ping_max:MAX:%4.1lf ms Max,',
+               'GPRINT:ping_avg:LAST:%4.1lf ms Last');
+       $GraphDefs['power'] = array(
+               '-v', 'Watt',
+               'DEF:avg={file}:value:AVERAGE',
+               'DEF:min={file}:value:MIN',
+               'DEF:max={file}:value:MAX',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Watt",
+               'GPRINT:min:MIN:%5.1lf%sW Min,',
+               'GPRINT:avg:AVERAGE:%5.1lf%sW Avg,',
+               'GPRINT:max:MAX:%5.1lf%sW Max,',
+               'GPRINT:avg:LAST:%5.1lf%sW Last\l');
+       $GraphDefs['processes'] = array(
+               "DEF:running_avg={file}:running:AVERAGE",
+               "DEF:running_min={file}:running:MIN",
+               "DEF:running_max={file}:running:MAX",
+               "DEF:sleeping_avg={file}:sleeping:AVERAGE",
+               "DEF:sleeping_min={file}:sleeping:MIN",
+               "DEF:sleeping_max={file}:sleeping:MAX",
+               "DEF:zombies_avg={file}:zombies:AVERAGE",
+               "DEF:zombies_min={file}:zombies:MIN",
+               "DEF:zombies_max={file}:zombies:MAX",
+               "DEF:stopped_avg={file}:stopped:AVERAGE",
+               "DEF:stopped_min={file}:stopped:MIN",
+               "DEF:stopped_max={file}:stopped:MAX",
+               "DEF:paging_avg={file}:paging:AVERAGE",
+               "DEF:paging_min={file}:paging:MIN",
+               "DEF:paging_max={file}:paging:MAX",
+               "DEF:blocked_avg={file}:blocked:AVERAGE",
+               "DEF:blocked_min={file}:blocked:MIN",
+               "DEF:blocked_max={file}:blocked:MAX",
+               'CDEF:paging_acc=sleeping_avg,running_avg,stopped_avg,zombies_avg,blocked_avg,paging_avg,+,+,+,+,+',
+               'CDEF:blocked_acc=sleeping_avg,running_avg,stopped_avg,zombies_avg,blocked_avg,+,+,+,+',
+               'CDEF:zombies_acc=sleeping_avg,running_avg,stopped_avg,zombies_avg,+,+,+',
+               'CDEF:stopped_acc=sleeping_avg,running_avg,stopped_avg,+,+',
+               'CDEF:running_acc=sleeping_avg,running_avg,+',
+               'CDEF:sleeping_acc=sleeping_avg',
+               "AREA:paging_acc#$HalfYellow",
+               "AREA:blocked_acc#$HalfCyan",
+               "AREA:zombies_acc#$HalfRed",
+               "AREA:stopped_acc#$HalfMagenta",
+               "AREA:running_acc#$HalfGreen",
+               "AREA:sleeping_acc#$HalfBlue",
+               "LINE1:paging_acc#$FullYellow:Paging  ",
+               'GPRINT:paging_min:MIN:%5.1lf Min,',
+               'GPRINT:paging_avg:AVERAGE:%5.1lf Average,',
+               'GPRINT:paging_max:MAX:%5.1lf Max,',
+               'GPRINT:paging_avg:LAST:%5.1lf Last\l',
+               "LINE1:blocked_acc#$FullCyan:Blocked ",
+               'GPRINT:blocked_min:MIN:%5.1lf Min,',
+               'GPRINT:blocked_avg:AVERAGE:%5.1lf Average,',
+               'GPRINT:blocked_max:MAX:%5.1lf Max,',
+               'GPRINT:blocked_avg:LAST:%5.1lf Last\l',
+               "LINE1:zombies_acc#$FullRed:Zombies ",
+               'GPRINT:zombies_min:MIN:%5.1lf Min,',
+               'GPRINT:zombies_avg:AVERAGE:%5.1lf Average,',
+               'GPRINT:zombies_max:MAX:%5.1lf Max,',
+               'GPRINT:zombies_avg:LAST:%5.1lf Last\l',
+               "LINE1:stopped_acc#$FullMagenta:Stopped ",
+               'GPRINT:stopped_min:MIN:%5.1lf Min,',
+               'GPRINT:stopped_avg:AVERAGE:%5.1lf Average,',
+               'GPRINT:stopped_max:MAX:%5.1lf Max,',
+               'GPRINT:stopped_avg:LAST:%5.1lf Last\l',
+               "LINE1:running_acc#$FullGreen:Running ",
+               'GPRINT:running_min:MIN:%5.1lf Min,',
+               'GPRINT:running_avg:AVERAGE:%5.1lf Average,',
+               'GPRINT:running_max:MAX:%5.1lf Max,',
+               'GPRINT:running_avg:LAST:%5.1lf Last\l',
+               "LINE1:sleeping_acc#$FullBlue:Sleeping",
+               'GPRINT:sleeping_min:MIN:%5.1lf Min,',
+               'GPRINT:sleeping_avg:AVERAGE:%5.1lf Average,',
+               'GPRINT:sleeping_max:MAX:%5.1lf Max,',
+               'GPRINT:sleeping_avg:LAST:%5.1lf Last\l');
+       $GraphDefs['ps_count'] = array(
+               '-v', 'Processes',
+               'DEF:procs_avg={file}:processes:AVERAGE',
+               'DEF:procs_min={file}:processes:MIN',
+               'DEF:procs_max={file}:processes:MAX',
+               'DEF:thrds_avg={file}:threads:AVERAGE',
+               'DEF:thrds_min={file}:threads:MIN',
+               'DEF:thrds_max={file}:threads:MAX',
+               "AREA:thrds_avg#$HalfBlue",
+               "AREA:procs_avg#$HalfRed",
+               "LINE1:thrds_avg#$FullBlue:Threads  ",
+               'GPRINT:thrds_min:MIN:%5.1lf Min,',
+               'GPRINT:thrds_avg:AVERAGE:%5.1lf Avg,',
+               'GPRINT:thrds_max:MAX:%5.1lf Max,',
+               'GPRINT:thrds_avg:LAST:%5.1lf Last\l',
+               "LINE1:procs_avg#$FullRed:Processes",
+               'GPRINT:procs_min:MIN:%5.1lf Min,',
+               'GPRINT:procs_avg:AVERAGE:%5.1lf Avg,',
+               'GPRINT:procs_max:MAX:%5.1lf Max,',
+               'GPRINT:procs_avg:LAST:%5.1lf Last\l');
+       $GraphDefs['ps_cputime'] = array(
+               '-v', 'Jiffies',
+               'DEF:user_avg_raw={file}:user:AVERAGE',
+               'DEF:user_min_raw={file}:user:MIN',
+               'DEF:user_max_raw={file}:user:MAX',
+               'DEF:syst_avg_raw={file}:syst:AVERAGE',
+               'DEF:syst_min_raw={file}:syst:MIN',
+               'DEF:syst_max_raw={file}:syst:MAX',
+               'CDEF:user_avg=user_avg_raw,1000000,/',
+               'CDEF:user_min=user_min_raw,1000000,/',
+               'CDEF:user_max=user_max_raw,1000000,/',
+               'CDEF:syst_avg=syst_avg_raw,1000000,/',
+               'CDEF:syst_min=syst_min_raw,1000000,/',
+               'CDEF:syst_max=syst_max_raw,1000000,/',
+               'CDEF:user_syst=syst_avg,UN,0,syst_avg,IF,user_avg,+',
+               "AREA:user_syst#$HalfBlue",
+               "AREA:syst_avg#$HalfRed",
+               "LINE1:user_syst#$FullBlue:User  ",
+               'GPRINT:user_min:MIN:%5.1lf%s Min,',
+               'GPRINT:user_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:user_max:MAX:%5.1lf%s Max,',
+               'GPRINT:user_avg:LAST:%5.1lf%s Last\l',
+               "LINE1:syst_avg#$FullRed:System",
+               'GPRINT:syst_min:MIN:%5.1lf%s Min,',
+               'GPRINT:syst_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:syst_max:MAX:%5.1lf%s Max,',
+               'GPRINT:syst_avg:LAST:%5.1lf%s Last\l');
+       $GraphDefs['ps_pagefaults'] = array(
+               '-v', 'Pagefaults/s',
+               'DEF:minor_avg={file}:minflt:AVERAGE',
+               'DEF:minor_min={file}:minflt:MIN',
+               'DEF:minor_max={file}:minflt:MAX',
+               'DEF:major_avg={file}:majflt:AVERAGE',
+               'DEF:major_min={file}:majflt:MIN',
+               'DEF:major_max={file}:majflt:MAX',
+               'CDEF:minor_major=major_avg,UN,0,major_avg,IF,minor_avg,+',
+               "AREA:minor_major#$HalfBlue",
+               "AREA:major_avg#$HalfRed",
+               "LINE1:minor_major#$FullBlue:Minor",
+               'GPRINT:minor_min:MIN:%5.1lf%s Min,',
+               'GPRINT:minor_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:minor_max:MAX:%5.1lf%s Max,',
+               'GPRINT:minor_avg:LAST:%5.1lf%s Last\l',
+               "LINE1:major_avg#$FullRed:Major",
+               'GPRINT:major_min:MIN:%5.1lf%s Min,',
+               'GPRINT:major_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:major_max:MAX:%5.1lf%s Max,',
+               'GPRINT:major_avg:LAST:%5.1lf%s Last\l');
+       $GraphDefs['ps_rss'] = array(
+               '-v', 'Bytes',
+               'DEF:avg={file}:value:AVERAGE',
+               'DEF:min={file}:value:MIN',
+               'DEF:max={file}:value:MAX',
+               "AREA:avg#$HalfBlue",
+               "LINE1:avg#$FullBlue:RSS",
+               'GPRINT:min:MIN:%5.1lf%s Min,',
+               'GPRINT:avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:max:MAX:%5.1lf%s Max,',
+               'GPRINT:avg:LAST:%5.1lf%s Last\l');
+       $GraphDefs['ps_state'] = array(
+               '-v', 'Processes',
+               'DEF:avg={file}:value:AVERAGE',
+               'DEF:min={file}:value:MIN',
+               'DEF:max={file}:value:MAX',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Processes",
+               'GPRINT:min:MIN:%6.2lf Min,',
+               'GPRINT:avg:AVERAGE:%6.2lf Avg,',
+               'GPRINT:max:MAX:%6.2lf Max,',
+               'GPRINT:avg:LAST:%6.2lf Last\l');
+       $GraphDefs['qtype'] = array(
+               'DEF:avg={file}:value:AVERAGE',
+               'DEF:min={file}:value:MIN',
+               'DEF:max={file}:value:MAX',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Queries/s",
+               'GPRINT:min:MIN:%9.3lf Min,',
+               'GPRINT:avg:AVERAGE:%9.3lf Average,',
+               'GPRINT:max:MAX:%9.3lf Max,',
+               'GPRINT:avg:LAST:%9.3lf Last\l');
+       $GraphDefs['rcode'] = array(
+               'DEF:avg={file}:value:AVERAGE',
+               'DEF:min={file}:value:MIN',
+               'DEF:max={file}:value:MAX',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Queries/s",
+               'GPRINT:min:MIN:%9.3lf Min,',
+               'GPRINT:avg:AVERAGE:%9.3lf Average,',
+               'GPRINT:max:MAX:%9.3lf Max,',
+               'GPRINT:avg:LAST:%9.3lf Last\l');
+       $GraphDefs['swap'] = array(
+               '-v', 'Bytes', '-b', '1024',
+               'DEF:avg={file}:value:AVERAGE',
+               'DEF:min={file}:value:MIN',
+               'DEF:max={file}:value:MAX',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Bytes",
+               'GPRINT:min:MIN:%6.2lf%sByte Min,',
+               'GPRINT:avg:AVERAGE:%6.2lf%sByte Avg,',
+               'GPRINT:max:MAX:%6.2lf%sByte Max,',
+               'GPRINT:avg:LAST:%6.2lf%sByte Last\l');
+       $GraphDefs['old_swap'] = array(
+               'DEF:used_avg={file}:used:AVERAGE',
+               'DEF:used_min={file}:used:MIN',
+               'DEF:used_max={file}:used:MAX',
+               'DEF:free_avg={file}:free:AVERAGE',
+               'DEF:free_min={file}:free:MIN',
+               'DEF:free_max={file}:free:MAX',
+               'DEF:cach_avg={file}:cached:AVERAGE',
+               'DEF:cach_min={file}:cached:MIN',
+               'DEF:cach_max={file}:cached:MAX',
+               'DEF:resv_avg={file}:resv:AVERAGE',
+               'DEF:resv_min={file}:resv:MIN',
+               'DEF:resv_max={file}:resv:MAX',
+               'CDEF:cach_avg_notnull=cach_avg,UN,0,cach_avg,IF',
+               'CDEF:resv_avg_notnull=resv_avg,UN,0,resv_avg,IF',
+               'CDEF:used_acc=used_avg',
+               'CDEF:resv_acc=used_acc,resv_avg_notnull,+',
+               'CDEF:cach_acc=resv_acc,cach_avg_notnull,+',
+               'CDEF:free_acc=cach_acc,free_avg,+',
+               "AREA:free_acc#$HalfGreen",
+               "AREA:cach_acc#$HalfBlue",
+               "AREA:resv_acc#$HalfYellow",
+               "AREA:used_acc#$HalfRed",
+               "LINE1:free_acc#$FullGreen:Free    ",
+               'GPRINT:free_min:MIN:%5.1lf%s Min,',
+               'GPRINT:free_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:free_max:MAX:%5.1lf%s Max,',
+               'GPRINT:free_avg:LAST:%5.1lf%s Last\n',
+               "LINE1:cach_acc#$FullBlue:Cached  ",
+               'GPRINT:cach_min:MIN:%5.1lf%s Min,',
+               'GPRINT:cach_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:cach_max:MAX:%5.1lf%s Max,',
+               'GPRINT:cach_avg:LAST:%5.1lf%s Last\l',
+               "LINE1:resv_acc#$FullYellow:Reserved",
+               'GPRINT:resv_min:MIN:%5.1lf%s Min,',
+               'GPRINT:resv_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:resv_max:MAX:%5.1lf%s Max,',
+               'GPRINT:resv_avg:LAST:%5.1lf%s Last\n',
+               "LINE1:used_acc#$FullRed:Used    ",
+               'GPRINT:used_min:MIN:%5.1lf%s Min,',
+               'GPRINT:used_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:used_max:MAX:%5.1lf%s Max,',
+               'GPRINT:used_avg:LAST:%5.1lf%s Last\l');
+       $GraphDefs['tcp_connections'] = array(
+               '-v', 'Connections',
+               'DEF:avg={file}:value:AVERAGE',
+               'DEF:min={file}:value:MIN',
+               'DEF:max={file}:value:MAX',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Connections",
+               'GPRINT:min:MIN:%4.1lf Min,',
+               'GPRINT:avg:AVERAGE:%4.1lf Avg,',
+               'GPRINT:max:MAX:%4.1lf Max,',
+               'GPRINT:avg:LAST:%4.1lf Last\l');
+       $GraphDefs['temperature'] = array(
+               '-v', 'Celsius',
+               'DEF:temp_avg={file}:value:AVERAGE',
+               'DEF:temp_min={file}:value:MIN',
+               'DEF:temp_max={file}:value:MAX',
+               'CDEF:average=temp_avg,0.2,*,PREV,UN,temp_avg,PREV,IF,0.8,*,+',
+               "AREA:temp_max#$HalfRed",
+               "AREA:temp_min#$Canvas",
+               "LINE1:temp_avg#$FullRed:Temperature",
+               'GPRINT:temp_min:MIN:%4.1lf Min,',
+               'GPRINT:temp_avg:AVERAGE:%4.1lf Avg,',
+               'GPRINT:temp_max:MAX:%4.1lf Max,',
+               'GPRINT:temp_avg:LAST:%4.1lf Last\l');
+       $GraphDefs['timeleft'] = array(
+               '-v', 'Minutes',
+               'DEF:avg={file}:timeleft:AVERAGE',
+               'DEF:min={file}:timeleft:MIN',
+               'DEF:max={file}:timeleft:MAX',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Time left [min]",
+               'GPRINT:min:MIN:%5.1lf%s Min,',
+               'GPRINT:avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:max:MAX:%5.1lf%s Max,',
+               'GPRINT:avg:LAST:%5.1lf%s Last\l');
+       $GraphDefs['time_offset'] = array( # NTPd
+               'DEF:s_avg={file}:seconds:AVERAGE',
+               'DEF:s_min={file}:seconds:MIN',
+               'DEF:s_max={file}:seconds:MAX',
+               "AREA:s_max#$HalfBlue",
+               "AREA:s_min#$Canvas",
+               "LINE1:s_avg#$FullBlue:{inst}",
+               'GPRINT:s_min:MIN:%7.3lf%s Min,',
+               'GPRINT:s_avg:AVERAGE:%7.3lf%s Avg,',
+               'GPRINT:s_max:MAX:%7.3lf%s Max,',
+               'GPRINT:s_avg:LAST:%7.3lf%s Last');
+       $GraphDefs['if_octets'] = array(
+               '-v', 'Bits/s', '--units=si',
+               'DEF:out_min_raw={file}:tx:MIN',
+               'DEF:out_avg_raw={file}:tx:AVERAGE',
+               'DEF:out_max_raw={file}:tx:MAX',
+               'DEF:inc_min_raw={file}:rx:MIN',
+               'DEF:inc_avg_raw={file}:rx:AVERAGE',
+               'DEF:inc_max_raw={file}:rx:MAX',
+               'CDEF:out_min=out_min_raw,8,*',
+               'CDEF:out_avg=out_avg_raw,8,*',
+               'CDEF:out_max=out_max_raw,8,*',
+               'CDEF:inc_min=inc_min_raw,8,*',
+               'CDEF:inc_avg=inc_avg_raw,8,*',
+               'CDEF:inc_max=inc_max_raw,8,*',
+               'CDEF:overlap=out_avg,inc_avg,GT,inc_avg,out_avg,IF',
+               'CDEF:mytime=out_avg_raw,TIME,TIME,IF',
+               'CDEF:sample_len_raw=mytime,PREV(mytime),-',
+               'CDEF:sample_len=sample_len_raw,UN,0,sample_len_raw,IF',
+               'CDEF:out_avg_sample=out_avg_raw,UN,0,out_avg_raw,IF,sample_len,*',
+               'CDEF:out_avg_sum=PREV,UN,0,PREV,IF,out_avg_sample,+',
+               'CDEF:inc_avg_sample=inc_avg_raw,UN,0,inc_avg_raw,IF,sample_len,*',
+               'CDEF:inc_avg_sum=PREV,UN,0,PREV,IF,inc_avg_sample,+',
+               "AREA:out_avg#$HalfGreen",
+               "AREA:inc_avg#$HalfBlue",
+               "AREA:overlap#$HalfBlueGreen",
+               "LINE1:out_avg#$FullGreen:Outgoing",
+               'GPRINT:out_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:out_max:MAX:%5.1lf%s Max,',
+               'GPRINT:out_avg:LAST:%5.1lf%s Last',
+               'GPRINT:out_avg_sum:LAST:(ca. %5.1lf%sB Total)\l',
+               "LINE1:inc_avg#$FullBlue:Incoming",
+//                     'GPRINT:inc_min:MIN:%5.1lf %s Min,',
+               'GPRINT:inc_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:inc_max:MAX:%5.1lf%s Max,',
+               'GPRINT:inc_avg:LAST:%5.1lf%s Last',
+               'GPRINT:inc_avg_sum:LAST:(ca. %5.1lf%sB Total)\l');
+       $GraphDefs['cpufreq'] = array(
+               'DEF:cpufreq_avg={file}:value:AVERAGE',
+               'DEF:cpufreq_min={file}:value:MIN',
+               'DEF:cpufreq_max={file}:value:MAX',
+               "AREA:cpufreq_max#$HalfBlue",
+               "AREA:cpufreq_min#$Canvas",
+               "LINE1:cpufreq_avg#$FullBlue:Frequency",
+               'GPRINT:cpufreq_min:MIN:%5.1lf%s Min,',
+               'GPRINT:cpufreq_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:cpufreq_max:MAX:%5.1lf%s Max,',
+               'GPRINT:cpufreq_avg:LAST:%5.1lf%s Last\l');
+       $GraphDefs['multimeter'] = array(
+               'DEF:multimeter_avg={file}:value:AVERAGE',
+               'DEF:multimeter_min={file}:value:MIN',
+               'DEF:multimeter_max={file}:value:MAX',
+               "AREA:multimeter_max#$HalfBlue",
+               "AREA:multimeter_min#$Canvas",
+               "LINE1:multimeter_avg#$FullBlue:Multimeter",
+               'GPRINT:multimeter_min:MIN:%4.1lf Min,',
+               'GPRINT:multimeter_avg:AVERAGE:%4.1lf Average,',
+               'GPRINT:multimeter_max:MAX:%4.1lf Max,',
+               'GPRINT:multimeter_avg:LAST:%4.1lf Last\l');
+       $GraphDefs['users'] = array(
+               '-v', 'Users',
+               'DEF:users_avg={file}:users:AVERAGE',
+               'DEF:users_min={file}:users:MIN',
+               'DEF:users_max={file}:users:MAX',
+               "AREA:users_max#$HalfBlue",
+               "AREA:users_min#$Canvas",
+               "LINE1:users_avg#$FullBlue:Users",
+               'GPRINT:users_min:MIN:%4.1lf Min,',
+               'GPRINT:users_avg:AVERAGE:%4.1lf Average,',
+               'GPRINT:users_max:MAX:%4.1lf Max,',
+               'GPRINT:users_avg:LAST:%4.1lf Last\l');
+       $GraphDefs['voltage'] = array(
+               '-v', 'Voltage',
+               'DEF:avg={file}:value:AVERAGE',
+               'DEF:min={file}:value:MIN',
+               'DEF:max={file}:value:MAX',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Voltage",
+               'GPRINT:min:MIN:%5.1lf%sV Min,',
+               'GPRINT:avg:AVERAGE:%5.1lf%sV Avg,',
+               'GPRINT:max:MAX:%5.1lf%sV Max,',
+               'GPRINT:avg:LAST:%5.1lf%sV Last\l');
+       $GraphDefs['vmpage_action'] = array(
+               '-v', 'Actions',
+               'DEF:avg={file}:value:AVERAGE',
+               'DEF:min={file}:value:MIN',
+               'DEF:max={file}:value:MAX',
+               "AREA:max#$HalfBlue",
+               "AREA:min#$Canvas",
+               "LINE1:avg#$FullBlue:Action",
+               'GPRINT:min:MIN:%5.1lf%sV Min,',
+               'GPRINT:avg:AVERAGE:%5.1lf%sV Avg,',
+               'GPRINT:max:MAX:%5.1lf%sV Max,',
+               'GPRINT:avg:LAST:%5.1lf%sV Last\l');
+       $GraphDefs['vmpage_faults'] = $GraphDefs['ps_pagefaults'];
+       $GraphDefs['vmpage_io'] = array(
+               '-v', 'Bytes/s',
+               'DEF:out_min={file}:out:MIN',
+               'DEF:out_avg={file}:out:AVERAGE',
+               'DEF:out_max={file}:out:MAX',
+               'DEF:inc_min={file}:in:MIN',
+               'DEF:inc_avg={file}:in:AVERAGE',
+               'DEF:inc_max={file}:in:MAX',
+               'CDEF:overlap=out_avg,inc_avg,GT,inc_avg,out_avg,IF',
+               'CDEF:mytime=out_avg,TIME,TIME,IF',
+               'CDEF:sample_len_raw=mytime,PREV(mytime),-',
+               'CDEF:sample_len=sample_len_raw,UN,0,sample_len_raw,IF',
+               'CDEF:out_avg_sample=out_avg,UN,0,out_avg,IF,sample_len,*',
+               'CDEF:out_avg_sum=PREV,UN,0,PREV,IF,out_avg_sample,+',
+               'CDEF:inc_avg_sample=inc_avg,UN,0,inc_avg,IF,sample_len,*',
+               'CDEF:inc_avg_sum=PREV,UN,0,PREV,IF,inc_avg_sample,+',
+               "AREA:out_avg#$HalfGreen",
+               "AREA:inc_avg#$HalfBlue",
+               "AREA:overlap#$HalfBlueGreen",
+               "LINE1:out_avg#$FullGreen:Written",
+               'GPRINT:out_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:out_max:MAX:%5.1lf%s Max,',
+               'GPRINT:out_avg:LAST:%5.1lf%s Last',
+               'GPRINT:out_avg_sum:LAST:(ca. %5.1lf%sB Total)\l',
+               "LINE1:inc_avg#$FullBlue:Read   ",
+               'GPRINT:inc_avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:inc_max:MAX:%5.1lf%s Max,',
+               'GPRINT:inc_avg:LAST:%5.1lf%s Last',
+               'GPRINT:inc_avg_sum:LAST:(ca. %5.1lf%sB Total)\l');
+       $GraphDefs['vmpage_number'] = array(
+               '-v', 'Count',
+               'DEF:avg={file}:value:AVERAGE',
+               'DEF:min={file}:value:MIN',
+               'DEF:max={file}:value:MAX',
+               "AREA:avg#$HalfBlue",
+               "LINE1:avg#$FullBlue:Count",
+               'GPRINT:min:MIN:%5.1lf%s Min,',
+               'GPRINT:avg:AVERAGE:%5.1lf%s Avg,',
+               'GPRINT:max:MAX:%5.1lf%s Max,',
+               'GPRINT:avg:LAST:%5.1lf%s Last\l');
+       $GraphDefs['vs_threads'] = array(
+               "DEF:total_avg={file}:total:AVERAGE",
+               "DEF:total_min={file}:total:MIN",
+               "DEF:total_max={file}:total:MAX",
+               "DEF:running_avg={file}:running:AVERAGE",
+               "DEF:running_min={file}:running:MIN",
+               "DEF:running_max={file}:running:MAX",
+               "DEF:uninterruptible_avg={file}:uninterruptible:AVERAGE",
+               "DEF:uninterruptible_min={file}:uninterruptible:MIN",
+               "DEF:uninterruptible_max={file}:uninterruptible:MAX",
+               "DEF:onhold_avg={file}:onhold:AVERAGE",
+               "DEF:onhold_min={file}:onhold:MIN",
+               "DEF:onhold_max={file}:onhold:MAX",
+               "LINE1:total_avg#$FullYellow:Total   ",
+               'GPRINT:total_min:MIN:%5.1lf Min,',
+               'GPRINT:total_avg:AVERAGE:%5.1lf Avg.,',
+               'GPRINT:total_max:MAX:%5.1lf Max,',
+               'GPRINT:total_avg:LAST:%5.1lf Last\l',
+               "LINE1:running_avg#$FullRed:Running ",
+               'GPRINT:running_min:MIN:%5.1lf Min,',
+               'GPRINT:running_avg:AVERAGE:%5.1lf Avg.,',
+               'GPRINT:running_max:MAX:%5.1lf Max,',
+               'GPRINT:running_avg:LAST:%5.1lf Last\l',
+               "LINE1:uninterruptible_avg#$FullGreen:Unintr  ",
+               'GPRINT:uninterruptible_min:MIN:%5.1lf Min,',
+               'GPRINT:uninterruptible_avg:AVERAGE:%5.1lf Avg.,',
+               'GPRINT:uninterruptible_max:MAX:%5.1lf Max,',
+               'GPRINT:uninterruptible_avg:LAST:%5.1lf Last\l',
+               "LINE1:onhold_avg#$FullBlue:Onhold  ",
+               'GPRINT:onhold_min:MIN:%5.1lf Min,',
+               'GPRINT:onhold_avg:AVERAGE:%5.1lf Avg.,',
+               'GPRINT:onhold_max:MAX:%5.1lf Max,',
+               'GPRINT:onhold_avg:LAST:%5.1lf Last\l');
+       $GraphDefs['vs_memory'] = array(
+               'DEF:vm_avg={file}:vm:AVERAGE',
+               'DEF:vm_min={file}:vm:MIN',
+               'DEF:vm_max={file}:vm:MAX',
+               'DEF:vml_avg={file}:vml:AVERAGE',
+               'DEF:vml_min={file}:vml:MIN',
+               'DEF:vml_max={file}:vml:MAX',
+               'DEF:rss_avg={file}:rss:AVERAGE',
+               'DEF:rss_min={file}:rss:MIN',
+               'DEF:rss_max={file}:rss:MAX',
+               'DEF:anon_avg={file}:anon:AVERAGE',
+               'DEF:anon_min={file}:anon:MIN',
+               'DEF:anon_max={file}:anon:MAX',
+               "LINE1:vm_avg#$FullYellow:VM     ",
+               'GPRINT:vm_min:MIN:%5.1lf%s Min,',
+               'GPRINT:vm_avg:AVERAGE:%5.1lf%s Avg.,',
+               'GPRINT:vm_max:MAX:%5.1lf%s Avg.,',
+               'GPRINT:vm_avg:LAST:%5.1lf%s Last\l',
+               "LINE1:vml_avg#$FullRed:Locked ",
+               'GPRINT:vml_min:MIN:%5.1lf%s Min,',
+               'GPRINT:vml_avg:AVERAGE:%5.1lf%s Avg.,',
+               'GPRINT:vml_max:MAX:%5.1lf%s Avg.,',
+               'GPRINT:vml_avg:LAST:%5.1lf%s Last\l',
+               "LINE1:rss_avg#$FullGreen:RSS    ",
+               'GPRINT:rss_min:MIN:%5.1lf%s Min,',
+               'GPRINT:rss_avg:AVERAGE:%5.1lf%s Avg.,',
+               'GPRINT:rss_max:MAX:%5.1lf%s Avg.,',
+               'GPRINT:rss_avg:LAST:%5.1lf%s Last\l',
+               "LINE1:anon_avg#$FullBlue:Anon.  ",
+               'GPRINT:anon_min:MIN:%5.1lf%s Min,',
+               'GPRINT:anon_avg:AVERAGE:%5.1lf%s Avg.,',
+               'GPRINT:anon_max:MAX:%5.1lf%s Avg.,',
+               'GPRINT:anon_avg:LAST:%5.1lf%s Last\l');
+       $GraphDefs['vs_processes'] = array(
+               '-v', 'Processes',
+               'DEF:proc_avg={file}:value:AVERAGE',
+               'DEF:proc_min={file}:value:MIN',
+               'DEF:proc_max={file}:value:MAX',
+               "AREA:proc_max#$HalfBlue",
+               "AREA:proc_min#$Canvas",
+               "LINE1:proc_avg#$FullBlue:Processes",
+               'GPRINT:proc_min:MIN:%4.1lf Min,',
+               'GPRINT:proc_avg:AVERAGE:%4.1lf Avg.,',
+               'GPRINT:proc_max:MAX:%4.1lf Max,',
+               'GPRINT:proc_avg:LAST:%4.1lf Last\l');
+       $GraphDefs['if_multicast'] = $GraphDefs['ipt_packets'];
+       $GraphDefs['if_tx_errors'] = $GraphDefs['if_rx_errors'];
+
+       $MetaGraphDefs['files_count']       = 'meta_graph_files_count';
+       $MetaGraphDefs['files_size']        = 'meta_graph_files_size';
+       $MetaGraphDefs['cpu']               = 'meta_graph_cpu';
+       $MetaGraphDefs['if_rx_errors']      = 'meta_graph_if_rx_errors';
+       $MetaGraphDefs['if_tx_errors']      = 'meta_graph_if_rx_errors';
+       $MetaGraphDefs['memory']            = 'meta_graph_memory';
+       $MetaGraphDefs['vs_memory']         = 'meta_graph_vs_memory';
+       $MetaGraphDefs['vs_threads']        = 'meta_graph_vs_threads';
+       $MetaGraphDefs['nfs_procedure']     = 'meta_graph_nfs_procedure';
+       $MetaGraphDefs['ps_state']          = 'meta_graph_ps_state';
+       $MetaGraphDefs['swap']              = 'meta_graph_swap';
+       $MetaGraphDefs['apache_scoreboard'] = 'meta_graph_apache_scoreboard';
+       $MetaGraphDefs['mysql_commands']    = 'meta_graph_mysql_commands';
+       $MetaGraphDefs['mysql_handler']     = 'meta_graph_mysql_commands';
+       $MetaGraphDefs['tcp_connections']   = 'meta_graph_tcp_connections';
+       $MetaGraphDefs['dns_opcode']        = 'meta_graph_dns_event';
+       $MetaGraphDefs['dns_qtype']         = 'meta_graph_dns_event';
+       $MetaGraphDefs['dns_qtype_cached']  = 'meta_graph_dns_event';
+       $MetaGraphDefs['dns_rcode']         = 'meta_graph_dns_event';
+       $MetaGraphDefs['dns_request']       = 'meta_graph_dns_event';
+       $MetaGraphDefs['dns_resolver']      = 'meta_graph_dns_event';
+       $MetaGraphDefs['dns_update']        = 'meta_graph_dns_event';
+       $MetaGraphDefs['dns_zops']          = 'meta_graph_dns_event';
+       $MetaGraphDefs['dns_response']      = 'meta_graph_dns_event';
+       $MetaGraphDefs['dns_query']         = 'meta_graph_dns_event';
+       $MetaGraphDefs['dns_reject']        = 'meta_graph_dns_event';
+       $MetaGraphDefs['dns_notify']        = 'meta_graph_dns_event';
+       $MetaGraphDefs['dns_transfer']      = 'meta_graph_dns_event';
+
+       if (function_exists('load_graph_definitions_local'))
+               load_graph_definitions_local($logarithmic, $tinylegend);
+
+       if ($logarithmic)
+               foreach ($GraphDefs as &$GraphDef)
+                       array_unshift($GraphDef, '-o');
+       if ($tinylegend)
+               foreach ($GraphDefs as &$GraphDef)
+                       for ($i = count($GraphDef)-1; $i >=0; $i--)
+                               if (strncmp('GPRINT:', $GraphDef[$i], 7) == 0)
+                                       unset($GraphDef[$i]);
+}
+
+function meta_graph_files_count($host, $plugin, $plugin_instance, $type, $type_instances, $opts = array()) {
+       global $config;
+       $sources = array();
+
+       $title = "$host/$plugin".(!is_null($plugin_instance) ? "-$plugin_instance" : '')."/$type";
+       if (!isset($opts['title']))
+               $opts['title'] = $title;
+       $opts['rrd_opts'] = array('-v', 'Mails');
+
+       $files = array();
+       $opts['colors'] = array(
+               'incoming' => '00e000',
+               'active'   => 'a0e000',
+               'deferred' => 'a00050'
+       );
+
+       $type_instances = array('incoming', 'active', 'deferred');
+       while (list($k, $inst) = each($type_instances)) {
+               $file  = '';
+               foreach ($config['datadirs'] as $datadir)
+                       if (is_file($datadir.'/'.$title.'-'.$inst.'.rrd')) {
+                               $file = $datadir.'/'.$title.'-'.$inst.'.rrd';
+                               break;
+                       }
+               if ($file == '')
+                       continue;
+
+               $sources[] = array('name'=>$inst, 'file'=>$file);
+       }
+
+       return collectd_draw_meta_stack($opts, $sources);
+}
+
+function meta_graph_files_size($host, $plugin, $plugin_instance, $type, $type_instances, $opts = array()) {
+       global $config;
+       $sources = array();
+
+       $title = "$host/$plugin".(!is_null($plugin_instance) ? "-$plugin_instance" : '')."/$type";
+       if (!isset($opts['title']))
+               $opts['title'] = $title;
+       $opts['rrd_opts'] = array('-v', 'Bytes');
+
+       $files = array();
+       $opts['colors'] = array(
+               'incoming' => '00e000',
+               'active'   => 'a0e000',
+               'deferred' => 'a00050'
+       );
+
+       $type_instances = array('incoming', 'active', 'deferred');
+       while (list($k, $inst) = each($type_instances)) {
+               $file  = '';
+               foreach ($config['datadirs'] as $datadir)
+                       if (is_file($datadir.'/'.$title.'-'.$inst.'.rrd')) {
+                               $file = $datadir.'/'.$title.'-'.$inst.'.rrd';
+                               break;
+                       }
+               if ($file == '')
+                       continue;
+
+               $sources[] = array('name'=>$inst, 'file'=>$file);
+       }
+
+       return collectd_draw_meta_stack($opts, $sources);
+}
+
+function meta_graph_cpu($host, $plugin, $plugin_instance, $type, $type_instances, $opts = array()) {
+       global $config;
+       $sources = array();
+
+       $title = "$host/$plugin".(!is_null($plugin_instance) ? "-$plugin_instance" : '')."/$type";
+       if (!isset($opts['title']))
+               $opts['title'] = $title;
+       $opts['rrd_opts'] = array('-v', 'Percent', '-r', '-u', '100');
+
+       $files = array();
+       $opts['colors'] = array(
+               'idle'      => 'ffffff',
+               'nice'      => '00e000',
+               'user'      => '0000ff',
+               'wait'      => 'ffb000',
+               'system'    => 'ff0000',
+               'softirq'   => 'ff00ff',
+               'interrupt' => 'a000a0',
+               'steal'     => '000000'
+       );
+
+       $type_instances = array('idle', 'wait', 'nice', 'user', 'system', 'softirq', 'interrupt', 'steal');
+       while (list($k, $inst) = each($type_instances)) {
+               $file  = '';
+               foreach ($config['datadirs'] as $datadir)
+                       if (is_file($datadir.'/'.$title.'-'.$inst.'.rrd')) {
+                               $file = $datadir.'/'.$title.'-'.$inst.'.rrd';
+                               break;
+                       }
+               if ($file == '')
+                       continue;
+
+               $sources[] = array('name'=>$inst, 'file'=>$file);
+       }
+
+       return collectd_draw_meta_stack($opts, $sources);
+}
+
+function meta_graph_memory($host, $plugin, $plugin_instance, $type, $type_instances, $opts = array()) {
+       global $config;
+       $sources = array();
+
+       $title = "$host/$plugin".(!is_null($plugin_instance) ? "-$plugin_instance" : '')."/$type";
+       if (!isset($opts['title']))
+               $opts['title'] = $title;
+       $opts['number_format'] = '%5.1lf%s';
+       $opts['rrd_opts']      = array('-b', '1024', '-v', 'Bytes');
+
+       $files = array();
+       $opts['colors'] = array(
+               // Linux - System memoery
+               'free'     => '00e000',
+               'cached'   => '0000ff',
+               'buffered' => 'ffb000',
+               'used'     => 'ff0000',
+               // Bind - Server memory
+               'TotalUse'    => '00e000',
+               'InUse'       => 'ff0000',
+               'BlockSize'   => '8888dd',
+               'ContextSize' => '444499',
+               'Lost'        => '222222'
+       );
+
+       $type_instances = array('free', 'cached', 'buffered', 'used',   'TotalUse', 'InUse', 'BlockSize', 'ContextSize', 'Lost');
+       while (list($k, $inst) = each($type_instances)) {
+               $file = '';
+               foreach ($config['datadirs'] as $datadir)
+                       if (is_file($datadir.'/'.$title.'-'.$inst.'.rrd')) {
+                               $file = $datadir.'/'.$title.'-'.$inst.'.rrd';
+                               break;
+                       }
+               if ($file == '')
+                       continue;
+
+               $sources[] = array('name'=>$inst, 'file'=>$file);
+       }
+
+       if ($plugin == 'bind')
+               return collectd_draw_meta_line($opts, $sources);
+       else
+               return collectd_draw_meta_stack($opts, $sources);
+}
+
+function meta_graph_vs_threads($host, $plugin, $plugin_instance, $type, $type_instances, $opts = array()) {
+       global $config;
+       $sources = array();
+
+       $title = "$host/$plugin".(!is_null($plugin_instance) ? "-$plugin_instance" : '')."/$type";
+       if (!isset($opts['title']))
+               $opts['title'] = $title;
+       $opts['number_format'] = '%5.1lf%s';
+       $opts['rrd_opts']      = array('-v', 'Threads');
+
+       $files = array();
+       $opts['colors'] = array(
+               'total'   => 'F0A000',
+               'running'  => 'FF0000',
+               'onhold'  => '00E000',
+               'uninterruptable' => '0000FF'
+       );
+
+       $type_instances = array('total', 'running', 'onhold', 'uninterruptable');
+       while (list($k, $inst) = each($type_instances)) {
+               $file = '';
+               foreach ($config['datadirs'] as $datadir)
+                       if (is_file($datadir.'/'.$title.'-'.$inst.'.rrd')) {
+                               $file = $datadir.'/'.$title.'-'.$inst.'.rrd';
+                               break;
+                       }
+               if ($file == '')
+                       continue;
+
+               $sources[] = array('name'=>$inst, 'file'=>$file);
+       }
+
+       return collectd_draw_meta_line($opts, $sources);
+}
+
+function meta_graph_vs_memory($host, $plugin, $plugin_instance, $type, $type_instances, $opts = array()) {
+       global $config;
+       $sources = array();
+
+       $title = "$host/$plugin".(!is_null($plugin_instance) ? "-$plugin_instance" : '')."/$type";
+       if (!isset($opts['title']))
+               $opts['title'] = $title;
+       $opts['number_format'] = '%5.1lf%s';
+       $opts['rrd_opts']      = array('-b', '1024', '-v', 'Bytes');
+
+       $files = array();
+       $opts['colors'] = array(
+               'vm'   => 'F0A000',
+               'vml'  => 'FF0000',
+               'rss'  => '00E000',
+               'anon' => '0000FF'
+       );
+
+       $type_instances = array('anon', 'rss', 'vml', 'vm');
+       while (list($k, $inst) = each($type_instances)) {
+               $file = '';
+               foreach ($config['datadirs'] as $datadir)
+                       if (is_file($datadir.'/'.$title.'-'.$inst.'.rrd')) {
+                               $file = $datadir.'/'.$title.'-'.$inst.'.rrd';
+                               break;
+                       }
+               if ($file == '')
+                       continue;
+
+               $sources[] = array('name'=>$inst, 'file'=>$file);
+       }
+
+       return collectd_draw_meta_line($opts, $sources);
+}
+
+function meta_graph_if_rx_errors($host, $plugin, $plugin_instance, $type, $type_instances, $opts = array()) {
+       global $config;
+       $sources = array();
+
+       $title = "$host/$plugin".(!is_null($plugin_instance) ? "-$plugin_instance" : '')."/$type";
+       if (!isset($opts['title']))
+               $opts['title'] = $title;
+       $opts['number_format'] = '%5.2lf';
+       $opts['rrd_opts']      = array('-v', 'Errors/s');
+
+       $files = array();
+
+       while (list($k, $inst) = each($type_instances)) {
+               $file = '';
+               foreach ($config['datadirs'] as $datadir)
+                       if (is_file($datadir.'/'.$title.'-'.$inst.'.rrd')) {
+                               $file = $datadir.'/'.$title.'-'.$inst.'.rrd';
+                               break;
+                       }
+               if ($file == '')
+                       continue;
+
+               $sources[] = array('name'=>$inst, 'file'=>$file);
+       }
+
+       return collectd_draw_meta_stack($opts, $sources);
+}
+
+function meta_graph_mysql_commands($host, $plugin, $plugin_instance, $type, $type_instances, $opts = array()) {
+       global $config;
+       $sources = array();
+
+       $title = "$host/$plugin".(!is_null($plugin_instance) ? "-$plugin_instance" : '')."/$type";
+       if (!isset($opts['title']))
+               $opts['title'] = $title;
+       $opts['rrd_opts'] = array('-v', 'Issues/s');
+       $opts['number_format'] = '%5.2lf';
+
+       $files = array();
+
+       while (list($k, $inst) = each($type_instances)) {
+               $file  = '';
+               foreach ($config['datadirs'] as $datadir)
+                       if (is_file($datadir.'/'.$title.'-'.$inst.'.rrd')) {
+                               $file = $datadir.'/'.$title.'-'.$inst.'.rrd';
+                               break;
+                       }
+               if ($file == '')
+                       continue;
+
+               $sources[] = array('name'=>$inst, 'file'=>$file);
+       }
+
+       return collectd_draw_meta_stack($opts, $sources);
+}
+
+function meta_graph_nfs_procedure($host, $plugin, $plugin_instance, $type, $type_instances, $opts = array()) {
+       global $config;
+       $sources = array();
+
+       $title = "$host/$plugin".(!is_null($plugin_instance) ? "-$plugin_instance" : '')."/$type";
+       if (!isset($opts['title']))
+               $opts['title'] = $title;
+       $opts['number_format'] = '%5.1lf%s';
+       $opts['rrd_opts'] = array('-v', 'Ops/s');
+
+       $files = array();
+
+       while (list($k, $inst) = each($type_instances)) {
+               $file  = '';
+               foreach ($config['datadirs'] as $datadir)
+                       if (is_file($datadir.'/'.$title.'-'.$inst.'.rrd')) {
+                               $file = $datadir.'/'.$title.'-'.$inst.'.rrd';
+                               break;
+                       }
+               if ($file == '')
+                       continue;
+
+               $sources[] = array('name'=>$inst, 'file'=>$file);
+       }
+
+       return collectd_draw_meta_stack($opts, $sources);
+}
+
+function meta_graph_ps_state($host, $plugin, $plugin_instance, $type, $type_instances, $opts = array()) {
+       global $config;
+       $sources = array();
+
+       $title = "$host/$plugin".(!is_null($plugin_instance) ? "-$plugin_instance" : '')."/$type";
+       if (!isset($opts['title']))
+               $opts['title'] = $title;
+       $opts['rrd_opts'] = array('-v', 'Processes');
+
+       $files = array();
+       $opts['colors'] = array(
+               'running'  => '00e000',
+               'sleeping' => '0000ff',
+               'paging'   => 'ffb000',
+               'zombies'  => 'ff0000',
+               'blocked'  => 'ff00ff',
+               'stopped'  => 'a000a0'
+       );
+
+       $type_instances = array('paging', 'blocked', 'zombies', 'stopped', 'running', 'sleeping');
+       while (list($k, $inst) = each($type_instances)) {
+               $file = '';
+               foreach ($config['datadirs'] as $datadir)
+                       if (is_file($datadir.'/'.$title.'-'.$inst.'.rrd')) {
+                               $file = $datadir.'/'.$title.'-'.$inst.'.rrd';
+                               break;
+                       }
+               if ($file == '')
+                       continue;
+
+               $sources[] = array('name'=>$inst, 'file'=>$file);
+       }
+
+       return collectd_draw_meta_stack($opts, $sources);
+}
+
+function meta_graph_swap($host, $plugin, $plugin_instance, $type, $type_instances, $opts = array()) {
+       global $config;
+       $sources = array();
+
+       $title = "$host/$plugin".(!is_null($plugin_instance) ? "-$plugin_instance" : '')."/$type";
+       if (!isset($opts['title']))
+               $opts['title'] = $title;
+       $opts['number_format'] = '%5.1lf%s';
+       $opts['rrd_opts']      = array('-b', '1024', '-v', 'Bytes');
+
+       $files = array();
+       $opts['colors'] = array(
+               'free'     => '00e000',
+               'cached'   => '0000ff',
+               'used'     => 'ff0000'
+       );
+
+       $type_instances = array('free', 'cached', 'used');
+       while (list($k, $inst) = each($type_instances)) {
+               $file = '';
+               foreach ($config['datadirs'] as $datadir)
+                       if (is_file($datadir.'/'.$title.'-'.$inst.'.rrd')) {
+                               $file = $datadir.'/'.$title.'-'.$inst.'.rrd';
+                               break;
+                       }
+               if ($file == '')
+                       continue;
+
+               $sources[] = array('name'=>$inst, 'file'=>$file);
+       }
+
+       return collectd_draw_meta_stack($opts, $sources);
+}
+
+function meta_graph_apache_scoreboard($host, $plugin, $plugin_instance, $type, $type_instances, $opts = array()) {
+       global $config;
+       $sources = array();
+
+       $title = "$host/$plugin".(!is_null($plugin_instance) ? "-$plugin_instance" : '')."/$type";
+       if (!isset($opts['title']))
+               $opts['title'] = $title;
+       $opts['number_format'] = '%6.2lf%s';
+       $opts['rrd_opts']      = array('-v', 'Processes');
+
+       $files = array();
+       $opts['colors'] = array(
+               'open'         => '00e000',
+               'waiting'      => '0000ff',
+               'starting'     => 'a00000',
+               'reading'      => 'ff0000',
+               'sending'      => '00ff00',
+               'keepalive'    => 'f000f0',
+               'dnslookup'    => '00a000',
+               'logging'      => '008080',
+               'closing'      => 'a000a0',
+               'finishing'    => '000080',
+               'idle_cleanup' => '000000',
+       );
+
+       $type_instances = array(/* 'open',*/ 'waiting', 'starting', 'reading', 'sending', 'keepalive', 'dnslookup', 'logging', 'closing', 'finishing', 'idle_cleanup');
+       while (list($k, $inst) = each($type_instances)) {
+               $file = '';
+               foreach ($config['datadirs'] as $datadir)
+                       if (is_file($datadir.'/'.$title.'-'.$inst.'.rrd')) {
+                               $file = $datadir.'/'.$title.'-'.$inst.'.rrd';
+                               break;
+                       }
+               if ($file == '')
+                       continue;
+
+               $sources[] = array('name'=>$inst, 'file'=>$file, 'ds'=>'count');
+       }
+
+       return collectd_draw_meta_stack($opts, $sources);
+}
+
+function meta_graph_tcp_connections($host, $plugin, $plugin_instance, $type, $type_instances, $opts = array()) {
+       global $config;
+       $sources = array();
+
+       $title = "$host/$plugin".(!is_null($plugin_instance) ? "-$plugin_instance" : '')."/$type";
+       if (!isset($opts['title']))
+               $opts['title'] = $title;
+       $opts['number_format'] = '%6.2lf%s';
+       $opts['rrd_opts']      = array('-v', 'Connections');
+
+       $files = array();
+       $opts['colors'] = array(
+               'ESTABLISHED' => '00e000',
+               'SYN_SENT'    => '00e0ff',
+               'SYN_RECV'    => '00e0a0',
+               'FIN_WAIT1'   => 'f000f0',
+               'FIN_WAIT2'   => 'f000a0',
+               'TIME_WAIT'   => 'ffb000',
+               'CLOSE'       => '0000f0',
+               'CLOSE_WAIT'  => '0000a0',
+               'LAST_ACK'    => '000080',
+               'LISTEN'      => 'ff0000',
+               'CLOSING'     => '000000'
+       );
+
+       $type_instances = array('ESTABLISHED', 'SYN_SENT', 'SYN_RECV', 'FIN_WAIT1', 'FIN_WAIT2', 'TIME_WAIT', 'CLOSE', 'CLOSE_WAIT', 'LAST_ACK', 'CLOSING', 'LISTEN');
+       while (list($k, $inst) = each($type_instances)) {
+               $file = '';
+               foreach ($config['datadirs'] as $datadir)
+                       if (is_file($datadir.'/'.$title.'-'.$inst.'.rrd')) {
+                               $file = $datadir.'/'.$title.'-'.$inst.'.rrd';
+                               break;
+                       }
+               if ($file == '')
+                       continue;
+
+               $sources[] = array('name'=>$inst, 'file'=>$file, 'ds'=>'value');
+       }
+
+       return collectd_draw_meta_stack($opts, $sources);
+}
+
+function meta_graph_dns_event($host, $plugin, $plugin_instance, $type, $type_instances, $opts = array()) {
+       global $config;
+       $sources = array();
+
+       $title = "$host/$plugin".(!is_null($plugin_instance) ? "-$plugin_instance" : '')."/$type";
+       if (!isset($opts['title']))
+               $opts['title'] = $title;
+       $opts['rrd_opts'] = array('-v', 'Events', '-r', '-l', '0');
+
+       $files = array();
+//     $opts['colors'] = array(
+//     );
+
+//     $type_instances = array('IQUERY', 'NOTIFY');
+       while (list($k, $inst) = each($type_instances)) {
+               $file  = '';
+               $title = $opts['title'];
+               foreach ($config['datadirs'] as $datadir)
+                       if (is_file($datadir.'/'.$title.'-'.$inst.'.rrd')) {
+                               $file = $datadir.'/'.$title.'-'.$inst.'.rrd';
+                               break;
+                       }
+               if ($file == '')
+                       continue;
+
+               $sources[] = array('name'=>$inst, 'file'=>$file);
+       }
+       return collectd_draw_meta_stack($opts, $sources);
+}
+
+?>
diff --git a/contrib/php-collection/functions.php b/contrib/php-collection/functions.php
new file mode 100644 (file)
index 0000000..fa2badc
--- /dev/null
@@ -0,0 +1,841 @@
+<?php // vim:fenc=utf-8:filetype=php:ts=4
+/*
+ * Copyright (C) 2009  Bruno Prémont <bonbons AT linux-vserver.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+define('REGEXP_HOST', '/^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/');
+define('REGEXP_PLUGIN', '/^[a-zA-Z0-9_.-]+$/');
+
+/**
+ * Read input variable from GET, POST or COOKIE taking
+ * care of magic quotes
+ * @name Name of value to return
+ * @array User-input array ($_GET, $_POST or $_COOKIE)
+ * @default Default value
+ * @return $default if name in unknown in $array, otherwise
+ *         input value with magic quotes stripped off
+ */
+function read_var($name, &$array, $default = null) {
+       if (isset($array[$name])) {
+               if (is_array($array[$name])) {
+                       if (get_magic_quotes_gpc()) {
+                               $ret = array();
+                               while (list($k, $v) = each($array[$name]))
+                                       $ret[stripslashes($k)] = stripslashes($v);
+                               return $ret;
+                       } else
+                               return $array[$name];
+               } else if (is_string($array[$name]) && get_magic_quotes_gpc()) {
+                       return stripslashes($array[$name]);
+               } else
+                       return $array[$name];
+       } else
+               return $default;
+}
+
+/**
+ * Alphabetically compare host names, comparing label
+ * from tld to node name
+ */
+function collectd_compare_host($a, $b) {
+       $ea = explode('.', $a);
+       $eb = explode('.', $b);
+       $i = count($ea) - 1;
+       $j = count($eb) - 1;
+       while ($i >= 0 && $j >= 0)
+               if (($r = strcmp($ea[$i--], $eb[$j--])) != 0)
+                       return $r;
+       return 0;
+}
+
+function collectd_walk(&$options) {
+       global $config;
+
+       foreach($config['datadirs'] as $datadir)
+               if ($dh = @opendir($datadir)) {
+                       while (($hdent = readdir($dh)) !== false) {
+                               if ($hdent == '.' || $hdent == '..' || !is_dir($datadir.'/'.$hdent))
+                                       continue;
+                               if (!preg_match(REGEXP_HOST, $hdent))
+                                       continue;
+                               if (isset($options['cb_host']) && ($options['cb_host'] === false || !$options['cb_host']($options, $hdent)))
+                                       continue;
+
+                               if ($dp = @opendir($datadir.'/'.$hdent)) {
+                                       while (($pdent = readdir($dp)) !== false) {
+                                               if ($pdent == '.' || $pdent == '..' || !is_dir($datadir.'/'.$hdent.'/'.$pdent))
+                                                       continue;
+                                               if ($i = strpos($pdent, '-')) {
+                                                       $plugin = substr($pdent, 0, $i);
+                                                       $pinst  = substr($pdent, $i+1);
+                                               } else {
+                                                       $plugin = $pdent;
+                                                       $pinst  = '';
+                                               }
+                                               if (isset($options['cb_plugin']) && ($options['cb_plugin'] === false || !$options['cb_plugin']($options, $hdent, $plugin)))
+                                                       continue;
+                                               if (isset($options['cb_pinst']) && ($options['cb_pinst'] === false || !$options['cb_pinst']($options, $hdent, $plugin, $pinst)))
+                                                       continue;
+
+                                               if ($dt = @opendir($datadir.'/'.$hdent.'/'.$pdent)) {
+                                                       while (($tdent = readdir($dt)) !== false) {
+                                                               if ($tdent == '.' || $tdent == '..' || !is_file($datadir.'/'.$hdent.'/'.$pdent.'/'.$tdent))
+                                                                       continue;
+                                                               if (substr($tdent, strlen($tdent)-4) != '.rrd')
+                                                                       continue;
+                                                               $tdent = substr($tdent, 0, strlen($tdent)-4);
+                                                               if ($i = strpos($tdent, '-')) {
+                                                                       $type  = substr($tdent, 0, $i);
+                                                                       $tinst = substr($tdent, $i+1);
+                                                               } else {
+                                                                       $type  = $tdent;
+                                                                       $tinst = '';
+                                                               }
+                                                               if (isset($options['cb_type']) && ($options['cb_type'] === false || !$options['cb_type']($options, $hdent, $plugin, $pinst, $type)))
+                                                                       continue;
+                                                               if (isset($options['cb_tinst']) && ($options['cb_tinst'] === false || !$options['cb_tinst']($options, $hdent, $plugin, $pinst, $type, $tinst)))
+                                                                       continue;
+                                                       }
+                                                       closedir($dt);
+                                               }
+                                       }
+                                       closedir($dp);
+                               }
+                       }
+                       closedir($dh);
+               } else
+                       error_log('Failed to open datadir: '.$datadir);
+               return true;
+}
+
+function _collectd_list_cb_host(&$options, $host) {
+       if ($options['cb_plugin'] === false) {
+               $options['result'][] = $host;
+               return false;
+       } else if (isset($options['filter_host'])) {
+               if ($options['filter_host'] == '@all') {
+                       return true; // We take anything
+               } else if (substr($options['filter_host'], 0, 2) == '@.') {
+                       if ($host == substr($options['filter_host'], 2) || substr($host, 0, 1-strlen($options['filter_host'])) == substr($options['filter_host'], 1))
+                               return true; // Host part of domain
+                       else
+                               return false;
+               } else if ($options['filter_host'] == $host) {
+                       return true;
+               } else
+                       return false;
+       } else
+               return true;
+}
+
+function _collectd_list_cb_plugin(&$options, $host, $plugin) {
+       if ($options['cb_pinst'] === false) {
+               $options['result'][] = $plugin;
+               return false;
+       } else if (isset($options['filter_plugin'])) {
+               if ($options['filter_plugin'] == '@all')
+                       return true;
+               else if ($options['filter_plugin'] == $plugin)
+                       return true;
+               else
+                       return false;
+       } else
+               return true;
+}
+
+function _collectd_list_cb_pinst(&$options, $host, $plugin, $pinst) {
+       if ($options['cb_type'] === false) {
+               $options['result'][] = $pinst;
+               return false;
+       } else if (isset($options['filter_pinst'])) {
+               if ($options['filter_pinst'] == '@all')
+                       return true;
+               else if (strncmp($options['filter_pinst'], '@merge_', 7) == 0)
+                       return true;
+               else if ($options['filter_pinst'] == $pinst)
+                       return true;
+               else
+                       return false;
+       } else
+               return true;
+}
+
+function _collectd_list_cb_type(&$options, $host, $plugin, $pinst, $type) {
+       if ($options['cb_tinst'] === false) {
+               $options['result'][] = $type;
+               return false;
+       } else if (isset($options['filter_type'])) {
+               if ($options['filter_type'] == '@all')
+                       return true;
+               else if ($options['filter_type'] == $type)
+                       return true;
+               else
+                       return false;
+       } else
+               return true;
+}
+
+function _collectd_list_cb_tinst(&$options, $host, $plugin, $pinst, $type, $tinst) {
+       $options['result'][] = $tinst;
+       return false;
+}
+
+function _collectd_list_cb_graph(&$options, $host, $plugin, $pinst, $type, $tinst) {
+       if (isset($options['filter_tinst'])) {
+               if ($options['filter_tinst'] == '@all') {
+               } else if ($options['filter_tinst'] == $tinst) {
+               } else if (strncmp($options['filter_tinst'], '@merge', 6) == 0) {
+                       // Need to exclude @merge with non-existent meta graph
+               } else
+                       return false;
+       }
+       if (isset($options['filter_pinst']) && strncmp($options['filter_pinst'], '@merge', 6) == 0)
+               $pinst = $options['filter_pinst'];
+       if (isset($options['filter_tinst']) && strncmp($options['filter_tinst'], '@merge', 6) == 0)
+               $tinst = $options['filter_tinst'];
+       $ident = collectd_identifier($host, $plugin, $pinst, $type, $tinst);
+       if (!in_array($ident, $options['ridentifiers'])) {
+               $options['ridentifiers'][] = $ident;
+               $options['result'][] = array('host'=>$host, 'plugin'=>$plugin, 'pinst'=>$pinst, 'type'=>$type, 'tinst'=>$tinst);
+       }
+}
+
+/**
+ * Fetch list of hosts found in collectd's datadirs.
+ * @return Sorted list of hosts (sorted by label from rigth to left)
+ */
+function collectd_list_hosts() {
+       $options = array(
+               'result' => array(),
+               'cb_host' => '_collectd_list_cb_host',
+               'cb_plugin' => false,
+               'cb_pinst' => false,
+               'cb_type' => false,
+               'cb_tinst' => false
+       );
+       collectd_walk($options);
+       $hosts = array_unique($options['result']);
+       usort($hosts, 'collectd_compare_host');
+       return $hosts;
+}
+
+/**
+ * Fetch list of plugins found in collectd's datadirs for given host.
+ * @arg_host Name of host for which to return plugins
+ * @return Sorted list of plugins (sorted alphabetically)
+ */
+function collectd_list_plugins($arg_host, $arg_plugin = null) {
+       $options = array(
+               'result' => array(),
+               'cb_host' => '_collectd_list_cb_host',
+               'cb_plugin' => '_collectd_list_cb_plugin',
+               'cb_pinst' => is_null($arg_plugin) ? false : '_collectd_list_cb_pinst',
+               'cb_type' => false,
+               'cb_tinst' => false,
+               'filter_host' => $arg_host,
+               'filter_plugin' => $arg_plugin
+       );
+       collectd_walk($options);
+       $plugins = array_unique($options['result']);
+       sort($plugins);
+       return $plugins;
+}
+
+/**
+ * Fetch list of types found in collectd's datadirs for given host+plugin+instance
+ * @arg_host Name of host
+ * @arg_plugin Name of plugin
+ * @arg_pinst Plugin instance
+ * @return Sorted list of types (sorted alphabetically)
+ */
+function collectd_list_types($arg_host, $arg_plugin, $arg_pinst, $arg_type = null) {
+       $options = array(
+               'result' => array(),
+               'cb_host' => '_collectd_list_cb_host',
+               'cb_plugin' => '_collectd_list_cb_plugin',
+               'cb_pinst' => '_collectd_list_cb_pinst',
+               'cb_type' => '_collectd_list_cb_type',
+               'cb_tinst' => is_null($arg_type) ? false : '_collectd_list_cb_tinst',
+               'filter_host' => $arg_host,
+               'filter_plugin' => $arg_plugin,
+               'filter_pinst' => $arg_pinst,
+               'filter_type' => $arg_type
+       );
+       collectd_walk($options);
+       $types = array_unique($options['result']);
+       sort($types);
+       return $types;
+}
+
+function collectd_list_graphs($arg_host, $arg_plugin, $arg_pinst, $arg_type, $arg_tinst) {
+       $options = array(
+               'result' => array(),
+               'ridentifiers' => array(),
+               'cb_host' => '_collectd_list_cb_host',
+               'cb_plugin' => '_collectd_list_cb_plugin',
+               'cb_pinst' => '_collectd_list_cb_pinst',
+               'cb_type' => '_collectd_list_cb_type',
+               'cb_tinst' => '_collectd_list_cb_graph',
+               'filter_host' => $arg_host,
+               'filter_plugin' => $arg_plugin,
+               'filter_pinst' => $arg_pinst,
+               'filter_type' => $arg_type,
+               'filter_tinst' => $arg_tinst == '@' ? '@merge' : $arg_tinst
+       );
+       collectd_walk($options);
+       return $options['result'];
+}
+
+/**
+ * Parse symlinks in order to get an identifier that collectd understands
+ * (e.g. virtualisation is collected on host for individual VMs and can be
+ *  symlinked to the VM's hostname, support FLUSH for these by flushing
+ *  on the host-identifier instead of VM-identifier)
+ * @host Host name
+ * @plugin Plugin name
+ * @pinst Plugin instance
+ * @type Type name
+ * @tinst Type instance
+ * @return Identifier that collectd's FLUSH command understands
+ */
+function collectd_identifier($host, $plugin, $pinst, $type, $tinst) {
+       global $config;
+       $rrd_realpath    = null;
+       $orig_identifier = sprintf('%s/%s%s%s/%s%s%s', $host, $plugin, strlen($pinst) ? '-' : '', $pinst, $type, strlen($tinst) ? '-' : '', $tinst);
+       $identifier      = null;
+       foreach ($config['datadirs'] as $datadir)
+               if (is_file($datadir.'/'.$orig_identifier.'.rrd')) {
+                       $rrd_realpath = realpath($datadir.'/'.$orig_identifier.'.rrd');
+                       break;
+               }
+       if ($rrd_realpath) {
+               $identifier   = basename($rrd_realpath);
+               $identifier   = substr($identifier, 0, strlen($identifier)-4);
+               $rrd_realpath = dirname($rrd_realpath);
+               $identifier   = basename($rrd_realpath).'/'.$identifier;
+               $rrd_realpath = dirname($rrd_realpath);
+               $identifier   = basename($rrd_realpath).'/'.$identifier;
+       }
+
+       if (is_null($identifier))
+               return $orig_identifier;
+       else
+               return $identifier;
+}
+
+/**
+ * Tell collectd that it should FLUSH all data it has regarding the
+ * graph we are about to generate.
+ * @host Host name
+ * @plugin Plugin name
+ * @pinst Plugin instance
+ * @type Type name
+ * @tinst Type instance
+ */
+function collectd_flush($identifier) {
+       global $config;
+
+       if (!$config['collectd_sock'])
+               return false;
+       if (is_null($identifier) || (is_array($identifier) && count($identifier) == 0) || !(is_string($identifier) || is_array($identifier)))
+               return false;
+
+       $u_errno  = 0;
+       $u_errmsg = '';
+       if ($socket = @fsockopen($config['collectd_sock'], 0, $u_errno, $u_errmsg)) {
+               $cmd = 'FLUSH plugin=rrdtool';
+               if (is_array($identifier)) {
+                       foreach ($identifier as $val)
+                               $cmd .= sprintf(' identifier="%s"', $val);
+               } else
+                       $cmd .= sprintf(' identifier="%s"', $identifier);
+               $cmd .= "\n";
+
+               $r = fwrite($socket, $cmd, strlen($cmd));
+               if ($r === false || $r != strlen($cmd))
+                       error_log(sprintf("graph.php: Failed to write whole command to unix-socket: %d out of %d written", $r === false ? -1 : $r, strlen($cmd)));
+
+               $resp = fgets($socket);
+               if ($resp === false)
+                       error_log(sprintf("graph.php: Failed to read response from collectd for command: %s", trim($cmd)));
+
+               $n = (int)$resp;
+               while ($n-- > 0)
+                       fgets($socket);
+
+               fclose($socket);
+       } else
+               error_log(sprintf("graph.php: Failed to open unix-socket to collectd: %d: %s", $u_errno, $u_errmsg));
+}
+
+class CollectdColor {
+       private $r = 0;
+       private $g = 0;
+       private $b = 0;
+
+       function __construct($value = null) {
+               if (is_null($value)) {
+               } else if (is_array($value)) {
+                       if (isset($value['r']))
+                               $this->r = $value['r'] > 0 ? ($value['r'] > 1 ? 1 : $value['r']) : 0;
+                       if (isset($value['g']))
+                               $this->g = $value['g'] > 0 ? ($value['g'] > 1 ? 1 : $value['g']) : 0;
+                       if (isset($value['b']))
+                               $this->b = $value['b'] > 0 ? ($value['b'] > 1 ? 1 : $value['b']) : 0;
+               } else if (is_string($value)) {
+                       $matches = array();
+                       if ($value == 'random') {
+                               $this->randomize();
+                       } else if (preg_match('/([0-9A-Fa-f][0-9A-Fa-f])([0-9A-Fa-f][0-9A-Fa-f])([0-9A-Fa-f][0-9A-Fa-f])/', $value, $matches)) {
+                               $this->r = ('0x'.$matches[1]) / 255.0;
+                               $this->g = ('0x'.$matches[2]) / 255.0;
+                               $this->b = ('0x'.$matches[3]) / 255.0;
+                       }
+               } else if (is_a($value, 'CollectdColor')) {
+                       $this->r = $value->r;
+                       $this->g = $value->g;
+                       $this->b = $value->b;
+               }
+       }
+
+       function randomize() {
+               $this->r = rand(0, 255) / 255.0;
+               $this->g = rand(0, 255) / 255.0;
+               $this->b = 0.0;
+               $min = 0.0;
+               $max = 1.0;
+
+               if (($this->r + $this->g) < 1.0) {
+                       $min = 1.0 - ($this->r + $this->g);
+               } else {
+                       $max = 2.0 - ($this->r + $this->g);
+               }
+               $this->b = $min + ((rand(0, 255)/255.0) * ($max - $min));
+       }
+
+       function fade($bkgnd = null, $alpha = 0.25) {
+               if (is_null($bkgnd) || !is_a($bkgnd, 'CollectdColor')) {
+                       $bg_r = 1.0;
+                       $bg_g = 1.0;
+                       $bg_b = 1.0;
+               } else {
+                       $bg_r = $bkgnd->r;
+                       $bg_g = $bkgnd->g;
+                       $bg_b = $bkgnd->b;
+               }
+
+               $this->r = $alpha * $this->r + ((1.0 - $alpha) * $bg_r);
+               $this->g = $alpha * $this->g + ((1.0 - $alpha) * $bg_g);
+               $this->b = $alpha * $this->b + ((1.0 - $alpha) * $bg_b);
+       }
+
+       function as_array() {
+               return array('r'=>$this->r, 'g'=>$this->g, 'b'=>$this->b);
+       }
+
+       function as_string() {
+               $r = (int)($this->r*255);
+               $g = (int)($this->g*255);
+               $b = (int)($this->b*255);
+               return sprintf('%02x%02x%02x', $r > 255 ? 255 : $r, $g > 255 ? 255 : $g, $b > 255 ? 255 : $b);
+       }
+}
+
+
+/**
+ * Helper function to strip quotes from RRD output
+ * @str RRD-Info generated string
+ * @return String with one surrounding pair of quotes stripped
+ */
+function rrd_strip_quotes($str) {
+       if ($str[0] == '"' && $str[strlen($str)-1] == '"')
+               return substr($str, 1, strlen($str)-2);
+       else
+               return $str;
+}
+
+function rrd_escape($str) {
+       return str_replace(array('\\', ':'), array('\\\\', '\\:'), $str);
+}
+
+/**
+ * Determine useful information about RRD file
+ * @file Name of RRD file to analyse
+ * @return Array describing the RRD file
+ */
+function rrd_info($file) {
+       $info = array('filename'=>$file);
+
+       $rrd = popen(RRDTOOL.' info '.escapeshellarg($file), 'r');
+       if ($rrd) {
+               while (($s = fgets($rrd)) !== false) {
+                       $p = strpos($s, '=');
+                       if ($p === false)
+                               continue;
+                       $key = trim(substr($s, 0, $p));
+                       $value = trim(substr($s, $p+1));
+                       if (strncmp($key,'ds[', 3) == 0) {
+                               /* DS definition */
+                               $p = strpos($key, ']');
+                               $ds = substr($key, 3, $p-3);
+                               if (!isset($info['DS']))
+                                       $info['DS'] = array();
+                               $ds_key = substr($key, $p+2);
+
+                               if (strpos($ds_key, '[') === false) {
+                                       if (!isset($info['DS']["$ds"]))
+                                               $info['DS']["$ds"] = array();
+                                       $info['DS']["$ds"]["$ds_key"] = rrd_strip_quotes($value);
+                               }
+                       } else if (strncmp($key, 'rra[', 4) == 0) {
+                               /* RRD definition */
+                               $p = strpos($key, ']');
+                               $rra = substr($key, 4, $p-4);
+                               if (!isset($info['RRA']))
+                                       $info['RRA'] = array();
+                               $rra_key = substr($key, $p+2);
+
+                               if (strpos($rra_key, '[') === false) {
+                                       if (!isset($info['RRA']["$rra"]))
+                                               $info['RRA']["$rra"] = array();
+                                       $info['RRA']["$rra"]["$rra_key"] = rrd_strip_quotes($value);
+                               }
+                       } else if (strpos($key, '[') === false) {
+                               $info[$key] = rrd_strip_quotes($value);
+                       }
+               }
+               pclose($rrd);
+       }
+       return $info;
+}
+
+function rrd_get_color($code, $line = true) {
+       global $config;
+       $name = ($line ? 'f_' : 'h_').$code;
+       if (!isset($config['rrd_colors'][$name])) {
+               $c_f = new CollectdColor('random');
+               $c_h = new CollectdColor($c_f);
+               $c_h->fade();
+               $config['rrd_colors']['f_'.$code] = $c_f->as_string();
+               $config['rrd_colors']['h_'.$code] = $c_h->as_string();
+       }
+       return $config['rrd_colors'][$name];
+}
+
+/**
+ * Draw RRD file based on it's structure
+ * @host
+ * @plugin
+ * @pinst
+ * @type
+ * @tinst
+ * @opts
+ * @return Commandline to call RRDGraph in order to generate the final graph
+ */
+function collectd_draw_rrd($host, $plugin, $pinst = null, $type, $tinst = null, $opts = array()) {
+       global $config;
+       $timespan_def = null;
+       if (!isset($opts['timespan']))
+               $timespan_def = reset($config['timespan']);
+       else foreach ($config['timespan'] as &$ts)
+               if ($ts['name'] == $opts['timespan'])
+                       $timespan_def = $ts;
+
+       if (!isset($opts['rrd_opts']))
+               $opts['rrd_opts'] = array();
+       if (isset($opts['logarithmic']) && $opts['logarithmic'])
+               array_unshift($opts['rrd_opts'], '-o');
+
+       $rrdinfo = null;
+       $rrdfile = sprintf('%s/%s%s%s/%s%s%s', $host, $plugin, is_null($pinst) ? '' : '-', $pinst, $type, is_null($tinst) ? '' : '-', $tinst);
+       foreach ($config['datadirs'] as $datadir)
+               if (is_file($datadir.'/'.$rrdfile.'.rrd')) {
+                       $rrdinfo = rrd_info($datadir.'/'.$rrdfile.'.rrd');
+                       if (isset($rrdinfo['RRA']) && is_array($rrdinfo['RRA']))
+                               break;
+                       else
+                               $rrdinfo = null;
+               }
+
+       if (is_null($rrdinfo))
+               return false;
+
+       $graph = array();
+       $has_avg = false;
+       $has_max = false;
+       $has_min = false;
+       reset($rrdinfo['RRA']);
+       $l_max = 0;
+       while (list($k, $v) = each($rrdinfo['RRA'])) {
+               if ($v['cf'] == 'MAX')
+                       $has_max = true;
+               else if ($v['cf'] == 'AVERAGE')
+                       $has_avg = true;
+               else if ($v['cf'] == 'MIN')
+                       $has_min = true;
+       }
+       reset($rrdinfo['DS']);
+       while (list($k, $v) = each($rrdinfo['DS'])) {
+               if (strlen($k) > $l_max)
+                       $l_max = strlen($k);
+               if ($has_min)
+                       $graph[] = sprintf('DEF:%s_min=%s:%s:MIN', $k, rrd_escape($rrdinfo['filename']), $k);
+               if ($has_avg)
+                       $graph[] = sprintf('DEF:%s_avg=%s:%s:AVERAGE', $k, rrd_escape($rrdinfo['filename']), $k);
+               if ($has_max)
+                       $graph[] = sprintf('DEF:%s_max=%s:%s:MAX', $k, rrd_escape($rrdinfo['filename']), $k);
+       }
+       if ($has_min && $has_max || $has_min && $has_avg || $has_avg && $has_max) {
+               $n = 1;
+               reset($rrdinfo['DS']);
+               while (list($k, $v) = each($rrdinfo['DS'])) {
+                       $graph[] = sprintf('LINE:%s_%s', $k, $has_min ? 'min' : 'avg');
+                       $graph[] = sprintf('CDEF:%s_var=%s_%s,%s_%s,-', $k, $k, $has_max ? 'max' : 'avg', $k, $has_min ? 'min' : 'avg');
+                       $graph[] = sprintf('AREA:%s_var#%s::STACK', $k, rrd_get_color($n++, false));
+               }
+       }
+
+       reset($rrdinfo['DS']);
+       $n = 1;
+       while (list($k, $v) = each($rrdinfo['DS'])) {
+               $graph[] = sprintf('LINE1:%s_avg#%s:%s ', $k, rrd_get_color($n++, true), $k.substr('                  ', 0, $l_max-strlen($k)));
+               if (isset($opts['tinylegend']) && $opts['tinylegend'])
+                       continue;
+               if ($has_avg)
+                       $graph[] = sprintf('GPRINT:%s_avg:AVERAGE:%%5.1lf%%s Avg%s', $k, $has_max || $has_min || $has_avg ? ',' : "\\l");
+               if ($has_min)
+                       $graph[] = sprintf('GPRINT:%s_min:MIN:%%5.1lf%%s Max%s', $k, $has_max || $has_avg ? ',' : "\\l");
+               if ($has_max)
+                       $graph[] = sprintf('GPRINT:%s_max:MAX:%%5.1lf%%s Max%s', $k, $has_avg ? ',' : "\\l");
+               if ($has_avg)
+                       $graph[] = sprintf('GPRINT:%s_avg:LAST:%%5.1lf%%s Last\\l', $k);
+       }
+
+       $rrd_cmd = array(RRDTOOL, 'graph', '-', '-a', 'PNG', '-w', $config['rrd_width'], '-h', $config['rrd_height'], '-s', -1*$timespan_def['seconds'], '-t', $rrdfile);
+       $rrd_cmd = array_merge($rrd_cmd, $config['rrd_opts'], $opts['rrd_opts'], $graph);
+
+       $cmd = RRDTOOL;
+       for ($i = 1; $i < count($rrd_cmd); $i++)
+               $cmd .= ' '.escapeshellarg($rrd_cmd[$i]);
+
+       return $cmd;
+}
+
+/**
+ * Draw RRD file based on it's structure
+ * @timespan
+ * @host
+ * @plugin
+ * @pinst
+ * @type
+ * @tinst
+ * @opts
+ * @return Commandline to call RRDGraph in order to generate the final graph
+ */
+function collectd_draw_generic($timespan, $host, $plugin, $pinst = null, $type, $tinst = null) {
+       global $config, $GraphDefs;
+       $timespan_def = null;
+       foreach ($config['timespan'] as &$ts)
+               if ($ts['name'] == $timespan)
+                       $timespan_def = $ts;
+       if (is_null($timespan_def))
+               $timespan_def = reset($config['timespan']);
+
+       if (!isset($GraphDefs[$type]))
+               return false;
+
+       $rrd_file = sprintf('%s/%s%s%s/%s%s%s', $host, $plugin, is_null($pinst) ? '' : '-', $pinst, $type, is_null($tinst) ? '' : '-', $tinst);
+       $rrd_cmd  = array(RRDTOOL, 'graph', '-', '-a', 'PNG', '-w', $config['rrd_width'], '-h', $config['rrd_height'], '-s', -1*$timespan_def['seconds'], '-t', $rrd_file);
+       $rrd_cmd  = array_merge($rrd_cmd, $config['rrd_opts']);
+       $rrd_args = $GraphDefs[$type];
+
+       foreach ($config['datadirs'] as $datadir) {
+               $file = $datadir.'/'.$rrd_file.'.rrd';
+               if (!is_file($file))
+                       continue;
+
+               $file = str_replace(":", "\\:", $file);
+               $rrd_args = str_replace('{file}', rrd_escape($file), $rrd_args);
+
+               $rrdgraph = array_merge($rrd_cmd, $rrd_args);
+               $cmd = RRDTOOL;
+               for ($i = 1; $i < count($rrdgraph); $i++)
+                       $cmd .= ' '.escapeshellarg($rrdgraph[$i]);
+
+               return $cmd;
+       }
+       return false;
+}
+
+/**
+ * Draw stack-graph for set of RRD files
+ * @opts Graph options like colors
+ * @sources List of array(name, file, ds)
+ * @return Commandline to call RRDGraph in order to generate the final graph
+ */
+function collectd_draw_meta_stack(&$opts, &$sources) {
+       global $config;
+       $timespan_def = null;
+       if (!isset($opts['timespan']))
+               $timespan_def = reset($config['timespan']);
+       else foreach ($config['timespan'] as &$ts)
+               if ($ts['name'] == $opts['timespan'])
+                       $timespan_def = $ts;
+
+       if (!isset($opts['title']))
+               $opts['title'] = 'Unknown title';
+       if (!isset($opts['rrd_opts']))
+               $opts['rrd_opts'] = array();
+       if (!isset($opts['colors']))
+               $opts['colors'] = array();
+       if (isset($opts['logarithmic']) && $opts['logarithmic'])
+               array_unshift($opts['rrd_opts'], '-o');
+
+       $cmd = array(RRDTOOL, 'graph', '-', '-a', 'PNG', '-w', $config['rrd_width'], '-h', $config['rrd_height'], '-s', -1*$timespan_def['seconds'], '-t', $opts['title']);
+       $cmd = array_merge($cmd, $config['rrd_opts'], $opts['rrd_opts']);
+       $max_inst_name = 0;
+
+       foreach($sources as &$inst_data) {
+               $inst_name = str_replace('!', '_', $inst_data['name']);
+               $file      = $inst_data['file'];
+               $ds        = isset($inst_data['ds']) ? $inst_data['ds'] : 'value';
+
+               if (strlen($inst_name) > $max_inst_name)
+                       $max_inst_name = strlen($inst_name);
+
+               if (!is_file($file))
+                       continue;
+
+               $cmd[] = 'DEF:'.$inst_name.'_min='.rrd_escape($file).':'.$ds.':MIN';
+               $cmd[] = 'DEF:'.$inst_name.'_avg='.rrd_escape($file).':'.$ds.':AVERAGE';
+               $cmd[] = 'DEF:'.$inst_name.'_max='.rrd_escape($file).':'.$ds.':MAX';
+               $cmd[] = 'CDEF:'.$inst_name.'_nnl='.$inst_name.'_avg,UN,0,'.$inst_name.'_avg,IF';
+       }
+       $inst_data = end($sources);
+       $inst_name = $inst_data['name'];
+       $cmd[] = 'CDEF:'.$inst_name.'_stk='.$inst_name.'_nnl';
+
+       $inst_data1 = end($sources);
+       while (($inst_data0 = prev($sources)) !== false) {
+               $inst_name0 = str_replace('!', '_', $inst_data0['name']);
+               $inst_name1 = str_replace('!', '_', $inst_data1['name']);
+
+               $cmd[] = 'CDEF:'.$inst_name0.'_stk='.$inst_name0.'_nnl,'.$inst_name1.'_stk,+';
+               $inst_data1 = $inst_data0;
+       }
+
+       foreach($sources as &$inst_data) {
+               $inst_name = str_replace('!', '_', $inst_data['name']);
+               $legend = sprintf('%s', $inst_data['name']);
+               while (strlen($legend) < $max_inst_name)
+                       $legend .= ' ';
+               $number_format = isset($opts['number_format']) ? $opts['number_format'] : '%6.1lf';
+
+               if (isset($opts['colors'][$inst_name]))
+                       $line_color = new CollectdColor($opts['colors'][$inst_name]);
+               else
+                       $line_color = new CollectdColor('random');
+               $area_color = new CollectdColor($line_color);
+               $area_color->fade();
+
+               $cmd[] = 'AREA:'.$inst_name.'_stk#'.$area_color->as_string();
+               $cmd[] = 'LINE1:'.$inst_name.'_stk#'.$line_color->as_string().':'.$legend;
+               if (!(isset($opts['tinylegend']) && $opts['tinylegend'])) {
+                       $cmd[] = 'GPRINT:'.$inst_name.'_min:MIN:'.$number_format.' Min,';
+                       $cmd[] = 'GPRINT:'.$inst_name.'_avg:AVERAGE:'.$number_format.' Avg,';
+                       $cmd[] = 'GPRINT:'.$inst_name.'_max:MAX:'.$number_format.' Max,';
+                       $cmd[] = 'GPRINT:'.$inst_name.'_avg:LAST:'.$number_format.' Last\\l';
+               }
+       }
+
+       $rrdcmd = RRDTOOL;
+       for ($i = 1; $i < count($cmd); $i++)
+               $rrdcmd .= ' '.escapeshellarg($cmd[$i]);
+       return $rrdcmd;
+}
+
+/**
+ * Draw stack-graph for set of RRD files
+ * @opts Graph options like colors
+ * @sources List of array(name, file, ds)
+ * @return Commandline to call RRDGraph in order to generate the final graph
+ */
+function collectd_draw_meta_line(&$opts, &$sources) {
+       global $config;
+       $timespan_def = null;
+       if (!isset($opts['timespan']))
+               $timespan_def = reset($config['timespan']);
+       else foreach ($config['timespan'] as &$ts)
+               if ($ts['name'] == $opts['timespan'])
+                       $timespan_def = $ts;
+
+       if (!isset($opts['title']))
+               $opts['title'] = 'Unknown title';
+       if (!isset($opts['rrd_opts']))
+               $opts['rrd_opts'] = array();
+       if (!isset($opts['colors']))
+               $opts['colors'] = array();
+       if (isset($opts['logarithmic']) && $opts['logarithmic'])
+               array_unshift($opts['rrd_opts'], '-o');
+
+       $cmd = array(RRDTOOL, 'graph', '-', '-a', 'PNG', '-w', $config['rrd_width'], '-h', $config['rrd_height'], '-s', -1*$timespan_def['seconds'], '-t', $opts['title']);
+       $cmd = array_merge($cmd, $config['rrd_opts'], $opts['rrd_opts']);
+       $max_inst_name = 0;
+
+       foreach ($sources as &$inst_data) {
+               $inst_name = str_replace('!', '_', $inst_data['name']);
+               $file      = $inst_data['file'];
+               $ds        = isset($inst_data['ds']) ? $inst_data['ds'] : 'value';
+
+               if (strlen($inst_name) > $max_inst_name)
+                       $max_inst_name = strlen($inst_name);
+
+               if (!is_file($file))
+                       continue;
+
+               $cmd[] = 'DEF:'.$inst_name.'_min='.rrd_escape($file).':'.$ds.':MIN';
+               $cmd[] = 'DEF:'.$inst_name.'_avg='.rrd_escape($file).':'.$ds.':AVERAGE';
+               $cmd[] = 'DEF:'.$inst_name.'_max='.rrd_escape($file).':'.$ds.':MAX';
+       }
+
+       foreach ($sources as &$inst_data) {
+               $inst_name = str_replace('!', '_', $inst_data['name']);
+               $legend = sprintf('%s', $inst_name);
+               while (strlen($legend) < $max_inst_name)
+                       $legend .= ' ';
+               $number_format = isset($opts['number_format']) ? $opts['number_format'] : '%6.1lf';
+
+               if (isset($opts['colors'][$inst_name]))
+                       $line_color = new CollectdColor($opts['colors'][$inst_name]);
+               else
+                       $line_color = new CollectdColor('random');
+
+               $cmd[] = 'LINE1:'.$inst_name.'_avg#'.$line_color->as_string().':'.$legend;
+               if (!(isset($opts['tinylegend']) && $opts['tinylegend'])) {
+                       $cmd[] = 'GPRINT:'.$inst_name.'_min:MIN:'.$number_format.' Min,';
+                       $cmd[] = 'GPRINT:'.$inst_name.'_avg:AVERAGE:'.$number_format.' Avg,';
+                       $cmd[] = 'GPRINT:'.$inst_name.'_max:MAX:'.$number_format.' Max,';
+                       $cmd[] = 'GPRINT:'.$inst_name.'_avg:LAST:'.$number_format.' Last\\l';
+               }
+       }
+
+       $rrdcmd = RRDTOOL;
+       for ($i = 1; $i < count($cmd); $i++)
+               $rrdcmd .= ' '.escapeshellarg($cmd[$i]);
+       return $rrdcmd;
+}
+
+?>
diff --git a/contrib/php-collection/graph.php b/contrib/php-collection/graph.php
new file mode 100644 (file)
index 0000000..b9fefa6
--- /dev/null
@@ -0,0 +1,217 @@
+<?php // vim:fenc=utf-8:filetype=php:ts=4
+/*
+ * Copyright (C) 2009  Bruno Prémont <bonbons AT linux-vserver.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+error_reporting(E_ALL | E_NOTICE | E_WARNING);
+
+require('config.php');
+require('functions.php');
+require('definitions.php');
+
+function makeTextBlock($text, $fontfile, $fontsize, $width) {
+       // TODO: handle explicit line-break!
+       $words = explode(' ', $text);
+       $lines = array($words[0]);
+       $currentLine = 0;
+       foreach ($words as $word) {
+               $lineSize = imagettfbbox($fontsize, 0, $fontfile, $lines[$currentLine] . ' ' . $word);
+               if($lineSize[2] - $lineSize[0] < $width) {
+                       $lines[$currentLine] .= ' ' . $word;
+               } else {
+                       $currentLine++;
+                       $lines[$currentLine] = $word;
+               }
+       }
+       error_log(sprintf('Handles message "%s", %d words => %d/%d lines', $text, count($words), $currentLine, count($lines)));
+       return implode("\n", $lines);
+}
+
+/**
+ * No RRD files found that could match request
+ * @code HTTP error code
+ * @code_msg Short text description of HTTP error code
+ * @title Title for fake RRD graph
+ * @msg Complete error message to display in place of graph content
+ */
+function error($code, $code_msg, $title, $msg) {
+       global $config;
+       header(sprintf("HTTP/1.0 %d %s", $code, $code_msg));
+       header("Pragma: no-cache");
+       header("Expires: Mon, 01 Jan 2008 00:00:00 CET");
+       header("Content-Type: image/png");
+       $w = $config['rrd_width']+81;
+       $h = $config['rrd_height']+79;
+
+       $png     = imagecreate($w, $h);
+       $c_bkgnd = imagecolorallocate($png, 240, 240, 240);
+       $c_fgnd  = imagecolorallocate($png, 255, 255, 255);
+       $c_blt   = imagecolorallocate($png, 208, 208, 208);
+       $c_brb   = imagecolorallocate($png, 160, 160, 160);
+       $c_grln  = imagecolorallocate($png, 114, 114, 114);
+       $c_grarr = imagecolorallocate($png, 128,  32,  32);
+       $c_txt   = imagecolorallocate($png, 0, 0, 0);
+       $c_etxt  = imagecolorallocate($png, 64, 0, 0);
+
+       if (function_exists('imageantialias'))
+               imageantialias($png, true);
+       imagefilledrectangle($png, 0,   0, $w, $h, $c_bkgnd);
+       imagefilledrectangle($png, 51, 33, $w-31, $h-47, $c_fgnd);
+       imageline($png,  51,  30,  51, $h-43, $c_grln);
+       imageline($png,  48, $h-46, $w-28, $h-46, $c_grln);
+       imagefilledpolygon($png, array(49, 30,    51, 26,    53, 30), 3, $c_grarr);
+       imagefilledpolygon($png, array($w-28, $h-48,  $w-24, $h-46,  $w-28, $h-44), 3, $c_grarr);
+       imageline($png,    0,    0,   $w,    0, $c_blt);
+       imageline($png,    0,    1,   $w,    1, $c_blt);
+       imageline($png,    0,    0,    0,   $h, $c_blt);
+       imageline($png,    1,    0,    1,   $h, $c_blt);
+       imageline($png, $w-1,    0, $w-1,   $h, $c_brb);
+       imageline($png, $w-2,    1, $w-2,   $h, $c_brb);
+       imageline($png,    1, $h-2,   $w, $h-2, $c_brb);
+       imageline($png,    0, $h-1,   $w, $h-1, $c_brb);
+
+       imagestring($png, 4, ceil(($w-strlen($title)*imagefontwidth(4)) / 2), 10, $title, $c_txt);
+       imagestring($png, 5, 60, 35, sprintf('%s [%d]', $code_msg, $code), $c_etxt);
+       if (function_exists('imagettfbbox') && is_file($config['error_font'])) {
+               // Detailled error message
+               $fmt_msg = makeTextBlock($msg, $errorfont, 10, $w-86);
+               $fmtbox  = imagettfbbox(12, 0, $errorfont, $fmt_msg);
+               imagettftext($png, 10, 0, 55, 35+3+imagefontwidth(5)-$fmtbox[7]+$fmtbox[1], $c_txt, $errorfont, $fmt_msg);
+       } else {
+               imagestring($png, 4, 53, 35+6+imagefontwidth(5), $msg, $c_txt);
+       }
+
+       imagepng($png);
+       imagedestroy($png);
+}
+
+/**
+ * No RRD files found that could match request
+ */
+function error404($title, $msg) {
+       return error(404, "Not found", $title, $msg);
+}
+
+/**
+ * Incomplete / invalid request
+ */
+function error400($title, $msg) {
+       return error(400, "Bad request", $title, $msg);
+}
+
+/**
+ * Incomplete / invalid request
+ */
+function error500($title, $msg) {
+       return error(500, "Internal error", $title, $msg);
+}
+
+// Process input arguments
+$host     = read_var('host', $_GET, null);
+if (is_null($host))
+       return error400("?/?-?/?", "Missing host name");
+else if (!is_string($host))
+       return error400("?/?-?/?", "Expecting exactly 1 host name");
+else if (strlen($host) == 0)
+       return error400("?/?-?/?", "Host name may not be blank");
+
+$plugin   = read_var('plugin', $_GET, null);
+if (is_null($plugin))
+       return error400($host.'/?-?/?', "Missing plugin name");
+else if (!is_string($plugin))
+       return error400($host.'/?-?/?', "Plugin name must be a string");
+else if (strlen($plugin) == 0)
+       return error400($host.'/?-?/?', "Plugin name may not be blank");
+
+$pinst    = read_var('plugin_instance', $_GET, '');
+if (!is_string($pinst))
+       return error400($host.'/'.$plugin.'-?/?', "Plugin instance name must be a string");
+
+$type     = read_var('type', $_GET, '');
+if (is_null($type))
+       return error400($host.'/'.$plugin.(strlen($pinst) ? '-'.$pinst : '').'/?', "Missing type name");
+else if (!is_string($type))
+       return error400($host.'/'.$plugin.(strlen($pinst) ? '-'.$pinst : '').'/?', "Type name must be a string");
+else if (strlen($type) == 0)
+       return error400($host.'/'.$plugin.(strlen($pinst) ? '-'.$pinst : '').'/?', "Type name may not be blank");
+
+$tinst    = read_var('type_instance', $_GET, '');
+
+$graph_identifier = $host.'/'.$plugin.(strlen($pinst) ? '-'.$pinst : '').'/'.$type.(strlen($tinst) ? '-'.$tinst : '-*');
+
+$timespan = read_var('timespan', $_GET, $config['timespan'][0]['name']);
+$timespan_ok = false;
+foreach ($config['timespan'] as &$ts)
+       if ($ts['name'] == $timespan)
+               $timespan_ok = true;
+if (!$timespan_ok)
+       return error400($graph_identifier, "Unknown timespan requested");
+
+$logscale   = (boolean)read_var('logarithmic', $_GET, false);
+$tinylegend = (boolean)read_var('tinylegend', $_GET, false);
+
+// Check that at least 1 RRD exists for the specified request
+$all_tinst = collectd_list_types($host, $plugin, $pinst, $type);
+if (count($all_tinst) == 0)
+       return error404($graph_identifier, "No rrd file found for graphing");
+
+// Now that we are read, do the bulk work
+load_graph_definitions($logscale, $tinylegend);
+
+$pinst = strlen($pinst) == 0 ? null : $pinst;
+$tinst = strlen($tinst) == 0 ? null : $tinst;
+
+$opts  = array();
+$opts['timespan'] = $timespan;
+if ($logscale)
+       $opts['logarithmic'] = 1;
+if ($tinylegend)
+       $opts['tinylegend']  = 1;
+
+$rrd_cmd = false;
+if ((is_null($tinst) || $tinst == '@merge') && isset($MetaGraphDefs[$type])) {
+       $identifiers = array();
+       foreach ($all_tinst as &$atinst)
+               $identifiers[] = collectd_identifier($host, $plugin, is_null($pinst) ? '' : $pinst, $type, $atinst);
+       collectd_flush($identifiers);
+       $rrd_cmd = $MetaGraphDefs[$type]($host, $plugin, $pinst, $type, $all_tinst, $opts);
+} else {
+       if (!in_array(is_null($tinst) ? '' : $tinst, $all_tinst))
+               return error404($host.'/'.$plugin.(!is_null($pinst) ? '-'.$pinst : '').'/'.$type.(!is_null($tinst) ? '-'.$tinst : ''), "No rrd file found for graphing");
+       collectd_flush(collectd_identifier($host, $plugin, is_null($pinst) ? '' : $pinst, $type, is_null($tinst) ? '' : $tinst));
+       if (isset($GraphDefs[$type]))
+               $rrd_cmd = collectd_draw_generic($timespan, $host, $plugin, $pinst, $type, $tinst);
+       else
+               $rrd_cmd = collectd_draw_rrd($host, $plugin, $pinst, $type, $tinst);
+}
+
+if (isset($_GET['debug'])) {
+       header('Content-Type: text/plain; charset=utf-8');
+       printf("Would have executed:\n%s\n", $rrd_cmd);
+       return 0;
+} else if ($rrd_cmd) {
+       header('Content-Type: image/png');
+       header('Cache-Control: max-age=60');
+       $rt = 0;
+       passthru($rrd_cmd, $rt);
+       if ($rt != 0)
+               return error500($graph_identifier, "RRD failed to generate the graph: ".$rt);
+       return $rt;
+} else {
+       return error500($graph_identifier, "Failed to tell RRD how to generate the graph");
+}
+
+?>
diff --git a/contrib/php-collection/index.php b/contrib/php-collection/index.php
new file mode 100644 (file)
index 0000000..5953fbd
--- /dev/null
@@ -0,0 +1,330 @@
+<?php // vim:fenc=utf-8:filetype=php:ts=4
+/*
+ * Copyright (C) 2009  Bruno Prémont <bonbons AT linux-vserver.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+error_reporting(E_ALL | E_NOTICE | E_WARNING);
+
+require('config.php');
+require('functions.php');
+
+/**
+ * Send back new list content
+ * @items Array of options values to return to browser
+ * @method Name of Javascript method that will be called to process data
+ */
+function dhtml_response_list(&$items, $method) {
+       header("Content-Type: text/xml");
+
+       print('<?xml version="1.0" encoding="utf-8" ?>'."\n");
+       print("<response>\n");
+       printf(" <method>%s</method>\n", htmlspecialchars($method));
+       print(" <result>\n");
+       foreach ($items as &$item)
+               printf('  <option>%s</option>'."\n", htmlspecialchars($item));
+       print(" </result>\n");
+       print("</response>");
+}
+
+function dhtml_response_graphs(&$graphs, $method) {
+       header("Content-Type: text/xml");
+
+       print('<?xml version="1.0" encoding="utf-8" ?>'."\n");
+       print("<response>\n");
+       printf(" <method>%s</method>\n", htmlspecialchars($method));
+       print(" <result>\n");
+       foreach ($graphs as &$graph)
+               printf('  <graph host="%s" plugin="%s" plugin_instance="%s" type="%s" type_instance="%s" timespan="%s" logarithmic="%d" tinyLegend="%d" />'."\n",
+                      htmlspecialchars($graph['host']), htmlspecialchars($graph['plugin']), htmlspecialchars($graph['pinst']),
+                      htmlspecialchars($graph['type']), htmlspecialchars($graph['tinst']), htmlspecialchars($graph['timespan']),
+                      htmlspecialchars($graph['logarithmic']), htmlspecialchars($graph['tinyLegend']));
+       print(" </result>\n");
+       print("</response>");
+}
+
+/**
+ * Product page body with selection fields
+ */
+function build_page() {
+       global $config;
+
+       if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match('/compatible; MSIE [0-9]+.[0-9];/', $_SERVER['HTTP_USER_AGENT'])) {
+               // Internet Explorer does not support XHTML
+               header("Content-Type: text/html");
+
+               print('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">');
+               print('<html lang="en">'."\n");
+       } else {
+               header("Content-Type: application/xhtml+xml");
+
+               print('<?xml version="1.0" encoding="utf-8" ?>'."\n");
+               print('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'."\n");
+               print('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">'."\n");
+       }
+       $url_base = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off' ? 'https://' : 'http://').$_SERVER['HTTP_HOST'].dirname($_SERVER['PHP_SELF']).'/';
+?>
+       <head>
+               <title>Collectd graph viewer</title>
+               <link rel="icon" href="favicon.png" type="image/png" />
+               <style type="text/css">
+                       body, html { background-color: #EEEEEE; color: #000000; }
+                       h1 { text-align: center; }
+                       div.body { margin: auto; width: <?php echo isset($config['rrd_width']) ? 110+(int)$config['rrd_width'] : 600; ?>px; background: #FFFFFF; border: 1px solid #DDDDDD; }
+                       p.error { color: #CC0000; margin: 0em; }
+                       div.selector { margin: 0.5em 2em; }
+                       div.selectorbox { padding: 5px; border: 1px solid #CCCCCC; background-color: #F8F8F8; }
+                       div.selectorbox table { border: none; }
+                       div.selectorbox table td.s1 { border-bottom: 1px dashed #F0F0F0; padding-right: 1em; vertical-align: middle; }
+                       div.selectorbox table td.s2 { border-bottom: 1px dashed #F0F0F0; vertical-align: middle; }
+                       div.selectorbox table td.s3 { vertical-align: middle; }
+                       div.selectorbox table td.sc { padding: 0.5em 2em; text-align: center; }
+                       a img { border: none; }
+                       div.graphs { border-top: 1px solid #DDDDDD; padding: 5px; overflow: auto; }
+                       div.graphs_t { position: relative; }
+                       div.graph { text-align: right; }
+                       div.selector select { width: 100%; }
+                       table.toolbox { border: 1px solid #5500dd; padding: 0px; margin: 0px; background: #ffffff;}
+                       table.toolbox td.c1 { vertical-align: middle; text-align: left; padding-left: 0.3em; padding-right: 1em; border-right: 1px solid #5500dd; }
+                       table.toolbox td.c2 { vertical-align: middle; text-align: center; padding-left: 5px; padding-right: 5px; }
+               </style>
+               <script type="text/javascript">// <![CDATA[
+var dhtml_url = '<?php echo addslashes($url_base.basename($_SERVER['PHP_SELF'])); ?>';
+var graph_url = '<?php echo addslashes($url_base.'graph.php'); ?>';
+//             ]]></script>
+               <script type="text/javascript" src="<?php echo htmlspecialchars($url_base.'browser.js'); ?>"></script>
+       </head>
+
+       <body onload="ListRefreshHost(); GraphListRefresh(); "><div class="body">
+               <h1><img src="collectd-logo.png" align="bottom" alt="" /> Collectd browser for system statistics graphs</h1>
+               <div class="selector"><a href="javascript:toggleDiv('selector')"><span id="selector_sw">Hide</span> graph selection tool</a><div id="selector" class="selectorbox">
+                       <table>
+                               <tr>
+                                       <td class="s1">Host:</td>
+                                       <td class="s2"><select id="host_list"   name="host"   disabled="disabled" onchange="ListRefreshPlugin()">
+                                       </select></td>
+                                       <td class="s3"><a href="javascript:ListRefreshHost()"><img src="refresh.png" width="16" height="16" alt="R" title="Refresh host list" /></a></td>
+                               </tr>
+                               <tr>
+                                       <td class="s1">Plugin:</td>
+                                       <td class="s2"><select id="plugin_list" name="plugin" disabled="disabled" onchange="ListRefreshPluginInstance()">
+                                       </select></td>
+                                       <td class="s3"><a href="javascript:ListRefreshPlugin()"><img src="refresh.png" width="16" height="16" alt="R" title="Refresh plugin list" /></a></td>
+                               </tr>
+                               <tr>
+                                       <td class="s1">Plugin instance:</td>
+                                       <td class="s2"><select id="pinst_list"  name="pinst"  disabled="disabled" onchange="ListRefreshType()">
+                                       </select></td>
+                                       <td class="s3"><a href="javascript:ListRefreshPluginInstance()"><img src="refresh.png" width="16" height="16" alt="R" title="Refresh plugin instance list" /></a></td>
+                               </tr>
+                               <tr>
+                                       <td class="s1">Type:</td>
+                                       <td class="s2"><select id="type_list"   name="type"   disabled="disabled" onchange="ListRefreshTypeInstance()">
+                                       </select></td>
+                                       <td class="s3"><a href="javascript:ListRefreshType()"><img src="refresh.png" width="16" height="16" alt="R" title="Refresh type list" /></a></td>
+                               </tr>
+                               <tr>
+                                       <td class="s1">Type instance:</td>
+                                       <td class="s2"><select id="tinst_list"  name="tinst"  disabled="disabled" onchange="RefreshButtons()">
+                                       </select></td>
+                                       <td class="s3"><a href="javascript:ListRefreshTypeInstance()"><img src="refresh.png" width="16" height="16" alt="R" title="Refresh type instance list" /></a></td>
+                               </tr>
+                               <tr>
+                                       <td class="s1">Graph settings:</td>
+                                       <td class="s2"><select id="timespan" name="timespan">
+<?php                          foreach ($config['timespan'] as &$timespan)
+                                               printf("\t\t\t\t\t\t<option value=\"%s\">%s</option>\n", htmlspecialchars($timespan['name']), htmlspecialchars($timespan['label']));
+?>                                     </select>
+                                       <br /><label><input id="logarithmic"  name="logarithmic" type="checkbox" value="1" /> Logarithmic scale</label>
+                                       <br /><label><input id="tinylegend"  name="tinylegend" type="checkbox" value="1" /> Minimal legend</label></td>
+                                       <td class="s3"></td>
+                               </tr>
+                               <tr>
+                                       <td class="sc" colspan="3"><input id="btnAdd"     name="btnAdd"     type="button" disabled="disabled" onclick="GraphAppend()" value="Add graph" />
+                                       <input id="btnClear"   name="btnClear"   type="button" disabled="disabled" onclick="GraphDropAll()" value="Remove all graphs" />
+                                       <input id="btnRefresh" name="btnRefresh" type="button" disabled="disabled" onclick="GraphRefreshAll()" value="Refresh all graphs" /></td>
+                               </tr>
+                               <tr>
+                                       <td class="s1" rowspan="2">Graph list favorites:</td>
+                                       <td class="s3"><input type="text" style="width: 100%" maxlength="30" id="GraphListName" name="GraphListName" value="default" onchange="GraphListCheckName(false)" /></td>
+                                       <td class="s3"><a href="javascript:GraphSave()"><img src="save.png" width="16" height="16" alt="S" title="Save graph list to cookie" /></a></td>
+                               </tr>
+                               <tr>
+                                       <td class="s2"><select id="GraphList" name="GraphList" onfocus="GraphListRefresh()">
+                                               <option value="default">default</option>
+                                       </select></td>
+                                       <td class="s3"><a href="javascript:GraphLoad()"><img src="load.png" width="16" height="16" alt="L" title="Load graph list from cookie" /></a><a href="javascript:GraphDrop()"><img src="delete.png" width="16" height="16" alt="D" title="Delete graph list from cookie" /></a></td>
+                               </tr>
+                       </table>
+               </div></div>
+               <div class="graphs"><div id="graphs" class="graphs_t">
+                       <div id="nograph">Please use above graph selection tool to add graphs to this area.<?php
+                       // Config checking
+                       if (!isset($config['datadirs']))
+                               echo '<p class="error">Config error: $config["datadirs"] is not set</p>';
+                       else if (!is_array($config['datadirs']))
+                               echo '<p class="error">Config error: $config["datadirs"] is not an array</p>';
+                       else if (count($config['datadirs']) == 0)
+                               echo '<p class="error">Config error: $config["datadirs"] is empty</p>';
+                       else foreach ($config['datadirs'] as $datadir)
+                               if (!is_dir($datadir))
+                                       echo '<p class="error">Config error: $config["datadirs"], '.htmlspecialchars($datadir).' does not exist</p>';
+                       if (!isset($config['rrd_width']))
+                               echo '<p class="error">Config error: $config["rrd_width"] is not set</p>';
+                       else if (10 > (int)$config['rrd_width'])
+                               echo '<p class="error">Config error: $config["rrd_width"] is invalid. Integer >= 10 expected</p>';
+                       if (!isset($config['rrd_height']))
+                               echo '<p class="error">Config error: $config["rrd_height"] is not set</p>';
+                       else if (10 > (int)$config['rrd_height'])
+                               echo '<p class="error">Config error: $config["rrd_height"] is invalid. Integer >= 10 expected</p>';
+                       if (!isset($config['rrd_opts']))
+                               echo '<p class="error">Config error: $config["rrd_opts"] is not set</p>';
+                       else if (!is_array($config['rrd_opts']))
+                               echo '<p class="error">Config error: $config["rrd_opts"] is not an array</p>';
+                       if (!isset($config['timespan']))
+                               echo '<p class="error">Config error: $config["timespan"] is not set</p>';
+                       else if (!is_array($config['timespan']))
+                               echo '<p class="error">Config error: $config["timespan"] is not an array</p>';
+                       else if (count($config['timespan']) == 0)
+                               echo '<p class="error">Config error: $config["timespan"] is empty</p>';
+                       else foreach ($config['timespan'] as &$timespan)
+                               if (!is_array($timespan) || !isset($timespan['name']) || !isset($timespan['label']) || !isset($timespan['seconds']) || 10 > (int)$timespan['seconds'])
+                                       echo '<p class="error">Config error: $config["timespan"], invalid entry found</p>';
+                       if (!is_null($config['collectd_sock']) && strncmp('unix://', $config['collectd_sock'], 7) != 0)
+                               echo '<p class="error">Config error: $config["collectd_sock"] is not valid</p>';
+                       if (!defined('RRDTOOL'))
+                               echo '<p class="error">Config error: RRDTOOL is not defined</p>';
+                       else if (!is_executable(RRDTOOL))
+                               echo '<p class="error">Config error: RRDTOOL ('.htmlspecialchars(RRDTOOL).') is not executable</p>';
+                       ?></div>
+               </div></div>
+               <input type="hidden" name="ge_graphid" id="ge_graphid" value="" />
+               <table id="ge" class="toolbox" style="position: absolute; display: none; " cellspacing="1" cellpadding="0">
+                       <tr>
+                               <td class="c1" rowspan="3"><select id="ge_timespan" name="ge_timespan" onchange="GraphAdjust(null)"><?php
+                               foreach ($config['timespan'] as &$timespan)
+                                       printf("\t\t\t\t\t\t<option value=\"%s\">%s</option>\n", htmlspecialchars($timespan['name']), htmlspecialchars($timespan['label']));
+                               ?></select><br />
+                               <label><input id="ge_logarithmic"  name="ge_logarithmic" type="checkbox" value="1" onchange="GraphAdjust(null)" /> Logarithmic scale</label><br />
+                               <label><input id="ge_tinylegend"  name="ge_tinylegend" type="checkbox" value="1" onchange="GraphAdjust(null)" /> Minimal legend</label></td>
+                               <td class="c2"><a href="javascript:GraphMoveUp(null)"><img src="move-up.png" alt="UP" title="Move graph up"/></a></td>
+                       </tr>
+                       <tr>
+                               <td class="c2"><a href="javascript:GraphRefresh(null)"><img src="refresh.png" alt="R" title="Refresh graph"/></a>&nbsp;<a href="javascript:GraphRemove(null)"><img src="delete.png" alt="RM" title="Remove graph"/></a></td>
+                       </tr>
+                       <tr>
+                               <td class="c2"><a href="javascript:GraphMoveDown(null)"><img src="move-down.png" alt="DN" title="Move graph down"/></a></td>
+                       </tr>
+               </table>
+       </div></body>
+</html><?php
+}
+
+
+/*
+ * Select action based on user input
+ */
+$action = read_var('action', $_POST, 'overview');
+switch ($action) {
+       case 'list_hosts':
+               // Generate a list of hosts
+               $hosts = collectd_list_hosts();
+               if (count($hosts) > 1)
+                       array_unshift($hosts, '@all');
+               return dhtml_response_list($hosts, 'ListOfHost');
+
+       case 'list_plugins':
+               // Generate list of plugins for selected hosts
+               $arg_hosts = read_var('host', $_POST, '');
+               if (is_array($arg_hosts))
+                       $arg_hosts = reset($arg_hosts);
+               $plugins = collectd_list_plugins($arg_hosts);
+               if (count($plugins) > 1)
+                       array_unshift($plugins, '@all');
+               return dhtml_response_list($plugins, 'ListOfPlugin');
+
+       case 'list_pinsts':
+               // Generate list of plugin_instances for selected hosts and plugin
+               $arg_hosts = read_var('host', $_POST, '');
+               if (is_array($arg_hosts))
+                       $arg_hosts = reset($arg_hosts);
+               $arg_plugin = read_var('plugin', $_POST, '');
+               $pinsts = collectd_list_plugins($arg_hosts, $arg_plugin);
+               if (count($pinsts) > 1)
+                       array_unshift($pinsts, '@all' /* , '@merge_sum', '@merge_avg', '@merge_stack', '@merge_line' */);
+               return dhtml_response_list($pinsts, 'ListOfPluginInstance');
+
+       case 'list_types':
+               // Generate list of types for selected hosts, plugin and plugin-instance
+               $arg_hosts  = read_var('host', $_POST, '');
+               if (is_array($arg_hosts))
+                       $arg_hosts = reset($arg_hosts);
+               $arg_plugin = read_var('plugin', $_POST, '');
+               $arg_pinst  = read_var('plugin_instance', $_POST, '');
+               $types = collectd_list_types($arg_hosts, $arg_plugin, $arg_pinst);
+               if (count($types) > 1)
+                       array_unshift($types, '@all');
+               return dhtml_response_list($types, 'ListOfType');
+
+       case 'list_tinsts':
+               // Generate list of types for selected hosts, plugin and plugin-instance
+               $arg_hosts  = read_var('host', $_POST, '');
+               if (is_array($arg_hosts))
+                       $arg_hosts = reset($arg_hosts);
+               $arg_plugin = read_var('plugin', $_POST, '');
+               $arg_pinst  = read_var('plugin_instance', $_POST, '');
+               $arg_type   = read_var('type', $_POST, '');
+               $tinsts = collectd_list_types($arg_hosts, $arg_plugin, $arg_pinst, $arg_type);
+               if (count($tinsts))
+                       if ($arg_type != '@all') {
+                               require('definitions.php');
+                               load_graph_definitions();
+                               if (isset($MetaGraphDefs[$arg_type]))
+                                       array_unshift($tinsts, '@merge');
+                               if (count($tinsts) > 1)
+                                       array_unshift($tinsts, '@all');
+                       } else {
+                               array_unshift($tinsts, /* '@merge_sum', '@merge_avg', '@merge_stack', '@merge_line', */ '@merge');
+                               if (count($tinsts) > 1)
+                                       array_unshift($tinsts, '@all');
+                       }
+               return dhtml_response_list($tinsts, 'ListOfTypeInstance');
+
+       case 'list_graphs':
+               // Generate list of types for selected hosts, plugin and plugin-instance
+               $arg_hosts  = read_var('host', $_POST, '');
+               if (is_array($arg_hosts))
+                       $arg_hosts = reset($arg_hosts);
+               $arg_plugin = read_var('plugin', $_POST, '');
+               $arg_pinst  = read_var('plugin_instance', $_POST, '');
+               $arg_type   = read_var('type', $_POST, '');
+               $arg_tinst  = read_var('type_instance', $_POST, '');
+               $arg_log    = (int)read_var('logarithmic', $_POST, '0');
+               $arg_legend = (int)read_var('tinyLegend', $_POST, '0');
+               $arg_period = read_var('timespan', $_POST, '');
+               $graphs = collectd_list_graphs($arg_hosts, $arg_plugin, $arg_pinst, $arg_type, $arg_tinst);
+               foreach ($graphs as &$graph) {
+                       $graph['logarithmic'] = $arg_log;
+                       $graph['tinyLegend']  = $arg_legend;
+                       $graph['timespan']    = $arg_period;
+               }
+               return dhtml_response_graphs($graphs, 'ListOfGraph');
+
+       case 'overview':
+       default:
+               return build_page();
+               break;
+}
+?>
diff --git a/contrib/python/getsigchld.py b/contrib/python/getsigchld.py
new file mode 100644 (file)
index 0000000..557adc0
--- /dev/null
@@ -0,0 +1,21 @@
+#!/usr/bin/python
+
+###############################################################################
+#         WARNING! Importing this script will break the exec plugin!          #
+###############################################################################
+# Use this if you want to create new processes from your python scripts.      #
+# Normally you will get a OSError exception when the new process terminates   #
+# because collectd will ignore the SIGCHLD python is waiting for.             #
+# This script will restore the default SIGCHLD behavior so python scripts can #
+# create new processes without errors.                                        #
+###############################################################################
+#         WARNING! Importing this script will break the exec plugin!          #
+###############################################################################
+
+import signal
+import collectd
+
+def init():
+       signal.signal(signal.SIGCHLD, signal.SIG_DFL)
+
+collectd.register_init(init)
diff --git a/contrib/redhat/apache.conf b/contrib/redhat/apache.conf
new file mode 100644 (file)
index 0000000..e9c767a
--- /dev/null
@@ -0,0 +1,8 @@
+LoadPlugin apache
+#<Plugin apache>
+#      URL "http://localhost/status?auto"
+#      User "www-user"
+#      Password "secret"
+#      CACert "/etc/ssl/ca.crt"
+#</Plugin>
+
diff --git a/contrib/redhat/collectd.conf b/contrib/redhat/collectd.conf
new file mode 100644 (file)
index 0000000..f8352ff
--- /dev/null
@@ -0,0 +1,210 @@
+#
+# Config file for collectd(1).
+# Please read collectd.conf(5) for a list of options.
+# http://collectd.org/
+#
+
+#Hostname    "localhost"
+FQDNLookup   true
+BaseDir     "/var/lib/collectd"
+PIDFile     "/var/run/collectd.pid"
+PluginDir   "/usr/lib/collectd"
+TypesDB     "/usr/share/collectd/types.db"
+Interval     10
+ReadThreads  5
+
+LoadPlugin apcups
+#LoadPlugin apple_sensors
+LoadPlugin battery
+LoadPlugin conntrack
+LoadPlugin cpu
+LoadPlugin cpufreq
+LoadPlugin csv
+LoadPlugin df
+LoadPlugin disk
+LoadPlugin dns
+LoadPlugin entropy
+LoadPlugin exec
+LoadPlugin hddtemp
+LoadPlugin interface
+#LoadPlugin iptables
+#LoadPlugin ipvs
+LoadPlugin irq
+#LoadPlugin libvirt
+LoadPlugin load
+LoadPlugin logfile
+LoadPlugin mbmon
+LoadPlugin memcached
+LoadPlugin memory
+LoadPlugin multimeter
+#LoadPlugin netlink
+LoadPlugin network
+LoadPlugin nfs
+LoadPlugin ntpd
+#LoadPlugin nut
+LoadPlugin perl
+LoadPlugin ping
+LoadPlugin processes
+LoadPlugin rrdtool
+LoadPlugin serial
+LoadPlugin swap
+LoadPlugin syslog
+#LoadPlugin tape
+LoadPlugin tcpconns
+LoadPlugin unixsock
+LoadPlugin users
+LoadPlugin uuid
+LoadPlugin vserver
+LoadPlugin wireless
+#LoadPlugin xmms
+
+
+#<Plugin apcups>
+#      Host "localhost"
+#      Port "3551"
+#</Plugin>
+
+#<Plugin csv>
+#      DataDir "/usr/var/lib/collectd/csv"
+#      StoreRates false
+#</Plugin>
+
+#<Plugin df>
+#      Device "/dev/hda1"
+#      Device "192.168.0.2:/mnt/nfs"
+#      MountPoint "/home"
+#      FSType "ext3"
+#      IgnoreSelected false
+#</Plugin>
+
+#<Plugin dns>
+#      Interface "eth0"
+#      IgnoreSource "192.168.0.1"
+#</Plugin>
+
+#<Plugin exec>
+#      Exec "user:group" "/path/to/exec"
+#      NotificationExec "/path/to/exec"
+#</Plugin>
+
+#<Plugin hddtemp>
+#      Host "127.0.0.1"
+#      Port "7634"
+#      TranslateDevicename false
+#</Plugin>
+
+#<Plugin interface>
+#      Interface "eth0"
+#      IgnoreSelected false
+#</Plugin>
+
+#<Plugin iptables>
+#      Chain table chain
+#</Plugin>
+
+#<Plugin irq>
+#      Irq 7
+#      Irq 8
+#      Irq 9
+#      IgnoreSelected true
+#</Plugin>
+
+#<Plugin libvirt>
+#      Connection "xen:///"
+#      RefreshInterval 60
+#      Domain "name"
+#      BlockDevice "name:device"
+#      InterfaceDevice "name:device"
+#      IgnoreSelected false
+#      HostnameFormat name
+#</Plugin>
+
+#<Plugin logfile>
+#      LogLevel info
+#      File STDOUT
+#      Timestamp true
+#</Plugin>
+
+#<Plugin mbmon>
+#      Host "127.0.0.1"
+#      Port "411"
+#</Plugin>
+
+#<Plugin memcached>
+#      Host "127.0.0.1"
+#      Port "11211"
+#</Plugin>
+
+#<Plugin netlink>
+#      Interface "All"
+#      VerboseInterface "All"
+#      QDisc "eth0" "pfifo_fast-1:0"
+#      Class "ppp0" "htb-1:10"
+#      Filter "ppp0" "u32-1:0"
+#      IgnoreSelected false
+#</Plugin>
+
+#<Plugin network>
+#      Server "ff18::efc0:4a42" "25826"
+#      Server "239.192.74.66" "25826"
+#      Listen "ff18::efc0:4a42" "25826"
+#      Listen "239.192.74.66" "25826"
+#      TimeToLive "128"
+#      Forward false
+#      CacheFlush 1800
+#</Plugin>
+
+#<Plugin ntpd>
+#      Host "localhost"
+#      Port 123
+#      ReverseLookups false
+#</Plugin>
+
+#<Plugin nut>
+#      UPS "upsname@hostname:port"
+#</Plugin>
+
+#<Plugin perl>
+#      IncludeDir "/my/include/path"
+#      BaseName "Collectd::Plugin"
+#      EnableDebugger ""
+#      LoadPlugin foo
+#</Plugin>
+
+#<Plugin ping>
+#      Host "host.foo.bar"
+#      TTL 255
+#</Plugin>
+
+#<Plugin processes>
+#      Process "name"
+#</Plugin>
+
+#<Plugin rrdtool>
+#      DataDir "/usr/var/lib/collectd/rrd"
+#      CacheTimeout 120
+#      CacheFlush   900
+#</Plugin>
+
+#<Plugin syslog>
+#      LogLevel info
+#</Plugin>
+
+#<Plugin tcpconns>
+#      ListeningPorts false
+#      LocalPort "25"
+#      RemotePort "25"
+#</Plugin>
+
+#<Plugin unixsock>
+#      SocketFile "/usr/var/run/collectd-unixsock"
+#      SocketGroup "collectd"
+#      SocketPerms "0660"
+#</Plugin>
+
+#<Plugin uuid>
+#      UUIDFile "/etc/uuid"
+#</Plugin>
+
+Include "/etc/collectd.d"
+
diff --git a/contrib/redhat/collectd.spec b/contrib/redhat/collectd.spec
new file mode 100644 (file)
index 0000000..6eefac9
--- /dev/null
@@ -0,0 +1,421 @@
+
+%define with_java %(test -z "$JAVA_HOME" ; echo $?)
+
+Summary:       Statistics collection daemon for filling RRD files.
+Name:          collectd
+Version:       5.0.1
+Release:       1%{?dist}
+Source:                http://collectd.org/files/%{name}-%{version}.tar.gz
+License:       GPL
+Group:         System Environment/Daemons
+BuildRoot:     %{_tmppath}/%{name}-%{version}-root
+BuildPrereq:   lm_sensors-devel, rrdtool-devel, libpcap-devel, net-snmp-devel, libstatgrab-devel, libxml2-devel, libiptcdata-devel
+# libcurl deps
+BuildPrereq:   curl-devel,libidn-devel,openssl-devel
+Requires:      rrdtool, perl-Regexp-Common, libstatgrab
+Packager:      RightScale <support@rightscale.com>
+Vendor:                collectd development team <collectd@verplant.org>
+
+%description
+collectd is a small daemon which collects system information periodically and
+provides mechanisms to monitor and store the values in a variety of ways. It
+is written in C for performance. Since the daemon doesn't need to startup
+every time it wants to update the values it's very fast and easy on the
+system. Also, the statistics are very fine grained since the files are updated
+every 10 seconds.
+
+
+%package apache
+Summary:       apache-plugin for collectd.
+Group:         System Environment/Daemons
+Requires:      collectd = %{version}, curl
+%description apache
+This plugin collects data provided by Apache's `mod_status'.
+
+%package email
+Summary:       email-plugin for collectd.
+Group:         System Environment/Daemons
+Requires:      collectd = %{version}, spamassassin
+%description email
+This plugin collects data provided by spamassassin.
+
+%package mysql
+Summary:       mysql-module for collectd.
+Group:         System Environment/Daemons
+Requires:      collectd = %{version}, mysql
+%description mysql
+MySQL querying plugin. This plugins provides data of issued commands, called
+handlers and database traffic.
+
+%package nginx
+Summary:       nginx-plugin for collectd.
+Group:         System Environment/Daemons
+Requires:      collectd = %{version}, curl
+%description nginx
+This plugin gets data provided by nginx.
+
+%package sensors
+Summary:       libsensors-module for collectd.
+Group:         System Environment/Daemons
+Requires:      collectd = %{version}, lm_sensors
+%description sensors
+This plugin for collectd provides querying of sensors supported by lm_sensors.
+
+%package snmp
+Summary:       snmp-module for collectd.
+Group:         System Environment/Daemons
+Requires:      collectd = %{version}, net-snmp
+%description snmp
+This plugin for collectd allows querying of network equipment using SNMP.
+
+%if %with_java
+%package java
+Summary:       java-module for collectd.
+Group:         System Environment/Daemons
+Requires:      collectd = %{version}, jdk >= 1.6
+BuildPrereq:   jdk >= 1.6
+%description java
+This plugin for collectd allows plugins to be written in Java and executed
+in an embedded JVM.
+%endif
+
+%prep
+rm -rf $RPM_BUILD_ROOT
+%setup
+
+%build
+./configure CFLAGS=-"DLT_LAZY_OR_NOW='RTLD_LAZY|RTLD_GLOBAL'" --prefix=%{_prefix} --sbindir=%{_sbindir} --mandir=%{_mandir} --libdir=%{_libdir} --sysconfdir=%{_sysconfdir} \
+    %{!?with_java:"--with-java=$JAVA_HOME --enable-java"} \
+    --disable-battery
+make
+
+%install
+make install DESTDIR=$RPM_BUILD_ROOT
+mkdir -p $RPM_BUILD_ROOT/etc/rc.d/init.d
+mkdir -p $RPM_BUILD_ROOT/var/www/cgi-bin
+cp contrib/redhat/init.d-collectd $RPM_BUILD_ROOT/etc/rc.d/init.d/collectd
+cp contrib/collection.cgi $RPM_BUILD_ROOT/var/www/cgi-bin
+mkdir -p $RPM_BUILD_ROOT/etc/collectd.d
+mkdir -p $RPM_BUILD_ROOT/var/lib/collectd
+### Clean up docs
+find contrib/ -type f -exec %{__chmod} a-x {} \;
+
+###Modify Config for Redhat Based Distros
+sed -i 's:#BaseDir     "/usr/var/lib/collectd":BaseDir     "/var/lib/collectd":' $RPM_BUILD_ROOT/etc/collectd.conf
+sed -i 's:#PIDFile     "/usr/var/run/collectd.pid":PIDFile     "/var/run/collectd.pid":' $RPM_BUILD_ROOT/etc/collectd.conf
+sed -i 's:#PluginDir   "/usr/lib/collectd":PluginDir   "%{_libdir}/collectd":' $RPM_BUILD_ROOT/etc/collectd.conf
+sed -i 's:#TypesDB     "/usr/share/collectd/types.db":TypesDB     "/usr/share/collectd/types.db":' $RPM_BUILD_ROOT/etc/collectd.conf
+sed -i 's:#Interval     10:Interval     30:' $RPM_BUILD_ROOT/etc/collectd.conf
+sed -i 's:#ReadThreads  5:ReadThreads  5:' $RPM_BUILD_ROOT/etc/collectd.conf
+###Include broken out config directory
+echo -e '\nInclude "/etc/collectd.d"' >> $RPM_BUILD_ROOT/etc/collectd.conf
+
+##Move config contribs
+cp contrib/redhat/apache.conf $RPM_BUILD_ROOT/etc/collectd.d/apache.conf
+cp contrib/redhat/email.conf $RPM_BUILD_ROOT/etc/collectd.d/email.conf
+cp contrib/redhat/sensors.conf $RPM_BUILD_ROOT/etc/collectd.d/sensors.conf
+cp contrib/redhat/mysql.conf $RPM_BUILD_ROOT/etc/collectd.d/mysql.conf
+cp contrib/redhat/nginx.conf $RPM_BUILD_ROOT/etc/collectd.d/nginx.conf
+cp contrib/redhat/snmp.conf $RPM_BUILD_ROOT/etc/collectd.d/snmp.conf
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%post
+/sbin/chkconfig --add collectd
+/sbin/chkconfig collectd on
+
+%preun
+if [ "$1" = 0 ]; then
+   /sbin/chkconfig collectd off
+   /etc/init.d/collectd stop
+   /sbin/chkconfig --del collectd
+fi
+exit 0
+
+%postun
+if [ "$1" -ge 1 ]; then
+    /etc/init.d/collectd restart
+fi
+exit 0
+
+%files
+%defattr(-,root,root)
+%doc AUTHORS COPYING ChangeLog INSTALL NEWS README contrib/
+%config %attr(0644,root,root) /etc/collectd.conf
+%attr(0755,root,root) /etc/rc.d/init.d/collectd
+%attr(0755,root,root) /var/www/cgi-bin/collection.cgi
+%attr(0755,root,root) %{_sbindir}/collectd
+%attr(0755,root,root) %{_bindir}/collectd-nagios
+%attr(0755,root,root) %{_bindir}/collectdctl
+%attr(0755,root,root) %{_sbindir}/collectdmon
+%attr(0644,root,root) %{_mandir}/man1/*
+%attr(0644,root,root) %{_mandir}/man5/*
+%dir /etc/collectd.d
+
+# client
+%attr(0644,root,root) /usr/include/collectd/client.h
+%attr(0644,root,root) /usr/include/collectd/lcc_features.h
+
+%attr(0644,root,root) %{_libdir}/libcollectdclient.*
+%attr(0644,root,root) %{_libdir}/pkgconfig/libcollectdclient.pc
+
+# macro to grab binaries for a plugin, given a name
+%define plugin_macro() \
+%attr(0644,root,root) %{_libdir}/%{name}/%1.a \
+%attr(0644,root,root) %{_libdir}/%{name}/%1.so* \
+%attr(0644,root,root) %{_libdir}/%{name}/%1.la
+
+%plugin_macro apcups
+%plugin_macro ascent
+%plugin_macro bind
+%plugin_macro conntrack
+%plugin_macro contextswitch
+%plugin_macro cpufreq
+%plugin_macro cpu
+%plugin_macro csv
+%plugin_macro curl
+%plugin_macro curl_xml
+%plugin_macro df
+%plugin_macro disk
+%plugin_macro dns
+%plugin_macro entropy
+%plugin_macro email
+%plugin_macro exec
+%plugin_macro filecount
+%plugin_macro fscache
+%plugin_macro hddtemp
+%plugin_macro interface
+%plugin_macro iptables
+%plugin_macro irq
+%plugin_macro load
+%plugin_macro logfile
+%plugin_macro madwifi
+
+%plugin_macro match_empty_counter
+%plugin_macro match_hashed
+%plugin_macro match_regex
+%plugin_macro match_timediff
+%plugin_macro match_value
+
+%plugin_macro mbmon
+%plugin_macro memcachec
+%plugin_macro memcached
+%plugin_macro memory
+%plugin_macro multimeter
+%plugin_macro network
+%plugin_macro nfs
+%plugin_macro ntpd
+%plugin_macro openvpn
+%plugin_macro olsrd
+%plugin_macro perl
+%plugin_macro powerdns
+%plugin_macro processes
+%plugin_macro protocols
+%plugin_macro python
+%plugin_macro rrdtool
+%plugin_macro serial
+%plugin_macro sensors
+%plugin_macro swap
+%plugin_macro syslog
+%plugin_macro table
+%plugin_macro tail
+
+%plugin_macro target_notification
+%plugin_macro target_replace
+%plugin_macro target_scale
+%plugin_macro target_set
+%plugin_macro target_v5upgrade
+
+%plugin_macro tcpconns
+%plugin_macro teamspeak2
+%plugin_macro ted
+%plugin_macro thermal
+%plugin_macro threshold
+
+%plugin_macro unixsock
+%plugin_macro uptime
+%plugin_macro users
+%plugin_macro uuid
+%plugin_macro vmem
+%plugin_macro vserver
+%plugin_macro wireless
+%plugin_macro write_http
+
+%attr(0644,root,root) %{_datadir}/%{name}/types.db
+
+%exclude %{_libdir}/perl5/5.8.8/%{_arch}-linux-thread-multi/perllocal.pod
+%attr(0644,root,root) %{_libdir}/perl5/site_perl/5.8.8/%{_arch}-linux-thread-multi/auto/Collectd/.packlist
+%attr(0644,root,root) /usr/lib/perl5/site_perl/5.8.8/Collectd.pm
+%attr(0644,root,root) /usr/lib/perl5/site_perl/5.8.8/Collectd/Unixsock.pm
+%attr(0644,root,root) /usr/lib/perl5/site_perl/5.8.8/Collectd/Plugins/OpenVZ.pm
+%attr(0644,root,root) /usr/lib/perl5/site_perl/5.8.8/Collectd/Plugins/Monitorus.pm
+%attr(0644,root,root) /usr/share/man/man3/Collectd::Unixsock.3pm.gz
+
+%exclude /usr/share/collectd/postgresql_default.conf
+
+%dir /var/lib/collectd
+
+%if %with_java
+%files java
+/usr/share/collectd/java/collectd-api.jar
+/usr/share/collectd/java/generic-jmx.jar
+%plugin_macro java
+%endif
+
+%files apache
+%config %attr(0644,root,root) /etc/collectd.d/apache.conf
+%plugin_macro apache
+
+%files email
+%attr(0644,root,root) %{_libdir}/%{name}/email.so*
+%attr(0644,root,root) %{_libdir}/%{name}/email.la
+%config %attr(0644,root,root) /etc/collectd.d/email.conf
+
+%files mysql
+%config %attr(0644,root,root) /etc/collectd.d/mysql.conf
+%plugin_macro mysql
+
+%files nginx
+%config %attr(0644,root,root) /etc/collectd.d/nginx.conf
+%plugin_macro nginx
+
+%files sensors
+%attr(0644,root,root) %{_libdir}/%{name}/sensors.so*
+%attr(0644,root,root) %{_libdir}/%{name}/sensors.la
+%config %attr(0644,root,root) /etc/collectd.d/sensors.conf
+
+%files snmp
+%attr(0644,root,root) /etc/collectd.d/snmp.conf
+%plugin_macro snmp
+
+%changelog
+* Tue Jan 03 2011 Monetate <jason.stelzer@monetate.com> 5.0.1
+- New upstream version
+- Changes to support 5.0.1
+
+* Tue Jan 04 2010 Rackspace <stu.hood@rackspace.com> 4.9.0
+- New upstream version
+- Changes to support 4.9.0
+- Added support for Java/GenericJMX plugin
+
+* Mon Mar 17 2008 RightScale <support@rightscale.com> 4.3.1
+- New upstream version
+- Changes to support 4.3.1
+- Added More Prereqs to support more plugins
+- Added support for perl plugin
+
+* Mon Aug 06 2007 Kjell Randa <Kjell.Randa@broadpark.no> 4.0.6
+- New upstream version
+
+* Wed Jul 25 2007 Kjell Randa <Kjell.Randa@broadpark.no> 4.0.5
+- New major releas
+- Changes to support 4.0.5
+
+* Wed Jan 11 2007 Iain Lea <iain@bricbrac.de> 3.11.0-0
+- fixed spec file to build correctly on fedora core
+- added improved init.d script to work with chkconfig
+- added %post and %postun to call chkconfig automatically
+
+* Sun Jul 09 2006 Florian octo Forster <octo@verplant.org> 3.10.0-1
+- New upstream version
+
+* Tue Jun 25 2006 Florian octo Forster <octo@verplant.org> 3.9.4-1
+- New upstream version
+
+* Tue Jun 01 2006 Florian octo Forster <octo@verplant.org> 3.9.3-1
+- New upstream version
+
+* Tue May 09 2006 Florian octo Forster <octo@verplant.org> 3.9.2-1
+- New upstream version
+
+* Tue May 09 2006 Florian octo Forster <octo@verplant.org> 3.8.5-1
+- New upstream version
+
+* Fri Apr 21 2006 Florian octo Forster <octo@verplant.org> 3.9.1-1
+- New upstream version
+
+* Fri Apr 14 2006 Florian octo Forster <octo@verplant.org> 3.9.0-1
+- New upstream version
+- Added the `apache' package.
+
+* Thu Mar 14 2006 Florian octo Forster <octo@verplant.org> 3.8.2-1
+- New upstream version
+
+* Thu Mar 13 2006 Florian octo Forster <octo@verplant.org> 3.8.1-1
+- New upstream version
+
+* Thu Mar 09 2006 Florian octo Forster <octo@verplant.org> 3.8.0-1
+- New upstream version
+
+* Sat Feb 18 2006 Florian octo Forster <octo@verplant.org> 3.7.2-1
+- Include `tape.so' so the build doesn't terminate because of missing files..
+- New upstream version
+
+* Sat Feb 04 2006 Florian octo Forster <octo@verplant.org> 3.7.1-1
+- New upstream version
+
+* Mon Jan 30 2006 Florian octo Forster <octo@verplant.org> 3.7.0-1
+- New upstream version
+- Removed the extra `hddtemp' package
+
+* Tue Jan 24 2006 Florian octo Forster <octo@verplant.org> 3.6.2-1
+- New upstream version
+
+* Fri Jan 20 2006 Florian octo Forster <octo@verplant.org> 3.6.1-1
+- New upstream version
+
+* Fri Jan 20 2006 Florian octo Forster <octo@verplant.org> 3.6.0-1
+- New upstream version
+- Added config file, `collectd.conf(5)', `df.so'
+- Added package `collectd-mysql', dependency on `mysqlclient10 | mysql'
+
+* Wed Dec 07 2005 Florian octo Forster <octo@verplant.org> 3.5.0-1
+- New upstream version
+
+* Sat Nov 26 2005 Florian octo Forster <octo@verplant.org> 3.4.0-1
+- New upstream version
+
+* Sat Nov 05 2005 Florian octo Forster <octo@verplant.org> 3.3.0-1
+- New upstream version
+
+* Tue Oct 26 2005 Florian octo Forster <octo@verplant.org> 3.2.0-1
+- New upstream version
+- Added statement to remove the `*.la' files. This fixes a problem when
+  `Unpackaged files terminate build' is in effect.
+- Added `processes.so*' to the main package
+
+* Fri Oct 14 2005 Florian octo Forster <octo@verplant.org> 3.1.0-1
+- New upstream version
+- Added package `collectd-hddtemp'
+
+* Fri Sep 30 2005 Florian octo Forster <octo@verplant.org> 3.0.0-1
+- New upstream version
+- Split the package into `collectd' and `collectd-sensors'
+
+* Fri Sep 16 2005 Florian octo Forster <octo@verplant.org> 2.1.0-1
+- New upstream version
+
+* Mon Sep 10 2005 Florian octo Forster <octo@verplant.org> 2.0.0-1
+- New upstream version
+
+* Mon Aug 29 2005 Florian octo Forster <octo@verplant.org> 1.8.0-1
+- New upstream version
+
+* Sun Aug 25 2005 Florian octo Forster <octo@verplant.org> 1.7.0-1
+- New upstream version
+
+* Sun Aug 21 2005 Florian octo Forster <octo@verplant.org> 1.6.0-1
+- New upstream version
+
+* Sun Jul 17 2005 Florian octo Forster <octo@verplant.org> 1.5.1-1
+- New upstream version
+
+* Sun Jul 17 2005 Florian octo Forster <octo@verplant.org> 1.5-1
+- New upstream version
+
+* Mon Jul 11 2005 Florian octo Forster <octo@verplant.org> 1.4.2-1
+- New upstream version
+
+* Sat Jul 09 2005 Florian octo Forster <octo@verplant.org> 1.4-1
+- Built on RedHat 7.3
diff --git a/contrib/redhat/email.conf b/contrib/redhat/email.conf
new file mode 100644 (file)
index 0000000..6f2caba
--- /dev/null
@@ -0,0 +1,8 @@
+LoadPlugin email
+#<Plugin email>
+#      SocketFile "/usr/var/run/collectd-email"
+#      SocketGroup "collectd"
+#      SocketPerms "0770"
+#      MaxConns 5
+#</Plugin>
+
diff --git a/contrib/redhat/init.d-collectd b/contrib/redhat/init.d-collectd
new file mode 100644 (file)
index 0000000..a60acb3
--- /dev/null
@@ -0,0 +1,69 @@
+#!/bin/bash
+#
+# collectd    Startup script for the Collectd statistics gathering daemon
+# chkconfig: - 99 01
+# description: Collectd is a statistics gathering daemon used to collect \
+#   system information ie. cpu, memory, disk, network
+# processname: collectd
+# config: /etc/collectd.conf
+# config: /etc/sysconfig/collectd
+# pidfile: /var/run/collectd.pid
+
+# Source function library.
+. /etc/init.d/functions
+
+RETVAL=0
+ARGS=""
+prog="collectdmon"
+service="collectd"
+CONFIG=/etc/collectd.conf
+COLLECTD=/usr/sbin/collectd
+COLLECTDMONPID=/var/run/collectdmon.pid
+
+if [ -r /etc/default/$prog ]; then
+       . /etc/default/$prog
+fi
+
+start () {
+       echo -n $"Starting collectd: "
+       if [ -r "$CONFIG" ]
+       then
+               daemon $prog -P $COLLECTDMONPID -c $COLLECTD -- -C "$CONFIG"
+               RETVAL=$?
+               echo
+               [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$service
+       fi
+}
+stop () {
+       echo -n $"Stopping collectd: "
+       killproc $prog
+       RETVAL=$?
+       echo
+       [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$service
+}
+# See how we were called.
+case "$1" in
+  start)
+       start
+       ;;
+  stop)
+       stop
+       ;;
+  status)
+       status $prog
+       ;;
+  restart|reload)
+       stop
+       start
+       ;;
+  condrestart)
+       [ -f /var/lock/subsys/$prog ] && restart || :
+       ;;
+  *)
+       echo $"Usage: $0 {start|stop|status|restart|reload|condrestart}"
+       exit 1
+esac
+
+exit $?
+
+# vim:syntax=sh
diff --git a/contrib/redhat/mysql.conf b/contrib/redhat/mysql.conf
new file mode 100644 (file)
index 0000000..ad87557
--- /dev/null
@@ -0,0 +1,9 @@
+LoadPlugin mysql
+
+#<Plugin mysql>
+#      Host "database.serv.er"
+#      User "db_user"
+#      Password "secret"
+#      Database "db_name"
+#</Plugin>
+
diff --git a/contrib/redhat/nginx.conf b/contrib/redhat/nginx.conf
new file mode 100644 (file)
index 0000000..56ce35d
--- /dev/null
@@ -0,0 +1,8 @@
+LoadPlugin nginx
+
+#<Plugin nginx>
+#      URL "http://localhost/status?auto"
+#      User "www-user"
+#      Password "secret"
+#      CACert "/etc/ssl/ca.crt"
+#</Plugin>
diff --git a/contrib/redhat/sensors.conf b/contrib/redhat/sensors.conf
new file mode 100644 (file)
index 0000000..82455f8
--- /dev/null
@@ -0,0 +1,9 @@
+LoadPlugin sensors
+
+#<Plugin sensors>
+#      Sensor "it8712-isa-0290/temperature-temp1"
+#      Sensor "it8712-isa-0290/fanspeed-fan3"
+#      Sensor "it8712-isa-0290/voltage-in8"
+#      IgnoreSelected false
+#</Plugin>
+
diff --git a/contrib/redhat/snmp.conf b/contrib/redhat/snmp.conf
new file mode 100644 (file)
index 0000000..e13833c
--- /dev/null
@@ -0,0 +1,44 @@
+LoadPlugin snmp
+
+#<Plugin snmp>
+#   <Data "powerplus_voltge_input">
+#       Type "voltage"
+#       Table false
+#       Instance "input_line1"
+#       Values "SNMPv2-SMI::enterprises.6050.5.4.1.1.2.1"
+#   </Data>
+#   <Data "hr_users">
+#       Type "users"
+#       Table false
+#       Instance ""
+#       Values "HOST-RESOURCES-MIB::hrSystemNumUsers.0"
+#   </Data>
+#   <Data "std_traffic">
+#       Type "if_octets"
+#       Table true
+#       Instance "IF-MIB::ifDescr"
+#       Values "IF-MIB::ifInOctets" "IF-MIB::ifOutOctets"
+#   </Data>
+#   
+#   <Host "some.switch.mydomain.org">
+#       Address "192.168.0.2"
+#       Version 1
+#       Community "community_string"
+#       Collect "std_traffic"
+#       Inverval 120
+#   </Host>
+#   <Host "some.server.mydomain.org">
+#       Address "192.168.0.42"
+#       Version 2
+#       Community "another_string"
+#       Collect "std_traffic" "hr_users"
+#   </Host>
+#   <Host "some.ups.mydomain.org">
+#       Address "192.168.0.3"
+#       Version 1
+#       Community "more_communities"
+#       Collect "powerplus_voltge_input"
+#       Interval 300
+#   </Host>
+#</Plugin>
+
diff --git a/contrib/rrd_filter.px b/contrib/rrd_filter.px
new file mode 100755 (executable)
index 0000000..d28f9f2
--- /dev/null
@@ -0,0 +1,874 @@
+#!/usr/bin/perl
+
+# collectd - contrib/rrd_filter.px
+# Copyright (C) 2007-2008  Florian octo Forster
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; only version 2 of the License is applicable.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+#
+# Authors:
+#   Florian octo Forster <octo at verplant.org>
+
+use strict;
+use warnings;
+
+=head1 NAME
+
+rrd_filter.px - Perform same advanced non-standard operations on an RRD file.
+
+=head1 SYNOPSYS
+
+  rrd_filter.px -i input.rrd -o output.rrd [options]
+
+=head1 DEPENDENCIES
+
+rrd_filter.px requires the RRDTool binary, Perl and the included
+L<Getopt::Long> module.
+
+=cut
+
+use Getopt::Long ('GetOptions');
+
+our $InFile;
+our $InDS = [];
+our $OutFile;
+our $OutDS = [];
+
+our $NewDSes = [];
+our $NewRRAs = [];
+
+our $Step = 0;
+
+our $Scale = 1.0;
+our $Shift = 0.0;
+
+our $Debug = 0;
+
+=head1 OPTIONS
+
+The following options can be passed on the command line:
+
+=over 4
+
+=item B<--infile> I<file>
+
+=item B<-i> I<file>
+
+Reads from I<file>. If I<file> ends in C<.rrd>, then C<rrdtool dump> is invoked
+to create an XML dump of the RRD file. Otherwise the XML dump is expected
+directly. The special filename C<-> can be used to read from STDIN.
+
+=item B<--outfile> I<file>
+
+=item B<-o> I<file>
+
+Writes output to I<file>. If I<file> ends in C<.rrd>, then C<rrdtool restore>
+is invoked to create a binary RRD file. Otherwise an XML output is written. The
+special filename C<-> can be used to write to STDOUT.
+
+=item B<--map> I<in_ds>:I<out_ds>
+
+=item B<-m> I<in_ds>:I<out_ds>
+
+Writes the datasource I<in_ds> to the output and renames it to I<out_ds>. This
+is useful to extract one DS from an RRD file.
+
+=item B<--step> I<seconds>
+
+=item B<-s> I<seconds>
+
+Changes the step of the output RRD file to be I<seconds>. The new stepsize must
+be a multiple of the old stepsize of the other way around. When increasing the
+stepsize the number of PDPs in each RRA must be dividable by the factor by
+which the stepsize is increased. The length of CDPs and the absolute length of
+RRAs (and thus the data itself) is not altered.
+
+Examples:
+
+  step =  10, rra_steps = 12   =>   step = 60, rra_steps =  2
+  step = 300, rra_steps =  1   =>   step = 10, rra_steps = 30
+
+=item B<--rra> B<RRA>:I<CF>:I<XFF>:I<steps>:I<rows>
+
+=item B<-a> B<RRA>:I<CF>:I<XFF>:I<steps>:I<rows>
+
+Inserts a new RRA in the generated RRD file. This is done B<after> the step has
+been adjusted, take that into account when specifying I<steps> and I<rows>. For
+an explanation of the format please see L<rrdcreate(1)>.
+
+=item B<--scale> I<factor>
+
+Scales the values by the factor I<factor>, i.E<nbsp>e. all values are
+multiplied by I<factor>.
+
+=item B<--shift> I<offset>
+
+Shifts all values by I<offset>, i.E<nbsp>e. I<offset> is added to all values.
+
+=back
+
+=cut
+
+GetOptions ("infile|i=s" => \$InFile,
+       "outfile|o=s" => \$OutFile,
+       'map|m=s' => sub
+       {
+               my ($in_ds, $out_ds) = split (':', $_[1]);
+               if (!defined ($in_ds) || !defined ($out_ds))
+               {
+                       print STDERR "Argument for `map' incorrect! The format is `--map in_ds:out_ds'\n";
+                       exit (1);
+               }
+               push (@$InDS, $in_ds);
+               push (@$OutDS, $out_ds);
+       },
+       'step|s=i' => \$Step,
+       'ds|d=s' => sub
+       {
+               #DS:ds-name:GAUGE | COUNTER | DERIVE | ABSOLUTE:heartbeat:min:max
+               my ($ds, $name, $type, $hb, $min, $max) = split (':', $_[1]);
+               if (($ds ne 'DS') || !defined ($max))
+               {
+                       print STDERR "Please use the standard RRDTool syntax when adding DSes. I. e. DS:<name>:<type>:<heartbeat>:<min>:<max>.\n";
+                       exit (1);
+               }
+               push (@$NewDSes, {name => $name, type => $type, heartbeat => $hb, min => $min, max => $max});
+       },
+       'rra|a=s' => sub
+       {
+               my ($rra, $cf, $xff, $steps, $rows) = split (':', $_[1]);
+               if (($rra ne 'RRA') || !defined ($rows))
+               {
+                       print STDERR "Please use the standard RRDTool syntax when adding RRAs. I. e. RRA:<cf><xff>:<steps>:<rows>.\n";
+                       exit (1);
+               }
+               push (@$NewRRAs, {cf => $cf, xff => $xff, steps => $steps, rows => $rows});
+       },
+       'scale=f' => \$Scale,
+       'shift=f' => \$Shift
+) or exit (1);
+
+if (!$InFile || !$OutFile)
+{
+       print STDERR "Usage: $0 -i <infile> -m <in_ds>:<out_ds> -s <step>\n";
+       exit (1);
+}
+if ((1 + @$InDS) != (1 + @$OutDS))
+{
+       print STDERR "You need the same amount of in- and out-DSes\n";
+       exit (1);
+}
+main ($InFile, $OutFile);
+exit (0);
+
+{
+my $ds_index;
+my $current_index;
+# state 0 == searching for DS index
+# state 1 == parse RRA header
+# state 2 == parse values
+my $state;
+my $out_cache;
+sub handle_line_dsmap
+{
+       my $line = shift;
+       my $index = shift;
+       my $ret = '';
+
+       if ((@$InDS == 0) || (@$OutDS == 0))
+       {
+               post_line ($line, $index + 1);
+               return;
+       }
+
+       if (!defined ($state))
+       {
+               $current_index = -1;
+               $state = 0;
+               $out_cache = [];
+
+               # $ds_index->[new_index] = old_index
+               $ds_index = [];
+               for (my $i = 0; $i < @$InDS; $i++)
+               {
+                       print STDOUT "DS map $i: $InDS->[$i] -> $OutDS->[$i]\n" if ($Debug);
+                       $ds_index->[$i] = -1;
+               }
+       }
+
+       if ($state == 0)
+       {
+               if ($line =~ m/<ds>/)
+               {
+                       $current_index++;
+                       $out_cache->[$current_index] = $line;
+               }
+               elsif ($line =~ m#<name>\s*([^<\s]+)\s*</name>#)
+               {
+                       # old_index == $current_index
+                       # new_index == $i
+                       for (my $i = 0; $i < @$InDS; $i++)
+                       {
+                               next if ($ds_index->[$i] >= 0);
+
+                               if ($1 eq $InDS->[$i])
+                               {
+                                       $line =~ s#<name>\s*([^<\s]+)\s*</name>#<name> $OutDS->[$i] </name>#;
+                                       $ds_index->[$i] = $current_index;
+                                       last;
+                               }
+                       }
+
+                       $out_cache->[$current_index] .= $line;
+               }
+               elsif ($line =~ m#<last_ds>\s*([^\s>]+)\s*</last_ds>#i)
+               {
+                       $out_cache->[$current_index] .= "\t\t<last_ds> NaN </last_ds>\n";
+               }
+               elsif ($line =~ m#<value>\s*([^\s>]+)\s*</value>#i)
+               {
+                       $out_cache->[$current_index] .= "\t\t<value> NaN </value>\n";
+               }
+               elsif ($line =~ m#</ds>#)
+               {
+                       $out_cache->[$current_index] .= $line;
+               }
+               elsif ($line =~ m#<rra>#)
+               {
+                       # Print out all the DS definitions we need
+                       for (my $new_index = 0; $new_index < @$InDS; $new_index++)
+                       {
+                               my $old_index = $ds_index->[$new_index];
+                               while ($out_cache->[$old_index] =~ m/^(.*)$/gm)
+                               {
+                                       post_line ("$1\n", $index + 1);
+                               }
+                       }
+
+                       # Clear the cache - it's used in state1, too.
+                       for (my $i = 0; $i <= $current_index; $i++)
+                       {
+                               $out_cache->[$i] = '';
+                       }
+
+                       $ret .= $line;
+                       $current_index = -1;
+                       $state = 1;
+               }
+               elsif ($current_index == -1)
+               {
+                       # Print all the lines before the first DS definition
+                       $ret .= $line;
+               }
+               else
+               {
+                       # Something belonging to a DS-definition
+                       $out_cache->[$current_index] .= $line;
+               }
+       }
+       elsif ($state == 1)
+       {
+               if ($line =~ m#<ds>#)
+               {
+                       $current_index++;
+                       $out_cache->[$current_index] .= $line;
+               }
+               elsif ($line =~ m#<value>\s*([^\s>]+)\s*</value>#i)
+               {
+                       $out_cache->[$current_index] .= "\t\t\t<value> NaN </value>\n";
+               }
+               elsif ($line =~ m#</cdp_prep>#)
+               {
+                       # Print out all the DS definitions we need
+                       for (my $new_index = 0; $new_index < @$InDS; $new_index++)
+                       {
+                               my $old_index = $ds_index->[$new_index];
+                               while ($out_cache->[$old_index] =~ m/^(.*)$/gm)
+                               {
+                                       post_line ("$1\n", $index + 1);
+                               }
+                       }
+
+                       # Clear the cache
+                       for (my $i = 0; $i <= $current_index; $i++)
+                       {
+                               $out_cache->[$i] = '';
+                       }
+
+                       $ret .= $line;
+                       $current_index = -1;
+               }
+               elsif ($line =~ m#<database>#)
+               {
+                       $ret .= $line;
+                       $state = 2;
+               }
+               elsif ($current_index == -1)
+               {
+                       # Print all the lines before the first DS definition
+                       # and after cdp_prep
+                       $ret .= $line;
+               }
+               else
+               {
+                       # Something belonging to a DS-definition
+                       $out_cache->[$current_index] .= $line;
+               }
+       }
+       elsif ($state == 2)
+       {
+               if ($line =~ m#</database>#)
+               {
+                       $ret .= $line;
+                       $current_index = -1;
+                       $state = 1;
+               }
+               else
+               {
+                       my @values = ();
+                       my $i;
+                       
+                       $ret .= "\t\t";
+
+                       if ($line =~ m#(<!-- .*? -->)#)
+                       {
+                               $ret .= "$1 ";
+                       }
+                       $ret .= "<row> ";
+
+                       $i = 0;
+                       while ($line =~ m#<v>\s*([^<\s]+)\s*</v>#g)
+                       {
+                               $values[$i] = $1;
+                               $i++;
+                       }
+
+                       for (my $new_index = 0; $new_index < @$InDS; $new_index++)
+                       {
+                               my $old_index = $ds_index->[$new_index];
+                               $ret .= '<v> ' . $values[$old_index] . ' </v> ';
+                       }
+                       $ret .= "</row>\n";
+               }
+       }
+       else
+       {
+               die;
+       }
+
+       if ($ret)
+       {
+               post_line ($ret, $index + 1);
+       }
+}} # handle_line_dsmap
+
+#
+# The _step_ handler
+#
+{
+my $step_factor_up;
+my $step_factor_down;
+sub handle_line_step
+{
+       my $line = shift;
+       my $index = shift;
+
+       if (!$Step)
+       {
+               post_line ($line, $index + 1);
+               return;
+       }
+
+       if ($Debug && !defined ($step_factor_up))
+       {
+               print STDOUT "New step: $Step\n";
+       }
+
+       $step_factor_up ||= 0;
+       $step_factor_down ||= 0;
+
+       if (($step_factor_up == 0) && ($step_factor_down == 0))
+       {
+               if ($line =~ m#<step>\s*(\d+)\s*</step>#i)
+               {
+                       my $old_step = 0 + $1;
+                       if ($Step < $old_step)
+                       {
+                               $step_factor_down = int ($old_step / $Step);
+                               if (($step_factor_down * $Step) != $old_step)
+                               {
+                                       print STDERR "The old step ($old_step seconds) "
+                                       . "is not a multiple of the new step "
+                                       . "($Step seconds).\n";
+                                       exit (1);
+                               }
+                               $line = "<step> $Step </step>\n";
+                       }
+                       elsif ($Step > $old_step)
+                       {
+                               $step_factor_up = int ($Step / $old_step);
+                               if (($step_factor_up * $old_step) != $Step)
+                               {
+                                       print STDERR "The new step ($Step seconds) "
+                                       . "is not a multiple of the old step "
+                                       . "($old_step seconds).\n";
+                                       exit (1);
+                               }
+                               $line = "<step> $Step </step>\n";
+                       }
+                       else
+                       {
+                               $Step = 0;
+                       }
+               }
+       }
+       elsif ($line =~ m#<pdp_per_row>\s*(\d+)\s*</pdp_per_row>#i)
+       {
+               my $old_val = 0 + $1;
+               my $new_val;
+               if ($step_factor_up)
+               {
+                       $new_val = int ($old_val / $step_factor_up);
+                       if (($new_val * $step_factor_up) != $old_val)
+                       {
+                               print STDERR "Can't divide number of PDPs per row ($old_val) by step-factor ($step_factor_up).\n";
+                               exit (1);
+                       }
+               }
+               else
+               {
+                       $new_val = $step_factor_down * $old_val;
+               }
+               $line = "<pdp_per_row> $new_val </pdp_per_row>\n";
+       }
+
+       post_line ($line, $index + 1);
+}} # handle_line_step
+
+#
+# The _add DS_ handler
+#
+{
+my $add_ds_done;
+sub handle_line_add_ds
+{
+  my $line = shift;
+  my $index = shift;
+
+  my $post = sub { for (@_) { post_line ($_, $index + 1); } };
+
+  if (!@$NewDSes)
+  {
+    $post->($line);
+    return;
+  }
+
+  if (!$add_ds_done && ($line =~ m#<rra>#i))
+  {
+    for (my $i = 0; $i < @$NewDSes; $i++)
+    {
+      my $ds = $NewDSes->[$i];
+      my $temp;
+
+      my $min;
+      my $max;
+
+      if ($Debug)
+      {
+       print STDOUT "Adding DS: name = $ds->{'name'}, type = $ds->{'type'}, heartbeat = $ds->{'heartbeat'}, min = $ds->{'min'}, max = $ds->{'max'}\n";
+      }
+
+      $min = 'NaN';
+      if (defined ($ds->{'min'}) && ($ds->{'min'} ne 'U'))
+      {
+       $min = sprintf ('%.10e', $ds->{'min'});
+      }
+      
+      $max = 'NaN';
+      if (defined ($ds->{'max'}) && ($ds->{'max'} ne 'U'))
+      {
+       $max = sprintf ('%.10e', $ds->{'max'});
+      }
+      
+
+      $post->("\t<ds>\n",
+      "\t\t<name> $ds->{'name'} </name>\n",
+      "\t\t<type> $ds->{'type'} </type>\n",
+      "\t\t<minimal_heartbeat> $ds->{'heartbeat'} </minimal_heartbeat>\n",
+      "\t\t<min> $min </min>\n",
+      "\t\t<max> $max </max>\n",
+      "\n",
+      "\t\t<!-- PDP Status -->\n",
+      "\t\t<last_ds> UNKN </last_ds>\n",
+      "\t\t<value> NaN </value>\n",
+      "\t\t<unknown_sec> 0 </unknown_sec>\n",
+      "\t</ds>\n",
+      "\n");
+    }
+
+    $add_ds_done = 1;
+  }
+  elsif ($add_ds_done && ($line =~ m#</ds>#i)) # inside a cdp_prep block
+  {
+    $post->("\t\t\t</ds>\n",
+       "\t\t\t<ds>\n",
+       "\t\t\t<primary_value> NaN </primary_value>\n",
+       "\t\t\t<secondary_value> NaN </secondary_value>\n",
+       "\t\t\t<value> NaN </value>\n",
+       "\t\t\t<unknown_datapoints> 0 </unknown_datapoints>\n");
+  }
+  elsif ($line =~ m#<row>#i)
+  {
+         my $insert = '<v> NaN </v>' x (0 + @$NewDSes);
+         $line =~ s#</row>#$insert</row>#i;
+  }
+
+  $post->($line);
+}} # handle_line_add_ds
+
+#
+# The _add RRA_ handler
+#
+{
+my $add_rra_done;
+my $num_ds;
+sub handle_line_add_rra
+{
+  my $line = shift;
+  my $index = shift;
+
+  my $post = sub { for (@_) { post_line ($_, $index + 1); } };
+
+  $num_ds ||= 0;
+
+  if (!@$NewRRAs || $add_rra_done)
+  {
+    $post->($line);
+    return;
+  }
+
+  if ($line =~ m#<ds>#i)
+  {
+    $num_ds++;
+  }
+  elsif ($line =~ m#<rra>#i)
+  {
+    for (my $i = 0; $i < @$NewRRAs; $i++)
+    {
+      my $rra = $NewRRAs->[$i];
+      my $temp;
+
+      if ($Debug)
+      {
+       print STDOUT "Adding RRA: CF = $rra->{'cf'}, xff = $rra->{'xff'}, steps = $rra->{'steps'}, rows = $rra->{'rows'}, num_ds = $num_ds\n";
+      }
+
+      $post->("\t<rra>\n",
+      "\t\t<cf> $rra->{'cf'} </cf>\n",
+      "\t\t<pdp_per_row> $rra->{'steps'} </pdp_per_row>\n",
+      "\t\t<params>\n",
+      "\t\t\t<xff> $rra->{'xff'} </xff>\n",
+      "\t\t</params>\n",
+      "\t\t<cdp_prep>\n");
+
+      for (my $j = 0; $j < $num_ds; $j++)
+      {
+       $post->("\t\t\t<ds>\n",
+       "\t\t\t\t<primary_value> NaN </primary_value>\n",
+       "\t\t\t\t<secondary_value> NaN </secondary_value>\n",
+       "\t\t\t\t<value> NaN </value>\n",
+       "\t\t\t\t<unknown_datapoints> 0 </unknown_datapoints>\n",
+       "\t\t\t</ds>\n");
+      }
+
+      $post->("\t\t</cdp_prep>\n", "\t\t<database>\n");
+      $temp = "\t\t\t<row>" . join ('', map { "<v> NaN </v>" } (1 .. $num_ds)) . "</row>\n";
+      for (my $j = 0; $j < $rra->{'rows'}; $j++)
+      {
+       $post->($temp);
+      }
+      $post->("\t\t</database>\n", "\t</rra>\n");
+    }
+
+    $add_rra_done = 1;
+  }
+
+  $post->($line);
+}} # handle_line_add_rra
+
+#
+# The _scale/shift_ handler
+#
+sub calculate_scale_shift 
+{
+  my $value = shift;
+  my $tag = shift;
+  my $scale = shift;
+  my $shift = shift;
+
+  if (lc ("$value") eq 'nan')
+  {
+    $value = 'NaN';
+    return ("<$tag> NaN </$tag>");
+  }
+
+  $value = ($scale * (0.0 + $value)) + $shift;
+  return (sprintf ("<%s> %1.10e </%s>", $tag, $value, $tag));
+}
+
+sub handle_line_scale_shift
+{
+  my $line = shift;
+  my $index = shift;
+
+  if (($Scale != 1.0) || ($Shift != 0.0))
+  {
+    $line =~ s#<(min|max|last_ds|value|primary_value|secondary_value|v)>\s*([^\s<]+)\s*</[^>]+>#calculate_scale_shift ($2, $1, $Scale, $Shift)#eg;
+  }
+
+  post_line ($line, $index + 1);
+}
+
+#
+# The _output_ handler
+#
+# This filter is unfinished!
+#
+{
+my $fh;
+sub set_output
+{
+       $fh = shift;
+}
+
+{
+my $previous_values;
+my $previous_differences;
+my $pdp_per_row;
+sub handle_line_peak_detect
+{
+  my $line = shift;
+  my $index = shift;
+
+  if (!$previous_values)
+  {
+    $previous_values = [];
+    $previous_differences = [];
+  }
+
+  if ($line =~ m#</database>#i)
+  {
+    $previous_values = [];
+    $previous_differences = [];
+    print STDERR "==============================================================================\n";
+  }
+  elsif ($line =~ m#<pdp_per_row>\s*([1-9][0-9]*)\s*</pdp_per_row>#)
+  {
+    $pdp_per_row = int ($1);
+    print STDERR "pdp_per_row = $pdp_per_row;\n";
+  }
+  elsif ($line =~ m#<row>#)
+  {
+    my @values = ();
+    while ($line =~ m#<v>\s*([^\s>]+)\s*</v>#ig)
+    {
+      if ($1 eq 'NaN')
+      {
+       push (@values, undef);
+      }
+      else
+      {
+       push (@values, 0.0 + $1);
+      }
+    }
+
+    for (my $i = 0; $i < @values; $i++)
+    {
+      if (!defined ($values[$i]))
+      {
+       $previous_values->[$i] = undef;
+      }
+      elsif (!defined ($previous_values->[$i]))
+      {
+       $previous_values->[$i] = $values[$i];
+      }
+      elsif (!defined ($previous_differences->[$i]))
+      {
+       $previous_differences->[$i] = abs ($previous_values->[$i] - $values[$i]);
+      }
+      else
+      {
+       my $divisor = ($previous_differences->[$i] < 1.0) ? 1.0 : $previous_differences->[$i];
+       my $difference = abs ($previous_values->[$i] - $values[$i]);
+       my $change = $pdp_per_row * $difference / $divisor;
+       if (($divisor > 10.0) &&  ($change > 10e5))
+       {
+         print STDERR "i = $i; average difference = " . $previous_differences->[$i]. "; current difference = " . $difference. "; change = $change;\n";
+       }
+       $previous_values->[$i] = $values[$i];
+       $previous_differences->[$i] = (0.95 * $previous_differences->[$i]) + (0.05 * $difference);
+      }
+    }
+  }
+
+  post_line ($line, $index + 1);
+}} # handle_line_peak_detect
+
+sub handle_line_output
+{
+       my $line = shift;
+       my $index = shift;
+
+       if (!defined ($fh))
+       {
+               post_line ($line, $index + 1);
+               return;
+       }
+       
+       print $fh $line;
+}} # handle_line_output
+
+#
+# Dispatching logic
+#
+{
+my @handlers = ();
+sub add_handler
+{
+       my $handler = shift;
+
+       die unless (ref ($handler) eq 'CODE');
+       push (@handlers, $handler);
+} # add_handler
+
+sub post_line
+{
+       my $line = shift;
+       my $index = shift;
+
+       if (0)
+       {
+               my $copy = $line;
+               chomp ($copy);
+               print "DEBUG: post_line ($copy, $index);\n";
+       }
+
+       if ($index > $#handlers)
+       {
+               return;
+       }
+       $handlers[$index]->($line, $index);
+}} # post_line
+
+sub handle_fh
+{
+  my $in_fh = shift;
+  my $out_fh = shift;
+
+  set_output ($out_fh);
+
+  if (@$InDS)
+  {
+    add_handler (\&handle_line_dsmap);
+  }
+
+  if ($Step)
+  {
+    add_handler (\&handle_line_step);
+  }
+
+  if (($Scale != 1.0) || ($Shift != 0.0))
+  {
+    add_handler (\&handle_line_scale_shift);
+  }
+
+  #add_handler (\&handle_line_peak_detect);
+
+  if (@$NewDSes)
+  {
+    add_handler (\&handle_line_add_ds);
+  }
+
+  if (@$NewRRAs)
+  {
+    add_handler (\&handle_line_add_rra);
+  }
+
+  add_handler (\&handle_line_output);
+
+  while (my $line = <$in_fh>)
+  {
+    post_line ($line, 0);
+  }
+} # handle_fh
+
+sub main
+{
+       my $in_file = shift;
+       my $out_file = shift;
+
+       my $in_fh;
+       my $out_fh;
+
+       my $in_needs_close = 1;
+       my $out_needs_close = 1;
+
+       if ($in_file =~ m/\.rrd$/i)
+       {
+               open ($in_fh,  '-|', 'rrdtool', 'dump', $in_file) or die ("open (rrdtool): $!");
+       }
+       elsif ($in_file eq '-')
+       {
+               $in_fh = \*STDIN;
+               $in_needs_close = 0;
+       }
+       else
+       {
+               open ($in_fh, '<', $in_file) or die ("open ($in_file): $!");
+       }
+
+       if ($out_file =~ m/\.rrd$/i)
+       {
+               open ($out_fh, '|-', 'rrdtool', 'restore', '-', $out_file) or die ("open (rrdtool): $!");
+       }
+       elsif ($out_file eq '-')
+       {
+               $out_fh = \*STDOUT;
+               $out_needs_close = 0;
+       }
+       else
+       {
+               open ($out_fh, '>', $out_file) or die ("open ($out_file): $!");
+       }
+
+       handle_fh ($in_fh, $out_fh);
+
+       if ($in_needs_close)
+       {
+               close ($in_fh);
+       }
+       if ($out_needs_close)
+       {
+               close ($out_fh);
+       }
+} # main
+
+=head1 LICENSE
+
+This script is licensed under the GNU general public license, versionE<nbsp>2
+(GPLv2).
+
+=head1 AUTHOR
+
+Florian octo Forster E<lt>octo at verplant.orgE<gt>
+
diff --git a/contrib/sles10.1/collectd.spec b/contrib/sles10.1/collectd.spec
new file mode 100644 (file)
index 0000000..2d558bd
--- /dev/null
@@ -0,0 +1,231 @@
+Summary:       Statistics collection daemon for filling RRD files.
+Name:           collectd
+Version:       3.11.1
+Release:       0.sl10.1
+Source:                http://collectd.org/files/%{name}-%{version}.tar.gz
+Source1:       collectd-init.d
+License:       GPL
+Group:         System Environment/Daemons
+BuildRoot:     %{_tmppath}/%{name}-%{version}-root
+BuildPrereq:   curl-devel, sensors, mysql-devel, rrdtool, libpcap
+Requires:      rrdtool
+Packager:      Florian octo Forster <octo@verplant.org>
+Vendor:                Florian octo Forster <octo@verplant.org>
+
+%description
+collectd is a small daemon written in C for performance.  It reads various
+system  statistics  and updates  RRD files,  creating  them if neccessary.
+Since the daemon doesn't need to startup every time it wants to update the
+files it's very fast and easy on the system. Also, the statistics are very
+fine grained since the files are updated every 10 seconds.
+
+%package apache
+Summary:       apache-plugin for collectd.
+Group:         System Environment/Daemons
+Requires:      collectd = %{version}, curl
+%description apache
+This plugin collects data provided by Apache's `mod_status'.
+
+%package dns
+Summary:       dns-plugin for collectd.
+Group:         System Environment/Daemons
+Requires:      collectd = %{version}, libpcap
+%description dns
+This plugin collects information about DNS traffic, queries and responses.
+
+%package mysql
+Summary:       mysql-module for collectd.
+Group:         System Environment/Daemons
+Requires:      collectd = %{version}, mysql
+%description mysql
+MySQL  querying  plugin.  This plugins  provides data of  issued commands,
+called handlers and database traffic.
+
+%package sensors
+Summary:       libsensors-module for collectd.
+Group:         System Environment/Daemons
+Requires:      collectd = %{version}, sensors
+%description sensors
+This  plugin  for  collectd  provides  querying  of sensors  supported  by
+lm_sensors.
+
+%prep
+rm -rf $RPM_BUILD_ROOT
+%setup
+
+%build
+./configure --prefix=%{_prefix} --sbindir=%{_sbindir} --mandir=%{_mandir} --libdir=%{_libdir} --sysconfdir=%{_sysconfdir} --localstatedir=%{_localstatedir}
+make
+
+%install
+make install DESTDIR=$RPM_BUILD_ROOT
+cp src/collectd.conf $RPM_BUILD_ROOT/etc/collectd.conf
+mkdir -p $RPM_BUILD_ROOT/var/lib/collectd
+rm -f $RPM_BUILD_ROOT%{_libdir}/%{name}/*.a
+rm -f $RPM_BUILD_ROOT%{_libdir}/%{name}/*.la
+mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/init.d
+cp %{SOURCE1} $RPM_BUILD_ROOT%{_sysconfdir}/init.d/collectd
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%post
+chkconfig collectd on
+/etc/init.d/collectd start
+
+%preun
+/etc/init.d/collectd stop
+chkconfig collectd off
+
+%files
+%defattr(-,root,root)
+%doc AUTHORS COPYING ChangeLog INSTALL NEWS README
+%doc contrib
+%config /etc/collectd.conf
+%attr(0755,root,root) /etc/init.d/collectd
+%attr(0755,root,root) %{_sbindir}/collectd
+%attr(0444,root,root) %{_mandir}/man1/*
+%attr(0444,root,root) %{_mandir}/man5/*
+%attr(0444,root,root) %{_libdir}/%{name}/apcups.so
+%attr(0444,root,root) %{_libdir}/%{name}/apple_sensors.so
+%attr(0444,root,root) %{_libdir}/%{name}/battery.so
+%attr(0444,root,root) %{_libdir}/%{name}/cpu.so
+%attr(0444,root,root) %{_libdir}/%{name}/cpufreq.so
+%attr(0444,root,root) %{_libdir}/%{name}/df.so
+%attr(0444,root,root) %{_libdir}/%{name}/disk.so
+%attr(0444,root,root) %{_libdir}/%{name}/email.so
+%attr(0444,root,root) %{_libdir}/%{name}/hddtemp.so
+%attr(0444,root,root) %{_libdir}/%{name}/irq.so
+%attr(0444,root,root) %{_libdir}/%{name}/load.so
+%attr(0444,root,root) %{_libdir}/%{name}/mbmon.so
+%attr(0444,root,root) %{_libdir}/%{name}/memory.so
+%attr(0444,root,root) %{_libdir}/%{name}/multimeter.so
+%attr(0444,root,root) %{_libdir}/%{name}/nfs.so
+%attr(0444,root,root) %{_libdir}/%{name}/ntpd.so
+%attr(0444,root,root) %{_libdir}/%{name}/ping.so
+%attr(0444,root,root) %{_libdir}/%{name}/processes.so
+%attr(0444,root,root) %{_libdir}/%{name}/serial.so
+%attr(0444,root,root) %{_libdir}/%{name}/swap.so
+%attr(0444,root,root) %{_libdir}/%{name}/tape.so
+%attr(0444,root,root) %{_libdir}/%{name}/traffic.so
+%attr(0444,root,root) %{_libdir}/%{name}/users.so
+%attr(0444,root,root) %{_libdir}/%{name}/vserver.so
+%attr(0444,root,root) %{_libdir}/%{name}/wireless.so
+
+%dir /var/lib/collectd
+
+%files apache
+%attr(0444,root,root) %{_libdir}/%{name}/apache.so
+
+%files dns
+%attr(0444,root,root) %{_libdir}/%{name}/dns.so
+
+%files mysql
+%attr(0444,root,root) %{_libdir}/%{name}/mysql.so
+
+%files sensors
+%attr(0444,root,root) %{_libdir}/%{name}/sensors.so
+
+%changelog
+* Sun Jul 09 2006 Florian octo Forster <octo@verplant.org> 3.10.0-1
+- New upstream version
+
+* Tue Jun 25 2006 Florian octo Forster <octo@verplant.org> 3.9.4-1
+- New upstream version
+
+* Tue Jun 01 2006 Florian octo Forster <octo@verplant.org> 3.9.3-1
+- New upstream version
+
+* Tue May 09 2006 Florian octo Forster <octo@verplant.org> 3.9.2-1
+- New upstream version
+
+* Tue May 09 2006 Florian octo Forster <octo@verplant.org> 3.8.5-1
+- New upstream version
+
+* Fri Apr 21 2006 Florian octo Forster <octo@verplant.org> 3.9.1-1
+- New upstream version
+
+* Fri Apr 14 2006 Florian octo Forster <octo@verplant.org> 3.9.0-1
+- New upstream version
+- Added the `apache' package.
+
+* Thu Mar 14 2006 Florian octo Forster <octo@verplant.org> 3.8.2-1
+- New upstream version
+
+* Thu Mar 13 2006 Florian octo Forster <octo@verplant.org> 3.8.1-1
+- New upstream version
+
+* Thu Mar 09 2006 Florian octo Forster <octo@verplant.org> 3.8.0-1
+- New upstream version
+
+* Sat Feb 18 2006 Florian octo Forster <octo@verplant.org> 3.7.2-1
+- Include `tape.so' so the build doesn't terminate because of missing files..
+- New upstream version
+
+* Sat Feb 04 2006 Florian octo Forster <octo@verplant.org> 3.7.1-1
+- New upstream version
+
+* Mon Jan 30 2006 Florian octo Forster <octo@verplant.org> 3.7.0-1
+- New upstream version
+- Removed the extra `hddtemp' package
+
+* Tue Jan 24 2006 Florian octo Forster <octo@verplant.org> 3.6.2-1
+- New upstream version
+
+* Fri Jan 20 2006 Florian octo Forster <octo@verplant.org> 3.6.1-1
+- New upstream version
+
+* Fri Jan 20 2006 Florian octo Forster <octo@verplant.org> 3.6.0-1
+- New upstream version
+- Added config file, `collectd.conf(5)', `df.so'
+- Added package `collectd-mysql', dependency on `mysqlclient10 | mysql'
+
+* Wed Dec 07 2005 Florian octo Forster <octo@verplant.org> 3.5.0-1
+- New upstream version
+
+* Sat Nov 26 2005 Florian octo Forster <octo@verplant.org> 3.4.0-1
+- New upstream version
+
+* Sat Nov 05 2005 Florian octo Forster <octo@verplant.org> 3.3.0-1
+- New upstream version
+
+* Tue Oct 26 2005 Florian octo Forster <octo@verplant.org> 3.2.0-1
+- New upstream version
+- Added statement to remove the `*.la' files. This fixes a problem when
+  `Unpackaged files terminate build' is in effect.
+- Added `processes.so*' to the main package
+
+* Fri Oct 14 2005 Florian octo Forster <octo@verplant.org> 3.1.0-1
+- New upstream version
+- Added package `collectd-hddtemp'
+
+* Fri Sep 30 2005 Florian octo Forster <octo@verplant.org> 3.0.0-1
+- New upstream version
+- Split the package into `collectd' and `collectd-sensors'
+
+* Fri Sep 16 2005 Florian octo Forster <octo@verplant.org> 2.1.0-1
+- New upstream version
+
+* Mon Sep 10 2005 Florian octo Forster <octo@verplant.org> 2.0.0-1
+- New upstream version
+
+* Mon Aug 29 2005 Florian octo Forster <octo@verplant.org> 1.8.0-1
+- New upstream version
+
+* Sun Aug 25 2005 Florian octo Forster <octo@verplant.org> 1.7.0-1
+- New upstream version
+
+* Sun Aug 21 2005 Florian octo Forster <octo@verplant.org> 1.6.0-1
+- New upstream version
+
+* Sun Jul 17 2005 Florian octo Forster <octo@verplant.org> 1.5.1-1
+- New upstream version
+
+* Sun Jul 17 2005 Florian octo Forster <octo@verplant.org> 1.5-1
+- New upstream version
+
+* Mon Jul 11 2005 Florian octo Forster <octo@verplant.org> 1.4.2-1
+- New upstream version
+
+* Sat Jul 09 2005 Florian octo Forster <octo@verplant.org> 1.4-1
+- Built on RedHat 7.3
diff --git a/contrib/sles10.1/init.d-collectd b/contrib/sles10.1/init.d-collectd
new file mode 100755 (executable)
index 0000000..edc415e
--- /dev/null
@@ -0,0 +1,75 @@
+#!/bin/bash
+
+### BEGIN INIT INFO
+# Provides:                    collectd
+# Required-Start:              $local_fs $remote_fs $network 
+# X-UnitedLinux-Should-Start:  $named $time apache mysql
+# Required-Stop:               $local_fs $remote_fs $network
+# X-UnitedLinux-Should-Stop:   
+# Default-Start:               3 5
+# Default-Stop:                        0 1 2 6
+# Short-Description:           Statistics daemon collectd
+# Description:                 Start the statistics daemon collectd
+### END INIT INFO
+
+
+#
+# load the configuration
+#
+test -s /etc/rc.status && . /etc/rc.status && rc_reset
+
+RETVAL=0
+ARGS=""
+prog="collectd"
+CONFIG=/etc/collectd.conf
+
+if [ -r /etc/default/$prog ]; then
+       . /etc/default/$prog
+fi
+
+start () {
+       echo -n $"Starting $prog: "
+       RETVAL=1
+       if [ -r "$CONFIG" ]
+       then
+               eval startproc /usr/sbin/collectd -C "$CONFIG"
+               RETVAL=$?
+               [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$prog
+       fi
+       rc_failed $RETVAL
+       rc_status -v
+}
+stop () {
+       echo -n $"Stopping $prog: "
+       killproc $prog
+       RETVAL=$?
+       rc_failed $RETVAL
+       rc_status -v
+       [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$prog
+}
+# See how we were called.
+case "$1" in
+  start)
+       start
+       ;;
+  stop)
+       stop
+       ;;
+  status)
+       status $prog
+       ;;
+  restart|reload)
+       stop
+       sleep 1
+       start
+       ;;
+  condrestart)
+       [ -f /var/lock/subsys/$prog ] && restart || :
+       ;;
+  *)
+       echo $"Usage: $0 {start|stop|status|restart|reload|condrestart}"
+       exit 1
+esac
+
+rc_exit
+# vim:syntax=sh
diff --git a/contrib/snmp-data.conf b/contrib/snmp-data.conf
new file mode 100644 (file)
index 0000000..07381db
--- /dev/null
@@ -0,0 +1,524 @@
+<Plugin snmp>
+    #
+    # IF-MIB
+    # Interface statistics using the IF-MIB
+    #
+    <Data "ifmib_if_octets32">
+       Type "if_octets"
+       Table true
+       Instance "IF-MIB::ifDescr"
+       Values "IF-MIB::ifInOctets" "IF-MIB::ifOutOctets"
+    </Data>
+    <Data "ifmib_if_octets64">
+       Type "if_octets"
+       Table true
+       Instance "IF-MIB::ifName"
+       Values "IF-MIB::ifHCInOctets" "IF-MIB::ifHCOutOctets"
+    </Data>
+    <Data "ifmib_if_packets32">
+       Type "if_packets"
+       Table true
+       Instance "IF-MIB::ifDescr"
+       Values "IF-MIB::ifInUcastPkts" "IF-MIB::ifOutUcastPkts"
+    </Data>
+    <Data "ifmib_if_packets64">
+       Type "if_packets"
+       Table true
+       Instance "IF-MIB::ifName"
+       Values "IF-MIB::ifHCInUcastPkts" "IF-MIB::ifHCOutUcastPkts"
+    </Data>
+    <Data "ifmib_if_errors32">
+       Type "if_errors"
+       Table true
+       Instance "IF-MIB::ifDescr"
+       Values "IF-MIB::ifInErrors" "IF-MIB::ifOutErrors"
+    </Data>
+    <Data "ifmib_if_errors64">
+       Type "if_errors"
+       Table true
+       Instance "IF-MIB::ifName"
+       Values "IF-MIB::ifHCInErrors" "IF-MIB::ifHCOutErrors"
+    </Data>
+
+    #
+    # UPS-MIB
+    # Statistics about your UPS using the UPS-MIB from the RFC1628.
+    #
+    # Battery branch
+    <Data "upsmib_timeleft_battery">
+       Type "timeleft"
+       Table false
+       Instance "battery"
+       Values ".1.3.6.1.2.1.33.1.2.3.0"
+    </Data>
+    <Data "upsmib_charge_battery">
+       Type "percent"
+       Table false
+       Instance "charge-battery"
+       Values ".1.3.6.1.2.1.33.1.2.4.0"
+    </Data>
+    <Data "upsmib_voltage_battery">
+       Type "voltage"
+       Table false
+       Instance "battery"
+       Values ".1.3.6.1.2.1.33.1.2.5.0"
+       Scale 0.1
+    </Data>
+    <Data "upsmib_current_battery">
+       Type "current"
+       Table false
+       Instance "battery"
+       Values ".1.3.6.1.2.1.33.1.2.6.0"
+       Scale 0.1
+    </Data>
+    <Data "upsmib_temperature_battery">
+       Type "temperature"
+       Table false
+       Instance "battery"
+       Values ".1.3.6.1.2.1.33.1.2.7.0"
+    </Data>
+    # Input branch
+    <Data "upsmib_frequency_input">
+       Type "frequency"
+       Table true
+       InstancePrefix "input"
+       Values ".1.3.6.1.2.1.33.1.3.3.1.2"
+       Scale 0.1
+    </Data>
+    <Data "upsmib_voltage_input">
+       Type "voltage"
+       Table true
+       InstancePrefix "input"
+       Values ".1.3.6.1.2.1.33.1.3.3.1.3"
+    </Data>
+    <Data "upsmib_current_input">
+       Type "current"
+       Table true
+       InstancePrefix "input"
+       Values ".1.3.6.1.2.1.33.1.3.3.1.4"
+       Scale 0.1
+    </Data>
+    <Data "upsmib_power_input">
+       Type "power"
+       Table true
+       InstancePrefix "input"
+       Values ".1.3.6.1.2.1.33.1.3.3.1.5"
+    </Data>
+    # Output branch
+    <Data "upsmib_frequency_output">
+       Type "frequency"
+       Table false
+       Instance "output"
+       Values ".1.3.6.1.2.1.33.1.4.2.0"
+       Scale 0.1
+    </Data>
+    <Data "upsmib_voltage_output">
+       Type "voltage"
+       Table true
+       InstancePrefix "output"
+       Values ".1.3.6.1.2.1.33.1.4.4.1.2"
+    </Data>
+    <Data "upsmib_current_output">
+       Type "current"
+       Table true
+       InstancePrefix "output"
+       Values ".1.3.6.1.2.1.33.1.4.4.1.3"
+       Scale 0.1
+    </Data>
+    <Data "upsmib_power_output">
+       Type "power"
+       Table true
+       InstancePrefix "output"
+       Values ".1.3.6.1.2.1.33.1.4.4.1.4"
+    </Data>
+    <Data "upsmib_load_output">
+       Type "percent"
+       Table true
+       InstancePrefix "load-output"
+       Values ".1.3.6.1.2.1.33.1.4.4.1.5"
+    </Data>
+    # Bypass branch
+    <Data "upsmib_frequency_bypass">
+       Type "frequency"
+       Table false
+       Instance "output"
+       Values ".1.3.6.1.2.1.33.1.5.1.0"
+       Scale 0.1
+    </Data>
+    <Data "upsmib_voltage_bypass">
+       Type "voltage"
+       Table true
+       InstancePrefix "bypass"
+       Values ".1.3.6.1.2.1.33.1.5.3.1.2"
+    </Data>
+    <Data "upsmib_current_bypass">
+       Type "current"
+       Table true
+       InstancePrefix "bypass"
+       Values ".1.3.6.1.2.1.33.1.5.3.1.3"
+       Scale 0.1
+    </Data>
+    <Data "upsmib_power_bypass">
+       Type "power"
+       Table true
+       InstancePrefix "bypass"
+       Values ".1.3.6.1.2.1.33.1.5.3.1.4"
+    </Data>
+    # Special definitions for broken UPSes
+    <Data "upsmib_voltage_battery_unscaled">
+       Type "voltage"
+       Table false
+       Instance "battery"
+       Values ".1.3.6.1.2.1.33.1.2.5.0"
+    </Data>
+    <Data "upsmib_current_input_unscaled">
+       Type "current"
+       Table true
+       InstancePrefix "input"
+       Values ".1.3.6.1.2.1.33.1.3.3.1.4"
+    </Data>
+    <Data "upsmib_current_output_unscaled">
+       Type "current"
+       Table true
+       InstancePrefix "output"
+       Values ".1.3.6.1.2.1.33.1.4.4.1.3"
+    </Data>
+
+    #
+    # Riello UPS
+    # Temperatures for UPSes by Riello, <http://www.riello-ups.de/>
+    #
+    <Data "riello_temperature_system">
+       Type "temperature"
+       Table false
+       Instance "system"
+       Values ".1.3.6.1.4.1.5491.1.51.1.5.4.0"
+    </Data>
+    <Data "riello_temperature_rectifier">
+       Type "temperature"
+       Table false
+       Instance "rectifier"
+       Values ".1.3.6.1.4.1.5491.1.51.1.5.5.0"
+    </Data>
+    <Data "riello_temperature_inverter">
+       Type "temperature"
+       Table false
+       Instance "inverter"
+       Values ".1.3.6.1.4.1.5491.1.51.1.5.6.0"
+    </Data>
+
+    #
+    # PowerPlus UPS, manufactured by Gamatronic, <http://www.gamatronic.com/>,
+    # distributed in Germany by AdPoS, <http://adpos-usv.de/>
+    #
+    # Global inputs
+    <Data "powerplus_voltage_input">
+       Type "voltage"
+       Table true
+       InstancePrefix "input"
+       Values ".1.3.6.1.4.1.6050.5.4.1.1.2"
+    </Data>
+    <Data "powerplus_current_input">
+       Type "current"
+       Table true
+       InstancePrefix "input"
+       Values ".1.3.6.1.4.1.6050.5.4.1.1.3"
+    </Data>
+    <Data "powerplus_power_apparent_input">
+       Type "power"
+       Table true
+       InstancePrefix "apparent-input"
+       Values ".1.3.6.1.4.1.6050.5.4.1.1.4"
+       Scale 100.0
+    </Data>
+    <Data "powerplus_power_active_input">
+       Type "power"
+       Table true
+       InstancePrefix "active-input"
+       Values ".1.3.6.1.4.1.6050.5.4.1.1.5"
+       Scale 100.0
+    </Data>
+    <Data "powerplus_performance_factor_input">
+       Type "percent"
+       Table true
+       InstancePrefix "performance_factor-input"
+       Values ".1.3.6.1.4.1.6050.5.4.1.1.6"
+    </Data>
+    # Global outputs
+    <Data "powerplus_voltage_output">
+       Type "voltage"
+       Table true
+       InstancePrefix "output"
+       Values ".1.3.6.1.4.1.6050.5.5.1.1.2"
+    </Data>
+    <Data "powerplus_current_output">
+       Type "current"
+       Table true
+       InstancePrefix "output"
+       Values ".1.3.6.1.4.1.6050.5.5.1.1.3"
+    </Data>
+    <Data "powerplus_power_apparent_output">
+       Type "power"
+       Table true
+       InstancePrefix "apparent-output"
+       Values ".1.3.6.1.4.1.6050.5.5.1.1.4"
+       Scale 100.0
+    </Data>
+    <Data "powerplus_power_active_output">
+       Type "power"
+       Table true
+       InstancePrefix "active-output"
+       Values ".1.3.6.1.4.1.6050.5.5.1.1.5"
+       Scale 100.0
+    </Data>
+    <Data "powerplus_load_level_output">
+       Type "percent"
+       Table true
+       InstancePrefix "load_level-output"
+       Values ".1.3.6.1.4.1.6050.5.5.1.1.6"
+    </Data>
+    <Data "powerplus_active_load_level_output">
+       Type "percent"
+       Table true
+       InstancePrefix "active_load_level-output"
+       Values ".1.3.6.1.4.1.6050.5.5.1.1.7"
+    </Data>
+    <Data "powerplus_performance_factor_output">
+       Type "percent"
+       Table true
+       InstancePrefix "performance_factor-output"
+       Values ".1.3.6.1.4.1.6050.5.5.1.1.8"
+    </Data>
+    # Global DC
+    <Data "powerplus_global_dc_positive">
+       Type "voltage"
+       Table false
+       Instance "dc_positive-global"
+       Values ".1.3.6.1.4.1.6050.5.6.1.0"
+    </Data>
+    <Data "powerplus_global_dc_negative">
+       Type "voltage"
+       Table false
+       Instance "dc_negative-global"
+       Values ".1.3.6.1.4.1.6050.5.6.2.0"
+    </Data>
+    <Data "powerplus_global_dc_total">
+       Type "voltage"
+       Table false
+       Instance "dc_total-global"
+       Values ".1.3.6.1.4.1.6050.5.6.3.0"
+    </Data>
+
+    #
+    # NetApp
+    # Some simple statistics of storage systems by NetApp.
+    #
+    <Data "netapp_cpu_system">
+       Type "cpu"
+       Table false
+       Instance "system"
+       Values ".1.3.6.1.4.1.789.1.2.1.2.0"
+    </Data>
+    <Data "netapp_cpu_idle">
+       Type "cpu"
+       Table false
+       Instance "idle"
+       Values ".1.3.6.1.4.1.789.1.2.1.4.0"
+    </Data>
+    <Data "netapp_if_octets">
+       Type "if_octets"
+       Table false
+       Instance "net"
+       Values ".1.3.6.1.4.1.789.1.2.2.12.0" ".1.3.6.1.4.1.789.1.2.2.14.0"
+    </Data>
+
+    #
+    # Juniper SSL
+    # Some stats of an SSL-appliance by Juniper.
+    #
+    <Data "juniperssl_users_web">
+       Type "users"
+       Table false
+       Instance "web"
+       Values ".1.3.6.1.4.1.12532.2.0"
+    </Data>
+    <Data "juniperssl_users_mail">
+       Type "users"
+       Table false
+       Instance "mail"
+       Values ".1.3.6.1.4.1.12532.3.0"
+    </Data>
+    <Data "juniperssl_percent_logfull">
+       Type "percent"
+       Table false
+       Instance "logfull"
+       Values ".1.3.6.1.4.1.12532.1.0"
+    </Data>
+    <Data "juniperssl_percent_diskfull">
+       Type "percent"
+       Table false
+       Instance "diskfull"
+       Values ".1.3.6.1.4.1.12532.25.0"
+    </Data>
+
+
+    #
+    # WuT
+    # Some thermometers and digital IO devices from WuT
+    # <http://www.wut.de/>
+    #
+    <Data "wut_an8graph">
+       Type "temperature"
+       Table true
+       Instance ".1.3.6.1.4.1.5040.1.2.6.3.2.1.1.2"
+       Values ".1.3.6.1.4.1.5040.1.2.6.1.4.1.1"
+       Scale 0.1
+    </Data>
+    <Data "wut_an2graph">
+       Type "temperature"
+       Table true
+       Instance ".1.3.6.1.4.1.5040.1.2.7.3.2.1.1.2"
+       Values ".1.3.6.1.4.1.5040.1.2.7.1.4.1.1"
+       Scale 0.1
+    </Data>
+    <Data "wut_an1graph">
+       Type "temperature"
+       Table true
+       Instance ".1.3.6.1.4.1.5040.1.2.8.3.2.1.1.2"
+       Values ".1.3.6.1.4.1.5040.1.2.8.1.4.1.1"
+       Scale 0.1
+    </Data>
+    <Data "wut_thermo8">
+       Type "temperature"
+       Table true
+       Instance ".1.3.6.1.4.1.5040.1.2.1.3.2.1.1.2"
+       Values ".1.3.6.1.4.1.5040.1.2.1.1.4.1.1"
+       Scale 0.1
+    </Data>
+    <Data "wut_thermo2">
+       Type "temperature"
+       Table true
+       Instance ".1.3.6.1.4.1.5040.1.2.2.3.2.1.1.2"
+       Values ".1.3.6.1.4.1.5040.1.2.2.1.4.1.1"
+       Scale 0.1
+    </Data>
+    <Data "wut_thermo1">
+       Type "temperature"
+       Table true
+       Instance ".1.3.6.1.4.1.5040.1.2.3.3.2.1.1.2"
+       Values ".1.3.6.1.4.1.5040.1.2.3.1.4.1.1"
+       Scale 0.1
+    </Data>
+
+    #
+    # Infratec
+    # Rack monitoring devices by Infratec, <http://www.infratec-ag.de/>
+    #
+    # Model H2-17
+    <Data "infratec_h2_17_temperature">
+       Type "temperature"
+       Table true
+       Instance ".1.3.6.1.4.1.4519.10.4.1.1.2"
+       Values ".1.3.6.1.4.1.4519.10.4.1.1.3"
+    </Data>
+    <Data "infratec_h2_17_humidity">
+       Type "humidity"
+       Table true
+       Instance ".1.3.6.1.4.1.4519.10.5.1.1.2"
+       Values ".1.3.6.1.4.1.4519.10.5.1.1.3"
+    </Data>
+    <Data "infratec_h2_17_voltage">
+       Type "voltage"
+       Table true
+       InstancePrefix "input"
+       Values ".1.3.6.1.4.1.4519.10.6.1.1.3"
+    </Data>
+    # Model H2-30
+    <Data "infratec_h2_30_temperature">
+       Type "temperature"
+       Table true
+       Instance ".1.3.6.1.4.1.1909.10.4.1.1.2"
+       Values ".1.3.6.1.4.1.1909.10.4.1.1.3"
+    </Data>
+    <Data "infratec_h2_30_humidity">
+       Type "humidity"
+       Table true
+       Instance ".1.3.6.1.4.1.1909.10.5.1.1.2"
+       Values ".1.3.6.1.4.1.1909.10.5.1.1.3"
+    </Data>
+    <Data "infratec_h2_30_voltage">
+       Type "voltage"
+       Table true
+       InstancePrefix "input"
+       Values ".1.3.6.1.4.1.1909.10.6.1.1.3"
+    </Data>
+
+    #
+    # Mikrotik RouterBoards
+    #
+    # Wireless statistics: station mode
+    <Data "mikrotik_wl_sta_bitrate_tx">
+        tYPE "Bitrate"
+        Table true
+        InstancePrefix "st-tx-"
+        Instance ".1.3.6.1.4.1.14988.1.1.1.1.1.5"
+        Values ".1.3.6.1.4.1.14988.1.1.1.1.1.2"
+    </Data>
+
+    <Data "mikrotik_wl_sta_bitrate_rx">
+        Type "bitrate"
+        Table true
+        InstancePrefix "st-rx-"
+        Instance ".1.3.6.1.4.1.14988.1.1.1.1.1.5"
+        Values ".1.3.6.1.4.1.14988.1.1.1.1.1.3"
+    </Data>
+
+    <Data "mikrotik_wl_sta_signal">
+        Type "signal_power"
+        Table true
+        InstancePrefix "st-"
+        Instance ".1.3.6.1.4.1.14988.1.1.1.1.1.5"
+        Values ".1.3.6.1.4.1.14988.1.1.1.1.1.4"
+    </Data>
+
+    # Wireless statistics: AP mode / registration table
+    <Data "mikrotik_wl_reg_signal">
+        Type "signal_power"
+        Table true
+        InstancePrefix "ap-"
+        Instance ".1.3.6.1.4.1.14988.1.1.1.2.1.1"
+        Values ".1.3.6.1.4.1.14988.1.1.1.2.1.3"
+    </Data>
+
+    <Data "mikrotik_wl_reg_octets">
+        Type "if_octets"
+        Table true
+        InstancePrefix "ap-"
+        Instance ".1.3.6.1.4.1.14988.1.1.1.2.1.1"
+        Values ".1.3.6.1.4.1.14988.1.1.1.2.1.5" ".1.3.6.1.4.1.14988.1.1.1.2.1.4"
+    </Data>
+
+    <Data "mikrotik_wl_reg_packets">
+        Type "if_packets"
+        Table true
+        InstancePrefix "ap-"
+        Instance ".1.3.6.1.4.1.14988.1.1.1.2.1.1"
+        Values ".1.3.6.1.4.1.14988.1.1.1.2.1.7" ".1.3.6.1.4.1.14988.1.1.1.2.1.6"
+    </Data>
+
+    <Data "mikrotik_wl_reg_bitrate_tx">
+        Type "bitrate"
+        Table true
+        InstancePrefix "ap-tx-"
+        Instance ".1.3.6.1.4.1.14988.1.1.1.2.1.1"
+        Values ".1.3.6.1.4.1.14988.1.1.1.2.1.8"
+    </Data>
+
+    <Data "mikrotik_wl_reg_bitrate_rx">
+        Type "bitrate"
+        Table true
+        InstancePrefix "ap-rx-"
+        Instance ".1.3.6.1.4.1.14988.1.1.1.2.1.1"
+        Values ".1.3.6.1.4.1.14988.1.1.1.2.1.9"
+    </Data>
+</Plugin>
diff --git a/contrib/snmp-probe-host.px b/contrib/snmp-probe-host.px
new file mode 100755 (executable)
index 0000000..d1a7a88
--- /dev/null
@@ -0,0 +1,439 @@
+#!/usr/bin/perl
+#
+# collectd - snmp-probe-host.px
+# Copyright (C) 2008,2009  Florian octo Forster
+# Copyright (C) 2009       noris network AG
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; only version 2 of the License is applicable.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+#
+# Author:
+#   Florian octo Forster <octo at noris.net>
+#
+
+use strict;
+use warnings;
+use SNMP;
+use Config::General ('ParseConfig');
+use Getopt::Long ('GetOptions');
+use Socket6;
+
+our %ExcludeOptions =
+(
+  'IF-MIB64' => qr/^\.?1\.3\.6\.1\.2\.1\.31/,
+  'IF-MIB32' => qr/^\.?1\.3\.6\.1\.2\.1\.2/
+);
+
+sub get_config
+{
+  my %conf;
+  my $file = shift;
+
+  %conf = ParseConfig (-ConfigFile => $file,
+    -LowerCaseNames => 1,
+    -UseApacheInclude => 1,
+    -IncludeDirectories => 1,
+    ($Config::General::VERSION >= 2.38) ? (-IncludeAgain => 0) : (),
+    -MergeDuplicateBlocks => 1,
+    -CComments => 0);
+  if (!%conf)
+  {
+    return;
+  }
+  return (\%conf);
+} # get_config
+
+sub probe_one
+{
+  my $sess = shift;
+  my $conf = shift;
+  my $excludes = @_ ? shift : [];
+  my @oids;
+  my $cmd = 'GET';
+  my $vl;
+
+  if (!$conf->{'table'} || !$conf->{'values'})
+  {
+    warn "No 'table' or 'values' setting";
+    return;
+  }
+
+  @oids = split (/"\s*"/, $conf->{'values'});
+  if ($conf->{'table'} =~ m/^(true|yes|on)$/i)
+  {
+    $cmd = 'GETNEXT';
+    if (defined ($conf->{'instance'}))
+    {
+      push (@oids, $conf->{'instance'});
+    }
+  }
+
+  require Data::Dumper;
+
+  #print "probe_one: \@oids = (" . join (', ', @oids) . ");\n";
+  for (@oids)
+  {
+    my $oid_orig = $_;
+    my $vb;
+    my $status;
+
+    if ($oid_orig =~ m/[^0-9\.]/)
+    {
+      my $tmp = SNMP::translateObj ($oid_orig);
+      if (!defined ($tmp))
+      {
+        warn ("Cannot translate OID $oid_orig");
+        return;
+      }
+      $oid_orig = $tmp;
+    }
+
+    for (@$excludes)
+    {
+      if ($oid_orig =~ $_)
+      {
+        return;
+      }
+    }
+
+    $vb = SNMP::Varbind->new ([$oid_orig]);
+
+    if ($cmd eq 'GET')
+    {
+      $status = $sess->get ($vb);
+      if ($sess->{'ErrorNum'} != 0)
+      {
+        return;
+      }
+      if (!defined ($status))
+      {
+        return;
+      }
+      if ("$status" eq 'NOSUCHOBJECT')
+      {
+        return;
+      }
+    }
+    else
+    {
+      my $oid_copy;
+
+      $status = $sess->getnext ($vb);
+      if ($sess->{'ErrorNum'} != 0)
+      {
+        return;
+      }
+
+      $oid_copy = $vb->[0];
+      if ($oid_copy =~ m/[^0-9\.]/)
+      {
+        my $tmp = SNMP::translateObj ($oid_copy);
+        if (!defined ($tmp))
+        {
+          warn ("Cannot translate OID $oid_copy");
+          return;
+        }
+        $oid_copy = $tmp;
+      }
+
+      #print "$oid_orig > $oid_copy ?\n";
+      if (substr ($oid_copy, 0, length ($oid_orig)) ne $oid_orig)
+      {
+        return;
+      }
+    }
+
+    #print STDOUT Data::Dumper->Dump ([$oid_orig, $status], [qw(oid_orig status)]);
+  } # for (@oids)
+
+  return (1);
+} # probe_one
+
+sub probe_all
+{
+  my $host = shift;
+  my $community = shift;
+  my $data = shift;
+  my $excludes = @_ ? shift : [];
+  my $version = 2;
+  my @valid_data = ();
+  my $begin;
+  my $address;
+
+  {
+    my @status;
+
+    @status = getaddrinfo ($host, 'snmp');
+    while (@status >= 5)
+    {
+      my $family    = shift (@status);
+      my $socktype  = shift (@status);
+      my $proto     = shift (@status);
+      my $saddr     = shift (@status);
+      my $canonname = shift (@status);
+      my $host;
+      my $port;
+
+      ($host, $port) = getnameinfo ($saddr, NI_NUMERICHOST);
+      if (defined ($port))
+      {
+        $address = $host;
+      }
+      else
+      {
+        warn ("getnameinfo failed: $host");
+      }
+    }
+  }
+  if (!$address)
+  {
+    return;
+  }
+
+  while ($version > 0)
+  {
+    my $sess;
+
+    $sess = new SNMP::Session (DestHost => $host,
+      Community => $community,
+      Version => $version,
+      Timeout => 1000000,
+      UseNumeric => 1);
+    if (!$sess)
+    {
+      $version--;
+      next;
+    }
+
+    $begin = time ();
+
+    for (keys %$data)
+    {
+      my $name = $_;
+      if (probe_one ($sess, $data->{$name}, $excludes))
+      {
+        push (@valid_data, $name);
+      }
+
+      if ((@valid_data == 0) && ((time () - $begin) > 10))
+      {
+        # break for loop
+        last;
+      }
+    }
+
+    if (@valid_data)
+    {
+      # break while loop
+      last;
+    }
+
+    $version--;
+  } # while ($version > 0)
+
+  print <<EOF;
+  <Host "$host">
+    Address "$address"
+    Version $version
+    Community "$community"
+EOF
+  for (sort (@valid_data))
+  {
+    print "    Collect \"$_\"\n";
+  }
+  if (!@valid_data)
+  {
+    print <<EOF;
+# WARNING: Autoconfiguration failed.
+# TODO: Add one or more `Collect' statements here:
+#   Collect "foo"
+EOF
+  }
+  print <<EOF;
+    Interval 60
+  </Host>
+EOF
+} # probe_all
+
+sub exit_usage
+{
+  print <<USAGE;
+Usage: snmp-probe-host.px --host <host> [options]
+
+Options are:
+  -H | --host          Hostname of the device to probe.
+  -C | --config        Path to config file holding the SNMP data blocks.
+  -c | --community     SNMP community to use. Default: `public'.
+  -h | --help          Print this information and exit.
+  -x | --exclude       Exclude a specific MIB. Call with "help" for more
+                       information.
+
+USAGE
+  exit (1);
+}
+
+sub exit_usage_exclude
+{
+  print "Available exclude MIBs:\n\n";
+  for (sort (keys %ExcludeOptions))
+  {
+    print "  $_\n";
+  }
+  print "\n";
+  exit (1);
+}
+
+=head1 NAME
+
+snmp-probe-host.px - Find out what information an SNMP device provides.
+
+=head1 SYNOPSIS
+
+  ./snmp-probe-host.px --host switch01.mycompany.com --community ei2Acoum
+
+=head1 DESCRIPTION
+
+The C<snmp-probe-host.px> script can be used to automatically generate SNMP
+configuration snippets for collectd's snmp plugin (see L<collectd-snmp(5)>).
+
+This script parses the collectd configuration and detecs all "data" blocks that
+are defined for the SNMP plugin. It then queries the device specified on the
+command line for all OIDs and registeres which OIDs could be answered correctly
+and which resulted in an error. With that information the script figures out
+which "data" blocks can be used with this hosts and prints an appropriate
+"host" block to standard output.
+
+The script first tries to contact the device via SNMPv2. If after ten seconds
+no working "data" block has been found, it will try to downgrade to SNMPv1.
+This is a bit a hack, but works for now.
+
+=cut
+
+my $host;
+my $file = '/etc/collectd/collectd.conf';
+my $community = 'public';
+my $conf;
+my $working_data;
+my @excludes = ();
+
+=head1 OPTIONS
+
+The following command line options are accepted:
+
+=over 4
+
+=item B<--host> I<hostname>
+
+Hostname of the device. This B<should> be a fully qualified domain name (FQDN),
+but anything the system can resolve to an IP address will word. B<Required
+argument>.
+
+=item B<--config> I<config file>
+
+Sets the name of the collectd config file which defined the SNMP "data" blocks.
+Due to limitations of the config parser used in this script
+(C<Config::General>), C<Include> statements cannot be parsed correctly.
+Defaults to F</etc/collectd/collectd.conf>.
+
+=item B<--community> I<community>
+
+SNMP community to use. Should be pretty straight forward.
+
+=item B<--exclude> I<MIB>
+
+This option can be used to exclude specific data from being enabled in the
+generated config. Currently the following MIBs are understood:
+
+=over 4
+
+=item B<IF-MIB>
+
+Exclude interface information, such as I<ifOctets> and I<ifPackets>.
+
+=back
+
+=back
+
+=cut
+
+GetOptions ('H|host|hostname=s' => \$host,
+  'C|conf|config=s' => \$file,
+  'c|community=s' => \$community,
+  'x|exclude=s' => \@excludes,
+  'h|help' => \&exit_usage) or die;
+
+if (!$host)
+{
+  print STDERR "No hostname given. Please use `--host'.\n";
+  exit (1);
+}
+
+if (@excludes)
+{
+  my $tmp = join (',', @excludes);
+  my @tmp = split (/\s*,\s*/, $tmp);
+
+  @excludes = ();
+  for (@tmp)
+  {
+    my $mib = uc ($_);
+    if ($mib eq 'HELP')
+    {
+      exit_usage_exclude ();
+    }
+    elsif (!exists ($ExcludeOptions{$mib}))
+    {
+      print STDERR "No such MIB: $mib\n";
+      exit_usage_exclude ();
+    }
+    push (@excludes, $ExcludeOptions{$mib});
+  }
+}
+
+$conf = get_config ($file) or die ("Cannot read config");
+
+if (!defined ($conf->{'plugin'})
+  || !defined ($conf->{'plugin'}{'snmp'})
+  || !defined ($conf->{'plugin'}{'snmp'}{'data'}))
+{
+  print STDERR "Error: No <plugin>, <snmp>, or <data> block found.\n";
+  exit (1);
+}
+
+probe_all ($host, $community, $conf->{'plugin'}{'snmp'}{'data'}, \@excludes);
+
+exit (0);
+
+=head1 BUGS
+
+=over 4
+
+=item
+
+C<Include> statements in the config file are not handled correctly.
+
+=item
+
+SNMPv2 / SNMPv1 detection is a hack.
+
+=back
+
+=head1 AUTHOR
+
+Copyright (c) 2008 by Florian octo Forster
+E<lt>octoE<nbsp>atE<nbsp>noris.netE<gt>. Licensed under the terms of the GPLv2.
+Written for the norisE<nbsp>networkE<nbsp>AG L<http://noris.net/>.
+
+=cut
+
+# vim: set sw=2 sts=2 ts=8 et :
diff --git a/contrib/solaris-smf/README b/contrib/solaris-smf/README
new file mode 100644 (file)
index 0000000..dfd990b
--- /dev/null
@@ -0,0 +1,331 @@
+SMF is the way Solaris 10 and later preferably manages services. It is intended
+to replace the old init.d process and provides advances features, such as
+automatic restarting of failing services.
+
+The following blog entry by Piotr Hosowicz describes the process in more
+detail. The original entry can be found at
+<http://phosowicz.jogger.pl/2008/12/21/smf-izing-collectd/>.
+
+The files in this directory are:
+
+  README          This file
+  collectd        Start / stop script
+  collectd.xml    SMF manifest for collectd.
+
+------------------------------------------------------------------------
+
+      SMF-izing collectd <#>
+
+Wpis na 0. poziomie, wysłany 21 grudnia 2008 o 16:30:49.
+
+My two previous blog entries were about building and running collectd
+<http://collectd.org/> on Sun Solaris 10. After the first one Octo
+contacted me and was so kind as to release a packaged version for x86_64
+<http://collectd.org/download.shtml#solaris>. I have put aside the build
+I rolled on my own and decided to install and run the packaged one on
+the production servers. This blog entry is about SMF-izing the collectd
+daemon.
+
+A few words about the SMF – the Solaris'es Service Management Facility.
+I think it appeared in Solaris 10. From then on the good old |/etc/rcN.d
+|| /etc/init.d| services are called /legacy services/. They still can be
+run, but are not fully supported by SMF. SMF enables you to start and
+stop services in the unified way, can direct you to man pages in case a
+service enters maintenance mode, resolves dependencies between services,
+can store properties of services and so on. A nice feature is that SMF
+will take care of restarting services in case they terminate
+unexpectedly, we will use it at the end to check that things are working
+as they should.
+
+The 3V|L thing about SMF is that each service needs so called SMF
+manifest written in XML and a script or scripts that are executed, when
+the service needs to be stopped or started. It can be one script, which
+should accept respective parameters. Even more 3V|L is the fact that the
+manifest is imported into the SMF database and kept there in SQLite format.
+
+Below you will find collectd manifest and the script. I will post them
+to collectd mailing list in matter of minutes with this blog entry
+serving as a README. Please read all down to the bottom, including the
+remarks.
+
+Manifest (based on the work of Kangurek, thanks!), see the "collectd.xml"
+file:
+<?xml version="1.0"?>
+<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
+<service_bundle type='manifest' name='collectd'>
+<service
+        name='application/collectd'
+        type='service'
+        version='1'>
+       <create_default_instance enabled='true' />
+       <single_instance/>
+        <dependency
+                name='network'
+                grouping='require_all'
+                restart_on='none'
+                type='service'>
+                <service_fmri value='svc:/milestone/network:default' />
+        </dependency> 
+        <dependency
+                name='filesystem-local'
+                grouping='require_all'
+                restart_on='none'
+                type='service'>
+                <service_fmri value='svc:/system/filesystem/local:default' />
+        </dependency> 
+        <exec_method
+                type='method'
+                name='start'
+                exec='/lib/svc/method/collectd start'
+                timeout_seconds='60'>
+           <method_context>
+               <method_credential user='root' group='root' />
+           </method_context>
+       </exec_method>
+        <exec_method
+                type='method'
+                name='stop'
+                exec='/lib/svc/method/collectd stop'
+                timeout_seconds='60'>
+           <method_context>
+               <method_credential user='root' group='root' />
+           </method_context>
+       </exec_method>
+        <stability value='Evolving' />
+</service>
+</service_bundle>
+
+Script, see the "collectd" file:
+
+#!/sbin/sh
+PIDFILE=/opt/collectd/var/run/collectd.pid
+DAEMON=/opt/collectd/sbin/collectd
+. /lib/svc/share/smf_include.sh
+case "$1" in
+  start)
+    if [ -f $PIDFILE ] ; then
+      echo "Already running. Stale PID file?"
+      PID=`cat $PIDFILE`
+      echo "$PIDFILE contains $PID"
+      ps -p $PID
+      exit $SMF_EXIT_ERR_FATAL
+    fi
+    $DAEMON
+    if [ $? -ne 0 ] ; then
+      echo $DAEMON faild to start
+      exit $SMF_EXIT_ERR_FATAL
+    fi
+  ;;
+  stop)
+    PID=`cat $PIDFILE 2>/dev/null`
+    kill -15 $PID 2>/dev/null
+    pwait $PID 1> /dev/null 2>/dev/null
+  ;;
+  restart)
+    $0 stop
+    $0 start
+  ;;
+  status)
+    ps -ef | grep collectd | grep -v status | grep -v grep
+  ;;
+  *)
+    echo "Usage: $0 [ start | stop | restart | status ]"
+    exit 1
+  ;;
+esac
+exit $SMF_EXIT_OK
+
+So you have two files: |collectd| script and |collectd.xml| manifest.
+What do you do with these files?
+
+First – before you begin – make sure that collectd is not running, close
+it down. My script above assumes that you are using the default place
+for PID file. Second: remove / move away collectd's |/etc/rcN.d| and
+|/etc/init.d| stuff, you won't need it from now on, because collectd
+will be SMF-ized. Tada!
+
+Next – install the script in place. It took me a minute or two to figure
+out why Solaris'es |install| tool does not work as expected. It turned
+out that the switches and parameters must be in exactly same order as in
+man page, especially the -c parameter must be first:
+
+|# install -c /lib/svc/method/ -m 755 -u root -g bin collectd|
+
+Now is the moment to test once again that the script is working OK. Try
+running:
+
+|# /lib/svc/method/collectd start
+# /lib/svc/method/collectd stop
+# /lib/svc/method/collectd restart
+|
+
+|pgrep| and |kill| are your friends here, also collectd logs. At last
+stop the collectd service and continue.
+
+Now is the time to /slurp/ attached XML manifest into the SMF database.
+This is done using the |svccfg| tool. Transcript follows:
+
+|# svccfg
+svc:> validate collectd.xml
+svc:> import collectd.xml
+svc:>
+|
+
+It is good to run |validate| command first, especially if you copied and
+pasted the XML manifest from this HTML document opened in your
+browser!!! Second thing worth noting is that |svccfg| starts the service
+immediately upon importing the manifest. It might be not what you want.
+For example it will start collecting data on remote collectd server if
+you use network plugin and it will do it under the hostname, that is not
+right. So be sure to configure collectd prior to running it from SMF.
+
+Now a few words about SMF tools. To see the state of all services issue
+|svcs -a| command. To see state of collectd service issue |svcs
+collectd| command. Quite normal states are enabled and disabled. If you
+see maintenance state then something is wrong. Be sure that you stopped
+all non-SMF collectd processes before you follow the procedure described
+here. To stop collectd the SMF way issue the |svcadm disable
+collectd|command. To start collectd the SMF way issue the |svcadm enable
+collectd|command. Be aware that setting it this way makes the change
+persistent across OS reboots, if you want to enable / disable the
+service only temporarily then add |-t| switch after the |enable| /
+|disable| keywords.
+
+And now is time for a grand finale – seeing if SMF can take care of
+collectd in case it crashes. See PID of collectd either using |pgrep| or
+seeing the contents of the PID file and kill it using |kill|. Then check
+with |svcs collectd| command that SMF has restarted collectd soon
+afterwards. You should see that the service is once again enabled in the
+first column, without your intervention.
+
+Things that could or should be clarified:
+
+    * How hard is it to correct mistakes in manifests? Does svccfg just
+      overwrite entries for specified service FMRI or does it require to
+      delete bad entry (and how to do it?!) and import the correct one?
+    * How does SMF know that a service has crashed / terminated? I
+      attended Sun's trainings and the trainer didn't know how to
+      explain this, we formed a hypothesis that it watches new PIDs
+      after starting a service or something like that. I think it is a
+      bad hypothesis, because SMF can watch PID for the startup script
+      itself but I think not the processes launched inside the script. I
+      have a slight idea that it is done based on so called contracts.
+
+------------------------------------------------------------------------
+
+
+        Komentarze do notki “SMF-izing collectd” <#comments>
+
+
+        Zostaw odpowiedź
+
+Nick
+
+------------------------------------------------------------------------
+
+
+    Archiwum
+
+    * Grudzień 2008 (5) </2008/12/>
+    * Październik 2008 (4) </2008/10/>
+    * Wrzesień 2008 (7) </2008/09/>
+    * Sierpień 2008 (5) </2008/08/>
+    * Lipiec 2008 (12) </2008/07/>
+    * Czerwiec 2008 (11) </2008/06/>
+    * Maj 2008 (3) </2008/05/>
+    * Kwiecień 2008 (10) </2008/04/>
+    * Marzec 2008 (3) </2008/03/>
+    * Luty 2008 (1) </2008/02/>
+    * Styczeń 2008 (1) </2008/01/>
+    * Listopad 2007 (4) </2007/11/>
+    * Październik 2007 (6) </2007/10/>
+    * Wrzesień 2007 (10) </2007/09/>
+    * Sierpień 2007 (3) </2007/08/>
+    * Lipiec 2007 (8) </2007/07/>
+    * Czerwiec 2007 (10) </2007/06/>
+    * Maj 2007 (12) </2007/05/>
+    * Kwiecień 2007 (17) </2007/04/>
+
+
+    Kategorie
+
+    * Film (8) <http://phosowicz.jogger.pl/kategoria/film/>
+    * Książki (35) <http://phosowicz.jogger.pl/kategoria/ksiazki/>
+    * Muzyka (15) <http://phosowicz.jogger.pl/kategoria/muzyka/>
+    * Ogólne (20) <http://phosowicz.jogger.pl/kategoria/ogolne/>
+    * Polityka (59) <http://phosowicz.jogger.pl/kategoria/polityka/>
+    * Sprzedam (5) <http://phosowicz.jogger.pl/kategoria/sprzedam/>
+    * Techblog (20) <http://phosowicz.jogger.pl/kategoria/techblog/>
+    * Technikalia (34) <http://phosowicz.jogger.pl/kategoria/technikalia/>
+    * Wystawy (3) <http://phosowicz.jogger.pl/kategoria/wystawy/>
+
+
+    Czytuję
+
+    * ArsTechnica.com <http://arstechnica.com/>
+    * Fund. Orientacja <http://www.abcnet.com.pl/>
+    * Techblog Jogger'a <http://techblog.pl/>
+    * The Register <http://www.theregister.co.uk/>
+
+
+    Inne blogi
+
+    * Bloody.Users <http://bloody.users.jogger.pl/>
+    * Dandys <http://dandys.jogger.pl/>
+    * Derin <http://derin.jogger.pl>
+    * Kefir87 <http://kefir87.jogger.pl/>
+    * Klisu <http://klisu.jogger.pl/>
+    * Kwantowe Krajobrazy <http://kwantowekrajobrazy.jogger.pl/>
+    * Paczor <http://paczor.fubar.pl/>
+    * Prestidigitator <http://prestidigitator.jogger.pl>
+    * Remiq <http://remiq.jogger.pl>
+    * Torero <http://torero.jogger.pl/>
+    * Zdzichubg <http://zdzichubg.jogger.pl/>
+
+
+    Inne moje strony
+
+    * www.delphi.org.pl <http://www.delphi.org.pl>
+    * www.hosowicz.com <http://www.hosowicz.com>
+
+
+    Pajacyk.pl
+
+    * Nakarm dzieci! <http://pajacyk.pl/>
+
+
+    Meta
+
+    * Panel administracyjny <https://login.jogger.pl/>
+
+
+------------------------------------------------------------------------
+
+phosowicz is powered by Jogger <http://jogger.pl> and K2
+<http://binarybonsai.com/k2/> by Michael <http://binarybonsai.com> and
+Chris <http://chrisjdavis.org>, ported by Patryk Zawadzki.
+<http://patrys.jogger.pl/>
+Notki w RSS <http://phosowicz.jogger.pl/rss/content/html/20>
+
diff --git a/contrib/solaris-smf/collectd b/contrib/solaris-smf/collectd
new file mode 100755 (executable)
index 0000000..5ffdb24
--- /dev/null
@@ -0,0 +1,42 @@
+#!/sbin/sh
+
+PIDFILE=/opt/collectd/var/run/collectd.pid
+DAEMON=/opt/collectd/sbin/collectd
+
+. /lib/svc/share/smf_include.sh
+
+case "$1" in
+  start)
+    if [ -f $PIDFILE ] ; then
+      echo "Already running. Stale PID file?"
+      PID=`cat $PIDFILE`
+      echo "$PIDFILE contains $PID"
+      ps -p $PID
+      exit $SMF_EXIT_ERR_FATAL
+    fi
+    $DAEMON
+    if [ $? -ne 0 ] ; then
+      echo $DAEMON faild to start
+      exit $SMF_EXIT_ERR_FATAL
+    fi
+  ;;
+  stop)
+    PID=`cat $PIDFILE 2>/dev/null`
+    kill -15 $PID 2>/dev/null
+    pwait $PID 1> /dev/null 2>/dev/null
+  ;;
+  restart)
+    $0 stop
+    $0 start
+  ;;
+  status)
+    ps -ef | grep collectd | grep -v status | grep -v grep
+  ;;
+  *)
+    echo "Usage: $0 [ start | stop | restart | status ]"
+    exit 1
+  ;;
+esac
+
+
+exit $SMF_EXIT_OK
diff --git a/contrib/solaris-smf/collectd.xml b/contrib/solaris-smf/collectd.xml
new file mode 100644 (file)
index 0000000..d1ae3a4
--- /dev/null
@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
+
+<service_bundle type='manifest' name='collectd'>
+<service
+        name='application/collectd'
+        type='service'
+        version='1'>
+
+       <create_default_instance enabled='true' />
+       
+       <single_instance/>
+
+        <dependency
+                name='network'
+                grouping='require_all'
+                restart_on='none'
+                type='service'>
+                <service_fmri value='svc:/milestone/network:default' />
+        </dependency> 
+
+        <dependency
+                name='filesystem-local'
+                grouping='require_all'
+                restart_on='none'
+                type='service'>
+                <service_fmri value='svc:/system/filesystem/local:default' />
+        </dependency> 
+
+        <exec_method
+                type='method'
+                name='start'
+                exec='/lib/svc/method/collectd start'
+                timeout_seconds='60'>
+           <method_context>
+               <method_credential user='root' group='root' />
+           </method_context>
+       </exec_method>
+
+
+        <exec_method
+                type='method'
+                name='stop'
+                exec='/lib/svc/method/collectd stop'
+                timeout_seconds='60'>
+           <method_context>
+               <method_credential user='root' group='root' />
+           </method_context>
+       </exec_method>
+
+        <stability value='Evolving' />
+
+</service>
+
+</service_bundle>
+
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644 (file)
index 0000000..affc171
--- /dev/null
@@ -0,0 +1,1353 @@
+SUBDIRS = libcollectdclient
+if BUILD_WITH_OWN_LIBOCONFIG
+SUBDIRS += liboconfig
+endif
+
+if COMPILER_IS_GCC
+AM_CFLAGS = -Wall -Werror
+endif
+
+AM_CPPFLAGS = -DPREFIX='"${prefix}"'
+AM_CPPFLAGS += -DCONFIGFILE='"${sysconfdir}/${PACKAGE_NAME}.conf"'
+AM_CPPFLAGS += -DLOCALSTATEDIR='"${localstatedir}"'
+AM_CPPFLAGS += -DPKGLOCALSTATEDIR='"${localstatedir}/lib/${PACKAGE_NAME}"'
+if BUILD_FEATURE_DAEMON
+AM_CPPFLAGS += -DPIDFILE='"${localstatedir}/run/${PACKAGE_NAME}.pid"'
+endif
+AM_CPPFLAGS += -DPLUGINDIR='"${pkglibdir}"'
+AM_CPPFLAGS += -DPKGDATADIR='"${pkgdatadir}"'
+
+sbin_PROGRAMS = collectd collectdmon
+bin_PROGRAMS = collectd-nagios collectdctl
+
+collectd_SOURCES = collectd.c collectd.h \
+                  common.c common.h \
+                  configfile.c configfile.h \
+                  filter_chain.c filter_chain.h \
+                  meta_data.c meta_data.h \
+                  plugin.c plugin.h \
+                  utils_avltree.c utils_avltree.h \
+                  utils_cache.c utils_cache.h \
+                  utils_complain.c utils_complain.h \
+                  utils_heap.c utils_heap.h \
+                  utils_ignorelist.c utils_ignorelist.h \
+                  utils_llist.c utils_llist.h \
+                  utils_parse_option.c utils_parse_option.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
+
+collectd_CPPFLAGS =  $(AM_CPPFLAGS) $(LTDLINCL)
+collectd_CFLAGS = $(AM_CFLAGS)
+collectd_LDFLAGS = -export-dynamic
+collectd_LDADD =
+collectd_DEPENDENCIES =
+
+# Link to these libraries..
+if BUILD_WITH_LIBRT
+collectd_LDADD += -lrt
+endif
+if BUILD_WITH_LIBPOSIX4
+collectd_LDADD += -lposix4
+endif
+if BUILD_WITH_LIBSOCKET
+collectd_LDADD += -lsocket
+endif
+if BUILD_WITH_LIBRESOLV
+collectd_LDADD += -lresolv
+endif
+if BUILD_WITH_LIBPTHREAD
+collectd_LDADD += -lpthread
+endif
+if BUILD_WITH_LIBKSTAT
+collectd_LDADD += -lkstat
+endif
+if BUILD_WITH_LIBDEVINFO
+collectd_LDADD += -ldevinfo
+endif
+if BUILD_AIX
+collectd_LDFLAGS += -Wl,-bexpall,-brtllib
+collectd_LDADD += -lm
+endif
+
+# The daemon needs to call sg_init, so we need to link it against libstatgrab,
+# too. -octo
+if BUILD_WITH_LIBSTATGRAB
+collectd_CFLAGS += $(BUILD_WITH_LIBSTATGRAB_CFLAGS)
+collectd_LDADD += $(BUILD_WITH_LIBSTATGRAB_LDFLAGS)
+endif
+
+if BUILD_WITH_OWN_LIBOCONFIG
+collectd_LDADD += $(LIBLTDL) liboconfig/liboconfig.la
+collectd_DEPENDENCIES += $(LIBLTDL) liboconfig/liboconfig.la
+else
+collectd_LDADD += -loconfig
+endif
+
+collectdmon_SOURCES = collectdmon.c
+collectdmon_CPPFLAGS = $(AM_CPPFLAGS)
+
+collectd_nagios_SOURCES = collectd-nagios.c
+collectd_nagios_LDADD =
+if BUILD_WITH_LIBSOCKET
+collectd_nagios_LDADD += -lsocket
+endif
+if BUILD_AIX
+collectd_nagios_LDADD += -lm
+endif
+
+collectd_nagios_LDADD += libcollectdclient/libcollectdclient.la
+collectd_nagios_DEPENDENCIES = libcollectdclient/libcollectdclient.la
+
+
+collectdctl_SOURCES = collectdctl.c
+collectdctl_LDADD =
+if BUILD_WITH_LIBSOCKET
+collectdctl_LDADD += -lsocket
+endif
+if BUILD_AIX
+collectdctl_LDADD += -lm
+endif
+collectdctl_LDADD += libcollectdclient/libcollectdclient.la
+collectdctl_DEPENDENCIES = libcollectdclient/libcollectdclient.la
+
+
+pkglib_LTLIBRARIES = 
+
+BUILT_SOURCES = 
+CLEANFILES = 
+
+if BUILD_PLUGIN_AMQP
+pkglib_LTLIBRARIES += amqp.la
+amqp_la_SOURCES = amqp.c \
+                 utils_cmd_putval.c utils_cmd_putval.h \
+                 utils_format_json.c utils_format_json.h
+amqp_la_LDFLAGS = -module -avoid-version $(BUILD_WITH_LIBRABBITMQ_LDFLAGS)
+amqp_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBRABBITMQ_CPPFLAGS)
+amqp_la_LIBADD = $(BUILD_WITH_LIBRABBITMQ_LIBS)
+collectd_LDADD += "-dlopen" amqp.la
+collectd_DEPENDENCIES += amqp.la
+endif
+
+if BUILD_PLUGIN_APACHE
+pkglib_LTLIBRARIES += apache.la
+apache_la_SOURCES = apache.c
+apache_la_LDFLAGS = -module -avoid-version
+apache_la_CFLAGS = $(AM_CFLAGS)
+apache_la_LIBADD =
+collectd_LDADD += "-dlopen" apache.la
+if BUILD_WITH_LIBCURL
+apache_la_CFLAGS += $(BUILD_WITH_LIBCURL_CFLAGS)
+apache_la_LIBADD += $(BUILD_WITH_LIBCURL_LIBS)
+endif
+collectd_DEPENDENCIES += apache.la
+endif
+
+if BUILD_PLUGIN_APCUPS
+pkglib_LTLIBRARIES += apcups.la
+apcups_la_SOURCES = apcups.c
+apcups_la_LDFLAGS = -module -avoid-version
+apcups_la_LIBADD =
+if BUILD_WITH_LIBSOCKET
+apcups_la_LIBADD += -lsocket
+endif
+collectd_LDADD += "-dlopen" apcups.la
+collectd_DEPENDENCIES += apcups.la
+endif
+
+if BUILD_PLUGIN_APPLE_SENSORS
+pkglib_LTLIBRARIES += apple_sensors.la
+apple_sensors_la_SOURCES = apple_sensors.c
+apple_sensors_la_LDFLAGS = -module -avoid-version
+apple_sensors_la_LIBADD = -lIOKit
+collectd_LDADD += "-dlopen" apple_sensors.la
+collectd_DEPENDENCIES += apple_sensors.la
+endif
+
+if BUILD_PLUGIN_ASCENT
+pkglib_LTLIBRARIES += ascent.la
+ascent_la_SOURCES = ascent.c
+ascent_la_LDFLAGS = -module -avoid-version
+ascent_la_CFLAGS = $(AM_CFLAGS) \
+               $(BUILD_WITH_LIBCURL_CFLAGS) $(BUILD_WITH_LIBXML2_CFLAGS)
+ascent_la_LIBADD = $(BUILD_WITH_LIBCURL_LIBS) $(BUILD_WITH_LIBXML2_LIBS)
+collectd_LDADD += "-dlopen" ascent.la
+collectd_DEPENDENCIES += ascent.la
+endif
+
+if BUILD_PLUGIN_BATTERY
+pkglib_LTLIBRARIES += battery.la
+battery_la_SOURCES = battery.c
+battery_la_LDFLAGS = -module -avoid-version
+battery_la_LIBADD =
+if BUILD_WITH_LIBIOKIT
+battery_la_LIBADD += -lIOKit
+endif
+collectd_LDADD += "-dlopen" battery.la
+collectd_DEPENDENCIES += battery.la
+endif
+
+if BUILD_PLUGIN_BIND
+pkglib_LTLIBRARIES += bind.la
+bind_la_SOURCES = bind.c
+bind_la_LDFLAGS = -module -avoid-version
+bind_la_CFLAGS = $(AM_CFLAGS) \
+               $(BUILD_WITH_LIBCURL_CFLAGS) $(BUILD_WITH_LIBXML2_CFLAGS)
+bind_la_LIBADD = $(BUILD_WITH_LIBCURL_LIBS) $(BUILD_WITH_LIBXML2_LIBS)
+collectd_LDADD += "-dlopen" bind.la
+collectd_DEPENDENCIES += bind.la
+endif
+
+if BUILD_PLUGIN_CONNTRACK
+pkglib_LTLIBRARIES += conntrack.la
+conntrack_la_SOURCES = conntrack.c
+conntrack_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" conntrack.la
+collectd_DEPENDENCIES += conntrack.la
+endif
+
+if BUILD_PLUGIN_CONTEXTSWITCH
+pkglib_LTLIBRARIES += contextswitch.la
+contextswitch_la_SOURCES = contextswitch.c
+contextswitch_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" contextswitch.la
+collectd_DEPENDENCIES += contextswitch.la
+endif
+
+if BUILD_PLUGIN_CPU
+pkglib_LTLIBRARIES += cpu.la
+cpu_la_SOURCES = cpu.c
+cpu_la_CFLAGS = $(AM_CFLAGS)
+cpu_la_LDFLAGS = -module -avoid-version
+cpu_la_LIBADD = 
+if BUILD_WITH_LIBKSTAT
+cpu_la_LIBADD += -lkstat
+endif
+if BUILD_WITH_LIBDEVINFO
+cpu_la_LIBADD += -ldevinfo
+endif
+if BUILD_WITH_LIBSTATGRAB
+cpu_la_CFLAGS += $(BUILD_WITH_LIBSTATGRAB_CFLAGS)
+cpu_la_LIBADD += $(BUILD_WITH_LIBSTATGRAB_LDFLAGS)
+endif
+if BUILD_WITH_PERFSTAT
+cpu_la_LIBADD += -lperfstat
+endif
+collectd_LDADD += "-dlopen" cpu.la
+collectd_DEPENDENCIES += cpu.la
+endif
+
+if BUILD_PLUGIN_CPUFREQ
+pkglib_LTLIBRARIES += cpufreq.la
+cpufreq_la_SOURCES = cpufreq.c
+cpufreq_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" cpufreq.la
+collectd_DEPENDENCIES += cpufreq.la
+endif
+
+if BUILD_PLUGIN_CSV
+pkglib_LTLIBRARIES += csv.la
+csv_la_SOURCES = csv.c
+csv_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" csv.la
+collectd_DEPENDENCIES += csv.la
+endif
+
+if BUILD_PLUGIN_CURL
+pkglib_LTLIBRARIES += curl.la
+curl_la_SOURCES = curl.c
+curl_la_LDFLAGS = -module -avoid-version
+curl_la_CFLAGS = $(AM_CFLAGS)
+curl_la_LIBADD =
+collectd_LDADD += "-dlopen" curl.la
+if BUILD_WITH_LIBCURL
+curl_la_CFLAGS += $(BUILD_WITH_LIBCURL_CFLAGS)
+curl_la_LIBADD += $(BUILD_WITH_LIBCURL_LIBS)
+endif
+collectd_DEPENDENCIES += curl.la
+endif
+
+if BUILD_PLUGIN_CURL_JSON
+pkglib_LTLIBRARIES += curl_json.la
+curl_json_la_SOURCES = curl_json.c
+curl_json_la_CFLAGS = $(AM_CFLAGS)
+curl_json_la_LDFLAGS = -module -avoid-version $(BUILD_WITH_LIBYAJL_LDFLAGS)
+curl_json_la_CPPFLAGS = $(BUILD_WITH_LIBYAJL_CPPFLAGS)
+curl_json_la_LIBADD = $(BUILD_WITH_LIBYAJL_LIBS)
+if BUILD_WITH_LIBCURL
+curl_json_la_CFLAGS += $(BUILD_WITH_LIBCURL_CFLAGS)
+curl_json_la_LIBADD += $(BUILD_WITH_LIBCURL_LIBS)
+endif
+collectd_LDADD += "-dlopen" curl_json.la
+collectd_DEPENDENCIES += curl_json.la
+endif
+
+if BUILD_PLUGIN_CURL_XML
+pkglib_LTLIBRARIES += curl_xml.la
+curl_xml_la_SOURCES = curl_xml.c
+curl_xml_la_LDFLAGS = -module -avoid-version
+curl_xml_la_CFLAGS = $(AM_CFLAGS) \
+               $(BUILD_WITH_LIBCURL_CFLAGS) $(BUILD_WITH_LIBXML2_CFLAGS)
+curl_xml_la_LIBADD = $(BUILD_WITH_LIBCURL_LIBS) $(BUILD_WITH_LIBXML2_LIBS)
+collectd_LDADD += "-dlopen" curl_xml.la
+collectd_DEPENDENCIES += curl_xml.la
+endif
+
+if BUILD_PLUGIN_DBI
+pkglib_LTLIBRARIES += dbi.la
+dbi_la_SOURCES = dbi.c \
+                utils_db_query.c utils_db_query.h
+dbi_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBDBI_CPPFLAGS)
+dbi_la_LDFLAGS = -module -avoid-version $(BUILD_WITH_LIBDBI_LDFLAGS)
+dbi_la_LIBADD = $(BUILD_WITH_LIBDBI_LIBS)
+collectd_LDADD += "-dlopen" dbi.la
+collectd_DEPENDENCIES += dbi.la
+endif
+
+if BUILD_PLUGIN_DF
+pkglib_LTLIBRARIES += df.la
+df_la_SOURCES = df.c utils_mount.c utils_mount.h
+df_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" df.la
+collectd_DEPENDENCIES += df.la
+endif
+
+if BUILD_PLUGIN_DISK
+pkglib_LTLIBRARIES += disk.la
+disk_la_SOURCES = disk.c
+disk_la_CFLAGS = $(AM_CFLAGS)
+disk_la_LDFLAGS = -module -avoid-version
+disk_la_LIBADD = 
+if BUILD_WITH_LIBKSTAT
+disk_la_LIBADD += -lkstat
+endif
+if BUILD_WITH_LIBDEVINFO
+disk_la_LIBADD += -ldevinfo
+endif
+if BUILD_WITH_LIBIOKIT
+disk_la_LIBADD += -lIOKit
+endif
+if BUILD_WITH_LIBSTATGRAB
+disk_la_CFLAGS += $(BUILD_WITH_LIBSTATGRAB_CFLAGS)  
+disk_la_LIBADD += $(BUILD_WITH_LIBSTATGRAB_LDFLAGS)
+endif
+if BUILD_WITH_PERFSTAT
+disk_la_LIBADD += -lperfstat
+endif
+collectd_LDADD += "-dlopen" disk.la
+collectd_DEPENDENCIES += disk.la
+endif
+
+if BUILD_PLUGIN_DNS
+pkglib_LTLIBRARIES += dns.la
+dns_la_SOURCES = dns.c utils_dns.c utils_dns.h
+dns_la_LDFLAGS = -module -avoid-version
+dns_la_LIBADD = -lpcap -lpthread
+collectd_LDADD += "-dlopen" dns.la
+collectd_DEPENDENCIES += dns.la
+endif
+
+if BUILD_PLUGIN_EMAIL
+pkglib_LTLIBRARIES += email.la
+email_la_SOURCES = email.c
+email_la_LDFLAGS = -module -avoid-version
+email_la_LIBADD = -lpthread
+collectd_LDADD += "-dlopen" email.la
+collectd_DEPENDENCIES += email.la
+endif
+
+if BUILD_PLUGIN_ENTROPY
+pkglib_LTLIBRARIES += entropy.la
+entropy_la_SOURCES = entropy.c
+entropy_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" entropy.la
+collectd_DEPENDENCIES += entropy.la
+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
+exec_la_LDFLAGS = -module -avoid-version
+exec_la_LIBADD = -lpthread
+collectd_LDADD += "-dlopen" exec.la
+collectd_DEPENDENCIES += exec.la
+endif
+
+if BUILD_PLUGIN_FILECOUNT
+pkglib_LTLIBRARIES += filecount.la
+filecount_la_SOURCES = filecount.c
+filecount_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" filecount.la
+collectd_DEPENDENCIES += filecount.la
+endif
+
+if BUILD_PLUGIN_GMOND
+pkglib_LTLIBRARIES += gmond.la
+gmond_la_SOURCES = gmond.c
+gmond_la_CPPFLAGS = $(AM_CPPFLAGS) $(GANGLIA_CPPFLAGS)
+gmond_la_LDFLAGS = -module -avoid-version $(GANGLIA_LDFLAGS)
+gmond_la_LIBADD = $(GANGLIA_LIBS)
+collectd_LDADD += "-dlopen" gmond.la
+collectd_DEPENDENCIES += gmond.la
+endif
+
+if BUILD_PLUGIN_HDDTEMP
+pkglib_LTLIBRARIES += hddtemp.la
+hddtemp_la_SOURCES = hddtemp.c
+hddtemp_la_LDFLAGS = -module -avoid-version
+hddtemp_la_LIBADD =
+if BUILD_WITH_LIBSOCKET
+hddtemp_la_LIBADD += -lsocket
+endif
+collectd_LDADD += "-dlopen" hddtemp.la
+collectd_DEPENDENCIES += hddtemp.la
+endif
+
+if BUILD_PLUGIN_INTERFACE
+pkglib_LTLIBRARIES += interface.la
+interface_la_SOURCES = interface.c
+interface_la_CFLAGS = $(AM_CFLAGS)
+interface_la_LDFLAGS = -module -avoid-version
+interface_la_LIBADD =
+collectd_LDADD += "-dlopen" interface.la
+collectd_DEPENDENCIES += interface.la
+if BUILD_WITH_LIBSTATGRAB
+interface_la_CFLAGS += $(BUILD_WITH_LIBSTATGRAB_CFLAGS)
+interface_la_LIBADD += $(BUILD_WITH_LIBSTATGRAB_LDFLAGS)
+else
+if BUILD_WITH_LIBKSTAT
+interface_la_LIBADD += -lkstat
+endif
+if BUILD_WITH_LIBDEVINFO
+interface_la_LIBADD += -ldevinfo
+endif # BUILD_WITH_LIBDEVINFO
+endif # !BUILD_WITH_LIBSTATGRAB
+if BUILD_WITH_PERFSTAT
+interface_la_LIBADD += -lperfstat
+endif
+endif # BUILD_PLUGIN_INTERFACE
+
+if BUILD_PLUGIN_IPTABLES
+pkglib_LTLIBRARIES += iptables.la
+iptables_la_SOURCES = iptables.c
+iptables_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBIPTC_CPPFLAGS)
+iptables_la_LDFLAGS = -module -avoid-version $(BUILD_WITH_LIBIPTC_LDFLAGS)
+iptables_la_LIBADD = -liptc
+collectd_LDADD += "-dlopen" iptables.la
+collectd_DEPENDENCIES += iptables.la
+endif
+
+if BUILD_PLUGIN_IPMI
+pkglib_LTLIBRARIES += ipmi.la
+ipmi_la_SOURCES = ipmi.c
+ipmi_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_OPENIPMI_CFLAGS)
+ipmi_la_LDFLAGS = -module -avoid-version
+ipmi_la_LIBADD = $(BUILD_WITH_OPENIPMI_LIBS)
+collectd_LDADD += "-dlopen" ipmi.la
+collectd_DEPENDENCIES += ipmi.la
+endif
+
+if BUILD_PLUGIN_IPVS
+pkglib_LTLIBRARIES += ipvs.la
+ipvs_la_SOURCES = ipvs.c
+if IP_VS_H_NEEDS_KERNEL_CFLAGS
+ipvs_la_CFLAGS = $(AM_CFLAGS) $(KERNEL_CFLAGS)
+endif
+ipvs_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" ipvs.la
+collectd_DEPENDENCIES += ipvs.la
+endif
+
+if BUILD_PLUGIN_IRQ
+pkglib_LTLIBRARIES += irq.la
+irq_la_SOURCES = irq.c
+irq_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" irq.la
+collectd_DEPENDENCIES += irq.la
+endif
+
+if BUILD_PLUGIN_JAVA
+pkglib_LTLIBRARIES += java.la
+java_la_SOURCES = java.c
+java_la_CPPFLAGS = $(AM_CPPFLAGS) $(JAVA_CPPFLAGS)
+java_la_CFLAGS = $(AM_CFLAGS) $(JAVA_CFLAGS)
+java_la_LDFLAGS = -module -avoid-version $(JAVA_LDFLAGS)
+java_la_LIBADD = $(JAVA_LIBS)
+collectd_LDADD += "-dlopen" java.la
+collectd_DEPENDENCIES += java.la
+endif
+
+if BUILD_PLUGIN_LIBVIRT
+pkglib_LTLIBRARIES += libvirt.la
+libvirt_la_SOURCES = libvirt.c
+libvirt_la_CFLAGS = $(AM_CFLAGS) \
+               $(BUILD_WITH_LIBVIRT_CFLAGS) $(BUILD_WITH_LIBXML2_CFLAGS)
+libvirt_la_LIBADD = $(BUILD_WITH_LIBVIRT_LIBS) $(BUILD_WITH_LIBXML2_LIBS)
+libvirt_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" libvirt.la
+collectd_DEPENDENCIES += libvirt.la
+endif
+
+if BUILD_PLUGIN_LOAD
+pkglib_LTLIBRARIES += load.la
+load_la_SOURCES = load.c
+load_la_CFLAGS = $(AM_CFLAGS)
+load_la_LDFLAGS = -module -avoid-version
+load_la_LIBADD =
+collectd_LDADD += "-dlopen" load.la
+collectd_DEPENDENCIES += load.la
+if BUILD_WITH_LIBSTATGRAB
+load_la_CFLAGS += $(BUILD_WITH_LIBSTATGRAB_CFLAGS)
+load_la_LIBADD += $(BUILD_WITH_LIBSTATGRAB_LDFLAGS)
+endif # BUILD_WITH_LIBSTATGRAB
+if BUILD_WITH_PERFSTAT
+load_la_LIBADD += -lperfstat
+endif
+endif # BUILD_PLUGIN_LOAD
+
+if BUILD_PLUGIN_LOGFILE
+pkglib_LTLIBRARIES += logfile.la
+logfile_la_SOURCES = logfile.c
+logfile_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" logfile.la
+collectd_DEPENDENCIES += logfile.la
+endif
+
+if BUILD_PLUGIN_LPAR
+pkglib_LTLIBRARIES += lpar.la
+lpar_la_SOURCES = lpar.c
+lpar_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" lpar.la
+collectd_DEPENDENCIES += lpar.la
+lpar_la_LIBADD = -lperfstat
+endif
+
+if BUILD_PLUGIN_MADWIFI
+pkglib_LTLIBRARIES += madwifi.la
+madwifi_la_SOURCES = madwifi.c madwifi.h
+madwifi_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" madwifi.la
+collectd_DEPENDENCIES += madwifi.la
+endif
+
+if BUILD_PLUGIN_MATCH_EMPTY_COUNTER
+pkglib_LTLIBRARIES += match_empty_counter.la
+match_empty_counter_la_SOURCES = match_empty_counter.c
+match_empty_counter_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" match_empty_counter.la
+collectd_DEPENDENCIES += match_empty_counter.la
+endif
+
+if BUILD_PLUGIN_MATCH_HASHED
+pkglib_LTLIBRARIES += match_hashed.la
+match_hashed_la_SOURCES = match_hashed.c
+match_hashed_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" match_hashed.la
+collectd_DEPENDENCIES += match_hashed.la
+endif
+
+if BUILD_PLUGIN_MATCH_REGEX
+pkglib_LTLIBRARIES += match_regex.la
+match_regex_la_SOURCES = match_regex.c
+match_regex_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" match_regex.la
+collectd_DEPENDENCIES += match_regex.la
+endif
+
+if BUILD_PLUGIN_MATCH_TIMEDIFF
+pkglib_LTLIBRARIES += match_timediff.la
+match_timediff_la_SOURCES = match_timediff.c
+match_timediff_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" match_timediff.la
+collectd_DEPENDENCIES += match_timediff.la
+endif
+
+if BUILD_PLUGIN_MATCH_VALUE
+pkglib_LTLIBRARIES += match_value.la
+match_value_la_SOURCES = match_value.c
+match_value_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" match_value.la
+collectd_DEPENDENCIES += match_value.la
+endif
+
+if BUILD_PLUGIN_MBMON
+pkglib_LTLIBRARIES += mbmon.la
+mbmon_la_SOURCES = mbmon.c
+mbmon_la_LDFLAGS = -module -avoid-version
+mbmon_la_LIBADD =
+if BUILD_WITH_LIBSOCKET
+mbmon_la_LIBADD += -lsocket
+endif
+collectd_LDADD += "-dlopen" mbmon.la
+collectd_DEPENDENCIES += mbmon.la
+endif
+
+if BUILD_PLUGIN_MEMCACHEC
+pkglib_LTLIBRARIES += memcachec.la
+memcachec_la_SOURCES = memcachec.c
+memcachec_la_LDFLAGS = -module -avoid-version $(BUILD_WITH_LIBMEMCACHED_LDFLAGS)
+memcachec_la_CPPFLAGS = $(BUILD_WITH_LIBMEMCACHED_CPPFLAGS)
+memcachec_la_LIBADD = $(BUILD_WITH_LIBMEMCACHED_LIBS)
+collectd_LDADD += "-dlopen" memcachec.la
+collectd_DEPENDENCIES += memcachec.la
+endif
+
+if BUILD_PLUGIN_MEMCACHED
+pkglib_LTLIBRARIES += memcached.la
+memcached_la_SOURCES = memcached.c
+memcached_la_LDFLAGS = -module -avoid-version
+memcached_la_LIBADD =
+if BUILD_WITH_LIBSOCKET
+memcached_la_LIBADD += -lsocket
+endif
+collectd_LDADD += "-dlopen" memcached.la
+collectd_DEPENDENCIES += memcached.la
+endif
+
+if BUILD_PLUGIN_MEMORY
+pkglib_LTLIBRARIES += memory.la
+memory_la_SOURCES = memory.c
+memory_la_CFLAGS = $(AM_CFLAGS)
+memory_la_LDFLAGS = -module -avoid-version
+memory_la_LIBADD =
+collectd_LDADD += "-dlopen" memory.la
+collectd_DEPENDENCIES += memory.la
+if BUILD_WITH_LIBKSTAT
+memory_la_LIBADD += -lkstat
+endif
+if BUILD_WITH_LIBDEVINFO
+memory_la_LIBADD += -ldevinfo
+endif
+if BUILD_WITH_LIBSTATGRAB
+memory_la_CFLAGS += $(BUILD_WITH_LIBSTATGRAB_CFLAGS)
+memory_la_LIBADD += $(BUILD_WITH_LIBSTATGRAB_LDFLAGS)
+endif
+if BUILD_WITH_PERFSTAT
+memory_la_LIBADD += -lperfstat
+endif
+endif
+
+if BUILD_PLUGIN_MODBUS
+pkglib_LTLIBRARIES += modbus.la
+modbus_la_SOURCES = modbus.c
+modbus_la_LDFLAGS = -module -avoid-version
+modbus_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBMODBUS_CFLAGS)
+modbus_la_LIBADD = $(BUILD_WITH_LIBMODBUS_LIBS)
+collectd_LDADD += "-dlopen" modbus.la
+collectd_DEPENDENCIES += modbus.la
+endif
+
+if BUILD_PLUGIN_MULTIMETER
+pkglib_LTLIBRARIES += multimeter.la
+multimeter_la_SOURCES = multimeter.c
+multimeter_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" multimeter.la
+collectd_DEPENDENCIES += multimeter.la
+endif
+
+if BUILD_PLUGIN_MYSQL
+pkglib_LTLIBRARIES += mysql.la
+mysql_la_SOURCES = mysql.c
+mysql_la_LDFLAGS = -module -avoid-version
+mysql_la_CFLAGS = $(AM_CFLAGS)
+mysql_la_LIBADD =
+collectd_LDADD += "-dlopen" mysql.la
+if BUILD_WITH_LIBMYSQL
+mysql_la_CFLAGS += $(BUILD_WITH_LIBMYSQL_CFLAGS)
+mysql_la_LIBADD += $(BUILD_WITH_LIBMYSQL_LIBS)
+endif
+collectd_DEPENDENCIES += mysql.la
+endif
+
+if BUILD_PLUGIN_NETAPP
+pkglib_LTLIBRARIES += netapp.la
+netapp_la_SOURCES = netapp.c
+netapp_la_CPPFLAGS = $(AM_CPPFLAGS) $(LIBNETAPP_CPPFLAGS)
+netapp_la_LDFLAGS = -module -avoid-version $(LIBNETAPP_LDFLAGS)
+netapp_la_LIBADD = $(LIBNETAPP_LIBS)
+collectd_LDADD += "-dlopen" netapp.la
+collectd_DEPENDENCIES += netapp.la
+endif
+
+if BUILD_PLUGIN_NETLINK
+pkglib_LTLIBRARIES += netlink.la
+netlink_la_SOURCES = netlink.c
+netlink_la_LDFLAGS = -module -avoid-version
+netlink_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBNETLINK_CFLAGS)
+netlink_la_LIBADD = $(BUILD_WITH_LIBNETLINK_LIBS)
+collectd_LDADD += "-dlopen" netlink.la
+collectd_DEPENDENCIES += netlink.la
+endif
+
+if BUILD_PLUGIN_NETWORK
+pkglib_LTLIBRARIES += network.la
+network_la_SOURCES = network.c network.h \
+                    utils_fbhash.c utils_fbhash.h
+network_la_CPPFLAGS = $(AM_CPPFLAGS)
+network_la_LDFLAGS = -module -avoid-version
+network_la_LIBADD = -lpthread
+if BUILD_WITH_LIBSOCKET
+network_la_LIBADD += -lsocket
+endif
+if BUILD_WITH_LIBGCRYPT
+network_la_CPPFLAGS += $(GCRYPT_CPPFLAGS)
+network_la_LDFLAGS += $(GCRYPT_LDFLAGS)
+network_la_LIBADD += $(GCRYPT_LIBS)
+endif
+collectd_LDADD += "-dlopen" network.la
+collectd_DEPENDENCIES += network.la
+endif
+
+if BUILD_PLUGIN_NFS
+pkglib_LTLIBRARIES += nfs.la
+nfs_la_SOURCES = nfs.c
+nfs_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" nfs.la
+collectd_DEPENDENCIES += nfs.la
+endif
+
+if BUILD_PLUGIN_FSCACHE
+pkglib_LTLIBRARIES += fscache.la
+fscache_la_SOURCES = fscache.c
+fscache_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" fscache.la
+collectd_DEPENDENCIES += fscache.la
+endif
+
+if BUILD_PLUGIN_NGINX
+pkglib_LTLIBRARIES += nginx.la
+nginx_la_SOURCES = nginx.c
+nginx_la_CFLAGS = $(AM_CFLAGS)
+nginx_la_LIBADD =
+nginx_la_LDFLAGS = -module -avoid-version
+if BUILD_WITH_LIBCURL
+nginx_la_CFLAGS += $(BUILD_WITH_LIBCURL_CFLAGS)
+nginx_la_LIBADD += $(BUILD_WITH_LIBCURL_LIBS)
+endif
+collectd_LDADD += "-dlopen" nginx.la
+collectd_DEPENDENCIES += nginx.la
+endif
+
+if BUILD_PLUGIN_NOTIFY_DESKTOP
+pkglib_LTLIBRARIES += notify_desktop.la
+notify_desktop_la_SOURCES = notify_desktop.c
+notify_desktop_la_CFLAGS = $(AM_CFLAGS) $(LIBNOTIFY_CFLAGS)
+notify_desktop_la_LDFLAGS = -module -avoid-version
+notify_desktop_la_LIBADD = $(LIBNOTIFY_LIBS)
+collectd_LDADD += "-dlopen" notify_desktop.la
+collectd_DEPENDENCIES += notify_desktop.la
+endif
+
+if BUILD_PLUGIN_NOTIFY_EMAIL
+pkglib_LTLIBRARIES += notify_email.la
+notify_email_la_SOURCES = notify_email.c
+notify_email_la_LDFLAGS = -module -avoid-version
+notify_email_la_LIBADD = -lesmtp -lssl -lcrypto -lpthread -ldl
+collectd_LDADD += "-dlopen" notify_email.la
+collectd_DEPENDENCIES += notify_email.la
+endif
+
+if BUILD_PLUGIN_NTPD
+pkglib_LTLIBRARIES += ntpd.la
+ntpd_la_SOURCES = ntpd.c
+ntpd_la_LDFLAGS = -module -avoid-version
+ntpd_la_LIBADD =
+if BUILD_WITH_LIBSOCKET
+ntpd_la_LIBADD += -lsocket
+endif
+collectd_LDADD += "-dlopen" ntpd.la
+collectd_DEPENDENCIES += ntpd.la
+endif
+
+if BUILD_PLUGIN_NUT
+pkglib_LTLIBRARIES += nut.la
+nut_la_SOURCES = nut.c
+nut_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBUPSCLIENT_CFLAGS)
+nut_la_LDFLAGS = -module -avoid-version
+nut_la_LIBADD = -lpthread $(BUILD_WITH_LIBUPSCLIENT_LIBS)
+collectd_LDADD += "-dlopen" nut.la
+collectd_DEPENDENCIES += nut.la
+endif
+
+if BUILD_PLUGIN_OLSRD
+pkglib_LTLIBRARIES += olsrd.la
+olsrd_la_SOURCES = olsrd.c
+olsrd_la_LDFLAGS = -module -avoid-version
+olsrd_la_LIBADD = 
+if BUILD_WITH_LIBSOCKET
+olsrd_la_LIBADD += -lsocket
+endif
+collectd_LDADD += "-dlopen" olsrd.la
+collectd_DEPENDENCIES += olsrd.la
+endif
+
+if BUILD_PLUGIN_ONEWIRE
+pkglib_LTLIBRARIES += onewire.la
+onewire_la_SOURCES = onewire.c
+onewire_la_CFLAGS = $(AM_CFLAGS)
+onewire_la_CPPFLAGS = $(BUILD_WITH_LIBOWCAPI_CPPFLAGS)
+onewire_la_LIBADD = $(BUILD_WITH_LIBOWCAPI_LIBS)
+onewire_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" onewire.la
+collectd_DEPENDENCIES += onewire.la
+endif
+
+if BUILD_PLUGIN_OPENVPN
+pkglib_LTLIBRARIES += openvpn.la
+openvpn_la_SOURCES = openvpn.c
+openvpn_la_CFLAGS = $(AM_CFLAGS)
+openvpn_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" openvpn.la
+collectd_DEPENDENCIES += openvpn.la
+endif
+
+if BUILD_PLUGIN_ORACLE
+pkglib_LTLIBRARIES += oracle.la
+oracle_la_SOURCES = oracle.c \
+       utils_db_query.c utils_db_query.h
+oracle_la_CFLAGS = $(AM_CFLAGS)
+oracle_la_CPPFLAGS = $(BUILD_WITH_ORACLE_CFLAGS)
+oracle_la_LIBADD = $(BUILD_WITH_ORACLE_LIBS)
+oracle_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" oracle.la
+collectd_DEPENDENCIES += oracle.la
+endif
+
+if BUILD_PLUGIN_PERL
+pkglib_LTLIBRARIES += perl.la
+perl_la_SOURCES = perl.c
+# Despite C99 providing the "bool" type thru stdbool.h, Perl defines its own
+# version of that type if HAS_BOOL is not defined... *sigh*
+perl_la_CPPFLAGS = $(AM_CPPFLAGS) -DHAS_BOOL=1
+perl_la_CFLAGS  = $(AM_CFLAGS) \
+               $(PERL_CFLAGS) \
+               -DXS_VERSION=\"$(VERSION)\" -DVERSION=\"$(VERSION)\"
+# Work-around for issues #41 and #42 - Perl 5.10 incorrectly introduced
+# __attribute__nonnull__(3) for Perl_load_module().
+if HAVE_BROKEN_PERL_LOAD_MODULE
+perl_la_CFLAGS += -Wno-nonnull
+endif
+perl_la_LDFLAGS = -module -avoid-version \
+               $(PERL_LDFLAGS)
+collectd_LDADD += "-dlopen" perl.la
+collectd_DEPENDENCIES += perl.la
+endif
+
+if BUILD_PLUGIN_PINBA
+BUILT_SOURCES += pinba.pb-c.c pinba.pb-c.h
+CLEANFILES += pinba.pb-c.c pinba.pb-c.h
+pkglib_LTLIBRARIES += pinba.la
+pinba_la_SOURCES = pinba.c
+pinba_la_LDFLAGS = -module -avoid-version
+pinba_la_LIBADD = -lprotobuf-c
+collectd_LDADD += "-dlopen" pinba.la
+collectd_DEPENDENCIES += pinba.la
+endif
+
+if BUILD_PLUGIN_PING
+pkglib_LTLIBRARIES += ping.la
+ping_la_SOURCES = ping.c
+ping_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBOPING_CPPFLAGS)
+ping_la_LDFLAGS = -module -avoid-version $(BUILD_WITH_LIBOPING_LDFLAGS)
+ping_la_LIBADD = -loping -lm
+collectd_LDADD += "-dlopen" ping.la
+collectd_DEPENDENCIES += ping.la
+endif
+
+if BUILD_PLUGIN_POSTGRESQL
+pkglib_LTLIBRARIES += postgresql.la
+postgresql_la_SOURCES = postgresql.c \
+                utils_db_query.c utils_db_query.h
+postgresql_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBPQ_CPPFLAGS)
+postgresql_la_LDFLAGS = -module -avoid-version \
+               $(BUILD_WITH_LIBPQ_LDFLAGS)
+postgresql_la_LIBADD = -lpq
+collectd_LDADD += "-dlopen" postgresql.la
+collectd_DEPENDENCIES += postgresql.la
+endif
+
+if BUILD_PLUGIN_POWERDNS
+pkglib_LTLIBRARIES += powerdns.la
+powerdns_la_SOURCES = powerdns.c
+powerdns_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" powerdns.la
+collectd_DEPENDENCIES += powerdns.la
+endif
+
+if BUILD_PLUGIN_PYTHON
+pkglib_LTLIBRARIES += python.la
+python_la_SOURCES = python.c pyconfig.c pyvalues.c cpython.h
+python_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_PYTHON_CPPFLAGS)
+python_la_CFLAGS = $(AM_CFLAGS)
+if COMPILER_IS_GCC
+python_la_CFLAGS += -fno-strict-aliasing -Wno-strict-aliasing
+endif
+python_la_LDFLAGS = -module -avoid-version $(BUILD_WITH_PYTHON_LDFLAGS)
+python_la_LIBADD = $(BUILD_WITH_PYTHON_LIBS)
+collectd_LDADD += "-dlopen" python.la
+collectd_DEPENDENCIES += python.la
+endif
+
+if BUILD_PLUGIN_PROCESSES
+pkglib_LTLIBRARIES += processes.la
+processes_la_SOURCES = processes.c
+processes_la_LDFLAGS = -module -avoid-version
+processes_la_LIBADD =
+collectd_LDADD += "-dlopen" processes.la
+collectd_DEPENDENCIES += processes.la
+if BUILD_WITH_LIBKVM_GETPROCS
+processes_la_LIBADD += -lkvm
+endif
+endif
+
+if BUILD_PLUGIN_PROTOCOLS
+pkglib_LTLIBRARIES += protocols.la
+protocols_la_SOURCES = protocols.c
+protocols_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" protocols.la
+collectd_DEPENDENCIES += protocols.la
+endif
+
+if BUILD_PLUGIN_REDIS
+pkglib_LTLIBRARIES += redis.la
+redis_la_SOURCES = redis.c
+redis_la_LDFLAGS = -module -avoid-version $(BUILD_WITH_LIBCREDIS_LDFLAGS)
+redis_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBCREDIS_CPPFLAGS)
+redis_la_LIBADD = -lcredis
+collectd_LDADD += "-dlopen" redis.la
+collectd_DEPENDENCIES += redis.la
+endif
+
+if BUILD_PLUGIN_ROUTEROS
+pkglib_LTLIBRARIES += routeros.la
+routeros_la_SOURCES = routeros.c
+routeros_la_CPPFLAGS = $(BUILD_WITH_LIBROUTEROS_CPPFLAGS)
+routeros_la_LDFLAGS = -module -avoid-version $(BUILD_WITH_LIBROUTEROS_LDFLAGS)
+routeros_la_LIBADD = -lrouteros
+collectd_LDADD += "-dlopen" routeros.la
+collectd_DEPENDENCIES += routeros.la
+endif
+
+if BUILD_PLUGIN_RRDCACHED
+pkglib_LTLIBRARIES += rrdcached.la
+rrdcached_la_SOURCES = rrdcached.c utils_rrdcreate.c utils_rrdcreate.h
+rrdcached_la_LDFLAGS = -module -avoid-version
+rrdcached_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBRRD_CFLAGS)
+rrdcached_la_LIBADD = $(BUILD_WITH_LIBRRD_LDFLAGS)
+collectd_LDADD += "-dlopen" rrdcached.la
+collectd_DEPENDENCIES += rrdcached.la
+endif
+
+if BUILD_PLUGIN_RRDTOOL
+pkglib_LTLIBRARIES += rrdtool.la
+rrdtool_la_SOURCES = rrdtool.c utils_rrdcreate.c utils_rrdcreate.h
+rrdtool_la_LDFLAGS = -module -avoid-version
+rrdtool_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBRRD_CFLAGS)
+rrdtool_la_LIBADD = $(BUILD_WITH_LIBRRD_LDFLAGS)
+collectd_LDADD += "-dlopen" rrdtool.la
+collectd_DEPENDENCIES += rrdtool.la
+endif
+
+if BUILD_PLUGIN_SENSORS
+pkglib_LTLIBRARIES += sensors.la
+sensors_la_SOURCES = sensors.c
+sensors_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBSENSORS_CFLAGS)
+sensors_la_LDFLAGS = -module -avoid-version $(BUILD_WITH_LIBSENSORS_LDFLAGS)
+sensors_la_LIBADD = -lsensors
+collectd_LDADD += "-dlopen" sensors.la
+collectd_DEPENDENCIES += sensors.la
+endif
+
+if BUILD_PLUGIN_SERIAL
+pkglib_LTLIBRARIES += serial.la
+serial_la_SOURCES = serial.c
+serial_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" serial.la
+collectd_DEPENDENCIES += serial.la
+endif
+
+if BUILD_PLUGIN_SNMP
+pkglib_LTLIBRARIES += snmp.la
+snmp_la_SOURCES = snmp.c
+snmp_la_LDFLAGS = -module -avoid-version
+snmp_la_CFLAGS = $(AM_CFLAGS)
+snmp_la_LIBADD =
+if BUILD_WITH_LIBNETSNMP
+snmp_la_CFLAGS += $(BUILD_WITH_LIBSNMP_CFLAGS)
+snmp_la_LIBADD += $(BUILD_WITH_LIBSNMP_LIBS)
+endif
+if BUILD_WITH_LIBPTHREAD
+snmp_la_LIBADD += -lpthread
+endif
+collectd_LDADD += "-dlopen" snmp.la
+collectd_DEPENDENCIES += snmp.la
+endif
+
+if BUILD_PLUGIN_SWAP
+pkglib_LTLIBRARIES += swap.la
+swap_la_SOURCES = swap.c
+swap_la_CFLAGS = $(AM_CFLAGS)
+swap_la_LDFLAGS = -module -avoid-version
+swap_la_LIBADD =
+collectd_LDADD += "-dlopen" swap.la
+collectd_DEPENDENCIES += swap.la
+if BUILD_WITH_LIBKSTAT
+swap_la_LIBADD += -lkstat
+endif
+if BUILD_WITH_LIBDEVINFO
+swap_la_LIBADD += -ldevinfo
+endif
+if BUILD_WITH_LIBKVM_GETSWAPINFO
+swap_la_LIBADD += -lkvm
+endif
+if BUILD_WITH_LIBSTATGRAB
+swap_la_CFLAGS += $(BUILD_WITH_LIBSTATGRAB_CFLAGS)
+swap_la_LIBADD += $(BUILD_WITH_LIBSTATGRAB_LDFLAGS)
+endif
+if BUILD_WITH_PERFSTAT
+swap_la_LIBADD += -lperfstat
+endif
+
+endif
+
+if BUILD_PLUGIN_SYSLOG
+pkglib_LTLIBRARIES += syslog.la
+syslog_la_SOURCES = syslog.c
+syslog_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" syslog.la
+collectd_DEPENDENCIES += syslog.la
+endif
+
+if BUILD_PLUGIN_TABLE
+pkglib_LTLIBRARIES += table.la
+table_la_SOURCES = table.c
+table_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" table.la
+collectd_DEPENDENCIES += table.la
+endif
+
+if BUILD_PLUGIN_TAIL
+pkglib_LTLIBRARIES += tail.la
+tail_la_SOURCES = tail.c
+tail_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" tail.la
+collectd_DEPENDENCIES += tail.la
+endif
+
+if BUILD_PLUGIN_TAPE
+pkglib_LTLIBRARIES += tape.la
+tape_la_SOURCES = tape.c
+tape_la_LDFLAGS = -module -avoid-version
+tape_la_LIBADD = -lkstat -ldevinfo
+collectd_LDADD += "-dlopen" tape.la
+collectd_DEPENDENCIES += tape.la
+endif
+
+if BUILD_PLUGIN_TARGET_NOTIFICATION
+pkglib_LTLIBRARIES += target_notification.la
+target_notification_la_SOURCES = target_notification.c
+target_notification_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" target_notification.la
+collectd_DEPENDENCIES += target_notification.la
+endif
+
+if BUILD_PLUGIN_TARGET_REPLACE
+pkglib_LTLIBRARIES += target_replace.la
+target_replace_la_SOURCES = target_replace.c
+target_replace_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" target_replace.la
+collectd_DEPENDENCIES += target_replace.la
+endif
+
+if BUILD_PLUGIN_TARGET_SCALE
+pkglib_LTLIBRARIES += target_scale.la
+target_scale_la_SOURCES = target_scale.c
+target_scale_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" target_scale.la
+collectd_DEPENDENCIES += target_scale.la
+endif
+
+if BUILD_PLUGIN_TARGET_SET
+pkglib_LTLIBRARIES += target_set.la
+target_set_la_SOURCES = target_set.c
+target_set_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" target_set.la
+collectd_DEPENDENCIES += target_set.la
+endif
+
+if BUILD_PLUGIN_TARGET_V5UPGRADE
+pkglib_LTLIBRARIES += target_v5upgrade.la
+target_v5upgrade_la_SOURCES = target_v5upgrade.c
+target_v5upgrade_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" target_v5upgrade.la
+collectd_DEPENDENCIES += target_v5upgrade.la
+endif
+
+if BUILD_PLUGIN_TCPCONNS
+pkglib_LTLIBRARIES += tcpconns.la
+tcpconns_la_SOURCES = tcpconns.c
+tcpconns_la_LDFLAGS = -module -avoid-version
+tcpconns_la_LIBADD =
+collectd_LDADD += "-dlopen" tcpconns.la
+collectd_DEPENDENCIES += tcpconns.la
+if BUILD_WITH_LIBKVM_NLIST
+tcpconns_la_LIBADD += -lkvm
+endif
+endif
+
+if BUILD_PLUGIN_TEAMSPEAK2
+pkglib_LTLIBRARIES += teamspeak2.la
+teamspeak2_la_SOURCES = teamspeak2.c
+teamspeak2_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" teamspeak2.la
+collectd_DEPENDENCIES += teamspeak2.la
+endif
+
+if BUILD_PLUGIN_TED
+pkglib_LTLIBRARIES += ted.la
+ted_la_SOURCES = ted.c
+ted_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" ted.la
+collectd_DEPENDENCIES += ted.la
+endif
+
+if BUILD_PLUGIN_THERMAL
+pkglib_LTLIBRARIES += thermal.la
+thermal_la_SOURCES = thermal.c
+thermal_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" thermal.la
+collectd_DEPENDENCIES += thermal.la
+endif
+
+if BUILD_PLUGIN_THRESHOLD
+pkglib_LTLIBRARIES += threshold.la
+threshold_la_SOURCES = threshold.c
+threshold_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" threshold.la
+collectd_DEPENDENCIES += threshold.la
+endif
+
+if BUILD_PLUGIN_TOKYOTYRANT
+pkglib_LTLIBRARIES += tokyotyrant.la
+tokyotyrant_la_SOURCES = tokyotyrant.c
+tokyotyrant_la_CPPFLAGS  = $(AM_CPPFLAGS) $(BUILD_WITH_LIBTOKYOTYRANT_CPPFLAGS)
+tokyotyrant_la_LDFLAGS = -module -avoid-version $(BUILD_WITH_LIBTOKYOTYRANT_LDFLAGS)
+tokyotyrant_la_LIBADD  = $(BUILD_WITH_LIBTOKYOTYRANT_LIBS)
+if BUILD_WITH_LIBSOCKET
+tokyotyrant_la_LIBADD += -lsocket
+endif
+collectd_LDADD += "-dlopen" tokyotyrant.la
+collectd_DEPENDENCIES += tokyotyrant.la
+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_listval.h utils_cmd_listval.c \
+                     utils_cmd_putval.h utils_cmd_putval.c \
+                     utils_cmd_putnotif.h utils_cmd_putnotif.c
+unixsock_la_LDFLAGS = -module -avoid-version
+unixsock_la_LIBADD = -lpthread
+collectd_LDADD += "-dlopen" unixsock.la
+collectd_DEPENDENCIES += unixsock.la
+endif
+
+if BUILD_PLUGIN_UPTIME
+pkglib_LTLIBRARIES += uptime.la
+uptime_la_SOURCES = uptime.c
+uptime_la_CFLAGS = $(AM_CFLAGS)
+uptime_la_LDFLAGS = -module -avoid-version
+uptime_la_LIBADD =
+if BUILD_WITH_LIBKSTAT
+uptime_la_LIBADD += -lkstat
+endif
+collectd_LDADD += "-dlopen" uptime.la
+collectd_DEPENDENCIES += uptime.la
+endif
+
+if BUILD_PLUGIN_USERS
+pkglib_LTLIBRARIES += users.la
+users_la_SOURCES = users.c
+users_la_CFLAGS = $(AM_CFLAGS)
+users_la_LDFLAGS = -module -avoid-version
+users_la_LIBADD =
+if BUILD_WITH_LIBSTATGRAB
+users_la_CFLAGS += $(BUILD_WITH_LIBSTATGRAB_CFLAGS)
+users_la_LIBADD += $(BUILD_WITH_LIBSTATGRAB_LDFLAGS)
+endif
+collectd_LDADD += "-dlopen" users.la
+collectd_DEPENDENCIES += users.la
+endif
+
+if BUILD_PLUGIN_UUID
+pkglib_LTLIBRARIES += uuid.la
+uuid_la_SOURCES = uuid.c
+uuid_la_CFLAGS  = $(AM_CFLAGS) $(BUILD_WITH_LIBHAL_CFLAGS)
+uuid_la_LIBADD  = $(BUILD_WITH_LIBHAL_LIBS)
+uuid_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" uuid.la
+collectd_DEPENDENCIES += uuid.la
+endif
+
+if BUILD_PLUGIN_VARNISH
+pkglib_LTLIBRARIES += varnish.la
+varnish_la_SOURCES = varnish.c
+varnish_la_LDFLAGS = -module -avoid-version
+varnish_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBVARNISH_CFLAGS)
+varnish_la_LIBADD = $(BUILD_WITH_LIBVARNISH_LIBS)
+collectd_LDADD += "-dlopen" varnish.la
+collectd_DEPENDENCIES += varnish.la
+endif
+
+if BUILD_PLUGIN_VMEM
+pkglib_LTLIBRARIES += vmem.la
+vmem_la_SOURCES = vmem.c
+vmem_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" vmem.la
+collectd_DEPENDENCIES += vmem.la
+endif
+
+if BUILD_PLUGIN_VSERVER
+pkglib_LTLIBRARIES += vserver.la
+vserver_la_SOURCES = vserver.c
+vserver_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" vserver.la
+collectd_DEPENDENCIES += vserver.la
+endif
+
+if BUILD_PLUGIN_WIRELESS
+pkglib_LTLIBRARIES += wireless.la
+wireless_la_SOURCES = wireless.c
+wireless_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" wireless.la
+collectd_DEPENDENCIES += wireless.la
+endif
+
+if BUILD_PLUGIN_WRITE_HTTP
+pkglib_LTLIBRARIES += write_http.la
+write_http_la_SOURCES = write_http.c \
+                       utils_format_json.c utils_format_json.h
+write_http_la_LDFLAGS = -module -avoid-version
+write_http_la_CFLAGS = $(AM_CFLAGS)
+write_http_la_LIBADD =
+collectd_LDADD += "-dlopen" write_http.la
+if BUILD_WITH_LIBCURL
+write_http_la_CFLAGS += $(BUILD_WITH_LIBCURL_CFLAGS)
+write_http_la_LIBADD += $(BUILD_WITH_LIBCURL_LIBS)
+endif
+collectd_DEPENDENCIES += write_http.la
+endif
+
+if BUILD_PLUGIN_WRITE_MONGODB
+pkglib_LTLIBRARIES += write_mongodb.la
+write_mongodb_la_SOURCES = write_mongodb.c
+write_mongodb_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBMONGOC_CPPFLAGS)
+write_mongodb_la_LDFLAGS = -module -avoid-version $(BUILD_WITH_LIBMONGOC_LDFLAGS)
+write_mongodb_la_LIBADD = -lmongoc
+collectd_LDADD += "-dlopen" write_mongodb.la
+collectd_DEPENDENCIES += write_mongodb.la
+endif
+
+if BUILD_PLUGIN_WRITE_REDIS
+pkglib_LTLIBRARIES += write_redis.la
+write_redis_la_SOURCES = write_redis.c
+write_redis_la_LDFLAGS = -module -avoid-version $(BUILD_WITH_LIBCREDIS_LDFLAGS)
+write_redis_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBCREDIS_CPPFLAGS)
+write_redis_la_LIBADD = -lcredis
+collectd_LDADD += "-dlopen" write_redis.la
+collectd_DEPENDENCIES += write_redis.la
+endif
+
+if BUILD_PLUGIN_XMMS
+pkglib_LTLIBRARIES += xmms.la
+xmms_la_SOURCES = xmms.c
+xmms_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBXMMS_CFLAGS)
+xmms_la_LDFLAGS = -module -avoid-version
+xmms_la_LIBADD = $(BUILD_WITH_LIBXMMS_LIBS)
+collectd_LDADD += "-dlopen" xmms.la
+collectd_DEPENDENCIES += xmms.la
+endif
+
+if BUILD_PLUGIN_ZFS_ARC
+pkglib_LTLIBRARIES += zfs_arc.la
+zfs_arc_la_SOURCES = zfs_arc.c
+zfs_arc_la_CFLAGS = $(AM_CFLAGS)
+zfs_arc_la_LDFLAGS = -module -avoid-version
+zfs_arc_la_LIBADD = -lkstat
+collectd_LDADD += "-dlopen" zfs_arc.la
+collectd_DEPENDENCIES += zfs_arc.la
+endif
+
+dist_man_MANS = collectd.1 \
+               collectd.conf.5 \
+               collectd-email.5 \
+               collectd-exec.5 \
+               collectdctl.1 \
+               collectd-java.5 \
+               collectdmon.1 \
+               collectd-nagios.1 \
+               collectd-perl.5 \
+               collectd-python.5 \
+               collectd-snmp.5 \
+               collectd-threshold.5 \
+               collectd-unixsock.5 \
+               types.db.5
+
+#collectd_1_SOURCES = collectd.pod
+
+EXTRA_DIST = types.db pinba.proto
+
+EXTRA_DIST +=   collectd.conf.pod \
+               collectd-email.pod \
+               collectd-exec.pod \
+               collectdctl.pod \
+               collectd-java.pod \
+               collectdmon.pod \
+               collectd-nagios.pod \
+               collectd-perl.pod \
+               collectd-python.pod \
+               collectd.pod \
+               collectd-snmp.pod \
+               collectd-threshold.pod \
+               collectd-unixsock.pod \
+               postgresql_default.conf \
+               types.db.pod
+
+.pod.1:
+       pod2man --release=$(VERSION) --center=$(PACKAGE) $< \
+               >.pod2man.tmp.$$$$ 2>/dev/null && mv -f .pod2man.tmp.$$$$ $@ || true
+       @if grep '\<POD ERRORS\>' $@ >/dev/null 2>&1; \
+       then \
+               echo "$@ has some POD errors!"; false; \
+       fi
+
+.pod.5:
+       pod2man --section=5 --release=$(VERSION) --center=$(PACKAGE) $< \
+               >.pod2man.tmp.$$$$ 2>/dev/null && mv -f .pod2man.tmp.$$$$ $@ || true
+       @if grep '\<POD ERRORS\>' $@ >/dev/null 2>&1; \
+       then \
+               echo "$@ has some POD errors!"; false; \
+       fi
+
+pinba.pb-c.c pinba.pb-c.h: pinba.proto
+       protoc-c --c_out $(builddir) pinba.proto
+
+install-exec-hook:
+       $(mkinstalldirs) $(DESTDIR)$(sysconfdir)
+       if test -e $(DESTDIR)$(sysconfdir)/collectd.conf; \
+       then \
+               $(INSTALL) -m 0640 collectd.conf $(DESTDIR)$(sysconfdir)/collectd.conf.pkg-orig; \
+       else \
+               $(INSTALL) -m 0640 collectd.conf $(DESTDIR)$(sysconfdir)/collectd.conf; \
+       fi; \
+       $(mkinstalldirs) $(DESTDIR)$(pkgdatadir)
+       $(INSTALL) -m 0644 $(srcdir)/types.db $(DESTDIR)$(pkgdatadir)/types.db;
+       $(INSTALL) -m 0644 $(srcdir)/postgresql_default.conf \
+               $(DESTDIR)$(pkgdatadir)/postgresql_default.conf;
diff --git a/src/amqp.c b/src/amqp.c
new file mode 100644 (file)
index 0000000..adf4792
--- /dev/null
@@ -0,0 +1,941 @@
+/**
+ * collectd - src/amqp.c
+ * Copyright (C) 2009  Sebastien Pahl
+ * Copyright (C) 2010  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:
+ *   Sebastien Pahl <sebastien.pahl at dotcloud.com>
+ *   Florian Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "utils_cmd_putval.h"
+#include "utils_format_json.h"
+
+#include <pthread.h>
+
+#include <amqp.h>
+#include <amqp_framing.h>
+
+/* Defines for the delivery mode. I have no idea why they're not defined by the
+ * library.. */
+#define CAMQP_DM_VOLATILE   1
+#define CAMQP_DM_PERSISTENT 2
+
+#define CAMQP_FORMAT_COMMAND 1
+#define CAMQP_FORMAT_JSON    2
+
+#define CAMQP_CHANNEL 1
+
+/*
+ * Data types
+ */
+struct camqp_config_s
+{
+    _Bool   publish;
+    char   *name;
+
+    char   *host;
+    int     port;
+    char   *vhost;
+    char   *user;
+    char   *password;
+
+    char   *exchange;
+    char   *routing_key;
+
+    /* publish only */
+    uint8_t delivery_mode;
+    _Bool   store_rates;
+    int     format;
+
+    /* subscribe only */
+    char   *exchange_type;
+    char   *queue;
+
+    amqp_connection_state_t connection;
+    pthread_mutex_t lock;
+};
+typedef struct camqp_config_s camqp_config_t;
+
+/*
+ * Global variables
+ */
+static const char *def_host       = "localhost";
+static const char *def_vhost      = "/";
+static const char *def_user       = "guest";
+static const char *def_password   = "guest";
+static const char *def_exchange   = "amq.fanout";
+
+static pthread_t *subscriber_threads     = NULL;
+static size_t     subscriber_threads_num = 0;
+static _Bool      subscriber_threads_running = 1;
+
+#define CONF(c,f) (((c)->f != NULL) ? (c)->f : def_##f)
+
+/*
+ * Functions
+ */
+static void camqp_close_connection (camqp_config_t *conf) /* {{{ */
+{
+    int sockfd;
+
+    if ((conf == NULL) || (conf->connection == NULL))
+        return;
+
+    sockfd = amqp_get_sockfd (conf->connection);
+    amqp_channel_close (conf->connection, CAMQP_CHANNEL, AMQP_REPLY_SUCCESS);
+    amqp_connection_close (conf->connection, AMQP_REPLY_SUCCESS);
+    amqp_destroy_connection (conf->connection);
+    close (sockfd);
+    conf->connection = NULL;
+} /* }}} void camqp_close_connection */
+
+static void camqp_config_free (void *ptr) /* {{{ */
+{
+    camqp_config_t *conf = ptr;
+
+    if (conf == NULL)
+        return;
+
+    camqp_close_connection (conf);
+
+    sfree (conf->name);
+    sfree (conf->host);
+    sfree (conf->vhost);
+    sfree (conf->user);
+    sfree (conf->password);
+    sfree (conf->exchange);
+    sfree (conf->exchange_type);
+    sfree (conf->queue);
+    sfree (conf->routing_key);
+
+    sfree (conf);
+} /* }}} void camqp_config_free */
+
+static char *camqp_bytes_cstring (amqp_bytes_t *in) /* {{{ */
+{
+    char *ret;
+
+    if ((in == NULL) || (in->bytes == NULL))
+        return (NULL);
+
+    ret = malloc (in->len + 1);
+    if (ret == NULL)
+        return (NULL);
+
+    memcpy (ret, in->bytes, in->len);
+    ret[in->len] = 0;
+
+    return (ret);
+} /* }}} char *camqp_bytes_cstring */
+
+static _Bool camqp_is_error (camqp_config_t *conf) /* {{{ */
+{
+    amqp_rpc_reply_t r;
+
+    r = amqp_get_rpc_reply (conf->connection);
+    if (r.reply_type == AMQP_RESPONSE_NORMAL)
+        return (0);
+
+    return (1);
+} /* }}} _Bool camqp_is_error */
+
+static char *camqp_strerror (camqp_config_t *conf, /* {{{ */
+        char *buffer, size_t buffer_size)
+{
+    amqp_rpc_reply_t r;
+
+    r = amqp_get_rpc_reply (conf->connection);
+    switch (r.reply_type)
+    {
+        case AMQP_RESPONSE_NORMAL:
+            sstrncpy (buffer, "Success", sizeof (buffer));
+            break;
+
+        case AMQP_RESPONSE_NONE:
+            sstrncpy (buffer, "Missing RPC reply type", sizeof (buffer));
+            break;
+
+        case AMQP_RESPONSE_LIBRARY_EXCEPTION:
+            if (r.library_errno)
+                return (sstrerror (r.library_errno, buffer, buffer_size));
+            else
+                sstrncpy (buffer, "End of stream", sizeof (buffer));
+            break;
+
+        case AMQP_RESPONSE_SERVER_EXCEPTION:
+            if (r.reply.id == AMQP_CONNECTION_CLOSE_METHOD)
+            {
+                amqp_connection_close_t *m = r.reply.decoded;
+                char *tmp = camqp_bytes_cstring (&m->reply_text);
+                ssnprintf (buffer, buffer_size, "Server connection error %d: %s",
+                        m->reply_code, tmp);
+                sfree (tmp);
+            }
+            else if (r.reply.id == AMQP_CHANNEL_CLOSE_METHOD)
+            {
+                amqp_channel_close_t *m = r.reply.decoded;
+                char *tmp = camqp_bytes_cstring (&m->reply_text);
+                ssnprintf (buffer, buffer_size, "Server channel error %d: %s",
+                        m->reply_code, tmp);
+                sfree (tmp);
+            }
+            else
+            {
+                ssnprintf (buffer, buffer_size, "Server error method %#"PRIx32,
+                        r.reply.id);
+            }
+            break;
+
+        default:
+            ssnprintf (buffer, buffer_size, "Unknown reply type %i",
+                    (int) r.reply_type);
+    }
+
+    return (buffer);
+} /* }}} char *camqp_strerror */
+
+static int camqp_create_exchange (camqp_config_t *conf) /* {{{ */
+{
+    amqp_exchange_declare_ok_t *ed_ret;
+
+    if (conf->exchange_type == NULL)
+        return (0);
+
+    ed_ret = amqp_exchange_declare (conf->connection,
+            /* channel     = */ CAMQP_CHANNEL,
+            /* exchange    = */ amqp_cstring_bytes (conf->exchange),
+            /* type        = */ amqp_cstring_bytes (conf->exchange_type),
+            /* passive     = */ 0,
+            /* durable     = */ 0,
+            /* auto_delete = */ 1,
+            /* arguments   = */ AMQP_EMPTY_TABLE);
+    if ((ed_ret == NULL) && camqp_is_error (conf))
+    {
+        char errbuf[1024];
+        ERROR ("amqp plugin: amqp_exchange_declare failed: %s",
+                camqp_strerror (conf, errbuf, sizeof (errbuf)));
+        camqp_close_connection (conf);
+        return (-1);
+    }
+
+    INFO ("amqp plugin: Successfully created exchange \"%s\" "
+            "with type \"%s\".",
+            conf->exchange, conf->exchange_type);
+
+    return (0);
+} /* }}} int camqp_create_exchange */
+
+static int camqp_setup_queue (camqp_config_t *conf) /* {{{ */
+{
+    amqp_queue_declare_ok_t *qd_ret;
+    amqp_basic_consume_ok_t *cm_ret;
+
+    qd_ret = amqp_queue_declare (conf->connection,
+            /* channel     = */ CAMQP_CHANNEL,
+            /* queue       = */ (conf->queue != NULL)
+            ? amqp_cstring_bytes (conf->queue)
+            : AMQP_EMPTY_BYTES,
+            /* passive     = */ 0,
+            /* durable     = */ 0,
+            /* exclusive   = */ 0,
+            /* auto_delete = */ 1,
+            /* arguments   = */ AMQP_EMPTY_TABLE);
+    if (qd_ret == NULL)
+    {
+        ERROR ("amqp plugin: amqp_queue_declare failed.");
+        camqp_close_connection (conf);
+        return (-1);
+    }
+
+    if (conf->queue == NULL)
+    {
+        conf->queue = camqp_bytes_cstring (&qd_ret->queue);
+        if (conf->queue == NULL)
+        {
+            ERROR ("amqp plugin: camqp_bytes_cstring failed.");
+            camqp_close_connection (conf);
+            return (-1);
+        }
+
+        INFO ("amqp plugin: Created queue \"%s\".", conf->queue);
+    }
+    DEBUG ("amqp plugin: Successfully created queue \"%s\".", conf->queue);
+
+    /* bind to an exchange */
+    if (conf->exchange != NULL)
+    {
+        amqp_queue_bind_ok_t *qb_ret;
+
+        assert (conf->queue != NULL);
+        qb_ret = amqp_queue_bind (conf->connection,
+                /* channel     = */ CAMQP_CHANNEL,
+                /* queue       = */ amqp_cstring_bytes (conf->queue),
+                /* exchange    = */ amqp_cstring_bytes (conf->exchange),
+                /* routing_key = */ (conf->routing_key != NULL)
+                ? amqp_cstring_bytes (conf->routing_key)
+                : AMQP_EMPTY_BYTES,
+                /* arguments   = */ AMQP_EMPTY_TABLE);
+        if ((qb_ret == NULL) && camqp_is_error (conf))
+        {
+            char errbuf[1024];
+            ERROR ("amqp plugin: amqp_queue_bind failed: %s",
+                    camqp_strerror (conf, errbuf, sizeof (errbuf)));
+            camqp_close_connection (conf);
+            return (-1);
+        }
+
+        DEBUG ("amqp plugin: Successfully bound queue \"%s\" to exchange \"%s\".",
+                conf->queue, conf->exchange);
+    } /* if (conf->exchange != NULL) */
+
+    cm_ret = amqp_basic_consume (conf->connection,
+            /* channel      = */ CAMQP_CHANNEL,
+            /* queue        = */ amqp_cstring_bytes (conf->queue),
+            /* consumer_tag = */ AMQP_EMPTY_BYTES,
+            /* no_local     = */ 0,
+            /* no_ack       = */ 1,
+            /* exclusive    = */ 0);
+    if ((cm_ret == NULL) && camqp_is_error (conf))
+    {
+        char errbuf[1024];
+        ERROR ("amqp plugin: amqp_basic_consume failed: %s",
+                    camqp_strerror (conf, errbuf, sizeof (errbuf)));
+        camqp_close_connection (conf);
+        return (-1);
+    }
+
+    return (0);
+} /* }}} int camqp_setup_queue */
+
+static int camqp_connect (camqp_config_t *conf) /* {{{ */
+{
+    amqp_rpc_reply_t reply;
+    int sockfd;
+    int status;
+
+    if (conf->connection != NULL)
+        return (0);
+
+    conf->connection = amqp_new_connection ();
+    if (conf->connection == NULL)
+    {
+        ERROR ("amqp plugin: amqp_new_connection failed.");
+        return (ENOMEM);
+    }
+
+    sockfd = amqp_open_socket (CONF(conf, host), conf->port);
+    if (sockfd < 0)
+    {
+        char errbuf[1024];
+        status = (-1) * sockfd;
+        ERROR ("amqp plugin: amqp_open_socket failed: %s",
+                sstrerror (status, errbuf, sizeof (errbuf)));
+        amqp_destroy_connection (conf->connection);
+        conf->connection = NULL;
+        return (status);
+    }
+    amqp_set_sockfd (conf->connection, sockfd);
+
+    reply = amqp_login (conf->connection, CONF(conf, vhost),
+            /* channel max = */      0,
+            /* frame max   = */ 131072,
+            /* heartbeat   = */      0,
+            /* authentication = */ AMQP_SASL_METHOD_PLAIN,
+            CONF(conf, user), CONF(conf, password));
+    if (reply.reply_type != AMQP_RESPONSE_NORMAL)
+    {
+        ERROR ("amqp plugin: amqp_login (vhost = %s, user = %s) failed.",
+                CONF(conf, vhost), CONF(conf, user));
+        amqp_destroy_connection (conf->connection);
+        close (sockfd);
+        conf->connection = NULL;
+        return (1);
+    }
+
+    amqp_channel_open (conf->connection, /* channel = */ 1);
+    /* FIXME: Is checking "reply.reply_type" really correct here? How does
+     * it get set? --octo */
+    if (reply.reply_type != AMQP_RESPONSE_NORMAL)
+    {
+        ERROR ("amqp plugin: amqp_channel_open failed.");
+        amqp_connection_close (conf->connection, AMQP_REPLY_SUCCESS);
+        amqp_destroy_connection (conf->connection);
+        close(sockfd);
+        conf->connection = NULL;
+        return (1);
+    }
+
+    INFO ("amqp plugin: Successfully opened connection to vhost \"%s\" "
+            "on %s:%i.", CONF(conf, vhost), CONF(conf, host), conf->port);
+
+    status = camqp_create_exchange (conf);
+    if (status != 0)
+        return (status);
+
+    if (!conf->publish)
+        return (camqp_setup_queue (conf));
+    return (0);
+} /* }}} int camqp_connect */
+
+static int camqp_shutdown (void) /* {{{ */
+{
+    size_t i;
+
+    DEBUG ("amqp plugin: Shutting down %zu subscriber threads.",
+            subscriber_threads_num);
+
+    subscriber_threads_running = 0;
+    for (i = 0; i < subscriber_threads_num; i++)
+    {
+        /* FIXME: Sending a signal is not very elegant here. Maybe find out how
+         * to use a timeout in the thread and check for the variable in regular
+         * intervals. */
+        pthread_kill (subscriber_threads[i], SIGTERM);
+        pthread_join (subscriber_threads[i], /* retval = */ NULL);
+    }
+
+    subscriber_threads_num = 0;
+    sfree (subscriber_threads);
+
+    DEBUG ("amqp plugin: All subscriber threads exited.");
+
+    return (0);
+} /* }}} int camqp_shutdown */
+
+/*
+ * Subscribing code
+ */
+static int camqp_read_body (camqp_config_t *conf, /* {{{ */
+        size_t body_size, const char *content_type)
+{
+    char body[body_size + 1];
+    char *body_ptr;
+    size_t received;
+    amqp_frame_t frame;
+    int status;
+
+    memset (body, 0, sizeof (body));
+    body_ptr = &body[0];
+    received = 0;
+
+    while (received < body_size)
+    {
+        status = amqp_simple_wait_frame (conf->connection, &frame);
+        if (status < 0)
+        {
+            char errbuf[1024];
+            status = (-1) * status;
+            ERROR ("amqp plugin: amqp_simple_wait_frame failed: %s",
+                    sstrerror (status, errbuf, sizeof (errbuf)));
+            camqp_close_connection (conf);
+            return (status);
+        }
+
+        if (frame.frame_type != AMQP_FRAME_BODY)
+        {
+            NOTICE ("amqp plugin: Unexpected frame type: %#"PRIx8,
+                    frame.frame_type);
+            return (-1);
+        }
+
+        if ((body_size - received) < frame.payload.body_fragment.len)
+        {
+            WARNING ("amqp plugin: Body is larger than indicated by header.");
+            return (-1);
+        }
+
+        memcpy (body_ptr, frame.payload.body_fragment.bytes,
+                frame.payload.body_fragment.len);
+        body_ptr += frame.payload.body_fragment.len;
+        received += frame.payload.body_fragment.len;
+    } /* while (received < body_size) */
+
+    if (strcasecmp ("text/collectd", content_type) == 0)
+    {
+        status = handle_putval (stderr, body);
+        if (status != 0)
+            ERROR ("amqp plugin: 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 "
+                "been implemented yet. FIXME!");
+        return (0);
+    }
+    else
+    {
+        ERROR ("amqp plugin: camqp_read_body: Unknown content type \"%s\".",
+                content_type);
+        return (EINVAL);
+    }
+
+    /* not reached */
+    return (0);
+} /* }}} int camqp_read_body */
+
+static int camqp_read_header (camqp_config_t *conf) /* {{{ */
+{
+    int status;
+    amqp_frame_t frame;
+    amqp_basic_properties_t *properties;
+    char *content_type;
+
+    status = amqp_simple_wait_frame (conf->connection, &frame);
+    if (status < 0)
+    {
+        char errbuf[1024];
+        status = (-1) * status;
+        ERROR ("amqp plugin: amqp_simple_wait_frame failed: %s",
+                    sstrerror (status, errbuf, sizeof (errbuf)));
+        camqp_close_connection (conf);
+        return (status);
+    }
+
+    if (frame.frame_type != AMQP_FRAME_HEADER)
+    {
+        NOTICE ("amqp plugin: Unexpected frame type: %#"PRIx8,
+                frame.frame_type);
+        return (-1);
+    }
+
+    properties = frame.payload.properties.decoded;
+    content_type = camqp_bytes_cstring (&properties->content_type);
+    if (content_type == NULL)
+    {
+        ERROR ("amqp plugin: Unable to determine content type.");
+        return (-1);
+    }
+
+    status = camqp_read_body (conf,
+            (size_t) frame.payload.properties.body_size,
+            content_type);
+
+    sfree (content_type);
+    return (status);
+} /* }}} int camqp_read_header */
+
+static void *camqp_subscribe_thread (void *user_data) /* {{{ */
+{
+    camqp_config_t *conf = user_data;
+    int status;
+
+    while (subscriber_threads_running)
+    {
+        amqp_frame_t frame;
+
+        status = camqp_connect (conf);
+        if (status != 0)
+        {
+            ERROR ("amqp plugin: camqp_connect failed. "
+                    "Will sleep for %.3f seconds.",
+                    CDTIME_T_TO_DOUBLE (interval_g));
+            sleep (interval_g);
+            continue;
+        }
+
+        status = amqp_simple_wait_frame (conf->connection, &frame);
+        if (status < 0)
+        {
+            ERROR ("amqp plugin: amqp_simple_wait_frame failed. "
+                    "Will sleep for %.3f seconds.",
+                    CDTIME_T_TO_DOUBLE (interval_g));
+            camqp_close_connection (conf);
+            sleep (interval_g);
+            continue;
+        }
+
+        if (frame.frame_type != AMQP_FRAME_METHOD)
+        {
+            DEBUG ("amqp plugin: Unexpected frame type: %#"PRIx8,
+                    frame.frame_type);
+            continue;
+        }
+
+        if (frame.payload.method.id != AMQP_BASIC_DELIVER_METHOD)
+        {
+            DEBUG ("amqp plugin: Unexpected method id: %#"PRIx32,
+                    frame.payload.method.id);
+            continue;
+        }
+
+        status = camqp_read_header (conf);
+
+        amqp_maybe_release_buffers (conf->connection);
+    } /* while (subscriber_threads_running) */
+
+    camqp_config_free (conf);
+    pthread_exit (NULL);
+} /* }}} void *camqp_subscribe_thread */
+
+static int camqp_subscribe_init (camqp_config_t *conf) /* {{{ */
+{
+    int status;
+    pthread_t *tmp;
+
+    tmp = realloc (subscriber_threads,
+            sizeof (*subscriber_threads) * (subscriber_threads_num + 1));
+    if (tmp == NULL)
+    {
+        ERROR ("amqp plugin: realloc failed.");
+        camqp_config_free (conf);
+        return (ENOMEM);
+    }
+    subscriber_threads = tmp;
+    tmp = subscriber_threads + subscriber_threads_num;
+    memset (tmp, 0, sizeof (*tmp));
+
+    status = pthread_create (tmp, /* attr = */ NULL,
+            camqp_subscribe_thread, conf);
+    if (status != 0)
+    {
+        char errbuf[1024];
+        ERROR ("amqp plugin: pthread_create failed: %s",
+                sstrerror (status, errbuf, sizeof (errbuf)));
+        camqp_config_free (conf);
+        return (status);
+    }
+
+    subscriber_threads_num++;
+
+    return (0);
+} /* }}} int camqp_subscribe_init */
+
+/*
+ * Publishing code
+ */
+/* XXX: You must hold "conf->lock" when calling this function! */
+static int camqp_write_locked (camqp_config_t *conf, /* {{{ */
+        const char *buffer, const char *routing_key)
+{
+    amqp_basic_properties_t props;
+    int status;
+
+    status = camqp_connect (conf);
+    if (status != 0)
+        return (status);
+
+    memset (&props, 0, sizeof (props));
+    props._flags = AMQP_BASIC_CONTENT_TYPE_FLAG
+        | AMQP_BASIC_DELIVERY_MODE_FLAG
+        | AMQP_BASIC_APP_ID_FLAG;
+    if (conf->format == CAMQP_FORMAT_COMMAND)
+        props.content_type = amqp_cstring_bytes("text/collectd");
+    else if (conf->format == CAMQP_FORMAT_JSON)
+        props.content_type = amqp_cstring_bytes("application/json");
+    else
+        assert (23 == 42);
+    props.delivery_mode = conf->delivery_mode;
+    props.app_id = amqp_cstring_bytes("collectd");
+
+    status = amqp_basic_publish(conf->connection,
+                /* channel = */ 1,
+                amqp_cstring_bytes(CONF(conf, exchange)),
+                amqp_cstring_bytes (routing_key),
+                /* mandatory = */ 0,
+                /* immediate = */ 0,
+                &props,
+                amqp_cstring_bytes(buffer));
+    if (status != 0)
+    {
+        ERROR ("amqp plugin: amqp_basic_publish failed with status %i.",
+                status);
+        camqp_close_connection (conf);
+    }
+
+    return (status);
+} /* }}} int camqp_write_locked */
+
+static int camqp_write (const data_set_t *ds, const value_list_t *vl, /* {{{ */
+        user_data_t *user_data)
+{
+    camqp_config_t *conf = user_data->data;
+    char routing_key[6 * DATA_MAX_NAME_LEN];
+    char buffer[4096];
+    int status;
+
+    if ((ds == NULL) || (vl == NULL) || (conf == NULL))
+        return (EINVAL);
+
+    memset (buffer, 0, sizeof (buffer));
+
+    if (conf->routing_key != NULL)
+    {
+        sstrncpy (routing_key, conf->routing_key, sizeof (routing_key));
+    }
+    else
+    {
+        size_t i;
+        ssnprintf (routing_key, sizeof (routing_key), "collectd/%s/%s/%s/%s/%s",
+                vl->host,
+                vl->plugin, vl->plugin_instance,
+                vl->type, vl->type_instance);
+
+        /* Switch slashes (the only character forbidden by collectd) and dots
+         * (the separation character used by AMQP). */
+        for (i = 0; routing_key[i] != 0; i++)
+        {
+            if (routing_key[i] == '.')
+                routing_key[i] = '/';
+            else if (routing_key[i] == '/')
+                routing_key[i] = '.';
+        }
+    }
+
+    if (conf->format == CAMQP_FORMAT_COMMAND)
+    {
+        status = create_putval (buffer, sizeof (buffer), ds, vl);
+        if (status != 0)
+        {
+            ERROR ("amqp plugin: create_putval failed with status %i.",
+                    status);
+            return (status);
+        }
+    }
+    else if (conf->format == CAMQP_FORMAT_JSON)
+    {
+        size_t bfree = sizeof (buffer);
+        size_t bfill = 0;
+
+        format_json_initialize (buffer, &bfill, &bfree);
+        format_json_value_list (buffer, &bfill, &bfree, ds, vl, conf->store_rates);
+        format_json_finalize (buffer, &bfill, &bfree);
+    }
+    else
+    {
+        ERROR ("amqp plugin: Invalid format (%i).", conf->format);
+        return (-1);
+    }
+
+    pthread_mutex_lock (&conf->lock);
+    status = camqp_write_locked (conf, buffer, routing_key);
+    pthread_mutex_unlock (&conf->lock);
+
+    return (status);
+} /* }}} int camqp_write */
+
+/*
+ * Config handling
+ */
+static int camqp_config_set_format (oconfig_item_t *ci, /* {{{ */
+        camqp_config_t *conf)
+{
+    char *string;
+    int status;
+
+    string = NULL;
+    status = cf_util_get_string (ci, &string);
+    if (status != 0)
+        return (status);
+
+    assert (string != NULL);
+    if (strcasecmp ("Command", string) == 0)
+        conf->format = CAMQP_FORMAT_COMMAND;
+    else if (strcasecmp ("JSON", string) == 0)
+        conf->format = CAMQP_FORMAT_JSON;
+    else
+    {
+        WARNING ("amqp plugin: Invalid format string: %s",
+                string);
+    }
+
+    free (string);
+
+    return (0);
+} /* }}} int config_set_string */
+
+static int camqp_config_connection (oconfig_item_t *ci, /* {{{ */
+        _Bool publish)
+{
+    camqp_config_t *conf;
+    int status;
+    int i;
+
+    conf = malloc (sizeof (*conf));
+    if (conf == NULL)
+    {
+        ERROR ("amqp plugin: malloc failed.");
+        return (ENOMEM);
+    }
+
+    /* Initialize "conf" {{{ */
+    memset (conf, 0, sizeof (*conf));
+    conf->publish = publish;
+    conf->name = NULL;
+    conf->format = CAMQP_FORMAT_COMMAND;
+    conf->host = NULL;
+    conf->port = 5672;
+    conf->vhost = NULL;
+    conf->user = NULL;
+    conf->password = NULL;
+    conf->exchange = NULL;
+    conf->routing_key = NULL;
+    /* publish only */
+    conf->delivery_mode = CAMQP_DM_VOLATILE;
+    conf->store_rates = 0;
+    /* subscribe only */
+    conf->exchange_type = NULL;
+    conf->queue = NULL;
+    /* general */
+    conf->connection = NULL;
+    pthread_mutex_init (&conf->lock, /* attr = */ NULL);
+    /* }}} */
+
+    status = cf_util_get_string (ci, &conf->name);
+    if (status != 0)
+    {
+        sfree (conf);
+        return (status);
+    }
+
+    for (i = 0; i < ci->children_num; i++)
+    {
+        oconfig_item_t *child = ci->children + i;
+
+        if (strcasecmp ("Host", child->key) == 0)
+            status = cf_util_get_string (child, &conf->host);
+        else if (strcasecmp ("Port", child->key) == 0)
+        {
+            status = cf_util_get_port_number (child);
+            if (status > 0)
+            {
+                conf->port = status;
+                status = 0;
+            }
+        }
+        else if (strcasecmp ("VHost", child->key) == 0)
+            status = cf_util_get_string (child, &conf->vhost);
+        else if (strcasecmp ("User", child->key) == 0)
+            status = cf_util_get_string (child, &conf->user);
+        else if (strcasecmp ("Password", child->key) == 0)
+            status = cf_util_get_string (child, &conf->password);
+        else if (strcasecmp ("Exchange", child->key) == 0)
+            status = cf_util_get_string (child, &conf->exchange);
+        else if ((strcasecmp ("ExchangeType", child->key) == 0) && !publish)
+            status = cf_util_get_string (child, &conf->exchange_type);
+        else if ((strcasecmp ("Queue", child->key) == 0) && !publish)
+            status = cf_util_get_string (child, &conf->queue);
+        else if (strcasecmp ("RoutingKey", child->key) == 0)
+            status = cf_util_get_string (child, &conf->routing_key);
+        else if ((strcasecmp ("Persistent", child->key) == 0) && publish)
+        {
+            _Bool tmp = 0;
+            status = cf_util_get_boolean (child, &tmp);
+            if (tmp)
+                conf->delivery_mode = CAMQP_DM_PERSISTENT;
+            else
+                conf->delivery_mode = CAMQP_DM_VOLATILE;
+        }
+        else if ((strcasecmp ("StoreRates", child->key) == 0) && publish)
+            status = cf_util_get_boolean (child, &conf->store_rates);
+        else if ((strcasecmp ("Format", child->key) == 0) && publish)
+            status = camqp_config_set_format (child, conf);
+        else
+            WARNING ("amqp plugin: Ignoring unknown "
+                    "configuration option \"%s\".", child->key);
+
+        if (status != 0)
+            break;
+    } /* for (i = 0; i < ci->children_num; i++) */
+
+    if ((status == 0) && (conf->exchange == NULL))
+    {
+        if (conf->exchange_type != NULL)
+            WARNING ("amqp plugin: The option \"ExchangeType\" was given "
+                    "without the \"Exchange\" option. It will be ignored.");
+
+        if (!publish && (conf->routing_key != NULL))
+            WARNING ("amqp plugin: The option \"RoutingKey\" was given "
+                    "without the \"Exchange\" option. It will be ignored.");
+
+    }
+
+    if (status != 0)
+    {
+        camqp_config_free (conf);
+        return (status);
+    }
+
+    if (conf->exchange != NULL)
+    {
+        DEBUG ("amqp plugin: camqp_config_connection: exchange = %s;",
+                conf->exchange);
+    }
+
+    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);
+        if (status != 0)
+        {
+            camqp_config_free (conf);
+            return (status);
+        }
+    }
+    else
+    {
+        status = camqp_subscribe_init (conf);
+        if (status != 0)
+        {
+            camqp_config_free (conf);
+            return (status);
+        }
+    }
+
+    return (0);
+} /* }}} int camqp_config_connection */
+
+static int camqp_config (oconfig_item_t *ci) /* {{{ */
+{
+    int i;
+
+    for (i = 0; i < ci->children_num; i++)
+    {
+        oconfig_item_t *child = ci->children + i;
+
+        if (strcasecmp ("Publish", child->key) == 0)
+            camqp_config_connection (child, /* publish = */ 1);
+        else if (strcasecmp ("Subscribe", child->key) == 0)
+            camqp_config_connection (child, /* publish = */ 0);
+        else
+            WARNING ("amqp plugin: Ignoring unknown config option \"%s\".",
+                    child->key);
+    } /* for (ci->children_num) */
+
+    return (0);
+} /* }}} int camqp_config */
+
+void module_register (void)
+{
+    plugin_register_complex_config ("amqp", camqp_config);
+    plugin_register_shutdown ("amqp", camqp_shutdown);
+} /* void module_register */
+
+/* vim: set sw=4 sts=4 et fdm=marker : */
diff --git a/src/apache.c b/src/apache.c
new file mode 100644 (file)
index 0000000..c31dd87
--- /dev/null
@@ -0,0 +1,678 @@
+/**
+ * collectd - src/apache.c
+ * Copyright (C) 2006-2010  Florian octo Forster
+ * Copyright (C) 2007       Florent EppO Monbillard
+ * Copyright (C) 2009       Amit Gupta
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ *   Florent EppO Monbillard <eppo at darox.net>
+ *   - connections/lighttpd extension
+ *   Amit Gupta <amit.gupta221 at gmail.com>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+
+#include <curl/curl.h>
+
+enum server_enum
+{
+       APACHE = 0,
+       LIGHTTPD
+};
+
+struct apache_s
+{
+       int server_type;
+       char *name;
+       char *host;
+       char *url;
+       char *user;
+       char *pass;
+       int   verify_peer;
+       int   verify_host;
+       char *cacert;
+       char *server; /* user specific server type */
+       char *apache_buffer;
+       char apache_curl_error[CURL_ERROR_SIZE];
+       size_t apache_buffer_size;
+       size_t apache_buffer_fill;
+       CURL *curl;
+}; /* apache_s */
+
+typedef struct apache_s apache_t;
+
+/* TODO: Remove this prototype */
+static int apache_read_host (user_data_t *user_data);
+
+static void apache_free (apache_t *st)
+{
+       if (st == NULL)
+               return;
+
+       sfree (st->name);
+       sfree (st->host);
+       sfree (st->url);
+       sfree (st->user);
+       sfree (st->pass);
+       sfree (st->cacert);
+       sfree (st->server);
+       sfree (st->apache_buffer);
+       if (st->curl) {
+               curl_easy_cleanup(st->curl);
+               st->curl = NULL;
+       }
+} /* apache_free */
+
+static size_t apache_curl_callback (void *buf, size_t size, size_t nmemb,
+               void *user_data)
+{
+       size_t len = size * nmemb;
+       apache_t *st;
+
+       st = user_data;
+       if (st == NULL)
+       {
+               ERROR ("apache plugin: apache_curl_callback: "
+                               "user_data pointer is NULL.");
+               return (0);
+       }
+
+       if (len <= 0)
+               return (len);
+
+       if ((st->apache_buffer_fill + len) >= st->apache_buffer_size)
+       {
+               char *temp;
+
+               temp = (char *) realloc (st->apache_buffer,
+                               st->apache_buffer_fill + len + 1);
+               if (temp == NULL)
+               {
+                       ERROR ("apache plugin: realloc failed.");
+                       return (0);
+               }
+               st->apache_buffer = temp;
+               st->apache_buffer_size = st->apache_buffer_fill + len + 1;
+       }
+
+       memcpy (st->apache_buffer + st->apache_buffer_fill, (char *) buf, len);
+       st->apache_buffer_fill += len;
+       st->apache_buffer[st->apache_buffer_fill] = 0;
+
+       return (len);
+} /* int apache_curl_callback */
+
+static size_t apache_header_callback (void *buf, size_t size, size_t nmemb,
+               void *user_data)
+{
+       size_t len = size * nmemb;
+       apache_t *st;
+
+       st = user_data;
+       if (st == NULL)
+       {
+               ERROR ("apache plugin: apache_header_callback: "
+                               "user_data pointer is NULL.");
+               return (0);
+       }
+
+       if (len <= 0)
+               return (len);
+
+       /* look for the Server header */
+       if (strncasecmp (buf, "Server: ", strlen ("Server: ")) != 0)
+               return (len);
+
+       if (strstr (buf, "Apache") != NULL)
+               st->server_type = APACHE;
+       else if (strstr (buf, "lighttpd") != NULL)
+               st->server_type = LIGHTTPD;
+       else if (strstr (buf, "IBM_HTTP_Server") != NULL)
+               st->server_type = APACHE;
+       else
+       {
+               const char *hdr = buf;
+
+               hdr += strlen ("Server: ");
+               NOTICE ("apache plugin: Unknown server software: %s", hdr);
+       }
+
+       return (len);
+} /* apache_header_callback */
+
+/* Configuration handling functiions
+ * <Plugin apache>
+ *   <Instance "instance_name">
+ *     URL ...
+ *   </Instance>
+ *   URL ...
+ * </Plugin>
+ */
+static int config_set_string (char **ret_string, /* {{{ */
+                                   oconfig_item_t *ci)
+{
+       char *string;
+
+       if ((ci->values_num != 1)
+                       || (ci->values[0].type != OCONFIG_TYPE_STRING))
+       {
+               WARNING ("apache plugin: The `%s' config option "
+                               "needs exactly one string argument.", ci->key);
+               return (-1);
+       }
+
+       string = strdup (ci->values[0].value.string);
+       if (string == NULL)
+       {
+               ERROR ("apache plugin: strdup failed.");
+               return (-1);
+       }
+
+       if (*ret_string != NULL)
+               free (*ret_string);
+       *ret_string = string;
+
+       return (0);
+} /* }}} int config_set_string */
+
+static int config_set_boolean (int *ret_boolean, /* {{{ */
+                                   oconfig_item_t *ci)
+{
+       if ((ci->values_num != 1)
+                       || ((ci->values[0].type != OCONFIG_TYPE_BOOLEAN)
+                               && (ci->values[0].type != OCONFIG_TYPE_STRING)))
+       {
+               WARNING ("apache plugin: The `%s' config option "
+                               "needs exactly one boolean argument.", ci->key);
+               return (-1);
+       }
+
+       if (ci->values[0].type == OCONFIG_TYPE_BOOLEAN)
+       {
+               if (ci->values[0].value.boolean)
+                       *ret_boolean = 1;
+               else
+                       *ret_boolean = 0;
+       }
+       else /* if (ci->values[0].type != OCONFIG_TYPE_STRING) */
+       {
+               char *string = ci->values[0].value.string;
+               if (IS_TRUE (string))
+                       *ret_boolean = 1;
+               else if (IS_FALSE (string))
+                       *ret_boolean = 0;
+               else
+               {
+                       ERROR ("apache plugin: Cannot parse string "
+                                       "as boolean value: %s", string);
+                       return (-1);
+               }
+       }
+
+       return (0);
+} /* }}} int config_set_boolean */
+
+static int config_add (oconfig_item_t *ci)
+{
+       apache_t *st;
+       int i;
+       int status;
+
+       if ((ci->values_num != 1)
+               || (ci->values[0].type != OCONFIG_TYPE_STRING))
+       {
+               WARNING ("apache plugin: The `%s' config option "
+                       "needs exactly one string argument.", ci->key);
+               return (-1);
+       }
+
+       st = (apache_t *) malloc (sizeof (*st));
+       if (st == NULL)
+       {
+               ERROR ("apache plugin: malloc failed.");
+               return (-1);
+       }
+
+       memset (st, 0, sizeof (*st));
+
+       status = config_set_string (&st->name, ci);
+       if (status != 0)
+       {
+               sfree (st);
+               return (status);
+       }
+       assert (st->name != NULL);
+
+       for (i = 0; i < ci->children_num; i++)
+       {
+               oconfig_item_t *child = ci->children + i;
+
+               if (strcasecmp ("URL", child->key) == 0)
+                       status = config_set_string (&st->url, child);
+               else if (strcasecmp ("Host", child->key) == 0)
+                       status = config_set_string (&st->host, child);
+               else if (strcasecmp ("User", child->key) == 0)
+                       status = config_set_string (&st->user, child);
+               else if (strcasecmp ("Password", child->key) == 0)
+                       status = config_set_string (&st->pass, child);
+               else if (strcasecmp ("VerifyPeer", child->key) == 0)
+                       status = config_set_boolean (&st->verify_peer, child);
+               else if (strcasecmp ("VerifyHost", child->key) == 0)
+                       status = config_set_boolean (&st->verify_host, child);
+               else if (strcasecmp ("CACert", child->key) == 0)
+                       status = config_set_string (&st->cacert, child);
+               else if (strcasecmp ("Server", child->key) == 0)
+                       status = config_set_string (&st->server, child);
+               else
+               {
+                       WARNING ("apache plugin: Option `%s' not allowed here.",
+                                       child->key);
+                       status = -1;
+               }
+
+               if (status != 0)
+                       break;
+       }
+
+       /* Check if struct is complete.. */
+       if ((status == 0) && (st->url == NULL))
+       {
+               ERROR ("apache plugin: Instance `%s': "
+                               "No URL has been configured.",
+                               st->name);
+               status = -1;
+       }
+
+       if (status == 0)
+       {
+               user_data_t ud;
+               char callback_name[3*DATA_MAX_NAME_LEN];
+
+               memset (&ud, 0, sizeof (ud));
+               ud.data = st;
+               ud.free_func = (void *) apache_free;
+
+               memset (callback_name, 0, sizeof (callback_name));
+               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  = */ NULL,
+                               /* user_data = */ &ud);
+       }
+
+       if (status != 0)
+       {
+               apache_free(st);
+               return (-1);
+       }
+
+       return (0);
+} /* int config_add */
+
+static int config (oconfig_item_t *ci)
+{
+       int status = 0;
+       int i;
+
+       for (i = 0; i < ci->children_num; i++)
+       {
+               oconfig_item_t *child = ci->children + i;
+
+               if (strcasecmp ("Instance", child->key) == 0)
+                       config_add (child);
+               else
+                       WARNING ("apache plugin: The configuration option "
+                                       "\"%s\" is not allowed here. Did you "
+                                       "forget to add an <Instance /> block "
+                                       "around the configuration?",
+                                       child->key);
+       } /* for (ci->children) */
+
+       return (status);
+} /* int config */
+
+/* initialize curl for each host */
+static int init_host (apache_t *st) /* {{{ */
+{
+       static char credentials[1024];
+
+       assert (st->url != NULL);
+       /* (Assured by `config_add') */
+
+       if (st->curl != NULL)
+       {
+               curl_easy_cleanup (st->curl);
+               st->curl = NULL;
+       }
+
+       if ((st->curl = curl_easy_init ()) == NULL)
+       {
+               ERROR ("apache plugin: init_host: `curl_easy_init' failed.");
+               return (-1);
+       }
+
+       curl_easy_setopt (st->curl, CURLOPT_NOSIGNAL, 1);
+       curl_easy_setopt (st->curl, CURLOPT_WRITEFUNCTION, apache_curl_callback);
+       curl_easy_setopt (st->curl, CURLOPT_WRITEDATA, st);
+
+       /* not set as yet if the user specified string doesn't match apache or
+        * lighttpd, then ignore it. Headers will be parsed to find out the
+        * server type */
+       st->server_type = -1;
+
+       if (st->server != NULL)
+       {
+               if (strcasecmp(st->server, "apache") == 0)
+                       st->server_type = APACHE;
+               else if (strcasecmp(st->server, "lighttpd") == 0)
+                       st->server_type = LIGHTTPD;
+               else if (strcasecmp(st->server, "ibm_http_server") == 0)
+                       st->server_type = APACHE;
+               else
+                       WARNING ("apache plugin: Unknown `Server' setting: %s",
+                                       st->server);
+       }
+
+       /* if not found register a header callback to determine the server_type */
+       if (st->server_type == -1)
+       {
+               curl_easy_setopt (st->curl, CURLOPT_HEADERFUNCTION, apache_header_callback);
+               curl_easy_setopt (st->curl, CURLOPT_WRITEHEADER, st);
+       }
+
+       curl_easy_setopt (st->curl, CURLOPT_USERAGENT, PACKAGE_NAME"/"PACKAGE_VERSION);
+       curl_easy_setopt (st->curl, CURLOPT_ERRORBUFFER, st->apache_curl_error);
+
+       if (st->user != NULL)
+       {
+               int status;
+
+               status = ssnprintf (credentials, sizeof (credentials), "%s:%s",
+                               st->user, (st->pass == NULL) ? "" : st->pass);
+               if ((status < 0) || ((size_t) status >= sizeof (credentials)))
+               {
+                       ERROR ("apache plugin: init_host: Returning an error "
+                                       "because the credentials have been "
+                                       "truncated.");
+                       curl_easy_cleanup (st->curl);
+                       st->curl = NULL;
+                       return (-1);
+               }
+
+               curl_easy_setopt (st->curl, CURLOPT_USERPWD, credentials);
+       }
+
+       curl_easy_setopt (st->curl, CURLOPT_URL, st->url);
+       curl_easy_setopt (st->curl, CURLOPT_FOLLOWLOCATION, 1);
+
+       if (st->verify_peer != 0)
+       {
+               curl_easy_setopt (st->curl, CURLOPT_SSL_VERIFYPEER, 1);
+       }
+       else
+       {
+               curl_easy_setopt (st->curl, CURLOPT_SSL_VERIFYPEER, 0);
+       }
+
+       if (st->verify_host != 0)
+       {
+               curl_easy_setopt (st->curl, CURLOPT_SSL_VERIFYHOST, 2);
+       }
+       else
+       {
+               curl_easy_setopt (st->curl, CURLOPT_SSL_VERIFYHOST, 0);
+       }
+
+       if (st->cacert != NULL)
+       {
+               curl_easy_setopt (st->curl, CURLOPT_CAINFO, st->cacert);
+       }
+
+       return (0);
+} /* }}} int init_host */
+
+static void submit_value (const char *type, const char *type_instance,
+               value_t value, apache_t *st)
+{
+       value_list_t vl = VALUE_LIST_INIT;
+
+       vl.values = &value;
+       vl.values_len = 1;
+
+       sstrncpy (vl.host, (st->host != NULL) ? st->host : hostname_g,
+                       sizeof (vl.host));
+
+       sstrncpy (vl.plugin, "apache", sizeof (vl.plugin));
+       if (st->name != NULL)
+               sstrncpy (vl.plugin_instance, st->name,
+                               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);
+} /* 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);
+} /* 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);
+} /* void submit_gauge */
+
+static void submit_scoreboard (char *buf, apache_t *st)
+{
+       /*
+        * Scoreboard Key:
+        * "_" Waiting for Connection, "S" Starting up,
+        * "R" Reading Request for apache and read-POST for lighttpd,
+        * "W" Sending Reply, "K" Keepalive (read), "D" DNS Lookup,
+        * "C" Closing connection, "L" Logging, "G" Gracefully finishing,
+        * "I" Idle cleanup of worker, "." Open slot with no current process
+        * Lighttpd specific legends -
+        * "E" hard error, "." connect, "h" handle-request,
+        * "q" request-start, "Q" request-end, "s" response-start
+        * "S" response-end, "r" read
+        */
+       long long open      = 0LL;
+       long long waiting   = 0LL;
+       long long starting  = 0LL;
+       long long reading   = 0LL;
+       long long sending   = 0LL;
+       long long keepalive = 0LL;
+       long long dnslookup = 0LL;
+       long long closing   = 0LL;
+       long long logging   = 0LL;
+       long long finishing = 0LL;
+       long long idle_cleanup = 0LL;
+
+       /* lighttpd specific */
+       long long hard_error     = 0LL;
+       long long lighttpd_read  = 0LL;
+       long long handle_request = 0LL;
+       long long request_start  = 0LL;
+       long long request_end    = 0LL;
+       long long response_start = 0LL;
+       long long response_end   = 0LL;
+
+       int i;
+       for (i = 0; buf[i] != '\0'; i++)
+       {
+               if (buf[i] == '.') open++;
+               else if (buf[i] == '_') waiting++;
+               else if (buf[i] == 'S') starting++;
+               else if (buf[i] == 'R') reading++;
+               else if (buf[i] == 'W') sending++;
+               else if (buf[i] == 'K') keepalive++;
+               else if (buf[i] == 'D') dnslookup++;
+               else if (buf[i] == 'C') closing++;
+               else if (buf[i] == 'L') logging++;
+               else if (buf[i] == 'G') finishing++;
+               else if (buf[i] == 'I') idle_cleanup++;
+               else if (buf[i] == 'r') lighttpd_read++;
+               else if (buf[i] == 'h') handle_request++;
+               else if (buf[i] == 'E') hard_error++;
+               else if (buf[i] == 'q') request_start++;
+               else if (buf[i] == 'Q') request_end++;
+               else if (buf[i] == 's') response_start++;
+               else if (buf[i] == 'S') response_end++;
+       }
+
+       if (st->server_type == APACHE)
+       {
+               submit_gauge ("apache_scoreboard", "open"     , open, st);
+               submit_gauge ("apache_scoreboard", "waiting"  , waiting, st);
+               submit_gauge ("apache_scoreboard", "starting" , starting, st);
+               submit_gauge ("apache_scoreboard", "reading"  , reading, st);
+               submit_gauge ("apache_scoreboard", "sending"  , sending, st);
+               submit_gauge ("apache_scoreboard", "keepalive", keepalive, st);
+               submit_gauge ("apache_scoreboard", "dnslookup", dnslookup, st);
+               submit_gauge ("apache_scoreboard", "closing"  , closing, st);
+               submit_gauge ("apache_scoreboard", "logging"  , logging, st);
+               submit_gauge ("apache_scoreboard", "finishing", finishing, st);
+               submit_gauge ("apache_scoreboard", "idle_cleanup", idle_cleanup, st);
+       }
+       else
+       {
+               submit_gauge ("apache_scoreboard", "connect"       , open, st);
+               submit_gauge ("apache_scoreboard", "close"         , closing, st);
+               submit_gauge ("apache_scoreboard", "hard_error"    , hard_error, st);
+               submit_gauge ("apache_scoreboard", "read"          , lighttpd_read, st);
+               submit_gauge ("apache_scoreboard", "read_post"     , reading, st);
+               submit_gauge ("apache_scoreboard", "write"         , sending, st);
+               submit_gauge ("apache_scoreboard", "handle_request", handle_request, st);
+               submit_gauge ("apache_scoreboard", "request_start" , request_start, st);
+               submit_gauge ("apache_scoreboard", "request_end"   , request_end, st);
+               submit_gauge ("apache_scoreboard", "response_start", response_start, st);
+               submit_gauge ("apache_scoreboard", "response_end"  , response_end, st);
+       }
+}
+
+static int apache_read_host (user_data_t *user_data) /* {{{ */
+{
+       int i;
+
+       char *ptr;
+       char *saveptr;
+       char *lines[16];
+       int   lines_num = 0;
+
+       char *fields[4];
+       int   fields_num;
+
+       apache_t *st;
+
+       st = user_data->data;
+
+       assert (st->url != NULL);
+       /* (Assured by `config_add') */
+
+       if (st->curl == NULL)
+       {
+               int status;
+
+               status = init_host (st);
+               if (status != 0)
+                       return (-1);
+       }
+       assert (st->curl != NULL);
+
+       st->apache_buffer_fill = 0;
+       if (curl_easy_perform (st->curl) != 0)
+       {
+               ERROR ("apache: curl_easy_perform failed: %s",
+                               st->apache_curl_error);
+               return (-1);
+       }
+
+       /* fallback - server_type to apache if not set at this time */
+       if (st->server_type == -1)
+       {
+               WARNING ("apache plugin: Unable to determine server software "
+                               "automatically. Will assume Apache.");
+               st->server_type = APACHE;
+       }
+
+       ptr = st->apache_buffer;
+       saveptr = NULL;
+       while ((lines[lines_num] = strtok_r (ptr, "\n\r", &saveptr)) != NULL)
+       {
+               ptr = NULL;
+               lines_num++;
+
+               if (lines_num >= 16)
+                       break;
+       }
+
+       for (i = 0; i < lines_num; i++)
+       {
+               fields_num = strsplit (lines[i], fields, 4);
+
+               if (fields_num == 3)
+               {
+                       if ((strcmp (fields[0], "Total") == 0)
+                                       && (strcmp (fields[1], "Accesses:") == 0))
+                               submit_derive ("apache_requests", "",
+                                               atoll (fields[2]), st);
+                       else if ((strcmp (fields[0], "Total") == 0)
+                                       && (strcmp (fields[1], "kBytes:") == 0))
+                               submit_derive ("apache_bytes", "",
+                                               1024LL * atoll (fields[2]), st);
+               }
+               else if (fields_num == 2)
+               {
+                       if (strcmp (fields[0], "Scoreboard:") == 0)
+                               submit_scoreboard (fields[1], st);
+                       else if ((strcmp (fields[0], "BusyServers:") == 0) /* Apache 1.* */
+                                       || (strcmp (fields[0], "BusyWorkers:") == 0) /* Apache 2.* */)
+                               submit_gauge ("apache_connections", NULL, atol (fields[1]), st);
+                       else if ((strcmp (fields[0], "IdleServers:") == 0) /* Apache 1.x */
+                                       || (strcmp (fields[0], "IdleWorkers:") == 0) /* Apache 2.x */)
+                               submit_gauge ("apache_idle_workers", NULL, atol (fields[1]), st);
+               }
+       }
+
+       st->apache_buffer_fill = 0;
+
+       return (0);
+} /* }}} int apache_read_host */
+
+void module_register (void)
+{
+       plugin_register_complex_config ("apache", config);
+} /* void module_register */
+
+/* vim: set sw=8 noet fdm=marker : */
diff --git a/src/apcups.c b/src/apcups.c
new file mode 100644 (file)
index 0000000..a0629d5
--- /dev/null
@@ -0,0 +1,432 @@
+/*
+ * collectd - src/apcups.c
+ * Copyright (C) 2006-2007  Florian octo Forster
+ * Copyright (C) 2006       Anthony Gialluca <tonyabg at charter.net>
+ * Copyright (C) 2000-2004  Kern Sibbald
+ * Copyright (C) 1996-1999  Andre M. Hedrick <andre at suse.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General
+ * Public License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ *
+ * Authors:
+ *   Anthony Gialluca <tonyabg at charter.net>
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"      /* rrd_update_file */
+#include "plugin.h"      /* plugin_register, plugin_submit */
+#include "configfile.h"  /* cf_register */
+
+#if HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#if HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif
+#if HAVE_NETDB_H
+# include <netdb.h>
+#endif
+
+#if HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+
+#define NISPORT 3551
+#define MAXSTRING               256
+#define MODULE_NAME "apcups"
+
+#define APCUPS_DEFAULT_HOST "localhost"
+
+/*
+ * Private data types
+ */
+struct apc_detail_s
+{
+       double linev;
+       double loadpct;
+       double bcharge;
+       double timeleft;
+       double outputv;
+       double itemp;
+       double battv;
+       double linefreq;
+};
+
+/*
+ * Private variables
+ */
+/* Default values for contacting daemon */
+static char *conf_host = NULL;
+static int   conf_port = NISPORT;
+
+static int global_sockfd = -1;
+
+static const char *config_keys[] =
+{
+       "Host",
+       "Port",
+       NULL
+};
+static int config_keys_num = 2;
+
+/* Close the network connection */
+static int apcups_shutdown (void)
+{
+       uint16_t packet_size = 0;
+
+       if (global_sockfd < 0)
+               return (0);
+
+       DEBUG ("Gracefully shutting down socket %i.", global_sockfd);
+
+       /* send EOF sentinel */
+       swrite (global_sockfd, (void *) &packet_size, sizeof (packet_size));
+
+       close (global_sockfd);
+       global_sockfd = -1;
+
+       return (0);
+} /* int apcups_shutdown */
+
+/*     
+ * Open a TCP connection to the UPS network server
+ * Returns -1 on error
+ * Returns socket file descriptor otherwise
+ */
+static int net_open (char *host, int port)
+{
+       int              sd;
+       int              status;
+       char             port_str[8];
+       struct addrinfo  ai_hints;
+       struct addrinfo *ai_return;
+       struct addrinfo *ai_list;
+
+       assert ((port > 0x00000000) && (port <= 0x0000FFFF));
+
+       /* Convert the port to a string */
+       ssnprintf (port_str, sizeof (port_str), "%i", port);
+
+       /* Resolve name */
+       memset ((void *) &ai_hints, '\0', sizeof (ai_hints));
+       ai_hints.ai_family   = AF_INET; /* XXX: Change this to `AF_UNSPEC' if apcupsd can handle IPv6 */
+       ai_hints.ai_socktype = SOCK_STREAM;
+
+       status = getaddrinfo (host, port_str, &ai_hints, &ai_return);
+       if (status != 0)
+       {
+               char errbuf[1024];
+               INFO ("getaddrinfo failed: %s",
+                               (status == EAI_SYSTEM)
+                               ? sstrerror (errno, errbuf, sizeof (errbuf))
+                               : gai_strerror (status));
+               return (-1);
+       }
+
+       /* Create socket */
+       sd = -1;
+       for (ai_list = ai_return; ai_list != NULL; ai_list = ai_list->ai_next)
+       {
+               sd = socket (ai_list->ai_family, ai_list->ai_socktype, ai_list->ai_protocol);
+               if (sd >= 0)
+                       break;
+       }
+       /* `ai_list' still holds the current description of the socket.. */
+
+       if (sd < 0)
+       {
+               DEBUG ("Unable to open a socket");
+               freeaddrinfo (ai_return);
+               return (-1);
+       }
+
+       status = connect (sd, ai_list->ai_addr, ai_list->ai_addrlen);
+
+       freeaddrinfo (ai_return);
+
+       if (status != 0) /* `connect(2)' failed */
+       {
+               char errbuf[1024];
+               INFO ("connect failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               close (sd);
+               return (-1);
+       }
+
+       DEBUG ("Done opening a socket %i", sd);
+
+       return (sd);
+} /* int net_open (char *host, char *service, int port) */
+
+/* 
+ * Receive a message from the other end. Each message consists of
+ * two packets. The first is a header that contains the size
+ * of the data that follows in the second packet.
+ * Returns number of bytes read
+ * Returns 0 on end of file
+ * Returns -1 on hard end of file (i.e. network connection close)
+ * Returns -2 on error
+ */
+static int net_recv (int *sockfd, char *buf, int buflen)
+{
+       uint16_t packet_size;
+
+       /* get data size -- in short */
+       if (sread (*sockfd, (void *) &packet_size, sizeof (packet_size)) != 0)
+       {
+               *sockfd = -1;
+               return (-1);
+       }
+
+       packet_size = ntohs (packet_size);
+       if (packet_size > buflen)
+       {
+               DEBUG ("record length too large");
+               return (-2);
+       }
+
+       if (packet_size == 0)
+               return (0);
+
+       /* now read the actual data */
+       if (sread (*sockfd, (void *) buf, packet_size) != 0)
+       {
+               *sockfd = -1;
+               return (-1);
+       }
+
+       return ((int) packet_size);
+} /* static int net_recv (int *sockfd, char *buf, int buflen) */
+
+/*
+ * Send a message over the network. The send consists of
+ * two network packets. The first is sends a short containing
+ * the length of the data packet which follows.
+ * Returns zero on success
+ * Returns non-zero on error
+ */
+static int net_send (int *sockfd, char *buff, int len)
+{
+       uint16_t packet_size;
+
+       assert (len > 0);
+       assert (*sockfd >= 0);
+
+       /* send short containing size of data packet */
+       packet_size = htons ((uint16_t) len);
+
+       if (swrite (*sockfd, (void *) &packet_size, sizeof (packet_size)) != 0)
+       {
+               *sockfd = -1;
+               return (-1);
+       }
+
+       /* send data packet */
+       if (swrite (*sockfd, (void *) buff, len) != 0)
+       {
+               *sockfd = -1;
+               return (-2);
+       }
+
+       return (0);
+}
+
+/* Get and print status from apcupsd NIS server */
+static int apc_query_server (char *host, int port,
+               struct apc_detail_s *apcups_detail)
+{
+       int     n;
+       char    recvline[1024];
+       char   *tokptr;
+       char   *toksaveptr;
+       char   *key;
+       double  value;
+
+#if APCMAIN
+# define PRINT_VALUE(name, val) printf("  Found property: name = %s; value = %f;\n", name, val)
+#else
+# define PRINT_VALUE(name, val) /**/
+#endif
+
+       if (global_sockfd < 0)
+       {
+               global_sockfd = net_open (host, port);
+               if (global_sockfd < 0)
+               {
+                       ERROR ("apcups plugin: Connecting to the "
+                                       "apcupsd failed.");
+                       return (-1);
+               }
+       }
+
+       if (net_send (&global_sockfd, "status", 6) < 0)
+       {
+               ERROR ("apcups plugin: Writing to the socket failed.");
+               return (-1);
+       }
+
+       while ((n = net_recv (&global_sockfd, recvline, sizeof (recvline) - 1)) > 0)
+       {
+               assert ((unsigned int)n < sizeof (recvline));
+               recvline[n] = '\0';
+#if APCMAIN
+               printf ("net_recv = `%s';\n", recvline);
+#endif /* if APCMAIN */
+
+               toksaveptr = NULL;
+               tokptr = strtok_r (recvline, " :\t", &toksaveptr);
+               while (tokptr != NULL)
+               {
+                       key = tokptr;
+                       if ((tokptr = strtok_r (NULL, " :\t", &toksaveptr)) == NULL)
+                               continue;
+                       value = atof (tokptr);
+
+                       PRINT_VALUE (key, value);
+
+                       if (strcmp ("LINEV", key) == 0)
+                               apcups_detail->linev = value;
+                       else if (strcmp ("BATTV", key) == 0) 
+                               apcups_detail->battv = value;
+                       else if (strcmp ("ITEMP", key) == 0)
+                               apcups_detail->itemp = value;
+                       else if (strcmp ("LOADPCT", key) == 0)
+                               apcups_detail->loadpct = value;
+                       else if (strcmp ("BCHARGE", key) == 0)
+                               apcups_detail->bcharge = value;
+                       else if (strcmp ("OUTPUTV", key) == 0)
+                               apcups_detail->outputv = value;
+                       else if (strcmp ("LINEFREQ", key) == 0)
+                               apcups_detail->linefreq = value;
+                       else if (strcmp ("TIMELEFT", key) == 0)
+                               apcups_detail->timeleft = value;
+
+                       tokptr = strtok_r (NULL, ":", &toksaveptr);
+               } /* while (tokptr != NULL) */
+       }
+       
+       if (n < 0)
+       {
+               WARNING ("apcups plugin: Error reading from socket");
+               return (-1);
+       }
+
+       return (0);
+}
+
+static int apcups_config (const char *key, const char *value)
+{
+       if (strcasecmp (key, "host") == 0)
+       {
+               if (conf_host != NULL)
+               {
+                       free (conf_host);
+                       conf_host = NULL;
+               }
+               if ((conf_host = strdup (value)) == NULL)
+                       return (1);
+       }
+       else if (strcasecmp (key, "Port") == 0)
+       {
+               int port_tmp = atoi (value);
+               if (port_tmp < 1 || port_tmp > 65535)
+               {
+                       WARNING ("apcups plugin: Invalid port: %i", port_tmp);
+                       return (1);
+               }
+               conf_port = port_tmp;
+       }
+       else
+       {
+               return (-1);
+       }
+       return (0);
+}
+
+static void apc_submit_generic (char *type, 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_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));
+
+       plugin_dispatch_values (&vl);
+}
+
+static void apc_submit (struct apc_detail_s *apcups_detail)
+{
+       apc_submit_generic ("voltage",    "input",   apcups_detail->linev);
+       apc_submit_generic ("voltage",    "output",  apcups_detail->outputv);
+       apc_submit_generic ("voltage",    "battery", apcups_detail->battv);
+       apc_submit_generic ("charge",     "",        apcups_detail->bcharge);
+       apc_submit_generic ("percent",    "load",    apcups_detail->loadpct);
+       apc_submit_generic ("timeleft",   "",        apcups_detail->timeleft);
+       apc_submit_generic ("temperature", "",       apcups_detail->itemp);
+       apc_submit_generic ("frequency",  "input",   apcups_detail->linefreq);
+}
+
+static int apcups_read (void)
+{
+       struct apc_detail_s apcups_detail;
+       int status;
+
+       apcups_detail.linev    =   -1.0;
+       apcups_detail.outputv  =   -1.0;
+       apcups_detail.battv    =   -1.0;
+       apcups_detail.loadpct  =   -1.0;
+       apcups_detail.bcharge  =   -1.0;
+       apcups_detail.timeleft =   -1.0;
+       apcups_detail.itemp    = -300.0;
+       apcups_detail.linefreq =   -1.0;
+  
+       status = apc_query_server (conf_host == NULL
+                       ? APCUPS_DEFAULT_HOST
+                       : conf_host,
+                       conf_port, &apcups_detail);
+       /*
+        * if we did not connect then do not bother submitting
+        * zeros. We want rrd files to have NAN.
+        */
+       if (status != 0)
+       {
+               DEBUG ("apc_query_server (%s, %i) = %i",
+                               conf_host == NULL
+                               ? APCUPS_DEFAULT_HOST
+                               : conf_host,
+                               conf_port, status);
+               return (-1);
+       }
+
+       apc_submit (&apcups_detail);
+
+       return (0);
+} /* apcups_read */
+
+void module_register (void)
+{
+       plugin_register_config ("apcups", apcups_config, config_keys,
+                       config_keys_num);
+       plugin_register_read ("apcups", apcups_read);
+       plugin_register_shutdown ("apcups", apcups_shutdown);
+} /* void module_register */
diff --git a/src/apple_sensors.c b/src/apple_sensors.c
new file mode 100644 (file)
index 0000000..bdba0ff
--- /dev/null
@@ -0,0 +1,239 @@
+/**
+ * collectd - src/apple_sensors.c
+ * Copyright (C) 2006,2007  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#if HAVE_CTYPE_H
+#  include <ctype.h>
+#endif
+
+#if HAVE_MACH_MACH_TYPES_H
+#  include <mach/mach_types.h>
+#endif
+#if HAVE_MACH_MACH_INIT_H
+#  include <mach/mach_init.h>
+#endif
+#if HAVE_MACH_MACH_ERROR_H
+#  include <mach/mach_error.h>
+#endif
+#if HAVE_MACH_MACH_PORT_H
+#  include <mach/mach_port.h>
+#endif
+#if HAVE_COREFOUNDATION_COREFOUNDATION_H
+#  include <CoreFoundation/CoreFoundation.h>
+#endif
+#if HAVE_IOKIT_IOKITLIB_H
+#  include <IOKit/IOKitLib.h>
+#endif
+#if HAVE_IOKIT_IOTYPES_H
+#  include <IOKit/IOTypes.h>
+#endif
+
+static mach_port_t io_master_port = MACH_PORT_NULL;
+
+static int as_init (void)
+{
+       kern_return_t status;
+       
+       if (io_master_port != MACH_PORT_NULL)
+       {
+               mach_port_deallocate (mach_task_self (),
+                               io_master_port);
+               io_master_port = MACH_PORT_NULL;
+       }
+
+       status = IOMasterPort (MACH_PORT_NULL, &io_master_port);
+       if (status != kIOReturnSuccess)
+       {
+               ERROR ("IOMasterPort failed: %s",
+                               mach_error_string (status));
+               io_master_port = MACH_PORT_NULL;
+               return (-1);
+       }
+
+       return (0);
+}
+
+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_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));
+
+       plugin_dispatch_values (&vl);
+}
+
+static int as_read (void)
+{
+       kern_return_t   status;
+       io_iterator_t   iterator;
+       io_object_t     io_obj;
+       CFMutableDictionaryRef prop_dict;
+       CFTypeRef       property;
+
+       char   type[128];
+       char   inst[128];
+       int    value_int;
+       double value_double;
+       int    i;
+
+       if (!io_master_port || (io_master_port == MACH_PORT_NULL))
+               return (-1);
+
+       status = IOServiceGetMatchingServices (io_master_port,
+                       IOServiceNameMatching("IOHWSensor"),
+                       &iterator);
+       if (status != kIOReturnSuccess)
+               {
+               ERROR ("IOServiceGetMatchingServices failed: %s",
+                               mach_error_string (status));
+               return (-1);
+       }
+
+       while ((io_obj = IOIteratorNext (iterator)))
+       {
+               prop_dict = NULL;
+               status = IORegistryEntryCreateCFProperties (io_obj,
+                               &prop_dict,
+                               kCFAllocatorDefault,
+                               kNilOptions);
+               if (status != kIOReturnSuccess)
+               {
+                       DEBUG ("IORegistryEntryCreateCFProperties failed: %s",
+                                       mach_error_string (status));
+                       continue;
+               }
+
+               /* Copy the sensor type. */
+               property = NULL;
+               if (!CFDictionaryGetValueIfPresent (prop_dict,
+                                       CFSTR ("type"),
+                                       &property))
+                       continue;
+               if (CFGetTypeID (property) != CFStringGetTypeID ())
+                       continue;
+               if (!CFStringGetCString (property,
+                                       type, sizeof (type),
+                                       kCFStringEncodingASCII))
+                       continue;
+               type[sizeof (type) - 1] = '\0';
+
+               /* Copy the sensor location. This will be used as `instance'. */
+               property = NULL;
+               if (!CFDictionaryGetValueIfPresent (prop_dict,
+                                       CFSTR ("location"),
+                                       &property))
+                       continue;
+               if (CFGetTypeID (property) != CFStringGetTypeID ())
+                       continue;
+               if (!CFStringGetCString (property,
+                                       inst, sizeof (inst),
+                                       kCFStringEncodingASCII))
+                       continue;
+               inst[sizeof (inst) - 1] = '\0';
+               for (i = 0; i < 128; i++)
+               {
+                       if (inst[i] == '\0')
+                               break;
+                       else if (isalnum (inst[i]))
+                               inst[i] = (char) tolower (inst[i]);
+                       else
+                               inst[i] = '_';
+               }
+
+               /* Get the actual value. Some computation, based on the `type'
+                * is neccessary. */
+               property = NULL;
+               if (!CFDictionaryGetValueIfPresent (prop_dict,
+                                       CFSTR ("current-value"),
+                                       &property))
+                       continue;
+               if (CFGetTypeID (property) != CFNumberGetTypeID ())
+                       continue;
+               if (!CFNumberGetValue (property,
+                                       kCFNumberIntType,
+                                       &value_int))
+                       continue;
+
+               /* Found e.g. in the 1.5GHz PowerBooks */
+               if (strcmp (type, "temperature") == 0)
+               {
+                       value_double = ((double) value_int) / 65536.0;
+                       sstrncpy (type, "temperature", sizeof (type));
+               }
+               else if (strcmp (type, "temp") == 0)
+               {
+                       value_double = ((double) value_int) / 10.0;
+                       sstrncpy (type, "temperature", sizeof (type));
+               }
+               else if (strcmp (type, "fanspeed") == 0)
+               {
+                       value_double = ((double) value_int) / 65536.0;
+                       sstrncpy (type, "fanspeed", sizeof (type));
+               }
+               else if (strcmp (type, "voltage") == 0)
+               {
+                       /* Leave this to the battery plugin. */
+                       continue;
+               }
+               else if (strcmp (type, "adc") == 0)
+               {
+                       value_double = ((double) value_int) / 10.0;
+                       sstrncpy (type, "fanspeed", sizeof (type));
+               }
+               else
+               {
+                       DEBUG ("apple_sensors: Read unknown sensor type: %s",
+                                       type);
+                       value_double = (double) value_int;
+               }
+
+               as_submit (type, inst, value_double);
+
+               CFRelease (prop_dict);
+               IOObjectRelease (io_obj);
+       } /* while (iterator) */
+
+       IOObjectRelease (iterator);
+
+       return (0);
+} /* int as_read */
+
+void module_register (void)
+{
+       plugin_register_init ("apple_sensors", as_init);
+       plugin_register_read ("apple_sensors", as_read);
+} /* void module_register */
diff --git a/src/ascent.c b/src/ascent.c
new file mode 100644 (file)
index 0000000..993e480
--- /dev/null
@@ -0,0 +1,620 @@
+/**
+ * collectd - src/ascent.c
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+
+#include <curl/curl.h>
+#include <libxml/parser.h>
+
+static char *races_list[] = /* {{{ */
+{
+  NULL,
+  "Human",    /*  1 */
+  "Orc",      /*  2 */
+  "Dwarf",    /*  3 */
+  "Nightelf", /*  4 */
+  "Undead",   /*  5 */
+  "Tauren",   /*  6 */
+  "Gnome",    /*  7 */
+  "Troll",    /*  8 */
+  NULL,
+  "Bloodelf", /* 10 */
+  "Draenei"   /* 11 */
+}; /* }}} */
+#define RACES_LIST_LENGTH STATIC_ARRAY_SIZE (races_list)
+
+static char *classes_list[] = /* {{{ */
+{
+  NULL,
+  "Warrior", /*  1 */
+  "Paladin", /*  2 */
+  "Hunter",  /*  3 */
+  "Rogue",   /*  4 */
+  "Priest",  /*  5 */
+  NULL,
+  "Shaman",  /*  7 */
+  "Mage",    /*  8 */
+  "Warlock", /*  9 */
+  NULL,
+  "Druid"    /* 11 */
+}; /* }}} */
+#define CLASSES_LIST_LENGTH STATIC_ARRAY_SIZE (classes_list)
+
+static char *genders_list[] = /* {{{ */
+{
+  "Male",
+  "Female"
+}; /* }}} */
+#define GENDERS_LIST_LENGTH STATIC_ARRAY_SIZE (genders_list)
+
+struct player_stats_s
+{
+  int races[RACES_LIST_LENGTH];
+  int classes[CLASSES_LIST_LENGTH];
+  int genders[GENDERS_LIST_LENGTH];
+  int level_sum;
+  int level_num;
+  int latency_sum;
+  int latency_num;
+};
+typedef struct player_stats_s player_stats_t;
+
+struct player_info_s
+{
+  int race;
+  int class;
+  int gender;
+  int level;
+  int latency;
+};
+typedef struct player_info_s player_info_t;
+#define PLAYER_INFO_STATIC_INIT { -1, -1, -1, -1, -1 }
+
+static char *url         = NULL;
+static char *user        = NULL;
+static char *pass        = NULL;
+static char *verify_peer = NULL;
+static char *verify_host = NULL;
+static char *cacert      = NULL;
+
+static CURL *curl = NULL;
+
+static char  *ascent_buffer = NULL;
+static size_t ascent_buffer_size = 0;
+static size_t ascent_buffer_fill = 0;
+static char   ascent_curl_error[CURL_ERROR_SIZE];
+
+static const char *config_keys[] =
+{
+  "URL",
+  "User",
+  "Password",
+  "VerifyPeer",
+  "VerifyHost",
+  "CACert"
+};
+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_len = 1;
+  sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+  sstrncpy (vl.plugin, "ascent", sizeof (vl.plugin));
+
+  if (plugin_instance != NULL)
+    sstrncpy (vl.plugin_instance, plugin_instance,
+        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);
+  return (0);
+} /* }}} int ascent_submit_gauge */
+
+static size_t ascent_curl_callback (void *buf, size_t size, size_t nmemb, /* {{{ */
+    void __attribute__((unused)) *stream)
+{
+  size_t len = size * nmemb;
+
+  if (len <= 0)
+    return (len);
+
+  if ((ascent_buffer_fill + len) >= ascent_buffer_size)
+  {
+    char *temp;
+
+    temp = (char *) realloc (ascent_buffer,
+        ascent_buffer_fill + len + 1);
+    if (temp == NULL)
+    {
+      ERROR ("ascent plugin: realloc failed.");
+      return (0);
+    }
+    ascent_buffer = temp;
+    ascent_buffer_size = ascent_buffer_fill + len + 1;
+  }
+
+  memcpy (ascent_buffer + ascent_buffer_fill, (char *) buf, len);
+  ascent_buffer_fill += len;
+  ascent_buffer[ascent_buffer_fill] = 0;
+
+  return (len);
+} /* }}} size_t ascent_curl_callback */
+
+static int ascent_submit_players (player_stats_t *ps) /* {{{ */
+{
+  size_t i;
+  gauge_t value;
+
+  for (i = 0; i < RACES_LIST_LENGTH; i++)
+    if (races_list[i] != NULL)
+      ascent_submit_gauge ("by-race", "players", races_list[i],
+          (gauge_t) ps->races[i]);
+
+  for (i = 0; i < CLASSES_LIST_LENGTH; i++)
+    if (classes_list[i] != NULL)
+      ascent_submit_gauge ("by-class", "players", classes_list[i],
+          (gauge_t) ps->classes[i]);
+
+  for (i = 0; i < GENDERS_LIST_LENGTH; i++)
+    if (genders_list[i] != NULL)
+      ascent_submit_gauge ("by-gender", "players", genders_list[i],
+          (gauge_t) ps->genders[i]);
+
+  if (ps->level_num <= 0)
+    value = NAN;
+  else
+    value = ((double) ps->level_sum) / ((double) ps->level_num);
+  ascent_submit_gauge (NULL, "gauge", "avg-level", value);
+
+  /* Latency is in ms, but we store seconds. */
+  if (ps->latency_num <= 0)
+    value = NAN;
+  else
+    value = ((double) ps->latency_sum) / (1000.0 * ((double) ps->latency_num));
+  ascent_submit_gauge (NULL, "latency", "average", value);
+
+  return (0);
+} /* }}} int ascent_submit_players */
+
+static int ascent_account_player (player_stats_t *ps, /* {{{ */
+    player_info_t *pi)
+{
+  if (pi->race >= 0)
+  {
+    if (((size_t) pi->race >= RACES_LIST_LENGTH)
+        || (races_list[pi->race] == NULL))
+      ERROR ("ascent plugin: Ignoring invalid numeric race %i.", pi->race);
+    else
+      ps->races[pi->race]++;
+  }
+
+  if (pi->class >= 0)
+  {
+    if (((size_t) pi->class >= CLASSES_LIST_LENGTH)
+        || (classes_list[pi->class] == NULL))
+      ERROR ("ascent plugin: Ignoring invalid numeric class %i.", pi->class);
+    else
+      ps->classes[pi->class]++;
+  }
+
+  if (pi->gender >= 0)
+  {
+    if (((size_t) pi->gender >= GENDERS_LIST_LENGTH)
+        || (genders_list[pi->gender] == NULL))
+      ERROR ("ascent plugin: Ignoring invalid numeric gender %i.",
+          pi->gender);
+    else
+      ps->genders[pi->gender]++;
+  }
+
+
+  if (pi->level > 0)
+  {
+    ps->level_sum += pi->level;
+    ps->level_num++;
+  }
+
+  if (pi->latency >= 0)
+  {
+    ps->latency_sum += pi->latency;
+    ps->latency_num++;
+  }
+
+  return (0);
+} /* }}} int ascent_account_player */
+
+static int ascent_xml_submit_gauge (xmlDoc *doc, xmlNode *node, /* {{{ */
+    const char *plugin_instance, const char *type, const char *type_instance)
+{
+  char *str_ptr;
+  gauge_t value;
+
+  str_ptr = (char *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+  if (str_ptr == NULL)
+  {
+    ERROR ("ascent plugin: ascent_xml_submit_gauge: xmlNodeListGetString failed.");
+    return (-1);
+  }
+
+  if (strcasecmp ("N/A", str_ptr) == 0)
+    value = NAN;
+  else
+  {
+    char *end_ptr = NULL;
+    value = strtod (str_ptr, &end_ptr);
+    if (str_ptr == end_ptr)
+    {
+      xmlFree(str_ptr);
+      ERROR ("ascent plugin: ascent_xml_submit_gauge: strtod failed.");
+      return (-1);
+    }
+  }
+  xmlFree(str_ptr);
+
+  return (ascent_submit_gauge (plugin_instance, type, type_instance, value));
+} /* }}} int ascent_xml_submit_gauge */
+
+static int ascent_xml_read_int (xmlDoc *doc, xmlNode *node, /* {{{ */
+    int *ret_value)
+{
+  char *str_ptr;
+  int value;
+
+  str_ptr = (char *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+  if (str_ptr == NULL)
+  {
+    ERROR ("ascent plugin: ascent_xml_read_int: xmlNodeListGetString failed.");
+    return (-1);
+  }
+
+  if (strcasecmp ("N/A", str_ptr) == 0)
+    value = -1;
+  else
+  {
+    char *end_ptr = NULL;
+    value = strtol (str_ptr, &end_ptr, 0);
+    if (str_ptr == end_ptr)
+    {
+      xmlFree(str_ptr);
+      ERROR ("ascent plugin: ascent_xml_read_int: strtol failed.");
+      return (-1);
+    }
+  }
+  xmlFree(str_ptr);
+
+  *ret_value = value;
+  return (0);
+} /* }}} int ascent_xml_read_int */
+
+static int ascent_xml_sessions_plr (xmlDoc *doc, xmlNode *node, /* {{{ */
+    player_info_t *pi)
+{
+  xmlNode *child;
+
+  for (child = node->xmlChildrenNode; child != NULL; child = child->next)
+  {
+    if ((xmlStrcmp ((const xmlChar *) "comment", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "text", child->name) == 0))
+      /* ignore */;
+    else if (xmlStrcmp ((const xmlChar *) "race", child->name) == 0)
+      ascent_xml_read_int (doc, child, &pi->race);
+    else if (xmlStrcmp ((const xmlChar *) "class", child->name) == 0)
+      ascent_xml_read_int (doc, child, &pi->class);
+    else if (xmlStrcmp ((const xmlChar *) "gender", child->name) == 0)
+      ascent_xml_read_int (doc, child, &pi->gender);
+    else if (xmlStrcmp ((const xmlChar *) "level", child->name) == 0)
+      ascent_xml_read_int (doc, child, &pi->level);
+    else if (xmlStrcmp ((const xmlChar *) "latency", child->name) == 0)
+      ascent_xml_read_int (doc, child, &pi->latency);
+    else if ((xmlStrcmp ((const xmlChar *) "name", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "pvprank", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "map", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "areaid", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "xpos", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "ypos", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "onime", child->name) == 0))
+      /* ignore */;
+    else
+    {
+      WARNING ("ascent plugin: ascent_xml_status: Unknown tag: %s", child->name);
+    }
+  } /* for (child) */
+
+  return (0);
+} /* }}} int ascent_xml_sessions_plr */
+
+static int ascent_xml_sessions (xmlDoc *doc, xmlNode *node) /* {{{ */
+{
+  xmlNode *child;
+  player_stats_t ps;
+
+  memset (&ps, 0, sizeof (ps));
+
+  for (child = node->xmlChildrenNode; child != NULL; child = child->next)
+  {
+    if ((xmlStrcmp ((const xmlChar *) "comment", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "text", child->name) == 0))
+      /* ignore */;
+    else if (xmlStrcmp ((const xmlChar *) "plr", child->name) == 0)
+    {
+      int status;
+      player_info_t pi = PLAYER_INFO_STATIC_INIT;
+
+      status = ascent_xml_sessions_plr (doc, child, &pi);
+      if (status == 0)
+        ascent_account_player (&ps, &pi);
+    }
+    else
+    {
+      WARNING ("ascent plugin: ascent_xml_status: Unknown tag: %s", child->name);
+    }
+  } /* for (child) */
+
+  ascent_submit_players (&ps);
+
+  return (0);
+} /* }}} int ascent_xml_sessions */
+
+static int ascent_xml_status (xmlDoc *doc, xmlNode *node) /* {{{ */
+{
+  xmlNode *child;
+
+  for (child = node->xmlChildrenNode; child != NULL; child = child->next)
+  {
+    if ((xmlStrcmp ((const xmlChar *) "comment", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "text", child->name) == 0))
+      /* ignore */;
+    else if (xmlStrcmp ((const xmlChar *) "alliance", child->name) == 0)
+      ascent_xml_submit_gauge (doc, child, NULL, "players", "alliance");
+    else if (xmlStrcmp ((const xmlChar *) "horde", child->name) == 0)
+      ascent_xml_submit_gauge (doc, child, NULL, "players", "horde");
+    else if (xmlStrcmp ((const xmlChar *) "qplayers", child->name) == 0)
+      ascent_xml_submit_gauge (doc, child, NULL, "players", "queued");
+    else if ((xmlStrcmp ((const xmlChar *) "acceptedconns", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "avglat", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "cdbquerysize", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "cpu", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "fthreads", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "gmcount", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "lastupdate", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "ontime", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "oplayers", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "peakcount", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "platform", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "ram", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "threads", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "uptime", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "wdbquerysize", child->name) == 0))
+      /* ignore */;
+    else
+    {
+      WARNING ("ascent plugin: ascent_xml_status: Unknown tag: %s", child->name);
+    }
+  } /* for (child) */
+
+  return (0);
+} /* }}} int ascent_xml_status */
+
+static int ascent_xml (const char *data) /* {{{ */
+{
+  xmlDoc *doc;
+  xmlNode *cur;
+  xmlNode *child;
+
+#if 0
+  doc = xmlParseMemory (data, strlen (data),
+      /* URL = */ "ascent.xml",
+      /* encoding = */ NULL,
+      /* options = */ 0);
+#else
+  doc = xmlParseMemory (data, strlen (data));
+#endif
+  if (doc == NULL)
+  {
+    ERROR ("ascent plugin: xmlParseMemory failed.");
+    return (-1);
+  }
+
+  cur = xmlDocGetRootElement (doc);
+  if (cur == NULL)
+  {
+    ERROR ("ascent plugin: XML document is empty.");
+    xmlFreeDoc (doc);
+    return (-1);
+  }
+
+  if (xmlStrcmp ((const xmlChar *) "serverpage", cur->name) != 0)
+  {
+    ERROR ("ascent plugin: XML root element is not \"serverpage\".");
+    xmlFreeDoc (doc);
+    return (-1);
+  }
+
+  for (child = cur->xmlChildrenNode; child != NULL; child = child->next)
+  {
+    if ((xmlStrcmp ((const xmlChar *) "comment", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "text", child->name) == 0))
+      /* ignore */;
+    else if (xmlStrcmp ((const xmlChar *) "status", child->name) == 0)
+      ascent_xml_status (doc, child);
+    else if (xmlStrcmp ((const xmlChar *) "instances", child->name) == 0)
+      /* ignore for now */;
+    else if (xmlStrcmp ((const xmlChar *) "gms", child->name) == 0)
+      /* ignore for now */;
+    else if (xmlStrcmp ((const xmlChar *) "sessions", child->name) == 0)
+      ascent_xml_sessions (doc, child);
+    else
+    {
+      WARNING ("ascent plugin: ascent_xml: Unknown tag: %s", child->name);
+    }
+  } /* for (child) */
+
+  xmlFreeDoc (doc);
+  return (0);
+} /* }}} int ascent_xml */
+
+static int config_set (char **var, const char *value) /* {{{ */
+{
+  if (*var != NULL)
+  {
+    free (*var);
+    *var = NULL;
+  }
+
+  if ((*var = strdup (value)) == NULL)
+    return (1);
+  else
+    return (0);
+} /* }}} int config_set */
+
+static int ascent_config (const char *key, const char *value) /* {{{ */
+{
+  if (strcasecmp (key, "URL") == 0)
+    return (config_set (&url, value));
+  else if (strcasecmp (key, "User") == 0)
+    return (config_set (&user, value));
+  else if (strcasecmp (key, "Password") == 0)
+    return (config_set (&pass, value));
+  else if (strcasecmp (key, "VerifyPeer") == 0)
+    return (config_set (&verify_peer, value));
+  else if (strcasecmp (key, "VerifyHost") == 0)
+    return (config_set (&verify_host, value));
+  else if (strcasecmp (key, "CACert") == 0)
+    return (config_set (&cacert, value));
+  else
+    return (-1);
+} /* }}} int ascent_config */
+
+static int ascent_init (void) /* {{{ */
+{
+  static char credentials[1024];
+
+  if (url == NULL)
+  {
+    WARNING ("ascent plugin: ascent_init: No URL configured, "
+        "returning an error.");
+    return (-1);
+  }
+
+  if (curl != NULL)
+  {
+    curl_easy_cleanup (curl);
+  }
+
+  if ((curl = curl_easy_init ()) == NULL)
+  {
+    ERROR ("ascent plugin: ascent_init: curl_easy_init failed.");
+    return (-1);
+  }
+
+  curl_easy_setopt (curl, CURLOPT_NOSIGNAL, 1);
+  curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, ascent_curl_callback);
+  curl_easy_setopt (curl, CURLOPT_USERAGENT, PACKAGE_NAME"/"PACKAGE_VERSION);
+  curl_easy_setopt (curl, CURLOPT_ERRORBUFFER, ascent_curl_error);
+
+  if (user != NULL)
+  {
+    int status;
+
+    status = ssnprintf (credentials, sizeof (credentials), "%s:%s",
+        user, (pass == NULL) ? "" : pass);
+    if ((status < 0) || ((size_t) status >= sizeof (credentials)))
+    {
+      ERROR ("ascent plugin: ascent_init: Returning an error because the "
+          "credentials have been truncated.");
+      return (-1);
+    }
+
+    curl_easy_setopt (curl, CURLOPT_USERPWD, credentials);
+  }
+
+  curl_easy_setopt (curl, CURLOPT_URL, url);
+  curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, 1);
+
+  if ((verify_peer == NULL) || IS_TRUE (verify_peer))
+    curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 1);
+  else
+    curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0);
+
+  if ((verify_host == NULL) || IS_TRUE (verify_host))
+    curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 2);
+  else
+    curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0);
+
+  if (cacert != NULL)
+    curl_easy_setopt (curl, CURLOPT_CAINFO, cacert);
+
+  return (0);
+} /* }}} int ascent_init */
+
+static int ascent_read (void) /* {{{ */
+{
+  int status;
+
+  if (curl == NULL)
+  {
+    ERROR ("ascent plugin: I don't have a CURL object.");
+    return (-1);
+  }
+
+  if (url == NULL)
+  {
+    ERROR ("ascent plugin: No URL has been configured.");
+    return (-1);
+  }
+
+  ascent_buffer_fill = 0;
+  if (curl_easy_perform (curl) != 0)
+  {
+    ERROR ("ascent plugin: curl_easy_perform failed: %s",
+        ascent_curl_error);
+    return (-1);
+  }
+
+  status = ascent_xml (ascent_buffer);
+  if (status != 0)
+    return (-1);
+  else
+    return (0);
+} /* }}} int ascent_read */
+
+void module_register (void)
+{
+  plugin_register_config ("ascent", ascent_config, config_keys, config_keys_num);
+  plugin_register_init ("ascent", ascent_init);
+  plugin_register_read ("ascent", ascent_read);
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 ts=8 et fdm=marker : */
diff --git a/src/battery.c b/src/battery.c
new file mode 100644 (file)
index 0000000..4178d8b
--- /dev/null
@@ -0,0 +1,537 @@
+/**
+ * collectd - src/battery.c
+ * Copyright (C) 2006,2007  Florian octo Forster
+ * Copyright (C) 2008       Michał Mirosław
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ *   Michał Mirosław <mirq-linux at rere.qmqm.pl>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include "utils_complain.h"
+
+#if HAVE_MACH_MACH_TYPES_H
+#  include <mach/mach_types.h>
+#endif
+#if HAVE_MACH_MACH_INIT_H
+#  include <mach/mach_init.h>
+#endif
+#if HAVE_MACH_MACH_ERROR_H
+#  include <mach/mach_error.h>
+#endif
+#if HAVE_COREFOUNDATION_COREFOUNDATION_H
+#  include <CoreFoundation/CoreFoundation.h>
+#endif
+#if HAVE_IOKIT_IOKITLIB_H
+#  include <IOKit/IOKitLib.h>
+#endif
+#if HAVE_IOKIT_IOTYPES_H
+#  include <IOKit/IOTypes.h>
+#endif
+#if HAVE_IOKIT_PS_IOPOWERSOURCES_H
+#  include <IOKit/ps/IOPowerSources.h>
+#endif
+#if HAVE_IOKIT_PS_IOPSKEYS_H
+#  include <IOKit/ps/IOPSKeys.h>
+#endif
+
+#if !HAVE_IOKIT_IOKITLIB_H && !HAVE_IOKIT_PS_IOPOWERSOURCES_H && !KERNEL_LINUX
+# error "No applicable input method."
+#endif
+
+#define INVALID_VALUE 47841.29
+
+#if HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H
+       /* No global variables */
+/* #endif HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H */
+
+#elif KERNEL_LINUX
+static int   battery_pmu_num = 0;
+static char *battery_pmu_file = "/proc/pmu/battery_%i";
+static const char *battery_acpi_dir = "/proc/acpi/battery";
+#endif /* KERNEL_LINUX */
+
+static int battery_init (void)
+{
+#if HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H
+       /* No init neccessary */
+/* #endif HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H */
+
+#elif KERNEL_LINUX
+       int len;
+       char filename[128];
+
+       for (battery_pmu_num = 0; ; battery_pmu_num++)
+       {
+               len = ssnprintf (filename, sizeof (filename), battery_pmu_file, battery_pmu_num);
+
+               if ((len < 0) || ((unsigned int)len >= sizeof (filename)))
+                       break;
+
+               if (access (filename, R_OK))
+                       break;
+       }
+#endif /* KERNEL_LINUX */
+
+       return (0);
+}
+
+static void battery_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_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));
+
+       plugin_dispatch_values (&vl);
+} /* void battery_submit */
+
+#if HAVE_IOKIT_PS_IOPOWERSOURCES_H || HAVE_IOKIT_IOKITLIB_H
+double dict_get_double (CFDictionaryRef dict, char *key_string)
+{
+       double      val_double;
+       long long   val_int;
+       CFNumberRef val_obj;
+       CFStringRef key_obj;
+
+       key_obj = CFStringCreateWithCString (kCFAllocatorDefault, key_string,
+                       kCFStringEncodingASCII);
+       if (key_obj == NULL)
+       {
+               DEBUG ("CFStringCreateWithCString (%s) failed.\n", key_string);
+               return (INVALID_VALUE);
+       }
+
+       if ((val_obj = CFDictionaryGetValue (dict, key_obj)) == NULL)
+       {
+               DEBUG ("CFDictionaryGetValue (%s) failed.", key_string);
+               CFRelease (key_obj);
+               return (INVALID_VALUE);
+       }
+       CFRelease (key_obj);
+
+       if (CFGetTypeID (val_obj) == CFNumberGetTypeID ())
+       {
+               if (CFNumberIsFloatType (val_obj))
+               {
+                       CFNumberGetValue (val_obj,
+                                       kCFNumberDoubleType,
+                                       &val_double);
+               }
+               else
+               {
+                       CFNumberGetValue (val_obj,
+                                       kCFNumberLongLongType,
+                                       &val_int);
+                       val_double = val_int;
+               }
+       }
+       else
+       {
+               DEBUG ("CFGetTypeID (val_obj) = %i", (int) CFGetTypeID (val_obj));
+               return (INVALID_VALUE);
+       }
+
+       return (val_double);
+}
+#endif /* HAVE_IOKIT_PS_IOPOWERSOURCES_H || HAVE_IOKIT_IOKITLIB_H */
+
+#if HAVE_IOKIT_PS_IOPOWERSOURCES_H
+static void get_via_io_power_sources (double *ret_charge,
+               double *ret_current,
+               double *ret_voltage)
+{
+       CFTypeRef       ps_raw;
+       CFArrayRef      ps_array;
+       int             ps_array_len;
+       CFDictionaryRef ps_dict;
+       CFTypeRef       ps_obj;
+
+       double temp_double;
+       int i;
+
+       ps_raw       = IOPSCopyPowerSourcesInfo ();
+       ps_array     = IOPSCopyPowerSourcesList (ps_raw);
+       ps_array_len = CFArrayGetCount (ps_array);
+
+       DEBUG ("ps_array_len == %i", ps_array_len);
+
+       for (i = 0; i < ps_array_len; i++)
+       {
+               ps_obj  = CFArrayGetValueAtIndex (ps_array, i);
+               ps_dict = IOPSGetPowerSourceDescription (ps_raw, ps_obj);
+
+               if (ps_dict == NULL)
+               {
+                       DEBUG ("IOPSGetPowerSourceDescription failed.");
+                       continue;
+               }
+
+               if (CFGetTypeID (ps_dict) != CFDictionaryGetTypeID ())
+               {
+                       DEBUG ("IOPSGetPowerSourceDescription did not return a CFDictionaryRef");
+                       continue;
+               }
+
+               /* FIXME: Check if this is really an internal battery */
+
+               if (*ret_charge == INVALID_VALUE)
+               {
+                       /* This is the charge in percent. */
+                       temp_double = dict_get_double (ps_dict,
+                                       kIOPSCurrentCapacityKey);
+                       if ((temp_double != INVALID_VALUE)
+                                       && (temp_double >= 0.0)
+                                       && (temp_double <= 100.0))
+                               *ret_charge = temp_double;
+               }
+
+               if (*ret_current == INVALID_VALUE)
+               {
+                       temp_double = dict_get_double (ps_dict,
+                                       kIOPSCurrentKey);
+                       if (temp_double != INVALID_VALUE)
+                               *ret_current = temp_double / 1000.0;
+               }
+
+               if (*ret_voltage == INVALID_VALUE)
+               {
+                       temp_double = dict_get_double (ps_dict,
+                                       kIOPSVoltageKey);
+                       if (temp_double != INVALID_VALUE)
+                               *ret_voltage = temp_double / 1000.0;
+               }
+       }
+
+       CFRelease(ps_array);
+       CFRelease(ps_raw);
+}
+#endif /* HAVE_IOKIT_PS_IOPOWERSOURCES_H */
+
+#if HAVE_IOKIT_IOKITLIB_H
+static void get_via_generic_iokit (double *ret_charge,
+               double *ret_current,
+               double *ret_voltage)
+{
+       kern_return_t   status;
+       io_iterator_t   iterator;
+       io_object_t     io_obj;
+
+       CFDictionaryRef bat_root_dict;
+       CFArrayRef      bat_info_arry;
+       CFIndex         bat_info_arry_len;
+       CFIndex         bat_info_arry_pos;
+       CFDictionaryRef bat_info_dict;
+
+       double temp_double;
+
+       status = IOServiceGetMatchingServices (kIOMasterPortDefault,
+                       IOServiceNameMatching ("battery"),
+                       &iterator);
+       if (status != kIOReturnSuccess)
+       {
+               DEBUG ("IOServiceGetMatchingServices failed.");
+               return;
+       }
+
+       while ((io_obj = IOIteratorNext (iterator)))
+       {
+               status = IORegistryEntryCreateCFProperties (io_obj,
+                               (CFMutableDictionaryRef *) &bat_root_dict,
+                               kCFAllocatorDefault,
+                               kNilOptions);
+               if (status != kIOReturnSuccess)
+               {
+                       DEBUG ("IORegistryEntryCreateCFProperties failed.");
+                       continue;
+               }
+
+               bat_info_arry = (CFArrayRef) CFDictionaryGetValue (bat_root_dict,
+                               CFSTR ("IOBatteryInfo"));
+               if (bat_info_arry == NULL)
+               {
+                       CFRelease (bat_root_dict);
+                       continue;
+               }
+               bat_info_arry_len = CFArrayGetCount (bat_info_arry);
+
+               for (bat_info_arry_pos = 0;
+                               bat_info_arry_pos < bat_info_arry_len;
+                               bat_info_arry_pos++)
+               {
+                       bat_info_dict = (CFDictionaryRef) CFArrayGetValueAtIndex (bat_info_arry, bat_info_arry_pos);
+
+                       if (*ret_charge == INVALID_VALUE)
+                       {
+                               temp_double = dict_get_double (bat_info_dict,
+                                               "Capacity");
+                               if (temp_double != INVALID_VALUE)
+                                       *ret_charge = temp_double / 1000.0;
+                       }
+
+                       if (*ret_current == INVALID_VALUE)
+                       {
+                               temp_double = dict_get_double (bat_info_dict,
+                                               "Current");
+                               if (temp_double != INVALID_VALUE)
+                                       *ret_current = temp_double / 1000.0;
+                       }
+
+                       if (*ret_voltage == INVALID_VALUE)
+                       {
+                               temp_double = dict_get_double (bat_info_dict,
+                                               "Voltage");
+                               if (temp_double != INVALID_VALUE)
+                                       *ret_voltage = temp_double / 1000.0;
+                       }
+               }
+               
+               CFRelease (bat_root_dict);
+       }
+
+       IOObjectRelease (iterator);
+}
+#endif /* HAVE_IOKIT_IOKITLIB_H */
+
+#if KERNEL_LINUX
+static int battery_read_acpi (const char __attribute__((unused)) *dir,
+               const char *name, void __attribute__((unused)) *user_data)
+{
+       double  current = INVALID_VALUE;
+       double  voltage = INVALID_VALUE;
+       double  charge  = INVALID_VALUE;
+       double *valptr = NULL;
+       int charging = 0;
+
+       char filename[256];
+       FILE *fh;
+
+       char buffer[1024];
+       char *fields[8];
+       int numfields;
+       char *endptr;
+       int len;
+
+       len = ssnprintf (filename, sizeof (filename), "%s/%s/state", battery_acpi_dir, name);
+
+       if ((len < 0) || ((unsigned int)len >= sizeof (filename)))
+               return -1;
+
+       if ((fh = fopen (filename, "r")) == NULL) {
+               char errbuf[1024];
+               ERROR ("Cannot open `%s': %s", filename,
+                       sstrerror (errno, errbuf, sizeof (errbuf)));
+               return -1;
+       }
+
+       /*
+        * [11:00] <@tokkee> $ cat /proc/acpi/battery/BAT1/state
+        * [11:00] <@tokkee> present:                 yes
+        * [11:00] <@tokkee> capacity state:          ok
+        * [11:00] <@tokkee> charging state:          charging
+        * [11:00] <@tokkee> present rate:            1724 mA
+        * [11:00] <@tokkee> remaining capacity:      4136 mAh
+        * [11:00] <@tokkee> present voltage:         12428 mV
+        */
+       while (fgets (buffer, sizeof (buffer), fh) != NULL)
+       {
+               numfields = strsplit (buffer, fields, 8);
+
+               if (numfields < 3)
+                       continue;
+
+               if ((strcmp (fields[0], "charging") == 0)
+                               && (strcmp (fields[1], "state:") == 0))
+               {
+                       if (strcmp (fields[2], "charging") == 0)
+                               charging = 1;
+                       else
+                               charging = 0;
+                       continue;
+               }
+
+               if ((strcmp (fields[0], "present") == 0)
+                               && (strcmp (fields[1], "rate:") == 0))
+                       valptr = &current;
+               else if ((strcmp (fields[0], "remaining") == 0)
+                               && (strcmp (fields[1], "capacity:") == 0))
+                       valptr = &charge;
+               else if ((strcmp (fields[0], "present") == 0)
+                               && (strcmp (fields[1], "voltage:") == 0))
+                       valptr = &voltage;
+               else
+                       continue;
+
+               endptr = NULL;
+               errno  = 0;
+               *valptr = strtod (fields[2], &endptr) / 1000.0;
+
+               if ((fields[2] == endptr) || (errno != 0))
+                       *valptr = INVALID_VALUE;
+       } /* while (fgets (buffer, sizeof (buffer), fh) != NULL) */
+
+       fclose (fh);
+
+       if ((current != INVALID_VALUE) && (charging == 0))
+                       current *= -1;
+
+       if (charge != INVALID_VALUE)
+               battery_submit ("0", "charge", charge);
+       if (current != INVALID_VALUE)
+               battery_submit ("0", "current", current);
+       if (voltage != INVALID_VALUE)
+               battery_submit ("0", "voltage", voltage);
+
+       return 0;
+}
+#endif /* KERNEL_LINUX */
+
+
+static int battery_read (void)
+{
+#if HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H
+       double charge  = INVALID_VALUE; /* Current charge in Ah */
+       double current = INVALID_VALUE; /* Current in A */
+       double voltage = INVALID_VALUE; /* Voltage in V */
+
+       double charge_rel = INVALID_VALUE; /* Current charge in percent */
+       double charge_abs = INVALID_VALUE; /* Total capacity */
+
+#if HAVE_IOKIT_PS_IOPOWERSOURCES_H
+       get_via_io_power_sources (&charge_rel, &current, &voltage);
+#endif
+#if HAVE_IOKIT_IOKITLIB_H
+       get_via_generic_iokit (&charge_abs, &current, &voltage);
+#endif
+
+       if ((charge_rel != INVALID_VALUE) && (charge_abs != INVALID_VALUE))
+               charge = charge_abs * charge_rel / 100.0;
+
+       if (charge != INVALID_VALUE)
+               battery_submit ("0", "charge", charge);
+       if (current != INVALID_VALUE)
+               battery_submit ("0", "current", current);
+       if (voltage != INVALID_VALUE)
+               battery_submit ("0", "voltage", voltage);
+/* #endif HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H */
+
+#elif KERNEL_LINUX
+       static c_complain_t acpi_dir_complaint = C_COMPLAIN_INIT_STATIC;
+
+       FILE *fh;
+       char buffer[1024];
+       char filename[256];
+       
+       char *fields[8];
+       int numfields;
+
+       int i;
+       int len;
+
+       for (i = 0; i < battery_pmu_num; i++)
+       {
+               char    batnum_str[256];
+               double  current = INVALID_VALUE;
+               double  voltage = INVALID_VALUE;
+               double  charge  = INVALID_VALUE;
+               double *valptr = NULL;
+
+               len = ssnprintf (filename, sizeof (filename), battery_pmu_file, i);
+               if ((len < 0) || ((unsigned int)len >= sizeof (filename)))
+                       continue;
+
+               len = ssnprintf (batnum_str, sizeof (batnum_str), "%i", i);
+               if ((len < 0) || ((unsigned int)len >= sizeof (batnum_str)))
+                       continue;
+
+               if ((fh = fopen (filename, "r")) == NULL)
+                       continue;
+
+               while (fgets (buffer, sizeof (buffer), fh) != NULL)
+               {
+                       numfields = strsplit (buffer, fields, 8);
+
+                       if (numfields < 3)
+                               continue;
+
+                       if (strcmp ("current", fields[0]) == 0)
+                               valptr = &current;
+                       else if (strcmp ("voltage", fields[0]) == 0)
+                               valptr = &voltage;
+                       else if (strcmp ("charge", fields[0]) == 0)
+                               valptr = &charge;
+                       else
+                               valptr = NULL;
+
+                       if (valptr != NULL)
+                       {
+                               char *endptr;
+
+                               endptr = NULL;
+                               errno  = 0;
+
+                               *valptr = strtod (fields[2], &endptr) / 1000.0;
+
+                               if ((fields[2] == endptr) || (errno != 0))
+                                       *valptr = INVALID_VALUE;
+                       }
+               }
+
+               fclose (fh);
+               fh = NULL;
+
+               if (charge != INVALID_VALUE)
+                       battery_submit ("0", "charge", charge);
+               if (current != INVALID_VALUE)
+                       battery_submit ("0", "current", current);
+               if (voltage != INVALID_VALUE)
+                       battery_submit ("0", "voltage", voltage);
+       }
+
+       if (0 == access (battery_acpi_dir, R_OK))
+               walk_directory (battery_acpi_dir, battery_read_acpi,
+                               /* user_data = */ NULL,
+                               /* include hidden */ 0);
+       else
+       {
+               char errbuf[1024];
+               c_complain_once (LOG_WARNING, &acpi_dir_complaint,
+                               "battery plugin: Failed to access `%s': %s",
+                               battery_acpi_dir,
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+       }
+
+#endif /* KERNEL_LINUX */
+
+       return (0);
+}
+
+void module_register (void)
+{
+       plugin_register_init ("battery", battery_init);
+       plugin_register_read ("battery", battery_read);
+} /* void module_register */
diff --git a/src/bind.c b/src/bind.c
new file mode 100644 (file)
index 0000000..b640a59
--- /dev/null
@@ -0,0 +1,1442 @@
+/**
+ * collectd - src/bind.c
+ * Copyright (C) 2009       Bruno Prémont
+ * Copyright (C) 2009,2010  Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Bruno Prémont <bonbons at linux-vserver.org>
+ *   Florian Forster <octo at collectd.org>
+ **/
+
+#include "config.h"
+
+#if STRPTIME_NEEDS_STANDARDS
+# ifndef _ISOC99_SOURCE
+#  define _ISOC99_SOURCE 1
+# endif
+# ifndef _POSIX_C_SOURCE
+#  define _POSIX_C_SOURCE 200112L
+# endif
+# ifndef _XOPEN_SOURCE
+#  define _XOPEN_SOURCE 500
+# endif
+#endif /* STRPTIME_NEEDS_STANDARDS */
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+
+/* Some versions of libcurl don't include this themselves and then don't have
+ * fd_set available. */
+#if HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif
+
+#include <curl/curl.h>
+#include <libxml/parser.h>
+#include <libxml/xpath.h>
+
+#ifndef BIND_DEFAULT_URL
+# define BIND_DEFAULT_URL "http://localhost:8053/"
+#endif
+
+/* 
+ * Some types used for the callback functions. `translation_table_ptr_t' and
+ * `list_info_ptr_t' are passed to the callbacks in the `void *user_data'
+ * pointer.
+ */
+typedef int (*list_callback_t) (const char *name, value_t value,
+    time_t current_time, void *user_data);
+
+struct cb_view_s
+{
+  char *name;
+
+  int qtypes;
+  int resolver_stats;
+  int cacherrsets;
+
+  char **zones;
+  size_t zones_num;
+};
+typedef struct cb_view_s cb_view_t;
+
+struct translation_info_s
+{
+  const char *xml_name;
+  const char *type;
+  const char *type_instance;
+};
+typedef struct translation_info_s translation_info_t;
+
+struct translation_table_ptr_s
+{
+  const translation_info_t *table;
+  size_t table_length;
+  const char *plugin_instance;
+};
+typedef struct translation_table_ptr_s translation_table_ptr_t;
+
+struct list_info_ptr_s
+{
+  const char *plugin_instance;
+  const char *type;
+};
+typedef struct list_info_ptr_s list_info_ptr_t;
+
+static char *url                   = NULL;
+static int global_opcodes          = 1;
+static int global_qtypes           = 1;
+static int global_server_stats     = 1;
+static int global_zone_maint_stats = 1;
+static int global_resolver_stats   = 0;
+static int global_memory_stats     = 1;
+
+static cb_view_t *views = NULL;
+static size_t     views_num = 0;
+
+static CURL *curl = NULL;
+
+static char  *bind_buffer = NULL;
+static size_t bind_buffer_size = 0;
+static size_t bind_buffer_fill = 0;
+static char   bind_curl_error[CURL_ERROR_SIZE];
+
+/* Translation table for the `nsstats' values. */
+static const translation_info_t nsstats_translation_table[] = /* {{{ */
+{
+  /* Requests */
+  { "Requestv4",       "dns_request",  "IPv4"        },
+  { "Requestv6",       "dns_request",  "IPv6"        },
+  { "ReqEdns0",        "dns_request",  "EDNS0"       },
+  { "ReqBadEDNSVer",   "dns_request",  "BadEDNSVer"  },
+  { "ReqTSIG",         "dns_request",  "TSIG"        },
+  { "ReqSIG0",         "dns_request",  "SIG0"        },
+  { "ReqBadSIG",       "dns_request",  "BadSIG"      },
+  { "ReqTCP",          "dns_request",  "TCP"         },
+  /* Rejects */
+  { "AuthQryRej",      "dns_reject",   "authorative" },
+  { "RecQryRej",       "dns_reject",   "recursive"   },
+  { "XfrRej",          "dns_reject",   "transfer"    },
+  { "UpdateRej",       "dns_reject",   "update"      },
+  /* Responses */
+  { "Response",        "dns_response", "normal"      },
+  { "TruncatedResp",   "dns_response", "truncated"   },
+  { "RespEDNS0",       "dns_response", "EDNS0"       },
+  { "RespTSIG",        "dns_response", "TSIG"        },
+  { "RespSIG0",        "dns_response", "SIG0"        },
+  /* Queries */
+  { "QryAuthAns",      "dns_query",    "authorative" },
+  { "QryNoauthAns",    "dns_query",    "nonauth"     },
+  { "QryReferral",     "dns_query",    "referral"    },
+  { "QryRecursion",    "dns_query",    "recursion"   },
+  { "QryDuplicate",    "dns_query",    "dupliate"    },
+  { "QryDropped",      "dns_query",    "dropped"     },
+  { "QryFailure",      "dns_query",    "failure"     },
+  /* Response codes */
+  { "QrySuccess",      "dns_rcode",    "tx-NOERROR"  },
+  { "QryNxrrset",      "dns_rcode",    "tx-NXRRSET"  },
+  { "QrySERVFAIL",     "dns_rcode",    "tx-SERVFAIL" },
+  { "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"       },
+#endif
+};
+static int nsstats_translation_table_length =
+  STATIC_ARRAY_SIZE (nsstats_translation_table);
+/* }}} */
+
+/* Translation table for the `zonestats' values. */
+static const translation_info_t zonestats_translation_table[] = /* {{{ */
+{
+  /* Notify's */
+  { "NotifyOutv4",     "dns_notify",   "tx-IPv4"     },
+  { "NotifyOutv6",     "dns_notify",   "tx-IPv6"     },
+  { "NotifyInv4",      "dns_notify",   "rx-IPv4"     },
+  { "NotifyInv6",      "dns_notify",   "rx-IPv6"     },
+  { "NotifyRej",       "dns_notify",   "rejected"    },
+  /* SOA/AXFS/IXFS requests */
+  { "SOAOutv4",        "dns_opcode",   "SOA-IPv4"    },
+  { "SOAOutv6",        "dns_opcode",   "SOA-IPv6"    },
+  { "AXFRReqv4",       "dns_opcode",   "AXFR-IPv4"   },
+  { "AXFRReqv6",       "dns_opcode",   "AXFR-IPv6"   },
+  { "IXFRReqv4",       "dns_opcode",   "IXFR-IPv4"   },
+  { "IXFRReqv6",       "dns_opcode",   "IXFR-IPv6"   },
+  /* Domain transfers */
+  { "XfrSuccess",      "dns_transfer", "success"     },
+  { "XfrFail",         "dns_transfer", "failure"     }
+};
+static int zonestats_translation_table_length =
+  STATIC_ARRAY_SIZE (zonestats_translation_table);
+/* }}} */
+
+/* Translation table for the `resstats' values. */
+static const translation_info_t resstats_translation_table[] = /* {{{ */
+{
+  /* Generic resolver information */
+  { "Queryv4",         "dns_query",    "IPv4"        },
+  { "Queryv6",         "dns_query",    "IPv6"        },
+  { "Responsev4",      "dns_response", "IPv4"        },
+  { "Responsev6",      "dns_response", "IPv6"        },
+  /* Received response codes */
+  { "NXDOMAIN",        "dns_rcode",    "rx-NXDOMAIN" },
+  { "SERVFAIL",        "dns_rcode",    "rx-SERVFAIL" },
+  { "FORMERR",         "dns_rcode",    "rx-FORMERR"  },
+  { "OtherError",      "dns_rcode",    "rx-OTHER"    },
+  { "EDNS0Fail",       "dns_rcode",    "rx-EDNS0Fail"},
+  /* Received responses */
+  { "Mismatch",        "dns_response", "mismatch"    },
+  { "Truncated",       "dns_response", "truncated"   },
+  { "Lame",            "dns_response", "lame"        },
+  { "Retry",           "dns_query",    "retry"       },
+#if 0
+  { "GlueFetchv4",     "type", "type_instance" },
+  { "GlueFetchv6",     "type", "type_instance" },
+  { "GlueFetchv4Fail", "type", "type_instance" },
+  { "GlueFetchv6Fail", "type", "type_instance" },
+#endif
+  /* DNSSEC information */
+  { "ValAttempt",      "dns_resolver", "DNSSEC-attempt" },
+  { "ValOk",           "dns_resolver", "DNSSEC-okay"    },
+  { "ValNegOk",        "dns_resolver", "DNSSEC-negokay" },
+  { "ValFail",         "dns_resolver", "DNSSEC-fail"    }
+};
+static int resstats_translation_table_length =
+  STATIC_ARRAY_SIZE (resstats_translation_table);
+/* }}} */
+
+/* Translation table for the `memory/summary' values. */
+static const translation_info_t memsummary_translation_table[] = /* {{{ */
+{
+  { "TotalUse",        "memory",       "TotalUse"    },
+  { "InUse",           "memory",       "InUse"       },
+  { "BlockSize",       "memory",       "BlockSize"   },
+  { "ContextSize",     "memory",       "ContextSize" },
+  { "Lost",            "memory",       "Lost"        }
+};
+static int memsummary_translation_table_length =
+  STATIC_ARRAY_SIZE (memsummary_translation_table);
+/* }}} */
+
+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_len = 1;
+  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));
+    replace_special (vl.plugin_instance, sizeof (vl.plugin_instance));
+  }
+  sstrncpy(vl.type, type, sizeof(vl.type));
+  if (type_instance) {
+    sstrncpy(vl.type_instance, type_instance,
+        sizeof(vl.type_instance));
+    replace_special (vl.plugin_instance, sizeof (vl.plugin_instance));
+  }
+  plugin_dispatch_values(&vl);
+} /* }}} void submit */
+
+static size_t bind_curl_callback (void *buf, size_t size, /* {{{ */
+    size_t nmemb, void __attribute__((unused)) *stream)
+{
+  size_t len = size * nmemb;
+
+  if (len <= 0)
+    return (len);
+
+  if ((bind_buffer_fill + len) >= bind_buffer_size)
+  {
+    char *temp;
+
+    temp = realloc(bind_buffer, bind_buffer_fill + len + 1);
+    if (temp == NULL)
+    {
+      ERROR ("bind plugin: realloc failed.");
+      return (0);
+    }
+    bind_buffer = temp;
+    bind_buffer_size = bind_buffer_fill + len + 1;
+  }
+
+  memcpy (bind_buffer + bind_buffer_fill, (char *) buf, len);
+  bind_buffer_fill += len;
+  bind_buffer[bind_buffer_fill] = 0;
+
+  return (len);
+} /* }}} size_t bind_curl_callback */
+
+/*
+ * Callback, that's called with a translation table.
+ * (Plugin instance is fixed, type and type instance come from lookup table.)
+ */
+static int bind_xml_table_callback (const char *name, value_t value, /* {{{ */
+    time_t current_time, void *user_data)
+{
+  translation_table_ptr_t *table = (translation_table_ptr_t *) user_data;
+  size_t i;
+
+  if (table == NULL)
+    return (-1);
+
+  for (i = 0; i < table->table_length; i++)
+  {
+    if (strcmp (table->table[i].xml_name, name) != 0)
+      continue;
+
+    submit (current_time,
+        table->plugin_instance,
+        table->table[i].type,
+        table->table[i].type_instance,
+        value);
+    break;
+  }
+
+  return (0);
+} /* }}} int bind_xml_table_callback */
+
+/*
+ * Callback, that's used for lists.
+ * (Plugin instance and type are fixed, xml name is used as type instance.)
+ */
+static int bind_xml_list_callback (const char *name, /* {{{ */
+    value_t value, time_t current_time, void *user_data)
+{
+  list_info_ptr_t *list_info = (list_info_ptr_t *) user_data;
+
+  if (list_info == NULL)
+    return (-1);
+
+  submit (current_time,
+      list_info->plugin_instance,
+      list_info->type,
+      /* type instance = */ name,
+      value);
+
+  return (0);
+} /* }}} int bind_xml_list_callback */
+
+static int bind_xml_read_derive (xmlDoc *doc, xmlNode *node, /* {{{ */
+    derive_t *ret_value)
+{
+  char *str_ptr;
+  value_t value;
+  int status;
+
+  str_ptr = (char *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+  if (str_ptr == NULL)
+  {
+    ERROR ("bind plugin: bind_xml_read_derive: xmlNodeListGetString failed.");
+    return (-1);
+  }
+
+  status = parse_value (str_ptr, &value, DS_TYPE_DERIVE);
+  if (status != 0)
+  {
+    ERROR ("bind plugin: Parsing string \"%s\" to derive value failed.",
+        str_ptr);
+    return (-1);
+  }
+
+  *ret_value = value.derive;
+  return (0);
+} /* }}} int bind_xml_read_derive */
+
+static int bind_xml_read_gauge (xmlDoc *doc, xmlNode *node, /* {{{ */
+    gauge_t *ret_value)
+{
+  char *str_ptr, *end_ptr;
+  double value;
+
+  str_ptr = (char *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+  if (str_ptr == NULL)
+  {
+    ERROR ("bind plugin: bind_xml_read_gauge: xmlNodeListGetString failed.");
+    return (-1);
+  }
+
+  errno = 0;
+  value = strtod (str_ptr, &end_ptr);
+  xmlFree(str_ptr);
+  if (str_ptr == end_ptr || errno)
+  {
+    if (errno && (value < 0))
+      ERROR ("bind plugin: bind_xml_read_gauge: strtod failed with underflow.");
+    else if (errno && (value > 0))
+      ERROR ("bind plugin: bind_xml_read_gauge: strtod failed with overflow.");
+    else
+      ERROR ("bind plugin: bind_xml_read_gauge: strtod failed.");
+    return (-1);
+  }
+
+  *ret_value = (gauge_t) value;
+  return (0);
+} /* }}} int bind_xml_read_gauge */
+
+static int bind_xml_read_timestamp (const char *xpath_expression, /* {{{ */
+    xmlDoc *doc, xmlXPathContext *xpathCtx, time_t *ret_value)
+{
+  xmlXPathObject *xpathObj = NULL;
+  xmlNode *node;
+  char *str_ptr;
+  char *tmp;
+  struct tm tm;
+
+  xpathObj = xmlXPathEvalExpression (BAD_CAST xpath_expression, xpathCtx);
+  if (xpathObj == NULL)
+  {
+    ERROR ("bind plugin: Unable to evaluate XPath expression `%s'.",
+        xpath_expression);
+    return (-1);
+  }
+
+  if ((xpathObj->nodesetval == NULL) || (xpathObj->nodesetval->nodeNr < 1))
+  {
+    xmlXPathFreeObject (xpathObj);
+    return (-1);
+  }
+
+  if (xpathObj->nodesetval->nodeNr != 1)
+  {
+    NOTICE ("bind plugin: Evaluating the XPath expression `%s' returned "
+        "%i nodes. Only handling the first one.",
+        xpath_expression, xpathObj->nodesetval->nodeNr);
+  }
+
+  node = xpathObj->nodesetval->nodeTab[0];
+
+  if (node->xmlChildrenNode == NULL)
+  {
+    ERROR ("bind plugin: bind_xml_read_timestamp: "
+        "node->xmlChildrenNode == NULL");
+    xmlXPathFreeObject (xpathObj);
+    return (-1);
+  }
+
+  str_ptr = (char *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+  if (str_ptr == NULL)
+  {
+    ERROR ("bind plugin: bind_xml_read_timestamp: xmlNodeListGetString failed.");
+    xmlXPathFreeObject (xpathObj);
+    return (-1);
+  }
+
+  memset (&tm, 0, sizeof(tm));
+  tmp = strptime (str_ptr, "%Y-%m-%dT%T", &tm);
+  xmlFree(str_ptr);
+  if (tmp == NULL)
+  {
+    ERROR ("bind plugin: bind_xml_read_timestamp: strptime failed.");
+    xmlXPathFreeObject (xpathObj);
+    return (-1);
+  }
+
+  *ret_value = mktime(&tm);
+
+  xmlXPathFreeObject (xpathObj);
+  return (0);
+} /* }}} int bind_xml_read_timestamp */
+
+/* 
+ * bind_parse_generic_name_value
+ *
+ * Reads statistics in the form:
+ * <foo>
+ *   <name>QUERY</name>
+ *   <counter>123</counter>
+ * </foo>
+ */
+static int bind_parse_generic_name_value (const char *xpath_expression, /* {{{ */
+    list_callback_t list_callback,
+    void *user_data,
+    xmlDoc *doc, xmlXPathContext *xpathCtx,
+    time_t current_time, int ds_type)
+{
+  xmlXPathObject *xpathObj = NULL;
+  int num_entries;
+  int i;
+
+  xpathObj = xmlXPathEvalExpression(BAD_CAST xpath_expression, xpathCtx);
+  if (xpathObj == NULL)
+  {
+    ERROR("bind plugin: Unable to evaluate XPath expression `%s'.",
+        xpath_expression);
+    return (-1);
+  }
+
+  num_entries = 0;
+  /* Iterate over all matching nodes. */
+  for (i = 0; xpathObj->nodesetval && (i < xpathObj->nodesetval->nodeNr); i++)
+  {
+    xmlNode *name_node = NULL;
+    xmlNode *counter = NULL;
+    xmlNode *parent;
+    xmlNode *child;
+
+    parent = xpathObj->nodesetval->nodeTab[i];
+    DEBUG ("bind plugin: bind_parse_generic_name_value: parent->name = %s;",
+        (char *) parent->name);
+
+    /* Iterate over all child nodes. */
+    for (child = parent->xmlChildrenNode;
+        child != NULL;
+        child = child->next)
+    {
+      if (child->type != XML_ELEMENT_NODE)
+        continue;
+
+      if (xmlStrcmp (BAD_CAST "name", child->name) == 0)
+        name_node = child;
+      else if (xmlStrcmp (BAD_CAST "counter", child->name) == 0)
+        counter = child;
+    }
+
+    if ((name_node != NULL) && (counter != NULL))
+    {
+      char *name = (char *) xmlNodeListGetString (doc,
+          name_node->xmlChildrenNode, 1);
+      value_t value;
+      int status;
+
+      if (ds_type == DS_TYPE_GAUGE)
+        status = bind_xml_read_gauge (doc, counter, &value.gauge);
+      else
+        status = bind_xml_read_derive (doc, counter, &value.derive);
+      if (status != 0)
+        continue;
+
+      status = (*list_callback) (name, value, current_time, user_data);
+      if (status == 0)
+        num_entries++;
+
+      xmlFree (name);
+    }
+  }
+
+  DEBUG ("bind plugin: Found %d %s for XPath expression `%s'",
+      num_entries, (num_entries == 1) ? "entry" : "entries",
+      xpath_expression);
+
+  xmlXPathFreeObject(xpathObj);
+
+  return (0);
+} /* }}} int bind_parse_generic_name_value */
+
+/* 
+ * bind_parse_generic_value_list
+ *
+ * Reads statistics in the form:
+ * <foo>
+ *   <name0>123</name0>
+ *   <name1>234</name1>
+ *   <name2>345</name2>
+ *   :
+ * </foo>
+ */
+static int bind_parse_generic_value_list (const char *xpath_expression, /* {{{ */
+    list_callback_t list_callback,
+    void *user_data,
+    xmlDoc *doc, xmlXPathContext *xpathCtx,
+    time_t current_time, int ds_type)
+{
+  xmlXPathObject *xpathObj = NULL;
+  int num_entries;
+  int i;
+
+  xpathObj = xmlXPathEvalExpression(BAD_CAST xpath_expression, xpathCtx);
+  if (xpathObj == NULL)
+  {
+    ERROR("bind plugin: Unable to evaluate XPath expression `%s'.",
+        xpath_expression);
+    return (-1);
+  }
+
+  num_entries = 0;
+  /* Iterate over all matching nodes. */
+  for (i = 0; xpathObj->nodesetval && (i < xpathObj->nodesetval->nodeNr); i++)
+  {
+    xmlNode *child;
+
+    /* Iterate over all child nodes. */
+    for (child = xpathObj->nodesetval->nodeTab[i]->xmlChildrenNode;
+        child != NULL;
+        child = child->next)
+    {
+      char *node_name;
+      value_t value;
+      int status;
+
+      if (child->type != XML_ELEMENT_NODE)
+        continue;
+
+      node_name = (char *) child->name;
+
+      if (ds_type == DS_TYPE_GAUGE)
+        status = bind_xml_read_gauge (doc, child, &value.gauge);
+      else
+        status = bind_xml_read_derive (doc, child, &value.derive);
+      if (status != 0)
+        continue;
+
+      status = (*list_callback) (node_name, value, current_time, user_data);
+      if (status == 0)
+        num_entries++;
+    }
+  }
+
+  DEBUG ("bind plugin: Found %d %s for XPath expression `%s'",
+      num_entries, (num_entries == 1) ? "entry" : "entries",
+      xpath_expression);
+
+  xmlXPathFreeObject(xpathObj);
+
+  return (0);
+} /* }}} int bind_parse_generic_value_list */
+
+static int bind_xml_stats_handle_zone (int version, xmlDoc *doc, /* {{{ */
+    xmlXPathContext *path_ctx, xmlNode *node, cb_view_t *view,
+    time_t current_time)
+{
+  xmlXPathObject *path_obj;
+  char *zone_name = NULL;
+  int i;
+  size_t j;
+
+  path_obj = xmlXPathEvalExpression (BAD_CAST "name", path_ctx);
+  if (path_obj == NULL)
+  {
+    ERROR ("bind plugin: xmlXPathEvalExpression failed.");
+    return (-1);
+  }
+
+  for (i = 0; path_obj->nodesetval && (i < path_obj->nodesetval->nodeNr); i++)
+  {
+    zone_name = (char *) xmlNodeListGetString (doc,
+        path_obj->nodesetval->nodeTab[i]->xmlChildrenNode, 1);
+    if (zone_name != NULL)
+      break;
+  }
+
+  if (zone_name == NULL)
+  {
+    ERROR ("bind plugin: Could not determine zone name.");
+    xmlXPathFreeObject (path_obj);
+    return (-1);
+  }
+
+  for (j = 0; j < view->zones_num; j++)
+  {
+    if (strcasecmp (zone_name, view->zones[j]) == 0)
+      break;
+  }
+
+  xmlFree (zone_name);
+  zone_name = NULL;
+
+  if (j >= views_num)
+  {
+    xmlXPathFreeObject (path_obj);
+    return (0);
+  }
+
+  zone_name = view->zones[j];
+
+  DEBUG ("bind plugin: bind_xml_stats_handle_zone: Found zone `%s'.",
+      zone_name);
+
+  { /* Parse the <counters> tag {{{ */
+    char plugin_instance[DATA_MAX_NAME_LEN];
+    translation_table_ptr_t table_ptr =
+    { 
+      nsstats_translation_table,
+      nsstats_translation_table_length,
+      plugin_instance
+    };
+
+    ssnprintf (plugin_instance, sizeof (plugin_instance), "%s-zone-%s",
+        view->name, zone_name);
+
+    bind_parse_generic_value_list (/* xpath = */ "counters",
+        /* callback = */ bind_xml_table_callback,
+        /* user_data = */ &table_ptr,
+        doc, path_ctx, current_time, DS_TYPE_COUNTER);
+  } /* }}} */
+
+  xmlXPathFreeObject (path_obj);
+
+  return (0);
+} /* }}} int bind_xml_stats_handle_zone */
+
+static int bind_xml_stats_search_zones (int version, xmlDoc *doc, /* {{{ */
+    xmlXPathContext *path_ctx, xmlNode *node, cb_view_t *view,
+    time_t current_time)
+{
+  xmlXPathObject *zone_nodes = NULL;
+  xmlXPathContext *zone_path_context;
+  int i;
+
+  zone_path_context = xmlXPathNewContext (doc);
+  if (zone_path_context == NULL)
+  {
+    ERROR ("bind plugin: xmlXPathNewContext failed.");
+    return (-1);
+  }
+
+  zone_nodes = xmlXPathEvalExpression (BAD_CAST "zones/zone", path_ctx);
+  if (zone_nodes == NULL)
+  {
+    ERROR ("bind plugin: Cannot find any <view> tags.");
+    xmlXPathFreeContext (zone_path_context);
+    return (-1);
+  }
+
+  for (i = 0; i < zone_nodes->nodesetval->nodeNr; i++)
+  {
+    xmlNode *node;
+
+    node = zone_nodes->nodesetval->nodeTab[i];
+    assert (node != NULL);
+
+    zone_path_context->node = node;
+
+    bind_xml_stats_handle_zone (version, doc, zone_path_context, node, view,
+        current_time);
+  }
+
+  xmlXPathFreeObject (zone_nodes);
+  xmlXPathFreeContext (zone_path_context);
+  return (0);
+} /* }}} int bind_xml_stats_search_zones */
+
+static int bind_xml_stats_handle_view (int version, xmlDoc *doc, /* {{{ */
+    xmlXPathContext *path_ctx, xmlNode *node, time_t current_time)
+{
+  xmlXPathObject *path_obj;
+  char *view_name = NULL;
+  cb_view_t *view;
+  int i;
+  size_t j;
+
+  path_obj = xmlXPathEvalExpression (BAD_CAST "name", path_ctx);
+  if (path_obj == NULL)
+  {
+    ERROR ("bind plugin: xmlXPathEvalExpression failed.");
+    return (-1);
+  }
+
+  for (i = 0; path_obj->nodesetval && (i < path_obj->nodesetval->nodeNr); i++)
+  {
+    view_name = (char *) xmlNodeListGetString (doc,
+        path_obj->nodesetval->nodeTab[i]->xmlChildrenNode, 1);
+    if (view_name != NULL)
+      break;
+  }
+
+  if (view_name == NULL)
+  {
+    ERROR ("bind plugin: Could not determine view name.");
+    xmlXPathFreeObject (path_obj);
+    return (-1);
+  }
+
+  for (j = 0; j < views_num; j++)
+  {
+    if (strcasecmp (view_name, views[j].name) == 0)
+      break;
+  }
+
+  xmlFree (view_name);
+  xmlXPathFreeObject (path_obj);
+
+  view_name = NULL;
+  path_obj = NULL;
+
+  if (j >= views_num)
+    return (0);
+
+  view = views + j;
+
+  DEBUG ("bind plugin: bind_xml_stats_handle_view: Found view `%s'.",
+      view->name);
+
+  if (view->qtypes != 0) /* {{{ */
+  {
+    char plugin_instance[DATA_MAX_NAME_LEN];
+    list_info_ptr_t list_info =
+    {
+      plugin_instance,
+      /* type = */ "dns_qtype_gauge"
+    };
+
+    ssnprintf (plugin_instance, sizeof (plugin_instance), "%s-qtypes",
+        view->name);
+
+    bind_parse_generic_name_value (/* xpath = */ "rdtype",
+        /* callback = */ bind_xml_list_callback,
+        /* user_data = */ &list_info,
+        doc, path_ctx, current_time, DS_TYPE_COUNTER);
+  } /* }}} */
+
+  if (view->resolver_stats != 0) /* {{{ */
+  {
+    char plugin_instance[DATA_MAX_NAME_LEN];
+    translation_table_ptr_t table_ptr =
+    { 
+      resstats_translation_table,
+      resstats_translation_table_length,
+      plugin_instance
+    };
+
+    ssnprintf (plugin_instance, sizeof (plugin_instance),
+        "%s-resolver_stats", view->name);
+
+    bind_parse_generic_name_value ("resstat",
+        /* callback = */ bind_xml_table_callback,
+        /* user_data = */ &table_ptr,
+        doc, path_ctx, current_time, DS_TYPE_COUNTER);
+  } /* }}} */
+
+  if (view->cacherrsets != 0) /* {{{ */
+  {
+    char plugin_instance[DATA_MAX_NAME_LEN];
+    list_info_ptr_t list_info =
+    {
+      plugin_instance,
+      /* type = */ "dns_qtype_gauge"
+    };
+
+    ssnprintf (plugin_instance, sizeof (plugin_instance), "%s-cache_rr_sets",
+        view->name);
+
+    bind_parse_generic_name_value (/* xpath = */ "cache/rrset",
+        /* callback = */ bind_xml_list_callback,
+        /* user_data = */ &list_info,
+        doc, path_ctx, current_time, DS_TYPE_GAUGE);
+  } /* }}} */
+
+  if (view->zones_num > 0)
+    bind_xml_stats_search_zones (version, doc, path_ctx, node, view,
+        current_time);
+
+  return (0);
+} /* }}} int bind_xml_stats_handle_view */
+
+static int bind_xml_stats_search_views (int version, xmlDoc *doc, /* {{{ */
+    xmlXPathContext *xpathCtx, xmlNode *statsnode, time_t current_time)
+{
+  xmlXPathObject *view_nodes = NULL;
+  xmlXPathContext *view_path_context;
+  int i;
+
+  view_path_context = xmlXPathNewContext (doc);
+  if (view_path_context == NULL)
+  {
+    ERROR ("bind plugin: xmlXPathNewContext failed.");
+    return (-1);
+  }
+
+  view_nodes = xmlXPathEvalExpression (BAD_CAST "views/view", xpathCtx);
+  if (view_nodes == NULL)
+  {
+    ERROR ("bind plugin: Cannot find any <view> tags.");
+    xmlXPathFreeContext (view_path_context);
+    return (-1);
+  }
+
+  for (i = 0; i < view_nodes->nodesetval->nodeNr; i++)
+  {
+    xmlNode *node;
+
+    node = view_nodes->nodesetval->nodeTab[i];
+    assert (node != NULL);
+
+    view_path_context->node = node;
+
+    bind_xml_stats_handle_view (version, doc, view_path_context, node,
+        current_time);
+  }
+
+  xmlXPathFreeObject (view_nodes);
+  xmlXPathFreeContext (view_path_context);
+  return (0);
+} /* }}} int bind_xml_stats_search_views */
+
+static int bind_xml_stats (int version, xmlDoc *doc, /* {{{ */
+    xmlXPathContext *xpathCtx, xmlNode *statsnode)
+{
+  time_t current_time = 0;
+  int status;
+
+  xpathCtx->node = statsnode;
+
+  /* TODO: Check `server/boot-time' to recognize server restarts. */
+
+  status = bind_xml_read_timestamp ("server/current-time",
+      doc, xpathCtx, &current_time);
+  if (status != 0)
+  {
+    ERROR ("bind plugin: Reading `server/current-time' failed.");
+    return (-1);
+  }
+  DEBUG ("bind plugin: Current server time is %i.", (int) current_time);
+
+  /* XPath:     server/requests/opcode
+   * Variables: QUERY, IQUERY, NOTIFY, UPDATE, ...
+   * Layout:
+   *   <opcode>
+   *     <name>A</name>
+   *     <counter>1</counter>
+   *   </opcode>
+   *   :
+   */
+  if (global_opcodes != 0)
+  {
+    list_info_ptr_t list_info =
+    {
+      /* plugin instance = */ "global-opcodes",
+      /* type = */ "dns_opcode"
+    };
+
+    bind_parse_generic_name_value (/* xpath = */ "server/requests/opcode",
+        /* callback = */ bind_xml_list_callback,
+        /* user_data = */ &list_info,
+        doc, xpathCtx, current_time, DS_TYPE_COUNTER);
+  }
+
+  /* XPath:     server/queries-in/rdtype
+   * Variables: RESERVED0, A, NS, CNAME, SOA, MR, PTR, HINFO, MX, TXT, RP,
+   *            X25, PX, AAAA, LOC, SRV, NAPTR, A6, DS, RRSIG, NSEC, DNSKEY,
+   *            SPF, TKEY, IXFR, AXFR, ANY, ..., Others
+   * Layout:
+   *   <rdtype>
+   *     <name>A</name>
+   *     <counter>1</counter>
+   *   </rdtype>
+   *   :
+   */
+  if (global_qtypes != 0)
+  {
+    list_info_ptr_t list_info =
+    {
+      /* plugin instance = */ "global-qtypes",
+      /* type = */ "dns_qtype"
+    };
+
+    bind_parse_generic_name_value (/* xpath = */ "server/queries-in/rdtype",
+        /* callback = */ bind_xml_list_callback,
+        /* user_data = */ &list_info,
+        doc, xpathCtx, current_time, DS_TYPE_COUNTER);
+  }
+  
+  /* XPath:     server/nsstats, server/nsstat
+   * Variables: Requestv4, Requestv6, ReqEdns0, ReqBadEDNSVer, ReqTSIG,
+   *            ReqSIG0, ReqBadSIG, ReqTCP, AuthQryRej, RecQryRej, XfrRej,
+   *            UpdateRej, Response, TruncatedResp, RespEDNS0, RespTSIG,
+   *            RespSIG0, QrySuccess, QryAuthAns, QryNoauthAns, QryReferral,
+   *            QryNxrrset, QrySERVFAIL, QryFORMERR, QryNXDOMAIN, QryRecursion,
+   *            QryDuplicate, QryDropped, QryFailure, XfrReqDone, UpdateReqFwd,
+   *            UpdateRespFwd, UpdateFwdFail, UpdateDone, UpdateFail,
+   *            UpdateBadPrereq
+   * Layout v1:
+   *   <nsstats>
+   *     <Requestv4>1</Requestv4>
+   *     <Requestv6>0</Requestv6>
+   *     :
+   *   </nsstats>
+   * Layout v2:
+   *   <nsstat>
+   *     <name>Requestv4</name>
+   *     <counter>1</counter>
+   *   </nsstat>
+   *   <nsstat>
+   *     <name>Requestv6</name>
+   *     <counter>0</counter>
+   *   </nsstat>
+   *   :
+   */
+  if (global_server_stats)
+  {
+    translation_table_ptr_t table_ptr =
+    { 
+      nsstats_translation_table,
+      nsstats_translation_table_length,
+      /* plugin_instance = */ "global-server_stats"
+    };
+
+    if (version == 1)
+    {
+      bind_parse_generic_value_list ("server/nsstats",
+          /* callback = */ bind_xml_table_callback,
+          /* user_data = */ &table_ptr,
+          doc, xpathCtx, current_time, DS_TYPE_COUNTER);
+    }
+    else
+    {
+      bind_parse_generic_name_value ("server/nsstat",
+          /* callback = */ bind_xml_table_callback,
+          /* user_data = */ &table_ptr,
+          doc, xpathCtx, current_time, DS_TYPE_COUNTER);
+    }
+  }
+
+  /* XPath:     server/zonestats, server/zonestat
+   * Variables: NotifyOutv4, NotifyOutv6, NotifyInv4, NotifyInv6, NotifyRej,
+   *            SOAOutv4, SOAOutv6, AXFRReqv4, AXFRReqv6, IXFRReqv4, IXFRReqv6,
+   *            XfrSuccess, XfrFail
+   * Layout v1:
+   *   <zonestats>
+   *     <NotifyOutv4>0</NotifyOutv4>
+   *     <NotifyOutv6>0</NotifyOutv6>
+   *     :
+   *   </zonestats>
+   * Layout v2:
+   *   <zonestat>
+   *     <name>NotifyOutv4</name>
+   *     <counter>0</counter>
+   *   </zonestat>
+   *   <zonestat>
+   *     <name>NotifyOutv6</name>
+   *     <counter>0</counter>
+   *   </zonestat>
+   *   :
+   */
+  if (global_zone_maint_stats)
+  {
+    translation_table_ptr_t table_ptr =
+    { 
+      zonestats_translation_table,
+      zonestats_translation_table_length,
+      /* plugin_instance = */ "global-zone_maint_stats"
+    };
+
+    if (version == 1)
+    {
+      bind_parse_generic_value_list ("server/zonestats",
+          /* callback = */ bind_xml_table_callback,
+          /* user_data = */ &table_ptr,
+          doc, xpathCtx, current_time, DS_TYPE_COUNTER);
+    }
+    else
+    {
+      bind_parse_generic_name_value ("server/zonestat",
+          /* callback = */ bind_xml_table_callback,
+          /* user_data = */ &table_ptr,
+          doc, xpathCtx, current_time, DS_TYPE_COUNTER);
+    }
+  }
+
+  /* XPath:     server/resstats
+   * Variables: Queryv4, Queryv6, Responsev4, Responsev6, NXDOMAIN, SERVFAIL,
+   *            FORMERR, OtherError, EDNS0Fail, Mismatch, Truncated, Lame,
+   *            Retry, GlueFetchv4, GlueFetchv6, GlueFetchv4Fail,
+   *            GlueFetchv6Fail, ValAttempt, ValOk, ValNegOk, ValFail
+   * Layout v1:
+   *   <resstats>
+   *     <Queryv4>0</Queryv4>
+   *     <Queryv6>0</Queryv6>
+   *     :
+   *   </resstats>
+   * Layout v2:
+   *   <resstat>
+   *     <name>Queryv4</name>
+   *     <counter>0</counter>
+   *   </resstat>
+   *   <resstat>
+   *     <name>Queryv6</name>
+   *     <counter>0</counter>
+   *   </resstat>
+   *   :
+   */
+  if (global_resolver_stats != 0)
+  {
+    translation_table_ptr_t table_ptr =
+    { 
+      resstats_translation_table,
+      resstats_translation_table_length,
+      /* plugin_instance = */ "global-resolver_stats"
+    };
+
+    if (version == 1)
+    {
+      bind_parse_generic_value_list ("server/resstats",
+          /* callback = */ bind_xml_table_callback,
+          /* user_data = */ &table_ptr,
+          doc, xpathCtx, current_time, DS_TYPE_COUNTER);
+    }
+    else
+    {
+      bind_parse_generic_name_value ("server/resstat",
+          /* callback = */ bind_xml_table_callback,
+          /* user_data = */ &table_ptr,
+          doc, xpathCtx, current_time, DS_TYPE_COUNTER);
+    }
+  }
+
+  /* XPath:  memory/summary
+   * Variables: TotalUse, InUse, BlockSize, ContextSize, Lost
+   * Layout: v2:
+   *   <summary>
+   *     <TotalUse>6587096</TotalUse>
+   *     <InUse>1345424</InUse>
+   *     <BlockSize>5505024</BlockSize>
+   *     <ContextSize>3732456</ContextSize>
+   *     <Lost>0</Lost>
+   *   </summary>
+   */
+  if (global_memory_stats != 0)
+  {
+    translation_table_ptr_t table_ptr =
+    {
+      memsummary_translation_table,
+      memsummary_translation_table_length,
+      /* plugin_instance = */ "global-memory_stats"
+    };
+
+    bind_parse_generic_value_list ("memory/summary",
+          /* callback = */ bind_xml_table_callback,
+          /* user_data = */ &table_ptr,
+          doc, xpathCtx, current_time, DS_TYPE_GAUGE);
+  }
+
+  if (views_num > 0)
+    bind_xml_stats_search_views (version, doc, xpathCtx, statsnode,
+        current_time);
+
+  return 0;
+} /* }}} int bind_xml_stats */
+
+static int bind_xml (const char *data) /* {{{ */
+{
+  xmlDoc *doc = NULL;
+  xmlXPathContext *xpathCtx = NULL;
+  xmlXPathObject *xpathObj = NULL;
+  int ret = -1;
+  int i;
+
+  doc = xmlParseMemory (data, strlen (data));
+  if (doc == NULL)
+  {
+    ERROR ("bind plugin: xmlParseMemory failed.");
+    return (-1);
+  }
+
+  xpathCtx = xmlXPathNewContext (doc);
+  if (xpathCtx == NULL)
+  {
+    ERROR ("bind plugin: xmlXPathNewContext failed.");
+    xmlFreeDoc (doc);
+    return (-1);
+  }
+
+  xpathObj = xmlXPathEvalExpression (BAD_CAST "/isc/bind/statistics", xpathCtx);
+  if (xpathObj == NULL)
+  {
+    ERROR ("bind plugin: Cannot find the <statistics> tag.");
+    xmlXPathFreeContext (xpathCtx);
+    xmlFreeDoc (doc);
+    return (-1);
+  }
+  else if (xpathObj->nodesetval == NULL)
+  {
+    ERROR ("bind plugin: xmlXPathEvalExpression failed.");
+    xmlXPathFreeObject (xpathObj);
+    xmlXPathFreeContext (xpathCtx);
+    xmlFreeDoc (doc);
+    return (-1);
+  }
+
+  for (i = 0; i < xpathObj->nodesetval->nodeNr; i++)
+  {
+    xmlNode *node;
+    char *attr_version;
+    int parsed_version = 0;
+
+    node = xpathObj->nodesetval->nodeTab[i];
+    assert (node != NULL);
+
+    attr_version = (char *) xmlGetProp (node, BAD_CAST "version");
+    if (attr_version == NULL)
+    {
+      NOTICE ("bind plugin: Found <statistics> tag doesn't have a "
+          "`version' attribute.");
+      continue;
+    }
+    DEBUG ("bind plugin: Found: <statistics version=\"%s\">", attr_version);
+
+    /* At the time this plugin was written, version "1.0" was used by
+     * BIND 9.5.0, version "2.0" was used by BIND 9.5.1 and 9.6.0. We assume
+     * that "1.*" and "2.*" don't introduce structural changes, so we just
+     * check for the first two characters here. */
+    if (strncmp ("1.", attr_version, strlen ("1.")) == 0)
+      parsed_version = 1;
+    else if (strncmp ("2.", attr_version, strlen ("2.")) == 0)
+      parsed_version = 2;
+    else
+    {
+      /* TODO: Use the complaint mechanism here. */
+      NOTICE ("bind plugin: Found <statistics> tag with version `%s'. "
+          "Unfortunately I have no clue how to parse that. "
+          "Please open a bug report for this.", attr_version);
+      xmlFree (attr_version);
+      continue;
+    }
+
+    ret = bind_xml_stats (parsed_version,
+        doc, xpathCtx, node);
+
+    xmlFree (attr_version);
+    /* One <statistics> node ought to be enough. */
+    break;
+  }
+
+  xmlXPathFreeObject (xpathObj);
+  xmlXPathFreeContext (xpathCtx);
+  xmlFreeDoc (doc);
+
+  return (ret);
+} /* }}} int bind_xml */
+
+static int bind_config_set_bool (const char *name, int *var, /* {{{ */
+    oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1) || ( ci->values[0].type != OCONFIG_TYPE_BOOLEAN))
+  {
+    WARNING ("bind plugin: The `%s' option needs "
+        "exactly one boolean argument.", name);
+    return (-1);
+  }
+
+  if (ci->values[0].value.boolean)
+    *var = 1;
+  else
+    *var = 0;
+  return 0;
+} /* }}} int bind_config_set_bool */
+
+static int bind_config_add_view_zone (cb_view_t *view, /* {{{ */
+    oconfig_item_t *ci)
+{
+  char **tmp;
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("bind plugin: The `Zone' option needs "
+        "exactly one string argument.");
+    return (-1);
+  }
+
+  tmp = (char **) realloc (view->zones,
+      sizeof (char *) * (view->zones_num + 1));
+  if (tmp == NULL)
+  {
+    ERROR ("bind plugin: realloc failed.");
+    return (-1);
+  }
+  view->zones = tmp;
+
+  view->zones[view->zones_num] = strdup (ci->values[0].value.string);
+  if (view->zones[view->zones_num] == NULL)
+  {
+    ERROR ("bind plugin: strdup failed.");
+    return (-1);
+  }
+  view->zones_num++;
+
+  return (0);
+} /* }}} int bind_config_add_view_zone */
+
+static int bind_config_add_view (oconfig_item_t *ci) /* {{{ */
+{
+  cb_view_t *tmp;
+  int i;
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("bind plugin: `View' blocks need exactly one string argument.");
+    return (-1);
+  }
+
+  tmp = (cb_view_t *) realloc (views, sizeof (*views) * (views_num + 1));
+  if (tmp == NULL)
+  {
+    ERROR ("bind plugin: realloc failed.");
+    return (-1);
+  }
+  views = tmp;
+  tmp = views + views_num;
+
+  memset (tmp, 0, sizeof (*tmp));
+  tmp->qtypes = 1;
+  tmp->resolver_stats = 1;
+  tmp->cacherrsets = 1;
+  tmp->zones = NULL;
+  tmp->zones_num = 0;
+
+  tmp->name = strdup (ci->values[0].value.string);
+  if (tmp->name == NULL)
+  {
+    ERROR ("bind plugin: strdup failed.");
+    free (tmp);
+    return (-1);
+  }
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("QTypes", child->key) == 0)
+      bind_config_set_bool ("QTypes", &tmp->qtypes, child);
+    else if (strcasecmp ("ResolverStats", child->key) == 0)
+      bind_config_set_bool ("ResolverStats", &tmp->resolver_stats, child);
+    else if (strcasecmp ("CacheRRSets", child->key) == 0)
+      bind_config_set_bool ("CacheRRSets", &tmp->cacherrsets, child);
+    else if (strcasecmp ("Zone", child->key) == 0)
+      bind_config_add_view_zone (tmp, child);
+    else
+    {
+      WARNING ("bind plugin: Unknown configuration option "
+          "`%s' in view `%s' will be ignored.", child->key, tmp->name);
+    }
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  views_num++;
+  return (0);
+} /* }}} int bind_config_add_view */
+
+static int bind_config (oconfig_item_t *ci) /* {{{ */
+{
+  int i;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Url", child->key) == 0) {
+      if ((child->values_num != 1) || (child->values[0].type != OCONFIG_TYPE_STRING))
+      {
+        WARNING ("bind plugin: The `Url' option needs "
+                 "exactly one string argument.");
+        return (-1);
+      }
+
+      url = strdup (child->values[0].value.string);
+    } else if (strcasecmp ("OpCodes", child->key) == 0)
+      bind_config_set_bool ("OpCodes", &global_opcodes, child);
+    else if (strcasecmp ("QTypes", child->key) == 0)
+      bind_config_set_bool ("QTypes", &global_qtypes, child);
+    else if (strcasecmp ("ServerStats", child->key) == 0)
+      bind_config_set_bool ("ServerStats", &global_server_stats, child);
+    else if (strcasecmp ("ZoneMaintStats", child->key) == 0)
+      bind_config_set_bool ("ZoneMaintStats", &global_zone_maint_stats, child);
+    else if (strcasecmp ("ResolverStats", child->key) == 0)
+      bind_config_set_bool ("ResolverStats", &global_resolver_stats, child);
+    else if (strcasecmp ("MemoryStats", child->key) == 0)
+      bind_config_set_bool ("MemoryStats", &global_memory_stats, child);
+    else if (strcasecmp ("View", child->key) == 0)
+      bind_config_add_view (child);
+    else
+    {
+      WARNING ("bind plugin: Unknown configuration option "
+          "`%s' will be ignored.", child->key);
+    }
+  }
+
+  return (0);
+} /* }}} int bind_config */
+
+static int bind_init (void) /* {{{ */
+{
+  if (curl != NULL)
+    return (0);
+
+  curl = curl_easy_init ();
+  if (curl == NULL)
+  {
+    ERROR ("bind plugin: bind_init: curl_easy_init failed.");
+    return (-1);
+  }
+
+  curl_easy_setopt (curl, CURLOPT_NOSIGNAL, 1);
+  curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, bind_curl_callback);
+  curl_easy_setopt (curl, CURLOPT_USERAGENT, PACKAGE_NAME"/"PACKAGE_VERSION);
+  curl_easy_setopt (curl, CURLOPT_ERRORBUFFER, bind_curl_error);
+  curl_easy_setopt (curl, CURLOPT_URL, (url != NULL) ? url : BIND_DEFAULT_URL);
+  curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, 1);
+
+  return (0);
+} /* }}} int bind_init */
+
+static int bind_read (void) /* {{{ */
+{
+  int status;
+
+  if (curl == NULL)
+  {
+    ERROR ("bind plugin: I don't have a CURL object.");
+    return (-1);
+  }
+
+  bind_buffer_fill = 0;
+  if (curl_easy_perform (curl) != 0)
+  {
+    ERROR ("bind plugin: curl_easy_perform failed: %s",
+        bind_curl_error);
+    return (-1);
+  }
+
+  status = bind_xml (bind_buffer);
+  if (status != 0)
+    return (-1);
+  else
+    return (0);
+} /* }}} int bind_read */
+
+static int bind_shutdown (void) /* {{{ */
+{
+  if (curl != NULL)
+  {
+    curl_easy_cleanup (curl);
+    curl = NULL;
+  }
+
+  return (0);
+} /* }}} int bind_shutdown */
+
+void module_register (void)
+{
+  plugin_register_complex_config ("bind", bind_config);
+  plugin_register_init ("bind", bind_init);
+  plugin_register_read ("bind", bind_read);
+  plugin_register_shutdown ("bind", bind_shutdown);
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 ts=8 et fdm=marker : */
diff --git a/src/collectd-email.pod b/src/collectd-email.pod
new file mode 100644 (file)
index 0000000..e19d13e
--- /dev/null
@@ -0,0 +1,72 @@
+=head1 NAME
+
+collectd-email - Documentation of collectd's C<email plugin>
+
+=head1 SYNOPSIS
+
+  # See collectd.conf(5)
+  LoadPlugin email
+  # ...
+  <Plugin email>
+    SocketGroup "collectd"
+    SocketPerms "0770"
+    MaxConns 5
+  </Plugin>
+
+=head1 DESCRIPTION
+
+The C<email plugin> opens an UNIX-socket over which one can submit email
+statistics, such as the number of "ham", "spam", "virus", etc. mails
+received/handled, spam scores and matched spam checks.
+
+This plugin is intended to be used with the
+L<Mail::SpamAssassin::Plugin::Collectd> SpamAssassin-plugin which is included
+in F<contrib/>, but is of course not limited to that use.
+
+=head1 OPERATION
+
+This plugin collects data indirectly by providing a UNIX-socket that external
+programs can connect to. A simple line based protocol is used to communicate
+with the plugin:
+
+=over 4
+
+=item
+
+E-Mail type (e.g. "ham", "spam", "virus", ...) and size (bytes):
+
+  e:<type>:<size>
+
+If C<size> is less than or equal to zero, C<size> is ignored.
+
+=item
+
+Spam score:
+
+  s:<value>
+
+=item
+
+Successful spam checks (e.g. "BAYES_99", "SUBJECT_DRUG_GAP_C", ...):
+
+  c:<type1>[,<type2>,...]
+
+Each line is limited to 256 characters (including the newline character). 
+Longer lines will be ignored.
+
+=back
+
+=head1 SEE ALSO
+
+L<collectd(1)>,
+L<collectd.conf(5)>
+
+=head1 AUTHOR
+
+The C<email plugin> has been written by Sebastian Harl E<lt>shE<nbsp>atE<nbsp>tokkee.orgE<gt>.
+
+The SpamAssassin-plugin has been written by Alexander Wirt E<lt>formorerE<nbsp>atE<nbsp>formorer.deE<gt>.
+
+This manpage has been written by Florian Forster E<lt>octoE<nbsp>atE<nbsp>verplant.orgE<gt>.
+
+=cut
diff --git a/src/collectd-exec.pod b/src/collectd-exec.pod
new file mode 100644 (file)
index 0000000..10f6829
--- /dev/null
@@ -0,0 +1,298 @@
+=head1 NAME
+
+collectd-exec - Documentation of collectd's C<exec plugin>
+
+=head1 SYNOPSIS
+
+  # See collectd.conf(5)
+  LoadPlugin exec
+  # ...
+  <Plugin exec>
+    Exec "myuser:mygroup" "myprog"
+    Exec "otheruser" "/path/to/another/binary" "arg0" "arg1"
+    NotificationExec "user" "/usr/lib/collectd/exec/handle_notification"
+  </Plugin>
+
+=head1 DESCRIPTION
+
+The C<exec plugin> forks of an executable either to receive values or to
+dispatch notifications to the outside world. The syntax of the configuration is
+explained in L<collectd.conf(5)> but summarized in the above synopsis.
+
+If you want/need better performance or more functionality you should take a
+long look at the C<perl plugin>, L<collectd-perl(5)>.
+
+=head1 EXECUTABLE TYPES
+
+There are currently two types of executables that can be executed by the
+C<exec plugin>:
+
+=over 4
+
+=item C<Exec>
+
+These programs are forked and values that it writes to C<STDOUT> are read back.
+The executable is forked in a fashion similar to L<init>: It is forked once and
+not again until it exits. If it exited, it will be forked again after at most
+I<Interval> seconds. It is perfectly legal for the executable to run for a long
+time and continuously write values to C<STDOUT>.
+
+See L<EXEC DATA FORMAT> below for a description of the output format expected
+from these programs.
+
+B<Warning:> If the executable only writes one value and then exits I will be
+executed every I<Interval> seconds. If I<Interval> is short (the default is 10
+seconds) this may result in serious system load.
+
+=item C<NotificationExec>
+
+The program is forked once for each notification that is handled by the daemon.
+The notification is passed to the program on C<STDIN> in a fashion similar to
+HTTP-headers. In contrast to programs specified with C<Exec> the execution of
+this program is not serialized, so that several instances of this program may
+run at once if multiple notifications are received.
+
+See L<NOTIFICATION DATA FORMAT> below for a description of the data passed to
+these programs.
+
+=back
+
+=head1 EXEC DATA FORMAT
+
+The forked executable is expected to print values to C<STDOUT>. The expected
+format is as follows:
+
+=over 4
+
+=item Comments
+
+Each line beginning with a C<#> (hash mark) is ignored.
+
+=item B<PUTVAL> I<Identifier> [I<OptionList>] I<Valuelist>
+
+Submits one or more values (identified by I<Identifier>, see below) to the
+daemon which will dispatch it to all it's write-plugins.
+
+An I<Identifier> is of the form
+C<I<host>B</>I<plugin>B<->I<instance>B</>I<type>B<->I<instance>> with both
+I<instance>-parts being optional. If they're omitted the hyphen must be
+omitted, too. I<plugin> and each I<instance>-part may be chosen freely as long
+as the tuple (plugin, plugin instance, type instance) uniquely identifies the
+plugin within collectd. I<type> identifies the type and number of values
+(i.E<nbsp>e. data-set) passed to collectd. A large list of predefined
+data-sets is available in the B<types.db> file. See L<types.db(5)> for a
+description of the format of this file.
+
+The I<OptionList> is an optional list of I<Options>, where each option is a
+key-value-pair. A list of currently understood options can be found below, all
+other options will be ignored. Values that contain spaces must be quoted with
+double quotes.
+
+I<Valuelist> is a colon-separated list of the time and the values, each either
+an integer if the data-source is a counter, or a double if the data-source is
+of type "gauge". You can submit an undefined gauge-value by using B<U>. When
+submitting B<U> to a counter the behavior is undefined. The time is given as
+epoch (i.E<nbsp>e. standard UNIX time).
+
+You can mix options and values, but the order is important: Options only
+effect following values, so specifying an option as last field is allowed, but
+useless. Also, an option applies to B<all> following values, so you don't need
+to re-set an option over and over again.
+
+The currently defined B<Options> are:
+
+=over 4
+
+=item B<interval=>I<seconds>
+
+Gives the interval in which the data identified by I<Identifier> is being
+collected.
+
+=back
+
+Please note that this is the same format as used in the B<unixsock plugin>, see
+L<collectd-unixsock(5)>. There's also a bit more information on identifiers in
+case you're confused.
+
+Since examples usually let one understand a lot better, here are some:
+
+  leeloo/cpu-0/cpu-idle N:2299366
+  alice/interface/if_octets-eth0 interval=10 1180647081:421465:479194
+
+Since this action was the only one supported with older versions of the C<exec
+plugin> all lines were treated as if they were prefixed with B<PUTVAL>. This is
+still the case to maintain backwards compatibility but deprecated.
+
+=item B<PUTNOTIF> [I<OptionList>] B<message=>I<Message>
+
+Submits a notification to the daemon which will then dispatch it to all plugins
+which have registered for receiving notifications. 
+
+The B<PUTNOTIF> if followed by a list of options which further describe the
+notification. The B<message> option is special in that it will consume the rest
+of the line as its value. The B<message>, B<severity>, and B<time> options are
+mandatory.
+
+Valid options are:
+
+=over 4
+
+=item B<message=>I<Message> (B<REQUIRED>)
+
+Sets the message of the notification. This is the message that will be made
+accessible to the user, so it should contain some useful information. As with
+all options: If the message includes spaces, it must be quoted with double
+quotes. This option is mandatory.
+
+=item B<severity=failure>|B<warning>|B<okay> (B<REQUIRED>)
+
+Sets the severity of the notification. This option is mandatory.
+
+=item B<time=>I<Time> (B<REQUIRED>)
+
+Sets the time of the notification. The time is given as "epoch", i.E<nbsp>e. as
+seconds since January 1st, 1970, 00:00:00. This option is mandatory.
+
+=item B<host=>I<Hostname>
+
+=item B<plugin=>I<Plugin>
+
+=item B<plugin_instance=>I<Plugin-Instance>
+
+=item B<type=>I<Type>
+
+=item B<type_instance=>I<Type-Instance>
+
+These "associative" options establish a relation between this notification and
+collected performance data. This connection is purely informal, i.E<nbsp>e. the
+daemon itself doesn't do anything with this information. However, websites or
+GUIs may use this information to place notifications near the affected graph or
+table. All the options are optional, but B<plugin_instance> without B<plugin>
+or B<type_instance> without B<type> doesn't make much sense and should be
+avoided.
+
+=back
+
+=back
+
+Please note that this is the same format as used in the B<unixsock plugin>, see
+L<collectd-unixsock(5)>.
+
+When collectd exits it sends a B<SIGTERM> to all still running
+child-processes upon which they have to quit.
+
+=head1 NOTIFICATION DATA FORMAT
+
+The notification executables receive values rather than providing them. In
+fact, after the program is started C<STDOUT> is connected to C</dev/null>.
+
+The data is passed to the executables over C<STDIN> in a format very similar to
+HTTP: At first there is a "header" with one line per field. Every line consists
+of a field name, ended by a colon, and the associated value until end-of-line.
+The "header" is ended by two newlines immediately following another,
+i.E<nbsp>e. an empty line. The rest, basically the "body", is the message of
+the notification.
+
+The following is an example notification passed to a program:
+
+  Severity: FAILURE
+  Time: 1200928930
+  Host: myhost.mydomain.org
+  \n
+  This is a test notification to demonstrate the format
+
+The following header files are currently used. Please note, however, that you
+should ignore unknown header files to be as forward-compatible as possible.
+
+=over 4
+
+=item B<Severity>
+
+Severity of the notification. May either be B<FAILURE>, B<WARNING>, or B<OKAY>.
+
+=item B<Time>
+
+The time in epoch, i.E<nbsp>e. as seconds since 1970-01-01 00:00:00 UTC.
+
+=item B<Host>
+
+=item B<Plugin>
+
+=item B<PluginInstance>
+
+=item B<Type>
+
+=item B<TypeInstance>
+
+Identification of the performance data this notification is associated with.
+All of these fields are optional because notifications do not B<need> to be
+associated with a certain value.
+
+=back
+
+=head1 ENVIRONMENT
+
+The following environment variables are set by the plugin before calling
+I<exec>:
+
+=over 4
+
+=item COLLECTD_INTERVAL
+
+Value of the global interval setting.
+
+=item COLLECTD_HOSTNAME
+
+Hostname used by I<collectd> to dispatch local values.
+
+=back
+
+=head1 USING NAGIOS PLUGINS
+
+Though the interface is far from perfect, there are tons of plugins for Nagios.
+You can use these plugins with collectd by using a simple transition layer,
+C<exec-nagios.px>, which is shipped with the collectd distribution in the
+C<contrib/> directory. It is a simple Perl script that comes with embedded
+documentation. To see it, run the following command:
+
+  perldoc exec-nagios.px
+
+This script expects a configuration file, C<exec-nagios.conf>. You can find an
+example in the C<contrib/> directory, too.
+
+Even a simple mechanism to submit "performance data" to collectd is
+implemented. If you need a more sophisticated setup, please rewrite the plugin
+to make use of collectd's more powerful interface.
+
+=head1 CAVEATS
+
+=over 4
+
+=item
+
+The user, the binary is executed as, may not have root privileges, i.E<nbsp>e.
+must have an UID that is non-zero. This is for your own good.
+
+=item
+
+Early versions of the plugin did not use a command but treated all lines as if
+they were arguments to the I<PUTVAL> command. When the I<PUTNOTIF> command was
+implemented, this behavior was kept for lines which start with an unknown
+command for backwards compatibility. This compatibility code has been removed
+in I<collectdE<nbsp>5>.
+
+=back
+
+=head1 SEE ALSO
+
+L<collectd(1)>,
+L<collectd.conf(5)>,
+L<collectd-perl(5)>,
+L<collectd-unixsock(5)>,
+L<fork(2)>, L<exec(3)>
+
+=head1 AUTHOR
+
+Florian Forster E<lt>octo@verplant.orgE<gt>
+
+=cut
diff --git a/src/collectd-java.pod b/src/collectd-java.pod
new file mode 100644 (file)
index 0000000..9e2f81a
--- /dev/null
@@ -0,0 +1,695 @@
+=head1 NAME
+
+collectd-java - Documentation of collectd's "java plugin"
+
+=head1 SYNOPSIS
+
+ LoadPlugin "java"
+ <Plugin "java">
+   JVMArg "-verbose:jni"
+   JVMArg "-Djava.class.path=/opt/collectd/lib/collectd/bindings/java"
+   
+   LoadPlugin "org.collectd.java.Foobar"
+   <Plugin "org.collectd.java.Foobar">
+     # To be parsed by the plugin
+   </Plugin>
+ </Plugin>
+
+=head1 DESCRIPTION
+
+The I<Java> plugin embeds a I<Java Virtual Machine> (JVM) into I<collectd> and
+provides a Java interface to part of collectd's API. This makes it possible to
+write additions to the daemon in Java.
+
+This plugin is similar in nature to, but shares no code with, the I<Perl>
+plugin by Sebastian Harl, see L<collectd-perl(5)> for details.
+
+=head1 CONFIGURATION
+
+A short outline of this plugin's configuration can be seen in L<"SYNOPSIS">
+above. For a complete list of all configuration options and their semantics
+please read L<collectd.conf(5)/Plugin C<java>>.
+
+=head1 OVERVIEW
+
+When writing additions for collectd in Java, the underlying C base is mostly
+hidden from you. All complex data types are converted to their Java counterparts
+before they're passed to your functions. These Java classes reside in the
+I<org.collectd.api> namespace.
+
+The I<Java> plugin will create one object of each class configured with the
+B<LoadPlugin> option. The constructor of this class can then register "callback
+methods", i.E<nbsp>e. methods that will be called by the daemon when
+appropriate.
+
+The available classes are:
+
+=over 4
+
+=item B<org.collectd.api.Collectd>
+
+All API functions exported to Java are implemented as static functions of this
+class. See L<"EXPORTED API FUNCTIONS"> below.
+
+=item B<org.collectd.api.OConfigValue>
+
+Corresponds to C<oconfig_value_t>, defined in F<src/liboconfig/oconfig.h>.
+
+=item B<org.collectd.api.OConfigItem>
+
+Corresponds to C<oconfig_item_t>, defined in F<src/liboconfig/oconfig.h>.
+
+=item B<org.collectd.api.DataSource>
+
+Corresponds to C<data_source_t>, defined in F<src/plugin.h>.
+
+=item B<org.collectd.api.DataSet>
+
+Corresponds to C<data_set_t>, defined in F<src/plugin.h>.
+
+=item B<org.collectd.api.ValueList>
+
+Corresponds to C<value_list_t>, defined in F<src/plugin.h>.
+
+=item B<org.collectd.api.Notification>
+
+Corresponds to C<notification_t>, defined in F<src/plugin.h>.
+
+=back
+
+In the remainder of this document, we'll use the short form of these names, for
+example B<ValueList>. In order to be able to use these abbreviated names, you
+need to B<import> the classes.
+
+=head1 EXPORTED API FUNCTIONS
+
+All collectd API functions that are available to Java plugins are implemented
+as I<publicE<nbsp>static> functions of the B<Collectd> class. This makes
+calling these functions pretty straight forward. For example, to send an error
+message to the daemon, you'd do something like this:
+
+  Collectd.logError ("That wasn't chicken!");
+
+The following are the currently exported functions.
+
+=head2 registerConfig
+
+Signature: I<int> B<registerConfig> (I<String> name,
+I<CollectdConfigInterface> object);
+
+Registers the B<config> function of I<object> with the daemon.
+
+Returns zero upon success and non-zero when an error occurred.
+
+See L<"config callback"> below.
+
+=head2 registerInit
+
+Signature: I<int> B<registerInit> (I<String> name,
+I<CollectdInitInterface> object);
+
+Registers the B<init> function of I<object> with the daemon.
+
+Returns zero upon success and non-zero when an error occurred.
+
+See L<"init callback"> below.
+
+=head2 registerRead
+
+Signature: I<int> B<registerRead> (I<String> name,
+I<CollectdReadInterface> object)
+
+Registers the B<read> function of I<object> with the daemon.
+
+Returns zero upon success and non-zero when an error occurred.
+
+See L<"read callback"> below.
+
+=head2 registerWrite
+
+Signature: I<int> B<registerWrite> (I<String> name,
+I<CollectdWriteInterface> object)
+
+Registers the B<write> function of I<object> with the daemon.
+
+Returns zero upon success and non-zero when an error occurred.
+
+See L<"write callback"> below.
+
+=head2 registerFlush
+
+Signature: I<int> B<registerFlush> (I<String> name,
+I<CollectdFlushInterface> object)
+
+Registers the B<flush> function of I<object> with the daemon.
+
+Returns zero upon success and non-zero when an error occurred.
+
+See L<"flush callback"> below.
+
+=head2 registerShutdown
+
+Signature: I<int> B<registerShutdown> (I<String> name,
+I<CollectdShutdownInterface> object);
+
+Registers the B<shutdown> function of I<object> with the daemon.
+
+Returns zero upon success and non-zero when an error occurred.
+
+See L<"shutdown callback"> below.
+
+=head2 registerLog
+
+Signature: I<int> B<registerLog> (I<String> name,
+I<CollectdLogInterface> object);
+
+Registers the B<log> function of I<object> with the daemon.
+
+Returns zero upon success and non-zero when an error occurred.
+
+See L<"log callback"> below.
+
+=head2 registerNotification
+
+Signature: I<int> B<registerNotification> (I<String> name,
+I<CollectdNotificationInterface> object);
+
+Registers the B<notification> function of I<object> with the daemon.
+
+Returns zero upon success and non-zero when an error occurred.
+
+See L<"notification callback"> below.
+
+=head2 registerMatch
+
+Signature: I<int> B<registerMatch> (I<String> name,
+I<CollectdMatchFactoryInterface> object);
+
+Registers the B<createMatch> function of I<object> with the daemon.
+
+Returns zero upon success and non-zero when an error occurred.
+
+See L<"match callback"> below.
+
+=head2 registerTarget
+
+Signature: I<int> B<registerTarget> (I<String> name,
+I<CollectdTargetFactoryInterface> object);
+
+Registers the B<createTarget> function of I<object> with the daemon.
+
+Returns zero upon success and non-zero when an error occurred.
+
+See L<"target callback"> below.
+
+=head2 dispatchValues
+
+Signature: I<int> B<dispatchValues> (I<ValueList>)
+
+Passes the values represented by the B<ValueList> object to the
+C<plugin_dispatch_values> function of the daemon. The "data set" (or list of
+"data sources") associated with the object are ignored, because
+C<plugin_dispatch_values> will automatically lookup the required data set. It
+is therefore absolutely okay to leave this blank.
+
+Returns zero upon success or non-zero upon failure.
+
+=head2 getDS
+
+Signature: I<DataSet> B<getDS> (I<String>)
+
+Returns the appropriate I<type> or B<null> if the type is not defined.
+
+=head2 logError
+
+Signature: I<void> B<logError> (I<String>)
+
+Sends a log message with severity B<ERROR> to the daemon.
+
+=head2 logWarning
+
+Signature: I<void> B<logWarning> (I<String>)
+
+Sends a log message with severity B<WARNING> to the daemon.
+
+=head2 logNotice
+
+Signature: I<void> B<logNotice> (I<String>)
+
+Sends a log message with severity B<NOTICE> to the daemon.
+
+=head2 logInfo
+
+Signature: I<void> B<logInfo> (I<String>)
+
+Sends a log message with severity B<INFO> to the daemon.
+
+=head2 logDebug
+
+Signature: I<void> B<logDebug> (I<String>)
+
+Sends a log message with severity B<DEBUG> to the daemon.
+
+=head1 REGISTERING CALLBACKS
+
+When starting up, collectd creates an object of each configured class. The
+constructor of this class should then register "callbacks" with the daemon,
+using the appropriate static functions in B<Collectd>,
+see L<"EXPORTED API FUNCTIONS"> above. To register a callback, the object being
+passed to one of the register functions must implement an appropriate
+interface, which are all in the B<org.collectd.api> namespace.
+
+A constructor may register any number of these callbacks, even none. An object
+without callback methods is never actively called by collectd, but may still
+call the exported API functions. One could, for example, start a new thread in
+the constructor and dispatch (submit to the daemon) values asynchronously,
+whenever one is available.
+
+Each callback method is now explained in more detail:
+
+=head2 config callback
+
+Interface: B<org.collectd.api.CollectdConfigInterface>
+
+Signature: I<int> B<config> (I<OConfigItem> ci)
+
+This method is passed a B<OConfigItem> object, if both, method and
+configuration, are available. B<OConfigItem> is the root of a tree representing
+the configuration for this plugin. The root itself is the representation of the
+B<E<lt>PluginE<nbsp>/E<gt>> block, so in next to all cases the children of the
+root are the first interesting objects.
+
+To signal success, this method has to return zero. Anything else will be
+considered an error condition and the plugin will be disabled entirely.
+
+See L<"registerConfig"> above.
+
+=head2 init callback
+
+Interface: B<org.collectd.api.CollectdInitInterface>
+
+Signature: I<int> B<init> ()
+
+This method is called after the configuration has been handled. It is
+supposed to set up the plugin. e.E<nbsp>g. start threads, open connections, or
+check if can do anything useful at all.
+
+To signal success, this method has to return zero. Anything else will be
+considered an error condition and the plugin will be disabled entirely.
+
+See L<"registerInit"> above.
+
+=head2 read callback
+
+Interface: B<org.collectd.api.CollectdReadInterface>
+
+Signature: I<int> B<read> ()
+
+This method is called periodically and is supposed to gather statistics in
+whatever fashion. These statistics are represented as a B<ValueList> object and
+sent to the daemon using L<dispatchValues|"dispatchValues">.
+
+To signal success, this method has to return zero. Anything else will be
+considered an error condition and cause an appropriate message to be logged.
+Currently, returning non-zero does not have any other effects. In particular,
+Java "read"-methods are not suspended for increasing intervals like C
+"read"-functions.
+
+See L<"registerRead"> above.
+
+=head2 write callback
+
+Interface: B<org.collectd.api.CollectdWriteInterface>
+
+Signature: I<int> B<write> (I<ValueList> vl)
+
+This method is called whenever a value is dispatched to the daemon. The
+corresponding C "write"-functions are passed a C<data_set_t>, so they can
+decide which values are absolute values (gauge) and which are counter values.
+To get the corresponding C<ListE<lt>DataSourceE<gt>>, call the B<getDataSource>
+method of the B<ValueList> object.
+
+To signal success, this method has to return zero. Anything else will be
+considered an error condition and cause an appropriate message to be logged.
+
+See L<"registerWrite"> above.
+
+=head2 flush callback
+
+Interface: B<org.collectd.api.CollectdFlushInterface>
+
+Signature: I<int> B<flush> (I<int> timeout, I<String> identifier)
+
+This method is called when the daemon received a flush command. This can either
+be done using the C<USR1> signal (see L<collectd(1)>) or using the I<unixsock>
+plugin (see L<collectd-unixsock(5)>).
+
+If I<timeout> is greater than zero, only values older than this number of
+seconds should be flushed. To signal that all values should be flushed
+regardless of age, this argument is set to a negative number.
+
+The I<identifier> specifies which value should be flushed. If it is not
+possible to flush one specific value, flush all values. To signal that all
+values should be flushed, this argument is set to I<null>.
+
+To signal success, this method has to return zero. Anything else will be
+considered an error condition and cause an appropriate message to be logged.
+
+See L<"registerFlush"> above.
+
+=head2 shutdown callback
+
+Interface: B<org.collectd.api.CollectdShutdownInterface>
+
+Signature: I<int> B<shutdown> ()
+
+This method is called when the daemon is shutting down. You should not rely on
+the destructor to clean up behind the object but use this function instead.
+
+To signal success, this method has to return zero. Anything else will be
+considered an error condition and cause an appropriate message to be logged.
+
+See L<"registerShutdown"> above.
+
+=head2 log callback
+
+Interface: B<org.collectd.api.CollectdLogInterface>
+
+Signature: I<void> B<log> (I<int> severity, I<String> message)
+
+This callback can be used to receive log messages from the daemon.
+
+The argument I<severity> is one of:
+
+=over 4
+
+=item *
+
+org.collectd.api.Collectd.LOG_ERR
+
+=item *
+
+org.collectd.api.Collectd.LOG_WARNING
+
+=item *
+
+org.collectd.api.Collectd.LOG_NOTICE
+
+=item *
+
+org.collectd.api.Collectd.LOG_INFO
+
+=item *
+
+org.collectd.api.Collectd.LOG_DEBUG
+
+=back
+
+The function does not return any value.
+
+See L<"registerLog"> above.
+
+=head2 notification callback
+
+Interface: B<org.collectd.api.CollectdNotificationInterface>
+
+Signature: I<int> B<notification> (I<Notification> n)
+
+This callback can be used to receive notifications from the daemon.
+
+To signal success, this method has to return zero. Anything else will be
+considered an error condition and cause an appropriate message to be logged.
+
+See L<"registerNotification"> above.
+
+=head2 match callback
+
+The match (and target, see L<"target callback"> below) callbacks work a bit
+different from the other callbacks above: You don't register a match callback
+with the daemon directly, but you register a function which, when called,
+creates an appropriate object. The object creating the "match" objects is
+called "match factory".
+
+See L<"registerMatch"> above.
+
+=head3 Factory object
+
+Interface: B<org.collectd.api.CollectdMatchFactoryInterface>
+
+Signature: I<CollectdMatchInterface> B<createMatch>
+(I<OConfigItem> ci);
+
+Called by the daemon to create "match" objects.
+
+Returns: A new object which implements the B<CollectdMatchInterface> interface.
+
+=head3 Match object
+
+Interface: B<org.collectd.api.CollectdMatchInterface>
+
+Signature: I<int> B<match> (I<DataSet> ds, I<ValueList> vl);
+
+Called when processing a chain to determine whether or not a I<ValueList>
+matches. How values are matches is up to the implementing class.
+
+Has to return one of:
+
+=over 4
+
+=item *
+
+B<Collectd.FC_MATCH_NO_MATCH>
+
+=item *
+
+B<Collectd.FC_MATCH_MATCHES>
+
+=back
+
+=head2 target callback
+
+The target (and match, see L<"match callback"> above) callbacks work a bit
+different from the other callbacks above: You don't register a target callback
+with the daemon directly, but you register a function which, when called,
+creates an appropriate object. The object creating the "target" objects is
+called "target factory".
+
+See L<"registerTarget"> above.
+
+=head3 Factory object
+
+Interface: B<org.collectd.api.CollectdTargetFactoryInterface>
+
+Signature: I<CollectdTargetInterface> B<createTarget>
+(I<OConfigItem> ci);
+
+Called by the daemon to create "target" objects.
+
+Returns: A new object which implements the B<CollectdTargetInterface>
+interface.
+
+=head3 Target object
+
+Interface: B<org.collectd.api.CollectdTargetInterface>
+
+Signature: I<int> B<invoke> (I<DataSet> ds, I<ValueList> vl);
+
+Called when processing a chain to perform some action. The action performed is
+up to the implementing class.
+
+Has to return one of:
+
+=over 4
+
+=item *
+
+B<Collectd.FC_TARGET_CONTINUE>
+
+=item *
+
+B<Collectd.FC_TARGET_STOP>
+
+=item *
+
+B<Collectd.FC_TARGET_RETURN>
+
+=back
+
+=head1 EXAMPLE
+
+This short example demonstrates how to register a read callback with the
+daemon:
+
+  import org.collectd.api.Collectd;
+  import org.collectd.api.ValueList;
+  
+  import org.collectd.api.CollectdReadInterface;
+  
+  public class Foobar implements CollectdReadInterface
+  {
+    public Foobar ()
+    {
+      Collectd.registerRead ("Foobar", this);
+    }
+    
+    public int read ()
+    {
+      ValueList vl;
+      
+      /* Do something... */
+      
+      Collectd.dispatchValues (vl);
+    }
+  }
+
+=head1 PLUGINS
+
+The following plugins are implemented in I<Java>. Both, the B<LoadPlugin>
+option and the B<Plugin> block must be inside the
+B<E<lt>PluginE<nbsp>javaE<gt>> block (see above).
+
+=head2 GenericJMX plugin
+
+The GenericJMX plugin reads I<Managed Beans> (MBeans) from an I<MBeanServer>
+using JMX. JMX is a generic framework to provide and query various management
+information. The interface is used by Java processes to provide internal
+statistics as well as by the I<Java Virtual Machine> (JVM) to provide
+information about the memory used, threads and so on. 
+
+The configuration of the I<GenericJMX plugin> consists of two blocks: I<MBean>
+blocks that define a mapping of MBean attributes to the “types” used by
+I<collectd>, and I<Connection> blocks which define the parameters needed to
+connect to an I<MBeanServer> and what data to collect. The configuration of the
+I<SNMP plugin> is similar in nature, in case you know it.
+
+=head3   MBean blocks
+
+I<MBean> blocks specify what data is retrieved from I<MBeans> and how that data
+is mapped on the I<collectd> data types. The block requires one string
+argument, a name. This name is used in the I<Connection> blocks (see below) to
+refer to a specific I<MBean> block. Therefore, the names must be unique.
+
+The following options are recognized within I<MBean> blocks: 
+
+=over 4
+
+=item B<ObjectName> I<pattern>
+
+Sets the pattern which is used to retrieve I<MBeans> from the I<MBeanServer>.
+If more than one MBean is returned you should use the B<InstanceFrom> option
+(see below) to make the identifiers unique.
+
+See also:
+L<http://java.sun.com/javase/6/docs/api/javax/management/ObjectName.html>
+
+=item B<InstancePrefix> I<prefix>
+
+Prefixes the generated I<plugin instance> with I<prefix>. I<(optional)>
+
+=item B<InstanceFrom> I<property>
+
+The I<object names> used by JMX to identify I<MBeans> include so called
+I<“properties”> which are basically key-value-pairs. If the given object name
+is not unique and multiple MBeans are returned, the values of those properties
+usually differ. You can use this option to build the I<plugin instance> from
+the appropriate property values. This option is optional and may be repeated to
+generate the I<plugin instance> from multiple property values. 
+
+=item B<E<lt>value /E<gt>> blocks
+
+The I<value> blocks map one or more attributes of an I<MBean> to a value list
+in I<collectd>. There must be at least one Value block within each I<MBean>
+block.
+
+=over 4
+
+=item B<Type> type
+
+Sets the data set used within I<collectd> to handle the values of the I<MBean>
+attribute.
+
+=item B<InstancePrefix> I<prefix>
+
+Works like the option of the same name directly beneath the I<MBean> block, but
+sets the type instance instead. I<(optional)>
+
+=item B<InstanceFrom> I<prefix>
+
+Works like the option of the same name directly beneath the I<MBean> block, but
+sets the type instance instead. I<(optional)>
+
+=item B<Table> B<true>|B<false>
+
+Set this to true if the returned attribute is a I<composite type>. If set to
+true, the keys within the I<composite type> is appended to the
+I<type instance>.
+
+=item B<Attribute> I<path>
+
+Sets the name of the attribute from which to read the value. You can access the
+keys of composite types by using a dot to concatenate the key name to the
+attribute name. For example: “attrib0.key42”. If B<Table> is set to B<true>
+I<path> must point to a I<composite type>, otherwise it must point to a numeric
+type. 
+
+=back
+
+=back
+
+=head3 Connection blocks
+
+Connection blocks specify I<how> to connect to an I<MBeanServer> and what data
+to retrieve. The following configuration options are available:
+
+=over 4
+
+=item B<Host> I<name>
+
+Host name used when dispatching the values to I<collectd>. The option sets this
+field only, it is I<not> used to connect to anything and doesn't need to be a
+real, resolvable name.
+
+=item B<ServiceURL> I<URL>
+
+Specifies how the I<MBeanServer> can be reached. Any string accepted by the
+I<JMXServiceURL> is valid.
+
+See also:
+L<http://java.sun.com/javase/6/docs/api/javax/management/remote/JMXServiceURL.html>
+
+=item B<User> I<name>
+
+Use I<name> to authenticate to the server. If not configured, “monitorRole”
+will be used.
+
+=item B<Password> I<password>
+
+Use I<password> to authenticate to the server. If not given, unauthenticated
+access is used.
+
+=item B<InstancePrefix> I<prefix>
+
+Prefixes the generated I<plugin instance> with I<prefix>. If a second
+I<InstancePrefix> is specified in a referenced I<MBean> block, the prefix
+specified in the I<Connection> block will appear at the beginning of the
+I<plugin instance>, the prefix specified in the I<MBean> block will be appended
+to it.
+
+=item B<Collect> I<mbean_block_name>
+
+Configures which of the I<MBean> blocks to use with this connection. May be
+repeated to collect multiple I<MBeans> from this server. 
+
+=back
+
+=head1 SEE ALSO
+
+L<collectd(1)>,
+L<collectd.conf(5)>,
+L<collectd-perl(5)>,
+L<types.db(5)>
+
+=head1 AUTHOR
+
+Florian Forster E<lt>octoE<nbsp>atE<nbsp>verplant.orgE<gt>
+
diff --git a/src/collectd-nagios.c b/src/collectd-nagios.c
new file mode 100644 (file)
index 0000000..88a5302
--- /dev/null
@@ -0,0 +1,741 @@
+/**
+ * collectd-nagios - src/collectd-nagios.c
+ * Copyright (C) 2008-2010  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#if HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#if !defined(__GNUC__) || !__GNUC__
+# define __attribute__(x) /**/
+#endif
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <strings.h>
+#include <assert.h>
+
+#if NAN_STATIC_DEFAULT
+# include <math.h>
+/* #endif NAN_STATIC_DEFAULT*/
+#elif NAN_STATIC_ISOC
+# ifndef __USE_ISOC99
+#  define DISABLE_ISOC99 1
+#  define __USE_ISOC99 1
+# endif /* !defined(__USE_ISOC99) */
+# include <math.h>
+# if DISABLE_ISOC99
+#  undef DISABLE_ISOC99
+#  undef __USE_ISOC99
+# endif /* DISABLE_ISOC99 */
+/* #endif NAN_STATIC_ISOC */
+#elif NAN_ZERO_ZERO
+# include <math.h>
+# ifdef NAN
+#  undef NAN
+# endif
+# define NAN (0.0 / 0.0)
+# ifndef isnan
+#  define isnan(f) ((f) != (f))
+# endif /* !defined(isnan) */
+# ifndef isfinite
+#  define isfinite(f) (((f) - (f)) == 0.0)
+# endif
+# ifndef isinf
+#  define isinf(f) (!isfinite(f) && !isnan(f))
+# endif
+#endif /* NAN_ZERO_ZERO */
+
+#include "libcollectdclient/client.h"
+
+#define RET_OKAY     0
+#define RET_WARNING  1
+#define RET_CRITICAL 2
+#define RET_UNKNOWN  3
+
+#define CON_NONE     0
+#define CON_AVERAGE  1
+#define CON_SUM      2
+#define CON_PERCENTAGE  3
+
+struct range_s
+{
+       double min;
+       double max;
+       int    invert;
+};
+typedef struct range_s range_t;
+
+extern char *optarg;
+extern int optind, opterr, optopt;
+
+static char *socket_file_g = NULL;
+static char *value_string_g = NULL;
+static char *hostname_g = NULL;
+
+static range_t range_critical_g;
+static range_t range_warning_g;
+static int consolitation_g = CON_NONE;
+static _Bool nan_is_error_g = 0;
+
+static char **match_ds_g = NULL;
+static int    match_ds_num_g = 0;
+
+/* `strdup' is an XSI extension. I don't want to pull in all of XSI just for
+ * that, so here's an own implementation.. It's easy enough. The GCC attributes
+ * are supposed to get good performance..  -octo */
+__attribute__((malloc, nonnull (1)))
+static char *cn_strdup (const char *str) /* {{{ */
+{
+  size_t strsize;
+  char *ret;
+
+  strsize = strlen (str) + 1;
+  ret = (char *) malloc (strsize);
+  if (ret != NULL)
+    memcpy (ret, str, strsize);
+  return (ret);
+} /* }}} char *cn_strdup */
+
+static int filter_ds (size_t *values_num,
+               double **values, char ***values_names)
+{
+       gauge_t *new_values;
+       char   **new_names;
+
+       size_t i;
+
+       if (match_ds_g == NULL)
+               return (RET_OKAY);
+
+       new_values = (gauge_t *)calloc (match_ds_num_g, sizeof (*new_values));
+       if (new_values == NULL)
+       {
+               fprintf (stderr, "malloc failed: %s\n", strerror (errno));
+               return (RET_UNKNOWN);
+       }
+
+       new_names = (char **)calloc (match_ds_num_g, sizeof (*new_names));
+       if (new_names == NULL)
+       {
+               fprintf (stderr, "malloc failed: %s\n", strerror (errno));
+               free (new_values);
+               return (RET_UNKNOWN);
+       }
+
+       for (i = 0; i < (size_t) match_ds_num_g; i++)
+       {
+               size_t j;
+
+               /* match_ds_g keeps pointers into argv but the names will be freed */
+               new_names[i] = cn_strdup (match_ds_g[i]);
+               if (new_names[i] == NULL)
+               {
+                       fprintf (stderr, "cn_strdup failed: %s\n", strerror (errno));
+                       free (new_values);
+                       for (j = 0; j < i; j++)
+                               free (new_names[j]);
+                       free (new_names);
+                       return (RET_UNKNOWN);
+               }
+
+               for (j = 0; j < *values_num; j++)
+                       if (strcasecmp (new_names[i], (*values_names)[j]) == 0)
+                               break;
+
+               if (j == *values_num)
+               {
+                       printf ("ERROR: DS `%s' is not available.\n", new_names[i]);
+                       free (new_values);
+                       for (j = 0; j <= i; j++)
+                               free (new_names[j]);
+                       free (new_names);
+                       return (RET_CRITICAL);
+               }
+
+               new_values[i] = (*values)[j];
+       }
+
+       free (*values);
+       for (i = 0; i < *values_num; i++)
+               free ((*values_names)[i]);
+       free (*values_names);
+
+       *values       = new_values;
+       *values_names = new_names;
+       *values_num   = match_ds_num_g;
+       return (RET_OKAY);
+} /* int filter_ds */
+
+static void parse_range (char *string, range_t *range)
+{
+       char *min_ptr;
+       char *max_ptr;
+
+       if (*string == '@')
+       {
+               range->invert = 1;
+               string++;
+       }
+
+       max_ptr = strchr (string, ':');
+       if (max_ptr == NULL)
+       {
+               min_ptr = NULL;
+               max_ptr = string;
+       }
+       else
+       {
+               min_ptr = string;
+               *max_ptr = '\0';
+               max_ptr++;
+       }
+
+       assert (max_ptr != NULL);
+
+       /* `10' == `0:10' */
+       if (min_ptr == NULL)
+               range->min = 0.0;
+       /* :10 == ~:10 == -inf:10 */
+       else if ((*min_ptr == '\0') || (*min_ptr == '~'))
+               range->min = NAN;
+       else
+               range->min = atof (min_ptr);
+
+       if ((*max_ptr == '\0') || (*max_ptr == '~'))
+               range->max = NAN;
+       else
+               range->max = atof (max_ptr);
+} /* void parse_range */
+
+static int match_range (range_t *range, double value)
+{
+       int ret = 0;
+
+       if (!isnan (range->min) && (range->min > value))
+               ret = 1;
+       if (!isnan (range->max) && (range->max < value))
+               ret = 1;
+
+       return (((ret - range->invert) == 0) ? 0 : 1);
+} /* int match_range */
+
+static void usage (const char *name)
+{
+       fprintf (stderr, "Usage: %s <-s socket> <-n value_spec> <-H hostname> [options]\n"
+                       "\n"
+                       "Valid options are:\n"
+                       "  -s <socket>    Path to collectd's UNIX-socket.\n"
+                       "  -n <v_spec>    Value specification to get from collectd.\n"
+                       "                 Format: `plugin-instance/type-instance'\n"
+                       "  -d <ds>        Select the DS to examine. May be repeated to examine multiple\n"
+                       "                 DSes. By default all DSes are used.\n"
+                       "  -g <consol>    Method to use to consolidate several DSes.\n"
+                       "                 See below for a list of valid arguments.\n"
+                       "  -H <host>      Hostname to query the values for.\n"
+                       "  -c <range>     Critical range\n"
+                       "  -w <range>     Warning range\n"
+                       "  -m             Treat \"Not a Number\" (NaN) as critical (default: warning)\n"
+                       "\n"
+                       "Consolidation functions:\n"
+                       "  none:          Apply the warning- and critical-ranges to each data-source\n"
+                       "                 individually.\n"
+                       "  average:       Calculate the average of all matching DSes and apply the\n"
+                       "                 warning- and critical-ranges to the calculated average.\n"
+                       "  sum:           Apply the ranges to the sum of all DSes.\n"
+                       "  percentage:    Apply the ranges to the ratio (in percent) of the first value\n"
+                       "                 and the sum of all values."
+                       "\n", name);
+       exit (1);
+} /* void usage */
+
+static int do_listval (lcc_connection_t *connection)
+{
+       lcc_identifier_t *ret_ident = NULL;
+       size_t ret_ident_num = 0;
+
+       char *hostname = NULL;
+
+       int status;
+       size_t i;
+
+       status = lcc_listval (connection, &ret_ident, &ret_ident_num);
+       if (status != 0) {
+               printf ("UNKNOWN: %s\n", lcc_strerror (connection));
+               if (ret_ident != NULL)
+                       free (ret_ident);
+               return (RET_UNKNOWN);
+       }
+
+       status = lcc_sort_identifiers (connection, ret_ident, ret_ident_num);
+       if (status != 0) {
+               printf ("UNKNOWN: %s\n", lcc_strerror (connection));
+               if (ret_ident != NULL)
+                       free (ret_ident);
+               return (RET_UNKNOWN);
+       }
+
+       for (i = 0; i < ret_ident_num; ++i) {
+               char id[1024];
+
+               if ((hostname_g != NULL) && (strcasecmp (hostname_g, ret_ident[i].host)))
+                       continue;
+
+               if ((hostname == NULL) || strcasecmp (hostname, ret_ident[i].host))
+               {
+                       if (hostname != NULL)
+                               free (hostname);
+                       hostname = strdup (ret_ident[i].host);
+                       printf ("Host: %s\n", hostname);
+               }
+
+               /* empty hostname; not to be printed again */
+               ret_ident[i].host[0] = '\0';
+
+               status = lcc_identifier_to_string (connection,
+                               id, sizeof (id), ret_ident + i);
+               if (status != 0) {
+                       printf ("ERROR: listval: Failed to convert returned "
+                                       "identifier to a string: %s\n",
+                                       lcc_strerror (connection));
+                       continue;
+               }
+
+               /* skip over the (empty) hostname and following '/' */
+               printf ("\t%s\n", id + 1);
+       }
+
+       if (ret_ident != NULL)
+               free (ret_ident);
+       return (RET_OKAY);
+} /* int do_listval */
+
+static int do_check_con_none (size_t values_num,
+               double *values, char **values_names)
+{
+       int num_critical = 0;
+       int num_warning  = 0;
+       int num_okay = 0;
+       const char *status_str = "UNKNOWN";
+       int status_code = RET_UNKNOWN;
+       size_t i;
+
+       for (i = 0; i < values_num; i++)
+       {
+               if (isnan (values[i]))
+               {
+                       if (nan_is_error_g)
+                               num_critical++;
+                       else
+                               num_warning++;
+               }
+               else if (match_range (&range_critical_g, values[i]) != 0)
+                       num_critical++;
+               else if (match_range (&range_warning_g, values[i]) != 0)
+                       num_warning++;
+               else
+                       num_okay++;
+       }
+
+       if ((num_critical == 0) && (num_warning == 0) && (num_okay == 0))
+       {
+               printf ("WARNING: No defined values found\n");
+               return (RET_WARNING);
+       }
+       else if ((num_critical == 0) && (num_warning == 0))
+       {
+               status_str = "OKAY";
+               status_code = RET_OKAY;
+       }
+       else if (num_critical == 0)
+       {
+               status_str = "WARNING";
+               status_code = RET_WARNING;
+       }
+       else
+       {
+               status_str = "CRITICAL";
+               status_code = RET_CRITICAL;
+       }
+
+       printf ("%s: %i critical, %i warning, %i okay", status_str,
+                       num_critical, num_warning, num_okay);
+       if (values_num > 0)
+       {
+               printf (" |");
+               for (i = 0; i < values_num; i++)
+                       printf (" %s=%f;;;;", values_names[i], values[i]);
+       }
+       printf ("\n");
+
+       return (status_code);
+} /* int do_check_con_none */
+
+static int do_check_con_average (size_t values_num,
+               double *values, char **values_names)
+{
+       size_t i;
+       double total;
+       int total_num;
+       double average;
+       const char *status_str = "UNKNOWN";
+       int status_code = RET_UNKNOWN;
+
+       total = 0.0;
+       total_num = 0;
+       for (i = 0; i < values_num; i++)
+       {
+               if (isnan (values[i]))
+               {
+                       if (!nan_is_error_g)
+                               continue;
+
+                       printf ("CRITICAL: Data source \"%s\" is NaN\n",
+                                       values_names[i]);
+                       return (RET_CRITICAL);
+               }
+
+               total += values[i];
+               total_num++;
+       }
+
+       if (total_num == 0)
+       {
+               printf ("WARNING: No defined values found\n");
+               return (RET_WARNING);
+       }
+
+       average = total / total_num;
+
+       if (match_range (&range_critical_g, average) != 0)
+       {
+               status_str = "CRITICAL";
+               status_code = RET_CRITICAL;
+       }
+       else if (match_range (&range_warning_g, average) != 0)
+       {
+               status_str = "WARNING";
+               status_code = RET_WARNING;
+       }
+       else
+       {
+               status_str = "OKAY";
+               status_code = RET_OKAY;
+       }
+
+       printf ("%s: %g average |", status_str, average);
+       for (i = 0; i < values_num; i++)
+               printf (" %s=%f;;;;", values_names[i], values[i]);
+       printf ("\n");
+
+       return (status_code);
+} /* int do_check_con_average */
+
+static int do_check_con_sum (size_t values_num,
+               double *values, char **values_names)
+{
+       size_t i;
+       double total;
+       int total_num;
+       const char *status_str = "UNKNOWN";
+       int status_code = RET_UNKNOWN;
+
+       total = 0.0;
+       total_num = 0;
+       for (i = 0; i < values_num; i++)
+       {
+               if (isnan (values[i]))
+               {
+                       if (!nan_is_error_g)
+                               continue;
+
+                       printf ("CRITICAL: Data source \"%s\" is NaN\n",
+                                       values_names[i]);
+                       return (RET_CRITICAL);
+               }
+
+               total += values[i];
+               total_num++;
+       }
+
+       if (total_num == 0)
+       {
+               printf ("WARNING: No defined values found\n");
+               return (RET_WARNING);
+       }
+
+       if (match_range (&range_critical_g, total) != 0)
+       {
+               status_str = "CRITICAL";
+               status_code = RET_CRITICAL;
+       }
+       else if (match_range (&range_warning_g, total) != 0)
+       {
+               status_str = "WARNING";
+               status_code = RET_WARNING;
+       }
+       else
+       {
+               status_str = "OKAY";
+               status_code = RET_OKAY;
+       }
+
+       printf ("%s: %g sum |", status_str, total);
+       for (i = 0; i < values_num; i++)
+               printf (" %s=%f;;;;", values_names[i], values[i]);
+       printf ("\n");
+
+       return (status_code);
+} /* int do_check_con_sum */
+
+static int do_check_con_percentage (size_t values_num,
+               double *values, char **values_names)
+{
+       size_t i;
+       double sum = 0.0;
+       double percentage;
+
+       const char *status_str  = "UNKNOWN";
+       int         status_code = RET_UNKNOWN;
+
+       if ((values_num < 1) || (isnan (values[0])))
+       {
+               printf ("WARNING: The first value is not defined\n");
+               return (RET_WARNING);
+       }
+
+       for (i = 0; i < values_num; i++)
+       {
+               if (isnan (values[i]))
+               {
+                       if (!nan_is_error_g)
+                               continue;
+
+                       printf ("CRITICAL: Data source \"%s\" is NaN\n",
+                                       values_names[i]);
+                       return (RET_CRITICAL);
+               }
+
+               sum += values[i];
+       }
+
+       if (sum == 0.0)
+       {
+               printf ("WARNING: Values sum up to zero\n");
+               return (RET_WARNING);
+       }
+
+       percentage = 100.0 * values[0] / sum;
+
+       if (match_range (&range_critical_g, percentage) != 0)
+       {
+               status_str  = "CRITICAL";
+               status_code = RET_CRITICAL;
+       }
+       else if (match_range (&range_warning_g, percentage) != 0)
+       {
+               status_str  = "WARNING";
+               status_code = RET_WARNING;
+       }
+       else
+       {
+               status_str  = "OKAY";
+               status_code = RET_OKAY;
+       }
+
+       printf ("%s: %lf percent |", status_str, percentage);
+       for (i = 0; i < values_num; i++)
+               printf (" %s=%lf;;;;", values_names[i], values[i]);
+       return (status_code);
+} /* int do_check_con_percentage */
+
+static int do_check (lcc_connection_t *connection)
+{
+       gauge_t *values;
+       char   **values_names;
+       size_t   values_num;
+       char ident_str[1024];
+       lcc_identifier_t ident;
+       size_t i;
+       int status;
+
+       snprintf (ident_str, sizeof (ident_str), "%s/%s",
+                       hostname_g, value_string_g);
+       ident_str[sizeof (ident_str) - 1] = 0;
+
+       memset (&ident, 0, sizeof (ident));
+       status = lcc_string_to_identifier (connection, &ident, ident_str);
+       if (status != 0)
+       {
+               printf ("ERROR: Creating an identifier failed: %s.\n",
+                               lcc_strerror (connection));
+               LCC_DESTROY (connection);
+               return (RET_CRITICAL);
+       }
+
+       status = lcc_getval (connection, &ident,
+                       &values_num, &values, &values_names);
+       if (status != 0)
+       {
+               printf ("ERROR: Retrieving values from the daemon failed: %s.\n",
+                               lcc_strerror (connection));
+               LCC_DESTROY (connection);
+               return (RET_CRITICAL);
+       }
+
+       LCC_DESTROY (connection);
+
+       status = filter_ds (&values_num, &values, &values_names);
+       if (status != RET_OKAY)
+               return (status);
+
+       status = RET_UNKNOWN;
+       if (consolitation_g == CON_NONE)
+               status =  do_check_con_none (values_num, values, values_names);
+       else if (consolitation_g == CON_AVERAGE)
+               status =  do_check_con_average (values_num, values, values_names);
+       else if (consolitation_g == CON_SUM)
+               status = do_check_con_sum (values_num, values, values_names);
+       else if (consolitation_g == CON_PERCENTAGE)
+               status = do_check_con_percentage (values_num, values, values_names);
+
+       free (values);
+       if (values_names != NULL)
+               for (i = 0; i < values_num; i++)
+                       free (values_names[i]);
+       free (values_names);
+
+       return (status);
+} /* int do_check */
+
+int main (int argc, char **argv)
+{
+       char address[1024];
+       lcc_connection_t *connection;
+
+       int status;
+
+       range_critical_g.min = NAN;
+       range_critical_g.max = NAN;
+       range_critical_g.invert = 0;
+
+       range_warning_g.min = NAN;
+       range_warning_g.max = NAN;
+       range_warning_g.invert = 0;
+
+       while (42)
+       {
+               int c;
+
+               c = getopt (argc, argv, "w:c:s:n:H:g:d:hm");
+               if (c < 0)
+                       break;
+
+               switch (c)
+               {
+                       case 'c':
+                               parse_range (optarg, &range_critical_g);
+                               break;
+                       case 'w':
+                               parse_range (optarg, &range_warning_g);
+                               break;
+                       case 's':
+                               socket_file_g = optarg;
+                               break;
+                       case 'n':
+                               value_string_g = optarg;
+                               break;
+                       case 'H':
+                               hostname_g = optarg;
+                               break;
+                       case 'g':
+                               if (strcasecmp (optarg, "none") == 0)
+                                       consolitation_g = CON_NONE;
+                               else if (strcasecmp (optarg, "average") == 0)
+                                       consolitation_g = CON_AVERAGE;
+                               else if (strcasecmp (optarg, "sum") == 0)
+                                       consolitation_g = CON_SUM;
+                               else if (strcasecmp (optarg, "percentage") == 0)
+                                       consolitation_g = CON_PERCENTAGE;
+                               else
+                               {
+                                       fprintf (stderr, "Unknown consolidation function `%s'.\n",
+                                                       optarg);
+                                       usage (argv[0]);
+                               }
+                               break;
+                       case 'd':
+                       {
+                               char **tmp;
+                               tmp = (char **) realloc (match_ds_g,
+                                               (match_ds_num_g + 1)
+                                               * sizeof (char *));
+                               if (tmp == NULL)
+                               {
+                                       fprintf (stderr, "realloc failed: %s\n",
+                                                       strerror (errno));
+                                       return (RET_UNKNOWN);
+                               }
+                               match_ds_g = tmp;
+                               match_ds_g[match_ds_num_g] = cn_strdup (optarg);
+                               if (match_ds_g[match_ds_num_g] == NULL)
+                               {
+                                       fprintf (stderr, "cn_strdup failed: %s\n",
+                                                       strerror (errno));
+                                       return (RET_UNKNOWN);
+                               }
+                               match_ds_num_g++;
+                               break;
+                       }
+                       case 'm':
+                               nan_is_error_g = 1;
+                               break;
+                       default:
+                               usage (argv[0]);
+               } /* switch (c) */
+       }
+
+       if ((socket_file_g == NULL) || (value_string_g == NULL)
+                       || ((hostname_g == NULL) && (strcasecmp (value_string_g, "LIST"))))
+       {
+               fprintf (stderr, "Missing required arguments.\n");
+               usage (argv[0]);
+       }
+
+       snprintf (address, sizeof (address), "unix:%s", socket_file_g);
+       address[sizeof (address) - 1] = 0;
+
+       connection = NULL;
+       status = lcc_connect (address, &connection);
+       if (status != 0)
+       {
+               printf ("ERROR: Connecting to daemon at %s failed.\n",
+                               socket_file_g);
+               return (RET_CRITICAL);
+       }
+
+       if (0 == strcasecmp (value_string_g, "LIST"))
+               return (do_listval (connection));
+
+       return (do_check (connection));
+} /* int main */
diff --git a/src/collectd-nagios.pod b/src/collectd-nagios.pod
new file mode 100644 (file)
index 0000000..d7c749c
--- /dev/null
@@ -0,0 +1,124 @@
+=head1 NAME
+
+collectd-nagios - Nagios plugin for querying collectd
+
+=head1 SYNOPSIS
+
+collectd-nagios B<-s> I<socket> B<-n> I<value_spec> B<-H> I<hostname> I<[options]>
+
+=head1 DESCRIPTION
+
+This small program is the glue between collectd and nagios. collectd collects
+various performance statistics which it provides via the C<unixsock plugin>,
+see L<collectd-unixsock(5)>. This program is called by Nagios, connects to the
+UNIX socket and reads the values from collectd. It then returns B<OKAY>,
+B<WARNING> or B<CRITICAL> depending on the values and the ranges provided by
+Nagios.
+
+=head1 ARGUMENTS AND OPTIONS
+
+The following arguments and options are required and understood by
+collectd-nagios. The order of the arguments generally doesn't matter, as long
+as no argument is passed more than once.
+
+=over 4
+
+=item B<-s> I<socket>
+
+Path of the UNIX socket opened by collectd's C<unixsock plugin>.
+
+=item B<-n> I<value_spec>
+
+The value to read from collectd. The argument is in the form
+C<plugin[-instance]/type[-instance]>.
+
+=item B<-H> I<hostname>
+
+Hostname to query the values for.
+
+=item B<-d> I<data_source>
+
+Each I<value_spec> may be made of multiple "data sources". With this option you
+can select one or more data sources. To select multiple data sources simply
+specify this option again. If multiple data sources are examined they are
+handled according to the consolidation function given with the B<-g> option.
+
+=item B<-g> B<none>I<|>B<average>I<|>B<sum>
+
+When multiple data sources are selected from a value spec, they can be handled
+differently depending on this option. The values of the following meaning:
+
+=over 4
+
+=item B<none>
+
+No consolidation if done and the warning and critical regions are applied to
+each value independently.
+
+=item B<average>
+
+The warning and critical ranges are applied to the average of all values.
+
+=item B<sum>
+
+The warning and critical ranges are applied to the sum of all values.
+
+=item B<percentage>
+
+The warning and critical ranges are applied to the ratio (in percent) of the
+first value and the sum of all values. A warning is returned if the first
+value is not defined or if all values sum up to zero.
+
+=back
+
+=item B<-c> I<range>
+
+=item B<-w> I<range>
+
+Set the critical (B<-c>) and warning (B<-w>) ranges. These options mostly
+follow the normal syntax of Nagios plugins. The general format is
+"I<min>B<:>I<max>". If a value is smaller than I<min> or bigger than I<max>, a
+I<warning> or I<critical> status is returned, otherwise the status is
+I<success>.
+
+The tilde sign (B<~>) can be used to explicitly specify infinity. If B<~> is
+used as a I<min> value, negative infinity is used. In case of I<max>, it is
+interpreted as positive infinity.
+
+If the first character of the I<range> is the atE<nbsp>sign (B<@>), the meaning
+of the range will be inverted. I.E<nbsp>e. all values I<within> the range will
+yield a I<warning> or I<critical> status, while all values I<outside> the range
+will result in a I<success> status.
+
+I<min> (and the colon) may be omitted,
+I<min> is then assumed to be zero. If I<max> (but not the trailing colon) is
+omitted, I<max> is assumed to be positive infinity.
+
+=item B<-m>
+
+If this option is given, "Not a Number" (NaN) is treated as I<critical>. By
+default, the I<none> consolidation reports NaNs as I<warning>. Other
+consolidations simply ignore NaN values.
+
+=back
+
+=head1 RETURN VALUE
+
+As usual for Nagios plugins, this program writes a short, one line status
+message to STDOUT and signals success or failure with it's return value. It
+exits with a return value of B<0> for I<success>, B<1> for I<warning> and B<2>
+for I<critical>. If the values are not available or some other error occurred,
+it returns B<3> for I<unknown>.
+
+=head1 SEE ALSO
+
+L<collectd(1)>,
+L<collectd.conf(5)>,
+L<collectd-unixsock(5)>,
+L<http://nagios.org/>
+
+=head1 AUTHOR
+
+Florian Forster E<lt>octoE<nbsp>atE<nbsp>verplant.orgE<gt>
+
+=cut
diff --git a/src/collectd-perl.pod b/src/collectd-perl.pod
new file mode 100644 (file)
index 0000000..d5401dd
--- /dev/null
@@ -0,0 +1,789 @@
+=head1 NAME
+
+collectd-perl - Documentation of collectd's C<perl plugin>
+
+=head1 SYNOPSIS
+
+  <LoadPlugin perl>
+    Globals true
+  </LoadPlugin>
+  # ...
+  <Plugin perl>
+    IncludeDir "/path/to/perl/plugins"
+    BaseName "Collectd::Plugins"
+    EnableDebugger ""
+    LoadPlugin "FooBar"
+
+    <Plugin FooBar>
+      Foo "Bar"
+    </Plugin>
+  </Plugin>
+
+=head1 DESCRIPTION
+
+The C<perl plugin> embeds a Perl-interpreter into collectd and provides an
+interface to collectd's plugin system. This makes it possible to write plugins
+for collectd in Perl. This is a lot more efficient than executing a
+Perl-script every time you want to read a value with the C<exec plugin> (see
+L<collectd-exec(5)>) and provides a lot more functionality, too.
+
+When loading the C<perl plugin>, the B<Globals> option should be enabled.
+Else, the perl plugin will fail to load any Perl modules implemented in C,
+which includes, amongst many others, the B<threads> module used by the plugin
+itself. See the documentation of the B<Globals> option in L<collectd.conf(5)>
+for details.
+
+=head1 CONFIGURATION
+
+=over 4
+
+=item B<LoadPlugin> I<Plugin>
+
+Loads the Perl plugin I<Plugin>. This does basically the same as B<use> would
+do in a Perl program. As a side effect, the first occurrence of this option
+causes the Perl-interpreter to be initialized.
+
+=item B<BaseName> I<Name>
+
+Prepends I<Name>B<::> to all plugin names loaded after this option. This is
+provided for convenience to keep plugin names short. All Perl-based plugins
+provided with the I<collectd> distributions reside in the C<Collectd::Plugins>
+namespace.
+
+=item E<lt>B<Plugin> I<Name>E<gt> block
+
+This block may be used to pass on configuration settings to a Perl plugin. The
+configuration is converted into a config-item data type which is passed to the
+registered configuration callback. See below for details about the config-item
+data type and how to register callbacks.
+
+The I<name> identifies the callback. It is used literally and independent of
+the B<BaseName> setting.
+
+=item B<EnableDebugger> I<Package>[=I<option>,...]
+
+Run collectd under the control of the Perl source debugger. If I<Package> is
+not the empty string, control is passed to the debugging, profiling, or
+tracing module installed as Devel::I<Package>. A comma-separated list of
+options may be specified after the "=" character. Please note that you may not
+leave out the I<Package> option even if you specify B<"">. This is the same as
+using the B<-d:Package> command line option.
+
+See L<perldebug> for detailed documentation about debugging Perl.
+
+This option does not prevent collectd from daemonizing, so you should start
+collectd with the B<-f> command line option. Else you will not be able to use
+the command line driven interface of the debugger.
+
+=item B<IncludeDir> I<Dir>
+
+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.
+
+=back
+
+=head1 WRITING YOUR OWN PLUGINS
+
+Writing your own plugins is quite simple. collectd manages plugins by means of
+B<dispatch functions> which call the appropriate B<callback functions>
+registered by the plugins. Any plugin basically consists of the implementation
+of these callback functions and initializing code which registers the
+functions with collectd. See the section "EXAMPLES" below for a really basic
+example. The following types of B<callback functions> are known to collectd
+(all of them are optional):
+
+=over 4
+
+=item configuration functions
+
+This type of functions is called during configuration if an appropriate
+B<Plugin> block has been encountered. It is called once for each B<Plugin>
+block which matches the name of the callback as provided with the
+B<plugin_register> method - see below.
+
+=item init functions
+
+This type of functions is called once after loading the module and before any
+calls to the read and write functions. It should be used to initialize the
+internal state of the plugin (e.E<nbsp>g. open sockets, ...). If the return
+value evaluates to B<false>, the plugin will be disabled.
+
+=item read functions
+
+This type of function is used to collect the actual data. It is called once
+per interval (see the B<Interval> configuration option of collectd). Usually
+it will call B<plugin_dispatch_values> to dispatch the values to collectd
+which will pass them on to all registered B<write functions>. If the return
+value evaluates to B<false> the plugin will be skipped for an increasing
+amount of time until it returns B<true> again.
+
+=item write functions
+
+This type of function is used to write the dispatched values. It is called
+once for each call to B<plugin_dispatch_values>.
+
+=item flush functions
+
+This type of function is used to flush internal caches of plugins. It is
+usually triggered by the user only. Any plugin which caches data before
+writing it to disk should provide this kind of callback function.
+
+=item log functions
+
+This type of function is used to pass messages of plugins or the daemon itself
+to the user.
+
+=item notification function
+
+This type of function is used to act upon notifications. In general, a
+notification is a status message that may be associated with a data instance.
+Usually, a notification is generated by the daemon if a configured threshold
+has been exceeded (see the section "THRESHOLD CONFIGURATION" in
+L<collectd.conf(5)> for more details), but any plugin may dispatch
+notifications as well.
+
+=item shutdown functions
+
+This type of function is called once before the daemon shuts down. It should
+be used to clean up the plugin (e.g. close sockets, ...).
+
+=back
+
+Any function (except log functions) may set the B<$@> variable to describe
+errors in more detail. The message will be passed on to the user using
+collectd's logging mechanism.
+
+See the documentation of the B<plugin_register> method in the section
+"METHODS" below for the number and types of arguments passed to each
+B<callback function>. This section also explains how to register B<callback
+functions> with collectd.
+
+To enable a plugin, copy it to a place where Perl can find it (i.E<nbsp>e. a
+directory listed in the B<@INC> array) just as any other Perl plugin and add
+an appropriate B<LoadPlugin> option to the configuration file. After
+restarting collectd you're done.
+
+=head1 DATA TYPES
+
+The following complex types are used to pass values between the Perl plugin
+and collectd:
+
+=over 4
+
+=item Config-Item
+
+A config-item is one structure which keeps the information provided in the
+configuration file. The array of children keeps one entry for each
+configuration option. Each such entry is another config-item structure, which
+may nest further if nested blocks are used.
+
+  {
+    key      => key,
+    values   => [ val1, val2, ... ],
+    children => [ { ... }, { ... }, ... ]
+  }
+
+=item Data-Set
+
+A data-set is a list of one or more data-sources. Each data-source defines a
+name, type, min- and max-value and the data-set wraps them up into one
+structure. The general layout looks like this:
+
+  [{
+    name => 'data_source_name',
+    type => DS_TYPE_COUNTER || DS_TYPE_GAUGE || DS_TYPE_DERIVE || DS_TYPE_ABSOLUTE,
+    min  => value || undef,
+    max  => value || undef
+  }, ...]
+
+=item Value-List
+
+A value-list is one structure which features an array of values and fields to
+identify the values, i.E<nbsp>e. time and host, plugin name and
+plugin-instance as well as a type and type-instance. Since the "type" is not
+included in the value-list but is passed as an extra argument, the general
+layout looks like this:
+
+  {
+    values => [123, 0.5],
+    time   => time (),
+    interval => $interval_g,
+    host   => $hostname_g,
+    plugin => 'myplugin',
+    type   => 'myplugin',
+    plugin_instance => '',
+    type_instance   => ''
+  }
+
+=item Notification
+
+A notification is one structure defining the severity, time and message of the
+status message as well as an identification of a data instance. Also, it
+includes an optional list of user-defined meta information represented as
+(name, value) pairs:
+
+  {
+    severity => NOTIF_FAILURE || NOTIF_WARNING || NOTIF_OKAY,
+    time     => time (),
+    message  => 'status message',
+    host     => $hostname_g,
+    plugin   => 'myplugin',
+    type     => 'mytype',
+    plugin_instance => '',
+    type_instance   => '',
+    meta     => [ { name => <name>, value => <value> }, ... ]
+  }
+
+=item Match-Proc
+
+A match-proc is one structure storing the callbacks of a "match" of the filter
+chain infrastructure. The general layout looks like this:
+
+  {
+    create  => 'my_create',
+    destroy => 'my_destroy',
+    match   => 'my_match'
+  }
+
+=item Target-Proc
+
+A target-proc is one structure storing the callbacks of a "target" of the
+filter chain infrastructure. The general layout looks like this:
+
+  {
+    create  => 'my_create',
+    destroy => 'my_destroy',
+    invoke  => 'my_invoke'
+  }
+
+=back
+
+=head1 METHODS
+
+The following functions provide the C-interface to Perl-modules. They are
+exported by the ":plugin" export tag (see the section "EXPORTS" below).
+
+=over 4
+
+=item B<plugin_register> (I<type>, I<name>, I<data>)
+
+Registers a callback-function or data-set.
+
+I<type> can be one of:
+
+=over 4
+
+=item TYPE_CONFIG
+
+=item TYPE_INIT
+
+=item TYPE_READ
+
+=item TYPE_WRITE
+
+=item TYPE_FLUSH
+
+=item TYPE_LOG
+
+=item TYPE_NOTIF
+
+=item TYPE_SHUTDOWN
+
+=item TYPE_DATASET
+
+=back
+
+I<name> is the name of the callback-function or the type of the data-set,
+depending on the value of I<type>. (Please note that the type of the data-set
+is the value passed as I<name> here and has nothing to do with the I<type>
+argument which simply tells B<plugin_register> what is being registered.)
+
+The last argument, I<data>, is either a function name or an array-reference.
+If I<type> is B<TYPE_DATASET>, then the I<data> argument must be an
+array-reference which points to an array of hashes. Each hash describes one
+data-set. For the exact layout see B<Data-Set> above. Please note that
+there is a large number of predefined data-sets available in the B<types.db>
+file which are automatically registered with collectd - see L<types.db(5)> for
+a description of the format of this file.
+
+B<Note>: Using B<plugin_register> to register a data-set is deprecated. Add
+the new type to a custom L<types.db(5)> file instead. This functionality might
+be removed in a future version of collectd.
+
+If the I<type> argument is any of the other types (B<TYPE_INIT>, B<TYPE_READ>,
+...) then I<data> is expected to be a function name. If the name is not
+prefixed with the plugin's package name collectd will add it automatically.
+The interface slightly differs from the C interface (which expects a function
+pointer instead) because Perl does not support to share references to
+subroutines between threads.
+
+These functions are called in the various stages of the daemon (see the
+section "WRITING YOUR OWN PLUGINS" above) and are passed the following
+arguments:
+
+=over 4
+
+=item TYPE_CONFIG
+
+The only argument passed is I<config-item>. See above for the layout of this
+data type.
+
+=item TYPE_INIT
+
+=item TYPE_READ
+
+=item TYPE_SHUTDOWN
+
+No arguments are passed.
+
+=item TYPE_WRITE
+
+The arguments passed are I<type>, I<data-set>, and I<value-list>. I<type> is a
+string. For the layout of I<data-set> and I<value-list> see above.
+
+=item TYPE_FLUSH
+
+The arguments passed are I<timeout> and I<identifier>. I<timeout> indicates
+that only data older than I<timeout> seconds is to be flushed. I<identifier>
+specifies which values are to be flushed.
+
+=item TYPE_LOG
+
+The arguments are I<log-level> and I<message>. The log level is small for
+important messages and high for less important messages. The least important
+level is B<LOG_DEBUG>, the most important level is B<LOG_ERR>. In between there
+are (from least to most important): B<LOG_INFO>, B<LOG_NOTICE>, and
+B<LOG_WARNING>. I<message> is simply a string B<without> a newline at the end.
+
+=item TYPE_NOTIF
+
+The only argument passed is I<notification>. See above for the layout of this
+data type.
+
+=back
+
+=item B<plugin_unregister> (I<type>, I<plugin>)
+
+Removes a callback or data-set from collectd's internal list of
+functionsE<nbsp>/ datasets.
+
+=item B<plugin_dispatch_values> (I<value-list>)
+
+Submits a I<value-list> to the daemon. If the data-set identified by
+I<value-list>->{I<type>}
+is found (and the number of values matches the number of data-sources) then the
+type, data-set and value-list is passed to all write-callbacks that are
+registered with the daemon.
+
+=item B<plugin_write> ([B<plugins> => I<...>][, B<datasets> => I<...>],
+B<valuelists> => I<...>)
+
+Calls the write function of the given I<plugins> with the provided I<data
+sets> and I<value lists>. In contrast to B<plugin_dispatch_values>, it does
+not update collectd's internal cache and bypasses the filter mechanism (see
+L<collectd.conf(5)> for details). If the B<plugins> argument has been omitted,
+the values will be dispatched to all registered write plugins. If the
+B<datasets> argument has been omitted, the required data sets are looked up
+according to the C<type> member in the appropriate value list. The value of
+all three arguments may either be a single scalar or a reference to an array.
+If the B<datasets> argument has been specified, the number of data sets has to
+equal the number of specified value lists.
+
+=item B<plugin_flush> ([B<timeout> => I<timeout>][, B<plugins> => I<...>][,
+B<identifiers> => I<...>])
+
+Flush one or more plugins. I<timeout> and the specified I<identifiers> are
+passed on to the registered flush-callbacks. If omitted, the timeout defaults
+to C<-1>. The identifier defaults to the undefined value. If the B<plugins>
+argument has been specified, only named plugins will be flushed. The value of
+the B<plugins> and B<identifiers> arguments may either be a string or a
+reference to an array of strings.
+
+=item B<plugin_dispatch_notification> (I<notification>)
+
+Submits a I<notification> to the daemon which will then pass it to all
+notification-callbacks that are registered.
+
+=item B<plugin_log> (I<log-level>, I<message>)
+
+Submits a I<message> of level I<log-level> to collectd's logging mechanism.
+The message is passed to all log-callbacks that are registered with collectd.
+
+=item B<ERROR>, B<WARNING>, B<NOTICE>, B<INFO>, B<DEBUG> (I<message>)
+
+Wrappers around B<plugin_log>, using B<LOG_ERR>, B<LOG_WARNING>,
+B<LOG_NOTICE>, B<LOG_INFO> and B<LOG_DEBUG> respectively as I<log-level>.
+
+=back
+
+The following function provides the filter chain C-interface to Perl-modules.
+It is exported by the ":filter_chain" export tag (see the section "EXPORTS"
+below).
+
+=over 4
+
+=item B<fc_register> (I<type>, I<name>, I<proc>)
+
+Registers filter chain callbacks with collectd.
+
+I<type> may be any of:
+
+=over 4
+
+=item FC_MATCH
+
+=item FC_TARGET
+
+=back
+
+I<name> is the name of the match or target. By this name, the callbacks are
+identified in the configuration file when specifying a B<Match> or B<Target>
+block (see L<collectd.conf(5)> for details).
+
+I<proc> is a hash reference. The hash includes up to three callbacks: an
+optional constructor (B<create>) and destructor (B<destroy>) and a mandatory
+B<match> or B<invoke> callback. B<match> is called whenever processing an
+appropriate match, while B<invoke> is called whenever processing an
+appropriate target (see the section "FILTER CONFIGURATION" in
+L<collectd.conf(5)> for details). Just like any other callbacks, filter chain
+callbacks are identified by the function name rather than a function pointer
+because Perl does not support to share references to subroutines between
+threads. The following arguments are passed to the callbacks:
+
+=over 4
+
+=item create
+
+The arguments passed are I<config-item> and I<user-data>. See above for the
+layout of the config-item data-type. I<user-data> is a reference to a scalar
+value that may be used to store any information specific to this particular
+instance. The daemon does not care about this information at all. It's for the
+plugin's use only.
+
+=item destroy
+
+The only argument passed is I<user-data> which is a reference to the user data
+initialized in the B<create> callback. This callback may be used to cleanup
+instance-specific information and settings.
+
+=item match, invoke
+
+The arguments passed are I<data-set>, I<value-list>, I<meta> and I<user-data>.
+See above for the layout of the data-set and value-list data-types. I<meta> is
+a pointer to an array of meta information, just like the B<meta> member of the
+notification data-type (see above). I<user-data> is a reference to the user
+data initialized in the B<create> callback.
+
+=back
+
+=back
+
+=head1 GLOBAL VARIABLES
+
+=over 4
+
+=item B<$hostname_g>
+
+As the name suggests this variable keeps the hostname of the system collectd
+is running on. The value might be influenced by the B<Hostname> or
+B<FQDNLookup> configuration options (see L<collectd.conf(5)> for details).
+
+=item B<$interval_g>
+
+This variable keeps the interval in seconds in which the read functions are
+queried (see the B<Interval> configuration option).
+
+=back
+
+Any changes to these variables will be globally visible in collectd.
+
+=head1 EXPORTS
+
+By default no symbols are exported. However, the following export tags are
+available (B<:all> will export all of them):
+
+=over 4
+
+=item B<:plugin>
+
+=over 4
+
+=item B<plugin_register> ()
+
+=item B<plugin_unregister> ()
+
+=item B<plugin_dispatch_values> ()
+
+=item B<plugin_flush> ()
+
+=item B<plugin_flush_one> ()
+
+=item B<plugin_flush_all> ()
+
+=item B<plugin_dispatch_notification> ()
+
+=item B<plugin_log> ()
+
+=back
+
+=item B<:types>
+
+=over 4
+
+=item B<TYPE_CONFIG>
+
+=item B<TYPE_INIT>
+
+=item B<TYPE_READ>
+
+=item B<TYPE_WRITE>
+
+=item B<TYPE_FLUSH>
+
+=item B<TYPE_SHUTDOWN>
+
+=item B<TYPE_LOG>
+
+=item B<TYPE_DATASET>
+
+=back
+
+=item B<:ds_types>
+
+=over 4
+
+=item B<DS_TYPE_COUNTER>
+
+=item B<DS_TYPE_GAUGE>
+
+=item B<DS_TYPE_DERIVE>
+
+=item B<DS_TYPE_ABSOLUTE>
+
+=back
+
+=item B<:log>
+
+=over 4
+
+=item B<ERROR> ()
+
+=item B<WARNING> ()
+
+=item B<NOTICE> ()
+
+=item B<INFO> ()
+
+=item B<DEBUG> ()
+
+=item B<LOG_ERR>
+
+=item B<LOG_WARNING>
+
+=item B<LOG_NOTICE>
+
+=item B<LOG_INFO>
+
+=item B<LOG_DEBUG>
+
+=back
+
+=item B<:filter_chain>
+
+=over 4
+
+=item B<fc_register>
+
+=item B<FC_MATCH_NO_MATCH>
+
+=item B<FC_MATCH_MATCHES>
+
+=item B<FC_TARGET_CONTINUE>
+
+=item B<FC_TARGET_STOP>
+
+=item B<FC_TARGET_RETURN>
+
+=back
+
+=item B<:fc_types>
+
+=over 4
+
+=item B<FC_MATCH>
+
+=item B<FC_TARGET>
+
+=back
+
+=item B<:notif>
+
+=over 4
+
+=item B<NOTIF_FAILURE>
+
+=item B<NOTIF_WARNING>
+
+=item B<NOTIF_OKAY>
+
+=back
+
+=item B<:globals>
+
+=over 4
+
+=item B<$hostname_g>
+
+=item B<$interval_g>
+
+=back
+
+=back
+
+=head1 EXAMPLES
+
+Any Perl plugin will start similar to:
+
+  package Collectd::Plugins::FooBar;
+
+  use strict;
+  use warnings;
+
+  use Collectd qw( :all );
+
+A very simple read function might look like:
+
+  sub foobar_read
+  {
+    my $vl = { plugin => 'foobar', type => 'gauge' };
+    $vl->{'values'} = [ rand(42) ];
+    plugin_dispatch_values ($vl);
+    return 1;
+  }
+
+A very simple write function might look like:
+
+  sub foobar_write
+  {
+    my ($type, $ds, $vl) = @_;
+    for (my $i = 0; $i < scalar (@$ds); ++$i) {
+      print "$vl->{'plugin'} ($vl->{'type'}): $vl->{'values'}->[$i]\n";
+    }
+    return 1;
+  }
+
+A very simple match callback might look like:
+
+  sub foobar_match
+  {
+    my ($ds, $vl, $meta, $user_data) = @_;
+    if (matches($ds, $vl)) {
+      return FC_MATCH_MATCHES;
+    } else {
+      return FC_MATCH_NO_MATCH;
+    }
+  }
+
+To register those functions with collectd:
+
+  plugin_register (TYPE_READ, "foobar", "foobar_read");
+  plugin_register (TYPE_WRITE, "foobar", "foobar_write");
+
+  fc_register (FC_MATCH, "foobar", "foobar_match");
+
+See the section "DATA TYPES" above for a complete documentation of the data
+types used by the read, write and match functions.
+
+=head1 NOTES
+
+=over 4
+
+=item
+
+Please feel free to send in new plugins to collectd's mailing list at
+E<lt>collectdE<nbsp>atE<nbsp>verplant.orgE<gt> for review and, possibly,
+inclusion in the main distribution. In the latter case, we will take care of
+keeping the plugin up to date and adapting it to new versions of collectd.
+
+Before submitting your plugin, please take a look at
+L<http://collectd.org/dev-info.shtml>.
+
+=back
+
+=head1 CAVEATS
+
+=over 4
+
+=item
+
+collectd is heavily multi-threaded. Each collectd thread accessing the perl
+plugin will be mapped to a Perl interpreter thread (see L<threads(3perl)>).
+Any such thread will be created and destroyed transparently and on-the-fly.
+
+Hence, any plugin has to be thread-safe if it provides several entry points
+from collectd (i.E<nbsp>e. if it registers more than one callback or if a
+registered callback may be called more than once in parallel). Please note
+that no data is shared between threads by default. You have to use the
+B<threads::shared> module to do so.
+
+=item
+
+Each function name registered with collectd has to be available before the
+first thread has been created (i.E<nbsp>e. basically at compile time). This
+basically means that hacks (yes, I really consider this to be a hack) like
+C<*foo = \&bar; plugin_register (TYPE_READ, "plugin", "foo");> most likely
+will not work. This is due to the fact that the symbol table is not shared
+across different threads.
+
+=item
+
+Each plugin is usually only loaded once and kept in memory for performance
+reasons. Therefore, END blocks are only executed once when collectd shuts
+down. You should not rely on END blocks anyway - use B<shutdown functions>
+instead.
+
+=item
+
+The perl plugin exports the internal API of collectd which is considered
+unstable and subject to change at any time. We try hard to not break backwards
+compatibility in the Perl API during the life cycle of one major release.
+However, this cannot be guaranteed at all times. Watch out for warnings
+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)>,
+L<collectd.conf(5)>,
+L<collectd-exec(5)>,
+L<types.db(5)>,
+L<perl(1)>,
+L<threads(3perl)>,
+L<threads::shared(3perl)>,
+L<perldebug(1)>
+
+=head1 AUTHOR
+
+The C<perl plugin> has been written by Sebastian Harl
+E<lt>shE<nbsp>atE<nbsp>tokkee.orgE<gt>.
+
+This manpage has been written by Florian Forster
+E<lt>octoE<nbsp>atE<nbsp>verplant.orgE<gt> and Sebastian Harl
+E<lt>shE<nbsp>atE<nbsp>tokkee.orgE<gt>.
+
+=cut
+
diff --git a/src/collectd-python.pod b/src/collectd-python.pod
new file mode 100644 (file)
index 0000000..5fd1f4f
--- /dev/null
@@ -0,0 +1,731 @@
+# 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.
+
+=head1 NAME
+
+collectd-python - Documentation of collectd's C<python plugin>
+
+=head1 SYNOPSIS
+
+  <LoadPlugin python>
+    Globals true
+  </LoadPlugin>
+  # ...
+  <Plugin python>
+    ModulePath "/path/to/your/python/modules"
+    LogTraces true
+    Interactive false
+    Import "spam"
+
+    <Module spam>
+      spam "wonderful" "lovely"
+    </Module>
+  </Plugin>
+
+=head1 DESCRIPTION
+
+The C<python plugin> embeds a Python-interpreter into collectd and provides an
+interface to collectd's plugin system. This makes it possible to write plugins
+for collectd in Python. This is a lot more efficient than executing a
+Python-script every time you want to read a value with the C<exec plugin> (see
+L<collectd-exec(5)>) and provides a lot more functionality, too.
+
+At least python I<version 2.3> is required.
+
+=head1 CONFIGURATION
+
+=over 4
+
+=item B<LoadPlugin> I<Plugin>
+
+Loads the Python plugin I<Plugin>. Unlike most other LoadPlugin lines, this one
+should be a block containing the line "Globals true". This will cause collectd
+to export the name of all objects in the python interpreter for all plugins to
+see. If you don't do this or your platform does not support it, the embedded
+interpreter will start anyway but you won't be able to load certain python
+modules, e.g. "time".
+
+=item B<Encoding> I<Name>
+
+The default encoding for Unicode objects you pass to collectd. If you omit this
+option it will default to B<ascii> on I<Python 2> and B<utf-8> on I<Python 3>.
+This is hardcoded in Python and will ignore everything else, including your
+locale.
+
+=item B<ModulePath> I<Name>
+
+Appends I<Name> to B<sys.path>. You won't be able to import any scripts you
+wrote unless they are located in one of the directories in this list. Please
+note that it only has effect on plugins loaded after this option. You can
+use multiple B<ModulePath> lines to add more than one directory.
+
+=item B<LogTraces> I<bool>
+
+If a python script throws an exception it will be logged by collectd with the
+name of the exception and the message. If you set this option to true it will
+also log the full stacktrace just like the default output of an interactive
+python interpreter. This should probably be set to false most of the time but
+is very useful for development and debugging of new modules.
+
+=item B<Interactive> I<bool>
+
+This option will cause the module to launch an interactive python interpreter
+that reads from and writes to the terminal. Note that collectd will terminate
+right after starting up if you try to run it as a daemon while this option is
+enabled to make sure to start collectd with the B<-f> option.
+
+The B<collectd> module is I<not> imported into the interpreter's globals. You
+have to do it manually. Be sure to read the help text of the module, it can be
+used as a reference guide during coding.
+
+This interactive session will behave slightly differently from a daemonized
+collectd script as well as from a normal python interpreter:
+
+=over 4
+
+=item
+
+B<1.> collectd will try to import the B<readline> module to give you a decent
+way of entering your commands. The daemonized collectd won't do that.
+
+=item
+
+B<2.> collectd will block I<SIGINT>. Pressing I<Ctrl+C> will usually cause
+collectd to shut down. This would be problematic in an interactive session,
+therefore this signal will be blocked. You can still use it to interrupt
+syscalls like sleep and pause but it won't generate a I<KeyboardInterrupt>
+exception either.
+
+To quit collectd send I<EOF> (press I<Ctrl+D> at the beginning of a new line).
+
+=item
+
+B<3.> collectd handles I<SIGCHLD>. This means that python won't be able to
+determine the return code of spawned processes with system(), popen() and
+subprocess. This will result in python not using external programs like less
+to display help texts. You can override this behavior with the B<PAGER>
+environment variable, e.g. I<export PAGER=less> before starting collectd.
+Depending on your version of python this might or might not result in an
+B<OSError> exception which can be ignored.
+
+If you really need to spawn new processes from python you can register an init
+callback and reset the action for SIGCHLD to the default behavior. Please note
+that this I<will> break the exec plugin. Do not even load the exec plugin if
+you intend to do this!
+
+There is an example script located in B<contrib/python/getsigchld.py>  to do
+this. If you import this from I<collectd.conf> SIGCHLD will be handled
+normally and spawning processes from python will work as intended.
+
+=back
+
+=item E<lt>B<Module> I<Name>E<gt> block
+
+This block may be used to pass on configuration settings to a Python module.
+The configuration is converted into an instance of the B<Config> class which is
+passed to the registered configuration callback. See below for details about
+the B<Config> class and how to register callbacks.
+
+The I<name> identifies the callback.
+
+=back
+
+=head1 STRINGS
+
+There are a lot of places where strings are send from collectd to python and
+from python to collectd. How exactly this works depends on wheather byte or
+unicode strings or python2 or python3 are used.
+
+Python2 has I<str>, which is just bytes, and I<unicode>. Python3 has I<str>,
+which is a unicode object, and I<bytes>.
+
+When passing strings from python to collectd all of these object are supported
+in all places, however I<str> should be used if possible. These strings must
+not contain a NUL byte. Ignoring this will result in a I<TypeError> exception.
+If a byte string was used it will be used as is by collectd. If a unicode
+object was used it will be encoded using the default encoding (see above). If
+this is not possible python will raise a I<UnicodeEncodeError> exception.
+
+Wenn passing strings from collectd to python the behavior depends on the
+python version used. Python2 will always receive a I<str> object. Python3 will
+usually receive a I<str> object as well, however the original string will be
+decoded to unicode using the default encoding. If this fails because the
+string is not a valid sequence for this encoding a I<bytes> object will be
+returned instead.
+
+=head1 WRITING YOUR OWN PLUGINS
+
+Writing your own plugins is quite simple. collectd manages plugins by means of
+B<dispatch functions> which call the appropriate B<callback functions>
+registered by the plugins. Any plugin basically consists of the implementation
+of these callback functions and initializing code which registers the
+functions with collectd. See the section "EXAMPLES" below for a really basic
+example. The following types of B<callback functions> are known to collectd
+(all of them are optional):
+
+=over 4
+
+=item configuration functions
+
+This type of functions is called during configuration if an appropriate
+B<Module> block has been encountered. It is called once for each B<Module>
+block which matches the name of the callback as provided with the
+B<register_config> method - see below.
+
+Python thread support has not been initialized at this point so do not use any
+threading functions here!
+
+=item init functions
+
+This type of functions is called once after loading the module and before any
+calls to the read and write functions. It should be used to initialize the
+internal state of the plugin (e.E<nbsp>g. open sockets, ...). This is the
+earliest point where you may use threads.
+
+=item read functions
+
+This type of function is used to collect the actual data. It is called once
+per interval (see the B<Interval> configuration option of collectd). Usually
+it will call B<plugin_dispatch_values> to dispatch the values to collectd
+which will pass them on to all registered B<write functions>. If this function
+throws any kind of exception the plugin will be skipped for an increasing
+amount of time until it returns normally again.
+
+=item write functions
+
+This type of function is used to write the dispatched values. It is called
+once for every value that was dispatched by any plugin.
+
+=item flush functions
+
+This type of function is used to flush internal caches of plugins. It is
+usually triggered by the user only. Any plugin which caches data before
+writing it to disk should provide this kind of callback function.
+
+=item log functions
+
+This type of function is used to pass messages of plugins or the daemon itself
+to the user.
+
+=item notification function
+
+This type of function is used to act upon notifications. In general, a
+notification is a status message that may be associated with a data instance.
+Usually, a notification is generated by the daemon if a configured threshold
+has been exceeded (see the section "THRESHOLD CONFIGURATION" in
+L<collectd.conf(5)> for more details), but any plugin may dispatch
+notifications as well.
+
+=item shutdown functions
+
+This type of function is called once before the daemon shuts down. It should
+be used to clean up the plugin (e.g. close sockets, ...).
+
+=back
+
+Any function (except log functions) may set throw an exception in case of any
+errors. The exception will be passed on to the user using collectd's logging
+mechanism. If a log callback throws an exception it will be printed to standard
+error instead.
+
+See the documentation of the various B<register_> methods in the section
+"FUNCTIONS" below for the number and types of arguments passed to each
+B<callback function>. This section also explains how to register B<callback
+functions> with collectd.
+
+To enable a module, copy it to a place where Python can find it (i.E<nbsp>e. a
+directory listed in B<sys.path>) just as any other Python plugin and add
+an appropriate B<Import> option to the configuration file. After restarting
+collectd you're done.
+
+=head1 CLASSES
+
+The following complex types are used to pass values between the Python plugin
+and collectd:
+
+=head2 Signed
+
+The Signed class is just a long. It has all its methods and behaves exactly
+like any other long object. It is used to indicate if an integer was or should
+be stored as a signed or unsigned integer object.
+
+ class Signed(long)
+
+This is a long by another name. Use it in meta data dicts
+to choose the way it is stored in the meta data.
+
+=head2 Unsigned
+
+The Unsigned class is just a long. It has all its methods and behaves exactly
+like any other long object. It is used to indicate if an integer was or should
+be stored as a signed or unsigned integer object.
+
+ class Unsigned(long)
+
+This is a long by another name. Use it in meta data dicts
+to choose the way it is stored in the meta data.
+
+=head2 Config
+
+The Config class is an object which keeps the information provided in the
+configuration file. The sequence of children keeps one entry for each
+configuration option. Each such entry is another Config instance, which
+may nest further if nested blocks are used.
+
+ class Config(object)
+
+This represents a piece of collectd's config file. It is passed to scripts with
+config callbacks (see B<register_config>) and is of little use if created
+somewhere else.
+
+It has no methods beyond the bare minimum and only exists for its data members.
+
+Data descriptors defined here:
+
+=over 4
+
+=item parent
+
+This represents the parent of this node. On the root node
+of the config tree it will be None.
+
+=item key
+
+This is the keyword of this item, i.e. the first word of any given line in the
+config file. It will always be a string.
+
+=item values
+
+This is a tuple (which might be empty) of all value, i.e. words following the
+keyword in any given line in the config file.
+
+Every item in this tuple will be either a string or a float or a boolean,
+depending on the contents of the configuration file.
+
+=item children
+
+This is a tuple of child nodes. For most nodes this will be empty. If this node
+represents a block instead of a single line of the config file it will contain
+all nodes in this block.
+
+=back
+
+=head2 PluginData
+
+This should not be used directly but it is the base class for both Values and
+Notification. It is used to identify the source of a value or notification.
+
+ class PluginData(object)
+
+This is an internal class that is the base for Values and Notification. It is
+pretty useless by itself and was therefore not exported to the collectd module.
+
+Data descriptors defined here:
+
+=over 4
+
+=item host
+
+The hostname of the host this value was read from. For dispatching this can be
+set to an empty string which means the local hostname as defined in
+collectd.conf.
+
+=item plugin
+
+The name of the plugin that read the data. Setting this member to an empty
+string will insert "python" upon dispatching.
+
+=item plugin_instance
+
+Plugin instance string. May be empty.
+
+=item time
+
+This is the Unix timestamp of the time this value was read. For dispatching
+values this can be set to zero which means "now". This means the time the value
+is actually dispatched, not the time it was set to 0.
+
+=item type
+
+The type of this value. This type has to be defined in your I<types.db>.
+Attempting to set it to any other value will raise a I<TypeError> exception.
+Assigning a type is mandatory, calling dispatch without doing so will raise a
+I<RuntimeError> exception.
+
+=item type_instance
+
+Type instance string. May be empty.
+
+=back
+
+=head2 Values
+
+A Value is an object which features a sequence of values. It is based on then
+I<PluginData> type and uses its members to identify the values.
+
+ class Values(PluginData)
+
+A Values object used for dispatching values to collectd and receiving values
+from write callbacks.
+
+Method resolution order:
+
+=over 4
+
+=item Values
+
+=item PluginData
+
+=item object
+
+=back
+
+Methods defined here:
+
+=over 4
+
+=item B<dispatch>([type][, values][, plugin_instance][, type_instance][, plugin][, host][, time][, interval]) -> None.
+
+Dispatch this instance to the collectd process. The object has members for each
+of the possible arguments for this method. For a detailed explanation of these
+parameters see the member of the same same.
+
+If you do not submit a parameter the value saved in its member will be
+submitted. If you do provide a parameter it will be used instead, without
+altering the member.
+
+=item B<write>([destination][, type][, values][, plugin_instance][, type_instance][, plugin][, host][, time][, interval]) -> None.
+
+Write this instance to a single plugin or all plugins if "destination" is
+omitted. This will bypass the main collectd process and all filtering and
+caching. Other than that it works similar to "dispatch". In most cases
+"dispatch" should be used instead of "write".
+
+=back
+
+Data descriptors defined here:
+
+=over 4
+
+=item interval
+
+The interval is the timespan in seconds between two submits for the same data
+source. This value has to be a positive integer, so you can't submit more than
+one value per second. If this member is set to a non-positive value, the
+default value as specified in the config file will be used (default: 10).
+
+If you submit values more often than the specified interval, the average will
+be used. If you submit less values, your graphs will have gaps.
+
+=item values
+
+These are the actual values that get dispatched to collectd. It has to be a
+sequence (a tuple or list) of numbers. The size of the sequence and the type of
+its content depend on the type member your I<types.db> file. For more
+information on this read the L<types.db(5)> manual page.
+
+If the sequence does not have the correct size upon dispatch a I<RuntimeError>
+exception will be raised. If the content of the sequence is not a number, a
+I<TypeError> exception will be raised.
+
+=item meta
+
+These are the meta data for this Value object.
+It has to be a dictionary of numbers, strings or bools. All keys must be
+strings. I<int> and <long> objects will be dispatched as signed integers unless
+they are between 2**63 and 2**64-1, which will result in a unsigned integer.
+You can force one of these storage classes by using the classes
+B<collectd.Signed> and B<collectd.Unsigned>. A meta object received by a write
+callback will always contain B<Signed> or B<Unsigned> objects.
+
+=back
+
+=head2 Notification
+
+A notification is an object defining the severity and message of the status
+message as well as an identification of a data instance by means of the members
+of I<PluginData> on which it is based.
+
+class Notification(PluginData)
+The Notification class is a wrapper around the collectd notification.
+It can be used to notify other plugins about bad stuff happening. It works
+similar to Values but has a severity and a message instead of interval
+and time.
+Notifications can be dispatched at any time and can be received with
+register_notification.
+
+Method resolution order:
+
+=over 4
+
+=item Notification
+
+=item PluginData
+
+=item object
+
+=back
+
+Methods defined here:
+
+=over 4
+
+=item B<dispatch>([type][, values][, plugin_instance][, type_instance][, plugin][, host][, time][, interval]) -> None.  Dispatch a value list.
+
+Dispatch this instance to the collectd process. The object has members for each
+of the possible arguments for this method. For a detailed explanation of these
+parameters see the member of the same same.
+
+If you do not submit a parameter the value saved in its member will be
+submitted. If you do provide a parameter it will be used instead, without
+altering the member.
+
+=back
+
+Data descriptors defined here:
+
+=over 4
+
+=item message
+
+Some kind of description what's going on and why this Notification was
+generated.
+
+=item severity
+
+The severity of this notification. Assign or compare to I<NOTIF_FAILURE>,
+I<NOTIF_WARNING> or I<NOTIF_OKAY>.
+
+=back
+
+=head1 FUNCTIONS
+
+The following functions provide the C-interface to Python-modules.
+
+=over 4
+
+=item B<register_*>(I<callback>[, I<data>][, I<name>]) -> identifier
+
+There are eight different register functions to get callback for eight
+different events. With one exception all of them are called as shown above.
+
+=over 4
+
+=item
+
+I<callback> is a callable object that will be called every time the event is
+triggered.
+
+=item
+
+I<data> is an optional object that will be passed back to the callback function
+every time it is called. If you omit this parameter no object is passed back to
+your callback, not even None.
+
+=item
+
+I<name> is an optional identifier for this callback. The default name is
+B<python>.I<module>. I<module> is taken from the B<__module__> attribute of
+your callback function. Every callback needs a unique identifier, so if you
+want to register the same callback multiple time in the same module you need to
+specify a name here. Otherwise it's save to ignore this parameter I<identifier>
+is the full identifier assigned to this callback.
+
+=back
+
+These functions are called in the various stages of the daemon (see the section
+L<"WRITING YOUR OWN PLUGINS"> above) and are passed the following arguments:
+
+=over 4
+
+=item register_config
+
+The only argument passed is a I<Config> object. See above for the layout of this
+data type.
+Note that you can not receive the whole config files this way, only B<Module>
+blocks inside the Python configuration block. Additionally you will only
+receive blocks where your callback identifier matches B<python.>I<blockname>.
+
+=item register_init
+
+The callback will be called without arguments.
+
+=item register_read(callback[, interval][, data][, name]) -> identifier
+
+This function takes an additional parameter: I<interval>. It specifies the
+time between calls to the callback function.
+
+The callback will be called without arguments.
+
+=item register_shutdown
+
+The callback will be called without arguments.
+
+=item register_write
+
+The callback function will be called with one arguments passed, which will be a
+I<Values> object. For the layout of I<Values> see above.
+If this callback function throws an exception the next call will be delayed by
+an increasing interval.
+
+=item register_flush
+
+Like B<register_config> is important for this callback because it determines
+what flush requests the plugin will receive.
+
+The arguments passed are I<timeout> and I<identifier>. I<timeout> indicates
+that only data older than I<timeout> seconds is to be flushed. I<identifier>
+specifies which values are to be flushed.
+
+=item register_log
+
+The arguments are I<severity> and I<message>. The severity is an integer and
+small for important messages and high for less important messages. The least
+important level is B<LOG_DEBUG>, the most important level is B<LOG_ERR>. In
+between there are (from least to most important): B<LOG_INFO>, B<LOG_NOTICE>,
+and B<LOG_WARNING>. I<message> is simply a string B<without> a newline at the
+end.
+
+If this callback throws an exception it will B<not> be logged. It will just be
+printed to B<sys.stderr> which usually means silently ignored.
+
+=item register_notification
+
+The only argument passed is a I<Notification> object. See above for the layout of this
+data type.
+
+=back
+
+=item B<unregister_*>(I<identifier>) -> None
+
+Removes a callback or data-set from collectd's internal list of callback
+functions. Every I<register_*> function has an I<unregister_*> function.
+I<identifier> is either the string that was returned by the register function
+or a callback function. The identifier will be constructed in the same way as
+for the register functions.
+
+=item B<flush>(I<plugin[, I<timeout>][, I<identifier>]) -> None
+
+Flush one or all plugins. I<timeout> and the specified I<identifiers> are
+passed on to the registered flush-callbacks. If omitted, the timeout defaults
+to C<-1>. The identifier defaults to None. If the B<plugin> argument has been
+specified, only named plugin will be flushed.
+
+=item B<error>, B<warning>, B<notice>, B<info>, B<debug>(I<message>)
+
+Log a message with the specified severity.
+
+=back
+
+=head1 EXAMPLES
+
+Any Python module will start similar to:
+
+  import collectd
+
+A very simple read function might look like:
+
+  def read(data=None):
+    vl = collectd.Values(type='gauge')
+    vl.plugin='python.spam'
+    vl.dispatch(values=[random.random() * 100])
+
+A very simple write function might look like:
+
+  def write(vl, data=None):
+    for i in vl.values:
+      print "%s (%s): %f" % (vl.plugin, vl.type, i)
+
+To register those functions with collectd:
+
+  collectd.register_read(read);
+  collectd.register_write(write);
+
+See the section L<"CLASSES"> above for a complete documentation of the data
+types used by the read, write and match functions.
+
+=head1 NOTES
+
+=over 4
+
+=item
+
+Please feel free to send in new plugins to collectd's mailinglist at
+E<lt>collectdE<nbsp>atE<nbsp>verplant.orgE<gt> for review and, possibly,
+inclusion in the main distribution. In the latter case, we will take care of
+keeping the plugin up to date and adapting it to new versions of collectd.
+
+Before submitting your plugin, please take a look at
+L<http://collectd.org/dev-info.shtml>.
+
+=back
+
+=head1 CAVEATS
+
+=over 4
+
+=item
+
+collectd is heavily multi-threaded. Each collectd thread accessing the python
+plugin will be mapped to a Python interpreter thread. Any such thread will be
+created and destroyed transparently and on-the-fly.
+
+Hence, any plugin has to be thread-safe if it provides several entry points
+from collectd (i.E<nbsp>e. if it registers more than one callback or if a
+registered callback may be called more than once in parallel).
+
+=item
+
+The Python thread module is initialized just before calling the init callbacks.
+This means you must not use Python's threading module prior to this point. This
+includes all config and possibly other callback as well.
+
+=item
+
+The python plugin exports the internal API of collectd which is considered
+unstable and subject to change at any time. We try hard to not break backwards
+compatibility in the Python API during the life cycle of one major release.
+However, this cannot be guaranteed at all times. Watch out for warnings
+dispatched by the python plugin after upgrades.
+
+=back
+
+=head1 KNOWN BUGS
+
+=over 4
+
+=item
+
+Not all aspects of the collectd API are accessible from python. This includes
+but is not limited to filters and data sets.
+
+=back
+
+=head1 SEE ALSO
+
+L<collectd(1)>,
+L<collectd.conf(5)>,
+L<collectd-perl(5)>,
+L<collectd-exec(5)>,
+L<types.db(5)>,
+L<python(1)>,
+
+=head1 AUTHOR
+
+The C<python plugin> has been written by
+Sven Trenkel E<lt>collectdE<nbsp>atE<nbsp>semidefinite.deE<gt>.
+
+This manpage has been written by Sven Trenkel
+E<lt>collectdE<nbsp>atE<nbsp>semidefinite.deE<gt>.
+It is based on the L<collectd-perl(5)> manual page by
+Florian Forster E<lt>octoE<nbsp>atE<nbsp>verplant.orgE<gt> and
+Sebastian Harl E<lt>shE<nbsp>atE<nbsp>tokkee.orgE<gt>.
+
+=cut
diff --git a/src/collectd-snmp.pod b/src/collectd-snmp.pod
new file mode 100644 (file)
index 0000000..3c6e799
--- /dev/null
@@ -0,0 +1,246 @@
+=head1 NAME
+
+collectd-snmp - Documentation of collectd's C<snmp plugin>
+
+=head1 SYNOPSIS
+
+  LoadPlugin snmp
+  # ...
+  <Plugin snmp>
+    <Data "powerplus_voltge_input">
+      Type "voltage"
+      Table false
+      Instance "input_line1"
+      Scale 0.1
+      Values "SNMPv2-SMI::enterprises.6050.5.4.1.1.2.1"
+    </Data>
+    <Data "hr_users">
+      Type "users"
+      Table false
+      Instance ""
+      Shift -1
+      Values "HOST-RESOURCES-MIB::hrSystemNumUsers.0"
+    </Data>
+    <Data "std_traffic">
+      Type "if_octets"
+      Table true
+      Instance "IF-MIB::ifDescr"
+      Values "IF-MIB::ifInOctets" "IF-MIB::ifOutOctets"
+    </Data>
+
+    <Host "some.switch.mydomain.org">
+      Address "192.168.0.2"
+      Version 1
+      Community "community_string"
+      Collect "std_traffic"
+      Interval 120
+    </Host>
+    <Host "some.server.mydomain.org">
+      Address "192.168.0.42"
+      Version 2
+      Community "another_string"
+      Collect "std_traffic" "hr_users"
+    </Host>
+    <Host "some.ups.mydomain.org">
+      Address "192.168.0.3"
+      Version 1
+      Community "more_communities"
+      Collect "powerplus_voltge_input"
+      Interval 300
+    </Host>
+  </Plugin>
+
+=head1 DESCRIPTION
+
+The C<snmp plugin> queries other hosts using SNMP, the simple network
+management protocol, and translates the value it receives to collectd's
+internal format and dispatches them. Depending on the write plugins you have
+loaded they may be written to disk or submitted to another instance or
+whatever you configured.
+
+Because querying a host via SNMP may produce a timeout multiple threads are
+used to query hosts in parallel. Depending on the number of hosts between one
+and ten threads are used.
+
+=head1 CONFIGURATION
+
+Since the aim of the C<snmp plugin> is to provide a generic interface to SNMP,
+it's configuration is not trivial and may take some time.
+
+Since the C<Net-SNMP> library is used you can use all the environment variables
+that are interpreted by that package. See L<snmpcmd(1)> for more details.
+
+There are two types of blocks that can be contained in the
+C<E<lt>PluginE<nbsp>snmpE<gt>> block: B<Data> and B<Host>:
+
+=head2 The B<Data> block
+
+The B<Data> block defines a list of values or a table of values that are to be
+queried. The following options can be set:
+
+=over 4
+
+=item B<Type> I<type>
+
+collectd's type that is to be used, e.E<nbsp>g. "if_octets" for interface
+traffic or "users" for a user count. The types are read from the B<TypesDB>
+(see L<collectd.conf(5)>), so you may want to check for which types are
+defined. See L<types.db(5)> for a description of the format of this file.
+
+=item B<Table> I<true|false>
+
+Define if this is a single list of values or a table of values. The difference
+is the following:
+
+When B<Table> is set to B<false>, the OIDs given to B<Values> (see below) are
+queried using the C<GET> SNMP command (see L<snmpget(1)>) and transmitted to
+collectd. B<One> value list is dispatched and, eventually, one file will be
+written.
+
+When B<Table> is set to B<true>, the OIDs given to B<Values> (see below) are
+queried using the C<GETNEXT> SNMP command until the subtree is left. After all
+the lists (think: all columns of the table) have been read B<several> values
+sets will be dispatches and, eventually, several files will be written. If you
+configure a B<Type> (see above) which needs more than one data source (for
+example C<if_octets> which needs C<rx> and C<tx>) you will need to specify more
+than one (two, in the example case) OIDs with the B<Values> option. This has
+nothing to do with the B<Table> setting.
+
+For example, if you want to query the number of users on a system, you can use
+C<HOST-RESOURCES-MIB::hrSystemNumUsers.0>. This is one value and belongs to one
+value list, therefore B<Table> must be set to B<false>. Please note that, in
+this case, you have to include the sequence number (zero in this case) in the
+OID.
+
+Counter example: If you want to query the interface table provided by the
+C<IF-MIB>, e.E<nbsp>g. the bytes transmitted. There are potentially many
+interfaces, so you will want to set B<Table> to B<true>. Because the
+C<if_octets> type needs two values, received and transmitted bytes, you need to
+specify two OIDs in the B<Values> setting, in this case likely
+C<IF-MIB::ifHCInOctets> and C<IF-MIB::ifHCOutOctets>. But, this is because of
+the B<Type> setting, not the B<Table> setting.
+
+Since the semantic of B<Instance> and B<Values> depends on this setting you
+need to set it before setting them. Doing vice verse will result in undefined
+behavior.
+
+=item B<Instance> I<Instance>
+
+Sets the type-instance of the values that are dispatched. The meaning of this
+setting depends on whether B<Table> is set to I<true> or I<false>:
+
+If B<Table> is set to I<true>, I<Instance> is interpreted as an SNMP-prefix
+that will return a list of values. Those values are then used as the actual
+type-instance. An example would be the C<IF-MIB::ifDescr> subtree.
+L<variables(5)> from the SNMP distribution describes the format of OIDs.
+
+If B<Table> is set to I<true> and B<Instance> is omitted, then "SUBID" will be
+used as the instance.
+
+If B<Table> is set to I<false> the actual string configured for I<Instance> is
+copied into the value-list. In this case I<Instance> may be empty, i.E<nbsp>e.
+"".
+
+=item B<InstancePrefix> I<String>
+
+If B<Table> is set to I<true>, you may feel the need to add something to the
+instance of the files. If set, I<String> is prepended to the instance as
+determined by querying the agent. When B<Table> is set to I<false> this option
+has no effect.
+
+The C<UPS-MIB> is an example where you need this setting: It has voltages of
+the inlets, outlets and the battery of an UPS. However, it doesn't provide a
+descriptive column for these voltages. In this case having 1, 2,E<nbsp>... as
+instances is not enough, because the inlet voltages and outlet voltages may
+both have the subids 1, 2,E<nbsp>... You can use this setting to distinguish
+between the different voltages.
+
+=item B<Values> I<OID> [I<OID> ...]
+
+Configures the values to be queried from the SNMP host. The meaning slightly
+changes with the B<Table> setting. L<variables(5)> from the SNMP distribution
+describes the format of OIDs.
+
+If B<Table> is set to I<true>, each I<OID> must be the prefix of all the
+values to query, e.E<nbsp>g. C<IF-MIB::ifInOctets> for all the counters of
+incoming traffic. This subtree is walked (using C<GETNEXT>) until a value from
+outside the subtree is returned.
+
+If B<Table> is set to I<false>, each I<OID> must be the OID of exactly one
+value, e.E<nbsp>g. C<IF-MIB::ifInOctets.3> for the third counter of incoming
+traffic.
+
+=item B<Scale> I<Value>
+
+The gauge-values returned by the SNMP-agent are multiplied by I<Value>.  This
+is useful when values are transfered as a fixed point real number. For example,
+thermometers may transfer B<243> but actually mean B<24.3>, so you can specify
+a scale value of B<0.1> to correct this. The default value is, of course,
+B<1.0>.
+
+This value is not applied to counter-values.
+
+=item B<Shift> I<Value>
+
+I<Value> is added to gauge-values returned by the SNMP-agent after they have
+been multiplied by any B<Scale> value. If, for example, a thermometer returns
+degrees Kelvin you could specify a shift of B<273.15> here to store values in
+degrees Celsius. The default value is, of course, B<0.0>.
+
+This value is not applied to counter-values.
+
+=back
+
+=head2 The Host block
+
+The B<Host> block defines which hosts to query, which SNMP community and
+version to use and which of the defined B<Data> to query.
+
+The argument passed to the B<Host> block is used as the hostname in the data
+stored by collectd.
+
+=over 4
+
+=item B<Address> I<IP-Address>|I<Hostname>
+
+Set the address to connect to.
+
+=item B<Version> B<1>|B<2>
+
+Set the SNMP version to use. When giving B<2> version C<2c> is actually used.
+Version 3 is not supported by this plugin.
+
+=item B<Community> I<Community>
+
+Pass I<Community> to the host.
+
+=item B<Collect> I<Data> [I<Data> ...]
+
+Defines which values to collect. I<Data> refers to one of the B<Data> block
+above. Since the config file is read top-down you need to define the data
+before using it here.
+
+=item B<Interval> I<Seconds>
+
+Collect data from this host every I<Seconds> seconds. This option is meant for
+devices with not much CPU power, e.E<nbsp>g. network equipment such as
+switches, embedded devices, rack monitoring systems and so on. Since the
+B<Step> of generated RRD files depends on this setting it's wise to select a
+reasonable value once and never change it.
+
+=back
+
+=head1 SEE ALSO
+
+L<collectd(1)>,
+L<collectd.conf(5)>,
+L<snmpget(1)>,
+L<snmpgetnext(1)>,
+L<variables(5)>,
+L<unix(7)>
+
+=head1 AUTHOR
+
+Florian Forster E<lt>octo@verplant.orgE<gt>
+
+=cut
diff --git a/src/collectd-threshold.pod b/src/collectd-threshold.pod
new file mode 100644 (file)
index 0000000..02e41b8
--- /dev/null
@@ -0,0 +1,201 @@
+=head1 NAME
+
+collectd-threshold - Documentation of collectd's I<Threshold plugin>
+
+=head1 SYNOPSIS
+
+ LoadPlugin "threshold"
+ <Plugin "threshold">
+   <Type "foo">
+     WarningMin    0.00
+     WarningMax 1000.00
+     FailureMin    0.00
+     FailureMax 1200.00
+     Invert false
+     Instance "bar"
+   </Type>
+ </Plugin>
+
+=head1 DESCRIPTION
+
+Starting with version C<4.3.0> I<collectd> has support for B<monitoring>. By
+that we mean that the values are not only stored or sent somewhere, but that
+they are judged and, if a problem is recognized, acted upon. The only action
+the I<Threshold plugin> takes itself is to generate and dispatch a
+I<notification>. Other plugins can register to receive notifications and
+perform appropriate further actions.
+
+Since systems and what you expect them to do differ a lot, you can configure
+I<thresholds> for your values freely. This gives you a lot of flexibility but
+also a lot of responsibility.
+
+Every time a value is out of range, a notification is dispatched. This means
+that the idle percentage of your CPU needs to be less then the configured
+threshold only once for a notification to be generated. There's no such thing
+as a moving average or similar - at least not now.
+
+Also, all values that match a threshold are considered to be relevant or
+"interesting". As a consequence collectd will issue a notification if they are
+not received for B<Timeout> iterations. The B<Timeout> configuration option is
+explained in section L<collectd.conf(5)/"GLOBAL OPTIONS">. If, for example,
+B<Timeout> is set to "2" (the default) and some hosts sends it's CPU statistics
+to the server every 60 seconds, a notification will be dispatched after about
+120 seconds. It may take a little longer because the timeout is checked only
+once each B<Interval> on the server.
+
+When a value comes within range again or is received after it was missing, an
+"OKAY-notification" is dispatched.
+
+=head1 CONFIGURATION
+
+Here is a configuration example to get you started. Read below for more
+information.
+
+ LoadPlugin "threshold"
+ <Plugin "threshold">
+   <Type "foo">
+     WarningMin    0.00
+     WarningMax 1000.00
+     FailureMin    0.00
+     FailureMax 1200.00
+     Invert false
+     Instance "bar"
+   </Type>
+   
+   <Plugin "interface">
+     Instance "eth0"
+     <Type "if_octets">
+       FailureMax 10000000
+       DataSource "rx"
+     </Type>
+   </Plugin>
+   
+   <Host "hostname">
+     <Type "cpu">
+       Instance "idle"
+       FailureMin 10
+     </Type>
+   
+     <Plugin "memory">
+       <Type "memory">
+         Instance "cached"
+         WarningMin 100000000
+       </Type>
+     </Plugin>
+   
+     <Type "load">
+       DataSource "midterm"
+       FailureMax 4
+       Hits 3
+       Hysteresis 3
+     </Type>
+   </Host>
+ </Plugin>
+
+There are basically two types of configuration statements: The C<Host>,
+C<Plugin>, and C<Type> blocks select the value for which a threshold should be
+configured. The C<Plugin> and C<Type> blocks may be specified further using the
+C<Instance> option. You can combine the block by nesting the blocks, though
+they must be nested in the above order, i.e. C<Host> may contain either
+C<Plugin> and C<Type> blocks, C<Plugin> may only contain C<Type> blocks and
+C<Type> may not contain other blocks. If multiple blocks apply to the same
+value the most specific block is used.
+
+The other statements specify the threshold to configure. They B<must> be
+included in a C<Type> block. Currently the following statements are recognized:
+
+=over 4
+
+=item B<FailureMax> I<Value>
+
+=item B<WarningMax> I<Value>
+
+Sets the upper bound of acceptable values. If unset defaults to positive
+infinity. If a value is greater than B<FailureMax> a B<FAILURE> notification
+will be created. If the value is greater than B<WarningMax> but less than (or
+equal to) B<FailureMax> a B<WARNING> notification will be created.
+
+=item B<FailureMin> I<Value>
+
+=item B<WarningMin> I<Value>
+
+Sets the lower bound of acceptable values. If unset defaults to negative
+infinity. If a value is less than B<FailureMin> a B<FAILURE> notification will
+be created. If the value is less than B<WarningMin> but greater than (or equal
+to) B<FailureMin> a B<WARNING> notification will be created.
+
+=item B<DataSource> I<DSName>
+
+Some data sets have more than one "data source". Interesting examples are the
+C<if_octets> data set, which has received (C<rx>) and sent (C<tx>) bytes and
+the C<disk_ops> data set, which holds C<read> and C<write> operations. The
+system load data set, C<load>, even has three data sources: C<shortterm>,
+C<midterm>, and C<longterm>.
+
+Normally, all data sources are checked against a configured threshold. If this
+is undesirable, or if you want to specify different limits for each data
+source, you can use the B<DataSource> option to have a threshold apply only to
+one data source.
+
+=item B<Invert> B<true>|B<false>
+
+If set to B<true> the range of acceptable values is inverted, i.e. values
+between B<FailureMin> and B<FailureMax> (B<WarningMin> and B<WarningMax>) are
+not okay. Defaults to B<false>.
+
+=item B<Persist> B<true>|B<false>
+
+Sets how often notifications are generated. If set to B<true> one notification
+will be generated for each value that is out of the acceptable range. If set to
+B<false> (the default) then a notification is only generated if a value is out
+of range but the previous value was okay.
+
+This applies to missing values, too: If set to B<true> a notification about a
+missing value is generated once every B<Interval> seconds. If set to B<false>
+only one such notification is generated until the value appears again.
+
+=item B<PersistOK> B<true>|B<false>
+
+Sets how OKAY notifications act. If set to B<true> one notification will be
+generated for each value that is in the acceptable range. If set to B<false>
+(the default) then a notification is only generated if a value is in range but
+the previous value was not.
+
+=item B<Percentage> B<true>|B<false>
+
+If set to B<true>, the minimum and maximum values given are interpreted as
+percentage value, relative to the other data sources. This is helpful for
+example for the "df" type, where you may want to issue a warning when less than
+5E<nbsp>% of the total space is available. Defaults to B<false>.
+
+=item B<Hits> I<Value>
+
+Sets the number of occurrences which the threshold must be raised before to
+dispatch any notification or, in other words, the number of B<Interval>s
+that the threshold must be match before dispatch any notification.
+
+=item B<Hysteresis> I<Value>
+
+Sets the hysteresis value for threshold. The hysteresis is a method to prevent
+flapping between states, until a new received value for a previously matched
+threshold down below the threshold condition (B<WarningMax>, B<FailureMin> or
+everything else) minus the hysteresis value, the failure (respectively warning)
+state will be keep.
+
+=item B<Interesting> B<true>|B<false>
+
+If set to B<true> (the default), the threshold must be treated as interesting
+and, when a number of B<Timeout> values will lost, then a missing notification
+will be dispatched. On the other hand, if set to B<false>, the missing
+notification will never dispatched for this threshold.
+
+=back
+
+=head1 SEE ALSO
+
+L<collectd(1)>,
+L<collectd.conf(5)>
+
+=head1 AUTHOR
+
+Florian Forster E<lt>octoE<nbsp>atE<nbsp>collectd.orgE<gt>
diff --git a/src/collectd-unixsock.pod b/src/collectd-unixsock.pod
new file mode 100644 (file)
index 0000000..83802a1
--- /dev/null
@@ -0,0 +1,245 @@
+=head1 NAME
+
+collectd-unixsock - Documentation of collectd's C<unixsock plugin>
+
+=head1 SYNOPSIS
+
+  # See collectd.conf(5)
+  LoadPlugin unixsock
+  # ...
+  <Plugin unixsock>
+    SocketFile "/path/to/socket"
+    SocketGroup "collectd"
+    SocketPerms "0770"
+  </Plugin>
+
+=head1 DESCRIPTION
+
+The C<unixsock plugin> opens an UNIX-socket over which one can interact with
+the daemon. This can be used to use the values collected by collectd in other
+applications, such as monitoring solutions, or submit externally collected
+values to collectd.
+
+For example, this plugin is used by L<collectd-nagios(1)> to check if some
+value is in a certain range and exit with a Nagios-compatible exit code.
+
+=head1 COMMANDS
+
+Upon start the C<unixsock plugin> opens a UNIX-socket and waits for
+connections. Once a connection is established the client can send commands to
+the daemon which it will answer, if it understand them.
+
+In general the plugin answers with a status line of the following form:
+
+I<Status> I<Message>
+
+If I<Status> is greater than or equal to zero the message indicates success,
+if I<Status> is less than zero the message indicates failure. I<Message> is a
+human-readable string that further describes the return value.
+
+On success, I<Status> furthermore indicates the number of subsequent lines of
+output (not including the status line). Each such lines usually contains a
+single return value. See the description of each command for details.
+
+The following commands are implemented:
+
+=over 4
+
+=item B<GETVAL> I<Identifier>
+
+If the value identified by I<Identifier> (see below) is found the complete
+value-list is returned. The response is a list of name-value-pairs, each pair
+on its own line (the number of lines is indicated by the status line - see
+above). Each name-value-pair is of the form I<name>B<=>I<value>.
+Counter-values are converted to a rate, e.E<nbsp>g. bytes per second.
+Undefined values are returned as B<NaN>.
+
+Example:
+  -> | GETVAL myhost/cpu-0/cpu-user
+  <- | 1 Value found
+  <- | value=1.260000e+00
+
+=item B<LISTVAL>
+
+Returns a list of the values available in the value cache together with the
+time of the last update, so that querying applications can issue a B<GETVAL>
+command for the values that have changed. Each return value consists of the
+update time as an epoch value and the identifier, separated by a space. The
+update time is the time of the last value, as provided by the collecting
+instance and may be very different from the time the server considers to be
+"now".
+
+Example:
+  -> | LISTVAL
+  <- | 69 Values found
+  <- | 1182204284 myhost/cpu-0/cpu-idle
+  <- | 1182204284 myhost/cpu-0/cpu-nice
+  <- | 1182204284 myhost/cpu-0/cpu-system
+  <- | 1182204284 myhost/cpu-0/cpu-user
+  ...
+
+=item B<PUTVAL> I<Identifier> [I<OptionList>] I<Valuelist>
+
+Submits one or more values (identified by I<Identifier>, see below) to the
+daemon which will dispatch it to all it's write-plugins.
+
+An I<Identifier> is of the form
+C<I<host>B</>I<plugin>B<->I<instance>B</>I<type>B<->I<instance>> with both
+I<instance>-parts being optional. If they're omitted the hyphen must be
+omitted, too. I<plugin> and each I<instance>-part may be chosen freely as long
+as the tuple (plugin, plugin instance, type instance) uniquely identifies the
+plugin within collectd. I<type> identifies the type and number of values
+(i.E<nbsp>e. data-set) passed to collectd. A large list of predefined
+data-sets is available in the B<types.db> file.
+
+The I<OptionList> is an optional list of I<Options>, where each option is a
+key-value-pair. A list of currently understood options can be found below, all
+other options will be ignored. Values that contain spaces must be quoted with
+double quotes.
+
+I<Valuelist> is a colon-separated list of the time and the values, each either
+an integer if the data-source is a counter, or a double if the data-source is
+of type "gauge". You can submit an undefined gauge-value by using B<U>. When
+submitting B<U> to a counter the behavior is undefined. The time is given as
+epoch (i.E<nbsp>e. standard UNIX time).
+
+You can mix options and values, but the order is important: Options only
+effect following values, so specifying an option as last field is allowed, but
+useless. Also, an option applies to B<all> following values, so you don't need
+to re-set an option over and over again.
+
+The currently defined B<Options> are:
+
+=over 4
+
+=item B<interval=>I<seconds>
+
+Gives the interval in which the data identified by I<Identifier> is being
+collected.
+
+=back
+
+Please note that this is the same format as used in the B<exec plugin>, see
+L<collectd-exec(5)>.
+
+Example:
+  -> | PUTVAL testhost/interface/if_octets-test0 interval=10 1179574444:123:456
+  <- | 0 Success
+
+=item B<PUTNOTIF> [I<OptionList>] B<message=>I<Message>
+
+Submits a notification to the daemon which will then dispatch it to all plugins
+which have registered for receiving notifications. 
+
+The B<PUTNOTIF> command is followed by a list of options which further describe
+the notification. The B<message> option is special in that it will consume the
+rest of the line as its value. The B<message>, B<severity>, and B<time> options
+are mandatory.
+
+Valid options are:
+
+=over 4
+
+=item B<message=>I<Message> (B<REQUIRED>)
+
+Sets the message of the notification. This is the message that will be made
+accessible to the user, so it should contain some useful information. As with
+all options: If the message includes spaces, it must be quoted with double
+quotes. This option is mandatory.
+
+=item B<severity=failure>|B<warning>|B<okay> (B<REQUIRED>)
+
+Sets the severity of the notification. This option is mandatory.
+
+=item B<time=>I<Time> (B<REQUIRED>)
+
+Sets the time of the notification. The time is given as "epoch", i.E<nbsp>e. as
+seconds since January 1st, 1970, 00:00:00. This option is mandatory.
+
+=item B<host=>I<Hostname>
+
+=item B<plugin=>I<Plugin>
+
+=item B<plugin_instance=>I<Plugin-Instance>
+
+=item B<type=>I<Type>
+
+=item B<type_instance=>I<Type-Instance>
+
+These "associative" options establish a relation between this notification and
+collected performance data. This connection is purely informal, i.E<nbsp>e. the
+daemon itself doesn't do anything with this information. However, websites or
+GUIs may use this information to place notifications near the affected graph or
+table. All the options are optional, but B<plugin_instance> without B<plugin>
+or B<type_instance> without B<type> doesn't make much sense and should be
+avoided.
+
+Please note that this is the same format as used in the B<exec plugin>, see
+L<collectd-exec(5)>.
+
+=back
+
+Example:
+  -> | PUTNOTIF type=temperature severity=warning time=1201094702 message=The roof is on fire!
+  <- | 0 Success
+
+=item B<FLUSH> [B<timeout=>I<Timeout>] [B<plugin=>I<Plugin> [...]] [B<identifier=>I<Ident> [...]]
+
+Flushes all cached data older than I<Timeout> seconds. If no timeout has been
+specified, it defaults to -1 which causes all data to be flushed.
+
+If the B<plugin> option has been specified, only the I<Plugin> plugin will be
+flushed. You can have multiple B<plugin> options to flush multiple plugins in
+one go. If the B<plugin> option is not given all plugins providing a flush
+callback will be flushed.
+
+If the B<identifier> option is given only the specified values will be flushed.
+This is meant to be used by graphing or displaying frontends which want to have
+the latest values for a specific graph. Again, you can specify the
+B<identifier> option multiple times to flush several values. If this option is
+not specified at all, all values will be flushed.
+
+Example:
+  -> | FLUSH plugin=rrdtool identifier=localhost/df/df-root identifier=localhost/df/df-var
+  <- | 0 Done: 2 successful, 0 errors
+
+=back
+
+=head2 Identifiers
+
+Value or value-lists are identified in a uniform fashion:
+
+I<Hostname>/I<Plugin>/I<Type>
+
+Where I<Plugin> and I<Type> are both either of type "I<Name>" or
+"I<Name>-I<Instance>". If the identifier includes spaces, it must be quoted
+using double quotes. This sounds more complicated than it is, so here are
+some examples:
+
+  myhost/cpu-0/cpu-user
+  myhost/load/load
+  myhost/memory/memory-used
+  myhost/disk-sda/disk_octets
+  "myups/snmp/temperature-Outlet 1"
+
+=head1 ABSTRACTION LAYER
+
+B<collectd> ships the Perl-Module L<Collectd::Unixsock> which
+provides an abstraction layer over the actual socket connection. It can be
+found in the directory F<bindings/perl/> in the source distribution or
+(usually) somewhere near F</usr/share/perl5/> if you're using a package. If
+you want to use Perl to communicate with the daemon, you're encouraged to use
+and expand this module.
+
+=head1 SEE ALSO
+
+L<collectd(1)>,
+L<collectd.conf(5)>,
+L<collectd-nagios(1)>,
+L<unix(7)>
+
+=head1 AUTHOR
+
+Florian Forster E<lt>octo@verplant.orgE<gt>
+
+=cut
diff --git a/src/collectd.c b/src/collectd.c
new file mode 100644 (file)
index 0000000..d33d1d6
--- /dev/null
@@ -0,0 +1,628 @@
+/**
+ * collectd - src/collectd.c
+ * Copyright (C) 2005-2007  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ *   Alvaro Barcellos <alvaro.barcellos at gmail.com>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+#include <pthread.h>
+
+#include "plugin.h"
+#include "configfile.h"
+
+#if HAVE_STATGRAB_H
+# include <statgrab.h>
+#endif
+
+/*
+ * Global variables
+ */
+char hostname_g[DATA_MAX_NAME_LEN];
+cdtime_t interval_g;
+int  timeout_g;
+#if HAVE_LIBKSTAT
+kstat_ctl_t *kc;
+#endif /* HAVE_LIBKSTAT */
+
+static int loop = 0;
+
+static void *do_flush (void __attribute__((unused)) *arg)
+{
+       INFO ("Flushing all data.");
+       plugin_flush (/* plugin = */ NULL,
+                       /* timeout = */ 0,
+                       /* ident = */ NULL);
+       INFO ("Finished flushing all data.");
+       pthread_exit (NULL);
+       return NULL;
+}
+
+static void sig_int_handler (int __attribute__((unused)) signal)
+{
+       loop++;
+}
+
+static void sig_term_handler (int __attribute__((unused)) signal)
+{
+       loop++;
+}
+
+static void sig_usr1_handler (int __attribute__((unused)) signal)
+{
+       pthread_t      thread;
+       pthread_attr_t attr;
+
+       /* flushing the data might take a while,
+        * so it should be done asynchronously */
+       pthread_attr_init (&attr);
+       pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
+       pthread_create (&thread, &attr, do_flush, NULL);
+}
+
+static int init_hostname (void)
+{
+       const char *str;
+
+       struct addrinfo  ai_hints;
+       struct addrinfo *ai_list;
+       struct addrinfo *ai_ptr;
+       int status;
+
+       str = global_option_get ("Hostname");
+       if (str != NULL)
+       {
+               sstrncpy (hostname_g, str, sizeof (hostname_g));
+               return (0);
+       }
+
+       if (gethostname (hostname_g, sizeof (hostname_g)) != 0)
+       {
+               fprintf (stderr, "`gethostname' failed and no "
+                               "hostname was configured.\n");
+               return (-1);
+       }
+
+       str = global_option_get ("FQDNLookup");
+       if (IS_FALSE (str))
+               return (0);
+
+       memset (&ai_hints, '\0', sizeof (ai_hints));
+       ai_hints.ai_flags = AI_CANONNAME;
+
+       status = getaddrinfo (hostname_g, NULL, &ai_hints, &ai_list);
+       if (status != 0)
+       {
+               ERROR ("Looking up \"%s\" failed. You have set the "
+                               "\"FQDNLookup\" option, but I cannot resolve "
+                               "my hostname to a fully qualified domain "
+                               "name. Please fix you network "
+                               "configuration.", hostname_g);
+               return (-1);
+       }
+
+       for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
+       {
+               if (ai_ptr->ai_canonname == NULL)
+                       continue;
+
+               sstrncpy (hostname_g, ai_ptr->ai_canonname, sizeof (hostname_g));
+               break;
+       }
+
+       freeaddrinfo (ai_list);
+       return (0);
+} /* int init_hostname */
+
+static int init_global_variables (void)
+{
+       const char *str;
+
+       str = global_option_get ("Interval");
+       if (str == NULL)
+       {
+               interval_g = TIME_T_TO_CDTIME_T (10);
+       }
+       else
+       {
+               double tmp;
+
+               tmp = atof (str);
+               if (tmp <= 0.0)
+               {
+                       fprintf (stderr, "Cannot set the interval to a "
+                                       "correct value.\n"
+                                       "Please check your settings.\n");
+                       return (-1);
+               }
+
+               interval_g = DOUBLE_TO_CDTIME_T (tmp);
+       }
+       DEBUG ("interval_g = %.3f;", CDTIME_T_TO_DOUBLE (interval_g));
+
+       str = global_option_get ("Timeout");
+       if (str == NULL)
+               str = "2";
+       timeout_g = atoi (str);
+       if (timeout_g <= 1)
+       {
+               fprintf (stderr, "Cannot set the timeout to a correct value.\n"
+                               "Please check your settings.\n");
+               return (-1);
+       }
+       DEBUG ("timeout_g = %i;", timeout_g);
+
+       if (init_hostname () != 0)
+               return (-1);
+       DEBUG ("hostname_g = %s;", hostname_g);
+
+       return (0);
+} /* int init_global_variables */
+
+static int change_basedir (const char *orig_dir)
+{
+       char *dir = strdup (orig_dir);
+       int dirlen;
+       int status;
+
+       if (dir == NULL)
+       {
+               char errbuf[1024];
+               ERROR ("strdup failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+       
+       dirlen = strlen (dir);
+       while ((dirlen > 0) && (dir[dirlen - 1] == '/'))
+               dir[--dirlen] = '\0';
+
+       if (dirlen <= 0)
+               return (-1);
+
+       status = chdir (dir);
+       free (dir);
+
+       if (status != 0)
+       {
+               if (errno == ENOENT)
+               {
+                       if (mkdir (orig_dir, 0755) == -1)
+                       {
+                               char errbuf[1024];
+                               ERROR ("change_basedir: mkdir (%s): %s", orig_dir,
+                                               sstrerror (errno, errbuf,
+                                                       sizeof (errbuf)));
+                               return (-1);
+                       }
+                       else if (chdir (orig_dir) == -1)
+                       {
+                               char errbuf[1024];
+                               ERROR ("chdir (%s): %s", orig_dir,
+                                               sstrerror (errno, errbuf,
+                                                       sizeof (errbuf)));
+                               return (-1);
+                       }
+               }
+               else
+               {
+                       char errbuf[1024];
+                       ERROR ("chdir (%s): %s", orig_dir,
+                                       sstrerror (errno, errbuf,
+                                               sizeof (errbuf)));
+                       return (-1);
+               }
+       }
+
+       return (0);
+} /* static int change_basedir (char *dir) */
+
+#if HAVE_LIBKSTAT
+static void update_kstat (void)
+{
+       if (kc == NULL)
+       {
+               if ((kc = kstat_open ()) == NULL)
+                       ERROR ("Unable to open kstat control structure");
+       }
+       else
+       {
+               kid_t kid;
+               kid = kstat_chain_update (kc);
+               if (kid > 0)
+               {
+                       INFO ("kstat chain has been updated");
+                       plugin_init_all ();
+               }
+               else if (kid < 0)
+                       ERROR ("kstat chain update failed");
+               /* else: everything works as expected */
+       }
+
+       return;
+} /* static void update_kstat (void) */
+#endif /* HAVE_LIBKSTAT */
+
+/* TODO
+ * Remove all settings but `-f' and `-C'
+ */
+static void exit_usage (int status)
+{
+       printf ("Usage: "PACKAGE" [OPTIONS]\n\n"
+                       
+                       "Available options:\n"
+                       "  General:\n"
+                       "    -C <file>       Configuration file.\n"
+                       "                    Default: "CONFIGFILE"\n"
+                       "    -t              Test config and exit.\n"
+                       "    -T              Test plugin read and exit.\n"
+                       "    -P <file>       PID-file.\n"
+                       "                    Default: "PIDFILE"\n"
+#if COLLECT_DAEMON
+                       "    -f              Don't fork to the background.\n"
+#endif
+                       "    -h              Display help (this message)\n"
+                       "\nBuiltin defaults:\n"
+                       "  Config file       "CONFIGFILE"\n"
+                       "  PID file          "PIDFILE"\n"
+                       "  Plugin directory  "PLUGINDIR"\n"
+                       "  Data directory    "PKGLOCALSTATEDIR"\n"
+                       "\n"PACKAGE" "VERSION", http://collectd.org/\n"
+                       "by Florian octo Forster <octo@verplant.org>\n"
+                       "for contributions see `AUTHORS'\n");
+       exit (status);
+} /* static void exit_usage (int status) */
+
+static int do_init (void)
+{
+#if HAVE_LIBKSTAT
+       kc = NULL;
+       update_kstat ();
+#endif
+
+#if HAVE_LIBSTATGRAB
+       if (sg_init ())
+       {
+               ERROR ("sg_init: %s", sg_str_error (sg_get_error ()));
+               return (-1);
+       }
+
+       if (sg_drop_privileges ())
+       {
+               ERROR ("sg_drop_privileges: %s", sg_str_error (sg_get_error ()));
+               return (-1);
+       }
+#endif
+
+       plugin_init_all ();
+
+       return (0);
+} /* int do_init () */
+
+
+static int do_loop (void)
+{
+       cdtime_t wait_until;
+
+       wait_until = cdtime () + interval_g;
+
+       while (loop == 0)
+       {
+               struct timespec ts_wait = { 0, 0 };
+               cdtime_t now;
+
+#if HAVE_LIBKSTAT
+               update_kstat ();
+#endif
+
+               /* Issue all plugins */
+               plugin_read_all ();
+
+               now = cdtime ();
+               if (now >= wait_until)
+               {
+                       WARNING ("Not sleeping because the next interval is "
+                                       "%.3f seconds in the past!",
+                                       CDTIME_T_TO_DOUBLE (now - wait_until));
+                       wait_until = now + interval_g;
+                       continue;
+               }
+
+               CDTIME_T_TO_TIMESPEC (wait_until - now, &ts_wait);
+               wait_until = wait_until + interval_g;
+
+               while ((loop == 0) && (nanosleep (&ts_wait, &ts_wait) != 0))
+               {
+                       if (errno != EINTR)
+                       {
+                               char errbuf[1024];
+                               ERROR ("nanosleep failed: %s",
+                                               sstrerror (errno, errbuf,
+                                                       sizeof (errbuf)));
+                               return (-1);
+                       }
+               }
+       } /* while (loop == 0) */
+
+       return (0);
+} /* int do_loop */
+
+static int do_shutdown (void)
+{
+       plugin_shutdown_all ();
+       return (0);
+} /* int do_shutdown */
+
+#if COLLECT_DAEMON
+static int pidfile_create (void)
+{
+       FILE *fh;
+       const char *file = global_option_get ("PIDFile");
+
+       if ((fh = fopen (file, "w")) == NULL)
+       {
+               char errbuf[1024];
+               ERROR ("fopen (%s): %s", file,
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (1);
+       }
+
+       fprintf (fh, "%i\n", (int) getpid ());
+       fclose(fh);
+
+       return (0);
+} /* static int pidfile_create (const char *file) */
+
+static int pidfile_remove (void)
+{
+       const char *file = global_option_get ("PIDFile");
+
+       DEBUG ("unlink (%s)", (file != NULL) ? file : "<null>");
+       return (unlink (file));
+} /* static int pidfile_remove (const char *file) */
+#endif /* COLLECT_DAEMON */
+
+int main (int argc, char **argv)
+{
+       struct sigaction sig_int_action;
+       struct sigaction sig_term_action;
+       struct sigaction sig_usr1_action;
+       struct sigaction sig_pipe_action;
+       char *configfile = CONFIGFILE;
+       int test_config  = 0;
+       int test_readall = 0;
+       const char *basedir;
+#if COLLECT_DAEMON
+       struct sigaction sig_chld_action;
+       pid_t pid;
+       int daemonize    = 1;
+#endif
+       int exit_status = 0;
+
+       /* read options */
+       while (1)
+       {
+               int c;
+
+               c = getopt (argc, argv, "htTC:"
+#if COLLECT_DAEMON
+                               "fP:"
+#endif
+               );
+
+               if (c == -1)
+                       break;
+
+               switch (c)
+               {
+                       case 'C':
+                               configfile = optarg;
+                               break;
+                       case 't':
+                               test_config = 1;
+                               break;
+                       case 'T':
+                               test_readall = 1;
+                               global_option_set ("ReadThreads", "-1");
+#if COLLECT_DAEMON
+                               daemonize = 0;
+#endif /* COLLECT_DAEMON */
+                               break;
+#if COLLECT_DAEMON
+                       case 'P':
+                               global_option_set ("PIDFile", optarg);
+                               break;
+                       case 'f':
+                               daemonize = 0;
+                               break;
+#endif /* COLLECT_DAEMON */
+                       case 'h':
+                               exit_usage (0);
+                               break;
+                       default:
+                               exit_usage (1);
+               } /* switch (c) */
+       } /* while (1) */
+
+       if (optind < argc)
+               exit_usage (1);
+
+       /*
+        * Read options from the config file, the environment and the command
+        * line (in that order, with later options overwriting previous ones in
+        * general).
+        * Also, this will automatically load modules.
+        */
+       if (cf_read (configfile))
+       {
+               fprintf (stderr, "Error: Reading the config file failed!\n"
+                               "Read the syslog for details.\n");
+               return (1);
+       }
+
+       /*
+        * Change directory. We do this _after_ reading the config and loading
+        * modules to relative paths work as expected.
+        */
+       if ((basedir = global_option_get ("BaseDir")) == NULL)
+       {
+               fprintf (stderr, "Don't have a basedir to use. This should not happen. Ever.");
+               return (1);
+       }
+       else if (change_basedir (basedir))
+       {
+               fprintf (stderr, "Error: Unable to change to directory `%s'.\n", basedir);
+               return (1);
+       }
+
+       /*
+        * Set global variables or, if that failes, exit. We cannot run with
+        * them being uninitialized. If nothing is configured, then defaults
+        * are being used. So this means that the user has actually done
+        * something wrong.
+        */
+       if (init_global_variables () != 0)
+               return (1);
+
+       if (test_config)
+               return (0);
+
+#if COLLECT_DAEMON
+       /*
+        * fork off child
+        */
+       memset (&sig_chld_action, '\0', sizeof (sig_chld_action));
+       sig_chld_action.sa_handler = SIG_IGN;
+       sigaction (SIGCHLD, &sig_chld_action, NULL);
+
+       if (daemonize)
+       {
+               if ((pid = fork ()) == -1)
+               {
+                       /* error */
+                       char errbuf[1024];
+                       fprintf (stderr, "fork: %s",
+                                       sstrerror (errno, errbuf,
+                                               sizeof (errbuf)));
+                       return (1);
+               }
+               else if (pid != 0)
+               {
+                       /* parent */
+                       /* printf ("Running (PID %i)\n", pid); */
+                       return (0);
+               }
+
+               /* Detach from session */
+               setsid ();
+
+               /* Write pidfile */
+               if (pidfile_create ())
+                       exit (2);
+
+               /* close standard descriptors */
+               close (2);
+               close (1);
+               close (0);
+
+               if (open ("/dev/null", O_RDWR) != 0)
+               {
+                       ERROR ("Error: Could not connect `STDIN' to `/dev/null'");
+                       return (1);
+               }
+               if (dup (0) != 1)
+               {
+                       ERROR ("Error: Could not connect `STDOUT' to `/dev/null'");
+                       return (1);
+               }
+               if (dup (0) != 2)
+               {
+                       ERROR ("Error: Could not connect `STDERR' to `/dev/null'");
+                       return (1);
+               }
+       } /* if (daemonize) */
+#endif /* COLLECT_DAEMON */
+
+       memset (&sig_pipe_action, '\0', sizeof (sig_pipe_action));
+       sig_pipe_action.sa_handler = SIG_IGN;
+       sigaction (SIGPIPE, &sig_pipe_action, NULL);
+
+       /*
+        * install signal handlers
+        */
+       memset (&sig_int_action, '\0', sizeof (sig_int_action));
+       sig_int_action.sa_handler = sig_int_handler;
+       if (0 != sigaction (SIGINT, &sig_int_action, NULL)) {
+               char errbuf[1024];
+               ERROR ("Error: Failed to install a signal handler for signal INT: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (1);
+       }
+
+       memset (&sig_term_action, '\0', sizeof (sig_term_action));
+       sig_term_action.sa_handler = sig_term_handler;
+       if (0 != sigaction (SIGTERM, &sig_term_action, NULL)) {
+               char errbuf[1024];
+               ERROR ("Error: Failed to install a signal handler for signal TERM: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (1);
+       }
+
+       memset (&sig_usr1_action, '\0', sizeof (sig_usr1_action));
+       sig_usr1_action.sa_handler = sig_usr1_handler;
+       if (0 != sigaction (SIGUSR1, &sig_usr1_action, NULL)) {
+               char errbuf[1024];
+               ERROR ("Error: Failed to install a signal handler for signal USR1: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (1);
+       }
+
+       /*
+        * run the actual loops
+        */
+       do_init ();
+
+       if (test_readall)
+       {
+               if (plugin_read_all_once () != 0)
+                       exit_status = 1;
+       }
+       else
+       {
+               INFO ("Initialization complete, entering read-loop.");
+               do_loop ();
+       }
+
+       /* close syslog */
+       INFO ("Exiting normally.");
+
+       do_shutdown ();
+
+#if COLLECT_DAEMON
+       if (daemonize)
+               pidfile_remove ();
+#endif /* COLLECT_DAEMON */
+
+       return (exit_status);
+} /* int main */
diff --git a/src/collectd.conf.in b/src/collectd.conf.in
new file mode 100644 (file)
index 0000000..c731810
--- /dev/null
@@ -0,0 +1,1062 @@
+#
+# Config file for collectd(1).
+# Please read collectd.conf(5) for a list of options.
+# http://collectd.org/
+#
+
+##############################################################################
+# Global                                                                     #
+#----------------------------------------------------------------------------#
+# Global settings for the daemon.                                            #
+##############################################################################
+
+#Hostname    "localhost"
+#FQDNLookup   true
+#BaseDir     "@prefix@/var/lib/@PACKAGE_NAME@"
+#PIDFile     "@prefix@/var/run/@PACKAGE_NAME@.pid"
+#PluginDir   "@prefix@/lib/@PACKAGE_NAME@"
+#TypesDB     "@prefix@/share/@PACKAGE_NAME@/types.db"
+#Interval     10
+#Timeout      2
+#ReadThreads  5
+
+##############################################################################
+# Logging                                                                    #
+#----------------------------------------------------------------------------#
+# Plugins which provide logging functions should be loaded first, so log     #
+# messages generated when loading or configuring other plugins can be        #
+# accessed.                                                                  #
+##############################################################################
+
+@LOAD_PLUGIN_SYSLOG@LoadPlugin syslog
+@LOAD_PLUGIN_LOGFILE@LoadPlugin logfile
+
+#<Plugin logfile>
+#      LogLevel @DEFAULT_LOG_LEVEL@
+#      File STDOUT
+#      Timestamp true
+#      PrintSeverity false
+#</Plugin>
+
+#<Plugin syslog>
+#      LogLevel @DEFAULT_LOG_LEVEL@
+#</Plugin>
+
+##############################################################################
+# LoadPlugin section                                                         #
+#----------------------------------------------------------------------------#
+# Lines beginning with a single `#' belong to plugins which have been built  #
+# but are disabled by default.                                               #
+#                                                                            #
+# Lines begnning with `##' belong to plugins which have not been built due   #
+# to missing dependencies or because they have been deactivated explicitly.  #
+##############################################################################
+
+#@BUILD_PLUGIN_AMQP_TRUE@LoadPlugin amqp
+#@BUILD_PLUGIN_APACHE_TRUE@LoadPlugin apache
+#@BUILD_PLUGIN_APCUPS_TRUE@LoadPlugin apcups
+#@BUILD_PLUGIN_APPLE_SENSORS_TRUE@LoadPlugin apple_sensors
+#@BUILD_PLUGIN_ASCENT_TRUE@LoadPlugin ascent
+#@BUILD_PLUGIN_BATTERY_TRUE@LoadPlugin battery
+#@BUILD_PLUGIN_BIND_TRUE@LoadPlugin bind
+#@BUILD_PLUGIN_CONNTRACK_TRUE@LoadPlugin conntrack
+#@BUILD_PLUGIN_CONTEXTSWITCH_TRUE@LoadPlugin contextswitch
+@BUILD_PLUGIN_CPU_TRUE@@BUILD_PLUGIN_CPU_TRUE@LoadPlugin cpu
+#@BUILD_PLUGIN_CPUFREQ_TRUE@LoadPlugin cpufreq
+@LOAD_PLUGIN_CSV@LoadPlugin csv
+#@BUILD_PLUGIN_CURL_TRUE@LoadPlugin curl
+#@BUILD_PLUGIN_CURL_JSON_TRUE@LoadPlugin curl_json
+#@BUILD_PLUGIN_CURL_XML_TRUE@LoadPlugin curl_xml
+#@BUILD_PLUGIN_DBI_TRUE@LoadPlugin dbi
+#@BUILD_PLUGIN_DF_TRUE@LoadPlugin df
+#@BUILD_PLUGIN_DISK_TRUE@LoadPlugin disk
+#@BUILD_PLUGIN_DNS_TRUE@LoadPlugin dns
+#@BUILD_PLUGIN_EMAIL_TRUE@LoadPlugin email
+#@BUILD_PLUGIN_ENTROPY_TRUE@LoadPlugin entropy
+#@BUILD_PLUGIN_EXEC_TRUE@LoadPlugin exec
+#@BUILD_PLUGIN_FILECOUNT_TRUE@LoadPlugin filecount
+#@BUILD_PLUGIN_FSCACHE_TRUE@LoadPlugin fscache
+#@BUILD_PLUGIN_GMOND_TRUE@LoadPlugin gmond
+#@BUILD_PLUGIN_HDDTEMP_TRUE@LoadPlugin hddtemp
+@BUILD_PLUGIN_INTERFACE_TRUE@@BUILD_PLUGIN_INTERFACE_TRUE@LoadPlugin interface
+#@BUILD_PLUGIN_IPTABLES_TRUE@LoadPlugin iptables
+#@BUILD_PLUGIN_IPMI_TRUE@LoadPlugin ipmi
+#@BUILD_PLUGIN_IPVS_TRUE@LoadPlugin ipvs
+#@BUILD_PLUGIN_IRQ_TRUE@LoadPlugin irq
+#@BUILD_PLUGIN_JAVA_TRUE@LoadPlugin java
+#@BUILD_PLUGIN_LIBVIRT_TRUE@LoadPlugin libvirt
+@BUILD_PLUGIN_LOAD_TRUE@@BUILD_PLUGIN_LOAD_TRUE@LoadPlugin load
+#@BUILD_PLUGIN_LPAR_TRUE@LoadPlugin lpar
+#@BUILD_PLUGIN_MADWIFI_TRUE@LoadPlugin madwifi
+#@BUILD_PLUGIN_MBMON_TRUE@LoadPlugin mbmon
+#@BUILD_PLUGIN_MEMCACHEC_TRUE@LoadPlugin memcachec
+#@BUILD_PLUGIN_MEMCACHED_TRUE@LoadPlugin memcached
+@BUILD_PLUGIN_MEMORY_TRUE@@BUILD_PLUGIN_MEMORY_TRUE@LoadPlugin memory
+#@BUILD_PLUGIN_MODBUS_TRUE@LoadPlugin modbus
+#@BUILD_PLUGIN_MULTIMETER_TRUE@LoadPlugin multimeter
+#@BUILD_PLUGIN_MYSQL_TRUE@LoadPlugin mysql
+#@BUILD_PLUGIN_NETAPP_TRUE@LoadPlugin netapp
+#@BUILD_PLUGIN_NETLINK_TRUE@LoadPlugin netlink
+@LOAD_PLUGIN_NETWORK@LoadPlugin network
+#@BUILD_PLUGIN_NFS_TRUE@LoadPlugin nfs
+#@BUILD_PLUGIN_NGINX_TRUE@LoadPlugin nginx
+#@BUILD_PLUGIN_NOTIFY_DESKTOP_TRUE@LoadPlugin notify_desktop
+#@BUILD_PLUGIN_NOTIFY_EMAIL_TRUE@LoadPlugin notify_email
+#@BUILD_PLUGIN_NTPD_TRUE@LoadPlugin ntpd
+#@BUILD_PLUGIN_NUT_TRUE@LoadPlugin nut
+#@BUILD_PLUGIN_OLSRD_TRUE@LoadPlugin olsrd
+#@BUILD_PLUGIN_ONEWIRE_TRUE@LoadPlugin onewire
+#@BUILD_PLUGIN_OPENVPN_TRUE@LoadPlugin openvpn
+#@BUILD_PLUGIN_ORACLE_TRUE@LoadPlugin oracle
+#@BUILD_PLUGIN_PERL_TRUE@<LoadPlugin perl>
+#@BUILD_PLUGIN_PERL_TRUE@  Globals true
+#@BUILD_PLUGIN_PERL_TRUE@</LoadPlugin>
+#@BUILD_PLUGIN_PINBA_TRUE@LoadPlugin pinba
+#@BUILD_PLUGIN_PING_TRUE@LoadPlugin ping
+#@BUILD_PLUGIN_POSTGRESQL_TRUE@LoadPlugin postgresql
+#@BUILD_PLUGIN_POWERDNS_TRUE@LoadPlugin powerdns
+#@BUILD_PLUGIN_PROCESSES_TRUE@LoadPlugin processes
+#@BUILD_PLUGIN_PROTOCOLS_TRUE@LoadPlugin protocols
+#@BUILD_PLUGIN_PYTHON_TRUE@<LoadPlugin python>
+#@BUILD_PLUGIN_PYTHON_TRUE@  Globals true
+#@BUILD_PLUGIN_PYTHON_TRUE@</LoadPlugin>
+#@BUILD_PLUGIN_REDIS_TRUE@LoadPlugin redis
+#@BUILD_PLUGIN_ROUTEROS_TRUE@LoadPlugin routeros
+#@BUILD_PLUGIN_RRDCACHED_TRUE@LoadPlugin rrdcached
+@LOAD_PLUGIN_RRDTOOL@LoadPlugin rrdtool
+#@BUILD_PLUGIN_SENSORS_TRUE@LoadPlugin sensors
+#@BUILD_PLUGIN_SERIAL_TRUE@LoadPlugin serial
+#@BUILD_PLUGIN_SNMP_TRUE@LoadPlugin snmp
+#@BUILD_PLUGIN_SWAP_TRUE@LoadPlugin swap
+#@BUILD_PLUGIN_TABLE_TRUE@LoadPlugin table
+#@BUILD_PLUGIN_TAIL_TRUE@LoadPlugin tail
+#@BUILD_PLUGIN_TAPE_TRUE@LoadPlugin tape
+#@BUILD_PLUGIN_TCPCONNS_TRUE@LoadPlugin tcpconns
+#@BUILD_PLUGIN_TEAMSPEAK2_TRUE@LoadPlugin teamspeak2
+#@BUILD_PLUGIN_TED_TRUE@LoadPlugin ted
+#@BUILD_PLUGIN_THERMAL_TRUE@LoadPlugin thermal
+#@BUILD_PLUGIN_TOKYOTYRANT_TRUE@LoadPlugin tokyotyrant
+#@BUILD_PLUGIN_UNIXSOCK_TRUE@LoadPlugin unixsock
+#@BUILD_PLUGIN_UPTIME_TRUE@LoadPlugin uptime
+#@BUILD_PLUGIN_USERS_TRUE@LoadPlugin users
+#@BUILD_PLUGIN_UUID_TRUE@LoadPlugin uuid
+#@BUILD_PLUGIN_VARNISH_TRUE@LoadPlugin varnish
+#@BUILD_PLUGIN_VMEM_TRUE@LoadPlugin vmem
+#@BUILD_PLUGIN_VSERVER_TRUE@LoadPlugin vserver
+#@BUILD_PLUGIN_WIRELESS_TRUE@LoadPlugin wireless
+#@BUILD_PLUGIN_WRITE_HTTP_TRUE@LoadPlugin write_http
+#@BUILD_PLUGIN_WRITE_REDIS_TRUE@LoadPlugin write_redis
+#@BUILD_PLUGIN_WRITE_MONGO_TRUE@LoadPlugin write_mongo
+#@BUILD_PLUGIN_XMMS_TRUE@LoadPlugin xmms
+#@BUILD_PLUGIN_ZFS_ARC_TRUE@LoadPlugin zfs_arc
+
+##############################################################################
+# Plugin configuration                                                       #
+#----------------------------------------------------------------------------#
+# In this section configuration stubs for each plugin are provided. A desc-  #
+# ription of those options is available in the collectd.conf(5) manual page. #
+##############################################################################
+
+#<Plugin "amqp">
+#  <Publish "name">
+#    Host "localhost"
+#    Port "5672"
+#    VHost "/"
+#    User "guest"
+#    Password "guest"
+#    Exchange "amq.fanout"
+#    RoutingKey "collectd"
+#    Persistent false
+#    StoreRates false
+#  </Publish>
+#</Plugin>
+
+#<Plugin apache>
+#  <Instance "local">
+#    URL "http://localhost/status?auto"
+#    User "www-user"
+#    Password "secret"
+#    CACert "/etc/ssl/ca.crt"
+#  </Instance>
+#</Plugin>
+
+#<Plugin apcups>
+#      Host "localhost"
+#      Port "3551"
+#</Plugin>
+
+#<Plugin ascent>
+#      URL "http://localhost/ascent/status/"
+#      User "www-user"
+#      Password "secret"
+#      CACert "/etc/ssl/ca.crt"
+#</Plugin>
+
+#<Plugin "bind">
+#  URL "http://localhost:8053/"
+#  OpCodes         true
+#  QTypes          true
+#
+#  ServerStats     true
+#  ZoneMaintStats  true
+#  ResolverStats   false
+#  MemoryStats     true
+#
+#  <View "_default">
+#    QTypes        true
+#    ResolverStats true
+#    CacheRRSets   true
+#
+#    Zone "127.in-addr.arpa/IN"
+#  </View>
+#</Plugin>
+
+#<Plugin csv>
+#      DataDir "@prefix@/var/lib/@PACKAGE_NAME@/csv"
+#      StoreRates false
+#</Plugin>
+
+#<Plugin curl>
+#  <Page "stock_quotes">
+#    URL "http://finance.google.com/finance?q=NYSE%3AAMD"
+#    User "foo"
+#    Password "bar"
+#    MeasureResponseTime false
+#    <Match>
+#      Regex "<span +class=\"pr\"[^>]*> *([0-9]*\\.[0-9]+) *</span>"
+#      DSType "GaugeAverage"
+#      Type "stock_value"
+#      Instance "AMD"
+#    </Match>
+#  </Page>
+#</Plugin>
+
+#<Plugin curl_json>
+## See: http://wiki.apache.org/couchdb/Runtime_Statistics
+#  <URL "http://localhost:5984/_stats">
+#    Instance "httpd"
+#    <Key "httpd/requests/count">
+#      Type "http_requests"
+#    </Key>
+#
+#    <Key "httpd_request_methods/*/count">
+#      Type "http_request_methods"
+#    </Key>
+#
+#    <Key "httpd_status_codes/*/count">
+#      Type "http_response_codes"
+#    </Key>
+#  </URL>
+## Database status metrics:
+#  <URL "http://localhost:5984/_all_dbs">
+#    Instance "dbs"
+#    <Key "*/doc_count">
+#      Type "gauge"
+#    </Key>
+#    <Key "*/doc_del_count">
+#      Type "counter"
+#    </Key>
+#    <Key "*/disk_size">
+#      Type "bytes"
+#    </Key>
+#  </URL>
+#</Plugin>
+
+#<Plugin "curl_xml">
+#  <URL "http://localhost/stats.xml">
+#    Host "my_host"
+#    Instance "some_instance"
+#    User "collectd"
+#    Password "thaiNg0I"
+#    VerifyPeer true
+#    VerifyHost true
+#    CACert "/path/to/ca.crt"
+#
+#    <XPath "table[@id=\"magic_level\"]/tr">
+#      Type "magic_level"
+#      #InstancePrefix "prefix-"
+#      InstanceFrom "td[1]"
+#      ValuesFrom "td[2]/span[@class=\"level\"]"
+#    </XPath>
+#  </URL>
+#</Plugin>
+
+#<Plugin dbi>
+#      <Query "num_of_customers">
+#              Statement "SELECT 'customers' AS c_key, COUNT(*) AS c_value FROM customers_tbl"
+#              <Result>
+#                      Type "gauge"
+#                      InstancesFrom "c_key"
+#                      ValuesFrom "c_value"
+#              </Result>
+#      </Query>
+#      <Database "customers_db">
+#              Driver "mysql"
+#              DriverOption "host" "localhost"
+#              DriverOption "username" "collectd"
+#              DriverOption "password" "AeXohy0O"
+#              DriverOption "dbname" "custdb0"
+#              #SelectDB "custdb0"
+#              Query "num_of_customers"
+#              #Query "..."
+#      </Database>
+#</Plugin>
+
+#<Plugin df>
+#      Device "/dev/hda1"
+#      Device "192.168.0.2:/mnt/nfs"
+#      MountPoint "/home"
+#      FSType "ext3"
+#      IgnoreSelected false
+#      ReportByDevice false
+#      ReportReserved false
+#      ReportInodes false
+#</Plugin>
+
+#<Plugin disk>
+#      Disk "/^[hs]d[a-f][0-9]?$/"
+#      IgnoreSelected false
+#</Plugin>
+
+#<Plugin dns>
+#      Interface "eth0"
+#      IgnoreSource "192.168.0.1"
+#      SelectNumericQueryTypes true
+#</Plugin>
+
+#<Plugin email>
+#      SocketFile "@prefix@/var/run/@PACKAGE_NAME@-email"
+#      SocketGroup "collectd"
+#      SocketPerms "0770"
+#      MaxConns 5
+#</Plugin>
+
+#<Plugin exec>
+#      Exec "user:group" "/path/to/exec"
+#      NotificationExec "user:group" "/path/to/exec"
+#</Plugin>
+
+#<Plugin filecount>
+#      <Directory "/path/to/dir">
+#              Instance "foodir"
+#              Name "*.conf"
+#              MTime "-5m"
+#              Size "+10k"
+#              Recursive true
+#              IncludeHidden false
+#      </Directory>
+#</Plugin>
+
+#<Plugin "gmond">
+#  MCReceiveFrom "239.2.11.71" "8649"
+#  <Metric "swap_total">
+#    Type "swap"
+#    TypeInstance "total"
+#    DataSource "value"
+#  </Metric>
+#  <Metric "swap_free">
+#    Type "swap"
+#    TypeInstance "free"
+#    DataSource "value"
+#  </Metric>
+#</Plugin>
+
+#<Plugin hddtemp>
+#  Host "127.0.0.1"
+#  Port "7634"
+#</Plugin>
+
+#<Plugin interface>
+#      Interface "eth0"
+#      IgnoreSelected false
+#</Plugin>
+
+#<Plugin ipmi>
+#      Sensor "some_sensor"
+#      Sensor "another_one"
+#      IgnoreSelected false
+#      NotifySensorAdd false
+#      NotifySensorRemove true
+#      NotifySensorNotPresent false
+#</Plugin>
+
+#<Plugin iptables>
+#      Chain table chain
+#</Plugin>
+
+#<Plugin irq>
+#      Irq 7
+#      Irq 8
+#      Irq 9
+#      IgnoreSelected true
+#</Plugin>
+
+#<Plugin "java">
+#      JVMArg "-verbose:jni"
+#      JVMArg "-Djava.class.path=@prefix@/share/collectd/java/collectd-api.jar"
+#
+#      LoadPlugin "org.collectd.java.Foobar"
+#      <Plugin "org.collectd.java.Foobar">
+#        # To be parsed by the plugin
+#      </Plugin>
+#</Plugin>
+
+#<Plugin libvirt>
+#      Connection "xen:///"
+#      RefreshInterval 60
+#      Domain "name"
+#      BlockDevice "name:device"
+#      InterfaceDevice "name:device"
+#      IgnoreSelected false
+#      HostnameFormat name
+#      InterfaceFormat name
+#</Plugin>
+
+#<Plugin lpar>
+#      CpuPoolStats   false
+#      ReportBySerial false
+#</Plugin>
+
+#<Plugin madwifi>
+#      Interface "wlan0"
+#      IgnoreSelected false
+#      Source "SysFS"
+#      WatchSet "None"
+#      WatchAdd "node_octets"
+#      WatchAdd "node_rssi"
+#      WatchAdd "is_rx_acl"
+#      WatchAdd "is_scan_active"
+#</Plugin>
+
+#<Plugin mbmon>
+#      Host "127.0.0.1"
+#      Port "411"
+#</Plugin>
+
+#<Plugin memcachec>
+#      <Page "plugin_instance">
+#              Server "localhost"
+#              Key "page_key"
+#              <Match>
+#                      Regex "(\\d+) bytes sent"
+#                      ExcludeRegex "<lines to be excluded>"
+#                      DSType CounterAdd
+#                      Type "ipt_octets"
+#                      Instance "type_instance"
+#              </Match>
+#      </Page>
+#</Plugin>
+
+#<Plugin memcached>
+#      Host "127.0.0.1"
+#      Port "11211"
+#</Plugin>
+
+#<Plugin modbus>
+#      <Data "data_name">
+#              RegisterBase 1234
+#              RegisterType float
+#              Type gauge
+#              Instance "..."
+#      </Data>
+#
+#      <Host "name">
+#              Address "addr"
+#              Port "1234"
+#              Interval 60
+#
+#              <Slave 1>
+#                      Instance "foobar" # optional
+#                      Collect "data_name"
+#              </Slave>
+#      </Host>
+#</Plugin>
+
+#<Plugin mysql>
+#      <Database db_name>
+#              Host "database.serv.er"
+#              User "db_user"
+#              Password "secret"
+#              Database "db_name"
+#              MasterStats true
+#      </Database>
+#
+#      <Database db_name2>
+#              Host "localhost"
+#              Socket "/var/run/mysql/mysqld.sock"
+#              SlaveStats true
+#              SlaveNotifications true
+#      </Database>
+#</Plugin>
+
+#<Plugin netapp>
+#      <Host "netapp1.example.com">
+#              Protocol      "https"
+#              Address       "10.0.0.1"
+#              Port          443
+#              User          "username"
+#              Password      "aef4Aebe"
+#              Interval      30
+#
+#              <WAFL>
+#                      Interval 30
+#                      GetNameCache   true
+#                      GetDirCache    true
+#                      GetBufferCache true
+#                      GetInodeCache  true
+#              </WAFL>
+#
+#              <Disks>
+#                      Interval 30
+#                      GetBusy true
+#              </Disks>
+#
+#              <VolumePerf>
+#                      Interval 30
+#                      GetIO      "volume0"
+#                      IgnoreSelectedIO      false
+#                      GetOps     "volume0"
+#                      IgnoreSelectedOps     false
+#                      GetLatency "volume0"
+#                      IgnoreSelectedLatency false
+#              </VolumePerf>
+#
+#              <VolumeUsage>
+#                      Interval 30
+#                      GetCapacity "vol0"
+#                      GetCapacity "vol1"
+#                      IgnoreSelectedCapacity false
+#                      GetSnapshot "vol1"
+#                      GetSnapshot "vol3"
+#                      IgnoreSelectedSnapshot false
+#              </VolumeUsage>
+#
+#              <System>
+#                      Interval 30
+#                      GetCPULoad     true
+#                      GetInterfaces  true
+#                      GetDiskOps     true
+#                      GetDiskIO      true
+#              </System>
+#      </Host>
+#</Plugin>
+
+#<Plugin netlink>
+#      Interface "All"
+#      VerboseInterface "All"
+#      QDisc "eth0" "pfifo_fast-1:0"
+#      Class "ppp0" "htb-1:10"
+#      Filter "ppp0" "u32-1:0"
+#      IgnoreSelected false
+#</Plugin>
+
+@LOAD_PLUGIN_NETWORK@<Plugin network>
+#      # client setup:
+@LOAD_PLUGIN_NETWORK@  Server "ff18::efc0:4a42" "25826"
+@LOAD_PLUGIN_NETWORK@  <Server "239.192.74.66" "25826">
+#              SecurityLevel Encrypt
+#              Username "user"
+#              Password "secret"
+#              Interface "eth0"
+@LOAD_PLUGIN_NETWORK@  </Server>
+#      TimeToLive "128"
+#
+#      # server setup:
+#      Listen "ff18::efc0:4a42" "25826"
+#      <Listen "239.192.74.66" "25826">
+#              SecurityLevel Sign
+#              AuthFile "/etc/collectd/passwd"
+#              Interface "eth0"
+#      </Listen>
+#      MaxPacketSize 1024
+#
+#      # proxy setup (client and server as above):
+#      Forward true
+#
+#      # statistics about the network plugin itself
+#      ReportStats false
+#
+#      # "garbage collection"
+#      CacheFlush 1800
+@LOAD_PLUGIN_NETWORK@</Plugin>
+
+#<Plugin nginx>
+#      URL "http://localhost/status?auto"
+#      User "www-user"
+#      Password "secret"
+#      CACert "/etc/ssl/ca.crt"
+#</Plugin>
+
+#<Plugin notify_desktop>
+#      OkayTimeout 1000
+#      WarningTimeout 5000
+#      FailureTimeout 0
+#</Plugin>
+
+#<Plugin notify_email>
+#       SMTPServer "localhost"
+#      SMTPPort 25
+#      SMTPUser "my-username"
+#      SMTPPassword "my-password"
+#      From "collectd@main0server.com"
+#      # <WARNING/FAILURE/OK> on <hostname>. beware! do not use not more than two %s in this string!!!
+#      Subject "Aaaaaa!! %s on %s!!!!!"
+#      Recipient "email1@domain1.net"
+#      Recipient "email2@domain2.com"
+#</Plugin>
+
+#<Plugin ntpd>
+#      Host "localhost"
+#      Port 123
+#      ReverseLookups false
+#</Plugin>
+
+#<Plugin nut>
+#      UPS "upsname@hostname:port"
+#</Plugin>
+
+#<Plugin olsrd>
+#      Host "127.0.0.1"
+#      Port "2006"
+#      CollectLinks "Summary"
+#      CollectRoutes "Summary"
+#      CollectTopology "Summary"
+#</Plugin>
+
+#<Plugin onewire>
+#      Device "-s localhost:4304"
+#      Sensor "F10FCA000800"
+#      IgnoreSelected false
+#</Plugin>
+
+#<Plugin openvpn>
+#      StatusFile "/etc/openvpn/openvpn-status.log"
+#      ImprovedNamingSchema false
+#      CollectCompression true
+#      CollectIndividualUsers true
+#      CollectUserCount false
+#</Plugin>
+
+#<Plugin oracle>
+#  <Query "out_of_stock">
+#    Statement "SELECT category, COUNT(*) AS value FROM products WHERE in_stock = 0 GROUP BY category"
+#    <Result>
+#      Type "gauge"
+#      InstancesFrom "category"
+#      ValuesFrom "value"
+#    </Result>
+#  </Query>
+#  <Database "product_information">
+#    ConnectID "db01"
+#    Username "oracle"
+#    Password "secret"
+#    Query "out_of_stock"
+#  </Database>
+#</Plugin>
+
+#<Plugin perl>
+#      IncludeDir "/my/include/path"
+#      BaseName "Collectd::Plugins"
+#      EnableDebugger ""
+#      LoadPlugin Monitorus
+#      LoadPlugin OpenVZ
+#
+#      <Plugin foo>
+#              Foo "Bar"
+#              Qux "Baz"
+#      </Plugin>
+#</Plugin>
+
+#<Plugin pinba>
+#      Address "::0"
+#      Port "30002"
+#      <View "name">
+#              Host "host name"
+#              Server "server name"
+#              Script "script name"
+#      </View>
+#</Plugin>
+
+#<Plugin ping>
+#      Host "host.foo.bar"
+#      Interval 1.0
+#      Timeout 0.9
+#      TTL 255
+#      SourceAddress "1.2.3.4"
+#      Device "eth0"
+#      MaxMissed -1
+#</Plugin>
+
+#<Plugin postgresql>
+#      <Query magic>
+#              Statement "SELECT magic FROM wizard WHERE host = $1;"
+#              Param hostname
+#              <Result>
+#                      Type gauge
+#                      InstancePrefix "magic"
+#                      ValuesFrom magic
+#              </Result>
+#      </Query>
+#      <Query rt36_tickets>
+#              Statement "SELECT COUNT(type) AS count, type \
+#                                FROM (SELECT CASE \
+#                                             WHEN resolved = 'epoch' THEN 'open' \
+#                                             ELSE 'resolved' END AS type \
+#                                             FROM tickets) type \
+#                                GROUP BY type;"
+#              <Result>
+#                      Type counter
+#                      InstancePrefix "rt36_tickets"
+#                      InstancesFrom "type"
+#                      ValuesFrom "count"
+#              </Result>
+#      </Query>
+#      <Database foo>
+#              Host "hostname"
+#              Port "5432"
+#              User "username"
+#              Password "secret"
+#              SSLMode "prefer"
+#              KRBSrvName "kerberos_service_name"
+#              Query magic
+#      </Database>
+#      <Database bar>
+#              Interval 60
+#              Service "service_name"
+#              Query backend # predefined
+#              Query rt36_tickets
+#      </Database>
+#</Plugin>
+
+#<Plugin powerdns>
+#  <Server "server_name">
+#    Collect "latency"
+#    Collect "udp-answers" "udp-queries"
+#    Socket "/var/run/pdns.controlsocket"
+#  </Server>
+#  <Recursor "recursor_name">
+#    Collect "questions"
+#    Collect "cache-hits" "cache-misses"
+#    Socket "/var/run/pdns_recursor.controlsocket"
+#  </Recursor>
+#  LocalSocket "/opt/collectd/var/run/collectd-powerdns"
+#</Plugin>
+
+#<Plugin processes>
+#      Process "name"
+#</Plugin>
+
+#<Plugin protocols>
+#      Value "/^Tcp:/"
+#      IgnoreSelected false
+#</Plugin>
+
+#<Plugin python>
+#      ModulePath "/path/to/your/python/modules"
+#      LogTraces true
+#      Interactive true
+#      Import "spam"
+#
+#      <Module spam>
+#              spam "wonderful" "lovely"
+#      </Module>
+#</Plugin>
+
+#<Plugin redis>
+#   <Node example>
+#      Host "redis.example.com"
+#      Port "6379"
+#      Timeout 2000
+#   </Node>
+#</Plugin>
+
+#<Plugin routeros>
+#      <Router>
+#              Host "router.example.com"
+#              Port "8728"
+#              User "admin"
+#              Password "dozaiTh4"
+#              CollectInterface true
+#              CollectRegistrationTable true
+#              CollectCPULoad true
+#              CollectMemory true
+#              CollectDF true
+#              CollectDisk true
+#      </Router>
+#</Plugin>
+
+#<Plugin rrdcached>
+#      DaemonAddress "unix:/tmp/rrdcached.sock"
+#      DataDir "@prefix@/var/lib/@PACKAGE_NAME@/rrd"
+#      CreateFiles true
+#      CollectStatistics true
+#</Plugin>
+
+#<Plugin rrdtool>
+#      DataDir "@prefix@/var/lib/@PACKAGE_NAME@/rrd"
+#      CacheTimeout 120
+#      CacheFlush   900
+#</Plugin>
+
+#<Plugin sensors>
+#      Sensor "it8712-isa-0290/temperature-temp1"
+#      Sensor "it8712-isa-0290/fanspeed-fan3"
+#      Sensor "it8712-isa-0290/voltage-in8"
+#      IgnoreSelected false
+#</Plugin>
+
+#<Plugin snmp>
+#   <Data "powerplus_voltge_input">
+#       Type "voltage"
+#       Table false
+#       Instance "input_line1"
+#       Values "SNMPv2-SMI::enterprises.6050.5.4.1.1.2.1"
+#   </Data>
+#   <Data "hr_users">
+#       Type "users"
+#       Table false
+#       Instance ""
+#       Values "HOST-RESOURCES-MIB::hrSystemNumUsers.0"
+#   </Data>
+#   <Data "std_traffic">
+#       Type "if_octets"
+#       Table true
+#       Instance "IF-MIB::ifDescr"
+#       Values "IF-MIB::ifInOctets" "IF-MIB::ifOutOctets"
+#   </Data>
+#
+#   <Host "some.switch.mydomain.org">
+#       Address "192.168.0.2"
+#       Version 1
+#       Community "community_string"
+#       Collect "std_traffic"
+#       Interval 120
+#   </Host>
+#   <Host "some.server.mydomain.org">
+#       Address "192.168.0.42"
+#       Version 2
+#       Community "another_string"
+#       Collect "std_traffic" "hr_users"
+#   </Host>
+#   <Host "some.ups.mydomain.org">
+#       Address "192.168.0.3"
+#       Version 1
+#       Community "more_communities"
+#       Collect "powerplus_voltge_input"
+#       Interval 300
+#   </Host>
+#</Plugin>
+
+#<Plugin "swap">
+#      ReportByDevice false
+#</Plugin>
+
+#<Plugin "table">
+#      <Table "/proc/slabinfo">
+#              Instance "slabinfo"
+#              Separator " "
+#              <Result>
+#                      Type gauge
+#                      InstancePrefix "active_objs"
+#                      InstancesFrom 0
+#                      ValuesFrom 1
+#              </Result>
+#              <Result>
+#                      Type gauge
+#                      InstancePrefix "objperslab"
+#                      InstancesFrom 0
+#                      ValuesFrom 4
+#              </Result>
+#      </Table>
+#</Plugin>
+
+#<Plugin "tail">
+#  <File "/var/log/exim4/mainlog">
+#    Instance "exim"
+#    <Match>
+#      Regex "S=([1-9][0-9]*)"
+#      DSType "CounterAdd"
+#      Type "ipt_bytes"
+#      Instance "total"
+#    </Match>
+#    <Match>
+#      Regex "\\<R=local_user\\>"
+#      ExcludeRegex "\\<R=local_user\\>.*mail_spool defer"
+#      DSType "CounterInc"
+#      Type "counter"
+#      Instance "local_user"
+#    </Match>
+#  </File>
+#</Plugin>
+
+#<Plugin tcpconns>
+#      ListeningPorts false
+#      LocalPort "25"
+#      RemotePort "25"
+#</Plugin>
+
+#<Plugin teamspeak2>
+#      Host "127.0.0.1"
+#      Port "51234"
+#      Server "8767"
+#</Plugin>
+
+#<Plugin ted>
+#      Device "/dev/ttyUSB0"
+#      Retries 0
+#</Plugin>
+
+#<Plugin thermal>
+#      ForceUseProcfs false
+#      Device "THRM"
+#      IgnoreSelected false
+#</Plugin>
+
+#<Plugin tokyotyrant>
+#      Host "localhost"
+#      Port "1978"
+#</Plugin>
+
+#<Plugin unixsock>
+#      SocketFile "@prefix@/var/run/@PACKAGE_NAME@-unixsock"
+#      SocketGroup "collectd"
+#      SocketPerms "0660"
+#      DeleteSocket false
+#</Plugin>
+
+#<Plugin uuid>
+#      UUIDFile "/etc/uuid"
+#</Plugin>
+
+#<Plugin varnish>
+#   This tag support an argument if you want to
+#   monitor the local instance just use </Instance>
+#   If you prefer defining another instance you can do
+#   so by using <Instance "myinstance">
+#   <Instance>
+#      CollectCache true
+#      CollectBackend true
+#      CollectConnections true
+#      CollectSHM true
+#      CollectESI false
+#      CollectFetch false
+#      CollectHCB false
+#      CollectSMA false
+#      CollectSMS false
+#      CollectSM false
+#      CollectTotals false
+#      CollectWorkers false
+#   </Instance>
+#</Plugin>
+
+#<Plugin vmem>
+#      Verbose false
+#</Plugin>
+
+#<Plugin write_http>
+#      <URL "http://example.com/collectd-post">
+#              User "collectd"
+#              Password "weCh3ik0"
+#              VerifyPeer true
+#              VerifyHost true
+#              CACert "/etc/ssl/ca.crt"
+#              Format "Command"
+#              StoreRates false
+#      </URL>
+#</Plugin>
+
+#<Plugin write_redis>
+#      <Node "example">
+#              Host "localhost"
+#              Port "6379"
+#              Timeout 1000
+#      </Node>
+#</Plugin>
+
+#<Plugin write_mongo>
+#      <Node "example">
+#              Host "localhost"
+#              Port "27017"
+#              Timeout 1000
+#      </Node>
+#</Plugin>
+
+##############################################################################
+# Filter configuration                                                       #
+#----------------------------------------------------------------------------#
+# The following configures collectd's filtering mechanism. Before changing   #
+# anything in this section, please read the `FILTER CONFIGURATION' section   #
+# in the collectd.conf(5) manual page.                                       #
+##############################################################################
+
+# Load required matches:
+#@BUILD_PLUGIN_MATCH_EMPTY_COUNTER_TRUE@LoadPlugin match_empty_counter
+#@BUILD_PLUGIN_MATCH_HASHED_TRUE@LoadPlugin match_hashed
+#@BUILD_PLUGIN_MATCH_REGEX_TRUE@LoadPlugin match_regex
+#@BUILD_PLUGIN_MATCH_VALUE_TRUE@LoadPlugin match_value
+#@BUILD_PLUGIN_MATCH_TIMEDIFF_TRUE@LoadPlugin match_timediff
+
+# Load required targets:
+#@BUILD_PLUGIN_TARGET_NOTIFICATION_TRUE@LoadPlugin target_notification
+#@BUILD_PLUGIN_TARGET_REPLACE_TRUE@LoadPlugin target_replace
+#@BUILD_PLUGIN_TARGET_SCALE_TRUE@LoadPlugin target_scale
+#@BUILD_PLUGIN_TARGET_SET_TRUE@LoadPlugin target_set
+#@BUILD_PLUGIN_TARGET_V5UPGRADE_TRUE@LoadPlugin target_v5upgrade
+
+#----------------------------------------------------------------------------#
+# The following block demonstrates the default behavior if no filtering is   #
+# configured at all: All values will be sent to all available write plugins. #
+#----------------------------------------------------------------------------#
+
+#<Chain "PostCache">
+#  Target "write"
+#</Chain>
+
+##############################################################################
+# Threshold configuration                                                    #
+#----------------------------------------------------------------------------#
+# The following outlines how to configure collectd's threshold checking      #
+# plugin. The plugin and possible configuration options are documented in    #
+# the collectd-threshold(5) manual page.                                     #
+##############################################################################
+
+#@BUILD_PLUGIN_THRESHOLD_TRUE@LoadPlugin "threshold"
+#<Plugin "threshold">
+#  <Type "foo">
+#    WarningMin    0.00
+#    WarningMax 1000.00
+#    FailureMin    0.00
+#    FailureMax 1200.00
+#    Invert false
+#    Instance "bar"
+#  </Type>
+#
+#  <Plugin "interface">
+#    Instance "eth0"
+#    <Type "if_octets">
+#      FailureMax 10000000
+#      DataSource "rx"
+#    </Type>
+#  </Plugin>
+#
+#  <Host "hostname">
+#    <Type "cpu">
+#      Instance "idle"
+#      FailureMin 10
+#    </Type>
+#
+#    <Plugin "memory">
+#      <Type "memory">
+#        Instance "cached"
+#        WarningMin 100000000
+#      </Type>
+#    </Plugin>
+#
+#    <Type "load">
+#      DataSource "midterm"
+#      FailureMax 4
+#      Hits 3
+#      Hysteresis 3
+#    </Type>
+#  </Host>
+#</Plugin>
diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod
new file mode 100644 (file)
index 0000000..91a8493
--- /dev/null
@@ -0,0 +1,5578 @@
+=head1 NAME
+
+collectd.conf - Configuration for the system statistics collection daemon B<collectd>
+
+=head1 SYNOPSIS
+
+  BaseDir "/path/to/data/"
+  PIDFile "/path/to/pidfile/collectd.pid"
+  Server  "123.123.123.123" 12345
+
+  LoadPlugin cpu
+  LoadPlugin load
+  LoadPlugin ping
+
+  <Plugin ping>
+    Host "example.org"
+    Host "provider.net"
+  </Plugin>
+
+=head1 DESCRIPTION
+
+This config file controls how the system statistics collection daemon
+B<collectd> behaves. The most significant option is B<LoadPlugin>, which
+controls which plugins to load. These plugins ultimately define collectd's
+behavior.
+
+The syntax of this config file is similar to the config file of the famous
+B<Apache Webserver>. Each line contains either a key-value-pair or a
+section-start or -end. Empty lines and everything after the hash-symbol `#' is
+ignored. Values are either string, enclosed in double-quotes,
+(floating-point-)numbers or a boolean expression, i.E<nbsp>e. either B<true> or
+B<false>. String containing of only alphanumeric characters and underscores do
+not need to be quoted. Lines may be wrapped by using `\' as the last character
+before the newline. This allows long lines to be split into multiple lines.
+Quoted strings may be wrapped as well. However, those are treated special in
+that whitespace at the beginning of the following lines will be ignored, which
+allows for nicely indenting the wrapped lines.
+
+The configuration is read and processed in order, i.E<nbsp>e. from top to
+bottom. So the plugins are loaded in the order listed in this config file. It
+is a good idea to load any logging plugins first in order to catch messages
+from plugins during configuration. Also, the C<LoadPlugin> option B<must> occur
+B<before> the C<E<lt>Plugin ...E<gt>> block.
+
+=head1 GLOBAL OPTIONS
+
+=over 4
+
+=item B<BaseDir> I<Directory>
+
+Sets the base directory. This is the directory beneath all RRD-files are
+created. Possibly more subdirectories are created. This is also the working
+directory for the daemon.
+
+=item B<LoadPlugin> I<Plugin>
+
+Loads the plugin I<Plugin>. There must be at least one such line or B<collectd>
+will be mostly useless.
+
+Starting with collectd 4.9, this may also be a block in which further options
+affecting the behavior of B<LoadPlugin> may be specified. The following
+options are allowed inside a B<LoadPlugin> block:
+
+  <LoadPlugin perl>
+    Globals true
+  </LoadPlugin>
+
+=over 4
+
+=item B<Globals> B<true|false>
+
+If enabled, collectd will export all global symbols of the plugin (and of all
+libraries loaded as dependencies of the plugin) and, thus, makes those symbols
+available for resolving unresolved symbols in subsequently loaded plugins if
+that is supported by your system.
+
+This is useful (or possibly even required), e.g., when loading a plugin that
+embeds some scripting language into the daemon (e.g. the I<Perl> and
+I<Python plugins>). Scripting languages usually provide means to load
+extensions written in C. Those extensions require symbols provided by the
+interpreter, which is loaded as a dependency of the respective collectd plugin.
+See the documentation of those plugins (e.g., L<collectd-perl(5)> or
+L<collectd-python(5)>) for details.
+
+By default, this is disabled. As a special exception, if the plugin name is
+either C<perl> or C<python>, the default is changed to enabled in order to keep
+the average user from ever having to deal with this low level linking stuff.
+
+=back
+
+=item B<Include> I<Path>
+
+If I<Path> points to a file, includes that file. If I<Path> points to a
+directory, recursively includes all files within that directory and its
+subdirectories. If the C<wordexp> function is available on your system,
+shell-like wildcards are expanded before files are included. This means you can
+use statements like the following:
+
+  Include "/etc/collectd.d/*.conf"
+
+If more than one files are included by a single B<Include> option, the files
+will be included in lexicographical order (as defined by the C<strcmp>
+function). Thus, you can e.E<nbsp>g. use numbered prefixes to specify the
+order in which the files are loaded.
+
+To prevent loops and shooting yourself in the foot in interesting ways the
+nesting is limited to a depth of 8E<nbsp>levels, which should be sufficient for
+most uses. Since symlinks are followed it is still possible to crash the daemon
+by looping symlinks. In our opinion significant stupidity should result in an
+appropriate amount of pain.
+
+It is no problem to have a block like C<E<lt>Plugin fooE<gt>> in more than one
+file, but you cannot include files from within blocks.
+
+=item B<PIDFile> I<File>
+
+Sets where to write the PID file to. This file is overwritten when it exists
+and deleted when the program is stopped. Some init-scripts might override this
+setting using the B<-P> command-line option.
+
+=item B<PluginDir> I<Directory>
+
+Path to the plugins (shared objects) of collectd.
+
+=item B<TypesDB> I<File> [I<File> ...]
+
+Set one or more files that contain the data-set descriptions. See
+L<types.db(5)> for a description of the format of this file.
+
+=item B<Interval> I<Seconds>
+
+Configures the interval in which to query the read plugins. Obviously smaller
+values lead to a higher system load produced by collectd, while higher values
+lead to more coarse statistics.
+
+B<Warning:> You should set this once and then never touch it again. If you do,
+I<you will have to delete all your RRD files> or know some serious RRDtool
+magic! (Assuming you're using the I<RRDtool> or I<RRDCacheD> plugin.)
+
+=item B<Timeout> I<Iterations>
+
+Consider a value list "missing" when no update has been read or received for
+I<Iterations> iterations. By default, I<collectd> considers a value list
+missing when no update has been received for twice the update interval. Since
+this setting uses iterations, the maximum allowed time without update depends
+on the I<Interval> information contained in each value list. This is used in
+the I<Threshold> configuration to dispatch notifications about missing values,
+see L<collectd-threshold(5)> for details.
+
+=item B<ReadThreads> I<Num>
+
+Number of threads to start for reading plugins. The default value is B<5>, but
+you may want to increase this if you have more than five plugins that take a
+long time to read. Mostly those are plugin that do network-IO. Setting this to
+a value higher than the number of plugins you've loaded is totally useless.
+
+=item B<Hostname> I<Name>
+
+Sets the hostname that identifies a host. If you omit this setting, the
+hostname will be determined using the L<gethostname(2)> system call.
+
+=item B<FQDNLookup> B<true|false>
+
+If B<Hostname> is determined automatically this setting controls whether or not
+the daemon should try to figure out the "fully qualified domain name", FQDN.
+This is done using a lookup of the name returned by C<gethostname>. This option
+is enabled by default.
+
+=item B<PreCacheChain> I<ChainName>
+
+=item B<PostCacheChain> I<ChainName>
+
+Configure the name of the "pre-cache chain" and the "post-cache chain". Please
+see L<FILTER CONFIGURATION> below on information on chains and how these
+setting change the daemon's behavior.
+
+=back
+
+=head1 PLUGIN OPTIONS
+
+Some plugins may register own options. These options must be enclosed in a
+C<Plugin>-Section. Which options exist depends on the plugin used. Some plugins
+require external configuration, too. The C<apache plugin>, for example,
+required C<mod_status> to be configured in the webserver you're going to
+collect data from. These plugins are listed below as well, even if they don't
+require any configuration within collectd's configfile.
+
+A list of all plugins and a short summary for each plugin can be found in the
+F<README> file shipped with the sourcecode and hopefully binary packets as
+well.
+
+=head2 Plugin C<amqp>
+
+The I<AMQMP plugin> can be used to communicate with other instances of
+I<collectd> or third party applications using an AMQP message broker. Values
+are sent to or received from the broker, which handles routing, queueing and
+possibly filtering or messages.
+
+ <Plugin "amqp">
+   # Send values to an AMQP broker
+   <Publish "some_name">
+     Host "localhost"
+     Port "5672"
+     VHost "/"
+     User "guest"
+     Password "guest"
+     Exchange "amq.fanout"
+ #   ExchangeType "fanout"
+ #   RoutingKey "collectd"
+ #   Persistent false
+ #   Format "command"
+ #   StoreRates false
+   </Publish>
+   
+   # Receive values from an AMQP broker
+   <Subscribe "some_name">
+     Host "localhost"
+     Port "5672"
+     VHost "/"
+     User "guest"
+     Password "guest"
+     Exchange "amq.fanout"
+ #   ExchangeType "fanout"
+ #   Queue "queue_name"
+ #   RoutingKey "collectd.#"
+   </Subscribe>
+ </Plugin>
+
+The plugin's configuration consists of a number of I<Publish> and I<Subscribe>
+blocks, which configure sending and receiving of values respectively. The two
+blocks are very similar, so unless otherwise noted, an option can be used in
+either block. The name given in the blocks starting tag is only used for
+reporting messages, but may be used to support I<flushing> of certain
+I<Publish> blocks in the future.
+
+=over 4
+
+=item B<Host> I<Host>
+
+Hostname or IP-address of the AMQP broker. Defaults to the default behavior of
+the underlying communications library, I<rabbitmq-c>, which is "localhost".
+
+=item B<Port> I<Port>
+
+Service name or port number on which the AMQP broker accepts connections. This
+argument must be a string, even if the numeric form is used. Defaults to
+"5672".
+
+=item B<VHost> I<VHost>
+
+Name of the I<virtual host> on the AMQP broker to use. Defaults to "/".
+
+=item B<User> I<User>
+
+=item B<Password> I<Password>
+
+Credentials used to authenticate to the AMQP broker. By default "guest"/"guest"
+is used.
+
+=item B<Exchange> I<Exchange>
+
+In I<Publish> blocks, this option specifies the I<exchange> to send values to.
+By default, "amq.fanout" will be used.
+
+In I<Subscribe> blocks this option is optional. If given, a I<binding> between
+the given exchange and the I<queue> is created, using the I<routing key> if
+configured. See the B<Queue> and B<RoutingKey> options below.
+
+=item B<ExchangeType> I<Type>
+
+If given, the plugin will try to create the configured I<exchange> with this
+I<type> after connecting. When in a I<Subscribe> block, the I<queue> will then
+be bound to this exchange.
+
+=item B<Queue> I<Queue> (Subscribe only)
+
+Configures the I<queue> name to subscribe to. If no queue name was configures
+explicitly, a unique queue name will be created by the broker.
+
+=item B<RoutingKey> I<Key>
+
+In I<Publish> blocks, this configures the routing key to set on all outgoing
+messages. If not given, the routing key will be computed from the I<identifier>
+of the value. The host, plugin, type and the two instances are concatenated
+together using dots as the separator and all containing dots replaced with
+slashes. For example "collectd.host/example/com.cpu.0.cpu.user". This makes it
+possible to receive only specific values using a "topic" exchange.
+
+In I<Subscribe> blocks, configures the I<routing key> used when creating a
+I<binding> between an I<exchange> and the I<queue>. The usual wildcards can be
+used to filter messages when using a "topic" exchange. If you're only
+interested in CPU statistics, you could use the routing key "collectd.*.cpu.#"
+for example.
+
+=item B<Persistent> B<true>|B<false> (Publish only)
+
+Selects the I<delivery method> to use. If set to B<true>, the I<persistent>
+mode will be used, i.e. delivery is guaranteed. If set to B<false> (the
+default), the I<transient> delivery mode will be used, i.e. messages may be
+lost due to high load, overflowing queues or similar issues.
+
+=item B<Format> B<Command>|B<JSON> (Publish only)
+
+Selects the format in which messages are sent to the broker. If set to
+B<Command> (the default), values are sent as C<PUTVAL> commands which are
+identical to the syntax used by the I<Exec> and I<UnixSock plugins>. In this
+case, the C<Content-Type> header field will be set to C<text/collectd>.
+
+If set to B<JSON>, the values are encoded in the I<JavaScript Object Notation>,
+an easy and straight forward exchange format. The C<Content-Type> header field
+will be set to C<application/json>.
+
+A subscribing client I<should> use the C<Content-Type> header field to
+determine how to decode the values. Currently, the I<AMQP plugin> itself can
+only decode the B<Command> format.
+
+=item B<StoreRates> B<true>|B<false> (Publish only)
+
+Determines whether or not C<COUNTER>, C<DERIVE> and C<ABSOLUTE> data sources
+are converted to a I<rate> (i.e. a C<GAUGE> value). If set to B<false> (the
+default), no conversion is performed. Otherwise the conversion is performed
+using the internal value cache.
+
+Please note that currently this option is only used if the B<Format> option has
+been set to B<JSON>.
+
+=back
+
+=head2 Plugin C<apache>
+
+To configure the C<apache>-plugin you first need to configure the Apache
+webserver correctly. The Apache-plugin C<mod_status> needs to be loaded and
+working and the C<ExtendedStatus> directive needs to be B<enabled>. You can use
+the following snipped to base your Apache config upon:
+
+  ExtendedStatus on
+  <IfModule mod_status.c>
+    <Location /mod_status>
+      SetHandler server-status
+    </Location>
+  </IfModule>
+
+Since its C<mod_status> module is very similar to Apache's, B<lighttpd> is
+also supported. It introduces a new field, called C<BusyServers>, to count the
+number of currently connected clients. This field is also supported.
+
+The configuration of the I<Apache> plugin consists of one or more
+C<E<lt>InstanceE<nbsp>/E<gt>> blocks. Each block requires one string argument
+as the instance name. For example:
+
+ <Plugin "apache">
+   <Instance "www1">
+     URL "http://www1.example.com/mod_status?auto"
+   </Instance>
+   <Instance "www2">
+     URL "http://www2.example.com/mod_status?auto"
+   </Instance>
+ </Plugin>
+
+The instance name will be used as the I<plugin instance>. To emulate the old
+(versionE<nbsp>4) behavior, you can use an empty string (""). In order for the
+plugin to work correctly, each instance name must be unique. This is not
+enforced by the plugin and it is your responsibility to ensure it.
+
+The following options are accepted within each I<Instance> block:
+
+=over 4
+
+=item B<URL> I<http://host/mod_status?auto>
+
+Sets the URL of the C<mod_status> output. This needs to be the output generated
+by C<ExtendedStatus on> and it needs to be the machine readable output
+generated by appending the C<?auto> argument. This option is I<mandatory>.
+
+=item B<User> I<Username>
+
+Optional user name needed for authentication.
+
+=item B<Password> I<Password>
+
+Optional password needed for authentication.
+
+=item B<VerifyPeer> B<true|false>
+
+Enable or disable peer SSL certificate verification. See
+L<http://curl.haxx.se/docs/sslcerts.html> for details. Enabled by default.
+
+=item B<VerifyHost> B<true|false>
+
+Enable or disable peer host name verification. If enabled, the plugin checks
+if the C<Common Name> or a C<Subject Alternate Name> field of the SSL
+certificate matches the host name provided by the B<URL> option. If this
+identity check fails, the connection is aborted. Obviously, only works when
+connecting to a SSL enabled server. Enabled by default.
+
+=item B<CACert> I<File>
+
+File that holds one or more SSL certificates. If you want to use HTTPS you will
+possibly need this option. What CA certificates come bundled with C<libcurl>
+and are checked by default depends on the distribution you use.
+
+=back
+
+=head2 Plugin C<apcups>
+
+=over 4
+
+=item B<Host> I<Hostname>
+
+Hostname of the host running B<apcupsd>. Defaults to B<localhost>. Please note
+that IPv6 support has been disabled unless someone can confirm or decline that
+B<apcupsd> can handle it.
+
+=item B<Port> I<Port>
+
+TCP-Port to connect to. Defaults to B<3551>.
+
+=back
+
+=head2 Plugin C<ascent>
+
+This plugin collects information about an Ascent server, a free server for the
+"World of Warcraft" game. This plugin gathers the information by fetching the
+XML status page using C<libcurl> and parses it using C<libxml2>.
+
+The configuration options are the same as for the C<apache> plugin above:
+
+=over 4
+
+=item B<URL> I<http://localhost/ascent/status/>
+
+Sets the URL of the XML status output.
+
+=item B<User> I<Username>
+
+Optional user name needed for authentication.
+
+=item B<Password> I<Password>
+
+Optional password needed for authentication.
+
+=item B<VerifyPeer> B<true|false>
+
+Enable or disable peer SSL certificate verification. See
+L<http://curl.haxx.se/docs/sslcerts.html> for details. Enabled by default.
+
+=item B<VerifyHost> B<true|false>
+
+Enable or disable peer host name verification. If enabled, the plugin checks
+if the C<Common Name> or a C<Subject Alternate Name> field of the SSL
+certificate matches the host name provided by the B<URL> option. If this
+identity check fails, the connection is aborted. Obviously, only works when
+connecting to a SSL enabled server. Enabled by default.
+
+=item B<CACert> I<File>
+
+File that holds one or more SSL certificates. If you want to use HTTPS you will
+possibly need this option. What CA certificates come bundled with C<libcurl>
+and are checked by default depends on the distribution you use.
+
+=back
+
+=head2 Plugin C<bind>
+
+Starting with BIND 9.5.0, the most widely used DNS server software provides
+extensive statistics about queries, responses and lots of other information.
+The bind plugin retrieves this information that's encoded in XML and provided
+via HTTP and submits the values to collectd.
+
+To use this plugin, you first need to tell BIND to make this information
+available. This is done with the C<statistics-channels> configuration option:
+
+ statistics-channels {
+   inet localhost port 8053;
+ };
+
+The configuration follows the grouping that can be seen when looking at the
+data with an XSLT compatible viewer, such as a modern web browser. It's
+probably a good idea to make yourself familiar with the provided values, so you
+can understand what the collected statistics actually mean.
+
+Synopsis:
+
+ <Plugin "bind">
+   URL "http://localhost:8053/"
+   OpCodes         true
+   QTypes          true
+   ServerStats     true
+   ZoneMaintStats  true
+   ResolverStats   false
+   MemoryStats     true
+   <View "_default">
+     QTypes        true
+     ResolverStats true
+     CacheRRSets   true
+     Zone "127.in-addr.arpa/IN"
+   </View>
+ </Plugin>
+
+The bind plugin accepts the following configuration options:
+
+=over 4
+
+=item B<URL> I<URL>
+
+URL from which to retrieve the XML data. If not specified,
+C<http://localhost:8053/> will be used.
+
+=item B<OpCodes> I<true>|I<false>
+
+When enabled, statistics about the I<"OpCodes">, for example the number of
+C<QUERY> packets, are collected.
+
+Default: Enabled.
+
+=item B<QTypes> I<true>|I<false>
+
+When enabled, the number of I<incoming> queries by query types (for example
+C<A>, C<MX>, C<AAAA>) is collected.
+
+Default: Enabled.
+
+=item B<ServerStats> I<true>|I<false>
+
+Collect global server statistics, such as requests received over IPv4 and IPv6,
+successful queries, and failed updates.
+
+Default: Enabled.
+
+=item B<ZoneMaintStats> I<true>|I<false>
+
+Collect zone maintenance statistics, mostly information about notifications
+(zone updates) and zone transfers.
+
+Default: Enabled.
+
+=item B<ResolverStats> I<true>|I<false>
+
+Collect resolver statistics, i.E<nbsp>e. statistics about outgoing requests
+(e.E<nbsp>g. queries over IPv4, lame servers). Since the global resolver
+counters apparently were removed in BIND 9.5.1 and 9.6.0, this is disabled by
+default. Use the B<ResolverStats> option within a B<View "_default"> block
+instead for the same functionality.
+
+Default: Disabled.
+
+=item B<MemoryStats>
+
+Collect global memory statistics.
+
+Default: Enabled.
+
+=item B<View> I<Name>
+
+Collect statistics about a specific I<"view">. BIND can behave different,
+mostly depending on the source IP-address of the request. These different
+configurations are called "views". If you don't use this feature, you most
+likely are only interested in the C<_default> view.
+
+Within a E<lt>B<View>E<nbsp>I<name>E<gt> block, you can specify which
+information you want to collect about a view. If no B<View> block is
+configured, no detailed view statistics will be collected.
+
+=over 4
+
+=item B<QTypes> I<true>|I<false>
+
+If enabled, the number of I<outgoing> queries by query type (e.E<nbsp>g. C<A>,
+C<MX>) is collected.
+
+Default: Enabled.
+
+=item B<ResolverStats> I<true>|I<false>
+
+Collect resolver statistics, i.E<nbsp>e. statistics about outgoing requests
+(e.E<nbsp>g. queries over IPv4, lame servers).
+
+Default: Enabled.
+
+=item B<CacheRRSets> I<true>|I<false>
+
+If enabled, the number of entries (I<"RR sets">) in the view's cache by query
+type is collected. Negative entries (queries which resulted in an error, for
+example names that do not exist) are reported with a leading exclamation mark,
+e.E<nbsp>g. "!A".
+
+Default: Enabled.
+
+=item B<Zone> I<Name>
+
+When given, collect detailed information about the given zone in the view. The
+information collected if very similar to the global B<ServerStats> information
+(see above).
+
+You can repeat this option to collect detailed information about multiple
+zones.
+
+By default no detailed zone information is collected.
+
+=back
+
+=back
+
+=head2 Plugin C<cpufreq>
+
+This plugin doesn't have any options. It reads
+F</sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq> (for the first CPU
+installed) to get the current CPU frequency. If this file does not exist make
+sure B<cpufreqd> (L<http://cpufreqd.sourceforge.net/>) or a similar tool is
+installed and an "cpu governor" (that's a kernel module) is loaded.
+
+=head2 Plugin C<csv>
+
+=over 4
+
+=item B<DataDir> I<Directory>
+
+Set the directory to store CSV-files under. Per default CSV-files are generated
+beneath the daemon's working directory, i.E<nbsp>e. the B<BaseDir>.
+The special strings B<stdout> and B<stderr> can be used to write to the standard
+output and standard error channels, respectively. This, of course, only makes
+much sense when collectd is running in foreground- or non-daemon-mode.
+
+=item B<StoreRates> B<true|false>
+
+If set to B<true>, convert counter values to rates. If set to B<false> (the
+default) counter values are stored as is, i.E<nbsp>e. as an increasing integer
+number.
+
+=back
+
+=head2 Plugin C<curl>
+
+The curl plugin uses the B<libcurl> (L<http://curl.haxx.se/>) to read web pages
+and the match infrastructure (the same code used by the tail plugin) to use
+regular expressions with the received data.
+
+The following example will read the current value of AMD stock from Google's
+finance page and dispatch the value to collectd.
+
+  <Plugin curl>
+    <Page "stock_quotes">
+      URL "http://finance.google.com/finance?q=NYSE%3AAMD"
+      User "foo"
+      Password "bar"
+      <Match>
+        Regex "<span +class=\"pr\"[^>]*> *([0-9]*\\.[0-9]+) *</span>"
+        DSType "GaugeAverage"
+        # Note: `stock_value' is not a standard type.
+        Type "stock_value"
+        Instance "AMD"
+      </Match>
+    </Page>
+  </Plugin>
+
+In the B<Plugin> block, there may be one or more B<Page> blocks, each defining
+a web page and one or more "matches" to be performed on the returned data. The
+string argument to the B<Page> block is used as plugin instance.
+
+The following options are valid within B<Page> blocks:
+
+=over 4
+
+=item B<URL> I<URL>
+
+URL of the web site to retrieve. Since a regular expression will be used to
+extract information from this data, non-binary data is a big plus here ;)
+
+=item B<User> I<Name>
+
+Username to use if authorization is required to read the page.
+
+=item B<Password> I<Password>
+
+Password to use if authorization is required to read the page.
+
+=item B<VerifyPeer> B<true>|B<false>
+
+Enable or disable peer SSL certificate verification. See
+L<http://curl.haxx.se/docs/sslcerts.html> for details. Enabled by default.
+
+=item B<VerifyHost> B<true>|B<false>
+
+Enable or disable peer host name verification. If enabled, the plugin checks if
+the C<Common Name> or a C<Subject Alternate Name> field of the SSL certificate
+matches the host name provided by the B<URL> option. If this identity check
+fails, the connection is aborted. Obviously, only works when connecting to a
+SSL enabled server. Enabled by default.
+
+=item B<CACert> I<file>
+
+File that holds one or more SSL certificates. If you want to use HTTPS you will
+possibly need this option. What CA certificates come bundled with C<libcurl>
+and are checked by default depends on the distribution you use.
+
+=item B<MeasureResponseTime> B<true>|B<false>
+
+Measure response time for the request. If this setting is enabled, B<Match>
+blocks (see below) are optional. Disabled by default.
+
+=item B<E<lt>MatchE<gt>>
+
+One or more B<Match> blocks that define how to match information in the data
+returned by C<libcurl>. The C<curl> plugin uses the same infrastructure that's
+used by the C<tail> plugin, so please see the documentation of the C<tail>
+plugin below on how matches are defined. If the B<MeasureResponseTime> option
+is set to B<true>, B<Match> blocks are optional.
+
+=back
+
+=head2 Plugin C<curl_json>
+
+The B<curl_json plugin> uses B<libcurl> (L<http://curl.haxx.se/>) and
+B<libyajl> (L<http://www.lloydforge.org/projects/yajl/>) to retrieve JSON data
+via cURL. This can be used to collect values from CouchDB documents (which are
+stored JSON notation), for example.
+
+The following example will collect several values from the built-in `_stats'
+runtime statistics module of CouchDB
+(L<http://wiki.apache.org/couchdb/Runtime_Statistics>).
+
+  <Plugin curl_json>
+    <URL "http://localhost:5984/_stats">
+      Instance "httpd"
+      <Key "httpd/requests/count">
+        Type "http_requests"
+      </Key>
+
+      <Key "httpd_request_methods/*/count">
+        Type "http_request_methods"
+      </Key>
+
+      <Key "httpd_status_codes/*/count">
+        Type "http_response_codes"
+      </Key>
+    </URL>
+  </Plugin>
+
+In the B<Plugin> block, there may be one or more B<URL> blocks, each defining
+a URL to be fetched via HTTP (using libcurl) and one or more B<Key> blocks.
+The B<Key> string argument must be in a path format, which is used to collect a
+value from a JSON map object. If a path element of B<Key> is the
+I<*>E<nbsp>wildcard, the values for all keys will be collectd.
+
+The following options are valid within B<URL> blocks:
+
+=over 4
+
+=item B<Instance> I<Instance>
+
+Sets the plugin instance to I<Instance>.
+
+=item B<User> I<Name>
+
+Username to use if authorization is required to read the page.
+
+=item B<Password> I<Password>
+
+Password to use if authorization is required to read the page.
+
+=item B<VerifyPeer> B<true>|B<false>
+
+Enable or disable peer SSL certificate verification. See
+L<http://curl.haxx.se/docs/sslcerts.html> for details. Enabled by default.
+
+=item B<VerifyHost> B<true>|B<false>
+
+Enable or disable peer host name verification. If enabled, the plugin checks if
+the C<Common Name> or a C<Subject Alternate Name> field of the SSL certificate
+matches the host name provided by the B<URL> option. If this identity check
+fails, the connection is aborted. Obviously, only works when connecting to a
+SSL enabled server. Enabled by default.
+
+=item B<CACert> I<file>
+
+File that holds one or more SSL certificates. If you want to use HTTPS you will
+possibly need this option. What CA certificates come bundled with C<libcurl>
+and are checked by default depends on the distribution you use.
+
+=back
+
+The following options are valid within B<Key> blocks:
+
+=over 4
+
+=item B<Type> I<Type>
+
+Sets the type used to dispatch the values to the daemon. Detailed information
+about types and their configuration can be found in L<types.db(5)>. This
+option is mandatory.
+
+=item B<Instance> I<Instance>
+
+Type-instance to use. Defaults to the current map key or current string array element value.
+
+=back
+
+=head2 Plugin C<curl_xml>
+
+The B<curl_xml plugin> uses B<libcurl> (L<http://curl.haxx.se/>) and B<libxml2>
+(L<http://xmlsoft.org/>) to retrieve XML data via cURL.
+
+ <Plugin "curl_xml">
+   <URL "http://localhost/stats.xml">
+     Host "my_host"
+     Instance "some_instance"
+     User "collectd"
+     Password "thaiNg0I"
+     VerifyPeer true
+     VerifyHost true
+     CACert "/path/to/ca.crt"
+
+     <XPath "table[@id=\"magic_level\"]/tr">
+       Type "magic_level"
+       #InstancePrefix "prefix-"
+       InstanceFrom "td[1]"
+       ValuesFrom "td[2]/span[@class=\"level\"]"
+     </XPath>
+   </URL>
+ </Plugin>
+
+In the B<Plugin> block, there may be one or more B<URL> blocks, each defining a
+URL to be fetched using libcurl. Within each B<URL> block there are
+options which specify the connection parameters, for example authentication
+information, and one or more B<XPath> blocks.
+
+Each B<XPath> block specifies how to get one type of information. The
+string argument must be a valid XPath expression which returns a list
+of "base elements". One value is dispatched for each "base element". The
+I<type instance> and values are looked up using further I<XPath> expressions
+that should be relative to the base element.
+
+Within the B<URL> block the following options are accepted:
+
+=over 4
+
+=item B<Host> I<Name>
+
+Use I<Name> as the host name when submitting values. Defaults to the global
+host name setting.
+
+=item B<Instance> I<Instance>
+
+Use I<Instance> as the plugin instance when submitting values. Defaults to an
+empty string (no plugin instance).
+
+=item B<User> I<User>
+=item B<Password> I<Password>
+=item B<VerifyPeer> B<true>|B<false>
+=item B<VerifyHost> B<true>|B<false>
+=item B<CACert> I<CA Cert File>
+
+These options behave exactly equivalent to the appropriate options of the
+I<cURL> and I<cURL-JSON> plugins. Please see there for a detailed description.
+
+=item E<lt>B<XPath> I<XPath-expression>E<gt>
+
+Within each B<URL> block, there must be one or more B<XPath> blocks. Each
+B<XPath> block specifies how to get one type of information. The string
+argument must be a valid XPath expression which returns a list of "base
+elements". One value is dispatched for each "base element".
+
+Within the B<XPath> block the following options are accepted:
+
+=over 4
+
+=item B<Type> I<Type>
+
+Specifies the I<Type> used for submitting patches. This determines the number
+of values that are required / expected and whether the strings are parsed as
+signed or unsigned integer or as double values. See L<types.db(5)> for details.
+This option is required.
+
+=item B<InstancePrefix> I<InstancePrefix>
+
+Prefix the I<type instance> with I<InstancePrefix>. The values are simply
+concatenated together without any separator.
+This option is optional.
+
+=item B<InstanceFrom> I<InstanceFrom>
+
+Specifies a XPath expression to use for determining the I<type instance>. The
+XPath expression must return exactly one element. The element's value is then
+used as I<type instance>, possibly prefixed with I<InstancePrefix> (see above).
+
+This value is required. As a special exception, if the "base XPath expression"
+(the argument to the B<XPath> block) returns exactly one argument, then this
+option may be omitted.
+
+=item B<ValuesFrom> I<ValuesFrom> [I<ValuesFrom> ...]
+
+Specifies one or more XPath expression to use for reading the values. The
+number of XPath expressions must match the number of data sources in the
+I<type> specified with B<Type> (see above). Each XPath expression must return
+exactly one element. The element's value is then parsed as a number and used as
+value for the appropriate value in the value list dispatched to the daemon.
+
+=back
+
+=back
+
+=head2 Plugin C<dbi>
+
+This plugin uses the B<dbi> library (L<http://libdbi.sourceforge.net/>) to
+connect to various databases, execute I<SQL> statements and read back the
+results. I<dbi> is an acronym for "database interface" in case you were
+wondering about the name. You can configure how each column is to be
+interpreted and the plugin will generate one or more data sets from each row
+returned according to these rules.
+
+Because the plugin is very generic, the configuration is a little more complex
+than those of other plugins. It usually looks something like this:
+
+  <Plugin dbi>
+    <Query "out_of_stock">
+      Statement "SELECT category, COUNT(*) AS value FROM products WHERE in_stock = 0 GROUP BY category"
+      # Use with MySQL 5.0.0 or later
+      MinVersion 50000
+      <Result>
+        Type "gauge"
+        InstancePrefix "out_of_stock"
+        InstancesFrom "category"
+        ValuesFrom "value"
+      </Result>
+    </Query>
+    <Database "product_information">
+      Driver "mysql"
+      DriverOption "host" "localhost"
+      DriverOption "username" "collectd"
+      DriverOption "password" "aZo6daiw"
+      DriverOption "dbname" "prod_info"
+      SelectDB "prod_info"
+      Query "out_of_stock"
+    </Database>
+  </Plugin>
+
+The configuration above defines one query with one result and one database. The
+query is then linked to the database with the B<Query> option I<within> the
+B<E<lt>DatabaseE<gt>> block. You can have any number of queries and databases
+and you can also use the B<Include> statement to split up the configuration
+file in multiple, smaller files. However, the B<E<lt>QueryE<gt>> block I<must>
+precede the B<E<lt>DatabaseE<gt>> blocks, because the file is interpreted from
+top to bottom!
+
+The following is a complete list of options:
+
+=head3 B<Query> blocks
+
+Query blocks define I<SQL> statements and how the returned data should be
+interpreted. They are identified by the name that is given in the opening line
+of the block. Thus the name needs to be unique. Other than that, the name is
+not used in collectd.
+
+In each B<Query> block, there is one or more B<Result> blocks. B<Result> blocks
+define which column holds which value or instance information. You can use
+multiple B<Result> blocks to create multiple values from one returned row. This
+is especially useful, when queries take a long time and sending almost the same
+query again and again is not desirable.
+
+Example:
+
+  <Query "environment">
+    Statement "select station, temperature, humidity from environment"
+    <Result>
+      Type "temperature"
+      # InstancePrefix "foo"
+      InstancesFrom "station"
+      ValuesFrom "temperature"
+    </Result>
+    <Result>
+      Type "humidity"
+      InstancesFrom "station"
+      ValuesFrom "humidity"
+    </Result>
+  </Query>
+
+The following options are accepted:
+
+=over 4
+
+=item B<Statement> I<SQL>
+
+Sets the statement that should be executed on the server. This is B<not>
+interpreted by collectd, but simply passed to the database server. Therefore,
+the SQL dialect that's used depends on the server collectd is connected to.
+
+The query has to return at least two columns, one for the instance and one
+value. You cannot omit the instance, even if the statement is guaranteed to
+always return exactly one line. In that case, you can usually specify something
+like this:
+
+  Statement "SELECT \"instance\", COUNT(*) AS value FROM table"
+
+(That works with MySQL but may not be valid SQL according to the spec. If you
+use a more strict database server, you may have to select from a dummy table or
+something.)
+
+Please note that some databases, for example B<Oracle>, will fail if you
+include a semicolon at the end of the statement.
+
+=item B<MinVersion> I<Version>
+
+=item B<MaxVersion> I<Value>
+
+Only use this query for the specified database version. You can use these
+options to provide multiple queries with the same name but with a slightly
+different syntax. The plugin will use only those queries, where the specified
+minimum and maximum versions fit the version of the database in use.
+
+The database version is determined by C<dbi_conn_get_engine_version>, see the
+L<libdbi documentation|http://libdbi.sourceforge.net/docs/programmers-guide/reference-conn.html#DBI-CONN-GET-ENGINE-VERSION>
+for details. Basically, each part of the version is assumed to be in the range
+from B<00> to B<99> and all dots are removed. So version "4.1.2" becomes
+"40102", version "5.0.42" becomes "50042".
+
+B<Warning:> The plugin will use B<all> matching queries, so if you specify
+multiple queries with the same name and B<overlapping> ranges, weird stuff will
+happen. Don't to it! A valid example would be something along these lines:
+
+  MinVersion 40000
+  MaxVersion 49999
+  ...
+  MinVersion 50000
+  MaxVersion 50099
+  ...
+  MinVersion 50100
+  # No maximum
+
+In the above example, there are three ranges that don't overlap. The last one
+goes from version "5.1.0" to infinity, meaning "all later versions". Versions
+before "4.0.0" are not specified.
+
+=item B<Type> I<Type>
+
+The B<type> that's used for each line returned. See L<types.db(5)> for more
+details on how types are defined. In short: A type is a predefined layout of
+data and the number of values and type of values has to match the type
+definition.
+
+If you specify "temperature" here, you need exactly one gauge column. If you
+specify "if_octets", you will need two counter columns. See the B<ValuesFrom>
+setting below.
+
+There must be exactly one B<Type> option inside each B<Result> block.
+
+=item B<InstancePrefix> I<prefix>
+
+Prepends I<prefix> to the type instance. If B<InstancesFrom> (see below) is not
+given, the string is simply copied. If B<InstancesFrom> is given, I<prefix> and
+all strings returned in the appropriate columns are concatenated together,
+separated by dashes I<("-")>.
+
+=item B<InstancesFrom> I<column0> [I<column1> ...]
+
+Specifies the columns whose values will be used to create the "type-instance"
+for each row. If you specify more than one column, the value of all columns
+will be joined together with dashes I<("-")> as separation characters.
+
+The plugin itself does not check whether or not all built instances are
+different. It's your responsibility to assure that each is unique. This is
+especially true, if you do not specify B<InstancesFrom>: B<You> have to make
+sure that only one row is returned in this case.
+
+If neither B<InstancePrefix> nor B<InstancesFrom> is given, the type-instance
+will be empty.
+
+=item B<ValuesFrom> I<column0> [I<column1> ...]
+
+Names the columns whose content is used as the actual data for the data sets
+that are dispatched to the daemon. How many such columns you need is determined
+by the B<Type> setting above. If you specify too many or not enough columns,
+the plugin will complain about that and no data will be submitted to the
+daemon.
+
+The actual data type in the columns is not that important. The plugin will
+automatically cast the values to the right type if it know how to do that. So
+it should be able to handle integer an floating point types, as well as strings
+(if they include a number at the beginning).
+
+There must be at least one B<ValuesFrom> option inside each B<Result> block.
+
+=back
+
+=head3 B<Database> blocks
+
+Database blocks define a connection to a database and which queries should be
+sent to that database. Since the used "dbi" library can handle a wide variety
+of databases, the configuration is very generic. If in doubt, refer to libdbi's
+documentationE<nbsp>- we stick as close to the terminology used there.
+
+Each database needs a "name" as string argument in the starting tag of the
+block. This name will be used as "PluginInstance" in the values submitted to
+the daemon. Other than that, that name is not used.
+
+=over 4
+
+=item B<Driver> I<Driver>
+
+Specifies the driver to use to connect to the database. In many cases those
+drivers are named after the database they can connect to, but this is not a
+technical necessity. These drivers are sometimes referred to as "DBD",
+B<D>ataB<B>ase B<D>river, and some distributions ship them in separate
+packages. Drivers for the "dbi" library are developed by the B<libdbi-drivers>
+project at L<http://libdbi-drivers.sourceforge.net/>.
+
+You need to give the driver name as expected by the "dbi" library here. You
+should be able to find that in the documentation for each driver. If you
+mistype the driver name, the plugin will dump a list of all known driver names
+to the log.
+
+=item B<DriverOption> I<Key> I<Value>
+
+Sets driver-specific options. What option a driver supports can be found in the
+documentation for each driver, somewhere at
+L<http://libdbi-drivers.sourceforge.net/>. However, the options "host",
+"username", "password", and "dbname" seem to be deE<nbsp>facto standards.
+
+Unfortunately, drivers are not too keen to report errors when an unknown option
+is passed to them, so invalid settings here may go unnoticed. This is not the
+plugin's fault, it will report errors if it gets them from the libraryE<nbsp>/
+the driver. If a driver complains about an option, the plugin will dump a
+complete list of all options understood by that driver to the log.
+
+=item B<SelectDB> I<Database>
+
+In some cases, the database name you connect with is not the database name you
+want to use for querying data. If this option is set, the plugin will "select"
+(switch to) that database after the connection is established.
+
+=item B<Query> I<QueryName>
+
+Associates the query named I<QueryName> with this database connection. The
+query needs to be defined I<before> this statement, i.E<nbsp>e. all query
+blocks you want to refer to must be placed above the database block you want to
+refer to them from.
+
+=back
+
+=head2 Plugin C<df>
+
+=over 4
+
+=item B<Device> I<Device>
+
+Select partitions based on the devicename.
+
+=item B<MountPoint> I<Directory>
+
+Select partitions based on the mountpoint.
+
+=item B<FSType> I<FSType>
+
+Select partitions based on the filesystem type.
+
+=item B<IgnoreSelected> B<true>|B<false>
+
+Invert the selection: If set to true, all partitions B<except> the ones that
+match any one of the criteria are collected. By default only selected
+partitions are collected if a selection is made. If no selection is configured
+at all, B<all> partitions are selected.
+
+=item B<ReportByDevice> B<true>|B<false>
+
+Report using the device name rather than the mountpoint. i.e. with this I<false>,
+(the default), it will report a disk as "root", but with it I<true>, it will be
+"sda1" (or whichever).
+
+=item B<ReportInodes> B<true>|B<false>
+
+Enables or disables reporting of free, reserved and used inodes. Defaults to
+inode collection being disabled.
+
+Enable this option if inodes are a scarce resource for you, usually because
+many small files are stored on the disk. This is a usual scenario for mail
+transfer agents and web caches.
+
+=back
+
+=head2 Plugin C<disk>
+
+The C<disk> plugin collects information about the usage of physical disks and
+logical disks (partitions). Values collected are the number of octets written
+to and read from a disk or partition, the number of read/write operations
+issued to the disk and a rather complex "time" it took for these commands to be
+issued.
+
+Using the following two options you can ignore some disks or configure the
+collection only of specific disks.
+
+=over 4
+
+=item B<Disk> I<Name>
+
+Select the disk I<Name>. Whether it is collected or ignored depends on the
+B<IgnoreSelected> setting, see below. As with other plugins that use the
+daemon's ignorelist functionality, a string that starts and ends with a slash
+is interpreted as a regular expression. Examples:
+
+  Disk "sdd"
+  Disk "/hda[34]/"
+
+=item B<IgnoreSelected> B<true>|B<false>
+
+Sets whether selected disks, i.E<nbsp>e. the ones matches by any of the B<Disk>
+statements, are ignored or if all other disks are ignored. The behavior
+(hopefully) is intuitive: If no B<Disk> option is configured, all disks are
+collected. If at least one B<Disk> option is given and no B<IgnoreSelected> or
+set to B<false>, B<only> matching disks will be collected. If B<IgnoreSelected>
+is set to B<true>, all disks are collected B<except> the ones matched.
+
+=back
+
+=head2 Plugin C<dns>
+
+=over 4
+
+=item B<Interface> I<Interface>
+
+The dns plugin uses B<libpcap> to capture dns traffic and analyzes it. This
+option sets the interface that should be used. If this option is not set, or
+set to "any", the plugin will try to get packets from B<all> interfaces. This
+may not work on certain platforms, such as MacE<nbsp>OSE<nbsp>X.
+
+=item B<IgnoreSource> I<IP-address>
+
+Ignore packets that originate from this address.
+
+=item B<SelectNumericQueryTypes> B<true>|B<false>
+
+Enabled by default, collects unknown (and thus presented as numeric only) query types.
+
+=back
+
+=head2 Plugin C<email>
+
+=over 4
+
+=item B<SocketFile> I<Path>
+
+Sets the socket-file which is to be created.
+
+=item B<SocketGroup> I<Group>
+
+If running as root change the group of the UNIX-socket after it has been
+created. Defaults to B<collectd>.
+
+=item B<SocketPerms> I<Permissions>
+
+Change the file permissions of the UNIX-socket after it has been created. The
+permissions must be given as a numeric, octal value as you would pass to
+L<chmod(1)>. Defaults to B<0770>.
+
+=item B<MaxConns> I<Number>
+
+Sets the maximum number of connections that can be handled in parallel. Since
+this many threads will be started immediately setting this to a very high
+value will waste valuable resources. Defaults to B<5> and will be forced to be
+at most B<16384> to prevent typos and dumb mistakes.
+
+=back
+
+=head2 Plugin C<exec>
+
+Please make sure to read L<collectd-exec(5)> before using this plugin. It
+contains valuable information on when the executable is executed and the
+output that is expected from it.
+
+=over 4
+
+=item B<Exec> I<User>[:[I<Group>]] I<Executable> [I<E<lt>argE<gt>> [I<E<lt>argE<gt>> ...]]
+
+=item B<NotificationExec> I<User>[:[I<Group>]] I<Executable> [I<E<lt>argE<gt>> [I<E<lt>argE<gt>> ...]]
+
+Execute the executable I<Executable> as user I<User>. If the user name is
+followed by a colon and a group name, the effective group is set to that group.
+The real group and saved-set group will be set to the default group of that
+user. If no group is given the effective group ID will be the same as the real
+group ID.
+
+Please note that in order to change the user and/or group the daemon needs
+superuser privileges. If the daemon is run as an unprivileged user you must
+specify the same user/group here. If the daemon is run with superuser
+privileges, you must supply a non-root user here.
+
+The executable may be followed by optional arguments that are passed to the
+program. Please note that due to the configuration parsing numbers and boolean
+values may be changed. If you want to be absolutely sure that something is
+passed as-is please enclose it in quotes.
+
+The B<Exec> and B<NotificationExec> statements change the semantics of the
+programs executed, i.E<nbsp>e. the data passed to them and the response
+expected from them. This is documented in great detail in L<collectd-exec(5)>.
+
+=back
+
+=head2 Plugin C<filecount>
+
+The C<filecount> plugin counts the number of files in a certain directory (and
+its subdirectories) and their combined size. The configuration is very straight
+forward:
+
+  <Plugin "filecount">
+    <Directory "/var/qmail/queue/mess">
+      Instance "qmail-message"
+    </Directory>
+    <Directory "/var/qmail/queue/todo">
+      Instance "qmail-todo"
+    </Directory>
+    <Directory "/var/lib/php5">
+      Instance "php5-sessions"
+      Name "sess_*"
+    </Directory>
+  </Plugin>
+
+The example above counts the number of files in QMail's queue directories and
+the number of PHP5 sessions. Jfiy: The "todo" queue holds the messages that
+QMail has not yet looked at, the "message" queue holds the messages that were
+classified into "local" and "remote".
+
+As you can see, the configuration consists of one or more C<Directory> blocks,
+each of which specifies a directory in which to count the files. Within those
+blocks, the following options are recognized:
+
+=over 4
+
+=item B<Instance> I<Instance>
+
+Sets the plugin instance to I<Instance>. That instance name must be unique, but
+it's your responsibility, the plugin doesn't check for that. If not given, the
+instance is set to the directory name with all slashes replaced by underscores
+and all leading underscores removed.
+
+=item B<Name> I<Pattern>
+
+Only count files that match I<Pattern>, where I<Pattern> is a shell-like
+wildcard as understood by L<fnmatch(3)>. Only the B<filename> is checked
+against the pattern, not the entire path. In case this makes it easier for you:
+This option has been named after the B<-name> parameter to L<find(1)>.
+
+=item B<MTime> I<Age>
+
+Count only files of a specific age: If I<Age> is greater than zero, only files
+that haven't been touched in the last I<Age> seconds are counted. If I<Age> is
+a negative number, this is inversed. For example, if B<-60> is specified, only
+files that have been modified in the last minute will be counted.
+
+The number can also be followed by a "multiplier" to easily specify a larger
+timespan. When given in this notation, the argument must in quoted, i.E<nbsp>e.
+must be passed as string. So the B<-60> could also be written as B<"-1m"> (one
+minute). Valid multipliers are C<s> (second), C<m> (minute), C<h> (hour), C<d>
+(day), C<w> (week), and C<y> (year). There is no "month" multiplier. You can
+also specify fractional numbers, e.E<nbsp>g. B<"0.5d"> is identical to
+B<"12h">.
+
+=item B<Size> I<Size>
+
+Count only files of a specific size. When I<Size> is a positive number, only
+files that are at least this big are counted. If I<Size> is a negative number,
+this is inversed, i.E<nbsp>e. only files smaller than the absolute value of
+I<Size> are counted.
+
+As with the B<MTime> option, a "multiplier" may be added. For a detailed
+description see above. Valid multipliers here are C<b> (byte), C<k> (kilobyte),
+C<m> (megabyte), C<g> (gigabyte), C<t> (terabyte), and C<p> (petabyte). Please
+note that there are 1000 bytes in a kilobyte, not 1024.
+
+=item B<Recursive> I<true>|I<false>
+
+Controls whether or not to recurse into subdirectories. Enabled by default.
+
+=item B<IncludeHidden> I<true>|I<false>
+
+Controls whether or not to include "hidden" files and directories in the count.
+"Hidden" files and directories are those, whose name begins with a dot.
+Defaults to I<false>, i.e. by default hidden files and directories are ignored.
+
+=back
+
+=head2 Plugin C<GenericJMX>
+
+The I<GenericJMX plugin> is written in I<Java> and therefore documented in
+L<collectd-java(5)>.
+
+=head2 Plugin C<gmond>
+
+The I<gmond> plugin received the multicast traffic sent by B<gmond>, the
+statistics collection daemon of Ganglia. Mappings for the standard "metrics"
+are built-in, custom mappings may be added via B<Metric> blocks, see below.
+
+Synopsis:
+
+ <Plugin "gmond">
+   MCReceiveFrom "239.2.11.71" "8649"
+   <Metric "swap_total">
+     Type "swap"
+     TypeInstance "total"
+     DataSource "value"
+   </Metric>
+   <Metric "swap_free">
+     Type "swap"
+     TypeInstance "free"
+     DataSource "value"
+   </Metric>
+ </Plugin>
+
+The following metrics are built-in:
+
+=over 4
+
+=item *
+
+load_one, load_five, load_fifteen
+
+=item *
+
+cpu_user, cpu_system, cpu_idle, cpu_nice, cpu_wio
+
+=item *
+
+mem_free, mem_shared, mem_buffers, mem_cached, mem_total
+
+=item *
+
+bytes_in, bytes_out
+
+=item *
+
+pkts_in, pkts_out
+
+=back
+
+Available configuration options:
+
+=over 4
+
+=item B<MCReceiveFrom> I<MCGroup> [I<Port>]
+
+Sets sets the multicast group and UDP port to which to subscribe.
+
+Default: B<239.2.11.71>E<nbsp>/E<nbsp>B<8649>
+
+=item E<lt>B<Metric> I<Name>E<gt>
+
+These blocks add a new metric conversion to the internal table. I<Name>, the
+string argument to the B<Metric> block, is the metric name as used by Ganglia.
+
+=over 4
+
+=item B<Type> I<Type>
+
+Type to map this metric to. Required.
+
+=item B<TypeInstance> I<Instance>
+
+Type-instance to use. Optional.
+
+=item B<DataSource> I<Name>
+
+Data source to map this metric to. If the configured type has exactly one data
+source, this is optional. Otherwise the option is required.
+
+=back
+
+=back
+
+=head2 Plugin C<hddtemp>
+
+To get values from B<hddtemp> collectd connects to B<localhost> (127.0.0.1),
+port B<7634/tcp>. The B<Host> and B<Port> options can be used to change these
+default values, see below. C<hddtemp> has to be running to work correctly. If
+C<hddtemp> is not running timeouts may appear which may interfere with other
+statistics..
+
+The B<hddtemp> homepage can be found at
+L<http://www.guzu.net/linux/hddtemp.php>.
+
+=over 4
+
+=item B<Host> I<Hostname>
+
+Hostname to connect to. Defaults to B<127.0.0.1>.
+
+=item B<Port> I<Port>
+
+TCP-Port to connect to. Defaults to B<7634>.
+
+=back
+
+=head2 Plugin C<interface>
+
+=over 4
+
+=item B<Interface> I<Interface>
+
+Select this interface. By default these interfaces will then be collected. For
+a more detailed description see B<IgnoreSelected> below.
+
+=item B<IgnoreSelected> I<true>|I<false>
+
+If no configuration if given, the B<traffic>-plugin will collect data from
+all interfaces. This may not be practical, especially for loopback- and
+similar interfaces. Thus, you can use the B<Interface>-option to pick the
+interfaces you're interested in. Sometimes, however, it's easier/preferred
+to collect all interfaces I<except> a few ones. This option enables you to
+do that: By setting B<IgnoreSelected> to I<true> the effect of
+B<Interface> is inverted: All selected interfaces are ignored and all
+other interfaces are collected.
+
+=back
+
+=head2 Plugin C<ipmi>
+
+=over 4
+
+=item B<Sensor> I<Sensor>
+
+Selects sensors to collect or to ignore, depending on B<IgnoreSelected>.
+
+=item B<IgnoreSelected> I<true>|I<false>
+
+If no configuration if given, the B<ipmi> plugin will collect data from all
+sensors found of type "temperature", "voltage", "current" and "fanspeed".
+This option enables you to do that: By setting B<IgnoreSelected> to I<true>
+the effect of B<Sensor> is inverted: All selected sensors are ignored and
+all other sensors are collected.
+
+=item B<NotifySensorAdd> I<true>|I<false>
+
+If a sensor appears after initialization time of a minute a notification
+is sent.
+
+=item B<NotifySensorRemove> I<true>|I<false>
+
+If a sensor disappears a notification is sent.
+
+=item B<NotifySensorNotPresent> I<true>|I<false>
+
+If you have for example dual power supply and one of them is (un)plugged then
+a notification is sent.
+
+=back
+
+=head2 Plugin C<iptables>
+
+=over 4
+
+=item B<Chain> I<Table> I<Chain> [I<Comment|Number> [I<Name>]]
+
+Select the rules to count. If only I<Table> and I<Chain> are given, this plugin
+will collect the counters of all rules which have a comment-match. The comment
+is then used as type-instance.
+
+If I<Comment> or I<Number> is given, only the rule with the matching comment or
+the I<n>th rule will be collected. Again, the comment (or the number) will be
+used as the type-instance.
+
+If I<Name> is supplied, it will be used as the type-instance instead of the
+comment or the number.
+
+=back
+
+=head2 Plugin C<irq>
+
+=over 4
+
+=item B<Irq> I<Irq>
+
+Select this irq. By default these irqs will then be collected. For a more
+detailed description see B<IgnoreSelected> below.
+
+=item B<IgnoreSelected> I<true>|I<false>
+
+If no configuration if given, the B<irq>-plugin will collect data from all
+irqs. This may not be practical, especially if no interrupts happen. Thus, you
+can use the B<Irq>-option to pick the interrupt you're interested in.
+Sometimes, however, it's easier/preferred to collect all interrupts I<except> a
+few ones. This option enables you to do that: By setting B<IgnoreSelected> to
+I<true> the effect of B<Irq> is inverted: All selected interrupts are ignored
+and all other interrupts are collected.
+
+=back
+
+=head2 Plugin C<java>
+
+The I<Java> plugin makes it possible to write extensions for collectd in Java.
+This section only discusses the syntax and semantic of the configuration
+options. For more in-depth information on the I<Java> plugin, please read
+L<collectd-java(5)>.
+
+Synopsis:
+
+ <Plugin "java">
+   JVMArg "-verbose:jni"
+   JVMArg "-Djava.class.path=/opt/collectd/lib/collectd/bindings/java"
+   LoadPlugin "org.collectd.java.Foobar"
+   <Plugin "org.collectd.java.Foobar">
+     # To be parsed by the plugin
+   </Plugin>
+ </Plugin>
+
+Available configuration options:
+
+=over 4
+
+=item B<JVMArg> I<Argument>
+
+Argument that is to be passed to the I<Java Virtual Machine> (JVM). This works
+exactly the way the arguments to the I<java> binary on the command line work.
+Execute C<javaE<nbsp>--help> for details.
+
+Please note that B<all> these options must appear B<before> (i.E<nbsp>e. above)
+any other options! When another option is found, the JVM will be started and
+later options will have to be ignored!
+
+=item B<LoadPlugin> I<JavaClass>
+
+Instantiates a new I<JavaClass> object. The constructor of this object very
+likely then registers one or more callback methods with the server.
+
+See L<collectd-java(5)> for details.
+
+When the first such option is found, the virtual machine (JVM) is created. This
+means that all B<JVMArg> options must appear before (i.E<nbsp>e. above) all
+B<LoadPlugin> options!
+
+=item B<Plugin> I<Name>
+
+The entire block is passed to the Java plugin as an
+I<org.collectd.api.OConfigItem> object.
+
+For this to work, the plugin has to register a configuration callback first,
+see L<collectd-java(5)/"config callback">. This means, that the B<Plugin> block
+must appear after the appropriate B<LoadPlugin> block. Also note, that I<Name>
+depends on the (Java) plugin registering the callback and is completely
+independent from the I<JavaClass> argument passed to B<LoadPlugin>.
+
+=back
+
+=head2 Plugin C<libvirt>
+
+This plugin allows CPU, disk and network load to be collected for virtualized
+guests on the machine. This means that these characteristics can be collected
+for guest systems without installing any software on them - collectd only runs
+on the hosting system. The statistics are collected through libvirt
+(L<http://libvirt.org/>).
+
+Only I<Connection> is required.
+
+=over 4
+
+=item B<Connection> I<uri>
+
+Connect to the hypervisor given by I<uri>. For example if using Xen use:
+
+ Connection "xen:///"
+
+Details which URIs allowed are given at L<http://libvirt.org/uri.html>.
+
+=item B<RefreshInterval> I<seconds>
+
+Refresh the list of domains and devices every I<seconds>. The default is 60
+seconds. Setting this to be the same or smaller than the I<Interval> will cause
+the list of domains and devices to be refreshed on every iteration.
+
+Refreshing the devices in particular is quite a costly operation, so if your
+virtualization setup is static you might consider increasing this. If this
+option is set to 0, refreshing is disabled completely.
+
+=item B<Domain> I<name>
+
+=item B<BlockDevice> I<name:dev>
+
+=item B<InterfaceDevice> I<name:dev>
+
+=item B<IgnoreSelected> I<true>|I<false>
+
+Select which domains and devices are collected.
+
+If I<IgnoreSelected> is not given or I<false> then only the listed domains and
+disk/network devices are collected.
+
+If I<IgnoreSelected> is I<true> then the test is reversed and the listed
+domains and disk/network devices are ignored, while the rest are collected.
+
+The domain name and device names may use a regular expression, if the name is
+surrounded by I</.../> and collectd was compiled with support for regexps.
+
+The default is to collect statistics for all domains and all their devices.
+
+Example:
+
+ BlockDevice "/:hdb/"
+ IgnoreSelected "true"
+
+Ignore all I<hdb> devices on any domain, but other block devices (eg. I<hda>)
+will be collected.
+
+=item B<HostnameFormat> B<name|uuid|hostname|...>
+
+When the libvirt plugin logs data, it sets the hostname of the collected data
+according to this setting. The default is to use the guest name as provided by
+the hypervisor, which is equal to setting B<name>.
+
+B<uuid> means use the guest's UUID. This is useful if you want to track the
+same guest across migrations.
+
+B<hostname> means to use the global B<Hostname> setting, which is probably not
+useful on its own because all guests will appear to have the same name.
+
+You can also specify combinations of these fields. For example B<name uuid>
+means to concatenate the guest name and UUID (with a literal colon character
+between, thus I<"foo:1234-1234-1234-1234">).
+
+=item B<InterfaceFormat> B<name>|B<address>
+
+When the libvirt plugin logs interface data, it sets the name of the collected
+data according to this setting. The default is to use the path as provided by
+the hypervisor (the "dev" property of the target node), which is equal to
+setting B<name>.
+
+B<address> means use the interface's mac address. This is useful since the
+interface path might change between reboots of a guest or across migrations.
+
+=back
+
+=head2 Plugin C<logfile>
+
+=over 4
+
+=item B<LogLevel> B<debug|info|notice|warning|err>
+
+Sets the log-level. If, for example, set to B<notice>, then all events with
+severity B<notice>, B<warning>, or B<err> will be written to the logfile.
+
+Please note that B<debug> is only available if collectd has been compiled with
+debugging support.
+
+=item B<File> I<File>
+
+Sets the file to write log messages to. The special strings B<stdout> and
+B<stderr> can be used to write to the standard output and standard error
+channels, respectively. This, of course, only makes much sense when I<collectd>
+is running in foreground- or non-daemon-mode.
+
+=item B<Timestamp> B<true>|B<false>
+
+Prefix all lines printed by the current time. Defaults to B<true>.
+
+=item B<PrintSeverity> B<true>|B<false>
+
+When enabled, all lines are prefixed by the severity of the log message, for
+example "warning". Defaults to B<false>.
+
+=back
+
+B<Note>: There is no need to notify the daemon after moving or removing the
+log file (e.E<nbsp>g. when rotating the logs). The plugin reopens the file
+for each line it writes.
+
+=head2 Plugin C<lpar>
+
+The I<LPAR plugin> reads CPU statistics of I<Logical Partitions>, a
+virtualization technique for IBM POWER processors. It takes into account CPU
+time stolen from or donated to a partition, in addition to the usual user,
+system, I/O statistics.
+
+The following configuration options are available:
+
+=over 4
+
+=item B<CpuPoolStats> B<false>|B<true>
+
+When enabled, statistics about the processor pool are read, too. The partition
+needs to have pool authority in order to be able to acquire this information.
+Defaults to false.
+
+=item B<ReportBySerial> B<false>|B<true>
+
+If enabled, the serial of the physical machine the partition is currently
+running on is reported as I<hostname> and the logical hostname of the machine
+is reported in the I<plugin instance>. Otherwise, the logical hostname will be
+used (just like other plugins) and the I<plugin instance> will be empty.
+Defaults to false.
+
+=back
+
+=head2 Plugin C<mbmon>
+
+The C<mbmon plugin> uses mbmon to retrieve temperature, voltage, etc.
+
+Be default collectd connects to B<localhost> (127.0.0.1), port B<411/tcp>. The
+B<Host> and B<Port> options can be used to change these values, see below.
+C<mbmon> has to be running to work correctly. If C<mbmon> is not running
+timeouts may appear which may interfere with other statistics..
+
+C<mbmon> must be run with the -r option ("print TAG and Value format");
+Debian's F</etc/init.d/mbmon> script already does this, other people
+will need to ensure that this is the case.
+
+=over 4
+
+=item B<Host> I<Hostname>
+
+Hostname to connect to. Defaults to B<127.0.0.1>.
+
+=item B<Port> I<Port>
+
+TCP-Port to connect to. Defaults to B<411>.
+
+=back
+
+=head2 Plugin C<memcachec>
+
+The C<memcachec plugin> connects to a memcached server, queries one or more
+given I<pages> and parses the returned data according to user specification.
+The I<matches> used are the same as the matches used in the C<curl> and C<tail>
+plugins.
+
+In order to talk to the memcached server, this plugin uses the I<libmemcached>
+library. Please note that there is another library with a very similar name,
+libmemcache (notice the missing `d'), which is not applicable.
+
+Synopsis of the configuration:
+
+ <Plugin "memcachec">
+   <Page "plugin_instance">
+     Server "localhost"
+     Key "page_key"
+     <Match>
+       Regex "(\\d+) bytes sent"
+       DSType CounterAdd
+       Type "ipt_octets"
+       Instance "type_instance"
+     </Match>
+   </Page>
+ </Plugin>
+
+The configuration options are:
+
+=over 4
+
+=item E<lt>B<Page> I<Name>E<gt>
+
+Each B<Page> block defines one I<page> to be queried from the memcached server.
+The block requires one string argument which is used as I<plugin instance>.
+
+=item B<Server> I<Address>
+
+Sets the server address to connect to when querying the page. Must be inside a
+B<Page> block.
+
+=item B<Key> I<Key>
+
+When connected to the memcached server, asks for the page I<Key>.
+
+=item E<lt>B<Match>E<gt>
+
+Match blocks define which strings to look for and how matches substrings are
+interpreted. For a description of match blocks, please see L<"Plugin tail">.
+
+=back
+
+=head2 Plugin C<memcached>
+
+The C<memcached plugin> connects to a memcached server and queries statistics
+about cache utilization, memory and bandwidth used.
+L<http://www.danga.com/memcached/>
+
+=over 4
+
+=item B<Host> I<Hostname>
+
+Hostname to connect to. Defaults to B<127.0.0.1>.
+
+=item B<Port> I<Port>
+
+TCP-Port to connect to. Defaults to B<11211>.
+
+=back
+
+=head2 Plugin C<modbus>
+
+The B<modbus plugin> connects to a Modbus "slave" via Modbus/TCP and reads
+register values. It supports reading single registers (unsigned 16E<nbsp>bit
+values), large integer values (unsigned 32E<nbsp>bit values) and floating point
+values (two registers interpreted as IEEE floats in big endian notation).
+
+Synopsis:
+
+ <Data "voltage-input-1">
+   RegisterBase 0
+   RegisterType float
+   Type voltage
+   Instance "input-1"
+ </Data>
+ <Data "voltage-input-2">
+   RegisterBase 2
+   RegisterType float
+   Type voltage
+   Instance "input-2"
+ </Data>
+ <Host "modbus.example.com">
+   Address "192.168.0.42"
+   Port    "502"
+   Interval 60
+   
+   <Slave 1>
+     Instance "power-supply"
+     Collect  "voltage-input-1"
+     Collect  "voltage-input-2"
+   </Slave>
+ </Host>
+
+=over 4
+
+=item E<lt>B<Data> I<Name>E<gt> blocks
+
+Data blocks define a mapping between register numbers and the "types" used by
+I<collectd>.
+
+Within E<lt>DataE<nbsp>/E<gt> blocks, the following options are allowed:
+
+=over 4
+
+=item B<RegisterBase> I<Number>
+
+Configures the base register to read from the device. If the option
+B<RegisterType> has been set to B<Uint32> or B<Float>, this and the next
+register will be read (the register number is increased by one).
+
+=item B<RegisterType> B<Int16>|B<Int32>|B<Uint16>|B<Uint32>|B<Float>
+
+Specifies what kind of data is returned by the device. If the type is B<Int32>,
+B<Uint32> or B<Float>, two 16E<nbsp>bit registers will be read and the data is
+combined into one value. Defaults to B<Uint16>.
+
+=item B<Type> I<Type>
+
+Specifies the "type" (data set) to use when dispatching the value to
+I<collectd>. Currently, only data sets with exactly one data source are
+supported.
+
+=item B<Instance> I<Instance>
+
+Sets the type instance to use when dispatching the value to I<collectd>. If
+unset, an empty string (no type instance) is used.
+
+=back
+
+=item E<lt>B<Host> I<Name>E<gt> blocks
+
+Host blocks are used to specify to which hosts to connect and what data to read
+from their "slaves". The string argument I<Name> is used as hostname when
+dispatching the values to I<collectd>.
+
+Within E<lt>HostE<nbsp>/E<gt> blocks, the following options are allowed:
+
+=over 4
+
+=item B<Address> I<Hostname>
+
+Specifies the node name (the actual network address) used to connect to the
+host. This may be an IP address or a hostname. Please note that the used
+I<libmodbus> library only supports IPv4 at the moment.
+
+=item B<Port> I<Service>
+
+Specifies the port used to connect to the host. The port can either be given as
+a number or as a service name. Please note that the I<Service> argument must be
+a string, even if ports are given in their numerical form. Defaults to "502".
+
+=item B<Interval> I<Interval>
+
+Sets the interval (in seconds) in which the values will be collected from this
+host. By default the global B<Interval> setting will be used.
+
+=item E<lt>B<Slave> I<ID>E<gt>
+
+Over each TCP connection, multiple Modbus devices may be reached. The slave ID
+is used to specify which device should be addressed. For each device you want
+to query, one B<Slave> block must be given.
+
+Within E<lt>SlaveE<nbsp>/E<gt> blocks, the following options are allowed:
+
+=over 4
+
+=item B<Instance> I<Instance>
+
+Specify the plugin instance to use when dispatching the values to I<collectd>.
+By default "slave_I<ID>" is used.
+
+=item B<Collect> I<DataName>
+
+Specifies which data to retrieve from the device. I<DataName> must be the same
+string as the I<Name> argument passed to a B<Data> block. You can specify this
+option multiple times to collect more than one value from a slave. At least one
+B<Collect> option is mandatory.
+
+=back
+
+=back
+
+=back
+
+=head2 Plugin C<mysql>
+
+The C<mysql plugin> requires B<mysqlclient> to be installed. It connects to
+one or more databases when started and keeps the connection up as long as
+possible. When the connection is interrupted for whatever reason it will try
+to re-connect. The plugin will complain loudly in case anything goes wrong.
+
+This plugin issues the MySQL C<SHOW STATUS> / C<SHOW GLOBAL STATUS> command
+and collects information about MySQL network traffic, executed statements,
+requests, the query cache and threads by evaluating the
+C<Bytes_{received,sent}>, C<Com_*>, C<Handler_*>, C<Qcache_*> and C<Threads_*>
+return values. Please refer to the B<MySQL reference manual>, I<5.1.6. Server
+Status Variables> for an explanation of these values.
+
+Optionally, master and slave statistics may be collected in a MySQL
+replication setup. In that case, information about the synchronization state
+of the nodes are collected by evaluating the C<Position> return value of the
+C<SHOW MASTER STATUS> command and the C<Seconds_Behind_Master>,
+C<Read_Master_Log_Pos> and C<Exec_Master_Log_Pos> return values of the
+C<SHOW SLAVE STATUS> command. See the B<MySQL reference manual>,
+I<12.5.5.21 SHOW MASTER STATUS Syntax> and
+I<12.5.5.31 SHOW SLAVE STATUS Syntax> for details.
+
+Synopsis:
+
+  <Plugin mysql>
+    <Database foo>
+      Host "hostname"
+      User "username"
+      Password "password"
+      Port "3306"
+      MasterStats true
+    </Database>
+
+    <Database bar>
+      Host "localhost"
+      Socket "/var/run/mysql/mysqld.sock"
+      SlaveStats true
+      SlaveNotifications true
+    </Database>
+  </Plugin>
+
+A B<Database> block defines one connection to a MySQL database. It accepts a
+single argument which specifies the name of the database. None of the other
+options are required. MySQL will use default values as documented in the
+section "mysql_real_connect()" in the B<MySQL reference manual>.
+
+=over 4
+
+=item B<Host> I<Hostname>
+
+Hostname of the database server. Defaults to B<localhost>.
+
+=item B<User> I<Username>
+
+Username to use when connecting to the database. The user does not have to be
+granted any privileges (which is synonym to granting the C<USAGE> privilege),
+unless you want to collectd replication statistics (see B<MasterStats> and
+B<SlaveStats> below). In this case, the user needs the C<REPLICATION CLIENT>
+(or C<SUPER>) privileges. Else, any existing MySQL user will do.
+
+=item B<Password> I<Password>
+
+Password needed to log into the database.
+
+=item B<Database> I<Database>
+
+Select this database. Defaults to I<no database> which is a perfectly reasonable
+option for what this plugin does.
+
+=item B<Port> I<Port>
+
+TCP-port to connect to. The port must be specified in its numeric form, but it
+must be passed as a string nonetheless. For example:
+
+  Port "3306"
+
+If B<Host> is set to B<localhost> (the default), this setting has no effect.
+See the documentation for the C<mysql_real_connect> function for details.
+
+=item B<Socket> I<Socket>
+
+Specifies the path to the UNIX domain socket of the MySQL server. This option
+only has any effect, if B<Host> is set to B<localhost> (the default).
+Otherwise, use the B<Port> option above. See the documentation for the
+C<mysql_real_connect> function for details.
+
+=item B<MasterStats> I<true|false>
+
+=item B<SlaveStats> I<true|false>
+
+Enable the collection of master / slave statistics in a replication setup. In
+order to be able to get access to these statistics, the user needs special
+privileges. See the B<User> documentation above.
+
+=item B<SlaveNotifications> I<true|false>
+
+If enabled, the plugin sends a notification if the replication slave I/O and /
+or SQL threads are not running.
+
+=back
+
+=head2 Plugin C<netapp>
+
+The netapp plugin can collect various performance and capacity information
+from a NetApp filer using the NetApp API.
+
+Please note that NetApp has a wide line of products and a lot of different
+software versions for each of these products. This plugin was developed for a
+NetApp FAS3040 running OnTap 7.2.3P8 and tested on FAS2050 7.3.1.1L1,
+FAS3140 7.2.5.1 and FAS3020 7.2.4P9. It I<should> work for most combinations of
+model and software version but it is very hard to test this.
+If you have used this plugin with other models and/or software version, feel
+free to send us a mail to tell us about the results, even if it's just a short
+"It works".
+
+To collect these data collectd will log in to the NetApp via HTTP(S) and HTTP
+basic authentication.
+
+B<Do not use a regular user for this!> Create a special collectd user with just
+the minimum of capabilities needed. The user only needs the "login-http-admin"
+capability as well as a few more depending on which data will be collected.
+Required capabilities are documented below.
+
+=head3 Synopsis
+
+ <Plugin "netapp">
+   <Host "netapp1.example.com">
+    Protocol      "https"
+    Address       "10.0.0.1"
+    Port          443
+    User          "username"
+    Password      "aef4Aebe"
+    Interval      30
+    
+    <WAFL>
+      Interval 30
+      GetNameCache   true
+      GetDirCache    true
+      GetBufferCache true
+      GetInodeCache  true
+    </WAFL>
+    
+    <Disks>
+      Interval 30
+      GetBusy true
+    </Disks>
+    
+    <VolumePerf>
+      Interval 30
+      GetIO      "volume0"
+      IgnoreSelectedIO      false
+      GetOps     "volume0"
+      IgnoreSelectedOps     false
+      GetLatency "volume0"
+      IgnoreSelectedLatency false
+    </VolumePerf>
+    
+    <VolumeUsage>
+      Interval 30
+      GetCapacity "vol0"
+      GetCapacity "vol1"
+      IgnoreSelectedCapacity false
+      GetSnapshot "vol1"
+      GetSnapshot "vol3"
+      IgnoreSelectedSnapshot false
+    </VolumeUsage>
+    
+    <System>
+      Interval 30
+      GetCPULoad     true
+      GetInterfaces  true
+      GetDiskOps     true
+      GetDiskIO      true
+    </System>
+   </Host>
+ </Plugin>
+
+The netapp plugin accepts the following configuration options:
+
+=over 4
+
+=item B<Host> I<Name>
+
+A host block defines one NetApp filer. It will appear in collectd with the name
+you specify here which does not have to be its real name nor its hostname.
+
+=item B<Protocol> B<httpd>|B<http>
+
+The protocol collectd will use to query this host.
+
+Optional
+
+Type: string
+
+Default: https
+
+Valid options: http, https
+
+=item B<Address> I<Address>
+
+The hostname or IP address of the host.
+
+Optional
+
+Type: string
+
+Default: The "host" block's name.
+
+=item B<Port> I<Port>
+
+The TCP port to connect to on the host.
+
+Optional
+
+Type: integer
+
+Default: 80 for protocol "http", 443 for protocol "https"
+
+=item B<User> I<User>
+
+=item B<Password> I<Password>
+
+The username and password to use to login to the NetApp.
+
+Mandatory
+
+Type: string
+
+=item B<Interval> I<Interval>
+
+B<TODO>
+
+=back
+
+The following options decide what kind of data will be collected. You can
+either use them as a block and fine tune various parameters inside this block,
+use them as a single statement to just accept all default values, or omit it to
+not collect any data.
+
+The following options are valid inside all blocks:
+
+=over 4
+
+=item B<Interval> I<Seconds>
+
+Collect the respective statistics every I<Seconds> seconds. Defaults to the
+host specific setting.
+
+=back
+
+=head3 The System block
+
+This will collect various performance data about the whole system.
+
+B<Note:> To get this data the collectd user needs the
+"api-perf-object-get-instances" capability.
+
+=over 4
+
+=item B<Interval> I<Seconds>
+
+Collect disk statistics every I<Seconds> seconds.
+
+=item B<GetCPULoad> B<true>|B<false>
+
+If you set this option to true the current CPU usage will be read. This will be
+the average usage between all CPUs in your NetApp without any information about
+individual CPUs.
+
+B<Note:> These are the same values that the NetApp CLI command "sysstat"
+returns in the "CPU" field.
+
+Optional
+
+Type: boolean
+
+Default: true
+
+Result: Two value lists of type "cpu", and type instances "idle" and "system".
+
+=item B<GetInterfaces> B<true>|B<false>
+
+If you set this option to true the current traffic of the network interfaces
+will be read. This will be the total traffic over all interfaces of your NetApp
+without any information about individual interfaces.
+
+B<Note:> This is the same values that the NetApp CLI command "sysstat" returns
+in the "Net kB/s" field.
+
+B<Or is it?>
+
+Optional
+
+Type: boolean
+
+Default: true
+
+Result: One value list of type "if_octects".
+
+=item B<GetDiskIO> B<true>|B<false>
+
+If you set this option to true the current IO throughput will be read. This
+will be the total IO of your NetApp without any information about individual
+disks, volumes or aggregates.
+
+B<Note:> This is the same values that the NetApp CLI command "sysstat" returns
+in the "DiskE<nbsp>kB/s" field.
+
+Optional
+
+Type: boolean
+
+Default: true
+
+Result: One value list of type "disk_octets".
+
+=item B<GetDiskOps> B<true>|B<false>
+
+If you set this option to true the current number of HTTP, NFS, CIFS, FCP,
+iSCSI, etc. operations will be read. This will be the total number of
+operations on your NetApp without any information about individual volumes or
+aggregates.
+
+B<Note:> These are the same values that the NetApp CLI command "sysstat"
+returns in the "NFS", "CIFS", "HTTP", "FCP" and "iSCSI" fields.
+
+Optional
+
+Type: boolean
+
+Default: true
+
+Result: A variable number of value lists of type "disk_ops_complex". Each type
+of operation will result in one value list with the name of the operation as
+type instance.
+
+=back
+
+=head3 The WAFL block
+
+This will collect various performance data about the WAFL file system. At the
+moment this just means cache performance.
+
+B<Note:> To get this data the collectd user needs the
+"api-perf-object-get-instances" capability.
+
+B<Note:> The interface to get these values is classified as "Diagnostics" by
+NetApp. This means that it is not guaranteed to be stable even between minor
+releases.
+
+=over 4
+
+=item B<Interval> I<Seconds>
+
+Collect disk statistics every I<Seconds> seconds.
+
+=item B<GetNameCache> B<true>|B<false>
+
+Optional
+
+Type: boolean
+
+Default: true
+
+Result: One value list of type "cache_ratio" and type instance
+"name_cache_hit".
+
+=item B<GetDirCache> B<true>|B<false>
+
+Optional
+
+Type: boolean
+
+Default: true
+
+Result: One value list of type "cache_ratio" and type instance "find_dir_hit".
+
+=item B<GetInodeCache> B<true>|B<false>
+
+Optional
+
+Type: boolean
+
+Default: true
+
+Result: One value list of type "cache_ratio" and type instance
+"inode_cache_hit".
+
+=item B<GetBufferCache> B<true>|B<false>
+
+B<Note:> This is the same value that the NetApp CLI command "sysstat" returns
+in the "Cache hit" field.
+
+Optional
+
+Type: boolean
+
+Default: true
+
+Result: One value list of type "cache_ratio" and type instance "buf_hash_hit".
+
+=back
+
+=head3 The Disks block
+
+This will collect performance data about the individual disks in the NetApp.
+
+B<Note:> To get this data the collectd user needs the
+"api-perf-object-get-instances" capability.
+
+=over 4
+
+=item B<Interval> I<Seconds>
+
+Collect disk statistics every I<Seconds> seconds.
+
+=item B<GetBusy> B<true>|B<false>
+
+If you set this option to true the busy time of all disks will be calculated
+and the value of the busiest disk in the system will be written.
+
+B<Note:> This is the same values that the NetApp CLI command "sysstat" returns
+in the "Disk util" field. Probably.
+
+Optional
+
+Type: boolean
+
+Default: true
+
+Result: One value list of type "percent" and type instance "disk_busy".
+
+=back
+
+=head3 The VolumePerf block
+
+This will collect various performance data about the individual volumes.
+
+You can select which data to collect about which volume using the following
+options. They follow the standard ignorelist semantic.
+
+B<Note:> To get this data the collectd user needs the
+I<api-perf-object-get-instances> capability.
+
+=over 4
+
+=item B<Interval> I<Seconds>
+
+Collect volume performance data every I<Seconds> seconds.
+
+=item B<GetIO> I<Volume>
+
+=item B<GetOps> I<Volume>
+
+=item B<GetLatency> I<Volume>
+
+Select the given volume for IO, operations or latency statistics collection.
+The argument is the name of the volume without the C</vol/> prefix.
+
+Since the standard ignorelist functionality is used here, you can use a string
+starting and ending with a slash to specify regular expression matching: To
+match the volumes "vol0", "vol2" and "vol7", you can use this regular
+expression:
+
+  GetIO "/^vol[027]$/"
+
+If no regular expression is specified, an exact match is required. Both,
+regular and exact matching are case sensitive.
+
+If no volume was specified at all for either of the three options, that data
+will be collected for all available volumes.
+
+=item B<IgnoreSelectedIO> B<true>|B<false>
+
+=item B<IgnoreSelectedOps> B<true>|B<false>
+
+=item B<IgnoreSelectedLatency> B<true>|B<false>
+
+When set to B<true>, the volumes selected for IO, operations or latency
+statistics collection will be ignored and the data will be collected for all
+other volumes.
+
+When set to B<false>, data will only be collected for the specified volumes and
+all other volumes will be ignored.
+
+If no volumes have been specified with the above B<Get*> options, all volumes
+will be collected regardless of the B<IgnoreSelected*> option.
+
+Defaults to B<false>
+
+=back
+
+=head3 The VolumeUsage block
+
+This will collect capacity data about the individual volumes.
+
+B<Note:> To get this data the collectd user needs the I<api-volume-list-info>
+capability.
+
+=over 4
+
+=item B<Interval> I<Seconds>
+
+Collect volume usage statistics every I<Seconds> seconds.
+
+=item B<GetCapacity> I<VolumeName>
+
+The current capacity of the volume will be collected. This will result in two
+to four value lists, depending on the configuration of the volume. All data
+sources are of type "df_complex" with the name of the volume as
+plugin_instance.
+
+There will be type_instances "used" and "free" for the number of used and
+available bytes on the volume.  If the volume has some space reserved for
+snapshots, a type_instance "snap_reserved" will be available.  If the volume
+has SIS enabled, a type_instance "sis_saved" will be available. This is the
+number of bytes saved by the SIS feature.
+
+B<Note:> The current NetApp API has a bug that results in this value being
+reported as a 32E<nbsp>bit number. This plugin tries to guess the correct
+number which works most of the time.  If you see strange values here, bug
+NetApp support to fix this.
+
+Repeat this option to specify multiple volumes.
+
+=item B<IgnoreSelectedCapacity> B<true>|B<false>
+
+Specify whether to collect only the volumes selected by the B<GetCapacity>
+option or to ignore those volumes. B<IgnoreSelectedCapacity> defaults to
+B<false>. However, if no B<GetCapacity> option is specified at all, all
+capacities will be selected anyway.
+
+=item B<GetSnapshot> I<VolumeName>
+
+Select volumes from which to collect snapshot information.
+
+Usually, the space used for snapshots is included in the space reported as
+"used". If snapshot information is collected as well, the space used for
+snapshots is subtracted from the used space.
+
+To make things even more interesting, it is possible to reserve space to be
+used for snapshots. If the space required for snapshots is less than that
+reserved space, there is "reserved free" and "reserved used" space in addition
+to "free" and "used". If the space required for snapshots exceeds the reserved
+space, that part allocated in the normal space is subtracted from the "used"
+space again.
+
+Repeat this option to specify multiple volumes.
+
+=item B<IgnoreSelectedSnapshot>
+
+Specify whether to collect only the volumes selected by the B<GetSnapshot>
+option or to ignore those volumes. B<IgnoreSelectedSnapshot> defaults to
+B<false>. However, if no B<GetSnapshot> option is specified at all, all
+capacities will be selected anyway.
+
+=back
+
+=head2 Plugin C<netlink>
+
+The C<netlink> plugin uses a netlink socket to query the Linux kernel about
+statistics of various interface and routing aspects.
+
+=over 4
+
+=item B<Interface> I<Interface>
+
+=item B<VerboseInterface> I<Interface>
+
+Instruct the plugin to collect interface statistics. This is basically the same
+as the statistics provided by the C<interface> plugin (see above) but
+potentially much more detailed.
+
+When configuring with B<Interface> only the basic statistics will be collected,
+namely octets, packets, and errors. These statistics are collected by
+the C<interface> plugin, too, so using both at the same time is no benefit.
+
+When configured with B<VerboseInterface> all counters B<except> the basic ones,
+so that no data needs to be collected twice if you use the C<interface> plugin.
+This includes dropped packets, received multicast packets, collisions and a
+whole zoo of differentiated RX and TX errors. You can try the following command
+to get an idea of what awaits you:
+
+  ip -s -s link list
+
+If I<Interface> is B<All>, all interfaces will be selected.
+
+=item B<QDisc> I<Interface> [I<QDisc>]
+
+=item B<Class> I<Interface> [I<Class>]
+
+=item B<Filter> I<Interface> [I<Filter>]
+
+Collect the octets and packets that pass a certain qdisc, class or filter.
+
+QDiscs and classes are identified by their type and handle (or classid).
+Filters don't necessarily have a handle, therefore the parent's handle is used.
+The notation used in collectd differs from that used in tc(1) in that it
+doesn't skip the major or minor number if it's zero and doesn't print special
+ids by their name. So, for example, a qdisc may be identified by
+C<pfifo_fast-1:0> even though the minor number of B<all> qdiscs is zero and
+thus not displayed by tc(1).
+
+If B<QDisc>, B<Class>, or B<Filter> is given without the second argument,
+i.E<nbsp>.e. without an identifier, all qdiscs, classes, or filters that are
+associated with that interface will be collected.
+
+Since a filter itself doesn't necessarily have a handle, the parent's handle is
+used. This may lead to problems when more than one filter is attached to a
+qdisc or class. This isn't nice, but we don't know how this could be done any
+better. If you have a idea, please don't hesitate to tell us.
+
+As with the B<Interface> option you can specify B<All> as the interface,
+meaning all interfaces.
+
+Here are some examples to help you understand the above text more easily:
+
+  <Plugin netlink>
+    VerboseInterface "All"
+    QDisc "eth0" "pfifo_fast-1:0"
+    QDisc "ppp0"
+    Class "ppp0" "htb-1:10"
+    Filter "ppp0" "u32-1:0"
+  </Plugin>
+
+=item B<IgnoreSelected>
+
+The behavior is the same as with all other similar plugins: If nothing is
+selected at all, everything is collected. If some things are selected using the
+options described above, only these statistics are collected. If you set
+B<IgnoreSelected> to B<true>, this behavior is inverted, i.E<nbsp>e. the
+specified statistics will not be collected.
+
+=back
+
+=head2 Plugin C<network>
+
+The Network plugin sends data to a remote instance of collectd, receives data
+from a remote instance, or both at the same time. Data which has been received
+from the network is usually not transmitted again, but this can be activated, see
+the B<Forward> option below.
+
+The default IPv6 multicast group is C<ff18::efc0:4a42>. The default IPv4
+multicast group is C<239.192.74.66>. The default I<UDP> port is B<25826>.
+
+Both, B<Server> and B<Listen> can be used as single option or as block. When
+used as block, given options are valid for this socket only. For example:
+
+ <Plugin "network">
+   Server "collectd.internal.tld"
+   <Server "collectd.external.tld">
+     SecurityLevel "sign"
+     Username "myhostname"
+     Password "ohl0eQue"
+   </Server>
+ </Plugin>
+
+=over 4
+
+=item B<E<lt>Server> I<Host> [I<Port>]B<E<gt>>
+
+The B<Server> statement/block sets the server to send datagrams to. The
+statement may occur multiple times to send each datagram to multiple
+destinations.
+
+The argument I<Host> may be a hostname, an IPv4 address or an IPv6 address. The
+optional second argument specifies a port number or a service name. If not
+given, the default, B<25826>, is used.
+
+The following options are recognized within B<Server> blocks:
+
+=over 4
+
+=item B<SecurityLevel> B<Encrypt>|B<Sign>|B<None>
+
+Set the security you require for network communication. When the security level
+has been set to B<Encrypt>, data sent over the network will be encrypted using
+I<AES-256>. The integrity of encrypted packets is ensured using I<SHA-1>. When
+set to B<Sign>, transmitted data is signed using the I<HMAC-SHA-256> message
+authentication code. When set to B<None>, data is sent without any security.
+
+This feature is only available if the I<network> plugin was linked with
+I<libgcrypt>.
+
+=item B<Username> I<Username>
+
+Sets the username to transmit. This is used by the server to lookup the
+password. See B<AuthFile> below. All security levels except B<None> require
+this setting.
+
+This feature is only available if the I<network> plugin was linked with
+I<libgcrypt>.
+
+=item B<Password> I<Password>
+
+Sets a password (shared secret) for this socket. All security levels except
+B<None> require this setting.
+
+This feature is only available if the I<network> plugin was linked with
+I<libgcrypt>.
+
+=item B<Interface> I<Interface name>
+
+Set the outgoing interface for IP packets. This applies at least
+to IPv6 packets and if possible to IPv4. If this option is not applicable,
+undefined or a non-existent interface name is specified, the default
+behavior is to let the kernel choose the appropriate interface. Be warned
+that the manual selection of an interface for unicast traffic is only
+necessary in rare cases.
+
+=back
+
+=item B<E<lt>Listen> I<Host> [I<Port>]B<E<gt>>
+
+The B<Listen> statement sets the interfaces to bind to. When multiple
+statements are found the daemon will bind to multiple interfaces.
+
+The argument I<Host> may be a hostname, an IPv4 address or an IPv6 address. If
+the argument is a multicast address the daemon will join that multicast group.
+The optional second argument specifies a port number or a service name. If not
+given, the default, B<25826>, is used.
+
+The following options are recognized within C<E<lt>ListenE<gt>> blocks:
+
+=over 4
+
+=item B<SecurityLevel> B<Encrypt>|B<Sign>|B<None>
+
+Set the security you require for network communication. When the security level
+has been set to B<Encrypt>, only encrypted data will be accepted. The integrity
+of encrypted packets is ensured using I<SHA-1>. When set to B<Sign>, only
+signed and encrypted data is accepted. When set to B<None>, all data will be
+accepted. If an B<AuthFile> option was given (see below), encrypted data is
+decrypted if possible.
+
+This feature is only available if the I<network> plugin was linked with
+I<libgcrypt>.
+
+=item B<AuthFile> I<Filename>
+
+Sets a file in which usernames are mapped to passwords. These passwords are
+used to verify signatures and to decrypt encrypted network packets. If
+B<SecurityLevel> is set to B<None>, this is optional. If given, signed data is
+verified and encrypted packets are decrypted. Otherwise, signed data is
+accepted without checking the signature and encrypted data cannot be decrypted.
+For the other security levels this option is mandatory.
+
+The file format is very simple: Each line consists of a username followed by a
+colon and any number of spaces followed by the password. To demonstrate, an
+example file could look like this:
+
+  user0: foo
+  user1: bar
+
+Each time a packet is received, the modification time of the file is checked
+using L<stat(2)>. If the file has been changed, the contents is re-read. While
+the file is being read, it is locked using L<fcntl(2)>.
+
+=item B<Interface> I<Interface name>
+
+Set the incoming interface for IP packets explicitly. This applies at least
+to IPv6 packets and if possible to IPv4. If this option is not applicable,
+undefined or a non-existent interface name is specified, the default
+behavior is, to let the kernel choose the appropriate interface. Thus incoming
+traffic gets only accepted, if it arrives on the given interface.
+
+=back
+
+=item B<TimeToLive> I<1-255>
+
+Set the time-to-live of sent packets. This applies to all, unicast and
+multicast, and IPv4 and IPv6 packets. The default is to not change this value.
+That means that multicast packets will be sent with a TTL of C<1> (one) on most
+operating systems.
+
+=item B<MaxPacketSize> I<1024-65535>
+
+Set the maximum size for datagrams received over the network. Packets larger
+than this will be truncated. Defaults to 1452E<nbsp>bytes.
+
+=item B<Forward> I<true|false>
+
+If set to I<true>, write packets that were received via the network plugin to
+the sending sockets. This should only be activated when the B<Listen>- and
+B<Server>-statements differ. Otherwise packets may be send multiple times to
+the same multicast group. While this results in more network traffic than
+necessary it's not a huge problem since the plugin has a duplicate detection,
+so the values will not loop.
+
+=item B<ReportStats> B<true>|B<false>
+
+The network plugin cannot only receive and send statistics, it can also create
+statistics about itself. Collected data included the number of received and
+sent octets and packets, the length of the receive queue and the number of
+values handled. When set to B<true>, the I<Network plugin> will make these
+statistics available. Defaults to B<false>.
+
+=back
+
+=head2 Plugin C<nginx>
+
+This plugin collects the number of connections and requests handled by the
+C<nginx daemon> (speak: engineE<nbsp>X), a HTTP and mail server/proxy. It
+queries the page provided by the C<ngx_http_stub_status_module> module, which
+isn't compiled by default. Please refer to
+L<http://wiki.codemongers.com/NginxStubStatusModule> for more information on
+how to compile and configure nginx and this module.
+
+The following options are accepted by the C<nginx plugin>:
+
+=over 4
+
+=item B<URL> I<http://host/nginx_status>
+
+Sets the URL of the C<ngx_http_stub_status_module> output.
+
+=item B<User> I<Username>
+
+Optional user name needed for authentication.
+
+=item B<Password> I<Password>
+
+Optional password needed for authentication.
+
+=item B<VerifyPeer> B<true|false>
+
+Enable or disable peer SSL certificate verification. See
+L<http://curl.haxx.se/docs/sslcerts.html> for details. Enabled by default.
+
+=item B<VerifyHost> B<true|false>
+
+Enable or disable peer host name verification. If enabled, the plugin checks
+if the C<Common Name> or a C<Subject Alternate Name> field of the SSL
+certificate matches the host name provided by the B<URL> option. If this
+identity check fails, the connection is aborted. Obviously, only works when
+connecting to a SSL enabled server. Enabled by default.
+
+=item B<CACert> I<File>
+
+File that holds one or more SSL certificates. If you want to use HTTPS you will
+possibly need this option. What CA certificates come bundled with C<libcurl>
+and are checked by default depends on the distribution you use.
+
+=back
+
+=head2 Plugin C<notify_desktop>
+
+This plugin sends a desktop notification to a notification daemon, as defined
+in the Desktop Notification Specification. To actually display the
+notifications, B<notification-daemon> is required and B<collectd> has to be
+able to access the X server (i.E<nbsp>e., the C<DISPLAY> and C<XAUTHORITY>
+environment variables have to be set correctly) and the D-Bus message bus.
+
+The Desktop Notification Specification can be found at
+L<http://www.galago-project.org/specs/notification/>.
+
+=over 4
+
+=item B<OkayTimeout> I<timeout>
+
+=item B<WarningTimeout> I<timeout>
+
+=item B<FailureTimeout> I<timeout>
+
+Set the I<timeout>, in milliseconds, after which to expire the notification
+for C<OKAY>, C<WARNING> and C<FAILURE> severities respectively. If zero has
+been specified, the displayed notification will not be closed at all - the
+user has to do so herself. These options default to 5000. If a negative number
+has been specified, the default is used as well.
+
+=back
+
+=head2 Plugin C<notify_email>
+
+The I<notify_email> plugin uses the I<ESMTP> library to send notifications to a
+configured email address.
+
+I<libESMTP> is available from L<http://www.stafford.uklinux.net/libesmtp/>.
+
+Available configuration options:
+
+=over 4
+
+=item B<From> I<Address>
+
+Email address from which the emails should appear to come from.
+
+Default: C<root@localhost>
+
+=item B<Recipient> I<Address>
+
+Configures the email address(es) to which the notifications should be mailed.
+May be repeated to send notifications to multiple addresses.
+
+At least one B<Recipient> must be present for the plugin to work correctly.
+
+=item B<SMTPServer> I<Hostname>
+
+Hostname of the SMTP server to connect to.
+
+Default: C<localhost>
+
+=item B<SMTPPort> I<Port>
+
+TCP port to connect to.
+
+Default: C<25>
+
+=item B<SMTPUser> I<Username>
+
+Username for ASMTP authentication. Optional.
+
+=item B<SMTPPassword> I<Password>
+
+Password for ASMTP authentication. Optional.
+
+=item B<Subject> I<Subject>
+
+Subject-template to use when sending emails. There must be exactly two
+string-placeholders in the subject, given in the standard I<printf(3)> syntax,
+i.E<nbsp>e. C<%s>. The first will be replaced with the severity, the second
+with the hostname.
+
+Default: C<Collectd notify: %s@%s>
+
+=back
+
+=head2 Plugin C<ntpd>
+
+=over 4
+
+=item B<Host> I<Hostname>
+
+Hostname of the host running B<ntpd>. Defaults to B<localhost>.
+
+=item B<Port> I<Port>
+
+UDP-Port to connect to. Defaults to B<123>.
+
+=item B<ReverseLookups> B<true>|B<false>
+
+Sets whether or not to perform reverse lookups on peers. Since the name or
+IP-address may be used in a filename it is recommended to disable reverse
+lookups. The default is to do reverse lookups to preserve backwards
+compatibility, though.
+
+=back
+
+=head2 Plugin C<nut>
+
+=over 4
+
+=item B<UPS> I<upsname>B<@>I<hostname>[B<:>I<port>]
+
+Add a UPS to collect data from. The format is identical to the one accepted by
+L<upsc(8)>.
+
+=back
+
+=head2 Plugin C<olsrd>
+
+The I<olsrd> plugin connects to the TCP port opened by the I<txtinfo> plugin of
+the Optimized Link State Routing daemon and reads information about the current
+state of the meshed network.
+
+The following configuration options are understood:
+
+=over 4
+
+=item B<Host> I<Host>
+
+Connect to I<Host>. Defaults to B<"localhost">.
+
+=item B<Port> I<Port>
+
+Specifies the port to connect to. This must be a string, even if you give the
+port as a number rather than a service name. Defaults to B<"2006">.
+
+=item B<CollectLinks> B<No>|B<Summary>|B<Detail>
+
+Specifies what information to collect about links, i.E<nbsp>e. direct
+connections of the daemon queried. If set to B<No>, no information is
+collected. If set to B<Summary>, the number of links and the average of all
+I<link quality> (LQ) and I<neighbor link quality> (NLQ) values is calculated.
+If set to B<Detail> LQ and NLQ are collected per link.
+
+Defaults to B<Detail>.
+
+=item B<CollectRoutes> B<No>|B<Summary>|B<Detail>
+
+Specifies what information to collect about routes of the daemon queried. If
+set to B<No>, no information is collected. If set to B<Summary>, the number of
+routes and the average I<metric> and I<ETX> is calculated. If set to B<Detail>
+metric and ETX are collected per route.
+
+Defaults to B<Summary>.
+
+=item B<CollectTopology> B<No>|B<Summary>|B<Detail>
+
+Specifies what information to collect about the global topology. If set to
+B<No>, no information is collected. If set to B<Summary>, the number of links
+in the entire topology and the average I<link quality> (LQ) is calculated.
+If set to B<Detail> LQ and NLQ are collected for each link in the entire topology.
+
+Defaults to B<Summary>.
+
+=back
+
+=head2 Plugin C<onewire>
+
+B<EXPERIMENTAL!> See notes below.
+
+The C<onewire> plugin uses the B<owcapi> library from the B<owfs> project
+L<http://owfs.org/> to read sensors connected via the onewire bus.
+
+Currently only temperature sensors (sensors with the family code C<10>,
+e.E<nbsp>g. DS1820, DS18S20, DS1920) can be read. If you have other sensors you
+would like to have included, please send a sort request to the mailing list.
+
+Hubs (the DS2409 chips) are working, but read the note, why this plugin is
+experimental, below.
+
+=over 4
+
+=item B<Device> I<Device>
+
+Sets the device to read the values from. This can either be a "real" hardware
+device, such as a serial port or an USB port, or the address of the
+L<owserver(1)> socket, usually B<localhost:4304>.
+
+Though the documentation claims to automatically recognize the given address
+format, with versionE<nbsp>2.7p4 we had to specify the type explicitly. So
+with that version, the following configuration worked for us:
+
+  <Plugin onewire>
+    Device "-s localhost:4304"
+  </Plugin>
+
+This directive is B<required> and does not have a default value.
+
+=item B<Sensor> I<Sensor>
+
+Selects sensors to collect or to ignore, depending on B<IgnoreSelected>, see
+below. Sensors are specified without the family byte at the beginning, to you'd
+use C<F10FCA000800>, and B<not> include the leading C<10.> family byte and
+point.
+
+=item B<IgnoreSelected> I<true>|I<false>
+
+If no configuration if given, the B<onewire> plugin will collect data from all
+sensors found. This may not be practical, especially if sensors are added and
+removed regularly. Sometimes, however, it's easier/preferred to collect only
+specific sensors or all sensors I<except> a few specified ones. This option
+enables you to do that: By setting B<IgnoreSelected> to I<true> the effect of
+B<Sensor> is inverted: All selected interfaces are ignored and all other
+interfaces are collected.
+
+=item B<Interval> I<Seconds>
+
+Sets the interval in which all sensors should be read. If not specified, the
+global B<Interval> setting is used.
+
+=back
+
+B<EXPERIMENTAL!> The C<onewire> plugin is experimental, because it doesn't yet
+work with big setups. It works with one sensor being attached to one
+controller, but as soon as you throw in a couple more senors and maybe a hub
+or two, reading all values will take more than ten seconds (the default
+interval). We will probably add some separate thread for reading the sensors
+and some cache or something like that, but it's not done yet. We will try to
+maintain backwards compatibility in the future, but we can't promise. So in
+short: If it works for you: Great! But keep in mind that the config I<might>
+change, though this is unlikely. Oh, and if you want to help improving this
+plugin, just send a short notice to the mailing list. ThanksE<nbsp>:)
+
+=head2 Plugin C<openvpn>
+
+The OpenVPN plugin reads a status file maintained by OpenVPN and gathers
+traffic statistics about connected clients.
+
+To set up OpenVPN to write to the status file periodically, use the
+B<--status> option of OpenVPN. Since OpenVPN can write two different formats,
+you need to set the required format, too. This is done by setting
+B<--status-version> to B<2>.
+
+So, in a nutshell you need:
+
+  openvpn $OTHER_OPTIONS \
+    --status "/var/run/openvpn-status" 10 \
+    --status-version 2
+
+Available options:
+
+=over 4
+
+=item B<StatusFile> I<File>
+
+Specifies the location of the status file.
+
+=item B<ImprovedNamingSchema> B<true>|B<false>
+
+When enabled, the filename of the status file will be used as plugin instance
+and the client's "common name" will be used as type instance. This is required
+when reading multiple status files. Enabling this option is recommended, but to
+maintain backwards compatibility this option is disabled by default.
+
+=item B<CollectCompression> B<true>|B<false>
+
+Sets whether or not statistics about the compression used by OpenVPN should be
+collected. This information is only available in I<single> mode. Enabled by
+default.
+
+=item B<CollectIndividualUsers> B<true>|B<false>
+
+Sets whether or not traffic information is collected for each connected client
+individually. If set to false, currently no traffic data is collected at all
+because aggregating this data in a save manner is tricky. Defaults to B<true>.
+
+=item B<CollectUserCount> B<true>|B<false>
+
+When enabled, the number of currently connected clients or users is collected.
+This is especially interesting when B<CollectIndividualUsers> is disabled, but
+can be configured independently from that option. Defaults to B<false>.
+
+=back
+
+=head2 Plugin C<oracle>
+
+The "oracle" plugin uses the Oracle® Call Interface I<(OCI)> to connect to an
+Oracle® Database and lets you execute SQL statements there. It is very similar
+to the "dbi" plugin, because it was written around the same time. See the "dbi"
+plugin's documentation above for details.
+
+  <Plugin oracle>
+    <Query "out_of_stock">
+      Statement "SELECT category, COUNT(*) AS value FROM products WHERE in_stock = 0 GROUP BY category"
+      <Result>
+        Type "gauge"
+        # InstancePrefix "foo"
+        InstancesFrom "category"
+        ValuesFrom "value"
+      </Result>
+    </Query>
+    <Database "product_information">
+      ConnectID "db01"
+      Username "oracle"
+      Password "secret"
+      Query "out_of_stock"
+    </Database>
+  </Plugin>
+
+=head3 B<Query> blocks
+
+The Query blocks are handled identically to the Query blocks of the "dbi"
+plugin. Please see its documentation above for details on how to specify
+queries.
+
+=head3 B<Database> blocks
+
+Database blocks define a connection to a database and which queries should be
+sent to that database. Each database needs a "name" as string argument in the
+starting tag of the block. This name will be used as "PluginInstance" in the
+values submitted to the daemon. Other than that, that name is not used.
+
+=over 4
+
+=item B<ConnectID> I<ID>
+
+Defines the "database alias" or "service name" to connect to. Usually, these
+names are defined in the file named C<$ORACLE_HOME/network/admin/tnsnames.ora>.
+
+=item B<Username> I<Username>
+
+Username used for authentication.
+
+=item B<Password> I<Password>
+
+Password used for authentication.
+
+=item B<Query> I<QueryName>
+
+Associates the query named I<QueryName> with this database connection. The
+query needs to be defined I<before> this statement, i.E<nbsp>e. all query
+blocks you want to refer to must be placed above the database block you want to
+refer to them from.
+
+=back
+
+=head2 Plugin C<perl>
+
+This plugin embeds a Perl-interpreter into collectd and provides an interface
+to collectd's plugin system. See L<collectd-perl(5)> for its documentation.
+
+=head2 Plugin C<pinba>
+
+The I<Pinba plugin> receives profiling information from I<Pinba>, an extension
+for the I<PHP> interpreter. At the end of executing a script, i.e. after a
+PHP-based webpage has been delivered, the extension will send a UDP packet
+containing timing information, peak memory usage and so on. The plugin will
+wait for such packets, parse them and account the provided information, which
+is then dispatched to the daemon once per interval.
+
+Synopsis:
+
+ <Plugin pinba>
+   Address "::0"
+   Port "30002"
+   # Overall statistics for the website.
+   <View "www-total">
+     Server "www.example.com"
+   </View>
+   # Statistics for www-a only
+   <View "www-a">
+     Host "www-a.example.com"
+     Server "www.example.com"
+   </View>
+   # Statistics for www-b only
+   <View "www-b">
+     Host "www-b.example.com"
+     Server "www.example.com"
+   </View>
+ </Plugin>
+
+The plugin provides the following configuration options:
+
+=over 4
+
+=item B<Address> I<Node>
+
+Configures the address used to open a listening socket. By default, plugin will
+bind to the I<any> address C<::0>.
+
+=item B<Port> I<Service>
+
+Configures the port (service) to bind to. By default the default Pinba port
+"30002" will be used. The option accepts service names in addition to port
+numbers and thus requires a I<string> argument.
+
+=item E<lt>B<View> I<Name>E<gt> block
+
+The packets sent by the Pinba extension include the hostname of the server, the
+server name (the name of the virtual host) and the script that was executed.
+Using B<View> blocks it is possible to separate the data into multiple groups
+to get more meaningful statistics. Each packet is added to all matching groups,
+so that a packet may be accounted for more than once.
+
+=over 4
+
+=item B<Host> I<Host>
+
+Matches the hostname of the system the webserver / script is running on. This
+will contain the result of the L<gethostname(2)> system call. If not
+configured, all hostnames will be accepted.
+
+=item B<Server> I<Server>
+
+Matches the name of the I<virtual host>, i.e. the contents of the
+C<$_SERVER["SERVER_NAME"]> variable when within PHP. If not configured, all
+server names will be accepted.
+
+=item B<Script> I<Script>
+
+Matches the name of the I<script name>, i.e. the contents of the
+C<$_SERVER["SCRIPT_NAME"]> variable when within PHP. If not configured, all
+script names will be accepted.
+
+=back
+
+=back
+
+=head2 Plugin C<ping>
+
+The I<Ping> plugin starts a new thread which sends ICMP "ping" packets to the
+configured hosts periodically and measures the network latency. Whenever the
+C<read> function of the plugin is called, it submits the average latency, the
+standard deviation and the drop rate for each host.
+
+Available configuration options:
+
+=over 4
+
+=item B<Host> I<IP-address>
+
+Host to ping periodically. This option may be repeated several times to ping
+multiple hosts.
+
+=item B<Interval> I<Seconds>
+
+Sets the interval in which to send ICMP echo packets to the configured hosts.
+This is B<not> the interval in which statistics are queries from the plugin but
+the interval in which the hosts are "pinged". Therefore, the setting here
+should be smaller than or equal to the global B<Interval> setting. Fractional
+times, such as "1.24" are allowed.
+
+Default: B<1.0>
+
+=item B<Timeout> I<Seconds>
+
+Time to wait for a response from the host to which an ICMP packet had been
+sent. If a reply was not received after I<Seconds> seconds, the host is assumed
+to be down or the packet to be dropped. This setting must be smaller than the
+B<Interval> setting above for the plugin to work correctly. Fractional
+arguments are accepted.
+
+Default: B<0.9>
+
+=item B<TTL> I<0-255>
+
+Sets the Time-To-Live of generated ICMP packets.
+
+=item B<SourceAddress> I<host>
+
+Sets the source address to use. I<host> may either be a numerical network
+address or a network hostname.
+
+=item B<Device> I<name>
+
+Sets the outgoing network device to be used. I<name> has to specify an
+interface name (e.E<nbsp>g. C<eth0>). This might not be supported by all
+operating systems.
+
+=item B<MaxMissed> I<Packets>
+
+Trigger a DNS resolve after the host has not replied to I<Packets> packets. This
+enables the use of dynamic DNS services (like dyndns.org) with the ping plugin.
+
+Default: B<-1> (disabled)
+
+=back
+
+=head2 Plugin C<postgresql>
+
+The C<postgresql> plugin queries statistics from PostgreSQL databases. It
+keeps a persistent connection to all configured databases and tries to
+reconnect if the connection has been interrupted. A database is configured by
+specifying a B<Database> block as described below. The default statistics are
+collected from PostgreSQL's B<statistics collector> which thus has to be
+enabled for this plugin to work correctly. This should usually be the case by
+default. See the section "The Statistics Collector" of the B<PostgreSQL
+Documentation> for details.
+
+By specifying custom database queries using a B<Query> block as described
+below, you may collect any data that is available from some PostgreSQL
+database. This way, you are able to access statistics of external daemons
+which are available in a PostgreSQL database or use future or special
+statistics provided by PostgreSQL without the need to upgrade your collectd
+installation.
+
+The B<PostgreSQL Documentation> manual can be found at
+L<http://www.postgresql.org/docs/manuals/>.
+
+  <Plugin postgresql>
+    <Query magic>
+      Statement "SELECT magic FROM wizard WHERE host = $1;"
+      Param hostname
+      <Result>
+        Type gauge
+        InstancePrefix "magic"
+        ValuesFrom magic
+      </Result>
+    </Query>
+
+    <Query rt36_tickets>
+      Statement "SELECT COUNT(type) AS count, type \
+                        FROM (SELECT CASE \
+                                     WHEN resolved = 'epoch' THEN 'open' \
+                                     ELSE 'resolved' END AS type \
+                                     FROM tickets) type \
+                        GROUP BY type;"
+      <Result>
+        Type counter
+        InstancePrefix "rt36_tickets"
+        InstancesFrom "type"
+        ValuesFrom "count"
+      </Result>
+    </Query>
+
+    <Database foo>
+      Host "hostname"
+      Port "5432"
+      User "username"
+      Password "secret"
+      SSLMode "prefer"
+      KRBSrvName "kerberos_service_name"
+      Query magic
+    </Database>
+
+    <Database bar>
+      Interval 300
+      Service "service_name"
+      Query backend # predefined
+      Query rt36_tickets
+    </Database>
+  </Plugin>
+
+The B<Query> block defines one database query which may later be used by a
+database definition. It accepts a single mandatory argument which specifies
+the name of the query. The names of all queries have to be unique (see the
+B<MinVersion> and B<MaxVersion> options below for an exception to this
+rule). The following configuration options are available to define the query:
+
+In each B<Query> block, there is one or more B<Result> blocks. B<Result>
+blocks define how to handle the values returned from the query. They define
+which column holds which value and how to dispatch that value to the daemon.
+Multiple B<Result> blocks may be used to extract multiple values from a single
+query.
+
+=over 4
+
+=item B<Statement> I<sql query statement>
+
+Specify the I<sql query statement> which the plugin should execute. The string
+may contain the tokens B<$1>, B<$2>, etc. which are used to reference the
+first, second, etc. parameter. The value of the parameters is specified by the
+B<Param> configuration option - see below for details. To include a literal
+B<$> character followed by a number, surround it with single quotes (B<'>).
+
+Any SQL command which may return data (such as C<SELECT> or C<SHOW>) is
+allowed. Note, however, that only a single command may be used. Semicolons are
+allowed as long as a single non-empty command has been specified only.
+
+The returned lines will be handled separately one after another.
+
+=item B<Param> I<hostname>|I<database>|I<username>|I<interval>
+
+Specify the parameters which should be passed to the SQL query. The parameters
+are referred to in the SQL query as B<$1>, B<$2>, etc. in the same order as
+they appear in the configuration file. The value of the parameter is
+determined depending on the value of the B<Param> option as follows:
+
+=over 4
+
+=item I<hostname>
+
+The configured hostname of the database connection. If a UNIX domain socket is
+used, the parameter expands to "localhost".
+
+=item I<database>
+
+The name of the database of the current connection.
+
+=item I<username>
+
+The username used to connect to the database.
+
+=item I<interval>
+
+The interval with which this database is queried (as specified by the database
+specific or global B<Interval> options).
+
+=back
+
+Please note that parameters are only supported by PostgreSQL's protocol
+version 3 and above which was introduced in version 7.4 of PostgreSQL.
+
+=item B<Type> I<type>
+
+The I<type> name to be used when dispatching the values. The type describes
+how to handle the data and where to store it. See L<types.db(5)> for more
+details on types and their configuration. The number and type of values (as
+selected by the B<ValuesFrom> option) has to match the type of the given name.
+
+This option is required inside a B<Result> block.
+
+=item B<InstancePrefix> I<prefix>
+
+=item B<InstancesFrom> I<column0> [I<column1> ...]
+
+Specify how to create the "TypeInstance" for each data set (i.E<nbsp>e. line).
+B<InstancePrefix> defines a static prefix that will be prepended to all type
+instances. B<InstancesFrom> defines the column names whose values will be used
+to create the type instance. Multiple values will be joined together using the
+hyphen (C<->) as separation character.
+
+The plugin itself does not check whether or not all built instances are
+different. It is your responsibility to assure that each is unique.
+
+Both options are optional. If none is specified, the type instance will be
+empty.
+
+=item B<ValuesFrom> I<column0> [I<column1> ...]
+
+Names the columns whose content is used as the actual data for the data sets
+that are dispatched to the daemon. How many such columns you need is
+determined by the B<Type> setting as explained above. If you specify too many
+or not enough columns, the plugin will complain about that and no data will be
+submitted to the daemon.
+
+The actual data type, as seen by PostgreSQL, is not that important as long as
+it represents numbers. The plugin will automatically cast the values to the
+right type if it know how to do that. For that, it uses the L<strtoll(3)> and
+L<strtod(3)> functions, so anything supported by those functions is supported
+by the plugin as well.
+
+This option is required inside a B<Result> block and may be specified multiple
+times. If multiple B<ValuesFrom> options are specified, the columns are read
+in the given order.
+
+=item B<MinVersion> I<version>
+
+=item B<MaxVersion> I<version>
+
+Specify the minimum or maximum version of PostgreSQL that this query should be
+used with. Some statistics might only be available with certain versions of
+PostgreSQL. This allows you to specify multiple queries with the same name but
+which apply to different versions, thus allowing you to use the same
+configuration in a heterogeneous environment.
+
+The I<version> has to be specified as the concatenation of the major, minor
+and patch-level versions, each represented as two-decimal-digit numbers. For
+example, version 8.2.3 will become 80203.
+
+=back
+
+The following predefined queries are available (the definitions can be found
+in the F<postgresql_default.conf> file which, by default, is available at
+C<I<prefix>/share/collectd/>):
+
+=over 4
+
+=item B<backends>
+
+This query collects the number of backends, i.E<nbsp>e. the number of
+connected clients.
+
+=item B<transactions>
+
+This query collects the numbers of committed and rolled-back transactions of
+the user tables.
+
+=item B<queries>
+
+This query collects the numbers of various table modifications (i.E<nbsp>e.
+insertions, updates, deletions) of the user tables.
+
+=item B<query_plans>
+
+This query collects the numbers of various table scans and returned tuples of
+the user tables.
+
+=item B<table_states>
+
+This query collects the numbers of live and dead rows in the user tables.
+
+=item B<disk_io>
+
+This query collects disk block access counts for user tables.
+
+=item B<disk_usage>
+
+This query collects the on-disk size of the database in bytes.
+
+=back
+
+The B<Database> block defines one PostgreSQL database for which to collect
+statistics. It accepts a single mandatory argument which specifies the
+database name. None of the other options are required. PostgreSQL will use
+default values as documented in the section "CONNECTING TO A DATABASE" in the
+L<psql(1)> manpage. However, be aware that those defaults may be influenced by
+the user collectd is run as and special environment variables. See the manpage
+for details.
+
+=over 4
+
+=item B<Interval> I<seconds>
+
+Specify the interval with which the database should be queried. The default is
+to use the global B<Interval> setting.
+
+=item B<Host> I<hostname>
+
+Specify the hostname or IP of the PostgreSQL server to connect to. If the
+value begins with a slash, it is interpreted as the directory name in which to
+look for the UNIX domain socket.
+
+This option is also used to determine the hostname that is associated with a
+collected data set. If it has been omitted or either begins with with a slash
+or equals B<localhost> it will be replaced with the global hostname definition
+of collectd. Any other value will be passed literally to collectd when
+dispatching values. Also see the global B<Hostname> and B<FQDNLookup> options.
+
+=item B<Port> I<port>
+
+Specify the TCP port or the local UNIX domain socket file extension of the
+server.
+
+=item B<User> I<username>
+
+Specify the username to be used when connecting to the server.
+
+=item B<Password> I<password>
+
+Specify the password to be used when connecting to the server.
+
+=item B<SSLMode> I<disable>|I<allow>|I<prefer>|I<require>
+
+Specify whether to use an SSL connection when contacting the server. The
+following modes are supported:
+
+=over 4
+
+=item I<disable>
+
+Do not use SSL at all.
+
+=item I<allow>
+
+First, try to connect without using SSL. If that fails, try using SSL.
+
+=item I<prefer> (default)
+
+First, try to connect using SSL. If that fails, try without using SSL.
+
+=item I<require>
+
+Use SSL only.
+
+=back
+
+=item B<KRBSrvName> I<kerberos_service_name>
+
+Specify the Kerberos service name to use when authenticating with Kerberos 5
+or GSSAPI. See the sections "Kerberos authentication" and "GSSAPI" of the
+B<PostgreSQL Documentation> for details.
+
+=item B<Service> I<service_name>
+
+Specify the PostgreSQL service name to use for additional parameters. That
+service has to be defined in F<pg_service.conf> and holds additional
+connection parameters. See the section "The Connection Service File" in the
+B<PostgreSQL Documentation> for details.
+
+=item B<Query> I<query>
+
+Specify a I<query> which should be executed for the database connection. This
+may be any of the predefined or user-defined queries. If no such option is
+given, it defaults to "backends", "transactions", "queries", "query_plans",
+"table_states", "disk_io" and "disk_usage". Else, the specified queries are
+used only.
+
+=back
+
+=head2 Plugin C<powerdns>
+
+The C<powerdns> plugin queries statistics from an authoritative PowerDNS
+nameserver and/or a PowerDNS recursor. Since both offer a wide variety of
+values, many of which are probably meaningless to most users, but may be useful
+for some. So you may chose which values to collect, but if you don't, some
+reasonable defaults will be collected.
+
+  <Plugin "powerdns">
+    <Server "server_name">
+      Collect "latency"
+      Collect "udp-answers" "udp-queries"
+      Socket "/var/run/pdns.controlsocket"
+    </Server>
+    <Recursor "recursor_name">
+      Collect "questions"
+      Collect "cache-hits" "cache-misses"
+      Socket "/var/run/pdns_recursor.controlsocket"
+    </Recursor>
+    LocalSocket "/opt/collectd/var/run/collectd-powerdns"
+  </Plugin>
+
+=over 4
+
+=item B<Server> and B<Recursor> block
+
+The B<Server> block defines one authoritative server to query, the B<Recursor>
+does the same for an recursing server. The possible options in both blocks are
+the same, though. The argument defines a name for the serverE<nbsp>/ recursor
+and is required.
+
+=over 4
+
+=item B<Collect> I<Field>
+
+Using the B<Collect> statement you can select which values to collect. Here,
+you specify the name of the values as used by the PowerDNS servers, e.E<nbsp>g.
+C<dlg-only-drops>, C<answers10-100>.
+
+The method of getting the values differs for B<Server> and B<Recursor> blocks:
+When querying the server a C<SHOW *> command is issued in any case, because
+that's the only way of getting multiple values out of the server at once.
+collectd then picks out the values you have selected. When querying the
+recursor, a command is generated to query exactly these values. So if you
+specify invalid fields when querying the recursor, a syntax error may be
+returned by the daemon and collectd may not collect any values at all.
+
+If no B<Collect> statement is given, the following B<Server> values will be
+collected:
+
+=over 4
+
+=item latency
+
+=item packetcache-hit
+
+=item packetcache-miss
+
+=item packetcache-size
+
+=item query-cache-hit
+
+=item query-cache-miss
+
+=item recursing-answers
+
+=item recursing-questions
+
+=item tcp-answers
+
+=item tcp-queries
+
+=item udp-answers
+
+=item udp-queries
+
+=back
+
+The following B<Recursor> values will be collected by default:
+
+=over 4
+
+=item noerror-answers
+
+=item nxdomain-answers
+
+=item servfail-answers
+
+=item sys-msec
+
+=item user-msec
+
+=item qa-latency
+
+=item cache-entries
+
+=item cache-hits
+
+=item cache-misses
+
+=item questions
+
+=back
+
+Please note that up to that point collectd doesn't know what values are
+available on the server and values that are added do not need a change of the
+mechanism so far. However, the values must be mapped to collectd's naming
+scheme, which is done using a lookup table that lists all known values. If
+values are added in the future and collectd does not know about them, you will
+get an error much like this:
+
+  powerdns plugin: submit: Not found in lookup table: foobar = 42
+
+In this case please file a bug report with the collectd team.
+
+=item B<Socket> I<Path>
+
+Configures the path to the UNIX domain socket to be used when connecting to the
+daemon. By default C<${localstatedir}/run/pdns.controlsocket> will be used for
+an authoritative server and C<${localstatedir}/run/pdns_recursor.controlsocket>
+will be used for the recursor.
+
+=back
+
+=item B<LocalSocket> I<Path>
+
+Querying the recursor is done using UDP. When using UDP over UNIX domain
+sockets, the client socket needs a name in the file system, too. You can set
+this local name to I<Path> using the B<LocalSocket> option. The default is
+C<I<prefix>/var/run/collectd-powerdns>.
+
+=back
+
+=head2 Plugin C<processes>
+
+=over 4
+
+=item B<Process> I<Name>
+
+Select more detailed statistics of processes matching this name. The statistics
+collected for these selected processes are size of the resident segment size
+(RSS), user- and system-time used, number of processes and number of threads,
+io data (where available) and minor and major pagefaults.
+
+=item B<ProcessMatch> I<name> I<regex>
+
+Similar to the B<Process> option this allows to select more detailed
+statistics of processes matching the specified I<regex> (see L<regex(7)> for
+details). The statistics of all matching processes are summed up and
+dispatched to the daemon using the specified I<name> as an identifier. This
+allows to "group" several processes together. I<name> must not contain
+slashes.
+
+=back
+
+=head2 Plugin C<protocols>
+
+Collects a lot of information about various network protocols, such as I<IP>,
+I<TCP>, I<UDP>, etc.
+
+Available configuration options:
+
+=over 4
+
+=item B<Value> I<Selector>
+
+Selects whether or not to select a specific value. The string being matched is
+of the form "I<Protocol>:I<ValueName>", where I<Protocol> will be used as the
+plugin instance and I<ValueName> will be used as type instance. An example of
+the string being used would be C<Tcp:RetransSegs>.
+
+You can use regular expressions to match a large number of values with just one
+configuration option. To select all "extended" I<TCP> values, you could use the
+following statement:
+
+  Value "/^TcpExt:/"
+
+Whether only matched values are selected or all matched values are ignored
+depends on the B<IgnoreSelected>. By default, only matched values are selected.
+If no value is configured at all, all values will be selected.
+
+=item B<IgnoreSelected> B<true>|B<false>
+
+If set to B<true>, inverts the selection made by B<Value>, i.E<nbsp>e. all
+matching values will be ignored.
+
+=back
+
+=head2 Plugin C<python>
+
+This plugin embeds a Python-interpreter into collectd and provides an interface
+to collectd's plugin system. See L<collectd-python(5)> for its documentation.
+
+=head2 Plugin C<routeros>
+
+The C<routeros> plugin connects to a device running I<RouterOS>, the
+Linux-based operating system for routers by I<MikroTik>. The plugin uses
+I<librouteros> to connect and reads information about the interfaces and
+wireless connections of the device. The configuration supports querying
+multiple routers:
+
+  <Plugin "routeros">
+    <Router>
+      Host "router0.example.com"
+      User "collectd"
+      Password "secr3t"
+      CollectInterface true
+      CollectCPULoad true
+      CollectMemory true
+    </Router>
+    <Router>
+      Host "router1.example.com"
+      User "collectd"
+      Password "5ecret"
+      CollectInterface true
+      CollectRegistrationTable true
+      CollectDF true
+      CollectDisk true
+    </Router>
+  </Plugin>
+
+As you can see above, the configuration of the I<routeros> plugin consists of
+one or more B<E<lt>RouterE<gt>> blocks. Within each block, the following
+options are understood:
+
+=over 4
+
+=item B<Host> I<Host>
+
+Hostname or IP-address of the router to connect to.
+
+=item B<Port> I<Port>
+
+Port name or port number used when connecting. If left unspecified, the default
+will be chosen by I<librouteros>, currently "8728". This option expects a
+string argument, even when a numeric port number is given.
+
+=item B<User> I<User>
+
+Use the user name I<User> to authenticate. Defaults to "admin".
+
+=item B<Password> I<Password>
+
+Set the password used to authenticate.
+
+=item B<CollectInterface> B<true>|B<false>
+
+When set to B<true>, interface statistics will be collected for all interfaces
+present on the device. Defaults to B<false>.
+
+=item B<CollectRegistrationTable> B<true>|B<false>
+
+When set to B<true>, information about wireless LAN connections will be
+collected. Defaults to B<false>.
+
+=item B<CollectCPULoad> B<true>|B<false>
+
+When set to B<true>, information about the CPU usage will be collected. The
+number is a dimensionless value where zero indicates no CPU usage at all.
+Defaults to B<false>.
+
+=item B<CollectMemory> B<true>|B<false>
+
+When enabled, the amount of used and free memory will be collected. How used
+memory is calculated is unknown, for example whether or not caches are counted
+as used space.
+Defaults to B<false>.
+
+=item B<CollectDF> B<true>|B<false>
+
+When enabled, the amount of used and free disk space will be collected.
+Defaults to B<false>.
+
+=item B<CollectDisk> B<true>|B<false>
+
+When enabled, the number of sectors written and bad blocks will be collected.
+Defaults to B<false>.
+
+=back
+
+=head2 Plugin C<redis>
+
+The I<Redis plugin> connects to one or more Redis servers and gathers
+information about each server's state. For each server there is a I<Node> block
+which configures the connection parameters for this node.
+
+  <Plugin redis>
+    <Node "example">
+        Host "localhost"
+        Port "6379"
+        Timeout 2000
+    </Node>
+  </Plugin>
+
+The information shown in the synopsis above is the I<default configuration>
+which is used by the plugin if no configuration is present.
+
+=over 4
+
+=item B<Node> I<Nodename>
+
+The B<Node> block identifies a new Redis node, that is a new Redis instance
+running in an specified host and port. The name for node is a canonical
+identifier which is used as I<plugin instance>. It is limited to
+64E<nbsp>characters in length.
+
+=item B<Host> I<Hostname>
+
+The B<Host> option is the hostname or IP-address where the Redis instance is
+running on.
+
+=item B<Port> I<Port>
+
+The B<Port> option is the TCP port on which the Redis instance accepts
+connections. Either a service name of a port number may be given. Please note
+that numerical port numbers must be given as a string, too.
+
+=item B<Timeout> I<Timeout in miliseconds>
+
+The B<Timeout> option set the socket timeout for node response. Since the Redis
+read function is blocking, you should keep this value as low as possible. Keep
+in mind that the sum of all B<Timeout> values for all B<Nodes> should be lower
+than B<Interval> defined globally.
+
+=back
+
+=head2 Plugin C<rrdcached>
+
+The C<rrdcached> plugin uses the RRDtool accelerator daemon, L<rrdcached(1)>,
+to store values to RRD files in an efficient manner. The combination of the
+C<rrdcached> B<plugin> and the C<rrdcached> B<daemon> is very similar to the
+way the C<rrdtool> plugin works (see below). The added abstraction layer
+provides a number of benefits, though: Because the cache is not within
+C<collectd> anymore, it does not need to be flushed when C<collectd> is to be
+restarted. This results in much shorter (if any) gaps in graphs, especially
+under heavy load. Also, the C<rrdtool> command line utility is aware of the
+daemon so that it can flush values to disk automatically when needed. This
+allows to integrate automated flushing of values into graphing solutions much
+more easily.
+
+There are disadvantages, though: The daemon may reside on a different host, so
+it may not be possible for C<collectd> to create the appropriate RRD files
+anymore. And even if C<rrdcached> runs on the same host, it may run in a
+different base directory, so relative paths may do weird stuff if you're not
+careful.
+
+So the B<recommended configuration> is to let C<collectd> and C<rrdcached> run
+on the same host, communicating via a UNIX domain socket. The B<DataDir>
+setting should be set to an absolute path, so that a changed base directory
+does not result in RRD files being createdE<nbsp>/ expected in the wrong place.
+
+=over 4
+
+=item B<DaemonAddress> I<Address>
+
+Address of the daemon as understood by the C<rrdc_connect> function of the RRD
+library. See L<rrdcached(1)> for details. Example:
+
+  <Plugin "rrdcached">
+    DaemonAddress "unix:/var/run/rrdcached.sock"
+  </Plugin>
+
+=item B<DataDir> I<Directory>
+
+Set the base directory in which the RRD files reside. If this is a relative
+path, it is relative to the working base directory of the C<rrdcached> daemon!
+Use of an absolute path is recommended.
+
+=item B<CreateFiles> B<true>|B<false>
+
+Enables or disables the creation of RRD files. If the daemon is not running
+locally, or B<DataDir> is set to a relative path, this will not work as
+expected. Default is B<true>.
+
+=back
+
+=head2 Plugin C<rrdtool>
+
+You can use the settings B<StepSize>, B<HeartBeat>, B<RRARows>, and B<XFF> to
+fine-tune your RRD-files. Please read L<rrdcreate(1)> if you encounter problems
+using these settings. If you don't want to dive into the depths of RRDtool, you
+can safely ignore these settings.
+
+=over 4
+
+=item B<DataDir> I<Directory>
+
+Set the directory to store RRD-files under. Per default RRD-files are generated
+beneath the daemon's working directory, i.E<nbsp>e. the B<BaseDir>.
+
+=item B<StepSize> I<Seconds>
+
+B<Force> the stepsize of newly created RRD-files. Ideally (and per default)
+this setting is unset and the stepsize is set to the interval in which the data
+is collected. Do not use this option unless you absolutely have to for some
+reason. Setting this option may cause problems with the C<snmp plugin>, the
+C<exec plugin> or when the daemon is set up to receive data from other hosts.
+
+=item B<HeartBeat> I<Seconds>
+
+B<Force> the heartbeat of newly created RRD-files. This setting should be unset
+in which case the heartbeat is set to twice the B<StepSize> which should equal
+the interval in which data is collected. Do not set this option unless you have
+a very good reason to do so.
+
+=item B<RRARows> I<NumRows>
+
+The C<rrdtool plugin> calculates the number of PDPs per CDP based on the
+B<StepSize>, this setting and a timespan. This plugin creates RRD-files with
+three times five RRAs, i. e. five RRAs with the CFs B<MIN>, B<AVERAGE>, and
+B<MAX>. The five RRAs are optimized for graphs covering one hour, one day, one
+week, one month, and one year.
+
+So for each timespan, it calculates how many PDPs need to be consolidated into
+one CDP by calculating:
+  number of PDPs = timespan / (stepsize * rrarows)
+
+Bottom line is, set this no smaller than the width of you graphs in pixels. The
+default is 1200.
+
+=item B<RRATimespan> I<Seconds>
+
+Adds an RRA-timespan, given in seconds. Use this option multiple times to have
+more then one RRA. If this option is never used, the built-in default of (3600,
+86400, 604800, 2678400, 31622400) is used.
+
+For more information on how RRA-sizes are calculated see B<RRARows> above.
+
+=item B<XFF> I<Factor>
+
+Set the "XFiles Factor". The default is 0.1. If unsure, don't set this option.
+
+=item B<CacheFlush> I<Seconds>
+
+When the C<rrdtool> plugin uses a cache (by setting B<CacheTimeout>, see below)
+it writes all values for a certain RRD-file if the oldest value is older than
+(or equal to) the number of seconds specified. If some RRD-file is not updated
+anymore for some reason (the computer was shut down, the network is broken,
+etc.) some values may still be in the cache. If B<CacheFlush> is set, then the
+entire cache is searched for entries older than B<CacheTimeout> seconds and
+written to disk every I<Seconds> seconds. Since this is kind of expensive and
+does nothing under normal circumstances, this value should not be too small.
+900 seconds might be a good value, though setting this to 7200 seconds doesn't
+normally do much harm either.
+
+=item B<CacheTimeout> I<Seconds>
+
+If this option is set to a value greater than zero, the C<rrdtool plugin> will
+save values in a cache, as described above. Writing multiple values at once
+reduces IO-operations and thus lessens the load produced by updating the files.
+The trade off is that the graphs kind of "drag behind" and that more memory is
+used.
+
+=item B<WritesPerSecond> I<Updates>
+
+When collecting many statistics with collectd and the C<rrdtool> plugin, you
+will run serious performance problems. The B<CacheFlush> setting and the
+internal update queue assert that collectd continues to work just fine even
+under heavy load, but the system may become very unresponsive and slow. This is
+a problem especially if you create graphs from the RRD files on the same
+machine, for example using the C<graph.cgi> script included in the
+C<contrib/collection3/> directory.
+
+This setting is designed for very large setups. Setting this option to a value
+between 25 and 80 updates per second, depending on your hardware, will leave
+the server responsive enough to draw graphs even while all the cached values
+are written to disk. Flushed values, i.E<nbsp>e. values that are forced to disk
+by the B<FLUSH> command, are B<not> effected by this limit. They are still
+written as fast as possible, so that web frontends have up to date data when
+generating graphs.
+
+For example: If you have 100,000 RRD files and set B<WritesPerSecond> to 30
+updates per second, writing all values to disk will take approximately
+56E<nbsp>minutes. Together with the flushing ability that's integrated into
+"collection3" you'll end up with a responsive and fast system, up to date
+graphs and basically a "backup" of your values every hour.
+
+=item B<RandomTimeout> I<Seconds>
+
+When set, the actual timeout for each value is chosen randomly between
+I<CacheTimeout>-I<RandomTimeout> and I<CacheTimeout>+I<RandomTimeout>. The
+intention is to avoid high load situations that appear when many values timeout
+at the same time. This is especially a problem shortly after the daemon starts,
+because all values were added to the internal cache at roughly the same time.
+
+=back
+
+=head2 Plugin C<sensors>
+
+The I<Sensors plugin> uses B<lm_sensors> to retrieve sensor-values. This means
+that all the needed modules have to be loaded and lm_sensors has to be
+configured (most likely by editing F</etc/sensors.conf>. Read
+L<sensors.conf(5)> for details.
+
+The B<lm_sensors> homepage can be found at
+L<http://secure.netroedge.com/~lm78/>.
+
+=over 4
+
+=item B<Sensor> I<chip-bus-address/type-feature>
+
+Selects the name of the sensor which you want to collect or ignore, depending
+on the B<IgnoreSelected> below. For example, the option "B<Sensor>
+I<it8712-isa-0290/voltage-in1>" will cause collectd to gather data for the
+voltage sensor I<in1> of the I<it8712> on the isa bus at the address 0290.
+
+=item B<IgnoreSelected> I<true>|I<false>
+
+If no configuration if given, the B<sensors>-plugin will collect data from all
+sensors. This may not be practical, especially for uninteresting sensors.
+Thus, you can use the B<Sensor>-option to pick the sensors you're interested
+in. Sometimes, however, it's easier/preferred to collect all sensors I<except> a
+few ones. This option enables you to do that: By setting B<IgnoreSelected> to
+I<true> the effect of B<Sensor> is inverted: All selected sensors are ignored
+and all other sensors are collected.
+
+=back
+
+=head2 Plugin C<snmp>
+
+Since the configuration of the C<snmp plugin> is a little more complicated than
+other plugins, its documentation has been moved to an own manpage,
+L<collectd-snmp(5)>. Please see there for details.
+
+=head2 Plugin C<swap>
+
+The I<Swap plugin> collects information about used and available swap space. On
+I<Linux> and I<Solaris>, the following options are available:
+
+=over 4
+
+=item B<ReportByDevice> B<false>|B<true>
+
+Configures how to report physical swap devices. If set to B<false> (the
+default), the summary over all swap devices is reported only, i.e. the globally
+used and available space over all devices. If B<true> is configured, the used
+and available space of each device will be reported separately.
+
+This option is only available if the I<Swap plugin> can read C</proc/swaps>
+(under Linux) or use the L<swapctl(2)> mechanism (under I<Solaris>).
+
+=back
+
+=head2 Plugin C<syslog>
+
+=over 4
+
+=item B<LogLevel> B<debug|info|notice|warning|err>
+
+Sets the log-level. If, for example, set to B<notice>, then all events with
+severity B<notice>, B<warning>, or B<err> will be submitted to the
+syslog-daemon.
+
+Please note that B<debug> is only available if collectd has been compiled with
+debugging support.
+
+=back
+
+=head2 Plugin C<table>
+
+The C<table plugin> provides generic means to parse tabular data and dispatch
+user specified values. Values are selected based on column numbers. For
+example, this plugin may be used to get values from the Linux L<proc(5)>
+filesystem or CSV (comma separated values) files.
+
+  <Plugin table>
+    <Table "/proc/slabinfo">
+      Instance "slabinfo"
+      Separator " "
+      <Result>
+        Type gauge
+        InstancePrefix "active_objs"
+        InstancesFrom 0
+        ValuesFrom 1
+      </Result>
+      <Result>
+        Type gauge
+        InstancePrefix "objperslab"
+        InstancesFrom 0
+        ValuesFrom 4
+      </Result>
+    </Table>
+  </Plugin>
+
+The configuration consists of one or more B<Table> blocks, each of which
+configures one file to parse. Within each B<Table> block, there are one or
+more B<Result> blocks, which configure which data to select and how to
+interpret it.
+
+The following options are available inside a B<Table> block:
+
+=over 4
+
+=item B<Instance> I<instance>
+
+If specified, I<instance> is used as the plugin instance. So, in the above
+example, the plugin name C<table-slabinfo> would be used. If omitted, the
+filename of the table is used instead, with all special characters replaced
+with an underscore (C<_>).
+
+=item B<Separator> I<string>
+
+Any character of I<string> is interpreted as a delimiter between the different
+columns of the table. A sequence of two or more contiguous delimiters in the
+table is considered to be a single delimiter, i.E<nbsp>e. there cannot be any
+empty columns. The plugin uses the L<strtok_r(3)> function to parse the lines
+of a table - see its documentation for more details. This option is mandatory.
+
+A horizontal tab, newline and carriage return may be specified by C<\\t>,
+C<\\n> and C<\\r> respectively. Please note that the double backslashes are
+required because of collectd's config parsing.
+
+=back
+
+The following options are available inside a B<Result> block:
+
+=over 4
+
+=item B<Type> I<type>
+
+Sets the type used to dispatch the values to the daemon. Detailed information
+about types and their configuration can be found in L<types.db(5)>. This
+option is mandatory.
+
+=item B<InstancePrefix> I<prefix>
+
+If specified, prepend I<prefix> to the type instance. If omitted, only the
+B<InstancesFrom> option is considered for the type instance.
+
+=item B<InstancesFrom> I<column0> [I<column1> ...]
+
+If specified, the content of the given columns (identified by the column
+number starting at zero) will be used to create the type instance for each
+row. Multiple values (and the instance prefix) will be joined together with
+dashes (I<->) as separation character. If omitted, only the B<InstancePrefix>
+option is considered for the type instance.
+
+The plugin itself does not check whether or not all built instances are
+different. It’s your responsibility to assure that each is unique. This is
+especially true, if you do not specify B<InstancesFrom>: B<You> have to make
+sure that the table only contains one row.
+
+If neither B<InstancePrefix> nor B<InstancesFrom> is given, the type instance
+will be empty.
+
+=item B<ValuesFrom> I<column0> [I<column1> ...]
+
+Specifies the columns (identified by the column numbers starting at zero)
+whose content is used as the actual data for the data sets that are dispatched
+to the daemon. How many such columns you need is determined by the B<Type>
+setting above. If you specify too many or not enough columns, the plugin will
+complain about that and no data will be submitted to the daemon. The plugin
+uses L<strtoll(3)> and L<strtod(3)> to parse counter and gauge values
+respectively, so anything supported by those functions is supported by the
+plugin as well. This option is mandatory.
+
+=back
+
+=head2 Plugin C<tail>
+
+The C<tail plugin> follows logfiles, just like L<tail(1)> does, parses
+each line and dispatches found values. What is matched can be configured by the
+user using (extended) regular expressions, as described in L<regex(7)>.
+
+  <Plugin "tail">
+    <File "/var/log/exim4/mainlog">
+      Instance "exim"
+      <Match>
+        Regex "S=([1-9][0-9]*)"
+        DSType "CounterAdd"
+        Type "ipt_bytes"
+        Instance "total"
+      </Match>
+      <Match>
+        Regex "\\<R=local_user\\>"
+        ExcludeRegex "\\<R=local_user\\>.*mail_spool defer"
+        DSType "CounterInc"
+        Type "counter"
+        Instance "local_user"
+      </Match>
+    </File>
+  </Plugin>
+
+The config consists of one or more B<File> blocks, each of which configures one
+logfile to parse. Within each B<File> block, there are one or more B<Match>
+blocks, which configure a regular expression to search for.
+
+The B<Instance> option in the B<File> block may be used to set the plugin
+instance. So in the above example the plugin name C<tail-foo> would be used.
+This plugin instance is for all B<Match> blocks that B<follow> it, until the
+next B<Instance> option. This way you can extract several plugin instances from
+one logfile, handy when parsing syslog and the like.
+
+Each B<Match> block has the following options to describe how the match should
+be performed:
+
+=over 4
+
+=item B<Regex> I<regex>
+
+Sets the regular expression to use for matching against a line. The first
+subexpression has to match something that can be turned into a number by
+L<strtoll(3)> or L<strtod(3)>, depending on the value of C<CounterAdd>, see
+below. Because B<extended> regular expressions are used, you do not need to use
+backslashes for subexpressions! If in doubt, please consult L<regex(7)>. Due to
+collectd's config parsing you need to escape backslashes, though. So if you
+want to match literal parentheses you need to do the following:
+
+  Regex "SPAM \\(Score: (-?[0-9]+\\.[0-9]+)\\)"
+
+=item B<ExcludeRegex> I<regex>
+
+Sets an optional regular expression to use for excluding lines from the match.
+An example which excludes all connections from localhost from the match:
+
+  ExcludeRegex "127\\.0\\.0\\.1"
+
+=item B<DSType> I<Type>
+
+Sets how the values are cumulated. I<Type> is one of:
+
+=over 4
+
+=item B<GaugeAverage>
+
+Calculate the average.
+
+=item B<GaugeMin>
+
+Use the smallest number only.
+
+=item B<GaugeMax>
+
+Use the greatest number only.
+
+=item B<GaugeLast>
+
+Use the last number found.
+
+=item B<CounterSet>
+
+=item B<DeriveSet>
+
+=item B<AbsoluteSet>
+
+The matched number is a counter. Simply I<sets> the internal counter to this
+value. Variants exist for C<COUNTER>, C<DERIVE>, and C<ABSOLUTE> data sources.
+
+=item B<CounterAdd>
+
+=item B<DeriveAdd>
+
+Add the matched value to the internal counter. In case of B<DeriveAdd>, the
+matched number may be negative, which will effectively subtract from the
+internal counter.
+
+=item B<CounterInc>
+
+=item B<DeriveInc>
+
+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.
+
+=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.
+
+=item B<Type> I<Type>
+
+Sets the type used to dispatch this value. Detailed information about types and
+their configuration can be found in L<types.db(5)>.
+
+=item B<Instance> I<TypeInstance>
+
+This optional setting sets the type instance to use.
+
+=back
+
+=head2 Plugin C<teamspeak2>
+
+The C<teamspeak2 plugin> connects to the query port of a teamspeak2 server and
+polls interesting global and virtual server data. The plugin can query only one
+physical server but unlimited virtual servers. You can use the following
+options to configure it:
+
+=over 4
+
+=item B<Host> I<hostname/ip>
+
+The hostname or ip which identifies the physical server.
+Default: 127.0.0.1
+
+=item B<Port> I<port>
+
+The query port of the physical server. This needs to be a string.
+Default: "51234"
+
+=item B<Server> I<port>
+
+This option has to be added once for every virtual server the plugin should
+query. If you want to query the virtual server on port 8767 this is what the
+option would look like:
+
+  Server "8767"
+
+This option, although numeric, needs to be a string, i.E<nbsp>e. you B<must>
+use quotes around it! If no such statement is given only global information
+will be collected.
+
+=back
+
+=head2 Plugin C<ted>
+
+The I<TED> plugin connects to a device of "The Energy Detective", a device to
+measure power consumption. These devices are usually connected to a serial
+(RS232) or USB port. The plugin opens a configured device and tries to read the
+current energy readings. For more information on TED, visit
+L<http://www.theenergydetective.com/>.
+
+Available configuration options:
+
+=over 4
+
+=item B<Device> I<Path>
+
+Path to the device on which TED is connected. collectd will need read and write
+permissions on that file.
+
+Default: B</dev/ttyUSB0>
+
+=item B<Retries> I<Num>
+
+Apparently reading from TED is not that reliable. You can therefore configure a
+number of retries here. You only configure the I<retries> here, to if you
+specify zero, one reading will be performed (but no retries if that fails); if
+you specify three, a maximum of four readings are performed. Negative values
+are illegal.
+
+Default: B<0>
+
+=back
+
+=head2 Plugin C<tcpconns>
+
+The C<tcpconns plugin> counts the number of currently established TCP
+connections based on the local port and/or the remote port. Since there may be
+a lot of connections the default if to count all connections with a local port,
+for which a listening socket is opened. You can use the following options to
+fine-tune the ports you are interested in:
+
+=over 4
+
+=item B<ListeningPorts> I<true>|I<false>
+
+If this option is set to I<true>, statistics for all local ports for which a
+listening socket exists are collected. The default depends on B<LocalPort> and
+B<RemotePort> (see below): If no port at all is specifically selected, the
+default is to collect listening ports. If specific ports (no matter if local or
+remote ports) are selected, this option defaults to I<false>, i.E<nbsp>e. only
+the selected ports will be collected unless this option is set to I<true>
+specifically.
+
+=item B<LocalPort> I<Port>
+
+Count the connections to a specific local port. This can be used to see how
+many connections are handled by a specific daemon, e.E<nbsp>g. the mailserver.
+You have to specify the port in numeric form, so for the mailserver example
+you'd need to set B<25>.
+
+=item B<RemotePort> I<Port>
+
+Count the connections to a specific remote port. This is useful to see how
+much a remote service is used. This is most useful if you want to know how many
+connections a local service has opened to remote services, e.E<nbsp>g. how many
+connections a mail server or news server has to other mail or news servers, or
+how many connections a web proxy holds to web servers. You have to give the
+port in numeric form.
+
+=back
+
+=head2 Plugin C<thermal>
+
+=over 4
+
+=item B<ForceUseProcfs> I<true>|I<false>
+
+By default, the I<Thermal plugin> tries to read the statistics from the Linux
+C<sysfs> interface. If that is not available, the plugin falls back to the
+C<procfs> interface. By setting this option to I<true>, you can force the
+plugin to use the latter. This option defaults to I<false>.
+
+=item B<Device> I<Device>
+
+Selects the name of the thermal device that you want to collect or ignore,
+depending on the value of the B<IgnoreSelected> option. This option may be
+used multiple times to specify a list of devices.
+
+=item B<IgnoreSelected> I<true>|I<false>
+
+Invert the selection: If set to true, all devices B<except> the ones that
+match the device names specified by the B<Device> option are collected. By
+default only selected devices are collected if a selection is made. If no
+selection is configured at all, B<all> devices are selected.
+
+=back
+
+=head2 Plugin C<threshold>
+
+The I<Threshold plugin> checks values collected or received by I<collectd>
+against a configurable I<threshold> and issues I<notifications> if values are
+out of bounds.
+
+Documentation for this plugin is available in the L<collectd-threshold(5)>
+manual page.
+
+=head2 Plugin C<tokyotyrant>
+
+The I<TokyoTyrant plugin> connects to a TokyoTyrant server and collects a
+couple metrics: number of records, and database size on disk.
+
+=over 4
+
+=item B<Host> I<Hostname/IP>
+
+The hostname or ip which identifies the server.
+Default: B<127.0.0.1>
+
+=item B<Port> I<Service/Port>
+
+The query port of the server. This needs to be a string, even if the port is
+given in its numeric form.
+Default: B<1978>
+
+=back
+
+=head2 Plugin C<unixsock>
+
+=over 4
+
+=item B<SocketFile> I<Path>
+
+Sets the socket-file which is to be created.
+
+=item B<SocketGroup> I<Group>
+
+If running as root change the group of the UNIX-socket after it has been
+created. Defaults to B<collectd>.
+
+=item B<SocketPerms> I<Permissions>
+
+Change the file permissions of the UNIX-socket after it has been created. The
+permissions must be given as a numeric, octal value as you would pass to
+L<chmod(1)>. Defaults to B<0770>.
+
+=item B<DeleteSocket> B<false>|B<true>
+
+If set to B<true>, delete the socket file before calling L<bind(2)>, if a file
+with the given name already exists. If I<collectd> crashes a socket file may be
+left over, preventing the daemon from opening a new socket when restarted.
+Since this is potentially dangerous, this defaults to B<false>.
+
+=back
+
+=head2 Plugin C<uuid>
+
+This plugin, if loaded, causes the Hostname to be taken from the machine's
+UUID. The UUID is a universally unique designation for the machine, usually
+taken from the machine's BIOS. This is most useful if the machine is running in
+a virtual environment such as Xen, in which case the UUID is preserved across
+shutdowns and migration.
+
+The following methods are used to find the machine's UUID, in order:
+
+=over 4
+
+=item
+
+Check I</etc/uuid> (or I<UUIDFile>).
+
+=item
+
+Check for UUID from HAL (L<http://www.freedesktop.org/wiki/Software/hal>) if
+present.
+
+=item
+
+Check for UUID from C<dmidecode> / SMBIOS.
+
+=item
+
+Check for UUID from Xen hypervisor.
+
+=back
+
+If no UUID can be found then the hostname is not modified.
+
+=over 4
+
+=item B<UUIDFile> I<Path>
+
+Take the UUID from the given file (default I</etc/uuid>).
+
+=back
+
+=head2 Plugin C<varnish>
+
+The Varnish plugin collects information about Varnish, an HTTP accelerator.
+
+=over 4
+
+=item B<CollectCache> B<true>|B<false>
+
+Cache hits and misses. True by default.
+
+=item B<CollectConnections> B<true>|B<false>
+
+Number of client connections received, accepted and dropped. True by default.
+
+=item B<CollectBackend> B<true>|B<false>
+
+Back-end connection statistics, such as successful, reused,
+and closed connections. True by default.
+
+=item B<CollectSHM> B<true>|B<false>
+
+Statistics about the shared memory log, a memory region to store
+log messages which is flushed to disk when full. True by default.
+
+=item B<CollectESI> B<true>|B<false>
+
+Edge Side Includes (ESI) parse statistics. False by default.
+
+=item B<CollectFetch> B<true>|B<false>
+
+Statistics about fetches (HTTP requests sent to the backend). False by default.
+
+=item B<CollectHCB> B<true>|B<false>
+
+Inserts and look-ups in the crit bit tree based hash. Look-ups are
+divided into locked and unlocked look-ups. False by default.
+
+=item B<CollectSMA> B<true>|B<false>
+
+malloc or umem (umem_alloc(3MALLOC) based) storage statistics.
+The umem storage component is Solaris specific. False by default.
+
+=item B<CollectSMS> B<true>|B<false>
+
+synth (synthetic content) storage statistics. This storage
+component is used internally only. False by default.
+
+=item B<CollectSM> B<true>|B<false>
+
+file (memory mapped file) storage statistics. False by default.
+
+=item B<CollectTotals> B<true>|B<false>
+
+Collects overview counters, such as the number of sessions created,
+the number of requests and bytes transferred. False by default.
+
+=item B<CollectWorkers> B<true>|B<false>
+
+Collect statistics about worker threads. False by default.
+
+=back
+
+=head2 Plugin C<vmem>
+
+The C<vmem> plugin collects information about the usage of virtual memory.
+Since the statistics provided by the Linux kernel are very detailed, they are
+collected very detailed. However, to get all the details, you have to switch
+them on manually. Most people just want an overview over, such as the number of
+pages read from swap space.
+
+=over 4
+
+=item B<Verbose> B<true>|B<false>
+
+Enables verbose collection of information. This will start collecting page
+"actions", e.E<nbsp>g. page allocations, (de)activations, steals and so on.
+Part of these statistics are collected on a "per zone" basis.
+
+=back
+
+=head2 Plugin C<vserver>
+
+This plugin doesn't have any options. B<VServer> support is only available for
+Linux. It cannot yet be found in a vanilla kernel, though. To make use of this
+plugin you need a kernel that has B<VServer> support built in, i.E<nbsp>e. you
+need to apply the patches and compile your own kernel, which will then provide
+the F</proc/virtual> filesystem that is required by this plugin.
+
+The B<VServer> homepage can be found at L<http://linux-vserver.org/>.
+
+B<Note>: The traffic collected by this plugin accounts for the amount of
+traffic passing a socket which might be a lot less than the actual on-wire
+traffic (e.E<nbsp>g. due to headers and retransmission). If you want to
+collect on-wire traffic you could, for example, use the logging facilities of
+iptables to feed data for the guest IPs into the iptables plugin.
+
+=head2 Plugin C<write_http>
+
+This output plugin submits values to an http server by POST them using the
+PUTVAL plain-text protocol. Each destination you want to post data to needs to
+have one B<URL> block, within which the destination can be configured further,
+for example by specifying authentication data.
+
+Synopsis:
+
+ <Plugin "write_http">
+   <URL "http://example.com/post-collectd">
+     User "collectd"
+     Password "weCh3ik0"
+   </URL>
+ </Plugin>
+
+B<URL> blocks need one string argument which is used as the URL to which data
+is posted. The following options are understood within B<URL> blocks.
+
+=over 4
+
+=item B<User> I<Username>
+
+Optional user name needed for authentication.
+
+=item B<Password> I<Password>
+
+Optional password needed for authentication.
+
+=item B<VerifyPeer> B<true>|B<false>
+
+Enable or disable peer SSL certificate verification. See
+L<http://curl.haxx.se/docs/sslcerts.html> for details. Enabled by default.
+
+=item B<VerifyHost> B<true|false>
+
+Enable or disable peer host name verification. If enabled, the plugin checks if
+the C<Common Name> or a C<Subject Alternate Name> field of the SSL certificate
+matches the host name provided by the B<URL> option. If this identity check
+fails, the connection is aborted. Obviously, only works when connecting to a
+SSL enabled server. Enabled by default.
+
+=item B<CACert> I<File>
+
+File that holds one or more SSL certificates. If you want to use HTTPS you will
+possibly need this option. What CA certificates come bundled with C<libcurl>
+and are checked by default depends on the distribution you use.
+
+=item B<Format> B<Command>|B<JSON>
+
+Format of the output to generate. If set to B<Command>, will create output that
+is understood by the I<Exec> and I<UnixSock> plugins. When set to B<JSON>, will
+create output in the I<JavaScript Object Notation> (JSON).
+
+Defaults to B<Command>.
+
+=item B<StoreRates> B<true|false>
+
+If set to B<true>, convert counter values to rates. If set to B<false> (the
+default) counter values are stored as is, i.E<nbsp>e. as an increasing integer
+number.
+
+=back
+
+=head1 FILTER CONFIGURATION
+
+Starting with collectd 4.6 there is a powerful filtering infrastructure
+implemented in the daemon. The concept has mostly been copied from
+I<ip_tables>, the packet filter infrastructure for Linux. We'll use a similar
+terminology, so that users that are familiar with iptables feel right at home.
+
+=head2 Terminology
+
+The following are the terms used in the remainder of the filter configuration
+documentation. For an ASCII-art schema of the mechanism, see
+L<"General structure"> below.
+
+=over 4
+
+=item B<Match>
+
+A I<match> is a criteria to select specific values. Examples are, of course, the
+name of the value or it's current value.
+
+Matches are implemented in plugins which you have to load prior to using the
+match. The name of such plugins starts with the "match_" prefix.
+
+=item B<Target>
+
+A I<target> is some action that is to be performed with data. Such actions
+could, for example, be to change part of the value's identifier or to ignore
+the value completely.
+
+Some of these targets are built into the daemon, see L<"Built-in targets">
+below. Other targets are implemented in plugins which you have to load prior to
+using the target. The name of such plugins starts with the "target_" prefix.
+
+=item B<Rule>
+
+The combination of any number of matches and at least one target is called a
+I<rule>. The target actions will be performed for all values for which B<all>
+matches apply. If the rule does not have any matches associated with it, the
+target action will be performed for all values.
+
+=item B<Chain>
+
+A I<chain> is a list of rules and possibly default targets. The rules are tried
+in order and if one matches, the associated target will be called. If a value
+is handled by a rule, it depends on the target whether or not any subsequent
+rules are considered or if traversal of the chain is aborted, see
+L<"Flow control"> below. After all rules have been checked, the default targets
+will be executed.
+
+=back
+
+=head2 General structure
+
+The following shows the resulting structure:
+
+ +---------+
+ ! Chain   !
+ +---------+
+      !
+      V
+ +---------+  +---------+  +---------+  +---------+
+ ! Rule    !->! Match   !->! Match   !->! Target  !
+ +---------+  +---------+  +---------+  +---------+
+      !
+      V
+ +---------+  +---------+  +---------+
+ ! Rule    !->! Target  !->! Target  !
+ +---------+  +---------+  +---------+
+      !
+      V
+      :
+      :
+      !
+      V
+ +---------+  +---------+  +---------+
+ ! Rule    !->! Match   !->! Target  !
+ +---------+  +---------+  +---------+
+      !
+      V
+ +---------+
+ ! Default !
+ ! Target  !
+ +---------+
+
+=head2 Flow control
+
+There are four ways to control which way a value takes through the filter
+mechanism:
+
+=over 4
+
+=item B<jump>
+
+The built-in B<jump> target can be used to "call" another chain, i.E<nbsp>e.
+process the value with another chain. When the called chain finishes, usually
+the next target or rule after the jump is executed.
+
+=item B<stop>
+
+The stop condition, signaled for example by the built-in target B<stop>, causes
+all processing of the value to be stopped immediately.
+
+=item B<return>
+
+Causes processing in the current chain to be aborted, but processing of the
+value generally will continue. This means that if the chain was called via
+B<Jump>, the next target or rule after the jump will be executed. If the chain
+was not called by another chain, control will be returned to the daemon and it
+may pass the value to another chain.
+
+=item B<continue>
+
+Most targets will signal the B<continue> condition, meaning that processing
+should continue normally. There is no special built-in target for this
+condition.
+
+=back
+
+=head2 Synopsis
+
+The configuration reflects this structure directly:
+
+ PostCacheChain "PostCache"
+ <Chain "PostCache">
+   <Rule "ignore_mysql_show">
+     <Match "regex">
+       Plugin "^mysql$"
+       Type "^mysql_command$"
+       TypeInstance "^show_"
+     </Match>
+     <Target "stop">
+     </Target>
+   </Rule>
+   <Target "write">
+     Plugin "rrdtool"
+   </Target>
+ </Chain>
+
+The above configuration example will ignore all values where the plugin field
+is "mysql", the type is "mysql_command" and the type instance begins with
+"show_". All other values will be sent to the C<rrdtool> write plugin via the
+default target of the chain. Since this chain is run after the value has been
+added to the cache, the MySQL C<show_*> command statistics will be available
+via the C<unixsock> plugin.
+
+=head2 List of configuration options
+
+=over 4
+
+=item B<PreCacheChain> I<ChainName>
+
+=item B<PostCacheChain> I<ChainName>
+
+Configure the name of the "pre-cache chain" and the "post-cache chain". The
+argument is the name of a I<chain> that should be executed before and/or after
+the values have been added to the cache.
+
+To understand the implications, it's important you know what is going on inside
+I<collectd>. The following diagram shows how values are passed from the
+read-plugins to the write-plugins:
+
+   +---------------+
+   !  Read-Plugin  !
+   +-------+-------+
+           !
+ + - - - - V - - - - +
+ : +---------------+ :
+ : !   Pre-Cache   ! :
+ : !     Chain     ! :
+ : +-------+-------+ :
+ :         !         :
+ :         V         :
+ : +-------+-------+ :  +---------------+
+ : !     Cache     !--->!  Value Cache  !
+ : !     insert    ! :  +---+---+-------+
+ : +-------+-------+ :      !   !
+ :         !   ,------------'   !
+ :         V   V     :          V
+ : +-------+---+---+ :  +-------+-------+
+ : !  Post-Cache   +--->! Write-Plugins !
+ : !     Chain     ! :  +---------------+
+ : +---------------+ :
+ :                   :
+ :  dispatch values  :
+ + - - - - - - - - - +
+
+After the values are passed from the "read" plugins to the dispatch functions,
+the pre-cache chain is run first. The values are added to the internal cache
+afterwards. The post-cache chain is run after the values have been added to the
+cache. So why is it such a huge deal if chains are run before or after the
+values have been added to this cache?
+
+Targets that change the identifier of a value list should be executed before
+the values are added to the cache, so that the name in the cache matches the
+name that is used in the "write" plugins. The C<unixsock> plugin, too, uses
+this cache to receive a list of all available values. If you change the
+identifier after the value list has been added to the cache, this may easily
+lead to confusion, but it's not forbidden of course.
+
+The cache is also used to convert counter values to rates. These rates are, for
+example, used by the C<value> match (see below). If you use the rate stored in
+the cache B<before> the new value is added, you will use the old, B<previous>
+rate. Write plugins may use this rate, too, see the C<csv> plugin, for example.
+The C<unixsock> plugin uses these rates too, to implement the C<GETVAL>
+command.
+
+Last but not last, the B<stop> target makes a difference: If the pre-cache
+chain returns the stop condition, the value will not be added to the cache and
+the post-cache chain will not be run.
+
+=item B<Chain> I<Name>
+
+Adds a new chain with a certain name. This name can be used to refer to a
+specific chain, for example to jump to it.
+
+Within the B<Chain> block, there can be B<Rule> blocks and B<Target> blocks.
+
+=item B<Rule> [I<Name>]
+
+Adds a new rule to the current chain. The name of the rule is optional and
+currently has no meaning for the daemon.
+
+Within the B<Rule> block, there may be any number of B<Match> blocks and there
+must be at least one B<Target> block.
+
+=item B<Match> I<Name>
+
+Adds a match to a B<Rule> block. The name specifies what kind of match should
+be performed. Available matches depend on the plugins that have been loaded.
+
+The arguments inside the B<Match> block are passed to the plugin implementing
+the match, so which arguments are valid here depends on the plugin being used.
+If you do not need any to pass any arguments to a match, you can use the
+shorter syntax:
+
+ Match "foobar"
+
+Which is equivalent to:
+
+ <Match "foobar">
+ </Match>
+
+=item B<Target> I<Name>
+
+Add a target to a rule or a default target to a chain. The name specifies what
+kind of target is to be added. Which targets are available depends on the
+plugins being loaded.
+
+The arguments inside the B<Target> block are passed to the plugin implementing
+the target, so which arguments are valid here depends on the plugin being used.
+If you do not need any to pass any arguments to a target, you can use the
+shorter syntax:
+
+ Target "stop"
+
+This is the same as writing:
+
+ <Target "stop">
+ </Target>
+
+=back
+
+=head2 Built-in targets
+
+The following targets are built into the core daemon and therefore need no
+plugins to be loaded:
+
+=over 4
+
+=item B<return>
+
+Signals the "return" condition, see the L<"Flow control"> section above. This
+causes the current chain to stop processing the value and returns control to
+the calling chain. The calling chain will continue processing targets and rules
+just after the B<jump> target (see below). This is very similar to the
+B<RETURN> target of iptables, see L<iptables(8)>.
+
+This target does not have any options.
+
+Example:
+
+ Target "return"
+
+=item B<stop>
+
+Signals the "stop" condition, see the L<"Flow control"> section above. This
+causes processing of the value to be aborted immediately. This is similar to
+the B<DROP> target of iptables, see L<iptables(8)>.
+
+This target does not have any options.
+
+Example:
+
+ Target "stop"
+
+=item B<write>
+
+Sends the value to "write" plugins.
+
+Available options:
+
+=over 4
+
+=item B<Plugin> I<Name>
+
+Name of the write plugin to which the data should be sent. This option may be
+given multiple times to send the data to more than one write plugin.
+
+=back
+
+If no plugin is explicitly specified, the values will be sent to all available
+write plugins.
+
+Example:
+
+ <Target "write">
+   Plugin "rrdtool"
+ </Target>
+
+=item B<jump>
+
+Starts processing the rules of another chain, see L<"Flow control"> above. If
+the end of that chain is reached, or a stop condition is encountered,
+processing will continue right after the B<jump> target, i.E<nbsp>e. with the
+next target or the next rule. This is similar to the B<-j> command line option
+of iptables, see L<iptables(8)>.
+
+Available options:
+
+=over 4
+
+=item B<Chain> I<Name>
+
+Jumps to the chain I<Name>. This argument is required and may appear only once.
+
+=back
+
+Example:
+
+ <Target "jump">
+   Chain "foobar"
+ </Target>
+
+=back
+
+=head2 Available matches
+
+=over 4
+
+=item B<regex>
+
+Matches a value using regular expressions.
+
+Available options:
+
+=over 4
+
+=item B<Host> I<Regex>
+
+=item B<Plugin> I<Regex>
+
+=item B<PluginInstance> I<Regex>
+
+=item B<Type> I<Regex>
+
+=item B<TypeInstance> 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.
+
+=item B<Invert> B<false>|B<true>
+
+When set to B<true>, the result of the match is inverted, i.e. all value lists
+where all regular expressions apply are not matched, all other value lists are
+matched. Defaults to B<false>.
+
+=back
+
+Example:
+
+ <Match "regex">
+   Host "customer[0-9]+"
+   Plugin "^foobar$"
+ </Match>
+
+=item B<timediff>
+
+Matches values that have a time which differs from the time on the server.
+
+This match is mainly intended for servers that receive values over the
+C<network> plugin and write them to disk using the C<rrdtool> plugin. RRDtool
+is very sensitive to the timestamp used when updating the RRD files. In
+particular, the time must be ever increasing. If a misbehaving client sends one
+packet with a timestamp far in the future, all further packets with a correct
+time will be ignored because of that one packet. What's worse, such corrupted
+RRD files are hard to fix.
+
+This match lets one match all values B<outside> a specified time range
+(relative to the server's time), so you can use the B<stop> target (see below)
+to ignore the value, for example.
+
+Available options:
+
+=over 4
+
+=item B<Future> I<Seconds>
+
+Matches all values that are I<ahead> of the server's time by I<Seconds> or more
+seconds. Set to zero for no limit. Either B<Future> or B<Past> must be
+non-zero.
+
+=item B<Past> I<Seconds>
+
+Matches all values that are I<behind> of the server's time by I<Seconds> or
+more seconds. Set to zero for no limit. Either B<Future> or B<Past> must be
+non-zero.
+
+=back
+
+Example:
+
+ <Match "timediff">
+   Future  300
+   Past   3600
+ </Match>
+
+This example matches all values that are five minutes or more ahead of the
+server or one hour (or more) lagging behind.
+
+=item B<value>
+
+Matches the actual value of data sources against given minimumE<nbsp>/ maximum
+values. If a data-set consists of more than one data-source, all data-sources
+must match the specified ranges for a positive match.
+
+Available options:
+
+=over 4
+
+=item B<Min> I<Value>
+
+Sets the smallest value which still results in a match. If unset, behaves like
+negative infinity.
+
+=item B<Max> I<Value>
+
+Sets the largest value which still results in a match. If unset, behaves like
+positive infinity.
+
+=item B<Invert> B<true>|B<false>
+
+Inverts the selection. If the B<Min> and B<Max> settings result in a match,
+no-match is returned and vice versa. Please note that the B<Invert> setting
+only effects how B<Min> and B<Max> are applied to a specific value. Especially
+the B<DataSource> and B<Satisfy> settings (see below) are not inverted.
+
+=item B<DataSource> I<DSName> [I<DSName> ...]
+
+Select one or more of the data sources. If no data source is configured, all
+data sources will be checked. If the type handled by the match does not have a
+data source of the specified name(s), this will always result in no match
+(independent of the B<Invert> setting).
+
+=item B<Satisfy> B<Any>|B<All>
+
+Specifies how checking with several data sources is performed. If set to
+B<Any>, the match succeeds if one of the data sources is in the configured
+range. If set to B<All> the match only succeeds if all data sources are within
+the configured range. Default is B<All>.
+
+Usually B<All> is used for positive matches, B<Any> is used for negative
+matches. This means that with B<All> you usually check that all values are in a
+"good" range, while with B<Any> you check if any value is within a "bad" range
+(or outside the "good" range).
+
+=back
+
+Either B<Min> or B<Max>, but not both, may be unset.
+
+Example:
+
+ # Match all values smaller than or equal to 100. Matches only if all data
+ # sources are below 100.
+ <Match "value">
+   Max 100
+   Satisfy "All"
+ </Match>
+ # Match if the value of any data source is outside the range of 0 - 100.
+ <Match "value">
+   Min   0
+   Max 100
+   Invert true
+   Satisfy "Any"
+ </Match>
+
+=item B<empty_counter>
+
+Matches all values with one or more data sources of type B<COUNTER> and where
+all counter values are zero. These counters usually I<never> increased since
+they started existing (and are therefore uninteresting), or got reset recently
+or overflowed and you had really, I<really> bad luck.
+
+Please keep in mind that ignoring such counters can result in confusing
+behavior: Counters which hardly ever increase will be zero for long periods of
+time. If the counter is reset for some reason (machine or service restarted,
+usually), the graph will be empty (NAN) for a long time. People may not
+understand why.
+
+=item B<hashed>
+
+Calculates a hash value of the host name and matches values according to that
+hash value. This makes it possible to divide all hosts into groups and match
+only values that are in a specific group. The intended use is in load
+balancing, where you want to handle only part of all data and leave the rest
+for other servers.
+
+The hashing function used tries to distribute the hosts evenly. First, it
+calculates a 32E<nbsp>bit hash value using the characters of the hostname:
+
+  hash_value = 0;
+  for (i = 0; host[i] != 0; i++)
+    hash_value = (hash_value * 251) + host[i];
+
+The constant 251 is a prime number which is supposed to make this hash value
+more random. The code then checks the group for this host according to the
+I<Total> and I<Match> arguments:
+
+  if ((hash_value % Total) == Match)
+    matches;
+  else
+    does not match;
+
+Please note that when you set I<Total> to two (i.E<nbsp>e. you have only two
+groups), then the least significant bit of the hash value will be the XOR of
+all least significant bits in the host name. One consequence is that when you
+have two hosts, "server0.example.com" and "server1.example.com", where the host
+name differs in one digit only and the digits differ by one, those hosts will
+never end up in the same group.
+
+Available options:
+
+=over 4
+
+=item B<Match> I<Match> I<Total>
+
+Divide the data into I<Total> groups and match all hosts in group I<Match> as
+described above. The groups are numbered from zero, i.E<nbsp>e. I<Match> must
+be smaller than I<Total>. I<Total> must be at least one, although only values
+greater than one really do make any sense.
+
+You can repeat this option to match multiple groups, for example:
+
+  Match 3 7
+  Match 5 7
+
+The above config will divide the data into seven groups and match groups three
+and five. One use would be to keep every value on two hosts so that if one
+fails the missing data can later be reconstructed from the second host.
+
+=back
+
+Example:
+
+ # Operate on the pre-cache chain, so that ignored values are not even in the
+ # global cache.
+ <Chain "PreCache">
+   <Rule>
+     <Match "hashed">
+       # Divide all received hosts in seven groups and accept all hosts in
+       # group three.
+       Match 3 7
+     </Match>
+     # If matched: Return and continue.
+     Target "return"
+   </Rule>
+   # If not matched: Return and stop.
+   Target "stop"
+ </Chain>
+
+=back
+
+=head2 Available targets
+
+=over 4
+
+=item B<notification>
+
+Creates and dispatches a notification.
+
+Available options:
+
+=over 4
+
+=item B<Message> I<String>
+
+This required option sets the message of the notification. 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<%{ds:>I<name>B<}>
+
+These placeholders are replaced by a (hopefully) human readable representation
+of the current rate of this data source. If you changed the instance name
+(using the B<set> or B<replace> targets, see below), it may not be possible to
+convert counter values to rates.
+
+=back
+
+Please note that these placeholders are B<case sensitive>!
+
+=item B<Severity> B<"FATAL">|B<"WARNING">|B<"OKAY">
+
+Sets the severity of the message. If omitted, the severity B<"WARNING"> is
+used.
+
+=back
+
+Example:
+
+  <Target "notification">
+    Message "Oops, the %{type_instance} temperature is currently %{ds:value}!"
+    Severity "WARNING"
+  </Target>
+
+=item B<replace>
+
+Replaces parts of the identifier using regular expressions.
+
+Available options:
+
+=over 4
+
+=item B<Host> I<Regex> I<Replacement>
+
+=item B<Plugin> I<Regex> I<Replacement>
+
+=item B<PluginInstance> I<Regex> I<Replacement>
+
+=item B<TypeInstance> I<Regex> I<Replacement>
+
+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
+expression, only the first occurrence will be replaced.
+
+You can specify each option multiple times to use multiple regular expressions
+one after another.
+
+=back
+
+Example:
+
+ <Target "replace">
+   # Replace "example.net" with "example.com"
+   Host "\\<example.net\\>" "example.com"
+   # Strip "www." from hostnames
+   Host "\\<www\\." ""
+ </Target>
+
+=item B<set>
+
+Sets part of the identifier of a value to a given string.
+
+Available options:
+
+=over 4
+
+=item B<Host> I<String>
+
+=item B<Plugin> I<String>
+
+=item B<PluginInstance> I<String>
+
+=item B<TypeInstance> 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.
+
+=back
+
+Example:
+
+ <Target "set">
+   PluginInstance "coretemp"
+   TypeInstance "core3"
+ </Target>
+
+=back
+
+=head2 Backwards compatibility
+
+If you use collectd with an old configuration, i.E<nbsp>e. one without a
+B<Chain> block, it will behave as it used to. This is equivalent to the
+following configuration:
+
+ <Chain "PostCache">
+   Target "write"
+ </Chain>
+
+If you specify a B<PostCacheChain>, the B<write> target will not be added
+anywhere and you will have to make sure that it is called where appropriate. We
+suggest to add the above snippet as default target to your "PostCache" chain.
+
+=head2 Examples
+
+Ignore all values, where the hostname does not contain a dot, i.E<nbsp>e. can't
+be an FQDN.
+
+ <Chain "PreCache">
+   <Rule "no_fqdn">
+     <Match "regex">
+       Host "^[^\.]*$"
+     </Match>
+     Target "stop"
+   </Rule>
+   Target "write"
+ </Chain>
+
+=head1 SEE ALSO
+
+L<collectd(1)>,
+L<collectd-exec(5)>,
+L<collectd-perl(5)>,
+L<collectd-unixsock(5)>,
+L<types.db(5)>,
+L<hddtemp(8)>,
+L<iptables(8)>,
+L<kstat(3KSTAT)>,
+L<mbmon(1)>,
+L<psql(1)>,
+L<regex(7)>,
+L<rrdtool(1)>,
+L<sensors(1)>
+
+=head1 AUTHOR
+
+Florian Forster E<lt>octo@verplant.orgE<gt>
+
+=cut
diff --git a/src/collectd.h b/src/collectd.h
new file mode 100644 (file)
index 0000000..4079ad1
--- /dev/null
@@ -0,0 +1,295 @@
+/**
+ * collectd - src/collectd.h
+ * Copyright (C) 2005,2006  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef COLLECTD_H
+#define COLLECTD_H
+
+#if HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdio.h>
+#if HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#if HAVE_SYS_STAT_H
+# include <sys/stat.h>
+#endif
+#if STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# if HAVE_STDLIB_H
+#  include <stdlib.h>
+# endif
+#endif
+#if HAVE_STRING_H
+# if !STDC_HEADERS && HAVE_MEMORY_H
+#  include <memory.h>
+# endif
+# include <string.h>
+#endif
+#if HAVE_STRINGS_H
+# include <strings.h>
+#endif
+#if HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#if HAVE_SYS_WAIT_H
+# include <sys/wait.h>
+#endif
+#ifndef WEXITSTATUS
+# define WEXITSTATUS(stat_val) ((unsigned int) (stat_val) >> 8)
+#endif
+#ifndef WIFEXITED
+# define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
+#endif
+#if HAVE_SIGNAL_H
+# include <signal.h>
+#endif
+#if HAVE_FCNTL_H
+# include <fcntl.h>
+#endif
+#if HAVE_ERRNO_H
+# include <errno.h>
+#endif
+#if HAVE_LIMITS_H
+# include <limits.h>
+#endif
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+#  include <sys/time.h>
+# else
+#  include <time.h>
+# endif
+#endif
+
+#if HAVE_ASSERT_H
+# include <assert.h>
+#else
+# define assert(...) /* nop */
+#endif
+
+#if !defined(HAVE__BOOL) || !HAVE__BOOL
+typedef int _Bool;
+# undef HAVE__BOOL
+# define HAVE__BOOL 1
+#endif
+
+#if NAN_STATIC_DEFAULT
+# include <math.h>
+/* #endif NAN_STATIC_DEFAULT*/
+#elif NAN_STATIC_ISOC
+# ifndef __USE_ISOC99
+#  define DISABLE_ISOC99 1
+#  define __USE_ISOC99 1
+# endif /* !defined(__USE_ISOC99) */
+# include <math.h>
+# if DISABLE_ISOC99
+#  undef DISABLE_ISOC99
+#  undef __USE_ISOC99
+# endif /* DISABLE_ISOC99 */
+/* #endif NAN_STATIC_ISOC */
+#elif NAN_ZERO_ZERO
+# include <math.h>
+# ifdef NAN
+#  undef NAN
+# endif
+# define NAN (0.0 / 0.0)
+# ifndef isnan
+#  define isnan(f) ((f) != (f))
+# endif /* !defined(isnan) */
+# ifndef isfinite
+#  define isfinite(f) (((f) - (f)) == 0.0)
+# endif
+# ifndef isinf
+#  define isinf(f) (!isfinite(f) && !isnan(f))
+# endif
+#endif /* NAN_ZERO_ZERO */
+
+/* Try really, really hard to determine endianess. Under NexentaStor 1.0.2 this
+ * information is in <sys/isa_defs.h>, possibly some other Solaris versions do
+ * this too.. */
+#if HAVE_ENDIAN_H
+# include <endian.h>
+#elif HAVE_SYS_ISA_DEFS_H
+# include <sys/isa_defs.h>
+#endif
+
+#ifndef BYTE_ORDER
+# if defined(_BYTE_ORDER)
+#  define BYTE_ORDER _BYTE_ORDER
+# elif defined(__BYTE_ORDER)
+#  define BYTE_ORDER __BYTE_ORDER
+# elif defined(__DARWIN_BYTE_ORDER)
+#  define BYTE_ORDER __DARWIN_BYTE_ORDER
+# endif
+#endif
+#ifndef BIG_ENDIAN
+# if defined(_BIG_ENDIAN)
+#  define BIG_ENDIAN _BIG_ENDIAN
+# elif defined(__BIG_ENDIAN)
+#  define BIG_ENDIAN __BIG_ENDIAN
+# elif defined(__DARWIN_BIG_ENDIAN)
+#  define BIG_ENDIAN __DARWIN_BIG_ENDIAN
+# endif
+#endif
+#ifndef LITTLE_ENDIAN
+# if defined(_LITTLE_ENDIAN)
+#  define LITTLE_ENDIAN _LITTLE_ENDIAN
+# elif defined(__LITTLE_ENDIAN)
+#  define LITTLE_ENDIAN __LITTLE_ENDIAN
+# elif defined(__DARWIN_LITTLE_ENDIAN)
+#  define LITTLE_ENDIAN __DARWIN_LITTLE_ENDIAN
+# endif
+#endif
+#ifndef BYTE_ORDER
+# if defined(BIG_ENDIAN) && !defined(LITTLE_ENDIAN)
+#  undef BIG_ENDIAN
+#  define BIG_ENDIAN 4321
+#  define LITTLE_ENDIAN 1234
+#  define BYTE_ORDER BIG_ENDIAN
+# elif !defined(BIG_ENDIAN) && defined(LITTLE_ENDIAN)
+#  undef LITTLE_ENDIAN
+#  define BIG_ENDIAN 4321
+#  define LITTLE_ENDIAN 1234
+#  define BYTE_ORDER LITTLE_ENDIAN
+# endif
+#endif
+#if !defined(BYTE_ORDER) || !defined(BIG_ENDIAN)
+# error "Cannot determine byte order"
+#endif
+
+#if HAVE_DIRENT_H
+# include <dirent.h>
+# define NAMLEN(dirent) strlen((dirent)->d_name)
+#else
+# define dirent direct
+# define NAMLEN(dirent) (dirent)->d_namlen
+# if HAVE_SYS_NDIR_H
+#  include <sys/ndir.h>
+# endif
+# if HAVE_SYS_DIR_H
+#  include <sys/dir.h>
+# endif
+# if HAVE_NDIR_H
+#  include <ndir.h>
+# endif
+#endif
+
+#if HAVE_STDARG_H
+# include <stdarg.h>
+#endif
+#if HAVE_CTYPE_H
+# include <ctype.h>
+#endif
+#if HAVE_SYS_PARAM_H
+# include <sys/param.h>
+#endif
+
+#if HAVE_KSTAT_H
+# include <kstat.h>
+#endif
+
+#ifndef PACKAGE_NAME
+#define PACKAGE_NAME "collectd"
+#endif
+
+#ifndef PREFIX
+#define PREFIX "/opt/" PACKAGE_NAME
+#endif
+
+#ifndef SYSCONFDIR
+#define SYSCONFDIR PREFIX "/etc"
+#endif
+
+#ifndef CONFIGFILE
+#define CONFIGFILE SYSCONFDIR"/collectd.conf"
+#endif
+
+#ifndef LOCALSTATEDIR
+#define LOCALSTATEDIR PREFIX "/var"
+#endif
+
+#ifndef PKGLOCALSTATEDIR
+#define PKGLOCALSTATEDIR PREFIX "/var/lib/" PACKAGE_NAME
+#endif
+
+#ifndef PIDFILE
+#define PIDFILE PREFIX "/var/run/" PACKAGE_NAME ".pid"
+#endif
+
+#ifndef PLUGINDIR
+#define PLUGINDIR PREFIX "/lib/" PACKAGE_NAME
+#endif
+
+#ifndef PKGDATADIR
+#define PKGDATADIR PREFIX "/share/" PACKAGE_NAME
+#endif
+
+#ifndef COLLECTD_GRP_NAME
+# define COLLECTD_GRP_NAME "collectd"
+#endif
+
+#define STATIC_ARRAY_LEN(array) (sizeof (array) / sizeof ((array)[0]))
+
+/* Remove GNU specific __attribute__ settings when using another compiler */
+#if !__GNUC__
+# define __attribute__(x) /**/
+#endif
+
+#if defined(COLLECT_DEBUG) && COLLECT_DEBUG && defined(__GNUC__) && __GNUC__
+# undef strcpy
+# undef strcat
+# undef strtok
+# pragma GCC poison strcpy strcat strtok
+#endif
+
+/* 
+ * Special hack for the perl plugin: Because the later included perl.h defines
+ * a macro which is never used, but contains `sprintf', we cannot poison that
+ * identifies just yet. The parl plugin will do that itself once perl.h is
+ * included.
+ */
+#ifndef DONT_POISON_SPRINTF_YET
+# if defined(COLLECT_DEBUG) && COLLECT_DEBUG && defined(__GNUC__) && __GNUC__
+#  undef sprintf
+#  pragma GCC poison sprintf
+# endif
+#endif
+
+/* Type for time as used by "utils_time.h" */
+typedef uint64_t cdtime_t;
+
+extern char     hostname_g[];
+extern cdtime_t interval_g;
+extern int      timeout_g;
+
+#endif /* COLLECTD_H */
diff --git a/src/collectd.pod b/src/collectd.pod
new file mode 100644 (file)
index 0000000..e36dcdf
--- /dev/null
@@ -0,0 +1,141 @@
+=head1 NAME
+
+collectd - System statistics collection daemon
+
+=head1 SYNOPSIS
+
+collectd I<[options]>
+
+=head1 DESCRIPTION
+
+collectd is a daemon that receives system statistics and makes them available
+in a number of ways. The main daemon itself doesn't have any real functionality
+apart from loading, querying and submitting to plugins. For a description of
+available plugins please see L</PLUGINS> below.
+
+=head1 OPTIONS
+
+Most of collectd's configuration is done using using a configfile. See
+L<collectd.conf(5)> for an in-depth description of all options.
+
+=over 4
+
+=item B<-C> I<E<lt>config-fileE<gt>>
+
+Specify an alternative config file. This is the place to go when you wish to
+change B<collectd>'s behavior. The path may be relative to the current working
+directory.
+
+=item B<-t>
+
+Test the configuration only. The program immediately exits after parsing the
+config file. A return code not equal to zero indicates an error.
+
+=item B<-T>
+
+Test the plugin read callbacks only. The program immediately exits after invoking
+the read callbacks once. A return code not equal to zero indicates an error.
+
+=item B<-P> I<E<lt>pid-fileE<gt>>
+
+Specify an alternative pid file. This overwrites any settings in the config 
+file. This is thought for init-scripts that require the PID-file in a certain
+directory to work correctly. For everyday-usage use the B<PIDFile>
+config-option.
+
+=item B<-f>
+
+Don't fork to the background. I<collectd> will also B<not> close standard file
+descriptors, detach from the session nor write a pid file. This is mainly
+thought for 'supervising' init replacements such as I<runit>.
+
+=item B<-h>
+
+Output usage information and exit.
+
+=back
+
+=head1 PLUGINS
+
+As noted above, the real power of collectd lies within it's plugins. A
+(hopefully complete) list of plugins and short descriptions can be found in the
+F<README> file that is distributed with the sourcecode. If you're using a
+package it's a good bet to search somewhere near F</usr/share/doc/collectd>.
+
+There are two big groups of plugins, B<input> and B<output> plugins:
+
+=over 4
+
+=item
+
+Input plugins are queried periodically. They somehow acquire the current value
+of whatever they where designed to work with and submit these values back to
+the daemon, i. e. they "dispatch" the values. As an example, the C<cpu plugin>
+reads the current cpu-counters of time spent in the various modes (user,
+system, nice, ...) and dispatches these counters to the daemon.
+
+=item
+
+Output plugins get the dispatched values from the daemon and does something
+with them. Common applications are writing to RRD-files, CSV-files or sending
+the data over a network link to a remote box.
+
+=back
+
+Of course not all plugins fit neatly into one of the two above categories. The
+C<network plugin>, for example, is able to send (i.E<nbsp>e. "write") B<and>
+receive (i.E<nbsp>e. "dispatch") values. Also, it opens a socket upon
+initialization and dispatches the values when it receives them and isn't
+triggered at the same time the input plugins are being read. You can think of
+the network receive part as working asynchronous if it helps.
+
+In addition to the above, there are "logging plugins". Right now those are the
+C<logfile plugin> and the C<syslog plugin>. With these plugins collectd can
+provide information about issues and significant situations to the user.
+Several loglevels let you suppress uninteresting messages.
+
+Starting with version C<4.3.0> collectd has support for B<monitoring>. This is
+done by checking thresholds defined by the user. If a value is out of range, a
+notification will be dispatched to "notification plugins". See
+L<collectd.conf(5)> for more detailed information about threshold checking.
+
+Please note that some plugins, that provide other means of communicating with
+the daemon, have manpages of their own to describe their functionality in more
+detail. In particular those are L<collectd-email(5)>, L<collectd-exec(5)>,
+L<collectd-perl(5)>, L<collectd-snmp(5)>, and L<collectd-unixsock(5)>
+
+=head1 SIGNALS
+
+B<collectd> accepts the following signals:
+
+=over 4
+
+=item B<SIGINT>, B<SIGTERM>
+
+These signals cause B<collectd> to shut down all plugins and terminate.
+
+=item B<SIGUSR1>
+
+This signal causes B<collectd> to signal all plugins to flush data from
+internal caches. E.E<nbsp>g. the C<rrdtool plugin> will write all pending data
+to the RRD files. This is the same as using the C<FLUSH -1> command of the
+C<unixsock plugin>.
+
+=back
+
+=head1 SEE ALSO
+
+L<collectd.conf(5)>,
+L<collectd-email(5)>,
+L<collectd-exec(5)>,
+L<collectd-perl(5)>,
+L<collectd-snmp(5)>,
+L<collectd-unixsock(5)>,
+L<types.db(5)>,
+L<http://collectd.org/>
+
+=head1 AUTHOR
+
+Florian Forster E<lt>octo@verplant.orgE<gt>
+
+=cut
diff --git a/src/collectdctl.c b/src/collectdctl.c
new file mode 100644 (file)
index 0000000..3bd8f04
--- /dev/null
@@ -0,0 +1,608 @@
+/**
+ * collectd - src/collectdctl.c
+ * Copyright (C) 2010 Håkon J Dugstad Johnsen
+ * Copyright (C) 2010 Sebastian Harl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Håkon J Dugstad Johnsen <hakon-dugstad.johnsen at telenor.com>
+ *   Sebastian "tokkee" Harl <sh@tokkee.org>
+ **/
+
+#if HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+
+#include <assert.h>
+#include <errno.h>
+
+#if NAN_STATIC_DEFAULT
+# include <math.h>
+/* #endif NAN_STATIC_DEFAULT*/
+#elif NAN_STATIC_ISOC
+# ifndef __USE_ISOC99
+#  define DISABLE_ISOC99 1
+#  define __USE_ISOC99 1
+# endif /* !defined(__USE_ISOC99) */
+# include <math.h>
+# if DISABLE_ISOC99
+#  undef DISABLE_ISOC99
+#  undef __USE_ISOC99
+# endif /* DISABLE_ISOC99 */
+/* #endif NAN_STATIC_ISOC */
+#elif NAN_ZERO_ZERO
+# include <math.h>
+# ifdef NAN
+#  undef NAN
+# endif
+# define NAN (0.0 / 0.0)
+# ifndef isnan
+#  define isnan(f) ((f) != (f))
+# endif /* !defined(isnan) */
+# ifndef isfinite
+#  define isfinite(f) (((f) - (f)) == 0.0)
+# endif
+# ifndef isinf
+#  define isinf(f) (!isfinite(f) && !isnan(f))
+# endif
+#endif /* NAN_ZERO_ZERO */
+
+#include "libcollectdclient/client.h"
+
+#define DEFAULT_SOCK LOCALSTATEDIR"/run/"PACKAGE_NAME"-unixsock"
+
+extern char *optarg;
+extern int   optind;
+
+static void exit_usage (const char *name, int status) {
+  fprintf ((status == 0) ? stdout : stderr,
+      "Usage: %s [options] <command> [cmd options]\n\n"
+
+      "Available options:\n"
+      "  -s       Path to collectd's UNIX socket.\n"
+      "           Default: "DEFAULT_SOCK"\n"
+
+      "\n  -h       Display this help and exit.\n"
+
+      "\nAvailable commands:\n\n"
+
+      " * getval <identifier>\n"
+      " * flush [timeout=<seconds>] [plugin=<name>] [identifier=<id>]\n"
+      " * listval\n"
+      " * putval <identifier> [interval=<seconds>] <value-list(s)>\n"
+
+      "\nIdentifiers:\n\n"
+
+      "An identifier has the following format:\n\n"
+
+      "  [<hostname>/]<plugin>[-<plugin_instance>]/<type>[-<type_instance>]\n\n"
+
+      "Hostname defaults to the local hostname if omitted (e.g., uptime/uptime).\n"
+      "No error is returned if the specified identifier does not exist.\n"
+
+      "\n"PACKAGE" "VERSION", http://collectd.org/\n"
+      "by Florian octo Forster <octo@verplant.org>\n"
+      "for contributions see `AUTHORS'\n"
+      , name);
+  exit (status);
+}
+
+/* Count the number of occurrences of the character 'chr'
+ * in the specified string. */
+static int count_chars (const char *str, char chr) {
+  int count = 0;
+
+  while (*str != '\0') {
+    if (*str == chr) {
+      count++;
+    }
+    str++;
+  }
+
+  return count;
+} /* count_chars */
+
+static int array_grow (void **array, int *array_len, size_t elem_size)
+{
+  void *tmp;
+
+  assert ((array != NULL) && (array_len != NULL));
+
+  tmp = realloc (*array, (*array_len + 1) * elem_size);
+  if (tmp == NULL) {
+    fprintf (stderr, "ERROR: Failed to allocate memory.\n");
+    return (-1);
+  }
+
+  *array = tmp;
+  ++(*array_len);
+  return (0);
+} /* array_grow */
+
+static int parse_identifier (lcc_connection_t *c,
+    const char *value, lcc_identifier_t *ident)
+{
+  char hostname[1024];
+  char ident_str[1024] = "";
+  int  n_slashes;
+
+  int status;
+
+  n_slashes = count_chars (value, '/');
+  if (n_slashes == 1) {
+    /* The user has omitted the hostname part of the identifier
+     * (there is only one '/' in the identifier)
+     * Let's add the local hostname */
+    if (gethostname (hostname, sizeof (hostname)) != 0) {
+      fprintf (stderr, "ERROR: Failed to get local hostname: %s",
+          strerror (errno));
+      return (-1);
+    }
+    hostname[sizeof (hostname) - 1] = '\0';
+
+    snprintf (ident_str, sizeof (ident_str), "%s/%s", hostname, value);
+    ident_str[sizeof(ident_str) - 1] = '\0';
+  }
+  else {
+    strncpy (ident_str, value, sizeof (ident_str));
+    ident_str[sizeof (ident_str) - 1] = '\0';
+  }
+
+  status = lcc_string_to_identifier (c, ident, ident_str);
+  if (status != 0) {
+    fprintf (stderr, "ERROR: Failed to parse identifier ``%s'': %s.\n",
+        ident_str, lcc_strerror(c));
+    return (-1);
+  }
+  return (0);
+} /* parse_identifier */
+
+static int getval (lcc_connection_t *c, int argc, char **argv)
+{
+  lcc_identifier_t ident;
+
+  size_t   ret_values_num   = 0;
+  gauge_t *ret_values       = NULL;
+  char   **ret_values_names = NULL;
+
+  int status;
+  size_t i;
+
+  assert (strcasecmp (argv[0], "getval") == 0);
+
+  if (argc != 2) {
+    fprintf (stderr, "ERROR: getval: Missing identifier.\n");
+    return (-1);
+  }
+
+  memset (&ident, 0, sizeof (ident));
+  status = parse_identifier (c, argv[1], &ident);
+  if (status != 0)
+    return (status);
+
+#define BAIL_OUT(s) \
+  do { \
+    if (ret_values != NULL) \
+      free (ret_values); \
+    if (ret_values_names != NULL) { \
+      for (i = 0; i < ret_values_num; ++i) \
+        free (ret_values_names[i]); \
+      free (ret_values_names); \
+    } \
+    ret_values_num = 0; \
+    return (s); \
+  } while (0)
+
+  status = lcc_getval (c, &ident,
+      &ret_values_num, &ret_values, &ret_values_names);
+  if (status != 0) {
+    fprintf (stderr, "ERROR: %s\n", lcc_strerror (c));
+    BAIL_OUT (-1);
+  }
+
+  for (i = 0; i < ret_values_num; ++i)
+    printf ("%s=%e\n", ret_values_names[i], ret_values[i]);
+  BAIL_OUT (0);
+#undef BAIL_OUT
+} /* getval */
+
+static int flush (lcc_connection_t *c, int argc, char **argv)
+{
+  int timeout = -1;
+
+  lcc_identifier_t *identifiers = NULL;
+  int identifiers_num = 0;
+
+  char **plugins = NULL;
+  int plugins_num = 0;
+
+  int status;
+  int i;
+
+  assert (strcasecmp (argv[0], "flush") == 0);
+
+#define BAIL_OUT(s) \
+  do { \
+    if (identifiers != NULL) \
+      free (identifiers); \
+    identifiers_num = 0; \
+    if (plugins != NULL) \
+      free (plugins); \
+    plugins_num = 0; \
+    return (s); \
+  } while (0)
+
+  for (i = 1; i < argc; ++i) {
+    char *key, *value;
+
+    key   = argv[i];
+    value = strchr (argv[i], (int)'=');
+
+    if (! value) {
+      fprintf (stderr, "ERROR: flush: Invalid option ``%s''.\n", argv[i]);
+      BAIL_OUT (-1);
+    }
+
+    *value = '\0';
+    ++value;
+
+    if (strcasecmp (key, "timeout") == 0) {
+      char *endptr = NULL;
+
+      timeout = (int) strtol (value, &endptr, 0);
+
+      if (endptr == value) {
+        fprintf (stderr, "ERROR: Failed to parse timeout as number: %s.\n",
+            value);
+        BAIL_OUT (-1);
+      }
+      else if ((endptr != NULL) && (*endptr != '\0')) {
+        fprintf (stderr, "WARNING: Ignoring trailing garbage after timeout: "
+            "%s.\n", endptr);
+      }
+    }
+    else if (strcasecmp (key, "plugin") == 0) {
+      status = array_grow ((void *)&plugins, &plugins_num,
+          sizeof (*plugins));
+      if (status != 0)
+        BAIL_OUT (status);
+
+      plugins[plugins_num - 1] = value;
+    }
+    else if (strcasecmp (key, "identifier") == 0) {
+      status = array_grow ((void *)&identifiers, &identifiers_num,
+          sizeof (*identifiers));
+      if (status != 0)
+        BAIL_OUT (status);
+
+      memset (identifiers + (identifiers_num - 1), 0, sizeof (*identifiers));
+      status = parse_identifier (c, value,
+          identifiers + (identifiers_num - 1));
+      if (status != 0)
+        BAIL_OUT (status);
+    }
+    else {
+      fprintf (stderr, "ERROR: flush: Unknown option `%s'.\n", key);
+      BAIL_OUT (-1);
+    }
+  }
+
+  if (plugins_num == 0) {
+    status = array_grow ((void *)&plugins, &plugins_num, sizeof (*plugins));
+    if (status != 0)
+      BAIL_OUT (status);
+
+    assert (plugins_num == 1);
+    plugins[0] = NULL;
+  }
+
+  for (i = 0; i < plugins_num; ++i) {
+    if (identifiers_num == 0) {
+      status = lcc_flush (c, plugins[i], NULL, timeout);
+      if (status != 0)
+        fprintf (stderr, "ERROR: Failed to flush plugin `%s': %s.\n",
+            (plugins[i] == NULL) ? "(all)" : plugins[i], lcc_strerror (c));
+    }
+    else {
+      int j;
+
+      for (j = 0; j < identifiers_num; ++j) {
+        status = lcc_flush (c, plugins[i], identifiers + j, timeout);
+        if (status != 0) {
+          char id[1024];
+
+          lcc_identifier_to_string (c, id, sizeof (id), identifiers + j);
+          fprintf (stderr, "ERROR: Failed to flush plugin `%s', "
+              "identifier `%s': %s.\n",
+              (plugins[i] == NULL) ? "(all)" : plugins[i],
+              id, lcc_strerror (c));
+        }
+      }
+    }
+  }
+
+  BAIL_OUT (0);
+#undef BAIL_OUT
+} /* flush */
+
+static int listval (lcc_connection_t *c, int argc, char **argv)
+{
+  lcc_identifier_t *ret_ident     = NULL;
+  size_t            ret_ident_num = 0;
+
+  int status;
+  size_t i;
+
+  assert (strcasecmp (argv[0], "listval") == 0);
+
+  if (argc != 1) {
+    fprintf (stderr, "ERROR: listval: Does not accept any arguments.\n");
+    return (-1);
+  }
+
+#define BAIL_OUT(s) \
+  do { \
+    if (ret_ident != NULL) \
+      free (ret_ident); \
+    ret_ident_num = 0; \
+    return (s); \
+  } while (0)
+
+  status = lcc_listval (c, &ret_ident, &ret_ident_num);
+  if (status != 0) {
+    fprintf (stderr, "ERROR: %s\n", lcc_strerror (c));
+    BAIL_OUT (status);
+  }
+
+  for (i = 0; i < ret_ident_num; ++i) {
+    char id[1024];
+
+    status = lcc_identifier_to_string (c, id, sizeof (id), ret_ident + i);
+    if (status != 0) {
+      fprintf (stderr, "ERROR: listval: Failed to convert returned "
+          "identifier to a string: %s\n", lcc_strerror (c));
+      continue;
+    }
+
+    printf ("%s\n", id);
+  }
+  BAIL_OUT (0);
+#undef BAIL_OUT
+} /* listval */
+
+static int putval (lcc_connection_t *c, int argc, char **argv)
+{
+  lcc_value_list_t vl = LCC_VALUE_LIST_INIT;
+
+  /* 64 ought to be enough for anybody ;-) */
+  value_t values[64];
+  int     values_types[64];
+  size_t  values_len = 0;
+
+  int status;
+  int i;
+
+  assert (strcasecmp (argv[0], "putval") == 0);
+
+  if (argc < 3) {
+    fprintf (stderr, "ERROR: putval: Missing identifier "
+        "and/or value list.\n");
+    return (-1);
+  }
+
+  vl.values       = values;
+  vl.values_types = values_types;
+
+  status = parse_identifier (c, argv[1], &vl.identifier);
+  if (status != 0)
+    return (status);
+
+  for (i = 2; i < argc; ++i) {
+    char *tmp;
+
+    tmp = strchr (argv[i], (int)'=');
+
+    if (tmp != NULL) { /* option */
+      char *key   = argv[i];
+      char *value = tmp;
+
+      *value = '\0';
+      ++value;
+
+      if (strcasecmp (key, "interval") == 0) {
+        char *endptr;
+
+        vl.interval = strtol (value, &endptr, 0);
+
+        if (endptr == value) {
+          fprintf (stderr, "ERROR: Failed to parse interval as number: %s.\n",
+              value);
+          return (-1);
+        }
+        else if ((endptr != NULL) && (*endptr != '\0')) {
+          fprintf (stderr, "WARNING: Ignoring trailing garbage after "
+              "interval: %s.\n", endptr);
+        }
+      }
+      else {
+        fprintf (stderr, "ERROR: putval: Unknown option `%s'.\n", key);
+        return (-1);
+      }
+    }
+    else { /* value list */
+      char *value;
+
+      tmp = strchr (argv[i], (int)':');
+
+      if (tmp == NULL) {
+        fprintf (stderr, "ERROR: putval: Invalid value list: %s.\n",
+            argv[i]);
+        return (-1);
+      }
+
+      *tmp = '\0';
+      ++tmp;
+
+      if (strcasecmp (argv[i], "N") == 0) {
+        vl.time = 0;
+      }
+      else {
+        char *endptr;
+
+        vl.time = strtol (argv[i], &endptr, 0);
+
+        if (endptr == argv[i]) {
+          fprintf (stderr, "ERROR: Failed to parse time as number: %s.\n",
+              argv[i]);
+          return (-1);
+        }
+        else if ((endptr != NULL) && (*endptr != '\0')) {
+          fprintf (stderr, "ERROR: Garbage after time: %s.\n", endptr);
+          return (-1);
+        }
+      }
+
+      values_len = 0;
+      value = tmp;
+      while (value != 0) {
+        char *dot, *endptr;
+
+        tmp = strchr (argv[i], (int)':');
+
+        if (tmp != NULL) {
+          *tmp = '\0';
+          ++tmp;
+        }
+
+        /* This is a bit of a hack, but parsing types.db just does not make
+         * much sense imho -- the server might have different types defined
+         * anyway. Also, lcc uses the type information for formatting the
+         * number only, so the real meaning does not matter. -tokkee */
+        dot = strchr (value, (int)'.');
+        endptr = NULL;
+        if (strcasecmp (value, "U") == 0) {
+          values[values_len].gauge = NAN;
+          values_types[values_len] = LCC_TYPE_GAUGE;
+        }
+        else if (dot) { /* floating point value */
+          values[values_len].gauge = strtod (value, &endptr);
+          values_types[values_len] = LCC_TYPE_GAUGE;
+        }
+        else { /* integer */
+          values[values_len].counter = strtol (value, &endptr, 0);
+          values_types[values_len] = LCC_TYPE_COUNTER;
+        }
+        ++values_len;
+
+        if (endptr == value) {
+          fprintf (stderr, "ERROR: Failed to parse value as number: %s.\n",
+              argv[i]);
+          return (-1);
+        }
+        else if ((endptr != NULL) && (*endptr != '\0')) {
+          fprintf (stderr, "ERROR: Garbage after value: %s.\n", endptr);
+          return (-1);
+        }
+
+        value = tmp;
+      }
+
+      assert (values_len >= 1);
+      vl.values_len = values_len;
+
+      status = lcc_putval (c, &vl);
+      if (status != 0) {
+        fprintf (stderr, "ERROR: %s\n", lcc_strerror (c));
+        return (-1);
+      }
+    }
+  }
+
+  if (values_len == 0) {
+    fprintf (stderr, "ERROR: putval: Missing value list(s).\n");
+    return (-1);
+  }
+  return (0);
+} /* putval */
+
+int main (int argc, char **argv) {
+  char address[1024] = "unix:"DEFAULT_SOCK;
+
+  lcc_connection_t *c;
+
+  int status;
+
+  while (42) {
+    int c;
+
+    c = getopt (argc, argv, "s:h");
+
+    if (c == -1)
+      break;
+
+    switch (c) {
+      case 's':
+        snprintf (address, sizeof (address), "unix:%s", optarg);
+        address[sizeof (address) - 1] = '\0';
+        break;
+      case 'h':
+        exit_usage (argv[0], 0);
+        break;
+      default:
+        exit_usage (argv[0], 1);
+    }
+  }
+
+  if (optind >= argc) {
+    fprintf (stderr, "%s: missing command\n", argv[0]);
+    exit_usage (argv[0], 1);
+  }
+
+  c = NULL;
+  status = lcc_connect (address, &c);
+  if (status != 0) {
+    fprintf (stderr, "ERROR: Failed to connect to daemon at %s: %s.\n",
+        address, strerror (errno));
+    return (1);
+  }
+
+  if (strcasecmp (argv[optind], "getval") == 0)
+    status = getval (c, argc - optind, argv + optind);
+  else if (strcasecmp (argv[optind], "flush") == 0)
+    status = flush (c, argc - optind, argv + optind);
+  else if (strcasecmp (argv[optind], "listval") == 0)
+    status = listval (c, argc - optind, argv + optind);
+  else if (strcasecmp (argv[optind], "putval") == 0)
+    status = putval (c, argc - optind, argv + optind);
+  else {
+    fprintf (stderr, "%s: invalid command: %s\n", argv[0], argv[optind]);
+    return (1);
+  }
+
+  LCC_DESTROY (c);
+
+  if (status != 0)
+    return (status);
+  return (0);
+} /* main */
+
+/* vim: set sw=2 ts=2 tw=78 expandtab : */
+
diff --git a/src/collectdctl.pod b/src/collectdctl.pod
new file mode 100644 (file)
index 0000000..21c0b50
--- /dev/null
@@ -0,0 +1,160 @@
+=head1 NAME
+
+collectdctl - Control interface for collectd
+
+=head1 SYNOPSIS
+
+collectdctl I<[options]> I<E<lt>commandE<gt>> I<[command options]>
+
+=head1 DESCRIPTION
+
+collectdctl provides a control interface for collectd, which may be used to
+interact with the daemon using the C<unixsock plugin>.
+
+=head1 OPTIONS
+
+collectdctl supports the following options:
+
+=over 4
+
+=item B<-s> I<socket>
+
+Path to the UNIX socket opened by collectd's C<unixsock plugin>.
+Default: /var/run/collectd-unixsock
+
+=item B<-h>
+
+Display usage information and exit.
+
+=back
+
+=head1 AVAILABLE COMMANDS
+
+The following commands are supported:
+
+=over 4
+
+=item B<getval> I<E<lt>identifierE<gt>>
+
+Query the latest collected value identified by the specified
+I<E<lt>identifierE<gt>> (see below). The value-list associated with that
+data-set is returned as a list of key-value-pairs, each on its own line. Keys
+and values are separated by the equal sign (C<=>).
+
+=item B<flush> [B<timeout=>I<E<lt>secondsE<gt>>] [B<plugin=>I<E<lt>nameE<gt>>]
+[B<identifier=>I<E<lt>idE<gt>>]
+
+Flush the daemon. This is useful, e.E<nbsp>g., to make sure that the latest
+values have been written to the respective RRD file before graphing them or
+copying them to somewhere else.
+
+The following options are supported by the flush command:
+
+=over 4
+
+=item B<timeout=>I<E<lt>secondsE<gt>>
+
+Flush values older than the specified timeout (in seconds) only.
+
+=item B<plugin=>I<E<lt>nameE<gt>>
+
+Flush the specified plugin only. I.E<nbsp>e., data cached by the specified
+plugin is written to disk (or network or whatever), if the plugin supports
+that operation.
+
+Example: B<rrdtool>.
+
+=item B<identifier=>I<E<lt>idE<gt>>
+
+If this option is present, only the data specified by the specified identifier
+(see below) will be flushed. Note that this option is not supported by all
+plugins (e.E<nbsp>g., the C<network> plugin does not support this).
+
+=back
+
+The B<plugin> and B<identifier> options may be specified more than once. In
+that case, all combinations of specified plugins and identifiers will be
+flushed only.
+
+=item B<listval>
+
+Returns a list of all values (by their identifier) available to the
+C<unixsock> plugin. Each value is printed on its own line. I.E<nbsp>e., this
+command returns a list of valid identifiers that may be used with the other
+commands.
+
+=item B<putval> I<E<lt>identifierE<gt>> [B<interval=>I<E<lt>secondsE<gt>>]
+I<E<lt>value-list(s)E<gt>>
+
+Submit one or more values (identified by I<E<lt>identifierE<gt>>, see below)
+to the daemon which will then dispatch them to the write plugins. B<interval>
+specifies the interval (in seconds) used to collect the values following that
+option. It defaults to the default of the running collectd instance receiving
+the data. Multiple I<E<lt>value-list(s)E<gt>> (see below) may be specified.
+Each of them will be submitted to the daemon. The values have to match the
+data-set definition specified by the type as given in the identifier (see
+L<types.db(5)> for details).
+
+=back
+
+=head1 IDENTIFIERS
+
+An identifier has the following format:
+
+[I<hostname>/]I<plugin>[-I<plugin_instance>]/I<type>[-I<type_instance>]
+
+Examples:
+ somehost/cpu-0/cpu-idle
+ uptime/uptime
+ otherhost/memory/memory-used
+
+Hostname defaults to the local (non-fully qualified) hostname if omitted. No
+error is returned if the specified identifier does not exist (this is a
+limitation in the C<libcollectdclient> library).
+
+=head1 VALUE-LIST
+
+A value list describes one data-set as handled by collectd. It is a colon
+(C<:>) separated list of the time and the values. Each value is either given
+as an integer if the data-type is a counter, or as a double if the data-type
+is a gauge value. A literal C<U> is interpreted as an undefined gauge value.
+The number of values and the data-types have to match the type specified in
+the identifier (see L<types.db(5)> for details). The time is specified as
+epoch (i.E<nbsp>e., standard UNIX time) or as a literal C<N> which will be
+interpreted as now.
+
+=head1 EXAMPLES
+
+=over 4
+
+=item C<collectdctl flush plugin=rrdtool identifier=somehost/cpu-0/cpu-wait>
+
+Flushes all CPU wait RRD values of the first CPU of the local host.
+I.E<nbsp>e., writes all pending RRD updates of that data-source to disk.
+
+=item C<for ident in `collectdctl listval | grep users/users`; do
+      collectdctl getval $ident;
+  done>
+
+Query the latest number of logged in users on all hosts known to the local
+collectd instance.
+
+=back
+
+=head1 SEE ALSO
+
+L<collectd(1)>,
+L<collectd.conf(5)>,
+L<collectd-unixsock(5)>,
+L<types.db(5)>
+
+=head1 AUTHOR
+
+collectd has been written by Florian Forster E<lt>octo at verplant.orgE<gt>
+and many contributors (see `AUTHORS').
+
+collectdctl has been written by
+Håkon J Dugstad Johnsen E<lt>hakon-dugstad.johnsenE<nbsp>atE<nbsp>telenor.comE<gt>
+and Sebastian Harl E<lt>sh at tokkee.orgE<gt>.
+
+=cut
diff --git a/src/collectdmon.c b/src/collectdmon.c
new file mode 100644 (file)
index 0000000..078b2eb
--- /dev/null
@@ -0,0 +1,375 @@
+/**
+ * collectd - src/collectdmon.c
+ * Copyright (C) 2007  Sebastian Harl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Sebastian Harl <sh at tokkee.org>
+ **/
+
+#if !defined(__GNUC__) || !__GNUC__
+# define __attribute__(x) /**/
+#endif
+
+#include "config.h"
+
+#include <assert.h>
+
+#include <errno.h>
+
+#include <fcntl.h>
+
+#include <signal.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <string.h>
+
+#include <syslog.h>
+
+#include <sys/resource.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <time.h>
+
+#include <unistd.h>
+
+#ifndef COLLECTDMON_PIDFILE
+# define COLLECTDMON_PIDFILE LOCALSTATEDIR"/run/collectdmon.pid"
+#endif /* ! COLLECTDMON_PIDFILE */
+
+#ifndef WCOREDUMP
+# define WCOREDUMP(s) 0
+#endif /* ! WCOREDUMP */
+
+static int loop    = 0;
+static int restart = 0;
+
+static char  *pidfile      = NULL;
+static pid_t  collectd_pid = 0;
+
+static void exit_usage (char *name)
+{
+       printf ("Usage: %s <options> [-- <collectd options>]\n"
+
+                       "\nAvailable options:\n"
+                       "  -h         Display this help and exit.\n"
+                       "  -c <path>  Path to the collectd binary.\n"
+                       "  -P <file>  PID-file.\n"
+
+                       "\nFor <collectd options> see collectd.conf(5).\n"
+
+                       "\n"PACKAGE" "VERSION", http://collectd.org/\n"
+                       "by Florian octo Forster <octo@verplant.org>\n"
+                       "for contributions see `AUTHORS'\n", name);
+       exit (0);
+} /* exit_usage */
+
+static int pidfile_create (void)
+{
+       FILE *file = NULL;
+
+       if (NULL == pidfile)
+               pidfile = COLLECTDMON_PIDFILE;
+
+       if (NULL == (file = fopen (pidfile, "w"))) {
+               syslog (LOG_ERR, "Error: couldn't open PID-file (%s) for writing: %s",
+                               pidfile, strerror (errno));
+               return -1;
+       }
+
+       fprintf (file, "%i\n", (int)getpid ());
+       fclose (file);
+       return 0;
+} /* pidfile_create */
+
+static int pidfile_delete (void)
+{
+       assert (NULL != pidfile);
+
+       if (0 != unlink (pidfile)) {
+               syslog (LOG_ERR, "Error: couldn't delete PID-file (%s): %s",
+                               pidfile, strerror (errno));
+               return -1;
+       }
+       return 0;
+} /* pidfile_remove */
+
+static int daemonize (void)
+{
+       struct rlimit rl;
+
+       pid_t pid = 0;
+       int   i   = 0;
+
+       if (0 != chdir ("/")) {
+               fprintf (stderr, "Error: chdir() failed: %s\n", strerror (errno));
+               return -1;
+       }
+
+       if (0 != getrlimit (RLIMIT_NOFILE, &rl)) {
+               fprintf (stderr, "Error: getrlimit() failed: %s\n", strerror (errno));
+               return -1;
+       }
+
+       if (0 > (pid = fork ())) {
+               fprintf (stderr, "Error: fork() failed: %s\n", strerror (errno));
+               return -1;
+       }
+       else if (pid != 0) {
+               exit (0);
+       }
+
+       if (0 != pidfile_create ())
+               return -1;
+
+       setsid ();
+
+       if (RLIM_INFINITY == rl.rlim_max)
+               rl.rlim_max = 1024;
+
+       for (i = 0; i < (int)rl.rlim_max; ++i)
+               close (i);
+
+       errno = 0;
+       if (open ("/dev/null", O_RDWR) != 0) {
+               syslog (LOG_ERR, "Error: couldn't connect STDIN to /dev/null: %s",
+                               strerror (errno));
+               return -1;
+       }
+
+       errno = 0;
+       if (dup (0) != 1) {
+               syslog (LOG_ERR, "Error: couldn't connect STDOUT to /dev/null: %s",
+                               strerror (errno));
+               return -1;
+       }
+
+       errno = 0;
+       if (dup (0) != 2) {
+               syslog (LOG_ERR, "Error: couldn't connect STDERR to /dev/null: %s",
+                               strerror (errno));
+               return -1;
+       }
+       return 0;
+} /* daemonize */
+
+static int collectd_start (char **argv)
+{
+       pid_t pid = 0;
+
+       if (0 > (pid = fork ())) {
+               syslog (LOG_ERR, "Error: fork() failed: %s", strerror (errno));
+               return -1;
+       }
+       else if (pid != 0) {
+               collectd_pid = pid;
+               return 0;
+       }
+
+       execvp (argv[0], argv);
+       syslog (LOG_ERR, "Error: execvp(%s) failed: %s",
+                       argv[0], strerror (errno));
+       exit (-1);
+} /* collectd_start */
+
+static int collectd_stop (void)
+{
+       if (0 == collectd_pid)
+               return 0;
+
+       if (0 != kill (collectd_pid, SIGTERM)) {
+               syslog (LOG_ERR, "Error: kill() failed: %s", strerror (errno));
+               return -1;
+       }
+       return 0;
+} /* collectd_stop */
+
+static void sig_int_term_handler (int __attribute__((unused)) signo)
+{
+       ++loop;
+       return;
+} /* sig_int_term_handler */
+
+static void sig_hup_handler (int __attribute__((unused)) signo)
+{
+       ++restart;
+       return;
+} /* sig_hup_handler */
+
+static void log_status (int status)
+{
+       if (WIFEXITED (status)) {
+               if (0 == WEXITSTATUS (status))
+                       syslog (LOG_INFO, "Info: collectd terminated with exit status %i",
+                                       WEXITSTATUS (status));
+               else
+                       syslog (LOG_WARNING,
+                                       "Warning: collectd terminated with exit status %i",
+                                       WEXITSTATUS (status));
+       }
+       else if (WIFSIGNALED (status)) {
+               syslog (LOG_WARNING, "Warning: collectd was terminated by signal %i%s",
+                               WTERMSIG (status), WCOREDUMP (status) ? " (core dumped)" : "");
+       }
+       return;
+} /* log_status */
+
+static void check_respawn (void)
+{
+       time_t t = time (NULL);
+
+       static time_t timestamp = 0;
+       static int    counter   = 0;
+
+       if ((t - 120) < timestamp)
+               ++counter;
+       else {
+               timestamp = t;
+               counter   = 0;
+       }
+
+       if (10 < counter) {
+               unsigned int time_left = 300;
+
+               syslog (LOG_ERR, "Error: collectd is respawning too fast - "
+                               "disabled for %i seconds", time_left);
+
+               while ((0 < (time_left = sleep (time_left))) && (0 == loop));
+       }
+       return;
+} /* check_respawn */
+
+int main (int argc, char **argv)
+{
+       int    collectd_argc = 0;
+       char  *collectd      = NULL;
+       char **collectd_argv = NULL;
+
+       struct sigaction sa;
+
+       int i = 0;
+
+       /* parse command line options */
+       while (42) {
+               int c = getopt (argc, argv, "hc:P:");
+
+               if (-1 == c)
+                       break;
+
+               switch (c) {
+                       case 'c':
+                               collectd = optarg;
+                               break;
+                       case 'P':
+                               pidfile = optarg;
+                               break;
+                       case 'h':
+                       default:
+                               exit_usage (argv[0]);
+               }
+       }
+
+       for (i = optind; i < argc; ++i)
+               if (0 == strcmp (argv[i], "-f"))
+                       break;
+
+       /* i < argc => -f already present */
+       collectd_argc = 1 + argc - optind + ((i < argc) ? 0 : 1);
+       collectd_argv = (char **)calloc (collectd_argc + 1, sizeof (char *));
+
+       if (NULL == collectd_argv) {
+               fprintf (stderr, "Out of memory.");
+               return 3;
+       }
+
+       collectd_argv[0] = (NULL == collectd) ? "collectd" : collectd;
+
+       if (i == argc)
+               collectd_argv[collectd_argc - 1] = "-f";
+
+       for (i = optind; i < argc; ++i)
+               collectd_argv[i - optind + 1] = argv[i];
+
+       collectd_argv[collectd_argc] = NULL;
+
+       openlog ("collectdmon", LOG_CONS | LOG_PID, LOG_DAEMON);
+
+       if (-1 == daemonize ())
+               return 1;
+
+       sa.sa_handler = sig_int_term_handler;
+       sa.sa_flags   = 0;
+       sigemptyset (&sa.sa_mask);
+
+       if (0 != sigaction (SIGINT, &sa, NULL)) {
+               syslog (LOG_ERR, "Error: sigaction() failed: %s", strerror (errno));
+               return 1;
+       }
+
+       if (0 != sigaction (SIGTERM, &sa, NULL)) {
+               syslog (LOG_ERR, "Error: sigaction() failed: %s", strerror (errno));
+               return 1;
+       }
+
+       sa.sa_handler = sig_hup_handler;
+
+       if (0 != sigaction (SIGHUP, &sa, NULL)) {
+               syslog (LOG_ERR, "Error: sigaction() failed: %s", strerror (errno));
+               return 1;
+       }
+
+       while (0 == loop) {
+               int status = 0;
+
+               if (0 != collectd_start (collectd_argv)) {
+                       syslog (LOG_ERR, "Error: failed to start collectd.");
+                       break;
+               }
+
+               assert (0 < collectd_pid);
+               while ((collectd_pid != waitpid (collectd_pid, &status, 0))
+                               && (EINTR == errno))
+                       if ((0 != loop) || (0 != restart))
+                               collectd_stop ();
+
+               collectd_pid = 0;
+
+               log_status (status);
+               check_respawn ();
+
+               if (0 != restart) {
+                       syslog (LOG_INFO, "Info: restarting collectd");
+                       restart = 0;
+               }
+               else if (0 == loop)
+                       syslog (LOG_WARNING, "Warning: restarting collectd");
+       }
+
+       syslog (LOG_INFO, "Info: shutting down collectdmon");
+
+       pidfile_delete ();
+       closelog ();
+
+       free (collectd_argv);
+       return 0;
+} /* main */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
+
diff --git a/src/collectdmon.pod b/src/collectdmon.pod
new file mode 100644 (file)
index 0000000..73ba6b8
--- /dev/null
@@ -0,0 +1,73 @@
+=head1 NAME
+
+collectdmon - Monitoring daemon for collectd
+
+=head1 SYNOPSIS
+
+collectdmon I<[options]> [-- I<collectd options>]
+
+=head1 DESCRIPTION
+
+collectdmon is a small "wrapper" daemon which starts and monitors the collectd
+daemon. If collectd terminates it will automatically be restarted, unless
+collectdmon was told to shut it down.
+
+=head1 OPTIONS
+
+collectdmon supports the following options:
+
+=over 4
+
+=item B<-c> I<E<lt>pathE<gt>>
+
+Specify the pathname of the collectd binary. You may either specify an
+absolute path or simply the name of the binary in which case the B<PATH>
+variable will be searched for it. The default is "B<collectd>".
+
+=item B<-P> I<E<lt>pid-fileE<gt>>
+
+Specify the pid file. The default is "I</var/run/collectdmon.pid>".
+
+=item B<-h>
+
+Output usage information and exit.
+
+=item I<collectd options>
+
+Specify options that are passed on to collectd. If it is not already included,
+B<-f> will be added to these options. See L<collectd(1)>.
+
+=back
+
+=head1 SIGNALS
+
+B<collectdmon> accepts the following signals:
+
+=over 4
+
+=item B<SIGINT>, B<SIGTERM>
+
+These signals cause B<collectdmon> to terminate B<collectd>, wait for its
+termination and then shut down.
+
+=item B<SIGHUP>
+
+This signal causes B<collectdmon> to terminate B<collectd>, wait for its
+termination and then restart it.
+
+=back
+
+=head1 SEE ALSO
+
+L<collectd(1)>,
+L<collectd.conf(5)>,
+L<http://collectd.org/>
+
+=head1 AUTHOR
+
+collectd has been written by Florian Forster E<lt>octo at verplant.orgE<gt>
+and many contributors (see `AUTHORS').
+
+collectdmon has been written by Sebastian Harl E<lt>sh@tokkee.orgE<gt>.
+
+=cut
diff --git a/src/common.c b/src/common.c
new file mode 100644 (file)
index 0000000..0069a8b
--- /dev/null
@@ -0,0 +1,1285 @@
+/**
+ * collectd - src/common.c
+ * Copyright (C) 2005-2010  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at collectd.org>
+ *   Niki W. Waibel <niki.waibel@gmx.net>
+ *   Sebastian Harl <sh at tokkee.org>
+ *   Michał Mirosław <mirq-linux at rere.qmqm.pl>
+**/
+
+#if HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "utils_cache.h"
+
+#if HAVE_PTHREAD_H
+# include <pthread.h>
+#endif
+
+#ifdef HAVE_MATH_H
+# include <math.h>
+#endif
+
+/* for getaddrinfo */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+#if HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+
+/* for ntohl and htonl */
+#if HAVE_ARPA_INET_H
+# include <arpa/inet.h>
+#endif
+
+#ifdef HAVE_LIBKSTAT
+extern kstat_ctl_t *kc;
+#endif
+
+#if !HAVE_GETPWNAM_R
+static pthread_mutex_t getpwnam_r_lock = PTHREAD_MUTEX_INITIALIZER;
+#endif
+
+#if !HAVE_STRERROR_R
+static pthread_mutex_t strerror_r_lock = PTHREAD_MUTEX_INITIALIZER;
+#endif
+
+char *sstrncpy (char *dest, const char *src, size_t n)
+{
+       strncpy (dest, src, n);
+       dest[n - 1] = '\0';
+
+       return (dest);
+} /* char *sstrncpy */
+
+int ssnprintf (char *dest, size_t n, const char *format, ...)
+{
+       int ret = 0;
+       va_list ap;
+
+       va_start (ap, format);
+       ret = vsnprintf (dest, n, format, ap);
+       dest[n - 1] = '\0';
+       va_end (ap);
+
+       return (ret);
+} /* int ssnprintf */
+
+char *sstrdup (const char *s)
+{
+       char *r;
+       size_t sz;
+
+       if (s == NULL)
+               return (NULL);
+
+       /* Do not use `strdup' here, because it's not specified in POSIX. It's
+        * ``only'' an XSI extension. */
+       sz = strlen (s) + 1;
+       r = (char *) malloc (sizeof (char) * sz);
+       if (r == NULL)
+       {
+               ERROR ("sstrdup: Out of memory.");
+               exit (3);
+       }
+       memcpy (r, s, sizeof (char) * sz);
+
+       return (r);
+} /* char *sstrdup */
+
+/* Even though Posix requires "strerror_r" to return an "int",
+ * some systems (e.g. the GNU libc) return a "char *" _and_
+ * ignore the second argument ... -tokkee */
+char *sstrerror (int errnum, char *buf, size_t buflen)
+{
+       buf[0] = '\0';
+
+#if !HAVE_STRERROR_R
+       {
+               char *temp;
+
+               pthread_mutex_lock (&strerror_r_lock);
+
+               temp = strerror (errnum);
+               sstrncpy (buf, temp, buflen);
+
+               pthread_mutex_unlock (&strerror_r_lock);
+       }
+/* #endif !HAVE_STRERROR_R */
+
+#elif STRERROR_R_CHAR_P
+       {
+               char *temp;
+               temp = strerror_r (errnum, buf, buflen);
+               if (buf[0] == '\0')
+               {
+                       if ((temp != NULL) && (temp != buf) && (temp[0] != '\0'))
+                               sstrncpy (buf, temp, buflen);
+                       else
+                               sstrncpy (buf, "strerror_r did not return "
+                                               "an error message", buflen);
+               }
+       }
+/* #endif STRERROR_R_CHAR_P */
+
+#else
+       if (strerror_r (errnum, buf, buflen) != 0)
+       {
+               ssnprintf (buf, buflen, "Error #%i; "
+                               "Additionally, strerror_r failed.",
+                               errnum);
+       }
+#endif /* STRERROR_R_CHAR_P */
+
+       return (buf);
+} /* char *sstrerror */
+
+void *smalloc (size_t size)
+{
+       void *r;
+
+       if ((r = malloc (size)) == NULL)
+       {
+               ERROR ("Not enough memory.");
+               exit (3);
+       }
+
+       return (r);
+} /* void *smalloc */
+
+#if 0
+void sfree (void **ptr)
+{
+       if (ptr == NULL)
+               return;
+
+       if (*ptr != NULL)
+               free (*ptr);
+
+       *ptr = NULL;
+}
+#endif
+
+ssize_t sread (int fd, void *buf, size_t count)
+{
+       char    *ptr;
+       size_t   nleft;
+       ssize_t  status;
+
+       ptr   = (char *) buf;
+       nleft = count;
+
+       while (nleft > 0)
+       {
+               status = read (fd, (void *) ptr, nleft);
+
+               if ((status < 0) && ((errno == EAGAIN) || (errno == EINTR)))
+                       continue;
+
+               if (status < 0)
+                       return (status);
+
+               if (status == 0)
+               {
+                       DEBUG ("Received EOF from fd %i. "
+                                       "Closing fd and returning error.",
+                                       fd);
+                       close (fd);
+                       return (-1);
+               }
+
+               assert ((0 > status) || (nleft >= (size_t)status));
+
+               nleft = nleft - status;
+               ptr   = ptr   + status;
+       }
+
+       return (0);
+}
+
+
+ssize_t swrite (int fd, const void *buf, size_t count)
+{
+       const char *ptr;
+       size_t      nleft;
+       ssize_t     status;
+
+       ptr   = (const char *) buf;
+       nleft = count;
+
+       while (nleft > 0)
+       {
+               status = write (fd, (const void *) ptr, nleft);
+
+               if ((status < 0) && ((errno == EAGAIN) || (errno == EINTR)))
+                       continue;
+
+               if (status < 0)
+                       return (status);
+
+               nleft = nleft - status;
+               ptr   = ptr   + status;
+       }
+
+       return (0);
+}
+
+int strsplit (char *string, char **fields, size_t size)
+{
+       size_t i;
+       char *ptr;
+       char *saveptr;
+
+       i = 0;
+       ptr = string;
+       saveptr = NULL;
+       while ((fields[i] = strtok_r (ptr, " \t\r\n", &saveptr)) != NULL)
+       {
+               ptr = NULL;
+               i++;
+
+               if (i >= size)
+                       break;
+       }
+
+       return ((int) i);
+}
+
+int strjoin (char *dst, size_t dst_len,
+               char **fields, size_t fields_num,
+               const char *sep)
+{
+       size_t field_len;
+       size_t sep_len;
+       int i;
+
+       memset (dst, '\0', dst_len);
+
+       if (fields_num <= 0)
+               return (-1);
+
+       sep_len = 0;
+       if (sep != NULL)
+               sep_len = strlen (sep);
+
+       for (i = 0; i < (int)fields_num; i++)
+       {
+               if ((i > 0) && (sep_len > 0))
+               {
+                       if (dst_len <= sep_len)
+                               return (-1);
+
+                       strncat (dst, sep, dst_len);
+                       dst_len -= sep_len;
+               }
+
+               field_len = strlen (fields[i]);
+
+               if (dst_len <= field_len)
+                       return (-1);
+
+               strncat (dst, fields[i], dst_len);
+               dst_len -= field_len;
+       }
+
+       return (strlen (dst));
+}
+
+int strsubstitute (char *str, char c_from, char c_to)
+{
+       int ret;
+
+       if (str == NULL)
+               return (-1);
+
+       ret = 0;
+       while (*str != '\0')
+       {
+               if (*str == c_from)
+               {
+                       *str = c_to;
+                       ret++;
+               }
+               str++;
+       }
+
+       return (ret);
+} /* int strsubstitute */
+
+int strunescape (char *buf, size_t buf_len)
+{
+       size_t i;
+
+       for (i = 0; (i < buf_len) && (buf[i] != '\0'); ++i)
+       {
+               if (buf[i] != '\\')
+                       continue;
+
+               if ((i >= buf_len) || (buf[i + 1] == '\0')) {
+                       ERROR ("string unescape: backslash found at end of string.");
+                       return (-1);
+               }
+
+               switch (buf[i + 1]) {
+                       case 't':
+                               buf[i] = '\t';
+                               break;
+                       case 'n':
+                               buf[i] = '\n';
+                               break;
+                       case 'r':
+                               buf[i] = '\r';
+                               break;
+                       default:
+                               buf[i] = buf[i + 1];
+                               break;
+               }
+
+               memmove (buf + i + 1, buf + i + 2, buf_len - i - 2);
+       }
+       return (0);
+} /* int strunescape */
+
+int escape_slashes (char *buf, int buf_len)
+{
+       int i;
+
+       if (strcmp (buf, "/") == 0)
+       {
+               if (buf_len < 5)
+                       return (-1);
+
+               strncpy (buf, "root", buf_len);
+               return (0);
+       }
+
+       if (buf_len <= 1)
+               return (0);
+
+       /* Move one to the left */
+       if (buf[0] == '/')
+               memmove (buf, buf + 1, buf_len - 1);
+
+       for (i = 0; i < buf_len - 1; i++)
+       {
+               if (buf[i] == '\0')
+                       break;
+               else if (buf[i] == '/')
+                       buf[i] = '_';
+       }
+       buf[i] = '\0';
+
+       return (0);
+} /* int escape_slashes */
+
+void replace_special (char *buffer, size_t buffer_size)
+{
+       size_t i;
+
+       for (i = 0; i < buffer_size; i++)
+       {
+               if (buffer[i] == 0)
+                       return;
+               if ((!isalnum ((int) buffer[i])) && (buffer[i] != '-'))
+                       buffer[i] = '_';
+       }
+} /* void replace_special */
+
+int timeval_cmp (struct timeval tv0, struct timeval tv1, struct timeval *delta)
+{
+       struct timeval *larger;
+       struct timeval *smaller;
+
+       int status;
+
+       NORMALIZE_TIMEVAL (tv0);
+       NORMALIZE_TIMEVAL (tv1);
+
+       if ((tv0.tv_sec == tv1.tv_sec) && (tv0.tv_usec == tv1.tv_usec))
+       {
+               if (delta != NULL) {
+                       delta->tv_sec  = 0;
+                       delta->tv_usec = 0;
+               }
+               return (0);
+       }
+
+       if ((tv0.tv_sec < tv1.tv_sec)
+                       || ((tv0.tv_sec == tv1.tv_sec) && (tv0.tv_usec < tv1.tv_usec)))
+       {
+               larger  = &tv1;
+               smaller = &tv0;
+               status  = -1;
+       }
+       else
+       {
+               larger  = &tv0;
+               smaller = &tv1;
+               status  = 1;
+       }
+
+       if (delta != NULL) {
+               delta->tv_sec = larger->tv_sec - smaller->tv_sec;
+
+               if (smaller->tv_usec <= larger->tv_usec)
+                       delta->tv_usec = larger->tv_usec - smaller->tv_usec;
+               else
+               {
+                       --delta->tv_sec;
+                       delta->tv_usec = 1000000 + larger->tv_usec - smaller->tv_usec;
+               }
+       }
+
+       assert ((delta == NULL)
+                       || ((0 <= delta->tv_usec) && (delta->tv_usec < 1000000)));
+
+       return (status);
+} /* int timeval_cmp */
+
+int check_create_dir (const char *file_orig)
+{
+       struct stat statbuf;
+
+       char  file_copy[512];
+       char  dir[512];
+       int   dir_len = 512;
+       char *fields[16];
+       int   fields_num;
+       char *ptr;
+       char *saveptr;
+       int   last_is_file = 1;
+       int   path_is_absolute = 0;
+       size_t len;
+       int   i;
+
+       /*
+        * Sanity checks first
+        */
+       if (file_orig == NULL)
+               return (-1);
+
+       if ((len = strlen (file_orig)) < 1)
+               return (-1);
+       else if (len >= sizeof (file_copy))
+               return (-1);
+
+       /*
+        * If `file_orig' ends in a slash the last component is a directory,
+        * otherwise it's a file. Act accordingly..
+        */
+       if (file_orig[len - 1] == '/')
+               last_is_file = 0;
+       if (file_orig[0] == '/')
+               path_is_absolute = 1;
+
+       /*
+        * Create a copy for `strtok_r' to destroy
+        */
+       sstrncpy (file_copy, file_orig, sizeof (file_copy));
+
+       /*
+        * Break into components. This will eat up several slashes in a row and
+        * remove leading and trailing slashes..
+        */
+       ptr = file_copy;
+       saveptr = NULL;
+       fields_num = 0;
+       while ((fields[fields_num] = strtok_r (ptr, "/", &saveptr)) != NULL)
+       {
+               ptr = NULL;
+               fields_num++;
+
+               if (fields_num >= 16)
+                       break;
+       }
+
+       /*
+        * For each component, do..
+        */
+       for (i = 0; i < (fields_num - last_is_file); i++)
+       {
+               /*
+                * Do not create directories that start with a dot. This
+                * prevents `../../' attacks and other likely malicious
+                * behavior.
+                */
+               if (fields[i][0] == '.')
+               {
+                       ERROR ("Cowardly refusing to create a directory that "
+                                       "begins with a `.' (dot): `%s'", file_orig);
+                       return (-2);
+               }
+
+               /*
+                * Join the components together again
+                */
+               dir[0] = '/';
+               if (strjoin (dir + path_is_absolute, dir_len - path_is_absolute,
+                                       fields, i + 1, "/") < 0)
+               {
+                       ERROR ("strjoin failed: `%s', component #%i", file_orig, i);
+                       return (-1);
+               }
+
+               while (42) {
+                       if ((stat (dir, &statbuf) == -1)
+                                       && (lstat (dir, &statbuf) == -1))
+                       {
+                               if (errno == ENOENT)
+                               {
+                                       if (mkdir (dir, 0755) == 0)
+                                               break;
+
+                                       /* this might happen, if a different thread created
+                                        * the directory in the meantime
+                                        * => call stat() again to check for S_ISDIR() */
+                                       if (EEXIST == errno)
+                                               continue;
+
+                                       char errbuf[1024];
+                                       ERROR ("check_create_dir: mkdir (%s): %s", dir,
+                                                       sstrerror (errno,
+                                                               errbuf, sizeof (errbuf)));
+                                       return (-1);
+                               }
+                               else
+                               {
+                                       char errbuf[1024];
+                                       ERROR ("check_create_dir: stat (%s): %s", dir,
+                                                       sstrerror (errno, errbuf,
+                                                               sizeof (errbuf)));
+                                       return (-1);
+                               }
+                       }
+                       else if (!S_ISDIR (statbuf.st_mode))
+                       {
+                               ERROR ("check_create_dir: `%s' exists but is not "
+                                               "a directory!", dir);
+                               return (-1);
+                       }
+                       break;
+               }
+       }
+
+       return (0);
+} /* check_create_dir */
+
+#ifdef HAVE_LIBKSTAT
+int get_kstat (kstat_t **ksp_ptr, char *module, int instance, char *name)
+{
+       char ident[128];
+
+       *ksp_ptr = NULL;
+       
+       if (kc == NULL)
+               return (-1);
+
+       ssnprintf (ident, sizeof (ident), "%s,%i,%s", module, instance, name);
+
+       *ksp_ptr = kstat_lookup (kc, module, instance, name);
+       if (*ksp_ptr == NULL)
+       {
+               ERROR ("get_kstat: Cound not find kstat %s", ident);
+               return (-1);
+       }
+
+       if ((*ksp_ptr)->ks_type != KSTAT_TYPE_NAMED)
+       {
+               ERROR ("get_kstat: kstat %s has wrong type", ident);
+               *ksp_ptr = NULL;
+               return (-1);
+       }
+
+#ifdef assert
+       assert (*ksp_ptr != NULL);
+       assert ((*ksp_ptr)->ks_type == KSTAT_TYPE_NAMED);
+#endif
+
+       if (kstat_read (kc, *ksp_ptr, NULL) == -1)
+       {
+               ERROR ("get_kstat: kstat %s could not be read", ident);
+               return (-1);
+       }
+
+       if ((*ksp_ptr)->ks_type != KSTAT_TYPE_NAMED)
+       {
+               ERROR ("get_kstat: kstat %s has wrong type", ident);
+               return (-1);
+       }
+
+       return (0);
+}
+
+long long get_kstat_value (kstat_t *ksp, char *name)
+{
+       kstat_named_t *kn;
+       long long retval = -1LL;
+
+#ifdef assert
+       assert (ksp != NULL);
+       assert (ksp->ks_type == KSTAT_TYPE_NAMED);
+#else
+       if (ksp == NULL)
+       {
+               ERROR ("ERROR: %s:%i: ksp == NULL\n", __FILE__, __LINE__);
+               return (-1LL);
+       }
+       else if (ksp->ks_type != KSTAT_TYPE_NAMED)
+       {
+               ERROR ("ERROR: %s:%i: ksp->ks_type != KSTAT_TYPE_NAMED\n", __FILE__, __LINE__);
+               return (-1LL);
+       }
+#endif
+
+       if ((kn = (kstat_named_t *) kstat_data_lookup (ksp, name)) == NULL)
+               return (retval);
+
+       if (kn->data_type == KSTAT_DATA_INT32)
+               retval = (long long) kn->value.i32;
+       else if (kn->data_type == KSTAT_DATA_UINT32)
+               retval = (long long) kn->value.ui32;
+       else if (kn->data_type == KSTAT_DATA_INT64)
+               retval = (long long) kn->value.i64; /* According to ANSI C99 `long long' must hold at least 64 bits */
+       else if (kn->data_type == KSTAT_DATA_UINT64)
+               retval = (long long) kn->value.ui64; /* XXX: Might overflow! */
+       else
+               WARNING ("get_kstat_value: Not a numeric value: %s", name);
+                
+       return (retval);
+}
+#endif /* HAVE_LIBKSTAT */
+
+#ifndef HAVE_HTONLL
+unsigned long long ntohll (unsigned long long n)
+{
+#if BYTE_ORDER == BIG_ENDIAN
+       return (n);
+#else
+       return (((unsigned long long) ntohl (n)) << 32) + ntohl (n >> 32);
+#endif
+} /* unsigned long long ntohll */
+
+unsigned long long htonll (unsigned long long n)
+{
+#if BYTE_ORDER == BIG_ENDIAN
+       return (n);
+#else
+       return (((unsigned long long) htonl (n)) << 32) + htonl (n >> 32);
+#endif
+} /* unsigned long long htonll */
+#endif /* HAVE_HTONLL */
+
+#if FP_LAYOUT_NEED_NOTHING
+/* Well, we need nothing.. */
+/* #endif FP_LAYOUT_NEED_NOTHING */
+
+#elif FP_LAYOUT_NEED_ENDIANFLIP || FP_LAYOUT_NEED_INTSWAP
+# if FP_LAYOUT_NEED_ENDIANFLIP
+#  define FP_CONVERT(A) ((((uint64_t)(A) & 0xff00000000000000LL) >> 56) | \
+                         (((uint64_t)(A) & 0x00ff000000000000LL) >> 40) | \
+                         (((uint64_t)(A) & 0x0000ff0000000000LL) >> 24) | \
+                         (((uint64_t)(A) & 0x000000ff00000000LL) >> 8)  | \
+                         (((uint64_t)(A) & 0x00000000ff000000LL) << 8)  | \
+                         (((uint64_t)(A) & 0x0000000000ff0000LL) << 24) | \
+                         (((uint64_t)(A) & 0x000000000000ff00LL) << 40) | \
+                         (((uint64_t)(A) & 0x00000000000000ffLL) << 56))
+# else
+#  define FP_CONVERT(A) ((((uint64_t)(A) & 0xffffffff00000000LL) >> 32) | \
+                         (((uint64_t)(A) & 0x00000000ffffffffLL) << 32))
+# endif
+
+double ntohd (double d)
+{
+       union
+       {
+               uint8_t  byte[8];
+               uint64_t integer;
+               double   floating;
+       } ret;
+
+       ret.floating = d;
+
+       /* NAN in x86 byte order */
+       if ((ret.byte[0] == 0x00) && (ret.byte[1] == 0x00)
+                       && (ret.byte[2] == 0x00) && (ret.byte[3] == 0x00)
+                       && (ret.byte[4] == 0x00) && (ret.byte[5] == 0x00)
+                       && (ret.byte[6] == 0xf8) && (ret.byte[7] == 0x7f))
+       {
+               return (NAN);
+       }
+       else
+       {
+               uint64_t tmp;
+
+               tmp = ret.integer;
+               ret.integer = FP_CONVERT (tmp);
+               return (ret.floating);
+       }
+} /* double ntohd */
+
+double htond (double d)
+{
+       union
+       {
+               uint8_t  byte[8];
+               uint64_t integer;
+               double   floating;
+       } ret;
+
+       if (isnan (d))
+       {
+               ret.byte[0] = ret.byte[1] = ret.byte[2] = ret.byte[3] = 0x00;
+               ret.byte[4] = ret.byte[5] = 0x00;
+               ret.byte[6] = 0xf8;
+               ret.byte[7] = 0x7f;
+               return (ret.floating);
+       }
+       else
+       {
+               uint64_t tmp;
+
+               ret.floating = d;
+               tmp = FP_CONVERT (ret.integer);
+               ret.integer = tmp;
+               return (ret.floating);
+       }
+} /* double htond */
+#endif /* FP_LAYOUT_NEED_ENDIANFLIP || FP_LAYOUT_NEED_INTSWAP */
+
+int format_name (char *ret, int ret_len,
+               const char *hostname,
+               const char *plugin, const char *plugin_instance,
+               const char *type, const char *type_instance)
+{
+       int  status;
+
+       assert (plugin != NULL);
+       assert (type != NULL);
+
+       if ((plugin_instance == NULL) || (strlen (plugin_instance) == 0))
+       {
+               if ((type_instance == NULL) || (strlen (type_instance) == 0))
+                       status = ssnprintf (ret, ret_len, "%s/%s/%s",
+                                       hostname, plugin, type);
+               else
+                       status = ssnprintf (ret, ret_len, "%s/%s/%s-%s",
+                                       hostname, plugin, type,
+                                       type_instance);
+       }
+       else
+       {
+               if ((type_instance == NULL) || (strlen (type_instance) == 0))
+                       status = ssnprintf (ret, ret_len, "%s/%s-%s/%s",
+                                       hostname, plugin, plugin_instance,
+                                       type);
+               else
+                       status = ssnprintf (ret, ret_len, "%s/%s-%s/%s-%s",
+                                       hostname, plugin, plugin_instance,
+                                       type, type_instance);
+       }
+
+       if ((status < 1) || (status >= ret_len))
+               return (-1);
+       return (0);
+} /* int format_name */
+
+int format_values (char *ret, size_t ret_len, /* {{{ */
+               const data_set_t *ds, const value_list_t *vl,
+               _Bool store_rates)
+{
+        size_t offset = 0;
+        int status;
+        int i;
+        gauge_t *rates = NULL;
+
+        assert (0 == strcmp (ds->type, vl->type));
+
+        memset (ret, 0, ret_len);
+
+#define BUFFER_ADD(...) do { \
+        status = ssnprintf (ret + offset, ret_len - offset, \
+                        __VA_ARGS__); \
+        if (status < 1) \
+        { \
+                sfree (rates); \
+                return (-1); \
+        } \
+        else if (((size_t) status) >= (ret_len - offset)) \
+        { \
+                sfree (rates); \
+                return (-1); \
+        } \
+        else \
+                offset += ((size_t) status); \
+} while (0)
+
+        BUFFER_ADD ("%.3f", CDTIME_T_TO_DOUBLE (vl->time));
+
+        for (i = 0; i < ds->ds_num; i++)
+        {
+                if (ds->ds[i].type == DS_TYPE_GAUGE)
+                        BUFFER_ADD (":%f", vl->values[i].gauge);
+                else if (store_rates)
+                {
+                        if (rates == NULL)
+                                rates = uc_get_rate (ds, vl);
+                        if (rates == NULL)
+                        {
+                                WARNING ("format_values: "
+                                               "uc_get_rate failed.");
+                                return (-1);
+                        }
+                        BUFFER_ADD (":%g", rates[i]);
+                }
+                else if (ds->ds[i].type == DS_TYPE_COUNTER)
+                        BUFFER_ADD (":%llu", vl->values[i].counter);
+                else if (ds->ds[i].type == DS_TYPE_DERIVE)
+                        BUFFER_ADD (":%"PRIi64, vl->values[i].derive);
+                else if (ds->ds[i].type == DS_TYPE_ABSOLUTE)
+                        BUFFER_ADD (":%"PRIu64, vl->values[i].absolute);
+                else
+                {
+                        ERROR ("format_values plugin: Unknown data source type: %i",
+                                        ds->ds[i].type);
+                        sfree (rates);
+                        return (-1);
+                }
+        } /* for ds->ds_num */
+
+#undef BUFFER_ADD
+
+        sfree (rates);
+        return (0);
+} /* }}} int format_values */
+
+int parse_identifier (char *str, char **ret_host,
+               char **ret_plugin, char **ret_plugin_instance,
+               char **ret_type, char **ret_type_instance)
+{
+       char *hostname = NULL;
+       char *plugin = NULL;
+       char *plugin_instance = NULL;
+       char *type = NULL;
+       char *type_instance = NULL;
+
+       hostname = str;
+       if (hostname == NULL)
+               return (-1);
+
+       plugin = strchr (hostname, '/');
+       if (plugin == NULL)
+               return (-1);
+       *plugin = '\0'; plugin++;
+
+       type = strchr (plugin, '/');
+       if (type == NULL)
+               return (-1);
+       *type = '\0'; type++;
+
+       plugin_instance = strchr (plugin, '-');
+       if (plugin_instance != NULL)
+       {
+               *plugin_instance = '\0';
+               plugin_instance++;
+       }
+
+       type_instance = strchr (type, '-');
+       if (type_instance != NULL)
+       {
+               *type_instance = '\0';
+               type_instance++;
+       }
+
+       *ret_host = hostname;
+       *ret_plugin = plugin;
+       *ret_plugin_instance = plugin_instance;
+       *ret_type = type;
+       *ret_type_instance = type_instance;
+       return (0);
+} /* int parse_identifier */
+
+int parse_identifier_vl (const char *str, value_list_t *vl) /* {{{ */
+{
+       char str_copy[6 * DATA_MAX_NAME_LEN];
+       char *host = NULL;
+       char *plugin = NULL;
+       char *plugin_instance = NULL;
+       char *type = NULL;
+       char *type_instance = NULL;
+       int status;
+
+       if ((str == NULL) || (vl == NULL))
+               return (EINVAL);
+
+       sstrncpy (str_copy, str, sizeof (str_copy));
+
+       status = parse_identifier (str_copy, &host,
+                       &plugin, &plugin_instance,
+                       &type, &type_instance);
+       if (status != 0)
+               return (status);
+
+       sstrncpy (vl->host, host, sizeof (vl->host));
+       sstrncpy (vl->plugin, plugin, sizeof (vl->plugin));
+       sstrncpy (vl->plugin_instance,
+                       (plugin_instance != NULL) ? plugin_instance : "",
+                       sizeof (vl->plugin_instance));
+       sstrncpy (vl->type, type, sizeof (vl->type));
+       sstrncpy (vl->type_instance,
+                       (type_instance != NULL) ? type_instance : "",
+                       sizeof (vl->type_instance));
+
+       return (0);
+} /* }}} int parse_identifier_vl */
+
+int parse_value (const char *value, value_t *ret_value, int ds_type)
+{
+  char *endptr = NULL;
+
+  switch (ds_type)
+  {
+    case DS_TYPE_COUNTER:
+      ret_value->counter = (counter_t) strtoull (value, &endptr, 0);
+      break;
+
+    case DS_TYPE_GAUGE:
+      ret_value->gauge = (gauge_t) strtod (value, &endptr);
+      break;
+
+    case DS_TYPE_DERIVE:
+      ret_value->derive = (derive_t) strtoll (value, &endptr, 0);
+      break;
+
+    case DS_TYPE_ABSOLUTE:
+      ret_value->absolute = (absolute_t) strtoull (value, &endptr, 0);
+      break;
+
+    default:
+      ERROR ("parse_value: Invalid data source type: %i.", ds_type);
+      return -1;
+  }
+
+  if (value == endptr) {
+    ERROR ("parse_value: Failed to parse string as %s: %s.",
+        DS_TYPE_TO_STRING (ds_type), value);
+    return -1;
+  }
+  else if ((NULL != endptr) && ('\0' != *endptr))
+    INFO ("parse_value: Ignoring trailing garbage \"%s\" after %s value. "
+        "Input string was \"%s\".",
+        endptr, DS_TYPE_TO_STRING (ds_type), value);
+
+  return 0;
+} /* int parse_value */
+
+int parse_values (char *buffer, value_list_t *vl, const data_set_t *ds)
+{
+       int i;
+       char *dummy;
+       char *ptr;
+       char *saveptr;
+
+       i = -1;
+       dummy = buffer;
+       saveptr = NULL;
+       while ((ptr = strtok_r (dummy, ":", &saveptr)) != NULL)
+       {
+               dummy = NULL;
+
+               if (i >= vl->values_len)
+               {
+                       /* Make sure i is invalid. */
+                       i = vl->values_len + 1;
+                       break;
+               }
+
+               if (i == -1)
+               {
+                       if (strcmp ("N", ptr) == 0)
+                               vl->time = cdtime ();
+                       else
+                       {
+                               char *endptr = NULL;
+                               double tmp;
+
+                               errno = 0;
+                               tmp = strtod (ptr, &endptr);
+                               if ((errno != 0)                    /* Overflow */
+                                               || (endptr == ptr)  /* Invalid string */
+                                               || (endptr == NULL) /* This should not happen */
+                                               || (*endptr != 0))  /* Trailing chars */
+                                       return (-1);
+
+                               vl->time = DOUBLE_TO_CDTIME_T (tmp);
+                       }
+               }
+               else
+               {
+                       if ((strcmp ("U", ptr) == 0) && (ds->ds[i].type == DS_TYPE_GAUGE))
+                               vl->values[i].gauge = NAN;
+                       else if (0 != parse_value (ptr, &vl->values[i], ds->ds[i].type))
+                               return -1;
+               }
+
+               i++;
+       } /* while (strtok_r) */
+
+       if ((ptr != NULL) || (i != vl->values_len))
+               return (-1);
+       return (0);
+} /* int parse_values */
+
+#if !HAVE_GETPWNAM_R
+int getpwnam_r (const char *name, struct passwd *pwbuf, char *buf,
+               size_t buflen, struct passwd **pwbufp)
+{
+       int status = 0;
+       struct passwd *pw;
+
+       memset (pwbuf, '\0', sizeof (struct passwd));
+
+       pthread_mutex_lock (&getpwnam_r_lock);
+
+       do
+       {
+               pw = getpwnam (name);
+               if (pw == NULL)
+               {
+                       status = (errno != 0) ? errno : ENOENT;
+                       break;
+               }
+
+#define GETPWNAM_COPY_MEMBER(member) \
+               if (pw->member != NULL) \
+               { \
+                       int len = strlen (pw->member); \
+                       if (len >= buflen) \
+                       { \
+                               status = ENOMEM; \
+                               break; \
+                       } \
+                       sstrncpy (buf, pw->member, buflen); \
+                       pwbuf->member = buf; \
+                       buf    += (len + 1); \
+                       buflen -= (len + 1); \
+               }
+               GETPWNAM_COPY_MEMBER(pw_name);
+               GETPWNAM_COPY_MEMBER(pw_passwd);
+               GETPWNAM_COPY_MEMBER(pw_gecos);
+               GETPWNAM_COPY_MEMBER(pw_dir);
+               GETPWNAM_COPY_MEMBER(pw_shell);
+
+               pwbuf->pw_uid = pw->pw_uid;
+               pwbuf->pw_gid = pw->pw_gid;
+
+               if (pwbufp != NULL)
+                       *pwbufp = pwbuf;
+       } while (0);
+
+       pthread_mutex_unlock (&getpwnam_r_lock);
+
+       return (status);
+} /* int getpwnam_r */
+#endif /* !HAVE_GETPWNAM_R */
+
+int notification_init (notification_t *n, int severity, const char *message,
+               const char *host,
+               const char *plugin, const char *plugin_instance,
+               const char *type, const char *type_instance)
+{
+       memset (n, '\0', sizeof (notification_t));
+
+       n->severity = severity;
+
+       if (message != NULL)
+               sstrncpy (n->message, message, sizeof (n->message));
+       if (host != NULL)
+               sstrncpy (n->host, host, sizeof (n->host));
+       if (plugin != NULL)
+               sstrncpy (n->plugin, plugin, sizeof (n->plugin));
+       if (plugin_instance != NULL)
+               sstrncpy (n->plugin_instance, plugin_instance,
+                               sizeof (n->plugin_instance));
+       if (type != NULL)
+               sstrncpy (n->type, type, sizeof (n->type));
+       if (type_instance != NULL)
+               sstrncpy (n->type_instance, type_instance,
+                               sizeof (n->type_instance));
+
+       return (0);
+} /* int notification_init */
+
+int walk_directory (const char *dir, dirwalk_callback_f callback,
+               void *user_data, int include_hidden)
+{
+       struct dirent *ent;
+       DIR *dh;
+       int success;
+       int failure;
+
+       success = 0;
+       failure = 0;
+
+       if ((dh = opendir (dir)) == NULL)
+       {
+               char errbuf[1024];
+               ERROR ("walk_directory: Cannot open '%s': %s", dir,
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return -1;
+       }
+
+       while ((ent = readdir (dh)) != NULL)
+       {
+               int status;
+               
+               if (include_hidden)
+               {
+                       if ((strcmp (".", ent->d_name) == 0)
+                                       || (strcmp ("..", ent->d_name) == 0))
+                               continue;
+               }
+               else /* if (!include_hidden) */
+               {
+                       if (ent->d_name[0]=='.')
+                               continue;
+               }
+
+               status = (*callback) (dir, ent->d_name, user_data);
+               if (status != 0)
+                       failure++;
+               else
+                       success++;
+       }
+
+       closedir (dh);
+
+       if ((success == 0) && (failure > 0))
+               return (-1);
+       return (0);
+}
+
+int read_file_contents (const char *filename, char *buf, int bufsize)
+{
+       FILE *fh;
+       int n;
+
+       if ((fh = fopen (filename, "r")) == NULL)
+               return -1;
+
+       n = fread(buf, 1, bufsize, fh);
+       fclose(fh);
+
+       return n;
+}
+
+counter_t counter_diff (counter_t old_value, counter_t new_value)
+{
+       counter_t diff;
+
+       if (old_value > new_value)
+       {
+               if (old_value <= 4294967295U)
+                       diff = (4294967295U - old_value) + new_value;
+               else
+                       diff = (18446744073709551615ULL - old_value)
+                               + new_value;
+       }
+       else
+       {
+               diff = new_value - old_value;
+       }
+
+       return (diff);
+} /* counter_t counter_to_gauge */
+
+int service_name_to_port_number (const char *service_name)
+{
+       struct addrinfo *ai_list;
+       struct addrinfo *ai_ptr;
+       struct addrinfo ai_hints;
+       int status;
+       int service_number;
+
+       if (service_name == NULL)
+               return (-1);
+
+       ai_list = NULL;
+       memset (&ai_hints, 0, sizeof (ai_hints));
+       ai_hints.ai_family = AF_UNSPEC;
+
+       status = getaddrinfo (/* node = */ NULL, service_name,
+                       &ai_hints, &ai_list);
+       if (status != 0)
+       {
+               ERROR ("service_name_to_port_number: getaddrinfo failed: %s",
+                               gai_strerror (status));
+               return (-1);
+       }
+
+       service_number = -1;
+       for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
+       {
+               if (ai_ptr->ai_family == AF_INET)
+               {
+                       struct sockaddr_in *sa;
+
+                       sa = (void *) ai_ptr->ai_addr;
+                       service_number = (int) ntohs (sa->sin_port);
+               }
+               else if (ai_ptr->ai_family == AF_INET6)
+               {
+                       struct sockaddr_in6 *sa;
+
+                       sa = (void *) ai_ptr->ai_addr;
+                       service_number = (int) ntohs (sa->sin6_port);
+               }
+
+               if ((service_number > 0) && (service_number <= 65535))
+                       break;
+       }
+
+       freeaddrinfo (ai_list);
+
+       if ((service_number > 0) && (service_number <= 65535))
+               return (service_number);
+       return (-1);
+} /* int service_name_to_port_number */
+
+int strtoderive (const char *string, derive_t *ret_value) /* {{{ */
+{
+       derive_t tmp;
+       char *endptr;
+
+       if ((string == NULL) || (ret_value == NULL))
+               return (EINVAL);
+
+       errno = 0;
+       endptr = NULL;
+       tmp = (derive_t) strtoll (string, &endptr, /* base = */ 0);
+       if ((endptr == string) || (errno != 0))
+               return (-1);
+
+       *ret_value = tmp;
+       return (0);
+} /* }}} int strtoderive */
diff --git a/src/common.h b/src/common.h
new file mode 100644 (file)
index 0000000..e6b899d
--- /dev/null
@@ -0,0 +1,302 @@
+/**
+ * collectd - src/common.h
+ * Copyright (C) 2005-2010  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at collectd.org>
+ *   Niki W. Waibel <niki.waibel@gmx.net>
+**/
+
+#ifndef COMMON_H
+#define COMMON_H
+
+#include "collectd.h"
+#include "plugin.h"
+
+#if HAVE_PWD_H
+# include <pwd.h>
+#endif
+
+#define sfree(ptr) \
+       do { \
+               if((ptr) != NULL) { \
+                       free(ptr); \
+               } \
+               (ptr) = NULL; \
+       } while (0)
+
+#define STATIC_ARRAY_SIZE(a) (sizeof (a) / sizeof (*(a)))
+
+#define IS_TRUE(s) ((strcasecmp ("true", (s)) == 0) \
+               || (strcasecmp ("yes", (s)) == 0) \
+               || (strcasecmp ("on", (s)) == 0))
+#define IS_FALSE(s) ((strcasecmp ("false", (s)) == 0) \
+               || (strcasecmp ("no", (s)) == 0) \
+               || (strcasecmp ("off", (s)) == 0))
+
+char *sstrncpy (char *dest, const char *src, size_t n);
+int ssnprintf (char *dest, size_t n, const char *format, ...);
+char *sstrdup(const char *s);
+void *smalloc(size_t size);
+char *sstrerror (int errnum, char *buf, size_t buflen);
+
+/*
+ * NAME
+ *   sread
+ *
+ * DESCRIPTION
+ *   Reads exactly `n' bytes or fails. Syntax and other behavior is analogous
+ *   to `read(2)'. If EOF is received the file descriptor is closed and an
+ *   error is returned.
+ *
+ * PARAMETERS
+ *   `fd'          File descriptor to write to.
+ *   `buf'         Buffer that is to be written.
+ *   `count'       Number of bytes in the buffer.
+ *
+ * RETURN VALUE
+ *   Zero upon success or non-zero if an error occurred. `errno' is set in this
+ *   case.
+ */
+ssize_t sread (int fd, void *buf, size_t count);
+
+/*
+ * NAME
+ *   swrite
+ *
+ * DESCRIPTION
+ *   Writes exactly `n' bytes or fails. Syntax and other behavior is analogous
+ *   to `write(2)'.
+ *
+ * PARAMETERS
+ *   `fd'          File descriptor to write to.
+ *   `buf'         Buffer that is to be written.
+ *   `count'       Number of bytes in the buffer.
+ *
+ * RETURN VALUE
+ *   Zero upon success or non-zero if an error occurred. `errno' is set in this
+ *   case.
+ */
+ssize_t swrite (int fd, const void *buf, size_t count);
+
+/*
+ * NAME
+ *   strsplit
+ *
+ * DESCRIPTION
+ *   Splits a string into parts and stores pointers to the parts in `fields'.
+ *   The characters split at are: " ", "\t", "\r", and "\n".
+ *
+ * PARAMETERS
+ *   `string'      String to split. This string will be modified. `fields' will
+ *                 contain pointers to parts of this string, so free'ing it
+ *                 will destroy `fields' as well.
+ *   `fields'      Array of strings where pointers to the parts will be stored.
+ *   `size'        Number of elements in the array. No more than `size'
+ *                 pointers will be stored in `fields'.
+ *
+ * RETURN VALUE
+ *    Returns the number of parts stored in `fields'.
+ */
+int strsplit (char *string, char **fields, size_t size);
+
+/*
+ * NAME
+ *   strjoin
+ *
+ * DESCRIPTION
+ *   Joins together several parts of a string using `sep' as a separator. This
+ *   is equivalent to the Perl built-in `join'.
+ *
+ * PARAMETERS
+ *   `dst'         Buffer where the result is stored.
+ *   `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.
+ *   `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'.
+ *                 This string is neither prepended nor appended to the result.
+ *                 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.
+ */
+int strjoin (char *dst, size_t dst_len, char **fields, size_t fields_num, const char *sep);
+
+/*
+ * NAME
+ *   escape_slashes
+ *
+ * DESCRIPTION
+ *   Removes slashes from the string `buf' and substitutes them with something
+ *   appropriate. This function should be used whenever a path is to be used as
+ *   (part of) an instance.
+ *
+ * PARAMETERS
+ *   `buf'         String to be escaped.
+ *   `buf_len'     Length of the buffer. No more then this many bytes will be
+ *   written to `buf', including the trailing null-byte.
+ *
+ * RETURN VALUE
+ *   Returns zero upon success and a value smaller than zero upon failure.
+ */
+int escape_slashes (char *buf, int buf_len);
+
+/*
+ * NAME
+ *   replace_special
+ *
+ * DESCRIPTION
+ *   Replaces any special characters (anything that's not alpha-numeric or a
+ *   dash) with an underscore.
+ *
+ *   E.g. "foo$bar&" would become "foo_bar_".
+ *
+ * PARAMETERS
+ *   `buffer'      String to be handled.
+ *   `buffer_size' Length of the string. The function returns after
+ *                 encountering a null-byte or reading this many bytes.
+ */
+void replace_special (char *buffer, size_t buffer_size);
+
+int strsubstitute (char *str, char c_from, char c_to);
+
+/*
+ * NAME
+ *   strunescape
+ *
+ * DESCRIPTION
+ *   Replaces any escaped characters in a string with the appropriate special
+ *   characters. The following escaped characters are recognized:
+ *
+ *     \t -> <tab>
+ *     \n -> <newline>
+ *     \r -> <carriage return>
+ *
+ *   For all other escacped characters only the backslash will be removed.
+ *
+ * PARAMETERS
+ *   `buf'         String to be unescaped.
+ *   `buf_len'     Length of the string, including the terminating null-byte.
+ *
+ * RETURN VALUE
+ *   Returns zero upon success, a value less than zero else.
+ */
+int strunescape (char *buf, size_t buf_len);
+
+/*
+ * NAME
+ *   timeval_cmp
+ *
+ * DESCRIPTION
+ *   Compare the two time values `tv0' and `tv1' and store the absolut value
+ *   of the difference in the time value pointed to by `delta' if it does not
+ *   equal NULL.
+ *
+ * RETURN VALUE
+ *   Returns an integer less than, equal to, or greater than zero if `tv0' is
+ *   less than, equal to, or greater than `tv1' respectively.
+ */
+int timeval_cmp (struct timeval tv0, struct timeval tv1, struct timeval *delta);
+
+/* make sure tv_usec stores less than a second */
+#define NORMALIZE_TIMEVAL(tv) \
+       do { \
+               (tv).tv_sec += (tv).tv_usec / 1000000; \
+               (tv).tv_usec = (tv).tv_usec % 1000000; \
+       } while (0)
+
+/* make sure tv_sec stores less than a second */
+#define NORMALIZE_TIMESPEC(tv) \
+       do { \
+               (tv).tv_sec += (tv).tv_nsec / 1000000000; \
+               (tv).tv_nsec = (tv).tv_nsec % 1000000000; \
+       } while (0)
+
+int check_create_dir (const char *file_orig);
+
+#ifdef HAVE_LIBKSTAT
+int get_kstat (kstat_t **ksp_ptr, char *module, int instance, char *name);
+long long get_kstat_value (kstat_t *ksp, char *name);
+#endif
+
+#ifndef HAVE_HTONLL
+unsigned long long ntohll (unsigned long long n);
+unsigned long long htonll (unsigned long long n);
+#endif
+
+#if FP_LAYOUT_NEED_NOTHING
+# define ntohd(d) (d)
+# define htond(d) (d)
+#elif FP_LAYOUT_NEED_ENDIANFLIP || FP_LAYOUT_NEED_INTSWAP
+double ntohd (double d);
+double htond (double d);
+#else
+# error "Don't know how to convert between host and network representation of doubles."
+#endif
+
+int format_name (char *ret, int ret_len,
+               const char *hostname,
+               const char *plugin, const char *plugin_instance,
+               const char *type, const char *type_instance);
+#define FORMAT_VL(ret, ret_len, vl) \
+       format_name (ret, ret_len, (vl)->host, (vl)->plugin, (vl)->plugin_instance, \
+                       (vl)->type, (vl)->type_instance)
+int format_values (char *ret, size_t ret_len,
+               const data_set_t *ds, const value_list_t *vl,
+               _Bool store_rates);
+
+int parse_identifier (char *str, char **ret_host,
+               char **ret_plugin, char **ret_plugin_instance,
+               char **ret_type, char **ret_type_instance);
+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);
+
+#if !HAVE_GETPWNAM_R
+int getpwnam_r (const char *name, struct passwd *pwbuf, char *buf,
+               size_t buflen, struct passwd **pwbufp);
+#endif
+
+int notification_init (notification_t *n, int severity, const char *message,
+               const char *host,
+               const char *plugin, const char *plugin_instance,
+               const char *type, const char *type_instance);
+#define NOTIFICATION_INIT_VL(n, vl) \
+       notification_init (n, NOTIF_FAILURE, NULL, \
+                       (vl)->host, (vl)->plugin, (vl)->plugin_instance, \
+                       (vl)->type, (vl)->type_instance)
+
+typedef int (*dirwalk_callback_f)(const char *dirname, const char *filename,
+               void *user_data);
+int walk_directory (const char *dir, dirwalk_callback_f callback,
+               void *user_data, int hidden);
+int read_file_contents (const char *filename, char *buf, int bufsize);
+
+counter_t counter_diff (counter_t old_value, counter_t new_value);
+
+/* Converts a service name (a string) to a port number
+ * (in the range [1-65535]). Returns less than zero on error. */
+int service_name_to_port_number (const char *service_name);
+
+/** Parse a string to a derive_t value. Returns zero on success or non-zero on
+ * failure. If failure is returned, ret_value is not touched. */
+int strtoderive (const char *string, derive_t *ret_value);
+
+#endif /* COMMON_H */
diff --git a/src/configfile.c b/src/configfile.c
new file mode 100644 (file)
index 0000000..7c8347b
--- /dev/null
@@ -0,0 +1,1120 @@
+/**
+ * collectd - src/configfile.c
+ * Copyright (C) 2005-2011  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at collectd.org>
+ *   Sebastian tokkee Harl <sh at tokkee.org>
+ **/
+
+#include "collectd.h"
+
+#include "liboconfig/oconfig.h"
+
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+#include "types_list.h"
+#include "filter_chain.h"
+
+#if HAVE_WORDEXP_H
+# include <wordexp.h>
+#endif /* HAVE_WORDEXP_H */
+
+#define ESCAPE_NULL(str) ((str) == NULL ? "(null)" : (str))
+
+/*
+ * Private types
+ */
+typedef struct cf_callback
+{
+       const char  *type;
+       int  (*callback) (const char *, const char *);
+       const char **keys;
+       int    keys_num;
+       struct cf_callback *next;
+} cf_callback_t;
+
+typedef struct cf_complex_callback_s
+{
+       char *type;
+       int (*callback) (oconfig_item_t *);
+       struct cf_complex_callback_s *next;
+} cf_complex_callback_t;
+
+typedef struct cf_value_map_s
+{
+       char *key;
+       int (*func) (const oconfig_item_t *);
+} cf_value_map_t;
+
+typedef struct cf_global_option_s
+{
+       char *key;
+       char *value;
+       char *def;
+} cf_global_option_t;
+
+/*
+ * Prototypes of callback functions
+ */
+static int dispatch_value_typesdb (const oconfig_item_t *ci);
+static int dispatch_value_plugindir (const oconfig_item_t *ci);
+static int dispatch_loadplugin (const oconfig_item_t *ci);
+
+/*
+ * Private variables
+ */
+static cf_callback_t *first_callback = NULL;
+static cf_complex_callback_t *complex_callback_head = NULL;
+
+static cf_value_map_t cf_value_map[] =
+{
+       {"TypesDB",    dispatch_value_typesdb},
+       {"PluginDir",  dispatch_value_plugindir},
+       {"LoadPlugin", dispatch_loadplugin}
+};
+static int cf_value_map_num = STATIC_ARRAY_LEN (cf_value_map);
+
+static cf_global_option_t cf_global_options[] =
+{
+       {"BaseDir",     NULL, PKGLOCALSTATEDIR},
+       {"PIDFile",     NULL, PIDFILE},
+       {"Hostname",    NULL, NULL},
+       {"FQDNLookup",  NULL, "true"},
+       {"Interval",    NULL, "10"},
+       {"ReadThreads", NULL, "5"},
+       {"Timeout",     NULL, "2"},
+       {"PreCacheChain",  NULL, "PreCache"},
+       {"PostCacheChain", NULL, "PostCache"}
+};
+static int cf_global_options_num = STATIC_ARRAY_LEN (cf_global_options);
+
+static int cf_default_typesdb = 1;
+
+/*
+ * Functions to handle register/unregister, search, and other plugin related
+ * stuff
+ */
+static cf_callback_t *cf_search (const char *type)
+{
+       cf_callback_t *cf_cb;
+
+       if (type == NULL)
+               return (NULL);
+
+       for (cf_cb = first_callback; cf_cb != NULL; cf_cb = cf_cb->next)
+               if (strcasecmp (cf_cb->type, type) == 0)
+                       break;
+
+       return (cf_cb);
+}
+
+static int cf_dispatch (const char *type, const char *orig_key,
+               const char *orig_value)
+{
+       cf_callback_t *cf_cb;
+       char *key;
+       char *value;
+       int ret;
+       int i;
+
+       DEBUG ("type = %s, key = %s, value = %s",
+                       ESCAPE_NULL(type),
+                       ESCAPE_NULL(orig_key),
+                       ESCAPE_NULL(orig_value));
+
+       if ((cf_cb = cf_search (type)) == NULL)
+       {
+               WARNING ("Found a configuration for the `%s' plugin, but "
+                               "the plugin isn't loaded or didn't register "
+                               "a configuration callback.", type);
+               return (-1);
+       }
+
+       if ((key = strdup (orig_key)) == NULL)
+               return (1);
+       if ((value = strdup (orig_value)) == NULL)
+       {
+               free (key);
+               return (2);
+       }
+
+       ret = -1;
+
+       for (i = 0; i < cf_cb->keys_num; i++)
+       {
+               if ((cf_cb->keys[i] != NULL)
+                               && (strcasecmp (cf_cb->keys[i], key) == 0))
+               {
+                       ret = (*cf_cb->callback) (key, value);
+                       break;
+               }
+       }
+
+       if (i >= cf_cb->keys_num)
+               WARNING ("Plugin `%s' did not register for value `%s'.", type, key);
+
+       free (key);
+       free (value);
+
+       DEBUG ("cf_dispatch: return (%i)", ret);
+
+       return (ret);
+} /* int cf_dispatch */
+
+static int dispatch_global_option (const oconfig_item_t *ci)
+{
+       if (ci->values_num != 1)
+               return (-1);
+       if (ci->values[0].type == OCONFIG_TYPE_STRING)
+               return (global_option_set (ci->key, ci->values[0].value.string));
+       else if (ci->values[0].type == OCONFIG_TYPE_NUMBER)
+       {
+               char tmp[128];
+               ssnprintf (tmp, sizeof (tmp), "%lf", ci->values[0].value.number);
+               return (global_option_set (ci->key, tmp));
+       }
+       else if (ci->values[0].type == OCONFIG_TYPE_BOOLEAN)
+       {
+               if (ci->values[0].value.boolean)
+                       return (global_option_set (ci->key, "true"));
+               else
+                       return (global_option_set (ci->key, "false"));
+       }
+
+       return (-1);
+} /* int dispatch_global_option */
+
+static int dispatch_value_typesdb (const oconfig_item_t *ci)
+{
+       int i = 0;
+
+       assert (strcasecmp (ci->key, "TypesDB") == 0);
+
+       cf_default_typesdb = 0;
+
+       if (ci->values_num < 1) {
+               ERROR ("configfile: `TypesDB' needs at least one argument.");
+               return (-1);
+       }
+
+       for (i = 0; i < ci->values_num; ++i)
+       {
+               if (OCONFIG_TYPE_STRING != ci->values[i].type) {
+                       WARNING ("configfile: TypesDB: Skipping %i. argument which "
+                                       "is not a string.", i + 1);
+                       continue;
+               }
+
+               read_types_list (ci->values[i].value.string);
+       }
+       return (0);
+} /* int dispatch_value_typesdb */
+
+static int dispatch_value_plugindir (const oconfig_item_t *ci)
+{
+       assert (strcasecmp (ci->key, "PluginDir") == 0);
+       
+       if (ci->values_num != 1)
+               return (-1);
+       if (ci->values[0].type != OCONFIG_TYPE_STRING)
+               return (-1);
+
+       plugin_set_dir (ci->values[0].value.string);
+       return (0);
+}
+
+static int dispatch_loadplugin (const oconfig_item_t *ci)
+{
+       int i;
+       const char *name;
+       unsigned int flags = 0;
+       assert (strcasecmp (ci->key, "LoadPlugin") == 0);
+
+       if (ci->values_num != 1)
+               return (-1);
+       if (ci->values[0].type != OCONFIG_TYPE_STRING)
+               return (-1);
+
+       name = ci->values[0].value.string;
+
+       /*
+        * XXX: Magic at work:
+        *
+        * Some of the language bindings, for example the Python and Perl
+        * plugins, need to be able to export symbols to the scripts they run.
+        * For this to happen, the "Globals" flag needs to be set.
+        * Unfortunately, this technical detail is hard to explain to the
+        * average user and she shouldn't have to worry about this, ideally.
+        * So in order to save everyone's sanity use a different default for a
+        * handful of special plugins. --octo
+        */
+       if ((strcasecmp ("Perl", name) == 0)
+                       || (strcasecmp ("Python", name) == 0))
+               flags |= PLUGIN_FLAGS_GLOBAL;
+
+       for (i = 0; i < ci->children_num; ++i) {
+               if (strcasecmp("Globals", ci->children[i].key) == 0)
+                       cf_util_get_flag (ci->children + i, &flags, PLUGIN_FLAGS_GLOBAL);
+               else {
+                       WARNING("Ignoring unknown LoadPlugin option \"%s\" "
+                                       "for plugin \"%s\"",
+                                       ci->children[i].key, ci->values[0].value.string);
+               }
+       }
+
+       return (plugin_load (name, (uint32_t) flags));
+} /* int dispatch_value_loadplugin */
+
+static int dispatch_value_plugin (const char *plugin, oconfig_item_t *ci)
+{
+       char  buffer[4096];
+       char *buffer_ptr;
+       int   buffer_free;
+       int i;
+
+       buffer_ptr = buffer;
+       buffer_free = sizeof (buffer);
+
+       for (i = 0; i < ci->values_num; i++)
+       {
+               int status = -1;
+
+               if (ci->values[i].type == OCONFIG_TYPE_STRING)
+                       status = ssnprintf (buffer_ptr, buffer_free, " %s",
+                                       ci->values[i].value.string);
+               else if (ci->values[i].type == OCONFIG_TYPE_NUMBER)
+                       status = ssnprintf (buffer_ptr, buffer_free, " %lf",
+                                       ci->values[i].value.number);
+               else if (ci->values[i].type == OCONFIG_TYPE_BOOLEAN)
+                       status = ssnprintf (buffer_ptr, buffer_free, " %s",
+                                       ci->values[i].value.boolean
+                                       ? "true" : "false");
+
+               if ((status < 0) || (status >= buffer_free))
+                       return (-1);
+               buffer_free -= status;
+               buffer_ptr  += status;
+       }
+       /* skip the initial space */
+       buffer_ptr = buffer + 1;
+
+       return (cf_dispatch (plugin, ci->key, buffer_ptr));
+} /* int dispatch_value_plugin */
+
+static int dispatch_value (const oconfig_item_t *ci)
+{
+       int ret = -2;
+       int i;
+
+       for (i = 0; i < cf_value_map_num; i++)
+               if (strcasecmp (cf_value_map[i].key, ci->key) == 0)
+               {
+                       ret = cf_value_map[i].func (ci);
+                       break;
+               }
+
+       for (i = 0; i < cf_global_options_num; i++)
+               if (strcasecmp (cf_global_options[i].key, ci->key) == 0)
+               {
+                       ret = dispatch_global_option (ci);
+                       break;
+               }
+
+       return (ret);
+} /* int dispatch_value */
+
+static int dispatch_block_plugin (oconfig_item_t *ci)
+{
+       int i;
+       char *name;
+
+       cf_complex_callback_t *cb;
+
+       if (strcasecmp (ci->key, "Plugin") != 0)
+               return (-1);
+       if (ci->values_num < 1)
+               return (-1);
+       if (ci->values[0].type != OCONFIG_TYPE_STRING)
+               return (-1);
+
+       name = ci->values[0].value.string;
+
+       /* Check for a complex callback first */
+       for (cb = complex_callback_head; cb != NULL; cb = cb->next)
+               if (strcasecmp (name, cb->type) == 0)
+                       return (cb->callback (ci));
+
+       /* Hm, no complex plugin found. Dispatch the values one by one */
+       for (i = 0; i < ci->children_num; i++)
+       {
+               if (ci->children[i].children == NULL)
+                       dispatch_value_plugin (name, ci->children + i);
+               else
+               {
+                       WARNING ("There is a `%s' block within the "
+                                       "configuration for the %s plugin. "
+                                       "The plugin either only expects "
+                                       "\"simple\" configuration statements "
+                                       "or wasn't loaded using `LoadPlugin'."
+                                       " Please check your configuration.",
+                                       ci->children[i].key, name);
+               }
+       }
+
+       return (0);
+}
+
+
+static int dispatch_block (oconfig_item_t *ci)
+{
+       if (strcasecmp (ci->key, "LoadPlugin") == 0)
+               return (dispatch_loadplugin (ci));
+       else if (strcasecmp (ci->key, "Plugin") == 0)
+               return (dispatch_block_plugin (ci));
+       else if (strcasecmp (ci->key, "Chain") == 0)
+               return (fc_configure (ci));
+
+       return (0);
+}
+
+static int cf_ci_replace_child (oconfig_item_t *dst, oconfig_item_t *src,
+               int offset)
+{
+       oconfig_item_t *temp;
+       int i;
+
+       assert (offset >= 0);
+       assert (dst->children_num > offset);
+
+       /* Free the memory used by the replaced child. Usually that's the
+        * `Include "blah"' statement. */
+       temp = dst->children + offset;
+       for (i = 0; i < temp->values_num; i++)
+       {
+               if (temp->values[i].type == OCONFIG_TYPE_STRING)
+               {
+                       sfree (temp->values[i].value.string);
+               }
+       }
+       sfree (temp->values);
+       temp = NULL;
+
+       /* If (src->children_num == 0) the array size is decreased. If offset
+        * is _not_ the last element, (offset < (dst->children_num - 1)), then
+        * we need to move the trailing elements before resizing the array. */
+       if ((src->children_num == 0) && (offset < (dst->children_num - 1)))
+       {
+               int nmemb = dst->children_num - (offset + 1);
+               memmove (dst->children + offset, dst->children + offset + 1,
+                               sizeof (oconfig_item_t) * nmemb);
+       }
+
+       /* Resize the memory containing the children to be big enough to hold
+        * all children. */
+       temp = (oconfig_item_t *) realloc (dst->children,
+                       sizeof (oconfig_item_t)
+                       * (dst->children_num + src->children_num - 1));
+       if (temp == NULL)
+       {
+               ERROR ("configfile: realloc failed.");
+               return (-1);
+       }
+       dst->children = temp;
+
+       /* If there are children behind the include statement, and they have
+        * not yet been moved because (src->children_num == 0), then move them
+        * to the end of the list, so that the new children have room before
+        * them. */
+       if ((src->children_num > 0)
+                       && ((dst->children_num - (offset + 1)) > 0))
+       {
+               int nmemb = dst->children_num - (offset + 1);
+               int old_offset = offset + 1;
+               int new_offset = offset + src->children_num;
+
+               memmove (dst->children + new_offset,
+                               dst->children + old_offset,
+                               sizeof (oconfig_item_t) * nmemb);
+       }
+
+       /* Last but not least: If there are new children, copy them to the
+        * memory reserved for them. */
+       if (src->children_num > 0)
+       {
+               memcpy (dst->children + offset,
+                               src->children,
+                               sizeof (oconfig_item_t) * src->children_num);
+       }
+
+       /* Update the number of children. */
+       dst->children_num += (src->children_num - 1);
+
+       return (0);
+} /* int cf_ci_replace_child */
+
+static int cf_ci_append_children (oconfig_item_t *dst, oconfig_item_t *src)
+{
+       oconfig_item_t *temp;
+
+       if ((src == NULL) || (src->children_num == 0))
+               return (0);
+
+       temp = (oconfig_item_t *) realloc (dst->children,
+                       sizeof (oconfig_item_t)
+                       * (dst->children_num + src->children_num));
+       if (temp == NULL)
+       {
+               ERROR ("configfile: realloc failed.");
+               return (-1);
+       }
+       dst->children = temp;
+
+       memcpy (dst->children + dst->children_num,
+                       src->children,
+                       sizeof (oconfig_item_t)
+                       * src->children_num);
+       dst->children_num += src->children_num;
+
+       return (0);
+} /* int cf_ci_append_children */
+
+#define CF_MAX_DEPTH 8
+static oconfig_item_t *cf_read_generic (const char *path, int depth);
+
+static int cf_include_all (oconfig_item_t *root, int depth)
+{
+       int i;
+
+       for (i = 0; i < root->children_num; i++)
+       {
+               oconfig_item_t *new;
+               oconfig_item_t *old;
+
+               /* Ignore all blocks, including `Include' blocks. */
+               if (root->children[i].children_num != 0)
+                       continue;
+
+               if (strcasecmp (root->children[i].key, "Include") != 0)
+                       continue;
+
+               old = root->children + i;
+
+               if ((old->values_num != 1)
+                               || (old->values[0].type != OCONFIG_TYPE_STRING))
+               {
+                       ERROR ("configfile: `Include' needs exactly one string argument.");
+                       continue;
+               }
+
+               new = cf_read_generic (old->values[0].value.string, depth + 1);
+               if (new == NULL)
+                       continue;
+
+               /* Now replace the i'th child in `root' with `new'. */
+               cf_ci_replace_child (root, new, i);
+
+               /* ... and go back to the new i'th child. */
+               --i;
+
+               sfree (new->values);
+               sfree (new);
+       } /* for (i = 0; i < root->children_num; i++) */
+
+       return (0);
+} /* int cf_include_all */
+
+static oconfig_item_t *cf_read_file (const char *file, int depth)
+{
+       oconfig_item_t *root;
+
+       assert (depth < CF_MAX_DEPTH);
+
+       root = oconfig_parse_file (file);
+       if (root == NULL)
+       {
+               ERROR ("configfile: Cannot read file `%s'.", file);
+               return (NULL);
+       }
+
+       cf_include_all (root, depth);
+
+       return (root);
+} /* oconfig_item_t *cf_read_file */
+
+static int cf_compare_string (const void *p1, const void *p2)
+{
+       return strcmp (*(const char **) p1, *(const char **) p2);
+}
+
+static oconfig_item_t *cf_read_dir (const char *dir, int depth)
+{
+       oconfig_item_t *root = NULL;
+       DIR *dh;
+       struct dirent *de;
+       char **filenames = NULL;
+       int filenames_num = 0;
+       int status;
+       int i;
+
+       assert (depth < CF_MAX_DEPTH);
+
+       dh = opendir (dir);
+       if (dh == NULL)
+       {
+               char errbuf[1024];
+               ERROR ("configfile: opendir failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (NULL);
+       }
+
+       root = (oconfig_item_t *) malloc (sizeof (oconfig_item_t));
+       if (root == NULL)
+       {
+               ERROR ("configfile: malloc failed.");
+               return (NULL);
+       }
+       memset (root, 0, sizeof (oconfig_item_t));
+
+       while ((de = readdir (dh)) != NULL)
+       {
+               char   name[1024];
+               char **tmp;
+
+               if ((de->d_name[0] == '.') || (de->d_name[0] == 0))
+                       continue;
+
+               status = ssnprintf (name, sizeof (name), "%s/%s",
+                               dir, de->d_name);
+               if ((status < 0) || ((size_t) status >= sizeof (name)))
+               {
+                       ERROR ("configfile: Not including `%s/%s' because its"
+                                       " name is too long.",
+                                       dir, de->d_name);
+                       for (i = 0; i < filenames_num; ++i)
+                               free (filenames[i]);
+                       free (filenames);
+                       free (root);
+                       return (NULL);
+               }
+
+               ++filenames_num;
+               tmp = (char **) realloc (filenames,
+                               filenames_num * sizeof (*filenames));
+               if (tmp == NULL) {
+                       ERROR ("configfile: realloc failed.");
+                       for (i = 0; i < filenames_num - 1; ++i)
+                               free (filenames[i]);
+                       free (filenames);
+                       free (root);
+                       return (NULL);
+               }
+               filenames = tmp;
+
+               filenames[filenames_num - 1] = sstrdup (name);
+       }
+
+       qsort ((void *) filenames, filenames_num, sizeof (*filenames),
+                       cf_compare_string);
+
+       for (i = 0; i < filenames_num; ++i)
+       {
+               oconfig_item_t *temp;
+               char *name = filenames[i];
+
+               temp = cf_read_generic (name, depth);
+               if (temp == NULL)
+               {
+                       /* An error should already have been reported. */
+                       sfree (name);
+                       continue;
+               }
+
+               cf_ci_append_children (root, temp);
+               sfree (temp->children);
+               sfree (temp);
+
+               free (name);
+       }
+
+       free(filenames);
+       return (root);
+} /* oconfig_item_t *cf_read_dir */
+
+/* 
+ * cf_read_generic
+ *
+ * Path is stat'ed and either cf_read_file or cf_read_dir is called
+ * accordingly.
+ *
+ * There are two versions of this function: If `wordexp' exists shell wildcards
+ * will be expanded and the function will include all matches found. If
+ * `wordexp' (or, more precisely, it's header file) is not available the
+ * simpler function is used which does not do any such expansion.
+ */
+#if HAVE_WORDEXP_H
+static oconfig_item_t *cf_read_generic (const char *path, int depth)
+{
+       oconfig_item_t *root = NULL;
+       int status;
+       const char *path_ptr;
+       wordexp_t we;
+       size_t i;
+
+       if (depth >= CF_MAX_DEPTH)
+       {
+               ERROR ("configfile: Not including `%s' because the maximum "
+                               "nesting depth has been reached.", path);
+               return (NULL);
+       }
+
+       status = wordexp (path, &we, WRDE_NOCMD);
+       if (status != 0)
+       {
+               ERROR ("configfile: wordexp (%s) failed.", path);
+               return (NULL);
+       }
+
+       root = (oconfig_item_t *) malloc (sizeof (oconfig_item_t));
+       if (root == NULL)
+       {
+               ERROR ("configfile: malloc failed.");
+               return (NULL);
+       }
+       memset (root, '\0', sizeof (oconfig_item_t));
+
+       /* wordexp() might return a sorted list already. That's not
+        * documented though, so let's make sure we get what we want. */
+       qsort ((void *) we.we_wordv, we.we_wordc, sizeof (*we.we_wordv),
+                       cf_compare_string);
+
+       for (i = 0; i < we.we_wordc; i++)
+       {
+               oconfig_item_t *temp;
+               struct stat statbuf;
+
+               path_ptr = we.we_wordv[i];
+
+               status = stat (path_ptr, &statbuf);
+               if (status != 0)
+               {
+                       char errbuf[1024];
+                       WARNING ("configfile: stat (%s) failed: %s",
+                                       path_ptr,
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       continue;
+               }
+
+               if (S_ISREG (statbuf.st_mode))
+                       temp = cf_read_file (path_ptr, depth);
+               else if (S_ISDIR (statbuf.st_mode))
+                       temp = cf_read_dir (path_ptr, depth);
+               else
+               {
+                       WARNING ("configfile: %s is neither a file nor a "
+                                       "directory.", path);
+                       continue;
+               }
+
+               if (temp == NULL) {
+                       oconfig_free (root);
+                       return (NULL);
+               }
+
+               cf_ci_append_children (root, temp);
+               sfree (temp->children);
+               sfree (temp);
+       }
+
+       wordfree (&we);
+
+       if (root->children == NULL)
+       {
+               oconfig_free (root);
+               return (NULL);
+       }
+
+       return (root);
+} /* oconfig_item_t *cf_read_generic */
+/* #endif HAVE_WORDEXP_H */
+
+#else /* if !HAVE_WORDEXP_H */
+static oconfig_item_t *cf_read_generic (const char *path, int depth)
+{
+       struct stat statbuf;
+       int status;
+
+       if (depth >= CF_MAX_DEPTH)
+       {
+               ERROR ("configfile: Not including `%s' because the maximum "
+                               "nesting depth has been reached.", path);
+               return (NULL);
+       }
+
+       status = stat (path, &statbuf);
+       if (status != 0)
+       {
+               char errbuf[1024];
+               ERROR ("configfile: stat (%s) failed: %s",
+                               path,
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (NULL);
+       }
+
+       if (S_ISREG (statbuf.st_mode))
+               return (cf_read_file (path, depth));
+       else if (S_ISDIR (statbuf.st_mode))
+               return (cf_read_dir (path, depth));
+
+       ERROR ("configfile: %s is neither a file nor a directory.", path);
+       return (NULL);
+} /* oconfig_item_t *cf_read_generic */
+#endif /* !HAVE_WORDEXP_H */
+
+/* 
+ * Public functions
+ */
+int global_option_set (const char *option, const char *value)
+{
+       int i;
+
+       DEBUG ("option = %s; value = %s;", option, value);
+
+       for (i = 0; i < cf_global_options_num; i++)
+               if (strcasecmp (cf_global_options[i].key, option) == 0)
+                       break;
+
+       if (i >= cf_global_options_num)
+               return (-1);
+
+       sfree (cf_global_options[i].value);
+
+       if (value != NULL)
+               cf_global_options[i].value = strdup (value);
+       else
+               cf_global_options[i].value = NULL;
+
+       return (0);
+}
+
+const char *global_option_get (const char *option)
+{
+       int i;
+
+       for (i = 0; i < cf_global_options_num; i++)
+               if (strcasecmp (cf_global_options[i].key, option) == 0)
+                       break;
+
+       if (i >= cf_global_options_num)
+               return (NULL);
+       
+       return ((cf_global_options[i].value != NULL)
+                       ? cf_global_options[i].value
+                       : cf_global_options[i].def);
+} /* char *global_option_get */
+
+void cf_unregister (const char *type)
+{
+       cf_callback_t *this, *prev;
+
+       for (prev = NULL, this = first_callback;
+                       this != NULL;
+                       prev = this, this = this->next)
+               if (strcasecmp (this->type, type) == 0)
+               {
+                       if (prev == NULL)
+                               first_callback = this->next;
+                       else
+                               prev->next = this->next;
+
+                       free (this);
+                       break;
+               }
+} /* void cf_unregister */
+
+void cf_unregister_complex (const char *type)
+{
+       cf_complex_callback_t *this, *prev;
+
+       for (prev = NULL, this = complex_callback_head;
+                       this != NULL;
+                       prev = this, this = this->next)
+               if (strcasecmp (this->type, type) == 0)
+               {
+                       if (prev == NULL)
+                               complex_callback_head = this->next;
+                       else
+                               prev->next = this->next;
+
+                       sfree (this->type);
+                       sfree (this);
+                       break;
+               }
+} /* void cf_unregister */
+
+void cf_register (const char *type,
+               int (*callback) (const char *, const char *),
+               const char **keys, int keys_num)
+{
+       cf_callback_t *cf_cb;
+
+       /* Remove this module from the list, if it already exists */
+       cf_unregister (type);
+
+       /* This pointer will be free'd in `cf_unregister' */
+       if ((cf_cb = (cf_callback_t *) malloc (sizeof (cf_callback_t))) == NULL)
+               return;
+
+       cf_cb->type     = type;
+       cf_cb->callback = callback;
+       cf_cb->keys     = keys;
+       cf_cb->keys_num = keys_num;
+
+       cf_cb->next = first_callback;
+       first_callback = cf_cb;
+} /* void cf_register */
+
+int cf_register_complex (const char *type, int (*callback) (oconfig_item_t *))
+{
+       cf_complex_callback_t *new;
+
+       new = (cf_complex_callback_t *) malloc (sizeof (cf_complex_callback_t));
+       if (new == NULL)
+               return (-1);
+
+       new->type = strdup (type);
+       if (new->type == NULL)
+       {
+               sfree (new);
+               return (-1);
+       }
+
+       new->callback = callback;
+       new->next = NULL;
+
+       if (complex_callback_head == NULL)
+       {
+               complex_callback_head = new;
+       }
+       else
+       {
+               cf_complex_callback_t *last = complex_callback_head;
+               while (last->next != NULL)
+                       last = last->next;
+               last->next = new;
+       }
+
+       return (0);
+} /* int cf_register_complex */
+
+int cf_read (char *filename)
+{
+       oconfig_item_t *conf;
+       int i;
+
+       conf = cf_read_generic (filename, 0 /* depth */);
+       if (conf == NULL)
+       {
+               ERROR ("Unable to read config file %s.", filename);
+               return (-1);
+       }
+
+       for (i = 0; i < conf->children_num; i++)
+       {
+               if (conf->children[i].children == NULL)
+                       dispatch_value (conf->children + i);
+               else
+                       dispatch_block (conf->children + i);
+       }
+
+       oconfig_free (conf);
+
+       /* Read the default types.db if no `TypesDB' option was given. */
+       if (cf_default_typesdb)
+               read_types_list (PKGDATADIR"/types.db");
+
+       return (0);
+} /* int cf_read */
+
+/* Assures the config option is a string, duplicates it and returns the copy in
+ * "ret_string". If necessary "*ret_string" is freed first. Returns zero upon
+ * success. */
+int cf_util_get_string (const oconfig_item_t *ci, char **ret_string) /* {{{ */
+{
+       char *string;
+
+       if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+       {
+               ERROR ("cf_util_get_string: The %s option requires "
+                               "exactly one string argument.", ci->key);
+               return (-1);
+       }
+
+       string = strdup (ci->values[0].value.string);
+       if (string == NULL)
+               return (-1);
+
+       if (*ret_string != NULL)
+               sfree (*ret_string);
+       *ret_string = string;
+
+       return (0);
+} /* }}} int cf_util_get_string */
+
+/* Assures the config option is a string and copies it to the provided buffer.
+ * Assures null-termination. */
+int cf_util_get_string_buffer (const oconfig_item_t *ci, char *buffer, /* {{{ */
+               size_t buffer_size)
+{
+       if ((ci == NULL) || (buffer == NULL) || (buffer_size < 1))
+               return (EINVAL);
+
+       if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+       {
+               ERROR ("cf_util_get_string_buffer: The %s option requires "
+                               "exactly one string argument.", ci->key);
+               return (-1);
+       }
+
+       strncpy (buffer, ci->values[0].value.string, buffer_size);
+       buffer[buffer_size - 1] = 0;
+
+       return (0);
+} /* }}} int cf_util_get_string_buffer */
+
+/* Assures the config option is a number and returns it as an int. */
+int cf_util_get_int (const oconfig_item_t *ci, int *ret_value) /* {{{ */
+{
+       if ((ci == NULL) || (ret_value == NULL))
+               return (EINVAL);
+
+       if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
+       {
+               ERROR ("cf_util_get_int: The %s option requires "
+                               "exactly one numeric argument.", ci->key);
+               return (-1);
+       }
+
+       *ret_value = (int) ci->values[0].value.number;
+
+       return (0);
+} /* }}} int cf_util_get_int */
+
+int cf_util_get_boolean (const oconfig_item_t *ci, _Bool *ret_bool) /* {{{ */
+{
+       if ((ci == NULL) || (ret_bool == NULL))
+               return (EINVAL);
+
+       if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN))
+       {
+               ERROR ("cf_util_get_boolean: The %s option requires "
+                               "exactly one boolean argument.", ci->key);
+               return (-1);
+       }
+
+       *ret_bool = ci->values[0].value.boolean ? 1 : 0;
+
+       return (0);
+} /* }}} int cf_util_get_boolean */
+
+int cf_util_get_flag (const oconfig_item_t *ci, /* {{{ */
+               unsigned int *ret_value, unsigned int flag)
+{
+       int status;
+       _Bool b;
+
+       if (ret_value == NULL)
+               return (EINVAL);
+
+       b = 0;
+       status = cf_util_get_boolean (ci, &b);
+       if (status != 0)
+               return (status);
+
+       if (b)
+       {
+               *ret_value |= flag;
+       }
+       else
+       {
+               *ret_value &= ~flag;
+       }
+
+       return (0);
+} /* }}} int cf_util_get_flag */
+
+/* Assures that the config option is a string or a number if the correct range
+ * of 1-65535. The string is then converted to a port number using
+ * `service_name_to_port_number' and returned.
+ * Returns the port number in the range [1-65535] or less than zero upon
+ * failure. */
+int cf_util_get_port_number (const oconfig_item_t *ci) /* {{{ */
+{
+       int tmp;
+
+       if ((ci->values_num != 1)
+                       || ((ci->values[0].type != OCONFIG_TYPE_STRING)
+                               && (ci->values[0].type != OCONFIG_TYPE_NUMBER)))
+       {
+               ERROR ("cf_util_get_port_number: The \"%s\" option requires "
+                               "exactly one string argument.", ci->key);
+               return (-1);
+       }
+
+       if (ci->values[0].type == OCONFIG_TYPE_STRING)
+               return (service_name_to_port_number (ci->values[0].value.string));
+
+       assert (ci->values[0].type == OCONFIG_TYPE_NUMBER);
+       tmp = (int) (ci->values[0].value.number + 0.5);
+       if ((tmp < 1) || (tmp > 65535))
+       {
+               ERROR ("cf_util_get_port_number: The \"%s\" option requires "
+                               "a service name or a port number. The number "
+                               "you specified, %i, is not in the valid "
+                               "range of 1-65535.",
+                               ci->key, tmp);
+               return (-1);
+       }
+
+       return (tmp);
+} /* }}} int cf_util_get_port_number */
+
+int cf_util_get_cdtime (const oconfig_item_t *ci, cdtime_t *ret_value) /* {{{ */
+{
+       if ((ci == NULL) || (ret_value == NULL))
+               return (EINVAL);
+
+       if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
+       {
+               ERROR ("cf_util_get_cdtime: The %s option requires "
+                               "exactly one numeric argument.", ci->key);
+               return (-1);
+       }
+
+       if (ci->values[0].value.number < 0.0)
+       {
+               ERROR ("cf_util_get_cdtime: The numeric argument of the %s "
+                               "option must not be negative.", ci->key);
+               return (-1);
+       }
+
+       *ret_value = DOUBLE_TO_CDTIME_T (ci->values[0].value.number);
+
+       return (0);
+} /* }}} int cf_util_get_cdtime */
+
diff --git a/src/configfile.h b/src/configfile.h
new file mode 100644 (file)
index 0000000..e63a0ea
--- /dev/null
@@ -0,0 +1,121 @@
+#ifndef CONFIGFILE_H
+#define CONFIGFILE_H
+/**
+ * collectd - src/configfile.h
+ * Copyright (C) 2005-2011  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at collectd.org>
+ **/
+
+#include "collectd.h"
+#include "utils_time.h"
+#include "liboconfig/oconfig.h"
+
+/*
+ * DESCRIPTION
+ *  Remove a registered plugin from the internal data structures.
+ * 
+ * PARAMETERS
+ *  `type'      Name of the plugin (must be the same as passed to
+ *              `plugin_register'
+ */
+void cf_unregister (const char *type);
+void cf_unregister_complex (const char *type);
+
+/*
+ * DESCRIPTION
+ *  `cf_register' is called by plugins that wish to receive config keys. The
+ *  plugin will then receive all keys it registered for if they're found in a
+ *  `<Plugin $type>' section.
+ *
+ * PARAMETERS
+ *  `type'      Name of the plugin (must be the same as passed to
+ *              `plugin_register'
+ *  `callback'  Pointer to the callback function. The callback must return zero
+ *              upon success, a value smaller than zero if it doesn't know how
+ *              to handle the `key' passed to it (the first argument) or a
+ *              value greater than zero if it knows how to handle the key but
+ *              failed.
+ *  `keys'      Array of key values this plugin wished to receive. The last
+ *              element must be a NULL-pointer.
+ *  `keys_num'  Number of elements in the array (not counting the last NULL-
+ *              pointer.
+ *
+ * NOTES
+ *  `cf_unregister' will be called for `type' to make sure only one record
+ *  exists for each `type' at any time. This means that `cf_register' may be
+ *  called multiple times, but only the last call will have an effect.
+ */
+void cf_register (const char *type,
+               int (*callback) (const char *, const char *),
+               const char **keys, int keys_num);
+
+int cf_register_complex (const char *type, int (*callback) (oconfig_item_t *));
+
+/*
+ * DESCRIPTION
+ *  `cf_read' reads the config file `filename' and dispatches the read
+ *  information to functions/variables. Most important: Is calls `plugin_load'
+ *  to load specific plugins, depending on the current mode of operation.
+ *
+ * PARAMETERS
+ *  `filename'  An additional filename to look for. This function calls
+ *              `lc_process' which already searches many standard locations..
+ *              If set to NULL will use the `CONFIGFILE' define.
+ *
+ * RETURN VALUE
+ *  Returns zero upon success and non-zero otherwise. A error-message will have
+ *  been printed in this case.
+ */
+int cf_read (char *filename);
+
+int global_option_set (const char *option, const char *value);
+const char *global_option_get (const char *option);
+
+/* Assures the config option is a string, duplicates it and returns the copy in
+ * "ret_string". If necessary "*ret_string" is freed first. Returns zero upon
+ * success. */
+int cf_util_get_string (const oconfig_item_t *ci, char **ret_string);
+
+/* Assures the config option is a string and copies it to the provided buffer.
+ * Assures null-termination. */
+int cf_util_get_string_buffer (const oconfig_item_t *ci, char *buffer,
+               size_t buffer_size);
+
+/* Assures the config option is a number and returns it as an int. */
+int cf_util_get_int (const oconfig_item_t *ci, int *ret_value);
+
+/* Assures the config option is a boolean and assignes it to `ret_bool'.
+ * Otherwise, `ret_bool' is not changed and non-zero is returned. */
+int cf_util_get_boolean (const oconfig_item_t *ci, _Bool *ret_bool);
+
+/* Assures the config option is a boolean and set or unset the given flag in
+ * `ret_value' as appropriate. Returns non-zero on error. */
+int cf_util_get_flag (const oconfig_item_t *ci,
+               unsigned int *ret_value, unsigned int flag);
+
+/* Assures that the config option is a string or a number if the correct range
+ * of 1-65535. The string is then converted to a port number using
+ * `service_name_to_port_number' and returned.
+ * Returns the port number in the range [1-65535] or less than zero upon
+ * failure. */
+int cf_util_get_port_number (const oconfig_item_t *ci);
+
+int cf_util_get_cdtime (const oconfig_item_t *ci, cdtime_t *ret_value);
+
+#endif /* defined(CONFIGFILE_H) */
diff --git a/src/conntrack.c b/src/conntrack.c
new file mode 100644 (file)
index 0000000..e70ff5f
--- /dev/null
@@ -0,0 +1,78 @@
+/**
+ * collectd - src/conntrack.c
+ * Copyright (C) 2009  Tomasz Pala
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Tomasz Pala <gotar at pld-linux.org>
+ * based on entropy.c by:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#if !KERNEL_LINUX
+# error "No applicable input method."
+#endif
+
+#define CONNTRACK_FILE "/proc/sys/net/netfilter/nf_conntrack_count"
+
+static void conntrack_submit (double conntrack)
+{
+       value_t values[1];
+       value_list_t vl = VALUE_LIST_INIT;
+
+       values[0].gauge = conntrack;
+
+       vl.values = values;
+       vl.values_len = 1;
+       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+       sstrncpy (vl.plugin, "conntrack", sizeof (vl.plugin));
+       sstrncpy (vl.type, "conntrack", sizeof (vl.type));
+
+       plugin_dispatch_values (&vl);
+} /* static void conntrack_submit */
+
+static int conntrack_read (void)
+{
+       double conntrack;
+       FILE *fh;
+       char buffer[64];
+
+       fh = fopen (CONNTRACK_FILE, "r");
+       if (fh == NULL)
+               return (-1);
+
+       if (fgets (buffer, sizeof (buffer), fh) == NULL)
+       {
+               fclose (fh);
+               return (-1);
+       }
+       fclose (fh);
+
+       conntrack = atof (buffer);
+
+       if (conntrack > 0.0)
+               conntrack_submit (conntrack);
+
+       return (0);
+} /* static int conntrack_read */
+
+void module_register (void)
+{
+       plugin_register_read ("conntrack", conntrack_read);
+} /* void module_register */
diff --git a/src/contextswitch.c b/src/contextswitch.c
new file mode 100644 (file)
index 0000000..c207318
--- /dev/null
@@ -0,0 +1,132 @@
+/**
+ * collectd - src/contextswitch.c
+ * Copyright (C) 2009  Patrik Weiskircher
+ * Copyright (C) 2010  Kimo Rosenbaum
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Patrik Weiskircher <weiskircher at inqnet.at>
+ *   Kimo Rosenbaum <http://github.com/kimor79>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#ifdef HAVE_SYS_SYSCTL_H
+# include <sys/sysctl.h>
+#endif
+
+#if HAVE_SYSCTLBYNAME
+/* no global variables */
+/* #endif HAVE_SYSCTLBYNAME */
+
+#elif KERNEL_LINUX
+/* no global variables */
+/* #endif KERNEL_LINUX */
+
+#else
+# error "No applicable input method."
+#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_len = 1;
+       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+       sstrncpy (vl.plugin, "contextswitch", sizeof (vl.plugin));
+       sstrncpy (vl.type, "contextswitch", sizeof (vl.type));
+
+       plugin_dispatch_values (&vl);
+}
+
+static int cs_read (void)
+{
+#if HAVE_SYSCTLBYNAME
+       int value = 0;
+       size_t value_len = sizeof (value);
+       int status;
+
+       status = sysctlbyname ("vm.stats.sys.v_swtch",
+                       &value, &value_len,
+                       /* new pointer = */ NULL, /* new length = */ 0);
+       if (status != 0)
+       {
+               ERROR("contextswitch plugin: sysctlbyname "
+                               "(vm.stats.sys.v_swtch) failed");
+               return (-1);
+       }
+
+       cs_submit (value);
+/* #endif HAVE_SYSCTLBYNAME */
+
+#elif KERNEL_LINUX
+       FILE *fh;
+       char buffer[64];
+       int numfields;
+       char *fields[3];
+       derive_t result = 0;
+       int status = -2;
+
+       fh = fopen ("/proc/stat", "r");
+       if (fh == NULL) {
+               ERROR ("contextswitch plugin: unable to open /proc/stat: %s",
+                               sstrerror (errno, buffer, sizeof (buffer)));
+               return (-1);
+       }
+
+       while (fgets(buffer, sizeof(buffer), fh) != NULL)
+       {
+               char *endptr;
+
+               numfields = strsplit(buffer, fields, STATIC_ARRAY_SIZE (fields));
+               if (numfields != 2)
+                       continue;
+
+               if (strcmp("ctxt", fields[0]) != 0)
+                       continue;
+
+               errno = 0;
+               endptr = NULL;
+               result = (derive_t) strtoll (fields[1], &endptr, /* base = */ 10);
+               if ((endptr == fields[1]) || (errno != 0)) {
+                       ERROR ("contextswitch plugin: Cannot parse ctxt value: %s",
+                                       fields[1]);
+                       status = -1;
+                       break;
+               }
+
+               cs_submit(result);
+               status = 0;
+               break;
+       }
+       fclose(fh);
+
+       if (status == -2)
+               ERROR ("contextswitch plugin: Unable to find context switch value.");
+#endif /* KERNEL_LINUX */
+
+       return status;
+}
+
+void module_register (void)
+{
+       plugin_register_read ("contextswitch", cs_read);
+} /* void module_register */
diff --git a/src/cpu.c b/src/cpu.c
new file mode 100644 (file)
index 0000000..12071a2
--- /dev/null
+++ b/src/cpu.c
@@ -0,0 +1,608 @@
+/**
+ * collectd - src/cpu.c
+ * Copyright (C) 2005-2010  Florian octo Forster
+ * Copyright (C) 2008       Oleg King
+ * Copyright (C) 2009       Simon Kuhnle
+ * Copyright (C) 2009       Manuel Sanmartin
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ *   Oleg King <king2 at kaluga.ru>
+ *   Simon Kuhnle <simon at blarzwurst.de>
+ *   Manuel Sanmartin
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#ifdef HAVE_MACH_KERN_RETURN_H
+# include <mach/kern_return.h>
+#endif
+#ifdef HAVE_MACH_MACH_INIT_H
+# include <mach/mach_init.h>
+#endif
+#ifdef HAVE_MACH_HOST_PRIV_H
+# include <mach/host_priv.h>
+#endif
+#if HAVE_MACH_MACH_ERROR_H
+#  include <mach/mach_error.h>
+#endif
+#ifdef HAVE_MACH_PROCESSOR_INFO_H
+# include <mach/processor_info.h>
+#endif
+#ifdef HAVE_MACH_PROCESSOR_H
+# include <mach/processor.h>
+#endif
+#ifdef HAVE_MACH_VM_MAP_H
+# include <mach/vm_map.h>
+#endif
+
+#ifdef HAVE_LIBKSTAT
+# include <sys/sysinfo.h>
+#endif /* HAVE_LIBKSTAT */
+
+#if (defined(HAVE_SYSCTL) && HAVE_SYSCTL) \
+       || (defined(HAVE_SYSCTLBYNAME) && HAVE_SYSCTLBYNAME)
+# ifdef HAVE_SYS_SYSCTL_H
+#  include <sys/sysctl.h>
+# endif
+
+# ifdef HAVE_SYS_DKSTAT_H
+#  include <sys/dkstat.h>
+# endif
+
+# if !defined(CP_USER) || !defined(CP_NICE) || !defined(CP_SYS) || !defined(CP_INTR) || !defined(CP_IDLE) || !defined(CPUSTATES)
+#  define CP_USER   0
+#  define CP_NICE   1
+#  define CP_SYS    2
+#  define CP_INTR   3
+#  define CP_IDLE   4
+#  define CPUSTATES 5
+# endif
+#endif /* HAVE_SYSCTL || HAVE_SYSCTLBYNAME */
+
+#if HAVE_SYSCTL
+# if defined(CTL_HW) && defined(HW_NCPU) \
+       && defined(CTL_KERN) && defined(KERN_CPTIME) && defined(CPUSTATES)
+#  define CAN_USE_SYSCTL 1
+# else
+#  define CAN_USE_SYSCTL 0
+# endif
+#else
+# define CAN_USE_SYSCTL 0
+#endif
+
+#if HAVE_STATGRAB_H
+# include <statgrab.h>
+#endif
+
+# ifdef HAVE_PERFSTAT
+#  include <sys/protosw.h>
+#  include <libperfstat.h>
+# endif /* HAVE_PERFSTAT */
+
+#if !PROCESSOR_CPU_LOAD_INFO && !KERNEL_LINUX && !HAVE_LIBKSTAT \
+       && !CAN_USE_SYSCTL && !HAVE_SYSCTLBYNAME && !HAVE_LIBSTATGRAB && !HAVE_PERFSTAT
+# error "No applicable input method."
+#endif
+
+#ifdef PROCESSOR_CPU_LOAD_INFO
+static mach_port_t port_host;
+static processor_port_array_t cpu_list;
+static mach_msg_type_number_t cpu_list_len;
+
+#if PROCESSOR_TEMPERATURE
+static int cpu_temp_retry_counter = 0;
+static int cpu_temp_retry_step    = 1;
+static int cpu_temp_retry_max     = 1;
+#endif /* PROCESSOR_TEMPERATURE */
+/* #endif PROCESSOR_CPU_LOAD_INFO */
+
+#elif defined(KERNEL_LINUX)
+/* no variables needed */
+/* #endif KERNEL_LINUX */
+
+#elif defined(HAVE_LIBKSTAT)
+/* colleague tells me that Sun doesn't sell systems with more than 100 or so CPUs.. */
+# define MAX_NUMCPU 256
+extern kstat_ctl_t *kc;
+static kstat_t *ksp[MAX_NUMCPU];
+static int numcpu;
+/* #endif HAVE_LIBKSTAT */
+
+#elif CAN_USE_SYSCTL
+static int numcpu;
+/* #endif CAN_USE_SYSCTL */
+
+#elif defined(HAVE_SYSCTLBYNAME)
+static int numcpu;
+#  ifdef HAVE_SYSCTL_KERN_CP_TIMES
+static int maxcpu;
+#  endif /* HAVE_SYSCTL_KERN_CP_TIMES */
+/* #endif HAVE_SYSCTLBYNAME */
+
+#elif defined(HAVE_LIBSTATGRAB)
+/* no variables needed */
+/* #endif  HAVE_LIBSTATGRAB */
+
+#elif defined(HAVE_PERFSTAT)
+static perfstat_cpu_t *perfcpu;
+static int numcpu;
+static int pnumcpu;
+#endif /* HAVE_PERFSTAT */
+
+static int init (void)
+{
+#if PROCESSOR_CPU_LOAD_INFO || PROCESSOR_TEMPERATURE
+       kern_return_t status;
+
+       port_host = mach_host_self ();
+
+       /* FIXME: Free `cpu_list' if it's not NULL */
+       if ((status = host_processors (port_host, &cpu_list, &cpu_list_len)) != KERN_SUCCESS)
+       {
+               ERROR ("cpu plugin: host_processors returned %i", (int) status);
+               cpu_list_len = 0;
+               return (-1);
+       }
+
+       DEBUG ("host_processors returned %i %s", (int) cpu_list_len, cpu_list_len == 1 ? "processor" : "processors");
+       INFO ("cpu plugin: Found %i processor%s.", (int) cpu_list_len, cpu_list_len == 1 ? "" : "s");
+
+       cpu_temp_retry_max = 86400 / CDTIME_T_TO_TIME_T (interval_g);
+/* #endif PROCESSOR_CPU_LOAD_INFO */
+
+#elif defined(HAVE_LIBKSTAT)
+       kstat_t *ksp_chain;
+
+       numcpu = 0;
+
+       if (kc == NULL)
+               return (-1);
+
+       /* Solaris doesn't count linear.. *sigh* */
+       for (numcpu = 0, ksp_chain = kc->kc_chain;
+                       (numcpu < MAX_NUMCPU) && (ksp_chain != NULL);
+                       ksp_chain = ksp_chain->ks_next)
+               if (strncmp (ksp_chain->ks_module, "cpu_stat", 8) == 0)
+                       ksp[numcpu++] = ksp_chain;
+/* #endif HAVE_LIBKSTAT */
+
+#elif CAN_USE_SYSCTL
+       size_t numcpu_size;
+       int mib[2] = {CTL_HW, HW_NCPU};
+       int status;
+
+       numcpu = 0;
+       numcpu_size = sizeof (numcpu);
+
+       status = sysctl (mib, STATIC_ARRAY_SIZE (mib),
+                       &numcpu, &numcpu_size, NULL, 0);
+       if (status == -1)
+       {
+               char errbuf[1024];
+               WARNING ("cpu plugin: sysctl: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+/* #endif CAN_USE_SYSCTL */
+
+#elif defined (HAVE_SYSCTLBYNAME)
+       size_t numcpu_size;
+
+       numcpu_size = sizeof (numcpu);
+
+       if (sysctlbyname ("hw.ncpu", &numcpu, &numcpu_size, NULL, 0) < 0)
+       {
+               char errbuf[1024];
+               WARNING ("cpu plugin: sysctlbyname(hw.ncpu): %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+#ifdef HAVE_SYSCTL_KERN_CP_TIMES
+       numcpu_size = sizeof (maxcpu);
+
+       if (sysctlbyname("kern.smp.maxcpus", &maxcpu, &numcpu_size, NULL, 0) < 0)
+       {
+               char errbuf[1024];
+               WARNING ("cpu plugin: sysctlbyname(kern.smp.maxcpus): %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+#else
+       if (numcpu != 1)
+               NOTICE ("cpu: Only one processor supported when using `sysctlbyname' (found %i)", numcpu);
+#endif
+/* #endif HAVE_SYSCTLBYNAME */
+
+#elif defined(HAVE_LIBSTATGRAB)
+       /* nothing to initialize */
+/* #endif HAVE_LIBSTATGRAB */
+
+#elif defined(HAVE_PERFSTAT)
+       /* nothing to initialize */
+#endif /* HAVE_PERFSTAT */
+
+       return (0);
+} /* int init */
+
+static void submit (int cpu_num, 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_len = 1;
+       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+       sstrncpy (vl.plugin, "cpu", sizeof (vl.plugin));
+       ssnprintf (vl.plugin_instance, sizeof (vl.plugin_instance),
+                       "%i", cpu_num);
+       sstrncpy (vl.type, "cpu", sizeof (vl.type));
+       sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
+
+       plugin_dispatch_values (&vl);
+}
+
+static int cpu_read (void)
+{
+#if PROCESSOR_CPU_LOAD_INFO || PROCESSOR_TEMPERATURE
+       int cpu;
+
+       kern_return_t status;
+       
+#if PROCESSOR_CPU_LOAD_INFO
+       processor_cpu_load_info_data_t cpu_info;
+       mach_msg_type_number_t         cpu_info_len;
+#endif
+#if PROCESSOR_TEMPERATURE
+       processor_info_data_t          cpu_temp;
+       mach_msg_type_number_t         cpu_temp_len;
+#endif
+
+       host_t cpu_host;
+
+       for (cpu = 0; cpu < cpu_list_len; cpu++)
+       {
+#if PROCESSOR_CPU_LOAD_INFO
+               cpu_host = 0;
+               cpu_info_len = PROCESSOR_BASIC_INFO_COUNT;
+
+               if ((status = processor_info (cpu_list[cpu],
+                                               PROCESSOR_CPU_LOAD_INFO, &cpu_host,
+                                               (processor_info_t) &cpu_info, &cpu_info_len)) != KERN_SUCCESS)
+               {
+                       ERROR ("cpu plugin: processor_info failed with status %i", (int) status);
+                       continue;
+               }
+
+               if (cpu_info_len < CPU_STATE_MAX)
+               {
+                       ERROR ("cpu plugin: processor_info returned only %i elements..", cpu_info_len);
+                       continue;
+               }
+
+               submit (cpu, "user", (derive_t) cpu_info.cpu_ticks[CPU_STATE_USER]);
+               submit (cpu, "nice", (derive_t) cpu_info.cpu_ticks[CPU_STATE_NICE]);
+               submit (cpu, "system", (derive_t) cpu_info.cpu_ticks[CPU_STATE_SYSTEM]);
+               submit (cpu, "idle", (derive_t) cpu_info.cpu_ticks[CPU_STATE_IDLE]);
+#endif /* PROCESSOR_CPU_LOAD_INFO */
+#if PROCESSOR_TEMPERATURE
+               /*
+                * Not all Apple computers do have this ability. To minimize
+                * the messages sent to the syslog we do an exponential
+                * stepback if `processor_info' fails. We still try ~once a day
+                * though..
+                */
+               if (cpu_temp_retry_counter > 0)
+               {
+                       cpu_temp_retry_counter--;
+                       continue;
+               }
+
+               cpu_temp_len = PROCESSOR_INFO_MAX;
+
+               status = processor_info (cpu_list[cpu],
+                               PROCESSOR_TEMPERATURE,
+                               &cpu_host,
+                               cpu_temp, &cpu_temp_len);
+               if (status != KERN_SUCCESS)
+               {
+                       ERROR ("cpu plugin: processor_info failed: %s",
+                                       mach_error_string (status));
+
+                       cpu_temp_retry_counter = cpu_temp_retry_step;
+                       cpu_temp_retry_step *= 2;
+                       if (cpu_temp_retry_step > cpu_temp_retry_max)
+                               cpu_temp_retry_step = cpu_temp_retry_max;
+
+                       continue;
+               }
+
+               if (cpu_temp_len != 1)
+               {
+                       DEBUG ("processor_info (PROCESSOR_TEMPERATURE) returned %i elements..?",
+                                       (int) cpu_temp_len);
+                       continue;
+               }
+
+               cpu_temp_retry_counter = 0;
+               cpu_temp_retry_step    = 1;
+
+               DEBUG ("cpu_temp = %i", (int) cpu_temp);
+#endif /* PROCESSOR_TEMPERATURE */
+       }
+/* #endif PROCESSOR_CPU_LOAD_INFO */
+
+#elif defined(KERNEL_LINUX)
+       int cpu;
+       derive_t user, nice, syst, idle;
+       derive_t wait, intr, sitr; /* sitr == soft interrupt */
+       FILE *fh;
+       char buf[1024];
+
+       char *fields[9];
+       int numfields;
+
+       if ((fh = fopen ("/proc/stat", "r")) == NULL)
+       {
+               char errbuf[1024];
+               ERROR ("cpu plugin: fopen (/proc/stat) failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       while (fgets (buf, 1024, fh) != NULL)
+       {
+               if (strncmp (buf, "cpu", 3))
+                       continue;
+               if ((buf[3] < '0') || (buf[3] > '9'))
+                       continue;
+
+               numfields = strsplit (buf, fields, 9);
+               if (numfields < 5)
+                       continue;
+
+               cpu = atoi (fields[0] + 3);
+               user = atoll (fields[1]);
+               nice = atoll (fields[2]);
+               syst = atoll (fields[3]);
+               idle = atoll (fields[4]);
+
+               submit (cpu, "user", user);
+               submit (cpu, "nice", nice);
+               submit (cpu, "system", syst);
+               submit (cpu, "idle", idle);
+
+               if (numfields >= 8)
+               {
+                       wait = atoll (fields[5]);
+                       intr = atoll (fields[6]);
+                       sitr = atoll (fields[7]);
+
+                       submit (cpu, "wait", wait);
+                       submit (cpu, "interrupt", intr);
+                       submit (cpu, "softirq", sitr);
+
+                       if (numfields >= 9)
+                               submit (cpu, "steal", atoll (fields[8]));
+               }
+       }
+
+       fclose (fh);
+/* #endif defined(KERNEL_LINUX) */
+
+#elif defined(HAVE_LIBKSTAT)
+       int cpu;
+       derive_t user, syst, idle, wait;
+       static cpu_stat_t cs;
+
+       if (kc == NULL)
+               return (-1);
+
+       for (cpu = 0; cpu < numcpu; cpu++)
+       {
+               if (kstat_read (kc, ksp[cpu], &cs) == -1)
+                       continue; /* error message? */
+
+               idle = (derive_t) cs.cpu_sysinfo.cpu[CPU_IDLE];
+               user = (derive_t) cs.cpu_sysinfo.cpu[CPU_USER];
+               syst = (derive_t) cs.cpu_sysinfo.cpu[CPU_KERNEL];
+               wait = (derive_t) cs.cpu_sysinfo.cpu[CPU_WAIT];
+
+               submit (ksp[cpu]->ks_instance, "user", user);
+               submit (ksp[cpu]->ks_instance, "system", syst);
+               submit (ksp[cpu]->ks_instance, "idle", idle);
+               submit (ksp[cpu]->ks_instance, "wait", wait);
+       }
+/* #endif defined(HAVE_LIBKSTAT) */
+
+#elif CAN_USE_SYSCTL
+       uint64_t cpuinfo[numcpu][CPUSTATES];
+       size_t cpuinfo_size;
+       int status;
+       int i;
+
+       if (numcpu < 1)
+       {
+               ERROR ("cpu plugin: Could not determine number of "
+                               "installed CPUs using sysctl(3).");
+               return (-1);
+       }
+
+       memset (cpuinfo, 0, sizeof (cpuinfo));
+
+#if defined(KERN_CPTIME2)
+       if (numcpu > 1) {
+               for (i = 0; i < numcpu; i++) {
+                       int mib[] = {CTL_KERN, KERN_CPTIME2, i};
+
+                       cpuinfo_size = sizeof (cpuinfo[0]);
+
+                       status = sysctl (mib, STATIC_ARRAY_SIZE (mib),
+                                       cpuinfo[i], &cpuinfo_size, NULL, 0);
+                       if (status == -1) {
+                               char errbuf[1024];
+                               ERROR ("cpu plugin: sysctl failed: %s.",
+                                               sstrerror (errno, errbuf, sizeof (errbuf)));
+                               return (-1);
+                       }
+               }
+       }
+       else
+#endif /* defined(KERN_CPTIME2) */
+       {
+               int mib[] = {CTL_KERN, KERN_CPTIME};
+               long cpuinfo_tmp[CPUSTATES];
+
+               cpuinfo_size = sizeof(cpuinfo_tmp);
+
+               status = sysctl (mib, STATIC_ARRAY_SIZE (mib),
+                                       &cpuinfo_tmp, &cpuinfo_size, NULL, 0);
+               if (status == -1)
+               {
+                       char errbuf[1024];
+                       ERROR ("cpu plugin: sysctl failed: %s.",
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       return (-1);
+               }
+
+               for(i = 0; i < CPUSTATES; i++) {
+                       cpuinfo[0][i] = cpuinfo_tmp[i];
+               }
+       }
+
+       for (i = 0; i < numcpu; i++) {
+               submit (i, "user",      cpuinfo[i][CP_USER]);
+               submit (i, "nice",      cpuinfo[i][CP_NICE]);
+               submit (i, "system",    cpuinfo[i][CP_SYS]);
+               submit (i, "idle",      cpuinfo[i][CP_IDLE]);
+               submit (i, "interrupt", cpuinfo[i][CP_INTR]);
+       }
+/* #endif CAN_USE_SYSCTL */
+#elif defined(HAVE_SYSCTLBYNAME) && defined(HAVE_SYSCTL_KERN_CP_TIMES)
+       long cpuinfo[maxcpu][CPUSTATES];
+       size_t cpuinfo_size;
+       int i;
+
+       memset (cpuinfo, 0, sizeof (cpuinfo));
+
+       cpuinfo_size = sizeof (cpuinfo);
+       if (sysctlbyname("kern.cp_times", &cpuinfo, &cpuinfo_size, NULL, 0) < 0)
+       {
+               char errbuf[1024];
+               ERROR ("cpu plugin: sysctlbyname failed: %s.",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       for (i = 0; i < numcpu; i++) {
+               submit (i, "user", cpuinfo[i][CP_USER]);
+               submit (i, "nice", cpuinfo[i][CP_NICE]);
+               submit (i, "system", cpuinfo[i][CP_SYS]);
+               submit (i, "idle", cpuinfo[i][CP_IDLE]);
+               submit (i, "interrupt", cpuinfo[i][CP_INTR]);
+       }
+/* #endif HAVE_SYSCTL_KERN_CP_TIMES */
+#elif defined(HAVE_SYSCTLBYNAME)
+       long cpuinfo[CPUSTATES];
+       size_t cpuinfo_size;
+
+       cpuinfo_size = sizeof (cpuinfo);
+
+       if (sysctlbyname("kern.cp_time", &cpuinfo, &cpuinfo_size, NULL, 0) < 0)
+       {
+               char errbuf[1024];
+               ERROR ("cpu plugin: sysctlbyname failed: %s.",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       submit (0, "user", cpuinfo[CP_USER]);
+       submit (0, "nice", cpuinfo[CP_NICE]);
+       submit (0, "system", cpuinfo[CP_SYS]);
+       submit (0, "idle", cpuinfo[CP_IDLE]);
+       submit (0, "interrupt", cpuinfo[CP_INTR]);
+/* #endif HAVE_SYSCTLBYNAME */
+
+#elif defined(HAVE_LIBSTATGRAB)
+       sg_cpu_stats *cs;
+       cs = sg_get_cpu_stats ();
+
+       if (cs == NULL)
+       {
+               ERROR ("cpu plugin: sg_get_cpu_stats failed.");
+               return (-1);
+       }
+
+       submit (0, "idle",   (derive_t) cs->idle);
+       submit (0, "nice",   (derive_t) cs->nice);
+       submit (0, "swap",   (derive_t) cs->swap);
+       submit (0, "system", (derive_t) cs->kernel);
+       submit (0, "user",   (derive_t) cs->user);
+       submit (0, "wait",   (derive_t) cs->iowait);
+/* #endif HAVE_LIBSTATGRAB */
+
+#elif defined(HAVE_PERFSTAT)
+       perfstat_id_t id;
+       int i, cpus;
+
+       numcpu =  perfstat_cpu(NULL, NULL, sizeof(perfstat_cpu_t), 0);
+       if(numcpu == -1)
+       {
+               char errbuf[1024];
+               WARNING ("cpu plugin: perfstat_cpu: %s",
+                       sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+       
+       if (pnumcpu != numcpu || perfcpu == NULL) 
+       {
+               if (perfcpu != NULL) 
+                       free(perfcpu);
+               perfcpu = malloc(numcpu * sizeof(perfstat_cpu_t));
+       }
+       pnumcpu = numcpu;
+
+       id.name[0] = '\0';
+       if ((cpus = perfstat_cpu(&id, perfcpu, sizeof(perfstat_cpu_t), numcpu)) < 0)
+       {
+               char errbuf[1024];
+               WARNING ("cpu plugin: perfstat_cpu: %s",
+                       sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       for (i = 0; i < cpus; i++) 
+       {
+               submit (i, "idle",   (derive_t) perfcpu[i].idle);
+               submit (i, "system", (derive_t) perfcpu[i].sys);
+               submit (i, "user",   (derive_t) perfcpu[i].user);
+               submit (i, "wait",   (derive_t) perfcpu[i].wait);
+       }
+#endif /* HAVE_PERFSTAT */
+
+       return (0);
+}
+
+void module_register (void)
+{
+       plugin_register_init ("cpu", init);
+       plugin_register_read ("cpu", cpu_read);
+} /* void module_register */
diff --git a/src/cpufreq.c b/src/cpufreq.c
new file mode 100644 (file)
index 0000000..b92b1d0
--- /dev/null
@@ -0,0 +1,137 @@
+/**
+ * collectd - src/cpufreq.c
+ * Copyright (C) 2005-2007  Peter Holik
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Peter Holik <peter at holik.at>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#define MODULE_NAME "cpufreq"
+
+static int num_cpu = 0;
+
+static int cpufreq_init (void)
+{
+        int status;
+       char filename[256];
+
+       num_cpu = 0;
+
+       while (1)
+       {
+               status = ssnprintf (filename, sizeof (filename),
+                               "/sys/devices/system/cpu/cpu%d/cpufreq/"
+                               "scaling_cur_freq", num_cpu);
+               if ((status < 1) || ((unsigned int)status >= sizeof (filename)))
+                       break;
+
+               if (access (filename, R_OK))
+                       break;
+
+               num_cpu++;
+       }
+
+       INFO ("cpufreq plugin: Found %d CPU%s", num_cpu,
+                       (num_cpu == 1) ? "" : "s");
+
+       if (num_cpu == 0)
+               plugin_unregister_read ("cpufreq");
+
+       return (0);
+} /* int cpufreq_init */
+
+static void cpufreq_submit (int cpu_num, double value)
+{
+       value_t values[1];
+       value_list_t vl = VALUE_LIST_INIT;
+
+       values[0].gauge = value;
+
+       vl.values = values;
+       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);
+
+       plugin_dispatch_values (&vl);
+}
+
+static int cpufreq_read (void)
+{
+        int status;
+       unsigned long long val;
+       int i = 0;
+       FILE *fp;
+       char filename[256];
+       char buffer[16];
+
+       for (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)));
+               }
+
+
+               /* You're seeing correctly: The file is reporting kHz values.. */
+               val = atoll (buffer) * 1000;
+
+               cpufreq_submit (i, val);
+       }
+
+       return (0);
+} /* int cpufreq_read */
+
+void module_register (void)
+{
+       plugin_register_init ("cpufreq", cpufreq_init);
+       plugin_register_read ("cpufreq", cpufreq_read);
+}
diff --git a/src/cpython.h b/src/cpython.h
new file mode 100644 (file)
index 0000000..4b8aa72
--- /dev/null
@@ -0,0 +1,209 @@
+/**
+ * collectd - src/cpython.h
+ * Copyright (C) 2009  Sven Trenkel
+ *
+ * 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:
+ *   Sven Trenkel <collectd at semidefinite.de>  
+ **/
+
+/* Some python versions don't include this by default. */
+
+#include <longintrepr.h>
+
+/* These two macros are basicly Py_BEGIN_ALLOW_THREADS and Py_BEGIN_ALLOW_THREADS
+ * from the other direction. If a Python thread calls a C function
+ * Py_BEGIN_ALLOW_THREADS is used to allow other python threads to run because
+ * we don't intend to call any Python functions.
+ *
+ * These two macros are used whenever a C thread intends to call some Python
+ * function, usually because some registered callback was triggered.
+ * Just like Py_BEGIN_ALLOW_THREADS it opens a block so these macros have to be
+ * used in pairs. They aquire the GIL, create a new Python thread state and swap
+ * the current thread state with the new one. This means this thread is now allowed
+ * to execute Python code. */
+
+#define CPY_LOCK_THREADS {\
+       PyGILState_STATE gil_state;\
+       gil_state = PyGILState_Ensure();
+
+#define CPY_RETURN_FROM_THREADS \
+       PyGILState_Release(gil_state);\
+       return
+
+#define CPY_RELEASE_THREADS \
+       PyGILState_Release(gil_state);\
+}
+
+/* Python 2.4 has this macro, older versions do not. */
+#ifndef Py_VISIT
+#define Py_VISIT(o) do {\
+       int _vret;\
+       if ((o) != NULL) {\
+               _vret = visit((o), arg);\
+               if (_vret != 0)\
+               return _vret;\
+       }\
+} while (0)
+#endif
+
+/* Python 2.4 has this macro, older versions do not. */
+#ifndef Py_CLEAR
+#define Py_CLEAR(o) do {\
+       PyObject *tmp = o;\
+       (o) = NULL;\
+       Py_XDECREF(tmp);\
+} while (0)
+#endif
+
+/* Python 2.4 has this macro, older versions do not. */
+#ifndef Py_RETURN_NONE
+# define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None
+#endif
+
+/* This macro is a shortcut for calls like
+ * x = PyObject_Repr(x);
+ * This can't be done like this example because this would leak
+ * a reference the the original x and crash in case of x == NULL.
+ * This calling syntax is less than elegant but it works, saves
+ * a lot of lines and avoids potential refcount errors. */
+
+#define CPY_SUBSTITUTE(func, a, ...) do {\
+       if ((a) != NULL) {\
+               PyObject *__tmp = (a);\
+               (a) = func(__VA_ARGS__);\
+               Py_DECREF(__tmp);\
+       }\
+} while(0)
+
+/* Python3 compatibility layer. To keep the actual code as clean as possible
+ * do a lot of defines here. */
+
+#if PY_MAJOR_VERSION >= 3
+#define IS_PY3K
+#endif
+
+#ifdef IS_PY3K
+
+#define PyInt_FromLong PyLong_FromLong
+#define CPY_INIT_TYPE         PyVarObject_HEAD_INIT(NULL, 0)
+#define IS_BYTES_OR_UNICODE(o) (PyUnicode_Check(o) || PyBytes_Check(o))
+#define CPY_STRCAT_AND_DEL(a, b) do {\
+       CPY_STRCAT((a), (b));\
+       Py_XDECREF((b));\
+} while (0)
+static inline void CPY_STRCAT(PyObject **a, PyObject *b) {
+       PyObject *ret;
+       
+       if (!a || !*a)
+               return;
+       
+       ret = PyUnicode_Concat(*a, b);
+       Py_DECREF(*a);
+       *a = ret;
+}
+
+#else
+
+#define CPY_INIT_TYPE         PyObject_HEAD_INIT(NULL) 0,
+#define IS_BYTES_OR_UNICODE(o) (PyUnicode_Check(o) || PyString_Check(o))
+#define CPY_STRCAT_AND_DEL PyString_ConcatAndDel
+#define CPY_STRCAT PyString_Concat
+
+#endif
+
+static inline const char *cpy_unicode_or_bytes_to_string(PyObject **o) {
+       if (PyUnicode_Check(*o)) {
+               PyObject *tmp;
+               tmp = PyUnicode_AsEncodedString(*o, NULL, NULL); /* New reference. */
+               if (tmp == NULL)
+                       return NULL;
+               Py_DECREF(*o);
+               *o = tmp;
+       }
+#ifdef IS_PY3K
+       return PyBytes_AsString(*o);
+#else
+       return PyString_AsString(*o);
+#endif
+}
+
+static inline PyObject *cpy_string_to_unicode_or_bytes(const char *buf) {
+#ifdef IS_PY3K
+/* Python3 preferrs unicode */
+       PyObject *ret;
+       ret = PyUnicode_Decode(buf, strlen(buf), NULL, NULL);
+       if (ret != NULL)
+               return ret;
+       PyErr_Clear();
+       return PyBytes_FromString(buf);
+#else
+       return PyString_FromString(buf);
+#endif 
+}
+
+void cpy_log_exception(const char *context);
+
+/* Python object declarations. */
+
+typedef struct {
+       PyObject_HEAD        /* No semicolon! */
+       PyObject *parent;    /* Config */
+       PyObject *key;       /* String */
+       PyObject *values;    /* Sequence */
+       PyObject *children;  /* Sequence */
+} Config;
+PyTypeObject ConfigType;
+
+typedef struct {
+       PyObject_HEAD        /* No semicolon! */
+       double time;
+       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];
+} PluginData;
+PyTypeObject PluginDataType;
+#define PluginData_New() PyObject_CallFunctionObjArgs((PyObject *) &PluginDataType, (void *) 0)
+
+typedef struct {
+       PluginData data;
+       PyObject *values;    /* Sequence */
+       PyObject *meta;      /* dict */
+       double interval;
+} Values;
+PyTypeObject ValuesType;
+#define Values_New() PyObject_CallFunctionObjArgs((PyObject *) &ValuesType, (void *) 0)
+
+typedef struct {
+       PluginData data;
+       int severity;
+       char message[NOTIF_MAX_MSG_LEN];
+} Notification;
+PyTypeObject NotificationType;
+#define Notification_New() PyObject_CallFunctionObjArgs((PyObject *) &NotificationType, (void *) 0)
+
+typedef PyLongObject Signed;
+PyTypeObject SignedType;
+
+typedef PyLongObject Unsigned;
+PyTypeObject UnsignedType;
+
diff --git a/src/csv.c b/src/csv.c
new file mode 100644 (file)
index 0000000..02d62c1
--- /dev/null
+++ b/src/csv.c
@@ -0,0 +1,373 @@
+/**
+ * collectd - src/csv.c
+ * Copyright (C) 2007-2009  Florian octo Forster
+ * Copyright (C) 2009       Doug MacEachern
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ *   Doug MacEachern <dougm@hyperic.com>
+ **/
+
+#include "collectd.h"
+#include "plugin.h"
+#include "common.h"
+#include "utils_cache.h"
+#include "utils_parse_option.h"
+
+/*
+ * Private variables
+ */
+static const char *config_keys[] =
+{
+       "DataDir",
+       "StoreRates"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static char *datadir   = NULL;
+static int store_rates = 0;
+static int use_stdio   = 0;
+
+static int value_list_to_string (char *buffer, int buffer_len,
+               const data_set_t *ds, const value_list_t *vl)
+{
+       int offset;
+       int status;
+       int i;
+       gauge_t *rates = NULL;
+
+       assert (0 == strcmp (ds->type, vl->type));
+
+       memset (buffer, '\0', buffer_len);
+
+       status = ssnprintf (buffer, buffer_len, "%.3f",
+                       CDTIME_T_TO_DOUBLE (vl->time));
+       if ((status < 1) || (status >= buffer_len))
+               return (-1);
+       offset = status;
+
+       for (i = 0; i < ds->ds_num; i++)
+       {
+               if ((ds->ds[i].type != DS_TYPE_COUNTER)
+                               && (ds->ds[i].type != DS_TYPE_GAUGE)
+                               && (ds->ds[i].type != DS_TYPE_DERIVE)
+                               && (ds->ds[i].type != DS_TYPE_ABSOLUTE))
+                       return (-1);
+
+               if (ds->ds[i].type == DS_TYPE_GAUGE) 
+               {
+                       status = ssnprintf (buffer + offset, buffer_len - offset,
+                                       ",%lf", vl->values[i].gauge);
+               } 
+               else if (store_rates != 0)
+               {
+                       if (rates == NULL)
+                               rates = uc_get_rate (ds, vl);
+                       if (rates == NULL)
+                       {
+                               WARNING ("csv plugin: "
+                                               "uc_get_rate failed.");
+                               return (-1);
+                       }
+                       status = ssnprintf (buffer + offset,
+                                       buffer_len - offset,
+                                       ",%lf", rates[i]);
+               }
+               else if (ds->ds[i].type == DS_TYPE_COUNTER)
+               {
+                       status = ssnprintf (buffer + offset,
+                                       buffer_len - offset,
+                                       ",%llu",
+                                       vl->values[i].counter);
+               }
+               else if (ds->ds[i].type == DS_TYPE_DERIVE)
+               {
+                       status = ssnprintf (buffer + offset,
+                                       buffer_len - offset,
+                                       ",%"PRIi64,
+                                       vl->values[i].derive);
+               }
+               else if (ds->ds[i].type == DS_TYPE_ABSOLUTE)
+               {
+                       status = ssnprintf (buffer + offset,
+                                       buffer_len - offset,
+                                       ",%"PRIu64,
+                                       vl->values[i].absolute);
+               }
+
+               if ((status < 1) || (status >= (buffer_len - offset)))
+               {
+                       sfree (rates);
+                       return (-1);
+               }
+
+               offset += status;
+       } /* for ds->ds_num */
+
+       sfree (rates);
+       return (0);
+} /* int value_list_to_string */
+
+static int value_list_to_filename (char *buffer, int buffer_len,
+               const data_set_t *ds, const value_list_t *vl)
+{
+       int offset = 0;
+       int status;
+
+       assert (0 == strcmp (ds->type, vl->type));
+
+       if (datadir != NULL)
+       {
+               status = ssnprintf (buffer + offset, buffer_len - offset,
+                               "%s/", datadir);
+               if ((status < 1) || (status >= buffer_len - offset))
+                       return (-1);
+               offset += status;
+       }
+
+       status = ssnprintf (buffer + offset, buffer_len - offset,
+                       "%s/", vl->host);
+       if ((status < 1) || (status >= buffer_len - offset))
+               return (-1);
+       offset += status;
+
+       if (strlen (vl->plugin_instance) > 0)
+               status = ssnprintf (buffer + offset, buffer_len - offset,
+                               "%s-%s/", vl->plugin, vl->plugin_instance);
+       else
+               status = ssnprintf (buffer + offset, buffer_len - offset,
+                               "%s/", vl->plugin);
+       if ((status < 1) || (status >= buffer_len - offset))
+               return (-1);
+       offset += status;
+
+       if (strlen (vl->type_instance) > 0)
+               status = ssnprintf (buffer + offset, buffer_len - offset,
+                               "%s-%s", vl->type, vl->type_instance);
+       else
+               status = ssnprintf (buffer + offset, buffer_len - offset,
+                               "%s", vl->type);
+       if ((status < 1) || (status >= buffer_len - offset))
+               return (-1);
+       offset += status;
+
+       if (!use_stdio)
+       {
+               time_t now;
+               struct tm stm;
+
+               /* TODO: Find a way to minimize the calls to `localtime_r',
+                * since they are pretty expensive.. */
+               now = time (NULL);
+               if (localtime_r (&now, &stm) == NULL)
+               {
+                       ERROR ("csv plugin: localtime_r failed");
+                       return (1);
+               }
+
+               strftime (buffer + offset, buffer_len - offset,
+                               "-%Y-%m-%d", &stm);
+       }
+
+       return (0);
+} /* int value_list_to_filename */
+
+static int csv_create_file (const char *filename, const data_set_t *ds)
+{
+       FILE *csv;
+       int i;
+
+       if (check_create_dir (filename))
+               return (-1);
+
+       csv = fopen (filename, "w");
+       if (csv == NULL)
+       {
+               char errbuf[1024];
+               ERROR ("csv plugin: fopen (%s) failed: %s",
+                               filename,
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       fprintf (csv, "epoch");
+       for (i = 0; i < ds->ds_num; i++)
+               fprintf (csv, ",%s", ds->ds[i].name);
+
+       fprintf (csv, "\n");
+       fclose (csv);
+
+       return 0;
+} /* int csv_create_file */
+
+static int csv_config (const char *key, const char *value)
+{
+       if (strcasecmp ("DataDir", key) == 0)
+       {
+               if (datadir != NULL)
+                       free (datadir);
+               if (strcasecmp ("stdout", value) == 0)
+               {
+                       use_stdio = 1;
+                       return (0);
+               }
+               else if (strcasecmp ("stderr", value) == 0)
+               {
+                       use_stdio = 2;
+                       return (0);
+               }
+               datadir = strdup (value);
+               if (datadir != NULL)
+               {
+                       int len = strlen (datadir);
+                       while ((len > 0) && (datadir[len - 1] == '/'))
+                       {
+                               len--;
+                               datadir[len] = '\0';
+                       }
+                       if (len <= 0)
+                       {
+                               free (datadir);
+                               datadir = NULL;
+                       }
+               }
+       }
+       else if (strcasecmp ("StoreRates", key) == 0)
+       {
+               if (IS_TRUE (value))
+                       store_rates = 1;
+               else
+                       store_rates = 0;
+       }
+       else
+       {
+               return (-1);
+       }
+       return (0);
+} /* int csv_config */
+
+static int csv_write (const data_set_t *ds, const value_list_t *vl,
+               user_data_t __attribute__((unused)) *user_data)
+{
+       struct stat  statbuf;
+       char         filename[512];
+       char         values[4096];
+       FILE        *csv;
+       int          csv_fd;
+       struct flock fl;
+       int          status;
+
+       if (0 != strcmp (ds->type, vl->type)) {
+               ERROR ("csv plugin: DS type does not match value list type");
+               return -1;
+       }
+
+       if (value_list_to_filename (filename, sizeof (filename), ds, vl) != 0)
+               return (-1);
+
+       DEBUG ("csv plugin: csv_write: filename = %s;", filename);
+
+       if (value_list_to_string (values, sizeof (values), ds, vl) != 0)
+               return (-1);
+
+       if (use_stdio)
+       {
+               size_t i;
+
+               escape_string (filename, sizeof (filename));
+
+               /* Replace commas by colons for PUTVAL compatible output. */
+               for (i = 0; i < sizeof (values); i++)
+               {
+                       if (values[i] == 0)
+                               break;
+                       else if (values[i] == ',')
+                               values[i] = ':';
+               }
+
+               fprintf (use_stdio == 1 ? stdout : stderr,
+                        "PUTVAL %s interval=%.3f %s\n",
+                        filename,
+                        CDTIME_T_TO_DOUBLE (vl->interval),
+                        values);
+               return (0);
+       }
+
+       if (stat (filename, &statbuf) == -1)
+       {
+               if (errno == ENOENT)
+               {
+                       if (csv_create_file (filename, ds))
+                               return (-1);
+               }
+               else
+               {
+                       char errbuf[1024];
+                       ERROR ("stat(%s) failed: %s", filename,
+                                       sstrerror (errno, errbuf,
+                                               sizeof (errbuf)));
+                       return (-1);
+               }
+       }
+       else if (!S_ISREG (statbuf.st_mode))
+       {
+               ERROR ("stat(%s): Not a regular file!",
+                               filename);
+               return (-1);
+       }
+
+       csv = fopen (filename, "a");
+       if (csv == NULL)
+       {
+               char errbuf[1024];
+               ERROR ("csv plugin: fopen (%s) failed: %s", filename,
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+       csv_fd = fileno (csv);
+
+       memset (&fl, '\0', sizeof (fl));
+       fl.l_start  = 0;
+       fl.l_len    = 0; /* till end of file */
+       fl.l_pid    = getpid ();
+       fl.l_type   = F_WRLCK;
+       fl.l_whence = SEEK_SET;
+
+       status = fcntl (csv_fd, F_SETLK, &fl);
+       if (status != 0)
+       {
+               char errbuf[1024];
+               ERROR ("csv plugin: flock (%s) failed: %s", filename,
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               fclose (csv);
+               return (-1);
+       }
+
+       fprintf (csv, "%s\n", values);
+
+       /* The lock is implicitely released. I we don't release it explicitely
+        * because the `FILE *' may need to flush a cache first */
+       fclose (csv);
+
+       return (0);
+} /* int csv_write */
+
+void module_register (void)
+{
+       plugin_register_config ("csv", csv_config,
+                       config_keys, config_keys_num);
+       plugin_register_write ("csv", csv_write, /* user_data = */ NULL);
+} /* void module_register */
diff --git a/src/curl.c b/src/curl.c
new file mode 100644 (file)
index 0000000..2160b98
--- /dev/null
@@ -0,0 +1,684 @@
+/**
+ * collectd - src/curl.c
+ * Copyright (C) 2006-2009  Florian octo Forster
+ * Copyright (C) 2009       Aman Gupta
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ *   Aman Gupta <aman at tmm1.net>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+#include "utils_match.h"
+
+#include <curl/curl.h>
+
+/*
+ * Data types
+ */
+struct web_match_s;
+typedef struct web_match_s web_match_t;
+struct web_match_s /* {{{ */
+{
+  char *regex;
+  char *exclude_regex;
+  int dstype;
+  char *type;
+  char *instance;
+
+  cu_match_t *match;
+
+  web_match_t *next;
+}; /* }}} */
+
+struct web_page_s;
+typedef struct web_page_s web_page_t;
+struct web_page_s /* {{{ */
+{
+  char *instance;
+
+  char *url;
+  char *user;
+  char *pass;
+  char *credentials;
+  int   verify_peer;
+  int   verify_host;
+  char *cacert;
+  int   response_time;
+
+  CURL *curl;
+  char curl_errbuf[CURL_ERROR_SIZE];
+  char *buffer;
+  size_t buffer_size;
+  size_t buffer_fill;
+
+  web_match_t *matches;
+
+  web_page_t *next;
+}; /* }}} */
+
+/*
+ * Global variables;
+ */
+/* static CURLM *curl = NULL; */
+static web_page_t *pages_g = NULL;
+
+/*
+ * Private functions
+ */
+static size_t cc_curl_callback (void *buf, /* {{{ */
+    size_t size, size_t nmemb, void *user_data)
+{
+  web_page_t *wp;
+  size_t len;
+  
+  len = size * nmemb;
+  if (len <= 0)
+    return (len);
+
+  wp = user_data;
+  if (wp == NULL)
+    return (0);
+
+  if ((wp->buffer_fill + len) >= wp->buffer_size)
+  {
+    char *temp;
+    size_t temp_size;
+
+    temp_size = wp->buffer_fill + len + 1;
+    temp = (char *) realloc (wp->buffer, temp_size);
+    if (temp == NULL)
+    {
+      ERROR ("curl plugin: realloc failed.");
+      return (0);
+    }
+    wp->buffer = temp;
+    wp->buffer_size = temp_size;
+  }
+
+  memcpy (wp->buffer + wp->buffer_fill, (char *) buf, len);
+  wp->buffer_fill += len;
+  wp->buffer[wp->buffer_fill] = 0;
+
+  return (len);
+} /* }}} size_t cc_curl_callback */
+
+static void cc_web_match_free (web_match_t *wm) /* {{{ */
+{
+  if (wm == NULL)
+    return;
+
+  sfree (wm->regex);
+  sfree (wm->type);
+  sfree (wm->instance);
+  match_destroy (wm->match);
+  cc_web_match_free (wm->next);
+  sfree (wm);
+} /* }}} void cc_web_match_free */
+
+static void cc_web_page_free (web_page_t *wp) /* {{{ */
+{
+  if (wp == NULL)
+    return;
+
+  if (wp->curl != NULL)
+    curl_easy_cleanup (wp->curl);
+  wp->curl = NULL;
+
+  sfree (wp->instance);
+
+  sfree (wp->url);
+  sfree (wp->user);
+  sfree (wp->pass);
+  sfree (wp->credentials);
+  sfree (wp->cacert);
+
+  sfree (wp->buffer);
+
+  cc_web_match_free (wp->matches);
+  cc_web_page_free (wp->next);
+  sfree (wp);
+} /* }}} void cc_web_page_free */
+
+static int cc_config_add_string (const char *name, char **dest, /* {{{ */
+    oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("curl plugin: `%s' needs exactly one string argument.", name);
+    return (-1);
+  }
+
+  sfree (*dest);
+  *dest = strdup (ci->values[0].value.string);
+  if (*dest == NULL)
+    return (-1);
+
+  return (0);
+} /* }}} int cc_config_add_string */
+
+static int cc_config_set_boolean (const char *name, int *dest, /* {{{ */
+    oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN))
+  {
+    WARNING ("curl plugin: `%s' needs exactly one boolean argument.", name);
+    return (-1);
+  }
+
+  *dest = ci->values[0].value.boolean ? 1 : 0;
+
+  return (0);
+} /* }}} int cc_config_set_boolean */
+
+static int cc_config_add_match_dstype (int *dstype_ret, /* {{{ */
+    oconfig_item_t *ci)
+{
+  int dstype;
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("curl plugin: `DSType' needs exactly one string argument.");
+    return (-1);
+  }
+
+  if (strncasecmp ("Gauge", ci->values[0].value.string,
+        strlen ("Gauge")) == 0)
+  {
+    dstype = UTILS_MATCH_DS_TYPE_GAUGE;
+    if (strcasecmp ("GaugeAverage", ci->values[0].value.string) == 0)
+      dstype |= UTILS_MATCH_CF_GAUGE_AVERAGE;
+    else if (strcasecmp ("GaugeMin", ci->values[0].value.string) == 0)
+      dstype |= UTILS_MATCH_CF_GAUGE_MIN;
+    else if (strcasecmp ("GaugeMax", ci->values[0].value.string) == 0)
+      dstype |= UTILS_MATCH_CF_GAUGE_MAX;
+    else if (strcasecmp ("GaugeLast", ci->values[0].value.string) == 0)
+      dstype |= UTILS_MATCH_CF_GAUGE_LAST;
+    else
+      dstype = 0;
+  }
+  else if (strncasecmp ("Counter", ci->values[0].value.string,
+        strlen ("Counter")) == 0)
+  {
+    dstype = UTILS_MATCH_DS_TYPE_COUNTER;
+    if (strcasecmp ("CounterSet", ci->values[0].value.string) == 0)
+      dstype |= UTILS_MATCH_CF_COUNTER_SET;
+    else if (strcasecmp ("CounterAdd", ci->values[0].value.string) == 0)
+      dstype |= UTILS_MATCH_CF_COUNTER_ADD;
+    else if (strcasecmp ("CounterInc", ci->values[0].value.string) == 0)
+      dstype |= UTILS_MATCH_CF_COUNTER_INC;
+    else
+      dstype = 0;
+  }
+else if (strncasecmp ("Derive", ci->values[0].value.string,
+        strlen ("Derive")) == 0)
+  {
+    dstype = UTILS_MATCH_DS_TYPE_DERIVE;
+    if (strcasecmp ("DeriveSet", ci->values[0].value.string) == 0)
+      dstype |= UTILS_MATCH_CF_DERIVE_SET;
+    else if (strcasecmp ("DeriveAdd", ci->values[0].value.string) == 0)
+      dstype |= UTILS_MATCH_CF_DERIVE_ADD;
+    else if (strcasecmp ("DeriveInc", ci->values[0].value.string) == 0)
+      dstype |= UTILS_MATCH_CF_DERIVE_INC;
+    else
+      dstype = 0;
+  }
+else if (strncasecmp ("Absolute", ci->values[0].value.string,
+        strlen ("Absolute")) == 0)
+  {
+    dstype = UTILS_MATCH_DS_TYPE_ABSOLUTE;
+    if (strcasecmp ("AbsoluteSet", ci->values[0].value.string) == 0) /* Absolute DS is reset-on-read so no sense doin anything else but set */
+      dstype |= UTILS_MATCH_CF_ABSOLUTE_SET;
+    else
+      dstype = 0;
+  }
+
+  else
+  {
+    dstype = 0;
+  }
+
+  if (dstype == 0)
+  {
+    WARNING ("curl plugin: `%s' is not a valid argument to `DSType'.",
+       ci->values[0].value.string);
+    return (-1);
+  }
+
+  *dstype_ret = dstype;
+  return (0);
+} /* }}} int cc_config_add_match_dstype */
+
+static int cc_config_add_match (web_page_t *page, /* {{{ */
+    oconfig_item_t *ci)
+{
+  web_match_t *match;
+  int status;
+  int i;
+
+  if (ci->values_num != 0)
+  {
+    WARNING ("curl plugin: Ignoring arguments for the `Match' block.");
+  }
+
+  match = (web_match_t *) malloc (sizeof (*match));
+  if (match == NULL)
+  {
+    ERROR ("curl plugin: malloc failed.");
+    return (-1);
+  }
+  memset (match, 0, sizeof (*match));
+
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Regex", child->key) == 0)
+      status = cc_config_add_string ("Regex", &match->regex, child);
+    else if (strcasecmp ("ExcludeRegex", child->key) == 0)
+      status = cc_config_add_string ("ExcludeRegex", &match->exclude_regex, child);
+    else if (strcasecmp ("DSType", child->key) == 0)
+      status = cc_config_add_match_dstype (&match->dstype, child);
+    else if (strcasecmp ("Type", child->key) == 0)
+      status = cc_config_add_string ("Type", &match->type, child);
+    else if (strcasecmp ("Instance", child->key) == 0)
+      status = cc_config_add_string ("Instance", &match->instance, child);
+    else
+    {
+      WARNING ("curl plugin: Option `%s' not allowed here.", child->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  while (status == 0)
+  {
+    if (match->regex == NULL)
+    {
+      WARNING ("curl plugin: `Regex' missing in `Match' block.");
+      status = -1;
+    }
+
+    if (match->type == NULL)
+    {
+      WARNING ("curl plugin: `Type' missing in `Match' block.");
+      status = -1;
+    }
+
+    if (match->dstype == 0)
+    {
+      WARNING ("curl plugin: `DSType' missing in `Match' block.");
+      status = -1;
+    }
+
+    break;
+  } /* while (status == 0) */
+
+  if (status != 0)
+    return (status);
+
+  match->match = match_create_simple (match->regex, match->exclude_regex,
+      match->dstype);
+  if (match->match == NULL)
+  {
+    ERROR ("curl plugin: tail_match_add_match_simple failed.");
+    cc_web_match_free (match);
+    return (-1);
+  }
+  else
+  {
+    web_match_t *prev;
+
+    prev = page->matches;
+    while ((prev != NULL) && (prev->next != NULL))
+      prev = prev->next;
+
+    if (prev == NULL)
+      page->matches = match;
+    else
+      prev->next = match;
+  }
+
+  return (0);
+} /* }}} int cc_config_add_match */
+
+static int cc_page_init_curl (web_page_t *wp) /* {{{ */
+{
+  wp->curl = curl_easy_init ();
+  if (wp->curl == NULL)
+  {
+    ERROR ("curl plugin: curl_easy_init failed.");
+    return (-1);
+  }
+
+  curl_easy_setopt (wp->curl, CURLOPT_NOSIGNAL, 1);
+  curl_easy_setopt (wp->curl, CURLOPT_WRITEFUNCTION, cc_curl_callback);
+  curl_easy_setopt (wp->curl, CURLOPT_WRITEDATA, wp);
+  curl_easy_setopt (wp->curl, CURLOPT_USERAGENT,
+      PACKAGE_NAME"/"PACKAGE_VERSION);
+  curl_easy_setopt (wp->curl, CURLOPT_ERRORBUFFER, wp->curl_errbuf);
+  curl_easy_setopt (wp->curl, CURLOPT_URL, wp->url);
+  curl_easy_setopt (wp->curl, CURLOPT_FOLLOWLOCATION, 1);
+
+  if (wp->user != NULL)
+  {
+    size_t credentials_size;
+
+    credentials_size = strlen (wp->user) + 2;
+    if (wp->pass != NULL)
+      credentials_size += strlen (wp->pass);
+
+    wp->credentials = (char *) malloc (credentials_size);
+    if (wp->credentials == NULL)
+    {
+      ERROR ("curl plugin: malloc failed.");
+      return (-1);
+    }
+
+    ssnprintf (wp->credentials, credentials_size, "%s:%s",
+        wp->user, (wp->pass == NULL) ? "" : wp->pass);
+    curl_easy_setopt (wp->curl, CURLOPT_USERPWD, wp->credentials);
+  }
+
+  curl_easy_setopt (wp->curl, CURLOPT_SSL_VERIFYPEER, wp->verify_peer);
+  curl_easy_setopt (wp->curl, CURLOPT_SSL_VERIFYHOST,
+      wp->verify_host ? 2 : 0);
+  if (wp->cacert != NULL)
+    curl_easy_setopt (wp->curl, CURLOPT_CAINFO, wp->cacert);
+
+  return (0);
+} /* }}} int cc_page_init_curl */
+
+static int cc_config_add_page (oconfig_item_t *ci) /* {{{ */
+{
+  web_page_t *page;
+  int status;
+  int i;
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("curl plugin: `Page' blocks need exactly one string argument.");
+    return (-1);
+  }
+
+  page = (web_page_t *) malloc (sizeof (*page));
+  if (page == NULL)
+  {
+    ERROR ("curl plugin: malloc failed.");
+    return (-1);
+  }
+  memset (page, 0, sizeof (*page));
+  page->url = NULL;
+  page->user = NULL;
+  page->pass = NULL;
+  page->verify_peer = 1;
+  page->verify_host = 1;
+  page->response_time = 0;
+
+  page->instance = strdup (ci->values[0].value.string);
+  if (page->instance == NULL)
+  {
+    ERROR ("curl plugin: strdup failed.");
+    sfree (page);
+    return (-1);
+  }
+
+  /* Process all children */
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("URL", child->key) == 0)
+      status = cc_config_add_string ("URL", &page->url, child);
+    else if (strcasecmp ("User", child->key) == 0)
+      status = cc_config_add_string ("User", &page->user, child);
+    else if (strcasecmp ("Password", child->key) == 0)
+      status = cc_config_add_string ("Password", &page->pass, child);
+    else if (strcasecmp ("VerifyPeer", child->key) == 0)
+      status = cc_config_set_boolean ("VerifyPeer", &page->verify_peer, child);
+    else if (strcasecmp ("VerifyHost", child->key) == 0)
+      status = cc_config_set_boolean ("VerifyHost", &page->verify_host, child);
+    else if (strcasecmp ("MeasureResponseTime", child->key) == 0)
+      status = cc_config_set_boolean (child->key, &page->response_time, child);
+    else if (strcasecmp ("CACert", child->key) == 0)
+      status = cc_config_add_string ("CACert", &page->cacert, child);
+    else if (strcasecmp ("Match", child->key) == 0)
+      /* Be liberal with failing matches => don't set `status'. */
+      cc_config_add_match (page, child);
+    else
+    {
+      WARNING ("curl plugin: Option `%s' not allowed here.", child->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  /* Additionial sanity checks and libCURL initialization. */
+  while (status == 0)
+  {
+    if (page->url == NULL)
+    {
+      WARNING ("curl plugin: `URL' missing in `Page' block.");
+      status = -1;
+    }
+
+    if (page->matches == NULL && !page->response_time)
+    {
+      assert (page->instance != NULL);
+      WARNING ("curl plugin: No (valid) `Match' block "
+          "or MeasureResponseTime within `Page' block `%s'.", page->instance);
+      status = -1;
+    }
+
+    if (status == 0)
+      status = cc_page_init_curl (page);
+
+    break;
+  } /* while (status == 0) */
+
+  if (status != 0)
+  {
+    cc_web_page_free (page);
+    return (status);
+  }
+
+  /* Add the new page to the linked list */
+  if (pages_g == NULL)
+    pages_g = page;
+  else
+  {
+    web_page_t *prev;
+
+    prev = pages_g;
+    while ((prev != NULL) && (prev->next != NULL))
+      prev = prev->next;
+    prev->next = page;
+  }
+
+  return (0);
+} /* }}} int cc_config_add_page */
+
+static int cc_config (oconfig_item_t *ci) /* {{{ */
+{
+  int success;
+  int errors;
+  int status;
+  int i;
+
+  success = 0;
+  errors = 0;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Page", child->key) == 0)
+    {
+      status = cc_config_add_page (child);
+      if (status == 0)
+        success++;
+      else
+        errors++;
+    }
+    else
+    {
+      WARNING ("curl plugin: Option `%s' not allowed here.", child->key);
+      errors++;
+    }
+  }
+
+  if ((success == 0) && (errors > 0))
+  {
+    ERROR ("curl plugin: All statements failed.");
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int cc_config */
+
+static int cc_init (void) /* {{{ */
+{
+  if (pages_g == NULL)
+  {
+    INFO ("curl plugin: No pages have been defined.");
+    return (-1);
+  }
+  return (0);
+} /* }}} 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_list_t vl = VALUE_LIST_INIT;
+
+  values[0] = mv->value;
+
+  vl.values = values;
+  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));
+  sstrncpy (vl.type_instance, wm->instance, sizeof (vl.type_instance));
+
+  plugin_dispatch_values (&vl);
+} /* }}} void cc_submit */
+
+static void cc_submit_response_time (const web_page_t *wp, double seconds) /* {{{ */
+{
+  value_t values[1];
+  value_list_t vl = VALUE_LIST_INIT;
+
+  values[0].gauge = seconds;
+
+  vl.values = values;
+  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));
+
+  plugin_dispatch_values (&vl);
+} /* }}} void cc_submit_response_time */
+
+static int cc_read_page (web_page_t *wp) /* {{{ */
+{
+  web_match_t *wm;
+  int status;
+  struct timeval start, end;
+
+  if (wp->response_time)
+    gettimeofday (&start, NULL);
+
+  wp->buffer_fill = 0;
+  status = curl_easy_perform (wp->curl);
+  if (status != 0)
+  {
+    ERROR ("curl plugin: curl_easy_perform failed with staus %i: %s",
+        status, wp->curl_errbuf);
+    return (-1);
+  }
+
+  if (wp->response_time)
+  {
+    double secs = 0;
+    gettimeofday (&end, NULL);
+    secs += end.tv_sec - start.tv_sec;
+    secs += (end.tv_usec - start.tv_usec) / 1000000.0;
+    cc_submit_response_time (wp, secs);
+  }
+
+  for (wm = wp->matches; wm != NULL; wm = wm->next)
+  {
+    cu_match_value_t *mv;
+
+    status = match_apply (wm->match, wp->buffer);
+    if (status != 0)
+    {
+      WARNING ("curl plugin: match_apply failed.");
+      continue;
+    }
+
+    mv = match_get_user_data (wm->match);
+    if (mv == NULL)
+    {
+      WARNING ("curl plugin: match_get_user_data returned NULL.");
+      continue;
+    }
+
+    cc_submit (wp, wm, mv);
+  } /* for (wm = wp->matches; wm != NULL; wm = wm->next) */
+
+  return (0);
+} /* }}} int cc_read_page */
+
+static int cc_read (void) /* {{{ */
+{
+  web_page_t *wp;
+
+  for (wp = pages_g; wp != NULL; wp = wp->next)
+    cc_read_page (wp);
+
+  return (0);
+} /* }}} int cc_read */
+
+static int cc_shutdown (void) /* {{{ */
+{
+  cc_web_page_free (pages_g);
+  pages_g = NULL;
+
+  return (0);
+} /* }}} int cc_shutdown */
+
+void module_register (void)
+{
+  plugin_register_complex_config ("curl", cc_config);
+  plugin_register_init ("curl", cc_init);
+  plugin_register_read ("curl", cc_read);
+  plugin_register_shutdown ("curl", cc_shutdown);
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/curl_json.c b/src/curl_json.c
new file mode 100644 (file)
index 0000000..cc8b4ad
--- /dev/null
@@ -0,0 +1,843 @@
+/**
+ * collectd - src/curl_json.c
+ * Copyright (C) 2009       Doug MacEachern
+ * Copyright (C) 2006-2011  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Doug MacEachern <dougm at hyperic.com>
+ *   Florian octo Forster <octo at collectd.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+#include "utils_avltree.h"
+#include "utils_complain.h"
+
+#include <curl/curl.h>
+#include <yajl/yajl_parse.h>
+#if HAVE_YAJL_YAJL_VERSION_H
+# include <yajl/yajl_version.h>
+#endif
+
+#if defined(YAJL_MAJOR) && (YAJL_MAJOR > 1)
+# define HAVE_YAJL_V2 1
+#endif
+
+#define CJ_DEFAULT_HOST "localhost"
+#define CJ_KEY_MAGIC 0x43484b59UL /* CHKY */
+#define CJ_IS_KEY(key) ((key)->magic == CJ_KEY_MAGIC)
+#define CJ_ANY "*"
+#define COUCH_MIN(x,y) ((x) < (y) ? (x) : (y))
+
+struct cj_key_s;
+typedef struct cj_key_s cj_key_t;
+struct cj_key_s /* {{{ */
+{
+  char *path;
+  char *type;
+  char *instance;
+  unsigned long magic;
+};
+/* }}} */
+
+struct cj_s /* {{{ */
+{
+  char *instance;
+  char *host;
+
+  char *url;
+  char *user;
+  char *pass;
+  char *credentials;
+  _Bool verify_peer;
+  _Bool verify_host;
+  char *cacert;
+
+  CURL *curl;
+  char curl_errbuf[CURL_ERROR_SIZE];
+
+  yajl_handle yajl;
+  c_avl_tree_t *tree;
+  cj_key_t *key;
+  int depth;
+  struct {
+    union {
+      c_avl_tree_t *tree;
+      cj_key_t *key;
+    };
+    char name[DATA_MAX_NAME_LEN];
+  } state[YAJL_MAX_DEPTH];
+};
+typedef struct cj_s cj_t; /* }}} */
+
+#if HAVE_YAJL_V2
+typedef size_t yajl_len_t;
+#else
+typedef unsigned int yajl_len_t;
+#endif
+
+static int cj_read (user_data_t *ud);
+static int cj_curl_perform (cj_t *db, CURL *curl);
+static void cj_submit (cj_t *db, cj_key_t *key, value_t *value);
+
+static size_t cj_curl_callback (void *buf, /* {{{ */
+    size_t size, size_t nmemb, void *user_data)
+{
+  cj_t *db;
+  size_t len;
+  yajl_status status;
+
+  len = size * nmemb;
+
+  if (len <= 0)
+    return (len);
+
+  db = user_data;
+  if (db == NULL)
+    return (0);
+
+  status = yajl_parse(db->yajl, (unsigned char *) buf, len);
+  if (status == yajl_status_ok)
+  {
+#if HAVE_YAJL_V2
+    status = yajl_complete_parse(db->yajl);
+#else
+    status = yajl_parse_complete(db->yajl);
+#endif
+    return (len);
+  }
+#if !HAVE_YAJL_V2
+  else if (status == yajl_status_insufficient_data)
+    return (len);
+#endif
+
+  if (status != yajl_status_ok)
+  {
+    unsigned char *msg =
+      yajl_get_error(db->yajl, /* verbose = */ 1,
+          /* jsonText = */ (unsigned char *) buf, (unsigned int) len);
+    ERROR ("curl_json plugin: yajl_parse failed: %s", msg);
+    yajl_free_error(db->yajl, msg);
+    return (0); /* abort write callback */
+  }
+
+  return (len);
+} /* }}} size_t cj_curl_callback */
+
+static int cj_get_type (cj_key_t *key)
+{
+  const data_set_t *ds;
+
+  ds = plugin_get_ds (key->type);
+  if (ds == NULL)
+  {
+    static char type[DATA_MAX_NAME_LEN] = "!!!invalid!!!";
+
+    assert (key->type != NULL);
+    if (strcmp (type, key->type) != 0)
+    {
+      ERROR ("curl_json plugin: Unable to look up DS type \"%s\".",
+          key->type);
+      sstrncpy (type, key->type, sizeof (type));
+    }
+
+    return -1;
+  }
+  else if (ds->ds_num > 1)
+  {
+    static c_complain_t complaint = C_COMPLAIN_INIT_STATIC;
+
+    c_complain_once (LOG_WARNING, &complaint,
+        "curl_json plugin: The type \"%s\" has more than one data source. "
+        "This is currently not supported. I will return the type of the "
+        "first data source, but this will likely lead to problems later on.",
+        key->type);
+  }
+
+  return ds->ds[0].type;
+}
+
+/* yajl callbacks */
+#define CJ_CB_ABORT    0
+#define CJ_CB_CONTINUE 1
+
+/* "number" may not be null terminated, so copy it into a buffer before
+ * parsing. */
+static int cj_cb_number (void *ctx,
+    const char *number, yajl_len_t number_len)
+{
+  char buffer[number_len + 1];
+
+  cj_t *db = (cj_t *)ctx;
+  cj_key_t *key = db->state[db->depth].key;
+  value_t vt;
+  int type;
+  int status;
+
+  if ((key == NULL) || !CJ_IS_KEY (key))
+    return (CJ_CB_CONTINUE);
+
+  memcpy (buffer, number, number_len);
+  buffer[sizeof (buffer) - 1] = 0;
+
+  type = cj_get_type (key);
+  status = parse_value (buffer, &vt, type);
+  if (status != 0)
+  {
+    NOTICE ("curl_json plugin: Unable to parse number: \"%s\"", buffer);
+    return (CJ_CB_CONTINUE);
+  }
+
+  cj_submit (db, key, &vt);
+  return (CJ_CB_CONTINUE);
+} /* int cj_cb_number */
+
+static int cj_cb_map_key (void *ctx, const unsigned char *val,
+    yajl_len_t len)
+{
+  cj_t *db = (cj_t *)ctx;
+  c_avl_tree_t *tree;
+
+  tree = db->state[db->depth-1].tree;
+
+  if (tree != NULL)
+  {
+    cj_key_t *value;
+    char *name;
+
+    name = db->state[db->depth].name;
+    len = COUCH_MIN(len, sizeof (db->state[db->depth].name)-1);
+    sstrncpy (name, (char *)val, len+1);
+
+    if (c_avl_get (tree, name, (void *) &value) == 0)
+      db->state[db->depth].key = value;
+    else if (c_avl_get (tree, CJ_ANY, (void *) &value) == 0)
+      db->state[db->depth].key = value;
+    else
+      db->state[db->depth].key = NULL;
+  }
+
+  return (CJ_CB_CONTINUE);
+}
+
+static int cj_cb_string (void *ctx, const unsigned char *val,
+    yajl_len_t len)
+{
+  cj_t *db = (cj_t *)ctx;
+  char str[len + 1];
+
+  /* Create a null-terminated version of the string. */
+  memcpy (str, val, len);
+  str[len] = 0;
+
+  /* No configuration for this string -> simply return. */
+  if (db->state[db->depth].key == NULL)
+    return (CJ_CB_CONTINUE);
+
+  if (!CJ_IS_KEY (db->state[db->depth].key))
+  {
+    NOTICE ("curl_json plugin: Found string \"%s\", but the configuration "
+        "expects a map here.", str);
+    return (CJ_CB_CONTINUE);
+  }
+
+  /* Handle the string as if it was a number. */
+  return (cj_cb_number (ctx, (const char *) val, len));
+} /* int cj_cb_string */
+
+static int cj_cb_start (void *ctx)
+{
+  cj_t *db = (cj_t *)ctx;
+  if (++db->depth >= YAJL_MAX_DEPTH)
+  {
+    ERROR ("curl_json plugin: %s depth exceeds max, aborting.", db->url);
+    return (CJ_CB_ABORT);
+  }
+  return (CJ_CB_CONTINUE);
+}
+
+static int cj_cb_end (void *ctx)
+{
+  cj_t *db = (cj_t *)ctx;
+  db->state[db->depth].tree = NULL;
+  --db->depth;
+  return (CJ_CB_CONTINUE);
+}
+
+static int cj_cb_start_map (void *ctx)
+{
+  return cj_cb_start (ctx);
+}
+
+static int cj_cb_end_map (void *ctx)
+{
+  return cj_cb_end (ctx);
+}
+
+static int cj_cb_start_array (void * ctx)
+{
+  return cj_cb_start (ctx);
+}
+
+static int cj_cb_end_array (void * ctx)
+{
+  return cj_cb_end (ctx);
+}
+
+static yajl_callbacks ycallbacks = {
+  NULL, /* null */
+  NULL, /* boolean */
+  NULL, /* integer */
+  NULL, /* double */
+  cj_cb_number,
+  cj_cb_string,
+  cj_cb_start_map,
+  cj_cb_map_key,
+  cj_cb_end_map,
+  cj_cb_start_array,
+  cj_cb_end_array
+};
+
+/* end yajl callbacks */
+
+static void cj_key_free (cj_key_t *key) /* {{{ */
+{
+  if (key == NULL)
+    return;
+
+  sfree (key->path);
+  sfree (key->type);
+  sfree (key->instance);
+
+  sfree (key);
+} /* }}} void cj_key_free */
+
+static void cj_tree_free (c_avl_tree_t *tree) /* {{{ */
+{
+  char *name;
+  void *value;
+
+  while (c_avl_pick (tree, (void *) &name, (void *) &value) == 0)
+  {
+    cj_key_t *key = (cj_key_t *)value;
+
+    if (CJ_IS_KEY(key))
+      cj_key_free (key);
+    else
+      cj_tree_free ((c_avl_tree_t *)value);
+
+    sfree (name);
+  }
+
+  c_avl_destroy (tree);
+} /* }}} void cj_tree_free */
+
+static void cj_free (void *arg) /* {{{ */
+{
+  cj_t *db;
+
+  DEBUG ("curl_json plugin: cj_free (arg = %p);", arg);
+
+  db = (cj_t *) arg;
+
+  if (db == NULL)
+    return;
+
+  if (db->curl != NULL)
+    curl_easy_cleanup (db->curl);
+  db->curl = NULL;
+
+  if (db->tree != NULL)
+    cj_tree_free (db->tree);
+  db->tree = NULL;
+
+  sfree (db->instance);
+  sfree (db->host);
+
+  sfree (db->url);
+  sfree (db->user);
+  sfree (db->pass);
+  sfree (db->credentials);
+  sfree (db->cacert);
+
+  sfree (db);
+} /* }}} void cj_free */
+
+/* Configuration handling functions {{{ */
+
+static c_avl_tree_t *cj_avl_create(void)
+{
+  return c_avl_create ((int (*) (const void *, const void *)) strcmp);
+}
+
+static int cj_config_add_key (cj_t *db, /* {{{ */
+                                   oconfig_item_t *ci)
+{
+  cj_key_t *key;
+  int status;
+  int i;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("curl_json plugin: The `Key' block "
+             "needs exactly one string argument.");
+    return (-1);
+  }
+
+  key = (cj_key_t *) malloc (sizeof (*key));
+  if (key == NULL)
+  {
+    ERROR ("curl_json plugin: malloc failed.");
+    return (-1);
+  }
+  memset (key, 0, sizeof (*key));
+  key->magic = CJ_KEY_MAGIC;
+
+  if (strcasecmp ("Key", ci->key) == 0)
+  {
+    status = cf_util_get_string (ci, &key->path);
+    if (status != 0)
+    {
+      sfree (key);
+      return (status);
+    }
+  }
+  else
+  {
+    ERROR ("curl_json plugin: cj_config: "
+           "Invalid key: %s", ci->key);
+    return (-1);
+  }
+
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Type", child->key) == 0)
+      status = cf_util_get_string (child, &key->type);
+    else if (strcasecmp ("Instance", child->key) == 0)
+      status = cf_util_get_string (child, &key->instance);
+    else
+    {
+      WARNING ("curl_json plugin: Option `%s' not allowed here.", child->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  while (status == 0)
+  {
+    if (key->type == NULL)
+    {
+      WARNING ("curl_json plugin: `Type' missing in `Key' block.");
+      status = -1;
+    }
+
+    break;
+  } /* while (status == 0) */
+
+  /* store path in a tree that will match the json map structure, example:
+   * "httpd/requests/count",
+   * "httpd/requests/current" ->
+   * { "httpd": { "requests": { "count": $key, "current": $key } } }
+   */
+  if (status == 0)
+  {
+    char *ptr;
+    char *name;
+    char ent[PATH_MAX];
+    c_avl_tree_t *tree;
+
+    if (db->tree == NULL)
+      db->tree = cj_avl_create();
+
+    tree = db->tree;
+    name = key->path;
+    ptr = key->path;
+    if (*ptr == '/')
+      ++ptr;
+
+    name = ptr;
+    while (*ptr)
+    {
+      if (*ptr == '/')
+      {
+        c_avl_tree_t *value;
+        int len;
+
+        len = ptr-name;
+        if (len == 0)
+          break;
+        sstrncpy (ent, name, len+1);
+
+        if (c_avl_get (tree, ent, (void *) &value) != 0)
+        {
+          value = cj_avl_create ();
+          c_avl_insert (tree, strdup (ent), value);
+        }
+
+        tree = value;
+        name = ptr+1;
+      }
+      ++ptr;
+    }
+    if (*name)
+      c_avl_insert (tree, strdup(name), key);
+    else
+    {
+      ERROR ("curl_json plugin: invalid key: %s", key->path);
+      status = -1;
+    }
+  }
+
+  return (status);
+} /* }}} int cj_config_add_key */
+
+static int cj_init_curl (cj_t *db) /* {{{ */
+{
+  db->curl = curl_easy_init ();
+  if (db->curl == NULL)
+  {
+    ERROR ("curl_json plugin: curl_easy_init failed.");
+    return (-1);
+  }
+
+  curl_easy_setopt (db->curl, CURLOPT_NOSIGNAL, 1);
+  curl_easy_setopt (db->curl, CURLOPT_WRITEFUNCTION, cj_curl_callback);
+  curl_easy_setopt (db->curl, CURLOPT_WRITEDATA, db);
+  curl_easy_setopt (db->curl, CURLOPT_USERAGENT,
+                    PACKAGE_NAME"/"PACKAGE_VERSION);
+  curl_easy_setopt (db->curl, CURLOPT_ERRORBUFFER, db->curl_errbuf);
+  curl_easy_setopt (db->curl, CURLOPT_URL, db->url);
+
+  if (db->user != NULL)
+  {
+    size_t credentials_size;
+
+    credentials_size = strlen (db->user) + 2;
+    if (db->pass != NULL)
+      credentials_size += strlen (db->pass);
+
+    db->credentials = (char *) malloc (credentials_size);
+    if (db->credentials == NULL)
+    {
+      ERROR ("curl_json plugin: malloc failed.");
+      return (-1);
+    }
+
+    ssnprintf (db->credentials, credentials_size, "%s:%s",
+               db->user, (db->pass == NULL) ? "" : db->pass);
+    curl_easy_setopt (db->curl, CURLOPT_USERPWD, db->credentials);
+  }
+
+  curl_easy_setopt (db->curl, CURLOPT_SSL_VERIFYPEER, (int) db->verify_peer);
+  curl_easy_setopt (db->curl, CURLOPT_SSL_VERIFYHOST,
+                    (int) (db->verify_host ? 2 : 0));
+  if (db->cacert != NULL)
+    curl_easy_setopt (db->curl, CURLOPT_CAINFO, db->cacert);
+
+  return (0);
+} /* }}} int cj_init_curl */
+
+static int cj_config_add_url (oconfig_item_t *ci) /* {{{ */
+{
+  cj_t *db;
+  int status = 0;
+  int i;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("curl_json plugin: The `URL' block "
+             "needs exactly one string argument.");
+    return (-1);
+  }
+
+  db = (cj_t *) malloc (sizeof (*db));
+  if (db == NULL)
+  {
+    ERROR ("curl_json plugin: malloc failed.");
+    return (-1);
+  }
+  memset (db, 0, sizeof (*db));
+
+  if (strcasecmp ("URL", ci->key) == 0)
+  {
+    status = cf_util_get_string (ci, &db->url);
+    if (status != 0)
+    {
+      sfree (db);
+      return (status);
+    }
+  }
+  else
+  {
+    ERROR ("curl_json plugin: cj_config: "
+           "Invalid key: %s", ci->key);
+    return (-1);
+  }
+
+  /* Fill the `cj_t' structure.. */
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Instance", child->key) == 0)
+      status = cf_util_get_string (child, &db->instance);
+    else if (strcasecmp ("Host", child->key) == 0)
+      status = cf_util_get_string (child, &db->host);
+    else if (strcasecmp ("User", child->key) == 0)
+      status = cf_util_get_string (child, &db->user);
+    else if (strcasecmp ("Password", child->key) == 0)
+      status = cf_util_get_string (child, &db->pass);
+    else if (strcasecmp ("VerifyPeer", child->key) == 0)
+      status = cf_util_get_boolean (child, &db->verify_peer);
+    else if (strcasecmp ("VerifyHost", child->key) == 0)
+      status = cf_util_get_boolean (child, &db->verify_host);
+    else if (strcasecmp ("CACert", child->key) == 0)
+      status = cf_util_get_string (child, &db->cacert);
+    else if (strcasecmp ("Key", child->key) == 0)
+      status = cj_config_add_key (db, child);
+    else
+    {
+      WARNING ("curl_json plugin: Option `%s' not allowed here.", child->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  if (status == 0)
+  {
+    if (db->tree == NULL)
+    {
+      WARNING ("curl_json plugin: No (valid) `Key' block "
+               "within `URL' block `%s'.", db->url);
+      status = -1;
+    }
+    if (status == 0)
+      status = cj_init_curl (db);
+  }
+
+  /* If all went well, register this database for reading */
+  if (status == 0)
+  {
+    user_data_t ud;
+    char cb_name[DATA_MAX_NAME_LEN];
+
+    if (db->instance == NULL)
+      db->instance = strdup("default");
+
+    DEBUG ("curl_json plugin: Registering new read callback: %s",
+           db->instance);
+
+    memset (&ud, 0, sizeof (ud));
+    ud.data = (void *) db;
+    ud.free_func = cj_free;
+
+    ssnprintf (cb_name, sizeof (cb_name), "curl_json-%s-%s",
+               db->instance, db->url);
+
+    plugin_register_complex_read (/* group = */ NULL, cb_name, cj_read,
+                                  /* interval = */ NULL, &ud);
+  }
+  else
+  {
+    cj_free (db);
+    return (-1);
+  }
+
+  return (0);
+}
+ /* }}} int cj_config_add_database */
+
+static int cj_config (oconfig_item_t *ci) /* {{{ */
+{
+  int success;
+  int errors;
+  int status;
+  int i;
+
+  success = 0;
+  errors = 0;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("URL", child->key) == 0)
+    {
+      status = cj_config_add_url (child);
+      if (status == 0)
+        success++;
+      else
+        errors++;
+    }
+    else
+    {
+      WARNING ("curl_json plugin: Option `%s' not allowed here.", child->key);
+      errors++;
+    }
+  }
+
+  if ((success == 0) && (errors > 0))
+  {
+    ERROR ("curl_json plugin: All statements failed.");
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int cj_config */
+
+/* }}} End of configuration handling functions */
+
+static void cj_submit (cj_t *db, cj_key_t *key, value_t *value) /* {{{ */
+{
+  value_list_t vl = VALUE_LIST_INIT;
+  char *host;
+
+  vl.values     = value;
+  vl.values_len = 1;
+
+  if ((db->host == NULL)
+      || (strcmp ("", db->host) == 0)
+      || (strcmp (CJ_DEFAULT_HOST, db->host) == 0))
+    host = hostname_g;
+  else
+    host = db->host;
+
+  if (key->instance == NULL)
+  {
+    if ((db->depth == 0) || (strcmp ("", db->state[db->depth-1].name) == 0))
+      sstrncpy (vl.type_instance, db->state[db->depth].name, sizeof (vl.type_instance));
+    else
+      ssnprintf (vl.type_instance, sizeof (vl.type_instance), "%s-%s",
+          db->state[db->depth-1].name, db->state[db->depth].name);
+  }
+  else
+    sstrncpy (vl.type_instance, key->instance, sizeof (vl.type_instance));
+
+  sstrncpy (vl.host, host, sizeof (vl.host));
+  sstrncpy (vl.plugin, "curl_json", sizeof (vl.plugin));
+  sstrncpy (vl.plugin_instance, db->instance, sizeof (vl.plugin_instance));
+  sstrncpy (vl.type, key->type, sizeof (vl.type));
+
+  plugin_dispatch_values (&vl);
+} /* }}} int cj_submit */
+
+static int cj_curl_perform (cj_t *db, CURL *curl) /* {{{ */
+{
+  int status;
+  long rc;
+  char *url;
+  yajl_handle yprev = db->yajl;
+
+  db->yajl = yajl_alloc (&ycallbacks,
+#if HAVE_YAJL_V2
+      /* alloc funcs = */ NULL,
+#else
+      /* alloc funcs = */ NULL, NULL,
+#endif
+      /* context = */ (void *)db);
+  if (db->yajl == NULL)
+  {
+    ERROR ("curl_json plugin: yajl_alloc failed.");
+    db->yajl = yprev;
+    return (-1);
+  }
+
+  url = NULL;
+  curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url);
+
+  status = curl_easy_perform (curl);
+  if (status != 0)
+  {
+    ERROR ("curl_json plugin: curl_easy_perform failed with status %i: %s (%s)",
+           status, db->curl_errbuf, (url != NULL) ? url : "<null>");
+    yajl_free (db->yajl);
+    db->yajl = yprev;
+    return (-1);
+  }
+
+  curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &rc);
+
+  /* The response code is zero if a non-HTTP transport was used. */
+  if ((rc != 0) && (rc != 200))
+  {
+    ERROR ("curl_json plugin: curl_easy_perform failed with "
+        "response code %ld (%s)", rc, url);
+    yajl_free (db->yajl);
+    db->yajl = yprev;
+    return (-1);
+  }
+
+#if HAVE_YAJL_V2
+    status = yajl_complete_parse(db->yajl);
+#else
+    status = yajl_parse_complete(db->yajl);
+#endif
+  if (status != yajl_status_ok)
+  {
+    unsigned char *errmsg;
+
+    errmsg = yajl_get_error (db->yajl, /* verbose = */ 0,
+        /* jsonText = */ NULL, /* jsonTextLen = */ 0);
+    ERROR ("curl_json plugin: yajl_parse_complete failed: %s",
+        (char *) errmsg);
+    yajl_free_error (db->yajl, errmsg);
+    yajl_free (db->yajl);
+    db->yajl = yprev;
+    return (-1);
+  }
+
+  yajl_free (db->yajl);
+  db->yajl = yprev;
+  return (0);
+} /* }}} int cj_curl_perform */
+
+static int cj_read (user_data_t *ud) /* {{{ */
+{
+  cj_t *db;
+
+  if ((ud == NULL) || (ud->data == NULL))
+  {
+    ERROR ("curl_json plugin: cj_read: Invalid user data.");
+    return (-1);
+  }
+
+  db = (cj_t *) ud->data;
+
+  db->depth = 0;
+  memset (&db->state, 0, sizeof(db->state));
+  db->state[db->depth].tree = db->tree;
+  db->key = NULL;
+
+  return cj_curl_perform (db, db->curl);
+} /* }}} int cj_read */
+
+void module_register (void)
+{
+  plugin_register_complex_config ("curl_json", cj_config);
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/curl_xml.c b/src/curl_xml.c
new file mode 100644 (file)
index 0000000..052ea1e
--- /dev/null
@@ -0,0 +1,932 @@
+/**
+ * collectd - src/curl_xml.c
+ * Copyright (C) 2009,2010       Amit Gupta
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Amit Gupta <amit.gupta221 at gmail.com>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+#include "utils_llist.h"
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/xpath.h>
+
+#include <curl/curl.h>
+
+#define CX_DEFAULT_HOST "localhost"
+
+/*
+ * Private data structures
+ */
+struct cx_values_s /* {{{ */
+{
+  char path[DATA_MAX_NAME_LEN];
+  size_t path_len;
+};
+typedef struct cx_values_s cx_values_t;
+/* }}} */
+
+struct cx_xpath_s /* {{{ */
+{
+  char *path;
+  char *type;
+  cx_values_t *values;
+  int values_len;
+  char *instance_prefix;
+  char *instance;
+  int is_table;
+  unsigned long magic;
+};
+typedef struct cx_xpath_s cx_xpath_t;
+/* }}} */
+
+struct cx_s /* {{{ */
+{
+  char *instance;
+  char *host;
+
+  char *url;
+  char *user;
+  char *pass;
+  char *credentials;
+  _Bool verify_peer;
+  _Bool verify_host;
+  char *cacert;
+
+  CURL *curl;
+  char curl_errbuf[CURL_ERROR_SIZE];
+  char *buffer;
+  size_t buffer_size;
+  size_t buffer_fill;
+
+  llist_t *list; /* list of xpath blocks */
+};
+typedef struct cx_s cx_t; /* }}} */
+
+/*
+ * Private functions
+ */
+static size_t cx_curl_callback (void *buf, /* {{{ */
+    size_t size, size_t nmemb, void *user_data)
+{
+  size_t len = size * nmemb;
+  cx_t *db;
+
+  db = user_data;
+  if (db == NULL)
+  {
+    ERROR ("curl_xml plugin: cx_curl_callback: "
+           "user_data pointer is NULL.");
+    return (0);
+  }
+
+   if (len <= 0)
+    return (len);
+
+  if ((db->buffer_fill + len) >= db->buffer_size)
+  {
+    char *temp;
+
+    temp = (char *) realloc (db->buffer,
+                    db->buffer_fill + len + 1);
+    if (temp == NULL)
+    {
+      ERROR ("curl_xml plugin: realloc failed.");
+      return (0);
+    }
+    db->buffer = temp;
+    db->buffer_size = db->buffer_fill + len + 1;
+  }
+
+  memcpy (db->buffer + db->buffer_fill, (char *) buf, len);
+  db->buffer_fill += len;
+  db->buffer[db->buffer_fill] = 0;
+
+  return (len);
+} /* }}} size_t cx_curl_callback */
+
+static void cx_xpath_free (cx_xpath_t *xpath) /* {{{ */
+{
+  if (xpath == NULL)
+    return;
+
+  sfree (xpath->path);
+  sfree (xpath->type);
+  sfree (xpath->instance_prefix);
+  sfree (xpath->instance);
+  sfree (xpath->values);
+  sfree (xpath);
+} /* }}} void cx_xpath_free */
+
+static void cx_list_free (llist_t *list) /* {{{ */
+{
+  llentry_t *le;
+
+  le = llist_head (list);
+  while (le != NULL)
+  {
+    llentry_t *le_next;
+
+    le_next = le->next;
+
+    sfree (le->key);
+    cx_xpath_free (le->value);
+
+    le = le_next;
+  }
+
+  llist_destroy (list);
+  list = NULL;
+} /* }}} void cx_list_free */
+
+static void cx_free (void *arg) /* {{{ */
+{
+  cx_t *db;
+
+  DEBUG ("curl_xml plugin: cx_free (arg = %p);", arg);
+
+  db = (cx_t *) arg;
+
+  if (db == NULL)
+    return;
+
+  if (db->curl != NULL)
+    curl_easy_cleanup (db->curl);
+  db->curl = NULL;
+
+  if (db->list != NULL)
+    cx_list_free (db->list);
+
+  sfree (db->buffer);
+  sfree (db->instance);
+  sfree (db->host);
+
+  sfree (db->url);
+  sfree (db->user);
+  sfree (db->pass);
+  sfree (db->credentials);
+  sfree (db->cacert);
+
+  sfree (db);
+} /* }}} void cx_free */
+
+static int cx_check_type (const data_set_t *ds, cx_xpath_t *xpath) /* {{{ */
+{
+  if (!ds)
+  {
+    WARNING ("curl_xml plugin: DataSet `%s' not defined.", xpath->type);
+    return (-1);
+  }
+
+  if (ds->ds_num != xpath->values_len)
+  {
+    WARNING ("curl_xml plugin: DataSet `%s' requires %i values, but config talks about %i",
+        xpath->type, ds->ds_num, xpath->values_len);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} cx_check_type */
+
+static xmlXPathObjectPtr cx_evaluate_xpath (xmlXPathContextPtr xpath_ctx, /* {{{ */ 
+           xmlChar *expr)
+{
+  xmlXPathObjectPtr xpath_obj;
+
+  /* XXX: When to free this? */
+  xpath_obj = xmlXPathEvalExpression(BAD_CAST expr, xpath_ctx);
+  if (xpath_obj == NULL)
+  {
+     WARNING ("curl_xml plugin: "
+               "Error unable to evaluate xpath expression \"%s\". Skipping...", expr);
+     return NULL;
+  }
+
+  return xpath_obj;
+} /* }}} cx_evaluate_xpath */
+
+static int cx_if_not_text_node (xmlNodePtr node) /* {{{ */
+{
+  if (node->type == XML_TEXT_NODE || node->type == XML_ATTRIBUTE_NODE)
+    return (0);
+
+  WARNING ("curl_xml plugin: "
+           "Node \"%s\" doesn't seem to be a text node. Skipping...", node->name);
+  return -1;
+} /* }}} cx_if_not_text_node */
+
+static int cx_handle_single_value_xpath (xmlXPathContextPtr xpath_ctx, /* {{{ */
+    cx_xpath_t *xpath,
+    const data_set_t *ds, value_list_t *vl, int index)
+{
+  xmlXPathObjectPtr values_node_obj;
+  xmlNodeSetPtr values_node;
+  int tmp_size;
+  char *node_value;
+
+  values_node_obj = cx_evaluate_xpath (xpath_ctx, BAD_CAST xpath->values[index].path);
+  if (values_node_obj == NULL)
+    return (-1); /* Error already logged. */
+
+  values_node = values_node_obj->nodesetval;
+  tmp_size = (values_node) ? values_node->nodeNr : 0;
+
+  if (tmp_size == 0)
+  {
+    WARNING ("curl_xml plugin: "
+        "relative xpath expression \"%s\" doesn't match any of the nodes. "
+        "Skipping...", xpath->values[index].path);
+    xmlXPathFreeObject (values_node_obj);
+    return (-1);
+  }
+
+  if (tmp_size > 1)
+  {
+    WARNING ("curl_xml plugin: "
+        "relative xpath expression \"%s\" is expected to return "
+        "only one node. Skipping...", xpath->values[index].path);
+    xmlXPathFreeObject (values_node_obj);
+    return (-1);
+  }
+
+  /* ignoring the element if other than textnode/attribute*/
+  if (cx_if_not_text_node(values_node->nodeTab[0]))
+  {
+    WARNING ("curl_xml plugin: "
+        "relative xpath expression \"%s\" is expected to return "
+        "only text/attribute node which is not the case. Skipping...", 
+        xpath->values[index].path);
+    xmlXPathFreeObject (values_node_obj);
+    return (-1);
+  }
+
+  node_value = (char *) xmlNodeGetContent(values_node->nodeTab[0]);
+  switch (ds->ds[index].type)
+  {
+    case DS_TYPE_COUNTER:
+      vl->values[index].counter = (counter_t) strtoull (node_value,
+          /* endptr = */ NULL, /* base = */ 0);
+      break;
+    case DS_TYPE_DERIVE:
+      vl->values[index].derive = (derive_t) strtoll (node_value,
+          /* endptr = */ NULL, /* base = */ 0);
+      break;
+    case DS_TYPE_ABSOLUTE:
+      vl->values[index].absolute = (absolute_t) strtoull (node_value,
+          /* endptr = */ NULL, /* base = */ 0);
+      break;
+    case DS_TYPE_GAUGE: 
+      vl->values[index].gauge = (gauge_t) strtod (node_value,
+          /* endptr = */ NULL);
+  }
+
+  /* free up object */
+  xmlXPathFreeObject (values_node_obj);
+
+  /* We have reached here which means that
+   * we have got something to work */
+  return (0);
+} /* }}} int cx_handle_single_value_xpath */
+
+static int cx_handle_all_value_xpaths (xmlXPathContextPtr xpath_ctx, /* {{{ */
+    cx_xpath_t *xpath,
+    const data_set_t *ds, value_list_t *vl)
+{
+  value_t values[xpath->values_len];
+  int status;
+  int i;
+
+  assert (xpath->values_len > 0);
+  assert (xpath->values_len == vl->values_len);
+  assert (xpath->values_len == ds->ds_num);
+  vl->values = values;
+
+  for (i = 0; i < xpath->values_len; i++)
+  {
+    status = cx_handle_single_value_xpath (xpath_ctx, xpath, ds, vl, i);
+    if (status != 0)
+      return (-1); /* An error has been printed. */
+  } /* for (i = 0; i < xpath->values_len; i++) */
+
+  plugin_dispatch_values (vl);
+  vl->values = NULL;
+
+  return (0);
+} /* }}} int cx_handle_all_value_xpaths */
+
+static int cx_handle_instance_xpath (xmlXPathContextPtr xpath_ctx, /* {{{ */
+    cx_xpath_t *xpath, value_list_t *vl,
+    _Bool is_table)
+{
+  xmlXPathObjectPtr instance_node_obj = NULL;
+  xmlNodeSetPtr instance_node = NULL;
+
+  memset (vl->type_instance, 0, sizeof (vl->type_instance));
+
+  /* If the base xpath returns more than one block, the result is assumed to be
+   * a table. The `Instnce' option is not optional in this case. Check for the
+   * condition and inform the user. */
+  if (is_table && (vl->type_instance == NULL))
+  {
+    WARNING ("curl_xml plugin: "
+        "Base-XPath %s is a table (more than one result was returned), "
+        "but no instance-XPath has been defined.",
+        xpath->path);
+    return (-1);
+  }
+
+  /* instance has to be an xpath expression */
+  if (xpath->instance != NULL)
+  {
+    int tmp_size;
+
+    instance_node_obj = cx_evaluate_xpath (xpath_ctx, BAD_CAST xpath->instance);
+    if (instance_node_obj == NULL)
+      return (-1); /* error is logged already */
+
+    instance_node = instance_node_obj->nodesetval;
+    tmp_size = (instance_node) ? instance_node->nodeNr : 0;
+
+    if ( (tmp_size == 0) && (is_table) )
+    {
+      WARNING ("curl_xml plugin: "
+          "relative xpath expression for 'InstanceFrom' \"%s\" doesn't match "
+          "any of the nodes. Skipping the node.", xpath->instance);
+      xmlXPathFreeObject (instance_node_obj);
+      return (-1);
+    }
+
+    if (tmp_size > 1)
+    {
+      WARNING ("curl_xml plugin: "
+          "relative xpath expression for 'InstanceFrom' \"%s\" is expected "
+          "to return only one text node. Skipping the node.", xpath->instance);
+      xmlXPathFreeObject (instance_node_obj);
+      return (-1);
+    }
+
+    /* ignoring the element if other than textnode/attribute */
+    if (cx_if_not_text_node(instance_node->nodeTab[0]))
+    {
+      WARNING ("curl_xml plugin: "
+          "relative xpath expression \"%s\" is expected to return only text node "
+          "which is not the case. Skipping the node.", xpath->instance);
+      xmlXPathFreeObject (instance_node_obj);
+      return (-1);
+    }
+  } /* if (xpath->instance != NULL) */
+
+  if (xpath->instance_prefix != NULL)
+  {
+    if (instance_node != NULL)
+      ssnprintf (vl->type_instance, sizeof (vl->type_instance),"%s%s",
+          xpath->instance_prefix, (char *) xmlNodeGetContent(instance_node->nodeTab[0]));
+    else
+      sstrncpy (vl->type_instance, xpath->instance_prefix,
+          sizeof (vl->type_instance));
+  }
+  else
+  {
+    /* If instance_prefix and instance_node are NULL, then
+     * don't set the type_instance */
+    if (instance_node != NULL)
+      sstrncpy (vl->type_instance, (char *) xmlNodeGetContent(instance_node->nodeTab[0]),
+          sizeof (vl->type_instance));
+  }
+
+  /* Free `instance_node_obj' this late, because `instance_node' points to
+   * somewhere inside this structure. */
+  xmlXPathFreeObject (instance_node_obj);
+
+  return (0);
+} /* }}} int cx_handle_instance_xpath */
+
+static int  cx_handle_base_xpath (char *plugin_instance, /* {{{ */
+    xmlXPathContextPtr xpath_ctx, const data_set_t *ds, 
+    char *base_xpath, cx_xpath_t *xpath)
+{
+  int total_nodes;
+  int i;
+
+  xmlXPathObjectPtr base_node_obj = NULL;
+  xmlNodeSetPtr base_nodes = NULL;
+
+  value_list_t vl = VALUE_LIST_INIT;
+
+  base_node_obj = cx_evaluate_xpath (xpath_ctx, BAD_CAST base_xpath); 
+  if (base_node_obj == NULL)
+    return -1; /* error is logged already */
+
+  base_nodes = base_node_obj->nodesetval;
+  total_nodes = (base_nodes) ? base_nodes->nodeNr : 0;
+
+  if (total_nodes == 0)
+  {
+     ERROR ("curl_xml plugin: "
+              "xpath expression \"%s\" doesn't match any of the nodes. "
+              "Skipping the xpath block...", base_xpath);
+     xmlXPathFreeObject (base_node_obj);
+     return -1;
+  }
+
+  /* If base_xpath returned multiple results, then */
+  /* Instance in the xpath block is required */ 
+  if (total_nodes > 1 && xpath->instance == NULL)
+  {
+    ERROR ("curl_xml plugin: "
+             "InstanceFrom is must in xpath block since the base xpath expression \"%s\" "
+             "returned multiple results. Skipping the xpath block...", base_xpath);
+    return -1;
+  }
+
+  /* set the values for the value_list */
+  vl.values_len = ds->ds_num;
+  sstrncpy (vl.type, xpath->type, sizeof (vl.type));
+  sstrncpy (vl.plugin, "curl_xml", sizeof (vl.plugin));
+  sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+  if (plugin_instance != NULL)
+    sstrncpy (vl.plugin_instance, plugin_instance, sizeof (vl.plugin_instance)); 
+
+  for (i = 0; i < total_nodes; i++)
+  {
+    int status;
+
+    xpath_ctx->node = base_nodes->nodeTab[i];
+
+    status = cx_handle_instance_xpath (xpath_ctx, xpath, &vl,
+        /* is_table = */ (total_nodes > 1));
+    if (status != 0)
+      continue; /* An error has already been reported. */
+
+    status = cx_handle_all_value_xpaths (xpath_ctx, xpath, ds, &vl);
+    if (status != 0)
+      continue; /* An error has been logged. */
+  } /* for (i = 0; i < total_nodes; i++) */
+
+  /* free up the allocated memory */
+  xmlXPathFreeObject (base_node_obj); 
+
+  return (0); 
+} /* }}} cx_handle_base_xpath */
+
+static int cx_handle_parsed_xml(xmlDocPtr doc, /* {{{ */ 
+                       xmlXPathContextPtr xpath_ctx, cx_t *db)
+{
+  llentry_t *le;
+  const data_set_t *ds;
+  cx_xpath_t *xpath;
+  int status=-1;
+  
+
+  le = llist_head (db->list);
+  while (le != NULL)
+  {
+    /* get the ds */
+    xpath = (cx_xpath_t *) le->value;
+    ds = plugin_get_ds (xpath->type);
+
+    if ( (cx_check_type(ds, xpath) == 0) &&
+         (cx_handle_base_xpath(db->instance, xpath_ctx, ds, le->key, xpath) == 0) )
+      status = 0; /* we got atleast one success */
+
+    le = le->next;
+  } /* while (le != NULL) */
+
+  return status;
+} /* }}} cx_handle_parsed_xml */
+
+static int cx_parse_stats_xml(xmlChar* xml, cx_t *db) /* {{{ */
+{
+  int status;
+  xmlDocPtr doc;
+  xmlXPathContextPtr xpath_ctx;
+
+  /* Load the XML */
+  doc = xmlParseDoc(xml);
+  if (doc == NULL)
+  {
+    ERROR ("curl_xml plugin: Failed to parse the xml document  - %s", xml);
+    return (-1);
+  }
+
+  xpath_ctx = xmlXPathNewContext(doc);
+  if(xpath_ctx == NULL)
+  {
+    ERROR ("curl_xml plugin: Failed to create the xml context");
+    xmlFreeDoc(doc);
+    return (-1);
+  }
+
+  status = cx_handle_parsed_xml (doc, xpath_ctx, db);
+  /* Cleanup */
+  xmlXPathFreeContext(xpath_ctx);
+  xmlFreeDoc(doc);
+  return status;
+} /* }}} cx_parse_stats_xml */
+
+static int cx_curl_perform (cx_t *db, CURL *curl) /* {{{ */
+{
+  int status;
+  long rc;
+  char *ptr;
+  char *url;
+
+  db->buffer_fill = 0; 
+  status = curl_easy_perform (curl);
+
+  curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url);
+  curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &rc);
+
+  /* The response code is zero if a non-HTTP transport was used. */
+  if ((rc != 0) && (rc != 200))
+  {
+    ERROR ("curl_xml plugin: curl_easy_perform failed with response code %ld (%s)",
+           rc, url);
+    return (-1);
+  }
+
+  if (status != 0)
+  {
+    ERROR ("curl_xml plugin: curl_easy_perform failed with status %i: %s (%s)",
+           status, db->curl_errbuf, url);
+    return (-1);
+  }
+
+  ptr = db->buffer;
+
+  status = cx_parse_stats_xml(BAD_CAST ptr, db);
+  db->buffer_fill = 0;
+
+  return status;
+} /* }}} int cx_curl_perform */
+
+static int cx_read (user_data_t *ud) /* {{{ */
+{
+  cx_t *db;
+
+  if ((ud == NULL) || (ud->data == NULL))
+  {
+    ERROR ("curl_xml plugin: cx_read: Invalid user data.");
+    return (-1);
+  }
+
+  db = (cx_t *) ud->data;
+
+  return cx_curl_perform (db, db->curl);
+} /* }}} int cx_read */
+
+/* Configuration handling functions {{{ */
+
+static int cx_config_add_values (const char *name, cx_xpath_t *xpath, /* {{{ */
+                                      oconfig_item_t *ci)
+{
+  int i;
+
+  if (ci->values_num < 1)
+  {
+    WARNING ("curl_xml plugin: `ValuesFrom' needs at least one argument.");
+    return (-1);
+  }
+
+  for (i = 0; i < ci->values_num; i++)
+    if (ci->values[i].type != OCONFIG_TYPE_STRING)
+    {
+      WARNING ("curl_xml plugin: `ValuesFrom' needs only string argument.");
+      return (-1);
+    }
+
+  sfree (xpath->values);
+
+  xpath->values_len = 0;
+  xpath->values = (cx_values_t *) malloc (sizeof (cx_values_t) * ci->values_num);
+  if (xpath->values == NULL)
+    return (-1);
+  xpath->values_len = ci->values_num;
+
+  /* populate cx_values_t structure */
+  for (i = 0; i < ci->values_num; i++)
+  {
+    xpath->values[i].path_len = sizeof (ci->values[i].value.string);
+    sstrncpy (xpath->values[i].path, ci->values[i].value.string, sizeof (xpath->values[i].path));
+  }
+
+  return (0); 
+} /* }}} cx_config_add_values */
+
+static int cx_config_add_xpath (cx_t *db, /* {{{ */
+                                   oconfig_item_t *ci)
+{
+  cx_xpath_t *xpath;
+  int status;
+  int i;
+
+  xpath = (cx_xpath_t *) malloc (sizeof (*xpath));
+  if (xpath == NULL)
+  {
+    ERROR ("curl_xml plugin: malloc failed.");
+    return (-1);
+  }
+  memset (xpath, 0, sizeof (*xpath));
+
+  status = cf_util_get_string (ci, &xpath->path);
+  if (status != 0)
+  {
+    sfree (xpath);
+    return (status);
+  }
+
+  /* error out if xpath->path is an empty string */
+  if (*xpath->path == 0)
+  {
+    ERROR ("curl_xml plugin: invalid xpath. "
+           "xpath value can't be an empty string");
+    return (-1);
+  }
+
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Type", child->key) == 0)
+      status = cf_util_get_string (child, &xpath->type);
+    else if (strcasecmp ("InstancePrefix", child->key) == 0)
+      status = cf_util_get_string (child, &xpath->instance_prefix);
+    else if (strcasecmp ("InstanceFrom", child->key) == 0)
+      status = cf_util_get_string (child, &xpath->instance);
+    else if (strcasecmp ("ValuesFrom", child->key) == 0)
+      status = cx_config_add_values ("ValuesFrom", xpath, child);
+    else
+    {
+      WARNING ("curl_xml plugin: Option `%s' not allowed here.", child->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  if (status == 0 && xpath->type == NULL)
+  {
+    WARNING ("curl_xml plugin: `Type' missing in `xpath' block.");
+    status = -1;
+  }
+
+  if (status == 0)
+  {
+    char *name;
+    llentry_t *le;
+
+    if (db->list == NULL)
+    {
+      db->list = llist_create();
+      if (db->list == NULL)
+      {
+        ERROR ("curl_xml plugin: list creation failed.");
+        return (-1);
+      }
+    }
+
+    name = strdup(xpath->path);
+    if (name == NULL)
+    {
+        ERROR ("curl_xml plugin: strdup failed.");
+        return (-1);
+    }
+
+    le = llentry_create (name, xpath);
+    if (le == NULL)
+    {
+      ERROR ("curl_xml plugin: llentry_create failed.");
+      return (-1);
+    }
+
+    llist_append (db->list, le);
+  }
+
+  return (status);
+} /* }}} int cx_config_add_xpath */
+
+/* Initialize db->curl */
+static int cx_init_curl (cx_t *db) /* {{{ */
+{
+  db->curl = curl_easy_init ();
+  if (db->curl == NULL)
+  {
+    ERROR ("curl_xml plugin: curl_easy_init failed.");
+    return (-1);
+  }
+
+  curl_easy_setopt (db->curl, CURLOPT_NOSIGNAL, 1);
+  curl_easy_setopt (db->curl, CURLOPT_WRITEFUNCTION, cx_curl_callback);
+  curl_easy_setopt (db->curl, CURLOPT_WRITEDATA, db);
+  curl_easy_setopt (db->curl, CURLOPT_USERAGENT,
+                    PACKAGE_NAME"/"PACKAGE_VERSION);
+  curl_easy_setopt (db->curl, CURLOPT_ERRORBUFFER, db->curl_errbuf);
+  curl_easy_setopt (db->curl, CURLOPT_URL, db->url);
+
+  if (db->user != NULL)
+  {
+    size_t credentials_size;
+
+    credentials_size = strlen (db->user) + 2;
+    if (db->pass != NULL)
+      credentials_size += strlen (db->pass);
+
+    db->credentials = (char *) malloc (credentials_size);
+    if (db->credentials == NULL)
+    {
+      ERROR ("curl_xml plugin: malloc failed.");
+      return (-1);
+    }
+
+    ssnprintf (db->credentials, credentials_size, "%s:%s",
+               db->user, (db->pass == NULL) ? "" : db->pass);
+    curl_easy_setopt (db->curl, CURLOPT_USERPWD, db->credentials);
+  }
+
+  curl_easy_setopt (db->curl, CURLOPT_SSL_VERIFYPEER, db->verify_peer ? 1L : 0L);
+  curl_easy_setopt (db->curl, CURLOPT_SSL_VERIFYHOST,
+                    db->verify_host ? 2L : 0L);
+  if (db->cacert != NULL)
+    curl_easy_setopt (db->curl, CURLOPT_CAINFO, db->cacert);
+
+  return (0);
+} /* }}} int cx_init_curl */
+
+static int cx_config_add_url (oconfig_item_t *ci) /* {{{ */
+{
+  cx_t *db;
+  int status = 0;
+  int i;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("curl_xml plugin: The `URL' block "
+             "needs exactly one string argument.");
+    return (-1);
+  }
+
+  db = (cx_t *) malloc (sizeof (*db));
+  if (db == NULL)
+  {
+    ERROR ("curl_xml plugin: malloc failed.");
+    return (-1);
+  }
+  memset (db, 0, sizeof (*db));
+
+  if (strcasecmp ("URL", ci->key) == 0)
+  {
+    status = cf_util_get_string (ci, &db->url);
+    if (status != 0)
+    {
+      sfree (db);
+      return (status);
+    }
+  }
+  else
+  {
+    ERROR ("curl_xml plugin: cx_config: "
+           "Invalid key: %s", ci->key);
+    return (-1);
+  }
+
+  /* Fill the `cx_t' structure.. */
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Instance", child->key) == 0)
+      status = cf_util_get_string (child, &db->instance);
+    else if (strcasecmp ("Host", child->key) == 0)
+      status = cf_util_get_string (child, &db->host);
+    else if (strcasecmp ("User", child->key) == 0)
+      status = cf_util_get_string (child, &db->user);
+    else if (strcasecmp ("Password", child->key) == 0)
+      status = cf_util_get_string (child, &db->pass);
+    else if (strcasecmp ("VerifyPeer", child->key) == 0)
+      status = cf_util_get_boolean (child, &db->verify_peer);
+    else if (strcasecmp ("VerifyHost", child->key) == 0)
+      status = cf_util_get_boolean (child, &db->verify_host);
+    else if (strcasecmp ("CACert", child->key) == 0)
+      status = cf_util_get_string (child, &db->cacert);
+    else if (strcasecmp ("xpath", child->key) == 0)
+      status = cx_config_add_xpath (db, child);
+    else
+    {
+      WARNING ("curl_xml plugin: Option `%s' not allowed here.", child->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  if (status == 0)
+  {
+    if (db->list == NULL)
+    {
+      WARNING ("curl_xml plugin: No (valid) `Key' block "
+               "within `URL' block `%s'.", db->url);
+      status = -1;
+    }
+    if (status == 0)
+      status = cx_init_curl (db);
+  }
+
+  /* If all went well, register this database for reading */
+  if (status == 0)
+  {
+    user_data_t ud;
+    char cb_name[DATA_MAX_NAME_LEN];
+
+    if (db->instance == NULL)
+      db->instance = strdup("default");
+
+    DEBUG ("curl_xml plugin: Registering new read callback: %s",
+           db->instance);
+
+    memset (&ud, 0, sizeof (ud));
+    ud.data = (void *) db;
+    ud.free_func = cx_free;
+
+    ssnprintf (cb_name, sizeof (cb_name), "curl_xml-%s-%s",
+               db->instance, db->url);
+
+    plugin_register_complex_read (/* group = */ NULL, cb_name, cx_read,
+                                  /* interval = */ NULL, &ud);
+  }
+  else
+  {
+    cx_free (db);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int cx_config_add_url */
+
+/* }}} End of configuration handling functions */
+
+static int cx_config (oconfig_item_t *ci) /* {{{ */
+{
+  int success;
+  int errors;
+  int status;
+  int i;
+
+  success = 0;
+  errors = 0;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("URL", child->key) == 0)
+    {
+      status = cx_config_add_url (child);
+      if (status == 0)
+        success++;
+      else
+        errors++;
+    }
+    else
+    {
+      WARNING ("curl_xml plugin: Option `%s' not allowed here.", child->key);
+      errors++;
+    }
+  }
+
+  if ((success == 0) && (errors > 0))
+  {
+    ERROR ("curl_xml plugin: All statements failed.");
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int cx_config */
+
+void module_register (void)
+{
+  plugin_register_complex_config ("curl_xml", cx_config);
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/dbi.c b/src/dbi.c
new file mode 100644 (file)
index 0000000..caa41ef
--- /dev/null
+++ b/src/dbi.c
@@ -0,0 +1,850 @@
+/**
+ * collectd - src/dbi.c
+ * Copyright (C) 2008,2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+#include "utils_db_query.h"
+
+#include <dbi/dbi.h>
+
+/*
+ * Data types
+ */
+struct cdbi_driver_option_s /* {{{ */
+{
+  char *key;
+  char *value;
+};
+typedef struct cdbi_driver_option_s cdbi_driver_option_t; /* }}} */
+
+struct cdbi_database_s /* {{{ */
+{
+  char *name;
+  char *select_db;
+
+  char *driver;
+  cdbi_driver_option_t *driver_options;
+  size_t driver_options_num;
+
+  udb_query_preparation_area_t **q_prep_areas;
+  udb_query_t **queries;
+  size_t        queries_num;
+
+  dbi_conn connection;
+};
+typedef struct cdbi_database_s cdbi_database_t; /* }}} */
+
+/*
+ * Global variables
+ */
+static udb_query_t     **queries       = NULL;
+static size_t            queries_num   = 0;
+static cdbi_database_t **databases     = NULL;
+static size_t            databases_num = 0;
+
+/*
+ * Functions
+ */
+static const char *cdbi_strerror (dbi_conn conn, /* {{{ */
+    char *buffer, size_t buffer_size)
+{
+  const char *msg;
+  int status;
+
+  if (conn == NULL)
+  {
+    sstrncpy (buffer, "connection is NULL", buffer_size);
+    return (buffer);
+  }
+
+  msg = NULL;
+  status = dbi_conn_error (conn, &msg);
+  if ((status >= 0) && (msg != NULL))
+    ssnprintf (buffer, buffer_size, "%s (status %i)", msg, status);
+  else
+    ssnprintf (buffer, buffer_size, "dbi_conn_error failed with status %i",
+        status);
+
+  return (buffer);
+} /* }}} const char *cdbi_conn_error */
+
+static int cdbi_result_get_field (dbi_result res, /* {{{ */
+    unsigned int index, char *buffer, size_t buffer_size)
+{
+  unsigned short src_type;
+
+  src_type = dbi_result_get_field_type_idx (res, index);
+  if (src_type == DBI_TYPE_ERROR)
+  {
+    ERROR ("dbi plugin: cdbi_result_get: "
+        "dbi_result_get_field_type_idx failed.");
+    return (-1);
+  }
+
+  if (src_type == DBI_TYPE_INTEGER)
+  {
+    long long value;
+
+    value = dbi_result_get_longlong_idx (res, index);
+    ssnprintf (buffer, buffer_size, "%lli", value);
+  }
+  else if (src_type == DBI_TYPE_DECIMAL)
+  {
+    double value;
+
+    value = dbi_result_get_double_idx (res, index);
+    ssnprintf (buffer, buffer_size, "%63.15g", value);
+  }
+  else if (src_type == DBI_TYPE_STRING)
+  {
+    const char *value;
+    
+    value = dbi_result_get_string_idx (res, index);
+    if (value == NULL)
+      sstrncpy (buffer, "", buffer_size);
+    else if (strcmp ("ERROR", value) == 0)
+      return (-1);
+    else
+      sstrncpy (buffer, value, buffer_size);
+  }
+  /* DBI_TYPE_BINARY */
+  /* DBI_TYPE_DATETIME */
+  else
+  {
+    const char *field_name;
+
+    field_name = dbi_result_get_field_name (res, index);
+    if (field_name == NULL)
+      field_name = "<unknown>";
+
+    ERROR ("dbi plugin: Column `%s': Don't know how to handle "
+        "source type %hu.",
+        field_name, src_type);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int cdbi_result_get_field */
+
+static void cdbi_database_free (cdbi_database_t *db) /* {{{ */
+{
+  size_t i;
+
+  if (db == NULL)
+    return;
+
+  sfree (db->name);
+  sfree (db->driver);
+
+  for (i = 0; i < db->driver_options_num; i++)
+  {
+    sfree (db->driver_options[i].key);
+    sfree (db->driver_options[i].value);
+  }
+  sfree (db->driver_options);
+
+  if (db->q_prep_areas)
+    for (i = 0; i < db->queries_num; ++i)
+      udb_query_delete_preparation_area (db->q_prep_areas[i]);
+  free (db->q_prep_areas);
+
+  sfree (db);
+} /* }}} void cdbi_database_free */
+
+/* Configuration handling functions {{{
+ *
+ * <Plugin dbi>
+ *   <Query "plugin_instance0">
+ *     Statement "SELECT name, value FROM table"
+ *     <Result>
+ *       Type "gauge"
+ *       InstancesFrom "name"
+ *       ValuesFrom "value"
+ *     </Result>
+ *     ...
+ *   </Query>
+ *     
+ *   <Database "plugin_instance1">
+ *     Driver "mysql"
+ *     DriverOption "hostname" "localhost"
+ *     ...
+ *     Query "plugin_instance0"
+ *   </Database>
+ * </Plugin>
+ */
+
+static int cdbi_config_set_string (char **ret_string, /* {{{ */
+    oconfig_item_t *ci)
+{
+  char *string;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("dbi plugin: The `%s' config option "
+        "needs exactly one string argument.", ci->key);
+    return (-1);
+  }
+
+  string = strdup (ci->values[0].value.string);
+  if (string == NULL)
+  {
+    ERROR ("dbi plugin: strdup failed.");
+    return (-1);
+  }
+
+  if (*ret_string != NULL)
+    free (*ret_string);
+  *ret_string = string;
+
+  return (0);
+} /* }}} int cdbi_config_set_string */
+
+static int cdbi_config_add_database_driver_option (cdbi_database_t *db, /* {{{ */
+    oconfig_item_t *ci)
+{
+  cdbi_driver_option_t *option;
+
+  if ((ci->values_num != 2)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING)
+      || (ci->values[1].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("dbi plugin: The `DriverOption' config option "
+        "needs exactly two string arguments.");
+    return (-1);
+  }
+
+  option = (cdbi_driver_option_t *) realloc (db->driver_options,
+      sizeof (*option) * (db->driver_options_num + 1));
+  if (option == NULL)
+  {
+    ERROR ("dbi plugin: realloc failed");
+    return (-1);
+  }
+
+  db->driver_options = option;
+  option = db->driver_options + db->driver_options_num;
+
+  option->key = strdup (ci->values[0].value.string);
+  if (option->key == NULL)
+  {
+    ERROR ("dbi plugin: strdup failed.");
+    return (-1);
+  }
+
+  option->value = strdup (ci->values[1].value.string);
+  if (option->value == NULL)
+  {
+    ERROR ("dbi plugin: strdup failed.");
+    sfree (option->key);
+    return (-1);
+  }
+
+  db->driver_options_num++;
+  return (0);
+} /* }}} int cdbi_config_add_database_driver_option */
+
+static int cdbi_config_add_database (oconfig_item_t *ci) /* {{{ */
+{
+  cdbi_database_t *db;
+  int status;
+  int i;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("dbi plugin: The `Database' block "
+        "needs exactly one string argument.");
+    return (-1);
+  }
+
+  db = (cdbi_database_t *) malloc (sizeof (*db));
+  if (db == NULL)
+  {
+    ERROR ("dbi plugin: malloc failed.");
+    return (-1);
+  }
+  memset (db, 0, sizeof (*db));
+
+  status = cdbi_config_set_string (&db->name, ci);
+  if (status != 0)
+  {
+    sfree (db);
+    return (status);
+  }
+
+  /* Fill the `cdbi_database_t' structure.. */
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Driver", child->key) == 0)
+      status = cdbi_config_set_string (&db->driver, child);
+    else if (strcasecmp ("DriverOption", child->key) == 0)
+      status = cdbi_config_add_database_driver_option (db, child);
+    else if (strcasecmp ("SelectDB", child->key) == 0)
+      status = cdbi_config_set_string (&db->select_db, child);
+    else if (strcasecmp ("Query", child->key) == 0)
+      status = udb_query_pick_from_list (child, queries, queries_num,
+          &db->queries, &db->queries_num);
+    else
+    {
+      WARNING ("dbi plugin: Option `%s' not allowed here.", child->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  /* Check that all necessary options have been given. */
+  while (status == 0)
+  {
+    if (db->driver == NULL)
+    {
+      WARNING ("dbi plugin: `Driver' not given for database `%s'", db->name);
+      status = -1;
+    }
+    if (db->driver_options_num == 0)
+    {
+      WARNING ("dbi plugin: No `DriverOption' given for database `%s'. "
+          "This will likely not work.", db->name);
+    }
+
+    break;
+  } /* while (status == 0) */
+
+  while ((status == 0) && (db->queries_num > 0))
+  {
+    db->q_prep_areas = (udb_query_preparation_area_t **) calloc (
+        db->queries_num, sizeof (*db->q_prep_areas));
+
+    if (db->q_prep_areas == NULL)
+    {
+      WARNING ("dbi plugin: malloc failed");
+      status = -1;
+      break;
+    }
+
+    for (i = 0; i < db->queries_num; ++i)
+    {
+      db->q_prep_areas[i]
+        = udb_query_allocate_preparation_area (db->queries[i]);
+
+      if (db->q_prep_areas[i] == NULL)
+      {
+        WARNING ("dbi plugin: udb_query_allocate_preparation_area failed");
+        status = -1;
+        break;
+      }
+    }
+
+    break;
+  }
+
+  /* If all went well, add this database to the global list of databases. */
+  if (status == 0)
+  {
+    cdbi_database_t **temp;
+
+    temp = (cdbi_database_t **) realloc (databases,
+        sizeof (*databases) * (databases_num + 1));
+    if (temp == NULL)
+    {
+      ERROR ("dbi plugin: realloc failed");
+      status = -1;
+    }
+    else
+    {
+      databases = temp;
+      databases[databases_num] = db;
+      databases_num++;
+    }
+  }
+
+  if (status != 0)
+  {
+    cdbi_database_free (db);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int cdbi_config_add_database */
+
+static int cdbi_config (oconfig_item_t *ci) /* {{{ */
+{
+  int i;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+    if (strcasecmp ("Query", child->key) == 0)
+      udb_query_create (&queries, &queries_num, child,
+          /* callback = */ NULL);
+    else if (strcasecmp ("Database", child->key) == 0)
+      cdbi_config_add_database (child);
+    else
+    {
+      WARNING ("snmp plugin: Ignoring unknown config option `%s'.", child->key);
+    }
+  } /* for (ci->children) */
+
+  return (0);
+} /* }}} int cdbi_config */
+
+/* }}} End of configuration handling functions */
+
+static int cdbi_init (void) /* {{{ */
+{
+  static int did_init = 0;
+  int status;
+
+  if (did_init != 0)
+    return (0);
+
+  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.");
+    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.");
+    return (-1);
+  }
+
+  status = dbi_initialize (NULL);
+  if (status < 0)
+  {
+    ERROR ("dbi plugin: cdbi_init: dbi_initialize failed with status %i.",
+        status);
+    return (-1);
+  }
+  else if (status == 0)
+  {
+    ERROR ("dbi plugin: `dbi_initialize' could not load any drivers. Please "
+        "install at least one `DBD' or check your installation.");
+    return (-1);
+  }
+  DEBUG ("dbi plugin: cdbi_init: dbi_initialize reports %i driver%s.",
+      status, (status == 1) ? "" : "s");
+
+  return (0);
+} /* }}} int cdbi_init */
+
+static int cdbi_read_database_query (cdbi_database_t *db, /* {{{ */
+    udb_query_t *q, udb_query_preparation_area_t *prep_area)
+{
+  const char *statement;
+  dbi_result res;
+  size_t column_num;
+  char **column_names;
+  char **column_values;
+  int status;
+  size_t i;
+
+  /* Macro that cleans up dynamically allocated memory and returns the
+   * specified status. */
+#define BAIL_OUT(status) \
+  if (column_names != NULL) { sfree (column_names[0]); sfree (column_names); } \
+  if (column_values != NULL) { sfree (column_values[0]); sfree (column_values); } \
+  if (res != NULL) { dbi_result_free (res); res = NULL; } \
+  return (status)
+
+  column_names = NULL;
+  column_values = NULL;
+  res = NULL;
+
+  statement = udb_query_get_statement (q);
+  assert (statement != NULL);
+
+  res = dbi_conn_query (db->connection, statement);
+  if (res == NULL)
+  {
+    char errbuf[1024];
+    ERROR ("dbi plugin: cdbi_read_database_query (%s, %s): "
+        "dbi_conn_query failed: %s",
+        db->name, udb_query_get_name (q),
+        cdbi_strerror (db->connection, errbuf, sizeof (errbuf)));
+    BAIL_OUT (-1);
+  }
+  else /* Get the number of columns */
+  {
+    unsigned int db_status;
+
+    db_status = dbi_result_get_numfields (res);
+    if (db_status == DBI_FIELD_ERROR)
+    {
+      char errbuf[1024];
+      ERROR ("dbi plugin: cdbi_read_database_query (%s, %s): "
+          "dbi_result_get_numfields failed: %s",
+          db->name, udb_query_get_name (q),
+          cdbi_strerror (db->connection, errbuf, sizeof (errbuf)));
+      BAIL_OUT (-1);
+    }
+
+    column_num = (size_t) db_status;
+    DEBUG ("cdbi_read_database_query (%s, %s): There are %zu columns.",
+        db->name, udb_query_get_name (q), column_num);
+  }
+
+  /* Allocate `column_names' and `column_values'. {{{ */
+  column_names = (char **) calloc (column_num, sizeof (char *));
+  if (column_names == NULL)
+  {
+    ERROR ("dbi plugin: malloc failed.");
+    BAIL_OUT (-1);
+  }
+
+  column_names[0] = (char *) calloc (column_num,
+      DATA_MAX_NAME_LEN * sizeof (char));
+  if (column_names[0] == NULL)
+  {
+    ERROR ("dbi plugin: malloc failed.");
+    BAIL_OUT (-1);
+  }
+  for (i = 1; i < column_num; i++)
+    column_names[i] = column_names[i - 1] + DATA_MAX_NAME_LEN;
+
+  column_values = (char **) calloc (column_num, sizeof (char *));
+  if (column_values == NULL)
+  {
+    ERROR ("dbi plugin: malloc failed.");
+    BAIL_OUT (-1);
+  }
+
+  column_values[0] = (char *) calloc (column_num,
+      DATA_MAX_NAME_LEN * sizeof (char));
+  if (column_values[0] == NULL)
+  {
+    ERROR ("dbi plugin: malloc failed.");
+    BAIL_OUT (-1);
+  }
+  for (i = 1; i < column_num; i++)
+    column_values[i] = column_values[i - 1] + DATA_MAX_NAME_LEN;
+  /* }}} */
+
+  /* Copy the field names to `column_names' */
+  for (i = 0; i < column_num; i++) /* {{{ */
+  {
+    const char *column_name;
+
+    column_name = dbi_result_get_field_name (res, (unsigned int) (i + 1));
+    if (column_name == NULL)
+    {
+      ERROR ("dbi plugin: cdbi_read_database_query (%s, %s): "
+          "Cannot retrieve name of field %zu.",
+          db->name, udb_query_get_name (q), i + 1);
+      BAIL_OUT (-1);
+    }
+
+    sstrncpy (column_names[i], column_name, DATA_MAX_NAME_LEN);
+  } /* }}} for (i = 0; i < column_num; i++) */
+
+  udb_query_prepare_result (q, prep_area, hostname_g,
+      /* plugin = */ "dbi", db->name,
+      column_names, column_num, /* interval = */ 0);
+
+  /* 0 = error; 1 = success; */
+  status = dbi_result_first_row (res); /* {{{ */
+  if (status != 1)
+  {
+    char errbuf[1024];
+    ERROR ("dbi plugin: cdbi_read_database_query (%s, %s): "
+        "dbi_result_first_row failed: %s. Maybe the statement didn't "
+        "return any rows?",
+        db->name, udb_query_get_name (q),
+        cdbi_strerror (db->connection, errbuf, sizeof (errbuf)));
+    udb_query_finish_result (q, prep_area);
+    BAIL_OUT (-1);
+  } /* }}} */
+
+  /* Iterate over all rows and call `udb_query_handle_result' with each list of
+   * values. */
+  while (42) /* {{{ */
+  {
+    status = 0;
+    /* Copy the value of the columns to `column_values' */
+    for (i = 0; i < column_num; i++) /* {{{ */
+    {
+      status = cdbi_result_get_field (res, (unsigned int) (i + 1),
+          column_values[i], DATA_MAX_NAME_LEN);
+
+      if (status != 0)
+      {
+        ERROR ("dbi plugin: cdbi_read_database_query (%s, %s): "
+            "cdbi_result_get_field (%zu) failed.",
+            db->name, udb_query_get_name (q), i + 1);
+        status = -1;
+        break;
+      }
+    } /* }}} for (i = 0; i < column_num; i++) */
+
+    /* If all values were copied successfully, call `udb_query_handle_result'
+     * to dispatch the row to the daemon. */
+    if (status == 0) /* {{{ */
+    {
+      status = udb_query_handle_result (q, prep_area, column_values);
+      if (status != 0)
+      {
+        ERROR ("dbi plugin: cdbi_read_database_query (%s, %s): "
+            "udb_query_handle_result failed.",
+            db->name, udb_query_get_name (q));
+      }
+    } /* }}} */
+
+    /* Get the next row from the database. */
+    status = dbi_result_next_row (res); /* {{{ */
+    if (status != 1)
+    {
+      if (dbi_conn_error (db->connection, NULL) != 0)
+      {
+        char errbuf[1024];
+        WARNING ("dbi plugin: cdbi_read_database_query (%s, %s): "
+            "dbi_result_next_row failed: %s.",
+            db->name, udb_query_get_name (q),
+            cdbi_strerror (db->connection, errbuf, sizeof (errbuf)));
+      }
+      break;
+    } /* }}} */
+  } /* }}} while (42) */
+
+  /* Tell the db query interface that we're done with this query. */
+  udb_query_finish_result (q, prep_area);
+
+  /* Clean up and return `status = 0' (success) */
+  BAIL_OUT (0);
+#undef BAIL_OUT
+} /* }}} int cdbi_read_database_query */
+
+static int cdbi_connect_database (cdbi_database_t *db) /* {{{ */
+{
+  dbi_driver driver;
+  dbi_conn connection;
+  size_t i;
+  int status;
+
+  if (db->connection != NULL)
+  {
+    status = dbi_conn_ping (db->connection);
+    if (status != 0) /* connection is alive */
+      return (0);
+
+    dbi_conn_close (db->connection);
+    db->connection = NULL;
+  }
+
+  driver = dbi_driver_open (db->driver);
+  if (driver == NULL)
+  {
+    ERROR ("dbi plugin: cdbi_connect_database: dbi_driver_open (%s) failed.",
+        db->driver);
+    INFO ("dbi plugin: Maybe the driver isn't installed? "
+        "Known drivers are:");
+    for (driver = dbi_driver_list (NULL);
+        driver != NULL;
+        driver = dbi_driver_list (driver))
+    {
+      INFO ("dbi plugin: * %s", dbi_driver_get_name (driver));
+    }
+    return (-1);
+  }
+
+  connection = dbi_conn_open (driver);
+  if (connection == NULL)
+  {
+    ERROR ("dbi plugin: cdbi_connect_database: dbi_conn_open (%s) failed.",
+        db->driver);
+    return (-1);
+  }
+
+  /* Set all the driver options. Because this is a very very very generic
+   * interface, the error handling is kind of long. If an invalid option is
+   * encountered, it will get a list of options understood by the driver and
+   * report that as `INFO'. This way, users hopefully don't have too much
+   * trouble finding out how to configure the plugin correctly.. */
+  for (i = 0; i < db->driver_options_num; i++)
+  {
+    DEBUG ("dbi plugin: cdbi_connect_database (%s): "
+        "key = %s; value = %s;",
+        db->name,
+        db->driver_options[i].key,
+        db->driver_options[i].value);
+
+    status = dbi_conn_set_option (connection,
+        db->driver_options[i].key, db->driver_options[i].value);
+    if (status != 0)
+    {
+      char errbuf[1024];
+      const char *opt;
+
+      ERROR ("dbi plugin: cdbi_connect_database (%s): "
+          "dbi_conn_set_option (%s, %s) failed: %s.",
+          db->name,
+          db->driver_options[i].key, db->driver_options[i].value,
+          cdbi_strerror (connection, errbuf, sizeof (errbuf)));
+
+      INFO ("dbi plugin: This is a list of all options understood "
+          "by the `%s' driver:", db->driver);
+      for (opt = dbi_conn_get_option_list (connection, NULL);
+          opt != NULL;
+          opt = dbi_conn_get_option_list (connection, opt))
+      {
+        INFO ("dbi plugin: * %s", opt);
+      }
+
+      dbi_conn_close (connection);
+      return (-1);
+    }
+  } /* for (i = 0; i < db->driver_options_num; i++) */
+
+  status = dbi_conn_connect (connection);
+  if (status != 0)
+  {
+    char errbuf[1024];
+    ERROR ("dbi plugin: cdbi_connect_database (%s): "
+        "dbi_conn_connect failed: %s",
+        db->name, cdbi_strerror (connection, errbuf, sizeof (errbuf)));
+    dbi_conn_close (connection);
+    return (-1);
+  }
+
+  if (db->select_db != NULL)
+  {
+    status = dbi_conn_select_db (connection, db->select_db);
+    if (status != 0)
+    {
+      char errbuf[1024];
+      WARNING ("dbi plugin: cdbi_connect_database (%s): "
+          "dbi_conn_select_db (%s) failed: %s. Check the `SelectDB' option.",
+          db->name, db->select_db,
+          cdbi_strerror (connection, errbuf, sizeof (errbuf)));
+      dbi_conn_close (connection);
+      return (-1);
+    }
+  }
+
+  db->connection = connection;
+  return (0);
+} /* }}} int cdbi_connect_database */
+
+static int cdbi_read_database (cdbi_database_t *db) /* {{{ */
+{
+  size_t i;
+  int success;
+  int status;
+
+  unsigned int db_version;
+
+  status = cdbi_connect_database (db);
+  if (status != 0)
+    return (status);
+  assert (db->connection != NULL);
+
+  db_version = dbi_conn_get_engine_version (db->connection);
+  /* TODO: Complain if `db_version == 0' */
+
+  success = 0;
+  for (i = 0; i < db->queries_num; i++)
+  {
+    /* Check if we know the database's version and if so, if this query applies
+     * to that version. */
+    if ((db_version != 0)
+        && (udb_query_check_version (db->queries[i], db_version) == 0))
+      continue;
+
+    status = cdbi_read_database_query (db,
+        db->queries[i], db->q_prep_areas[i]);
+    if (status == 0)
+      success++;
+  }
+
+  if (success == 0)
+  {
+    ERROR ("dbi plugin: All queries failed for database `%s'.", db->name);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int cdbi_read_database */
+
+static int cdbi_read (void) /* {{{ */
+{
+  size_t i;
+  int success = 0;
+  int status;
+
+  for (i = 0; i < databases_num; i++)
+  {
+    status = cdbi_read_database (databases[i]);
+    if (status == 0)
+      success++;
+  }
+
+  if (success == 0)
+  {
+    ERROR ("dbi plugin: No database could be read. Will return an error so "
+        "the plugin will be delayed.");
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int cdbi_read */
+
+static int cdbi_shutdown (void) /* {{{ */
+{
+  size_t i;
+
+  for (i = 0; i < databases_num; i++)
+  {
+    if (databases[i]->connection != NULL)
+    {
+      dbi_conn_close (databases[i]->connection);
+      databases[i]->connection = NULL;
+    }
+    cdbi_database_free (databases[i]);
+  }
+  sfree (databases);
+  databases_num = 0;
+
+  udb_query_free (queries, queries_num);
+  queries = NULL;
+  queries_num = 0;
+
+  return (0);
+} /* }}} int cdbi_shutdown */
+
+void module_register (void) /* {{{ */
+{
+  plugin_register_complex_config ("dbi", cdbi_config);
+  plugin_register_init ("dbi", cdbi_init);
+  plugin_register_read ("dbi", cdbi_read);
+  plugin_register_shutdown ("dbi", cdbi_shutdown);
+} /* }}} void module_register */
+
+/*
+ * vim: shiftwidth=2 softtabstop=2 et fdm=marker
+ */
diff --git a/src/df.c b/src/df.c
new file mode 100644 (file)
index 0000000..371a7fc
--- /dev/null
+++ b/src/df.c
@@ -0,0 +1,317 @@
+/**
+ * collectd - src/df.c
+ * Copyright (C) 2005-2009  Florian octo Forster
+ * Copyright (C) 2009       Paul Sadauskas
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ *   Paul Sadauskas <psadauskas at gmail.com>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+#include "utils_mount.h"
+#include "utils_ignorelist.h"
+
+#if HAVE_STATVFS
+# if HAVE_SYS_STATVFS_H
+#  include <sys/statvfs.h>
+# endif
+# define STATANYFS statvfs
+# define STATANYFS_STR "statvfs"
+# define BLOCKSIZE(s) ((s).f_frsize ? (s).f_frsize : (s).f_bsize)
+#elif HAVE_STATFS
+# if HAVE_SYS_STATFS_H
+#  include <sys/statfs.h>
+# endif
+# define STATANYFS statfs
+# define STATANYFS_STR "statfs"
+# define BLOCKSIZE(s) (s).f_bsize
+#else
+# error "No applicable input method."
+#endif
+
+static const char *config_keys[] =
+{
+       "Device",
+       "MountPoint",
+       "FSType",
+       "IgnoreSelected",
+       "ReportByDevice",
+       "ReportReserved",
+       "ReportInodes"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static ignorelist_t *il_device = NULL;
+static ignorelist_t *il_mountpoint = NULL;
+static ignorelist_t *il_fstype = NULL;
+
+static _Bool by_device = 0;
+static _Bool report_inodes = 0;
+
+static int df_init (void)
+{
+       if (il_device == NULL)
+               il_device = ignorelist_create (1);
+       if (il_mountpoint == NULL)
+               il_mountpoint = ignorelist_create (1);
+       if (il_fstype == NULL)
+               il_fstype = ignorelist_create (1);
+
+       return (0);
+}
+
+static int df_config (const char *key, const char *value)
+{
+       df_init ();
+
+       if (strcasecmp (key, "Device") == 0)
+       {
+               if (ignorelist_add (il_device, value))
+                       return (1);
+               return (0);
+       }
+       else if (strcasecmp (key, "MountPoint") == 0)
+       {
+               if (ignorelist_add (il_mountpoint, value))
+                       return (1);
+               return (0);
+       }
+       else if (strcasecmp (key, "FSType") == 0)
+       {
+               if (ignorelist_add (il_fstype, value))
+                       return (1);
+               return (0);
+       }
+       else if (strcasecmp (key, "IgnoreSelected") == 0)
+       {
+               if (IS_TRUE (value))
+               {
+                       ignorelist_set_invert (il_device, 0);
+                       ignorelist_set_invert (il_mountpoint, 0);
+                       ignorelist_set_invert (il_fstype, 0);
+               }
+               else
+               {
+                       ignorelist_set_invert (il_device, 1);
+                       ignorelist_set_invert (il_mountpoint, 1);
+                       ignorelist_set_invert (il_fstype, 1);
+               }
+               return (0);
+       }
+       else if (strcasecmp (key, "ReportByDevice") == 0)
+       {
+               if (IS_TRUE (value))
+                       by_device = 1;
+
+               return (0);
+       }
+       else if (strcasecmp (key, "ReportInodes") == 0)
+       {
+               if (IS_TRUE (value))
+                       report_inodes = 1;
+               else
+                       report_inodes = 0;
+
+               return (0);
+       }
+
+
+       return (-1);
+}
+
+__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_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));
+       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);
+} /* void df_submit_one */
+
+static int df_read (void)
+{
+#if HAVE_STATVFS
+       struct statvfs statbuf;
+#elif HAVE_STATFS
+       struct statfs statbuf;
+#endif
+       /* struct STATANYFS statbuf; */
+       cu_mount_t *mnt_list;
+       cu_mount_t *mnt_ptr;
+
+       mnt_list = NULL;
+       if (cu_mount_getlist (&mnt_list) == NULL)
+       {
+               ERROR ("df plugin: cu_mount_getlist failed.");
+               return (-1);
+       }
+
+       for (mnt_ptr = mnt_list; mnt_ptr != NULL; mnt_ptr = mnt_ptr->next)
+       {
+               unsigned long long blocksize;
+               char disk_name[256];
+               uint64_t blk_free;
+               uint64_t blk_reserved;
+               uint64_t blk_used;
+
+               if (ignorelist_match (il_device,
+                                       (mnt_ptr->spec_device != NULL)
+                                       ? mnt_ptr->spec_device
+                                       : mnt_ptr->device))
+                       continue;
+               if (ignorelist_match (il_mountpoint, mnt_ptr->dir))
+                       continue;
+               if (ignorelist_match (il_fstype, mnt_ptr->type))
+                       continue;
+
+               if (STATANYFS (mnt_ptr->dir, &statbuf) < 0)
+               {
+                       char errbuf[1024];
+                       ERROR (STATANYFS_STR"(%s) failed: %s",
+                                       mnt_ptr->dir,
+                                       sstrerror (errno, errbuf,
+                                               sizeof (errbuf)));
+                       continue;
+               }
+
+               if (!statbuf.f_blocks)
+                       continue;
+
+               if (by_device) 
+               {
+                       /* eg, /dev/hda1  -- strip off the "/dev/" */
+                       if (strncmp (mnt_ptr->spec_device, "/dev/", strlen ("/dev/")) == 0)
+                               sstrncpy (disk_name, mnt_ptr->spec_device + strlen ("/dev/"), sizeof (disk_name));
+                       else
+                               sstrncpy (disk_name, mnt_ptr->spec_device, sizeof (disk_name));
+
+                       if (strlen(disk_name) < 1) 
+                       {
+                               DEBUG("df: no device name name for mountpoint %s, skipping", mnt_ptr->dir);
+                               continue;
+                       }
+               } 
+               else 
+               {
+                       if (strcmp (mnt_ptr->dir, "/") == 0)
+                       {
+                               sstrncpy (disk_name, "root", sizeof (disk_name));
+                       }
+                       else
+                       {
+                               int i, len;
+
+                               sstrncpy (disk_name, mnt_ptr->dir + 1, sizeof (disk_name));
+                               len = strlen (disk_name);
+
+                               for (i = 0; i < len; i++)
+                                       if (disk_name[i] == '/')
+                                               disk_name[i] = '-';
+                       }
+               }
+
+               blocksize = BLOCKSIZE(statbuf);
+
+               /*
+                * Sanity-check for the values in the struct
+                */
+               /* Check for negative "available" byes. For example UFS can
+                * report negative free space for user. Notice. blk_reserved
+                * will start to diminish after this. */
+#if HAVE_STATVFS
+               /* Cast is needed to avoid compiler warnings.
+                * ((struct statvfs).f_bavail is unsigned (POSIX)) */
+               if (((int64_t) statbuf.f_bavail) < 0)
+                       statbuf.f_bavail = 0;
+#elif HAVE_STATFS
+               if (statbuf.f_bavail < 0)
+                       statbuf.f_bavail = 0;
+#endif
+               /* Make sure that f_blocks >= f_bfree >= f_bavail */
+               if (statbuf.f_bfree < statbuf.f_bavail)
+                       statbuf.f_bfree = statbuf.f_bavail;
+               if (statbuf.f_blocks < statbuf.f_bfree)
+                       statbuf.f_blocks = statbuf.f_bfree;
+
+               blk_free     = (uint64_t) statbuf.f_bavail;
+               blk_reserved = (uint64_t) (statbuf.f_bfree - statbuf.f_bavail);
+               blk_used     = (uint64_t) (statbuf.f_blocks - statbuf.f_bfree);
+
+               df_submit_one (disk_name, "df_complex", "free",
+                               (gauge_t) (blk_free * blocksize));
+               df_submit_one (disk_name, "df_complex", "reserved",
+                               (gauge_t) (blk_reserved * blocksize));
+               df_submit_one (disk_name, "df_complex", "used",
+                               (gauge_t) (blk_used * blocksize));
+
+               /* inode handling */
+               if (report_inodes)
+               {
+                       uint64_t inode_free;
+                       uint64_t inode_reserved;
+                       uint64_t inode_used;
+
+                       /* Sanity-check for the values in the struct */
+                       if (statbuf.f_ffree < statbuf.f_favail)
+                               statbuf.f_ffree = statbuf.f_favail;
+                       if (statbuf.f_files < statbuf.f_ffree)
+                               statbuf.f_files = statbuf.f_ffree;
+
+                       inode_free = (uint64_t) statbuf.f_favail;
+                       inode_reserved = (uint64_t) (statbuf.f_ffree - statbuf.f_favail);
+                       inode_used = (uint64_t) (statbuf.f_files - statbuf.f_ffree);
+                       
+                       df_submit_one (disk_name, "df_inodes", "free",
+                                       (gauge_t) inode_free);
+                       df_submit_one (disk_name, "df_inodes", "reserved",
+                                       (gauge_t) inode_reserved);
+                       df_submit_one (disk_name, "df_inodes", "used",
+                                       (gauge_t) inode_used);
+               }
+       }
+
+       cu_mount_freelist (mnt_list);
+
+       return (0);
+} /* int df_read */
+
+void module_register (void)
+{
+       plugin_register_config ("df", df_config,
+                       config_keys, config_keys_num);
+       plugin_register_init ("df", df_init);
+       plugin_register_read ("df", df_read);
+} /* void module_register */
diff --git a/src/disk.c b/src/disk.c
new file mode 100644 (file)
index 0000000..fde0dcd
--- /dev/null
@@ -0,0 +1,762 @@
+/**
+ * collectd - src/disk.c
+ * Copyright (C) 2005-2010  Florian octo Forster
+ * Copyright (C) 2009       Manuel Sanmartin
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ *   Manuel Sanmartin
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "utils_ignorelist.h"
+
+#if HAVE_MACH_MACH_TYPES_H
+#  include <mach/mach_types.h>
+#endif
+#if HAVE_MACH_MACH_INIT_H
+#  include <mach/mach_init.h>
+#endif
+#if HAVE_MACH_MACH_ERROR_H
+#  include <mach/mach_error.h>
+#endif
+#if HAVE_MACH_MACH_PORT_H
+#  include <mach/mach_port.h>
+#endif
+#if HAVE_COREFOUNDATION_COREFOUNDATION_H
+#  include <CoreFoundation/CoreFoundation.h>
+#endif
+#if HAVE_IOKIT_IOKITLIB_H
+#  include <IOKit/IOKitLib.h>
+#endif
+#if HAVE_IOKIT_IOTYPES_H
+#  include <IOKit/IOTypes.h>
+#endif
+#if HAVE_IOKIT_STORAGE_IOBLOCKSTORAGEDRIVER_H
+#  include <IOKit/storage/IOBlockStorageDriver.h>
+#endif
+#if HAVE_IOKIT_IOBSD_H
+#  include <IOKit/IOBSD.h>
+#endif
+
+#if HAVE_LIMITS_H
+# include <limits.h>
+#endif
+#ifndef UINT_MAX
+#  define UINT_MAX 4294967295U
+#endif
+
+#if HAVE_STATGRAB_H
+# include <statgrab.h>
+#endif
+
+#if HAVE_PERFSTAT
+# ifndef _AIXVERSION_610
+# include <sys/systemcfg.h>
+# endif
+# include <sys/protosw.h>
+# include <libperfstat.h>
+#endif
+
+#if HAVE_IOKIT_IOKITLIB_H
+static mach_port_t io_master_port = MACH_PORT_NULL;
+/* #endif HAVE_IOKIT_IOKITLIB_H */
+
+#elif KERNEL_LINUX
+typedef struct diskstats
+{
+       char *name;
+
+       /* This overflows in roughly 1361 years */
+       unsigned int poll_count;
+
+       derive_t read_sectors;
+       derive_t write_sectors;
+
+       derive_t read_bytes;
+       derive_t write_bytes;
+
+       derive_t read_ops;
+       derive_t write_ops;
+       derive_t read_time;
+       derive_t write_time;
+
+       derive_t avg_read_time;
+       derive_t avg_write_time;
+
+       struct diskstats *next;
+} diskstats_t;
+
+static diskstats_t *disklist;
+/* #endif KERNEL_LINUX */
+
+#elif HAVE_LIBKSTAT
+#define MAX_NUMDISK 256
+extern kstat_ctl_t *kc;
+static kstat_t *ksp[MAX_NUMDISK];
+static int numdisk = 0;
+/* #endif HAVE_LIBKSTAT */
+
+#elif defined(HAVE_LIBSTATGRAB)
+/* #endif HAVE_LIBKSTATGRAB */
+
+#elif HAVE_PERFSTAT
+static perfstat_disk_t * stat_disk;
+static int numdisk;
+static int pnumdisk;
+/* #endif HAVE_PERFSTAT */
+
+#else
+# error "No applicable input method."
+#endif
+
+static const char *config_keys[] =
+{
+       "Disk",
+       "IgnoreSelected"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static ignorelist_t *ignorelist = NULL;
+
+static int disk_config (const char *key, const char *value)
+{
+  if (ignorelist == NULL)
+    ignorelist = ignorelist_create (/* invert = */ 1);
+  if (ignorelist == NULL)
+    return (1);
+
+  if (strcasecmp ("Disk", key) == 0)
+  {
+    ignorelist_add (ignorelist, value);
+  }
+  else if (strcasecmp ("IgnoreSelected", key) == 0)
+  {
+    int invert = 1;
+    if (IS_TRUE (value))
+      invert = 0;
+    ignorelist_set_invert (ignorelist, invert);
+  }
+  else
+  {
+    return (-1);
+  }
+
+  return (0);
+} /* int disk_config */
+
+static int disk_init (void)
+{
+#if HAVE_IOKIT_IOKITLIB_H
+       kern_return_t status;
+
+       if (io_master_port != MACH_PORT_NULL)
+       {
+               mach_port_deallocate (mach_task_self (),
+                               io_master_port);
+               io_master_port = MACH_PORT_NULL;
+       }
+
+       status = IOMasterPort (MACH_PORT_NULL, &io_master_port);
+       if (status != kIOReturnSuccess)
+       {
+               ERROR ("IOMasterPort failed: %s",
+                               mach_error_string (status));
+               io_master_port = MACH_PORT_NULL;
+               return (-1);
+       }
+/* #endif HAVE_IOKIT_IOKITLIB_H */
+
+#elif KERNEL_LINUX
+       /* do nothing */
+/* #endif KERNEL_LINUX */
+
+#elif HAVE_LIBKSTAT
+       kstat_t *ksp_chain;
+
+       numdisk = 0;
+
+       if (kc == NULL)
+               return (-1);
+
+       for (numdisk = 0, ksp_chain = kc->kc_chain;
+                       (numdisk < MAX_NUMDISK) && (ksp_chain != NULL);
+                       ksp_chain = ksp_chain->ks_next)
+       {
+               if (strncmp (ksp_chain->ks_class, "disk", 4)
+                               && strncmp (ksp_chain->ks_class, "partition", 9))
+                       continue;
+               if (ksp_chain->ks_type != KSTAT_TYPE_IO)
+                       continue;
+               ksp[numdisk++] = ksp_chain;
+       }
+#endif /* HAVE_LIBKSTAT */
+
+       return (0);
+} /* int disk_init */
+
+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;
+
+       /* Both `ignorelist' and `plugin_instance' may be NULL. */
+       if (ignorelist_match (ignorelist, plugin_instance) != 0)
+         return;
+
+       values[0].derive = read;
+       values[1].derive = write;
+
+       vl.values = values;
+       vl.values_len = 2;
+       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+       sstrncpy (vl.plugin, "disk", sizeof (vl.plugin));
+       sstrncpy (vl.plugin_instance, plugin_instance,
+                       sizeof (vl.plugin_instance));
+       sstrncpy (vl.type, type, sizeof (vl.type));
+
+       plugin_dispatch_values (&vl);
+} /* void disk_submit */
+
+#if HAVE_IOKIT_IOKITLIB_H
+static signed long long dict_get_value (CFDictionaryRef dict, const char *key)
+{
+       signed long long val_int;
+       CFNumberRef      val_obj;
+       CFStringRef      key_obj;
+
+       /* `key_obj' needs to be released. */
+       key_obj = CFStringCreateWithCString (kCFAllocatorDefault, key,
+                       kCFStringEncodingASCII);
+       if (key_obj == NULL)
+       {
+               DEBUG ("CFStringCreateWithCString (%s) failed.", key);
+               return (-1LL);
+       }
+       
+       /* get => we don't need to release (== free) the object */
+       val_obj = (CFNumberRef) CFDictionaryGetValue (dict, key_obj);
+
+       CFRelease (key_obj);
+
+       if (val_obj == NULL)
+       {
+               DEBUG ("CFDictionaryGetValue (%s) failed.", key);
+               return (-1LL);
+       }
+
+       if (!CFNumberGetValue (val_obj, kCFNumberSInt64Type, &val_int))
+       {
+               DEBUG ("CFNumberGetValue (%s) failed.", key);
+               return (-1LL);
+       }
+
+       return (val_int);
+}
+#endif /* HAVE_IOKIT_IOKITLIB_H */
+
+static int disk_read (void)
+{
+#if HAVE_IOKIT_IOKITLIB_H
+       io_registry_entry_t     disk;
+       io_registry_entry_t     disk_child;
+       io_iterator_t           disk_list;
+       CFDictionaryRef         props_dict;
+       CFDictionaryRef         stats_dict;
+       CFDictionaryRef         child_dict;
+       kern_return_t           status;
+
+       signed long long read_ops;
+       signed long long read_byt;
+       signed long long read_tme;
+       signed long long write_ops;
+       signed long long write_byt;
+       signed long long write_tme;
+
+       int  disk_major;
+       int  disk_minor;
+       char disk_name[64];
+
+       /* Get the list of all disk objects. */
+       if (IOServiceGetMatchingServices (io_master_port,
+                               IOServiceMatching (kIOBlockStorageDriverClass),
+                               &disk_list) != kIOReturnSuccess)
+       {
+               ERROR ("disk plugin: IOServiceGetMatchingServices failed.");
+               return (-1);
+       }
+
+       while ((disk = IOIteratorNext (disk_list)) != 0)
+       {
+               props_dict = NULL;
+               stats_dict = NULL;
+               child_dict = NULL;
+
+               /* `disk_child' must be released */
+               if ((status = IORegistryEntryGetChildEntry (disk, kIOServicePlane, &disk_child))
+                               != kIOReturnSuccess)
+               {
+                       /* This fails for example for DVD/CD drives.. */
+                       DEBUG ("IORegistryEntryGetChildEntry (disk) failed: 0x%08x", status);
+                       IOObjectRelease (disk);
+                       continue;
+               }
+
+               /* We create `props_dict' => we need to release it later */
+               if (IORegistryEntryCreateCFProperties (disk,
+                                       (CFMutableDictionaryRef *) &props_dict,
+                                       kCFAllocatorDefault,
+                                       kNilOptions)
+                               != kIOReturnSuccess)
+               {
+                       ERROR ("disk-plugin: IORegistryEntryCreateCFProperties failed.");
+                       IOObjectRelease (disk_child);
+                       IOObjectRelease (disk);
+                       continue;
+               }
+
+               if (props_dict == NULL)
+               {
+                       DEBUG ("IORegistryEntryCreateCFProperties (disk) failed.");
+                       IOObjectRelease (disk_child);
+                       IOObjectRelease (disk);
+                       continue;
+               }
+
+               stats_dict = (CFDictionaryRef) CFDictionaryGetValue (props_dict,
+                               CFSTR (kIOBlockStorageDriverStatisticsKey));
+
+               if (stats_dict == NULL)
+               {
+                       DEBUG ("CFDictionaryGetValue (%s) failed.",
+                                       kIOBlockStorageDriverStatisticsKey);
+                       CFRelease (props_dict);
+                       IOObjectRelease (disk_child);
+                       IOObjectRelease (disk);
+                       continue;
+               }
+
+               if (IORegistryEntryCreateCFProperties (disk_child,
+                                       (CFMutableDictionaryRef *) &child_dict,
+                                       kCFAllocatorDefault,
+                                       kNilOptions)
+                               != kIOReturnSuccess)
+               {
+                       DEBUG ("IORegistryEntryCreateCFProperties (disk_child) failed.");
+                       IOObjectRelease (disk_child);
+                       CFRelease (props_dict);
+                       IOObjectRelease (disk);
+                       continue;
+               }
+
+               /* kIOBSDNameKey */
+               disk_major = (int) dict_get_value (child_dict,
+                               kIOBSDMajorKey);
+               disk_minor = (int) dict_get_value (child_dict,
+                               kIOBSDMinorKey);
+               read_ops  = dict_get_value (stats_dict,
+                               kIOBlockStorageDriverStatisticsReadsKey);
+               read_byt  = dict_get_value (stats_dict,
+                               kIOBlockStorageDriverStatisticsBytesReadKey);
+               read_tme  = dict_get_value (stats_dict,
+                               kIOBlockStorageDriverStatisticsTotalReadTimeKey);
+               write_ops = dict_get_value (stats_dict,
+                               kIOBlockStorageDriverStatisticsWritesKey);
+               write_byt = dict_get_value (stats_dict,
+                               kIOBlockStorageDriverStatisticsBytesWrittenKey);
+               /* This property describes the number of nanoseconds spent
+                * performing writes since the block storage driver was
+                * instantiated. It is one of the statistic entries listed
+                * under the top-level kIOBlockStorageDriverStatisticsKey
+                * property table. It has an OSNumber value. */
+               write_tme = dict_get_value (stats_dict,
+                               kIOBlockStorageDriverStatisticsTotalWriteTimeKey);
+
+               if (ssnprintf (disk_name, sizeof (disk_name),
+                               "%i-%i", disk_major, disk_minor) >= sizeof (disk_name))
+               {
+                       DEBUG ("snprintf (major, minor) failed.");
+                       CFRelease (child_dict);
+                       IOObjectRelease (disk_child);
+                       CFRelease (props_dict);
+                       IOObjectRelease (disk);
+                       continue;
+               }
+               DEBUG ("disk_name = %s", disk_name);
+
+               if ((read_byt != -1LL) || (write_byt != -1LL))
+                       disk_submit (disk_name, "disk_octets", read_byt, write_byt);
+               if ((read_ops != -1LL) || (write_ops != -1LL))
+                       disk_submit (disk_name, "disk_ops", read_ops, write_ops);
+               if ((read_tme != -1LL) || (write_tme != -1LL))
+                       disk_submit (disk_name, "disk_time",
+                                       read_tme / 1000,
+                                       write_tme / 1000);
+
+               CFRelease (child_dict);
+               IOObjectRelease (disk_child);
+               CFRelease (props_dict);
+               IOObjectRelease (disk);
+       }
+       IOObjectRelease (disk_list);
+/* #endif HAVE_IOKIT_IOKITLIB_H */
+
+#elif KERNEL_LINUX
+       FILE *fh;
+       char buffer[1024];
+       
+       char *fields[32];
+       int numfields;
+       int fieldshift = 0;
+
+       int minor = 0;
+
+       derive_t read_sectors  = 0;
+       derive_t write_sectors = 0;
+
+       derive_t read_ops      = 0;
+       derive_t read_merged   = 0;
+       derive_t read_time     = 0;
+       derive_t write_ops     = 0;
+       derive_t write_merged  = 0;
+       derive_t write_time    = 0;
+       int is_disk = 0;
+
+       diskstats_t *ds, *pre_ds;
+
+       if ((fh = fopen ("/proc/diskstats", "r")) == NULL)
+       {
+               fh = fopen ("/proc/partitions", "r");
+               if (fh == NULL)
+               {
+                       ERROR ("disk plugin: fopen (/proc/{diskstats,partitions}) failed.");
+                       return (-1);
+               }
+
+               /* Kernel is 2.4.* */
+               fieldshift = 1;
+       }
+
+       while (fgets (buffer, sizeof (buffer), fh) != NULL)
+       {
+               char *disk_name;
+
+               numfields = strsplit (buffer, fields, 32);
+
+               if ((numfields != (14 + fieldshift)) && (numfields != 7))
+                       continue;
+
+               minor = atoll (fields[1]);
+
+               disk_name = fields[2 + fieldshift];
+
+               for (ds = disklist, pre_ds = disklist; ds != NULL; pre_ds = ds, ds = ds->next)
+                       if (strcmp (disk_name, ds->name) == 0)
+                               break;
+
+               if (ds == NULL)
+               {
+                       if ((ds = (diskstats_t *) calloc (1, sizeof (diskstats_t))) == NULL)
+                               continue;
+
+                       if ((ds->name = strdup (disk_name)) == NULL)
+                       {
+                               free (ds);
+                               continue;
+                       }
+
+                       if (pre_ds == NULL)
+                               disklist = ds;
+                       else
+                               pre_ds->next = ds;
+               }
+
+               is_disk = 0;
+               if (numfields == 7)
+               {
+                       /* Kernel 2.6, Partition */
+                       read_ops      = atoll (fields[3]);
+                       read_sectors  = atoll (fields[4]);
+                       write_ops     = atoll (fields[5]);
+                       write_sectors = atoll (fields[6]);
+               }
+               else if (numfields == (14 + fieldshift))
+               {
+                       read_ops  =  atoll (fields[3 + fieldshift]);
+                       write_ops =  atoll (fields[7 + fieldshift]);
+
+                       read_sectors  = atoll (fields[5 + fieldshift]);
+                       write_sectors = atoll (fields[9 + fieldshift]);
+
+                       if ((fieldshift == 0) || (minor == 0))
+                       {
+                               is_disk = 1;
+                               read_merged  = atoll (fields[4 + fieldshift]);
+                               read_time    = atoll (fields[6 + fieldshift]);
+                               write_merged = atoll (fields[8 + fieldshift]);
+                               write_time   = atoll (fields[10+ fieldshift]);
+                       }
+               }
+               else
+               {
+                       DEBUG ("numfields = %i; => unknown file format.", numfields);
+                       continue;
+               }
+
+               {
+                       derive_t diff_read_sectors;
+                       derive_t diff_write_sectors;
+
+               /* If the counter wraps around, it's only 32 bits.. */
+                       if (read_sectors < ds->read_sectors)
+                               diff_read_sectors = 1 + read_sectors
+                                       + (UINT_MAX - ds->read_sectors);
+                       else
+                               diff_read_sectors = read_sectors - ds->read_sectors;
+                       if (write_sectors < ds->write_sectors)
+                               diff_write_sectors = 1 + write_sectors
+                                       + (UINT_MAX - ds->write_sectors);
+                       else
+                               diff_write_sectors = write_sectors - ds->write_sectors;
+
+                       ds->read_bytes += 512 * diff_read_sectors;
+                       ds->write_bytes += 512 * diff_write_sectors;
+                       ds->read_sectors = read_sectors;
+                       ds->write_sectors = write_sectors;
+               }
+
+               /* Calculate the average time an io-op needs to complete */
+               if (is_disk)
+               {
+                       derive_t diff_read_ops;
+                       derive_t diff_write_ops;
+                       derive_t diff_read_time;
+                       derive_t diff_write_time;
+
+                       if (read_ops < ds->read_ops)
+                               diff_read_ops = 1 + read_ops
+                                       + (UINT_MAX - ds->read_ops);
+                       else
+                               diff_read_ops = read_ops - ds->read_ops;
+                       DEBUG ("disk plugin: disk_name = %s; read_ops = %"PRIi64"; "
+                                       "ds->read_ops = %"PRIi64"; diff_read_ops = %"PRIi64";",
+                                       disk_name,
+                                       read_ops, ds->read_ops, diff_read_ops);
+
+                       if (write_ops < ds->write_ops)
+                               diff_write_ops = 1 + write_ops
+                                       + (UINT_MAX - ds->write_ops);
+                       else
+                               diff_write_ops = write_ops - ds->write_ops;
+
+                       if (read_time < ds->read_time)
+                               diff_read_time = 1 + read_time
+                                       + (UINT_MAX - ds->read_time);
+                       else
+                               diff_read_time = read_time - ds->read_time;
+
+                       if (write_time < ds->write_time)
+                               diff_write_time = 1 + write_time
+                                       + (UINT_MAX - ds->write_time);
+                       else
+                               diff_write_time = write_time - ds->write_time;
+
+                       if (diff_read_ops != 0)
+                               ds->avg_read_time += (diff_read_time
+                                               + (diff_read_ops / 2))
+                                       / diff_read_ops;
+                       if (diff_write_ops != 0)
+                               ds->avg_write_time += (diff_write_time
+                                               + (diff_write_ops / 2))
+                                       / diff_write_ops;
+
+                       ds->read_ops = read_ops;
+                       ds->read_time = read_time;
+                       ds->write_ops = write_ops;
+                       ds->write_time = write_time;
+               } /* if (is_disk) */
+
+               /* Don't write to the RRDs if we've just started.. */
+               ds->poll_count++;
+               if (ds->poll_count <= 2)
+               {
+                       DEBUG ("disk plugin: (ds->poll_count = %i) <= "
+                                       "(min_poll_count = 2); => Not writing.",
+                                       ds->poll_count);
+                       continue;
+               }
+
+               if ((read_ops == 0) && (write_ops == 0))
+               {
+                       DEBUG ("disk plugin: ((read_ops == 0) && "
+                                       "(write_ops == 0)); => Not writing.");
+                       continue;
+               }
+
+               if ((ds->read_bytes != 0) || (ds->write_bytes != 0))
+                       disk_submit (disk_name, "disk_octets",
+                                       ds->read_bytes, ds->write_bytes);
+
+               if ((ds->read_ops != 0) || (ds->write_ops != 0))
+                       disk_submit (disk_name, "disk_ops",
+                                       read_ops, write_ops);
+
+               if ((ds->avg_read_time != 0) || (ds->avg_write_time != 0))
+                       disk_submit (disk_name, "disk_time",
+                                       ds->avg_read_time, ds->avg_write_time);
+
+               if (is_disk)
+               {
+                       disk_submit (disk_name, "disk_merged",
+                                       read_merged, write_merged);
+               } /* if (is_disk) */
+       } /* while (fgets (buffer, sizeof (buffer), fh) != NULL) */
+
+       fclose (fh);
+/* #endif defined(KERNEL_LINUX) */
+
+#elif HAVE_LIBKSTAT
+# if HAVE_KSTAT_IO_T_WRITES && HAVE_KSTAT_IO_T_NWRITES && HAVE_KSTAT_IO_T_WTIME
+#  define KIO_ROCTETS reads
+#  define KIO_WOCTETS writes
+#  define KIO_ROPS    nreads
+#  define KIO_WOPS    nwrites
+#  define KIO_RTIME   rtime
+#  define KIO_WTIME   wtime
+# elif HAVE_KSTAT_IO_T_NWRITTEN && HAVE_KSTAT_IO_T_WRITES && HAVE_KSTAT_IO_T_WTIME
+#  define KIO_ROCTETS nread
+#  define KIO_WOCTETS nwritten
+#  define KIO_ROPS    reads
+#  define KIO_WOPS    writes
+#  define KIO_RTIME   rtime
+#  define KIO_WTIME   wtime
+# else
+#  error "kstat_io_t does not have the required members"
+# endif
+       static kstat_io_t kio;
+       int i;
+
+       if (kc == NULL)
+               return (-1);
+
+       for (i = 0; i < numdisk; i++)
+       {
+               if (kstat_read (kc, ksp[i], &kio) == -1)
+                       continue;
+
+               if (strncmp (ksp[i]->ks_class, "disk", 4) == 0)
+               {
+                       disk_submit (ksp[i]->ks_name, "disk_octets",
+                                       kio.KIO_ROCTETS, kio.KIO_WOCTETS);
+                       disk_submit (ksp[i]->ks_name, "disk_ops",
+                                       kio.KIO_ROPS, kio.KIO_WOPS);
+                       /* FIXME: Convert this to microseconds if necessary */
+                       disk_submit (ksp[i]->ks_name, "disk_time",
+                                       kio.KIO_RTIME, kio.KIO_WTIME);
+               }
+               else if (strncmp (ksp[i]->ks_class, "partition", 9) == 0)
+               {
+                       disk_submit (ksp[i]->ks_name, "disk_octets",
+                                       kio.KIO_ROCTETS, kio.KIO_WOCTETS);
+                       disk_submit (ksp[i]->ks_name, "disk_ops",
+                                       kio.KIO_ROPS, kio.KIO_WOPS);
+               }
+       }
+/* #endif defined(HAVE_LIBKSTAT) */
+
+#elif defined(HAVE_LIBSTATGRAB)
+       sg_disk_io_stats *ds;
+       int disks, counter;
+       char name[DATA_MAX_NAME_LEN];
+       
+       if ((ds = sg_get_disk_io_stats(&disks)) == NULL)
+               return (0);
+               
+       for (counter=0; counter < disks; counter++) {
+               strncpy(name, ds->disk_name, sizeof(name));
+               name[sizeof(name)-1] = '\0'; /* strncpy doesn't terminate longer strings */
+               disk_submit (name, "disk_octets", ds->read_bytes, ds->write_bytes);
+               ds++;
+       }
+/* #endif defined(HAVE_LIBSTATGRAB) */
+
+#elif defined(HAVE_PERFSTAT)
+       derive_t read_sectors;
+       derive_t write_sectors;
+       derive_t read_time;
+       derive_t write_time;
+       derive_t read_ops;
+       derive_t write_ops;
+       perfstat_id_t firstpath;
+       int rnumdisk;
+       int i;
+
+       if ((numdisk = perfstat_disk(NULL, NULL, sizeof(perfstat_disk_t), 0)) < 0) 
+       {
+               char errbuf[1024];
+               WARNING ("disk plugin: perfstat_disk: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       if (numdisk != pnumdisk || stat_disk==NULL) {
+               if (stat_disk!=NULL) 
+                       free(stat_disk);
+               stat_disk = (perfstat_disk_t *)calloc(numdisk, sizeof(perfstat_disk_t));
+       } 
+       pnumdisk = numdisk;
+
+       firstpath.name[0]='\0';
+       if ((rnumdisk = perfstat_disk(&firstpath, stat_disk, sizeof(perfstat_disk_t), numdisk)) < 0) 
+       {
+               char errbuf[1024];
+               WARNING ("disk plugin: perfstat_disk : %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       for (i = 0; i < rnumdisk; i++) 
+       {
+               read_sectors = stat_disk[i].rblks*stat_disk[i].bsize;
+               write_sectors = stat_disk[i].wblks*stat_disk[i].bsize;
+               disk_submit (stat_disk[i].name, "disk_octets", read_sectors, write_sectors);
+
+               read_ops = stat_disk[i].xrate;
+               write_ops = stat_disk[i].xfers - stat_disk[i].xrate;
+               disk_submit (stat_disk[i].name, "disk_ops", read_ops, write_ops);
+
+               read_time = stat_disk[i].rserv;
+               read_time *= ((double)(_system_configuration.Xint)/(double)(_system_configuration.Xfrac)) / 1000000.0;
+               write_time = stat_disk[i].wserv;
+               write_time *= ((double)(_system_configuration.Xint)/(double)(_system_configuration.Xfrac)) / 1000000.0;
+               disk_submit (stat_disk[i].name, "disk_time", read_time, write_time);
+       }
+#endif /* defined(HAVE_PERFSTAT) */
+
+       return (0);
+} /* int disk_read */
+
+void module_register (void)
+{
+  plugin_register_config ("disk", disk_config,
+      config_keys, config_keys_num);
+  plugin_register_init ("disk", disk_init);
+  plugin_register_read ("disk", disk_read);
+} /* void module_register */
diff --git a/src/dns.c b/src/dns.c
new file mode 100644 (file)
index 0000000..95797f5
--- /dev/null
+++ b/src/dns.c
@@ -0,0 +1,412 @@
+/**
+ * collectd - src/dns.c
+ * Copyright (C) 2006-2011  Florian octo Forster
+ * Copyright (C) 2009       Mirko Buffoni
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at collectd.org>
+ *   Mirko Buffoni <briareos at eswat.org>
+ **/
+
+#define _BSD_SOURCE
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+
+#include "utils_dns.h"
+#include <pthread.h>
+#include <poll.h>
+
+#include <pcap.h>
+#include <pcap-bpf.h>
+
+/*
+ * Private data types
+ */
+struct counter_list_s
+{
+       unsigned int key;
+       unsigned int value;
+       struct counter_list_s *next;
+};
+typedef struct counter_list_s counter_list_t;
+
+/*
+ * Private variables
+ */
+static const char *config_keys[] =
+{
+       "Interface",
+       "IgnoreSource",
+       "SelectNumericQueryTypes"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+static int select_numeric_qtype = 1;
+
+#define PCAP_SNAPLEN 1460
+static char   *pcap_device = NULL;
+
+static derive_t       tr_queries;
+static derive_t       tr_responses;
+static counter_list_t *qtype_list;
+static counter_list_t *opcode_list;
+static counter_list_t *rcode_list;
+
+static pthread_t       listen_thread;
+static int             listen_thread_init = 0;
+/* The `traffic' mutex if for `tr_queries' and `tr_responses' */
+static pthread_mutex_t traffic_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t qtype_mutex   = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t opcode_mutex  = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t rcode_mutex   = PTHREAD_MUTEX_INITIALIZER;
+
+/*
+ * Private functions
+ */
+static counter_list_t *counter_list_search (counter_list_t **list, unsigned int key)
+{
+       counter_list_t *entry;
+
+       for (entry = *list; entry != NULL; entry = entry->next)
+               if (entry->key == key)
+                       break;
+
+       return (entry);
+}
+
+static counter_list_t *counter_list_create (counter_list_t **list,
+               unsigned int key, unsigned int value)
+{
+       counter_list_t *entry;
+
+       entry = (counter_list_t *) malloc (sizeof (counter_list_t));
+       if (entry == NULL)
+               return (NULL);
+
+       memset (entry, 0, sizeof (counter_list_t));
+       entry->key = key;
+       entry->value = value;
+
+       if (*list == NULL)
+       {
+               *list = entry;
+       }
+       else
+       {
+               counter_list_t *last;
+
+               last = *list;
+               while (last->next != NULL)
+                       last = last->next;
+
+               last->next = entry;
+       }
+
+       return (entry);
+}
+
+static void counter_list_add (counter_list_t **list,
+               unsigned int key, unsigned int increment)
+{
+       counter_list_t *entry;
+
+       entry = counter_list_search (list, key);
+
+       if (entry != NULL)
+       {
+               entry->value += increment;
+       }
+       else
+       {
+               counter_list_create (list, key, increment);
+       }
+}
+
+static int dns_config (const char *key, const char *value)
+{
+       if (strcasecmp (key, "Interface") == 0)
+       {
+               if (pcap_device != NULL)
+                       free (pcap_device);
+               if ((pcap_device = strdup (value)) == NULL)
+                       return (1);
+       }
+       else if (strcasecmp (key, "IgnoreSource") == 0)
+       {
+               if (value != NULL)
+                       ignore_list_add_name (value);
+       }
+       else if (strcasecmp (key, "SelectNumericQueryTypes") == 0)
+       {
+               if ((value != NULL) && IS_FALSE (value))
+                       select_numeric_qtype = 0;
+               else
+                       select_numeric_qtype = 1;
+       }
+       else
+       {
+               return (-1);
+       }
+
+       return (0);
+}
+
+static void dns_child_callback (const rfc1035_header_t *dns)
+{
+       if (dns->qr == 0)
+       {
+               /* This is a query */
+               int skip = 0;
+               if (!select_numeric_qtype)
+               {
+                       const char *str = qtype_str(dns->qtype);
+                       if ((str == NULL) || (str[0] == '#'))
+                               skip = 1;
+               }
+
+               pthread_mutex_lock (&traffic_mutex);
+               tr_queries += dns->length;
+               pthread_mutex_unlock (&traffic_mutex);
+
+               if (skip == 0)
+               {
+                       pthread_mutex_lock (&qtype_mutex);
+                       counter_list_add (&qtype_list, dns->qtype,  1);
+                       pthread_mutex_unlock (&qtype_mutex);
+               }
+       }
+       else
+       {
+               /* This is a reply */
+               pthread_mutex_lock (&traffic_mutex);
+               tr_responses += dns->length;
+               pthread_mutex_unlock (&traffic_mutex);
+
+               pthread_mutex_lock (&rcode_mutex);
+               counter_list_add (&rcode_list,  dns->rcode,  1);
+               pthread_mutex_unlock (&rcode_mutex);
+       }
+
+       /* FIXME: Are queries, replies or both interesting? */
+       pthread_mutex_lock (&opcode_mutex);
+       counter_list_add (&opcode_list, dns->opcode, 1);
+       pthread_mutex_unlock (&opcode_mutex);
+}
+
+static void *dns_child_loop (__attribute__((unused)) void *dummy)
+{
+       pcap_t *pcap_obj;
+       char    pcap_error[PCAP_ERRBUF_SIZE];
+       struct  bpf_program fp;
+
+       int status;
+
+       /* Don't block any signals */
+       {
+               sigset_t sigmask;
+               sigemptyset (&sigmask);
+               pthread_sigmask (SIG_SETMASK, &sigmask, NULL);
+       }
+
+       /* Passing `pcap_device == NULL' is okay and the same as passign "any" */
+       DEBUG ("dns plugin: Creating PCAP object..");
+       pcap_obj = pcap_open_live ((pcap_device != NULL) ? pcap_device : "any",
+                       PCAP_SNAPLEN,
+                       0 /* Not promiscuous */,
+                       (int) CDTIME_T_TO_MS (interval_g / 2),
+                       pcap_error);
+       if (pcap_obj == NULL)
+       {
+               ERROR ("dns plugin: Opening interface `%s' "
+                               "failed: %s",
+                               (pcap_device != NULL) ? pcap_device : "any",
+                               pcap_error);
+               return (NULL);
+       }
+
+       memset (&fp, 0, sizeof (fp));
+       if (pcap_compile (pcap_obj, &fp, "udp port 53", 1, 0) < 0)
+       {
+               ERROR ("dns plugin: pcap_compile failed");
+               return (NULL);
+       }
+       if (pcap_setfilter (pcap_obj, &fp) < 0)
+       {
+               ERROR ("dns plugin: pcap_setfilter failed");
+               return (NULL);
+       }
+
+       DEBUG ("dns plugin: PCAP object created.");
+
+       dnstop_set_pcap_obj (pcap_obj);
+       dnstop_set_callback (dns_child_callback);
+
+       status = pcap_loop (pcap_obj,
+                       -1 /* loop forever */,
+                       handle_pcap /* callback */,
+                       NULL /* Whatever this means.. */);
+       if (status < 0)
+               ERROR ("dns plugin: Listener thread is exiting "
+                               "abnormally: %s", pcap_geterr (pcap_obj));
+
+       DEBUG ("dns plugin: Child is exiting.");
+
+       pcap_close (pcap_obj);
+       listen_thread_init = 0;
+       pthread_exit (NULL);
+
+       return (NULL);
+} /* static void dns_child_loop (void) */
+
+static int dns_init (void)
+{
+       /* clean up an old thread */
+       int status;
+
+       pthread_mutex_lock (&traffic_mutex);
+       tr_queries   = 0;
+       tr_responses = 0;
+       pthread_mutex_unlock (&traffic_mutex);
+
+       if (listen_thread_init != 0)
+               return (-1);
+
+       status = pthread_create (&listen_thread, NULL, dns_child_loop,
+                       (void *) 0);
+       if (status != 0)
+       {
+               char errbuf[1024];
+               ERROR ("dns plugin: pthread_create failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       listen_thread_init = 1;
+
+       return (0);
+} /* int dns_init */
+
+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_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));
+
+       plugin_dispatch_values (&vl);
+} /* void submit_derive */
+
+static void submit_octets (derive_t queries, derive_t responses)
+{
+       value_t values[2];
+       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));
+       sstrncpy (vl.plugin, "dns", sizeof (vl.plugin));
+       sstrncpy (vl.type, "dns_octets", sizeof (vl.type));
+
+       plugin_dispatch_values (&vl);
+} /* void submit_octets */
+
+static int dns_read (void)
+{
+       unsigned int keys[T_MAX];
+       unsigned int values[T_MAX];
+       int len;
+       int i;
+
+       counter_list_t *ptr;
+
+       pthread_mutex_lock (&traffic_mutex);
+       values[0] = tr_queries;
+       values[1] = tr_responses;
+       pthread_mutex_unlock (&traffic_mutex);
+
+       if ((values[0] != 0) || (values[1] != 0))
+               submit_octets (values[0], values[1]);
+
+       pthread_mutex_lock (&qtype_mutex);
+       for (ptr = qtype_list, len = 0;
+                       (ptr != NULL) && (len < T_MAX);
+                       ptr = ptr->next, len++)
+       {
+               keys[len]   = ptr->key;
+               values[len] = ptr->value;
+       }
+       pthread_mutex_unlock (&qtype_mutex);
+
+       for (i = 0; i < len; i++)
+       {
+               DEBUG ("dns plugin: qtype = %u; counter = %u;", keys[i], values[i]);
+               submit_derive ("dns_qtype", qtype_str (keys[i]), values[i]);
+       }
+
+       pthread_mutex_lock (&opcode_mutex);
+       for (ptr = opcode_list, len = 0;
+                       (ptr != NULL) && (len < T_MAX);
+                       ptr = ptr->next, len++)
+       {
+               keys[len]   = ptr->key;
+               values[len] = ptr->value;
+       }
+       pthread_mutex_unlock (&opcode_mutex);
+
+       for (i = 0; i < len; i++)
+       {
+               DEBUG ("dns plugin: opcode = %u; counter = %u;", keys[i], values[i]);
+               submit_derive ("dns_opcode", opcode_str (keys[i]), values[i]);
+       }
+
+       pthread_mutex_lock (&rcode_mutex);
+       for (ptr = rcode_list, len = 0;
+                       (ptr != NULL) && (len < T_MAX);
+                       ptr = ptr->next, len++)
+       {
+               keys[len]   = ptr->key;
+               values[len] = ptr->value;
+       }
+       pthread_mutex_unlock (&rcode_mutex);
+
+       for (i = 0; i < len; i++)
+       {
+               DEBUG ("dns plugin: rcode = %u; counter = %u;", keys[i], values[i]);
+               submit_derive ("dns_rcode", rcode_str (keys[i]), values[i]);
+       }
+
+       return (0);
+} /* int dns_read */
+
+void module_register (void)
+{
+       plugin_register_config ("dns", dns_config, config_keys, config_keys_num);
+       plugin_register_init ("dns", dns_init);
+       plugin_register_read ("dns", dns_read);
+} /* void module_register */
diff --git a/src/email.c b/src/email.c
new file mode 100644 (file)
index 0000000..8fc5509
--- /dev/null
@@ -0,0 +1,771 @@
+/**
+ * collectd - src/email.c
+ * Copyright (C) 2006-2008  Sebastian Harl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Sebastian Harl <sh at tokkee.org>
+ **/
+
+/*
+ * This plugin communicates with a spam filter, a virus scanner or similar
+ * software using a UNIX socket and a very simple protocol:
+ *
+ * e-mail type (e.g. ham, spam, virus, ...) and size
+ * e:<type>:<bytes>
+ *
+ * spam score
+ * s:<value>
+ *
+ * successful spam checks
+ * c:<type1>[,<type2>,...]
+ */
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include "configfile.h"
+
+#include <stddef.h>
+
+#if HAVE_LIBPTHREAD
+# include <pthread.h>
+#endif
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/select.h>
+
+/* some systems (e.g. Darwin) seem to not define UNIX_PATH_MAX at all */
+#ifndef UNIX_PATH_MAX
+# define UNIX_PATH_MAX sizeof (((struct sockaddr_un *)0)->sun_path)
+#endif /* UNIX_PATH_MAX */
+
+#if HAVE_GRP_H
+#      include <grp.h>
+#endif /* HAVE_GRP_H */
+
+#define SOCK_PATH LOCALSTATEDIR"/run/"PACKAGE_NAME"-email"
+#define MAX_CONNS 5
+#define MAX_CONNS_LIMIT 16384
+
+#define log_debug(...) DEBUG ("email: "__VA_ARGS__)
+#define log_err(...) ERROR ("email: "__VA_ARGS__)
+#define log_warn(...) WARNING ("email: "__VA_ARGS__)
+
+/*
+ * Private data structures
+ */
+/* linked list of email and check types */
+typedef struct type {
+       char        *name;
+       int         value;
+       struct type *next;
+} type_t;
+
+typedef struct {
+       type_t *head;
+       type_t *tail;
+} type_list_t;
+
+/* collector thread control information */
+typedef struct collector {
+       pthread_t thread;
+
+       /* socket descriptor of the current/last connection */
+       FILE *socket;
+} collector_t;
+
+/* linked list of pending connections */
+typedef struct conn {
+       /* socket to read data from */
+       FILE *socket;
+
+       /* linked list of connections */
+       struct conn *next;
+} conn_t;
+
+typedef struct {
+       conn_t *head;
+       conn_t *tail;
+} conn_list_t;
+
+/*
+ * Private variables
+ */
+/* valid configuration file keys */
+static const char *config_keys[] =
+{
+       "SocketFile",
+       "SocketGroup",
+       "SocketPerms",
+       "MaxConns"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+/* socket configuration */
+static char *sock_file  = NULL;
+static char *sock_group = NULL;
+static int  sock_perms  = S_IRWXU | S_IRWXG;
+static int  max_conns   = MAX_CONNS;
+
+/* state of the plugin */
+static int disabled = 0;
+
+/* thread managing "client" connections */
+static pthread_t connector = (pthread_t) 0;
+static int connector_socket = -1;
+
+/* tell the collector threads that a new connection is available */
+static pthread_cond_t conn_available = PTHREAD_COND_INITIALIZER;
+
+/* connections that are waiting to be processed */
+static pthread_mutex_t conns_mutex = PTHREAD_MUTEX_INITIALIZER;
+static conn_list_t conns;
+
+/* tell the connector thread that a collector is available */
+static pthread_cond_t collector_available = PTHREAD_COND_INITIALIZER;
+
+/* collector threads */
+static collector_t **collectors = NULL;
+
+static pthread_mutex_t available_mutex = PTHREAD_MUTEX_INITIALIZER;
+static int available_collectors;
+
+static pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER;
+static type_list_t list_count;
+static type_list_t list_count_copy;
+
+static pthread_mutex_t size_mutex = PTHREAD_MUTEX_INITIALIZER;
+static type_list_t list_size;
+static type_list_t list_size_copy;
+
+static pthread_mutex_t score_mutex = PTHREAD_MUTEX_INITIALIZER;
+static double score;
+static int score_count;
+
+static pthread_mutex_t check_mutex = PTHREAD_MUTEX_INITIALIZER;
+static type_list_t list_check;
+static type_list_t list_check_copy;
+
+/*
+ * Private functions
+ */
+static int email_config (const char *key, const char *value)
+{
+       if (0 == strcasecmp (key, "SocketFile")) {
+               if (NULL != sock_file)
+                       free (sock_file);
+               sock_file = sstrdup (value);
+       }
+       else if (0 == strcasecmp (key, "SocketGroup")) {
+               if (NULL != sock_group)
+                       free (sock_group);
+               sock_group = sstrdup (value);
+       }
+       else if (0 == strcasecmp (key, "SocketPerms")) {
+               /* the user is responsible for providing reasonable values */
+               sock_perms = (int)strtol (value, NULL, 8);
+       }
+       else if (0 == strcasecmp (key, "MaxConns")) {
+               long int tmp = strtol (value, NULL, 0);
+
+               if (tmp < 1) {
+                       fprintf (stderr, "email plugin: `MaxConns' was set to invalid "
+                                       "value %li, will use default %i.\n",
+                                       tmp, MAX_CONNS);
+                       ERROR ("email plugin: `MaxConns' was set to invalid "
+                                       "value %li, will use default %i.\n",
+                                       tmp, MAX_CONNS);
+                       max_conns = MAX_CONNS;
+               }
+               else if (tmp > MAX_CONNS_LIMIT) {
+                       fprintf (stderr, "email plugin: `MaxConns' was set to invalid "
+                                       "value %li, will use hardcoded limit %i.\n",
+                                       tmp, MAX_CONNS_LIMIT);
+                       ERROR ("email plugin: `MaxConns' was set to invalid "
+                                       "value %li, will use hardcoded limit %i.\n",
+                                       tmp, MAX_CONNS_LIMIT);
+                       max_conns = MAX_CONNS_LIMIT;
+               }
+               else {
+                       max_conns = (int)tmp;
+               }
+       }
+       else {
+               return -1;
+       }
+       return 0;
+} /* static int email_config (char *, char *) */
+
+/* Increment the value of the given name in the given list by incr. */
+static void type_list_incr (type_list_t *list, char *name, int incr)
+{
+       if (NULL == list->head) {
+               list->head = (type_t *)smalloc (sizeof (type_t));
+
+               list->head->name  = sstrdup (name);
+               list->head->value = incr;
+               list->head->next  = NULL;
+
+               list->tail = list->head;
+       }
+       else {
+               type_t *ptr;
+
+               for (ptr = list->head; NULL != ptr; ptr = ptr->next) {
+                       if (0 == strcmp (name, ptr->name))
+                               break;
+               }
+
+               if (NULL == ptr) {
+                       list->tail->next = (type_t *)smalloc (sizeof (type_t));
+                       list->tail = list->tail->next;
+
+                       list->tail->name  = sstrdup (name);
+                       list->tail->value = incr;
+                       list->tail->next  = NULL;
+               }
+               else {
+                       ptr->value += incr;
+               }
+       }
+       return;
+} /* static void type_list_incr (type_list_t *, char *) */
+
+static void *collect (void *arg)
+{
+       collector_t *this = (collector_t *)arg;
+
+       while (1) {
+               int loop = 1;
+
+               conn_t *connection;
+
+               pthread_mutex_lock (&conns_mutex);
+
+               while (NULL == conns.head) {
+                       pthread_cond_wait (&conn_available, &conns_mutex);
+               }
+
+               connection = conns.head;
+               conns.head = conns.head->next;
+
+               if (NULL == conns.head) {
+                       conns.tail = NULL;
+               }
+
+               pthread_mutex_unlock (&conns_mutex);
+
+               /* make the socket available to the global
+                * thread and connection management */
+               this->socket = connection->socket;
+
+               log_debug ("collect: handling connection on fd #%i",
+                               fileno (this->socket));
+
+               while (loop) {
+                       /* 256 bytes ought to be enough for anybody ;-) */
+                       char line[256 + 1]; /* line + '\0' */
+                       int  len = 0;
+
+                       errno = 0;
+                       if (NULL == fgets (line, sizeof (line), this->socket)) {
+                               loop = 0;
+
+                               if (0 != errno) {
+                                       char errbuf[1024];
+                                       log_err ("collect: reading from socket (fd #%i) "
+                                                       "failed: %s", fileno (this->socket),
+                                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                               }
+                               break;
+                       }
+
+                       len = strlen (line);
+                       if (('\n' != line[len - 1]) && ('\r' != line[len - 1])) {
+                               log_warn ("collect: line too long (> %zu characters): "
+                                               "'%s' (truncated)", sizeof (line) - 1, line);
+
+                               while (NULL != fgets (line, sizeof (line), this->socket))
+                                       if (('\n' == line[len - 1]) || ('\r' == line[len - 1]))
+                                               break;
+                               continue;
+                       }
+
+                       line[len - 1] = '\0';
+
+                       log_debug ("collect: line = '%s'", line);
+
+                       if (':' != line[1]) {
+                               log_err ("collect: syntax error in line '%s'", line);
+                               continue;
+                       }
+
+                       if ('e' == line[0]) { /* e:<type>:<bytes> */
+                               char *ptr  = NULL;
+                               char *type = strtok_r (line + 2, ":", &ptr);
+                               char *tmp  = strtok_r (NULL, ":", &ptr);
+                               int  bytes = 0;
+
+                               if (NULL == tmp) {
+                                       log_err ("collect: syntax error in line '%s'", line);
+                                       continue;
+                               }
+
+                               bytes = atoi (tmp);
+
+                               pthread_mutex_lock (&count_mutex);
+                               type_list_incr (&list_count, type, 1);
+                               pthread_mutex_unlock (&count_mutex);
+
+                               if (bytes > 0) {
+                                       pthread_mutex_lock (&size_mutex);
+                                       type_list_incr (&list_size, type, bytes);
+                                       pthread_mutex_unlock (&size_mutex);
+                               }
+                       }
+                       else if ('s' == line[0]) { /* s:<value> */
+                               pthread_mutex_lock (&score_mutex);
+                               score = (score * (double)score_count + atof (line + 2))
+                                               / (double)(score_count + 1);
+                               ++score_count;
+                               pthread_mutex_unlock (&score_mutex);
+                       }
+                       else if ('c' == line[0]) { /* c:<type1>[,<type2>,...] */
+                               char *ptr  = NULL;
+                               char *type = strtok_r (line + 2, ",", &ptr);
+
+                               do {
+                                       pthread_mutex_lock (&check_mutex);
+                                       type_list_incr (&list_check, type, 1);
+                                       pthread_mutex_unlock (&check_mutex);
+                               } while (NULL != (type = strtok_r (NULL, ",", &ptr)));
+                       }
+                       else {
+                               log_err ("collect: unknown type '%c'", line[0]);
+                       }
+               } /* while (loop) */
+
+               log_debug ("Shutting down connection on fd #%i",
+                               fileno (this->socket));
+
+               fclose (connection->socket);
+               free (connection);
+
+               this->socket = NULL;
+
+               pthread_mutex_lock (&available_mutex);
+               ++available_collectors;
+               pthread_mutex_unlock (&available_mutex);
+
+               pthread_cond_signal (&collector_available);
+       } /* while (1) */
+
+       pthread_exit ((void *)0);
+} /* static void *collect (void *) */
+
+static void *open_connection (void __attribute__((unused)) *arg)
+{
+       struct sockaddr_un addr;
+
+       char *path  = (NULL == sock_file) ? SOCK_PATH : sock_file;
+       char *group = (NULL == sock_group) ? COLLECTD_GRP_NAME : sock_group;
+
+       /* create UNIX socket */
+       errno = 0;
+       if (-1 == (connector_socket = socket (PF_UNIX, SOCK_STREAM, 0))) {
+               char errbuf[1024];
+               disabled = 1;
+               log_err ("socket() failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               pthread_exit ((void *)1);
+       }
+
+       addr.sun_family = AF_UNIX;
+       sstrncpy (addr.sun_path, path, (size_t)(UNIX_PATH_MAX - 1));
+
+       errno = 0;
+       if (-1 == bind (connector_socket, (struct sockaddr *)&addr,
+                               offsetof (struct sockaddr_un, sun_path)
+                                       + strlen(addr.sun_path))) {
+               char errbuf[1024];
+               disabled = 1;
+               close (connector_socket);
+               connector_socket = -1;
+               log_err ("bind() failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               pthread_exit ((void *)1);
+       }
+
+       errno = 0;
+       if (-1 == listen (connector_socket, 5)) {
+               char errbuf[1024];
+               disabled = 1;
+               close (connector_socket);
+               connector_socket = -1;
+               log_err ("listen() failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               pthread_exit ((void *)1);
+       }
+
+       {
+               struct group sg;
+               struct group *grp;
+               char grbuf[2048];
+               int status;
+
+               grp = NULL;
+               status = getgrnam_r (group, &sg, grbuf, sizeof (grbuf), &grp);
+               if (status != 0)
+               {
+                       char errbuf[1024];
+                       log_warn ("getgrnam_r (%s) failed: %s", group,
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+               }
+               else if (grp == NULL)
+               {
+                       log_warn ("No such group: `%s'", group);
+               }
+               else
+               {
+                       status = chown (path, (uid_t) -1, grp->gr_gid);
+                       if (status != 0)
+                       {
+                               char errbuf[1024];
+                               log_warn ("chown (%s, -1, %i) failed: %s",
+                                               path, (int) grp->gr_gid,
+                                               sstrerror (errno, errbuf, sizeof (errbuf)));
+                       }
+               }
+       }
+
+       errno = 0;
+       if (0 != chmod (path, sock_perms)) {
+               char errbuf[1024];
+               log_warn ("chmod() failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+       }
+
+       { /* initialize collector threads */
+               int i   = 0;
+               int err = 0;
+
+               pthread_attr_t ptattr;
+
+               conns.head = NULL;
+               conns.tail = NULL;
+
+               pthread_attr_init (&ptattr);
+               pthread_attr_setdetachstate (&ptattr, PTHREAD_CREATE_DETACHED);
+
+               available_collectors = max_conns;
+
+               collectors =
+                       (collector_t **)smalloc (max_conns * sizeof (collector_t *));
+
+               for (i = 0; i < max_conns; ++i) {
+                       collectors[i] = (collector_t *)smalloc (sizeof (collector_t));
+                       collectors[i]->socket = NULL;
+
+                       if (0 != (err = pthread_create (&collectors[i]->thread, &ptattr,
+                                                       collect, collectors[i]))) {
+                               char errbuf[1024];
+                               log_err ("pthread_create() failed: %s",
+                                               sstrerror (errno, errbuf, sizeof (errbuf)));
+                               collectors[i]->thread = (pthread_t) 0;
+                       }
+               }
+
+               pthread_attr_destroy (&ptattr);
+       }
+
+       while (1) {
+               int remote = 0;
+
+               conn_t *connection;
+
+               pthread_mutex_lock (&available_mutex);
+
+               while (0 == available_collectors) {
+                       pthread_cond_wait (&collector_available, &available_mutex);
+               }
+
+               --available_collectors;
+
+               pthread_mutex_unlock (&available_mutex);
+
+               do {
+                       errno = 0;
+                       if (-1 == (remote = accept (connector_socket, NULL, NULL))) {
+                               if (EINTR != errno) {
+                                       char errbuf[1024];
+                                       disabled = 1;
+                                       close (connector_socket);
+                                       connector_socket = -1;
+                                       log_err ("accept() failed: %s",
+                                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                                       pthread_exit ((void *)1);
+                               }
+                       }
+               } while (EINTR == errno);
+
+               connection = (conn_t *)smalloc (sizeof (conn_t));
+
+               connection->socket = fdopen (remote, "r");
+               connection->next   = NULL;
+
+               if (NULL == connection->socket) {
+                       close (remote);
+                       continue;
+               }
+
+               pthread_mutex_lock (&conns_mutex);
+
+               if (NULL == conns.head) {
+                       conns.head = connection;
+                       conns.tail = connection;
+               }
+               else {
+                       conns.tail->next = connection;
+                       conns.tail = conns.tail->next;
+               }
+
+               pthread_mutex_unlock (&conns_mutex);
+
+               pthread_cond_signal (&conn_available);
+       }
+       pthread_exit ((void *)0);
+} /* static void *open_connection (void *) */
+
+static int email_init (void)
+{
+       int err = 0;
+
+       if (0 != (err = pthread_create (&connector, NULL,
+                               open_connection, NULL))) {
+               char errbuf[1024];
+               disabled = 1;
+               log_err ("pthread_create() failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       return (0);
+} /* int email_init */
+
+static int email_shutdown (void)
+{
+       type_t *ptr = NULL;
+
+       int i = 0;
+
+       if (connector != ((pthread_t) 0)) {
+               pthread_kill (connector, SIGTERM);
+               connector = (pthread_t) 0;
+       }
+
+       if (connector_socket >= 0) {
+               close (connector_socket);
+               connector_socket = -1;
+       }
+
+       /* don't allow any more connections to be processed */
+       pthread_mutex_lock (&conns_mutex);
+
+       available_collectors = 0;
+
+       if (collectors != NULL) {
+               for (i = 0; i < max_conns; ++i) {
+                       if (collectors[i] == NULL)
+                               continue;
+
+                       if (collectors[i]->thread != ((pthread_t) 0)) {
+                               pthread_kill (collectors[i]->thread, SIGTERM);
+                               collectors[i]->thread = (pthread_t) 0;
+                       }
+
+                       if (collectors[i]->socket != NULL) {
+                               fclose (collectors[i]->socket);
+                               collectors[i]->socket = NULL;
+                       }
+
+                       sfree (collectors[i]);
+               }
+               sfree (collectors);
+       } /* if (collectors != NULL) */
+
+       pthread_mutex_unlock (&conns_mutex);
+
+       for (ptr = list_count.head; NULL != ptr; ptr = ptr->next) {
+               free (ptr->name);
+               free (ptr);
+       }
+
+       for (ptr = list_count_copy.head; NULL != ptr; ptr = ptr->next) {
+               free (ptr->name);
+               free (ptr);
+       }
+
+       for (ptr = list_size.head; NULL != ptr; ptr = ptr->next) {
+               free (ptr->name);
+               free (ptr);
+       }
+
+       for (ptr = list_size_copy.head; NULL != ptr; ptr = ptr->next) {
+               free (ptr->name);
+               free (ptr);
+       }
+
+       for (ptr = list_check.head; NULL != ptr; ptr = ptr->next) {
+               free (ptr->name);
+               free (ptr);
+       }
+
+       for (ptr = list_check_copy.head; NULL != ptr; ptr = ptr->next) {
+               free (ptr->name);
+               free (ptr);
+       }
+
+       unlink ((NULL == sock_file) ? SOCK_PATH : sock_file);
+
+       sfree (sock_file);
+       sfree (sock_group);
+       return (0);
+} /* static void 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_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));
+
+       plugin_dispatch_values (&vl);
+} /* void email_submit */
+
+/* Copy list l1 to list l2. l2 may partly exist already, but it is assumed
+ * that neither the order nor the name of any element of either list is
+ * changed and no elements are deleted. The values of l1 are reset to zero
+ * after they have been copied to l2. */
+static void copy_type_list (type_list_t *l1, type_list_t *l2)
+{
+       type_t *ptr1;
+       type_t *ptr2;
+
+       type_t *last = NULL;
+
+       for (ptr1 = l1->head, ptr2 = l2->head; NULL != ptr1;
+                       ptr1 = ptr1->next, last = ptr2, ptr2 = ptr2->next) {
+               if (NULL == ptr2) {
+                       ptr2 = (type_t *)smalloc (sizeof (type_t));
+                       ptr2->name = NULL;
+                       ptr2->next = NULL;
+
+                       if (NULL == last) {
+                               l2->head = ptr2;
+                       }
+                       else {
+                               last->next = ptr2;
+                       }
+
+                       l2->tail = ptr2;
+               }
+
+               if (NULL == ptr2->name) {
+                       ptr2->name = sstrdup (ptr1->name);
+               }
+
+               ptr2->value = ptr1->value;
+               ptr1->value = 0;
+       }
+       return;
+}
+
+static int email_read (void)
+{
+       type_t *ptr;
+
+       double score_old;
+       int score_count_old;
+
+       if (disabled)
+               return (-1);
+
+       /* email count */
+       pthread_mutex_lock (&count_mutex);
+
+       copy_type_list (&list_count, &list_count_copy);
+
+       pthread_mutex_unlock (&count_mutex);
+
+       for (ptr = list_count_copy.head; NULL != ptr; ptr = ptr->next) {
+               email_submit ("email_count", ptr->name, ptr->value);
+       }
+
+       /* email size */
+       pthread_mutex_lock (&size_mutex);
+
+       copy_type_list (&list_size, &list_size_copy);
+
+       pthread_mutex_unlock (&size_mutex);
+
+       for (ptr = list_size_copy.head; NULL != ptr; ptr = ptr->next) {
+               email_submit ("email_size", ptr->name, ptr->value);
+       }
+
+       /* spam score */
+       pthread_mutex_lock (&score_mutex);
+
+       score_old = score;
+       score_count_old = score_count;
+       score = 0.0;
+       score_count = 0;
+
+       pthread_mutex_unlock (&score_mutex);
+
+       if (score_count_old > 0)
+               email_submit ("spam_score", "", score_old);
+
+       /* spam checks */
+       pthread_mutex_lock (&check_mutex);
+
+       copy_type_list (&list_check, &list_check_copy);
+
+       pthread_mutex_unlock (&check_mutex);
+
+       for (ptr = list_check_copy.head; NULL != ptr; ptr = ptr->next)
+               email_submit ("spam_check", ptr->name, ptr->value);
+
+       return (0);
+} /* int email_read */
+
+void module_register (void)
+{
+       plugin_register_config ("email", email_config, config_keys, config_keys_num);
+       plugin_register_init ("email", email_init);
+       plugin_register_read ("email", email_read);
+       plugin_register_shutdown ("email", email_shutdown);
+} /* void module_register */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
diff --git a/src/entropy.c b/src/entropy.c
new file mode 100644 (file)
index 0000000..d56be6d
--- /dev/null
@@ -0,0 +1,76 @@
+/**
+ * collectd - src/entropy.c
+ * Copyright (C) 2007  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#if !KERNEL_LINUX
+# error "No applicable input method."
+#endif
+
+#define ENTROPY_FILE "/proc/sys/kernel/random/entropy_avail"
+
+static void entropy_submit (double entropy)
+{
+       value_t values[1];
+       value_list_t vl = VALUE_LIST_INIT;
+
+       values[0].gauge = entropy;
+
+       vl.values = values;
+       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));
+
+       plugin_dispatch_values (&vl);
+}
+
+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);
+               return (-1);
+       }
+       fclose (fh);
+
+       entropy = atof (buffer);
+       
+       if (entropy > 0.0)
+               entropy_submit (entropy);
+
+       return (0);
+}
+
+void module_register (void)
+{
+       plugin_register_read ("entropy", entropy_read);
+} /* void module_register */
diff --git a/src/exec.c b/src/exec.c
new file mode 100644 (file)
index 0000000..4f6f9df
--- /dev/null
@@ -0,0 +1,903 @@
+/**
+ * collectd - src/exec.c
+ * Copyright (C) 2007-2010  Florian octo Forster
+ * Copyright (C) 2007-2009  Sebastian Harl
+ * Copyright (C) 2008       Peter Holik
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ *   Sebastian Harl <sh at tokkee.org>
+ *   Peter Holik <peter at holik.at>
+ **/
+
+#define _BSD_SOURCE /* For setgroups */
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include "utils_cmd_putval.h"
+#include "utils_cmd_putnotif.h"
+
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+#include <signal.h>
+
+#include <pthread.h>
+
+#define PL_NORMAL        0x01
+#define PL_NOTIF_ACTION  0x02
+
+#define PL_RUNNING       0x10
+
+/*
+ * Private data types
+ */
+/*
+ * Access to this structure is serialized using the `pl_lock' lock and the
+ * `PL_RUNNING' flag. The execution of notifications is *not* serialized, so
+ * all functions used to handle notifications MUST NOT write to this structure.
+ * The `pid' and `status' fields are thus unused if the `PL_NOTIF_ACTION' flag
+ * is set.
+ * The `PL_RUNNING' flag is set in `exec_read' and unset in `exec_read_one'.
+ */
+struct program_list_s;
+typedef struct program_list_s program_list_t;
+struct program_list_s
+{
+  char           *user;
+  char           *group;
+  char           *exec;
+  char          **argv;
+  int             pid;
+  int             status;
+  int             flags;
+  program_list_t *next;
+};
+
+typedef struct program_list_and_notification_s
+{
+  program_list_t *pl;
+  notification_t n;
+} program_list_and_notification_t;
+
+/*
+ * Private variables
+ */
+static program_list_t *pl_head = NULL;
+static pthread_mutex_t pl_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/*
+ * Functions
+ */
+static void sigchld_handler (int __attribute__((unused)) signal) /* {{{ */
+{
+  pid_t pid;
+  int status;
+  while ((pid = waitpid (-1, &status, WNOHANG)) > 0)
+  {
+    program_list_t *pl;
+    for (pl = pl_head; pl != NULL; pl = pl->next)
+      if (pl->pid == pid)
+       break;
+    if (pl != NULL)
+      pl->status = status;
+  } /* while (waitpid) */
+} /* void sigchld_handler }}} */
+
+static int exec_config_exec (oconfig_item_t *ci) /* {{{ */
+{
+  program_list_t *pl;
+  char buffer[128];
+  int i;
+
+  if (ci->children_num != 0)
+  {
+    WARNING ("exec plugin: The config option `%s' may not be a block.",
+       ci->key);
+    return (-1);
+  }
+  if (ci->values_num < 2)
+  {
+    WARNING ("exec plugin: The config option `%s' needs at least two "
+       "arguments.", ci->key);
+    return (-1);
+  }
+  if ((ci->values[0].type != OCONFIG_TYPE_STRING)
+      || (ci->values[1].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("exec plugin: The first two arguments to the `%s' option must "
+       "be string arguments.", ci->key);
+    return (-1);
+  }
+
+  pl = (program_list_t *) malloc (sizeof (program_list_t));
+  if (pl == NULL)
+  {
+    ERROR ("exec plugin: malloc failed.");
+    return (-1);
+  }
+  memset (pl, '\0', sizeof (program_list_t));
+
+  if (strcasecmp ("NotificationExec", ci->key) == 0)
+    pl->flags |= PL_NOTIF_ACTION;
+  else
+    pl->flags |= PL_NORMAL;
+
+  pl->user = strdup (ci->values[0].value.string);
+  if (pl->user == NULL)
+  {
+    ERROR ("exec plugin: strdup failed.");
+    sfree (pl);
+    return (-1);
+  }
+
+  pl->group = strchr (pl->user, ':');
+  if (pl->group != NULL)
+  {
+    *pl->group = '\0';
+    pl->group++;
+  }
+
+  pl->exec = strdup (ci->values[1].value.string);
+  if (pl->exec == NULL)
+  {
+    ERROR ("exec plugin: strdup failed.");
+    sfree (pl->user);
+    sfree (pl);
+    return (-1);
+  }
+
+  pl->argv = (char **) malloc (ci->values_num * sizeof (char *));
+  if (pl->argv == NULL)
+  {
+    ERROR ("exec plugin: malloc failed.");
+    sfree (pl->exec);
+    sfree (pl->user);
+    sfree (pl);
+    return (-1);
+  }
+  memset (pl->argv, '\0', ci->values_num * sizeof (char *));
+
+  {
+    char *tmp = strrchr (ci->values[1].value.string, '/');
+    if (tmp == NULL)
+      sstrncpy (buffer, ci->values[1].value.string, sizeof (buffer));
+    else
+      sstrncpy (buffer, tmp + 1, sizeof (buffer));
+  }
+  pl->argv[0] = strdup (buffer);
+  if (pl->argv[0] == NULL)
+  {
+    ERROR ("exec plugin: malloc failed.");
+    sfree (pl->argv);
+    sfree (pl->exec);
+    sfree (pl->user);
+    sfree (pl);
+    return (-1);
+  }
+
+  for (i = 1; i < (ci->values_num - 1); i++)
+  {
+    if (ci->values[i + 1].type == OCONFIG_TYPE_STRING)
+    {
+      pl->argv[i] = strdup (ci->values[i + 1].value.string);
+    }
+    else
+    {
+      if (ci->values[i + 1].type == OCONFIG_TYPE_NUMBER)
+      {
+       ssnprintf (buffer, sizeof (buffer), "%lf",
+           ci->values[i + 1].value.number);
+      }
+      else
+      {
+       if (ci->values[i + 1].value.boolean)
+         sstrncpy (buffer, "true", sizeof (buffer));
+       else
+         sstrncpy (buffer, "false", sizeof (buffer));
+      }
+
+      pl->argv[i] = strdup (buffer);
+    }
+
+    if (pl->argv[i] == NULL)
+    {
+      ERROR ("exec plugin: strdup failed.");
+      break;
+    }
+  } /* for (i) */
+
+  if (i < (ci->values_num - 1))
+  {
+    while ((--i) >= 0)
+    {
+      sfree (pl->argv[i]);
+    }
+    sfree (pl->argv);
+    sfree (pl->exec);
+    sfree (pl->user);
+    sfree (pl);
+    return (-1);
+  }
+
+  for (i = 0; pl->argv[i] != NULL; i++)
+  {
+    DEBUG ("exec plugin: argv[%i] = %s", i, pl->argv[i]);
+  }
+
+  pl->next = pl_head;
+  pl_head = pl;
+
+  return (0);
+} /* int exec_config_exec }}} */
+
+static int exec_config (oconfig_item_t *ci) /* {{{ */
+{
+  int i;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+    if ((strcasecmp ("Exec", child->key) == 0)
+       || (strcasecmp ("NotificationExec", child->key) == 0))
+      exec_config_exec (child);
+    else
+    {
+      WARNING ("exec plugin: Unknown config option `%s'.", child->key);
+    }
+  } /* for (i) */
+
+  return (0);
+} /* int exec_config }}} */
+
+static void set_environment (void) /* {{{ */
+{
+  char buffer[1024];
+
+#ifdef HAVE_SETENV
+  ssnprintf (buffer, sizeof (buffer), "%.3f", CDTIME_T_TO_DOUBLE (interval_g));
+  setenv ("COLLECTD_INTERVAL", buffer, /* overwrite = */ 1);
+
+  ssnprintf (buffer, sizeof (buffer), "%s", hostname_g);
+  setenv ("COLLECTD_HOSTNAME", buffer, /* overwrite = */ 1);
+#else
+  ssnprintf (buffer, sizeof (buffer), "COLLECTD_INTERVAL=%.3f",
+      CDTIME_T_TO_DOUBLE (interval_g));
+  putenv (buffer);
+
+  ssnprintf (buffer, sizeof (buffer), "COLLECTD_HOSTNAME=%s", hostname_g);
+  putenv (buffer);
+#endif
+} /* }}} void set_environment */
+
+__attribute__((noreturn))
+static void exec_child (program_list_t *pl) /* {{{ */
+{
+  int status;
+  int uid;
+  int gid;
+  int egid;
+
+  struct passwd *sp_ptr;
+  struct passwd sp;
+  char nambuf[2048];
+  char errbuf[1024];
+
+  sp_ptr = NULL;
+  status = getpwnam_r (pl->user, &sp, nambuf, sizeof (nambuf), &sp_ptr);
+  if (status != 0)
+  {
+    ERROR ("exec plugin: Failed to get user information for user ``%s'': %s",
+       pl->user, sstrerror (errno, errbuf, sizeof (errbuf)));
+    exit (-1);
+  }
+  if (sp_ptr == NULL)
+  {
+    ERROR ("exec plugin: No such user: `%s'", pl->user);
+    exit (-1);
+  }
+
+  uid = sp.pw_uid;
+  gid = sp.pw_gid;
+  if (uid == 0)
+  {
+    ERROR ("exec plugin: Cowardly refusing to exec program as root.");
+    exit (-1);
+  }
+
+  /* The group configured in the configfile is set as effective group, because
+   * this way the forked process can (re-)gain the user's primary group. */
+  egid = -1;
+  if (NULL != pl->group)
+  {
+    if ('\0' != *pl->group) {
+      struct group *gr_ptr = NULL;
+      struct group gr;
+
+      status = getgrnam_r (pl->group, &gr, nambuf, sizeof (nambuf), &gr_ptr);
+      if (0 != status)
+      {
+       ERROR ("exec plugin: Failed to get group information "
+           "for group ``%s'': %s", pl->group,
+           sstrerror (errno, errbuf, sizeof (errbuf)));
+       exit (-1);
+      }
+      if (NULL == gr_ptr)
+      {
+       ERROR ("exec plugin: No such group: `%s'", pl->group);
+       exit (-1);
+      }
+
+      egid = gr.gr_gid;
+    }
+    else
+    {
+      egid = gid;
+    }
+  } /* if (pl->group == NULL) */
+
+#if HAVE_SETGROUPS
+  if (getuid () == 0)
+  {
+    gid_t  glist[2];
+    size_t glist_len;
+
+    glist[0] = gid;
+    glist_len = 1;
+
+    if ((gid != egid) && (egid != -1))
+    {
+      glist[1] = egid;
+      glist_len = 2;
+    }
+
+    setgroups (glist_len, glist);
+  }
+#endif /* HAVE_SETGROUPS */
+
+  status = setgid (gid);
+  if (status != 0)
+  {
+    ERROR ("exec plugin: setgid (%i) failed: %s",
+       gid, sstrerror (errno, errbuf, sizeof (errbuf)));
+    exit (-1);
+  }
+
+  if (egid != -1)
+  {
+    status = setegid (egid);
+    if (status != 0)
+    {
+      ERROR ("exec plugin: setegid (%i) failed: %s",
+         egid, sstrerror (errno, errbuf, sizeof (errbuf)));
+      exit (-1);
+    }
+  }
+
+  status = setuid (uid);
+  if (status != 0)
+  {
+    ERROR ("exec plugin: setuid (%i) failed: %s",
+       uid, sstrerror (errno, errbuf, sizeof (errbuf)));
+    exit (-1);
+  }
+
+  status = execvp (pl->exec, pl->argv);
+
+  ERROR ("exec plugin: Failed to execute ``%s'': %s",
+      pl->exec, sstrerror (errno, errbuf, sizeof (errbuf)));
+  exit (-1);
+} /* void exec_child }}} */
+
+static void reset_signal_mask (void) /* {{{ */
+{
+  sigset_t ss;
+
+  memset (&ss, 0, sizeof (ss));
+  sigemptyset (&ss);
+  sigprocmask (SIG_SETMASK, &ss, /* old mask = */ NULL);
+} /* }}} void reset_signal_mask */
+
+/*
+ * Creates three pipes (one for reading, one for writing and one for errors),
+ * forks a child, sets up the pipes so that fd_in is connected to STDIN of
+ * the child and fd_out is connected to STDOUT and fd_err is connected to STDERR
+ * of the child. Then is calls `exec_child'.
+ */
+static int fork_child (program_list_t *pl, int *fd_in, int *fd_out, int *fd_err) /* {{{ */
+{
+  int fd_pipe_in[2];
+  int fd_pipe_out[2];
+  int fd_pipe_err[2];
+  char errbuf[1024];
+  int status;
+  int pid;
+
+  if (pl->pid != 0)
+    return (-1);
+
+  status = pipe (fd_pipe_in);
+  if (status != 0)
+  {
+    ERROR ("exec plugin: pipe failed: %s",
+       sstrerror (errno, errbuf, sizeof (errbuf)));
+    return (-1);
+  }
+
+  status = pipe (fd_pipe_out);
+  if (status != 0)
+  {
+    ERROR ("exec plugin: pipe failed: %s",
+       sstrerror (errno, errbuf, sizeof (errbuf)));
+    return (-1);
+  }
+
+  status = pipe (fd_pipe_err);
+  if (status != 0)
+  {
+    ERROR ("exec plugin: pipe failed: %s",
+       sstrerror (errno, errbuf, sizeof (errbuf)));
+    return (-1);
+  }
+
+  pid = fork ();
+  if (pid < 0)
+  {
+    ERROR ("exec plugin: fork failed: %s",
+       sstrerror (errno, errbuf, sizeof (errbuf)));
+    return (-1);
+  }
+  else if (pid == 0)
+  {
+    int fd_num;
+    int fd;
+
+    /* Close all file descriptors but the pipe end we need. */
+    fd_num = getdtablesize ();
+    for (fd = 0; fd < fd_num; fd++)
+    {
+      if ((fd == fd_pipe_in[0])
+         || (fd == fd_pipe_out[1])
+         || (fd == fd_pipe_err[1]))
+       continue;
+      close (fd);
+    }
+
+    /* Connect the `in' pipe to STDIN */
+    if (fd_pipe_in[0] != STDIN_FILENO)
+    {
+      dup2 (fd_pipe_in[0], STDIN_FILENO);
+      close (fd_pipe_in[0]);
+    }
+
+    /* Now connect the `out' pipe to STDOUT */
+    if (fd_pipe_out[1] != STDOUT_FILENO)
+    {
+      dup2 (fd_pipe_out[1], STDOUT_FILENO);
+      close (fd_pipe_out[1]);
+    }
+
+    /* Now connect the `out' pipe to STDOUT */
+    if (fd_pipe_err[1] != STDERR_FILENO)
+    {
+      dup2 (fd_pipe_err[1], STDERR_FILENO);
+      close (fd_pipe_err[1]);
+    }
+
+    set_environment ();
+
+    /* Unblock all signals */
+    reset_signal_mask ();
+
+    exec_child (pl);
+    /* does not return */
+  }
+
+  close (fd_pipe_in[0]);
+  close (fd_pipe_out[1]);
+  close (fd_pipe_err[1]);
+
+  if (fd_in != NULL)
+    *fd_in = fd_pipe_in[1];
+  else
+    close (fd_pipe_in[1]);
+
+  if (fd_out != NULL)
+    *fd_out = fd_pipe_out[0];
+  else
+    close (fd_pipe_out[0]);
+
+  if (fd_err != NULL)
+    *fd_err = fd_pipe_err[0];
+  else
+    close (fd_pipe_err[0]);
+
+  return (pid);
+} /* int fork_child }}} */
+
+static int parse_line (char *buffer) /* {{{ */
+{
+  if (strncasecmp ("PUTVAL", buffer, strlen ("PUTVAL")) == 0)
+    return (handle_putval (stdout, buffer));
+  else if (strncasecmp ("PUTNOTIF", buffer, strlen ("PUTNOTIF")) == 0)
+    return (handle_putnotif (stdout, buffer));
+  else
+  {
+    ERROR ("exec plugin: Unable to parse command, ignoring line: \"%s\"",
+       buffer);
+    return (-1);
+  }
+} /* int parse_line }}} */
+
+static void *exec_read_one (void *arg) /* {{{ */
+{
+  program_list_t *pl = (program_list_t *) arg;
+  int fd, fd_err, highest_fd;
+  fd_set fdset, copy;
+  int status;
+  char buffer[1200];  /* if not completely read */
+  char buffer_err[1024];
+  char *pbuffer = buffer;
+  char *pbuffer_err = buffer_err;
+
+  status = fork_child (pl, NULL, &fd, &fd_err);
+  if (status < 0)
+  {
+    /* Reset the "running" flag */
+    pthread_mutex_lock (&pl_lock);
+    pl->flags &= ~PL_RUNNING;
+    pthread_mutex_unlock (&pl_lock);
+    pthread_exit ((void *) 1);
+  }
+  pl->pid = status;
+
+  assert (pl->pid != 0);
+
+  FD_ZERO( &fdset );
+  FD_SET(fd, &fdset);
+  FD_SET(fd_err, &fdset);
+
+  /* Determine the highest file descriptor */
+  highest_fd = (fd > fd_err) ? fd : fd_err;
+
+  /* We use a copy of fdset, as select modifies it */
+  copy = fdset;
+
+  while (select(highest_fd + 1, &copy, NULL, NULL, NULL ) > 0)
+  {
+    int len;
+
+    if (FD_ISSET(fd, &copy))
+    {
+      char *pnl;
+
+      len = read(fd, pbuffer, sizeof(buffer) - 1 - (pbuffer - buffer));
+
+      if (len < 0)
+      {
+        if (errno == EAGAIN || errno == EINTR)  continue;
+        break;
+      }
+      else if (len == 0) break;  /* We've reached EOF */
+
+      pbuffer[len] = '\0';
+
+      len += pbuffer - buffer;
+      pbuffer = buffer;
+
+      while ((pnl = strchr(pbuffer, '\n')))
+      {
+        *pnl = '\0';
+        if (*(pnl-1) == '\r' ) *(pnl-1) = '\0';
+
+        parse_line (pbuffer);
+
+        pbuffer = ++pnl;
+      }
+      /* not completely read ? */
+      if (pbuffer - buffer < len)
+      {
+        len -= pbuffer - buffer;
+        memmove(buffer, pbuffer, len);
+        pbuffer = buffer + len;
+      }
+      else
+        pbuffer = buffer;
+    }
+    else if (FD_ISSET(fd_err, &copy))
+    {
+      char *pnl;
+
+      len = read(fd_err, pbuffer_err, sizeof(buffer_err) - 1 - (pbuffer_err - buffer_err));
+
+      if (len < 0)
+      {
+        if (errno == EAGAIN || errno == EINTR)  continue;
+        break;
+      }
+      else if (len == 0)
+      {
+       /* We've reached EOF */
+       NOTICE ("exec plugin: Program `%s' has closed STDERR.",
+           pl->exec);
+       close (fd_err);
+       FD_CLR (fd_err, &fdset);
+       highest_fd = fd;
+       fd_err = -1;
+       continue;
+      }
+
+      pbuffer_err[len] = '\0';
+
+      len += pbuffer_err - buffer_err;
+      pbuffer_err = buffer_err;
+
+      while ((pnl = strchr(pbuffer_err, '\n')))
+      {
+        *pnl = '\0';
+        if (*(pnl-1) == '\r' ) *(pnl-1) = '\0';
+
+        ERROR ("exec plugin: exec_read_one: error = %s", pbuffer_err);
+
+        pbuffer_err = ++pnl;
+      }
+      /* not completely read ? */
+      if (pbuffer_err - buffer_err < len)
+      {
+        len -= pbuffer_err - buffer_err;
+        memmove(buffer_err, pbuffer_err, len);
+        pbuffer_err = buffer_err + len;
+      }
+      else
+        pbuffer_err = buffer_err;
+    }
+    /* reset copy */
+    copy = fdset;
+  }
+
+  DEBUG ("exec plugin: exec_read_one: Waiting for `%s' to exit.", pl->exec);
+  if (waitpid (pl->pid, &status, 0) > 0)
+    pl->status = status;
+
+  DEBUG ("exec plugin: Child %i exited with status %i.",
+      (int) pl->pid, pl->status);
+
+  pl->pid = 0;
+
+  pthread_mutex_lock (&pl_lock);
+  pl->flags &= ~PL_RUNNING;
+  pthread_mutex_unlock (&pl_lock);
+
+  close (fd);
+  if (fd_err >= 0)
+    close (fd_err);
+
+  pthread_exit ((void *) 0);
+  return (NULL);
+} /* void *exec_read_one }}} */
+
+static void *exec_notification_one (void *arg) /* {{{ */
+{
+  program_list_t *pl = ((program_list_and_notification_t *) arg)->pl;
+  notification_t *n = &((program_list_and_notification_t *) arg)->n;
+  notification_meta_t *meta;
+  int fd;
+  FILE *fh;
+  int pid;
+  int status;
+  const char *severity;
+
+  pid = fork_child (pl, &fd, NULL, NULL);
+  if (pid < 0) {
+    sfree (arg);
+    pthread_exit ((void *) 1);
+  }
+
+  fh = fdopen (fd, "w");
+  if (fh == NULL)
+  {
+    char errbuf[1024];
+    ERROR ("exec plugin: fdopen (%i) failed: %s", fd,
+       sstrerror (errno, errbuf, sizeof (errbuf)));
+    kill (pl->pid, SIGTERM);
+    pl->pid = 0;
+    close (fd);
+    sfree (arg);
+    pthread_exit ((void *) 1);
+  }
+
+  severity = "FAILURE";
+  if (n->severity == NOTIF_WARNING)
+    severity = "WARNING";
+  else if (n->severity == NOTIF_OKAY)
+    severity = "OKAY";
+
+  fprintf (fh,
+      "Severity: %s\n"
+      "Time: %.3f\n",
+      severity, CDTIME_T_TO_DOUBLE (n->time));
+
+  /* Print the optional fields */
+  if (strlen (n->host) > 0)
+    fprintf (fh, "Host: %s\n", n->host);
+  if (strlen (n->plugin) > 0)
+    fprintf (fh, "Plugin: %s\n", n->plugin);
+  if (strlen (n->plugin_instance) > 0)
+    fprintf (fh, "PluginInstance: %s\n", n->plugin_instance);
+  if (strlen (n->type) > 0)
+    fprintf (fh, "Type: %s\n", n->type);
+  if (strlen (n->type_instance) > 0)
+    fprintf (fh, "TypeInstance: %s\n", n->type_instance);
+
+  for (meta = n->meta; meta != NULL; meta = meta->next)
+  {
+    if (meta->type == NM_TYPE_STRING)
+      fprintf (fh, "%s: %s\n", meta->name, meta->nm_value.nm_string);
+    else if (meta->type == NM_TYPE_SIGNED_INT)
+      fprintf (fh, "%s: %"PRIi64"\n", meta->name, meta->nm_value.nm_signed_int);
+    else if (meta->type == NM_TYPE_UNSIGNED_INT)
+      fprintf (fh, "%s: %"PRIu64"\n", meta->name, meta->nm_value.nm_unsigned_int);
+    else if (meta->type == NM_TYPE_DOUBLE)
+      fprintf (fh, "%s: %e\n", meta->name, meta->nm_value.nm_double);
+    else if (meta->type == NM_TYPE_BOOLEAN)
+      fprintf (fh, "%s: %s\n", meta->name,
+         meta->nm_value.nm_boolean ? "true" : "false");
+  }
+
+  fprintf (fh, "\n%s\n", n->message);
+
+  fflush (fh);
+  fclose (fh);
+
+  waitpid (pid, &status, 0);
+
+  DEBUG ("exec plugin: Child %i exited with status %i.",
+      pid, status);
+
+  if (n->meta != NULL)
+    plugin_notification_meta_free (n->meta);
+  n->meta = NULL;
+  sfree (arg);
+  pthread_exit ((void *) 0);
+  return (NULL);
+} /* void *exec_notification_one }}} */
+
+static int exec_init (void) /* {{{ */
+{
+  struct sigaction sa;
+
+  memset (&sa, '\0', sizeof (sa));
+  sa.sa_handler = sigchld_handler;
+  sigaction (SIGCHLD, &sa, NULL);
+
+  return (0);
+} /* int exec_init }}} */
+
+static int exec_read (void) /* {{{ */
+{
+  program_list_t *pl;
+
+  for (pl = pl_head; pl != NULL; pl = pl->next)
+  {
+    pthread_t t;
+    pthread_attr_t attr;
+
+    /* Only execute `normal' style executables here. */
+    if ((pl->flags & PL_NORMAL) == 0)
+      continue;
+
+    pthread_mutex_lock (&pl_lock);
+    /* Skip if a child is already running. */
+    if ((pl->flags & PL_RUNNING) != 0)
+    {
+      pthread_mutex_unlock (&pl_lock);
+      continue;
+    }
+    pl->flags |= PL_RUNNING;
+    pthread_mutex_unlock (&pl_lock);
+
+    pthread_attr_init (&attr);
+    pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
+    pthread_create (&t, &attr, exec_read_one, (void *) pl);
+  } /* for (pl) */
+
+  return (0);
+} /* int exec_read }}} */
+
+static int exec_notification (const notification_t *n, /* {{{ */
+    user_data_t __attribute__((unused)) *user_data)
+{
+  program_list_t *pl;
+  program_list_and_notification_t *pln;
+
+  for (pl = pl_head; pl != NULL; pl = pl->next)
+  {
+    pthread_t t;
+    pthread_attr_t attr;
+
+    /* Only execute `notification' style executables here. */
+    if ((pl->flags & PL_NOTIF_ACTION) == 0)
+      continue;
+
+    /* Skip if a child is already running. */
+    if (pl->pid != 0)
+      continue;
+
+    pln = (program_list_and_notification_t *) malloc (sizeof
+       (program_list_and_notification_t));
+    if (pln == NULL)
+    {
+      ERROR ("exec plugin: malloc failed.");
+      continue;
+    }
+
+    pln->pl = pl;
+    memcpy (&pln->n, n, sizeof (notification_t));
+
+    /* Set the `meta' member to NULL, otherwise `plugin_notification_meta_copy'
+     * will run into an endless loop. */
+    pln->n.meta = NULL;
+    plugin_notification_meta_copy (&pln->n, n);
+
+    pthread_attr_init (&attr);
+    pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
+    pthread_create (&t, &attr, exec_notification_one, (void *) pln);
+  } /* for (pl) */
+
+  return (0);
+} /* }}} int exec_notification */
+
+static int exec_shutdown (void) /* {{{ */
+{
+  program_list_t *pl;
+  program_list_t *next;
+
+  pl = pl_head;
+  while (pl != NULL)
+  {
+    next = pl->next;
+
+    if (pl->pid > 0)
+    {
+      kill (pl->pid, SIGTERM);
+      INFO ("exec plugin: Sent SIGTERM to %hu", (unsigned short int) pl->pid);
+    }
+
+    sfree (pl->user);
+    sfree (pl);
+
+    pl = next;
+  } /* while (pl) */
+  pl_head = NULL;
+
+  return (0);
+} /* int exec_shutdown }}} */
+
+void module_register (void)
+{
+  plugin_register_complex_config ("exec", exec_config);
+  plugin_register_init ("exec", exec_init);
+  plugin_register_read ("exec", exec_read);
+  plugin_register_notification ("exec", exec_notification,
+      /* user_data = */ NULL);
+  plugin_register_shutdown ("exec", exec_shutdown);
+} /* void module_register */
+
+/*
+ * vim:shiftwidth=2:softtabstop=2:tabstop=8:fdm=marker
+ */
diff --git a/src/filecount.c b/src/filecount.c
new file mode 100644 (file)
index 0000000..47f99e9
--- /dev/null
@@ -0,0 +1,577 @@
+/**
+ * collectd - src/filecount.c
+ * Copyright (C) 2008  Alessandro Iurlano
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Alessandro Iurlano <alessandro.iurlano at gmail.com>
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"       
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <fnmatch.h>
+
+#define FC_RECURSIVE 1
+#define FC_HIDDEN 2
+
+struct fc_directory_conf_s
+{
+  char *path;
+  char *instance;
+
+  int options;
+
+  /* Data counters */
+  uint64_t files_num;
+  uint64_t files_size;
+
+  /* Selectors */
+  char *name;
+  int64_t mtime;
+  int64_t size;
+
+  /* Helper for the recursive functions */
+  time_t now;
+};
+typedef struct fc_directory_conf_s fc_directory_conf_t;
+
+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));
+  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;
+  sstrncpy (vl.type, "bytes", sizeof (vl.type));
+
+  plugin_dispatch_values (&vl);
+} /* void fc_submit_dir */
+
+/*
+ * Config:
+ * <Plugin filecount>
+ *   <Directory /path/to/dir>
+ *     Instance "foobar"
+ *     Name "*.conf"
+ *     MTime -3600
+ *     Size "+10M"
+ *   </Directory>
+ * </Plugin>
+ *
+ * Collect:
+ * - Number of files
+ * - Total size
+ */
+
+static int fc_config_set_instance (fc_directory_conf_t *dir, const char *str)
+{
+  char buffer[1024];
+  char *ptr;
+  char *copy;
+
+  sstrncpy (buffer, str, sizeof (buffer));
+  for (ptr = buffer; *ptr != 0; ptr++)
+    if (*ptr == '/')
+      *ptr = '_';
+
+  for (ptr = buffer; *ptr == '_'; ptr++)
+    /* do nothing */;
+
+  if (*ptr == 0)
+    return (-1);
+
+  copy = strdup (ptr);
+  if (copy == NULL)
+    return (-1);
+
+  sfree (dir->instance);
+  dir->instance = copy;
+
+  return (0);
+} /* int fc_config_set_instance */
+
+static int fc_config_add_dir_instance (fc_directory_conf_t *dir,
+    oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("filecount plugin: The `Instance' config option needs exactly "
+        "one string argument.");
+    return (-1);
+  }
+
+  return (fc_config_set_instance (dir, ci->values[0].value.string));
+} /* int fc_config_add_dir_instance */
+
+static int fc_config_add_dir_name (fc_directory_conf_t *dir,
+    oconfig_item_t *ci)
+{
+  char *temp;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("filecount plugin: The `Name' config option needs exactly one "
+        "string argument.");
+    return (-1);
+  }
+
+  temp = strdup (ci->values[0].value.string);
+  if (temp == NULL)
+  {
+    ERROR ("filecount plugin: strdup failed.");
+    return (-1);
+  }
+
+  sfree (dir->name);
+  dir->name = temp;
+
+  return (0);
+} /* int fc_config_add_dir_name */
+
+static int fc_config_add_dir_mtime (fc_directory_conf_t *dir,
+    oconfig_item_t *ci)
+{
+  char *endptr;
+  double temp;
+
+  if ((ci->values_num != 1)
+      || ((ci->values[0].type != OCONFIG_TYPE_STRING)
+        && (ci->values[0].type != OCONFIG_TYPE_NUMBER)))
+  {
+    WARNING ("filecount plugin: The `MTime' config option needs exactly one "
+        "string or numeric argument.");
+    return (-1);
+  }
+
+  if (ci->values[0].type == OCONFIG_TYPE_NUMBER)
+  {
+    dir->mtime = (int64_t) ci->values[0].value.number;
+    return (0);
+  }
+
+  errno = 0;
+  endptr = NULL;
+  temp = strtod (ci->values[0].value.string, &endptr);
+  if ((errno != 0) || (endptr == NULL)
+      || (endptr == ci->values[0].value.string))
+  {
+    WARNING ("filecount plugin: Converting `%s' to a number failed.",
+        ci->values[0].value.string);
+    return (-1);
+  }
+
+  switch (*endptr)
+  {
+    case 0:
+    case 's':
+    case 'S':
+      break;
+
+    case 'm':
+    case 'M':
+      temp *= 60;
+      break;
+
+    case 'h':
+    case 'H':
+      temp *= 3600;
+      break;
+
+    case 'd':
+    case 'D':
+      temp *= 86400;
+      break;
+
+    case 'w':
+    case 'W':
+      temp *= 7 * 86400;
+      break;
+
+    case 'y':
+    case 'Y':
+      temp *= 31557600; /* == 365.25 * 86400 */
+      break;
+
+    default:
+      WARNING ("filecount plugin: Invalid suffix for `MTime': `%c'", *endptr);
+      return (-1);
+  } /* switch (*endptr) */
+
+  dir->mtime = (int64_t) temp;
+
+  return (0);
+} /* int fc_config_add_dir_mtime */
+
+static int fc_config_add_dir_size (fc_directory_conf_t *dir,
+    oconfig_item_t *ci)
+{
+  char *endptr;
+  double temp;
+
+  if ((ci->values_num != 1)
+      || ((ci->values[0].type != OCONFIG_TYPE_STRING)
+        && (ci->values[0].type != OCONFIG_TYPE_NUMBER)))
+  {
+    WARNING ("filecount plugin: The `Size' config option needs exactly one "
+        "string or numeric argument.");
+    return (-1);
+  }
+
+  if (ci->values[0].type == OCONFIG_TYPE_NUMBER)
+  {
+    dir->size = (int64_t) ci->values[0].value.number;
+    return (0);
+  }
+
+  errno = 0;
+  endptr = NULL;
+  temp = strtod (ci->values[0].value.string, &endptr);
+  if ((errno != 0) || (endptr == NULL)
+      || (endptr == ci->values[0].value.string))
+  {
+    WARNING ("filecount plugin: Converting `%s' to a number failed.",
+        ci->values[0].value.string);
+    return (-1);
+  }
+
+  switch (*endptr)
+  {
+    case 0:
+    case 'b':
+    case 'B':
+      break;
+
+    case 'k':
+    case 'K':
+      temp *= 1000.0;
+      break;
+
+    case 'm':
+    case 'M':
+      temp *= 1000.0 * 1000.0;
+      break;
+
+    case 'g':
+    case 'G':
+      temp *= 1000.0 * 1000.0 * 1000.0;
+      break;
+
+    case 't':
+    case 'T':
+      temp *= 1000.0 * 1000.0 * 1000.0 * 1000.0;
+      break;
+
+    case 'p':
+    case 'P':
+      temp *= 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0;
+      break;
+
+    default:
+      WARNING ("filecount plugin: Invalid suffix for `Size': `%c'", *endptr);
+      return (-1);
+  } /* switch (*endptr) */
+
+  dir->size = (int64_t) temp;
+
+  return (0);
+} /* int fc_config_add_dir_size */
+
+static int fc_config_add_dir_option (fc_directory_conf_t *dir,
+    oconfig_item_t *ci, int bit)
+{
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN))
+  {
+    WARNING ("filecount plugin: The `Recursive' config options needs exactly "
+        "one boolean argument.");
+    return (-1);
+  }
+
+  if (ci->values[0].value.boolean)
+    dir->options |= bit;
+  else
+    dir->options &= ~bit;
+
+  return (0);
+} /* int fc_config_add_dir_option */
+
+static int fc_config_add_dir (oconfig_item_t *ci)
+{
+  fc_directory_conf_t *dir;
+  int status;
+  int i;
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("filecount plugin: `Directory' needs exactly one string "
+        "argument.");
+    return (-1);
+  }
+
+  /* Initialize `dir' */
+  dir = (fc_directory_conf_t *) malloc (sizeof (*dir));
+  if (dir == NULL)
+  {
+    ERROR ("filecount plugin: malloc failed.");
+    return (-1);
+  }
+  memset (dir, 0, sizeof (*dir));
+
+  dir->path = strdup (ci->values[0].value.string);
+  if (dir->path == NULL)
+  {
+    ERROR ("filecount plugin: strdup failed.");
+    return (-1);
+  }
+
+  fc_config_set_instance (dir, dir->path);
+
+  dir->options = FC_RECURSIVE;
+
+  dir->name = NULL;
+  dir->mtime = 0;
+  dir->size = 0;
+
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+
+    if (strcasecmp ("Instance", option->key) == 0)
+      status = fc_config_add_dir_instance (dir, option);
+    else if (strcasecmp ("Name", option->key) == 0)
+      status = fc_config_add_dir_name (dir, option);
+    else if (strcasecmp ("MTime", option->key) == 0)
+      status = fc_config_add_dir_mtime (dir, option);
+    else if (strcasecmp ("Size", option->key) == 0)
+      status = fc_config_add_dir_size (dir, option);
+    else if (strcasecmp ("Recursive", option->key) == 0)
+      status = fc_config_add_dir_option (dir, option, FC_RECURSIVE);
+    else if (strcasecmp ("IncludeHidden", option->key) == 0)
+      status = fc_config_add_dir_option (dir, option, FC_HIDDEN);
+    else
+    {
+      WARNING ("filecount plugin: fc_config_add_dir: "
+          "Option `%s' not allowed here.", option->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  } /* for (ci->children) */
+
+  if (status == 0)
+  {
+    fc_directory_conf_t **temp;
+
+    temp = (fc_directory_conf_t **) realloc (directories,
+        sizeof (*directories) * (directories_num + 1));
+    if (temp == NULL)
+    {
+      ERROR ("filecount plugin: realloc failed.");
+      status = -1;
+    }
+    else
+    {
+      directories = temp;
+      directories[directories_num] = dir;
+      directories_num++;
+    }
+  }
+
+  if (status != 0)
+  {
+    sfree (dir->name);
+    sfree (dir->instance);
+    sfree (dir->path);
+    sfree (dir);
+    return (-1);
+  }
+
+  return (0);
+} /* int fc_config_add_dir */
+
+static int fc_config (oconfig_item_t *ci)
+{
+  int i;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+    if (strcasecmp ("Directory", child->key) == 0)
+      fc_config_add_dir (child);
+    else
+    {
+      WARNING ("filecount plugin: Ignoring unknown config option `%s'.",
+          child->key);
+    }
+  } /* for (ci->children) */
+
+  return (0);
+} /* int fc_config */
+
+static int fc_init (void)
+{
+  if (directories_num < 1)
+  {
+    WARNING ("filecount plugin: No directories have been configured.");
+    return (-1);
+  }
+
+  return (0);
+} /* int fc_init */
+
+static int fc_read_dir_callback (const char *dirname, const char *filename,
+    void *user_data)
+{
+  fc_directory_conf_t *dir = user_data;
+  char abs_path[PATH_MAX];
+  struct stat statbuf;
+  int status;
+
+  if (dir == NULL)
+    return (-1);
+
+  ssnprintf (abs_path, sizeof (abs_path), "%s/%s", dirname, filename);
+
+  status = lstat (abs_path, &statbuf);
+  if (status != 0)
+  {
+    ERROR ("filecount plugin: stat (%s) failed.", abs_path);
+    return (-1);
+  }
+
+  if (S_ISDIR (statbuf.st_mode) && (dir->options & FC_RECURSIVE))
+  {
+    status = walk_directory (abs_path, fc_read_dir_callback, dir,
+        /* include hidden = */ (dir->options & FC_HIDDEN) ? 1 : 0);
+    return (status);
+  }
+  else if (!S_ISREG (statbuf.st_mode))
+  {
+    return (0);
+  }
+
+  if (dir->name != NULL)
+  {
+    status = fnmatch (dir->name, filename, /* flags = */ 0);
+    if (status != 0)
+      return (0);
+  }
+
+  if (dir->mtime != 0)
+  {
+    time_t mtime = dir->now;
+
+    if (dir->mtime < 0)
+      mtime += dir->mtime;
+    else
+      mtime -= dir->mtime;
+
+    DEBUG ("filecount plugin: Only collecting files that were touched %s %u.",
+        (dir->mtime < 0) ? "after" : "before",
+        (unsigned int) mtime);
+
+    if (((dir->mtime < 0) && (statbuf.st_mtime < mtime))
+        || ((dir->mtime > 0) && (statbuf.st_mtime > mtime)))
+      return (0);
+  }
+
+  if (dir->size != 0)
+  {
+    off_t size;
+
+    if (dir->size < 0)
+      size = (off_t) ((-1) * dir->size);
+    else
+      size = (off_t) dir->size;
+
+    if (((dir->size < 0) && (statbuf.st_size > size))
+        || ((dir->size > 0) && (statbuf.st_size < size)))
+      return (0);
+  }
+
+  dir->files_num++;
+  dir->files_size += (uint64_t) statbuf.st_size;
+
+  return (0);
+} /* int fc_read_dir_callback */
+
+static int fc_read_dir (fc_directory_conf_t *dir)
+{
+  int status;
+
+  dir->files_num = 0;
+  dir->files_size = 0;
+
+  if (dir->mtime != 0)
+    dir->now = time (NULL);
+    
+  status = walk_directory (dir->path, fc_read_dir_callback, dir,
+      /* include hidden */ (dir->options & FC_HIDDEN) ? 1 : 0);
+  if (status != 0)
+  {
+    WARNING ("filecount plugin: walk_directory (%s) failed.", dir->path);
+    return (-1);
+  }
+
+  fc_submit_dir (dir);
+
+  return (0);
+} /* int fc_read_dir */
+
+static int fc_read (void)
+{
+  size_t i;
+
+  for (i = 0; i < directories_num; i++)
+    fc_read_dir (directories[i]);
+
+  return (0);
+} /* int fc_read */
+
+void module_register (void)
+{
+  plugin_register_complex_config ("filecount", fc_config);
+  plugin_register_init ("filecount", fc_init);
+  plugin_register_read ("filecount", fc_read);
+} /* void module_register */
+
+/*
+ * vim: set sw=2 sts=2 et :
+ */
diff --git a/src/filter_chain.c b/src/filter_chain.c
new file mode 100644 (file)
index 0000000..ed2df61
--- /dev/null
@@ -0,0 +1,1040 @@
+/**
+ * collectd - src/filter_chain.h
+ * Copyright (C) 2008-2010  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "configfile.h"
+#include "plugin.h"
+#include "utils_complain.h"
+#include "common.h"
+#include "filter_chain.h"
+
+/*
+ * Data types
+ */
+/* List of matches, used in fc_rule_t and for the global `match_list_head'
+ * variable. */
+struct fc_match_s;
+typedef struct fc_match_s fc_match_t; /* {{{ */
+struct fc_match_s
+{
+  char name[DATA_MAX_NAME_LEN];
+  match_proc_t proc;
+  void *user_data;
+  fc_match_t *next;
+}; /* }}} */
+
+/* List of targets, used in fc_rule_t and for the global `target_list_head'
+ * variable. */
+struct fc_target_s;
+typedef struct fc_target_s fc_target_t; /* {{{ */
+struct fc_target_s
+{
+  char name[DATA_MAX_NAME_LEN];
+  void *user_data;
+  target_proc_t proc;
+  fc_target_t *next;
+}; /* }}} */
+
+/* List of rules, used in fc_chain_t */
+struct fc_rule_s;
+typedef struct fc_rule_s fc_rule_t; /* {{{ */
+struct fc_rule_s
+{
+  char name[DATA_MAX_NAME_LEN];
+  fc_match_t  *matches;
+  fc_target_t *targets;
+  fc_rule_t *next;
+}; /* }}} */
+
+/* List of chains, used for `chain_list_head' */
+struct fc_chain_s /* {{{ */
+{
+  char name[DATA_MAX_NAME_LEN];
+  fc_rule_t   *rules;
+  fc_target_t *targets;
+  fc_chain_t  *next;
+}; /* }}} */
+
+/*
+ * Global variables
+ */
+static fc_match_t  *match_list_head;
+static fc_target_t *target_list_head;
+static fc_chain_t  *chain_list_head;
+
+/*
+ * Private functions
+ */
+static void fc_free_matches (fc_match_t *m) /* {{{ */
+{
+  if (m == NULL)
+    return;
+
+  if (m->proc.destroy != NULL)
+    (*m->proc.destroy) (&m->user_data);
+  else if (m->user_data != NULL)
+  {
+    ERROR ("Filter sybsystem: fc_free_matches: There is user data, but no "
+        "destroy functions has been specified. "
+        "Memory will probably be lost!");
+  }
+
+  if (m->next != NULL)
+    fc_free_matches (m->next);
+
+  free (m);
+} /* }}} void fc_free_matches */
+
+static void fc_free_targets (fc_target_t *t) /* {{{ */
+{
+  if (t == NULL)
+    return;
+
+  if (t->proc.destroy != NULL)
+    (*t->proc.destroy) (&t->user_data);
+  else if (t->user_data != NULL)
+  {
+    ERROR ("Filter sybsystem: fc_free_targets: There is user data, but no "
+        "destroy functions has been specified. "
+        "Memory will probably be lost!");
+  }
+
+  if (t->next != NULL)
+    fc_free_targets (t->next);
+
+  free (t);
+} /* }}} void fc_free_targets */
+
+static void fc_free_rules (fc_rule_t *r) /* {{{ */
+{
+  if (r == NULL)
+    return;
+
+  fc_free_matches (r->matches);
+  fc_free_targets (r->targets);
+
+  if (r->next != NULL)
+    fc_free_rules (r->next);
+
+  free (r);
+} /* }}} void fc_free_rules */
+
+static void fc_free_chains (fc_chain_t *c) /* {{{ */
+{
+  if (c == NULL)
+    return;
+
+  fc_free_rules (c->rules);
+  fc_free_targets (c->targets);
+
+  if (c->next != NULL)
+    fc_free_chains (c->next);
+
+  free (c);
+} /* }}} void fc_free_chains */
+
+static char *fc_strdup (const char *orig) /* {{{ */
+{
+  size_t sz;
+  char *dest;
+
+  if (orig == NULL)
+    return (NULL);
+
+  sz = strlen (orig) + 1;
+  dest = (char *) malloc (sz);
+  if (dest == NULL)
+    return (NULL);
+
+  memcpy (dest, orig, sz);
+
+  return (dest);
+} /* }}} char *fc_strdup */
+
+/*
+ * Configuration.
+ *
+ * The configuration looks somewhat like this:
+ *
+ *  <Chain "PreCache">
+ *    <Rule>
+ *      <Match "regex">
+ *        Plugin "^mysql$"
+ *        Type "^mysql_command$"
+ *        TypeInstance "^show_"
+ *      </Match>
+ *      <Target "drop">
+ *      </Target>
+ *    </Rule>
+ *
+ *    <Target "write">
+ *      Plugin "rrdtool"
+ *    </Target>
+ *  </Chain>
+ */
+static int fc_config_add_match (fc_match_t **matches_head, /* {{{ */
+    oconfig_item_t *ci)
+{
+  fc_match_t *m;
+  fc_match_t *ptr;
+  int status;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("Filter subsystem: `Match' blocks require "
+        "exactly one string argument.");
+    return (-1);
+  }
+
+  ptr = match_list_head;
+  while (ptr != NULL)
+  {
+    if (strcasecmp (ptr->name, ci->values[0].value.string) == 0)
+      break;
+    ptr = ptr->next;
+  }
+
+  if (ptr == NULL)
+  {
+    WARNING ("Filter subsystem: Cannot find a \"%s\" match. "
+        "Did you load the appropriate plugin?",
+        ci->values[0].value.string);
+    return (-1);
+  }
+
+  m = (fc_match_t *) malloc (sizeof (*m));
+  if (m == NULL)
+  {
+    ERROR ("fc_config_add_match: malloc failed.");
+    return (-1);
+  }
+  memset (m, 0, sizeof (*m));
+
+  sstrncpy (m->name, ptr->name, sizeof (m->name));
+  memcpy (&m->proc, &ptr->proc, sizeof (m->proc));
+  m->user_data = NULL;
+  m->next = NULL;
+
+  if (m->proc.create != NULL)
+  {
+    status = (*m->proc.create) (ci, &m->user_data);
+    if (status != 0)
+    {
+      WARNING ("Filter subsystem: Failed to create a %s match.",
+          m->name);
+      fc_free_matches (m);
+      return (-1);
+    }
+  }
+
+  if (*matches_head != NULL)
+  {
+    ptr = *matches_head;
+    while (ptr->next != NULL)
+      ptr = ptr->next;
+
+    ptr->next = m;
+  }
+  else
+  {
+    *matches_head = m;
+  }
+
+  return (0);
+} /* }}} int fc_config_add_match */
+
+static int fc_config_add_target (fc_target_t **targets_head, /* {{{ */
+    oconfig_item_t *ci)
+{
+  fc_target_t *t;
+  fc_target_t *ptr;
+  int status;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("Filter subsystem: `Target' blocks require "
+        "exactly one string argument.");
+    return (-1);
+  }
+
+  ptr = target_list_head;
+  while (ptr != NULL)
+  {
+    if (strcasecmp (ptr->name, ci->values[0].value.string) == 0)
+      break;
+    ptr = ptr->next;
+  }
+
+  if (ptr == NULL)
+  {
+    WARNING ("Filter subsystem: Cannot find a \"%s\" target. "
+        "Did you load the appropriate plugin?",
+        ci->values[0].value.string);
+    return (-1);
+  }
+
+  t = (fc_target_t *) malloc (sizeof (*t));
+  if (t == NULL)
+  {
+    ERROR ("fc_config_add_target: malloc failed.");
+    return (-1);
+  }
+  memset (t, 0, sizeof (*t));
+
+  sstrncpy (t->name, ptr->name, sizeof (t->name));
+  memcpy (&t->proc, &ptr->proc, sizeof (t->proc));
+  t->user_data = NULL;
+  t->next = NULL;
+
+  if (t->proc.create != NULL)
+  {
+    status = (*t->proc.create) (ci, &t->user_data);
+    if (status != 0)
+    {
+      WARNING ("Filter subsystem: Failed to create a %s target.",
+          t->name);
+      fc_free_targets (t);
+      return (-1);
+    }
+  }
+  else
+  {
+    t->user_data = NULL;
+  }
+  
+  if (*targets_head != NULL)
+  {
+    ptr = *targets_head;
+    while (ptr->next != NULL)
+      ptr = ptr->next;
+
+    ptr->next = t;
+  }
+  else
+  {
+    *targets_head = t;
+  }
+
+  return (0);
+} /* }}} int fc_config_add_target */
+
+static int fc_config_add_rule (fc_chain_t *chain, /* {{{ */
+    oconfig_item_t *ci)
+{
+  fc_rule_t *rule;
+  char rule_name[2*DATA_MAX_NAME_LEN] = "Unnamed rule";
+  int status = 0;
+  int i;
+
+  if (ci->values_num > 1)
+  {
+    WARNING ("Filter subsystem: `Rule' blocks have at most one argument.");
+    return (-1);
+  }
+  else if ((ci->values_num == 1)
+      && (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("Filter subsystem: `Rule' blocks expect one string argument "
+        "or no argument at all.");
+    return (-1);
+  }
+
+  rule = (fc_rule_t *) malloc (sizeof (*rule));
+  if (rule == NULL)
+  {
+    ERROR ("fc_config_add_rule: malloc failed.");
+    return (-1);
+  }
+  memset (rule, 0, sizeof (*rule));
+  rule->next = NULL;
+
+  if (ci->values_num == 1)
+  {
+    sstrncpy (rule->name, ci->values[0].value.string, sizeof (rule->name));
+    ssnprintf (rule_name, sizeof (rule_name), "Rule \"%s\"",
+        ci->values[0].value.string);
+  }
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+    status = 0;
+
+    if (strcasecmp ("Match", option->key) == 0)
+      status = fc_config_add_match (&rule->matches, option);
+    else if (strcasecmp ("Target", option->key) == 0)
+      status = fc_config_add_target (&rule->targets, option);
+    else
+    {
+      WARNING ("Filter subsystem: %s: Option `%s' not allowed "
+          "inside a <Rule> block.", rule_name, option->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  } /* for (ci->children) */
+
+  /* Additional sanity checking. */
+  while (status == 0)
+  {
+    if (rule->targets == NULL)
+    {
+      WARNING ("Filter subsystem: %s: No target has been specified.",
+          rule_name);
+      status = -1;
+      break;
+    }
+
+    break;
+  } /* while (status == 0) */
+
+  if (status != 0)
+  {
+    fc_free_rules (rule);
+    return (-1);
+  }
+
+  if (chain->rules != NULL)
+  {
+    fc_rule_t *ptr;
+
+    ptr = chain->rules;
+    while (ptr->next != NULL)
+      ptr = ptr->next;
+
+    ptr->next = rule;
+  }
+  else
+  {
+    chain->rules = rule;
+  }
+
+  return (0);
+} /* }}} int fc_config_add_rule */
+
+static int fc_config_add_chain (const oconfig_item_t *ci) /* {{{ */
+{
+  fc_chain_t *chain;
+  int status = 0;
+  int i;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("Filter subsystem: <Chain> blocks require exactly one "
+        "string argument.");
+    return (-1);
+  }
+
+  chain = (fc_chain_t *) malloc (sizeof (*chain));
+  if (chain == NULL)
+  {
+    ERROR ("fc_config_add_chain: malloc failed.");
+    return (-1);
+  }
+  memset (chain, 0, sizeof (*chain));
+  sstrncpy (chain->name, ci->values[0].value.string, sizeof (chain->name));
+  chain->rules = NULL;
+  chain->targets = NULL;
+  chain->next = NULL;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+    status = 0;
+
+    if (strcasecmp ("Rule", option->key) == 0)
+      status = fc_config_add_rule (chain, option);
+    else if (strcasecmp ("Target", option->key) == 0)
+      status = fc_config_add_target (&chain->targets, option);
+    else
+    {
+      WARNING ("Filter subsystem: Chain %s: Option `%s' not allowed "
+          "inside a <Chain> block.", chain->name, option->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  } /* for (ci->children) */
+
+  if (status != 0)
+  {
+    fc_free_chains (chain);
+    return (-1);
+  }
+
+  if (chain_list_head != NULL)
+  {
+    fc_chain_t *ptr;
+
+    ptr = chain_list_head;
+    while (ptr->next != NULL)
+      ptr = ptr->next;
+
+    ptr->next = chain;
+  }
+  else
+  {
+    chain_list_head = chain;
+  }
+
+  return (0);
+} /* }}} int fc_config_add_chain */
+
+/*
+ * Built-in target "jump"
+ *
+ * Prefix `bit' like `_b_uilt-_i_n _t_arget'
+ */
+static int fc_bit_jump_create (const oconfig_item_t *ci, /* {{{ */
+    void **user_data)
+{
+  oconfig_item_t *ci_chain;
+
+  if (ci->children_num != 1)
+  {
+    ERROR ("Filter subsystem: The built-in target `jump' needs exactly "
+        "one `Chain' argument!");
+    return (-1);
+  }
+
+  ci_chain = ci->children;
+  if (strcasecmp ("Chain", ci_chain->key) != 0)
+  {
+    ERROR ("Filter subsystem: The built-in target `jump' does not "
+        "support the configuration option `%s'.",
+        ci_chain->key);
+    return (-1);
+  }
+
+  if ((ci_chain->values_num != 1)
+      || (ci_chain->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    ERROR ("Filter subsystem: Built-in target `jump': The `Chain' option "
+        "needs exactly one string argument.");
+    return (-1);
+  }
+
+  *user_data = fc_strdup (ci_chain->values[0].value.string);
+  if (*user_data == NULL)
+  {
+    ERROR ("fc_bit_jump_create: fc_strdup failed.");
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int fc_bit_jump_create */
+
+static int fc_bit_jump_destroy (void **user_data) /* {{{ */
+{
+  if (user_data != NULL)
+  {
+    free (*user_data);
+    *user_data = NULL;
+  }
+
+  return (0);
+} /* }}} int fc_bit_jump_destroy */
+
+static int fc_bit_jump_invoke (const data_set_t *ds, /* {{{ */
+    value_list_t *vl, notification_meta_t __attribute__((unused)) **meta,
+    void **user_data)
+{
+  char *chain_name;
+  fc_chain_t *chain;
+  int status;
+
+  chain_name = *user_data;
+
+  for (chain = chain_list_head; chain != NULL; chain = chain->next)
+    if (strcasecmp (chain_name, chain->name) == 0)
+      break;
+
+  if (chain == NULL)
+  {
+    ERROR ("Filter subsystem: Built-in target `jump': There is no chain "
+        "named `%s'.", chain_name);
+    return (-1);
+  }
+
+  status = fc_process_chain (ds, vl, chain);
+  if (status < 0)
+    return (status);
+  else if (status == FC_TARGET_STOP)
+    return (FC_TARGET_STOP);
+  else
+    return (FC_TARGET_CONTINUE);
+} /* }}} int fc_bit_jump_invoke */
+
+static int fc_bit_stop_invoke (const data_set_t __attribute__((unused)) *ds, /* {{{ */
+    value_list_t __attribute__((unused)) *vl,
+    notification_meta_t __attribute__((unused)) **meta,
+    void __attribute__((unused)) **user_data)
+{
+  return (FC_TARGET_STOP);
+} /* }}} int fc_bit_stop_invoke */
+
+static int fc_bit_return_invoke (const data_set_t __attribute__((unused)) *ds, /* {{{ */
+    value_list_t __attribute__((unused)) *vl,
+    notification_meta_t __attribute__((unused)) **meta,
+    void __attribute__((unused)) **user_data)
+{
+  return (FC_TARGET_RETURN);
+} /* }}} int fc_bit_return_invoke */
+
+static int fc_bit_write_create (const oconfig_item_t *ci, /* {{{ */
+    void **user_data)
+{
+  int i;
+
+  char **plugin_list;
+  size_t plugin_list_len;
+
+  plugin_list = NULL;
+  plugin_list_len = 0;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+    char **temp;
+    int j;
+
+    if (strcasecmp ("Plugin", child->key) != 0)
+    {
+      ERROR ("Filter subsystem: The built-in target `write' does not "
+          "support the configuration option `%s'.",
+          child->key);
+      continue;
+    }
+
+    for (j = 0; j < child->values_num; j++)
+    {
+      if (child->values[j].type != OCONFIG_TYPE_STRING)
+      {
+        ERROR ("Filter subsystem: Built-in target `write': "
+            "The `Plugin' option accepts only string arguments.");
+        continue;
+      }
+
+      temp = (char **) realloc (plugin_list, (plugin_list_len + 2)
+          * (sizeof (*plugin_list)));
+      if (temp == NULL)
+      {
+        ERROR ("fc_bit_write_create: realloc failed.");
+        continue;
+      }
+      plugin_list = temp;
+
+      plugin_list[plugin_list_len] = fc_strdup (child->values[j].value.string);
+      if (plugin_list[plugin_list_len] == NULL)
+      {
+        ERROR ("fc_bit_write_create: fc_strdup failed.");
+        continue;
+      }
+      plugin_list_len++;
+      plugin_list[plugin_list_len] = NULL;
+    } /* for (j = 0; j < child->values_num; j++) */
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  *user_data = plugin_list;
+
+  return (0);
+} /* }}} int fc_bit_write_create */
+
+static int fc_bit_write_destroy (void **user_data) /* {{{ */
+{
+  char **plugin_list;
+  size_t i;
+
+  if ((user_data == NULL) || (*user_data == NULL))
+    return (0);
+
+  plugin_list = *user_data;
+
+  for (i = 0; plugin_list[i] != NULL; i++)
+    free (plugin_list[i]);
+  free (plugin_list);
+
+  return (0);
+} /* }}} int fc_bit_write_destroy */
+
+static int fc_bit_write_invoke (const data_set_t *ds, /* {{{ */
+    value_list_t *vl, notification_meta_t __attribute__((unused)) **meta,
+    void **user_data)
+{
+  char **plugin_list;
+  int status;
+
+  plugin_list = NULL;
+  if (user_data != NULL)
+    plugin_list = *user_data;
+
+  if ((plugin_list == NULL) || (plugin_list[0] == NULL))
+  {
+    static c_complain_t enoent_complaint = C_COMPLAIN_INIT_STATIC;
+
+    status = plugin_write (/* plugin = */ NULL, ds, vl);
+    if (status == ENOENT)
+    {
+      /* in most cases this is a permanent error, so use the complain
+       * mechanism rather than spamming the logs */
+      c_complain (LOG_INFO, &enoent_complaint,
+          "Filter subsystem: Built-in target `write': Dispatching value to "
+          "all write plugins failed with status %i (ENOENT). "
+          "Most likely this means you didn't load any write plugins.",
+          status);
+    }
+    else if (status != 0)
+    {
+      INFO ("Filter subsystem: Built-in target `write': Dispatching value to "
+          "all write plugins failed with status %i.", status);
+    }
+    else
+    {
+      assert (status == 0);
+      c_release (LOG_INFO, &enoent_complaint, "Filter subsystem: "
+          "Built-in target `write': Some write plugin is back to normal "
+          "operation. `write' succeeded.");
+    }
+  }
+  else
+  {
+    size_t i;
+
+    for (i = 0; plugin_list[i] != NULL; i++)
+    {
+      status = plugin_write (plugin_list[i], ds, vl);
+      if (status != 0)
+      {
+        INFO ("Filter subsystem: Built-in target `write': Dispatching value to "
+            "the `%s' plugin failed with status %i.", plugin_list[i], status);
+      }
+    } /* for (i = 0; plugin_list[i] != NULL; i++) */
+  }
+
+  return (FC_TARGET_CONTINUE);
+} /* }}} int fc_bit_write_invoke */
+
+static int fc_init_once (void) /* {{{ */
+{
+  static int done = 0;
+  target_proc_t tproc;
+
+  if (done != 0)
+    return (0);
+
+  memset (&tproc, 0, sizeof (tproc));
+  tproc.create  = fc_bit_jump_create;
+  tproc.destroy = fc_bit_jump_destroy;
+  tproc.invoke  = fc_bit_jump_invoke;
+  fc_register_target ("jump", tproc);
+
+  memset (&tproc, 0, sizeof (tproc));
+  tproc.create  = NULL;
+  tproc.destroy = NULL;
+  tproc.invoke  = fc_bit_stop_invoke;
+  fc_register_target ("stop", tproc);
+
+  memset (&tproc, 0, sizeof (tproc));
+  tproc.create  = NULL;
+  tproc.destroy = NULL;
+  tproc.invoke  = fc_bit_return_invoke;
+  fc_register_target ("return", tproc);
+
+  memset (&tproc, 0, sizeof (tproc));
+  tproc.create  = fc_bit_write_create;
+  tproc.destroy = fc_bit_write_destroy;
+  tproc.invoke  = fc_bit_write_invoke;
+  fc_register_target ("write", tproc);
+
+  done++;
+  return (0);
+} /* }}} int fc_init_once */
+
+/*
+ * Public functions
+ */
+/* Add a match to list of available matches. */
+int fc_register_match (const char *name, match_proc_t proc) /* {{{ */
+{
+  fc_match_t *m;
+
+  DEBUG ("fc_register_match (%s);", name);
+
+  m = (fc_match_t *) malloc (sizeof (*m));
+  if (m == NULL)
+    return (-ENOMEM);
+  memset (m, 0, sizeof (*m));
+
+  sstrncpy (m->name, name, sizeof (m->name));
+  memcpy (&m->proc, &proc, sizeof (m->proc));
+  m->next = NULL;
+
+  if (match_list_head == NULL)
+  {
+    match_list_head = m;
+  }
+  else
+  {
+    fc_match_t *ptr;
+
+    ptr = match_list_head;
+    while (ptr->next != NULL)
+      ptr = ptr->next;
+
+    ptr->next = m;
+  }
+
+  return (0);
+} /* }}} int fc_register_match */
+
+/* Add a target to list of available targets. */
+int fc_register_target (const char *name, target_proc_t proc) /* {{{ */
+{
+  fc_target_t *t;
+
+  DEBUG ("fc_register_target (%s);", name);
+
+  t = (fc_target_t *) malloc (sizeof (*t));
+  if (t == NULL)
+    return (-ENOMEM);
+  memset (t, 0, sizeof (*t));
+
+  sstrncpy (t->name, name, sizeof (t->name));
+  memcpy (&t->proc, &proc, sizeof (t->proc));
+  t->next = NULL;
+
+  if (target_list_head == NULL)
+  {
+    target_list_head = t;
+  }
+  else
+  {
+    fc_target_t *ptr;
+
+    ptr = target_list_head;
+    while (ptr->next != NULL)
+      ptr = ptr->next;
+
+    ptr->next = t;
+  }
+
+  return (0);
+} /* }}} int fc_register_target */
+
+fc_chain_t *fc_chain_get_by_name (const char *chain_name) /* {{{ */
+{
+  fc_chain_t *chain;
+
+  if (chain_name == NULL)
+    return (NULL);
+
+  for (chain = chain_list_head; chain != NULL; chain = chain->next)
+    if (strcasecmp (chain_name, chain->name) == 0)
+      return (chain);
+
+  return (NULL);
+} /* }}} int fc_chain_get_by_name */
+
+int fc_process_chain (const data_set_t *ds, value_list_t *vl, /* {{{ */
+    fc_chain_t *chain)
+{
+  fc_rule_t *rule;
+  fc_target_t *target;
+  int status;
+
+  if (chain == NULL)
+    return (-1);
+
+  DEBUG ("fc_process_chain (chain = %s);", chain->name);
+
+  status = FC_TARGET_CONTINUE;
+  for (rule = chain->rules; rule != NULL; rule = rule->next)
+  {
+    fc_match_t *match;
+
+    if (rule->name[0] != 0)
+    {
+      DEBUG ("fc_process_chain (%s): Testing the `%s' rule.",
+          chain->name, rule->name);
+    }
+
+    /* N. B.: rule->matches may be NULL. */
+    for (match = rule->matches; match != NULL; match = match->next)
+    {
+      /* FIXME: Pass the meta-data to match targets here (when implemented). */
+      status = (*match->proc.match) (ds, vl, /* meta = */ NULL,
+          &match->user_data);
+      if (status < 0)
+      {
+        WARNING ("fc_process_chain (%s): A match failed.", chain->name);
+        break;
+      }
+      else if (status != FC_MATCH_MATCHES)
+        break;
+    }
+
+    /* for-loop has been aborted: Either error or no match. */
+    if (match != NULL)
+    {
+      status = FC_TARGET_CONTINUE;
+      continue;
+    }
+
+    if (rule->name[0] != 0)
+    {
+      DEBUG ("fc_process_chain (%s): Rule `%s' matches.",
+          chain->name, rule->name);
+    }
+
+    for (target = rule->targets; target != NULL; target = target->next)
+    {
+      /* If we get here, all matches have matched the value. Execute the
+       * target. */
+      /* FIXME: Pass the meta-data to match targets here (when implemented). */
+      status = (*target->proc.invoke) (ds, vl, /* meta = */ NULL,
+          &target->user_data);
+      if (status < 0)
+      {
+        WARNING ("fc_process_chain (%s): A target failed.", chain->name);
+        continue;
+      }
+      else if (status == FC_TARGET_CONTINUE)
+        continue;
+      else if (status == FC_TARGET_STOP)
+        break;
+      else if (status == FC_TARGET_RETURN)
+        break;
+      else
+      {
+        WARNING ("fc_process_chain (%s): Unknown return value "
+            "from target `%s': %i",
+            chain->name, target->name, status);
+      }
+    }
+
+    if ((status == FC_TARGET_STOP)
+        || (status == FC_TARGET_RETURN))
+    {
+      if (rule->name[0] != 0)
+      {
+        DEBUG ("fc_process_chain (%s): Rule `%s' signaled "
+            "the %s condition.",
+            chain->name, rule->name,
+            (status == FC_TARGET_STOP) ? "stop" : "return");
+      }
+      break;
+    }
+    else
+    {
+      status = FC_TARGET_CONTINUE;
+    }
+  } /* for (rule) */
+
+  if (status == FC_TARGET_STOP)
+    return (FC_TARGET_STOP);
+  else if (status == FC_TARGET_RETURN)
+    return (FC_TARGET_CONTINUE);
+
+  /* for-loop has been aborted: A target returned `FC_TARGET_STOP' */
+  if (rule != NULL)
+    return (FC_TARGET_CONTINUE);
+
+  DEBUG ("fc_process_chain (%s): Executing the default targets.",
+      chain->name);
+
+  status = FC_TARGET_CONTINUE;
+  for (target = chain->targets; target != NULL; target = target->next)
+  {
+    /* If we get here, all matches have matched the value. Execute the
+     * target. */
+    /* FIXME: Pass the meta-data to match targets here (when implemented). */
+    status = (*target->proc.invoke) (ds, vl, /* meta = */ NULL,
+        &target->user_data);
+    if (status < 0)
+    {
+      WARNING ("fc_process_chain (%s): The default target failed.",
+          chain->name);
+    }
+    else if (status == FC_TARGET_CONTINUE)
+      continue;
+    else if (status == FC_TARGET_STOP)
+      break;
+    else if (status == FC_TARGET_RETURN)
+      break;
+    else
+    {
+      WARNING ("fc_process_chain (%s): Unknown return value "
+          "from target `%s': %i",
+          chain->name, target->name, status);
+    }
+  }
+
+  if ((status == FC_TARGET_STOP)
+      || (status == FC_TARGET_RETURN))
+  {
+    assert (target != NULL);
+    DEBUG ("fc_process_chain (%s): Default target `%s' signaled "
+        "the %s condition.",
+        chain->name, target->name,
+        (status == FC_TARGET_STOP) ? "stop" : "return");
+    if (status == FC_TARGET_STOP)
+      return (FC_TARGET_STOP);
+    else
+      return (FC_TARGET_CONTINUE);
+  }
+
+  DEBUG ("fc_process_chain (%s): Signaling `continue' at end of chain.",
+      chain->name);
+
+  return (FC_TARGET_CONTINUE);
+} /* }}} int fc_process_chain */
+
+/* Iterate over all rules in the chain and execute all targets for which all
+ * matches match. */
+int fc_default_action (const data_set_t *ds, value_list_t *vl) /* {{{ */
+{
+  /* FIXME: Pass the meta-data to match targets here (when implemented). */
+  return (fc_bit_write_invoke (ds, vl,
+        /* meta = */ NULL, /* user_data = */ NULL));
+} /* }}} int fc_default_action */
+
+int fc_configure (const oconfig_item_t *ci) /* {{{ */
+{
+  fc_init_once ();
+
+  if (ci == NULL)
+    return (-EINVAL);
+
+  if (strcasecmp ("Chain", ci->key) == 0)
+    return (fc_config_add_chain (ci));
+
+  WARNING ("Filter subsystem: Unknown top level config option `%s'.",
+      ci->key);
+
+  return (-1);
+} /* }}} int fc_configure */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/filter_chain.h b/src/filter_chain.h
new file mode 100644 (file)
index 0000000..187fe22
--- /dev/null
@@ -0,0 +1,101 @@
+/**
+ * collectd - src/filter_chain.h
+ * Copyright (C) 2008,2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef FILTER_CHAIN_H
+#define FILTER_CHAIN_H 1
+
+#include "collectd.h"
+#include "plugin.h"
+
+#define FC_MATCH_NO_MATCH  0
+#define FC_MATCH_MATCHES   1
+
+#define FC_TARGET_CONTINUE 0
+#define FC_TARGET_STOP     1
+#define FC_TARGET_RETURN   2
+
+/*
+ * Match functions
+ */
+struct match_proc_s
+{
+  int (*create) (const oconfig_item_t *ci, void **user_data);
+  int (*destroy) (void **user_data);
+  int (*match) (const data_set_t *ds, const value_list_t *vl,
+      notification_meta_t **meta, void **user_data);
+};
+typedef struct match_proc_s match_proc_t;
+
+int fc_register_match (const char *name, match_proc_t proc);
+
+/*
+ * Target functions
+ */
+struct target_proc_s
+{
+  int (*create) (const oconfig_item_t *ci, void **user_data);
+  int (*destroy) (void **user_data);
+  int (*invoke) (const data_set_t *ds, value_list_t *vl,
+      notification_meta_t **meta, void **user_data);
+};
+typedef struct target_proc_s target_proc_t;
+
+struct fc_chain_s;
+typedef struct fc_chain_s fc_chain_t;
+
+int fc_register_target (const char *name, target_proc_t proc);
+
+/*
+ * TODO: Chain management
+ */
+#if 0
+int fc_chain_add (const char *chain_name,
+    const char *target_name, int target_argc, char **target_argv);
+int fc_chain_delete (const char *chain_name);
+#endif
+
+/*
+ * TODO: Rule management
+ */
+#if 0
+int fc_rule_add (const char *chain_name, int position,
+    int match_num, const char **match_name, int *match_argc, char ***match_argv,
+    const char *target_name, int target_argc, char **target_argv);
+int fc_rule_delete (const char *chain_name, int position);
+#endif
+
+/*
+ * Processing function
+ */
+fc_chain_t *fc_chain_get_by_name (const char *chain_name);
+
+int fc_process_chain (const data_set_t *ds, value_list_t *vl,
+    fc_chain_t *chain);
+
+int fc_default_action (const data_set_t *ds, value_list_t *vl);
+
+/* 
+ * Shortcut for global configuration
+ */
+int fc_configure (const oconfig_item_t *ci);
+
+#endif /* FILTER_CHAIN_H */
+/* vim: set sw=2 sts=2 et : */
diff --git a/src/fscache.c b/src/fscache.c
new file mode 100644 (file)
index 0000000..8fbd271
--- /dev/null
@@ -0,0 +1,230 @@
+/**
+ * collectd - src/fscache.c
+ * Copyright (C) 2009 Edward "Koko" Konetzko
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Edward "Koko" Konetzko <konetzed at quixoticagony.com>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include <stdio.h>  /* a header needed for FILE */
+#include <string.h> /* a header needed for scanf function */
+#include <stdlib.h> /* used for atoi */
+
+
+#if !KERNEL_LINUX
+# error "This module only supports the Linux implementation of fscache"
+#endif
+
+#define BUFSIZE 1024
+
+/*
+see /proc/fs/fscache/stats
+see Documentation/filesystems/caching/fscache.txt in linux kernel >= 2.6.30
+
+This shows counts of a number of events that can happen in FS-Cache:
+
+CLASS   EVENT   MEANING
+======= ======= =======================================================
+Cookies idx=N   Number of index cookies allocated
+        dat=N   Number of data storage cookies allocated
+        spc=N   Number of special cookies allocated
+Objects alc=N   Number of objects allocated
+        nal=N   Number of object allocation failures
+        avl=N   Number of objects that reached the available state
+        ded=N   Number of objects that reached the dead state
+ChkAux  non=N   Number of objects that didn't have a coherency check
+        ok=N    Number of objects that passed a coherency check
+        upd=N   Number of objects that needed a coherency data update
+        obs=N   Number of objects that were declared obsolete
+Pages   mrk=N   Number of pages marked as being cached
+        unc=N   Number of uncache page requests seen
+Acquire n=N Number of acquire cookie requests seen
+        nul=N   Number of acq reqs given a NULL parent
+        noc=N   Number of acq reqs rejected due to no cache available
+        ok=N    Number of acq reqs succeeded
+        nbf=N   Number of acq reqs rejected due to error
+        oom=N   Number of acq reqs failed on ENOMEM
+Lookups n=N Number of lookup calls made on cache backends
+        neg=N   Number of negative lookups made
+        pos=N   Number of positive lookups made
+        crt=N   Number of objects created by lookup
+Updates n=N Number of update cookie requests seen
+        nul=N   Number of upd reqs given a NULL parent
+        run=N   Number of upd reqs granted CPU time
+Relinqs n=N Number of relinquish cookie requests seen
+        nul=N   Number of rlq reqs given a NULL parent
+        wcr=N   Number of rlq reqs waited on completion of creation
+AttrChg n=N Number of attribute changed requests seen
+        ok=N    Number of attr changed requests queued
+        nbf=N   Number of attr changed rejected -ENOBUFS
+        oom=N   Number of attr changed failed -ENOMEM
+        run=N   Number of attr changed ops given CPU time
+Allocs  n=N Number of allocation requests seen
+        ok=N    Number of successful alloc reqs
+        wt=N    Number of alloc reqs that waited on lookup completion
+        nbf=N   Number of alloc reqs rejected -ENOBUFS
+        ops=N   Number of alloc reqs submitted
+        owt=N   Number of alloc reqs waited for CPU time
+Retrvls n=N Number of retrieval (read) requests seen
+        ok=N    Number of successful retr reqs
+        wt=N    Number of retr reqs that waited on lookup completion
+        nod=N   Number of retr reqs returned -ENODATA
+        nbf=N   Number of retr reqs rejected -ENOBUFS
+        int=N   Number of retr reqs aborted -ERESTARTSYS
+        oom=N   Number of retr reqs failed -ENOMEM
+        ops=N   Number of retr reqs submitted
+        owt=N   Number of retr reqs waited for CPU time
+Stores  n=N Number of storage (write) requests seen
+        ok=N    Number of successful store reqs
+        agn=N   Number of store reqs on a page already pending storage
+        nbf=N   Number of store reqs rejected -ENOBUFS
+        oom=N   Number of store reqs failed -ENOMEM
+        ops=N   Number of store reqs submitted
+        run=N   Number of store reqs granted CPU time
+Ops pend=N  Number of times async ops added to pending queues
+        run=N   Number of times async ops given CPU time
+        enq=N   Number of times async ops queued for processing
+        dfr=N   Number of async ops queued for deferred release
+        rel=N   Number of async ops released
+        gc=N    Number of deferred-release async ops garbage collected
+
+63 events to collect in 13 groups
+*/
+static void fscache_submit (const char *section, const char *name,
+        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, "fscache", sizeof (vl.plugin));
+    sstrncpy(vl.plugin_instance, section, sizeof (vl.plugin_instance));
+    sstrncpy(vl.type, "fscache_stat", sizeof(vl.type));
+    sstrncpy(vl.type_instance, name, sizeof(vl.type_instance));
+
+    plugin_dispatch_values (&vl);
+}
+
+static void fscache_read_stats_file (FILE *fh)
+{
+    char section[DATA_MAX_NAME_LEN];
+    size_t section_len;
+
+    char linebuffer[BUFSIZE];
+
+/*
+ *  cat /proc/fs/fscache/stats
+ *      FS-Cache statistics
+ *      Cookies: idx=2 dat=0 spc=0
+ *      Objects: alc=0 nal=0 avl=0 ded=0
+ *      ChkAux : non=0 ok=0 upd=0 obs=0
+ *      Pages  : mrk=0 unc=0
+ *      Acquire: n=2 nul=0 noc=0 ok=2 nbf=0 oom=0
+ *      Lookups: n=0 neg=0 pos=0 crt=0
+ *      Updates: n=0 nul=0 run=0
+ *      Relinqs: n=0 nul=0 wcr=0
+ *      AttrChg: n=0 ok=0 nbf=0 oom=0 run=0
+ *      Allocs : n=0 ok=0 wt=0 nbf=0
+ *      Allocs : ops=0 owt=0
+ *      Retrvls: n=0 ok=0 wt=0 nod=0 nbf=0 int=0 oom=0
+ *      Retrvls: ops=0 owt=0
+ *      Stores : n=0 ok=0 agn=0 nbf=0 oom=0
+ *      Stores : ops=0 run=0
+ *      Ops    : pend=0 run=0 enq=0
+ *      Ops    : dfr=0 rel=0 gc=0
+ */
+
+    /* Read file line by line */
+    while (fgets (linebuffer, sizeof (linebuffer), fh) != NULL)
+    {
+        char *lineptr;
+        char *fields[32];
+        int fields_num;
+        int i;
+
+        /* Find the colon and replace it with a null byte */
+        lineptr = strchr (linebuffer, ':');
+        if (lineptr == NULL)
+            continue;
+        *lineptr = 0;
+        lineptr++;
+
+        /* Copy and clean up the section name */
+        sstrncpy (section, linebuffer, sizeof (section));
+        section_len = strlen (section);
+        while ((section_len > 0) && isspace ((int) section[section_len - 1]))
+        {
+            section_len--;
+            section[section_len] = 0;
+        }
+        if (section_len <= 0)
+            continue;
+
+        fields_num = strsplit (lineptr, fields, STATIC_ARRAY_SIZE (fields));
+        if (fields_num <= 0)
+            continue;
+
+        for (i = 0; i < fields_num; i++)
+        {
+            char *field_name;
+            char *field_value_str;
+            value_t field_value_cnt;
+            int status;
+
+            field_name = fields[i];
+            assert (field_name != NULL);
+
+            field_value_str = strchr (field_name, '=');
+            if (field_value_str == NULL)
+                continue;
+            *field_value_str = 0;
+            field_value_str++;
+
+            status = parse_value (field_value_str, &field_value_cnt,
+                    DS_TYPE_DERIVE);
+            if (status != 0)
+                continue;
+
+            fscache_submit (section, field_name, field_value_cnt);
+        }
+    } /* while (fgets) */
+} /* void fscache_read_stats_file */
+
+static int fscache_read (void){
+    FILE *fh;
+    fh = fopen("/proc/fs/fscache/stats", "r");
+    if (fh != NULL){
+        fscache_read_stats_file(fh);
+        fclose(fh);
+
+    }else{
+        printf("cant open file\n");
+        return (-1);
+    }
+    return (0);
+}
+
+void module_register (void)
+{
+    plugin_register_read ("fscache", fscache_read);
+} /* void module_register */
+
+/* vim: set sw=4 sts=4 et : */
diff --git a/src/gmond.c b/src/gmond.c
new file mode 100644 (file)
index 0000000..3c746c4
--- /dev/null
@@ -0,0 +1,1124 @@
+/**
+ * collectd - src/gmond.c
+ * Copyright (C) 2009,2010  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at collectd.org>
+ **/
+
+#include "collectd.h"
+#include "plugin.h"
+#include "common.h"
+#include "configfile.h"
+#include "utils_avltree.h"
+
+#if HAVE_PTHREAD_H
+# include <pthread.h>
+#endif
+#if HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif
+#if HAVE_NETDB_H
+# include <netdb.h>
+#endif
+#if HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+#if HAVE_ARPA_INET_H
+# include <arpa/inet.h>
+#endif
+#if HAVE_POLL_H
+# include <poll.h>
+#endif
+
+#include <gm_protocol.h>
+
+#ifndef IPV6_ADD_MEMBERSHIP
+# ifdef IPV6_JOIN_GROUP
+#  define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP
+# else
+#  error "Neither IP_ADD_MEMBERSHIP nor IPV6_JOIN_GROUP is defined"
+# endif
+#endif /* !IP_ADD_MEMBERSHIP */
+
+#ifdef GANGLIA_MAX_MESSAGE_LEN
+# define BUFF_SIZE GANGLIA_MAX_MESSAGE_LEN
+#else
+# define BUFF_SIZE 1400
+#endif
+
+struct socket_entry_s
+{
+  int                     fd;
+  struct sockaddr_storage addr;
+  socklen_t               addrlen;
+};
+typedef struct socket_entry_s socket_entry_t;
+
+struct staging_entry_s
+{
+  char key[2 * DATA_MAX_NAME_LEN];
+  value_list_t vl;
+  int flags;
+};
+typedef struct staging_entry_s staging_entry_t;
+
+struct metric_map_s
+{
+  char *ganglia_name;
+  char *type;
+  char *type_instance;
+  char *ds_name;
+  int   ds_type;
+  int   ds_index;
+};
+typedef struct metric_map_s metric_map_t;
+
+#define MC_RECEIVE_GROUP_DEFAULT "239.2.11.71"
+static char          *mc_receive_group = NULL;
+#define MC_RECEIVE_PORT_DEFAULT "8649"
+static char          *mc_receive_port = NULL;
+
+static struct pollfd *mc_receive_sockets = NULL;
+static size_t         mc_receive_sockets_num = 0;
+
+static socket_entry_t  *mc_send_sockets = NULL;
+static size_t           mc_send_sockets_num = 0;
+static pthread_mutex_t  mc_send_sockets_lock = PTHREAD_MUTEX_INITIALIZER;
+
+static int            mc_receive_thread_loop    = 0;
+static int            mc_receive_thread_running = 0;
+static pthread_t      mc_receive_thread_id;
+
+static metric_map_t metric_map_default[] =
+{ /*---------------+-------------+-----------+-------------+------+-----*
+   * ganglia_name  ! type        ! type_inst ! data_source ! type ! idx *
+   *---------------+-------------+-----------+-------------+------+-----*/
+  { "load_one",     "load",       "",         "shortterm",     -1,   -1 },
+  { "load_five",    "load",       "",         "midterm",       -1,   -1 },
+  { "load_fifteen", "load",       "",         "longterm",      -1,   -1 },
+  { "cpu_user",     "cpu",        "user",     "value",         -1,   -1 },
+  { "cpu_system",   "cpu",        "system",   "value",         -1,   -1 },
+  { "cpu_idle",     "cpu",        "idle",     "value",         -1,   -1 },
+  { "cpu_nice",     "cpu",        "nice",     "value",         -1,   -1 },
+  { "cpu_wio",      "cpu",        "wait",     "value",         -1,   -1 },
+  { "mem_free",     "memory",     "free",     "value",         -1,   -1 },
+  { "mem_shared",   "memory",     "shared",   "value",         -1,   -1 },
+  { "mem_buffers",  "memory",     "buffered", "value",         -1,   -1 },
+  { "mem_cached",   "memory",     "cached",   "value",         -1,   -1 },
+  { "mem_total",    "memory",     "total",    "value",         -1,   -1 },
+  { "bytes_in",     "if_octets",  "",         "rx",            -1,   -1 },
+  { "bytes_out",    "if_octets",  "",         "tx",            -1,   -1 },
+  { "pkts_in",      "if_packets", "",         "rx",            -1,   -1 },
+  { "pkts_out",     "if_packets", "",         "tx",            -1,   -1 }
+};
+static size_t metric_map_len_default = STATIC_ARRAY_SIZE (metric_map_default);
+
+static metric_map_t *metric_map = NULL;
+static size_t        metric_map_len = 0;
+
+static c_avl_tree_t   *staging_tree;
+static pthread_mutex_t staging_lock = PTHREAD_MUTEX_INITIALIZER;
+
+static metric_map_t *metric_lookup (const char *key) /* {{{ */
+{
+  metric_map_t *map;
+  size_t map_len;
+  size_t i;
+
+  /* Search the user-supplied table first.. */
+  map = metric_map;
+  map_len = metric_map_len;
+  for (i = 0; i < map_len; i++)
+    if (strcmp (map[i].ganglia_name, key) == 0)
+      break;
+
+  /* .. and fall back to the built-in table if nothing is found. */
+  if (i >= map_len)
+  {
+    map = metric_map_default;
+    map_len = metric_map_len_default;
+
+    for (i = 0; i < map_len; i++)
+      if (strcmp (map[i].ganglia_name, key) == 0)
+        break;
+  }
+
+  if (i >= map_len)
+    return (NULL);
+
+  /* Look up the DS type and ds_index. */
+  if ((map[i].ds_type < 0) || (map[i].ds_index < 0)) /* {{{ */
+  {
+    const data_set_t *ds;
+
+    ds = plugin_get_ds (map[i].type);
+    if (ds == NULL)
+    {
+      WARNING ("gmond plugin: Type not defined: %s", map[i].type);
+      return (NULL);
+    }
+
+    if ((map[i].ds_name == NULL) && (ds->ds_num != 1))
+    {
+      WARNING ("gmond plugin: No data source name defined for metric %s, "
+          "but type %s has more than one data source.",
+          map[i].ganglia_name, map[i].type);
+      return (NULL);
+    }
+
+    if (map[i].ds_name == NULL)
+    {
+      map[i].ds_index = 0;
+    }
+    else
+    {
+      int j;
+
+      for (j = 0; j < ds->ds_num; j++)
+        if (strcasecmp (ds->ds[j].name, map[i].ds_name) == 0)
+          break;
+
+      if (j >= ds->ds_num)
+      {
+        WARNING ("gmond plugin: There is no data source "
+            "named `%s' in type `%s'.",
+            map[i].ds_name, ds->type);
+        return (NULL);
+      }
+      map[i].ds_index = j;
+    }
+
+    map[i].ds_type = ds->ds[map[i].ds_index].type;
+  } /* }}} if ((map[i].ds_type < 0) || (map[i].ds_index < 0)) */
+
+  return (map + i);
+} /* }}} metric_map_t *metric_lookup */
+
+static int create_sockets (socket_entry_t **ret_sockets, /* {{{ */
+    size_t *ret_sockets_num,
+    const char *node, const char *service, int listen)
+{
+  struct addrinfo  ai_hints;
+  struct addrinfo *ai_list;
+  struct addrinfo *ai_ptr;
+  int              ai_return;
+
+  socket_entry_t *sockets;
+  size_t          sockets_num;
+
+  int status;
+    
+  sockets     = *ret_sockets;
+  sockets_num = *ret_sockets_num;
+
+  memset (&ai_hints, 0, sizeof (ai_hints));
+  ai_hints.ai_flags    = 0;
+#ifdef AI_PASSIVE
+  ai_hints.ai_flags |= AI_PASSIVE;
+#endif
+#ifdef AI_ADDRCONFIG
+  ai_hints.ai_flags |= AI_ADDRCONFIG;
+#endif
+  ai_hints.ai_family   = AF_UNSPEC;
+  ai_hints.ai_socktype = SOCK_DGRAM;
+  ai_hints.ai_protocol = IPPROTO_UDP;
+
+  ai_return = getaddrinfo (node, service, &ai_hints, &ai_list);
+  if (ai_return != 0)
+  {
+    char errbuf[1024];
+    ERROR ("gmond plugin: getaddrinfo (%s, %s) failed: %s",
+        (node == NULL) ? "(null)" : node,
+        (service == NULL) ? "(null)" : service,
+        (ai_return == EAI_SYSTEM)
+        ? sstrerror (errno, errbuf, sizeof (errbuf))
+        : gai_strerror (ai_return));
+    return (-1);
+  }
+
+  for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) /* {{{ */
+  {
+    socket_entry_t *tmp;
+
+    tmp = realloc (sockets, (sockets_num + 1) * sizeof (*sockets));
+    if (tmp == NULL)
+    {
+      ERROR ("gmond plugin: realloc failed.");
+      continue;
+    }
+    sockets = tmp;
+
+    sockets[sockets_num].fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype,
+        ai_ptr->ai_protocol);
+    if (sockets[sockets_num].fd < 0)
+    {
+      char errbuf[1024];
+      ERROR ("gmond plugin: socket failed: %s",
+          sstrerror (errno, errbuf, sizeof (errbuf)));
+      continue;
+    }
+
+    assert (sizeof (sockets[sockets_num].addr) >= ai_ptr->ai_addrlen);
+    memcpy (&sockets[sockets_num].addr, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
+    sockets[sockets_num].addrlen = ai_ptr->ai_addrlen;
+
+    /* Sending socket: Open only one socket and don't bind it. */
+    if (listen == 0)
+    {
+      sockets_num++;
+      break;
+    }
+    else
+    {
+      int yes = 1;
+
+      setsockopt (sockets[sockets_num].fd, SOL_SOCKET, SO_REUSEADDR,
+          (void *) &yes, sizeof (yes));
+    }
+
+    status = bind (sockets[sockets_num].fd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
+    if (status != 0)
+    {
+      char errbuf[1024];
+      ERROR ("gmond plugin: bind failed: %s",
+          sstrerror (errno, errbuf, sizeof (errbuf)));
+      close (sockets[sockets_num].fd);
+      continue;
+    }
+
+    if (ai_ptr->ai_family == AF_INET)
+    {
+      struct sockaddr_in *addr;
+      struct ip_mreq mreq;
+      int loop;
+
+      addr = (struct sockaddr_in *) ai_ptr->ai_addr;
+
+      if (!IN_MULTICAST (ntohl (addr->sin_addr.s_addr)))
+      {
+        sockets_num++;
+        continue;
+      }
+
+      loop = 1;
+      setsockopt (sockets[sockets_num].fd, IPPROTO_IP, IP_MULTICAST_LOOP,
+          (void *) &loop, sizeof (loop));
+
+      memset (&mreq, 0, sizeof (mreq));
+      mreq.imr_multiaddr.s_addr = addr->sin_addr.s_addr;
+      mreq.imr_interface.s_addr = htonl (INADDR_ANY);
+      setsockopt (sockets[sockets_num].fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+          (void *) &mreq, sizeof (mreq));
+    } /* if (ai_ptr->ai_family == AF_INET) */
+    else if (ai_ptr->ai_family == AF_INET6)
+    {
+      struct sockaddr_in6 *addr;
+      struct ipv6_mreq mreq;
+      int loop;
+
+      addr = (struct sockaddr_in6 *) ai_ptr->ai_addr;
+
+      if (!IN6_IS_ADDR_MULTICAST (&addr->sin6_addr))
+      {
+        sockets_num++;
+        continue;
+      }
+
+      loop = 1;
+      setsockopt (sockets[sockets_num].fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP,
+          (void *) &loop, sizeof (loop));
+
+      memset (&mreq, 0, sizeof (mreq));
+      memcpy (&mreq.ipv6mr_multiaddr,
+          &addr->sin6_addr, sizeof (addr->sin6_addr));
+      mreq.ipv6mr_interface = 0; /* any */
+      setsockopt (sockets[sockets_num].fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP,
+          (void *) &mreq, sizeof (mreq));
+    } /* if (ai_ptr->ai_family == AF_INET6) */
+
+    sockets_num++;
+  } /* }}} for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) */
+
+  freeaddrinfo (ai_list);
+
+  if ((*ret_sockets_num) >= sockets_num)
+    return (-1);
+
+  *ret_sockets = sockets;
+  *ret_sockets_num = sockets_num;
+  return (0);
+} /* }}} int create_sockets */
+
+static int request_meta_data (const char *host, const char *name) /* {{{ */
+{
+  Ganglia_metadata_msg msg;
+  char buffer[BUFF_SIZE];
+  unsigned int buffer_size;
+  XDR xdr;
+  size_t i;
+
+  memset (&msg, 0, sizeof (msg));
+
+  msg.id = gmetadata_request;
+  msg.Ganglia_metadata_msg_u.grequest.metric_id.host = strdup (host);
+  msg.Ganglia_metadata_msg_u.grequest.metric_id.name = strdup (name);
+
+  if ((msg.Ganglia_metadata_msg_u.grequest.metric_id.host == NULL)
+      || (msg.Ganglia_metadata_msg_u.grequest.metric_id.name == NULL))
+  {
+    sfree (msg.Ganglia_metadata_msg_u.grequest.metric_id.host);
+    sfree (msg.Ganglia_metadata_msg_u.grequest.metric_id.name);
+    return (-1);
+  }
+
+  memset (buffer, 0, sizeof (buffer));
+  xdrmem_create (&xdr, buffer, sizeof (buffer), XDR_ENCODE);
+
+  if (!xdr_Ganglia_metadata_msg (&xdr, &msg))
+  {
+    sfree (msg.Ganglia_metadata_msg_u.grequest.metric_id.host);
+    sfree (msg.Ganglia_metadata_msg_u.grequest.metric_id.name);
+    return (-1);
+  }
+
+  buffer_size = xdr_getpos (&xdr);
+
+  DEBUG ("gmond plugin: Requesting meta data for %s/%s.",
+      host, name);
+
+  pthread_mutex_lock (&mc_send_sockets_lock);
+  for (i = 0; i < mc_send_sockets_num; i++)
+    sendto (mc_send_sockets[i].fd, buffer, (size_t) buffer_size,
+        /* flags = */ 0,
+        (struct sockaddr *) &mc_send_sockets[i].addr,
+        mc_send_sockets[i].addrlen);
+  pthread_mutex_unlock (&mc_send_sockets_lock);
+
+  sfree (msg.Ganglia_metadata_msg_u.grequest.metric_id.host);
+  sfree (msg.Ganglia_metadata_msg_u.grequest.metric_id.name);
+  return (0);
+} /* }}} int request_meta_data */
+
+static staging_entry_t *staging_entry_get (const char *host, /* {{{ */
+    const char *name,
+    const char *type, const char *type_instance,
+    int values_len)
+{
+  char key[2 * DATA_MAX_NAME_LEN];
+  staging_entry_t *se;
+  int status;
+
+  if (staging_tree == NULL)
+    return (NULL);
+
+  ssnprintf (key, sizeof (key), "%s/%s/%s", host, type,
+      (type_instance != NULL) ? type_instance : "");
+
+  se = NULL;
+  status = c_avl_get (staging_tree, key, (void *) &se);
+  if (status == 0)
+    return (se);
+
+  /* insert new entry */
+  se = (staging_entry_t *) malloc (sizeof (*se));
+  if (se == NULL)
+    return (NULL);
+  memset (se, 0, sizeof (*se));
+
+  sstrncpy (se->key, key, sizeof (se->key));
+  se->flags = 0;
+
+  se->vl.values = (value_t *) calloc (values_len, sizeof (*se->vl.values));
+  if (se->vl.values == NULL)
+  {
+    sfree (se);
+    return (NULL);
+  }
+  se->vl.values_len = values_len;
+
+  se->vl.time = 0;
+  se->vl.interval = 0;
+  sstrncpy (se->vl.host, host, sizeof (se->vl.host));
+  sstrncpy (se->vl.plugin, "gmond", sizeof (se->vl.plugin));
+  sstrncpy (se->vl.type, type, sizeof (se->vl.type));
+  if (type_instance != NULL)
+    sstrncpy (se->vl.type_instance, type_instance,
+        sizeof (se->vl.type_instance));
+
+  status = c_avl_insert (staging_tree, se->key, se);
+  if (status != 0)
+  {
+    ERROR ("gmond plugin: c_avl_insert failed.");
+    sfree (se->vl.values);
+    sfree (se);
+    return (NULL);
+  }
+
+  return (se);
+} /* }}} staging_entry_t *staging_entry_get */
+
+static int staging_entry_submit (const char *host, const char *name, /* {{{ */
+    staging_entry_t *se)
+{
+  value_list_t vl;
+  value_t values[se->vl.values_len];
+
+  if (se->vl.interval == 0)
+  {
+    /* No meta data has been received for this metric yet. */
+    se->flags = 0;
+    pthread_mutex_unlock (&staging_lock);
+    request_meta_data (host, name);
+    return (0);
+  }
+
+  se->flags = 0;
+
+  memcpy (values, se->vl.values, sizeof (values));
+  memcpy (&vl, &se->vl, sizeof (vl));
+
+  /* Unlock before calling `plugin_dispatch_values'.. */
+  pthread_mutex_unlock (&staging_lock);
+
+  vl.values = values;
+
+  plugin_dispatch_values (&vl);
+
+  return (0);
+} /* }}} int staging_entry_submit */
+
+static int staging_entry_update (const char *host, const char *name, /* {{{ */
+    const char *type, const char *type_instance,
+    int ds_index, int ds_type, value_t value)
+{
+  const data_set_t *ds;
+  staging_entry_t *se;
+
+  ds = plugin_get_ds (type);
+  if (ds == NULL)
+  {
+    ERROR ("gmond plugin: Looking up type %s failed.", type);
+    return (-1);
+  }
+
+  if (ds->ds_num <= ds_index)
+  {
+    ERROR ("gmond plugin: Invalid index %i: %s has only %i data source(s).",
+        ds_index, ds->type, ds->ds_num);
+    return (-1);
+  }
+
+  pthread_mutex_lock (&staging_lock);
+
+  se = staging_entry_get (host, name, type, type_instance, ds->ds_num);
+  if (se == NULL)
+  {
+    pthread_mutex_unlock (&staging_lock);
+    ERROR ("gmond plugin: staging_entry_get failed.");
+    return (-1);
+  }
+  if (se->vl.values_len != ds->ds_num)
+  {
+    pthread_mutex_unlock (&staging_lock);
+    return (-1);
+  }
+
+  if (ds_type == DS_TYPE_COUNTER)
+    se->vl.values[ds_index].counter += value.counter;
+  else if (ds_type == DS_TYPE_GAUGE)
+    se->vl.values[ds_index].gauge = value.gauge;
+  else if (ds_type == DS_TYPE_DERIVE)
+    se->vl.values[ds_index].derive += value.derive;
+  else if (ds_type == DS_TYPE_ABSOLUTE)
+    se->vl.values[ds_index].absolute = value.absolute;
+  else
+    assert (23 == 42);
+
+  se->flags |= (0x01 << ds_index);
+
+  /* Check if all values have been set and submit if so. */
+  if (se->flags == ((0x01 << se->vl.values_len) - 1))
+  {
+    /* `staging_lock' is unlocked in `staging_entry_submit'. */
+    staging_entry_submit (host, name, se);
+  }
+  else
+  {
+    pthread_mutex_unlock (&staging_lock);
+  }
+
+  return (0);
+} /* }}} int staging_entry_update */
+
+static int mc_handle_value_msg (Ganglia_value_msg *msg) /* {{{ */
+{
+  const char *host;
+  const char *name;
+  metric_map_t *map;
+
+  value_t value_counter;
+  value_t value_gauge;
+  value_t value_derive;
+
+  /* Fill in `host', `name', `value_counter', and `value_gauge' according to
+   * the value type, or return with an error. */
+  switch (msg->id) /* {{{ */
+  {
+    case gmetric_uint:
+    {
+      Ganglia_gmetric_uint msg_uint;
+
+      msg_uint = msg->Ganglia_value_msg_u.gu_int;
+
+      host = msg_uint.metric_id.host;
+      name = msg_uint.metric_id.name;
+      value_counter.counter = (counter_t) msg_uint.ui;
+      value_gauge.gauge = (gauge_t) msg_uint.ui;
+      value_derive.derive = (derive_t) msg_uint.ui;
+      break;
+    }
+
+    case gmetric_string:
+    {
+      Ganglia_gmetric_string msg_string;
+      int status;
+
+      msg_string = msg->Ganglia_value_msg_u.gstr;
+
+      host = msg_string.metric_id.host;
+      name = msg_string.metric_id.name;
+
+      status = parse_value (msg_string.str, &value_derive, DS_TYPE_DERIVE);
+      if (status != 0)
+        value_derive.derive = -1;
+
+      status = parse_value (msg_string.str, &value_gauge, DS_TYPE_GAUGE);
+      if (status != 0)
+        value_gauge.gauge = NAN;
+
+      status = parse_value (msg_string.str, &value_counter, DS_TYPE_COUNTER);
+      if (status != 0)
+        value_counter.counter = 0;
+
+      break;
+    }
+
+    case gmetric_float:
+    {
+      Ganglia_gmetric_float msg_float;
+
+      msg_float = msg->Ganglia_value_msg_u.gf;
+
+      host = msg_float.metric_id.host;
+      name = msg_float.metric_id.name;
+      value_counter.counter = (counter_t) msg_float.f;
+      value_gauge.gauge = (gauge_t) msg_float.f;
+      value_derive.derive = (derive_t) msg_float.f;
+      break;
+    }
+
+    case gmetric_double:
+    {
+      Ganglia_gmetric_double msg_double;
+
+      msg_double = msg->Ganglia_value_msg_u.gd;
+
+      host = msg_double.metric_id.host;
+      name = msg_double.metric_id.name;
+      value_counter.counter = (counter_t) msg_double.d;
+      value_gauge.gauge = (gauge_t) msg_double.d;
+      value_derive.derive = (derive_t) msg_double.d;
+      break;
+    }
+    default:
+      DEBUG ("gmond plugin: Value type not handled: %i", msg->id);
+      return (-1);
+  } /* }}} switch (msg->id) */
+
+  assert (host != NULL);
+  assert (name != NULL);
+
+  map = metric_lookup (name);
+  if (map != NULL)
+  {
+    value_t val_copy;
+
+    if ((map->ds_type == DS_TYPE_COUNTER)
+        || (map->ds_type == DS_TYPE_ABSOLUTE))
+      val_copy = value_counter;
+    if (map->ds_type == DS_TYPE_GAUGE)
+      val_copy = value_gauge;
+    else if (map->ds_type == DS_TYPE_DERIVE)
+      val_copy = value_derive;
+    else
+      assert (23 == 42);
+
+    return (staging_entry_update (host, name,
+          map->type, map->type_instance,
+          map->ds_index, map->ds_type,
+          val_copy));
+  }
+
+  DEBUG ("gmond plugin: Cannot find a translation for %s.", name);
+  return (-1);
+} /* }}} int mc_handle_value_msg */
+
+static int mc_handle_metadata_msg (Ganglia_metadata_msg *msg) /* {{{ */
+{
+  switch (msg->id)
+  {
+    case gmetadata_full:
+    {
+      Ganglia_metadatadef msg_meta;
+      staging_entry_t *se;
+      const data_set_t *ds;
+      metric_map_t *map;
+
+      msg_meta = msg->Ganglia_metadata_msg_u.gfull;
+
+      if (msg_meta.metric.tmax <= 0)
+        return (-1);
+
+      map = metric_lookup (msg_meta.metric_id.name);
+      if (map == NULL)
+      {
+        DEBUG ("gmond plugin: Not handling meta data %s.",
+            msg_meta.metric_id.name);
+        return (0);
+      }
+
+      ds = plugin_get_ds (map->type);
+      if (ds == NULL)
+      {
+        WARNING ("gmond plugin: Could not find data set %s.", map->type);
+        return (-1);
+      }
+
+      DEBUG ("gmond plugin: Received meta data for %s/%s.",
+          msg_meta.metric_id.host, msg_meta.metric_id.name);
+
+      pthread_mutex_lock (&staging_lock);
+      se = staging_entry_get (msg_meta.metric_id.host,
+          msg_meta.metric_id.name,
+          map->type, map->type_instance,
+          ds->ds_num);
+      if (se != NULL)
+        se->vl.interval = TIME_T_TO_CDTIME_T (msg_meta.metric.tmax);
+      pthread_mutex_unlock (&staging_lock);
+
+      if (se == NULL)
+      {
+        ERROR ("gmond plugin: staging_entry_get failed.");
+        return (-1);
+      }
+
+      break;
+    }
+
+    default:
+    {
+      return (-1);
+    }
+  }
+
+  return (0);
+} /* }}} int mc_handle_metadata_msg */
+
+static int mc_handle_metric (void *buffer, size_t buffer_size) /* {{{ */
+{
+  XDR xdr;
+  Ganglia_msg_formats format;
+
+  xdrmem_create (&xdr, buffer, buffer_size, XDR_DECODE);
+
+  xdr_Ganglia_msg_formats (&xdr, &format);
+  xdr_setpos (&xdr, 0);
+
+  switch (format)
+  {
+    case gmetric_ushort:
+    case gmetric_short:
+    case gmetric_int:
+    case gmetric_uint:
+    case gmetric_string:
+    case gmetric_float:
+    case gmetric_double:
+    {
+      Ganglia_value_msg msg;
+
+      memset (&msg, 0, sizeof (msg));
+      if (xdr_Ganglia_value_msg (&xdr, &msg))
+        mc_handle_value_msg (&msg);
+      break;
+    }
+
+    case gmetadata_full:
+    case gmetadata_request:
+    {
+      Ganglia_metadata_msg msg;
+      memset (&msg, 0, sizeof (msg));
+      if (xdr_Ganglia_metadata_msg (&xdr, &msg))
+        mc_handle_metadata_msg (&msg);
+      break;
+    }
+
+    default:
+      DEBUG ("gmond plugin: Unknown format: %i", format);
+      return (-1);
+  } /* switch (format) */
+
+
+  return (0);
+} /* }}} int mc_handle_metric */
+
+static int mc_handle_socket (struct pollfd *p) /* {{{ */
+{
+  char buffer[BUFF_SIZE];
+  ssize_t buffer_size;
+
+  if ((p->revents & (POLLIN | POLLPRI)) == 0)
+  {
+    p->revents = 0;
+    return (-1);
+  }
+
+  buffer_size = recv (p->fd, buffer, sizeof (buffer), /* flags = */ 0);
+  if (buffer_size <= 0)
+  {
+    char errbuf[1024];
+    ERROR ("gmond plugin: recv failed: %s",
+        sstrerror (errno, errbuf, sizeof (errbuf)));
+    p->revents = 0;
+    return (-1);
+  }
+
+  mc_handle_metric (buffer, (size_t) buffer_size);
+  return (0);
+} /* }}} int mc_handle_socket */
+
+static void *mc_receive_thread (void *arg) /* {{{ */
+{
+  socket_entry_t *mc_receive_socket_entries;
+  int status;
+  size_t i;
+
+  mc_receive_socket_entries = NULL;
+  status = create_sockets (&mc_receive_socket_entries, &mc_receive_sockets_num,
+      (mc_receive_group != NULL) ? mc_receive_group : MC_RECEIVE_GROUP_DEFAULT,
+      (mc_receive_port != NULL) ? mc_receive_port : MC_RECEIVE_PORT_DEFAULT,
+      /* listen = */ 1);
+  if (status != 0)
+  {
+    ERROR ("gmond plugin: create_sockets failed.");
+    return ((void *) -1);
+  }
+
+  mc_receive_sockets = (struct pollfd *) calloc (mc_receive_sockets_num,
+      sizeof (*mc_receive_sockets));
+  if (mc_receive_sockets == NULL)
+  {
+    ERROR ("gmond plugin: calloc failed.");
+    for (i = 0; i < mc_receive_sockets_num; i++)
+      close (mc_receive_socket_entries[i].fd);
+    free (mc_receive_socket_entries);
+    mc_receive_socket_entries = NULL;
+    mc_receive_sockets_num = 0;
+    return ((void *) -1);
+  }
+
+  for (i = 0; i < mc_receive_sockets_num; i++)
+  {
+    mc_receive_sockets[i].fd = mc_receive_socket_entries[i].fd;
+    mc_receive_sockets[i].events = POLLIN | POLLPRI;
+    mc_receive_sockets[i].revents = 0;
+  }
+
+  while (mc_receive_thread_loop != 0)
+  {
+    status = poll (mc_receive_sockets, mc_receive_sockets_num, -1);
+    if (status <= 0)
+    {
+      char errbuf[1024];
+      if (errno == EINTR)
+        continue;
+      ERROR ("gmond plugin: poll failed: %s",
+          sstrerror (errno, errbuf, sizeof (errbuf)));
+      break;
+    }
+
+    for (i = 0; i < mc_receive_sockets_num; i++)
+    {
+      if (mc_receive_sockets[i].revents != 0)
+        mc_handle_socket (mc_receive_sockets + i);
+    }
+  } /* while (mc_receive_thread_loop != 0) */
+
+  return ((void *) 0);
+} /* }}} void *mc_receive_thread */
+
+static int mc_receive_thread_start (void) /* {{{ */
+{
+  int status;
+
+  if (mc_receive_thread_running != 0)
+    return (-1);
+
+  mc_receive_thread_loop = 1;
+
+  status = pthread_create (&mc_receive_thread_id, /* attr = */ NULL,
+      mc_receive_thread, /* args = */ NULL);
+  if (status != 0)
+  {
+    ERROR ("gmond plugin: Starting receive thread failed.");
+    mc_receive_thread_loop = 0;
+    return (-1);
+  }
+
+  mc_receive_thread_running = 1;
+  return (0);
+} /* }}} int start_receive_thread */
+
+static int mc_receive_thread_stop (void) /* {{{ */
+{
+  if (mc_receive_thread_running == 0)
+    return (-1);
+
+  mc_receive_thread_loop = 0;
+
+  INFO ("gmond plugin: Stopping receive thread.");
+  pthread_kill (mc_receive_thread_id, SIGTERM);
+  pthread_join (mc_receive_thread_id, /* return value = */ NULL);
+  memset (&mc_receive_thread_id, 0, sizeof (mc_receive_thread_id));
+
+  mc_receive_thread_running = 0;
+
+  return (0);
+} /* }}} int mc_receive_thread_stop */
+
+/* 
+ * Config:
+ *
+ * <Plugin gmond>
+ *   MCReceiveFrom "239.2.11.71" "8649"
+ *   <Metric "load_one">
+ *     Type "load"
+ *     [TypeInstance "foo"]
+ *     [DataSource "bar"]
+ *   </Metric>
+ * </Plugin>
+ */
+static int gmond_config_set_string (oconfig_item_t *ci, char **str) /* {{{ */
+{
+  char *tmp;
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("gmond plugin: The `%s' option needs "
+        "exactly one string argument.", ci->key);
+    return (-1);
+  }
+
+  tmp = strdup (ci->values[0].value.string);
+  if (tmp == NULL)
+  {
+    ERROR ("gmond plugin: strdup failed.");
+    return (-1);
+  }
+
+  sfree (*str);
+  *str = tmp;
+  return (0);
+} /* }}} int gmond_config_set_string */
+
+static int gmond_config_add_metric (oconfig_item_t *ci) /* {{{ */
+{
+  metric_map_t *map;
+  int i;
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("gmond plugin: `Metric' blocks need "
+        "exactly one string argument.");
+    return (-1);
+  }
+
+  map = realloc (metric_map, (metric_map_len + 1) * sizeof (*metric_map));
+  if (map == NULL)
+  {
+    ERROR ("gmond plugin: realloc failed.");
+    return (-1);
+  }
+  metric_map = map;
+  map = metric_map + metric_map_len;
+
+  memset (map, 0, sizeof (*map));
+  map->type = NULL;
+  map->type_instance = NULL;
+  map->ds_name = NULL;
+  map->ds_type = -1;
+  map->ds_index = -1;
+
+  map->ganglia_name = strdup (ci->values[0].value.string);
+  if (map->ganglia_name == NULL)
+  {
+    ERROR ("gmond plugin: strdup failed.");
+    return (-1);
+  }
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+    if (strcasecmp ("Type", child->key) == 0)
+      gmond_config_set_string (child, &map->type);
+    else if (strcasecmp ("TypeInstance", child->key) == 0)
+      gmond_config_set_string (child, &map->type_instance);
+    else if (strcasecmp ("DataSource", child->key) == 0)
+      gmond_config_set_string (child, &map->ds_name);
+    else
+    {
+      WARNING ("gmond plugin: Unknown configuration option `%s' ignored.",
+          child->key);
+    }
+  }
+
+  if (map->type == NULL)
+  {
+    ERROR ("gmond plugin: No type is set for metric %s.",
+        map->ganglia_name);
+    sfree (map->ganglia_name);
+    sfree (map->type_instance);
+    return (-1);
+  }
+
+  metric_map_len++;
+  return (0);
+} /* }}} int gmond_config_add_metric */
+
+static int gmond_config_set_address (oconfig_item_t *ci, /* {{{ */
+    char **ret_addr, char **ret_port)
+{
+  char *addr;
+  char *port;
+
+  if ((ci->values_num != 1) && (ci->values_num != 2))
+  {
+    WARNING ("gmond plugin: The `%s' config option needs "
+        "one or two string arguments.",
+        ci->key);
+    return (-1);
+  }
+  if ((ci->values[0].type != OCONFIG_TYPE_STRING)
+      || ((ci->values_num == 2)
+        && (ci->values[1].type != OCONFIG_TYPE_STRING)))
+  {
+    WARNING ("gmond plugin: The `%s' config option needs "
+        "one or two string arguments.",
+        ci->key);
+    return (-1);
+  }
+
+  addr = strdup (ci->values[0].value.string);
+  if (ci->values_num == 2)
+    port = strdup (ci->values[1].value.string);
+  else
+    port = NULL;
+
+  if ((addr == NULL) || ((ci->values_num == 2) && (port == NULL)))
+  {
+    ERROR ("gmond plugin: strdup failed.");
+    sfree (addr);
+    sfree (port);
+    return (-1);
+  }
+
+  sfree (*ret_addr);
+  sfree (*ret_port);
+
+  *ret_addr = addr;
+  *ret_port = port;
+
+  return (0);
+} /* }}} int gmond_config_set_address */
+
+static int gmond_config (oconfig_item_t *ci) /* {{{ */
+{
+  int i;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+    if (strcasecmp ("MCReceiveFrom", child->key) == 0)
+      gmond_config_set_address (child, &mc_receive_group, &mc_receive_port);
+    else if (strcasecmp ("Metric", child->key) == 0)
+      gmond_config_add_metric (child);
+    else
+    {
+      WARNING ("gmond plugin: Unknown configuration option `%s' ignored.",
+          child->key);
+    }
+  }
+
+  return (0);
+} /* }}} int gmond_config */
+
+static int gmond_init (void) /* {{{ */
+{
+  create_sockets (&mc_send_sockets, &mc_send_sockets_num,
+      (mc_receive_group != NULL) ? mc_receive_group : MC_RECEIVE_GROUP_DEFAULT,
+      (mc_receive_port != NULL) ? mc_receive_port : MC_RECEIVE_PORT_DEFAULT,
+      /* listen = */ 0);
+
+  staging_tree = c_avl_create ((void *) strcmp);
+  if (staging_tree == NULL)
+  {
+    ERROR ("gmond plugin: c_avl_create failed.");
+    return (-1);
+  }
+
+  mc_receive_thread_start ();
+
+  return (0);
+} /* }}} int gmond_init */
+
+static int gmond_shutdown (void) /* {{{ */
+{
+  size_t i;
+
+  mc_receive_thread_stop ();
+
+  pthread_mutex_lock (&mc_send_sockets_lock);
+  for (i = 0; i < mc_send_sockets_num; i++)
+  {
+    close (mc_send_sockets[i].fd);
+    mc_send_sockets[i].fd = -1;
+  }
+  sfree (mc_send_sockets);
+  mc_send_sockets_num = 0;
+  pthread_mutex_unlock (&mc_send_sockets_lock);
+
+
+  return (0);
+} /* }}} int gmond_shutdown */
+
+void module_register (void)
+{
+  plugin_register_complex_config ("gmond", gmond_config);
+  plugin_register_init ("gmond", gmond_init);
+  plugin_register_shutdown ("gmond", gmond_shutdown);
+}
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/hddtemp.c b/src/hddtemp.c
new file mode 100644 (file)
index 0000000..4428b75
--- /dev/null
@@ -0,0 +1,309 @@
+/**
+ * collectd - src/hddtemp.c
+ * Copyright (C) 2005,2006  Vincent Stehlé
+ * Copyright (C) 2006-2010  Florian octo Forster
+ * Copyright (C) 2008       Sebastian Harl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Vincent Stehlé <vincent.stehle at free.fr>
+ *   Florian octo Forster <octo at verplant.org>
+ *   Sebastian Harl <sh at tokkee.org>
+ *
+ * TODO:
+ *   Do a pass, some day, and spare some memory. We consume too much for now
+ *   in string buffers and the like.
+ *
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+
+# include <netdb.h>
+# include <sys/socket.h>
+# include <netinet/in.h>
+# include <netinet/tcp.h>
+# include <libgen.h> /* for basename */
+
+#if HAVE_LINUX_MAJOR_H
+# include <linux/major.h>
+#endif
+
+#define HDDTEMP_DEF_HOST "127.0.0.1"
+#define HDDTEMP_DEF_PORT "7634"
+
+static const char *config_keys[] =
+{
+       "Host",
+       "Port"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static char *hddtemp_host = NULL;
+static char hddtemp_port[16];
+
+/*
+ * NAME
+ *  hddtemp_query_daemon
+ *
+ * DESCRIPTION
+ * Connect to the hddtemp daemon and receive data.
+ *
+ * ARGUMENTS:
+ *  `buffer'            The buffer where we put the received ascii string.
+ *  `buffer_size'       Size of the buffer
+ *
+ * RETURN VALUE:
+ *   >= 0 if ok, < 0 otherwise.
+ *
+ * NOTES:
+ *  Example of possible strings, as received from daemon:
+ *    |/dev/hda|ST340014A|36|C|
+ *    |/dev/hda|ST380011A|46|C||/dev/hdd|ST340016A|SLP|*|
+ *
+ * FIXME:
+ *  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)
+{
+       int fd;
+       ssize_t status;
+       int buffer_fill;
+
+       const char *host;
+       const char *port;
+
+       struct addrinfo  ai_hints;
+       struct addrinfo *ai_list, *ai_ptr;
+       int              ai_return;
+
+       memset (&ai_hints, '\0', sizeof (ai_hints));
+       ai_hints.ai_flags    = 0;
+#ifdef AI_ADDRCONFIG
+       ai_hints.ai_flags   |= AI_ADDRCONFIG;
+#endif
+       ai_hints.ai_family   = PF_UNSPEC;
+       ai_hints.ai_socktype = SOCK_STREAM;
+       ai_hints.ai_protocol = IPPROTO_TCP;
+
+       host = hddtemp_host;
+       if (host == NULL)
+               host = HDDTEMP_DEF_HOST;
+
+       port = hddtemp_port;
+       if (strlen (port) == 0)
+               port = HDDTEMP_DEF_PORT;
+
+       if ((ai_return = getaddrinfo (host, port, &ai_hints, &ai_list)) != 0)
+       {
+               char errbuf[1024];
+               ERROR ("hddtemp plugin: getaddrinfo (%s, %s): %s",
+                               host, port,
+                               (ai_return == EAI_SYSTEM)
+                               ? sstrerror (errno, errbuf, sizeof (errbuf))
+                               : gai_strerror (ai_return));
+               return (-1);
+       }
+
+       fd = -1;
+       for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
+       {
+               /* create our socket descriptor */
+               fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype,
+                               ai_ptr->ai_protocol);
+               if (fd < 0)
+               {
+                       char errbuf[1024];
+                       ERROR ("hddtemp plugin: socket: %s",
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       continue;
+               }
+
+               /* connect to the hddtemp daemon */
+               if (connect (fd, (struct sockaddr *) ai_ptr->ai_addr,
+                                       ai_ptr->ai_addrlen))
+               {
+                       char errbuf[1024];
+                       INFO ("hddtemp plugin: connect (%s, %s) failed: %s",
+                                       host, port,
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       close (fd);
+                       fd = -1;
+                       continue;
+               }
+
+               /* A socket could be opened and connecting succeeded. We're
+                * done. */
+               break;
+       }
+
+       freeaddrinfo (ai_list);
+
+       if (fd < 0)
+       {
+               ERROR ("hddtemp plugin: Could not connect to daemon.");
+               return (-1);
+       }
+
+       /* receive data from the hddtemp daemon */
+       memset (buffer, '\0', buffer_size);
+
+       buffer_fill = 0;
+       while ((status = read (fd, buffer + buffer_fill, buffer_size - buffer_fill)) != 0)
+       {
+               if (status == -1)
+               {
+                       char errbuf[1024];
+
+                       if ((errno == EAGAIN) || (errno == EINTR))
+                               continue;
+
+                       ERROR ("hddtemp plugin: Error reading from socket: %s",
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       close (fd);
+                       return (-1);
+               }
+               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)
+       {
+               WARNING ("hddtemp plugin: Peer has unexpectedly shut down "
+                               "the socket. Buffer: `%s'", buffer);
+               close (fd);
+               return (-1);
+       }
+
+       close (fd);
+       return (0);
+}
+
+static int hddtemp_config (const char *key, const char *value)
+{
+       if (strcasecmp (key, "Host") == 0)
+       {
+               if (hddtemp_host != NULL)
+                       free (hddtemp_host);
+               hddtemp_host = strdup (value);
+       }
+       else if (strcasecmp (key, "Port") == 0)
+       {
+               int port = (int) (atof (value));
+               if ((port > 0) && (port <= 65535))
+                       ssnprintf (hddtemp_port, sizeof (hddtemp_port),
+                                       "%i", port);
+               else
+                       sstrncpy (hddtemp_port, value, sizeof (hddtemp_port));
+       }
+       else
+       {
+               return (-1);
+       }
+
+       return (0);
+}
+
+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_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));
+
+       plugin_dispatch_values (&vl);
+}
+
+static int hddtemp_read (void)
+{
+       char buf[1024];
+       char *fields[128];
+       char *ptr;
+       char *saveptr;
+       int num_fields;
+       int num_disks;
+       int i;
+
+       /* get data from daemon */
+       if (hddtemp_query_daemon (buf, sizeof (buf)) < 0)
+               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++;
+
+               if (num_fields >= 128)
+                       break;
+       }
+
+       num_disks = num_fields / 4;
+
+       for (i = 0; i < num_disks; i++)
+       {
+               char *name;
+               double temperature;
+               char *mode;
+
+               mode = fields[4*i + 3];
+               name = basename (fields[4*i + 0]);
+
+               /* Skip non-temperature information */
+               if (mode[0] != 'C' && mode[0] != 'F')
+                       continue;
+
+               temperature = atof (fields[4*i + 2]);
+
+               /* Convert farenheit to celsius */
+               if (mode[0] == 'F')
+                       temperature = (temperature - 32.0) * 5.0 / 9.0;
+
+               hddtemp_submit (name, temperature);
+       }
+       
+       return (0);
+} /* int hddtemp_read */
+
+/* module_register
+   Register collectd plugin. */
+void module_register (void)
+{
+       plugin_register_config ("hddtemp", hddtemp_config,
+                       config_keys, config_keys_num);
+       plugin_register_read ("hddtemp", hddtemp_read);
+}
diff --git a/src/interface.c b/src/interface.c
new file mode 100644 (file)
index 0000000..9501161
--- /dev/null
@@ -0,0 +1,393 @@
+/**
+ * collectd - src/interface.c
+ * Copyright (C) 2005-2010  Florian octo Forster
+ * Copyright (C) 2009       Manuel Sanmartin
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ *   Sune Marcher <sm at flork.dk>
+ *   Manuel Sanmartin
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+#include "utils_ignorelist.h"
+
+#if HAVE_SYS_TYPES_H
+#  include <sys/types.h>
+#endif
+#if HAVE_SYS_SOCKET_H
+#  include <sys/socket.h>
+#endif
+
+/* One cannot include both. This sucks. */
+#if HAVE_LINUX_IF_H
+#  include <linux/if.h>
+#elif HAVE_NET_IF_H
+#  include <net/if.h>
+#endif
+
+#if HAVE_LINUX_NETDEVICE_H
+#  include <linux/netdevice.h>
+#endif
+#if HAVE_IFADDRS_H
+#  include <ifaddrs.h>
+#endif
+
+#if HAVE_STATGRAB_H
+# include <statgrab.h>
+#endif
+
+#if HAVE_PERFSTAT
+# include <sys/protosw.h>
+# include <libperfstat.h>
+#endif
+
+/*
+ * Various people have reported problems with `getifaddrs' and varying versions
+ * of `glibc'. That's why it's disabled by default. Since more statistics are
+ * available this way one may enable it using the `--enable-getifaddrs' option
+ * of the configure script. -octo
+ */
+#if KERNEL_LINUX
+# if !COLLECT_GETIFADDRS
+#  undef HAVE_GETIFADDRS
+# endif /* !COLLECT_GETIFADDRS */
+#endif /* KERNEL_LINUX */
+
+#if HAVE_PERFSTAT
+static perfstat_netinterface_t *ifstat;
+static int nif;
+static int pnif;
+#endif /* HAVE_PERFSTAT */
+
+#if !HAVE_GETIFADDRS && !KERNEL_LINUX && !HAVE_LIBKSTAT && !HAVE_LIBSTATGRAB && !HAVE_PERFSTAT
+# error "No applicable input method."
+#endif
+
+/*
+ * (Module-)Global variables
+ */
+static const char *config_keys[] =
+{
+       "Interface",
+       "IgnoreSelected",
+       NULL
+};
+static int config_keys_num = 2;
+
+static ignorelist_t *ignorelist = NULL;
+
+#ifdef HAVE_LIBKSTAT
+#define MAX_NUMIF 256
+extern kstat_ctl_t *kc;
+static kstat_t *ksp[MAX_NUMIF];
+static int numif = 0;
+#endif /* HAVE_LIBKSTAT */
+
+static int interface_config (const char *key, const char *value)
+{
+       if (ignorelist == NULL)
+               ignorelist = ignorelist_create (/* invert = */ 1);
+
+       if (strcasecmp (key, "Interface") == 0)
+       {
+               ignorelist_add (ignorelist, value);
+       }
+       else if (strcasecmp (key, "IgnoreSelected") == 0)
+       {
+               int invert = 1;
+               if (IS_TRUE (value))
+                       invert = 0;
+               ignorelist_set_invert (ignorelist, invert);
+       }
+       else
+       {
+               return (-1);
+       }
+
+       return (0);
+}
+
+#if HAVE_LIBKSTAT
+static int interface_init (void)
+{
+       kstat_t *ksp_chain;
+       derive_t val;
+
+       numif = 0;
+
+       if (kc == NULL)
+               return (-1);
+
+       for (numif = 0, ksp_chain = kc->kc_chain;
+                       (numif < MAX_NUMIF) && (ksp_chain != NULL);
+                       ksp_chain = ksp_chain->ks_next)
+       {
+               if (strncmp (ksp_chain->ks_class, "net", 3))
+                       continue;
+               /* Ignore kstat entry if not the regular statistic set. This
+                * avoids problems with "bogus" interfaces, such as
+                * "wrsmd<num>" */
+               if (strncmp (ksp_chain->ks_name, ksp_chain->ks_module,
+                                       strlen (ksp_chain->ks_module)) != 0)
+                       continue;
+               if (ksp_chain->ks_type != KSTAT_TYPE_NAMED)
+                       continue;
+               if (kstat_read (kc, ksp_chain, NULL) == -1)
+                       continue;
+               if ((val = get_kstat_value (ksp_chain, "ifspeed")) == -1LL)
+                       continue;
+               ksp[numif++] = ksp_chain;
+       }
+
+       return (0);
+} /* int interface_init */
+#endif /* HAVE_LIBKSTAT */
+
+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;
+
+       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));
+       sstrncpy (vl.plugin, "interface", sizeof (vl.plugin));
+       sstrncpy (vl.plugin_instance, dev, sizeof (vl.plugin_instance));
+       sstrncpy (vl.type, type, sizeof (vl.type));
+
+       plugin_dispatch_values (&vl);
+} /* void if_submit */
+
+static int interface_read (void)
+{
+#if HAVE_GETIFADDRS
+       struct ifaddrs *if_list;
+       struct ifaddrs *if_ptr;
+
+/* Darin/Mac OS X and possible other *BSDs */
+#if HAVE_STRUCT_IF_DATA
+#  define IFA_DATA if_data
+#  define IFA_RX_BYTES ifi_ibytes
+#  define IFA_TX_BYTES ifi_obytes
+#  define IFA_RX_PACKT ifi_ipackets
+#  define IFA_TX_PACKT ifi_opackets
+#  define IFA_RX_ERROR ifi_ierrors
+#  define IFA_TX_ERROR ifi_oerrors
+/* #endif HAVE_STRUCT_IF_DATA */
+
+#elif HAVE_STRUCT_NET_DEVICE_STATS
+#  define IFA_DATA net_device_stats
+#  define IFA_RX_BYTES rx_bytes
+#  define IFA_TX_BYTES tx_bytes
+#  define IFA_RX_PACKT rx_packets
+#  define IFA_TX_PACKT tx_packets
+#  define IFA_RX_ERROR rx_errors
+#  define IFA_TX_ERROR tx_errors
+#else
+#  error "No suitable type for `struct ifaddrs->ifa_data' found."
+#endif
+
+       struct IFA_DATA *if_data;
+
+       if (getifaddrs (&if_list) != 0)
+               return (-1);
+
+       for (if_ptr = if_list; if_ptr != NULL; if_ptr = if_ptr->ifa_next)
+       {
+               if ((if_data = (struct IFA_DATA *) if_ptr->ifa_data) == NULL)
+                       continue;
+
+               if_submit (if_ptr->ifa_name, "if_octets",
+                               if_data->IFA_RX_BYTES,
+                               if_data->IFA_TX_BYTES);
+               if_submit (if_ptr->ifa_name, "if_packets",
+                               if_data->IFA_RX_PACKT,
+                               if_data->IFA_TX_PACKT);
+               if_submit (if_ptr->ifa_name, "if_errors",
+                               if_data->IFA_RX_ERROR,
+                               if_data->IFA_TX_ERROR);
+       }
+
+       freeifaddrs (if_list);
+/* #endif HAVE_GETIFADDRS */
+
+#elif KERNEL_LINUX
+       FILE *fh;
+       char buffer[1024];
+       derive_t incoming, outgoing;
+       char *device;
+
+       char *dummy;
+       char *fields[16];
+       int numfields;
+
+       if ((fh = fopen ("/proc/net/dev", "r")) == NULL)
+       {
+               char errbuf[1024];
+               WARNING ("interface plugin: fopen: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       while (fgets (buffer, 1024, fh) != NULL)
+       {
+               if (!(dummy = strchr(buffer, ':')))
+                       continue;
+               dummy[0] = '\0';
+               dummy++;
+
+               device = buffer;
+               while (device[0] == ' ')
+                       device++;
+
+               if (device[0] == '\0')
+                       continue;
+
+               numfields = strsplit (dummy, fields, 16);
+
+               if (numfields < 11)
+                       continue;
+
+               incoming = atoll (fields[0]);
+               outgoing = atoll (fields[8]);
+               if_submit (device, "if_octets", incoming, outgoing);
+
+               incoming = atoll (fields[1]);
+               outgoing = atoll (fields[9]);
+               if_submit (device, "if_packets", incoming, outgoing);
+
+               incoming = atoll (fields[2]);
+               outgoing = atoll (fields[10]);
+               if_submit (device, "if_errors", incoming, outgoing);
+       }
+
+       fclose (fh);
+/* #endif KERNEL_LINUX */
+
+#elif HAVE_LIBKSTAT
+       int i;
+       derive_t rx;
+       derive_t tx;
+
+       if (kc == NULL)
+               return (-1);
+
+       for (i = 0; i < numif; i++)
+       {
+               if (kstat_read (kc, ksp[i], NULL) == -1)
+                       continue;
+
+               /* try to get 64bit counters */
+               rx = get_kstat_value (ksp[i], "rbytes64");
+               tx = get_kstat_value (ksp[i], "obytes64");
+               /* or fallback to 32bit */
+               if (rx == -1LL)
+                       rx = get_kstat_value (ksp[i], "rbytes");
+               if (tx == -1LL)
+                       tx = get_kstat_value (ksp[i], "obytes");
+               if ((rx != -1LL) || (tx != -1LL))
+                       if_submit (ksp[i]->ks_name, "if_octets", rx, tx);
+
+               /* try to get 64bit counters */
+               rx = get_kstat_value (ksp[i], "ipackets64");
+               tx = get_kstat_value (ksp[i], "opackets64");
+               /* or fallback to 32bit */
+               if (rx == -1LL)
+                       rx = get_kstat_value (ksp[i], "ipackets");
+               if (tx == -1LL)
+                       tx = get_kstat_value (ksp[i], "opackets");
+               if ((rx != -1LL) || (tx != -1LL))
+                       if_submit (ksp[i]->ks_name, "if_packets", rx, tx);
+
+               /* no 64bit error counters yet */
+               rx = get_kstat_value (ksp[i], "ierrors");
+               tx = get_kstat_value (ksp[i], "oerrors");
+               if ((rx != -1LL) || (tx != -1LL))
+                       if_submit (ksp[i]->ks_name, "if_errors", rx, tx);
+       }
+/* #endif HAVE_LIBKSTAT */
+
+#elif defined(HAVE_LIBSTATGRAB)
+       sg_network_io_stats *ios;
+       int i, num;
+
+       ios = sg_get_network_io_stats (&num);
+
+       for (i = 0; i < num; i++)
+               if_submit (ios[i].interface_name, "if_octets", ios[i].rx, ios[i].tx);
+/* #endif HAVE_LIBSTATGRAB */
+
+#elif defined(HAVE_PERFSTAT)
+       perfstat_id_t id;
+       int i, ifs;
+
+       if ((nif =  perfstat_netinterface(NULL, NULL, sizeof(perfstat_netinterface_t), 0)) < 0)
+       {
+               char errbuf[1024];
+               WARNING ("interface plugin: perfstat_netinterface: %s",
+                       sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       if (pnif != nif || ifstat == NULL)
+       {
+               if (ifstat != NULL)
+                       free(ifstat);
+               ifstat = malloc(nif * sizeof(perfstat_netinterface_t));
+       }
+       pnif = nif;
+
+       id.name[0]='\0';
+       if ((ifs = perfstat_netinterface(&id, ifstat, sizeof(perfstat_netinterface_t), nif)) < 0)
+       {
+               char errbuf[1024];
+               WARNING ("interface plugin: perfstat_netinterface (interfaces=%d): %s",
+                       nif, sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       for (i = 0; i < ifs; i++)
+       {
+               if_submit (ifstat[i].name, "if_octets", ifstat[i].ibytes, ifstat[i].obytes);
+               if_submit (ifstat[i].name, "if_packets", ifstat[i].ipackets ,ifstat[i].opackets);
+               if_submit (ifstat[i].name, "if_errors", ifstat[i].ierrors, ifstat[i].oerrors );
+       }
+#endif /* HAVE_PERFSTAT */
+
+       return (0);
+} /* int interface_read */
+
+void module_register (void)
+{
+       plugin_register_config ("interface", interface_config,
+                       config_keys, config_keys_num);
+#if HAVE_LIBKSTAT
+       plugin_register_init ("interface", interface_init);
+#endif
+       plugin_register_read ("interface", interface_read);
+} /* void module_register */
diff --git a/src/ipmi.c b/src/ipmi.c
new file mode 100644 (file)
index 0000000..f341320
--- /dev/null
@@ -0,0 +1,727 @@
+/**
+ * collectd - src/ipmi.c
+ * Copyright (C) 2008-2009  Florian octo Forster
+ * Copyright (C) 2008       Peter Holik
+ * Copyright (C) 2009       Bruno Prémont
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ *   Peter Holik <peter at holik.at>
+ *   Bruno Prémont <bonbons at linux-vserver.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "utils_ignorelist.h"
+
+#include <pthread.h>
+
+#include <OpenIPMI/ipmiif.h>
+#include <OpenIPMI/ipmi_err.h>
+#include <OpenIPMI/ipmi_posix.h>
+#include <OpenIPMI/ipmi_conn.h>
+#include <OpenIPMI/ipmi_smi.h>
+
+/*
+ * Private data types
+ */
+struct c_ipmi_sensor_list_s;
+typedef struct c_ipmi_sensor_list_s c_ipmi_sensor_list_t;
+
+struct c_ipmi_sensor_list_s
+{
+  ipmi_sensor_id_t sensor_id;
+  char sensor_name[DATA_MAX_NAME_LEN];
+  char sensor_type[DATA_MAX_NAME_LEN];
+  int sensor_not_present;
+  c_ipmi_sensor_list_t *next;
+};
+
+/*
+ * Module global variables
+ */
+static pthread_mutex_t sensor_list_lock = PTHREAD_MUTEX_INITIALIZER;
+static c_ipmi_sensor_list_t *sensor_list = NULL;
+
+static int c_ipmi_init_in_progress = 0;
+static int c_ipmi_active = 0;
+static pthread_t thread_id = (pthread_t) 0;
+
+static const char *config_keys[] =
+{
+       "Sensor",
+       "IgnoreSelected",
+       "NotifySensorAdd",
+       "NotifySensorRemove",
+       "NotifySensorNotPresent"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static ignorelist_t *ignorelist = NULL;
+
+static int c_ipmi_nofiy_add = 0;
+static int c_ipmi_nofiy_remove = 0;
+static int c_ipmi_nofiy_notpresent = 0;
+
+/*
+ * Misc private functions
+ */
+static void c_ipmi_error (const char *func, int status)
+{
+  char errbuf[4096];
+
+  memset (errbuf, 0, sizeof (errbuf));
+
+  if (IPMI_IS_OS_ERR (status))
+  {
+    sstrerror (IPMI_GET_OS_ERR (status), errbuf, sizeof (errbuf));
+  }
+  else if (IPMI_IS_IPMI_ERR (status))
+  {
+    ipmi_get_error_string (IPMI_GET_IPMI_ERR (status), errbuf, sizeof (errbuf));
+  }
+
+  if (errbuf[0] == 0)
+  {
+    ssnprintf (errbuf, sizeof (errbuf), "Unknown error %#x", status);
+  }
+  errbuf[sizeof (errbuf) - 1] = 0;
+
+  ERROR ("ipmi plugin: %s failed: %s", func, errbuf);
+} /* void c_ipmi_error */
+
+/*
+ * Sensor handlers
+ */
+/* Prototype for sensor_list_remove, so sensor_read_handler can call it. */
+static int sensor_list_remove (ipmi_sensor_t *sensor);
+
+static void sensor_read_handler (ipmi_sensor_t *sensor,
+    int err,
+    enum ipmi_value_present_e value_present,
+    unsigned int __attribute__((unused)) raw_value,
+    double value,
+    ipmi_states_t __attribute__((unused)) *states,
+    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;
+
+  if (err != 0)
+  {
+    if ((err & 0xff) == IPMI_NOT_PRESENT_CC)
+    {
+      if (list_item->sensor_not_present == 0)
+      {
+        list_item->sensor_not_present = 1;
+
+        INFO ("ipmi plugin: sensor_read_handler: sensor %s "
+            "not present.", list_item->sensor_name);
+
+        if (c_ipmi_nofiy_notpresent)
+        {
+          notification_t n = { NOTIF_WARNING, cdtime (), "", "", "ipmi",
+            "", "", "", NULL };
+
+          sstrncpy (n.host, hostname_g, sizeof (n.host));
+          sstrncpy (n.type_instance, list_item->sensor_name,
+              sizeof (n.type_instance));
+          sstrncpy (n.type, list_item->sensor_type, sizeof (n.type));
+          ssnprintf (n.message, sizeof (n.message),
+              "sensor %s not present", list_item->sensor_name);
+
+          plugin_dispatch_notification (&n);
+        }
+      }
+    }
+    else if (IPMI_IS_IPMI_ERR(err) && IPMI_GET_IPMI_ERR(err) == IPMI_NOT_SUPPORTED_IN_PRESENT_STATE_CC)
+    {
+      INFO ("ipmi plugin: sensor_read_handler: Sensor %s not ready",
+          list_item->sensor_name);
+    }
+    else
+    {
+      if (IPMI_IS_IPMI_ERR(err))
+        INFO ("ipmi plugin: sensor_read_handler: Removing sensor %s, "
+            "because it failed with IPMI error %#x.",
+            list_item->sensor_name, IPMI_GET_IPMI_ERR(err));
+      else if (IPMI_IS_OS_ERR(err))
+        INFO ("ipmi plugin: sensor_read_handler: Removing sensor %s, "
+            "because it failed with OS error %#x.",
+            list_item->sensor_name, IPMI_GET_OS_ERR(err));
+      else if (IPMI_IS_RMCPP_ERR(err))
+        INFO ("ipmi plugin: sensor_read_handler: Removing sensor %s, "
+            "because it failed with RMCPP error %#x.",
+            list_item->sensor_name, IPMI_GET_RMCPP_ERR(err));
+      else if (IPMI_IS_SOL_ERR(err))
+        INFO ("ipmi plugin: sensor_read_handler: Removing sensor %s, "
+            "because it failed with RMCPP error %#x.",
+            list_item->sensor_name, IPMI_GET_SOL_ERR(err));
+      else
+        INFO ("ipmi plugin: sensor_read_handler: Removing sensor %s, "
+            "because it failed with error %#x. of class %#x",
+            list_item->sensor_name, err & 0xff, err & 0xffffff00);
+      sensor_list_remove (sensor);
+    }
+    return;
+  }
+  else if (list_item->sensor_not_present == 1)
+  {
+    list_item->sensor_not_present = 0;
+
+    INFO ("ipmi plugin: sensor_read_handler: sensor %s present.",
+        list_item->sensor_name);
+
+    if (c_ipmi_nofiy_notpresent)
+    {
+      notification_t n = { NOTIF_OKAY, cdtime (), "", "", "ipmi",
+        "", "", "", NULL };
+
+      sstrncpy (n.host, hostname_g, sizeof (n.host));
+      sstrncpy (n.type_instance, list_item->sensor_name,
+          sizeof (n.type_instance));
+      sstrncpy (n.type, list_item->sensor_type, sizeof (n.type));
+      ssnprintf (n.message, sizeof (n.message),
+          "sensor %s present", list_item->sensor_name);
+
+      plugin_dispatch_notification (&n);
+    }
+  }
+
+  if (value_present != IPMI_BOTH_VALUES_PRESENT)
+  {
+    INFO ("ipmi plugin: sensor_read_handler: Removing sensor %s, "
+        "because it provides %s. If you need this sensor, "
+        "please file a bug report.",
+        list_item->sensor_name,
+        (value_present == IPMI_RAW_VALUE_PRESENT)
+        ? "only the raw value"
+        : "no value");
+    sensor_list_remove (sensor);
+    return;
+  }
+
+  values[0].gauge = value;
+
+  vl.values = values;
+  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));
+
+  plugin_dispatch_values (&vl);
+} /* void sensor_read_handler */
+
+static int sensor_list_add (ipmi_sensor_t *sensor)
+{
+  ipmi_sensor_id_t sensor_id;
+  c_ipmi_sensor_list_t *list_item;
+  c_ipmi_sensor_list_t *list_prev;
+
+  char buffer[DATA_MAX_NAME_LEN];
+  const char *entity_id_string;
+  char sensor_name[DATA_MAX_NAME_LEN];
+  char *sensor_name_ptr;
+  int sensor_type;
+  const char *type;
+  ipmi_entity_t *ent = ipmi_sensor_get_entity(sensor);
+
+  sensor_id = ipmi_sensor_convert_to_id (sensor);
+
+  memset (buffer, 0, sizeof (buffer));
+  ipmi_sensor_get_name (sensor, buffer, sizeof (buffer));
+  buffer[sizeof (buffer) - 1] = 0;
+
+  entity_id_string = ipmi_entity_get_entity_id_string (ent);
+
+  if (entity_id_string == NULL)
+    sstrncpy (sensor_name, buffer, sizeof (sensor_name));
+  else
+    ssnprintf (sensor_name, sizeof (sensor_name),
+        "%s %s", buffer, entity_id_string);
+
+  sstrncpy (buffer, sensor_name, sizeof (buffer));
+  sensor_name_ptr = strstr (buffer, ").");
+  if (sensor_name_ptr != NULL)
+  {
+    /* If name is something like "foo (123).bar",
+     * change that to "bar (123)".
+     * Both, sensor_name_ptr and sensor_id_ptr point to memory within the
+     * `buffer' array, which holds a copy of the current `sensor_name'. */
+    char *sensor_id_ptr;
+
+    /* `sensor_name_ptr' points to ").bar". */
+    sensor_name_ptr[1] = 0;
+    /* `buffer' holds "foo (123)\0bar\0". */
+    sensor_name_ptr += 2;
+    /* `sensor_name_ptr' now points to "bar". */
+
+    sensor_id_ptr = strstr (buffer, "(");
+    if (sensor_id_ptr != NULL)
+    {
+      /* `sensor_id_ptr' now points to "(123)". */
+      ssnprintf (sensor_name, sizeof (sensor_name),
+          "%s %s", sensor_name_ptr, sensor_id_ptr); 
+    }
+    /* else: don't touch sensor_name. */
+  }
+  sensor_name_ptr = sensor_name;
+
+  /* Both `ignorelist' and `plugin_instance' may be NULL. */
+  if (ignorelist_match (ignorelist, sensor_name_ptr) != 0)
+    return (0);
+
+  /* FIXME: Use rate unit or base unit to scale the value */
+
+  sensor_type = ipmi_sensor_get_sensor_type (sensor);
+  switch (sensor_type)
+  {
+    case IPMI_SENSOR_TYPE_TEMPERATURE:
+      type = "temperature";
+      break;
+
+    case IPMI_SENSOR_TYPE_VOLTAGE:
+      type = "voltage";
+      break;
+
+    case IPMI_SENSOR_TYPE_CURRENT:
+      type = "current";
+      break;
+
+    case IPMI_SENSOR_TYPE_FAN:
+      type = "fanspeed";
+      break;
+
+    default:
+      {
+        const char *sensor_type_str;
+
+        sensor_type_str = ipmi_sensor_get_sensor_type_string (sensor);
+        INFO ("ipmi plugin: sensor_list_add: Ignore sensor %s, "
+            "because I don't know how to handle its type (%#x, %s). "
+            "If you need this sensor, please file a bug report.",
+            sensor_name_ptr, sensor_type, sensor_type_str);
+        return (-1);
+      }
+  } /* switch (sensor_type) */
+
+  pthread_mutex_lock (&sensor_list_lock);
+
+  list_prev = NULL;
+  for (list_item = sensor_list;
+      list_item != NULL;
+      list_item = list_item->next)
+  {
+    if (ipmi_cmp_sensor_id (sensor_id, list_item->sensor_id) == 0)
+      break;
+    list_prev = list_item;
+  } /* for (list_item) */
+
+  if (list_item != NULL)
+  {
+    pthread_mutex_unlock (&sensor_list_lock);
+    return (0);
+  }
+
+  list_item = (c_ipmi_sensor_list_t *) calloc (1, sizeof (c_ipmi_sensor_list_t));
+  if (list_item == NULL)
+  {
+    pthread_mutex_unlock (&sensor_list_lock);
+    return (-1);
+  }
+
+  list_item->sensor_id = ipmi_sensor_convert_to_id (sensor);
+
+  if (list_prev != NULL)
+    list_prev->next = list_item;
+  else
+    sensor_list = list_item;
+
+  sstrncpy (list_item->sensor_name, sensor_name_ptr,
+            sizeof (list_item->sensor_name));
+  sstrncpy (list_item->sensor_type, type, sizeof (list_item->sensor_type));
+
+  pthread_mutex_unlock (&sensor_list_lock);
+
+  if (c_ipmi_nofiy_add && (c_ipmi_init_in_progress == 0))
+  {
+    notification_t n = { NOTIF_OKAY, cdtime (), "", "", "ipmi",
+                         "", "", "", NULL };
+
+    sstrncpy (n.host, hostname_g, sizeof (n.host));
+    sstrncpy (n.type_instance, list_item->sensor_name,
+              sizeof (n.type_instance));
+    sstrncpy (n.type, list_item->sensor_type, sizeof (n.type));
+    ssnprintf (n.message, sizeof (n.message),
+              "sensor %s added", list_item->sensor_name);
+
+    plugin_dispatch_notification (&n);
+  }
+
+  return (0);
+} /* int sensor_list_add */
+
+static int sensor_list_remove (ipmi_sensor_t *sensor)
+{
+  ipmi_sensor_id_t sensor_id;
+  c_ipmi_sensor_list_t *list_item;
+  c_ipmi_sensor_list_t *list_prev;
+
+  sensor_id = ipmi_sensor_convert_to_id (sensor);
+
+  pthread_mutex_lock (&sensor_list_lock);
+
+  list_prev = NULL;
+  for (list_item = sensor_list;
+      list_item != NULL;
+      list_item = list_item->next)
+  {
+    if (ipmi_cmp_sensor_id (sensor_id, list_item->sensor_id) == 0)
+      break;
+    list_prev = list_item;
+  } /* for (list_item) */
+
+  if (list_item == NULL)
+  {
+    pthread_mutex_unlock (&sensor_list_lock);
+    return (-1);
+  }
+
+  if (list_prev == NULL)
+    sensor_list = list_item->next;
+  else
+    list_prev->next = list_item->next;
+
+  list_prev = NULL;
+  list_item->next = NULL;
+
+  pthread_mutex_unlock (&sensor_list_lock);
+
+  if (c_ipmi_nofiy_remove && c_ipmi_active)
+  {
+    notification_t n = { NOTIF_WARNING, cdtime (), "", "",
+                         "ipmi", "", "", "", NULL };
+
+    sstrncpy (n.host, hostname_g, sizeof (n.host));
+    sstrncpy (n.type_instance, list_item->sensor_name,
+              sizeof (n.type_instance));
+    sstrncpy (n.type, list_item->sensor_type, sizeof (n.type));
+    ssnprintf (n.message, sizeof (n.message),
+              "sensor %s removed", list_item->sensor_name);
+
+    plugin_dispatch_notification (&n);
+  }
+
+  free (list_item);
+  return (0);
+} /* int sensor_list_remove */
+
+static int sensor_list_read_all (void)
+{
+  c_ipmi_sensor_list_t *list_item;
+
+  pthread_mutex_lock (&sensor_list_lock);
+
+  for (list_item = sensor_list;
+      list_item != NULL;
+      list_item = list_item->next)
+  {
+    ipmi_sensor_id_get_reading (list_item->sensor_id,
+        sensor_read_handler, /* user data = */ list_item);
+  } /* for (list_item) */
+
+  pthread_mutex_unlock (&sensor_list_lock);
+
+  return (0);
+} /* int sensor_list_read_all */
+
+static int sensor_list_remove_all (void)
+{
+  c_ipmi_sensor_list_t *list_item;
+
+  pthread_mutex_lock (&sensor_list_lock);
+
+  list_item = sensor_list;
+  sensor_list = NULL;
+
+  pthread_mutex_unlock (&sensor_list_lock);
+
+  while (list_item != NULL)
+  {
+    c_ipmi_sensor_list_t *list_next = list_item->next;
+
+    free (list_item);
+
+    list_item = list_next;
+  } /* while (list_item) */
+
+  return (0);
+} /* int sensor_list_remove_all */
+
+/*
+ * Entity handlers
+ */
+static void entity_sensor_update_handler (enum ipmi_update_e op,
+    ipmi_entity_t __attribute__((unused)) *entity,
+    ipmi_sensor_t *sensor,
+    void __attribute__((unused)) *user_data)
+{
+  /* TODO: Ignore sensors we cannot read */
+
+  if ((op == IPMI_ADDED) || (op == IPMI_CHANGED))
+  {
+    /* Will check for duplicate entries.. */
+    sensor_list_add (sensor);
+  }
+  else if (op == IPMI_DELETED)
+  {
+    sensor_list_remove (sensor);
+  }
+} /* void entity_sensor_update_handler */
+
+/*
+ * Domain handlers
+ */
+static void domain_entity_update_handler (enum ipmi_update_e op,
+    ipmi_domain_t __attribute__((unused)) *domain,
+    ipmi_entity_t *entity,
+    void __attribute__((unused)) *user_data)
+{
+  int status;
+
+  if (op == IPMI_ADDED)
+  {
+    status = ipmi_entity_add_sensor_update_handler (entity,
+        entity_sensor_update_handler, /* user data = */ NULL);
+    if (status != 0)
+    {
+      c_ipmi_error ("ipmi_entity_add_sensor_update_handler", status);
+    }
+  }
+  else if (op == IPMI_DELETED)
+  {
+    status = ipmi_entity_remove_sensor_update_handler (entity,
+        entity_sensor_update_handler, /* user data = */ NULL);
+    if (status != 0)
+    {
+      c_ipmi_error ("ipmi_entity_remove_sensor_update_handler", status);
+    }
+  }
+} /* void domain_entity_update_handler */
+
+static void domain_connection_change_handler (ipmi_domain_t *domain,
+    int err,
+    unsigned int conn_num,
+    unsigned int port_num,
+    int still_connected,
+    void *user_data)
+{
+  int status;
+
+  DEBUG ("domain_connection_change_handler (domain = %p, err = %i, "
+      "conn_num = %u, port_num = %u, still_connected = %i, "
+      "user_data = %p);\n",
+      (void *) domain, err, conn_num, port_num, still_connected, user_data);
+
+  status = ipmi_domain_add_entity_update_handler (domain,
+      domain_entity_update_handler, /* user data = */ NULL);
+  if (status != 0)
+  {
+    c_ipmi_error ("ipmi_domain_add_entity_update_handler", status);
+  }
+} /* void domain_connection_change_handler */
+
+static int thread_init (os_handler_t **ret_os_handler)
+{
+  os_handler_t *os_handler;
+  ipmi_open_option_t open_option[1];
+  ipmi_con_t *smi_connection = NULL;
+  ipmi_domain_id_t domain_id;
+  int status;
+
+  os_handler = ipmi_posix_thread_setup_os_handler (SIGUSR2);
+  if (os_handler == NULL)
+  {
+    ERROR ("ipmi plugin: ipmi_posix_thread_setup_os_handler failed.");
+    return (-1);
+  }
+
+  ipmi_init (os_handler);
+
+  status = ipmi_smi_setup_con (/* if_num = */ 0,
+      os_handler,
+      /* user data = */ NULL,
+      &smi_connection);
+  if (status != 0)
+  {
+    c_ipmi_error ("ipmi_smi_setup_con", status);
+    return (-1);
+  }
+
+  memset (open_option, 0, sizeof (open_option));
+  open_option[0].option = IPMI_OPEN_OPTION_ALL;
+  open_option[0].ival = 1;
+
+  status = ipmi_open_domain ("mydomain", &smi_connection, /* num_con = */ 1,
+      domain_connection_change_handler, /* user data = */ NULL,
+      /* domain_fully_up_handler = */ NULL, /* user data = */ NULL,
+      open_option, sizeof (open_option) / sizeof (open_option[0]),
+      &domain_id);
+  if (status != 0)
+  {
+    c_ipmi_error ("ipmi_open_domain", status);
+    return (-1);
+  }
+
+  *ret_os_handler = os_handler;
+  return (0);
+} /* int thread_init */
+
+static void *thread_main (void __attribute__((unused)) *user_data)
+{
+  int status;
+  os_handler_t *os_handler = NULL;
+
+  status = thread_init (&os_handler);
+  if (status != 0)
+  {
+    ERROR ("ipmi plugin: thread_init failed.\n");
+    return ((void *) -1);
+  }
+
+  while (c_ipmi_active != 0)
+  {
+    struct timeval tv = { 1, 0 };
+    os_handler->perform_one_op (os_handler, &tv);
+  }
+
+  ipmi_posix_thread_free_os_handler (os_handler);
+
+  return ((void *) 0);
+} /* void *thread_main */
+
+static int c_ipmi_config (const char *key, const char *value)
+{
+  if (ignorelist == NULL)
+    ignorelist = ignorelist_create (/* invert = */ 1);
+  if (ignorelist == NULL)
+    return (1);
+
+  if (strcasecmp ("Sensor", key) == 0)
+  {
+    ignorelist_add (ignorelist, value);
+  }
+  else if (strcasecmp ("IgnoreSelected", key) == 0)
+  {
+    int invert = 1;
+    if (IS_TRUE (value))
+      invert = 0;
+    ignorelist_set_invert (ignorelist, invert);
+  }
+  else if (strcasecmp ("NotifySensorAdd", key) == 0)
+  {
+    if (IS_TRUE (value))
+      c_ipmi_nofiy_add = 1;
+  }
+  else if (strcasecmp ("NotifySensorRemove", key) == 0)
+  {
+    if (IS_TRUE (value))
+      c_ipmi_nofiy_remove = 1;
+  }
+  else if (strcasecmp ("NotifySensorNotPresent", key) == 0)
+  {
+    if (IS_TRUE (value))
+      c_ipmi_nofiy_notpresent = 1;
+  }
+  else
+  {
+    return (-1);
+  }
+
+  return (0);
+} /* int c_ipmi_config */
+
+static int c_ipmi_init (void)
+{
+  int status;
+
+  /* Don't send `ADD' notifications during startup (~ 1 minute) */
+  time_t iv = CDTIME_T_TO_TIME_T (interval_g);
+  c_ipmi_init_in_progress = 1 + (60 / iv);
+
+  c_ipmi_active = 1;
+
+  status = pthread_create (&thread_id, /* attr = */ NULL, thread_main,
+      /* user data = */ NULL);
+  if (status != 0)
+  {
+    c_ipmi_active = 0;
+    thread_id = (pthread_t) 0;
+    ERROR ("ipmi plugin: pthread_create failed.");
+    return (-1);
+  }
+
+  return (0);
+} /* int c_ipmi_init */
+
+static int c_ipmi_read (void)
+{
+  if ((c_ipmi_active == 0) || (thread_id == (pthread_t) 0))
+  {
+    INFO ("ipmi plugin: c_ipmi_read: I'm not active, returning false.");
+    return (-1);
+  }
+
+  sensor_list_read_all ();
+
+  if (c_ipmi_init_in_progress > 0)
+    c_ipmi_init_in_progress--;
+  else
+    c_ipmi_init_in_progress = 0;
+
+  return (0);
+} /* int c_ipmi_read */
+
+static int c_ipmi_shutdown (void)
+{
+  c_ipmi_active = 0;
+
+  if (thread_id != (pthread_t) 0)
+  {
+    pthread_join (thread_id, NULL);
+    thread_id = (pthread_t) 0;
+  }
+
+  sensor_list_remove_all ();
+
+  return (0);
+} /* int c_ipmi_shutdown */
+
+void module_register (void)
+{
+  plugin_register_config ("ipmi", c_ipmi_config,
+      config_keys, config_keys_num);
+  plugin_register_init ("ipmi", c_ipmi_init);
+  plugin_register_read ("ipmi", c_ipmi_read);
+  plugin_register_shutdown ("ipmi", c_ipmi_shutdown);
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 ts=8 fdm=marker et : */
diff --git a/src/iptables.c b/src/iptables.c
new file mode 100644 (file)
index 0000000..49454f0
--- /dev/null
@@ -0,0 +1,524 @@
+/**
+ * collectd - src/iptables.c
+ * Copyright (C) 2007       Sjoerd van der Berg
+ * Copyright (C) 2007-2010  Florian octo Forster
+ * Copyright (C) 2009       Marco Chiappero
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *  Sjoerd van der Berg <harekiet at users.sourceforge.net>
+ *  Florian Forster <octo at collectd.org>
+ *  Marco Chiappero <marco at absence.it>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+
+#include <sys/socket.h>
+
+#include <libiptc/libiptc.h>
+#include <libiptc/libip6tc.h>
+
+/*
+ * iptc_handle_t was available before libiptc was officially available as a
+ * shared library. Note, that when the shared lib was introduced, the API and
+ * ABI have changed slightly:
+ * 'iptc_handle_t' used to be 'struct iptc_handle *' and most functions used
+ * 'iptc_handle_t *' as an argument. Now, most functions use 'struct
+ * iptc_handle *' (thus removing one level of pointer indirection).
+ *
+ * HAVE_IPTC_HANDLE_T is used to determine which API ought to be used. While
+ * this is somewhat hacky, I didn't find better way to solve that :-/
+ * -tokkee
+ */
+#ifndef HAVE_IPTC_HANDLE_T
+typedef struct iptc_handle iptc_handle_t;
+#endif
+#ifndef HAVE_IP6TC_HANDLE_T
+typedef struct ip6tc_handle ip6tc_handle_t;
+#endif
+
+/*
+ * (Module-)Global variables
+ */
+
+/*
+ * Config format should be `Chain table chainname',
+ * e. g. `Chain mangle incoming'
+ */
+static const char *config_keys[] =
+{
+       "Chain",
+       "Chain6"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+/*
+    Each table/chain combo that will be queried goes into this list
+*/
+
+enum protocol_version_e
+{
+    IPV4,
+    IPV6
+};
+typedef enum protocol_version_e protocol_version_t;
+
+#ifndef XT_TABLE_MAXNAMELEN
+# define XT_TABLE_MAXNAMELEN 32
+#endif
+typedef struct {
+    protocol_version_t ip_version;
+    char table[XT_TABLE_MAXNAMELEN];
+    char chain[XT_TABLE_MAXNAMELEN];
+    union
+    {
+       int   num;
+       char *comment;
+    } rule;
+    enum
+    {
+       RTYPE_NUM,
+       RTYPE_COMMENT,
+       RTYPE_COMMENT_ALL
+    } rule_type;
+    char name[64];
+} ip_chain_t;
+
+static ip_chain_t **chain_list = NULL;
+static int chain_num = 0;
+
+static int iptables_config (const char *key, const char *value)
+{
+       /* int ip_value; */
+       protocol_version_t ip_version = 0;
+
+       if (strcasecmp (key, "Chain") == 0)
+               ip_version = IPV4;
+       else if (strcasecmp (key, "Chain6") == 0)
+               ip_version = IPV6;
+
+       if (( ip_version == IPV4 ) || ( ip_version == IPV6 ))
+       {
+               ip_chain_t temp, *final, **list;
+               char *table;
+               int   table_len;
+               char *chain;
+               int   chain_len;
+
+               char *value_copy;
+               char *fields[4];
+               int   fields_num;
+               
+               memset (&temp, 0, sizeof (temp));
+
+               value_copy = strdup (value);
+               if (value_copy == NULL)
+               {
+                   char errbuf[1024];
+                   ERROR ("strdup failed: %s",
+                           sstrerror (errno, errbuf, sizeof (errbuf)));
+                   return (1);
+               }
+
+               /*
+                *  Time to fill the temp element
+                *  Examine value string, it should look like:
+                *  Chain[6] <table> <chain> [<comment|num> [name]]
+                        */
+
+               /* set IPv4 or IPv6 */
+                temp.ip_version = ip_version;
+
+               /* Chain <table> <chain> [<comment|num> [name]] */
+               fields_num = strsplit (value_copy, fields, 4);
+               if (fields_num < 2)
+               {
+                   free (value_copy);
+                   return (1);
+               }
+
+               table = fields[0];
+               chain = fields[1];
+
+               table_len = strlen (table) + 1;
+               if ((unsigned int)table_len > sizeof(temp.table))
+               {
+                       ERROR ("Table `%s' too long.", table);
+                       free (value_copy);
+                       return (1);
+               }
+               sstrncpy (temp.table, table, table_len);
+
+               chain_len = strlen (chain) + 1;
+               if ((unsigned int)chain_len > sizeof(temp.chain))
+               {
+                       ERROR ("Chain `%s' too long.", chain);
+                       free (value_copy);
+                       return (1);
+               }
+               sstrncpy (temp.chain, chain, chain_len);
+
+               if (fields_num >= 3)
+               {
+                   char *comment = fields[2];
+                   int   rule = atoi (comment);
+
+                   if (rule)
+                   {
+                       temp.rule.num = rule;
+                       temp.rule_type = RTYPE_NUM;
+                   }
+                   else
+                   {
+                       temp.rule.comment = strdup (comment);
+                       if (temp.rule.comment == NULL)
+                       {
+                           free (value_copy);
+                           return (1);
+                       }
+                       temp.rule_type = RTYPE_COMMENT;
+                   }
+               }
+               else
+               {
+                   temp.rule_type = RTYPE_COMMENT_ALL;
+               }
+
+               if (fields_num >= 4)
+                   sstrncpy (temp.name, fields[3], sizeof (temp.name));
+
+               free (value_copy);
+               value_copy = NULL;
+               table = NULL;
+               chain = NULL;
+
+               list = (ip_chain_t **) realloc (chain_list, (chain_num + 1) * sizeof (ip_chain_t *));
+               if (list == NULL)
+               {
+                   char errbuf[1024];
+                   ERROR ("realloc failed: %s",
+                           sstrerror (errno, errbuf, sizeof (errbuf)));
+                   return (1);
+               }
+
+               chain_list = list;
+               final = (ip_chain_t *) malloc( sizeof(temp) );
+               if (final == NULL) 
+               {
+                   char errbuf[1024];
+                   ERROR ("malloc failed: %s",
+                           sstrerror (errno, errbuf, sizeof (errbuf)));
+                   return (1);
+               }
+               memcpy (final, &temp, sizeof (temp));
+               chain_list[chain_num] = final;
+               chain_num++;
+
+               DEBUG ("Chain #%i: table = %s; chain = %s;", chain_num, final->table, final->chain);
+       }
+       else 
+       {
+               return (-1);
+       }
+
+       return (0);
+} /* int iptables_config */
+
+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 */
+    if (chain->rule_type == RTYPE_NUM)
+    {
+        if (chain->rule.num != rule_num)
+            return (0);
+    }
+    else
+    {
+        if (strcmp (match->u.user.name, "comment") != 0)
+            return (0);
+        if ((chain->rule_type == RTYPE_COMMENT)
+                && (strcmp (chain->rule.comment, (char *) match->data) != 0))
+            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", chain->table, chain->chain);
+    if ((status < 1) || ((unsigned int)status >= sizeof (vl.plugin_instance)))
+        return (0);
+
+    if (chain->name[0] != '\0')
+    {
+        sstrncpy (vl.type_instance, chain->name, sizeof (vl.type_instance));
+    }
+    else
+    {
+        if (chain->rule_type == RTYPE_NUM)
+            ssnprintf (vl.type_instance, sizeof (vl.type_instance),
+                    "%i", chain->rule.num);
+        else
+            sstrncpy (vl.type_instance, (char *) match->data,
+                    sizeof (vl.type_instance));
+    }
+
+    sstrncpy (vl.type, "ipt_bytes", sizeof (vl.type));
+    values[0].derive = (derive_t) entry->counters.bcnt;
+    plugin_dispatch_values (&vl);
+
+    sstrncpy (vl.type, "ipt_packets", sizeof (vl.type));
+    values[0].derive = (derive_t) entry->counters.pcnt;
+    plugin_dispatch_values (&vl);
+
+    return (0);
+} /* int submit_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 */
+    if (chain->rule_type == RTYPE_NUM)
+    {
+       if (chain->rule.num != rule_num)
+           return (0);
+    }
+    else
+    {
+       if (strcmp (match->u.user.name, "comment") != 0)
+           return (0);
+       if ((chain->rule_type == RTYPE_COMMENT)
+               && (strcmp (chain->rule.comment, (char *) match->data) != 0))
+           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", chain->table, chain->chain);
+    if ((status < 1) || ((unsigned int)status >= sizeof (vl.plugin_instance)))
+       return (0);
+
+    if (chain->name[0] != '\0')
+    {
+       sstrncpy (vl.type_instance, chain->name, sizeof (vl.type_instance));
+    }
+    else
+    {
+       if (chain->rule_type == RTYPE_NUM)
+           ssnprintf (vl.type_instance, sizeof (vl.type_instance),
+                   "%i", chain->rule.num);
+       else
+           sstrncpy (vl.type_instance, (char *) match->data,
+                   sizeof (vl.type_instance));
+    }
+
+    sstrncpy (vl.type, "ipt_bytes", sizeof (vl.type));
+    values[0].derive = (derive_t) entry->counters.bcnt;
+    plugin_dispatch_values (&vl);
+
+    sstrncpy (vl.type, "ipt_packets", sizeof (vl.type));
+    values[0].derive = (derive_t) entry->counters.pcnt;
+    plugin_dispatch_values (&vl);
+
+    return (0);
+} /* int submit_match */
+
+
+/* ipv6 submit_chain */
+static void submit6_chain( ip6tc_handle_t *handle, ip_chain_t *chain )
+{
+    const struct ip6t_entry *entry;
+    int rule_num;
+
+    /* Find first rule for chain and use the iterate macro */
+    entry = ip6tc_first_rule( chain->chain, handle );
+    if (entry == NULL)
+    {
+        DEBUG ("ip6tc_first_rule failed: %s", ip6tc_strerror (errno));
+        return;
+    }
+
+    rule_num = 1;
+    while (entry)
+    {
+        if (chain->rule_type == RTYPE_NUM)
+        {
+            submit6_match (NULL, entry, chain, rule_num);
+        }
+        else
+        {
+            IP6T_MATCH_ITERATE( entry, submit6_match, entry, chain, rule_num );
+        }
+
+        entry = ip6tc_next_rule( entry, handle );
+        rule_num++;
+    } /* while (entry) */
+}
+
+
+/* ipv4 submit_chain */
+static void submit_chain( iptc_handle_t *handle, ip_chain_t *chain )
+{
+    const struct ipt_entry *entry;
+    int rule_num;
+
+    /* Find first rule for chain and use the iterate macro */    
+    entry = iptc_first_rule( chain->chain, handle );
+    if (entry == NULL)
+    {
+       DEBUG ("iptc_first_rule failed: %s", iptc_strerror (errno));
+       return;
+    }
+
+    rule_num = 1;
+    while (entry)
+    {
+       if (chain->rule_type == RTYPE_NUM)
+       {
+           submit_match (NULL, entry, chain, rule_num);
+       }
+       else
+       {
+           IPT_MATCH_ITERATE( entry, submit_match, entry, chain, rule_num );
+       }
+
+       entry = iptc_next_rule( entry, handle );
+       rule_num++;
+    } /* while (entry) */
+}
+
+
+static int iptables_read (void)
+{
+    int i;
+    int num_failures = 0;
+    ip_chain_t *chain;
+
+    /* Init the iptc handle structure and query the correct table */    
+    for (i = 0; i < chain_num; i++)
+    {
+       chain = chain_list[i];
+       
+       if (!chain)
+       {
+           DEBUG ("iptables plugin: chain == NULL");
+           continue;
+       }
+
+       if ( chain->ip_version == IPV4 )
+        {
+#ifdef HAVE_IPTC_HANDLE_T
+               iptc_handle_t _handle;
+               iptc_handle_t *handle = &_handle;
+
+               *handle = iptc_init (chain->table);
+#else
+               iptc_handle_t *handle;
+                handle = iptc_init (chain->table);
+#endif
+
+                if (!handle)
+                {
+                        ERROR ("iptables plugin: iptc_init (%s) failed: %s",
+                                chain->table, iptc_strerror (errno));
+                        num_failures++;
+                        continue;
+                }
+
+                submit_chain (handle, chain);
+                iptc_free (handle);
+        }
+        else if ( chain->ip_version == IPV6 )
+        {
+#ifdef HAVE_IP6TC_HANDLE_T
+               ip6tc_handle_t _handle;
+               ip6tc_handle_t *handle = &_handle;
+
+               *handle = ip6tc_init (chain->table);
+#else
+                ip6tc_handle_t *handle;
+                handle = ip6tc_init (chain->table);
+#endif
+
+                if (!handle)
+                {
+                        ERROR ("iptables plugin: ip6tc_init (%s) failed: %s",
+                                chain->table, ip6tc_strerror (errno));
+                        num_failures++;
+                        continue;
+                }
+
+                submit6_chain (handle, chain);
+                ip6tc_free (handle);
+        }
+        else num_failures++;
+
+    } /* for (i = 0 .. chain_num) */
+
+    return ((num_failures < chain_num) ? 0 : -1);
+} /* int iptables_read */
+
+static int iptables_shutdown (void)
+{
+    int i;
+
+    for (i = 0; i < chain_num; i++)
+    {
+       if ((chain_list[i] != NULL) && (chain_list[i]->rule_type == RTYPE_COMMENT))
+       {
+           sfree (chain_list[i]->rule.comment);
+       }
+       sfree (chain_list[i]);
+    }
+    sfree (chain_list);
+
+    return (0);
+} /* int iptables_shutdown */
+
+void module_register (void)
+{
+    plugin_register_config ("iptables", iptables_config,
+           config_keys, config_keys_num);
+    plugin_register_read ("iptables", iptables_read);
+    plugin_register_shutdown ("iptables", iptables_shutdown);
+} /* void module_register */
+
+/*
+ * vim:shiftwidth=4:softtabstop=4:tabstop=8
+ */
diff --git a/src/ipvs.c b/src/ipvs.c
new file mode 100644 (file)
index 0000000..fa89489
--- /dev/null
@@ -0,0 +1,346 @@
+/**
+ * collectd - src/ipvs.c (based on ipvsadm and libipvs)
+ * Copyright (C) 1997  Steven Clarke <steven@monmouth.demon.co.uk>
+ * Copyright (C) 1998-2004  Wensong Zhang <wensong@linuxvirtualserver.org>
+ * Copyright (C) 2003-2004  Peter Kese <peter.kese@ijs.si>
+ * Copyright (C) 2007  Sebastian Harl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors:
+ *   Sebastian Harl <sh at tokkee.org>
+ **/
+
+/*
+ * This plugin collects statistics about IPVS connections. It requires Linux
+ * kernels >= 2.6.
+ *
+ * See http://www.linuxvirtualserver.org/software/index.html for more
+ * information about IPVS.
+ */
+
+#include "collectd.h"
+#include "plugin.h"
+#include "common.h"
+
+#if HAVE_ARPA_INET_H
+# include <arpa/inet.h>
+#endif /* HAVE_ARPA_INET_H */
+#if HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif /* HAVE_SYS_SOCKET_H */
+#if HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif /* HAVE_NETINET_IN_H */
+
+/* this can probably only be found in the kernel sources */
+#if HAVE_LINUX_IP_VS_H
+# include <linux/ip_vs.h>
+#elif HAVE_NET_IP_VS_H
+# include <net/ip_vs.h>
+#elif HAVE_IP_VS_H
+# include <ip_vs.h>
+#endif /* HAVE_IP_VS_H */
+
+#define log_err(...) ERROR ("ipvs: " __VA_ARGS__)
+#define log_info(...) INFO ("ipvs: " __VA_ARGS__)
+
+/*
+ * private variables
+ */
+static int sockfd = -1;
+
+/*
+ * libipvs API
+ */
+static struct ip_vs_get_services *ipvs_get_services (void)
+{
+       struct ip_vs_getinfo       ipvs_info;
+       struct ip_vs_get_services *ret;
+
+       socklen_t len;
+
+       len = sizeof (ipvs_info);
+
+       if (0 != getsockopt (sockfd, IPPROTO_IP, IP_VS_SO_GET_INFO,
+                               (void *)&ipvs_info, &len)) {
+               char errbuf[1024];
+               log_err ("ip_vs_get_services: getsockopt() failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return NULL;
+       }
+
+       len = sizeof (*ret) +
+               sizeof (struct ip_vs_service_entry) * ipvs_info.num_services;
+
+       if (NULL == (ret = malloc (len))) {
+               log_err ("ipvs_get_services: Out of memory.");
+               exit (3);
+       }
+
+       ret->num_services = ipvs_info.num_services;
+
+       if (0 != getsockopt (sockfd, IPPROTO_IP, IP_VS_SO_GET_SERVICES,
+                               (void *)ret, &len)) {
+               char errbuf[1024];
+               log_err ("ipvs_get_services: getsockopt failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+
+               free(ret);
+               return NULL;
+       }
+       return ret;
+} /* ipvs_get_services */
+
+static struct ip_vs_get_dests *ipvs_get_dests (struct ip_vs_service_entry *se)
+{
+       struct ip_vs_get_dests *ret;
+       socklen_t len;
+
+       len = sizeof (*ret) + sizeof (struct ip_vs_dest_entry) * se->num_dests;
+
+       if (NULL == (ret = malloc (len))) {
+               log_err ("ipvs_get_dests: Out of memory.");
+               exit (3);
+       }
+
+       ret->fwmark    = se->fwmark;
+       ret->protocol  = se->protocol;
+       ret->addr      = se->addr;
+       ret->port      = se->port;
+       ret->num_dests = se->num_dests;
+
+       if (0 != getsockopt (sockfd, IPPROTO_IP, IP_VS_SO_GET_DESTS,
+                               (void *)ret, &len)) {
+               char errbuf[1024];
+               log_err ("ipvs_get_dests: getsockopt() failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               free (ret);
+               return NULL;
+       }
+       return ret;
+} /* ip_vs_get_dests */
+
+/*
+ * collectd plugin API and helper functions
+ */
+static int cipvs_init (void)
+{
+       struct ip_vs_getinfo ipvs_info;
+
+       socklen_t len;
+
+       if (-1 == (sockfd = socket (AF_INET, SOCK_RAW, IPPROTO_RAW))) {
+               char errbuf[1024];
+               log_err ("cipvs_init: socket() failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return -1;
+       }
+
+       len = sizeof (ipvs_info);
+
+       if (0 != getsockopt (sockfd, IPPROTO_IP, IP_VS_SO_GET_INFO,
+                               (void *)&ipvs_info, &len)) {
+               char errbuf[1024];
+               log_err ("cipvs_init: getsockopt() failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               close (sockfd);
+               sockfd = -1;
+               return -1;
+       }
+
+       /* we need IPVS >= 1.1.4 */
+       if (ipvs_info.version < ((1 << 16) + (1 << 8) + 4)) {
+               log_err ("cipvs_init: IPVS version too old (%d.%d.%d < %d.%d.%d)",
+                               NVERSION (ipvs_info.version), 1, 1, 4);
+               close (sockfd);
+               sockfd = -1;
+               return -1;
+       }
+       else {
+               log_info ("Successfully connected to IPVS %d.%d.%d",
+                               NVERSION (ipvs_info.version));
+       }
+       return 0;
+} /* cipvs_init */
+
+/*
+ * ipvs-<virtual IP>_{UDP,TCP}<port>/<type>-total
+ * ipvs-<virtual IP>_{UDP,TCP}<port>/<type>-<real IP>_<port>
+ */
+
+/* plugin instance */
+static int get_pi (struct ip_vs_service_entry *se, char *pi, size_t size)
+{
+       struct in_addr addr;
+       int len = 0;
+
+       if ((NULL == se) || (NULL == pi))
+               return 0;
+
+       addr.s_addr = se->addr;
+
+       /* inet_ntoa() returns a pointer to a statically allocated buffer
+        * I hope non-glibc systems behave the same */
+       len = ssnprintf (pi, size, "%s_%s%u", inet_ntoa (addr),
+                       (se->protocol == IPPROTO_TCP) ? "TCP" : "UDP",
+                       ntohs (se->port));
+
+       if ((0 > len) || (size <= len)) {
+               log_err ("plugin instance truncated: %s", pi);
+               return -1;
+       }
+       return 0;
+} /* get_pi */
+
+/* type instance */
+static int get_ti (struct ip_vs_dest_entry *de, char *ti, size_t size)
+{
+       struct in_addr addr;
+       int len = 0;
+
+       if ((NULL == de) || (NULL == ti))
+               return 0;
+
+       addr.s_addr = de->addr;
+
+       /* inet_ntoa() returns a pointer to a statically allocated buffer
+        * I hope non-glibc systems behave the same */
+       len = ssnprintf (ti, size, "%s_%u", inet_ntoa (addr),
+                       ntohs (de->port));
+
+       if ((0 > len) || (size <= len)) {
+               log_err ("type instance truncated: %s", ti);
+               return -1;
+       }
+       return 0;
+} /* get_ti */
+
+static void cipvs_submit_connections (char *pi, 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_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));
+       sstrncpy (vl.type_instance, (NULL != ti) ? ti : "total",
+               sizeof (vl.type_instance));
+
+       plugin_dispatch_values (&vl);
+       return;
+} /* cipvs_submit_connections */
+
+static void cipvs_submit_if (char *pi, char *t, char *ti,
+               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;
+
+       vl.values     = values;
+       vl.values_len = 2;
+
+       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));
+       sstrncpy (vl.type_instance, (NULL != ti) ? ti : "total",
+               sizeof (vl.type_instance));
+
+       plugin_dispatch_values (&vl);
+       return;
+} /* cipvs_submit_if */
+
+static void cipvs_submit_dest (char *pi, struct ip_vs_dest_entry *de) {
+       struct ip_vs_stats_user stats = de->stats;
+
+       char ti[DATA_MAX_NAME_LEN];
+
+       if (0 != get_ti (de, ti, sizeof (ti)))
+               return;
+
+       cipvs_submit_connections (pi, ti, stats.conns);
+       cipvs_submit_if (pi, "if_packets", ti, stats.inpkts, stats.outpkts);
+       cipvs_submit_if (pi, "if_octets", ti, stats.inbytes, stats.outbytes);
+       return;
+} /* cipvs_submit_dest */
+
+static void cipvs_submit_service (struct ip_vs_service_entry *se)
+{
+       struct ip_vs_stats_user  stats = se->stats;
+       struct ip_vs_get_dests  *dests = ipvs_get_dests (se);
+
+       char pi[DATA_MAX_NAME_LEN];
+
+       int i = 0;
+
+       if (0 != get_pi (se, pi, sizeof (pi)))
+               return;
+
+       cipvs_submit_connections (pi, NULL, stats.conns);
+       cipvs_submit_if (pi, "if_packets", NULL, stats.inpkts, stats.outpkts);
+       cipvs_submit_if (pi, "if_octets", NULL, stats.inbytes, stats.outbytes);
+
+       for (i = 0; i < dests->num_dests; ++i)
+               cipvs_submit_dest (pi, &dests->entrytable[i]);
+
+       free (dests);
+       return;
+} /* cipvs_submit_service */
+
+static int cipvs_read (void)
+{
+       struct ip_vs_get_services *services = NULL;
+       int i = 0;
+
+       if (sockfd < 0)
+               return (-1);
+
+       if (NULL == (services = ipvs_get_services ()))
+               return -1;
+
+       for (i = 0; i < services->num_services; ++i)
+               cipvs_submit_service (&services->entrytable[i]);
+
+       free (services);
+       return 0;
+} /* cipvs_read */
+
+static int cipvs_shutdown (void)
+{
+       if (sockfd >= 0)
+               close (sockfd);
+       sockfd = -1;
+
+       return 0;
+} /* cipvs_shutdown */
+
+void module_register (void)
+{
+       plugin_register_init ("ipvs", cipvs_init);
+       plugin_register_read ("ipvs", cipvs_read);
+       plugin_register_shutdown ("ipvs", cipvs_shutdown);
+       return;
+} /* module_register */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
diff --git a/src/irq.c b/src/irq.c
new file mode 100644 (file)
index 0000000..96bf7f0
--- /dev/null
+++ b/src/irq.c
@@ -0,0 +1,165 @@
+/**
+ * collectd - src/irq.c
+ * Copyright (C) 2007  Peter Holik
+ * Copyright (C) 2011  Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Peter Holik <peter at holik.at>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+#include "utils_ignorelist.h"
+
+#if !KERNEL_LINUX
+# error "No applicable input method."
+#endif
+
+/*
+ * (Module-)Global variables
+ */
+static const char *config_keys[] =
+{
+       "Irq",
+       "IgnoreSelected"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static ignorelist_t *ignorelist = NULL;
+
+/*
+ * Private functions
+ */
+static int irq_config (const char *key, const char *value)
+{
+       if (ignorelist == NULL)
+               ignorelist = ignorelist_create (/* invert = */ 1);
+
+       if (strcasecmp (key, "Irq") == 0)
+       {
+               ignorelist_add (ignorelist, value);
+       }
+       else if (strcasecmp (key, "IgnoreSelected") == 0)
+       {
+               int invert = 1;
+               if (IS_TRUE (value))
+                       invert = 0;
+               ignorelist_set_invert (ignorelist, invert);
+       }
+       else
+       {
+               return (-1);
+       }
+
+       return (0);
+}
+
+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_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));
+
+       plugin_dispatch_values (&vl);
+} /* void irq_submit */
+
+static int irq_read (void)
+{
+       FILE *fh;
+       char buffer[1024];
+
+       fh = fopen ("/proc/interrupts", "r");
+       if (fh == NULL)
+       {
+               char errbuf[1024];
+               ERROR ("irq plugin: fopen (/proc/interrupts): %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       while (fgets (buffer, sizeof (buffer), fh) != NULL)
+       {
+               char *irq_name;
+               size_t irq_name_len;
+               derive_t irq_value;
+               int i;
+
+               char *fields[64];
+               int fields_num;
+
+               fields_num = strsplit (buffer, fields, 64);
+               if (fields_num < 2)
+                       continue;
+
+               irq_name = fields[0];
+               irq_name_len = strlen (irq_name);
+               if (irq_name_len < 2)
+                       continue;
+
+               /* Check if irq name ends with colon.
+                * Otherwise it's a header. */
+               if (irq_name[irq_name_len - 1] != ':')
+                       continue;
+
+               irq_name[irq_name_len - 1] = 0;
+               irq_name_len--;
+
+               irq_value = 0;
+               for (i = 1; i < fields_num; i++)
+               {
+                       /* Per-CPU value */
+                       value_t v;
+                       int status;
+
+                       status = parse_value (fields[i], &v, DS_TYPE_DERIVE);
+                       if (status != 0)
+                               break;
+
+                       irq_value += v.derive;
+               } /* for (i) */
+
+               /* No valid fields -> do not submit anything. */
+               if (i <= 1)
+                       continue;
+
+               irq_submit (irq_name, irq_value);
+       }
+
+       fclose (fh);
+
+       return (0);
+} /* int irq_read */
+
+void module_register (void)
+{
+       plugin_register_config ("irq", irq_config,
+                       config_keys, config_keys_num);
+       plugin_register_read ("irq", irq_read);
+} /* void module_register */
diff --git a/src/java.c b/src/java.c
new file mode 100644 (file)
index 0000000..b69ca94
--- /dev/null
@@ -0,0 +1,3111 @@
+/**
+ * collectd - src/java.c
+ * Copyright (C) 2009  Florian octo Forster
+ * Copyright (C) 2008  Justo Alonso Achaques
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ *   Justo Alonso Achaques <justo.alonso at gmail.com>
+ **/
+
+#include "collectd.h"
+#include "plugin.h"
+#include "common.h"
+#include "filter_chain.h"
+
+#include <pthread.h>
+#include <jni.h>
+
+#if !defined(JNI_VERSION_1_2)
+# error "Need JNI 1.2 compatible interface!"
+#endif
+
+/*
+ * Types
+ */
+struct cjni_jvm_env_s /* {{{ */
+{
+  JNIEnv *jvm_env;
+  int reference_counter;
+};
+typedef struct cjni_jvm_env_s cjni_jvm_env_t;
+/* }}} */
+
+struct java_plugin_class_s /* {{{ */
+{
+  char     *name;
+  jclass    class;
+  jobject   object;
+};
+typedef struct java_plugin_class_s java_plugin_class_t;
+/* }}} */
+
+#define CB_TYPE_CONFIG       1
+#define CB_TYPE_INIT         2
+#define CB_TYPE_READ         3
+#define CB_TYPE_WRITE        4
+#define CB_TYPE_FLUSH        5
+#define CB_TYPE_SHUTDOWN     6
+#define CB_TYPE_LOG          7
+#define CB_TYPE_NOTIFICATION 8
+#define CB_TYPE_MATCH        9
+#define CB_TYPE_TARGET      10
+struct cjni_callback_info_s /* {{{ */
+{
+  char     *name;
+  int       type;
+  jclass    class;
+  jobject   object;
+  jmethodID method;
+};
+typedef struct cjni_callback_info_s cjni_callback_info_t;
+/* }}} */
+
+/*
+ * Global variables
+ */
+static JavaVM *jvm = NULL;
+static pthread_key_t jvm_env_key;
+
+/* Configuration options for the JVM. */
+static char **jvm_argv = NULL;
+static size_t jvm_argc = 0;
+
+/* List of class names to load */
+static java_plugin_class_t  *java_classes_list = NULL;
+static size_t                java_classes_list_len;
+
+/* List of config, init, and shutdown callbacks. */
+static cjni_callback_info_t *java_callbacks      = NULL;
+static size_t                java_callbacks_num  = 0;
+static pthread_mutex_t       java_callbacks_lock = PTHREAD_MUTEX_INITIALIZER;
+
+static oconfig_item_t       *config_block = NULL;
+
+/*
+ * Prototypes
+ *
+ * Mostly functions that are needed by the Java interface (``native'')
+ * functions.
+ */
+static void cjni_callback_info_destroy (void *arg);
+static cjni_callback_info_t *cjni_callback_info_create (JNIEnv *jvm_env,
+    jobject o_name, jobject o_callback, int type);
+static int cjni_callback_register (JNIEnv *jvm_env, jobject o_name,
+    jobject o_callback, int type);
+static int cjni_read (user_data_t *user_data);
+static int cjni_write (const data_set_t *ds, const value_list_t *vl,
+    user_data_t *ud);
+static int cjni_flush (cdtime_t timeout, const char *identifier, user_data_t *ud);
+static void cjni_log (int severity, const char *message, user_data_t *ud);
+static int cjni_notification (const notification_t *n, user_data_t *ud);
+
+/* Create, destroy, and match/invoke functions, used by both, matches AND
+ * targets. */
+static int cjni_match_target_create (const oconfig_item_t *ci, void **user_data);
+static int cjni_match_target_destroy (void **user_data);
+static int cjni_match_target_invoke (const data_set_t *ds, value_list_t *vl,
+    notification_meta_t **meta, void **user_data);
+
+/* 
+ * C to Java conversion functions
+ */
+static int ctoj_string (JNIEnv *jvm_env, /* {{{ */
+    const char *string,
+    jclass class_ptr, jobject object_ptr, const char *method_name)
+{
+  jmethodID m_set;
+  jstring o_string;
+
+  /* Create a java.lang.String */
+  o_string = (*jvm_env)->NewStringUTF (jvm_env,
+      (string != NULL) ? string : "");
+  if (o_string == NULL)
+  {
+    ERROR ("java plugin: ctoj_string: NewStringUTF failed.");
+    return (-1);
+  }
+
+  /* Search for the `void setFoo (String s)' method. */
+  m_set = (*jvm_env)->GetMethodID (jvm_env, class_ptr,
+      method_name, "(Ljava/lang/String;)V");
+  if (m_set == NULL)
+  {
+    ERROR ("java plugin: ctoj_string: Cannot find method `void %s (String)'.",
+        method_name);
+    (*jvm_env)->DeleteLocalRef (jvm_env, o_string);
+    return (-1);
+  }
+
+  /* Call the method. */
+  (*jvm_env)->CallVoidMethod (jvm_env, object_ptr, m_set, o_string);
+
+  /* Decrease reference counter on the java.lang.String object. */
+  (*jvm_env)->DeleteLocalRef (jvm_env, o_string);
+
+  return (0);
+} /* }}} int ctoj_string */
+
+static int ctoj_int (JNIEnv *jvm_env, /* {{{ */
+    jint value,
+    jclass class_ptr, jobject object_ptr, const char *method_name)
+{
+  jmethodID m_set;
+
+  /* Search for the `void setFoo (int i)' method. */
+  m_set = (*jvm_env)->GetMethodID (jvm_env, class_ptr,
+      method_name, "(I)V");
+  if (m_set == NULL)
+  {
+    ERROR ("java plugin: ctoj_int: Cannot find method `void %s (int)'.",
+        method_name);
+    return (-1);
+  }
+
+  (*jvm_env)->CallVoidMethod (jvm_env, object_ptr, m_set, value);
+
+  return (0);
+} /* }}} int ctoj_int */
+
+static int ctoj_long (JNIEnv *jvm_env, /* {{{ */
+    jlong value,
+    jclass class_ptr, jobject object_ptr, const char *method_name)
+{
+  jmethodID m_set;
+
+  /* Search for the `void setFoo (long l)' method. */
+  m_set = (*jvm_env)->GetMethodID (jvm_env, class_ptr,
+      method_name, "(J)V");
+  if (m_set == NULL)
+  {
+    ERROR ("java plugin: ctoj_long: Cannot find method `void %s (long)'.",
+        method_name);
+    return (-1);
+  }
+
+  (*jvm_env)->CallVoidMethod (jvm_env, object_ptr, m_set, value);
+
+  return (0);
+} /* }}} int ctoj_long */
+
+static int ctoj_double (JNIEnv *jvm_env, /* {{{ */
+    jdouble value,
+    jclass class_ptr, jobject object_ptr, const char *method_name)
+{
+  jmethodID m_set;
+
+  /* Search for the `void setFoo (double d)' method. */
+  m_set = (*jvm_env)->GetMethodID (jvm_env, class_ptr,
+      method_name, "(D)V");
+  if (m_set == NULL)
+  {
+    ERROR ("java plugin: ctoj_double: Cannot find method `void %s (double)'.",
+        method_name);
+    return (-1);
+  }
+
+  (*jvm_env)->CallVoidMethod (jvm_env, object_ptr, m_set, value);
+
+  return (0);
+} /* }}} int ctoj_double */
+
+/* Convert a jlong to a java.lang.Number */
+static jobject ctoj_jlong_to_number (JNIEnv *jvm_env, jlong value) /* {{{ */
+{
+  jclass c_long;
+  jmethodID m_long_constructor;
+
+  /* Look up the java.lang.Long class */
+  c_long = (*jvm_env)->FindClass (jvm_env, "java/lang/Long");
+  if (c_long == NULL)
+  {
+    ERROR ("java plugin: ctoj_jlong_to_number: Looking up the "
+        "java.lang.Long class failed.");
+    return (NULL);
+  }
+
+  m_long_constructor = (*jvm_env)->GetMethodID (jvm_env,
+      c_long, "<init>", "(J)V");
+  if (m_long_constructor == NULL)
+  {
+    ERROR ("java plugin: ctoj_jlong_to_number: Looking up the "
+        "`Long (long)' constructor failed.");
+    return (NULL);
+  }
+
+  return ((*jvm_env)->NewObject (jvm_env,
+        c_long, m_long_constructor, value));
+} /* }}} jobject ctoj_jlong_to_number */
+
+/* Convert a jdouble to a java.lang.Number */
+static jobject ctoj_jdouble_to_number (JNIEnv *jvm_env, jdouble value) /* {{{ */
+{
+  jclass c_double;
+  jmethodID m_double_constructor;
+
+  /* Look up the java.lang.Long class */
+  c_double = (*jvm_env)->FindClass (jvm_env, "java/lang/Double");
+  if (c_double == NULL)
+  {
+    ERROR ("java plugin: ctoj_jdouble_to_number: Looking up the "
+        "java.lang.Double class failed.");
+    return (NULL);
+  }
+
+  m_double_constructor = (*jvm_env)->GetMethodID (jvm_env,
+      c_double, "<init>", "(D)V");
+  if (m_double_constructor == NULL)
+  {
+    ERROR ("java plugin: ctoj_jdouble_to_number: Looking up the "
+        "`Double (double)' constructor failed.");
+    return (NULL);
+  }
+
+  return ((*jvm_env)->NewObject (jvm_env,
+        c_double, m_double_constructor, value));
+} /* }}} jobject ctoj_jdouble_to_number */
+
+/* Convert a value_t to a java.lang.Number */
+static jobject ctoj_value_to_number (JNIEnv *jvm_env, /* {{{ */
+    value_t value, int ds_type)
+{
+  if (ds_type == DS_TYPE_COUNTER)
+    return (ctoj_jlong_to_number (jvm_env, (jlong) value.counter));
+  else if (ds_type == DS_TYPE_GAUGE)
+    return (ctoj_jdouble_to_number (jvm_env, (jdouble) value.gauge));
+  if (ds_type == DS_TYPE_DERIVE)
+    return (ctoj_jlong_to_number (jvm_env, (jlong) value.derive));
+  if (ds_type == DS_TYPE_ABSOLUTE)
+    return (ctoj_jlong_to_number (jvm_env, (jlong) value.absolute));
+  else
+    return (NULL);
+} /* }}} jobject ctoj_value_to_number */
+
+/* Convert a data_source_t to a org/collectd/api/DataSource */
+static jobject ctoj_data_source (JNIEnv *jvm_env, /* {{{ */
+    const data_source_t *dsrc)
+{
+  jclass c_datasource;
+  jmethodID m_datasource_constructor;
+  jobject o_datasource;
+  int status;
+
+  /* Look up the DataSource class */
+  c_datasource = (*jvm_env)->FindClass (jvm_env,
+      "org/collectd/api/DataSource");
+  if (c_datasource == NULL)
+  {
+    ERROR ("java plugin: ctoj_data_source: "
+        "FindClass (org/collectd/api/DataSource) failed.");
+    return (NULL);
+  }
+
+  /* Lookup the `ValueList ()' constructor. */
+  m_datasource_constructor = (*jvm_env)->GetMethodID (jvm_env, c_datasource,
+      "<init>", "()V");
+  if (m_datasource_constructor == NULL)
+  {
+    ERROR ("java plugin: ctoj_data_source: Cannot find the "
+        "`DataSource ()' constructor.");
+    return (NULL);
+  }
+
+  /* Create a new instance. */
+  o_datasource = (*jvm_env)->NewObject (jvm_env, c_datasource,
+      m_datasource_constructor);
+  if (o_datasource == NULL)
+  {
+    ERROR ("java plugin: ctoj_data_source: "
+        "Creating a new DataSource instance failed.");
+    return (NULL);
+  }
+
+  /* Set name via `void setName (String name)' */
+  status = ctoj_string (jvm_env, dsrc->name,
+      c_datasource, o_datasource, "setName");
+  if (status != 0)
+  {
+    ERROR ("java plugin: ctoj_data_source: "
+        "ctoj_string (setName) failed.");
+    (*jvm_env)->DeleteLocalRef (jvm_env, o_datasource);
+    return (NULL);
+  }
+
+  /* Set type via `void setType (int type)' */
+  status = ctoj_int (jvm_env, dsrc->type,
+      c_datasource, o_datasource, "setType");
+  if (status != 0)
+  {
+    ERROR ("java plugin: ctoj_data_source: "
+        "ctoj_int (setType) failed.");
+    (*jvm_env)->DeleteLocalRef (jvm_env, o_datasource);
+    return (NULL);
+  }
+
+  /* Set min via `void setMin (double min)' */
+  status = ctoj_double (jvm_env, dsrc->min,
+      c_datasource, o_datasource, "setMin");
+  if (status != 0)
+  {
+    ERROR ("java plugin: ctoj_data_source: "
+        "ctoj_double (setMin) failed.");
+    (*jvm_env)->DeleteLocalRef (jvm_env, o_datasource);
+    return (NULL);
+  }
+
+  /* Set max via `void setMax (double max)' */
+  status = ctoj_double (jvm_env, dsrc->max,
+      c_datasource, o_datasource, "setMax");
+  if (status != 0)
+  {
+    ERROR ("java plugin: ctoj_data_source: "
+        "ctoj_double (setMax) failed.");
+    (*jvm_env)->DeleteLocalRef (jvm_env, o_datasource);
+    return (NULL);
+  }
+
+  return (o_datasource);
+} /* }}} jobject ctoj_data_source */
+
+/* Convert a oconfig_value_t to a org/collectd/api/OConfigValue */
+static jobject ctoj_oconfig_value (JNIEnv *jvm_env, /* {{{ */
+    oconfig_value_t ocvalue)
+{
+  jclass c_ocvalue;
+  jmethodID m_ocvalue_constructor;
+  jobject o_argument;
+  jobject o_ocvalue;
+
+  m_ocvalue_constructor = NULL;
+  o_argument = NULL;
+
+  c_ocvalue = (*jvm_env)->FindClass (jvm_env,
+      "org/collectd/api/OConfigValue");
+  if (c_ocvalue == NULL)
+  {
+    ERROR ("java plugin: ctoj_oconfig_value: "
+        "FindClass (org/collectd/api/OConfigValue) failed.");
+    return (NULL);
+  }
+
+  if (ocvalue.type == OCONFIG_TYPE_BOOLEAN)
+  {
+    jboolean tmp_boolean;
+
+    tmp_boolean = (ocvalue.value.boolean == 0) ? JNI_FALSE : JNI_TRUE;
+
+    m_ocvalue_constructor = (*jvm_env)->GetMethodID (jvm_env, c_ocvalue,
+        "<init>", "(Z)V");
+    if (m_ocvalue_constructor == NULL)
+    {
+      ERROR ("java plugin: ctoj_oconfig_value: Cannot find the "
+          "`OConfigValue (boolean)' constructor.");
+      return (NULL);
+    }
+
+    return ((*jvm_env)->NewObject (jvm_env,
+          c_ocvalue, m_ocvalue_constructor, tmp_boolean));
+  } /* if (ocvalue.type == OCONFIG_TYPE_BOOLEAN) */
+  else if (ocvalue.type == OCONFIG_TYPE_STRING)
+  {
+    m_ocvalue_constructor = (*jvm_env)->GetMethodID (jvm_env, c_ocvalue,
+        "<init>", "(Ljava/lang/String;)V");
+    if (m_ocvalue_constructor == NULL)
+    {
+      ERROR ("java plugin: ctoj_oconfig_value: Cannot find the "
+          "`OConfigValue (String)' constructor.");
+      return (NULL);
+    }
+
+    o_argument = (*jvm_env)->NewStringUTF (jvm_env, ocvalue.value.string);
+    if (o_argument == NULL)
+    {
+      ERROR ("java plugin: ctoj_oconfig_value: "
+          "Creating a String object failed.");
+      return (NULL);
+    }
+  }
+  else if (ocvalue.type == OCONFIG_TYPE_NUMBER)
+  {
+    m_ocvalue_constructor = (*jvm_env)->GetMethodID (jvm_env, c_ocvalue,
+        "<init>", "(Ljava/lang/Number;)V");
+    if (m_ocvalue_constructor == NULL)
+    {
+      ERROR ("java plugin: ctoj_oconfig_value: Cannot find the "
+          "`OConfigValue (Number)' constructor.");
+      return (NULL);
+    }
+
+    o_argument = ctoj_jdouble_to_number (jvm_env,
+        (jdouble) ocvalue.value.number);
+    if (o_argument == NULL)
+    {
+      ERROR ("java plugin: ctoj_oconfig_value: "
+          "Creating a Number object failed.");
+      return (NULL);
+    }
+  }
+  else
+  {
+    return (NULL);
+  }
+
+  assert (m_ocvalue_constructor != NULL);
+  assert (o_argument != NULL);
+
+  o_ocvalue = (*jvm_env)->NewObject (jvm_env,
+      c_ocvalue, m_ocvalue_constructor, o_argument);
+  if (o_ocvalue == NULL)
+  {
+    ERROR ("java plugin: ctoj_oconfig_value: "
+        "Creating an OConfigValue object failed.");
+    (*jvm_env)->DeleteLocalRef (jvm_env, o_argument);
+    return (NULL);
+  }
+
+  (*jvm_env)->DeleteLocalRef (jvm_env, o_argument);
+  return (o_ocvalue);
+} /* }}} jobject ctoj_oconfig_value */
+
+/* Convert a oconfig_item_t to a org/collectd/api/OConfigItem */
+static jobject ctoj_oconfig_item (JNIEnv *jvm_env, /* {{{ */
+    const oconfig_item_t *ci)
+{
+  jclass c_ocitem;
+  jmethodID m_ocitem_constructor;
+  jmethodID m_addvalue;
+  jmethodID m_addchild;
+  jobject o_key;
+  jobject o_ocitem;
+  int i;
+
+  c_ocitem = (*jvm_env)->FindClass (jvm_env, "org/collectd/api/OConfigItem");
+  if (c_ocitem == NULL)
+  {
+    ERROR ("java plugin: ctoj_oconfig_item: "
+        "FindClass (org/collectd/api/OConfigItem) failed.");
+    return (NULL);
+  }
+
+  /* Get the required methods: m_ocitem_constructor, m_addvalue, and m_addchild
+   * {{{ */
+  m_ocitem_constructor = (*jvm_env)->GetMethodID (jvm_env, c_ocitem,
+      "<init>", "(Ljava/lang/String;)V");
+  if (m_ocitem_constructor == NULL)
+  {
+    ERROR ("java plugin: ctoj_oconfig_item: Cannot find the "
+        "`OConfigItem (String)' constructor.");
+    return (NULL);
+  }
+
+  m_addvalue = (*jvm_env)->GetMethodID (jvm_env, c_ocitem,
+      "addValue", "(Lorg/collectd/api/OConfigValue;)V");
+  if (m_addvalue == NULL)
+  {
+    ERROR ("java plugin: ctoj_oconfig_item: Cannot find the "
+        "`addValue (OConfigValue)' method.");
+    return (NULL);
+  }
+
+  m_addchild = (*jvm_env)->GetMethodID (jvm_env, c_ocitem,
+      "addChild", "(Lorg/collectd/api/OConfigItem;)V");
+  if (m_addchild == NULL)
+  {
+    ERROR ("java plugin: ctoj_oconfig_item: Cannot find the "
+        "`addChild (OConfigItem)' method.");
+    return (NULL);
+  }
+  /* }}} */
+
+  /* Create a String object with the key.
+   * Needed for calling the constructor. */
+  o_key = (*jvm_env)->NewStringUTF (jvm_env, ci->key);
+  if (o_key == NULL)
+  {
+    ERROR ("java plugin: ctoj_oconfig_item: "
+        "Creating String object failed.");
+    return (NULL);
+  }
+
+  /* Create an OConfigItem object */
+  o_ocitem = (*jvm_env)->NewObject (jvm_env,
+      c_ocitem, m_ocitem_constructor, o_key);
+  if (o_ocitem == NULL)
+  {
+    ERROR ("java plugin: ctoj_oconfig_item: "
+        "Creating an OConfigItem object failed.");
+    (*jvm_env)->DeleteLocalRef (jvm_env, o_key);
+    return (NULL);
+  }
+
+  /* We don't need the String object any longer.. */
+  (*jvm_env)->DeleteLocalRef (jvm_env, o_key);
+
+  /* Call OConfigItem.addValue for each value */
+  for (i = 0; i < ci->values_num; i++) /* {{{ */
+  {
+    jobject o_value;
+
+    o_value = ctoj_oconfig_value (jvm_env, ci->values[i]);
+    if (o_value == NULL)
+    {
+      ERROR ("java plugin: ctoj_oconfig_item: "
+          "Creating an OConfigValue object failed.");
+      (*jvm_env)->DeleteLocalRef (jvm_env, o_ocitem);
+      return (NULL);
+    }
+
+    (*jvm_env)->CallVoidMethod (jvm_env, o_ocitem, m_addvalue, o_value);
+    (*jvm_env)->DeleteLocalRef (jvm_env, o_value);
+  } /* }}} for (i = 0; i < ci->values_num; i++) */
+
+  /* Call OConfigItem.addChild for each child */
+  for (i = 0; i < ci->children_num; i++) /* {{{ */
+  {
+    jobject o_child;
+
+    o_child = ctoj_oconfig_item (jvm_env, ci->children + i);
+    if (o_child == NULL)
+    {
+      ERROR ("java plugin: ctoj_oconfig_item: "
+          "Creating an OConfigItem object failed.");
+      (*jvm_env)->DeleteLocalRef (jvm_env, o_ocitem);
+      return (NULL);
+    }
+
+    (*jvm_env)->CallVoidMethod (jvm_env, o_ocitem, m_addchild, o_child);
+    (*jvm_env)->DeleteLocalRef (jvm_env, o_child);
+  } /* }}} for (i = 0; i < ci->children_num; i++) */
+
+  return (o_ocitem);
+} /* }}} jobject ctoj_oconfig_item */
+
+/* Convert a data_set_t to a org/collectd/api/DataSet */
+static jobject ctoj_data_set (JNIEnv *jvm_env, const data_set_t *ds) /* {{{ */
+{
+  jclass c_dataset;
+  jmethodID m_constructor;
+  jmethodID m_add;
+  jobject o_type;
+  jobject o_dataset;
+  int i;
+
+  /* Look up the org/collectd/api/DataSet class */
+  c_dataset = (*jvm_env)->FindClass (jvm_env, "org/collectd/api/DataSet");
+  if (c_dataset == NULL)
+  {
+    ERROR ("java plugin: ctoj_data_set: Looking up the "
+        "org/collectd/api/DataSet class failed.");
+    return (NULL);
+  }
+
+  /* Search for the `DataSet (String type)' constructor. */
+  m_constructor = (*jvm_env)->GetMethodID (jvm_env,
+      c_dataset, "<init>", "(Ljava/lang/String;)V");
+  if (m_constructor == NULL)
+  {
+    ERROR ("java plugin: ctoj_data_set: Looking up the "
+        "`DataSet (String)' constructor failed.");
+    return (NULL);
+  }
+
+  /* Search for the `void addDataSource (DataSource)' method. */
+  m_add = (*jvm_env)->GetMethodID (jvm_env,
+      c_dataset, "addDataSource", "(Lorg/collectd/api/DataSource;)V");
+  if (m_add == NULL)
+  {
+    ERROR ("java plugin: ctoj_data_set: Looking up the "
+        "`addDataSource (DataSource)' method failed.");
+    return (NULL);
+  }
+
+  o_type = (*jvm_env)->NewStringUTF (jvm_env, ds->type);
+  if (o_type == NULL)
+  {
+    ERROR ("java plugin: ctoj_data_set: Creating a String object failed.");
+    return (NULL);
+  }
+
+  o_dataset = (*jvm_env)->NewObject (jvm_env,
+      c_dataset, m_constructor, o_type);
+  if (o_dataset == NULL)
+  {
+    ERROR ("java plugin: ctoj_data_set: Creating a DataSet object failed.");
+    (*jvm_env)->DeleteLocalRef (jvm_env, o_type);
+    return (NULL);
+  }
+
+  /* Decrease reference counter on the java.lang.String object. */
+  (*jvm_env)->DeleteLocalRef (jvm_env, o_type);
+
+  for (i = 0; i < ds->ds_num; i++)
+  {
+    jobject o_datasource;
+
+    o_datasource = ctoj_data_source (jvm_env, ds->ds + i);
+    if (o_datasource == NULL)
+    {
+      ERROR ("java plugin: ctoj_data_set: ctoj_data_source (%s.%s) failed",
+          ds->type, ds->ds[i].name);
+      (*jvm_env)->DeleteLocalRef (jvm_env, o_dataset);
+      return (NULL);
+    }
+
+    (*jvm_env)->CallVoidMethod (jvm_env, o_dataset, m_add, o_datasource);
+
+    (*jvm_env)->DeleteLocalRef (jvm_env, o_datasource);
+  } /* for (i = 0; i < ds->ds_num; i++) */
+
+  return (o_dataset);
+} /* }}} jobject ctoj_data_set */
+
+static int ctoj_value_list_add_value (JNIEnv *jvm_env, /* {{{ */
+    value_t value, int ds_type,
+    jclass class_ptr, jobject object_ptr)
+{
+  jmethodID m_addvalue;
+  jobject o_number;
+
+  m_addvalue = (*jvm_env)->GetMethodID (jvm_env, class_ptr,
+      "addValue", "(Ljava/lang/Number;)V");
+  if (m_addvalue == NULL)
+  {
+    ERROR ("java plugin: ctoj_value_list_add_value: "
+        "Cannot find method `void addValue (Number)'.");
+    return (-1);
+  }
+
+  o_number = ctoj_value_to_number (jvm_env, value, ds_type);
+  if (o_number == NULL)
+  {
+    ERROR ("java plugin: ctoj_value_list_add_value: "
+        "ctoj_value_to_number failed.");
+    return (-1);
+  }
+
+  (*jvm_env)->CallVoidMethod (jvm_env, object_ptr, m_addvalue, o_number);
+
+  (*jvm_env)->DeleteLocalRef (jvm_env, o_number);
+
+  return (0);
+} /* }}} int ctoj_value_list_add_value */
+
+static int ctoj_value_list_add_data_set (JNIEnv *jvm_env, /* {{{ */
+    jclass c_valuelist, jobject o_valuelist, const data_set_t *ds)
+{
+  jmethodID m_setdataset;
+  jobject o_dataset;
+
+  /* Look for the `void setDataSource (List<DataSource> ds)' method. */
+  m_setdataset = (*jvm_env)->GetMethodID (jvm_env, c_valuelist,
+      "setDataSet", "(Lorg/collectd/api/DataSet;)V");
+  if (m_setdataset == NULL)
+  {
+    ERROR ("java plugin: ctoj_value_list_add_data_set: "
+        "Cannot find the `void setDataSet (DataSet)' method.");
+    return (-1);
+  }
+
+  /* Create a DataSet object. */
+  o_dataset = ctoj_data_set (jvm_env, ds);
+  if (o_dataset == NULL)
+  {
+    ERROR ("java plugin: ctoj_value_list_add_data_set: "
+        "ctoj_data_set (%s) failed.", ds->type);
+    return (-1);
+  }
+
+  /* Actually call the method. */
+  (*jvm_env)->CallVoidMethod (jvm_env,
+      o_valuelist, m_setdataset, o_dataset);
+
+  /* Decrease reference counter on the List<DataSource> object. */
+  (*jvm_env)->DeleteLocalRef (jvm_env, o_dataset);
+
+  return (0);
+} /* }}} int ctoj_value_list_add_data_set */
+
+/* Convert a value_list_t (and data_set_t) to a org/collectd/api/ValueList */
+static jobject ctoj_value_list (JNIEnv *jvm_env, /* {{{ */
+    const data_set_t *ds, const value_list_t *vl)
+{
+  jclass c_valuelist;
+  jmethodID m_valuelist_constructor;
+  jobject o_valuelist;
+  int status;
+  int i;
+
+  /* First, create a new ValueList instance..
+   * Look up the class.. */
+  c_valuelist = (*jvm_env)->FindClass (jvm_env,
+      "org/collectd/api/ValueList");
+  if (c_valuelist == NULL)
+  {
+    ERROR ("java plugin: ctoj_value_list: "
+        "FindClass (org/collectd/api/ValueList) failed.");
+    return (NULL);
+  }
+
+  /* Lookup the `ValueList ()' constructor. */
+  m_valuelist_constructor = (*jvm_env)->GetMethodID (jvm_env, c_valuelist,
+      "<init>", "()V");
+  if (m_valuelist_constructor == NULL)
+  {
+    ERROR ("java plugin: ctoj_value_list: Cannot find the "
+        "`ValueList ()' constructor.");
+    return (NULL);
+  }
+
+  /* Create a new instance. */
+  o_valuelist = (*jvm_env)->NewObject (jvm_env, c_valuelist,
+      m_valuelist_constructor);
+  if (o_valuelist == NULL)
+  {
+    ERROR ("java plugin: ctoj_value_list: Creating a new ValueList instance "
+        "failed.");
+    return (NULL);
+  }
+
+  status = ctoj_value_list_add_data_set (jvm_env,
+      c_valuelist, o_valuelist, ds);
+  if (status != 0)
+  {
+    ERROR ("java plugin: ctoj_value_list: "
+        "ctoj_value_list_add_data_set failed.");
+    (*jvm_env)->DeleteLocalRef (jvm_env, o_valuelist);
+    return (NULL);
+  }
+
+  /* Set the strings.. */
+#define SET_STRING(str,method_name) do { \
+  status = ctoj_string (jvm_env, str, \
+      c_valuelist, o_valuelist, method_name); \
+  if (status != 0) { \
+    ERROR ("java plugin: ctoj_value_list: ctoj_string (%s) failed.", \
+        method_name); \
+    (*jvm_env)->DeleteLocalRef (jvm_env, o_valuelist); \
+    return (NULL); \
+  } } while (0)
+
+  SET_STRING (vl->host,            "setHost");
+  SET_STRING (vl->plugin,          "setPlugin");
+  SET_STRING (vl->plugin_instance, "setPluginInstance");
+  SET_STRING (vl->type,            "setType");
+  SET_STRING (vl->type_instance,   "setTypeInstance");
+
+#undef SET_STRING
+
+  /* Set the `time' member. Java stores time in milliseconds. */
+  status = ctoj_long (jvm_env, (jlong) CDTIME_T_TO_MS (vl->time),
+      c_valuelist, o_valuelist, "setTime");
+  if (status != 0)
+  {
+    ERROR ("java plugin: ctoj_value_list: ctoj_long (setTime) failed.");
+    (*jvm_env)->DeleteLocalRef (jvm_env, o_valuelist);
+    return (NULL);
+  }
+
+  /* Set the `interval' member.. */
+  status = ctoj_long (jvm_env,
+      (jlong) CDTIME_T_TO_MS (vl->interval),
+      c_valuelist, o_valuelist, "setInterval");
+  if (status != 0)
+  {
+    ERROR ("java plugin: ctoj_value_list: ctoj_long (setInterval) failed.");
+    (*jvm_env)->DeleteLocalRef (jvm_env, o_valuelist);
+    return (NULL);
+  }
+
+  for (i = 0; i < vl->values_len; i++)
+  {
+    status = ctoj_value_list_add_value (jvm_env, vl->values[i], ds->ds[i].type,
+        c_valuelist, o_valuelist);
+    if (status != 0)
+    {
+      ERROR ("java plugin: ctoj_value_list: "
+          "ctoj_value_list_add_value failed.");
+      (*jvm_env)->DeleteLocalRef (jvm_env, o_valuelist);
+      return (NULL);
+    }
+  }
+
+  return (o_valuelist);
+} /* }}} jobject ctoj_value_list */
+
+/* Convert a notification_t to a org/collectd/api/Notification */
+static jobject ctoj_notification (JNIEnv *jvm_env, /* {{{ */
+    const notification_t *n)
+{
+  jclass c_notification;
+  jmethodID m_constructor;
+  jobject o_notification;
+  int status;
+
+  /* First, create a new Notification instance..
+   * Look up the class.. */
+  c_notification = (*jvm_env)->FindClass (jvm_env,
+      "org/collectd/api/Notification");
+  if (c_notification == NULL)
+  {
+    ERROR ("java plugin: ctoj_notification: "
+        "FindClass (org/collectd/api/Notification) failed.");
+    return (NULL);
+  }
+
+  /* Lookup the `Notification ()' constructor. */
+  m_constructor = (*jvm_env)->GetMethodID (jvm_env, c_notification,
+      "<init>", "()V");
+  if (m_constructor == NULL)
+  {
+    ERROR ("java plugin: ctoj_notification: Cannot find the "
+        "`Notification ()' constructor.");
+    return (NULL);
+  }
+
+  /* Create a new instance. */
+  o_notification = (*jvm_env)->NewObject (jvm_env, c_notification,
+      m_constructor);
+  if (o_notification == NULL)
+  {
+    ERROR ("java plugin: ctoj_notification: Creating a new Notification "
+        "instance failed.");
+    return (NULL);
+  }
+
+  /* Set the strings.. */
+#define SET_STRING(str,method_name) do { \
+  status = ctoj_string (jvm_env, str, \
+      c_notification, o_notification, method_name); \
+  if (status != 0) { \
+    ERROR ("java plugin: ctoj_notification: ctoj_string (%s) failed.", \
+        method_name); \
+    (*jvm_env)->DeleteLocalRef (jvm_env, o_notification); \
+    return (NULL); \
+  } } while (0)
+
+  SET_STRING (n->host,            "setHost");
+  SET_STRING (n->plugin,          "setPlugin");
+  SET_STRING (n->plugin_instance, "setPluginInstance");
+  SET_STRING (n->type,            "setType");
+  SET_STRING (n->type_instance,   "setTypeInstance");
+  SET_STRING (n->message,         "setMessage");
+
+#undef SET_STRING
+
+  /* Set the `time' member. Java stores time in milliseconds. */
+  status = ctoj_long (jvm_env, ((jlong) n->time) * ((jlong) 1000),
+      c_notification, o_notification, "setTime");
+  if (status != 0)
+  {
+    ERROR ("java plugin: ctoj_notification: ctoj_long (setTime) failed.");
+    (*jvm_env)->DeleteLocalRef (jvm_env, o_notification);
+    return (NULL);
+  }
+
+  /* Set the `severity' member.. */
+  status = ctoj_int (jvm_env, (jint) n->severity,
+      c_notification, o_notification, "setSeverity");
+  if (status != 0)
+  {
+    ERROR ("java plugin: ctoj_notification: ctoj_int (setSeverity) failed.");
+    (*jvm_env)->DeleteLocalRef (jvm_env, o_notification);
+    return (NULL);
+  }
+
+  return (o_notification);
+} /* }}} jobject ctoj_notification */
+
+/*
+ * Java to C conversion functions
+ */
+/* Call a `String <method> ()' method. */
+static int jtoc_string (JNIEnv *jvm_env, /* {{{ */
+    char *buffer, size_t buffer_size, int empty_okay,
+    jclass class_ptr, jobject object_ptr, const char *method_name)
+{
+  jmethodID method_id;
+  jobject string_obj;
+  const char *c_str;
+
+  method_id = (*jvm_env)->GetMethodID (jvm_env, class_ptr,
+      method_name, "()Ljava/lang/String;");
+  if (method_id == NULL)
+  {
+    ERROR ("java plugin: jtoc_string: Cannot find method `String %s ()'.",
+        method_name);
+    return (-1);
+  }
+
+  string_obj = (*jvm_env)->CallObjectMethod (jvm_env, object_ptr, method_id);
+  if ((string_obj == NULL) && (empty_okay == 0))
+  {
+    ERROR ("java plugin: jtoc_string: CallObjectMethod (%s) failed.",
+        method_name);
+    return (-1);
+  }
+  else if ((string_obj == NULL) && (empty_okay != 0))
+  {
+    memset (buffer, 0, buffer_size);
+    return (0);
+  }
+
+  c_str = (*jvm_env)->GetStringUTFChars (jvm_env, string_obj, 0);
+  if (c_str == NULL)
+  {
+    ERROR ("java plugin: jtoc_string: GetStringUTFChars failed.");
+    (*jvm_env)->DeleteLocalRef (jvm_env, string_obj);
+    return (-1);
+  }
+
+  sstrncpy (buffer, c_str, buffer_size);
+
+  (*jvm_env)->ReleaseStringUTFChars (jvm_env, string_obj, c_str);
+  (*jvm_env)->DeleteLocalRef (jvm_env, string_obj);
+
+  return (0);
+} /* }}} int jtoc_string */
+
+/* Call an `int <method> ()' method. */
+static int jtoc_int (JNIEnv *jvm_env, /* {{{ */
+    jint *ret_value,
+    jclass class_ptr, jobject object_ptr, const char *method_name)
+{
+  jmethodID method_id;
+
+  method_id = (*jvm_env)->GetMethodID (jvm_env, class_ptr,
+      method_name, "()I");
+  if (method_id == NULL)
+  {
+    ERROR ("java plugin: jtoc_int: Cannot find method `int %s ()'.",
+        method_name);
+    return (-1);
+  }
+
+  *ret_value = (*jvm_env)->CallIntMethod (jvm_env, object_ptr, method_id);
+
+  return (0);
+} /* }}} int jtoc_int */
+
+/* Call a `long <method> ()' method. */
+static int jtoc_long (JNIEnv *jvm_env, /* {{{ */
+    jlong *ret_value,
+    jclass class_ptr, jobject object_ptr, const char *method_name)
+{
+  jmethodID method_id;
+
+  method_id = (*jvm_env)->GetMethodID (jvm_env, class_ptr,
+      method_name, "()J");
+  if (method_id == NULL)
+  {
+    ERROR ("java plugin: jtoc_long: Cannot find method `long %s ()'.",
+        method_name);
+    return (-1);
+  }
+
+  *ret_value = (*jvm_env)->CallLongMethod (jvm_env, object_ptr, method_id);
+
+  return (0);
+} /* }}} int jtoc_long */
+
+/* Call a `double <method> ()' method. */
+static int jtoc_double (JNIEnv *jvm_env, /* {{{ */
+    jdouble *ret_value,
+    jclass class_ptr, jobject object_ptr, const char *method_name)
+{
+  jmethodID method_id;
+
+  method_id = (*jvm_env)->GetMethodID (jvm_env, class_ptr,
+      method_name, "()D");
+  if (method_id == NULL)
+  {
+    ERROR ("java plugin: jtoc_double: Cannot find method `double %s ()'.",
+        method_name);
+    return (-1);
+  }
+
+  *ret_value = (*jvm_env)->CallDoubleMethod (jvm_env, object_ptr, method_id);
+
+  return (0);
+} /* }}} int jtoc_double */
+
+static int jtoc_value (JNIEnv *jvm_env, /* {{{ */
+    value_t *ret_value, int ds_type, jobject object_ptr)
+{
+  jclass class_ptr;
+  int status;
+
+  class_ptr = (*jvm_env)->GetObjectClass (jvm_env, object_ptr);
+
+  if (ds_type == DS_TYPE_GAUGE)
+  {
+    jdouble tmp_double;
+
+    status = jtoc_double (jvm_env, &tmp_double,
+        class_ptr, object_ptr, "doubleValue");
+    if (status != 0)
+    {
+      ERROR ("java plugin: jtoc_value: "
+          "jtoc_double failed.");
+      return (-1);
+    }
+    (*ret_value).gauge = (gauge_t) tmp_double;
+  }
+  else
+  {
+    jlong tmp_long;
+
+    status = jtoc_long (jvm_env, &tmp_long,
+        class_ptr, object_ptr, "longValue");
+    if (status != 0)
+    {
+      ERROR ("java plugin: jtoc_value: "
+          "jtoc_long failed.");
+      return (-1);
+    }
+
+    if (ds_type == DS_TYPE_DERIVE)
+      (*ret_value).derive = (derive_t) tmp_long;
+    else if (ds_type == DS_TYPE_ABSOLUTE)
+      (*ret_value).absolute = (absolute_t) tmp_long;
+    else
+      (*ret_value).counter = (counter_t) tmp_long;
+  }
+
+  return (0);
+} /* }}} int jtoc_value */
+
+/* Read a List<Number>, convert it to `value_t' and add it to the given
+ * `value_list_t'. */
+static int jtoc_values_array (JNIEnv *jvm_env, /* {{{ */
+    const data_set_t *ds, value_list_t *vl,
+    jclass class_ptr, jobject object_ptr)
+{
+  jmethodID m_getvalues;
+  jmethodID m_toarray;
+  jobject o_list;
+  jobjectArray o_number_array;
+
+  value_t *values;
+  int values_num;
+  int i;
+
+  values_num = ds->ds_num;
+
+  values = NULL;
+  o_number_array = NULL;
+  o_list = NULL;
+
+#define BAIL_OUT(status) \
+  free (values); \
+  if (o_number_array != NULL) \
+    (*jvm_env)->DeleteLocalRef (jvm_env, o_number_array); \
+  if (o_list != NULL) \
+    (*jvm_env)->DeleteLocalRef (jvm_env, o_list); \
+  return (status);
+
+  /* Call: List<Number> ValueList.getValues () */
+  m_getvalues = (*jvm_env)->GetMethodID (jvm_env, class_ptr,
+      "getValues", "()Ljava/util/List;");
+  if (m_getvalues == NULL)
+  {
+    ERROR ("java plugin: jtoc_values_array: "
+        "Cannot find method `List getValues ()'.");
+    BAIL_OUT (-1);
+  }
+
+  o_list = (*jvm_env)->CallObjectMethod (jvm_env, object_ptr, m_getvalues);
+  if (o_list == NULL)
+  {
+    ERROR ("java plugin: jtoc_values_array: "
+        "CallObjectMethod (getValues) failed.");
+    BAIL_OUT (-1);
+  }
+
+  /* Call: Number[] List.toArray () */
+  m_toarray = (*jvm_env)->GetMethodID (jvm_env,
+      (*jvm_env)->GetObjectClass (jvm_env, o_list),
+      "toArray", "()[Ljava/lang/Object;");
+  if (m_toarray == NULL)
+  {
+    ERROR ("java plugin: jtoc_values_array: "
+        "Cannot find method `Object[] toArray ()'.");
+    BAIL_OUT (-1);
+  }
+
+  o_number_array = (*jvm_env)->CallObjectMethod (jvm_env, o_list, m_toarray);
+  if (o_number_array == NULL)
+  {
+    ERROR ("java plugin: jtoc_values_array: "
+        "CallObjectMethod (toArray) failed.");
+    BAIL_OUT (-1);
+  }
+
+  values = (value_t *) calloc (values_num, sizeof (value_t));
+  if (values == NULL)
+  {
+    ERROR ("java plugin: jtoc_values_array: calloc failed.");
+    BAIL_OUT (-1);
+  }
+
+  for (i = 0; i < values_num; i++)
+  {
+    jobject o_number;
+    int status;
+
+    o_number = (*jvm_env)->GetObjectArrayElement (jvm_env,
+        o_number_array, (jsize) i);
+    if (o_number == NULL)
+    {
+      ERROR ("java plugin: jtoc_values_array: "
+          "GetObjectArrayElement (%i) failed.", i);
+      BAIL_OUT (-1);
+    }
+
+    status = jtoc_value (jvm_env, values + i, ds->ds[i].type, o_number);
+    if (status != 0)
+    {
+      ERROR ("java plugin: jtoc_values_array: "
+          "jtoc_value (%i) failed.", i);
+      BAIL_OUT (-1);
+    }
+  } /* for (i = 0; i < values_num; i++) */
+
+  vl->values = values;
+  vl->values_len = values_num;
+
+#undef BAIL_OUT
+  (*jvm_env)->DeleteLocalRef (jvm_env, o_number_array);
+  (*jvm_env)->DeleteLocalRef (jvm_env, o_list);
+  return (0);
+} /* }}} int jtoc_values_array */
+
+/* Convert a org/collectd/api/ValueList to a value_list_t. */
+static int jtoc_value_list (JNIEnv *jvm_env, value_list_t *vl, /* {{{ */
+    jobject object_ptr)
+{
+  jclass class_ptr;
+  int status;
+  jlong tmp_long;
+  const data_set_t *ds;
+
+  class_ptr = (*jvm_env)->GetObjectClass (jvm_env, object_ptr);
+  if (class_ptr == NULL)
+  {
+    ERROR ("java plugin: jtoc_value_list: GetObjectClass failed.");
+    return (-1);
+  }
+
+  /* eo == empty okay */
+#define SET_STRING(buffer,method, eo) do { \
+  status = jtoc_string (jvm_env, buffer, sizeof (buffer), eo, \
+      class_ptr, object_ptr, method); \
+  if (status != 0) { \
+    ERROR ("java plugin: jtoc_value_list: jtoc_string (%s) failed.", \
+        method); \
+    return (-1); \
+  } } while (0)
+
+  SET_STRING(vl->type, "getType", /* empty = */ 0);
+
+  ds = plugin_get_ds (vl->type);
+  if (ds == NULL)
+  {
+    ERROR ("java plugin: jtoc_value_list: Data-set `%s' is not defined. "
+        "Please consult the types.db(5) manpage for mor information.",
+        vl->type);
+    return (-1);
+  }
+
+  SET_STRING(vl->host,            "getHost",           /* empty = */ 0);
+  SET_STRING(vl->plugin,          "getPlugin",         /* empty = */ 0);
+  SET_STRING(vl->plugin_instance, "getPluginInstance", /* empty = */ 1);
+  SET_STRING(vl->type_instance,   "getTypeInstance",   /* empty = */ 1);
+
+#undef SET_STRING
+
+  status = jtoc_long (jvm_env, &tmp_long, class_ptr, object_ptr, "getTime");
+  if (status != 0)
+  {
+    ERROR ("java plugin: jtoc_value_list: jtoc_long (getTime) failed.");
+    return (-1);
+  }
+  /* Java measures time in milliseconds. */
+  vl->time = MS_TO_CDTIME_T (tmp_long);
+
+  status = jtoc_long (jvm_env, &tmp_long,
+      class_ptr, object_ptr, "getInterval");
+  if (status != 0)
+  {
+    ERROR ("java plugin: jtoc_value_list: jtoc_long (getInterval) failed.");
+    return (-1);
+  }
+  vl->interval = MS_TO_CDTIME_T (tmp_long);
+
+  status = jtoc_values_array (jvm_env, ds, vl, class_ptr, object_ptr);
+  if (status != 0)
+  {
+    ERROR ("java plugin: jtoc_value_list: jtoc_values_array failed.");
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int jtoc_value_list */
+
+/* Convert a org/collectd/api/Notification to a notification_t. */
+static int jtoc_notification (JNIEnv *jvm_env, notification_t *n, /* {{{ */
+    jobject object_ptr)
+{
+  jclass class_ptr;
+  int status;
+  jlong tmp_long;
+  jint tmp_int;
+
+  class_ptr = (*jvm_env)->GetObjectClass (jvm_env, object_ptr);
+  if (class_ptr == NULL)
+  {
+    ERROR ("java plugin: jtoc_notification: GetObjectClass failed.");
+    return (-1);
+  }
+
+  /* eo == empty okay */
+#define SET_STRING(buffer,method, eo) do { \
+  status = jtoc_string (jvm_env, buffer, sizeof (buffer), eo, \
+      class_ptr, object_ptr, method); \
+  if (status != 0) { \
+    ERROR ("java plugin: jtoc_notification: jtoc_string (%s) failed.", \
+        method); \
+    return (-1); \
+  } } while (0)
+
+  SET_STRING (n->host,            "getHost",           /* empty = */ 1);
+  SET_STRING (n->plugin,          "getPlugin",         /* empty = */ 1);
+  SET_STRING (n->plugin_instance, "getPluginInstance", /* empty = */ 1);
+  SET_STRING (n->type,            "getType",           /* empty = */ 1);
+  SET_STRING (n->type_instance,   "getTypeInstance",   /* empty = */ 1);
+  SET_STRING (n->message,         "getMessage",        /* empty = */ 0);
+
+#undef SET_STRING
+
+  status = jtoc_long (jvm_env, &tmp_long, class_ptr, object_ptr, "getTime");
+  if (status != 0)
+  {
+    ERROR ("java plugin: jtoc_notification: jtoc_long (getTime) failed.");
+    return (-1);
+  }
+  /* Java measures time in milliseconds. */
+  n->time = (time_t) (tmp_long / ((jlong) 1000));
+
+  status = jtoc_int (jvm_env, &tmp_int,
+      class_ptr, object_ptr, "getSeverity");
+  if (status != 0)
+  {
+    ERROR ("java plugin: jtoc_notification: jtoc_int (getSeverity) failed.");
+    return (-1);
+  }
+  n->severity = (int) tmp_int;
+
+  return (0);
+} /* }}} int jtoc_notification */
+/* 
+ * Functions accessible from Java
+ */
+static jint JNICALL cjni_api_dispatch_values (JNIEnv *jvm_env, /* {{{ */
+    jobject this, jobject java_vl)
+{
+  value_list_t vl = VALUE_LIST_INIT;
+  int status;
+
+  DEBUG ("cjni_api_dispatch_values: java_vl = %p;", (void *) java_vl);
+
+  status = jtoc_value_list (jvm_env, &vl, java_vl);
+  if (status != 0)
+  {
+    ERROR ("java plugin: cjni_api_dispatch_values: jtoc_value_list failed.");
+    return (-1);
+  }
+
+  status = plugin_dispatch_values (&vl);
+
+  sfree (vl.values);
+
+  return (status);
+} /* }}} jint cjni_api_dispatch_values */
+
+static jint JNICALL cjni_api_dispatch_notification (JNIEnv *jvm_env, /* {{{ */
+    jobject this, jobject o_notification)
+{
+  notification_t n;
+  int status;
+
+  memset (&n, 0, sizeof (n));
+  n.meta = NULL;
+
+  status = jtoc_notification (jvm_env, &n, o_notification);
+  if (status != 0)
+  {
+    ERROR ("java plugin: cjni_api_dispatch_notification: jtoc_notification failed.");
+    return (-1);
+  }
+
+  status = plugin_dispatch_notification (&n);
+
+  return (status);
+} /* }}} jint cjni_api_dispatch_notification */
+
+static jobject JNICALL cjni_api_get_ds (JNIEnv *jvm_env, /* {{{ */
+    jobject this, jobject o_string_type)
+{
+  const char *ds_name;
+  const data_set_t *ds;
+  jobject o_dataset;
+
+  ds_name = (*jvm_env)->GetStringUTFChars (jvm_env, o_string_type, 0);
+  if (ds_name == NULL)
+  {
+    ERROR ("java plugin: cjni_api_get_ds: GetStringUTFChars failed.");
+    return (NULL);
+  }
+
+  ds = plugin_get_ds (ds_name);
+  DEBUG ("java plugin: cjni_api_get_ds: "
+      "plugin_get_ds (%s) = %p;", ds_name, (void *) ds);
+
+  (*jvm_env)->ReleaseStringUTFChars (jvm_env, o_string_type, ds_name);
+
+  if (ds == NULL)
+    return (NULL);
+
+  o_dataset = ctoj_data_set (jvm_env, ds);
+  return (o_dataset);
+} /* }}} jint cjni_api_get_ds */
+
+static jint JNICALL cjni_api_register_config (JNIEnv *jvm_env, /* {{{ */
+    jobject this, jobject o_name, jobject o_config)
+{
+  return (cjni_callback_register (jvm_env, o_name, o_config, CB_TYPE_CONFIG));
+} /* }}} jint cjni_api_register_config */
+
+static jint JNICALL cjni_api_register_init (JNIEnv *jvm_env, /* {{{ */
+    jobject this, jobject o_name, jobject o_config)
+{
+  return (cjni_callback_register (jvm_env, o_name, o_config, CB_TYPE_INIT));
+} /* }}} jint cjni_api_register_init */
+
+static jint JNICALL cjni_api_register_read (JNIEnv *jvm_env, /* {{{ */
+    jobject this, jobject o_name, jobject o_read)
+{
+  user_data_t ud;
+  cjni_callback_info_t *cbi;
+
+  cbi = cjni_callback_info_create (jvm_env, o_name, o_read, CB_TYPE_READ);
+  if (cbi == NULL)
+    return (-1);
+
+  DEBUG ("java plugin: Registering new read callback: %s", cbi->name);
+
+  memset (&ud, 0, sizeof (ud));
+  ud.data = (void *) cbi;
+  ud.free_func = cjni_callback_info_destroy;
+
+  plugin_register_complex_read (/* group = */ NULL, cbi->name, cjni_read,
+      /* interval = */ NULL, &ud);
+
+  (*jvm_env)->DeleteLocalRef (jvm_env, o_read);
+
+  return (0);
+} /* }}} jint cjni_api_register_read */
+
+static jint JNICALL cjni_api_register_write (JNIEnv *jvm_env, /* {{{ */
+    jobject this, jobject o_name, jobject o_write)
+{
+  user_data_t ud;
+  cjni_callback_info_t *cbi;
+
+  cbi = cjni_callback_info_create (jvm_env, o_name, o_write, CB_TYPE_WRITE);
+  if (cbi == NULL)
+    return (-1);
+
+  DEBUG ("java plugin: Registering new write callback: %s", cbi->name);
+
+  memset (&ud, 0, sizeof (ud));
+  ud.data = (void *) cbi;
+  ud.free_func = cjni_callback_info_destroy;
+
+  plugin_register_write (cbi->name, cjni_write, &ud);
+
+  (*jvm_env)->DeleteLocalRef (jvm_env, o_write);
+
+  return (0);
+} /* }}} jint cjni_api_register_write */
+
+static jint JNICALL cjni_api_register_flush (JNIEnv *jvm_env, /* {{{ */
+    jobject this, jobject o_name, jobject o_flush)
+{
+  user_data_t ud;
+  cjni_callback_info_t *cbi;
+
+  cbi = cjni_callback_info_create (jvm_env, o_name, o_flush, CB_TYPE_FLUSH);
+  if (cbi == NULL)
+    return (-1);
+
+  DEBUG ("java plugin: Registering new flush callback: %s", cbi->name);
+
+  memset (&ud, 0, sizeof (ud));
+  ud.data = (void *) cbi;
+  ud.free_func = cjni_callback_info_destroy;
+
+  plugin_register_flush (cbi->name, cjni_flush, &ud);
+
+  (*jvm_env)->DeleteLocalRef (jvm_env, o_flush);
+
+  return (0);
+} /* }}} jint cjni_api_register_flush */
+
+static jint JNICALL cjni_api_register_shutdown (JNIEnv *jvm_env, /* {{{ */
+    jobject this, jobject o_name, jobject o_shutdown)
+{
+  return (cjni_callback_register (jvm_env, o_name, o_shutdown,
+        CB_TYPE_SHUTDOWN));
+} /* }}} jint cjni_api_register_shutdown */
+
+static jint JNICALL cjni_api_register_log (JNIEnv *jvm_env, /* {{{ */
+    jobject this, jobject o_name, jobject o_log)
+{
+  user_data_t ud;
+  cjni_callback_info_t *cbi;
+
+  cbi = cjni_callback_info_create (jvm_env, o_name, o_log, CB_TYPE_LOG);
+  if (cbi == NULL)
+    return (-1);
+
+  DEBUG ("java plugin: Registering new log callback: %s", cbi->name);
+
+  memset (&ud, 0, sizeof (ud));
+  ud.data = (void *) cbi;
+  ud.free_func = cjni_callback_info_destroy;
+
+  plugin_register_log (cbi->name, cjni_log, &ud);
+
+  (*jvm_env)->DeleteLocalRef (jvm_env, o_log);
+
+  return (0);
+} /* }}} jint cjni_api_register_log */
+
+static jint JNICALL cjni_api_register_notification (JNIEnv *jvm_env, /* {{{ */
+    jobject this, jobject o_name, jobject o_notification)
+{
+  user_data_t ud;
+  cjni_callback_info_t *cbi;
+
+  cbi = cjni_callback_info_create (jvm_env, o_name, o_notification,
+      CB_TYPE_NOTIFICATION);
+  if (cbi == NULL)
+    return (-1);
+
+  DEBUG ("java plugin: Registering new notification callback: %s", cbi->name);
+
+  memset (&ud, 0, sizeof (ud));
+  ud.data = (void *) cbi;
+  ud.free_func = cjni_callback_info_destroy;
+
+  plugin_register_notification (cbi->name, cjni_notification, &ud);
+
+  (*jvm_env)->DeleteLocalRef (jvm_env, o_notification);
+
+  return (0);
+} /* }}} jint cjni_api_register_notification */
+
+static jint JNICALL cjni_api_register_match_target (JNIEnv *jvm_env, /* {{{ */
+    jobject this, jobject o_name, jobject o_match, int type)
+{
+  int status;
+  const char *c_name;
+
+  c_name = (*jvm_env)->GetStringUTFChars (jvm_env, o_name, 0);
+  if (c_name == NULL)
+  {
+    ERROR ("java plugin: cjni_api_register_match_target: "
+        "GetStringUTFChars failed.");
+    return (-1);
+  }
+
+  status = cjni_callback_register (jvm_env, o_name, o_match, type);
+  if (status != 0)
+  {
+    (*jvm_env)->ReleaseStringUTFChars (jvm_env, o_name, c_name);
+    return (-1);
+  }
+
+  if (type == CB_TYPE_MATCH)
+  {
+    match_proc_t m_proc;
+
+    memset (&m_proc, 0, sizeof (m_proc));
+    m_proc.create  = cjni_match_target_create;
+    m_proc.destroy = cjni_match_target_destroy;
+    m_proc.match   = (void *) cjni_match_target_invoke;
+
+    status = fc_register_match (c_name, m_proc);
+  }
+  else if (type == CB_TYPE_TARGET)
+  {
+    target_proc_t t_proc;
+
+    memset (&t_proc, 0, sizeof (t_proc));
+    t_proc.create  = cjni_match_target_create;
+    t_proc.destroy = cjni_match_target_destroy;
+    t_proc.invoke  = cjni_match_target_invoke;
+
+    status = fc_register_target (c_name, t_proc);
+  }
+  else
+  {
+    ERROR ("java plugin: cjni_api_register_match_target: "
+        "Don't know whether to create a match or a target.");
+    (*jvm_env)->ReleaseStringUTFChars (jvm_env, o_name, c_name);
+    return (-1);
+  }
+
+  if (status != 0)
+  {
+    ERROR ("java plugin: cjni_api_register_match_target: "
+        "%s failed.",
+        (type == CB_TYPE_MATCH) ? "fc_register_match" : "fc_register_target");
+    (*jvm_env)->ReleaseStringUTFChars (jvm_env, o_name, c_name);
+    return (-1);
+  }
+
+  (*jvm_env)->ReleaseStringUTFChars (jvm_env, o_name, c_name);
+
+  return (0);
+} /* }}} jint cjni_api_register_match_target */
+
+static jint JNICALL cjni_api_register_match (JNIEnv *jvm_env, /* {{{ */
+    jobject this, jobject o_name, jobject o_match)
+{
+  return (cjni_api_register_match_target (jvm_env, this, o_name, o_match,
+        CB_TYPE_MATCH));
+} /* }}} jint cjni_api_register_match */
+
+static jint JNICALL cjni_api_register_target (JNIEnv *jvm_env, /* {{{ */
+    jobject this, jobject o_name, jobject o_target)
+{
+  return (cjni_api_register_match_target (jvm_env, this, o_name, o_target,
+        CB_TYPE_TARGET));
+} /* }}} jint cjni_api_register_target */
+
+static void JNICALL cjni_api_log (JNIEnv *jvm_env, /* {{{ */
+    jobject this, jint severity, jobject o_message)
+{
+  const char *c_str;
+
+  c_str = (*jvm_env)->GetStringUTFChars (jvm_env, o_message, 0);
+  if (c_str == NULL)
+  {
+    ERROR ("java plugin: cjni_api_log: GetStringUTFChars failed.");
+    return;
+  }
+
+  if (severity < LOG_ERR)
+    severity = LOG_ERR;
+  if (severity > LOG_DEBUG)
+    severity = LOG_DEBUG;
+
+  plugin_log (severity, "%s", c_str);
+
+  (*jvm_env)->ReleaseStringUTFChars (jvm_env, o_message, c_str);
+} /* }}} void cjni_api_log */
+
+/* List of ``native'' functions, i. e. C-functions that can be called from
+ * Java. */
+static JNINativeMethod jni_api_functions[] = /* {{{ */
+{
+  { "dispatchValues",
+    "(Lorg/collectd/api/ValueList;)I",
+    cjni_api_dispatch_values },
+
+  { "dispatchNotification",
+    "(Lorg/collectd/api/Notification;)I",
+    cjni_api_dispatch_notification },
+
+  { "getDS",
+    "(Ljava/lang/String;)Lorg/collectd/api/DataSet;",
+    cjni_api_get_ds },
+
+  { "registerConfig",
+    "(Ljava/lang/String;Lorg/collectd/api/CollectdConfigInterface;)I",
+    cjni_api_register_config },
+
+  { "registerInit",
+    "(Ljava/lang/String;Lorg/collectd/api/CollectdInitInterface;)I",
+    cjni_api_register_init },
+
+  { "registerRead",
+    "(Ljava/lang/String;Lorg/collectd/api/CollectdReadInterface;)I",
+    cjni_api_register_read },
+
+  { "registerWrite",
+    "(Ljava/lang/String;Lorg/collectd/api/CollectdWriteInterface;)I",
+    cjni_api_register_write },
+
+  { "registerFlush",
+    "(Ljava/lang/String;Lorg/collectd/api/CollectdFlushInterface;)I",
+    cjni_api_register_flush },
+
+  { "registerShutdown",
+    "(Ljava/lang/String;Lorg/collectd/api/CollectdShutdownInterface;)I",
+    cjni_api_register_shutdown },
+
+  { "registerLog",
+    "(Ljava/lang/String;Lorg/collectd/api/CollectdLogInterface;)I",
+    cjni_api_register_log },
+
+  { "registerNotification",
+    "(Ljava/lang/String;Lorg/collectd/api/CollectdNotificationInterface;)I",
+    cjni_api_register_notification },
+
+  { "registerMatch",
+    "(Ljava/lang/String;Lorg/collectd/api/CollectdMatchFactoryInterface;)I",
+    cjni_api_register_match },
+
+  { "registerTarget",
+    "(Ljava/lang/String;Lorg/collectd/api/CollectdTargetFactoryInterface;)I",
+    cjni_api_register_target },
+
+  { "log",
+    "(ILjava/lang/String;)V",
+    cjni_api_log },
+};
+static size_t jni_api_functions_num = sizeof (jni_api_functions)
+  / sizeof (jni_api_functions[0]);
+/* }}} */
+
+/*
+ * Functions
+ */
+/* Allocate a `cjni_callback_info_t' given the type and objects necessary for
+ * all registration functions. */
+static cjni_callback_info_t *cjni_callback_info_create (JNIEnv *jvm_env, /* {{{ */
+    jobject o_name, jobject o_callback, int type)
+{
+  const char *c_name;
+  cjni_callback_info_t *cbi;
+  const char *method_name;
+  const char *method_signature;
+
+  switch (type)
+  {
+    case CB_TYPE_CONFIG:
+      method_name = "config";
+      method_signature = "(Lorg/collectd/api/OConfigItem;)I";
+      break;
+
+    case CB_TYPE_INIT:
+      method_name = "init";
+      method_signature = "()I";
+      break;
+
+    case CB_TYPE_READ:
+      method_name = "read";
+      method_signature = "()I";
+      break;
+
+    case CB_TYPE_WRITE:
+      method_name = "write";
+      method_signature = "(Lorg/collectd/api/ValueList;)I";
+      break;
+
+    case CB_TYPE_FLUSH:
+      method_name = "flush";
+      method_signature = "(Ljava/lang/Number;Ljava/lang/String;)I";
+      break;
+
+    case CB_TYPE_SHUTDOWN:
+      method_name = "shutdown";
+      method_signature = "()I";
+      break;
+
+    case CB_TYPE_LOG:
+      method_name = "log";
+      method_signature = "(ILjava/lang/String;)V";
+      break;
+
+    case CB_TYPE_NOTIFICATION:
+      method_name = "notification";
+      method_signature = "(Lorg/collectd/api/Notification;)I";
+      break;
+
+    case CB_TYPE_MATCH:
+      method_name = "createMatch";
+      method_signature = "(Lorg/collectd/api/OConfigItem;)"
+        "Lorg/collectd/api/CollectdMatchInterface;";
+      break;
+
+    case CB_TYPE_TARGET:
+      method_name = "createTarget";
+      method_signature = "(Lorg/collectd/api/OConfigItem;)"
+        "Lorg/collectd/api/CollectdTargetInterface;";
+      break;
+
+    default:
+      ERROR ("java plugin: cjni_callback_info_create: Unknown type: %#x",
+          type);
+      return (NULL);
+  }
+
+  c_name = (*jvm_env)->GetStringUTFChars (jvm_env, o_name, 0);
+  if (c_name == NULL)
+  {
+    ERROR ("java plugin: cjni_callback_info_create: "
+        "GetStringUTFChars failed.");
+    return (NULL);
+  }
+
+  cbi = (cjni_callback_info_t *) malloc (sizeof (*cbi));
+  if (cbi == NULL)
+  {
+    ERROR ("java plugin: cjni_callback_info_create: malloc failed.");
+    (*jvm_env)->ReleaseStringUTFChars (jvm_env, o_name, c_name);
+    return (NULL);
+  }
+  memset (cbi, 0, sizeof (*cbi));
+  cbi->type = type;
+
+  cbi->name = strdup (c_name);
+  if (cbi->name == NULL)
+  {
+    pthread_mutex_unlock (&java_callbacks_lock);
+    ERROR ("java plugin: cjni_callback_info_create: strdup failed.");
+    (*jvm_env)->ReleaseStringUTFChars (jvm_env, o_name, c_name);
+    return (NULL);
+  }
+
+  (*jvm_env)->ReleaseStringUTFChars (jvm_env, o_name, c_name);
+
+  cbi->object = (*jvm_env)->NewGlobalRef (jvm_env, o_callback);
+  if (cbi->object == NULL)
+  {
+    ERROR ("java plugin: cjni_callback_info_create: NewGlobalRef failed.");
+    free (cbi);
+    return (NULL);
+  }
+
+  cbi->class  = (*jvm_env)->GetObjectClass (jvm_env, cbi->object);
+  if (cbi->class == NULL)
+  {
+    ERROR ("java plugin: cjni_callback_info_create: GetObjectClass failed.");
+    free (cbi);
+    return (NULL);
+  }
+
+  cbi->method = (*jvm_env)->GetMethodID (jvm_env, cbi->class,
+      method_name, method_signature);
+  if (cbi->method == NULL)
+  {
+    ERROR ("java plugin: cjni_callback_info_create: "
+        "Cannot find the `%s' method with signature `%s'.",
+        method_name, method_signature);
+    free (cbi);
+    return (NULL);
+  }
+
+  return (cbi);
+} /* }}} cjni_callback_info_t cjni_callback_info_create */
+
+/* Allocate a `cjni_callback_info_t' via `cjni_callback_info_create' and add it
+ * to the global `java_callbacks' variable. This is used for `config', `init',
+ * and `shutdown' callbacks. */
+static int cjni_callback_register (JNIEnv *jvm_env, /* {{{ */
+    jobject o_name, jobject o_callback, int type)
+{
+  cjni_callback_info_t *cbi;
+  cjni_callback_info_t *tmp;
+#if COLLECT_DEBUG
+  const char *type_str;
+#endif
+
+  cbi = cjni_callback_info_create (jvm_env, o_name, o_callback, type);
+  if (cbi == NULL)
+    return (-1);
+
+#if COLLECT_DEBUG
+  switch (type)
+  {
+    case CB_TYPE_CONFIG:
+      type_str = "config";
+      break;
+
+    case CB_TYPE_INIT:
+      type_str = "init";
+      break;
+
+    case CB_TYPE_SHUTDOWN:
+      type_str = "shutdown";
+      break;
+
+    case CB_TYPE_MATCH:
+      type_str = "match";
+      break;
+
+    case CB_TYPE_TARGET:
+      type_str = "target";
+      break;
+
+    default:
+      type_str = "<unknown>";
+  }
+  DEBUG ("java plugin: Registering new %s callback: %s",
+      type_str, cbi->name);
+#endif
+
+  pthread_mutex_lock (&java_callbacks_lock);
+
+  tmp = (cjni_callback_info_t *) realloc (java_callbacks,
+      (java_callbacks_num + 1) * sizeof (*java_callbacks));
+  if (tmp == NULL)
+  {
+    pthread_mutex_unlock (&java_callbacks_lock);
+    ERROR ("java plugin: cjni_callback_register: realloc failed.");
+
+    (*jvm_env)->DeleteGlobalRef (jvm_env, cbi->object);
+    free (cbi);
+
+    return (-1);
+  }
+  java_callbacks = tmp;
+  java_callbacks[java_callbacks_num] = *cbi;
+  java_callbacks_num++;
+
+  pthread_mutex_unlock (&java_callbacks_lock);
+
+  free (cbi);
+  return (0);
+} /* }}} int cjni_callback_register */
+
+/* Callback for `pthread_key_create'. It frees the data contained in
+ * `jvm_env_key' and prints a warning if the reference counter is not zero. */
+static void cjni_jvm_env_destroy (void *args) /* {{{ */
+{
+  cjni_jvm_env_t *cjni_env;
+
+  if (args == NULL)
+    return;
+
+  cjni_env = (cjni_jvm_env_t *) args;
+
+  if (cjni_env->reference_counter > 0)
+  {
+    ERROR ("java plugin: cjni_jvm_env_destroy: "
+        "cjni_env->reference_counter = %i;", cjni_env->reference_counter);
+  }
+
+  if (cjni_env->jvm_env != NULL)
+  {
+    ERROR ("java plugin: cjni_jvm_env_destroy: cjni_env->jvm_env = %p;",
+        (void *) cjni_env->jvm_env);
+  }
+
+  /* The pointer is allocated in `cjni_thread_attach' */
+  free (cjni_env);
+} /* }}} void cjni_jvm_env_destroy */
+
+/* Register ``native'' functions with the JVM. Native functions are C-functions
+ * that can be called by Java code. */
+static int cjni_init_native (JNIEnv *jvm_env) /* {{{ */
+{
+  jclass api_class_ptr;
+  int status;
+
+  api_class_ptr = (*jvm_env)->FindClass (jvm_env, "org/collectd/api/Collectd");
+  if (api_class_ptr == NULL)
+  {
+    ERROR ("cjni_init_native: Cannot find the API class \"org.collectd.api"
+        ".Collectd\". Please set the correct class path "
+        "using 'JVMArg \"-Djava.class.path=...\"'.");
+    return (-1);
+  }
+
+  status = (*jvm_env)->RegisterNatives (jvm_env, api_class_ptr,
+      jni_api_functions, (jint) jni_api_functions_num);
+  if (status != 0)
+  {
+    ERROR ("cjni_init_native: RegisterNatives failed with status %i.", status);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int cjni_init_native */
+
+/* Create the JVM. This is called when the first thread tries to access the JVM
+ * via cjni_thread_attach. */
+static int cjni_create_jvm (void) /* {{{ */
+{
+  JNIEnv *jvm_env;
+  JavaVMInitArgs vm_args;
+  JavaVMOption vm_options[jvm_argc];
+
+  int status;
+  size_t i;
+
+  if (jvm != NULL)
+    return (0);
+
+  status = pthread_key_create (&jvm_env_key, cjni_jvm_env_destroy);
+  if (status != 0)
+  {
+    ERROR ("java plugin: cjni_create_jvm: pthread_key_create failed "
+        "with status %i.", status);
+    return (-1);
+  }
+
+  jvm_env = NULL;
+
+  memset (&vm_args, 0, sizeof (vm_args));
+  vm_args.version = JNI_VERSION_1_2;
+  vm_args.options = vm_options;
+  vm_args.nOptions = (jint) jvm_argc;
+
+  for (i = 0; i < jvm_argc; i++)
+  {
+    DEBUG ("java plugin: cjni_create_jvm: jvm_argv[%zu] = %s",
+        i, jvm_argv[i]);
+    vm_args.options[i].optionString = jvm_argv[i];
+  }
+
+  status = JNI_CreateJavaVM (&jvm, (void *) &jvm_env, (void *) &vm_args);
+  if (status != 0)
+  {
+    ERROR ("java plugin: cjni_create_jvm: "
+        "JNI_CreateJavaVM failed with status %i.",
+       status);
+    return (-1);
+  }
+  assert (jvm != NULL);
+  assert (jvm_env != NULL);
+
+  /* Call RegisterNatives */
+  status = cjni_init_native (jvm_env);
+  if (status != 0)
+  {
+    ERROR ("java plugin: cjni_create_jvm: cjni_init_native failed.");
+    return (-1);
+  }
+
+  DEBUG ("java plugin: The JVM has been created.");
+  return (0);
+} /* }}} int cjni_create_jvm */
+
+/* Increase the reference counter to the JVM for this thread. If it was zero,
+ * attach the JVM first. */
+static JNIEnv *cjni_thread_attach (void) /* {{{ */
+{
+  cjni_jvm_env_t *cjni_env;
+  JNIEnv *jvm_env;
+
+  /* If we're the first thread to access the JVM, we'll have to create it
+   * first.. */
+  if (jvm == NULL)
+  {
+    int status;
+
+    status = cjni_create_jvm ();
+    if (status != 0)
+    {
+      ERROR ("java plugin: cjni_thread_attach: cjni_create_jvm failed.");
+      return (NULL);
+    }
+  }
+  assert (jvm != NULL);
+
+  cjni_env = pthread_getspecific (jvm_env_key);
+  if (cjni_env == NULL)
+  {
+    /* This pointer is free'd in `cjni_jvm_env_destroy'. */
+    cjni_env = (cjni_jvm_env_t *) malloc (sizeof (*cjni_env));
+    if (cjni_env == NULL)
+    {
+      ERROR ("java plugin: cjni_thread_attach: malloc failed.");
+      return (NULL);
+    }
+    memset (cjni_env, 0, sizeof (*cjni_env));
+    cjni_env->reference_counter = 0;
+    cjni_env->jvm_env = NULL;
+
+    pthread_setspecific (jvm_env_key, cjni_env);
+  }
+
+  if (cjni_env->reference_counter > 0)
+  {
+    cjni_env->reference_counter++;
+    jvm_env = cjni_env->jvm_env;
+  }
+  else
+  {
+    int status;
+    JavaVMAttachArgs args;
+
+    assert (cjni_env->jvm_env == NULL);
+
+    memset (&args, 0, sizeof (args));
+    args.version = JNI_VERSION_1_2;
+
+    status = (*jvm)->AttachCurrentThread (jvm, (void *) &jvm_env, (void *) &args);
+    if (status != 0)
+    {
+      ERROR ("java plugin: cjni_thread_attach: AttachCurrentThread failed "
+          "with status %i.", status);
+      return (NULL);
+    }
+
+    cjni_env->reference_counter = 1;
+    cjni_env->jvm_env = jvm_env;
+  }
+
+  DEBUG ("java plugin: cjni_thread_attach: cjni_env->reference_counter = %i",
+      cjni_env->reference_counter);
+  assert (jvm_env != NULL);
+  return (jvm_env);
+} /* }}} JNIEnv *cjni_thread_attach */
+
+/* Decrease the reference counter of this thread. If it reaches zero, detach
+ * from the JVM. */
+static int cjni_thread_detach (void) /* {{{ */
+{
+  cjni_jvm_env_t *cjni_env;
+  int status;
+
+  cjni_env = pthread_getspecific (jvm_env_key);
+  if (cjni_env == NULL)
+  {
+    ERROR ("java plugin: cjni_thread_detach: pthread_getspecific failed.");
+    return (-1);
+  }
+
+  assert (cjni_env->reference_counter > 0);
+  assert (cjni_env->jvm_env != NULL);
+
+  cjni_env->reference_counter--;
+  DEBUG ("java plugin: cjni_thread_detach: cjni_env->reference_counter = %i",
+      cjni_env->reference_counter);
+
+  if (cjni_env->reference_counter > 0)
+    return (0);
+
+  status = (*jvm)->DetachCurrentThread (jvm);
+  if (status != 0)
+  {
+    ERROR ("java plugin: cjni_thread_detach: DetachCurrentThread failed "
+        "with status %i.", status);
+  }
+
+  cjni_env->reference_counter = 0;
+  cjni_env->jvm_env = NULL;
+
+  return (0);
+} /* }}} JNIEnv *cjni_thread_attach */
+
+static int cjni_config_add_jvm_arg (oconfig_item_t *ci) /* {{{ */
+{
+  char **tmp;
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("java plugin: `JVMArg' needs exactly one string argument.");
+    return (-1);
+  }
+
+  if (jvm != NULL)
+  {
+    ERROR ("java plugin: All `JVMArg' options MUST appear before all "
+        "`LoadPlugin' options! The JVM is already started and I have to "
+        "ignore this argument: %s",
+        ci->values[0].value.string);
+    return (-1);
+  }
+
+  tmp = (char **) realloc (jvm_argv, sizeof (char *) * (jvm_argc + 1));
+  if (tmp == NULL)
+  {
+    ERROR ("java plugin: realloc failed.");
+    return (-1);
+  }
+  jvm_argv = tmp;
+
+  jvm_argv[jvm_argc] = strdup (ci->values[0].value.string);
+  if (jvm_argv[jvm_argc] == NULL)
+  {
+    ERROR ("java plugin: strdup failed.");
+    return (-1);
+  }
+  jvm_argc++;
+
+  return (0);
+} /* }}} int cjni_config_add_jvm_arg */
+
+static int cjni_config_load_plugin (oconfig_item_t *ci) /* {{{ */
+{
+  JNIEnv *jvm_env;
+  java_plugin_class_t *class;
+  jmethodID constructor_id;
+  jobject tmp_object;
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("java plugin: `LoadPlugin' needs exactly one string argument.");
+    return (-1);
+  }
+
+  jvm_env = cjni_thread_attach ();
+  if (jvm_env == NULL)
+    return (-1);
+
+  class = (java_plugin_class_t *) realloc (java_classes_list,
+      (java_classes_list_len + 1) * sizeof (*java_classes_list));
+  if (class == NULL)
+  {
+    ERROR ("java plugin: realloc failed.");
+    cjni_thread_detach ();
+    return (-1);
+  }
+  java_classes_list = class;
+  class = java_classes_list + java_classes_list_len;
+
+  memset (class, 0, sizeof (*class));
+  class->name = strdup (ci->values[0].value.string);
+  if (class->name == NULL)
+  {
+    ERROR ("java plugin: strdup failed.");
+    cjni_thread_detach ();
+    return (-1);
+  }
+  class->class = NULL;
+  class->object = NULL;
+
+  { /* Replace all dots ('.') with slashes ('/'). Dots are usually used
+       thorough the Java community, but (Sun's) `FindClass' and friends need
+       slashes. */
+    size_t i;
+    for (i = 0; class->name[i] != 0; i++)
+      if (class->name[i] == '.')
+        class->name[i] = '/';
+  }
+
+  DEBUG ("java plugin: Loading class %s", class->name);
+
+  class->class = (*jvm_env)->FindClass (jvm_env, class->name);
+  if (class->class == NULL)
+  {
+    ERROR ("java plugin: cjni_config_load_plugin: FindClass (%s) failed.",
+        class->name);
+    cjni_thread_detach ();
+    free (class->name);
+    return (-1);
+  }
+
+  constructor_id = (*jvm_env)->GetMethodID (jvm_env, class->class,
+      "<init>", "()V");
+  if (constructor_id == NULL)
+  {
+    ERROR ("java plugin: cjni_config_load_plugin: "
+        "Could not find the constructor for `%s'.",
+        class->name);
+    cjni_thread_detach ();
+    free (class->name);
+    return (-1);
+  }
+
+  tmp_object = (*jvm_env)->NewObject (jvm_env, class->class,
+      constructor_id);
+  if (tmp_object != NULL)
+    class->object = (*jvm_env)->NewGlobalRef (jvm_env, tmp_object);
+  else
+    class->object = NULL;
+  if (class->object == NULL)
+  {
+    ERROR ("java plugin: cjni_config_load_plugin: "
+        "Could create a new `%s' object.",
+        class->name);
+    cjni_thread_detach ();
+    free (class->name);
+    return (-1);
+  }
+
+  cjni_thread_detach ();
+
+  java_classes_list_len++;
+
+  return (0);
+} /* }}} int cjni_config_load_plugin */
+
+static int cjni_config_plugin_block (oconfig_item_t *ci) /* {{{ */
+{
+  JNIEnv *jvm_env;
+  cjni_callback_info_t *cbi;
+  jobject o_ocitem;
+  const char *name;
+  size_t i;
+
+  jclass class;
+  jmethodID method;
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("java plugin: `Plugin' blocks "
+        "need exactly one string argument.");
+    return (-1);
+  }
+
+  name = ci->values[0].value.string;
+
+  cbi = NULL;
+  for (i = 0; i < java_callbacks_num; i++)
+  {
+    if (java_callbacks[i].type != CB_TYPE_CONFIG)
+      continue;
+
+    if (strcmp (name, java_callbacks[i].name) != 0)
+      continue;
+
+    cbi = java_callbacks + i;
+    break;
+  }
+
+  if (cbi == NULL)
+  {
+    NOTICE ("java plugin: Configuration block for `%s' found, but no such "
+        "configuration callback has been registered. Please make sure, the "
+        "`LoadPlugin' lines precede the `Plugin' blocks.",
+        name);
+    return (0);
+  }
+
+  DEBUG ("java plugin: Configuring %s", name);
+
+  jvm_env = cjni_thread_attach ();
+  if (jvm_env == NULL)
+    return (-1);
+
+  o_ocitem = ctoj_oconfig_item (jvm_env, ci);
+  if (o_ocitem == NULL)
+  {
+    ERROR ("java plugin: cjni_config_plugin_block: ctoj_oconfig_item failed.");
+    cjni_thread_detach ();
+    return (-1);
+  }
+
+  class = (*jvm_env)->GetObjectClass (jvm_env, cbi->object);
+  method = (*jvm_env)->GetMethodID (jvm_env, class,
+      "config", "(Lorg/collectd/api/OConfigItem;)I");
+
+  (*jvm_env)->CallIntMethod (jvm_env,
+      cbi->object, method, o_ocitem);
+
+  (*jvm_env)->DeleteLocalRef (jvm_env, o_ocitem);
+  cjni_thread_detach ();
+  return (0);
+} /* }}} int cjni_config_plugin_block */
+
+static int cjni_config_perform (oconfig_item_t *ci) /* {{{ */
+{
+  int success;
+  int errors;
+  int status;
+  int i;
+
+  success = 0;
+  errors = 0;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("JVMArg", child->key) == 0)
+    {
+      status = cjni_config_add_jvm_arg (child);
+      if (status == 0)
+        success++;
+      else
+        errors++;
+    }
+    else if (strcasecmp ("LoadPlugin", child->key) == 0)
+    {
+      status = cjni_config_load_plugin (child);
+      if (status == 0)
+        success++;
+      else
+        errors++;
+    }
+    else if (strcasecmp ("Plugin", child->key) == 0)
+    {
+      status = cjni_config_plugin_block (child);
+      if (status == 0)
+        success++;
+      else
+        errors++;
+    }
+    else
+    {
+      WARNING ("java plugin: Option `%s' not allowed here.", child->key);
+      errors++;
+    }
+  }
+
+  DEBUG ("java plugin: jvm_argc = %zu;", jvm_argc);
+  DEBUG ("java plugin: java_classes_list_len = %zu;", java_classes_list_len);
+
+  if ((success == 0) && (errors > 0))
+  {
+    ERROR ("java plugin: All statements failed.");
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int cjni_config_perform */
+
+/* Copy the children of `ci' to the global `config_block' variable. */
+static int cjni_config_callback (oconfig_item_t *ci) /* {{{ */
+{
+  oconfig_item_t *ci_copy;
+  oconfig_item_t *tmp;
+
+  assert (ci != NULL);
+  if (ci->children_num == 0)
+    return (0); /* nothing to do */
+
+  ci_copy = oconfig_clone (ci);
+  if (ci_copy == NULL)
+  {
+    ERROR ("java plugin: oconfig_clone failed.");
+    return (-1);
+  }
+
+  if (config_block == NULL)
+  {
+    config_block = ci_copy;
+    return (0);
+  }
+
+  tmp = realloc (config_block->children,
+      (config_block->children_num + ci_copy->children_num) * sizeof (*tmp));
+  if (tmp == NULL)
+  {
+    ERROR ("java plugin: realloc failed.");
+    oconfig_free (ci_copy);
+    return (-1);
+  }
+  config_block->children = tmp;
+
+  /* Copy the pointers */
+  memcpy (config_block->children + config_block->children_num,
+      ci_copy->children,
+      ci_copy->children_num * sizeof (*ci_copy->children));
+  config_block->children_num += ci_copy->children_num;
+
+  /* Delete the pointers from the copy, so `oconfig_free' can't free them. */
+  memset (ci_copy->children, 0,
+      ci_copy->children_num * sizeof (*ci_copy->children));
+  ci_copy->children_num = 0;
+
+  oconfig_free (ci_copy);
+
+  return (0);
+} /* }}} int cjni_config_callback */
+
+/* Free the data contained in the `user_data_t' pointer passed to `cjni_read'
+ * and `cjni_write'. In particular, delete the global reference to the Java
+ * object. */
+static void cjni_callback_info_destroy (void *arg) /* {{{ */
+{
+  JNIEnv *jvm_env;
+  cjni_callback_info_t *cbi;
+
+  DEBUG ("java plugin: cjni_callback_info_destroy (arg = %p);", arg);
+
+  cbi = (cjni_callback_info_t *) arg;
+
+  /* This condition can occurr when shutting down. */
+  if (jvm == NULL)
+  {
+    sfree (cbi);
+    return;
+  }
+
+  if (arg == NULL)
+    return;
+
+  jvm_env = cjni_thread_attach ();
+  if (jvm_env == NULL)
+  {
+    ERROR ("java plugin: cjni_callback_info_destroy: cjni_thread_attach failed.");
+    return;
+  }
+
+  (*jvm_env)->DeleteGlobalRef (jvm_env, cbi->object);
+
+  cbi->method = NULL;
+  cbi->object = NULL;
+  cbi->class  = NULL;
+  free (cbi);
+
+  cjni_thread_detach ();
+} /* }}} void cjni_callback_info_destroy */
+
+/* Call the CB_TYPE_READ callback pointed to by the `user_data_t' pointer. */
+static int cjni_read (user_data_t *ud) /* {{{ */
+{
+  JNIEnv *jvm_env;
+  cjni_callback_info_t *cbi;
+  int status;
+  int ret_status;
+
+  if (jvm == NULL)
+  {
+    ERROR ("java plugin: cjni_read: jvm == NULL");
+    return (-1);
+  }
+
+  if ((ud == NULL) || (ud->data == NULL))
+  {
+    ERROR ("java plugin: cjni_read: Invalid user data.");
+    return (-1);
+  }
+
+  jvm_env = cjni_thread_attach ();
+  if (jvm_env == NULL)
+    return (-1);
+
+  cbi = (cjni_callback_info_t *) ud->data;
+
+  ret_status = (*jvm_env)->CallIntMethod (jvm_env, cbi->object,
+      cbi->method);
+
+  status = cjni_thread_detach ();
+  if (status != 0)
+  {
+    ERROR ("java plugin: cjni_read: cjni_thread_detach failed.");
+    return (-1);
+  }
+
+  return (ret_status);
+} /* }}} int cjni_read */
+
+/* Call the CB_TYPE_WRITE callback pointed to by the `user_data_t' pointer. */
+static int cjni_write (const data_set_t *ds, const value_list_t *vl, /* {{{ */
+    user_data_t *ud)
+{
+  JNIEnv *jvm_env;
+  cjni_callback_info_t *cbi;
+  jobject vl_java;
+  int status;
+  int ret_status;
+
+  if (jvm == NULL)
+  {
+    ERROR ("java plugin: cjni_write: jvm == NULL");
+    return (-1);
+  }
+
+  if ((ud == NULL) || (ud->data == NULL))
+  {
+    ERROR ("java plugin: cjni_write: Invalid user data.");
+    return (-1);
+  }
+
+  jvm_env = cjni_thread_attach ();
+  if (jvm_env == NULL)
+    return (-1);
+
+  cbi = (cjni_callback_info_t *) ud->data;
+
+  vl_java = ctoj_value_list (jvm_env, ds, vl);
+  if (vl_java == NULL)
+  {
+    ERROR ("java plugin: cjni_write: ctoj_value_list failed.");
+    return (-1);
+  }
+
+  ret_status = (*jvm_env)->CallIntMethod (jvm_env,
+      cbi->object, cbi->method, vl_java);
+
+  (*jvm_env)->DeleteLocalRef (jvm_env, vl_java);
+
+  status = cjni_thread_detach ();
+  if (status != 0)
+  {
+    ERROR ("java plugin: cjni_write: cjni_thread_detach failed.");
+    return (-1);
+  }
+
+  return (ret_status);
+} /* }}} int cjni_write */
+
+/* Call the CB_TYPE_FLUSH callback pointed to by the `user_data_t' pointer. */
+static int cjni_flush (cdtime_t timeout, const char *identifier, /* {{{ */
+    user_data_t *ud)
+{
+  JNIEnv *jvm_env;
+  cjni_callback_info_t *cbi;
+  jobject o_timeout;
+  jobject o_identifier;
+  int status;
+  int ret_status;
+
+  if (jvm == NULL)
+  {
+    ERROR ("java plugin: cjni_flush: jvm == NULL");
+    return (-1);
+  }
+
+  if ((ud == NULL) || (ud->data == NULL))
+  {
+    ERROR ("java plugin: cjni_flush: Invalid user data.");
+    return (-1);
+  }
+
+  jvm_env = cjni_thread_attach ();
+  if (jvm_env == NULL)
+    return (-1);
+
+  cbi = (cjni_callback_info_t *) ud->data;
+
+  o_timeout = ctoj_jdouble_to_number (jvm_env,
+      (jdouble) CDTIME_T_TO_DOUBLE (timeout));
+  if (o_timeout == NULL)
+  {
+    ERROR ("java plugin: cjni_flush: Converting double "
+        "to Number object failed.");
+    return (-1);
+  }
+
+  o_identifier = NULL;
+  if (identifier != NULL)
+  {
+    o_identifier = (*jvm_env)->NewStringUTF (jvm_env, identifier);
+    if (o_identifier == NULL)
+    {
+      (*jvm_env)->DeleteLocalRef (jvm_env, o_timeout);
+      ERROR ("java plugin: cjni_flush: NewStringUTF failed.");
+      return (-1);
+    }
+  }
+
+  ret_status = (*jvm_env)->CallIntMethod (jvm_env,
+      cbi->object, cbi->method, o_timeout, o_identifier);
+
+  (*jvm_env)->DeleteLocalRef (jvm_env, o_identifier);
+  (*jvm_env)->DeleteLocalRef (jvm_env, o_timeout);
+
+  status = cjni_thread_detach ();
+  if (status != 0)
+  {
+    ERROR ("java plugin: cjni_flush: cjni_thread_detach failed.");
+    return (-1);
+  }
+
+  return (ret_status);
+} /* }}} int cjni_flush */
+
+/* Call the CB_TYPE_LOG callback pointed to by the `user_data_t' pointer. */
+static void cjni_log (int severity, const char *message, /* {{{ */
+    user_data_t *ud)
+{
+  JNIEnv *jvm_env;
+  cjni_callback_info_t *cbi;
+  jobject o_message;
+
+  if (jvm == NULL)
+    return;
+
+  if ((ud == NULL) || (ud->data == NULL))
+    return;
+
+  jvm_env = cjni_thread_attach ();
+  if (jvm_env == NULL)
+    return;
+
+  cbi = (cjni_callback_info_t *) ud->data;
+
+  o_message = (*jvm_env)->NewStringUTF (jvm_env, message);
+  if (o_message == NULL)
+    return;
+
+  (*jvm_env)->CallVoidMethod (jvm_env,
+      cbi->object, cbi->method, (jint) severity, o_message);
+
+  (*jvm_env)->DeleteLocalRef (jvm_env, o_message);
+
+  cjni_thread_detach ();
+} /* }}} void cjni_log */
+
+/* Call the CB_TYPE_NOTIFICATION callback pointed to by the `user_data_t'
+ * pointer. */
+static int cjni_notification (const notification_t *n, /* {{{ */
+    user_data_t *ud)
+{
+  JNIEnv *jvm_env;
+  cjni_callback_info_t *cbi;
+  jobject o_notification;
+  int status;
+  int ret_status;
+
+  if (jvm == NULL)
+  {
+    ERROR ("java plugin: cjni_read: jvm == NULL");
+    return (-1);
+  }
+
+  if ((ud == NULL) || (ud->data == NULL))
+  {
+    ERROR ("java plugin: cjni_read: Invalid user data.");
+    return (-1);
+  }
+
+  jvm_env = cjni_thread_attach ();
+  if (jvm_env == NULL)
+    return (-1);
+
+  cbi = (cjni_callback_info_t *) ud->data;
+
+  o_notification = ctoj_notification (jvm_env, n);
+  if (o_notification == NULL)
+  {
+    ERROR ("java plugin: cjni_notification: ctoj_notification failed.");
+    return (-1);
+  }
+
+  ret_status = (*jvm_env)->CallIntMethod (jvm_env,
+      cbi->object, cbi->method, o_notification);
+
+  (*jvm_env)->DeleteLocalRef (jvm_env, o_notification);
+
+  status = cjni_thread_detach ();
+  if (status != 0)
+  {
+    ERROR ("java plugin: cjni_read: cjni_thread_detach failed.");
+    return (-1);
+  }
+
+  return (ret_status);
+} /* }}} int cjni_notification */
+
+/* Callbacks for matches implemented in Java */
+static int cjni_match_target_create (const oconfig_item_t *ci, /* {{{ */
+    void **user_data)
+{
+  JNIEnv *jvm_env;
+  cjni_callback_info_t *cbi_ret;
+  cjni_callback_info_t *cbi_factory;
+  const char *name;
+  jobject o_ci;
+  jobject o_tmp;
+  int type;
+  size_t i;
+
+  cbi_ret = NULL;
+  o_ci = NULL;
+  jvm_env = NULL;
+
+#define BAIL_OUT(status) \
+  if (cbi_ret != NULL) { \
+    free (cbi_ret->name); \
+    if ((jvm_env != NULL) && (cbi_ret->object != NULL)) \
+      (*jvm_env)->DeleteLocalRef (jvm_env, cbi_ret->object); \
+  } \
+  free (cbi_ret); \
+  if (jvm_env != NULL) { \
+    if (o_ci != NULL) \
+      (*jvm_env)->DeleteLocalRef (jvm_env, o_ci); \
+    cjni_thread_detach (); \
+  } \
+  return (status)
+
+  if (jvm == NULL)
+  {
+    ERROR ("java plugin: cjni_read: jvm == NULL");
+    BAIL_OUT (-1);
+  }
+
+  jvm_env = cjni_thread_attach ();
+  if (jvm_env == NULL)
+  {
+    BAIL_OUT (-1);
+  }
+
+  /* Find out whether to create a match or a target. */
+  if (strcasecmp ("Match", ci->key) == 0)
+    type = CB_TYPE_MATCH;
+  else if (strcasecmp ("Target", ci->key) == 0)
+    type = CB_TYPE_TARGET;
+  else
+  {
+    ERROR ("java plugin: cjni_match_target_create: Can't figure out whether "
+        "to create a match or a target.");
+    BAIL_OUT (-1);
+  }
+
+  /* This is the name of the match we should create. */
+  name = ci->values[0].value.string;
+
+  /* Lets see if we have a matching factory here.. */
+  cbi_factory = NULL;
+  for (i = 0; i < java_callbacks_num; i++)
+  {
+    if (java_callbacks[i].type != type)
+      continue;
+
+    if (strcmp (name, java_callbacks[i].name) != 0)
+      continue;
+
+    cbi_factory = java_callbacks + i;
+    break;
+  }
+
+  /* Nope, no factory for that name.. */
+  if (cbi_factory == NULL)
+  {
+    ERROR ("java plugin: cjni_match_target_create: "
+        "No such match factory registered: %s",
+        name);
+    BAIL_OUT (-1);
+  }
+
+  /* We convert `ci' to its Java equivalent.. */
+  o_ci = ctoj_oconfig_item (jvm_env, ci);
+  if (o_ci == NULL)
+  {
+    ERROR ("java plugin: cjni_match_target_create: "
+        "ctoj_oconfig_item failed.");
+    BAIL_OUT (-1);
+  }
+
+  /* Allocate a new callback info structure. This is going to be our user_data
+   * pointer. */
+  cbi_ret = (cjni_callback_info_t *) malloc (sizeof (*cbi_ret));
+  if (cbi_ret == NULL)
+  {
+    ERROR ("java plugin: cjni_match_target_create: malloc failed.");
+    BAIL_OUT (-1);
+  }
+  memset (cbi_ret, 0, sizeof (*cbi_ret));
+  cbi_ret->object = NULL;
+  cbi_ret->type = type;
+
+  /* Lets fill the callback info structure.. First, the name: */
+  cbi_ret->name = strdup (name);
+  if (cbi_ret->name == NULL)
+  {
+    ERROR ("java plugin: cjni_match_target_create: strdup failed.");
+    BAIL_OUT (-1);
+  }
+
+  /* Then call the factory method so it creates a new object for us. */
+  o_tmp = (*jvm_env)->CallObjectMethod (jvm_env,
+      cbi_factory->object, cbi_factory->method, o_ci);
+  if (o_tmp == NULL)
+  {
+    ERROR ("java plugin: cjni_match_target_create: CallObjectMethod failed.");
+    BAIL_OUT (-1);
+  }
+
+  cbi_ret->object = (*jvm_env)->NewGlobalRef (jvm_env, o_tmp);
+  if (o_tmp == NULL)
+  {
+    ERROR ("java plugin: cjni_match_target_create: NewGlobalRef failed.");
+    BAIL_OUT (-1);
+  }
+
+  /* This is the class of the match. It is possibly different from the class of
+   * the match-factory! */
+  cbi_ret->class = (*jvm_env)->GetObjectClass (jvm_env, cbi_ret->object);
+  if (cbi_ret->class == NULL)
+  {
+    ERROR ("java plugin: cjni_match_target_create: GetObjectClass failed.");
+    BAIL_OUT (-1);
+  }
+
+  /* Lookup the `int match (DataSet, ValueList)' method. */
+  cbi_ret->method = (*jvm_env)->GetMethodID (jvm_env, cbi_ret->class,
+      /* method name = */ (type == CB_TYPE_MATCH) ? "match" : "invoke",
+      "(Lorg/collectd/api/DataSet;Lorg/collectd/api/ValueList;)I");
+  if (cbi_ret->method == NULL)
+  {
+    ERROR ("java plugin: cjni_match_target_create: GetMethodID failed.");
+    BAIL_OUT (-1);
+  }
+
+  /* Return the newly created match via the user_data pointer. */
+  *user_data = (void *) cbi_ret;
+
+  cjni_thread_detach ();
+
+  DEBUG ("java plugin: cjni_match_target_create: "
+      "Successfully created a `%s' %s.",
+      cbi_ret->name, (type == CB_TYPE_MATCH) ? "match" : "target");
+
+  /* Success! */
+  return (0);
+#undef BAIL_OUT
+} /* }}} int cjni_match_target_create */
+
+static int cjni_match_target_destroy (void **user_data) /* {{{ */
+{
+  cjni_callback_info_destroy (*user_data);
+  *user_data = NULL;
+
+  return (0);
+} /* }}} int cjni_match_target_destroy */
+
+static int cjni_match_target_invoke (const data_set_t *ds, /* {{{ */
+    value_list_t *vl, notification_meta_t **meta, void **user_data)
+{
+  JNIEnv *jvm_env;
+  cjni_callback_info_t *cbi;
+  jobject o_vl;
+  jobject o_ds;
+  int ret_status;
+  int status;
+
+  if (jvm == NULL)
+  {
+    ERROR ("java plugin: cjni_match_target_invoke: jvm == NULL");
+    return (-1);
+  }
+
+  jvm_env = cjni_thread_attach ();
+  if (jvm_env == NULL)
+    return (-1);
+
+  cbi = (cjni_callback_info_t *) *user_data;
+
+  o_vl = ctoj_value_list (jvm_env, ds, vl);
+  if (o_vl == NULL)
+  {
+    ERROR ("java plugin: cjni_match_target_invoke: ctoj_value_list failed.");
+    cjni_thread_detach ();
+    return (-1);
+  }
+
+  o_ds = ctoj_data_set (jvm_env, ds);
+  if (o_ds == NULL)
+  {
+    ERROR ("java plugin: cjni_match_target_invoke: ctoj_value_list failed.");
+    cjni_thread_detach ();
+    return (-1);
+  }
+
+  ret_status = (*jvm_env)->CallIntMethod (jvm_env, cbi->object, cbi->method,
+      o_ds, o_vl);
+
+  DEBUG ("java plugin: cjni_match_target_invoke: Method returned %i.", ret_status);
+
+  /* If we're executing a target, copy the `ValueList' back to our
+   * `value_list_t'. */
+  if (cbi->type == CB_TYPE_TARGET)
+  {
+    value_list_t new_vl;
+
+    memset (&new_vl, 0, sizeof (new_vl));
+    status = jtoc_value_list (jvm_env, &new_vl, o_vl);
+    if (status != 0)
+    {
+      ERROR ("java plugin: cjni_match_target_invoke: "
+          "jtoc_value_list failed.");
+    }
+    else /* if (status == 0) */
+    {
+      /* plugin_dispatch_values assures that this is dynamically allocated
+       * memory. */
+      sfree (vl->values);
+
+      /* This will replace the vl->values pointer to a new, dynamically
+       * allocated piece of memory. */
+      memcpy (vl, &new_vl, sizeof (*vl));
+    }
+  } /* if (cbi->type == CB_TYPE_TARGET) */
+
+  status = cjni_thread_detach ();
+  if (status != 0)
+    ERROR ("java plugin: cjni_read: cjni_thread_detach failed.");
+
+  return (ret_status);
+} /* }}} int cjni_match_target_invoke */
+
+/* Iterate over `java_callbacks' and call all CB_TYPE_INIT callbacks. */
+static int cjni_init_plugins (JNIEnv *jvm_env) /* {{{ */
+{
+  int status;
+  size_t i;
+
+  for (i = 0; i < java_callbacks_num; i++)
+  {
+    if (java_callbacks[i].type != CB_TYPE_INIT)
+      continue;
+
+    DEBUG ("java plugin: Initializing %s", java_callbacks[i].name);
+
+    status = (*jvm_env)->CallIntMethod (jvm_env,
+        java_callbacks[i].object, java_callbacks[i].method);
+    if (status != 0)
+    {
+      ERROR ("java plugin: Initializing `%s' failed with status %i. "
+          "Removing read function.",
+          java_callbacks[i].name, status);
+      plugin_unregister_read (java_callbacks[i].name);
+    }
+  }
+
+  return (0);
+} /* }}} int cjni_init_plugins */
+
+/* Iterate over `java_callbacks' and call all CB_TYPE_SHUTDOWN callbacks. */
+static int cjni_shutdown_plugins (JNIEnv *jvm_env) /* {{{ */
+{
+  int status;
+  size_t i;
+
+  for (i = 0; i < java_callbacks_num; i++)
+  {
+    if (java_callbacks[i].type != CB_TYPE_SHUTDOWN)
+      continue;
+
+    DEBUG ("java plugin: Shutting down %s", java_callbacks[i].name);
+
+    status = (*jvm_env)->CallIntMethod (jvm_env,
+        java_callbacks[i].object, java_callbacks[i].method);
+    if (status != 0)
+    {
+      ERROR ("java plugin: Shutting down `%s' failed with status %i. ",
+          java_callbacks[i].name, status);
+    }
+  }
+
+  return (0);
+} /* }}} int cjni_shutdown_plugins */
+
+
+static int cjni_shutdown (void) /* {{{ */
+{
+  JNIEnv *jvm_env;
+  JavaVMAttachArgs args;
+  int status;
+  size_t i;
+
+  if (jvm == NULL)
+    return (0);
+
+  jvm_env = NULL;
+  memset (&args, 0, sizeof (args));
+  args.version = JNI_VERSION_1_2;
+
+  status = (*jvm)->AttachCurrentThread (jvm, (void *) &jvm_env, &args);
+  if (status != 0)
+  {
+    ERROR ("java plugin: cjni_shutdown: AttachCurrentThread failed with status %i.",
+        status);
+    return (-1);
+  }
+
+  /* Execute all the shutdown functions registered by plugins. */
+  cjni_shutdown_plugins (jvm_env);
+
+  /* Release all the global references to callback functions */
+  for (i = 0; i < java_callbacks_num; i++)
+  {
+    if (java_callbacks[i].object != NULL)
+    {
+      (*jvm_env)->DeleteGlobalRef (jvm_env, java_callbacks[i].object);
+      java_callbacks[i].object = NULL;
+    }
+    sfree (java_callbacks[i].name);
+  }
+  java_callbacks_num = 0;
+  sfree (java_callbacks);
+
+  /* Release all the global references to directly loaded classes. */
+  for (i = 0; i < java_classes_list_len; i++)
+  {
+    if (java_classes_list[i].object != NULL)
+    {
+      (*jvm_env)->DeleteGlobalRef (jvm_env, java_classes_list[i].object);
+      java_classes_list[i].object = NULL;
+    }
+    sfree (java_classes_list[i].name);
+  }
+  java_classes_list_len = 0;
+  sfree (java_classes_list);
+
+  /* Destroy the JVM */
+  DEBUG ("java plugin: Destroying the JVM.");
+  (*jvm)->DestroyJavaVM (jvm);
+  jvm = NULL;
+  jvm_env = NULL;
+
+  pthread_key_delete (jvm_env_key);
+
+  /* Free the JVM argument list */
+  for (i = 0; i < jvm_argc; i++)
+    sfree (jvm_argv[i]);
+  jvm_argc = 0;
+  sfree (jvm_argv);
+
+  return (0);
+} /* }}} int cjni_shutdown */
+
+/* Initialization: Create a JVM, load all configured classes and call their
+ * `config' and `init' callback methods. */
+static int cjni_init (void) /* {{{ */
+{
+  JNIEnv *jvm_env;
+
+  if ((config_block == NULL) && (jvm == NULL))
+  {
+    ERROR ("java plugin: cjni_init: No configuration block for "
+        "the java plugin was found.");
+    return (-1);
+  }
+
+  if (config_block != NULL)
+  {
+
+    cjni_config_perform (config_block);
+    oconfig_free (config_block);
+    config_block = NULL;
+  }
+
+  if (jvm == NULL)
+  {
+    ERROR ("java plugin: cjni_init: jvm == NULL");
+    return (-1);
+  }
+
+  jvm_env = cjni_thread_attach ();
+  if (jvm_env == NULL)
+    return (-1);
+
+  cjni_init_plugins (jvm_env);
+
+  cjni_thread_detach ();
+  return (0);
+} /* }}} int cjni_init */
+
+void module_register (void)
+{
+  plugin_register_complex_config ("java", cjni_config_callback);
+  plugin_register_init ("java", cjni_init);
+  plugin_register_shutdown ("java", cjni_shutdown);
+} /* void module_register (void) */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/libcollectdclient/Makefile.am b/src/libcollectdclient/Makefile.am
new file mode 100644 (file)
index 0000000..6044145
--- /dev/null
@@ -0,0 +1,14 @@
+AUTOMAKE_OPTIONS = foreign no-dependencies
+
+if COMPILER_IS_GCC
+AM_CFLAGS = -Wall -Werror
+endif
+
+pkginclude_HEADERS = client.h lcc_features.h
+lib_LTLIBRARIES = libcollectdclient.la
+nodist_pkgconfig_DATA = libcollectdclient.pc
+
+BUILT_SOURCES = lcc_features.h
+
+libcollectdclient_la_SOURCES = client.c
+libcollectdclient_la_LDFLAGS = -version-info 0:0:0
diff --git a/src/libcollectdclient/client.c b/src/libcollectdclient/client.c
new file mode 100644 (file)
index 0000000..d13decd
--- /dev/null
@@ -0,0 +1,1119 @@
+/**
+ * libcollectdclient - src/libcollectdclient/client.c
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#if HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#if !defined(__GNUC__) || !__GNUC__
+# define __attribute__(x) /**/
+#endif
+
+#include "lcc_features.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+#include <math.h>
+#include <netdb.h>
+
+#include "client.h"
+
+/* NI_MAXHOST has been obsoleted by RFC 3493 which is a reason for SunOS 5.11
+ * to no longer define it. We'll use the old, RFC 2553 value here. */
+#ifndef NI_MAXHOST
+# define NI_MAXHOST 1025
+#endif
+
+/* OpenBSD doesn't have EPROTO, FreeBSD doesn't have EILSEQ. Oh what joy! */
+#ifndef EILSEQ
+# ifdef EPROTO
+#  define EILSEQ EPROTO
+# else
+#  define EILSEQ EINVAL
+# endif
+#endif
+
+/* Secure/static macros. They work like `strcpy' and `strcat', but assure null
+ * termination. They work for static buffers only, because they use `sizeof'.
+ * The `SSTRCATF' combines the functionality of `snprintf' and `strcat' which
+ * is very useful to add formatted stuff to the end of a buffer. */
+#define SSTRCPY(d,s) do { \
+    strncpy ((d), (s), sizeof (d)); \
+    (d)[sizeof (d) - 1] = 0; \
+  } while (0)
+
+#define SSTRCAT(d,s) do { \
+    size_t _l = strlen (d); \
+    strncpy ((d) + _l, (s), sizeof (d) - _l); \
+    (d)[sizeof (d) - 1] = 0; \
+  } while (0)
+
+#define SSTRCATF(d, ...) do { \
+    char _b[sizeof (d)]; \
+    snprintf (_b, sizeof (_b), __VA_ARGS__); \
+    _b[sizeof (_b) - 1] = 0; \
+    SSTRCAT ((d), _b); \
+  } while (0)
+    
+
+#define LCC_SET_ERRSTR(c, ...) do { \
+  snprintf ((c)->errbuf, sizeof ((c)->errbuf), __VA_ARGS__); \
+  (c)->errbuf[sizeof ((c)->errbuf) - 1] = 0; \
+} while (0)
+
+#if COLLECT_DEBUG
+# define LCC_DEBUG(...) printf (__VA_ARGS__)
+#else
+# define LCC_DEBUG(...) /**/
+#endif
+
+/*
+ * Types
+ */
+struct lcc_connection_s
+{
+  FILE *fh;
+  char errbuf[1024];
+};
+
+struct lcc_response_s
+{
+  int status;
+  char message[1024];
+  char **lines;
+  size_t lines_num;
+};
+typedef struct lcc_response_s lcc_response_t;
+
+/*
+ * Private functions
+ */
+/* Even though Posix requires "strerror_r" to return an "int",
+ * some systems (e.g. the GNU libc) return a "char *" _and_
+ * ignore the second argument ... -tokkee */
+static char *sstrerror (int errnum, char *buf, size_t buflen)
+{
+  buf[0] = 0;
+
+#if !HAVE_STRERROR_R
+  snprintf (buf, buflen, "Error #%i; strerror_r is not available.", errnum);
+/* #endif !HAVE_STRERROR_R */
+
+#elif STRERROR_R_CHAR_P
+  {
+    char *temp;
+    temp = strerror_r (errnum, buf, buflen);
+    if (buf[0] == 0)
+    {
+      if ((temp != NULL) && (temp != buf) && (temp[0] != 0))
+        strncpy (buf, temp, buflen);
+      else
+        strncpy (buf, "strerror_r did not return "
+            "an error message", buflen);
+    }
+  }
+/* #endif STRERROR_R_CHAR_P */
+
+#else
+  if (strerror_r (errnum, buf, buflen) != 0)
+  {
+    snprintf (buf, buflen, "Error #%i; "
+        "Additionally, strerror_r failed.",
+        errnum);
+  }
+#endif /* STRERROR_R_CHAR_P */
+
+  buf[buflen - 1] = 0;
+
+  return (buf);
+} /* char *sstrerror */
+
+static int lcc_set_errno (lcc_connection_t *c, int err) /* {{{ */
+{
+  if (c == NULL)
+    return (-1);
+
+  sstrerror (err, c->errbuf, sizeof (c->errbuf));
+  c->errbuf[sizeof (c->errbuf) - 1] = 0;
+
+  return (0);
+} /* }}} int lcc_set_errno */
+
+static char *lcc_strescape (char *dest, const char *src, size_t dest_size) /* {{{ */
+{
+  size_t dest_pos;
+  size_t src_pos;
+
+  if ((dest == NULL) || (src == NULL))
+    return (NULL);
+
+  dest_pos = 0;
+  src_pos = 0;
+
+  assert (dest_size >= 3);
+
+  dest[dest_pos] = '"';
+  dest_pos++;
+
+  while (42)
+  {
+    if ((dest_pos == (dest_size - 2))
+        || (src[src_pos] == 0))
+      break;
+
+    if ((src[src_pos] == '"') || (src[src_pos] == '\\'))
+    {
+      /* Check if there is enough space for both characters.. */
+      if (dest_pos == (dest_size - 3))
+        break;
+
+      dest[dest_pos] = '\\';
+      dest_pos++;
+    }
+
+    dest[dest_pos] = src[src_pos];
+    dest_pos++;
+    src_pos++;
+  }
+
+  assert (dest_pos <= (dest_size - 2));
+
+  dest[dest_pos] = '"';
+  dest_pos++;
+
+  dest[dest_pos] = 0;
+  dest_pos++;
+  src_pos++;
+
+  return (dest);
+} /* }}} char *lcc_strescape */
+
+/* lcc_chomp: Removes all control-characters at the end of a string. */
+static void lcc_chomp (char *str) /* {{{ */
+{
+  size_t str_len;
+
+  str_len = strlen (str);
+  while (str_len > 0)
+  {
+    if (str[str_len - 1] >= 32)
+      break;
+    str[str_len - 1] = 0;
+    str_len--;
+  }
+} /* }}} void lcc_chomp */
+
+static int lcc_identifier_cmp (const void *a, const void *b)
+{
+  const lcc_identifier_t *ident_a, *ident_b;
+
+  int status;
+
+  ident_a = a;
+  ident_b = b;
+
+  status = strcasecmp (ident_a->host, ident_b->host);
+  if (status != 0)
+    return (status);
+
+  status = strcmp (ident_a->plugin, ident_b->plugin);
+  if (status != 0)
+    return (status);
+
+  if ((*ident_a->plugin_instance != '\0') || (*ident_b->plugin_instance != '\0'))
+  {
+    if (*ident_a->plugin_instance == '\0')
+      return (-1);
+    else if (*ident_b->plugin_instance == '\0')
+      return (1);
+
+    status = strcmp (ident_a->plugin_instance, ident_b->plugin_instance);
+    if (status != 0)
+      return (status);
+  }
+
+  status = strcmp (ident_a->type, ident_b->type);
+  if (status != 0)
+    return (status);
+
+  if ((*ident_a->type_instance != '\0') || (*ident_b->type_instance != '\0'))
+  {
+    if (*ident_a->type_instance == '\0')
+      return (-1);
+    else if (*ident_b->type_instance == '\0')
+      return (1);
+
+    status = strcmp (ident_a->type_instance, ident_b->type_instance);
+    if (status != 0)
+      return (status);
+  }
+  return (0);
+} /* }}} int lcc_identifier_cmp */
+
+static void lcc_response_free (lcc_response_t *res) /* {{{ */
+{
+  size_t i;
+
+  if (res == NULL)
+    return;
+
+  for (i = 0; i < res->lines_num; i++)
+    free (res->lines[i]);
+  free (res->lines);
+  res->lines = NULL;
+} /* }}} void lcc_response_free */
+
+static int lcc_send (lcc_connection_t *c, const char *command) /* {{{ */
+{
+  int status;
+
+  LCC_DEBUG ("send:    --> %s\n", command);
+
+  status = fprintf (c->fh, "%s\r\n", command);
+  if (status < 0)
+  {
+    lcc_set_errno (c, errno);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int lcc_send */
+
+static int lcc_receive (lcc_connection_t *c, /* {{{ */
+    lcc_response_t *ret_res)
+{
+  lcc_response_t res;
+  char *ptr;
+  char buffer[4096];
+  size_t i;
+
+  memset (&res, 0, sizeof (res));
+
+  /* Read the first line, containing the status and a message */
+  ptr = fgets (buffer, sizeof (buffer), c->fh);
+  if (ptr == NULL)
+  {
+    lcc_set_errno (c, errno);
+    return (-1);
+  }
+  lcc_chomp (buffer);
+  LCC_DEBUG ("receive: <-- %s\n", buffer);
+
+  /* Convert the leading status to an integer and make `ptr' to point to the
+   * beginning of the message. */
+  ptr = NULL;
+  errno = 0;
+  res.status = strtol (buffer, &ptr, 0);
+  if ((errno != 0) || (ptr == &buffer[0]))
+  {
+    lcc_set_errno (c, errno);
+    return (-1);
+  }
+
+  /* Skip white spaces after the status number */
+  while ((*ptr == ' ') || (*ptr == '\t'))
+    ptr++;
+
+  /* Now copy the message. */
+  strncpy (res.message, ptr, sizeof (res.message));
+  res.message[sizeof (res.message) - 1] = 0;
+
+  /* Error or no lines follow: We're done. */
+  if (res.status <= 0)
+  {
+    memcpy (ret_res, &res, sizeof (res));
+    return (0);
+  }
+
+  /* Allocate space for the char-pointers */
+  res.lines_num = (size_t) res.status;
+  res.status = 0;
+  res.lines = (char **) malloc (res.lines_num * sizeof (char *));
+  if (res.lines == NULL)
+  {
+    lcc_set_errno (c, ENOMEM);
+    return (-1);
+  }
+
+  /* Now receive all the lines */
+  for (i = 0; i < res.lines_num; i++)
+  {
+    ptr = fgets (buffer, sizeof (buffer), c->fh);
+    if (ptr == NULL)
+    {
+      lcc_set_errno (c, errno);
+      break;
+    }
+    lcc_chomp (buffer);
+    LCC_DEBUG ("receive: <-- %s\n", buffer);
+
+    res.lines[i] = strdup (buffer);
+    if (res.lines[i] == NULL)
+    {
+      lcc_set_errno (c, ENOMEM);
+      break;
+    }
+  }
+
+  /* Check if the for-loop exited with an error. */
+  if (i < res.lines_num)
+  {
+    while (i > 0)
+    {
+      i--;
+      free (res.lines[i]);
+    }
+    free (res.lines);
+    return (-1);
+  }
+
+  memcpy (ret_res, &res, sizeof (res));
+  return (0);
+} /* }}} int lcc_receive */
+
+static int lcc_sendreceive (lcc_connection_t *c, /* {{{ */
+    const char *command, lcc_response_t *ret_res)
+{
+  lcc_response_t res;
+  int status;
+
+  if (c->fh == NULL)
+  {
+    lcc_set_errno (c, EBADF);
+    return (-1);
+  }
+
+  status = lcc_send (c, command);
+  if (status != 0)
+    return (status);
+
+  memset (&res, 0, sizeof (res));
+  status = lcc_receive (c, &res);
+  if (status == 0)
+    memcpy (ret_res, &res, sizeof (*ret_res));
+
+  return (status);
+} /* }}} int lcc_sendreceive */
+
+static int lcc_open_unixsocket (lcc_connection_t *c, const char *path) /* {{{ */
+{
+  struct sockaddr_un sa;
+  int fd;
+  int status;
+
+  assert (c != NULL);
+  assert (c->fh == NULL);
+  assert (path != NULL);
+
+  /* Don't use PF_UNIX here, because it's broken on Mac OS X (10.4, possibly
+   * others). */
+  fd = socket (AF_UNIX, SOCK_STREAM, /* protocol = */ 0);
+  if (fd < 0)
+  {
+    lcc_set_errno (c, errno);
+    return (-1);
+  }
+
+  memset (&sa, 0, sizeof (sa));
+  sa.sun_family = AF_UNIX;
+  strncpy (sa.sun_path, path, sizeof (sa.sun_path) - 1);
+
+  status = connect (fd, (struct sockaddr *) &sa, sizeof (sa));
+  if (status != 0)
+  {
+    lcc_set_errno (c, errno);
+    close (fd);
+    return (-1);
+  }
+
+  c->fh = fdopen (fd, "r+");
+  if (c->fh == NULL)
+  {
+    lcc_set_errno (c, errno);
+    close (fd);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int lcc_open_unixsocket */
+
+static int lcc_open_netsocket (lcc_connection_t *c, /* {{{ */
+    const char *addr_orig)
+{
+  struct addrinfo ai_hints;
+  struct addrinfo *ai_res;
+  struct addrinfo *ai_ptr;
+  char addr_copy[NI_MAXHOST];
+  char *addr;
+  char *port;
+  int fd;
+  int status;
+
+  assert (c != NULL);
+  assert (c->fh == NULL);
+  assert (addr_orig != NULL);
+
+  strncpy(addr_copy, addr_orig, sizeof(addr_copy));
+  addr_copy[sizeof(addr_copy) - 1] = '\0';
+  addr = addr_copy;
+
+  memset (&ai_hints, 0, sizeof (ai_hints));
+  ai_hints.ai_flags = 0;
+#ifdef AI_ADDRCONFIG
+  ai_hints.ai_flags |= AI_ADDRCONFIG;
+#endif
+  ai_hints.ai_family = AF_UNSPEC;
+  ai_hints.ai_socktype = SOCK_STREAM;
+
+  port = NULL;
+  if (*addr == '[') /* IPv6+port format */
+  {
+    /* `addr' is something like "[2001:780:104:2:211:24ff:feab:26f8]:12345" */
+    addr++;
+
+    port = strchr (addr, ']');
+    if (port == NULL)
+    {
+      LCC_SET_ERRSTR (c, "malformed address: %s", addr_orig);
+      return (-1);
+    }
+    *port = 0;
+    port++;
+
+    if (*port == ':')
+      port++;
+    else if (*port == 0)
+      port = NULL;
+    else
+    {
+      LCC_SET_ERRSTR (c, "garbage after address: %s", port);
+      return (-1);
+    }
+  } /* if (*addr = ']') */
+  else if (strchr (addr, '.') != NULL) /* Hostname or IPv4 */
+  {
+    port = strrchr (addr, ':');
+    if (port != NULL)
+    {
+      *port = 0;
+      port++;
+    }
+  }
+
+  ai_res = NULL;
+  status = getaddrinfo (addr,
+                        port == NULL ? LCC_DEFAULT_PORT : port,
+                        &ai_hints, &ai_res);
+  if (status != 0)
+  {
+    LCC_SET_ERRSTR (c, "getaddrinfo: %s", gai_strerror (status));
+    return (-1);
+  }
+
+  for (ai_ptr = ai_res; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
+  {
+    fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol);
+    if (fd < 0)
+    {
+      status = errno;
+      fd = -1;
+      continue;
+    }
+
+    status = connect (fd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
+    if (status != 0)
+    {
+      status = errno;
+      close (fd);
+      fd = -1;
+      continue;
+    }
+
+    c->fh = fdopen (fd, "r+");
+    if (c->fh == NULL)
+    {
+      status = errno;
+      close (fd);
+      fd = -1;
+      continue;
+    }
+
+    assert (status == 0);
+    break;
+  } /* for (ai_ptr) */
+
+  if (status != 0)
+  {
+    lcc_set_errno (c, status);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int lcc_open_netsocket */
+
+static int lcc_open_socket (lcc_connection_t *c, const char *addr) /* {{{ */
+{
+  int status = 0;
+
+  if (addr == NULL)
+    return (-1);
+
+  assert (c != NULL);
+  assert (c->fh == NULL);
+  assert (addr != NULL);
+
+  if (strncmp ("unix:", addr, strlen ("unix:")) == 0)
+    status = lcc_open_unixsocket (c, addr + strlen ("unix:"));
+  else if (addr[0] == '/')
+    status = lcc_open_unixsocket (c, addr);
+  else
+    status = lcc_open_netsocket (c, addr);
+
+  return (status);
+} /* }}} int lcc_open_socket */
+
+/*
+ * Public functions
+ */
+unsigned int lcc_version (void) /* {{{ */
+{
+  return (LCC_VERSION);
+} /* }}} unsigned int lcc_version */
+
+const char *lcc_version_string (void) /* {{{ */
+{
+  return (LCC_VERSION_STRING);
+} /* }}} const char *lcc_version_string */
+
+const char *lcc_version_extra (void) /* {{{ */
+{
+  return (LCC_VERSION_EXTRA);
+} /* }}} const char *lcc_version_extra */
+
+int lcc_connect (const char *address, lcc_connection_t **ret_con) /* {{{ */
+{
+  lcc_connection_t *c;
+  int status;
+
+  if (address == NULL)
+    return (-1);
+
+  if (ret_con == NULL)
+    return (-1);
+
+  c = (lcc_connection_t *) malloc (sizeof (*c));
+  if (c == NULL)
+    return (-1);
+  memset (c, 0, sizeof (*c));
+
+  status = lcc_open_socket (c, address);
+  if (status != 0)
+  {
+    lcc_disconnect (c);
+    return (status);
+  }
+
+  *ret_con = c;
+  return (0);
+} /* }}} int lcc_connect */
+
+int lcc_disconnect (lcc_connection_t *c) /* {{{ */
+{
+  if (c == NULL)
+    return (-1);
+
+  if (c->fh != NULL)
+  {
+    fclose (c->fh);
+    c->fh = NULL;
+  }
+
+  free (c);
+  return (0);
+} /* }}} int lcc_disconnect */
+
+int lcc_getval (lcc_connection_t *c, lcc_identifier_t *ident, /* {{{ */
+    size_t *ret_values_num, gauge_t **ret_values, char ***ret_values_names)
+{
+  char ident_str[6 * LCC_NAME_LEN];
+  char ident_esc[12 * LCC_NAME_LEN];
+  char command[14 * LCC_NAME_LEN];
+
+  lcc_response_t res;
+  size_t   values_num;
+  gauge_t *values = NULL;
+  char   **values_names = NULL;
+
+  size_t i;
+  int status;
+
+  if (c == NULL)
+    return (-1);
+
+  if (ident == NULL)
+  {
+    lcc_set_errno (c, EINVAL);
+    return (-1);
+  }
+
+  /* Build a commend with an escaped version of the identifier string. */
+  status = lcc_identifier_to_string (c, ident_str, sizeof (ident_str), ident);
+  if (status != 0)
+    return (status);
+
+  snprintf (command, sizeof (command), "GETVAL %s",
+      lcc_strescape (ident_esc, ident_str, sizeof (ident_esc)));
+  command[sizeof (command) - 1] = 0;
+
+  /* Send talk to the daemon.. */
+  status = lcc_sendreceive (c, command, &res);
+  if (status != 0)
+    return (status);
+
+  if (res.status != 0)
+  {
+    LCC_SET_ERRSTR (c, "Server error: %s", res.message);
+    lcc_response_free (&res);
+    return (-1);
+  }
+
+  values_num = res.lines_num;
+
+#define BAIL_OUT(e) do { \
+  lcc_set_errno (c, (e)); \
+  free (values); \
+  if (values_names != NULL) { \
+    for (i = 0; i < values_num; i++) { \
+      free (values_names[i]); \
+    } \
+  } \
+  free (values_names); \
+  lcc_response_free (&res); \
+  return (-1); \
+} while (0)
+
+  /* If neither the values nor the names are requested, return here.. */
+  if ((ret_values == NULL) && (ret_values_names == NULL))
+  {
+    if (ret_values_num != NULL)
+      *ret_values_num = values_num;
+    lcc_response_free (&res);
+    return (0);
+  }
+
+  /* Allocate space for the values */
+  if (ret_values != NULL)
+  {
+    values = (gauge_t *) malloc (values_num * sizeof (*values));
+    if (values == NULL)
+      BAIL_OUT (ENOMEM);
+  }
+
+  if (ret_values_names != NULL)
+  {
+    values_names = (char **) calloc (values_num, sizeof (*values_names));
+    if (values_names == NULL)
+      BAIL_OUT (ENOMEM);
+  }
+
+  for (i = 0; i < res.lines_num; i++)
+  {
+    char *key;
+    char *value;
+    char *endptr;
+
+    key = res.lines[i];
+    value = strchr (key, '=');
+    if (value == NULL)
+      BAIL_OUT (EILSEQ);
+
+    *value = 0;
+    value++;
+
+    if (values != NULL)
+    {
+      endptr = NULL;
+      errno = 0;
+      values[i] = strtod (value, &endptr);
+
+      if ((endptr == value) || (errno != 0))
+        BAIL_OUT (errno);
+    }
+
+    if (values_names != NULL)
+    {
+      values_names[i] = strdup (key);
+      if (values_names[i] == NULL)
+        BAIL_OUT (ENOMEM);
+    }
+  } /* for (i = 0; i < res.lines_num; i++) */
+
+  if (ret_values_num != NULL)
+    *ret_values_num = values_num;
+  if (ret_values != NULL)
+    *ret_values = values;
+  if (ret_values_names != NULL)
+    *ret_values_names = values_names;
+
+  return (0);
+} /* }}} int lcc_getval */
+
+int lcc_putval (lcc_connection_t *c, const lcc_value_list_t *vl) /* {{{ */
+{
+  char ident_str[6 * LCC_NAME_LEN];
+  char ident_esc[12 * LCC_NAME_LEN];
+  char command[1024] = "";
+  lcc_response_t res;
+  int status;
+  size_t i;
+
+  if ((c == NULL) || (vl == NULL) || (vl->values_len < 1)
+      || (vl->values == NULL) || (vl->values_types == NULL))
+  {
+    lcc_set_errno (c, EINVAL);
+    return (-1);
+  }
+
+  status = lcc_identifier_to_string (c, ident_str, sizeof (ident_str),
+      &vl->identifier);
+  if (status != 0)
+    return (status);
+
+  SSTRCATF (command, "PUTVAL %s",
+      lcc_strescape (ident_esc, ident_str, sizeof (ident_esc)));
+
+  if (vl->interval > 0)
+    SSTRCATF (command, " interval=%i", vl->interval);
+
+  if (vl->time > 0)
+    SSTRCATF (command, " %u", (unsigned int) vl->time);
+  else
+    SSTRCAT (command, " N");
+
+  for (i = 0; i < vl->values_len; i++)
+  {
+    if (vl->values_types[i] == LCC_TYPE_COUNTER)
+      SSTRCATF (command, ":%"PRIu64, vl->values[i].counter);
+    else if (vl->values_types[i] == LCC_TYPE_GAUGE)
+    {
+      if (isnan (vl->values[i].gauge))
+        SSTRCATF (command, ":U");
+      else
+        SSTRCATF (command, ":%g", vl->values[i].gauge);
+    }
+    else if (vl->values_types[i] == LCC_TYPE_DERIVE)
+       SSTRCATF (command, ":%"PRIu64, vl->values[i].derive);
+    else if (vl->values_types[i] == LCC_TYPE_ABSOLUTE)
+       SSTRCATF (command, ":%"PRIu64, vl->values[i].absolute);
+
+  } /* for (i = 0; i < vl->values_len; i++) */
+
+  status = lcc_sendreceive (c, command, &res);
+  if (status != 0)
+    return (status);
+
+  if (res.status != 0)
+  {
+    LCC_SET_ERRSTR (c, "Server error: %s", res.message);
+    lcc_response_free (&res);
+    return (-1);
+  }
+
+  lcc_response_free (&res);
+  return (0);
+} /* }}} int lcc_putval */
+
+int lcc_flush (lcc_connection_t *c, const char *plugin, /* {{{ */
+    lcc_identifier_t *ident, int timeout)
+{
+  char command[1024] = "";
+  lcc_response_t res;
+  int status;
+
+  if (c == NULL)
+  {
+    lcc_set_errno (c, EINVAL);
+    return (-1);
+  }
+
+  SSTRCPY (command, "FLUSH");
+
+  if (timeout > 0)
+    SSTRCATF (command, " timeout=%i", timeout);
+
+  if (plugin != NULL)
+  {
+    char buffer[2 * LCC_NAME_LEN];
+    SSTRCATF (command, " plugin=%s",
+        lcc_strescape (buffer, plugin, sizeof (buffer)));
+  }
+
+  if (ident != NULL)
+  {
+    char ident_str[6 * LCC_NAME_LEN];
+    char ident_esc[12 * LCC_NAME_LEN];
+
+    status = lcc_identifier_to_string (c, ident_str, sizeof (ident_str), ident);
+    if (status != 0)
+      return (status);
+
+    SSTRCATF (command, " identifier=%s",
+        lcc_strescape (ident_esc, ident_str, sizeof (ident_esc)));
+  }
+
+  status = lcc_sendreceive (c, command, &res);
+  if (status != 0)
+    return (status);
+
+  if (res.status != 0)
+  {
+    LCC_SET_ERRSTR (c, "Server error: %s", res.message);
+    lcc_response_free (&res);
+    return (-1);
+  }
+
+  lcc_response_free (&res);
+  return (0);
+} /* }}} int lcc_flush */
+
+/* TODO: Implement lcc_putnotif */
+
+int lcc_listval (lcc_connection_t *c, /* {{{ */
+    lcc_identifier_t **ret_ident, size_t *ret_ident_num)
+{
+  lcc_response_t res;
+  size_t i;
+  int status;
+
+  lcc_identifier_t *ident;
+  size_t ident_num;
+
+  if (c == NULL)
+    return (-1);
+
+  if ((ret_ident == NULL) || (ret_ident_num == NULL))
+  {
+    lcc_set_errno (c, EINVAL);
+    return (-1);
+  }
+
+  status = lcc_sendreceive (c, "LISTVAL", &res);
+  if (status != 0)
+    return (status);
+
+  if (res.status != 0)
+  {
+    LCC_SET_ERRSTR (c, "Server error: %s", res.message);
+    lcc_response_free (&res);
+    return (-1);
+  }
+
+  ident_num = res.lines_num;
+  ident = (lcc_identifier_t *) malloc (ident_num * sizeof (*ident));
+  if (ident == NULL)
+  {
+    lcc_response_free (&res);
+    lcc_set_errno (c, ENOMEM);
+    return (-1);
+  }
+
+  for (i = 0; i < res.lines_num; i++)
+  {
+    char *time_str;
+    char *ident_str;
+
+    /* First field is the time. */
+    time_str = res.lines[i];
+
+    /* Set `ident_str' to the beginning of the second field. */
+    ident_str = time_str;
+    while ((*ident_str != ' ') && (*ident_str != '\t') && (*ident_str != 0))
+      ident_str++;
+    while ((*ident_str == ' ') || (*ident_str == '\t'))
+    {
+      *ident_str = 0;
+      ident_str++;
+    }
+
+    if (*ident_str == 0)
+    {
+      lcc_set_errno (c, EILSEQ);
+      status = -1;
+      break;
+    }
+
+    status = lcc_string_to_identifier (c, ident + i, ident_str);
+    if (status != 0)
+      break;
+  }
+
+  lcc_response_free (&res);
+
+  if (status != 0)
+  {
+    free (ident);
+    return (-1);
+  }
+
+  *ret_ident = ident;
+  *ret_ident_num = ident_num;
+
+  return (0);
+} /* }}} int lcc_listval */
+
+const char *lcc_strerror (lcc_connection_t *c) /* {{{ */
+{
+  if (c == NULL)
+    return ("Invalid object");
+  return (c->errbuf);
+} /* }}} const char *lcc_strerror */
+
+int lcc_identifier_to_string (lcc_connection_t *c, /* {{{ */
+    char *string, size_t string_size, const lcc_identifier_t *ident)
+{
+  if ((string == NULL) || (string_size < 6) || (ident == NULL))
+  {
+    lcc_set_errno (c, EINVAL);
+    return (-1);
+  }
+
+  if (ident->plugin_instance[0] == 0)
+  {
+    if (ident->type_instance[0] == 0)
+      snprintf (string, string_size, "%s/%s/%s",
+          ident->host,
+          ident->plugin,
+          ident->type);
+    else
+      snprintf (string, string_size, "%s/%s/%s-%s",
+          ident->host,
+          ident->plugin,
+          ident->type,
+          ident->type_instance);
+  }
+  else
+  {
+    if (ident->type_instance[0] == 0)
+      snprintf (string, string_size, "%s/%s-%s/%s",
+          ident->host,
+          ident->plugin,
+          ident->plugin_instance,
+          ident->type);
+    else
+      snprintf (string, string_size, "%s/%s-%s/%s-%s",
+          ident->host,
+          ident->plugin,
+          ident->plugin_instance,
+          ident->type,
+          ident->type_instance);
+  }
+
+  string[string_size - 1] = 0;
+  return (0);
+} /* }}} int lcc_identifier_to_string */
+
+int lcc_string_to_identifier (lcc_connection_t *c, /* {{{ */
+    lcc_identifier_t *ident, const char *string)
+{
+  char *string_copy;
+  char *host;
+  char *plugin;
+  char *plugin_instance;
+  char *type;
+  char *type_instance;
+
+  string_copy = strdup (string);
+  if (string_copy == NULL)
+  {
+    lcc_set_errno (c, ENOMEM);
+    return (-1);
+  }
+
+  host = string_copy;
+  plugin = strchr (host, '/');
+  if (plugin == NULL)
+  {
+    LCC_SET_ERRSTR (c, "Malformed identifier string: %s", string);
+    free (string_copy);
+    return (-1);
+  }
+  *plugin = 0;
+  plugin++;
+
+  type = strchr (plugin, '/');
+  if (type == NULL)
+  {
+    LCC_SET_ERRSTR (c, "Malformed identifier string: %s", string);
+    free (string_copy);
+    return (-1);
+  }
+  *type = 0;
+  type++;
+
+  plugin_instance = strchr (plugin, '-');
+  if (plugin_instance != NULL)
+  {
+    *plugin_instance = 0;
+    plugin_instance++;
+  }
+
+  type_instance = strchr (type, '-');
+  if (type_instance != NULL)
+  {
+    *type_instance = 0;
+    type_instance++;
+  }
+
+  memset (ident, 0, sizeof (*ident));
+
+  SSTRCPY (ident->host, host);
+  SSTRCPY (ident->plugin, plugin);
+  if (plugin_instance != NULL)
+    SSTRCPY (ident->plugin_instance, plugin_instance);
+  SSTRCPY (ident->type, type);
+  if (type_instance != NULL)
+    SSTRCPY (ident->type_instance, type_instance);
+
+  free (string_copy);
+  return (0);
+} /* }}} int lcc_string_to_identifier */
+
+int lcc_sort_identifiers (lcc_connection_t *c, /* {{{ */
+    lcc_identifier_t *idents, size_t idents_num)
+{
+  if (idents == NULL)
+  {
+    lcc_set_errno (c, EINVAL);
+    return (-1);
+  }
+
+  qsort (idents, idents_num, sizeof (*idents), lcc_identifier_cmp);
+  return (0);
+} /* }}} int lcc_sort_identifiers */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/libcollectdclient/client.h b/src/libcollectdclient/client.h
new file mode 100644 (file)
index 0000000..36b1d4d
--- /dev/null
@@ -0,0 +1,125 @@
+/**
+ * libcollectdclient - src/libcollectdclient/client.h
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef LIBCOLLECTD_COLLECTDCLIENT_H
+#define LIBCOLLECTD_COLLECTDCLIENT_H 1
+
+#include "lcc_features.h"
+
+/*
+ * Includes (for data types)
+ */
+#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#include <inttypes.h>
+#include <time.h>
+
+/*
+ * Defines
+ */
+#define LCC_NAME_LEN 64
+#define LCC_DEFAULT_PORT "25826"
+
+/*
+ * Types
+ */
+#define LCC_TYPE_COUNTER 0
+#define LCC_TYPE_GAUGE   1
+#define LCC_TYPE_DERIVE   2
+#define LCC_TYPE_ABSOLUTE   3
+
+LCC_BEGIN_DECLS
+
+typedef uint64_t counter_t;
+typedef double gauge_t;
+typedef uint64_t derive_t;
+typedef uint64_t absolute_t;
+
+union value_u
+{
+  counter_t counter;
+  gauge_t   gauge;
+  derive_t  derive;
+  absolute_t absolute;
+};
+typedef union value_u value_t;
+
+struct lcc_identifier_s
+{
+  char host[LCC_NAME_LEN];
+  char plugin[LCC_NAME_LEN];
+  char plugin_instance[LCC_NAME_LEN];
+  char type[LCC_NAME_LEN];
+  char type_instance[LCC_NAME_LEN];
+};
+typedef struct lcc_identifier_s lcc_identifier_t;
+#define LCC_IDENTIFIER_INIT { "localhost", "", "", "", "" }
+
+struct lcc_value_list_s
+{
+  value_t *values;
+  int     *values_types;
+  size_t   values_len;
+  time_t   time;
+  int      interval;
+  lcc_identifier_t identifier;
+};
+typedef struct lcc_value_list_s lcc_value_list_t;
+#define LCC_VALUE_LIST_INIT { NULL, NULL, 0, 0, 0, LCC_IDENTIFIER_INIT }
+
+struct lcc_connection_s;
+typedef struct lcc_connection_s lcc_connection_t;
+
+/*
+ * Functions
+ */
+int lcc_connect (const char *address, lcc_connection_t **ret_con);
+int lcc_disconnect (lcc_connection_t *c);
+#define LCC_DESTROY(c) do { lcc_disconnect (c); (c) = NULL; } while (0)
+
+int lcc_getval (lcc_connection_t *c, lcc_identifier_t *ident,
+    size_t *ret_values_num, gauge_t **ret_values, char ***ret_values_names);
+
+int lcc_putval (lcc_connection_t *c, const lcc_value_list_t *vl);
+
+int lcc_flush (lcc_connection_t *c, const char *plugin,
+    lcc_identifier_t *ident, int timeout);
+
+int lcc_listval (lcc_connection_t *c,
+    lcc_identifier_t **ret_ident, size_t *ret_ident_num);
+
+/* TODO: putnotif */
+
+const char *lcc_strerror (lcc_connection_t *c);
+
+int lcc_identifier_to_string (lcc_connection_t *c,
+    char *string, size_t string_size, const lcc_identifier_t *ident);
+int lcc_string_to_identifier (lcc_connection_t *c,
+    lcc_identifier_t *ident, const char *string);
+
+int lcc_sort_identifiers (lcc_connection_t *c,
+    lcc_identifier_t *idents, size_t idents_num);
+
+LCC_END_DECLS
+
+/* vim: set sw=2 sts=2 et : */
+#endif /* LIBCOLLECTD_COLLECTDCLIENT_H */
diff --git a/src/libcollectdclient/lcc_features.h.in b/src/libcollectdclient/lcc_features.h.in
new file mode 100644 (file)
index 0000000..3916a17
--- /dev/null
@@ -0,0 +1,62 @@
+/**
+ * libcollectdclient - src/libcollectdclient/lcc_features.h
+ * Copyright (C) 2009  Sebastian Harl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Sebastian tokkee Harl <sh at tokkee.org>
+ **/
+
+#ifndef LIBCOLLECTD_LCC_FEATURES_H
+#define LIBCOLLECTD_LCC_FEATURES_H 1
+
+#ifdef __cplusplus
+# define LCC_BEGIN_DECLS extern "C" {
+# define LCC_END_DECLS   }
+#else
+# define LCC_BEGIN_DECLS
+# define LCC_END_DECLS
+#endif
+
+#define LCC_API_VERSION 0
+
+#define LCC_VERSION_MAJOR @LCC_VERSION_MAJOR@
+#define LCC_VERSION_MINOR @LCC_VERSION_MINOR@
+#define LCC_VERSION_PATCH @LCC_VERSION_PATCH@
+
+#define LCC_VERSION_EXTRA "@LCC_VERSION_EXTRA@"
+
+#define LCC_VERSION_STRING "@LCC_VERSION_STRING@"
+
+#define LCC_VERSION_ENCODE(major, minor, patch) \
+       ((major) * 10000 + (minor) * 100 + (patch))
+
+#define LCC_VERSION \
+       LCC_VERSION_ENCODE(LCC_VERSION_MAJOR, LCC_VERSION_MINOR, LCC_VERSION_PATCH)
+
+LCC_BEGIN_DECLS
+
+unsigned int lcc_version (void);
+
+const char *lcc_version_string (void);
+
+const char *lcc_version_extra (void);
+
+LCC_END_DECLS
+
+#endif /* ! LIBCOLLECTD_LCC_FEATURES_H */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
+
diff --git a/src/libcollectdclient/libcollectdclient.pc.in b/src/libcollectdclient/libcollectdclient.pc.in
new file mode 100644 (file)
index 0000000..faade70
--- /dev/null
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libcollectdclient
+Description: Client library for the unixsock plugin of collectd.
+Version: @LCC_VERSION_STRING@
+URL: http://collectd.org/
+Libs: -L${libdir} -lcollectdclient
+Cflags: -I${includedir}
diff --git a/src/liboconfig/AUTHORS b/src/liboconfig/AUTHORS
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/liboconfig/COPYING b/src/liboconfig/COPYING
new file mode 100644 (file)
index 0000000..623b625
--- /dev/null
@@ -0,0 +1,340 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+     51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year  name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/src/liboconfig/ChangeLog b/src/liboconfig/ChangeLog
new file mode 100644 (file)
index 0000000..390c507
--- /dev/null
@@ -0,0 +1,5 @@
+2007-02-15, Version 0.1.1
+       * src/parser.y: Fixes a memory leak.
+
+2007-02-11, Version 0.1.0
+       * Initial release.
diff --git a/src/liboconfig/Makefile.am b/src/liboconfig/Makefile.am
new file mode 100644 (file)
index 0000000..c3de92c
--- /dev/null
@@ -0,0 +1,10 @@
+AUTOMAKE_OPTIONS = foreign no-dependencies
+
+BUILT_SOURCES = parser.h
+#CLEANFILES = parser.[ch] scanner.c
+AM_YFLAGS = -d
+
+noinst_LTLIBRARIES = liboconfig.la
+
+liboconfig_la_LDFLAGS = -version-info 0:0:0 $(LEXLIB)
+liboconfig_la_SOURCES = oconfig.c oconfig.h aux_types.h scanner.l parser.y
diff --git a/src/liboconfig/aux_types.h b/src/liboconfig/aux_types.h
new file mode 100644 (file)
index 0000000..25b81ab
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef AUX_TYPES_H
+#define AUX_TYPES_H 1
+
+struct statement_list_s
+{
+       oconfig_item_t *statement;
+       int             statement_num;
+};
+typedef struct statement_list_s statement_list_t;
+
+struct argument_list_s
+{
+       oconfig_value_t *argument;
+       int              argument_num;
+};
+typedef struct argument_list_s argument_list_t;
+
+#endif /* AUX_TYPES_H */
diff --git a/src/liboconfig/oconfig.c b/src/liboconfig/oconfig.c
new file mode 100644 (file)
index 0000000..629775a
--- /dev/null
@@ -0,0 +1,217 @@
+/**
+ * oconfig - src/oconfig.c
+ * Copyright (C) 2006,2007  Florian octo Forster <octo at verplant.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+
+#include "oconfig.h"
+
+extern FILE *yyin;
+
+oconfig_item_t *ci_root;
+const char     *c_file;
+
+static void yyset_in  (FILE *fd)
+{
+  yyin = fd;
+} /* void yyset_in */
+
+oconfig_item_t *oconfig_parse_fh (FILE *fh)
+{
+  int status;
+  oconfig_item_t *ret;
+
+  char file[10];
+
+  yyset_in (fh);
+
+  if (NULL == c_file) {
+    int status;
+
+    status = snprintf (file, sizeof (file), "<fd#%d>", fileno (fh));
+
+    if ((status < 0) || (status >= sizeof (file))) {
+      c_file = "<unknown>";
+    }
+    else {
+      file[sizeof (file) - 1] = '\0';
+      c_file = file;
+    }
+  }
+
+  status = yyparse ();
+  if (status != 0)
+  {
+    fprintf (stderr, "yyparse returned error #%i\n", status);
+    return (NULL);
+  }
+
+  c_file = NULL;
+
+  ret = ci_root;
+  ci_root = NULL;
+  yyset_in ((FILE *) 0);
+
+  return (ret);
+} /* oconfig_item_t *oconfig_parse_fh */
+
+oconfig_item_t *oconfig_parse_file (const char *file)
+{
+  FILE *fh;
+  oconfig_item_t *ret;
+
+  c_file = file;
+
+  fh = fopen (file, "r");
+  if (fh == NULL)
+  {
+    fprintf (stderr, "fopen (%s) failed: %s\n", file, strerror (errno));
+    return (NULL);
+  }
+
+  ret = oconfig_parse_fh (fh);
+  fclose (fh);
+
+  c_file = NULL;
+
+  return (ret);
+} /* oconfig_item_t *oconfig_parse_file */
+
+oconfig_item_t *oconfig_clone (const oconfig_item_t *ci_orig)
+{
+  oconfig_item_t *ci_copy;
+
+  ci_copy = (oconfig_item_t *) malloc (sizeof (*ci_copy));
+  if (ci_copy == NULL)
+  {
+    fprintf (stderr, "malloc failed.\n");
+    return (NULL);
+  }
+  memset (ci_copy, 0, sizeof (*ci_copy));
+  ci_copy->values = NULL;
+  ci_copy->parent = NULL;
+  ci_copy->children = NULL;
+
+  ci_copy->key = strdup (ci_orig->key);
+  if (ci_copy->key == NULL)
+  {
+    fprintf (stderr, "strdup failed.\n");
+    free (ci_copy);
+    return (NULL);
+  }
+
+  if (ci_orig->values_num > 0) /* {{{ */
+  {
+    int i;
+
+    ci_copy->values = (oconfig_value_t *) calloc (ci_orig->values_num,
+       sizeof (*ci_copy->values));
+    if (ci_copy->values == NULL)
+    {
+      fprintf (stderr, "calloc failed.\n");
+      free (ci_copy->key);
+      free (ci_copy);
+      return (NULL);
+    }
+    ci_copy->values_num = ci_orig->values_num;
+
+    for (i = 0; i < ci_copy->values_num; i++)
+    {
+       ci_copy->values[i].type = ci_orig->values[i].type;
+       if (ci_copy->values[i].type == OCONFIG_TYPE_STRING)
+       {
+        ci_copy->values[i].value.string
+          = strdup (ci_orig->values[i].value.string);
+        if (ci_copy->values[i].value.string == NULL)
+        {
+          fprintf (stderr, "strdup failed.\n");
+          oconfig_free (ci_copy);
+          return (NULL);
+        }
+       }
+       else /* ci_copy->values[i].type != OCONFIG_TYPE_STRING) */
+       {
+        ci_copy->values[i].value = ci_orig->values[i].value;
+       }
+    }
+  } /* }}} if (ci_orig->values_num > 0) */
+
+  if (ci_orig->children_num > 0) /* {{{ */
+  {
+    int i;
+
+    ci_copy->children = (oconfig_item_t *) calloc (ci_orig->children_num,
+       sizeof (*ci_copy->children));
+    if (ci_copy->children == NULL)
+    {
+      fprintf (stderr, "calloc failed.\n");
+      oconfig_free (ci_copy);
+      return (NULL);
+    }
+    ci_copy->children_num = ci_orig->children_num;
+
+    for (i = 0; i < ci_copy->children_num; i++)
+    {
+      oconfig_item_t *child;
+      
+      child = oconfig_clone (ci_orig->children + i);
+      if (child == NULL)
+      {
+       oconfig_free (ci_copy);
+       return (NULL);
+      }
+      child->parent = ci_copy;
+      ci_copy->children[i] = *child;
+      free (child);
+    } /* for (i = 0; i < ci_copy->children_num; i++) */
+  } /* }}} if (ci_orig->children_num > 0) */
+
+  return (ci_copy);
+} /* oconfig_item_t *oconfig_clone */
+
+void oconfig_free (oconfig_item_t *ci)
+{
+  int i;
+
+  if (ci == NULL)
+    return;
+
+  if (ci->key != NULL)
+    free (ci->key);
+
+  for (i = 0; i < ci->values_num; i++)
+    if ((ci->values[i].type == OCONFIG_TYPE_STRING)
+        && (NULL != ci->values[i].value.string))
+      free (ci->values[i].value.string);
+
+  if (ci->values != NULL)
+    free (ci->values);
+
+  for (i = 0; i < ci->children_num; i++)
+    oconfig_free (ci->children + i);
+
+  if (ci->children != NULL)
+    free (ci->children);
+}
+
+/*
+ * vim:shiftwidth=2:tabstop=8:softtabstop=2:fdm=marker
+ */
diff --git a/src/liboconfig/oconfig.h b/src/liboconfig/oconfig.h
new file mode 100644 (file)
index 0000000..70fc623
--- /dev/null
@@ -0,0 +1,69 @@
+#ifndef OCONFIG_H
+#define OCONFIG_H 1
+
+#include <stdio.h>
+
+/**
+ * oconfig - src/oconfig.h
+ * Copyright (C) 2006-2009  Florian octo Forster <octo at verplant.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+/*
+ * Types
+ */
+#define OCONFIG_TYPE_STRING  0
+#define OCONFIG_TYPE_NUMBER  1
+#define OCONFIG_TYPE_BOOLEAN 2
+
+struct oconfig_value_s
+{
+  union
+  {
+    char  *string;
+    double number;
+    int    boolean;
+  } value;
+  int type;
+};
+typedef struct oconfig_value_s oconfig_value_t;
+
+struct oconfig_item_s;
+typedef struct oconfig_item_s oconfig_item_t;
+struct oconfig_item_s
+{
+  char            *key;
+  oconfig_value_t *values;
+  int              values_num;
+
+  oconfig_item_t  *parent;
+  oconfig_item_t  *children;
+  int              children_num;
+};
+
+/*
+ * Functions
+ */
+oconfig_item_t *oconfig_parse_fh (FILE *fh);
+oconfig_item_t *oconfig_parse_file (const char *file);
+
+oconfig_item_t *oconfig_clone (const oconfig_item_t *ci);
+
+void oconfig_free (oconfig_item_t *ci);
+
+/*
+ * vim: shiftwidth=2:tabstop=8:softtabstop=2
+ */
+#endif /* OCONFIG_H */
diff --git a/src/liboconfig/parser.y b/src/liboconfig/parser.y
new file mode 100644 (file)
index 0000000..5b7aa94
--- /dev/null
@@ -0,0 +1,239 @@
+/**
+ * oconfig - src/parser.y
+ * Copyright (C) 2007,2008  Florian octo Forster <octo at verplant.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+%{
+#include <stdlib.h>
+#include <string.h>
+#include "oconfig.h"
+#include "aux_types.h"
+
+static char *unquote (const char *orig);
+static int yyerror (const char *s);
+
+/* Lexer variables */
+extern int yylineno;
+extern char *yytext;
+
+extern oconfig_item_t *ci_root;
+extern char           *c_file;
+%}
+
+%start entire_file
+
+%union {
+       double  number;
+       int     boolean;
+       char   *string;
+       oconfig_value_t  cv;
+       oconfig_item_t   ci;
+       argument_list_t  al;
+       statement_list_t sl;
+}
+
+%token <number> NUMBER
+%token <boolean> BTRUE BFALSE
+%token <string> QUOTED_STRING UNQUOTED_STRING
+%token SLASH OPENBRAC CLOSEBRAC EOL
+
+%type <string> string
+%type <string> identifier
+/* arguments */
+%type <cv> argument
+%type <al> argument_list
+/* blocks */
+%type <ci> block_begin
+%type <ci> block
+%type <string> block_end
+/* statements */
+%type <ci> option
+%type <ci> statement
+%type <sl> statement_list
+%type <ci> entire_file
+
+/* pass an verbose, specific error message to yyerror() */
+%error-verbose
+
+%%
+string:
+       QUOTED_STRING           {$$ = unquote ($1);}
+       | UNQUOTED_STRING       {$$ = strdup ($1);}
+       ;
+
+argument:
+       NUMBER                  {$$.value.number = $1; $$.type = OCONFIG_TYPE_NUMBER;}
+       | BTRUE                 {$$.value.boolean = 1; $$.type = OCONFIG_TYPE_BOOLEAN;}
+       | BFALSE                {$$.value.boolean = 0; $$.type = OCONFIG_TYPE_BOOLEAN;}
+       | string                {$$.value.string = $1; $$.type = OCONFIG_TYPE_STRING;}
+       ;
+
+argument_list:
+       argument_list argument
+       {
+        $$ = $1;
+        $$.argument_num++;
+        $$.argument = realloc ($$.argument, $$.argument_num * sizeof (oconfig_value_t));
+        $$.argument[$$.argument_num-1] = $2;
+       }
+       | argument
+       {
+        $$.argument = malloc (sizeof (oconfig_value_t));
+        $$.argument[0] = $1;
+        $$.argument_num = 1;
+       }
+       ;
+
+identifier:
+       UNQUOTED_STRING                 {$$ = strdup ($1);}
+       ;
+
+option:
+       identifier argument_list EOL
+       {
+        memset (&$$, '\0', sizeof ($$));
+        $$.key = $1;
+        $$.values = $2.argument;
+        $$.values_num = $2.argument_num;
+       }
+       ;
+
+block_begin:
+       OPENBRAC identifier CLOSEBRAC EOL
+       {
+        memset (&$$, '\0', sizeof ($$));
+        $$.key = $2;
+       }
+       |
+       OPENBRAC identifier argument_list CLOSEBRAC EOL
+       {
+        memset (&$$, '\0', sizeof ($$));
+        $$.key = $2;
+        $$.values = $3.argument;
+        $$.values_num = $3.argument_num;
+       }
+       ;
+
+block_end:
+       OPENBRAC SLASH identifier CLOSEBRAC EOL
+       {
+        $$ = $3;
+       }
+       ;
+
+block:
+       block_begin statement_list block_end
+       {
+        if (strcmp ($1.key, $3) != 0)
+        {
+               printf ("block_begin = %s; block_end = %s;\n", $1.key, $3);
+               yyerror ("Block not closed..\n");
+               exit (1);
+        }
+        free ($3); $3 = NULL;
+        $$ = $1;
+        $$.children = $2.statement;
+        $$.children_num = $2.statement_num;
+       }
+       ;
+
+statement:
+       option          {$$ = $1;}
+       | block         {$$ = $1;}
+       | EOL           {$$.values_num = 0;}
+       ;
+
+statement_list:
+       statement_list statement
+       {
+        $$ = $1;
+        if (($2.values_num > 0) || ($2.children_num > 0))
+        {
+                $$.statement_num++;
+                $$.statement = realloc ($$.statement, $$.statement_num * sizeof (oconfig_item_t));
+                $$.statement[$$.statement_num-1] = $2;
+        }
+       }
+       | statement
+       {
+        if (($1.values_num > 0) || ($1.children_num > 0))
+        {
+                $$.statement = malloc (sizeof (oconfig_item_t));
+                $$.statement[0] = $1;
+                $$.statement_num = 1;
+        }
+        else
+        {
+               $$.statement = NULL;
+               $$.statement_num = 0;
+        }
+       }
+       ;
+
+entire_file:
+       statement_list
+       {
+        ci_root = malloc (sizeof (oconfig_item_t));
+        memset (ci_root, '\0', sizeof (oconfig_item_t));
+        ci_root->children = $1.statement;
+        ci_root->children_num = $1.statement_num;
+       }
+       ;
+
+%%
+static int yyerror (const char *s)
+{
+       char *text;
+
+       if (*yytext == '\n')
+               text = "<newline>";
+       else
+               text = yytext;
+
+       fprintf (stderr, "Parse error in file `%s', line %i near `%s': %s\n",
+               c_file, yylineno, text, s);
+       return (-1);
+} /* int yyerror */
+
+static char *unquote (const char *orig)
+{
+       char *ret = strdup (orig);
+       int len;
+       int i;
+
+       if (ret == NULL)
+               return (NULL);
+
+       len = strlen (ret);
+
+       if ((len < 2) || (ret[0] != '"') || (ret[len - 1] != '"'))
+               return (ret);
+
+       len -= 2;
+       memmove (ret, ret + 1, len);
+       ret[len] = '\0';
+
+       for (i = 0; i < len; i++)
+       {
+               if (ret[i] == '\\')
+               {
+                       memmove (ret + i, ret + (i + 1), len - i);
+                       len--;
+               }
+       }
+
+       return (ret);
+} /* char *unquote */
diff --git a/src/liboconfig/scanner.l b/src/liboconfig/scanner.l
new file mode 100644 (file)
index 0000000..9f0cd8e
--- /dev/null
@@ -0,0 +1,137 @@
+/**
+ * oconfig - src/scanner.l
+ * Copyright (C) 2007  Florian octo Forster <octo at verplant.org>
+ * Copyright (C) 2008  Sebastian tokkee Harl <sh at tokkee.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+%{
+#include <stdlib.h>
+#include "oconfig.h"
+#include "aux_types.h"
+#include "parser.h"
+
+/* multiline string buffer */
+static char *ml_buffer = NULL;
+static int   ml_pos    = 0;
+static int   ml_len    = 0;
+
+#define ml_free (ml_len - ml_pos)
+
+static void ml_append (char *);
+
+#ifdef yyterminate
+# undef yyterminate
+#endif
+#define yyterminate() \
+       do { free (ml_buffer); ml_buffer = NULL; ml_pos = 0; ml_len = 0; \
+               return YY_NULL; } while (0)
+%}
+%option yylineno
+%option noyywrap
+%x ML
+WHITE_SPACE [\ \t\b]
+NON_WHITE_SPACE [^\ \t\b]
+EOL (\r\n|\n)
+QUOTED_STRING ([^\\"]+|\\.)*
+UNQUOTED_STRING [0-9A-Za-z_]+
+HEX_NUMBER 0[xX][0-9a-fA-F]+
+OCT_NUMBER 0[0-7]+
+DEC_NUMBER [\+\-]?[0-9]+
+FLOAT_NUMBER [\+\-]?[0-9]*\.[0-9]+([eE][\+\-][0-9]+)?
+NUMBER ({FLOAT_NUMBER}|{HEX_NUMBER}|{OCT_NUMBER}|{DEC_NUMBER})
+BOOL_TRUE (true|yes|on)
+BOOL_FALSE (false|no|off)
+COMMENT #.*
+PORT (6(5(5(3[0-5]|[0-2][0-9])|[0-4][0-9][0-9])|[0-4][0-9][0-9][0-9])|[1-5][0-9][0-9][0-9][0-9]|[1-9][0-9]?[0-9]?[0-9]?)
+IP_BYTE (2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])
+IPV4_ADDR {IP_BYTE}\.{IP_BYTE}\.{IP_BYTE}\.{IP_BYTE}(:{PORT})?
+
+%%
+{WHITE_SPACE}          |
+{COMMENT}              {/* ignore */}
+
+\\{EOL}                        {/* continue line */}
+
+{EOL}                  {return (EOL);}
+"/"                    {return (SLASH);}
+"<"                    {return (OPENBRAC);}
+">"                    {return (CLOSEBRAC);}
+{BOOL_TRUE}            {yylval.boolean = 1; return (BTRUE);}
+{BOOL_FALSE}           {yylval.boolean = 0; return (BFALSE);}
+
+{IPV4_ADDR}            {yylval.string = yytext; return (UNQUOTED_STRING);}
+
+{NUMBER}               {yylval.number = strtod (yytext, NULL); return (NUMBER);}
+
+\"{QUOTED_STRING}\"    {yylval.string = yytext; return (QUOTED_STRING);}
+{UNQUOTED_STRING}      {yylval.string = yytext; return (UNQUOTED_STRING);}
+
+\"{QUOTED_STRING}\\{EOL} {
+       int len = strlen (yytext);
+
+       ml_pos = 0;
+
+       /* remove "\\<EOL>" */
+       if ('\r' == yytext[len - 2])
+               len -= 3;
+       else
+               len -= 2;
+       yytext[len] = '\0';
+
+       ml_append (yytext);
+       BEGIN (ML);
+}
+<ML>^{WHITE_SPACE}+ {/* remove leading white-space */}
+<ML>{NON_WHITE_SPACE}{QUOTED_STRING}\\{EOL} {
+       int len = strlen (yytext);
+
+       /* remove "\\<EOL>" */
+       if ('\r' == yytext[len - 2])
+               len -= 3;
+       else
+               len -= 2;
+       yytext[len] = '\0';
+
+       ml_append(yytext);
+}
+<ML>{NON_WHITE_SPACE}{QUOTED_STRING}\" {
+       ml_append(yytext);
+       yylval.string = ml_buffer;
+
+       BEGIN (INITIAL);
+       return (QUOTED_STRING);
+}
+%%
+static void ml_append (char *string)
+{
+       int len = strlen (string);
+       int s;
+
+       if (ml_free <= len) {
+               ml_len += len - ml_free + 1;
+               ml_buffer = (char *)realloc (ml_buffer, ml_len);
+               if (NULL == ml_buffer)
+                       YY_FATAL_ERROR ("out of dynamic memory in ml_append");
+       }
+
+       s = snprintf (ml_buffer + ml_pos, ml_free, "%s", string);
+       if ((0 > s) || (ml_free <= s))
+               YY_FATAL_ERROR ("failed to write to multiline buffer");
+
+       ml_pos += s;
+       return;
+} /* ml_append */
+
diff --git a/src/libvirt.c b/src/libvirt.c
new file mode 100644 (file)
index 0000000..774067c
--- /dev/null
@@ -0,0 +1,837 @@
+/**
+ * collectd - src/libvirt.c
+ * Copyright (C) 2006-2008  Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the license is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Richard W.M. Jones <rjones@redhat.com>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+#include "utils_ignorelist.h"
+#include "utils_complain.h"
+
+#include <libvirt/libvirt.h>
+#include <libvirt/virterror.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/xpath.h>
+
+static const char *config_keys[] = {
+    "Connection",
+
+    "RefreshInterval",
+
+    "Domain",
+    "BlockDevice",
+    "InterfaceDevice",
+    "IgnoreSelected",
+
+    "HostnameFormat",
+    "InterfaceFormat",
+
+    NULL
+};
+#define NR_CONFIG_KEYS ((sizeof config_keys / sizeof config_keys[0]) - 1)
+
+/* Connection. */
+static virConnectPtr conn = 0;
+static char *conn_string = NULL;
+static c_complain_t conn_complain = C_COMPLAIN_INIT_STATIC;
+
+/* Seconds between list refreshes, 0 disables completely. */
+static int interval = 60;
+
+/* List of domains, if specified. */
+static ignorelist_t *il_domains = NULL;
+/* List of block devices, if specified. */
+static ignorelist_t *il_block_devices = NULL;
+/* List of network interface devices, if specified. */
+static ignorelist_t *il_interface_devices = NULL;
+
+static int ignore_device_match (ignorelist_t *,
+                                const char *domname, const char *devpath);
+
+/* Actual list of domains found on last refresh. */
+static virDomainPtr *domains = NULL;
+static int nr_domains = 0;
+
+static void free_domains (void);
+static int add_domain (virDomainPtr dom);
+
+/* Actual list of block devices found on last refresh. */
+struct block_device {
+    virDomainPtr dom;           /* domain */
+    char *path;                 /* name of block device */
+};
+
+static struct block_device *block_devices = NULL;
+static int nr_block_devices = 0;
+
+static void free_block_devices (void);
+static int add_block_device (virDomainPtr dom, const char *path);
+
+/* Actual list of network interfaces found on last refresh. */
+struct interface_device {
+    virDomainPtr dom;           /* domain */
+    char *path;                 /* name of interface device */
+    char *address;              /* mac address of interface device */
+};
+
+static struct interface_device *interface_devices = NULL;
+static int nr_interface_devices = 0;
+
+static void free_interface_devices (void);
+static int add_interface_device (virDomainPtr dom, const char *path, const char *address);
+
+/* HostnameFormat. */
+#define HF_MAX_FIELDS 3
+
+enum hf_field {
+    hf_none = 0,
+    hf_hostname,
+    hf_name,
+    hf_uuid
+};
+
+static enum hf_field hostname_format[HF_MAX_FIELDS] =
+    { hf_name };
+
+/* InterfaceFormat. */
+enum if_field {
+    if_address,
+    if_name
+};
+
+static enum if_field interface_format = if_name;
+
+/* Time that we last refreshed. */
+static time_t last_refresh = (time_t) 0;
+
+static int refresh_lists (void);
+
+/* ERROR(...) macro for virterrors. */
+#define VIRT_ERROR(conn,s) do {                 \
+        virErrorPtr err;                        \
+        err = (conn) ? virConnGetLastError ((conn)) : virGetLastError (); \
+        if (err) ERROR ("%s: %s", (s), err->message);                   \
+    } while(0)
+
+static void
+init_value_list (value_list_t *vl, virDomainPtr dom)
+{
+    int i, n;
+    const char *name;
+    char uuid[VIR_UUID_STRING_BUFLEN];
+
+    vl->interval = interval_g;
+
+    sstrncpy (vl->plugin, "libvirt", sizeof (vl->plugin));
+
+    vl->host[0] = '\0';
+
+    /* Construct the hostname field according to HostnameFormat. */
+    for (i = 0; i < HF_MAX_FIELDS; ++i) {
+        if (hostname_format[i] == hf_none)
+            continue;
+
+        n = DATA_MAX_NAME_LEN - strlen (vl->host) - 2;
+
+        if (i > 0 && n >= 1) {
+            strncat (vl->host, ":", 1);
+            n--;
+        }
+
+        switch (hostname_format[i]) {
+        case hf_none: break;
+        case hf_hostname:
+            strncat (vl->host, hostname_g, n);
+            break;
+        case hf_name:
+            name = virDomainGetName (dom);
+            if (name)
+                strncat (vl->host, name, n);
+            break;
+        case hf_uuid:
+            if (virDomainGetUUIDString (dom, uuid) == 0)
+                strncat (vl->host, uuid, n);
+            break;
+        }
+    }
+
+    vl->host[sizeof (vl->host) - 1] = '\0';
+} /* void init_value_list */
+
+static void
+cpu_submit (unsigned long long cpu_time,
+            virDomainPtr dom, const char *type)
+{
+    value_t values[1];
+    value_list_t vl = VALUE_LIST_INIT;
+
+    init_value_list (&vl, dom);
+
+    values[0].derive = cpu_time;
+
+    vl.values = values;
+    vl.values_len = 1;
+
+    sstrncpy (vl.type, type, sizeof (vl.type));
+
+    plugin_dispatch_values (&vl);
+}
+
+static void
+vcpu_submit (derive_t cpu_time,
+             virDomainPtr dom, int vcpu_nr, const char *type)
+{
+    value_t values[1];
+    value_list_t vl = VALUE_LIST_INIT;
+
+    init_value_list (&vl, dom);
+
+    values[0].derive = cpu_time;
+    vl.values = values;
+    vl.values_len = 1;
+
+    sstrncpy (vl.type, type, sizeof (vl.type));
+    ssnprintf (vl.type_instance, sizeof (vl.type_instance), "%d", vcpu_nr);
+
+    plugin_dispatch_values (&vl);
+}
+
+static void
+submit_derive2 (const char *type, derive_t v0, derive_t v1,
+             virDomainPtr dom, const char *devname)
+{
+    value_t values[2];
+    value_list_t vl = VALUE_LIST_INIT;
+
+    init_value_list (&vl, dom);
+
+    values[0].derive = v0;
+    values[1].derive = v1;
+    vl.values = values;
+    vl.values_len = 2;
+
+    sstrncpy (vl.type, type, sizeof (vl.type));
+    sstrncpy (vl.type_instance, devname, sizeof (vl.type_instance));
+
+    plugin_dispatch_values (&vl);
+} /* void submit_derive2 */
+
+static int
+lv_init (void)
+{
+    if (virInitialize () != 0)
+        return -1;
+
+       return 0;
+}
+
+static int
+lv_config (const char *key, const char *value)
+{
+    if (virInitialize () != 0)
+        return 1;
+
+    if (il_domains == NULL)
+        il_domains = ignorelist_create (1);
+    if (il_block_devices == NULL)
+        il_block_devices = ignorelist_create (1);
+    if (il_interface_devices == NULL)
+        il_interface_devices = ignorelist_create (1);
+
+    if (strcasecmp (key, "Connection") == 0) {
+        char *tmp = strdup (value);
+        if (tmp == NULL) {
+            ERROR ("libvirt plugin: Connection strdup failed.");
+            return 1;
+        }
+        sfree (conn_string);
+        conn_string = tmp;
+        return 0;
+    }
+
+    if (strcasecmp (key, "RefreshInterval") == 0) {
+        char *eptr = NULL;
+        interval = strtol (value, &eptr, 10);
+        if (eptr == NULL || *eptr != '\0') return 1;
+        return 0;
+    }
+
+    if (strcasecmp (key, "Domain") == 0) {
+        if (ignorelist_add (il_domains, value)) return 1;
+        return 0;
+    }
+    if (strcasecmp (key, "BlockDevice") == 0) {
+        if (ignorelist_add (il_block_devices, value)) return 1;
+        return 0;
+    }
+    if (strcasecmp (key, "InterfaceDevice") == 0) {
+        if (ignorelist_add (il_interface_devices, value)) return 1;
+        return 0;
+    }
+
+    if (strcasecmp (key, "IgnoreSelected") == 0) {
+        if (IS_TRUE (value))
+        {
+            ignorelist_set_invert (il_domains, 0);
+            ignorelist_set_invert (il_block_devices, 0);
+            ignorelist_set_invert (il_interface_devices, 0);
+        }
+        else
+        {
+            ignorelist_set_invert (il_domains, 1);
+            ignorelist_set_invert (il_block_devices, 1);
+            ignorelist_set_invert (il_interface_devices, 1);
+        }
+        return 0;
+    }
+
+    if (strcasecmp (key, "HostnameFormat") == 0) {
+        char *value_copy;
+        char *fields[HF_MAX_FIELDS];
+        int i, n;
+
+        value_copy = strdup (value);
+        if (value_copy == NULL) {
+            ERROR ("libvirt plugin: strdup failed.");
+            return -1;
+        }
+
+        n = strsplit (value_copy, fields, HF_MAX_FIELDS);
+        if (n < 1) {
+            sfree (value_copy);
+            ERROR ("HostnameFormat: no fields");
+            return -1;
+        }
+
+        for (i = 0; i < n; ++i) {
+            if (strcasecmp (fields[i], "hostname") == 0)
+                hostname_format[i] = hf_hostname;
+            else if (strcasecmp (fields[i], "name") == 0)
+                hostname_format[i] = hf_name;
+            else if (strcasecmp (fields[i], "uuid") == 0)
+                hostname_format[i] = hf_uuid;
+            else {
+                sfree (value_copy);
+                ERROR ("unknown HostnameFormat field: %s", fields[i]);
+                return -1;
+            }
+        }
+        sfree (value_copy);
+
+        for (i = n; i < HF_MAX_FIELDS; ++i)
+            hostname_format[i] = hf_none;
+
+        return 0;
+    }
+
+    if (strcasecmp (key, "InterfaceFormat") == 0) {
+        if (strcasecmp (value, "name") == 0)
+            interface_format = if_name;
+        else if (strcasecmp (value, "address") == 0)
+            interface_format = if_address;
+        else {
+            ERROR ("unknown InterfaceFormat: %s", value);
+            return -1;
+        }
+        return 0;
+    }
+
+    /* Unrecognised option. */
+    return -1;
+}
+
+static int
+lv_read (void)
+{
+    time_t t;
+    int i;
+
+    if (conn == NULL) {
+        /* `conn_string == NULL' is acceptable. */
+        conn = virConnectOpenReadOnly (conn_string);
+        if (conn == NULL) {
+            c_complain (LOG_ERR, &conn_complain,
+                    "libvirt plugin: Unable to connect: "
+                    "virConnectOpenReadOnly failed.");
+            return -1;
+        }
+    }
+    c_release (LOG_NOTICE, &conn_complain,
+            "libvirt plugin: Connection established.");
+
+    time (&t);
+
+    /* Need to refresh domain or device lists? */
+    if ((last_refresh == (time_t) 0) ||
+            ((interval > 0) && ((last_refresh + interval) <= t))) {
+        if (refresh_lists () != 0) {
+            if (conn != NULL)
+                virConnectClose (conn);
+            conn = NULL;
+            return -1;
+        }
+        last_refresh = t;
+    }
+
+#if 0
+    for (i = 0; i < nr_domains; ++i)
+        fprintf (stderr, "domain %s\n", virDomainGetName (domains[i]));
+    for (i = 0; i < nr_block_devices; ++i)
+        fprintf  (stderr, "block device %d %s:%s\n",
+                  i, virDomainGetName (block_devices[i].dom),
+                  block_devices[i].path);
+    for (i = 0; i < nr_interface_devices; ++i)
+        fprintf (stderr, "interface device %d %s:%s\n",
+                 i, virDomainGetName (interface_devices[i].dom),
+                 interface_devices[i].path);
+#endif
+
+    /* Get CPU usage, VCPU usage for each domain. */
+    for (i = 0; i < nr_domains; ++i) {
+        virDomainInfo info;
+        virVcpuInfoPtr vinfo = NULL;
+        int status;
+        int j;
+
+        status = virDomainGetInfo (domains[i], &info);
+        if (status != 0)
+        {
+            ERROR ("libvirt plugin: virDomainGetInfo failed with status %i.",
+                    status);
+            continue;
+        }
+
+        cpu_submit (info.cpuTime, domains[i], "virt_cpu_total");
+
+        vinfo = malloc (info.nrVirtCpu * sizeof (vinfo[0]));
+        if (vinfo == NULL) {
+            ERROR ("libvirt plugin: malloc failed.");
+            continue;
+        }
+
+        status = virDomainGetVcpus (domains[i], vinfo, info.nrVirtCpu,
+                /* cpu map = */ NULL, /* cpu map length = */ 0);
+        if (status < 0)
+        {
+            ERROR ("libvirt plugin: virDomainGetVcpus failed with status %i.",
+                    status);
+            free (vinfo);
+            continue;
+        }
+
+        for (j = 0; j < info.nrVirtCpu; ++j)
+            vcpu_submit (vinfo[j].cpuTime,
+                    domains[i], vinfo[j].number, "virt_vcpu");
+
+        sfree (vinfo);
+    }
+
+    /* Get block device stats for each domain. */
+    for (i = 0; i < nr_block_devices; ++i) {
+        struct _virDomainBlockStats stats;
+
+        if (virDomainBlockStats (block_devices[i].dom, block_devices[i].path,
+                    &stats, sizeof stats) != 0)
+            continue;
+
+        if ((stats.rd_req != -1) && (stats.wr_req != -1))
+            submit_derive2 ("disk_ops",
+                    (derive_t) stats.rd_req, (derive_t) stats.wr_req,
+                    block_devices[i].dom, block_devices[i].path);
+
+        if ((stats.rd_bytes != -1) && (stats.wr_bytes != -1))
+            submit_derive2 ("disk_octets",
+                    (derive_t) stats.rd_bytes, (derive_t) stats.wr_bytes,
+                    block_devices[i].dom, block_devices[i].path);
+    } /* for (nr_block_devices) */
+
+    /* Get interface stats for each domain. */
+    for (i = 0; i < nr_interface_devices; ++i) {
+        struct _virDomainInterfaceStats stats;
+        char *display_name = interface_devices[i].path;
+
+        if (interface_format == if_address)
+            display_name = interface_devices[i].address;
+
+        if (virDomainInterfaceStats (interface_devices[i].dom,
+                    interface_devices[i].path,
+                    &stats, sizeof stats) != 0)
+            continue;
+
+       if ((stats.rx_bytes != -1) && (stats.tx_bytes != -1))
+           submit_derive2 ("if_octets",
+                   (derive_t) stats.rx_bytes, (derive_t) stats.tx_bytes,
+                   interface_devices[i].dom, display_name);
+
+       if ((stats.rx_packets != -1) && (stats.tx_packets != -1))
+           submit_derive2 ("if_packets",
+                   (derive_t) stats.rx_packets, (derive_t) stats.tx_packets,
+                   interface_devices[i].dom, display_name);
+
+       if ((stats.rx_errs != -1) && (stats.tx_errs != -1))
+           submit_derive2 ("if_errors",
+                   (derive_t) stats.rx_errs, (derive_t) stats.tx_errs,
+                   interface_devices[i].dom, display_name);
+
+       if ((stats.rx_drop != -1) && (stats.tx_drop != -1))
+           submit_derive2 ("if_dropped",
+                   (derive_t) stats.rx_drop, (derive_t) stats.tx_drop,
+                   interface_devices[i].dom, display_name);
+    } /* for (nr_interface_devices) */
+
+    return 0;
+}
+
+static int
+refresh_lists (void)
+{
+    int n;
+
+    n = virConnectNumOfDomains (conn);
+    if (n < 0) {
+        VIRT_ERROR (conn, "reading number of domains");
+        return -1;
+    }
+
+    if (n > 0) {
+        int i;
+        int *domids;
+
+        /* Get list of domains. */
+        domids = malloc (sizeof (int) * n);
+        if (domids == 0) {
+            ERROR ("libvirt plugin: malloc failed.");
+            return -1;
+        }
+
+        n = virConnectListDomains (conn, domids, n);
+        if (n < 0) {
+            VIRT_ERROR (conn, "reading list of domains");
+            sfree (domids);
+            return -1;
+        }
+
+        free_block_devices ();
+        free_interface_devices ();
+        free_domains ();
+
+        /* Fetch each domain and add it to the list, unless ignore. */
+        for (i = 0; i < n; ++i) {
+            virDomainPtr dom = NULL;
+            const char *name;
+            char *xml = NULL;
+            xmlDocPtr xml_doc = NULL;
+            xmlXPathContextPtr xpath_ctx = NULL;
+            xmlXPathObjectPtr xpath_obj = NULL;
+            int j;
+
+            dom = virDomainLookupByID (conn, domids[i]);
+            if (dom == NULL) {
+                VIRT_ERROR (conn, "virDomainLookupByID");
+                /* Could be that the domain went away -- ignore it anyway. */
+                continue;
+            }
+
+            name = virDomainGetName (dom);
+            if (name == NULL) {
+                VIRT_ERROR (conn, "virDomainGetName");
+                goto cont;
+            }
+
+            if (il_domains && ignorelist_match (il_domains, name) != 0)
+                goto cont;
+
+            if (add_domain (dom) < 0) {
+                ERROR ("libvirt plugin: malloc failed.");
+                goto cont;
+            }
+
+            /* Get a list of devices for this domain. */
+            xml = virDomainGetXMLDesc (dom, 0);
+            if (!xml) {
+                VIRT_ERROR (conn, "virDomainGetXMLDesc");
+                goto cont;
+            }
+
+            /* Yuck, XML.  Parse out the devices. */
+            xml_doc = xmlReadDoc ((xmlChar *) xml, NULL, NULL, XML_PARSE_NONET);
+            if (xml_doc == NULL) {
+                VIRT_ERROR (conn, "xmlReadDoc");
+                goto cont;
+            }
+
+            xpath_ctx = xmlXPathNewContext (xml_doc);
+
+            /* Block devices. */
+            xpath_obj = xmlXPathEval
+                ((xmlChar *) "/domain/devices/disk/target[@dev]",
+                 xpath_ctx);
+            if (xpath_obj == NULL || xpath_obj->type != XPATH_NODESET ||
+                xpath_obj->nodesetval == NULL)
+                goto cont;
+
+            for (j = 0; j < xpath_obj->nodesetval->nodeNr; ++j) {
+                xmlNodePtr node;
+                char *path = NULL;
+
+                node = xpath_obj->nodesetval->nodeTab[j];
+                if (!node) continue;
+                path = (char *) xmlGetProp (node, (xmlChar *) "dev");
+                if (!path) continue;
+
+                if (il_block_devices &&
+                    ignore_device_match (il_block_devices, name, path) != 0)
+                    goto cont2;
+
+                add_block_device (dom, path);
+            cont2:
+                if (path) xmlFree (path);
+            }
+            xmlXPathFreeObject (xpath_obj);
+
+            /* Network interfaces. */
+            xpath_obj = xmlXPathEval
+                ((xmlChar *) "/domain/devices/interface[target[@dev]]",
+                 xpath_ctx);
+            if (xpath_obj == NULL || xpath_obj->type != XPATH_NODESET ||
+                xpath_obj->nodesetval == NULL)
+                goto cont;
+
+            xmlNodeSetPtr xml_interfaces = xpath_obj->nodesetval;
+
+            for (j = 0; j < xml_interfaces->nodeNr; ++j) {
+                char *path = NULL;
+                char *address = NULL;
+                xmlNodePtr xml_interface;
+
+                xml_interface = xml_interfaces->nodeTab[j];
+                if (!xml_interface) continue;
+                xmlNodePtr child = NULL;
+
+                for (child = xml_interface->children; child; child = child->next) {
+                    if (child->type != XML_ELEMENT_NODE) continue;
+
+                    if (xmlStrEqual(child->name, (const xmlChar *) "target")) {
+                        path = (char *) xmlGetProp (child, (const xmlChar *) "dev");
+                        if (!path) continue;
+                    } else if (xmlStrEqual(child->name, (const xmlChar *) "mac")) {
+                        address = (char *) xmlGetProp (child, (const xmlChar *) "address");
+                        if (!address) continue;
+                    }
+                }
+
+                if (il_interface_devices &&
+                    (ignore_device_match (il_interface_devices, name, path) != 0 ||
+                     ignore_device_match (il_interface_devices, name, address) != 0))
+                    goto cont3;
+
+                add_interface_device (dom, path, address);
+                cont3:
+                    if (path) xmlFree (path);
+                    if (address) xmlFree (address);
+            }
+
+        cont:
+            if (xpath_obj) xmlXPathFreeObject (xpath_obj);
+            if (xpath_ctx) xmlXPathFreeContext (xpath_ctx);
+            if (xml_doc) xmlFreeDoc (xml_doc);
+            sfree (xml);
+        }
+
+        sfree (domids);
+    }
+
+    return 0;
+}
+
+static void
+free_domains ()
+{
+    int i;
+
+    if (domains) {
+        for (i = 0; i < nr_domains; ++i)
+            virDomainFree (domains[i]);
+        sfree (domains);
+    }
+    domains = NULL;
+    nr_domains = 0;
+}
+
+static int
+add_domain (virDomainPtr dom)
+{
+    virDomainPtr *new_ptr;
+    int new_size = sizeof (domains[0]) * (nr_domains+1);
+
+    if (domains)
+        new_ptr = realloc (domains, new_size);
+    else
+        new_ptr = malloc (new_size);
+
+    if (new_ptr == NULL)
+        return -1;
+
+    domains = new_ptr;
+    domains[nr_domains] = dom;
+    return nr_domains++;
+}
+
+static void
+free_block_devices ()
+{
+    int i;
+
+    if (block_devices) {
+        for (i = 0; i < nr_block_devices; ++i)
+            sfree (block_devices[i].path);
+        sfree (block_devices);
+    }
+    block_devices = NULL;
+    nr_block_devices = 0;
+}
+
+static int
+add_block_device (virDomainPtr dom, const char *path)
+{
+    struct block_device *new_ptr;
+    int new_size = sizeof (block_devices[0]) * (nr_block_devices+1);
+    char *path_copy;
+
+    path_copy = strdup (path);
+    if (!path_copy)
+        return -1;
+
+    if (block_devices)
+        new_ptr = realloc (block_devices, new_size);
+    else
+        new_ptr = malloc (new_size);
+
+    if (new_ptr == NULL) {
+        sfree (path_copy);
+        return -1;
+    }
+    block_devices = new_ptr;
+    block_devices[nr_block_devices].dom = dom;
+    block_devices[nr_block_devices].path = path_copy;
+    return nr_block_devices++;
+}
+
+static void
+free_interface_devices ()
+{
+    int i;
+
+    if (interface_devices) {
+        for (i = 0; i < nr_interface_devices; ++i) {
+            sfree (interface_devices[i].path);
+            sfree (interface_devices[i].address);
+        }
+        sfree (interface_devices);
+    }
+    interface_devices = NULL;
+    nr_interface_devices = 0;
+}
+
+static int
+add_interface_device (virDomainPtr dom, const char *path, const char *address)
+{
+    struct interface_device *new_ptr;
+    int new_size = sizeof (interface_devices[0]) * (nr_interface_devices+1);
+    char *path_copy, *address_copy;
+
+    path_copy = strdup (path);
+    if (!path_copy) return -1;
+
+    address_copy = strdup (address);
+    if (!address_copy) return -1;
+
+    if (interface_devices)
+        new_ptr = realloc (interface_devices, new_size);
+    else
+        new_ptr = malloc (new_size);
+
+    if (new_ptr == NULL) {
+        sfree (path_copy);
+        sfree (address_copy);
+        return -1;
+    }
+    interface_devices = new_ptr;
+    interface_devices[nr_interface_devices].dom = dom;
+    interface_devices[nr_interface_devices].path = path_copy;
+    interface_devices[nr_interface_devices].address = address_copy;
+    return nr_interface_devices++;
+}
+
+static int
+ignore_device_match (ignorelist_t *il, const char *domname, const char *devpath)
+{
+    char *name;
+    int n, r;
+
+    n = sizeof (char) * (strlen (domname) + strlen (devpath) + 2);
+    name = malloc (n);
+    if (name == NULL) {
+        ERROR ("libvirt plugin: malloc failed.");
+        return 0;
+    }
+    ssnprintf (name, n, "%s:%s", domname, devpath);
+    r = ignorelist_match (il, name);
+    sfree (name);
+    return r;
+}
+
+static int
+lv_shutdown (void)
+{
+    free_block_devices ();
+    free_interface_devices ();
+    free_domains ();
+
+    if (conn != NULL)
+       virConnectClose (conn);
+    conn = NULL;
+
+    ignorelist_free (il_domains);
+    il_domains = NULL;
+    ignorelist_free (il_block_devices);
+    il_block_devices = NULL;
+    ignorelist_free (il_interface_devices);
+    il_interface_devices = NULL;
+
+    return 0;
+}
+
+void
+module_register (void)
+{
+    plugin_register_config ("libvirt",
+           lv_config,
+           config_keys, NR_CONFIG_KEYS);
+    plugin_register_init ("libvirt", lv_init);
+    plugin_register_read ("libvirt", lv_read);
+    plugin_register_shutdown ("libvirt", lv_shutdown);
+}
+
+/*
+ * vim: shiftwidth=4 tabstop=8 softtabstop=4 expandtab fdm=marker
+ */
diff --git a/src/load.c b/src/load.c
new file mode 100644 (file)
index 0000000..0188da7
--- /dev/null
@@ -0,0 +1,172 @@
+/**
+ * collectd - src/load.c
+ * Copyright (C) 2005-2008  Florian octo Forster
+ * Copyright (C) 2009       Manuel Sanmartin
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ *   Manuel Sanmartin
+ **/
+
+#define _BSD_SOURCE
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#ifdef HAVE_SYS_LOADAVG_H
+#include <sys/loadavg.h>
+#endif
+
+#if HAVE_STATGRAB_H
+# include <statgrab.h>
+#endif
+
+#ifdef HAVE_GETLOADAVG
+#if !defined(LOADAVG_1MIN) || !defined(LOADAVG_5MIN) || !defined(LOADAVG_15MIN)
+#define LOADAVG_1MIN  0
+#define LOADAVG_5MIN  1
+#define LOADAVG_15MIN 2
+#endif
+#endif /* defined(HAVE_GETLOADAVG) */
+
+#ifdef HAVE_PERFSTAT
+# include <sys/proc.h> /* AIX 5 */
+# include <sys/protosw.h>
+# include <libperfstat.h>
+#endif /* HAVE_PERFSTAT */
+
+static void load_submit (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;
+
+       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));
+
+       plugin_dispatch_values (&vl);
+}
+
+static int load_read (void)
+{
+#if defined(HAVE_GETLOADAVG)
+       double load[3];
+
+       if (getloadavg (load, 3) == 3)
+               load_submit (load[LOADAVG_1MIN], load[LOADAVG_5MIN], load[LOADAVG_15MIN]);
+       else
+       {
+               char errbuf[1024];
+               WARNING ("load: getloadavg failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+       }
+/* #endif HAVE_GETLOADAVG */
+
+#elif defined(KERNEL_LINUX)
+       gauge_t snum, mnum, lnum;
+       FILE *loadavg;
+       char buffer[16];
+
+       char *fields[8];
+       int numfields;
+
+       if ((loadavg = fopen ("/proc/loadavg", "r")) == NULL)
+       {
+               char errbuf[1024];
+               WARNING ("load: fopen: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       if (fgets (buffer, 16, loadavg) == NULL)
+       {
+               char errbuf[1024];
+               WARNING ("load: fgets: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               fclose (loadavg);
+               return (-1);
+       }
+
+       if (fclose (loadavg))
+       {
+               char errbuf[1024];
+               WARNING ("load: fclose: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+       }
+
+       numfields = strsplit (buffer, fields, 8);
+
+       if (numfields < 3)
+               return (-1);
+
+       snum = atof (fields[0]);
+       mnum = atof (fields[1]);
+       lnum = atof (fields[2]);
+
+       load_submit (snum, mnum, lnum);
+/* #endif KERNEL_LINUX */
+
+#elif HAVE_LIBSTATGRAB
+       gauge_t snum, mnum, lnum;
+       sg_load_stats *ls;
+
+       if ((ls = sg_get_load_stats ()) == NULL)
+               return;
+
+       snum = ls->min1;
+       mnum = ls->min5;
+       lnum = ls->min15;
+
+       load_submit (snum, mnum, lnum);
+/* #endif HAVE_LIBSTATGRAB */
+
+#elif HAVE_PERFSTAT
+       gauge_t snum, mnum, lnum;
+       perfstat_cpu_total_t cputotal;
+
+       if (perfstat_cpu_total(NULL,  &cputotal, sizeof(perfstat_cpu_total_t), 1) < 0)
+       {
+               char errbuf[1024];
+               WARNING ("load: perfstat_cpu : %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       snum = (float)cputotal.loadavg[0]/(float)(1<<SBITS);
+       mnum = (float)cputotal.loadavg[1]/(float)(1<<SBITS);
+       lnum = (float)cputotal.loadavg[2]/(float)(1<<SBITS);
+
+       load_submit (snum, mnum, lnum);
+/* #endif HAVE_PERFSTAT */
+
+#else
+# error "No applicable input method."
+#endif
+
+       return (0);
+}
+
+void module_register (void)
+{
+       plugin_register_read ("load", load_read);
+} /* void module_register */
diff --git a/src/logfile.c b/src/logfile.c
new file mode 100644 (file)
index 0000000..60fb5d9
--- /dev/null
@@ -0,0 +1,238 @@
+/**
+ * collectd - src/logfile.c
+ * Copyright (C) 2007  Sebastian Harl
+ * Copyright (C) 2007,2008  Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Sebastian Harl <sh at tokkee.org>
+ *   Florian Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include <pthread.h>
+
+#define DEFAULT_LOGFILE LOCALSTATEDIR"/log/collectd.log"
+
+#if COLLECT_DEBUG
+static int log_level = LOG_DEBUG;
+#else
+static int log_level = LOG_INFO;
+#endif /* COLLECT_DEBUG */
+
+static pthread_mutex_t file_lock = PTHREAD_MUTEX_INITIALIZER;
+
+static char *log_file = NULL;
+static int print_timestamp = 1;
+static int print_severity = 0;
+
+static const char *config_keys[] =
+{
+       "LogLevel",
+       "File",
+       "Timestamp",
+       "PrintSeverity"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static int logfile_config (const char *key, const char *value)
+{
+       if (0 == strcasecmp (key, "LogLevel")) {
+               if ((0 == strcasecmp (value, "emerg"))
+                               || (0 == strcasecmp (value, "alert"))
+                               || (0 == strcasecmp (value, "crit"))
+                               || (0 == strcasecmp (value, "err")))
+                       log_level = LOG_ERR;
+               else if (0 == strcasecmp (value, "warning"))
+                       log_level = LOG_WARNING;
+               else if (0 == strcasecmp (value, "notice"))
+                       log_level = LOG_NOTICE;
+               else if (0 == strcasecmp (value, "info"))
+                       log_level = LOG_INFO;
+#if COLLECT_DEBUG
+               else if (0 == strcasecmp (value, "debug"))
+                       log_level = LOG_DEBUG;
+#endif /* COLLECT_DEBUG */
+               else
+                       return 1;
+       }
+       else if (0 == strcasecmp (key, "File")) {
+               sfree (log_file);
+               log_file = strdup (value);
+       }
+       else if (0 == strcasecmp (key, "Timestamp")) {
+               if (IS_FALSE (value))
+                       print_timestamp = 0;
+               else
+                       print_timestamp = 1;
+       } else if (0 == strcasecmp(key, "PrintSeverity")) {
+               if (IS_FALSE (value))
+                       print_severity = 0;
+               else
+                       print_severity = 1;
+       }
+       else {
+               return -1;
+       }
+       return 0;
+} /* int logfile_config (const char *, const char *) */
+
+static void logfile_print (const char *msg, int severity,
+               cdtime_t timestamp_time)
+{
+       FILE *fh;
+       int do_close = 0;
+       struct tm timestamp_tm;
+       char timestamp_str[64];
+       char level_str[16] = "";
+
+       if (print_severity)
+       {
+               switch (severity)
+               {
+               case LOG_ERR:
+                       snprintf(level_str, sizeof (level_str), "[error] ");
+                       break;  
+               case LOG_WARNING:
+                       snprintf(level_str, sizeof (level_str), "[warning] ");
+                       break;
+               case LOG_NOTICE:
+                       snprintf(level_str, sizeof (level_str), "[notice] ");
+                       break;  
+               case LOG_INFO:
+                       snprintf(level_str, sizeof (level_str), "[info] ");
+                       break;  
+               case LOG_DEBUG:
+                       snprintf(level_str, sizeof (level_str), "[debug] ");
+                       break;  
+               default:
+                       break;
+               }
+       }
+
+       if (print_timestamp)
+       {
+               time_t tt = CDTIME_T_TO_TIME_T (timestamp_time);
+               localtime_r (&tt, &timestamp_tm);
+
+               strftime (timestamp_str, sizeof (timestamp_str), "%Y-%m-%d %H:%M:%S",
+                               &timestamp_tm);
+               timestamp_str[sizeof (timestamp_str) - 1] = '\0';
+       }
+
+       pthread_mutex_lock (&file_lock);
+
+       if (log_file == NULL)
+       {
+               fh = fopen (DEFAULT_LOGFILE, "a");
+               do_close = 1;
+       }
+       else if (strcasecmp (log_file, "stderr") == 0)
+               fh = stderr;
+       else if (strcasecmp (log_file, "stdout") == 0)
+               fh = stdout;
+       else
+       {
+               fh = fopen (log_file, "a");
+               do_close = 1;
+       }
+
+       if (fh == NULL)
+       {
+                       char errbuf[1024];
+                       fprintf (stderr, "logfile plugin: fopen (%s) failed: %s\n",
+                                       (log_file == NULL) ? DEFAULT_LOGFILE : log_file,
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+       }
+       else
+       {
+               if (print_timestamp)
+                       fprintf (fh, "[%s] %s%s\n", timestamp_str, level_str, msg);
+               else
+                       fprintf (fh, "%s%s\n", level_str, msg);
+
+               if (do_close != 0)
+                       fclose (fh);
+       }
+
+       pthread_mutex_unlock (&file_lock);
+
+       return;
+} /* void logfile_print */
+
+static void logfile_log (int severity, const char *msg,
+               user_data_t __attribute__((unused)) *user_data)
+{
+       if (severity > log_level)
+               return;
+
+       logfile_print (msg, severity, cdtime ());
+} /* void logfile_log (int, const char *) */
+
+static int logfile_notification (const notification_t *n,
+               user_data_t __attribute__((unused)) *user_data)
+{
+       char  buf[1024] = "";
+       char *buf_ptr = buf;
+       int   buf_len = sizeof (buf);
+       int status;
+
+       status = ssnprintf (buf_ptr, buf_len, "Notification: severity = %s",
+                       (n->severity == NOTIF_FAILURE) ? "FAILURE"
+                       : ((n->severity == NOTIF_WARNING) ? "WARNING"
+                               : ((n->severity == NOTIF_OKAY) ? "OKAY" : "UNKNOWN")));
+       if (status > 0)
+       {
+               buf_ptr += status;
+               buf_len -= status;
+       }
+
+#define APPEND(bufptr, buflen, key, value) \
+       if ((buflen > 0) && (strlen (value) > 0)) { \
+               int status = ssnprintf (bufptr, buflen, ", %s = %s", key, value); \
+               if (status > 0) { \
+                       bufptr += status; \
+                       buflen -= status; \
+               } \
+       }
+       APPEND (buf_ptr, buf_len, "host", n->host);
+       APPEND (buf_ptr, buf_len, "plugin", n->plugin);
+       APPEND (buf_ptr, buf_len, "plugin_instance", n->plugin_instance);
+       APPEND (buf_ptr, buf_len, "type", n->type);
+       APPEND (buf_ptr, buf_len, "type_instance", n->type_instance);
+       APPEND (buf_ptr, buf_len, "message", n->message);
+
+       buf[sizeof (buf) - 1] = '\0';
+
+       logfile_print (buf, LOG_INFO,
+                       (n->time != 0) ? n->time : cdtime ());
+
+       return (0);
+} /* int logfile_notification */
+
+void module_register (void)
+{
+       plugin_register_config ("logfile", logfile_config,
+                       config_keys, config_keys_num);
+       plugin_register_log ("logfile", logfile_log, /* user_data = */ NULL);
+       plugin_register_notification ("logfile", logfile_notification,
+                       /* user_data = */ NULL);
+} /* void module_register (void) */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
+
diff --git a/src/lpar.c b/src/lpar.c
new file mode 100644 (file)
index 0000000..4d53447
--- /dev/null
@@ -0,0 +1,273 @@
+/**
+ * collectd - src/lpar.c
+ * Copyright (C) 2010  Aurélien Reynaud
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Aurélien Reynaud <collectd at wattapower.net>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include <sys/protosw.h>
+#include <libperfstat.h>
+#include <sys/utsname.h>
+
+/* XINTFRAC was defined in libperfstat.h somewhere between AIX 5.3 and 6.1 */
+#ifndef XINTFRAC
+# include <sys/systemcfg.h>
+# define XINTFRAC ((double)(_system_configuration.Xint) / \
+                   (double)(_system_configuration.Xfrac))
+#endif
+
+#define CLOCKTICKS_TO_TICKS(cticks) ((cticks) / XINTFRAC)
+
+static const char *config_keys[] =
+{
+  "CpuPoolStats",
+  "ReportBySerial"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static _Bool pool_stats = 0;
+static _Bool report_by_serial = 0;
+#if PERFSTAT_SUPPORTS_DONATION
+static _Bool donate_flag = 0;
+#endif
+static char serial[SYS_NMLN];
+
+static perfstat_partition_total_t lparstats_old;
+
+static int lpar_config (const char *key, const char *value)
+{
+       if (strcasecmp ("CpuPoolStats", key) == 0)
+       {
+               if (IS_TRUE (value))
+                       pool_stats = 1;
+               else
+                       pool_stats = 0;
+       }
+       else if (strcasecmp ("ReportBySerial", key) == 0)
+       {
+               if (IS_TRUE (value))
+                       report_by_serial = 1;
+               else
+                       report_by_serial = 0;
+       }
+       else
+       {
+               return (-1);
+       }
+
+       return (0);
+} /* int lpar_config */
+
+static int lpar_init (void)
+{
+       int status;
+
+       /* Retrieve the initial metrics. Returns the number of structures filled. */
+       status = perfstat_partition_total (/* name = */ NULL, /* (must be NULL) */
+                       &lparstats_old, sizeof (perfstat_partition_total_t),
+                       /* number = */ 1 /* (must be 1) */);
+       if (status != 1)
+       {
+               char errbuf[1024];
+               ERROR ("lpar plugin: perfstat_partition_total failed: %s (%i)",
+                               sstrerror (errno, errbuf, sizeof (errbuf)),
+                               status);
+               return (-1);
+       }
+
+#if PERFSTAT_SUPPORTS_DONATION
+       if (!lparstats_old.type.b.shared_enabled
+                       && lparstats_old.type.b.donate_enabled)
+       {
+               donate_flag = 1;
+       }
+#endif
+
+       if (pool_stats && !lparstats_old.type.b.pool_util_authority)
+       {
+               WARNING ("lpar plugin: This partition does not have pool authority. "
+                               "Disabling CPU pool statistics collection.");
+               pool_stats = 0;
+       }
+
+       return (0);
+} /* 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_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));
+       sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
+
+       plugin_dispatch_values (&vl);
+} /* void lpar_submit */
+
+static int lpar_read (void)
+{
+       perfstat_partition_total_t lparstats;
+       int status;
+       struct utsname name;
+       u_longlong_t ticks;
+       u_longlong_t user_ticks, syst_ticks, wait_ticks, idle_ticks;
+       u_longlong_t consumed_ticks;
+       double entitled_proc_capacity;
+
+       /* An LPAR has the same serial number as the physical system it is currently
+          running on. It is a convenient way of tracking LPARs as they are moved
+          from chassis to chassis through Live Partition Mobility (LPM). */
+       if (uname (&name) != 0)
+       {
+               ERROR ("lpar plugin: uname failed.");
+               return (-1);
+       }
+       sstrncpy (serial, name.machine, sizeof (serial));
+
+       /* Retrieve the current metrics. Returns the number of structures filled. */
+       status = perfstat_partition_total (/* name = */ NULL, /* (must be NULL) */
+                       &lparstats, sizeof (perfstat_partition_total_t),
+                       /* number = */ 1 /* (must be 1) */);
+       if (status != 1)
+       {
+               char errbuf[1024];
+               ERROR ("lpar plugin: perfstat_partition_total failed: %s (%i)",
+                               sstrerror (errno, errbuf, sizeof (errbuf)),
+                               status);
+               return (-1);
+       }
+
+       /* Number of ticks since we last run. */
+       ticks = lparstats.timebase_last - lparstats_old.timebase_last;
+       if (ticks == 0)
+       {
+               /* The stats have not been updated. Return now to avoid
+                * dividing by zero */
+               return (0);
+       }
+
+       /*
+        * On a shared partition, we're "entitled" to a certain amount of
+        * processing power, for example 250/100 of a physical CPU. Processing
+        * capacity not used by the partition may be assigned to a different
+        * partition by the hypervisor, so "idle" is hopefully a very small
+        * number.
+        *
+        * A dedicated partition may donate its CPUs to another partition and
+        * may steal ticks from somewhere else (another partition or maybe the
+        * shared pool, I don't know --octo).
+        */
+
+       /* entitled_proc_capacity is in 1/100th of a CPU */
+       entitled_proc_capacity = 0.01 * ((double) lparstats.entitled_proc_capacity);
+       lpar_submit ("entitled", entitled_proc_capacity);
+
+       /* The number of ticks actually spent in the various states */
+       user_ticks = lparstats.puser - lparstats_old.puser;
+       syst_ticks = lparstats.psys  - lparstats_old.psys;
+       wait_ticks = lparstats.pwait - lparstats_old.pwait;
+       idle_ticks = lparstats.pidle - lparstats_old.pidle;
+       consumed_ticks = user_ticks + syst_ticks + wait_ticks + idle_ticks;
+
+       lpar_submit ("user", (double) user_ticks / (double) ticks);
+       lpar_submit ("system", (double) syst_ticks / (double) ticks);
+       lpar_submit ("wait", (double) wait_ticks / (double) ticks);
+       lpar_submit ("idle", (double) idle_ticks / (double) ticks);
+
+#if PERFSTAT_SUPPORTS_DONATION
+       if (donate_flag)
+       {
+               /* donated => ticks given to another partition
+                * stolen  => ticks received from another partition */
+               u_longlong_t idle_donated_ticks, busy_donated_ticks;
+               u_longlong_t idle_stolen_ticks, busy_stolen_ticks;
+
+               /* FYI:  PURR == Processor Utilization of Resources Register
+                *      SPURR == Scaled PURR */
+               idle_donated_ticks = lparstats.idle_donated_purr - lparstats_old.idle_donated_purr;
+               busy_donated_ticks = lparstats.busy_donated_purr - lparstats_old.busy_donated_purr;
+               idle_stolen_ticks  = lparstats.idle_stolen_purr  - lparstats_old.idle_stolen_purr;
+               busy_stolen_ticks  = lparstats.busy_stolen_purr  - lparstats_old.busy_stolen_purr;
+
+               lpar_submit ("idle_donated", (double) idle_donated_ticks / (double) ticks);
+               lpar_submit ("busy_donated", (double) busy_donated_ticks / (double) ticks);
+               lpar_submit ("idle_stolen",  (double) idle_stolen_ticks  / (double) ticks);
+               lpar_submit ("busy_stolen",  (double) busy_stolen_ticks  / (double) ticks);
+
+               /* Donated ticks will be accounted for as stolen ticks in other LPARs */
+               consumed_ticks += idle_stolen_ticks + busy_stolen_ticks;
+       }
+#endif
+
+       lpar_submit ("consumed", (double) consumed_ticks / (double) ticks);
+
+       if (pool_stats)
+       {
+               char typinst[DATA_MAX_NAME_LEN];
+               u_longlong_t pool_idle_cticks;
+               double pool_idle_cpus;
+               double pool_busy_cpus;
+
+               /* We're calculating "busy" from "idle" and the total number of
+                * CPUs, because the "busy" member didn't exist in early versions
+                * of libperfstat. It was added somewhere between AIX 5.3 ML5 and ML9. */
+               pool_idle_cticks = lparstats.pool_idle_time - lparstats_old.pool_idle_time;
+               pool_idle_cpus = CLOCKTICKS_TO_TICKS ((double) pool_idle_cticks) / (double) ticks;
+               pool_busy_cpus = ((double) lparstats.phys_cpus_pool) - pool_idle_cpus;
+               if (pool_busy_cpus < 0.0)
+                       pool_busy_cpus = 0.0;
+
+               ssnprintf (typinst, sizeof (typinst), "pool-%X-busy", lparstats.pool_id);
+               lpar_submit (typinst, pool_busy_cpus);
+
+               ssnprintf (typinst, sizeof (typinst), "pool-%X-idle", lparstats.pool_id);
+               lpar_submit (typinst, pool_idle_cpus);
+       }
+
+       memcpy (&lparstats_old, &lparstats, sizeof (lparstats_old));
+
+       return (0);
+} /* int lpar_read */
+
+void module_register (void)
+{
+       plugin_register_config ("lpar", lpar_config,
+                               config_keys, config_keys_num);
+       plugin_register_init ("lpar", lpar_init);
+       plugin_register_read ("lpar", lpar_read);
+} /* void module_register */
+
+/* vim: set sw=8 noet : */
+
diff --git a/src/madwifi.c b/src/madwifi.c
new file mode 100644 (file)
index 0000000..13301ff
--- /dev/null
@@ -0,0 +1,975 @@
+/**
+ * collectd - src/madwifi.c
+ * Copyright (C) 2009  Ondrej 'SanTiago' Zajicek
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Ondrej 'SanTiago' Zajicek <santiago@crfreenet.org>
+ *
+ *   based on some code from interfaces.c (collectd) and Madwifi driver
+ **/
+
+
+/**
+ * There are several data streams provided by Madwifi plugin, some are 
+ * connected to network interface, some are connected to each node
+ * associated to that interface. Nodes represents other sides in
+ * wireless communication, for example on network interface in AP mode,
+ * there is one node for each associated station. Node data streams
+ * contain MAC address of the node as the last part  of the type_instance
+ * field.
+ *
+ * Inteface data streams:
+ *     ath_nodes       The number of associated nodes
+ *     ath_stat        Device statistic counters
+ *
+ * Node data streams:
+ *     node_octets     RX and TX data count (octets/bytes)
+ *     node_rssi       Received RSSI of the node
+ *     node_tx_rate    Reported TX rate to that node
+ *     node_stat       Node statistic counters
+ *
+ * Both statistic counters have type instances for each counter returned
+ * by Madwifi. See madwifi.h for content of ieee80211_nodestats, 
+ * ieee80211_stats and ath_stats structures. Type instances use the same
+ * name as fields in these structures (like ns_rx_dup). Some fields are
+ * not reported, because they are not counters (like ns_tx_deauth_code
+ * or ast_tx_rssi). Fields ns_rx_bytes and ns_tx_bytes are reported as
+ * node_octets data stream instead of type instance of node_stat.
+ * Statistics are not logged when they are zero.
+ * 
+ * There are two sets of these counters - the first 'WatchList' is a
+ * set of counters that are individually logged. The second 'MiscList'
+ * is a set of counters that are summed together and the sum is logged.
+ * By default, the most important statistics are in the WatchList and 
+ * many error statistics are in MiscList. There are also many statistics
+ * that are not in any of these sets, so they are not monitored by default.
+ * It is possible to alter these lists using configuration options:
+ *
+ *     WatchAdd X      Adds X to WachList
+ *     WatchRemove X   Removes X from WachList
+ *     WatchSet All    Adds all statistics to WatchList
+ *     WatchSet None   Removes all statistics from WachList
+ *
+ * There are also Misc* variants fo these options, they modifies MiscList
+ * instead of WatchList.
+ *
+ * Example:
+ *
+ *     WatchSet None
+ *     WatchAdd node_octets
+ *     WatchAdd node_rssi
+ *     WatchAdd is_rx_acl
+ *     WatchAdd is_scan_active
+ *
+ * That causes that just the four mentioned data streams are logged.
+ *
+ *
+ * By default, madwifi plugin enumerates network interfaces using /sys
+ * filesystem. Configuration option `Source' can change this to use
+ * /proc filesystem (which is useful for example when running on Linux
+ * 2.4). But without /sys filesystem, Madwifi plugin cannot check whether
+ * given interface is madwifi interface and there are private ioctls used,
+ * which may do something completely different on non-madwifi devices.
+ * Therefore, the /proc filesystem should always be used together with option
+ * `Interface', to limit found interfaces to madwifi interfaces only.
+ **/
+
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+#include "utils_ignorelist.h"
+
+#include <dirent.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#if !KERNEL_LINUX
+# error "No applicable input method."
+#endif
+
+#include <linux/wireless.h>
+#include "madwifi.h"
+
+
+
+struct stat_spec {
+       uint16_t flags;
+       uint16_t offset;
+       const char *name;
+};
+
+
+#define OFFSETOF(s, i) ((size_t)&((s *)0)->i)
+
+#define FLAG(i)  (((uint32_t) 1) << ((i) % 32))
+
+#define SPC_STAT 0
+#define NOD_STAT 1
+#define IFA_STAT 2
+#define ATH_STAT 3
+#define SRC_MASK 3
+
+/* By default, the item is disabled */
+#define D 0
+
+/* By default, the item is logged */
+#define LOG 4
+
+/* By default, the item is summed with other such items and logged together */
+#define SU 8
+
+#define SS_STAT(flags, name) { flags | SPC_STAT, 0, #name }
+#define NS_STAT(flags, name) { flags | NOD_STAT, OFFSETOF(struct ieee80211_nodestats, name), #name }
+#define IS_STAT(flags, name) { flags | IFA_STAT, OFFSETOF(struct ieee80211_stats, name), #name }
+#define AS_STAT(flags, name) { flags | ATH_STAT, OFFSETOF(struct ath_stats, name), #name }
+
+
+/*
+ * (Module-)Global variables
+ */
+
+/* Indices of special stats in specs array */
+#define STAT_NODE_OCTETS       0
+#define STAT_NODE_RSSI         1
+#define STAT_NODE_TX_RATE      2
+#define STAT_ATH_NODES         3
+#define STAT_NS_RX_BEACONS     4
+#define STAT_AST_ANT_RX                5
+#define STAT_AST_ANT_TX                6
+
+static struct stat_spec specs[] = {
+
+/* Special statistics */
+SS_STAT(LOG, node_octets),             /* rx and tx data count (bytes) */
+SS_STAT(LOG, node_rssi),               /* received RSSI of the node */
+SS_STAT(LOG, node_tx_rate),            /* used tx rate to the node */
+SS_STAT(LOG, ath_nodes),               /* the number of associated nodes */
+SS_STAT(D,   ns_rx_beacons),           /* rx beacon frames */
+SS_STAT(LOG, ast_ant_rx),              /* rx frames with antenna */
+SS_STAT(LOG, ast_ant_tx),              /* tx frames with antenna */
+
+/* Node statistics */
+NS_STAT(LOG, ns_rx_data),              /* rx data frames */
+NS_STAT(LOG, ns_rx_mgmt),              /* rx management frames */
+NS_STAT(LOG, ns_rx_ctrl),              /* rx control frames */
+NS_STAT(D,   ns_rx_ucast),             /* rx unicast frames */
+NS_STAT(D,   ns_rx_mcast),             /* rx multi/broadcast frames */
+NS_STAT(D,   ns_rx_proberesp),         /* rx probe response frames */
+NS_STAT(LOG, ns_rx_dup),               /* rx discard because it's a dup */
+NS_STAT(SU,  ns_rx_noprivacy),         /* rx w/ wep but privacy off */
+NS_STAT(SU,  ns_rx_wepfail),           /* rx wep processing failed */
+NS_STAT(SU,  ns_rx_demicfail),         /* rx demic failed */
+NS_STAT(SU,  ns_rx_decap),             /* rx decapsulation failed */
+NS_STAT(SU,  ns_rx_defrag),            /* rx defragmentation failed */
+NS_STAT(D,   ns_rx_disassoc),          /* rx disassociation */
+NS_STAT(D,   ns_rx_deauth),            /* rx deauthentication */
+NS_STAT(SU,  ns_rx_decryptcrc),                /* rx decrypt failed on crc */
+NS_STAT(SU,  ns_rx_unauth),            /* rx on unauthorized port */
+NS_STAT(SU,  ns_rx_unencrypted),       /* rx unecrypted w/ privacy */
+NS_STAT(LOG, ns_tx_data),              /* tx data frames */
+NS_STAT(LOG, ns_tx_mgmt),              /* tx management frames */
+NS_STAT(D,   ns_tx_ucast),             /* tx unicast frames */
+NS_STAT(D,   ns_tx_mcast),             /* tx multi/broadcast frames */
+NS_STAT(D,   ns_tx_probereq),          /* tx probe request frames */
+NS_STAT(D,   ns_tx_uapsd),             /* tx on uapsd queue */
+NS_STAT(SU,  ns_tx_novlantag),         /* tx discard due to no tag */
+NS_STAT(SU,  ns_tx_vlanmismatch),      /* tx discard due to of bad tag */
+NS_STAT(D,   ns_tx_eosplost),          /* uapsd EOSP retried out */
+NS_STAT(D,   ns_ps_discard),           /* ps discard due to of age */
+NS_STAT(D,   ns_uapsd_triggers),       /* uapsd triggers */
+NS_STAT(LOG, ns_tx_assoc),             /* [re]associations */
+NS_STAT(LOG, ns_tx_auth),              /* [re]authentications */
+NS_STAT(D,   ns_tx_deauth),            /* deauthentications */
+NS_STAT(D,   ns_tx_disassoc),          /* disassociations */
+NS_STAT(D,   ns_psq_drops),            /* power save queue drops */
+
+/* Iface statistics */
+IS_STAT(SU,  is_rx_badversion),                /* rx frame with bad version */
+IS_STAT(SU,  is_rx_tooshort),          /* rx frame too short */
+IS_STAT(LOG, is_rx_wrongbss),          /* rx from wrong bssid */
+IS_STAT(LOG, is_rx_dup),               /* rx discard due to it's a dup */
+IS_STAT(SU,  is_rx_wrongdir),          /* rx w/ wrong direction */
+IS_STAT(D,   is_rx_mcastecho),         /* rx discard due to of mcast echo */
+IS_STAT(SU,  is_rx_notassoc),          /* rx discard due to sta !assoc */
+IS_STAT(SU,  is_rx_noprivacy),         /* rx w/ wep but privacy off */
+IS_STAT(SU,  is_rx_unencrypted),       /* rx w/o wep and privacy on */
+IS_STAT(SU,  is_rx_wepfail),           /* rx wep processing failed */
+IS_STAT(SU,  is_rx_decap),             /* rx decapsulation failed */
+IS_STAT(D,   is_rx_mgtdiscard),                /* rx discard mgt frames */
+IS_STAT(D,   is_rx_ctl),               /* rx discard ctrl frames */
+IS_STAT(D,   is_rx_beacon),            /* rx beacon frames */
+IS_STAT(D,   is_rx_rstoobig),          /* rx rate set truncated */
+IS_STAT(SU,  is_rx_elem_missing),      /* rx required element missing*/
+IS_STAT(SU,  is_rx_elem_toobig),       /* rx element too big */
+IS_STAT(SU,  is_rx_elem_toosmall),     /* rx element too small */
+IS_STAT(LOG, is_rx_elem_unknown),      /* rx element unknown */
+IS_STAT(SU,  is_rx_badchan),           /* rx frame w/ invalid chan */
+IS_STAT(SU,  is_rx_chanmismatch),      /* rx frame chan mismatch */
+IS_STAT(SU,  is_rx_nodealloc),         /* rx frame dropped */
+IS_STAT(LOG, is_rx_ssidmismatch),      /* rx frame ssid mismatch  */
+IS_STAT(SU,  is_rx_auth_unsupported),  /* rx w/ unsupported auth alg */
+IS_STAT(SU,  is_rx_auth_fail),         /* rx sta auth failure */
+IS_STAT(SU,  is_rx_auth_countermeasures),/* rx auth discard due to CM */
+IS_STAT(SU,  is_rx_assoc_bss),         /* rx assoc from wrong bssid */
+IS_STAT(SU,  is_rx_assoc_notauth),     /* rx assoc w/o auth */
+IS_STAT(SU,  is_rx_assoc_capmismatch), /* rx assoc w/ cap mismatch */
+IS_STAT(SU,  is_rx_assoc_norate),      /* rx assoc w/ no rate match */
+IS_STAT(SU,  is_rx_assoc_badwpaie),    /* rx assoc w/ bad WPA IE */
+IS_STAT(LOG, is_rx_deauth),            /* rx deauthentication */
+IS_STAT(LOG, is_rx_disassoc),          /* rx disassociation */
+IS_STAT(SU,  is_rx_badsubtype),                /* rx frame w/ unknown subtype*/
+IS_STAT(SU,  is_rx_nobuf),             /* rx failed for lack of buf */
+IS_STAT(SU,  is_rx_decryptcrc),                /* rx decrypt failed on crc */
+IS_STAT(D,   is_rx_ahdemo_mgt),                /* rx discard ahdemo mgt frame*/
+IS_STAT(SU,  is_rx_bad_auth),          /* rx bad auth request */
+IS_STAT(SU,  is_rx_unauth),            /* rx on unauthorized port */
+IS_STAT(SU,  is_rx_badkeyid),          /* rx w/ incorrect keyid */
+IS_STAT(D,   is_rx_ccmpreplay),                /* rx seq# violation (CCMP), */
+IS_STAT(D,   is_rx_ccmpformat),                /* rx format bad (CCMP), */
+IS_STAT(D,   is_rx_ccmpmic),           /* rx MIC check failed (CCMP), */
+IS_STAT(D,   is_rx_tkipreplay),                /* rx seq# violation (TKIP), */
+IS_STAT(D,   is_rx_tkipformat),                /* rx format bad (TKIP), */
+IS_STAT(D,   is_rx_tkipmic),           /* rx MIC check failed (TKIP), */
+IS_STAT(D,   is_rx_tkipicv),           /* rx ICV check failed (TKIP), */
+IS_STAT(D,   is_rx_badcipher),         /* rx failed due to of key type */
+IS_STAT(D,   is_rx_nocipherctx),       /* rx failed due to key !setup */
+IS_STAT(D,   is_rx_acl),               /* rx discard due to of acl policy */
+IS_STAT(D,   is_rx_ffcnt),             /* rx fast frames */
+IS_STAT(SU,  is_rx_badathtnl),         /* driver key alloc failed */
+IS_STAT(SU,  is_tx_nobuf),             /* tx failed for lack of buf */
+IS_STAT(SU,  is_tx_nonode),            /* tx failed for no node */
+IS_STAT(SU,  is_tx_unknownmgt),                /* tx of unknown mgt frame */
+IS_STAT(SU,  is_tx_badcipher),         /* tx failed due to of key type */
+IS_STAT(SU,  is_tx_nodefkey),          /* tx failed due to no defkey */
+IS_STAT(SU,  is_tx_noheadroom),                /* tx failed due to no space */
+IS_STAT(D,   is_tx_ffokcnt),           /* tx fast frames sent success */
+IS_STAT(D,   is_tx_fferrcnt),          /* tx fast frames sent success */
+IS_STAT(D,   is_scan_active),          /* active scans started */
+IS_STAT(D,   is_scan_passive),         /* passive scans started */
+IS_STAT(D,   is_node_timeout),         /* nodes timed out inactivity */
+IS_STAT(D,   is_crypto_nomem),         /* no memory for crypto ctx */
+IS_STAT(D,   is_crypto_tkip),          /* tkip crypto done in s/w */
+IS_STAT(D,   is_crypto_tkipenmic),     /* tkip en-MIC done in s/w */
+IS_STAT(D,   is_crypto_tkipdemic),     /* tkip de-MIC done in s/w */
+IS_STAT(D,   is_crypto_tkipcm),                /* tkip counter measures */
+IS_STAT(D,   is_crypto_ccmp),          /* ccmp crypto done in s/w */
+IS_STAT(D,   is_crypto_wep),           /* wep crypto done in s/w */
+IS_STAT(D,   is_crypto_setkey_cipher), /* cipher rejected key */
+IS_STAT(D,   is_crypto_setkey_nokey),  /* no key index for setkey */
+IS_STAT(D,   is_crypto_delkey),                /* driver key delete failed */
+IS_STAT(D,   is_crypto_badcipher),     /* unknown cipher */
+IS_STAT(D,   is_crypto_nocipher),      /* cipher not available */
+IS_STAT(D,   is_crypto_attachfail),    /* cipher attach failed */
+IS_STAT(D,   is_crypto_swfallback),    /* cipher fallback to s/w */
+IS_STAT(D,   is_crypto_keyfail),       /* driver key alloc failed */
+IS_STAT(D,   is_crypto_enmicfail),     /* en-MIC failed */
+IS_STAT(SU,  is_ibss_capmismatch),     /* merge failed-cap mismatch */
+IS_STAT(SU,  is_ibss_norate),          /* merge failed-rate mismatch */
+IS_STAT(D,   is_ps_unassoc),           /* ps-poll for unassoc. sta */
+IS_STAT(D,   is_ps_badaid),            /* ps-poll w/ incorrect aid */
+IS_STAT(D,   is_ps_qempty),            /* ps-poll w/ nothing to send */
+
+/* Atheros statistics */
+AS_STAT(D,   ast_watchdog),            /* device reset by watchdog */
+AS_STAT(D,   ast_hardware),            /* fatal hardware error interrupts */
+AS_STAT(D,   ast_bmiss),               /* beacon miss interrupts */
+AS_STAT(D,   ast_rxorn),               /* rx overrun interrupts */
+AS_STAT(D,   ast_rxeol),               /* rx eol interrupts */
+AS_STAT(D,   ast_txurn),               /* tx underrun interrupts */
+AS_STAT(D,   ast_mib),                 /* mib interrupts */
+AS_STAT(D,   ast_tx_packets),          /* packet sent on the interface */
+AS_STAT(D,   ast_tx_mgmt),             /* management frames transmitted */
+AS_STAT(LOG, ast_tx_discard),          /* frames discarded prior to assoc */
+AS_STAT(SU,  ast_tx_invalid),          /* frames discarded due to is device gone */
+AS_STAT(SU,  ast_tx_qstop),            /* tx queue stopped because it's full */
+AS_STAT(SU,  ast_tx_encap),            /* tx encapsulation failed */
+AS_STAT(SU,  ast_tx_nonode),           /* tx failed due to of no node */
+AS_STAT(SU,  ast_tx_nobuf),            /* tx failed due to of no tx buffer (data), */
+AS_STAT(SU,  ast_tx_nobufmgt),         /* tx failed due to of no tx buffer (mgmt),*/
+AS_STAT(LOG, ast_tx_xretries),         /* tx failed due to of too many retries */
+AS_STAT(SU,  ast_tx_fifoerr),          /* tx failed due to of FIFO underrun */
+AS_STAT(SU,  ast_tx_filtered),         /* tx failed due to xmit filtered */
+AS_STAT(LOG, ast_tx_shortretry),       /* tx on-chip retries (short), */
+AS_STAT(LOG, ast_tx_longretry),                /* tx on-chip retries (long), */
+AS_STAT(SU,  ast_tx_badrate),          /* tx failed due to of bogus xmit rate */
+AS_STAT(D,   ast_tx_noack),            /* tx frames with no ack marked */
+AS_STAT(D,   ast_tx_rts),              /* tx frames with rts enabled */
+AS_STAT(D,   ast_tx_cts),              /* tx frames with cts enabled */
+AS_STAT(D,   ast_tx_shortpre),         /* tx frames with short preamble */
+AS_STAT(LOG, ast_tx_altrate),          /* tx frames with alternate rate */
+AS_STAT(D,   ast_tx_protect),          /* tx frames with protection */
+AS_STAT(SU,  ast_rx_orn),              /* rx failed due to of desc overrun */
+AS_STAT(LOG, ast_rx_crcerr),           /* rx failed due to of bad CRC */
+AS_STAT(SU,  ast_rx_fifoerr),          /* rx failed due to of FIFO overrun */
+AS_STAT(SU,  ast_rx_badcrypt),         /* rx failed due to of decryption */
+AS_STAT(SU,  ast_rx_badmic),           /* rx failed due to of MIC failure */
+AS_STAT(LOG, ast_rx_phyerr),           /* rx PHY error summary count */
+AS_STAT(SU,  ast_rx_tooshort),         /* rx discarded due to frame too short */
+AS_STAT(SU,  ast_rx_toobig),           /* rx discarded due to frame too large */
+AS_STAT(SU,  ast_rx_nobuf),            /* rx setup failed due to of no skbuff */
+AS_STAT(D,   ast_rx_packets),          /* packet recv on the interface */
+AS_STAT(D,   ast_rx_mgt),              /* management frames received */
+AS_STAT(D,   ast_rx_ctl),              /* control frames received */
+AS_STAT(D,   ast_be_xmit),             /* beacons transmitted */
+AS_STAT(SU,  ast_be_nobuf),            /* no skbuff available for beacon */
+AS_STAT(D,   ast_per_cal),             /* periodic calibration calls */
+AS_STAT(D,   ast_per_calfail),         /* periodic calibration failed */
+AS_STAT(D,   ast_per_rfgain),          /* periodic calibration rfgain reset */
+AS_STAT(D,   ast_rate_calls),          /* rate control checks */
+AS_STAT(D,   ast_rate_raise),          /* rate control raised xmit rate */
+AS_STAT(D,   ast_rate_drop),           /* rate control dropped xmit rate */
+AS_STAT(D,   ast_ant_defswitch),       /* rx/default antenna switches */
+AS_STAT(D,   ast_ant_txswitch)         /* tx antenna switches */
+};
+
+/* Bounds between SS, NS, IS and AS stats in stats array */
+static int bounds[4];
+
+#define WL_LEN 6
+/* Bitmasks for logged and error items */
+static uint32_t watch_items[WL_LEN];
+static uint32_t misc_items[WL_LEN];
+
+
+static const char *config_keys[] =
+{
+       "Interface",
+       "IgnoreSelected",
+       "Source",
+       "WatchAdd",
+       "WatchRemove",
+       "WatchSet",
+       "MiscAdd",
+       "MiscRemove",
+       "MiscSet"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static ignorelist_t *ignorelist = NULL;
+
+static int use_sysfs = 1;
+static int init_state = 0;
+
+static inline int item_watched(int i)
+{
+       assert (i >= 0);
+       assert (i < ((STATIC_ARRAY_SIZE (watch_items) + 1) * 32));
+       return watch_items[i / 32] & FLAG (i);
+}
+
+static inline int item_summed(int i)
+{
+       assert (i >= 0);
+       assert (i < ((STATIC_ARRAY_SIZE (misc_items) + 1) * 32));
+       return misc_items[i / 32] & FLAG (i);
+}
+
+static inline void watchlist_add (uint32_t *wl, int item)
+{
+       assert (item >= 0);
+       assert (item < ((WL_LEN + 1) * 32));
+       wl[item / 32] |= FLAG (item);
+}
+
+static inline void watchlist_remove (uint32_t *wl, int item)
+{
+       assert (item >= 0);
+       assert (item < ((WL_LEN + 1) * 32));
+       wl[item / 32] &= ~FLAG (item);
+}
+
+static inline void watchlist_set (uint32_t *wl, uint32_t val)
+{
+       int i;
+       for (i = 0; i < WL_LEN; i++)
+               wl[i] = val;
+}
+
+/* This is horribly inefficient, but it is called only during configuration */
+static int watchitem_find (const char *name)
+{
+       int max = STATIC_ARRAY_SIZE (specs);
+       int i;
+
+       for (i = 0; i < max; i++)
+               if (strcasecmp (name, specs[i].name) == 0)
+                       return i;
+
+       return -1;
+}
+
+
+/* Collectd hooks */
+
+/* We need init function called before madwifi_config */
+
+static int madwifi_real_init (void)
+{
+       int max = STATIC_ARRAY_SIZE (specs);
+       int i;
+
+       for (i = 0; i < STATIC_ARRAY_SIZE (bounds); i++)
+               bounds[i] = 0;
+
+       watchlist_set(watch_items, 0);
+       watchlist_set(misc_items, 0);
+
+       for (i = 0; i < max; i++)
+       {
+               bounds[specs[i].flags & SRC_MASK] = i;
+
+               if (specs[i].flags & LOG)
+                       watch_items[i / 32] |= FLAG (i);
+
+               if (specs[i].flags & SU)
+                       misc_items[i / 32] |= FLAG (i);
+       }
+
+       for (i = 0; i < STATIC_ARRAY_SIZE (bounds); i++)
+               bounds[i]++;
+
+       return (0);
+}
+
+static int madwifi_config (const char *key, const char *value)
+{
+       if (init_state != 1)
+               madwifi_real_init();
+       init_state = 1;
+
+       if (ignorelist == NULL)
+               ignorelist = ignorelist_create (/* invert = */ 1);
+
+       if (strcasecmp (key, "Interface") == 0)
+               ignorelist_add (ignorelist, value);
+
+       else if (strcasecmp (key, "IgnoreSelected") == 0)
+               ignorelist_set_invert (ignorelist, IS_TRUE (value) ? 0 : 1);
+
+       else if (strcasecmp (key, "Source") == 0)
+       {
+               if (strcasecmp (value, "ProcFS") == 0)
+                       use_sysfs = 0;
+               else if (strcasecmp (value, "SysFS") == 0)
+                       use_sysfs = 1;
+               else
+               {
+                       ERROR ("madwifi plugin: The argument of the `Source' "
+                                       "option must either be `SysFS' or "
+                                       "`ProcFS'.");
+                       return -1;
+               }
+       }
+
+       else if (strcasecmp (key, "WatchSet") == 0)
+       {
+               if (strcasecmp (value, "All") == 0)
+                       watchlist_set (watch_items, 0xFFFFFFFF);
+               else if (strcasecmp (value, "None") == 0)
+                       watchlist_set (watch_items, 0);
+               else return -1;
+       }
+
+       else if (strcasecmp (key, "WatchAdd") == 0)
+       {
+               int id = watchitem_find (value);
+
+               if (id < 0)
+                       return (-1);
+               else
+                       watchlist_add (watch_items, id);
+       }
+
+       else if (strcasecmp (key, "WatchRemove") == 0)
+       {
+               int id = watchitem_find (value);
+
+               if (id < 0)
+                       return (-1);
+               else
+                       watchlist_remove (watch_items, id);
+       }
+
+       else if (strcasecmp (key, "MiscSet") == 0)
+       {
+               if (strcasecmp (value, "All") == 0)
+                       watchlist_set (misc_items, 0xFFFFFFFF);
+               else if (strcasecmp (value, "None") == 0)
+                       watchlist_set (misc_items, 0);
+               else return -1;
+       }
+
+       else if (strcasecmp (key, "MiscAdd") == 0)
+       {
+               int id = watchitem_find (value);
+
+               if (id < 0)
+                       return (-1);
+               else
+                       watchlist_add (misc_items, id);
+       }
+
+       else if (strcasecmp (key, "MiscRemove") == 0)
+       {
+               int id = watchitem_find (value);
+
+               if (id < 0)
+                       return (-1);
+               else
+                       watchlist_remove (misc_items, id);
+       }
+
+       else
+               return (-1);
+
+       return (0);
+}
+
+
+static void submit (const char *dev, const char *type, const char *ti1,
+                       const char *ti2, value_t *val, int 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));
+
+       if ((ti1 != NULL) && (ti2 != NULL))
+               ssnprintf (vl.type_instance, sizeof (vl.type_instance), "%s-%s", ti1, ti2);
+       else if ((ti1 != NULL) && (ti2 == NULL))
+               sstrncpy (vl.type_instance, ti1, sizeof (vl.type_instance));
+
+       plugin_dispatch_values (&vl);
+}
+
+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);
+}
+
+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);
+}
+
+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);
+}
+
+static void submit_antx (const char *dev, const char *name,
+               u_int32_t *vals, int vals_num)
+{
+       char ti2[16];
+       int i;
+
+       for (i = 0; i < vals_num; i++)
+       {
+               if (vals[i] == 0)
+                       continue;
+
+               ssnprintf (ti2, sizeof (ti2), "%i", i);
+               submit_derive (dev, "ath_stat", name, ti2,
+                               (derive_t) vals[i]);
+       }
+}
+
+static inline void
+macaddr_to_str (char *buf, size_t bufsize, const uint8_t mac[IEEE80211_ADDR_LEN])
+{
+       ssnprintf (buf, bufsize, "%02x:%02x:%02x:%02x:%02x:%02x",
+               mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+}
+
+static void
+process_stat_struct (int which, const void *ptr, const char *dev, const char *mac,
+                        const char *type_name, const char *misc_name)
+{
+       uint32_t misc = 0;
+       int i;
+
+       assert (which >= 1);
+       assert (which < STATIC_ARRAY_SIZE (bounds));
+
+       for (i = bounds[which - 1]; i < bounds[which]; i++)
+       {
+               uint32_t val = *(uint32_t *)(((char *) ptr) + specs[i].offset) ;
+
+               if (item_watched (i) && (val != 0))
+                       submit_derive (dev, type_name, specs[i].name, mac, val);
+
+               if (item_summed (i))
+                       misc += val;
+       }
+       
+       if (misc != 0)
+               submit_derive (dev, type_name, misc_name, mac, misc);
+
+}
+
+static int
+process_athstats (int sk, const char *dev)
+{
+       struct ifreq ifr;
+       struct ath_stats stats;
+       int status;
+
+       sstrncpy (ifr.ifr_name, dev, sizeof (ifr.ifr_name));
+       ifr.ifr_data = (void *) &stats;
+       status = ioctl (sk, SIOCGATHSTATS, &ifr);
+       if (status < 0)
+       {
+               /* Silent, because not all interfaces support all ioctls. */
+               DEBUG ("madwifi plugin: Sending IO-control "
+                               "SIOCGATHSTATS to device %s "
+                               "failed with status %i.",
+                               dev, status);
+               return (status);
+       }
+
+       /* These stats are handled as a special case, because they are
+          eight values each */
+
+       if (item_watched (STAT_AST_ANT_RX))
+               submit_antx (dev, "ast_ant_rx", stats.ast_ant_rx,
+                               STATIC_ARRAY_SIZE (stats.ast_ant_rx));
+
+       if (item_watched (STAT_AST_ANT_TX))
+               submit_antx (dev, "ast_ant_tx", stats.ast_ant_tx,
+                               STATIC_ARRAY_SIZE (stats.ast_ant_tx));
+
+       /* All other ath statistics */
+       process_stat_struct (ATH_STAT, &stats, dev, NULL, "ath_stat", "ast_misc");
+       return (0);
+}
+
+static int
+process_80211stats (int sk, const char *dev)
+{
+       struct ifreq ifr;
+       struct ieee80211_stats stats;
+       int status;
+
+       sstrncpy (ifr.ifr_name, dev, sizeof (ifr.ifr_name));
+       ifr.ifr_data = (void *) &stats;
+       status = ioctl(sk, SIOCG80211STATS, &ifr);
+       if (status < 0)
+       {
+               /* Silent, because not all interfaces support all ioctls. */
+               DEBUG ("madwifi plugin: Sending IO-control "
+                               "SIOCG80211STATS to device %s "
+                               "failed with status %i.",
+                               dev, status);
+               return (status);
+       }
+
+       process_stat_struct (IFA_STAT, &stats, dev, NULL, "ath_stat", "is_misc");
+       return (0);
+}
+
+
+static int
+process_station (int sk, const char *dev, struct ieee80211req_sta_info *si)
+{
+       struct iwreq iwr;
+       static char mac[DATA_MAX_NAME_LEN];
+       struct ieee80211req_sta_stats stats;
+       const struct ieee80211_nodestats *ns = &stats.is_stats;
+       int status;
+
+       macaddr_to_str (mac, sizeof (mac), si->isi_macaddr);
+
+       if (item_watched (STAT_NODE_TX_RATE))
+               submit_gauge (dev, "node_tx_rate", mac, NULL,
+                       (si->isi_rates[si->isi_txrate] & IEEE80211_RATE_VAL) / 2);
+
+       if (item_watched (STAT_NODE_RSSI))
+               submit_gauge (dev, "node_rssi", mac, NULL, si->isi_rssi);
+
+       memset (&iwr, 0, sizeof (iwr));
+       sstrncpy(iwr.ifr_name, dev, sizeof (iwr.ifr_name));
+       iwr.u.data.pointer = (void *) &stats;
+       iwr.u.data.length = sizeof (stats);
+       memcpy(stats.is_u.macaddr, si->isi_macaddr, IEEE80211_ADDR_LEN);
+       status = ioctl(sk, IEEE80211_IOCTL_STA_STATS, &iwr);
+       if (status < 0)
+       {
+               /* Silent, because not all interfaces support all ioctls. */
+               DEBUG ("madwifi plugin: Sending IO-control "
+                               "IEEE80211_IOCTL_STA_STATS to device %s "
+                               "failed with status %i.",
+                               dev, status);
+               return (status);
+       }
+
+       /* These two stats are handled as a special case as they are
+          a pair of 64bit values */
+       if (item_watched (STAT_NODE_OCTETS))
+               submit_derive2 (dev, "node_octets", mac, NULL,
+                       ns->ns_rx_bytes, ns->ns_tx_bytes);
+
+       /* This stat is handled as a special case, because it is stored
+          as uin64_t, but we will ignore upper half */
+       if (item_watched (STAT_NS_RX_BEACONS))
+               submit_derive (dev, "node_stat", "ns_rx_beacons", mac,
+                       (ns->ns_rx_beacons & 0xFFFFFFFF));
+
+       /* All other node statistics */
+       process_stat_struct (NOD_STAT, ns, dev, mac, "node_stat", "ns_misc");
+       return (0);
+}
+
+static int
+process_stations (int sk, const char *dev)
+{
+       uint8_t buf[24*1024];
+       struct iwreq iwr;
+       uint8_t *cp;
+       int len, nodes;
+       int status;
+
+       memset (&iwr, 0, sizeof (iwr));
+       sstrncpy (iwr.ifr_name, dev, sizeof (iwr.ifr_name));
+       iwr.u.data.pointer = (void *) buf;
+       iwr.u.data.length = sizeof (buf);
+
+       status = ioctl (sk, IEEE80211_IOCTL_STA_INFO, &iwr);
+       if (status < 0)
+       {
+               /* Silent, because not all interfaces support all ioctls. */
+               DEBUG ("madwifi plugin: Sending IO-control "
+                               "IEEE80211_IOCTL_STA_INFO to device %s "
+                               "failed with status %i.",
+                               dev, status);
+               return (status);
+       }
+
+       len = iwr.u.data.length;
+
+       cp = buf;
+       nodes = 0;
+       while (len >= sizeof (struct ieee80211req_sta_info))
+       {
+               struct ieee80211req_sta_info *si = (void *) cp;
+               process_station(sk, dev, si);
+               cp += si->isi_len;
+               len -= si->isi_len;
+               nodes++;
+       }
+
+       if (item_watched (STAT_ATH_NODES))
+               submit_gauge (dev, "ath_nodes", NULL, NULL, nodes);
+       return (0);
+}
+
+static int
+process_device (int sk, const char *dev)
+{
+       int num_success = 0;
+       int status;
+
+       status = process_athstats (sk, dev);
+       if (status == 0)
+               num_success++;
+
+       status = process_80211stats (sk, dev);
+       if (status == 0)
+               num_success++;
+
+       status = process_stations (sk, dev);
+       if (status == 0)
+               num_success++;
+
+       return ((num_success == 0) ? -1 : 0);
+}
+
+static int
+check_devname (const char *dev)
+{
+       char buf[PATH_MAX];
+       char buf2[PATH_MAX];
+       int i;
+
+       if (dev[0] == '.')
+               return 0;
+       
+       ssnprintf (buf, sizeof (buf), "/sys/class/net/%s/device/driver", dev);
+       buf[sizeof (buf) - 1] = 0;
+
+       memset (buf2, 0, sizeof (buf2));
+       i = readlink (buf, buf2, sizeof (buf2) - 1);
+       if (i < 0)
+               return 0;
+
+       if (strstr (buf2, "/drivers/ath_") == NULL)
+               return 0;
+       return 1;
+}
+
+static int
+sysfs_iterate(int sk)
+{
+       struct dirent *de;
+       DIR *nets;
+       int status;
+       int num_success;
+       int num_fail;
+
+       nets = opendir ("/sys/class/net/");
+       if (nets == NULL)
+       {
+               WARNING ("madwifi plugin: opening /sys/class/net failed");
+               return (-1);
+       }
+
+       num_success = 0;
+       num_fail = 0;
+       while ((de = readdir (nets)))
+       {
+               if (check_devname (de->d_name) == 0)
+                       continue;
+
+               if (ignorelist_match (ignorelist, de->d_name) != 0)
+                       continue;
+
+               status = process_device (sk, de->d_name);
+               if (status != 0)
+               {
+                       ERROR ("madwifi plugin: Processing interface "
+                                       "%s failed.", de->d_name);
+                       num_fail++;
+               }
+               else
+               {
+                       num_success++;
+               }
+       } /* while (readdir) */
+
+       closedir(nets);
+
+       if ((num_success == 0) && (num_fail != 0))
+               return (-1);
+       return (0);
+}
+
+static int
+procfs_iterate(int sk)
+{
+       char buffer[1024];
+       char *device, *dummy;
+       FILE *fh;
+       int status;
+       int num_success;
+       int num_fail;
+       
+       if ((fh = fopen ("/proc/net/dev", "r")) == NULL)
+       {
+               WARNING ("madwifi plugin: opening /proc/net/dev failed");
+               return (-1);
+       }
+
+       num_success = 0;
+       num_fail = 0;
+       while (fgets (buffer, sizeof (buffer), fh) != NULL)
+       {
+               dummy = strchr(buffer, ':');
+               if (dummy == NULL)
+                       continue;
+               dummy[0] = 0;
+
+               device = buffer;
+               while (device[0] == ' ')
+                       device++;
+
+               if (device[0] == 0)
+                       continue;
+
+               if (ignorelist_match (ignorelist, device) != 0)
+                       continue;
+
+               status = process_device (sk, device);
+               if (status != 0)
+               {
+                       ERROR ("madwifi plugin: Processing interface "
+                                       "%s failed.", device);
+                       num_fail++;
+               }
+               else
+               {
+                       num_success++;
+               }
+       } /* while (fgets) */
+
+       fclose(fh);
+
+       if ((num_success == 0) && (num_fail != 0))
+               return (-1);
+       return 0;
+}
+
+static int madwifi_read (void)
+{
+       int rv;
+       int sk;
+
+       if (init_state == 0)
+               madwifi_real_init();
+       init_state = 2;
+
+       sk = socket(AF_INET, SOCK_DGRAM, 0);
+       if (sk < 0)
+               return (-1);
+
+
+/* procfs iteration is not safe because it does not check whether given
+   interface is madwifi interface and there are private ioctls used, which
+   may do something completely different on non-madwifi devices.   
+   Therefore, it is not used unless explicitly enabled (and should be used
+   together with ignorelist). */
+
+       if (use_sysfs)
+               rv = sysfs_iterate(sk);
+       else
+               rv = procfs_iterate(sk);
+
+       close(sk);
+
+       return rv;
+}
+
+void module_register (void)
+{
+       plugin_register_config ("madwifi", madwifi_config,
+                       config_keys, config_keys_num);
+
+       plugin_register_read ("madwifi", madwifi_read);
+}
diff --git a/src/madwifi.h b/src/madwifi.h
new file mode 100644 (file)
index 0000000..d6a5e35
--- /dev/null
@@ -0,0 +1,307 @@
+/**
+ * This file is compiled from several Madwifi header files.
+ * Original copyright is:
+ *
+ * Copyright (c) 2001 Atsushi Onoe
+ * Copyright (c) 2002-2005 Sam Leffler, Errno Consulting
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2 as published by the Free
+ * Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef MADWIFI_H
+#define MADWIFI_H
+
+#define        IEEE80211_ADDR_LEN              6               /* size of 802.11 address */
+#define        IEEE80211_RATE_VAL              0x7f
+#define        IEEE80211_RATE_SIZE             8               /* 802.11 standard */
+#define        IEEE80211_RATE_MAXSIZE          15              /* max rates we'll handle */
+
+
+/*
+ * Per/node (station) statistics available when operating as an AP.
+ */
+struct ieee80211_nodestats {
+       u_int32_t ns_rx_data;           /* rx data frames */
+       u_int32_t ns_rx_mgmt;           /* rx management frames */
+       u_int32_t ns_rx_ctrl;           /* rx control frames */
+       u_int32_t ns_rx_ucast;          /* rx unicast frames */
+       u_int32_t ns_rx_mcast;          /* rx multi/broadcast frames */
+       u_int64_t ns_rx_bytes;          /* rx data count (bytes) */
+       u_int64_t ns_rx_beacons;        /* rx beacon frames */
+       u_int32_t ns_rx_proberesp;      /* rx probe response frames */
+
+       u_int32_t ns_rx_dup;            /* rx discard because it's a dup */
+       u_int32_t ns_rx_noprivacy;      /* rx w/ wep but privacy off */
+       u_int32_t ns_rx_wepfail;        /* rx wep processing failed */
+       u_int32_t ns_rx_demicfail;      /* rx demic failed */
+       u_int32_t ns_rx_decap;          /* rx decapsulation failed */
+       u_int32_t ns_rx_defrag;         /* rx defragmentation failed */
+       u_int32_t ns_rx_disassoc;       /* rx disassociation */
+       u_int32_t ns_rx_deauth;         /* rx deauthentication */
+       u_int32_t ns_rx_decryptcrc;     /* rx decrypt failed on crc */
+       u_int32_t ns_rx_unauth;         /* rx on unauthorized port */
+       u_int32_t ns_rx_unencrypted;    /* rx unecrypted w/ privacy */
+
+       u_int32_t ns_tx_data;           /* tx data frames */
+       u_int32_t ns_tx_mgmt;           /* tx management frames */
+       u_int32_t ns_tx_ucast;          /* tx unicast frames */
+       u_int32_t ns_tx_mcast;          /* tx multi/broadcast frames */
+       u_int64_t ns_tx_bytes;          /* tx data count (bytes) */
+       u_int32_t ns_tx_probereq;       /* tx probe request frames */
+       u_int32_t ns_tx_uapsd;          /* tx on uapsd queue */
+
+       u_int32_t ns_tx_novlantag;      /* tx discard due to no tag */
+       u_int32_t ns_tx_vlanmismatch;   /* tx discard due to of bad tag */
+
+       u_int32_t ns_tx_eosplost;       /* uapsd EOSP retried out */
+
+       u_int32_t ns_ps_discard;        /* ps discard due to of age */
+
+       u_int32_t ns_uapsd_triggers;    /* uapsd triggers */
+
+       /* MIB-related state */
+       u_int32_t ns_tx_assoc;          /* [re]associations */
+       u_int32_t ns_tx_assoc_fail;     /* [re]association failures */
+       u_int32_t ns_tx_auth;           /* [re]authentications */
+       u_int32_t ns_tx_auth_fail;      /* [re]authentication failures*/
+       u_int32_t ns_tx_deauth;         /* deauthentications */
+       u_int32_t ns_tx_deauth_code;    /* last deauth reason */
+       u_int32_t ns_tx_disassoc;       /* disassociations */
+       u_int32_t ns_tx_disassoc_code;  /* last disassociation reason */
+       u_int32_t ns_psq_drops;         /* power save queue drops */
+};
+
+/*
+ * Summary statistics.
+ */
+struct ieee80211_stats {
+       u_int32_t is_rx_badversion;     /* rx frame with bad version */
+       u_int32_t is_rx_tooshort;       /* rx frame too short */
+       u_int32_t is_rx_wrongbss;       /* rx from wrong bssid */
+       u_int32_t is_rx_dup;            /* rx discard due to it's a dup */
+       u_int32_t is_rx_wrongdir;       /* rx w/ wrong direction */
+       u_int32_t is_rx_mcastecho;      /* rx discard due to of mcast echo */
+       u_int32_t is_rx_notassoc;       /* rx discard due to sta !assoc */
+       u_int32_t is_rx_noprivacy;      /* rx w/ wep but privacy off */
+       u_int32_t is_rx_unencrypted;    /* rx w/o wep and privacy on */
+       u_int32_t is_rx_wepfail;        /* rx wep processing failed */
+       u_int32_t is_rx_decap;          /* rx decapsulation failed */
+       u_int32_t is_rx_mgtdiscard;     /* rx discard mgt frames */
+       u_int32_t is_rx_ctl;            /* rx discard ctrl frames */
+       u_int32_t is_rx_beacon;         /* rx beacon frames */
+       u_int32_t is_rx_rstoobig;       /* rx rate set truncated */
+       u_int32_t is_rx_elem_missing;   /* rx required element missing*/
+       u_int32_t is_rx_elem_toobig;    /* rx element too big */
+       u_int32_t is_rx_elem_toosmall;  /* rx element too small */
+       u_int32_t is_rx_elem_unknown;   /* rx element unknown */
+       u_int32_t is_rx_badchan;        /* rx frame w/ invalid chan */
+       u_int32_t is_rx_chanmismatch;   /* rx frame chan mismatch */
+       u_int32_t is_rx_nodealloc;      /* rx frame dropped */
+       u_int32_t is_rx_ssidmismatch;   /* rx frame ssid mismatch  */
+       u_int32_t is_rx_auth_unsupported;/* rx w/ unsupported auth alg */
+       u_int32_t is_rx_auth_fail;      /* rx sta auth failure */
+       u_int32_t is_rx_auth_countermeasures;/* rx auth discard due to CM */
+       u_int32_t is_rx_assoc_bss;      /* rx assoc from wrong bssid */
+       u_int32_t is_rx_assoc_notauth;  /* rx assoc w/o auth */
+       u_int32_t is_rx_assoc_capmismatch;/* rx assoc w/ cap mismatch */
+       u_int32_t is_rx_assoc_norate;   /* rx assoc w/ no rate match */
+       u_int32_t is_rx_assoc_badwpaie; /* rx assoc w/ bad WPA IE */
+       u_int32_t is_rx_deauth;         /* rx deauthentication */
+       u_int32_t is_rx_disassoc;       /* rx disassociation */
+       u_int32_t is_rx_badsubtype;     /* rx frame w/ unknown subtype*/
+       u_int32_t is_rx_nobuf;          /* rx failed for lack of buf */
+       u_int32_t is_rx_decryptcrc;     /* rx decrypt failed on crc */
+       u_int32_t is_rx_ahdemo_mgt;     /* rx discard ahdemo mgt frame*/
+       u_int32_t is_rx_bad_auth;       /* rx bad auth request */
+       u_int32_t is_rx_unauth;         /* rx on unauthorized port */
+       u_int32_t is_rx_badkeyid;       /* rx w/ incorrect keyid */
+       u_int32_t is_rx_ccmpreplay;     /* rx seq# violation (CCMP) */
+       u_int32_t is_rx_ccmpformat;     /* rx format bad (CCMP) */
+       u_int32_t is_rx_ccmpmic;        /* rx MIC check failed (CCMP) */
+       u_int32_t is_rx_tkipreplay;     /* rx seq# violation (TKIP) */
+       u_int32_t is_rx_tkipformat;     /* rx format bad (TKIP) */
+       u_int32_t is_rx_tkipmic;        /* rx MIC check failed (TKIP) */
+       u_int32_t is_rx_tkipicv;        /* rx ICV check failed (TKIP) */
+       u_int32_t is_rx_badcipher;      /* rx failed due to of key type */
+       u_int32_t is_rx_nocipherctx;    /* rx failed due to key !setup */
+       u_int32_t is_rx_acl;            /* rx discard due to of acl policy */
+       u_int32_t is_rx_ffcnt;          /* rx fast frames */
+       u_int32_t is_rx_badathtnl;      /* driver key alloc failed */
+       u_int32_t is_tx_nobuf;          /* tx failed for lack of buf */
+       u_int32_t is_tx_nonode;         /* tx failed for no node */
+       u_int32_t is_tx_unknownmgt;     /* tx of unknown mgt frame */
+       u_int32_t is_tx_badcipher;      /* tx failed due to of key type */
+       u_int32_t is_tx_nodefkey;       /* tx failed due to no defkey */
+       u_int32_t is_tx_noheadroom;     /* tx failed due to no space */
+       u_int32_t is_tx_ffokcnt;        /* tx fast frames sent success */
+       u_int32_t is_tx_fferrcnt;       /* tx fast frames sent success */
+       u_int32_t is_scan_active;       /* active scans started */
+       u_int32_t is_scan_passive;      /* passive scans started */
+       u_int32_t is_node_timeout;      /* nodes timed out inactivity */
+       u_int32_t is_crypto_nomem;      /* no memory for crypto ctx */
+       u_int32_t is_crypto_tkip;       /* tkip crypto done in s/w */
+       u_int32_t is_crypto_tkipenmic;  /* tkip en-MIC done in s/w */
+       u_int32_t is_crypto_tkipdemic;  /* tkip de-MIC done in s/w */
+       u_int32_t is_crypto_tkipcm;     /* tkip counter measures */
+       u_int32_t is_crypto_ccmp;       /* ccmp crypto done in s/w */
+       u_int32_t is_crypto_wep;        /* wep crypto done in s/w */
+       u_int32_t is_crypto_setkey_cipher;/* cipher rejected key */
+       u_int32_t is_crypto_setkey_nokey;/* no key index for setkey */
+       u_int32_t is_crypto_delkey;     /* driver key delete failed */
+       u_int32_t is_crypto_badcipher;  /* unknown cipher */
+       u_int32_t is_crypto_nocipher;   /* cipher not available */
+       u_int32_t is_crypto_attachfail; /* cipher attach failed */
+       u_int32_t is_crypto_swfallback; /* cipher fallback to s/w */
+       u_int32_t is_crypto_keyfail;    /* driver key alloc failed */
+       u_int32_t is_crypto_enmicfail;  /* en-MIC failed */
+       u_int32_t is_ibss_capmismatch;  /* merge failed-cap mismatch */
+       u_int32_t is_ibss_norate;       /* merge failed-rate mismatch */
+       u_int32_t is_ps_unassoc;        /* ps-poll for unassoc. sta */
+       u_int32_t is_ps_badaid;         /* ps-poll w/ incorrect aid */
+       u_int32_t is_ps_qempty;         /* ps-poll w/ nothing to send */
+};
+
+/*
+ * Retrieve per-node statistics.
+ */
+struct ieee80211req_sta_stats {
+       union {
+               /* NB: explicitly force 64-bit alignment */
+               u_int8_t macaddr[IEEE80211_ADDR_LEN];
+               u_int64_t pad;
+       } is_u;
+       struct ieee80211_nodestats is_stats;
+};
+
+/*
+ * Station information block; the mac address is used
+ * to retrieve other data like stats, unicast key, etc.
+ */
+struct ieee80211req_sta_info {
+       u_int16_t isi_len;              /* length (mult of 4) */
+       u_int16_t isi_freq;             /* MHz */
+       u_int16_t isi_flags;            /* channel flags */
+       u_int16_t isi_state;            /* state flags */
+       u_int8_t isi_authmode;          /* authentication algorithm */
+       u_int8_t isi_rssi;
+       u_int16_t isi_capinfo;          /* capabilities */
+       u_int8_t isi_athflags;          /* Atheros capabilities */
+       u_int8_t isi_erp;               /* ERP element */
+       u_int8_t isi_macaddr[IEEE80211_ADDR_LEN];
+       u_int8_t isi_nrates;            /* negotiated rates */
+       u_int8_t isi_rates[IEEE80211_RATE_MAXSIZE];
+       u_int8_t isi_txrate;            /* index to isi_rates[] */
+       u_int16_t isi_ie_len;           /* IE length */
+       u_int16_t isi_associd;          /* assoc response */
+       u_int16_t isi_txpower;          /* current tx power */
+       u_int16_t isi_vlan;             /* vlan tag */
+       u_int16_t isi_txseqs[17];       /* seq to be transmitted */
+       u_int16_t isi_rxseqs[17];       /* seq previous for qos frames*/
+       u_int16_t isi_inact;            /* inactivity timer */
+       u_int8_t isi_uapsd;             /* UAPSD queues */
+       u_int8_t isi_opmode;            /* sta operating mode */
+
+       /* XXX frag state? */
+       /* variable length IE data */
+};
+
+
+struct ath_stats {
+       u_int32_t ast_watchdog;         /* device reset by watchdog */
+       u_int32_t ast_hardware;         /* fatal hardware error interrupts */
+       u_int32_t ast_bmiss;            /* beacon miss interrupts */
+       u_int32_t ast_rxorn;            /* rx overrun interrupts */
+       u_int32_t ast_rxeol;            /* rx eol interrupts */
+       u_int32_t ast_txurn;            /* tx underrun interrupts */
+       u_int32_t ast_mib;              /* mib interrupts */
+       u_int32_t ast_tx_packets;       /* packet sent on the interface */
+       u_int32_t ast_tx_mgmt;          /* management frames transmitted */
+       u_int32_t ast_tx_discard;       /* frames discarded prior to assoc */
+       u_int32_t ast_tx_invalid;       /* frames discarded due to is device gone */
+       u_int32_t ast_tx_qstop;         /* tx queue stopped because it's full */
+       u_int32_t ast_tx_encap;         /* tx encapsulation failed */
+       u_int32_t ast_tx_nonode;        /* tx failed due to of no node */
+       u_int32_t ast_tx_nobuf;         /* tx failed due to of no tx buffer (data) */
+       u_int32_t ast_tx_nobufmgt;      /* tx failed due to of no tx buffer (mgmt)*/
+       u_int32_t ast_tx_xretries;      /* tx failed due to of too many retries */
+       u_int32_t ast_tx_fifoerr;       /* tx failed due to of FIFO underrun */
+       u_int32_t ast_tx_filtered;      /* tx failed due to xmit filtered */
+       u_int32_t ast_tx_shortretry;    /* tx on-chip retries (short) */
+       u_int32_t ast_tx_longretry;     /* tx on-chip retries (long) */
+       u_int32_t ast_tx_badrate;       /* tx failed due to of bogus xmit rate */
+       u_int32_t ast_tx_noack;         /* tx frames with no ack marked */
+       u_int32_t ast_tx_rts;           /* tx frames with rts enabled */
+       u_int32_t ast_tx_cts;           /* tx frames with cts enabled */
+       u_int32_t ast_tx_shortpre;      /* tx frames with short preamble */
+       u_int32_t ast_tx_altrate;       /* tx frames with alternate rate */
+       u_int32_t ast_tx_protect;       /* tx frames with protection */
+       u_int32_t ast_rx_orn;           /* rx failed due to of desc overrun */
+       u_int32_t ast_rx_crcerr;        /* rx failed due to of bad CRC */
+       u_int32_t ast_rx_fifoerr;       /* rx failed due to of FIFO overrun */
+       u_int32_t ast_rx_badcrypt;      /* rx failed due to of decryption */
+       u_int32_t ast_rx_badmic;        /* rx failed due to of MIC failure */
+       u_int32_t ast_rx_phyerr;        /* rx PHY error summary count */
+       u_int32_t ast_rx_phy[32];       /* rx PHY error per-code counts */
+       u_int32_t ast_rx_tooshort;      /* rx discarded due to frame too short */
+       u_int32_t ast_rx_toobig;        /* rx discarded due to frame too large */
+       u_int32_t ast_rx_nobuf;         /* rx setup failed due to of no skbuff */
+       u_int32_t ast_rx_packets;       /* packet recv on the interface */
+       u_int32_t ast_rx_mgt;           /* management frames received */
+       u_int32_t ast_rx_ctl;           /* control frames received */
+       int8_t ast_tx_rssi;             /* tx rssi of last ack */
+       int8_t ast_rx_rssi;             /* rx rssi from histogram */
+       u_int32_t ast_be_xmit;          /* beacons transmitted */
+       u_int32_t ast_be_nobuf;         /* no skbuff available for beacon */
+       u_int32_t ast_per_cal;          /* periodic calibration calls */
+       u_int32_t ast_per_calfail;      /* periodic calibration failed */
+       u_int32_t ast_per_rfgain;       /* periodic calibration rfgain reset */
+       u_int32_t ast_rate_calls;       /* rate control checks */
+       u_int32_t ast_rate_raise;       /* rate control raised xmit rate */
+       u_int32_t ast_rate_drop;        /* rate control dropped xmit rate */
+       u_int32_t ast_ant_defswitch;    /* rx/default antenna switches */
+       u_int32_t ast_ant_txswitch;     /* tx antenna switches */
+       u_int32_t ast_ant_rx[8];        /* rx frames with antenna */
+       u_int32_t ast_ant_tx[8];        /* tx frames with antenna */
+};
+
+#define        SIOCGATHSTATS                   (SIOCDEVPRIVATE+0)
+#define        SIOCGATHDIAG                    (SIOCDEVPRIVATE+1)
+#define        SIOCGATHRADARSIG                (SIOCDEVPRIVATE+2)
+#define        SIOCGATHHALDIAG                 (SIOCDEVPRIVATE+3)
+#define        SIOCG80211STATS                 (SIOCDEVPRIVATE+2)
+/* NB: require in+out parameters so cannot use wireless extensions, yech */
+#define        IEEE80211_IOCTL_GETKEY          (SIOCDEVPRIVATE+3)
+#define        IEEE80211_IOCTL_GETWPAIE        (SIOCDEVPRIVATE+4)
+#define        IEEE80211_IOCTL_STA_STATS       (SIOCDEVPRIVATE+5)
+#define        IEEE80211_IOCTL_STA_INFO        (SIOCDEVPRIVATE+6)
+#define        SIOC80211IFCREATE               (SIOCDEVPRIVATE+7)
+#define        SIOC80211IFDESTROY              (SIOCDEVPRIVATE+8)
+#define        IEEE80211_IOCTL_SCAN_RESULTS    (SIOCDEVPRIVATE+9)
+
+
+#endif
diff --git a/src/match_empty_counter.c b/src/match_empty_counter.c
new file mode 100644 (file)
index 0000000..1ab445a
--- /dev/null
@@ -0,0 +1,116 @@
+/**
+ * collectd - src/match_empty_counter.c
+ * Copyright (C) 2009  Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "utils_cache.h"
+#include "filter_chain.h"
+
+/*
+ * private data types
+ */
+struct mec_match_s;
+typedef struct mec_match_s mec_match_t;
+struct mec_match_s
+{
+  int dummy;
+};
+
+/*
+ * internal helper functions
+ */
+static int mec_create (const oconfig_item_t *ci, void **user_data) /* {{{ */
+{
+  mec_match_t *m;
+
+  m = (mec_match_t *) malloc (sizeof (*m));
+  if (m == NULL)
+  {
+    ERROR ("mec_create: malloc failed.");
+    return (-ENOMEM);
+  }
+  memset (m, 0, sizeof (*m));
+
+  if (ci->children_num != 0)
+  {
+    ERROR ("empty_counter match: This match does not take any additional "
+        "configuration.");
+  }
+
+  *user_data = m;
+  return (0);
+} /* }}} int mec_create */
+
+static int mec_destroy (void **user_data) /* {{{ */
+{
+  if (user_data != NULL)
+  {
+    sfree (*user_data);
+  }
+
+  return (0);
+} /* }}} int mec_destroy */
+
+static int mec_match (const data_set_t __attribute__((unused)) *ds, /* {{{ */
+    const value_list_t *vl,
+    notification_meta_t __attribute__((unused)) **meta, void **user_data)
+{
+  int num_counters;
+  int num_empty;
+  int i;
+
+  if ((user_data == NULL) || (*user_data == NULL))
+    return (-1);
+
+
+  num_counters = 0;
+  num_empty = 0;
+
+  for (i = 0; i < ds->ds_num; i++)
+  {
+    if (ds->ds[i].type != DS_TYPE_COUNTER)
+      continue;
+
+    num_counters++;
+    if (vl->values[i].counter == 0)
+      num_empty++;
+  }
+
+  if (num_counters == 0)
+    return (FC_MATCH_NO_MATCH);
+  else if (num_counters == num_empty)
+    return (FC_MATCH_MATCHES);
+  else
+    return (FC_MATCH_NO_MATCH);
+} /* }}} int mec_match */
+
+void module_register (void)
+{
+  match_proc_t mproc;
+
+  memset (&mproc, 0, sizeof (mproc));
+  mproc.create  = mec_create;
+  mproc.destroy = mec_destroy;
+  mproc.match   = mec_match;
+  fc_register_match ("empty_counter", mproc);
+} /* module_register */
+
+/* vim: set sw=2 sts=2 tw=78 et fdm=marker : */
diff --git a/src/match_hashed.c b/src/match_hashed.c
new file mode 100644 (file)
index 0000000..ee3101a
--- /dev/null
@@ -0,0 +1,184 @@
+/**
+ * collectd - src/match_hashed.c
+ * Copyright (C) 2009  Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "utils_cache.h"
+#include "filter_chain.h"
+
+/*
+ * private data types
+ */
+struct mh_hash_match_s
+{
+  uint32_t match;
+  uint32_t total;
+};
+typedef struct mh_hash_match_s mh_hash_match_t;
+
+struct mh_match_s;
+typedef struct mh_match_s mh_match_t;
+struct mh_match_s
+{
+  mh_hash_match_t *matches;
+  size_t           matches_num;
+};
+
+/*
+ * internal helper functions
+ */
+static int mh_config_match (const oconfig_item_t *ci, /* {{{ */
+    mh_match_t *m)
+{
+  mh_hash_match_t *tmp;
+
+  if ((ci->values_num != 2)
+      || (ci->values[0].type != OCONFIG_TYPE_NUMBER)
+      || (ci->values[1].type != OCONFIG_TYPE_NUMBER))
+  {
+    ERROR ("hashed match: The `Match' option requires "
+        "exactly two numeric arguments.");
+    return (-1);
+  }
+
+  if ((ci->values[0].value.number < 0)
+      || (ci->values[1].value.number < 0))
+  {
+    ERROR ("hashed match: The arguments of the `Match' "
+        "option must be positive.");
+    return (-1);
+  }
+
+  tmp = realloc (m->matches, sizeof (*tmp) * (m->matches_num + 1));
+  if (tmp == NULL)
+  {
+    ERROR ("hashed match: realloc failed.");
+    return (-1);
+  }
+  m->matches = tmp;
+  tmp = m->matches + m->matches_num;
+
+  tmp->match = (uint32_t) (ci->values[0].value.number + .5);
+  tmp->total = (uint32_t) (ci->values[1].value.number + .5);
+
+  if (tmp->match >= tmp->total)
+  {
+    ERROR ("hashed match: The first argument of the `Match' option "
+        "must be smaller than the second argument.");
+    return (-1);
+  }
+  assert (tmp->total != 0);
+
+  m->matches_num++;
+  return (0);
+} /* }}} int mh_config_match */
+
+static int mh_create (const oconfig_item_t *ci, void **user_data) /* {{{ */
+{
+  mh_match_t *m;
+  int i;
+
+  m = (mh_match_t *) malloc (sizeof (*m));
+  if (m == NULL)
+  {
+    ERROR ("mh_create: malloc failed.");
+    return (-ENOMEM);
+  }
+  memset (m, 0, sizeof (*m));
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Match", child->key) == 0)
+      mh_config_match (child, m);
+    else
+      ERROR ("hashed match: No such config option: %s", child->key);
+  }
+
+  if (m->matches_num == 0)
+  {
+    sfree (m->matches);
+    sfree (m);
+    ERROR ("hashed match: No matches were configured. Not creating match.");
+    return (-1);
+  }
+
+  *user_data = m;
+  return (0);
+} /* }}} int mh_create */
+
+static int mh_destroy (void **user_data) /* {{{ */
+{
+  mh_match_t *mh;
+
+  if ((user_data == NULL) || (*user_data == NULL))
+    return (0);
+
+  mh = *user_data;
+  sfree (mh->matches);
+  sfree (mh);
+
+  return (0);
+} /* }}} int mh_destroy */
+
+static int mh_match (const data_set_t __attribute__((unused)) *ds, /* {{{ */
+    const value_list_t *vl,
+    notification_meta_t __attribute__((unused)) **meta, void **user_data)
+{
+  mh_match_t *m;
+  uint32_t hash_val;
+  const char *host_ptr;
+  size_t i;
+
+  if ((user_data == NULL) || (*user_data == NULL))
+    return (-1);
+
+  m = *user_data;
+
+  hash_val = 0;
+
+  for (host_ptr = vl->host; *host_ptr != 0; host_ptr++)
+  {
+    /* 2184401929 is some appropriately sized prime number. */
+    hash_val = (hash_val * UINT32_C (2184401929)) + ((uint32_t) *host_ptr);
+  }
+  DEBUG ("hashed match: host = %s; hash_val = %"PRIu32";", vl->host, hash_val);
+
+  for (i = 0; i < m->matches_num; i++)
+    if ((hash_val % m->matches[i].total) == m->matches[i].match)
+      return (FC_MATCH_MATCHES);
+
+  return (FC_MATCH_NO_MATCH);
+} /* }}} int mh_match */
+
+void module_register (void)
+{
+  match_proc_t mproc;
+
+  memset (&mproc, 0, sizeof (mproc));
+  mproc.create  = mh_create;
+  mproc.destroy = mh_destroy;
+  mproc.match   = mh_match;
+  fc_register_match ("hashed", mproc);
+} /* module_register */
+
+/* vim: set sw=2 sts=2 tw=78 et fdm=marker : */
diff --git a/src/match_regex.c b/src/match_regex.c
new file mode 100644 (file)
index 0000000..1defc18
--- /dev/null
@@ -0,0 +1,313 @@
+/**
+ * collectd - src/match_regex.c
+ * Copyright (C) 2008  Sebastian Harl
+ * Copyright (C) 2008  Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Sebastian Harl <sh at tokkee.org>
+ *   Florian Forster <octo at verplant.org>
+ **/
+
+/*
+ * This module allows to filter and rewrite value lists based on
+ * Perl-compatible regular expressions.
+ */
+
+#include "collectd.h"
+#include "filter_chain.h"
+
+#include <sys/types.h>
+#include <regex.h>
+
+#define log_err(...) ERROR ("`regex' match: " __VA_ARGS__)
+#define log_warn(...) WARNING ("`regex' match: " __VA_ARGS__)
+
+/*
+ * private data types
+ */
+
+struct mr_regex_s;
+typedef struct mr_regex_s mr_regex_t;
+struct mr_regex_s
+{
+       regex_t re;
+       char *re_str;
+
+       mr_regex_t *next;
+};
+
+struct mr_match_s;
+typedef struct mr_match_s mr_match_t;
+struct mr_match_s
+{
+       mr_regex_t *host;
+       mr_regex_t *plugin;
+       mr_regex_t *plugin_instance;
+       mr_regex_t *type;
+       mr_regex_t *type_instance;
+       _Bool invert;
+};
+
+/*
+ * internal helper functions
+ */
+static void mr_free_regex (mr_regex_t *r) /* {{{ */
+{
+       if (r == NULL)
+               return;
+
+       regfree (&r->re);
+       memset (&r->re, 0, sizeof (r->re));
+       free (r->re_str);
+
+       if (r->next != NULL)
+               mr_free_regex (r->next);
+} /* }}} void mr_free_regex */
+
+static void mr_free_match (mr_match_t *m) /* {{{ */
+{
+       if (m == NULL)
+               return;
+
+       mr_free_regex (m->host);
+       mr_free_regex (m->plugin);
+       mr_free_regex (m->plugin_instance);
+       mr_free_regex (m->type);
+       mr_free_regex (m->type_instance);
+
+       free (m);
+} /* }}} void mr_free_match */
+
+static int mr_match_regexen (mr_regex_t *re_head, /* {{{ */
+               const char *string)
+{
+       mr_regex_t *re;
+
+       if (re_head == NULL)
+               return (FC_MATCH_MATCHES);
+
+       for (re = re_head; re != NULL; re = re->next)
+       {
+               int status;
+
+               status = regexec (&re->re, string,
+                               /* nmatch = */ 0, /* pmatch = */ NULL,
+                               /* eflags = */ 0);
+               if (status == 0)
+               {
+                       DEBUG ("regex match: Regular expression `%s' matches `%s'.",
+                                       re->re_str, string);
+               }
+               else
+               {
+                       DEBUG ("regex match: Regular expression `%s' does not match `%s'.",
+                                       re->re_str, string);
+                       return (FC_MATCH_NO_MATCH);
+               }
+
+       }
+
+       return (FC_MATCH_MATCHES);
+} /* }}} int mr_match_regexen */
+
+static int mr_config_add_regex (mr_regex_t **re_head, /* {{{ */
+               oconfig_item_t *ci)
+{
+       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 = (mr_regex_t *) malloc (sizeof (*re));
+       if (re == NULL)
+       {
+               log_err ("mr_config_add_regex: malloc failed.");
+               return (-1);
+       }
+       memset (re, 0, sizeof (*re));
+       re->next = NULL;
+
+       re->re_str = strdup (ci->values[0].value.string);
+       if (re->re_str == NULL)
+       {
+               free (re);
+               log_err ("mr_config_add_regex: strdup failed.");
+               return (-1);
+       }
+
+       status = regcomp (&re->re, re->re_str, REG_EXTENDED | REG_NOSUB);
+       if (status != 0)
+       {
+               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, errmsg);
+               free (re->re_str);
+               free (re);
+               return (-1);
+       }
+
+       if (*re_head == NULL)
+       {
+               *re_head = re;
+       }
+       else
+       {
+               mr_regex_t *ptr;
+
+               ptr = *re_head;
+               while (ptr->next != NULL)
+                       ptr = ptr->next;
+
+               ptr->next = re;
+       }
+
+       return (0);
+} /* }}} int mr_config_add_regex */
+
+static int mr_create (const oconfig_item_t *ci, void **user_data) /* {{{ */
+{
+       mr_match_t *m;
+       int status;
+       int i;
+
+       m = (mr_match_t *) malloc (sizeof (*m));
+       if (m == NULL)
+       {
+               log_err ("mr_create: malloc failed.");
+               return (-ENOMEM);
+       }
+       memset (m, 0, sizeof (*m));
+       
+       m->invert = 0;
+
+       status = 0;
+       for (i = 0; i < ci->children_num; i++)
+       {
+               oconfig_item_t *child = ci->children + i;
+
+               if ((strcasecmp ("Host", child->key) == 0)
+                               || (strcasecmp ("Hostname", child->key) == 0))
+                       status = mr_config_add_regex (&m->host, child);
+               else if (strcasecmp ("Plugin", child->key) == 0)
+                       status = mr_config_add_regex (&m->plugin, child);
+               else if (strcasecmp ("PluginInstance", child->key) == 0)
+                       status = mr_config_add_regex (&m->plugin_instance, child);
+               else if (strcasecmp ("Type", child->key) == 0)
+                       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 ("Invert", child->key) == 0)
+                       status = cf_util_get_boolean(child, &m->invert);
+               else
+               {
+                       log_err ("The `%s' configuration option is not understood and "
+                                       "will be ignored.", child->key);
+                       status = 0;
+               }
+
+               if (status != 0)
+                       break;
+       }
+
+       /* Additional sanity-checking */
+       while (status == 0)
+       {
+               if ((m->host == NULL)
+                               && (m->plugin == NULL)
+                               && (m->plugin_instance == NULL)
+                               && (m->type == NULL)
+                               && (m->type_instance == NULL))
+               {
+                       log_err ("No (valid) regular expressions have been configured. "
+                                       "This match will be ignored.");
+                       status = -1;
+               }
+
+               break;
+       }
+
+       if (status != 0)
+       {
+               mr_free_match (m);
+               return (status);
+       }
+
+       *user_data = m;
+       return (0);
+} /* }}} int mr_create */
+
+static int mr_destroy (void **user_data) /* {{{ */
+{
+       if ((user_data != NULL) && (*user_data != NULL))
+               mr_free_match (*user_data);
+       return (0);
+} /* }}} int mr_destroy */
+
+static int mr_match (const data_set_t __attribute__((unused)) *ds, /* {{{ */
+               const value_list_t *vl,
+               notification_meta_t __attribute__((unused)) **meta,
+               void **user_data)
+{
+       mr_match_t *m;
+       int match_value = FC_MATCH_MATCHES;
+       int nomatch_value = FC_MATCH_NO_MATCH;
+
+       if ((user_data == NULL) || (*user_data == NULL))
+               return (-1);
+
+       m = *user_data;
+
+       if (m->invert)
+       {
+               match_value = FC_MATCH_NO_MATCH;
+               nomatch_value = FC_MATCH_MATCHES;
+       }
+
+       if (mr_match_regexen (m->host, vl->host) == FC_MATCH_NO_MATCH)
+               return (nomatch_value);
+       if (mr_match_regexen (m->plugin, vl->plugin) == FC_MATCH_NO_MATCH)
+               return (nomatch_value);
+       if (mr_match_regexen (m->plugin_instance,
+                               vl->plugin_instance) == FC_MATCH_NO_MATCH)
+               return (nomatch_value);
+       if (mr_match_regexen (m->type, vl->type) == FC_MATCH_NO_MATCH)
+               return (nomatch_value);
+       if (mr_match_regexen (m->type_instance,
+                               vl->type_instance) == FC_MATCH_NO_MATCH)
+               return (nomatch_value);
+
+       return (match_value);
+} /* }}} int mr_match */
+
+void module_register (void)
+{
+       match_proc_t mproc;
+
+       memset (&mproc, 0, sizeof (mproc));
+       mproc.create  = mr_create;
+       mproc.destroy = mr_destroy;
+       mproc.match   = mr_match;
+       fc_register_match ("regex", mproc);
+} /* module_register */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab fdm=marker : */
+
diff --git a/src/match_timediff.c b/src/match_timediff.c
new file mode 100644 (file)
index 0000000..2e27415
--- /dev/null
@@ -0,0 +1,153 @@
+/**
+ * collectd - src/match_timediff.c
+ * Copyright (C) 2008,2009  Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "utils_cache.h"
+#include "filter_chain.h"
+
+#define SATISFY_ALL 0
+#define SATISFY_ANY 1
+
+/*
+ * private data types
+ */
+struct mt_match_s;
+typedef struct mt_match_s mt_match_t;
+struct mt_match_s
+{
+  cdtime_t future;
+  cdtime_t past;
+};
+
+/*
+ * internal helper functions
+ */
+static int mt_create (const oconfig_item_t *ci, void **user_data) /* {{{ */
+{
+  mt_match_t *m;
+  int status;
+  int i;
+
+  m = (mt_match_t *) malloc (sizeof (*m));
+  if (m == NULL)
+  {
+    ERROR ("mt_create: malloc failed.");
+    return (-ENOMEM);
+  }
+  memset (m, 0, sizeof (*m));
+
+  m->future = 0;
+  m->past = 0;
+
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Future", child->key) == 0)
+      status = cf_util_get_cdtime (child, &m->future);
+    else if (strcasecmp ("Past", child->key) == 0)
+      status = cf_util_get_cdtime (child, &m->past);
+    else
+    {
+      ERROR ("timediff match: The `%s' configuration option is not "
+          "understood and will be ignored.", child->key);
+      status = 0;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  /* Additional sanity-checking */
+  while (status == 0)
+  {
+    if ((m->future == 0) && (m->past == 0))
+    {
+      ERROR ("timediff match: Either `Future' or `Past' must be configured. "
+          "This match will be ignored.");
+      status = -1;
+    }
+
+    break;
+  }
+
+  if (status != 0)
+  {
+    free (m);
+    return (status);
+  }
+
+  *user_data = m;
+  return (0);
+} /* }}} int mt_create */
+
+static int mt_destroy (void **user_data) /* {{{ */
+{
+  if (user_data != NULL)
+  {
+    sfree (*user_data);
+  }
+
+  return (0);
+} /* }}} int mt_destroy */
+
+static int mt_match (const data_set_t __attribute__((unused)) *ds, /* {{{ */
+    const value_list_t *vl,
+    notification_meta_t __attribute__((unused)) **meta, void **user_data)
+{
+  mt_match_t *m;
+  cdtime_t now;
+
+  if ((user_data == NULL) || (*user_data == NULL))
+    return (-1);
+
+  m = *user_data;
+  now = cdtime ();
+
+  if (m->future != 0)
+  {
+    if (vl->time >= (now + m->future))
+      return (FC_MATCH_MATCHES);
+  }
+
+  if (m->past != 0)
+  {
+    if (vl->time <= (now - m->past))
+      return (FC_MATCH_MATCHES);
+  }
+
+  return (FC_MATCH_NO_MATCH);
+} /* }}} int mt_match */
+
+void module_register (void)
+{
+  match_proc_t mproc;
+
+  memset (&mproc, 0, sizeof (mproc));
+  mproc.create  = mt_create;
+  mproc.destroy = mt_destroy;
+  mproc.match   = mt_match;
+  fc_register_match ("timediff", mproc);
+} /* module_register */
+
+/* vim: set sw=2 sts=2 tw=78 et fdm=marker : */
diff --git a/src/match_value.c b/src/match_value.c
new file mode 100644 (file)
index 0000000..ae6282c
--- /dev/null
@@ -0,0 +1,358 @@
+/**
+ * collectd - src/match_value.c
+ * Copyright (C) 2008  Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian Forster <octo at verplant.org>
+ **/
+
+/*
+ * This module allows to filter and rewrite value lists based on
+ * Perl-compatible regular expressions.
+ */
+
+#include "collectd.h"
+#include "common.h"
+#include "utils_cache.h"
+#include "filter_chain.h"
+
+#define SATISFY_ALL 0
+#define SATISFY_ANY 1
+
+/*
+ * private data types
+ */
+struct mv_match_s;
+typedef struct mv_match_s mv_match_t;
+struct mv_match_s
+{
+  gauge_t min;
+  gauge_t max;
+  int invert;
+  int satisfy;
+
+  char **data_sources;
+  size_t data_sources_num;
+};
+
+/*
+ * internal helper functions
+ */
+static void mv_free_match (mv_match_t *m) /* {{{ */
+{
+  int i;
+  
+  if (m == NULL)
+    return;
+
+  if (m->data_sources != NULL)
+  {
+    for (i = 0; i < m->data_sources_num; ++i)
+      free(m->data_sources[i]);
+    free(m->data_sources);
+  }
+  
+  free (m);
+} /* }}} void mv_free_match */
+
+static int mv_config_add_satisfy (mv_match_t *m, /* {{{ */
+    oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    ERROR ("`value' match: `%s' needs exactly one string argument.",
+        ci->key);
+    return (-1);
+  }
+
+  if (strcasecmp ("All", ci->values[0].value.string) == 0)
+    m->satisfy = SATISFY_ALL;
+  else if (strcasecmp ("Any", ci->values[0].value.string) == 0)
+    m->satisfy = SATISFY_ANY;
+  else
+  {
+    ERROR ("`value' match: Passing `%s' to the `%s' option is invalid. "
+        "The argument must either be `All' or `Any'.",
+        ci->values[0].value.string, ci->key);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int mv_config_add_satisfy */
+
+static int mv_config_add_data_source (mv_match_t *m, /* {{{ */
+    oconfig_item_t *ci)
+{
+  size_t new_data_sources_num;
+  char **temp;
+  int i;
+
+  /* Check number of arbuments. */
+  if (ci->values_num < 1)
+  {
+    ERROR ("`value' match: `%s' needs at least one argument.",
+        ci->key);
+    return (-1);
+  }
+
+  /* Check type of arguments */
+  for (i = 0; i < ci->values_num; i++)
+  {
+    if (ci->values[i].type == OCONFIG_TYPE_STRING)
+      continue;
+
+    ERROR ("`value' match: `%s' accepts only string arguments "
+        "(argument %i is a %s).",
+        ci->key, i + 1,
+        (ci->values[i].type == OCONFIG_TYPE_BOOLEAN)
+        ? "truth value" : "number");
+    return (-1);
+  }
+
+  /* Allocate space for the char pointers */
+  new_data_sources_num = m->data_sources_num + ((size_t) ci->values_num);
+  temp = (char **) realloc (m->data_sources,
+      new_data_sources_num * sizeof (char *));
+  if (temp == NULL)
+  {
+    ERROR ("`value' match: realloc failed.");
+    return (-1);
+  }
+  m->data_sources = temp;
+
+  /* Copy the strings, allocating memory as needed. */
+  for (i = 0; i < ci->values_num; i++)
+  {
+    size_t j;
+
+    /* If we get here, there better be memory for us to write to. */
+    assert (m->data_sources_num < new_data_sources_num);
+
+    j = m->data_sources_num;
+    m->data_sources[j] = sstrdup (ci->values[i].value.string);
+    if (m->data_sources[j] == NULL)
+    {
+      ERROR ("`value' match: sstrdup failed.");
+      continue;
+    }
+    m->data_sources_num++;
+  }
+
+  return (0);
+} /* }}} int mv_config_add_data_source */
+
+static int mv_config_add_gauge (gauge_t *ret_value, /* {{{ */
+    oconfig_item_t *ci)
+{
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
+  {
+    ERROR ("`value' match: `%s' needs exactly one numeric argument.",
+        ci->key);
+    return (-1);
+  }
+
+  *ret_value = ci->values[0].value.number;
+
+  return (0);
+} /* }}} int mv_config_add_gauge */
+
+static int mv_config_add_boolean (int *ret_value, /* {{{ */
+    oconfig_item_t *ci)
+{
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN))
+  {
+    ERROR ("`value' match: `%s' needs exactly one boolean argument.",
+        ci->key);
+    return (-1);
+  }
+
+  if (ci->values[0].value.boolean)
+    *ret_value = 1;
+  else
+    *ret_value = 0;
+
+  return (0);
+} /* }}} int mv_config_add_boolean */
+
+static int mv_create (const oconfig_item_t *ci, void **user_data) /* {{{ */
+{
+  mv_match_t *m;
+  int status;
+  int i;
+
+  m = (mv_match_t *) malloc (sizeof (*m));
+  if (m == NULL)
+  {
+    ERROR ("mv_create: malloc failed.");
+    return (-ENOMEM);
+  }
+  memset (m, 0, sizeof (*m));
+
+  m->min = NAN;
+  m->max = NAN;
+  m->invert = 0;
+  m->satisfy = SATISFY_ALL;
+  m->data_sources = NULL;
+  m->data_sources_num = 0;
+
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Min", child->key) == 0)
+      status = mv_config_add_gauge (&m->min, child);
+    else if (strcasecmp ("Max", child->key) == 0)
+      status = mv_config_add_gauge (&m->max, child);
+    else if (strcasecmp ("Invert", child->key) == 0)
+      status = mv_config_add_boolean (&m->invert, child);
+    else if (strcasecmp ("Satisfy", child->key) == 0)
+      status = mv_config_add_satisfy (m, child);
+    else if (strcasecmp ("DataSource", child->key) == 0)
+      status = mv_config_add_data_source (m, child);
+    else
+    {
+      ERROR ("`value' match: The `%s' configuration option is not "
+          "understood and will be ignored.", child->key);
+      status = 0;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  /* Additional sanity-checking */
+  while (status == 0)
+  {
+    if (isnan (m->min) && isnan (m->max))
+    {
+      ERROR ("`value' match: Neither minimum nor maximum are defined. "
+          "This match will be ignored.");
+      status = -1;
+    }
+
+    break;
+  }
+
+  if (status != 0)
+  {
+    mv_free_match (m);
+    return (status);
+  }
+
+  *user_data = m;
+  return (0);
+} /* }}} int mv_create */
+
+static int mv_destroy (void **user_data) /* {{{ */
+{
+  if ((user_data != NULL) && (*user_data != NULL))
+    mv_free_match (*user_data);
+  return (0);
+} /* }}} int mv_destroy */
+
+static int mv_match (const data_set_t *ds, const value_list_t *vl, /* {{{ */
+    notification_meta_t __attribute__((unused)) **meta, void **user_data)
+{
+  mv_match_t *m;
+  gauge_t *values;
+  int status;
+  int i;
+
+  if ((user_data == NULL) || (*user_data == NULL))
+    return (-1);
+
+  m = *user_data;
+
+  values = uc_get_rate (ds, vl);
+  if (values == NULL)
+  {
+    ERROR ("`value' match: Retrieving the current rate from the cache "
+        "failed.");
+    return (-1);
+  }
+
+  status = FC_MATCH_NO_MATCH;
+
+  for (i = 0; i < ds->ds_num; i++)
+  {
+    int value_matches = 0;
+
+    /* Check if this data source is relevant. */
+    if (m->data_sources != NULL)
+    {
+      size_t j;
+
+      for (j = 0; j < m->data_sources_num; j++)
+        if (strcasecmp (ds->ds[i].name, m->data_sources[j]) == 0)
+          break;
+
+      /* No match, ignore this data source. */
+      if (j >=  m->data_sources_num)
+        continue;
+    }
+
+    DEBUG ("`value' match: current = %g; min = %g; max = %g; invert = %s;",
+        values[i], m->min, m->max,
+        m->invert ? "true" : "false");
+
+    if ((!isnan (m->min) && (values[i] < m->min))
+        || (!isnan (m->max) && (values[i] > m->max)))
+      value_matches = 0;
+    else
+      value_matches = 1;
+
+    if (m->invert)
+    {
+      if (value_matches)
+        value_matches = 0;
+      else
+        value_matches = 1;
+    }
+
+    if (value_matches != 0)
+    {
+      status = FC_MATCH_MATCHES;
+      if (m->satisfy == SATISFY_ANY)
+        break;
+    }
+    else if (value_matches == 0)
+    {
+      status = FC_MATCH_NO_MATCH;
+      if (m->satisfy == SATISFY_ALL)
+        break;
+    }
+  } /* for (i = 0; i < ds->ds_num; i++) */
+
+  free (values);
+  return (status);
+} /* }}} int mv_match */
+
+void module_register (void)
+{
+  match_proc_t mproc;
+
+  memset (&mproc, 0, sizeof (mproc));
+  mproc.create  = mv_create;
+  mproc.destroy = mv_destroy;
+  mproc.match   = mv_match;
+  fc_register_match ("value", mproc);
+} /* module_register */
+
+/* vim: set sw=2 sts=2 tw=78 et fdm=marker : */
+
diff --git a/src/mbmon.c b/src/mbmon.c
new file mode 100644 (file)
index 0000000..90226bb
--- /dev/null
@@ -0,0 +1,314 @@
+/**
+ * collectd - src/mbmon.c
+ * Copyright (C) 2006       Flavio Stanchina
+ * Copyright (C) 2006-2007  Florian octo Forster
+ * Based on the hddtemp plugin.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Flavio Stanchina <flavio at stanchina.net>
+ *   Florian Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+
+#include <netdb.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#define MBMON_DEF_HOST "127.0.0.1"
+#define MBMON_DEF_PORT "411" /* the default for Debian */
+
+static const char *config_keys[] =
+{
+       "Host",
+       "Port",
+       NULL
+};
+static int config_keys_num = 2;
+
+static char *mbmon_host = NULL;
+static char *mbmon_port = NULL;
+
+/*
+ * NAME
+ *  mbmon_query_daemon
+ *
+ * DESCRIPTION
+ * Connect to the mbmon daemon and receive data.
+ *
+ * ARGUMENTS:
+ *  `buffer'            The buffer where we put the received ascii string.
+ *  `buffer_size'       Size of the buffer
+ *
+ * RETURN VALUE:
+ *   >= 0 if ok, < 0 otherwise.
+ *
+ * NOTES:
+ *  Example of possible strings, as received from daemon:
+ *    TEMP0 : 27.0
+ *    TEMP1 : 31.0
+ *    TEMP2 : 29.5
+ *    FAN0  : 4411
+ *    FAN1  : 4470
+ *    FAN2  : 4963
+ *    VC0   :  +1.68
+ *    VC1   :  +1.73
+ *
+ * FIXME:
+ *  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 mbmon_query_daemon (char *buffer, int buffer_size)
+{
+       int fd;
+       ssize_t status;
+       int buffer_fill;
+
+       const char *host;
+       const char *port;
+
+       struct addrinfo  ai_hints;
+       struct addrinfo *ai_list, *ai_ptr;
+       int              ai_return;
+
+       memset (&ai_hints, '\0', sizeof (ai_hints));
+       ai_hints.ai_flags    = 0;
+#ifdef AI_ADDRCONFIG
+       ai_hints.ai_flags   |= AI_ADDRCONFIG;
+#endif
+       ai_hints.ai_family   = PF_UNSPEC;
+       ai_hints.ai_socktype = SOCK_STREAM;
+       ai_hints.ai_protocol = IPPROTO_TCP;
+
+       host = mbmon_host;
+       if (host == NULL)
+               host = MBMON_DEF_HOST;
+
+       port = mbmon_port;
+       if (port == NULL)
+               port = MBMON_DEF_PORT;
+
+       if ((ai_return = getaddrinfo (host, port, &ai_hints, &ai_list)) != 0)
+       {
+               char errbuf[1024];
+               ERROR ("mbmon: getaddrinfo (%s, %s): %s",
+                               host, port,
+                               (ai_return == EAI_SYSTEM)
+                               ? sstrerror (errno, errbuf, sizeof (errbuf))
+                               : gai_strerror (ai_return));
+               return (-1);
+       }
+
+       fd = -1;
+       for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
+       {
+               /* create our socket descriptor */
+               if ((fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol)) < 0)
+               {
+                       char errbuf[1024];
+                       ERROR ("mbmon: socket: %s",
+                                       sstrerror (errno, errbuf,
+                                               sizeof (errbuf)));
+                       continue;
+               }
+
+               /* connect to the mbmon daemon */
+               if (connect (fd, (struct sockaddr *) ai_ptr->ai_addr, ai_ptr->ai_addrlen))
+               {
+                       char errbuf[1024];
+                       INFO ("mbmon: connect (%s, %s): %s", host, port,
+                                       sstrerror (errno, errbuf,
+                                               sizeof (errbuf)));
+                       close (fd);
+                       fd = -1;
+                       continue;
+               }
+
+               /* A socket could be opened and connecting succeeded. We're
+                * done. */
+               break;
+       }
+
+       freeaddrinfo (ai_list);
+
+       if (fd < 0)
+       {
+               ERROR ("mbmon: Could not connect to daemon.");
+               return (-1);
+       }
+
+       /* receive data from the mbmon daemon */
+       memset (buffer, '\0', buffer_size);
+
+       buffer_fill = 0;
+       while ((status = read (fd, buffer + buffer_fill, buffer_size - buffer_fill)) != 0)
+       {
+               if (status == -1)
+               {
+                       char errbuf[1024];
+
+                       if ((errno == EAGAIN) || (errno == EINTR))
+                               continue;
+
+                       ERROR ("mbmon: Error reading from socket: %s",
+                                       sstrerror (errno, errbuf,
+                                               sizeof (errbuf)));
+                       close (fd);
+                       return (-1);
+               }
+               buffer_fill += status;
+
+               if (buffer_fill >= buffer_size)
+                       break;
+       }
+
+       if (buffer_fill >= buffer_size)
+       {
+               buffer[buffer_size - 1] = '\0';
+               WARNING ("mbmon: Message from mbmon has been truncated.");
+       }
+       else if (buffer_fill == 0)
+       {
+               WARNING ("mbmon: Peer has unexpectedly shut down the socket. "
+                               "Buffer: `%s'", buffer);
+               close (fd);
+               return (-1);
+       }
+
+       close (fd);
+       return (0);
+}
+
+static int mbmon_config (const char *key, const char *value)
+{
+       if (strcasecmp (key, "host") == 0)
+       {
+               if (mbmon_host != NULL)
+                       free (mbmon_host);
+               mbmon_host = strdup (value);
+       }
+       else if (strcasecmp (key, "port") == 0)
+       {
+               if (mbmon_port != NULL)
+                       free (mbmon_port);
+               mbmon_port = strdup (value);
+       }
+       else
+       {
+               return (-1);
+       }
+
+       return (0);
+}
+
+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_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));
+
+       plugin_dispatch_values (&vl);
+} /* void mbmon_submit */
+
+/* Trim trailing whitespace from a string. */
+static void trim_spaces (char *s)
+{
+       size_t l;
+
+       for (l = strlen (s) - 1; (l > 0) && isspace ((int) s[l]); l--)
+               s[l] = '\0';
+}
+
+static int mbmon_read (void)
+{
+       char buf[1024];
+       char *s, *t;
+
+       /* get data from daemon */
+       if (mbmon_query_daemon (buf, sizeof (buf)) < 0)
+               return (-1);
+
+       s = buf;
+       while ((t = strchr (s, ':')) != NULL)
+       {
+               double value;
+               char *nextc;
+
+               char *type;
+               char *inst;
+
+               *t++ = '\0';
+               trim_spaces (s);
+
+               value = strtod (t, &nextc);
+               if ((*nextc != '\n') && (*nextc != '\0'))
+               {
+                       ERROR ("mbmon: value for `%s' contains invalid characters: `%s'", s, t);
+                       break;
+               }
+
+               if (strncmp (s, "TEMP", 4) == 0)
+               {
+                       inst = s + 4;
+                       type = "temperature";
+               }
+               else if (strncmp (s, "FAN", 3) == 0)
+               {
+                       inst = s + 3;
+                       type = "fanspeed";
+               }
+               else if (strncmp (s, "V", 1) == 0)
+               {
+                       inst = s + 1;
+                       type = "voltage";
+               }
+               else
+               {
+                       continue;
+               }
+
+               mbmon_submit (type, inst, value);
+
+               if (*nextc == '\0')
+                       break;
+
+               s = nextc + 1;
+       }
+
+       return (0);
+} /* void mbmon_read */
+
+/* module_register
+   Register collectd plugin. */
+void module_register (void)
+{
+       plugin_register_config ("mbmon", mbmon_config, config_keys, config_keys_num);
+       plugin_register_read ("mbmon", mbmon_read);
+} /* void module_register */
diff --git a/src/memcachec.c b/src/memcachec.c
new file mode 100644 (file)
index 0000000..8f51e22
--- /dev/null
@@ -0,0 +1,536 @@
+/**
+ * collectd - src/memcachec.c
+ * Copyright (C) 2009       Doug MacEachern
+ * Copyright (C) 2006-2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Doug MacEachern <Doug.MacEachern at hyperic.com>
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+#include "utils_match.h"
+
+#include <libmemcached/memcached.h>
+
+/*
+ * Data types
+ */
+struct web_match_s;
+typedef struct web_match_s web_match_t;
+struct web_match_s /* {{{ */
+{
+  char *regex;
+  char *exclude_regex;
+  int dstype;
+  char *type;
+  char *instance;
+
+  cu_match_t *match;
+
+  web_match_t *next;
+}; /* }}} */
+
+struct web_page_s;
+typedef struct web_page_s web_page_t;
+struct web_page_s /* {{{ */
+{
+  char *instance;
+
+  char *server;
+  char *key;
+
+  memcached_st *memc;
+  char *buffer;
+
+  web_match_t *matches;
+
+  web_page_t *next;
+}; /* }}} */
+
+/*
+ * Global variables;
+ */
+static web_page_t *pages_g = NULL;
+
+/*
+ * Private functions
+ */
+static void cmc_web_match_free (web_match_t *wm) /* {{{ */
+{
+  if (wm == NULL)
+    return;
+
+  sfree (wm->regex);
+  sfree (wm->type);
+  sfree (wm->instance);
+  match_destroy (wm->match);
+  cmc_web_match_free (wm->next);
+  sfree (wm);
+} /* }}} void cmc_web_match_free */
+
+static void cmc_web_page_free (web_page_t *wp) /* {{{ */
+{
+  if (wp == NULL)
+    return;
+
+  if (wp->memc != NULL)
+    memcached_free(wp->memc);
+  wp->memc = NULL;
+
+  sfree (wp->instance);
+  sfree (wp->server);
+  sfree (wp->key);
+  sfree (wp->buffer);
+
+  cmc_web_match_free (wp->matches);
+  cmc_web_page_free (wp->next);
+  sfree (wp);
+} /* }}} void cmc_web_page_free */
+
+static int cmc_page_init_memc (web_page_t *wp) /* {{{ */
+{
+  memcached_server_st *server;
+
+  wp->memc = memcached_create(NULL);
+  if (wp->memc == NULL)
+  {
+    ERROR ("memcachec plugin: memcached_create failed.");
+    return (-1);
+  }
+
+  server = memcached_servers_parse (wp->server);
+  memcached_server_push (wp->memc, server);
+  memcached_server_list_free (server);
+
+  return (0);
+} /* }}} int cmc_page_init_memc */
+
+static int cmc_config_add_string (const char *name, char **dest, /* {{{ */
+    oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("memcachec plugin: `%s' needs exactly one string argument.", name);
+    return (-1);
+  }
+
+  sfree (*dest);
+  *dest = strdup (ci->values[0].value.string);
+  if (*dest == NULL)
+    return (-1);
+
+  return (0);
+} /* }}} int cmc_config_add_string */
+
+static int cmc_config_add_match_dstype (int *dstype_ret, /* {{{ */
+    oconfig_item_t *ci)
+{
+  int dstype;
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("memcachec plugin: `DSType' needs exactly one string argument.");
+    return (-1);
+  }
+
+  if (strncasecmp ("Gauge", ci->values[0].value.string,
+        strlen ("Gauge")) == 0)
+  {
+    dstype = UTILS_MATCH_DS_TYPE_GAUGE;
+    if (strcasecmp ("GaugeAverage", ci->values[0].value.string) == 0)
+      dstype |= UTILS_MATCH_CF_GAUGE_AVERAGE;
+    else if (strcasecmp ("GaugeMin", ci->values[0].value.string) == 0)
+      dstype |= UTILS_MATCH_CF_GAUGE_MIN;
+    else if (strcasecmp ("GaugeMax", ci->values[0].value.string) == 0)
+      dstype |= UTILS_MATCH_CF_GAUGE_MAX;
+    else if (strcasecmp ("GaugeLast", ci->values[0].value.string) == 0)
+      dstype |= UTILS_MATCH_CF_GAUGE_LAST;
+    else
+      dstype = 0;
+  }
+  else if (strncasecmp ("Counter", ci->values[0].value.string,
+        strlen ("Counter")) == 0)
+  {
+    dstype = UTILS_MATCH_DS_TYPE_COUNTER;
+    if (strcasecmp ("CounterSet", ci->values[0].value.string) == 0)
+      dstype |= UTILS_MATCH_CF_COUNTER_SET;
+    else if (strcasecmp ("CounterAdd", ci->values[0].value.string) == 0)
+      dstype |= UTILS_MATCH_CF_COUNTER_ADD;
+    else if (strcasecmp ("CounterInc", ci->values[0].value.string) == 0)
+      dstype |= UTILS_MATCH_CF_COUNTER_INC;
+    else
+      dstype = 0;
+  }
+  else
+  {
+    dstype = 0;
+  }
+
+  if (dstype == 0)
+  {
+    WARNING ("memcachec plugin: `%s' is not a valid argument to `DSType'.",
+       ci->values[0].value.string);
+    return (-1);
+  }
+
+  *dstype_ret = dstype;
+  return (0);
+} /* }}} int cmc_config_add_match_dstype */
+
+static int cmc_config_add_match (web_page_t *page, /* {{{ */
+    oconfig_item_t *ci)
+{
+  web_match_t *match;
+  int status;
+  int i;
+
+  if (ci->values_num != 0)
+  {
+    WARNING ("memcachec plugin: Ignoring arguments for the `Match' block.");
+  }
+
+  match = (web_match_t *) malloc (sizeof (*match));
+  if (match == NULL)
+  {
+    ERROR ("memcachec plugin: malloc failed.");
+    return (-1);
+  }
+  memset (match, 0, sizeof (*match));
+
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Regex", child->key) == 0)
+      status = cmc_config_add_string ("Regex", &match->regex, child);
+    else if (strcasecmp ("ExcludeRegex", child->key) == 0)
+      status = cmc_config_add_string ("ExcludeRegex", &match->exclude_regex, child);
+    else if (strcasecmp ("DSType", child->key) == 0)
+      status = cmc_config_add_match_dstype (&match->dstype, child);
+    else if (strcasecmp ("Type", child->key) == 0)
+      status = cmc_config_add_string ("Type", &match->type, child);
+    else if (strcasecmp ("Instance", child->key) == 0)
+      status = cmc_config_add_string ("Instance", &match->instance, child);
+    else
+    {
+      WARNING ("memcachec plugin: Option `%s' not allowed here.", child->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  while (status == 0)
+  {
+    if (match->regex == NULL)
+    {
+      WARNING ("memcachec plugin: `Regex' missing in `Match' block.");
+      status = -1;
+    }
+
+    if (match->type == NULL)
+    {
+      WARNING ("memcachec plugin: `Type' missing in `Match' block.");
+      status = -1;
+    }
+
+    if (match->dstype == 0)
+    {
+      WARNING ("memcachec plugin: `DSType' missing in `Match' block.");
+      status = -1;
+    }
+
+    break;
+  } /* while (status == 0) */
+
+  if (status != 0)
+    return (status);
+
+  match->match = match_create_simple (match->regex, match->exclude_regex,
+      match->dstype);
+  if (match->match == NULL)
+  {
+    ERROR ("memcachec plugin: tail_match_add_match_simple failed.");
+    cmc_web_match_free (match);
+    return (-1);
+  }
+  else
+  {
+    web_match_t *prev;
+
+    prev = page->matches;
+    while ((prev != NULL) && (prev->next != NULL))
+      prev = prev->next;
+
+    if (prev == NULL)
+      page->matches = match;
+    else
+      prev->next = match;
+  }
+
+  return (0);
+} /* }}} int cmc_config_add_match */
+
+static int cmc_config_add_page (oconfig_item_t *ci) /* {{{ */
+{
+  web_page_t *page;
+  int status;
+  int i;
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("memcachec plugin: `Page' blocks need exactly one string argument.");
+    return (-1);
+  }
+
+  page = (web_page_t *) malloc (sizeof (*page));
+  if (page == NULL)
+  {
+    ERROR ("memcachec plugin: malloc failed.");
+    return (-1);
+  }
+  memset (page, 0, sizeof (*page));
+  page->server = NULL;
+  page->key = NULL;
+
+  page->instance = strdup (ci->values[0].value.string);
+  if (page->instance == NULL)
+  {
+    ERROR ("memcachec plugin: strdup failed.");
+    sfree (page);
+    return (-1);
+  }
+
+  /* Process all children */
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Server", child->key) == 0)
+      status = cmc_config_add_string ("Server", &page->server, child);
+    if (strcasecmp ("Key", child->key) == 0)
+      status = cmc_config_add_string ("Key", &page->key, child);
+    else if (strcasecmp ("Match", child->key) == 0)
+      /* Be liberal with failing matches => don't set `status'. */
+      cmc_config_add_match (page, child);
+    else
+    {
+      WARNING ("memcachec plugin: Option `%s' not allowed here.", child->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  /* Additionial sanity checks and libmemcached initialization. */
+  while (status == 0)
+  {
+    if (page->server == NULL)
+    {
+      WARNING ("memcachec plugin: `Server' missing in `Page' block.");
+      status = -1;
+    }
+
+    if (page->key == NULL)
+    {
+      WARNING ("memcachec plugin: `Key' missing in `Page' block.");
+      status = -1;
+    }
+
+    if (page->matches == NULL)
+    {
+      assert (page->instance != NULL);
+      WARNING ("memcachec plugin: No (valid) `Match' block "
+          "within `Page' block `%s'.", page->instance);
+      status = -1;
+    }
+
+    if (status == 0)
+      status = cmc_page_init_memc (page);
+
+    break;
+  } /* while (status == 0) */
+
+  if (status != 0)
+  {
+    cmc_web_page_free (page);
+    return (status);
+  }
+
+  /* Add the new page to the linked list */
+  if (pages_g == NULL)
+    pages_g = page;
+  else
+  {
+    web_page_t *prev;
+
+    prev = pages_g;
+    while ((prev != NULL) && (prev->next != NULL))
+      prev = prev->next;
+    prev->next = page;
+  }
+
+  return (0);
+} /* }}} int cmc_config_add_page */
+
+static int cmc_config (oconfig_item_t *ci) /* {{{ */
+{
+  int success;
+  int errors;
+  int status;
+  int i;
+
+  success = 0;
+  errors = 0;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Page", child->key) == 0)
+    {
+      status = cmc_config_add_page (child);
+      if (status == 0)
+        success++;
+      else
+        errors++;
+    }
+    else
+    {
+      WARNING ("memcachec plugin: Option `%s' not allowed here.", child->key);
+      errors++;
+    }
+  }
+
+  if ((success == 0) && (errors > 0))
+  {
+    ERROR ("memcachec plugin: All statements failed.");
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int cmc_config */
+
+static int cmc_init (void) /* {{{ */
+{
+  if (pages_g == NULL)
+  {
+    INFO ("memcachec plugin: No pages have been defined.");
+    return (-1);
+  }
+  return (0);
+} /* }}} 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_list_t vl = VALUE_LIST_INIT;
+
+  values[0] = mv->value;
+
+  vl.values = values;
+  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));
+  sstrncpy (vl.type_instance, wm->instance, sizeof (vl.type_instance));
+
+  plugin_dispatch_values (&vl);
+} /* }}} void cmc_submit */
+
+static int cmc_read_page (web_page_t *wp) /* {{{ */
+{
+  web_match_t *wm;
+  memcached_return rc;
+  size_t string_length;
+  uint32_t flags;
+  int status;
+
+  if (wp->memc == NULL)
+    return (-1);
+
+  wp->buffer = memcached_get (wp->memc, wp->key, strlen (wp->key),
+                              &string_length, &flags, &rc);
+  if (rc != MEMCACHED_SUCCESS)
+  {
+    ERROR ("memcachec plugin: memcached_get failed: %s",
+        memcached_strerror (wp->memc, rc));
+    return (-1);
+  }
+
+  for (wm = wp->matches; wm != NULL; wm = wm->next)
+  {
+    cu_match_value_t *mv;
+
+    status = match_apply (wm->match, wp->buffer);
+    if (status != 0)
+    {
+      WARNING ("memcachec plugin: match_apply failed.");
+      continue;
+    }
+
+    mv = match_get_user_data (wm->match);
+    if (mv == NULL)
+    {
+      WARNING ("memcachec plugin: match_get_user_data returned NULL.");
+      continue;
+    }
+
+    cmc_submit (wp, wm, mv);
+  } /* for (wm = wp->matches; wm != NULL; wm = wm->next) */
+
+  sfree (wp->buffer);
+
+  return (0);
+} /* }}} int cmc_read_page */
+
+static int cmc_read (void) /* {{{ */
+{
+  web_page_t *wp;
+
+  for (wp = pages_g; wp != NULL; wp = wp->next)
+    cmc_read_page (wp);
+
+  return (0);
+} /* }}} int cmc_read */
+
+static int cmc_shutdown (void) /* {{{ */
+{
+  cmc_web_page_free (pages_g);
+  pages_g = NULL;
+
+  return (0);
+} /* }}} int cmc_shutdown */
+
+void module_register (void)
+{
+  plugin_register_complex_config ("memcachec", cmc_config);
+  plugin_register_init ("memcachec", cmc_init);
+  plugin_register_read ("memcachec", cmc_read);
+  plugin_register_shutdown ("memcachec", cmc_shutdown);
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/memcached.c b/src/memcached.c
new file mode 100644 (file)
index 0000000..ee3dbe1
--- /dev/null
@@ -0,0 +1,532 @@
+/**
+ * collectd - src/memcached.c, based on src/hddtemp.c
+ * Copyright (C) 2007       Antony Dovgal
+ * Copyright (C) 2007-2010  Florian Forster
+ * Copyright (C) 2009       Doug MacEachern
+ * Copyright (C) 2009       Franck Lombardi
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Antony Dovgal <tony at daylessday dot org>
+ *   Florian octo Forster <octo at collectd.org>
+ *   Doug MacEachern <dougm at hyperic.com>
+ *   Franck Lombardi
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+
+# include <poll.h>
+# include <netdb.h>
+# include <sys/socket.h>
+# include <sys/un.h>
+# include <netinet/in.h>
+# include <netinet/tcp.h>
+
+/* Hack to work around the missing define in AIX */
+#ifndef MSG_DONTWAIT
+# define MSG_DONTWAIT MSG_NONBLOCK
+#endif
+
+#define MEMCACHED_DEF_HOST "127.0.0.1"
+#define MEMCACHED_DEF_PORT "11211"
+
+#define MEMCACHED_RETRY_COUNT 100
+
+static const char *config_keys[] =
+{
+       "Socket",
+       "Host",
+       "Port"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static char *memcached_socket = NULL;
+static char *memcached_host = NULL;
+static char memcached_port[16];
+
+static int memcached_query_daemon (char *buffer, int buffer_size) /* {{{ */
+{
+       int fd;
+       ssize_t status;
+       int buffer_fill;
+       int i = 0;
+
+       if (memcached_socket != NULL) {
+               struct sockaddr_un serv_addr;
+
+               memset (&serv_addr, 0, sizeof (serv_addr));
+               serv_addr.sun_family = AF_UNIX;
+               sstrncpy (serv_addr.sun_path, memcached_socket,
+                               sizeof (serv_addr.sun_path));
+
+               /* create our socket descriptor */
+               fd = socket (AF_UNIX, SOCK_STREAM, 0);
+               if (fd < 0) {
+                       char errbuf[1024];
+                       ERROR ("memcached: unix socket: %s", sstrerror (errno, errbuf,
+                                               sizeof (errbuf)));
+                       return -1;
+               }
+
+               /* connect to the memcached daemon */
+               status = (ssize_t) connect (fd, (struct sockaddr *) &serv_addr,
+                               sizeof (serv_addr));
+               if (status != 0) {
+                       shutdown (fd, SHUT_RDWR);
+                       close (fd);
+                       fd = -1;
+               }
+       }
+       else { /* if (memcached_socket == NULL) */
+               const char *host;
+               const char *port;
+
+               struct addrinfo  ai_hints;
+               struct addrinfo *ai_list, *ai_ptr;
+               int              ai_return = 0;
+
+               memset (&ai_hints, '\0', sizeof (ai_hints));
+               ai_hints.ai_flags    = 0;
+#ifdef AI_ADDRCONFIG
+               /*      ai_hints.ai_flags   |= AI_ADDRCONFIG; */
+#endif
+               ai_hints.ai_family   = AF_INET;
+               ai_hints.ai_socktype = SOCK_STREAM;
+               ai_hints.ai_protocol = 0;
+
+               host = memcached_host;
+               if (host == NULL) {
+                       host = MEMCACHED_DEF_HOST;
+               }
+
+               port = memcached_port;
+               if (strlen (port) == 0) {
+                       port = MEMCACHED_DEF_PORT;
+               }
+
+               if ((ai_return = getaddrinfo (host, port, &ai_hints, &ai_list)) != 0) {
+                       char errbuf[1024];
+                       ERROR ("memcached: getaddrinfo (%s, %s): %s",
+                                       host, port,
+                                       (ai_return == EAI_SYSTEM)
+                                       ? sstrerror (errno, errbuf, sizeof (errbuf))
+                                       : gai_strerror (ai_return));
+                       return -1;
+               }
+
+               fd = -1;
+               for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) {
+                       /* create our socket descriptor */
+                       fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol);
+                       if (fd < 0) {
+                               char errbuf[1024];
+                               ERROR ("memcached: socket: %s", sstrerror (errno, errbuf, sizeof (errbuf)));
+                               continue;
+                       }
+
+                       /* connect to the memcached daemon */
+                       status = (ssize_t) connect (fd, (struct sockaddr *) ai_ptr->ai_addr, ai_ptr->ai_addrlen);
+                       if (status != 0) {
+                               shutdown (fd, SHUT_RDWR);
+                               close (fd);
+                               fd = -1;
+                               continue;
+                       }
+
+                       /* A socket could be opened and connecting succeeded. We're
+                        * done. */
+                       break;
+               }
+
+               freeaddrinfo (ai_list);
+       }
+
+       if (fd < 0) {
+               ERROR ("memcached: Could not connect to daemon.");
+               return -1;
+       }
+
+       if (send(fd, "stats\r\n", sizeof("stats\r\n") - 1, MSG_DONTWAIT) != (sizeof("stats\r\n") - 1)) {
+               ERROR ("memcached: Could not send command to the memcached daemon.");
+               return -1;
+       }
+
+       {
+               struct pollfd p;
+               int status;
+
+               memset (&p, 0, sizeof (p));
+               p.fd = fd;
+               p.events = POLLIN | POLLERR | POLLHUP;
+               p.revents = 0;
+
+               status = poll (&p, /* nfds = */ 1,
+                               /* timeout = */ CDTIME_T_TO_MS (interval_g));
+               if (status <= 0)
+               {
+                       if (status == 0)
+                       {
+                               ERROR ("memcached: poll(2) timed out after %.3f seconds.",
+                                               CDTIME_T_TO_DOUBLE (interval_g));
+                       }
+                       else
+                       {
+                               char errbuf[1024];
+                               ERROR ("memcached: poll(2) failed: %s",
+                                               sstrerror (errno, errbuf, sizeof (errbuf)));
+                       }
+                       shutdown (fd, SHUT_RDWR);
+                       close (fd);
+                       return (-1);
+               }
+       }
+
+       /* receive data from the memcached daemon */
+       memset (buffer, '\0', buffer_size);
+
+       buffer_fill = 0;
+       while ((status = recv (fd, buffer + buffer_fill, buffer_size - buffer_fill, MSG_DONTWAIT)) != 0) {
+               if (i > MEMCACHED_RETRY_COUNT) {
+                       ERROR("recv() timed out");
+                       break;
+               }
+               i++;
+
+               if (status == -1) {
+                       char errbuf[1024];
+
+                       if (errno == EAGAIN) {
+                               continue;
+                       }
+
+                       ERROR ("memcached: Error reading from socket: %s",
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       shutdown(fd, SHUT_RDWR);
+                       close (fd);
+                       return -1;
+               }
+               buffer_fill += status;
+
+               if (buffer_fill > 3 && buffer[buffer_fill-5] == 'E' && buffer[buffer_fill-4] == 'N' && buffer[buffer_fill-3] == 'D') {
+                       /* we got all the data */
+                       break;
+               }
+       }
+
+       if (buffer_fill >= buffer_size) {
+               buffer[buffer_size - 1] = '\0';
+               WARNING ("memcached: Message from memcached has been truncated.");
+       } else if (buffer_fill == 0) {
+               WARNING ("memcached: Peer has unexpectedly shut down the socket. "
+                               "Buffer: `%s'", buffer);
+               shutdown(fd, SHUT_RDWR);
+               close(fd);
+               return -1;
+       }
+
+       shutdown(fd, SHUT_RDWR);
+       close(fd);
+       return 0;
+}
+/* }}} */
+
+static int memcached_config (const char *key, const char *value) /* {{{ */
+{
+       if (strcasecmp (key, "Socket") == 0) {
+               if (memcached_socket != NULL) {
+                       free (memcached_socket);
+               }
+               memcached_socket = strdup (value);
+       } else if (strcasecmp (key, "Host") == 0) {
+               if (memcached_host != NULL) {
+                       free (memcached_host);
+               }
+               memcached_host = strdup (value);
+       } else if (strcasecmp (key, "Port") == 0) {
+               int port = (int) (atof (value));
+               if ((port > 0) && (port <= 65535)) {
+                       ssnprintf (memcached_port, sizeof (memcached_port), "%i", port);
+               } else {
+                       sstrncpy (memcached_port, value, sizeof (memcached_port));
+               }
+       } else {
+               return -1;
+       }
+
+       return 0;
+}
+/* }}} */
+
+static void submit_derive (const char *type, const char *type_inst,
+               derive_t value) /* {{{ */
+{
+       value_t values[1];
+       value_list_t vl = VALUE_LIST_INIT;
+
+       values[0].derive = value;
+
+       vl.values = values;
+       vl.values_len = 1;
+       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+       sstrncpy (vl.plugin, "memcached", sizeof (vl.plugin));
+       sstrncpy (vl.type, type, sizeof (vl.type));
+       if (type_inst != NULL)
+               sstrncpy (vl.type_instance, type_inst, sizeof (vl.type_instance));
+
+       plugin_dispatch_values (&vl);
+} /* void memcached_submit_cmd */
+/* }}} */
+
+static void submit_derive2 (const char *type, const char *type_inst,
+               derive_t value0, derive_t value1) /* {{{ */
+{
+       value_t values[2];
+       value_list_t vl = VALUE_LIST_INIT;
+
+       values[0].derive = value0;
+       values[1].derive = value1;
+
+       vl.values = values;
+       vl.values_len = 2;
+       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+       sstrncpy (vl.plugin, "memcached", sizeof (vl.plugin));
+       sstrncpy (vl.type, type, sizeof (vl.type));
+       if (type_inst != NULL)
+               sstrncpy (vl.type_instance, type_inst, sizeof (vl.type_instance));
+
+       plugin_dispatch_values (&vl);
+} /* void memcached_submit_cmd */
+/* }}} */
+
+static void submit_gauge (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_len = 1;
+       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+       sstrncpy (vl.plugin, "memcached", sizeof (vl.plugin));
+       sstrncpy (vl.type, type, sizeof (vl.type));
+       if (type_inst != NULL)
+               sstrncpy (vl.type_instance, type_inst, sizeof (vl.type_instance));
+
+       plugin_dispatch_values (&vl);
+}
+/* }}} */
+
+static void submit_gauge2 (const char *type, const char *type_inst,
+               gauge_t value0, gauge_t value1) /* {{{ */
+{
+       value_t values[2];
+       value_list_t vl = VALUE_LIST_INIT;
+
+       values[0].gauge = value0;
+       values[1].gauge = value1;
+
+       vl.values = values;
+       vl.values_len = 2;
+       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+       sstrncpy (vl.plugin, "memcached", sizeof (vl.plugin));
+       sstrncpy (vl.type, type, sizeof (vl.type));
+       if (type_inst != NULL)
+               sstrncpy (vl.type_instance, type_inst, sizeof (vl.type_instance));
+
+       plugin_dispatch_values (&vl);
+}
+/* }}} */
+
+static int memcached_read (void) /* {{{ */
+{
+       char buf[1024];
+       char *fields[3];
+       char *ptr;
+       char *line;
+       char *saveptr;
+       int fields_num;
+
+       gauge_t bytes_used = NAN;
+       gauge_t bytes_total = NAN;
+       gauge_t hits = NAN;
+       gauge_t gets = NAN;
+       derive_t rusage_user = 0;
+       derive_t rusage_syst = 0;
+       derive_t octets_rx = 0;
+       derive_t octets_tx = 0;
+
+       /* get data from daemon */
+       if (memcached_query_daemon (buf, sizeof (buf)) < 0) {
+               return -1;
+       }
+
+#define FIELD_IS(cnst) \
+       (((sizeof(cnst) - 1) == name_len) && (strcmp (cnst, fields[1]) == 0))
+
+       ptr = buf;
+       saveptr = NULL;
+       while ((line = strtok_r (ptr, "\n\r", &saveptr)) != NULL)
+       {
+               int name_len;
+
+               ptr = NULL;
+
+               fields_num = strsplit(line, fields, 3);
+               if (fields_num != 3)
+                       continue;
+
+               name_len = strlen(fields[1]);
+               if (name_len == 0)
+                       continue;
+
+               /*
+                * For an explanation on these fields please refer to
+                * <http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt>
+                */
+
+               /*
+                * CPU time consumed by the memcached process
+                */
+               if (FIELD_IS ("rusage_user"))
+               {
+                       rusage_user = atoll (fields[2]);
+               }
+               else if (FIELD_IS ("rusage_system"))
+               {
+                       rusage_syst = atoll(fields[2]);
+               }
+
+               /*
+                * Number of threads of this instance
+                */
+               else if (FIELD_IS ("threads"))
+               {
+                       submit_gauge2 ("ps_count", NULL, NAN, atof (fields[2]));
+               }
+
+               /*
+                * Number of items stored
+                */
+               else if (FIELD_IS ("curr_items"))
+               {
+                       submit_gauge ("memcached_items", "current", atof (fields[2]));
+               }
+
+               /*
+                * Number of bytes used and available (total - used)
+                */
+               else if (FIELD_IS ("bytes"))
+               {
+                       bytes_used = atof (fields[2]);
+               }
+               else if (FIELD_IS ("limit_maxbytes"))
+               {
+                       bytes_total = atof(fields[2]);
+               }
+
+               /*
+                * Connections
+                */
+               else if (FIELD_IS ("curr_connections"))
+               {
+                       submit_gauge ("memcached_connections", "current", atof (fields[2]));
+               }
+
+               /*
+                * Commands
+                */
+               else if ((name_len > 4) && (strncmp (fields[1], "cmd_", 4) == 0))
+               {
+                       const char *name = fields[1] + 4;
+                       submit_derive ("memcached_command", name, atoll (fields[2]));
+                       if (strcmp (name, "get") == 0)
+                               gets = atof (fields[2]);
+               }
+
+               /*
+                * Operations on the cache, i. e. cache hits, cache misses and evictions of items
+                */
+               else if (FIELD_IS ("get_hits"))
+               {
+                       submit_derive ("memcached_ops", "hits", atoll (fields[2]));
+                       hits = atof (fields[2]);
+               }
+               else if (FIELD_IS ("get_misses"))
+               {
+                       submit_derive ("memcached_ops", "misses", atoll (fields[2]));
+               }
+               else if (FIELD_IS ("evictions"))
+               {
+                       submit_derive ("memcached_ops", "evictions", atoll (fields[2]));
+               }
+
+               /*
+                * Network traffic
+                */
+               else if (FIELD_IS ("bytes_read"))
+               {
+                       octets_rx = atoll (fields[2]);
+               }
+               else if (FIELD_IS ("bytes_written"))
+               {
+                       octets_tx = atoll (fields[2]);
+               }
+       } /* while ((line = strtok_r (ptr, "\n\r", &saveptr)) != NULL) */
+
+       if (!isnan (bytes_used) && !isnan (bytes_total) && (bytes_used <= bytes_total))
+               submit_gauge2 ("df", "cache", bytes_used, bytes_total - bytes_used);
+
+       if ((rusage_user != 0) || (rusage_syst != 0))
+               submit_derive2 ("ps_cputime", NULL, rusage_user, rusage_syst);
+
+       if ((octets_rx != 0) || (octets_tx != 0))
+               submit_derive2 ("memcached_octets", NULL, octets_rx, octets_tx);
+
+       if (!isnan (gets) && !isnan (hits))
+       {
+               gauge_t rate = NAN;
+
+               if (gets != 0.0)
+                       rate = 100.0 * hits / gets;
+
+               submit_gauge ("percent", "hitratio", rate);
+       }
+
+       return 0;
+}
+/* }}} */
+
+void module_register (void) /* {{{ */
+{
+       plugin_register_config ("memcached", memcached_config, config_keys, config_keys_num);
+       plugin_register_read ("memcached", memcached_read);
+}
+/* }}} */
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: sw=4 ts=4 fdm=marker noexpandtab
+ * vim<600: sw=4 ts=4 noexpandtab
+ */
+
diff --git a/src/memory.c b/src/memory.c
new file mode 100644 (file)
index 0000000..6a50161
--- /dev/null
@@ -0,0 +1,453 @@
+/**
+ * collectd - src/memory.c
+ * Copyright (C) 2005-2008  Florian octo Forster
+ * Copyright (C) 2009       Simon Kuhnle
+ * Copyright (C) 2009       Manuel Sanmartin
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ *   Simon Kuhnle <simon at blarzwurst.de>
+ *   Manuel Sanmartin
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#ifdef HAVE_SYS_SYSCTL_H
+# include <sys/sysctl.h>
+#endif
+
+#ifdef HAVE_MACH_KERN_RETURN_H
+# include <mach/kern_return.h>
+#endif
+#ifdef HAVE_MACH_MACH_INIT_H
+# include <mach/mach_init.h>
+#endif
+#ifdef HAVE_MACH_MACH_HOST_H
+# include <mach/mach_host.h>
+#endif
+#ifdef HAVE_MACH_HOST_PRIV_H
+# include <mach/host_priv.h>
+#endif
+#ifdef HAVE_MACH_VM_STATISTICS_H
+# include <mach/vm_statistics.h>
+#endif
+
+#if HAVE_STATGRAB_H
+# include <statgrab.h>
+#endif
+
+#if HAVE_PERFSTAT
+# include <sys/protosw.h>
+# include <libperfstat.h>
+#endif /* HAVE_PERFSTAT */
+
+/* vm_statistics_data_t */
+#if HAVE_HOST_STATISTICS
+static mach_port_t port_host;
+static vm_size_t pagesize;
+/* #endif HAVE_HOST_STATISTICS */
+
+#elif HAVE_SYSCTLBYNAME
+/* no global variables */
+/* #endif HAVE_SYSCTLBYNAME */
+
+#elif KERNEL_LINUX
+/* no global variables */
+/* #endif KERNEL_LINUX */
+
+#elif HAVE_LIBKSTAT
+static int pagesize;
+static kstat_t *ksp;
+/* #endif HAVE_LIBKSTAT */
+
+#elif HAVE_SYSCTL
+static int pagesize;
+/* #endif HAVE_SYSCTL */
+
+#elif HAVE_LIBSTATGRAB
+/* no global variables */
+/* endif HAVE_LIBSTATGRAB */
+#elif HAVE_PERFSTAT
+static int pagesize;
+static perfstat_memory_total_t pmemory;
+/* endif HAVE_PERFSTAT */
+#else
+# error "No applicable input method."
+#endif
+
+static int memory_init (void)
+{
+#if HAVE_HOST_STATISTICS
+       port_host = mach_host_self ();
+       host_page_size (port_host, &pagesize);
+/* #endif HAVE_HOST_STATISTICS */
+
+#elif HAVE_SYSCTLBYNAME
+/* no init stuff */
+/* #endif HAVE_SYSCTLBYNAME */
+
+#elif defined(KERNEL_LINUX)
+/* no init stuff */
+/* #endif KERNEL_LINUX */
+
+#elif defined(HAVE_LIBKSTAT)
+       /* getpagesize(3C) tells me this does not fail.. */
+       pagesize = getpagesize ();
+       if (get_kstat (&ksp, "unix", 0, "system_pages") != 0)
+       {
+               ksp = NULL;
+               return (-1);
+       }
+/* #endif HAVE_LIBKSTAT */
+
+#elif HAVE_SYSCTL
+       pagesize = getpagesize ();
+       if (pagesize <= 0)
+       {
+               ERROR ("memory plugin: Invalid pagesize: %i", pagesize);
+               return (-1);
+       }
+/* #endif HAVE_SYSCTL */
+
+#elif HAVE_LIBSTATGRAB
+/* no init stuff */
+/* #endif HAVE_LIBSTATGRAB */
+
+#elif HAVE_PERFSTAT
+       pagesize = getpagesize ();
+#endif /* HAVE_PERFSTAT */
+       return (0);
+} /* int memory_init */
+
+static void memory_submit (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 = 1;
+       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+       sstrncpy (vl.plugin, "memory", sizeof (vl.plugin));
+       sstrncpy (vl.type, "memory", sizeof (vl.type));
+       sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
+
+       plugin_dispatch_values (&vl);
+}
+
+static int memory_read (void)
+{
+#if HAVE_HOST_STATISTICS
+       kern_return_t status;
+       vm_statistics_data_t   vm_data;
+       mach_msg_type_number_t vm_data_len;
+
+       gauge_t wired;
+       gauge_t active;
+       gauge_t inactive;
+       gauge_t free;
+
+       if (!port_host || !pagesize)
+               return (-1);
+
+       vm_data_len = sizeof (vm_data) / sizeof (natural_t);
+       if ((status = host_statistics (port_host, HOST_VM_INFO,
+                                       (host_info_t) &vm_data,
+                                       &vm_data_len)) != KERN_SUCCESS)
+       {
+               ERROR ("memory-plugin: host_statistics failed and returned the value %i", (int) status);
+               return (-1);
+       }
+
+       /*
+        * From <http://docs.info.apple.com/article.html?artnum=107918>:
+        *
+        * Wired memory
+        *   This information can't be cached to disk, so it must stay in RAM.
+        *   The amount depends on what applications you are using.
+        *
+        * Active memory
+        *   This information is currently in RAM and actively being used.
+        *
+        * Inactive memory
+        *   This information is no longer being used and has been cached to
+        *   disk, but it will remain in RAM until another application needs
+        *   the space. Leaving this information in RAM is to your advantage if
+        *   you (or a client of your computer) come back to it later.
+        *
+        * Free memory
+        *   This memory is not being used.
+        */
+
+       wired    = (gauge_t) (((uint64_t) vm_data.wire_count)     * ((uint64_t) pagesize));
+       active   = (gauge_t) (((uint64_t) vm_data.active_count)   * ((uint64_t) pagesize));
+       inactive = (gauge_t) (((uint64_t) vm_data.inactive_count) * ((uint64_t) pagesize));
+       free     = (gauge_t) (((uint64_t) vm_data.free_count)     * ((uint64_t) pagesize));
+
+       memory_submit ("wired",    wired);
+       memory_submit ("active",   active);
+       memory_submit ("inactive", inactive);
+       memory_submit ("free",     free);
+/* #endif HAVE_HOST_STATISTICS */
+
+#elif HAVE_SYSCTLBYNAME
+       /*
+        * vm.stats.vm.v_page_size: 4096
+        * vm.stats.vm.v_page_count: 246178
+        * vm.stats.vm.v_free_count: 28760
+        * vm.stats.vm.v_wire_count: 37526
+        * vm.stats.vm.v_active_count: 55239
+        * vm.stats.vm.v_inactive_count: 113730
+        * vm.stats.vm.v_cache_count: 10809
+        */
+       char *sysctl_keys[8] =
+       {
+               "vm.stats.vm.v_page_size",
+               "vm.stats.vm.v_page_count",
+               "vm.stats.vm.v_free_count",
+               "vm.stats.vm.v_wire_count",
+               "vm.stats.vm.v_active_count",
+               "vm.stats.vm.v_inactive_count",
+               "vm.stats.vm.v_cache_count",
+               NULL
+       };
+       double sysctl_vals[8];
+
+       int    i;
+
+       for (i = 0; sysctl_keys[i] != NULL; i++)
+       {
+               int value;
+               size_t value_len = sizeof (value);
+
+               if (sysctlbyname (sysctl_keys[i], (void *) &value, &value_len,
+                                       NULL, 0) == 0)
+               {
+                       sysctl_vals[i] = value;
+                       DEBUG ("memory plugin: %26s: %g", sysctl_keys[i], sysctl_vals[i]);
+               }
+               else
+               {
+                       sysctl_vals[i] = NAN;
+               }
+       } /* for (sysctl_keys) */
+
+       /* multiply all all page counts with the pagesize */
+       for (i = 1; sysctl_keys[i] != NULL; i++)
+               if (!isnan (sysctl_vals[i]))
+                       sysctl_vals[i] *= sysctl_vals[0];
+
+       memory_submit ("free",     sysctl_vals[2]);
+       memory_submit ("wired",    sysctl_vals[3]);
+       memory_submit ("active",   sysctl_vals[4]);
+       memory_submit ("inactive", sysctl_vals[5]);
+       memory_submit ("cache",    sysctl_vals[6]);
+/* #endif HAVE_SYSCTLBYNAME */
+
+#elif KERNEL_LINUX
+       FILE *fh;
+       char buffer[1024];
+
+       char *fields[8];
+       int numfields;
+
+       long long mem_used = 0;
+       long long mem_buffered = 0;
+       long long mem_cached = 0;
+       long long mem_free = 0;
+
+       if ((fh = fopen ("/proc/meminfo", "r")) == NULL)
+       {
+               char errbuf[1024];
+               WARNING ("memory: fopen: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       while (fgets (buffer, 1024, fh) != NULL)
+       {
+               long long *val = NULL;
+
+               if (strncasecmp (buffer, "MemTotal:", 9) == 0)
+                       val = &mem_used;
+               else if (strncasecmp (buffer, "MemFree:", 8) == 0)
+                       val = &mem_free;
+               else if (strncasecmp (buffer, "Buffers:", 8) == 0)
+                       val = &mem_buffered;
+               else if (strncasecmp (buffer, "Cached:", 7) == 0)
+                       val = &mem_cached;
+               else
+                       continue;
+
+               numfields = strsplit (buffer, fields, 8);
+
+               if (numfields < 2)
+                       continue;
+
+               *val = atoll (fields[1]) * 1024LL;
+       }
+
+       if (fclose (fh))
+       {
+               char errbuf[1024];
+               WARNING ("memory: fclose: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+       }
+
+       if (mem_used >= (mem_free + mem_buffered + mem_cached))
+       {
+               mem_used -= mem_free + mem_buffered + mem_cached;
+               memory_submit ("used",     mem_used);
+               memory_submit ("buffered", mem_buffered);
+               memory_submit ("cached",   mem_cached);
+               memory_submit ("free",     mem_free);
+       }
+/* #endif KERNEL_LINUX */
+
+#elif HAVE_LIBKSTAT
+        /* Most of the additions here were taken as-is from the k9toolkit from
+         * Brendan Gregg and are subject to change I guess */
+       long long mem_used;
+       long long mem_free;
+       long long mem_lock;
+       long long mem_kern;
+       long long mem_unus;
+
+       long long pp_kernel;
+       long long physmem;
+       long long availrmem;
+
+       if (ksp == NULL)
+               return (-1);
+
+       mem_used = get_kstat_value (ksp, "pagestotal");
+       mem_free = get_kstat_value (ksp, "pagesfree");
+       mem_lock = get_kstat_value (ksp, "pageslocked");
+       mem_kern = 0;
+       mem_unus = 0;
+
+       pp_kernel = get_kstat_value (ksp, "pp_kernel");
+       physmem = get_kstat_value (ksp, "physmem");
+       availrmem = get_kstat_value (ksp, "availrmem");
+
+       if ((mem_used < 0LL) || (mem_free < 0LL) || (mem_lock < 0LL))
+       {
+               WARNING ("memory plugin: one of used, free or locked is negative.");
+               return (-1);
+       }
+
+       mem_unus = physmem - mem_used;
+
+       if (mem_used < (mem_free + mem_lock))
+       {
+               /* source: http://wesunsolve.net/bugid/id/4909199
+                * this seems to happen when swap space is small, e.g. 2G on a 32G system
+                * we will make some assumptions here
+                * educated solaris internals help welcome here */
+               DEBUG ("memory plugin: pages total is smaller than \"free\" "
+                               "+ \"locked\". This is probably due to small "
+                               "swap space");
+               mem_free = availrmem;
+               mem_used = 0;
+       }
+       else
+       {
+               mem_used -= mem_free + mem_lock;
+       }
+
+       /* mem_kern is accounted for in mem_lock */
+       if ( pp_kernel < mem_lock )
+       {
+               mem_kern = pp_kernel;
+               mem_lock -= pp_kernel;
+       }
+       else
+       {
+               mem_kern = mem_lock;
+               mem_lock = 0;
+       }
+
+       mem_used *= pagesize; /* If this overflows you have some serious */
+       mem_free *= pagesize; /* memory.. Why not call me up and give me */
+       mem_lock *= pagesize; /* some? ;) */
+       mem_kern *= pagesize; /* it's 2011 RAM is cheap */
+       mem_unus *= pagesize;
+
+       memory_submit ("used",   mem_used);
+       memory_submit ("free",   mem_free);
+       memory_submit ("locked", mem_lock);
+       memory_submit ("kernel", mem_kern);
+       memory_submit ("unusable", mem_unus);
+/* #endif HAVE_LIBKSTAT */
+
+#elif HAVE_SYSCTL
+       int mib[] = {CTL_VM, VM_METER};
+       struct vmtotal vmtotal;
+       size_t size;
+
+       memset (&vmtotal, 0, sizeof (vmtotal));
+       size = sizeof (vmtotal);
+
+       if (sysctl (mib, 2, &vmtotal, &size, NULL, 0) < 0) {
+               char errbuf[1024];
+               WARNING ("memory plugin: sysctl failed: %s",
+                       sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       assert (pagesize > 0);
+       memory_submit ("active",   vmtotal.t_arm * pagesize);
+       memory_submit ("inactive", (vmtotal.t_rm - vmtotal.t_arm) * pagesize);
+       memory_submit ("free",     vmtotal.t_free * pagesize);
+/* #endif HAVE_SYSCTL */
+
+#elif HAVE_LIBSTATGRAB
+       sg_mem_stats *ios;
+
+       if ((ios = sg_get_mem_stats ()) != NULL)
+       {
+               memory_submit ("used",   ios->used);
+               memory_submit ("cached", ios->cache);
+               memory_submit ("free",   ios->free);
+       }
+/* #endif HAVE_LIBSTATGRAB */
+
+#elif HAVE_PERFSTAT
+       if (perfstat_memory_total(NULL, &pmemory, sizeof(perfstat_memory_total_t), 1) < 0)
+       {
+               char errbuf[1024];
+               WARNING ("memory plugin: perfstat_memory_total failed: %s",
+                       sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+       memory_submit ("used",   pmemory.real_inuse * pagesize);
+       memory_submit ("free",   pmemory.real_free * pagesize);
+       memory_submit ("cached", pmemory.numperm * pagesize);
+       memory_submit ("system", pmemory.real_system * pagesize);
+       memory_submit ("user",   pmemory.real_process * pagesize);
+#endif /* HAVE_PERFSTAT */
+
+       return (0);
+}
+
+void module_register (void)
+{
+       plugin_register_init ("memory", memory_init);
+       plugin_register_read ("memory", memory_read);
+} /* void module_register */
diff --git a/src/meta_data.c b/src/meta_data.c
new file mode 100644 (file)
index 0000000..b502b37
--- /dev/null
@@ -0,0 +1,625 @@
+/**
+ * collectd - src/meta_data.c
+ * Copyright (C) 2008-2011  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "plugin.h"
+#include "meta_data.h"
+
+#include <pthread.h>
+
+/*
+ * Data types
+ */
+union meta_value_u
+{
+  char    *mv_string;
+  int64_t  mv_signed_int;
+  uint64_t mv_unsigned_int;
+  double   mv_double;
+  _Bool    mv_boolean;
+};
+typedef union meta_value_u meta_value_t;
+
+struct meta_entry_s;
+typedef struct meta_entry_s meta_entry_t;
+struct meta_entry_s
+{
+  char         *key;
+  meta_value_t  value;
+  int           type;
+  meta_entry_t *next;
+};
+
+struct meta_data_s
+{
+  meta_entry_t   *head;
+  pthread_mutex_t lock;
+};
+
+/*
+ * Private functions
+ */
+static char *md_strdup (const char *orig) /* {{{ */
+{
+  size_t sz;
+  char *dest;
+
+  if (orig == NULL)
+    return (NULL);
+
+  sz = strlen (orig) + 1;
+  dest = (char *) malloc (sz);
+  if (dest == NULL)
+    return (NULL);
+
+  memcpy (dest, orig, sz);
+
+  return (dest);
+} /* }}} char *md_strdup */
+
+static meta_entry_t *md_entry_alloc (const char *key) /* {{{ */
+{
+  meta_entry_t *e;
+
+  e = (meta_entry_t *) malloc (sizeof (*e));
+  if (e == NULL)
+  {
+    ERROR ("md_entry_alloc: malloc failed.");
+    return (NULL);
+  }
+  memset (e, 0, sizeof (*e));
+
+  e->key = md_strdup (key);
+  if (e->key == NULL)
+  {
+    free (e);
+    ERROR ("md_entry_alloc: md_strdup failed.");
+    return (NULL);
+  }
+
+  e->type = 0;
+  e->next = NULL;
+
+  return (e);
+} /* }}} meta_entry_t *md_entry_alloc */
+
+static meta_entry_t *md_entry_clone (const meta_entry_t *orig) /* {{{ */
+{
+  meta_entry_t *copy;
+
+  if (orig == NULL)
+    return (NULL);
+
+  copy = md_entry_alloc (orig->key);
+  copy->type = orig->type;
+  if (copy->type == MD_TYPE_STRING)
+    copy->value.mv_string = strdup (orig->value.mv_string);
+  else
+    copy->value = orig->value;
+
+  copy->next = md_entry_clone (orig->next);
+  return (copy);
+} /* }}} meta_entry_t *md_entry_clone */
+
+static void md_entry_free (meta_entry_t *e) /* {{{ */
+{
+  if (e == NULL)
+    return;
+
+  free (e->key);
+
+  if (e->type == MD_TYPE_STRING)
+    free (e->value.mv_string);
+
+  if (e->next != NULL)
+    md_entry_free (e->next);
+
+  free (e);
+} /* }}} void md_entry_free */
+
+static int md_entry_insert (meta_data_t *md, meta_entry_t *e) /* {{{ */
+{
+  meta_entry_t *this;
+  meta_entry_t *prev;
+
+  if ((md == NULL) || (e == NULL))
+    return (-EINVAL);
+
+  pthread_mutex_lock (&md->lock);
+
+  prev = NULL;
+  this = md->head;
+  while (this != NULL)
+  {
+    if (strcasecmp (e->key, this->key) == 0)
+      break;
+
+    prev = this;
+    this = this->next;
+  }
+
+  if (this == NULL)
+  {
+    /* This key does not exist yet. */
+    if (md->head == NULL)
+      md->head = e;
+    else
+    {
+      assert (prev != NULL);
+      prev->next = e;
+    }
+
+    e->next = NULL;
+  }
+  else /* (this != NULL) */
+  {
+    if (prev == NULL)
+      md->head = e;
+    else
+      prev->next = e;
+
+    e->next = this->next;
+  }
+
+  pthread_mutex_unlock (&md->lock);
+
+  if (this != NULL)
+  {
+    this->next = NULL;
+    md_entry_free (this);
+  }
+
+  return (0);
+} /* }}} int md_entry_insert */
+
+/* XXX: The lock on md must be held while calling this function! */
+static meta_entry_t *md_entry_lookup (meta_data_t *md, /* {{{ */
+    const char *key)
+{
+  meta_entry_t *e;
+
+  if ((md == NULL) || (key == NULL))
+    return (NULL);
+
+  for (e = md->head; e != NULL; e = e->next)
+    if (strcasecmp (key, e->key) == 0)
+      break;
+
+  return (e);
+} /* }}} meta_entry_t *md_entry_lookup */
+
+/*
+ * Public functions
+ */
+meta_data_t *meta_data_create (void) /* {{{ */
+{
+  meta_data_t *md;
+
+  md = (meta_data_t *) malloc (sizeof (*md));
+  if (md == NULL)
+  {
+    ERROR ("meta_data_create: malloc failed.");
+    return (NULL);
+  }
+  memset (md, 0, sizeof (*md));
+
+  md->head = NULL;
+  pthread_mutex_init (&md->lock, /* attr = */ NULL);
+
+  return (md);
+} /* }}} meta_data_t *meta_data_create */
+
+meta_data_t *meta_data_clone (meta_data_t *orig) /* {{{ */
+{
+  meta_data_t *copy;
+
+  if (orig == NULL)
+    return (NULL);
+
+  copy = meta_data_create ();
+  if (copy == NULL)
+    return (NULL);
+
+  pthread_mutex_lock (&orig->lock);
+  copy->head = md_entry_clone (orig->head);
+  pthread_mutex_unlock (&orig->lock);
+
+  return (copy);
+} /* }}} meta_data_t *meta_data_clone */
+
+void meta_data_destroy (meta_data_t *md) /* {{{ */
+{
+  if (md == NULL)
+    return;
+
+  md_entry_free (md->head);
+  pthread_mutex_destroy (&md->lock);
+  free (md);
+} /* }}} void meta_data_destroy */
+
+int meta_data_exists (meta_data_t *md, const char *key) /* {{{ */
+{
+  meta_entry_t *e;
+
+  if ((md == NULL) || (key == NULL))
+    return (-EINVAL);
+
+  pthread_mutex_lock (&md->lock);
+
+  for (e = md->head; e != NULL; e = e->next)
+  {
+    if (strcasecmp (key, e->key) == 0)
+    {
+      pthread_mutex_unlock (&md->lock);
+      return (1);
+    }
+  }
+
+  pthread_mutex_unlock (&md->lock);
+  return (0);
+} /* }}} int meta_data_exists */
+
+int meta_data_type (meta_data_t *md, const char *key) /* {{{ */
+{
+  meta_entry_t *e;
+
+  if ((md == NULL) || (key == NULL))
+    return -EINVAL;
+
+  pthread_mutex_lock (&md->lock);
+
+  for (e = md->head; e != NULL; e = e->next)
+  {
+    if (strcasecmp (key, e->key) == 0)
+    {
+      pthread_mutex_unlock (&md->lock);
+      return e->type;
+    }
+  }
+
+  pthread_mutex_unlock (&md->lock);
+  return 0;
+} /* }}} int meta_data_type */
+
+int meta_data_toc (meta_data_t *md, char ***toc) /* {{{ */
+{
+  int i = 0, count = 0;
+  meta_entry_t *e;
+
+  if ((md == NULL) || (toc == NULL))
+    return -EINVAL;
+
+  pthread_mutex_lock (&md->lock);
+
+  for (e = md->head; e != NULL; e = e->next)
+    ++count;    
+
+  *toc = malloc(count * sizeof(**toc));
+  for (e = md->head; e != NULL; e = e->next)
+    (*toc)[i++] = strdup(e->key);
+  
+  pthread_mutex_unlock (&md->lock);
+  return count;
+} /* }}} int meta_data_toc */
+
+int meta_data_delete (meta_data_t *md, const char *key) /* {{{ */
+{
+  meta_entry_t *this;
+  meta_entry_t *prev;
+
+  if ((md == NULL) || (key == NULL))
+    return (-EINVAL);
+
+  pthread_mutex_lock (&md->lock);
+
+  prev = NULL;
+  this = md->head;
+  while (this != NULL)
+  {
+    if (strcasecmp (key, this->key) == 0)
+      break;
+
+    prev = this;
+    this = this->next;
+  }
+
+  if (this == NULL)
+  {
+    pthread_mutex_unlock (&md->lock);
+    return (-ENOENT);
+  }
+
+  if (prev == NULL)
+    md->head = this->next;
+  else
+    prev->next = this->next;
+
+  pthread_mutex_unlock (&md->lock);
+
+  this->next = NULL;
+  md_entry_free (this);
+
+  return (0);
+} /* }}} int meta_data_delete */
+
+/*
+ * Add functions
+ */
+int meta_data_add_string (meta_data_t *md, /* {{{ */
+    const char *key, const char *value)
+{
+  meta_entry_t *e;
+
+  if ((md == NULL) || (key == NULL) || (value == NULL))
+    return (-EINVAL);
+
+  e = md_entry_alloc (key);
+  if (e == NULL)
+    return (-ENOMEM);
+
+  e->value.mv_string = md_strdup (value);
+  if (e->value.mv_string == NULL)
+  {
+    ERROR ("meta_data_add_string: md_strdup failed.");
+    md_entry_free (e);
+    return (-ENOMEM);
+  }
+  e->type = MD_TYPE_STRING;
+
+  return (md_entry_insert (md, e));
+} /* }}} int meta_data_add_string */
+
+int meta_data_add_signed_int (meta_data_t *md, /* {{{ */
+    const char *key, int64_t value)
+{
+  meta_entry_t *e;
+
+  if ((md == NULL) || (key == NULL))
+    return (-EINVAL);
+
+  e = md_entry_alloc (key);
+  if (e == NULL)
+    return (-ENOMEM);
+
+  e->value.mv_signed_int = value;
+  e->type = MD_TYPE_SIGNED_INT;
+
+  return (md_entry_insert (md, e));
+} /* }}} int meta_data_add_signed_int */
+
+int meta_data_add_unsigned_int (meta_data_t *md, /* {{{ */
+    const char *key, uint64_t value)
+{
+  meta_entry_t *e;
+
+  if ((md == NULL) || (key == NULL))
+    return (-EINVAL);
+
+  e = md_entry_alloc (key);
+  if (e == NULL)
+    return (-ENOMEM);
+
+  e->value.mv_unsigned_int = value;
+  e->type = MD_TYPE_UNSIGNED_INT;
+
+  return (md_entry_insert (md, e));
+} /* }}} int meta_data_add_unsigned_int */
+
+int meta_data_add_double (meta_data_t *md, /* {{{ */
+    const char *key, double value)
+{
+  meta_entry_t *e;
+
+  if ((md == NULL) || (key == NULL))
+    return (-EINVAL);
+
+  e = md_entry_alloc (key);
+  if (e == NULL)
+    return (-ENOMEM);
+
+  e->value.mv_double = value;
+  e->type = MD_TYPE_DOUBLE;
+
+  return (md_entry_insert (md, e));
+} /* }}} int meta_data_add_double */
+
+int meta_data_add_boolean (meta_data_t *md, /* {{{ */
+    const char *key, _Bool value)
+{
+  meta_entry_t *e;
+
+  if ((md == NULL) || (key == NULL))
+    return (-EINVAL);
+
+  e = md_entry_alloc (key);
+  if (e == NULL)
+    return (-ENOMEM);
+
+  e->value.mv_boolean = value;
+  e->type = MD_TYPE_BOOLEAN;
+
+  return (md_entry_insert (md, e));
+} /* }}} int meta_data_add_boolean */
+
+/*
+ * Get functions
+ */
+int meta_data_get_string (meta_data_t *md, /* {{{ */
+    const char *key, char **value)
+{
+  meta_entry_t *e;
+  char *temp;
+
+  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);
+  }
+
+  if (e->type != MD_TYPE_STRING)
+  {
+    ERROR ("meta_data_get_signed_int: Type mismatch for key `%s'", e->key);
+    pthread_mutex_unlock (&md->lock);
+    return (-ENOENT);
+  }
+
+  temp = md_strdup (e->value.mv_string);
+  if (temp == NULL)
+  {
+    pthread_mutex_unlock (&md->lock);
+    ERROR ("meta_data_get_string: md_strdup failed.");
+    return (-ENOMEM);
+  }
+  pthread_mutex_unlock (&md->lock);
+
+  *value = temp;
+
+  return (0);
+} /* }}} int meta_data_get_string */
+
+int meta_data_get_signed_int (meta_data_t *md, /* {{{ */
+    const char *key, int64_t *value)
+{
+  meta_entry_t *e;
+
+  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);
+  }
+
+  if (e->type != MD_TYPE_SIGNED_INT)
+  {
+    ERROR ("meta_data_get_signed_int: Type mismatch for key `%s'", e->key);
+    pthread_mutex_unlock (&md->lock);
+    return (-ENOENT);
+  }
+
+  *value = e->value.mv_signed_int;
+
+  pthread_mutex_unlock (&md->lock);
+  return (0);
+} /* }}} int meta_data_get_signed_int */
+
+int meta_data_get_unsigned_int (meta_data_t *md, /* {{{ */
+    const char *key, uint64_t *value)
+{
+  meta_entry_t *e;
+
+  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);
+  }
+
+  if (e->type != MD_TYPE_UNSIGNED_INT)
+  {
+    ERROR ("meta_data_get_unsigned_int: Type mismatch for key `%s'", e->key);
+    pthread_mutex_unlock (&md->lock);
+    return (-ENOENT);
+  }
+
+  *value = e->value.mv_unsigned_int;
+
+  pthread_mutex_unlock (&md->lock);
+  return (0);
+} /* }}} int meta_data_get_unsigned_int */
+
+int meta_data_get_double (meta_data_t *md, /* {{{ */
+    const char *key, double *value)
+{
+  meta_entry_t *e;
+
+  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);
+  }
+
+  if (e->type != MD_TYPE_DOUBLE)
+  {
+    ERROR ("meta_data_get_double: Type mismatch for key `%s'", e->key);
+    pthread_mutex_unlock (&md->lock);
+    return (-ENOENT);
+  }
+
+  *value = e->value.mv_double;
+
+  pthread_mutex_unlock (&md->lock);
+  return (0);
+} /* }}} int meta_data_get_double */
+
+int meta_data_get_boolean (meta_data_t *md, /* {{{ */
+    const char *key, _Bool *value)
+{
+  meta_entry_t *e;
+
+  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);
+  }
+
+  if (e->type != MD_TYPE_BOOLEAN)
+  {
+    ERROR ("meta_data_get_boolean: Type mismatch for key `%s'", e->key);
+    pthread_mutex_unlock (&md->lock);
+    return (-ENOENT);
+  }
+
+  *value = e->value.mv_boolean;
+
+  pthread_mutex_unlock (&md->lock);
+  return (0);
+} /* }}} int meta_data_get_boolean */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/meta_data.h b/src/meta_data.h
new file mode 100644 (file)
index 0000000..f1af40e
--- /dev/null
@@ -0,0 +1,81 @@
+/**
+ * collectd - src/meta_data.h
+ * Copyright (C) 2008-2011  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef META_DATA_H
+#define META_DATA_H
+
+#include "collectd.h"
+
+/*
+ * Defines
+ */
+#define MD_TYPE_STRING       1
+#define MD_TYPE_SIGNED_INT   2
+#define MD_TYPE_UNSIGNED_INT 3
+#define MD_TYPE_DOUBLE       4
+#define MD_TYPE_BOOLEAN      5
+
+struct meta_data_s;
+typedef struct meta_data_s meta_data_t;
+
+meta_data_t *meta_data_create (void);
+meta_data_t *meta_data_clone (meta_data_t *orig);
+void meta_data_destroy (meta_data_t *md);
+
+int meta_data_exists (meta_data_t *md, const char *key);
+int meta_data_type (meta_data_t *md, const char *key);
+int meta_data_toc (meta_data_t *md, char ***toc);
+int meta_data_delete (meta_data_t *md, const char *key);
+
+int meta_data_add_string (meta_data_t *md,
+    const char *key,
+    const char *value);
+int meta_data_add_signed_int (meta_data_t *md,
+    const char *key,
+    int64_t value);
+int meta_data_add_unsigned_int (meta_data_t *md,
+    const char *key,
+    uint64_t value);
+int meta_data_add_double (meta_data_t *md,
+    const char *key,
+    double value);
+int meta_data_add_boolean (meta_data_t *md,
+    const char *key,
+    _Bool value);
+
+int meta_data_get_string (meta_data_t *md,
+    const char *key,
+    char **value);
+int meta_data_get_signed_int (meta_data_t *md,
+    const char *key,
+    int64_t *value);
+int meta_data_get_unsigned_int (meta_data_t *md,
+    const char *key,
+    uint64_t *value);
+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);
+
+#endif /* META_DATA_H */
+/* vim: set sw=2 sts=2 et : */
diff --git a/src/modbus.c b/src/modbus.c
new file mode 100644 (file)
index 0000000..19848b0
--- /dev/null
@@ -0,0 +1,988 @@
+/**
+ * collectd - src/modbus.c
+ * Copyright (C) 2010,2011  noris network AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; only version 2.1 of the License is
+ * applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors:
+ *   Florian Forster <octo at noris.net>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+
+#include <netdb.h>
+
+#include <modbus/modbus.h>
+
+#ifndef LIBMODBUS_VERSION_CHECK
+/* Assume version 2.0.3 */
+# define LEGACY_LIBMODBUS 1
+#else
+/* Assume version 2.9.2 */
+#endif
+
+#ifndef MODBUS_TCP_DEFAULT_PORT
+# ifdef MODBUS_TCP_PORT
+#  define MODBUS_TCP_DEFAULT_PORT MODBUS_TCP_PORT
+# else
+#  define MODBUS_TCP_DEFAULT_PORT 502
+# endif
+#endif
+
+/*
+ * <Data "data_name">
+ *   RegisterBase 1234
+ *   RegisterType float
+ *   Type gauge
+ *   Instance "..."
+ * </Data>
+ *
+ * <Host "name">
+ *   Address "addr"
+ *   Port "1234"
+ *   Interval 60
+ *
+ *   <Slave 1>
+ *     Instance "foobar" # optional
+ *     Collect "data_name"
+ *   </Slave>
+ * </Host>
+ */
+
+/*
+ * Data structures
+ */
+enum mb_register_type_e /* {{{ */
+{
+  REG_TYPE_INT16,
+  REG_TYPE_INT32,
+  REG_TYPE_UINT16,
+  REG_TYPE_UINT32,
+  REG_TYPE_FLOAT
+}; /* }}} */
+typedef enum mb_register_type_e mb_register_type_t;
+
+struct mb_data_s;
+typedef struct mb_data_s mb_data_t;
+struct mb_data_s /* {{{ */
+{
+  char *name;
+  int register_base;
+  mb_register_type_t register_type;
+  char type[DATA_MAX_NAME_LEN];
+  char instance[DATA_MAX_NAME_LEN];
+
+  mb_data_t *next;
+}; /* }}} */
+
+struct mb_slave_s /* {{{ */
+{
+  int id;
+  char instance[DATA_MAX_NAME_LEN];
+  mb_data_t *collect;
+}; /* }}} */
+typedef struct mb_slave_s mb_slave_t;
+
+struct mb_host_s /* {{{ */
+{
+  char host[DATA_MAX_NAME_LEN];
+  char node[NI_MAXHOST];
+  /* char service[NI_MAXSERV]; */
+  int port;
+  cdtime_t interval;
+
+  mb_slave_t *slaves;
+  size_t slaves_num;
+
+#if LEGACY_LIBMODBUS
+  modbus_param_t connection;
+#else
+  modbus_t *connection;
+#endif
+  _Bool is_connected;
+  _Bool have_reconnected;
+}; /* }}} */
+typedef struct mb_host_s mb_host_t;
+
+struct mb_data_group_s;
+typedef struct mb_data_group_s mb_data_group_t;
+struct mb_data_group_s /* {{{ */
+{
+  mb_data_t *registers;
+  size_t registers_num;
+
+  mb_data_group_t *next;
+}; /* }}} */
+
+/*
+ * Global variables
+ */
+static mb_data_t *data_definitions = NULL;
+
+/*
+ * Functions
+ */
+static mb_data_t *data_get_by_name (mb_data_t *src, /* {{{ */
+    const char *name)
+{
+  mb_data_t *ptr;
+
+  if (name == NULL)
+    return (NULL);
+
+  for (ptr = src; ptr != NULL; ptr = ptr->next)
+    if (strcasecmp (ptr->name, name) == 0)
+      return (ptr);
+
+  return (NULL);
+} /* }}} mb_data_t *data_get_by_name */
+
+static int data_append (mb_data_t **dst, mb_data_t *src) /* {{{ */
+{
+  mb_data_t *ptr;
+
+  if ((dst == NULL) || (src == NULL))
+    return (EINVAL);
+
+  ptr = *dst;
+
+  if (ptr == NULL)
+  {
+    *dst = src;
+    return (0);
+  }
+
+  while (ptr->next != NULL)
+    ptr = ptr->next;
+
+  ptr->next = src;
+
+  return (0);
+} /* }}} int data_append */
+
+/* Copy a single mb_data_t and append it to another list. */
+static int data_copy (mb_data_t **dst, const mb_data_t *src) /* {{{ */
+{
+  mb_data_t *tmp;
+  int status;
+
+  if ((dst == NULL) || (src == NULL))
+    return (EINVAL);
+
+  tmp = malloc (sizeof (*tmp));
+  if (tmp == NULL)
+    return (ENOMEM);
+  memcpy (tmp, src, sizeof (*tmp));
+  tmp->name = NULL;
+  tmp->next = NULL;
+
+  tmp->name = strdup (src->name);
+  if (tmp->name == NULL)
+  {
+    sfree (tmp);
+    return (ENOMEM);
+  }
+
+  status = data_append (dst, tmp);
+  if (status != 0)
+  {
+    sfree (tmp->name);
+    sfree (tmp);
+    return (status);
+  }
+
+  return (0);
+} /* }}} int data_copy */
+
+/* Lookup a single mb_data_t instance, copy it and append the copy to another
+ * list. */
+static int data_copy_by_name (mb_data_t **dst, mb_data_t *src, /* {{{ */
+    const char *name)
+{
+  mb_data_t *ptr;
+
+  if ((dst == NULL) || (src == NULL) || (name == NULL))
+    return (EINVAL);
+
+  ptr = data_get_by_name (src, name);
+  if (ptr == NULL)
+    return (ENOENT);
+
+  return (data_copy (dst, ptr));
+} /* }}} int data_copy_by_name */
+
+/* Read functions */
+
+static int mb_submit (mb_host_t *host, mb_slave_t *slave, /* {{{ */
+    mb_data_t *data, value_t value)
+{
+  value_list_t vl = VALUE_LIST_INIT;
+
+  if ((host == NULL) || (slave == NULL) || (data == NULL))
+    return (EINVAL);
+
+  if (host->interval <= 0)
+    host->interval = interval_g;
+
+  if (slave->instance[0] == 0)
+    ssnprintf (slave->instance, sizeof (slave->instance), "slave_%i",
+        slave->id);
+
+  vl.values = &value;
+  vl.values_len = 1;
+  vl.interval = host->interval;
+  sstrncpy (vl.host, host->host, sizeof (vl.host));
+  sstrncpy (vl.plugin, "modbus", sizeof (vl.plugin));
+  sstrncpy (vl.plugin_instance, slave->instance, sizeof (vl.plugin_instance));
+  sstrncpy (vl.type, data->type, sizeof (vl.type));
+  sstrncpy (vl.type_instance, data->instance, sizeof (vl.type_instance));
+
+  return (plugin_dispatch_values (&vl));
+} /* }}} int mb_submit */
+
+static float mb_register_to_float (uint16_t hi, uint16_t lo) /* {{{ */
+{
+  union
+  {
+    uint8_t b[4];
+    float f;
+  } conv;
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+  /* little endian */
+  conv.b[0] = lo & 0x00ff;
+  conv.b[1] = (lo >> 8) & 0x00ff;
+  conv.b[2] = hi & 0x00ff;
+  conv.b[3] = (hi >> 8) & 0x00ff;
+#else
+  conv.b[3] = lo & 0x00ff;
+  conv.b[2] = (lo >> 8) & 0x00ff;
+  conv.b[1] = hi & 0x00ff;
+  conv.b[0] = (hi >> 8) & 0x00ff;
+#endif
+
+  return (conv.f);
+} /* }}} float mb_register_to_float */
+
+#if LEGACY_LIBMODBUS
+/* Version 2.0.3 */
+static int mb_init_connection (mb_host_t *host) /* {{{ */
+{
+  int status;
+
+  if (host == NULL)
+    return (EINVAL);
+
+  if (host->is_connected)
+    return (0);
+
+  /* Only reconnect once per interval. */
+  if (host->have_reconnected)
+    return (-1);
+
+  modbus_set_debug (&host->connection, 1);
+
+  /* We'll do the error handling ourselves. */
+  modbus_set_error_handling (&host->connection, NOP_ON_ERROR);
+
+  if ((host->port < 1) || (host->port > 65535))
+    host->port = MODBUS_TCP_DEFAULT_PORT;
+
+  DEBUG ("Modbus plugin: Trying to connect to \"%s\", port %i.",
+      host->node, host->port);
+
+  modbus_init_tcp (&host->connection,
+      /* host = */ host->node,
+      /* port = */ host->port);
+
+  status = modbus_connect (&host->connection);
+  if (status != 0)
+  {
+    ERROR ("Modbus plugin: modbus_connect (%s, %i) failed with status %i.",
+        host->node, host->port, status);
+    return (status);
+  }
+
+  host->is_connected = 1;
+  host->have_reconnected = 1;
+  return (0);
+} /* }}} int mb_init_connection */
+/* #endif LEGACY_LIBMODBUS */
+
+#else /* if !LEGACY_LIBMODBUS */
+/* Version 2.9.2 */
+static int mb_init_connection (mb_host_t *host) /* {{{ */
+{
+  int status;
+
+  if (host == NULL)
+    return (EINVAL);
+
+  if (host->connection != NULL)
+    return (0);
+
+  /* Only reconnect once per interval. */
+  if (host->have_reconnected)
+    return (-1);
+
+  if ((host->port < 1) || (host->port > 65535))
+    host->port = MODBUS_TCP_DEFAULT_PORT;
+
+  DEBUG ("Modbus plugin: Trying to connect to \"%s\", port %i.",
+      host->node, host->port);
+
+  host->connection = modbus_new_tcp (host->node, host->port);
+  if (host->connection == NULL)
+  {
+    host->have_reconnected = 1;
+    ERROR ("Modbus plugin: Creating new Modbus/TCP object failed.");
+    return (-1);
+  }
+
+  modbus_set_debug (host->connection, 1);
+
+  /* We'll do the error handling ourselves. */
+  modbus_set_error_recovery (host->connection, 0);
+
+  status = modbus_connect (host->connection);
+  if (status != 0)
+  {
+    ERROR ("Modbus plugin: modbus_connect (%s, %i) failed with status %i.",
+        host->node, host->port, status);
+    modbus_free (host->connection);
+    host->connection = NULL;
+    return (status);
+  }
+
+  host->have_reconnected = 1;
+  return (0);
+} /* }}} int mb_init_connection */
+#endif /* !LEGACY_LIBMODBUS */
+
+#define CAST_TO_VALUE_T(ds,vt,raw) do { \
+  if ((ds)->ds[0].type == DS_TYPE_COUNTER) \
+    (vt).counter = (counter_t) (raw); \
+  else if ((ds)->ds[0].type == DS_TYPE_GAUGE) \
+    (vt).gauge = (gauge_t) (raw); \
+  else if ((ds)->ds[0].type == DS_TYPE_DERIVE) \
+    (vt).derive = (derive_t) (raw); \
+  else /* if (ds->ds[0].type == DS_TYPE_ABSOLUTE) */ \
+    (vt).absolute = (absolute_t) (raw); \
+} while (0)
+
+static int mb_read_data (mb_host_t *host, mb_slave_t *slave, /* {{{ */
+    mb_data_t *data)
+{
+  uint16_t values[2];
+  int values_num;
+  const data_set_t *ds;
+  int status;
+  int i;
+
+  if ((host == NULL) || (slave == NULL) || (data == NULL))
+    return (EINVAL);
+
+  ds = plugin_get_ds (data->type);
+  if (ds == NULL)
+  {
+    ERROR ("Modbus plugin: Type \"%s\" is not defined.", data->type);
+    return (-1);
+  }
+
+  if (ds->ds_num != 1)
+  {
+    ERROR ("Modbus plugin: The type \"%s\" has %i data sources. "
+        "I can only handle data sets with only one data source.",
+        data->type, ds->ds_num);
+    return (-1);
+  }
+
+  if ((ds->ds[0].type != DS_TYPE_GAUGE)
+      && (data->register_type != REG_TYPE_INT32)
+      && (data->register_type != REG_TYPE_UINT32))
+  {
+    NOTICE ("Modbus plugin: The data source of type \"%s\" is %s, not gauge. "
+        "This will most likely result in problems, because the register type "
+        "is not UINT32.", data->type, DS_TYPE_TO_STRING (ds->ds[0].type));
+  }
+
+  memset (values, 0, sizeof (values));
+  if ((data->register_type == REG_TYPE_INT32)
+      || (data->register_type == REG_TYPE_UINT32)
+      || (data->register_type == REG_TYPE_FLOAT))
+    values_num = 2;
+  else
+    values_num = 1;
+
+#if LEGACY_LIBMODBUS
+  /* Version 2.0.3: Pass the connection struct as a pointer and pass the slave
+   * id to each call of "read_holding_registers". */
+# define modbus_read_registers(ctx, addr, nb, dest) \
+  read_holding_registers (&(ctx), slave->id, (addr), (nb), (dest))
+#else /* if !LEGACY_LIBMODBUS */
+  /* Version 2.9.2: Set the slave id once before querying the registers. */
+  status = modbus_set_slave (host->connection, slave->id);
+  if (status != 0)
+  {
+    ERROR ("Modbus plugin: modbus_set_slave (%i) failed with status %i.",
+        slave->id, status);
+    return (-1);
+  }
+#endif
+
+  for (i = 0; i < 2; i++)
+  {
+    status = modbus_read_registers (host->connection,
+        /* start_addr = */ data->register_base,
+        /* num_registers = */ values_num, /* buffer = */ values);
+    if (status > 0)
+      break;
+
+    if (host->is_connected)
+    {
+#if LEGACY_LIBMODBUS
+      modbus_close (&host->connection);
+      host->is_connected = 0;
+#else
+      modbus_close (host->connection);
+      modbus_free (host->connection);
+      host->connection = NULL;
+#endif
+    }
+
+    /* If we already tried reconnecting this round, give up. */
+    if (host->have_reconnected)
+    {
+      ERROR ("Modbus plugin: modbus_read_registers (%s) failed. "
+          "Reconnecting has already been tried. Giving up.", host->host);
+      return (-1);
+    }
+
+    /* Maybe the device closed the connection during the waiting interval.
+     * Try re-establishing the connection. */
+    status = mb_init_connection (host);
+    if (status != 0)
+    {
+      ERROR ("Modbus plugin: modbus_read_registers (%s) failed. "
+          "While trying to reconnect, connecting to \"%s\" failed. "
+          "Giving up.",
+          host->host, host->node);
+      return (-1);
+    }
+
+    DEBUG ("Modbus plugin: Re-established connection to %s", host->host);
+
+    /* try again */
+    continue;
+  } /* for (i = 0, 1) */
+
+  DEBUG ("Modbus plugin: mb_read_data: Success! "
+      "modbus_read_registers returned with status %i.", status);
+
+  if (data->register_type == REG_TYPE_FLOAT)
+  {
+    float float_value;
+    value_t vt;
+
+    float_value = mb_register_to_float (values[0], values[1]);
+    DEBUG ("Modbus plugin: mb_read_data: "
+        "Returned float value is %g", (double) float_value);
+
+    CAST_TO_VALUE_T (ds, vt, float_value);
+    mb_submit (host, slave, data, vt);
+  }
+  else if (data->register_type == REG_TYPE_INT32)
+  {
+    union
+    {
+      uint32_t u32;
+      int32_t  i32;
+    } v;
+    value_t vt;
+
+    v.u32 = (((uint32_t) values[0]) << 16)
+      | ((uint32_t) values[1]);
+    DEBUG ("Modbus plugin: mb_read_data: "
+        "Returned int32 value is %"PRIi32, v.i32);
+
+    CAST_TO_VALUE_T (ds, vt, v.i32);
+    mb_submit (host, slave, data, vt);
+  }
+  else if (data->register_type == REG_TYPE_INT16)
+  {
+    union
+    {
+      uint16_t u16;
+      int16_t  i16;
+    } v;
+    value_t vt;
+
+    v.u16 = values[0];
+
+    DEBUG ("Modbus plugin: mb_read_data: "
+        "Returned int16 value is %"PRIi16, v.i16);
+
+    CAST_TO_VALUE_T (ds, vt, v.i16);
+    mb_submit (host, slave, data, vt);
+  }
+  else if (data->register_type == REG_TYPE_UINT32)
+  {
+    uint32_t v32;
+    value_t vt;
+
+    v32 = (((uint32_t) values[0]) << 16)
+      | ((uint32_t) values[1]);
+    DEBUG ("Modbus plugin: mb_read_data: "
+        "Returned uint32 value is %"PRIu32, v32);
+
+    CAST_TO_VALUE_T (ds, vt, v32);
+    mb_submit (host, slave, data, vt);
+  }
+  else /* if (data->register_type == REG_TYPE_UINT16) */
+  {
+    value_t vt;
+
+    DEBUG ("Modbus plugin: mb_read_data: "
+        "Returned uint16 value is %"PRIu16, values[0]);
+
+    CAST_TO_VALUE_T (ds, vt, values[0]);
+    mb_submit (host, slave, data, vt);
+  }
+
+  return (0);
+} /* }}} int mb_read_data */
+
+static int mb_read_slave (mb_host_t *host, mb_slave_t *slave) /* {{{ */
+{
+  mb_data_t *data;
+  int success;
+  int status;
+
+  if ((host == NULL) || (slave == NULL))
+    return (EINVAL);
+
+  success = 0;
+  for (data = slave->collect; data != NULL; data = data->next)
+  {
+    status = mb_read_data (host, slave, data);
+    if (status == 0)
+      success++;
+  }
+
+  if (success == 0)
+    return (-1);
+  else
+    return (0);
+} /* }}} int mb_read_slave */
+
+static int mb_read (user_data_t *user_data) /* {{{ */
+{
+  mb_host_t *host;
+  size_t i;
+  int success;
+  int status;
+
+  if ((user_data == NULL) || (user_data->data == NULL))
+    return (EINVAL);
+
+  host = user_data->data;
+
+  /* Clear the reconnect flag. */
+  host->have_reconnected = 0;
+
+  success = 0;
+  for (i = 0; i < host->slaves_num; i++)
+  {
+    status = mb_read_slave (host, host->slaves + i);
+    if (status == 0)
+      success++;
+  }
+
+  if (success == 0)
+    return (-1);
+  else
+    return (0);
+} /* }}} int mb_read */
+
+/* Free functions */
+
+static void data_free_one (mb_data_t *data) /* {{{ */
+{
+  if (data == NULL)
+    return;
+
+  sfree (data->name);
+  sfree (data);
+} /* }}} void data_free_one */
+
+static void data_free_all (mb_data_t *data) /* {{{ */
+{
+  mb_data_t *next;
+
+  if (data == NULL)
+    return;
+
+  next = data->next;
+  data_free_one (data);
+
+  data_free_all (next);
+} /* }}} void data_free_all */
+
+static void slaves_free_all (mb_slave_t *slaves, size_t slaves_num) /* {{{ */
+{
+  size_t i;
+
+  if (slaves == NULL)
+    return;
+
+  for (i = 0; i < slaves_num; i++)
+    data_free_all (slaves[i].collect);
+  sfree (slaves);
+} /* }}} void slaves_free_all */
+
+static void host_free (void *void_host) /* {{{ */
+{
+  mb_host_t *host = void_host;
+
+  if (host == NULL)
+    return;
+
+  slaves_free_all (host->slaves, host->slaves_num);
+  sfree (host);
+} /* }}} void host_free */
+
+/* Config functions */
+
+static int mb_config_add_data (oconfig_item_t *ci) /* {{{ */
+{
+  mb_data_t data;
+  int status;
+  int i;
+
+  memset (&data, 0, sizeof (data));
+  data.name = NULL;
+  data.register_type = REG_TYPE_UINT16;
+  data.next = NULL;
+
+  status = cf_util_get_string (ci, &data.name);
+  if (status != 0)
+    return (status);
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+    status = 0;
+
+    if (strcasecmp ("Type", child->key) == 0)
+      status = cf_util_get_string_buffer (child,
+          data.type, sizeof (data.type));
+    else if (strcasecmp ("Instance", child->key) == 0)
+      status = cf_util_get_string_buffer (child,
+          data.instance, sizeof (data.instance));
+    else if (strcasecmp ("RegisterBase", child->key) == 0)
+      status = cf_util_get_int (child, &data.register_base);
+    else if (strcasecmp ("RegisterType", child->key) == 0)
+    {
+      char tmp[16];
+      status = cf_util_get_string_buffer (child, tmp, sizeof (tmp));
+      if (status != 0)
+        /* do nothing */;
+      else if (strcasecmp ("Int16", tmp) == 0)
+        data.register_type = REG_TYPE_INT16;
+      else if (strcasecmp ("Int32", tmp) == 0)
+        data.register_type = REG_TYPE_INT32;
+      else if (strcasecmp ("Uint16", tmp) == 0)
+        data.register_type = REG_TYPE_UINT16;
+      else if (strcasecmp ("Uint32", tmp) == 0)
+        data.register_type = REG_TYPE_UINT32;
+      else if (strcasecmp ("Float", tmp) == 0)
+        data.register_type = REG_TYPE_FLOAT;
+      else
+      {
+        ERROR ("Modbus plugin: The register type \"%s\" is unknown.", tmp);
+        status = -1;
+      }
+    }
+    else
+    {
+      ERROR ("Modbus plugin: Unknown configuration option: %s", child->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  assert (data.name != NULL);
+  if (data.type[0] == 0)
+  {
+    ERROR ("Modbus plugin: Data block \"%s\": No type has been specified.",
+        data.name);
+    status = -1;
+  }
+
+  if (status == 0)
+    data_copy (&data_definitions, &data);
+
+  sfree (data.name);
+
+  return (status);
+} /* }}} int mb_config_add_data */
+
+static int mb_config_set_host_address (mb_host_t *host, /* {{{ */
+    const char *address)
+{
+  struct addrinfo *ai_list;
+  struct addrinfo *ai_ptr;
+  struct addrinfo  ai_hints;
+  int status;
+
+  if ((host == NULL) || (address == NULL))
+    return (EINVAL);
+
+  memset (&ai_hints, 0, sizeof (ai_hints));
+#if AI_ADDRCONFIG
+  ai_hints.ai_flags |= AI_ADDRCONFIG;
+#endif
+  /* XXX: libmodbus can only handle IPv4 addresses. */
+  ai_hints.ai_family = AF_INET;
+  ai_hints.ai_addr = NULL;
+  ai_hints.ai_canonname = NULL;
+  ai_hints.ai_next = NULL;
+
+  ai_list = NULL;
+  status = getaddrinfo (address, /* service = */ NULL,
+      &ai_hints, &ai_list);
+  if (status != 0)
+  {
+    char errbuf[1024];
+    ERROR ("Modbus plugin: getaddrinfo failed: %s",
+        (status == EAI_SYSTEM)
+        ? sstrerror (errno, errbuf, sizeof (errbuf))
+        : gai_strerror (status));
+    return (status);
+  }
+
+  for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
+  {
+    status = getnameinfo (ai_ptr->ai_addr, ai_ptr->ai_addrlen,
+        host->node, sizeof (host->node),
+        /* service = */ NULL, /* length = */ 0,
+        /* flags = */ NI_NUMERICHOST);
+    if (status == 0)
+      break;
+  } /* for (ai_ptr) */
+
+  freeaddrinfo (ai_list);
+
+  if (status != 0)
+    ERROR ("Modbus plugin: Unable to translate node name: \"%s\"", address);
+  else /* if (status == 0) */
+  {
+    DEBUG ("Modbus plugin: mb_config_set_host_address: %s -> %s",
+        address, host->node);
+  }
+
+  return (status);
+} /* }}} int mb_config_set_host_address */
+
+static int mb_config_add_slave (mb_host_t *host, oconfig_item_t *ci) /* {{{ */
+{
+  mb_slave_t *slave;
+  int status;
+  int i;
+
+  if ((host == NULL) || (ci == NULL))
+    return (EINVAL);
+
+  slave = realloc (host->slaves, sizeof (*slave) * (host->slaves_num + 1));
+  if (slave == NULL)
+    return (ENOMEM);
+  host->slaves = slave;
+  slave = host->slaves + host->slaves_num;
+  memset (slave, 0, sizeof (*slave));
+  slave->collect = NULL;
+
+  status = cf_util_get_int (ci, &slave->id);
+  if (status != 0)
+    return (status);
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+    status = 0;
+
+    if (strcasecmp ("Instance", child->key) == 0)
+      status = cf_util_get_string_buffer (child,
+          slave->instance, sizeof (slave->instance));
+    else if (strcasecmp ("Collect", child->key) == 0)
+    {
+      char buffer[1024];
+      status = cf_util_get_string_buffer (child, buffer, sizeof (buffer));
+      if (status == 0)
+        data_copy_by_name (&slave->collect, data_definitions, buffer);
+      status = 0; /* continue after failure. */
+    }
+    else
+    {
+      ERROR ("Modbus plugin: Unknown configuration option: %s", child->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  if ((status == 0) && (slave->collect == NULL))
+    status = EINVAL;
+
+  if (slave->id < 0)
+    status = EINVAL;
+
+  if (status == 0)
+    host->slaves_num++;
+  else /* if (status != 0) */
+    data_free_all (slave->collect);
+
+  return (status);
+} /* }}} int mb_config_add_slave */
+
+static int mb_config_add_host (oconfig_item_t *ci) /* {{{ */
+{
+  mb_host_t *host;
+  int status;
+  int i;
+
+  host = malloc (sizeof (*host));
+  if (host == NULL)
+    return (ENOMEM);
+  memset (host, 0, sizeof (*host));
+  host->slaves = NULL;
+
+  status = cf_util_get_string_buffer (ci, host->host, sizeof (host->host));
+  if (status != 0)
+    return (status);
+  if (host->host[0] == 0)
+    return (EINVAL);
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+    status = 0;
+
+    if (strcasecmp ("Address", child->key) == 0)
+    {
+      char buffer[NI_MAXHOST];
+      status = cf_util_get_string_buffer (child, buffer, sizeof (buffer));
+      if (status == 0)
+        status = mb_config_set_host_address (host, buffer);
+    }
+    else if (strcasecmp ("Port", child->key) == 0)
+    {
+      host->port = cf_util_get_port_number (child);
+      if (host->port <= 0)
+        status = -1;
+    }
+    else if (strcasecmp ("Interval", child->key) == 0)
+      status = cf_util_get_cdtime (child, &host->interval);
+    else if (strcasecmp ("Slave", child->key) == 0)
+      /* Don't set status: Gracefully continue if a slave fails. */
+      mb_config_add_slave (host, child);
+    else
+    {
+      ERROR ("Modbus plugin: Unknown configuration option: %s", child->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  assert (host->host[0] != 0);
+  if (host->host[0] == 0)
+  {
+    ERROR ("Modbus plugin: Data block \"%s\": No type has been specified.",
+        host->host);
+    status = -1;
+  }
+
+  if (status == 0)
+  {
+    user_data_t ud;
+    char name[1024];
+    struct timespec interval = { 0, 0 };
+
+    ud.data = host;
+    ud.free_func = host_free;
+
+    ssnprintf (name, sizeof (name), "modbus-%s", host->host);
+
+    CDTIME_T_TO_TIMESPEC (host->interval, &interval);
+
+    plugin_register_complex_read (/* group = */ NULL, name,
+        /* callback = */ mb_read,
+        /* interval = */ (host->interval > 0) ? &interval : NULL,
+        &ud);
+  }
+  else
+  {
+    host_free (host);
+  }
+
+  return (status);
+} /* }}} int mb_config_add_host */
+
+static int mb_config (oconfig_item_t *ci) /* {{{ */
+{
+  int i;
+
+  if (ci == NULL)
+    return (EINVAL);
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Data", child->key) == 0)
+      mb_config_add_data (child);
+    else if (strcasecmp ("Host", child->key) == 0)
+      mb_config_add_host (child);
+    else
+      ERROR ("Modbus plugin: Unknown configuration option: %s", child->key);
+  }
+
+  return (0);
+} /* }}} int mb_config */
+
+/* ========= */
+
+static int mb_shutdown (void) /* {{{ */
+{
+  data_free_all (data_definitions);
+  data_definitions = NULL;
+
+  return (0);
+} /* }}} int mb_shutdown */
+
+void module_register (void)
+{
+  plugin_register_complex_config ("modbus", mb_config);
+  plugin_register_shutdown ("modbus", mb_shutdown);
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/multimeter.c b/src/multimeter.c
new file mode 100644 (file)
index 0000000..775eb57
--- /dev/null
@@ -0,0 +1,240 @@
+/**
+ * collectd - src/multimeter.c
+ * Copyright (C) 2005,2006  Peter Holik
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Peter Holik <peter at holik.at>
+ *
+ * Used multimeter: Metex M-4650CR
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#if HAVE_TERMIOS_H && HAVE_SYS_IOCTL_H && HAVE_MATH_H
+# include <termios.h>
+# include <sys/ioctl.h>
+# include <math.h>
+#else
+# error "No applicable input method."
+#endif
+
+static int fd = -1;
+
+#define LINE_LENGTH 14
+static int multimeter_read_value(double *value)
+{
+       int retry = 3; /* sometimes we receive garbadge */
+
+       do
+       {
+               struct timeval time_end;
+
+               tcflush(fd, TCIFLUSH);
+
+               if (gettimeofday (&time_end, NULL) < 0)
+               {
+                       char errbuf[1024];
+                       ERROR ("multimeter plugin: gettimeofday failed: %s",
+                                       sstrerror (errno, errbuf,
+                                               sizeof (errbuf)));
+                       return (-1);
+               }
+               time_end.tv_sec++;      
+
+               while (1)
+               {
+                       char buf[LINE_LENGTH];
+                       char *range;
+                       int status;
+                       fd_set rfds;
+                       struct timeval timeout;
+                       struct timeval time_now;
+
+                       status = swrite (fd, "D", 1);
+                       if (status < 0)
+                       {
+                               ERROR ("multimeter plugin: swrite failed.");
+                               return (-1);
+                       }
+
+                       FD_ZERO(&rfds);
+                       FD_SET(fd, &rfds);
+
+                       if (gettimeofday (&time_now, NULL) < 0)
+                       {
+                               char errbuf[1024];
+                               ERROR ("multimeter plugin: "
+                                               "gettimeofday failed: %s",
+                                               sstrerror (errno, errbuf,
+                                                       sizeof (errbuf)));
+                               return (-1);
+                       }
+                       if (timeval_cmp (time_end, time_now, &timeout) < 0)
+                               break;
+
+                       status = select(fd+1, &rfds, NULL, NULL, &timeout);
+
+                       if (status > 0) /* usually we succeed */
+                       {
+                               status = read(fd, buf, LINE_LENGTH);
+
+                               if ((status < 0) && ((errno == EAGAIN) || (errno == EINTR)))
+                                       continue;
+
+                               /* Format: "DC 00.000mV  \r" */
+                               if (status > 0 && status == LINE_LENGTH)
+                               {
+                                       *value = strtod(buf + 2, &range);
+
+                                       if ( range > (buf + 6) )
+                                       {
+                                               range = buf + 9;
+
+                                               switch ( *range )
+                                               {
+                                                       case 'p': *value *= 1.0E-12; break;
+                                                       case 'n': *value *= 1.0E-9; break;
+                                                       case 'u': *value *= 1.0E-6; break;
+                                                       case 'm': *value *= 1.0E-3; break;
+                                                       case 'k': *value *= 1.0E3; break;
+                                                       case 'M': *value *= 1.0E6; break;
+                                                       case 'G': *value *= 1.0E9; break;
+                                               }
+                                       }
+                                       else
+                                               return (-1); /* Overflow */
+
+                                       return (0); /* value received */
+                               }
+                               else break;
+                       }
+                       else if (!status) /* Timeout */
+                       {
+                               break;
+                       }
+                       else if ((status == -1) && ((errno == EAGAIN) || (errno == EINTR)))
+                       {
+                               continue;
+                       }
+                       else /* status == -1 */
+                       {
+                               char errbuf[1024];
+                               ERROR ("multimeter plugin: "
+                                               "select failed: %s",
+                                               sstrerror (errno, errbuf, sizeof (errbuf)));
+                               break;
+                       }
+               }
+       } while (--retry);
+
+       return (-2);  /* no value received */
+} /* int multimeter_read_value */
+
+static int multimeter_init (void)
+{
+       int i;
+       char device[] = "/dev/ttyS ";
+
+       for (i = 0; i < 10; i++)
+       {
+               device[strlen(device)-1] = i + '0'; 
+
+               if ((fd = open(device, O_RDWR | O_NOCTTY)) > 0)
+               {
+                       struct termios tios;
+                       int rts = TIOCM_RTS;
+                       double value;
+
+                       tios.c_cflag = B1200 | CS7 | CSTOPB | CREAD | CLOCAL;
+                       tios.c_iflag = IGNBRK | IGNPAR;
+                       tios.c_oflag = 0;
+                       tios.c_lflag = 0;
+                       tios.c_cc[VTIME] = 3;
+                       tios.c_cc[VMIN]  = LINE_LENGTH;
+
+                       tcflush(fd, TCIFLUSH);
+                       tcsetattr(fd, TCSANOW, &tios);
+                       ioctl(fd, TIOCMBIC, &rts);
+                       
+                       if (multimeter_read_value (&value) < -1)
+                       {
+                               close (fd);
+                               fd = -1;
+                       }
+                       else
+                       {
+                               INFO ("multimeter plugin: Device "
+                                               "found at %s", device);
+                               return (0);
+                       }
+               }
+       }
+
+       ERROR ("multimeter plugin: No device found");
+       return (-1);
+}
+#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_len = 1;
+       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+       sstrncpy (vl.plugin, "multimeter", sizeof (vl.plugin));
+       sstrncpy (vl.type, "multimeter", sizeof (vl.type));
+
+       plugin_dispatch_values (&vl);
+}
+
+static int multimeter_read (void)
+{
+       double value;
+
+       if (fd < 0)
+               return (-1);
+
+       if (multimeter_read_value (&value) != 0)
+               return (-1);
+
+       multimeter_submit (value);
+       return (0);
+} /* int multimeter_read */
+
+static int multimeter_shutdown (void)
+{
+       if (fd >= 0)
+       {
+               close (fd);
+               fd = -1;
+       }
+
+       return (0);
+}
+
+void module_register (void)
+{
+       plugin_register_init ("multimeter", multimeter_init);
+       plugin_register_read ("multimeter", multimeter_read);
+       plugin_register_shutdown ("multimeter", multimeter_shutdown);
+} /* void module_register */
diff --git a/src/mysql.c b/src/mysql.c
new file mode 100644 (file)
index 0000000..6b63678
--- /dev/null
@@ -0,0 +1,712 @@
+/**
+ * collectd - src/mysql.c
+ * Copyright (C) 2006-2010  Florian octo Forster
+ * Copyright (C) 2008       Mirko Buffoni
+ * Copyright (C) 2009       Doug MacEachern
+ * Copyright (C) 2009       Sebastian tokkee Harl
+ * Copyright (C) 2009       Rodolphe Quiédeville
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at collectd.org>
+ *   Mirko Buffoni <briareos at eswat.org>
+ *   Doug MacEachern <dougm at hyperic.com>
+ *   Sebastian tokkee Harl <sh at tokkee.org>
+ *   Rodolphe Quiédeville <rquiedeville at bearstech.com>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+
+#ifdef HAVE_MYSQL_H
+#include <mysql.h>
+#elif defined(HAVE_MYSQL_MYSQL_H)
+#include <mysql/mysql.h>
+#endif
+
+/* TODO: Understand `Select_*' and possibly do that stuff as well.. */
+
+struct mysql_database_s /* {{{ */
+{
+       char *instance;
+       char *host;
+       char *user;
+       char *pass;
+       char *database;
+       char *socket;
+       int   port;
+
+       _Bool master_stats;
+       _Bool slave_stats;
+
+       _Bool slave_notif;
+       _Bool slave_io_running;
+       _Bool slave_sql_running;
+
+       MYSQL *con;
+       int    state;
+};
+typedef struct mysql_database_s mysql_database_t; /* }}} */
+
+static int mysql_read (user_data_t *ud);
+
+static void mysql_database_free (void *arg) /* {{{ */
+{
+       mysql_database_t *db;
+
+       DEBUG ("mysql plugin: mysql_database_free (arg = %p);", arg);
+
+       db = (mysql_database_t *) arg;
+
+       if (db == NULL)
+               return;
+
+       if (db->con != NULL)
+               mysql_close (db->con);
+
+       sfree (db->host);
+       sfree (db->user);
+       sfree (db->pass);
+       sfree (db->socket);
+       sfree (db->instance);
+       sfree (db->database);
+       sfree (db);
+} /* }}} void mysql_database_free */
+
+/* Configuration handling functions {{{
+ *
+ * <Plugin mysql>
+ *   <Database "plugin_instance1">
+ *     Host "localhost"
+ *     Port 22000
+ *     ...
+ *   </Database>
+ * </Plugin>
+ */
+static int mysql_config_database (oconfig_item_t *ci) /* {{{ */
+{
+       mysql_database_t *db;
+       int status = 0;
+       int i;
+
+       if ((ci->values_num != 1)
+           || (ci->values[0].type != OCONFIG_TYPE_STRING))
+       {
+               WARNING ("mysql plugin: The `Database' block "
+                        "needs exactly one string argument.");
+               return (-1);
+       }
+
+       db = (mysql_database_t *) malloc (sizeof (*db));
+       if (db == NULL)
+       {
+               ERROR ("mysql plugin: malloc failed.");
+               return (-1);
+       }
+       memset (db, 0, sizeof (*db));
+
+       /* initialize all the pointers */
+       db->host     = NULL;
+       db->user     = NULL;
+       db->pass     = NULL;
+       db->database = NULL;
+       db->socket   = NULL;
+       db->con      = NULL;
+
+       /* trigger a notification, if it's not running */
+       db->slave_io_running  = 1;
+       db->slave_sql_running = 1;
+
+       status = cf_util_get_string (ci, &db->instance);
+       if (status != 0)
+       {
+               sfree (db);
+               return (status);
+       }
+       assert (db->instance != NULL);
+
+       /* Fill the `mysql_database_t' structure.. */
+       for (i = 0; i < ci->children_num; i++)
+       {
+               oconfig_item_t *child = ci->children + i;
+
+               if (strcasecmp ("Host", child->key) == 0)
+                       status = cf_util_get_string (child, &db->host);
+               else if (strcasecmp ("User", child->key) == 0)
+                       status = cf_util_get_string (child, &db->user);
+               else if (strcasecmp ("Password", child->key) == 0)
+                       status = cf_util_get_string (child, &db->pass);
+               else if (strcasecmp ("Port", child->key) == 0)
+               {
+                       status = cf_util_get_port_number (child);
+                       if (status > 0)
+                       {
+                               db->port = status;
+                               status = 0;
+                       }
+               }
+               else if (strcasecmp ("Socket", child->key) == 0)
+                       status = cf_util_get_string (child, &db->socket);
+               else if (strcasecmp ("Database", child->key) == 0)
+                       status = cf_util_get_string (child, &db->database);
+               else if (strcasecmp ("MasterStats", child->key) == 0)
+                       status = cf_util_get_boolean (child, &db->master_stats);
+               else if (strcasecmp ("SlaveStats", child->key) == 0)
+                       status = cf_util_get_boolean (child, &db->slave_stats);
+               else if (strcasecmp ("SlaveNotifications", child->key) == 0)
+                       status = cf_util_get_boolean (child, &db->slave_notif);
+               else
+               {
+                       WARNING ("mysql plugin: Option `%s' not allowed here.", child->key);
+                       status = -1;
+               }
+
+               if (status != 0)
+                       break;
+       }
+
+       /* If all went well, register this database for reading */
+       if (status == 0)
+       {
+               user_data_t ud;
+               char cb_name[DATA_MAX_NAME_LEN];
+
+               DEBUG ("mysql plugin: Registering new read callback: %s",
+                               (db->database != NULL) ? db->database : "<default>");
+
+               memset (&ud, 0, sizeof (ud));
+               ud.data = (void *) db;
+               ud.free_func = mysql_database_free;
+
+               if (db->database != NULL)
+                       ssnprintf (cb_name, sizeof (cb_name), "mysql-%s",
+                                       db->database);
+               else
+                       sstrncpy (cb_name, "mysql", sizeof (cb_name));
+
+               plugin_register_complex_read (/* group = */ NULL, cb_name,
+                                             mysql_read,
+                                             /* interval = */ NULL, &ud);
+       }
+       else
+       {
+               mysql_database_free (db);
+               return (-1);
+       }
+
+       return (0);
+} /* }}} int mysql_config_database */
+
+static int mysql_config (oconfig_item_t *ci) /* {{{ */
+{
+       int i;
+
+       if (ci == NULL)
+               return (EINVAL);
+
+       /* Fill the `mysql_database_t' structure.. */
+       for (i = 0; i < ci->children_num; i++)
+       {
+               oconfig_item_t *child = ci->children + i;
+
+               if (strcasecmp ("Database", child->key) == 0)
+                       mysql_config_database (child);
+               else
+                       WARNING ("mysql plugin: Option \"%s\" not allowed here.",
+                                       child->key);
+       }
+
+       return (0);
+} /* }}} int mysql_config */
+
+/* }}} End of configuration handling functions */
+
+static MYSQL *getconnection (mysql_database_t *db)
+{
+       if (db->state != 0)
+       {
+               int err;
+               if ((err = mysql_ping (db->con)) != 0)
+               {
+                       /* Assured by "mysql_config_database" */
+                       assert (db->instance != NULL);
+                       WARNING ("mysql_ping failed for instance \"%s\": %s",
+                                       db->instance,
+                                       mysql_error (db->con));
+                       db->state = 0;
+               }
+               else
+               {
+                       db->state = 1;
+                       return (db->con);
+               }
+       }
+
+       if ((db->con = mysql_init (db->con)) == NULL)
+       {
+               ERROR ("mysql_init failed: %s", mysql_error (db->con));
+               db->state = 0;
+               return (NULL);
+       }
+
+       if (mysql_real_connect (db->con, db->host, db->user, db->pass,
+                               db->database, db->port, db->socket, 0) == NULL)
+       {
+               ERROR ("mysql plugin: Failed to connect to database %s "
+                               "at server %s: %s",
+                               (db->database != NULL) ? db->database : "<none>",
+                               (db->host != NULL) ? db->host : "localhost",
+                               mysql_error (db->con));
+               db->state = 0;
+               return (NULL);
+       }
+       else
+       {
+               INFO ("mysql plugin: Successfully connected to database %s "
+                               "at server %s (server version: %s, protocol version: %d)",
+                               (db->database != NULL) ? db->database : "<none>",
+                               mysql_get_host_info (db->con),
+                               mysql_get_server_info (db->con),
+                               mysql_get_proto_info (db->con));
+               db->state = 1;
+               return (db->con);
+       }
+} /* static MYSQL *getconnection (mysql_database_t *db) */
+
+static void set_host (mysql_database_t *db, char *buf, size_t buflen)
+{
+       if ((db->host == NULL)
+                       || (strcmp ("", db->host) == 0)
+                       || (strcmp ("localhost", db->host) == 0))
+               sstrncpy (buf, hostname_g, buflen);
+       else
+               sstrncpy (buf, db->host, buflen);
+} /* void set_host */
+
+static void submit (const char *type, const char *type_instance,
+               value_t *values, size_t values_len, mysql_database_t *db)
+{
+       value_list_t vl = VALUE_LIST_INIT;
+
+       vl.values     = values;
+       vl.values_len = values_len;
+
+       set_host (db, vl.host, sizeof (vl.host));
+
+       sstrncpy (vl.plugin, "mysql", sizeof (vl.plugin));
+
+       /* Assured by "mysql_config_database" */
+       assert (db->instance != NULL);
+       sstrncpy (vl.plugin_instance, db->instance, 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);
+} /* 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);
+} /* 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);
+} /* 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;
+
+       submit ("mysql_octets", NULL, values, STATIC_ARRAY_SIZE (values), db);
+} /* void traffic_submit */
+
+static MYSQL_RES *exec_query (MYSQL *con, const char *query)
+{
+       MYSQL_RES *res;
+
+       int query_len = strlen (query);
+
+       if (mysql_real_query (con, query, query_len))
+       {
+               ERROR ("mysql plugin: Failed to execute query: %s",
+                               mysql_error (con));
+               INFO ("mysql plugin: SQL query was: %s", query);
+               return (NULL);
+       }
+
+       res = mysql_store_result (con);
+       if (res == NULL)
+       {
+               ERROR ("mysql plugin: Failed to store query result: %s",
+                               mysql_error (con));
+               INFO ("mysql plugin: SQL query was: %s", query);
+               return (NULL);
+       }
+
+       return (res);
+} /* exec_query */
+
+static int mysql_read_master_stats (mysql_database_t *db, MYSQL *con)
+{
+       MYSQL_RES *res;
+       MYSQL_ROW  row;
+
+       char *query;
+       int   field_num;
+       unsigned long long position;
+
+       query = "SHOW MASTER STATUS";
+
+       res = exec_query (con, query);
+       if (res == NULL)
+               return (-1);
+
+       row = mysql_fetch_row (res);
+       if (row == NULL)
+       {
+               ERROR ("mysql plugin: Failed to get master statistics: "
+                               "`%s' did not return any rows.", query);
+               return (-1);
+       }
+
+       field_num = mysql_num_fields (res);
+       if (field_num < 2)
+       {
+               ERROR ("mysql plugin: Failed to get master statistics: "
+                               "`%s' returned less than two columns.", query);
+               return (-1);
+       }
+
+       position = atoll (row[1]);
+       counter_submit ("mysql_log_position", "master-bin", position, db);
+
+       row = mysql_fetch_row (res);
+       if (row != NULL)
+               WARNING ("mysql plugin: `%s' returned more than one row - "
+                               "ignoring further results.", query);
+
+       mysql_free_result (res);
+
+       return (0);
+} /* mysql_read_master_stats */
+
+static int mysql_read_slave_stats (mysql_database_t *db, MYSQL *con)
+{
+       MYSQL_RES *res;
+       MYSQL_ROW  row;
+
+       char *query;
+       int   field_num;
+
+       /* WTF? libmysqlclient does not seem to provide any means to
+        * translate a column name to a column index ... :-/ */
+       const int READ_MASTER_LOG_POS_IDX   = 6;
+       const int SLAVE_IO_RUNNING_IDX      = 10;
+       const int SLAVE_SQL_RUNNING_IDX     = 11;
+       const int EXEC_MASTER_LOG_POS_IDX   = 21;
+       const int SECONDS_BEHIND_MASTER_IDX = 32;
+
+       query = "SHOW SLAVE STATUS";
+
+       res = exec_query (con, query);
+       if (res == NULL)
+               return (-1);
+
+       row = mysql_fetch_row (res);
+       if (row == NULL)
+       {
+               ERROR ("mysql plugin: Failed to get slave statistics: "
+                               "`%s' did not return any rows.", query);
+               return (-1);
+       }
+
+       field_num = mysql_num_fields (res);
+       if (field_num < 33)
+       {
+               ERROR ("mysql plugin: Failed to get slave statistics: "
+                               "`%s' returned less than 33 columns.", query);
+               return (-1);
+       }
+
+       if (db->slave_stats)
+       {
+               unsigned long long counter;
+               double gauge;
+
+               counter = atoll (row[READ_MASTER_LOG_POS_IDX]);
+               counter_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);
+
+               if (row[SECONDS_BEHIND_MASTER_IDX] != NULL)
+               {
+                       gauge = atof (row[SECONDS_BEHIND_MASTER_IDX]);
+                       gauge_submit ("time_offset", NULL, gauge, db);
+               }
+       }
+
+       if (db->slave_notif)
+       {
+               notification_t n = { 0, cdtime (), "", "",
+                       "mysql", "", "time_offset", "", NULL };
+
+               char *io, *sql;
+
+               io  = row[SLAVE_IO_RUNNING_IDX];
+               sql = row[SLAVE_SQL_RUNNING_IDX];
+
+               set_host (db, n.host, sizeof (n.host));
+
+               /* Assured by "mysql_config_database" */
+               assert (db->instance != NULL);
+               sstrncpy (n.plugin_instance, db->instance, sizeof (n.plugin_instance));
+
+               if (((io == NULL) || (strcasecmp (io, "yes") != 0))
+                               && (db->slave_io_running))
+               {
+                       n.severity = NOTIF_WARNING;
+                       ssnprintf (n.message, sizeof (n.message),
+                                       "slave I/O thread not started or not connected to master");
+                       plugin_dispatch_notification (&n);
+                       db->slave_io_running = 0;
+               }
+               else if (((io != NULL) && (strcasecmp (io, "yes") == 0))
+                               && (! db->slave_io_running))
+               {
+                       n.severity = NOTIF_OKAY;
+                       ssnprintf (n.message, sizeof (n.message),
+                                       "slave I/O thread started and connected to master");
+                       plugin_dispatch_notification (&n);
+                       db->slave_io_running = 1;
+               }
+
+               if (((sql == NULL) || (strcasecmp (sql, "yes") != 0))
+                               && (db->slave_sql_running))
+               {
+                       n.severity = NOTIF_WARNING;
+                       ssnprintf (n.message, sizeof (n.message),
+                                       "slave SQL thread not started");
+                       plugin_dispatch_notification (&n);
+                       db->slave_sql_running = 0;
+               }
+               else if (((sql != NULL) && (strcasecmp (sql, "yes") == 0))
+                               && (! db->slave_sql_running))
+               {
+                       n.severity = NOTIF_OKAY;
+                       ssnprintf (n.message, sizeof (n.message),
+                                       "slave SQL thread started");
+                       plugin_dispatch_notification (&n);
+                       db->slave_sql_running = 0;
+               }
+       }
+
+       row = mysql_fetch_row (res);
+       if (row != NULL)
+               WARNING ("mysql plugin: `%s' returned more than one row - "
+                               "ignoring further results.", query);
+
+       mysql_free_result (res);
+
+       return (0);
+} /* mysql_read_slave_stats */
+
+static int mysql_read (user_data_t *ud)
+{
+       mysql_database_t *db;
+       MYSQL     *con;
+       MYSQL_RES *res;
+       MYSQL_ROW  row;
+       char      *query;
+
+       derive_t qcache_hits          = 0;
+       derive_t qcache_inserts       = 0;
+       derive_t qcache_not_cached    = 0;
+       derive_t qcache_lowmem_prunes = 0;
+       gauge_t qcache_queries_in_cache = NAN;
+
+       gauge_t threads_running   = NAN;
+       gauge_t threads_connected = NAN;
+       gauge_t threads_cached    = NAN;
+       derive_t threads_created = 0;
+
+       unsigned long long traffic_incoming = 0ULL;
+       unsigned long long traffic_outgoing = 0ULL;
+
+       if ((ud == NULL) || (ud->data == NULL))
+       {
+               ERROR ("mysql plugin: mysql_database_read: Invalid user data.");
+               return (-1);
+       }
+
+       db = (mysql_database_t *) ud->data;
+
+       /* An error message will have been printed in this case */
+       if ((con = getconnection (db)) == NULL)
+               return (-1);
+
+       query = "SHOW STATUS";
+       if (mysql_get_server_version (con) >= 50002)
+               query = "SHOW GLOBAL STATUS";
+
+       res = exec_query (con, query);
+       if (res == NULL)
+               return (-1);
+
+       while ((row = mysql_fetch_row (res)))
+       {
+               char *key;
+               unsigned long long val;
+
+               key = row[0];
+               val = atoll (row[1]);
+
+               if (strncmp (key, "Com_", 
+                                 strlen ("Com_")) == 0)
+               {
+                       if (val == 0ULL)
+                               continue;
+
+                       /* Ignore `prepared statements' */
+                       if (strncmp (key, "Com_stmt_", strlen ("Com_stmt_")) != 0)
+                               counter_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);
+               }
+               else if (strncmp (key, "Qcache_",
+                                               strlen ("Qcache_")) == 0)
+               {
+                       if (strcmp (key, "Qcache_hits") == 0)
+                               qcache_hits = (derive_t) val;
+                       else if (strcmp (key, "Qcache_inserts") == 0)
+                               qcache_inserts = (derive_t) val;
+                       else if (strcmp (key, "Qcache_not_cached") == 0)
+                               qcache_not_cached = (derive_t) val;
+                       else if (strcmp (key, "Qcache_lowmem_prunes") == 0)
+                               qcache_lowmem_prunes = (derive_t) val;
+                       else if (strcmp (key, "Qcache_queries_in_cache") == 0)
+                               qcache_queries_in_cache = (gauge_t) val;
+               }
+               else if (strncmp (key, "Bytes_", 
+                                       strlen ("Bytes_")) == 0)
+               {
+                       if (strcmp (key, "Bytes_received") == 0)
+                               traffic_incoming += val;
+                       else if (strcmp (key, "Bytes_sent") == 0)
+                               traffic_outgoing += val;
+               }
+               else if (strncmp (key, "Threads_", 
+                                               strlen ("Threads_")) == 0)
+               {
+                       if (strcmp (key, "Threads_running") == 0)
+                               threads_running = (gauge_t) val;
+                       else if (strcmp (key, "Threads_connected") == 0)
+                               threads_connected = (gauge_t) val;
+                       else if (strcmp (key, "Threads_cached") == 0)
+                               threads_cached = (gauge_t) val;
+                       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);
+               }
+       }
+       mysql_free_result (res); res = NULL;
+
+       if ((qcache_hits != 0)
+                       || (qcache_inserts != 0)
+                       || (qcache_not_cached != 0)
+                       || (qcache_lowmem_prunes != 0))
+       {
+               derive_submit ("cache_result", "qcache-hits",
+                               qcache_hits, db);
+               derive_submit ("cache_result", "qcache-inserts",
+                               qcache_inserts, db);
+               derive_submit ("cache_result", "qcache-not_cached",
+                               qcache_not_cached, db);
+               derive_submit ("cache_result", "qcache-prunes",
+                               qcache_lowmem_prunes, db);
+
+               gauge_submit ("cache_size", "qcache",
+                               qcache_queries_in_cache, db);
+       }
+
+       if (threads_created != 0)
+       {
+               gauge_submit ("threads", "running",
+                               threads_running, db);
+               gauge_submit ("threads", "connected",
+                               threads_connected, db);
+               gauge_submit ("threads", "cached",
+                               threads_cached, db);
+
+               derive_submit ("total_threads", "created",
+                               threads_created, db);
+       }
+
+       traffic_submit  (traffic_incoming, traffic_outgoing, db);
+
+       if (db->master_stats)
+               mysql_read_master_stats (db, con);
+
+       if ((db->slave_stats) || (db->slave_notif))
+               mysql_read_slave_stats (db, con);
+
+       return (0);
+} /* int mysql_read */
+
+void module_register (void)
+{
+       plugin_register_complex_config ("mysql", mysql_config);
+} /* void module_register */
diff --git a/src/netapp.c b/src/netapp.c
new file mode 100644 (file)
index 0000000..d9bd1ae
--- /dev/null
@@ -0,0 +1,2593 @@
+/**
+ * collectd - src/netapp.c
+ * Copyright (C) 2009  Sven Trenkel
+ *
+ * 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:
+ *   Sven Trenkel <collectd at semidefinite.de>  
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "utils_ignorelist.h"
+
+#include <netapp_api.h>
+#include <netapp_errno.h>
+
+#define HAS_ALL_FLAGS(has,needs) (((has) & (needs)) == (needs))
+
+typedef struct host_config_s host_config_t;
+typedef void service_handler_t(host_config_t *host, na_elem_t *result, void *data);
+
+struct cna_interval_s
+{
+       cdtime_t interval;
+       cdtime_t last_read;
+};
+typedef struct cna_interval_s cna_interval_t;
+
+/*! Data types for WAFL statistics {{{
+ *
+ * \brief Persistent data for WAFL performance counters. (a.k.a. cache performance)
+ *
+ * The cache counters use old counter values to calculate a hit ratio for each
+ * counter. The "cfg_wafl_t" struct therefore contains old counter values along
+ * with flags, which are set if the counter is valid.
+ *
+ * The function "cna_handle_wafl_data" will fill a new structure of this kind
+ * with new values, then pass both, new and old data, to "submit_wafl_data".
+ * That function calculates the hit ratios, submits the calculated values and
+ * updates the old counter values for the next iteration.
+ */
+#define CFG_WAFL_NAME_CACHE        0x0001
+#define CFG_WAFL_DIR_CACHE         0x0002
+#define CFG_WAFL_BUF_CACHE         0x0004
+#define CFG_WAFL_INODE_CACHE       0x0008
+#define CFG_WAFL_ALL               0x000F
+#define HAVE_WAFL_NAME_CACHE_HIT   0x0100
+#define HAVE_WAFL_NAME_CACHE_MISS  0x0200
+#define HAVE_WAFL_NAME_CACHE       (HAVE_WAFL_NAME_CACHE_HIT | HAVE_WAFL_NAME_CACHE_MISS)
+#define HAVE_WAFL_FIND_DIR_HIT     0x0400
+#define HAVE_WAFL_FIND_DIR_MISS    0x0800
+#define HAVE_WAFL_FIND_DIR         (HAVE_WAFL_FIND_DIR_HIT | HAVE_WAFL_FIND_DIR_MISS)
+#define HAVE_WAFL_BUF_HASH_HIT     0x1000
+#define HAVE_WAFL_BUF_HASH_MISS    0x2000
+#define HAVE_WAFL_BUF_HASH         (HAVE_WAFL_BUF_HASH_HIT | HAVE_WAFL_BUF_HASH_MISS)
+#define HAVE_WAFL_INODE_CACHE_HIT  0x4000
+#define HAVE_WAFL_INODE_CACHE_MISS 0x8000
+#define HAVE_WAFL_INODE_CACHE      (HAVE_WAFL_INODE_CACHE_HIT | HAVE_WAFL_INODE_CACHE_MISS)
+#define HAVE_WAFL_ALL              0xff00
+typedef struct {
+       uint32_t flags;
+       cna_interval_t interval;
+       na_elem_t *query;
+
+       cdtime_t timestamp;
+       uint64_t name_cache_hit;
+       uint64_t name_cache_miss;
+       uint64_t find_dir_hit;
+       uint64_t find_dir_miss;
+       uint64_t buf_hash_hit;
+       uint64_t buf_hash_miss;
+       uint64_t inode_cache_hit;
+       uint64_t inode_cache_miss;
+} cfg_wafl_t;
+/* }}} cfg_wafl_t */
+
+/*! Data types for disk statistics {{{
+ *
+ * \brief A disk in the NetApp.
+ *
+ * A disk doesn't have any more information than its name at the moment.
+ * The name includes the "disk_" prefix.
+ */
+#define HAVE_DISK_BUSY   0x10
+#define HAVE_DISK_BASE   0x20
+#define HAVE_DISK_ALL    0x30
+typedef struct disk_s {
+       char *name;
+       uint32_t flags;
+       cdtime_t timestamp;
+       uint64_t disk_busy;
+       uint64_t base_for_disk_busy;
+       double disk_busy_percent;
+       struct disk_s *next;
+} disk_t;
+
+#define CFG_DISK_BUSIEST 0x01
+#define CFG_DISK_ALL     0x01
+typedef struct {
+       uint32_t flags;
+       cna_interval_t interval;
+       na_elem_t *query;
+       disk_t *disks;
+} cfg_disk_t;
+/* }}} cfg_disk_t */
+
+/*! Data types for volume performance statistics {{{
+ *
+ * \brief Persistent data for volume performance data.
+ *
+ * The code below uses the difference of the operations and latency counters to
+ * calculate an average per-operation latency. For this, old counters need to
+ * be stored in the "data_volume_perf_t" structure. The byte-counters are just
+ * kept for completeness sake. The "flags" member indicates if each counter is
+ * valid or not.
+ *
+ * The "cna_handle_volume_perf_data" function will fill a new struct of this
+ * type and pass both, old and new data, to "submit_volume_perf_data". In that
+ * function, the per-operation latency is calculated and dispatched, then the
+ * old counters are updated.
+ */
+#define CFG_VOLUME_PERF_INIT           0x0001
+#define CFG_VOLUME_PERF_IO             0x0002
+#define CFG_VOLUME_PERF_OPS            0x0003
+#define CFG_VOLUME_PERF_LATENCY        0x0008
+#define CFG_VOLUME_PERF_ALL            0x000F
+#define HAVE_VOLUME_PERF_BYTES_READ    0x0010
+#define HAVE_VOLUME_PERF_BYTES_WRITE   0x0020
+#define HAVE_VOLUME_PERF_OPS_READ      0x0040
+#define HAVE_VOLUME_PERF_OPS_WRITE     0x0080
+#define HAVE_VOLUME_PERF_LATENCY_READ  0x0100
+#define HAVE_VOLUME_PERF_LATENCY_WRITE 0x0200
+#define HAVE_VOLUME_PERF_ALL           0x03F0
+struct data_volume_perf_s;
+typedef struct data_volume_perf_s data_volume_perf_t;
+struct data_volume_perf_s {
+       char *name;
+       uint32_t flags;
+       cdtime_t timestamp;
+
+       uint64_t read_bytes;
+       uint64_t write_bytes;
+       uint64_t read_ops;
+       uint64_t write_ops;
+       uint64_t read_latency;
+       uint64_t write_latency;
+
+       data_volume_perf_t *next;
+};
+
+typedef struct {
+       cna_interval_t interval;
+       na_elem_t *query;
+
+       ignorelist_t *il_octets;
+       ignorelist_t *il_operations;
+       ignorelist_t *il_latency;
+
+       data_volume_perf_t *volumes;
+} cfg_volume_perf_t;
+/* }}} data_volume_perf_t */
+
+/*! Data types for volume usage statistics {{{
+ *
+ * \brief Configuration struct for volume usage data (free / used).
+ */
+#define CFG_VOLUME_USAGE_DF             0x0002
+#define CFG_VOLUME_USAGE_SNAP           0x0004
+#define CFG_VOLUME_USAGE_ALL            0x0006
+#define HAVE_VOLUME_USAGE_NORM_FREE     0x0010
+#define HAVE_VOLUME_USAGE_NORM_USED     0x0020
+#define HAVE_VOLUME_USAGE_SNAP_RSVD     0x0040
+#define HAVE_VOLUME_USAGE_SNAP_USED     0x0080
+#define HAVE_VOLUME_USAGE_SIS_SAVED     0x0100
+#define HAVE_VOLUME_USAGE_ALL           0x01f0
+#define IS_VOLUME_USAGE_OFFLINE         0x0200
+struct data_volume_usage_s;
+typedef struct data_volume_usage_s data_volume_usage_t;
+struct data_volume_usage_s {
+       char *name;
+       uint32_t flags;
+
+       na_elem_t *snap_query;
+
+       uint64_t norm_free;
+       uint64_t norm_used;
+       uint64_t snap_reserved;
+       uint64_t snap_used;
+       uint64_t sis_saved;
+
+       data_volume_usage_t *next;
+};
+
+typedef struct {
+       cna_interval_t interval;
+       na_elem_t *query;
+
+       ignorelist_t *il_capacity;
+       ignorelist_t *il_snapshot;
+
+       data_volume_usage_t *volumes;
+} cfg_volume_usage_t;
+/* }}} cfg_volume_usage_t */
+
+/*! Data types for system statistics {{{
+ *
+ * \brief Persistent data for system performance counters
+ */
+#define CFG_SYSTEM_CPU  0x01
+#define CFG_SYSTEM_NET  0x02
+#define CFG_SYSTEM_OPS  0x04
+#define CFG_SYSTEM_DISK 0x08
+#define CFG_SYSTEM_ALL  0x0F
+typedef struct {
+       uint32_t flags;
+       cna_interval_t interval;
+       na_elem_t *query;
+} cfg_system_t;
+/* }}} cfg_system_t */
+
+struct host_config_s {
+       char *name;
+       na_server_transport_t protocol;
+       char *host;
+       int port;
+       char *username;
+       char *password;
+       cdtime_t interval;
+
+       na_server_t *srv;
+       cfg_wafl_t *cfg_wafl;
+       cfg_disk_t *cfg_disk;
+       cfg_volume_perf_t *cfg_volume_perf;
+       cfg_volume_usage_t *cfg_volume_usage;
+       cfg_system_t *cfg_system;
+
+       struct host_config_s *next;
+};
+
+/*
+ * Free functions
+ *
+ * Used to free the various structures above.
+ */
+static void free_disk (disk_t *disk) /* {{{ */
+{
+       disk_t *next;
+
+       if (disk == NULL)
+               return;
+
+       next = disk->next;
+
+       sfree (disk->name);
+       sfree (disk);
+
+       free_disk (next);
+} /* }}} void free_disk */
+
+static void free_cfg_wafl (cfg_wafl_t *cw) /* {{{ */
+{
+       if (cw == NULL)
+               return;
+
+       if (cw->query != NULL)
+               na_elem_free (cw->query);
+
+       sfree (cw);
+} /* }}} void free_cfg_wafl */
+
+static void free_cfg_disk (cfg_disk_t *cfg_disk) /* {{{ */
+{
+       if (cfg_disk == NULL)
+               return;
+
+       if (cfg_disk->query != NULL)
+               na_elem_free (cfg_disk->query);
+
+       free_disk (cfg_disk->disks);
+       sfree (cfg_disk);
+} /* }}} void free_cfg_disk */
+
+static void free_cfg_volume_perf (cfg_volume_perf_t *cvp) /* {{{ */
+{
+       data_volume_perf_t *data;
+
+       if (cvp == NULL)
+               return;
+
+       /* Free the ignorelists */
+       ignorelist_free (cvp->il_octets);
+       ignorelist_free (cvp->il_operations);
+       ignorelist_free (cvp->il_latency);
+
+       /* Free the linked list of volumes */
+       data = cvp->volumes;
+       while (data != NULL)
+       {
+               data_volume_perf_t *next = data->next;
+               sfree (data->name);
+               sfree (data);
+               data = next;
+       }
+
+       if (cvp->query != NULL)
+               na_elem_free (cvp->query);
+
+       sfree (cvp);
+} /* }}} void free_cfg_volume_perf */
+
+static void free_cfg_volume_usage (cfg_volume_usage_t *cvu) /* {{{ */
+{
+       data_volume_usage_t *data;
+
+       if (cvu == NULL)
+               return;
+
+       /* Free the ignorelists */
+       ignorelist_free (cvu->il_capacity);
+       ignorelist_free (cvu->il_snapshot);
+
+       /* Free the linked list of volumes */
+       data = cvu->volumes;
+       while (data != NULL)
+       {
+               data_volume_usage_t *next = data->next;
+               sfree (data->name);
+               if (data->snap_query != NULL)
+                       na_elem_free(data->snap_query);
+               sfree (data);
+               data = next;
+       }
+
+       if (cvu->query != NULL)
+               na_elem_free (cvu->query);
+
+       sfree (cvu);
+} /* }}} void free_cfg_volume_usage */
+
+static void free_cfg_system (cfg_system_t *cs) /* {{{ */
+{
+       if (cs == NULL)
+               return;
+
+       if (cs->query != NULL)
+               na_elem_free (cs->query);
+
+       sfree (cs);
+} /* }}} void free_cfg_system */
+
+static void free_host_config (host_config_t *hc) /* {{{ */
+{
+       host_config_t *next;
+
+       if (hc == NULL)
+               return;
+
+       next = hc->next;
+
+       sfree (hc->name);
+       sfree (hc->host);
+       sfree (hc->username);
+       sfree (hc->password);
+
+       free_cfg_disk (hc->cfg_disk);
+       free_cfg_wafl (hc->cfg_wafl);
+       free_cfg_volume_perf (hc->cfg_volume_perf);
+       free_cfg_volume_usage (hc->cfg_volume_usage);
+       free_cfg_system (hc->cfg_system);
+
+       if (hc->srv != NULL)
+               na_server_close (hc->srv);
+
+       sfree (hc);
+
+       free_host_config (next);
+} /* }}} void free_host_config */
+
+/*
+ * Auxiliary functions
+ *
+ * Used to look up volumes and disks or to handle flags.
+ */
+static disk_t *get_disk(cfg_disk_t *cd, const char *name) /* {{{ */
+{
+       disk_t *d;
+
+       if ((cd == NULL) || (name == NULL))
+               return (NULL);
+
+       for (d = cd->disks; d != NULL; d = d->next) {
+               if (strcmp(d->name, name) == 0)
+                       return d;
+       }
+
+       d = malloc(sizeof(*d));
+       if (d == NULL)
+               return (NULL);
+       memset (d, 0, sizeof (*d));
+       d->next = NULL;
+
+       d->name = strdup(name);
+       if (d->name == NULL) {
+               sfree (d);
+               return (NULL);
+       }
+
+       d->next = cd->disks;
+       cd->disks = d;
+
+       return d;
+} /* }}} disk_t *get_disk */
+
+static data_volume_usage_t *get_volume_usage (cfg_volume_usage_t *cvu, /* {{{ */
+               const char *name)
+{
+       data_volume_usage_t *last;
+       data_volume_usage_t *new;
+
+       int ignore_capacity = 0;
+       int ignore_snapshot = 0;
+
+       if ((cvu == NULL) || (name == NULL))
+               return (NULL);
+
+       last = cvu->volumes;
+       while (last != NULL)
+       {
+               if (strcmp (last->name, name) == 0)
+                       return (last);
+
+               if (last->next == NULL)
+                       break;
+
+               last = last->next;
+       }
+
+       /* Check the ignorelists. If *both* tell us to ignore a volume, return NULL. */
+       ignore_capacity = ignorelist_match (cvu->il_capacity, name);
+       ignore_snapshot = ignorelist_match (cvu->il_snapshot, name);
+       if ((ignore_capacity != 0) && (ignore_snapshot != 0))
+               return (NULL);
+
+       /* Not found: allocate. */
+       new = malloc (sizeof (*new));
+       if (new == NULL)
+               return (NULL);
+       memset (new, 0, sizeof (*new));
+       new->next = NULL;
+
+       new->name = strdup (name);
+       if (new->name == NULL)
+       {
+               sfree (new);
+               return (NULL);
+       }
+
+       if (ignore_capacity == 0)
+               new->flags |= CFG_VOLUME_USAGE_DF;
+       if (ignore_snapshot == 0) {
+               new->flags |= CFG_VOLUME_USAGE_SNAP;
+               new->snap_query = na_elem_new ("snapshot-list-info");
+               na_child_add_string(new->snap_query, "target-type", "volume");
+               na_child_add_string(new->snap_query, "target-name", name);
+       } else {
+               new->snap_query = NULL;
+       }
+
+       /* Add to end of list. */
+       if (last == NULL)
+               cvu->volumes = new;
+       else
+               last->next = new;
+
+       return (new);
+} /* }}} data_volume_usage_t *get_volume_usage */
+
+static data_volume_perf_t *get_volume_perf (cfg_volume_perf_t *cvp, /* {{{ */
+               const char *name)
+{
+       data_volume_perf_t *last;
+       data_volume_perf_t *new;
+
+       int ignore_octets = 0;
+       int ignore_operations = 0;
+       int ignore_latency = 0;
+
+       if ((cvp == NULL) || (name == NULL))
+               return (NULL);
+
+       last = cvp->volumes;
+       while (last != NULL)
+       {
+               if (strcmp (last->name, name) == 0)
+                       return (last);
+
+               if (last->next == NULL)
+                       break;
+
+               last = last->next;
+       }
+
+       /* Check the ignorelists. If *all three* tell us to ignore a volume, return
+        * NULL. */
+       ignore_octets = ignorelist_match (cvp->il_octets, name);
+       ignore_operations = ignorelist_match (cvp->il_operations, name);
+       ignore_latency = ignorelist_match (cvp->il_latency, name);
+       if ((ignore_octets != 0) || (ignore_operations != 0)
+                       || (ignore_latency != 0))
+               return (NULL);
+
+       /* Not found: allocate. */
+       new = malloc (sizeof (*new));
+       if (new == NULL)
+               return (NULL);
+       memset (new, 0, sizeof (*new));
+       new->next = NULL;
+
+       new->name = strdup (name);
+       if (new->name == NULL)
+       {
+               sfree (new);
+               return (NULL);
+       }
+
+       if (ignore_octets == 0)
+               new->flags |= CFG_VOLUME_PERF_IO;
+       if (ignore_operations == 0)
+               new->flags |= CFG_VOLUME_PERF_OPS;
+       if (ignore_latency == 0)
+               new->flags |= CFG_VOLUME_PERF_LATENCY;
+
+       /* Add to end of list. */
+       if (last == NULL)
+               cvp->volumes = new;
+       else
+               last->next = new;
+
+       return (new);
+} /* }}} data_volume_perf_t *get_volume_perf */
+
+/*
+ * Various submit functions.
+ *
+ * They all eventually call "submit_values" which creates a value_list_t and
+ * dispatches it to the daemon.
+ */
+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)
+{
+       value_list_t vl = VALUE_LIST_INIT;
+
+       vl.values = values;
+       vl.values_len = values_len;
+
+       if (timestamp > 0)
+               vl.time = timestamp;
+
+       if (interval > 0)
+               vl.interval = interval;
+
+       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));
+       sstrncpy (vl.type, type, sizeof (vl.type));
+       if (type_inst != NULL)
+               sstrncpy (vl.type_instance, type_inst, sizeof (vl.type_instance));
+
+       return (plugin_dispatch_values (&vl));
+} /* }}} int submit_uint64 */
+
+static int submit_two_derive (const char *host, const char *plugin_inst, /* {{{ */
+               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;
+
+       return (submit_values (host, plugin_inst, type, type_inst,
+                               values, 2, 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));
+} /* }}} 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];
+
+       values[0].gauge = val0;
+       values[1].gauge = val1;
+
+       return (submit_values (host, plugin_inst, type, type_inst,
+                               values, 2, 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));
+} /* }}} int submit_uint64 */
+
+/* Calculate hit ratio from old and new counters and submit the resulting
+ * percentage. Used by "submit_wafl_data". */
+static int submit_cache_ratio (const char *host, /* {{{ */
+               const char *plugin_inst,
+               const char *type_inst,
+               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;
+
+       if ((new_hits >= old_hits) && (new_misses >= old_misses)) {
+               uint64_t hits;
+               uint64_t misses;
+
+               hits = new_hits - old_hits;
+               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, timestamp, interval));
+} /* }}} int submit_cache_ratio */
+
+/* Submits all the caches used by WAFL. Uses "submit_cache_ratio". */
+static int submit_wafl_data (const char *hostname, const char *instance, /* {{{ */
+               cfg_wafl_t *old_data, const cfg_wafl_t *new_data, int interval)
+{
+       /* Submit requested counters */
+       if (HAS_ALL_FLAGS (old_data->flags, CFG_WAFL_NAME_CACHE | HAVE_WAFL_NAME_CACHE)
+                       && HAS_ALL_FLAGS (new_data->flags, HAVE_WAFL_NAME_CACHE))
+               submit_cache_ratio (hostname, instance, "name_cache_hit",
+                               new_data->name_cache_hit, new_data->name_cache_miss,
+                               old_data->name_cache_hit, old_data->name_cache_miss,
+                               new_data->timestamp, interval);
+
+       if (HAS_ALL_FLAGS (old_data->flags, CFG_WAFL_DIR_CACHE | HAVE_WAFL_FIND_DIR)
+                       && HAS_ALL_FLAGS (new_data->flags, HAVE_WAFL_FIND_DIR))
+               submit_cache_ratio (hostname, instance, "find_dir_hit",
+                               new_data->find_dir_hit, new_data->find_dir_miss,
+                               old_data->find_dir_hit, old_data->find_dir_miss,
+                               new_data->timestamp, interval);
+
+       if (HAS_ALL_FLAGS (old_data->flags, CFG_WAFL_BUF_CACHE | HAVE_WAFL_BUF_HASH)
+                       && HAS_ALL_FLAGS (new_data->flags, HAVE_WAFL_BUF_HASH))
+               submit_cache_ratio (hostname, instance, "buf_hash_hit",
+                               new_data->buf_hash_hit, new_data->buf_hash_miss,
+                               old_data->buf_hash_hit, old_data->buf_hash_miss,
+                               new_data->timestamp, interval);
+
+       if (HAS_ALL_FLAGS (old_data->flags, CFG_WAFL_INODE_CACHE | HAVE_WAFL_INODE_CACHE)
+                       && HAS_ALL_FLAGS (new_data->flags, HAVE_WAFL_INODE_CACHE))
+               submit_cache_ratio (hostname, instance, "inode_cache_hit",
+                               new_data->inode_cache_hit, new_data->inode_cache_miss,
+                               old_data->inode_cache_hit, old_data->inode_cache_miss,
+                               new_data->timestamp, interval);
+
+       /* Clear old HAVE_* flags */
+       old_data->flags &= ~HAVE_WAFL_ALL;
+
+       /* Copy all counters */
+       old_data->timestamp        = new_data->timestamp;
+       old_data->name_cache_hit   = new_data->name_cache_hit;
+       old_data->name_cache_miss  = new_data->name_cache_miss;
+       old_data->find_dir_hit     = new_data->find_dir_hit;
+       old_data->find_dir_miss    = new_data->find_dir_miss;
+       old_data->buf_hash_hit     = new_data->buf_hash_hit;
+       old_data->buf_hash_miss    = new_data->buf_hash_miss;
+       old_data->inode_cache_hit  = new_data->inode_cache_hit;
+       old_data->inode_cache_miss = new_data->inode_cache_miss;
+
+       /* Copy HAVE_* flags */
+       old_data->flags |= (new_data->flags & HAVE_WAFL_ALL);
+
+       return (0);
+} /* }}} int submit_wafl_data */
+
+/* Submits volume performance data to the daemon, taking care to honor and
+ * update flags appropriately. */
+static int submit_volume_perf_data (const char *hostname, /* {{{ */
+               data_volume_perf_t *old_data,
+               const data_volume_perf_t *new_data, int interval)
+{
+       char plugin_instance[DATA_MAX_NAME_LEN];
+
+       if ((hostname == NULL) || (old_data == NULL) || (new_data == NULL))
+               return (-1);
+
+       ssnprintf (plugin_instance, sizeof (plugin_instance),
+                       "volume-%s", old_data->name);
+
+       /* Check for and submit disk-octet values */
+       if (HAS_ALL_FLAGS (old_data->flags, CFG_VOLUME_PERF_IO)
+                       && HAS_ALL_FLAGS (new_data->flags, HAVE_VOLUME_PERF_BYTES_READ | HAVE_VOLUME_PERF_BYTES_WRITE))
+       {
+               submit_two_derive (hostname, plugin_instance, "disk_octets", /* type instance = */ NULL,
+                               (derive_t) new_data->read_bytes, (derive_t) new_data->write_bytes, new_data->timestamp, interval);
+       }
+
+       /* Check for and submit disk-operations values */
+       if (HAS_ALL_FLAGS (old_data->flags, CFG_VOLUME_PERF_OPS)
+                       && HAS_ALL_FLAGS (new_data->flags, HAVE_VOLUME_PERF_OPS_READ | HAVE_VOLUME_PERF_OPS_WRITE))
+       {
+               submit_two_derive (hostname, plugin_instance, "disk_ops", /* type instance = */ NULL,
+                               (derive_t) new_data->read_ops, (derive_t) new_data->write_ops, new_data->timestamp, interval);
+       }
+
+       /* Check for, calculate and submit disk-latency values */
+       if (HAS_ALL_FLAGS (old_data->flags, CFG_VOLUME_PERF_LATENCY
+                               | HAVE_VOLUME_PERF_OPS_READ | HAVE_VOLUME_PERF_OPS_WRITE
+                               | HAVE_VOLUME_PERF_LATENCY_READ | HAVE_VOLUME_PERF_LATENCY_WRITE)
+                       && HAS_ALL_FLAGS (new_data->flags, HAVE_VOLUME_PERF_OPS_READ | HAVE_VOLUME_PERF_OPS_WRITE
+                               | HAVE_VOLUME_PERF_LATENCY_READ | HAVE_VOLUME_PERF_LATENCY_WRITE))
+       {
+               gauge_t latency_per_op_read;
+               gauge_t latency_per_op_write;
+
+               latency_per_op_read = NAN;
+               latency_per_op_write = NAN;
+
+               /* Check if a counter wrapped around. */
+               if ((new_data->read_ops > old_data->read_ops)
+                               && (new_data->read_latency > old_data->read_latency))
+               {
+                       uint64_t diff_ops_read;
+                       uint64_t diff_latency_read;
+
+                       diff_ops_read = new_data->read_ops - old_data->read_ops;
+                       diff_latency_read = new_data->read_latency - old_data->read_latency;
+
+                       if (diff_ops_read > 0)
+                               latency_per_op_read = ((gauge_t) diff_latency_read) / ((gauge_t) diff_ops_read);
+               }
+
+               if ((new_data->write_ops > old_data->write_ops)
+                               && (new_data->write_latency > old_data->write_latency))
+               {
+                       uint64_t diff_ops_write;
+                       uint64_t diff_latency_write;
+
+                       diff_ops_write = new_data->write_ops - old_data->write_ops;
+                       diff_latency_write = new_data->write_latency - old_data->write_latency;
+
+                       if (diff_ops_write > 0)
+                               latency_per_op_write = ((gauge_t) diff_latency_write) / ((gauge_t) diff_ops_write);
+               }
+
+               submit_two_gauge (hostname, plugin_instance, "disk_latency", /* type instance = */ NULL,
+                               latency_per_op_read, latency_per_op_write, new_data->timestamp, interval);
+       }
+
+       /* Clear all HAVE_* flags. */
+       old_data->flags &= ~HAVE_VOLUME_PERF_ALL;
+
+       /* Copy all counters */
+       old_data->timestamp = new_data->timestamp;
+       old_data->read_bytes = new_data->read_bytes;
+       old_data->write_bytes = new_data->write_bytes;
+       old_data->read_ops = new_data->read_ops;
+       old_data->write_ops = new_data->write_ops;
+       old_data->read_latency = new_data->read_latency;
+       old_data->write_latency = new_data->write_latency;
+
+       /* Copy the HAVE_* flags */
+       old_data->flags |= (new_data->flags & HAVE_VOLUME_PERF_ALL);
+
+       return (0);
+} /* }}} int submit_volume_perf_data */
+
+static cdtime_t cna_child_get_cdtime (na_elem_t *data) /* {{{ */
+{
+       time_t t;
+
+       t = (time_t) na_child_get_uint64 (data, "timestamp", /* default = */ 0);
+
+       return (TIME_T_TO_CDTIME_T (t));
+} /* }}} cdtime_t cna_child_get_cdtime */
+
+
+/* 
+ * Query functions
+ *
+ * These functions are called with appropriate data returned by the libnetapp
+ * interface which is parsed and submitted with the above functions.
+ */
+/* Data corresponding to <WAFL /> */
+static int cna_handle_wafl_data (const char *hostname, cfg_wafl_t *cfg_wafl, /* {{{ */
+               na_elem_t *data, int interval)
+{
+       cfg_wafl_t perf_data;
+       const char *plugin_inst;
+
+       na_elem_t *instances;
+       na_elem_t *counter;
+       na_elem_iter_t counter_iter;
+
+       memset (&perf_data, 0, sizeof (perf_data));
+       
+       perf_data.timestamp = cna_child_get_cdtime (data);
+
+       instances = na_elem_child(na_elem_child (data, "instances"), "instance-data");
+       if (instances == NULL)
+       {
+               ERROR ("netapp plugin: cna_handle_wafl_data: "
+                               "na_elem_child (\"instances\") failed "
+                               "for host %s.", hostname);
+               return (-1);
+       }
+
+       plugin_inst = na_child_get_string(instances, "name");
+       if (plugin_inst == NULL)
+       {
+               ERROR ("netapp plugin: cna_handle_wafl_data: "
+                               "na_child_get_string (\"name\") failed "
+                               "for host %s.", hostname);
+               return (-1);
+       }
+
+       /* Iterate over all counters */
+       counter_iter = na_child_iterator (na_elem_child (instances, "counters"));
+       for (counter = na_iterator_next (&counter_iter);
+                       counter != NULL;
+                       counter = na_iterator_next (&counter_iter))
+       {
+               const char *name;
+               uint64_t value;
+
+               name = na_child_get_string(counter, "name");
+               if (name == NULL)
+                       continue;
+
+               value = na_child_get_uint64(counter, "value", UINT64_MAX);
+               if (value == UINT64_MAX)
+                       continue;
+
+               if (!strcmp(name, "name_cache_hit")) {
+                       perf_data.name_cache_hit = value;
+                       perf_data.flags |= HAVE_WAFL_NAME_CACHE_HIT;
+               } else if (!strcmp(name, "name_cache_miss")) {
+                       perf_data.name_cache_miss = value;
+                       perf_data.flags |= HAVE_WAFL_NAME_CACHE_MISS;
+               } else if (!strcmp(name, "find_dir_hit")) {
+                       perf_data.find_dir_hit = value;
+                       perf_data.flags |= HAVE_WAFL_FIND_DIR_HIT;
+               } else if (!strcmp(name, "find_dir_miss")) {
+                       perf_data.find_dir_miss = value;
+                       perf_data.flags |= HAVE_WAFL_FIND_DIR_MISS;
+               } else if (!strcmp(name, "buf_hash_hit")) {
+                       perf_data.buf_hash_hit = value;
+                       perf_data.flags |= HAVE_WAFL_BUF_HASH_HIT;
+               } else if (!strcmp(name, "buf_hash_miss")) {
+                       perf_data.buf_hash_miss = value;
+                       perf_data.flags |= HAVE_WAFL_BUF_HASH_MISS;
+               } else if (!strcmp(name, "inode_cache_hit")) {
+                       perf_data.inode_cache_hit = value;
+                       perf_data.flags |= HAVE_WAFL_INODE_CACHE_HIT;
+               } else if (!strcmp(name, "inode_cache_miss")) {
+                       perf_data.inode_cache_miss = value;
+                       perf_data.flags |= HAVE_WAFL_INODE_CACHE_MISS;
+               } else {
+                       DEBUG("netapp plugin: cna_handle_wafl_data: "
+                                       "Found unexpected child: %s "
+                                       "for host %s.", name, hostname);
+               }
+       }
+
+       return (submit_wafl_data (hostname, plugin_inst, cfg_wafl, &perf_data, interval));
+} /* }}} void cna_handle_wafl_data */
+
+static int cna_setup_wafl (cfg_wafl_t *cw) /* {{{ */
+{
+       na_elem_t *e;
+
+       if (cw == NULL)
+               return (EINVAL);
+
+       if (cw->query != NULL)
+               return (0);
+
+       cw->query = na_elem_new("perf-object-get-instances");
+       if (cw->query == NULL)
+       {
+               ERROR ("netapp plugin: na_elem_new failed.");
+               return (-1);
+       }
+       na_child_add_string (cw->query, "objectname", "wafl");
+
+       e = na_elem_new("counters");
+       if (e == NULL)
+       {
+               na_elem_free (cw->query);
+               cw->query = NULL;
+               ERROR ("netapp plugin: na_elem_new failed.");
+               return (-1);
+       }
+       na_child_add_string(e, "counter", "name_cache_hit");
+       na_child_add_string(e, "counter", "name_cache_miss");
+       na_child_add_string(e, "counter", "find_dir_hit");
+       na_child_add_string(e, "counter", "find_dir_miss");
+       na_child_add_string(e, "counter", "buf_hash_hit");
+       na_child_add_string(e, "counter", "buf_hash_miss");
+       na_child_add_string(e, "counter", "inode_cache_hit");
+       na_child_add_string(e, "counter", "inode_cache_miss");
+
+       na_child_add(cw->query, e);
+
+       return (0);
+} /* }}} int cna_setup_wafl */
+
+static int cna_query_wafl (host_config_t *host) /* {{{ */
+{
+       na_elem_t *data;
+       int status;
+       cdtime_t now;
+
+       if (host == NULL)
+               return (EINVAL);
+
+       /* If WAFL was not configured, return without doing anything. */
+       if (host->cfg_wafl == NULL)
+               return (0);
+
+       now = cdtime ();
+       if ((host->cfg_wafl->interval.interval + host->cfg_wafl->interval.last_read) > now)
+               return (0);
+
+       status = cna_setup_wafl (host->cfg_wafl);
+       if (status != 0)
+               return (status);
+       assert (host->cfg_wafl->query != NULL);
+
+       data = na_server_invoke_elem(host->srv, host->cfg_wafl->query);
+       if (na_results_status (data) != NA_OK)
+       {
+               ERROR ("netapp plugin: cna_query_wafl: na_server_invoke_elem failed for host %s: %s",
+                               host->name, na_results_reason (data));
+               na_elem_free (data);
+               return (-1);
+       }
+
+       status = cna_handle_wafl_data (host->name, host->cfg_wafl, data, host->interval);
+
+       if (status == 0)
+               host->cfg_wafl->interval.last_read = now;
+
+       na_elem_free (data);
+       return (status);
+} /* }}} int cna_query_wafl */
+
+/* Data corresponding to <Disks /> */
+static int cna_handle_disk_data (const char *hostname, /* {{{ */
+               cfg_disk_t *cfg_disk, na_elem_t *data, cdtime_t interval)
+{
+       cdtime_t timestamp;
+       na_elem_t *instances;
+       na_elem_t *instance;
+       na_elem_iter_t instance_iter;
+       disk_t *worst_disk = NULL;
+
+       if ((cfg_disk == NULL) || (data == NULL))
+               return (EINVAL);
+       
+       timestamp = cna_child_get_cdtime (data);
+
+       instances = na_elem_child (data, "instances");
+       if (instances == NULL)
+       {
+               ERROR ("netapp plugin: cna_handle_disk_data: "
+                               "na_elem_child (\"instances\") failed "
+                               "for host %s.", hostname);
+               return (-1);
+       }
+
+       /* Iterate over all children */
+       instance_iter = na_child_iterator (instances);
+       for (instance = na_iterator_next (&instance_iter);
+                       instance != NULL;
+                       instance = na_iterator_next(&instance_iter))
+       {
+               disk_t *old_data;
+               disk_t  new_data;
+
+               na_elem_iter_t counter_iterator;
+               na_elem_t *counter;
+
+               memset (&new_data, 0, sizeof (new_data));
+               new_data.timestamp = timestamp;
+               new_data.disk_busy_percent = NAN;
+
+               old_data = get_disk(cfg_disk, na_child_get_string (instance, "name"));
+               if (old_data == NULL)
+                       continue;
+
+               /* Look for the "disk_busy" and "base_for_disk_busy" counters */
+               counter_iterator = na_child_iterator(na_elem_child(instance, "counters"));
+               for (counter = na_iterator_next(&counter_iterator);
+                               counter != NULL;
+                               counter = na_iterator_next(&counter_iterator))
+               {
+                       const char *name;
+                       uint64_t value;
+
+                       name = na_child_get_string(counter, "name");
+                       if (name == NULL)
+                               continue;
+
+                       value = na_child_get_uint64(counter, "value", UINT64_MAX);
+                       if (value == UINT64_MAX)
+                               continue;
+
+                       if (strcmp(name, "disk_busy") == 0)
+                       {
+                               new_data.disk_busy = value;
+                               new_data.flags |= HAVE_DISK_BUSY;
+                       }
+                       else if (strcmp(name, "base_for_disk_busy") == 0)
+                       {
+                               new_data.base_for_disk_busy = value;
+                               new_data.flags |= HAVE_DISK_BASE;
+                       }
+                       else
+                       {
+                               DEBUG ("netapp plugin: cna_handle_disk_data: "
+                                               "Counter not handled: %s = %"PRIu64,
+                                               name, value);
+                       }
+               }
+
+               /* If all required counters are available and did not just wrap around,
+                * calculate the busy percentage. Otherwise, the value is initialized to
+                * NAN at the top of the for-loop. */
+               if (HAS_ALL_FLAGS (old_data->flags, HAVE_DISK_BUSY | HAVE_DISK_BASE)
+                               && HAS_ALL_FLAGS (new_data.flags, HAVE_DISK_BUSY | HAVE_DISK_BASE)
+                               && (new_data.disk_busy >= old_data->disk_busy)
+                               && (new_data.base_for_disk_busy > old_data->base_for_disk_busy))
+               {
+                       uint64_t busy_diff;
+                       uint64_t base_diff;
+
+                       busy_diff = new_data.disk_busy - old_data->disk_busy;
+                       base_diff = new_data.base_for_disk_busy - old_data->base_for_disk_busy;
+
+                       new_data.disk_busy_percent = 100.0
+                               * ((gauge_t) busy_diff) / ((gauge_t) base_diff);
+               }
+
+               /* Clear HAVE_* flags */
+               old_data->flags &= ~HAVE_DISK_ALL;
+
+               /* Copy data */
+               old_data->timestamp = new_data.timestamp;
+               old_data->disk_busy = new_data.disk_busy;
+               old_data->base_for_disk_busy = new_data.base_for_disk_busy;
+               old_data->disk_busy_percent = new_data.disk_busy_percent;
+
+               /* Copy flags */
+               old_data->flags |= (new_data.flags & HAVE_DISK_ALL);
+
+               if ((worst_disk == NULL)
+                               || (worst_disk->disk_busy_percent < old_data->disk_busy_percent))
+                       worst_disk = old_data;
+       } /* for (all disks) */
+
+       if ((cfg_disk->flags & CFG_DISK_BUSIEST) && (worst_disk != NULL))
+               submit_double (hostname, "system", "percent", "disk_busy",
+                               worst_disk->disk_busy_percent, timestamp, interval);
+
+       return (0);
+} /* }}} int cna_handle_disk_data */
+
+static int cna_setup_disk (cfg_disk_t *cd) /* {{{ */
+{
+       na_elem_t *e;
+
+       if (cd == NULL)
+               return (EINVAL);
+
+       if (cd->query != NULL)
+               return (0);
+
+       cd->query = na_elem_new ("perf-object-get-instances");
+       if (cd->query == NULL)
+       {
+               ERROR ("netapp plugin: na_elem_new failed.");
+               return (-1);
+       }
+       na_child_add_string (cd->query, "objectname", "disk");
+
+       e = na_elem_new("counters");
+       if (e == NULL)
+       {
+               na_elem_free (cd->query);
+               cd->query = NULL;
+               ERROR ("netapp plugin: na_elem_new failed.");
+               return (-1);
+       }
+       na_child_add_string(e, "counter", "disk_busy");
+       na_child_add_string(e, "counter", "base_for_disk_busy");
+       na_child_add(cd->query, e);
+
+       return (0);
+} /* }}} int cna_setup_disk */
+
+static int cna_query_disk (host_config_t *host) /* {{{ */
+{
+       na_elem_t *data;
+       int status;
+       cdtime_t now;
+
+       if (host == NULL)
+               return (EINVAL);
+
+       /* If the user did not configure disk statistics, return without doing
+        * anything. */
+       if (host->cfg_disk == NULL)
+               return (0);
+
+       now = cdtime ();
+       if ((host->cfg_disk->interval.interval + host->cfg_disk->interval.last_read) > now)
+               return (0);
+
+       status = cna_setup_disk (host->cfg_disk);
+       if (status != 0)
+               return (status);
+       assert (host->cfg_disk->query != NULL);
+
+       data = na_server_invoke_elem(host->srv, host->cfg_disk->query);
+       if (na_results_status (data) != NA_OK)
+       {
+               ERROR ("netapp plugin: cna_query_disk: na_server_invoke_elem failed for host %s: %s",
+                               host->name, na_results_reason (data));
+               na_elem_free (data);
+               return (-1);
+       }
+
+       status = cna_handle_disk_data (host->name, host->cfg_disk, data, host->interval);
+
+       if (status == 0)
+               host->cfg_disk->interval.last_read = now;
+
+       na_elem_free (data);
+       return (status);
+} /* }}} int cna_query_disk */
+
+/* Data corresponding to <VolumePerf /> */
+static int cna_handle_volume_perf_data (const char *hostname, /* {{{ */
+               cfg_volume_perf_t *cvp, na_elem_t *data, cdtime_t interval)
+{
+       cdtime_t timestamp;
+       na_elem_t *elem_instances;
+       na_elem_iter_t iter_instances;
+       na_elem_t *elem_instance;
+       
+       timestamp = cna_child_get_cdtime (data);
+
+       elem_instances = na_elem_child(data, "instances");
+       if (elem_instances == NULL)
+       {
+               ERROR ("netapp plugin: handle_volume_perf_data: "
+                               "na_elem_child (\"instances\") failed "
+                               "for host %s.", hostname);
+               return (-1);
+       }
+
+       iter_instances = na_child_iterator (elem_instances);
+       for (elem_instance = na_iterator_next(&iter_instances);
+                       elem_instance != NULL;
+                       elem_instance = na_iterator_next(&iter_instances))
+       {
+               const char *name;
+
+               data_volume_perf_t perf_data;
+               data_volume_perf_t *v;
+
+               na_elem_t *elem_counters;
+               na_elem_iter_t iter_counters;
+               na_elem_t *elem_counter;
+
+               memset (&perf_data, 0, sizeof (perf_data));
+               perf_data.timestamp = timestamp;
+
+               name = na_child_get_string (elem_instance, "name");
+               if (name == NULL)
+                       continue;
+
+               /* get_volume_perf may return NULL if this volume is to be ignored. */
+               v = get_volume_perf (cvp, name);
+               if (v == NULL)
+                       continue;
+
+               elem_counters = na_elem_child (elem_instance, "counters");
+               if (elem_counters == NULL)
+                       continue;
+
+               iter_counters = na_child_iterator (elem_counters);
+               for (elem_counter = na_iterator_next(&iter_counters);
+                               elem_counter != NULL;
+                               elem_counter = na_iterator_next(&iter_counters))
+               {
+                       const char *name;
+                       uint64_t value;
+
+                       name = na_child_get_string (elem_counter, "name");
+                       if (name == NULL)
+                               continue;
+
+                       value = na_child_get_uint64 (elem_counter, "value", UINT64_MAX);
+                       if (value == UINT64_MAX)
+                               continue;
+
+                       if (!strcmp(name, "read_data")) {
+                               perf_data.read_bytes = value;
+                               perf_data.flags |= HAVE_VOLUME_PERF_BYTES_READ;
+                       } else if (!strcmp(name, "write_data")) {
+                               perf_data.write_bytes = value;
+                               perf_data.flags |= HAVE_VOLUME_PERF_BYTES_WRITE;
+                       } else if (!strcmp(name, "read_ops")) {
+                               perf_data.read_ops = value;
+                               perf_data.flags |= HAVE_VOLUME_PERF_OPS_READ;
+                       } else if (!strcmp(name, "write_ops")) {
+                               perf_data.write_ops = value;
+                               perf_data.flags |= HAVE_VOLUME_PERF_OPS_WRITE;
+                       } else if (!strcmp(name, "read_latency")) {
+                               perf_data.read_latency = value;
+                               perf_data.flags |= HAVE_VOLUME_PERF_LATENCY_READ;
+                       } else if (!strcmp(name, "write_latency")) {
+                               perf_data.write_latency = value;
+                               perf_data.flags |= HAVE_VOLUME_PERF_LATENCY_WRITE;
+                       }
+               } /* for (elem_counter) */
+
+               submit_volume_perf_data (hostname, v, &perf_data, interval);
+       } /* for (volume) */
+
+       return (0);
+} /* }}} int cna_handle_volume_perf_data */
+
+static int cna_setup_volume_perf (cfg_volume_perf_t *cd) /* {{{ */
+{
+       na_elem_t *e;
+
+       if (cd == NULL)
+               return (EINVAL);
+
+       if (cd->query != NULL)
+               return (0);
+
+       cd->query = na_elem_new ("perf-object-get-instances");
+       if (cd->query == NULL)
+       {
+               ERROR ("netapp plugin: na_elem_new failed.");
+               return (-1);
+       }
+       na_child_add_string (cd->query, "objectname", "volume");
+
+       e = na_elem_new("counters");
+       if (e == NULL)
+       {
+               na_elem_free (cd->query);
+               cd->query = NULL;
+               ERROR ("netapp plugin: na_elem_new failed.");
+               return (-1);
+       }
+       na_child_add_string(e, "counter", "read_ops");
+       na_child_add_string(e, "counter", "write_ops");
+       na_child_add_string(e, "counter", "read_data");
+       na_child_add_string(e, "counter", "write_data");
+       na_child_add_string(e, "counter", "read_latency");
+       na_child_add_string(e, "counter", "write_latency");
+       na_child_add(cd->query, e);
+
+       return (0);
+} /* }}} int cna_setup_volume_perf */
+
+static int cna_query_volume_perf (host_config_t *host) /* {{{ */
+{
+       na_elem_t *data;
+       int status;
+       cdtime_t now;
+
+       if (host == NULL)
+               return (EINVAL);
+
+       /* If the user did not configure volume performance statistics, return
+        * without doing anything. */
+       if (host->cfg_volume_perf == NULL)
+               return (0);
+
+       now = cdtime ();
+       if ((host->cfg_volume_perf->interval.interval + host->cfg_volume_perf->interval.last_read) > now)
+               return (0);
+
+       status = cna_setup_volume_perf (host->cfg_volume_perf);
+       if (status != 0)
+               return (status);
+       assert (host->cfg_volume_perf->query != NULL);
+
+       data = na_server_invoke_elem (host->srv, host->cfg_volume_perf->query);
+       if (na_results_status (data) != NA_OK)
+       {
+               ERROR ("netapp plugin: cna_query_volume_perf: na_server_invoke_elem failed for host %s: %s",
+                               host->name, na_results_reason (data));
+               na_elem_free (data);
+               return (-1);
+       }
+
+       status = cna_handle_volume_perf_data (host->name, host->cfg_volume_perf, data, host->interval);
+
+       if (status == 0)
+               host->cfg_volume_perf->interval.last_read = now;
+
+       na_elem_free (data);
+       return (status);
+} /* }}} int cna_query_volume_perf */
+
+/* Data corresponding to <VolumeUsage /> */
+static int cna_submit_volume_usage_data (const char *hostname, /* {{{ */
+               cfg_volume_usage_t *cfg_volume, int interval)
+{
+       data_volume_usage_t *v;
+
+       for (v = cfg_volume->volumes; v != NULL; v = v->next)
+       {
+               char plugin_instance[DATA_MAX_NAME_LEN];
+
+               uint64_t norm_used = v->norm_used;
+               uint64_t norm_free = v->norm_free;
+               uint64_t sis_saved = v->sis_saved;
+               uint64_t snap_reserve_used = 0;
+               uint64_t snap_reserve_free = v->snap_reserved;
+               uint64_t snap_norm_used = v->snap_used;
+
+               ssnprintf (plugin_instance, sizeof (plugin_instance),
+                               "volume-%s", v->name);
+
+               if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_SNAP_USED | HAVE_VOLUME_USAGE_SNAP_RSVD)) {
+                       if (v->snap_reserved > v->snap_used) {
+                               snap_reserve_free = v->snap_reserved - v->snap_used;
+                               snap_reserve_used = v->snap_used;
+                               snap_norm_used = 0;
+                       } else {
+                               snap_reserve_free = 0;
+                               snap_reserve_used = v->snap_reserved;
+                               snap_norm_used = v->snap_used - v->snap_reserved;
+                       }
+               }
+
+               /* The space used by snapshots but not reserved for them is included in
+                * both, norm_used and snap_norm_used. If possible, subtract this here. */
+               if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_NORM_USED | HAVE_VOLUME_USAGE_SNAP_USED))
+               {
+                       if (norm_used >= snap_norm_used)
+                               norm_used -= snap_norm_used;
+                       else
+                       {
+                               ERROR ("netapp plugin: (norm_used = %"PRIu64") < (snap_norm_used = "
+                                               "%"PRIu64") for host %s. Invalidating both.",
+                                               norm_used, snap_norm_used, hostname);
+                               v->flags &= ~(HAVE_VOLUME_USAGE_NORM_USED | HAVE_VOLUME_USAGE_SNAP_USED);
+                       }
+               }
+
+               if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_NORM_FREE))
+                       submit_double (hostname, /* plugin instance = */ plugin_instance,
+                                       "df_complex", "free",
+                                       (double) norm_free, /* timestamp = */ 0, interval);
+
+               if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_SIS_SAVED))
+                       submit_double (hostname, /* plugin instance = */ plugin_instance,
+                                       "df_complex", "sis_saved",
+                                       (double) sis_saved, /* timestamp = */ 0, interval);
+
+               if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_NORM_USED))
+                       submit_double (hostname, /* plugin instance = */ plugin_instance,
+                                       "df_complex", "used",
+                                       (double) norm_used, /* timestamp = */ 0, interval);
+
+               if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_SNAP_RSVD))
+                       submit_double (hostname, /* plugin instance = */ plugin_instance,
+                                       "df_complex", "snap_reserved",
+                                       (double) snap_reserve_free, /* timestamp = */ 0, interval);
+
+               if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_SNAP_USED | HAVE_VOLUME_USAGE_SNAP_RSVD))
+                       submit_double (hostname, /* plugin instance = */ plugin_instance,
+                                       "df_complex", "snap_reserve_used",
+                                       (double) snap_reserve_used, /* timestamp = */ 0, interval);
+
+               if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_SNAP_USED))
+                       submit_double (hostname, /* plugin instance = */ plugin_instance,
+                                       "df_complex", "snap_normal_used",
+                                       (double) snap_norm_used, /* timestamp = */ 0, interval);
+
+               /* Clear all the HAVE_* flags */
+               v->flags &= ~HAVE_VOLUME_USAGE_ALL;
+       } /* for (v = cfg_volume->volumes) */
+
+       return (0);
+} /* }}} int cna_submit_volume_usage_data */
+
+/* Switch the state of a volume between online and offline and send out a
+ * notification. */
+static int cna_change_volume_status (const char *hostname, /* {{{ */
+               data_volume_usage_t *v)
+{
+       notification_t n;
+
+       memset (&n, 0, sizeof (&n));
+       n.time = cdtime ();
+       sstrncpy (n.host, hostname, sizeof (n.host));
+       sstrncpy (n.plugin, "netapp", sizeof (n.plugin));
+       sstrncpy (n.plugin_instance, v->name, sizeof (n.plugin_instance));
+
+       if ((v->flags & IS_VOLUME_USAGE_OFFLINE) != 0) {
+               n.severity = NOTIF_OKAY;
+               ssnprintf (n.message, sizeof (n.message),
+                               "Volume %s is now online.", v->name);
+               v->flags &= ~IS_VOLUME_USAGE_OFFLINE;
+       } else {
+               n.severity = NOTIF_WARNING;
+               ssnprintf (n.message, sizeof (n.message),
+                               "Volume %s is now offline.", v->name);
+               v->flags |= IS_VOLUME_USAGE_OFFLINE;
+       }
+
+       return (plugin_dispatch_notification (&n));
+} /* }}} int cna_change_volume_status */
+
+static void cna_handle_volume_snap_usage(const host_config_t *host, /* {{{ */
+               data_volume_usage_t *v)
+{
+       uint64_t snap_used = 0, value;
+       na_elem_t *data, *elem_snap, *elem_snapshots;
+       na_elem_iter_t iter_snap;
+
+       data = na_server_invoke_elem(host->srv, v->snap_query);
+       if (na_results_status(data) != NA_OK)
+       {
+               if (na_results_errno(data) == EVOLUMEOFFLINE) {
+                       if ((v->flags & IS_VOLUME_USAGE_OFFLINE) == 0)
+                               cna_change_volume_status (host->name, v);
+               } else {
+                       ERROR ("netapp plugin: cna_handle_volume_snap_usage: na_server_invoke_elem for "
+                                       "volume \"%s\" on host %s failed with error %d: %s", v->name,
+                                       host->name, na_results_errno(data), na_results_reason(data));
+               }
+               na_elem_free(data);
+               return;
+       }
+
+       if ((v->flags & IS_VOLUME_USAGE_OFFLINE) != 0)
+               cna_change_volume_status (host->name, v);
+
+       elem_snapshots = na_elem_child (data, "snapshots");
+       if (elem_snapshots == NULL)
+       {
+               ERROR ("netapp plugin: cna_handle_volume_snap_usage: "
+                               "na_elem_child (\"snapshots\") failed "
+                               "for host %s.", host->name);
+               na_elem_free(data);
+               return;
+       }
+
+       iter_snap = na_child_iterator (elem_snapshots);
+       for (elem_snap = na_iterator_next (&iter_snap);
+                       elem_snap != NULL;
+                       elem_snap = na_iterator_next (&iter_snap))
+       {
+               value = na_child_get_uint64(elem_snap, "cumulative-total", 0);
+               /* "cumulative-total" is the total size of the oldest snapshot plus all
+                * newer ones in blocks (1KB). We therefore are looking for the highest
+                * number of all snapshots - that's the size required for the snapshots. */
+               if (value > snap_used)
+                       snap_used = value;
+       }
+       na_elem_free (data);
+       /* snap_used is in 1024 byte blocks */
+       v->snap_used = snap_used * 1024;
+       v->flags |= HAVE_VOLUME_USAGE_SNAP_USED;
+} /* }}} void cna_handle_volume_snap_usage */
+
+static int cna_handle_volume_usage_data (const host_config_t *host, /* {{{ */
+               cfg_volume_usage_t *cfg_volume, na_elem_t *data)
+{
+       na_elem_t *elem_volume;
+       na_elem_t *elem_volumes;
+       na_elem_iter_t iter_volume;
+
+       elem_volumes = na_elem_child (data, "volumes");
+       if (elem_volumes == NULL)
+       {
+               ERROR ("netapp plugin: cna_handle_volume_usage_data: "
+                               "na_elem_child (\"volumes\") failed "
+                               "for host %s.", host->name);
+               return (-1);
+       }
+
+       iter_volume = na_child_iterator (elem_volumes);
+       for (elem_volume = na_iterator_next (&iter_volume);
+                       elem_volume != NULL;
+                       elem_volume = na_iterator_next (&iter_volume))
+       {
+               const char *volume_name, *state;
+
+               data_volume_usage_t *v;
+               uint64_t value;
+
+               na_elem_t *sis;
+               const char *sis_state;
+               uint64_t sis_saved_reported;
+
+               volume_name = na_child_get_string (elem_volume, "name");
+               if (volume_name == NULL)
+                       continue;
+
+               state = na_child_get_string (elem_volume, "state");
+               if ((state == NULL) || (strcmp(state, "online") != 0))
+                       continue;
+
+               /* get_volume_usage may return NULL if the volume is to be ignored. */
+               v = get_volume_usage (cfg_volume, volume_name);
+               if (v == NULL)
+                       continue;
+
+               if ((v->flags & CFG_VOLUME_USAGE_SNAP) != 0)
+                       cna_handle_volume_snap_usage(host, v);
+               
+               if ((v->flags & CFG_VOLUME_USAGE_DF) == 0)
+                       continue;
+
+               /* 2^4 exa-bytes? This will take a while ;) */
+               value = na_child_get_uint64(elem_volume, "size-available", UINT64_MAX);
+               if (value != UINT64_MAX) {
+                       v->norm_free = value;
+                       v->flags |= HAVE_VOLUME_USAGE_NORM_FREE;
+               }
+
+               value = na_child_get_uint64(elem_volume, "size-used", UINT64_MAX);
+               if (value != UINT64_MAX) {
+                       v->norm_used = value;
+                       v->flags |= HAVE_VOLUME_USAGE_NORM_USED;
+               }
+
+               value = na_child_get_uint64(elem_volume, "snapshot-blocks-reserved", UINT64_MAX);
+               if (value != UINT64_MAX) {
+                       /* 1 block == 1024 bytes  as per API docs */
+                       v->snap_reserved = 1024 * value;
+                       v->flags |= HAVE_VOLUME_USAGE_SNAP_RSVD;
+               }
+
+               sis = na_elem_child(elem_volume, "sis");
+               if (sis == NULL)
+                       continue;
+
+               if (na_elem_child(sis, "sis-info"))
+                       sis = na_elem_child(sis, "sis-info");
+               
+               sis_state = na_child_get_string(sis, "state");
+               if (sis_state == NULL)
+                       continue;
+
+               /* If SIS is not enabled, there's nothing left to do for this volume. */
+               if (strcmp ("enabled", sis_state) != 0)
+                       continue;
+
+               sis_saved_reported = na_child_get_uint64(sis, "size-saved", UINT64_MAX);
+               if (sis_saved_reported == UINT64_MAX)
+                       continue;
+
+               /* size-saved is actually a 32 bit number, so ... time for some guesswork. */
+               if ((sis_saved_reported >> 32) != 0) {
+                       /* In case they ever fix this bug. */
+                       v->sis_saved = sis_saved_reported;
+                       v->flags |= HAVE_VOLUME_USAGE_SIS_SAVED;
+               } else { /* really hacky work-around code. {{{ */
+                       uint64_t sis_saved_percent;
+                       uint64_t sis_saved_guess;
+                       uint64_t overflow_guess;
+                       uint64_t guess1, guess2, guess3;
+
+                       /* Check if we have v->norm_used. Without it, we cannot calculate
+                        * sis_saved_guess. */
+                       if ((v->flags & HAVE_VOLUME_USAGE_NORM_USED) == 0)
+                               continue;
+
+                       sis_saved_percent = na_child_get_uint64(sis, "percentage-saved", UINT64_MAX);
+                       if (sis_saved_percent > 100)
+                               continue;
+
+                       /* The "size-saved" value is a 32bit unsigned integer. This is a bug and
+                        * will hopefully be fixed in later versions. To work around the bug, try
+                        * to figure out how often the 32bit integer wrapped around by using the
+                        * "percentage-saved" value. Because the percentage is in the range
+                        * [0-100], this should work as long as the saved space does not exceed
+                        * 400 GBytes. */
+                       /* percentage-saved = size-saved / (size-saved + size-used) */
+                       if (sis_saved_percent < 100)
+                               sis_saved_guess = v->norm_used * sis_saved_percent / (100 - sis_saved_percent);
+                       else
+                               sis_saved_guess = v->norm_used;
+
+                       overflow_guess = sis_saved_guess >> 32;
+                       guess1 = overflow_guess ? ((overflow_guess - 1) << 32) + sis_saved_reported : sis_saved_reported;
+                       guess2 = (overflow_guess << 32) + sis_saved_reported;
+                       guess3 = ((overflow_guess + 1) << 32) + sis_saved_reported;
+
+                       if (sis_saved_guess < guess2) {
+                               if ((sis_saved_guess - guess1) < (guess2 - sis_saved_guess))
+                                       v->sis_saved = guess1;
+                               else
+                                       v->sis_saved = guess2;
+                       } else {
+                               if ((sis_saved_guess - guess2) < (guess3 - sis_saved_guess))
+                                       v->sis_saved = guess2;
+                               else
+                                       v->sis_saved = guess3;
+                       }
+                       v->flags |= HAVE_VOLUME_USAGE_SIS_SAVED;
+               } /* }}} end of 32-bit workaround */
+       } /* for (elem_volume) */
+
+       return (cna_submit_volume_usage_data (host->name, cfg_volume, host->interval));
+} /* }}} int cna_handle_volume_usage_data */
+
+static int cna_setup_volume_usage (cfg_volume_usage_t *cvu) /* {{{ */
+{
+       if (cvu == NULL)
+               return (EINVAL);
+
+       if (cvu->query != NULL)
+               return (0);
+
+       cvu->query = na_elem_new ("volume-list-info");
+       if (cvu->query == NULL)
+       {
+               ERROR ("netapp plugin: na_elem_new failed.");
+               return (-1);
+       }
+
+       return (0);
+} /* }}} int cna_setup_volume_usage */
+
+static int cna_query_volume_usage (host_config_t *host) /* {{{ */
+{
+       na_elem_t *data;
+       int status;
+       cdtime_t now;
+
+       if (host == NULL)
+               return (EINVAL);
+
+       /* If the user did not configure volume_usage statistics, return without
+        * doing anything. */
+       if (host->cfg_volume_usage == NULL)
+               return (0);
+
+       now = cdtime ();
+       if ((host->cfg_volume_usage->interval.interval + host->cfg_volume_usage->interval.last_read) > now)
+               return (0);
+
+       status = cna_setup_volume_usage (host->cfg_volume_usage);
+       if (status != 0)
+               return (status);
+       assert (host->cfg_volume_usage->query != NULL);
+
+       data = na_server_invoke_elem(host->srv, host->cfg_volume_usage->query);
+       if (na_results_status (data) != NA_OK)
+       {
+               ERROR ("netapp plugin: cna_query_volume_usage: na_server_invoke_elem failed for host %s: %s",
+                               host->name, na_results_reason (data));
+               na_elem_free (data);
+               return (-1);
+       }
+
+       status = cna_handle_volume_usage_data (host, host->cfg_volume_usage, data);
+
+       if (status == 0)
+               host->cfg_volume_usage->interval.last_read = now;
+
+       na_elem_free (data);
+       return (status);
+} /* }}} int cna_query_volume_usage */
+
+/* Data corresponding to <System /> */
+static int cna_handle_system_data (const char *hostname, /* {{{ */
+               cfg_system_t *cfg_system, na_elem_t *data, int interval)
+{
+       na_elem_t *instances;
+       na_elem_t *counter;
+       na_elem_iter_t counter_iter;
+
+       derive_t disk_read = 0, disk_written = 0;
+       derive_t net_recv = 0, net_sent = 0;
+       derive_t cpu_busy = 0, cpu_total = 0;
+       uint32_t counter_flags = 0;
+
+       const char *instance;
+       cdtime_t timestamp;
+       
+       timestamp = cna_child_get_cdtime (data);
+
+       instances = na_elem_child(na_elem_child (data, "instances"), "instance-data");
+       if (instances == NULL)
+       {
+               ERROR ("netapp plugin: cna_handle_system_data: "
+                               "na_elem_child (\"instances\") failed "
+                               "for host %s.", hostname);
+               return (-1);
+       }
+
+       instance = na_child_get_string (instances, "name");
+       if (instance == NULL)
+       {
+               ERROR ("netapp plugin: cna_handle_system_data: "
+                               "na_child_get_string (\"name\") failed "
+                               "for host %s.", hostname);
+               return (-1);
+       }
+
+       counter_iter = na_child_iterator (na_elem_child (instances, "counters"));
+       for (counter = na_iterator_next (&counter_iter);
+                       counter != NULL;
+                       counter = na_iterator_next (&counter_iter))
+       {
+               const char *name;
+               uint64_t value;
+
+               name = na_child_get_string(counter, "name");
+               if (name == NULL)
+                       continue;
+
+               value = na_child_get_uint64(counter, "value", UINT64_MAX);
+               if (value == UINT64_MAX)
+                       continue;
+
+               if (!strcmp(name, "disk_data_read")) {
+                       disk_read = (derive_t) (value * 1024);
+                       counter_flags |= 0x01;
+               } else if (!strcmp(name, "disk_data_written")) {
+                       disk_written = (derive_t) (value * 1024);
+                       counter_flags |= 0x02;
+               } else if (!strcmp(name, "net_data_recv")) {
+                       net_recv = (derive_t) (value * 1024);
+                       counter_flags |= 0x04;
+               } else if (!strcmp(name, "net_data_sent")) {
+                       net_sent = (derive_t) (value * 1024);
+                       counter_flags |= 0x08;
+               } else if (!strcmp(name, "cpu_busy")) {
+                       cpu_busy = (derive_t) value;
+                       counter_flags |= 0x10;
+               } else if (!strcmp(name, "cpu_elapsed_time")) {
+                       cpu_total = (derive_t) value;
+                       counter_flags |= 0x20;
+               } else if ((cfg_system->flags & CFG_SYSTEM_OPS)
+                               && (value > 0) && (strlen(name) > 4)
+                               && (!strcmp(name + strlen(name) - 4, "_ops"))) {
+                       submit_derive (hostname, instance, "disk_ops_complex", name,
+                                       (derive_t) value, timestamp, interval);
+               }
+       } /* for (counter) */
+
+       if ((cfg_system->flags & CFG_SYSTEM_DISK)
+                       && (HAS_ALL_FLAGS (counter_flags, 0x01 | 0x02)))
+               submit_two_derive (hostname, instance, "disk_octets", NULL,
+                               disk_read, disk_written, timestamp, interval);
+                               
+       if ((cfg_system->flags & CFG_SYSTEM_NET)
+                       && (HAS_ALL_FLAGS (counter_flags, 0x04 | 0x08)))
+               submit_two_derive (hostname, instance, "if_octets", NULL,
+                               net_recv, net_sent, timestamp, interval);
+
+       if ((cfg_system->flags & CFG_SYSTEM_CPU)
+                       && (HAS_ALL_FLAGS (counter_flags, 0x10 | 0x20)))
+       {
+               submit_derive (hostname, instance, "cpu", "system",
+                               cpu_busy, timestamp, interval);
+               submit_derive (hostname, instance, "cpu", "idle",
+                               cpu_total - cpu_busy, timestamp, interval);
+       }
+
+       return (0);
+} /* }}} int cna_handle_system_data */
+
+static int cna_setup_system (cfg_system_t *cs) /* {{{ */
+{
+       if (cs == NULL)
+               return (EINVAL);
+
+       if (cs->query != NULL)
+               return (0);
+
+       cs->query = na_elem_new ("perf-object-get-instances");
+       if (cs->query == NULL)
+       {
+               ERROR ("netapp plugin: na_elem_new failed.");
+               return (-1);
+       }
+       na_child_add_string (cs->query, "objectname", "system");
+
+       return (0);
+} /* }}} int cna_setup_system */
+
+static int cna_query_system (host_config_t *host) /* {{{ */
+{
+       na_elem_t *data;
+       int status;
+       cdtime_t now;
+
+       if (host == NULL)
+               return (EINVAL);
+
+       /* If system statistics were not configured, return without doing anything. */
+       if (host->cfg_system == NULL)
+               return (0);
+
+       now = cdtime ();
+       if ((host->cfg_system->interval.interval + host->cfg_system->interval.last_read) > now)
+               return (0);
+
+       status = cna_setup_system (host->cfg_system);
+       if (status != 0)
+               return (status);
+       assert (host->cfg_system->query != NULL);
+
+       data = na_server_invoke_elem(host->srv, host->cfg_system->query);
+       if (na_results_status (data) != NA_OK)
+       {
+               ERROR ("netapp plugin: cna_query_system: na_server_invoke_elem failed for host %s: %s",
+                               host->name, na_results_reason (data));
+               na_elem_free (data);
+               return (-1);
+       }
+
+       status = cna_handle_system_data (host->name, host->cfg_system, data, host->interval);
+
+       if (status == 0)
+               host->cfg_system->interval.last_read = now;
+
+       na_elem_free (data);
+       return (status);
+} /* }}} int cna_query_system */
+
+/*
+ * Configuration handling
+ */
+/* Sets a given flag if the boolean argument is true and unsets the flag if it
+ * is false. On error, the flag-field is not changed. */
+static int cna_config_bool_to_flag (const oconfig_item_t *ci, /* {{{ */
+               uint32_t *flags, uint32_t flag)
+{
+       if ((ci == NULL) || (flags == NULL))
+               return (EINVAL);
+
+       if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN))
+       {
+               WARNING ("netapp plugin: The %s option needs exactly one boolean argument.",
+                               ci->key);
+               return (-1);
+       }
+
+       if (ci->values[0].value.boolean)
+               *flags |= flag;
+       else
+               *flags &= ~flag;
+
+       return (0);
+} /* }}} int cna_config_bool_to_flag */
+
+/* Handling of the "Interval" option which is allowed in every block. */
+static int cna_config_get_interval (const oconfig_item_t *ci, /* {{{ */
+               cna_interval_t *out_interval)
+{
+       cdtime_t tmp = 0;
+       int status;
+
+       status = cf_util_get_cdtime (ci, &tmp);
+       if (status != 0)
+               return (status);
+
+       out_interval->interval = tmp;
+       out_interval->last_read = 0;
+
+       return (0);
+} /* }}} int cna_config_get_interval */
+
+/* Handling of the "GetIO", "GetOps" and "GetLatency" options within a
+ * <VolumePerf /> block. */
+static void cna_config_volume_perf_option (cfg_volume_perf_t *cvp, /* {{{ */
+               const oconfig_item_t *ci)
+{
+       char *name;
+       ignorelist_t * il;
+
+       if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+       {
+               WARNING ("netapp plugin: The %s option requires exactly one string argument.",
+                               ci->key);
+               return;
+       }
+
+       name = ci->values[0].value.string;
+
+       if (strcasecmp ("GetIO", ci->key) == 0)
+               il = cvp->il_octets;
+       else if (strcasecmp ("GetOps", ci->key) == 0)
+               il = cvp->il_operations;
+       else if (strcasecmp ("GetLatency", ci->key) == 0)
+               il = cvp->il_latency;
+       else
+               return;
+
+       ignorelist_add (il, name);
+} /* }}} void cna_config_volume_perf_option */
+
+/* Handling of the "IgnoreSelectedIO", "IgnoreSelectedOps" and
+ * "IgnoreSelectedLatency" options within a <VolumePerf /> block. */
+static void cna_config_volume_perf_default (cfg_volume_perf_t *cvp, /* {{{ */
+               const oconfig_item_t *ci)
+{
+       ignorelist_t *il;
+
+       if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN))
+       {
+               WARNING ("netapp plugin: The %s option requires exactly one string argument.",
+                               ci->key);
+               return;
+       }
+
+       if (strcasecmp ("IgnoreSelectedIO", ci->key) == 0)
+               il = cvp->il_octets;
+       else if (strcasecmp ("IgnoreSelectedOps", ci->key) == 0)
+               il = cvp->il_operations;
+       else if (strcasecmp ("IgnoreSelectedLatency", ci->key) == 0)
+               il = cvp->il_latency;
+       else
+               return;
+
+       if (ci->values[0].value.boolean)
+               ignorelist_set_invert (il, /* invert = */ 0);
+       else
+               ignorelist_set_invert (il, /* invert = */ 1);
+} /* }}} void cna_config_volume_perf_default */
+
+/* Corresponds to a <Disks /> block */
+/*
+ * <VolumePerf>
+ *   GetIO "vol0"
+ *   GetIO "vol1"
+ *   IgnoreSelectedIO false
+ *
+ *   GetOps "vol0"
+ *   GetOps "vol2"
+ *   IgnoreSelectedOps false
+ *
+ *   GetLatency "vol2"
+ *   GetLatency "vol3"
+ *   IgnoreSelectedLatency false
+ * </VolumePerf>
+ */
+/* Corresponds to a <VolumePerf /> block */
+static int cna_config_volume_performance (host_config_t *host, /* {{{ */
+               const oconfig_item_t *ci)
+{
+       cfg_volume_perf_t *cfg_volume_perf;
+       int i;
+
+       if ((host == NULL) || (ci == NULL))
+               return (EINVAL);
+
+       if (host->cfg_volume_perf == NULL)
+       {
+               cfg_volume_perf = malloc (sizeof (*cfg_volume_perf));
+               if (cfg_volume_perf == NULL)
+                       return (ENOMEM);
+               memset (cfg_volume_perf, 0, sizeof (*cfg_volume_perf));
+
+               /* Set default flags */
+               cfg_volume_perf->query = NULL;
+               cfg_volume_perf->volumes = NULL;
+
+               cfg_volume_perf->il_octets = ignorelist_create (/* invert = */ 1);
+               if (cfg_volume_perf->il_octets == NULL)
+               {
+                       sfree (cfg_volume_perf);
+                       return (ENOMEM);
+               }
+
+               cfg_volume_perf->il_operations = ignorelist_create (/* invert = */ 1);
+               if (cfg_volume_perf->il_operations == NULL)
+               {
+                       ignorelist_free (cfg_volume_perf->il_octets);
+                       sfree (cfg_volume_perf);
+                       return (ENOMEM);
+               }
+
+               cfg_volume_perf->il_latency = ignorelist_create (/* invert = */ 1);
+               if (cfg_volume_perf->il_latency == NULL)
+               {
+                       ignorelist_free (cfg_volume_perf->il_octets);
+                       ignorelist_free (cfg_volume_perf->il_operations);
+                       sfree (cfg_volume_perf);
+                       return (ENOMEM);
+               }
+
+               host->cfg_volume_perf = cfg_volume_perf;
+       }
+       cfg_volume_perf = host->cfg_volume_perf;
+       
+       for (i = 0; i < ci->children_num; ++i) {
+               oconfig_item_t *item = ci->children + i;
+               
+               /* if (!item || !item->key || !*item->key) continue; */
+               if (strcasecmp(item->key, "Interval") == 0)
+                       cna_config_get_interval (item, &cfg_volume_perf->interval);
+               else if (!strcasecmp(item->key, "GetIO"))
+                       cna_config_volume_perf_option (cfg_volume_perf, item);
+               else if (!strcasecmp(item->key, "GetOps"))
+                       cna_config_volume_perf_option (cfg_volume_perf, item);
+               else if (!strcasecmp(item->key, "GetLatency"))
+                       cna_config_volume_perf_option (cfg_volume_perf, item);
+               else if (!strcasecmp(item->key, "IgnoreSelectedIO"))
+                       cna_config_volume_perf_default (cfg_volume_perf, item);
+               else if (!strcasecmp(item->key, "IgnoreSelectedOps"))
+                       cna_config_volume_perf_default (cfg_volume_perf, item);
+               else if (!strcasecmp(item->key, "IgnoreSelectedLatency"))
+                       cna_config_volume_perf_default (cfg_volume_perf, item);
+               else
+                       WARNING ("netapp plugin: The option %s is not allowed within "
+                                       "`VolumePerf' blocks.", item->key);
+       }
+
+       return (0);
+} /* }}} int cna_config_volume_performance */
+
+/* Handling of the "GetCapacity" and "GetSnapshot" options within a
+ * <VolumeUsage /> block. */
+static void cna_config_volume_usage_option (cfg_volume_usage_t *cvu, /* {{{ */
+               const oconfig_item_t *ci)
+{
+       char *name;
+       ignorelist_t * il;
+
+       if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+       {
+               WARNING ("netapp plugin: The %s option requires exactly one string argument.",
+                               ci->key);
+               return;
+       }
+
+       name = ci->values[0].value.string;
+
+       if (strcasecmp ("GetCapacity", ci->key) == 0)
+               il = cvu->il_capacity;
+       else if (strcasecmp ("GetSnapshot", ci->key) == 0)
+               il = cvu->il_snapshot;
+       else
+               return;
+
+       ignorelist_add (il, name);
+} /* }}} void cna_config_volume_usage_option */
+
+/* Handling of the "IgnoreSelectedCapacity" and "IgnoreSelectedSnapshot"
+ * options within a <VolumeUsage /> block. */
+static void cna_config_volume_usage_default (cfg_volume_usage_t *cvu, /* {{{ */
+               const oconfig_item_t *ci)
+{
+       ignorelist_t *il;
+
+       if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN))
+       {
+               WARNING ("netapp plugin: The %s option requires exactly one string argument.",
+                               ci->key);
+               return;
+       }
+
+       if (strcasecmp ("IgnoreSelectedCapacity", ci->key) == 0)
+               il = cvu->il_capacity;
+       else if (strcasecmp ("IgnoreSelectedSnapshot", ci->key) == 0)
+               il = cvu->il_snapshot;
+       else
+               return;
+
+       if (ci->values[0].value.boolean)
+               ignorelist_set_invert (il, /* invert = */ 0);
+       else
+               ignorelist_set_invert (il, /* invert = */ 1);
+} /* }}} void cna_config_volume_usage_default */
+
+/* Corresponds to a <Disks /> block */
+static int cna_config_disk(host_config_t *host, oconfig_item_t *ci) { /* {{{ */
+       cfg_disk_t *cfg_disk;
+       int i;
+
+       if ((host == NULL) || (ci == NULL))
+               return (EINVAL);
+
+       if (host->cfg_disk == NULL)
+       {
+               cfg_disk = malloc (sizeof (*cfg_disk));
+               if (cfg_disk == NULL)
+                       return (ENOMEM);
+               memset (cfg_disk, 0, sizeof (*cfg_disk));
+
+               /* Set default flags */
+               cfg_disk->flags = CFG_DISK_ALL;
+               cfg_disk->query = NULL;
+               cfg_disk->disks = NULL;
+
+               host->cfg_disk = cfg_disk;
+       }
+       cfg_disk = host->cfg_disk;
+       
+       for (i = 0; i < ci->children_num; ++i) {
+               oconfig_item_t *item = ci->children + i;
+               
+               /* if (!item || !item->key || !*item->key) continue; */
+               if (strcasecmp(item->key, "Interval") == 0)
+                       cna_config_get_interval (item, &cfg_disk->interval);
+               else if (strcasecmp(item->key, "GetBusy") == 0)
+                       cna_config_bool_to_flag (item, &cfg_disk->flags, CFG_DISK_BUSIEST);
+       }
+
+       if ((cfg_disk->flags & CFG_DISK_ALL) == 0)
+       {
+               NOTICE ("netapp plugin: All disk related values have been disabled. "
+                               "Collection of per-disk data will be disabled entirely.");
+               free_cfg_disk (host->cfg_disk);
+               host->cfg_disk = NULL;
+       }
+
+       return (0);
+} /* }}} int cna_config_disk */
+
+/* Corresponds to a <WAFL /> block */
+static int cna_config_wafl(host_config_t *host, oconfig_item_t *ci) /* {{{ */
+{
+       cfg_wafl_t *cfg_wafl;
+       int i;
+
+       if ((host == NULL) || (ci == NULL))
+               return (EINVAL);
+
+       if (host->cfg_wafl == NULL)
+       {
+               cfg_wafl = malloc (sizeof (*cfg_wafl));
+               if (cfg_wafl == NULL)
+                       return (ENOMEM);
+               memset (cfg_wafl, 0, sizeof (*cfg_wafl));
+
+               /* Set default flags */
+               cfg_wafl->flags = CFG_WAFL_ALL;
+
+               host->cfg_wafl = cfg_wafl;
+       }
+       cfg_wafl = host->cfg_wafl;
+
+       for (i = 0; i < ci->children_num; ++i) {
+               oconfig_item_t *item = ci->children + i;
+               
+               if (strcasecmp(item->key, "Interval") == 0)
+                       cna_config_get_interval (item, &cfg_wafl->interval);
+               else if (!strcasecmp(item->key, "GetNameCache"))
+                       cna_config_bool_to_flag (item, &cfg_wafl->flags, CFG_WAFL_NAME_CACHE);
+               else if (!strcasecmp(item->key, "GetDirCache"))
+                       cna_config_bool_to_flag (item, &cfg_wafl->flags, CFG_WAFL_DIR_CACHE);
+               else if (!strcasecmp(item->key, "GetBufferCache"))
+                       cna_config_bool_to_flag (item, &cfg_wafl->flags, CFG_WAFL_BUF_CACHE);
+               else if (!strcasecmp(item->key, "GetInodeCache"))
+                       cna_config_bool_to_flag (item, &cfg_wafl->flags, CFG_WAFL_INODE_CACHE);
+               else
+                       WARNING ("netapp plugin: The %s config option is not allowed within "
+                                       "`WAFL' blocks.", item->key);
+       }
+
+       if ((cfg_wafl->flags & CFG_WAFL_ALL) == 0)
+       {
+               NOTICE ("netapp plugin: All WAFL related values have been disabled. "
+                               "Collection of WAFL data will be disabled entirely.");
+               free_cfg_wafl (host->cfg_wafl);
+               host->cfg_wafl = NULL;
+       }
+
+       return (0);
+} /* }}} int cna_config_wafl */
+
+/*
+ * <VolumeUsage>
+ *   GetCapacity "vol0"
+ *   GetCapacity "vol1"
+ *   GetCapacity "vol2"
+ *   GetCapacity "vol3"
+ *   GetCapacity "vol4"
+ *   IgnoreSelectedCapacity false
+ *
+ *   GetSnapshot "vol0"
+ *   GetSnapshot "vol3"
+ *   GetSnapshot "vol4"
+ *   GetSnapshot "vol7"
+ *   IgnoreSelectedSnapshot false
+ * </VolumeUsage>
+ */
+/* Corresponds to a <VolumeUsage /> block */
+static int cna_config_volume_usage(host_config_t *host, /* {{{ */
+               const oconfig_item_t *ci)
+{
+       cfg_volume_usage_t *cfg_volume_usage;
+       int i;
+
+       if ((host == NULL) || (ci == NULL))
+               return (EINVAL);
+
+       if (host->cfg_volume_usage == NULL)
+       {
+               cfg_volume_usage = malloc (sizeof (*cfg_volume_usage));
+               if (cfg_volume_usage == NULL)
+                       return (ENOMEM);
+               memset (cfg_volume_usage, 0, sizeof (*cfg_volume_usage));
+
+               /* Set default flags */
+               cfg_volume_usage->query = NULL;
+               cfg_volume_usage->volumes = NULL;
+
+               cfg_volume_usage->il_capacity = ignorelist_create (/* invert = */ 1);
+               if (cfg_volume_usage->il_capacity == NULL)
+               {
+                       sfree (cfg_volume_usage);
+                       return (ENOMEM);
+               }
+
+               cfg_volume_usage->il_snapshot = ignorelist_create (/* invert = */ 1);
+               if (cfg_volume_usage->il_snapshot == NULL)
+               {
+                       ignorelist_free (cfg_volume_usage->il_capacity);
+                       sfree (cfg_volume_usage);
+                       return (ENOMEM);
+               }
+
+               host->cfg_volume_usage = cfg_volume_usage;
+       }
+       cfg_volume_usage = host->cfg_volume_usage;
+       
+       for (i = 0; i < ci->children_num; ++i) {
+               oconfig_item_t *item = ci->children + i;
+               
+               /* if (!item || !item->key || !*item->key) continue; */
+               if (strcasecmp(item->key, "Interval") == 0)
+                       cna_config_get_interval (item, &cfg_volume_usage->interval);
+               else if (!strcasecmp(item->key, "GetCapacity"))
+                       cna_config_volume_usage_option (cfg_volume_usage, item);
+               else if (!strcasecmp(item->key, "GetSnapshot"))
+                       cna_config_volume_usage_option (cfg_volume_usage, item);
+               else if (!strcasecmp(item->key, "IgnoreSelectedCapacity"))
+                       cna_config_volume_usage_default (cfg_volume_usage, item);
+               else if (!strcasecmp(item->key, "IgnoreSelectedSnapshot"))
+                       cna_config_volume_usage_default (cfg_volume_usage, item);
+               else
+                       WARNING ("netapp plugin: The option %s is not allowed within "
+                                       "`VolumeUsage' blocks.", item->key);
+       }
+
+       return (0);
+} /* }}} int cna_config_volume_usage */
+
+/* Corresponds to a <System /> block */
+static int cna_config_system (host_config_t *host, /* {{{ */
+               oconfig_item_t *ci)
+{
+       cfg_system_t *cfg_system;
+       int i;
+       
+       if ((host == NULL) || (ci == NULL))
+               return (EINVAL);
+
+       if (host->cfg_system == NULL)
+       {
+               cfg_system = malloc (sizeof (*cfg_system));
+               if (cfg_system == NULL)
+                       return (ENOMEM);
+               memset (cfg_system, 0, sizeof (*cfg_system));
+
+               /* Set default flags */
+               cfg_system->flags = CFG_SYSTEM_ALL;
+               cfg_system->query = NULL;
+
+               host->cfg_system = cfg_system;
+       }
+       cfg_system = host->cfg_system;
+
+       for (i = 0; i < ci->children_num; ++i) {
+               oconfig_item_t *item = ci->children + i;
+
+               if (strcasecmp(item->key, "Interval") == 0) {
+                       cna_config_get_interval (item, &cfg_system->interval);
+               } else if (!strcasecmp(item->key, "GetCPULoad")) {
+                       cna_config_bool_to_flag (item, &cfg_system->flags, CFG_SYSTEM_CPU);
+               } else if (!strcasecmp(item->key, "GetInterfaces")) {
+                       cna_config_bool_to_flag (item, &cfg_system->flags, CFG_SYSTEM_NET);
+               } else if (!strcasecmp(item->key, "GetDiskOps")) {
+                       cna_config_bool_to_flag (item, &cfg_system->flags, CFG_SYSTEM_OPS);
+               } else if (!strcasecmp(item->key, "GetDiskIO")) {
+                       cna_config_bool_to_flag (item, &cfg_system->flags, CFG_SYSTEM_DISK);
+               } else {
+                       WARNING ("netapp plugin: The %s config option is not allowed within "
+                                       "`System' blocks.", item->key);
+               }
+       }
+
+       if ((cfg_system->flags & CFG_SYSTEM_ALL) == 0)
+       {
+               NOTICE ("netapp plugin: All system related values have been disabled. "
+                               "Collection of system data will be disabled entirely.");
+               free_cfg_system (host->cfg_system);
+               host->cfg_system = NULL;
+       }
+
+       return (0);
+} /* }}} int cna_config_system */
+
+/* Corresponds to a <Host /> block. */
+static host_config_t *cna_config_host (const oconfig_item_t *ci) /* {{{ */
+{
+       oconfig_item_t *item;
+       host_config_t *host;
+       int status;
+       int i;
+       
+       if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
+               WARNING("netapp plugin: \"Host\" needs exactly one string argument. Ignoring host block.");
+               return 0;
+       }
+
+       host = malloc(sizeof(*host));
+       memset (host, 0, sizeof (*host));
+       host->name = NULL;
+       host->protocol = NA_SERVER_TRANSPORT_HTTPS;
+       host->host = NULL;
+       host->username = NULL;
+       host->password = NULL;
+       host->srv = NULL;
+       host->cfg_wafl = NULL;
+       host->cfg_disk = NULL;
+       host->cfg_volume_perf = NULL;
+       host->cfg_volume_usage = NULL;
+       host->cfg_system = NULL;
+
+       status = cf_util_get_string (ci, &host->name);
+       if (status != 0)
+       {
+               sfree (host);
+               return (NULL);
+       }
+
+       for (i = 0; i < ci->children_num; ++i) {
+               item = ci->children + i;
+
+               status = 0;
+
+               if (!strcasecmp(item->key, "Address")) {
+                       status = cf_util_get_string (item, &host->host);
+               } else if (!strcasecmp(item->key, "Port")) {
+                       int tmp;
+
+                       tmp = cf_util_get_port_number (item);
+                       if (tmp > 0)
+                               host->port = tmp;
+               } else if (!strcasecmp(item->key, "Protocol")) {
+                       if ((item->values_num != 1) || (item->values[0].type != OCONFIG_TYPE_STRING) || (strcasecmp(item->values[0].value.string, "http") && strcasecmp(item->values[0].value.string, "https"))) {
+                               WARNING("netapp plugin: \"Protocol\" needs to be either \"http\" or \"https\". Ignoring host block \"%s\".", ci->values[0].value.string);
+                               return 0;
+                       }
+                       if (!strcasecmp(item->values[0].value.string, "http")) host->protocol = NA_SERVER_TRANSPORT_HTTP;
+                       else host->protocol = NA_SERVER_TRANSPORT_HTTPS;
+               } else if (!strcasecmp(item->key, "User")) {
+                       status = cf_util_get_string (item, &host->username);
+               } else if (!strcasecmp(item->key, "Password")) {
+                       status = cf_util_get_string (item, &host->password);
+               } else if (!strcasecmp(item->key, "Interval")) {
+                       status = cf_util_get_cdtime (item, &host->interval);
+               } else if (!strcasecmp(item->key, "WAFL")) {
+                       cna_config_wafl(host, item);
+               } else if (!strcasecmp(item->key, "Disks")) {
+                       cna_config_disk(host, item);
+               } else if (!strcasecmp(item->key, "VolumePerf")) {
+                       cna_config_volume_performance(host, item);
+               } else if (!strcasecmp(item->key, "VolumeUsage")) {
+                       cna_config_volume_usage(host, item);
+               } else if (!strcasecmp(item->key, "System")) {
+                       cna_config_system(host, item);
+               } else {
+                       WARNING("netapp plugin: Ignoring unknown config option \"%s\" in host block \"%s\".",
+                                       item->key, ci->values[0].value.string);
+               }
+
+               if (status != 0)
+                       break;
+       }
+
+       if (host->host == NULL)
+               host->host = strdup (host->name);
+
+       if (host->host == NULL)
+               status = -1;
+
+       if (host->port <= 0)
+               host->port = (host->protocol == NA_SERVER_TRANSPORT_HTTP) ? 80 : 443;
+
+       if ((host->username == NULL) || (host->password == NULL)) {
+               WARNING("netapp plugin: Please supply login information for host \"%s\". "
+                               "Ignoring host block.", host->name);
+               status = -1;
+       }
+
+       if (status != 0)
+       {
+               free_host_config (host);
+               return (NULL);
+       }
+
+       return host;
+} /* }}} host_config_t *cna_config_host */
+
+/*
+ * Callbacks registered with the daemon
+ *
+ * Pretty standard stuff here.
+ */
+static int cna_init_host (host_config_t *host) /* {{{ */
+{
+       if (host == NULL)
+               return (EINVAL);
+
+       if (host->srv != NULL)
+               return (0);
+
+       /* Request version 1.1 of the ONTAP API */
+       host->srv = na_server_open(host->host,
+                       /* major version = */ 1, /* minor version = */ 1); 
+       if (host->srv == NULL) {
+               ERROR ("netapp plugin: na_server_open (%s) failed.", host->host);
+               return (-1);
+       }
+
+       na_server_set_transport_type(host->srv, host->protocol,
+                       /* transportarg = */ NULL);
+       na_server_set_port(host->srv, host->port);
+       na_server_style(host->srv, NA_STYLE_LOGIN_PASSWORD);
+       na_server_adminuser(host->srv, host->username, host->password);
+       na_server_set_timeout(host->srv, 5 /* seconds */);
+
+       return 0;
+} /* }}} int cna_init_host */
+
+static int cna_init (void) /* {{{ */
+{
+       char err[256];
+
+       memset (err, 0, sizeof (err));
+       if (!na_startup(err, sizeof(err))) {
+               err[sizeof (err) - 1] = 0;
+               ERROR("netapp plugin: Error initializing netapp API: %s", err);
+               return 1;
+       }
+
+       return (0);
+} /* }}} cna_init */
+
+static int cna_read (user_data_t *ud) { /* {{{ */
+       host_config_t *host;
+       int status;
+
+       if ((ud == NULL) || (ud->data == NULL))
+               return (-1);
+
+       host = ud->data;
+
+       status = cna_init_host (host);
+       if (status != 0)
+               return (status);
+       
+       cna_query_wafl (host);
+       cna_query_disk (host);
+       cna_query_volume_perf (host);
+       cna_query_volume_usage (host);
+       cna_query_system (host);
+
+       return 0;
+} /* }}} int cna_read */
+
+static int cna_config (oconfig_item_t *ci) { /* {{{ */
+       int i;
+       oconfig_item_t *item;
+
+       for (i = 0; i < ci->children_num; ++i) {
+               item = ci->children + i;
+
+               if (strcasecmp(item->key, "Host") == 0)
+               {
+                       host_config_t *host;
+                       char cb_name[256];
+                       struct timespec interval;
+                       user_data_t ud;
+
+                       host = cna_config_host (item);
+                       if (host == NULL)
+                               continue;
+
+                       ssnprintf (cb_name, sizeof (cb_name), "netapp-%s", host->name);
+
+                       CDTIME_T_TO_TIMESPEC (host->interval, &interval);
+
+                       memset (&ud, 0, sizeof (ud));
+                       ud.data = host;
+                       ud.free_func = (void (*) (void *)) free_host_config;
+
+                       plugin_register_complex_read (/* group = */ NULL, cb_name,
+                                       /* callback  = */ cna_read, 
+                                       /* interval  = */ (host->interval > 0) ? &interval : NULL,
+                                       /* user data = */ &ud);
+                       continue;
+               }
+               else /* if (item->key != "Host") */
+               {
+                       WARNING("netapp plugin: Ignoring unknown config option \"%s\".", item->key);
+               }
+       }
+       return 0;
+} /* }}} int cna_config */
+
+static int cna_shutdown (void) /* {{{ */
+{
+       /* Clean up system resources and stuff. */
+       na_shutdown ();
+
+       return (0);
+} /* }}} int cna_shutdown */
+
+void module_register(void) {
+       plugin_register_complex_config("netapp", cna_config);
+       plugin_register_init("netapp", cna_init);
+       plugin_register_shutdown("netapp", cna_shutdown);
+}
+
+/* vim: set sw=2 ts=2 noet fdm=marker : */
diff --git a/src/netlink.c b/src/netlink.c
new file mode 100644 (file)
index 0000000..ef851d3
--- /dev/null
@@ -0,0 +1,644 @@
+/**
+ * collectd - src/netlink.c
+ * Copyright (C) 2007-2010  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at collectd.org>
+ **/
+
+#include "collectd.h"
+#include "plugin.h"
+#include "common.h"
+
+#include <asm/types.h>
+#include <sys/socket.h>
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#if HAVE_LINUX_GEN_STATS_H
+# include <linux/gen_stats.h>
+#endif
+#if HAVE_LINUX_PKT_SCHED_H
+# include <linux/pkt_sched.h>
+#endif
+
+#if HAVE_LIBNETLINK_H
+# include <libnetlink.h>
+#elif HAVE_IPROUTE_LIBNETLINK_H
+# include <iproute/libnetlink.h>
+#elif HAVE_LINUX_LIBNETLINK_H
+# include <linux/libnetlink.h>
+#endif
+
+typedef struct ir_ignorelist_s
+{
+  char *device;
+  char *type;
+  char *inst;
+  struct ir_ignorelist_s *next;
+} ir_ignorelist_t;
+
+static int ir_ignorelist_invert = 1;
+static ir_ignorelist_t *ir_ignorelist_head = NULL;
+
+static struct rtnl_handle rth;
+
+static char **iflist = NULL;
+static size_t iflist_len = 0;
+
+static const char *config_keys[] =
+{
+       "Interface",
+       "VerboseInterface",
+       "QDisc",
+       "Class",
+       "Filter",
+       "IgnoreSelected"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static int add_ignorelist (const char *dev, const char *type,
+    const char *inst)
+{
+  ir_ignorelist_t *entry;
+
+  entry = (ir_ignorelist_t *) malloc (sizeof (ir_ignorelist_t));
+  if (entry == NULL)
+    return (-1);
+
+  memset (entry, '\0', sizeof (ir_ignorelist_t));
+
+  if (strcasecmp (dev, "All") != 0)
+  {
+    entry->device = strdup (dev);
+    if (entry->device == NULL)
+    {
+      sfree (entry);
+      return (-1);
+    }
+  }
+
+  entry->type = strdup (type);
+  if (entry->type == NULL)
+  {
+    sfree (entry->device);
+    sfree (entry);
+    return (-1);
+  }
+
+  if (inst != NULL)
+  {
+    entry->inst = strdup (inst);
+    if (entry->inst == NULL)
+    {
+      sfree (entry->type);
+      sfree (entry->device);
+      sfree (entry);
+      return (-1);
+    }
+  }
+
+  entry->next = ir_ignorelist_head;
+  ir_ignorelist_head = entry;
+
+  return (0);
+} /* int add_ignorelist */
+
+/* 
+ * Checks wether a data set should be ignored. Returns `true' is the value
+ * should be ignored, `false' otherwise.
+ */
+static int check_ignorelist (const char *dev,
+    const char *type, const char *type_instance)
+{
+  ir_ignorelist_t *i;
+
+  assert ((dev != NULL) && (type != NULL));
+
+  if (ir_ignorelist_head == NULL)
+    return (ir_ignorelist_invert ? 0 : 1);
+
+  for (i = ir_ignorelist_head; i != NULL; i = i->next)
+  {
+    /* i->device == NULL  =>  match all devices */
+    if ((i->device != NULL)
+       && (strcasecmp (i->device, dev) != 0))
+      continue;
+
+    if (strcasecmp (i->type, type) != 0)
+      continue;
+
+    if ((i->inst != NULL) && (type_instance != NULL)
+       && (strcasecmp (i->inst, type_instance) != 0))
+      continue;
+
+    DEBUG ("netlink plugin: check_ignorelist: "
+       "(dev = %s; type = %s; inst = %s) matched "
+       "(dev = %s; type = %s; inst = %s)",
+       dev, type,
+       type_instance == NULL ? "(nil)" : type_instance,
+       i->device == NULL ? "(nil)" : i->device,
+       i->type,
+       i->inst == NULL ? "(nil)" : i->inst);
+
+    return (ir_ignorelist_invert ? 0 : 1);
+  } /* for i */
+
+  return (ir_ignorelist_invert);
+} /* int check_ignorelist */
+
+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_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));
+
+  if (type_instance != NULL)
+    sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
+
+  plugin_dispatch_values (&vl);
+} /* void submit_one */
+
+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;
+
+  vl.values = values;
+  vl.values_len = 2;
+  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));
+
+  if (type_instance != NULL)
+    sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
+
+  plugin_dispatch_values (&vl);
+} /* void submit_two */
+
+static int link_filter (const struct sockaddr_nl __attribute__((unused)) *sa,
+    struct nlmsghdr *nmh, void __attribute__((unused)) *args)
+{
+  struct ifinfomsg *msg;
+  int msg_len;
+  struct rtattr *attrs[IFLA_MAX + 1];
+  struct rtnl_link_stats *stats;
+
+  const char *dev;
+
+  if (nmh->nlmsg_type != RTM_NEWLINK)
+  {
+    ERROR ("netlink plugin: link_filter: Don't know how to handle type %i.",
+       nmh->nlmsg_type);
+    return (-1);
+  }
+
+  msg = NLMSG_DATA (nmh);
+
+  msg_len = nmh->nlmsg_len - sizeof (struct ifinfomsg);
+  if (msg_len < 0)
+  {
+    ERROR ("netlink plugin: link_filter: msg_len = %i < 0;", msg_len);
+    return (-1);
+  }
+
+  memset (attrs, '\0', sizeof (attrs));
+  if (parse_rtattr (attrs, IFLA_MAX, IFLA_RTA (msg), msg_len) != 0)
+  {
+    ERROR ("netlink plugin: link_filter: parse_rtattr failed.");
+    return (-1);
+  }
+
+  if (attrs[IFLA_IFNAME] == NULL)
+  {
+    ERROR ("netlink plugin: link_filter: attrs[IFLA_IFNAME] == NULL");
+    return (-1);
+  }
+  dev = RTA_DATA (attrs[IFLA_IFNAME]);
+
+  /* Update the `iflist'. It's used to know which interfaces exist and query
+   * them later for qdiscs and classes. */
+  if ((msg->ifi_index >= 0) && ((size_t) msg->ifi_index >= iflist_len))
+  {
+    char **temp;
+
+    temp = (char **) realloc (iflist, (msg->ifi_index + 1) * sizeof (char *));
+    if (temp == NULL)
+    {
+      ERROR ("netlink plugin: link_filter: realloc failed.");
+      return (-1);
+    }
+
+    memset (temp + iflist_len, '\0',
+       (msg->ifi_index + 1 - iflist_len) * sizeof (char *));
+    iflist = temp;
+    iflist_len = msg->ifi_index + 1;
+  }
+  if ((iflist[msg->ifi_index] == NULL)
+      || (strcmp (iflist[msg->ifi_index], dev) != 0))
+  {
+    sfree (iflist[msg->ifi_index]);
+    iflist[msg->ifi_index] = strdup (dev);
+  }
+
+  if (attrs[IFLA_STATS] == NULL)
+  {
+    DEBUG ("netlink plugin: link_filter: No statistics for interface %s.", dev);
+    return (0);
+  }
+  stats = RTA_DATA (attrs[IFLA_STATS]);
+
+  if (check_ignorelist (dev, "interface", NULL) == 0)
+  {
+    submit_two (dev, "if_octets", NULL, stats->rx_bytes, stats->tx_bytes);
+    submit_two (dev, "if_packets", NULL, stats->rx_packets, stats->tx_packets);
+    submit_two (dev, "if_errors", NULL, stats->rx_errors, stats->tx_errors);
+  }
+  else
+  {
+    DEBUG ("netlink plugin: Ignoring %s/interface.", dev);
+  }
+
+  if (check_ignorelist (dev, "if_detail", NULL) == 0)
+  {
+    submit_two (dev, "if_dropped", NULL, stats->rx_dropped, stats->tx_dropped);
+    submit_one (dev, "if_multicast", NULL, stats->multicast);
+    submit_one (dev, "if_collisions", NULL, stats->collisions);
+
+    submit_one (dev, "if_rx_errors", "length", stats->rx_length_errors);
+    submit_one (dev, "if_rx_errors", "over", stats->rx_over_errors);
+    submit_one (dev, "if_rx_errors", "crc", stats->rx_crc_errors);
+    submit_one (dev, "if_rx_errors", "frame", stats->rx_frame_errors);
+    submit_one (dev, "if_rx_errors", "fifo", stats->rx_fifo_errors);
+    submit_one (dev, "if_rx_errors", "missed", stats->rx_missed_errors);
+
+    submit_one (dev, "if_tx_errors", "aborted", stats->tx_aborted_errors);
+    submit_one (dev, "if_tx_errors", "carrier", stats->tx_carrier_errors);
+    submit_one (dev, "if_tx_errors", "fifo", stats->tx_fifo_errors);
+    submit_one (dev, "if_tx_errors", "heartbeat", stats->tx_heartbeat_errors);
+    submit_one (dev, "if_tx_errors", "window", stats->tx_window_errors);
+  }
+  else
+  {
+    DEBUG ("netlink plugin: Ignoring %s/if_detail.", dev);
+  }
+
+  return (0);
+} /* int link_filter */
+
+static int qos_filter (const struct sockaddr_nl __attribute__((unused)) *sa,
+    struct nlmsghdr *nmh, void *args)
+{
+  struct tcmsg *msg;
+  int msg_len;
+  struct rtattr *attrs[TCA_MAX + 1];
+
+  int wanted_ifindex = *((int *) args);
+
+  const char *dev;
+
+  /* char *type_instance; */
+  char *tc_type;
+  char tc_inst[DATA_MAX_NAME_LEN];
+
+  if (nmh->nlmsg_type == RTM_NEWQDISC)
+    tc_type = "qdisc";
+  else if (nmh->nlmsg_type == RTM_NEWTCLASS)
+    tc_type = "class";
+  else if (nmh->nlmsg_type == RTM_NEWTFILTER)
+    tc_type = "filter";
+  else
+  {
+    ERROR ("netlink plugin: qos_filter: Don't know how to handle type %i.",
+       nmh->nlmsg_type);
+    return (-1);
+  }
+
+  msg = NLMSG_DATA (nmh);
+
+  msg_len = nmh->nlmsg_len - sizeof (struct tcmsg);
+  if (msg_len < 0)
+  {
+    ERROR ("netlink plugin: qos_filter: msg_len = %i < 0;", msg_len);
+    return (-1);
+  }
+
+  if (msg->tcm_ifindex != wanted_ifindex)
+  {
+    DEBUG ("netlink plugin: qos_filter: Got %s for interface #%i, "
+       "but expected #%i.",
+       tc_type, msg->tcm_ifindex, wanted_ifindex);
+    return (0);
+  }
+
+  if ((msg->tcm_ifindex >= 0)
+      && ((size_t) msg->tcm_ifindex >= iflist_len))
+  {
+    ERROR ("netlink plugin: qos_filter: msg->tcm_ifindex = %i "
+       ">= iflist_len = %zu",
+       msg->tcm_ifindex, iflist_len);
+    return (-1);
+  }
+
+  dev = iflist[msg->tcm_ifindex];
+  if (dev == NULL)
+  {
+    ERROR ("netlink plugin: qos_filter: iflist[%i] == NULL",
+       msg->tcm_ifindex);
+    return (-1);
+  }
+
+  memset (attrs, '\0', sizeof (attrs));
+  if (parse_rtattr (attrs, TCA_MAX, TCA_RTA (msg), msg_len) != 0)
+  {
+    ERROR ("netlink plugin: qos_filter: parse_rtattr failed.");
+    return (-1);
+  }
+
+  if (attrs[TCA_KIND] == NULL)
+  {
+    ERROR ("netlink plugin: qos_filter: attrs[TCA_KIND] == NULL");
+    return (-1);
+  }
+
+  { /* The the ID */
+    uint32_t numberic_id;
+
+    numberic_id = msg->tcm_handle;
+    if (strcmp (tc_type, "filter") == 0)
+      numberic_id = msg->tcm_parent;
+
+    ssnprintf (tc_inst, sizeof (tc_inst), "%s-%x:%x",
+       (const char *) RTA_DATA (attrs[TCA_KIND]),
+       numberic_id >> 16,
+       numberic_id & 0x0000FFFF);
+  }
+
+  DEBUG ("netlink plugin: qos_filter: got %s for %s (%i).",
+      tc_type, dev, msg->tcm_ifindex);
+  
+  if (check_ignorelist (dev, tc_type, tc_inst))
+    return (0);
+
+#if HAVE_TCA_STATS2
+  if (attrs[TCA_STATS2])
+  {
+    struct rtattr *attrs_stats[TCA_STATS_MAX + 1];
+
+    memset (attrs_stats, '\0', sizeof (attrs_stats));
+    parse_rtattr_nested (attrs_stats, TCA_STATS_MAX, attrs[TCA_STATS2]);
+
+    if (attrs_stats[TCA_STATS_BASIC])
+    {
+      struct gnet_stats_basic bs;
+      char type_instance[DATA_MAX_NAME_LEN];
+
+      ssnprintf (type_instance, sizeof (type_instance), "%s-%s",
+         tc_type, tc_inst);
+
+      memset (&bs, '\0', sizeof (bs));
+      memcpy (&bs, RTA_DATA (attrs_stats[TCA_STATS_BASIC]),
+         MIN (RTA_PAYLOAD (attrs_stats[TCA_STATS_BASIC]), sizeof(bs)));
+
+      submit_one (dev, "ipt_bytes", type_instance, bs.bytes);
+      submit_one (dev, "ipt_packets", type_instance, bs.packets);
+    }
+  }
+#endif /* TCA_STATS2 */
+#if HAVE_TCA_STATS && HAVE_TCA_STATS2
+  else
+#endif
+#if HAVE_TCA_STATS
+  if (attrs[TCA_STATS] != NULL)
+  {
+    struct tc_stats ts;
+    char type_instance[DATA_MAX_NAME_LEN];
+
+    ssnprintf (type_instance, sizeof (type_instance), "%s-%s",
+       tc_type, tc_inst);
+
+    memset(&ts, '\0', sizeof (ts));
+    memcpy(&ts, RTA_DATA (attrs[TCA_STATS]),
+       MIN (RTA_PAYLOAD (attrs[TCA_STATS]), sizeof (ts)));
+
+    submit_one (dev, "ipt_bytes", type_instance, ts.bytes);
+    submit_one (dev, "ipt_packets", type_instance, ts.packets);
+  }
+#endif /* TCA_STATS */
+#if HAVE_TCA_STATS || HAVE_TCA_STATS2
+  else
+#endif
+  {
+    DEBUG ("netlink plugin: qos_filter: Have neither TCA_STATS2 nor "
+       "TCA_STATS.");
+  }
+
+  return (0);
+} /* int qos_filter */
+
+static int ir_config (const char *key, const char *value)
+{
+  char *new_val;
+  char *fields[8];
+  int fields_num;
+  int status = 1;
+
+  new_val = strdup (value);
+  if (new_val == NULL)
+    return (-1);
+
+  fields_num = strsplit (new_val, fields, STATIC_ARRAY_SIZE (fields));
+  if ((fields_num < 1) || (fields_num > 8))
+  {
+    sfree (new_val);
+    return (-1);
+  }
+
+  if ((strcasecmp (key, "Interface") == 0)
+      || (strcasecmp (key, "VerboseInterface") == 0))
+  {
+    if (fields_num != 1)
+    {
+      ERROR ("netlink plugin: Invalid number of fields for option "
+         "`%s'. Got %i, expected 1.", key, fields_num);
+      status = -1;
+    }
+    else
+    {
+      add_ignorelist (fields[0], "interface", NULL);
+      if (strcasecmp (key, "VerboseInterface") == 0)
+       add_ignorelist (fields[0], "if_detail", NULL);
+      status = 0;
+    }
+  }
+  else if ((strcasecmp (key, "QDisc") == 0)
+      || (strcasecmp (key, "Class") == 0)
+      || (strcasecmp (key, "Filter") == 0))
+  {
+    if ((fields_num < 1) || (fields_num > 2))
+    {
+      ERROR ("netlink plugin: Invalid number of fields for option "
+         "`%s'. Got %i, expected 1 or 2.", key, fields_num);
+      return (-1);
+    }
+    else
+    {
+      add_ignorelist (fields[0], key,
+         (fields_num == 2) ? fields[1] : NULL);
+      status = 0;
+    }
+  }
+  else if (strcasecmp (key, "IgnoreSelected") == 0)
+  {
+    if (fields_num != 1)
+    {
+      ERROR ("netlink plugin: Invalid number of fields for option "
+         "`IgnoreSelected'. Got %i, expected 1.", fields_num);
+      status = -1;
+    }
+    else
+    {
+      if (IS_TRUE (fields[0]))
+       ir_ignorelist_invert = 0;
+      else
+       ir_ignorelist_invert = 1;
+      status = 0;
+    }
+  }
+
+  sfree (new_val);
+
+  return (status);
+} /* int ir_config */
+
+static int ir_init (void)
+{
+  memset (&rth, '\0', sizeof (rth));
+
+  if (rtnl_open (&rth, 0) != 0)
+  {
+    ERROR ("netlink plugin: ir_init: rtnl_open failed.");
+    return (-1);
+  }
+
+  return (0);
+} /* int ir_init */
+
+static int ir_read (void)
+{
+  struct ifinfomsg im;
+  struct tcmsg tm;
+  int ifindex;
+
+  static const int type_id[] = { RTM_GETQDISC, RTM_GETTCLASS, RTM_GETTFILTER };
+  static const char *type_name[] = { "qdisc", "class", "filter" };
+
+  memset (&im, '\0', sizeof (im));
+  im.ifi_type = AF_UNSPEC;
+
+  if (rtnl_dump_request (&rth, RTM_GETLINK, &im, sizeof (im)) < 0)
+  {
+    ERROR ("netlink plugin: ir_read: rtnl_dump_request failed.");
+    return (-1);
+  }
+
+  if (rtnl_dump_filter (&rth, link_filter, /* arg1 = */ NULL,
+       NULL, NULL) != 0)
+  {
+    ERROR ("netlink plugin: ir_read: rtnl_dump_filter failed.");
+    return (-1);
+  }
+
+  /* `link_filter' will update `iflist' which is used here to iterate over all
+   * interfaces. */
+  for (ifindex = 0; (size_t) ifindex < iflist_len; ifindex++)
+  {
+    size_t type_index;
+
+    if (iflist[ifindex] == NULL)
+      continue;
+
+    for (type_index = 0; type_index < STATIC_ARRAY_SIZE (type_id); type_index++)
+    {
+      if (check_ignorelist (iflist[ifindex], type_name[type_index], NULL))
+      {
+       DEBUG ("netlink plugin: ir_read: check_ignorelist (%s, %s, (nil)) "
+           "== TRUE", iflist[ifindex], type_name[type_index]);
+       continue;
+      }
+
+      DEBUG ("netlink plugin: ir_read: querying %s from %s (%i).",
+         type_name[type_index], iflist[ifindex], ifindex);
+
+      memset (&tm, '\0', sizeof (tm));
+      tm.tcm_family = AF_UNSPEC;
+      tm.tcm_ifindex = ifindex;
+
+      if (rtnl_dump_request (&rth, type_id[type_index], &tm, sizeof (tm)) < 0)
+      {
+       ERROR ("netlink plugin: ir_read: rtnl_dump_request failed.");
+       continue;
+      }
+
+      if (rtnl_dump_filter (&rth, qos_filter, (void *) &ifindex,
+           NULL, NULL) != 0)
+      {
+       ERROR ("netlink plugin: ir_read: rtnl_dump_filter failed.");
+       continue;
+      }
+    } /* for (type_index) */
+  } /* for (if_index) */
+
+  return (0);
+} /* int ir_read */
+
+static int ir_shutdown (void)
+{
+  if ((rth.fd != 0) || (rth.seq != 0) || (rth.dump != 0))
+  {
+    rtnl_close(&rth);
+    memset (&rth, '\0', sizeof (rth));
+  }
+  
+  return (0);
+} /* int ir_shutdown */
+
+void module_register (void)
+{
+  plugin_register_config ("netlink", ir_config, config_keys, config_keys_num);
+  plugin_register_init ("netlink", ir_init);
+  plugin_register_read ("netlink", ir_read);
+  plugin_register_shutdown ("netlink", ir_shutdown);
+} /* void module_register */
+
+/*
+ * vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
+ */
diff --git a/src/network.c b/src/network.c
new file mode 100644 (file)
index 0000000..840577f
--- /dev/null
@@ -0,0 +1,3393 @@
+/**
+ * collectd - src/network.c
+ * Copyright (C) 2005-2010  Florian octo Forster
+ * Copyright (C) 2009       Aman Gupta
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; only version 2.1 of the License is
+ * applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ *   Aman Gupta <aman at tmm1.net>
+ **/
+
+#define _BSD_SOURCE /* For struct ip_mreq */
+
+#include "collectd.h"
+#include "plugin.h"
+#include "common.h"
+#include "configfile.h"
+#include "utils_fbhash.h"
+#include "utils_avltree.h"
+#include "utils_cache.h"
+#include "utils_complain.h"
+
+#include "network.h"
+
+#if HAVE_PTHREAD_H
+# include <pthread.h>
+#endif
+#if HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif
+#if HAVE_NETDB_H
+# include <netdb.h>
+#endif
+#if HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+#if HAVE_ARPA_INET_H
+# include <arpa/inet.h>
+#endif
+#if HAVE_POLL_H
+# include <poll.h>
+#endif
+#if HAVE_NET_IF_H
+# include <net/if.h>
+#endif
+
+#if HAVE_LIBGCRYPT
+# include <gcrypt.h>
+GCRY_THREAD_OPTION_PTHREAD_IMPL;
+#endif
+
+#ifndef IPV6_ADD_MEMBERSHIP
+# ifdef IPV6_JOIN_GROUP
+#  define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP
+# else
+#  error "Neither IP_ADD_MEMBERSHIP nor IPV6_JOIN_GROUP is defined"
+# endif
+#endif /* !IP_ADD_MEMBERSHIP */
+
+/*
+ * Maximum size required for encryption / signing:
+ *
+ *    42 bytes for the encryption header
+ * +  64 bytes for the username
+ * -----------
+ * = 106 bytes
+ */
+#define BUFF_SIG_SIZE 106
+
+/*
+ * Private data types
+ */
+#define SECURITY_LEVEL_NONE     0
+#if HAVE_LIBGCRYPT
+# define SECURITY_LEVEL_SIGN    1
+# define SECURITY_LEVEL_ENCRYPT 2
+#endif
+struct sockent_client
+{
+       int fd;
+       struct sockaddr_storage *addr;
+       socklen_t                addrlen;
+#if HAVE_LIBGCRYPT
+       int security_level;
+       char *username;
+       char *password;
+       gcry_cipher_hd_t cypher;
+       unsigned char password_hash[32];
+#endif
+};
+
+struct sockent_server
+{
+       int *fd;
+       size_t fd_num;
+#if HAVE_LIBGCRYPT
+       int security_level;
+       char *auth_file;
+       fbhash_t *userdb;
+       gcry_cipher_hd_t cypher;
+#endif
+};
+
+typedef struct sockent
+{
+#define SOCKENT_TYPE_CLIENT 1
+#define SOCKENT_TYPE_SERVER 2
+       int type;
+
+       char *node;
+       char *service;
+       int interface;
+
+       union
+       {
+               struct sockent_client client;
+               struct sockent_server server;
+       } data;
+
+       struct sockent *next;
+} sockent_t;
+
+/*                      1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
+ *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-------+-----------------------+-------------------------------+
+ * ! Ver.  !                       ! Length                        !
+ * +-------+-----------------------+-------------------------------+
+ */
+struct part_header_s
+{
+       uint16_t type;
+       uint16_t length;
+};
+typedef struct part_header_s part_header_t;
+
+/*                      1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
+ *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-------------------------------+-------------------------------+
+ * ! Type                          ! Length                        !
+ * +-------------------------------+-------------------------------+
+ * : (Length - 4) Bytes                                            :
+ * +---------------------------------------------------------------+
+ */
+struct part_string_s
+{
+       part_header_t *head;
+       char *value;
+};
+typedef struct part_string_s part_string_t;
+
+/*                      1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
+ *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-------------------------------+-------------------------------+
+ * ! Type                          ! Length                        !
+ * +-------------------------------+-------------------------------+
+ * : (Length - 4 == 2 || 4 || 8) Bytes                             :
+ * +---------------------------------------------------------------+
+ */
+struct part_number_s
+{
+       part_header_t *head;
+       uint64_t *value;
+};
+typedef struct part_number_s part_number_t;
+
+/*                      1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
+ *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-------------------------------+-------------------------------+
+ * ! Type                          ! Length                        !
+ * +-------------------------------+---------------+---------------+
+ * ! Num of values                 ! Type0         ! Type1         !
+ * +-------------------------------+---------------+---------------+
+ * ! Value0                                                        !
+ * !                                                               !
+ * +---------------------------------------------------------------+
+ * ! Value1                                                        !
+ * !                                                               !
+ * +---------------------------------------------------------------+
+ */
+struct part_values_s
+{
+       part_header_t *head;
+       uint16_t *num_values;
+       uint8_t  *values_types;
+       value_t  *values;
+};
+typedef struct part_values_s part_values_t;
+
+/*                      1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
+ *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-------------------------------+-------------------------------+
+ * ! Type                          ! Length                        !
+ * +-------------------------------+-------------------------------+
+ * ! Hash (Bits   0 -  31)                                         !
+ * : :                                                             :
+ * ! Hash (Bits 224 - 255)                                         !
+ * +---------------------------------------------------------------+
+ */
+/* Minimum size */
+#define PART_SIGNATURE_SHA256_SIZE 36
+struct part_signature_sha256_s
+{
+  part_header_t head;
+  unsigned char hash[32];
+  char *username;
+};
+typedef struct part_signature_sha256_s part_signature_sha256_t;
+
+/*                      1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
+ *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-------------------------------+-------------------------------+
+ * ! Type                          ! Length                        !
+ * +-------------------------------+-------------------------------+
+ * ! Original length               ! Padding (0 - 15 bytes)        !
+ * +-------------------------------+-------------------------------+
+ * ! Hash (Bits   0 -  31)                                         !
+ * : :                                                             :
+ * ! Hash (Bits 128 - 159)                                         !
+ * +---------------------------------------------------------------+
+ */
+/* Minimum size */
+#define PART_ENCRYPTION_AES256_SIZE 42
+struct part_encryption_aes256_s
+{
+  part_header_t head;
+  uint16_t username_length;
+  char *username;
+  unsigned char iv[16];
+  /* <encrypted> */
+  unsigned char hash[20];
+  /*   <payload /> */
+  /* </encrypted> */
+};
+typedef struct part_encryption_aes256_s part_encryption_aes256_t;
+
+struct receive_list_entry_s
+{
+  char *data;
+  int  data_len;
+  int  fd;
+  struct receive_list_entry_s *next;
+};
+typedef struct receive_list_entry_s receive_list_entry_t;
+
+/*
+ * Private variables
+ */
+static int network_config_ttl = 0;
+static size_t network_config_packet_size = 1452;
+static int network_config_forward = 0;
+static int network_config_stats = 0;
+
+static sockent_t *sending_sockets = NULL;
+
+static receive_list_entry_t *receive_list_head = NULL;
+static receive_list_entry_t *receive_list_tail = NULL;
+static pthread_mutex_t       receive_list_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t        receive_list_cond = PTHREAD_COND_INITIALIZER;
+static uint64_t              receive_list_length = 0;
+
+static sockent_t     *listen_sockets = NULL;
+static struct pollfd *listen_sockets_pollfd = NULL;
+static size_t         listen_sockets_num = 0;
+
+/* The receive and dispatch threads will run as long as `listen_loop' is set to
+ * zero. */
+static int       listen_loop = 0;
+static int       receive_thread_running = 0;
+static pthread_t receive_thread_id;
+static int       dispatch_thread_running = 0;
+static pthread_t dispatch_thread_id;
+
+/* Buffer in which to-be-sent network packets are constructed. */
+static char            *send_buffer;
+static char            *send_buffer_ptr;
+static int              send_buffer_fill;
+static value_list_t     send_buffer_vl = VALUE_LIST_STATIC;
+static pthread_mutex_t  send_buffer_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/* XXX: These counters are incremented from one place only. The spot in which
+ * the values are incremented is either only reachable by one thread (the
+ * dispatch thread, for example) or locked by some lock (send_buffer_lock for
+ * example). Only if neither is true, the stats_lock is acquired. The counters
+ * are always read without holding a lock in the hope that writing 8 bytes to
+ * memory is an atomic operation. */
+static derive_t stats_octets_rx  = 0;
+static derive_t stats_octets_tx  = 0;
+static derive_t stats_packets_rx = 0;
+static derive_t stats_packets_tx = 0;
+static derive_t stats_values_dispatched = 0;
+static derive_t stats_values_not_dispatched = 0;
+static derive_t stats_values_sent = 0;
+static derive_t stats_values_not_sent = 0;
+static pthread_mutex_t stats_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/*
+ * Private functions
+ */
+static _Bool check_receive_okay (const value_list_t *vl) /* {{{ */
+{
+  uint64_t time_sent = 0;
+  int status;
+
+  status = uc_meta_data_get_unsigned_int (vl,
+      "network:time_sent", &time_sent);
+
+  /* This is a value we already sent. Don't allow it to be received again in
+   * order to avoid looping. */
+  if ((status == 0) && (time_sent >= ((uint64_t) vl->time)))
+    return (0);
+
+  return (1);
+} /* }}} _Bool check_receive_okay */
+
+static _Bool check_send_okay (const value_list_t *vl) /* {{{ */
+{
+  _Bool received = 0;
+  int status;
+
+  if (network_config_forward != 0)
+    return (1);
+
+  if (vl->meta == NULL)
+    return (1);
+
+  status = meta_data_get_boolean (vl->meta, "network:received", &received);
+  if (status == -ENOENT)
+    return (1);
+  else if (status != 0)
+  {
+    ERROR ("network plugin: check_send_okay: meta_data_get_boolean failed "
+       "with status %i.", status);
+    return (1);
+  }
+
+  /* By default, only *send* value lists that were not *received* by the
+   * network plugin. */
+  return (!received);
+} /* }}} _Bool check_send_okay */
+
+static int network_dispatch_values (value_list_t *vl, /* {{{ */
+    const char *username)
+{
+  int status;
+
+  if ((vl->time <= 0)
+      || (strlen (vl->host) <= 0)
+      || (strlen (vl->plugin) <= 0)
+      || (strlen (vl->type) <= 0))
+    return (-EINVAL);
+
+  if (!check_receive_okay (vl))
+  {
+#if COLLECT_DEBUG
+    char name[6*DATA_MAX_NAME_LEN];
+    FORMAT_VL (name, sizeof (name), vl);
+    name[sizeof (name) - 1] = 0;
+    DEBUG ("network plugin: network_dispatch_values: "
+       "NOT dispatching %s.", name);
+#endif
+    stats_values_not_dispatched++;
+    return (0);
+  }
+
+  assert (vl->meta == NULL);
+
+  vl->meta = meta_data_create ();
+  if (vl->meta == NULL)
+  {
+    ERROR ("network plugin: meta_data_create failed.");
+    return (-ENOMEM);
+  }
+
+  status = meta_data_add_boolean (vl->meta, "network:received", 1);
+  if (status != 0)
+  {
+    ERROR ("network plugin: meta_data_add_boolean failed.");
+    meta_data_destroy (vl->meta);
+    vl->meta = NULL;
+    return (status);
+  }
+
+  if (username != NULL)
+  {
+    status = meta_data_add_string (vl->meta, "network:username", username);
+    if (status != 0)
+    {
+      ERROR ("network plugin: meta_data_add_string failed.");
+      meta_data_destroy (vl->meta);
+      vl->meta = NULL;
+      return (status);
+    }
+  }
+
+  plugin_dispatch_values_secure (vl);
+  stats_values_dispatched++;
+
+  meta_data_destroy (vl->meta);
+  vl->meta = NULL;
+
+  return (0);
+} /* }}} int network_dispatch_values */
+
+#if HAVE_LIBGCRYPT
+static gcry_cipher_hd_t network_get_aes256_cypher (sockent_t *se, /* {{{ */
+    const void *iv, size_t iv_size, const char *username)
+{
+  gcry_error_t err;
+  gcry_cipher_hd_t *cyper_ptr;
+  unsigned char password_hash[32];
+
+  if (se->type == SOCKENT_TYPE_CLIENT)
+  {
+         cyper_ptr = &se->data.client.cypher;
+         memcpy (password_hash, se->data.client.password_hash,
+                         sizeof (password_hash));
+  }
+  else
+  {
+         char *secret;
+
+         cyper_ptr = &se->data.server.cypher;
+
+         if (username == NULL)
+                 return (NULL);
+
+         secret = fbh_get (se->data.server.userdb, username);
+         if (secret == NULL)
+                 return (NULL);
+
+         gcry_md_hash_buffer (GCRY_MD_SHA256,
+                         password_hash,
+                         secret, strlen (secret));
+
+         sfree (secret);
+  }
+
+  if (*cyper_ptr == NULL)
+  {
+    err = gcry_cipher_open (cyper_ptr,
+        GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_OFB, /* flags = */ 0);
+    if (err != 0)
+    {
+      ERROR ("network plugin: gcry_cipher_open returned: %s",
+          gcry_strerror (err));
+      *cyper_ptr = NULL;
+      return (NULL);
+    }
+  }
+  else
+  {
+    gcry_cipher_reset (*cyper_ptr);
+  }
+  assert (*cyper_ptr != NULL);
+
+  err = gcry_cipher_setkey (*cyper_ptr,
+      password_hash, sizeof (password_hash));
+  if (err != 0)
+  {
+    ERROR ("network plugin: gcry_cipher_setkey returned: %s",
+        gcry_strerror (err));
+    gcry_cipher_close (*cyper_ptr);
+    *cyper_ptr = NULL;
+    return (NULL);
+  }
+
+  err = gcry_cipher_setiv (*cyper_ptr, iv, iv_size);
+  if (err != 0)
+  {
+    ERROR ("network plugin: gcry_cipher_setkey returned: %s",
+        gcry_strerror (err));
+    gcry_cipher_close (*cyper_ptr);
+    *cyper_ptr = NULL;
+    return (NULL);
+  }
+
+  return (*cyper_ptr);
+} /* }}} int network_get_aes256_cypher */
+#endif /* HAVE_LIBGCRYPT */
+
+static int write_part_values (char **ret_buffer, int *ret_buffer_len,
+               const data_set_t *ds, const value_list_t *vl)
+{
+       char *packet_ptr;
+       int packet_len;
+       int num_values;
+
+       part_header_t pkg_ph;
+       uint16_t      pkg_num_values;
+       uint8_t      *pkg_values_types;
+       value_t      *pkg_values;
+
+       int offset;
+       int i;
+
+       num_values = vl->values_len;
+       packet_len = sizeof (part_header_t) + sizeof (uint16_t)
+               + (num_values * sizeof (uint8_t))
+               + (num_values * sizeof (value_t));
+
+       if (*ret_buffer_len < packet_len)
+               return (-1);
+
+       pkg_values_types = (uint8_t *) malloc (num_values * sizeof (uint8_t));
+       if (pkg_values_types == NULL)
+       {
+               ERROR ("network plugin: write_part_values: malloc failed.");
+               return (-1);
+       }
+
+       pkg_values = (value_t *) malloc (num_values * sizeof (value_t));
+       if (pkg_values == NULL)
+       {
+               free (pkg_values_types);
+               ERROR ("network plugin: write_part_values: malloc failed.");
+               return (-1);
+       }
+
+       pkg_ph.type = htons (TYPE_VALUES);
+       pkg_ph.length = htons (packet_len);
+
+       pkg_num_values = htons ((uint16_t) vl->values_len);
+
+       for (i = 0; i < num_values; i++)
+       {
+               pkg_values_types[i] = (uint8_t) ds->ds[i].type;
+               switch (ds->ds[i].type)
+               {
+                       case DS_TYPE_COUNTER:
+                               pkg_values[i].counter = htonll (vl->values[i].counter);
+                               break;
+
+                       case DS_TYPE_GAUGE:
+                               pkg_values[i].gauge = htond (vl->values[i].gauge);
+                               break;
+
+                       case DS_TYPE_DERIVE:
+                               pkg_values[i].derive = htonll (vl->values[i].derive);
+                               break;
+
+                       case DS_TYPE_ABSOLUTE:
+                               pkg_values[i].absolute = htonll (vl->values[i].absolute);
+                               break;
+
+                       default:
+                               free (pkg_values_types);
+                               free (pkg_values);
+                               ERROR ("network plugin: write_part_values: "
+                                               "Unknown data source type: %i",
+                                               ds->ds[i].type);
+                               return (-1);
+               } /* switch (ds->ds[i].type) */
+       } /* for (num_values) */
+
+       /*
+        * Use `memcpy' to write everything to the buffer, because the pointer
+        * may be unaligned and some architectures, such as SPARC, can't handle
+        * that.
+        */
+       packet_ptr = *ret_buffer;
+       offset = 0;
+       memcpy (packet_ptr + offset, &pkg_ph, sizeof (pkg_ph));
+       offset += sizeof (pkg_ph);
+       memcpy (packet_ptr + offset, &pkg_num_values, sizeof (pkg_num_values));
+       offset += sizeof (pkg_num_values);
+       memcpy (packet_ptr + offset, pkg_values_types, num_values * sizeof (uint8_t));
+       offset += num_values * sizeof (uint8_t);
+       memcpy (packet_ptr + offset, pkg_values, num_values * sizeof (value_t));
+       offset += num_values * sizeof (value_t);
+
+       assert (offset == packet_len);
+
+       *ret_buffer = packet_ptr + packet_len;
+       *ret_buffer_len -= packet_len;
+
+       free (pkg_values_types);
+       free (pkg_values);
+
+       return (0);
+} /* int write_part_values */
+
+static int write_part_number (char **ret_buffer, int *ret_buffer_len,
+               int type, uint64_t value)
+{
+       char *packet_ptr;
+       int packet_len;
+
+       part_header_t pkg_head;
+       uint64_t pkg_value;
+       
+       int offset;
+
+       packet_len = sizeof (pkg_head) + sizeof (pkg_value);
+
+       if (*ret_buffer_len < packet_len)
+               return (-1);
+
+       pkg_head.type = htons (type);
+       pkg_head.length = htons (packet_len);
+       pkg_value = htonll (value);
+
+       packet_ptr = *ret_buffer;
+       offset = 0;
+       memcpy (packet_ptr + offset, &pkg_head, sizeof (pkg_head));
+       offset += sizeof (pkg_head);
+       memcpy (packet_ptr + offset, &pkg_value, sizeof (pkg_value));
+       offset += sizeof (pkg_value);
+
+       assert (offset == packet_len);
+
+       *ret_buffer = packet_ptr + packet_len;
+       *ret_buffer_len -= packet_len;
+
+       return (0);
+} /* int write_part_number */
+
+static int write_part_string (char **ret_buffer, int *ret_buffer_len,
+               int type, const char *str, int str_len)
+{
+       char *buffer;
+       int buffer_len;
+
+       uint16_t pkg_type;
+       uint16_t pkg_length;
+
+       int offset;
+
+       buffer_len = 2 * sizeof (uint16_t) + str_len + 1;
+       if (*ret_buffer_len < buffer_len)
+               return (-1);
+
+       pkg_type = htons (type);
+       pkg_length = htons (buffer_len);
+
+       buffer = *ret_buffer;
+       offset = 0;
+       memcpy (buffer + offset, (void *) &pkg_type, sizeof (pkg_type));
+       offset += sizeof (pkg_type);
+       memcpy (buffer + offset, (void *) &pkg_length, sizeof (pkg_length));
+       offset += sizeof (pkg_length);
+       memcpy (buffer + offset, str, str_len);
+       offset += str_len;
+       memset (buffer + offset, '\0', 1);
+       offset += 1;
+
+       assert (offset == buffer_len);
+
+       *ret_buffer = buffer + buffer_len;
+       *ret_buffer_len -= buffer_len;
+
+       return (0);
+} /* int write_part_string */
+
+static int parse_part_values (void **ret_buffer, size_t *ret_buffer_len,
+               value_t **ret_values, int *ret_num_values)
+{
+       char *buffer = *ret_buffer;
+       size_t buffer_len = *ret_buffer_len;
+
+       uint16_t tmp16;
+       size_t exp_size;
+       int   i;
+
+       uint16_t pkg_length;
+       uint16_t pkg_type;
+       uint16_t pkg_numval;
+
+       uint8_t *pkg_types;
+       value_t *pkg_values;
+
+       if (buffer_len < 15)
+       {
+               NOTICE ("network plugin: packet is too short: "
+                               "buffer_len = %zu", buffer_len);
+               return (-1);
+       }
+
+       memcpy ((void *) &tmp16, buffer, sizeof (tmp16));
+       buffer += sizeof (tmp16);
+       pkg_type = ntohs (tmp16);
+
+       memcpy ((void *) &tmp16, buffer, sizeof (tmp16));
+       buffer += sizeof (tmp16);
+       pkg_length = ntohs (tmp16);
+
+       memcpy ((void *) &tmp16, buffer, sizeof (tmp16));
+       buffer += sizeof (tmp16);
+       pkg_numval = ntohs (tmp16);
+
+       assert (pkg_type == TYPE_VALUES);
+
+       exp_size = 3 * sizeof (uint16_t)
+               + pkg_numval * (sizeof (uint8_t) + sizeof (value_t));
+       if ((buffer_len < 0) || (buffer_len < exp_size))
+       {
+               WARNING ("network plugin: parse_part_values: "
+                               "Packet too short: "
+                               "Chunk of size %zu expected, "
+                               "but buffer has only %zu bytes left.",
+                               exp_size, buffer_len);
+               return (-1);
+       }
+
+       if (pkg_length != exp_size)
+       {
+               WARNING ("network plugin: parse_part_values: "
+                               "Length and number of values "
+                               "in the packet don't match.");
+               return (-1);
+       }
+
+       pkg_types = (uint8_t *) malloc (pkg_numval * sizeof (uint8_t));
+       pkg_values = (value_t *) malloc (pkg_numval * sizeof (value_t));
+       if ((pkg_types == NULL) || (pkg_values == NULL))
+       {
+               sfree (pkg_types);
+               sfree (pkg_values);
+               ERROR ("network plugin: parse_part_values: malloc failed.");
+               return (-1);
+       }
+
+       memcpy ((void *) pkg_types, (void *) buffer, pkg_numval * sizeof (uint8_t));
+       buffer += pkg_numval * sizeof (uint8_t);
+       memcpy ((void *) pkg_values, (void *) buffer, pkg_numval * sizeof (value_t));
+       buffer += pkg_numval * sizeof (value_t);
+
+       for (i = 0; i < pkg_numval; i++)
+       {
+               switch (pkg_types[i])
+               {
+                 case DS_TYPE_COUNTER:
+                   pkg_values[i].counter = (counter_t) ntohll (pkg_values[i].counter);
+                   break;
+
+                 case DS_TYPE_GAUGE:
+                   pkg_values[i].gauge = (gauge_t) ntohd (pkg_values[i].gauge);
+                   break;
+
+                 case DS_TYPE_DERIVE:
+                   pkg_values[i].derive = (derive_t) ntohll (pkg_values[i].derive);
+                   break;
+
+                 case DS_TYPE_ABSOLUTE:
+                   pkg_values[i].absolute = (absolute_t) ntohll (pkg_values[i].absolute);
+                   break;
+
+                 default:
+                   NOTICE ("network plugin: parse_part_values: "
+                       "Don't know how to handle data source type %"PRIu8,
+                       pkg_types[i]);
+                   sfree (pkg_types);
+                   sfree (pkg_values);
+                   return (-1);
+               } /* switch (pkg_types[i]) */
+       }
+
+       *ret_buffer     = buffer;
+       *ret_buffer_len = buffer_len - pkg_length;
+       *ret_num_values = pkg_numval;
+       *ret_values     = pkg_values;
+
+       sfree (pkg_types);
+
+       return (0);
+} /* int parse_part_values */
+
+static int parse_part_number (void **ret_buffer, size_t *ret_buffer_len,
+               uint64_t *value)
+{
+       char *buffer = *ret_buffer;
+       size_t buffer_len = *ret_buffer_len;
+
+       uint16_t tmp16;
+       uint64_t tmp64;
+       size_t exp_size = 2 * sizeof (uint16_t) + sizeof (uint64_t);
+
+       uint16_t pkg_length;
+
+       if ((buffer_len < 0) || ((size_t) buffer_len < exp_size))
+       {
+               WARNING ("network plugin: parse_part_number: "
+                               "Packet too short: "
+                               "Chunk of size %zu expected, "
+                               "but buffer has only %zu bytes left.",
+                               exp_size, buffer_len);
+               return (-1);
+       }
+
+       memcpy ((void *) &tmp16, buffer, sizeof (tmp16));
+       buffer += sizeof (tmp16);
+       /* pkg_type = ntohs (tmp16); */
+
+       memcpy ((void *) &tmp16, buffer, sizeof (tmp16));
+       buffer += sizeof (tmp16);
+       pkg_length = ntohs (tmp16);
+
+       memcpy ((void *) &tmp64, buffer, sizeof (tmp64));
+       buffer += sizeof (tmp64);
+       *value = ntohll (tmp64);
+
+       *ret_buffer = buffer;
+       *ret_buffer_len = buffer_len - pkg_length;
+
+       return (0);
+} /* int parse_part_number */
+
+static int parse_part_string (void **ret_buffer, size_t *ret_buffer_len,
+               char *output, int output_len)
+{
+       char *buffer = *ret_buffer;
+       size_t buffer_len = *ret_buffer_len;
+
+       uint16_t tmp16;
+       size_t header_size = 2 * sizeof (uint16_t);
+
+       uint16_t pkg_length;
+
+       if ((buffer_len < 0) || (buffer_len < header_size))
+       {
+               WARNING ("network plugin: parse_part_string: "
+                               "Packet too short: "
+                               "Chunk of at least size %zu expected, "
+                               "but buffer has only %zu bytes left.",
+                               header_size, buffer_len);
+               return (-1);
+       }
+
+       memcpy ((void *) &tmp16, buffer, sizeof (tmp16));
+       buffer += sizeof (tmp16);
+       /* pkg_type = ntohs (tmp16); */
+
+       memcpy ((void *) &tmp16, buffer, sizeof (tmp16));
+       buffer += sizeof (tmp16);
+       pkg_length = ntohs (tmp16);
+
+       /* Check that packet fits in the input buffer */
+       if (pkg_length > buffer_len)
+       {
+               WARNING ("network plugin: parse_part_string: "
+                               "Packet too big: "
+                               "Chunk of size %"PRIu16" received, "
+                               "but buffer has only %zu bytes left.",
+                               pkg_length, buffer_len);
+               return (-1);
+       }
+
+       /* Check that pkg_length is in the valid range */
+       if (pkg_length <= header_size)
+       {
+               WARNING ("network plugin: parse_part_string: "
+                               "Packet too short: "
+                               "Header claims this packet is only %hu "
+                               "bytes long.", pkg_length);
+               return (-1);
+       }
+
+       /* Check that the package data fits into the output buffer.
+        * The previous if-statement ensures that:
+        * `pkg_length > header_size' */
+       if ((output_len < 0)
+                       || ((size_t) output_len < ((size_t) pkg_length - header_size)))
+       {
+               WARNING ("network plugin: parse_part_string: "
+                               "Output buffer too small.");
+               return (-1);
+       }
+
+       /* All sanity checks successfull, let's copy the data over */
+       output_len = pkg_length - header_size;
+       memcpy ((void *) output, (void *) buffer, output_len);
+       buffer += output_len;
+
+       /* For some very weird reason '\0' doesn't do the trick on SPARC in
+        * this statement. */
+       if (output[output_len - 1] != 0)
+       {
+               WARNING ("network plugin: parse_part_string: "
+                               "Received string does not end "
+                               "with a NULL-byte.");
+               return (-1);
+       }
+
+       *ret_buffer = buffer;
+       *ret_buffer_len = buffer_len - pkg_length;
+
+       return (0);
+} /* int parse_part_string */
+
+/* Forward declaration: parse_part_sign_sha256 and parse_part_encr_aes256 call
+ * parse_packet and vice versa. */
+#define PP_SIGNED    0x01
+#define PP_ENCRYPTED 0x02
+static int parse_packet (sockent_t *se,
+               void *buffer, size_t buffer_size, int flags,
+               const char *username);
+
+#define BUFFER_READ(p,s) do { \
+  memcpy ((p), buffer + buffer_offset, (s)); \
+  buffer_offset += (s); \
+} while (0)
+
+#if HAVE_LIBGCRYPT
+static int parse_part_sign_sha256 (sockent_t *se, /* {{{ */
+    void **ret_buffer, size_t *ret_buffer_len, int flags)
+{
+  static c_complain_t complain_no_users = C_COMPLAIN_INIT_STATIC;
+
+  char *buffer;
+  size_t buffer_len;
+  size_t buffer_offset;
+
+  size_t username_len;
+  char *secret;
+
+  part_signature_sha256_t pss;
+  uint16_t pss_head_length;
+  char hash[sizeof (pss.hash)];
+
+  gcry_md_hd_t hd;
+  gcry_error_t err;
+  unsigned char *hash_ptr;
+
+  buffer = *ret_buffer;
+  buffer_len = *ret_buffer_len;
+  buffer_offset = 0;
+
+  if (se->data.server.userdb == NULL)
+  {
+    c_complain (LOG_NOTICE, &complain_no_users,
+        "network plugin: Received signed network packet but can't verify it "
+        "because no user DB has been configured. Will accept it.");
+    return (0);
+  }
+
+  /* Check if the buffer has enough data for this structure. */
+  if (buffer_len <= PART_SIGNATURE_SHA256_SIZE)
+    return (-ENOMEM);
+
+  /* Read type and length header */
+  BUFFER_READ (&pss.head.type, sizeof (pss.head.type));
+  BUFFER_READ (&pss.head.length, sizeof (pss.head.length));
+  pss_head_length = ntohs (pss.head.length);
+
+  /* Check if the `pss_head_length' is within bounds. */
+  if ((pss_head_length <= PART_SIGNATURE_SHA256_SIZE)
+      || (pss_head_length > buffer_len))
+  {
+    ERROR ("network plugin: HMAC-SHA-256 with invalid length received.");
+    return (-1);
+  }
+
+  /* Copy the hash. */
+  BUFFER_READ (pss.hash, sizeof (pss.hash));
+
+  /* Calculate username length (without null byte) and allocate memory */
+  username_len = pss_head_length - PART_SIGNATURE_SHA256_SIZE;
+  pss.username = malloc (username_len + 1);
+  if (pss.username == NULL)
+    return (-ENOMEM);
+
+  /* Read the username */
+  BUFFER_READ (pss.username, username_len);
+  pss.username[username_len] = 0;
+
+  assert (buffer_offset == pss_head_length);
+
+  /* Query the password */
+  secret = fbh_get (se->data.server.userdb, pss.username);
+  if (secret == NULL)
+  {
+    ERROR ("network plugin: Unknown user: %s", pss.username);
+    sfree (pss.username);
+    return (-ENOENT);
+  }
+
+  /* Create a hash device and check the HMAC */
+  hd = NULL;
+  err = gcry_md_open (&hd, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC);
+  if (err != 0)
+  {
+    ERROR ("network plugin: Creating HMAC-SHA-256 object failed: %s",
+        gcry_strerror (err));
+    sfree (secret);
+    sfree (pss.username);
+    return (-1);
+  }
+
+  err = gcry_md_setkey (hd, secret, strlen (secret));
+  if (err != 0)
+  {
+    ERROR ("network plugin: gcry_md_setkey failed: %s", gcry_strerror (err));
+    gcry_md_close (hd);
+    sfree (secret);
+    sfree (pss.username);
+    return (-1);
+  }
+
+  gcry_md_write (hd,
+      buffer     + PART_SIGNATURE_SHA256_SIZE,
+      buffer_len - PART_SIGNATURE_SHA256_SIZE);
+  hash_ptr = gcry_md_read (hd, GCRY_MD_SHA256);
+  if (hash_ptr == NULL)
+  {
+    ERROR ("network plugin: gcry_md_read failed.");
+    gcry_md_close (hd);
+    sfree (secret);
+    sfree (pss.username);
+    return (-1);
+  }
+  memcpy (hash, hash_ptr, sizeof (hash));
+
+  /* Clean up */
+  gcry_md_close (hd);
+  hd = NULL;
+
+  if (memcmp (pss.hash, hash, sizeof (pss.hash)) != 0)
+  {
+    WARNING ("network plugin: Verifying HMAC-SHA-256 signature failed: "
+        "Hash mismatch.");
+  }
+  else
+  {
+    parse_packet (se, buffer + buffer_offset, buffer_len - buffer_offset,
+        flags | PP_SIGNED, pss.username);
+  }
+
+  sfree (secret);
+  sfree (pss.username);
+
+  *ret_buffer = buffer + buffer_len;
+  *ret_buffer_len = 0;
+
+  return (0);
+} /* }}} int parse_part_sign_sha256 */
+/* #endif HAVE_LIBGCRYPT */
+
+#else /* if !HAVE_LIBGCRYPT */
+static int parse_part_sign_sha256 (sockent_t *se, /* {{{ */
+    void **ret_buffer, size_t *ret_buffer_size, int flags)
+{
+  static int warning_has_been_printed = 0;
+
+  char *buffer;
+  size_t buffer_size;
+  size_t buffer_offset;
+  uint16_t part_len;
+
+  part_signature_sha256_t pss;
+
+  buffer = *ret_buffer;
+  buffer_size = *ret_buffer_size;
+  buffer_offset = 0;
+
+  if (buffer_size <= PART_SIGNATURE_SHA256_SIZE)
+    return (-ENOMEM);
+
+  BUFFER_READ (&pss.head.type, sizeof (pss.head.type));
+  BUFFER_READ (&pss.head.length, sizeof (pss.head.length));
+  part_len = ntohs (pss.head.length);
+
+  if ((part_len <= PART_SIGNATURE_SHA256_SIZE)
+      || (part_len > buffer_size))
+    return (-EINVAL);
+
+  if (warning_has_been_printed == 0)
+  {
+    WARNING ("network plugin: Received signed packet, but the network "
+        "plugin was not linked with libgcrypt, so I cannot "
+        "verify the signature. The packet will be accepted.");
+    warning_has_been_printed = 1;
+  }
+
+  parse_packet (se, buffer + part_len, buffer_size - part_len, flags,
+      /* username = */ NULL);
+
+  *ret_buffer = buffer + buffer_size;
+  *ret_buffer_size = 0;
+
+  return (0);
+} /* }}} int parse_part_sign_sha256 */
+#endif /* !HAVE_LIBGCRYPT */
+
+#if HAVE_LIBGCRYPT
+static int parse_part_encr_aes256 (sockent_t *se, /* {{{ */
+               void **ret_buffer, size_t *ret_buffer_len,
+               int flags)
+{
+  char  *buffer = *ret_buffer;
+  size_t buffer_len = *ret_buffer_len;
+  size_t payload_len;
+  size_t part_size;
+  size_t buffer_offset;
+  uint16_t username_len;
+  part_encryption_aes256_t pea;
+  unsigned char hash[sizeof (pea.hash)];
+
+  gcry_cipher_hd_t cypher;
+  gcry_error_t err;
+
+  /* Make sure at least the header if available. */
+  if (buffer_len <= PART_ENCRYPTION_AES256_SIZE)
+  {
+    NOTICE ("network plugin: parse_part_encr_aes256: "
+        "Discarding short packet.");
+    return (-1);
+  }
+
+  buffer_offset = 0;
+
+  /* Copy the unencrypted information into `pea'. */
+  BUFFER_READ (&pea.head.type, sizeof (pea.head.type));
+  BUFFER_READ (&pea.head.length, sizeof (pea.head.length));
+
+  /* Check the `part size'. */
+  part_size = ntohs (pea.head.length);
+  if ((part_size <= PART_ENCRYPTION_AES256_SIZE)
+      || (part_size > buffer_len))
+  {
+    NOTICE ("network plugin: parse_part_encr_aes256: "
+        "Discarding part with invalid size.");
+    return (-1);
+  }
+
+  /* Read the username */
+  BUFFER_READ (&username_len, sizeof (username_len));
+  username_len = ntohs (username_len);
+
+  if ((username_len <= 0)
+      || (username_len > (part_size - (PART_ENCRYPTION_AES256_SIZE + 1))))
+  {
+    NOTICE ("network plugin: parse_part_encr_aes256: "
+        "Discarding part with invalid username length.");
+    return (-1);
+  }
+
+  assert (username_len > 0);
+  pea.username = malloc (username_len + 1);
+  if (pea.username == NULL)
+    return (-ENOMEM);
+  BUFFER_READ (pea.username, username_len);
+  pea.username[username_len] = 0;
+
+  /* Last but not least, the initialization vector */
+  BUFFER_READ (pea.iv, sizeof (pea.iv));
+
+  /* Make sure we are at the right position */
+  assert (buffer_offset == (username_len +
+        PART_ENCRYPTION_AES256_SIZE - sizeof (pea.hash)));
+
+  cypher = network_get_aes256_cypher (se, pea.iv, sizeof (pea.iv),
+      pea.username);
+  if (cypher == NULL)
+  {
+    sfree (pea.username);
+    return (-1);
+  }
+
+  payload_len = part_size - (PART_ENCRYPTION_AES256_SIZE + username_len);
+  assert (payload_len > 0);
+
+  /* Decrypt the packet in-place */
+  err = gcry_cipher_decrypt (cypher,
+      buffer    + buffer_offset,
+      part_size - buffer_offset,
+      /* in = */ NULL, /* in len = */ 0);
+  if (err != 0)
+  {
+    sfree (pea.username);
+    ERROR ("network plugin: gcry_cipher_decrypt returned: %s",
+        gcry_strerror (err));
+    return (-1);
+  }
+
+  /* Read the hash */
+  BUFFER_READ (pea.hash, sizeof (pea.hash));
+
+  /* Make sure we're at the right position - again */
+  assert (buffer_offset == (username_len + PART_ENCRYPTION_AES256_SIZE));
+  assert (buffer_offset == (part_size - payload_len));
+
+  /* Check hash sum */
+  memset (hash, 0, sizeof (hash));
+  gcry_md_hash_buffer (GCRY_MD_SHA1, hash,
+      buffer + buffer_offset, payload_len);
+  if (memcmp (hash, pea.hash, sizeof (hash)) != 0)
+  {
+    sfree (pea.username);
+    ERROR ("network plugin: Decryption failed: Checksum mismatch.");
+    return (-1);
+  }
+
+  parse_packet (se, buffer + buffer_offset, payload_len,
+      flags | PP_ENCRYPTED, pea.username);
+
+  /* XXX: Free pea.username?!? */
+
+  /* Update return values */
+  *ret_buffer =     buffer     + part_size;
+  *ret_buffer_len = buffer_len - part_size;
+
+  sfree (pea.username);
+
+  return (0);
+} /* }}} int parse_part_encr_aes256 */
+/* #endif HAVE_LIBGCRYPT */
+
+#else /* if !HAVE_LIBGCRYPT */
+static int parse_part_encr_aes256 (sockent_t *se, /* {{{ */
+    void **ret_buffer, size_t *ret_buffer_size, int flags)
+{
+  static int warning_has_been_printed = 0;
+
+  char *buffer;
+  size_t buffer_size;
+  size_t buffer_offset;
+
+  part_header_t ph;
+  size_t ph_length;
+
+  buffer = *ret_buffer;
+  buffer_size = *ret_buffer_size;
+  buffer_offset = 0;
+
+  /* parse_packet assures this minimum size. */
+  assert (buffer_size >= (sizeof (ph.type) + sizeof (ph.length)));
+
+  BUFFER_READ (&ph.type, sizeof (ph.type));
+  BUFFER_READ (&ph.length, sizeof (ph.length));
+  ph_length = ntohs (ph.length);
+
+  if ((ph_length <= PART_ENCRYPTION_AES256_SIZE)
+      || (ph_length > buffer_size))
+  {
+    ERROR ("network plugin: AES-256 encrypted part "
+        "with invalid length received.");
+    return (-1);
+  }
+
+  if (warning_has_been_printed == 0)
+  {
+    WARNING ("network plugin: Received encrypted packet, but the network "
+        "plugin was not linked with libgcrypt, so I cannot "
+        "decrypt it. The part will be discarded.");
+    warning_has_been_printed = 1;
+  }
+
+  *ret_buffer += ph_length;
+  *ret_buffer_size -= ph_length;
+
+  return (0);
+} /* }}} int parse_part_encr_aes256 */
+#endif /* !HAVE_LIBGCRYPT */
+
+#undef BUFFER_READ
+
+static int parse_packet (sockent_t *se, /* {{{ */
+               void *buffer, size_t buffer_size, int flags,
+               const char *username)
+{
+       int status;
+
+       value_list_t vl = VALUE_LIST_INIT;
+       notification_t n;
+
+#if HAVE_LIBGCRYPT
+       int packet_was_signed = (flags & PP_SIGNED);
+        int packet_was_encrypted = (flags & PP_ENCRYPTED);
+       int printed_ignore_warning = 0;
+#endif /* HAVE_LIBGCRYPT */
+
+
+       memset (&vl, '\0', sizeof (vl));
+       memset (&n, '\0', sizeof (n));
+       status = 0;
+
+       while ((status == 0) && (0 < buffer_size)
+                       && ((unsigned int) buffer_size > sizeof (part_header_t)))
+       {
+               uint16_t pkg_length;
+               uint16_t pkg_type;
+
+               memcpy ((void *) &pkg_type,
+                               (void *) buffer,
+                               sizeof (pkg_type));
+               memcpy ((void *) &pkg_length,
+                               (void *) (buffer + sizeof (pkg_type)),
+                               sizeof (pkg_length));
+
+               pkg_length = ntohs (pkg_length);
+               pkg_type = ntohs (pkg_type);
+
+               if (pkg_length > buffer_size)
+                       break;
+               /* Ensure that this loop terminates eventually */
+               if (pkg_length < (2 * sizeof (uint16_t)))
+                       break;
+
+               if (pkg_type == TYPE_ENCR_AES256)
+               {
+                       status = parse_part_encr_aes256 (se,
+                                       &buffer, &buffer_size, flags);
+                       if (status != 0)
+                       {
+                               ERROR ("network plugin: Decrypting AES256 "
+                                               "part failed "
+                                               "with status %i.", status);
+                               break;
+                       }
+               }
+#if HAVE_LIBGCRYPT
+               else if ((se->data.server.security_level == SECURITY_LEVEL_ENCRYPT)
+                               && (packet_was_encrypted == 0))
+               {
+                       if (printed_ignore_warning == 0)
+                       {
+                               INFO ("network plugin: Unencrypted packet or "
+                                               "part has been ignored.");
+                               printed_ignore_warning = 1;
+                       }
+                       buffer = ((char *) buffer) + pkg_length;
+                       continue;
+               }
+#endif /* HAVE_LIBGCRYPT */
+               else if (pkg_type == TYPE_SIGN_SHA256)
+               {
+                       status = parse_part_sign_sha256 (se,
+                                        &buffer, &buffer_size, flags);
+                       if (status != 0)
+                       {
+                               ERROR ("network plugin: Verifying HMAC-SHA-256 "
+                                               "signature failed "
+                                               "with status %i.", status);
+                               break;
+                       }
+               }
+#if HAVE_LIBGCRYPT
+               else if ((se->data.server.security_level == SECURITY_LEVEL_SIGN)
+                               && (packet_was_encrypted == 0)
+                               && (packet_was_signed == 0))
+               {
+                       if (printed_ignore_warning == 0)
+                       {
+                               INFO ("network plugin: Unsigned packet or "
+                                               "part has been ignored.");
+                               printed_ignore_warning = 1;
+                       }
+                       buffer = ((char *) buffer) + pkg_length;
+                       continue;
+               }
+#endif /* HAVE_LIBGCRYPT */
+               else if (pkg_type == TYPE_VALUES)
+               {
+                       status = parse_part_values (&buffer, &buffer_size,
+                                       &vl.values, &vl.values_len);
+                       if (status != 0)
+                               break;
+
+                       network_dispatch_values (&vl, username);
+
+                       sfree (vl.values);
+               }
+               else if (pkg_type == TYPE_TIME)
+               {
+                       uint64_t tmp = 0;
+                       status = parse_part_number (&buffer, &buffer_size,
+                                       &tmp);
+                       if (status == 0)
+                       {
+                               vl.time = TIME_T_TO_CDTIME_T (tmp);
+                               n.time  = TIME_T_TO_CDTIME_T (tmp);
+                       }
+               }
+               else if (pkg_type == TYPE_TIME_HR)
+               {
+                       uint64_t tmp = 0;
+                       status = parse_part_number (&buffer, &buffer_size,
+                                       &tmp);
+                       if (status == 0)
+                       {
+                               vl.time = (cdtime_t) tmp;
+                               n.time  = (cdtime_t) tmp;
+                       }
+               }
+               else if (pkg_type == TYPE_INTERVAL)
+               {
+                       uint64_t tmp = 0;
+                       status = parse_part_number (&buffer, &buffer_size,
+                                       &tmp);
+                       if (status == 0)
+                               vl.interval = TIME_T_TO_CDTIME_T (tmp);
+               }
+               else if (pkg_type == TYPE_INTERVAL_HR)
+               {
+                       uint64_t tmp = 0;
+                       status = parse_part_number (&buffer, &buffer_size,
+                                       &tmp);
+                       if (status == 0)
+                               vl.interval = (cdtime_t) tmp;
+               }
+               else if (pkg_type == TYPE_HOST)
+               {
+                       status = parse_part_string (&buffer, &buffer_size,
+                                       vl.host, sizeof (vl.host));
+                       if (status == 0)
+                               sstrncpy (n.host, vl.host, sizeof (n.host));
+               }
+               else if (pkg_type == TYPE_PLUGIN)
+               {
+                       status = parse_part_string (&buffer, &buffer_size,
+                                       vl.plugin, sizeof (vl.plugin));
+                       if (status == 0)
+                               sstrncpy (n.plugin, vl.plugin,
+                                               sizeof (n.plugin));
+               }
+               else if (pkg_type == TYPE_PLUGIN_INSTANCE)
+               {
+                       status = parse_part_string (&buffer, &buffer_size,
+                                       vl.plugin_instance,
+                                       sizeof (vl.plugin_instance));
+                       if (status == 0)
+                               sstrncpy (n.plugin_instance,
+                                               vl.plugin_instance,
+                                               sizeof (n.plugin_instance));
+               }
+               else if (pkg_type == TYPE_TYPE)
+               {
+                       status = parse_part_string (&buffer, &buffer_size,
+                                       vl.type, sizeof (vl.type));
+                       if (status == 0)
+                               sstrncpy (n.type, vl.type, sizeof (n.type));
+               }
+               else if (pkg_type == TYPE_TYPE_INSTANCE)
+               {
+                       status = parse_part_string (&buffer, &buffer_size,
+                                       vl.type_instance,
+                                       sizeof (vl.type_instance));
+                       if (status == 0)
+                               sstrncpy (n.type_instance, vl.type_instance,
+                                               sizeof (n.type_instance));
+               }
+               else if (pkg_type == TYPE_MESSAGE)
+               {
+                       status = parse_part_string (&buffer, &buffer_size,
+                                       n.message, sizeof (n.message));
+
+                       if (status != 0)
+                       {
+                               /* do nothing */
+                       }
+                       else if ((n.severity != NOTIF_FAILURE)
+                                       && (n.severity != NOTIF_WARNING)
+                                       && (n.severity != NOTIF_OKAY))
+                       {
+                               INFO ("network plugin: "
+                                               "Ignoring notification with "
+                                               "unknown severity %i.",
+                                               n.severity);
+                       }
+                       else if (n.time <= 0)
+                       {
+                               INFO ("network plugin: "
+                                               "Ignoring notification with "
+                                               "time == 0.");
+                       }
+                       else if (strlen (n.message) <= 0)
+                       {
+                               INFO ("network plugin: "
+                                               "Ignoring notification with "
+                                               "an empty message.");
+                       }
+                       else
+                       {
+                               plugin_dispatch_notification (&n);
+                       }
+               }
+               else if (pkg_type == TYPE_SEVERITY)
+               {
+                       uint64_t tmp = 0;
+                       status = parse_part_number (&buffer, &buffer_size,
+                                       &tmp);
+                       if (status == 0)
+                               n.severity = (int) tmp;
+               }
+               else
+               {
+                       DEBUG ("network plugin: parse_packet: Unknown part"
+                                       " type: 0x%04hx", pkg_type);
+                       buffer = ((char *) buffer) + pkg_length;
+               }
+       } /* while (buffer_size > sizeof (part_header_t)) */
+
+       if (status == 0 && buffer_size > 0)
+               WARNING ("network plugin: parse_packet: Received truncated "
+                               "packet, try increasing `MaxPacketSize'");
+
+       return (status);
+} /* }}} int parse_packet */
+
+static void free_sockent_client (struct sockent_client *sec) /* {{{ */
+{
+  if (sec->fd >= 0)
+  {
+    close (sec->fd);
+    sec->fd = -1;
+  }
+  sfree (sec->addr);
+#if HAVE_LIBGCRYPT
+  sfree (sec->username);
+  sfree (sec->password);
+  if (sec->cypher != NULL)
+    gcry_cipher_close (sec->cypher);
+#endif
+} /* }}} void free_sockent_client */
+
+static void free_sockent_server (struct sockent_server *ses) /* {{{ */
+{
+  size_t i;
+
+  for (i = 0; i < ses->fd_num; i++)
+  {
+    if (ses->fd[i] >= 0)
+    {
+      close (ses->fd[i]);
+      ses->fd[i] = -1;
+    }
+  }
+
+  sfree (ses->fd);
+#if HAVE_LIBGCRYPT
+  sfree (ses->auth_file);
+  fbh_destroy (ses->userdb);
+  if (ses->cypher != NULL)
+    gcry_cipher_close (ses->cypher);
+#endif
+} /* }}} void free_sockent_server */
+
+static void sockent_destroy (sockent_t *se) /* {{{ */
+{
+  sockent_t *next;
+
+  DEBUG ("network plugin: sockent_destroy (se = %p);", (void *) se);
+
+  while (se != NULL)
+  {
+    next = se->next;
+
+    sfree (se->node);
+    sfree (se->service);
+
+    if (se->type == SOCKENT_TYPE_CLIENT)
+      free_sockent_client (&se->data.client);
+    else
+      free_sockent_server (&se->data.server);
+
+    sfree (se);
+    se = next;
+  }
+} /* }}} void sockent_destroy */
+
+/*
+ * int network_set_ttl
+ *
+ * Set the `IP_MULTICAST_TTL', `IP_TTL', `IPV6_MULTICAST_HOPS' or
+ * `IPV6_UNICAST_HOPS', depending on which option is applicable.
+ *
+ * The `struct addrinfo' is used to destinguish between unicast and multicast
+ * sockets.
+ */
+static int network_set_ttl (const sockent_t *se, const struct addrinfo *ai)
+{
+       DEBUG ("network plugin: network_set_ttl: network_config_ttl = %i;",
+                       network_config_ttl);
+
+        assert (se->type == SOCKENT_TYPE_CLIENT);
+
+       if ((network_config_ttl < 1) || (network_config_ttl > 255))
+               return (-1);
+
+       if (ai->ai_family == AF_INET)
+       {
+               struct sockaddr_in *addr = (struct sockaddr_in *) ai->ai_addr;
+               int optname;
+
+               if (IN_MULTICAST (ntohl (addr->sin_addr.s_addr)))
+                       optname = IP_MULTICAST_TTL;
+               else
+                       optname = IP_TTL;
+
+               if (setsockopt (se->data.client.fd, IPPROTO_IP, optname,
+                                       &network_config_ttl,
+                                       sizeof (network_config_ttl)) != 0)
+               {
+                       char errbuf[1024];
+                       ERROR ("setsockopt: %s",
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       return (-1);
+               }
+       }
+       else if (ai->ai_family == AF_INET6)
+       {
+               /* Useful example: http://gsyc.escet.urjc.es/~eva/IPv6-web/examples/mcast.html */
+               struct sockaddr_in6 *addr = (struct sockaddr_in6 *) ai->ai_addr;
+               int optname;
+
+               if (IN6_IS_ADDR_MULTICAST (&addr->sin6_addr))
+                       optname = IPV6_MULTICAST_HOPS;
+               else
+                       optname = IPV6_UNICAST_HOPS;
+
+               if (setsockopt (se->data.client.fd, IPPROTO_IPV6, optname,
+                                       &network_config_ttl,
+                                       sizeof (network_config_ttl)) != 0)
+               {
+                       char errbuf[1024];
+                       ERROR ("setsockopt: %s",
+                                       sstrerror (errno, errbuf,
+                                               sizeof (errbuf)));
+                       return (-1);
+               }
+       }
+
+       return (0);
+} /* int network_set_ttl */
+
+static int network_set_interface (const sockent_t *se, const struct addrinfo *ai) /* {{{ */
+{
+       DEBUG ("network plugin: network_set_interface: interface index = %i;",
+                       se->interface);
+
+        assert (se->type == SOCKENT_TYPE_CLIENT);
+
+       if (ai->ai_family == AF_INET)
+       {
+               struct sockaddr_in *addr = (struct sockaddr_in *) ai->ai_addr;
+
+               if (IN_MULTICAST (ntohl (addr->sin_addr.s_addr)))
+               {
+#if HAVE_STRUCT_IP_MREQN_IMR_IFINDEX
+                       /* If possible, use the "ip_mreqn" structure which has
+                        * an "interface index" member. Using the interface
+                        * index is preferred here, because of its similarity
+                        * to the way IPv6 handles this. Unfortunately, it
+                        * appears not to be portable. */
+                       struct ip_mreqn mreq;
+
+                       memset (&mreq, 0, sizeof (mreq));
+                       mreq.imr_multiaddr.s_addr = addr->sin_addr.s_addr;
+                       mreq.imr_address.s_addr = ntohl (INADDR_ANY);
+                       mreq.imr_ifindex = se->interface;
+#else
+                       struct ip_mreq mreq;
+
+                       memset (&mreq, 0, sizeof (mreq));
+                       mreq.imr_multiaddr.s_addr = addr->sin_addr.s_addr;
+                       mreq.imr_interface.s_addr = ntohl (INADDR_ANY);
+#endif
+
+                       if (setsockopt (se->data.client.fd, IPPROTO_IP, IP_MULTICAST_IF,
+                                               &mreq, sizeof (mreq)) != 0)
+                       {
+                               char errbuf[1024];
+                               ERROR ("setsockopt: %s",
+                                               sstrerror (errno, errbuf, sizeof (errbuf)));
+                               return (-1);
+                       }
+
+                       return (0);
+               }
+       }
+       else if (ai->ai_family == AF_INET6)
+       {
+               struct sockaddr_in6 *addr = (struct sockaddr_in6 *) ai->ai_addr;
+
+               if (IN6_IS_ADDR_MULTICAST (&addr->sin6_addr))
+               {
+                       if (setsockopt (se->data.client.fd, IPPROTO_IPV6, IPV6_MULTICAST_IF,
+                                               &se->interface,
+                                               sizeof (se->interface)) != 0)
+                       {
+                               char errbuf[1024];
+                               ERROR ("setsockopt: %s",
+                                               sstrerror (errno, errbuf,
+                                                       sizeof (errbuf)));
+                               return (-1);
+                       }
+
+                       return (0);
+               }
+       }
+
+       /* else: Not a multicast interface. */
+#if defined(HAVE_IF_INDEXTONAME) && HAVE_IF_INDEXTONAME && defined(SO_BINDTODEVICE)
+       if (se->interface != 0)
+       {
+               char interface_name[IFNAMSIZ];
+
+               if (if_indextoname (se->interface, interface_name) == NULL)
+                       return (-1);
+
+               DEBUG ("network plugin: Binding socket to interface %s", interface_name);
+
+               if (setsockopt (se->data.client.fd, SOL_SOCKET, SO_BINDTODEVICE,
+                                       interface_name,
+                                       sizeof(interface_name)) == -1 )
+               {
+                       char errbuf[1024];
+                       ERROR ("setsockopt: %s",
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       return (-1);
+               }
+       }
+/* #endif HAVE_IF_INDEXTONAME && SO_BINDTODEVICE */
+
+#else
+       WARNING ("network plugin: Cannot set the interface on a unicast "
+                       "socket because "
+# if !defined(SO_BINDTODEVICE)
+                       "the the \"SO_BINDTODEVICE\" socket option "
+# else
+                       "the \"if_indextoname\" function "
+# endif
+                       "is not available on your system.");
+#endif
+
+       return (0);
+} /* }}} network_set_interface */
+
+static int network_bind_socket (int fd, const struct addrinfo *ai, const int interface_idx)
+{
+       int loop = 0;
+       int yes  = 1;
+
+       /* allow multiple sockets to use the same PORT number */
+       if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR,
+                               &yes, sizeof(yes)) == -1) {
+                char errbuf[1024];
+                ERROR ("setsockopt: %s", 
+                                sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       DEBUG ("fd = %i; calling `bind'", fd);
+
+       if (bind (fd, ai->ai_addr, ai->ai_addrlen) == -1)
+       {
+               char errbuf[1024];
+               ERROR ("bind: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       if (ai->ai_family == AF_INET)
+       {
+               struct sockaddr_in *addr = (struct sockaddr_in *) ai->ai_addr;
+               if (IN_MULTICAST (ntohl (addr->sin_addr.s_addr)))
+               {
+#if HAVE_STRUCT_IP_MREQN_IMR_IFINDEX
+                       struct ip_mreqn mreq;
+#else
+                       struct ip_mreq mreq;
+#endif
+
+                       DEBUG ("fd = %i; IPv4 multicast address found", fd);
+
+                       mreq.imr_multiaddr.s_addr = addr->sin_addr.s_addr;
+#if HAVE_STRUCT_IP_MREQN_IMR_IFINDEX
+                       /* Set the interface using the interface index if
+                        * possible (available). Unfortunately, the struct
+                        * ip_mreqn is not portable. */
+                       mreq.imr_address.s_addr = ntohl (INADDR_ANY);
+                       mreq.imr_ifindex = interface_idx;
+#else
+                       mreq.imr_interface.s_addr = ntohl (INADDR_ANY);
+#endif
+
+                       if (setsockopt (fd, IPPROTO_IP, IP_MULTICAST_LOOP,
+                                               &loop, sizeof (loop)) == -1)
+                       {
+                               char errbuf[1024];
+                               ERROR ("setsockopt: %s",
+                                               sstrerror (errno, errbuf,
+                                                       sizeof (errbuf)));
+                               return (-1);
+                       }
+
+                       if (setsockopt (fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+                                               &mreq, sizeof (mreq)) == -1)
+                       {
+                               char errbuf[1024];
+                               ERROR ("setsockopt: %s",
+                                               sstrerror (errno, errbuf,
+                                                       sizeof (errbuf)));
+                               return (-1);
+                       }
+
+                       return (0);
+               }
+       }
+       else if (ai->ai_family == AF_INET6)
+       {
+               /* Useful example: http://gsyc.escet.urjc.es/~eva/IPv6-web/examples/mcast.html */
+               struct sockaddr_in6 *addr = (struct sockaddr_in6 *) ai->ai_addr;
+               if (IN6_IS_ADDR_MULTICAST (&addr->sin6_addr))
+               {
+                       struct ipv6_mreq mreq;
+
+                       DEBUG ("fd = %i; IPv6 multicast address found", fd);
+
+                       memcpy (&mreq.ipv6mr_multiaddr,
+                                       &addr->sin6_addr,
+                                       sizeof (addr->sin6_addr));
+
+                       /* http://developer.apple.com/documentation/Darwin/Reference/ManPages/man4/ip6.4.html
+                        * ipv6mr_interface may be set to zeroes to
+                        * choose the default multicast interface or to
+                        * the index of a particular multicast-capable
+                        * interface if the host is multihomed.
+                        * Membership is associ-associated with a
+                        * single interface; programs running on
+                        * multihomed hosts may need to join the same
+                        * group on more than one interface.*/
+                       mreq.ipv6mr_interface = interface_idx;
+
+                       if (setsockopt (fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP,
+                                               &loop, sizeof (loop)) == -1)
+                       {
+                               char errbuf[1024];
+                               ERROR ("setsockopt: %s",
+                                               sstrerror (errno, errbuf,
+                                                       sizeof (errbuf)));
+                               return (-1);
+                       }
+
+                       if (setsockopt (fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP,
+                                               &mreq, sizeof (mreq)) == -1)
+                       {
+                               char errbuf[1024];
+                               ERROR ("setsockopt: %s",
+                                               sstrerror (errno, errbuf,
+                                                       sizeof (errbuf)));
+                               return (-1);
+                       }
+
+                       return (0);
+               }
+       }
+
+#if defined(HAVE_IF_INDEXTONAME) && HAVE_IF_INDEXTONAME && defined(SO_BINDTODEVICE)
+       /* if a specific interface was set, bind the socket to it. But to avoid
+        * possible problems with multicast routing, only do that for non-multicast
+        * addresses */
+       if (interface_idx != 0)
+       {
+               char interface_name[IFNAMSIZ];
+
+               if (if_indextoname (interface_idx, interface_name) == NULL)
+                       return (-1);
+
+               DEBUG ("fd = %i; Binding socket to interface %s", fd, interface_name);
+
+               if (setsockopt (fd, SOL_SOCKET, SO_BINDTODEVICE,
+                                       interface_name,
+                                       sizeof(interface_name)) == -1 )
+               {
+                       char errbuf[1024];
+                       ERROR ("setsockopt: %s",
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       return (-1);
+               }
+       }
+#endif /* HAVE_IF_INDEXTONAME && SO_BINDTODEVICE */
+
+       return (0);
+} /* int network_bind_socket */
+
+/* Initialize a sockent structure. `type' must be either `SOCKENT_TYPE_CLIENT'
+ * or `SOCKENT_TYPE_SERVER' */
+static int sockent_init (sockent_t *se, int type) /* {{{ */
+{
+       if (se == NULL)
+               return (-1);
+
+       memset (se, 0, sizeof (*se));
+
+       se->type = SOCKENT_TYPE_CLIENT;
+       se->node = NULL;
+       se->service = NULL;
+       se->interface = 0;
+       se->next = NULL;
+
+       if (type == SOCKENT_TYPE_SERVER)
+       {
+               se->type = SOCKENT_TYPE_SERVER;
+               se->data.server.fd = NULL;
+#if HAVE_LIBGCRYPT
+               se->data.server.security_level = SECURITY_LEVEL_NONE;
+               se->data.server.auth_file = NULL;
+               se->data.server.userdb = NULL;
+               se->data.server.cypher = NULL;
+#endif
+       }
+       else
+       {
+               se->data.client.fd = -1;
+               se->data.client.addr = NULL;
+#if HAVE_LIBGCRYPT
+               se->data.client.security_level = SECURITY_LEVEL_NONE;
+               se->data.client.username = NULL;
+               se->data.client.password = NULL;
+               se->data.client.cypher = NULL;
+#endif
+       }
+
+       return (0);
+} /* }}} int sockent_init */
+
+/* Open the file descriptors for a initialized sockent structure. */
+static int sockent_open (sockent_t *se) /* {{{ */
+{
+       struct addrinfo  ai_hints;
+       struct addrinfo *ai_list, *ai_ptr;
+       int              ai_return;
+
+        const char *node;
+        const char *service;
+
+       if (se == NULL)
+               return (-1);
+
+       /* Set up the security structures. */
+#if HAVE_LIBGCRYPT /* {{{ */
+       if (se->type == SOCKENT_TYPE_CLIENT)
+       {
+               if (se->data.client.security_level > SECURITY_LEVEL_NONE)
+               {
+                       if ((se->data.client.username == NULL)
+                                       || (se->data.client.password == NULL))
+                       {
+                               ERROR ("network plugin: Client socket with "
+                                               "security requested, but no "
+                                               "credentials are configured.");
+                               return (-1);
+                       }
+                       gcry_md_hash_buffer (GCRY_MD_SHA256,
+                                       se->data.client.password_hash,
+                                       se->data.client.password,
+                                       strlen (se->data.client.password));
+               }
+       }
+       else /* (se->type == SOCKENT_TYPE_SERVER) */
+       {
+               if (se->data.server.security_level > SECURITY_LEVEL_NONE)
+               {
+                       if (se->data.server.auth_file == NULL)
+                       {
+                               ERROR ("network plugin: Server socket with "
+                                               "security requested, but no "
+                                               "password file is configured.");
+                               return (-1);
+                       }
+               }
+               if (se->data.server.auth_file != NULL)
+               {
+                       se->data.server.userdb = fbh_create (se->data.server.auth_file);
+                       if (se->data.server.userdb == NULL)
+                       {
+                               ERROR ("network plugin: Reading password file "
+                                               "`%s' failed.",
+                                               se->data.server.auth_file);
+                               if (se->data.server.security_level > SECURITY_LEVEL_NONE)
+                                       return (-1);
+                       }
+               }
+       }
+#endif /* }}} HAVE_LIBGCRYPT */
+
+        node = se->node;
+        service = se->service;
+
+        if (service == NULL)
+          service = NET_DEFAULT_PORT;
+
+        DEBUG ("network plugin: sockent_open: node = %s; service = %s;",
+            node, service);
+
+       memset (&ai_hints, 0, sizeof (ai_hints));
+       ai_hints.ai_flags  = 0;
+#ifdef AI_PASSIVE
+       ai_hints.ai_flags |= AI_PASSIVE;
+#endif
+#ifdef AI_ADDRCONFIG
+       ai_hints.ai_flags |= AI_ADDRCONFIG;
+#endif
+       ai_hints.ai_family   = AF_UNSPEC;
+       ai_hints.ai_socktype = SOCK_DGRAM;
+       ai_hints.ai_protocol = IPPROTO_UDP;
+
+       ai_return = getaddrinfo (node, service, &ai_hints, &ai_list);
+       if (ai_return != 0)
+       {
+               ERROR ("network plugin: getaddrinfo (%s, %s) failed: %s",
+                               (se->node == NULL) ? "(null)" : se->node,
+                               (se->service == NULL) ? "(null)" : se->service,
+                               gai_strerror (ai_return));
+               return (-1);
+       }
+
+       for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
+       {
+               int status;
+
+               if (se->type == SOCKENT_TYPE_SERVER) /* {{{ */
+               {
+                       int *tmp;
+
+                       tmp = realloc (se->data.server.fd,
+                                       sizeof (*tmp) * (se->data.server.fd_num + 1));
+                       if (tmp == NULL)
+                       {
+                               ERROR ("network plugin: realloc failed.");
+                               continue;
+                       }
+                       se->data.server.fd = tmp;
+                       tmp = se->data.server.fd + se->data.server.fd_num;
+
+                       *tmp = socket (ai_ptr->ai_family, ai_ptr->ai_socktype,
+                                       ai_ptr->ai_protocol);
+                       if (*tmp < 0)
+                       {
+                               char errbuf[1024];
+                               ERROR ("network plugin: socket(2) failed: %s",
+                                               sstrerror (errno, errbuf,
+                                                       sizeof (errbuf)));
+                               continue;
+                       }
+
+                       status = network_bind_socket (*tmp, ai_ptr, se->interface);
+                       if (status != 0)
+                       {
+                               close (*tmp);
+                               *tmp = -1;
+                               continue;
+                       }
+
+                       se->data.server.fd_num++;
+                       continue;
+               } /* }}} if (se->type == SOCKENT_TYPE_SERVER) */
+               else /* if (se->type == SOCKENT_TYPE_CLIENT) {{{ */
+               {
+                       se->data.client.fd = socket (ai_ptr->ai_family,
+                                       ai_ptr->ai_socktype,
+                                       ai_ptr->ai_protocol);
+                       if (se->data.client.fd < 0)
+                       {
+                               char errbuf[1024];
+                               ERROR ("network plugin: socket(2) failed: %s",
+                                               sstrerror (errno, errbuf,
+                                                       sizeof (errbuf)));
+                               continue;
+                       }
+
+                       se->data.client.addr = malloc (sizeof (*se->data.client.addr));
+                       if (se->data.client.addr == NULL)
+                       {
+                               ERROR ("network plugin: malloc failed.");
+                               close (se->data.client.fd);
+                               se->data.client.fd = -1;
+                               continue;
+                       }
+
+                       memset (se->data.client.addr, 0, sizeof (*se->data.client.addr));
+                       assert (sizeof (*se->data.client.addr) >= ai_ptr->ai_addrlen);
+                       memcpy (se->data.client.addr, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
+                       se->data.client.addrlen = ai_ptr->ai_addrlen;
+
+                       network_set_ttl (se, ai_ptr);
+                       network_set_interface (se, ai_ptr);
+
+                       /* We don't open more than one write-socket per
+                        * node/service pair.. */
+                       break;
+               } /* }}} if (se->type == SOCKENT_TYPE_CLIENT) */
+       } /* for (ai_list) */
+
+       freeaddrinfo (ai_list);
+
+       /* Check if all went well. */
+       if (se->type == SOCKENT_TYPE_SERVER)
+       {
+               if (se->data.server.fd_num <= 0)
+                       return (-1);
+       }
+       else /* if (se->type == SOCKENT_TYPE_CLIENT) */
+       {
+               if (se->data.client.fd < 0)
+                       return (-1);
+       }
+
+       return (0);
+} /* }}} int sockent_open */
+
+/* Add a sockent to the global list of sockets */
+static int sockent_add (sockent_t *se) /* {{{ */
+{
+       sockent_t *last_ptr;
+
+       if (se == NULL)
+               return (-1);
+
+       if (se->type == SOCKENT_TYPE_SERVER)
+       {
+               struct pollfd *tmp;
+               size_t i;
+
+               tmp = realloc (listen_sockets_pollfd,
+                               sizeof (*tmp) * (listen_sockets_num
+                                       + se->data.server.fd_num));
+               if (tmp == NULL)
+               {
+                       ERROR ("network plugin: realloc failed.");
+                       return (-1);
+               }
+               listen_sockets_pollfd = tmp;
+               tmp = listen_sockets_pollfd + listen_sockets_num;
+
+               for (i = 0; i < se->data.server.fd_num; i++)
+               {
+                       memset (tmp + i, 0, sizeof (*tmp));
+                       tmp[i].fd = se->data.server.fd[i];
+                       tmp[i].events = POLLIN | POLLPRI;
+                       tmp[i].revents = 0;
+               }
+
+               listen_sockets_num += se->data.server.fd_num;
+
+               if (listen_sockets == NULL)
+               {
+                       listen_sockets = se;
+                       return (0);
+               }
+               last_ptr = listen_sockets;
+       }
+       else /* if (se->type == SOCKENT_TYPE_CLIENT) */
+       {
+               if (sending_sockets == NULL)
+               {
+                       sending_sockets = se;
+                       return (0);
+               }
+               last_ptr = sending_sockets;
+       }
+
+       while (last_ptr->next != NULL)
+               last_ptr = last_ptr->next;
+       last_ptr->next = se;
+
+       return (0);
+} /* }}} int sockent_add */
+
+static void *dispatch_thread (void __attribute__((unused)) *arg) /* {{{ */
+{
+  while (42)
+  {
+    receive_list_entry_t *ent;
+    sockent_t *se;
+
+    /* Lock and wait for more data to come in */
+    pthread_mutex_lock (&receive_list_lock);
+    while ((listen_loop == 0)
+        && (receive_list_head == NULL))
+      pthread_cond_wait (&receive_list_cond, &receive_list_lock);
+
+    /* Remove the head entry and unlock */
+    ent = receive_list_head;
+    if (ent != NULL)
+      receive_list_head = ent->next;
+    receive_list_length--;
+    pthread_mutex_unlock (&receive_list_lock);
+
+    /* Check whether we are supposed to exit. We do NOT check `listen_loop'
+     * because we dispatch all missing packets before shutting down. */
+    if (ent == NULL)
+      break;
+
+    /* Look for the correct `sockent_t' */
+    se = listen_sockets;
+    while (se != NULL)
+    {
+      size_t i;
+
+      for (i = 0; i < se->data.server.fd_num; i++)
+        if (se->data.server.fd[i] == ent->fd)
+          break;
+
+      if (i < se->data.server.fd_num)
+        break;
+
+      se = se->next;
+    }
+
+    if (se == NULL)
+    {
+      ERROR ("network plugin: Got packet from FD %i, but can't "
+          "find an appropriate socket entry.",
+          ent->fd);
+      sfree (ent->data);
+      sfree (ent);
+      continue;
+    }
+
+    parse_packet (se, ent->data, ent->data_len, /* flags = */ 0,
+       /* username = */ NULL);
+    sfree (ent->data);
+    sfree (ent);
+  } /* while (42) */
+
+  return (NULL);
+} /* }}} void *dispatch_thread */
+
+static int network_receive (void) /* {{{ */
+{
+       char buffer[network_config_packet_size];
+       int  buffer_len;
+
+       int i;
+       int status;
+
+       receive_list_entry_t *private_list_head;
+       receive_list_entry_t *private_list_tail;
+       uint64_t              private_list_length;
+
+        assert (listen_sockets_num > 0);
+
+       private_list_head = NULL;
+       private_list_tail = NULL;
+       private_list_length = 0;
+
+       while (listen_loop == 0)
+       {
+               status = poll (listen_sockets_pollfd, listen_sockets_num, -1);
+
+               if (status <= 0)
+               {
+                       char errbuf[1024];
+                       if (errno == EINTR)
+                               continue;
+                       ERROR ("poll failed: %s",
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       return (-1);
+               }
+
+               for (i = 0; (i < listen_sockets_num) && (status > 0); i++)
+               {
+                       receive_list_entry_t *ent;
+
+                       if ((listen_sockets_pollfd[i].revents
+                                               & (POLLIN | POLLPRI)) == 0)
+                               continue;
+                       status--;
+
+                       buffer_len = recv (listen_sockets_pollfd[i].fd,
+                                       buffer, sizeof (buffer),
+                                       0 /* no flags */);
+                       if (buffer_len < 0)
+                       {
+                               char errbuf[1024];
+                               ERROR ("recv failed: %s",
+                                               sstrerror (errno, errbuf,
+                                                       sizeof (errbuf)));
+                               return (-1);
+                       }
+
+                       stats_octets_rx += ((uint64_t) buffer_len);
+                       stats_packets_rx++;
+
+                       /* TODO: Possible performance enhancement: Do not free
+                        * these entries in the dispatch thread but put them in
+                        * another list, so we don't have to allocate more and
+                        * more of these structures. */
+                       ent = malloc (sizeof (receive_list_entry_t));
+                       if (ent == NULL)
+                       {
+                               ERROR ("network plugin: malloc failed.");
+                               return (-1);
+                       }
+                       memset (ent, 0, sizeof (receive_list_entry_t));
+                       ent->data = malloc (network_config_packet_size);
+                       if (ent->data == NULL)
+                       {
+                               sfree (ent);
+                               ERROR ("network plugin: malloc failed.");
+                               return (-1);
+                       }
+                       ent->fd = listen_sockets_pollfd[i].fd;
+                       ent->next = NULL;
+
+                       memcpy (ent->data, buffer, buffer_len);
+                       ent->data_len = buffer_len;
+
+                       if (private_list_head == NULL)
+                               private_list_head = ent;
+                       else
+                               private_list_tail->next = ent;
+                       private_list_tail = ent;
+                       private_list_length++;
+
+                       /* Do not block here. Blocking here has led to
+                        * insufficient performance in the past. */
+                       if (pthread_mutex_trylock (&receive_list_lock) == 0)
+                       {
+                               assert (((receive_list_head == NULL) && (receive_list_length == 0))
+                                               || ((receive_list_head != NULL) && (receive_list_length != 0)));
+
+                               if (receive_list_head == NULL)
+                                       receive_list_head = private_list_head;
+                               else
+                                       receive_list_tail->next = private_list_head;
+                               receive_list_tail = private_list_tail;
+                               receive_list_length += private_list_length;
+
+                               pthread_cond_signal (&receive_list_cond);
+                               pthread_mutex_unlock (&receive_list_lock);
+
+                               private_list_head = NULL;
+                               private_list_tail = NULL;
+                               private_list_length = 0;
+                       }
+               } /* for (listen_sockets_pollfd) */
+       } /* while (listen_loop == 0) */
+
+       /* Make sure everything is dispatched before exiting. */
+       if (private_list_head != NULL)
+       {
+               pthread_mutex_lock (&receive_list_lock);
+
+               if (receive_list_head == NULL)
+                       receive_list_head = private_list_head;
+               else
+                       receive_list_tail->next = private_list_head;
+               receive_list_tail = private_list_tail;
+               receive_list_length += private_list_length;
+
+               private_list_head = NULL;
+               private_list_tail = NULL;
+               private_list_length = 0;
+
+               pthread_cond_signal (&receive_list_cond);
+               pthread_mutex_unlock (&receive_list_lock);
+       }
+
+       return (0);
+} /* }}} int network_receive */
+
+static void *receive_thread (void __attribute__((unused)) *arg)
+{
+       return (network_receive () ? (void *) 1 : (void *) 0);
+} /* void *receive_thread */
+
+static void network_init_buffer (void)
+{
+       memset (send_buffer, 0, network_config_packet_size);
+       send_buffer_ptr = send_buffer;
+       send_buffer_fill = 0;
+
+       memset (&send_buffer_vl, 0, sizeof (send_buffer_vl));
+} /* int network_init_buffer */
+
+static void networt_send_buffer_plain (const sockent_t *se, /* {{{ */
+               const char *buffer, size_t buffer_size)
+{
+       int status;
+
+       while (42)
+       {
+               status = sendto (se->data.client.fd, buffer, buffer_size,
+                    /* flags = */ 0,
+                    (struct sockaddr *) se->data.client.addr,
+                    se->data.client.addrlen);
+                if (status < 0)
+               {
+                       char errbuf[1024];
+                       if (errno == EINTR)
+                               continue;
+                       ERROR ("network plugin: sendto failed: %s",
+                                       sstrerror (errno, errbuf,
+                                               sizeof (errbuf)));
+                       break;
+               }
+
+               break;
+       } /* while (42) */
+} /* }}} void networt_send_buffer_plain */
+
+#if HAVE_LIBGCRYPT
+#define BUFFER_ADD(p,s) do { \
+  memcpy (buffer + buffer_offset, (p), (s)); \
+  buffer_offset += (s); \
+} while (0)
+
+static void networt_send_buffer_signed (const sockent_t *se, /* {{{ */
+               const char *in_buffer, size_t in_buffer_size)
+{
+  part_signature_sha256_t ps;
+  char buffer[BUFF_SIG_SIZE + in_buffer_size];
+  size_t buffer_offset;
+  size_t username_len;
+
+  gcry_md_hd_t hd;
+  gcry_error_t err;
+  unsigned char *hash;
+
+  hd = NULL;
+  err = gcry_md_open (&hd, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC);
+  if (err != 0)
+  {
+    ERROR ("network plugin: Creating HMAC object failed: %s",
+        gcry_strerror (err));
+    return;
+  }
+
+  err = gcry_md_setkey (hd, se->data.client.password,
+      strlen (se->data.client.password));
+  if (err != 0)
+  {
+    ERROR ("network plugin: gcry_md_setkey failed: %s",
+        gcry_strerror (err));
+    gcry_md_close (hd);
+    return;
+  }
+
+  username_len = strlen (se->data.client.username);
+  if (username_len > (BUFF_SIG_SIZE - PART_SIGNATURE_SHA256_SIZE))
+  {
+    ERROR ("network plugin: Username too long: %s",
+        se->data.client.username);
+    return;
+  }
+
+  memcpy (buffer + PART_SIGNATURE_SHA256_SIZE,
+      se->data.client.username, username_len);
+  memcpy (buffer + PART_SIGNATURE_SHA256_SIZE + username_len,
+      in_buffer, in_buffer_size);
+
+  /* Initialize the `ps' structure. */
+  memset (&ps, 0, sizeof (ps));
+  ps.head.type = htons (TYPE_SIGN_SHA256);
+  ps.head.length = htons (PART_SIGNATURE_SHA256_SIZE + username_len);
+
+  /* Calculate the hash value. */
+  gcry_md_write (hd, buffer + PART_SIGNATURE_SHA256_SIZE,
+      username_len + in_buffer_size);
+  hash = gcry_md_read (hd, GCRY_MD_SHA256);
+  if (hash == NULL)
+  {
+    ERROR ("network plugin: gcry_md_read failed.");
+    gcry_md_close (hd);
+    return;
+  }
+  memcpy (ps.hash, hash, sizeof (ps.hash));
+
+  /* Add the header */
+  buffer_offset = 0;
+
+  BUFFER_ADD (&ps.head.type, sizeof (ps.head.type));
+  BUFFER_ADD (&ps.head.length, sizeof (ps.head.length));
+  BUFFER_ADD (ps.hash, sizeof (ps.hash));
+
+  assert (buffer_offset == PART_SIGNATURE_SHA256_SIZE);
+
+  gcry_md_close (hd);
+  hd = NULL;
+
+  buffer_offset = PART_SIGNATURE_SHA256_SIZE + username_len + in_buffer_size;
+  networt_send_buffer_plain (se, buffer, buffer_offset);
+} /* }}} void networt_send_buffer_signed */
+
+static void networt_send_buffer_encrypted (sockent_t *se, /* {{{ */
+               const char *in_buffer, size_t in_buffer_size)
+{
+  part_encryption_aes256_t pea;
+  char buffer[BUFF_SIG_SIZE + in_buffer_size];
+  size_t buffer_size;
+  size_t buffer_offset;
+  size_t header_size;
+  size_t username_len;
+  gcry_error_t err;
+  gcry_cipher_hd_t cypher;
+
+  /* Initialize the header fields */
+  memset (&pea, 0, sizeof (pea));
+  pea.head.type = htons (TYPE_ENCR_AES256);
+
+  pea.username = se->data.client.username;
+
+  username_len = strlen (pea.username);
+  if ((PART_ENCRYPTION_AES256_SIZE + username_len) > BUFF_SIG_SIZE)
+  {
+    ERROR ("network plugin: Username too long: %s", pea.username);
+    return;
+  }
+
+  buffer_size = PART_ENCRYPTION_AES256_SIZE + username_len + in_buffer_size;
+  header_size = PART_ENCRYPTION_AES256_SIZE + username_len
+    - sizeof (pea.hash);
+
+  assert (buffer_size <= sizeof (buffer));
+  DEBUG ("network plugin: networt_send_buffer_encrypted: "
+      "buffer_size = %zu;", buffer_size);
+
+  pea.head.length = htons ((uint16_t) (PART_ENCRYPTION_AES256_SIZE
+        + username_len + in_buffer_size));
+  pea.username_length = htons ((uint16_t) username_len);
+
+  /* Chose a random initialization vector. */
+  gcry_randomize ((void *) &pea.iv, sizeof (pea.iv), GCRY_STRONG_RANDOM);
+
+  /* Create hash of the payload */
+  gcry_md_hash_buffer (GCRY_MD_SHA1, pea.hash, in_buffer, in_buffer_size);
+
+  /* Initialize the buffer */
+  buffer_offset = 0;
+  memset (buffer, 0, sizeof (buffer));
+
+
+  BUFFER_ADD (&pea.head.type, sizeof (pea.head.type));
+  BUFFER_ADD (&pea.head.length, sizeof (pea.head.length));
+  BUFFER_ADD (&pea.username_length, sizeof (pea.username_length));
+  BUFFER_ADD (pea.username, username_len);
+  BUFFER_ADD (pea.iv, sizeof (pea.iv));
+  assert (buffer_offset == header_size);
+  BUFFER_ADD (pea.hash, sizeof (pea.hash));
+  BUFFER_ADD (in_buffer, in_buffer_size);
+
+  assert (buffer_offset == buffer_size);
+
+  cypher = network_get_aes256_cypher (se, pea.iv, sizeof (pea.iv),
+      se->data.client.password);
+  if (cypher == NULL)
+    return;
+
+  /* Encrypt the buffer in-place */
+  err = gcry_cipher_encrypt (cypher,
+      buffer      + header_size,
+      buffer_size - header_size,
+      /* in = */ NULL, /* in len = */ 0);
+  if (err != 0)
+  {
+    ERROR ("network plugin: gcry_cipher_encrypt returned: %s",
+        gcry_strerror (err));
+    return;
+  }
+
+  /* Send it out without further modifications */
+  networt_send_buffer_plain (se, buffer, buffer_size);
+} /* }}} void networt_send_buffer_encrypted */
+#undef BUFFER_ADD
+#endif /* HAVE_LIBGCRYPT */
+
+static void network_send_buffer (char *buffer, size_t buffer_len) /* {{{ */
+{
+  sockent_t *se;
+
+  DEBUG ("network plugin: network_send_buffer: buffer_len = %zu", buffer_len);
+
+  for (se = sending_sockets; se != NULL; se = se->next)
+  {
+#if HAVE_LIBGCRYPT
+    if (se->data.client.security_level == SECURITY_LEVEL_ENCRYPT)
+      networt_send_buffer_encrypted (se, buffer, buffer_len);
+    else if (se->data.client.security_level == SECURITY_LEVEL_SIGN)
+      networt_send_buffer_signed (se, buffer, buffer_len);
+    else /* if (se->data.client.security_level == SECURITY_LEVEL_NONE) */
+#endif /* HAVE_LIBGCRYPT */
+      networt_send_buffer_plain (se, buffer, buffer_len);
+  } /* for (sending_sockets) */
+} /* }}} void network_send_buffer */
+
+static int add_to_buffer (char *buffer, int buffer_size, /* {{{ */
+               value_list_t *vl_def,
+               const data_set_t *ds, const value_list_t *vl)
+{
+       char *buffer_orig = buffer;
+
+       if (strcmp (vl_def->host, vl->host) != 0)
+       {
+               if (write_part_string (&buffer, &buffer_size, TYPE_HOST,
+                                       vl->host, strlen (vl->host)) != 0)
+                       return (-1);
+               sstrncpy (vl_def->host, vl->host, sizeof (vl_def->host));
+       }
+
+       if (vl_def->time != vl->time)
+       {
+               if (write_part_number (&buffer, &buffer_size, TYPE_TIME_HR,
+                                       (uint64_t) vl->time))
+                       return (-1);
+               vl_def->time = vl->time;
+       }
+
+       if (vl_def->interval != vl->interval)
+       {
+               if (write_part_number (&buffer, &buffer_size, TYPE_INTERVAL_HR,
+                                       (uint64_t) vl->interval))
+                       return (-1);
+               vl_def->interval = vl->interval;
+       }
+
+       if (strcmp (vl_def->plugin, vl->plugin) != 0)
+       {
+               if (write_part_string (&buffer, &buffer_size, TYPE_PLUGIN,
+                                       vl->plugin, strlen (vl->plugin)) != 0)
+                       return (-1);
+               sstrncpy (vl_def->plugin, vl->plugin, sizeof (vl_def->plugin));
+       }
+
+       if (strcmp (vl_def->plugin_instance, vl->plugin_instance) != 0)
+       {
+               if (write_part_string (&buffer, &buffer_size, TYPE_PLUGIN_INSTANCE,
+                                       vl->plugin_instance,
+                                       strlen (vl->plugin_instance)) != 0)
+                       return (-1);
+               sstrncpy (vl_def->plugin_instance, vl->plugin_instance, sizeof (vl_def->plugin_instance));
+       }
+
+       if (strcmp (vl_def->type, vl->type) != 0)
+       {
+               if (write_part_string (&buffer, &buffer_size, TYPE_TYPE,
+                                       vl->type, strlen (vl->type)) != 0)
+                       return (-1);
+               sstrncpy (vl_def->type, ds->type, sizeof (vl_def->type));
+       }
+
+       if (strcmp (vl_def->type_instance, vl->type_instance) != 0)
+       {
+               if (write_part_string (&buffer, &buffer_size, TYPE_TYPE_INSTANCE,
+                                       vl->type_instance,
+                                       strlen (vl->type_instance)) != 0)
+                       return (-1);
+               sstrncpy (vl_def->type_instance, vl->type_instance, sizeof (vl_def->type_instance));
+       }
+       
+       if (write_part_values (&buffer, &buffer_size, ds, vl) != 0)
+               return (-1);
+
+       return (buffer - buffer_orig);
+} /* }}} int add_to_buffer */
+
+static void flush_buffer (void)
+{
+       DEBUG ("network plugin: flush_buffer: send_buffer_fill = %i",
+                       send_buffer_fill);
+
+       network_send_buffer (send_buffer, (size_t) send_buffer_fill);
+
+       stats_octets_tx += ((uint64_t) send_buffer_fill);
+       stats_packets_tx++;
+
+       network_init_buffer ();
+}
+
+static int network_write (const data_set_t *ds, const value_list_t *vl,
+               user_data_t __attribute__((unused)) *user_data)
+{
+       int status;
+
+       if (!check_send_okay (vl))
+       {
+#if COLLECT_DEBUG
+         char name[6*DATA_MAX_NAME_LEN];
+         FORMAT_VL (name, sizeof (name), vl);
+         name[sizeof (name) - 1] = 0;
+         DEBUG ("network plugin: network_write: "
+             "NOT sending %s.", name);
+#endif
+         /* Counter is not protected by another lock and may be reached by
+          * multiple threads */
+         pthread_mutex_lock (&stats_lock);
+         stats_values_not_sent++;
+         pthread_mutex_unlock (&stats_lock);
+         return (0);
+       }
+
+       uc_meta_data_add_unsigned_int (vl,
+           "network:time_sent", (uint64_t) vl->time);
+
+       pthread_mutex_lock (&send_buffer_lock);
+
+       status = add_to_buffer (send_buffer_ptr,
+                       network_config_packet_size - (send_buffer_fill + BUFF_SIG_SIZE),
+                       &send_buffer_vl,
+                       ds, vl);
+       if (status >= 0)
+       {
+               /* status == bytes added to the buffer */
+               send_buffer_fill += status;
+               send_buffer_ptr  += status;
+
+               stats_values_sent++;
+       }
+       else
+       {
+               flush_buffer ();
+
+               status = add_to_buffer (send_buffer_ptr,
+                               network_config_packet_size - (send_buffer_fill + BUFF_SIG_SIZE),
+                               &send_buffer_vl,
+                               ds, vl);
+
+               if (status >= 0)
+               {
+                       send_buffer_fill += status;
+                       send_buffer_ptr  += status;
+
+                       stats_values_sent++;
+               }
+       }
+
+       if (status < 0)
+       {
+               ERROR ("network plugin: Unable to append to the "
+                               "buffer for some weird reason");
+       }
+       else if ((network_config_packet_size - send_buffer_fill) < 15)
+       {
+               flush_buffer ();
+       }
+
+       pthread_mutex_unlock (&send_buffer_lock);
+
+       return ((status < 0) ? -1 : 0);
+} /* int network_write */
+
+static int network_config_set_boolean (const oconfig_item_t *ci, /* {{{ */
+    int *retval)
+{
+  if ((ci->values_num != 1)
+      || ((ci->values[0].type != OCONFIG_TYPE_BOOLEAN)
+        && (ci->values[0].type != OCONFIG_TYPE_STRING)))
+  {
+    ERROR ("network plugin: The `%s' config option needs "
+        "exactly one boolean argument.", ci->key);
+    return (-1);
+  }
+
+  if (ci->values[0].type == OCONFIG_TYPE_BOOLEAN)
+  {
+    if (ci->values[0].value.boolean)
+      *retval = 1;
+    else
+      *retval = 0;
+  }
+  else
+  {
+    char *str = ci->values[0].value.string;
+
+    if (IS_TRUE (str))
+      *retval = 1;
+    else if (IS_FALSE (str))
+      *retval = 0;
+    else
+    {
+      ERROR ("network plugin: Cannot parse string value `%s' of the `%s' "
+          "option as boolean value.",
+          str, ci->key);
+      return (-1);
+    }
+  }
+
+  return (0);
+} /* }}} int network_config_set_boolean */
+
+static int network_config_set_ttl (const oconfig_item_t *ci) /* {{{ */
+{
+  int tmp;
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
+  {
+    WARNING ("network plugin: The `TimeToLive' config option needs exactly "
+        "one numeric argument.");
+    return (-1);
+  }
+
+  tmp = (int) ci->values[0].value.number;
+  if ((tmp > 0) && (tmp <= 255))
+    network_config_ttl = tmp;
+
+  return (0);
+} /* }}} int network_config_set_ttl */
+
+static int network_config_set_interface (const oconfig_item_t *ci, /* {{{ */
+    int *interface)
+{
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("network plugin: The `Interface' config option needs exactly "
+        "one string argument.");
+    return (-1);
+  }
+
+  if (interface == NULL)
+    return (-1);
+
+  *interface = if_nametoindex (ci->values[0].value.string);
+
+  return (0);
+} /* }}} int network_config_set_interface */
+
+static int network_config_set_buffer_size (const oconfig_item_t *ci) /* {{{ */
+{
+  int tmp;
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
+  {
+    WARNING ("network plugin: The `MaxPacketSize' config option needs exactly "
+        "one numeric argument.");
+    return (-1);
+  }
+
+  tmp = (int) ci->values[0].value.number;
+  if ((tmp >= 1024) && (tmp <= 65535))
+    network_config_packet_size = tmp;
+
+  return (0);
+} /* }}} int network_config_set_buffer_size */
+
+#if HAVE_LIBGCRYPT
+static int network_config_set_string (const oconfig_item_t *ci, /* {{{ */
+    char **ret_string)
+{
+  char *tmp;
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("network plugin: The `%s' config option needs exactly "
+        "one string argument.", ci->key);
+    return (-1);
+  }
+
+  tmp = strdup (ci->values[0].value.string);
+  if (tmp == NULL)
+    return (-1);
+
+  sfree (*ret_string);
+  *ret_string = tmp;
+
+  return (0);
+} /* }}} int network_config_set_string */
+#endif /* HAVE_LIBGCRYPT */
+
+#if HAVE_LIBGCRYPT
+static int network_config_set_security_level (oconfig_item_t *ci, /* {{{ */
+    int *retval)
+{
+  char *str;
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("network plugin: The `SecurityLevel' config option needs exactly "
+        "one string argument.");
+    return (-1);
+  }
+
+  str = ci->values[0].value.string;
+  if (strcasecmp ("Encrypt", str) == 0)
+    *retval = SECURITY_LEVEL_ENCRYPT;
+  else if (strcasecmp ("Sign", str) == 0)
+    *retval = SECURITY_LEVEL_SIGN;
+  else if (strcasecmp ("None", str) == 0)
+    *retval = SECURITY_LEVEL_NONE;
+  else
+  {
+    WARNING ("network plugin: Unknown security level: %s.", str);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int network_config_set_security_level */
+#endif /* HAVE_LIBGCRYPT */
+
+static int network_config_add_listen (const oconfig_item_t *ci) /* {{{ */
+{
+  sockent_t *se;
+  int status;
+  int i;
+
+  if ((ci->values_num < 1) || (ci->values_num > 2)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING)
+      || ((ci->values_num > 1) && (ci->values[1].type != OCONFIG_TYPE_STRING)))
+  {
+    ERROR ("network plugin: The `%s' config option needs "
+        "one or two string arguments.", ci->key);
+    return (-1);
+  }
+
+  se = malloc (sizeof (*se));
+  if (se == NULL)
+  {
+    ERROR ("network plugin: malloc failed.");
+    return (-1);
+  }
+  sockent_init (se, SOCKENT_TYPE_SERVER);
+
+  se->node = strdup (ci->values[0].value.string);
+  if (ci->values_num >= 2)
+    se->service = strdup (ci->values[1].value.string);
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+#if HAVE_LIBGCRYPT
+    if (strcasecmp ("AuthFile", child->key) == 0)
+      network_config_set_string (child, &se->data.server.auth_file);
+    else if (strcasecmp ("SecurityLevel", child->key) == 0)
+      network_config_set_security_level (child,
+          &se->data.server.security_level);
+    else
+#endif /* HAVE_LIBGCRYPT */
+    if (strcasecmp ("Interface", child->key) == 0)
+      network_config_set_interface (child,
+          &se->interface);
+    else
+    {
+      WARNING ("network plugin: Option `%s' is not allowed here.",
+          child->key);
+    }
+  }
+
+#if HAVE_LIBGCRYPT
+  if ((se->data.server.security_level > SECURITY_LEVEL_NONE)
+      && (se->data.server.auth_file == NULL))
+  {
+    ERROR ("network plugin: A security level higher than `none' was "
+        "requested, but no AuthFile option was given. Cowardly refusing to "
+        "open this socket!");
+    sockent_destroy (se);
+    return (-1);
+  }
+#endif /* HAVE_LIBGCRYPT */
+
+  status = sockent_open (se);
+  if (status != 0)
+  {
+    ERROR ("network plugin: network_config_add_listen: sockent_open failed.");
+    sockent_destroy (se);
+    return (-1);
+  }
+
+  status = sockent_add (se);
+  if (status != 0)
+  {
+    ERROR ("network plugin: network_config_add_listen: sockent_add failed.");
+    sockent_destroy (se);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int network_config_add_listen */
+
+static int network_config_add_server (const oconfig_item_t *ci) /* {{{ */
+{
+  sockent_t *se;
+  int status;
+  int i;
+
+  if ((ci->values_num < 1) || (ci->values_num > 2)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING)
+      || ((ci->values_num > 1) && (ci->values[1].type != OCONFIG_TYPE_STRING)))
+  {
+    ERROR ("network plugin: The `%s' config option needs "
+        "one or two string arguments.", ci->key);
+    return (-1);
+  }
+
+  se = malloc (sizeof (*se));
+  if (se == NULL)
+  {
+    ERROR ("network plugin: malloc failed.");
+    return (-1);
+  }
+  sockent_init (se, SOCKENT_TYPE_CLIENT);
+
+  se->node = strdup (ci->values[0].value.string);
+  if (ci->values_num >= 2)
+    se->service = strdup (ci->values[1].value.string);
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+#if HAVE_LIBGCRYPT
+    if (strcasecmp ("Username", child->key) == 0)
+      network_config_set_string (child, &se->data.client.username);
+    else if (strcasecmp ("Password", child->key) == 0)
+      network_config_set_string (child, &se->data.client.password);
+    else if (strcasecmp ("SecurityLevel", child->key) == 0)
+      network_config_set_security_level (child,
+          &se->data.client.security_level);
+    else
+#endif /* HAVE_LIBGCRYPT */
+    if (strcasecmp ("Interface", child->key) == 0)
+      network_config_set_interface (child,
+          &se->interface);
+    else
+    {
+      WARNING ("network plugin: Option `%s' is not allowed here.",
+          child->key);
+    }
+  }
+
+#if HAVE_LIBGCRYPT
+  if ((se->data.client.security_level > SECURITY_LEVEL_NONE)
+      && ((se->data.client.username == NULL)
+        || (se->data.client.password == NULL)))
+  {
+    ERROR ("network plugin: A security level higher than `none' was "
+        "requested, but no Username or Password option was given. "
+        "Cowardly refusing to open this socket!");
+    sockent_destroy (se);
+    return (-1);
+  }
+#endif /* HAVE_LIBGCRYPT */
+
+  status = sockent_open (se);
+  if (status != 0)
+  {
+    ERROR ("network plugin: network_config_add_server: sockent_open failed.");
+    sockent_destroy (se);
+    return (-1);
+  }
+
+  status = sockent_add (se);
+  if (status != 0)
+  {
+    ERROR ("network plugin: network_config_add_server: sockent_add failed.");
+    sockent_destroy (se);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int network_config_add_server */
+
+static int network_config (oconfig_item_t *ci) /* {{{ */
+{
+  int i;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Listen", child->key) == 0)
+      network_config_add_listen (child);
+    else if (strcasecmp ("Server", child->key) == 0)
+      network_config_add_server (child);
+    else if (strcasecmp ("TimeToLive", child->key) == 0)
+      network_config_set_ttl (child);
+    else if (strcasecmp ("MaxPacketSize", child->key) == 0)
+      network_config_set_buffer_size (child);
+    else if (strcasecmp ("Forward", child->key) == 0)
+      network_config_set_boolean (child, &network_config_forward);
+    else if (strcasecmp ("ReportStats", child->key) == 0)
+      network_config_set_boolean (child, &network_config_stats);
+    else
+    {
+      WARNING ("network plugin: Option `%s' is not allowed here.",
+          child->key);
+    }
+  }
+
+  return (0);
+} /* }}} int network_config */
+
+static int network_notification (const notification_t *n,
+               user_data_t __attribute__((unused)) *user_data)
+{
+  char  buffer[network_config_packet_size];
+  char *buffer_ptr = buffer;
+  int   buffer_free = sizeof (buffer);
+  int   status;
+
+  memset (buffer, '\0', sizeof (buffer));
+
+  status = write_part_number (&buffer_ptr, &buffer_free, TYPE_TIME_HR,
+      (uint64_t) n->time);
+  if (status != 0)
+    return (-1);
+
+  status = write_part_number (&buffer_ptr, &buffer_free, TYPE_SEVERITY,
+      (uint64_t) n->severity);
+  if (status != 0)
+    return (-1);
+
+  if (strlen (n->host) > 0)
+  {
+    status = write_part_string (&buffer_ptr, &buffer_free, TYPE_HOST,
+       n->host, strlen (n->host));
+    if (status != 0)
+      return (-1);
+  }
+
+  if (strlen (n->plugin) > 0)
+  {
+    status = write_part_string (&buffer_ptr, &buffer_free, TYPE_PLUGIN,
+       n->plugin, strlen (n->plugin));
+    if (status != 0)
+      return (-1);
+  }
+
+  if (strlen (n->plugin_instance) > 0)
+  {
+    status = write_part_string (&buffer_ptr, &buffer_free,
+       TYPE_PLUGIN_INSTANCE,
+       n->plugin_instance, strlen (n->plugin_instance));
+    if (status != 0)
+      return (-1);
+  }
+
+  if (strlen (n->type) > 0)
+  {
+    status = write_part_string (&buffer_ptr, &buffer_free, TYPE_TYPE,
+       n->type, strlen (n->type));
+    if (status != 0)
+      return (-1);
+  }
+
+  if (strlen (n->type_instance) > 0)
+  {
+    status = write_part_string (&buffer_ptr, &buffer_free, TYPE_TYPE_INSTANCE,
+       n->type_instance, strlen (n->type_instance));
+    if (status != 0)
+      return (-1);
+  }
+
+  status = write_part_string (&buffer_ptr, &buffer_free, TYPE_MESSAGE,
+      n->message, strlen (n->message));
+  if (status != 0)
+    return (-1);
+
+  network_send_buffer (buffer, sizeof (buffer) - buffer_free);
+
+  return (0);
+} /* int network_notification */
+
+static int network_shutdown (void)
+{
+       listen_loop++;
+
+       /* Kill the listening thread */
+       if (receive_thread_running != 0)
+       {
+               INFO ("network plugin: Stopping receive thread.");
+               pthread_kill (receive_thread_id, SIGTERM);
+               pthread_join (receive_thread_id, NULL /* no return value */);
+               memset (&receive_thread_id, 0, sizeof (receive_thread_id));
+               receive_thread_running = 0;
+       }
+
+       /* Shutdown the dispatching thread */
+       if (dispatch_thread_running != 0)
+       {
+               INFO ("network plugin: Stopping dispatch thread.");
+               pthread_mutex_lock (&receive_list_lock);
+               pthread_cond_broadcast (&receive_list_cond);
+               pthread_mutex_unlock (&receive_list_lock);
+               pthread_join (dispatch_thread_id, /* ret = */ NULL);
+               dispatch_thread_running = 0;
+       }
+
+       sockent_destroy (listen_sockets);
+
+       if (send_buffer_fill > 0)
+               flush_buffer ();
+
+       sfree (send_buffer);
+
+       /* TODO: Close `sending_sockets' */
+
+       plugin_unregister_config ("network");
+       plugin_unregister_init ("network");
+       plugin_unregister_write ("network");
+       plugin_unregister_shutdown ("network");
+
+       return (0);
+} /* int network_shutdown */
+
+static int network_stats_read (void) /* {{{ */
+{
+       derive_t copy_octets_rx;
+       derive_t copy_octets_tx;
+       derive_t copy_packets_rx;
+       derive_t copy_packets_tx;
+       derive_t copy_values_dispatched;
+       derive_t copy_values_not_dispatched;
+       derive_t copy_values_sent;
+       derive_t copy_values_not_sent;
+       derive_t copy_receive_list_length;
+       value_list_t vl = VALUE_LIST_INIT;
+       value_t values[2];
+
+       copy_octets_rx = stats_octets_rx;
+       copy_octets_tx = stats_octets_tx;
+       copy_packets_rx = stats_packets_rx;
+       copy_packets_tx = stats_packets_tx;
+       copy_values_dispatched = stats_values_dispatched;
+       copy_values_not_dispatched = stats_values_not_dispatched;
+       copy_values_sent = stats_values_sent;
+       copy_values_not_sent = stats_values_not_sent;
+       copy_receive_list_length = receive_list_length;
+
+       /* Initialize `vl' */
+       vl.values = values;
+       vl.values_len = 2;
+       vl.time = 0;
+       vl.interval = interval_g;
+       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+       sstrncpy (vl.plugin, "network", sizeof (vl.plugin));
+
+       /* Octets received / sent */
+       vl.values[0].derive = (derive_t) copy_octets_rx;
+       vl.values[1].derive = (derive_t) copy_octets_tx;
+       sstrncpy (vl.type, "if_octets", sizeof (vl.type));
+       plugin_dispatch_values_secure (&vl);
+
+       /* Packets received / send */
+       vl.values[0].derive = (derive_t) copy_packets_rx;
+       vl.values[1].derive = (derive_t) copy_packets_tx;
+       sstrncpy (vl.type, "if_packets", sizeof (vl.type));
+       plugin_dispatch_values_secure (&vl);
+
+       /* Values (not) dispatched and (not) send */
+       sstrncpy (vl.type, "total_values", sizeof (vl.type));
+       vl.values_len = 1;
+
+       vl.values[0].derive = (derive_t) copy_values_dispatched;
+       sstrncpy (vl.type_instance, "dispatch-accepted",
+                       sizeof (vl.type_instance));
+       plugin_dispatch_values_secure (&vl);
+
+       vl.values[0].derive = (derive_t) copy_values_not_dispatched;
+       sstrncpy (vl.type_instance, "dispatch-rejected",
+                       sizeof (vl.type_instance));
+       plugin_dispatch_values_secure (&vl);
+
+       vl.values[0].derive = (derive_t) copy_values_sent;
+       sstrncpy (vl.type_instance, "send-accepted",
+                       sizeof (vl.type_instance));
+       plugin_dispatch_values_secure (&vl);
+
+       vl.values[0].derive = (derive_t) copy_values_not_sent;
+       sstrncpy (vl.type_instance, "send-rejected",
+                       sizeof (vl.type_instance));
+       plugin_dispatch_values_secure (&vl);
+
+       /* Receive queue length */
+       vl.values[0].gauge = (gauge_t) copy_receive_list_length;
+       sstrncpy (vl.type, "queue_length", sizeof (vl.type));
+       vl.type_instance[0] = 0;
+       plugin_dispatch_values_secure (&vl);
+
+       return (0);
+} /* }}} int network_stats_read */
+
+static int network_init (void)
+{
+       static _Bool have_init = 0;
+
+       /* Check if we were already initialized. If so, just return - there's
+        * nothing more to do (for now, that is). */
+       if (have_init)
+               return (0);
+       have_init = 1;
+
+#if HAVE_LIBGCRYPT
+       gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
+       gcry_control (GCRYCTL_INIT_SECMEM, 32768, 0);
+       gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
+#endif
+
+       if (network_config_stats != 0)
+               plugin_register_read ("network", network_stats_read);
+
+       plugin_register_shutdown ("network", network_shutdown);
+
+       send_buffer = malloc (network_config_packet_size);
+       if (send_buffer == NULL)
+       {
+               ERROR ("network plugin: malloc failed.");
+               return (-1);
+       }
+       network_init_buffer ();
+
+       /* setup socket(s) and so on */
+       if (sending_sockets != NULL)
+       {
+               plugin_register_write ("network", network_write,
+                               /* user_data = */ NULL);
+               plugin_register_notification ("network", network_notification,
+                               /* user_data = */ NULL);
+       }
+
+       /* If no threads need to be started, return here. */
+       if ((listen_sockets_num == 0)
+                       || ((dispatch_thread_running != 0)
+                               && (receive_thread_running != 0)))
+               return (0);
+
+       if (dispatch_thread_running == 0)
+       {
+               int status;
+               status = pthread_create (&dispatch_thread_id,
+                               NULL /* no attributes */,
+                               dispatch_thread,
+                               NULL /* no argument */);
+               if (status != 0)
+               {
+                       char errbuf[1024];
+                       ERROR ("network: pthread_create failed: %s",
+                                       sstrerror (errno, errbuf,
+                                               sizeof (errbuf)));
+               }
+               else
+               {
+                       dispatch_thread_running = 1;
+               }
+       }
+
+       if (receive_thread_running == 0)
+       {
+               int status;
+               status = pthread_create (&receive_thread_id,
+                               NULL /* no attributes */,
+                               receive_thread,
+                               NULL /* no argument */);
+               if (status != 0)
+               {
+                       char errbuf[1024];
+                       ERROR ("network: pthread_create failed: %s",
+                                       sstrerror (errno, errbuf,
+                                               sizeof (errbuf)));
+               }
+               else
+               {
+                       receive_thread_running = 1;
+               }
+       }
+
+       return (0);
+} /* int network_init */
+
+/* 
+ * The flush option of the network plugin cannot flush individual identifiers.
+ * All the values are added to a buffer and sent when the buffer is full, the
+ * requested value may or may not be in there, it's not worth finding out. We
+ * just send the buffer if `flush'  is called - if the requested value was in
+ * there, good. If not, well, then there is nothing to flush.. -octo
+ */
+static int network_flush (__attribute__((unused)) cdtime_t timeout,
+               __attribute__((unused)) const char *identifier,
+               __attribute__((unused)) user_data_t *user_data)
+{
+       pthread_mutex_lock (&send_buffer_lock);
+
+       if (send_buffer_fill > 0)
+         flush_buffer ();
+
+       pthread_mutex_unlock (&send_buffer_lock);
+
+       return (0);
+} /* int network_flush */
+
+void module_register (void)
+{
+       plugin_register_complex_config ("network", network_config);
+       plugin_register_init   ("network", network_init);
+       plugin_register_flush   ("network", network_flush,
+                       /* user_data = */ NULL);
+} /* void module_register */
+
+/* vim: set fdm=marker : */
diff --git a/src/network.h b/src/network.h
new file mode 100644 (file)
index 0000000..1b35456
--- /dev/null
@@ -0,0 +1,75 @@
+/**
+ * collectd - src/network.h
+ * Copyright (C) 2005-2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef NETWORK_H
+#define NETWORK_H
+
+/*
+ * From RFC2365: Administratively Scoped IP Multicast
+ *
+ * The IPv4 Organization Local Scope -- 239.192.0.0/14
+ *
+ * 239.192.0.0/14 is defined to be the IPv4 Organization Local Scope, and is
+ * the space from which an organization should allocate sub-ranges when
+ * defining scopes for private use.
+ *
+ * Port 25826 is not assigned as of 2005-09-12
+ */
+
+/*
+ * From RFC2373: IP Version 6 Addressing Architecture
+ *
+ * 2.7 Multicast Addresses
+ *
+ *  |   8    |  4 |  4 |          80 bits          |     32 bits     |
+ *  +--------+----+----+---------------------------+-----------------+
+ *  |11111111|flgs|scop|   reserved must be zero   |    group ID     |
+ *  +--------+----+----+---------------------------+-----------------+
+ *
+ * flgs = 1 => non-permanently-assigned ("transient") multicast address.
+ * scop = 8 => organization-local scope
+ *
+ * group = efc0:4a42 = 239.192.74.66
+ */
+
+#define NET_DEFAULT_V4_ADDR "239.192.74.66"
+#define NET_DEFAULT_V6_ADDR "ff18::efc0:4a42"
+#define NET_DEFAULT_PORT    "25826"
+
+#define TYPE_HOST            0x0000
+#define TYPE_TIME            0x0001
+#define TYPE_TIME_HR         0x0008
+#define TYPE_PLUGIN          0x0002
+#define TYPE_PLUGIN_INSTANCE 0x0003
+#define TYPE_TYPE            0x0004
+#define TYPE_TYPE_INSTANCE   0x0005
+#define TYPE_VALUES          0x0006
+#define TYPE_INTERVAL        0x0007
+#define TYPE_INTERVAL_HR     0x0009
+
+/* Types to transmit notifications */
+#define TYPE_MESSAGE         0x0100
+#define TYPE_SEVERITY        0x0101
+
+#define TYPE_SIGN_SHA256     0x0200
+#define TYPE_ENCR_AES256     0x0210
+
+#endif /* NETWORK_H */
diff --git a/src/nfs.c b/src/nfs.c
new file mode 100644 (file)
index 0000000..f3c636e
--- /dev/null
+++ b/src/nfs.c
@@ -0,0 +1,362 @@
+/**
+ * collectd - src/nfs.c
+ * Copyright (C) 2005,2006  Jason Pepas
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Jason Pepas <cell at ices.utexas.edu>
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#if !KERNEL_LINUX
+# error "No applicable input method."
+#endif
+
+/*
+see /proc/net/rpc/nfs
+see http://www.missioncriticallinux.com/orph/NFS-Statistics
+
+net x x x x
+rpc_stat.netcnt         Not used; always zero.
+rpc_stat.netudpcnt      Not used; always zero.
+rpc_stat.nettcpcnt      Not used; always zero.
+rpc_stat.nettcpconn     Not used; always zero.
+
+rpc x x x
+rpc_stat.rpccnt             The number of RPC calls.
+rpc_stat.rpcretrans         The number of retransmitted RPC calls.
+rpc_stat.rpcauthrefresh     The number of credential refreshes.
+
+proc2 x x x...
+proc3 x x x...
+
+Procedure   NFS Version NFS Version 3
+Number      Procedures  Procedures
+
+0           null        null
+1           getattr     getattr
+2           setattr     setattr
+3           root        lookup
+4           lookup      access
+5           readlink    readlink
+6           read        read
+7           wrcache     write
+8           write       create
+9           create      mkdir
+10          remove      symlink
+11          rename      mknod
+12          link        remove
+13          symlink     rmdir
+14          mkdir       rename
+15          rmdir       link
+16          readdir     readdir
+17          fsstat      readdirplus
+18                      fsstat
+19                      fsinfo
+20                      pathconf
+21                      commit
+*/
+
+static const char *nfs2_procedures_names[] =
+{
+       "null",
+       "getattr",
+       "setattr",
+       "root",
+       "lookup",
+       "readlink",
+       "read",
+       "wrcache",
+       "write",
+       "create",
+       "remove",
+       "rename",
+       "link",
+       "symlink",
+       "mkdir",
+       "rmdir",
+       "readdir",
+       "fsstat",
+       NULL
+};
+static int nfs2_procedures_names_num = 18;
+
+static const char *nfs3_procedures_names[] =
+{
+       "null",
+       "getattr",
+       "setattr",
+       "lookup",
+       "access",
+       "readlink",
+       "read",
+       "write",
+       "create",
+       "mkdir",
+       "symlink",
+       "mknod",
+       "remove",
+       "rmdir",
+       "rename",
+       "link",
+       "readdir",
+       "readdirplus",
+       "fsstat",
+       "fsinfo",
+       "pathconf",
+       "commit",
+       NULL
+};
+static int nfs3_procedures_names_num = 22;
+
+#if HAVE_LIBKSTAT && 0
+extern kstat_ctl_t *kc;
+static kstat_t *nfs2_ksp_client;
+static kstat_t *nfs2_ksp_server;
+static kstat_t *nfs3_ksp_client;
+static kstat_t *nfs3_ksp_server;
+static kstat_t *nfs4_ksp_client;
+static kstat_t *nfs4_ksp_server;
+#endif
+
+/* Possibly TODO: NFSv4 statistics */
+
+#if 0
+static int nfs_init (void)
+{
+#if HAVE_LIBKSTAT && 0
+       kstat_t *ksp_chain;
+
+       nfs2_ksp_client = NULL;
+       nfs2_ksp_server = NULL;
+       nfs3_ksp_client = NULL;
+       nfs3_ksp_server = NULL;
+       nfs4_ksp_client = NULL;
+       nfs4_ksp_server = NULL;
+       
+       if (kc == NULL)
+               return;
+
+       for (ksp_chain = kc->kc_chain; ksp_chain != NULL;
+                       ksp_chain = ksp_chain->ks_next)
+       {
+               if (strncmp (ksp_chain->ks_module, "nfs", 3) != 0)
+                       continue;
+               else if (strncmp (ksp_chain->ks_name, "rfsproccnt_v2", 13) == 0)
+                       nfs2_ksp_server = ksp_chain;
+               else if (strncmp (ksp_chain->ks_name, "rfsproccnt_v3", 13) == 0)
+                       nfs3_ksp_server = ksp_chain;
+               else if (strncmp (ksp_chain->ks_name, "rfsproccnt_v4", 13) == 0)
+                       nfs4_ksp_server = ksp_chain;
+               else if (strncmp (ksp_chain->ks_name, "rfsreqcnt_v2", 12) == 0)
+                       nfs2_ksp_client = ksp_chain;
+               else if (strncmp (ksp_chain->ks_name, "rfsreqcnt_v3", 12) == 0)
+                       nfs3_ksp_client = ksp_chain;
+               else if (strncmp (ksp_chain->ks_name, "rfsreqcnt_v4", 12) == 0)
+                       nfs4_ksp_client = ksp_chain;
+       }
+#endif
+
+       return (0);
+} /* int nfs_init */
+#endif
+
+#define BUFSIZE 1024
+static void nfs_procedures_submit (const char *plugin_instance,
+               unsigned long long *val, const char **names, int len)
+{
+       value_t values[1];
+       value_list_t vl = VALUE_LIST_INIT;
+       int i;
+
+       vl.values = values;
+       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));
+
+       for (i = 0; i < len; i++)
+       {
+               values[0].derive = val[i];
+               sstrncpy (vl.type_instance, names[i],
+                               sizeof (vl.type_instance));
+               DEBUG ("%s-%s/nfs_procedure-%s = %llu",
+                               vl.plugin, vl.plugin_instance,
+                               vl.type_instance, val[i]);
+               plugin_dispatch_values (&vl);
+       }
+} /* void nfs_procedures_submit */
+
+static void nfs_read_stats_file (FILE *fh, char *inst)
+{
+       char buffer[BUFSIZE];
+
+       char plugin_instance[DATA_MAX_NAME_LEN];
+
+       char *fields[48];
+       int numfields = 0;
+
+       if (fh == NULL)
+               return;
+
+       while (fgets (buffer, BUFSIZE, fh) != NULL)
+       {
+               numfields = strsplit (buffer, fields, 48);
+
+               if (((numfields - 2) != nfs2_procedures_names_num)
+                               && ((numfields - 2)
+                                       != nfs3_procedures_names_num))
+                       continue;
+
+               if (strcmp (fields[0], "proc2") == 0)
+               {
+                       int i;
+                       unsigned long long *values;
+
+                       if ((numfields - 2) != nfs2_procedures_names_num)
+                       {
+                               WARNING ("nfs plugin: Wrong "
+                                               "number of fields (= %i) "
+                                               "for NFSv2 statistics.",
+                                               numfields - 2);
+                               continue;
+                       }
+
+                       ssnprintf (plugin_instance, sizeof (plugin_instance),
+                                       "v2%s", inst);
+
+                       values = (unsigned long long *) malloc (nfs2_procedures_names_num * sizeof (unsigned long long));
+                       if (values == NULL)
+                       {
+                               char errbuf[1024];
+                               ERROR ("nfs plugin: malloc "
+                                               "failed: %s",
+                                               sstrerror (errno, errbuf, sizeof (errbuf)));
+                               continue;
+                       }
+
+                       for (i = 0; i < nfs2_procedures_names_num; i++)
+                               values[i] = atoll (fields[i + 2]);
+
+                       nfs_procedures_submit (plugin_instance, values,
+                                       nfs2_procedures_names,
+                                       nfs2_procedures_names_num);
+
+                       free (values);
+               }
+               else if (strncmp (fields[0], "proc3", 5) == 0)
+               {
+                       int i;
+                       unsigned long long *values;
+
+                       if ((numfields - 2) != nfs3_procedures_names_num)
+                       {
+                               WARNING ("nfs plugin: Wrong "
+                                               "number of fields (= %i) "
+                                               "for NFSv3 statistics.",
+                                               numfields - 2);
+                               continue;
+                       }
+
+                       ssnprintf (plugin_instance, sizeof (plugin_instance),
+                                       "v3%s", inst);
+
+                       values = (unsigned long long *) malloc (nfs3_procedures_names_num * sizeof (unsigned long long));
+                       if (values == NULL)
+                       {
+                               char errbuf[1024];
+                               ERROR ("nfs plugin: malloc "
+                                               "failed: %s",
+                                               sstrerror (errno, errbuf, sizeof (errbuf)));
+                               continue;
+                       }
+
+                       for (i = 0; i < nfs3_procedures_names_num; i++)
+                               values[i] = atoll (fields[i + 2]);
+
+                       nfs_procedures_submit (plugin_instance, values,
+                                       nfs3_procedures_names,
+                                       nfs3_procedures_names_num);
+
+                       free (values);
+               }
+       } /* while (fgets (buffer, BUFSIZE, fh) != NULL) */
+} /* void nfs_read_stats_file */
+#undef BUFSIZE
+
+#if HAVE_LIBKSTAT && 0
+static void nfs2_read_kstat (kstat_t *ksp, char *inst)
+{
+       unsigned long long values[18];
+
+       values[0] = get_kstat_value (ksp, "null");
+       values[1] = get_kstat_value (ksp, "getattr");
+       values[2] = get_kstat_value (ksp, "setattr");
+       values[3] = get_kstat_value (ksp, "root");
+       values[4] = get_kstat_value (ksp, "lookup");
+       values[5] = get_kstat_value (ksp, "readlink");
+       values[6] = get_kstat_value (ksp, "read");
+       values[7] = get_kstat_value (ksp, "wrcache");
+       values[8] = get_kstat_value (ksp, "write");
+       values[9] = get_kstat_value (ksp, "create");
+       values[10] = get_kstat_value (ksp, "remove");
+       values[11] = get_kstat_value (ksp, "rename");
+       values[12] = get_kstat_value (ksp, "link");
+       values[13] = get_kstat_value (ksp, "symlink");
+       values[14] = get_kstat_value (ksp, "mkdir");
+       values[15] = get_kstat_value (ksp, "rmdir");
+       values[16] = get_kstat_value (ksp, "readdir");
+       values[17] = get_kstat_value (ksp, "statfs");
+
+       nfs2_procedures_submit (values, inst);
+}
+#endif
+
+static int nfs_read (void)
+{
+       FILE *fh;
+
+       if ((fh = fopen ("/proc/net/rpc/nfs", "r")) != NULL)
+       {
+               nfs_read_stats_file (fh, "client");
+               fclose (fh);
+       }
+
+       if ((fh = fopen ("/proc/net/rpc/nfsd", "r")) != NULL)
+       {
+               nfs_read_stats_file (fh, "server");
+               fclose (fh);
+       }
+
+#if HAVE_LIBKSTAT && 0
+       if (nfs2_ksp_client != NULL)
+               nfs2_read_kstat (nfs2_ksp_client, "client");
+       if (nfs2_ksp_server != NULL)
+               nfs2_read_kstat (nfs2_ksp_server, "server");
+#endif /* defined(HAVE_LIBKSTAT) */
+
+       return (0);
+}
+
+void module_register (void)
+{
+       plugin_register_read ("nfs", nfs_read);
+} /* void module_register */
diff --git a/src/nginx.c b/src/nginx.c
new file mode 100644 (file)
index 0000000..3e162ba
--- /dev/null
@@ -0,0 +1,286 @@
+/**
+ * collectd - src/nginx.c
+ * Copyright (C) 2006-2010  Florian octo Forster
+ * Copyright (C) 2008       Sebastian Harl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at collectd.org>
+ *   Sebastian Harl <sh at tokkee.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+
+#include <curl/curl.h>
+
+static char *url         = NULL;
+static char *user        = NULL;
+static char *pass        = NULL;
+static char *verify_peer = NULL;
+static char *verify_host = NULL;
+static char *cacert      = NULL;
+
+static CURL *curl = NULL;
+
+static char   nginx_buffer[16384];
+static size_t nginx_buffer_len = 0;
+static char   nginx_curl_error[CURL_ERROR_SIZE];
+
+static const char *config_keys[] =
+{
+  "URL",
+  "User",
+  "Password",
+  "VerifyPeer",
+  "VerifyHost",
+  "CACert"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static size_t nginx_curl_callback (void *buf, size_t size, size_t nmemb,
+    void __attribute__((unused)) *stream)
+{
+  size_t len = size * nmemb;
+
+  /* Check if the data fits into the memory. If not, truncate it. */
+  if ((nginx_buffer_len + len) >= sizeof (nginx_buffer))
+  {
+    assert (sizeof (nginx_buffer) > nginx_buffer_len);
+    len = (sizeof (nginx_buffer) - 1) - nginx_buffer_len;
+  }
+
+  if (len <= 0)
+    return (len);
+
+  memcpy (&nginx_buffer[nginx_buffer_len], buf, len);
+  nginx_buffer_len += len;
+  nginx_buffer[nginx_buffer_len] = 0;
+
+  return (len);
+}
+
+static int config_set (char **var, const char *value)
+{
+  if (*var != NULL)
+  {
+    free (*var);
+    *var = NULL;
+  }
+
+  if ((*var = strdup (value)) == NULL)
+    return (1);
+  else
+    return (0);
+}
+
+static int config (const char *key, const char *value)
+{
+  if (strcasecmp (key, "url") == 0)
+    return (config_set (&url, value));
+  else if (strcasecmp (key, "user") == 0)
+    return (config_set (&user, value));
+  else if (strcasecmp (key, "password") == 0)
+    return (config_set (&pass, value));
+  else if (strcasecmp (key, "verifypeer") == 0)
+    return (config_set (&verify_peer, value));
+  else if (strcasecmp (key, "verifyhost") == 0)
+    return (config_set (&verify_host, value));
+  else if (strcasecmp (key, "cacert") == 0)
+    return (config_set (&cacert, value));
+  else
+    return (-1);
+} /* int config */
+
+static int init (void)
+{
+  static char credentials[1024];
+
+  if (curl != NULL)
+    curl_easy_cleanup (curl);
+
+  if ((curl = curl_easy_init ()) == NULL)
+  {
+    ERROR ("nginx plugin: curl_easy_init failed.");
+    return (-1);
+  }
+
+  curl_easy_setopt (curl, CURLOPT_NOSIGNAL, 1);
+  curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, nginx_curl_callback);
+  curl_easy_setopt (curl, CURLOPT_USERAGENT, PACKAGE_NAME"/"PACKAGE_VERSION);
+  curl_easy_setopt (curl, CURLOPT_ERRORBUFFER, nginx_curl_error);
+
+  if (user != NULL)
+  {
+    int status = ssnprintf (credentials, sizeof (credentials),
+       "%s:%s", user, pass == NULL ? "" : pass);
+    if ((status < 0) || ((size_t) status >= sizeof (credentials)))
+    {
+      ERROR ("nginx plugin: Credentials would have been truncated.");
+      return (-1);
+    }
+
+    curl_easy_setopt (curl, CURLOPT_USERPWD, credentials);
+  }
+
+  if (url != NULL)
+  {
+    curl_easy_setopt (curl, CURLOPT_URL, url);
+  }
+
+  curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, 1);
+
+  if ((verify_peer == NULL) || IS_TRUE (verify_peer))
+  {
+    curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 1);
+  }
+  else
+  {
+    curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0);
+  }
+
+  if ((verify_host == NULL) || IS_TRUE (verify_host))
+  {
+    curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 2);
+  }
+  else
+  {
+    curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0);
+  }
+
+  if (cacert != NULL)
+  {
+    curl_easy_setopt (curl, CURLOPT_CAINFO, cacert);
+  }
+
+  return (0);
+} /* void init */
+
+static void submit (char *type, char *inst, long long value)
+{
+  value_t values[1];
+  value_list_t vl = VALUE_LIST_INIT;
+
+  if (strcmp (type, "nginx_connections") == 0)
+    values[0].gauge = value;
+  else if (strcmp (type, "nginx_requests") == 0)
+    values[0].derive = value;
+  else
+    return;
+
+  vl.values = values;
+  vl.values_len = 1;
+  sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+  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)
+    sstrncpy (vl.type_instance, inst, sizeof (vl.type_instance));
+
+  plugin_dispatch_values (&vl);
+} /* void submit */
+
+static int nginx_read (void)
+{
+  int i;
+
+  char *ptr;
+  char *lines[16];
+  int   lines_num = 0;
+  char *saveptr;
+
+  char *fields[16];
+  int   fields_num;
+
+  if (curl == NULL)
+    return (-1);
+  if (url == NULL)
+    return (-1);
+
+  nginx_buffer_len = 0;
+  if (curl_easy_perform (curl) != 0)
+  {
+    WARNING ("nginx plugin: curl_easy_perform failed: %s", nginx_curl_error);
+    return (-1);
+  }
+
+  ptr = nginx_buffer;
+  saveptr = NULL;
+  while ((lines[lines_num] = strtok_r (ptr, "\n\r", &saveptr)) != NULL)
+  {
+    ptr = NULL;
+    lines_num++;
+
+    if (lines_num >= 16)
+      break;
+  }
+
+  /*
+   * Active connections: 291
+   * server accepts handled requests
+   *  16630948 16630948 31070465
+   * Reading: 6 Writing: 179 Waiting: 106
+   */
+  for (i = 0; i < lines_num; i++)
+  {
+    fields_num = strsplit (lines[i], fields,
+       (sizeof (fields) / sizeof (fields[0])));
+
+    if (fields_num == 3)
+    {
+      if ((strcmp (fields[0], "Active") == 0)
+         && (strcmp (fields[1], "connections:") == 0))
+      {
+       submit ("nginx_connections", "active", atoll (fields[2]));
+      }
+      else if ((atoll (fields[0]) != 0)
+         && (atoll (fields[1]) != 0)
+         && (atoll (fields[2]) != 0))
+      {
+       submit ("nginx_requests", NULL, atoll (fields[2]));
+      }
+    }
+    else if (fields_num == 6)
+    {
+      if ((strcmp (fields[0], "Reading:") == 0)
+         && (strcmp (fields[2], "Writing:") == 0)
+         && (strcmp (fields[4], "Waiting:") == 0))
+      {
+       submit ("nginx_connections", "reading", atoll (fields[1]));
+       submit ("nginx_connections", "writing", atoll (fields[3]));
+       submit ("nginx_connections", "waiting", atoll (fields[5]));
+      }
+    }
+  }
+
+  nginx_buffer_len = 0;
+
+  return (0);
+} /* int nginx_read */
+
+void module_register (void)
+{
+  plugin_register_config ("nginx", config, config_keys, config_keys_num);
+  plugin_register_init ("nginx", init);
+  plugin_register_read ("nginx", nginx_read);
+} /* void module_register */
+
+/*
+ * vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
+ */
diff --git a/src/notify_desktop.c b/src/notify_desktop.c
new file mode 100644 (file)
index 0000000..3f3c6df
--- /dev/null
@@ -0,0 +1,172 @@
+/**
+ * collectd - src/notify_desktop.c
+ * Copyright (C) 2008  Sebastian Harl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Sebastian Harl <sh at tokkee.org>
+ **/
+
+/*
+ * This plugin sends desktop notifications to a notification daemon.
+ */
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+
+#include <glib.h>
+#include <libnotify/notify.h>
+
+#ifndef NOTIFY_CHECK_VERSION
+# define NOTIFY_CHECK_VERSION(x,y,z) 0
+#endif
+
+#define log_info(...) INFO ("notify_desktop: " __VA_ARGS__)
+#define log_warn(...) WARNING ("notify_desktop: " __VA_ARGS__)
+#define log_err(...) ERROR ("notify_desktop: " __VA_ARGS__)
+
+#define DEFAULT_TIMEOUT 5000
+
+static int okay_timeout = DEFAULT_TIMEOUT;
+static int warn_timeout = DEFAULT_TIMEOUT;
+static int fail_timeout = DEFAULT_TIMEOUT;
+
+static int set_timeout (oconfig_item_t *ci, int *timeout)
+{
+       if ((0 != ci->children_num) || (1 != ci->values_num)
+                       || (OCONFIG_TYPE_NUMBER != ci->values[0].type)) {
+               log_err ("%s expects a single number argument.", ci->key);
+               return 1;
+       }
+
+       *timeout = (int)ci->values[0].value.number;
+       if (0 > *timeout)
+               *timeout = DEFAULT_TIMEOUT;
+       return 0;
+} /* set_timeout */
+
+static int c_notify_config (oconfig_item_t *ci)
+{
+       int i = 0;
+
+       for (i = 0; i < ci->children_num; ++i) {
+               oconfig_item_t *c = ci->children + i;
+
+               if (0 == strcasecmp (c->key, "OkayTimeout"))
+                       set_timeout (c, &okay_timeout);
+               else if (0 == strcasecmp (c->key, "WarningTimeout"))
+                       set_timeout (c, &warn_timeout);
+               else if (0 == strcasecmp (c->key, "FailureTimeout"))
+                       set_timeout (c, &fail_timeout);
+       }
+       return 0;
+} /* c_notify_config */
+
+static int c_notify (const notification_t *n,
+               user_data_t __attribute__((unused)) *user_data)
+{
+       NotifyNotification *notification = NULL;
+       NotifyUrgency       urgency      = NOTIFY_URGENCY_LOW;
+       int                 timeout      = okay_timeout;
+
+       char summary[1024];
+
+       if (NOTIF_WARNING == n->severity) {
+               urgency = NOTIFY_URGENCY_NORMAL;
+               timeout = warn_timeout;
+       }
+       else if (NOTIF_FAILURE == n->severity) {
+               urgency = NOTIFY_URGENCY_CRITICAL;
+               timeout = fail_timeout;
+       }
+
+       ssnprintf (summary, sizeof (summary), "collectd %s notification",
+                       (NOTIF_FAILURE == n->severity) ? "FAILURE"
+                               : (NOTIF_WARNING == n->severity) ? "WARNING"
+                               : (NOTIF_OKAY == n->severity) ? "OKAY" : "UNKNOWN");
+
+       notification = notify_notification_new (summary, n->message, NULL
+#if NOTIFY_CHECK_VERSION (0, 7, 0)
+       );
+#else
+       , NULL);
+#endif
+       if (NULL == notification) {
+               log_err ("Failed to create a new notification.");
+               return -1;
+       }
+
+       notify_notification_set_urgency (notification, urgency);
+       notify_notification_set_timeout (notification, timeout);
+
+       if (! notify_notification_show (notification, NULL))
+               log_err ("Failed to display notification.");
+
+       g_object_unref (G_OBJECT (notification));
+       return 0;
+} /* c_notify */
+
+static int c_notify_shutdown (void)
+{
+       plugin_unregister_init ("notify_desktop");
+       plugin_unregister_notification ("notify_desktop");
+       plugin_unregister_shutdown ("notify_desktop");
+
+       if (notify_is_initted ())
+               notify_uninit ();
+       return 0;
+} /* c_notify_shutdown */
+
+static int c_notify_init (void)
+{
+       char *name         = NULL;
+       char *vendor       = NULL;
+       char *version      = NULL;
+       char *spec_version = NULL;
+
+       if (! notify_init (PACKAGE_STRING)) {
+               log_err ("Failed to initialize libnotify.");
+               return -1;
+       }
+
+       if (! notify_get_server_info (&name, &vendor, &version, &spec_version))
+               log_warn ("Failed to get the notification server info. "
+                               "Check if you have a notification daemon running.");
+       else {
+               log_info ("Found notification daemon: %s (%s) %s (spec version %s)",
+                               name, vendor, version, spec_version);
+               free (name);
+               free (vendor);
+               free (version);
+               free (spec_version);
+       }
+
+       plugin_register_notification ("notify_desktop", c_notify,
+                       /* user_data = */ NULL);
+       plugin_register_shutdown ("notify_desktop", c_notify_shutdown);
+       return 0;
+} /* c_notify_init */
+
+void module_register (void)
+{
+       plugin_register_complex_config ("notify_desktop", c_notify_config);
+       plugin_register_init ("notify_desktop", c_notify_init);
+       return;
+} /* module_register */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
+
diff --git a/src/notify_email.c b/src/notify_email.c
new file mode 100644 (file)
index 0000000..cd216ca
--- /dev/null
@@ -0,0 +1,326 @@
+/**
+ * collectd - src/notify_email.c
+ * Copyright (C) 2008  Oleg King
+ * Copyright (C) 2010  Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Oleg King <king2 at kaluga.ru>
+ *   Florian Forster <octo at collectd.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include <auth-client.h>
+#include <libesmtp.h>
+#include <pthread.h>
+
+#define MAXSTRING               256
+
+static const char *config_keys[] =
+{
+  "SMTPServer",
+  "SMTPPort",
+  "SMTPUser",
+  "SMTPPassword",
+  "From",
+  "Recipient",
+  "Subject"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static char **recipients;
+static int recipients_len = 0;
+
+static smtp_session_t session;
+static pthread_mutex_t session_lock = PTHREAD_MUTEX_INITIALIZER;
+static smtp_message_t message;
+static auth_context_t authctx = NULL;
+
+static int smtp_port = 25;
+static char *smtp_host = NULL;
+static char *smtp_user = NULL;
+static char *smtp_password = NULL;
+static char *email_from = NULL;
+static char *email_subject = NULL;
+
+#define DEFAULT_SMTP_HOST      "localhost"
+#define DEFAULT_SMTP_FROM      "root@localhost"
+#define DEFAULT_SMTP_SUBJECT   "Collectd notify: %s@%s"
+
+/* Callback to get username and password */
+static int authinteract (auth_client_request_t request, char **result,
+    int fields, void __attribute__((unused)) *arg)
+{               
+  int i;
+  for (i = 0; i < fields; i++)
+  {
+    if (request[i].flags & AUTH_USER)
+      result[i] = smtp_user;
+    else if (request[i].flags & AUTH_PASS)
+      result[i] = smtp_password;
+    else
+      return 0;
+  }
+  return 1;
+} /* int authinteract */
+
+/* Callback to print the recipient status */
+static void print_recipient_status (smtp_recipient_t recipient,
+    const char *mailbox, void __attribute__((unused)) *arg)
+{
+  const smtp_status_t *status;
+
+  status = smtp_recipient_status (recipient);
+  if (status->text[strlen(status->text) - 2] == '\r')
+    status->text[strlen(status->text) - 2] = 0;
+  INFO ("notify_email: notify sent to %s: %d %s", mailbox, status->code,
+      status->text);
+} /* void print_recipient_status */
+
+/* Callback to monitor SMTP activity */
+static void monitor_cb (const char *buf, int buflen, int writing,
+    void __attribute__((unused)) *arg)
+{
+  char log_str[MAXSTRING];
+
+  sstrncpy (log_str, buf, sizeof (log_str));
+  if (buflen > 2)
+    log_str[buflen - 2] = 0; /* replace \n with \0 */
+
+  if (writing == SMTP_CB_HEADERS) {
+    DEBUG ("notify_email plugin: SMTP --- H: %s", log_str);
+    return;
+  }
+  DEBUG (writing
+      ? "notify_email plugin: SMTP >>> C: %s"
+      : "notify_email plugin: SMTP <<< S: %s",
+      log_str);
+} /* void monitor_cb */
+
+static int notify_email_init (void)
+{
+  char server[MAXSTRING];
+
+  ssnprintf(server, sizeof (server), "%s:%i",
+      (smtp_host == NULL) ? DEFAULT_SMTP_HOST : smtp_host,
+      smtp_port);
+
+  pthread_mutex_lock (&session_lock);
+
+  auth_client_init();
+
+  session = smtp_create_session ();
+  if (session == NULL) {
+    pthread_mutex_unlock (&session_lock);
+    ERROR ("notify_email plugin: cannot create SMTP session");
+    return (-1);
+  }
+
+  smtp_set_monitorcb (session, monitor_cb, NULL, 1);
+  smtp_set_hostname (session, hostname_g);
+  smtp_set_server (session, server);
+
+  if (smtp_user && smtp_password) {
+    authctx = auth_create_context ();
+    auth_set_mechanism_flags (authctx, AUTH_PLUGIN_PLAIN, 0);
+    auth_set_interact_cb (authctx, authinteract, NULL);
+  }
+
+  if ( !smtp_auth_set_context (session, authctx)) {
+    pthread_mutex_unlock (&session_lock);
+    ERROR ("notify_email plugin: cannot set SMTP auth context");
+    return (-1);   
+  }
+
+  pthread_mutex_unlock (&session_lock);
+  return (0);
+} /* int notify_email_init */
+
+static int notify_email_shutdown (void)
+{
+  pthread_mutex_lock (&session_lock);
+
+  if (session != NULL)
+    smtp_destroy_session (session);
+  session = NULL;
+
+  if (authctx != NULL)
+    auth_destroy_context (authctx);
+  authctx = NULL;
+
+  auth_client_exit();
+
+  pthread_mutex_unlock (&session_lock);
+  return (0);
+} /* int notify_email_shutdown */
+
+static int notify_email_config (const char *key, const char *value)
+{
+  if (strcasecmp (key, "Recipient") == 0)
+  {
+    char **tmp;
+
+    tmp = (char **) realloc ((void *) recipients, (recipients_len + 1) * sizeof (char *));
+    if (tmp == NULL) {
+      ERROR ("notify_email: realloc failed.");
+      return (-1);
+    }
+
+    recipients = tmp;
+    recipients[recipients_len] = strdup (value);
+    if (recipients[recipients_len] == NULL) {
+      ERROR ("notify_email: strdup failed.");
+      return (-1);
+    }
+    recipients_len++;
+  }
+  else if (0 == strcasecmp (key, "SMTPServer")) {
+    sfree (smtp_host);
+    smtp_host = strdup (value);
+  }
+  else if (0 == strcasecmp (key, "SMTPPort")) {
+    int port_tmp = atoi (value);
+    if (port_tmp < 1 || port_tmp > 65535)
+    {
+      WARNING ("notify_email plugin: Invalid SMTP port: %i", port_tmp);
+      return (1);
+    }
+    smtp_port = port_tmp;
+  }
+  else if (0 == strcasecmp (key, "SMTPUser")) {
+    sfree (smtp_user);
+    smtp_user = strdup (value);
+  }
+  else if (0 == strcasecmp (key, "SMTPPassword")) {
+    sfree (smtp_password);
+    smtp_password = strdup (value);
+  }
+  else if (0 == strcasecmp (key, "From")) {
+    sfree (email_from);
+    email_from = strdup (value);
+  }
+  else if (0 == strcasecmp (key, "Subject")) {
+    sfree (email_subject);
+    email_subject = strdup (value);
+  }
+  else {
+    return -1;
+  }
+  return 0;
+} /* int notify_email_config (const char *, const char *) */
+
+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];
+
+  char severity[32];
+  char subject[MAXSTRING];
+
+  char buf[4096] = "";
+  int  buf_len = sizeof (buf);
+  int i;
+
+  ssnprintf (severity, sizeof (severity), "%s",
+      (n->severity == NOTIF_FAILURE) ? "FAILURE"
+      : ((n->severity == NOTIF_WARNING) ? "WARNING"
+        : ((n->severity == NOTIF_OKAY) ? "OKAY" : "UNKNOWN")));
+
+  ssnprintf (subject, sizeof (subject),
+      (email_subject == NULL) ? DEFAULT_SMTP_SUBJECT : email_subject,
+      severity, n->host);
+
+  tt = CDTIME_T_TO_TIME_T (n->time);
+  localtime_r (&tt, &timestamp_tm);
+  strftime (timestamp_str, sizeof (timestamp_str), "%Y-%m-%d %H:%M:%S",
+      &timestamp_tm);
+  timestamp_str[sizeof (timestamp_str) - 1] = '\0';
+
+  /* Let's make RFC822 message text with \r\n EOLs */
+  ssnprintf (buf, buf_len,
+      "MIME-Version: 1.0\r\n"
+      "Content-Type: text/plain;\r\n"
+      "Content-Transfer-Encoding: 8bit\r\n"
+      "Subject: %s\r\n"
+      "\r\n"
+      "%s - %s@%s\r\n"
+      "\r\n"
+      "Message: %s",
+      subject,
+      timestamp_str,
+      severity,
+      n->host,
+      n->message);
+
+  pthread_mutex_lock (&session_lock);
+
+  if (session == NULL) {
+    /* Initialization failed or we're in the process of shutting down. */
+    pthread_mutex_unlock (&session_lock);
+    return (-1);
+  }
+
+  if (!(message = smtp_add_message (session))) {
+    pthread_mutex_unlock (&session_lock);
+    ERROR ("notify_email plugin: cannot set SMTP message");
+    return (-1);   
+  }
+  smtp_set_reverse_path (message, email_from);
+  smtp_set_header (message, "To", NULL, NULL);
+  smtp_set_message_str (message, buf);
+
+  for (i = 0; i < recipients_len; i++)
+    smtp_add_recipient (message, recipients[i]);
+
+  /* Initiate a connection to the SMTP server and transfer the message. */
+  if (!smtp_start_session (session)) {
+    char buf[MAXSTRING];
+    ERROR ("notify_email plugin: SMTP server problem: %s",
+        smtp_strerror (smtp_errno (), buf, sizeof buf));
+    pthread_mutex_unlock (&session_lock);
+    return (-1);
+  } else {
+    #if COLLECT_DEBUG
+    const smtp_status_t *status;
+    /* Report on the success or otherwise of the mail transfer. */
+    status = smtp_message_transfer_status (message);
+    DEBUG ("notify_email plugin: SMTP server report: %d %s",
+      status->code, (status->text != NULL) ? status->text : "\n");
+    #endif
+    smtp_enumerate_recipients (message, print_recipient_status, NULL);
+  }
+
+  pthread_mutex_unlock (&session_lock);
+  return (0);
+} /* int notify_email_notification */
+
+void module_register (void)
+{
+  plugin_register_init ("notify_email", notify_email_init);
+  plugin_register_shutdown ("notify_email", notify_email_shutdown);
+  plugin_register_config ("notify_email", notify_email_config,
+      config_keys, config_keys_num);
+  plugin_register_notification ("notify_email", notify_email_notification,
+      /* user_data = */ NULL);
+} /* void module_register (void) */
+
+/* vim: set sw=2 sts=2 ts=8 et : */
diff --git a/src/ntpd.c b/src/ntpd.c
new file mode 100644 (file)
index 0000000..8bbf74d
--- /dev/null
@@ -0,0 +1,965 @@
+/**
+ * collectd - src/ntpd.c
+ * Copyright (C) 2006-2007  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#define _BSD_SOURCE /* For NI_MAXHOST */
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+
+#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#if HAVE_NETDB_H
+# include <netdb.h>
+#endif
+#if HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif
+#if HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+#if HAVE_ARPA_INET_H
+# include <arpa/inet.h> /* inet_ntoa */
+#endif
+#if HAVE_NETINET_TCP_H
+# include <netinet/tcp.h>
+#endif
+#if HAVE_POLL_H
+# include <poll.h>
+#endif
+
+static const char *config_keys[] =
+{
+       "Host",
+       "Port",
+       "ReverseLookups"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static int do_reverse_lookups = 1;
+
+# define NTPD_DEFAULT_HOST "localhost"
+# define NTPD_DEFAULT_PORT "123"
+static int   sock_descr = -1;
+static char *ntpd_host = NULL;
+static char  ntpd_port[16];
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * The following definitions were copied from the NTPd distribution  *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+#define MAXFILENAME 128
+#define MAXSEQ  127
+#define MODE_PRIVATE 7
+#define NTP_OLDVERSION ((uint8_t) 1) /* oldest credible version */
+#define IMPL_XNTPD 3
+#define FP_FRAC 65536.0
+
+#define REFCLOCK_ADDR 0x7f7f0000 /* 127.127.0.0 */
+#define REFCLOCK_MASK 0xffff0000 /* 255.255.0.0 */
+
+/* This structure is missing the message authentication code, since collectd
+ * doesn't use it. */
+struct req_pkt
+{
+       uint8_t  rm_vn_mode;
+       uint8_t  auth_seq;
+       uint8_t  implementation;                /* implementation number */
+       uint8_t  request;                       /* request number */
+       uint16_t err_nitems;            /* error code/number of data items */
+       uint16_t mbz_itemsize;          /* item size */
+       char     data[MAXFILENAME + 48];        /* data area [32 prev](176 byte max) */
+                                       /* struct conf_peer must fit */
+};
+#define REQ_LEN_NOMAC (sizeof(struct req_pkt))
+
+/*
+ * A response packet.  The length here is variable, this is a
+ * maximally sized one.  Note that this implementation doesn't
+ * authenticate responses.
+ */
+#define        RESP_HEADER_SIZE        (8)
+#define        RESP_DATA_SIZE          (500)
+
+struct resp_pkt
+{
+       uint8_t  rm_vn_mode;           /* response, more, version, mode */
+       uint8_t  auth_seq;             /* key, sequence number */
+       uint8_t  implementation;       /* implementation number */
+       uint8_t  request;              /* request number */
+       uint16_t err_nitems;           /* error code/number of data items */
+       uint16_t mbz_itemsize;         /* item size */
+       char     data[RESP_DATA_SIZE]; /* data area */
+};
+
+/*
+ * Bit setting macros for multifield items.
+ */
+#define        RESP_BIT        0x80
+#define        MORE_BIT        0x40
+
+#define        ISRESPONSE(rm_vn_mode)  (((rm_vn_mode)&RESP_BIT)!=0)
+#define        ISMORE(rm_vn_mode)      (((rm_vn_mode)&MORE_BIT)!=0)
+#define INFO_VERSION(rm_vn_mode) ((uint8_t)(((rm_vn_mode)>>3)&0x7))
+#define        INFO_MODE(rm_vn_mode)   ((rm_vn_mode)&0x7)
+
+#define        RM_VN_MODE(resp, more, version)         \
+                               ((uint8_t)(((resp)?RESP_BIT:0)\
+                               |((more)?MORE_BIT:0)\
+                               |((version?version:(NTP_OLDVERSION+1))<<3)\
+                               |(MODE_PRIVATE)))
+
+#define        INFO_IS_AUTH(auth_seq)  (((auth_seq) & 0x80) != 0)
+#define        INFO_SEQ(auth_seq)      ((auth_seq)&0x7f)
+#define        AUTH_SEQ(auth, seq)     ((uint8_t)((((auth)!=0)?0x80:0)|((seq)&0x7f)))
+
+#define        INFO_ERR(err_nitems)    ((uint16_t)((ntohs(err_nitems)>>12)&0xf))
+#define        INFO_NITEMS(err_nitems) ((uint16_t)(ntohs(err_nitems)&0xfff))
+#define        ERR_NITEMS(err, nitems) (htons((uint16_t)((((uint16_t)(err)<<12)&0xf000)\
+                               |((uint16_t)(nitems)&0xfff))))
+
+#define        INFO_MBZ(mbz_itemsize)  ((ntohs(mbz_itemsize)>>12)&0xf)
+#define        INFO_ITEMSIZE(mbz_itemsize)     ((uint16_t)(ntohs(mbz_itemsize)&0xfff))
+#define        MBZ_ITEMSIZE(itemsize)  (htons((uint16_t)(itemsize)))
+
+/* negate a long float type */
+#define M_NEG(v_i, v_f) \
+       do { \
+               if ((v_f) == 0) \
+               (v_i) = -((uint32_t)(v_i)); \
+               else { \
+                       (v_f) = -((uint32_t)(v_f)); \
+                       (v_i) = ~(v_i); \
+               } \
+       } while(0)
+/* l_fp to double */
+#define M_LFPTOD(r_i, r_uf, d) \
+       do { \
+               register int32_t  i; \
+               register uint32_t f; \
+               \
+               i = (r_i); \
+               f = (r_uf); \
+               if (i < 0) { \
+                       M_NEG(i, f); \
+                       (d) = -((double) i + ((double) f) / 4294967296.0); \
+               } else { \
+                       (d) = (double) i + ((double) f) / 4294967296.0; \
+               } \
+       } while (0)
+
+#define REQ_PEER_LIST_SUM 1
+struct info_peer_summary
+{
+       uint32_t dstadr;         /* local address (zero for undetermined) */
+       uint32_t srcadr;         /* source address */
+       uint16_t srcport;        /* source port */
+       uint8_t stratum;         /* stratum of peer */
+       int8_t hpoll;            /* host polling interval */
+       int8_t ppoll;            /* peer polling interval */
+       uint8_t reach;           /* reachability register */
+       uint8_t flags;           /* flags, from above */
+       uint8_t hmode;           /* peer mode */
+       int32_t  delay;          /* peer.estdelay; s_fp */
+       int32_t  offset_int;     /* peer.estoffset; integral part */
+       int32_t  offset_frc;     /* peer.estoffset; fractional part */
+       uint32_t dispersion;     /* peer.estdisp; u_fp */
+       uint32_t v6_flag;        /* is this v6 or not */
+       uint32_t unused1;        /* (unused) padding for dstadr6 */
+       struct in6_addr dstadr6; /* local address (v6) */
+       struct in6_addr srcadr6; /* source address (v6) */
+};
+
+#define REQ_SYS_INFO 4
+struct info_sys
+{
+       uint32_t peer;           /* system peer address (v4) */
+       uint8_t  peer_mode;      /* mode we are syncing to peer in */
+       uint8_t  leap;           /* system leap bits */
+       uint8_t  stratum;        /* our stratum */
+       int8_t   precision;      /* local clock precision */
+       int32_t  rootdelay;      /* distance from sync source */
+       uint32_t rootdispersion; /* dispersion from sync source */
+       uint32_t refid;          /* reference ID of sync source */
+       uint64_t reftime;        /* system reference time */
+       uint32_t poll;           /* system poll interval */
+       uint8_t  flags;          /* system flags */
+       uint8_t  unused1;        /* unused */
+       uint8_t  unused2;        /* unused */
+       uint8_t  unused3;        /* unused */
+       int32_t  bdelay;         /* default broadcast offset */
+       int32_t  frequency;      /* frequency residual (scaled ppm)  */
+       uint64_t authdelay;      /* default authentication delay */
+       uint32_t stability;      /* clock stability (scaled ppm) */
+       int32_t  v6_flag;        /* is this v6 or not */
+       int32_t  unused4;        /* unused, padding for peer6 */
+       struct in6_addr peer6;   /* system peer address (v6) */
+};
+
+#define REQ_GET_KERNEL 38
+struct info_kernel
+{
+       int32_t  offset;
+       int32_t  freq;
+       int32_t  maxerror;
+       int32_t  esterror;
+       uint16_t status;
+       uint16_t shift;
+       int32_t  constant;
+       int32_t  precision;
+       int32_t  tolerance;
+       /* pps stuff */
+       int32_t  ppsfreq;
+       int32_t  jitter;
+       int32_t  stabil;
+       int32_t  jitcnt;
+       int32_t  calcnt;
+       int32_t  errcnt;
+       int32_t  stbcnt;
+};
+
+/* List of reference clock names */
+static char *refclock_names[] =
+{
+       "UNKNOWN",    "LOCAL",        "GPS_TRAK",   "WWV_PST",     /*  0- 3 */
+       "SPECTRACOM", "TRUETIME",     "IRIG_AUDIO", "CHU_AUDIO",   /*  4- 7 */
+       "GENERIC",    "GPS_MX4200",   "GPS_AS2201", "GPS_ARBITER", /*  8-11 */
+       "IRIG_TPRO",  "ATOM_LEITCH",  "MSF_EES",    "GPSTM_TRUE",  /* 12-15 */
+       "GPS_BANC",   "GPS_DATUM",    "ACTS_NIST",  "WWV_HEATH",   /* 16-19 */
+       "GPS_NMEA",   "GPS_VME",      "PPS",        "ACTS_PTB",    /* 20-23 */
+       "ACTS_USNO",  "TRUETIME",     "GPS_HP",     "MSF_ARCRON",  /* 24-27 */
+       "SHM",        "GPS_PALISADE", "GPS_ONCORE", "GPS_JUPITER", /* 28-31 */
+       "CHRONOLOG",  "DUMBCLOCK",    "ULINK_M320", "PCF",         /* 32-35 */
+       "WWV_AUDIO",  "GPS_FG",       "HOPF_S",     "HOPF_P",      /* 36-39 */
+       "JJY",        "TT_IRIG",      "GPS_ZYFER",  "GPS_RIPENCC", /* 40-43 */
+       "NEOCLK4X"                                                 /* 44    */
+};
+static int refclock_names_num = STATIC_ARRAY_SIZE (refclock_names);
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * End of the copied stuff..                                         *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+static int ntpd_config (const char *key, const char *value)
+{
+       if (strcasecmp (key, "Host") == 0)
+       {
+               if (ntpd_host != NULL)
+                       free (ntpd_host);
+               if ((ntpd_host = strdup (value)) == NULL)
+                       return (1);
+       }
+       else if (strcasecmp (key, "Port") == 0)
+       {
+               int port = (int) (atof (value));
+               if ((port > 0) && (port <= 65535))
+                       ssnprintf (ntpd_port, sizeof (ntpd_port),
+                                       "%i", port);
+               else
+                       sstrncpy (ntpd_port, value, sizeof (ntpd_port));
+       }
+       else if (strcasecmp (key, "ReverseLookups") == 0)
+       {
+               if (IS_TRUE (value))
+                       do_reverse_lookups = 1;
+               else
+                       do_reverse_lookups = 0;
+       }
+       else
+       {
+               return (-1);
+       }
+
+       return (0);
+}
+
+static void ntpd_submit (char *type, 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_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));
+
+       plugin_dispatch_values (&vl);
+}
+
+static int ntpd_connect (void)
+{
+       char *host;
+       char *port;
+
+       struct addrinfo  ai_hints;
+       struct addrinfo *ai_list;
+       struct addrinfo *ai_ptr;
+       int              status;
+
+       if (sock_descr >= 0)
+               return (sock_descr);
+
+       DEBUG ("Opening a new socket");
+
+       host = ntpd_host;
+       if (host == NULL)
+               host = NTPD_DEFAULT_HOST;
+
+       port = ntpd_port;
+       if (strlen (port) == 0)
+               port = NTPD_DEFAULT_PORT;
+
+       memset (&ai_hints, '\0', sizeof (ai_hints));
+       ai_hints.ai_flags    = 0;
+#ifdef AI_ADDRCONFIG
+       ai_hints.ai_flags   |= AI_ADDRCONFIG;
+#endif
+       ai_hints.ai_family   = PF_UNSPEC;
+       ai_hints.ai_socktype = SOCK_DGRAM;
+       ai_hints.ai_protocol = IPPROTO_UDP;
+
+       if ((status = getaddrinfo (host, port, &ai_hints, &ai_list)) != 0)
+       {
+               char errbuf[1024];
+               ERROR ("ntpd plugin: getaddrinfo (%s, %s): %s",
+                               host, port,
+                               (status == EAI_SYSTEM)
+                               ? sstrerror (errno, errbuf, sizeof (errbuf))
+                               : gai_strerror (status));
+               return (-1);
+       }
+
+       for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
+       {
+               /* create our socket descriptor */
+               if ((sock_descr = socket (ai_ptr->ai_family,
+                                               ai_ptr->ai_socktype,
+                                               ai_ptr->ai_protocol)) < 0)
+                       continue;
+
+               /* connect to the ntpd */
+               if (connect (sock_descr, ai_ptr->ai_addr, ai_ptr->ai_addrlen))
+               {
+                       close (sock_descr);
+                       sock_descr = -1;
+                       continue;
+               }
+
+               break;
+       }
+
+       freeaddrinfo (ai_list);
+
+       if (sock_descr < 0)
+       {
+               ERROR ("ntpd plugin: Unable to connect to server.");
+       }
+
+       return (sock_descr);
+}
+
+/* For a description of the arguments see `ntpd_do_query' below. */
+static int ntpd_receive_response (int *res_items, int *res_size,
+               char **res_data, int res_item_size)
+{
+       int              sd;
+       struct pollfd    poll_s;
+       struct resp_pkt  res;
+       int              status;
+       int              done;
+       int              i;
+
+       char            *items;
+       size_t           items_num;
+
+       struct timeval   time_end;
+       struct timeval   time_now;
+       int              timeout;
+
+       int              pkt_item_num;        /* items in this packet */
+       int              pkt_item_len;        /* size of the items in this packet */
+       int              pkt_sequence;
+       char             pkt_recvd[MAXSEQ+1]; /* sequence numbers that have been received */
+       int              pkt_recvd_num;       /* number of packets that have been received */
+       int              pkt_lastseq;         /* the last sequence number */
+       ssize_t          pkt_padding;         /* Padding in this packet */
+
+       if ((sd = ntpd_connect ()) < 0)
+               return (-1);
+
+       items = NULL;
+       items_num = 0;
+
+       memset (pkt_recvd, '\0', sizeof (pkt_recvd));
+       pkt_recvd_num = 0;
+       pkt_lastseq   = -1;
+
+       *res_items = 0;
+       *res_size  = 0;
+       *res_data  = NULL;
+
+       if (gettimeofday (&time_end, NULL) < 0)
+       {
+               char errbuf[1024];
+               ERROR ("ntpd plugin: gettimeofday failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+       time_end.tv_sec++; /* wait for a most one second */
+
+       done = 0;
+       while (done == 0)
+       {
+               struct timeval time_left;
+
+               if (gettimeofday (&time_now, NULL) < 0)
+               {
+                       char errbuf[1024];
+                       ERROR ("ntpd plugin: gettimeofday failed: %s",
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       return (-1);
+               }
+
+               if (timeval_cmp (time_end, time_now, &time_left) <= 0)
+                       timeout = 0;
+               else
+                       timeout = 1000 * time_left.tv_sec
+                               + ((time_left.tv_usec + 500) / 1000);
+
+               /* timeout reached */
+               if (timeout <= 0)
+                       break;
+
+               poll_s.fd      = sd;
+               poll_s.events  = POLLIN | POLLPRI;
+               poll_s.revents = 0;
+               
+               DEBUG ("Polling for %ims", timeout);
+               status = poll (&poll_s, 1, timeout);
+
+               if ((status < 0) && ((errno == EAGAIN) || (errno == EINTR)))
+                       continue;
+
+               if (status < 0)
+               {
+                       char errbuf[1024];
+                       ERROR ("ntpd plugin: poll failed: %s",
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       return (-1);
+               }
+
+               if (status == 0) /* timeout */
+               {
+                       DEBUG ("timeout reached.");
+                       break;
+               }
+
+               memset ((void *) &res, '\0', sizeof (res));
+               status = recv (sd, (void *) &res, sizeof (res), 0 /* no flags */);
+
+               if ((status < 0) && ((errno == EAGAIN) || (errno == EINTR)))
+                       continue;
+
+               if (status < 0)
+               {
+                       char errbuf[1024];
+                       INFO ("recv(2) failed: %s",
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       DEBUG ("Closing socket #%i", sd);
+                       close (sd);
+                       sock_descr = sd = -1;
+                       return (-1);
+               }
+
+               DEBUG ("recv'd %i bytes", status);
+
+               /* 
+                * Do some sanity checks first
+                */
+               if (status < RESP_HEADER_SIZE)
+               {
+                       WARNING ("ntpd plugin: Short (%i bytes) packet received",
+                                       (int) status);
+                       continue;
+               }
+               if (INFO_MODE (res.rm_vn_mode) != MODE_PRIVATE)
+               {
+                       NOTICE ("ntpd plugin: Packet received with mode %i",
+                                       INFO_MODE (res.rm_vn_mode));
+                       continue;
+               }
+               if (INFO_IS_AUTH (res.auth_seq))
+               {
+                       NOTICE ("ntpd plugin: Encrypted packet received");
+                       continue;
+               }
+               if (!ISRESPONSE (res.rm_vn_mode))
+               {
+                       NOTICE ("ntpd plugin: Received request packet, "
+                                       "wanted response");
+                       continue;
+               }
+               if (INFO_MBZ (res.mbz_itemsize))
+               {
+                       WARNING ("ntpd plugin: Received packet with nonzero "
+                                       "MBZ field!");
+                       continue;
+               }
+               if (res.implementation != IMPL_XNTPD)
+               {
+                       WARNING ("ntpd plugin: Asked for request of type %i, "
+                                       "got %i", (int) IMPL_XNTPD, (int) res.implementation);
+                       continue;
+               }
+
+               /* Check for error code */
+               if (INFO_ERR (res.err_nitems) != 0)
+               {
+                       ERROR ("ntpd plugin: Received error code %i",
+                                       (int) INFO_ERR(res.err_nitems));
+                       return ((int) INFO_ERR (res.err_nitems));
+               }
+
+               /* extract number of items in this packet and the size of these items */
+               pkt_item_num = INFO_NITEMS (res.err_nitems);
+               pkt_item_len = INFO_ITEMSIZE (res.mbz_itemsize);
+               DEBUG ("pkt_item_num = %i; pkt_item_len = %i;",
+                               pkt_item_num, pkt_item_len);
+
+               /* Check if the reported items fit in the packet */
+               if ((pkt_item_num * pkt_item_len) > (status - RESP_HEADER_SIZE))
+               {
+                       ERROR ("ntpd plugin: %i items * %i bytes > "
+                                       "%i bytes - %i bytes header",
+                                       (int) pkt_item_num, (int) pkt_item_len,
+                                       (int) status, (int) RESP_HEADER_SIZE);
+                       continue;
+               }
+
+               if (pkt_item_len > res_item_size)
+               {
+                       ERROR ("ntpd plugin: (pkt_item_len = %i) "
+                                       ">= (res_item_size = %i)",
+                                       pkt_item_len, res_item_size);
+                       continue;
+               }
+
+               /* If this is the first packet (time wise, not sequence wise),
+                * set `res_size'. If it's not the first packet check if the
+                * items have the same size. Discard invalid packets. */
+               if (items_num == 0) /* first packet */
+               {
+                       DEBUG ("*res_size = %i", pkt_item_len);
+                       *res_size = pkt_item_len;
+               }
+               else if (*res_size != pkt_item_len)
+               {
+                       DEBUG ("Error: *res_size = %i; pkt_item_len = %i;",
+                                       *res_size, pkt_item_len);
+                       ERROR ("Item sizes differ.");
+                       continue;
+               }
+
+               /*
+                * Because the items in the packet may be smaller than the
+                * items requested, the following holds true:
+                */
+               assert ((*res_size == pkt_item_len)
+                               && (pkt_item_len <= res_item_size));
+
+               /* Calculate the padding. No idea why there might be any padding.. */
+               pkt_padding = 0;
+               if (pkt_item_len < res_item_size)
+                       pkt_padding = res_item_size - pkt_item_len;
+               DEBUG ("res_item_size = %i; pkt_padding = %zi;",
+                               res_item_size, pkt_padding);
+
+               /* Extract the sequence number */
+               pkt_sequence = INFO_SEQ (res.auth_seq);
+               if ((pkt_sequence < 0) || (pkt_sequence > MAXSEQ))
+               {
+                       ERROR ("ntpd plugin: Received packet with sequence %i",
+                                       pkt_sequence);
+                       continue;
+               }
+
+               /* Check if this sequence has been received before. If so, discard it. */
+               if (pkt_recvd[pkt_sequence] != '\0')
+               {
+                       NOTICE ("ntpd plugin: Sequence %i received twice",
+                                       pkt_sequence);
+                       continue;
+               }
+
+               /* If `pkt_lastseq != -1' another packet without `more bit' has
+                * been received. */
+               if (!ISMORE (res.rm_vn_mode))
+               {
+                       if (pkt_lastseq != -1)
+                       {
+                               ERROR ("ntpd plugin: Two packets which both "
+                                               "claim to be the last one in the "
+                                               "sequence have been received.");
+                               continue;
+                       }
+                       pkt_lastseq = pkt_sequence;
+                       DEBUG ("Last sequence = %i;", pkt_lastseq);
+               }
+
+               /*
+                * Enough with the checks. Copy the data now.
+                * We start by allocating some more memory.
+                */
+               DEBUG ("realloc (%p, %zu)", (void *) *res_data,
+                               (items_num + pkt_item_num) * res_item_size);
+               items = realloc ((void *) *res_data,
+                               (items_num + pkt_item_num) * res_item_size);
+               if (items == NULL)
+               {
+                       items = *res_data;
+                       ERROR ("ntpd plugin: realloc failed.");
+                       continue;
+               }
+               items_num += pkt_item_num;
+               *res_data = items;
+
+               for (i = 0; i < pkt_item_num; i++)
+               {
+                       /* dst: There are already `*res_items' items with
+                        *      res_item_size bytes each in in `*res_data'. Set
+                        *      dst to the first byte after that. */
+                       void *dst = (void *) (*res_data + ((*res_items) * res_item_size));
+                       /* src: We use `pkt_item_len' to calculate the offset
+                        *      from the beginning of the packet, because the
+                        *      items in the packet may be smaller than the
+                        *      items that were requested. We skip `i' such
+                        *      items. */
+                       void *src = (void *) (((char *) res.data) + (i * pkt_item_len));
+
+                       /* Set the padding to zeros */
+                       if (pkt_padding != 0)
+                               memset (dst, '\0', res_item_size);
+                       memcpy (dst, src, (size_t) pkt_item_len);
+
+                       /* Increment `*res_items' by one, so `dst' will end up
+                        * one further in the next round. */
+                       (*res_items)++;
+               } /* for (pkt_item_num) */
+
+               pkt_recvd[pkt_sequence] = (char) 1;
+               pkt_recvd_num++;
+
+               if ((pkt_recvd_num - 1) == pkt_lastseq)
+                       done = 1;
+       } /* while (done == 0) */
+
+       return (0);
+} /* int ntpd_receive_response */
+
+/* For a description of the arguments see `ntpd_do_query' below. */
+static int ntpd_send_request (int req_code, int req_items, int req_size, char *req_data)
+{
+       int             sd;
+       struct req_pkt  req;
+       size_t          req_data_len;
+       int             status;
+
+       assert (req_items >= 0);
+       assert (req_size  >= 0);
+
+       if ((sd = ntpd_connect ()) < 0)
+               return (-1);
+
+       memset ((void *) &req, '\0', sizeof (req));
+       req.rm_vn_mode = RM_VN_MODE(0, 0, 0);
+       req.auth_seq   = AUTH_SEQ (0, 0);
+       req.implementation = IMPL_XNTPD;
+       req.request = (unsigned char) req_code;
+
+       req_data_len = (size_t) (req_items * req_size);
+
+       assert (((req_data != NULL) && (req_data_len > 0))
+                       || ((req_data == NULL) && (req_items == 0) && (req_size == 0)));
+
+       req.err_nitems   = ERR_NITEMS (0, req_items);
+       req.mbz_itemsize = MBZ_ITEMSIZE (req_size);
+       
+       if (req_data != NULL)
+               memcpy ((void *) req.data, (const void *) req_data, req_data_len);
+
+       DEBUG ("req_items = %i; req_size = %i; req_data = %p;",
+                       req_items, req_size, (void *) req_data);
+
+       status = swrite (sd, (const char *) &req, REQ_LEN_NOMAC);
+       if (status < 0)
+       {
+               DEBUG ("`swrite' failed. Closing socket #%i", sd);
+               close (sd);
+               sock_descr = sd = -1;
+               return (status);
+       }
+
+       return (0);
+}
+
+/*
+ * ntpd_do_query:
+ *
+ * req_code:      Type of request packet
+ * req_items:     Numver of items in the request
+ * req_size:      Size of one item in the request
+ * req_data:      Data of the request packet
+ * res_items:     Pointer to where the number returned items will be stored.
+ * res_size:      Pointer to where the size of one returned item will be stored.
+ * res_data:      This is where a pointer to the (allocated) data will be stored.
+ * res_item_size: Size of one returned item. (used to calculate padding)
+ *
+ * returns:       zero upon success, non-zero otherwise.
+ */
+static int ntpd_do_query (int req_code, int req_items, int req_size, char *req_data,
+               int *res_items, int *res_size, char **res_data, int res_item_size)
+{
+       int status;
+
+       status = ntpd_send_request (req_code, req_items, req_size, req_data);
+       if (status != 0)
+               return (status);
+
+       status = ntpd_receive_response (res_items, res_size, res_data,
+                       res_item_size);
+       return (status);
+}
+
+static double ntpd_read_fp (int32_t val_int)
+{
+       double val_double;
+
+       val_int = ntohl (val_int);
+       val_double = ((double) val_int) / FP_FRAC;
+
+       return (val_double);
+}
+
+static int ntpd_read (void)
+{
+       struct info_kernel *ik;
+       int                 ik_num;
+       int                 ik_size;
+
+       struct info_peer_summary *ps;
+       int                       ps_num;
+       int                       ps_size;
+
+       int status;
+       int i;
+
+       ik      = NULL;
+       ik_num  = 0;
+       ik_size = 0;
+
+       status = ntpd_do_query (REQ_GET_KERNEL,
+                       0, 0, NULL, /* request data */
+                       &ik_num, &ik_size, (char **) ((void *) &ik), /* response data */
+                       sizeof (struct info_kernel));
+       if (status != 0)
+       {
+               ERROR ("ntpd plugin: ntpd_do_query (REQ_GET_KERNEL) failed with status %i", status);
+               return (status);
+       }
+       else if ((ik == NULL) || (ik_num == 0) || (ik_size == 0))
+       {
+               ERROR ("ntpd plugin: ntpd_do_query returned unexpected data. "
+                               "(ik = %p; ik_num = %i; ik_size = %i)",
+                               (void *) ik, ik_num, ik_size);
+               return (-1);
+       }
+
+       /* kerninfo -> estimated error */
+
+       DEBUG ("info_kernel:\n"
+                       "  pll offset    = %.8f\n"
+                       "  pll frequency = %.8f\n" /* drift compensation */
+                       "  est error     = %.8f\n",
+                       ntpd_read_fp (ik->offset),
+                       ntpd_read_fp (ik->freq),
+                       ntpd_read_fp (ik->esterror));
+
+       ntpd_submit ("frequency_offset", "loop",  ntpd_read_fp (ik->freq));
+       ntpd_submit ("time_offset",      "loop",  ntpd_read_fp (ik->offset));
+       ntpd_submit ("time_offset",      "error", ntpd_read_fp (ik->esterror));
+
+       free (ik);
+       ik = NULL;
+
+       status = ntpd_do_query (REQ_PEER_LIST_SUM,
+                       0, 0, NULL, /* request data */
+                       &ps_num, &ps_size, (char **) ((void *) &ps), /* response data */
+                       sizeof (struct info_peer_summary));
+       if (status != 0)
+       {
+               ERROR ("ntpd plugin: ntpd_do_query (REQ_PEER_LIST_SUM) failed with status %i", status);
+               return (status);
+       }
+       else if ((ps == NULL) || (ps_num == 0) || (ps_size == 0))
+       {
+               ERROR ("ntpd plugin: ntpd_do_query returned unexpected data. "
+                               "(ps = %p; ps_num = %i; ps_size = %i)",
+                               (void *) ps, ps_num, ps_size);
+               return (-1);
+       }
+
+       for (i = 0; i < ps_num; i++)
+       {
+               struct info_peer_summary *ptr;
+               double offset;
+
+               char peername[NI_MAXHOST];
+               int refclock_id;
+               
+               ptr = ps + i;
+               refclock_id = 0;
+
+               /* Convert the `long floating point' offset value to double */
+               M_LFPTOD (ntohl (ptr->offset_int), ntohl (ptr->offset_frc), offset);
+
+               /* Special IP addresses for hardware clocks and stuff.. */
+               if (!ptr->v6_flag
+                               && ((ntohl (ptr->srcadr) & REFCLOCK_MASK)
+                                       == REFCLOCK_ADDR))
+               {
+                       struct in_addr  addr_obj;
+                       char *addr_str;
+
+                       refclock_id = (ntohl (ptr->srcadr) >> 8) & 0x000000FF;
+
+                       if (refclock_id < refclock_names_num)
+                       {
+                               sstrncpy (peername, refclock_names[refclock_id],
+                                               sizeof (peername));
+                       }
+                       else
+                       {
+                               memset ((void *) &addr_obj, '\0', sizeof (addr_obj));
+                               addr_obj.s_addr = ptr->srcadr;
+                               addr_str = inet_ntoa (addr_obj);
+
+                               sstrncpy (peername, addr_str, sizeof (peername));
+                       }
+               }
+               else /* Normal network host. */
+               {
+                       struct sockaddr_storage sa;
+                       socklen_t sa_len;
+                       int flags = 0;
+
+                       memset (&sa, '\0', sizeof (sa));
+
+                       if (ptr->v6_flag)
+                       {
+                               struct sockaddr_in6 sa6;
+
+                               assert (sizeof (sa) >= sizeof (sa6));
+
+                               memset (&sa6, 0, sizeof (sa6));
+                               sa6.sin6_family = AF_INET6;
+                               sa6.sin6_port = htons (123);
+                               memcpy (&sa6.sin6_addr, &ptr->srcadr6,
+                                               sizeof (struct in6_addr));
+                               sa_len = sizeof (sa6);
+
+                               memcpy (&sa, &sa6, sizeof (sa6));
+                       }
+                       else
+                       {
+                               struct sockaddr_in sa4;
+
+                               assert (sizeof (sa) >= sizeof (sa4));
+
+                               memset (&sa4, 0, sizeof (sa4));
+                               sa4.sin_family = AF_INET;
+                               sa4.sin_port = htons (123);
+                               memcpy (&sa4.sin_addr, &ptr->srcadr,
+                                               sizeof (struct in_addr));
+                               sa_len = sizeof (sa4);
+
+                               memcpy (&sa, &sa4, sizeof (sa4));
+                       }
+
+                       if (do_reverse_lookups == 0)
+                               flags |= NI_NUMERICHOST;
+
+                       status = getnameinfo ((const struct sockaddr *) &sa,
+                                       sa_len,
+                                       peername, sizeof (peername),
+                                       NULL, 0, /* No port name */
+                                       flags);
+                       if (status != 0)
+                       {
+                               char errbuf[1024];
+                               ERROR ("ntpd plugin: getnameinfo failed: %s",
+                                               (status == EAI_SYSTEM)
+                                               ? sstrerror (errno, errbuf, sizeof (errbuf))
+                                               : gai_strerror (status));
+                               continue;
+                       }
+               }
+
+               DEBUG ("peer %i:\n"
+                               "  peername   = %s\n"
+                               "  srcadr     = 0x%08x\n"
+                               "  delay      = %f\n"
+                               "  offset_int = %i\n"
+                               "  offset_frc = %i\n"
+                               "  offset     = %f\n"
+                               "  dispersion = %f\n",
+                               i,
+                               peername,
+                               ntohl (ptr->srcadr),
+                               ntpd_read_fp (ptr->delay),
+                               ntohl (ptr->offset_int),
+                               ntohl (ptr->offset_frc),
+                               offset,
+                               ntpd_read_fp (ptr->dispersion));
+
+               if (refclock_id != 1) /* not the system clock (offset will always be zero.. */
+                       ntpd_submit ("time_offset", peername, offset);
+               ntpd_submit ("time_dispersion", peername, ntpd_read_fp (ptr->dispersion));
+               if (refclock_id == 0) /* not a reference clock */
+                       ntpd_submit ("delay", peername, ntpd_read_fp (ptr->delay));
+       }
+
+       free (ps);
+       ps = NULL;
+
+       return (0);
+} /* int ntpd_read */
+
+void module_register (void)
+{
+       plugin_register_config ("ntpd", ntpd_config,
+                       config_keys, config_keys_num);
+       plugin_register_read ("ntpd", ntpd_read);
+} /* void module_register */
diff --git a/src/nut.c b/src/nut.c
new file mode 100644 (file)
index 0000000..edc48c6
--- /dev/null
+++ b/src/nut.c
@@ -0,0 +1,292 @@
+/**
+ * collectd - src/nut.c
+ * Copyright (C) 2007  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include <pthread.h>
+#include <upsclient.h>
+
+#if HAVE_UPSCONN_T
+typedef UPSCONN_t collectd_upsconn_t;
+#elif HAVE_UPSCONN
+typedef UPSCONN collectd_upsconn_t;
+#else
+# error "Unable to determine the UPS connection type."
+#endif
+
+struct nut_ups_s;
+typedef struct nut_ups_s nut_ups_t;
+struct nut_ups_s
+{
+  collectd_upsconn_t *conn;
+  char      *upsname;
+  char      *hostname;
+  int        port;
+  nut_ups_t *next;
+};
+
+static nut_ups_t *upslist_head = NULL;
+
+static pthread_mutex_t read_lock = PTHREAD_MUTEX_INITIALIZER;
+static int read_busy = 0;
+
+static const char *config_keys[] =
+{
+  "UPS"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
+
+static void free_nut_ups_t (nut_ups_t *ups)
+{
+  if (ups->conn != NULL)
+  {
+    upscli_disconnect (ups->conn);
+    sfree (ups->conn);
+  }
+  sfree (ups->hostname);
+  sfree (ups->upsname);
+  sfree (ups);
+} /* void free_nut_ups_t */
+
+static int nut_add_ups (const char *name)
+{
+  nut_ups_t *ups;
+  int status;
+
+  DEBUG ("nut plugin: nut_add_ups (name = %s);", name);
+
+  ups = (nut_ups_t *) malloc (sizeof (nut_ups_t));
+  if (ups == NULL)
+  {
+    ERROR ("nut plugin: nut_add_ups: malloc failed.");
+    return (1);
+  }
+  memset (ups, '\0', sizeof (nut_ups_t));
+
+  status = upscli_splitname (name, &ups->upsname, &ups->hostname,
+      &ups->port);
+  if (status != 0)
+  {
+    ERROR ("nut plugin: nut_add_ups: upscli_splitname (%s) failed.", name);
+    free_nut_ups_t (ups);
+    return (1);
+  }
+
+  if (upslist_head == NULL)
+    upslist_head = ups;
+  else
+  {
+    nut_ups_t *last = upslist_head;
+    while (last->next != NULL)
+      last = last->next;
+    last->next = ups;
+  }
+
+  return (0);
+} /* int nut_add_ups */
+
+static int nut_config (const char *key, const char *value)
+{
+  if (strcasecmp (key, "UPS") == 0)
+    return (nut_add_ups (value));
+  else
+    return (-1);
+} /* int nut_config */
+
+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);
+  sstrncpy (vl.host,
+      (strcasecmp (ups->hostname, "localhost") == 0)
+      ? hostname_g
+      : ups->hostname,
+      sizeof (vl.host));
+  sstrncpy (vl.plugin, "nut", sizeof (vl.plugin));
+  sstrncpy (vl.plugin_instance, ups->upsname, sizeof (vl.plugin_instance));
+  sstrncpy (vl.type, type, sizeof (vl.type));
+  sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
+
+  plugin_dispatch_values (&vl);
+} /* void nut_submit */
+
+static int nut_read_one (nut_ups_t *ups)
+{
+  const char *query[3] = {"VAR", ups->upsname, NULL};
+  unsigned int query_num = 2;
+  char **answer;
+  unsigned int answer_num;
+  int status;
+
+  /* (Re-)Connect if we have no connection */
+  if (ups->conn == NULL)
+  {
+    ups->conn = (collectd_upsconn_t *) malloc (sizeof (collectd_upsconn_t));
+    if (ups->conn == NULL)
+    {
+      ERROR ("nut plugin: malloc failed.");
+      return (-1);
+    }
+
+    status = upscli_connect (ups->conn, ups->hostname, ups->port,
+       UPSCLI_CONN_TRYSSL);
+    if (status != 0)
+    {
+      ERROR ("nut plugin: nut_read_one: upscli_connect (%s, %i) failed: %s",
+         ups->hostname, ups->port, upscli_strerror (ups->conn));
+      sfree (ups->conn);
+      return (-1);
+    }
+
+    INFO ("nut plugin: Connection to (%s, %i) established.",
+       ups->hostname, ups->port);
+  } /* if (ups->conn == NULL) */
+
+  /* nut plugin: nut_read_one: upscli_list_start (adpos) failed: Protocol
+   * error */
+  status = upscli_list_start (ups->conn, query_num, query);
+  if (status != 0)
+  {
+    ERROR ("nut plugin: nut_read_one: upscli_list_start (%s) failed: %s",
+       ups->upsname, upscli_strerror (ups->conn));
+    upscli_disconnect (ups->conn);
+    sfree (ups->conn);
+    return (-1);
+  }
+
+  while ((status = upscli_list_next (ups->conn, query_num, query,
+         &answer_num, &answer)) == 1)
+  {
+    char  *key;
+    double value;
+
+    if (answer_num < 4)
+      continue;
+
+    key = answer[2];
+    value = atof (answer[3]);
+
+    if (strncmp ("ambient.", key, 8) == 0)
+    {
+      if (strcmp ("ambient.humidity", key) == 0)
+       nut_submit (ups, "humidity", "ambient", value);
+      else if (strcmp ("ambient.temperature", key) == 0)
+       nut_submit (ups, "temperature", "ambient", value);
+    }
+    else if (strncmp ("battery.", key, 8) == 0)
+    {
+      if (strcmp ("battery.charge", key) == 0)
+       nut_submit (ups, "percent", "charge", value);
+      else if (strcmp ("battery.current", key) == 0)
+       nut_submit (ups, "current", "battery", value);
+      else if (strcmp ("battery.runtime", key) == 0)
+       nut_submit (ups, "timeleft", "battery", value);
+      else if (strcmp ("battery.temperature", key) == 0)
+       nut_submit (ups, "temperature", "battery", value);
+      else if (strcmp ("battery.voltage", key) == 0)
+       nut_submit (ups, "voltage", "battery", value);
+    }
+    else if (strncmp ("input.", key, 6) == 0)
+    {
+      if (strcmp ("input.frequency", key) == 0)
+       nut_submit (ups, "frequency", "input", value);
+      else if (strcmp ("input.voltage", key) == 0)
+       nut_submit (ups, "voltage", "input", value);
+    }
+    else if (strncmp ("output.", key, 7) == 0)
+    {
+      if (strcmp ("output.current", key) == 0)
+       nut_submit (ups, "current", "output", value);
+      else if (strcmp ("output.frequency", key) == 0)
+       nut_submit (ups, "frequency", "output", value);
+      else if (strcmp ("output.voltage", key) == 0)
+       nut_submit (ups, "voltage", "output", value);
+    }
+    else if (strncmp ("ups.", key, 4) == 0)
+    {
+      if (strcmp ("ups.load", key) == 0)
+       nut_submit (ups, "percent", "load", value);
+      else if (strcmp ("ups.power", key) == 0)
+       nut_submit (ups, "power", "ups", value);
+      else if (strcmp ("ups.temperature", key) == 0)
+       nut_submit (ups, "temperature", "ups", value);
+    }
+  } /* while (upscli_list_next) */
+
+  return (0);
+} /* int nut_read_one */
+
+static int nut_read (void)
+{
+  nut_ups_t *ups;
+  int success = 0;
+
+  pthread_mutex_lock (&read_lock);
+  success = read_busy;
+  read_busy = 1;
+  pthread_mutex_unlock (&read_lock);
+
+  if (success != 0)
+    return (0);
+
+  for (ups = upslist_head; ups != NULL; ups = ups->next)
+    if (nut_read_one (ups) == 0)
+      success++;
+
+  pthread_mutex_lock (&read_lock);
+  read_busy = 0;
+  pthread_mutex_unlock (&read_lock);
+
+  return ((success != 0) ? 0 : -1);
+} /* int nut_read */
+
+static int nut_shutdown (void)
+{
+  nut_ups_t *this;
+  nut_ups_t *next;
+
+  this = upslist_head;
+  while (this != NULL)
+  {
+    next = this->next;
+    free_nut_ups_t (this);
+    this = next;
+  }
+
+  return (0);
+} /* int nut_shutdown */
+
+void module_register (void)
+{
+  plugin_register_config ("nut", nut_config, config_keys, config_keys_num);
+  plugin_register_read ("nut", nut_read);
+  plugin_register_shutdown ("nut", nut_shutdown);
+} /* void module_register */
+
+/* vim: set sw=2 ts=8 sts=2 tw=78 : */
diff --git a/src/olsrd.c b/src/olsrd.c
new file mode 100644 (file)
index 0000000..be422ab
--- /dev/null
@@ -0,0 +1,709 @@
+/**
+ * collectd - src/olsrd.c
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include <sys/types.h>
+#include <netdb.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#define OLSRD_DEFAULT_NODE "localhost"
+#define OLSRD_DEFAULT_SERVICE "2006"
+
+static const char *config_keys[] =
+{
+  "Host",
+  "Port",
+  "CollectLinks",
+  "CollectRoutes",
+  "CollectTopology"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static char *config_node = NULL;
+static char *config_service = NULL;
+
+#define OLSRD_WANT_NOT     0
+#define OLSRD_WANT_SUMMARY 1
+#define OLSRD_WANT_DETAIL  2
+static int config_want_links    = OLSRD_WANT_DETAIL;
+static int config_want_routes   = OLSRD_WANT_SUMMARY;
+static int config_want_topology = OLSRD_WANT_SUMMARY;
+
+static const char *olsrd_get_node (void) /* {{{ */
+{
+  if (config_node != NULL)
+    return (config_node);
+  return (OLSRD_DEFAULT_NODE);
+} /* }}} const char *olsrd_get_node */
+
+static const char *olsrd_get_service (void) /* {{{ */
+{
+  if (config_service != NULL)
+    return (config_service);
+  return (OLSRD_DEFAULT_SERVICE);
+} /* }}} const char *olsrd_get_service */
+
+static void olsrd_set_node (const char *node) /* {{{ */
+{
+  char *tmp;
+  if (node == NULL)
+    return;
+  tmp = strdup (node);
+  if (tmp == NULL)
+    return;
+  config_node = tmp;
+} /* }}} void olsrd_set_node */
+
+static void olsrd_set_service (const char *service) /* {{{ */
+{
+  char *tmp;
+  if (service == NULL)
+    return;
+  tmp = strdup (service);
+  if (tmp == NULL)
+    return;
+  config_service = tmp;
+} /* }}} void olsrd_set_service */
+
+static void olsrd_set_detail (int *varptr, const char *detail, /* {{{ */
+    const char *key)
+{
+  if (strcasecmp ("No", detail) == 0)
+    *varptr = OLSRD_WANT_NOT;
+  else if (strcasecmp ("Summary", detail) == 0)
+    *varptr = OLSRD_WANT_SUMMARY;
+  else if (strcasecmp ("Detail", detail) == 0)
+    *varptr = OLSRD_WANT_DETAIL;
+  else
+  {
+    ERROR ("olsrd plugin: Invalid argument given to the `%s' configuration "
+        "option: `%s'. Expected: `No', `Summary', or `Detail'.",
+        key, detail);
+  }
+} /* }}} void olsrd_set_detail */
+
+/* Strip trailing newline characters. Returns length of string. */
+static size_t strchomp (char *buffer) /* {{{ */
+{
+  size_t buffer_len;
+
+  buffer_len = strlen (buffer);
+  while ((buffer_len > 0)
+      && ((buffer[buffer_len - 1] == '\r')
+        || (buffer[buffer_len - 1] == '\n')))
+  {
+    buffer_len--;
+    buffer[buffer_len] = 0;
+  }
+
+  return (buffer_len);
+} /* }}} size_t strchomp */
+
+static size_t strtabsplit (char *string, char **fields, size_t size) /* {{{ */
+{
+  size_t i;
+  char *ptr;
+  char *saveptr;
+
+  i = 0;
+  ptr = string;
+  saveptr = NULL;
+  while ((fields[i] = strtok_r (ptr, " \t\r\n", &saveptr)) != NULL)
+  {
+    ptr = NULL;
+    i++;
+
+    if (i >= size)
+      break;
+  }
+
+  return (i);
+} /* }}} size_t strtabsplit */
+
+static FILE *olsrd_connect (void) /* {{{ */
+{
+  struct addrinfo  ai_hints;
+  struct addrinfo *ai_list, *ai_ptr;
+  int              ai_return;
+
+  FILE *fh;
+
+  memset (&ai_hints, 0, sizeof (ai_hints));
+  ai_hints.ai_flags    = 0;
+#ifdef AI_ADDRCONFIG
+  ai_hints.ai_flags   |= AI_ADDRCONFIG;
+#endif
+  ai_hints.ai_family   = PF_UNSPEC;
+  ai_hints.ai_socktype = SOCK_STREAM;
+  ai_hints.ai_protocol = IPPROTO_TCP;
+
+  ai_list = NULL;
+  ai_return = getaddrinfo (olsrd_get_node (), olsrd_get_service (),
+      &ai_hints, &ai_list);
+  if (ai_return != 0)
+  {
+    ERROR ("olsrd plugin: getaddrinfo (%s, %s) failed: %s",
+        olsrd_get_node (), olsrd_get_service (),
+        gai_strerror (ai_return));
+    return (NULL);
+  }
+
+  fh = NULL;
+  for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
+  {
+    int fd;
+    int status;
+    char errbuf[1024];
+
+    fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol);
+    if (fd < 0)
+    {
+      ERROR ("olsrd plugin: socket failed: %s",
+          sstrerror (errno, errbuf, sizeof (errbuf)));
+      continue;
+    }
+
+    status = connect (fd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
+    if (status != 0)
+    {
+      ERROR ("olsrd plugin: connect failed: %s",
+          sstrerror (errno, errbuf, sizeof (errbuf)));
+      close (fd);
+      continue;
+    }
+
+    fh = fdopen (fd, "r+");
+    if (fh == NULL)
+    {
+      ERROR ("olsrd plugin: fdopen failed.");
+      close (fd);
+      continue;
+    }
+
+    break;
+  } /* for (ai_ptr) */
+
+  freeaddrinfo (ai_list);
+
+  return (fh);
+} /* }}} FILE *olsrd_connect */
+
+__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_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));
+  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);
+} /* }}} void olsrd_submit */
+
+static int olsrd_cb_ignore (int lineno, /* {{{ */
+    size_t fields_num, char **fields)
+{
+  return (0);
+} /* }}} int olsrd_cb_ignore */
+
+static int olsrd_cb_links (int lineno, /* {{{ */
+    size_t fields_num, char **fields)
+{
+  /* Fields:
+   *  0 = Local IP
+   *  1 = Remote IP
+   *  2 = Hyst.
+   *  3 = LQ
+   *  4 = NLQ
+   *  5 = Cost */
+
+  static uint32_t links_num;
+  static double    lq_sum;
+  static uint32_t  lq_num;
+  static double   nlq_sum;
+  static uint32_t nlq_num;
+
+  double lq;
+  double nlq;
+
+  char *endptr;
+
+  if (config_want_links == OLSRD_WANT_NOT)
+    return (0);
+
+  /* Special handling of the first line. */
+  if (lineno <= 0)
+  {
+    links_num = 0;
+    lq_sum = 0.0;
+    lq_num = 0;
+    nlq_sum = 0.0;
+    nlq_num = 0;
+
+    return (0);
+  }
+
+  /* Special handling of the last line. */
+  if (fields_num == 0)
+  {
+    DEBUG ("olsrd plugin: Number of links: %"PRIu32, links_num);
+    olsrd_submit (/* p.-inst = */ "links", /* type = */ "links",
+        /* t.-inst = */ NULL, (gauge_t) links_num);
+
+    lq = NAN;
+    if (lq_num > 0)
+      lq = lq_sum / ((double) lq_num);
+    DEBUG ("olsrd plugin: Average  LQ: %g", lq);
+    olsrd_submit (/* p.-inst = */ "links", /* type = */ "signal_quality",
+        "average-lq", lq);
+
+    nlq = NAN;
+    if (nlq_num > 0)
+      nlq = nlq_sum / ((double) nlq_num);
+    DEBUG ("olsrd plugin: Average NLQ: %g", nlq);
+    olsrd_submit (/* p.-inst = */ "links", /* type = */ "signal_quality",
+        "average-nlq", nlq);
+
+    return (0);
+  }
+
+  if (fields_num != 6)
+    return (-1);
+
+  links_num++;
+
+  errno = 0;
+  endptr = NULL;
+  lq = strtod (fields[3], &endptr);
+  if ((errno != 0) || (endptr == fields[3]))
+  {
+    ERROR ("olsrd plugin: Cannot parse link quality: %s", fields[3]);
+  }
+  else
+  {
+    if (!isnan (lq))
+    {
+      lq_sum += lq;
+      lq_num++;
+    }
+
+    if (config_want_links == OLSRD_WANT_DETAIL)
+    {
+      char type_instance[DATA_MAX_NAME_LEN];
+
+      ssnprintf (type_instance, sizeof (type_instance), "%s-%s-lq",
+          fields[0], fields[1]);
+
+      DEBUG ("olsrd plugin: links: type_instance = %s;  lq = %g;",
+          type_instance, lq);
+      olsrd_submit (/* p.-inst = */ "links", /* type = */ "signal_quality",
+          type_instance, lq);
+    }
+  }
+
+  errno = 0;
+  endptr = NULL;
+  nlq = strtod (fields[4], &endptr);
+  if ((errno != 0) || (endptr == fields[4]))
+  {
+    ERROR ("olsrd plugin: Cannot parse neighbor link quality: %s", fields[4]);
+  }
+  else
+  {
+    if (!isnan (nlq))
+    {
+      nlq_sum += nlq;
+      nlq_num++;
+    }
+
+    if (config_want_links == OLSRD_WANT_DETAIL)
+    {
+      char type_instance[DATA_MAX_NAME_LEN];
+
+      ssnprintf (type_instance, sizeof (type_instance), "%s-%s-rx",
+          fields[0], fields[1]);
+
+      DEBUG ("olsrd plugin: links: type_instance = %s; nlq = %g;",
+          type_instance, lq);
+      olsrd_submit (/* p.-inst = */ "links", /* type = */ "signal_quality",
+          type_instance, nlq);
+    }
+  }
+
+  return (0);
+} /* }}} int olsrd_cb_links */
+
+static int olsrd_cb_routes (int lineno, /* {{{ */
+    size_t fields_num, char **fields)
+{
+  /* Fields:
+   *  0 = Destination
+   *  1 = Gateway IP
+   *  2 = Metric
+   *  3 = ETX
+   *  4 = Interface */
+
+  static uint32_t routes_num;
+  static uint32_t metric_sum;
+  static uint32_t metric_num;
+  static double   etx_sum;
+  static uint32_t etx_num;
+
+  uint32_t metric;
+  double etx;
+  char *endptr;
+
+  if (config_want_routes == OLSRD_WANT_NOT)
+    return (0);
+
+  /* Special handling of the first line */
+  if (lineno <= 0)
+  {
+    routes_num = 0;
+    metric_num = 0;
+    metric_sum = 0;
+    etx_sum = 0.0;
+    etx_num = 0;
+
+    return (0);
+  }
+
+  /* Special handling after the last line */
+  if (fields_num == 0)
+  {
+    double metric_avg;
+
+    DEBUG ("olsrd plugin: Number of routes: %"PRIu32, routes_num);
+    olsrd_submit (/* p.-inst = */ "routes", /* type = */ "routes",
+        /* t.-inst = */ NULL, (gauge_t) routes_num);
+
+    metric_avg = NAN;
+    if (metric_num > 0)
+      metric_avg = ((double) metric_sum) / ((double) metric_num);
+    DEBUG ("olsrd plugin: Average metric: %g", metric_avg);
+    olsrd_submit (/* p.-inst = */ "routes", /* type = */ "route_metric",
+        "average", metric_avg);
+
+    etx = NAN;
+    if (etx_num > 0)
+      etx = etx_sum / ((double) etx_sum);
+    DEBUG ("olsrd plugin: Average ETX: %g", etx);
+    olsrd_submit (/* p.-inst = */ "routes", /* type = */ "route_etx",
+        "average", etx);
+
+    return (0);
+  }
+
+  if (fields_num != 5)
+    return (-1);
+
+  routes_num++;
+
+  errno = 0;
+  endptr = NULL;
+  metric = (uint32_t) strtoul (fields[2], &endptr, 0);
+  if ((errno != 0) || (endptr == fields[2]))
+  {
+    ERROR ("olsrd plugin: Unable to parse metric: %s", fields[2]);
+  }
+  else
+  {
+    metric_num++;
+    metric_sum += metric;
+
+    if (config_want_routes == OLSRD_WANT_DETAIL)
+    {
+      DEBUG ("olsrd plugin: destination = %s; metric = %"PRIu32";",
+          fields[0], metric);
+      olsrd_submit (/* p.-inst = */ "routes", /* type = */ "route_metric",
+          /* t.-inst = */ fields[0], (gauge_t) metric);
+    }
+  }
+
+  errno = 0;
+  endptr = NULL;
+  etx = strtod (fields[3], &endptr);
+  if ((errno != 0) || (endptr == fields[3]))
+  {
+    ERROR ("olsrd plugin: Unable to parse ETX: %s", fields[3]);
+  }
+  else
+  {
+    if (!isnan (etx))
+    {
+      etx_sum += etx;
+      etx_num++;
+    }
+
+    if (config_want_routes == OLSRD_WANT_DETAIL)
+    {
+      DEBUG ("olsrd plugin: destination = %s; etx = %g;",
+          fields[0], etx);
+      olsrd_submit (/* p.-inst = */ "routes", /* type = */ "route_etx",
+          /* t.-inst = */ fields[0], etx);
+    }
+  }
+
+  return (0);
+} /* }}} int olsrd_cb_routes */
+
+static int olsrd_cb_topology (int lineno, /* {{{ */
+    size_t fields_num, char **fields)
+{
+  /* Fields:
+   *  0 = Dest. IP
+   *  1 = Last hop IP
+   *  2 = LQ
+   *  3 = NLQ
+   *  4 = Cost */
+
+  static double   lq_sum;
+  static uint32_t lq_num;
+
+  static uint32_t links_num;
+
+  double lq;
+  char *endptr;
+
+  if (config_want_topology == OLSRD_WANT_NOT)
+    return (0);
+
+  /* Special handling of the first line */
+  if (lineno <= 0)
+  {
+    lq_sum = 0.0;
+    lq_num = 0;
+    links_num = 0;
+
+    return (0);
+  }
+
+  /* Special handling after the last line */
+  if (fields_num == 0)
+  {
+    DEBUG ("olsrd plugin: topology: Number of links: %"PRIu32, links_num);
+    olsrd_submit (/* p.-inst = */ "topology", /* type = */ "links",
+        /* t.-inst = */ NULL, (gauge_t) links_num);
+
+    lq = NAN;
+    if (lq_num > 0)
+      lq = lq_sum / ((double) lq_sum);
+    DEBUG ("olsrd plugin: topology: Average link quality: %g", lq);
+    olsrd_submit (/* p.-inst = */ "topology", /* type = */ "signal_quality",
+        /* t.-inst = */ "average", lq);
+
+    return (0);
+  }
+
+  if (fields_num != 5)
+    return (-1);
+
+  links_num++;
+
+  errno = 0;
+  endptr = NULL;
+  lq = strtod (fields[2], &endptr);
+  if ((errno != 0) || (endptr == fields[2]))
+  {
+    ERROR ("olsrd plugin: Unable to parse LQ: %s", fields[2]);
+  }
+  else
+  {
+    if (!isnan (lq))
+    {
+      lq_sum += lq;
+      lq_num++;
+    }
+
+    if (config_want_topology == OLSRD_WANT_DETAIL)
+    {
+      char type_instance[DATA_MAX_NAME_LEN];
+
+      memset (type_instance, 0, sizeof (type_instance));
+      ssnprintf (type_instance, sizeof (type_instance), "%s-%s-lq",
+          fields[0], fields[1]);
+      DEBUG ("olsrd plugin: type_instance = %s; lq = %g;", type_instance, lq);
+      olsrd_submit (/* p.-inst = */ "topology", /* type = */ "signal_quality",
+          type_instance, lq);
+    }
+  }
+
+  if (config_want_topology == OLSRD_WANT_DETAIL)
+  {
+    double nlq;
+
+    errno = 0;
+    endptr = NULL;
+    nlq = strtod (fields[3], &endptr);
+    if ((errno != 0) || (endptr == fields[3]))
+    {
+      ERROR ("olsrd plugin: Unable to parse NLQ: %s", fields[3]);
+    }
+    else
+    {
+      char type_instance[DATA_MAX_NAME_LEN];
+
+      memset (type_instance, 0, sizeof (type_instance));
+      ssnprintf (type_instance, sizeof (type_instance), "%s-%s-nlq",
+          fields[0], fields[1]);
+      DEBUG ("olsrd plugin: type_instance = %s; nlq = %g;", type_instance, nlq);
+      olsrd_submit (/* p.-inst = */ "topology", /* type = */ "signal_quality",
+          type_instance, nlq);
+    }
+  }
+
+  return (0);
+} /* }}} int olsrd_cb_topology */
+
+static int olsrd_read_table (FILE *fh, /* {{{ */
+    int (*callback) (int lineno, size_t fields_num, char **fields))
+{
+  char buffer[1024];
+  size_t buffer_len;
+
+  char *fields[32];
+  size_t fields_num;
+
+  int lineno;
+
+  lineno = 0;
+  while (fgets (buffer, sizeof (buffer), fh) != NULL)
+  {
+    /* An empty line ends the table. */
+    buffer_len = strchomp (buffer);
+    if (buffer_len <= 0)
+    {
+      (*callback) (lineno, /* fields_num = */ 0, /* fields = */ NULL);
+      break;
+    }
+
+    fields_num = strtabsplit (buffer, fields, STATIC_ARRAY_SIZE (fields));
+
+    (*callback) (lineno, fields_num, fields);
+    lineno++;
+  } /* while (fgets) */
+  
+  return (0);
+} /* }}} int olsrd_read_table */
+
+static int olsrd_config (const char *key, const char *value) /* {{{ */
+{
+  if (strcasecmp ("Host", key) == 0)
+    olsrd_set_node (value);
+  else if (strcasecmp ("Port", key) == 0)
+    olsrd_set_service (value);
+  else if (strcasecmp ("CollectLinks", key) == 0)
+    olsrd_set_detail (&config_want_links, value, key);
+  else if (strcasecmp ("CollectRoutes", key) == 0)
+    olsrd_set_detail (&config_want_routes, value, key);
+  else if (strcasecmp ("CollectTopology", key) == 0)
+    olsrd_set_detail (&config_want_topology, value, key);
+  else
+  {
+    ERROR ("olsrd plugin: Unknown configuration option given: %s", key);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int olsrd_config */
+
+static int olsrd_read (void) /* {{{ */
+{
+  FILE *fh;
+  char buffer[1024];
+  size_t buffer_len;
+
+  fh = olsrd_connect ();
+  if (fh == NULL)
+    return (-1);
+
+  fputs ("\r\n", fh);
+  fflush (fh);
+
+  while (fgets (buffer, sizeof (buffer), fh) != NULL)
+  {
+    buffer_len = strchomp (buffer);
+    if (buffer_len <= 0)
+      continue;
+    
+    if (strcmp ("Table: Links", buffer) == 0)
+      olsrd_read_table (fh, olsrd_cb_links);
+    else if (strcmp ("Table: Neighbors", buffer) == 0)
+      olsrd_read_table (fh, olsrd_cb_ignore);
+    else if (strcmp ("Table: Topology", buffer) == 0)
+      olsrd_read_table (fh, olsrd_cb_topology);
+    else if (strcmp ("Table: HNA", buffer) == 0)
+      olsrd_read_table (fh, olsrd_cb_ignore);
+    else if (strcmp ("Table: MID", buffer) == 0)
+      olsrd_read_table (fh, olsrd_cb_ignore);
+    else if (strcmp ("Table: Routes", buffer) == 0)
+      olsrd_read_table (fh, olsrd_cb_routes);
+    else if ((strcmp ("HTTP/1.0 200 OK", buffer) == 0)
+        || (strcmp ("Content-type: text/plain", buffer) == 0))
+    {
+      /* ignore */
+    }
+    else
+    {
+      DEBUG ("olsrd plugin: Unable to handle line: %s", buffer);
+    }
+  } /* while (fgets) */
+
+  fclose (fh);
+  
+  return (0);
+} /* }}} int olsrd_read */
+
+static int olsrd_shutdown (void) /* {{{ */
+{
+  sfree (config_node);
+  sfree (config_service);
+
+  return (0);
+} /* }}} int olsrd_shutdown */
+
+void module_register (void)
+{
+  plugin_register_config ("olsrd", olsrd_config,
+      config_keys, config_keys_num);
+  plugin_register_read ("olsrd", olsrd_read);
+  plugin_register_shutdown ("olsrd", olsrd_shutdown);
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/onewire.c b/src/onewire.c
new file mode 100644 (file)
index 0000000..09a6bf0
--- /dev/null
@@ -0,0 +1,326 @@
+/**
+ * collectd - src/owfs.c
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at noris.net>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "utils_ignorelist.h"
+
+#include <owcapi.h>
+
+#define OW_FAMILY_LENGTH 8
+#define OW_FAMILY_MAX_FEATURES 2
+struct ow_family_features_s
+{
+  char family[OW_FAMILY_LENGTH];
+  struct
+  {
+    char filename[DATA_MAX_NAME_LEN];
+    char type[DATA_MAX_NAME_LEN];
+    char type_instance[DATA_MAX_NAME_LEN];
+  } features[OW_FAMILY_MAX_FEATURES];
+  size_t features_num;
+};
+typedef struct ow_family_features_s ow_family_features_t;
+
+/* see http://owfs.sourceforge.net/ow_table.html for a list of families */
+static ow_family_features_t ow_family_features[] =
+{
+  {
+    /* family = */ "10.",
+    {
+      {
+        /* filename = */ "temperature",
+        /* type = */ "temperature",
+        /* type_instance = */ ""
+      }
+    },
+    /* features_num = */ 1
+  }
+};
+static int ow_family_features_num = STATIC_ARRAY_SIZE (ow_family_features);
+
+static char *device_g = NULL;
+static cdtime_t ow_interval = 0;
+
+static const char *config_keys[] =
+{
+  "Device",
+  "IgnoreSelected",
+  "Sensor",
+  "Interval"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static ignorelist_t *sensor_list;
+
+static int cow_load_config (const char *key, const char *value)
+{
+  if (sensor_list == NULL)
+    sensor_list = ignorelist_create (1);
+
+  if (strcasecmp (key, "Sensor") == 0)
+  {
+    if (ignorelist_add (sensor_list, value))
+    {
+      ERROR ("sensors plugin: "
+          "Cannot add value to ignorelist.");
+      return (1);
+    }
+  }
+  else if (strcasecmp (key, "IgnoreSelected") == 0)
+  {
+    ignorelist_set_invert (sensor_list, 1);
+    if (IS_TRUE (value))
+      ignorelist_set_invert (sensor_list, 0);
+  }
+  else if (strcasecmp (key, "Device") == 0)
+  {
+    char *temp;
+    temp = strdup (value);
+    if (temp == NULL)
+    {
+      ERROR ("onewire plugin: strdup failed.");
+      return (1);
+    }
+    sfree (device_g);
+    device_g = temp;
+  }
+  else if (strcasecmp ("Interval", key) == 0)
+  {
+    double tmp;
+    tmp = atof (value);
+    if (tmp > 0.0)
+      ow_interval = DOUBLE_TO_CDTIME_T (tmp);
+    else
+      ERROR ("onewire plugin: Invalid `Interval' setting: %s", value);
+  }
+  else
+  {
+    return (-1);
+  }
+
+  return (0);
+}
+
+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;
+  size_t i;
+
+  if (sensor_list != NULL)
+  {
+    DEBUG ("onewire plugin: Checking ignorelist for `%s'", name);
+    if (ignorelist_match (sensor_list, name) != 0)
+      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));
+
+  for (i = 0; i < family_info->features_num; i++)
+  {
+    char *buffer;
+    size_t buffer_size;
+    int status;
+
+    char file[4096];
+    char *endptr;
+
+    snprintf (file, sizeof (file), "%s/%s",
+        path, family_info->features[i].filename);
+    file[sizeof (file) - 1] = 0;
+
+    buffer = NULL;
+    buffer_size = 0;
+    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);
+      return (-1);
+    }
+
+    endptr = NULL;
+    values[0].gauge = strtod (buffer, &endptr);
+    if (endptr == NULL)
+    {
+      ERROR ("onewire plugin: Buffer is not a number: %s", buffer);
+      status = -1;
+      continue;
+    }
+
+    sstrncpy (vl.type, family_info->features[i].type, sizeof (vl.type));
+    sstrncpy (vl.type_instance, family_info->features[i].type_instance,
+        sizeof (vl.type_instance));
+
+    plugin_dispatch_values (&vl);
+    success++;
+
+    free (buffer);
+  } /* for (i = 0; i < features_num; i++) */
+
+  return ((success > 0) ? 0 : -1);
+} /* int cow_read_values */
+
+/* Forward declaration so the recursion below works */
+static int cow_read_bus (const char *path);
+
+/*
+ * cow_read_ds2409
+ *
+ * Handles:
+ * - DS2409 - MicroLAN Coupler
+ */
+static int cow_read_ds2409 (const char *path)
+{
+  char subpath[4096];
+  int status;
+
+  status = ssnprintf (subpath, sizeof (subpath), "%s/main", path);
+  if ((status > 0) && (status < sizeof (subpath)))
+    cow_read_bus (subpath);
+
+  status = ssnprintf (subpath, sizeof (subpath), "%s/aux", path);
+  if ((status > 0) && (status < sizeof (subpath)))
+    cow_read_bus (subpath);
+
+  return (0);
+} /* int cow_read_ds2409 */
+
+static int cow_read_bus (const char *path)
+{
+  char *buffer;
+  size_t buffer_size;
+  int status;
+
+  char *buffer_ptr;
+  char *dummy;
+  char *saveptr;
+  char subpath[4096];
+
+  status = OW_get (path, &buffer, &buffer_size);
+  if (status < 0)
+  {
+    ERROR ("onewire plugin: OW_get (%s) failed. status = %#x;",
+        path, status);
+    return (-1);
+  }
+  DEBUG ("onewire plugin: OW_get (%s) returned: %s",
+      path, buffer);
+
+  dummy = buffer;
+  saveptr = NULL;
+  while ((buffer_ptr = strtok_r (dummy, ",/", &saveptr)) != NULL)
+  {
+    int i;
+
+    dummy = NULL;
+
+    if (strcmp ("/", path) == 0)
+      status = ssnprintf (subpath, sizeof (subpath), "/%s", buffer_ptr);
+    else
+      status = ssnprintf (subpath, sizeof (subpath), "%s/%s",
+          path, buffer_ptr);
+    if ((status <= 0) || (status >= sizeof (subpath)))
+      continue;
+
+    for (i = 0; i < ow_family_features_num; i++)
+    {
+      if (strncmp (ow_family_features[i].family, buffer_ptr,
+            strlen (ow_family_features[i].family)) != 0)
+        continue;
+
+      cow_read_values (subpath,
+          buffer_ptr + strlen (ow_family_features[i].family),
+          ow_family_features + i);
+      break;
+    }
+    if (i < ow_family_features_num)
+      continue;
+
+    /* DS2409 */
+    if (strncmp ("1F.", buffer_ptr, strlen ("1F.")) == 0)
+    {
+      cow_read_ds2409 (subpath);
+      continue;
+    }
+  } /* while (strtok_r) */
+
+  free (buffer);
+  return (0);
+} /* int cow_read_bus */
+
+static int cow_read (user_data_t *ud __attribute__((unused)))
+{
+  return (cow_read_bus ("/"));
+} /* int cow_read */
+
+static int cow_shutdown (void)
+{
+  OW_finish ();
+  ignorelist_free (sensor_list);
+  return (0);
+} /* int cow_shutdown */
+
+static int cow_init (void)
+{
+  int status;
+  struct timespec cb_interval;
+
+  if (device_g == NULL)
+  {
+    ERROR ("onewire plugin: cow_init: No device configured.");
+    return (-1);
+  }
+
+  status = (int) OW_init (device_g);
+  if (status != 0)
+  {
+    ERROR ("onewire plugin: OW_init(%s) failed: %i.", device_g, status);
+    return (1);
+  }
+
+  CDTIME_T_TO_TIMESPEC (ow_interval, &cb_interval);
+
+  plugin_register_complex_read (/* group = */ NULL, "onewire", cow_read,
+      (ow_interval != 0) ? &cb_interval : NULL,
+      /* user data = */ NULL);
+  plugin_register_shutdown ("onewire", cow_shutdown);
+
+  return (0);
+} /* int cow_init */
+
+void module_register (void)
+{
+  plugin_register_init ("onewire", cow_init);
+  plugin_register_config ("onewire", cow_load_config,
+    config_keys, config_keys_num);
+}
+
+/* vim: set sw=2 sts=2 ts=8 et fdm=marker cindent : */
diff --git a/src/openvpn.c b/src/openvpn.c
new file mode 100644 (file)
index 0000000..9ce23b4
--- /dev/null
@@ -0,0 +1,729 @@
+/**
+ * collectd - src/openvpn.c
+ * Copyright (C) 2008       Doug MacEachern
+ * Copyright (C) 2009,2010  Florian octo Forster
+ * Copyright (C) 2009       Marco Chiappero
+ * Copyright (C) 2009       Fabian Schuh
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Doug MacEachern <dougm at hyperic.com>
+ *   Florian octo Forster <octo at collectd.org>
+ *   Marco Chiappero <marco at absence.it>
+ *   Fabian Schuh <mail at xeroc.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#define V1STRING "Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since\n"
+#define V2STRING "HEADER,CLIENT_LIST,Common Name,Real Address,Virtual Address,Bytes Received,Bytes Sent,Connected Since,Connected Since (time_t)\n"
+#define V3STRING "HEADER CLIENT_LIST Common Name Real Address Virtual Address Bytes Received Bytes Sent Connected Since Connected Since (time_t)\n"
+#define VSSTRING "OpenVPN STATISTICS\n"
+
+
+struct vpn_status_s
+{
+       char *file;
+       enum
+       {
+               MULTI1 = 1, /* status-version 1 */
+               MULTI2,     /* status-version 2 */
+               MULTI3,     /* status-version 3 */
+               SINGLE = 10 /* currently no versions for single mode, maybe in the future */
+       } version;
+       char *name;
+};
+typedef struct vpn_status_s vpn_status_t;
+
+static vpn_status_t **vpn_list = NULL;
+static int vpn_num = 0;
+
+static _Bool new_naming_schema = 0;
+static _Bool collect_compression = 1;
+static _Bool collect_user_count  = 0;
+static _Bool collect_individual_users  = 1;
+
+static const char *config_keys[] =
+{
+       "StatusFile",
+       "Compression", /* old, deprecated name */
+       "ImprovedNamingSchema",
+       "CollectCompression",
+       "CollectUserCount",
+       "CollectIndividualUsers"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+
+/* Helper function
+ * copy-n-pasted from common.c - changed delim to ","  */
+static int openvpn_strsplit (char *string, char **fields, size_t size)
+{
+       size_t i;
+       char *ptr;
+       char *saveptr;
+
+       i = 0;
+       ptr = string;
+       saveptr = NULL;
+       while ((fields[i] = strtok_r (ptr, ",", &saveptr)) != NULL)
+       {
+               ptr = NULL;
+               i++;
+
+               if (i >= size)
+                       break;
+       }
+
+       return (i);
+} /* int openvpn_strsplit */
+
+/* dispatches number of users */
+static void numusers_submit (char *pinst, 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));
+       sstrncpy (vl.plugin, "openvpn", sizeof (vl.plugin));
+       sstrncpy (vl.type, "users", sizeof (vl.type));
+       if (pinst != NULL)
+               sstrncpy (vl.plugin_instance, pinst, sizeof (vl.plugin_instance));
+       if (tinst != NULL)
+               sstrncpy (vl.type_instance, tinst, sizeof (vl.type_instance));
+
+       plugin_dispatch_values (&vl);
+} /* void numusers_submit */
+
+/* dispatches stats about traffic (TCP or UDP) generated by the tunnel per single endpoint */
+static void iostats_submit (char *pinst, 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;
+
+       /* NOTE ON THE NEW NAMING SCHEMA:
+        *       using plugin_instance to identify each vpn config (and
+        *       status) file; using type_instance to identify the endpoint
+        *       host when in multimode, traffic or overhead when in single.
+        */
+
+       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));
+       sstrncpy (vl.type, "if_octets", sizeof (vl.type));
+       if (tinst != NULL)
+               sstrncpy (vl.type_instance, tinst, sizeof (vl.type_instance));
+
+       plugin_dispatch_values (&vl);
+} /* void traffic_submit */
+
+/* dispatches stats about data compression shown when in single mode */
+static void compression_submit (char *pinst, 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;
+
+       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));
+       sstrncpy (vl.type, "compression", sizeof (vl.type));
+       if (tinst != NULL)
+               sstrncpy (vl.type_instance, tinst, sizeof (vl.type_instance));
+
+       plugin_dispatch_values (&vl);
+} /* void compression_submit */
+
+static int single_read (char *name, FILE *fh)
+{
+       char buffer[1024];
+       char *fields[4];
+       const int max_fields = STATIC_ARRAY_SIZE (fields);
+       int  fields_num, read = 0;
+
+       derive_t link_rx, link_tx;
+       derive_t tun_rx, tun_tx;
+       derive_t pre_compress, post_compress;
+       derive_t pre_decompress, post_decompress;
+       derive_t overhead_rx, overhead_tx;
+
+       link_rx = 0;
+       link_tx = 0;
+       tun_rx = 0;
+       tun_tx = 0;
+       pre_compress = 0;
+       post_compress = 0;
+       pre_decompress = 0;
+       post_decompress = 0;
+       overhead_rx = 0;
+       overhead_tx = 0;
+
+       while (fgets (buffer, sizeof (buffer), fh) != NULL)
+       {
+               fields_num = openvpn_strsplit (buffer, fields, max_fields);
+
+               /* status file is generated by openvpn/sig.c:print_status()
+                * http://svn.openvpn.net/projects/openvpn/trunk/openvpn/sig.c
+                *
+                * The line we're expecting has 2 fields. We ignore all lines
+                *  with more or less fields.
+                */
+               if (fields_num != 2)
+               {
+                       continue;
+               }
+
+               if (strcmp (fields[0], "TUN/TAP read bytes") == 0)
+               {
+                       /* read from the system and sent over the tunnel */
+                       tun_tx = atoll (fields[1]);
+               }
+               else if (strcmp (fields[0], "TUN/TAP write bytes") == 0)
+               {
+                       /* read from the tunnel and written in the system */
+                       tun_rx = atoll (fields[1]);
+               }
+               else if (strcmp (fields[0], "TCP/UDP read bytes") == 0)
+               {
+                       link_rx = atoll (fields[1]);
+               }
+               else if (strcmp (fields[0], "TCP/UDP write bytes") == 0)
+               {
+                       link_tx = atoll (fields[1]);
+               }
+               else if (strcmp (fields[0], "pre-compress bytes") == 0)
+               {
+                       pre_compress = atoll (fields[1]);
+               }
+               else if (strcmp (fields[0], "post-compress bytes") == 0)
+               {
+                       post_compress = atoll (fields[1]);
+               }
+               else if (strcmp (fields[0], "pre-decompress bytes") == 0)
+               {
+                       pre_decompress = atoll (fields[1]);
+               }
+               else if (strcmp (fields[0], "post-decompress bytes") == 0)
+               {
+                       post_decompress = atoll (fields[1]);
+               }
+       }
+
+       iostats_submit (name, "traffic", link_rx, link_tx);
+
+       /* we need to force this order to avoid negative values with these unsigned */
+       overhead_rx = (((link_rx - pre_decompress) + post_decompress) - tun_rx);
+       overhead_tx = (((link_tx - post_compress) + pre_compress) - tun_tx);
+
+       iostats_submit (name, "overhead", overhead_rx, overhead_tx);
+
+       if (collect_compression)
+       {
+               compression_submit (name, "data_in", post_decompress, pre_decompress);
+               compression_submit (name, "data_out", pre_compress, post_compress);
+       }
+
+       read = 1;
+
+       return (read);
+} /* int single_read */
+
+/* for reading status version 1 */
+static int multi1_read (char *name, FILE *fh)
+{
+       char buffer[1024];
+       char *fields[10];
+       int  fields_num, read = 0, found_header = 0;
+       long long sum_users = 0;
+
+       /* read the file until the "ROUTING TABLE" line is found (no more info after) */
+       while (fgets (buffer, sizeof (buffer), fh) != NULL)
+       {
+               if (strcmp (buffer, "ROUTING TABLE\n") == 0)
+                       break;
+
+               if (strcmp (buffer, V1STRING) == 0)
+               {
+                       found_header = 1;
+                       continue;
+               }
+
+               /* skip the first lines until the client list section is found */
+               if (found_header == 0)
+                       /* we can't start reading data until this string is found */
+                       continue;
+
+               fields_num = openvpn_strsplit (buffer,
+                               fields, STATIC_ARRAY_SIZE (fields));
+               if (fields_num < 4)
+                       continue;
+
+               if (collect_user_count)
+                       /* If so, sum all users, ignore the individuals*/
+               {
+                       sum_users += 1;
+               }
+               if (collect_individual_users)
+               {
+                       if (new_naming_schema)
+                       {
+                               iostats_submit (name,               /* vpn instance */
+                                               fields[0],          /* "Common Name" */
+                                               atoll (fields[2]),  /* "Bytes Received" */
+                                               atoll (fields[3])); /* "Bytes Sent" */
+                       }
+                       else
+                       {
+                               iostats_submit (fields[0],          /* "Common Name" */
+                                               NULL,               /* unused when in multimode */
+                                               atoll (fields[2]),  /* "Bytes Received" */
+                                               atoll (fields[3])); /* "Bytes Sent" */
+                       }
+               }
+
+               read = 1;
+       }
+
+       if (collect_user_count)
+       {
+               numusers_submit(name, name, sum_users);
+               read = 1;
+       }
+
+       return (read);
+} /* int multi1_read */
+
+/* for reading status version 2 */
+static int multi2_read (char *name, FILE *fh)
+{
+       char buffer[1024];
+       char *fields[10];
+       const int max_fields = STATIC_ARRAY_SIZE (fields);
+       int  fields_num, read = 0;
+       long long sum_users    = 0;
+
+       while (fgets (buffer, sizeof (buffer), fh) != NULL)
+       {
+               fields_num = openvpn_strsplit (buffer, fields, max_fields);
+
+               /* status file is generated by openvpn/multi.c:multi_print_status()
+                * http://svn.openvpn.net/projects/openvpn/trunk/openvpn/multi.c
+                *
+                * The line we're expecting has 8 fields. We ignore all lines
+                *  with more or less fields.
+                */
+               if (fields_num != 8)
+                       continue;
+
+               if (strcmp (fields[0], "CLIENT_LIST") != 0)
+                       continue;
+
+               if (collect_user_count)
+                       /* If so, sum all users, ignore the individuals*/
+               {
+                       sum_users += 1;
+               }
+               if (collect_individual_users)
+               {
+                       if (new_naming_schema)
+                       {
+                               /* plugin inst = file name, type inst = fields[1] */
+                               iostats_submit (name,               /* vpn instance */
+                                               fields[1],          /* "Common Name" */
+                                               atoll (fields[4]),  /* "Bytes Received" */
+                                               atoll (fields[5])); /* "Bytes Sent" */
+                       }
+                       else
+                       {
+                               /* plugin inst = fields[1], type inst = "" */
+                               iostats_submit (fields[1],          /* "Common Name" */
+                                               NULL,               /* unused when in multimode */
+                                               atoll (fields[4]),  /* "Bytes Received" */
+                                               atoll (fields[5])); /* "Bytes Sent" */
+                       }
+               }
+
+               read = 1;
+       }
+
+       if (collect_user_count)
+       {
+               numusers_submit(name, name, sum_users);
+               read = 1;
+       }
+
+       return (read);
+} /* int multi2_read */
+
+/* for reading status version 3 */
+static int multi3_read (char *name, FILE *fh)
+{
+       char buffer[1024];
+       char *fields[15];
+       const int max_fields = STATIC_ARRAY_SIZE (fields);
+       int  fields_num, read = 0;
+       long long sum_users    = 0;
+
+       while (fgets (buffer, sizeof (buffer), fh) != NULL)
+       {
+               fields_num = strsplit (buffer, fields, max_fields);
+
+               /* status file is generated by openvpn/multi.c:multi_print_status()
+                * http://svn.openvpn.net/projects/openvpn/trunk/openvpn/multi.c
+                *
+                * The line we're expecting has 12 fields. We ignore all lines
+                *  with more or less fields.
+                */
+               if (fields_num != 12)
+               {
+                       continue;
+               }
+               else
+               {
+                       if (strcmp (fields[0], "CLIENT_LIST") != 0)
+                               continue;
+
+                       if (collect_user_count)
+                               /* If so, sum all users, ignore the individuals*/
+                       {
+                               sum_users += 1;
+                       }
+
+                       if (collect_individual_users)
+                       {
+                               if (new_naming_schema)
+                               {
+                                       iostats_submit (name,               /* vpn instance */
+                                                       fields[1],          /* "Common Name" */
+                                                       atoll (fields[4]),  /* "Bytes Received" */
+                                                       atoll (fields[5])); /* "Bytes Sent" */
+                               }
+                               else
+                               {
+                                       iostats_submit (fields[1],          /* "Common Name" */
+                                                       NULL,               /* unused when in multimode */
+                                                       atoll (fields[4]),  /* "Bytes Received" */
+                                                       atoll (fields[5])); /* "Bytes Sent" */
+                               }
+                       }
+
+                       read = 1;
+               }
+       }
+
+       if (collect_user_count)
+       {
+               numusers_submit(name, name, sum_users);
+               read = 1;
+       }
+
+       return (read);
+} /* int multi3_read */
+
+/* read callback */
+static int openvpn_read (void)
+{
+       FILE *fh;
+       int  i, read;
+
+       read = 0;
+
+       /* call the right read function for every status entry in the list */
+       for (i = 0; i < vpn_num; i++)
+       {
+               fh = fopen (vpn_list[i]->file, "r");
+               if (fh == NULL)
+               {
+                       char errbuf[1024];
+                       WARNING ("openvpn plugin: fopen(%s) failed: %s", vpn_list[i]->file,
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+
+                       continue;
+               }
+
+               switch (vpn_list[i]->version)
+               {
+                       case SINGLE:
+                               read = single_read(vpn_list[i]->name, fh);
+                               break;
+
+                       case MULTI1:
+                               read = multi1_read(vpn_list[i]->name, fh);
+                               break;
+
+                       case MULTI2:
+                               read = multi2_read(vpn_list[i]->name, fh);
+                               break;
+
+                       case MULTI3:
+                               read = multi3_read(vpn_list[i]->name, fh);
+                               break;
+               }
+
+               fclose (fh);
+       }
+
+       return (read ? 0 : -1);
+} /* int openvpn_read */
+
+static int version_detect (const char *filename)
+{
+       FILE *fh;
+       char buffer[1024];
+       int version = 0;
+
+       /* Sanity checking. We're called from the config handling routine, so
+        * better play it save. */
+       if ((filename == NULL) || (*filename == 0))
+               return (0);
+
+       fh = fopen (filename, "r");
+       if (fh == NULL)
+       {
+               char errbuf[1024];
+               WARNING ("openvpn plugin: Unable to read \"%s\": %s", filename,
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (0);
+       }
+
+       /* now search for the specific multimode data format */
+       while ((fgets (buffer, sizeof (buffer), fh)) != NULL)
+       {
+               /* we look at the first line searching for SINGLE mode configuration */
+               if (strcmp (buffer, VSSTRING) == 0)
+               {
+                       DEBUG ("openvpn plugin: found status file version SINGLE");
+                       version = SINGLE;
+                       break;
+               }
+               /* searching for multi version 1 */
+               else if (strcmp (buffer, V1STRING) == 0)
+               {
+                       DEBUG ("openvpn plugin: found status file version MULTI1");
+                       version = MULTI1;
+                       break;
+               }
+               /* searching for multi version 2 */
+               else if (strcmp (buffer, V2STRING) == 0)
+               {
+                       DEBUG ("openvpn plugin: found status file version MULTI2");
+                       version = MULTI2;
+                       break;
+               }
+               /* searching for multi version 3 */
+               else if (strcmp (buffer, V3STRING) == 0)
+               {
+                       DEBUG ("openvpn plugin: found status file version MULTI3");
+                       version = MULTI3;
+                       break;
+               }
+       }
+
+       if (version == 0)
+       {
+               /* This is only reached during configuration, so complaining to
+                * the user is in order. */
+               NOTICE ("openvpn plugin: %s: Unknown file format, please "
+                               "report this as bug. Make sure to include "
+                               "your status file, so the plugin can "
+                               "be adapted.", filename);
+       }
+
+       fclose (fh);
+
+       return version;
+} /* int version_detect */
+
+static int openvpn_config (const char *key, const char *value)
+{
+       if (strcasecmp ("StatusFile", key) == 0)
+       {
+               char    *status_file, *status_name, *filename;
+               int     status_version, i;
+               vpn_status_t *temp;
+
+               /* try to detect the status file format */
+               status_version = version_detect (value);
+
+               if (status_version == 0)
+               {
+                       WARNING ("openvpn plugin: unable to detect status version, \
+                                       discarding status file \"%s\".", value);
+                       return (1);
+               }
+
+               status_file = sstrdup (value);
+               if (status_file == NULL)
+               {
+                       char errbuf[1024];
+                       WARNING ("openvpn plugin: sstrdup failed: %s",
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       return (1);
+               }
+
+               /* it determines the file name as string starting at location filename + 1 */
+               filename = strrchr (status_file, (int) '/');
+               if (filename == NULL)
+               {
+                       /* status_file is already the file name only */
+                       status_name = status_file;
+               }
+               else
+               {
+                       /* doesn't waste memory, uses status_file starting at filename + 1 */
+                       status_name = filename + 1;
+               }
+
+               /* scan the list looking for a clone */
+               for (i = 0; i < vpn_num; i++)
+               {
+                       if (strcasecmp (vpn_list[i]->name, status_name) == 0)
+                       {
+                               WARNING ("openvpn plugin: status filename \"%s\" "
+                                               "already used, please choose a "
+                                               "different one.", status_name);
+                               sfree (status_file);
+                               return (1);
+                       }
+               }
+
+               /* create a new vpn element since file, version and name are ok */
+               temp = (vpn_status_t *) malloc (sizeof (vpn_status_t));
+               temp->file = status_file;
+               temp->version = status_version;
+               temp->name = status_name;
+
+               vpn_list = (vpn_status_t **) realloc (vpn_list, (vpn_num + 1) * sizeof (vpn_status_t *));
+               if (vpn_list == NULL)
+               {
+                       char errbuf[1024];
+                       ERROR ("openvpn plugin: malloc failed: %s",
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+
+                       sfree (temp->file);
+                       sfree (temp);
+                       return (1);
+               }
+
+               vpn_list[vpn_num] = temp;
+               vpn_num++;
+
+               DEBUG ("openvpn plugin: status file \"%s\" added", temp->file);
+
+       } /* if (strcasecmp ("StatusFile", key) == 0) */
+       else if ((strcasecmp ("CollectCompression", key) == 0)
+               || (strcasecmp ("Compression", key) == 0)) /* old, deprecated name */
+       {
+               if (IS_FALSE (value))
+                       collect_compression = 0;
+               else
+                       collect_compression = 1;
+       } /* if (strcasecmp ("CollectCompression", key) == 0) */
+       else if (strcasecmp ("ImprovedNamingSchema", key) == 0)
+       {
+               if (IS_TRUE (value))
+               {
+                       DEBUG ("openvpn plugin: using the new naming schema");
+                       new_naming_schema = 1;
+               }
+               else
+               {
+                       new_naming_schema = 0;
+               }
+       } /* if (strcasecmp ("ImprovedNamingSchema", key) == 0) */
+       else if (strcasecmp("CollectUserCount", key) == 0)
+       {
+               if (IS_TRUE(value))
+                       collect_user_count = 1;
+               else
+                       collect_user_count = 0;
+       } /* if (strcasecmp("CollectUserCount", key) == 0) */
+       else if (strcasecmp("CollectIndividualUsers", key) == 0)
+       {
+               if (IS_FALSE (value))
+                       collect_individual_users = 0;
+               else
+                       collect_individual_users = 1;
+       } /* if (strcasecmp("CollectIndividualUsers", key) == 0) */
+       else
+       {
+               return (-1);
+       }
+
+       return (0);
+} /* int openvpn_config */
+
+/* shutdown callback */
+static int openvpn_shutdown (void)
+{
+       int i;
+
+       for (i = 0; i < vpn_num; i++)
+       {
+               sfree (vpn_list[i]->file);
+               sfree (vpn_list[i]);
+       }
+
+       sfree (vpn_list);
+
+       return (0);
+} /* int openvpn_shutdown */
+
+static int openvpn_init (void)
+{
+       if (!collect_individual_users
+                       && !collect_compression
+                       && !collect_user_count)
+       {
+               WARNING ("OpenVPN plugin: Neither `CollectIndividualUsers', "
+                               "`CollectCompression', nor `CollectUserCount' is true. There's no "
+                               "data left to collect.");
+               return (-1);
+       }
+
+       plugin_register_read ("openvpn", openvpn_read);
+       plugin_register_shutdown ("openvpn", openvpn_shutdown);
+
+       return (0);
+} /* int openvpn_init */
+
+void module_register (void)
+{
+       plugin_register_config ("openvpn", openvpn_config,
+                       config_keys, config_keys_num);
+       plugin_register_init ("openvpn", openvpn_init);
+} /* void module_register */
+
+/* vim: set sw=2 ts=2 : */
diff --git a/src/oracle.c b/src/oracle.c
new file mode 100644 (file)
index 0000000..4415b48
--- /dev/null
@@ -0,0 +1,780 @@
+/**
+ * collectd - src/oracle.c
+ * Copyright (C) 2008,2009  noris network AG
+ * Copyright (C) 2012       Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Linking src/oracle.c ("the oracle plugin") statically or dynamically with
+ * other modules is making a combined work based on the oracle plugin. Thus,
+ * the terms and conditions of the GNU General Public License cover the whole
+ * combination.
+ *
+ * In addition, as a special exception, the copyright holders of the oracle
+ * plugin give you permission to combine the oracle plugin with free software
+ * programs or libraries that are released under the GNU LGPL and with code
+ * included in the standard release of the Oracle® Call Interface (OCI) under
+ * the Oracle® Technology Network (OTN) License (or modified versions of such
+ * code, with unchanged license). You may copy and distribute such a system
+ * following the terms of the GNU GPL for the oracle plugin and the licenses of
+ * the other code concerned.
+ *
+ * Note that people who make modified versions of the oracle plugin are not
+ * obligated to grant this special exception for their modified versions; it is
+ * their choice whether to do so. The GNU General Public License gives
+ * permission to release a modified version without this exception; this
+ * exception also makes it possible to release a modified version which carries
+ * forward this exception. However, without this exception the OTN License does
+ * not allow linking with code licensed under the GNU General Public License.
+ *
+ * Oracle® is a registered trademark of Oracle Corporation and/or its
+ * affiliates. Other names may be trademarks of their respective owners.
+ *
+ * Authors:
+ *   Florian octo Forster <octo at collectd.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+#include "utils_db_query.h"
+
+#include <oci.h>
+
+/*
+ * Data types
+ */
+struct o_database_s
+{
+  char *name;
+  char *connect_id;
+  char *username;
+  char *password;
+
+  udb_query_preparation_area_t **q_prep_areas;
+  udb_query_t **queries;
+  size_t        queries_num;
+
+  OCISvcCtx *oci_service_context;
+};
+typedef struct o_database_s o_database_t;
+
+/*
+ * Global variables
+ */
+static udb_query_t  **queries       = NULL;
+static size_t         queries_num   = 0;
+static o_database_t **databases     = NULL;
+static size_t         databases_num = 0;
+
+OCIEnv   *oci_env = NULL;
+OCIError *oci_error = NULL;
+
+/*
+ * Functions
+ */
+static void o_report_error (const char *where, /* {{{ */
+    const char *what, OCIError *eh)
+{
+  char buffer[2048];
+  sb4 error_code;
+  int status;
+  unsigned int record_number;
+
+  /* An operation may cause / return multiple errors. Loop until we have
+   * handled all errors available (with a fail-save limit of 16). */
+  for (record_number = 1; record_number <= 16; record_number++)
+  {
+    memset (buffer, 0, sizeof (buffer));
+    error_code = -1;
+
+    status = OCIErrorGet (eh, (ub4) record_number,
+        /* sqlstate = */ NULL,
+        &error_code,
+        (text *) &buffer[0],
+        (ub4) sizeof (buffer),
+        OCI_HTYPE_ERROR);
+    buffer[sizeof (buffer) - 1] = 0;
+
+    if (status == OCI_NO_DATA)
+      return;
+
+    if (status == OCI_SUCCESS)
+    {
+      size_t buffer_length;
+
+      buffer_length = strlen (buffer);
+      while ((buffer_length > 0) && (buffer[buffer_length - 1] < 32))
+      {
+        buffer_length--;
+        buffer[buffer_length] = 0;
+      }
+
+      ERROR ("oracle plugin: %s: %s failed: %s", where, what, buffer);
+    }
+    else
+    {
+      ERROR ("oracle plugin: %s: %s failed. Additionally, OCIErrorGet failed with status %i.",
+          where, what, status);
+      return;
+    }
+  }
+} /* }}} void o_report_error */
+
+static void o_database_free (o_database_t *db) /* {{{ */
+{
+  size_t i;
+
+  if (db == NULL)
+    return;
+
+  sfree (db->name);
+  sfree (db->connect_id);
+  sfree (db->username);
+  sfree (db->password);
+  sfree (db->queries);
+
+  if (db->q_prep_areas != NULL)
+    for (i = 0; i < db->queries_num; ++i)
+      udb_query_delete_preparation_area (db->q_prep_areas[i]);
+  free (db->q_prep_areas);
+
+  sfree (db);
+} /* }}} void o_database_free */
+
+/* Configuration handling functions {{{
+ *
+ * <Plugin oracle>
+ *   <Query "plugin_instance0">
+ *     Statement "SELECT name, value FROM table"
+ *     <Result>
+ *       Type "gauge"
+ *       InstancesFrom "name"
+ *       ValuesFrom "value"
+ *     </Result>
+ *   </Query>
+ *     
+ *   <Database "plugin_instance1">
+ *     ConnectID "db01"
+ *     Username "oracle"
+ *     Password "secret"
+ *     Query "plugin_instance0"
+ *   </Database>
+ * </Plugin>
+ */
+
+static int o_config_set_string (char **ret_string, /* {{{ */
+    oconfig_item_t *ci)
+{
+  char *string;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("oracle plugin: The `%s' config option "
+        "needs exactly one string argument.", ci->key);
+    return (-1);
+  }
+
+  string = strdup (ci->values[0].value.string);
+  if (string == NULL)
+  {
+    ERROR ("oracle plugin: strdup failed.");
+    return (-1);
+  }
+
+  if (*ret_string != NULL)
+    free (*ret_string);
+  *ret_string = string;
+
+  return (0);
+} /* }}} int o_config_set_string */
+
+static int o_config_add_database (oconfig_item_t *ci) /* {{{ */
+{
+  o_database_t *db;
+  int status;
+  int i;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("oracle plugin: The `Database' block "
+        "needs exactly one string argument.");
+    return (-1);
+  }
+
+  db = (o_database_t *) malloc (sizeof (*db));
+  if (db == NULL)
+  {
+    ERROR ("oracle plugin: malloc failed.");
+    return (-1);
+  }
+  memset (db, 0, sizeof (*db));
+
+  status = o_config_set_string (&db->name, ci);
+  if (status != 0)
+  {
+    sfree (db);
+    return (status);
+  }
+
+  /* Fill the `o_database_t' structure.. */
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("ConnectID", child->key) == 0)
+      status = o_config_set_string (&db->connect_id, child);
+    else if (strcasecmp ("Username", child->key) == 0)
+      status = o_config_set_string (&db->username, child);
+    else if (strcasecmp ("Password", child->key) == 0)
+      status = o_config_set_string (&db->password, child);
+    else if (strcasecmp ("Query", child->key) == 0)
+      status = udb_query_pick_from_list (child, queries, queries_num,
+          &db->queries, &db->queries_num);
+    else
+    {
+      WARNING ("oracle plugin: Option `%s' not allowed here.", child->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  /* Check that all necessary options have been given. */
+  while (status == 0)
+  {
+    if (db->connect_id == NULL)
+    {
+      WARNING ("oracle plugin: `ConnectID' not given for query `%s'", db->name);
+      status = -1;
+    }
+    if (db->username == NULL)
+    {
+      WARNING ("oracle plugin: `Username' not given for query `%s'", db->name);
+      status = -1;
+    }
+    if (db->password == NULL)
+    {
+      WARNING ("oracle plugin: `Password' not given for query `%s'", db->name);
+      status = -1;
+    }
+
+    break;
+  } /* while (status == 0) */
+
+  while ((status == 0) && (db->queries_num > 0))
+  {
+    db->q_prep_areas = (udb_query_preparation_area_t **) calloc (
+        db->queries_num, sizeof (*db->q_prep_areas));
+
+    if (db->q_prep_areas == NULL)
+    {
+      WARNING ("oracle plugin: malloc failed");
+      status = -1;
+      break;
+    }
+
+    for (i = 0; i < db->queries_num; ++i)
+    {
+      db->q_prep_areas[i]
+        = udb_query_allocate_preparation_area (db->queries[i]);
+
+      if (db->q_prep_areas[i] == NULL)
+      {
+        WARNING ("oracle plugin: udb_query_allocate_preparation_area failed");
+        status = -1;
+        break;
+      }
+    }
+
+    break;
+  }
+
+  /* If all went well, add this query to the list of queries within the
+   * database structure. */
+  if (status == 0)
+  {
+    o_database_t **temp;
+
+    temp = (o_database_t **) realloc (databases,
+        sizeof (*databases) * (databases_num + 1));
+    if (temp == NULL)
+    {
+      ERROR ("oracle plugin: realloc failed");
+      status = -1;
+    }
+    else
+    {
+      databases = temp;
+      databases[databases_num] = db;
+      databases_num++;
+    }
+  }
+
+  if (status != 0)
+  {
+    o_database_free (db);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int o_config_add_database */
+
+static int o_config (oconfig_item_t *ci) /* {{{ */
+{
+  int i;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+    if (strcasecmp ("Query", child->key) == 0)
+      udb_query_create (&queries, &queries_num, child,
+          /* callback = */ NULL);
+    else if (strcasecmp ("Database", child->key) == 0)
+      o_config_add_database (child);
+    else
+    {
+      WARNING ("oracle plugin: Ignoring unknown config option `%s'.", child->key);
+    }
+
+    if (queries_num > 0)
+    {
+      DEBUG ("oracle plugin: o_config: queries_num = %zu; queries[0] = %p; udb_query_get_user_data (queries[0]) = %p;",
+          queries_num, (void *) queries[0], udb_query_get_user_data (queries[0]));
+    }
+  } /* for (ci->children) */
+
+  return (0);
+} /* }}} int o_config */
+
+/* }}} End of configuration handling functions */
+
+static int o_init (void) /* {{{ */
+{
+  int status;
+
+  if (oci_env != NULL)
+    return (0);
+
+  status = OCIEnvCreate (&oci_env,
+      /* mode = */ OCI_THREADED,
+      /* context        = */ NULL,
+      /* malloc         = */ NULL,
+      /* realloc        = */ NULL,
+      /* free           = */ NULL,
+      /* user_data_size = */ 0,
+      /* user_data_ptr  = */ NULL);
+  if (status != 0)
+  {
+    ERROR ("oracle plugin: OCIEnvCreate failed with status %i.", status);
+    return (-1);
+  }
+
+  status = OCIHandleAlloc (oci_env, (void *) &oci_error, OCI_HTYPE_ERROR,
+      /* user_data_size = */ 0, /* user_data = */ NULL);
+  if (status != OCI_SUCCESS)
+  {
+    ERROR ("oracle plugin: OCIHandleAlloc (OCI_HTYPE_ERROR) failed "
+        "with status %i.", status);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int o_init */
+
+static int o_read_database_query (o_database_t *db, /* {{{ */
+    udb_query_t *q, udb_query_preparation_area_t *prep_area)
+{
+  char **column_names;
+  char **column_values;
+  size_t column_num;
+
+  OCIStmt *oci_statement;
+
+  /* List of `OCIDefine' pointers. These defines map columns to the buffer
+   * space declared above. */
+  OCIDefine **oci_defines;
+
+  int status;
+  size_t i;
+
+  oci_statement = udb_query_get_user_data (q);
+
+  /* Prepare the statement */
+  if (oci_statement == NULL) /* {{{ */
+  {
+    const char *statement;
+
+    statement = udb_query_get_statement (q);
+    assert (statement != NULL);
+
+    status = OCIHandleAlloc (oci_env, (void *) &oci_statement,
+        OCI_HTYPE_STMT, /* user_data_size = */ 0, /* user_data = */ NULL);
+    if (status != OCI_SUCCESS)
+    {
+      o_report_error ("o_read_database_query", "OCIHandleAlloc", oci_error);
+      oci_statement = NULL;
+      return (-1);
+    }
+
+    status = OCIStmtPrepare (oci_statement, oci_error,
+        (text *) statement, (ub4) strlen (statement),
+        /* language = */ OCI_NTV_SYNTAX,
+        /* mode     = */ OCI_DEFAULT);
+    if (status != OCI_SUCCESS)
+    {
+      o_report_error ("o_read_database_query", "OCIStmtPrepare", oci_error);
+      OCIHandleFree (oci_statement, OCI_HTYPE_STMT);
+      oci_statement = NULL;
+      return (-1);
+    }
+    udb_query_set_user_data (q, oci_statement);
+
+    DEBUG ("oracle plugin: o_read_database_query (%s, %s): "
+        "Successfully allocated statement handle.",
+        db->name, udb_query_get_name (q));
+  } /* }}} */
+
+  assert (oci_statement != NULL);
+
+  /* Execute the statement */
+  status = OCIStmtExecute (db->oci_service_context, /* {{{ */
+      oci_statement,
+      oci_error,
+      /* iters = */ 0,
+      /* rowoff = */ 0,
+      /* snap_in = */ NULL, /* snap_out = */ NULL,
+      /* mode = */ OCI_DEFAULT);
+  if (status != OCI_SUCCESS)
+  {
+    DEBUG ("oracle plugin: o_read_database_query: status = %i (%#x)", status, status);
+    o_report_error ("o_read_database_query", "OCIStmtExecute", oci_error);
+    ERROR ("oracle plugin: o_read_database_query: "
+        "Failing statement was: %s", udb_query_get_statement (q));
+    return (-1);
+  } /* }}} */
+
+  /* Acquire the number of columns returned. */
+  do /* {{{ */
+  {
+    ub4 param_counter = 0;
+    status = OCIAttrGet (oci_statement, OCI_HTYPE_STMT, /* {{{ */
+        &param_counter, /* size pointer = */ NULL, 
+        OCI_ATTR_PARAM_COUNT, oci_error);
+    if (status != OCI_SUCCESS)
+    {
+      o_report_error ("o_read_database_query", "OCIAttrGet", oci_error);
+      return (-1);
+    } /* }}} */
+
+    column_num = (size_t) param_counter;
+  } while (0); /* }}} */
+
+  /* Allocate the following buffers:
+   * 
+   *  +---------------+-----------------------------------+
+   *  ! Name          ! Size                              !
+   *  +---------------+-----------------------------------+
+   *  ! column_names  ! column_num x DATA_MAX_NAME_LEN    !
+   *  ! column_values ! column_num x DATA_MAX_NAME_LEN    !
+   *  ! oci_defines   ! column_num x sizeof (OCIDefine *) !
+   *  +---------------+-----------------------------------+
+   *
+   * {{{ */
+#define NUMBER_BUFFER_SIZE 64
+
+#define FREE_ALL \
+  if (column_names != NULL) { \
+    sfree (column_names[0]); \
+    sfree (column_names); \
+  } \
+  if (column_values != NULL) { \
+    sfree (column_values[0]); \
+    sfree (column_values); \
+  } \
+  sfree (oci_defines)
+
+#define ALLOC_OR_FAIL(ptr, ptr_size) \
+  do { \
+    size_t alloc_size = (size_t) ((ptr_size)); \
+    (ptr) = malloc (alloc_size); \
+    if ((ptr) == NULL) { \
+      FREE_ALL; \
+      ERROR ("oracle plugin: o_read_database_query: malloc failed."); \
+      return (-1); \
+    } \
+    memset ((ptr), 0, alloc_size); \
+  } while (0)
+
+  /* Initialize everything to NULL so the above works. */
+  column_names  = NULL;
+  column_values = NULL;
+  oci_defines   = NULL;
+
+  ALLOC_OR_FAIL (column_names, column_num * sizeof (char *));
+  ALLOC_OR_FAIL (column_names[0], column_num * DATA_MAX_NAME_LEN
+      * sizeof (char));
+  for (i = 1; i < column_num; i++)
+    column_names[i] = column_names[i - 1] + DATA_MAX_NAME_LEN;
+
+  ALLOC_OR_FAIL (column_values, column_num * sizeof (char *));
+  ALLOC_OR_FAIL (column_values[0], column_num * DATA_MAX_NAME_LEN
+      * sizeof (char));
+  for (i = 1; i < column_num; i++)
+    column_values[i] = column_values[i - 1] + DATA_MAX_NAME_LEN;
+
+  ALLOC_OR_FAIL (oci_defines, column_num * sizeof (OCIDefine *));
+  /* }}} End of buffer allocations. */
+
+  /* ``Define'' the returned data, i. e. bind the columns to the buffers
+   * allocated above. */
+  for (i = 0; i < column_num; i++) /* {{{ */
+  {
+    char *column_name;
+    ub4 column_name_length;
+    OCIParam *oci_param;
+
+    oci_param = NULL;
+
+    status = OCIParamGet (oci_statement, OCI_HTYPE_STMT, oci_error,
+        (void *) &oci_param, (ub4) (i + 1));
+    if (status != OCI_SUCCESS)
+    {
+      /* This is probably alright */
+      DEBUG ("oracle plugin: o_read_database_query: status = %#x (= %i);", status, status);
+      o_report_error ("o_read_database_query", "OCIParamGet", oci_error);
+      status = OCI_SUCCESS;
+      break;
+    }
+
+    column_name = NULL;
+    column_name_length = 0;
+    status = OCIAttrGet (oci_param, OCI_DTYPE_PARAM,
+        &column_name, &column_name_length, OCI_ATTR_NAME, oci_error);
+    if (status != OCI_SUCCESS)
+    {
+      OCIDescriptorFree (oci_param, OCI_DTYPE_PARAM);
+      o_report_error ("o_read_database_query", "OCIAttrGet (OCI_ATTR_NAME)",
+          oci_error);
+      continue;
+    }
+
+    OCIDescriptorFree (oci_param, OCI_DTYPE_PARAM);
+    oci_param = NULL;
+
+    /* Copy the name to column_names. Warning: The ``string'' returned by OCI
+     * may not be null terminated! */
+    memset (column_names[i], 0, DATA_MAX_NAME_LEN);
+    if (column_name_length >= DATA_MAX_NAME_LEN)
+      column_name_length = DATA_MAX_NAME_LEN - 1;
+    memcpy (column_names[i], column_name, column_name_length);
+    column_names[i][column_name_length] = 0;
+
+    DEBUG ("oracle plugin: o_read_database_query: column_names[%zu] = %s; "
+        "column_name_length = %"PRIu32";",
+        i, column_names[i], (uint32_t) column_name_length);
+
+    status = OCIDefineByPos (oci_statement,
+        &oci_defines[i], oci_error, (ub4) (i + 1),
+        column_values[i], DATA_MAX_NAME_LEN, SQLT_STR,
+        NULL, NULL, NULL, OCI_DEFAULT);
+    if (status != OCI_SUCCESS)
+    {
+      o_report_error ("o_read_database_query", "OCIDefineByPos", oci_error);
+      continue;
+    }
+  } /* for (j = 1; j <= param_counter; j++) */
+  /* }}} End of the ``define'' stuff. */
+
+  status = udb_query_prepare_result (q, prep_area, hostname_g,
+      /* plugin = */ "oracle", db->name, column_names, column_num,
+      /* interval = */ 0);
+  if (status != 0)
+  {
+    ERROR ("oracle plugin: o_read_database_query (%s, %s): "
+        "udb_query_prepare_result failed.",
+        db->name, udb_query_get_name (q));
+    FREE_ALL;
+    return (-1);
+  }
+
+  /* Fetch and handle all the rows that matched the query. */
+  while (42) /* {{{ */
+  {
+    status = OCIStmtFetch2 (oci_statement, oci_error,
+        /* nrows = */ 1, /* orientation = */ OCI_FETCH_NEXT,
+        /* fetch offset = */ 0, /* mode = */ OCI_DEFAULT);
+    if (status == OCI_NO_DATA)
+    {
+      status = OCI_SUCCESS;
+      break;
+    }
+    else if ((status != OCI_SUCCESS) && (status != OCI_SUCCESS_WITH_INFO))
+    {
+      o_report_error ("o_read_database_query", "OCIStmtFetch2", oci_error);
+      break;
+    }
+
+    status = udb_query_handle_result (q, prep_area, column_values);
+    if (status != 0)
+    {
+      WARNING ("oracle plugin: o_read_database_query (%s, %s): "
+          "udb_query_handle_result failed.",
+          db->name, udb_query_get_name (q));
+    }
+  } /* }}} while (42) */
+
+  /* DEBUG ("oracle plugin: o_read_database_query: This statement succeeded: %s", q->statement); */
+  FREE_ALL;
+
+  return (0);
+#undef FREE_ALL
+#undef ALLOC_OR_FAIL
+} /* }}} int o_read_database_query */
+
+static int o_read_database (o_database_t *db) /* {{{ */
+{
+  size_t i;
+  int status;
+
+  if (db->oci_service_context != NULL)
+  {
+    OCIServer *server_handle;
+    ub4 connection_status;
+
+    server_handle = NULL;
+    status = OCIAttrGet ((void *) db->oci_service_context, OCI_HTYPE_SVCCTX, 
+        (void *) &server_handle, /* size pointer = */ NULL,
+        OCI_ATTR_SERVER, oci_error);
+    if (status != OCI_SUCCESS)
+    {
+      o_report_error ("o_read_database", "OCIAttrGet", oci_error);
+      return (-1);
+    }
+
+    if (server_handle == NULL)
+    {
+      connection_status = OCI_SERVER_NOT_CONNECTED;
+    }
+    else /* if (server_handle != NULL) */
+    {
+      connection_status = 0;
+      status = OCIAttrGet ((void *) server_handle, OCI_HTYPE_SERVER,
+          (void *) &connection_status, /* size pointer = */ NULL,
+          OCI_ATTR_SERVER_STATUS, oci_error);
+      if (status != OCI_SUCCESS)
+      {
+        o_report_error ("o_read_database", "OCIAttrGet", oci_error);
+        return (-1);
+      }
+    }
+
+    if (connection_status != OCI_SERVER_NORMAL)
+    {
+      INFO ("oracle plugin: Connection to %s lost. Trying to reconnect.",
+          db->name);
+      OCIHandleFree (db->oci_service_context, OCI_HTYPE_SVCCTX);
+      db->oci_service_context = NULL;
+    }
+  } /* if (db->oci_service_context != NULL) */
+
+  if (db->oci_service_context == NULL)
+  {
+    status = OCILogon (oci_env, oci_error,
+        &db->oci_service_context,
+        (OraText *) db->username, (ub4) strlen (db->username),
+        (OraText *) db->password, (ub4) strlen (db->password),
+        (OraText *) db->connect_id, (ub4) strlen (db->connect_id));
+    if ((status != OCI_SUCCESS) && (status != OCI_SUCCESS_WITH_INFO))
+    {
+      o_report_error ("o_read_database", "OCILogon", oci_error);
+      DEBUG ("oracle plugin: OCILogon (%s): db->oci_service_context = %p;",
+          db->connect_id, db->oci_service_context);
+      db->oci_service_context = NULL;
+      return (-1);
+    }
+    else if (status == OCI_SUCCESS_WITH_INFO)
+    {
+      /* TODO: Print NOTIFY message. */
+    }
+    assert (db->oci_service_context != NULL);
+  }
+
+  DEBUG ("oracle plugin: o_read_database: db->connect_id = %s; db->oci_service_context = %p;",
+      db->connect_id, db->oci_service_context);
+
+  for (i = 0; i < db->queries_num; i++)
+    o_read_database_query (db, db->queries[i], db->q_prep_areas[i]);
+
+  return (0);
+} /* }}} int o_read_database */
+
+static int o_read (void) /* {{{ */
+{
+  size_t i;
+
+  for (i = 0; i < databases_num; i++)
+    o_read_database (databases[i]);
+
+  return (0);
+} /* }}} int o_read */
+
+static int o_shutdown (void) /* {{{ */
+{
+  size_t i;
+
+  for (i = 0; i < databases_num; i++)
+    if (databases[i]->oci_service_context != NULL)
+    {
+      OCIHandleFree (databases[i]->oci_service_context, OCI_HTYPE_SVCCTX);
+      databases[i]->oci_service_context = NULL;
+    }
+  
+  for (i = 0; i < queries_num; i++)
+  {
+    OCIStmt *oci_statement;
+
+    oci_statement = udb_query_get_user_data (queries[i]);
+    if (oci_statement != NULL)
+    {
+      OCIHandleFree (oci_statement, OCI_HTYPE_STMT);
+      udb_query_set_user_data (queries[i], NULL);
+    }
+  }
+  
+  OCIHandleFree (oci_env, OCI_HTYPE_ENV);
+  oci_env = NULL;
+
+  udb_query_free (queries, queries_num);
+  queries = NULL;
+  queries_num = 0;
+
+  return (0);
+} /* }}} int o_shutdown */
+
+void module_register (void) /* {{{ */
+{
+  plugin_register_complex_config ("oracle", o_config);
+  plugin_register_init ("oracle", o_init);
+  plugin_register_read ("oracle", o_read);
+  plugin_register_shutdown ("oracle", o_shutdown);
+} /* }}} void module_register */
+
+/*
+ * vim: shiftwidth=2 softtabstop=2 et fdm=marker
+ */
diff --git a/src/perl.c b/src/perl.c
new file mode 100644 (file)
index 0000000..f8a4822
--- /dev/null
@@ -0,0 +1,2519 @@
+/**
+ * collectd - src/perl.c
+ * Copyright (C) 2007-2009  Sebastian Harl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Sebastian Harl <sh at tokkee.org>
+ **/
+
+/*
+ * This plugin embeds a Perl interpreter into collectd and provides an
+ * interface for collectd plugins written in perl.
+ */
+
+/* do not automatically get the thread specific perl interpreter */
+#define PERL_NO_GET_CONTEXT
+
+#define DONT_POISON_SPRINTF_YET 1
+#include "collectd.h"
+#undef DONT_POISON_SPRINTF_YET
+
+#include "configfile.h"
+
+#if HAVE_STDBOOL_H
+# include <stdbool.h>
+#endif
+
+#include <EXTERN.h>
+#include <perl.h>
+
+#if defined(COLLECT_DEBUG) && COLLECT_DEBUG && defined(__GNUC__) && __GNUC__
+# pragma GCC poison sprintf
+#endif
+
+#include <XSUB.h>
+
+/* Some versions of Perl define their own version of DEBUG... :-/ */
+#ifdef DEBUG
+# undef DEBUG
+#endif /* DEBUG */
+
+/* ... while we want the definition found in plugin.h. */
+#include "plugin.h"
+#include "common.h"
+
+#include "filter_chain.h"
+
+#include <pthread.h>
+
+#if !defined(USE_ITHREADS)
+# error "Perl does not support ithreads!"
+#endif /* !defined(USE_ITHREADS) */
+
+/* clear the Perl sub's stack frame
+ * (this should only be used inside an XSUB) */
+#define CLEAR_STACK_FRAME PL_stack_sp = PL_stack_base + *PL_markstack_ptr
+
+#define PLUGIN_INIT     0
+#define PLUGIN_READ     1
+#define PLUGIN_WRITE    2
+#define PLUGIN_SHUTDOWN 3
+#define PLUGIN_LOG      4
+#define PLUGIN_NOTIF    5
+#define PLUGIN_FLUSH    6
+
+#define PLUGIN_TYPES    7
+
+#define PLUGIN_CONFIG   254
+#define PLUGIN_DATASET  255
+
+#define FC_MATCH  0
+#define FC_TARGET 1
+
+#define FC_TYPES  2
+
+#define FC_CB_CREATE  0
+#define FC_CB_DESTROY 1
+#define FC_CB_EXEC    2
+
+#define FC_CB_TYPES   3
+
+#define log_debug(...) DEBUG ("perl: " __VA_ARGS__)
+#define log_info(...) INFO ("perl: " __VA_ARGS__)
+#define log_warn(...) WARNING ("perl: " __VA_ARGS__)
+#define log_err(...) ERROR ("perl: " __VA_ARGS__)
+
+/* this is defined in DynaLoader.a */
+void boot_DynaLoader (PerlInterpreter *, CV *);
+
+static XS (Collectd_plugin_register_ds);
+static XS (Collectd_plugin_unregister_ds);
+static XS (Collectd_plugin_dispatch_values);
+static XS (Collectd__plugin_write);
+static XS (Collectd__plugin_flush);
+static XS (Collectd_plugin_dispatch_notification);
+static XS (Collectd_plugin_log);
+static XS (Collectd__fc_register);
+static XS (Collectd_call_by_name);
+
+/*
+ * private data types
+ */
+
+typedef struct c_ithread_s {
+       /* the thread's Perl interpreter */
+       PerlInterpreter *interp;
+
+       /* double linked list of threads */
+       struct c_ithread_s *prev;
+       struct c_ithread_s *next;
+} c_ithread_t;
+
+typedef struct {
+       c_ithread_t *head;
+       c_ithread_t *tail;
+
+#if COLLECT_DEBUG
+       /* some usage stats */
+       int number_of_threads;
+#endif /* COLLECT_DEBUG */
+
+       pthread_mutex_t mutex;
+} c_ithread_list_t;
+
+/* name / user_data for Perl matches / targets */
+typedef struct {
+       char *name;
+       SV   *user_data;
+} pfc_user_data_t;
+
+#define PFC_USER_DATA_FREE(data) \
+       do { \
+               sfree ((data)->name); \
+               if (NULL != (data)->user_data) \
+                       sv_free ((data)->user_data); \
+               sfree (data); \
+       } while (0)
+
+/*
+ * Public variable
+ */
+extern char **environ;
+
+/*
+ * private variables
+ */
+
+/* if perl_threads != NULL perl_threads->head must
+ * point to the "base" thread */
+static c_ithread_list_t *perl_threads = NULL;
+
+/* the key used to store each pthread's ithread */
+static pthread_key_t perl_thr_key;
+
+static int    perl_argc = 0;
+static char **perl_argv = NULL;
+
+static char base_name[DATA_MAX_NAME_LEN] = "";
+
+static struct {
+       char name[64];
+       XS ((*f));
+} api[] =
+{
+       { "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 },
+       { "Collectd::_plugin_write",              Collectd__plugin_write },
+       { "Collectd::_plugin_flush",              Collectd__plugin_flush },
+       { "Collectd::plugin_dispatch_notification",
+               Collectd_plugin_dispatch_notification },
+       { "Collectd::plugin_log",                 Collectd_plugin_log },
+       { "Collectd::_fc_register",               Collectd__fc_register },
+       { "Collectd::call_by_name",               Collectd_call_by_name },
+       { "", NULL }
+};
+
+struct {
+       char name[64];
+       int  value;
+} constants[] =
+{
+       { "Collectd::TYPE_INIT",          PLUGIN_INIT },
+       { "Collectd::TYPE_READ",          PLUGIN_READ },
+       { "Collectd::TYPE_WRITE",         PLUGIN_WRITE },
+       { "Collectd::TYPE_SHUTDOWN",      PLUGIN_SHUTDOWN },
+       { "Collectd::TYPE_LOG",           PLUGIN_LOG },
+       { "Collectd::TYPE_NOTIF",         PLUGIN_NOTIF },
+       { "Collectd::TYPE_FLUSH",         PLUGIN_FLUSH },
+       { "Collectd::TYPE_CONFIG",        PLUGIN_CONFIG },
+       { "Collectd::TYPE_DATASET",       PLUGIN_DATASET },
+       { "Collectd::DS_TYPE_COUNTER",    DS_TYPE_COUNTER },
+       { "Collectd::DS_TYPE_GAUGE",      DS_TYPE_GAUGE },
+       { "Collectd::DS_TYPE_DERIVE",     DS_TYPE_DERIVE },
+       { "Collectd::DS_TYPE_ABSOLUTE",   DS_TYPE_ABSOLUTE },
+       { "Collectd::LOG_ERR",            LOG_ERR },
+       { "Collectd::LOG_WARNING",        LOG_WARNING },
+       { "Collectd::LOG_NOTICE",         LOG_NOTICE },
+       { "Collectd::LOG_INFO",           LOG_INFO },
+       { "Collectd::LOG_DEBUG",          LOG_DEBUG },
+       { "Collectd::FC_MATCH",           FC_MATCH },
+       { "Collectd::FC_TARGET",          FC_TARGET },
+       { "Collectd::FC_CB_CREATE",       FC_CB_CREATE },
+       { "Collectd::FC_CB_DESTROY",      FC_CB_DESTROY },
+       { "Collectd::FC_CB_EXEC",         FC_CB_EXEC },
+       { "Collectd::FC_MATCH_NO_MATCH",  FC_MATCH_NO_MATCH },
+       { "Collectd::FC_MATCH_MATCHES",   FC_MATCH_MATCHES },
+       { "Collectd::FC_TARGET_CONTINUE", FC_TARGET_CONTINUE },
+       { "Collectd::FC_TARGET_STOP",     FC_TARGET_STOP },
+       { "Collectd::FC_TARGET_RETURN",   FC_TARGET_RETURN },
+       { "Collectd::NOTIF_FAILURE",      NOTIF_FAILURE },
+       { "Collectd::NOTIF_WARNING",      NOTIF_WARNING },
+       { "Collectd::NOTIF_OKAY",         NOTIF_OKAY },
+       { "", 0 }
+};
+
+struct {
+       char  name[64];
+       char *var;
+} g_strings[] =
+{
+       { "Collectd::hostname_g", hostname_g },
+       { "", NULL }
+};
+
+/*
+ * Helper functions for data type conversion.
+ */
+
+/*
+ * data source:
+ * [
+ *   {
+ *     name => $ds_name,
+ *     type => $ds_type,
+ *     min  => $ds_min,
+ *     max  => $ds_max
+ *   },
+ *   ...
+ * ]
+ */
+static int hv2data_source (pTHX_ HV *hash, data_source_t *ds)
+{
+       SV **tmp = NULL;
+
+       if ((NULL == hash) || (NULL == ds))
+               return -1;
+
+       if (NULL != (tmp = hv_fetch (hash, "name", 4, 0))) {
+               sstrncpy (ds->name, SvPV_nolen (*tmp), sizeof (ds->name));
+       }
+       else {
+               log_err ("hv2data_source: No DS name given.");
+               return -1;
+       }
+
+       if (NULL != (tmp = hv_fetch (hash, "type", 4, 0))) {
+               ds->type = SvIV (*tmp);
+
+               if ((DS_TYPE_COUNTER != ds->type)
+                               && (DS_TYPE_GAUGE != ds->type)
+                               && (DS_TYPE_DERIVE != ds->type)
+                               && (DS_TYPE_ABSOLUTE != ds->type)) {
+                       log_err ("hv2data_source: Invalid DS type.");
+                       return -1;
+               }
+       }
+       else {
+               ds->type = DS_TYPE_COUNTER;
+       }
+
+       if (NULL != (tmp = hv_fetch (hash, "min", 3, 0)))
+               ds->min = SvNV (*tmp);
+       else
+               ds->min = NAN;
+
+       if (NULL != (tmp = hv_fetch (hash, "max", 3, 0)))
+               ds->max = SvNV (*tmp);
+       else
+               ds->max = NAN;
+       return 0;
+} /* static int hv2data_source (HV *, data_source_t *) */
+
+static int av2value (pTHX_ char *name, AV *array, value_t *value, int len)
+{
+       const data_set_t *ds;
+
+       int i = 0;
+
+       if ((NULL == name) || (NULL == array) || (NULL == value))
+               return -1;
+
+       if (av_len (array) < len - 1)
+               len = av_len (array) + 1;
+
+       if (0 >= len)
+               return -1;
+
+       ds = plugin_get_ds (name);
+       if (NULL == ds) {
+               log_err ("av2value: Unknown dataset \"%s\"", name);
+               return -1;
+       }
+
+       if (ds->ds_num < len) {
+               log_warn ("av2value: Value length exceeds data set length.");
+               len = ds->ds_num;
+       }
+
+       for (i = 0; i < len; ++i) {
+               SV **tmp = av_fetch (array, i, 0);
+
+               if (NULL != tmp) {
+                       if (DS_TYPE_COUNTER == ds->ds[i].type)
+                               value[i].counter = SvIV (*tmp);
+                       else if (DS_TYPE_GAUGE == ds->ds[i].type)
+                               value[i].gauge = SvNV (*tmp);
+                       else if (DS_TYPE_DERIVE == ds->ds[i].type)
+                               value[i].derive = SvIV (*tmp);
+                       else if (DS_TYPE_ABSOLUTE == ds->ds[i].type)
+                               value[i].absolute = SvIV (*tmp);
+               }
+               else {
+                       return -1;
+               }
+       }
+       return len;
+} /* static int av2value (char *, AV *, value_t *, int) */
+
+/*
+ * value list:
+ * {
+ *   values => [ @values ],
+ *   time   => $time,
+ *   host   => $host,
+ *   plugin => $plugin,
+ *   plugin_instance => $pinstance,
+ *   type_instance   => $tinstance,
+ * }
+ */
+static int hv2value_list (pTHX_ HV *hash, value_list_t *vl)
+{
+       SV **tmp;
+
+       if ((NULL == hash) || (NULL == vl))
+               return -1;
+
+       if (NULL == (tmp = hv_fetch (hash, "type", 4, 0))) {
+               log_err ("hv2value_list: No type given.");
+               return -1;
+       }
+
+       sstrncpy (vl->type, SvPV_nolen (*tmp), sizeof (vl->type));
+
+       if ((NULL == (tmp = hv_fetch (hash, "values", 6, 0)))
+                       || (! (SvROK (*tmp) && (SVt_PVAV == SvTYPE (SvRV (*tmp)))))) {
+               log_err ("hv2value_list: No valid values given.");
+               return -1;
+       }
+
+       {
+               AV  *array = (AV *)SvRV (*tmp);
+               int len    = av_len (array) + 1;
+
+               if (len <= 0)
+                       return -1;
+
+               vl->values     = (value_t *)smalloc (len * sizeof (value_t));
+               vl->values_len = av2value (aTHX_ vl->type, (AV *)SvRV (*tmp),
+                               vl->values, len);
+
+               if (-1 == vl->values_len) {
+                       sfree (vl->values);
+                       return -1;
+               }
+       }
+
+       if (NULL != (tmp = hv_fetch (hash, "time", 4, 0)))
+       {
+               double t = SvNV (*tmp);
+               vl->time = DOUBLE_TO_CDTIME_T (t);
+       }
+
+       if (NULL != (tmp = hv_fetch (hash, "interval", 8, 0)))
+       {
+               double t = SvNV (*tmp);
+               vl->interval = DOUBLE_TO_CDTIME_T (t);
+       }
+
+       if (NULL != (tmp = hv_fetch (hash, "host", 4, 0)))
+               sstrncpy (vl->host, SvPV_nolen (*tmp), sizeof (vl->host));
+       else
+               sstrncpy (vl->host, hostname_g, sizeof (vl->host));
+
+       if (NULL != (tmp = hv_fetch (hash, "plugin", 6, 0)))
+               sstrncpy (vl->plugin, SvPV_nolen (*tmp), sizeof (vl->plugin));
+
+       if (NULL != (tmp = hv_fetch (hash, "plugin_instance", 15, 0)))
+               sstrncpy (vl->plugin_instance, SvPV_nolen (*tmp),
+                               sizeof (vl->plugin_instance));
+
+       if (NULL != (tmp = hv_fetch (hash, "type_instance", 13, 0)))
+               sstrncpy (vl->type_instance, SvPV_nolen (*tmp),
+                               sizeof (vl->type_instance));
+       return 0;
+} /* static int hv2value_list (pTHX_ HV *, value_list_t *) */
+
+static int av2data_set (pTHX_ AV *array, char *name, data_set_t *ds)
+{
+       int len, i;
+
+       if ((NULL == array) || (NULL == name) || (NULL == ds))
+               return -1;
+
+       len = av_len (array);
+
+       if (-1 == len) {
+               log_err ("av2data_set: Invalid data set.");
+               return -1;
+       }
+
+       ds->ds = (data_source_t *)smalloc ((len + 1) * sizeof (data_source_t));
+       ds->ds_num = len + 1;
+
+       for (i = 0; i <= len; ++i) {
+               SV **elem = av_fetch (array, i, 0);
+
+               if (NULL == elem) {
+                       log_err ("av2data_set: Failed to fetch data source %i.", i);
+                       return -1;
+               }
+
+               if (! (SvROK (*elem) && (SVt_PVHV == SvTYPE (SvRV (*elem))))) {
+                       log_err ("av2data_set: Invalid data source.");
+                       return -1;
+               }
+
+               if (-1 == hv2data_source (aTHX_ (HV *)SvRV (*elem), &ds->ds[i]))
+                       return -1;
+
+               log_debug ("av2data_set: "
+                               "DS.name = \"%s\", DS.type = %i, DS.min = %f, DS.max = %f",
+                               ds->ds[i].name, ds->ds[i].type, ds->ds[i].min, ds->ds[i].max);
+       }
+
+       sstrncpy (ds->type, name, sizeof (ds->type));
+       return 0;
+} /* static int av2data_set (pTHX_ AV *, data_set_t *) */
+
+/*
+ * notification:
+ * {
+ *   severity => $severity,
+ *   time     => $time,
+ *   message  => $msg,
+ *   host     => $host,
+ *   plugin   => $plugin,
+ *   type     => $type,
+ *   plugin_instance => $instance,
+ *   type_instance   => $type_instance,
+ *   meta     => [ { name => <name>, value => <value> }, ... ]
+ * }
+ */
+static int av2notification_meta (pTHX_ AV *array, notification_meta_t **meta)
+{
+       notification_meta_t **m = meta;
+
+       int len = av_len (array);
+       int i;
+
+       for (i = 0; i <= len; ++i) {
+               SV **tmp = av_fetch (array, i, 0);
+               HV  *hash;
+
+               if (NULL == tmp)
+                       return -1;
+
+               if (! (SvROK (*tmp) && (SVt_PVHV == SvTYPE (SvRV (*tmp))))) {
+                       log_warn ("av2notification_meta: Skipping invalid "
+                                       "meta information.");
+                       continue;
+               }
+
+               hash = (HV *)SvRV (*tmp);
+
+               *m = (notification_meta_t *)smalloc (sizeof (**m));
+
+               if (NULL == (tmp = hv_fetch (hash, "name", 4, 0))) {
+                       log_warn ("av2notification_meta: Skipping invalid "
+                                       "meta information.");
+                       free (*m);
+                       continue;
+               }
+               sstrncpy ((*m)->name, SvPV_nolen (*tmp), sizeof ((*m)->name));
+
+               if (NULL == (tmp = hv_fetch (hash, "value", 5, 0))) {
+                       log_warn ("av2notification_meta: Skipping invalid "
+                                       "meta information.");
+                       free ((*m)->name);
+                       free (*m);
+                       continue;
+               }
+
+               if (SvNOK (*tmp)) {
+                       (*m)->nm_value.nm_double = SvNVX (*tmp);
+                       (*m)->type = NM_TYPE_DOUBLE;
+               }
+               else if (SvUOK (*tmp)) {
+                       (*m)->nm_value.nm_unsigned_int = SvUVX (*tmp);
+                       (*m)->type = NM_TYPE_UNSIGNED_INT;
+               }
+               else if (SvIOK (*tmp)) {
+                       (*m)->nm_value.nm_signed_int = SvIVX (*tmp);
+                       (*m)->type = NM_TYPE_SIGNED_INT;
+               }
+               else {
+                       (*m)->nm_value.nm_string = sstrdup (SvPV_nolen (*tmp));
+                       (*m)->type = NM_TYPE_STRING;
+               }
+
+               (*m)->next = NULL;
+               m = &((*m)->next);
+       }
+       return 0;
+} /* static int av2notification_meta (AV *, notification_meta_t *) */
+
+static int hv2notification (pTHX_ HV *hash, notification_t *n)
+{
+       SV **tmp = NULL;
+
+       if ((NULL == hash) || (NULL == n))
+               return -1;
+
+       if (NULL != (tmp = hv_fetch (hash, "severity", 8, 0)))
+               n->severity = SvIV (*tmp);
+       else
+               n->severity = NOTIF_FAILURE;
+
+       if (NULL != (tmp = hv_fetch (hash, "time", 4, 0)))
+       {
+               double t = SvNV (*tmp);
+               n->time = DOUBLE_TO_CDTIME_T (t);
+       }
+       else
+               n->time = cdtime ();
+
+       if (NULL != (tmp = hv_fetch (hash, "message", 7, 0)))
+               sstrncpy (n->message, SvPV_nolen (*tmp), sizeof (n->message));
+
+       if (NULL != (tmp = hv_fetch (hash, "host", 4, 0)))
+               sstrncpy (n->host, SvPV_nolen (*tmp), sizeof (n->host));
+       else
+               sstrncpy (n->host, hostname_g, sizeof (n->host));
+
+       if (NULL != (tmp = hv_fetch (hash, "plugin", 6, 0)))
+               sstrncpy (n->plugin, SvPV_nolen (*tmp), sizeof (n->plugin));
+
+       if (NULL != (tmp = hv_fetch (hash, "plugin_instance", 15, 0)))
+               sstrncpy (n->plugin_instance, SvPV_nolen (*tmp),
+                               sizeof (n->plugin_instance));
+
+       if (NULL != (tmp = hv_fetch (hash, "type", 4, 0)))
+               sstrncpy (n->type, SvPV_nolen (*tmp), sizeof (n->type));
+
+       if (NULL != (tmp = hv_fetch (hash, "type_instance", 13, 0)))
+               sstrncpy (n->type_instance, SvPV_nolen (*tmp),
+                               sizeof (n->type_instance));
+
+       n->meta = NULL;
+       while (NULL != (tmp = hv_fetch (hash, "meta", 4, 0))) {
+               if (! (SvROK (*tmp) && (SVt_PVAV == SvTYPE (SvRV (*tmp))))) {
+                       log_warn ("hv2notification: Ignoring invalid meta information.");
+                       break;
+               }
+
+               if (0 != av2notification_meta (aTHX_ (AV *)SvRV (*tmp), &n->meta)) {
+                       plugin_notification_meta_free (n->meta);
+                       n->meta = NULL;
+                       return -1;
+               }
+               break;
+       }
+       return 0;
+} /* static int hv2notification (pTHX_ HV *, notification_t *) */
+
+static int data_set2av (pTHX_ data_set_t *ds, AV *array)
+{
+       int i = 0;
+
+       if ((NULL == ds) || (NULL == array))
+               return -1;
+
+       av_extend (array, ds->ds_num);
+
+       for (i = 0; i < ds->ds_num; ++i) {
+               HV *source = newHV ();
+
+               if (NULL == hv_store (source, "name", 4,
+                               newSVpv (ds->ds[i].name, 0), 0))
+                       return -1;
+
+               if (NULL == hv_store (source, "type", 4, newSViv (ds->ds[i].type), 0))
+                       return -1;
+
+               if (! isnan (ds->ds[i].min))
+                       if (NULL == hv_store (source, "min", 3,
+                                       newSVnv (ds->ds[i].min), 0))
+                               return -1;
+
+               if (! isnan (ds->ds[i].max))
+                       if (NULL == hv_store (source, "max", 3,
+                                       newSVnv (ds->ds[i].max), 0))
+                               return -1;
+
+               if (NULL == av_store (array, i, newRV_noinc ((SV *)source)))
+                       return -1;
+       }
+       return 0;
+} /* static int data_set2av (data_set_t *, AV *) */
+
+static int value_list2hv (pTHX_ value_list_t *vl, data_set_t *ds, HV *hash)
+{
+       AV *values = NULL;
+
+       int i   = 0;
+       int len = 0;
+
+       if ((NULL == vl) || (NULL == ds) || (NULL == hash))
+               return -1;
+
+       len = vl->values_len;
+
+       if (ds->ds_num < len) {
+               log_warn ("value2av: Value length exceeds data set length.");
+               len = ds->ds_num;
+       }
+
+       values = newAV ();
+       av_extend (values, len - 1);
+
+       for (i = 0; i < len; ++i) {
+               SV *val = NULL;
+
+               if (DS_TYPE_COUNTER == ds->ds[i].type)
+                       val = newSViv (vl->values[i].counter);
+               else if (DS_TYPE_GAUGE == ds->ds[i].type)
+                       val = newSVnv (vl->values[i].gauge);
+               else if (DS_TYPE_DERIVE == ds->ds[i].type)
+                       val = newSViv (vl->values[i].derive);
+               else if (DS_TYPE_ABSOLUTE == ds->ds[i].type)
+                       val = newSViv (vl->values[i].absolute);
+
+               if (NULL == av_store (values, i, val)) {
+                       av_undef (values);
+                       return -1;
+               }
+       }
+
+       if (NULL == hv_store (hash, "values", 6, newRV_noinc ((SV *)values), 0))
+               return -1;
+
+       if (0 != vl->time)
+       {
+               double t = CDTIME_T_TO_DOUBLE (vl->time);
+               if (NULL == hv_store (hash, "time", 4, newSVnv (t), 0))
+                       return -1;
+       }
+
+       {
+               double t = CDTIME_T_TO_DOUBLE (vl->interval);
+               if (NULL == hv_store (hash, "interval", 8, newSVnv (t), 0))
+                       return -1;
+       }
+
+       if ('\0' != vl->host[0])
+               if (NULL == hv_store (hash, "host", 4, newSVpv (vl->host, 0), 0))
+                       return -1;
+
+       if ('\0' != vl->plugin[0])
+               if (NULL == hv_store (hash, "plugin", 6, newSVpv (vl->plugin, 0), 0))
+                       return -1;
+
+       if ('\0' != vl->plugin_instance[0])
+               if (NULL == hv_store (hash, "plugin_instance", 15,
+                               newSVpv (vl->plugin_instance, 0), 0))
+                       return -1;
+
+       if ('\0' != vl->type[0])
+               if (NULL == hv_store (hash, "type", 4, newSVpv (vl->type, 0), 0))
+                       return -1;
+
+       if ('\0' != vl->type_instance[0])
+               if (NULL == hv_store (hash, "type_instance", 13,
+                               newSVpv (vl->type_instance, 0), 0))
+                       return -1;
+       return 0;
+} /* static int value2av (value_list_t *, data_set_t *, HV *) */
+
+static int notification_meta2av (pTHX_ notification_meta_t *meta, AV *array)
+{
+       int meta_num = 0;
+       int i;
+
+       while (meta) {
+               ++meta_num;
+               meta = meta->next;
+       }
+
+       av_extend (array, meta_num);
+
+       for (i = 0; NULL != meta; meta = meta->next, ++i) {
+               HV *m = newHV ();
+               SV *value;
+
+               if (NULL == hv_store (m, "name", 4, newSVpv (meta->name, 0), 0))
+                       return -1;
+
+               if (NM_TYPE_STRING == meta->type)
+                       value = newSVpv (meta->nm_value.nm_string, 0);
+               else if (NM_TYPE_SIGNED_INT == meta->type)
+                       value = newSViv (meta->nm_value.nm_signed_int);
+               else if (NM_TYPE_UNSIGNED_INT == meta->type)
+                       value = newSVuv (meta->nm_value.nm_unsigned_int);
+               else if (NM_TYPE_DOUBLE == meta->type)
+                       value = newSVnv (meta->nm_value.nm_double);
+               else if (NM_TYPE_BOOLEAN == meta->type)
+                       value = meta->nm_value.nm_boolean ? &PL_sv_yes : &PL_sv_no;
+               else
+                       return -1;
+
+               if (NULL == hv_store (m, "value", 5, value, 0)) {
+                       sv_free (value);
+                       return -1;
+               }
+
+               if (NULL == av_store (array, i, newRV_noinc ((SV *)m))) {
+                       hv_clear (m);
+                       hv_undef (m);
+                       return -1;
+               }
+       }
+       return 0;
+} /* static int notification_meta2av (notification_meta_t *, AV *) */
+
+static int notification2hv (pTHX_ notification_t *n, HV *hash)
+{
+       if (NULL == hv_store (hash, "severity", 8, newSViv (n->severity), 0))
+               return -1;
+
+       if (0 != n->time)
+       {
+               double t = CDTIME_T_TO_DOUBLE (n->time);
+               if (NULL == hv_store (hash, "time", 4, newSVnv (t), 0))
+                       return -1;
+       }
+
+       if ('\0' != *n->message)
+               if (NULL == hv_store (hash, "message", 7, newSVpv (n->message, 0), 0))
+                       return -1;
+
+       if ('\0' != *n->host)
+               if (NULL == hv_store (hash, "host", 4, newSVpv (n->host, 0), 0))
+                       return -1;
+
+       if ('\0' != *n->plugin)
+               if (NULL == hv_store (hash, "plugin", 6, newSVpv (n->plugin, 0), 0))
+                       return -1;
+
+       if ('\0' != *n->plugin_instance)
+               if (NULL == hv_store (hash, "plugin_instance", 15,
+                               newSVpv (n->plugin_instance, 0), 0))
+                       return -1;
+
+       if ('\0' != *n->type)
+               if (NULL == hv_store (hash, "type", 4, newSVpv (n->type, 0), 0))
+                       return -1;
+
+       if ('\0' != *n->type_instance)
+               if (NULL == hv_store (hash, "type_instance", 13,
+                               newSVpv (n->type_instance, 0), 0))
+                       return -1;
+
+       if (NULL != n->meta) {
+               AV *meta = newAV ();
+               if ((0 != notification_meta2av (aTHX_ n->meta, meta))
+                               || (NULL == hv_store (hash, "meta", 4,
+                                               newRV_noinc ((SV *)meta), 0))) {
+                       av_clear (meta);
+                       av_undef (meta);
+                       return -1;
+               }
+       }
+       return 0;
+} /* static int notification2hv (notification_t *, HV *) */
+
+static int oconfig_item2hv (pTHX_ oconfig_item_t *ci, HV *hash)
+{
+       int i;
+
+       AV *values;
+       AV *children;
+
+       if (NULL == hv_store (hash, "key", 3, newSVpv (ci->key, 0), 0))
+               return -1;
+
+       values = newAV ();
+       if (0 < ci->values_num)
+               av_extend (values, ci->values_num);
+
+       if (NULL == hv_store (hash, "values", 6, newRV_noinc ((SV *)values), 0)) {
+               av_clear (values);
+               av_undef (values);
+               return -1;
+       }
+
+       for (i = 0; i < ci->values_num; ++i) {
+               SV *value;
+
+               switch (ci->values[i].type) {
+                       case OCONFIG_TYPE_STRING:
+                               value = newSVpv (ci->values[i].value.string, 0);
+                               break;
+                       case OCONFIG_TYPE_NUMBER:
+                               value = newSVnv ((NV)ci->values[i].value.number);
+                               break;
+                       case OCONFIG_TYPE_BOOLEAN:
+                               value = ci->values[i].value.boolean ? &PL_sv_yes : &PL_sv_no;
+                               break;
+                       default:
+                               log_err ("oconfig_item2hv: Invalid value type %i.",
+                                               ci->values[i].type);
+                               value = &PL_sv_undef;
+               }
+
+               if (NULL == av_store (values, i, value)) {
+                       sv_free (value);
+                       return -1;
+               }
+       }
+
+       /* ignoring 'parent' member which is uninteresting in this case */
+
+       children = newAV ();
+       if (0 < ci->children_num)
+               av_extend (children, ci->children_num);
+
+       if (NULL == hv_store (hash, "children", 8, newRV_noinc ((SV *)children), 0)) {
+               av_clear (children);
+               av_undef (children);
+               return -1;
+       }
+
+       for (i = 0; i < ci->children_num; ++i) {
+               HV *child = newHV ();
+
+               if (0 != oconfig_item2hv (aTHX_ ci->children + i, child)) {
+                       hv_clear (child);
+                       hv_undef (child);
+                       return -1;
+               }
+
+               if (NULL == av_store (children, i, newRV_noinc ((SV *)child))) {
+                       hv_clear (child);
+                       hv_undef (child);
+                       return -1;
+               }
+       }
+       return 0;
+} /* static int oconfig_item2hv (pTHX_ oconfig_item_t *, HV *) */
+
+/*
+ * Internal functions.
+ */
+
+static char *get_module_name (char *buf, size_t buf_len, const char *module) {
+       int status = 0;
+       if (base_name[0] == '\0')
+               status = ssnprintf (buf, buf_len, "%s", module);
+       else
+               status = ssnprintf (buf, buf_len, "%s::%s", base_name, module);
+       if ((status < 0) || ((unsigned int)status >= buf_len))
+               return (NULL);
+       return (buf);
+} /* char *get_module_name */
+
+/*
+ * Add a plugin's data set definition.
+ */
+static int pplugin_register_data_set (pTHX_ char *name, AV *dataset)
+{
+       int ret = 0;
+
+       data_set_t ds;
+
+       if ((NULL == name) || (NULL == dataset))
+               return -1;
+
+       if (0 != av2data_set (aTHX_ dataset, name, &ds))
+               return -1;
+
+       ret = plugin_register_data_set (&ds);
+
+       free (ds.ds);
+       return ret;
+} /* static int pplugin_register_data_set (char *, SV *) */
+
+/*
+ * Remove a plugin's data set definition.
+ */
+static int pplugin_unregister_data_set (char *name)
+{
+       if (NULL == name)
+               return 0;
+       return plugin_unregister_data_set (name);
+} /* static int pplugin_unregister_data_set (char *) */
+
+/*
+ * Submit the values to the write functions.
+ */
+static int pplugin_dispatch_values (pTHX_ HV *values)
+{
+       value_list_t vl = VALUE_LIST_INIT;
+
+       int ret = 0;
+
+       if (NULL == values)
+               return -1;
+
+       if (0 != hv2value_list (aTHX_ values, &vl))
+               return -1;
+
+       ret = plugin_dispatch_values (&vl);
+
+       sfree (vl.values);
+       return ret;
+} /* static int pplugin_dispatch_values (char *, HV *) */
+
+/*
+ * Submit the values to a single write function.
+ */
+static int pplugin_write (pTHX_ const char *plugin, AV *data_set, HV *values)
+{
+       data_set_t   ds;
+       value_list_t vl = VALUE_LIST_INIT;
+
+       int ret;
+
+       if (NULL == values)
+               return -1;
+
+       if (0 != hv2value_list (aTHX_ values, &vl))
+               return -1;
+
+       if ((NULL != data_set)
+                       && (0 != av2data_set (aTHX_ data_set, vl.type, &ds)))
+               return -1;
+
+       ret = plugin_write (plugin, NULL == data_set ? NULL : &ds, &vl);
+       if (0 != ret)
+               log_warn ("Dispatching value to plugin \"%s\" failed with status %i.",
+                               NULL == plugin ? "<any>" : plugin, ret);
+
+       if (NULL != data_set)
+               sfree (ds.ds);
+       sfree (vl.values);
+       return ret;
+} /* static int pplugin_write (const char *plugin, HV *, HV *) */
+
+/*
+ * Dispatch a notification.
+ */
+static int pplugin_dispatch_notification (pTHX_ HV *notif)
+{
+       notification_t n;
+
+       int ret;
+
+       if (NULL == notif)
+               return -1;
+
+       memset (&n, 0, sizeof (n));
+
+       if (0 != hv2notification (aTHX_ notif, &n))
+               return -1;
+
+       ret = plugin_dispatch_notification (&n);
+       plugin_notification_meta_free (n.meta);
+       return ret;
+} /* static int pplugin_dispatch_notification (HV *) */
+
+/*
+ * Call all working functions of the given type.
+ */
+static int pplugin_call_all (pTHX_ int type, ...)
+{
+       int retvals = 0;
+
+       va_list ap;
+       int ret = 0;
+
+       dSP;
+
+       if ((type < 0) || (type >= PLUGIN_TYPES))
+               return -1;
+
+       va_start (ap, type);
+
+       ENTER;
+       SAVETMPS;
+
+       PUSHMARK (SP);
+
+       XPUSHs (sv_2mortal (newSViv ((IV)type)));
+
+       if (PLUGIN_WRITE == type) {
+               /*
+                * $_[0] = $plugin_type;
+                *
+                * $_[1] =
+                * [
+                *   {
+                *     name => $ds_name,
+                *     type => $ds_type,
+                *     min  => $ds_min,
+                *     max  => $ds_max
+                *   },
+                *   ...
+                * ];
+                *
+                * $_[2] =
+                * {
+                *   values => [ $v1, ... ],
+                *   time   => $time,
+                *   host   => $hostname,
+                *   plugin => $plugin,
+                *   type   => $type,
+                *   plugin_instance => $instance,
+                *   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 *);
+
+               if (-1 == data_set2av (aTHX_ ds, pds)) {
+                       av_clear (pds);
+                       av_undef (pds);
+                       pds = (AV *)&PL_sv_undef;
+                       ret = -1;
+               }
+
+               if (-1 == value_list2hv (aTHX_ vl, ds, pvl)) {
+                       hv_clear (pvl);
+                       hv_undef (pvl);
+                       pvl = (HV *)&PL_sv_undef;
+                       ret = -1;
+               }
+
+               XPUSHs (sv_2mortal (newSVpv (ds->type, 0)));
+               XPUSHs (sv_2mortal (newRV_noinc ((SV *)pds)));
+               XPUSHs (sv_2mortal (newRV_noinc ((SV *)pvl)));
+       }
+       else if (PLUGIN_LOG == type) {
+               /*
+                * $_[0] = $level;
+                *
+                * $_[1] = $message;
+                */
+               XPUSHs (sv_2mortal (newSViv (va_arg (ap, int))));
+               XPUSHs (sv_2mortal (newSVpv (va_arg (ap, char *), 0)));
+       }
+       else if (PLUGIN_NOTIF == type) {
+               /*
+                * $_[0] =
+                * {
+                *   severity => $severity,
+                *   time     => $time,
+                *   message  => $msg,
+                *   host     => $host,
+                *   plugin   => $plugin,
+                *   type     => $type,
+                *   plugin_instance => $instance,
+                *   type_instance   => $type_instance
+                * };
+                */
+               notification_t *n;
+               HV *notif = newHV ();
+
+               n = va_arg (ap, notification_t *);
+
+               if (-1 == notification2hv (aTHX_ n, notif)) {
+                       hv_clear (notif);
+                       hv_undef (notif);
+                       notif = (HV *)&PL_sv_undef;
+                       ret = -1;
+               }
+
+               XPUSHs (sv_2mortal (newRV_noinc ((SV *)notif)));
+       }
+       else if (PLUGIN_FLUSH == type) {
+               cdtime_t timeout;
+
+               /*
+                * $_[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)));
+       }
+
+       PUTBACK;
+
+       retvals = call_pv ("Collectd::plugin_call_all", G_SCALAR);
+
+       SPAGAIN;
+       if (0 < retvals) {
+               SV *tmp = POPs;
+               if (! SvTRUE (tmp))
+                       ret = -1;
+       }
+
+       PUTBACK;
+       FREETMPS;
+       LEAVE;
+
+       va_end (ap);
+       return ret;
+} /* static int pplugin_call_all (int, ...) */
+
+/*
+ * collectd's perl interpreter based thread implementation.
+ *
+ * This has been inspired by Perl's ithreads introduced in version 5.6.0.
+ */
+
+/* must be called with perl_threads->mutex locked */
+static void c_ithread_destroy (c_ithread_t *ithread)
+{
+       dTHXa (ithread->interp);
+
+       assert (NULL != perl_threads);
+
+       PERL_SET_CONTEXT (aTHX);
+       log_debug ("Shutting down Perl interpreter %p...", aTHX);
+
+#if COLLECT_DEBUG
+       sv_report_used ();
+
+       --perl_threads->number_of_threads;
+#endif /* COLLECT_DEBUG */
+
+       perl_destruct (aTHX);
+       perl_free (aTHX);
+
+       if (NULL == ithread->prev)
+               perl_threads->head = ithread->next;
+       else
+               ithread->prev->next = ithread->next;
+
+       if (NULL == ithread->next)
+               perl_threads->tail = ithread->prev;
+       else
+               ithread->next->prev = ithread->prev;
+
+       sfree (ithread);
+       return;
+} /* static void c_ithread_destroy (c_ithread_t *) */
+
+static void c_ithread_destructor (void *arg)
+{
+       c_ithread_t *ithread = (c_ithread_t *)arg;
+       c_ithread_t *t = NULL;
+
+       if (NULL == perl_threads)
+               return;
+
+       pthread_mutex_lock (&perl_threads->mutex);
+
+       for (t = perl_threads->head; NULL != t; t = t->next)
+               if (t == ithread)
+                       break;
+
+       /* the ithread no longer exists */
+       if (NULL == t)
+               return;
+
+       c_ithread_destroy (ithread);
+
+       pthread_mutex_unlock (&perl_threads->mutex);
+       return;
+} /* static void c_ithread_destructor (void *) */
+
+/* must be called with perl_threads->mutex locked */
+static c_ithread_t *c_ithread_create (PerlInterpreter *base)
+{
+       c_ithread_t *t = NULL;
+       dTHXa (NULL);
+
+       assert (NULL != perl_threads);
+
+       t = (c_ithread_t *)smalloc (sizeof (c_ithread_t));
+       memset (t, 0, sizeof (c_ithread_t));
+
+       t->interp = (NULL == base)
+               ? NULL
+               : perl_clone (base, CLONEf_KEEP_PTR_TABLE);
+
+       aTHX = t->interp;
+
+       if ((NULL != base) && (NULL != PL_endav)) {
+               av_clear (PL_endav);
+               av_undef (PL_endav);
+               PL_endav = Nullav;
+       }
+
+#if COLLECT_DEBUG
+       ++perl_threads->number_of_threads;
+#endif /* COLLECT_DEBUG */
+
+       t->next = NULL;
+
+       if (NULL == perl_threads->tail) {
+               perl_threads->head = t;
+               t->prev = NULL;
+       }
+       else {
+               perl_threads->tail->next = t;
+               t->prev = perl_threads->tail;
+       }
+
+       perl_threads->tail = t;
+
+       pthread_setspecific (perl_thr_key, (const void *)t);
+       return t;
+} /* static c_ithread_t *c_ithread_create (PerlInterpreter *) */
+
+/*
+ * Filter chains implementation.
+ */
+
+static int fc_call (pTHX_ int type, int cb_type, pfc_user_data_t *data, ...)
+{
+       int retvals = 0;
+
+       va_list ap;
+       int ret = 0;
+
+       notification_meta_t **meta  = NULL;
+       AV                   *pmeta = NULL;
+
+       dSP;
+
+       if ((type < 0) || (type >= FC_TYPES))
+               return -1;
+
+       if ((cb_type < 0) || (cb_type >= FC_CB_TYPES))
+               return -1;
+
+       va_start (ap, data);
+
+       ENTER;
+       SAVETMPS;
+
+       PUSHMARK (SP);
+
+       XPUSHs (sv_2mortal (newSViv ((IV)type)));
+       XPUSHs (sv_2mortal (newSVpv (data->name, 0)));
+       XPUSHs (sv_2mortal (newSViv ((IV)cb_type)));
+
+       if (FC_CB_CREATE == cb_type) {
+               /*
+                * $_[0] = $ci;
+                * $_[1] = $user_data;
+                */
+               oconfig_item_t *ci;
+               HV *config = newHV ();
+
+               ci = va_arg (ap, oconfig_item_t *);
+
+               if (0 != oconfig_item2hv (aTHX_ ci, config)) {
+                       hv_clear (config);
+                       hv_undef (config);
+                       config = (HV *)&PL_sv_undef;
+                       ret = -1;
+               }
+
+               XPUSHs (sv_2mortal (newRV_noinc ((SV *)config)));
+       }
+       else if (FC_CB_DESTROY == cb_type) {
+               /*
+                * $_[1] = $user_data;
+                */
+
+               /* nothing to be done - the user data pointer
+                * is pushed onto the stack later */
+       }
+       else if (FC_CB_EXEC == cb_type) {
+               /*
+                * $_[0] = $ds;
+                * $_[1] = $vl;
+                * $_[2] = $meta;
+                * $_[3] = $user_data;
+                */
+               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 *);
+               meta = va_arg (ap, notification_meta_t **);
+
+               if (0 != data_set2av (aTHX_ ds, pds)) {
+                       av_clear (pds);
+                       av_undef (pds);
+                       pds = (AV *)&PL_sv_undef;
+                       ret = -1;
+               }
+
+               if (0 != value_list2hv (aTHX_ vl, ds, pvl)) {
+                       hv_clear (pvl);
+                       hv_undef (pvl);
+                       pvl = (HV *)&PL_sv_undef;
+                       ret = -1;
+               }
+
+               if (NULL != meta) {
+                       pmeta = newAV ();
+
+                       if (0 != notification_meta2av (aTHX_ *meta, pmeta)) {
+                               av_clear (pmeta);
+                               av_undef (pmeta);
+                               pmeta = (AV *)&PL_sv_undef;
+                               ret = -1;
+                       }
+               }
+               else {
+                       pmeta = (AV *)&PL_sv_undef;
+               }
+
+               XPUSHs (sv_2mortal (newRV_noinc ((SV *)pds)));
+               XPUSHs (sv_2mortal (newRV_noinc ((SV *)pvl)));
+               XPUSHs (sv_2mortal (newRV_noinc ((SV *)pmeta)));
+       }
+
+       XPUSHs (sv_2mortal (newRV_inc (data->user_data)));
+
+       PUTBACK;
+
+       retvals = call_pv ("Collectd::fc_call", G_SCALAR);
+
+       if ((FC_CB_EXEC == cb_type) && (meta != NULL)) {
+               assert (pmeta != NULL);
+
+               plugin_notification_meta_free (*meta);
+               av2notification_meta (aTHX_ pmeta, meta);
+       }
+
+       SPAGAIN;
+       if (0 < retvals) {
+               SV *tmp = POPs;
+
+               /* the exec callbacks return a status, while
+                * the others return a boolean value */
+               if (FC_CB_EXEC == cb_type)
+                       ret = SvIV (tmp);
+               else if (! SvTRUE (tmp))
+                       ret = -1;
+       }
+
+       PUTBACK;
+       FREETMPS;
+       LEAVE;
+
+       va_end (ap);
+       return ret;
+} /* static int fc_call (int, int, pfc_user_data_t *, ...) */
+
+static int fc_create (int type, const oconfig_item_t *ci, void **user_data)
+{
+       pfc_user_data_t *data;
+
+       int ret = 0;
+
+       dTHX;
+
+       if (NULL == perl_threads)
+               return 0;
+
+       if (NULL == aTHX) {
+               c_ithread_t *t = NULL;
+
+               pthread_mutex_lock (&perl_threads->mutex);
+               t = c_ithread_create (perl_threads->head->interp);
+               pthread_mutex_unlock (&perl_threads->mutex);
+
+               aTHX = t->interp;
+       }
+
+       log_debug ("fc_create: c_ithread: interp = %p (active threads: %i)",
+                       aTHX, perl_threads->number_of_threads);
+
+       if ((1 != ci->values_num)
+                       || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
+               log_warn ("A \"%s\" block expects a single string argument.",
+                               (FC_MATCH == type) ? "Match" : "Target");
+               return -1;
+       }
+
+       data = (pfc_user_data_t *)smalloc (sizeof (*data));
+       data->name      = sstrdup (ci->values[0].value.string);
+       data->user_data = newSV (0);
+
+       ret = fc_call (aTHX_ type, FC_CB_CREATE, data, ci);
+
+       if (0 != ret)
+               PFC_USER_DATA_FREE (data);
+       else
+               *user_data = data;
+       return ret;
+} /* static int fc_create (int, const oconfig_item_t *, void **) */
+
+static int fc_destroy (int type, void **user_data)
+{
+       pfc_user_data_t *data = *(pfc_user_data_t **)user_data;
+
+       int ret = 0;
+
+       dTHX;
+
+       if ((NULL == perl_threads) || (NULL == data))
+               return 0;
+
+       if (NULL == aTHX) {
+               c_ithread_t *t = NULL;
+
+               pthread_mutex_lock (&perl_threads->mutex);
+               t = c_ithread_create (perl_threads->head->interp);
+               pthread_mutex_unlock (&perl_threads->mutex);
+
+               aTHX = t->interp;
+       }
+
+       log_debug ("fc_destroy: c_ithread: interp = %p (active threads: %i)",
+                       aTHX, perl_threads->number_of_threads);
+
+       ret = fc_call (aTHX_ type, FC_CB_DESTROY, data);
+
+       PFC_USER_DATA_FREE (data);
+       *user_data = NULL;
+       return ret;
+} /* static int fc_destroy (int, void **) */
+
+static int fc_exec (int type, const data_set_t *ds, const value_list_t *vl,
+               notification_meta_t **meta, void **user_data)
+{
+       pfc_user_data_t *data = *(pfc_user_data_t **)user_data;
+
+       dTHX;
+
+       if (NULL == perl_threads)
+               return 0;
+
+       assert (NULL != data);
+
+       if (NULL == aTHX) {
+               c_ithread_t *t = NULL;
+
+               pthread_mutex_lock (&perl_threads->mutex);
+               t = c_ithread_create (perl_threads->head->interp);
+               pthread_mutex_unlock (&perl_threads->mutex);
+
+               aTHX = t->interp;
+       }
+
+       log_debug ("fc_exec: c_ithread: interp = %p (active threads: %i)",
+                       aTHX, perl_threads->number_of_threads);
+
+       return fc_call (aTHX_ type, FC_CB_EXEC, data, ds, vl, meta);
+} /* static int fc_exec (int, const data_set_t *, const value_list_t *,
+               notification_meta_t **, void **) */
+
+static int pmatch_create (const oconfig_item_t *ci, void **user_data)
+{
+       return fc_create (FC_MATCH, ci, user_data);
+} /* static int pmatch_create (const oconfig_item_t *, void **) */
+
+static int pmatch_destroy (void **user_data)
+{
+       return fc_destroy (FC_MATCH, user_data);
+} /* static int pmatch_destroy (void **) */
+
+static int pmatch_match (const data_set_t *ds, const value_list_t *vl,
+               notification_meta_t **meta, void **user_data)
+{
+       return fc_exec (FC_MATCH, ds, vl, meta, user_data);
+} /* static int pmatch_match (const data_set_t *, const value_list_t *,
+               notification_meta_t **, void **) */
+
+static match_proc_t pmatch = {
+       pmatch_create, pmatch_destroy, pmatch_match
+};
+
+static int ptarget_create (const oconfig_item_t *ci, void **user_data)
+{
+       return fc_create (FC_TARGET, ci, user_data);
+} /* static int ptarget_create (const oconfig_item_t *, void **) */
+
+static int ptarget_destroy (void **user_data)
+{
+       return fc_destroy (FC_TARGET, user_data);
+} /* static int ptarget_destroy (void **) */
+
+static int ptarget_invoke (const data_set_t *ds, value_list_t *vl,
+               notification_meta_t **meta, void **user_data)
+{
+       return fc_exec (FC_TARGET, ds, vl, meta, user_data);
+} /* static int ptarget_invoke (const data_set_t *, value_list_t *,
+               notification_meta_t **, void **) */
+
+static target_proc_t ptarget = {
+       ptarget_create, ptarget_destroy, ptarget_invoke
+};
+
+/*
+ * Exported Perl API.
+ */
+
+/*
+ * Collectd::plugin_register_data_set (type, dataset).
+ *
+ * type:
+ *   type of the dataset
+ *
+ * dataset:
+ *   dataset to be registered
+ */
+static XS (Collectd_plugin_register_ds)
+{
+       SV  *data = NULL;
+       int ret   = 0;
+
+       dXSARGS;
+
+       log_warn ("Using plugin_register() to register new data-sets is "
+                       "deprecated - add new entries to a custom types.db instead.");
+
+       if (2 != items) {
+               log_err ("Usage: Collectd::plugin_register_data_set(type, dataset)");
+               XSRETURN_EMPTY;
+       }
+
+       log_debug ("Collectd::plugin_register_data_set: "
+                       "type = \"%s\", dataset = \"%s\"",
+                       SvPV_nolen (ST (0)), SvPV_nolen (ST (1)));
+
+       data = ST (1);
+
+       if (SvROK (data) && (SVt_PVAV == SvTYPE (SvRV (data)))) {
+               ret = pplugin_register_data_set (aTHX_ SvPV_nolen (ST (0)),
+                               (AV *)SvRV (data));
+       }
+       else {
+               log_err ("Collectd::plugin_register_data_set: Invalid data.");
+               XSRETURN_EMPTY;
+       }
+
+       if (0 == ret)
+               XSRETURN_YES;
+       else
+               XSRETURN_EMPTY;
+} /* static XS (Collectd_plugin_register_ds) */
+
+/*
+ * Collectd::plugin_unregister_data_set (type).
+ *
+ * type:
+ *   type of the dataset
+ */
+static XS (Collectd_plugin_unregister_ds)
+{
+       dXSARGS;
+
+       if (1 != items) {
+               log_err ("Usage: Collectd::plugin_unregister_data_set(type)");
+               XSRETURN_EMPTY;
+       }
+
+       log_debug ("Collectd::plugin_unregister_data_set: type = \"%s\"",
+                       SvPV_nolen (ST (0)));
+
+       if (0 == pplugin_unregister_data_set (SvPV_nolen (ST (0))))
+               XSRETURN_YES;
+       else
+               XSRETURN_EMPTY;
+} /* static XS (Collectd_plugin_register_ds) */
+
+/*
+ * Collectd::plugin_dispatch_values (name, values).
+ *
+ * name:
+ *   name of the plugin
+ *
+ * values:
+ *   value list to submit
+ */
+static XS (Collectd_plugin_dispatch_values)
+{
+       SV *values     = NULL;
+
+       int ret = 0;
+
+       dXSARGS;
+
+       if (1 != items) {
+               log_err ("Usage: Collectd::plugin_dispatch_values(values)");
+               XSRETURN_EMPTY;
+       }
+
+       log_debug ("Collectd::plugin_dispatch_values: values=\"%s\"",
+                       SvPV_nolen (ST (/* stack index = */ 0)));
+
+       values = ST (/* stack index = */ 0);
+
+       /* Make sure the argument is a hash reference. */
+       if (! (SvROK (values) && (SVt_PVHV == SvTYPE (SvRV (values))))) {
+               log_err ("Collectd::plugin_dispatch_values: Invalid values.");
+               XSRETURN_EMPTY;
+       }
+
+       if (NULL == values)
+               XSRETURN_EMPTY;
+
+       ret = pplugin_dispatch_values (aTHX_ (HV *)SvRV (values));
+
+       if (0 == ret)
+               XSRETURN_YES;
+       else
+               XSRETURN_EMPTY;
+} /* static XS (Collectd_plugin_dispatch_values) */
+
+/* Collectd::plugin_write (plugin, ds, vl).
+ *
+ * plugin:
+ *   name of the plugin to call, may be 'undef'
+ *
+ * ds:
+ *   data-set that describes the submitted values, may be 'undef'
+ *
+ * vl:
+ *   value-list to be written
+ */
+static XS (Collectd__plugin_write)
+{
+       char *plugin;
+       SV   *ds, *vl;
+       AV   *ds_array;
+
+       int ret;
+
+       dXSARGS;
+
+       if (3 != items) {
+               log_err ("Usage: Collectd::plugin_write(plugin, ds, vl)");
+               XSRETURN_EMPTY;
+       }
+
+       log_debug ("Collectd::plugin_write: plugin=\"%s\", ds=\"%s\", vl=\"%s\"",
+                       SvPV_nolen (ST (0)), SvOK (ST (1)) ? SvPV_nolen (ST (1)) : "",
+                       SvPV_nolen (ST (2)));
+
+       if (! SvOK (ST (0)))
+               plugin = NULL;
+       else
+               plugin = SvPV_nolen (ST (0));
+
+       ds = ST (1);
+       if (SvROK (ds) && (SVt_PVAV == SvTYPE (SvRV (ds))))
+               ds_array = (AV *)SvRV (ds);
+       else if (! SvOK (ds))
+               ds_array = NULL;
+       else {
+               log_err ("Collectd::plugin_write: Invalid data-set.");
+               XSRETURN_EMPTY;
+       }
+
+       vl = ST (2);
+       if (! (SvROK (vl) && (SVt_PVHV == SvTYPE (SvRV (vl))))) {
+               log_err ("Collectd::plugin_write: Invalid value-list.");
+               XSRETURN_EMPTY;
+       }
+
+       ret = pplugin_write (aTHX_ plugin, ds_array, (HV *)SvRV (vl));
+
+       if (0 == ret)
+               XSRETURN_YES;
+       else
+               XSRETURN_EMPTY;
+} /* static XS (Collectd__plugin_write) */
+
+/*
+ * Collectd::_plugin_flush (plugin, timeout, identifier).
+ *
+ * plugin:
+ *   name of the plugin to flush
+ *
+ * timeout:
+ *   timeout to use when flushing the data
+ *
+ * identifier:
+ *   data-set identifier to flush
+ */
+static XS (Collectd__plugin_flush)
+{
+       char *plugin  = NULL;
+       int   timeout = -1;
+       char *id      = NULL;
+
+       dXSARGS;
+
+       if (3 != items) {
+               log_err ("Usage: Collectd::_plugin_flush(plugin, timeout, id)");
+               XSRETURN_EMPTY;
+       }
+
+       if (SvOK (ST (0)))
+               plugin = SvPV_nolen (ST (0));
+
+       if (SvOK (ST (1)))
+               timeout = (int)SvIV (ST (1));
+
+       if (SvOK (ST (2)))
+               id = SvPV_nolen (ST (2));
+
+       log_debug ("Collectd::_plugin_flush: plugin = \"%s\", timeout = %i, "
+                       "id = \"%s\"", plugin, timeout, id);
+
+       if (0 == plugin_flush (plugin, timeout, id))
+               XSRETURN_YES;
+       else
+               XSRETURN_EMPTY;
+} /* static XS (Collectd__plugin_flush) */
+
+/*
+ * Collectd::plugin_dispatch_notification (notif).
+ *
+ * notif:
+ *   notification to dispatch
+ */
+static XS (Collectd_plugin_dispatch_notification)
+{
+       SV *notif = NULL;
+
+       int ret = 0;
+
+       dXSARGS;
+
+       if (1 != items) {
+               log_err ("Usage: Collectd::plugin_dispatch_notification(notif)");
+               XSRETURN_EMPTY;
+       }
+
+       log_debug ("Collectd::plugin_dispatch_notification: notif = \"%s\"",
+                       SvPV_nolen (ST (0)));
+
+       notif = ST (0);
+
+       if (! (SvROK (notif) && (SVt_PVHV == SvTYPE (SvRV (notif))))) {
+               log_err ("Collectd::plugin_dispatch_notification: Invalid notif.");
+               XSRETURN_EMPTY;
+       }
+
+       ret = pplugin_dispatch_notification (aTHX_ (HV *)SvRV (notif));
+
+       if (0 == ret)
+               XSRETURN_YES;
+       else
+               XSRETURN_EMPTY;
+} /* static XS (Collectd_plugin_dispatch_notification) */
+
+/*
+ * Collectd::plugin_log (level, message).
+ *
+ * level:
+ *   log level (LOG_DEBUG, ... LOG_ERR)
+ *
+ * message:
+ *   log message
+ */
+static XS (Collectd_plugin_log)
+{
+       dXSARGS;
+
+       if (2 != items) {
+               log_err ("Usage: Collectd::plugin_log(level, message)");
+               XSRETURN_EMPTY;
+       }
+
+       plugin_log (SvIV (ST (0)), "%s", SvPV_nolen (ST (1)));
+       XSRETURN_YES;
+} /* static XS (Collectd_plugin_log) */
+
+/*
+ * Collectd::_fc_register (type, name)
+ *
+ * type:
+ *   match | target
+ *
+ * name:
+ *   name of the match
+ */
+static XS (Collectd__fc_register)
+{
+       int   type;
+       char *name;
+
+       int ret = 0;
+
+       dXSARGS;
+
+       if (2 != items) {
+               log_err ("Usage: Collectd::_fc_register(type, name)");
+               XSRETURN_EMPTY;
+       }
+
+       type = SvIV (ST (0));
+       name = SvPV_nolen (ST (1));
+
+       if (FC_MATCH == type)
+               ret = fc_register_match (name, pmatch);
+       else if (FC_TARGET == type)
+               ret = fc_register_target (name, ptarget);
+
+       if (0 == ret)
+               XSRETURN_YES;
+       else
+               XSRETURN_EMPTY;
+} /* static XS (Collectd_fc_register) */
+
+/*
+ * Collectd::call_by_name (...).
+ *
+ * Call a Perl sub identified by its name passed through $Collectd::cb_name.
+ */
+static XS (Collectd_call_by_name)
+{
+       SV   *tmp  = NULL;
+       char *name = NULL;
+
+       if (NULL == (tmp = get_sv ("Collectd::cb_name", 0))) {
+               sv_setpv (get_sv ("@", 1), "cb_name has not been set");
+               CLEAR_STACK_FRAME;
+               return;
+       }
+
+       name = SvPV_nolen (tmp);
+
+       if (NULL == get_cv (name, 0)) {
+               sv_setpvf (get_sv ("@", 1), "unknown callback \"%s\"", name);
+               CLEAR_STACK_FRAME;
+               return;
+       }
+
+       /* simply pass on the subroutine call without touching the stack,
+        * thus leaving any arguments and return values in place */
+       call_pv (name, 0);
+} /* static XS (Collectd_call_by_name) */
+
+/*
+ * Interface to collectd.
+ */
+
+static int perl_init (void)
+{
+       dTHX;
+
+       if (NULL == perl_threads)
+               return 0;
+
+       if (NULL == aTHX) {
+               c_ithread_t *t = NULL;
+
+               pthread_mutex_lock (&perl_threads->mutex);
+               t = c_ithread_create (perl_threads->head->interp);
+               pthread_mutex_unlock (&perl_threads->mutex);
+
+               aTHX = t->interp;
+       }
+
+       log_debug ("perl_init: c_ithread: interp = %p (active threads: %i)",
+                       aTHX, perl_threads->number_of_threads);
+       return pplugin_call_all (aTHX_ PLUGIN_INIT);
+} /* static int perl_init (void) */
+
+static int perl_read (void)
+{
+       dTHX;
+
+       if (NULL == perl_threads)
+               return 0;
+
+       if (NULL == aTHX) {
+               c_ithread_t *t = NULL;
+
+               pthread_mutex_lock (&perl_threads->mutex);
+               t = c_ithread_create (perl_threads->head->interp);
+               pthread_mutex_unlock (&perl_threads->mutex);
+
+               aTHX = t->interp;
+       }
+
+       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) */
+
+static int perl_write (const data_set_t *ds, const value_list_t *vl,
+               user_data_t __attribute__((unused)) *user_data)
+{
+       dTHX;
+
+       if (NULL == perl_threads)
+               return 0;
+
+       if (NULL == aTHX) {
+               c_ithread_t *t = NULL;
+
+               pthread_mutex_lock (&perl_threads->mutex);
+               t = c_ithread_create (perl_threads->head->interp);
+               pthread_mutex_unlock (&perl_threads->mutex);
+
+               aTHX = t->interp;
+       }
+
+       log_debug ("perl_write: c_ithread: interp = %p (active threads: %i)",
+                       aTHX, perl_threads->number_of_threads);
+       return pplugin_call_all (aTHX_ PLUGIN_WRITE, ds, vl);
+} /* 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)
+{
+       dTHX;
+
+       if (NULL == perl_threads)
+               return;
+
+       if (NULL == aTHX) {
+               c_ithread_t *t = NULL;
+
+               pthread_mutex_lock (&perl_threads->mutex);
+               t = c_ithread_create (perl_threads->head->interp);
+               pthread_mutex_unlock (&perl_threads->mutex);
+
+               aTHX = t->interp;
+       }
+
+       pplugin_call_all (aTHX_ PLUGIN_LOG, level, msg);
+       return;
+} /* static void perl_log (int, const char *) */
+
+static int perl_notify (const notification_t *notif,
+               user_data_t __attribute__((unused)) *user_data)
+{
+       dTHX;
+
+       if (NULL == perl_threads)
+               return 0;
+
+       if (NULL == aTHX) {
+               c_ithread_t *t = NULL;
+
+               pthread_mutex_lock (&perl_threads->mutex);
+               t = c_ithread_create (perl_threads->head->interp);
+               pthread_mutex_unlock (&perl_threads->mutex);
+
+               aTHX = t->interp;
+       }
+       return pplugin_call_all (aTHX_ PLUGIN_NOTIF, 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)
+{
+       dTHX;
+
+       if (NULL == perl_threads)
+               return 0;
+
+       if (NULL == aTHX) {
+               c_ithread_t *t = NULL;
+
+               pthread_mutex_lock (&perl_threads->mutex);
+               t = c_ithread_create (perl_threads->head->interp);
+               pthread_mutex_unlock (&perl_threads->mutex);
+
+               aTHX = t->interp;
+       }
+       return pplugin_call_all (aTHX_ PLUGIN_FLUSH, timeout, identifier);
+} /* static int perl_flush (const int) */
+
+static int perl_shutdown (void)
+{
+       c_ithread_t *t = NULL;
+
+       int ret = 0;
+
+       dTHX;
+
+       plugin_unregister_complex_config ("perl");
+
+       if (NULL == perl_threads)
+               return 0;
+
+       if (NULL == aTHX) {
+               c_ithread_t *t = NULL;
+
+               pthread_mutex_lock (&perl_threads->mutex);
+               t = c_ithread_create (perl_threads->head->interp);
+               pthread_mutex_unlock (&perl_threads->mutex);
+
+               aTHX = t->interp;
+       }
+
+       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");
+
+       ret = pplugin_call_all (aTHX_ PLUGIN_SHUTDOWN);
+
+       pthread_mutex_lock (&perl_threads->mutex);
+       t = perl_threads->tail;
+
+       while (NULL != t) {
+               c_ithread_t *thr = t;
+
+               /* the pointer has to be advanced before destroying
+                * the thread as this will free the memory */
+               t = t->prev;
+
+               c_ithread_destroy (thr);
+       }
+
+       pthread_mutex_unlock (&perl_threads->mutex);
+       pthread_mutex_destroy (&perl_threads->mutex);
+
+       sfree (perl_threads);
+
+       pthread_key_delete (perl_thr_key);
+
+       PERL_SYS_TERM ();
+
+       plugin_unregister_shutdown ("perl");
+       return ret;
+} /* static void perl_shutdown (void) */
+
+/*
+ * Access functions for global variables.
+ *
+ * These functions implement the "magic" used to access
+ * the global variables from Perl.
+ */
+
+static int g_pv_get (pTHX_ SV *var, MAGIC *mg)
+{
+       char *pv = mg->mg_ptr;
+       sv_setpv (var, pv);
+       return 0;
+} /* static int g_pv_get (pTHX_ SV *, MAGIC *) */
+
+static int g_pv_set (pTHX_ SV *var, MAGIC *mg)
+{
+       char *pv = mg->mg_ptr;
+       sstrncpy (pv, SvPV_nolen (var), DATA_MAX_NAME_LEN);
+       return 0;
+} /* static int g_pv_set (pTHX_ SV *, MAGIC *) */
+
+static int g_interval_get (pTHX_ SV *var, MAGIC *mg)
+{
+       cdtime_t *interval = (cdtime_t *)mg->mg_ptr;
+       double nv;
+
+       nv = CDTIME_T_TO_DOUBLE (*interval);
+
+       sv_setnv (var, nv);
+       return 0;
+} /* static int g_interval_get (pTHX_ SV *, MAGIC *) */
+
+static int g_interval_set (pTHX_ SV *var, MAGIC *mg)
+{
+       cdtime_t *interval = (cdtime_t *)mg->mg_ptr;
+       double nv;
+
+       nv = (double)SvNV (var);
+
+       *interval = DOUBLE_TO_CDTIME_T (nv);
+       return 0;
+} /* static int g_interval_set (pTHX_ SV *, MAGIC *) */
+
+static MGVTBL g_pv_vtbl = {
+       g_pv_get, g_pv_set, NULL, NULL, NULL, NULL, NULL
+#if HAVE_PERL_STRUCT_MGVTBL_SVT_LOCAL
+               , NULL
+#endif
+};
+static MGVTBL g_interval_vtbl = {
+       g_interval_get, g_interval_set, NULL, NULL, NULL, NULL, NULL
+#if HAVE_PERL_STRUCT_MGVTBL_SVT_LOCAL
+               , NULL
+#endif
+};
+
+/* bootstrap the Collectd module */
+static void xs_init (pTHX)
+{
+       HV   *stash = NULL;
+       SV   *tmp   = NULL;
+       char *file  = __FILE__;
+
+       int i = 0;
+
+       dXSUB_SYS;
+
+       /* enable usage of Perl modules using shared libraries */
+       newXS ("DynaLoader::boot_DynaLoader", boot_DynaLoader, file);
+
+       /* register API */
+       for (i = 0; NULL != api[i].f; ++i)
+               newXS (api[i].name, api[i].f, file);
+
+       stash = gv_stashpv ("Collectd", 1);
+
+       /* export "constants" */
+       for (i = 0; '\0' != constants[i].name[0]; ++i)
+               newCONSTSUB (stash, constants[i].name, newSViv (constants[i].value));
+
+       /* export global variables
+        * by adding "magic" to the SV's representing the globale variables
+        * perl is able to automagically call the get/set function when
+        * accessing any such variable (this is basically the same as using
+        * tie() in Perl) */
+       /* global strings */
+       for (i = 0; '\0' != g_strings[i].name[0]; ++i) {
+               tmp = get_sv (g_strings[i].name, 1);
+               sv_magicext (tmp, NULL, PERL_MAGIC_ext, &g_pv_vtbl,
+                               g_strings[i].var, 0);
+       }
+
+       tmp = get_sv ("Collectd::interval_g", /* create = */ 1);
+       sv_magicext (tmp, NULL, /* how = */ PERL_MAGIC_ext,
+                       /* vtbl = */ &g_interval_vtbl,
+                       /* name = */ (char *) &interval_g, /* namelen = */ 0);
+
+       return;
+} /* static void xs_init (pTHX) */
+
+/* Initialize the global Perl interpreter. */
+static int init_pi (int argc, char **argv)
+{
+       dTHXa (NULL);
+
+       if (NULL != perl_threads)
+               return 0;
+
+       log_info ("Initializing Perl interpreter...");
+#if COLLECT_DEBUG
+       {
+               int i = 0;
+
+               for (i = 0; i < argc; ++i)
+                       log_debug ("argv[%i] = \"%s\"", i, argv[i]);
+       }
+#endif /* COLLECT_DEBUG */
+
+       if (0 != pthread_key_create (&perl_thr_key, c_ithread_destructor)) {
+               log_err ("init_pi: pthread_key_create failed");
+
+               /* this must not happen - cowardly giving up if it does */
+               return -1;
+       }
+
+#ifdef __FreeBSD__
+       /* On FreeBSD, PERL_SYS_INIT3 expands to some expression which
+        * triggers a "value computed is not used" warning by gcc. */
+       (void)
+#endif
+       PERL_SYS_INIT3 (&argc, &argv, &environ);
+
+       perl_threads = (c_ithread_list_t *)smalloc (sizeof (c_ithread_list_t));
+       memset (perl_threads, 0, sizeof (c_ithread_list_t));
+
+       pthread_mutex_init (&perl_threads->mutex, NULL);
+       /* locking the mutex should not be necessary at this point
+        * but let's just do it for the sake of completeness */
+       pthread_mutex_lock (&perl_threads->mutex);
+
+       perl_threads->head = c_ithread_create (NULL);
+       perl_threads->tail = perl_threads->head;
+
+       if (NULL == (perl_threads->head->interp = perl_alloc ())) {
+               log_err ("init_pi: Not enough memory.");
+               exit (3);
+       }
+
+       aTHX = perl_threads->head->interp;
+       pthread_mutex_unlock (&perl_threads->mutex);
+
+       perl_construct (aTHX);
+
+       PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
+
+       if (0 != perl_parse (aTHX_ xs_init, argc, argv, NULL)) {
+               SV *err = get_sv ("@", 1);
+               log_err ("init_pi: Unable to bootstrap Collectd: %s",
+                               SvPV_nolen (err));
+
+               perl_destruct (perl_threads->head->interp);
+               perl_free (perl_threads->head->interp);
+               sfree (perl_threads);
+
+               pthread_key_delete (perl_thr_key);
+               return -1;
+       }
+
+       /* Set $0 to "collectd" because perl_parse() has to set it to "-e". */
+       sv_setpv (get_sv ("0", 0), "collectd");
+
+       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) */
+
+/*
+ * LoadPlugin "<Plugin>"
+ */
+static int perl_config_loadplugin (pTHX_ oconfig_item_t *ci)
+{
+       char module_name[DATA_MAX_NAME_LEN];
+
+       char *value = NULL;
+
+       if ((0 != ci->children_num) || (1 != ci->values_num)
+                       || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
+               log_err ("LoadPlugin expects a single string argument.");
+               return 1;
+       }
+
+       value = ci->values[0].value.string;
+
+       if (NULL == get_module_name (module_name, sizeof (module_name), value)) {
+               log_err ("Invalid module name %s", value);
+               return (1);
+       }
+
+       if (0 != init_pi (perl_argc, perl_argv))
+               return -1;
+
+       assert (NULL != perl_threads);
+       assert (NULL != perl_threads->head);
+
+       aTHX = perl_threads->head->interp;
+
+       log_debug ("perl_config: loading perl plugin \"%s\"", value);
+       load_module (PERL_LOADMOD_NOIMPORT,
+                       newSVpv (module_name, strlen (module_name)), Nullsv);
+       return 0;
+} /* static int perl_config_loadplugin (oconfig_item_it *) */
+
+/*
+ * BaseName "<Name>"
+ */
+static int perl_config_basename (pTHX_ oconfig_item_t *ci)
+{
+       char *value = NULL;
+
+       if ((0 != ci->children_num) || (1 != ci->values_num)
+                       || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
+               log_err ("BaseName expects a single string argument.");
+               return 1;
+       }
+
+       value = ci->values[0].value.string;
+
+       log_debug ("perl_config: Setting plugin basename to \"%s\"", value);
+       sstrncpy (base_name, value, sizeof (base_name));
+       return 0;
+} /* static int perl_config_basename (oconfig_item_it *) */
+
+/*
+ * EnableDebugger "<Package>"|""
+ */
+static int perl_config_enabledebugger (pTHX_ oconfig_item_t *ci)
+{
+       char *value = NULL;
+
+       if ((0 != ci->children_num) || (1 != ci->values_num)
+                       || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
+               log_err ("EnableDebugger expects a single string argument.");
+               return 1;
+       }
+
+       if (NULL != perl_threads) {
+               log_warn ("EnableDebugger has no effects if used after LoadPlugin.");
+               return 1;
+       }
+
+       value = ci->values[0].value.string;
+
+       perl_argv = (char **)realloc (perl_argv,
+                       (++perl_argc + 1) * sizeof (char *));
+
+       if (NULL == perl_argv) {
+               log_err ("perl_config: Not enough memory.");
+               exit (3);
+       }
+
+       if ('\0' == value[0]) {
+               perl_argv[perl_argc - 1] = "-d";
+       }
+       else {
+               perl_argv[perl_argc - 1] = (char *)smalloc (strlen (value) + 4);
+               sstrncpy (perl_argv[perl_argc - 1], "-d:", 4);
+               sstrncpy (perl_argv[perl_argc - 1] + 3, value, strlen (value) + 1);
+       }
+
+       perl_argv[perl_argc] = NULL;
+       return 0;
+} /* static int perl_config_enabledebugger (oconfig_item_it *) */
+
+/*
+ * IncludeDir "<Dir>"
+ */
+static int perl_config_includedir (pTHX_ oconfig_item_t *ci)
+{
+       char *value = NULL;
+
+       if ((0 != ci->children_num) || (1 != ci->values_num)
+                       || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
+               log_err ("IncludeDir expects a single string argument.");
+               return 1;
+       }
+
+       value = ci->values[0].value.string;
+
+       if (NULL == aTHX) {
+               perl_argv = (char **)realloc (perl_argv,
+                               (++perl_argc + 1) * sizeof (char *));
+
+               if (NULL == perl_argv) {
+                       log_err ("perl_config: Not enough memory.");
+                       exit (3);
+               }
+
+               perl_argv[perl_argc - 1] = (char *)smalloc (strlen (value) + 3);
+               sstrncpy(perl_argv[perl_argc - 1], "-I", 3);
+               sstrncpy(perl_argv[perl_argc - 1] + 2, value, strlen (value) + 1);
+
+               perl_argv[perl_argc] = NULL;
+       }
+       else {
+               /* prepend the directory to @INC */
+               av_unshift (GvAVn (PL_incgv), 1);
+               av_store (GvAVn (PL_incgv), 0, newSVpv (value, strlen (value)));
+       }
+       return 0;
+} /* static int perl_config_includedir (oconfig_item_it *) */
+
+/*
+ * <Plugin> block
+ */
+static int perl_config_plugin (pTHX_ oconfig_item_t *ci)
+{
+       int retvals = 0;
+       int ret     = 0;
+
+       char *plugin;
+       HV   *config;
+
+       dSP;
+
+       if ((1 != ci->values_num) || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
+               log_err ("LoadPlugin expects a single string argument.");
+               return 1;
+       }
+
+       plugin = ci->values[0].value.string;
+       config = newHV ();
+
+       if (0 != oconfig_item2hv (aTHX_ ci, config)) {
+               hv_clear (config);
+               hv_undef (config);
+
+               log_err ("Unable to convert configuration to a Perl hash value.");
+               config = (HV *)&PL_sv_undef;
+       }
+
+       ENTER;
+       SAVETMPS;
+
+       PUSHMARK (SP);
+
+       XPUSHs (sv_2mortal (newSVpv (plugin, 0)));
+       XPUSHs (sv_2mortal (newRV_noinc ((SV *)config)));
+
+       PUTBACK;
+
+       retvals = call_pv ("Collectd::_plugin_dispatch_config", G_SCALAR);
+
+       SPAGAIN;
+       if (0 < retvals) {
+               SV *tmp = POPs;
+               if (! SvTRUE (tmp))
+                       ret = 1;
+       }
+       else
+               ret = 1;
+
+       PUTBACK;
+       FREETMPS;
+       LEAVE;
+       return ret;
+} /* static int perl_config_plugin (oconfig_item_it *) */
+
+static int perl_config (oconfig_item_t *ci)
+{
+       int status = 0;
+       int i = 0;
+
+       dTHXa (NULL);
+
+       for (i = 0; i < ci->children_num; ++i) {
+               oconfig_item_t *c = ci->children + i;
+               int current_status = 0;
+
+               if (NULL != perl_threads)
+                       aTHX = PERL_GET_CONTEXT;
+
+               if (0 == strcasecmp (c->key, "LoadPlugin"))
+                       current_status = perl_config_loadplugin (aTHX_ c);
+               else if (0 == strcasecmp (c->key, "BaseName"))
+                       current_status = perl_config_basename (aTHX_ c);
+               else if (0 == strcasecmp (c->key, "EnableDebugger"))
+                       current_status = perl_config_enabledebugger (aTHX_ c);
+               else if (0 == strcasecmp (c->key, "IncludeDir"))
+                       current_status = perl_config_includedir (aTHX_ c);
+               else if (0 == strcasecmp (c->key, "Plugin"))
+                       current_status = perl_config_plugin (aTHX_ c);
+               else
+               {
+                       log_warn ("Ignoring unknown config key \"%s\".", c->key);
+                       current_status = 0;
+               }
+
+               /* fatal error - it's up to perl_config_* to clean up */
+               if (0 > current_status) {
+                       log_err ("Configuration failed with a fatal error - "
+                                       "plugin disabled!");
+                       return current_status;
+               }
+
+               status += current_status;
+       }
+       return status;
+} /* static int perl_config (oconfig_item_t *) */
+
+void module_register (void)
+{
+       perl_argc = 4;
+       perl_argv = (char **)smalloc ((perl_argc + 1) * sizeof (char *));
+
+       /* default options for the Perl interpreter */
+       perl_argv[0] = "";
+       perl_argv[1] = "-MCollectd";
+       perl_argv[2] = "-e";
+       perl_argv[3] = "1";
+       perl_argv[4] = NULL;
+
+       plugin_register_complex_config ("perl", perl_config);
+       return;
+} /* void module_register (void) */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
+
diff --git a/src/pinba.c b/src/pinba.c
new file mode 100644 (file)
index 0000000..a6fd06f
--- /dev/null
@@ -0,0 +1,750 @@
+/**
+ * collectd - src/pinba.c (based on code from pinba_engine 0.0.5)
+ * Copyright (c) 2007-2009  Antony Dovgal
+ * Copyright (C) 2010       Phoenix Kayo
+ * Copyright (C) 2010       Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Antony Dovgal <tony at daylessday.org>
+ *   Phoenix Kayo <kayo.k11.4 at gmail.com>
+ *   Florian Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+
+#include <pthread.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <poll.h>
+
+#include "pinba.pb-c.h"
+
+/*
+ * Defines
+ */
+#ifndef PINBA_UDP_BUFFER_SIZE
+# define PINBA_UDP_BUFFER_SIZE 65536
+#endif
+
+#ifndef PINBA_DEFAULT_NODE
+# define PINBA_DEFAULT_NODE "::0"
+#endif
+
+#ifndef PINBA_DEFAULT_SERVICE
+# define PINBA_DEFAULT_SERVICE "30002"
+#endif
+
+#ifndef PINBA_MAX_SOCKETS
+# define PINBA_MAX_SOCKETS 16
+#endif
+
+/*
+ * Private data structures
+ */
+/* {{{ */
+struct pinba_socket_s
+{
+  struct pollfd fd[PINBA_MAX_SOCKETS];
+  nfds_t fd_num;
+};
+typedef struct pinba_socket_s pinba_socket_t;
+
+/* Fixed point counter value. n is the decimal part multiplied by 10^9. */
+struct float_counter_s
+{
+  uint64_t i;
+  uint64_t n; /* nanos */
+};
+typedef struct float_counter_s float_counter_t;
+
+struct pinba_statnode_s
+{
+  /* collector name, used as plugin instance */
+  char *name;
+
+  /* query data */
+  char *host;
+  char *server;
+  char *script;
+
+  derive_t req_count;
+
+  float_counter_t req_time;
+  float_counter_t ru_utime;
+  float_counter_t ru_stime;
+
+  derive_t doc_size;
+  gauge_t mem_peak;
+};
+typedef struct pinba_statnode_s pinba_statnode_t;
+/* }}} */
+
+/*
+ * Module global variables
+ */
+/* {{{ */
+static pinba_statnode_t *stat_nodes = NULL;
+static unsigned int stat_nodes_num = 0;
+static pthread_mutex_t stat_nodes_lock;
+
+static char *conf_node = NULL;
+static char *conf_service = NULL;
+
+static _Bool collector_thread_running = 0;
+static _Bool collector_thread_do_shutdown = 0;
+static pthread_t collector_thread_id;
+/* }}} */
+
+/*
+ * Functions
+ */
+static void float_counter_add (float_counter_t *fc, float val) /* {{{ */
+{
+  uint64_t tmp;
+
+  if (val < 0.0)
+    return;
+
+  tmp = (uint64_t) val;
+  val -= (double) tmp;
+
+  fc->i += tmp;
+  fc->n += (uint64_t) ((val * 1000000000.0) + .5);
+
+  if (fc->n >= 1000000000)
+  {
+    fc->i += 1;
+    fc->n -= 1000000000;
+    assert (fc->n < 1000000000);
+  }
+} /* }}} void float_counter_add */
+
+static derive_t float_counter_get (const float_counter_t *fc, /* {{{ */
+    uint64_t factor)
+{
+  derive_t ret;
+
+  ret = (derive_t) (fc->i * factor);
+  ret += (derive_t) (fc->n / (1000000000 / factor));
+
+  return (ret);
+} /* }}} derive_t float_counter_get */
+
+static void strset (char **str, const char *new) /* {{{ */
+{
+  char *tmp;
+
+  if (!str || !new)
+    return;
+
+  tmp = strdup (new);
+  if (tmp == NULL)
+    return;
+
+  sfree (*str);
+  *str = tmp;
+} /* }}} void strset */
+
+static void service_statnode_add(const char *name, /* {{{ */
+    const char *host,
+    const char *server,
+    const char *script)
+{
+  pinba_statnode_t *node;
+  
+  node = realloc (stat_nodes,
+      sizeof (*stat_nodes) * (stat_nodes_num + 1));
+  if (node == NULL)
+  {
+    ERROR ("pinba plugin: realloc failed");
+    return;
+  }
+  stat_nodes = node;
+
+  node = stat_nodes + stat_nodes_num;
+  memset (node, 0, sizeof (*node));
+  
+  /* reset strings */
+  node->name   = NULL;
+  node->host   = NULL;
+  node->server = NULL;
+  node->script = NULL;
+
+  node->mem_peak = NAN;
+  
+  /* fill query data */
+  strset (&node->name, name);
+  strset (&node->host, host);
+  strset (&node->server, server);
+  strset (&node->script, script);
+  
+  /* increment counter */
+  stat_nodes_num++;
+} /* }}} void service_statnode_add */
+
+/* Copy the data from the global "stat_nodes" list into the buffer pointed to
+ * by "res", doing the derivation in the process. Returns the next index or
+ * zero if the end of the list has been reached. */
+static unsigned int service_statnode_collect (pinba_statnode_t *res, /* {{{ */
+    unsigned int index)
+{
+  pinba_statnode_t *node;
+  
+  if (stat_nodes_num == 0)
+    return 0;
+  
+  /* begin collecting */
+  if (index == 0)
+    pthread_mutex_lock (&stat_nodes_lock);
+  
+  /* end collecting */
+  if (index >= stat_nodes_num)
+  {
+    pthread_mutex_unlock (&stat_nodes_lock);
+    return 0;
+  }
+
+  node = stat_nodes + index;
+  memcpy (res, node, sizeof (*res));
+
+  /* reset node */
+  node->mem_peak = NAN;
+  
+  return (index + 1);
+} /* }}} unsigned int service_statnode_collect */
+
+static void service_statnode_process (pinba_statnode_t *node, /* {{{ */
+    Pinba__Request* request)
+{
+  node->req_count++;
+
+  float_counter_add (&node->req_time, request->request_time);
+  float_counter_add (&node->ru_utime, request->ru_utime);
+  float_counter_add (&node->ru_stime, request->ru_stime);
+
+  node->doc_size += request->document_size;
+
+  if (isnan (node->mem_peak)
+      || (node->mem_peak < ((gauge_t) request->memory_peak)))
+    node->mem_peak = (gauge_t) request->memory_peak;
+
+} /* }}} void service_statnode_process */
+
+static void service_process_request (Pinba__Request *request) /* {{{ */
+{
+  unsigned int i;
+
+  pthread_mutex_lock (&stat_nodes_lock);
+  
+  for (i = 0; i < stat_nodes_num; i++)
+  {
+    if ((stat_nodes[i].host != NULL)
+        && (strcmp (request->hostname, stat_nodes[i].host) != 0))
+      continue;
+
+    if ((stat_nodes[i].server != NULL)
+      && (strcmp (request->server_name, stat_nodes[i].server) != 0))
+      continue;
+
+    if ((stat_nodes[i].script != NULL)
+      && (strcmp (request->script_name, stat_nodes[i].script) != 0))
+      continue;
+
+    service_statnode_process(&stat_nodes[i], request);
+  }
+  
+  pthread_mutex_unlock(&stat_nodes_lock);
+} /* }}} void service_process_request */
+
+static int pb_del_socket (pinba_socket_t *s, /* {{{ */
+    nfds_t index)
+{
+  if (index >= s->fd_num)
+    return (EINVAL);
+
+  close (s->fd[index].fd);
+  s->fd[index].fd = -1;
+
+  /* When deleting the last element in the list, no memmove is necessary. */
+  if (index < (s->fd_num - 1))
+  {
+    memmove (&s->fd[index], &s->fd[index + 1],
+        sizeof (s->fd[0]) * (s->fd_num - (index + 1)));
+  }
+
+  s->fd_num--;
+  return (0);
+} /* }}} int pb_del_socket */
+
+static int pb_add_socket (pinba_socket_t *s, /* {{{ */
+    const struct addrinfo *ai)
+{
+  int fd;
+  int tmp;
+  int status;
+
+  if (s->fd_num == PINBA_MAX_SOCKETS)
+  {
+    WARNING ("pinba plugin: Sorry, you have hit the built-in limit of "
+        "%i sockets. Please complain to the collectd developers so we can "
+        "raise the limit.", PINBA_MAX_SOCKETS);
+    return (-1);
+  }
+
+  fd = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+  if (fd < 0)
+  {
+    char errbuf[1024];
+    ERROR ("pinba plugin: socket(2) failed: %s",
+        sstrerror (errno, errbuf, sizeof (errbuf)));
+    return (0);
+  }
+
+  tmp = 1;
+  status = setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &tmp, sizeof (tmp));
+  if (status != 0)
+  {
+    char errbuf[1024];
+    WARNING ("pinba plugin: setsockopt(SO_REUSEADDR) failed: %s",
+        sstrerror (errno, errbuf, sizeof (errbuf)));
+  }
+
+  status = bind (fd, ai->ai_addr, ai->ai_addrlen);
+  if (status != 0)
+  {
+    char errbuf[1024];
+    ERROR ("pinba plugin: bind(2) failed: %s",
+        sstrerror (errno, errbuf, sizeof (errbuf)));
+    return (0);
+  }
+
+  s->fd[s->fd_num].fd = fd;
+  s->fd[s->fd_num].events = POLLIN | POLLPRI;
+  s->fd[s->fd_num].revents = 0;
+  s->fd_num++;
+
+  return (0);
+} /* }}} int pb_add_socket */
+
+static pinba_socket_t *pinba_socket_open (const char *node, /* {{{ */
+    const char *service)
+{
+  pinba_socket_t *s;
+  struct addrinfo *ai_list;
+  struct addrinfo *ai_ptr;
+  struct addrinfo  ai_hints;
+  int status;
+
+  memset (&ai_hints, 0, sizeof (ai_hints));
+  ai_hints.ai_flags = AI_PASSIVE;
+  ai_hints.ai_family = AF_UNSPEC;
+  ai_hints.ai_socktype = SOCK_DGRAM;
+  ai_hints.ai_addr = NULL;
+  ai_hints.ai_canonname = NULL;
+  ai_hints.ai_next = NULL;
+
+  if (node == NULL)
+    node = PINBA_DEFAULT_NODE;
+
+  if (service == NULL)
+    service = PINBA_DEFAULT_SERVICE;
+
+  ai_list = NULL;
+  status = getaddrinfo (node, service,
+      &ai_hints, &ai_list);
+  if (status != 0)
+  {
+    ERROR ("pinba plugin: getaddrinfo(3) failed: %s",
+        gai_strerror (status));
+    return (NULL);
+  }
+  assert (ai_list != NULL);
+
+  s = malloc (sizeof (*s));
+  if (s == NULL)
+  {
+    freeaddrinfo (ai_list);
+    ERROR ("pinba plugin: malloc failed.");
+    return (NULL);
+  }
+  memset (s, 0, sizeof (*s));
+
+  for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
+  {
+    status = pb_add_socket (s, ai_ptr);
+    if (status != 0)
+      break;
+  } /* for (ai_list) */
+  
+  freeaddrinfo (ai_list);
+
+  if (s->fd_num < 1)
+  {
+    WARNING ("pinba plugin: Unable to open socket for address %s.", node);
+    sfree (s);
+    s = NULL;
+  }
+
+  return (s);
+} /* }}} pinba_socket_open */
+
+static void pinba_socket_free (pinba_socket_t *socket) /* {{{ */
+{
+  nfds_t i;
+
+  if (!socket)
+    return;
+  
+  for (i = 0; i < socket->fd_num; i++)
+  {
+    if (socket->fd[i].fd < 0)
+      continue;
+    close (socket->fd[i].fd);
+    socket->fd[i].fd = -1;
+  }
+  
+  sfree(socket);
+} /* }}} void pinba_socket_free */
+
+static int pinba_process_stats_packet (const uint8_t *buffer, /* {{{ */
+    size_t buffer_size)
+{
+  Pinba__Request *request;  
+  
+  request = pinba__request__unpack (NULL, buffer_size, buffer);
+  
+  if (!request)
+    return (-1);
+
+  service_process_request(request);
+  pinba__request__free_unpacked (request, NULL);
+    
+  return (0);
+} /* }}} int pinba_process_stats_packet */
+
+static int pinba_udp_read_callback_fn (int sock) /* {{{ */
+{
+  uint8_t buffer[PINBA_UDP_BUFFER_SIZE];
+  size_t buffer_size;
+  int status;
+
+  while (42)
+  {
+    buffer_size = sizeof (buffer);
+    status = recvfrom (sock, buffer, buffer_size - 1, MSG_DONTWAIT, /* from = */ NULL, /* from len = */ 0);
+    if (status < 0)
+    {
+      char errbuf[1024];
+
+      if ((errno == EINTR)
+#ifdef EWOULDBLOCK
+          || (errno == EWOULDBLOCK)
+#endif
+          || (errno == EAGAIN))
+      {
+        continue;
+      }
+
+      WARNING("pinba plugin: recvfrom(2) failed: %s",
+          sstrerror (errno, errbuf, sizeof (errbuf)));
+      return (-1);
+    }
+    else if (status == 0)
+    {
+      DEBUG ("pinba plugin: recvfrom(2) returned unexpected status zero.");
+      return (-1);
+    }
+    else /* if (status > 0) */
+    {
+      assert (((size_t) status) < buffer_size);
+      buffer_size = (size_t) status;
+      buffer[buffer_size] = 0;
+
+      status = pinba_process_stats_packet (buffer, buffer_size);
+      if (status != 0)
+        DEBUG("pinba plugin: Parsing packet failed.");
+      return (status);
+    }
+  } /* while (42) */
+
+  /* not reached */
+  assert (23 == 42);
+  return (-1);
+} /* }}} void pinba_udp_read_callback_fn */
+
+static int receive_loop (void) /* {{{ */
+{
+  pinba_socket_t *s;
+
+  s = pinba_socket_open (conf_node, conf_service);
+  if (s == NULL)
+  {
+    ERROR ("pinba plugin: Collector thread is exiting prematurely.");
+    return (-1);
+  }
+
+  while (!collector_thread_do_shutdown)
+  {
+    int status;
+    nfds_t i;
+
+    if (s->fd_num < 1)
+      break;
+
+    status = poll (s->fd, s->fd_num, /* timeout = */ 1000);
+    if (status == 0) /* timeout */
+    {
+      continue;
+    }
+    else if (status < 0)
+    {
+      char errbuf[1024];
+
+      if ((errno == EINTR) || (errno == EAGAIN))
+        continue;
+
+      ERROR ("pinba plugin: poll(2) failed: %s",
+          sstrerror (errno, errbuf, sizeof (errbuf)));
+      pinba_socket_free (s);
+      return (-1);
+    }
+
+    for (i = 0; i < s->fd_num; i++)
+    {
+      if (s->fd[i].revents & (POLLERR | POLLHUP | POLLNVAL))
+      {
+        pb_del_socket (s, i);
+        i--;
+      }
+      else if (s->fd[i].revents & (POLLIN | POLLPRI))
+      {
+        pinba_udp_read_callback_fn (s->fd[i].fd);
+      }
+    } /* for (s->fd) */
+  } /* while (!collector_thread_do_shutdown) */
+
+  pinba_socket_free (s);
+  s = NULL;
+
+  return (0);
+} /* }}} int receive_loop */
+
+static void *collector_thread (void *arg) /* {{{ */
+{
+  receive_loop ();
+
+  memset (&collector_thread_id, 0, sizeof (collector_thread_id));
+  collector_thread_running = 0;
+  pthread_exit (NULL);
+  return (NULL);
+} /* }}} void *collector_thread */
+
+/*
+ * Plugin declaration section
+ */
+static int pinba_config_view (const oconfig_item_t *ci) /* {{{ */
+{
+  char *name   = NULL;
+  char *host   = NULL;
+  char *server = NULL;
+  char *script = NULL;
+  int status;
+  int i;
+
+  status = cf_util_get_string (ci, &name);
+  if (status != 0)
+    return (status);
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Host", child->key) == 0)
+      status = cf_util_get_string (child, &host);
+    else if (strcasecmp ("Server", child->key) == 0)
+      status = cf_util_get_string (child, &server);
+    else if (strcasecmp ("Script", child->key) == 0)
+      status = cf_util_get_string (child, &script);
+    else
+    {
+      WARNING ("pinba plugin: Unknown config option: %s", child->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  if (status == 0)
+    service_statnode_add (name, host, server, script);
+
+  sfree (name);
+  sfree (host);
+  sfree (server);
+  sfree (script);
+
+  return (status);
+} /* }}} int pinba_config_view */
+
+static int plugin_config (oconfig_item_t *ci) /* {{{ */
+{
+  int i;
+  
+  /* The lock should not be necessary in the config callback, but let's be
+   * sure.. */
+  pthread_mutex_lock (&stat_nodes_lock);
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Address", child->key) == 0)
+      cf_util_get_string (child, &conf_node);
+    else if (strcasecmp ("Port", child->key) == 0)
+      cf_util_get_string (child, &conf_service);
+    else if (strcasecmp ("View", child->key) == 0)
+      pinba_config_view (child);
+    else
+      WARNING ("pinba plugin: Unknown config option: %s", child->key);
+  }
+
+  pthread_mutex_unlock(&stat_nodes_lock);
+  
+  return (0);
+} /* }}} int pinba_config */
+
+static int plugin_init (void) /* {{{ */
+{
+  int status;
+
+  if (stat_nodes == NULL)
+  {
+    /* Collect the "total" data by default. */
+    service_statnode_add ("total",
+        /* host   = */ NULL,
+        /* server = */ NULL,
+        /* script = */ NULL);
+  }
+
+  if (collector_thread_running)
+    return (0);
+
+  status = pthread_create (&collector_thread_id,
+      /* attrs = */ NULL,
+      collector_thread,
+      /* args = */ NULL);
+  if (status != 0)
+  {
+    char errbuf[1024];
+    ERROR ("pinba plugin: pthread_create(3) failed: %s",
+        sstrerror (errno, errbuf, sizeof (errbuf)));
+    return (-1);
+  }
+  collector_thread_running = 1;
+
+  return (0);
+} /* }}} */
+
+static int plugin_shutdown (void) /* {{{ */
+{
+  if (collector_thread_running)
+  {
+    int status;
+
+    DEBUG ("pinba plugin: Shutting down collector thread.");
+    collector_thread_do_shutdown = 1;
+
+    status = pthread_join (collector_thread_id, /* retval = */ NULL);
+    if (status != 0)
+    {
+      char errbuf[1024];
+      ERROR ("pinba plugin: pthread_join(3) failed: %s",
+          sstrerror (status, errbuf, sizeof (errbuf)));
+    }
+
+    collector_thread_running = 0;
+    collector_thread_do_shutdown = 0;
+  } /* if (collector_thread_running) */
+
+  return (0);
+} /* }}} int plugin_shutdown */
+
+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;
+  sstrncpy (vl.type, "total_requests", sizeof (vl.type)); 
+  plugin_dispatch_values (&vl);
+
+  value.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;
+  sstrncpy (vl.type, "total_bytes", sizeof (vl.type)); 
+  plugin_dispatch_values (&vl);
+
+  value.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);
+  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;
+  sstrncpy (vl.type, "memory", sizeof (vl.type));
+  sstrncpy (vl.type_instance, "peak", sizeof (vl.type_instance));
+  plugin_dispatch_values (&vl);
+
+  return (0);
+} /* }}} int plugin_submit */
+
+static int plugin_read (void) /* {{{ */
+{
+  unsigned int i=0;
+  pinba_statnode_t data;
+  
+  while ((i = service_statnode_collect (&data, i)) != 0)
+  {
+    plugin_submit (&data);
+  }
+  
+  return 0;
+} /* }}} int plugin_read */
+
+void module_register (void) /* {{{ */
+{
+  plugin_register_complex_config ("pinba", plugin_config);
+  plugin_register_init ("pinba", plugin_init);
+  plugin_register_read ("pinba", plugin_read);
+  plugin_register_shutdown ("pinba", plugin_shutdown);
+} /* }}} void module_register */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/pinba.proto b/src/pinba.proto
new file mode 100644 (file)
index 0000000..5fd5439
--- /dev/null
@@ -0,0 +1,22 @@
+package Pinba;
+option optimize_for = SPEED;
+
+message Request {
+       required string hostname                = 1;
+       required string server_name             = 2;
+       required string script_name             = 3;
+       required uint32 request_count   = 4;
+       required uint32 document_size   = 5;
+       required uint32 memory_peak             = 6;
+       required float request_time             = 7;
+       required float ru_utime                 = 8;
+       required float ru_stime                 = 9;
+
+       repeated uint32 timer_hit_count = 10;
+       repeated float timer_value      = 11;
+       repeated uint32 timer_tag_count = 12;
+       repeated uint32 timer_tag_name  = 13;
+       repeated uint32 timer_tag_value = 14;
+       repeated string dictionary      = 15;
+       optional uint32 status          = 16;
+}
diff --git a/src/ping.c b/src/ping.c
new file mode 100644 (file)
index 0000000..b536f42
--- /dev/null
@@ -0,0 +1,679 @@
+/**
+ * collectd - src/ping.c
+ * Copyright (C) 2005-2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+
+#include <pthread.h>
+#include <netinet/in.h>
+#if HAVE_NETDB_H
+# include <netdb.h> /* NI_MAXHOST */
+#endif
+
+#include <oping.h>
+
+#ifndef NI_MAXHOST
+# define NI_MAXHOST 1025
+#endif
+
+#if defined(OPING_VERSION) && (OPING_VERSION >= 1003000)
+# define HAVE_OPING_1_3
+#endif
+
+/*
+ * Private data types
+ */
+struct hostlist_s
+{
+  char *host;
+
+  uint32_t pkg_sent;
+  uint32_t pkg_recv;
+  uint32_t pkg_missed;
+
+  double latency_total;
+  double latency_squared;
+
+  struct hostlist_s *next;
+};
+typedef struct hostlist_s hostlist_t;
+
+/*
+ * Private variables
+ */
+static hostlist_t *hostlist_head = NULL;
+
+static char  *ping_source = NULL;
+#ifdef HAVE_OPING_1_3
+static char  *ping_device = NULL;
+#endif
+static int    ping_ttl = PING_DEF_TTL;
+static double ping_interval = 1.0;
+static double ping_timeout = 0.9;
+static int    ping_max_missed = -1;
+
+static int             ping_thread_loop = 0;
+static int             ping_thread_error = 0;
+static pthread_t       ping_thread_id;
+static pthread_mutex_t ping_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t  ping_cond = PTHREAD_COND_INITIALIZER;
+
+static const char *config_keys[] =
+{
+  "Host",
+  "SourceAddress",
+#ifdef HAVE_OPING_1_3
+  "Device",
+#endif
+  "TTL",
+  "Interval",
+  "Timeout",
+  "MaxMissed"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+/*
+ * Private functions
+ */
+/* Assure that `ts->tv_nsec' is in the range 0 .. 999999999 */
+static void time_normalize (struct timespec *ts) /* {{{ */
+{
+  while (ts->tv_nsec < 0)
+  {
+    if (ts->tv_sec == 0)
+    {
+      ts->tv_nsec = 0;
+      return;
+    }
+
+    ts->tv_sec  -= 1;
+    ts->tv_nsec += 1000000000;
+  }
+
+  while (ts->tv_nsec >= 1000000000)
+  {
+    ts->tv_sec  += 1;
+    ts->tv_nsec -= 1000000000;
+  }
+} /* }}} void time_normalize */
+
+/* Add `ts_int' to `tv_begin' and store the result in `ts_dest'. If the result
+ * is larger than `tv_end', copy `tv_end' to `ts_dest' instead. */
+static void time_calc (struct timespec *ts_dest, /* {{{ */
+    const struct timespec *ts_int,
+    const struct timeval  *tv_begin,
+    const struct timeval  *tv_end)
+{
+  ts_dest->tv_sec = tv_begin->tv_sec + ts_int->tv_sec;
+  ts_dest->tv_nsec = (tv_begin->tv_usec * 1000) + ts_int->tv_nsec;
+  time_normalize (ts_dest);
+
+  /* Assure that `(begin + interval) > end'.
+   * This may seem overly complicated, but `tv_sec' is of type `time_t'
+   * which may be `unsigned. *sigh* */
+  if ((tv_end->tv_sec > ts_dest->tv_sec)
+      || ((tv_end->tv_sec == ts_dest->tv_sec)
+        && ((tv_end->tv_usec * 1000) > ts_dest->tv_nsec)))
+  {
+    ts_dest->tv_sec = tv_end->tv_sec;
+    ts_dest->tv_nsec = 1000 * tv_end->tv_usec;
+  }
+
+  time_normalize (ts_dest);
+} /* }}} void time_calc */
+
+static void *ping_thread (void *arg) /* {{{ */
+{
+  static pingobj_t *pingobj = NULL;
+
+  struct timeval  tv_begin;
+  struct timeval  tv_end;
+  struct timespec ts_wait;
+  struct timespec ts_int;
+
+  hostlist_t *hl;
+  int count;
+
+  pthread_mutex_lock (&ping_lock);
+
+  pingobj = ping_construct ();
+  if (pingobj == NULL)
+  {
+    ERROR ("ping plugin: ping_construct failed.");
+    ping_thread_error = 1;
+    pthread_mutex_unlock (&ping_lock);
+    return ((void *) -1);
+  }
+
+  if (ping_source != NULL)
+    if (ping_setopt (pingobj, PING_OPT_SOURCE, (void *) ping_source) != 0)
+      ERROR ("ping plugin: Failed to set source address: %s",
+          ping_get_error (pingobj));
+
+#ifdef HAVE_OPING_1_3
+  if (ping_device != NULL)
+    if (ping_setopt (pingobj, PING_OPT_DEVICE, (void *) ping_device) != 0)
+      ERROR ("ping plugin: Failed to set device: %s",
+          ping_get_error (pingobj));
+#endif
+
+  ping_setopt (pingobj, PING_OPT_TIMEOUT, (void *) &ping_timeout);
+  ping_setopt (pingobj, PING_OPT_TTL, (void *) &ping_ttl);
+
+  /* Add all the hosts to the ping object. */
+  count = 0;
+  for (hl = hostlist_head; hl != NULL; hl = hl->next)
+  {
+    int tmp_status;
+    tmp_status = ping_host_add (pingobj, hl->host);
+    if (tmp_status != 0)
+      WARNING ("ping plugin: ping_host_add (%s) failed: %s",
+          hl->host, ping_get_error (pingobj));
+    else
+      count++;
+  }
+
+  if (count == 0)
+  {
+    ERROR ("ping plugin: No host could be added to ping object. Giving up.");
+    ping_thread_error = 1;
+    pthread_mutex_unlock (&ping_lock);
+    return ((void *) -1);
+  }
+
+  /* Set up `ts_int' */
+  {
+    double temp_sec;
+    double temp_nsec;
+
+    temp_nsec = modf (ping_interval, &temp_sec);
+    ts_int.tv_sec  = (time_t) temp_sec;
+    ts_int.tv_nsec = (long) (temp_nsec * 1000000000L);
+  }
+
+  while (ping_thread_loop > 0)
+  {
+    pingobj_iter_t *iter;
+    int status;
+
+    if (gettimeofday (&tv_begin, NULL) < 0)
+    {
+      char errbuf[1024];
+      ERROR ("ping plugin: gettimeofday failed: %s",
+          sstrerror (errno, errbuf, sizeof (errbuf)));
+      ping_thread_error = 1;
+      break;
+    }
+
+    pthread_mutex_unlock (&ping_lock);
+
+    status = ping_send (pingobj);
+    if (status < 0)
+    {
+      ERROR ("ping plugin: ping_send failed: %s", ping_get_error (pingobj));
+      pthread_mutex_lock (&ping_lock);
+      ping_thread_error = 1;
+      break;
+    }
+
+    pthread_mutex_lock (&ping_lock);
+
+    if (ping_thread_loop <= 0)
+      break;
+
+    for (iter = ping_iterator_get (pingobj);
+        iter != NULL;
+        iter = ping_iterator_next (iter))
+    { /* {{{ */
+      char userhost[NI_MAXHOST];
+      double latency;
+      size_t param_size;
+
+      param_size = sizeof (userhost);
+      status = ping_iterator_get_info (iter,
+#ifdef PING_INFO_USERNAME
+          PING_INFO_USERNAME,
+#else
+          PING_INFO_HOSTNAME,
+#endif
+          userhost, &param_size);
+      if (status != 0)
+      {
+        WARNING ("ping plugin: ping_iterator_get_info failed: %s",
+            ping_get_error (pingobj));
+        continue;
+      }
+
+      for (hl = hostlist_head; hl != NULL; hl = hl->next)
+        if (strcmp (userhost, hl->host) == 0)
+          break;
+
+      if (hl == NULL)
+      {
+        WARNING ("ping plugin: Cannot find host %s.", userhost);
+        continue;
+      }
+
+      param_size = sizeof (latency);
+      status = ping_iterator_get_info (iter, PING_INFO_LATENCY,
+          (void *) &latency, &param_size);
+      if (status != 0)
+      {
+        WARNING ("ping plugin: ping_iterator_get_info failed: %s",
+            ping_get_error (pingobj));
+        continue;
+      }
+
+      hl->pkg_sent++;
+      if (latency >= 0.0)
+      {
+        hl->pkg_recv++;
+        hl->latency_total += latency;
+        hl->latency_squared += (latency * latency);
+
+        /* reset missed packages counter */
+        hl->pkg_missed = 0;
+      } else
+        hl->pkg_missed++;
+
+      /* if the host did not answer our last N packages, trigger a resolv. */
+      if (ping_max_missed >= 0 && hl->pkg_missed >= ping_max_missed)
+      { /* {{{ */
+        /* we reset the missed package counter here, since we only want to
+         * trigger a resolv every N packages and not every package _AFTER_ N
+         * missed packages */
+        hl->pkg_missed = 0;
+
+        WARNING ("ping plugin: host %s has not answered %d PING requests,"
+          " triggering resolve", hl->host, ping_max_missed);
+
+        /* we trigger the resolv simply be removeing and adding the host to our
+         * ping object */
+        status = ping_host_remove (pingobj, hl->host);
+        if (status != 0)
+        {
+          WARNING ("ping plugin: ping_host_remove (%s) failed.", hl->host);
+        }
+        else
+        {
+          status = ping_host_add (pingobj, hl->host);
+          if (status != 0)
+            WARNING ("ping plugin: ping_host_add (%s) failed.", hl->host);
+        }
+      } /* }}} ping_max_missed */
+    } /* }}} for (iter) */
+
+    if (gettimeofday (&tv_end, NULL) < 0)
+    {
+      char errbuf[1024];
+      ERROR ("ping plugin: gettimeofday failed: %s",
+          sstrerror (errno, errbuf, sizeof (errbuf)));
+      ping_thread_error = 1;
+      break;
+    }
+
+    /* Calculate the absolute time until which to wait and store it in
+     * `ts_wait'. */
+    time_calc (&ts_wait, &ts_int, &tv_begin, &tv_end);
+
+    status = pthread_cond_timedwait (&ping_cond, &ping_lock, &ts_wait);
+    if (ping_thread_loop <= 0)
+      break;
+  } /* while (ping_thread_loop > 0) */
+
+  pthread_mutex_unlock (&ping_lock);
+  ping_destroy (pingobj);
+
+  return ((void *) 0);
+} /* }}} void *ping_thread */
+
+static int start_thread (void) /* {{{ */
+{
+  int status;
+
+  pthread_mutex_lock (&ping_lock);
+
+  if (ping_thread_loop != 0)
+  {
+    pthread_mutex_unlock (&ping_lock);
+    return (-1);
+  }
+
+  ping_thread_loop = 1;
+  ping_thread_error = 0;
+  status = pthread_create (&ping_thread_id, /* attr = */ NULL,
+      ping_thread, /* arg = */ (void *) 0);
+  if (status != 0)
+  {
+    ping_thread_loop = 0;
+    ERROR ("ping plugin: Starting thread failed.");
+    pthread_mutex_unlock (&ping_lock);
+    return (-1);
+  }
+    
+  pthread_mutex_unlock (&ping_lock);
+  return (0);
+} /* }}} int start_thread */
+
+static int stop_thread (void) /* {{{ */
+{
+  int status;
+
+  pthread_mutex_lock (&ping_lock);
+
+  if (ping_thread_loop == 0)
+  {
+    pthread_mutex_unlock (&ping_lock);
+    return (-1);
+  }
+
+  ping_thread_loop = 0;
+  pthread_cond_broadcast (&ping_cond);
+  pthread_mutex_unlock (&ping_lock);
+
+  status = pthread_join (ping_thread_id, /* return = */ NULL);
+  if (status != 0)
+  {
+    ERROR ("ping plugin: Stopping thread failed.");
+    status = -1;
+  }
+
+  memset (&ping_thread_id, 0, sizeof (ping_thread_id));
+  ping_thread_error = 0;
+
+  return (status);
+} /* }}} int stop_thread */
+
+static int ping_init (void) /* {{{ */
+{
+  if (hostlist_head == NULL)
+  {
+    NOTICE ("ping plugin: No hosts have been configured.");
+    return (-1);
+  }
+
+  if (ping_timeout > ping_interval)
+  {
+    ping_timeout = 0.9 * ping_interval;
+    WARNING ("ping plugin: Timeout is greater than interval. "
+        "Will use a timeout of %gs.", ping_timeout);
+  }
+
+  if (start_thread () != 0)
+    return (-1);
+
+  return (0);
+} /* }}} int ping_init */
+
+static int config_set_string (const char *name, /* {{{ */
+    char **var, const char *value)
+{
+  char *tmp;
+
+  tmp = strdup (value);
+  if (tmp == NULL)
+  {
+    char errbuf[1024];
+    ERROR ("ping plugin: Setting `%s' to `%s' failed: strdup failed: %s",
+        name, value, sstrerror (errno, errbuf, sizeof (errbuf)));
+    return (1);
+  }
+
+  if (*var != NULL)
+    free (*var);
+  *var = tmp;
+  return (0);
+} /* }}} int config_set_string */
+
+static int ping_config (const char *key, const char *value) /* {{{ */
+{
+  if (strcasecmp (key, "Host") == 0)
+  {
+    hostlist_t *hl;
+    char *host;
+
+    hl = (hostlist_t *) malloc (sizeof (hostlist_t));
+    if (hl == NULL)
+    {
+      char errbuf[1024];
+      ERROR ("ping plugin: malloc failed: %s",
+          sstrerror (errno, errbuf, sizeof (errbuf)));
+      return (1);
+    }
+
+    host = strdup (value);
+    if (host == NULL)
+    {
+      char errbuf[1024];
+      sfree (hl);
+      ERROR ("ping plugin: strdup failed: %s",
+          sstrerror (errno, errbuf, sizeof (errbuf)));
+      return (1);
+    }
+
+    hl->host = host;
+    hl->pkg_sent = 0;
+    hl->pkg_recv = 0;
+    hl->pkg_missed = 0;
+    hl->latency_total = 0.0;
+    hl->latency_squared = 0.0;
+    hl->next = hostlist_head;
+    hostlist_head = hl;
+  }
+  else if (strcasecmp (key, "SourceAddress") == 0)
+  {
+    int status = config_set_string (key, &ping_source, value);
+    if (status != 0)
+      return (status);
+  }
+#ifdef HAVE_OPING_1_3
+  else if (strcasecmp (key, "Device") == 0)
+  {
+    int status = config_set_string (key, &ping_device, value);
+    if (status != 0)
+      return (status);
+  }
+#endif
+  else if (strcasecmp (key, "TTL") == 0)
+  {
+    int ttl = atoi (value);
+    if ((ttl > 0) && (ttl <= 255))
+      ping_ttl = ttl;
+    else
+      WARNING ("ping plugin: Ignoring invalid TTL %i.", ttl);
+  }
+  else if (strcasecmp (key, "Interval") == 0)
+  {
+    double tmp;
+
+    tmp = atof (value);
+    if (tmp > 0.0)
+      ping_interval = tmp;
+    else
+      WARNING ("ping plugin: Ignoring invalid interval %g (%s)",
+          tmp, value);
+  }
+  else if (strcasecmp (key, "Timeout") == 0)
+  {
+    double tmp;
+
+    tmp = atof (value);
+    if (tmp > 0.0)
+      ping_timeout = tmp;
+    else
+      WARNING ("ping plugin: Ignoring invalid timeout %g (%s)",
+          tmp, value);
+  }
+  else if (strcasecmp (key, "MaxMissed") == 0)
+  {
+    ping_max_missed = atoi (value);
+    if (ping_max_missed < 0)
+      INFO ("ping plugin: MaxMissed < 0, disabled re-resolving of hosts");
+  }
+  else
+  {
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int ping_config */
+
+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_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));
+
+  plugin_dispatch_values (&vl);
+} /* }}} void ping_submit */
+
+static int ping_read (void) /* {{{ */
+{
+  hostlist_t *hl;
+
+  if (ping_thread_error != 0)
+  {
+    ERROR ("ping plugin: The ping thread had a problem. Restarting it.");
+
+    stop_thread ();
+
+    for (hl = hostlist_head; hl != NULL; hl = hl->next)
+    {
+      hl->pkg_sent = 0;
+      hl->pkg_recv = 0;
+      hl->latency_total = 0.0;
+      hl->latency_squared = 0.0;
+    }
+
+    start_thread ();
+
+    return (-1);
+  } /* if (ping_thread_error != 0) */
+
+  for (hl = hostlist_head; hl != NULL; hl = hl->next) /* {{{ */
+  {
+    uint32_t pkg_sent;
+    uint32_t pkg_recv;
+    double latency_total;
+    double latency_squared;
+
+    double latency_average;
+    double latency_stddev;
+
+    double droprate;
+
+    /* Locking here works, because the structure of the linked list is only
+     * changed during configure and shutdown. */
+    pthread_mutex_lock (&ping_lock);
+
+    pkg_sent = hl->pkg_sent;
+    pkg_recv = hl->pkg_recv;
+    latency_total = hl->latency_total;
+    latency_squared = hl->latency_squared;
+
+    hl->pkg_sent = 0;
+    hl->pkg_recv = 0;
+    hl->latency_total = 0.0;
+    hl->latency_squared = 0.0;
+
+    pthread_mutex_unlock (&ping_lock);
+
+    /* This e. g. happens when starting up. */
+    if (pkg_sent == 0)
+    {
+      DEBUG ("ping plugin: No packages for host %s have been sent.",
+          hl->host);
+      continue;
+    }
+
+    /* Calculate average. Beware of division by zero. */
+    if (pkg_recv == 0)
+      latency_average = NAN;
+    else
+      latency_average = latency_total / ((double) pkg_recv);
+
+    /* Calculate standard deviation. Beware even more of division by zero. */
+    if (pkg_recv == 0)
+      latency_stddev = NAN;
+    else if (pkg_recv == 1)
+      latency_stddev = 0.0;
+    else
+      latency_stddev = sqrt (((((double) pkg_recv) * latency_squared)
+          - (latency_total * latency_total))
+          / ((double) (pkg_recv * (pkg_recv - 1))));
+
+    /* Calculate drop rate. */
+    droprate = ((double) (pkg_sent - pkg_recv)) / ((double) pkg_sent);
+
+    submit (hl->host, "ping", latency_average);
+    submit (hl->host, "ping_stddev", latency_stddev);
+    submit (hl->host, "ping_droprate", droprate);
+  } /* }}} for (hl = hostlist_head; hl != NULL; hl = hl->next) */
+
+  return (0);
+} /* }}} int ping_read */
+
+static int ping_shutdown (void) /* {{{ */
+{
+  hostlist_t *hl;
+
+  INFO ("ping plugin: Shutting down thread.");
+  if (stop_thread () < 0)
+    return (-1);
+
+  hl = hostlist_head;
+  while (hl != NULL)
+  {
+    hostlist_t *hl_next;
+
+    hl_next = hl->next;
+
+    sfree (hl->host);
+    sfree (hl);
+
+    hl = hl_next;
+  }
+
+  return (0);
+} /* }}} int ping_shutdown */
+
+void module_register (void)
+{
+  plugin_register_config ("ping", ping_config,
+      config_keys, config_keys_num);
+  plugin_register_init ("ping", ping_init);
+  plugin_register_read ("ping", ping_read);
+  plugin_register_shutdown ("ping", ping_shutdown);
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/plugin.c b/src/plugin.c
new file mode 100644 (file)
index 0000000..91c40b6
--- /dev/null
@@ -0,0 +1,1897 @@
+/**
+ * collectd - src/plugin.c
+ * Copyright (C) 2005-2011  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at collectd.org>
+ *   Sebastian Harl <sh at tokkee.org>
+ **/
+
+#include "collectd.h"
+#include "utils_complain.h"
+
+#include <ltdl.h>
+
+#if HAVE_PTHREAD_H
+# include <pthread.h>
+#endif
+
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+#include "utils_avltree.h"
+#include "utils_llist.h"
+#include "utils_heap.h"
+#include "utils_cache.h"
+#include "filter_chain.h"
+
+/*
+ * Private structures
+ */
+struct callback_func_s
+{
+       void *cf_callback;
+       user_data_t cf_udata;
+};
+typedef struct callback_func_s callback_func_t;
+
+#define RF_SIMPLE  0
+#define RF_COMPLEX 1
+#define RF_REMOVE  65535
+struct read_func_s
+{
+       /* `read_func_t' "inherits" from `callback_func_t'.
+        * The `rf_super' member MUST be the first one in this structure! */
+#define rf_callback rf_super.cf_callback
+#define rf_udata rf_super.cf_udata
+       callback_func_t rf_super;
+       char rf_group[DATA_MAX_NAME_LEN];
+       char rf_name[DATA_MAX_NAME_LEN];
+       int rf_type;
+       struct timespec rf_interval;
+       struct timespec rf_effective_interval;
+       struct timespec rf_next_read;
+};
+typedef struct read_func_s read_func_t;
+
+/*
+ * Private variables
+ */
+static llist_t *list_init;
+static llist_t *list_write;
+static llist_t *list_flush;
+static llist_t *list_missing;
+static llist_t *list_shutdown;
+static llist_t *list_log;
+static llist_t *list_notification;
+
+static fc_chain_t *pre_cache_chain = NULL;
+static fc_chain_t *post_cache_chain = NULL;
+
+static c_avl_tree_t *data_sets;
+
+static char *plugindir = NULL;
+
+static c_heap_t       *read_heap = NULL;
+static llist_t        *read_list;
+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 functions
+ */
+static const char *plugin_get_dir (void)
+{
+       if (plugindir == NULL)
+               return (PLUGINDIR);
+       else
+               return (plugindir);
+}
+
+static void destroy_callback (callback_func_t *cf) /* {{{ */
+{
+       if (cf == NULL)
+               return;
+
+       if ((cf->cf_udata.data != NULL) && (cf->cf_udata.free_func != NULL))
+       {
+               cf->cf_udata.free_func (cf->cf_udata.data);
+               cf->cf_udata.data = NULL;
+               cf->cf_udata.free_func = NULL;
+       }
+       sfree (cf);
+} /* }}} void destroy_callback */
+
+static void destroy_all_callbacks (llist_t **list) /* {{{ */
+{
+       llentry_t *le;
+
+       if (*list == NULL)
+               return;
+
+       le = llist_head (*list);
+       while (le != NULL)
+       {
+               llentry_t *le_next;
+
+               le_next = le->next;
+
+               sfree (le->key);
+               destroy_callback (le->value);
+               le->value = NULL;
+
+               le = le_next;
+       }
+
+       llist_destroy (*list);
+       *list = NULL;
+} /* }}} void destroy_all_callbacks */
+
+static void destroy_read_heap (void) /* {{{ */
+{
+       if (read_heap == NULL)
+               return;
+
+       while (42)
+       {
+               callback_func_t *cf;
+
+               cf = c_heap_get_root (read_heap);
+               if (cf == NULL)
+                       break;
+
+               destroy_callback (cf);
+       }
+
+       c_heap_destroy (read_heap);
+       read_heap = NULL;
+} /* }}} void destroy_read_heap */
+
+static int register_callback (llist_t **list, /* {{{ */
+               const char *name, callback_func_t *cf)
+{
+       llentry_t *le;
+       char *key;
+
+       if (*list == NULL)
+       {
+               *list = llist_create ();
+               if (*list == NULL)
+               {
+                       ERROR ("plugin: register_callback: "
+                                       "llist_create failed.");
+                       destroy_callback (cf);
+                       return (-1);
+               }
+       }
+
+       key = strdup (name);
+       if (key == NULL)
+       {
+               ERROR ("plugin: register_callback: strdup failed.");
+               destroy_callback (cf);
+               return (-1);
+       }
+
+       le = llist_search (*list, name);
+       if (le == NULL)
+       {
+               le = llentry_create (key, cf);
+               if (le == NULL)
+               {
+                       ERROR ("plugin: register_callback: "
+                                       "llentry_create failed.");
+                       free (key);
+                       destroy_callback (cf);
+                       return (-1);
+               }
+
+               llist_append (*list, le);
+       }
+       else
+       {
+               callback_func_t *old_cf;
+
+               old_cf = le->value;
+               le->value = cf;
+
+               WARNING ("plugin: register_callback: "
+                               "a callback named `%s' already exists - "
+                               "overwriting the old entry!", name);
+
+               destroy_callback (old_cf);
+               sfree (key);
+       }
+
+       return (0);
+} /* }}} int register_callback */
+
+static int create_register_callback (llist_t **list, /* {{{ */
+               const char *name, void *callback, user_data_t *ud)
+{
+       callback_func_t *cf;
+
+       cf = (callback_func_t *) malloc (sizeof (*cf));
+       if (cf == NULL)
+       {
+               ERROR ("plugin: create_register_callback: malloc failed.");
+               return (-1);
+       }
+       memset (cf, 0, sizeof (*cf));
+
+       cf->cf_callback = callback;
+       if (ud == NULL)
+       {
+               cf->cf_udata.data = NULL;
+               cf->cf_udata.free_func = NULL;
+       }
+       else
+       {
+               cf->cf_udata = *ud;
+       }
+
+       return (register_callback (list, name, cf));
+} /* }}} int create_register_callback */
+
+static int plugin_unregister (llist_t *list, const char *name) /* {{{ */
+{
+       llentry_t *e;
+
+       if (list == NULL)
+               return (-1);
+
+       e = llist_search (list, name);
+       if (e == NULL)
+               return (-1);
+
+       llist_remove (list, e);
+
+       sfree (e->key);
+       destroy_callback (e->value);
+
+       llentry_destroy (e);
+
+       return (0);
+} /* }}} int plugin_unregister */
+
+/*
+ * (Try to) load the shared object `file'. Won't complain if it isn't a shared
+ * object, but it will bitch about a shared object not having a
+ * ``module_register'' symbol..
+ */
+static int plugin_load_file (char *file, uint32_t flags)
+{
+       lt_dlhandle dlh;
+       void (*reg_handle) (void);
+
+       lt_dlinit ();
+       lt_dlerror (); /* clear errors */
+
+#if LIBTOOL_VERSION == 2
+       if (flags & PLUGIN_FLAGS_GLOBAL) {
+               lt_dladvise advise;
+               lt_dladvise_init(&advise);
+               lt_dladvise_global(&advise);
+               dlh = lt_dlopenadvise(file, advise);
+               lt_dladvise_destroy(&advise);
+       } else {
+               dlh = lt_dlopen (file);
+       }
+#else /* if LIBTOOL_VERSION == 1 */
+       if (flags & PLUGIN_FLAGS_GLOBAL)
+               WARNING ("plugin_load_file: The global flag is not supported, "
+                               "libtool 2 is required for this.");
+       dlh = lt_dlopen (file);
+#endif
+
+       if (dlh == NULL)
+       {
+               char errbuf[1024] = "";
+
+               ssnprintf (errbuf, sizeof (errbuf),
+                               "lt_dlopen (\"%s\") failed: %s. "
+                               "The most common cause for this problem are "
+                               "missing dependencies. Use ldd(1) to check "
+                               "the dependencies of the plugin "
+                               "/ shared object.",
+                               file, lt_dlerror ());
+
+               ERROR ("%s", errbuf);
+               /* Make sure this is printed to STDERR in any case, but also
+                * make sure it's printed only once. */
+               if (list_log != NULL)
+                       fprintf (stderr, "ERROR: %s\n", errbuf);
+
+               return (1);
+       }
+
+       if ((reg_handle = (void (*) (void)) lt_dlsym (dlh, "module_register")) == NULL)
+       {
+               WARNING ("Couldn't find symbol \"module_register\" in \"%s\": %s\n",
+                               file, lt_dlerror ());
+               lt_dlclose (dlh);
+               return (-1);
+       }
+
+       (*reg_handle) ();
+
+       return (0);
+}
+
+static _Bool timeout_reached(struct timespec timeout)
+{
+       struct timeval now;
+       gettimeofday(&now, NULL);
+       return (now.tv_sec >= timeout.tv_sec && now.tv_usec >= (timeout.tv_nsec / 1000));
+}
+
+static void *plugin_read_thread (void __attribute__((unused)) *args)
+{
+       while (read_loop != 0)
+       {
+               read_func_t *rf;
+               cdtime_t now;
+               int status;
+               int rf_type;
+               int rc;
+
+               /* Get the read function that needs to be read next. */
+               rf = c_heap_get_root (read_heap);
+               if (rf == NULL)
+               {
+                       struct timespec abstime;
+
+                       now = cdtime ();
+
+                       CDTIME_T_TO_TIMESPEC (now + interval_g, &abstime);
+
+                       pthread_mutex_lock (&read_lock);
+                       pthread_cond_timedwait (&read_cond, &read_lock,
+                                       &abstime);
+                       pthread_mutex_unlock (&read_lock);
+                       continue;
+               }
+
+               if ((rf->rf_interval.tv_sec == 0) && (rf->rf_interval.tv_nsec == 0))
+               {
+                       now = cdtime ();
+
+                       CDTIME_T_TO_TIMESPEC (interval_g, &rf->rf_interval);
+
+                       rf->rf_effective_interval = rf->rf_interval;
+
+                       CDTIME_T_TO_TIMESPEC (now, &rf->rf_next_read);
+               }
+
+               /* sleep until this entry is due,
+                * using pthread_cond_timedwait */
+               pthread_mutex_lock (&read_lock);
+               /* In pthread_cond_timedwait, spurious wakeups are possible
+                * (and really happen, at least on NetBSD with > 1 CPU), thus
+                * we need to re-evaluate the condition every time
+                * pthread_cond_timedwait returns. */
+               rc = 0;
+               while ((read_loop != 0)
+                               && !timeout_reached(rf->rf_next_read)
+                               && rc == 0)
+               {
+                       rc = pthread_cond_timedwait (&read_cond, &read_lock,
+                               &rf->rf_next_read);
+               }
+
+               /* Must hold `read_lock' when accessing `rf->rf_type'. */
+               rf_type = rf->rf_type;
+               pthread_mutex_unlock (&read_lock);
+
+               /* Check if we're supposed to stop.. This may have interrupted
+                * the sleep, too. */
+               if (read_loop == 0)
+               {
+                       /* Insert `rf' again, so it can be free'd correctly */
+                       c_heap_insert (read_heap, rf);
+                       break;
+               }
+
+               /* The entry has been marked for deletion. The linked list
+                * entry has already been removed by `plugin_unregister_read'.
+                * All we have to do here is free the `read_func_t' and
+                * continue. */
+               if (rf_type == RF_REMOVE)
+               {
+                       DEBUG ("plugin_read_thread: Destroying the `%s' "
+                                       "callback.", rf->rf_name);
+                       destroy_callback ((callback_func_t *) rf);
+                       rf = NULL;
+                       continue;
+               }
+
+               DEBUG ("plugin_read_thread: Handling `%s'.", rf->rf_name);
+
+               if (rf_type == RF_SIMPLE)
+               {
+                       int (*callback) (void);
+
+                       callback = rf->rf_callback;
+                       status = (*callback) ();
+               }
+               else
+               {
+                       plugin_read_cb callback;
+
+                       assert (rf_type == RF_COMPLEX);
+
+                       callback = rf->rf_callback;
+                       status = (*callback) (&rf->rf_udata);
+               }
+
+               /* If the function signals failure, we will increase the
+                * intervals in which it will be called. */
+               if (status != 0)
+               {
+                       rf->rf_effective_interval.tv_sec *= 2;
+                       rf->rf_effective_interval.tv_nsec *= 2;
+                       NORMALIZE_TIMESPEC (rf->rf_effective_interval);
+
+                       if (rf->rf_effective_interval.tv_sec >= 86400)
+                       {
+                               rf->rf_effective_interval.tv_sec = 86400;
+                               rf->rf_effective_interval.tv_nsec = 0;
+                       }
+
+                       NOTICE ("read-function of plugin `%s' failed. "
+                                       "Will suspend it for %i seconds.",
+                                       rf->rf_name,
+                                       (int) rf->rf_effective_interval.tv_sec);
+               }
+               else
+               {
+                       /* Success: Restore the interval, if it was changed. */
+                       rf->rf_effective_interval = rf->rf_interval;
+               }
+
+               /* update the ``next read due'' field */
+               now = cdtime ();
+
+               DEBUG ("plugin_read_thread: Effective interval of the "
+                               "%s plugin is %i.%09i.",
+                               rf->rf_name,
+                               (int) rf->rf_effective_interval.tv_sec,
+                               (int) rf->rf_effective_interval.tv_nsec);
+
+               /* Calculate the next (absolute) time at which this function
+                * should be called. */
+               rf->rf_next_read.tv_sec = rf->rf_next_read.tv_sec
+                       + rf->rf_effective_interval.tv_sec;
+               rf->rf_next_read.tv_nsec = rf->rf_next_read.tv_nsec
+                       + rf->rf_effective_interval.tv_nsec;
+               NORMALIZE_TIMESPEC (rf->rf_next_read);
+
+               /* Check, if `rf_next_read' is in the past. */
+               if (TIMESPEC_TO_CDTIME_T (&rf->rf_next_read) < now)
+               {
+                       /* `rf_next_read' is in the past. Insert `now'
+                        * so this value doesn't trail off into the
+                        * past too much. */
+                       CDTIME_T_TO_TIMESPEC (now, &rf->rf_next_read);
+               }
+
+               DEBUG ("plugin_read_thread: Next read of the %s plugin at %i.%09i.",
+                               rf->rf_name,
+                               (int) rf->rf_next_read.tv_sec,
+                               (int) rf->rf_next_read.tv_nsec);
+
+               /* Re-insert this read function into the heap again. */
+               c_heap_insert (read_heap, rf);
+       } /* while (read_loop) */
+
+       pthread_exit (NULL);
+       return ((void *) 0);
+} /* void *plugin_read_thread */
+
+static void start_read_threads (int num)
+{
+       int i;
+
+       if (read_threads != NULL)
+               return;
+
+       read_threads = (pthread_t *) calloc (num, sizeof (pthread_t));
+       if (read_threads == NULL)
+       {
+               ERROR ("plugin: start_read_threads: calloc failed.");
+               return;
+       }
+
+       read_threads_num = 0;
+       for (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.");
+                       return;
+               }
+       } /* for (i) */
+} /* void start_read_threads */
+
+static void stop_read_threads (void)
+{
+       int i;
+
+       if (read_threads == NULL)
+               return;
+
+       INFO ("collectd: Stopping %i read threads.", read_threads_num);
+
+       pthread_mutex_lock (&read_lock);
+       read_loop = 0;
+       DEBUG ("plugin: stop_read_threads: Signalling `read_cond'");
+       pthread_cond_broadcast (&read_cond);
+       pthread_mutex_unlock (&read_lock);
+
+       for (i = 0; i < read_threads_num; i++)
+       {
+               if (pthread_join (read_threads[i], NULL) != 0)
+               {
+                       ERROR ("plugin: stop_read_threads: pthread_join failed.");
+               }
+               read_threads[i] = (pthread_t) 0;
+       }
+       sfree (read_threads);
+       read_threads_num = 0;
+} /* void stop_read_threads */
+
+/*
+ * Public functions
+ */
+void plugin_set_dir (const char *dir)
+{
+       if (plugindir != NULL)
+               free (plugindir);
+
+       if (dir == NULL)
+               plugindir = NULL;
+       else if ((plugindir = strdup (dir)) == NULL)
+       {
+               char errbuf[1024];
+               ERROR ("strdup failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+       }
+}
+
+#define BUFSIZE 512
+int plugin_load (const char *type, uint32_t flags)
+{
+       DIR  *dh;
+       const char *dir;
+       char  filename[BUFSIZE] = "";
+       char  typename[BUFSIZE];
+       int   typename_len;
+       int   ret;
+       struct stat    statbuf;
+       struct dirent *de;
+       int status;
+
+       DEBUG ("type = %s", type);
+
+       dir = plugin_get_dir ();
+       ret = 1;
+
+       /* `cpu' should not match `cpufreq'. To solve this we add `.so' to the
+        * type when matching the filename */
+       status = ssnprintf (typename, sizeof (typename), "%s.so", type);
+       if ((status < 0) || ((size_t) status >= sizeof (typename)))
+       {
+               WARNING ("snprintf: truncated: `%s.so'", type);
+               return (-1);
+       }
+       typename_len = strlen (typename);
+
+       if ((dh = opendir (dir)) == NULL)
+       {
+               char errbuf[1024];
+               ERROR ("opendir (%s): %s", dir,
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       while ((de = readdir (dh)) != NULL)
+       {
+               if (strncasecmp (de->d_name, typename, typename_len))
+                       continue;
+
+               status = ssnprintf (filename, sizeof (filename),
+                               "%s/%s", dir, de->d_name);
+               if ((status < 0) || ((size_t) status >= sizeof (filename)))
+               {
+                       WARNING ("snprintf: truncated: `%s/%s'", dir, de->d_name);
+                       continue;
+               }
+
+               if (lstat (filename, &statbuf) == -1)
+               {
+                       char errbuf[1024];
+                       WARNING ("stat %s: %s", filename,
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       continue;
+               }
+               else if (!S_ISREG (statbuf.st_mode))
+               {
+                       /* don't follow symlinks */
+                       WARNING ("stat %s: not a regular file", filename);
+                       continue;
+               }
+
+               if (plugin_load_file (filename, flags) == 0)
+               {
+                       /* success */
+                       ret = 0;
+                       break;
+               }
+               else
+               {
+                       fprintf (stderr, "Unable to load plugin %s.\n", type);
+               }
+       }
+
+       closedir (dh);
+
+       if (filename[0] == '\0')
+               fprintf (stderr, "Could not find plugin %s.\n", type);
+
+       return (ret);
+}
+
+/*
+ * The `register_*' functions follow
+ */
+int plugin_register_config (const char *name,
+               int (*callback) (const char *key, const char *val),
+               const char **keys, int keys_num)
+{
+       cf_register (name, callback, keys, keys_num);
+       return (0);
+} /* int plugin_register_config */
+
+int plugin_register_complex_config (const char *type,
+               int (*callback) (oconfig_item_t *))
+{
+       return (cf_register_complex (type, callback));
+} /* int plugin_register_complex_config */
+
+int plugin_register_init (const char *name,
+               int (*callback) (void))
+{
+       return (create_register_callback (&list_init, name, (void *) callback,
+                               /* user_data = */ NULL));
+} /* plugin_register_init */
+
+static int plugin_compare_read_func (const void *arg0, const void *arg1)
+{
+       const read_func_t *rf0;
+       const read_func_t *rf1;
+
+       rf0 = arg0;
+       rf1 = arg1;
+
+       if (rf0->rf_next_read.tv_sec < rf1->rf_next_read.tv_sec)
+               return (-1);
+       else if (rf0->rf_next_read.tv_sec > rf1->rf_next_read.tv_sec)
+               return (1);
+       else if (rf0->rf_next_read.tv_nsec < rf1->rf_next_read.tv_nsec)
+               return (-1);
+       else if (rf0->rf_next_read.tv_nsec > rf1->rf_next_read.tv_nsec)
+               return (1);
+       else
+               return (0);
+} /* int plugin_compare_read_func */
+
+/* Add a read function to both, the heap and a linked list. The linked list if
+ * used to look-up read functions, especially for the remove function. The heap
+ * is used to determine which plugin to read next. */
+static int plugin_insert_read (read_func_t *rf)
+{
+       int status;
+       llentry_t *le;
+
+       pthread_mutex_lock (&read_lock);
+
+       if (read_list == NULL)
+       {
+               read_list = llist_create ();
+               if (read_list == NULL)
+               {
+                       pthread_mutex_unlock (&read_lock);
+                       ERROR ("plugin_insert_read: read_list failed.");
+                       return (-1);
+               }
+       }
+
+       if (read_heap == NULL)
+       {
+               read_heap = c_heap_create (plugin_compare_read_func);
+               if (read_heap == NULL)
+               {
+                       pthread_mutex_unlock (&read_lock);
+                       ERROR ("plugin_insert_read: c_heap_create failed.");
+                       return (-1);
+               }
+       }
+
+       le = llist_search (read_list, rf->rf_name);
+       if (le != NULL)
+       {
+               pthread_mutex_unlock (&read_lock);
+               WARNING ("The read function \"%s\" is already registered. "
+                               "Check for duplicate \"LoadPlugin\" lines "
+                               "in your configuration!",
+                               rf->rf_name);
+               return (EINVAL);
+       }
+
+       le = llentry_create (rf->rf_name, rf);
+       if (le == NULL)
+       {
+               pthread_mutex_unlock (&read_lock);
+               ERROR ("plugin_insert_read: llentry_create failed.");
+               return (-1);
+       }
+
+       status = c_heap_insert (read_heap, rf);
+       if (status != 0)
+       {
+               pthread_mutex_unlock (&read_lock);
+               ERROR ("plugin_insert_read: c_heap_insert failed.");
+               llentry_destroy (le);
+               return (-1);
+       }
+
+       /* This does not fail. */
+       llist_append (read_list, le);
+
+       pthread_mutex_unlock (&read_lock);
+       return (0);
+} /* int plugin_insert_read */
+
+int plugin_register_read (const char *name,
+               int (*callback) (void))
+{
+       read_func_t *rf;
+       int status;
+
+       rf = malloc (sizeof (*rf));
+       if (rf == NULL)
+       {
+               ERROR ("plugin_register_read: malloc failed.");
+               return (ENOMEM);
+       }
+
+       memset (rf, 0, sizeof (read_func_t));
+       rf->rf_callback = (void *) callback;
+       rf->rf_udata.data = NULL;
+       rf->rf_udata.free_func = NULL;
+       rf->rf_group[0] = '\0';
+       sstrncpy (rf->rf_name, name, sizeof (rf->rf_name));
+       rf->rf_type = RF_SIMPLE;
+       rf->rf_interval.tv_sec = 0;
+       rf->rf_interval.tv_nsec = 0;
+       rf->rf_effective_interval = rf->rf_interval;
+
+       status = plugin_insert_read (rf);
+       if (status != 0)
+               sfree (rf);
+
+       return (status);
+} /* int plugin_register_read */
+
+int plugin_register_complex_read (const char *group, const char *name,
+               plugin_read_cb callback,
+               const struct timespec *interval,
+               user_data_t *user_data)
+{
+       read_func_t *rf;
+       int status;
+
+       rf = malloc (sizeof (*rf));
+       if (rf == NULL)
+       {
+               ERROR ("plugin_register_complex_read: malloc failed.");
+               return (ENOMEM);
+       }
+
+       memset (rf, 0, sizeof (read_func_t));
+       rf->rf_callback = (void *) callback;
+       if (group != NULL)
+               sstrncpy (rf->rf_group, group, sizeof (rf->rf_group));
+       else
+               rf->rf_group[0] = '\0';
+       sstrncpy (rf->rf_name, name, sizeof (rf->rf_name));
+       rf->rf_type = RF_COMPLEX;
+       if (interval != NULL)
+       {
+               rf->rf_interval = *interval;
+       }
+       rf->rf_effective_interval = rf->rf_interval;
+
+       /* Set user data */
+       if (user_data == NULL)
+       {
+               rf->rf_udata.data = NULL;
+               rf->rf_udata.free_func = NULL;
+       }
+       else
+       {
+               rf->rf_udata = *user_data;
+       }
+
+       status = plugin_insert_read (rf);
+       if (status != 0)
+               sfree (rf);
+
+       return (status);
+} /* int plugin_register_complex_read */
+
+int plugin_register_write (const char *name,
+               plugin_write_cb callback, user_data_t *ud)
+{
+       return (create_register_callback (&list_write, name,
+                               (void *) callback, ud));
+} /* int plugin_register_write */
+
+int plugin_register_flush (const char *name,
+               plugin_flush_cb callback, user_data_t *ud)
+{
+       return (create_register_callback (&list_flush, name,
+                               (void *) callback, ud));
+} /* int plugin_register_flush */
+
+int plugin_register_missing (const char *name,
+               plugin_missing_cb callback, user_data_t *ud)
+{
+       return (create_register_callback (&list_missing, name,
+                               (void *) callback, ud));
+} /* int plugin_register_missing */
+
+int plugin_register_shutdown (const char *name,
+               int (*callback) (void))
+{
+       return (create_register_callback (&list_shutdown, name,
+                               (void *) callback, /* user_data = */ NULL));
+} /* int plugin_register_shutdown */
+
+int plugin_register_data_set (const data_set_t *ds)
+{
+       data_set_t *ds_copy;
+       int i;
+
+       if ((data_sets != NULL)
+                       && (c_avl_get (data_sets, ds->type, NULL) == 0))
+       {
+               NOTICE ("Replacing DS `%s' with another version.", ds->type);
+               plugin_unregister_data_set (ds->type);
+       }
+       else if (data_sets == NULL)
+       {
+               data_sets = c_avl_create ((int (*) (const void *, const void *)) strcmp);
+               if (data_sets == NULL)
+                       return (-1);
+       }
+
+       ds_copy = (data_set_t *) malloc (sizeof (data_set_t));
+       if (ds_copy == NULL)
+               return (-1);
+       memcpy(ds_copy, ds, sizeof (data_set_t));
+
+       ds_copy->ds = (data_source_t *) malloc (sizeof (data_source_t)
+                       * ds->ds_num);
+       if (ds_copy->ds == NULL)
+       {
+               free (ds_copy);
+               return (-1);
+       }
+
+       for (i = 0; i < ds->ds_num; i++)
+               memcpy (ds_copy->ds + i, ds->ds + i, sizeof (data_source_t));
+
+       return (c_avl_insert (data_sets, (void *) ds_copy->type, (void *) ds_copy));
+} /* int plugin_register_data_set */
+
+int plugin_register_log (const char *name,
+               plugin_log_cb callback, user_data_t *ud)
+{
+       return (create_register_callback (&list_log, name,
+                               (void *) callback, ud));
+} /* int plugin_register_log */
+
+int plugin_register_notification (const char *name,
+               plugin_notification_cb callback, user_data_t *ud)
+{
+       return (create_register_callback (&list_notification, name,
+                               (void *) callback, ud));
+} /* int plugin_register_log */
+
+int plugin_unregister_config (const char *name)
+{
+       cf_unregister (name);
+       return (0);
+} /* int plugin_unregister_config */
+
+int plugin_unregister_complex_config (const char *name)
+{
+       cf_unregister_complex (name);
+       return (0);
+} /* int plugin_unregister_complex_config */
+
+int plugin_unregister_init (const char *name)
+{
+       return (plugin_unregister (list_init, name));
+}
+
+int plugin_unregister_read (const char *name) /* {{{ */
+{
+       llentry_t *le;
+       read_func_t *rf;
+
+       if (name == NULL)
+               return (-ENOENT);
+
+       pthread_mutex_lock (&read_lock);
+
+       if (read_list == NULL)
+       {
+               pthread_mutex_unlock (&read_lock);
+               return (-ENOENT);
+       }
+
+       le = llist_search (read_list, name);
+       if (le == NULL)
+       {
+               pthread_mutex_unlock (&read_lock);
+               WARNING ("plugin_unregister_read: No such read function: %s",
+                               name);
+               return (-ENOENT);
+       }
+
+       llist_remove (read_list, le);
+
+       rf = le->value;
+       assert (rf != NULL);
+       rf->rf_type = RF_REMOVE;
+
+       pthread_mutex_unlock (&read_lock);
+
+       llentry_destroy (le);
+
+       DEBUG ("plugin_unregister_read: Marked `%s' for removal.", name);
+
+       return (0);
+} /* }}} int plugin_unregister_read */
+
+static int compare_read_func_group (llentry_t *e, void *ud) /* {{{ */
+{
+       read_func_t *rf    = e->value;
+       char        *group = ud;
+
+       return strcmp (rf->rf_group, (const char *)group);
+} /* }}} int compare_read_func_group */
+
+int plugin_unregister_read_group (const char *group) /* {{{ */
+{
+       llentry_t *le;
+       read_func_t *rf;
+
+       int found = 0;
+
+       if (group == NULL)
+               return (-ENOENT);
+
+       pthread_mutex_lock (&read_lock);
+
+       if (read_list == NULL)
+       {
+               pthread_mutex_unlock (&read_lock);
+               return (-ENOENT);
+       }
+
+       while (42)
+       {
+               le = llist_search_custom (read_list,
+                               compare_read_func_group, (void *)group);
+
+               if (le == NULL)
+                       break;
+
+               ++found;
+
+               llist_remove (read_list, le);
+
+               rf = le->value;
+               assert (rf != NULL);
+               rf->rf_type = RF_REMOVE;
+
+               llentry_destroy (le);
+
+               DEBUG ("plugin_unregister_read_group: "
+                               "Marked `%s' (group `%s') for removal.",
+                               rf->rf_name, group);
+       }
+
+       pthread_mutex_unlock (&read_lock);
+
+       if (found == 0)
+       {
+               WARNING ("plugin_unregister_read_group: No such "
+                               "group of read function: %s", group);
+               return (-ENOENT);
+       }
+
+       return (0);
+} /* }}} int plugin_unregister_read_group */
+
+int plugin_unregister_write (const char *name)
+{
+       return (plugin_unregister (list_write, name));
+}
+
+int plugin_unregister_flush (const char *name)
+{
+       return (plugin_unregister (list_flush, name));
+}
+
+int plugin_unregister_missing (const char *name)
+{
+       return (plugin_unregister (list_missing, name));
+}
+
+int plugin_unregister_shutdown (const char *name)
+{
+       return (plugin_unregister (list_shutdown, name));
+}
+
+int plugin_unregister_data_set (const char *name)
+{
+       data_set_t *ds;
+
+       if (data_sets == NULL)
+               return (-1);
+
+       if (c_avl_remove (data_sets, name, NULL, (void *) &ds) != 0)
+               return (-1);
+
+       sfree (ds->ds);
+       sfree (ds);
+
+       return (0);
+} /* int plugin_unregister_data_set */
+
+int plugin_unregister_log (const char *name)
+{
+       return (plugin_unregister (list_log, name));
+}
+
+int plugin_unregister_notification (const char *name)
+{
+       return (plugin_unregister (list_notification, name));
+}
+
+void plugin_init_all (void)
+{
+       const char *chain_name;
+       llentry_t *le;
+       int status;
+
+       /* Init the value cache */
+       uc_init ();
+
+       chain_name = global_option_get ("PreCacheChain");
+       pre_cache_chain = fc_chain_get_by_name (chain_name);
+
+       chain_name = global_option_get ("PostCacheChain");
+       post_cache_chain = fc_chain_get_by_name (chain_name);
+
+
+       if ((list_init == NULL) && (read_heap == NULL))
+               return;
+
+       /* Calling all init callbacks before checking if read callbacks
+        * are available allows the init callbacks to register the read
+        * callback. */
+       le = llist_head (list_init);
+       while (le != NULL)
+       {
+               callback_func_t *cf;
+               plugin_init_cb callback;
+
+               cf = le->value;
+               callback = cf->cf_callback;
+               status = (*callback) ();
+
+               if (status != 0)
+               {
+                       ERROR ("Initialization of plugin `%s' "
+                                       "failed with status %i. "
+                                       "Plugin will be unloaded.",
+                                       le->key, status);
+                       /* Plugins that register read callbacks from the init
+                        * callback should take care of appropriate error
+                        * handling themselves. */
+                       /* FIXME: Unload _all_ functions */
+                       plugin_unregister_read (le->key);
+               }
+
+               le = le->next;
+       }
+
+       /* Start read-threads */
+       if (read_heap != NULL)
+       {
+               const char *rt;
+               int num;
+               rt = global_option_get ("ReadThreads");
+               num = atoi (rt);
+               if (num != -1)
+                       start_read_threads ((num > 0) ? num : 5);
+       }
+} /* void plugin_init_all */
+
+/* TODO: Rename this function. */
+void plugin_read_all (void)
+{
+       uc_check_timeout ();
+
+       return;
+} /* void plugin_read_all */
+
+/* Read function called when the `-T' command line argument is given. */
+int plugin_read_all_once (void)
+{
+       int status;
+       int return_status = 0;
+
+       if (read_heap == NULL)
+       {
+               NOTICE ("No read-functions are registered.");
+               return (0);
+       }
+
+       while (42)
+       {
+               read_func_t *rf;
+
+               rf = c_heap_get_root (read_heap);
+               if (rf == NULL)
+                       break;
+
+               if (rf->rf_type == RF_SIMPLE)
+               {
+                       int (*callback) (void);
+
+                       callback = rf->rf_callback;
+                       status = (*callback) ();
+               }
+               else
+               {
+                       plugin_read_cb callback;
+
+                       callback = rf->rf_callback;
+                       status = (*callback) (&rf->rf_udata);
+               }
+
+               if (status != 0)
+               {
+                       NOTICE ("read-function of plugin `%s' failed.",
+                                       rf->rf_name);
+                       return_status = -1;
+               }
+
+               destroy_callback ((void *) rf);
+       }
+
+       return (return_status);
+} /* int plugin_read_all_once */
+
+int plugin_write (const char *plugin, /* {{{ */
+               const data_set_t *ds, const value_list_t *vl)
+{
+  llentry_t *le;
+  int status;
+
+  if (vl == NULL)
+    return (EINVAL);
+
+  if (list_write == NULL)
+    return (ENOENT);
+
+  if (ds == NULL)
+  {
+    ds = plugin_get_ds (vl->type);
+    if (ds == NULL)
+    {
+      ERROR ("plugin_write: Unable to lookup type `%s'.", vl->type);
+      return (ENOENT);
+    }
+  }
+
+  if (plugin == NULL)
+  {
+    int success = 0;
+    int failure = 0;
+
+    le = llist_head (list_write);
+    while (le != NULL)
+    {
+      callback_func_t *cf = le->value;
+      plugin_write_cb callback;
+
+      DEBUG ("plugin: plugin_write: Writing values via %s.", le->key);
+      callback = cf->cf_callback;
+      status = (*callback) (ds, vl, &cf->cf_udata);
+      if (status != 0)
+        failure++;
+      else
+        success++;
+
+      le = le->next;
+    }
+
+    if ((success == 0) && (failure != 0))
+      status = -1;
+    else
+      status = 0;
+  }
+  else /* plugin != NULL */
+  {
+    callback_func_t *cf;
+    plugin_write_cb callback;
+
+    le = llist_head (list_write);
+    while (le != NULL)
+    {
+      if (strcasecmp (plugin, le->key) == 0)
+        break;
+
+      le = le->next;
+    }
+
+    if (le == NULL)
+      return (ENOENT);
+
+    cf = le->value;
+
+    DEBUG ("plugin: plugin_write: Writing values via %s.", le->key);
+    callback = cf->cf_callback;
+    status = (*callback) (ds, vl, &cf->cf_udata);
+  }
+
+  return (status);
+} /* }}} int plugin_write */
+
+int plugin_flush (const char *plugin, cdtime_t timeout, const char *identifier)
+{
+  llentry_t *le;
+
+  if (list_flush == NULL)
+    return (0);
+
+  le = llist_head (list_flush);
+  while (le != NULL)
+  {
+    callback_func_t *cf;
+    plugin_flush_cb callback;
+
+    if ((plugin != NULL)
+        && (strcmp (plugin, le->key) != 0))
+    {
+      le = le->next;
+      continue;
+    }
+
+    cf = le->value;
+    callback = cf->cf_callback;
+
+    (*callback) (timeout, identifier, &cf->cf_udata);
+
+    le = le->next;
+  }
+  return (0);
+} /* int plugin_flush */
+
+void plugin_shutdown_all (void)
+{
+       llentry_t *le;
+
+       stop_read_threads ();
+
+       destroy_all_callbacks (&list_init);
+
+       pthread_mutex_lock (&read_lock);
+       llist_destroy (read_list);
+       read_list = NULL;
+       pthread_mutex_unlock (&read_lock);
+
+       destroy_read_heap ();
+
+       plugin_flush (/* plugin = */ NULL,
+                       /* timeout = */ 0,
+                       /* identifier = */ NULL);
+
+       le = NULL;
+       if (list_shutdown != NULL)
+               le = llist_head (list_shutdown);
+
+       while (le != NULL)
+       {
+               callback_func_t *cf;
+               plugin_shutdown_cb callback;
+
+               cf = le->value;
+               callback = cf->cf_callback;
+
+               /* Advance the pointer before calling the callback allows
+                * shutdown functions to unregister themselves. If done the
+                * other way around the memory `le' points to will be freed
+                * after callback returns. */
+               le = le->next;
+
+               (*callback) ();
+       }
+
+       /* Write plugins which use the `user_data' pointer usually need the
+        * same data available to the flush callback. If this is the case, set
+        * the free_function to NULL when registering the flush callback and to
+        * the real free function when registering the write callback. This way
+        * the data isn't freed twice. */
+       destroy_all_callbacks (&list_flush);
+       destroy_all_callbacks (&list_missing);
+       destroy_all_callbacks (&list_write);
+
+       destroy_all_callbacks (&list_notification);
+       destroy_all_callbacks (&list_shutdown);
+       destroy_all_callbacks (&list_log);
+} /* void plugin_shutdown_all */
+
+int plugin_dispatch_missing (const value_list_t *vl) /* {{{ */
+{
+  llentry_t *le;
+
+  if (list_missing == NULL)
+    return (0);
+
+  le = llist_head (list_missing);
+  while (le != NULL)
+  {
+    callback_func_t *cf;
+    plugin_missing_cb callback;
+    int status;
+
+    cf = le->value;
+    callback = cf->cf_callback;
+
+    status = (*callback) (vl, &cf->cf_udata);
+    if (status != 0)
+    {
+      if (status < 0)
+      {
+        ERROR ("plugin_dispatch_missing: Callback function \"%s\" "
+            "failed with status %i.",
+            le->key, status);
+        return (status);
+      }
+      else
+      {
+        return (0);
+      }
+    }
+
+    le = le->next;
+  }
+  return (0);
+} /* int }}} plugin_dispatch_missing */
+
+int plugin_dispatch_values (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;
+
+       if ((vl == NULL) || (vl->type[0] == 0)
+                       || (vl->values == NULL) || (vl->values_len < 1))
+       {
+               ERROR ("plugin_dispatch_values: Invalid value list "
+                               "from plugin %s.", vl->plugin);
+               return (-1);
+       }
+
+       /* Free meta data only if the calling function didn't specify any. In
+        * this case matches and targets may add some and the calling function
+        * may not expect (and therefore free) that data. */
+       if (vl->meta == NULL)
+               free_meta_data = 1;
+
+       if (list_write == NULL)
+               c_complain_once (LOG_WARNING, &no_write_complaint,
+                               "plugin_dispatch_values: No write callback has been "
+                               "registered. Please load at least one output plugin, "
+                               "if you want the collected data to be stored.");
+
+       if (data_sets == NULL)
+       {
+               ERROR ("plugin_dispatch_values: No data sets registered. "
+                               "Could the types database be read? Check "
+                               "your `TypesDB' setting!");
+               return (-1);
+       }
+
+       if (c_avl_get (data_sets, vl->type, (void *) &ds) != 0)
+       {
+               char ident[6 * DATA_MAX_NAME_LEN];
+
+               FORMAT_VL (ident, sizeof (ident), vl);
+               INFO ("plugin_dispatch_values: Dataset not found: %s "
+                               "(from \"%s\"), check your types.db!",
+                               vl->type, ident);
+               return (-1);
+       }
+
+       if (vl->time == 0)
+               vl->time = cdtime ();
+
+       if (vl->interval <= 0)
+               vl->interval = interval_g;
+
+       DEBUG ("plugin_dispatch_values: time = %.3f; interval = %.3f; "
+                       "host = %s; "
+                       "plugin = %s; plugin_instance = %s; "
+                       "type = %s; type_instance = %s;",
+                       CDTIME_T_TO_DOUBLE (vl->time),
+                       CDTIME_T_TO_DOUBLE (vl->interval),
+                       vl->host,
+                       vl->plugin, vl->plugin_instance,
+                       vl->type, vl->type_instance);
+
+#if COLLECT_DEBUG
+       assert (0 == strcmp (ds->type, vl->type));
+#else
+       if (0 != strcmp (ds->type, vl->type))
+               WARNING ("plugin_dispatch_values: (ds->type = %s) != (vl->type = %s)",
+                               ds->type, vl->type);
+#endif
+
+#if COLLECT_DEBUG
+       assert (ds->ds_num == vl->values_len);
+#else
+       if (ds->ds_num != vl->values_len)
+       {
+               ERROR ("plugin_dispatch_values: ds->type = %s: "
+                               "(ds->ds_num = %i) != "
+                               "(vl->values_len = %i)",
+                               ds->type, ds->ds_num, vl->values_len);
+               return (-1);
+       }
+#endif
+
+       escape_slashes (vl->host, sizeof (vl->host));
+       escape_slashes (vl->plugin, sizeof (vl->plugin));
+       escape_slashes (vl->plugin_instance, sizeof (vl->plugin_instance));
+       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)
+               {
+                       WARNING ("plugin_dispatch_values: Running the "
+                                       "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)
+                       {
+                               free (vl->values);
+                               vl->values     = saved_values;
+                               vl->values_len = saved_values_len;
+                       }
+                       return (0);
+               }
+       }
+
+       /* Update the value cache */
+       uc_update (ds, vl);
+
+       if (post_cache_chain != NULL)
+       {
+               status = fc_process_chain (ds, vl, post_cache_chain);
+               if (status < 0)
+               {
+                       WARNING ("plugin_dispatch_values: Running the "
+                                       "post-cache chain failed with "
+                                       "status %i (%#x).",
+                                       status, status);
+               }
+       }
+       else
+               fc_default_action (ds, vl);
+
+       /* Restore the state of the value_list so that plugins don't get
+        * confused.. */
+       if (saved_values != NULL)
+       {
+               free (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;
+       }
+
+       return (0);
+} /* int plugin_dispatch_values */
+
+int plugin_dispatch_values_secure (const value_list_t *vl)
+{
+  value_list_t vl_copy;
+  int status;
+
+  if (vl == NULL)
+    return EINVAL;
+
+  memcpy (&vl_copy, vl, sizeof (vl_copy));
+
+  /* Write callbacks must not change the values and meta pointers, so we can
+   * savely skip copying those and make this more efficient. */
+  if ((pre_cache_chain == NULL) && (post_cache_chain == NULL))
+    return (plugin_dispatch_values (&vl_copy));
+
+  /* Set pointers to NULL, just to be on the save side. */
+  vl_copy.values = NULL;
+  vl_copy.meta = NULL;
+
+  vl_copy.values = malloc (sizeof (*vl_copy.values) * vl->values_len);
+  if (vl_copy.values == NULL)
+  {
+    ERROR ("plugin_dispatch_values_secure: malloc failed.");
+    return (ENOMEM);
+  }
+  memcpy (vl_copy.values, vl->values, sizeof (*vl_copy.values) * vl->values_len);
+
+  if (vl->meta != NULL)
+  {
+    vl_copy.meta = meta_data_clone (vl->meta);
+    if (vl_copy.meta == NULL)
+    {
+      ERROR ("plugin_dispatch_values_secure: meta_data_clone failed.");
+      free (vl_copy.values);
+      return (ENOMEM);
+    }
+  } /* if (vl->meta) */
+
+  status = plugin_dispatch_values (&vl_copy);
+
+  meta_data_destroy (vl_copy.meta);
+  free (vl_copy.values);
+
+  return (status);
+} /* int plugin_dispatch_values_secure */
+
+int plugin_dispatch_notification (const notification_t *notif)
+{
+       llentry_t *le;
+       /* Possible TODO: Add flap detection here */
+
+       DEBUG ("plugin_dispatch_notification: severity = %i; message = %s; "
+                       "time = %.3f; host = %s;",
+                       notif->severity, notif->message,
+                       CDTIME_T_TO_DOUBLE (notif->time), notif->host);
+
+       /* Nobody cares for notifications */
+       if (list_notification == NULL)
+               return (-1);
+
+       le = llist_head (list_notification);
+       while (le != NULL)
+       {
+               callback_func_t *cf;
+               plugin_notification_cb callback;
+               int status;
+
+               cf = le->value;
+               callback = cf->cf_callback;
+               status = (*callback) (notif, &cf->cf_udata);
+               if (status != 0)
+               {
+                       WARNING ("plugin_dispatch_notification: Notification "
+                                       "callback %s returned %i.",
+                                       le->key, status);
+               }
+
+               le = le->next;
+       }
+
+       return (0);
+} /* int plugin_dispatch_notification */
+
+void plugin_log (int level, const char *format, ...)
+{
+       char msg[1024];
+       va_list ap;
+       llentry_t *le;
+
+#if !COLLECT_DEBUG
+       if (level >= LOG_DEBUG)
+               return;
+#endif
+
+       va_start (ap, format);
+       vsnprintf (msg, sizeof (msg), format, ap);
+       msg[sizeof (msg) - 1] = '\0';
+       va_end (ap);
+
+       if (list_log == NULL)
+       {
+               fprintf (stderr, "%s\n", msg);
+               return;
+       }
+
+       le = llist_head (list_log);
+       while (le != NULL)
+       {
+               callback_func_t *cf;
+               plugin_log_cb callback;
+
+               cf = le->value;
+               callback = cf->cf_callback;
+
+               (*callback) (level, msg, &cf->cf_udata);
+
+               le = le->next;
+       }
+} /* void plugin_log */
+
+const data_set_t *plugin_get_ds (const char *name)
+{
+       data_set_t *ds;
+
+       if (c_avl_get (data_sets, name, (void *) &ds) != 0)
+       {
+               DEBUG ("No such dataset registered: %s", name);
+               return (NULL);
+       }
+
+       return (ds);
+} /* data_set_t *plugin_get_ds */
+
+static int plugin_notification_meta_add (notification_t *n,
+    const char *name,
+    enum notification_meta_type_e type,
+    const void *value)
+{
+  notification_meta_t *meta;
+  notification_meta_t *tail;
+
+  if ((n == NULL) || (name == NULL) || (value == NULL))
+  {
+    ERROR ("plugin_notification_meta_add: A pointer is NULL!");
+    return (-1);
+  }
+
+  meta = (notification_meta_t *) malloc (sizeof (notification_meta_t));
+  if (meta == NULL)
+  {
+    ERROR ("plugin_notification_meta_add: malloc failed.");
+    return (-1);
+  }
+  memset (meta, 0, sizeof (notification_meta_t));
+
+  sstrncpy (meta->name, name, sizeof (meta->name));
+  meta->type = type;
+
+  switch (type)
+  {
+    case NM_TYPE_STRING:
+    {
+      meta->nm_value.nm_string = strdup ((const char *) value);
+      if (meta->nm_value.nm_string == NULL)
+      {
+        ERROR ("plugin_notification_meta_add: strdup failed.");
+        sfree (meta);
+        return (-1);
+      }
+      break;
+    }
+    case NM_TYPE_SIGNED_INT:
+    {
+      meta->nm_value.nm_signed_int = *((int64_t *) value);
+      break;
+    }
+    case NM_TYPE_UNSIGNED_INT:
+    {
+      meta->nm_value.nm_unsigned_int = *((uint64_t *) value);
+      break;
+    }
+    case NM_TYPE_DOUBLE:
+    {
+      meta->nm_value.nm_double = *((double *) value);
+      break;
+    }
+    case NM_TYPE_BOOLEAN:
+    {
+      meta->nm_value.nm_boolean = *((_Bool *) value);
+      break;
+    }
+    default:
+    {
+      ERROR ("plugin_notification_meta_add: Unknown type: %i", type);
+      sfree (meta);
+      return (-1);
+    }
+  } /* switch (type) */
+
+  meta->next = NULL;
+  tail = n->meta;
+  while ((tail != NULL) && (tail->next != NULL))
+    tail = tail->next;
+
+  if (tail == NULL)
+    n->meta = meta;
+  else
+    tail->next = meta;
+
+  return (0);
+} /* int plugin_notification_meta_add */
+
+int plugin_notification_meta_add_string (notification_t *n,
+    const char *name,
+    const char *value)
+{
+  return (plugin_notification_meta_add (n, name, NM_TYPE_STRING, value));
+}
+
+int plugin_notification_meta_add_signed_int (notification_t *n,
+    const char *name,
+    int64_t value)
+{
+  return (plugin_notification_meta_add (n, name, NM_TYPE_SIGNED_INT, &value));
+}
+
+int plugin_notification_meta_add_unsigned_int (notification_t *n,
+    const char *name,
+    uint64_t value)
+{
+  return (plugin_notification_meta_add (n, name, NM_TYPE_UNSIGNED_INT, &value));
+}
+
+int plugin_notification_meta_add_double (notification_t *n,
+    const char *name,
+    double value)
+{
+  return (plugin_notification_meta_add (n, name, NM_TYPE_DOUBLE, &value));
+}
+
+int plugin_notification_meta_add_boolean (notification_t *n,
+    const char *name,
+    _Bool value)
+{
+  return (plugin_notification_meta_add (n, name, NM_TYPE_BOOLEAN, &value));
+}
+
+int plugin_notification_meta_copy (notification_t *dst,
+    const notification_t *src)
+{
+  notification_meta_t *meta;
+
+  assert (dst != NULL);
+  assert (src != NULL);
+  assert (dst != src);
+  assert ((src->meta == NULL) || (src->meta != dst->meta));
+
+  for (meta = src->meta; meta != NULL; meta = meta->next)
+  {
+    if (meta->type == NM_TYPE_STRING)
+      plugin_notification_meta_add_string (dst, meta->name,
+          meta->nm_value.nm_string);
+    else if (meta->type == NM_TYPE_SIGNED_INT)
+      plugin_notification_meta_add_signed_int (dst, meta->name,
+          meta->nm_value.nm_signed_int);
+    else if (meta->type == NM_TYPE_UNSIGNED_INT)
+      plugin_notification_meta_add_unsigned_int (dst, meta->name,
+          meta->nm_value.nm_unsigned_int);
+    else if (meta->type == NM_TYPE_DOUBLE)
+      plugin_notification_meta_add_double (dst, meta->name,
+          meta->nm_value.nm_double);
+    else if (meta->type == NM_TYPE_BOOLEAN)
+      plugin_notification_meta_add_boolean (dst, meta->name,
+          meta->nm_value.nm_boolean);
+  }
+
+  return (0);
+} /* int plugin_notification_meta_copy */
+
+int plugin_notification_meta_free (notification_meta_t *n)
+{
+  notification_meta_t *this;
+  notification_meta_t *next;
+
+  if (n == NULL)
+  {
+    ERROR ("plugin_notification_meta_free: n == NULL!");
+    return (-1);
+  }
+
+  this = n;
+  while (this != NULL)
+  {
+    next = this->next;
+
+    if (this->type == NM_TYPE_STRING)
+    {
+      free ((char *)this->nm_value.nm_string);
+      this->nm_value.nm_string = NULL;
+    }
+    sfree (this);
+
+    this = next;
+  }
+
+  return (0);
+} /* int plugin_notification_meta_free */
+
+/* vim: set sw=8 ts=8 noet fdm=marker : */
diff --git a/src/plugin.h b/src/plugin.h
new file mode 100644 (file)
index 0000000..86d4034
--- /dev/null
@@ -0,0 +1,362 @@
+#ifndef PLUGIN_H
+#define PLUGIN_H
+/**
+ * collectd - src/plugin.h
+ * Copyright (C) 2005-2011  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at collectd.org>
+ *   Sebastian Harl <sh at tokkee.org>
+ **/
+
+#include "collectd.h"
+#include "configfile.h"
+#include "meta_data.h"
+#include "utils_time.h"
+
+#define PLUGIN_FLAGS_GLOBAL 0x0001
+
+#define DATA_MAX_NAME_LEN 64
+
+#define DS_TYPE_COUNTER  0
+#define DS_TYPE_GAUGE    1
+#define DS_TYPE_DERIVE   2
+#define DS_TYPE_ABSOLUTE 3
+
+#define DS_TYPE_TO_STRING(t) (t == DS_TYPE_COUNTER)     ? "counter"  : \
+                               (t == DS_TYPE_GAUGE)    ? "gauge"    : \
+                               (t == DS_TYPE_DERIVE)   ? "derive"   : \
+                               (t == DS_TYPE_ABSOLUTE) ? "absolute" : \
+                               "unknown"
+
+
+#ifndef LOG_ERR
+# define LOG_ERR 3
+#endif
+#ifndef LOG_WARNING
+# define LOG_WARNING 4
+#endif
+#ifndef LOG_NOTICE
+# define LOG_NOTICE 5
+#endif
+#ifndef LOG_INFO
+# define LOG_INFO 6
+#endif
+#ifndef LOG_DEBUG
+# define LOG_DEBUG 7
+#endif
+
+#define NOTIF_MAX_MSG_LEN 256
+
+#define NOTIF_FAILURE 1
+#define NOTIF_WARNING 2
+#define NOTIF_OKAY    4
+
+/*
+ * Public data types
+ */
+typedef unsigned long long counter_t;
+typedef double gauge_t;
+typedef int64_t derive_t;
+typedef uint64_t absolute_t;
+
+union value_u
+{
+       counter_t  counter;
+       gauge_t    gauge;
+       derive_t   derive;
+       absolute_t absolute;
+};
+typedef union value_u value_t;
+
+struct value_list_s
+{
+       value_t *values;
+       int      values_len;
+       cdtime_t time;
+       cdtime_t interval;
+       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];
+       meta_data_t *meta;
+};
+typedef struct value_list_s value_list_t;
+
+#define VALUE_LIST_INIT { NULL, 0, 0, interval_g, "localhost", "", "", "", "", NULL }
+#define VALUE_LIST_STATIC { NULL, 0, 0, 0, "localhost", "", "", "", "", NULL }
+
+struct data_source_s
+{
+       char   name[DATA_MAX_NAME_LEN];
+       int    type;
+       double min;
+       double max;
+};
+typedef struct data_source_s data_source_t;
+
+struct data_set_s
+{
+       char           type[DATA_MAX_NAME_LEN];
+       int            ds_num;
+       data_source_t *ds;
+};
+typedef struct data_set_s data_set_t;
+
+enum notification_meta_type_e
+{
+       NM_TYPE_STRING,
+       NM_TYPE_SIGNED_INT,
+       NM_TYPE_UNSIGNED_INT,
+       NM_TYPE_DOUBLE,
+       NM_TYPE_BOOLEAN
+};
+
+typedef struct notification_meta_s
+{
+       char name[DATA_MAX_NAME_LEN];
+       enum notification_meta_type_e type;
+       union
+       {
+               const char *nm_string;
+               int64_t nm_signed_int;
+               uint64_t nm_unsigned_int;
+               double nm_double;
+               _Bool nm_boolean;
+       } nm_value;
+       struct notification_meta_s *next;
+} notification_meta_t;
+
+typedef struct notification_s
+{
+       int    severity;
+       cdtime_t time;
+       char   message[NOTIF_MAX_MSG_LEN];
+       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];
+       notification_meta_t *meta;
+} notification_t;
+
+struct user_data_s
+{
+       void *data;
+       void (*free_func) (void *);
+};
+typedef struct user_data_s user_data_t;
+
+/*
+ * Callback types
+ */
+typedef int (*plugin_init_cb) (void);
+typedef int (*plugin_read_cb) (user_data_t *);
+typedef int (*plugin_write_cb) (const data_set_t *, const value_list_t *,
+               user_data_t *);
+typedef int (*plugin_flush_cb) (cdtime_t timeout, const char *identifier,
+               user_data_t *);
+/* "missing" callback. Returns less than zero on failure, zero if other
+ * callbacks should be called, greater than zero if no more callbacks should be
+ * called. */
+typedef int (*plugin_missing_cb) (const value_list_t *, user_data_t *);
+typedef void (*plugin_log_cb) (int severity, const char *message,
+               user_data_t *);
+typedef int (*plugin_shutdown_cb) (void);
+typedef int (*plugin_notification_cb) (const notification_t *,
+               user_data_t *);
+
+/*
+ * NAME
+ *  plugin_set_dir
+ *
+ * DESCRIPTION
+ *  Sets the current `plugindir'
+ *
+ * ARGUMENTS
+ *  `dir'       Path to the plugin directory
+ *
+ * NOTES
+ *  If `dir' is NULL the compiled in default `PLUGINDIR' is used.
+ */
+void plugin_set_dir (const char *dir);
+
+/*
+ * NAME
+ *  plugin_load
+ *
+ * DESCRIPTION
+ *  Searches the current `plugindir' (see `plugin_set_dir') for the plugin
+ *  named $type and loads it. Afterwards the plugin's `module_register'
+ *  function is called, which then calls `plugin_register' to register callback
+ *  functions.
+ *
+ * ARGUMENTS
+ *  `name'      Name of the plugin to load.
+ *  `flags'     Hints on how to handle this plugin.
+ *
+ * RETURN VALUE
+ *  Returns zero upon success, a value greater than zero if no plugin was found
+ *  and a value below zero if an error occurs.
+ *
+ * NOTES
+ *  No attempt is made to re-load an already loaded module.
+ */
+int plugin_load (const char *name, uint32_t flags);
+
+void plugin_init_all (void);
+void plugin_read_all (void);
+int plugin_read_all_once (void);
+void plugin_shutdown_all (void);
+
+/*
+ * NAME
+ *  plugin_write
+ *
+ * DESCRIPTION
+ *  Calls the write function of the given plugin with the provided data set and
+ *  value list. It differs from `plugin_dispatch_value' in that it does not
+ *  update the cache, does not do threshold checking, call the chain subsystem
+ *  and so on. It looks up the requested plugin and invokes the function, end
+ *  of story.
+ *
+ * ARGUMENTS
+ *  plugin     Name of the plugin. If NULL, the value is sent to all registered
+ *             write functions.
+ *  ds         Pointer to the data_set_t structure. If NULL, the data set is
+ *             looked up according to the `type' member in the `vl' argument.
+ *  vl         The actual value to be processed. Must not be NULL.
+ *
+ * RETURN VALUE
+ *  Returns zero upon success or non-zero if an error occurred. If `plugin' is
+ *  NULL and more than one plugin is called, an error is only returned if *all*
+ *  plugins fail.
+ *
+ * NOTES
+ *  This is the function used by the `write' built-in target. May be used by
+ *  other target plugins.
+ */
+int plugin_write (const char *plugin,
+    const data_set_t *ds, const value_list_t *vl);
+
+int plugin_flush (const char *plugin, cdtime_t timeout, const char *identifier);
+
+/*
+ * The `plugin_register_*' functions are used to make `config', `init',
+ * `read', `write' and `shutdown' functions known to the plugin
+ * infrastructure. Also, the data-formats are made public like this.
+ */
+int plugin_register_config (const char *name,
+               int (*callback) (const char *key, const char *val),
+               const char **keys, int keys_num);
+int plugin_register_complex_config (const char *type,
+               int (*callback) (oconfig_item_t *));
+int plugin_register_init (const char *name,
+               plugin_init_cb callback);
+int plugin_register_read (const char *name,
+               int (*callback) (void));
+/* "user_data" will be freed automatically, unless
+ * "plugin_register_complex_read" returns an error (non-zero). */
+int plugin_register_complex_read (const char *group, const char *name,
+               plugin_read_cb callback,
+               const struct timespec *interval,
+               user_data_t *user_data);
+int plugin_register_write (const char *name,
+               plugin_write_cb callback, user_data_t *user_data);
+int plugin_register_flush (const char *name,
+               plugin_flush_cb callback, user_data_t *user_data);
+int plugin_register_missing (const char *name,
+               plugin_missing_cb callback, user_data_t *user_data);
+int plugin_register_shutdown (const char *name,
+               plugin_shutdown_cb callback);
+int plugin_register_data_set (const data_set_t *ds);
+int plugin_register_log (const char *name,
+               plugin_log_cb callback, user_data_t *user_data);
+int plugin_register_notification (const char *name,
+               plugin_notification_cb callback, user_data_t *user_data);
+
+int plugin_unregister_config (const char *name);
+int plugin_unregister_complex_config (const char *name);
+int plugin_unregister_init (const char *name);
+int plugin_unregister_read (const char *name);
+int plugin_unregister_read_group (const char *group);
+int plugin_unregister_write (const char *name);
+int plugin_unregister_flush (const char *name);
+int plugin_unregister_missing (const char *name);
+int plugin_unregister_shutdown (const char *name);
+int plugin_unregister_data_set (const char *name);
+int plugin_unregister_log (const char *name);
+int plugin_unregister_notification (const char *name);
+
+
+/*
+ * NAME
+ *  plugin_dispatch_values
+ *
+ * DESCRIPTION
+ *  This function is called by reading processes with the values they've
+ *  aquired. The function fetches the data-set definition (that has been
+ *  registered using `plugin_register_data_set') and calls _all_ registered
+ *  write-functions.
+ *
+ * ARGUMENTS
+ *  `vl'        Value list of the values that have been read by a `read'
+ *              function.
+ */
+int plugin_dispatch_values (value_list_t *vl);
+int plugin_dispatch_values_secure (const value_list_t *vl);
+int plugin_dispatch_missing (const value_list_t *vl);
+
+int plugin_dispatch_notification (const notification_t *notif);
+
+void plugin_log (int level, const char *format, ...)
+       __attribute__ ((format(printf,2,3)));
+
+#define ERROR(...)   plugin_log (LOG_ERR,     __VA_ARGS__)
+#define WARNING(...) plugin_log (LOG_WARNING, __VA_ARGS__)
+#define NOTICE(...)  plugin_log (LOG_NOTICE,  __VA_ARGS__)
+#define INFO(...)    plugin_log (LOG_INFO,    __VA_ARGS__)
+#if COLLECT_DEBUG
+# define DEBUG(...)  plugin_log (LOG_DEBUG,   __VA_ARGS__)
+#else /* COLLECT_DEBUG */
+# define DEBUG(...)  /* noop */
+#endif /* ! COLLECT_DEBUG */
+
+const data_set_t *plugin_get_ds (const char *name);
+
+int plugin_notification_meta_add_string (notification_t *n,
+    const char *name,
+    const char *value);
+int plugin_notification_meta_add_signed_int (notification_t *n,
+    const char *name,
+    int64_t value);
+int plugin_notification_meta_add_unsigned_int (notification_t *n,
+    const char *name,
+    uint64_t value);
+int plugin_notification_meta_add_double (notification_t *n,
+    const char *name,
+    double value);
+int plugin_notification_meta_add_boolean (notification_t *n,
+    const char *name,
+    _Bool value);
+
+int plugin_notification_meta_copy (notification_t *dst,
+    const notification_t *src);
+
+int plugin_notification_meta_free (notification_meta_t *n);
+
+#endif /* PLUGIN_H */
diff --git a/src/postgresql.c b/src/postgresql.c
new file mode 100644 (file)
index 0000000..a8812e2
--- /dev/null
@@ -0,0 +1,733 @@
+/**
+ * collectd - src/postgresql.c
+ * Copyright (C) 2008, 2009  Sebastian Harl
+ * Copyright (C) 2009        Florian Forster
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Authors:
+ *   Sebastian Harl <sh at tokkee.org>
+ *   Florian Forster <octo at verplant.org>
+ **/
+
+/*
+ * This module collects PostgreSQL database statistics.
+ */
+
+#include "collectd.h"
+#include "common.h"
+
+#include "configfile.h"
+#include "plugin.h"
+
+#include "utils_db_query.h"
+#include "utils_complain.h"
+
+#include <pg_config_manual.h>
+#include <libpq-fe.h>
+
+#define log_err(...) ERROR ("postgresql: " __VA_ARGS__)
+#define log_warn(...) WARNING ("postgresql: " __VA_ARGS__)
+#define log_info(...) INFO ("postgresql: " __VA_ARGS__)
+
+#ifndef C_PSQL_DEFAULT_CONF
+# define C_PSQL_DEFAULT_CONF PKGDATADIR "/postgresql_default.conf"
+#endif
+
+/* Appends the (parameter, value) pair to the string
+ * pointed to by 'buf' suitable to be used as argument
+ * for PQconnectdb(). If value equals NULL, the pair
+ * is ignored. */
+#define C_PSQL_PAR_APPEND(buf, buf_len, parameter, value) \
+       if ((0 < (buf_len)) && (NULL != (value)) && ('\0' != *(value))) { \
+               int s = ssnprintf (buf, buf_len, " %s = '%s'", parameter, value); \
+               if (0 < s) { \
+                       buf     += s; \
+                       buf_len -= s; \
+               } \
+       }
+
+/* Returns the tuple (major, minor, patchlevel)
+ * for the given version number. */
+#define C_PSQL_SERVER_VERSION3(server_version) \
+       (server_version) / 10000, \
+       (server_version) / 100 - (int)((server_version) / 10000) * 100, \
+       (server_version) - (int)((server_version) / 100) * 100
+
+/* Returns true if the given host specifies a
+ * UNIX domain socket. */
+#define C_PSQL_IS_UNIX_DOMAIN_SOCKET(host) \
+       ((NULL == (host)) || ('\0' == *(host)) || ('/' == *(host)))
+
+/* Returns the tuple (host, delimiter, port) for a
+ * given (host, port) pair. Depending on the value of
+ * 'host' a UNIX domain socket or a TCP socket is
+ * assumed. */
+#define C_PSQL_SOCKET3(host, port) \
+       ((NULL == (host)) || ('\0' == *(host))) ? DEFAULT_PGSOCKET_DIR : host, \
+       C_PSQL_IS_UNIX_DOMAIN_SOCKET (host) ? "/.s.PGSQL." : ":", \
+       port
+
+typedef enum {
+       C_PSQL_PARAM_HOST = 1,
+       C_PSQL_PARAM_DB,
+       C_PSQL_PARAM_USER,
+       C_PSQL_PARAM_INTERVAL,
+} c_psql_param_t;
+
+/* Parameter configuration. Stored as `user data' in the query objects. */
+typedef struct {
+       c_psql_param_t *params;
+       int             params_num;
+} c_psql_user_data_t;
+
+typedef struct {
+       PGconn      *conn;
+       c_complain_t conn_complaint;
+
+       int proto_version;
+       int server_version;
+
+       int max_params_num;
+
+       /* user configuration */
+       udb_query_preparation_area_t **q_prep_areas;
+       udb_query_t    **queries;
+       size_t           queries_num;
+
+       cdtime_t interval;
+
+       char *host;
+       char *port;
+       char *database;
+       char *user;
+       char *password;
+
+       char *sslmode;
+
+       char *krbsrvname;
+
+       char *service;
+} c_psql_database_t;
+
+static char *def_queries[] = {
+       "backends",
+       "transactions",
+       "queries",
+       "query_plans",
+       "table_states",
+       "disk_io",
+       "disk_usage"
+};
+static int def_queries_num = STATIC_ARRAY_SIZE (def_queries);
+
+static udb_query_t      **queries       = NULL;
+static size_t             queries_num   = 0;
+
+static c_psql_database_t *c_psql_database_new (const char *name)
+{
+       c_psql_database_t *db;
+
+       db = (c_psql_database_t *)malloc (sizeof (*db));
+       if (NULL == db) {
+               log_err ("Out of memory.");
+               return NULL;
+       }
+
+       db->conn = NULL;
+
+       C_COMPLAIN_INIT (&db->conn_complaint);
+
+       db->proto_version = 0;
+       db->server_version = 0;
+
+       db->max_params_num = 0;
+
+       db->q_prep_areas   = NULL;
+       db->queries        = NULL;
+       db->queries_num    = 0;
+
+       db->interval   = 0;
+
+       db->database   = sstrdup (name);
+       db->host       = NULL;
+       db->port       = NULL;
+       db->user       = NULL;
+       db->password   = NULL;
+
+       db->sslmode    = NULL;
+
+       db->krbsrvname = NULL;
+
+       db->service    = NULL;
+       return db;
+} /* c_psql_database_new */
+
+static void c_psql_database_delete (void *data)
+{
+       size_t i;
+
+       c_psql_database_t *db = data;
+
+       PQfinish (db->conn);
+       db->conn = NULL;
+
+       if (db->q_prep_areas)
+               for (i = 0; i < db->queries_num; ++i)
+                       udb_query_delete_preparation_area (db->q_prep_areas[i]);
+       free (db->q_prep_areas);
+
+       sfree (db->queries);
+       db->queries_num = 0;
+
+       sfree (db->database);
+       sfree (db->host);
+       sfree (db->port);
+       sfree (db->user);
+       sfree (db->password);
+
+       sfree (db->sslmode);
+
+       sfree (db->krbsrvname);
+
+       sfree (db->service);
+       return;
+} /* c_psql_database_delete */
+
+static int c_psql_connect (c_psql_database_t *db)
+{
+       char  conninfo[4096];
+       char *buf     = conninfo;
+       int   buf_len = sizeof (conninfo);
+       int   status;
+
+       if (! db)
+               return -1;
+
+       status = ssnprintf (buf, buf_len, "dbname = '%s'", db->database);
+       if (0 < status) {
+               buf     += status;
+               buf_len -= status;
+       }
+
+       C_PSQL_PAR_APPEND (buf, buf_len, "host",       db->host);
+       C_PSQL_PAR_APPEND (buf, buf_len, "port",       db->port);
+       C_PSQL_PAR_APPEND (buf, buf_len, "user",       db->user);
+       C_PSQL_PAR_APPEND (buf, buf_len, "password",   db->password);
+       C_PSQL_PAR_APPEND (buf, buf_len, "sslmode",    db->sslmode);
+       C_PSQL_PAR_APPEND (buf, buf_len, "krbsrvname", db->krbsrvname);
+       C_PSQL_PAR_APPEND (buf, buf_len, "service",    db->service);
+
+       db->conn = PQconnectdb (conninfo);
+       db->proto_version = PQprotocolVersion (db->conn);
+       return 0;
+} /* c_psql_connect */
+
+static int c_psql_check_connection (c_psql_database_t *db)
+{
+       _Bool init = 0;
+
+       if (! db->conn) {
+               init = 1;
+
+               /* trigger c_release() */
+               if (0 == db->conn_complaint.interval)
+                       db->conn_complaint.interval = 1;
+
+               c_psql_connect (db);
+       }
+
+       /* "ping" */
+       PQclear (PQexec (db->conn, "SELECT 42;"));
+
+       if (CONNECTION_OK != PQstatus (db->conn)) {
+               PQreset (db->conn);
+
+               /* trigger c_release() */
+               if (0 == db->conn_complaint.interval)
+                       db->conn_complaint.interval = 1;
+
+               if (CONNECTION_OK != PQstatus (db->conn)) {
+                       c_complain (LOG_ERR, &db->conn_complaint,
+                                       "Failed to connect to database %s: %s",
+                                       db->database, PQerrorMessage (db->conn));
+                       return -1;
+               }
+
+               db->proto_version = PQprotocolVersion (db->conn);
+       }
+
+       db->server_version = PQserverVersion (db->conn);
+
+       if (c_would_release (&db->conn_complaint)) {
+               char *server_host;
+               int   server_version;
+
+               server_host    = PQhost (db->conn);
+               server_version = PQserverVersion (db->conn);
+
+               c_do_release (LOG_INFO, &db->conn_complaint,
+                               "Successfully %sconnected to database %s (user %s) "
+                               "at server %s%s%s (server version: %d.%d.%d, "
+                               "protocol version: %d, pid: %d)", init ? "" : "re",
+                               PQdb (db->conn), PQuser (db->conn),
+                               C_PSQL_SOCKET3 (server_host, PQport (db->conn)),
+                               C_PSQL_SERVER_VERSION3 (server_version),
+                               db->proto_version, PQbackendPID (db->conn));
+
+               if (3 > db->proto_version)
+                       log_warn ("Protocol version %d does not support parameters.",
+                                       db->proto_version);
+       }
+       return 0;
+} /* c_psql_check_connection */
+
+static PGresult *c_psql_exec_query_noparams (c_psql_database_t *db,
+               udb_query_t *q)
+{
+       return PQexec (db->conn, udb_query_get_statement (q));
+} /* c_psql_exec_query_noparams */
+
+static PGresult *c_psql_exec_query_params (c_psql_database_t *db,
+               udb_query_t *q, c_psql_user_data_t *data)
+{
+       char *params[db->max_params_num];
+       char  interval[64];
+       int   i;
+
+       if ((data == NULL) || (data->params_num == 0))
+               return (c_psql_exec_query_noparams (db, q));
+
+       assert (db->max_params_num >= data->params_num);
+
+       for (i = 0; i < data->params_num; ++i) {
+               switch (data->params[i]) {
+                       case C_PSQL_PARAM_HOST:
+                               params[i] = C_PSQL_IS_UNIX_DOMAIN_SOCKET (db->host)
+                                       ? "localhost" : db->host;
+                               break;
+                       case C_PSQL_PARAM_DB:
+                               params[i] = db->database;
+                               break;
+                       case C_PSQL_PARAM_USER:
+                               params[i] = db->user;
+                               break;
+                       case C_PSQL_PARAM_INTERVAL:
+                               ssnprintf (interval, sizeof (interval), "%.3f",
+                                               (db->interval > 0)
+                                               ? CDTIME_T_TO_DOUBLE (db->interval) : interval_g);
+                               params[i] = interval;
+                               break;
+                       default:
+                               assert (0);
+               }
+       }
+
+       return PQexecParams (db->conn, udb_query_get_statement (q),
+                       data->params_num, NULL,
+                       (const char *const *) params,
+                       NULL, NULL, /* return text data */ 0);
+} /* c_psql_exec_query_params */
+
+static int c_psql_exec_query (c_psql_database_t *db, udb_query_t *q,
+               udb_query_preparation_area_t *prep_area)
+{
+       PGresult *res;
+
+       c_psql_user_data_t *data;
+
+       const char *host;
+
+       char **column_names;
+       char **column_values;
+       int    column_num;
+
+       int rows_num;
+       int status;
+       int row, col;
+
+       /* The user data may hold parameter information, but may be NULL. */
+       data = udb_query_get_user_data (q);
+
+       /* Versions up to `3' don't know how to handle parameters. */
+       if (3 <= db->proto_version)
+               res = c_psql_exec_query_params (db, q, data);
+       else if ((NULL == data) || (0 == data->params_num))
+               res = c_psql_exec_query_noparams (db, q);
+       else {
+               log_err ("Connection to database \"%s\" does not support parameters "
+                               "(protocol version %d) - cannot execute query \"%s\".",
+                               db->database, db->proto_version,
+                               udb_query_get_name (q));
+               return -1;
+       }
+
+       column_names = NULL;
+       column_values = NULL;
+
+#define BAIL_OUT(status) \
+       sfree (column_names); \
+       sfree (column_values); \
+       PQclear (res); \
+       return status
+
+       if (PGRES_TUPLES_OK != PQresultStatus (res)) {
+               log_err ("Failed to execute SQL query: %s",
+                               PQerrorMessage (db->conn));
+               log_info ("SQL query was: %s",
+                               udb_query_get_statement (q));
+               BAIL_OUT (-1);
+       }
+
+       rows_num = PQntuples (res);
+       if (1 > rows_num) {
+               BAIL_OUT (0);
+       }
+
+       column_num = PQnfields (res);
+       column_names = (char **) calloc (column_num, sizeof (char *));
+       if (NULL == column_names) {
+               log_err ("calloc failed.");
+               BAIL_OUT (-1);
+       }
+
+       column_values = (char **) calloc (column_num, sizeof (char *));
+       if (NULL == column_values) {
+               log_err ("calloc failed.");
+               BAIL_OUT (-1);
+       }
+       
+       for (col = 0; col < column_num; ++col) {
+               /* Pointers returned by `PQfname' are freed by `PQclear' via
+                * `BAIL_OUT'. */
+               column_names[col] = PQfname (res, col);
+               if (NULL == column_names[col]) {
+                       log_err ("Failed to resolve name of column %i.", col);
+                       BAIL_OUT (-1);
+               }
+       }
+
+       if (C_PSQL_IS_UNIX_DOMAIN_SOCKET (db->host)
+                       || (0 == strcmp (db->host, "localhost")))
+               host = hostname_g;
+       else
+               host = db->host;
+
+       status = udb_query_prepare_result (q, prep_area, host, "postgresql",
+                       db->database, column_names, (size_t) column_num, db->interval);
+       if (0 != status) {
+               log_err ("udb_query_prepare_result failed with status %i.",
+                               status);
+               BAIL_OUT (-1);
+       }
+
+       for (row = 0; row < rows_num; ++row) {
+               for (col = 0; col < column_num; ++col) {
+                       /* Pointers returned by `PQgetvalue' are freed by `PQclear' via
+                        * `BAIL_OUT'. */
+                       column_values[col] = PQgetvalue (res, row, col);
+                       if (NULL == column_values[col]) {
+                               log_err ("Failed to get value at (row = %i, col = %i).",
+                                               row, col);
+                               break;
+                       }
+               }
+
+               /* check for an error */
+               if (col < column_num)
+                       continue;
+
+               status = udb_query_handle_result (q, prep_area, column_values);
+               if (status != 0) {
+                       log_err ("udb_query_handle_result failed with status %i.",
+                                       status);
+               }
+       } /* for (row = 0; row < rows_num; ++row) */
+
+       udb_query_finish_result (q, prep_area);
+
+       BAIL_OUT (0);
+#undef BAIL_OUT
+} /* c_psql_exec_query */
+
+static int c_psql_read (user_data_t *ud)
+{
+       c_psql_database_t *db;
+
+       int success = 0;
+       int i;
+
+       if ((ud == NULL) || (ud->data == NULL)) {
+               log_err ("c_psql_read: Invalid user data.");
+               return -1;
+       }
+
+       db = ud->data;
+
+       assert (NULL != db->database);
+
+       if (0 != c_psql_check_connection (db))
+               return -1;
+
+       for (i = 0; i < db->queries_num; ++i)
+       {
+               udb_query_preparation_area_t *prep_area;
+               udb_query_t *q;
+
+               prep_area = db->q_prep_areas[i];
+               q = db->queries[i];
+
+               if ((0 != db->server_version)
+                               && (udb_query_check_version (q, db->server_version) <= 0))
+                       continue;
+
+               if (0 == c_psql_exec_query (db, q, prep_area))
+                       success = 1;
+       }
+
+       if (! success)
+               return -1;
+       return 0;
+} /* c_psql_read */
+
+static int c_psql_shutdown (void)
+{
+       plugin_unregister_read_group ("postgresql");
+
+       udb_query_free (queries, queries_num);
+       queries = NULL;
+       queries_num = 0;
+
+       return 0;
+} /* c_psql_shutdown */
+
+static int config_set_s (char *name, char **var, const oconfig_item_t *ci)
+{
+       if ((0 != ci->children_num) || (1 != ci->values_num)
+                       || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
+               log_err ("%s expects a single string argument.", name);
+               return 1;
+       }
+
+       sfree (*var);
+       *var = sstrdup (ci->values[0].value.string);
+       return 0;
+} /* config_set_s */
+
+static int config_query_param_add (udb_query_t *q, oconfig_item_t *ci)
+{
+       c_psql_user_data_t *data;
+       const char *param_str;
+
+       c_psql_param_t *tmp;
+
+       data = udb_query_get_user_data (q);
+       if (NULL == data) {
+               data = (c_psql_user_data_t *) smalloc (sizeof (*data));
+               if (NULL == data) {
+                       log_err ("Out of memory.");
+                       return -1;
+               }
+               memset (data, 0, sizeof (*data));
+               data->params = NULL;
+       }
+
+       tmp = (c_psql_param_t *) realloc (data->params,
+                       (data->params_num + 1) * sizeof (c_psql_param_t));
+       if (NULL == tmp) {
+               log_err ("Out of memory.");
+               return -1;
+       }
+       data->params = tmp;
+
+       param_str = ci->values[0].value.string;
+       if (0 == strcasecmp (param_str, "hostname"))
+               data->params[data->params_num] = C_PSQL_PARAM_HOST;
+       else if (0 == strcasecmp (param_str, "database"))
+               data->params[data->params_num] = C_PSQL_PARAM_DB;
+       else if (0 == strcasecmp (param_str, "username"))
+               data->params[data->params_num] = C_PSQL_PARAM_USER;
+       else if (0 == strcasecmp (param_str, "interval"))
+               data->params[data->params_num] = C_PSQL_PARAM_INTERVAL;
+       else {
+               log_err ("Invalid parameter \"%s\".", param_str);
+               return 1;
+       }
+
+       data->params_num++;
+       udb_query_set_user_data (q, data);
+
+       return (0);
+} /* config_query_param_add */
+
+static int config_query_callback (udb_query_t *q, oconfig_item_t *ci)
+{
+       if (0 == strcasecmp ("Param", ci->key))
+               return config_query_param_add (q, ci);
+
+       log_err ("Option not allowed within a Query block: `%s'", ci->key);
+
+       return (-1);
+} /* config_query_callback */
+
+static int c_psql_config_database (oconfig_item_t *ci)
+{
+       c_psql_database_t *db;
+
+       char cb_name[DATA_MAX_NAME_LEN];
+       struct timespec cb_interval = { 0, 0 };
+       user_data_t ud;
+
+       int i;
+
+       if ((1 != ci->values_num)
+                       || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
+               log_err ("<Database> expects a single string argument.");
+               return 1;
+       }
+
+       memset (&ud, 0, sizeof (ud));
+
+       db = c_psql_database_new (ci->values[0].value.string);
+       if (db == NULL)
+               return -1;
+
+       for (i = 0; i < ci->children_num; ++i) {
+               oconfig_item_t *c = ci->children + i;
+
+               if (0 == strcasecmp (c->key, "Host"))
+                       config_set_s ("Host", &db->host, c);
+               else if (0 == strcasecmp (c->key, "Port"))
+                       config_set_s ("Port", &db->port, c);
+               else if (0 == strcasecmp (c->key, "User"))
+                       config_set_s ("User", &db->user, c);
+               else if (0 == strcasecmp (c->key, "Password"))
+                       config_set_s ("Password", &db->password, c);
+               else if (0 == strcasecmp (c->key, "SSLMode"))
+                       config_set_s ("SSLMode", &db->sslmode, c);
+               else if (0 == strcasecmp (c->key, "KRBSrvName"))
+                       config_set_s ("KRBSrvName", &db->krbsrvname, c);
+               else if (0 == strcasecmp (c->key, "Service"))
+                       config_set_s ("Service", &db->service, c);
+               else if (0 == strcasecmp (c->key, "Query"))
+                       udb_query_pick_from_list (c, queries, queries_num,
+                                       &db->queries, &db->queries_num);
+               else if (0 == strcasecmp (c->key, "Interval"))
+                       cf_util_get_cdtime (c, &db->interval);
+               else
+                       log_warn ("Ignoring unknown config key \"%s\".", c->key);
+       }
+
+       /* If no `Query' options were given, add the default queries.. */
+       if (db->queries_num == 0) {
+               for (i = 0; i < def_queries_num; i++)
+                       udb_query_pick_from_list_by_name (def_queries[i],
+                                       queries, queries_num,
+                                       &db->queries, &db->queries_num);
+       }
+
+       if (db->queries_num > 0) {
+               db->q_prep_areas = (udb_query_preparation_area_t **) calloc (
+                               db->queries_num, sizeof (*db->q_prep_areas));
+
+               if (db->q_prep_areas == NULL) {
+                       log_err ("Out of memory.");
+                       c_psql_database_delete (db);
+                       return -1;
+               }
+       }
+
+       for (i = 0; (size_t)i < db->queries_num; ++i) {
+               c_psql_user_data_t *data;
+               data = udb_query_get_user_data (db->queries[i]);
+               if ((data != NULL) && (data->params_num > db->max_params_num))
+                       db->max_params_num = data->params_num;
+
+               db->q_prep_areas[i]
+                       = udb_query_allocate_preparation_area (db->queries[i]);
+
+               if (db->q_prep_areas[i] == NULL) {
+                       log_err ("Out of memory.");
+                       c_psql_database_delete (db);
+                       return -1;
+               }
+       }
+
+       ud.data = db;
+       ud.free_func = c_psql_database_delete;
+
+       ssnprintf (cb_name, sizeof (cb_name), "postgresql-%s", db->database);
+
+       CDTIME_T_TO_TIMESPEC (db->interval, &cb_interval);
+
+       plugin_register_complex_read ("postgresql", cb_name, c_psql_read,
+                       /* interval = */ (db->interval > 0) ? &cb_interval : NULL,
+                       &ud);
+       return 0;
+} /* c_psql_config_database */
+
+static int c_psql_config (oconfig_item_t *ci)
+{
+       static int have_def_config = 0;
+
+       int i;
+
+       if (0 == have_def_config) {
+               oconfig_item_t *c;
+
+               have_def_config = 1;
+
+               c = oconfig_parse_file (C_PSQL_DEFAULT_CONF);
+               if (NULL == c)
+                       log_err ("Failed to read default config ("C_PSQL_DEFAULT_CONF").");
+               else
+                       c_psql_config (c);
+
+               if (NULL == queries)
+                       log_err ("Default config ("C_PSQL_DEFAULT_CONF") did not define "
+                                       "any queries - please check your installation.");
+       }
+
+       for (i = 0; i < ci->children_num; ++i) {
+               oconfig_item_t *c = ci->children + i;
+
+               if (0 == strcasecmp (c->key, "Query"))
+                       udb_query_create (&queries, &queries_num, c,
+                                       /* callback = */ config_query_callback);
+               else if (0 == strcasecmp (c->key, "Database"))
+                       c_psql_config_database (c);
+               else
+                       log_warn ("Ignoring unknown config key \"%s\".", c->key);
+       }
+       return 0;
+} /* c_psql_config */
+
+void module_register (void)
+{
+       plugin_register_complex_config ("postgresql", c_psql_config);
+       plugin_register_shutdown ("postgresql", c_psql_shutdown);
+} /* module_register */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
diff --git a/src/postgresql_default.conf b/src/postgresql_default.conf
new file mode 100644 (file)
index 0000000..83a32c7
--- /dev/null
@@ -0,0 +1,208 @@
+# Pre-defined queries of collectd's postgresql plugin.
+#
+# Do not edit this file. If you want to change any of the query definitions,
+# overwrite them in collectd.conf instead.
+#
+# This file is distributed under the same terms as collectd itself.
+
+<Query backends>
+       Statement "SELECT count(*) AS count \
+               FROM pg_stat_activity \
+               WHERE datname = $1;"
+
+       Param database
+
+       <Result>
+               Type "pg_numbackends"
+               ValuesFrom "count"
+       </Result>
+</Query>
+
+<Query transactions>
+       Statement "SELECT xact_commit, xact_rollback \
+               FROM pg_stat_database \
+               WHERE datname = $1;"
+
+       Param database
+
+       <Result>
+               Type "pg_xact"
+               InstancePrefix "commit"
+               ValuesFrom "xact_commit"
+       </Result>
+       <Result>
+               Type "pg_xact"
+               InstancePrefix "rollback"
+               ValuesFrom "xact_rollback"
+       </Result>
+</Query>
+
+<Query queries>
+       Statement "SELECT sum(n_tup_ins) AS ins, \
+                       sum(n_tup_upd) AS upd, \
+                       sum(n_tup_del) AS del \
+               FROM pg_stat_user_tables;"
+
+       <Result>
+               Type "pg_n_tup_c"
+               InstancePrefix "ins"
+               ValuesFrom "ins"
+       </Result>
+       <Result>
+               Type "pg_n_tup_c"
+               InstancePrefix "upd"
+               ValuesFrom "upd"
+       </Result>
+       <Result>
+               Type "pg_n_tup_c"
+               InstancePrefix "del"
+               ValuesFrom "del"
+       </Result>
+
+       MaxVersion 80299
+</Query>
+
+<Query queries>
+       Statement "SELECT sum(n_tup_ins) AS ins, \
+                       sum(n_tup_upd) AS upd, \
+                       sum(n_tup_del) AS del, \
+                       sum(n_tup_hot_upd) AS hot_upd \
+               FROM pg_stat_user_tables;"
+
+       <Result>
+               Type "pg_n_tup_c"
+               InstancePrefix "ins"
+               ValuesFrom "ins"
+       </Result>
+       <Result>
+               Type "pg_n_tup_c"
+               InstancePrefix "upd"
+               ValuesFrom "upd"
+       </Result>
+       <Result>
+               Type "pg_n_tup_c"
+               InstancePrefix "del"
+               ValuesFrom "del"
+       </Result>
+       <Result>
+               Type "pg_n_tup_c"
+               InstancePrefix "hot_upd"
+               ValuesFrom "hot_upd"
+       </Result>
+
+       MinVersion 80300
+</Query>
+
+<Query query_plans>
+       Statement "SELECT sum(seq_scan) AS seq, \
+                       sum(seq_tup_read) AS seq_tup_read, \
+                       sum(idx_scan) AS idx, \
+                       sum(idx_tup_fetch) AS idx_tup_fetch \
+               FROM pg_stat_user_tables;"
+
+       <Result>
+               Type "pg_scan"
+               InstancePrefix "seq"
+               ValuesFrom "seq"
+       </Result>
+       <Result>
+               Type "pg_scan"
+               InstancePrefix "seq_tup_read"
+               ValuesFrom "seq_tup_read"
+       </Result>
+       <Result>
+               Type "pg_scan"
+               InstancePrefix "idx"
+               ValuesFrom "idx"
+       </Result>
+       <Result>
+               Type "pg_scan"
+               InstancePrefix "idx_tup_fetch"
+               ValuesFrom "idx_tup_fetch"
+       </Result>
+</Query>
+
+<Query table_states>
+       Statement "SELECT sum(n_live_tup) AS live, sum(n_dead_tup) AS dead \
+               FROM pg_stat_user_tables;"
+
+       <Result>
+               Type "pg_n_tup_g"
+               InstancePrefix "live"
+               ValuesFrom "live"
+       </Result>
+       <Result>
+               Type "pg_n_tup_g"
+               InstancePrefix "dead"
+               ValuesFrom "dead"
+       </Result>
+
+       MinVersion 80300
+</Query>
+
+<Query disk_io>
+       Statement "SELECT coalesce(sum(heap_blks_read), 0) AS heap_read, \
+                       coalesce(sum(heap_blks_hit), 0) AS heap_hit, \
+                       coalesce(sum(idx_blks_read), 0) AS idx_read, \
+                       coalesce(sum(idx_blks_hit), 0) AS idx_hit, \
+                       coalesce(sum(toast_blks_read), 0) AS toast_read, \
+                       coalesce(sum(toast_blks_hit), 0) AS toast_hit, \
+                       coalesce(sum(tidx_blks_read), 0) AS tidx_read, \
+                       coalesce(sum(tidx_blks_hit), 0) AS tidx_hit \
+               FROM pg_statio_user_tables;"
+
+       <Result>
+               Type "pg_blks"
+               InstancePrefix "heap_read"
+               ValuesFrom "heap_read"
+       </Result>
+       <Result>
+               Type "pg_blks"
+               InstancePrefix "heap_hit"
+               ValuesFrom "heap_hit"
+       </Result>
+       <Result>
+               Type "pg_blks"
+               InstancePrefix "idx_read"
+               ValuesFrom "idx_read"
+       </Result>
+       <Result>
+               Type "pg_blks"
+               InstancePrefix "idx_hit"
+               ValuesFrom "idx_hit"
+       </Result>
+       <Result>
+               Type "pg_blks"
+               InstancePrefix "toast_read"
+               ValuesFrom "toast_read"
+       </Result>
+       <Result>
+               Type "pg_blks"
+               InstancePrefix "toast_hit"
+               ValuesFrom "toast_hit"
+       </Result>
+       <Result>
+               Type "pg_blks"
+               InstancePrefix "tidx_read"
+               ValuesFrom "tidx_read"
+       </Result>
+       <Result>
+               Type "pg_blks"
+               InstancePrefix "tidx_hit"
+               ValuesFrom "tidx_hit"
+       </Result>
+</Query>
+
+<Query disk_usage>
+       Statement "SELECT pg_database_size($1) AS size;"
+
+       Param database
+
+       <Result>
+               Type pg_db_size
+               ValuesFrom "size"
+       </Result>
+</Query>
+
+# vim: set ft=config :
+
diff --git a/src/powerdns.c b/src/powerdns.c
new file mode 100644 (file)
index 0000000..a1b2355
--- /dev/null
@@ -0,0 +1,989 @@
+/**
+ * collectd - src/powerdns.c
+ * Copyright (C) 2007-2008  C-Ware, Inc.
+ * Copyright (C) 2008       Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Luke Heberling <lukeh at c-ware.com>
+ *   Florian Forster <octo at verplant.org>
+ *
+ * DESCRIPTION
+ *   Queries a PowerDNS control socket for statistics
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+#include "utils_llist.h"
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#ifndef UNIX_PATH_MAX
+# define UNIX_PATH_MAX sizeof (((struct sockaddr_un *)0)->sun_path)
+#endif
+#define FUNC_ERROR(func) do { char errbuf[1024]; ERROR ("powerdns plugin: %s failed: %s", func, sstrerror (errno, errbuf, sizeof (errbuf))); } while (0)
+
+#define SERVER_SOCKET  LOCALSTATEDIR"/run/pdns.controlsocket"
+#define SERVER_COMMAND "SHOW * \n"
+
+#define RECURSOR_SOCKET  LOCALSTATEDIR"/run/pdns_recursor.controlsocket"
+#define RECURSOR_COMMAND "get noerror-answers nxdomain-answers " \
+  "servfail-answers sys-msec user-msec qa-latency cache-entries cache-hits " \
+  "cache-misses questions\n"
+
+struct list_item_s;
+typedef struct list_item_s list_item_t;
+
+struct list_item_s
+{
+  enum
+  {
+    SRV_AUTHORITATIVE,
+    SRV_RECURSOR
+  } server_type;
+  int (*func) (list_item_t *item);
+  char *instance;
+
+  char **fields;
+  int fields_num;
+  char *command;
+
+  struct sockaddr_un sockaddr;
+  int socktype;
+};
+
+struct statname_lookup_s
+{
+  char *name;
+  char *type;
+  char *type_instance;
+};
+typedef struct statname_lookup_s statname_lookup_t;
+
+/* Description of statistics returned by the recursor: {{{
+all-outqueries      counts the number of outgoing UDP queries since starting
+answers0-1          counts the number of queries answered within 1 milisecond
+answers100-1000     counts the number of queries answered within 1 second
+answers10-100       counts the number of queries answered within 100 miliseconds
+answers1-10         counts the number of queries answered within 10 miliseconds
+answers-slow        counts the number of queries answered after 1 second
+cache-entries       shows the number of entries in the cache
+cache-hits          counts the number of cache hits since starting
+cache-misses        counts the number of cache misses since starting
+chain-resends       number of queries chained to existing outstanding query
+client-parse-errors counts number of client packets that could not be parsed
+concurrent-queries  shows the number of MThreads currently running
+dlg-only-drops      number of records dropped because of delegation only setting
+negcache-entries    shows the number of entries in the Negative answer cache
+noerror-answers     counts the number of times it answered NOERROR since starting
+nsspeeds-entries    shows the number of entries in the NS speeds map
+nsset-invalidations number of times an nsset was dropped because it no longer worked
+nxdomain-answers    counts the number of times it answered NXDOMAIN since starting
+outgoing-timeouts   counts the number of timeouts on outgoing UDP queries since starting
+qa-latency          shows the current latency average
+questions           counts all End-user initiated queries with the RD bit set
+resource-limits     counts number of queries that could not be performed because of resource limits
+server-parse-errors counts number of server replied packets that could not be parsed
+servfail-answers    counts the number of times it answered SERVFAIL since starting
+spoof-prevents      number of times PowerDNS considered itself spoofed, and dropped the data
+sys-msec            number of CPU milliseconds spent in 'system' mode
+tcp-client-overflow number of times an IP address was denied TCP access because it already had too many connections
+tcp-outqueries      counts the number of outgoing TCP queries since starting
+tcp-questions       counts all incoming TCP queries (since starting)
+throttled-out       counts the number of throttled outgoing UDP queries since starting
+throttle-entries    shows the number of entries in the throttle map
+unauthorized-tcp    number of TCP questions denied because of allow-from restrictions
+unauthorized-udp    number of UDP questions denied because of allow-from restrictions
+unexpected-packets  number of answers from remote servers that were unexpected (might point to spoofing)
+uptime              number of seconds process has been running (since 3.1.5)
+user-msec           number of CPU milliseconds spent in 'user' mode
+}}} */
+
+const char* const default_server_fields[] = /* {{{ */
+{
+  "latency"
+  "packetcache-hit",
+  "packetcache-miss",
+  "packetcache-size",
+  "query-cache-hit",
+  "query-cache-miss",
+  "recursing-answers",
+  "recursing-questions",
+  "tcp-answers",
+  "tcp-queries",
+  "udp-answers",
+  "udp-queries",
+}; /* }}} */
+int default_server_fields_num = STATIC_ARRAY_SIZE (default_server_fields);
+
+statname_lookup_t lookup_table[] = /* {{{ */
+{
+  /*********************
+   * Server statistics *
+   *********************/
+  /* Questions */
+  {"recursing-questions",    "dns_question", "recurse"},
+  {"tcp-queries",            "dns_question", "tcp"},
+  {"udp-queries",            "dns_question", "udp"},
+
+  /* Answers */
+  {"recursing-answers",      "dns_answer",   "recurse"},
+  {"tcp-answers",            "dns_answer",   "tcp"},
+  {"udp-answers",            "dns_answer",   "udp"},
+
+  /* Cache stuff */
+  {"packetcache-hit",        "cache_result", "packet-hit"},
+  {"packetcache-miss",       "cache_result", "packet-miss"},
+  {"packetcache-size",       "cache_size",   "packet"},
+  {"query-cache-hit",        "cache_result", "query-hit"},
+  {"query-cache-miss",       "cache_result", "query-miss"},
+
+  /* Latency */
+  {"latency",                "latency",      NULL},
+
+  /* Other stuff.. */
+  {"corrupt-packets",        "ipt_packets",  "corrupt"},
+  {"deferred-cache-inserts", "counter",      "cache-deferred_insert"},
+  {"deferred-cache-lookup",  "counter",      "cache-deferred_lookup"},
+  {"qsize-a",                "cache_size",   "answers"},
+  {"qsize-q",                "cache_size",   "questions"},
+  {"servfail-packets",       "ipt_packets",  "servfail"},
+  {"timedout-packets",       "ipt_packets",  "timeout"},
+  {"udp4-answers",           "dns_answer",   "udp4"},
+  {"udp4-queries",           "dns_question", "queries-udp4"},
+  {"udp6-answers",           "dns_answer",   "udp6"},
+  {"udp6-queries",           "dns_question", "queries-udp6"},
+
+  /***********************
+   * Recursor statistics *
+   ***********************/
+  /* Answers by return code */
+  {"noerror-answers",     "dns_rcode",    "NOERROR"},
+  {"nxdomain-answers",    "dns_rcode",    "NXDOMAIN"},
+  {"servfail-answers",    "dns_rcode",    "SERVFAIL"},
+
+  /* CPU utilization */
+  {"sys-msec",            "cpu",          "system"},
+  {"user-msec",           "cpu",          "user"},
+
+  /* Question-to-answer latency */
+  {"qa-latency",          "latency",      NULL},
+
+  /* Cache */
+  {"cache-entries",       "cache_size",   NULL},
+  {"cache-hits",          "cache_result", "hit"},
+  {"cache-misses",        "cache_result", "miss"},
+
+  /* Total number of questions.. */
+  {"questions",           "dns_qtype",    "total"},
+
+  /* All the other stuff.. */
+  {"all-outqueries",      "dns_question", "outgoing"},
+  {"answers0-1",          "dns_answer",   "0_1"},
+  {"answers1-10",         "dns_answer",   "1_10"},
+  {"answers10-100",       "dns_answer",   "10_100"},
+  {"answers100-1000",     "dns_answer",   "100_1000"},
+  {"answers-slow",        "dns_answer",   "slow"},
+  {"chain-resends",       "dns_question", "chained"},
+  {"client-parse-errors", "counter",      "drops-client_parse_error"},
+  {"concurrent-queries",  "dns_question", "concurrent"},
+  {"dlg-only-drops",      "counter",      "drops-delegation_only"},
+  {"negcache-entries",    "cache_size",   "negative"},
+  {"nsspeeds-entries",    "gauge",        "entries-ns_speeds"},
+  {"nsset-invalidations", "counter",      "ns_set_invalidation"},
+  {"outgoing-timeouts",   "counter",      "drops-timeout_outgoing"},
+  {"resource-limits",     "counter",      "drops-resource_limit"},
+  {"server-parse-errors", "counter",      "drops-server_parse_error"},
+  {"spoof-prevents",      "counter",      "drops-spoofed"},
+  {"tcp-client-overflow", "counter",      "denied-client_overflow_tcp"},
+  {"tcp-outqueries",      "dns_question", "outgoing-tcp"},
+  {"tcp-questions",       "dns_question", "incoming-tcp"},
+  {"throttled-out",       "dns_question", "outgoing-throttled"},
+  {"throttle-entries",    "gauge",        "entries-throttle"},
+  {"unauthorized-tcp",    "counter",      "denied-unauthorized_tcp"},
+  {"unauthorized-udp",    "counter",      "denied-unauthorized_udp"},
+  {"unexpected-packets",  "dns_answer",   "unexpected"}
+  /* {"uptime", "", ""} */
+}; /* }}} */
+int lookup_table_length = STATIC_ARRAY_SIZE (lookup_table);
+
+static llist_t *list = NULL;
+
+#define PDNS_LOCAL_SOCKPATH LOCALSTATEDIR"/run/"PACKAGE_NAME"-powerdns"
+static char *local_sockpath = NULL;
+
+/* TODO: Do this before 4.4:
+ * - Recursor:
+ *   - Complete list of known pdns -> collectd mappings.
+ * - Update the collectd.conf(5) manpage.
+ *
+ * -octo
+ */
+
+/* <http://doc.powerdns.com/recursor-stats.html> */
+static void submit (const char *plugin_instance, /* {{{ */
+    const char *pdns_type, const char *value)
+{
+  value_list_t vl = VALUE_LIST_INIT;
+  value_t values[1];
+
+  const char *type = NULL;
+  const char *type_instance = NULL;
+  const data_set_t *ds;
+
+  int i;
+
+  for (i = 0; i < lookup_table_length; i++)
+    if (strcmp (lookup_table[i].name, pdns_type) == 0)
+      break;
+
+  if (lookup_table[i].type == NULL)
+    return;
+
+  if (i >= lookup_table_length)
+  {
+    INFO ("powerdns plugin: submit: Not found in lookup table: %s = %s;",
+        pdns_type, value);
+    return;
+  }
+
+  type = lookup_table[i].type;
+  type_instance = lookup_table[i].type_instance;
+
+  ds = plugin_get_ds (type);
+  if (ds == NULL)
+  {
+    ERROR ("powerdns plugin: The lookup table returned type `%s', "
+        "but I cannot find it via `plugin_get_ds'.",
+        type);
+    return;
+  }
+
+  if (ds->ds_num != 1)
+  {
+    ERROR ("powerdns plugin: type `%s' has %i data sources, "
+        "but I can only handle one.",
+        type, ds->ds_num);
+    return;
+  }
+
+  if (0 != parse_value (value, &values[0], ds->ds[0].type))
+  {
+    ERROR ("powerdns plugin: Cannot convert `%s' "
+        "to a number.", value);
+    return;
+  }
+
+  vl.values = values;
+  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)
+    sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
+  sstrncpy (vl.plugin_instance, plugin_instance, sizeof (vl.plugin_instance));
+
+  plugin_dispatch_values (&vl);
+} /* }}} static void submit */
+
+static int powerdns_get_data_dgram (list_item_t *item, /* {{{ */
+    char **ret_buffer,
+    size_t *ret_buffer_size)
+{
+  int sd;
+  int status;
+
+  char temp[4096];
+  char *buffer = NULL;
+  size_t buffer_size = 0;
+
+  struct sockaddr_un sa_unix;
+
+  struct timeval stv_timeout;
+  cdtime_t cdt_timeout;
+
+  sd = socket (PF_UNIX, item->socktype, 0);
+  if (sd < 0)
+  {
+    FUNC_ERROR ("socket");
+    return (-1);
+  }
+
+  memset (&sa_unix, 0, sizeof (sa_unix));
+  sa_unix.sun_family = AF_UNIX;
+  sstrncpy (sa_unix.sun_path,
+      (local_sockpath != NULL) ? local_sockpath : PDNS_LOCAL_SOCKPATH,
+      sizeof (sa_unix.sun_path));
+
+  status = unlink (sa_unix.sun_path);
+  if ((status != 0) && (errno != ENOENT))
+  {
+    FUNC_ERROR ("unlink");
+    close (sd);
+    return (-1);
+  }
+
+  do /* while (0) */
+  {
+    /* We need to bind to a specific path, because this is a datagram socket
+     * and otherwise the daemon cannot answer. */
+    status = bind (sd, (struct sockaddr *) &sa_unix, sizeof (sa_unix));
+    if (status != 0)
+    {
+      FUNC_ERROR ("bind");
+      break;
+    }
+
+    /* Make the socket writeable by the daemon.. */
+    status = chmod (sa_unix.sun_path, 0666);
+    if (status != 0)
+    {
+      FUNC_ERROR ("chmod");
+      break;
+    }
+
+    cdt_timeout = interval_g * 3 / 4;
+    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));
+    if (status != 0)
+    {
+      FUNC_ERROR ("setsockopt");
+      break;
+    }
+
+    status = connect (sd, (struct sockaddr *) &item->sockaddr,
+        sizeof (item->sockaddr));
+    if (status != 0)
+    {
+      FUNC_ERROR ("connect");
+      break;
+    }
+
+    status = send (sd, item->command, strlen (item->command), 0);
+    if (status < 0)
+    {
+      FUNC_ERROR ("send");
+      break;
+    }
+
+    status = recv (sd, temp, sizeof (temp), /* flags = */ 0);
+    if (status < 0)
+    {
+      FUNC_ERROR ("recv");
+      break;
+    }
+    buffer_size = status + 1;
+    status = 0;
+  } while (0);
+
+  close (sd);
+  unlink (sa_unix.sun_path);
+
+  if (status != 0)
+    return (-1);
+
+  assert (buffer_size > 0);
+  buffer = (char *) malloc (buffer_size);
+  if (buffer == NULL)
+  {
+    FUNC_ERROR ("malloc");
+    return (-1);
+  }
+
+  memcpy (buffer, temp, buffer_size - 1);
+  buffer[buffer_size - 1] = 0;
+
+  *ret_buffer = buffer;
+  *ret_buffer_size = buffer_size;
+
+  return (0);
+} /* }}} int powerdns_get_data_dgram */
+
+static int powerdns_get_data_stream (list_item_t *item, /* {{{ */
+    char **ret_buffer,
+    size_t *ret_buffer_size)
+{
+  int sd;
+  int status;
+
+  char temp[4096];
+  char *buffer = NULL;
+  size_t buffer_size = 0;
+
+  sd = socket (PF_UNIX, item->socktype, 0);
+  if (sd < 0)
+  {
+    FUNC_ERROR ("socket");
+    return (-1);
+  }
+
+  struct timeval timeout;
+  timeout.tv_sec=5;
+  timeout.tv_usec=0;
+  status = setsockopt (sd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof (timeout));
+
+  status = connect (sd, (struct sockaddr *) &item->sockaddr,
+      sizeof (item->sockaddr));
+  if (status != 0)
+  {
+    FUNC_ERROR ("connect");
+    close (sd);
+    return (-1);
+  }
+
+  /* strlen + 1, because we need to send the terminating NULL byte, too. */
+  status = send (sd, item->command, strlen (item->command) + 1,
+      /* flags = */ 0);
+  if (status < 0)
+  {
+    FUNC_ERROR ("send");
+    close (sd);
+    return (-1);
+  }
+
+  while (42)
+  {
+    char *buffer_new;
+
+    status = recv (sd, temp, sizeof (temp), /* flags = */ 0);
+    if (status < 0)
+    {
+      FUNC_ERROR ("recv");
+      break;
+    }
+    else if (status == 0)
+      break;
+
+    buffer_new = (char *) realloc (buffer, buffer_size + status + 1);
+    if (buffer_new == NULL)
+    {
+      FUNC_ERROR ("realloc");
+      status = -1;
+      break;
+    }
+    buffer = buffer_new;
+
+    memcpy (buffer + buffer_size, temp, status);
+    buffer_size += status;
+    buffer[buffer_size] = 0;
+  } /* while (42) */
+  close (sd);
+  sd = -1;
+
+  if (status < 0)
+  {
+    sfree (buffer);
+  }
+  else
+  {
+    assert (status == 0);
+    *ret_buffer = buffer;
+    *ret_buffer_size = buffer_size;
+  }
+
+  return (status);
+} /* }}} int powerdns_get_data_stream */
+
+static int powerdns_get_data (list_item_t *item, char **ret_buffer,
+    size_t *ret_buffer_size)
+{
+  if (item->socktype == SOCK_DGRAM)
+    return (powerdns_get_data_dgram (item, ret_buffer, ret_buffer_size));
+  else if (item->socktype == SOCK_STREAM)
+    return (powerdns_get_data_stream (item, ret_buffer, ret_buffer_size));
+  else
+  {
+    ERROR ("powerdns plugin: Unknown socket type: %i", (int) item->socktype);
+    return (-1);
+  }
+} /* int powerdns_get_data */
+
+static int powerdns_read_server (list_item_t *item) /* {{{ */
+{
+  char *buffer = NULL;
+  size_t buffer_size = 0;
+  int status;
+
+  char *dummy;
+  char *saveptr;
+
+  char *key;
+  char *value;
+
+  const char* const *fields;
+  int fields_num;
+
+  if (item->command == NULL)
+    item->command = strdup (SERVER_COMMAND);
+  if (item->command == NULL)
+  {
+    ERROR ("powerdns plugin: strdup failed.");
+    return (-1);
+  }
+
+  status = powerdns_get_data (item, &buffer, &buffer_size);
+  if (status != 0)
+    return (-1);
+
+  if (item->fields_num != 0)
+  {
+    fields = (const char* const *) item->fields;
+    fields_num = item->fields_num;
+  }
+  else
+  {
+    fields = default_server_fields;
+    fields_num = default_server_fields_num;
+  }
+
+  assert (fields != NULL);
+  assert (fields_num > 0);
+
+  /* corrupt-packets=0,deferred-cache-inserts=0,deferred-cache-lookup=0,latency=0,packetcache-hit=0,packetcache-miss=0,packetcache-size=0,qsize-q=0,query-cache-hit=0,query-cache-miss=0,recursing-answers=0,recursing-questions=0,servfail-packets=0,tcp-answers=0,tcp-queries=0,timedout-packets=0,udp-answers=0,udp-queries=0,udp4-answers=0,udp4-queries=0,udp6-answers=0,udp6-queries=0, */
+  dummy = buffer;
+  saveptr = NULL;
+  while ((key = strtok_r (dummy, ",", &saveptr)) != NULL)
+  {
+    int i;
+
+    dummy = NULL;
+
+    value = strchr (key, '=');
+    if (value == NULL)
+      break;
+
+    *value = '\0';
+    value++;
+
+    if (value[0] == '\0')
+      continue;
+
+    /* Check if this item was requested. */
+    for (i = 0; i < fields_num; i++)
+      if (strcasecmp (key, fields[i]) == 0)
+       break;
+    if (i >= fields_num)
+      continue;
+
+    submit (item->instance, key, value);
+  } /* while (strtok_r) */
+
+  sfree (buffer);
+
+  return (0);
+} /* }}} int powerdns_read_server */
+
+/*
+ * powerdns_update_recursor_command
+ *
+ * Creates a string that holds the command to be sent to the recursor. This
+ * string is stores in the `command' member of the `list_item_t' passed to the
+ * function. This function is called by `powerdns_read_recursor'.
+ */
+static int powerdns_update_recursor_command (list_item_t *li) /* {{{ */
+{
+  char buffer[4096];
+  int status;
+
+  if (li == NULL)
+    return (0);
+
+  if (li->fields_num < 1)
+  {
+    sstrncpy (buffer, RECURSOR_COMMAND, sizeof (buffer));
+  }
+  else
+  {
+    sstrncpy (buffer, "get ", sizeof (buffer));
+    status = strjoin (&buffer[strlen("get ")], sizeof (buffer) - strlen ("get "),
+       li->fields, li->fields_num,
+       /* seperator = */ " ");
+    if (status < 0)
+    {
+      ERROR ("powerdns plugin: strjoin failed.");
+      return (-1);
+    }
+    buffer[sizeof (buffer) - 1] = 0;
+    int i = strlen (buffer);
+    if (i < sizeof (buffer) - 2)
+    {
+      buffer[i++] = ' ';
+      buffer[i++] = '\n';
+      buffer[i++] = '\0';
+    }
+  }
+
+  buffer[sizeof (buffer) - 1] = 0;
+  li->command = strdup (buffer);
+  if (li->command == NULL)
+  {
+    ERROR ("powerdns plugin: strdup failed.");
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int powerdns_update_recursor_command */
+
+static int powerdns_read_recursor (list_item_t *item) /* {{{ */
+{
+  char *buffer = NULL;
+  size_t buffer_size = 0;
+  int status;
+
+  char *dummy;
+
+  char *keys_list;
+  char *key;
+  char *key_saveptr;
+  char *value;
+  char *value_saveptr;
+
+  if (item->command == NULL)
+  {
+    status = powerdns_update_recursor_command (item);
+    if (status != 0)
+    {
+      ERROR ("powerdns plugin: powerdns_update_recursor_command failed.");
+      return (-1);
+    }
+
+    DEBUG ("powerdns plugin: powerdns_read_recursor: item->command = %s;",
+        item->command);
+  }
+  assert (item->command != NULL);
+
+  status = powerdns_get_data (item, &buffer, &buffer_size);
+  if (status != 0)
+  {
+    ERROR ("powerdns plugin: powerdns_get_data failed.");
+    return (-1);
+  }
+
+  keys_list = strdup (item->command);
+  if (keys_list == NULL)
+  {
+    FUNC_ERROR ("strdup");
+    sfree (buffer);
+    return (-1);
+  }
+
+  key_saveptr = NULL;
+  value_saveptr = NULL;
+
+  /* Skip the `get' at the beginning */
+  strtok_r (keys_list, " \t", &key_saveptr);
+
+  dummy = buffer;
+  while ((value = strtok_r (dummy, " \t\n\r", &value_saveptr)) != NULL)
+  {
+    dummy = NULL;
+
+    key = strtok_r (NULL, " \t", &key_saveptr);
+    if (key == NULL)
+      break;
+
+    submit (item->instance, key, value);
+  } /* while (strtok_r) */
+
+  sfree (buffer);
+  sfree (keys_list);
+
+  return (0);
+} /* }}} int powerdns_read_recursor */
+
+static int powerdns_config_add_string (const char *name, /* {{{ */
+    char **dest,
+    oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("powerdns plugin: `%s' needs exactly one string argument.",
+       name);
+    return (-1);
+  }
+
+  sfree (*dest);
+  *dest = strdup (ci->values[0].value.string);
+  if (*dest == NULL)
+    return (-1);
+
+  return (0);
+} /* }}} int powerdns_config_add_string */
+
+static int powerdns_config_add_collect (list_item_t *li, /* {{{ */
+    oconfig_item_t *ci)
+{
+  int i;
+  char **temp;
+
+  if (ci->values_num < 1)
+  {
+    WARNING ("powerdns plugin: The `Collect' option needs "
+       "at least one argument.");
+    return (-1);
+  }
+
+  for (i = 0; i < ci->values_num; i++)
+    if (ci->values[i].type != OCONFIG_TYPE_STRING)
+    {
+      WARNING ("powerdns plugin: Only string arguments are allowed to "
+         "the `Collect' option.");
+      return (-1);
+    }
+
+  temp = (char **) realloc (li->fields,
+      sizeof (char *) * (li->fields_num + ci->values_num));
+  if (temp == NULL)
+  {
+    WARNING ("powerdns plugin: realloc failed.");
+    return (-1);
+  }
+  li->fields = temp;
+
+  for (i = 0; i < ci->values_num; i++)
+  {
+    li->fields[li->fields_num] = strdup (ci->values[i].value.string);
+    if (li->fields[li->fields_num] == NULL)
+    {
+      WARNING ("powerdns plugin: strdup failed.");
+      continue;
+    }
+    li->fields_num++;
+  }
+
+  /* Invalidate a previously computed command */
+  sfree (li->command);
+
+  return (0);
+} /* }}} int powerdns_config_add_collect */
+
+static int powerdns_config_add_server (oconfig_item_t *ci) /* {{{ */
+{
+  char *socket_temp;
+
+  list_item_t *item;
+  int status;
+  int i;
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("powerdns plugin: `%s' needs exactly one string argument.",
+       ci->key);
+    return (-1);
+  }
+
+  item = (list_item_t *) malloc (sizeof (list_item_t));
+  if (item == NULL)
+  {
+    ERROR ("powerdns plugin: malloc failed.");
+    return (-1);
+  }
+  memset (item, '\0', sizeof (list_item_t));
+
+  item->instance = strdup (ci->values[0].value.string);
+  if (item->instance == NULL)
+  {
+    ERROR ("powerdns plugin: strdup failed.");
+    sfree (item);
+    return (-1);
+  }
+
+  /*
+   * Set default values for the members of list_item_t
+   */
+  if (strcasecmp ("Server", ci->key) == 0)
+  {
+    item->server_type = SRV_AUTHORITATIVE;
+    item->func = powerdns_read_server;
+    item->socktype = SOCK_STREAM;
+    socket_temp = strdup (SERVER_SOCKET);
+  }
+  else if (strcasecmp ("Recursor", ci->key) == 0)
+  {
+    item->server_type = SRV_RECURSOR;
+    item->func = powerdns_read_recursor;
+    item->socktype = SOCK_DGRAM;
+    socket_temp = strdup (RECURSOR_SOCKET);
+  }
+  else
+  {
+    /* We must never get here.. */
+    assert (0);
+    return (-1);
+  }
+
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+
+    if (strcasecmp ("Collect", option->key) == 0)
+      status = powerdns_config_add_collect (item, option);
+    else if (strcasecmp ("Socket", option->key) == 0)
+      status = powerdns_config_add_string ("Socket", &socket_temp, option);
+    else
+    {
+      ERROR ("powerdns plugin: Option `%s' not allowed here.", option->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  while (status == 0)
+  {
+    llentry_t *e;
+
+    if (socket_temp == NULL)
+    {
+      ERROR ("powerdns plugin: socket_temp == NULL.");
+      status = -1;
+      break;
+    }
+
+    item->sockaddr.sun_family = AF_UNIX;
+    sstrncpy (item->sockaddr.sun_path, socket_temp,
+      sizeof (item->sockaddr.sun_path));
+
+    e = llentry_create (item->instance, item);
+    if (e == NULL)
+    {
+      ERROR ("powerdns plugin: llentry_create failed.");
+      status = -1;
+      break;
+    }
+    llist_append (list, e);
+
+    break;
+  }
+
+  if (status != 0)
+  {
+    sfree (item);
+    return (-1);
+  }
+
+  DEBUG ("powerdns plugin: Add server: instance = %s;", item->instance);
+
+  return (0);
+} /* }}} int powerdns_config_add_server */
+
+static int powerdns_config (oconfig_item_t *ci) /* {{{ */
+{
+  int i;
+
+  DEBUG ("powerdns plugin: powerdns_config (ci = %p);", (void *) ci);
+
+  if (list == NULL)
+  {
+    list = llist_create ();
+
+    if (list == NULL)
+    {
+      ERROR ("powerdns plugin: `llist_create' failed.");
+      return (-1);
+    }
+  }
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+
+    if ((strcasecmp ("Server", option->key) == 0)
+       || (strcasecmp ("Recursor", option->key) == 0))
+      powerdns_config_add_server (option);
+    else if (strcasecmp ("LocalSocket", option->key) == 0)
+    {
+      if ((option->values_num != 1) || (option->values[0].type != OCONFIG_TYPE_STRING))
+      {
+        WARNING ("powerdns plugin: `%s' needs exactly one string argument.", option->key);
+      }
+      else
+      {
+        char *temp = strdup (option->values[0].value.string);
+        if (temp == NULL)
+          return (1);
+        sfree (local_sockpath);
+        local_sockpath = temp;
+      }
+    }
+    else
+    {
+      ERROR ("powerdns plugin: Option `%s' not allowed here.", option->key);
+    }
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  return (0);
+} /* }}} int powerdns_config */
+
+static int powerdns_read (void)
+{
+  llentry_t *e;
+
+  for (e = llist_head (list); e != NULL; e = e->next)
+  {
+    list_item_t *item = e->value;
+    item->func (item);
+  }
+
+  return (0);
+} /* static int powerdns_read */
+
+static int powerdns_shutdown (void)
+{
+  llentry_t *e;
+
+  if (list == NULL)
+    return (0);
+
+  for (e = llist_head (list); e != NULL; e = e->next)
+  {
+    list_item_t *item = (list_item_t *) e->value;
+    e->value = NULL;
+
+    sfree (item->instance);
+    sfree (item->command);
+    sfree (item);
+  }
+
+  llist_destroy (list);
+  list = NULL;
+
+  return (0);
+} /* static int powerdns_shutdown */
+
+void module_register (void)
+{
+  plugin_register_complex_config ("powerdns", powerdns_config);
+  plugin_register_read ("powerdns", powerdns_read);
+  plugin_register_shutdown ("powerdns", powerdns_shutdown );
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 ts=8 fdm=marker : */
diff --git a/src/processes.c b/src/processes.c
new file mode 100644 (file)
index 0000000..8f4eb88
--- /dev/null
@@ -0,0 +1,1845 @@
+/**
+ * collectd - src/processes.c
+ * Copyright (C) 2005       Lyonel Vincent
+ * Copyright (C) 2006-2010  Florian octo Forster
+ * Copyright (C) 2008       Oleg King
+ * Copyright (C) 2009       Sebastian Harl
+ * Copyright (C) 2009       Andrés J. Díaz
+ * Copyright (C) 2009       Manuel Sanmartin
+ * Copyright (C) 2010       Clément Stenac
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Lyonel Vincent <lyonel at ezix.org>
+ *   Florian octo Forster <octo at verplant.org>
+ *   Oleg King <king2 at kaluga.ru>
+ *   Sebastian Harl <sh at tokkee.org>
+ *   Andrés J. Díaz <ajdiaz at connectical.com>
+ *   Manuel Sanmartin
+ *   Clément Stenac <clement.stenac at diwi.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+
+/* Include header files for the mach system, if they exist.. */
+#if HAVE_THREAD_INFO
+#  if HAVE_MACH_MACH_INIT_H
+#    include <mach/mach_init.h>
+#  endif
+#  if HAVE_MACH_HOST_PRIV_H
+#    include <mach/host_priv.h>
+#  endif
+#  if HAVE_MACH_MACH_ERROR_H
+#    include <mach/mach_error.h>
+#  endif
+#  if HAVE_MACH_MACH_HOST_H
+#    include <mach/mach_host.h>
+#  endif
+#  if HAVE_MACH_MACH_PORT_H
+#    include <mach/mach_port.h>
+#  endif
+#  if HAVE_MACH_MACH_TYPES_H
+#    include <mach/mach_types.h>
+#  endif
+#  if HAVE_MACH_MESSAGE_H
+#    include <mach/message.h>
+#  endif
+#  if HAVE_MACH_PROCESSOR_SET_H
+#    include <mach/processor_set.h>
+#  endif
+#  if HAVE_MACH_TASK_H
+#    include <mach/task.h>
+#  endif
+#  if HAVE_MACH_THREAD_ACT_H
+#    include <mach/thread_act.h>
+#  endif
+#  if HAVE_MACH_VM_REGION_H
+#    include <mach/vm_region.h>
+#  endif
+#  if HAVE_MACH_VM_MAP_H
+#    include <mach/vm_map.h>
+#  endif
+#  if HAVE_MACH_VM_PROT_H
+#    include <mach/vm_prot.h>
+#  endif
+#  if HAVE_SYS_SYSCTL_H
+#    include <sys/sysctl.h>
+#  endif
+/* #endif HAVE_THREAD_INFO */
+
+#elif KERNEL_LINUX
+#  if HAVE_LINUX_CONFIG_H
+#    include <linux/config.h>
+#  endif
+#  ifndef CONFIG_HZ
+#    define CONFIG_HZ 100
+#  endif
+/* #endif KERNEL_LINUX */
+
+#elif HAVE_LIBKVM_GETPROCS && HAVE_STRUCT_KINFO_PROC_FREEBSD
+#  include <kvm.h>
+#  include <sys/param.h>
+#  include <sys/sysctl.h>
+#  include <sys/user.h>
+#  include <sys/proc.h>
+/* #endif HAVE_LIBKVM_GETPROCS && HAVE_STRUCT_KINFO_PROC_FREEBSD */
+
+#elif HAVE_PROCINFO_H
+#  include <procinfo.h>
+#  include <sys/types.h>
+
+#define MAXPROCENTRY 32
+#define MAXTHRDENTRY 16
+#define MAXARGLN 1024
+/* #endif HAVE_PROCINFO_H */
+
+#else
+# error "No applicable input method."
+#endif
+
+#if HAVE_REGEX_H
+# include <regex.h>
+#endif
+
+#ifndef ARG_MAX
+#  define ARG_MAX 4096
+#endif
+
+typedef struct procstat_entry_s
+{
+       unsigned long id;
+       unsigned long age;
+
+       unsigned long num_proc;
+       unsigned long num_lwp;
+       unsigned long vmem_size;
+       unsigned long vmem_rss;
+       unsigned long vmem_data;
+       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;
+
+       /* io data */
+       derive_t io_rchar;
+       derive_t io_wchar;
+       derive_t io_syscr;
+       derive_t io_syscw;
+
+       struct procstat_entry_s *next;
+} procstat_entry_t;
+
+#define PROCSTAT_NAME_LEN 256
+typedef struct procstat
+{
+       char          name[PROCSTAT_NAME_LEN];
+#if HAVE_REGEX_H
+       regex_t *re;
+#endif
+
+       unsigned long num_proc;
+       unsigned long num_lwp;
+       unsigned long vmem_size;
+       unsigned long vmem_rss;
+       unsigned long vmem_data;
+       unsigned long vmem_code;
+       unsigned long stack_size;
+
+       derive_t vmem_minflt_counter;
+       derive_t vmem_majflt_counter;
+
+       derive_t cpu_user_counter;
+       derive_t cpu_system_counter;
+
+       /* io data */
+       derive_t io_rchar;
+       derive_t io_wchar;
+       derive_t io_syscr;
+       derive_t io_syscw;
+
+       struct procstat   *next;
+       struct procstat_entry_s *instances;
+} procstat_t;
+
+static procstat_t *list_head_g = NULL;
+
+#if HAVE_THREAD_INFO
+static mach_port_t port_host_self;
+static mach_port_t port_task_self;
+
+static processor_set_name_array_t pset_list;
+static mach_msg_type_number_t     pset_list_len;
+/* #endif HAVE_THREAD_INFO */
+
+#elif KERNEL_LINUX
+static long pagesize_g;
+/* #endif KERNEL_LINUX */
+
+#elif HAVE_LIBKVM_GETPROCS && HAVE_STRUCT_KINFO_PROC_FREEBSD
+/* no global variables */
+/* #endif HAVE_LIBKVM_GETPROCS && HAVE_STRUCT_KINFO_PROC_FREEBSD */
+
+#elif HAVE_PROCINFO_H
+static  struct procentry64 procentry[MAXPROCENTRY];
+static  struct thrdentry64 thrdentry[MAXTHRDENTRY];
+static int pagesize;
+
+#ifndef _AIXVERSION_610
+int     getprocs64 (void *procsinfo, int sizproc, void *fdsinfo, int sizfd, pid_t *index, int count);
+int     getthrds64( pid_t, void *, int, tid64_t *, int );
+#endif
+int getargs (struct procentry64 *processBuffer, int bufferLen, char *argsBuffer, int argsLen);
+#endif /* HAVE_PROCINFO_H */
+
+/* put name of process from config to list_head_g tree
+   list_head_g is a list of 'procstat_t' structs with
+   processes names we want to watch */
+static void ps_list_register (const char *name, const char *regexp)
+{
+       procstat_t *new;
+       procstat_t *ptr;
+       int status;
+
+       new = (procstat_t *) malloc (sizeof (procstat_t));
+       if (new == NULL)
+       {
+               ERROR ("processes plugin: ps_list_register: malloc failed.");
+               return;
+       }
+       memset (new, 0, sizeof (procstat_t));
+       sstrncpy (new->name, name, sizeof (new->name));
+
+#if HAVE_REGEX_H
+       if (regexp != NULL)
+       {
+               DEBUG ("ProcessMatch: adding \"%s\" as criteria to process %s.", regexp, name);
+               new->re = (regex_t *) malloc (sizeof (regex_t));
+               if (new->re == NULL)
+               {
+                       ERROR ("processes plugin: ps_list_register: malloc failed.");
+                       sfree (new);
+                       return;
+               }
+
+               status = regcomp (new->re, regexp, REG_EXTENDED | REG_NOSUB);
+               if (status != 0)
+               {
+                       DEBUG ("ProcessMatch: compiling the regular expression \"%s\" failed.", regexp);
+                       sfree(new->re);
+                       return;
+               }
+       }
+#else
+       if (regexp != NULL)
+       {
+               ERROR ("processes plugin: ps_list_register: "
+                               "Regular expression \"%s\" found in config "
+                               "file, but support for regular expressions "
+                               "has been disabled at compile time.",
+                               regexp);
+               sfree (new);
+               return;
+       }
+#endif
+
+       for (ptr = list_head_g; ptr != NULL; ptr = ptr->next)
+       {
+               if (strcmp (ptr->name, name) == 0)
+               {
+                       WARNING ("processes plugin: You have configured more "
+                                       "than one `Process' or "
+                                       "`ProcessMatch' with the same name. "
+                                       "All but the first setting will be "
+                                       "ignored.");
+                       sfree (new->re);
+                       sfree (new);
+                       return;
+               }
+
+               if (ptr->next == NULL)
+                       break;
+       }
+
+       if (ptr == NULL)
+               list_head_g = new;
+       else
+               ptr->next = new;
+} /* void ps_list_register */
+
+/* try to match name against entry, returns 1 if success */
+static int ps_list_match (const char *name, const char *cmdline, procstat_t *ps)
+{
+#if HAVE_REGEX_H
+       if (ps->re != NULL)
+       {
+               int status;
+               const char *str;
+
+               str = cmdline;
+               if ((str == NULL) || (str[0] == 0))
+                       str = name;
+
+               assert (str != NULL);
+
+               status = regexec (ps->re, str,
+                               /* nmatch = */ 0,
+                               /* pmatch = */ NULL,
+                               /* eflags = */ 0);
+               if (status == 0)
+                       return (1);
+       }
+       else
+#endif
+       if (strcmp (ps->name, name) == 0)
+               return (1);
+
+       return (0);
+} /* int ps_list_match */
+
+/* add process entry to 'instances' of process 'name' (or refresh it) */
+static void ps_list_add (const char *name, const char *cmdline, procstat_entry_t *entry)
+{
+       procstat_t *ps;
+       procstat_entry_t *pse;
+
+       if (entry->id == 0)
+               return;
+
+       for (ps = list_head_g; ps != NULL; ps = ps->next)
+       {
+               if ((ps_list_match (name, cmdline, ps)) == 0)
+                       continue;
+
+               for (pse = ps->instances; pse != NULL; pse = pse->next)
+                       if ((pse->id == entry->id) || (pse->next == NULL))
+                               break;
+
+               if ((pse == NULL) || (pse->id != entry->id))
+               {
+                       procstat_entry_t *new;
+
+                       new = (procstat_entry_t *) malloc (sizeof (procstat_entry_t));
+                       if (new == NULL)
+                               return;
+                       memset (new, 0, sizeof (procstat_entry_t));
+                       new->id = entry->id;
+
+                       if (pse == NULL)
+                               ps->instances = new;
+                       else
+                               pse->next = new;
+
+                       pse = new;
+               }
+
+               pse->age = 0;
+               pse->num_proc   = entry->num_proc;
+               pse->num_lwp    = entry->num_lwp;
+               pse->vmem_size  = entry->vmem_size;
+               pse->vmem_rss   = entry->vmem_rss;
+               pse->vmem_data  = entry->vmem_data;
+               pse->vmem_code  = entry->vmem_code;
+               pse->stack_size = entry->stack_size;
+               pse->io_rchar   = entry->io_rchar;
+               pse->io_wchar   = entry->io_wchar;
+               pse->io_syscr   = entry->io_syscr;
+               pse->io_syscw   = entry->io_syscw;
+
+               ps->num_proc   += pse->num_proc;
+               ps->num_lwp    += pse->num_lwp;
+               ps->vmem_size  += pse->vmem_size;
+               ps->vmem_rss   += pse->vmem_rss;
+               ps->vmem_data  += pse->vmem_data;
+               ps->vmem_code  += pse->vmem_code;
+               ps->stack_size += pse->stack_size;
+
+               ps->io_rchar   += ((pse->io_rchar == -1)?0:pse->io_rchar);
+               ps->io_wchar   += ((pse->io_wchar == -1)?0:pse->io_wchar);
+               ps->io_syscr   += ((pse->io_syscr == -1)?0:pse->io_syscr);
+               ps->io_syscw   += ((pse->io_syscw == -1)?0:pse->io_syscw);
+
+               if ((entry->vmem_minflt_counter == 0)
+                               && (entry->vmem_majflt_counter == 0))
+               {
+                       pse->vmem_minflt_counter += entry->vmem_minflt;
+                       pse->vmem_minflt = entry->vmem_minflt;
+
+                       pse->vmem_majflt_counter += entry->vmem_majflt;
+                       pse->vmem_majflt = entry->vmem_majflt;
+               }
+               else
+               {
+                       if (entry->vmem_minflt_counter < pse->vmem_minflt_counter)
+                       {
+                               pse->vmem_minflt = entry->vmem_minflt_counter
+                                       + (ULONG_MAX - pse->vmem_minflt_counter);
+                       }
+                       else
+                       {
+                               pse->vmem_minflt = entry->vmem_minflt_counter - pse->vmem_minflt_counter;
+                       }
+                       pse->vmem_minflt_counter = entry->vmem_minflt_counter;
+
+                       if (entry->vmem_majflt_counter < pse->vmem_majflt_counter)
+                       {
+                               pse->vmem_majflt = entry->vmem_majflt_counter
+                                       + (ULONG_MAX - pse->vmem_majflt_counter);
+                       }
+                       else
+                       {
+                               pse->vmem_majflt = entry->vmem_majflt_counter - pse->vmem_majflt_counter;
+                       }
+                       pse->vmem_majflt_counter = entry->vmem_majflt_counter;
+               }
+
+               ps->vmem_minflt_counter += pse->vmem_minflt;
+               ps->vmem_majflt_counter += pse->vmem_majflt;
+
+               if ((entry->cpu_user_counter == 0)
+                               && (entry->cpu_system_counter == 0))
+               {
+                       pse->cpu_user_counter += entry->cpu_user;
+                       pse->cpu_user = entry->cpu_user;
+
+                       pse->cpu_system_counter += entry->cpu_system;
+                       pse->cpu_system = entry->cpu_system;
+               }
+               else
+               {
+                       if (entry->cpu_user_counter < pse->cpu_user_counter)
+                       {
+                               pse->cpu_user = entry->cpu_user_counter
+                                       + (ULONG_MAX - pse->cpu_user_counter);
+                       }
+                       else
+                       {
+                               pse->cpu_user = entry->cpu_user_counter - pse->cpu_user_counter;
+                       }
+                       pse->cpu_user_counter = entry->cpu_user_counter;
+
+                       if (entry->cpu_system_counter < pse->cpu_system_counter)
+                       {
+                               pse->cpu_system = entry->cpu_system_counter
+                                       + (ULONG_MAX - pse->cpu_system_counter);
+                       }
+                       else
+                       {
+                               pse->cpu_system = entry->cpu_system_counter - pse->cpu_system_counter;
+                       }
+                       pse->cpu_system_counter = entry->cpu_system_counter;
+               }
+
+               ps->cpu_user_counter   += pse->cpu_user;
+               ps->cpu_system_counter += pse->cpu_system;
+       }
+}
+
+/* remove old entries from instances of processes in list_head_g */
+static void ps_list_reset (void)
+{
+       procstat_t *ps;
+       procstat_entry_t *pse;
+       procstat_entry_t *pse_prev;
+
+       for (ps = list_head_g; ps != NULL; ps = ps->next)
+       {
+               ps->num_proc    = 0;
+               ps->num_lwp     = 0;
+               ps->vmem_size   = 0;
+               ps->vmem_rss    = 0;
+               ps->vmem_data   = 0;
+               ps->vmem_code   = 0;
+               ps->stack_size  = 0;
+               ps->io_rchar = -1;
+               ps->io_wchar = -1;
+               ps->io_syscr = -1;
+               ps->io_syscw = -1;
+
+               pse_prev = NULL;
+               pse = ps->instances;
+               while (pse != NULL)
+               {
+                       if (pse->age > 10)
+                       {
+                               DEBUG ("Removing this procstat entry cause it's too old: "
+                                               "id = %lu; name = %s;",
+                                               pse->id, ps->name);
+
+                               if (pse_prev == NULL)
+                               {
+                                       ps->instances = pse->next;
+                                       free (pse);
+                                       pse = ps->instances;
+                               }
+                               else
+                               {
+                                       pse_prev->next = pse->next;
+                                       free (pse);
+                                       pse = pse_prev->next;
+                               }
+                       }
+                       else
+                       {
+                               pse->age++;
+                               pse_prev = pse;
+                               pse = pse->next;
+                       }
+               } /* while (pse != NULL) */
+       } /* for (ps = list_head_g; ps != NULL; ps = ps->next) */
+}
+
+/* put all pre-defined 'Process' names from config to list_head_g tree */
+static int ps_config (oconfig_item_t *ci)
+{
+       int i;
+
+       for (i = 0; i < ci->children_num; ++i) {
+               oconfig_item_t *c = ci->children + i;
+
+               if (strcasecmp (c->key, "Process") == 0)
+               {
+                       if ((c->values_num != 1)
+                                       || (OCONFIG_TYPE_STRING != c->values[0].type)) {
+                               ERROR ("processes plugin: `Process' expects exactly "
+                                               "one string argument (got %i).",
+                                               c->values_num);
+                               continue;
+                       }
+
+                       if (c->children_num != 0) {
+                               WARNING ("processes plugin: the `Process' config option "
+                                               "does not expect any child elements -- ignoring "
+                                               "content (%i elements) of the <Process '%s'> block.",
+                                               c->children_num, c->values[0].value.string);
+                       }
+
+                       ps_list_register (c->values[0].value.string, NULL);
+               }
+               else if (strcasecmp (c->key, "ProcessMatch") == 0)
+               {
+                       if ((c->values_num != 2)
+                                       || (OCONFIG_TYPE_STRING != c->values[0].type)
+                                       || (OCONFIG_TYPE_STRING != c->values[1].type))
+                       {
+                               ERROR ("processes plugin: `ProcessMatch' needs exactly "
+                                               "two string arguments (got %i).",
+                                               c->values_num);
+                               continue;
+                       }
+
+                       if (c->children_num != 0) {
+                               WARNING ("processes plugin: the `ProcessMatch' config option "
+                                               "does not expect any child elements -- ignoring "
+                                               "content (%i elements) of the <ProcessMatch '%s' '%s'> "
+                                               "block.", c->children_num, c->values[0].value.string,
+                                               c->values[1].value.string);
+                       }
+
+                       ps_list_register (c->values[0].value.string,
+                                       c->values[1].value.string);
+               }
+               else
+               {
+                       ERROR ("processes plugin: The `%s' configuration option is not "
+                                       "understood and will be ignored.", c->key);
+                       continue;
+               }
+       }
+
+       return (0);
+}
+
+static int ps_init (void)
+{
+#if HAVE_THREAD_INFO
+       kern_return_t status;
+
+       port_host_self = mach_host_self ();
+       port_task_self = mach_task_self ();
+
+       if (pset_list != NULL)
+       {
+               vm_deallocate (port_task_self,
+                               (vm_address_t) pset_list,
+                               pset_list_len * sizeof (processor_set_t));
+               pset_list = NULL;
+               pset_list_len = 0;
+       }
+
+       if ((status = host_processor_sets (port_host_self,
+                                       &pset_list,
+                                       &pset_list_len)) != KERN_SUCCESS)
+       {
+               ERROR ("host_processor_sets failed: %s\n",
+                               mach_error_string (status));
+               pset_list = NULL;
+               pset_list_len = 0;
+               return (-1);
+       }
+/* #endif HAVE_THREAD_INFO */
+
+#elif KERNEL_LINUX
+       pagesize_g = sysconf(_SC_PAGESIZE);
+       DEBUG ("pagesize_g = %li; CONFIG_HZ = %i;",
+                       pagesize_g, CONFIG_HZ);
+/* #endif KERNEL_LINUX */
+
+#elif HAVE_LIBKVM_GETPROCS && HAVE_STRUCT_KINFO_PROC_FREEBSD
+/* no initialization */
+/* #endif HAVE_LIBKVM_GETPROCS && HAVE_STRUCT_KINFO_PROC_FREEBSD */
+
+#elif HAVE_PROCINFO_H
+       pagesize = getpagesize();
+#endif /* HAVE_PROCINFO_H */
+
+       return (0);
+} /* int ps_init */
+
+/* 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_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));
+       sstrncpy (vl.type_instance, state, sizeof (vl.type_instance));
+
+       plugin_dispatch_values (&vl);
+}
+
+/* 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;
+
+       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));
+
+       sstrncpy (vl.type, "ps_vm", sizeof (vl.type));
+       vl.values[0].gauge = ps->vmem_size;
+       vl.values_len = 1;
+       plugin_dispatch_values (&vl);
+
+       sstrncpy (vl.type, "ps_rss", sizeof (vl.type));
+       vl.values[0].gauge = ps->vmem_rss;
+       vl.values_len = 1;
+       plugin_dispatch_values (&vl);
+
+       sstrncpy (vl.type, "ps_data", sizeof (vl.type));
+       vl.values[0].gauge = ps->vmem_data;
+       vl.values_len = 1;
+       plugin_dispatch_values (&vl);
+
+       sstrncpy (vl.type, "ps_code", sizeof (vl.type));
+       vl.values[0].gauge = ps->vmem_code;
+       vl.values_len = 1;
+       plugin_dispatch_values (&vl);
+
+       sstrncpy (vl.type, "ps_stacksize", sizeof (vl.type));
+       vl.values[0].gauge = ps->stack_size;
+       vl.values_len = 1;
+       plugin_dispatch_values (&vl);
+
+       sstrncpy (vl.type, "ps_cputime", sizeof (vl.type));
+       vl.values[0].derive = ps->cpu_user_counter;
+       vl.values[1].derive = ps->cpu_system_counter;
+       vl.values_len = 2;
+       plugin_dispatch_values (&vl);
+
+       sstrncpy (vl.type, "ps_count", sizeof (vl.type));
+       vl.values[0].gauge = ps->num_proc;
+       vl.values[1].gauge = ps->num_lwp;
+       vl.values_len = 2;
+       plugin_dispatch_values (&vl);
+
+       sstrncpy (vl.type, "ps_pagefaults", sizeof (vl.type));
+       vl.values[0].derive = ps->vmem_minflt_counter;
+       vl.values[1].derive = ps->vmem_majflt_counter;
+       vl.values_len = 2;
+       plugin_dispatch_values (&vl);
+
+       if ( (ps->io_rchar != -1) && (ps->io_wchar != -1) )
+       {
+               sstrncpy (vl.type, "ps_disk_octets", sizeof (vl.type));
+               vl.values[0].derive = ps->io_rchar;
+               vl.values[1].derive = ps->io_wchar;
+               vl.values_len = 2;
+               plugin_dispatch_values (&vl);
+       }
+
+       if ( (ps->io_syscr != -1) && (ps->io_syscw != -1) )
+       {
+               sstrncpy (vl.type, "ps_disk_ops", sizeof (vl.type));
+               vl.values[0].derive = ps->io_syscr;
+               vl.values[1].derive = ps->io_syscw;
+               vl.values_len = 2;
+               plugin_dispatch_values (&vl);
+       }
+
+       DEBUG ("name = %s; num_proc = %lu; num_lwp = %lu; "
+                        "vmem_size = %lu; vmem_rss = %lu; vmem_data = %lu; "
+                       "vmem_code = %lu; "
+                       "vmem_minflt_counter = %"PRIi64"; vmem_majflt_counter = %"PRIi64"; "
+                       "cpu_user_counter = %"PRIi64"; cpu_system_counter = %"PRIi64"; "
+                       "io_rchar = %"PRIi64"; io_wchar = %"PRIi64"; "
+                       "io_syscr = %"PRIi64"; io_syscw = %"PRIi64";",
+                       ps->name, ps->num_proc, ps->num_lwp,
+                       ps->vmem_size, ps->vmem_rss,
+                       ps->vmem_data, ps->vmem_code,
+                       ps->vmem_minflt_counter, ps->vmem_majflt_counter,
+                       ps->cpu_user_counter, ps->cpu_system_counter,
+                       ps->io_rchar, ps->io_wchar, ps->io_syscr, ps->io_syscw);
+} /* void ps_submit_proc_list */
+
+/* ------- additional functions for KERNEL_LINUX/HAVE_THREAD_INFO ------- */
+#if KERNEL_LINUX
+static int ps_read_tasks (int pid)
+{
+       char           dirname[64];
+       DIR           *dh;
+       struct dirent *ent;
+       int count = 0;
+
+       ssnprintf (dirname, sizeof (dirname), "/proc/%i/task", pid);
+
+       if ((dh = opendir (dirname)) == NULL)
+       {
+               DEBUG ("Failed to open directory `%s'", dirname);
+               return (-1);
+       }
+
+       while ((ent = readdir (dh)) != NULL)
+       {
+               if (!isdigit ((int) ent->d_name[0]))
+                       continue;
+               else
+                       count++;
+       }
+       closedir (dh);
+
+       return ((count >= 1) ? count : 1);
+} /* int *ps_read_tasks */
+
+/* Read advanced virtual memory data from /proc/pid/status */
+static procstat_t *ps_read_vmem (int pid, procstat_t *ps)
+{
+       FILE *fh;
+       char buffer[1024];
+       char filename[64];
+       unsigned long long lib = 0;
+       unsigned long long exe = 0;
+       unsigned long long data = 0;
+       char *fields[8];
+       int numfields;
+
+       ssnprintf (filename, sizeof (filename), "/proc/%i/status", pid);
+       if ((fh = fopen (filename, "r")) == NULL)
+               return (NULL);
+
+       while (fgets (buffer, sizeof(buffer), fh) != NULL)
+       {
+               long long tmp;
+               char *endptr;
+
+               if (strncmp (buffer, "Vm", 2) != 0)
+                       continue;
+
+               numfields = strsplit (buffer, fields,
+                                      STATIC_ARRAY_SIZE (fields));
+
+               if (numfields < 2)
+                       continue;
+
+               errno = 0;
+               endptr = NULL;
+               tmp = strtoll (fields[1], &endptr, /* base = */ 10);
+               if ((errno == 0) && (endptr != fields[1]))
+               {
+                       if (strncmp (buffer, "VmData", 6) == 0) 
+                       {
+                               data = tmp;
+                       }
+                       else if (strncmp (buffer, "VmLib", 5) == 0)
+                       {
+                               lib = tmp;
+                       }
+                       else if  (strncmp(buffer, "VmExe", 5) == 0)
+                       {
+                               exe = tmp;
+                       }
+               }
+       } /* while (fgets) */
+
+       if (fclose (fh))
+       {
+               char errbuf[1024];
+               WARNING ("processes: fclose: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+       }
+
+       ps->vmem_data = data * 1024;
+       ps->vmem_code = (exe + lib) * 1024;
+
+       return (ps);
+} /* procstat_t *ps_read_vmem */
+
+static procstat_t *ps_read_io (int pid, procstat_t *ps)
+{
+       FILE *fh;
+       char buffer[1024];
+       char filename[64];
+
+       char *fields[8];
+       int numfields;
+
+       ssnprintf (filename, sizeof (filename), "/proc/%i/io", pid);
+       if ((fh = fopen (filename, "r")) == NULL)
+               return (NULL);
+
+       while (fgets (buffer, sizeof (buffer), fh) != NULL)
+       {
+               derive_t *val = NULL;
+               long long tmp;
+               char *endptr;
+
+               if (strncasecmp (buffer, "rchar:", 6) == 0)
+                       val = &(ps->io_rchar);
+               else if (strncasecmp (buffer, "wchar:", 6) == 0)
+                       val = &(ps->io_wchar);
+               else if (strncasecmp (buffer, "syscr:", 6) == 0)
+                       val = &(ps->io_syscr);
+               else if (strncasecmp (buffer, "syscw:", 6) == 0)
+                       val = &(ps->io_syscw);
+               else
+                       continue;
+
+               numfields = strsplit (buffer, fields,
+                               STATIC_ARRAY_SIZE (fields));
+
+               if (numfields < 2)
+                       continue;
+
+               errno = 0;
+               endptr = NULL;
+               tmp = strtoll (fields[1], &endptr, /* base = */ 10);
+               if ((errno != 0) || (endptr == fields[1]))
+                       *val = -1;
+               else
+                       *val = (derive_t) tmp;
+       } /* while (fgets) */
+
+       if (fclose (fh))
+       {
+               char errbuf[1024];
+               WARNING ("processes: fclose: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+       }
+
+       return (ps);
+} /* procstat_t *ps_read_io */
+
+int ps_read_process (int pid, procstat_t *ps, char *state)
+{
+       char  filename[64];
+       char  buffer[1024];
+
+       char *fields[64];
+       char  fields_len;
+
+       int   i;
+
+       int   name_len;
+
+       derive_t cpu_user_counter;
+       derive_t cpu_system_counter;
+       long long unsigned vmem_size;
+       long long unsigned vmem_rss;
+       long long unsigned stack_size;
+
+       memset (ps, 0, sizeof (procstat_t));
+
+       ssnprintf (filename, sizeof (filename), "/proc/%i/stat", pid);
+
+       i = read_file_contents (filename, buffer, sizeof(buffer) - 1);
+       if (i <= 0)
+               return (-1);
+       buffer[i] = 0;
+
+       fields_len = strsplit (buffer, fields, STATIC_ARRAY_SIZE (fields));
+       if (fields_len < 24)
+       {
+               DEBUG ("processes plugin: ps_read_process (pid = %i):"
+                               " `%s' has only %i fields..",
+                               (int) pid, filename, fields_len);
+               return (-1);
+       }
+
+       /* copy the name, strip brackets in the process */
+       name_len = strlen (fields[1]) - 2;
+       if ((fields[1][0] != '(') || (fields[1][name_len + 1] != ')'))
+       {
+               DEBUG ("No brackets found in process name: `%s'", fields[1]);
+               return (-1);
+       }
+       fields[1] = fields[1] + 1;
+       fields[1][name_len] = '\0';
+       strncpy (ps->name, fields[1], PROCSTAT_NAME_LEN);
+
+
+       *state = fields[2][0];
+
+       if (*state == 'Z')
+       {
+               ps->num_lwp  = 0;
+               ps->num_proc = 0;
+       }
+       else
+       {
+               if ( (ps->num_lwp = ps_read_tasks (pid)) == -1 )
+               {
+                       /* returns -1 => kernel 2.4 */
+                       ps->num_lwp = 1;
+               }
+               ps->num_proc = 1;
+       }
+
+       /* Leave the rest at zero if this is only a zombi */
+       if (ps->num_proc == 0)
+       {
+               DEBUG ("processes plugin: This is only a zombi: pid = %i; "
+                               "name = %s;", pid, ps->name);
+               return (0);
+       }
+
+       cpu_user_counter   = atoll (fields[13]);
+       cpu_system_counter = atoll (fields[14]);
+       vmem_size          = atoll (fields[22]);
+       vmem_rss           = atoll (fields[23]);
+       ps->vmem_minflt_counter = atoll (fields[9]);
+       ps->vmem_majflt_counter = atoll (fields[11]);
+
+       {
+               unsigned long long stack_start = atoll (fields[27]);
+               unsigned long long stack_ptr   = atoll (fields[28]);
+
+               stack_size = (stack_start > stack_ptr)
+                       ? stack_start - stack_ptr
+                       : stack_ptr - stack_start;
+       }
+
+       /* Convert jiffies to useconds */
+       cpu_user_counter   = cpu_user_counter   * 1000000 / CONFIG_HZ;
+       cpu_system_counter = cpu_system_counter * 1000000 / CONFIG_HZ;
+       vmem_rss = vmem_rss * pagesize_g;
+
+       if ( (ps_read_vmem(pid, ps)) == NULL)
+       {
+               /* No VMem data */
+               ps->vmem_data = -1;
+               ps->vmem_code = -1;
+               DEBUG("ps_read_process: did not get vmem data for pid %i",pid);
+       }
+
+       ps->cpu_user_counter = cpu_user_counter;
+       ps->cpu_system_counter = cpu_system_counter;
+       ps->vmem_size = (unsigned long) vmem_size;
+       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 %i",pid);
+       }
+
+       /* success */
+       return (0);
+} /* int ps_read_process (...) */
+
+static char *ps_get_cmdline (pid_t pid, char *name, char *buf, size_t buf_len)
+{
+       char  *buf_ptr;
+       size_t len;
+
+       char file[PATH_MAX];
+       int  fd;
+
+       size_t n;
+
+       if ((pid < 1) || (NULL == buf) || (buf_len < 2))
+               return NULL;
+
+       ssnprintf (file, sizeof (file), "/proc/%u/cmdline",
+                       (unsigned int) pid);
+
+       errno = 0;
+       fd = open (file, O_RDONLY);
+       if (fd < 0) {
+               char errbuf[4096];
+               /* ENOENT means the process exited while we were handling it.
+                * Don't complain about this, it only fills the logs. */
+               if (errno != ENOENT)
+                       WARNING ("processes plugin: Failed to open `%s': %s.", file,
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+               return NULL;
+       }
+
+       buf_ptr = buf;
+       len     = buf_len;
+
+       n = 0;
+
+       while (42) {
+               ssize_t status;
+
+               status = read (fd, (void *)buf_ptr, len);
+
+               if (status < 0) {
+                       char errbuf[1024];
+
+                       if ((EAGAIN == errno) || (EINTR == errno))
+                               continue;
+
+                       WARNING ("processes plugin: Failed to read from `%s': %s.", file,
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       close (fd);
+                       return NULL;
+               }
+
+               n += status;
+
+               if (status == 0)
+                       break;
+
+               buf_ptr += status;
+               len     -= status;
+
+               if (len <= 0)
+                       break;
+       }
+
+       close (fd);
+
+       if (0 == n) {
+               /* cmdline not available; e.g. kernel thread, zombie */
+               if (NULL == name)
+                       return NULL;
+
+               ssnprintf (buf, buf_len, "[%s]", name);
+               return buf;
+       }
+
+       assert (n <= buf_len);
+
+       if (n == buf_len)
+               --n;
+       buf[n] = '\0';
+
+       --n;
+       /* remove trailing whitespace */
+       while ((n > 0) && (isspace (buf[n]) || ('\0' == buf[n]))) {
+               buf[n] = '\0';
+               --n;
+       }
+
+       /* arguments are separated by '\0' in /proc/<pid>/cmdline */
+       while (n > 0) {
+               if ('\0' == buf[n])
+                       buf[n] = ' ';
+               --n;
+       }
+       return buf;
+} /* char *ps_get_cmdline (...) */
+
+static unsigned long read_fork_rate ()
+{
+       FILE *proc_stat;
+       char buf[1024];
+       unsigned long result = 0;
+       int numfields;
+       char *fields[3];
+
+       proc_stat = fopen("/proc/stat", "r");
+       if (proc_stat == NULL) {
+               char errbuf[1024];
+               ERROR ("processes plugin: fopen (/proc/stat) failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return ULONG_MAX;
+       }
+
+       while (fgets (buf, sizeof(buf), proc_stat) != NULL)
+       {
+               char *endptr;
+
+               numfields = strsplit(buf, fields, STATIC_ARRAY_SIZE (fields));
+               if (numfields != 2)
+                       continue;
+
+               if (strcmp ("processes", fields[0]) != 0)
+                       continue;
+
+               errno = 0;
+               endptr = NULL;
+               result = strtoul(fields[1], &endptr, /* base = */ 10);
+               if ((endptr == fields[1]) || (errno != 0)) {
+                       ERROR ("processes plugin: Cannot parse fork rate: %s",
+                                       fields[1]);
+                       result = ULONG_MAX;
+                       break;
+               }
+
+               break;
+       }
+
+       fclose(proc_stat);
+
+       return result;
+}
+
+static void ps_submit_fork_rate (unsigned long value)
+{
+       value_t values[1];
+       value_list_t vl = VALUE_LIST_INIT;
+
+       values[0].derive = (derive_t) value;
+
+       vl.values = values;
+       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));
+       sstrncpy (vl.type_instance, "", sizeof (vl.type_instance));
+
+       plugin_dispatch_values (&vl);
+}
+
+#endif /* KERNEL_LINUX */
+
+#if HAVE_THREAD_INFO
+static int mach_get_task_name (task_t t, int *pid, char *name, size_t name_max_len)
+{
+       int mib[4];
+
+       struct kinfo_proc kp;
+       size_t            kp_size;
+
+       mib[0] = CTL_KERN;
+       mib[1] = KERN_PROC;
+       mib[2] = KERN_PROC_PID;
+
+       if (pid_for_task (t, pid) != KERN_SUCCESS)
+               return (-1);
+       mib[3] = *pid;
+
+       kp_size = sizeof (kp);
+       if (sysctl (mib, 4, &kp, &kp_size, NULL, 0) != 0)
+               return (-1);
+
+       if (name_max_len > (MAXCOMLEN + 1))
+               name_max_len = MAXCOMLEN + 1;
+
+       strncpy (name, kp.kp_proc.p_comm, name_max_len - 1);
+       name[name_max_len - 1] = '\0';
+
+       DEBUG ("pid = %i; name = %s;", *pid, name);
+
+       /* We don't do the special handling for `p_comm == "LaunchCFMApp"' as
+        * `top' does it, because it is a lot of work and only used when
+        * debugging. -octo */
+
+       return (0);
+}
+#endif /* HAVE_THREAD_INFO */
+/* ------- end of additional functions for KERNEL_LINUX/HAVE_THREAD_INFO ------- */
+
+/* do actual readings from kernel */
+static int ps_read (void)
+{
+#if HAVE_THREAD_INFO
+       kern_return_t            status;
+
+       int                      pset;
+       processor_set_t          port_pset_priv;
+
+       int                      task;
+       task_array_t             task_list;
+       mach_msg_type_number_t   task_list_len;
+
+       int                      task_pid;
+       char                     task_name[MAXCOMLEN + 1];
+
+       int                      thread;
+       thread_act_array_t       thread_list;
+       mach_msg_type_number_t   thread_list_len;
+       thread_basic_info_data_t thread_data;
+       mach_msg_type_number_t   thread_data_len;
+
+       int running  = 0;
+       int sleeping = 0;
+       int zombies  = 0;
+       int stopped  = 0;
+       int blocked  = 0;
+
+       procstat_t *ps;
+       procstat_entry_t pse;
+
+       ps_list_reset ();
+
+       /*
+        * The Mach-concept is a little different from the traditional UNIX
+        * concept: All the work is done in threads. Threads are contained in
+        * `tasks'. Therefore, `task status' doesn't make much sense, since
+        * it's actually a `thread status'.
+        * Tasks are assigned to sets of processors, so that's where you go to
+        * get a list.
+        */
+       for (pset = 0; pset < pset_list_len; pset++)
+       {
+               if ((status = host_processor_set_priv (port_host_self,
+                                               pset_list[pset],
+                                               &port_pset_priv)) != KERN_SUCCESS)
+               {
+                       ERROR ("host_processor_set_priv failed: %s\n",
+                                       mach_error_string (status));
+                       continue;
+               }
+
+               if ((status = processor_set_tasks (port_pset_priv,
+                                               &task_list,
+                                               &task_list_len)) != KERN_SUCCESS)
+               {
+                       ERROR ("processor_set_tasks failed: %s\n",
+                                       mach_error_string (status));
+                       mach_port_deallocate (port_task_self, port_pset_priv);
+                       continue;
+               }
+
+               for (task = 0; task < task_list_len; task++)
+               {
+                       ps = NULL;
+                       if (mach_get_task_name (task_list[task],
+                                               &task_pid,
+                                               task_name, PROCSTAT_NAME_LEN) == 0)
+                       {
+                               /* search for at least one match */
+                               for (ps = list_head_g; ps != NULL; ps = ps->next)
+                                       /* FIXME: cmdline should be here instead of NULL */
+                                       if (ps_list_match (task_name, NULL, ps) == 1)
+                                               break;
+                       }
+
+                       /* Collect more detailed statistics for this process */
+                       if (ps != NULL)
+                       {
+                               task_basic_info_data_t        task_basic_info;
+                               mach_msg_type_number_t        task_basic_info_len;
+                               task_events_info_data_t       task_events_info;
+                               mach_msg_type_number_t        task_events_info_len;
+                               task_absolutetime_info_data_t task_absolutetime_info;
+                               mach_msg_type_number_t        task_absolutetime_info_len;
+
+                               memset (&pse, '\0', sizeof (pse));
+                               pse.id = task_pid;
+
+                               task_basic_info_len = TASK_BASIC_INFO_COUNT;
+                               status = task_info (task_list[task],
+                                               TASK_BASIC_INFO,
+                                               (task_info_t) &task_basic_info,
+                                               &task_basic_info_len);
+                               if (status != KERN_SUCCESS)
+                               {
+                                       ERROR ("task_info failed: %s",
+                                                       mach_error_string (status));
+                                       continue; /* with next thread_list */
+                               }
+
+                               task_events_info_len = TASK_EVENTS_INFO_COUNT;
+                               status = task_info (task_list[task],
+                                               TASK_EVENTS_INFO,
+                                               (task_info_t) &task_events_info,
+                                               &task_events_info_len);
+                               if (status != KERN_SUCCESS)
+                               {
+                                       ERROR ("task_info failed: %s",
+                                                       mach_error_string (status));
+                                       continue; /* with next thread_list */
+                               }
+
+                               task_absolutetime_info_len = TASK_ABSOLUTETIME_INFO_COUNT;
+                               status = task_info (task_list[task],
+                                               TASK_ABSOLUTETIME_INFO,
+                                               (task_info_t) &task_absolutetime_info,
+                                               &task_absolutetime_info_len);
+                               if (status != KERN_SUCCESS)
+                               {
+                                       ERROR ("task_info failed: %s",
+                                                       mach_error_string (status));
+                                       continue; /* with next thread_list */
+                               }
+
+                               pse.num_proc++;
+                               pse.vmem_size = task_basic_info.virtual_size;
+                               pse.vmem_rss = task_basic_info.resident_size;
+                               /* Does not seem to be easily exposed */
+                               pse.vmem_data = 0;
+                               pse.vmem_code = 0;
+
+                               pse.vmem_minflt_counter = task_events_info.cow_faults;
+                               pse.vmem_majflt_counter = task_events_info.faults;
+
+                               pse.cpu_user_counter = task_absolutetime_info.total_user;
+                               pse.cpu_system_counter = task_absolutetime_info.total_system;
+                       }
+
+                       status = task_threads (task_list[task], &thread_list,
+                                       &thread_list_len);
+                       if (status != KERN_SUCCESS)
+                       {
+                               /* Apple's `top' treats this case a zombie. It
+                                * makes sense to some extend: A `zombie'
+                                * thread is nonsense, since the task/process
+                                * is dead. */
+                               zombies++;
+                               DEBUG ("task_threads failed: %s",
+                                               mach_error_string (status));
+                               if (task_list[task] != port_task_self)
+                                       mach_port_deallocate (port_task_self,
+                                                       task_list[task]);
+                               continue; /* with next task_list */
+                       }
+
+                       for (thread = 0; thread < thread_list_len; thread++)
+                       {
+                               thread_data_len = THREAD_BASIC_INFO_COUNT;
+                               status = thread_info (thread_list[thread],
+                                               THREAD_BASIC_INFO,
+                                               (thread_info_t) &thread_data,
+                                               &thread_data_len);
+                               if (status != KERN_SUCCESS)
+                               {
+                                       ERROR ("thread_info failed: %s",
+                                                       mach_error_string (status));
+                                       if (task_list[task] != port_task_self)
+                                               mach_port_deallocate (port_task_self,
+                                                               thread_list[thread]);
+                                       continue; /* with next thread_list */
+                               }
+
+                               if (ps != NULL)
+                                       pse.num_lwp++;
+
+                               switch (thread_data.run_state)
+                               {
+                                       case TH_STATE_RUNNING:
+                                               running++;
+                                               break;
+                                       case TH_STATE_STOPPED:
+                                       /* What exactly is `halted'? */
+                                       case TH_STATE_HALTED:
+                                               stopped++;
+                                               break;
+                                       case TH_STATE_WAITING:
+                                               sleeping++;
+                                               break;
+                                       case TH_STATE_UNINTERRUPTIBLE:
+                                               blocked++;
+                                               break;
+                                       /* There is no `zombie' case here,
+                                        * since there are no zombie-threads.
+                                        * There's only zombie tasks, which are
+                                        * handled above. */
+                                       default:
+                                               WARNING ("Unknown thread status: %i",
+                                                               thread_data.run_state);
+                                               break;
+                               } /* switch (thread_data.run_state) */
+
+                               if (task_list[task] != port_task_self)
+                               {
+                                       status = mach_port_deallocate (port_task_self,
+                                                       thread_list[thread]);
+                                       if (status != KERN_SUCCESS)
+                                               ERROR ("mach_port_deallocate failed: %s",
+                                                               mach_error_string (status));
+                               }
+                       } /* for (thread_list) */
+
+                       if ((status = vm_deallocate (port_task_self,
+                                                       (vm_address_t) thread_list,
+                                                       thread_list_len * sizeof (thread_act_t)))
+                                       != KERN_SUCCESS)
+                       {
+                               ERROR ("vm_deallocate failed: %s",
+                                               mach_error_string (status));
+                       }
+                       thread_list = NULL;
+                       thread_list_len = 0;
+
+                       /* Only deallocate the task port, if it isn't our own.
+                        * Don't know what would happen in that case, but this
+                        * is what Apple's top does.. ;) */
+                       if (task_list[task] != port_task_self)
+                       {
+                               status = mach_port_deallocate (port_task_self,
+                                               task_list[task]);
+                               if (status != KERN_SUCCESS)
+                                       ERROR ("mach_port_deallocate failed: %s",
+                                                       mach_error_string (status));
+                       }
+
+                       if (ps != NULL)
+                               /* FIXME: cmdline should be here instead of NULL */
+                               ps_list_add (task_name, NULL, &pse);
+               } /* for (task_list) */
+
+               if ((status = vm_deallocate (port_task_self,
+                               (vm_address_t) task_list,
+                               task_list_len * sizeof (task_t))) != KERN_SUCCESS)
+               {
+                       ERROR ("vm_deallocate failed: %s",
+                                       mach_error_string (status));
+               }
+               task_list = NULL;
+               task_list_len = 0;
+
+               if ((status = mach_port_deallocate (port_task_self, port_pset_priv))
+                               != KERN_SUCCESS)
+               {
+                       ERROR ("mach_port_deallocate failed: %s",
+                                       mach_error_string (status));
+               }
+       } /* for (pset_list) */
+
+       ps_submit_state ("running", running);
+       ps_submit_state ("sleeping", sleeping);
+       ps_submit_state ("zombies", zombies);
+       ps_submit_state ("stopped", stopped);
+       ps_submit_state ("blocked", blocked);
+
+       for (ps = list_head_g; ps != NULL; ps = ps->next)
+               ps_submit_proc_list (ps);
+/* #endif HAVE_THREAD_INFO */
+
+#elif KERNEL_LINUX
+       int running  = 0;
+       int sleeping = 0;
+       int zombies  = 0;
+       int stopped  = 0;
+       int paging   = 0;
+       int blocked  = 0;
+
+       struct dirent *ent;
+       DIR           *proc;
+       int            pid;
+
+       char cmdline[ARG_MAX];
+
+       int        status;
+       procstat_t ps;
+       procstat_entry_t pse;
+       char       state;
+
+       unsigned long fork_rate;
+
+       procstat_t *ps_ptr;
+
+       running = sleeping = zombies = stopped = paging = blocked = 0;
+       ps_list_reset ();
+
+       if ((proc = opendir ("/proc")) == NULL)
+       {
+               char errbuf[1024];
+               ERROR ("Cannot open `/proc': %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       while ((ent = readdir (proc)) != NULL)
+       {
+               if (!isdigit (ent->d_name[0]))
+                       continue;
+
+               if ((pid = atoi (ent->d_name)) < 1)
+                       continue;
+
+               status = ps_read_process (pid, &ps, &state);
+               if (status != 0)
+               {
+                       DEBUG ("ps_read_process failed: %i", status);
+                       continue;
+               }
+
+               pse.id       = pid;
+               pse.age      = 0;
+
+               pse.num_proc   = ps.num_proc;
+               pse.num_lwp    = ps.num_lwp;
+               pse.vmem_size  = ps.vmem_size;
+               pse.vmem_rss   = ps.vmem_rss;
+               pse.vmem_data  = ps.vmem_data;
+               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;
+               pse.io_wchar = ps.io_wchar;
+               pse.io_syscr = ps.io_syscr;
+               pse.io_syscw = ps.io_syscw;
+
+               switch (state)
+               {
+                       case 'R': running++;  break;
+                       case 'S': sleeping++; break;
+                       case 'D': blocked++;  break;
+                       case 'Z': zombies++;  break;
+                       case 'T': stopped++;  break;
+                       case 'W': paging++;   break;
+               }
+
+               ps_list_add (ps.name,
+                               ps_get_cmdline (pid, ps.name, cmdline, sizeof (cmdline)),
+                               &pse);
+       }
+
+       closedir (proc);
+
+       ps_submit_state ("running",  running);
+       ps_submit_state ("sleeping", sleeping);
+       ps_submit_state ("zombies",  zombies);
+       ps_submit_state ("stopped",  stopped);
+       ps_submit_state ("paging",   paging);
+       ps_submit_state ("blocked",  blocked);
+
+       for (ps_ptr = list_head_g; ps_ptr != NULL; ps_ptr = ps_ptr->next)
+               ps_submit_proc_list (ps_ptr);
+
+       fork_rate = read_fork_rate();
+       if (fork_rate != ULONG_MAX)
+               ps_submit_fork_rate(fork_rate);
+/* #endif KERNEL_LINUX */
+
+#elif HAVE_LIBKVM_GETPROCS && HAVE_STRUCT_KINFO_PROC_FREEBSD
+       int running  = 0;
+       int sleeping = 0;
+       int zombies  = 0;
+       int stopped  = 0;
+       int blocked  = 0;
+       int idle     = 0;
+       int wait     = 0;
+
+       kvm_t *kd;
+       char errbuf[1024];
+       char cmdline[ARG_MAX];
+       char *cmdline_ptr;
+       struct kinfo_proc *procs;          /* array of processes */
+       char **argv;
+       int count;                         /* returns number of processes */
+       int i;
+
+       procstat_t *ps_ptr;
+       procstat_entry_t pse;
+
+       ps_list_reset ();
+
+       /* Open the kvm interface, get a descriptor */
+       kd = kvm_open (NULL, NULL, NULL, 0, errbuf);
+       if (kd == NULL)
+       {
+               ERROR ("processes plugin: Cannot open kvm interface: %s",
+                               errbuf);
+               return (0);
+       }
+
+       /* Get the list of processes. */
+       procs = kvm_getprocs(kd, KERN_PROC_ALL, 0, &count);
+       if (procs == NULL)
+       {
+               ERROR ("processes plugin: Cannot get kvm processes list: %s",
+                               kvm_geterr(kd));
+               kvm_close (kd);
+               return (0);
+       }
+
+       /* Iterate through the processes in kinfo_proc */
+       for (i = 0; i < count; i++)
+       {
+               /* retrieve the arguments */
+               cmdline[0] = 0;
+               cmdline_ptr = NULL;
+
+               argv = kvm_getargv (kd, (const struct kinfo_proc *) &(procs[i]), 0);
+               if (argv != NULL)
+               {
+                       int status;
+                       int argc;
+
+                       argc = 0;
+                       while (argv[argc] != NULL)
+                               argc++;
+
+                       status = strjoin (cmdline, sizeof (cmdline),
+                                       argv, argc, " ");
+
+                       if (status < 0)
+                       {
+                               WARNING ("processes plugin: Command line did "
+                                               "not fit into buffer.");
+                       }
+                       else
+                       {
+                               cmdline_ptr = &cmdline[0];
+                       }
+               }
+
+               pse.id       = procs[i].ki_pid;
+               pse.age      = 0;
+
+               pse.num_proc = 1;
+               pse.num_lwp  = procs[i].ki_numthreads;
+
+               pse.vmem_size = procs[i].ki_size;
+               pse.vmem_rss = procs[i].ki_rssize * getpagesize();
+               pse.vmem_data = procs[i].ki_dsize * getpagesize();
+               pse.vmem_code = procs[i].ki_tsize * getpagesize();
+               pse.stack_size = procs[i].ki_ssize * getpagesize();
+               pse.vmem_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_user_counter = procs[i].ki_rusage.ru_utime.tv_sec
+                       * 1000
+                       + procs[i].ki_rusage.ru_utime.tv_usec;
+               pse.cpu_system = 0;
+               pse.cpu_system_counter = procs[i].ki_rusage.ru_stime.tv_sec
+                       * 1000
+                       + procs[i].ki_rusage.ru_stime.tv_usec;
+
+               /* no io data */
+               pse.io_rchar = -1;
+               pse.io_wchar = -1;
+               pse.io_syscr = -1;
+               pse.io_syscw = -1;
+
+               switch (procs[i].ki_stat)
+               {
+                       case SSTOP:     stopped++;      break;
+                       case SSLEEP:    sleeping++;     break;
+                       case SRUN:      running++;      break;
+                       case SIDL:      idle++;         break;
+                       case SWAIT:     wait++;         break;
+                       case SLOCK:     blocked++;      break;
+                       case SZOMB:     zombies++;      break;
+               }
+
+               ps_list_add (procs[i].ki_comm, cmdline_ptr, &pse);
+       }
+
+       kvm_close(kd);
+
+       ps_submit_state ("running",  running);
+       ps_submit_state ("sleeping", sleeping);
+       ps_submit_state ("zombies",  zombies);
+       ps_submit_state ("stopped",  stopped);
+       ps_submit_state ("blocked",  blocked);
+       ps_submit_state ("idle",     idle);
+       ps_submit_state ("wait",     wait);
+
+       for (ps_ptr = list_head_g; ps_ptr != NULL; ps_ptr = ps_ptr->next)
+               ps_submit_proc_list (ps_ptr);
+/* #endif HAVE_LIBKVM_GETPROCS && HAVE_STRUCT_KINFO_PROC_FREEBSD */
+
+#elif HAVE_PROCINFO_H
+       /* AIX */
+       int running  = 0;
+       int sleeping = 0;
+       int zombies  = 0;
+       int stopped  = 0;
+       int paging   = 0;
+       int blocked  = 0;
+
+       pid_t pindex = 0;
+       int nprocs;
+
+       procstat_t *ps;
+       procstat_entry_t pse;
+
+       ps_list_reset ();
+       while ((nprocs = getprocs64 (procentry, sizeof(struct procentry64),
+                                       /* fdsinfo = */ NULL, sizeof(struct fdsinfo64),
+                                       &pindex, MAXPROCENTRY)) > 0)
+       {
+               int i;
+
+               for (i = 0; i < nprocs; i++)
+               {
+                       tid64_t thindex;
+                       int nthreads;
+                       char arglist[MAXARGLN+1];
+                       char *cargs;
+                       char *cmdline;
+
+                       if (procentry[i].pi_state == SNONE) continue;
+                       /* if (procentry[i].pi_state == SZOMB)  FIXME */
+
+                       cmdline = procentry[i].pi_comm;
+                       cargs = procentry[i].pi_comm;
+                       if ( procentry[i].pi_flags & SKPROC )
+                       {
+                               if (procentry[i].pi_pid == 0)
+                                       cmdline = "swapper";
+                               cargs = cmdline;
+                       }
+                       else
+                       {
+                               if (getargs(&procentry[i], sizeof(struct procentry64), arglist, MAXARGLN) >= 0)
+                               {
+                                       int n;
+
+                                       n = -1;
+                                       while (++n < MAXARGLN)
+                                       {
+                                               if (arglist[n] == '\0')
+                                               {
+                                                       if (arglist[n+1] == '\0')
+                                                               break;
+                                                       arglist[n] = ' ';
+                                               }
+                                       }
+                                       cargs = arglist;
+                               }
+                       }
+
+                       pse.id       = procentry[i].pi_pid;
+                       pse.age      = 0;
+                       pse.num_lwp  = procentry[i].pi_thcount;
+                       pse.num_proc = 1;
+
+                       thindex=0;
+                       while ((nthreads = getthrds64(procentry[i].pi_pid,
+                                                       thrdentry, sizeof(struct thrdentry64),
+                                                       &thindex, MAXTHRDENTRY)) > 0)
+                       {
+                               int j;
+
+                               for (j=0; j< nthreads; j++)
+                               {
+                                       switch (thrdentry[j].ti_state)
+                                       {
+                                               /* case TSNONE: break; */
+                                               case TSIDL:     blocked++;      break; /* FIXME is really blocked */
+                                               case TSRUN:     running++;      break;
+                                               case TSSLEEP:   sleeping++;     break;
+                                               case TSSWAP:    paging++;       break;
+                                               case TSSTOP:    stopped++;      break;
+                                               case TSZOMB:    zombies++;      break;
+                                       }
+                               }
+                               if (nthreads < MAXTHRDENTRY)
+                                       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;
+                       pse.vmem_rss = (procentry[i].pi_drss + procentry[i].pi_trss) * pagesize;
+                       /* Not supported */
+                       pse.vmem_data = 0;
+                       pse.vmem_code = 0;
+                       pse.stack_size =  0;
+
+                       pse.io_rchar = -1;
+                       pse.io_wchar = -1;
+                       pse.io_syscr = -1;
+                       pse.io_syscw = -1;
+
+                       ps_list_add (cmdline, cargs, &pse);
+               } /* for (i = 0 .. nprocs) */
+
+               if (nprocs < MAXPROCENTRY)
+                       break;
+       } /* while (getprocs64() > 0) */
+       ps_submit_state ("running",  running);
+       ps_submit_state ("sleeping", sleeping);
+       ps_submit_state ("zombies",  zombies);
+       ps_submit_state ("stopped",  stopped);
+       ps_submit_state ("paging",   paging);
+       ps_submit_state ("blocked",  blocked);
+
+       for (ps = list_head_g; ps != NULL; ps = ps->next)
+               ps_submit_proc_list (ps);
+#endif /* HAVE_PROCINFO_H */
+
+       return (0);
+} /* int ps_read */
+
+void module_register (void)
+{
+       plugin_register_complex_config ("processes", ps_config);
+       plugin_register_init ("processes", ps_init);
+       plugin_register_read ("processes", ps_read);
+} /* void module_register */
diff --git a/src/protocols.c b/src/protocols.c
new file mode 100644 (file)
index 0000000..0dfba21
--- /dev/null
@@ -0,0 +1,242 @@
+/**
+ * collectd - src/protocols.c
+ * Copyright (C) 2009,2010  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "utils_ignorelist.h"
+
+#if !KERNEL_LINUX
+# error "No applicable input method."
+#endif
+
+#define SNMP_FILE "/proc/net/snmp"
+#define NETSTAT_FILE "/proc/net/netstat"
+
+/*
+ * Global variables
+ */
+static const char *config_keys[] =
+{
+  "Value",
+  "IgnoreSelected",
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static ignorelist_t *values_list = NULL;
+
+/* 
+ * Functions
+ */
+static void submit (const char *protocol_name,
+    const char *str_key, const char *str_value)
+{
+  value_t values[1];
+  value_list_t vl = VALUE_LIST_INIT;
+  int status;
+
+  status = parse_value (str_value, values, DS_TYPE_DERIVE);
+  if (status != 0)
+  {
+    ERROR ("protocols plugin: Parsing string as integer failed: %s",
+        str_value);
+    return;
+  }
+
+  vl.values = values;
+  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));
+  sstrncpy (vl.type_instance, str_key, sizeof (vl.type_instance));
+
+  plugin_dispatch_values (&vl);
+} /* void submit */
+
+static int read_file (const char *path)
+{
+  FILE *fh;
+  char key_buffer[4096];
+  char value_buffer[4096];
+  char *key_ptr;
+  char *value_ptr;
+  char *key_fields[256];
+  char *value_fields[256];
+  int key_fields_num;
+  int value_fields_num;
+  int status;
+  int i;
+
+  fh = fopen (path, "r");
+  if (fh == NULL)
+  {
+    ERROR ("protocols plugin: fopen (%s) failed: %s.",
+        path, sstrerror (errno, key_buffer, sizeof (key_buffer)));
+    return (-1);
+  }
+
+  status = -1;
+  while (42)
+  {
+    clearerr (fh);
+    key_ptr = fgets (key_buffer, sizeof (key_buffer), fh);
+    if (key_ptr == NULL)
+    {
+      if (feof (fh) != 0)
+      {
+        status = 0;
+        break;
+      }
+      else if (ferror (fh) != 0)
+      {
+        ERROR ("protocols plugin: Reading from %s failed.", path);
+        break;
+      }
+      else
+      {
+        ERROR ("protocols plugin: fgets failed for an unknown reason.");
+        break;
+      }
+    } /* if (key_ptr == NULL) */
+
+    value_ptr = fgets (value_buffer, sizeof (value_buffer), fh);
+    if (value_ptr == NULL)
+    {
+      ERROR ("protocols plugin: read_file (%s): Could not read values line.",
+          path);
+      break;
+    }
+
+    key_ptr = strchr (key_buffer, ':');
+    if (key_ptr == NULL)
+    {
+      ERROR ("protocols plugin: Could not find protocol name in keys line.");
+      break;
+    }
+    *key_ptr = 0;
+    key_ptr++;
+
+    value_ptr = strchr (value_buffer, ':');
+    if (value_ptr == NULL)
+    {
+      ERROR ("protocols plugin: Could not find protocol name "
+          "in values line.");
+      break;
+    }
+    *value_ptr = 0;
+    value_ptr++;
+
+    if (strcmp (key_buffer, value_buffer) != 0)
+    {
+      ERROR ("protocols plugin: Protocol names in keys and values lines "
+          "don't match: `%s' vs. `%s'.",
+          key_buffer, value_buffer);
+      break;
+    }
+
+
+    key_fields_num = strsplit (key_ptr,
+        key_fields, STATIC_ARRAY_SIZE (key_fields));
+    value_fields_num = strsplit (value_ptr,
+        value_fields, STATIC_ARRAY_SIZE (value_fields));
+
+    if (key_fields_num != value_fields_num)
+    {
+      ERROR ("protocols plugin: Number of fields in keys and values lines "
+          "don't match: %i vs %i.",
+          key_fields_num, value_fields_num);
+      break;
+    }
+
+    for (i = 0; i < key_fields_num; i++)
+    {
+      if (values_list != NULL)
+      {
+        char match_name[2 * DATA_MAX_NAME_LEN];
+
+        ssnprintf (match_name, sizeof (match_name), "%s:%s",
+            key_buffer, key_fields[i]);
+
+        if (ignorelist_match (values_list, match_name))
+          continue;
+      } /* if (values_list != NULL) */
+
+      submit (key_buffer, key_fields[i], value_fields[i]);
+    } /* for (i = 0; i < key_fields_num; i++) */
+  } /* while (42) */
+
+  fclose (fh);
+
+  return (status);
+} /* int read_file */
+
+static int protocols_read (void)
+{
+  int status;
+  int success = 0;
+
+  status = read_file (SNMP_FILE);
+  if (status == 0)
+    success++;
+
+  status = read_file (NETSTAT_FILE);
+  if (status == 0)
+    success++;
+
+  if (success == 0)
+    return (-1);
+
+  return (0);
+} /* int protocols_read */
+
+static int protocols_config (const char *key, const char *value)
+{
+  if (values_list == NULL)
+    values_list = ignorelist_create (/* invert = */ 1);
+
+  if (strcasecmp (key, "Value") == 0)
+  {
+    ignorelist_add (values_list, value);
+  }
+  else if (strcasecmp (key, "IgnoreSelected") == 0)
+  {
+    int invert = 1;
+    if (IS_TRUE (value))
+      invert = 0;
+    ignorelist_set_invert (values_list, invert);
+  }
+  else
+  {
+    return (-1);
+  }
+
+  return (0);
+} /* int protocols_config */
+
+void module_register (void)
+{
+  plugin_register_config ("protocols", protocols_config,
+      config_keys, config_keys_num);
+  plugin_register_read ("protocols", protocols_read);
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 et : */
diff --git a/src/pyconfig.c b/src/pyconfig.c
new file mode 100644 (file)
index 0000000..b5c01aa
--- /dev/null
@@ -0,0 +1,217 @@
+/**
+ * collectd - src/pyconfig.c
+ * Copyright (C) 2009  Sven Trenkel
+ *
+ * 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:
+ *   Sven Trenkel <collectd at semidefinite.de>  
+ **/
+
+#include <Python.h>
+#include <structmember.h>
+
+#include "collectd.h"
+#include "common.h"
+
+#include "cpython.h"
+
+static char config_doc[] = "This represents a piece of collectd's config file.\n"
+               "It is passed to scripts with config callbacks (see \"register_config\")\n"
+               "and is of little use if created somewhere else.\n"
+               "\n"
+               "It has no methods beyond the bare minimum and only exists for its\n"
+               "data members";
+
+static char parent_doc[] = "This represents the parent of this node. On the root node\n"
+               "of the config tree it will be None.\n";
+
+static char key_doc[] = "This is the keyword of this item, ie the first word of any\n"
+               "given line in the config file. It will always be a string.\n";
+
+static char values_doc[] = "This is a tuple (which might be empty) of all value, ie words\n"
+               "following the keyword in any given line in the config file.\n"
+               "\n"
+               "Every item in this tuple will be either a string or a float or a bool,\n"
+               "depending on the contents of the configuration file.\n";
+
+static char children_doc[] = "This is a tuple of child nodes. For most nodes this will be\n"
+               "empty. If this node represents a block instead of a single line of the config\n"
+               "file it will contain all nodes in this block.\n";
+
+static PyObject *Config_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
+       Config *self;
+       
+       self = (Config *) type->tp_alloc(type, 0);
+       if (self == NULL)
+               return NULL;
+       
+       self->parent = NULL;
+       self->key = NULL;
+       self->values = NULL;
+       self->children = NULL;
+       return (PyObject *) self;
+}
+
+static int Config_init(PyObject *s, PyObject *args, PyObject *kwds) {
+       PyObject *key = NULL, *parent = NULL, *values = NULL, *children = NULL, *tmp;
+       Config *self = (Config *) s;
+       static char *kwlist[] = {"key", "parent", "values", "children", NULL};
+       
+       if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OOO", kwlist,
+                       &key, &parent, &values, &children))
+               return -1;
+       
+       if (!IS_BYTES_OR_UNICODE(key)) {
+               PyErr_SetString(PyExc_TypeError, "argument 1 must be str");
+               Py_XDECREF(parent);
+               Py_XDECREF(values);
+               Py_XDECREF(children);
+               return -1;
+       }
+       if (values == NULL) {
+               values = PyTuple_New(0);
+               PyErr_Clear();
+       }
+       if (children == NULL) {
+               children = PyTuple_New(0);
+               PyErr_Clear();
+       }
+       tmp = self->key;
+       Py_INCREF(key);
+       self->key = key;
+       Py_XDECREF(tmp);
+       if (parent != NULL) {
+               tmp = self->parent;
+               Py_INCREF(parent);
+               self->parent = parent;
+               Py_XDECREF(tmp);
+       }
+       if (values != NULL) {
+               tmp = self->values;
+               Py_INCREF(values);
+               self->values = values;
+               Py_XDECREF(tmp);
+       }
+       if (children != NULL) {
+               tmp = self->children;
+               Py_INCREF(children);
+               self->children = children;
+               Py_XDECREF(tmp);
+       }
+       return 0;
+}
+
+static PyObject *Config_repr(PyObject *s) {
+       Config *self = (Config *) s;
+       PyObject *ret = NULL;
+       static PyObject *node_prefix = NULL, *root_prefix = NULL, *ending = NULL;
+       
+       /* This is ok because we have the GIL, so this is thread-save by default. */
+       if (node_prefix == NULL)
+               node_prefix = cpy_string_to_unicode_or_bytes("<collectd.Config node ");
+       if (root_prefix == NULL)
+               root_prefix = cpy_string_to_unicode_or_bytes("<collectd.Config root node ");
+       if (ending == NULL)
+               ending = cpy_string_to_unicode_or_bytes(">");
+       if (node_prefix == NULL || root_prefix == NULL || ending == NULL)
+               return NULL;
+       
+       ret = PyObject_Str(self->key);
+       CPY_SUBSTITUTE(PyObject_Repr, ret, ret);
+       if (self->parent == NULL || self->parent == Py_None)
+               CPY_STRCAT(&ret, root_prefix);
+       else
+               CPY_STRCAT(&ret, node_prefix);
+       CPY_STRCAT(&ret, ending);
+       
+       return ret;
+}
+
+static int Config_traverse(PyObject *self, visitproc visit, void *arg) {
+       Config *c = (Config *) self;
+       Py_VISIT(c->parent);
+       Py_VISIT(c->key);
+       Py_VISIT(c->values);
+       Py_VISIT(c->children);
+       return 0;}
+
+static int Config_clear(PyObject *self) {
+       Config *c = (Config *) self;
+       Py_CLEAR(c->parent);
+       Py_CLEAR(c->key);
+       Py_CLEAR(c->values);
+       Py_CLEAR(c->children);
+       return 0;
+}
+
+static void Config_dealloc(PyObject *self) {
+       Config_clear(self);
+       self->ob_type->tp_free(self);
+}
+
+static PyMemberDef Config_members[] = {
+       {"parent", T_OBJECT, offsetof(Config, parent), 0, parent_doc},
+       {"key", T_OBJECT_EX, offsetof(Config, key), 0, key_doc},
+       {"values", T_OBJECT_EX, offsetof(Config, values), 0, values_doc},
+       {"children", T_OBJECT_EX, offsetof(Config, children), 0, children_doc},
+       {NULL}
+};
+
+PyTypeObject ConfigType = {
+       CPY_INIT_TYPE
+       "collectd.Config",         /* tp_name */
+       sizeof(Config),            /* tp_basicsize */
+       0,                         /* Will be filled in later */
+       Config_dealloc,            /* tp_dealloc */
+       0,                         /* tp_print */
+       0,                         /* tp_getattr */
+       0,                         /* tp_setattr */
+       0,                         /* tp_compare */
+       Config_repr,               /* tp_repr */
+       0,                         /* tp_as_number */
+       0,                         /* tp_as_sequence */
+       0,                         /* tp_as_mapping */
+       0,                         /* tp_hash */
+       0,                         /* tp_call */
+       0,                         /* tp_str */
+       0,                         /* tp_getattro */
+       0,                         /* tp_setattro */
+       0,                         /* tp_as_buffer */
+       Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /*tp_flags*/
+       config_doc,                /* tp_doc */
+       Config_traverse,           /* tp_traverse */
+       Config_clear,              /* tp_clear */
+       0,                         /* tp_richcompare */
+       0,                         /* tp_weaklistoffset */
+       0,                         /* tp_iter */
+       0,                         /* tp_iternext */
+       0,                         /* tp_methods */
+       Config_members,            /* tp_members */
+       0,                         /* tp_getset */
+       0,                         /* tp_base */
+       0,                         /* tp_dict */
+       0,                         /* tp_descr_get */
+       0,                         /* tp_descr_set */
+       0,                         /* tp_dictoffset */
+       Config_init,               /* tp_init */
+       0,                         /* tp_alloc */
+       Config_new                 /* tp_new */
+};
+
diff --git a/src/python.c b/src/python.c
new file mode 100644 (file)
index 0000000..4a828b4
--- /dev/null
@@ -0,0 +1,1155 @@
+/**
+ * collectd - src/python.c
+ * Copyright (C) 2009  Sven Trenkel
+ *
+ * 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:
+ *   Sven Trenkel <collectd at semidefinite.de>  
+ **/
+
+#include <Python.h>
+#include <structmember.h>
+
+#include <signal.h>
+#if HAVE_PTHREAD_H
+# include <pthread.h>
+#endif
+
+#include "collectd.h"
+#include "common.h"
+
+#include "cpython.h"
+
+typedef struct cpy_callback_s {
+       char *name;
+       PyObject *callback;
+       PyObject *data;
+       struct cpy_callback_s *next;
+} cpy_callback_t;
+
+static char log_doc[] = "This function sends a string to all logging plugins.";
+
+static char flush_doc[] = "flush([plugin][, timeout][, identifier]) -> None\n"
+               "\n"
+               "Flushes the cache of another plugin.";
+
+static char unregister_doc[] = "Unregisters a callback. This function needs exactly one parameter either\n"
+               "the function to unregister or the callback identifier to unregister.";
+
+static char reg_log_doc[] = "register_log(callback[, data][, name]) -> identifier\n"
+               "\n"
+               "Register a callback function for log messages.\n"
+               "\n"
+               "'callback' is a callable object that will be called every time something\n"
+               "    is logged.\n"
+               "'data' is an optional object that will be passed back to the callback\n"
+               "    function every time it is called.\n"
+               "'name' is an optional identifier for this callback. The default name\n"
+               "    is 'python.<module>'.\n"
+               "    Every callback needs a unique identifier, so if you want to\n"
+               "    register this callback multiple time from the same module you need\n"
+               "    to specify a name here.\n"
+               "'identifier' is the full identifier assigned to this callback.\n"
+               "\n"
+               "The callback function will be called with two or three parameters:\n"
+               "severity: An integer that should be compared to the LOG_ constants.\n"
+               "message: The text to be logged.\n"
+               "data: The optional data parameter passed to the register function.\n"
+               "    If the parameter was omitted it will be omitted here, too.";
+
+static char reg_init_doc[] = "register_init(callback[, data][, name]) -> identifier\n"
+               "\n"
+               "Register a callback function that will be executed once after the config.\n"
+               "file has been read, all plugins heve been loaded and the collectd has\n"
+               "forked into the background.\n"
+               "\n"
+               "'callback' is a callable object that will be executed.\n"
+               "'data' is an optional object that will be passed back to the callback\n"
+               "    function when it is called.\n"
+               "'name' is an optional identifier for this callback. The default name\n"
+               "    is 'python.<module>'.\n"
+               "    Every callback needs a unique identifier, so if you want to\n"
+               "    register this callback multiple time from the same module you need\n"
+               "    to specify a name here.\n"
+               "'identifier' is the full identifier assigned to this callback.\n"
+               "\n"
+               "The callback function will be called without parameters, except for\n"
+               "data if it was supplied.";
+
+static char reg_config_doc[] = "register_config(callback[, data][, name]) -> identifier\n"
+               "\n"
+               "Register a callback function for config file entries.\n"
+               "'callback' is a callable object that will be called for every config block.\n"
+               "'data' is an optional object that will be passed back to the callback\n"
+               "    function every time it is called.\n"
+               "'name' is an optional identifier for this callback. The default name\n"
+               "    is 'python.<module>'.\n"
+               "    Every callback needs a unique identifier, so if you want to\n"
+               "    register this callback multiple time from the same module you need\n"
+               "    to specify a name here.\n"
+               "'identifier' is the full identifier assigned to this callback.\n"
+               "\n"
+               "The callback function will be called with one or two parameters:\n"
+               "config: A Config object.\n"
+               "data: The optional data parameter passed to the register function.\n"
+               "    If the parameter was omitted it will be omitted here, too.";
+
+static char reg_read_doc[] = "register_read(callback[, interval][, data][, name]) -> identifier\n"
+               "\n"
+               "Register a callback function for reading data. It will just be called\n"
+               "in a fixed interval to signal that it's time to dispatch new values.\n"
+               "'callback' is a callable object that will be called every time something\n"
+               "    is logged.\n"
+               "'interval' is the number of seconds between between calls to the callback\n"
+               "    function. Full float precision is supported here.\n"
+               "'data' is an optional object that will be passed back to the callback\n"
+               "    function every time it is called.\n"
+               "'name' is an optional identifier for this callback. The default name\n"
+               "    is 'python.<module>'.\n"
+               "    Every callback needs a unique identifier, so if you want to\n"
+               "    register this callback multiple time from the same module you need\n"
+               "    to specify a name here.\n"
+               "'identifier' is the full identifier assigned to this callback.\n"
+               "\n"
+               "The callback function will be called without parameters, except for\n"
+               "data if it was supplied.";
+
+static char reg_write_doc[] = "register_write(callback[, data][, name]) -> identifier\n"
+               "\n"
+               "Register a callback function to receive values dispatched by other plugins.\n"
+               "'callback' is a callable object that will be called every time a value\n"
+               "    is dispatched.\n"
+               "'data' is an optional object that will be passed back to the callback\n"
+               "    function every time it is called.\n"
+               "'name' is an optional identifier for this callback. The default name\n"
+               "    is 'python.<module>'.\n"
+               "    Every callback needs a unique identifier, so if you want to\n"
+               "    register this callback multiple time from the same module you need\n"
+               "    to specify a name here.\n"
+               "'identifier' is the full identifier assigned to this callback.\n"
+               "\n"
+               "The callback function will be called with one or two parameters:\n"
+               "values: A Values object which is a copy of the dispatched values.\n"
+               "data: The optional data parameter passed to the register function.\n"
+               "    If the parameter was omitted it will be omitted here, too.";
+
+static char reg_notification_doc[] = "register_notification(callback[, data][, name]) -> identifier\n"
+               "\n"
+               "Register a callback function for notifications.\n"
+               "'callback' is a callable object that will be called every time a notification\n"
+               "    is dispatched.\n"
+               "'data' is an optional object that will be passed back to the callback\n"
+               "    function every time it is called.\n"
+               "'name' is an optional identifier for this callback. The default name\n"
+               "    is 'python.<module>'.\n"
+               "    Every callback needs a unique identifier, so if you want to\n"
+               "    register this callback multiple time from the same module you need\n"
+               "    to specify a name here.\n"
+               "'identifier' is the full identifier assigned to this callback.\n"
+               "\n"
+               "The callback function will be called with one or two parameters:\n"
+               "notification: A copy of the notification that was dispatched.\n"
+               "data: The optional data parameter passed to the register function.\n"
+               "    If the parameter was omitted it will be omitted here, too.";
+
+static char reg_flush_doc[] = "register_flush(callback[, data][, name]) -> identifier\n"
+               "\n"
+               "Register a callback function for flush messages.\n"
+               "'callback' is a callable object that will be called every time a plugin\n"
+               "    requests a flush for either this or all plugins.\n"
+               "'data' is an optional object that will be passed back to the callback\n"
+               "    function every time it is called.\n"
+               "'name' is an optional identifier for this callback. The default name\n"
+               "    is 'python.<module>'.\n"
+               "    Every callback needs a unique identifier, so if you want to\n"
+               "    register this callback multiple time from the same module you need\n"
+               "    to specify a name here.\n"
+               "'identifier' is the full identifier assigned to this callback.\n"
+               "\n"
+               "The callback function will be called with two or three parameters:\n"
+               "timeout: Indicates that only data older than 'timeout' seconds is to\n"
+               "    be flushed.\n"
+               "id: Specifies which values are to be flushed.\n"
+               "data: The optional data parameter passed to the register function.\n"
+               "    If the parameter was omitted it will be omitted here, too.";
+
+static char reg_shutdown_doc[] = "register_shutdown(callback[, data][, name]) -> identifier\n"
+               "\n"
+               "Register a callback function for collectd shutdown.\n"
+               "'callback' is a callable object that will be called once collectd is\n"
+               "    shutting down.\n"
+               "'data' is an optional object that will be passed back to the callback\n"
+               "    function if it is called.\n"
+               "'name' is an optional identifier for this callback. The default name\n"
+               "    is 'python.<module>'.\n"
+               "    Every callback needs a unique identifier, so if you want to\n"
+               "    register this callback multiple time from the same module you need\n"
+               "    to specify a name here.\n"
+               "'identifier' is the full identifier assigned to this callback.\n"
+               "\n"
+               "The callback function will be called with no parameters except for\n"
+               "    data if it was supplied.";
+
+
+static int do_interactive = 0;
+
+/* This is our global thread state. Python saves some stuff in thread-local
+ * storage. So if we allow the interpreter to run in the background
+ * (the scriptwriters might have created some threads from python), we have
+ * to save the state so we can resume it later after shutdown. */
+
+static PyThreadState *state;
+
+static PyObject *sys_path, *cpy_format_exception;
+
+static cpy_callback_t *cpy_config_callbacks;
+static cpy_callback_t *cpy_init_callbacks;
+static cpy_callback_t *cpy_shutdown_callbacks;
+
+static void cpy_destroy_user_data(void *data) {
+       cpy_callback_t *c = data;
+       free(c->name);
+       Py_DECREF(c->callback);
+       Py_XDECREF(c->data);
+       free(c);
+}
+
+/* You must hold the GIL to call this function!
+ * But if you managed to extract the callback parameter then you probably already do. */
+
+static void cpy_build_name(char *buf, size_t size, PyObject *callback, const char *name) {
+       const char *module = NULL;
+       PyObject *mod = NULL;
+       
+       if (name != NULL) {
+               snprintf(buf, size, "python.%s", name);
+               return;
+       }
+       
+       mod = PyObject_GetAttrString(callback, "__module__"); /* New reference. */
+       if (mod != NULL)
+               module = cpy_unicode_or_bytes_to_string(&mod);
+       
+       if (module != NULL) {
+               snprintf(buf, size, "python.%s", module);
+               Py_XDECREF(mod);
+               PyErr_Clear();
+               return;
+       }
+       Py_XDECREF(mod);
+       
+       snprintf(buf, size, "python.%p", callback);
+       PyErr_Clear();
+}
+
+void cpy_log_exception(const char *context) {
+       int l = 0, i;
+       const char *typename = NULL, *message = NULL;
+       PyObject *type, *value, *traceback, *tn, *m, *list;
+       
+       PyErr_Fetch(&type, &value, &traceback);
+       PyErr_NormalizeException(&type, &value, &traceback);
+       if (type == NULL) return;
+       tn = PyObject_GetAttrString(type, "__name__"); /* New reference. */
+       m = PyObject_Str(value); /* New reference. */
+       if (tn != NULL)
+               typename = cpy_unicode_or_bytes_to_string(&tn);
+       if (m != NULL)
+               message = cpy_unicode_or_bytes_to_string(&m);
+       if (typename == NULL)
+               typename = "NamelessException";
+       if (message == NULL)
+               message = "N/A";
+       Py_BEGIN_ALLOW_THREADS
+       ERROR("Unhandled python exception in %s: %s: %s", context, typename, message);
+       Py_END_ALLOW_THREADS
+       Py_XDECREF(tn);
+       Py_XDECREF(m);
+       if (!cpy_format_exception) {
+               PyErr_Clear();
+               Py_XDECREF(type);
+               Py_XDECREF(value);
+               Py_XDECREF(traceback);
+               return;
+       }
+       if (!traceback) {
+               PyErr_Clear();
+               return;
+       }
+       list = PyObject_CallFunction(cpy_format_exception, "NNN", type, value, traceback); /* New reference. */
+       if (list)
+               l = PyObject_Length(list);
+       for (i = 0; i < l; ++i) {
+               char *s;
+               PyObject *line;
+               
+               line = PyList_GET_ITEM(list, i); /* Borrowed reference. */
+               Py_INCREF(line);
+               s = strdup(cpy_unicode_or_bytes_to_string(&line));
+               Py_DECREF(line);
+               if (s[strlen(s) - 1] == '\n')
+                       s[strlen(s) - 1] = 0;
+               Py_BEGIN_ALLOW_THREADS
+               ERROR("%s", s);
+               Py_END_ALLOW_THREADS
+               free(s);
+       }
+       Py_XDECREF(list);
+       PyErr_Clear();
+}
+
+static int cpy_read_callback(user_data_t *data) {
+       cpy_callback_t *c = data->data;
+       PyObject *ret;
+
+       CPY_LOCK_THREADS
+               ret = PyObject_CallFunctionObjArgs(c->callback, c->data, (void *) 0); /* New reference. */
+               if (ret == NULL) {
+                       cpy_log_exception("read callback");
+               } else {
+                       Py_DECREF(ret);
+               }
+       CPY_RELEASE_THREADS
+       if (ret == NULL)
+               return 1;
+       return 0;
+}
+
+static int cpy_write_callback(const data_set_t *ds, const value_list_t *value_list, user_data_t *data) {
+       int i;
+       cpy_callback_t *c = data->data;
+       PyObject *ret, *list, *temp, *dict = NULL, *val;
+       Values *v;
+
+       CPY_LOCK_THREADS
+               list = PyList_New(value_list->values_len); /* New reference. */
+               if (list == NULL) {
+                       cpy_log_exception("write callback");
+                       CPY_RETURN_FROM_THREADS 0;
+               }
+               for (i = 0; i < value_list->values_len; ++i) {
+                       if (ds->ds[i].type == DS_TYPE_COUNTER) {
+                               if ((long) value_list->values[i].counter == value_list->values[i].counter)
+                                       PyList_SetItem(list, i, PyInt_FromLong(value_list->values[i].counter));
+                               else
+                                       PyList_SetItem(list, i, PyLong_FromUnsignedLongLong(value_list->values[i].counter));
+                       } else if (ds->ds[i].type == DS_TYPE_GAUGE) {
+                               PyList_SetItem(list, i, PyFloat_FromDouble(value_list->values[i].gauge));
+                       } else if (ds->ds[i].type == DS_TYPE_DERIVE) {
+                               if ((long) value_list->values[i].derive == value_list->values[i].derive)
+                                       PyList_SetItem(list, i, PyInt_FromLong(value_list->values[i].derive));
+                               else
+                                       PyList_SetItem(list, i, PyLong_FromLongLong(value_list->values[i].derive));
+                       } else if (ds->ds[i].type == DS_TYPE_ABSOLUTE) {
+                               if ((long) value_list->values[i].absolute == value_list->values[i].absolute)
+                                       PyList_SetItem(list, i, PyInt_FromLong(value_list->values[i].absolute));
+                               else
+                                       PyList_SetItem(list, i, PyLong_FromUnsignedLongLong(value_list->values[i].absolute));
+                       } else {
+                               Py_BEGIN_ALLOW_THREADS
+                               ERROR("cpy_write_callback: Unknown value type %d.", ds->ds[i].type);
+                               Py_END_ALLOW_THREADS
+                               Py_DECREF(list);
+                               CPY_RETURN_FROM_THREADS 0;
+                       }
+                       if (PyErr_Occurred() != NULL) {
+                               cpy_log_exception("value building for write callback");
+                               Py_DECREF(list);
+                               CPY_RETURN_FROM_THREADS 0;
+                       }
+               }
+               dict = PyDict_New();
+               if (value_list->meta) {
+                       int i, num;
+                       char **table;
+                       meta_data_t *meta = value_list->meta;
+
+                       num = meta_data_toc(meta, &table);
+                       for (i = 0; i < num; ++i) {
+                               int type;
+                               char *string;
+                               int64_t si;
+                               uint64_t ui;
+                               double d;
+                               _Bool b;
+                               
+                               type = meta_data_type(meta, table[i]);
+                               if (type == MD_TYPE_STRING) {
+                                       if (meta_data_get_string(meta, table[i], &string))
+                                               continue;
+                                       temp = cpy_string_to_unicode_or_bytes(string);
+                                       free(string);
+                                       PyDict_SetItemString(dict, table[i], temp);
+                                       Py_XDECREF(temp);
+                               } else if (type == MD_TYPE_SIGNED_INT) {
+                                       if (meta_data_get_signed_int(meta, table[i], &si))
+                                               continue;
+                                       temp = PyObject_CallFunctionObjArgs((void *) &SignedType, PyLong_FromLongLong(si), (void *) 0);
+                                       PyDict_SetItemString(dict, table[i], temp);
+                                       Py_XDECREF(temp);
+                               } else if (type == MD_TYPE_UNSIGNED_INT) {
+                                       if (meta_data_get_unsigned_int(meta, table[i], &ui))
+                                               continue;
+                                       temp = PyObject_CallFunctionObjArgs((void *) &UnsignedType, PyLong_FromUnsignedLongLong(ui), (void *) 0);
+                                       PyDict_SetItemString(dict, table[i], temp);
+                                       Py_XDECREF(temp);
+                               } else if (type == MD_TYPE_DOUBLE) {
+                                       if (meta_data_get_double(meta, table[i], &d))
+                                               continue;
+                                       temp = PyFloat_FromDouble(d);
+                                       PyDict_SetItemString(dict, table[i], temp);
+                                       Py_XDECREF(temp);
+                               } else if (type == MD_TYPE_BOOLEAN) {
+                                       if (meta_data_get_boolean(meta, table[i], &b))
+                                               continue;
+                                       if (b)
+                                               PyDict_SetItemString(dict, table[i], Py_True);
+                                       else
+                                               PyDict_SetItemString(dict, table[i], Py_False);
+                               }
+                               free(table[i]);
+                       }
+                       free(table);
+               }
+               val = Values_New(); /* New reference. */
+               v = (Values *) val; 
+               sstrncpy(v->data.host, value_list->host, sizeof(v->data.host));
+               sstrncpy(v->data.type, value_list->type, sizeof(v->data.type));
+               sstrncpy(v->data.type_instance, value_list->type_instance, sizeof(v->data.type_instance));
+               sstrncpy(v->data.plugin, value_list->plugin, sizeof(v->data.plugin));
+               sstrncpy(v->data.plugin_instance, value_list->plugin_instance, sizeof(v->data.plugin_instance));
+               v->data.time = CDTIME_T_TO_DOUBLE(value_list->time);
+               v->interval = CDTIME_T_TO_DOUBLE(value_list->interval);
+               Py_CLEAR(v->values);
+               v->values = list;
+               Py_CLEAR(v->meta);
+               v->meta = dict;
+               ret = PyObject_CallFunctionObjArgs(c->callback, v, c->data, (void *) 0); /* New reference. */
+               Py_XDECREF(val);
+               if (ret == NULL) {
+                       cpy_log_exception("write callback");
+               } else {
+                       Py_DECREF(ret);
+               }
+       CPY_RELEASE_THREADS
+       return 0;
+}
+
+static int cpy_notification_callback(const notification_t *notification, user_data_t *data) {
+       cpy_callback_t *c = data->data;
+       PyObject *ret, *notify;
+       Notification *n;
+
+       CPY_LOCK_THREADS
+               notify = Notification_New(); /* New reference. */
+               n = (Notification *) notify;
+               sstrncpy(n->data.host, notification->host, sizeof(n->data.host));
+               sstrncpy(n->data.type, notification->type, sizeof(n->data.type));
+               sstrncpy(n->data.type_instance, notification->type_instance, sizeof(n->data.type_instance));
+               sstrncpy(n->data.plugin, notification->plugin, sizeof(n->data.plugin));
+               sstrncpy(n->data.plugin_instance, notification->plugin_instance, sizeof(n->data.plugin_instance));
+               n->data.time = CDTIME_T_TO_DOUBLE(notification->time);
+               sstrncpy(n->message, notification->message, sizeof(n->message));
+               n->severity = notification->severity;
+               ret = PyObject_CallFunctionObjArgs(c->callback, n, c->data, (void *) 0); /* New reference. */
+               Py_XDECREF(notify);
+               if (ret == NULL) {
+                       cpy_log_exception("notification callback");
+               } else {
+                       Py_DECREF(ret);
+               }
+       CPY_RELEASE_THREADS
+       return 0;
+}
+
+static void cpy_log_callback(int severity, const char *message, user_data_t *data) {
+       cpy_callback_t * c = data->data;
+       PyObject *ret, *text;
+
+       CPY_LOCK_THREADS
+       text = cpy_string_to_unicode_or_bytes(message);
+       if (c->data == NULL)
+               ret = PyObject_CallFunction(c->callback, "iN", severity, text); /* New reference. */
+       else
+               ret = PyObject_CallFunction(c->callback, "iNO", severity, text, c->data); /* New reference. */
+
+       if (ret == NULL) {
+               /* FIXME */
+               /* Do we really want to trigger a log callback because a log callback failed?
+                * Probably not. */
+               PyErr_Print();
+               /* In case someone wanted to be clever, replaced stderr and failed at that. */
+               PyErr_Clear();
+       } else {
+               Py_DECREF(ret);
+       }
+       CPY_RELEASE_THREADS
+}
+
+static void cpy_flush_callback(int timeout, const char *id, user_data_t *data) {
+       cpy_callback_t * c = data->data;
+       PyObject *ret, *text;
+
+       CPY_LOCK_THREADS
+       text = cpy_string_to_unicode_or_bytes(id);
+       if (c->data == NULL)
+               ret = PyObject_CallFunction(c->callback, "iN", timeout, text); /* New reference. */
+       else
+               ret = PyObject_CallFunction(c->callback, "iNO", timeout, text, c->data); /* New reference. */
+
+       if (ret == NULL) {
+               cpy_log_exception("flush callback");
+       } else {
+               Py_DECREF(ret);
+       }
+       CPY_RELEASE_THREADS
+}
+
+static PyObject *cpy_register_generic(cpy_callback_t **list_head, PyObject *args, PyObject *kwds) {
+       char buf[512];
+       cpy_callback_t *c;
+       const char *name = NULL;
+       PyObject *callback = NULL, *data = NULL, *mod = NULL;
+       static char *kwlist[] = {"callback", "data", "name", NULL};
+       
+       if (PyArg_ParseTupleAndKeywords(args, kwds, "O|Oet", kwlist, &callback, &data, NULL, &name) == 0) return NULL;
+       if (PyCallable_Check(callback) == 0) {
+               PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object.");
+               return NULL;
+       }
+       cpy_build_name(buf, sizeof(buf), callback, name);
+
+       Py_INCREF(callback);
+       Py_XINCREF(data);
+       c = malloc(sizeof(*c));
+       c->name = strdup(buf);
+       c->callback = callback;
+       c->data = data;
+       c->next = *list_head;
+       *list_head = c;
+       Py_XDECREF(mod);
+       return cpy_string_to_unicode_or_bytes(buf);
+}
+
+static PyObject *cpy_flush(cpy_callback_t **list_head, PyObject *args, PyObject *kwds) {
+       int timeout = -1;
+       const char *plugin = NULL, *identifier = NULL;
+       static char *kwlist[] = {"plugin", "timeout", "identifier", NULL};
+       
+       if (PyArg_ParseTupleAndKeywords(args, kwds, "|etiet", kwlist, NULL, &plugin, &timeout, NULL, &identifier) == 0) return NULL;
+       Py_BEGIN_ALLOW_THREADS
+       plugin_flush(plugin, timeout, identifier);
+       Py_END_ALLOW_THREADS
+       Py_RETURN_NONE;
+}
+
+static PyObject *cpy_register_config(PyObject *self, PyObject *args, PyObject *kwds) {
+       return cpy_register_generic(&cpy_config_callbacks, args, kwds);
+}
+
+static PyObject *cpy_register_init(PyObject *self, PyObject *args, PyObject *kwds) {
+       return cpy_register_generic(&cpy_init_callbacks, args, kwds);
+}
+
+typedef int reg_function_t(const char *name, void *callback, void *data);
+
+static PyObject *cpy_register_generic_userdata(void *reg, void *handler, PyObject *args, PyObject *kwds) {
+       char buf[512];
+       reg_function_t *register_function = (reg_function_t *) reg;
+       cpy_callback_t *c = NULL;
+       user_data_t *user_data = NULL;
+       const char *name = NULL;
+       PyObject *callback = NULL, *data = NULL;
+       static char *kwlist[] = {"callback", "data", "name", NULL};
+       
+       if (PyArg_ParseTupleAndKeywords(args, kwds, "O|Oet", kwlist, &callback, &data, NULL, &name) == 0) return NULL;
+       if (PyCallable_Check(callback) == 0) {
+               PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object.");
+               return NULL;
+       }
+       cpy_build_name(buf, sizeof(buf), callback, name);
+       
+       Py_INCREF(callback);
+       Py_XINCREF(data);
+       c = malloc(sizeof(*c));
+       c->name = strdup(buf);
+       c->callback = callback;
+       c->data = data;
+       c->next = NULL;
+       user_data = malloc(sizeof(*user_data));
+       user_data->free_func = cpy_destroy_user_data;
+       user_data->data = c;
+       register_function(buf, handler, user_data);
+       return cpy_string_to_unicode_or_bytes(buf);
+}
+
+static PyObject *cpy_register_read(PyObject *self, PyObject *args, PyObject *kwds) {
+       char buf[512];
+       cpy_callback_t *c = NULL;
+       user_data_t *user_data = NULL;
+       double interval = 0;
+       const char *name = NULL;
+       PyObject *callback = NULL, *data = NULL;
+       struct timespec ts;
+       static char *kwlist[] = {"callback", "interval", "data", "name", NULL};
+       
+       if (PyArg_ParseTupleAndKeywords(args, kwds, "O|dOet", kwlist, &callback, &interval, &data, NULL, &name) == 0) return NULL;
+       if (PyCallable_Check(callback) == 0) {
+               PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object.");
+               return NULL;
+       }
+       cpy_build_name(buf, sizeof(buf), callback, name);
+       
+       Py_INCREF(callback);
+       Py_XINCREF(data);
+       c = malloc(sizeof(*c));
+       c->name = strdup(buf);
+       c->callback = callback;
+       c->data = data;
+       c->next = NULL;
+       user_data = malloc(sizeof(*user_data));
+       user_data->free_func = cpy_destroy_user_data;
+       user_data->data = c;
+       ts.tv_sec = interval;
+       ts.tv_nsec = (interval - ts.tv_sec) * 1000000000;
+       plugin_register_complex_read(/* group = */ NULL, buf,
+                       cpy_read_callback, &ts, user_data);
+       return cpy_string_to_unicode_or_bytes(buf);
+}
+
+static PyObject *cpy_register_log(PyObject *self, PyObject *args, PyObject *kwds) {
+       return cpy_register_generic_userdata((void *) plugin_register_log,
+                       (void *) cpy_log_callback, args, kwds);
+}
+
+static PyObject *cpy_register_write(PyObject *self, PyObject *args, PyObject *kwds) {
+       return cpy_register_generic_userdata((void *) plugin_register_write,
+                       (void *) cpy_write_callback, args, kwds);
+}
+
+static PyObject *cpy_register_notification(PyObject *self, PyObject *args, PyObject *kwds) {
+       return cpy_register_generic_userdata((void *) plugin_register_notification,
+                       (void *) cpy_notification_callback, args, kwds);
+}
+
+static PyObject *cpy_register_flush(PyObject *self, PyObject *args, PyObject *kwds) {
+       return cpy_register_generic_userdata((void *) plugin_register_flush,
+                       (void *) cpy_flush_callback, args, kwds);
+}
+
+static PyObject *cpy_register_shutdown(PyObject *self, PyObject *args, PyObject *kwds) {
+       return cpy_register_generic(&cpy_shutdown_callbacks, args, kwds);
+}
+
+static PyObject *cpy_error(PyObject *self, PyObject *args) {
+       const char *text;
+       if (PyArg_ParseTuple(args, "et", NULL, &text) == 0) return NULL;
+       Py_BEGIN_ALLOW_THREADS
+       plugin_log(LOG_ERR, "%s", text);
+       Py_END_ALLOW_THREADS
+       Py_RETURN_NONE;
+}
+
+static PyObject *cpy_warning(PyObject *self, PyObject *args) {
+       const char *text;
+       if (PyArg_ParseTuple(args, "et", NULL, &text) == 0) return NULL;
+       Py_BEGIN_ALLOW_THREADS
+       plugin_log(LOG_WARNING, "%s", text);
+       Py_END_ALLOW_THREADS
+       Py_RETURN_NONE;
+}
+
+static PyObject *cpy_notice(PyObject *self, PyObject *args) {
+       const char *text;
+       if (PyArg_ParseTuple(args, "et", NULL, &text) == 0) return NULL;
+       Py_BEGIN_ALLOW_THREADS
+       plugin_log(LOG_NOTICE, "%s", text);
+       Py_END_ALLOW_THREADS
+       Py_RETURN_NONE;
+}
+
+static PyObject *cpy_info(PyObject *self, PyObject *args) {
+       const char *text;
+       if (PyArg_ParseTuple(args, "et", NULL, &text) == 0) return NULL;
+       Py_BEGIN_ALLOW_THREADS
+       plugin_log(LOG_INFO, "%s", text);
+       Py_END_ALLOW_THREADS
+       Py_RETURN_NONE;
+}
+
+static PyObject *cpy_debug(PyObject *self, PyObject *args) {
+#ifdef COLLECT_DEBUG
+       const char *text;
+       if (PyArg_ParseTuple(args, "et", NULL, &text) == 0) return NULL;
+       Py_BEGIN_ALLOW_THREADS
+       plugin_log(LOG_DEBUG, "%s", text);
+       Py_END_ALLOW_THREADS
+#endif
+       Py_RETURN_NONE;
+}
+
+static PyObject *cpy_unregister_generic(cpy_callback_t **list_head, PyObject *arg, const char *desc) {
+       char buf[512];
+       const char *name;
+       cpy_callback_t *prev = NULL, *tmp;
+
+       Py_INCREF(arg);
+       name = cpy_unicode_or_bytes_to_string(&arg);
+       if (name == NULL) {
+               PyErr_Clear();
+               if (!PyCallable_Check(arg)) {
+                       PyErr_SetString(PyExc_TypeError, "This function needs a string or a callable object as its only parameter.");
+                       Py_DECREF(arg);
+                       return NULL;
+               }
+               cpy_build_name(buf, sizeof(buf), arg, NULL);
+               name = buf;
+       }
+       for (tmp = *list_head; tmp; prev = tmp, tmp = tmp->next)
+               if (strcmp(name, tmp->name) == 0)
+                       break;
+       
+       Py_DECREF(arg);
+       if (tmp == NULL) {
+               PyErr_Format(PyExc_RuntimeError, "Unable to unregister %s callback '%s'.", desc, name);
+               return NULL;
+       }
+       /* Yes, this is actually save. To call this function the caller has to
+        * hold the GIL. Well, save as long as there is only one GIL anyway ... */
+       if (prev == NULL)
+               *list_head = tmp->next;
+       else
+               prev->next = tmp->next;
+       cpy_destroy_user_data(tmp);
+       Py_RETURN_NONE;
+}
+
+typedef int cpy_unregister_function_t(const char *name);
+
+static PyObject *cpy_unregister_generic_userdata(cpy_unregister_function_t *unreg, PyObject *arg, const char *desc) {
+       char buf[512];
+       const char *name;
+
+       Py_INCREF(arg);
+       name = cpy_unicode_or_bytes_to_string(&arg);
+       if (name == NULL) {
+               PyErr_Clear();
+               if (!PyCallable_Check(arg)) {
+                       PyErr_SetString(PyExc_TypeError, "This function needs a string or a callable object as its only parameter.");
+                       Py_DECREF(arg);
+                       return NULL;
+               }
+               cpy_build_name(buf, sizeof(buf), arg, NULL);
+               name = buf;
+       }
+       if (unreg(name) == 0) {
+               Py_DECREF(arg);
+               Py_RETURN_NONE;
+       }
+       PyErr_Format(PyExc_RuntimeError, "Unable to unregister %s callback '%s'.", desc, name);
+       Py_DECREF(arg);
+       return NULL;
+}
+
+static PyObject *cpy_unregister_log(PyObject *self, PyObject *arg) {
+       return cpy_unregister_generic_userdata(plugin_unregister_log, arg, "log");
+}
+
+static PyObject *cpy_unregister_init(PyObject *self, PyObject *arg) {
+       return cpy_unregister_generic(&cpy_init_callbacks, arg, "init");
+}
+
+static PyObject *cpy_unregister_config(PyObject *self, PyObject *arg) {
+       return cpy_unregister_generic(&cpy_config_callbacks, arg, "config");
+}
+
+static PyObject *cpy_unregister_read(PyObject *self, PyObject *arg) {
+       return cpy_unregister_generic_userdata(plugin_unregister_read, arg, "read");
+}
+
+static PyObject *cpy_unregister_write(PyObject *self, PyObject *arg) {
+       return cpy_unregister_generic_userdata(plugin_unregister_write, arg, "write");
+}
+
+static PyObject *cpy_unregister_notification(PyObject *self, PyObject *arg) {
+       return cpy_unregister_generic_userdata(plugin_unregister_notification, arg, "notification");
+}
+
+static PyObject *cpy_unregister_flush(PyObject *self, PyObject *arg) {
+       return cpy_unregister_generic_userdata(plugin_unregister_flush, arg, "flush");
+}
+
+static PyObject *cpy_unregister_shutdown(PyObject *self, PyObject *arg) {
+       return cpy_unregister_generic(&cpy_shutdown_callbacks, arg, "shutdown");
+}
+
+static PyMethodDef cpy_methods[] = {
+       {"debug", cpy_debug, METH_VARARGS, log_doc},
+       {"info", cpy_info, METH_VARARGS, log_doc},
+       {"notice", cpy_notice, METH_VARARGS, log_doc},
+       {"warning", cpy_warning, METH_VARARGS, log_doc},
+       {"error", cpy_error, METH_VARARGS, log_doc},
+       {"flush", (PyCFunction) cpy_flush, METH_VARARGS | METH_KEYWORDS, flush_doc},
+       {"register_log", (PyCFunction) cpy_register_log, METH_VARARGS | METH_KEYWORDS, reg_log_doc},
+       {"register_init", (PyCFunction) cpy_register_init, METH_VARARGS | METH_KEYWORDS, reg_init_doc},
+       {"register_config", (PyCFunction) cpy_register_config, METH_VARARGS | METH_KEYWORDS, reg_config_doc},
+       {"register_read", (PyCFunction) cpy_register_read, METH_VARARGS | METH_KEYWORDS, reg_read_doc},
+       {"register_write", (PyCFunction) cpy_register_write, METH_VARARGS | METH_KEYWORDS, reg_write_doc},
+       {"register_notification", (PyCFunction) cpy_register_notification, METH_VARARGS | METH_KEYWORDS, reg_notification_doc},
+       {"register_flush", (PyCFunction) cpy_register_flush, METH_VARARGS | METH_KEYWORDS, reg_flush_doc},
+       {"register_shutdown", (PyCFunction) cpy_register_shutdown, METH_VARARGS | METH_KEYWORDS, reg_shutdown_doc},
+       {"unregister_log", cpy_unregister_log, METH_O, unregister_doc},
+       {"unregister_init", cpy_unregister_init, METH_O, unregister_doc},
+       {"unregister_config", cpy_unregister_config, METH_O, unregister_doc},
+       {"unregister_read", cpy_unregister_read, METH_O, unregister_doc},
+       {"unregister_write", cpy_unregister_write, METH_O, unregister_doc},
+       {"unregister_notification", cpy_unregister_notification, METH_O, unregister_doc},
+       {"unregister_flush", cpy_unregister_flush, METH_O, unregister_doc},
+       {"unregister_shutdown", cpy_unregister_shutdown, METH_O, unregister_doc},
+       {0, 0, 0, 0}
+};
+
+static int cpy_shutdown(void) {
+       cpy_callback_t *c;
+       PyObject *ret;
+       
+       /* This can happen if the module was loaded but not configured. */
+       if (state != NULL)
+               PyEval_RestoreThread(state);
+
+       for (c = cpy_shutdown_callbacks; c; c = c->next) {
+               ret = PyObject_CallFunctionObjArgs(c->callback, c->data, (void *) 0); /* New reference. */
+               if (ret == NULL)
+                       cpy_log_exception("shutdown callback");
+               else
+                       Py_DECREF(ret);
+       }
+       PyErr_Print();
+       Py_Finalize();
+       return 0;
+}
+
+static void cpy_int_handler(int sig) {
+       return;
+}
+
+static void *cpy_interactive(void *data) {
+       sigset_t sigset;
+       struct sigaction sig_int_action, old;
+       
+       /* Signal handler in a plugin? Bad stuff, but the best way to
+        * handle it I guess. In an interactive session people will
+        * press Ctrl+C at some time, which will generate a SIGINT.
+        * This will cause collectd to shutdown, thus killing the
+        * interactive interpreter, and leaving the terminal in a
+        * mess. Chances are, this isn't what the user wanted to do.
+        * 
+        * So this is the plan:
+        * 1. Block SIGINT in the main thread.
+        * 2. Install our own signal handler that does nothing.
+        * 3. Unblock SIGINT in the interactive thread.
+        *
+        * This will make sure that SIGINT won't kill collectd but
+        * still interrupt syscalls like sleep and pause.
+        * It does not raise a KeyboardInterrupt exception because so
+        * far nobody managed to figure out how to do that. */
+       memset (&sig_int_action, '\0', sizeof (sig_int_action));
+       sig_int_action.sa_handler = cpy_int_handler;
+       sigaction (SIGINT, &sig_int_action, &old);
+       
+       sigemptyset(&sigset);
+       sigaddset(&sigset, SIGINT);
+       pthread_sigmask(SIG_UNBLOCK, &sigset, NULL);
+       PyEval_AcquireThread(state);
+       if (PyImport_ImportModule("readline") == NULL) {
+               /* This interactive session will suck. */
+               cpy_log_exception("interactive session init");
+       }
+       PyRun_InteractiveLoop(stdin, "<stdin>");
+       PyErr_Print();
+       PyEval_ReleaseThread(state);
+       NOTICE("python: Interactive interpreter exited, stopping collectd ...");
+       /* Restore the original collectd SIGINT handler and raise SIGINT.
+        * The main thread still has SIGINT blocked and there's nothing we
+        * can do about that so this thread will handle it. But that's not
+        * important, except that it won't interrupt the main loop and so
+        * it might take a few seconds before collectd really shuts down. */
+       sigaction (SIGINT, &old, NULL);
+       raise(SIGINT);
+       pause();
+       return NULL;
+}
+
+static int cpy_init(void) {
+       cpy_callback_t *c;
+       PyObject *ret;
+       static pthread_t thread;
+       sigset_t sigset;
+       
+       if (!Py_IsInitialized()) {
+               WARNING("python: Plugin loaded but not configured.");
+               plugin_unregister_shutdown("python");
+               return 0;
+       }
+       PyEval_InitThreads();
+       /* Now it's finally OK to use python threads. */
+       for (c = cpy_init_callbacks; c; c = c->next) {
+               ret = PyObject_CallFunctionObjArgs(c->callback, c->data, (void *) 0); /* New reference. */
+               if (ret == NULL)
+                       cpy_log_exception("init callback");
+               else
+                       Py_DECREF(ret);
+       }
+       sigemptyset(&sigset);
+       sigaddset(&sigset, SIGINT);
+       pthread_sigmask(SIG_BLOCK, &sigset, NULL);
+       state = PyEval_SaveThread();
+       if (do_interactive) {
+               if (pthread_create(&thread, NULL, cpy_interactive, NULL)) {
+                       ERROR("python: Error creating thread for interactive interpreter.");
+               }
+       }
+
+       return 0;
+}
+
+static PyObject *cpy_oconfig_to_pyconfig(oconfig_item_t *ci, PyObject *parent) {
+       int i;
+       PyObject *item, *values, *children, *tmp;
+       
+       if (parent == NULL)
+               parent = Py_None;
+       
+       values = PyTuple_New(ci->values_num); /* New reference. */
+       for (i = 0; i < ci->values_num; ++i) {
+               if (ci->values[i].type == OCONFIG_TYPE_STRING) {
+                       PyTuple_SET_ITEM(values, i, cpy_string_to_unicode_or_bytes(ci->values[i].value.string));
+               } else if (ci->values[i].type == OCONFIG_TYPE_NUMBER) {
+                       PyTuple_SET_ITEM(values, i, PyFloat_FromDouble(ci->values[i].value.number));
+               } else if (ci->values[i].type == OCONFIG_TYPE_BOOLEAN) {
+                       PyTuple_SET_ITEM(values, i, PyBool_FromLong(ci->values[i].value.boolean));
+               }
+       }
+       
+       tmp = cpy_string_to_unicode_or_bytes(ci->key);
+       item = PyObject_CallFunction((void *) &ConfigType, "NONO", tmp, parent, values, Py_None);
+       if (item == NULL)
+               return NULL;
+       children = PyTuple_New(ci->children_num); /* New reference. */
+       for (i = 0; i < ci->children_num; ++i) {
+               PyTuple_SET_ITEM(children, i, cpy_oconfig_to_pyconfig(ci->children + i, item));
+       }
+       tmp = ((Config *) item)->children;
+       ((Config *) item)->children = children;
+       Py_XDECREF(tmp);
+       return item;
+}
+
+#ifdef IS_PY3K
+static struct PyModuleDef collectdmodule = {
+       PyModuleDef_HEAD_INIT,
+       "collectd",   /* name of module */
+       "The python interface to collectd", /* module documentation, may be NULL */
+       -1,
+       cpy_methods
+};
+
+PyMODINIT_FUNC PyInit_collectd(void) {
+       return PyModule_Create(&collectdmodule);
+}
+#endif
+
+static int cpy_init_python() {
+       char *argv = "";
+       PyObject *sys;
+       PyObject *module;
+
+#ifdef IS_PY3K
+       /* Add a builtin module, before Py_Initialize */
+       PyImport_AppendInittab("collectd", PyInit_collectd);
+#endif
+       
+       Py_Initialize();
+       
+       PyType_Ready(&ConfigType);
+       PyType_Ready(&PluginDataType);
+       ValuesType.tp_base = &PluginDataType;
+       PyType_Ready(&ValuesType);
+       NotificationType.tp_base = &PluginDataType;
+       PyType_Ready(&NotificationType);
+       SignedType.tp_base = &PyLong_Type;
+       PyType_Ready(&SignedType);
+       UnsignedType.tp_base = &PyLong_Type;
+       PyType_Ready(&UnsignedType);
+       sys = PyImport_ImportModule("sys"); /* New reference. */
+       if (sys == NULL) {
+               cpy_log_exception("python initialization");
+               return 1;
+       }
+       sys_path = PyObject_GetAttrString(sys, "path"); /* New reference. */
+       Py_DECREF(sys);
+       if (sys_path == NULL) {
+               cpy_log_exception("python initialization");
+               return 1;
+       }
+       PySys_SetArgv(1, &argv);
+       PyList_SetSlice(sys_path, 0, 1, NULL);
+
+#ifdef IS_PY3K
+       module = PyImport_ImportModule("collectd");
+#else
+       module = Py_InitModule("collectd", cpy_methods); /* Borrowed reference. */
+#endif
+       PyModule_AddObject(module, "Config", (void *) &ConfigType); /* Steals a reference. */
+       PyModule_AddObject(module, "Values", (void *) &ValuesType); /* Steals a reference. */
+       PyModule_AddObject(module, "Notification", (void *) &NotificationType); /* Steals a reference. */
+       PyModule_AddObject(module, "Signed", (void *) &SignedType); /* Steals a reference. */
+       PyModule_AddObject(module, "Unsigned", (void *) &UnsignedType); /* Steals a reference. */
+       PyModule_AddIntConstant(module, "LOG_DEBUG", LOG_DEBUG);
+       PyModule_AddIntConstant(module, "LOG_INFO", LOG_INFO);
+       PyModule_AddIntConstant(module, "LOG_NOTICE", LOG_NOTICE);
+       PyModule_AddIntConstant(module, "LOG_WARNING", LOG_WARNING);
+       PyModule_AddIntConstant(module, "LOG_ERROR", LOG_ERR);
+       PyModule_AddIntConstant(module, "NOTIF_FAILURE", NOTIF_FAILURE);
+       PyModule_AddIntConstant(module, "NOTIF_WARNING", NOTIF_WARNING);
+       PyModule_AddIntConstant(module, "NOTIF_OKAY", NOTIF_OKAY);
+       return 0;
+}
+
+static int cpy_config(oconfig_item_t *ci) {
+       int i;
+       PyObject *tb;
+
+       /* Ok in theory we shouldn't do initialization at this point
+        * but we have to. In order to give python scripts a chance
+        * to register a config callback we need to be able to execute
+        * python code during the config callback so we have to start
+        * the interpreter here. */
+       /* Do *not* use the python "thread" module at this point! */
+
+       if (!Py_IsInitialized() && cpy_init_python()) return 1;
+
+       for (i = 0; i < ci->children_num; ++i) {
+               oconfig_item_t *item = ci->children + i;
+               
+               if (strcasecmp(item->key, "Interactive") == 0) {
+                       if (item->values_num != 1 || item->values[0].type != OCONFIG_TYPE_BOOLEAN)
+                               continue;
+                       do_interactive = item->values[0].value.boolean;
+               } else if (strcasecmp(item->key, "Encoding") == 0) {
+                       if (item->values_num != 1 || item->values[0].type != OCONFIG_TYPE_STRING)
+                               continue;
+                       /* Why is this even necessary? And undocumented? */
+                       if (PyUnicode_SetDefaultEncoding(item->values[0].value.string))
+                               cpy_log_exception("setting default encoding");
+               } else if (strcasecmp(item->key, "LogTraces") == 0) {
+                       if (item->values_num != 1 || item->values[0].type != OCONFIG_TYPE_BOOLEAN)
+                               continue;
+                       if (!item->values[0].value.boolean) {
+                               Py_XDECREF(cpy_format_exception);
+                               cpy_format_exception = NULL;
+                               continue;
+                       }
+                       if (cpy_format_exception)
+                               continue;
+                       tb = PyImport_ImportModule("traceback"); /* New reference. */
+                       if (tb == NULL) {
+                               cpy_log_exception("python initialization");
+                               continue;
+                       }
+                       cpy_format_exception = PyObject_GetAttrString(tb, "format_exception"); /* New reference. */
+                       Py_DECREF(tb);
+                       if (cpy_format_exception == NULL)
+                               cpy_log_exception("python initialization");
+               } else if (strcasecmp(item->key, "ModulePath") == 0) {
+                       char *dir = NULL;
+                       PyObject *dir_object;
+                       
+                       if (cf_util_get_string(item, &dir) != 0) 
+                               continue;
+                       dir_object = cpy_string_to_unicode_or_bytes(dir); /* New reference. */
+                       if (dir_object == NULL) {
+                               ERROR("python plugin: Unable to convert \"%s\" to "
+                                     "a python object.", dir);
+                               free(dir);
+                               cpy_log_exception("python initialization");
+                               continue;
+                       }
+                       if (PyList_Append(sys_path, dir_object) != 0) {
+                               ERROR("python plugin: Unable to append \"%s\" to "
+                                     "python module path.", dir);
+                               cpy_log_exception("python initialization");
+                       }
+                       Py_DECREF(dir_object);
+                       free(dir);
+               } else if (strcasecmp(item->key, "Import") == 0) {
+                       char *module_name = NULL;
+                       PyObject *module;
+                       
+                       if (cf_util_get_string(item, &module_name) != 0) 
+                               continue;
+                       module = PyImport_ImportModule(module_name); /* New reference. */
+                       if (module == NULL) {
+                               ERROR("python plugin: Error importing module \"%s\".", module_name);
+                               cpy_log_exception("importing module");
+                       }
+                       free(module_name);
+                       Py_XDECREF(module);
+               } else if (strcasecmp(item->key, "Module") == 0) {
+                       char *name = NULL;
+                       cpy_callback_t *c;
+                       PyObject *ret;
+                       
+                       if (cf_util_get_string(item, &name) != 0)
+                               continue;
+                       for (c = cpy_config_callbacks; c; c = c->next) {
+                               if (strcasecmp(c->name + 7, name) == 0)
+                                       break;
+                       }
+                       if (c == NULL) {
+                               WARNING("python plugin: Found a configuration for the \"%s\" plugin, "
+                                       "but the plugin isn't loaded or didn't register "
+                                       "a configuration callback.", name);
+                               free(name);
+                               continue;
+                       }
+                       free(name);
+                       if (c->data == NULL)
+                               ret = PyObject_CallFunction(c->callback, "N",
+                                       cpy_oconfig_to_pyconfig(item, NULL)); /* New reference. */
+                       else
+                               ret = PyObject_CallFunction(c->callback, "NO",
+                                       cpy_oconfig_to_pyconfig(item, NULL), c->data); /* New reference. */
+                       if (ret == NULL)
+                               cpy_log_exception("loading module");
+                       else
+                               Py_DECREF(ret);
+               } else {
+                       WARNING("python plugin: Ignoring unknown config key \"%s\".", item->key);
+               }
+       }
+       return 0;
+}
+
+void module_register(void) {
+       plugin_register_complex_config("python", cpy_config);
+       plugin_register_init("python", cpy_init);
+       plugin_register_shutdown("python", cpy_shutdown);
+}
diff --git a/src/pyvalues.c b/src/pyvalues.c
new file mode 100644 (file)
index 0000000..9d8760a
--- /dev/null
@@ -0,0 +1,1084 @@
+/**
+ * collectd - src/pyvalues.c
+ * Copyright (C) 2009  Sven Trenkel
+ *
+ * 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:
+ *   Sven Trenkel <collectd at semidefinite.de>  
+ **/
+
+#include <Python.h>
+#include <structmember.h>
+
+#include "collectd.h"
+#include "common.h"
+
+#include "cpython.h"
+
+static PyObject *cpy_common_repr(PyObject *s) {
+       PyObject *ret, *tmp;
+       static PyObject *l_type = NULL, *l_type_instance = NULL, *l_plugin = NULL, *l_plugin_instance = NULL;
+       static PyObject *l_host = NULL, *l_time = NULL;
+       PluginData *self = (PluginData *) s;
+       
+       if (l_type == NULL)
+               l_type = cpy_string_to_unicode_or_bytes("(type=");
+       if (l_type_instance == NULL)
+               l_type_instance = cpy_string_to_unicode_or_bytes(",type_instance=");
+       if (l_plugin == NULL)
+               l_plugin = cpy_string_to_unicode_or_bytes(",plugin=");
+       if (l_plugin_instance == NULL)
+               l_plugin_instance = cpy_string_to_unicode_or_bytes(",plugin_instance=");
+       if (l_host == NULL)
+               l_host = cpy_string_to_unicode_or_bytes(",host=");
+       if (l_time == NULL)
+               l_time = cpy_string_to_unicode_or_bytes(",time=");
+       
+       if (!l_type || !l_type_instance || !l_plugin || !l_plugin_instance || !l_host || !l_time)
+               return NULL;
+       
+       ret = cpy_string_to_unicode_or_bytes(s->ob_type->tp_name);
+
+       CPY_STRCAT(&ret, l_type);
+       tmp = cpy_string_to_unicode_or_bytes(self->type);
+       CPY_SUBSTITUTE(PyObject_Repr, tmp, tmp);
+       CPY_STRCAT_AND_DEL(&ret, tmp);
+
+       if (self->type_instance[0] != 0) {
+               CPY_STRCAT(&ret, l_type_instance);
+               tmp = cpy_string_to_unicode_or_bytes(self->type_instance);
+               CPY_SUBSTITUTE(PyObject_Repr, tmp, tmp);
+               CPY_STRCAT_AND_DEL(&ret, tmp);
+       }
+
+       if (self->plugin[0] != 0) {
+               CPY_STRCAT(&ret, l_plugin);
+               tmp = cpy_string_to_unicode_or_bytes(self->plugin);
+               CPY_SUBSTITUTE(PyObject_Repr, tmp, tmp);
+               CPY_STRCAT_AND_DEL(&ret, tmp);
+       }
+
+       if (self->plugin_instance[0] != 0) {
+               CPY_STRCAT(&ret, l_plugin_instance);
+               tmp = cpy_string_to_unicode_or_bytes(self->plugin_instance);
+               CPY_SUBSTITUTE(PyObject_Repr, tmp, tmp);
+               CPY_STRCAT_AND_DEL(&ret, tmp);
+       }
+
+       if (self->host[0] != 0) {
+               CPY_STRCAT(&ret, l_host);
+               tmp = cpy_string_to_unicode_or_bytes(self->host);
+               CPY_SUBSTITUTE(PyObject_Repr, tmp, tmp);
+               CPY_STRCAT_AND_DEL(&ret, tmp);
+       }
+
+       if (self->time != 0) {
+               CPY_STRCAT(&ret, l_time);
+               tmp = PyFloat_FromDouble(self->time);
+               CPY_SUBSTITUTE(PyObject_Repr, tmp, tmp);
+               CPY_STRCAT_AND_DEL(&ret, tmp);
+       }
+       return ret;
+}
+
+static char time_doc[] = "This is the Unix timestap of the time this value was read.\n"
+               "For dispatching values this can be set to 0 which means \"now\".\n"
+               "This means the time the value is actually dispatched, not the time\n"
+               "it was set to 0.";
+
+static char host_doc[] = "The hostname of the host this value was read from.\n"
+               "For dispatching this can be set to an empty string which means\n"
+               "the local hostname as defined in the collectd.conf.";
+
+static char type_doc[] = "The type of this value. This type has to be defined\n"
+               "in your types.db. Attempting to set it to any other value will\n"
+               "raise a TypeError exception.\n"
+               "Assigning a type is mandetory, calling dispatch without doing\n"
+               "so will raise a RuntimeError exception.";
+
+static char type_instance_doc[] = "";
+
+static char plugin_doc[] = "The name of the plugin that read the data. Setting this\n"
+               "member to an empty string will insert \"python\" upon dispatching.";
+
+static char plugin_instance_doc[] = "";
+
+static char PluginData_doc[] = "This is an internal class that is the base for Values\n"
+               "and Notification. It is pretty useless by itself and was therefore not\n"
+               "exported to the collectd module.";
+
+static PyObject *PluginData_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
+       PluginData *self;
+       
+       self = (PluginData *) type->tp_alloc(type, 0);
+       if (self == NULL)
+               return NULL;
+       
+       self->time = 0;
+       self->host[0] = 0;
+       self->plugin[0] = 0;
+       self->plugin_instance[0] = 0;
+       self->type[0] = 0;
+       self->type_instance[0] = 0;
+       return (PyObject *) self;
+}
+
+static int PluginData_init(PyObject *s, PyObject *args, PyObject *kwds) {
+       PluginData *self = (PluginData *) s;
+       double time = 0;
+       const char *type = "", *plugin_instance = "", *type_instance = "", *plugin = "", *host = "";
+       static char *kwlist[] = {"type", "plugin_instance", "type_instance",
+                       "plugin", "host", "time", NULL};
+       
+       if (!PyArg_ParseTupleAndKeywords(args, kwds, "|etetetetetd", kwlist, NULL, &type,
+                       NULL, &plugin_instance, NULL, &type_instance, NULL, &plugin, NULL, &host, &time))
+               return -1;
+       
+       if (type[0] != 0 && plugin_get_ds(type) == NULL) {
+               PyErr_Format(PyExc_TypeError, "Dataset %s not found", type);
+               return -1;
+       }
+
+       sstrncpy(self->host, host, sizeof(self->host));
+       sstrncpy(self->plugin, plugin, sizeof(self->plugin));
+       sstrncpy(self->plugin_instance, plugin_instance, sizeof(self->plugin_instance));
+       sstrncpy(self->type, type, sizeof(self->type));
+       sstrncpy(self->type_instance, type_instance, sizeof(self->type_instance));
+       
+       self->time = time;
+       return 0;
+}
+
+static PyObject *PluginData_repr(PyObject *s) {
+       PyObject *ret;
+       static PyObject *l_closing = NULL;
+       
+       if (l_closing == NULL)
+               l_closing = cpy_string_to_unicode_or_bytes(")");
+       
+       if (l_closing == NULL)
+               return NULL;
+       
+       ret = cpy_common_repr(s);
+       CPY_STRCAT(&ret, l_closing);
+       return ret;
+}
+
+static PyMemberDef PluginData_members[] = {
+       {"time", T_DOUBLE, offsetof(PluginData, time), 0, time_doc},
+       {NULL}
+};
+
+static PyObject *PluginData_getstring(PyObject *self, void *data) {
+       const char *value = ((char *) self) + (intptr_t) data;
+       
+       return cpy_string_to_unicode_or_bytes(value);
+}
+
+static int PluginData_setstring(PyObject *self, PyObject *value, void *data) {
+       char *old;
+       const char *new;
+       
+       if (value == NULL) {
+               PyErr_SetString(PyExc_TypeError, "Cannot delete this attribute");
+               return -1;
+       }
+       Py_INCREF(value);
+       new = cpy_unicode_or_bytes_to_string(&value);
+       if (new == NULL) {
+               Py_DECREF(value);
+               return -1;
+       }
+       old = ((char *) self) + (intptr_t) data;
+       sstrncpy(old, new, DATA_MAX_NAME_LEN);
+       Py_DECREF(value);
+       return 0;
+}
+
+static int PluginData_settype(PyObject *self, PyObject *value, void *data) {
+       char *old;
+       const char *new;
+       
+       if (value == NULL) {
+               PyErr_SetString(PyExc_TypeError, "Cannot delete this attribute");
+               return -1;
+       }
+       Py_INCREF(value);
+       new = cpy_unicode_or_bytes_to_string(&value);
+       if (new == NULL) {
+               Py_DECREF(value);
+               return -1;
+       }
+
+       if (plugin_get_ds(new) == NULL) {
+               PyErr_Format(PyExc_TypeError, "Dataset %s not found", new);
+               Py_DECREF(value);
+               return -1;
+       }
+
+       old = ((char *) self) + (intptr_t) data;
+       sstrncpy(old, new, DATA_MAX_NAME_LEN);
+       Py_DECREF(value);
+       return 0;
+}
+
+static PyGetSetDef PluginData_getseters[] = {
+       {"host", PluginData_getstring, PluginData_setstring, host_doc, (void *) offsetof(PluginData, host)},
+       {"plugin", PluginData_getstring, PluginData_setstring, plugin_doc, (void *) offsetof(PluginData, plugin)},
+       {"plugin_instance", PluginData_getstring, PluginData_setstring, plugin_instance_doc, (void *) offsetof(PluginData, plugin_instance)},
+       {"type_instance", PluginData_getstring, PluginData_setstring, type_instance_doc, (void *) offsetof(PluginData, type_instance)},
+       {"type", PluginData_getstring, PluginData_settype, type_doc, (void *) offsetof(PluginData, type)},
+       {NULL}
+};
+
+PyTypeObject PluginDataType = {
+       CPY_INIT_TYPE
+       "collectd.PluginData",     /* tp_name */
+       sizeof(PluginData),        /* tp_basicsize */
+       0,                         /* Will be filled in later */
+       0,                         /* tp_dealloc */
+       0,                         /* tp_print */
+       0,                         /* tp_getattr */
+       0,                         /* tp_setattr */
+       0,                         /* tp_compare */
+       PluginData_repr,           /* tp_repr */
+       0,                         /* tp_as_number */
+       0,                         /* tp_as_sequence */
+       0,                         /* tp_as_mapping */
+       0,                         /* tp_hash */
+       0,                         /* tp_call */
+       0,                         /* tp_str */
+       0,                         /* tp_getattro */
+       0,                         /* tp_setattro */
+       0,                         /* tp_as_buffer */
+       Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE /*| Py_TPFLAGS_HAVE_GC*/, /*tp_flags*/
+       PluginData_doc,            /* tp_doc */
+       0,                         /* tp_traverse */
+       0,                         /* tp_clear */
+       0,                         /* tp_richcompare */
+       0,                         /* tp_weaklistoffset */
+       0,                         /* tp_iter */
+       0,                         /* tp_iternext */
+       0,                         /* tp_methods */
+       PluginData_members,        /* tp_members */
+       PluginData_getseters,      /* tp_getset */
+       0,                         /* tp_base */
+       0,                         /* tp_dict */
+       0,                         /* tp_descr_get */
+       0,                         /* tp_descr_set */
+       0,                         /* tp_dictoffset */
+       PluginData_init,           /* tp_init */
+       0,                         /* tp_alloc */
+       PluginData_new             /* tp_new */
+};
+
+static char interval_doc[] = "The interval is the timespan in seconds between two submits for\n"
+               "the same data source. This value has to be a positive integer, so you can't\n"
+               "submit more than one value per second. If this member is set to a\n"
+               "non-positive value, the default value as specified in the config file will\n"
+               "be used (default: 10).\n"
+               "\n"
+               "If you submit values more often than the specified interval, the average\n"
+               "will be used. If you submit less values, your graphs will have gaps.";
+
+static char values_doc[] = "These are the actual values that get dispatched to collectd.\n"
+               "It has to be a sequence (a tuple or list) of numbers.\n"
+               "The size of the sequence and the type of its content depend on the type\n"
+               "member your types.db file. For more information on this read the types.db\n"
+               "man page.\n"
+               "\n"
+               "If the sequence does not have the correct size upon dispatch a RuntimeError\n"
+               "exception will be raised. If the content of the sequence is not a number,\n"
+               "a TypeError exception will be raised.";
+
+static char meta_doc[] = "These are the meta data for this Value object.\n"
+               "It has to be a dictionary of numbers, strings or bools. All keys must be\n"
+               "strings. int and long objects will be dispatched as signed integers unless\n"
+               "they are between 2**63 and 2**64-1, which will result in a unsigned integer.\n"
+               "You can force one of these storage classes by using the classes\n"
+               "collectd.Signed and collectd.Unsigned. A meta object received by a write\n"
+               "callback will always contain Signed or Unsigned objects.";
+
+static char dispatch_doc[] = "dispatch([type][, values][, plugin_instance][, type_instance]"
+               "[, plugin][, host][, time][, interval]) -> None.  Dispatch a value list.\n"
+               "\n"
+               "Dispatch this instance to the collectd process. The object has members\n"
+               "for each of the possible arguments for this method. For a detailed explanation\n"
+               "of these parameters see the member of the same same.\n"
+               "\n"
+               "If you do not submit a parameter the value saved in its member will be submitted.\n"
+               "If you do provide a parameter it will be used instead, without altering the member.";
+
+static char write_doc[] = "write([destination][, type][, values][, plugin_instance][, type_instance]"
+               "[, plugin][, host][, time][, interval]) -> None.  Dispatch a value list.\n"
+               "\n"
+               "Write this instance to a single plugin or all plugins if 'destination' is obmitted.\n"
+               "This will bypass the main collectd process and all filtering and caching.\n"
+               "Other than that it works similar to 'dispatch'. In most cases 'dispatch' should be\n"
+               "used instead of 'write'.\n";
+
+static char Values_doc[] = "A Values object used for dispatching values to collectd and receiving values from write callbacks.";
+
+static PyObject *Values_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
+       Values *self;
+       
+       self = (Values *) PluginData_new(type, args, kwds);
+       if (self == NULL)
+               return NULL;
+       
+       self->values = PyList_New(0);
+       self->meta = PyDict_New();
+       self->interval = 0;
+       return (PyObject *) self;
+}
+
+static int Values_init(PyObject *s, PyObject *args, PyObject *kwds) {
+       Values *self = (Values *) s;
+       double interval = 0, time = 0;
+       PyObject *values = NULL, *meta = NULL, *tmp;
+       const char *type = "", *plugin_instance = "", *type_instance = "", *plugin = "", *host = "";
+       static char *kwlist[] = {"type", "values", "plugin_instance", "type_instance",
+                       "plugin", "host", "time", "interval", "meta", NULL};
+       
+       if (!PyArg_ParseTupleAndKeywords(args, kwds, "|etOetetetetddO", kwlist,
+                       NULL, &type, &values, NULL, &plugin_instance, NULL, &type_instance,
+                       NULL, &plugin, NULL, &host, &time, &interval, &meta))
+               return -1;
+       
+       if (type[0] != 0 && plugin_get_ds(type) == NULL) {
+               PyErr_Format(PyExc_TypeError, "Dataset %s not found", type);
+               return -1;
+       }
+
+       sstrncpy(self->data.host, host, sizeof(self->data.host));
+       sstrncpy(self->data.plugin, plugin, sizeof(self->data.plugin));
+       sstrncpy(self->data.plugin_instance, plugin_instance, sizeof(self->data.plugin_instance));
+       sstrncpy(self->data.type, type, sizeof(self->data.type));
+       sstrncpy(self->data.type_instance, type_instance, sizeof(self->data.type_instance));
+       self->data.time = time;
+
+       if (values == NULL) {
+               values = PyList_New(0);
+               PyErr_Clear();
+       } else {
+               Py_INCREF(values);
+       }
+       
+       if (meta == NULL) {
+               meta = PyDict_New();
+               PyErr_Clear();
+       } else {
+               Py_INCREF(meta);
+       }
+       
+       tmp = self->values;
+       self->values = values;
+       Py_XDECREF(tmp);
+       
+       tmp = self->meta;
+       self->meta = meta;
+       Py_XDECREF(tmp);
+
+       self->interval = interval;
+       return 0;
+}
+
+static meta_data_t *cpy_build_meta(PyObject *meta) {
+       int i, s;
+       meta_data_t *m = NULL;
+       PyObject *l;
+       
+       if (!meta)
+               return NULL;
+
+       l = PyDict_Items(meta); /* New reference. */
+       if (!l) {
+               cpy_log_exception("building meta data");
+               return NULL;
+       }
+       m = meta_data_create();
+       s = PyList_Size(l);
+       for (i = 0; i < s; ++i) {
+               const char *string, *keystring;
+               PyObject *key, *value, *item, *tmp;
+               
+               item = PyList_GET_ITEM(l, i);
+               key = PyTuple_GET_ITEM(item, 0);
+               Py_INCREF(key);
+               keystring = cpy_unicode_or_bytes_to_string(&key);
+               if (!keystring) {
+                       PyErr_Clear();
+                       Py_XDECREF(key);
+                       continue;
+               }
+               value = PyTuple_GET_ITEM(item, 1);
+               Py_INCREF(value);
+               if (value == Py_True) {
+                       meta_data_add_boolean(m, keystring, 1);
+               } else if (value == Py_False) {
+                       meta_data_add_boolean(m, keystring, 0);
+               } else if (PyFloat_Check(value)) {
+                       meta_data_add_double(m, keystring, PyFloat_AsDouble(value));
+               } else if (PyObject_TypeCheck(value, &SignedType)) {
+                       long long int lli;
+                       lli = PyLong_AsLongLong(value);
+                       if (!PyErr_Occurred() && (lli == (int64_t) lli))
+                               meta_data_add_signed_int(m, keystring, lli);
+               } else if (PyObject_TypeCheck(value, &UnsignedType)) {
+                       long long unsigned llu;
+                       llu = PyLong_AsUnsignedLongLong(value);
+                       if (!PyErr_Occurred() && (llu == (uint64_t) llu))
+                               meta_data_add_unsigned_int(m, keystring, llu);
+               } else if (PyNumber_Check(value)) {
+                       long long int lli;
+                       long long unsigned llu;
+                       tmp = PyNumber_Long(value);
+                       lli = PyLong_AsLongLong(tmp);
+                       if (!PyErr_Occurred() && (lli == (int64_t) lli)) {
+                               meta_data_add_signed_int(m, keystring, lli);
+                       } else {
+                               PyErr_Clear();
+                               llu = PyLong_AsUnsignedLongLong(tmp);
+                               if (!PyErr_Occurred() && (llu == (uint64_t) llu))
+                                       meta_data_add_unsigned_int(m, keystring, llu);
+                       }
+                       Py_XDECREF(tmp);
+               } else {
+                       string = cpy_unicode_or_bytes_to_string(&value);
+                       if (string) {
+                               meta_data_add_string(m, keystring, string);
+                       } else {
+                               PyErr_Clear();
+                               tmp = PyObject_Str(value);
+                               string = cpy_unicode_or_bytes_to_string(&tmp);
+                               if (string)
+                                       meta_data_add_string(m, keystring, string);
+                               Py_XDECREF(tmp);
+                       }
+               }
+               if (PyErr_Occurred())
+                       cpy_log_exception("building meta data");
+               Py_XDECREF(value);
+               Py_DECREF(key);
+       }
+       Py_XDECREF(l);
+       return m;
+}
+
+static PyObject *Values_dispatch(Values *self, PyObject *args, PyObject *kwds) {
+       int i, ret;
+       const data_set_t *ds;
+       int size;
+       value_t *value;
+       value_list_t value_list = VALUE_LIST_INIT;
+       PyObject *values = self->values, *meta = self->meta;
+       double time = self->data.time, interval = self->interval;
+       const char *host = self->data.host;
+       const char *plugin = self->data.plugin;
+       const char *plugin_instance = self->data.plugin_instance;
+       const char *type = self->data.type;
+       const char *type_instance = self->data.type_instance;
+       
+       static char *kwlist[] = {"type", "values", "plugin_instance", "type_instance",
+                       "plugin", "host", "time", "interval", "meta", NULL};
+       if (!PyArg_ParseTupleAndKeywords(args, kwds, "|etOetetetetddO", kwlist,
+                       NULL, &type, &values, NULL, &plugin_instance, NULL, &type_instance,
+                       NULL, &plugin, NULL, &host, &time, &interval, &meta))
+               return NULL;
+
+       if (type[0] == 0) {
+               PyErr_SetString(PyExc_RuntimeError, "type not set");
+               return NULL;
+       }
+       ds = plugin_get_ds(type);
+       if (ds == NULL) {
+               PyErr_Format(PyExc_TypeError, "Dataset %s not found", type);
+               return NULL;
+       }
+       if (values == NULL || (PyTuple_Check(values) == 0 && PyList_Check(values) == 0)) {
+               PyErr_Format(PyExc_TypeError, "values must be list or tuple");
+               return NULL;
+       }
+       if (meta != NULL && meta != Py_None && !PyDict_Check(meta)) {
+               PyErr_Format(PyExc_TypeError, "meta must be a dict");
+               return NULL;
+       }
+       size = (int) PySequence_Length(values);
+       if (size != ds->ds_num) {
+               PyErr_Format(PyExc_RuntimeError, "type %s needs %d values, got %i", type, ds->ds_num, size);
+               return NULL;
+       }
+       value = malloc(size * sizeof(*value));
+       for (i = 0; i < size; ++i) {
+               PyObject *item, *num;
+               item = PySequence_Fast_GET_ITEM(values, i); /* Borrowed reference. */
+               if (ds->ds->type == DS_TYPE_COUNTER) {
+                       num = PyNumber_Long(item); /* New reference. */
+                       if (num != NULL) {
+                               value[i].counter = PyLong_AsUnsignedLongLong(num);
+                               Py_XDECREF(num);
+                       }
+               } else if (ds->ds->type == DS_TYPE_GAUGE) {
+                       num = PyNumber_Float(item); /* New reference. */
+                       if (num != NULL) {
+                               value[i].gauge = PyFloat_AsDouble(num);
+                               Py_XDECREF(num);
+                       }
+               } else if (ds->ds->type == DS_TYPE_DERIVE) {
+                       /* This might overflow without raising an exception.
+                        * Not much we can do about it */
+                       num = PyNumber_Long(item); /* New reference. */
+                       if (num != NULL) {
+                               value[i].derive = PyLong_AsLongLong(num);
+                               Py_XDECREF(num);
+                       }
+               } else if (ds->ds->type == DS_TYPE_ABSOLUTE) {
+                       /* This might overflow without raising an exception.
+                        * Not much we can do about it */
+                       num = PyNumber_Long(item); /* New reference. */
+                       if (num != NULL) {
+                               value[i].absolute = PyLong_AsUnsignedLongLong(num);
+                               Py_XDECREF(num);
+                       }
+               } else {
+                       free(value);
+                       PyErr_Format(PyExc_RuntimeError, "unknown data type %d for %s", ds->ds->type, type);
+                       return NULL;
+               }
+               if (PyErr_Occurred() != NULL) {
+                       free(value);
+                       return NULL;
+               }
+       }
+       value_list.values = value;
+       value_list.meta = cpy_build_meta(meta);
+       value_list.values_len = size;
+       value_list.time = DOUBLE_TO_CDTIME_T(time);
+       value_list.interval = DOUBLE_TO_CDTIME_T(interval);
+       sstrncpy(value_list.host, host, sizeof(value_list.host));
+       sstrncpy(value_list.plugin, plugin, sizeof(value_list.plugin));
+       sstrncpy(value_list.plugin_instance, plugin_instance, sizeof(value_list.plugin_instance));
+       sstrncpy(value_list.type, type, sizeof(value_list.type));
+       sstrncpy(value_list.type_instance, type_instance, sizeof(value_list.type_instance));
+       if (value_list.host[0] == 0)
+               sstrncpy(value_list.host, hostname_g, sizeof(value_list.host));
+       if (value_list.plugin[0] == 0)
+               sstrncpy(value_list.plugin, "python", sizeof(value_list.plugin));
+       Py_BEGIN_ALLOW_THREADS;
+       ret = plugin_dispatch_values(&value_list);
+       Py_END_ALLOW_THREADS;
+       meta_data_destroy(value_list.meta);
+       free(value);
+       if (ret != 0) {
+               PyErr_SetString(PyExc_RuntimeError, "error dispatching values, read the logs");
+               return NULL;
+       }
+       Py_RETURN_NONE;
+}
+
+static PyObject *Values_write(Values *self, PyObject *args, PyObject *kwds) {
+       int i, ret;
+       const data_set_t *ds;
+       int size;
+       value_t *value;
+       value_list_t value_list = VALUE_LIST_INIT;
+       PyObject *values = self->values, *meta = self->meta;
+       double time = self->data.time, interval = self->interval;
+       const char *host = self->data.host;
+       const char *plugin = self->data.plugin;
+       const char *plugin_instance = self->data.plugin_instance;
+       const char *type = self->data.type;
+       const char *type_instance = self->data.type_instance;
+       const char *dest = NULL;
+       
+       static char *kwlist[] = {"destination", "type", "values", "plugin_instance", "type_instance",
+                       "plugin", "host", "time", "interval", "meta", NULL};
+       if (!PyArg_ParseTupleAndKeywords(args, kwds, "|etOetetetetddO", kwlist,
+                       NULL, &type, &values, NULL, &plugin_instance, NULL, &type_instance,
+                       NULL, &plugin, NULL, &host, &time, &interval, &meta))
+               return NULL;
+
+       if (type[0] == 0) {
+               PyErr_SetString(PyExc_RuntimeError, "type not set");
+               return NULL;
+       }
+       ds = plugin_get_ds(type);
+       if (ds == NULL) {
+               PyErr_Format(PyExc_TypeError, "Dataset %s not found", type);
+               return NULL;
+       }
+       if (values == NULL || (PyTuple_Check(values) == 0 && PyList_Check(values) == 0)) {
+               PyErr_Format(PyExc_TypeError, "values must be list or tuple");
+               return NULL;
+       }
+       size = (int) PySequence_Length(values);
+       if (size != ds->ds_num) {
+               PyErr_Format(PyExc_RuntimeError, "type %s needs %d values, got %i", type, ds->ds_num, size);
+               return NULL;
+       }
+       value = malloc(size * sizeof(*value));
+       for (i = 0; i < size; ++i) {
+               PyObject *item, *num;
+               item = PySequence_Fast_GET_ITEM(values, i); /* Borrowed reference. */
+               if (ds->ds->type == DS_TYPE_COUNTER) {
+                       num = PyNumber_Long(item); /* New reference. */
+                       if (num != NULL) {
+                               value[i].counter = PyLong_AsUnsignedLongLong(num);
+                               Py_XDECREF(num);
+                       }
+               } else if (ds->ds->type == DS_TYPE_GAUGE) {
+                       num = PyNumber_Float(item); /* New reference. */
+                       if (num != NULL) {
+                               value[i].gauge = PyFloat_AsDouble(num);
+                               Py_XDECREF(num);
+                       }
+               } else if (ds->ds->type == DS_TYPE_DERIVE) {
+                       /* This might overflow without raising an exception.
+                        * Not much we can do about it */
+                       num = PyNumber_Long(item); /* New reference. */
+                       if (num != NULL) {
+                               value[i].derive = PyLong_AsLongLong(num);
+                               Py_XDECREF(num);
+                       }
+               } else if (ds->ds->type == DS_TYPE_ABSOLUTE) {
+                       /* This might overflow without raising an exception.
+                        * Not much we can do about it */
+                       num = PyNumber_Long(item); /* New reference. */
+                       if (num != NULL) {
+                               value[i].absolute = PyLong_AsUnsignedLongLong(num);
+                               Py_XDECREF(num);
+                       }
+               } else {
+                       free(value);
+                       PyErr_Format(PyExc_RuntimeError, "unknown data type %d for %s", ds->ds->type, type);
+                       return NULL;
+               }
+               if (PyErr_Occurred() != NULL) {
+                       free(value);
+                       return NULL;
+               }
+       }
+       value_list.values = value;
+       value_list.values_len = size;
+       value_list.time = DOUBLE_TO_CDTIME_T(time);
+       value_list.interval = DOUBLE_TO_CDTIME_T(interval);
+       sstrncpy(value_list.host, host, sizeof(value_list.host));
+       sstrncpy(value_list.plugin, plugin, sizeof(value_list.plugin));
+       sstrncpy(value_list.plugin_instance, plugin_instance, sizeof(value_list.plugin_instance));
+       sstrncpy(value_list.type, type, sizeof(value_list.type));
+       sstrncpy(value_list.type_instance, type_instance, sizeof(value_list.type_instance));
+       value_list.meta = cpy_build_meta(meta);;
+       if (value_list.host[0] == 0)
+               sstrncpy(value_list.host, hostname_g, sizeof(value_list.host));
+       if (value_list.plugin[0] == 0)
+               sstrncpy(value_list.plugin, "python", sizeof(value_list.plugin));
+       Py_BEGIN_ALLOW_THREADS;
+       ret = plugin_write(dest, NULL, &value_list);
+       Py_END_ALLOW_THREADS;
+       meta_data_destroy(value_list.meta);
+       free(value);
+       if (ret != 0) {
+               PyErr_SetString(PyExc_RuntimeError, "error dispatching values, read the logs");
+               return NULL;
+       }
+       Py_RETURN_NONE;
+}
+
+static PyObject *Values_repr(PyObject *s) {
+       PyObject *ret, *tmp;
+       static PyObject *l_interval = NULL, *l_values = NULL, *l_meta = NULL, *l_closing = NULL;
+       Values *self = (Values *) s;
+       
+       if (l_interval == NULL)
+               l_interval = cpy_string_to_unicode_or_bytes(",interval=");
+       if (l_values == NULL)
+               l_values = cpy_string_to_unicode_or_bytes(",values=");
+       if (l_meta == NULL)
+               l_meta = cpy_string_to_unicode_or_bytes(",meta=");
+       if (l_closing == NULL)
+               l_closing = cpy_string_to_unicode_or_bytes(")");
+       
+       if (l_interval == NULL || l_values == NULL || l_meta == NULL || l_closing == NULL)
+               return NULL;
+       
+       ret = cpy_common_repr(s);
+       if (self->interval != 0) {
+               CPY_STRCAT(&ret, l_interval);
+               tmp = PyFloat_FromDouble(self->interval);
+               CPY_SUBSTITUTE(PyObject_Repr, tmp, tmp);
+               CPY_STRCAT_AND_DEL(&ret, tmp);
+       }
+       if (self->values && (!PyList_Check(self->values) || PySequence_Length(self->values) > 0)) {
+               CPY_STRCAT(&ret, l_values);
+               tmp = PyObject_Repr(self->values);
+               CPY_STRCAT_AND_DEL(&ret, tmp);
+       }
+       if (self->meta && (!PyDict_Check(self->meta) || PyDict_Size(self->meta) > 0)) {
+               CPY_STRCAT(&ret, l_meta);
+               tmp = PyObject_Repr(self->meta);
+               CPY_STRCAT_AND_DEL(&ret, tmp);
+       }
+       CPY_STRCAT(&ret, l_closing);
+       return ret;
+}
+
+static int Values_traverse(PyObject *self, visitproc visit, void *arg) {
+       Values *v = (Values *) self;
+       Py_VISIT(v->values);
+       Py_VISIT(v->meta);
+       return 0;
+}
+
+static int Values_clear(PyObject *self) {
+       Values *v = (Values *) self;
+       Py_CLEAR(v->values);
+       Py_CLEAR(v->meta);
+       return 0;
+}
+
+static void Values_dealloc(PyObject *self) {
+       Values_clear(self);
+       self->ob_type->tp_free(self);
+}
+
+static PyMemberDef Values_members[] = {
+       {"interval", T_INT, offsetof(Values, interval), 0, interval_doc},
+       {"values", T_OBJECT_EX, offsetof(Values, values), 0, values_doc},
+       {"meta", T_OBJECT_EX, offsetof(Values, meta), 0, meta_doc},
+       {NULL}
+};
+
+static PyMethodDef Values_methods[] = {
+       {"dispatch", (PyCFunction) Values_dispatch, METH_VARARGS | METH_KEYWORDS, dispatch_doc},
+       {"write", (PyCFunction) Values_write, METH_VARARGS | METH_KEYWORDS, write_doc},
+       {NULL}
+};
+
+PyTypeObject ValuesType = {
+       CPY_INIT_TYPE
+       "collectd.Values",         /* tp_name */
+       sizeof(Values),            /* tp_basicsize */
+       0,                         /* Will be filled in later */
+       Values_dealloc,            /* tp_dealloc */
+       0,                         /* tp_print */
+       0,                         /* tp_getattr */
+       0,                         /* tp_setattr */
+       0,                         /* tp_compare */
+       Values_repr,               /* tp_repr */
+       0,                         /* tp_as_number */
+       0,                         /* tp_as_sequence */
+       0,                         /* tp_as_mapping */
+       0,                         /* tp_hash */
+       0,                         /* tp_call */
+       0,                         /* tp_str */
+       0,                         /* tp_getattro */
+       0,                         /* tp_setattro */
+       0,                         /* tp_as_buffer */
+       Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /*tp_flags*/
+       Values_doc,                /* tp_doc */
+       Values_traverse,           /* tp_traverse */
+       Values_clear,              /* tp_clear */
+       0,                         /* tp_richcompare */
+       0,                         /* tp_weaklistoffset */
+       0,                         /* tp_iter */
+       0,                         /* tp_iternext */
+       Values_methods,            /* tp_methods */
+       Values_members,            /* tp_members */
+       0,                         /* tp_getset */
+       0,                         /* tp_base */
+       0,                         /* tp_dict */
+       0,                         /* tp_descr_get */
+       0,                         /* tp_descr_set */
+       0,                         /* tp_dictoffset */
+       Values_init,               /* tp_init */
+       0,                         /* tp_alloc */
+       Values_new                 /* tp_new */
+};
+
+static char severity_doc[] = "The severity of this notification. Assign or compare to\n"
+               "NOTIF_FAILURE, NOTIF_WARNING or NOTIF_OKAY.";
+
+static char message_doc[] = "Some kind of description what's going on and why this Notification was generated.";
+
+static char Notification_doc[] = "The Notification class is a wrapper around the collectd notification.\n"
+               "It can be used to notify other plugins about bad stuff happening. It works\n"
+               "similar to Values but has a severity and a message instead of interval\n"
+               "and time.\n"
+               "Notifications can be dispatched at any time and can be received with register_notification.";
+
+static int Notification_init(PyObject *s, PyObject *args, PyObject *kwds) {
+       Notification *self = (Notification *) s;
+       int severity = 0;
+       double time = 0;
+       const char *message = "";
+       const char *type = "", *plugin_instance = "", *type_instance = "", *plugin = "", *host = "";
+       static char *kwlist[] = {"type", "message", "plugin_instance", "type_instance",
+                       "plugin", "host", "time", "severity", NULL};
+       
+       if (!PyArg_ParseTupleAndKeywords(args, kwds, "|etetetetetetdi", kwlist,
+                       NULL, &type, NULL, &message, NULL, &plugin_instance, NULL, &type_instance,
+                       NULL, &plugin, NULL, &host, &time, &severity))
+               return -1;
+       
+       if (type[0] != 0 && plugin_get_ds(type) == NULL) {
+               PyErr_Format(PyExc_TypeError, "Dataset %s not found", type);
+               return -1;
+       }
+
+       sstrncpy(self->data.host, host, sizeof(self->data.host));
+       sstrncpy(self->data.plugin, plugin, sizeof(self->data.plugin));
+       sstrncpy(self->data.plugin_instance, plugin_instance, sizeof(self->data.plugin_instance));
+       sstrncpy(self->data.type, type, sizeof(self->data.type));
+       sstrncpy(self->data.type_instance, type_instance, sizeof(self->data.type_instance));
+       self->data.time = time;
+
+       sstrncpy(self->message, message, sizeof(self->message));
+       self->severity = severity;
+       return 0;
+}
+
+static PyObject *Notification_dispatch(Notification *self, PyObject *args, PyObject *kwds) {
+       int ret;
+       const data_set_t *ds;
+       notification_t notification;
+       double t = self->data.time;
+       int severity = self->severity;
+       const char *host = self->data.host;
+       const char *plugin = self->data.plugin;
+       const char *plugin_instance = self->data.plugin_instance;
+       const char *type = self->data.type;
+       const char *type_instance = self->data.type_instance;
+       const char *message = self->message;
+       
+       static char *kwlist[] = {"type", "message", "plugin_instance", "type_instance",
+                       "plugin", "host", "time", "severity", NULL};
+       if (!PyArg_ParseTupleAndKeywords(args, kwds, "|etetetetetetdi", kwlist,
+                       NULL, &type, NULL, &message, NULL, &plugin_instance, NULL, &type_instance,
+                       NULL, &plugin, NULL, &host, &t, &severity))
+               return NULL;
+
+       if (type[0] == 0) {
+               PyErr_SetString(PyExc_RuntimeError, "type not set");
+               return NULL;
+       }
+       ds = plugin_get_ds(type);
+       if (ds == NULL) {
+               PyErr_Format(PyExc_TypeError, "Dataset %s not found", type);
+               return NULL;
+       }
+
+       notification.time = DOUBLE_TO_CDTIME_T(t);
+       notification.severity = severity;
+       sstrncpy(notification.message, message, sizeof(notification.message));
+       sstrncpy(notification.host, host, sizeof(notification.host));
+       sstrncpy(notification.plugin, plugin, sizeof(notification.plugin));
+       sstrncpy(notification.plugin_instance, plugin_instance, sizeof(notification.plugin_instance));
+       sstrncpy(notification.type, type, sizeof(notification.type));
+       sstrncpy(notification.type_instance, type_instance, sizeof(notification.type_instance));
+       notification.meta = NULL;
+       if (notification.time == 0)
+               notification.time = cdtime();
+       if (notification.host[0] == 0)
+               sstrncpy(notification.host, hostname_g, sizeof(notification.host));
+       if (notification.plugin[0] == 0)
+               sstrncpy(notification.plugin, "python", sizeof(notification.plugin));
+       Py_BEGIN_ALLOW_THREADS;
+       ret = plugin_dispatch_notification(&notification);
+       Py_END_ALLOW_THREADS;
+       if (ret != 0) {
+               PyErr_SetString(PyExc_RuntimeError, "error dispatching notification, read the logs");
+               return NULL;
+       }
+       Py_RETURN_NONE;
+}
+
+static PyObject *Notification_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
+       Notification *self;
+       
+       self = (Notification *) PluginData_new(type, args, kwds);
+       if (self == NULL)
+               return NULL;
+       
+       self->message[0] = 0;
+       self->severity = 0;
+       return (PyObject *) self;
+}
+
+static int Notification_setstring(PyObject *self, PyObject *value, void *data) {
+       char *old;
+       const char *new;
+       
+       if (value == NULL) {
+               PyErr_SetString(PyExc_TypeError, "Cannot delete this attribute");
+               return -1;
+       }
+       Py_INCREF(value);
+       new = cpy_unicode_or_bytes_to_string(&value);
+       if (new == NULL) {
+               Py_DECREF(value);
+               return -1;
+       }
+       old = ((char *) self) + (intptr_t) data;
+       sstrncpy(old, new, NOTIF_MAX_MSG_LEN);
+       Py_DECREF(value);
+       return 0;
+}
+
+static PyObject *Notification_repr(PyObject *s) {
+       PyObject *ret, *tmp;
+       static PyObject *l_severity = NULL, *l_message = NULL, *l_closing = NULL;
+       Notification *self = (Notification *) s;
+       
+       if (l_severity == NULL)
+               l_severity = cpy_string_to_unicode_or_bytes(",severity=");
+       if (l_message == NULL)
+               l_message = cpy_string_to_unicode_or_bytes(",message=");
+       if (l_closing == NULL)
+               l_closing = cpy_string_to_unicode_or_bytes(")");
+       
+       if (l_severity == NULL || l_message == NULL || l_closing == NULL)
+               return NULL;
+       
+       ret = cpy_common_repr(s);
+       if (self->severity != 0) {
+               CPY_STRCAT(&ret, l_severity);
+               tmp = PyInt_FromLong(self->severity);
+               CPY_SUBSTITUTE(PyObject_Repr, tmp, tmp);
+               CPY_STRCAT_AND_DEL(&ret, tmp);
+       }
+       if (self->message[0] != 0) {
+               CPY_STRCAT(&ret, l_message);
+               tmp = cpy_string_to_unicode_or_bytes(self->message);
+               CPY_SUBSTITUTE(PyObject_Repr, tmp, tmp);
+               CPY_STRCAT_AND_DEL(&ret, tmp);
+       }
+       CPY_STRCAT(&ret, l_closing);
+       return ret;
+}
+
+static PyMethodDef Notification_methods[] = {
+       {"dispatch", (PyCFunction) Notification_dispatch, METH_VARARGS | METH_KEYWORDS, dispatch_doc},
+       {NULL}
+};
+
+static PyMemberDef Notification_members[] = {
+       {"severity", T_INT, offsetof(Notification, severity), 0, severity_doc},
+       {NULL}
+};
+
+static PyGetSetDef Notification_getseters[] = {
+       {"message", PluginData_getstring, Notification_setstring, message_doc, (void *) offsetof(Notification, message)},
+       {NULL}
+};
+
+PyTypeObject NotificationType = {
+       CPY_INIT_TYPE
+       "collectd.Notification",   /* tp_name */
+       sizeof(Notification),      /* tp_basicsize */
+       0,                         /* Will be filled in later */
+       0,                         /* tp_dealloc */
+       0,                         /* tp_print */
+       0,                         /* tp_getattr */
+       0,                         /* tp_setattr */
+       0,                         /* tp_compare */
+       Notification_repr,         /* tp_repr */
+       0,                         /* tp_as_number */
+       0,                         /* tp_as_sequence */
+       0,                         /* tp_as_mapping */
+       0,                         /* tp_hash */
+       0,                         /* tp_call */
+       0,                         /* tp_str */
+       0,                         /* tp_getattro */
+       0,                         /* tp_setattro */
+       0,                         /* tp_as_buffer */
+       Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+       Notification_doc,          /* tp_doc */
+       0,                         /* tp_traverse */
+       0,                         /* tp_clear */
+       0,                         /* tp_richcompare */
+       0,                         /* tp_weaklistoffset */
+       0,                         /* tp_iter */
+       0,                         /* tp_iternext */
+       Notification_methods,      /* tp_methods */
+       Notification_members,      /* tp_members */
+       Notification_getseters,    /* tp_getset */
+       0,                         /* tp_base */
+       0,                         /* tp_dict */
+       0,                         /* tp_descr_get */
+       0,                         /* tp_descr_set */
+       0,                         /* tp_dictoffset */
+       Notification_init,         /* tp_init */
+       0,                         /* tp_alloc */
+       Notification_new           /* tp_new */
+};
+
+static char Signed_doc[] = "This is a long by another name. Use it in meta data dicts\n"
+               "to choose the way it is stored in the meta data.";
+
+PyTypeObject SignedType = {
+       CPY_INIT_TYPE
+       "collectd.Signed",         /* tp_name */
+       sizeof(Signed),            /* tp_basicsize */
+       0,                         /* Will be filled in later */
+       0,                         /* tp_dealloc */
+       0,                         /* tp_print */
+       0,                         /* tp_getattr */
+       0,                         /* tp_setattr */
+       0,                         /* tp_compare */
+       0,                         /* tp_repr */
+       0,                         /* tp_as_number */
+       0,                         /* tp_as_sequence */
+       0,                         /* tp_as_mapping */
+       0,                         /* tp_hash */
+       0,                         /* tp_call */
+       0,                         /* tp_str */
+       0,                         /* tp_getattro */
+       0,                         /* tp_setattro */
+       0,                         /* tp_as_buffer */
+       Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+       Signed_doc                 /* tp_doc */
+};
+
+static char Unsigned_doc[] = "This is a long by another name. Use it in meta data dicts\n"
+               "to choose the way it is stored in the meta data.";
+
+PyTypeObject UnsignedType = {
+       CPY_INIT_TYPE
+       "collectd.Unsigned",       /* tp_name */
+       sizeof(Unsigned),          /* tp_basicsize */
+       0,                         /* Will be filled in later */
+       0,                         /* tp_dealloc */
+       0,                         /* tp_print */
+       0,                         /* tp_getattr */
+       0,                         /* tp_setattr */
+       0,                         /* tp_compare */
+       0,                         /* tp_repr */
+       0,                         /* tp_as_number */
+       0,                         /* tp_as_sequence */
+       0,                         /* tp_as_mapping */
+       0,                         /* tp_hash */
+       0,                         /* tp_call */
+       0,                         /* tp_str */
+       0,                         /* tp_getattro */
+       0,                         /* tp_setattro */
+       0,                         /* tp_as_buffer */
+       Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+       Unsigned_doc               /* tp_doc */
+};
diff --git a/src/redis.c b/src/redis.c
new file mode 100644 (file)
index 0000000..b694e09
--- /dev/null
@@ -0,0 +1,311 @@
+/**
+ * collectd - src/redis.c, based on src/memcached.c
+ * Copyright (C) 2010       Andrés J. Díaz <ajdiaz@connectical.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Andrés J. Díaz <ajdiaz@connectical.com>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+
+#include <pthread.h>
+#include <credis.h>
+
+#define REDIS_DEF_HOST   "localhost"
+#define REDIS_DEF_PORT    6379
+#define REDIS_DEF_TIMEOUT 2000
+#define MAX_REDIS_NODE_NAME 64
+
+/* Redis plugin configuration example:
+ *
+ * <Plugin redis>
+ *   <Node "mynode">
+ *     Host "localhost"
+ *     Port "6379"
+ *     Timeout 2000
+ *   </Node>
+ * </Plugin>
+ */
+
+struct redis_node_s;
+typedef struct redis_node_s redis_node_t;
+struct redis_node_s
+{
+  char name[MAX_REDIS_NODE_NAME];
+  char host[HOST_NAME_MAX];
+  int port;
+  int timeout;
+
+  redis_node_t *next;
+};
+
+static redis_node_t *nodes_head = NULL;
+
+static int redis_node_add (const redis_node_t *rn) /* {{{ */
+{
+  redis_node_t *rn_copy;
+  redis_node_t *rn_ptr;
+
+  /* Check for duplicates first */
+  for (rn_ptr = nodes_head; rn_ptr != NULL; rn_ptr = rn_ptr->next)
+    if (strcmp (rn->name, rn_ptr->name) == 0)
+      break;
+
+  if (rn_ptr != NULL)
+  {
+    ERROR ("redis plugin: A node with the name `%s' already exists.",
+        rn->name);
+    return (-1);
+  }
+
+  rn_copy = malloc (sizeof (*rn_copy));
+  if (rn_copy == NULL)
+  {
+    ERROR ("redis plugin: malloc failed adding redis_node to the tree.");
+    return (-1);
+  }
+
+  memcpy (rn_copy, rn, sizeof (*rn_copy));
+  rn_copy->next = NULL;
+
+  DEBUG ("redis plugin: Adding node \"%s\".", rn->name);
+
+  if (nodes_head == NULL)
+    nodes_head = rn_copy;
+  else
+  {
+    rn_ptr = nodes_head;
+    while (rn_ptr->next != NULL)
+      rn_ptr = rn_ptr->next;
+    rn_ptr->next = rn_copy;
+  }
+
+  return (0);
+} /* }}} */
+
+static int redis_config_node (oconfig_item_t *ci) /* {{{ */
+{
+  redis_node_t rn;
+  int i;
+  int status;
+
+  memset (&rn, 0, sizeof (rn));
+  sstrncpy (rn.host, REDIS_DEF_HOST, sizeof (rn.host));
+  rn.port = REDIS_DEF_PORT;
+  rn.timeout = REDIS_DEF_TIMEOUT;
+
+  status = cf_util_get_string_buffer (ci, rn.name, sizeof (rn.name));
+  if (status != 0)
+    return (status);
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+
+    if (strcasecmp ("Host", option->key) == 0)
+      status = cf_util_get_string_buffer (option, rn.host, sizeof (rn.host));
+    else if (strcasecmp ("Port", option->key) == 0)
+    {
+      status = cf_util_get_port_number (option);
+      if (status > 0)
+      {
+        rn.port = status;
+        status = 0;
+      }
+    }
+    else if (strcasecmp ("Timeout", option->key) == 0)
+      status = cf_util_get_int (option, &rn.timeout);
+    else
+      WARNING ("redis plugin: Option `%s' not allowed inside a `Node' "
+          "block. I'll ignore this option.", option->key);
+
+    if (status != 0)
+      break;
+  }
+
+  if (status != 0)
+    return (status);
+
+  return (redis_node_add (&rn));
+} /* }}} int redis_config_node */
+
+static int redis_config (oconfig_item_t *ci) /* {{{ */
+{
+  int i;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+
+    if (strcasecmp ("Node", option->key) == 0)
+      redis_config_node (option);
+    else
+      WARNING ("redis plugin: Option `%s' not allowed in redis"
+          " configuration. It will be ignored.", option->key);
+  }
+
+  if (nodes_head == NULL)
+  {
+    ERROR ("redis plugin: No valid node configuration could be found.");
+    return (ENOENT);
+  }
+
+  return (0);
+} /* }}} */
+
+  __attribute__ ((nonnull(2)))
+static void redis_submit_g (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 = 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));
+  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);
+} /* }}} */
+
+  __attribute__ ((nonnull(2)))
+static void redis_submit_d (char *plugin_instance,
+    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_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));
+  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 int redis_init (void) /* {{{ */
+{
+  redis_node_t rn = { "default", REDIS_DEF_HOST, REDIS_DEF_PORT,
+    REDIS_DEF_TIMEOUT, /* next = */ NULL };
+
+  if (nodes_head == NULL)
+    redis_node_add (&rn);
+
+  return (0);
+} /* }}} int redis_init */
+
+static int redis_read (void) /* {{{ */
+{
+  redis_node_t *rn;
+
+  for (rn = nodes_head; rn != NULL; rn = rn->next)
+  {
+    REDIS rh;
+    REDIS_INFO info;
+
+    int status;
+
+    DEBUG ("redis plugin: querying info from node `%s' (%s:%d).", rn->name, rn->host, rn->port);
+
+    rh = credis_connect (rn->host, rn->port, rn->timeout);
+    if (rh == NULL)
+    {
+      ERROR ("redis plugin: unable to connect to node `%s' (%s:%d).", rn->name, rn->host, rn->port);
+      continue;
+    }
+
+    memset (&info, 0, sizeof (info));
+    status = credis_info (rh, &info);
+    if (status != 0)
+    {
+      WARNING ("redis plugin: unable to get info from node `%s'.", rn->name);
+      credis_close (rh);
+      continue;
+    }
+
+    /* typedef struct _cr_info {
+     *   char redis_version[CREDIS_VERSION_STRING_SIZE];
+     *   int bgsave_in_progress;
+     *   int connected_clients;
+     *   int connected_slaves;
+     *   unsigned int used_memory;
+     *   long long changes_since_last_save;
+     *   int last_save_time;
+     *   long long total_connections_received;
+     *   long long total_commands_processed;
+     *   int uptime_in_seconds;
+     *   int uptime_in_days;
+     *   int role;
+     * } REDIS_INFO; */
+
+    DEBUG ("redis plugin: received info from node `%s': connected_clients = %d; "
+        "connected_slaves = %d; used_memory = %lu; changes_since_last_save = %lld; "
+        "bgsave_in_progress = %d; total_connections_received = %lld; "
+        "total_commands_processed = %lld; uptime_in_seconds = %ld", rn->name,
+        info.connected_clients, info.connected_slaves, info.used_memory,
+        info.changes_since_last_save, info.bgsave_in_progress,
+        info.total_connections_received, info.total_commands_processed,
+        info.uptime_in_seconds);
+
+    redis_submit_g (rn->name, "current_connections", "clients", info.connected_clients);
+    redis_submit_g (rn->name, "current_connections", "slaves", info.connected_slaves);
+    redis_submit_g (rn->name, "memory", "used", info.used_memory);
+    redis_submit_g (rn->name, "volatile_changes", NULL, info.changes_since_last_save);
+    redis_submit_d (rn->name, "total_connections", NULL, info.total_connections_received);
+    redis_submit_d (rn->name, "total_operations", NULL, info.total_commands_processed);
+
+    credis_close (rh);
+  }
+
+  return 0;
+}
+/* }}} */
+
+void module_register (void) /* {{{ */
+{
+  plugin_register_complex_config ("redis", redis_config);
+  plugin_register_init ("redis", redis_init);
+  plugin_register_read ("redis", redis_read);
+  /* TODO: plugin_register_write: one redis list per value id with
+   * X elements */
+}
+/* }}} */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/routeros.c b/src/routeros.c
new file mode 100644 (file)
index 0000000..d61ffe9
--- /dev/null
@@ -0,0 +1,444 @@
+/**
+ * collectd - src/routeros.c
+ * Copyright (C) 2009,2010  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at collectd.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include <routeros_api.h>
+
+struct cr_data_s
+{
+  ros_connection_t *connection;
+
+  char *node;
+  char *service;
+  char *username;
+  char *password;
+
+  _Bool collect_interface;
+  _Bool collect_regtable;
+  _Bool collect_cpu_load;
+  _Bool collect_memory;
+  _Bool collect_df;
+  _Bool collect_disk;
+};
+typedef struct cr_data_s cr_data_t;
+
+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;
+
+       vl.values = values;
+       vl.values_len = STATIC_ARRAY_SIZE (values);
+       sstrncpy (vl.host, rd->node, sizeof (vl.host));
+       sstrncpy (vl.plugin, "routeros", sizeof (vl.plugin));
+       sstrncpy (vl.type, type, sizeof (vl.type));
+       sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
+
+       plugin_dispatch_values (&vl);
+} /* }}} void cr_submit_io */
+
+static void submit_interface (cr_data_t *rd, /* {{{ */
+    const ros_interface_t *i)
+{
+  if (i == NULL)
+    return;
+
+  if (!i->running)
+  {
+    submit_interface (rd, i->next);
+    return;
+  }
+
+  cr_submit_io (rd, "if_packets", i->name,
+      (derive_t) i->rx_packets, (derive_t) i->tx_packets);
+  cr_submit_io (rd, "if_octets", i->name,
+      (derive_t) i->rx_bytes, (derive_t) i->tx_bytes);
+  cr_submit_io (rd, "if_errors", i->name,
+      (derive_t) i->rx_errors, (derive_t) i->tx_errors);
+  cr_submit_io (rd, "if_dropped", i->name,
+      (derive_t) i->rx_drops, (derive_t) i->tx_drops);
+
+  submit_interface (rd, i->next);
+} /* }}} void submit_interface */
+
+static int handle_interface (__attribute__((unused)) ros_connection_t *c, /* {{{ */
+    const ros_interface_t *i, void *user_data)
+{
+  if ((i == NULL) || (user_data == NULL))
+    return (EINVAL);
+
+  submit_interface (user_data, i);
+  return (0);
+} /* }}} int handle_interface */
+
+static void cr_submit_gauge (cr_data_t *rd, 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, rd->node, sizeof (vl.host));
+       sstrncpy (vl.plugin, "routeros", sizeof (vl.plugin));
+       sstrncpy (vl.type, type, sizeof (vl.type));
+       sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
+
+       plugin_dispatch_values (&vl);
+} /* }}} void cr_submit_gauge */
+
+#if ROS_VERSION >= ROS_VERSION_ENCODE(1, 1, 0)
+static void cr_submit_counter (cr_data_t *rd, const char *type, /* {{{ */
+    const char *type_instance, derive_t value)
+{
+       value_t values[1];
+       value_list_t vl = VALUE_LIST_INIT;
+
+       values[0].derive = value;
+
+       vl.values = values;
+       vl.values_len = STATIC_ARRAY_SIZE (values);
+       sstrncpy (vl.host, rd->node, sizeof (vl.host));
+       sstrncpy (vl.plugin, "routeros", sizeof (vl.plugin));
+       sstrncpy (vl.type, type, sizeof (vl.type));
+       sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
+
+       plugin_dispatch_values (&vl);
+} /* }}} void cr_submit_gauge */
+#endif
+
+static void submit_regtable (cr_data_t *rd, /* {{{ */
+    const ros_registration_table_t *r)
+{
+  char type_instance[DATA_MAX_NAME_LEN];
+
+  if (r == NULL)
+    return;
+
+  /*** RX ***/
+  ssnprintf (type_instance, sizeof (type_instance), "%s-%s-rx",
+      r->interface, r->radio_name);
+  cr_submit_gauge (rd, "bitrate", type_instance,
+      (gauge_t) (1000000.0 * r->rx_rate));
+  cr_submit_gauge (rd, "signal_power", type_instance,
+      (gauge_t) r->rx_signal_strength);
+  cr_submit_gauge (rd, "signal_quality", type_instance,
+      (gauge_t) r->rx_ccq);
+
+  /*** TX ***/
+  ssnprintf (type_instance, sizeof (type_instance), "%s-%s-tx",
+      r->interface, r->radio_name);
+  cr_submit_gauge (rd, "bitrate", type_instance,
+      (gauge_t) (1000000.0 * r->tx_rate));
+  cr_submit_gauge (rd, "signal_power", type_instance,
+      (gauge_t) r->tx_signal_strength);
+  cr_submit_gauge (rd, "signal_quality", type_instance,
+      (gauge_t) r->tx_ccq);
+
+  /*** RX / TX ***/
+  ssnprintf (type_instance, sizeof (type_instance), "%s-%s",
+      r->interface, r->radio_name);
+  cr_submit_io (rd, "if_octets", type_instance,
+      (derive_t) r->rx_bytes, (derive_t) r->tx_bytes);
+  cr_submit_gauge (rd, "snr", type_instance, (gauge_t) r->signal_to_noise);
+
+  submit_regtable (rd, r->next);
+} /* }}} void submit_regtable */
+
+static int handle_regtable (__attribute__((unused)) ros_connection_t *c, /* {{{ */
+    const ros_registration_table_t *r, void *user_data)
+{
+  if ((r == NULL) || (user_data == NULL))
+    return (EINVAL);
+
+  submit_regtable (user_data, r);
+  return (0);
+} /* }}} int handle_regtable */
+
+#if ROS_VERSION >= ROS_VERSION_ENCODE(1, 1, 0)
+static int handle_system_resource (__attribute__((unused)) ros_connection_t *c, /* {{{ */
+        const ros_system_resource_t *r,
+       __attribute__((unused)) void *user_data)
+{
+  cr_data_t *rd;
+
+  if ((r == NULL) || (user_data == NULL))
+    return (EINVAL);
+  rd = user_data;
+
+  if (rd->collect_cpu_load)
+    cr_submit_gauge (rd, "gauge", "cpu_load", (gauge_t) r->cpu_load);
+
+  if (rd->collect_memory)
+  {
+    cr_submit_gauge (rd, "memory", "used",
+       (gauge_t) (r->total_memory - r->free_memory));
+    cr_submit_gauge (rd, "memory", "free", (gauge_t) r->free_memory);
+  }
+
+  if (rd->collect_df)
+  {
+    cr_submit_gauge (rd, "df_complex", "used",
+       (gauge_t) (r->total_memory - r->free_memory));
+    cr_submit_gauge (rd, "df_complex", "free", (gauge_t) r->free_memory);
+  }
+
+  if (rd->collect_disk)
+  {
+    cr_submit_counter (rd, "counter", "secors_written", (derive_t) r->write_sect_total);
+    cr_submit_gauge (rd, "gauge", "bad_blocks", (gauge_t) r->bad_blocks);
+  }
+
+  return (0);
+} /* }}} int handle_system_resource */
+#endif
+
+static int cr_read (user_data_t *user_data) /* {{{ */
+{
+  int status;
+  cr_data_t *rd;
+
+  if (user_data == NULL)
+    return (EINVAL);
+
+  rd = user_data->data;
+  if (rd == NULL)
+    return (EINVAL);
+
+  if (rd->connection == NULL)
+  {
+    rd->connection = ros_connect (rd->node, rd->service,
+       rd->username, rd->password);
+    if (rd->connection == NULL)
+    {
+      char errbuf[128];
+      ERROR ("routeros plugin: ros_connect failed: %s",
+         sstrerror (errno, errbuf, sizeof (errbuf)));
+      return (-1);
+    }
+  }
+  assert (rd->connection != NULL);
+
+  if (rd->collect_interface)
+  {
+    status = ros_interface (rd->connection, handle_interface,
+       /* user data = */ rd);
+    if (status != 0)
+    {
+      char errbuf[128];
+      ERROR ("routeros plugin: ros_interface failed: %s",
+         sstrerror (status, errbuf, sizeof (errbuf)));
+      ros_disconnect (rd->connection);
+      rd->connection = NULL;
+      return (-1);
+    }
+  }
+
+  if (rd->collect_regtable)
+  {
+    status = ros_registration_table (rd->connection, handle_regtable,
+       /* user data = */ rd);
+    if (status != 0)
+    {
+      char errbuf[128];
+      ERROR ("routeros plugin: ros_registration_table failed: %s",
+         sstrerror (status, errbuf, sizeof (errbuf)));
+      ros_disconnect (rd->connection);
+      rd->connection = NULL;
+      return (-1);
+    }
+  }
+
+#if ROS_VERSION >= ROS_VERSION_ENCODE(1, 1, 0)
+  if (rd->collect_cpu_load
+      || rd->collect_memory
+      || rd->collect_df
+      || rd->collect_disk)
+  {
+    status = ros_system_resource (rd->connection, handle_system_resource,
+       /* user data = */ rd);
+    if (status != 0)
+    {
+      char errbuf[128];
+      ERROR ("routeros plugin: ros_system_resource failed: %s",
+         sstrerror (status, errbuf, sizeof (errbuf)));
+      ros_disconnect (rd->connection);
+      rd->connection = NULL;
+      return (-1);
+    }
+  }
+#endif
+
+  return (0);
+} /* }}} int cr_read */
+
+static void cr_free_data (cr_data_t *ptr) /* {{{ */
+{
+  if (ptr == NULL)
+    return;
+
+  ros_disconnect (ptr->connection);
+  ptr->connection = NULL;
+
+  sfree (ptr->node);
+  sfree (ptr->service);
+  sfree (ptr->username);
+  sfree (ptr->password);
+
+  sfree (ptr);
+} /* }}} void cr_free_data */
+
+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;
+  int i;
+
+  router_data = malloc (sizeof (*router_data));
+  if (router_data == NULL)
+    return (-1);
+  memset (router_data, 0, sizeof (router_data));
+  router_data->connection = NULL;
+  router_data->node = NULL;
+  router_data->service = NULL;
+  router_data->username = NULL;
+  router_data->password = NULL;
+
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Host", child->key) == 0)
+      status = cf_util_get_string (child, &router_data->node);
+    else if (strcasecmp ("Port", child->key) == 0)
+      status = cf_util_get_string (child, &router_data->service);
+    else if (strcasecmp ("User", child->key) == 0)
+      status = cf_util_get_string (child, &router_data->username);
+    else if (strcasecmp ("Password", child->key) == 0)
+      status = cf_util_get_string (child, &router_data->password);
+    else if (strcasecmp ("CollectInterface", child->key) == 0)
+      cf_util_get_boolean (child, &router_data->collect_interface);
+    else if (strcasecmp ("CollectRegistrationTable", child->key) == 0)
+      cf_util_get_boolean (child, &router_data->collect_regtable);
+#if ROS_VERSION >= ROS_VERSION_ENCODE(1, 1, 0)
+    else if (strcasecmp ("CollectCPULoad", child->key) == 0)
+      cf_util_get_boolean (child, &router_data->collect_cpu_load);
+    else if (strcasecmp ("CollectMemory", child->key) == 0)
+      cf_util_get_boolean (child, &router_data->collect_memory);
+    else if (strcasecmp ("CollectDF", child->key) == 0)
+      cf_util_get_boolean (child, &router_data->collect_df);
+    else if (strcasecmp ("CollectDisk", child->key) == 0)
+      cf_util_get_boolean (child, &router_data->collect_disk);
+#endif
+    else
+    {
+      WARNING ("routeros plugin: Unknown config option `%s'.", child->key);
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  if (status == 0)
+  {
+    if (router_data->node == NULL)
+    {
+      ERROR ("routeros plugin: No `Host' option within a `Router' block. "
+         "Where should I connect to?");
+      status = -1;
+    }
+
+    if (router_data->password == NULL)
+    {
+      ERROR ("routeros plugin: No `Password' option within a `Router' block. "
+         "How should I authenticate?");
+      status = -1;
+    }
+
+    if (!router_data->collect_interface
+       && !router_data->collect_regtable)
+    {
+      ERROR ("routeros plugin: No `Collect*' option within a `Router' block. "
+         "What statistics should I collect?");
+      status = -1;
+    }
+  }
+
+  if ((status == 0) && (router_data->username == NULL))
+  {
+    router_data->username = sstrdup ("admin");
+    if (router_data->username == NULL)
+    {
+      ERROR ("routeros plugin: sstrdup failed.");
+      status = -1;
+    }
+  }
+
+  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 = */ NULL, &user_data);
+
+  if (status != 0)
+    cr_free_data (router_data);
+
+  return (status);
+} /* }}} int cr_config_router */
+
+static int cr_config (oconfig_item_t *ci)
+{
+  int i;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Router", child->key) == 0)
+      cr_config_router (child);
+    else
+    {
+      WARNING ("routeros plugin: Unknown config option `%s'.", child->key);
+    }
+  }
+
+  return (0);
+} /* }}} int cr_config */
+
+void module_register (void)
+{
+  plugin_register_complex_config ("routeros", cr_config);
+} /* void module_register */
+
+/* vim: set sw=2 noet fdm=marker : */
diff --git a/src/rrdcached.c b/src/rrdcached.c
new file mode 100644 (file)
index 0000000..11c1c6a
--- /dev/null
@@ -0,0 +1,494 @@
+/**
+ * collectd - src/rrdcached.c
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "plugin.h"
+#include "common.h"
+#include "utils_rrdcreate.h"
+
+#undef HAVE_CONFIG_H
+#include <rrd.h>
+#include <rrd_client.h>
+
+/*
+ * Private variables
+ */
+static char *datadir = NULL;
+static char *daemon_address = NULL;
+static int config_create_files = 1;
+static int config_collect_stats = 1;
+static rrdcreate_config_t rrdcreate_config =
+{
+       /* stepsize = */ 0,
+       /* heartbeat = */ 0,
+       /* rrarows = */ 1200,
+       /* xff = */ 0.1,
+
+       /* timespans = */ NULL,
+       /* timespans_num = */ 0,
+
+       /* consolidation_functions = */ NULL,
+       /* consolidation_functions_num = */ 0
+};
+
+/*
+ * Prototypes.
+ */
+static int rc_write (const data_set_t *ds, const value_list_t *vl,
+    user_data_t __attribute__((unused)) *user_data);
+static int rc_flush (__attribute__((unused)) cdtime_t timeout,
+    const char *identifier, __attribute__((unused)) user_data_t *ud);
+
+static int value_list_to_string (char *buffer, int buffer_len,
+    const data_set_t *ds, const value_list_t *vl)
+{
+  int offset;
+  int status;
+  int i;
+  time_t t;
+
+  assert (0 == strcmp (ds->type, vl->type));
+
+  memset (buffer, '\0', buffer_len);
+
+  t = CDTIME_T_TO_TIME_T (vl->time);
+  status = ssnprintf (buffer, buffer_len, "%lu", (unsigned long) t);
+  if ((status < 1) || (status >= buffer_len))
+    return (-1);
+  offset = status;
+
+  for (i = 0; i < ds->ds_num; i++)
+  {
+    if ((ds->ds[i].type != DS_TYPE_COUNTER)
+        && (ds->ds[i].type != DS_TYPE_GAUGE)
+       && (ds->ds[i].type != DS_TYPE_DERIVE)
+       && (ds->ds[i].type != DS_TYPE_ABSOLUTE))
+      return (-1);
+
+    if (ds->ds[i].type == DS_TYPE_COUNTER)
+    {
+      status = ssnprintf (buffer + offset, buffer_len - offset,
+          ":%llu", vl->values[i].counter);
+    }
+    else if (ds->ds[i].type == DS_TYPE_GAUGE) 
+    {
+      status = ssnprintf (buffer + offset, buffer_len - offset,
+          ":%f", vl->values[i].gauge);
+    }
+    else if (ds->ds[i].type == DS_TYPE_DERIVE) {
+      status = ssnprintf (buffer + offset, buffer_len - offset,
+         ":%"PRIi64, vl->values[i].derive);
+    }
+    else /* if (ds->ds[i].type == DS_TYPE_ABSOLUTE) */ {
+      status = ssnprintf (buffer + offset, buffer_len - offset,
+         ":%"PRIu64, vl->values[i].absolute);
+    }
+
+    if ((status < 1) || (status >= (buffer_len - offset)))
+      return (-1);
+
+    offset += status;
+  } /* for ds->ds_num */
+
+  return (0);
+} /* int value_list_to_string */
+
+static int value_list_to_filename (char *buffer, int buffer_len,
+    const data_set_t *ds, const value_list_t *vl)
+{
+  int offset = 0;
+  int status;
+
+  assert (0 == strcmp (ds->type, vl->type));
+
+  if (datadir != NULL)
+  {
+    status = ssnprintf (buffer + offset, buffer_len - offset,
+        "%s/", datadir);
+    if ((status < 1) || (status >= buffer_len - offset))
+      return (-1);
+    offset += status;
+  }
+
+  status = ssnprintf (buffer + offset, buffer_len - offset,
+      "%s/", vl->host);
+  if ((status < 1) || (status >= buffer_len - offset))
+    return (-1);
+  offset += status;
+
+  if (strlen (vl->plugin_instance) > 0)
+    status = ssnprintf (buffer + offset, buffer_len - offset,
+        "%s-%s/", vl->plugin, vl->plugin_instance);
+  else
+    status = ssnprintf (buffer + offset, buffer_len - offset,
+        "%s/", vl->plugin);
+  if ((status < 1) || (status >= buffer_len - offset))
+    return (-1);
+  offset += status;
+
+  if (strlen (vl->type_instance) > 0)
+    status = ssnprintf (buffer + offset, buffer_len - offset,
+        "%s-%s", vl->type, vl->type_instance);
+  else
+    status = ssnprintf (buffer + offset, buffer_len - offset,
+        "%s", vl->type);
+  if ((status < 1) || (status >= buffer_len - offset))
+    return (-1);
+  offset += status;
+
+  strncpy (buffer + offset, ".rrd", buffer_len - offset);
+  buffer[buffer_len - 1] = 0;
+
+  return (0);
+} /* int value_list_to_filename */
+
+static const char *config_get_string (oconfig_item_t *ci)
+{
+  if ((ci->children_num != 0) || (ci->values_num != 1)
+      || ((ci->values[0].type != OCONFIG_TYPE_STRING)
+        && (ci->values[0].type != OCONFIG_TYPE_BOOLEAN)))
+  {
+    ERROR ("rrdcached plugin: %s expects a single string argument.",
+        ci->key);
+    return (NULL);
+  }
+
+  if (ci->values[0].type == OCONFIG_TYPE_BOOLEAN) {
+    if (ci->values[0].value.boolean)
+      return "true";
+    else
+      return "false";
+  }
+  return (ci->values[0].value.string);
+} /* const char *config_get_string */
+
+static int rc_config (oconfig_item_t *ci)
+{
+  int i;
+
+  for (i = 0; i < ci->children_num; ++i) {
+    const char *key = ci->children[i].key;
+    const char *value = config_get_string (ci->children + i);
+
+    if (value == NULL) /* config_get_strings prints error message */
+      continue;
+
+    if (strcasecmp ("DataDir", key) == 0)
+    {
+      if (datadir != NULL)
+        free (datadir);
+      datadir = strdup (value);
+      if (datadir != NULL)
+      {
+        int len = strlen (datadir);
+        while ((len > 0) && (datadir[len - 1] == '/'))
+        {
+          len--;
+          datadir[len] = '\0';
+        }
+        if (len <= 0)
+        {
+          free (datadir);
+          datadir = NULL;
+        }
+      }
+    }
+    else if (strcasecmp ("DaemonAddress", key) == 0)
+    {
+      sfree (daemon_address);
+      daemon_address = strdup (value);
+      if (daemon_address == NULL)
+      {
+        ERROR ("rrdcached plugin: strdup failed.");
+        continue;
+      }
+    }
+    else if (strcasecmp ("CreateFiles", key) == 0)
+    {
+      if (IS_FALSE (value))
+        config_create_files = 0;
+      else
+        config_create_files = 1;
+    }
+    else if (strcasecmp ("CollectStatistics", key) == 0)
+    {
+      if (IS_FALSE (value))
+        config_collect_stats = 0;
+      else
+        config_collect_stats = 1;
+    }
+    else
+    {
+      WARNING ("rrdcached plugin: Ignoring invalid option %s.", key);
+      continue;
+    }
+  }
+
+  if (daemon_address != NULL) {
+    plugin_register_write ("rrdcached", rc_write, /* user_data = */ NULL);
+    plugin_register_flush ("rrdcached", rc_flush, /* user_data = */ NULL);
+  }
+  return (0);
+} /* int rc_config */
+
+static int rc_read (void)
+{
+  int status;
+  rrdc_stats_t *head;
+  rrdc_stats_t *ptr;
+
+  value_t values[1];
+  value_list_t vl = VALUE_LIST_INIT;
+
+  if (daemon_address == NULL)
+    return (-1);
+
+  if (config_collect_stats == 0)
+    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
+    sstrncpy (vl.host, daemon_address, sizeof (vl.host));
+  sstrncpy (vl.plugin, "rrdcached", sizeof (vl.plugin));
+
+  head = NULL;
+  status = rrdc_stats_get (&head);
+  if (status != 0)
+  {
+    ERROR ("rrdcached plugin: rrdc_stats_get failed with status %i.", status);
+    return (-1);
+  }
+
+  for (ptr = head; ptr != NULL; ptr = ptr->next)
+  {
+    if (ptr->type == RRDC_STATS_TYPE_GAUGE)
+      values[0].gauge = (gauge_t) ptr->value.gauge;
+    else if (ptr->type == RRDC_STATS_TYPE_COUNTER)
+      values[0].counter = (counter_t) ptr->value.counter;
+    else
+      continue;
+
+    if (strcasecmp ("QueueLength", ptr->name) == 0)
+    {
+      sstrncpy (vl.type, "queue_length", sizeof (vl.type));
+      sstrncpy (vl.type_instance, "", sizeof (vl.type_instance));
+    }
+    else if (strcasecmp ("UpdatesWritten", ptr->name) == 0)
+    {
+      sstrncpy (vl.type, "operations", sizeof (vl.type));
+      sstrncpy (vl.type_instance, "write-updates", sizeof (vl.type_instance));
+    }
+    else if (strcasecmp ("DataSetsWritten", ptr->name) == 0)
+    {
+      sstrncpy (vl.type, "operations", sizeof (vl.type));
+      sstrncpy (vl.type_instance, "write-data_sets",
+          sizeof (vl.type_instance));
+    }
+    else if (strcasecmp ("TreeNodesNumber", ptr->name) == 0)
+    {
+      sstrncpy (vl.type, "gauge", sizeof (vl.type));
+      sstrncpy (vl.type_instance, "tree_nodes", sizeof (vl.type_instance));
+    }
+    else if (strcasecmp ("TreeDepth", ptr->name) == 0)
+    {
+      sstrncpy (vl.type, "gauge", sizeof (vl.type));
+      sstrncpy (vl.type_instance, "tree_depth", sizeof (vl.type_instance));
+    }
+    else if (strcasecmp ("FlushesReceived", ptr->name) == 0)
+    {
+      sstrncpy (vl.type, "operations", sizeof (vl.type));
+      sstrncpy (vl.type_instance, "receive-flush", sizeof (vl.type_instance));
+    }
+    else if (strcasecmp ("JournalBytes", ptr->name) == 0)
+    {
+      sstrncpy (vl.type, "counter", sizeof (vl.type));
+      sstrncpy (vl.type_instance, "journal-bytes", sizeof (vl.type_instance));
+    }
+    else if (strcasecmp ("JournalRotate", ptr->name) == 0)
+    {
+      sstrncpy (vl.type, "counter", sizeof (vl.type));
+      sstrncpy (vl.type_instance, "journal-rotates", sizeof (vl.type_instance));
+    }
+    else if (strcasecmp ("UpdatesReceived", ptr->name) == 0)
+    {
+      sstrncpy (vl.type, "operations", sizeof (vl.type));
+      sstrncpy (vl.type_instance, "receive-update", sizeof (vl.type_instance));
+    }
+    else
+    {
+      DEBUG ("rrdcached plugin: rc_read: Unknown statistic `%s'.", ptr->name);
+      continue;
+    }
+
+    plugin_dispatch_values (&vl);
+  } /* for (ptr = head; ptr != NULL; ptr = ptr->next) */
+
+  rrdc_stats_free (head);
+
+  return (0);
+} /* int rc_read */
+
+static int rc_init (void)
+{
+  if (config_collect_stats != 0)
+    plugin_register_read ("rrdcached", rc_read);
+
+  return (0);
+} /* int rc_init */
+
+static int rc_write (const data_set_t *ds, const value_list_t *vl,
+    user_data_t __attribute__((unused)) *user_data)
+{
+  char filename[PATH_MAX];
+  char values[512];
+  char *values_array[2];
+  int status;
+
+  if (daemon_address == NULL)
+  {
+    ERROR ("rrdcached plugin: daemon_address == NULL.");
+    plugin_unregister_write ("rrdcached");
+    return (-1);
+  }
+
+  if (strcmp (ds->type, vl->type) != 0)
+  {
+    ERROR ("rrdcached plugin: DS type does not match value list type");
+    return (-1);
+  }
+
+  if (value_list_to_filename (filename, sizeof (filename), ds, vl) != 0)
+  {
+    ERROR ("rrdcached plugin: value_list_to_filename failed.");
+    return (-1);
+  }
+
+  if (value_list_to_string (values, sizeof (values), ds, vl) != 0)
+  {
+    ERROR ("rrdcached plugin: value_list_to_string failed.");
+    return (-1);
+  }
+
+  values_array[0] = values;
+  values_array[1] = NULL;
+
+  if (config_create_files != 0)
+  {
+    struct stat statbuf;
+
+    status = stat (filename, &statbuf);
+    if (status != 0)
+    {
+      if (errno != ENOENT)
+      {
+        char errbuf[1024];
+        ERROR ("rrdcached plugin: stat (%s) failed: %s",
+            filename, sstrerror (errno, errbuf, sizeof (errbuf)));
+        return (-1);
+      }
+
+      status = cu_rrd_create_file (filename, ds, vl, &rrdcreate_config);
+      if (status != 0)
+      {
+        ERROR ("rrdcached plugin: cu_rrd_create_file (%s) failed.",
+            filename);
+        return (-1);
+      }
+    }
+  }
+
+  status = rrdc_connect (daemon_address);
+  if (status != 0)
+  {
+    ERROR ("rrdcached plugin: rrdc_connect (%s) failed with status %i.",
+        daemon_address, status);
+    return (-1);
+  }
+
+  status = rrdc_update (filename, /* values_num = */ 1, (void *) values_array);
+  if (status != 0)
+  {
+    ERROR ("rrdcached plugin: rrdc_update (%s, [%s], 1) failed with "
+        "status %i.",
+        filename, values_array[0], status);
+    return (-1);
+  }
+
+  return (0);
+} /* int rc_write */
+
+static int rc_flush (__attribute__((unused)) cdtime_t timeout, /* {{{ */
+    const char *identifier,
+    __attribute__((unused)) user_data_t *ud)
+{
+  char filename[PATH_MAX + 1];
+  int status;
+
+  if (identifier == NULL)
+    return (EINVAL);
+
+  if (datadir != NULL)
+    ssnprintf (filename, sizeof (filename), "%s/%s.rrd", datadir, identifier);
+  else
+    ssnprintf (filename, sizeof (filename), "%s.rrd", identifier);
+
+  status = rrdc_connect (daemon_address);
+  if (status != 0)
+  {
+    ERROR ("rrdcached plugin: rrdc_connect (%s) failed with status %i.",
+        daemon_address, status);
+    return (-1);
+  }
+
+  status = rrdc_flush (filename);
+  if (status != 0)
+  {
+    ERROR ("rrdcached plugin: rrdc_flush (%s) failed with status %i.",
+        filename, status);
+    return (-1);
+  }
+  DEBUG ("rrdcached plugin: rrdc_flush (%s): Success.", filename);
+
+  return (0);
+} /* }}} int rc_flush */
+
+static int rc_shutdown (void)
+{
+  rrdc_disconnect ();
+  return (0);
+} /* int rc_shutdown */
+
+void module_register (void)
+{
+  plugin_register_complex_config ("rrdcached", rc_config);
+  plugin_register_init ("rrdcached", rc_init);
+  plugin_register_shutdown ("rrdcached", rc_shutdown);
+} /* void module_register */
+
+/*
+ * vim: set sw=2 sts=2 et :
+ */
diff --git a/src/rrdtool.c b/src/rrdtool.c
new file mode 100644 (file)
index 0000000..56a82d0
--- /dev/null
@@ -0,0 +1,1226 @@
+/**
+ * collectd - src/rrdtool.c
+ * Copyright (C) 2006-2008  Florian octo Forster
+ * Copyright (C) 2008-2008  Sebastian Harl
+ * Copyright (C) 2009       Mariusz Gronczewski
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ *   Sebastian Harl <sh at tokkee.org>
+ *   Mariusz Gronczewski <xani666 at gmail.com>
+ **/
+
+#include "collectd.h"
+#include "plugin.h"
+#include "common.h"
+#include "utils_avltree.h"
+#include "utils_rrdcreate.h"
+
+#include <rrd.h>
+
+#if HAVE_PTHREAD_H
+# include <pthread.h>
+#endif
+
+/*
+ * Private types
+ */
+struct rrd_cache_s
+{
+       int      values_num;
+       char   **values;
+       cdtime_t first_value;
+       cdtime_t last_value;
+       int64_t  random_variation;
+       enum
+       {
+               FLAG_NONE   = 0x00,
+               FLAG_QUEUED = 0x01,
+               FLAG_FLUSHQ = 0x02
+       } flags;
+};
+typedef struct rrd_cache_s rrd_cache_t;
+
+enum rrd_queue_dir_e
+{
+  QUEUE_INSERT_FRONT,
+  QUEUE_INSERT_BACK
+};
+typedef enum rrd_queue_dir_e rrd_queue_dir_t;
+
+struct rrd_queue_s
+{
+       char *filename;
+       struct rrd_queue_s *next;
+};
+typedef struct rrd_queue_s rrd_queue_t;
+
+/*
+ * Private variables
+ */
+static const char *config_keys[] =
+{
+       "CacheTimeout",
+       "CacheFlush",
+       "DataDir",
+       "StepSize",
+       "HeartBeat",
+       "RRARows",
+       "RRATimespan",
+       "XFF",
+       "WritesPerSecond",
+       "RandomTimeout"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+/* If datadir is zero, the daemon's basedir is used. If stepsize or heartbeat
+ * is zero a default, depending on the `interval' member of the value list is
+ * being used. */
+static char *datadir   = NULL;
+static double write_rate = 0.0;
+static rrdcreate_config_t rrdcreate_config =
+{
+       /* stepsize = */ 0,
+       /* heartbeat = */ 0,
+       /* rrarows = */ 1200,
+       /* xff = */ 0.1,
+
+       /* timespans = */ NULL,
+       /* timespans_num = */ 0,
+
+       /* consolidation_functions = */ NULL,
+       /* consolidation_functions_num = */ 0
+};
+
+/* XXX: If you need to lock both, cache_lock and queue_lock, at the same time,
+ * 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    cache_flush_last;
+static c_avl_tree_t *cache = NULL;
+static pthread_mutex_t cache_lock = PTHREAD_MUTEX_INITIALIZER;
+
+static rrd_queue_t    *queue_head = NULL;
+static rrd_queue_t    *queue_tail = NULL;
+static rrd_queue_t    *flushq_head = NULL;
+static rrd_queue_t    *flushq_tail = NULL;
+static pthread_t       queue_thread;
+static int             queue_thread_running = 1;
+static pthread_mutex_t queue_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t  queue_cond = PTHREAD_COND_INITIALIZER;
+
+#if !HAVE_THREADSAFE_LIBRRD
+static pthread_mutex_t librrd_lock = PTHREAD_MUTEX_INITIALIZER;
+#endif
+
+static int do_shutdown = 0;
+
+#if HAVE_THREADSAFE_LIBRRD
+static int srrd_update (char *filename, char *template,
+               int argc, const char **argv)
+{
+       int status;
+
+       optind = 0; /* bug in librrd? */
+       rrd_clear_error ();
+
+       status = rrd_update_r (filename, template, argc, (void *) argv);
+
+       if (status != 0)
+       {
+               WARNING ("rrdtool plugin: rrd_update_r (%s) failed: %s",
+                               filename, rrd_get_error ());
+       }
+
+       return (status);
+} /* int srrd_update */
+/* #endif HAVE_THREADSAFE_LIBRRD */
+
+#else /* !HAVE_THREADSAFE_LIBRRD */
+static int srrd_update (char *filename, char *template,
+               int argc, const char **argv)
+{
+       int status;
+
+       int new_argc;
+       char **new_argv;
+
+       assert (template == NULL);
+
+       new_argc = 2 + argc;
+       new_argv = (char **) malloc ((new_argc + 1) * sizeof (char *));
+       if (new_argv == NULL)
+       {
+               ERROR ("rrdtool plugin: malloc failed.");
+               return (-1);
+       }
+
+       new_argv[0] = "update";
+       new_argv[1] = filename;
+
+       memcpy (new_argv + 2, argv, argc * sizeof (char *));
+       new_argv[new_argc] = NULL;
+
+       pthread_mutex_lock (&librrd_lock);
+       optind = 0; /* bug in librrd? */
+       rrd_clear_error ();
+
+       status = rrd_update (new_argc, new_argv);
+       pthread_mutex_unlock (&librrd_lock);
+
+       if (status != 0)
+       {
+               WARNING ("rrdtool plugin: rrd_update_r failed: %s: %s",
+                               argv[1], rrd_get_error ());
+       }
+
+       sfree (new_argv);
+
+       return (status);
+} /* int srrd_update */
+#endif /* !HAVE_THREADSAFE_LIBRRD */
+
+static int value_list_to_string (char *buffer, int buffer_len,
+               const data_set_t *ds, const value_list_t *vl)
+{
+       int offset;
+       int status;
+       time_t tt;
+       int i;
+
+       memset (buffer, '\0', buffer_len);
+
+       tt = CDTIME_T_TO_TIME_T (vl->time);
+       status = ssnprintf (buffer, buffer_len, "%u", (unsigned int) tt);
+       if ((status < 1) || (status >= buffer_len))
+               return (-1);
+       offset = status;
+
+       for (i = 0; i < ds->ds_num; i++)
+       {
+               if ((ds->ds[i].type != DS_TYPE_COUNTER)
+                               && (ds->ds[i].type != DS_TYPE_GAUGE)
+                               && (ds->ds[i].type != DS_TYPE_DERIVE)
+                               && (ds->ds[i].type != DS_TYPE_ABSOLUTE))
+                       return (-1);
+
+               if (ds->ds[i].type == DS_TYPE_COUNTER)
+                       status = ssnprintf (buffer + offset, buffer_len - offset,
+                                       ":%llu", vl->values[i].counter);
+               else if (ds->ds[i].type == DS_TYPE_GAUGE)
+                       status = ssnprintf (buffer + offset, buffer_len - offset,
+                                       ":%lf", vl->values[i].gauge);
+               else if (ds->ds[i].type == DS_TYPE_DERIVE)
+                       status = ssnprintf (buffer + offset, buffer_len - offset,
+                                       ":%"PRIi64, vl->values[i].derive);
+               else /*if (ds->ds[i].type == DS_TYPE_ABSOLUTE) */
+                       status = ssnprintf (buffer + offset, buffer_len - offset,
+                                       ":%"PRIu64, vl->values[i].absolute);
+
+               if ((status < 1) || (status >= (buffer_len - offset)))
+                       return (-1);
+
+               offset += status;
+       } /* for ds->ds_num */
+
+       return (0);
+} /* int value_list_to_string */
+
+static int value_list_to_filename (char *buffer, int buffer_len,
+               const data_set_t __attribute__((unused)) *ds, const value_list_t *vl)
+{
+       int offset = 0;
+       int status;
+
+       if (datadir != NULL)
+       {
+               status = ssnprintf (buffer + offset, buffer_len - offset,
+                               "%s/", datadir);
+               if ((status < 1) || (status >= buffer_len - offset))
+                       return (-1);
+               offset += status;
+       }
+
+       status = ssnprintf (buffer + offset, buffer_len - offset,
+                       "%s/", vl->host);
+       if ((status < 1) || (status >= buffer_len - offset))
+               return (-1);
+       offset += status;
+
+       if (strlen (vl->plugin_instance) > 0)
+               status = ssnprintf (buffer + offset, buffer_len - offset,
+                               "%s-%s/", vl->plugin, vl->plugin_instance);
+       else
+               status = ssnprintf (buffer + offset, buffer_len - offset,
+                               "%s/", vl->plugin);
+       if ((status < 1) || (status >= buffer_len - offset))
+               return (-1);
+       offset += status;
+
+       if (strlen (vl->type_instance) > 0)
+               status = ssnprintf (buffer + offset, buffer_len - offset,
+                               "%s-%s.rrd", vl->type, vl->type_instance);
+       else
+               status = ssnprintf (buffer + offset, buffer_len - offset,
+                               "%s.rrd", vl->type);
+       if ((status < 1) || (status >= buffer_len - offset))
+               return (-1);
+       offset += status;
+
+       return (0);
+} /* int value_list_to_filename */
+
+static void *rrd_queue_thread (void __attribute__((unused)) *data)
+{
+        struct timeval tv_next_update;
+        struct timeval tv_now;
+
+        gettimeofday (&tv_next_update, /* timezone = */ NULL);
+
+       while (42)
+       {
+               rrd_queue_t *queue_entry;
+               rrd_cache_t *cache_entry;
+               char **values;
+               int    values_num;
+               int    status;
+               int    i;
+
+               values = NULL;
+               values_num = 0;
+
+                pthread_mutex_lock (&queue_lock);
+                /* Wait for values to arrive */
+                while (42)
+                {
+                  struct timespec ts_wait;
+
+                  while ((flushq_head == NULL) && (queue_head == NULL)
+                      && (do_shutdown == 0))
+                    pthread_cond_wait (&queue_cond, &queue_lock);
+
+                  if ((flushq_head == NULL) && (queue_head == NULL))
+                    break;
+
+                  /* Don't delay if there's something to flush */
+                  if (flushq_head != NULL)
+                    break;
+
+                  /* Don't delay if we're shutting down */
+                  if (do_shutdown != 0)
+                    break;
+
+                  /* Don't delay if no delay was configured. */
+                  if (write_rate <= 0.0)
+                    break;
+
+                  gettimeofday (&tv_now, /* timezone = */ NULL);
+                  status = timeval_cmp (tv_next_update, tv_now, NULL);
+                  /* We're good to go */
+                  if (status <= 0)
+                    break;
+
+                  /* We're supposed to wait a bit with this update, so we'll
+                   * wait for the next addition to the queue or to the end of
+                   * the wait period - whichever comes first. */
+                  ts_wait.tv_sec = tv_next_update.tv_sec;
+                  ts_wait.tv_nsec = 1000 * tv_next_update.tv_usec;
+
+                  status = pthread_cond_timedwait (&queue_cond, &queue_lock,
+                      &ts_wait);
+                  if (status == ETIMEDOUT)
+                    break;
+                } /* while (42) */
+
+                /* XXX: If you need to lock both, cache_lock and queue_lock, at
+                 * the same time, ALWAYS lock `cache_lock' first! */
+
+                /* We're in the shutdown phase */
+                if ((flushq_head == NULL) && (queue_head == NULL))
+                {
+                  pthread_mutex_unlock (&queue_lock);
+                  break;
+                }
+
+                if (flushq_head != NULL)
+                {
+                  /* Dequeue the first flush entry */
+                  queue_entry = flushq_head;
+                  if (flushq_head == flushq_tail)
+                    flushq_head = flushq_tail = NULL;
+                  else
+                    flushq_head = flushq_head->next;
+                }
+                else /* if (queue_head != NULL) */
+                {
+                  /* Dequeue the first regular entry */
+                  queue_entry = queue_head;
+                  if (queue_head == queue_tail)
+                    queue_head = queue_tail = NULL;
+                  else
+                    queue_head = queue_head->next;
+                }
+
+               /* Unlock the queue again */
+               pthread_mutex_unlock (&queue_lock);
+
+               /* We now need the cache lock so the entry isn't updated while
+                * we make a copy of it's values */
+               pthread_mutex_lock (&cache_lock);
+
+               status = c_avl_get (cache, queue_entry->filename,
+                               (void *) &cache_entry);
+
+               if (status == 0)
+               {
+                       values = cache_entry->values;
+                       values_num = cache_entry->values_num;
+
+                       cache_entry->values = NULL;
+                       cache_entry->values_num = 0;
+                       cache_entry->flags = FLAG_NONE;
+               }
+
+               pthread_mutex_unlock (&cache_lock);
+
+               if (status != 0)
+               {
+                       sfree (queue_entry->filename);
+                       sfree (queue_entry);
+                       continue;
+               }
+
+               /* Update `tv_next_update' */
+               if (write_rate > 0.0) 
+                {
+                  gettimeofday (&tv_now, /* timezone = */ NULL);
+                  tv_next_update.tv_sec = tv_now.tv_sec;
+                  tv_next_update.tv_usec = tv_now.tv_usec
+                    + ((suseconds_t) (1000000 * write_rate));
+                  while (tv_next_update.tv_usec > 1000000)
+                  {
+                    tv_next_update.tv_sec++;
+                    tv_next_update.tv_usec -= 1000000;
+                  }
+                }
+
+               /* Write the values to the RRD-file */
+               srrd_update (queue_entry->filename, NULL,
+                               values_num, (const char **)values);
+               DEBUG ("rrdtool plugin: queue thread: Wrote %i value%s to %s",
+                               values_num, (values_num == 1) ? "" : "s",
+                               queue_entry->filename);
+
+               for (i = 0; i < values_num; i++)
+               {
+                       sfree (values[i]);
+               }
+               sfree (values);
+               sfree (queue_entry->filename);
+               sfree (queue_entry);
+       } /* while (42) */
+
+       pthread_exit ((void *) 0);
+       return ((void *) 0);
+} /* void *rrd_queue_thread */
+
+static int rrd_queue_enqueue (const char *filename,
+    rrd_queue_t **head, rrd_queue_t **tail)
+{
+  rrd_queue_t *queue_entry;
+
+  queue_entry = (rrd_queue_t *) malloc (sizeof (rrd_queue_t));
+  if (queue_entry == NULL)
+    return (-1);
+
+  queue_entry->filename = strdup (filename);
+  if (queue_entry->filename == NULL)
+  {
+    free (queue_entry);
+    return (-1);
+  }
+
+  queue_entry->next = NULL;
+
+  pthread_mutex_lock (&queue_lock);
+
+  if (*tail == NULL)
+    *head = queue_entry;
+  else
+    (*tail)->next = queue_entry;
+  *tail = queue_entry;
+
+  pthread_cond_signal (&queue_cond);
+  pthread_mutex_unlock (&queue_lock);
+
+  return (0);
+} /* int rrd_queue_enqueue */
+
+static int rrd_queue_dequeue (const char *filename,
+    rrd_queue_t **head, rrd_queue_t **tail)
+{
+  rrd_queue_t *this;
+  rrd_queue_t *prev;
+
+  pthread_mutex_lock (&queue_lock);
+
+  prev = NULL;
+  this = *head;
+
+  while (this != NULL)
+  {
+    if (strcmp (this->filename, filename) == 0)
+      break;
+    
+    prev = this;
+    this = this->next;
+  }
+
+  if (this == NULL)
+  {
+    pthread_mutex_unlock (&queue_lock);
+    return (-1);
+  }
+
+  if (prev == NULL)
+    *head = this->next;
+  else
+    prev->next = this->next;
+
+  if (this->next == NULL)
+    *tail = prev;
+
+  pthread_mutex_unlock (&queue_lock);
+
+  sfree (this->filename);
+  sfree (this);
+
+  return (0);
+} /* int rrd_queue_dequeue */
+
+/* XXX: You must hold "cache_lock" when calling this function! */
+static void rrd_cache_flush (cdtime_t timeout)
+{
+       rrd_cache_t *rc;
+       cdtime_t     now;
+
+       char **keys = NULL;
+       int    keys_num = 0;
+
+       char *key;
+       c_avl_iterator_t *iter;
+       int i;
+
+       DEBUG ("rrdtool plugin: Flushing cache, timeout = %.3f",
+                       CDTIME_T_TO_DOUBLE (timeout));
+
+       now = cdtime ();
+       timeout = TIME_T_TO_CDTIME_T (timeout);
+
+       /* Build a list of entries to be flushed */
+       iter = c_avl_get_iterator (cache);
+       while (c_avl_iterator_next (iter, (void *) &key, (void *) &rc) == 0)
+       {
+               if (rc->flags != FLAG_NONE)
+                       continue;
+               /* timeout == 0  =>  flush everything */
+               else if ((timeout != 0)
+                               && ((now - rc->first_value) < timeout))
+                       continue;
+               else if (rc->values_num > 0)
+               {
+                       int status;
+
+                       status = rrd_queue_enqueue (key, &queue_head,  &queue_tail);
+                       if (status == 0)
+                               rc->flags = FLAG_QUEUED;
+               }
+               else /* ancient and no values -> waste of memory */
+               {
+                       char **tmp = (char **) realloc ((void *) keys,
+                                       (keys_num + 1) * sizeof (char *));
+                       if (tmp == NULL)
+                       {
+                               char errbuf[1024];
+                               ERROR ("rrdtool plugin: "
+                                               "realloc failed: %s",
+                                               sstrerror (errno, errbuf,
+                                                       sizeof (errbuf)));
+                               c_avl_iterator_destroy (iter);
+                               sfree (keys);
+                               return;
+                       }
+                       keys = tmp;
+                       keys[keys_num] = key;
+                       keys_num++;
+               }
+       } /* while (c_avl_iterator_next) */
+       c_avl_iterator_destroy (iter);
+       
+       for (i = 0; i < keys_num; i++)
+       {
+               if (c_avl_remove (cache, keys[i], (void *) &key, (void *) &rc) != 0)
+               {
+                       DEBUG ("rrdtool plugin: c_avl_remove (%s) failed.", keys[i]);
+                       continue;
+               }
+
+               assert (rc->values == NULL);
+               assert (rc->values_num == 0);
+
+               sfree (rc);
+               sfree (key);
+               keys[i] = NULL;
+       } /* for (i = 0..keys_num) */
+
+       sfree (keys);
+
+       cache_flush_last = now;
+} /* void rrd_cache_flush */
+
+static int rrd_cache_flush_identifier (cdtime_t timeout,
+    const char *identifier)
+{
+  rrd_cache_t *rc;
+  cdtime_t now;
+  int status;
+  char key[2048];
+
+  if (identifier == NULL)
+  {
+    rrd_cache_flush (timeout);
+    return (0);
+  }
+
+  now = cdtime ();
+
+  if (datadir == NULL)
+    snprintf (key, sizeof (key), "%s.rrd",
+        identifier);
+  else
+    snprintf (key, sizeof (key), "%s/%s.rrd",
+        datadir, identifier);
+  key[sizeof (key) - 1] = 0;
+
+  status = c_avl_get (cache, key, (void *) &rc);
+  if (status != 0)
+  {
+    INFO ("rrdtool plugin: rrd_cache_flush_identifier: "
+        "c_avl_get (%s) failed. Does that file really exist?",
+        key);
+    return (status);
+  }
+
+  if (rc->flags == FLAG_FLUSHQ)
+  {
+    status = 0;
+  }
+  else if (rc->flags == FLAG_QUEUED)
+  {
+    rrd_queue_dequeue (key, &queue_head, &queue_tail);
+    status = rrd_queue_enqueue (key, &flushq_head, &flushq_tail);
+    if (status == 0)
+      rc->flags = FLAG_FLUSHQ;
+  }
+  else if ((now - rc->first_value) < timeout)
+  {
+    status = 0;
+  }
+  else if (rc->values_num > 0)
+  {
+    status = rrd_queue_enqueue (key, &flushq_head, &flushq_tail);
+    if (status == 0)
+      rc->flags = FLAG_FLUSHQ;
+  }
+
+  return (status);
+} /* int rrd_cache_flush_identifier */
+
+static int64_t rrd_get_random_variation (void)
+{
+  double dbl_timeout;
+  cdtime_t ctm_timeout;
+  double rand_fact;
+  _Bool negative;
+  int64_t ret;
+
+  if (random_timeout <= 0)
+    return (0);
+
+  /* Assure that "cache_timeout + random_variation" is never negative. */
+  if (random_timeout > cache_timeout)
+  {
+         INFO ("rrdtool plugin: Adjusting \"RandomTimeout\" to %.3f seconds.",
+                         CDTIME_T_TO_DOUBLE (cache_timeout));
+         random_timeout = cache_timeout;
+  }
+
+  /* This seems a bit complicated, but "random_timeout" is likely larger than
+   * RAND_MAX, so we can't simply use modulo here. */
+  dbl_timeout = CDTIME_T_TO_DOUBLE (random_timeout);
+  rand_fact = ((double) random ())
+    / ((double) RAND_MAX);
+  negative = (_Bool) (random () % 2);
+
+  ctm_timeout = DOUBLE_TO_CDTIME_T (dbl_timeout * rand_fact);
+
+  ret = (int64_t) ctm_timeout;
+  if (negative)
+    ret *= -1;
+
+  return (ret);
+} /* int64_t rrd_get_random_variation */
+
+static int rrd_cache_insert (const char *filename,
+               const char *value, cdtime_t value_time)
+{
+       rrd_cache_t *rc = NULL;
+       int new_rc = 0;
+       char **values_new;
+
+       pthread_mutex_lock (&cache_lock);
+
+       /* This shouldn't happen, but it did happen at least once, so we'll be
+        * careful. */
+       if (cache == NULL)
+       {
+               pthread_mutex_unlock (&cache_lock);
+               WARNING ("rrdtool plugin: cache == NULL.");
+               return (-1);
+       }
+
+       c_avl_get (cache, filename, (void *) &rc);
+
+       if (rc == NULL)
+       {
+               rc = malloc (sizeof (*rc));
+               if (rc == NULL)
+                       return (-1);
+               rc->values_num = 0;
+               rc->values = NULL;
+               rc->first_value = 0;
+               rc->last_value = 0;
+               rc->random_variation = rrd_get_random_variation ();
+               rc->flags = FLAG_NONE;
+               new_rc = 1;
+       }
+
+       if (rc->last_value >= value_time)
+       {
+               pthread_mutex_unlock (&cache_lock);
+               DEBUG ("rrdtool plugin: (rc->last_value = %"PRIu64") "
+                               ">= (value_time = %"PRIu64")",
+                               rc->last_value, value_time);
+               return (-1);
+       }
+
+       values_new = (char **) realloc ((void *) rc->values,
+                       (rc->values_num + 1) * sizeof (char *));
+       if (values_new == NULL)
+       {
+               char errbuf[1024];
+               void *cache_key = NULL;
+
+               sstrerror (errno, errbuf, sizeof (errbuf));
+
+               c_avl_remove (cache, filename, &cache_key, NULL);
+               pthread_mutex_unlock (&cache_lock);
+
+               ERROR ("rrdtool plugin: realloc failed: %s", errbuf);
+
+               sfree (cache_key);
+               sfree (rc->values);
+               sfree (rc);
+               return (-1);
+       }
+       rc->values = values_new;
+
+       rc->values[rc->values_num] = strdup (value);
+       if (rc->values[rc->values_num] != NULL)
+               rc->values_num++;
+
+       if (rc->values_num == 1)
+               rc->first_value = value_time;
+       rc->last_value = value_time;
+
+       /* Insert if this is the first value */
+       if (new_rc == 1)
+       {
+               void *cache_key = strdup (filename);
+
+               if (cache_key == NULL)
+               {
+                       char errbuf[1024];
+                       sstrerror (errno, errbuf, sizeof (errbuf));
+
+                       pthread_mutex_unlock (&cache_lock);
+
+                       ERROR ("rrdtool plugin: strdup failed: %s", errbuf);
+
+                       sfree (rc->values[0]);
+                       sfree (rc->values);
+                       sfree (rc);
+                       return (-1);
+               }
+
+               c_avl_insert (cache, cache_key, rc);
+       }
+
+       DEBUG ("rrdtool plugin: rrd_cache_insert: file = %s; "
+                       "values_num = %i; age = %.3f;",
+                       filename, rc->values_num,
+                       CDTIME_T_TO_DOUBLE (rc->last_value - rc->first_value));
+
+       if ((rc->last_value - rc->first_value) >= (cache_timeout + rc->random_variation))
+       {
+               /* XXX: If you need to lock both, cache_lock and queue_lock, at
+                * the same time, ALWAYS lock `cache_lock' first! */
+               if (rc->flags == FLAG_NONE)
+               {
+                       int status;
+
+                       status = rrd_queue_enqueue (filename, &queue_head, &queue_tail);
+                       if (status == 0)
+                               rc->flags = FLAG_QUEUED;
+
+                        rc->random_variation = rrd_get_random_variation ();
+               }
+               else
+               {
+                       DEBUG ("rrdtool plugin: `%s' is already queued.", filename);
+               }
+       }
+
+       if ((cache_timeout > 0) &&
+                       ((cdtime () - cache_flush_last) > cache_flush_timeout))
+               rrd_cache_flush (cache_flush_timeout);
+
+       pthread_mutex_unlock (&cache_lock);
+
+       return (0);
+} /* int rrd_cache_insert */
+
+static int rrd_cache_destroy (void) /* {{{ */
+{
+  void *key = NULL;
+  void *value = NULL;
+
+  int non_empty = 0;
+
+  pthread_mutex_lock (&cache_lock);
+
+  if (cache == NULL)
+  {
+    pthread_mutex_unlock (&cache_lock);
+    return (0);
+  }
+
+  while (c_avl_pick (cache, &key, &value) == 0)
+  {
+    rrd_cache_t *rc;
+    int i;
+
+    sfree (key);
+    key = NULL;
+
+    rc = value;
+    value = NULL;
+
+    if (rc->values_num > 0)
+      non_empty++;
+
+    for (i = 0; i < rc->values_num; i++)
+      sfree (rc->values[i]);
+    sfree (rc->values);
+    sfree (rc);
+  }
+
+  c_avl_destroy (cache);
+  cache = NULL;
+
+  if (non_empty > 0)
+  {
+    INFO ("rrdtool plugin: %i cache %s had values when destroying the cache.",
+        non_empty, (non_empty == 1) ? "entry" : "entries");
+  }
+  else
+  {
+    DEBUG ("rrdtool plugin: No values have been lost "
+        "when destroying the cache.");
+  }
+
+  pthread_mutex_unlock (&cache_lock);
+  return (0);
+} /* }}} int rrd_cache_destroy */
+
+static int rrd_compare_numeric (const void *a_ptr, const void *b_ptr)
+{
+       int a = *((int *) a_ptr);
+       int b = *((int *) b_ptr);
+
+       if (a < b)
+               return (-1);
+       else if (a > b)
+               return (1);
+       else
+               return (0);
+} /* int rrd_compare_numeric */
+
+static int rrd_write (const data_set_t *ds, const value_list_t *vl,
+               user_data_t __attribute__((unused)) *user_data)
+{
+       struct stat  statbuf;
+       char         filename[512];
+       char         values[512];
+       int          status;
+
+       if (do_shutdown)
+               return (0);
+
+       if (0 != strcmp (ds->type, vl->type)) {
+               ERROR ("rrdtool plugin: DS type does not match value list type");
+               return -1;
+       }
+
+       if (value_list_to_filename (filename, sizeof (filename), ds, vl) != 0)
+               return (-1);
+
+       if (value_list_to_string (values, sizeof (values), ds, vl) != 0)
+               return (-1);
+
+       if (stat (filename, &statbuf) == -1)
+       {
+               if (errno == ENOENT)
+               {
+                       status = cu_rrd_create_file (filename,
+                                       ds, vl, &rrdcreate_config);
+                       if (status != 0)
+                               return (-1);
+               }
+               else
+               {
+                       char errbuf[1024];
+                       ERROR ("stat(%s) failed: %s", filename,
+                                       sstrerror (errno, errbuf,
+                                               sizeof (errbuf)));
+                       return (-1);
+               }
+       }
+       else if (!S_ISREG (statbuf.st_mode))
+       {
+               ERROR ("stat(%s): Not a regular file!",
+                               filename);
+               return (-1);
+       }
+
+       status = rrd_cache_insert (filename, values, vl->time);
+
+       return (status);
+} /* int rrd_write */
+
+static int rrd_flush (cdtime_t timeout, const char *identifier,
+               __attribute__((unused)) user_data_t *user_data)
+{
+       pthread_mutex_lock (&cache_lock);
+
+       if (cache == NULL) {
+               pthread_mutex_unlock (&cache_lock);
+               return (0);
+       }
+
+       rrd_cache_flush_identifier (timeout, identifier);
+
+       pthread_mutex_unlock (&cache_lock);
+       return (0);
+} /* int rrd_flush */
+
+static int rrd_config (const char *key, const char *value)
+{
+       if (strcasecmp ("CacheTimeout", key) == 0)
+       {
+               double tmp = atof (value);
+               if (tmp < 0)
+               {
+                       fprintf (stderr, "rrdtool: `CacheTimeout' must "
+                                       "be greater than 0.\n");
+                       ERROR ("rrdtool: `CacheTimeout' must "
+                                       "be greater than 0.\n");
+                       return (1);
+               }
+               cache_timeout = DOUBLE_TO_CDTIME_T (tmp);
+       }
+       else if (strcasecmp ("CacheFlush", key) == 0)
+       {
+               int tmp = atoi (value);
+               if (tmp < 0)
+               {
+                       fprintf (stderr, "rrdtool: `CacheFlush' must "
+                                       "be greater than 0.\n");
+                       ERROR ("rrdtool: `CacheFlush' must "
+                                       "be greater than 0.\n");
+                       return (1);
+               }
+               cache_flush_timeout = tmp;
+       }
+       else if (strcasecmp ("DataDir", key) == 0)
+       {
+               if (datadir != NULL)
+                       free (datadir);
+               datadir = strdup (value);
+               if (datadir != NULL)
+               {
+                       int len = strlen (datadir);
+                       while ((len > 0) && (datadir[len - 1] == '/'))
+                       {
+                               len--;
+                               datadir[len] = '\0';
+                       }
+                       if (len <= 0)
+                       {
+                               free (datadir);
+                               datadir = NULL;
+                       }
+               }
+       }
+       else if (strcasecmp ("StepSize", key) == 0)
+       {
+               unsigned long temp = strtoul (value, NULL, 0);
+               if (temp > 0)
+                       rrdcreate_config.stepsize = temp;
+       }
+       else if (strcasecmp ("HeartBeat", key) == 0)
+       {
+               int temp = atoi (value);
+               if (temp > 0)
+                       rrdcreate_config.heartbeat = temp;
+       }
+       else if (strcasecmp ("RRARows", key) == 0)
+       {
+               int tmp = atoi (value);
+               if (tmp <= 0)
+               {
+                       fprintf (stderr, "rrdtool: `RRARows' must "
+                                       "be greater than 0.\n");
+                       ERROR ("rrdtool: `RRARows' must "
+                                       "be greater than 0.\n");
+                       return (1);
+               }
+               rrdcreate_config.rrarows = tmp;
+       }
+       else if (strcasecmp ("RRATimespan", key) == 0)
+       {
+               char *saveptr = NULL;
+               char *dummy;
+               char *ptr;
+               char *value_copy;
+               int *tmp_alloc;
+
+               value_copy = strdup (value);
+               if (value_copy == NULL)
+                       return (1);
+
+               dummy = value_copy;
+               while ((ptr = strtok_r (dummy, ", \t", &saveptr)) != NULL)
+               {
+                       dummy = NULL;
+                       
+                       tmp_alloc = realloc (rrdcreate_config.timespans,
+                                       sizeof (int) * (rrdcreate_config.timespans_num + 1));
+                       if (tmp_alloc == NULL)
+                       {
+                               fprintf (stderr, "rrdtool: realloc failed.\n");
+                               ERROR ("rrdtool: realloc failed.\n");
+                               free (value_copy);
+                               return (1);
+                       }
+                       rrdcreate_config.timespans = tmp_alloc;
+                       rrdcreate_config.timespans[rrdcreate_config.timespans_num] = atoi (ptr);
+                       if (rrdcreate_config.timespans[rrdcreate_config.timespans_num] != 0)
+                               rrdcreate_config.timespans_num++;
+               } /* while (strtok_r) */
+
+               qsort (/* base = */ rrdcreate_config.timespans,
+                               /* nmemb  = */ rrdcreate_config.timespans_num,
+                               /* size   = */ sizeof (rrdcreate_config.timespans[0]),
+                               /* compar = */ rrd_compare_numeric);
+
+               free (value_copy);
+       }
+       else if (strcasecmp ("XFF", key) == 0)
+       {
+               double tmp = atof (value);
+               if ((tmp < 0.0) || (tmp >= 1.0))
+               {
+                       fprintf (stderr, "rrdtool: `XFF' must "
+                                       "be in the range 0 to 1 (exclusive).");
+                       ERROR ("rrdtool: `XFF' must "
+                                       "be in the range 0 to 1 (exclusive).");
+                       return (1);
+               }
+               rrdcreate_config.xff = tmp;
+       }
+       else if (strcasecmp ("WritesPerSecond", key) == 0)
+       {
+               double wps = atof (value);
+
+               if (wps < 0.0)
+               {
+                       fprintf (stderr, "rrdtool: `WritesPerSecond' must be "
+                                       "greater than or equal to zero.");
+                       return (1);
+               }
+               else if (wps == 0.0)
+               {
+                       write_rate = 0.0;
+               }
+               else
+               {
+                       write_rate = 1.0 / wps;
+               }
+       }
+       else if (strcasecmp ("RandomTimeout", key) == 0)
+        {
+               double tmp;
+
+               tmp = atof (value);
+               if (tmp < 0.0)
+               {
+                       fprintf (stderr, "rrdtool: `RandomTimeout' must "
+                                       "be greater than or equal to zero.\n");
+                       ERROR ("rrdtool: `RandomTimeout' must "
+                                       "be greater then or equal to zero.");
+               }
+               else
+               {
+                       random_timeout = DOUBLE_TO_CDTIME_T (tmp);
+               }
+       }
+       else
+       {
+               return (-1);
+       }
+       return (0);
+} /* int rrd_config */
+
+static int rrd_shutdown (void)
+{
+       pthread_mutex_lock (&cache_lock);
+       rrd_cache_flush (0);
+       pthread_mutex_unlock (&cache_lock);
+
+       pthread_mutex_lock (&queue_lock);
+       do_shutdown = 1;
+       pthread_cond_signal (&queue_cond);
+       pthread_mutex_unlock (&queue_lock);
+
+       if ((queue_thread_running != 0)
+                       && ((queue_head != NULL) || (flushq_head != NULL)))
+       {
+               INFO ("rrdtool plugin: Shutting down the queue thread. "
+                               "This may take a while.");
+       }
+       else if (queue_thread_running != 0)
+       {
+               INFO ("rrdtool plugin: Shutting down the queue thread.");
+       }
+
+       /* Wait for all the values to be written to disk before returning. */
+       if (queue_thread_running != 0)
+       {
+               pthread_join (queue_thread, NULL);
+               memset (&queue_thread, 0, sizeof (queue_thread));
+               queue_thread_running = 0;
+               DEBUG ("rrdtool plugin: queue_thread exited.");
+       }
+
+       rrd_cache_destroy ();
+
+       return (0);
+} /* int rrd_shutdown */
+
+static int rrd_init (void)
+{
+       static int init_once = 0;
+       int status;
+
+       if (init_once != 0)
+               return (0);
+       init_once = 1;
+
+       if (rrdcreate_config.heartbeat <= 0)
+               rrdcreate_config.heartbeat = 2 * rrdcreate_config.stepsize;
+
+       if ((rrdcreate_config.heartbeat > 0)
+                       && (rrdcreate_config.heartbeat < CDTIME_T_TO_TIME_T (interval_g)))
+               WARNING ("rrdtool plugin: Your `heartbeat' is "
+                               "smaller than your `interval'. This will "
+                               "likely cause problems.");
+       else if ((rrdcreate_config.stepsize > 0)
+                       && (rrdcreate_config.stepsize < CDTIME_T_TO_TIME_T (interval_g)))
+               WARNING ("rrdtool plugin: Your `stepsize' is "
+                               "smaller than your `interval'. This will "
+                               "create needlessly big RRD-files.");
+
+       /* Set the cache up */
+       pthread_mutex_lock (&cache_lock);
+
+       cache = c_avl_create ((int (*) (const void *, const void *)) strcmp);
+       if (cache == NULL)
+       {
+               ERROR ("rrdtool plugin: c_avl_create failed.");
+               return (-1);
+       }
+
+       cache_flush_last = cdtime ();
+       if (cache_timeout == 0)
+       {
+               cache_flush_timeout = 0;
+       }
+       else if (cache_flush_timeout < cache_timeout)
+               cache_flush_timeout = 10 * cache_timeout;
+
+       pthread_mutex_unlock (&cache_lock);
+
+       status = pthread_create (&queue_thread, /* attr = */ NULL,
+                       rrd_queue_thread, /* args = */ NULL);
+       if (status != 0)
+       {
+               ERROR ("rrdtool plugin: Cannot create queue-thread.");
+               return (-1);
+       }
+       queue_thread_running = 1;
+
+       DEBUG ("rrdtool plugin: rrd_init: datadir = %s; stepsize = %lu;"
+                       " heartbeat = %i; rrarows = %i; xff = %lf;",
+                       (datadir == NULL) ? "(null)" : datadir,
+                       rrdcreate_config.stepsize,
+                       rrdcreate_config.heartbeat,
+                       rrdcreate_config.rrarows,
+                       rrdcreate_config.xff);
+
+       return (0);
+} /* int rrd_init */
+
+void module_register (void)
+{
+       plugin_register_config ("rrdtool", rrd_config,
+                       config_keys, config_keys_num);
+       plugin_register_init ("rrdtool", rrd_init);
+       plugin_register_write ("rrdtool", rrd_write, /* user_data = */ NULL);
+       plugin_register_flush ("rrdtool", rrd_flush, /* user_data = */ NULL);
+       plugin_register_shutdown ("rrdtool", rrd_shutdown);
+}
diff --git a/src/sensors.c b/src/sensors.c
new file mode 100644 (file)
index 0000000..8391346
--- /dev/null
@@ -0,0 +1,590 @@
+/**
+ * collectd - src/sensors.c
+ * Copyright (C) 2005-2008  Florian octo Forster
+ * Copyright (C) 2006       Luboš Staněk
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ *   
+ *   Lubos Stanek <lubek at users.sourceforge.net> Wed Oct 27, 2006
+ *   - config ExtendedSensorNaming option
+ *   - precise sensor feature selection (chip-bus-address/type-feature)
+ *     with ExtendedSensorNaming
+ *   - more sensor features (finite list)
+ *   - honor sensors.conf's ignored
+ *   - config Sensor option
+ *   - config IgnoreSelected option
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+#include "utils_ignorelist.h"
+
+#if defined(HAVE_SENSORS_SENSORS_H)
+# include <sensors/sensors.h>
+#endif
+
+#if !defined(SENSORS_API_VERSION)
+# define SENSORS_API_VERSION 0x000
+#endif
+
+/*
+ * The sensors library prior to version 3.0 (internal version 0x400) didn't
+ * report the type of values, only a name. The following lists are there to
+ * convert from the names to the type. They are not used with the new
+ * interface.
+ */
+#if SENSORS_API_VERSION < 0x400
+static char *sensor_type_name_map[] =
+{
+# define SENSOR_TYPE_VOLTAGE     0
+       "voltage",
+# define SENSOR_TYPE_FANSPEED    1
+       "fanspeed",
+# define SENSOR_TYPE_TEMPERATURE 2
+       "temperature",
+# define SENSOR_TYPE_UNKNOWN     3
+       NULL
+};
+
+struct sensors_labeltypes_s
+{
+       char *label;
+       int type;
+};
+typedef struct sensors_labeltypes_s sensors_labeltypes_t;
+
+/* finite list of known labels extracted from lm_sensors */
+static sensors_labeltypes_t known_features[] = 
+{
+       { "fan1", SENSOR_TYPE_FANSPEED },
+       { "fan2", SENSOR_TYPE_FANSPEED },
+       { "fan3", SENSOR_TYPE_FANSPEED },
+       { "fan4", SENSOR_TYPE_FANSPEED },
+       { "fan5", SENSOR_TYPE_FANSPEED },
+       { "fan6", SENSOR_TYPE_FANSPEED },
+       { "fan7", SENSOR_TYPE_FANSPEED },
+       { "AIN2", SENSOR_TYPE_VOLTAGE },
+       { "AIN1", SENSOR_TYPE_VOLTAGE },
+       { "in10", SENSOR_TYPE_VOLTAGE },
+       { "in9", SENSOR_TYPE_VOLTAGE },
+       { "in8", SENSOR_TYPE_VOLTAGE },
+       { "in7", SENSOR_TYPE_VOLTAGE },
+       { "in6", SENSOR_TYPE_VOLTAGE },
+       { "in5", SENSOR_TYPE_VOLTAGE },
+       { "in4", SENSOR_TYPE_VOLTAGE },
+       { "in3", SENSOR_TYPE_VOLTAGE },
+       { "in2", SENSOR_TYPE_VOLTAGE },
+       { "in0", SENSOR_TYPE_VOLTAGE },
+       { "CPU_Temp", SENSOR_TYPE_TEMPERATURE },
+       { "remote_temp", SENSOR_TYPE_TEMPERATURE },
+       { "temp1", SENSOR_TYPE_TEMPERATURE },
+       { "temp2", SENSOR_TYPE_TEMPERATURE },
+       { "temp3", SENSOR_TYPE_TEMPERATURE },
+       { "temp4", SENSOR_TYPE_TEMPERATURE },
+       { "temp5", SENSOR_TYPE_TEMPERATURE },
+       { "temp6", SENSOR_TYPE_TEMPERATURE },
+       { "temp7", SENSOR_TYPE_TEMPERATURE },
+       { "temp", SENSOR_TYPE_TEMPERATURE },
+       { "Vccp2", SENSOR_TYPE_VOLTAGE },
+       { "Vccp1", SENSOR_TYPE_VOLTAGE },
+       { "vdd", SENSOR_TYPE_VOLTAGE },
+       { "vid5", SENSOR_TYPE_VOLTAGE },
+       { "vid4", SENSOR_TYPE_VOLTAGE },
+       { "vid3", SENSOR_TYPE_VOLTAGE },
+       { "vid2", SENSOR_TYPE_VOLTAGE },
+       { "vid1", SENSOR_TYPE_VOLTAGE },
+       { "vid", SENSOR_TYPE_VOLTAGE },
+       { "vin4", SENSOR_TYPE_VOLTAGE },
+       { "vin3", SENSOR_TYPE_VOLTAGE },
+       { "vin2", SENSOR_TYPE_VOLTAGE },
+       { "vin1", SENSOR_TYPE_VOLTAGE },
+       { "voltbatt", SENSOR_TYPE_VOLTAGE },
+       { "volt12", SENSOR_TYPE_VOLTAGE },
+       { "volt5", SENSOR_TYPE_VOLTAGE },
+       { "vrm", SENSOR_TYPE_VOLTAGE },
+       { "5.0V", SENSOR_TYPE_VOLTAGE },
+       { "5V", SENSOR_TYPE_VOLTAGE },
+       { "3.3V", SENSOR_TYPE_VOLTAGE },
+       { "2.5V", SENSOR_TYPE_VOLTAGE },
+       { "2.0V", SENSOR_TYPE_VOLTAGE },
+       { "12V", SENSOR_TYPE_VOLTAGE }
+};
+static int known_features_num = STATIC_ARRAY_SIZE (known_features);
+/* end new naming */
+#endif /* SENSORS_API_VERSION < 0x400 */
+
+static const char *config_keys[] =
+{
+       "Sensor",
+       "IgnoreSelected"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+#if SENSORS_API_VERSION < 0x400
+typedef struct featurelist
+{
+       const sensors_chip_name    *chip;
+       const sensors_feature_data *data;
+       int                         type;
+       struct featurelist         *next;
+} featurelist_t;
+
+# ifndef SENSORS_CONF_PATH
+#  define SENSORS_CONF_PATH "/etc/sensors.conf"
+# endif
+/* #endif SENSORS_API_VERSION < 0x400 */
+
+#elif (SENSORS_API_VERSION >= 0x400) && (SENSORS_API_VERSION < 0x500)
+typedef struct featurelist
+{
+       const sensors_chip_name    *chip;
+       const sensors_feature      *feature;
+       const sensors_subfeature   *subfeature;
+       struct featurelist         *next;
+} featurelist_t;
+
+# ifndef SENSORS_CONF_PATH
+#  define SENSORS_CONF_PATH "/etc/sensors3.conf"
+# endif
+/* #endif (SENSORS_API_VERSION >= 0x400) && (SENSORS_API_VERSION < 0x500) */
+
+#else /* if SENSORS_API_VERSION >= 0x500 */
+# error "This version of libsensors is not supported yet. Please report this " \
+       "as bug."
+#endif
+
+static const char *conffile = SENSORS_CONF_PATH;
+featurelist_t *first_feature = NULL;
+static ignorelist_t *sensor_list;
+static time_t sensors_config_mtime = 0;
+
+#if SENSORS_API_VERSION < 0x400
+/* full chip name logic borrowed from lm_sensors */
+static int sensors_snprintf_chip_name (char *buf, size_t buf_size,
+               const sensors_chip_name *chip)
+{
+       int status = -1;
+
+       if (chip->bus == SENSORS_CHIP_NAME_BUS_ISA)
+       {
+               status = ssnprintf (buf, buf_size,
+                               "%s-isa-%04x",
+                               chip->prefix,
+                               chip->addr);
+       }
+       else if (chip->bus == SENSORS_CHIP_NAME_BUS_DUMMY)
+       {
+               status = snprintf (buf, buf_size, "%s-%s-%04x",
+                               chip->prefix,
+                               chip->busname,
+                               chip->addr);
+       }
+       else
+       {
+               status = snprintf (buf, buf_size, "%s-i2c-%d-%02x",
+                               chip->prefix,
+                               chip->bus,
+                               chip->addr);
+       }
+
+       return (status);
+} /* int sensors_snprintf_chip_name */
+
+static int sensors_feature_name_to_type (const char *name)
+{
+       int i;
+
+       /* Yes, this is slow, but it's only ever done during initialization, so
+        * it's a one time cost.. */
+       for (i = 0; i < known_features_num; i++)
+               if (strcasecmp (known_features[i].label, name) == 0)
+                       return (known_features[i].type);
+
+       return (SENSOR_TYPE_UNKNOWN);
+} /* int sensors_feature_name_to_type */
+#endif
+
+static int sensors_config (const char *key, const char *value)
+{
+       if (sensor_list == NULL)
+               sensor_list = ignorelist_create (1);
+
+       if (strcasecmp (key, "Sensor") == 0)
+       {
+               if (ignorelist_add (sensor_list, value))
+               {
+                       ERROR ("sensors plugin: "
+                                       "Cannot add value to ignorelist.");
+                       return (1);
+               }
+       }
+       else if (strcasecmp (key, "IgnoreSelected") == 0)
+       {
+               ignorelist_set_invert (sensor_list, 1);
+               if (IS_TRUE (value))
+                       ignorelist_set_invert (sensor_list, 0);
+       }
+       else
+       {
+               return (-1);
+       }
+
+       return (0);
+}
+
+void sensors_free_features (void)
+{
+       featurelist_t *thisft;
+       featurelist_t *nextft;
+
+       if (first_feature == NULL)
+               return;
+
+       sensors_cleanup ();
+
+       for (thisft = first_feature; thisft != NULL; thisft = nextft)
+       {
+               nextft = thisft->next;
+               sfree (thisft);
+       }
+       first_feature = NULL;
+}
+
+static int sensors_load_conf (void)
+{
+       FILE *fh;
+       featurelist_t *last_feature = NULL;
+       
+       const sensors_chip_name *chip;
+       int chip_num;
+
+       struct stat statbuf;
+       int status;
+       
+       status = stat (conffile, &statbuf);
+       if (status != 0)
+       {
+               char errbuf[1024];
+               ERROR ("sensors plugin: stat (%s) failed: %s", conffile,
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               sensors_config_mtime = 0;
+       }
+
+       if ((sensors_config_mtime != 0)
+                       && (sensors_config_mtime == statbuf.st_mtime))
+               return (0);
+
+       if (sensors_config_mtime != 0)
+       {
+               NOTICE ("sensors plugin: Reloading config from %s",
+                               conffile);
+               sensors_free_features ();
+               sensors_config_mtime = 0;
+       }
+
+       fh = fopen (conffile, "r");
+       if (fh == NULL)
+       {
+               char errbuf[1024];
+               ERROR ("sensors plugin: fopen(%s) failed: %s", conffile,
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       status = sensors_init (fh);
+       fclose (fh);
+       if (status != 0)
+       {
+               ERROR ("sensors plugin: Cannot initialize sensors. "
+                               "Data will not be collected.");
+               return (-1);
+       }
+
+       sensors_config_mtime = statbuf.st_mtime;
+
+#if SENSORS_API_VERSION < 0x400
+       chip_num = 0;
+       while ((chip = sensors_get_detected_chips (&chip_num)) != NULL)
+       {
+               int feature_num0 = 0;
+               int feature_num1 = 0;
+
+               while (42)
+               {
+                       const sensors_feature_data *feature;
+                       int feature_type;
+                       featurelist_t *fl;
+
+                       feature = sensors_get_all_features (*chip,
+                                       &feature_num0, &feature_num1);
+
+                       /* Check if all features have been read. */
+                       if (feature == NULL)
+                               break;
+
+                       /* "master features" only */
+                       if (feature->mapping != SENSORS_NO_MAPPING)
+                       {
+                               DEBUG ("sensors plugin: sensors_load_conf: "
+                                               "Ignoring subfeature `%s', "
+                                               "because (feature->mapping "
+                                               "!= SENSORS_NO_MAPPING).",
+                                               feature->name);
+                               continue;
+                       }
+
+                       /* skip ignored in sensors.conf */
+                       if (sensors_get_ignored (*chip, feature->number) == 0)
+                       {
+                               DEBUG ("sensors plugin: sensors_load_conf: "
+                                               "Ignoring subfeature `%s', "
+                                               "because "
+                                               "`sensors_get_ignored' told "
+                                               "me so.",
+                                               feature->name);
+                               continue;
+                       }
+
+                       feature_type = sensors_feature_name_to_type (
+                                       feature->name);
+                       if (feature_type == SENSOR_TYPE_UNKNOWN)
+                       {
+                               DEBUG ("sensors plugin: sensors_load_conf: "
+                                               "Ignoring subfeature `%s', "
+                                               "because its type is "
+                                               "unknown.",
+                                               feature->name);
+                               continue;
+                       }
+
+                       fl = (featurelist_t *) malloc (sizeof (featurelist_t));
+                       if (fl == NULL)
+                       {
+                               ERROR ("sensors plugin: malloc failed.");
+                               continue;
+                       }
+                       memset (fl, '\0', sizeof (featurelist_t));
+
+                       fl->chip = chip;
+                       fl->data = feature;
+                       fl->type = feature_type;
+
+                       if (first_feature == NULL)
+                               first_feature = fl;
+                       else
+                               last_feature->next = fl;
+                       last_feature = fl;
+               } /* while sensors_get_all_features */
+       } /* while sensors_get_detected_chips */
+/* #endif SENSORS_API_VERSION < 0x400 */
+
+#elif (SENSORS_API_VERSION >= 0x400) && (SENSORS_API_VERSION < 0x500)
+       chip_num = 0;
+       while ((chip = sensors_get_detected_chips (NULL, &chip_num)) != NULL)
+       {
+               const sensors_feature *feature;
+               int feature_num = 0;
+
+               while ((feature = sensors_get_features (chip, &feature_num)) != NULL)
+               {
+                       const sensors_subfeature *subfeature;
+                       int subfeature_num = 0;
+
+                       /* Only handle voltage, fanspeeds and temperatures */
+                       if ((feature->type != SENSORS_FEATURE_IN)
+                                       && (feature->type != SENSORS_FEATURE_FAN)
+                                       && (feature->type != SENSORS_FEATURE_TEMP))
+                       {
+                               DEBUG ("sensors plugin: sensors_load_conf: "
+                                               "Ignoring feature `%s', "
+                                               "because its type is not "
+                                               "supported.", feature->name);
+                               continue;
+                       }
+
+                       while ((subfeature = sensors_get_all_subfeatures (chip,
+                                                       feature, &subfeature_num)) != NULL)
+                       {
+                               featurelist_t *fl;
+
+                               if ((subfeature->type != SENSORS_SUBFEATURE_IN_INPUT)
+                                               && (subfeature->type != SENSORS_SUBFEATURE_FAN_INPUT)
+                                               && (subfeature->type != SENSORS_SUBFEATURE_TEMP_INPUT))
+                                       continue;
+
+                               fl = (featurelist_t *) malloc (sizeof (featurelist_t));
+                               if (fl == NULL)
+                               {
+                                       ERROR ("sensors plugin: malloc failed.");
+                                       continue;
+                               }
+                               memset (fl, '\0', sizeof (featurelist_t));
+
+                               fl->chip = chip;
+                               fl->feature = feature;
+                               fl->subfeature = subfeature;
+
+                               if (first_feature == NULL)
+                                       first_feature = fl;
+                               else
+                                       last_feature->next = fl;
+                               last_feature  = fl;
+                       } /* while (subfeature) */
+               } /* while (feature) */
+       } /* while (chip) */
+#endif /* (SENSORS_API_VERSION >= 0x400) && (SENSORS_API_VERSION < 0x500) */
+
+       if (first_feature == NULL)
+       {
+               sensors_cleanup ();
+               INFO ("sensors plugin: lm_sensors reports no "
+                               "features. Data will not be collected.");
+               return (-1);
+       }
+
+       return (0);
+} /* int sensors_load_conf */
+
+static int sensors_shutdown (void)
+{
+       sensors_free_features ();
+       ignorelist_free (sensor_list);
+
+       return (0);
+} /* int sensors_shutdown */
+
+static void sensors_submit (const char *plugin_instance,
+               const char *type, const char *type_instance,
+               double val)
+{
+       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, type, type_instance);
+       if (status < 1)
+               return;
+
+       if (sensor_list != NULL)
+       {
+               DEBUG ("sensors plugin: Checking ignorelist for `%s'", match_key);
+               if (ignorelist_match (sensor_list, match_key))
+                       return;
+       }
+
+       values[0].gauge = val;
+
+       vl.values = values;
+       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));
+       sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
+
+       plugin_dispatch_values (&vl);
+} /* void sensors_submit */
+
+static int sensors_read (void)
+{
+       featurelist_t *fl;
+
+       if (sensors_load_conf () != 0)
+               return (-1);
+
+#if SENSORS_API_VERSION < 0x400
+       for (fl = first_feature; fl != NULL; fl = fl->next)
+       {
+               double value;
+               int status;
+               char plugin_instance[DATA_MAX_NAME_LEN];
+               char type_instance[DATA_MAX_NAME_LEN];
+
+               status = sensors_get_feature (*fl->chip,
+                               fl->data->number, &value);
+               if (status < 0)
+                       continue;
+
+               status = sensors_snprintf_chip_name (plugin_instance,
+                               sizeof (plugin_instance), fl->chip);
+               if (status < 0)
+                       continue;
+
+               sstrncpy (type_instance, fl->data->name,
+                               sizeof (type_instance));
+
+               sensors_submit (plugin_instance,
+                               sensor_type_name_map[fl->type],
+                               type_instance,
+                               value);
+       } /* for fl = first_feature .. NULL */
+/* #endif SENSORS_API_VERSION < 0x400 */
+
+#elif (SENSORS_API_VERSION >= 0x400) && (SENSORS_API_VERSION < 0x500)
+       for (fl = first_feature; fl != NULL; fl = fl->next)
+       {
+               double value;
+               int status;
+               char plugin_instance[DATA_MAX_NAME_LEN];
+               char type_instance[DATA_MAX_NAME_LEN];
+               const char *type;
+
+               status = sensors_get_value (fl->chip,
+                               fl->subfeature->number, &value);
+               if (status < 0)
+                       continue;
+
+               status = sensors_snprintf_chip_name (plugin_instance,
+                               sizeof (plugin_instance), fl->chip);
+               if (status < 0)
+                       continue;
+
+               sstrncpy (type_instance, fl->feature->name,
+                               sizeof (type_instance));
+
+               if (fl->feature->type == SENSORS_FEATURE_IN)
+                       type = "voltage";
+               else if (fl->feature->type
+                               == SENSORS_FEATURE_FAN)
+                       type = "fanspeed";
+               else if (fl->feature->type
+                               == SENSORS_FEATURE_TEMP)
+                       type = "temperature";
+               else
+                       continue;
+
+               sensors_submit (plugin_instance, type, type_instance, value);
+       } /* for fl = first_feature .. NULL */
+#endif /* (SENSORS_API_VERSION >= 0x400) && (SENSORS_API_VERSION < 0x500) */
+
+       return (0);
+} /* int sensors_read */
+
+void module_register (void)
+{
+       plugin_register_config ("sensors", sensors_config,
+                       config_keys, config_keys_num);
+       plugin_register_read ("sensors", sensors_read);
+       plugin_register_shutdown ("sensors", sensors_shutdown);
+} /* void module_register */
diff --git a/src/serial.c b/src/serial.c
new file mode 100644 (file)
index 0000000..9bd885d
--- /dev/null
@@ -0,0 +1,125 @@
+/**
+ * collectd - src/serial.c
+ * Copyright (C) 2005,2006  David Bacher
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   David Bacher <drbacher at gmail.com>
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#if !KERNEL_LINUX
+# error "No applicable input method."
+#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;
+
+       vl.values = values;
+       vl.values_len = 2;
+       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+       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));
+
+       plugin_dispatch_values (&vl);
+}
+
+static int serial_read (void)
+{
+       FILE *fh;
+       char buffer[1024];
+
+       derive_t rx = 0;
+       derive_t tx = 0;
+       
+       char *fields[16];
+       int i, numfields;
+       int len;
+
+       /* there are a variety of names for the serial device */
+       if ((fh = fopen ("/proc/tty/driver/serial", "r")) == NULL &&
+               (fh = fopen ("/proc/tty/driver/ttyS", "r")) == NULL)
+       {
+               char errbuf[1024];
+               WARNING ("serial: fopen: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       while (fgets (buffer, sizeof (buffer), fh) != NULL)
+       {
+               int have_rx = 0, have_tx = 0;
+
+               numfields = strsplit (buffer, fields, 16);
+
+               if (numfields < 6)
+                       continue;
+
+               /*
+                * 0: uart:16550A port:000003F8 irq:4 tx:0 rx:0
+                * 1: uart:16550A port:000002F8 irq:3 tx:0 rx:0
+                */
+               len = strlen (fields[0]) - 1;
+               if (len < 1)
+                       continue;
+               if (fields[0][len] != ':')
+                       continue;
+               fields[0][len] = '\0';
+
+               for (i = 1; i < numfields; i++)
+               {
+                       len = strlen (fields[i]);
+                       if (len < 4)
+                               continue;
+
+                       if (strncmp (fields[i], "tx:", 3) == 0)
+                       {
+                               tx = atoll (fields[i] + 3);
+                               have_tx++;
+                       }
+                       else if (strncmp (fields[i], "rx:", 3) == 0)
+                       {
+                               rx = atoll (fields[i] + 3);
+                               have_rx++;
+                       }
+               }
+
+               if ((have_rx == 0) || (have_tx == 0))
+                       continue;
+
+               serial_submit (fields[0], rx, tx);
+       }
+
+       fclose (fh);
+       return (0);
+} /* int serial_read */
+
+void module_register (void)
+{
+       plugin_register_read ("serial", serial_read);
+} /* void module_register */
diff --git a/src/snmp.c b/src/snmp.c
new file mode 100644 (file)
index 0000000..7a84851
--- /dev/null
@@ -0,0 +1,1610 @@
+/**
+ * collectd - src/snmp.c
+ * Copyright (C) 2007  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "utils_complain.h"
+
+#include <pthread.h>
+
+#include <net-snmp/net-snmp-config.h>
+#include <net-snmp/net-snmp-includes.h>
+
+/*
+ * Private data structes
+ */
+struct oid_s
+{
+  oid oid[MAX_OID_LEN];
+  size_t oid_len;
+};
+typedef struct oid_s oid_t;
+
+union instance_u
+{
+  char  string[DATA_MAX_NAME_LEN];
+  oid_t oid;
+};
+typedef union instance_u instance_t;
+
+struct data_definition_s
+{
+  char *name; /* used to reference this from the `Collect' option */
+  char *type; /* used to find the data_set */
+  int is_table;
+  instance_t instance;
+  char *instance_prefix;
+  oid_t *values;
+  int values_len;
+  double scale;
+  double shift;
+  struct data_definition_s *next;
+};
+typedef struct data_definition_s data_definition_t;
+
+struct host_definition_s
+{
+  char *name;
+  char *address;
+  char *community;
+  int version;
+  void *sess_handle;
+  c_complain_t complaint;
+  cdtime_t interval;
+  data_definition_t **data_list;
+  int data_list_len;
+};
+typedef struct host_definition_s host_definition_t;
+
+/* These two types are used to cache values in `csnmp_read_table' to handle
+ * gaps in tables. */
+struct csnmp_list_instances_s
+{
+  oid subid;
+  char instance[DATA_MAX_NAME_LEN];
+  struct csnmp_list_instances_s *next;
+};
+typedef struct csnmp_list_instances_s csnmp_list_instances_t;
+
+struct csnmp_table_values_s
+{
+  oid subid;
+  value_t value;
+  struct csnmp_table_values_s *next;
+};
+typedef struct csnmp_table_values_s csnmp_table_values_t;
+
+/*
+ * Private variables
+ */
+static data_definition_t *data_head = NULL;
+
+/*
+ * Prototypes
+ */
+static int csnmp_read_host (user_data_t *ud);
+
+/*
+ * Private functions
+ */
+static void csnmp_host_close_session (host_definition_t *host) /* {{{ */
+{
+  if (host->sess_handle == NULL)
+    return;
+
+  snmp_sess_close (host->sess_handle);
+  host->sess_handle = NULL;
+} /* }}} void csnmp_host_close_session */
+
+static void csnmp_host_definition_destroy (void *arg) /* {{{ */
+{
+  host_definition_t *hd;
+
+  hd = arg;
+
+  if (hd == NULL)
+    return;
+
+  if (hd->name != NULL)
+  {
+    DEBUG ("snmp plugin: Destroying host definition for host `%s'.",
+       hd->name);
+  }
+
+  csnmp_host_close_session (hd);
+
+  sfree (hd->name);
+  sfree (hd->address);
+  sfree (hd->community);
+  sfree (hd->data_list);
+
+  sfree (hd);
+} /* }}} void csnmp_host_definition_destroy */
+
+/* Many functions to handle the configuration. {{{ */
+/* First there are many functions which do configuration stuff. It's a big
+ * bloated and messy, I'm afraid. */
+
+/*
+ * Callgraph for the config stuff:
+ *  csnmp_config
+ *  +-> call_snmp_init_once
+ *  +-> csnmp_config_add_data
+ *  !   +-> csnmp_config_add_data_type
+ *  !   +-> csnmp_config_add_data_table
+ *  !   +-> csnmp_config_add_data_instance
+ *  !   +-> csnmp_config_add_data_instance_prefix
+ *  !   +-> csnmp_config_add_data_values
+ *  +-> csnmp_config_add_host
+ *      +-> csnmp_config_add_host_address
+ *      +-> csnmp_config_add_host_community
+ *      +-> csnmp_config_add_host_version
+ *      +-> csnmp_config_add_host_collect
+ */
+static void call_snmp_init_once (void)
+{
+  static int have_init = 0;
+
+  if (have_init == 0)
+    init_snmp (PACKAGE_NAME);
+  have_init = 1;
+} /* void call_snmp_init_once */
+
+static int csnmp_config_add_data_type (data_definition_t *dd, oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("snmp plugin: `Type' needs exactly one string argument.");
+    return (-1);
+  }
+
+  sfree (dd->type);
+  dd->type = strdup (ci->values[0].value.string);
+  if (dd->type == NULL)
+    return (-1);
+
+  return (0);
+} /* int csnmp_config_add_data_type */
+
+static int csnmp_config_add_data_table (data_definition_t *dd, oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN))
+  {
+    WARNING ("snmp plugin: `Table' needs exactly one boolean argument.");
+    return (-1);
+  }
+
+  dd->is_table = ci->values[0].value.boolean ? 1 : 0;
+
+  return (0);
+} /* int csnmp_config_add_data_table */
+
+static int csnmp_config_add_data_instance (data_definition_t *dd, oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("snmp plugin: `Instance' needs exactly one string argument.");
+    return (-1);
+  }
+
+  if (dd->is_table)
+  {
+    /* Instance is an OID */
+    dd->instance.oid.oid_len = MAX_OID_LEN;
+
+    if (!read_objid (ci->values[0].value.string,
+         dd->instance.oid.oid, &dd->instance.oid.oid_len))
+    {
+      ERROR ("snmp plugin: read_objid (%s) failed.",
+         ci->values[0].value.string);
+      return (-1);
+    }
+  }
+  else
+  {
+    /* Instance is a simple string */
+    sstrncpy (dd->instance.string, ci->values[0].value.string,
+       sizeof (dd->instance.string));
+  }
+
+  return (0);
+} /* int csnmp_config_add_data_instance */
+
+static int csnmp_config_add_data_instance_prefix (data_definition_t *dd,
+    oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("snmp plugin: `InstancePrefix' needs exactly one string argument.");
+    return (-1);
+  }
+
+  if (!dd->is_table)
+  {
+    WARNING ("snmp plugin: data %s: InstancePrefix is ignored when `Table' "
+       "is set to `false'.", dd->name);
+    return (-1);
+  }
+
+  sfree (dd->instance_prefix);
+  dd->instance_prefix = strdup (ci->values[0].value.string);
+  if (dd->instance_prefix == NULL)
+    return (-1);
+
+  return (0);
+} /* int csnmp_config_add_data_instance_prefix */
+
+static int csnmp_config_add_data_values (data_definition_t *dd, oconfig_item_t *ci)
+{
+  int i;
+
+  if (ci->values_num < 1)
+  {
+    WARNING ("snmp plugin: `Values' needs at least one argument.");
+    return (-1);
+  }
+
+  for (i = 0; i < ci->values_num; i++)
+    if (ci->values[i].type != OCONFIG_TYPE_STRING)
+    {
+      WARNING ("snmp plugin: `Values' needs only string argument.");
+      return (-1);
+    }
+
+  sfree (dd->values);
+  dd->values_len = 0;
+  dd->values = (oid_t *) malloc (sizeof (oid_t) * ci->values_num);
+  if (dd->values == NULL)
+    return (-1);
+  dd->values_len = ci->values_num;
+
+  for (i = 0; i < ci->values_num; i++)
+  {
+    dd->values[i].oid_len = MAX_OID_LEN;
+
+    if (NULL == snmp_parse_oid (ci->values[i].value.string,
+         dd->values[i].oid, &dd->values[i].oid_len))
+    {
+      ERROR ("snmp plugin: snmp_parse_oid (%s) failed.",
+         ci->values[i].value.string);
+      free (dd->values);
+      dd->values = NULL;
+      dd->values_len = 0;
+      return (-1);
+    }
+  }
+
+  return (0);
+} /* int csnmp_config_add_data_instance */
+
+static int csnmp_config_add_data_shift (data_definition_t *dd, oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
+  {
+    WARNING ("snmp plugin: The `Scale' config option needs exactly one number argument.");
+    return (-1);
+  }
+
+  dd->shift = ci->values[0].value.number;
+
+  return (0);
+} /* int csnmp_config_add_data_shift */
+
+static int csnmp_config_add_data_scale (data_definition_t *dd, oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
+  {
+    WARNING ("snmp plugin: The `Scale' config option needs exactly one number argument.");
+    return (-1);
+  }
+
+  dd->scale = ci->values[0].value.number;
+
+  return (0);
+} /* int csnmp_config_add_data_scale */
+
+static int csnmp_config_add_data (oconfig_item_t *ci)
+{
+  data_definition_t *dd;
+  int status = 0;
+  int i;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("snmp plugin: The `Data' config option needs exactly one string argument.");
+    return (-1);
+  }
+
+  dd = (data_definition_t *) malloc (sizeof (data_definition_t));
+  if (dd == NULL)
+    return (-1);
+  memset (dd, '\0', sizeof (data_definition_t));
+
+  dd->name = strdup (ci->values[0].value.string);
+  if (dd->name == NULL)
+  {
+    free (dd);
+    return (-1);
+  }
+  dd->scale = 1.0;
+  dd->shift = 0.0;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+    status = 0;
+
+    if (strcasecmp ("Type", option->key) == 0)
+      status = csnmp_config_add_data_type (dd, option);
+    else if (strcasecmp ("Table", option->key) == 0)
+      status = csnmp_config_add_data_table (dd, option);
+    else if (strcasecmp ("Instance", option->key) == 0)
+      status = csnmp_config_add_data_instance (dd, option);
+    else if (strcasecmp ("InstancePrefix", option->key) == 0)
+      status = csnmp_config_add_data_instance_prefix (dd, option);
+    else if (strcasecmp ("Values", option->key) == 0)
+      status = csnmp_config_add_data_values (dd, option);
+    else if (strcasecmp ("Shift", option->key) == 0)
+      status = csnmp_config_add_data_shift (dd, option);
+    else if (strcasecmp ("Scale", option->key) == 0)
+      status = csnmp_config_add_data_scale (dd, option);
+    else
+    {
+      WARNING ("snmp plugin: Option `%s' not allowed here.", option->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  } /* for (ci->children) */
+
+  while (status == 0)
+  {
+    if (dd->type == NULL)
+    {
+      WARNING ("snmp plugin: `Type' not given for data `%s'", dd->name);
+      status = -1;
+      break;
+    }
+    if (dd->values == NULL)
+    {
+      WARNING ("snmp plugin: No `Value' given for data `%s'", dd->name);
+      status = -1;
+      break;
+    }
+
+    break;
+  } /* while (status == 0) */
+
+  if (status != 0)
+  {
+    sfree (dd->name);
+    sfree (dd->instance_prefix);
+    sfree (dd->values);
+    sfree (dd);
+    return (-1);
+  }
+
+  DEBUG ("snmp plugin: dd = { name = %s, type = %s, is_table = %s, values_len = %i }",
+      dd->name, dd->type, (dd->is_table != 0) ? "true" : "false", dd->values_len);
+
+  if (data_head == NULL)
+    data_head = dd;
+  else
+  {
+    data_definition_t *last;
+    last = data_head;
+    while (last->next != NULL)
+      last = last->next;
+    last->next = dd;
+  }
+
+  return (0);
+} /* int csnmp_config_add_data */
+
+static int csnmp_config_add_host_address (host_definition_t *hd, oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("snmp plugin: The `Address' config option needs exactly one string argument.");
+    return (-1);
+  }
+
+  if (hd->address == NULL)
+    free (hd->address);
+
+  hd->address = strdup (ci->values[0].value.string);
+  if (hd->address == NULL)
+    return (-1);
+
+  DEBUG ("snmp plugin: host = %s; host->address = %s;",
+      hd->name, hd->address);
+
+  return (0);
+} /* int csnmp_config_add_host_address */
+
+static int csnmp_config_add_host_community (host_definition_t *hd, oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("snmp plugin: The `Community' config option needs exactly one string argument.");
+    return (-1);
+  }
+
+  if (hd->community == NULL)
+    free (hd->community);
+
+  hd->community = strdup (ci->values[0].value.string);
+  if (hd->community == NULL)
+    return (-1);
+
+  DEBUG ("snmp plugin: host = %s; host->community = %s;",
+      hd->name, hd->community);
+
+  return (0);
+} /* int csnmp_config_add_host_community */
+
+static int csnmp_config_add_host_version (host_definition_t *hd, oconfig_item_t *ci)
+{
+  int version;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
+  {
+    WARNING ("snmp plugin: The `Version' config option needs exactly one number argument.");
+    return (-1);
+  }
+
+  version = (int) ci->values[0].value.number;
+  if ((version != 1) && (version != 2))
+  {
+    WARNING ("snmp plugin: `Version' must either be `1' or `2'.");
+    return (-1);
+  }
+
+  hd->version = version;
+
+  return (0);
+} /* int csnmp_config_add_host_address */
+
+static int csnmp_config_add_host_collect (host_definition_t *host,
+    oconfig_item_t *ci)
+{
+  data_definition_t *data;
+  data_definition_t **data_list;
+  int data_list_len;
+  int i;
+
+  if (ci->values_num < 1)
+  {
+    WARNING ("snmp plugin: `Collect' needs at least one argument.");
+    return (-1);
+  }
+
+  for (i = 0; i < ci->values_num; i++)
+    if (ci->values[i].type != OCONFIG_TYPE_STRING)
+    {
+      WARNING ("snmp plugin: All arguments to `Collect' must be strings.");
+      return (-1);
+    }
+
+  data_list_len = host->data_list_len + ci->values_num;
+  data_list = (data_definition_t **) realloc (host->data_list,
+      sizeof (data_definition_t *) * data_list_len);
+  if (data_list == NULL)
+    return (-1);
+  host->data_list = data_list;
+
+  for (i = 0; i < ci->values_num; i++)
+  {
+    for (data = data_head; data != NULL; data = data->next)
+      if (strcasecmp (ci->values[i].value.string, data->name) == 0)
+       break;
+
+    if (data == NULL)
+    {
+      WARNING ("snmp plugin: No such data configured: `%s'",
+         ci->values[i].value.string);
+      continue;
+    }
+
+    DEBUG ("snmp plugin: Collect: host = %s, data[%i] = %s;",
+       host->name, host->data_list_len, data->name);
+
+    host->data_list[host->data_list_len] = data;
+    host->data_list_len++;
+  } /* for (values_num) */
+
+  return (0);
+} /* int csnmp_config_add_host_collect */
+
+static int csnmp_config_add_host (oconfig_item_t *ci)
+{
+  host_definition_t *hd;
+  int status = 0;
+  int i;
+
+  /* Registration stuff. */
+  char cb_name[DATA_MAX_NAME_LEN];
+  user_data_t cb_data;
+  struct timespec cb_interval;
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("snmp plugin: `Host' needs exactly one string argument.");
+    return (-1);
+  }
+
+  hd = (host_definition_t *) malloc (sizeof (host_definition_t));
+  if (hd == NULL)
+    return (-1);
+  memset (hd, '\0', sizeof (host_definition_t));
+  hd->version = 2;
+  C_COMPLAIN_INIT (&hd->complaint);
+
+  hd->name = strdup (ci->values[0].value.string);
+  if (hd->name == NULL)
+  {
+    free (hd);
+    return (-1);
+  }
+
+  hd->sess_handle = NULL;
+  hd->interval = 0;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+    status = 0;
+
+    if (strcasecmp ("Address", option->key) == 0)
+      status = csnmp_config_add_host_address (hd, option);
+    else if (strcasecmp ("Community", option->key) == 0)
+      status = csnmp_config_add_host_community (hd, option);
+    else if (strcasecmp ("Version", option->key) == 0)
+      status = csnmp_config_add_host_version (hd, option);
+    else if (strcasecmp ("Collect", option->key) == 0)
+      csnmp_config_add_host_collect (hd, option);
+    else if (strcasecmp ("Interval", option->key) == 0)
+      cf_util_get_cdtime (option, &hd->interval);
+    else
+    {
+      WARNING ("snmp plugin: csnmp_config_add_host: Option `%s' not allowed here.", option->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  } /* for (ci->children) */
+
+  while (status == 0)
+  {
+    if (hd->address == NULL)
+    {
+      WARNING ("snmp plugin: `Address' not given for host `%s'", hd->name);
+      status = -1;
+      break;
+    }
+    if (hd->community == NULL)
+    {
+      WARNING ("snmp plugin: `Community' not given for host `%s'", hd->name);
+      status = -1;
+      break;
+    }
+
+    break;
+  } /* while (status == 0) */
+
+  if (status != 0)
+  {
+    csnmp_host_definition_destroy (hd);
+    return (-1);
+  }
+
+  DEBUG ("snmp plugin: hd = { name = %s, address = %s, community = %s, version = %i }",
+      hd->name, hd->address, hd->community, hd->version);
+
+  ssnprintf (cb_name, sizeof (cb_name), "snmp-%s", hd->name);
+
+  memset (&cb_data, 0, sizeof (cb_data));
+  cb_data.data = hd;
+  cb_data.free_func = csnmp_host_definition_destroy;
+
+  CDTIME_T_TO_TIMESPEC (hd->interval, &cb_interval);
+
+  status = plugin_register_complex_read (/* group = */ NULL, cb_name,
+      csnmp_read_host, /* interval = */ &cb_interval,
+      /* user_data = */ &cb_data);
+  if (status != 0)
+  {
+    ERROR ("snmp plugin: Registering complex read function failed.");
+    csnmp_host_definition_destroy (hd);
+    return (-1);
+  }
+
+  return (0);
+} /* int csnmp_config_add_host */
+
+static int csnmp_config (oconfig_item_t *ci)
+{
+  int i;
+
+  call_snmp_init_once ();
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+    if (strcasecmp ("Data", child->key) == 0)
+      csnmp_config_add_data (child);
+    else if (strcasecmp ("Host", child->key) == 0)
+      csnmp_config_add_host (child);
+    else
+    {
+      WARNING ("snmp plugin: Ignoring unknown config option `%s'.", child->key);
+    }
+  } /* for (ci->children) */
+
+  return (0);
+} /* int csnmp_config */
+
+/* }}} End of the config stuff. Now the interesting part begins */
+
+static void csnmp_host_open_session (host_definition_t *host)
+{
+  struct snmp_session sess;
+
+  if (host->sess_handle != NULL)
+    csnmp_host_close_session (host);
+
+  snmp_sess_init (&sess);
+  sess.peername = host->address;
+  sess.community = (u_char *) host->community;
+  sess.community_len = strlen (host->community);
+  sess.version = (host->version == 1) ? SNMP_VERSION_1 : SNMP_VERSION_2c;
+
+  /* snmp_sess_open will copy the `struct snmp_session *'. */
+  host->sess_handle = snmp_sess_open (&sess);
+
+  if (host->sess_handle == NULL)
+  {
+    char *errstr = NULL;
+
+    snmp_error (&sess, NULL, NULL, &errstr);
+
+    ERROR ("snmp plugin: host %s: snmp_sess_open failed: %s",
+       host->name, (errstr == NULL) ? "Unknown problem" : errstr);
+    sfree (errstr);
+  }
+} /* void csnmp_host_open_session */
+
+/* TODO: Check if negative values wrap around. Problem: negative temperatures. */
+static value_t csnmp_value_list_to_value (struct variable_list *vl, int type,
+    double scale, double shift,
+    const char *host_name, const char *data_name)
+{
+  value_t ret;
+  uint64_t tmp_unsigned = 0;
+  int64_t tmp_signed = 0;
+  int defined = 1;
+
+  if ((vl->type == ASN_INTEGER)
+      || (vl->type == ASN_UINTEGER)
+      || (vl->type == ASN_COUNTER)
+#ifdef ASN_TIMETICKS
+      || (vl->type == ASN_TIMETICKS)
+#endif
+      || (vl->type == ASN_GAUGE))
+  {
+    tmp_unsigned = (uint32_t) *vl->val.integer;
+    tmp_signed = (int32_t) *vl->val.integer;
+    DEBUG ("snmp plugin: Parsed int32 value is %"PRIi64".", tmp_signed);
+  }
+  else if (vl->type == ASN_COUNTER64)
+  {
+    tmp_unsigned = (uint32_t) vl->val.counter64->high;
+    tmp_unsigned = tmp_unsigned << 32;
+    tmp_unsigned += (uint32_t) vl->val.counter64->low;
+    tmp_signed = (int64_t) tmp_unsigned;
+    DEBUG ("snmp plugin: Parsed int64 value is %"PRIu64".", tmp_unsigned);
+  }
+  else if (vl->type == ASN_OCTET_STR)
+  {
+    /* We'll handle this later.. */
+  }
+  else
+  {
+    char oid_buffer[1024];
+
+    memset (oid_buffer, 0, sizeof (oid_buffer));
+    snprint_objid (oid_buffer, sizeof (oid_buffer) - 1,
+       vl->name, vl->name_length);
+
+#ifdef ASN_NULL
+    if (vl->type == ASN_NULL)
+      INFO ("snmp plugin: OID \"%s\" is undefined (type ASN_NULL)",
+         oid_buffer);
+    else
+#endif
+      WARNING ("snmp plugin: I don't know the ASN type #%i "
+               "(OID: \"%s\", data block \"%s\", host block \"%s\")",
+          (int) vl->type, oid_buffer,
+          (data_name != NULL) ? data_name : "UNKNOWN",
+          (host_name != NULL) ? host_name : "UNKNOWN");
+
+    defined = 0;
+  }
+
+  if (vl->type == ASN_OCTET_STR)
+  {
+    int status = -1;
+
+    if (vl->val.string != NULL)
+    {
+      char string[64];
+      size_t string_length;
+
+      string_length = sizeof (string) - 1;
+      if (vl->val_len < string_length)
+       string_length = vl->val_len;
+
+      /* The strings we get from the Net-SNMP library may not be null
+       * terminated. That is why we're using `memcpy' here and not `strcpy'.
+       * `string_length' is set to `vl->val_len' which holds the length of the
+       * string.  -octo */
+      memcpy (string, vl->val.string, string_length);
+      string[string_length] = 0;
+
+      status = parse_value (string, &ret, type);
+      if (status != 0)
+      {
+       ERROR ("snmp plugin: csnmp_value_list_to_value: Parsing string as %s failed: %s",
+           DS_TYPE_TO_STRING (type), string);
+      }
+    }
+
+    if (status != 0)
+    {
+      switch (type)
+      {
+       case DS_TYPE_COUNTER:
+       case DS_TYPE_DERIVE:
+       case DS_TYPE_ABSOLUTE:
+         memset (&ret, 0, sizeof (ret));
+         break;
+
+       case DS_TYPE_GAUGE:
+         ret.gauge = NAN;
+         break;
+
+       default:
+         ERROR ("snmp plugin: csnmp_value_list_to_value: Unknown "
+             "data source type: %i.", type);
+         ret.gauge = NAN;
+      }
+    }
+  } /* if (vl->type == ASN_OCTET_STR) */
+  else if (type == DS_TYPE_COUNTER)
+  {
+    ret.counter = tmp_unsigned;
+  }
+  else if (type == DS_TYPE_GAUGE)
+  {
+    ret.gauge = NAN;
+    if (defined != 0)
+      ret.gauge = (scale * tmp_signed) + shift;
+  }
+  else if (type == DS_TYPE_DERIVE)
+    ret.derive = (derive_t) tmp_signed;
+  else if (type == DS_TYPE_ABSOLUTE)
+    ret.absolute = (absolute_t) tmp_unsigned;
+  else
+  {
+    ERROR ("snmp plugin: csnmp_value_list_to_value: Unknown data source "
+       "type: %i.", type);
+    ret.gauge = NAN;
+  }
+
+  return (ret);
+} /* value_t csnmp_value_list_to_value */
+
+/* Returns true if all OIDs have left their subtree */
+static int csnmp_check_res_left_subtree (const host_definition_t *host,
+    const data_definition_t *data,
+    struct snmp_pdu *res)
+{
+  struct variable_list *vb;
+  int num_checked;
+  int num_left_subtree;
+  int i;
+
+  vb = res->variables;
+  if (vb == NULL)
+    return (-1);
+
+  num_checked = 0;
+  num_left_subtree = 0;
+
+  /* check all the variables and count how many have left their subtree */
+  for (vb = res->variables, i = 0;
+      (vb != NULL) && (i < data->values_len);
+      vb = vb->next_variable, i++)
+  {
+    num_checked++;
+    if (snmp_oid_ncompare (data->values[i].oid,
+         data->values[i].oid_len,
+         vb->name, vb->name_length,
+         data->values[i].oid_len) != 0)
+      num_left_subtree++;
+  }
+
+  /* check if enough variables have been returned */
+  if (i < data->values_len)
+  {
+    ERROR ("snmp plugin: host %s: Expected %i variables, but got only %i",
+       host->name, data->values_len, i);
+    return (-1);
+  }
+
+  if (data->instance.oid.oid_len > 0)
+  {
+    if (vb == NULL)
+    {
+      ERROR ("snmp plugin: host %s: Expected one more variable for "
+         "the instance..", host->name);
+      return (-1);
+    }
+
+    num_checked++;
+    if (snmp_oid_ncompare (data->instance.oid.oid,
+         data->instance.oid.oid_len,
+         vb->name, vb->name_length,
+         data->instance.oid.oid_len) != 0)
+      num_left_subtree++;
+  }
+
+  DEBUG ("snmp plugin: csnmp_check_res_left_subtree: %i of %i variables have "
+      "left their subtree",
+      num_left_subtree, num_checked);
+  if (num_left_subtree >= num_checked)
+    return (1);
+  return (0);
+} /* int csnmp_check_res_left_subtree */
+
+static int csnmp_strvbcopy_hexstring (char *dst, /* {{{ */
+    const struct variable_list *vb, size_t dst_size)
+{
+  char *buffer_ptr;
+  size_t buffer_free;
+  size_t i;
+
+  buffer_ptr = dst;
+  buffer_free = dst_size;
+
+  for (i = 0; i < vb->val_len; i++)
+  {
+    int status;
+
+    status = snprintf (buffer_ptr, buffer_free,
+       (i == 0) ? "%02x" : ":%02x", (unsigned int) vb->val.bitstring[i]);
+
+    if (status >= buffer_free)
+    {
+      buffer_ptr += (buffer_free - 1);
+      *buffer_ptr = 0;
+      return (dst_size + (buffer_free - status));
+    }
+    else /* if (status < buffer_free) */
+    {
+      buffer_ptr += status;
+      buffer_free -= status;
+    }
+  }
+
+  return ((int) (dst_size - buffer_free));
+} /* }}} int csnmp_strvbcopy_hexstring */
+
+static int csnmp_strvbcopy (char *dst, /* {{{ */
+    const struct variable_list *vb, size_t dst_size)
+{
+  char *src;
+  size_t num_chars;
+  size_t i;
+
+  if (vb->type == ASN_OCTET_STR)
+    src = (char *) vb->val.string;
+  else if (vb->type == ASN_BIT_STR)
+    src = (char *) vb->val.bitstring;
+  else
+  {
+    dst[0] = 0;
+    return (EINVAL);
+  }
+
+  num_chars = dst_size - 1;
+  if (num_chars > vb->val_len)
+    num_chars = vb->val_len;
+
+  for (i = 0; i < num_chars; i++)
+  {
+    /* Check for control characters. */
+    if ((unsigned char)src[i] < 32)
+      return (csnmp_strvbcopy_hexstring (dst, vb, dst_size));
+    dst[i] = src[i];
+  }
+  dst[num_chars] = 0;
+
+  return ((int) vb->val_len);
+} /* }}} int csnmp_strvbcopy */
+
+static int csnmp_instance_list_add (csnmp_list_instances_t **head,
+    csnmp_list_instances_t **tail,
+    const struct snmp_pdu *res,
+    const host_definition_t *hd, const data_definition_t *dd)
+{
+  csnmp_list_instances_t *il;
+  struct variable_list *vb;
+
+  /* Set vb on the last variable */
+  for (vb = res->variables;
+      (vb != NULL) && (vb->next_variable != NULL);
+      vb = vb->next_variable)
+    /* do nothing */;
+  if (vb == NULL)
+    return (-1);
+
+  il = (csnmp_list_instances_t *) malloc (sizeof (csnmp_list_instances_t));
+  if (il == NULL)
+  {
+    ERROR ("snmp plugin: malloc failed.");
+    return (-1);
+  }
+  il->subid = vb->name[vb->name_length - 1];
+  il->next = NULL;
+
+  /* Get instance name */
+  if ((vb->type == ASN_OCTET_STR) || (vb->type == ASN_BIT_STR))
+  {
+    char *ptr;
+
+    csnmp_strvbcopy (il->instance, vb, sizeof (il->instance));
+
+    for (ptr = il->instance; *ptr != '\0'; ptr++)
+    {
+      if ((*ptr > 0) && (*ptr < 32))
+       *ptr = ' ';
+      else if (*ptr == '/')
+       *ptr = '_';
+    }
+    DEBUG ("snmp plugin: il->instance = `%s';", il->instance);
+  }
+  else
+  {
+    value_t val = csnmp_value_list_to_value (vb, DS_TYPE_COUNTER,
+        /* scale = */ 1.0, /* shift = */ 0.0, hd->name, dd->name);
+    ssnprintf (il->instance, sizeof (il->instance),
+       "%llu", val.counter);
+  }
+
+  /* TODO: Debugging output */
+
+  if (*head == NULL)
+    *head = il;
+  else
+    (*tail)->next = il;
+  *tail = il;
+
+  return (0);
+} /* int csnmp_instance_list_add */
+
+static int csnmp_dispatch_table (host_definition_t *host, data_definition_t *data,
+    csnmp_list_instances_t *instance_list,
+    csnmp_table_values_t **value_table)
+{
+  const data_set_t *ds;
+  value_list_t vl = VALUE_LIST_INIT;
+
+  csnmp_list_instances_t *instance_list_ptr;
+  csnmp_table_values_t **value_table_ptr;
+
+  int i;
+  oid subid;
+  int have_more;
+
+  ds = plugin_get_ds (data->type);
+  if (!ds)
+  {
+    ERROR ("snmp plugin: DataSet `%s' not defined.", data->type);
+    return (-1);
+  }
+  assert (ds->ds_num == data->values_len);
+
+  instance_list_ptr = instance_list;
+
+  value_table_ptr = (csnmp_table_values_t **) malloc (sizeof (csnmp_table_values_t *)
+      * data->values_len);
+  if (value_table_ptr == NULL)
+    return (-1);
+  for (i = 0; i < data->values_len; i++)
+    value_table_ptr[i] = value_table[i];
+
+  vl.values_len = ds->ds_num;
+  vl.values = (value_t *) malloc (sizeof (value_t) * vl.values_len);
+  if (vl.values == NULL)
+  {
+    ERROR ("snmp plugin: malloc failed.");
+    sfree (value_table_ptr);
+    return (-1);
+  }
+
+  sstrncpy (vl.host, host->name, sizeof (vl.host));
+  sstrncpy (vl.plugin, "snmp", sizeof (vl.plugin));
+
+  vl.interval = host->interval;
+
+  subid = 0;
+  have_more = 1;
+
+  while (have_more != 0)
+  {
+    if (instance_list != NULL)
+    {
+      while ((instance_list_ptr != NULL)
+         && (instance_list_ptr->subid < subid))
+       instance_list_ptr = instance_list_ptr->next;
+
+      if (instance_list_ptr == NULL)
+      {
+       have_more = 0;
+       continue;
+      }
+      else if (instance_list_ptr->subid > subid)
+      {
+       subid = instance_list_ptr->subid;
+       continue;
+      }
+    } /* if (instance_list != NULL) */
+
+    for (i = 0; i < data->values_len; i++)
+    {
+      while ((value_table_ptr[i] != NULL)
+         && (value_table_ptr[i]->subid < subid))
+       value_table_ptr[i] = value_table_ptr[i]->next;
+
+      if (value_table_ptr[i] == NULL)
+      {
+       have_more = 0;
+       break;
+      }
+      else if (value_table_ptr[i]->subid > subid)
+      {
+       subid = value_table_ptr[i]->subid;
+       break;
+      }
+    } /* for (i = 0; i < columns; i++) */
+    /* The subid has been increased - start scanning from the beginning
+     * again.. */
+    if (i < data->values_len)
+      continue;
+
+    /* if we reach this line, all value_table_ptr[i] are non-NULL and are set
+     * to the same subid. instance_list_ptr is either NULL or points to the
+     * same subid, too. */
+#if COLLECT_DEBUG
+    for (i = 1; i < data->values_len; i++)
+    {
+      assert (value_table_ptr[i] != NULL);
+      assert (value_table_ptr[i-1]->subid == value_table_ptr[i]->subid);
+    }
+    assert ((instance_list_ptr == NULL)
+       || (instance_list_ptr->subid == value_table_ptr[0]->subid));
+#endif
+
+    sstrncpy (vl.type, data->type, sizeof (vl.type));
+
+    {
+      char temp[DATA_MAX_NAME_LEN];
+
+      if (instance_list_ptr == NULL)
+       ssnprintf (temp, sizeof (temp), "%"PRIu32, (uint32_t) subid);
+      else
+       sstrncpy (temp, instance_list_ptr->instance, sizeof (temp));
+
+      if (data->instance_prefix == NULL)
+       sstrncpy (vl.type_instance, temp, sizeof (vl.type_instance));
+      else
+       ssnprintf (vl.type_instance, sizeof (vl.type_instance), "%s%s",
+           data->instance_prefix, temp);
+    }
+
+    for (i = 0; i < data->values_len; i++)
+      vl.values[i] = value_table_ptr[i]->value;
+
+    /* If we get here `vl.type_instance' and all `vl.values' have been set */
+    plugin_dispatch_values (&vl);
+
+    subid++;
+  } /* while (have_more != 0) */
+
+  sfree (vl.values);
+  sfree (value_table_ptr);
+
+  return (0);
+} /* int csnmp_dispatch_table */
+
+static int csnmp_read_table (host_definition_t *host, data_definition_t *data)
+{
+  struct snmp_pdu *req;
+  struct snmp_pdu *res;
+  struct variable_list *vb;
+
+  const data_set_t *ds;
+  oid_t *oid_list;
+  uint32_t oid_list_len;
+
+  int status;
+  int i;
+
+  /* `value_table' and `value_table_ptr' implement a linked list for each
+   * value. `instance_list' and `instance_list_ptr' implement a linked list of
+   * instance names. This is used to jump gaps in the table. */
+  csnmp_list_instances_t *instance_list;
+  csnmp_list_instances_t *instance_list_ptr;
+  csnmp_table_values_t **value_table;
+  csnmp_table_values_t **value_table_ptr;
+
+  DEBUG ("snmp plugin: csnmp_read_table (host = %s, data = %s)",
+      host->name, data->name);
+
+  if (host->sess_handle == NULL)
+  {
+    DEBUG ("snmp plugin: csnmp_read_table: host->sess_handle == NULL");
+    return (-1);
+  }
+
+  ds = plugin_get_ds (data->type);
+  if (!ds)
+  {
+    ERROR ("snmp plugin: DataSet `%s' not defined.", data->type);
+    return (-1);
+  }
+
+  if (ds->ds_num != data->values_len)
+  {
+    ERROR ("snmp plugin: DataSet `%s' requires %i values, but config talks about %i",
+       data->type, ds->ds_num, data->values_len);
+    return (-1);
+  }
+
+  /* We need a copy of all the OIDs, because GETNEXT will destroy them. */
+  oid_list_len = data->values_len + 1;
+  oid_list = (oid_t *) malloc (sizeof (oid_t) * (oid_list_len));
+  if (oid_list == NULL)
+  {
+    ERROR ("snmp plugin: csnmp_read_table: malloc failed.");
+    return (-1);
+  }
+  memcpy (oid_list, data->values, data->values_len * sizeof (oid_t));
+  if (data->instance.oid.oid_len > 0)
+    memcpy (oid_list + data->values_len, &data->instance.oid, sizeof (oid_t));
+  else
+    oid_list_len--;
+
+  /* Allocate the `value_table' */
+  value_table = (csnmp_table_values_t **) malloc (sizeof (csnmp_table_values_t *)
+      * 2 * data->values_len);
+  if (value_table == NULL)
+  {
+    ERROR ("snmp plugin: csnmp_read_table: malloc failed.");
+    sfree (oid_list);
+    return (-1);
+  }
+  memset (value_table, '\0', sizeof (csnmp_table_values_t *) * 2 * data->values_len);
+  value_table_ptr = value_table + data->values_len;
+  
+  instance_list = NULL;
+  instance_list_ptr = NULL;
+
+  status = 0;
+  while (status == 0)
+  {
+    req = snmp_pdu_create (SNMP_MSG_GETNEXT);
+    if (req == NULL)
+    {
+      ERROR ("snmp plugin: snmp_pdu_create failed.");
+      status = -1;
+      break;
+    }
+
+    for (i = 0; (uint32_t) i < oid_list_len; i++)
+      snmp_add_null_var (req, oid_list[i].oid, oid_list[i].oid_len);
+
+    res = NULL;
+    status = snmp_sess_synch_response (host->sess_handle, req, &res);
+
+    if ((status != STAT_SUCCESS) || (res == NULL))
+    {
+      char *errstr = NULL;
+
+      snmp_sess_error (host->sess_handle, NULL, NULL, &errstr);
+
+      c_complain (LOG_ERR, &host->complaint,
+         "snmp plugin: host %s: snmp_sess_synch_response failed: %s",
+         host->name, (errstr == NULL) ? "Unknown problem" : errstr);
+
+      if (res != NULL)
+       snmp_free_pdu (res);
+      res = NULL;
+
+      sfree (errstr);
+      csnmp_host_close_session (host);
+
+      status = -1;
+      break;
+    }
+    status = 0;
+    assert (res != NULL);
+    c_release (LOG_INFO, &host->complaint,
+       "snmp plugin: host %s: snmp_sess_synch_response successful.",
+       host->name);
+
+    vb = res->variables;
+    if (vb == NULL)
+    {
+      status = -1;
+      break;
+    }
+
+    /* Check if all values (and possibly the instance) have left their
+     * subtree */
+    if (csnmp_check_res_left_subtree (host, data, res) != 0)
+    {
+      status = 0;
+      break;
+    }
+
+    /* if an instance-OID is configured.. */
+    if (data->instance.oid.oid_len > 0)
+    {
+      /* Allocate a new `csnmp_list_instances_t', insert the instance name and
+       * add it to the list */
+      if (csnmp_instance_list_add (&instance_list, &instance_list_ptr,
+           res, host, data) != 0)
+      {
+       ERROR ("snmp plugin: csnmp_instance_list_add failed.");
+       status = -1;
+       break;
+      }
+
+      /* Set vb on the last variable */
+      for (vb = res->variables;
+         (vb != NULL) && (vb->next_variable != NULL);
+         vb = vb->next_variable)
+       /* do nothing */;
+      assert (vb != NULL);
+
+      /* Copy OID to oid_list[data->values_len] */
+      memcpy (oid_list[data->values_len].oid, vb->name,
+         sizeof (oid) * vb->name_length);
+      oid_list[data->values_len].oid_len = vb->name_length;
+    }
+
+    for (vb = res->variables, i = 0;
+       (vb != NULL) && (i < data->values_len);
+       vb = vb->next_variable, i++)
+    {
+      csnmp_table_values_t *vt;
+
+      /* Check if we left the subtree */
+      if (snmp_oid_ncompare (data->values[i].oid,
+           data->values[i].oid_len,
+           vb->name, vb->name_length,
+           data->values[i].oid_len) != 0)
+      {
+       DEBUG ("snmp plugin: host = %s; data = %s; Value %i left its subtree.",
+           host->name, data->name, i);
+       continue;
+      }
+
+      if ((value_table_ptr[i] != NULL)
+         && (vb->name[vb->name_length - 1] <= value_table_ptr[i]->subid))
+      {
+       DEBUG ("snmp plugin: host = %s; data = %s; i = %i; "
+           "SUBID is not increasing.",
+           host->name, data->name, i);
+       continue;
+      }
+
+      vt = (csnmp_table_values_t *) malloc (sizeof (csnmp_table_values_t));
+      if (vt == NULL)
+      {
+       ERROR ("snmp plugin: malloc failed.");
+       status = -1;
+       break;
+      }
+
+      vt->subid = vb->name[vb->name_length - 1];
+      vt->value = csnmp_value_list_to_value (vb, ds->ds[i].type,
+          data->scale, data->shift, host->name, data->name);
+      vt->next = NULL;
+
+      if (value_table_ptr[i] == NULL)
+       value_table[i] = vt;
+      else
+       value_table_ptr[i]->next = vt;
+      value_table_ptr[i] = vt;
+
+      /* Copy OID to oid_list[i + 1] */
+      memcpy (oid_list[i].oid, vb->name, sizeof (oid) * vb->name_length);
+      oid_list[i].oid_len = vb->name_length;
+    } /* for (i = data->values_len) */
+
+    if (res != NULL)
+      snmp_free_pdu (res);
+    res = NULL;
+  } /* while (status == 0) */
+
+  if (res != NULL)
+    snmp_free_pdu (res);
+  res = NULL;
+
+  if (status == 0)
+    csnmp_dispatch_table (host, data, instance_list, value_table);
+
+  /* Free all allocated variables here */
+  while (instance_list != NULL)
+  {
+    instance_list_ptr = instance_list->next;
+    sfree (instance_list);
+    instance_list = instance_list_ptr;
+  }
+
+  for (i = 0; i < data->values_len; i++)
+  {
+    csnmp_table_values_t *tmp;
+    while (value_table[i] != NULL)
+    {
+      tmp = value_table[i]->next;
+      sfree (value_table[i]);
+      value_table[i] = tmp;
+    }
+  }
+
+  sfree (value_table);
+  sfree (oid_list);
+
+  return (0);
+} /* int csnmp_read_table */
+
+static int csnmp_read_value (host_definition_t *host, data_definition_t *data)
+{
+  struct snmp_pdu *req;
+  struct snmp_pdu *res;
+  struct variable_list *vb;
+
+  const data_set_t *ds;
+  value_list_t vl = VALUE_LIST_INIT;
+
+  int status;
+  int i;
+
+  DEBUG ("snmp plugin: csnmp_read_value (host = %s, data = %s)",
+      host->name, data->name);
+
+  if (host->sess_handle == NULL)
+  {
+    DEBUG ("snmp plugin: csnmp_read_table: host->sess_handle == NULL");
+    return (-1);
+  }
+
+  ds = plugin_get_ds (data->type);
+  if (!ds)
+  {
+    ERROR ("snmp plugin: DataSet `%s' not defined.", data->type);
+    return (-1);
+  }
+
+  if (ds->ds_num != data->values_len)
+  {
+    ERROR ("snmp plugin: DataSet `%s' requires %i values, but config talks about %i",
+       data->type, ds->ds_num, data->values_len);
+    return (-1);
+  }
+
+  vl.values_len = ds->ds_num;
+  vl.values = (value_t *) malloc (sizeof (value_t) * vl.values_len);
+  if (vl.values == NULL)
+    return (-1);
+  for (i = 0; i < vl.values_len; i++)
+  {
+    if (ds->ds[i].type == DS_TYPE_COUNTER)
+      vl.values[i].counter = 0;
+    else
+      vl.values[i].gauge = NAN;
+  }
+
+  sstrncpy (vl.host, host->name, sizeof (vl.host));
+  sstrncpy (vl.plugin, "snmp", sizeof (vl.plugin));
+  sstrncpy (vl.type, data->type, sizeof (vl.type));
+  sstrncpy (vl.type_instance, data->instance.string, sizeof (vl.type_instance));
+
+  vl.interval = host->interval;
+
+  req = snmp_pdu_create (SNMP_MSG_GET);
+  if (req == NULL)
+  {
+    ERROR ("snmp plugin: snmp_pdu_create failed.");
+    sfree (vl.values);
+    return (-1);
+  }
+
+  for (i = 0; i < data->values_len; i++)
+    snmp_add_null_var (req, data->values[i].oid, data->values[i].oid_len);
+
+  res = NULL;
+  status = snmp_sess_synch_response (host->sess_handle, req, &res);
+
+  if ((status != STAT_SUCCESS) || (res == NULL))
+  {
+    char *errstr = NULL;
+
+    snmp_sess_error (host->sess_handle, NULL, NULL, &errstr);
+    ERROR ("snmp plugin: host %s: snmp_sess_synch_response failed: %s",
+       host->name, (errstr == NULL) ? "Unknown problem" : errstr);
+
+    if (res != NULL)
+      snmp_free_pdu (res);
+    res = NULL;
+
+    sfree (errstr);
+    csnmp_host_close_session (host);
+
+    return (-1);
+  }
+
+
+  for (vb = res->variables; vb != NULL; vb = vb->next_variable)
+  {
+#if COLLECT_DEBUG
+    char buffer[1024];
+    snprint_variable (buffer, sizeof (buffer),
+       vb->name, vb->name_length, vb);
+    DEBUG ("snmp plugin: Got this variable: %s", buffer);
+#endif /* COLLECT_DEBUG */
+
+    for (i = 0; i < data->values_len; i++)
+      if (snmp_oid_compare (data->values[i].oid, data->values[i].oid_len,
+           vb->name, vb->name_length) == 0)
+        vl.values[i] = csnmp_value_list_to_value (vb, ds->ds[i].type,
+            data->scale, data->shift, host->name, data->name);
+  } /* for (res->variables) */
+
+  if (res != NULL)
+    snmp_free_pdu (res);
+  res = NULL;
+
+  DEBUG ("snmp plugin: -> plugin_dispatch_values (&vl);");
+  plugin_dispatch_values (&vl);
+  sfree (vl.values);
+
+  return (0);
+} /* int csnmp_read_value */
+
+static int csnmp_read_host (user_data_t *ud)
+{
+  host_definition_t *host;
+  cdtime_t time_start;
+  cdtime_t time_end;
+  int status;
+  int success;
+  int i;
+
+  host = ud->data;
+
+  if (host->interval == 0)
+    host->interval = interval_g;
+
+  time_start = cdtime ();
+
+  if (host->sess_handle == NULL)
+    csnmp_host_open_session (host);
+
+  if (host->sess_handle == NULL)
+    return (-1);
+
+  success = 0;
+  for (i = 0; i < host->data_list_len; i++)
+  {
+    data_definition_t *data = host->data_list[i];
+
+    if (data->is_table)
+      status = csnmp_read_table (host, data);
+    else
+      status = csnmp_read_value (host, data);
+
+    if (status == 0)
+      success++;
+  }
+
+  time_end = cdtime ();
+  if ((time_end - time_start) > host->interval)
+  {
+    WARNING ("snmp plugin: Host `%s' should be queried every %.3f "
+       "seconds, but reading all values takes %.3f seconds.",
+       host->name,
+       CDTIME_T_TO_DOUBLE (host->interval),
+       CDTIME_T_TO_DOUBLE (time_end - time_start));
+  }
+
+  if (success == 0)
+    return (-1);
+
+  return (0);
+} /* int csnmp_read_host */
+
+static int csnmp_init (void)
+{
+  call_snmp_init_once ();
+
+  return (0);
+} /* int csnmp_init */
+
+static int csnmp_shutdown (void)
+{
+  data_definition_t *data_this;
+  data_definition_t *data_next;
+
+  /* When we get here, the read threads have been stopped and all the
+   * `host_definition_t' will be freed. */
+  DEBUG ("snmp plugin: Destroying all data definitions.");
+
+  data_this = data_head;
+  data_head = NULL;
+  while (data_this != NULL)
+  {
+    data_next = data_this->next;
+
+    sfree (data_this->name);
+    sfree (data_this->type);
+    sfree (data_this->values);
+    sfree (data_this);
+
+    data_this = data_next;
+  }
+
+  return (0);
+} /* int csnmp_shutdown */
+
+void module_register (void)
+{
+  plugin_register_complex_config ("snmp", csnmp_config);
+  plugin_register_init ("snmp", csnmp_init);
+  plugin_register_shutdown ("snmp", csnmp_shutdown);
+} /* void module_register */
+
+/*
+ * vim: shiftwidth=2 softtabstop=2 tabstop=8 fdm=marker
+ */
diff --git a/src/swap.c b/src/swap.c
new file mode 100644 (file)
index 0000000..397969e
--- /dev/null
@@ -0,0 +1,783 @@
+/**
+ * collectd - src/swap.c
+ * Copyright (C) 2005-2010  Florian octo Forster
+ * Copyright (C) 2009       Stefan Völkel
+ * Copyright (C) 2009       Manuel Sanmartin
+ * Copyright (C) 2010       Aurélien Reynaud
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at collectd.org>
+ *   Manuel Sanmartin
+ *   Aurélien Reynaud <collectd at wattapower.net>
+ **/
+
+#if HAVE_CONFIG_H
+# include "config.h"
+# undef HAVE_CONFIG_H
+#endif
+/* avoid swap.h error "Cannot use swapctl in the large files compilation environment" */
+#if HAVE_SYS_SWAP_H && !defined(_LP64) && _FILE_OFFSET_BITS == 64
+#  undef _FILE_OFFSET_BITS
+#  undef _LARGEFILE64_SOURCE
+#endif
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#if HAVE_SYS_SWAP_H
+# include <sys/swap.h>
+#endif
+#if HAVE_VM_ANON_H
+# include <vm/anon.h>
+#endif
+#if HAVE_SYS_PARAM_H
+#  include <sys/param.h>
+#endif
+#if HAVE_SYS_SYSCTL_H
+#  include <sys/sysctl.h>
+#endif
+#if HAVE_SYS_DKSTAT_H
+#  include <sys/dkstat.h>
+#endif
+#if HAVE_KVM_H
+#  include <kvm.h>
+#endif
+
+#if HAVE_STATGRAB_H
+# include <statgrab.h>
+#endif
+
+#if HAVE_PERFSTAT
+# include <sys/protosw.h>
+# include <libperfstat.h>
+#endif
+
+#undef  MAX
+#define MAX(x,y) ((x) > (y) ? (x) : (y))
+
+#if KERNEL_LINUX
+# define SWAP_HAVE_CONFIG 1
+/* No global variables */
+/* #endif KERNEL_LINUX */
+
+#elif HAVE_SWAPCTL && HAVE_SWAPCTL_TWO_ARGS
+# define SWAP_HAVE_CONFIG 1
+static derive_t pagesize;
+/* #endif HAVE_SWAPCTL && HAVE_SWAPCTL_TWO_ARGS */
+
+#elif defined(VM_SWAPUSAGE)
+/* No global variables */
+/* #endif defined(VM_SWAPUSAGE) */
+
+#elif HAVE_LIBKVM_GETSWAPINFO
+static kvm_t *kvm_obj = NULL;
+int kvm_pagesize;
+/* #endif HAVE_LIBKVM_GETSWAPINFO */
+
+#elif HAVE_LIBSTATGRAB
+/* No global variables */
+/* #endif HAVE_LIBSTATGRAB */
+
+#elif HAVE_PERFSTAT
+static int pagesize;
+static perfstat_memory_total_t pmemory;
+/*# endif HAVE_PERFSTAT */
+
+#else
+# error "No applicable input method."
+#endif /* HAVE_LIBSTATGRAB */
+
+#if SWAP_HAVE_CONFIG
+static const char *config_keys[] =
+{
+       "ReportByDevice"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static _Bool report_by_device = 0;
+
+static int swap_config (const char *key, const char *value) /* {{{ */
+{
+       if (strcasecmp ("ReportByDevice", key) == 0)
+       {
+               if (IS_TRUE (value))
+                       report_by_device = 1;
+               else
+                       report_by_device = 0;
+       }
+       else
+       {
+               return (-1);
+       }
+
+       return (0);
+} /* }}} int swap_config */
+#endif /* SWAP_HAVE_CONFIG */
+
+static int swap_init (void) /* {{{ */
+{
+#if KERNEL_LINUX
+       /* No init stuff */
+/* #endif KERNEL_LINUX */
+
+#elif HAVE_SWAPCTL && HAVE_SWAPCTL_TWO_ARGS
+       /* getpagesize(3C) tells me this does not fail.. */
+       pagesize = (derive_t) getpagesize ();
+/* #endif HAVE_SWAPCTL */
+
+#elif defined(VM_SWAPUSAGE)
+       /* No init stuff */
+/* #endif defined(VM_SWAPUSAGE) */
+
+#elif HAVE_LIBKVM_GETSWAPINFO
+       if (kvm_obj != NULL)
+       {
+               kvm_close (kvm_obj);
+               kvm_obj = NULL;
+       }
+
+       kvm_pagesize = getpagesize ();
+
+       if ((kvm_obj = kvm_open (NULL, /* execfile */
+                                       NULL, /* corefile */
+                                       NULL, /* swapfile */
+                                       O_RDONLY, /* flags */
+                                       NULL)) /* errstr */
+                       == NULL)
+       {
+               ERROR ("swap plugin: kvm_open failed.");
+               return (-1);
+       }
+/* #endif HAVE_LIBKVM_GETSWAPINFO */
+
+#elif HAVE_LIBSTATGRAB
+       /* No init stuff */
+/* #endif HAVE_LIBSTATGRAB */
+
+#elif HAVE_PERFSTAT
+       pagesize = getpagesize();
+#endif /* HAVE_PERFSTAT */
+
+       return (0);
+} /* }}} int swap_init */
+
+static void swap_submit (const char *plugin_instance, /* {{{ */
+               const char *type, const char *type_instance,
+               value_t value)
+{
+       value_list_t vl = VALUE_LIST_INIT;
+
+       assert (type != NULL);
+
+       vl.values = &value;
+       vl.values_len = 1;
+       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+       sstrncpy (vl.plugin, "swap", sizeof (vl.plugin));
+       if (plugin_instance != NULL)
+               sstrncpy (vl.plugin_instance, plugin_instance, 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);
+} /* }}} void swap_submit_inst */
+
+static void swap_submit_gauge (const char *plugin_instance, /* {{{ */
+               const char *type_instance, gauge_t value)
+{
+       value_t v;
+
+       v.gauge = value;
+       swap_submit (plugin_instance, "swap", type_instance, v);
+} /* }}} void swap_submit_gauge */
+
+#if KERNEL_LINUX
+static void swap_submit_derive (const char *plugin_instance, /* {{{ */
+               const char *type_instance, derive_t value)
+{
+       value_t v;
+
+       v.derive = value;
+       swap_submit (plugin_instance, "swap_io", type_instance, v);
+} /* }}} void swap_submit_derive */
+
+static int swap_read_separate (void) /* {{{ */
+{
+       FILE *fh;
+       char buffer[1024];
+
+       fh = fopen ("/proc/swaps", "r");
+       if (fh == NULL)
+       {
+               char errbuf[1024];
+               WARNING ("swap plugin: fopen (/proc/swaps) failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       while (fgets (buffer, sizeof (buffer), fh) != NULL)
+       {
+               char *fields[8];
+               int numfields;
+               char *endptr;
+
+               char path[PATH_MAX];
+               gauge_t size;
+               gauge_t used;
+               gauge_t free;
+
+               numfields = strsplit (buffer, fields, STATIC_ARRAY_SIZE (fields));
+               if (numfields != 5)
+                       continue;
+
+               sstrncpy (path, fields[0], sizeof (path));
+               escape_slashes (path, sizeof (path));
+
+               errno = 0;
+               endptr = NULL;
+               size = strtod (fields[2], &endptr);
+               if ((endptr == fields[2]) || (errno != 0))
+                       continue;
+
+               errno = 0;
+               endptr = NULL;
+               used = strtod (fields[3], &endptr);
+               if ((endptr == fields[3]) || (errno != 0))
+                       continue;
+
+               if (size < used)
+                       continue;
+
+               free = size - used;
+
+               swap_submit_gauge (path, "used", used);
+               swap_submit_gauge (path, "free", free);
+       }
+
+       fclose (fh);
+
+       return (0);
+} /* }}} int swap_read_separate */
+
+static int swap_read_combined (void) /* {{{ */
+{
+       FILE *fh;
+       char buffer[1024];
+
+       uint8_t have_data = 0;
+       gauge_t swap_used   = 0.0;
+       gauge_t swap_cached = 0.0;
+       gauge_t swap_free   = 0.0;
+       gauge_t swap_total  = 0.0;
+
+       fh = fopen ("/proc/meminfo", "r");
+       if (fh == NULL)
+       {
+               char errbuf[1024];
+               WARNING ("swap plugin: fopen (/proc/meminfo) failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       while (fgets (buffer, sizeof (buffer), fh) != NULL)
+       {
+               char *fields[8];
+               int numfields;
+
+               numfields = strsplit (buffer, fields, STATIC_ARRAY_SIZE (fields));
+               if (numfields < 2)
+                       continue;
+
+               if (strcasecmp (fields[0], "SwapTotal:") == 0)
+               {
+                       swap_total = strtod (fields[1], /* endptr = */ NULL);
+                       have_data |= 0x01;
+               }
+               else if (strcasecmp (fields[0], "SwapFree:") == 0)
+               {
+                       swap_free = strtod (fields[1], /* endptr = */ NULL);
+                       have_data |= 0x02;
+               }
+               else if (strcasecmp (fields[0], "SwapCached:") == 0)
+               {
+                       swap_cached = strtod (fields[1], /* endptr = */ NULL);
+                       have_data |= 0x04;
+               }
+       }
+
+       fclose (fh);
+
+       if (have_data != 0x07)
+               return (ENOENT);
+
+       if (isnan (swap_total)
+                       || (swap_total <= 0.0)
+                       || ((swap_free + swap_cached) > swap_total))
+               return (EINVAL);
+
+       swap_used = swap_total - (swap_free + swap_cached);
+
+       swap_submit_gauge (NULL, "used",   1024.0 * swap_used);
+       swap_submit_gauge (NULL, "free",   1024.0 * swap_free);
+       swap_submit_gauge (NULL, "cached", 1024.0 * swap_cached);
+
+       return (0);
+} /* }}} int swap_read_combined */
+
+static int swap_read_io (void) /* {{{ */
+{
+       FILE *fh;
+       char buffer[1024];
+
+       _Bool old_kernel = 0;
+
+       uint8_t have_data = 0;
+       derive_t swap_in  = 0;
+       derive_t swap_out = 0;
+
+       fh = fopen ("/proc/vmstat", "r");
+       if (fh == NULL)
+       {
+               /* /proc/vmstat does not exist in kernels <2.6 */
+               fh = fopen ("/proc/stat", "r");
+               if (fh == NULL)
+               {
+                       char errbuf[1024];
+                       WARNING ("swap: fopen: %s",
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       return (-1);
+               }
+               else
+                       old_kernel = 1;
+       }
+
+       while (fgets (buffer, sizeof (buffer), fh) != NULL)
+       {
+               char *fields[8];
+               int numfields;
+
+               numfields = strsplit (buffer, fields, STATIC_ARRAY_SIZE (fields));
+
+               if (!old_kernel)
+               {
+                       if (numfields != 2)
+                               continue;
+
+                       if (strcasecmp ("pswpin", fields[0]) == 0)
+                       {
+                               strtoderive (fields[1], &swap_in);
+                               have_data |= 0x01;
+                       }
+                       else if (strcasecmp ("pswpout", fields[0]) == 0)
+                       {
+                               strtoderive (fields[1], &swap_out);
+                               have_data |= 0x02;
+                       }
+               }
+               else /* if (old_kernel) */
+               {
+                       if (numfields != 3)
+                               continue;
+
+                       if (strcasecmp ("page", fields[0]) == 0)
+                       {
+                               strtoderive (fields[1], &swap_in);
+                               strtoderive (fields[2], &swap_out);
+                       }
+               }
+       } /* while (fgets) */
+
+       fclose (fh);
+
+       if (have_data != 0x03)
+               return (ENOENT);
+
+       swap_submit_derive (NULL, "in",  swap_in);
+       swap_submit_derive (NULL, "out", swap_out);
+
+       return (0);
+} /* }}} int swap_read_io */
+
+static int swap_read (void) /* {{{ */
+{
+       if (report_by_device)
+               swap_read_separate ();
+       else
+               swap_read_combined ();
+
+       swap_read_io ();
+
+       return (0);
+} /* }}} int swap_read */
+/* #endif KERNEL_LINUX */
+
+/*
+ * Under Solaris, two mechanisms can be used to read swap statistics, swapctl
+ * and kstat. The former reads physical space used on a device, the latter
+ * reports the view from the virtual memory system. It was decided that the
+ * kstat-based information should be moved to the "vmem" plugin, but nobody
+ * with enough Solaris experience was available at that time to do this. The
+ * code below is still there for your reference but it won't be activated in
+ * *this* plugin again. --octo
+ */
+#elif 0 && HAVE_LIBKSTAT
+/* kstat-based read function */
+static int swap_read_kstat (void) /* {{{ */
+{
+       derive_t swap_alloc;
+       derive_t swap_resv;
+       derive_t swap_avail;
+
+       struct anoninfo ai;
+
+       if (swapctl (SC_AINFO, &ai) == -1)
+       {
+               char errbuf[1024];
+               ERROR ("swap plugin: swapctl failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       /*
+        * Calculations from:
+        * http://cvs.opensolaris.org/source/xref/on/usr/src/cmd/swap/swap.c
+        * Also see:
+        * http://www.itworld.com/Comp/2377/UIR980701perf/ (outdated?)
+        * /usr/include/vm/anon.h
+        *
+        * In short, swap -s shows: allocated + reserved = used, available
+        *
+        * However, Solaris does not allow to allocated/reserved more than the
+        * available swap (physical memory + disk swap), so the pedant may
+        * prefer: allocated + unallocated = reserved, available
+        *
+        * We map the above to: used + resv = n/a, free
+        *
+        * Does your brain hurt yet?  - Christophe Kalt
+        *
+        * Oh, and in case you wonder,
+        * swap_alloc = pagesize * ( ai.ani_max - ai.ani_free );
+        * can suffer from a 32bit overflow.
+        */
+       swap_alloc  = (derive_t) ((ai.ani_max - ai.ani_free) * pagesize);
+       swap_resv   = (derive_t) ((ai.ani_resv + ai.ani_free - ai.ani_max)
+                       * pagesize);
+       swap_avail  = (derive_t) ((ai.ani_max - ai.ani_resv) * pagesize);
+
+       swap_submit_gauge (NULL, "used", swap_alloc);
+       swap_submit_gauge (NULL, "free", swap_avail);
+       swap_submit_gauge (NULL, "reserved", swap_resv);
+
+       return (0);
+} /* }}} int swap_read_kstat */
+/* #endif 0 && HAVE_LIBKSTAT */
+
+#elif HAVE_SWAPCTL && HAVE_SWAPCTL_TWO_ARGS
+/* swapctl-based read function */
+static int swap_read (void) /* {{{ */
+{
+        swaptbl_t *s;
+       char *s_paths;
+        int swap_num;
+        int status;
+        int i;
+
+        derive_t avail = 0;
+        derive_t total = 0;
+
+        swap_num = swapctl (SC_GETNSWP, NULL);
+        if (swap_num < 0)
+        {
+                ERROR ("swap plugin: swapctl (SC_GETNSWP) failed with status %i.",
+                                swap_num);
+                return (-1);
+        }
+        else if (swap_num == 0)
+                return (0);
+
+       /* Allocate and initialize the swaptbl_t structure */
+        s = (swaptbl_t *) smalloc (swap_num * sizeof (swapent_t) + sizeof (struct swaptable));
+        if (s == NULL)
+        {
+                ERROR ("swap plugin: smalloc failed.");
+                return (-1);
+        }
+
+       /* Memory to store the path names. We only use these paths when the
+        * separate option has been configured, but it's easier to just
+        * allocate enough memory in any case. */
+       s_paths = calloc (swap_num, PATH_MAX);
+       if (s_paths == NULL)
+       {
+               ERROR ("swap plugin: malloc failed.");
+               sfree (s);
+               return (-1);
+       }
+        for (i = 0; i < swap_num; i++)
+               s->swt_ent[i].ste_path = s_paths + (i * PATH_MAX);
+        s->swt_n = swap_num;
+
+        status = swapctl (SC_LIST, s);
+        if (status < 0)
+        {
+               char errbuf[1024];
+                ERROR ("swap plugin: swapctl (SC_LIST) failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               sfree (s_paths);
+                sfree (s);
+                return (-1);
+        }
+       else if (swap_num < status)
+       {
+               /* more elements returned than requested */
+               ERROR ("swap plugin: I allocated memory for %i structure%s, "
+                               "but swapctl(2) claims to have returned %i. "
+                               "I'm confused and will give up.",
+                               swap_num, (swap_num == 1) ? "" : "s",
+                               status);
+               sfree (s_paths);
+                sfree (s);
+                return (-1);
+       }
+       else if (swap_num > status)
+               /* less elements returned than requested */
+               swap_num = status;
+
+        for (i = 0; i < swap_num; i++)
+        {
+               char path[PATH_MAX];
+               derive_t this_total;
+               derive_t this_avail;
+
+                if ((s->swt_ent[i].ste_flags & ST_INDEL) != 0)
+                        continue;
+
+               this_total = ((derive_t) s->swt_ent[i].ste_pages) * pagesize;
+               this_avail = ((derive_t) s->swt_ent[i].ste_free)  * pagesize;
+
+               /* Shortcut for the "combined" setting (default) */
+               if (!report_by_device)
+               {
+                       avail += this_avail;
+                       total += this_total;
+                       continue;
+               }
+
+               sstrncpy (path, s->swt_ent[i].ste_path, sizeof (path));
+               escape_slashes (path, sizeof (path));
+
+               swap_submit_gauge (path, "used", (gauge_t) (this_total - this_avail));
+               swap_submit_gauge (path, "free", (gauge_t) this_avail);
+        } /* for (swap_num) */
+
+        if (total < avail)
+        {
+                ERROR ("swap plugin: Total swap space (%"PRIi64") "
+                                "is less than free swap space (%"PRIi64").",
+                                total, avail);
+               sfree (s_paths);
+                sfree (s);
+                return (-1);
+        }
+
+       /* If the "separate" option was specified (report_by_device == 2), all
+        * values have already been dispatched from within the loop. */
+       if (!report_by_device)
+       {
+               swap_submit_gauge (NULL, "used", (gauge_t) (total - avail));
+               swap_submit_gauge (NULL, "free", (gauge_t) avail);
+       }
+
+       sfree (s_paths);
+        sfree (s);
+       return (0);
+} /* }}} int swap_read */
+/* #endif HAVE_SWAPCTL && HAVE_SWAPCTL_TWO_ARGS */
+
+#elif HAVE_SWAPCTL && HAVE_SWAPCTL_THREE_ARGS
+static int swap_read (void) /* {{{ */
+{
+       struct swapent *swap_entries;
+       int swap_num;
+       int status;
+       int i;
+
+       derive_t used  = 0;
+       derive_t total = 0;
+
+       swap_num = swapctl (SWAP_NSWAP, NULL, 0);
+       if (swap_num < 0)
+       {
+               ERROR ("swap plugin: swapctl (SWAP_NSWAP) failed with status %i.",
+                               swap_num);
+               return (-1);
+       }
+       else if (swap_num == 0)
+               return (0);
+
+       swap_entries = calloc (swap_num, sizeof (*swap_entries));
+       if (swap_entries == NULL)
+       {
+               ERROR ("swap plugin: calloc failed.");
+               return (-1);
+       }
+
+       status = swapctl (SWAP_STATS, swap_entries, swap_num);
+       if (status != swap_num)
+       {
+               ERROR ("swap plugin: swapctl (SWAP_STATS) failed with status %i.",
+                               status);
+               sfree (swap_entries);
+               return (-1);
+       }
+
+#if defined(DEV_BSIZE) && (DEV_BSIZE > 0)
+# define C_SWAP_BLOCK_SIZE ((derive_t) DEV_BSIZE)
+#else
+# define C_SWAP_BLOCK_SIZE ((derive_t) 512)
+#endif
+
+       for (i = 0; i < swap_num; i++)
+       {
+               if ((swap_entries[i].se_flags & SWF_ENABLE) == 0)
+                       continue;
+
+               used  += ((derive_t) swap_entries[i].se_inuse)
+                       * C_SWAP_BLOCK_SIZE;
+               total += ((derive_t) swap_entries[i].se_nblks)
+                       * C_SWAP_BLOCK_SIZE;
+       }
+
+       if (total < used)
+       {
+               ERROR ("swap plugin: Total swap space (%"PRIu64") "
+                               "is less than used swap space (%"PRIu64").",
+                               total, used);
+               return (-1);
+       }
+
+       swap_submit_gauge (NULL, "used", (gauge_t) used);
+       swap_submit_gauge (NULL, "free", (gauge_t) (total - used));
+
+       sfree (swap_entries);
+
+       return (0);
+} /* }}} int swap_read */
+/* #endif HAVE_SWAPCTL && HAVE_SWAPCTL_THREE_ARGS */
+
+#elif defined(VM_SWAPUSAGE)
+static int swap_read (void) /* {{{ */
+{
+       int              mib[3];
+       size_t           mib_len;
+       struct xsw_usage sw_usage;
+       size_t           sw_usage_len;
+
+       mib_len = 2;
+       mib[0]  = CTL_VM;
+       mib[1]  = VM_SWAPUSAGE;
+
+       sw_usage_len = sizeof (struct xsw_usage);
+
+       if (sysctl (mib, mib_len, &sw_usage, &sw_usage_len, NULL, 0) != 0)
+               return (-1);
+
+       /* The returned values are bytes. */
+       swap_submit_gauge (NULL, "used", (gauge_t) sw_usage.xsu_used);
+       swap_submit_gauge (NULL, "free", (gauge_t) sw_usage.xsu_avail);
+
+       return (0);
+} /* }}} int swap_read */
+/* #endif VM_SWAPUSAGE */
+
+#elif HAVE_LIBKVM_GETSWAPINFO
+static int swap_read (void) /* {{{ */
+{
+       struct kvm_swap data_s;
+       int             status;
+
+       derive_t used;
+       derive_t free;
+       derive_t total;
+
+       if (kvm_obj == NULL)
+               return (-1);
+
+       /* only one structure => only get the grand total, no details */
+       status = kvm_getswapinfo (kvm_obj, &data_s, 1, 0);
+       if (status == -1)
+               return (-1);
+
+       total = (derive_t) data_s.ksw_total;
+       used  = (derive_t) data_s.ksw_used;
+
+       total *= (derive_t) kvm_pagesize;
+       used  *= (derive_t) kvm_pagesize;
+
+       free = total - used;
+
+       swap_submit_gauge (NULL, "used", (gauge_t) used);
+       swap_submit_gauge (NULL, "free", (gauge_t) free);
+
+       return (0);
+} /* }}} int swap_read */
+/* #endif HAVE_LIBKVM_GETSWAPINFO */
+
+#elif HAVE_LIBSTATGRAB
+static int swap_read (void) /* {{{ */
+{
+       sg_swap_stats *swap;
+
+       swap = sg_get_swap_stats ();
+
+       if (swap == NULL)
+               return (-1);
+
+       swap_submit_gauge (NULL, "used", (gauge_t) swap->used);
+       swap_submit_gauge (NULL, "free", (gauge_t) swap->free);
+
+       return (0);
+} /* }}} int swap_read */
+/* #endif  HAVE_LIBSTATGRAB */
+
+#elif HAVE_PERFSTAT
+static int swap_read (void) /* {{{ */
+{
+        if(perfstat_memory_total(NULL, &pmemory, sizeof(perfstat_memory_total_t), 1) < 0)
+       {
+                char errbuf[1024];
+                WARNING ("memory plugin: perfstat_memory_total failed: %s",
+                        sstrerror (errno, errbuf, sizeof (errbuf)));
+                return (-1);
+        }
+       swap_submit_gauge (NULL, "used", (gauge_t) (pmemory.pgsp_total - pmemory.pgsp_free) * pagesize);
+       swap_submit_gauge (NULL, "free", (gauge_t) pmemory.pgsp_free * pagesize );
+
+       return (0);
+} /* }}} int swap_read */
+#endif /* HAVE_PERFSTAT */
+
+void module_register (void)
+{
+#if SWAP_HAVE_CONFIG
+       plugin_register_config ("swap", swap_config, config_keys, config_keys_num);
+#endif
+       plugin_register_init ("swap", swap_init);
+       plugin_register_read ("swap", swap_read);
+} /* void module_register */
+
+/* vim: set fdm=marker : */
diff --git a/src/syslog.c b/src/syslog.c
new file mode 100644 (file)
index 0000000..ace9dc6
--- /dev/null
@@ -0,0 +1,94 @@
+/**
+ * collectd - src/syslog.c
+ * Copyright (C) 2007  Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#if HAVE_SYSLOG_H
+# include <syslog.h>
+#endif
+
+#if COLLECT_DEBUG
+static int log_level = LOG_DEBUG;
+#else
+static int log_level = LOG_INFO;
+#endif /* COLLECT_DEBUG */
+
+static const char *config_keys[] =
+{
+       "LogLevel"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
+
+static int sl_config (const char *key, const char *value)
+{
+       if (strcasecmp (key, "LogLevel") == 0)
+       {
+               if ((strcasecmp (value, "emerg") == 0)
+                               || (strcasecmp (value, "alert") == 0)
+                               || (strcasecmp (value, "crit") == 0)
+                               || (strcasecmp (value, "err") == 0))
+                       log_level = LOG_ERR;
+               else if (strcasecmp (value, "warning") == 0)
+                       log_level = LOG_WARNING;
+               else if (strcasecmp (value, "notice") == 0)
+                       log_level = LOG_NOTICE;
+               else if (strcasecmp (value, "info") == 0)
+                       log_level = LOG_INFO;
+#if COLLECT_DEBUG
+               else if (strcasecmp (value, "debug") == 0)
+                       log_level = LOG_DEBUG;
+#endif
+               else
+                       return (1);
+       }
+       else
+               return (-1);
+
+       return (0);
+} /* int sl_config */
+
+static void sl_log (int severity, const char *msg,
+               user_data_t __attribute__((unused)) *user_data)
+{
+       if (severity > log_level)
+               return;
+
+       syslog (severity, "%s", msg);
+} /* void sl_log */
+
+static int sl_shutdown (void)
+{
+       closelog ();
+
+       return (0);
+}
+
+void module_register (void)
+{
+       openlog ("collectd", LOG_CONS | LOG_PID, LOG_DAEMON);
+
+       plugin_register_config ("syslog", sl_config, config_keys, config_keys_num);
+       plugin_register_log ("syslog", sl_log, /* user_data = */ NULL);
+       plugin_register_shutdown ("syslog", sl_shutdown);
+} /* void module_register(void) */
diff --git a/src/table.c b/src/table.c
new file mode 100644 (file)
index 0000000..9641c75
--- /dev/null
@@ -0,0 +1,560 @@
+/**
+ * collectd - src/table.c
+ * Copyright (C) 2009  Sebastian Harl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Sebastian Harl <sh at tokkee.org>
+ **/
+
+/*
+ * This module provides generic means to parse and dispatch tabular data.
+ */
+
+#include "collectd.h"
+#include "common.h"
+
+#include "configfile.h"
+#include "plugin.h"
+
+#define log_err(...) ERROR ("table plugin: " __VA_ARGS__)
+#define log_warn(...) WARNING ("table plugin: " __VA_ARGS__)
+
+/*
+ * private data types
+ */
+
+typedef struct {
+       char  *type;
+       char  *instance_prefix;
+       int   *instances;
+       size_t instances_num;
+       int   *values;
+       size_t values_num;
+
+       const data_set_t *ds;
+} tbl_result_t;
+
+typedef struct {
+       char *file;
+       char *sep;
+       char *instance;
+
+       tbl_result_t *results;
+       size_t        results_num;
+
+       size_t max_colnum;
+} tbl_t;
+
+static void tbl_result_setup (tbl_result_t *res)
+{
+       res->type            = NULL;
+
+       res->instance_prefix = NULL;
+       res->instances       = NULL;
+       res->instances_num   = 0;
+
+       res->values          = NULL;
+       res->values_num      = 0;
+
+       res->ds              = NULL;
+} /* tbl_result_setup */
+
+static void tbl_result_clear (tbl_result_t *res)
+{
+       sfree (res->type);
+
+       sfree (res->instance_prefix);
+       sfree (res->instances);
+       res->instances_num = 0;
+
+       sfree (res->values);
+       res->values_num = 0;
+
+       res->ds = NULL;
+} /* tbl_result_clear */
+
+static void tbl_setup (tbl_t *tbl, char *file)
+{
+       tbl->file        = sstrdup (file);
+       tbl->sep         = NULL;
+       tbl->instance    = NULL;
+
+       tbl->results     = NULL;
+       tbl->results_num = 0;
+
+       tbl->max_colnum  = 0;
+} /* tbl_setup */
+
+static void tbl_clear (tbl_t *tbl)
+{
+       size_t i;
+
+       sfree (tbl->file);
+       sfree (tbl->sep);
+       sfree (tbl->instance);
+
+       for (i = 0; i < tbl->results_num; ++i)
+               tbl_result_clear (tbl->results + i);
+       sfree (tbl->results);
+       tbl->results_num = 0;
+
+       tbl->max_colnum  = 0;
+} /* tbl_clear */
+
+static tbl_t *tables;
+static size_t tables_num;
+
+/*
+ * configuration handling
+ */
+
+static int tbl_config_set_s (char *name, char **var, oconfig_item_t *ci)
+{
+       if ((1 != ci->values_num)
+                       || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
+               log_err ("\"%s\" expects a single string argument.", name);
+               return 1;
+       }
+
+       sfree (*var);
+       *var = sstrdup (ci->values[0].value.string);
+       return 0;
+} /* tbl_config_set_separator */
+
+static int tbl_config_append_array_i (char *name, int **var, size_t *len,
+               oconfig_item_t *ci)
+{
+       int *tmp;
+
+       size_t i;
+
+       if (1 > ci->values_num) {
+               log_err ("\"%s\" expects at least one argument.", name);
+               return 1;
+       }
+
+       for (i = 0; i < ci->values_num; ++i) {
+               if (OCONFIG_TYPE_NUMBER != ci->values[i].type) {
+                       log_err ("\"%s\" expects numerical arguments only.", name);
+                       return 1;
+               }
+       }
+
+       *len += ci->values_num;
+       tmp = (int *)realloc (*var, *len * sizeof (**var));
+       if (NULL == tmp) {
+               char errbuf[1024];
+               log_err ("realloc failed: %s.",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return -1;
+       }
+
+       *var = tmp;
+
+       for (i = *len - ci->values_num; i < *len; ++i)
+               (*var)[i] = (int)ci->values[i].value.number;
+       return 0;
+} /* tbl_config_append_array_s */
+
+static int tbl_config_result (tbl_t *tbl, oconfig_item_t *ci)
+{
+       tbl_result_t *res;
+
+       int status = 0;
+       size_t i;
+
+       if (0 != ci->values_num) {
+               log_err ("<Result> does not expect any arguments.");
+               return 1;
+       }
+
+       res = (tbl_result_t *)realloc (tbl->results,
+                       (tbl->results_num + 1) * sizeof (*tbl->results));
+       if (NULL == tbl) {
+               char errbuf[1024];
+               log_err ("realloc failed: %s.",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return -1;
+       }
+
+       tbl->results = res;
+       ++tbl->results_num;
+
+       res = tbl->results + tbl->results_num - 1;
+       tbl_result_setup (res);
+
+       for (i = 0; i < ci->children_num; ++i) {
+               oconfig_item_t *c = ci->children + i;
+
+               if (0 == strcasecmp (c->key, "Type"))
+                       tbl_config_set_s (c->key, &res->type, c);
+               else if (0 == strcasecmp (c->key, "InstancePrefix"))
+                       tbl_config_set_s (c->key, &res->instance_prefix, c);
+               else if (0 == strcasecmp (c->key, "InstancesFrom"))
+                       tbl_config_append_array_i (c->key,
+                                       &res->instances, &res->instances_num, c);
+               else if (0 == strcasecmp (c->key, "ValuesFrom"))
+                       tbl_config_append_array_i (c->key,
+                                       &res->values, &res->values_num, c);
+               else
+                       log_warn ("Ignoring unknown config key \"%s\" "
+                                       " in <Result>.", c->key);
+       }
+
+       if (NULL == res->type) {
+               log_err ("No \"Type\" option specified for <Result> "
+                               "in table \"%s\".", tbl->file);
+               status = 1;
+       }
+
+       if (NULL == res->values) {
+               log_err ("No \"ValuesFrom\" option specified for <Result> "
+                               "in table \"%s\".", tbl->file);
+               status = 1;
+       }
+
+       if (0 != status) {
+               tbl_result_clear (res);
+               --tbl->results_num;
+               return status;
+       }
+       return 0;
+} /* tbl_config_result */
+
+static int tbl_config_table (oconfig_item_t *ci)
+{
+       tbl_t *tbl;
+
+       int status = 0;
+       size_t i;
+
+       if ((1 != ci->values_num)
+                       || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
+               log_err ("<Table> expects a single string argument.");
+               return 1;
+       }
+
+       tbl = (tbl_t *)realloc (tables, (tables_num + 1) * sizeof (*tables));
+       if (NULL == tbl) {
+               char errbuf[1024];
+               log_err ("realloc failed: %s.",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return -1;
+       }
+
+       tables = tbl;
+       ++tables_num;
+
+       tbl = tables + tables_num - 1;
+       tbl_setup (tbl, ci->values[0].value.string);
+
+       for (i = 0; i < ci->children_num; ++i) {
+               oconfig_item_t *c = ci->children + i;
+
+               if (0 == strcasecmp (c->key, "Separator"))
+                       tbl_config_set_s (c->key, &tbl->sep, c);
+               else if (0 == strcasecmp (c->key, "Instance"))
+                       tbl_config_set_s (c->key, &tbl->instance, c);
+               else if (0 == strcasecmp (c->key, "Result"))
+                       tbl_config_result (tbl, c);
+               else
+                       log_warn ("Ignoring unknown config key \"%s\" "
+                                       "in <Table %s>.", c->key, tbl->file);
+       }
+
+       if (NULL == tbl->sep) {
+               log_err ("Table \"%s\" does not specify any separator.", tbl->file);
+               status = 1;
+       }
+       strunescape (tbl->sep, strlen (tbl->sep) + 1);
+
+       if (NULL == tbl->instance) {
+               tbl->instance = sstrdup (tbl->file);
+               replace_special (tbl->instance, strlen (tbl->instance));
+       }
+
+       if (NULL == tbl->results) {
+               log_err ("Table \"%s\" does not specify any (valid) results.",
+                               tbl->file);
+               status = 1;
+       }
+
+       if (0 != status) {
+               tbl_clear (tbl);
+               --tables_num;
+               return status;
+       }
+
+       for (i = 0; i < tbl->results_num; ++i) {
+               tbl_result_t *res = tbl->results + i;
+               size_t j;
+
+               for (j = 0; j < res->instances_num; ++j)
+                       if (res->instances[j] > tbl->max_colnum)
+                               tbl->max_colnum = res->instances[j];
+
+               for (j = 0; j < res->values_num; ++j)
+                       if (res->values[j] > tbl->max_colnum)
+                               tbl->max_colnum = res->values[j];
+       }
+       return 0;
+} /* tbl_config_table */
+
+static int tbl_config (oconfig_item_t *ci)
+{
+       size_t i;
+
+       for (i = 0; i < ci->children_num; ++i) {
+               oconfig_item_t *c = ci->children + i;
+
+               if (0 == strcasecmp (c->key, "Table"))
+                       tbl_config_table (c);
+               else
+                       log_warn ("Ignoring unknown config key \"%s\".", c->key);
+       }
+       return 0;
+} /* tbl_config */
+
+/*
+ * result handling
+ */
+
+static int tbl_prepare (tbl_t *tbl)
+{
+       size_t i;
+
+       for (i = 0; i < tbl->results_num; ++i) {
+               tbl_result_t *res = tbl->results + i;
+
+               res->ds = plugin_get_ds (res->type);
+               if (NULL == res->ds) {
+                       log_err ("Unknown type \"%s\". See types.db(5) for details.",
+                                       res->type);
+                       return -1;
+               }
+
+               if (res->values_num != (size_t)res->ds->ds_num) {
+                       log_err ("Invalid type \"%s\". Expected %zu data source%s, "
+                                       "got %i.", res->type, res->values_num,
+                                       (1 == res->values_num) ? "" : "s",
+                                       res->ds->ds_num);
+                       return -1;
+               }
+       }
+       return 0;
+} /* tbl_prepare */
+
+static int tbl_finish (tbl_t *tbl)
+{
+       size_t i;
+
+       for (i = 0; i < tbl->results_num; ++i)
+               tbl->results[i].ds = NULL;
+       return 0;
+} /* tbl_finish */
+
+static int tbl_result_dispatch (tbl_t *tbl, tbl_result_t *res,
+               char **fields, size_t fields_num)
+{
+       value_list_t vl = VALUE_LIST_INIT;
+       value_t values[res->values_num];
+
+       size_t i;
+
+       assert (NULL != res->ds);
+       assert (res->values_num == res->ds->ds_num);
+
+       for (i = 0; i < res->values_num; ++i) {
+               char *value;
+
+               assert (res->values[i] < fields_num);
+               value = fields[res->values[i]];
+
+               if (0 != parse_value (value, &values[i], res->ds->ds[i].type))
+                       return -1;
+       }
+
+       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));
+
+       if (0 == res->instances_num) {
+               if (NULL != res->instance_prefix)
+                       sstrncpy (vl.type_instance, res->instance_prefix,
+                                       sizeof (vl.type_instance));
+       }
+       else {
+               char *instances[res->instances_num];
+               char  instances_str[DATA_MAX_NAME_LEN];
+
+               for (i = 0; i < res->instances_num; ++i) {
+                       assert (res->instances[i] < fields_num);
+                       instances[i] = fields[res->instances[i]];
+               }
+
+               strjoin (instances_str, sizeof (instances_str),
+                               instances, STATIC_ARRAY_SIZE (instances), "-");
+               instances_str[sizeof (instances_str) - 1] = '\0';
+
+               vl.type_instance[sizeof (vl.type_instance) - 1] = '\0';
+               if (NULL == res->instance_prefix)
+                       strncpy (vl.type_instance, instances_str,
+                                       sizeof (vl.type_instance));
+               else
+                       snprintf (vl.type_instance, sizeof (vl.type_instance),
+                                       "%s-%s", res->instance_prefix, instances_str);
+
+               if ('\0' != vl.type_instance[sizeof (vl.type_instance) - 1]) {
+                       vl.type_instance[sizeof (vl.type_instance) - 1] = '\0';
+                       log_warn ("Truncated type instance: %s.", vl.type_instance);
+               }
+       }
+
+       plugin_dispatch_values (&vl);
+       return 0;
+} /* tbl_result_dispatch */
+
+static int tbl_parse_line (tbl_t *tbl, char *line, size_t len)
+{
+       char *fields[tbl->max_colnum + 1];
+       char *ptr, *saveptr;
+
+       size_t i;
+
+       i = 0;
+       ptr = line;
+       saveptr = NULL;
+       while (NULL != (fields[i] = strtok_r (ptr, tbl->sep, &saveptr))) {
+               ptr = NULL;
+               ++i;
+
+               if (i > tbl->max_colnum)
+                       break;
+       }
+
+       if (i <= tbl->max_colnum) {
+               log_err ("Not enough columns in line "
+                               "(expected at least %zu, got %zu).",
+                               tbl->max_colnum + 1, i);
+               return -1;
+       }
+
+       for (i = 0; i < tbl->results_num; ++i)
+               if (0 != tbl_result_dispatch (tbl, tbl->results + i,
+                                       fields, STATIC_ARRAY_SIZE (fields))) {
+                       log_err ("Failed to dispatch result.");
+                       continue;
+               }
+       return 0;
+} /* tbl_parse_line */
+
+static int tbl_read_table (tbl_t *tbl)
+{
+       FILE *fh;
+       char  buf[4096];
+
+       fh = fopen (tbl->file, "r");
+       if (NULL == fh) {
+               char errbuf[1024];
+               log_err ("Failed to open file \"%s\": %s.", tbl->file,
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return -1;
+       }
+
+       buf[sizeof (buf) - 1] = '\0';
+       while (NULL != fgets (buf, sizeof (buf), fh)) {
+               if ('\0' != buf[sizeof (buf) - 1]) {
+                       buf[sizeof (buf) - 1] = '\0';
+                       log_err ("Table %s: Truncated line: %s", tbl->file, buf);
+               }
+
+               if (0 != tbl_parse_line (tbl, buf, sizeof (buf))) {
+                       log_err ("Table %s: Failed to parse line: %s", tbl->file, buf);
+                       continue;
+               }
+       }
+
+       if (0 != ferror (fh)) {
+               char errbuf[1024];
+               log_err ("Failed to read from file \"%s\": %s.", tbl->file,
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               fclose (fh);
+               return -1;
+       }
+
+       fclose (fh);
+       return 0;
+} /* tbl_read_table */
+
+/*
+ * collectd callbacks
+ */
+
+static int tbl_read (void)
+{
+       int status = -1;
+       size_t i;
+
+       if (0 == tables_num)
+               return 0;
+
+       for (i = 0; i < tables_num; ++i) {
+               tbl_t *tbl = tables + i;
+
+               if (0 != tbl_prepare (tbl)) {
+                       log_err ("Failed to prepare and parse table \"%s\".", tbl->file);
+                       continue;
+               }
+
+               if (0 == tbl_read_table (tbl))
+                       status = 0;
+
+               tbl_finish (tbl);
+       }
+       return status;
+} /* tbl_read */
+
+static int tbl_shutdown (void)
+{
+       size_t i;
+
+       for (i = 0; i < tables_num; ++i)
+               tbl_clear (&tables[i]);
+       sfree (tables);
+       return 0;
+} /* tbl_shutdown */
+
+static int tbl_init (void)
+{
+       if (0 == tables_num)
+               return 0;
+
+       plugin_register_read ("table", tbl_read);
+       plugin_register_shutdown ("table", tbl_shutdown);
+       return 0;
+} /* tbl_init */
+
+void module_register (void)
+{
+       plugin_register_complex_config ("table", tbl_config);
+       plugin_register_init ("table", tbl_init);
+} /* module_register */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
diff --git a/src/tail.c b/src/tail.c
new file mode 100644 (file)
index 0000000..25cbcd4
--- /dev/null
@@ -0,0 +1,379 @@
+/**
+ * collectd - src/tail.c
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "utils_tail_match.h"
+
+/*
+ *  <Plugin tail>
+ *    <File "/var/log/exim4/mainlog">
+ *     Instance "exim"
+ *     <Match>
+ *       Regex "S=([1-9][0-9]*)"
+ *       ExcludeRegex "U=root.*S="
+ *       DSType "CounterAdd"
+ *       Type "ipt_bytes"
+ *       Instance "total"
+ *     </Match>
+ *    </File>
+ *  </Plugin>
+ */
+
+struct ctail_config_match_s
+{
+  char *regex;
+  char *excluderegex;
+  int flags;
+  char *type;
+  char *type_instance;
+};
+typedef struct ctail_config_match_s ctail_config_match_t;
+
+cu_tail_match_t **tail_match_list = NULL;
+size_t tail_match_list_num = 0;
+
+static int ctail_config_add_string (const char *name, char **dest, oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("tail plugin: `%s' needs exactly one string argument.", name);
+    return (-1);
+  }
+
+  sfree (*dest);
+  *dest = strdup (ci->values[0].value.string);
+  if (*dest == NULL)
+    return (-1);
+
+  return (0);
+} /* int ctail_config_add_string */
+
+static int ctail_config_add_match_dstype (ctail_config_match_t *cm,
+    oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("tail plugin: `DSType' needs exactly one string argument.");
+    return (-1);
+  }
+
+  if (strncasecmp ("Gauge", ci->values[0].value.string, strlen ("Gauge")) == 0)
+  {
+    cm->flags = UTILS_MATCH_DS_TYPE_GAUGE;
+    if (strcasecmp ("GaugeAverage", ci->values[0].value.string) == 0)
+      cm->flags |= UTILS_MATCH_CF_GAUGE_AVERAGE;
+    else if (strcasecmp ("GaugeMin", ci->values[0].value.string) == 0)
+      cm->flags |= UTILS_MATCH_CF_GAUGE_MIN;
+    else if (strcasecmp ("GaugeMax", ci->values[0].value.string) == 0)
+      cm->flags |= UTILS_MATCH_CF_GAUGE_MAX;
+    else if (strcasecmp ("GaugeLast", ci->values[0].value.string) == 0)
+      cm->flags |= UTILS_MATCH_CF_GAUGE_LAST;
+    else
+      cm->flags = 0;
+  }
+  else if (strncasecmp ("Counter", ci->values[0].value.string, strlen ("Counter")) == 0)
+  {
+    cm->flags = UTILS_MATCH_DS_TYPE_COUNTER;
+    if (strcasecmp ("CounterSet", ci->values[0].value.string) == 0)
+      cm->flags |= UTILS_MATCH_CF_COUNTER_SET;
+    else if (strcasecmp ("CounterAdd", ci->values[0].value.string) == 0)
+      cm->flags |= UTILS_MATCH_CF_COUNTER_ADD;
+    else if (strcasecmp ("CounterInc", ci->values[0].value.string) == 0)
+      cm->flags |= UTILS_MATCH_CF_COUNTER_INC;
+    else
+      cm->flags = 0;
+  }
+  else if (strncasecmp ("Derive", ci->values[0].value.string, strlen ("Derive")) == 0)
+  {
+    cm->flags = UTILS_MATCH_DS_TYPE_DERIVE;
+    if (strcasecmp ("DeriveSet", ci->values[0].value.string) == 0)
+      cm->flags |= UTILS_MATCH_CF_DERIVE_SET;
+    else if (strcasecmp ("DeriveAdd", ci->values[0].value.string) == 0)
+      cm->flags |= UTILS_MATCH_CF_DERIVE_ADD;
+    else if (strcasecmp ("DeriveInc", ci->values[0].value.string) == 0)
+      cm->flags |= UTILS_MATCH_CF_DERIVE_INC;
+    else
+      cm->flags = 0;
+  }
+  else if (strncasecmp ("Absolute", ci->values[0].value.string, strlen ("Absolute")) == 0)
+  {
+    cm->flags = UTILS_MATCH_DS_TYPE_ABSOLUTE;
+    if (strcasecmp ("AbsoluteSet", ci->values[0].value.string) == 0)
+      cm->flags |= UTILS_MATCH_CF_ABSOLUTE_SET;
+    else
+      cm->flags = 0;
+  }
+  else
+  {
+    cm->flags = 0;
+  }
+
+  if (cm->flags == 0)
+  {
+    WARNING ("tail plugin: `%s' is not a valid argument to `DSType'.",
+       ci->values[0].value.string);
+    return (-1);
+  }
+
+  return (0);
+} /* int ctail_config_add_match_dstype */
+
+static int ctail_config_add_match (cu_tail_match_t *tm,
+    const char *plugin_instance, oconfig_item_t *ci)
+{
+  ctail_config_match_t cm;
+  int status;
+  int i;
+
+  memset (&cm, '\0', sizeof (cm));
+
+  if (ci->values_num != 0)
+  {
+    WARNING ("tail plugin: Ignoring arguments for the `Match' block.");
+  }
+
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+
+    if (strcasecmp ("Regex", option->key) == 0)
+      status = ctail_config_add_string ("Regex", &cm.regex, option);
+    else if (strcasecmp ("ExcludeRegex", option->key) == 0)
+      status = ctail_config_add_string ("ExcludeRegex", &cm.excluderegex,
+                                       option);
+    else if (strcasecmp ("DSType", option->key) == 0)
+      status = ctail_config_add_match_dstype (&cm, option);
+    else if (strcasecmp ("Type", option->key) == 0)
+      status = ctail_config_add_string ("Type", &cm.type, option);
+    else if (strcasecmp ("Instance", option->key) == 0)
+      status = ctail_config_add_string ("Instance", &cm.type_instance, option);
+    else
+    {
+      WARNING ("tail plugin: Option `%s' not allowed here.", option->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  while (status == 0)
+  {
+    if (cm.regex == NULL)
+    {
+      WARNING ("tail plugin: `Regex' missing in `Match' block.");
+      status = -1;
+      break;
+    }
+
+    if (cm.type == NULL)
+    {
+      WARNING ("tail plugin: `Type' missing in `Match' block.");
+      status = -1;
+      break;
+    }
+
+    if (cm.flags == 0)
+    {
+      WARNING ("tail plugin: `DSType' missing in `Match' block.");
+      status = -1;
+      break;
+    }
+
+    break;
+  } /* 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);
+
+    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);
+
+  return (status);
+} /* int ctail_config_add_match */
+
+static int ctail_config_add_file (oconfig_item_t *ci)
+{
+  cu_tail_match_t *tm;
+  char *plugin_instance = NULL;
+  int num_matches = 0;
+  int status;
+  int i;
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("tail plugin: `File' needs exactly one string argument.");
+    return (-1);
+  }
+
+  tm = tail_match_create (ci->values[0].value.string);
+  if (tm == NULL)
+  {
+    ERROR ("tail plugin: tail_match_create (%s) failed.",
+       ci->values[0].value.string);
+    return (-1);
+  }
+
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+
+    if (strcasecmp ("Match", option->key) == 0)
+    {
+      status = ctail_config_add_match (tm, plugin_instance, option);
+      if (status == 0)
+       num_matches++;
+      /* Be mild with failed matches.. */
+      status = 0;
+    }
+    else if (strcasecmp ("Instance", option->key) == 0)
+      status = ctail_config_add_string ("Instance", &plugin_instance, option);
+    else
+    {
+      WARNING ("tail plugin: Option `%s' not allowed here.", option->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  if (num_matches == 0)
+  {
+    ERROR ("tail plugin: No (valid) matches found for file `%s'.",
+       ci->values[0].value.string);
+    tail_match_destroy (tm);
+    return (-1);
+  }
+  else
+  {
+    cu_tail_match_t **temp;
+
+    temp = (cu_tail_match_t **) realloc (tail_match_list,
+       sizeof (cu_tail_match_t *) * (tail_match_list_num + 1));
+    if (temp == NULL)
+    {
+      ERROR ("tail plugin: realloc failed.");
+      tail_match_destroy (tm);
+      return (-1);
+    }
+
+    tail_match_list = temp;
+    tail_match_list[tail_match_list_num] = tm;
+    tail_match_list_num++;
+  }
+
+  return (0);
+} /* int ctail_config_add_file */
+
+static int ctail_config (oconfig_item_t *ci)
+{
+  int i;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+
+    if (strcasecmp ("File", option->key) == 0)
+      ctail_config_add_file (option);
+    else
+    {
+      WARNING ("tail plugin: Option `%s' not allowed here.", option->key);
+    }
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  return (0);
+} /* int ctail_config */
+
+static int ctail_init (void)
+{
+  if (tail_match_list_num == 0)
+  {
+    WARNING ("tail plugin: File list is empty. Returning an error.");
+    return (-1);
+  }
+
+  return (0);
+} /* int ctail_init */
+
+static int ctail_read (void)
+{
+  int success = 0;
+  size_t i;
+
+  for (i = 0; i < tail_match_list_num; i++)
+  {
+    int status;
+
+    status = tail_match_read (tail_match_list[i]);
+    if (status != 0)
+    {
+      ERROR ("tail plugin: tail_match_read[%zu] failed.", i);
+    }
+    else
+    {
+      success++;
+    }
+  }
+
+  if (success == 0)
+    return (-1);
+  return (0);
+} /* int ctail_read */
+
+static int ctail_shutdown (void)
+{
+  size_t i;
+
+  for (i = 0; i < tail_match_list_num; i++)
+  {
+    tail_match_destroy (tail_match_list[i]);
+    tail_match_list[i] = NULL;
+  }
+  sfree (tail_match_list);
+  tail_match_list_num = 0;
+
+  return (0);
+} /* int ctail_shutdown */
+
+void module_register (void)
+{
+  plugin_register_complex_config ("tail", ctail_config);
+  plugin_register_init ("tail", ctail_init);
+  plugin_register_read ("tail", ctail_read);
+  plugin_register_shutdown ("tail", ctail_shutdown);
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 ts=8 : */
diff --git a/src/tape.c b/src/tape.c
new file mode 100644 (file)
index 0000000..a8e7dc4
--- /dev/null
@@ -0,0 +1,133 @@
+/**
+ * collectd - src/tape.c
+ * Copyright (C) 2005,2006  Scott Garrett
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Scott Garrett <sgarrett at technomancer.com>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#if !HAVE_LIBKSTAT
+# error "No applicable input method."
+#endif
+
+#define MAX_NUMTAPE 256
+extern kstat_ctl_t *kc;
+static kstat_t *ksp[MAX_NUMTAPE];
+static int numtape = 0;
+
+static int tape_init (void)
+{
+       kstat_t *ksp_chain;
+
+       numtape = 0;
+
+       if (kc == NULL)
+               return (-1);
+
+       for (numtape = 0, ksp_chain = kc->kc_chain;
+                       (numtape < MAX_NUMTAPE) && (ksp_chain != NULL);
+                       ksp_chain = ksp_chain->ks_next)
+       {
+               if (strncmp (ksp_chain->ks_class, "tape", 4) )
+                       continue;
+               if (ksp_chain->ks_type != KSTAT_TYPE_IO)
+                       continue;
+               ksp[numtape++] = ksp_chain;
+       }
+
+       return (0);
+} /* int tape_init */
+
+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;
+
+       vl.values = values;
+       vl.values_len = 2;
+       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+       sstrncpy (vl.plugin, "tape", sizeof (vl.plugin));
+       sstrncpy (vl.plugin_instance, plugin_instance,
+                       sizeof (vl.plugin_instance));
+       sstrncpy (vl.type, type, sizeof (vl.type));
+
+       plugin_dispatch_values (&vl);
+} /* void tape_submit */
+
+static int tape_read (void)
+{
+
+#if HAVE_KSTAT_IO_T_WRITES && HAVE_KSTAT_IO_T_NWRITES && HAVE_KSTAT_IO_T_WTIME
+# define KIO_ROCTETS reads
+# define KIO_WOCTETS writes
+# define KIO_ROPS    nreads
+# define KIO_WOPS    nwrites
+# define KIO_RTIME   rtime
+# define KIO_WTIME   wtime
+#elif HAVE_KSTAT_IO_T_NWRITTEN && HAVE_KSTAT_IO_T_WRITES && HAVE_KSTAT_IO_T_WTIME
+# define KIO_ROCTETS nread
+# define KIO_WOCTETS nwritten
+# define KIO_ROPS    reads
+# define KIO_WOPS    writes
+# define KIO_RTIME   rtime
+# define KIO_WTIME   wtime
+#else
+# error "kstat_io_t does not have the required members"
+#endif
+       static kstat_io_t kio;
+       int i;
+
+       if (kc == NULL)
+               return (-1);
+
+       if (numtape <= 0)
+               return (-1);
+
+       for (i = 0; i < numtape; i++)
+       {
+               if (kstat_read (kc, ksp[i], &kio) == -1)
+                       continue;
+
+               if (strncmp (ksp[i]->ks_class, "tape", 4) == 0)
+               {
+                       tape_submit (ksp[i]->ks_name, "tape_octets",
+                                       kio.KIO_ROCTETS, kio.KIO_WOCTETS);
+                       tape_submit (ksp[i]->ks_name, "tape_ops",
+                                       kio.KIO_ROPS, kio.KIO_WOPS);
+                       /* FIXME: Convert this to microseconds if necessary */
+                       tape_submit (ksp[i]->ks_name, "tape_time",
+                                       kio.KIO_RTIME, kio.KIO_WTIME);
+               }
+       }
+
+       return (0);
+}
+
+void module_register (void)
+{
+       plugin_register_init ("tape", tape_init);
+       plugin_register_read ("tape", tape_read);
+}
diff --git a/src/target_notification.c b/src/target_notification.c
new file mode 100644 (file)
index 0000000..cb68048
--- /dev/null
@@ -0,0 +1,285 @@
+/**
+ * collectd - src/target_notification.c
+ * Copyright (C) 2008  Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "filter_chain.h"
+#include "utils_cache.h"
+#include "utils_subst.h"
+
+struct tn_data_s
+{
+  int severity;
+  char *message;
+};
+typedef struct tn_data_s tn_data_t;
+
+static int tn_config_add_severity (tn_data_t *data, /* {{{ */
+    const oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    ERROR ("Target `notification': The `%s' option requires exactly one string "
+        "argument.", ci->key);
+    return (-1);
+  }
+
+  if ((strcasecmp ("FAILURE", ci->values[0].value.string) == 0)
+      || (strcasecmp ("CRITICAL", ci->values[0].value.string) == 0))
+    data->severity = NOTIF_FAILURE;
+  else if ((strcasecmp ("WARNING", ci->values[0].value.string) == 0)
+      || (strcasecmp ("WARN", ci->values[0].value.string) == 0))
+    data->severity = NOTIF_WARNING;
+  else if (strcasecmp ("OKAY", ci->values[0].value.string) == 0)
+    data->severity = NOTIF_OKAY;
+  else
+  {
+    WARNING ("Target `notification': Unknown severity `%s'. "
+        "Will use `FAILURE' instead.",
+        ci->values[0].value.string);
+    data->severity = NOTIF_FAILURE;
+  }
+
+  return (0);
+} /* }}} int tn_config_add_severity */
+
+static int tn_config_add_string (char **dest, /* {{{ */
+    const oconfig_item_t *ci)
+{
+  char *temp;
+
+  if (dest == NULL)
+    return (-EINVAL);
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    ERROR ("Target `notification': The `%s' option requires exactly one string "
+        "argument.", ci->key);
+    return (-1);
+  }
+
+  if (ci->values[0].value.string[0] == 0)
+  {
+    ERROR ("Target `notification': The `%s' option does not accept empty strings.",
+        ci->key);
+    return (-1);
+  }
+
+  temp = sstrdup (ci->values[0].value.string);
+  if (temp == NULL)
+  {
+    ERROR ("tn_config_add_string: sstrdup failed.");
+    return (-1);
+  }
+
+  free (*dest);
+  *dest = temp;
+
+  return (0);
+} /* }}} int tn_config_add_string */
+
+static int tn_destroy (void **user_data) /* {{{ */
+{
+  tn_data_t *data;
+
+  if (user_data == NULL)
+    return (-EINVAL);
+
+  data = *user_data;
+  if (data == NULL)
+    return (0);
+
+  sfree (data->message);
+  sfree (data);
+
+  return (0);
+} /* }}} int tn_destroy */
+
+static int tn_create (const oconfig_item_t *ci, void **user_data) /* {{{ */
+{
+  tn_data_t *data;
+  int status;
+  int i;
+
+  data = (tn_data_t *) malloc (sizeof (*data));
+  if (data == NULL)
+  {
+    ERROR ("tn_create: malloc failed.");
+    return (-ENOMEM);
+  }
+  memset (data, 0, sizeof (*data));
+
+  data->message = NULL;
+  data->severity = 0;
+
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Message", child->key) == 0)
+      status = tn_config_add_string (&data->message, child);
+    else if (strcasecmp ("Severity", child->key) == 0)
+      status = tn_config_add_severity (data, child);
+    else
+    {
+      ERROR ("Target `notification': The `%s' configuration option is not understood "
+          "and will be ignored.", child->key);
+      status = 0;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  /* Additional sanity-checking */
+  while (status == 0)
+  {
+    if ((data->severity != NOTIF_FAILURE)
+        && (data->severity != NOTIF_WARNING)
+        && (data->severity != NOTIF_OKAY))
+    {
+      DEBUG ("Target `notification': Setting "
+          "the default severity `WARNING'.");
+      data->severity = NOTIF_WARNING;
+    }
+
+    if (data->message == NULL)
+    {
+      ERROR ("Target `notification': No `Message' option has been specified. "
+          "Without it, the `Notification' target is useless.");
+      status = -1;
+    }
+
+    break;
+  }
+
+  if (status != 0)
+  {
+    tn_destroy ((void *) data);
+    return (status);
+  }
+
+  *user_data = data;
+  return (0);
+} /* }}} int tn_create */
+
+static int tn_invoke (const data_set_t *ds, value_list_t *vl, /* {{{ */
+    notification_meta_t __attribute__((unused)) **meta, void **user_data)
+{
+  tn_data_t *data;
+  notification_t n;
+  char temp[NOTIF_MAX_MSG_LEN];
+
+  gauge_t *rates;
+  int rates_failed;
+
+  int i;
+
+  if ((ds == NULL) || (vl == NULL) || (user_data == NULL))
+    return (-EINVAL);
+
+  data = *user_data;
+  if (data == NULL)
+  {
+    ERROR ("Target `notification': Invoke: `data' is NULL.");
+    return (-EINVAL);
+  }
+
+  /* Initialize the structure. */
+  memset (&n, 0, sizeof (n));
+  n.severity = data->severity;
+  n.time = cdtime ();
+  sstrncpy (n.message, data->message, sizeof (n.message));
+  sstrncpy (n.host, vl->host, sizeof (n.host));
+  sstrncpy (n.plugin, vl->plugin, sizeof (n.plugin));
+  sstrncpy (n.plugin_instance, vl->plugin_instance,
+      sizeof (n.plugin_instance));
+  sstrncpy (n.type, vl->type, sizeof (n.type));
+  sstrncpy (n.type_instance, vl->type_instance,
+      sizeof (n.type_instance));
+  n.meta = NULL;
+
+#define REPLACE_FIELD(t,v) \
+  if (subst_string (temp, sizeof (temp), n.message, t, v) != NULL) \
+    sstrncpy (n.message, temp, sizeof (n.message));
+  REPLACE_FIELD ("%{host}", n.host);
+  REPLACE_FIELD ("%{plugin}", n.plugin);
+  REPLACE_FIELD ("%{plugin_instance}", n.plugin_instance);
+  REPLACE_FIELD ("%{type}", n.type);
+  REPLACE_FIELD ("%{type_instance}", n.type_instance);
+
+  rates_failed = 0;
+  rates = NULL;
+  for (i = 0; i < ds->ds_num; i++)
+  {
+    char template[DATA_MAX_NAME_LEN];
+    char value_str[DATA_MAX_NAME_LEN];
+
+    ssnprintf (template, sizeof (template), "%%{ds:%s}", ds->ds[i].name);
+
+    if (ds->ds[i].type != DS_TYPE_GAUGE)
+    {
+      if ((rates == NULL) && (rates_failed == 0))
+      {
+        rates = uc_get_rate (ds, vl);
+        if (rates == NULL)
+          rates_failed = 1;
+      }
+    }
+
+    /* If this is a gauge value, use the current value. */
+    if (ds->ds[i].type == DS_TYPE_GAUGE)
+      ssnprintf (value_str, sizeof (value_str),
+          "%g", (double) vl->values[i].gauge);
+    /* If it's a counter, try to use the current rate. This may fail, if the
+     * value has been renamed. */
+    else if (rates != NULL)
+      ssnprintf (value_str, sizeof (value_str),
+          "%g", (double) rates[i]);
+    /* Since we don't know any better, use the string `unknown'. */
+    else
+      sstrncpy (value_str, "unknown", sizeof (value_str));
+
+    REPLACE_FIELD (template, value_str);
+  }
+  sfree (rates);
+
+  plugin_dispatch_notification (&n);
+
+  return (FC_TARGET_CONTINUE);
+} /* }}} int tn_invoke */
+
+void module_register (void)
+{
+       target_proc_t tproc;
+
+       memset (&tproc, 0, sizeof (tproc));
+       tproc.create  = tn_create;
+       tproc.destroy = tn_destroy;
+       tproc.invoke  = tn_invoke;
+       fc_register_target ("notification", tproc);
+} /* module_register */
+
+/* vim: set sw=2 sts=2 tw=78 et fdm=marker : */
+
diff --git a/src/target_replace.c b/src/target_replace.c
new file mode 100644 (file)
index 0000000..9a9affb
--- /dev/null
@@ -0,0 +1,356 @@
+/**
+ * collectd - src/target_replace.c
+ * Copyright (C) 2008  Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "filter_chain.h"
+#include "utils_subst.h"
+
+#include <regex.h>
+
+struct tr_action_s;
+typedef struct tr_action_s tr_action_t;
+struct tr_action_s
+{
+  regex_t re;
+  char *replacement;
+  int may_be_empty;
+
+  tr_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;
+};
+typedef struct tr_data_s tr_data_t;
+
+static char *tr_strdup (const char *orig) /* {{{ */
+{
+  size_t sz;
+  char *dest;
+
+  if (orig == NULL)
+    return (NULL);
+
+  sz = strlen (orig) + 1;
+  dest = (char *) malloc (sz);
+  if (dest == NULL)
+    return (NULL);
+
+  memcpy (dest, orig, sz);
+
+  return (dest);
+} /* }}} char *tr_strdup */
+
+static void tr_action_destroy (tr_action_t *act) /* {{{ */
+{
+  if (act == NULL)
+    return;
+
+  regfree (&act->re);
+  sfree (act->replacement);
+
+  if (act->next != NULL)
+    tr_action_destroy (act->next);
+
+  sfree (act);
+} /* }}} void tr_action_destroy */
+
+static int tr_config_add_action (tr_action_t **dest, /* {{{ */
+    const oconfig_item_t *ci, int may_be_empty)
+{
+  tr_action_t *act;
+  int status;
+
+  if (dest == NULL)
+    return (-EINVAL);
+
+  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);
+  }
+
+  act = (tr_action_t *) malloc (sizeof (*act));
+  if (act == NULL)
+  {
+    ERROR ("tr_config_add_action: malloc failed.");
+    return (-ENOMEM);
+  }
+  memset (act, 0, sizeof (*act));
+
+  act->replacement = NULL;
+  act->may_be_empty = may_be_empty;
+
+  status = regcomp (&act->re, ci->values[0].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[0].value.string, errbuf);
+    sfree (act);
+    return (-EINVAL);
+  }
+
+  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);
+    return (-ENOMEM);
+  }
+
+  /* Insert action at end of list. */
+  if (*dest == NULL)
+    *dest = act;
+  else
+  {
+    tr_action_t *prev;
+
+    prev = *dest;
+    while (prev->next != NULL)
+      prev = prev->next;
+
+    prev->next = act;
+  }
+
+  return (0);
+} /* }}} int tr_config_add_action */
+
+static int tr_action_invoke (tr_action_t *act_head, /* {{{ */
+    char *buffer_in, size_t buffer_in_size, int may_be_empty)
+{
+  tr_action_t *act;
+  int status;
+  char buffer[DATA_MAX_NAME_LEN];
+  regmatch_t matches[8];
+
+  if (act_head == NULL)
+    return (-EINVAL);
+
+  sstrncpy (buffer, buffer_in, sizeof (buffer));
+  memset (matches, 0, sizeof (matches));
+
+  DEBUG ("target_replace plugin: tr_action_invoke: <- buffer = %s;", buffer);
+
+  for (act = act_head; act != NULL; act = act->next)
+  {
+    char temp[DATA_MAX_NAME_LEN];
+    char *subst_status;
+
+    status = regexec (&act->re, buffer,
+        STATIC_ARRAY_SIZE (matches), matches,
+        /* flags = */ 0);
+    if (status == REG_NOMATCH)
+      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);
+      continue;
+    }
+
+    subst_status = subst (temp, sizeof (temp), buffer,
+        matches[0].rm_so, matches[0].rm_eo, act->replacement);
+    if (subst_status == NULL)
+    {
+      ERROR ("Target `replace': subst (buffer = %s, start = %zu, end = %zu, "
+          "replacement = %s) failed.",
+          buffer, (size_t) matches[0].rm_so, (size_t) matches[0].rm_eo,
+          act->replacement);
+      continue;
+    }
+    sstrncpy (buffer, temp, sizeof (buffer));
+
+    DEBUG ("target_replace plugin: tr_action_invoke: -- buffer = %s;", buffer);
+  } /* for (act = act_head; act != NULL; act = act->next) */
+
+  if ((may_be_empty == 0) && (buffer[0] == 0))
+  {
+    WARNING ("Target `replace': Replacement resulted in an empty string, "
+        "which is not allowed for this buffer (`host' or `plugin').");
+    return (0);
+  }
+
+  DEBUG ("target_replace plugin: tr_action_invoke: -> buffer = %s;", buffer);
+  sstrncpy (buffer_in, buffer, buffer_in_size);
+
+  return (0);
+} /* }}} int tr_action_invoke */
+
+static int tr_destroy (void **user_data) /* {{{ */
+{
+  tr_data_t *data;
+
+  if (user_data == NULL)
+    return (-EINVAL);
+
+  data = *user_data;
+  if (data == NULL)
+    return (0);
+
+  tr_action_destroy (data->host);
+  tr_action_destroy (data->plugin);
+  tr_action_destroy (data->plugin_instance);
+  /* tr_action_destroy (data->type); */
+  tr_action_destroy (data->type_instance);
+  sfree (data);
+
+  return (0);
+} /* }}} int tr_destroy */
+
+static int tr_create (const oconfig_item_t *ci, void **user_data) /* {{{ */
+{
+  tr_data_t *data;
+  int status;
+  int i;
+
+  data = (tr_data_t *) malloc (sizeof (*data));
+  if (data == NULL)
+  {
+    ERROR ("tr_create: malloc failed.");
+    return (-ENOMEM);
+  }
+  memset (data, 0, sizeof (*data));
+
+  data->host = NULL;
+  data->plugin = NULL;
+  data->plugin_instance = NULL;
+  /* data->type = NULL; */
+  data->type_instance = NULL;
+
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if ((strcasecmp ("Host", child->key) == 0)
+        || (strcasecmp ("Hostname", child->key) == 0))
+      status = tr_config_add_action (&data->host, child,
+          /* may be empty = */ 0);
+    else if (strcasecmp ("Plugin", child->key) == 0)
+      status = tr_config_add_action (&data->plugin, child,
+          /* may be empty = */ 0);
+    else if (strcasecmp ("PluginInstance", child->key) == 0)
+      status = tr_config_add_action (&data->plugin_instance, child,
+          /* may be empty = */ 1);
+#if 0
+    else if (strcasecmp ("Type", child->key) == 0)
+      status = tr_config_add_action (&data->type, child,
+          /* may be empty = */ 0);
+#endif
+    else if (strcasecmp ("TypeInstance", child->key) == 0)
+      status = tr_config_add_action (&data->type_instance, child,
+          /* may be empty = */ 1);
+    else
+    {
+      ERROR ("Target `replace': The `%s' configuration option is not understood "
+          "and will be ignored.", child->key);
+      status = 0;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  /* Additional sanity-checking */
+  while (status == 0)
+  {
+    if ((data->host == NULL)
+        && (data->plugin == NULL)
+        && (data->plugin_instance == NULL)
+        /* && (data->type == NULL) */
+        && (data->type_instance == NULL))
+    {
+      ERROR ("Target `replace': You need to set at lease one of `Host', "
+          "`Plugin', `PluginInstance', `Type', or `TypeInstance'.");
+      status = -1;
+    }
+
+    break;
+  }
+
+  if (status != 0)
+  {
+    tr_destroy ((void *) &data);
+    return (status);
+  }
+
+  *user_data = data;
+  return (0);
+} /* }}} int tr_create */
+
+static int tr_invoke (const data_set_t *ds, value_list_t *vl, /* {{{ */
+    notification_meta_t __attribute__((unused)) **meta, void **user_data)
+{
+  tr_data_t *data;
+
+  if ((ds == NULL) || (vl == NULL) || (user_data == NULL))
+    return (-EINVAL);
+
+  data = *user_data;
+  if (data == NULL)
+  {
+    ERROR ("Target `replace': Invoke: `data' is NULL.");
+    return (-EINVAL);
+  }
+
+#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_instance, 1);
+
+  return (FC_TARGET_CONTINUE);
+} /* }}} int tr_invoke */
+
+void module_register (void)
+{
+       target_proc_t tproc;
+
+       memset (&tproc, 0, sizeof (tproc));
+       tproc.create  = tr_create;
+       tproc.destroy = tr_destroy;
+       tproc.invoke  = tr_invoke;
+       fc_register_target ("replace", tproc);
+} /* module_register */
+
+/* vim: set sw=2 sts=2 tw=78 et fdm=marker : */
+
diff --git a/src/target_scale.c b/src/target_scale.c
new file mode 100644 (file)
index 0000000..af224f1
--- /dev/null
@@ -0,0 +1,424 @@
+/**
+ * collectd - src/target_scale.c
+ * Copyright (C) 2008-2009  Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "filter_chain.h"
+
+#include "utils_cache.h"
+
+struct ts_data_s
+{
+       double factor;
+       double offset;
+};
+typedef struct ts_data_s ts_data_t;
+
+static int ts_invoke_counter (const data_set_t *ds, value_list_t *vl, /* {{{ */
+               ts_data_t *data, int dsrc_index)
+{
+       uint64_t curr_counter;
+       int status;
+       int failure;
+
+       /* Required meta data */
+       uint64_t prev_counter;
+       char key_prev_counter[128];
+       uint64_t int_counter;
+       char key_int_counter[128];
+       double int_fraction;
+       char key_int_fraction[128];
+
+       curr_counter = (uint64_t) vl->values[dsrc_index].counter;
+
+       ssnprintf (key_prev_counter, sizeof (key_prev_counter),
+                       "target_scale[%p,%i]:prev_counter",
+                       (void *) data, dsrc_index);
+       ssnprintf (key_int_counter, sizeof (key_int_counter),
+                       "target_scale[%p,%i]:int_counter",
+                       (void *) data, dsrc_index);
+       ssnprintf (key_int_fraction, sizeof (key_int_fraction),
+                       "target_scale[%p,%i]:int_fraction",
+                       (void *) data, dsrc_index);
+
+       prev_counter = curr_counter;
+       int_counter = 0;
+       int_fraction = 0.0;
+
+       /* Query the meta data */
+       failure = 0;
+
+       status = uc_meta_data_get_unsigned_int (vl, key_prev_counter,
+                       &prev_counter);
+       if (status != 0)
+               failure++;
+
+       status = uc_meta_data_get_unsigned_int (vl, key_int_counter, &int_counter);
+       if (status != 0)
+               failure++;
+
+       status = uc_meta_data_get_double (vl, key_int_fraction, &int_fraction);
+       if (status != 0)
+               failure++;
+
+       if (failure == 0)
+       {
+               uint64_t difference;
+               double rate;
+
+               /* Calcualte the rate */
+               if (prev_counter > curr_counter) /* => counter overflow */
+               {
+                       if (prev_counter <= 4294967295UL) /* 32 bit overflow */
+                               difference = (4294967295UL - prev_counter) + curr_counter;
+                       else /* 64 bit overflow */
+                               difference = (18446744073709551615ULL - prev_counter) + curr_counter;
+               }
+               else /* no overflow */
+               {
+                       difference = curr_counter - prev_counter;
+               }
+               rate = ((double) difference) / CDTIME_T_TO_DOUBLE (vl->interval);
+
+               /* Modify the rate. */
+               if (!isnan (data->factor))
+                       rate *= data->factor;
+               if (!isnan (data->offset))
+                       rate += data->offset;
+
+               /* Calculate the internal counter. */
+               int_fraction += (rate * CDTIME_T_TO_DOUBLE (vl->interval));
+               difference = (uint64_t) int_fraction;
+               int_fraction -= ((double) difference);
+               int_counter  += difference;
+
+               assert (int_fraction >= 0.0);
+               assert (int_fraction <  1.0);
+
+               DEBUG ("Target `scale': ts_invoke_counter: %"PRIu64" -> %g -> %"PRIu64
+                               "(+%g)",
+                               curr_counter, rate, int_counter, int_fraction);
+       }
+       else /* (failure != 0) */
+       {
+               int_counter = 0;
+               int_fraction = 0.0;
+       }
+
+       vl->values[dsrc_index].counter = (counter_t) int_counter;
+
+       /* Update to the new counter value */
+       uc_meta_data_add_unsigned_int (vl, key_prev_counter, curr_counter);
+       uc_meta_data_add_unsigned_int (vl, key_int_counter, int_counter);
+       uc_meta_data_add_double (vl, key_int_fraction, int_fraction);
+
+
+       return (0);
+} /* }}} int ts_invoke_counter */
+
+static int ts_invoke_gauge (const data_set_t *ds, value_list_t *vl, /* {{{ */
+               ts_data_t *data, int dsrc_index)
+{
+       if (!isnan (data->factor))
+               vl->values[dsrc_index].gauge *= data->factor;
+       if (!isnan (data->offset))
+               vl->values[dsrc_index].gauge += data->offset;
+
+       return (0);
+} /* }}} int ts_invoke_gauge */
+
+static int ts_invoke_derive (const data_set_t *ds, value_list_t *vl, /* {{{ */
+               ts_data_t *data, int dsrc_index)
+{
+       int64_t curr_derive;
+       int status;
+       int failure;
+
+       /* Required meta data */
+       int64_t prev_derive;
+       char key_prev_derive[128];
+       int64_t int_derive;
+       char key_int_derive[128];
+       double int_fraction;
+       char key_int_fraction[128];
+
+       curr_derive = (int64_t) vl->values[dsrc_index].derive;
+
+       ssnprintf (key_prev_derive, sizeof (key_prev_derive),
+                       "target_scale[%p,%i]:prev_derive",
+                       (void *) data, dsrc_index);
+       ssnprintf (key_int_derive, sizeof (key_int_derive),
+                       "target_scale[%p,%i]:int_derive",
+                       (void *) data, dsrc_index);
+       ssnprintf (key_int_fraction, sizeof (key_int_fraction),
+                       "target_scale[%p,%i]:int_fraction",
+                       (void *) data, dsrc_index);
+
+       prev_derive = curr_derive;
+       int_derive = 0;
+       int_fraction = 0.0;
+
+       /* Query the meta data */
+       failure = 0;
+
+       status = uc_meta_data_get_signed_int (vl, key_prev_derive,
+                       &prev_derive);
+       if (status != 0)
+               failure++;
+
+       status = uc_meta_data_get_signed_int (vl, key_int_derive, &int_derive);
+       if (status != 0)
+               failure++;
+
+       status = uc_meta_data_get_double (vl, key_int_fraction, &int_fraction);
+       if (status != 0)
+               failure++;
+
+       if (failure == 0)
+       {
+               int64_t difference;
+               double rate;
+
+               /* Calcualte the rate */
+               difference = curr_derive - prev_derive;
+               rate = ((double) difference) / CDTIME_T_TO_DOUBLE (vl->interval);
+
+               /* Modify the rate. */
+               if (!isnan (data->factor))
+                       rate *= data->factor;
+               if (!isnan (data->offset))
+                       rate += data->offset;
+
+               /* Calculate the internal derive. */
+               int_fraction += (rate * CDTIME_T_TO_DOUBLE (vl->interval));
+               if (int_fraction < 0.0) /* handle negative integer rounding correctly */
+                       difference = ((int64_t) int_fraction) - 1;
+               else
+                       difference = (int64_t) int_fraction;
+               int_fraction -= ((double) difference);
+               int_derive  += difference;
+
+               assert (int_fraction >= 0.0);
+               assert (int_fraction <  1.0);
+
+               DEBUG ("Target `scale': ts_invoke_derive: %"PRIu64" -> %g -> %"PRIu64
+                               "(+%g)",
+                               curr_derive, rate, int_derive, int_fraction);
+       }
+       else /* (failure != 0) */
+       {
+               int_derive = 0;
+               int_fraction = 0.0;
+       }
+
+       vl->values[dsrc_index].derive = (derive_t) int_derive;
+
+       /* Update to the new derive value */
+       uc_meta_data_add_signed_int (vl, key_prev_derive, curr_derive);
+       uc_meta_data_add_signed_int (vl, key_int_derive, int_derive);
+       uc_meta_data_add_double (vl, key_int_fraction, int_fraction);
+
+       return (0);
+} /* }}} int ts_invoke_derive */
+
+static int ts_invoke_absolute (const data_set_t *ds, value_list_t *vl, /* {{{ */
+               ts_data_t *data, int dsrc_index)
+{
+       uint64_t curr_absolute;
+       double rate;
+       int status;
+
+       /* Required meta data */
+       double int_fraction;
+       char key_int_fraction[128];
+
+       curr_absolute = (uint64_t) vl->values[dsrc_index].absolute;
+
+       ssnprintf (key_int_fraction, sizeof (key_int_fraction),
+                       "target_scale[%p,%i]:int_fraction",
+                       (void *) data, dsrc_index);
+
+       int_fraction = 0.0;
+
+       /* Query the meta data */
+       status = uc_meta_data_get_double (vl, key_int_fraction, &int_fraction);
+       if (status != 0)
+               int_fraction = 0.0;
+
+       rate = ((double) curr_absolute) / CDTIME_T_TO_DOUBLE (vl->interval);
+
+       /* Modify the rate. */
+       if (!isnan (data->factor))
+               rate *= data->factor;
+       if (!isnan (data->offset))
+               rate += data->offset;
+
+       /* Calculate the new absolute. */
+       int_fraction += (rate * CDTIME_T_TO_DOUBLE (vl->interval));
+       curr_absolute = (uint64_t) int_fraction;
+       int_fraction -= ((double) curr_absolute);
+
+       vl->values[dsrc_index].absolute = (absolute_t) curr_absolute;
+
+       /* Update to the new absolute value */
+       uc_meta_data_add_double (vl, key_int_fraction, int_fraction);
+
+       return (0);
+} /* }}} int ts_invoke_absolute */
+
+static int ts_config_set_double (double *ret, oconfig_item_t *ci) /* {{{ */
+{
+       if ((ci->values_num != 1)
+                       || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
+       {
+               WARNING ("scale target: The `%s' config option needs "
+                               "exactly one numeric argument.", ci->key);
+               return (-1);
+       }
+
+       *ret = ci->values[0].value.number;
+       DEBUG ("ts_config_set_double: *ret = %g", *ret);
+
+       return (0);
+} /* }}} int ts_config_set_double */
+
+static int ts_destroy (void **user_data) /* {{{ */
+{
+       ts_data_t **data;
+
+       if (user_data == NULL)
+               return (-EINVAL);
+
+       data = (ts_data_t **) user_data;
+
+       free (*data);
+       *data = NULL;
+
+       return (0);
+} /* }}} int ts_destroy */
+
+static int ts_create (const oconfig_item_t *ci, void **user_data) /* {{{ */
+{
+       ts_data_t *data;
+       int status;
+       int i;
+
+       data = (ts_data_t *) malloc (sizeof (*data));
+       if (data == NULL)
+       {
+               ERROR ("ts_create: malloc failed.");
+               return (-ENOMEM);
+       }
+       memset (data, 0, sizeof (*data));
+
+       data->factor = NAN;
+       data->offset = NAN;
+
+       status = 0;
+       for (i = 0; i < ci->children_num; i++)
+       {
+               oconfig_item_t *child = ci->children + i;
+
+               if (strcasecmp ("Factor", child->key) == 0)
+                               status = ts_config_set_double (&data->factor, child);
+               else if (strcasecmp ("Offset", child->key) == 0)
+                               status = ts_config_set_double (&data->offset, child);
+               else
+               {
+                       ERROR ("Target `scale': The `%s' configuration option is not understood "
+                                       "and will be ignored.", child->key);
+                       status = 0;
+               }
+
+               if (status != 0)
+                       break;
+       }
+
+       /* Additional sanity-checking */
+       while (status == 0)
+       {
+               if (isnan (data->factor) && isnan (data->offset))
+               {
+                       ERROR ("Target `scale': You need to at least set either the `Factor' "
+                                       "or `Offset' option!");
+                       status = -1;
+               }
+
+               break;
+       }
+
+       if (status != 0)
+       {
+               ts_destroy ((void *) &data);
+               return (status);
+       }
+
+       *user_data = data;
+       return (0);
+} /* }}} int ts_create */
+
+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;
+       int i;
+
+       if ((ds == NULL) || (vl == NULL) || (user_data == NULL))
+               return (-EINVAL);
+
+       data = *user_data;
+       if (data == NULL)
+       {
+               ERROR ("Target `scale': Invoke: `data' is NULL.");
+               return (-EINVAL);
+       }
+
+       for (i = 0; i < ds->ds_num; i++)
+       {
+               if (ds->ds[i].type == DS_TYPE_COUNTER)
+                       ts_invoke_counter (ds, vl, data, i);
+               else if (ds->ds[i].type == DS_TYPE_GAUGE)
+                       ts_invoke_gauge (ds, vl, data, i);
+               else if (ds->ds[i].type == DS_TYPE_DERIVE)
+                       ts_invoke_derive (ds, vl, data, i);
+               else if (ds->ds[i].type == DS_TYPE_ABSOLUTE)
+                       ts_invoke_absolute (ds, vl, data, i);
+               else
+                       ERROR ("Target `scale': Ignoring unknown data source type %i",
+                                       ds->ds[i].type);
+       }
+
+       return (FC_TARGET_CONTINUE);
+} /* }}} int ts_invoke */
+
+void module_register (void)
+{
+       target_proc_t tproc;
+
+       memset (&tproc, 0, sizeof (tproc));
+       tproc.create  = ts_create;
+       tproc.destroy = ts_destroy;
+       tproc.invoke  = ts_invoke;
+       fc_register_target ("scale", tproc);
+} /* module_register */
+
+/* vim: set sw=2 ts=2 tw=78 fdm=marker : */
+
diff --git a/src/target_set.c b/src/target_set.c
new file mode 100644 (file)
index 0000000..8a014c3
--- /dev/null
@@ -0,0 +1,229 @@
+/**
+ * collectd - src/target_set.c
+ * Copyright (C) 2008  Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "filter_chain.h"
+
+struct ts_data_s
+{
+  char *host;
+  char *plugin;
+  char *plugin_instance;
+  /* char *type; */
+  char *type_instance;
+};
+typedef struct ts_data_s ts_data_t;
+
+static char *ts_strdup (const char *orig) /* {{{ */
+{
+  size_t sz;
+  char *dest;
+
+  if (orig == NULL)
+    return (NULL);
+
+  sz = strlen (orig) + 1;
+  dest = (char *) malloc (sz);
+  if (dest == NULL)
+    return (NULL);
+
+  memcpy (dest, orig, sz);
+
+  return (dest);
+} /* }}} char *ts_strdup */
+
+static int ts_config_add_string (char **dest, /* {{{ */
+    const oconfig_item_t *ci, int may_be_empty)
+{
+  char *temp;
+
+  if (dest == NULL)
+    return (-EINVAL);
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    ERROR ("Target `set': The `%s' option requires exactly one string "
+        "argument.", ci->key);
+    return (-1);
+  }
+
+  if ((!may_be_empty) && (ci->values[0].value.string[0] == 0))
+  {
+    ERROR ("Target `set': The `%s' option does not accept empty strings.",
+        ci->key);
+    return (-1);
+  }
+
+  temp = ts_strdup (ci->values[0].value.string);
+  if (temp == NULL)
+  {
+    ERROR ("ts_config_add_string: ts_strdup failed.");
+    return (-1);
+  }
+
+  free (*dest);
+  *dest = temp;
+
+  return (0);
+} /* }}} int ts_config_add_string */
+
+static int ts_destroy (void **user_data) /* {{{ */
+{
+  ts_data_t *data;
+
+  if (user_data == NULL)
+    return (-EINVAL);
+
+  data = *user_data;
+  if (data == NULL)
+    return (0);
+
+  free (data->host);
+  free (data->plugin);
+  free (data->plugin_instance);
+  /* free (data->type); */
+  free (data->type_instance);
+  free (data);
+
+  return (0);
+} /* }}} int ts_destroy */
+
+static int ts_create (const oconfig_item_t *ci, void **user_data) /* {{{ */
+{
+  ts_data_t *data;
+  int status;
+  int i;
+
+  data = (ts_data_t *) malloc (sizeof (*data));
+  if (data == NULL)
+  {
+    ERROR ("ts_create: malloc failed.");
+    return (-ENOMEM);
+  }
+  memset (data, 0, sizeof (*data));
+
+  data->host = NULL;
+  data->plugin = NULL;
+  data->plugin_instance = NULL;
+  /* data->type = NULL; */
+  data->type_instance = NULL;
+
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if ((strcasecmp ("Host", child->key) == 0)
+        || (strcasecmp ("Hostname", child->key) == 0))
+      status = ts_config_add_string (&data->host, child,
+          /* may be empty = */ 0);
+    else if (strcasecmp ("Plugin", child->key) == 0)
+      status = ts_config_add_string (&data->plugin, child,
+          /* may be empty = */ 0);
+    else if (strcasecmp ("PluginInstance", child->key) == 0)
+      status = ts_config_add_string (&data->plugin_instance, child,
+          /* may be empty = */ 1);
+#if 0
+    else if (strcasecmp ("Type", child->key) == 0)
+      status = ts_config_add_string (&data->type, child,
+          /* may be empty = */ 0);
+#endif
+    else if (strcasecmp ("TypeInstance", child->key) == 0)
+      status = ts_config_add_string (&data->type_instance, child,
+          /* may be empty = */ 1);
+    else
+    {
+      ERROR ("Target `set': The `%s' configuration option is not understood "
+          "and will be ignored.", child->key);
+      status = 0;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  /* Additional sanity-checking */
+  while (status == 0)
+  {
+    if ((data->host == NULL)
+        && (data->plugin == NULL)
+        && (data->plugin_instance == NULL)
+        /* && (data->type == NULL) */
+        && (data->type_instance == NULL))
+    {
+      ERROR ("Target `set': You need to set at lease one of `Host', "
+          "`Plugin', `PluginInstance', `Type', or `TypeInstance'.");
+      status = -1;
+    }
+
+    break;
+  }
+
+  if (status != 0)
+  {
+    ts_destroy ((void *) &data);
+    return (status);
+  }
+
+  *user_data = data;
+  return (0);
+} /* }}} int ts_create */
+
+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;
+
+  if ((ds == NULL) || (vl == NULL) || (user_data == NULL))
+    return (-EINVAL);
+
+  data = *user_data;
+  if (data == NULL)
+  {
+    ERROR ("Target `set': Invoke: `data' is NULL.");
+    return (-EINVAL);
+  }
+
+#define SET_FIELD(f) if (data->f != NULL) { sstrncpy (vl->f, data->f, sizeof (vl->f)); }
+  SET_FIELD (host);
+  SET_FIELD (plugin);
+  SET_FIELD (plugin_instance);
+  /* SET_FIELD (type); */
+  SET_FIELD (type_instance);
+
+  return (FC_TARGET_CONTINUE);
+} /* }}} int ts_invoke */
+
+void module_register (void)
+{
+       target_proc_t tproc;
+
+       memset (&tproc, 0, sizeof (tproc));
+       tproc.create  = ts_create;
+       tproc.destroy = ts_destroy;
+       tproc.invoke  = ts_invoke;
+       fc_register_target ("set", tproc);
+} /* module_register */
+
+/* vim: set sw=2 sts=2 tw=78 et fdm=marker : */
+
diff --git a/src/target_v5upgrade.c b/src/target_v5upgrade.c
new file mode 100644 (file)
index 0000000..25f4637
--- /dev/null
@@ -0,0 +1,472 @@
+/**
+ * collectd - src/target_set.c
+ * Copyright (C) 2008-2010  Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; only version 2.1 of the License is
+ * applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors:
+ *   Florian Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "plugin.h"
+#include "common.h"
+#include "filter_chain.h"
+
+static void v5_swap_instances (value_list_t *vl) /* {{{ */
+{
+  char tmp[DATA_MAX_NAME_LEN];
+
+  assert (sizeof (tmp) == sizeof (vl->plugin_instance));
+  assert (sizeof (tmp) == sizeof (vl->type_instance));
+
+  memcpy (tmp, vl->plugin_instance, sizeof (tmp));
+  memcpy (vl->plugin_instance, vl->type_instance, sizeof (tmp));
+  memcpy (vl->type_instance, tmp, sizeof (tmp));
+} /* }}} void v5_swap_instances */
+
+/*
+ * Df type
+ *
+ * By default, the "df" plugin of version 4.* uses the "df" type and puts the
+ * mount point in the type instance. Detect this behavior and convert the type
+ * to "df_complex". This can be selected in versions 4.9 and 4.10 by setting
+ * the "ReportReserved" option of the "df" plugin.
+ */
+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))
+    return (FC_TARGET_CONTINUE);
+
+  /* Copy everything: Time, interval, host, ... */
+  memcpy (&new_vl, vl, sizeof (new_vl));
+
+  /* Reset data we can't simply copy */
+  new_vl.values = &new_value;
+  new_vl.values_len = 1;
+  new_vl.meta = NULL;
+
+  /* Move the mount point name to the plugin instance */
+  if (new_vl.plugin_instance[0] == 0)
+    v5_swap_instances (&new_vl);
+
+  /* Change the type to "df_complex" */
+  sstrncpy (new_vl.type, "df_complex", sizeof (new_vl.type));
+
+  /* Dispatch two new value lists instead of this one */
+  new_vl.values[0].gauge = vl->values[0].gauge;
+  sstrncpy (new_vl.type_instance, "used", sizeof (new_vl.type_instance));
+  plugin_dispatch_values (&new_vl);
+
+  new_vl.values[0].gauge = vl->values[1].gauge;
+  sstrncpy (new_vl.type_instance, "free", sizeof (new_vl.type_instance));
+  plugin_dispatch_values (&new_vl);
+
+  /* Abort processing */
+  return (FC_TARGET_STOP);
+} /* }}} int v5_df */
+
+/*
+ * Interface plugin
+ *
+ * 4.* stores the interface in the type instance and leaves the plugin
+ * instance empty. If this is the case, put the interface name into the plugin
+ * instance and clear the type instance.
+ */
+static int v5_interface (const data_set_t *ds, value_list_t *vl) /* {{{ */
+{
+  if ((vl->plugin_instance[0] != 0) || (vl->type_instance[0] == 0))
+    return (FC_TARGET_CONTINUE);
+
+  v5_swap_instances (vl);
+  return (FC_TARGET_CONTINUE);
+} /* }}} int v5_interface */
+
+/*
+ * MySQL query cache
+ *
+ * 4.* uses the "mysql_qcache" type which mixes different types of
+ * information. In 5.* this has been broken up.
+ */
+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);
+
+  /* Copy everything: Time, interval, host, ... */
+  memcpy (&new_vl, vl, sizeof (new_vl));
+
+  /* Reset data we can't simply copy */
+  new_vl.values = &new_value;
+  new_vl.values_len = 1;
+  new_vl.meta = NULL;
+
+  /* Change the type to "cache_result" */
+  sstrncpy (new_vl.type, "cache_result", sizeof (new_vl.type));
+
+  /* Dispatch new value lists instead of this one */
+  new_vl.values[0].derive = (derive_t) vl->values[0].counter;
+  sstrncpy (new_vl.type_instance, "qcache-hits",
+      sizeof (new_vl.type_instance));
+  plugin_dispatch_values (&new_vl);
+
+  new_vl.values[0].derive = (derive_t) vl->values[1].counter;
+  sstrncpy (new_vl.type_instance, "qcache-inserts",
+      sizeof (new_vl.type_instance));
+  plugin_dispatch_values (&new_vl);
+
+  new_vl.values[0].derive = (derive_t) vl->values[2].counter;
+  sstrncpy (new_vl.type_instance, "qcache-not_cached",
+      sizeof (new_vl.type_instance));
+  plugin_dispatch_values (&new_vl);
+
+  new_vl.values[0].derive = (derive_t) vl->values[3].counter;
+  sstrncpy (new_vl.type_instance, "qcache-prunes",
+      sizeof (new_vl.type_instance));
+  plugin_dispatch_values (&new_vl);
+
+  /* The last data source is a gauge value, so we have to use a different type
+   * here. */
+  new_vl.values[0].gauge = vl->values[4].gauge;
+  sstrncpy (new_vl.type, "cache_size", sizeof (new_vl.type));
+  sstrncpy (new_vl.type_instance, "qcache",
+      sizeof (new_vl.type_instance));
+  plugin_dispatch_values (&new_vl);
+
+  /* Abort processing */
+  return (FC_TARGET_STOP);
+} /* }}} int v5_mysql_qcache */
+
+/*
+ * MySQL thread count
+ *
+ * 4.* uses the "mysql_threads" type which mixes different types of
+ * information. In 5.* this has been broken up.
+ */
+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);
+
+  /* Copy everything: Time, interval, host, ... */
+  memcpy (&new_vl, vl, sizeof (new_vl));
+
+  /* Reset data we can't simply copy */
+  new_vl.values = &new_value;
+  new_vl.values_len = 1;
+  new_vl.meta = NULL;
+
+  /* Change the type to "threads" */
+  sstrncpy (new_vl.type, "threads", sizeof (new_vl.type));
+
+  /* Dispatch new value lists instead of this one */
+  new_vl.values[0].gauge = vl->values[0].gauge;
+  sstrncpy (new_vl.type_instance, "running",
+      sizeof (new_vl.type_instance));
+  plugin_dispatch_values (&new_vl);
+
+  new_vl.values[0].gauge = vl->values[1].gauge;
+  sstrncpy (new_vl.type_instance, "connected",
+      sizeof (new_vl.type_instance));
+  plugin_dispatch_values (&new_vl);
+
+  new_vl.values[0].gauge = vl->values[2].gauge;
+  sstrncpy (new_vl.type_instance, "cached",
+      sizeof (new_vl.type_instance));
+  plugin_dispatch_values (&new_vl);
+
+  /* The last data source is a counter value, so we have to use a different
+   * type here. */
+  new_vl.values[0].derive = (derive_t) vl->values[3].counter;
+  sstrncpy (new_vl.type, "total_threads", sizeof (new_vl.type));
+  sstrncpy (new_vl.type_instance, "created",
+      sizeof (new_vl.type_instance));
+  plugin_dispatch_values (&new_vl);
+
+  /* Abort processing */
+  return (FC_TARGET_STOP);
+} /* }}} int v5_mysql_threads */
+
+/*
+ * ZFS ARC hit and miss counters
+ *
+ * 4.* uses the flawed "arc_counts" type. In 5.* this has been replaced by the
+ * more generic "cache_result" type.
+ */
+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)
+    return (FC_TARGET_STOP);
+
+  if (strcmp ("hits", vl->type_instance) == 0)
+    is_hits = 1;
+  else if (strcmp ("misses", vl->type_instance) == 0)
+    is_hits = 0;
+  else
+    return (FC_TARGET_STOP);
+
+  /* Copy everything: Time, interval, host, ... */
+  memcpy (&new_vl, vl, sizeof (new_vl));
+
+  /* Reset data we can't simply copy */
+  new_vl.values = &new_value;
+  new_vl.values_len = 1;
+  new_vl.meta = NULL;
+
+  /* Change the type to "cache_result" */
+  sstrncpy (new_vl.type, "cache_result", sizeof (new_vl.type));
+
+  /* Dispatch new value lists instead of this one */
+  new_vl.values[0].derive = (derive_t) vl->values[0].counter;
+  ssnprintf (new_vl.type_instance, sizeof (new_vl.type_instance),
+      "demand_data-%s",
+      is_hits ? "hit" : "miss");
+  plugin_dispatch_values (&new_vl);
+
+  new_vl.values[0].derive = (derive_t) vl->values[1].counter;
+  ssnprintf (new_vl.type_instance, sizeof (new_vl.type_instance),
+      "demand_metadata-%s",
+      is_hits ? "hit" : "miss");
+  plugin_dispatch_values (&new_vl);
+
+  new_vl.values[0].derive = (derive_t) vl->values[2].counter;
+  ssnprintf (new_vl.type_instance, sizeof (new_vl.type_instance),
+      "prefetch_data-%s",
+      is_hits ? "hit" : "miss");
+  plugin_dispatch_values (&new_vl);
+
+  new_vl.values[0].derive = (derive_t) vl->values[3].counter;
+  ssnprintf (new_vl.type_instance, sizeof (new_vl.type_instance),
+      "prefetch_metadata-%s",
+      is_hits ? "hit" : "miss");
+  plugin_dispatch_values (&new_vl);
+
+  /* Abort processing */
+  return (FC_TARGET_STOP);
+} /* }}} int v5_zfs_arc_counts */
+
+/*
+ * ZFS ARC L2 bytes
+ *
+ * "arc_l2_bytes" -> "io_octets-L2".
+ */
+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);
+
+  /* Copy everything: Time, interval, host, ... */
+  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" */
+  sstrncpy (new_vl.type, "io_octets", sizeof (new_vl.type));
+  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;
+
+  /* Dispatch new value lists instead of this one */
+  plugin_dispatch_values (&new_vl);
+
+  /* Abort processing */
+  return (FC_TARGET_STOP);
+} /* }}} int v5_zfs_arc_l2_bytes */
+
+/*
+ * ZFS ARC L2 cache size
+ *
+ * 4.* uses a separate type for this. 5.* uses the generic "cache_size" type
+ * instead.
+ */
+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);
+
+  /* Copy everything: Time, interval, host, ... */
+  memcpy (&new_vl, vl, sizeof (new_vl));
+
+  /* Reset data we can't simply copy */
+  new_vl.values = &new_value;
+  new_vl.values_len = 1;
+  new_vl.meta = NULL;
+
+  new_vl.values[0].gauge = (gauge_t) vl->values[0].gauge;
+
+  /* Change the type to "cache_size" */
+  sstrncpy (new_vl.type, "cache_size", sizeof (new_vl.type));
+
+  /* Adapt the type instance */
+  sstrncpy (new_vl.type_instance, "L2", sizeof (new_vl.type_instance));
+
+  /* Dispatch new value lists instead of this one */
+  plugin_dispatch_values (&new_vl);
+
+  /* Abort processing */
+  return (FC_TARGET_STOP);
+} /* }}} int v5_zfs_arc_l2_size */
+
+/*
+ * ZFS ARC ratio
+ *
+ * "arc_ratio-L1" -> "cache_ratio-arc"
+ * "arc_ratio-L2" -> "cache_ratio-L2"
+ */
+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);
+
+  /* Copy everything: Time, interval, host, ... */
+  memcpy (&new_vl, vl, sizeof (new_vl));
+
+  /* Reset data we can't simply copy */
+  new_vl.values = &new_value;
+  new_vl.values_len = 1;
+  new_vl.meta = NULL;
+
+  new_vl.values[0].gauge = (gauge_t) vl->values[0].gauge;
+
+  /* Change the type to "cache_ratio" */
+  sstrncpy (new_vl.type, "cache_ratio", sizeof (new_vl.type));
+
+  /* Adapt the type instance */
+  if (strcmp ("L1", vl->type_instance) == 0)
+    sstrncpy (new_vl.type_instance, "arc", sizeof (new_vl.type_instance));
+
+  /* Dispatch new value lists instead of this one */
+  plugin_dispatch_values (&new_vl);
+
+  /* Abort processing */
+  return (FC_TARGET_STOP);
+} /* }}} int v5_zfs_arc_ratio */
+
+/*
+ * ZFS ARC size
+ *
+ * 4.* uses the "arc_size" type with four data sources. In 5.* this has been
+ * replaces with the "cache_size" type and static data has been removed.
+ */
+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);
+
+  /* Copy everything: Time, interval, host, ... */
+  memcpy (&new_vl, vl, sizeof (new_vl));
+
+  /* Reset data we can't simply copy */
+  new_vl.values = &new_value;
+  new_vl.values_len = 1;
+  new_vl.meta = NULL;
+
+  /* Change the type to "cache_size" */
+  sstrncpy (new_vl.type, "cache_size", sizeof (new_vl.type));
+
+  /* Dispatch new value lists instead of this one */
+  new_vl.values[0].derive = (derive_t) vl->values[0].counter;
+  sstrncpy (new_vl.type_instance, "arc", sizeof (new_vl.type_instance));
+  plugin_dispatch_values (&new_vl);
+
+  /* Abort processing */
+  return (FC_TARGET_STOP);
+} /* }}} int v5_zfs_arc_size */
+
+static int v5_destroy (void **user_data) /* {{{ */
+{
+  return (0);
+} /* }}} int v5_destroy */
+
+static int v5_create (const oconfig_item_t *ci, void **user_data) /* {{{ */
+{
+  *user_data = NULL;
+  return (0);
+} /* }}} int v5_create */
+
+static int v5_invoke (const data_set_t *ds, value_list_t *vl, /* {{{ */
+    notification_meta_t __attribute__((unused)) **meta,
+    void __attribute__((unused)) **user_data)
+{
+  if ((ds == NULL) || (vl == NULL) || (user_data == NULL))
+    return (-EINVAL);
+
+  if (strcmp ("df", vl->type) == 0)
+    return (v5_df (ds, vl));
+  else if (strcmp ("interface", vl->plugin) == 0)
+    return (v5_interface (ds, vl));
+  else if (strcmp ("mysql_qcache", vl->type) == 0)
+    return (v5_mysql_qcache (ds, vl));
+  else if (strcmp ("mysql_threads", vl->type) == 0)
+    return (v5_mysql_threads (ds, vl));
+  else if (strcmp ("arc_counts", vl->type) == 0)
+    return (v5_zfs_arc_counts (ds, vl));
+  else if (strcmp ("arc_l2_bytes", vl->type) == 0)
+    return (v5_zfs_arc_l2_bytes (ds, vl));
+  else if (strcmp ("arc_l2_size", vl->type) == 0)
+    return (v5_zfs_arc_l2_size (ds, vl));
+  else if (strcmp ("arc_ratio", vl->type) == 0)
+    return (v5_zfs_arc_ratio (ds, vl));
+  else if (strcmp ("arc_size", vl->type) == 0)
+    return (v5_zfs_arc_size (ds, vl));
+
+  return (FC_TARGET_CONTINUE);
+} /* }}} int v5_invoke */
+
+void module_register (void)
+{
+       target_proc_t tproc;
+
+       memset (&tproc, 0, sizeof (tproc));
+       tproc.create  = v5_create;
+       tproc.destroy = v5_destroy;
+       tproc.invoke  = v5_invoke;
+       fc_register_target ("v5upgrade", tproc);
+} /* module_register */
+
+/* vim: set sw=2 sts=2 tw=78 et fdm=marker : */
+
diff --git a/src/tcpconns.c b/src/tcpconns.c
new file mode 100644 (file)
index 0000000..6a7e32d
--- /dev/null
@@ -0,0 +1,836 @@
+/**
+ * collectd - src/tcpconns.c
+ * Copyright (C) 2007,2008  Florian octo Forster
+ * Copyright (C) 2008       Michael Stapelberg
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Florian octo Forster <octo at verplant.org>
+ *   Michael Stapelberg <michael+git at stapelberg.de>
+ **/
+
+/**
+ * Code within `HAVE_LIBKVM_NLIST' blocks is provided under the following
+ * license:
+ *
+ * $collectd: parts of tcpconns.c, 2008/08/08 03:48:30 Michael Stapelberg $
+ * $OpenBSD: inet.c,v 1.100 2007/06/19 05:28:30 ray Exp $
+ * $NetBSD: inet.c,v 1.14 1995/10/03 21:42:37 thorpej Exp $
+ *
+ * Copyright (c) 1983, 1988, 1993
+ *      The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#if defined(__OpenBSD__) || defined(__NetBSD__)
+#undef HAVE_SYSCTLBYNAME /* force HAVE_LIBKVM_NLIST path */
+#endif
+
+#if !KERNEL_LINUX && !HAVE_SYSCTLBYNAME && !HAVE_LIBKVM_NLIST && !KERNEL_AIX
+# error "No applicable input method."
+#endif
+
+#if KERNEL_LINUX
+/* #endif KERNEL_LINUX */
+
+#elif HAVE_SYSCTLBYNAME
+# include <sys/socketvar.h>
+# include <sys/sysctl.h>
+
+/* Some includes needed for compiling on FreeBSD */
+#include <sys/time.h>
+#if HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#if HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif
+#if HAVE_NET_IF_H
+# include <net/if.h>
+#endif
+
+# include <net/route.h>
+# include <netinet/in.h>
+# include <netinet/in_systm.h>
+# include <netinet/ip.h>
+# include <netinet/ip6.h>
+# include <netinet/in_pcb.h>
+# include <netinet/ip_var.h>
+# include <netinet/tcp.h>
+# include <netinet/tcpip.h>
+# include <netinet/tcp_seq.h>
+# include <netinet/tcp_var.h>
+/* #endif HAVE_SYSCTLBYNAME */
+
+/* This is for OpenBSD and NetBSD. */
+#elif HAVE_LIBKVM_NLIST
+# include <sys/queue.h>
+# include <sys/socket.h>
+# include <net/route.h>
+# include <netinet/in.h>
+# include <netinet/in_systm.h>
+# include <netinet/ip.h>
+# include <netinet/ip_var.h>
+# include <netinet/in_pcb.h>
+# include <netinet/tcp.h>
+# include <netinet/tcp_timer.h>
+# include <netinet/tcp_var.h>
+# include <netdb.h>
+# include <arpa/inet.h>
+# include <nlist.h>
+# include <kvm.h>
+/* #endif HAVE_LIBKVM_NLIST */
+
+#elif KERNEL_AIX
+# include <arpa/inet.h>
+# include <sys/socketvar.h>
+#endif /* KERNEL_AIX */
+
+#if KERNEL_LINUX
+static const char *tcp_state[] =
+{
+  "", /* 0 */
+  "ESTABLISHED",
+  "SYN_SENT",
+  "SYN_RECV",
+  "FIN_WAIT1",
+  "FIN_WAIT2",
+  "TIME_WAIT",
+  "CLOSED",
+  "CLOSE_WAIT",
+  "LAST_ACK",
+  "LISTEN", /* 10 */
+  "CLOSING"
+};
+
+# define TCP_STATE_LISTEN 10
+# define TCP_STATE_MIN 1
+# define TCP_STATE_MAX 11
+/* #endif KERNEL_LINUX */
+
+#elif HAVE_SYSCTLBYNAME
+static const char *tcp_state[] =
+{
+  "CLOSED",
+  "LISTEN",
+  "SYN_SENT",
+  "SYN_RECV",
+  "ESTABLISHED",
+  "CLOSE_WAIT",
+  "FIN_WAIT1",
+  "CLOSING",
+  "LAST_ACK",
+  "FIN_WAIT2",
+  "TIME_WAIT"
+};
+
+# define TCP_STATE_LISTEN 1
+# define TCP_STATE_MIN 0
+# define TCP_STATE_MAX 10
+/* #endif HAVE_SYSCTLBYNAME */
+
+#elif HAVE_LIBKVM_NLIST
+static const char *tcp_state[] =
+{
+  "CLOSED",
+  "LISTEN",
+  "SYN_SENT",
+  "SYN_RECV",
+  "ESTABLISHED",
+  "CLOSE_WAIT",
+  "FIN_WAIT1",
+  "CLOSING",
+  "LAST_ACK",
+  "FIN_WAIT2",
+  "TIME_WAIT"
+};
+
+static kvm_t *kvmd;
+static u_long      inpcbtable_off = 0;
+struct inpcbtable *inpcbtable_ptr = NULL;
+
+# define TCP_STATE_LISTEN 1
+# define TCP_STATE_MIN 1
+# define TCP_STATE_MAX 10
+/* #endif HAVE_LIBKVM_NLIST */
+
+#elif KERNEL_AIX
+static const char *tcp_state[] =
+{
+  "CLOSED",
+  "LISTEN",
+  "SYN_SENT",
+  "SYN_RCVD",
+  "ESTABLISHED",
+  "CLOSE_WAIT",
+  "FIN_WAIT_1",
+  "CLOSING",
+  "LAST_ACK",
+  "FIN_WAIT_2",
+  "TIME_WAIT"
+};
+
+# define TCP_STATE_LISTEN 1
+# define TCP_STATE_MIN 0
+# define TCP_STATE_MAX 10
+
+struct netinfo_conn {
+  uint32_t unknow1[2];
+  uint16_t dstport;
+  uint16_t unknow2;
+  struct in6_addr dstaddr;
+  uint16_t srcport;
+  uint16_t unknow3;
+  struct in6_addr srcaddr;
+  uint32_t unknow4[36];
+  uint16_t tcp_state;
+  uint16_t unknow5[7];
+};
+
+struct netinfo_header {
+  unsigned int proto;
+  unsigned int size;
+};
+
+# define NETINFO_TCP 3
+extern int netinfo (int proto, void *data, int *size,  int n);
+#endif /* KERNEL_AIX */
+
+#define PORT_COLLECT_LOCAL  0x01
+#define PORT_COLLECT_REMOTE 0x02
+#define PORT_IS_LISTENING   0x04
+
+typedef struct port_entry_s
+{
+  uint16_t port;
+  uint16_t flags;
+  uint32_t count_local[TCP_STATE_MAX + 1];
+  uint32_t count_remote[TCP_STATE_MAX + 1];
+  struct port_entry_s *next;
+} port_entry_t;
+
+static const char *config_keys[] =
+{
+  "ListeningPorts",
+  "LocalPort",
+  "RemotePort"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static int port_collect_listening = 0;
+static port_entry_t *port_list_head = NULL;
+
+static void conn_submit_port_entry (port_entry_t *pe)
+{
+  value_t values[1];
+  value_list_t vl = VALUE_LIST_INIT;
+  int i;
+
+  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));
+
+  if (((port_collect_listening != 0) && (pe->flags & PORT_IS_LISTENING))
+      || (pe->flags & PORT_COLLECT_LOCAL))
+  {
+    ssnprintf (vl.plugin_instance, sizeof (vl.plugin_instance),
+       "%"PRIu16"-local", pe->port);
+
+    for (i = 1; i <= TCP_STATE_MAX; i++)
+    {
+      vl.values[0].gauge = pe->count_local[i];
+
+      sstrncpy (vl.type_instance, tcp_state[i], sizeof (vl.type_instance));
+
+      plugin_dispatch_values (&vl);
+    }
+  }
+
+  if (pe->flags & PORT_COLLECT_REMOTE)
+  {
+    ssnprintf (vl.plugin_instance, sizeof (vl.plugin_instance),
+       "%"PRIu16"-remote", pe->port);
+
+    for (i = 1; i <= TCP_STATE_MAX; i++)
+    {
+      vl.values[0].gauge = pe->count_remote[i];
+
+      sstrncpy (vl.type_instance, tcp_state[i], sizeof (vl.type_instance));
+
+      plugin_dispatch_values (&vl);
+    }
+  }
+} /* void conn_submit */
+
+static void conn_submit_all (void)
+{
+  port_entry_t *pe;
+
+  for (pe = port_list_head; pe != NULL; pe = pe->next)
+    conn_submit_port_entry (pe);
+} /* void conn_submit_all */
+
+static port_entry_t *conn_get_port_entry (uint16_t port, int create)
+{
+  port_entry_t *ret;
+
+  ret = port_list_head;
+  while (ret != NULL)
+  {
+    if (ret->port == port)
+      break;
+    ret = ret->next;
+  }
+
+  if ((ret == NULL) && (create != 0))
+  {
+    ret = (port_entry_t *) malloc (sizeof (port_entry_t));
+    if (ret == NULL)
+      return (NULL);
+    memset (ret, '\0', sizeof (port_entry_t));
+
+    ret->port = port;
+    ret->next = port_list_head;
+    port_list_head = ret;
+  }
+
+  return (ret);
+} /* port_entry_t *conn_get_port_entry */
+
+/* Removes ports that were added automatically due to the `ListeningPorts'
+ * setting but which are no longer listening. */
+static void conn_reset_port_entry (void)
+{
+  port_entry_t *prev = NULL;
+  port_entry_t *pe = port_list_head;
+
+  while (pe != NULL)
+  {
+    /* If this entry was created while reading the files (ant not when handling
+     * the configuration) remove it now. */
+    if ((pe->flags & (PORT_COLLECT_LOCAL
+           | PORT_COLLECT_REMOTE
+           | PORT_IS_LISTENING)) == 0)
+    {
+      port_entry_t *next = pe->next;
+
+      DEBUG ("tcpconns plugin: Removing temporary entry "
+         "for listening port %"PRIu16, pe->port);
+
+      if (prev == NULL)
+       port_list_head = next;
+      else
+       prev->next = next;
+
+      sfree (pe);
+      pe = next;
+
+      continue;
+    }
+
+    memset (pe->count_local, '\0', sizeof (pe->count_local));
+    memset (pe->count_remote, '\0', sizeof (pe->count_remote));
+    pe->flags &= ~PORT_IS_LISTENING;
+
+    pe = pe->next;
+  }
+} /* void conn_reset_port_entry */
+
+static int conn_handle_ports (uint16_t port_local, uint16_t port_remote, uint8_t state)
+{
+  port_entry_t *pe = NULL;
+
+  if ((state > TCP_STATE_MAX)
+#if TCP_STATE_MIN > 0
+      || (state < TCP_STATE_MIN)
+#endif
+     )
+  {
+    NOTICE ("tcpconns plugin: Ignoring connection with "
+       "unknown state 0x%02"PRIx8".", state);
+    return (-1);
+  }
+
+  /* Listening sockets */
+  if ((state == TCP_STATE_LISTEN) && (port_collect_listening != 0))
+  {
+    pe = conn_get_port_entry (port_local, 1 /* create */);
+    if (pe != NULL)
+      pe->flags |= PORT_IS_LISTENING;
+  }
+
+  DEBUG ("tcpconns plugin: Connection %"PRIu16" <-> %"PRIu16" (%s)",
+      port_local, port_remote, tcp_state[state]);
+
+  pe = conn_get_port_entry (port_local, 0 /* no create */);
+  if (pe != NULL)
+    pe->count_local[state]++;
+
+  pe = conn_get_port_entry (port_remote, 0 /* no create */);
+  if (pe != NULL)
+    pe->count_remote[state]++;
+
+  return (0);
+} /* int conn_handle_ports */
+
+#if KERNEL_LINUX
+static int conn_handle_line (char *buffer)
+{
+  char *fields[32];
+  int   fields_len;
+
+  char *endptr;
+
+  char *port_local_str;
+  char *port_remote_str;
+  uint16_t port_local;
+  uint16_t port_remote;
+
+  uint8_t state;
+
+  int buffer_len = strlen (buffer);
+
+  while ((buffer_len > 0) && (buffer[buffer_len - 1] < 32))
+    buffer[--buffer_len] = '\0';
+  if (buffer_len <= 0)
+    return (-1);
+
+  fields_len = strsplit (buffer, fields, STATIC_ARRAY_SIZE (fields));
+  if (fields_len < 12)
+  {
+    DEBUG ("tcpconns plugin: Got %i fields, expected at least 12.", fields_len);
+    return (-1);
+  }
+
+  port_local_str  = strchr (fields[1], ':');
+  port_remote_str = strchr (fields[2], ':');
+
+  if ((port_local_str == NULL) || (port_remote_str == NULL))
+    return (-1);
+  port_local_str++;
+  port_remote_str++;
+  if ((*port_local_str == '\0') || (*port_remote_str == '\0'))
+    return (-1);
+
+  endptr = NULL;
+  port_local = (uint16_t) strtol (port_local_str, &endptr, 16);
+  if ((endptr == NULL) || (*endptr != '\0'))
+    return (-1);
+
+  endptr = NULL;
+  port_remote = (uint16_t) strtol (port_remote_str, &endptr, 16);
+  if ((endptr == NULL) || (*endptr != '\0'))
+    return (-1);
+
+  endptr = NULL;
+  state = (uint8_t) strtol (fields[3], &endptr, 16);
+  if ((endptr == NULL) || (*endptr != '\0'))
+    return (-1);
+
+  return (conn_handle_ports (port_local, port_remote, state));
+} /* int conn_handle_line */
+
+static int conn_read_file (const char *file)
+{
+  FILE *fh;
+  char buffer[1024];
+
+  fh = fopen (file, "r");
+  if (fh == NULL)
+    return (-1);
+
+  while (fgets (buffer, sizeof (buffer), fh) != NULL)
+  {
+    conn_handle_line (buffer);
+  } /* while (fgets) */
+
+  fclose (fh);
+
+  return (0);
+} /* int conn_read_file */
+/* #endif KERNEL_LINUX */
+
+#elif HAVE_SYSCTLBYNAME
+/* #endif HAVE_SYSCTLBYNAME */
+
+#elif HAVE_LIBKVM_NLIST
+#endif /* HAVE_LIBKVM_NLIST */
+
+static int conn_config (const char *key, const char *value)
+{
+  if (strcasecmp (key, "ListeningPorts") == 0)
+  {
+    if (IS_TRUE (value))
+      port_collect_listening = 1;
+    else
+      port_collect_listening = 0;
+  }
+  else if ((strcasecmp (key, "LocalPort") == 0)
+      || (strcasecmp (key, "RemotePort") == 0))
+  {
+      port_entry_t *pe;
+      int port = atoi (value);
+
+      if ((port < 1) || (port > 65535))
+      {
+       ERROR ("tcpconns plugin: Invalid port: %i", port);
+       return (1);
+      }
+
+      pe = conn_get_port_entry ((uint16_t) port, 1 /* create */);
+      if (pe == NULL)
+      {
+       ERROR ("tcpconns plugin: conn_get_port_entry failed.");
+       return (1);
+      }
+
+      if (strcasecmp (key, "LocalPort") == 0)
+       pe->flags |= PORT_COLLECT_LOCAL;
+      else
+       pe->flags |= PORT_COLLECT_REMOTE;
+  }
+  else
+  {
+    return (-1);
+  }
+
+  return (0);
+} /* int conn_config */
+
+#if KERNEL_LINUX
+static int conn_init (void)
+{
+  if (port_list_head == NULL)
+    port_collect_listening = 1;
+
+  return (0);
+} /* int conn_init */
+
+static int conn_read (void)
+{
+  int errors_num = 0;
+
+  conn_reset_port_entry ();
+
+  if (conn_read_file ("/proc/net/tcp") != 0)
+    errors_num++;
+  if (conn_read_file ("/proc/net/tcp6") != 0)
+    errors_num++;
+
+  if (errors_num < 2)
+  {
+    conn_submit_all ();
+  }
+  else
+  {
+    ERROR ("tcpconns plugin: Neither /proc/net/tcp nor /proc/net/tcp6 "
+       "coult be read.");
+    return (-1);
+  }
+
+  return (0);
+} /* int conn_read */
+/* #endif KERNEL_LINUX */
+
+#elif HAVE_SYSCTLBYNAME
+static int conn_read (void)
+{
+  int status;
+  char *buffer;
+  size_t buffer_len;;
+
+  struct xinpgen *in_orig;
+  struct xinpgen *in_ptr;
+
+  conn_reset_port_entry ();
+
+  buffer_len = 0;
+  status = sysctlbyname ("net.inet.tcp.pcblist", NULL, &buffer_len, 0, 0);
+  if (status < 0)
+  {
+    ERROR ("tcpconns plugin: sysctlbyname failed.");
+    return (-1);
+  }
+
+  buffer = (char *) malloc (buffer_len);
+  if (buffer == NULL)
+  {
+    ERROR ("tcpconns plugin: malloc failed.");
+    return (-1);
+  }
+
+  status = sysctlbyname ("net.inet.tcp.pcblist", buffer, &buffer_len, 0, 0);
+  if (status < 0)
+  {
+    ERROR ("tcpconns plugin: sysctlbyname failed.");
+    sfree (buffer);
+    return (-1);
+  }
+
+  if (buffer_len <= sizeof (struct xinpgen))
+  {
+    ERROR ("tcpconns plugin: (buffer_len <= sizeof (struct xinpgen))");
+    sfree (buffer);
+    return (-1);
+  }
+
+  in_orig = (struct xinpgen *) buffer;
+  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))
+  {
+    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;
+
+    /* Ignore non-TCP sockets */
+    if (so->xso_protocol != IPPROTO_TCP)
+      continue;
+
+    /* Ignore PCBs which were freed during copyout. */
+    if (inp->inp_gencnt > in_orig->xig_gen)
+      continue;
+
+    if (((inp->inp_vflag & INP_IPV4) == 0)
+       && ((inp->inp_vflag & INP_IPV6) == 0))
+      continue;
+
+    conn_handle_ports (ntohs (inp->inp_lport), ntohs (inp->inp_fport),
+       tp->t_state);
+  } /* for (in_ptr) */
+
+  in_orig = NULL;
+  in_ptr = NULL;
+  sfree (buffer);
+
+  conn_submit_all ();
+
+  return (0);
+} /* int conn_read */
+/* #endif HAVE_SYSCTLBYNAME */
+
+#elif HAVE_LIBKVM_NLIST
+static int kread (u_long addr, void *buf, int size)
+{
+  int status;
+
+  status = kvm_read (kvmd, addr, buf, size);
+  if (status != size)
+  {
+    ERROR ("tcpconns plugin: kvm_read failed (got %i, expected %i): %s\n",
+       status, size, kvm_geterr (kvmd));
+    return (-1);
+  }
+  return (0);
+} /* int kread */
+
+static int conn_init (void)
+{
+  char buf[_POSIX2_LINE_MAX];
+  struct nlist nl[] =
+  {
+#define N_TCBTABLE 0
+    { "_tcbtable" },
+    { "" }
+  };
+  int status;
+
+  kvmd = kvm_openfiles (NULL, NULL, NULL, O_RDONLY, buf);
+  if (kvmd == NULL)
+  {
+    ERROR ("tcpconns plugin: kvm_openfiles failed: %s", buf);
+    return (-1);
+  }
+
+  status = kvm_nlist (kvmd, nl);
+  if (status < 0)
+  {
+    ERROR ("tcpconns plugin: kvm_nlist failed with status %i.", status);
+    return (-1);
+  }
+
+  if (nl[N_TCBTABLE].n_type == 0)
+  {
+    ERROR ("tcpconns plugin: Error looking up kernel's namelist: "
+       "N_TCBTABLE is invalid.");
+    return (-1);
+  }
+
+  inpcbtable_off = (u_long) nl[N_TCBTABLE].n_value;
+  inpcbtable_ptr = (struct inpcbtable *) nl[N_TCBTABLE].n_value;
+
+  return (0);
+} /* int conn_init */
+
+static int conn_read (void)
+{
+  struct inpcbtable table;
+  struct inpcb *head;
+  struct inpcb *next;
+  struct inpcb inpcb;
+  struct tcpcb tcpcb;
+  int status;
+
+  conn_reset_port_entry ();
+
+  /* Read the pcbtable from the kernel */
+  status = kread (inpcbtable_off, &table, sizeof (table));
+  if (status != 0)
+    return (-1);
+
+  /* Get the `head' pcb */
+  head = (struct inpcb *) &(inpcbtable_ptr->inpt_queue);
+  /* Get the first pcb */
+  next = (struct inpcb *)CIRCLEQ_FIRST (&table.inpt_queue);
+
+  while (next != head)
+  {
+    /* Read the pcb pointed to by `next' into `inpcb' */
+    kread ((u_long) next, &inpcb, sizeof (inpcb));
+
+    /* Advance `next' */
+    next = (struct inpcb *)CIRCLEQ_NEXT (&inpcb, inp_queue);
+
+    /* Ignore sockets, that are not connected. */
+#ifdef __NetBSD__
+    if (inpcb.inp_af == AF_INET6)
+      continue; /* XXX see netbsd/src/usr.bin/netstat/inet6.c */
+#else
+    if (!(inpcb.inp_flags & INP_IPV6)
+       && (inet_lnaof(inpcb.inp_laddr) == INADDR_ANY))
+      continue;
+    if ((inpcb.inp_flags & INP_IPV6)
+       && IN6_IS_ADDR_UNSPECIFIED (&inpcb.inp_laddr6))
+      continue;
+#endif
+
+    kread ((u_long) inpcb.inp_ppcb, &tcpcb, sizeof (tcpcb));
+    conn_handle_ports (ntohs(inpcb.inp_lport), ntohs(inpcb.inp_fport), tcpcb.t_state);
+  } /* while (next != head) */
+
+  conn_submit_all ();
+
+  return (0);
+}
+/* #endif HAVE_LIBKVM_NLIST */
+
+#elif KERNEL_AIX
+
+static int conn_read (void)
+{
+  int size;
+  int i;
+  int nconn;
+  void *data;
+  struct netinfo_header *header;
+  struct netinfo_conn *conn;
+
+  conn_reset_port_entry ();
+
+  size = netinfo(NETINFO_TCP, 0, 0, 0);
+  if (size < 0)
+  {
+    ERROR ("tcpconns plugin: netinfo failed return: %i", size);
+    return (-1);
+  }
+
+  if (size == 0)
+    return (0);
+
+  if ((size - sizeof (struct netinfo_header)) % sizeof (struct netinfo_conn))
+  {
+    ERROR ("tcpconns plugin: invalid buffer size");
+    return (-1);
+  }
+
+  data = malloc(size);
+  if (data == NULL)
+  {
+    ERROR ("tcpconns plugin: malloc failed");
+    return (-1);
+  }
+
+  if (netinfo(NETINFO_TCP, data, &size, 0) < 0)
+  {
+    ERROR ("tcpconns plugin: netinfo failed");
+    free(data);
+    return (-1);
+  }
+
+  header = (struct netinfo_header *)data;
+  nconn = header->size;
+  conn = (struct netinfo_conn *)(data + sizeof(struct netinfo_header));
+
+  for (i=0; i < nconn; conn++, i++)
+  {
+    conn_handle_ports (conn->srcport, conn->dstport, conn->tcp_state);
+  }
+
+  free(data);
+
+  conn_submit_all ();
+
+  return (0);
+}
+#endif /* KERNEL_AIX */
+
+void module_register (void)
+{
+       plugin_register_config ("tcpconns", conn_config,
+                       config_keys, config_keys_num);
+#if KERNEL_LINUX
+       plugin_register_init ("tcpconns", conn_init);
+#elif HAVE_SYSCTLBYNAME
+       /* no initialization */
+#elif HAVE_LIBKVM_NLIST
+       plugin_register_init ("tcpconns", conn_init);
+#elif KERNEL_AIX
+       /* no initialization */
+#endif
+       plugin_register_read ("tcpconns", conn_read);
+} /* void module_register */
+
+/*
+ * vim: set shiftwidth=2 softtabstop=2 tabstop=8 fdm=marker :
+ */
diff --git a/src/teamspeak2.c b/src/teamspeak2.c
new file mode 100644 (file)
index 0000000..2552ad3
--- /dev/null
@@ -0,0 +1,851 @@
+/**
+ * collectd - src/teamspeak2.c
+ * Copyright (C) 2008  Stefan Hacker
+ * Copyright (C) 2008  Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Stefan Hacker <d0t at dbclan dot de>
+ *   Florian Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+/*
+ * Defines
+ */
+/* Default host and port */
+#define DEFAULT_HOST   "127.0.0.1"
+#define DEFAULT_PORT   "51234"
+
+/*
+ * Variables
+ */
+/* Server linked list structure */
+typedef struct vserver_list_s
+{
+       int port;
+       struct vserver_list_s *next;
+} vserver_list_t;
+static vserver_list_t *server_list = NULL;
+
+/* Host data */
+static char *config_host = NULL;
+static char *config_port = NULL;
+
+static FILE *global_read_fh = NULL;
+static FILE *global_write_fh = NULL;
+
+/* Config data */
+static const char *config_keys[] =
+{
+       "Host",
+       "Port",
+       "Server"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+/*
+ * Functions
+ */
+static int tss2_add_vserver (int vserver_port)
+{
+       /*
+        * Adds a new vserver to the linked list
+        */
+       vserver_list_t *entry;
+
+       /* Check port range */
+       if ((vserver_port <= 0) || (vserver_port > 65535))
+       {
+               ERROR ("teamspeak2 plugin: VServer port is invalid: %i",
+                               vserver_port);
+               return (-1);
+       }
+
+       /* Allocate memory */
+       entry = (vserver_list_t *) malloc (sizeof (vserver_list_t));
+       if (entry == NULL)
+       {
+               ERROR ("teamspeak2 plugin: malloc failed.");
+               return (-1);
+       }
+       memset (entry, 0, sizeof (vserver_list_t));
+
+       /* Save data */
+       entry->port = vserver_port;
+
+       /* Insert to list */
+       if(server_list == NULL) {
+               /* Add the server as the first element */
+               server_list = entry;
+       }
+       else {
+               vserver_list_t *prev;
+
+               /* Add the server to the end of the list */
+               prev = server_list;
+               while (prev->next != NULL)
+                       prev = prev->next;
+               prev->next = entry;
+       }
+
+       INFO ("teamspeak2 plugin: Registered new vserver: %i", vserver_port);
+
+       return (0);
+} /* int tss2_add_vserver */
+
+static void tss2_submit_gauge (const char *plugin_instance,
+               const char *type, const char *type_instance,
+               gauge_t value)
+{
+       /*
+        * 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_len = 1;
+       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+       sstrncpy (vl.plugin, "teamspeak2", sizeof (vl.plugin));
+
+       if (plugin_instance != NULL)
+               sstrncpy (vl.plugin_instance, plugin_instance,
+                               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);
+} /* void tss2_submit_gauge */
+
+static void tss2_submit_io (const char *plugin_instance, const char *type,
+               derive_t rx, derive_t tx)
+{
+       /*
+        * 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;
+
+       vl.values     = values;
+       vl.values_len = 2;
+       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+       sstrncpy (vl.plugin, "teamspeak2", sizeof (vl.plugin));
+
+       if (plugin_instance != NULL)
+               sstrncpy (vl.plugin_instance, plugin_instance,
+                               sizeof (vl.plugin_instance));
+
+       sstrncpy (vl.type, type, sizeof (vl.type));
+
+       plugin_dispatch_values (&vl);
+} /* void tss2_submit_gauge */
+
+static void tss2_close_socket (void)
+{
+       /*
+        * Closes all sockets
+        */
+       if (global_write_fh != NULL)
+       {
+               fputs ("quit\r\n", global_write_fh);
+       }
+
+       if (global_read_fh != NULL)
+       {
+               fclose (global_read_fh);
+               global_read_fh = NULL;
+       }
+
+       if (global_write_fh != NULL)
+       {
+               fclose (global_write_fh);
+               global_write_fh = NULL;
+       }
+} /* void tss2_close_socket */
+
+static int tss2_get_socket (FILE **ret_read_fh, FILE **ret_write_fh)
+{
+       /*
+        * Returns connected file objects or establishes the connection
+        * if it's not already present
+        */
+       struct addrinfo ai_hints;
+       struct addrinfo *ai_head;
+       struct addrinfo *ai_ptr;
+       int sd = -1;
+       int status;
+
+       /* Check if we already got opened connections */
+       if ((global_read_fh != NULL) && (global_write_fh != NULL))
+       {
+               /* If so, use them */
+               if (ret_read_fh != NULL)
+                       *ret_read_fh = global_read_fh;
+               if (ret_write_fh != NULL)
+                       *ret_write_fh = global_write_fh;
+               return (0);
+       }
+
+       /* Get all addrs for this hostname */
+       memset (&ai_hints, 0, sizeof (ai_hints));
+#ifdef AI_ADDRCONFIG
+       ai_hints.ai_flags |= AI_ADDRCONFIG;
+#endif
+       ai_hints.ai_family = AF_UNSPEC;
+       ai_hints.ai_socktype = SOCK_STREAM;
+
+       status = getaddrinfo ((config_host != NULL) ? config_host : DEFAULT_HOST,
+                       (config_port != NULL) ? config_port : DEFAULT_PORT,
+                       &ai_hints,
+                       &ai_head);
+       if (status != 0)
+       {
+               ERROR ("teamspeak2 plugin: getaddrinfo failed: %s",
+                               gai_strerror (status));
+               return (-1);
+       }
+
+       /* Try all given hosts until we can connect to one */
+       for (ai_ptr = ai_head; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
+       {
+               /* Create socket */
+               sd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype,
+                               ai_ptr->ai_protocol);
+               if (sd < 0)
+               {
+                       char errbuf[1024];
+                       WARNING ("teamspeak2 plugin: socket failed: %s",
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       continue;
+               }
+
+               /* Try to connect */
+               status = connect (sd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
+               if (status != 0)
+               {
+                       char errbuf[1024];
+                       WARNING ("teamspeak2 plugin: connect failed: %s",
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       close (sd);
+                       continue;
+               }
+
+               /*
+                * Success, we can break. Don't need more than one connection
+                */
+               break;
+       } /* for (ai_ptr) */
+
+       freeaddrinfo (ai_head);
+
+       /* Check if we really got connected */
+       if (sd < 0)
+               return (-1);
+
+       /* Create file objects from sockets */
+       global_read_fh = fdopen (sd, "r");
+       if (global_read_fh == NULL)
+       {
+               char errbuf[1024];
+               ERROR ("teamspeak2 plugin: fdopen failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               close (sd);
+               return (-1);
+       }
+
+       global_write_fh = fdopen (sd, "w");
+       if (global_write_fh == NULL)
+       {
+               char errbuf[1024];
+               ERROR ("teamspeak2 plugin: fdopen failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               tss2_close_socket ();
+               return (-1);
+       }
+
+       { /* Check that the server correctly identifies itself. */
+               char buffer[4096];
+               char *buffer_ptr;
+
+               buffer_ptr = fgets (buffer, sizeof (buffer), global_read_fh);
+               if (buffer_ptr == NULL)
+               {
+                       WARNING ("teamspeak2 plugin: Unexpected EOF received "
+                                       "from remote host %s:%s.",
+                                       config_host ? config_host : DEFAULT_HOST,
+                                       config_port ? config_port : DEFAULT_PORT);
+               }
+               buffer[sizeof (buffer) - 1] = 0;
+
+               if (memcmp ("[TS]\r\n", buffer, 6) != 0)
+               {
+                       ERROR ("teamspeak2 plugin: Unexpected response when connecting "
+                                       "to server. Expected ``[TS]'', got ``%s''.",
+                                       buffer);
+                       tss2_close_socket ();
+                       return (-1);
+               }
+               DEBUG ("teamspeak2 plugin: Server send correct banner, connected!");
+       }
+
+       /* Copy the new filehandles to the given pointers */
+       if (ret_read_fh != NULL)
+               *ret_read_fh = global_read_fh;
+       if (ret_write_fh != NULL)
+               *ret_write_fh = global_write_fh;
+       return (0);
+} /* int tss2_get_socket */
+
+static int tss2_send_request (FILE *fh, const char *request)
+{
+       /*
+        * This function puts a request to the server socket
+        */
+       int status;
+
+       status = fputs (request, fh);
+       if (status < 0)
+       {
+               ERROR ("teamspeak2 plugin: fputs failed.");
+               tss2_close_socket ();
+               return (-1);
+       }
+       fflush (fh);
+
+       return (0);
+} /* int tss2_send_request */
+
+static int tss2_receive_line (FILE *fh, char *buffer, int buffer_size)
+{
+       /*
+        * Receive a single line from the given file object
+        */
+       char *temp;
+        
+       /*
+        * fgets is blocking but much easier then doing anything else
+        * TODO: Non-blocking Version would be safer
+        */
+       temp = fgets (buffer, buffer_size, fh);
+       if (temp == NULL)
+       {
+               char errbuf[1024];
+               ERROR ("teamspeak2 plugin: fgets failed: %s",
+                               sstrerror (errno, errbuf, sizeof(errbuf)));
+               tss2_close_socket ();
+               return (-1);
+       }
+
+       buffer[buffer_size - 1] = 0;
+       return (0);
+} /* int tss2_receive_line */
+
+static int tss2_select_vserver (FILE *read_fh, FILE *write_fh, vserver_list_t *vserver)
+{
+       /*
+        * Tell the server to select the given vserver
+        */
+       char command[128];
+       char response[128];
+       int status;
+
+       /* Send request */
+       ssnprintf (command, sizeof (command), "sel %i\r\n", vserver->port);
+
+       status = tss2_send_request (write_fh, command);
+       if (status != 0)
+       {
+               ERROR ("teamspeak2 plugin: tss2_send_request (%s) failed.", command);
+               return (-1);
+       }
+
+       /* Get answer */
+       status = tss2_receive_line (read_fh, response, sizeof (response));
+       if (status != 0)
+       {
+               ERROR ("teamspeak2 plugin: tss2_receive_line failed.");
+               return (-1);
+       }
+       response[sizeof (response) - 1] = 0;
+
+       /* Check answer */
+       if ((strncasecmp ("OK", response, 2) == 0)
+                       && ((response[2] == 0)
+                               || (response[2] == '\n')
+                               || (response[2] == '\r')))
+               return (0);
+
+       ERROR ("teamspeak2 plugin: Command ``%s'' failed. "
+                       "Response received from server was: ``%s''.",
+                       command, response);
+       return (-1);
+} /* int tss2_select_vserver */
+
+static int tss2_vserver_gapl (FILE *read_fh, FILE *write_fh,
+               gauge_t *ret_value)
+{
+       /*
+        * Reads the vserver's average packet loss and submits it to collectd.
+        * Be sure to run the tss2_read_vserver function before calling this so
+        * the vserver is selected correctly.
+        */
+       gauge_t packet_loss = NAN;
+       int status;
+
+       status = tss2_send_request (write_fh, "gapl\r\n");
+       if (status != 0)
+       {
+               ERROR("teamspeak2 plugin: tss2_send_request (gapl) failed.");
+               return (-1);
+       }
+
+       while (42)
+       {
+               char buffer[4096];
+               char *value;
+               char *endptr = NULL;
+               
+               status = tss2_receive_line (read_fh, buffer, sizeof (buffer));
+               if (status != 0)
+               {
+                       /* Set to NULL just to make sure noone uses these FHs anymore. */
+                       read_fh = NULL;
+                       write_fh = NULL;
+                       ERROR ("teamspeak2 plugin: tss2_receive_line failed.");
+                       return (-1);
+               }
+               buffer[sizeof (buffer) - 1] = 0;
+               
+               if (strncmp ("average_packet_loss=", buffer,
+                                       strlen ("average_packet_loss=")) == 0)
+               {
+                       /* Got average packet loss, now interpret it */
+                       value = &buffer[20];
+                       /* Replace , with . */
+                       while (*value != 0)
+                       {
+                               if (*value == ',')
+                               {
+                                       *value = '.';
+                                       break;
+                               }
+                               value++;
+                       }
+                       
+                       value = &buffer[20];
+                       
+                       packet_loss = strtod (value, &endptr);
+                       if (value == endptr)
+                       {
+                               /* Failed */
+                               WARNING ("teamspeak2 plugin: Could not read average package "
+                                               "loss from string: %s", buffer);
+                               continue;
+                       }
+               }
+               else if (strncasecmp ("OK", buffer, 2) == 0)
+               {
+                       break;
+               }
+               else if (strncasecmp ("ERROR", buffer, 5) == 0)
+               {
+                       ERROR ("teamspeak2 plugin: Server returned an error: %s", buffer);
+                       return (-1);
+               }
+               else
+               {
+                       WARNING ("teamspeak2 plugin: Server returned unexpected string: %s",
+                                       buffer);
+               }
+       }
+       
+       *ret_value = packet_loss;
+       return (0);
+} /* int tss2_vserver_gapl */
+
+static int tss2_read_vserver (vserver_list_t *vserver)
+{
+       /*
+        * Poll information for the given vserver and submit it to collect.
+        * If vserver is NULL the global server information will be queried.
+        */
+       int status;
+
+       gauge_t users = NAN;
+       gauge_t channels = NAN;
+       gauge_t servers = NAN;
+       derive_t rx_octets = 0;
+       derive_t tx_octets = 0;
+       derive_t rx_packets = 0;
+       derive_t tx_packets = 0;
+       gauge_t packet_loss = NAN;
+       int valid = 0;
+
+       char plugin_instance[DATA_MAX_NAME_LEN];
+
+       FILE *read_fh;
+       FILE *write_fh;
+
+       /* Get the send/receive sockets */
+       status = tss2_get_socket (&read_fh, &write_fh);
+       if (status != 0)
+       {
+               ERROR ("teamspeak2 plugin: tss2_get_socket failed.");
+               return (-1);
+       }
+
+       if (vserver == NULL)
+       {
+               /* Request global information */
+               memset (plugin_instance, 0, sizeof (plugin_instance));
+
+               status = tss2_send_request (write_fh, "gi\r\n");
+       }
+       else
+       {
+               /* Request server information */
+               ssnprintf (plugin_instance, sizeof (plugin_instance), "vserver%i",
+                               vserver->port);
+
+               /* Select the server */
+               status = tss2_select_vserver (read_fh, write_fh, vserver);
+               if (status != 0)
+                       return (status);
+
+               status = tss2_send_request (write_fh, "si\r\n");
+       }
+
+       if (status != 0)
+       {
+               ERROR ("teamspeak2 plugin: tss2_send_request failed.");
+               return (-1);
+       }
+
+       /* Loop until break */
+       while (42)
+       {
+               char buffer[4096];
+               char *key;
+               char *value;
+               char *endptr = NULL;
+               
+               /* Read one line of the server's answer */
+               status = tss2_receive_line (read_fh, buffer, sizeof (buffer));
+               if (status != 0)
+               {
+                       /* Set to NULL just to make sure noone uses these FHs anymore. */
+                       read_fh = NULL;
+                       write_fh = NULL;
+                       ERROR ("teamspeak2 plugin: tss2_receive_line failed.");
+                       break;
+               }
+
+               if (strncasecmp ("ERROR", buffer, 5) == 0)
+               {
+                       ERROR ("teamspeak2 plugin: Server returned an error: %s",
+                                       buffer);
+                       break;
+               }
+               else if (strncasecmp ("OK", buffer, 2) == 0)
+               {
+                       break;
+               }
+
+               /* Split line into key and value */
+               key = strchr (buffer, '_');
+               if (key == NULL)
+               {
+                       DEBUG ("teamspeak2 plugin: Cannot parse line: %s", buffer);
+                       continue;
+               }
+               key++;
+
+               /* Evaluate assignment */
+               value = strchr (key, '=');
+               if (value == NULL)
+               {
+                       DEBUG ("teamspeak2 plugin: Cannot parse line: %s", buffer);
+                       continue;
+               }
+               *value = 0;
+               value++;
+
+               /* Check for known key and save the given value */
+               /* global info: users_online,
+                * server info: currentusers. */
+               if ((strcmp ("currentusers", key) == 0)
+                               || (strcmp ("users_online", key) == 0))
+               {
+                       users = strtod (value, &endptr);
+                       if (value != endptr)
+                               valid |= 0x01;
+               }
+               /* global info: channels,
+                * server info: currentchannels. */
+               else if ((strcmp ("currentchannels", key) == 0)
+                               || (strcmp ("channels", key) == 0))
+               {
+                       channels = strtod (value, &endptr);
+                       if (value != endptr)
+                               valid |= 0x40;
+               }
+               /* global only */
+               else if (strcmp ("servers", key) == 0)
+               {
+                       servers = strtod (value, &endptr);
+                       if (value != endptr)
+                               valid |= 0x80;
+               }
+               else if (strcmp ("bytesreceived", key) == 0)
+               {
+                       rx_octets = strtoll (value, &endptr, 0);
+                       if (value != endptr)
+                               valid |= 0x02;
+               }
+               else if (strcmp ("bytessend", key) == 0)
+               {
+                       tx_octets = strtoll (value, &endptr, 0);
+                       if (value != endptr)
+                               valid |= 0x04;
+               }
+               else if (strcmp ("packetsreceived", key) == 0)
+               {
+                       rx_packets = strtoll (value, &endptr, 0);
+                       if (value != endptr)
+                               valid |= 0x08;
+               }
+               else if (strcmp ("packetssend", key) == 0)
+               {
+                       tx_packets = strtoll (value, &endptr, 0);
+                       if (value != endptr)
+                               valid |= 0x10;
+               }
+               else if ((strncmp ("allow_codec_", key, strlen ("allow_codec_")) == 0)
+                               || (strncmp ("bwinlast", key, strlen ("bwinlast")) == 0)
+                               || (strncmp ("bwoutlast", key, strlen ("bwoutlast")) == 0)
+                               || (strncmp ("webpost_", key, strlen ("webpost_")) == 0)
+                               || (strcmp ("adminemail", key) == 0)
+                               || (strcmp ("clan_server", key) == 0)
+                               || (strcmp ("countrynumber", key) == 0)
+                               || (strcmp ("id", key) == 0)
+                               || (strcmp ("ispname", key) == 0)
+                               || (strcmp ("linkurl", key) == 0)
+                               || (strcmp ("maxusers", key) == 0)
+                               || (strcmp ("name", key) == 0)
+                               || (strcmp ("password", key) == 0)
+                               || (strcmp ("platform", key) == 0)
+                               || (strcmp ("server_platform", key) == 0)
+                               || (strcmp ("server_uptime", key) == 0)
+                               || (strcmp ("server_version", key) == 0)
+                               || (strcmp ("udpport", key) == 0)
+                               || (strcmp ("uptime", key) == 0)
+                               || (strcmp ("users_maximal", key) == 0)
+                               || (strcmp ("welcomemessage", key) == 0))
+                       /* ignore */;
+               else
+               {
+                       INFO ("teamspeak2 plugin: Unknown key-value-pair: "
+                                       "key = %s; value = %s;", key, value);
+               }
+       } /* while (42) */
+
+       /* Collect vserver packet loss rates only if the loop above did not exit
+        * with an error. */
+       if ((status == 0) && (vserver != NULL))
+       {
+               status = tss2_vserver_gapl (read_fh, write_fh, &packet_loss);
+               if (status == 0)
+               {
+                       valid |= 0x20;
+               }
+               else
+               {
+                       WARNING ("teamspeak2 plugin: Reading package loss "
+                                       "for vserver %i failed.", vserver->port);
+               }
+       }
+
+       if ((valid & 0x01) == 0x01)
+               tss2_submit_gauge (plugin_instance, "users", NULL, users);
+
+       if ((valid & 0x06) == 0x06)
+               tss2_submit_io (plugin_instance, "io_octets", rx_octets, tx_octets);
+
+       if ((valid & 0x18) == 0x18)
+               tss2_submit_io (plugin_instance, "io_packets", rx_packets, tx_packets);
+
+       if ((valid & 0x20) == 0x20)
+               tss2_submit_gauge (plugin_instance, "percent", "packet_loss", packet_loss);
+
+       if ((valid & 0x40) == 0x40)
+               tss2_submit_gauge (plugin_instance, "gauge", "channels", channels);
+
+       if ((valid & 0x80) == 0x80)
+               tss2_submit_gauge (plugin_instance, "gauge", "servers", servers);
+
+       if (valid == 0)
+               return (-1);
+       return (0);
+} /* int tss2_read_vserver */
+
+static int tss2_config (const char *key, const char *value)
+{
+       /*
+        * Interpret configuration values
+        */
+    if (strcasecmp ("Host", key) == 0)
+       {
+               char *temp;
+
+               temp = strdup (value);
+               if (temp == NULL)
+               {
+                       ERROR("teamspeak2 plugin: strdup failed.");
+                       return (1);
+               }
+               sfree (config_host);
+               config_host = temp;
+       }
+       else if (strcasecmp ("Port", key) == 0)
+       {
+               char *temp;
+
+               temp = strdup (value);
+               if (temp == NULL)
+               {
+                       ERROR("teamspeak2 plugin: strdup failed.");
+                       return (1);
+               }
+               sfree (config_port);
+               config_port = temp;
+       }
+       else if (strcasecmp ("Server", key) == 0)
+       {
+               /* Server variable found */
+               int status;
+               
+               status = tss2_add_vserver (atoi (value));
+               if (status != 0)
+                       return (1);
+       }
+       else
+       {
+               /* Unknown variable found */
+               return (-1);
+       }
+
+       return 0;
+} /* int tss2_config */
+
+static int tss2_read (void)
+{
+       /*
+        * Poll function which collects global and vserver information
+        * and submits it to collectd
+        */
+       vserver_list_t *vserver;
+       int success = 0;
+       int status;
+
+       /* Handle global server variables */
+       status = tss2_read_vserver (NULL);
+       if (status == 0)
+       {
+               success++;
+       }
+       else
+       {
+               WARNING ("teamspeak2 plugin: Reading global server variables failed.");
+       }
+
+       /* Handle vservers */
+       for (vserver = server_list; vserver != NULL; vserver = vserver->next)
+       {
+               status = tss2_read_vserver (vserver);
+               if (status == 0)
+               {
+                       success++;
+               }
+               else
+               {
+                       WARNING ("teamspeak2 plugin: Reading statistics "
+                                       "for vserver %i failed.", vserver->port);
+                       continue;
+               }
+       }
+       
+       if (success == 0)
+               return (-1);
+    return (0);
+} /* int tss2_read */
+
+static int tss2_shutdown(void)
+{
+       /*
+        * Shutdown handler
+        */
+       vserver_list_t *entry;
+
+       tss2_close_socket ();
+
+       entry = server_list;
+       server_list = NULL;
+       while (entry != NULL)
+       {
+               vserver_list_t *next;
+
+               next = entry->next;
+               sfree (entry);
+               entry = next;
+       }
+
+       /* Get rid of the configuration */
+       sfree (config_host);
+       sfree (config_port);
+       
+    return (0);
+} /* int tss2_shutdown */
+
+void module_register(void)
+{
+       /*
+        * Mandatory module_register function
+        */
+       plugin_register_config ("teamspeak2", tss2_config,
+                       config_keys, config_keys_num);
+       plugin_register_read ("teamspeak2", tss2_read);
+       plugin_register_shutdown ("teamspeak2", tss2_shutdown);
+} /* void module_register */
+
+/* vim: set sw=4 ts=4 : */
diff --git a/src/ted.c b/src/ted.c
new file mode 100644 (file)
index 0000000..bf519bb
--- /dev/null
+++ b/src/ted.c
@@ -0,0 +1,359 @@
+/**
+ * collectd - src/ted.c
+ * Copyright (C) 2009  Eric Reed
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Eric Reed <ericr at reedhome.net>
+ *
+ *  This is a collectd module for The Energy Detective: A low-cost whole
+ * house energy monitoring system. For more information on TED, see
+ * http://theenergydetective.com
+ *
+ * This module was not created by Energy, Inc. nor is it supported by
+ * them in any way. It was created using information from two sources:
+ * David Satterfield's TED module for Misterhouse, and Micah Dowty's TED
+ * Python Module.
+ *
+ * This has only tested with the model 1001 RDU, with
+ * firmware version 9.01U. The USB port is uses the very common FTDI
+ * USB-to-serial chip, so the RDU will show up as a serial device on
+ * Windows, Mac OS, or Linux.
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+
+#if HAVE_TERMIOS_H && HAVE_SYS_IOCTL_H && HAVE_MATH_H
+# include <termios.h>
+# include <sys/ioctl.h>
+# include <math.h>
+#else
+# error "No applicable input method."
+#endif
+
+#define EXPECTED_PACKAGE_LENGTH 278
+#define ESCAPE       0x10
+#define PKT_BEGIN    0x04
+#define PKT_END      0x03
+
+#define DEFAULT_DEVICE "/dev/ttyUSB0"
+
+static char *conf_device = NULL;
+static int   conf_retries = 0;
+
+static int fd = -1;
+
+static const char *config_keys[] =
+{
+    "Device",
+    "Retries"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static int ted_read_value(double *ret_power, double *ret_voltage)
+{
+    unsigned char receive_buffer[300];
+    unsigned char package_buffer[300];
+    char pkt_request[1] = {0xAA};
+    int package_buffer_pos;
+
+    fd_set input;
+    struct timeval timeout;
+
+    int end_flag;
+    int escape_flag;
+
+    int status;
+
+    assert (fd >= 0);
+
+    /* Initialize the input set*/
+    FD_ZERO (&input);
+    FD_SET (fd, &input);
+
+    /* Initialize timeout structure, set to 2 seconds */
+    memset (&timeout, 0, sizeof (timeout));
+    timeout.tv_sec = 2;
+    timeout.tv_usec = 0;
+
+    /* clear out anything in the buffer */
+    tcflush (fd, TCIFLUSH);
+
+    status = write (fd, pkt_request, sizeof(pkt_request));
+    if (status <= 0)
+    {
+        ERROR ("ted plugin: swrite failed.");
+        return (-1);
+    }
+
+    /* Loop until we find the end of the package */
+    end_flag = 0;
+    escape_flag = 0;
+    package_buffer_pos = 0;
+    while (end_flag == 0)
+    {
+        ssize_t receive_buffer_length;
+        ssize_t i;
+
+        /* check for timeout or input error*/
+        status = select (fd + 1, &input, NULL, NULL, &timeout);
+        if (status == 0) /* Timeout */
+        {
+            WARNING ("ted plugin: Timeout while waiting for file descriptor "
+                    "to become ready.");
+            return (-1);
+        }
+        else if ((status < 0) && ((errno == EAGAIN) || (errno == EINTR)))
+        {
+            /* Some signal or something. Start over.. */
+            continue;
+        }
+        else if (status < 0)
+        {
+            char errbuf[1024];
+            ERROR ("ted plugin: select failed: %s",
+                    sstrerror (errno, errbuf, sizeof (errbuf)));
+            return (-1);
+        }
+
+        receive_buffer_length = read (fd, receive_buffer, sizeof (receive_buffer));
+        if (receive_buffer_length < 0)
+        {
+            char errbuf[1024];
+            if ((errno == EAGAIN) || (errno == EINTR))
+                continue;
+            ERROR ("ted plugin: read(2) failed: %s",
+                    sstrerror (errno, errbuf, sizeof (errbuf)));
+            return (-1);
+        }
+        else if (receive_buffer_length == 0)
+        {
+            /* Should we close the FD in this case? */
+            WARNING ("ted plugin: Received EOF from file descriptor.");
+            return (-1);
+        }
+        else if (receive_buffer_length > sizeof (receive_buffer))
+        {
+            ERROR ("ted plugin: read(2) returned invalid value %zi.",
+                    receive_buffer_length);
+            return (-1);
+        }
+
+        /*
+         * packet filter loop
+         *
+         * Handle escape sequences in `receive_buffer' and put the
+         * result in `package_buffer'.
+         */
+        /* We need to see the begin sequence first. When we receive `ESCAPE
+         * PKT_BEGIN', we set `package_buffer_pos' to zero to signal that
+         * the beginning of the package has been found. */
+
+        escape_flag = 0;
+        for (i = 0; i < receive_buffer_length; i++)
+        {
+            /* Check if previous byte was the escape byte. */
+            if (escape_flag == 1)
+            {
+                escape_flag = 0;
+                /* escape escape = single escape */
+                if ((receive_buffer[i] == ESCAPE)
+                        && (package_buffer_pos >= 0))
+                {
+                    package_buffer[package_buffer_pos] = ESCAPE;
+                    package_buffer_pos++;
+                }
+                else if (receive_buffer[i] == PKT_BEGIN)
+                {
+                    package_buffer_pos = 0;
+                }
+                else if  (receive_buffer[i] == PKT_END)
+                {
+                    end_flag = 1;
+                    break;
+                }
+                else
+                {
+                    DEBUG ("ted plugin: Unknown escaped byte: %#x",
+                            (unsigned int) receive_buffer[i]);
+                }
+            }
+            else if (receive_buffer[i] == ESCAPE)
+            {
+                escape_flag = 1;
+            }
+            /* if we are in a package add byte to buffer
+             * otherwise throw away */
+            else if (package_buffer_pos >= 0)
+            {
+                package_buffer[package_buffer_pos] = receive_buffer[i];
+                package_buffer_pos++;
+            }
+        } /* for (i = 0; i < receive_buffer_length; i++) */
+    } /* while (end_flag == 0) */
+
+    /* Check for errors inside the loop. */
+    if ((end_flag == 0) || (package_buffer_pos != EXPECTED_PACKAGE_LENGTH))
+        return (-1);
+
+    /*
+     * Power is at positions 247 and 248 (LSB first) in [10kW].
+     * Voltage is at positions 251 and 252 (LSB first) in [.1V].
+     *
+     * Power is in 10 Watt steps
+     * Voltage is in volts
+     */
+    *ret_power = 10.0 * (double) ((((int) package_buffer[248]) * 256)
+            + ((int) package_buffer[247]));
+    *ret_voltage = 0.1 * (double) ((((int) package_buffer[252]) * 256)
+            + ((int) package_buffer[251]));
+
+    /* success */
+    return (0);
+} /* int ted_read_value */
+
+static int ted_open_device (void)
+{
+    const char *dev;
+    struct termios options;
+
+    if (fd >= 0)
+        return (0);
+
+    dev = DEFAULT_DEVICE;
+    if (conf_device != NULL)
+        dev = conf_device;
+
+    fd = open (dev, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK);
+    if (fd < 0)
+    {
+        ERROR ("ted plugin: Unable to open device %s.", dev);
+        return (-1);
+    }
+
+    /* Get the current options for the port... */
+    tcgetattr(fd, &options);
+    options.c_cflag = B19200 | CS8 | CSTOPB | CREAD | CLOCAL;
+    options.c_iflag = IGNBRK | IGNPAR;
+    options.c_oflag = 0;
+    options.c_lflag = 0;
+    options.c_cc[VTIME] = 20;
+    options.c_cc[VMIN]  = 250;
+
+    /* Set the new options for the port... */
+    tcflush(fd, TCIFLUSH);
+    tcsetattr(fd, TCSANOW, &options);
+
+    INFO ("ted plugin: Successfully opened %s.", dev);
+    return (0);
+} /* int ted_open_device */
+
+static void ted_submit (char *type, double value)
+{
+    value_t values[1];
+    value_list_t vl = VALUE_LIST_INIT;
+
+    values[0].gauge = value;
+
+    vl.values = values;
+    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));
+
+    plugin_dispatch_values (&vl);
+}
+
+static int ted_config (const char *key, const char *value)
+{
+    if (strcasecmp ("Device", key) == 0)
+    {
+        sfree (conf_device);
+        conf_device = sstrdup (value);
+    }
+    else if (strcasecmp ("Retries", key) == 0)
+    {
+        int tmp;
+
+        tmp = atoi (value);
+        if (tmp < 0)
+        {
+            WARNING ("ted plugin: Invalid retry count: %i", tmp);
+            return (1);
+        }
+        conf_retries = tmp;
+    }
+    else
+    {
+        ERROR ("ted plugin: Unknown config option: %s", key);
+        return (-1);
+    }
+
+    return (0);
+} /* int ted_config */
+
+static int ted_read (void)
+{
+    double power;
+    double voltage;
+    int status;
+    int i;
+
+    status = ted_open_device ();
+    if (status != 0)
+        return (-1);
+
+    power = NAN;
+    voltage = NAN;
+    for (i = 0; i <= conf_retries; i++)
+    {
+        status = ted_read_value (&power, &voltage);
+        if (status == 0)
+            break;
+    }
+
+    if (status != 0)
+        return (-1);
+
+    ted_submit ("power", power);
+    ted_submit ("voltage", voltage);
+
+    return (0);
+} /* int ted_read */
+
+static int ted_shutdown (void)
+{
+    if (fd >= 0)
+    {
+        close (fd);
+        fd = -1;
+    }
+
+    return (0);
+} /* int ted_shutdown */
+
+void module_register (void)
+{
+    plugin_register_config ("ted", ted_config,
+            config_keys, config_keys_num);
+    plugin_register_read ("ted", ted_read);
+    plugin_register_shutdown ("ted", ted_shutdown);
+} /* void module_register */
+
+/* vim: set sw=4 et : */
diff --git a/src/thermal.c b/src/thermal.c
new file mode 100644 (file)
index 0000000..0ad0d90
--- /dev/null
@@ -0,0 +1,257 @@
+/**
+ * collectd - src/thermal.c
+ * Copyright (C) 2008  Michał Mirosław
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Michał Mirosław <mirq-linux at rere.qmqm.pl>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+#include "utils_ignorelist.h"
+
+#if !KERNEL_LINUX
+# error "This module is for Linux only."
+#endif
+
+static const char *config_keys[] = {
+       "Device",
+       "IgnoreSelected",
+       "ForceUseProcfs"
+};
+
+const char *const dirname_sysfs = "/sys/class/thermal";
+const char *const dirname_procfs = "/proc/acpi/thermal_zone";
+
+static _Bool force_procfs = 0;
+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_list_t vl = VALUE_LIST_INIT;
+       value_t v;
+
+       v.gauge = value;
+       vl.values = &v;
+
+       sstrncpy (vl.plugin, "thermal", sizeof(vl.plugin));
+       if (plugin_instance != NULL)
+               sstrncpy (vl.plugin_instance, plugin_instance,
+                               sizeof (vl.plugin_instance));
+       sstrncpy (vl.type,
+                       (dt == TEMP) ? "temperature" : "gauge",
+                       sizeof (vl.type));
+
+       plugin_dispatch_values (&vl);
+}
+
+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;
+       _Bool success = 0;
+
+       if (device_list && ignorelist_match (device_list, name))
+               return -1;
+
+       len = snprintf (filename, sizeof (filename),
+                       "%s/%s/temp", dirname_sysfs, name);
+       if ((len < 0) || ((size_t) len >= sizeof (filename)))
+               return -1;
+
+       len = 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;
+               }
+       }
+
+       len = snprintf (filename, sizeof (filename),
+                       "%s/%s/cur_state", dirname_sysfs, name);
+       if ((len < 0) || ((size_t) len >= sizeof (filename)))
+               return -1;
+
+       len = 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;
+               }
+       }
+
+       return (success ? 0 : -1);
+}
+
+static int thermal_procfs_device_read (const char __attribute__((unused)) *dir,
+               const char *name, void __attribute__((unused)) *user_data)
+{
+       const char str_temp[] = "temperature:";
+       char filename[256];
+       char data[1024];
+       int len;
+
+       if (device_list && ignorelist_match (device_list, name))
+               return -1;
+
+       /**
+        * rechot ~ # cat /proc/acpi/thermal_zone/THRM/temperature
+        * temperature:             55 C
+        */
+       
+       len = snprintf (filename, sizeof (filename),
+                       "%s/%s/temperature", dirname_procfs, name);
+       if ((len < 0) || ((size_t) len >= sizeof (filename)))
+               return -1;
+
+       len = read_file_contents (filename, data, sizeof(data));
+       if ((len > 0) && ((size_t) len > sizeof(str_temp))
+                       && (data[--len] == '\n')
+                       && (! strncmp(data, str_temp, sizeof(str_temp)-1))) {
+               char *endptr = NULL;
+               double temp;
+               double factor, add;
+               
+               if (data[--len] == 'C') {
+                       add = 0;
+                       factor = 1.0;
+               } else if (data[len] == 'F') {
+                       add = -32;
+                       factor = 5.0/9.0;
+               } else if (data[len] == 'K') {
+                       add = -273.15;
+                       factor = 1.0;
+               } else
+                       return -1;
+
+               while (len > 0 && data[--len] == ' ')
+                       ;
+               data[len + 1] = 0;
+
+               while (len > 0 && data[--len] != ' ')
+                       ;
+               ++len;
+
+               errno = 0;
+               temp = (strtod (data + len, &endptr) + add) * factor;
+
+               if (endptr != data + len && errno == 0) {
+                       thermal_submit(name, TEMP, temp);
+                       return 0;
+               }
+       }
+
+       return -1;
+}
+
+static int thermal_config (const char *key, const char *value)
+{
+       if (device_list == NULL)
+               device_list = ignorelist_create (1);
+
+       if (strcasecmp (key, "Device") == 0)
+       {
+               if (ignorelist_add (device_list, value))
+               {
+                       ERROR ("thermal plugin: "
+                                       "Cannot add value to ignorelist.");
+                       return 1;
+               }
+       }
+       else if (strcasecmp (key, "IgnoreSelected") == 0)
+       {
+               ignorelist_set_invert (device_list, 1);
+               if (IS_TRUE (value))
+                       ignorelist_set_invert (device_list, 0);
+       }
+       else if (strcasecmp (key, "ForceUseProcfs") == 0)
+       {
+               force_procfs = 0;
+               if (IS_TRUE (value))
+                       force_procfs = 1;
+       }
+       else
+       {
+               return -1;
+       }
+
+       return 0;
+}
+
+static int thermal_sysfs_read (void)
+{
+       return walk_directory (dirname_sysfs, thermal_sysfs_device_read,
+                       /* user_data = */ NULL, /* include hidden */ 0);
+}
+
+static int thermal_procfs_read (void)
+{
+       return walk_directory (dirname_procfs, thermal_procfs_device_read,
+                       /* user_data = */ NULL, /* include hidden */ 0);
+}
+
+static int thermal_init (void)
+{
+       int ret = -1;
+
+       if (!force_procfs && access (dirname_sysfs, R_OK | X_OK) == 0) {
+               ret = plugin_register_read ("thermal", thermal_sysfs_read);
+       } else if (access (dirname_procfs, R_OK | X_OK) == 0) {
+               ret = plugin_register_read ("thermal", thermal_procfs_read);
+       }
+
+       return ret;
+}
+
+static int thermal_shutdown (void)
+{
+       ignorelist_free (device_list);
+
+       return 0;
+}
+
+void module_register (void)
+{
+       plugin_register_config ("thermal", thermal_config,
+                       config_keys, STATIC_ARRAY_SIZE(config_keys));
+       plugin_register_init ("thermal", thermal_init);
+       plugin_register_shutdown ("thermal", thermal_shutdown);
+}
+
diff --git a/src/threshold.c b/src/threshold.c
new file mode 100644 (file)
index 0000000..d4cfd6e
--- /dev/null
@@ -0,0 +1,1030 @@
+/**
+ * collectd - src/threshold.c
+ * Copyright (C) 2007-2010  Florian Forster
+ * Copyright (C) 2008-2009  Sebastian Harl
+ * Copyright (C) 2009       Andrés J. Díaz
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Florian octo Forster <octo at collectd.org>
+ *   Sebastian Harl <sh at tokkee.org>
+ *   Andrés J. Díaz <ajdiaz at connectical.com>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "utils_avltree.h"
+#include "utils_cache.h"
+
+#include <assert.h>
+#include <pthread.h>
+
+/*
+ * Private data structures
+ * {{{ */
+#define UT_FLAG_INVERT  0x01
+#define UT_FLAG_PERSIST 0x02
+#define UT_FLAG_PERCENTAGE 0x04
+#define UT_FLAG_INTERESTING 0x08
+#define UT_FLAG_PERSIST_OK 0x10
+typedef struct threshold_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];
+  char data_source[DATA_MAX_NAME_LEN];
+  gauge_t warning_min;
+  gauge_t warning_max;
+  gauge_t failure_min;
+  gauge_t failure_max;
+  gauge_t hysteresis;
+  unsigned int flags;
+  int hits;
+  struct threshold_s *next;
+} threshold_t;
+/* }}} */
+
+/*
+ * Private (static) variables
+ * {{{ */
+static c_avl_tree_t   *threshold_tree = NULL;
+static pthread_mutex_t threshold_lock = PTHREAD_MUTEX_INITIALIZER;
+/* }}} */
+
+/*
+ * Threshold management
+ * ====================
+ * The following functions add, delete, search, etc. configured thresholds to
+ * the underlying AVL trees.
+ */
+/*
+ * threshold_t *threshold_get
+ *
+ * Retrieve one specific threshold configuration. For looking up a threshold
+ * matching a value_list_t, see "threshold_search" below. Returns NULL if the
+ * specified threshold doesn't exist.
+ */
+static threshold_t *threshold_get (const char *hostname,
+    const char *plugin, const char *plugin_instance,
+    const char *type, const char *type_instance)
+{ /* {{{ */
+  char name[6 * DATA_MAX_NAME_LEN];
+  threshold_t *th = NULL;
+
+  format_name (name, sizeof (name),
+      (hostname == NULL) ? "" : hostname,
+      (plugin == NULL) ? "" : plugin, plugin_instance,
+      (type == NULL) ? "" : type, type_instance);
+  name[sizeof (name) - 1] = '\0';
+
+  if (c_avl_get (threshold_tree, name, (void *) &th) == 0)
+    return (th);
+  else
+    return (NULL);
+} /* }}} threshold_t *threshold_get */
+
+/*
+ * int ut_threshold_add
+ *
+ * Adds a threshold configuration to the list of thresholds. The threshold_t
+ * structure is copied and may be destroyed after this call. Returns zero on
+ * success, non-zero otherwise.
+ */
+static int ut_threshold_add (const threshold_t *th)
+{ /* {{{ */
+  char name[6 * DATA_MAX_NAME_LEN];
+  char *name_copy;
+  threshold_t *th_copy;
+  threshold_t *th_ptr;
+  int status = 0;
+
+  if (format_name (name, sizeof (name), th->host,
+       th->plugin, th->plugin_instance,
+       th->type, th->type_instance) != 0)
+  {
+    ERROR ("ut_threshold_add: format_name failed.");
+    return (-1);
+  }
+
+  name_copy = strdup (name);
+  if (name_copy == NULL)
+  {
+    ERROR ("ut_threshold_add: strdup failed.");
+    return (-1);
+  }
+
+  th_copy = (threshold_t *) malloc (sizeof (threshold_t));
+  if (th_copy == NULL)
+  {
+    sfree (name_copy);
+    ERROR ("ut_threshold_add: malloc failed.");
+    return (-1);
+  }
+  memcpy (th_copy, th, sizeof (threshold_t));
+  th_ptr = NULL;
+
+  DEBUG ("ut_threshold_add: Adding entry `%s'", name);
+
+  pthread_mutex_lock (&threshold_lock);
+
+  th_ptr = threshold_get (th->host, th->plugin, th->plugin_instance,
+      th->type, th->type_instance);
+
+  while ((th_ptr != NULL) && (th_ptr->next != NULL))
+    th_ptr = th_ptr->next;
+
+  if (th_ptr == NULL) /* no such threshold yet */
+  {
+    status = c_avl_insert (threshold_tree, name_copy, th_copy);
+  }
+  else /* th_ptr points to the last threshold in the list */
+  {
+    th_ptr->next = th_copy;
+    /* name_copy isn't needed */
+    sfree (name_copy);
+  }
+
+  pthread_mutex_unlock (&threshold_lock);
+
+  if (status != 0)
+  {
+    ERROR ("ut_threshold_add: c_avl_insert (%s) failed.", name);
+    sfree (name_copy);
+    sfree (th_copy);
+  }
+
+  return (status);
+} /* }}} int ut_threshold_add */
+
+/* 
+ * threshold_t *threshold_search
+ *
+ * Searches for a threshold configuration using all the possible variations of
+ * "Host", "Plugin" and "Type" blocks. Returns NULL if no threshold could be
+ * found.
+ * XXX: This is likely the least efficient function in collectd.
+ */
+static threshold_t *threshold_search (const value_list_t *vl)
+{ /* {{{ */
+  threshold_t *th;
+
+  if ((th = threshold_get (vl->host, vl->plugin, vl->plugin_instance,
+         vl->type, vl->type_instance)) != NULL)
+    return (th);
+  else if ((th = threshold_get (vl->host, vl->plugin, vl->plugin_instance,
+         vl->type, NULL)) != NULL)
+    return (th);
+  else if ((th = threshold_get (vl->host, vl->plugin, NULL,
+         vl->type, vl->type_instance)) != NULL)
+    return (th);
+  else if ((th = threshold_get (vl->host, vl->plugin, NULL,
+         vl->type, NULL)) != NULL)
+    return (th);
+  else if ((th = threshold_get (vl->host, "", NULL,
+         vl->type, vl->type_instance)) != NULL)
+    return (th);
+  else if ((th = threshold_get (vl->host, "", NULL,
+         vl->type, NULL)) != NULL)
+    return (th);
+  else if ((th = threshold_get ("", vl->plugin, vl->plugin_instance,
+         vl->type, vl->type_instance)) != NULL)
+    return (th);
+  else if ((th = threshold_get ("", vl->plugin, vl->plugin_instance,
+         vl->type, NULL)) != NULL)
+    return (th);
+  else if ((th = threshold_get ("", vl->plugin, NULL,
+         vl->type, vl->type_instance)) != NULL)
+    return (th);
+  else if ((th = threshold_get ("", vl->plugin, NULL,
+         vl->type, NULL)) != NULL)
+    return (th);
+  else if ((th = threshold_get ("", "", NULL,
+         vl->type, vl->type_instance)) != NULL)
+    return (th);
+  else if ((th = threshold_get ("", "", NULL,
+         vl->type, NULL)) != NULL)
+    return (th);
+
+  return (NULL);
+} /* }}} threshold_t *threshold_search */
+
+/*
+ * Configuration
+ * =============
+ * The following approximately two hundred functions are used to handle the
+ * configuration and fill the threshold list.
+ * {{{ */
+static int ut_config_type_datasource (threshold_t *th, oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("threshold values: The `DataSource' option needs exactly one "
+       "string argument.");
+    return (-1);
+  }
+
+  sstrncpy (th->data_source, ci->values[0].value.string,
+      sizeof (th->data_source));
+
+  return (0);
+} /* int ut_config_type_datasource */
+
+static int ut_config_type_instance (threshold_t *th, oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("threshold values: The `Instance' option needs exactly one "
+       "string argument.");
+    return (-1);
+  }
+
+  sstrncpy (th->type_instance, ci->values[0].value.string,
+      sizeof (th->type_instance));
+
+  return (0);
+} /* int ut_config_type_instance */
+
+static int ut_config_type_max (threshold_t *th, oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
+  {
+    WARNING ("threshold values: The `%s' option needs exactly one "
+       "number argument.", ci->key);
+    return (-1);
+  }
+
+  if (strcasecmp (ci->key, "WarningMax") == 0)
+    th->warning_max = ci->values[0].value.number;
+  else
+    th->failure_max = ci->values[0].value.number;
+
+  return (0);
+} /* int ut_config_type_max */
+
+static int ut_config_type_min (threshold_t *th, oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
+  {
+    WARNING ("threshold values: The `%s' option needs exactly one "
+       "number argument.", ci->key);
+    return (-1);
+  }
+
+  if (strcasecmp (ci->key, "WarningMin") == 0)
+    th->warning_min = ci->values[0].value.number;
+  else
+    th->failure_min = ci->values[0].value.number;
+
+  return (0);
+} /* int ut_config_type_min */
+
+static int ut_config_type_hits (threshold_t *th, oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
+  {
+    WARNING ("threshold values: The `%s' option needs exactly one "
+      "number argument.", ci->key);
+    return (-1);
+  }
+
+  th->hits = ci->values[0].value.number;
+
+  return (0);
+} /* int ut_config_type_hits */
+
+static int ut_config_type_hysteresis (threshold_t *th, oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
+  {
+    WARNING ("threshold values: The `%s' option needs exactly one "
+      "number argument.", ci->key);
+    return (-1);
+  }
+
+  th->hysteresis = ci->values[0].value.number;
+
+  return (0);
+} /* int ut_config_type_hysteresis */
+
+static int ut_config_type (const threshold_t *th_orig, oconfig_item_t *ci)
+{
+  int i;
+  threshold_t th;
+  int status = 0;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("threshold values: The `Type' block needs exactly one string "
+       "argument.");
+    return (-1);
+  }
+
+  if (ci->children_num < 1)
+  {
+    WARNING ("threshold values: The `Type' block needs at least one option.");
+    return (-1);
+  }
+
+  memcpy (&th, th_orig, sizeof (th));
+  sstrncpy (th.type, ci->values[0].value.string, sizeof (th.type));
+
+  th.warning_min = NAN;
+  th.warning_max = NAN;
+  th.failure_min = NAN;
+  th.failure_max = NAN;
+  th.hits = 0;
+  th.hysteresis = 0;
+  th.flags = UT_FLAG_INTERESTING; /* interesting by default */
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+    status = 0;
+
+    if (strcasecmp ("Instance", option->key) == 0)
+      status = ut_config_type_instance (&th, option);
+    else if (strcasecmp ("DataSource", option->key) == 0)
+      status = ut_config_type_datasource (&th, option);
+    else if ((strcasecmp ("WarningMax", option->key) == 0)
+       || (strcasecmp ("FailureMax", option->key) == 0))
+      status = ut_config_type_max (&th, option);
+    else if ((strcasecmp ("WarningMin", option->key) == 0)
+       || (strcasecmp ("FailureMin", option->key) == 0))
+      status = ut_config_type_min (&th, option);
+    else if (strcasecmp ("Interesting", option->key) == 0)
+      status = cf_util_get_flag (option, &th.flags, UT_FLAG_INTERESTING);
+    else if (strcasecmp ("Invert", option->key) == 0)
+      status = cf_util_get_flag (option, &th.flags, UT_FLAG_INVERT);
+    else if (strcasecmp ("Persist", option->key) == 0)
+      status = cf_util_get_flag (option, &th.flags, UT_FLAG_PERSIST);
+    else if (strcasecmp ("PersistOK", option->key) == 0)
+      status = cf_util_get_flag (option, &th.flags, UT_FLAG_PERSIST_OK);
+    else if (strcasecmp ("Percentage", option->key) == 0)
+      status = cf_util_get_flag (option, &th.flags, UT_FLAG_PERCENTAGE);
+    else if (strcasecmp ("Hits", option->key) == 0)
+      status = ut_config_type_hits (&th, option);
+    else if (strcasecmp ("Hysteresis", option->key) == 0)
+      status = ut_config_type_hysteresis (&th, option);
+    else
+    {
+      WARNING ("threshold values: Option `%s' not allowed inside a `Type' "
+         "block.", option->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  if (status == 0)
+  {
+    status = ut_threshold_add (&th);
+  }
+
+  return (status);
+} /* int ut_config_type */
+
+static int ut_config_plugin_instance (threshold_t *th, oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("threshold values: The `Instance' option needs exactly one "
+       "string argument.");
+    return (-1);
+  }
+
+  sstrncpy (th->plugin_instance, ci->values[0].value.string,
+      sizeof (th->plugin_instance));
+
+  return (0);
+} /* int ut_config_plugin_instance */
+
+static int ut_config_plugin (const threshold_t *th_orig, oconfig_item_t *ci)
+{
+  int i;
+  threshold_t th;
+  int status = 0;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("threshold values: The `Plugin' block needs exactly one string "
+       "argument.");
+    return (-1);
+  }
+
+  if (ci->children_num < 1)
+  {
+    WARNING ("threshold values: The `Plugin' block needs at least one nested "
+       "block.");
+    return (-1);
+  }
+
+  memcpy (&th, th_orig, sizeof (th));
+  sstrncpy (th.plugin, ci->values[0].value.string, sizeof (th.plugin));
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+    status = 0;
+
+    if (strcasecmp ("Type", option->key) == 0)
+      status = ut_config_type (&th, option);
+    else if (strcasecmp ("Instance", option->key) == 0)
+      status = ut_config_plugin_instance (&th, option);
+    else
+    {
+      WARNING ("threshold values: Option `%s' not allowed inside a `Plugin' "
+         "block.", option->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  return (status);
+} /* int ut_config_plugin */
+
+static int ut_config_host (const threshold_t *th_orig, oconfig_item_t *ci)
+{
+  int i;
+  threshold_t th;
+  int status = 0;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("threshold values: The `Host' block needs exactly one string "
+       "argument.");
+    return (-1);
+  }
+
+  if (ci->children_num < 1)
+  {
+    WARNING ("threshold values: The `Host' block needs at least one nested "
+       "block.");
+    return (-1);
+  }
+
+  memcpy (&th, th_orig, sizeof (th));
+  sstrncpy (th.host, ci->values[0].value.string, sizeof (th.host));
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+    status = 0;
+
+    if (strcasecmp ("Type", option->key) == 0)
+      status = ut_config_type (&th, option);
+    else if (strcasecmp ("Plugin", option->key) == 0)
+      status = ut_config_plugin (&th, option);
+    else
+    {
+      WARNING ("threshold values: Option `%s' not allowed inside a `Host' "
+         "block.", option->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  return (status);
+} /* int ut_config_host */
+/*
+ * End of the functions used to configure threshold values.
+ */
+/* }}} */
+
+/*
+ * int ut_report_state
+ *
+ * Checks if the `state' differs from the old state and creates a notification
+ * if appropriate.
+ * Does not fail.
+ */
+static int ut_report_state (const data_set_t *ds,
+    const value_list_t *vl,
+    const threshold_t *th,
+    const gauge_t *values,
+    int ds_index,
+    int state)
+{ /* {{{ */
+  int state_old;
+  notification_t n;
+
+  char *buf;
+  size_t bufsize;
+
+  int status;
+
+  /* Check if hits matched */
+  if ( (th->hits != 0) )
+  {
+    int hits = uc_get_hits(ds,vl);
+    /* STATE_OKAY resets hits unless PERSIST_OK flag is set. Hits resets if
+     * threshold is hit. */
+    if ( ( (state == STATE_OKAY) && ((th->flags & UT_FLAG_PERSIST_OK) == 0) ) || (hits > th->hits) )
+    {
+        DEBUG("ut_report_state: reset uc_get_hits = 0");
+        uc_set_hits(ds,vl,0); /* reset hit counter and notify */
+    } else {
+      DEBUG("ut_report_state: th->hits = %d, uc_get_hits = %d",th->hits,uc_get_hits(ds,vl));
+      (void) uc_inc_hits(ds,vl,1); /* increase hit counter */
+      return (0);
+    }
+  } /* end check hits */
+
+  state_old = uc_get_state (ds, vl);
+
+  /* If the state didn't change, report if `persistent' is specified. If the
+   * state is `okay', then only report if `persist_ok` flag is set. */
+  if (state == state_old)
+  {
+    if ((th->flags & UT_FLAG_PERSIST) == 0)
+      return (0);
+    else if ( (state == STATE_OKAY) && ((th->flags & UT_FLAG_PERSIST_OK) == 0) )
+      return (0);
+  }
+
+  if (state != state_old)
+    uc_set_state (ds, vl, state);
+
+  NOTIFICATION_INIT_VL (&n, vl);
+
+  buf = n.message;
+  bufsize = sizeof (n.message);
+
+  if (state == STATE_OKAY)
+    n.severity = NOTIF_OKAY;
+  else if (state == STATE_WARNING)
+    n.severity = NOTIF_WARNING;
+  else
+    n.severity = NOTIF_FAILURE;
+
+  n.time = vl->time;
+
+  status = ssnprintf (buf, bufsize, "Host %s, plugin %s",
+      vl->host, vl->plugin);
+  buf += status;
+  bufsize -= status;
+
+  if (vl->plugin_instance[0] != '\0')
+  {
+    status = ssnprintf (buf, bufsize, " (instance %s)",
+       vl->plugin_instance);
+    buf += status;
+    bufsize -= status;
+  }
+
+  status = ssnprintf (buf, bufsize, " type %s", vl->type);
+  buf += status;
+  bufsize -= status;
+
+  if (vl->type_instance[0] != '\0')
+  {
+    status = ssnprintf (buf, bufsize, " (instance %s)",
+       vl->type_instance);
+    buf += status;
+    bufsize -= status;
+  }
+
+  plugin_notification_meta_add_string (&n, "DataSource",
+      ds->ds[ds_index].name);
+  plugin_notification_meta_add_double (&n, "CurrentValue", values[ds_index]);
+  plugin_notification_meta_add_double (&n, "WarningMin", th->warning_min);
+  plugin_notification_meta_add_double (&n, "WarningMax", th->warning_max);
+  plugin_notification_meta_add_double (&n, "FailureMin", th->failure_min);
+  plugin_notification_meta_add_double (&n, "FailureMax", th->failure_max);
+
+  /* Send an okay notification */
+  if (state == STATE_OKAY)
+  {
+    if (state_old == STATE_MISSING)
+      status = ssnprintf (buf, bufsize,
+          ": Value is no longer missing.");
+    else
+      status = ssnprintf (buf, bufsize,
+          ": All data sources are within range again.");
+    buf += status;
+    bufsize -= status;
+  }
+  else
+  {
+    double min;
+    double max;
+
+    min = (state == STATE_ERROR) ? th->failure_min : th->warning_min;
+    max = (state == STATE_ERROR) ? th->failure_max : th->warning_max;
+
+    if (th->flags & UT_FLAG_INVERT)
+    {
+      if (!isnan (min) && !isnan (max))
+      {
+        status = ssnprintf (buf, bufsize, ": Data source \"%s\" is currently "
+            "%f. That is within the %s region of %f%s and %f%s.",
+            ds->ds[ds_index].name, values[ds_index],
+            (state == STATE_ERROR) ? "failure" : "warning",
+            min, ((th->flags & UT_FLAG_PERCENTAGE) != 0) ? "%" : "",
+            max, ((th->flags & UT_FLAG_PERCENTAGE) != 0) ? "%" : "");
+      }
+      else
+      {
+       status = ssnprintf (buf, bufsize, ": Data source \"%s\" is currently "
+           "%f. That is %s the %s threshold of %f%s.",
+           ds->ds[ds_index].name, values[ds_index],
+           isnan (min) ? "below" : "above",
+           (state == STATE_ERROR) ? "failure" : "warning",
+           isnan (min) ? max : min,
+           ((th->flags & UT_FLAG_PERCENTAGE) != 0) ? "%" : "");
+      }
+    }
+    else if (th->flags & UT_FLAG_PERCENTAGE)
+    {
+      gauge_t value;
+      gauge_t sum;
+      int i;
+
+      sum = 0.0;
+      for (i = 0; i < vl->values_len; i++)
+      {
+        if (isnan (values[i]))
+          continue;
+
+        sum += values[i];
+      }
+
+      if (sum == 0.0)
+        value = NAN;
+      else
+        value = 100.0 * values[ds_index] / sum;
+
+      status = ssnprintf (buf, bufsize, ": Data source \"%s\" is currently "
+          "%g (%.2f%%). That is %s the %s threshold of %.2f%%.",
+          ds->ds[ds_index].name, values[ds_index], value,
+          (value < min) ? "below" : "above",
+          (state == STATE_ERROR) ? "failure" : "warning",
+          (value < min) ? min : max);
+    }
+    else /* is not inverted */
+    {
+      status = ssnprintf (buf, bufsize, ": Data source \"%s\" is currently "
+         "%f. That is %s the %s threshold of %f.",
+         ds->ds[ds_index].name, values[ds_index],
+         (values[ds_index] < min) ? "below" : "above",
+         (state == STATE_ERROR) ? "failure" : "warning",
+         (values[ds_index] < min) ? min : max);
+    }
+    buf += status;
+    bufsize -= status;
+  }
+
+  plugin_dispatch_notification (&n);
+
+  plugin_notification_meta_free (n.meta);
+  return (0);
+} /* }}} int ut_report_state */
+
+/*
+ * int ut_check_one_data_source
+ *
+ * Checks one data source against the given threshold configuration. If the
+ * `DataSource' option is set in the threshold, and the name does NOT match,
+ * `okay' is returned. If the threshold does match, its failure and warning
+ * min and max values are checked and `failure' or `warning' is returned if
+ * appropriate.
+ * Does not fail.
+ */
+static int ut_check_one_data_source (const data_set_t *ds,
+    const value_list_t __attribute__((unused)) *vl,
+    const threshold_t *th,
+    const gauge_t *values,
+    int ds_index)
+{ /* {{{ */
+  const char *ds_name;
+  int is_warning = 0;
+  int is_failure = 0;
+  int prev_state = STATE_OKAY;
+
+  /* check if this threshold applies to this data source */
+  if (ds != NULL)
+  {
+    ds_name = ds->ds[ds_index].name;
+    if ((th->data_source[0] != 0)
+       && (strcmp (ds_name, th->data_source) != 0))
+      return (STATE_OKAY);
+  }
+
+  if ((th->flags & UT_FLAG_INVERT) != 0)
+  {
+    is_warning--;
+    is_failure--;
+  }
+
+  /* XXX: This is an experimental code, not optimized, not fast, not reliable,
+   * and probably, do not work as you expect. Enjoy! :D */
+  if ( (th->hysteresis > 0) && ((prev_state = uc_get_state(ds,vl)) != STATE_OKAY) )
+  {
+    switch(prev_state)
+    {
+      case STATE_ERROR:
+       if ( (!isnan (th->failure_min) && ((th->failure_min + th->hysteresis) < values[ds_index])) ||
+            (!isnan (th->failure_max) && ((th->failure_max - th->hysteresis) > values[ds_index])) )
+         return (STATE_OKAY);
+       else
+         is_failure++;
+      case STATE_WARNING:
+       if ( (!isnan (th->warning_min) && ((th->warning_min + th->hysteresis) < values[ds_index])) ||
+            (!isnan (th->warning_max) && ((th->warning_max - th->hysteresis) > values[ds_index])) )
+         return (STATE_OKAY);
+       else
+         is_warning++;
+     }
+  }
+  else { /* no hysteresis */
+    if ((!isnan (th->failure_min) && (th->failure_min > values[ds_index]))
+       || (!isnan (th->failure_max) && (th->failure_max < values[ds_index])))
+      is_failure++;
+
+    if ((!isnan (th->warning_min) && (th->warning_min > values[ds_index]))
+       || (!isnan (th->warning_max) && (th->warning_max < values[ds_index])))
+      is_warning++;
+ }
+
+  if (is_failure != 0)
+    return (STATE_ERROR);
+
+  if (is_warning != 0)
+    return (STATE_WARNING);
+
+  return (STATE_OKAY);
+} /* }}} int ut_check_one_data_source */
+
+/*
+ * int ut_check_one_threshold
+ *
+ * Checks all data sources of a value list against the given threshold, using
+ * the ut_check_one_data_source function above. Returns the worst status,
+ * which is `okay' if nothing has failed.
+ * Returns less than zero if the data set doesn't have any data sources.
+ */
+static int ut_check_one_threshold (const data_set_t *ds,
+    const value_list_t *vl,
+    const threshold_t *th,
+    const gauge_t *values,
+    int *ret_ds_index)
+{ /* {{{ */
+  int ret = -1;
+  int ds_index = -1;
+  int i;
+  gauge_t values_copy[ds->ds_num];
+
+  memcpy (values_copy, values, sizeof (values_copy));
+
+  if ((th->flags & UT_FLAG_PERCENTAGE) != 0)
+  {
+    int num = 0;
+    gauge_t sum=0.0;
+
+    if (ds->ds_num == 1)
+    {
+      WARNING ("ut_check_one_threshold: The %s type has only one data "
+          "source, but you have configured to check this as a percentage. "
+          "That doesn't make much sense, because the percentage will always "
+          "be 100%%!", ds->type);
+    }
+
+    /* Prepare `sum' and `num'. */
+    for (i = 0; i < ds->ds_num; i++)
+      if (!isnan (values[i]))
+      {
+        num++;
+       sum += values[i];
+      }
+
+    if ((num == 0) /* All data sources are undefined. */
+        || (sum == 0.0)) /* Sum is zero, cannot calculate percentage. */
+    {
+      for (i = 0; i < ds->ds_num; i++)
+        values_copy[i] = NAN;
+    }
+    else /* We can actually calculate the percentage. */
+    {
+      for (i = 0; i < ds->ds_num; i++)
+        values_copy[i] = 100.0 * values[i] / sum;
+    }
+  } /* if (UT_FLAG_PERCENTAGE) */
+
+  for (i = 0; i < ds->ds_num; i++)
+  {
+    int status;
+
+    status = ut_check_one_data_source (ds, vl, th, values_copy, i);
+    if (ret < status)
+    {
+      ret = status;
+      ds_index = i;
+    }
+  } /* for (ds->ds_num) */
+
+  if (ret_ds_index != NULL)
+    *ret_ds_index = ds_index;
+
+  return (ret);
+} /* }}} int ut_check_one_threshold */
+
+/*
+ * int ut_check_threshold
+ *
+ * Gets a list of matching thresholds and searches for the worst status by one
+ * of the thresholds. Then reports that status using the ut_report_state
+ * function above. 
+ * Returns zero on success and if no threshold has been configured. Returns
+ * less than zero on failure.
+ */
+static int ut_check_threshold (const data_set_t *ds, const value_list_t *vl,
+    __attribute__((unused)) user_data_t *ud)
+{ /* {{{ */
+  threshold_t *th;
+  gauge_t *values;
+  int status;
+
+  int worst_state = -1;
+  threshold_t *worst_th = NULL;
+  int worst_ds_index = -1;
+
+  if (threshold_tree == NULL)
+    return (0);
+
+  /* Is this lock really necessary? So far, thresholds are only inserted at
+   * startup. -octo */
+  pthread_mutex_lock (&threshold_lock);
+  th = threshold_search (vl);
+  pthread_mutex_unlock (&threshold_lock);
+  if (th == NULL)
+    return (0);
+
+  DEBUG ("ut_check_threshold: Found matching threshold(s)");
+
+  values = uc_get_rate (ds, vl);
+  if (values == NULL)
+    return (0);
+
+  while (th != NULL)
+  {
+    int ds_index = -1;
+
+    status = ut_check_one_threshold (ds, vl, th, values, &ds_index);
+    if (status < 0)
+    {
+      ERROR ("ut_check_threshold: ut_check_one_threshold failed.");
+      sfree (values);
+      return (-1);
+    }
+
+    if (worst_state < status)
+    {
+      worst_state = status;
+      worst_th = th;
+      worst_ds_index = ds_index;
+    }
+
+    th = th->next;
+  } /* while (th) */
+
+  status = ut_report_state (ds, vl, worst_th, values,
+      worst_ds_index, worst_state);
+  if (status != 0)
+  {
+    ERROR ("ut_check_threshold: ut_report_state failed.");
+    sfree (values);
+    return (-1);
+  }
+
+  sfree (values);
+
+  return (0);
+} /* }}} int ut_check_threshold */
+
+/*
+ * int ut_missing
+ *
+ * This function is called whenever a value goes "missing".
+ */
+static int ut_missing (const value_list_t *vl,
+    __attribute__((unused)) user_data_t *ud)
+{ /* {{{ */
+  threshold_t *th;
+  cdtime_t missing_time;
+  char identifier[6 * DATA_MAX_NAME_LEN];
+  notification_t n;
+
+  /* dispatch notifications for "interesting" values only */
+  if (threshold_tree == NULL)
+    return (0);
+
+  th = threshold_search (vl);
+  if (th == NULL)
+    return (0);
+
+  missing_time = cdtime () - vl->time;
+  FORMAT_VL (identifier, sizeof (identifier), vl);
+
+  NOTIFICATION_INIT_VL (&n, vl);
+  ssnprintf (n.message, sizeof (n.message),
+      "%s has not been updated for %.3f seconds.",
+      identifier, CDTIME_T_TO_DOUBLE (missing_time));
+
+  plugin_dispatch_notification (&n);
+
+  return (0);
+} /* }}} int ut_missing */
+
+int ut_config (oconfig_item_t *ci)
+{ /* {{{ */
+  int i;
+  int status = 0;
+
+  threshold_t th;
+
+  if (threshold_tree == NULL)
+  {
+    threshold_tree = c_avl_create ((void *) strcmp);
+    if (threshold_tree == NULL)
+    {
+      ERROR ("ut_config: c_avl_create failed.");
+      return (-1);
+    }
+  }
+
+  memset (&th, '\0', sizeof (th));
+  th.warning_min = NAN;
+  th.warning_max = NAN;
+  th.failure_min = NAN;
+  th.failure_max = NAN;
+
+  th.hits = 0;
+  th.hysteresis = 0;
+  th.flags = UT_FLAG_INTERESTING; /* interesting by default */
+    
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+    status = 0;
+
+    if (strcasecmp ("Type", option->key) == 0)
+      status = ut_config_type (&th, option);
+    else if (strcasecmp ("Plugin", option->key) == 0)
+      status = ut_config_plugin (&th, option);
+    else if (strcasecmp ("Host", option->key) == 0)
+      status = ut_config_host (&th, option);
+    else
+    {
+      WARNING ("threshold values: Option `%s' not allowed here.", option->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  if (c_avl_size (threshold_tree) > 0) {
+    plugin_register_missing ("threshold", ut_missing,
+        /* user data = */ NULL);
+    plugin_register_write ("threshold", ut_check_threshold,
+        /* user data = */ NULL);
+  }
+
+  return (status);
+} /* }}} int um_config */
+
+void module_register (void)
+{
+  plugin_register_complex_config ("threshold", ut_config);
+}
+
+/* vim: set sw=2 ts=8 sts=2 tw=78 et fdm=marker : */
diff --git a/src/tokyotyrant.c b/src/tokyotyrant.c
new file mode 100644 (file)
index 0000000..678a341
--- /dev/null
@@ -0,0 +1,180 @@
+/**
+ * collectd - src/tokyotyrant.c
+ * Copyright (C) 2009 Paul Sadauskas
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Paul Sadauskas <psadauskas@gmail.com>
+ **/
+
+#include "collectd.h"
+#include "plugin.h"
+#include "common.h"
+#include "utils_cache.h"
+#include "utils_parse_option.h"
+
+#include <tcrdb.h>
+
+#define DEFAULT_HOST "127.0.0.1"
+#define DEFAULT_PORT 1978
+
+static const char *config_keys[] =
+{
+       "Host",
+       "Port"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static char *config_host = NULL;
+static char *config_port = NULL;
+
+static TCRDB *rdb = NULL;
+
+static int tt_config (const char *key, const char *value)
+{
+       if (strcasecmp ("Host", key) == 0)
+       {
+               char *temp;
+
+               temp = strdup (value);
+               if (temp == NULL)
+               {
+                       ERROR("tokyotyrant plugin: Host strdup failed.");
+                       return (1);
+               }
+               sfree (config_host);
+               config_host = temp;
+       }
+       else if (strcasecmp ("Port", key) == 0)
+       {
+               char *temp;
+
+               temp = strdup (value);
+               if (temp == NULL)
+               {
+                       ERROR("tokyotyrant plugin: Port strdup failed.");
+                       return (1);
+               }
+               sfree (config_port);
+               config_port = temp;
+       }
+       else
+       {
+               ERROR ("tokyotyrant plugin: error: unrecognized configuration key %s", key);
+               return (-1);
+       }
+
+       return (0);
+}
+
+static void printerr()
+{
+       int ecode = tcrdbecode(rdb);
+       ERROR ("tokyotyrant plugin: error: %d, %s",
+                       ecode, tcrdberrmsg(ecode));
+}
+
+static void tt_submit (gauge_t val, const char* type)
+{
+       value_t values[1];
+       value_list_t vl = VALUE_LIST_INIT;
+
+       values[0].gauge = val;
+
+       vl.values = values;
+       vl.values_len = STATIC_ARRAY_SIZE (values);
+
+       sstrncpy (vl.host, config_host, sizeof (vl.host));
+       sstrncpy (vl.plugin, "tokyotyrant", sizeof (vl.plugin));
+       sstrncpy (vl.plugin_instance, config_port,
+                       sizeof (vl.plugin_instance));
+       sstrncpy (vl.type, type, sizeof (vl.type));
+
+       plugin_dispatch_values (&vl);
+}
+
+static void tt_open_db (void)
+{
+       char* host = NULL;
+       int   port = DEFAULT_PORT;
+
+       if (rdb != NULL)
+               return;
+
+       host = ((config_host != NULL) ? config_host : DEFAULT_HOST);
+
+       if (config_port != NULL)
+       {
+               port = service_name_to_port_number (config_port);
+               if (port <= 0)
+                       return;
+       }
+
+       rdb = tcrdbnew ();
+       if (rdb == NULL)
+               return;
+       else if (!tcrdbopen(rdb, host, port))
+       {
+               printerr ();
+               tcrdbdel (rdb);
+               rdb = NULL;
+       }
+} /* void tt_open_db */
+
+static int tt_read (void) {
+       gauge_t rnum, size;
+
+       tt_open_db ();
+       if (rdb == NULL)
+               return (-1);
+
+       rnum = tcrdbrnum(rdb);
+       tt_submit (rnum, "records");
+
+       size = tcrdbsize(rdb);
+       tt_submit (size, "file_size");
+
+       return (0);
+}
+
+static int tt_shutdown(void)
+{
+       sfree(config_host);
+       sfree(config_port);
+
+       if (rdb != NULL)
+       {
+               if (!tcrdbclose(rdb))
+               {
+                       printerr ();
+                       tcrdbdel (rdb);
+                       return (1);
+               }
+               tcrdbdel (rdb);
+               rdb = NULL;
+       }
+
+       return(0);
+}
+
+void module_register (void)
+{
+       plugin_register_config("tokyotyrant", tt_config,
+                       config_keys, config_keys_num);
+       plugin_register_read("tokyotyrant", tt_read);
+       plugin_register_shutdown("tokyotyrant", tt_shutdown);
+}
+
+/* vim: set sw=8 ts=8 tw=78 : */
diff --git a/src/types.db b/src/types.db
new file mode 100644 (file)
index 0000000..e6345ab
--- /dev/null
@@ -0,0 +1,187 @@
+absolute               value:ABSOLUTE:0:U
+apache_bytes           value:DERIVE:0:U
+apache_connections     value:GAUGE:0:65535
+apache_idle_workers    value:GAUGE:0:65535
+apache_requests                value:DERIVE:0:U
+apache_scoreboard      value:GAUGE:0:65535
+ath_nodes              value:GAUGE:0:65535
+ath_stat               value:DERIVE:0:U
+bitrate                        value:GAUGE:0:4294967295
+bytes                  value:GAUGE:0:U
+cache_operation                value:DERIVE:0:U
+cache_ratio            value:GAUGE:0:100
+cache_result           value:DERIVE:0:U
+cache_size             value:GAUGE:0:4294967295
+charge                 value:GAUGE:0:U
+compression_ratio      value:GAUGE:0:2
+compression            uncompressed:DERIVE:0:U, compressed:DERIVE:0:U
+connections            value:DERIVE:0:U
+conntrack              value:GAUGE:0:4294967295
+contextswitch          value:DERIVE:0:U
+counter                        value:COUNTER:U:U
+cpufreq                        value:GAUGE:0:U
+cpu                    value:DERIVE:0:U
+current_connections    value:GAUGE:0:U
+current_sessions       value:GAUGE:0:U
+current                        value:GAUGE:U:U
+delay                  value:GAUGE:-1000000:1000000
+derive                 value:DERIVE:0:U
+df_complex             value:GAUGE:0:U
+df_inodes              value:GAUGE:0:U
+df                     used:GAUGE:0:1125899906842623, free:GAUGE:0:1125899906842623
+disk_latency           read:GAUGE:0:U, write:GAUGE:0:U
+disk_merged            read:DERIVE:0:U, write:DERIVE:0:U
+disk_octets            read:DERIVE:0:U, write:DERIVE:0:U
+disk_ops_complex       value:DERIVE:0:U
+disk_ops               read:DERIVE:0:U, write:DERIVE:0:U
+disk_time              read:DERIVE:0:U, write:DERIVE:0:U
+dns_answer             value:DERIVE:0:U
+dns_notify             value:DERIVE:0:U
+dns_octets             queries:DERIVE:0:U, responses:DERIVE:0:U
+dns_opcode             value:DERIVE:0:U
+dns_qtype_cached       value:GAUGE:0:4294967295
+dns_qtype              value:DERIVE:0:U
+dns_query              value:DERIVE:0:U
+dns_question           value:DERIVE:0:U
+dns_rcode              value:DERIVE:0:U
+dns_reject             value:DERIVE:0:U
+dns_request            value:DERIVE:0:U
+dns_resolver           value:DERIVE:0:U
+dns_response           value:DERIVE:0:U
+dns_transfer           value:DERIVE:0:U
+dns_update             value:DERIVE:0:U
+dns_zops               value:DERIVE:0:U
+email_check            value:GAUGE:0:U
+email_count            value:GAUGE:0:U
+email_size             value:GAUGE:0:U
+entropy                        value:GAUGE:0:4294967295
+fanspeed               value:GAUGE:0:U
+file_size              value:GAUGE:0:U
+files                  value:GAUGE:0:U
+fork_rate              value:DERIVE:0:U
+frequency              value:GAUGE:0:U
+frequency_offset       value:GAUGE:-1000000:1000000
+fscache_stat           value:DERIVE:0:U
+gauge                  value:GAUGE:U:U
+http_request_methods   value:DERIVE:0:U
+http_requests          value:DERIVE:0:U
+http_response_codes    value:DERIVE:0:U
+humidity               value:GAUGE:0:100
+if_collisions          value:DERIVE:0:U
+if_dropped             rx:DERIVE:0:U, tx:DERIVE:0:U
+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_errors           value:DERIVE:0:U
+if_tx_errors           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
+ipt_bytes              value:DERIVE:0:U
+ipt_packets            value:DERIVE:0:U
+irq                    value:DERIVE:0:U
+latency                        value:GAUGE:0:65535
+links                  value:GAUGE:0:U
+load                   shortterm:GAUGE:0:100, midterm:GAUGE:0:100, longterm:GAUGE:0:100
+memcached_command      value:DERIVE:0:U
+memcached_connections  value:GAUGE:0:U
+memcached_items                value:GAUGE:0:U
+memcached_octets       rx:DERIVE:0:U, tx:DERIVE:0:U
+memcached_ops          value:DERIVE:0:U
+memory                 value:GAUGE:0:281474976710656
+multimeter             value:GAUGE:U:U
+mysql_commands         value:DERIVE:0:U
+mysql_handler          value:DERIVE:0:U
+mysql_locks            value:DERIVE:0:U
+mysql_log_position     value:DERIVE:0:U
+mysql_octets           rx:DERIVE:0:U, tx:DERIVE:0:U
+nfs_procedure          value:DERIVE:0:U
+nginx_connections      value:GAUGE:0:U
+nginx_requests         value:DERIVE:0:U
+node_octets            rx:DERIVE:0:U, tx:DERIVE:0:U
+node_rssi              value:GAUGE:0:255
+node_stat              value:DERIVE:0:U
+node_tx_rate           value:GAUGE:0:127
+operations             value:DERIVE:0:U
+percent                        value:GAUGE:0:100.1
+pg_blks                        value:DERIVE:0:U
+pg_db_size             value:GAUGE:0:U
+pg_n_tup_c             value:DERIVE:0:U
+pg_n_tup_g             value:GAUGE:0:U
+pg_numbackends         value:GAUGE:0:U
+pg_scan                        value:DERIVE:0:U
+pg_xact                        value:DERIVE:0:U
+ping_droprate          value:GAUGE:0:100
+ping                   value:GAUGE:0:65535
+ping_stddev            value:GAUGE:0:65535
+players                        value:GAUGE:0:1000000
+power                  value:GAUGE:0:U
+protocol_counter       value:DERIVE:0:U
+ps_code                        value:GAUGE:0:9223372036854775807
+ps_count               processes:GAUGE:0:1000000, threads:GAUGE:0:1000000
+ps_cputime             user:DERIVE:0:U, syst:DERIVE:0:U
+ps_data                        value:GAUGE:0:9223372036854775807
+ps_disk_octets         read:DERIVE:0:U, write:DERIVE:0:U
+ps_disk_ops            read:DERIVE:0:U, write:DERIVE:0:U
+ps_pagefaults          minflt:DERIVE:0:U, majflt:DERIVE:0:U
+ps_rss                 value:GAUGE:0:9223372036854775807
+ps_stacksize           value:GAUGE:0:9223372036854775807
+ps_state               value:GAUGE:0:65535
+ps_vm                  value:GAUGE:0:9223372036854775807
+queue_length           value:GAUGE:0:U
+records                        value:GAUGE:0:U
+requests               value:GAUGE:0:U
+response_time          value:GAUGE:0:U
+route_etx              value:GAUGE:0:U
+route_metric           value:GAUGE:0:U
+routes                 value:GAUGE:0:U
+serial_octets          rx:DERIVE:0:U, tx:DERIVE:0:U
+signal_noise           value:GAUGE:U:0
+signal_power           value:GAUGE:U:0
+signal_quality         value:GAUGE:0:U
+snr                    value:GAUGE:0:U
+spam_check             value:GAUGE:0:U
+spam_score             value:GAUGE:U:U
+swap_io                        value:DERIVE:0:U
+swap                   value:GAUGE:0:1099511627776
+tcp_connections                value:GAUGE:0:4294967295
+temperature            value:GAUGE:-273.15:U
+threads                        value:GAUGE:0:U
+time_dispersion                value:GAUGE:-1000000:1000000
+timeleft               value:GAUGE:0:3600
+time_offset            value:GAUGE:-1000000:1000000
+total_bytes            value:DERIVE:0:U
+total_connections      value:DERIVE:0:U
+total_operations       value:DERIVE:0:U
+total_requests         value:DERIVE:0:U
+total_sessions         value:DERIVE:0:U
+total_threads          value:DERIVE:0:U
+total_time_in_ms       value:DERIVE:0:U
+total_values           value:DERIVE:0:U
+uptime                 value:GAUGE:0:4294967295
+users                  value:GAUGE:0:65535
+vcpu                   value:GAUGE:0:U
+virt_cpu_total         value:DERIVE:0:U
+virt_vcpu              value:DERIVE:0:U
+vmpage_action          value:DERIVE:0:U
+vmpage_faults          minflt:DERIVE:0:U, majflt:DERIVE:0:U
+vmpage_io              in:DERIVE:0:U, out:DERIVE:0:U
+vmpage_number          value:GAUGE:0:4294967295
+volatile_changes       value:GAUGE:0:U
+voltage_threshold      value:GAUGE:U:U, threshold:GAUGE:U:U
+voltage                        value:GAUGE:U:U
+vs_memory              value:GAUGE:0:9223372036854775807
+vs_processes           value:GAUGE:0:65535
+vs_threads             value:GAUGE:0:65535
+#
+# Legacy types
+# (required for the v5 upgrade target)
+#
+arc_counts             demand_data:COUNTER:0:U, demand_metadata:COUNTER:0:U, prefetch_data:COUNTER:0:U, prefetch_metadata:COUNTER:0:U
+arc_l2_bytes           read:COUNTER:0:U, write:COUNTER:0:U
+arc_l2_size            value:GAUGE:0:U
+arc_ratio              value:GAUGE:0:U
+arc_size               current:GAUGE:0:U, target:GAUGE:0:U, minlimit:GAUGE:0:U, maxlimit:GAUGE:0:U
+mysql_qcache           hits:COUNTER:0:U, inserts:COUNTER:0:U, not_cached:COUNTER:0:U, lowmem_prunes:COUNTER:0:U, queries_in_cache:GAUGE:0:U
+mysql_threads          running:GAUGE:0:U, connected:GAUGE:0:U, cached:GAUGE:0:U, created:COUNTER:0:U
diff --git a/src/types.db.pod b/src/types.db.pod
new file mode 100644 (file)
index 0000000..67f9881
--- /dev/null
@@ -0,0 +1,66 @@
+=head1 NAME
+
+types.db - Data-set specifications for the system statistics collection daemon
+B<collectd>
+
+=head1 SYNOPSIS
+
+  bitrate    value:GAUGE:0:4294967295
+  counter    value:COUNTER:U:U
+  if_octets  rx:COUNTER:0:4294967295, tx:COUNTER:0:4294967295
+
+=head1 DESCRIPTION
+
+The types.db file contains one line for each data-set specification. Each line
+consists of two fields delimited by spaces and/or horizontal tabs. The first
+field defines the name of the data-set, while the second field defines a list
+of data-source specifications, delimited by spaces and, optionally, a comma
+(",") right after each list-entry.
+
+The format of the data-source specification has been inspired by RRDtool's
+data-source specification. Each data-source is defined by a quadruple made up
+of the data-source name, type, minimal and maximal values, delimited by colons
+(":"): I<ds-name>:I<ds-type>:I<min>:I<max>. I<ds-type> may be either
+B<ABSOLUTE>, B<COUNTER>, B<DERIVE>, or B<GAUGE>. I<min> and I<max> define the
+range of valid values for
+data stored for this data-source. If B<U> is specified for either the min or
+max value, it will be set to unknown, meaning that no range checks will
+happen. See L<rrdcreate(1)> for more details.
+
+=head1 FILES
+
+The location of the types.db file is defined by the B<TypesDB> configuration
+option (see L<collectd.conf(5)>). It defaults to collectd's shared data
+directory, i.E<nbsp>e. F<I<prefix>/share/collectd/>.
+
+=head1 CUSTOM TYPES
+
+If you want to specify custom types, you should do so by specifying a custom
+file in addition to the default one (see L<FILES>) above. You can do that by
+having multiple B<TypesDB> statements in your configuration file or by
+specifying more than one file in one line.
+
+For example:
+
+ TypesDB "/opt/collectd/share/collectd/types.db"
+ TypesDB "/opt/collectd/etc/types.db.custom"
+
+B<Note>: Make sure to make this file available on all systems if you're
+sending values over the network.
+
+=head1 SEE ALSO
+
+L<collectd(1)>,
+L<collectd.conf(5)>,
+L<rrdcreate(1)>
+
+=head1 AUTHOR
+
+B<collectd> has been written by Florian Forster
+E<lt>octoE<nbsp>atE<nbsp>verplant.orgE<gt>.
+
+This manpage has been written by Sebastian Harl
+E<lt>shE<nbsp>atE<nbsp>tokkee.orgE<gt>.
+
+=cut
+
diff --git a/src/types_list.c b/src/types_list.c
new file mode 100644 (file)
index 0000000..10cb4f2
--- /dev/null
@@ -0,0 +1,202 @@
+/**
+ * collectd - src/types_list.c
+ * Copyright (C) 2007  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+
+#include "plugin.h"
+#include "configfile.h"
+
+static int parse_ds (data_source_t *dsrc, char *buf, size_t buf_len)
+{
+  char *dummy;
+  char *saveptr;
+  char *fields[8];
+  int   fields_num;
+
+  if (buf_len < 11)
+  {
+    ERROR ("parse_ds: (buf_len = %zu) < 11", buf_len);
+    return (-1);
+  }
+
+  if (buf[buf_len - 1] == ',')
+  {
+    buf_len--;
+    buf[buf_len] = '\0';
+  }
+
+  dummy = buf;
+  saveptr = NULL;
+
+  fields_num = 0;
+  while (fields_num < 8)
+  {
+    if ((fields[fields_num] = strtok_r (dummy, ":", &saveptr)) == NULL)
+      break;
+    dummy = NULL;
+    fields_num++;
+  }
+
+  if (fields_num != 4)
+  {
+    ERROR ("parse_ds: (fields_num = %i) != 4", fields_num);
+    return (-1);
+  }
+
+  sstrncpy (dsrc->name, fields[0], sizeof (dsrc->name));
+
+  if (strcasecmp (fields[1], "GAUGE") == 0)
+    dsrc->type = DS_TYPE_GAUGE;
+  else if (strcasecmp (fields[1], "COUNTER") == 0)
+    dsrc->type = DS_TYPE_COUNTER;
+  else if (strcasecmp (fields[1], "DERIVE") == 0)
+    dsrc->type = DS_TYPE_DERIVE;
+  else if (strcasecmp (fields[1], "ABSOLUTE") == 0)
+    dsrc->type = DS_TYPE_ABSOLUTE;
+  else
+  {
+    ERROR ("(fields[1] = %s) != (GAUGE || COUNTER || DERIVE || ABSOLUTE)", fields[1]);
+    return (-1);
+  }
+
+  if (strcasecmp (fields[2], "U") == 0)
+    dsrc->min = NAN;
+  else
+    dsrc->min = atof (fields[2]);
+
+  if (strcasecmp (fields[3], "U") == 0)
+    dsrc->max = NAN;
+  else
+    dsrc->max = atof (fields[3]);
+
+  return (0);
+} /* int parse_ds */
+
+static void parse_line (char *buf)
+{
+  char  *fields[64];
+  size_t fields_num;
+  data_set_t *ds;
+  int i;
+
+  fields_num = strsplit (buf, fields, 64);
+  if (fields_num < 2)
+    return;
+
+  /* Ignore lines which begin with a hash sign. */
+  if (fields[0][0] == '#')
+    return;
+
+  ds = (data_set_t *) malloc (sizeof (data_set_t));
+  if (ds == NULL)
+    return;
+
+  memset (ds, '\0', sizeof (data_set_t));
+
+  sstrncpy (ds->type, fields[0], sizeof (ds->type));
+
+  ds->ds_num = fields_num - 1;
+  ds->ds = (data_source_t *) calloc (ds->ds_num, sizeof (data_source_t));
+  if (ds->ds == NULL)
+    return;
+
+  for (i = 0; i < ds->ds_num; i++)
+    if (parse_ds (ds->ds + i, fields[i + 1], strlen (fields[i + 1])) != 0)
+    {
+      sfree (ds->ds);
+      ERROR ("types_list: parse_line: Cannot parse data source #%i "
+         "of data set %s", i, ds->type);
+      return;
+    }
+
+  plugin_register_data_set (ds);
+
+  sfree (ds->ds);
+  sfree (ds);
+} /* void parse_line */
+
+static void parse_file (FILE *fh)
+{
+  char buf[4096];
+  size_t buf_len;
+
+  while (fgets (buf, sizeof (buf), fh) != NULL)
+  {
+    buf_len = strlen (buf);
+
+    if (buf_len >= 4095)
+    {
+      NOTICE ("Skipping line with more than 4095 characters.");
+      do
+      {
+       if (fgets (buf, sizeof (buf), fh) == NULL)
+         break;
+       buf_len = strlen (buf);
+      } while (buf_len >= 4095);
+      continue;
+    } /* if (buf_len >= 4095) */
+
+    if ((buf_len == 0) || (buf[0] == '#'))
+      continue;
+
+    while ((buf_len > 0) && ((buf[buf_len - 1] == '\n')
+         || (buf[buf_len - 1] == '\n')))
+      buf[--buf_len] = '\0';
+
+    if (buf_len == 0)
+      continue;
+
+    parse_line (buf);
+  } /* while (fgets) */
+} /* void parse_file */
+
+int read_types_list (const char *file)
+{
+  FILE *fh;
+
+  if (file == NULL)
+    return (-1);
+
+  fh = fopen (file, "r");
+  if (fh == NULL)
+  {
+    char errbuf[1024];
+    fprintf (stderr, "Failed to open types database `%s': %s.\n",
+       file, sstrerror (errno, errbuf, sizeof (errbuf)));
+    ERROR ("Failed to open types database `%s': %s",
+       file, sstrerror (errno, errbuf, sizeof (errbuf)));
+    return (-1);
+  }
+
+  parse_file (fh);
+
+  fclose (fh);
+  fh = NULL;
+
+  DEBUG ("Done parsing `%s'", file);
+
+  return (0);
+} /* int read_types_list */
+
+/*
+ * vim: shiftwidth=2:softtabstop=2:tabstop=8
+ */
diff --git a/src/types_list.h b/src/types_list.h
new file mode 100644 (file)
index 0000000..8fe6ce8
--- /dev/null
@@ -0,0 +1,27 @@
+#ifndef TYPES_LIST_H
+#define TYPES_LIST_H 1
+
+/**
+ * collectd - src/types_list.h
+ * Copyright (C) 2007  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+int read_types_list (const char *file);
+
+#endif /* TYPES_LIST_H */
diff --git a/src/unixsock.c b/src/unixsock.c
new file mode 100644 (file)
index 0000000..d729477
--- /dev/null
@@ -0,0 +1,476 @@
+/**
+ * collectd - src/unixsock.c
+ * Copyright (C) 2007,2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+
+#include "utils_cmd_flush.h"
+#include "utils_cmd_getval.h"
+#include "utils_cmd_listval.h"
+#include "utils_cmd_putval.h"
+#include "utils_cmd_putnotif.h"
+
+/* Folks without pthread will need to disable this plugin. */
+#include <pthread.h>
+
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+
+#include <grp.h>
+
+#ifndef UNIX_PATH_MAX
+# define UNIX_PATH_MAX sizeof (((struct sockaddr_un *)0)->sun_path)
+#endif
+
+#define US_DEFAULT_PATH LOCALSTATEDIR"/run/"PACKAGE_NAME"-unixsock"
+
+/*
+ * Private variables
+ */
+/* valid configuration file keys */
+static const char *config_keys[] =
+{
+       "SocketFile",
+       "SocketGroup",
+       "SocketPerms",
+       "DeleteSocket"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static int loop = 0;
+
+/* socket configuration */
+static int   sock_fd    = -1;
+static char *sock_file  = NULL;
+static char *sock_group = NULL;
+static int   sock_perms = S_IRWXU | S_IRWXG;
+static _Bool delete_socket = 0;
+
+static pthread_t listen_thread = (pthread_t) 0;
+
+/*
+ * Functions
+ */
+static int us_open_socket (void)
+{
+       struct sockaddr_un sa;
+       int status;
+
+       sock_fd = socket (PF_UNIX, SOCK_STREAM, 0);
+       if (sock_fd < 0)
+       {
+               char errbuf[1024];
+               ERROR ("unixsock plugin: socket failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       memset (&sa, '\0', sizeof (sa));
+       sa.sun_family = AF_UNIX;
+       sstrncpy (sa.sun_path, (sock_file != NULL) ? sock_file : US_DEFAULT_PATH,
+                       sizeof (sa.sun_path));
+
+       DEBUG ("unixsock plugin: socket path = %s", sa.sun_path);
+
+       if (delete_socket)
+       {
+               errno = 0;
+               status = unlink (sa.sun_path);
+               if ((status != 0) && (errno != ENOENT))
+               {
+                       char errbuf[1024];
+                       WARNING ("unixsock plugin: Deleting socket file \"%s\" failed: %s",
+                                       sa.sun_path,
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+               }
+               else if (status == 0)
+               {
+                       INFO ("unixsock plugin: Successfully deleted socket file \"%s\".",
+                                       sa.sun_path);
+               }
+       }
+
+       status = bind (sock_fd, (struct sockaddr *) &sa, sizeof (sa));
+       if (status != 0)
+       {
+               char errbuf[1024];
+               sstrerror (errno, errbuf, sizeof (errbuf));
+               ERROR ("unixsock plugin: bind failed: %s", errbuf);
+               close (sock_fd);
+               sock_fd = -1;
+               return (-1);
+       }
+
+       chmod (sa.sun_path, sock_perms);
+
+       status = listen (sock_fd, 8);
+       if (status != 0)
+       {
+               char errbuf[1024];
+               ERROR ("unixsock plugin: listen failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               close (sock_fd);
+               sock_fd = -1;
+               return (-1);
+       }
+
+       do
+       {
+               char *grpname;
+               struct group *g;
+               struct group sg;
+               char grbuf[2048];
+
+               grpname = (sock_group != NULL) ? sock_group : COLLECTD_GRP_NAME;
+               g = NULL;
+
+               status = getgrnam_r (grpname, &sg, grbuf, sizeof (grbuf), &g);
+               if (status != 0)
+               {
+                       char errbuf[1024];
+                       WARNING ("unixsock plugin: getgrnam_r (%s) failed: %s", grpname,
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       break;
+               }
+               if (g == NULL)
+               {
+                       WARNING ("unixsock plugin: No such group: `%s'",
+                                       grpname);
+                       break;
+               }
+
+               if (chown ((sock_file != NULL) ? sock_file : US_DEFAULT_PATH,
+                                       (uid_t) -1, g->gr_gid) != 0)
+               {
+                       char errbuf[1024];
+                       WARNING ("unixsock plugin: chown (%s, -1, %i) failed: %s",
+                                       (sock_file != NULL) ? sock_file : US_DEFAULT_PATH,
+                                       (int) g->gr_gid,
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+               }
+       } while (0);
+
+       return (0);
+} /* int us_open_socket */
+
+static void *us_handle_client (void *arg)
+{
+       int fdin;
+       int fdout;
+       FILE *fhin, *fhout;
+
+       fdin = *((int *) arg);
+       free (arg);
+       arg = NULL;
+
+       DEBUG ("unixsock plugin: us_handle_client: Reading from fd #%i", fdin);
+
+       fdout = dup (fdin);
+       if (fdout < 0)
+       {
+               char errbuf[1024];
+               ERROR ("unixsock plugin: dup failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               close (fdin);
+               pthread_exit ((void *) 1);
+       }
+
+       fhin  = fdopen (fdin, "r");
+       if (fhin == NULL)
+       {
+               char errbuf[1024];
+               ERROR ("unixsock plugin: fdopen failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               close (fdin);
+               close (fdout);
+               pthread_exit ((void *) 1);
+       }
+
+       fhout = fdopen (fdout, "w");
+       if (fhout == NULL)
+       {
+               char errbuf[1024];
+               ERROR ("unixsock plugin: fdopen failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               fclose (fhin); /* this closes fdin as well */
+               close (fdout);
+               pthread_exit ((void *) 1);
+       }
+
+       /* change output buffer to line buffered mode */
+       if (setvbuf (fhout, NULL, _IOLBF, 0) != 0)
+       {
+               char errbuf[1024];
+               ERROR ("unixsock plugin: setvbuf failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               fclose (fhin);
+               fclose (fhout);
+               pthread_exit ((void *) 1);
+       }
+
+       while (42)
+       {
+               char buffer[1024];
+               char buffer_copy[1024];
+               char *fields[128];
+               int   fields_num;
+               int   len;
+
+               errno = 0;
+               if (fgets (buffer, sizeof (buffer), fhin) == NULL)
+               {
+                       if (errno != 0)
+                       {
+                               char errbuf[1024];
+                               WARNING ("unixsock plugin: failed to read from socket #%i: %s",
+                                               fileno (fhin),
+                                               sstrerror (errno, errbuf, sizeof (errbuf)));
+                       }
+                       break;
+               }
+
+               len = strlen (buffer);
+               while ((len > 0)
+                               && ((buffer[len - 1] == '\n') || (buffer[len - 1] == '\r')))
+                       buffer[--len] = '\0';
+
+               if (len == 0)
+                       continue;
+
+               sstrncpy (buffer_copy, buffer, sizeof (buffer_copy));
+
+               fields_num = strsplit (buffer_copy, fields,
+                               sizeof (fields) / sizeof (fields[0]));
+               if (fields_num < 1)
+               {
+                       fprintf (fhout, "-1 Internal error\n");
+                       fclose (fhin);
+                       fclose (fhout);
+                       pthread_exit ((void *) 1);
+               }
+
+               if (strcasecmp (fields[0], "getval") == 0)
+               {
+                       handle_getval (fhout, buffer);
+               }
+               else if (strcasecmp (fields[0], "putval") == 0)
+               {
+                       handle_putval (fhout, buffer);
+               }
+               else if (strcasecmp (fields[0], "listval") == 0)
+               {
+                       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);
+               }
+               else
+               {
+                       if (fprintf (fhout, "-1 Unknown command: %s\n", fields[0]) < 0)
+                       {
+                               char errbuf[1024];
+                               WARNING ("unixsock plugin: failed to write to socket #%i: %s",
+                                               fileno (fhout),
+                                               sstrerror (errno, errbuf, sizeof (errbuf)));
+                               break;
+                       }
+               }
+       } /* while (fgets) */
+
+       DEBUG ("unixsock plugin: us_handle_client: Exiting..");
+       fclose (fhin);
+       fclose (fhout);
+
+       pthread_exit ((void *) 0);
+       return ((void *) 0);
+} /* void *us_handle_client */
+
+static void *us_server_thread (void __attribute__((unused)) *arg)
+{
+       int  status;
+       int *remote_fd;
+       pthread_t th;
+       pthread_attr_t th_attr;
+
+       if (us_open_socket () != 0)
+               pthread_exit ((void *) 1);
+
+       while (loop != 0)
+       {
+               DEBUG ("unixsock plugin: Calling accept..");
+               status = accept (sock_fd, NULL, NULL);
+               if (status < 0)
+               {
+                       char errbuf[1024];
+
+                       if (errno == EINTR)
+                               continue;
+
+                       ERROR ("unixsock plugin: accept failed: %s",
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       close (sock_fd);
+                       sock_fd = -1;
+                       pthread_exit ((void *) 1);
+               }
+
+               remote_fd = (int *) malloc (sizeof (int));
+               if (remote_fd == NULL)
+               {
+                       char errbuf[1024];
+                       WARNING ("unixsock plugin: malloc failed: %s",
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       close (status);
+                       continue;
+               }
+               *remote_fd = status;
+
+               DEBUG ("Spawning child to handle connection on fd #%i", *remote_fd);
+
+               pthread_attr_init (&th_attr);
+               pthread_attr_setdetachstate (&th_attr, PTHREAD_CREATE_DETACHED);
+
+               status = pthread_create (&th, &th_attr, us_handle_client, (void *) remote_fd);
+               if (status != 0)
+               {
+                       char errbuf[1024];
+                       WARNING ("unixsock plugin: pthread_create failed: %s",
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       close (*remote_fd);
+                       free (remote_fd);
+                       continue;
+               }
+       } /* while (loop) */
+
+       close (sock_fd);
+       sock_fd = -1;
+
+       status = unlink ((sock_file != NULL) ? sock_file : US_DEFAULT_PATH);
+       if (status != 0)
+       {
+               char errbuf[1024];
+               NOTICE ("unixsock plugin: unlink (%s) failed: %s",
+                               (sock_file != NULL) ? sock_file : US_DEFAULT_PATH,
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+       }
+
+       return ((void *) 0);
+} /* void *us_server_thread */
+
+static int us_config (const char *key, const char *val)
+{
+       if (strcasecmp (key, "SocketFile") == 0)
+       {
+               char *new_sock_file = strdup (val);
+               if (new_sock_file == NULL)
+                       return (1);
+
+               sfree (sock_file);
+               sock_file = new_sock_file;
+       }
+       else if (strcasecmp (key, "SocketGroup") == 0)
+       {
+               char *new_sock_group = strdup (val);
+               if (new_sock_group == NULL)
+                       return (1);
+
+               sfree (sock_group);
+               sock_group = new_sock_group;
+       }
+       else if (strcasecmp (key, "SocketPerms") == 0)
+       {
+               sock_perms = (int) strtol (val, NULL, 8);
+       }
+       else if (strcasecmp (key, "DeleteSocket") == 0)
+       {
+               if (IS_TRUE (val))
+                       delete_socket = 1;
+               else
+                       delete_socket = 0;
+       }
+       else
+       {
+               return (-1);
+       }
+
+       return (0);
+} /* int us_config */
+
+static int us_init (void)
+{
+       static int have_init = 0;
+
+       int status;
+
+       /* Initialize only once. */
+       if (have_init != 0)
+               return (0);
+       have_init = 1;
+
+       loop = 1;
+
+       status = pthread_create (&listen_thread, NULL, us_server_thread, NULL);
+       if (status != 0)
+       {
+               char errbuf[1024];
+               ERROR ("unixsock plugin: pthread_create failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       return (0);
+} /* int us_init */
+
+static int us_shutdown (void)
+{
+       void *ret;
+
+       loop = 0;
+
+       if (listen_thread != (pthread_t) 0)
+       {
+               pthread_kill (listen_thread, SIGTERM);
+               pthread_join (listen_thread, &ret);
+               listen_thread = (pthread_t) 0;
+       }
+
+       plugin_unregister_init ("unixsock");
+       plugin_unregister_shutdown ("unixsock");
+
+       return (0);
+} /* int us_shutdown */
+
+void module_register (void)
+{
+       plugin_register_config ("unixsock", us_config,
+                       config_keys, config_keys_num);
+       plugin_register_init ("unixsock", us_init);
+       plugin_register_shutdown ("unixsock", us_shutdown);
+} /* void module_register (void) */
+
+/* vim: set sw=4 ts=4 sts=4 tw=78 : */
diff --git a/src/uptime.c b/src/uptime.c
new file mode 100644 (file)
index 0000000..d2ba963
--- /dev/null
@@ -0,0 +1,230 @@
+/**
+ * collectd - src/uptime.c
+ * Copyright (C) 2009  Marco Chiappero
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Marco Chiappero <marco at absence.it>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#if KERNEL_LINUX
+# define STAT_FILE "/proc/stat"
+/* Using /proc filesystem to retrieve the boot time, Linux only. */
+/* #endif KERNEL_LINUX */
+
+#elif HAVE_LIBKSTAT
+/* Using kstats chain to retrieve the boot time on Solaris / OpenSolaris systems */
+/* #endif HAVE_LIBKSTAT */
+
+#elif HAVE_SYS_SYSCTL_H
+# include <sys/sysctl.h>
+/* Using sysctl interface to retrieve the boot time on *BSD / Darwin / OS X systems */
+/* #endif HAVE_SYS_SYSCTL_H */
+
+#else
+# error "No applicable input method."
+#endif
+
+/*
+ * Global variables
+ */
+/* boottime always used, no OS distinction */
+static time_t boottime;
+
+#if HAVE_LIBKSTAT
+extern kstat_ctl_t *kc;
+#endif /* #endif HAVE_LIBKSTAT */
+
+static void uptime_submit (gauge_t uptime)
+{
+       value_t values[1];
+       value_list_t vl = VALUE_LIST_INIT;
+
+       values[0].gauge = uptime;
+
+       vl.values = values;
+       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));
+
+       plugin_dispatch_values (&vl);
+}
+
+static int uptime_init (void) /* {{{ */
+{
+       /*
+        * On most unix systems the uptime is calculated by looking at the boot
+        * time (stored in unix time, since epoch) and the current one. We are
+        * going to do the same, reading the boot time value while executing
+        * the uptime_init function (there is no need to read, every time the
+        * plugin_read is called, a value that won't change). However, since
+        * uptime_init is run only once, if the function fails in retrieving
+        * the boot time, the plugin is unregistered and there is no chance to
+        * try again later. Nevertheless, this is very unlikely to happen.
+        */
+
+#if KERNEL_LINUX
+       unsigned long starttime;
+       char buffer[1024];
+       int ret;
+       FILE *fh;
+
+       ret = 0;
+
+       fh = fopen (STAT_FILE, "r");
+
+       if (fh == NULL)
+       {
+               char errbuf[1024];
+               ERROR ("uptime plugin: Cannot open "STAT_FILE": %s",
+                       sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       while (fgets (buffer, 1024, fh) != NULL)
+       {
+               /* look for the btime string and read the value */
+               ret = sscanf (buffer, "btime %lu", &starttime);
+               /* avoid further loops if btime has been found and read
+                * correctly (hopefully) */
+               if (ret == 1)
+                       break;
+       }
+
+       fclose (fh);
+
+       /* loop done, check if no value has been found/read */
+       if (ret != 1)
+       {
+               ERROR ("uptime plugin: No value read from "STAT_FILE"");
+               return (-1);
+       }
+
+       boottime = (time_t) starttime;
+
+       if (boottime == 0)
+       {
+               ERROR ("uptime plugin: btime read from "STAT_FILE", "
+                               "but `boottime' is zero!");
+               return (-1);
+       }
+/* #endif KERNEL_LINUX */
+
+#elif HAVE_LIBKSTAT
+       kstat_t *ksp;
+       kstat_named_t *knp;
+
+       ksp = NULL;
+       knp = NULL;
+
+       /* kstats chain already opened by update_kstat (using *kc), verify everything went fine. */
+       if (kc == NULL)
+       {
+               ERROR ("uptime plugin: kstat chain control structure not available.");
+               return (-1);
+       }
+
+       ksp = kstat_lookup (kc, "unix", 0, "system_misc");
+       if (ksp == NULL)
+       {
+               ERROR ("uptime plugin: Cannot find unix:0:system_misc kstat.");
+               return (-1);
+       }
+
+       if (kstat_read (kc, ksp, NULL) < 0)
+       {
+               ERROR ("uptime plugin: kstat_read failed.");
+               return (-1);
+       }
+
+       knp = (kstat_named_t *) kstat_data_lookup (ksp, "boot_time");
+       if (knp == NULL)
+       {
+               ERROR ("uptime plugin: kstat_data_lookup (boot_time) failed.");
+               return (-1);
+       }
+
+       boottime = (time_t) knp->value.ui32;
+
+       if (boottime == 0)
+       {
+               ERROR ("uptime plugin: kstat_data_lookup returned success, "
+                       "but `boottime' is zero!");
+               return (-1);
+       }
+/* #endif HAVE_LIBKSTAT */
+
+# elif HAVE_SYS_SYSCTL_H
+       struct timeval boottv;
+       size_t boottv_len;
+       int status;
+
+       int mib[2];
+
+       mib[0] = CTL_KERN;
+       mib[1] = KERN_BOOTTIME;
+
+       boottv_len = sizeof (boottv);
+       memset (&boottv, 0, boottv_len);
+
+       status = sysctl (mib, STATIC_ARRAY_SIZE (mib), &boottv, &boottv_len,
+                       /* new_value = */ NULL, /* new_length = */ 0);
+       if (status != 0)
+       {
+               char errbuf[1024];
+               ERROR ("uptime plugin: No value read from sysctl interface: %s",
+                       sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       boottime = boottv.tv_sec;
+
+       if (boottime == 0)
+       {
+               ERROR ("uptime plugin: sysctl(3) returned success, "
+                               "but `boottime' is zero!");
+               return (-1);
+       }
+#endif /* HAVE_SYS_SYSCTL_H */
+
+       return (0);
+} /* }}} int uptime_init */
+
+static int uptime_read (void)
+{
+       gauge_t uptime;
+       time_t elapsed;
+
+       /* calculate the ammount of time elapsed since boot, AKA uptime */
+       elapsed = time (NULL) - boottime;
+
+       uptime = (gauge_t) elapsed;
+
+       uptime_submit (uptime);
+
+       return (0);
+}
+
+void module_register (void)
+{
+       plugin_register_init ("uptime", uptime_init);
+       plugin_register_read ("uptime", uptime_read);
+} /* void module_register */
diff --git a/src/users.c b/src/users.c
new file mode 100644 (file)
index 0000000..1e33754
--- /dev/null
@@ -0,0 +1,122 @@
+/**
+ * collectd - src/users.c
+ * Copyright (C) 2005-2007  Sebastian Harl
+ * Copyright (C) 2005       Niki W. Waibel
+ * Copyright (C) 2005-2007  Florian octo Forster
+ * Copyright (C) 2008       Oleg King 
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the license is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Sebastian Harl <sh at tokkee.org>
+ *   Niki W. Waibel <niki.waibel at newlogic.com>
+ *   Florian octo Forster <octo at verplant.org>
+ *   Oleg King <king2 at kaluga.ru>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#if HAVE_STATGRAB_H
+# include <statgrab.h>
+#endif /* HAVE_STATGRAB_H */
+
+#if HAVE_UTMPX_H
+# include <utmpx.h>
+/* #endif HAVE_UTMPX_H */
+
+#elif HAVE_UTMP_H
+# include <utmp.h>
+/* #endif HAVE_UTMP_H */
+
+#else
+# error "No applicable input method."
+#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_len = 1;
+       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+       sstrncpy (vl.plugin, "users", sizeof (vl.plugin));
+       sstrncpy (vl.type, "users", sizeof (vl.plugin));
+
+       plugin_dispatch_values (&vl);
+} /* void users_submit */
+
+static int users_read (void)
+{
+#if HAVE_GETUTXENT
+       unsigned int users = 0;
+       struct utmpx *entry = NULL;
+
+       /* according to the *utent(3) man page none of the functions sets errno
+          in case of an error, so we cannot do any error-checking here */
+       setutxent();
+
+       while (NULL != (entry = getutxent())) {
+               if (USER_PROCESS == entry->ut_type) {
+                       ++users;
+               }
+       }
+       endutxent();
+
+       users_submit (users);
+/* #endif HAVE_GETUTXENT */
+       
+#elif HAVE_GETUTENT
+       unsigned int users = 0;
+       struct utmp *entry = NULL;
+
+       /* according to the *utent(3) man page none of the functions sets errno
+          in case of an error, so we cannot do any error-checking here */
+       setutent();
+
+       while (NULL != (entry = getutent())) {
+               if (USER_PROCESS == entry->ut_type) {
+                       ++users;
+               }
+       }
+       endutent();
+
+       users_submit (users);
+/* #endif HAVE_GETUTENT */
+
+#elif HAVE_LIBSTATGRAB
+       sg_user_stats *us;
+
+       us = sg_get_user_stats ();
+       if (us == NULL)
+               return (-1);   
+
+       users_submit ((gauge_t) us->num_entries);
+/* #endif HAVE_LIBSTATGRAB */
+
+#else
+# error "No applicable input method."
+#endif
+
+       return (0);
+} /* int users_read */
+
+void module_register (void)
+{
+       plugin_register_read ("users", users_read);
+} /* void module_register(void) */
diff --git a/src/utils_avltree.c b/src/utils_avltree.c
new file mode 100644 (file)
index 0000000..0436d8f
--- /dev/null
@@ -0,0 +1,723 @@
+/**
+ * collectd - src/utils_avltree.c
+ * Copyright (C) 2006,2007  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include "utils_avltree.h"
+
+#define BALANCE(n) ((((n)->left == NULL) ? 0 : (n)->left->height) \
+               - (((n)->right == NULL) ? 0 : (n)->right->height))
+
+/*
+ * private data types
+ */
+struct c_avl_node_s
+{
+       void *key;
+       void *value;
+
+       int height;
+       struct c_avl_node_s *left;
+       struct c_avl_node_s *right;
+       struct c_avl_node_s *parent;
+};
+typedef struct c_avl_node_s c_avl_node_t;
+
+struct c_avl_tree_s
+{
+       c_avl_node_t *root;
+       int (*compare) (const void *, const void *);
+       int size;
+};
+
+struct c_avl_iterator_s
+{
+       c_avl_tree_t *tree;
+       c_avl_node_t *node;
+};
+
+/*
+ * private functions
+ */
+#if 0
+static void verify_tree (c_avl_node_t *n)
+{
+       if (n == NULL)
+               return;
+
+       verify_tree (n->left);
+       verify_tree (n->right);
+
+       assert ((BALANCE (n) >= -1) && (BALANCE (n) <= 1));
+       assert ((n->parent == NULL) || (n->parent->right == n) || (n->parent->left == n));
+} /* void verify_tree */
+#else
+# define verify_tree(n) /**/
+#endif
+
+static void free_node (c_avl_node_t *n)
+{
+       if (n == NULL)
+               return;
+
+       if (n->left != NULL)
+               free_node (n->left);
+       if (n->right != NULL)
+               free_node (n->right);
+
+       free (n);
+}
+
+static int calc_height (c_avl_node_t *n)
+{
+       int height_left;
+       int height_right;
+
+       if (n == NULL)
+               return (0);
+
+       height_left  = (n->left == NULL)  ? 0 : n->left->height;
+       height_right = (n->right == NULL) ? 0 : n->right->height;
+
+       return (((height_left > height_right)
+                               ? height_left
+                               : height_right) + 1);
+} /* int calc_height */
+
+static c_avl_node_t *search (c_avl_tree_t *t, const void *key)
+{
+       c_avl_node_t *n;
+       int cmp;
+
+       n = t->root;
+       while (n != NULL)
+       {
+               cmp = t->compare (key, n->key);
+               if (cmp == 0)
+                       return (n);
+               else if (cmp < 0)
+                       n = n->left;
+               else
+                       n = n->right;
+       }
+
+       return (NULL);
+}
+
+/*         (x)             (y)
+ *        /   \           /   \
+ *     (y)    /\         /\    (x)
+ *    /   \  /_c\  ==>  / a\  /   \
+ *   /\   /\           /____\/\   /\
+ *  / a\ /_b\               /_b\ /_c\
+ * /____\
+ */
+static c_avl_node_t *rotate_right (c_avl_tree_t *t, c_avl_node_t *x)
+{
+       c_avl_node_t *p;
+       c_avl_node_t *y;
+       c_avl_node_t *b;
+
+       p = x->parent;
+       y = x->left;
+       b = y->right;
+
+       x->left = b;
+       if (b != NULL)
+               b->parent = x;
+
+       x->parent = y;
+       y->right = x;
+
+       y->parent = p;
+       assert ((p == NULL) || (p->left == x) || (p->right == x));
+       if (p == NULL)
+               t->root = y;
+       else if (p->left == x)
+               p->left = y;
+       else
+               p->right = y;
+
+       x->height = calc_height (x);
+       y->height = calc_height (y);
+
+       return (y);
+} /* void rotate_left */
+
+/*
+ *    (x)                   (y)
+ *   /   \                 /   \
+ *  /\    (y)           (x)    /\
+ * /_a\  /   \   ==>   /   \  / c\
+ *      /\   /\       /\   /\/____\
+ *     /_b\ / c\     /_a\ /_b\
+ *         /____\
+ */
+static c_avl_node_t *rotate_left (c_avl_tree_t *t, c_avl_node_t *x)
+{
+       c_avl_node_t *p;
+       c_avl_node_t *y;
+       c_avl_node_t *b;
+
+       p = x->parent;
+       y = x->right;
+       b = y->left;
+
+       x->right = b;
+       if (b != NULL)
+               b->parent = x;
+
+       x->parent = y;
+       y->left = x;
+
+       y->parent = p;
+       assert ((p == NULL) || (p->left == x) || (p->right == x));
+       if (p == NULL)
+               t->root = y;
+       else if (p->left == x)
+               p->left = y;
+       else
+               p->right = y;
+
+       x->height = calc_height (x);
+       y->height = calc_height (y);
+
+       return (y);
+} /* void rotate_left */
+
+static c_avl_node_t *rotate_left_right (c_avl_tree_t *t, c_avl_node_t *x)
+{
+       rotate_left (t, x->left);
+       return (rotate_right (t, x));
+} /* void rotate_left_right */
+
+static c_avl_node_t *rotate_right_left (c_avl_tree_t *t, c_avl_node_t *x)
+{
+       rotate_right (t, x->right);
+       return (rotate_left (t, x));
+} /* void rotate_right_left */
+
+static void rebalance (c_avl_tree_t *t, c_avl_node_t *n)
+{
+       int b_top;
+       int b_bottom;
+
+       while (n != NULL)
+       {
+               b_top = BALANCE (n);
+               assert ((b_top >= -2) && (b_top <= 2));
+
+               if (b_top == -2)
+               {
+                       assert (n->right != NULL);
+                       b_bottom = BALANCE (n->right);
+                       assert ((b_bottom >= -1) || (b_bottom <= 1));
+                       if (b_bottom == 1)
+                               n = rotate_right_left (t, n);
+                       else
+                               n = rotate_left (t, n);
+               }
+               else if (b_top == 2)
+               {
+                       assert (n->left != NULL);
+                       b_bottom = BALANCE (n->left);
+                       assert ((b_bottom >= -1) || (b_bottom <= 1));
+                       if (b_bottom == -1)
+                               n = rotate_left_right (t, n);
+                       else
+                               n = rotate_right (t, n);
+               }
+               else
+               {
+                       int height = calc_height (n);
+                       if (height == n->height)
+                               break;
+                       n->height = height;
+               }
+
+               assert (n->height == calc_height (n));
+
+               n = n->parent;
+       } /* while (n != NULL) */
+} /* void rebalance */
+
+static c_avl_node_t *c_avl_node_next (c_avl_node_t *n)
+{
+       c_avl_node_t *r; /* return node */
+
+       if (n == NULL)
+       {
+               return (NULL);
+       }
+
+       /* If we can't descent any further, we have to backtrack to the first
+        * parent that's bigger than we, i. e. who's _left_ child we are. */
+       if (n->right == NULL)
+       {
+               r = n->parent;
+               while ((r != NULL) && (r->parent != NULL))
+               {
+                       if (r->left == n)
+                               break;
+                       n = r;
+                       r = n->parent;
+               }
+
+               /* n->right == NULL && r == NULL => t is root and has no next
+                * r->left != n => r->right = n => r->parent == NULL */
+               if ((r == NULL) || (r->left != n))
+               {
+                       assert ((r == NULL) || (r->parent == NULL));
+                       return (NULL);
+               }
+               else
+               {
+                       assert (r->left == n);
+                       return (r);
+               }
+       }
+       else
+       {
+               r = n->right;
+               while (r->left != NULL)
+                       r = r->left;
+       }
+
+       return (r);
+} /* c_avl_node_t *c_avl_node_next */
+
+static c_avl_node_t *c_avl_node_prev (c_avl_node_t *n)
+{
+       c_avl_node_t *r; /* return node */
+
+       if (n == NULL)
+       {
+               return (NULL);
+       }
+
+       /* If we can't descent any further, we have to backtrack to the first
+        * parent that's smaller than we, i. e. who's _right_ child we are. */
+       if (n->left == NULL)
+       {
+               r = n->parent;
+               while ((r != NULL) && (r->parent != NULL))
+               {
+                       if (r->right == n)
+                               break;
+                       n = r;
+                       r = n->parent;
+               }
+
+               /* n->left == NULL && r == NULL => t is root and has no next
+                * r->right != n => r->left = n => r->parent == NULL */
+               if ((r == NULL) || (r->right != n))
+               {
+                       assert ((r == NULL) || (r->parent == NULL));
+                       return (NULL);
+               }
+               else
+               {
+                       assert (r->right == n);
+                       return (r);
+               }
+       }
+       else
+       {
+               r = n->left;
+               while (r->right != NULL)
+                       r = r->right;
+       }
+
+       return (r);
+} /* c_avl_node_t *c_avl_node_prev */
+
+static int _remove (c_avl_tree_t *t, c_avl_node_t *n)
+{
+       assert ((t != NULL) && (n != NULL));
+
+       if ((n->left != NULL) && (n->right != NULL))
+       {
+               c_avl_node_t *r; /* replacement node */
+               if (BALANCE (n) > 0) /* left subtree is higher */
+               {
+                       assert (n->left != NULL);
+                       r = c_avl_node_prev (n);
+                       
+               }
+               else /* right subtree is higher */
+               {
+                       assert (n->right != NULL);
+                       r = c_avl_node_next (n);
+               }
+
+               assert ((r->left == NULL) || (r->right == NULL));
+
+               /* copy content */
+               n->key   = r->key;
+               n->value = r->value;
+
+               n = r;
+       }
+
+       assert ((n->left == NULL) || (n->right == NULL));
+
+       if ((n->left == NULL) && (n->right == NULL))
+       {
+               /* Deleting a leave is easy */
+               if (n->parent == NULL)
+               {
+                       assert (t->root == n);
+                       t->root = NULL;
+               }
+               else
+               {
+                       assert ((n->parent->left == n)
+                                       || (n->parent->right == n));
+                       if (n->parent->left == n)
+                               n->parent->left = NULL;
+                       else
+                               n->parent->right = NULL;
+
+                       rebalance (t, n->parent);
+               }
+
+               free_node (n);
+       }
+       else if (n->left == NULL)
+       {
+               assert (BALANCE (n) == -1);
+               assert ((n->parent == NULL) || (n->parent->left == n) || (n->parent->right == n));
+               if (n->parent == NULL)
+               {
+                       assert (t->root == n);
+                       t->root = n->right;
+               }
+               else if (n->parent->left == n)
+               {
+                       n->parent->left = n->right;
+               }
+               else
+               {
+                       n->parent->right = n->right;
+               }
+               n->right->parent = n->parent;
+
+               if (n->parent != NULL)
+                       rebalance (t, n->parent);
+
+               n->right = NULL;
+               free_node (n);
+       }
+       else if (n->right == NULL)
+       {
+               assert (BALANCE (n) == 1);
+               assert ((n->parent == NULL) || (n->parent->left == n) || (n->parent->right == n));
+               if (n->parent == NULL)
+               {
+                       assert (t->root == n);
+                       t->root = n->left;
+               }
+               else if (n->parent->left == n)
+               {
+                       n->parent->left = n->left;
+               }
+               else
+               {
+                       n->parent->right = n->left;
+               }
+               n->left->parent = n->parent;
+
+               if (n->parent != NULL)
+                       rebalance (t, n->parent);
+
+               n->left = NULL;
+               free_node (n);
+       }
+       else
+       {
+               assert (0);
+       }
+
+       return (0);
+} /* void *_remove */
+
+/*
+ * public functions
+ */
+c_avl_tree_t *c_avl_create (int (*compare) (const void *, const void *))
+{
+       c_avl_tree_t *t;
+
+       if (compare == NULL)
+               return (NULL);
+
+       if ((t = (c_avl_tree_t *) malloc (sizeof (c_avl_tree_t))) == NULL)
+               return (NULL);
+
+       t->root = NULL;
+       t->compare = compare;
+       t->size = 0;
+
+       return (t);
+}
+
+void c_avl_destroy (c_avl_tree_t *t)
+{
+       free_node (t->root);
+       free (t);
+}
+
+int c_avl_insert (c_avl_tree_t *t, void *key, void *value)
+{
+       c_avl_node_t *new;
+       c_avl_node_t *nptr;
+       int cmp;
+
+       if ((new = (c_avl_node_t *) malloc (sizeof (c_avl_node_t))) == NULL)
+               return (-1);
+
+       new->key = key;
+       new->value = value;
+       new->height = 1;
+       new->left = NULL;
+       new->right = NULL;
+
+       if (t->root == NULL)
+       {
+               new->parent = NULL;
+               t->root = new;
+               return (0);
+       }
+
+       nptr = t->root;
+       while (42)
+       {
+               cmp = t->compare (nptr->key, new->key);
+               if (cmp == 0)
+               {
+                       free_node (new);
+                       return (1);
+               }
+               else if (cmp < 0)
+               {
+                       /* nptr < new */
+                       if (nptr->right == NULL)
+                       {
+                               nptr->right = new;
+                               new->parent = nptr;
+                               rebalance (t, nptr);
+                               break;
+                       }
+                       else
+                       {
+                               nptr = nptr->right;
+                       }
+               }
+               else /* if (cmp > 0) */
+               {
+                       /* nptr > new */
+                       if (nptr->left == NULL)
+                       {
+                               nptr->left = new;
+                               new->parent = nptr;
+                               rebalance (t, nptr);
+                               break;
+                       }
+                       else
+                       {
+                               nptr = nptr->left;
+                       }
+               }
+       } /* while (42) */
+
+       verify_tree (t->root);
+       ++t->size;
+       return (0);
+} /* int c_avl_insert */
+
+int c_avl_remove (c_avl_tree_t *t, const void *key, void **rkey, void **rvalue)
+{
+       c_avl_node_t *n;
+       int status;
+
+       assert (t != NULL);
+
+       n = search (t, key);
+       if (n == NULL)
+               return (-1);
+
+       if (rkey != NULL)
+               *rkey = n->key;
+       if (rvalue != NULL)
+               *rvalue = n->value;
+
+       status = _remove (t, n);
+       verify_tree (t->root);
+       --t->size;
+       return (status);
+} /* void *c_avl_remove */
+
+int c_avl_get (c_avl_tree_t *t, const void *key, void **value)
+{
+       c_avl_node_t *n;
+
+       assert (t != NULL);
+
+       n = search (t, key);
+       if (n == NULL)
+               return (-1);
+
+       if (value != NULL)
+               *value = n->value;
+
+       return (0);
+}
+
+int c_avl_pick (c_avl_tree_t *t, void **key, void **value)
+{
+       c_avl_node_t *n;
+       c_avl_node_t *p;
+
+       if ((key == NULL) || (value == NULL))
+               return (-1);
+       if (t->root == NULL)
+               return (-1);
+
+       n = t->root;
+       while ((n->left != NULL) || (n->right != NULL))
+       {
+               int height_left  = (n->left  == NULL) ? 0 : n->left->height;
+               int height_right = (n->right == NULL) ? 0 : n->right->height;
+
+               if (height_left > height_right)
+                       n = n->left;
+               else
+                       n = n->right;
+       }
+
+       p = n->parent;
+       if (p == NULL)
+               t->root = NULL;
+       else if (p->left == n)
+               p->left = NULL;
+       else
+               p->right = NULL;
+
+       *key   = n->key;
+       *value = n->value;
+
+       free_node (n);
+       rebalance (t, p);
+
+       return (0);
+} /* int c_avl_pick */
+
+c_avl_iterator_t *c_avl_get_iterator (c_avl_tree_t *t)
+{
+       c_avl_iterator_t *iter;
+
+       if (t == NULL)
+               return (NULL);
+
+       iter = (c_avl_iterator_t *) malloc (sizeof (c_avl_iterator_t));
+       if (iter == NULL)
+               return (NULL);
+       memset (iter, '\0', sizeof (c_avl_iterator_t));
+       iter->tree = t;
+
+       return (iter);
+} /* c_avl_iterator_t *c_avl_get_iterator */
+
+int c_avl_iterator_next (c_avl_iterator_t *iter, void **key, void **value)
+{
+       c_avl_node_t *n;
+
+       if ((iter == NULL) || (key == NULL) || (value == NULL))
+               return (-1);
+
+       if (iter->node == NULL)
+       {
+               for (n = iter->tree->root; n != NULL; n = n->left)
+                       if (n->left == NULL)
+                               break;
+               iter->node = n;
+       }
+       else
+       {
+               n = c_avl_node_next (iter->node);
+       }
+
+       if (n == NULL)
+               return (-1);
+
+       iter->node = n;
+       *key = n->key;
+       *value = n->value;
+
+       return (0);
+} /* int c_avl_iterator_next */
+
+int c_avl_iterator_prev (c_avl_iterator_t *iter, void **key, void **value)
+{
+       c_avl_node_t *n;
+
+       if ((iter == NULL) || (key == NULL) || (value == NULL))
+               return (-1);
+
+       if (iter->node == NULL)
+       {
+               for (n = iter->tree->root; n != NULL; n = n->left)
+                       if (n->right == NULL)
+                               break;
+               iter->node = n;
+       }
+       else
+       {
+               n = c_avl_node_prev (iter->node);
+       }
+
+       if (n == NULL)
+               return (-1);
+
+       iter->node = n;
+       *key = n->key;
+       *value = n->value;
+
+       return (0);
+} /* int c_avl_iterator_prev */
+
+void c_avl_iterator_destroy (c_avl_iterator_t *iter)
+{
+       free (iter);
+}
+
+int c_avl_size (c_avl_tree_t *t)
+{
+       if (t == NULL)
+               return (0);
+       return (t->size);
+}
diff --git a/src/utils_avltree.h b/src/utils_avltree.h
new file mode 100644 (file)
index 0000000..10fb5cb
--- /dev/null
@@ -0,0 +1,166 @@
+/**
+ * collectd - src/utils_avltree.h
+ * Copyright (C) 2006,2007  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef UTILS_AVLTREE_H
+#define UTILS_AVLTREE_H 1
+
+struct c_avl_tree_s;
+typedef struct c_avl_tree_s c_avl_tree_t;
+
+struct c_avl_iterator_s;
+typedef struct c_avl_iterator_s c_avl_iterator_t;
+
+/*
+ * NAME
+ *   c_avl_create
+ *
+ * DESCRIPTION
+ *   Allocates a new AVL-tree.
+ *
+ * PARAMETERS
+ *   `compare'  The function-pointer `compare' is used to compare two keys. It
+ *              has to return less than zero if it's first argument is smaller
+ *              then the second argument, more than zero if the first argument
+ *              is bigger than the second argument and zero if they are equal.
+ *              If your keys are char-pointers, you can use the `strcmp'
+ *              function from the libc here.
+ *
+ * RETURN VALUE
+ *   A c_avl_tree_t-pointer upon success or NULL upon failure.
+ */
+c_avl_tree_t *c_avl_create (int (*compare) (const void *, const void *));
+
+
+/*
+ * NAME
+ *   c_avl_destroy
+ *
+ * DESCRIPTION
+ *   Deallocates an AVL-tree. Stored value- and key-pointer are lost, but of
+ *   course not freed.
+ */
+void c_avl_destroy (c_avl_tree_t *t);
+
+/*
+ * NAME
+ *   c_avl_insert
+ *
+ * DESCRIPTION
+ *   Stores the key-value-pair in the AVL-tree pointed to by `t'.
+ *
+ * PARAMETERS
+ *   `t'        AVL-tree to store the data in.
+ *   `key'      Key used to store the value under. This is used to get back to
+ *              the value again. The pointer is stored in an internal structure
+ *              and _not_ copied. So the memory pointed to may _not_ be freed
+ *              before this entry is removed. You can use the `rkey' argument
+ *              to `avl_remove' to get the original pointer back and free it.
+ *   `value'    Value to be stored.
+ *
+ * RETURN VALUE
+ *   Zero upon success, non-zero otherwise. It's less than zero if an error
+ *   occurred or greater than zero if the key is already stored in the tree.
+ */
+int c_avl_insert (c_avl_tree_t *t, void *key, void *value);
+
+/*
+ * NAME
+ *   c_avl_remove
+ *
+ * DESCRIPTION
+ *   Removes a key-value-pair from the tree t. The stored key and value may be
+ *   returned in `rkey' and `rvalue'.
+ *
+ * PARAMETERS
+ *   `t'       AVL-tree to remove key-value-pair from.
+ *   `key'      Key to identify the entry.
+ *   `rkey'     Pointer to a pointer in which to store the key. May be NULL.
+ *              Since the `key' pointer is not copied when creating an entry,
+ *              the pointer may not be available anymore from outside the tree.
+ *              You can use this argument to get the actual pointer back and
+ *              free the memory pointed to by it.
+ *   `rvalue'   Pointer to a pointer in which to store the value. May be NULL.
+ *
+ * RETURN VALUE
+ *   Zero upon success or non-zero if the key isn't found in the tree.
+ */
+int c_avl_remove (c_avl_tree_t *t, const void *key, void **rkey, void **rvalue);
+
+/*
+ * NAME
+ *   c_avl_get
+ *
+ * DESCRIPTION
+ *   Retrieve the `value' belonging to `key'.
+ *
+ * PARAMETERS
+ *   `t'       AVL-tree to get the value from.
+ *   `key'      Key to identify the entry.
+ *   `value'    Pointer to a pointer in which to store the value. May be NULL.
+ *
+ * RETURN VALUE
+ *   Zero upon success or non-zero if the key isn't found in the tree.
+ */
+int c_avl_get (c_avl_tree_t *t, const void *key, void **value);
+
+/*
+ * NAME
+ *   c_avl_pick
+ *
+ * DESCRIPTION
+ *   Remove a (pseudo-)random element from the tree and return it's `key' and
+ *   `value'. Entries are not returned in any particular order. This function
+ *   is intended for cache-flushes that don't care about the order but simply
+ *   want to remove all elements, one at a time.
+ *
+ * PARAMETERS
+ *   `t'       AVL-tree to get the value from.
+ *   `key'      Pointer to a pointer in which to store the key.
+ *   `value'    Pointer to a pointer in which to store the value.
+ *
+ * RETURN VALUE
+ *   Zero upon success or non-zero if the tree is empty or key or value is
+ *   NULL.
+ */
+int c_avl_pick (c_avl_tree_t *t, void **key, void **value);
+
+c_avl_iterator_t *c_avl_get_iterator (c_avl_tree_t *t);
+int c_avl_iterator_next (c_avl_iterator_t *iter, void **key, void **value);
+int c_avl_iterator_prev (c_avl_iterator_t *iter, void **key, void **value);
+void c_avl_iterator_destroy (c_avl_iterator_t *iter);
+
+/*
+ * NAME
+ *   c_avl_size
+ *
+ * DESCRIPTION
+ *   Return the size (number of nodes) of the specified tree.
+ *
+ * PARAMETERS
+ *   `t'        AVL-tree to get the size of.
+ *
+ * RETURN VALUE
+ *   Number of nodes in the tree, 0 if the tree is empty or NULL.
+ */
+int c_avl_size (c_avl_tree_t *t);
+
+#endif /* UTILS_AVLTREE_H */
diff --git a/src/utils_cache.c b/src/utils_cache.c
new file mode 100644 (file)
index 0000000..dd5bcb5
--- /dev/null
@@ -0,0 +1,965 @@
+/**
+ * collectd - src/utils_cache.c
+ * Copyright (C) 2007-2010  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Florian octo Forster <octo at collectd.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "utils_avltree.h"
+#include "utils_cache.h"
+#include "meta_data.h"
+
+#include <assert.h>
+#include <pthread.h>
+
+typedef struct cache_entry_s
+{
+       char name[6 * DATA_MAX_NAME_LEN];
+       int        values_num;
+       gauge_t   *values_gauge;
+       value_t   *values_raw;
+       /* Time contained in the package
+        * (for calculating rates) */
+       cdtime_t last_time;
+       /* Time according to the local clock
+        * (for purging old entries) */
+       cdtime_t last_update;
+       /* Interval in which the data is collected
+        * (for purding old entries) */
+       cdtime_t interval;
+       int state;
+       int hits;
+
+       /*
+        * +-----+-----+-----+-----+-----+-----+-----+-----+-----+----
+        * !  0  !  1  !  2  !  3  !  4  !  5  !  6  !  7  !  8  ! ...
+        * +-----+-----+-----+-----+-----+-----+-----+-----+-----+----
+        * ! ds0 ! ds1 ! ds2 ! ds0 ! ds1 ! ds2 ! ds0 ! ds1 ! ds2 ! ...
+        * +-----+-----+-----+-----+-----+-----+-----+-----+-----+----
+        * !      t = 0      !      t = 1      !      t = 2      ! ...
+        * +-----------------+-----------------+-----------------+----
+        */
+       gauge_t *history;
+       size_t   history_index; /* points to the next position to write to. */
+       size_t   history_length;
+
+       meta_data_t *meta;
+} cache_entry_t;
+
+static c_avl_tree_t   *cache_tree = NULL;
+static pthread_mutex_t cache_lock = PTHREAD_MUTEX_INITIALIZER;
+
+static int cache_compare (const cache_entry_t *a, const cache_entry_t *b)
+{
+  assert ((a != NULL) && (b != NULL));
+  return (strcmp (a->name, b->name));
+} /* int cache_compare */
+
+static cache_entry_t *cache_alloc (int values_num)
+{
+  cache_entry_t *ce;
+
+  ce = (cache_entry_t *) malloc (sizeof (cache_entry_t));
+  if (ce == NULL)
+  {
+    ERROR ("utils_cache: cache_alloc: malloc failed.");
+    return (NULL);
+  }
+  memset (ce, '\0', sizeof (cache_entry_t));
+  ce->values_num = values_num;
+
+  ce->values_gauge = calloc (values_num, sizeof (*ce->values_gauge));
+  ce->values_raw   = calloc (values_num, sizeof (*ce->values_raw));
+  if ((ce->values_gauge == NULL) || (ce->values_raw == NULL))
+  {
+    sfree (ce->values_gauge);
+    sfree (ce->values_raw);
+    sfree (ce);
+    ERROR ("utils_cache: cache_alloc: calloc failed.");
+    return (NULL);
+  }
+
+  ce->history = NULL;
+  ce->history_length = 0;
+  ce->meta = NULL;
+
+  return (ce);
+} /* cache_entry_t *cache_alloc */
+
+static void cache_free (cache_entry_t *ce)
+{
+  if (ce == NULL)
+    return;
+
+  sfree (ce->values_gauge);
+  sfree (ce->values_raw);
+  sfree (ce->history);
+  if (ce->meta != NULL)
+  {
+    meta_data_destroy (ce->meta);
+    ce->meta = NULL;
+  }
+  sfree (ce);
+} /* void cache_free */
+
+static void uc_check_range (const data_set_t *ds, cache_entry_t *ce)
+{
+  int i;
+
+  for (i = 0; i < ds->ds_num; i++)
+  {
+    if (isnan (ce->values_gauge[i]))
+      continue;
+    else if (ce->values_gauge[i] < ds->ds[i].min)
+      ce->values_gauge[i] = NAN;
+    else if (ce->values_gauge[i] > ds->ds[i].max)
+      ce->values_gauge[i] = NAN;
+  }
+} /* void uc_check_range */
+
+static int uc_insert (const data_set_t *ds, const value_list_t *vl,
+    const char *key)
+{
+  int i;
+  char *key_copy;
+  cache_entry_t *ce;
+
+  /* `cache_lock' has been locked by `uc_update' */
+
+  key_copy = strdup (key);
+  if (key_copy == NULL)
+  {
+    ERROR ("uc_insert: strdup failed.");
+    return (-1);
+  }
+
+  ce = cache_alloc (ds->ds_num);
+  if (ce == NULL)
+  {
+    sfree (key_copy);
+    ERROR ("uc_insert: cache_alloc (%i) failed.", ds->ds_num);
+    return (-1);
+  }
+
+  sstrncpy (ce->name, key, sizeof (ce->name));
+
+  for (i = 0; i < ds->ds_num; i++)
+  {
+    switch (ds->ds[i].type)
+    {
+      case DS_TYPE_COUNTER:
+       ce->values_gauge[i] = NAN;
+       ce->values_raw[i].counter = vl->values[i].counter;
+       break;
+
+      case DS_TYPE_GAUGE:
+       ce->values_gauge[i] = vl->values[i].gauge;
+       ce->values_raw[i].gauge = vl->values[i].gauge;
+       break;
+
+      case DS_TYPE_DERIVE:
+       ce->values_gauge[i] = NAN;
+       ce->values_raw[i].derive = vl->values[i].derive;
+       break;
+
+      case DS_TYPE_ABSOLUTE:
+       ce->values_gauge[i] = NAN;
+       if (vl->interval > 0)
+         ce->values_gauge[i] = ((double) vl->values[i].absolute)
+           / CDTIME_T_TO_DOUBLE (vl->interval);
+       ce->values_raw[i].absolute = vl->values[i].absolute;
+       break;
+       
+      default:
+       /* This shouldn't happen. */
+       ERROR ("uc_insert: Don't know how to handle data source type %i.",
+           ds->ds[i].type);
+       return (-1);
+    } /* switch (ds->ds[i].type) */
+  } /* for (i) */
+
+  /* Prune invalid gauge data */
+  uc_check_range (ds, ce);
+
+  ce->last_time = vl->time;
+  ce->last_update = cdtime ();
+  ce->interval = vl->interval;
+  ce->state = STATE_OKAY;
+
+  if (c_avl_insert (cache_tree, key_copy, ce) != 0)
+  {
+    sfree (key_copy);
+    ERROR ("uc_insert: c_avl_insert failed.");
+    return (-1);
+  }
+
+  DEBUG ("uc_insert: Added %s to the cache.", key);
+  return (0);
+} /* int uc_insert */
+
+int uc_init (void)
+{
+  if (cache_tree == NULL)
+    cache_tree = c_avl_create ((int (*) (const void *, const void *))
+       cache_compare);
+
+  return (0);
+} /* 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;
+  int i;
+  
+  pthread_mutex_lock (&cache_lock);
+
+  now = cdtime ();
+
+  /* Build a list of entries to be flushed */
+  iter = c_avl_get_iterator (cache_tree);
+  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 = (char **) realloc ((void *) keys,
+       (keys_len + 1) * sizeof (char *));
+    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;
+
+    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;
+
+    keys[keys_len] = strdup (key);
+    if (keys[keys_len] == NULL)
+    {
+      ERROR ("uc_check_timeout: strdup failed.");
+      continue;
+    }
+    keys_time[keys_len] = ce->last_time;
+    keys_interval[keys_len] = ce->interval;
+
+    keys_len++;
+  } /* while (c_avl_iterator_next) */
+
+  c_avl_iterator_destroy (iter);
+  pthread_mutex_unlock (&cache_lock);
+
+  if (keys_len == 0)
+    return (0);
+
+  /* Call the "missing" callback for each value. Do this before removing the
+   * value from the cache, so that callbacks can still access the data stored,
+   * 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 (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]);
+      cache_free (ce);
+      continue;
+    }
+
+    vl.time = keys_time[i];
+    vl.interval = keys_interval[i];
+
+    plugin_dispatch_missing (&vl);
+  } /* for (i = 0; i < keys_len; 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 (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]);
+      continue;
+    }
+
+    sfree (keys[i]);
+    sfree (key);
+    cache_free (ce);
+  } /* for (i = 0; i < keys_len; i++) */
+  pthread_mutex_unlock (&cache_lock);
+
+  sfree (keys);
+  sfree (keys_time);
+  sfree (keys_interval);
+
+  return (0);
+} /* int uc_check_timeout */
+
+int uc_update (const data_set_t *ds, const value_list_t *vl)
+{
+  char name[6 * DATA_MAX_NAME_LEN];
+  cache_entry_t *ce = NULL;
+  int status;
+  int i;
+
+  if (FORMAT_VL (name, sizeof (name), vl) != 0)
+  {
+    ERROR ("uc_update: FORMAT_VL failed.");
+    return (-1);
+  }
+
+  pthread_mutex_lock (&cache_lock);
+
+  status = c_avl_get (cache_tree, name, (void *) &ce);
+  if (status != 0) /* entry does not yet exist */
+  {
+    status = uc_insert (ds, vl, name);
+    pthread_mutex_unlock (&cache_lock);
+    return (status);
+  }
+
+  assert (ce != NULL);
+  assert (ce->values_num == ds->ds_num);
+
+  if (ce->last_time >= vl->time)
+  {
+    pthread_mutex_unlock (&cache_lock);
+    NOTICE ("uc_update: Value too old: name = %s; value time = %.3f; "
+       "last cache update = %.3f;",
+       name,
+       CDTIME_T_TO_DOUBLE (vl->time),
+       CDTIME_T_TO_DOUBLE (ce->last_time));
+    return (-1);
+  }
+
+  for (i = 0; i < ds->ds_num; i++)
+  {
+    switch (ds->ds[i].type)
+    {
+      case DS_TYPE_COUNTER:
+       {
+         counter_t diff;
+
+         /* check if the counter has wrapped around */
+         if (vl->values[i].counter < ce->values_raw[i].counter)
+         {
+           if (ce->values_raw[i].counter <= 4294967295U)
+             diff = (4294967295U - ce->values_raw[i].counter)
+               + vl->values[i].counter;
+           else
+             diff = (18446744073709551615ULL - ce->values_raw[i].counter)
+               + vl->values[i].counter;
+         }
+         else /* counter has NOT wrapped around */
+         {
+           diff = vl->values[i].counter - ce->values_raw[i].counter;
+         }
+
+         ce->values_gauge[i] = ((double) diff)
+           / (CDTIME_T_TO_DOUBLE (vl->time - ce->last_time));
+         ce->values_raw[i].counter = vl->values[i].counter;
+       }
+       break;
+
+      case DS_TYPE_GAUGE:
+       ce->values_raw[i].gauge = vl->values[i].gauge;
+       ce->values_gauge[i] = vl->values[i].gauge;
+       break;
+
+      case DS_TYPE_DERIVE:
+       {
+         derive_t diff;
+
+         diff = vl->values[i].derive - ce->values_raw[i].derive;
+
+         ce->values_gauge[i] = ((double) diff)
+           / (CDTIME_T_TO_DOUBLE (vl->time - ce->last_time));
+         ce->values_raw[i].derive = vl->values[i].derive;
+       }
+       break;
+
+      case DS_TYPE_ABSOLUTE:
+       ce->values_gauge[i] = ((double) vl->values[i].absolute)
+         / (CDTIME_T_TO_DOUBLE (vl->time - ce->last_time));
+       ce->values_raw[i].absolute = vl->values[i].absolute;
+       break;
+
+      default:
+       /* This shouldn't happen. */
+       pthread_mutex_unlock (&cache_lock);
+       ERROR ("uc_update: Don't know how to handle data source type %i.",
+           ds->ds[i].type);
+       return (-1);
+    } /* switch (ds->ds[i].type) */
+
+    DEBUG ("uc_update: %s: ds[%i] = %lf", name, i, ce->values_gauge[i]);
+  } /* for (i) */
+
+  /* Update the history if it exists. */
+  if (ce->history != NULL)
+  {
+    assert (ce->history_index < ce->history_length);
+    for (i = 0; i < ce->values_num; i++)
+    {
+      size_t hist_idx = (ce->values_num * ce->history_index) + i;
+      ce->history[hist_idx] = ce->values_gauge[i];
+    }
+
+    assert (ce->history_length > 0);
+    ce->history_index = (ce->history_index + 1) % ce->history_length;
+  }
+
+  /* Prune invalid gauge data */
+  uc_check_range (ds, ce);
+
+  ce->last_time = vl->time;
+  ce->last_update = cdtime ();
+  ce->interval = vl->interval;
+
+  pthread_mutex_unlock (&cache_lock);
+
+  return (0);
+} /* int uc_update */
+
+int uc_get_rate_by_name (const char *name, gauge_t **ret_values, size_t *ret_values_num)
+{
+  gauge_t *ret = NULL;
+  size_t ret_num = 0;
+  cache_entry_t *ce = NULL;
+  int status = 0;
+
+  pthread_mutex_lock (&cache_lock);
+
+  if (c_avl_get (cache_tree, name, (void *) &ce) == 0)
+  {
+    assert (ce != NULL);
+
+    /* remove missing values from getval */
+    if (ce->state == STATE_MISSING)
+    {
+      status = -1;
+    }
+    else
+    {
+      ret_num = ce->values_num;
+      ret = (gauge_t *) malloc (ret_num * sizeof (gauge_t));
+      if (ret == NULL)
+      {
+        ERROR ("utils_cache: uc_get_rate_by_name: malloc failed.");
+        status = -1;
+      }
+      else
+      {
+        memcpy (ret, ce->values_gauge, ret_num * sizeof (gauge_t));
+      }
+    }
+  }
+  else
+  {
+    DEBUG ("utils_cache: uc_get_rate_by_name: No such value: %s", name);
+    status = -1;
+  }
+
+  pthread_mutex_unlock (&cache_lock);
+
+  if (status == 0)
+  {
+    *ret_values = ret;
+    *ret_values_num = ret_num;
+  }
+
+  return (status);
+} /* gauge_t *uc_get_rate_by_name */
+
+gauge_t *uc_get_rate (const data_set_t *ds, const value_list_t *vl)
+{
+  char name[6 * DATA_MAX_NAME_LEN];
+  gauge_t *ret = NULL;
+  size_t ret_num = 0;
+  int status;
+
+  if (FORMAT_VL (name, sizeof (name), vl) != 0)
+  {
+    ERROR ("utils_cache: uc_get_rate: FORMAT_VL failed.");
+    return (NULL);
+  }
+
+  status = uc_get_rate_by_name (name, &ret, &ret_num);
+  if (status != 0)
+    return (NULL);
+
+  /* This is important - the caller has no other way of knowing how many
+   * values are returned. */
+  if (ret_num != (size_t) ds->ds_num)
+  {
+    ERROR ("utils_cache: uc_get_rate: ds[%s] has %i values, "
+       "but uc_get_rate_by_name returned %zu.",
+       ds->type, ds->ds_num, ret_num);
+    sfree (ret);
+    return (NULL);
+  }
+
+  return (ret);
+} /* gauge_t *uc_get_rate */
+
+int uc_get_names (char ***ret_names, cdtime_t **ret_times, size_t *ret_number)
+{
+  c_avl_iterator_t *iter;
+  char *key;
+  cache_entry_t *value;
+
+  char **names = NULL;
+  cdtime_t *times = NULL;
+  size_t number = 0;
+
+  int status = 0;
+
+  if ((ret_names == NULL) || (ret_number == NULL))
+    return (-1);
+
+  pthread_mutex_lock (&cache_lock);
+
+  iter = c_avl_get_iterator (cache_tree);
+  while (c_avl_iterator_next (iter, (void *) &key, (void *) &value) == 0)
+  {
+    char **temp;
+
+    /* remove missing values when list values */
+    if (value->state == STATE_MISSING)
+      continue;
+
+    if (ret_times != NULL)
+    {
+      cdtime_t *tmp_times;
+
+      tmp_times = (cdtime_t *) realloc (times, sizeof (cdtime_t) * (number + 1));
+      if (tmp_times == NULL)
+      {
+       status = -1;
+       break;
+      }
+      times = tmp_times;
+      times[number] = value->last_time;
+    }
+
+    temp = (char **) realloc (names, sizeof (char *) * (number + 1));
+    if (temp == NULL)
+    {
+      status = -1;
+      break;
+    }
+    names = temp;
+    names[number] = strdup (key);
+    if (names[number] == NULL)
+    {
+      status = -1;
+      break;
+    }
+    number++;
+  } /* while (c_avl_iterator_next) */
+
+  c_avl_iterator_destroy (iter);
+  pthread_mutex_unlock (&cache_lock);
+
+  if (status != 0)
+  {
+    size_t i;
+    
+    for (i = 0; i < number; i++)
+    {
+      sfree (names[i]);
+    }
+    sfree (names);
+
+    return (-1);
+  }
+
+  *ret_names = names;
+  if (ret_times != NULL)
+    *ret_times = times;
+  *ret_number = number;
+
+  return (0);
+} /* int uc_get_names */
+
+int uc_get_state (const data_set_t *ds, const value_list_t *vl)
+{
+  char name[6 * DATA_MAX_NAME_LEN];
+  cache_entry_t *ce = NULL;
+  int ret = STATE_ERROR;
+
+  if (FORMAT_VL (name, sizeof (name), vl) != 0)
+  {
+    ERROR ("uc_get_state: FORMAT_VL failed.");
+    return (STATE_ERROR);
+  }
+
+  pthread_mutex_lock (&cache_lock);
+
+  if (c_avl_get (cache_tree, name, (void *) &ce) == 0)
+  {
+    assert (ce != NULL);
+    ret = ce->state;
+  }
+
+  pthread_mutex_unlock (&cache_lock);
+
+  return (ret);
+} /* int uc_get_state */
+
+int uc_set_state (const data_set_t *ds, const value_list_t *vl, int state)
+{
+  char name[6 * DATA_MAX_NAME_LEN];
+  cache_entry_t *ce = NULL;
+  int ret = -1;
+
+  if (FORMAT_VL (name, sizeof (name), vl) != 0)
+  {
+    ERROR ("uc_get_state: FORMAT_VL failed.");
+    return (STATE_ERROR);
+  }
+
+  pthread_mutex_lock (&cache_lock);
+
+  if (c_avl_get (cache_tree, name, (void *) &ce) == 0)
+  {
+    assert (ce != NULL);
+    ret = ce->state;
+    ce->state = state;
+  }
+
+  pthread_mutex_unlock (&cache_lock);
+
+  return (ret);
+} /* int uc_set_state */
+
+int uc_get_history_by_name (const char *name,
+    gauge_t *ret_history, size_t num_steps, size_t num_ds)
+{
+  cache_entry_t *ce = NULL;
+  size_t i;
+  int status = 0;
+
+  pthread_mutex_lock (&cache_lock);
+
+  status = c_avl_get (cache_tree, name, (void *) &ce);
+  if (status != 0)
+  {
+    pthread_mutex_unlock (&cache_lock);
+    return (-ENOENT);
+  }
+
+  if (((size_t) ce->values_num) != num_ds)
+  {
+    pthread_mutex_unlock (&cache_lock);
+    return (-EINVAL);
+  }
+
+  /* Check if there are enough values available. If not, increase the buffer
+   * size. */
+  if (ce->history_length < num_steps)
+  {
+    gauge_t *tmp;
+    size_t i;
+
+    tmp = realloc (ce->history, sizeof (*ce->history)
+       * num_steps * ce->values_num);
+    if (tmp == NULL)
+    {
+      pthread_mutex_unlock (&cache_lock);
+      return (-ENOMEM);
+    }
+
+    for (i = ce->history_length * ce->values_num;
+       i < (num_steps * ce->values_num);
+       i++)
+      tmp[i] = NAN;
+
+    ce->history = tmp;
+    ce->history_length = num_steps;
+  } /* if (ce->history_length < num_steps) */
+
+  /* Copy the values to the output buffer. */
+  for (i = 0; i < num_steps; i++)
+  {
+    size_t src_index;
+    size_t dst_index;
+
+    if (i < ce->history_index)
+      src_index = ce->history_index - (i + 1);
+    else
+      src_index = ce->history_length + ce->history_index - (i + 1);
+    src_index = src_index * num_ds;
+
+    dst_index = i * num_ds;
+
+    memcpy (ret_history + dst_index, ce->history + src_index,
+       sizeof (*ret_history) * num_ds);
+  }
+
+  pthread_mutex_unlock (&cache_lock);
+
+  return (0);
+} /* int uc_get_history_by_name */
+
+int uc_get_history (const data_set_t *ds, const value_list_t *vl,
+    gauge_t *ret_history, size_t num_steps, size_t num_ds)
+{
+  char name[6 * DATA_MAX_NAME_LEN];
+
+  if (FORMAT_VL (name, sizeof (name), vl) != 0)
+  {
+    ERROR ("utils_cache: uc_get_history: FORMAT_VL failed.");
+    return (-1);
+  }
+
+  return (uc_get_history_by_name (name, ret_history, num_steps, num_ds));
+} /* int uc_get_history */
+
+int uc_get_hits (const data_set_t *ds, const value_list_t *vl)
+{
+  char name[6 * DATA_MAX_NAME_LEN];
+  cache_entry_t *ce = NULL;
+  int ret = STATE_ERROR;
+
+  if (FORMAT_VL (name, sizeof (name), vl) != 0)
+  {
+    ERROR ("uc_get_state: FORMAT_VL failed.");
+    return (STATE_ERROR);
+  }
+
+  pthread_mutex_lock (&cache_lock);
+
+  if (c_avl_get (cache_tree, name, (void *) &ce) == 0)
+  {
+    assert (ce != NULL);
+    ret = ce->hits;
+  }
+
+  pthread_mutex_unlock (&cache_lock);
+
+  return (ret);
+} /* int uc_get_hits */
+
+int uc_set_hits (const data_set_t *ds, const value_list_t *vl, int hits)
+{
+  char name[6 * DATA_MAX_NAME_LEN];
+  cache_entry_t *ce = NULL;
+  int ret = -1;
+
+  if (FORMAT_VL (name, sizeof (name), vl) != 0)
+  {
+    ERROR ("uc_get_state: FORMAT_VL failed.");
+    return (STATE_ERROR);
+  }
+
+  pthread_mutex_lock (&cache_lock);
+
+  if (c_avl_get (cache_tree, name, (void *) &ce) == 0)
+  {
+    assert (ce != NULL);
+    ret = ce->hits;
+    ce->hits = hits;
+  }
+
+  pthread_mutex_unlock (&cache_lock);
+
+  return (ret);
+} /* int uc_set_hits */
+
+int uc_inc_hits (const data_set_t *ds, const value_list_t *vl, int step)
+{
+  char name[6 * DATA_MAX_NAME_LEN];
+  cache_entry_t *ce = NULL;
+  int ret = -1;
+
+  if (FORMAT_VL (name, sizeof (name), vl) != 0)
+  {
+    ERROR ("uc_get_state: FORMAT_VL failed.");
+    return (STATE_ERROR);
+  }
+
+  pthread_mutex_lock (&cache_lock);
+
+  if (c_avl_get (cache_tree, name, (void *) &ce) == 0)
+  {
+    assert (ce != NULL);
+    ret = ce->hits;
+    ce->hits = ret + step;
+  }
+
+  pthread_mutex_unlock (&cache_lock);
+
+  return (ret);
+} /* int uc_inc_hits */
+
+/*
+ * Meta data interface
+ */
+/* XXX: This function will acquire `cache_lock' but will not free it! */
+static meta_data_t *uc_get_meta (const value_list_t *vl) /* {{{ */
+{
+  char name[6 * DATA_MAX_NAME_LEN];
+  cache_entry_t *ce = NULL;
+  int status;
+
+  status = FORMAT_VL (name, sizeof (name), vl);
+  if (status != 0)
+  {
+    ERROR ("utils_cache: uc_get_meta: FORMAT_VL failed.");
+    return (NULL);
+  }
+
+  pthread_mutex_lock (&cache_lock);
+
+  status = c_avl_get (cache_tree, name, (void *) &ce);
+  if (status != 0)
+  {
+    pthread_mutex_unlock (&cache_lock);
+    return (NULL);
+  }
+  assert (ce != NULL);
+
+  if (ce->meta == NULL)
+    ce->meta = meta_data_create ();
+
+  if (ce->meta == NULL)
+    pthread_mutex_unlock (&cache_lock);
+
+  return (ce->meta);
+} /* }}} meta_data_t *uc_get_meta */
+
+/* Sorry about this preprocessor magic, but it really makes this file much
+ * shorter.. */
+#define UC_WRAP(wrap_function) { \
+  meta_data_t *meta; \
+  int status; \
+  meta = uc_get_meta (vl); \
+  if (meta == NULL) return (-1); \
+  status = wrap_function (meta, key); \
+  pthread_mutex_unlock (&cache_lock); \
+  return (status); \
+}
+int uc_meta_data_exists (const value_list_t *vl, const char *key)
+  UC_WRAP (meta_data_exists)
+
+int uc_meta_data_delete (const value_list_t *vl, const char *key)
+  UC_WRAP (meta_data_delete)
+#undef UC_WRAP
+
+/* We need a new version of this macro because the following functions take
+ * two argumetns. */
+#define UC_WRAP(wrap_function) { \
+  meta_data_t *meta; \
+  int status; \
+  meta = uc_get_meta (vl); \
+  if (meta == NULL) return (-1); \
+  status = wrap_function (meta, key, value); \
+  pthread_mutex_unlock (&cache_lock); \
+  return (status); \
+}
+int uc_meta_data_add_string (const value_list_t *vl,
+    const char *key,
+    const char *value)
+  UC_WRAP(meta_data_add_string)
+int uc_meta_data_add_signed_int (const value_list_t *vl,
+    const char *key,
+    int64_t value)
+  UC_WRAP(meta_data_add_signed_int)
+int uc_meta_data_add_unsigned_int (const value_list_t *vl,
+    const char *key,
+    uint64_t value)
+  UC_WRAP(meta_data_add_unsigned_int)
+int uc_meta_data_add_double (const value_list_t *vl,
+    const char *key,
+    double value)
+  UC_WRAP(meta_data_add_double)
+int uc_meta_data_add_boolean (const value_list_t *vl,
+    const char *key,
+    _Bool value)
+  UC_WRAP(meta_data_add_boolean)
+
+int uc_meta_data_get_string (const value_list_t *vl,
+    const char *key,
+    char **value)
+  UC_WRAP(meta_data_get_string)
+int uc_meta_data_get_signed_int (const value_list_t *vl,
+    const char *key,
+    int64_t *value)
+  UC_WRAP(meta_data_get_signed_int)
+int uc_meta_data_get_unsigned_int (const value_list_t *vl,
+    const char *key,
+    uint64_t *value)
+  UC_WRAP(meta_data_get_unsigned_int)
+int uc_meta_data_get_double (const value_list_t *vl,
+    const char *key,
+    double *value)
+  UC_WRAP(meta_data_get_double)
+int uc_meta_data_get_boolean (const value_list_t *vl,
+    const char *key,
+    _Bool *value)
+  UC_WRAP(meta_data_get_boolean)
+#undef UC_WRAP
+
+/* vim: set sw=2 ts=8 sts=2 tw=78 : */
diff --git a/src/utils_cache.h b/src/utils_cache.h
new file mode 100644 (file)
index 0000000..87f93c0
--- /dev/null
@@ -0,0 +1,90 @@
+/**
+ * collectd - src/utils_cache.h
+ * Copyright (C) 2007  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef UTILS_CACHE_H
+#define UTILS_CACHE_H 1
+
+#include "plugin.h"
+
+#define STATE_OKAY     0
+#define STATE_WARNING  1
+#define STATE_ERROR    2
+#define STATE_MISSING 15
+
+int uc_init (void);
+int uc_check_timeout (void);
+int uc_update (const data_set_t *ds, const value_list_t *vl);
+int uc_get_rate_by_name (const char *name, gauge_t **ret_values, size_t *ret_values_num);
+gauge_t *uc_get_rate (const data_set_t *ds, const value_list_t *vl);
+
+int uc_get_names (char ***ret_names, cdtime_t **ret_times, size_t *ret_number);
+
+int uc_get_state (const data_set_t *ds, const value_list_t *vl);
+int uc_set_state (const data_set_t *ds, const value_list_t *vl, int state);
+int uc_get_hits (const data_set_t *ds, const value_list_t *vl);
+int uc_set_hits (const data_set_t *ds, const value_list_t *vl, int hits);
+int uc_inc_hits (const data_set_t *ds, const value_list_t *vl, int step);
+
+int uc_get_history (const data_set_t *ds, const value_list_t *vl,
+    gauge_t *ret_history, size_t num_steps, size_t num_ds);
+int uc_get_history_by_name (const char *name,
+    gauge_t *ret_history, size_t num_steps, size_t num_ds);
+
+/*
+ * Meta data interface
+ */
+int uc_meta_data_exists (const value_list_t *vl, const char *key);
+int uc_meta_data_delete (const value_list_t *vl, const char *key);
+
+int uc_meta_data_add_string (const value_list_t *vl,
+    const char *key,
+    const char *value);
+int uc_meta_data_add_signed_int (const value_list_t *vl,
+    const char *key,
+    int64_t value);
+int uc_meta_data_add_unsigned_int (const value_list_t *vl,
+    const char *key,
+    uint64_t value);
+int uc_meta_data_add_double (const value_list_t *vl,
+    const char *key,
+    double value);
+int uc_meta_data_add_boolean (const value_list_t *vl,
+    const char *key,
+    _Bool value);
+
+int uc_meta_data_get_string (const value_list_t *vl,
+    const char *key,
+    char **value);
+int uc_meta_data_get_signed_int (const value_list_t *vl,
+    const char *key,
+    int64_t *value);
+int uc_meta_data_get_unsigned_int (const value_list_t *vl,
+    const char *key,
+    uint64_t *value);
+int uc_meta_data_get_double (const value_list_t *vl,
+    const char *key,
+    double *value);
+int uc_meta_data_get_boolean (const value_list_t *vl,
+    const char *key,
+    _Bool *value);
+
+/* vim: set shiftwidth=2 softtabstop=2 tabstop=8 : */
+#endif /* !UTILS_CACHE_H */
diff --git a/src/utils_cmd_flush.c b/src/utils_cmd_flush.c
new file mode 100644 (file)
index 0000000..3584f3b
--- /dev/null
@@ -0,0 +1,181 @@
+/**
+ * collectd - src/utils_cmd_flush.c
+ * Copyright (C) 2008  Sebastian Harl
+ * Copyright (C) 2008  Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Sebastian "tokkee" Harl <sh at tokkee.org>
+ *   Florian "octo" Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "utils_parse_option.h"
+
+#define print_to_socket(fh, ...) \
+       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))); \
+               return -1; \
+       }
+
+static int add_to_array (char ***array, int *array_num, char *value)
+{
+       char **temp;
+
+       temp = (char **) realloc (*array, sizeof (char *) * (*array_num + 1));
+       if (temp == NULL)
+               return (-1);
+
+       *array = temp;
+       (*array)[*array_num] = value;
+       (*array_num)++;
+
+       return (0);
+} /* int add_to_array */
+
+int handle_flush (FILE *fh, char *buffer)
+{
+       int success = 0;
+       int error   = 0;
+
+       double timeout = 0.0;
+       char **plugins = NULL;
+       int plugins_num = 0;
+       char **identifiers = NULL;
+       int identifiers_num = 0;
+
+       int i;
+
+       if ((fh == NULL) || (buffer == NULL))
+               return (-1);
+
+       DEBUG ("utils_cmd_flush: handle_flush (fh = %p, buffer = %s);",
+                       (void *) fh, buffer);
+
+       if (strncasecmp ("FLUSH", buffer, strlen ("FLUSH")) != 0)
+       {
+               print_to_socket (fh, "-1 Cannot parse command.\n");
+               return (-1);
+       }
+       buffer += strlen ("FLUSH");
+
+       while (*buffer != 0)
+       {
+               char *opt_key;
+               char *opt_value;
+               int status;
+
+               opt_key = NULL;
+               opt_value = NULL;
+               status = parse_option (&buffer, &opt_key, &opt_value);
+               if (status != 0)
+               {
+                       print_to_socket (fh, "-1 Parsing options failed.\n");
+                       sfree (plugins);
+                       sfree (identifiers);
+                       return (-1);
+               }
+
+               if (strcasecmp ("plugin", opt_key) == 0)
+               {
+                       add_to_array (&plugins, &plugins_num, opt_value);
+               }
+               else if (strcasecmp ("identifier", opt_key) == 0)
+               {
+                       add_to_array (&identifiers, &identifiers_num, opt_value);
+               }
+               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_socket (fh, "-1 Invalid value for option `timeout': "
+                                               "%s\n", opt_value);
+                               sfree (plugins);
+                               sfree (identifiers);
+                               return (-1);
+                       }
+                       else if (timeout < 0.0)
+                       {
+                               timeout = 0.0;
+                       }
+               }
+               else
+               {
+                       print_to_socket (fh, "-1 Cannot parse option %s\n", opt_key);
+                       sfree (plugins);
+                       sfree (identifiers);
+                       return (-1);
+               }
+       } /* while (*buffer != 0) */
+
+       /* Add NULL entries for `any plugin' and/or `any value' if nothing was
+        * specified. */
+       if (plugins_num == 0)
+               add_to_array (&plugins, &plugins_num, NULL);
+
+       if (identifiers_num == 0)
+               add_to_array (&identifiers, &identifiers_num, NULL);
+
+       for (i = 0; i < plugins_num; i++)
+       {
+               char *plugin;
+               int j;
+
+               plugin = plugins[i];
+
+               for (j = 0; j < identifiers_num; j++)
+               {
+                       char *identifier;
+                       int status;
+
+                       identifier = identifiers[j];
+                       status = plugin_flush (plugin,
+                                       DOUBLE_TO_CDTIME_T (timeout),
+                                       identifier);
+                       if (status == 0)
+                               success++;
+                       else
+                               error++;
+               }
+       }
+
+       if ((success + error) > 0)
+       {
+               print_to_socket (fh, "0 Done: %i successful, %i errors\n",
+                               success, error);
+       }
+       else
+       {
+               plugin_flush (NULL, timeout, NULL);
+               print_to_socket (fh, "0 Done\n");
+       }
+
+       sfree (plugins);
+       sfree (identifiers);
+       return (0);
+} /* int handle_flush */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
+
diff --git a/src/utils_cmd_flush.h b/src/utils_cmd_flush.h
new file mode 100644 (file)
index 0000000..6b54ace
--- /dev/null
@@ -0,0 +1,32 @@
+/**
+ * collectd - src/utils_cmd_flush.h
+ * Copyright (C) 2008  Sebastian Harl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Sebastian "tokkee" Harl <sh at tokkee.org>
+ **/
+
+#ifndef UTILS_CMD_FLUSH_H
+#define UTILS_CMD_FLUSH_H 1
+
+#include <stdio.h>
+
+int handle_flush (FILE *fh, char *buffer);
+
+#endif /* UTILS_CMD_FLUSH_H */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
+
diff --git a/src/utils_cmd_getthreshold.c b/src/utils_cmd_getthreshold.c
new file mode 100644 (file)
index 0000000..e8c29fa
--- /dev/null
@@ -0,0 +1,178 @@
+/**
+ * collectd - src/utils_cms_getthreshold.c
+ * Copyright (C) 2008,2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include "utils_threshold.h"
+#include "utils_parse_option.h" /* for `parse_string' */
+#include "utils_cmd_getthreshold.h"
+
+#define print_to_socket(fh, ...) \
+  if (fprintf (fh, __VA_ARGS__) < 0) { \
+    char errbuf[1024]; \
+    WARNING ("handle_getthreshold: failed to write to socket #%i: %s", \
+        fileno (fh), sstrerror (errno, errbuf, sizeof (errbuf))); \
+    return -1; \
+  }
+
+int handle_getthreshold (FILE *fh, char *buffer)
+{
+  char *command;
+  char *identifier;
+  char *identifier_copy;
+
+  char *host;
+  char *plugin;
+  char *plugin_instance;
+  char *type;
+  char *type_instance;
+
+  value_list_t vl;
+  threshold_t threshold;
+
+  int   status;
+  size_t i;
+
+  if ((fh == NULL) || (buffer == NULL))
+    return (-1);
+
+  DEBUG ("utils_cmd_getthreshold: handle_getthreshold (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 ("GETTHRESHOLD", command) != 0)
+  {
+    print_to_socket (fh, "-1 Unexpected command: `%s'.\n", command);
+    return (-1);
+  }
+
+  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, &host,
+      &plugin, &plugin_instance,
+      &type, &type_instance);
+  if (status != 0)
+  {
+    DEBUG ("handle_getthreshold: Cannot parse identifier `%s'.", identifier);
+    print_to_socket (fh, "-1 Cannot parse identifier `%s'.\n", identifier);
+    sfree (identifier_copy);
+    return (-1);
+  }
+
+  memset (&vl, 0, sizeof (vl));
+  sstrncpy (vl.host, host, sizeof (vl.host));
+  sstrncpy (vl.plugin, plugin, sizeof (vl.plugin));
+  if (plugin_instance != NULL)
+    sstrncpy (vl.plugin_instance, plugin_instance, 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));
+  sfree (identifier_copy);
+
+  memset (&threshold, 0, sizeof (threshold));
+  status = ut_search_threshold (&vl, &threshold);
+  if (status == ENOENT)
+  {
+    print_to_socket (fh, "-1 No threshold found for identifier %s\n",
+        identifier);
+    return (0);
+  }
+  else if (status != 0)
+  {
+    print_to_socket (fh, "-1 Error while looking up threshold: %i\n",
+        status);
+    return (-1);
+  }
+
+  /* Lets count the number of lines we'll return. */
+  i = 0;
+  if (threshold.host[0] != 0)            i++;
+  if (threshold.plugin[0] != 0)          i++;
+  if (threshold.plugin_instance[0] != 0) i++;
+  if (threshold.type[0] != 0)            i++;
+  if (threshold.type_instance[0] != 0)   i++;
+  if (threshold.data_source[0] != 0)     i++;
+  if (!isnan (threshold.warning_min))    i++;
+  if (!isnan (threshold.warning_max))    i++;
+  if (!isnan (threshold.failure_min))    i++;
+  if (!isnan (threshold.failure_max))    i++;
+  if (threshold.hysteresis > 0.0)        i++;
+  if (threshold.hits > 1)                i++;
+
+  /* Print the response */
+  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);
+} /* int handle_getthreshold */
+
+/* vim: set sw=2 sts=2 ts=8 et : */
diff --git a/src/utils_cmd_getthreshold.h b/src/utils_cmd_getthreshold.h
new file mode 100644 (file)
index 0000000..5481cfd
--- /dev/null
@@ -0,0 +1,31 @@
+/**
+ * collectd - src/utils_cmd_getthreshold.h
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef UTILS_CMD_GETTHRESHOLD_H
+#define UTILS_CMD_GETTHRESHOLD_H 1
+
+#include <stdio.h>
+
+int handle_getthreshold (FILE *fh, char *buffer);
+
+#endif /* UTILS_CMD_GETTHRESHOLD_H */
+
+/* vim: set sw=2 sts=2 ts=8 : */
diff --git a/src/utils_cmd_getval.c b/src/utils_cmd_getval.c
new file mode 100644 (file)
index 0000000..ce3e28e
--- /dev/null
@@ -0,0 +1,158 @@
+/**
+ * collectd - src/utils_cms_getval.c
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include "utils_cache.h"
+#include "utils_parse_option.h"
+
+#define print_to_socket(fh, ...) \
+  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))); \
+    return -1; \
+  }
+
+int handle_getval (FILE *fh, char *buffer)
+{
+  char *command;
+  char *identifier;
+  char *identifier_copy;
+
+  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;
+  size_t i;
+
+  if ((fh == NULL) || (buffer == NULL))
+    return (-1);
+
+  DEBUG ("utils_cmd_getval: 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);
+  }
+
+  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);
+  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);
+    return (-1);
+  }
+
+  values = NULL;
+  values_num = 0;
+  status = uc_get_rate_by_name (identifier, &values, &values_num);
+  if (status != 0)
+  {
+    print_to_socket (fh, "-1 No such value\n");
+    sfree (identifier_copy);
+    return (-1);
+  }
+
+  if ((size_t) ds->ds_num != values_num)
+  {
+    ERROR ("ds[%s]->ds_num = %i, "
+       "but uc_get_rate_by_name returned %u values.",
+       ds->type, ds->ds_num, (unsigned int) values_num);
+    print_to_socket (fh, "-1 Error reading value from cache.\n");
+    sfree (values);
+    sfree (identifier_copy);
+    return (-1);
+  }
+
+  print_to_socket (fh, "%u Value%s found\n", (unsigned int) values_num,
+      (values_num == 1) ? "" : "s");
+  for (i = 0; i < values_num; i++)
+  {
+    print_to_socket (fh, "%s=", ds->ds[i].name);
+    if (isnan (values[i]))
+    {
+      print_to_socket (fh, "NaN\n");
+    }
+    else
+    {
+      print_to_socket (fh, "%12e\n", values[i]);
+    }
+  }
+
+  sfree (values);
+  sfree (identifier_copy);
+
+  return (0);
+} /* int handle_getval */
+
+/* vim: set sw=2 sts=2 ts=8 : */
diff --git a/src/utils_cmd_getval.h b/src/utils_cmd_getval.h
new file mode 100644 (file)
index 0000000..ed9ca9a
--- /dev/null
@@ -0,0 +1,31 @@
+/**
+ * collectd - src/utils_cms_getval.h
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef UTILS_CMD_GETVAL_H
+#define UTILS_CMD_GETVAL_H 1
+
+#include <stdio.h>
+
+int handle_getval (FILE *fh, char *buffer);
+
+#endif /* UTILS_CMD_GETVAL_H */
+
+/* vim: set sw=2 sts=2 ts=8 : */
diff --git a/src/utils_cmd_listval.c b/src/utils_cmd_listval.c
new file mode 100644 (file)
index 0000000..ef66af5
--- /dev/null
@@ -0,0 +1,99 @@
+/**
+ * collectd - src/utils_cms_listval.c
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include "utils_cmd_listval.h"
+#include "utils_cache.h"
+#include "utils_parse_option.h"
+
+#define free_everything_and_return(status) do { \
+    size_t j; \
+    for (j = 0; j < number; j++) { \
+      sfree(names[j]); \
+      names[j] = NULL; \
+    } \
+    sfree(names); \
+    sfree(times); \
+    return (status); \
+  } while (0)
+
+#define print_to_socket(fh, ...) \
+  if (fprintf (fh, __VA_ARGS__) < 0) { \
+    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); \
+  }
+
+int handle_listval (FILE *fh, char *buffer)
+{
+  char *command;
+  char **names = NULL;
+  cdtime_t *times = NULL;
+  size_t number = 0;
+  size_t i;
+  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);
+  }
+
+  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);
+  }
+
+  print_to_socket (fh, "%i Value%s found\n",
+      (int) number, (number == 1) ? "" : "s");
+  for (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 */
+
+/* vim: set sw=2 sts=2 ts=8 : */
diff --git a/src/utils_cmd_listval.h b/src/utils_cmd_listval.h
new file mode 100644 (file)
index 0000000..0c72d67
--- /dev/null
@@ -0,0 +1,31 @@
+/**
+ * collectd - src/utils_cms_listval.h
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef UTILS_CMD_LISTVAL_H
+#define UTILS_CMD_LISTVAL_H 1
+
+#include <stdio.h>
+
+int handle_listval (FILE *fh, char *buffer);
+
+#endif /* UTILS_CMD_LISTVAL_H */
+
+/* vim: set sw=2 sts=2 ts=8 : */
diff --git a/src/utils_cmd_putnotif.c b/src/utils_cmd_putnotif.c
new file mode 100644 (file)
index 0000000..5a9faff
--- /dev/null
@@ -0,0 +1,171 @@
+/**
+ * collectd - src/utils_cms_putnotif.c
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include "utils_parse_option.h"
+
+#define print_to_socket(fh, ...) \
+  if (fprintf (fh, __VA_ARGS__) < 0) { \
+    char errbuf[1024]; \
+    WARNING ("handle_putnotif: failed to write to socket #%i: %s", \
+       fileno (fh), sstrerror (errno, errbuf, sizeof (errbuf))); \
+    return -1; \
+  }
+
+static int set_option_severity (notification_t *n, const char *value)
+{
+  if (strcasecmp (value, "Failure") == 0)
+    n->severity = NOTIF_FAILURE;
+  else if (strcasecmp (value, "Warning") == 0)
+    n->severity = NOTIF_WARNING;
+  else if (strcasecmp (value, "Okay") == 0)
+    n->severity = NOTIF_OKAY;
+  else
+    return (-1);
+
+  return (0);
+} /* int set_option_severity */
+
+static int set_option_time (notification_t *n, const char *value)
+{
+  time_t tmp;
+  
+  tmp = (time_t) atoi (value);
+  if (tmp <= 0)
+    return (-1);
+
+  n->time = tmp;
+
+  return (0);
+} /* int set_option_time */
+
+static int set_option (notification_t *n, const char *option, const char *value)
+{
+  if ((n == NULL) || (option == NULL) || (value == NULL))
+    return (-1);
+
+  DEBUG ("utils_cmd_putnotif: set_option (option = %s, value = %s);",
+      option, value);
+
+  if (strcasecmp ("severity", option) == 0)
+    return (set_option_severity (n, value));
+  else if (strcasecmp ("time", option) == 0)
+    return (set_option_time (n, value));
+  else if (strcasecmp ("message", option) == 0)
+    sstrncpy (n->message, value, sizeof (n->message));
+  else if (strcasecmp ("host", option) == 0)
+    sstrncpy (n->host, value, sizeof (n->host));
+  else if (strcasecmp ("plugin", option) == 0)
+    sstrncpy (n->plugin, value, sizeof (n->plugin));
+  else if (strcasecmp ("plugin_instance", option) == 0)
+    sstrncpy (n->plugin_instance, value, sizeof (n->plugin_instance));
+  else if (strcasecmp ("type", option) == 0)
+    sstrncpy (n->type, value, sizeof (n->type));
+  else if (strcasecmp ("type_instance", option) == 0)
+    sstrncpy (n->type_instance, value, sizeof (n->type_instance));
+  else
+    return (1);
+
+  return (0);
+} /* int set_option */
+
+int handle_putnotif (FILE *fh, char *buffer)
+{
+  char *command;
+  notification_t n;
+  int status;
+
+  if ((fh == NULL) || (buffer == NULL))
+    return (-1);
+
+  DEBUG ("utils_cmd_putnotif: handle_putnotif (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 ("PUTNOTIF", command) != 0)
+  {
+    print_to_socket (fh, "-1 Unexpected command: `%s'.\n", command);
+    return (-1);
+  }
+
+  memset (&n, '\0', sizeof (n));
+
+  status = 0;
+  while (*buffer != 0)
+  {
+    char *key;
+    char *value;
+
+    status = parse_option (&buffer, &key, &value);
+    if (status != 0)
+    {
+      print_to_socket (fh, "-1 Malformed option.\n");
+      break;
+    }
+
+    status = set_option (&n, key, value);
+    if (status != 0)
+    {
+      print_to_socket (fh, "-1 Error parsing option `%s'\n", key);
+      break;
+    }
+  } /* for (i) */
+
+  /* Check for required fields and complain if anything is missing. */
+  if ((status == 0) && (n.severity == 0))
+  {
+    print_to_socket (fh, "-1 Option `severity' missing.\n");
+    status = -1;
+  }
+  if ((status == 0) && (n.time == 0))
+  {
+    print_to_socket (fh, "-1 Option `time' missing.\n");
+    status = -1;
+  }
+  if ((status == 0) && (strlen (n.message) == 0))
+  {
+    print_to_socket (fh, "-1 No message or message of length 0 given.\n");
+    status = -1;
+  }
+
+  /* If status is still zero the notification is fine and we can finally
+   * dispatch it. */
+  if (status == 0)
+  {
+    plugin_dispatch_notification (&n);
+    print_to_socket (fh, "0 Success\n");
+  }
+
+  return (0);
+} /* int handle_putnotif */
+
+/* vim: set shiftwidth=2 softtabstop=2 tabstop=8 : */
diff --git a/src/utils_cmd_putnotif.h b/src/utils_cmd_putnotif.h
new file mode 100644 (file)
index 0000000..7e900b5
--- /dev/null
@@ -0,0 +1,31 @@
+/**
+ * collectd - src/utils_cms_putnotif.h
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef UTILS_CMD_PUTNOTIF_H
+#define UTILS_CMD_PUTNOTIF_H 1
+
+#include <stdio.h>
+
+int handle_putnotif (FILE *fh, char *buffer);
+
+/* vim: set shiftwidth=2 softtabstop=2 tabstop=8 : */
+
+#endif /* UTILS_CMD_PUTNOTIF_H */
diff --git a/src/utils_cmd_putval.c b/src/utils_cmd_putval.c
new file mode 100644 (file)
index 0000000..dd43337
--- /dev/null
@@ -0,0 +1,257 @@
+/**
+ * collectd - src/utils_cms_putval.c
+ * Copyright (C) 2007-2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include "utils_parse_option.h"
+
+#define print_to_socket(fh, ...) \
+       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))); \
+               return -1; \
+       }
+
+static int dispatch_values (const data_set_t *ds, value_list_t *vl,
+               FILE *fh, char *buffer)
+{
+       int status;
+
+       status = parse_values (buffer, vl, ds);
+       if (status != 0)
+       {
+               print_to_socket (fh, "-1 Parsing the values string failed.\n");
+               return (-1);
+       }
+
+       plugin_dispatch_values (vl);
+       return (0);
+} /* int dispatch_values */
+
+static int set_option (value_list_t *vl, const char *key, const char *value)
+{
+       if ((vl == NULL) || (key == NULL) || (value == NULL))
+               return (-1);
+
+       if (strcasecmp ("interval", key) == 0)
+       {
+               double tmp;
+               char *endptr;
+
+               endptr = NULL;
+               errno = 0;
+               tmp = strtod (value, &endptr);
+
+               if ((errno == 0) && (endptr != NULL)
+                               && (endptr != value) && (tmp > 0.0))
+                       vl->interval = DOUBLE_TO_CDTIME_T (tmp);
+       }
+       else
+               return (1);
+
+       return (0);
+} /* int parse_option */
+
+int handle_putval (FILE *fh, char *buffer)
+{
+       char *command;
+       char *identifier;
+       char *hostname;
+       char *plugin;
+       char *plugin_instance;
+       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;
+
+       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);
+       }
+       assert (command != NULL);
+
+       if (strcasecmp ("PUTVAL", command) != 0)
+       {
+               print_to_socket (fh, "-1 Unexpected command: `%s'.\n", command);
+               return (-1);
+       }
+
+       identifier = NULL;
+       status = parse_string (&buffer, &identifier);
+       if (status != 0)
+       {
+               print_to_socket (fh, "-1 Cannot parse identifier.\n");
+               return (-1);
+       }
+       assert (identifier != NULL);
+
+       /* 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_putval: Cannot parse identifier `%s'.",
+                               identifier);
+               print_to_socket (fh, "-1 Cannot parse identifier `%s'.\n",
+                               identifier);
+               sfree (identifier_copy);
+               return (-1);
+       }
+
+       if ((strlen (hostname) >= sizeof (vl.host))
+                       || (strlen (plugin) >= sizeof (vl.plugin))
+                       || ((plugin_instance != NULL)
+                               && (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");
+               sfree (identifier_copy);
+               return (-1);
+       }
+
+       sstrncpy (vl.host, hostname, sizeof (vl.host));
+       sstrncpy (vl.plugin, plugin, sizeof (vl.plugin));
+       sstrncpy (vl.type, type, sizeof (vl.type));
+       if (plugin_instance != NULL)
+               sstrncpy (vl.plugin_instance, plugin_instance, sizeof (vl.plugin_instance));
+       if (type_instance != NULL)
+               sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
+
+       ds = plugin_get_ds (type);
+       if (ds == NULL) {
+               print_to_socket (fh, "-1 Type `%s' isn't defined.\n", type);
+               sfree (identifier_copy);
+               return (-1);
+       }
+
+       /* 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 = (value_t *) malloc (vl.values_len * sizeof (value_t));
+       if (vl.values == NULL)
+       {
+               print_to_socket (fh, "-1 malloc failed.\n");
+               return (-1);
+       }
+
+       /* All the remaining fields are part of the optionlist. */
+       values_submitted = 0;
+       while (*buffer != 0)
+       {
+               char *string = 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");
+                       return (-1);
+               }
+               else if (status == 0)
+               {
+                       assert (string != NULL);
+                       assert (value != NULL);
+                       set_option (&vl, string, value);
+                       continue;
+               }
+               /* else: parse_option but buffer has not been modified. This is
+                * the default if no `=' is found.. */
+
+               status = parse_string (&buffer, &string);
+               if (status != 0)
+               {
+                       print_to_socket (fh, "-1 Misformatted value.\n");
+                       return (-1);
+               }
+               assert (string != NULL);
+
+               status = dispatch_values (ds, &vl, fh, string);
+               if (status != 0)
+               {
+                       /* An error has already been printed. */
+                       return (-1);
+               }
+               values_submitted++;
+       } /* while (*buffer != 0) */
+       /* Done parsing the options. */
+
+       print_to_socket (fh, "0 Success: %i %s been dispatched.\n",
+                       values_submitted,
+                       (values_submitted == 1) ? "value has" : "values have");
+
+       sfree (vl.values); 
+
+       return (0);
+} /* int handle_putval */
+
+int 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;
+
+       status = FORMAT_VL (buffer_ident, sizeof (buffer_ident), vl);
+       if (status != 0)
+               return (status);
+       escape_string (buffer_ident, sizeof (buffer_ident));
+
+       status = format_values (buffer_values, sizeof (buffer_values),
+                       ds, vl, /* store rates = */ 0);
+       if (status != 0)
+               return (status);
+       escape_string (buffer_values, sizeof (buffer_values));
+
+       ssnprintf (ret, ret_len,
+                       "PUTVAL %s interval=%.3f %s",
+                       buffer_ident,
+                       (vl->interval > 0)
+                       ? CDTIME_T_TO_DOUBLE (vl->interval)
+                       : CDTIME_T_TO_DOUBLE (interval_g),
+                       buffer_values);
+
+       return (0);
+} /* }}} int create_putval */
diff --git a/src/utils_cmd_putval.h b/src/utils_cmd_putval.h
new file mode 100644 (file)
index 0000000..9c92fd3
--- /dev/null
@@ -0,0 +1,34 @@
+/**
+ * collectd - src/utils_cms_putval.h
+ * Copyright (C) 2007  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef UTILS_CMD_PUTVAL_H
+#define UTILS_CMD_PUTVAL_H 1
+
+#include <stdio.h>
+
+#include "plugin.h"
+
+int handle_putval (FILE *fh, char *buffer);
+
+int 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_complain.c b/src/utils_complain.c
new file mode 100644 (file)
index 0000000..9074b18
--- /dev/null
@@ -0,0 +1,102 @@
+/**
+ * collectd - src/utils_complain.c
+ * Copyright (C) 2006-2007  Florian octo Forster
+ * Copyright (C) 2008  Sebastian tokkee Harl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ *   Sebastian tokkee Harl <sh at tokkee.org>
+ **/
+
+#include "collectd.h"
+#include "utils_complain.h"
+#include "plugin.h"
+
+/* vcomplain returns 0 if it did not report, 1 else */
+static int vcomplain (int level, c_complain_t *c,
+               const char *format, va_list ap)
+{
+       time_t now;
+       char   message[512];
+
+       now = time (NULL);
+
+       if (c->last + c->interval > now)
+               return 0;
+
+       c->last = now;
+
+       if (c->interval < interval_g)
+               c->interval = interval_g;
+       else
+               c->interval *= 2;
+
+       if (c->interval > 86400)
+               c->interval = 86400;
+
+       vsnprintf (message, sizeof (message), format, ap);
+       message[sizeof (message) - 1] = '\0';
+
+       plugin_log (level, "%s", message);
+       return 1;
+} /* vcomplain */
+
+void c_complain (int level, c_complain_t *c, const char *format, ...)
+{
+       va_list ap;
+
+       /* reset the old interval */
+       if (c->interval < 0)
+               c->interval *= -1;
+
+       va_start (ap, format);
+       vcomplain (level, c, format, ap);
+       va_end (ap);
+} /* c_complain */
+
+void c_complain_once (int level, c_complain_t *c, const char *format, ...)
+{
+       va_list ap;
+
+       if (c->interval < 0)
+               return;
+
+       va_start (ap, format);
+       if (vcomplain (level, c, format, ap))
+               c->interval *= -1;
+       va_end (ap);
+} /* c_complain_once */
+
+void c_do_release (int level, c_complain_t *c, const char *format, ...)
+{
+       char message[512];
+       va_list ap;
+
+       if (c->interval == 0)
+               return;
+
+       c->interval = 0;
+
+       va_start (ap, format);
+       vsnprintf (message, sizeof (message), format, ap);
+       message[sizeof (message) - 1] = '\0';
+       va_end (ap);
+
+       plugin_log (level, "%s", message);
+} /* c_release */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
+
diff --git a/src/utils_complain.h b/src/utils_complain.h
new file mode 100644 (file)
index 0000000..09c4375
--- /dev/null
@@ -0,0 +1,106 @@
+/**
+ * collectd - src/utils_complain.h
+ * Copyright (C) 2006-2007  Florian octo Forster
+ * Copyright (C) 2008  Sebastian tokkee Harl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ *   Sebastian tokkee Harl <sh at tokkee.org>
+ **/
+
+#ifndef UTILS_COMPLAIN_H
+#define UTILS_COMPLAIN_H 1
+
+#include <time.h>
+
+typedef struct
+{
+       /* time of the last report */
+       time_t last;
+
+       /* how long to wait until reporting again
+        *   0 indicates that the complaint is no longer valid
+        * < 0 indicates that the complaint has been reported once
+        *     => c_complain_once will not report again
+        *     => c_complain uses the absolute value to reset the old value */
+       int interval;
+} c_complain_t;
+
+#define C_COMPLAIN_INIT_STATIC { 0, 0 }
+#define C_COMPLAIN_INIT(c) do { (c)->last = 0; (c)->interval = 0; } while (0)
+
+/*
+ * NAME
+ *   c_complain
+ *
+ * DESCRIPTION
+ *   Complain about something. This function will report a message (usually
+ *   indicating some error condition) using the collectd logging mechanism.
+ *   When this function is called again, reporting the message again will be
+ *   deferred by an increasing interval (up to one day) to prevent flooding
+ *   the logs. A call to `c_release' resets the counter.
+ *
+ * PARAMETERS
+ *   `level'  The log level passed to `plugin_log'.
+ *   `c'      Identifier for the complaint.
+ *   `format' Message format - see the documentation of printf(3).
+ */
+void c_complain (int level, c_complain_t *c, const char *format, ...);
+
+/*
+ * NAME
+ *   c_complain_once
+ *
+ * DESCRIPTION
+ *   Complain about something once. This function will not report anything
+ *   again, unless `c_release' has been called in between. If used after some
+ *   calls to `c_complain', it will report again on the next interval and stop
+ *   after that.
+ *
+ *   See `c_complain' for further details and a description of the parameters.
+ */
+void c_complain_once (int level, c_complain_t *c, const char *format, ...);
+
+/*
+ * NAME
+ *   c_would_release
+ *
+ * DESCRIPTION
+ *   Returns true if the specified complaint would be released, false else.
+ */
+#define c_would_release(c) ((c)->interval != 0)
+
+/*
+ * NAME
+ *   c_release
+ *
+ * DESCRIPTION
+ *   Release a complaint. This will report a message once, marking the
+ *   complaint as released.
+ *
+ *   See `c_complain' for a description of the parameters.
+ */
+void c_do_release (int level, c_complain_t *c, const char *format, ...);
+#define c_release(level, c, ...) \
+       do { \
+               if (c_would_release (c)) \
+                       c_do_release(level, c, __VA_ARGS__); \
+       } while (0)
+
+#endif /* UTILS_COMPLAIN_H */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
+
diff --git a/src/utils_db_query.c b/src/utils_db_query.c
new file mode 100644 (file)
index 0000000..dcac807
--- /dev/null
@@ -0,0 +1,1040 @@
+/**
+ * collectd - src/utils_db_query.c
+ * Copyright (C) 2008,2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+#include "utils_db_query.h"
+
+/*
+ * Data types
+ */
+struct udb_result_s; /* {{{ */
+typedef struct udb_result_s udb_result_t;
+struct udb_result_s
+{
+  char    *type;
+  char    *instance_prefix;
+  char   **instances;
+  size_t   instances_num;
+  char   **values;
+  size_t   values_num;
+
+  udb_result_t *next;
+}; /* }}} */
+
+struct udb_query_s /* {{{ */
+{
+  char *name;
+  char *statement;
+  void *user_data;
+
+  unsigned int min_version;
+  unsigned int max_version;
+
+  udb_result_t *results;
+}; /* }}} */
+
+struct udb_result_preparation_area_s /* {{{ */
+{
+  const   data_set_t *ds;
+  size_t *instances_pos;
+  size_t *values_pos;
+  char  **instances_buffer;
+  char  **values_buffer;
+
+  struct udb_result_preparation_area_s *next;
+}; /* }}} */
+typedef struct udb_result_preparation_area_s udb_result_preparation_area_t;
+
+struct udb_query_preparation_area_s /* {{{ */
+{
+  size_t column_num;
+  char *host;
+  char *plugin;
+  char *db_name;
+
+  cdtime_t interval;
+
+  udb_result_preparation_area_t *result_prep_areas;
+}; /* }}} */
+
+/*
+ * Config Private functions
+ */
+static int udb_config_set_string (char **ret_string, /* {{{ */
+    oconfig_item_t *ci)
+{
+  char *string;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("db query utils: The `%s' config option "
+        "needs exactly one string argument.", ci->key);
+    return (-1);
+  }
+
+  string = strdup (ci->values[0].value.string);
+  if (string == NULL)
+  {
+    ERROR ("db query utils: strdup failed.");
+    return (-1);
+  }
+
+  if (*ret_string != NULL)
+    free (*ret_string);
+  *ret_string = string;
+
+  return (0);
+} /* }}} int udb_config_set_string */
+
+static int udb_config_add_string (char ***ret_array, /* {{{ */
+    size_t *ret_array_len, oconfig_item_t *ci)
+{
+  char **array;
+  size_t array_len;
+  int i;
+
+  if (ci->values_num < 1)
+  {
+    WARNING ("db query utils: The `%s' config option "
+        "needs at least one argument.", ci->key);
+    return (-1);
+  }
+
+  for (i = 0; i < ci->values_num; i++)
+  {
+    if (ci->values[i].type != OCONFIG_TYPE_STRING)
+    {
+      WARNING ("db query utils: Argument %i to the `%s' option "
+          "is not a string.", i + 1, ci->key);
+      return (-1);
+    }
+  }
+
+  array_len = *ret_array_len;
+  array = (char **) realloc (*ret_array,
+      sizeof (char *) * (array_len + ci->values_num));
+  if (array == NULL)
+  {
+    ERROR ("db query utils: realloc failed.");
+    return (-1);
+  }
+  *ret_array = array;
+
+  for (i = 0; i < ci->values_num; i++)
+  {
+    array[array_len] = strdup (ci->values[i].value.string);
+    if (array[array_len] == NULL)
+    {
+      ERROR ("db query utils: strdup failed.");
+      *ret_array_len = array_len;
+      return (-1);
+    }
+    array_len++;
+  }
+
+  *ret_array_len = array_len;
+  return (0);
+} /* }}} int udb_config_add_string */
+
+static int udb_config_set_uint (unsigned int *ret_value, /* {{{ */
+    oconfig_item_t *ci)
+{
+  double tmp;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
+  {
+    WARNING ("db query utils: The `%s' config option "
+        "needs exactly one numeric argument.", ci->key);
+    return (-1);
+  }
+
+  tmp = ci->values[0].value.number;
+  if ((tmp < 0.0) || (tmp > ((double) UINT_MAX)))
+    return (-ERANGE);
+
+  *ret_value = (unsigned int) (tmp + .5);
+  return (0);
+} /* }}} int udb_config_set_uint */
+
+/*
+ * Result private functions
+ */
+static int udb_result_submit (udb_result_t *r, /* {{{ */
+    udb_result_preparation_area_t *r_area,
+    const udb_query_t const *q, udb_query_preparation_area_t *q_area)
+{
+  value_list_t vl = VALUE_LIST_INIT;
+  size_t i;
+
+  assert (r != NULL);
+  assert (r_area->ds != NULL);
+  assert (((size_t) r_area->ds->ds_num) == r->values_num);
+
+  vl.values = (value_t *) calloc (r_area->ds->ds_num, sizeof (value_t));
+  if (vl.values == NULL)
+  {
+    ERROR ("db query utils: malloc failed.");
+    return (-1);
+  }
+  vl.values_len = r_area->ds->ds_num;
+
+  for (i = 0; i < r->values_num; i++)
+  {
+    char *value_str = r_area->values_buffer[i];
+
+    if (0 != parse_value (value_str, &vl.values[i], r_area->ds->ds[i].type))
+    {
+      ERROR ("db query utils: udb_result_submit: Parsing `%s' as %s failed.",
+          value_str, DS_TYPE_TO_STRING (r_area->ds->ds[i].type));
+      errno = EINVAL;
+      return (-1);
+    }
+  }
+
+  if (q_area->interval > 0)
+    vl.interval = q_area->interval;
+
+  sstrncpy (vl.host, q_area->host, sizeof (vl.host));
+  sstrncpy (vl.plugin, q_area->plugin, sizeof (vl.plugin));
+  sstrncpy (vl.plugin_instance, q_area->db_name, sizeof (vl.type_instance));
+  sstrncpy (vl.type, r->type, sizeof (vl.type));
+
+  /* Set vl.type_instance {{{ */
+  if (r->instances_num <= 0)
+  {
+    if (r->instance_prefix == NULL)
+      vl.type_instance[0] = 0;
+    else
+      sstrncpy (vl.type_instance, r->instance_prefix,
+          sizeof (vl.type_instance));
+  }
+  else /* if ((r->instances_num > 0) */
+  {
+    if (r->instance_prefix == NULL)
+    {
+      strjoin (vl.type_instance, sizeof (vl.type_instance),
+          r_area->instances_buffer, r->instances_num, "-");
+    }
+    else
+    {
+      char tmp[DATA_MAX_NAME_LEN];
+
+      strjoin (tmp, sizeof (tmp), r_area->instances_buffer,
+          r->instances_num, "-");
+      tmp[sizeof (tmp) - 1] = 0;
+
+      snprintf (vl.type_instance, sizeof (vl.type_instance), "%s-%s",
+          r->instance_prefix, tmp);
+    }
+  }
+  vl.type_instance[sizeof (vl.type_instance) - 1] = 0;
+  /* }}} */
+
+  plugin_dispatch_values (&vl);
+
+  sfree (vl.values);
+  return (0);
+} /* }}} void udb_result_submit */
+
+static void udb_result_finish_result (const udb_result_t const *r, /* {{{ */
+    udb_result_preparation_area_t *prep_area)
+{
+  if ((r == NULL) || (prep_area == NULL))
+    return;
+
+  prep_area->ds = NULL;
+  sfree (prep_area->instances_pos);
+  sfree (prep_area->values_pos);
+  sfree (prep_area->instances_buffer);
+  sfree (prep_area->values_buffer);
+} /* }}} void udb_result_finish_result */
+
+static int udb_result_handle_result (udb_result_t *r, /* {{{ */
+    udb_query_preparation_area_t *q_area,
+    udb_result_preparation_area_t *r_area,
+    const udb_query_t const *q, char **column_values)
+{
+  size_t i;
+
+  assert (r && q_area && r_area);
+
+  for (i = 0; i < r->instances_num; i++)
+    r_area->instances_buffer[i] = column_values[r_area->instances_pos[i]];
+
+  for (i = 0; i < r->values_num; i++)
+    r_area->values_buffer[i] = column_values[r_area->values_pos[i]];
+
+  return udb_result_submit (r, r_area, q, q_area);
+} /* }}} int udb_result_handle_result */
+
+static int udb_result_prepare_result (const udb_result_t const *r, /* {{{ */
+    udb_result_preparation_area_t *prep_area,
+    char **column_names, size_t column_num)
+{
+  size_t i;
+
+  if ((r == NULL) || (prep_area == NULL))
+    return (-EINVAL);
+
+#define BAIL_OUT(status) \
+  prep_area->ds = NULL; \
+  sfree (prep_area->instances_pos); \
+  sfree (prep_area->values_pos); \
+  sfree (prep_area->instances_buffer); \
+  sfree (prep_area->values_buffer); \
+  return (status)
+
+  /* Make sure previous preparations are cleaned up. */
+  udb_result_finish_result (r, prep_area);
+  prep_area->instances_pos = NULL;
+  prep_area->values_pos = NULL;
+
+  /* Read `ds' and check number of values {{{ */
+  prep_area->ds = plugin_get_ds (r->type);
+  if (prep_area->ds == NULL)
+  {
+    ERROR ("db query utils: udb_result_prepare_result: Type `%s' is not "
+        "known by the daemon. See types.db(5) for details.",
+        r->type);
+    BAIL_OUT (-1);
+  }
+
+  if (((size_t) prep_area->ds->ds_num) != r->values_num)
+  {
+    ERROR ("db query utils: udb_result_prepare_result: The type `%s' "
+        "requires exactly %i value%s, but the configuration specifies %zu.",
+        r->type,
+        prep_area->ds->ds_num, (prep_area->ds->ds_num == 1) ? "" : "s",
+        r->values_num);
+    BAIL_OUT (-1);
+  }
+  /* }}} */
+
+  /* Allocate r->instances_pos, r->values_pos, r->instances_buffer, and
+   * r->values_buffer {{{ */
+  if (r->instances_num > 0)
+  {
+    prep_area->instances_pos
+      = (size_t *) calloc (r->instances_num, sizeof (size_t));
+    if (prep_area->instances_pos == NULL)
+    {
+      ERROR ("db query utils: udb_result_prepare_result: malloc failed.");
+      BAIL_OUT (-ENOMEM);
+    }
+
+    prep_area->instances_buffer
+      = (char **) calloc (r->instances_num, sizeof (char *));
+    if (prep_area->instances_buffer == NULL)
+    {
+      ERROR ("db query utils: udb_result_prepare_result: malloc failed.");
+      BAIL_OUT (-ENOMEM);
+    }
+  } /* if (r->instances_num > 0) */
+
+  prep_area->values_pos
+    = (size_t *) calloc (r->values_num, sizeof (size_t));
+  if (prep_area->values_pos == NULL)
+  {
+    ERROR ("db query utils: udb_result_prepare_result: malloc failed.");
+    BAIL_OUT (-ENOMEM);
+  }
+
+  prep_area->values_buffer
+    = (char **) calloc (r->values_num, sizeof (char *));
+  if (prep_area->values_buffer == NULL)
+  {
+    ERROR ("db query utils: udb_result_prepare_result: malloc failed.");
+    BAIL_OUT (-ENOMEM);
+  }
+  /* }}} */
+
+  /* Determine the position of the instance columns {{{ */
+  for (i = 0; i < r->instances_num; i++)
+  {
+    size_t j;
+
+    for (j = 0; j < column_num; j++)
+    {
+      if (strcasecmp (r->instances[i], column_names[j]) == 0)
+      {
+        prep_area->instances_pos[i] = j;
+        break;
+      }
+    }
+
+    if (j >= column_num)
+    {
+      ERROR ("db query utils: udb_result_prepare_result: "
+          "Column `%s' could not be found.",
+          r->instances[i]);
+      BAIL_OUT (-ENOENT);
+    }
+  } /* }}} for (i = 0; i < r->instances_num; i++) */
+
+  /* Determine the position of the value columns {{{ */
+  for (i = 0; i < r->values_num; i++)
+  {
+    size_t j;
+
+    for (j = 0; j < column_num; j++)
+    {
+      if (strcasecmp (r->values[i], column_names[j]) == 0)
+      {
+        prep_area->values_pos[i] = j;
+        break;
+      }
+    }
+
+    if (j >= column_num)
+    {
+      ERROR ("db query utils: udb_result_prepare_result: "
+          "Column `%s' could not be found.",
+          r->values[i]);
+      BAIL_OUT (-ENOENT);
+    }
+  } /* }}} for (i = 0; i < r->values_num; i++) */
+
+#undef BAIL_OUT
+  return (0);
+} /* }}} int udb_result_prepare_result */
+
+static void udb_result_free (udb_result_t *r) /* {{{ */
+{
+  size_t i;
+
+  if (r == NULL)
+    return;
+
+  sfree (r->type);
+
+  for (i = 0; i < r->instances_num; i++)
+    sfree (r->instances[i]);
+  sfree (r->instances);
+
+  for (i = 0; i < r->values_num; i++)
+    sfree (r->values[i]);
+  sfree (r->values);
+
+  udb_result_free (r->next);
+
+  sfree (r);
+} /* }}} void udb_result_free */
+
+static int udb_result_create (const char *query_name, /* {{{ */
+    udb_result_t **r_head, oconfig_item_t *ci)
+{
+  udb_result_t *r;
+  int status;
+  int i;
+
+  if (ci->values_num != 0)
+  {
+    WARNING ("db query utils: The `Result' block doesn't accept "
+        "any arguments. Ignoring %i argument%s.",
+        ci->values_num, (ci->values_num == 1) ? "" : "s");
+  }
+
+  r = (udb_result_t *) malloc (sizeof (*r));
+  if (r == NULL)
+  {
+    ERROR ("db query utils: malloc failed.");
+    return (-1);
+  }
+  memset (r, 0, sizeof (*r));
+  r->type = NULL;
+  r->instance_prefix = NULL;
+  r->instances = NULL;
+  r->values = NULL;
+  r->next = NULL;
+
+  /* Fill the `udb_result_t' structure.. */
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Type", child->key) == 0)
+      status = udb_config_set_string (&r->type, child);
+    else if (strcasecmp ("InstancePrefix", child->key) == 0)
+      status = udb_config_set_string (&r->instance_prefix, child);
+    else if (strcasecmp ("InstancesFrom", child->key) == 0)
+      status = udb_config_add_string (&r->instances, &r->instances_num, child);
+    else if (strcasecmp ("ValuesFrom", child->key) == 0)
+      status = udb_config_add_string (&r->values, &r->values_num, child);
+    else
+    {
+      WARNING ("db query utils: Query `%s': Option `%s' not allowed here.",
+          query_name, child->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  /* Check that all necessary options have been given. */
+  while (status == 0)
+  {
+    if (r->type == NULL)
+    {
+      WARNING ("db query utils: `Type' not given for "
+          "result in query `%s'", query_name);
+      status = -1;
+    }
+    if (r->values == NULL)
+    {
+      WARNING ("db query utils: `ValuesFrom' not given for "
+          "result in query `%s'", query_name);
+      status = -1;
+    }
+
+    break;
+  } /* while (status == 0) */
+
+  if (status != 0)
+  {
+    udb_result_free (r);
+    return (-1);
+  }
+
+  /* If all went well, add this result to the list of results. */
+  if (*r_head == NULL)
+  {
+    *r_head = r;
+  }
+  else
+  {
+    udb_result_t *last;
+
+    last = *r_head;
+    while (last->next != NULL)
+      last = last->next;
+
+    last->next = r;
+  }
+
+  return (0);
+} /* }}} int udb_result_create */
+
+/*
+ * Query private functions
+ */
+void udb_query_free_one (udb_query_t *q) /* {{{ */
+{
+  if (q == NULL)
+    return;
+
+  sfree (q->name);
+  sfree (q->statement);
+
+  udb_result_free (q->results);
+
+  sfree (q);
+} /* }}} void udb_query_free_one */
+
+/*
+ * Query public functions
+ */
+int udb_query_create (udb_query_t ***ret_query_list, /* {{{ */
+    size_t *ret_query_list_len, oconfig_item_t *ci,
+    udb_query_create_callback_t cb)
+{
+  udb_query_t **query_list;
+  size_t        query_list_len;
+
+  udb_query_t *q;
+  int status;
+  int i;
+
+  if ((ret_query_list == NULL) || (ret_query_list_len == NULL))
+    return (-EINVAL);
+  query_list     = *ret_query_list;
+  query_list_len = *ret_query_list_len;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("db query utils: The `Query' block "
+        "needs exactly one string argument.");
+    return (-1);
+  }
+
+  q = (udb_query_t *) malloc (sizeof (*q));
+  if (q == NULL)
+  {
+    ERROR ("db query utils: malloc failed.");
+    return (-1);
+  }
+  memset (q, 0, sizeof (*q));
+  q->min_version = 0;
+  q->max_version = UINT_MAX;
+
+  status = udb_config_set_string (&q->name, ci);
+  if (status != 0)
+  {
+    sfree (q);
+    return (status);
+  }
+
+  /* Fill the `udb_query_t' structure.. */
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Statement", child->key) == 0)
+      status = udb_config_set_string (&q->statement, child);
+    else if (strcasecmp ("Result", child->key) == 0)
+      status = udb_result_create (q->name, &q->results, child);
+    else if (strcasecmp ("MinVersion", child->key) == 0)
+      status = udb_config_set_uint (&q->min_version, child);
+    else if (strcasecmp ("MaxVersion", child->key) == 0)
+      status = udb_config_set_uint (&q->max_version, child);
+
+    /* Call custom callbacks */
+    else if (cb != NULL)
+    {
+      status = (*cb) (q, child);
+      if (status != 0)
+      {
+        WARNING ("db query utils: The configuration callback failed "
+            "to handle `%s'.", child->key);
+      }
+    }
+    else
+    {
+      WARNING ("db query utils: Query `%s': Option `%s' not allowed here.",
+          q->name, child->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  /* Check that all necessary options have been given. */
+  if (status == 0)
+  {
+    if (q->statement == NULL)
+    {
+      WARNING ("db query utils: Query `%s': No `Statement' given.", q->name);
+      status = -1;
+    }
+    if (q->results == NULL)
+    {
+      WARNING ("db query utils: Query `%s': No (valid) `Result' block given.",
+          q->name);
+      status = -1;
+    }
+  } /* if (status == 0) */
+
+  /* If all went well, add this query to the list of queries within the
+   * database structure. */
+  if (status == 0)
+  {
+    udb_query_t **temp;
+
+    temp = (udb_query_t **) realloc (query_list,
+        sizeof (*query_list) * (query_list_len + 1));
+    if (temp == NULL)
+    {
+      ERROR ("db query utils: realloc failed");
+      status = -1;
+    }
+    else
+    {
+      query_list = temp;
+      query_list[query_list_len] = q;
+      query_list_len++;
+    }
+  }
+
+  if (status != 0)
+  {
+    udb_query_free_one (q);
+    return (-1);
+  }
+
+  *ret_query_list     = query_list;
+  *ret_query_list_len = query_list_len;
+
+  return (0);
+} /* }}} int udb_query_create */
+
+void udb_query_free (udb_query_t **query_list, size_t query_list_len) /* {{{ */
+{
+  size_t i;
+
+  if (query_list == NULL)
+    return;
+
+  for (i = 0; i < query_list_len; i++)
+    udb_query_free_one (query_list[i]);
+
+  sfree (query_list);
+} /* }}} void udb_query_free */
+
+int udb_query_pick_from_list_by_name (const char *name, /* {{{ */
+    udb_query_t **src_list, size_t src_list_len,
+    udb_query_t ***dst_list, size_t *dst_list_len)
+{
+  size_t i;
+  int num_added;
+
+  if ((name == NULL) || (src_list == NULL) || (dst_list == NULL)
+      || (dst_list_len == NULL))
+  {
+    ERROR ("db query utils: udb_query_pick_from_list_by_name: "
+        "Invalid argument.");
+    return (-EINVAL);
+  }
+
+  num_added = 0;
+  for (i = 0; i < src_list_len; i++)
+  {
+    udb_query_t **tmp_list;
+    size_t tmp_list_len;
+
+    if (strcasecmp (name, src_list[i]->name) != 0)
+      continue;
+
+    tmp_list_len = *dst_list_len;
+    tmp_list = (udb_query_t **) realloc (*dst_list, (tmp_list_len + 1)
+        * sizeof (udb_query_t *));
+    if (tmp_list == NULL)
+    {
+      ERROR ("db query utils: realloc failed.");
+      return (-ENOMEM);
+    }
+
+    tmp_list[tmp_list_len] = src_list[i];
+    tmp_list_len++;
+
+    *dst_list = tmp_list;
+    *dst_list_len = tmp_list_len;
+
+    num_added++;
+  } /* for (i = 0; i < src_list_len; i++) */
+
+  if (num_added <= 0)
+  {
+    ERROR ("db query utils: Cannot find query `%s'. Make sure the <Query> "
+        "block is above the database definition!",
+        name);
+    return (-ENOENT);
+  }
+  else
+  {
+    DEBUG ("db query utils: Added %i versions of query `%s'.",
+        num_added, name);
+  }
+
+  return (0);
+} /* }}} int udb_query_pick_from_list_by_name */
+
+int udb_query_pick_from_list (oconfig_item_t *ci, /* {{{ */
+    udb_query_t **src_list, size_t src_list_len,
+    udb_query_t ***dst_list, size_t *dst_list_len)
+{
+  const char *name;
+
+  if ((ci == NULL) || (src_list == NULL) || (dst_list == NULL)
+      || (dst_list_len == NULL))
+  {
+    ERROR ("db query utils: udb_query_pick_from_list: "
+        "Invalid argument.");
+    return (-EINVAL);
+  }
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    ERROR ("db query utils: The `%s' config option "
+        "needs exactly one string argument.", ci->key);
+    return (-1);
+  }
+  name = ci->values[0].value.string;
+
+  return (udb_query_pick_from_list_by_name (name,
+        src_list, src_list_len,
+        dst_list, dst_list_len));
+} /* }}} int udb_query_pick_from_list */
+
+const char *udb_query_get_name (udb_query_t *q) /* {{{ */
+{
+  if (q == NULL)
+    return (NULL);
+
+  return (q->name);
+} /* }}} const char *udb_query_get_name */
+
+const char *udb_query_get_statement (udb_query_t *q) /* {{{ */
+{
+  if (q == NULL)
+    return (NULL);
+
+  return (q->statement);
+} /* }}} const char *udb_query_get_statement */
+
+void udb_query_set_user_data (udb_query_t *q, void *user_data) /* {{{ */
+{
+  if (q == NULL)
+    return;
+
+  q->user_data = user_data;
+} /* }}} void udb_query_set_user_data */
+
+void *udb_query_get_user_data (udb_query_t *q) /* {{{ */
+{
+  if (q == NULL)
+    return (NULL);
+
+  return (q->user_data);
+} /* }}} void *udb_query_get_user_data */
+
+int udb_query_check_version (udb_query_t *q, unsigned int version) /* {{{ */
+{
+  if (q == NULL)
+    return (-EINVAL);
+
+  if ((version < q->min_version) || (version > q->max_version))
+    return (0);
+
+  return (1);
+} /* }}} int udb_query_check_version */
+
+void udb_query_finish_result (const udb_query_t const *q, /* {{{ */
+    udb_query_preparation_area_t *prep_area)
+{
+  udb_result_preparation_area_t *r_area;
+  udb_result_t *r;
+
+  if ((q == NULL) || (prep_area == NULL))
+    return;
+
+  prep_area->column_num = 0;
+  sfree (prep_area->host);
+  sfree (prep_area->plugin);
+  sfree (prep_area->db_name);
+
+  prep_area->interval = 0;
+
+  for (r = q->results, r_area = prep_area->result_prep_areas;
+      r != NULL; r = r->next, r_area = r_area->next)
+  {
+    /* this may happen during error conditions of the caller */
+    if (r_area == NULL)
+      break;
+    udb_result_finish_result (r, r_area);
+  }
+} /* }}} void udb_query_finish_result */
+
+int udb_query_handle_result (const udb_query_t const *q, /* {{{ */
+    udb_query_preparation_area_t *prep_area, char **column_values)
+{
+  udb_result_preparation_area_t *r_area;
+  udb_result_t *r;
+  int success;
+  int status;
+
+  if ((q == NULL) || (prep_area == NULL))
+    return (-EINVAL);
+
+  if ((prep_area->column_num < 1) || (prep_area->host == NULL)
+      || (prep_area->plugin == NULL) || (prep_area->db_name == NULL))
+  {
+    ERROR ("db query utils: Query `%s': Query is not prepared; "
+        "can't handle result.", q->name);
+    return (-EINVAL);
+  }
+
+#if defined(COLLECT_DEBUG) && COLLECT_DEBUG /* {{{ */
+  do
+  {
+    size_t i;
+
+    for (i = 0; i < prep_area->column_num; i++)
+    {
+      DEBUG ("db query utils: udb_query_handle_result (%s, %s): "
+          "column[%zu] = %s;",
+          prep_area->db_name, q->name, i, column_values[i]);
+    }
+  } while (0);
+#endif /* }}} */
+
+  success = 0;
+  for (r = q->results, r_area = prep_area->result_prep_areas;
+      r != NULL; r = r->next, r_area = r_area->next)
+  {
+    status = udb_result_handle_result (r, prep_area, r_area,
+        q, column_values);
+    if (status == 0)
+      success++;
+  }
+
+  if (success == 0)
+  {
+    ERROR ("db query utils: udb_query_handle_result (%s, %s): "
+        "All results failed.", prep_area->db_name, q->name);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int udb_query_handle_result */
+
+int udb_query_prepare_result (const udb_query_t const *q, /* {{{ */
+    udb_query_preparation_area_t *prep_area,
+    const char *host, const char *plugin, const char *db_name,
+    char **column_names, size_t column_num, cdtime_t interval)
+{
+  udb_result_preparation_area_t *r_area;
+  udb_result_t *r;
+  int status;
+
+  if ((q == NULL) || (prep_area == NULL))
+    return (-EINVAL);
+
+  udb_query_finish_result (q, prep_area);
+
+  prep_area->column_num = column_num;
+  prep_area->host = strdup (host);
+  prep_area->plugin = strdup (plugin);
+  prep_area->db_name = strdup (db_name);
+
+  prep_area->interval = interval;
+
+  if ((prep_area->host == NULL) || (prep_area->plugin == NULL)
+      || (prep_area->db_name == NULL))
+  {
+    ERROR ("db query utils: Query `%s': Prepare failed: Out of memory.", q->name);
+    udb_query_finish_result (q, prep_area);
+    return (-ENOMEM);
+  }
+
+#if defined(COLLECT_DEBUG) && COLLECT_DEBUG
+  do
+  {
+    size_t i;
+
+    for (i = 0; i < column_num; i++)
+    {
+      DEBUG ("db query utils: udb_query_prepare_result: "
+          "query = %s; column[%zu] = %s;",
+          q->name, i, column_names[i]);
+    }
+  } while (0);
+#endif
+
+  for (r = q->results, r_area = prep_area->result_prep_areas;
+      r != NULL; r = r->next, r_area = r_area->next)
+  {
+    if (! r_area)
+    {
+      ERROR ("db query utils: Query `%s': Invalid number of result "
+          "preparation areas.", q->name);
+      udb_query_finish_result (q, prep_area);
+      return (-EINVAL);
+    }
+
+    status = udb_result_prepare_result (r, r_area, column_names, column_num);
+    if (status != 0)
+    {
+      udb_query_finish_result (q, prep_area);
+      return (status);
+    }
+  }
+
+  return (0);
+} /* }}} int udb_query_prepare_result */
+
+udb_query_preparation_area_t *
+udb_query_allocate_preparation_area (udb_query_t *q) /* {{{ */
+{
+  udb_query_preparation_area_t   *q_area;
+  udb_result_preparation_area_t **next_r_area;
+  udb_result_t *r;
+
+  q_area = (udb_query_preparation_area_t *)malloc (sizeof (*q_area));
+  if (q_area == NULL)
+    return NULL;
+
+  memset (q_area, 0, sizeof (*q_area));
+
+  next_r_area = &q_area->result_prep_areas;
+  for (r = q->results; r != NULL; r = r->next)
+  {
+    udb_result_preparation_area_t *r_area;
+
+    r_area = (udb_result_preparation_area_t *)malloc (sizeof (*r_area));
+    if (r_area == NULL)
+    {
+      for (r_area = q_area->result_prep_areas;
+          r_area != NULL; r_area = r_area->next)
+      {
+        free (r_area);
+      }
+      free (q_area);
+      return NULL;
+    }
+
+    memset (r_area, 0, sizeof (*r_area));
+
+    *next_r_area = r_area;
+    next_r_area  = &r_area->next;
+  }
+
+  return (q_area);
+} /* }}} udb_query_preparation_area_t *udb_query_allocate_preparation_area */
+
+void
+udb_query_delete_preparation_area (udb_query_preparation_area_t *q_area) /* {{{ */
+{
+  udb_result_preparation_area_t *r_area;
+
+  if (q_area == NULL)
+    return;
+
+  r_area = q_area->result_prep_areas;
+  while (r_area != NULL)
+  {
+    udb_result_preparation_area_t *area = r_area;
+
+    r_area = r_area->next;
+
+    sfree (area->instances_pos);
+    sfree (area->values_pos);
+    sfree (area->instances_buffer);
+    sfree (area->values_buffer);
+    free (area);
+  }
+
+  sfree (q_area->host);
+  sfree (q_area->plugin);
+  sfree (q_area->db_name);
+
+  free (q_area);
+} /* }}} void udb_query_delete_preparation_area */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/utils_db_query.h b/src/utils_db_query.h
new file mode 100644 (file)
index 0000000..727be03
--- /dev/null
@@ -0,0 +1,83 @@
+/**
+ * collectd - src/utils_db_query.h
+ * Copyright (C) 2008,2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef UTILS_DB_QUERY_H
+#define UTILS_DB_QUERY_H 1
+
+#include "configfile.h"
+
+/*
+ * Data types
+ */
+struct udb_query_s;
+typedef struct udb_query_s udb_query_t;
+
+struct udb_query_preparation_area_s;
+typedef struct udb_query_preparation_area_s udb_query_preparation_area_t;
+
+typedef int (*udb_query_create_callback_t) (udb_query_t *q,
+    oconfig_item_t *ci);
+
+/* 
+ * Public functions
+ */
+int udb_query_create (udb_query_t ***ret_query_list,
+    size_t *ret_query_list_len, oconfig_item_t *ci,
+    udb_query_create_callback_t cb);
+void udb_query_free (udb_query_t **query_list, size_t query_list_len);
+
+int udb_query_pick_from_list_by_name (const char *name,
+    udb_query_t **src_list, size_t src_list_len,
+    udb_query_t ***dst_list, size_t *dst_list_len);
+int udb_query_pick_from_list (oconfig_item_t *ci,
+    udb_query_t **src_list, size_t src_list_len,
+    udb_query_t ***dst_list, size_t *dst_list_len);
+
+const char *udb_query_get_name (udb_query_t *q);
+const char *udb_query_get_statement (udb_query_t *q);
+
+void  udb_query_set_user_data (udb_query_t *q, void *user_data);
+void *udb_query_get_user_data (udb_query_t *q);
+
+/* 
+ * udb_query_check_version
+ *
+ * Returns 0 if the query is NOT suitable for `version' and >0 if the
+ * query IS suitable.
+ */
+int udb_query_check_version (udb_query_t *q, unsigned int version);
+
+int udb_query_prepare_result (const udb_query_t const *q,
+    udb_query_preparation_area_t *prep_area,
+    const char *host, const char *plugin, const char *db_name,
+    char **column_names, size_t column_num, cdtime_t interval);
+int udb_query_handle_result (const udb_query_t const *q,
+    udb_query_preparation_area_t *prep_area, char **column_values);
+void udb_query_finish_result (const udb_query_t const *q,
+    udb_query_preparation_area_t *prep_area);
+
+udb_query_preparation_area_t *
+udb_query_allocate_preparation_area (udb_query_t *q);
+void
+udb_query_delete_preparation_area (udb_query_preparation_area_t *q_area);
+
+#endif /* UTILS_DB_QUERY_H */
+/* vim: set sw=2 sts=2 et : */
diff --git a/src/utils_dns.c b/src/utils_dns.c
new file mode 100644 (file)
index 0000000..cfa4a5c
--- /dev/null
@@ -0,0 +1,1085 @@
+/*
+ * collectd - src/utils_dns.c
+ * Modifications Copyright (C) 2006  Florian octo Forster
+ * Copyright (C) 2002  The Measurement Factory, Inc.
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of The Measurement Factory nor the names of its
+ *    contributors may be used to endorse or promote products derived from this
+ *    software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Authors:
+ *   The Measurement Factory, Inc. <http://www.measurement-factory.com/>
+ *   Florian octo Forster <octo at verplant.org>
+ */
+
+#define _BSD_SOURCE
+
+#include "collectd.h"
+#include "plugin.h"
+#include "common.h"
+
+#if HAVE_NETINET_IN_SYSTM_H
+# include <netinet/in_systm.h>
+#endif
+#if HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+#if HAVE_ARPA_INET_H
+# include <arpa/inet.h>
+#endif
+#if HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif
+
+#if HAVE_ARPA_NAMESER_H
+# include <arpa/nameser.h>
+#endif
+#if HAVE_ARPA_NAMESER_COMPAT_H
+# include <arpa/nameser_compat.h>
+#endif
+
+#if HAVE_NET_IF_ARP_H
+# include <net/if_arp.h>
+#endif
+#if HAVE_NET_IF_H
+# include <net/if.h>
+#endif
+#if HAVE_NETINET_IF_ETHER_H
+# include <netinet/if_ether.h>
+#endif
+#if HAVE_NET_PPP_DEFS_H
+# include <net/ppp_defs.h>
+#endif
+#if HAVE_NET_IF_PPP_H
+# include <net/if_ppp.h>
+#endif
+
+#if HAVE_NETDB_H
+# include <netdb.h>
+#endif
+
+#if HAVE_NETINET_IP_H
+# include <netinet/ip.h>
+#endif
+#ifdef HAVE_NETINET_IP_VAR_H
+# include <netinet/ip_var.h>
+#endif
+#if HAVE_NETINET_IP6_H
+# include <netinet/ip6.h>
+#endif
+#if HAVE_NETINET_UDP_H
+# include <netinet/udp.h>
+#endif
+
+#if HAVE_PCAP_H
+# include <pcap.h>
+#endif
+
+#define PCAP_SNAPLEN 1460
+#ifndef ETHER_HDR_LEN
+#define ETHER_ADDR_LEN 6
+#define ETHER_TYPE_LEN 2
+#define ETHER_HDR_LEN (ETHER_ADDR_LEN * 2 + ETHER_TYPE_LEN)
+#endif
+#ifndef ETHERTYPE_8021Q
+# define ETHERTYPE_8021Q 0x8100
+#endif
+#ifndef ETHERTYPE_IPV6
+# define ETHERTYPE_IPV6 0x86DD
+#endif
+
+#ifndef PPP_ADDRESS_VAL
+# define PPP_ADDRESS_VAL 0xff  /* The address byte value */
+#endif
+#ifndef PPP_CONTROL_VAL
+# define PPP_CONTROL_VAL 0x03  /* The control byte value */
+#endif
+
+#if HAVE_STRUCT_UDPHDR_UH_DPORT && HAVE_STRUCT_UDPHDR_UH_SPORT
+# define UDP_DEST uh_dport
+# define UDP_SRC  uh_dport
+#elif HAVE_STRUCT_UDPHDR_DEST && HAVE_STRUCT_UDPHDR_SOURCE
+# define UDP_DEST dest
+# define UDP_SRC  source
+#else
+# error "`struct udphdr' is unusable."
+#endif
+
+#include "utils_dns.h"
+
+/*
+ * Type definitions
+ */
+struct ip_list_s
+{
+    struct in6_addr addr;
+    void *data;
+    struct ip_list_s *next;
+};
+typedef struct ip_list_s ip_list_t;
+
+typedef int (printer)(const char *, ...);
+
+/*
+ * flags/features for non-interactive mode
+ */
+
+#ifndef T_A6
+#define T_A6 38
+#endif
+#ifndef T_SRV
+#define T_SRV 33
+#endif
+
+/*
+ * Global variables
+ */
+int qtype_counts[T_MAX];
+int opcode_counts[OP_MAX];
+int qclass_counts[C_MAX];
+
+#if HAVE_PCAP_H
+static pcap_t *pcap_obj = NULL;
+#endif
+
+static ip_list_t *IgnoreList = NULL;
+
+#if HAVE_PCAP_H
+static void (*Callback) (const rfc1035_header_t *) = NULL;
+
+static int query_count_intvl = 0;
+static int query_count_total = 0;
+# ifdef __OpenBSD__
+static struct bpf_timeval last_ts;
+# else
+static struct timeval last_ts;
+# endif /* __OpenBSD__ */
+#endif /* HAVE_PCAP_H */
+
+static int cmp_in6_addr (const struct in6_addr *a,
+       const struct in6_addr *b)
+{
+    int i;
+
+    assert (sizeof (struct in6_addr) == 16);
+
+    for (i = 0; i < 16; i++)
+       if (a->s6_addr[i] != b->s6_addr[i])
+           break;
+
+    if (i >= 16)
+       return (0);
+
+    return (a->s6_addr[i] > b->s6_addr[i] ? 1 : -1);
+} /* int cmp_addrinfo */
+
+static inline int ignore_list_match (const struct in6_addr *addr)
+{
+    ip_list_t *ptr;
+
+    for (ptr = IgnoreList; ptr != NULL; ptr = ptr->next)
+       if (cmp_in6_addr (addr, &ptr->addr) == 0)
+           return (1);
+    return (0);
+} /* int ignore_list_match */
+
+static void ignore_list_add (const struct in6_addr *addr)
+{
+    ip_list_t *new;
+
+    if (ignore_list_match (addr) != 0)
+       return;
+
+    new = malloc (sizeof (ip_list_t));
+    if (new == NULL)
+    {
+       perror ("malloc");
+       return;
+    }
+
+    memcpy (&new->addr, addr, sizeof (struct in6_addr));
+    new->next = IgnoreList;
+
+    IgnoreList = new;
+} /* void ignore_list_add */
+
+void ignore_list_add_name (const char *name)
+{
+    struct addrinfo *ai_list;
+    struct addrinfo *ai_ptr;
+    struct in6_addr  addr;
+    int status;
+
+    status = getaddrinfo (name, NULL, NULL, &ai_list);
+    if (status != 0)
+       return;
+
+    for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
+    {
+       if (ai_ptr->ai_family == AF_INET)
+       {
+           memset (&addr, '\0', sizeof (addr));
+           addr.s6_addr[10] = 0xFF;
+           addr.s6_addr[11] = 0xFF;
+           memcpy (addr.s6_addr + 12, &((struct sockaddr_in *) ai_ptr->ai_addr)->sin_addr, 4);
+
+           ignore_list_add (&addr);
+       }
+       else
+       {
+           ignore_list_add (&((struct sockaddr_in6 *) ai_ptr->ai_addr)->sin6_addr);
+       }
+    } /* for */
+
+    freeaddrinfo (ai_list);
+}
+
+#if HAVE_PCAP_H
+static void in6_addr_from_buffer (struct in6_addr *ia,
+       const void *buf, size_t buf_len,
+       int family)
+{
+    memset (ia, 0, sizeof (struct in6_addr));
+    if ((AF_INET == family) && (sizeof (uint32_t) == buf_len))
+    {
+       ia->s6_addr[10] = 0xFF;
+       ia->s6_addr[11] = 0xFF;
+       memcpy (ia->s6_addr + 12, buf, buf_len);
+    }
+    else if ((AF_INET6 == family) && (sizeof (struct in6_addr) == buf_len))
+    {
+       memcpy (ia, buf, buf_len);
+    }
+} /* void in6_addr_from_buffer */
+
+void dnstop_set_pcap_obj (pcap_t *po)
+{
+       pcap_obj = po;
+}
+
+void dnstop_set_callback (void (*cb) (const rfc1035_header_t *))
+{
+       Callback = cb;
+}
+
+#define RFC1035_MAXLABELSZ 63
+static int
+rfc1035NameUnpack(const char *buf, size_t sz, off_t * off, char *name, size_t ns
+)
+{
+    off_t no = 0;
+    unsigned char c;
+    size_t len;
+    static int loop_detect = 0;
+    if (loop_detect > 2)
+       return 4;               /* compression loop */
+    if (ns <= 0)
+       return 4;               /* probably compression loop */
+    do {
+       if ((*off) >= sz)
+           break;
+       c = *(buf + (*off));
+       if (c > 191) {
+           /* blasted compression */
+           int rc;
+           unsigned short s;
+           off_t ptr;
+           memcpy(&s, buf + (*off), sizeof(s));
+           s = ntohs(s);
+           (*off) += sizeof(s);
+           /* Sanity check */
+           if ((*off) >= sz)
+               return 1;       /* message too short */
+           ptr = s & 0x3FFF;
+           /* Make sure the pointer is inside this message */
+           if (ptr >= sz)
+               return 2;       /* bad compression ptr */
+           if (ptr < DNS_MSG_HDR_SZ)
+               return 2;       /* bad compression ptr */
+           loop_detect++;
+           rc = rfc1035NameUnpack(buf, sz, &ptr, name + no, ns - no);
+           loop_detect--;
+           return rc;
+       } else if (c > RFC1035_MAXLABELSZ) {
+           /*
+            * "(The 10 and 01 combinations are reserved for future use.)"
+            */
+           return 3;           /* reserved label/compression flags */
+           break;
+       } else {
+           (*off)++;
+           len = (size_t) c;
+           if (len == 0)
+               break;
+           if (len > (ns - 1))
+               len = ns - 1;
+           if ((*off) + len > sz)
+               return 4;       /* message is too short */
+           if (no + len + 1 > ns)
+               return 5;       /* qname would overflow name buffer */
+           memcpy(name + no, buf + (*off), len);
+           (*off) += len;
+           no += len;
+           *(name + (no++)) = '.';
+       }
+    } while (c > 0);
+    if (no > 0)
+       *(name + no - 1) = '\0';
+    /* make sure we didn't allow someone to overflow the name buffer */
+    assert(no <= ns);
+    return 0;
+}
+
+static int
+handle_dns(const char *buf, int len)
+{
+    rfc1035_header_t qh;
+    uint16_t us;
+    off_t offset;
+    char *t;
+    int status;
+
+    /* The DNS header is 12 bytes long */
+    if (len < DNS_MSG_HDR_SZ)
+       return 0;
+
+    memcpy(&us, buf + 0, 2);
+    qh.id = ntohs(us);
+
+    memcpy(&us, buf + 2, 2);
+    us = ntohs(us);
+    qh.qr = (us >> 15) & 0x01;
+    qh.opcode = (us >> 11) & 0x0F;
+    qh.aa = (us >> 10) & 0x01;
+    qh.tc = (us >> 9) & 0x01;
+    qh.rd = (us >> 8) & 0x01;
+    qh.ra = (us >> 7) & 0x01;
+    qh.z  = (us >> 6) & 0x01;
+    qh.ad = (us >> 5) & 0x01;
+    qh.cd = (us >> 4) & 0x01;
+    qh.rcode = us & 0x0F;
+
+    memcpy(&us, buf + 4, 2);
+    qh.qdcount = ntohs(us);
+
+    memcpy(&us, buf + 6, 2);
+    qh.ancount = ntohs(us);
+
+    memcpy(&us, buf + 8, 2);
+    qh.nscount = ntohs(us);
+
+    memcpy(&us, buf + 10, 2);
+    qh.arcount = ntohs(us);
+
+    offset = DNS_MSG_HDR_SZ;
+    memset(qh.qname, '\0', MAX_QNAME_SZ);
+    status = rfc1035NameUnpack(buf, len, &offset, qh.qname, MAX_QNAME_SZ);
+    if (status != 0)
+    {
+       INFO ("utils_dns: handle_dns: rfc1035NameUnpack failed "
+               "with status %i.", status);
+       return 0;
+    }
+    if ('\0' == qh.qname[0])
+       sstrncpy (qh.qname, ".", sizeof (qh.qname));
+    while ((t = strchr(qh.qname, '\n')))
+       *t = ' ';
+    while ((t = strchr(qh.qname, '\r')))
+       *t = ' ';
+    for (t = qh.qname; *t; t++)
+       *t = tolower((int) *t);
+
+    memcpy(&us, buf + offset, 2);
+    qh.qtype = ntohs(us);
+    memcpy(&us, buf + offset + 2, 2);
+    qh.qclass = ntohs(us);
+
+    qh.length = (uint16_t) len;
+
+    /* gather stats */
+    qtype_counts[qh.qtype]++;
+    qclass_counts[qh.qclass]++;
+    opcode_counts[qh.opcode]++;
+
+    if (Callback != NULL)
+           Callback (&qh);
+
+    return 1;
+}
+
+static int
+handle_udp(const struct udphdr *udp, int len)
+{
+    char buf[PCAP_SNAPLEN];
+    if ((ntohs (udp->UDP_DEST) != 53)
+                   && (ntohs (udp->UDP_SRC) != 53))
+       return 0;
+    memcpy(buf, udp + 1, len - sizeof(*udp));
+    if (0 == handle_dns(buf, len - sizeof(*udp)))
+       return 0;
+    return 1;
+}
+
+#if HAVE_NETINET_IP6_H
+static int
+handle_ipv6 (struct ip6_hdr *ipv6, int len)
+{
+    char buf[PCAP_SNAPLEN];
+    unsigned int offset;
+    int nexthdr;
+
+    struct in6_addr s_addr;
+    uint16_t payload_len;
+
+    if (0 > len)
+       return (0);
+
+    offset = sizeof (struct ip6_hdr);
+    nexthdr = ipv6->ip6_nxt;
+    s_addr = ipv6->ip6_src;
+    payload_len = ntohs (ipv6->ip6_plen);
+
+    if (ignore_list_match (&s_addr))
+           return (0);
+
+    /* Parse extension headers. This only handles the standard headers, as
+     * defined in RFC 2460, correctly. Fragments are discarded. */
+    while ((IPPROTO_ROUTING == nexthdr) /* routing header */
+           || (IPPROTO_HOPOPTS == nexthdr) /* Hop-by-Hop options. */
+           || (IPPROTO_FRAGMENT == nexthdr) /* fragmentation header. */
+           || (IPPROTO_DSTOPTS == nexthdr) /* destination options. */
+           || (IPPROTO_DSTOPTS == nexthdr) /* destination options. */
+           || (IPPROTO_AH == nexthdr) /* destination options. */
+           || (IPPROTO_ESP == nexthdr)) /* encapsulating security payload. */
+    {
+       struct ip6_ext ext_hdr;
+       uint16_t ext_hdr_len;
+
+       /* Catch broken packets */
+       if ((offset + sizeof (struct ip6_ext)) > (unsigned int)len)
+           return (0);
+
+       /* Cannot handle fragments. */
+       if (IPPROTO_FRAGMENT == nexthdr)
+           return (0);
+
+       memcpy (&ext_hdr, (char *) ipv6 + offset, sizeof (struct ip6_ext));
+       nexthdr = ext_hdr.ip6e_nxt;
+       ext_hdr_len = (8 * (ntohs (ext_hdr.ip6e_len) + 1));
+
+       /* This header is longer than the packets payload.. WTF? */
+       if (ext_hdr_len > payload_len)
+           return (0);
+
+       offset += ext_hdr_len;
+       payload_len -= ext_hdr_len;
+    } /* while */
+
+    /* Catch broken and empty packets */
+    if (((offset + payload_len) > (unsigned int)len)
+           || (payload_len == 0)
+           || (payload_len > PCAP_SNAPLEN))
+       return (0);
+
+    if (IPPROTO_UDP != nexthdr)
+       return (0);
+
+    memcpy (buf, (char *) ipv6 + offset, payload_len);
+    if (handle_udp ((struct udphdr *) buf, payload_len) == 0)
+       return (0);
+
+    return (1); /* Success */
+} /* int handle_ipv6 */
+/* #endif HAVE_NETINET_IP6_H */
+
+#else /* if !HAVE_NETINET_IP6_H */
+static int
+handle_ipv6 (__attribute__((unused)) void *pkg,
+       __attribute__((unused)) int len)
+{
+    return (0);
+}
+#endif /* !HAVE_NETINET_IP6_H */
+
+static int
+handle_ip(const struct ip *ip, int len)
+{
+    char buf[PCAP_SNAPLEN];
+    int offset = ip->ip_hl << 2;
+    struct in6_addr s_addr;
+    struct in6_addr d_addr;
+
+    if (ip->ip_v == 6)
+       return (handle_ipv6 ((void *) ip, len));
+
+    in6_addr_from_buffer (&s_addr, &ip->ip_src.s_addr, sizeof (ip->ip_src.s_addr), AF_INET);
+    in6_addr_from_buffer (&d_addr, &ip->ip_dst.s_addr, sizeof (ip->ip_dst.s_addr), AF_INET);
+    if (ignore_list_match (&s_addr))
+           return (0);
+    if (IPPROTO_UDP != ip->ip_p)
+       return 0;
+    memcpy(buf, (void *) ip + offset, len - offset);
+    if (0 == handle_udp((struct udphdr *) buf, len - offset))
+       return 0;
+    return 1;
+}
+
+#if HAVE_NET_IF_PPP_H
+static int
+handle_ppp(const u_char * pkt, int len)
+{
+    char buf[PCAP_SNAPLEN];
+    unsigned short us;
+    unsigned short proto;
+    if (len < 2)
+       return 0;
+    if (*pkt == PPP_ADDRESS_VAL && *(pkt + 1) == PPP_CONTROL_VAL) {
+       pkt += 2;               /* ACFC not used */
+       len -= 2;
+    }
+    if (len < 2)
+       return 0;
+    if (*pkt % 2) {
+       proto = *pkt;           /* PFC is used */
+       pkt++;
+       len--;
+    } else {
+       memcpy(&us, pkt, sizeof(us));
+       proto = ntohs(us);
+       pkt += 2;
+       len -= 2;
+    }
+    if (ETHERTYPE_IP != proto && PPP_IP != proto)
+       return 0;
+    memcpy(buf, pkt, len);
+    return handle_ip((struct ip *) buf, len);
+}
+#endif /* HAVE_NET_IF_PPP_H */
+
+static int
+handle_null(const u_char * pkt, int len)
+{
+    unsigned int family;
+    memcpy(&family, pkt, sizeof(family));
+    if (AF_INET != family)
+       return 0;
+    return handle_ip((struct ip *) (pkt + 4), len - 4);
+}
+
+#ifdef DLT_LOOP
+static int
+handle_loop(const u_char * pkt, int len)
+{
+    unsigned int family;
+    memcpy(&family, pkt, sizeof(family));
+    if (AF_INET != ntohl(family))
+       return 0;
+    return handle_ip((struct ip *) (pkt + 4), len - 4);
+}
+
+#endif
+
+#ifdef DLT_RAW
+static int
+handle_raw(const u_char * pkt, int len)
+{
+    return handle_ip((struct ip *) pkt, len);
+}
+
+#endif
+
+static int
+handle_ether(const u_char * pkt, int len)
+{
+    char buf[PCAP_SNAPLEN];
+    struct ether_header *e = (void *) pkt;
+    unsigned short etype = ntohs(e->ether_type);
+    if (len < ETHER_HDR_LEN)
+       return 0;
+    pkt += ETHER_HDR_LEN;
+    len -= ETHER_HDR_LEN;
+    if (ETHERTYPE_8021Q == etype) {
+       etype = ntohs(*(unsigned short *) (pkt + 2));
+       pkt += 4;
+       len -= 4;
+    }
+    if ((ETHERTYPE_IP != etype)
+           && (ETHERTYPE_IPV6 != etype))
+       return 0;
+    memcpy(buf, pkt, len);
+    if (ETHERTYPE_IPV6 == etype)
+       return (handle_ipv6 ((void *) buf, len));
+    else
+       return handle_ip((struct ip *) buf, len);
+}
+
+#ifdef DLT_LINUX_SLL
+static int
+handle_linux_sll (const u_char *pkt, int len)
+{
+    struct sll_header
+    {
+       uint16_t pkt_type;
+       uint16_t dev_type;
+       uint16_t addr_len;
+       uint8_t  addr[8];
+       uint16_t proto_type;
+    } *hdr;
+    uint16_t etype;
+
+    if ((0 > len) || ((unsigned int)len < sizeof (struct sll_header)))
+       return (0);
+
+    hdr  = (struct sll_header *) pkt;
+    pkt  = (u_char *) (hdr + 1);
+    len -= sizeof (struct sll_header);
+
+    etype = ntohs (hdr->proto_type);
+
+    if ((ETHERTYPE_IP != etype)
+           && (ETHERTYPE_IPV6 != etype))
+       return 0;
+
+    if (ETHERTYPE_IPV6 == etype)
+       return (handle_ipv6 ((void *) pkt, len));
+    else
+       return handle_ip((struct ip *) pkt, len);
+}
+#endif /* DLT_LINUX_SLL */
+
+/* public function */
+void handle_pcap(u_char *udata, const struct pcap_pkthdr *hdr, const u_char *pkt)
+{
+    int status;
+
+    if (hdr->caplen < ETHER_HDR_LEN)
+       return;
+
+    switch (pcap_datalink (pcap_obj))
+    {
+       case DLT_EN10MB:
+           status = handle_ether (pkt, hdr->caplen);
+           break;
+#if HAVE_NET_IF_PPP_H
+       case DLT_PPP:
+           status = handle_ppp (pkt, hdr->caplen);
+           break;
+#endif
+#ifdef DLT_LOOP
+       case DLT_LOOP:
+           status = handle_loop (pkt, hdr->caplen);
+           break;
+#endif
+#ifdef DLT_RAW
+       case DLT_RAW:
+           status = handle_raw (pkt, hdr->caplen);
+           break;
+#endif
+#ifdef DLT_LINUX_SLL
+       case DLT_LINUX_SLL:
+           status = handle_linux_sll (pkt, hdr->caplen);
+           break;
+#endif
+       case DLT_NULL:
+           status = handle_null (pkt, hdr->caplen);
+           break;
+
+       default:
+           ERROR ("handle_pcap: unsupported data link type %d",
+                   pcap_datalink(pcap_obj));
+           status = 0;
+           break;
+    } /* switch (pcap_datalink(pcap_obj)) */
+
+    if (0 == status)
+       return;
+
+    query_count_intvl++;
+    query_count_total++;
+    last_ts = hdr->ts;
+}
+#endif /* HAVE_PCAP_H */
+
+const char *qtype_str(int t)
+{
+    static char buf[32];
+    switch (t) {
+#if (defined (__NAMESER)) && (__NAMESER >= 19991001)
+           case ns_t_a:        return ("A");
+           case ns_t_ns:       return ("NS");
+           case ns_t_md:       return ("MD");
+           case ns_t_mf:       return ("MF");
+           case ns_t_cname:    return ("CNAME");
+           case ns_t_soa:      return ("SOA");
+           case ns_t_mb:       return ("MB");
+           case ns_t_mg:       return ("MG");
+           case ns_t_mr:       return ("MR");
+           case ns_t_null:     return ("NULL");
+           case ns_t_wks:      return ("WKS");
+           case ns_t_ptr:      return ("PTR");
+           case ns_t_hinfo:    return ("HINFO");
+           case ns_t_minfo:    return ("MINFO");
+           case ns_t_mx:       return ("MX");
+           case ns_t_txt:      return ("TXT");
+           case ns_t_rp:       return ("RP");
+           case ns_t_afsdb:    return ("AFSDB");
+           case ns_t_x25:      return ("X25");
+           case ns_t_isdn:     return ("ISDN");
+           case ns_t_rt:       return ("RT");
+           case ns_t_nsap:     return ("NSAP");
+           case ns_t_nsap_ptr: return ("NSAP-PTR");
+           case ns_t_sig:      return ("SIG");
+           case ns_t_key:      return ("KEY");
+           case ns_t_px:       return ("PX");
+           case ns_t_gpos:     return ("GPOS");
+           case ns_t_aaaa:     return ("AAAA");
+           case ns_t_loc:      return ("LOC");
+           case ns_t_nxt:      return ("NXT");
+           case ns_t_eid:      return ("EID");
+           case ns_t_nimloc:   return ("NIMLOC");
+           case ns_t_srv:      return ("SRV");
+           case ns_t_atma:     return ("ATMA");
+           case ns_t_naptr:    return ("NAPTR");
+           case ns_t_kx:       return ("KX");
+           case ns_t_cert:     return ("CERT");
+           case ns_t_a6:       return ("A6");
+           case ns_t_dname:    return ("DNAME");
+           case ns_t_sink:     return ("SINK");
+           case ns_t_opt:      return ("OPT");
+# if __NAMESER >= 19991006
+           case ns_t_tsig:     return ("TSIG");
+# endif
+           case ns_t_ixfr:     return ("IXFR");
+           case ns_t_axfr:     return ("AXFR");
+           case ns_t_mailb:    return ("MAILB");
+           case ns_t_maila:    return ("MAILA");
+           case ns_t_any:      return ("ANY");
+           case ns_t_zxfr:     return ("ZXFR");
+/* #endif __NAMESER >= 19991006 */
+#elif (defined (__BIND)) && (__BIND >= 19950621)
+           case T_A:           return ("A"); /* 1 ... */
+           case T_NS:          return ("NS");
+           case T_MD:          return ("MD");
+           case T_MF:          return ("MF");
+           case T_CNAME:       return ("CNAME");
+           case T_SOA:         return ("SOA");
+           case T_MB:          return ("MB");
+           case T_MG:          return ("MG");
+           case T_MR:          return ("MR");
+           case T_NULL:        return ("NULL");
+           case T_WKS:         return ("WKS");
+           case T_PTR:         return ("PTR");
+           case T_HINFO:       return ("HINFO");
+           case T_MINFO:       return ("MINFO");
+           case T_MX:          return ("MX");
+           case T_TXT:         return ("TXT");
+           case T_RP:          return ("RP");
+           case T_AFSDB:       return ("AFSDB");
+           case T_X25:         return ("X25");
+           case T_ISDN:        return ("ISDN");
+           case T_RT:          return ("RT");
+           case T_NSAP:        return ("NSAP");
+           case T_NSAP_PTR:    return ("NSAP_PTR");
+           case T_SIG:         return ("SIG");
+           case T_KEY:         return ("KEY");
+           case T_PX:          return ("PX");
+           case T_GPOS:        return ("GPOS");
+           case T_AAAA:        return ("AAAA");
+           case T_LOC:         return ("LOC");
+           case T_NXT:         return ("NXT");
+           case T_EID:         return ("EID");
+           case T_NIMLOC:      return ("NIMLOC");
+           case T_SRV:         return ("SRV");
+           case T_ATMA:        return ("ATMA");
+           case T_NAPTR:       return ("NAPTR"); /* ... 35 */
+#if (__BIND >= 19960801)
+           case T_KX:          return ("KX"); /* 36 ... */
+           case T_CERT:        return ("CERT");
+           case T_A6:          return ("A6");
+           case T_DNAME:       return ("DNAME");
+           case T_SINK:        return ("SINK");
+           case T_OPT:         return ("OPT");
+           case T_APL:         return ("APL");
+           case T_DS:          return ("DS");
+           case T_SSHFP:       return ("SSHFP");
+           case T_RRSIG:       return ("RRSIG");
+           case T_NSEC:        return ("NSEC");
+           case T_DNSKEY:      return ("DNSKEY"); /* ... 48 */
+           case T_TKEY:        return ("TKEY"); /* 249 */
+#endif /* __BIND >= 19960801 */
+           case T_TSIG:        return ("TSIG"); /* 250 ... */
+           case T_IXFR:        return ("IXFR");
+           case T_AXFR:        return ("AXFR");
+           case T_MAILB:       return ("MAILB");
+           case T_MAILA:       return ("MAILA");
+           case T_ANY:         return ("ANY"); /* ... 255 */
+#endif /* __BIND >= 19950621 */
+           default:
+                   ssnprintf (buf, sizeof (buf), "#%i", t);
+                   return (buf);
+    }; /* switch (t) */
+    /* NOTREACHED */
+    return (NULL);
+}
+
+const char *opcode_str (int o)
+{
+    static char buf[30];
+    switch (o) {
+    case 0:
+       return "Query";
+       break;
+    case 1:
+       return "Iquery";
+       break;
+    case 2:
+       return "Status";
+       break;
+    case 4:
+       return "Notify";
+       break;
+    case 5:
+       return "Update";
+       break;
+    default:
+       ssnprintf(buf, sizeof (buf), "Opcode%d", o);
+       return buf;
+    }
+    /* NOTREACHED */
+}
+
+const char *rcode_str (int rcode)
+{
+       static char buf[32];
+       switch (rcode)
+       {
+#if (defined (__NAMESER)) && (__NAMESER >= 19991006)
+               case ns_r_noerror:  return ("NOERROR");
+               case ns_r_formerr:  return ("FORMERR");
+               case ns_r_servfail: return ("SERVFAIL");
+               case ns_r_nxdomain: return ("NXDOMAIN");
+               case ns_r_notimpl:  return ("NOTIMPL");
+               case ns_r_refused:  return ("REFUSED");
+               case ns_r_yxdomain: return ("YXDOMAIN");
+               case ns_r_yxrrset:  return ("YXRRSET");
+               case ns_r_nxrrset:  return ("NXRRSET");
+               case ns_r_notauth:  return ("NOTAUTH");
+               case ns_r_notzone:  return ("NOTZONE");
+               case ns_r_max:      return ("MAX");
+               case ns_r_badsig:   return ("BADSIG");
+               case ns_r_badkey:   return ("BADKEY");
+               case ns_r_badtime:  return ("BADTIME");
+/* #endif __NAMESER >= 19991006 */
+#elif (defined (__BIND)) && (__BIND >= 19950621)
+               case NOERROR:       return ("NOERROR");
+               case FORMERR:       return ("FORMERR");
+               case SERVFAIL:      return ("SERVFAIL");
+               case NXDOMAIN:      return ("NXDOMAIN");
+               case NOTIMP:        return ("NOTIMP");
+               case REFUSED:       return ("REFUSED");
+#if defined (YXDOMAIN) && defined (NXRRSET)
+               case YXDOMAIN:      return ("YXDOMAIN");
+               case YXRRSET:       return ("YXRRSET");
+               case NXRRSET:       return ("NXRRSET");
+               case NOTAUTH:       return ("NOTAUTH");
+               case NOTZONE:       return ("NOTZONE");
+#endif  /* RFC2136 rcodes */
+#endif /* __BIND >= 19950621 */
+               default:
+                       ssnprintf (buf, sizeof (buf), "RCode%i", rcode);
+                       return (buf);
+       }
+       /* Never reached */
+       return (NULL);
+} /* const char *rcode_str (int rcode) */
+
+#if 0
+static int
+main(int argc, char *argv[])
+{
+    char errbuf[PCAP_ERRBUF_SIZE];
+    int x;
+    struct stat sb;
+    int readfile_state = 0;
+    struct bpf_program fp;
+
+    port53 = htons(53);
+    SubReport = Sources_report;
+    ignore_addr.s_addr = 0;
+    progname = strdup(strrchr(argv[0], '/') ? strchr(argv[0], '/') + 1 : argv[0]);
+    srandom(time(NULL));
+    ResetCounters();
+
+    while ((x = getopt(argc, argv, "ab:f:i:pst")) != -1) {
+       switch (x) {
+       case 'a':
+           anon_flag = 1;
+           break;
+       case 's':
+           sld_flag = 1;
+           break;
+       case 't':
+           nld_flag = 1;
+           break;
+       case 'p':
+           promisc_flag = 0;
+           break;
+       case 'b':
+           bpf_program_str = strdup(optarg);
+           break;
+       case 'i':
+           ignore_addr.s_addr = inet_addr(optarg);
+           break;
+       case 'f':
+           set_filter(optarg);
+           break;
+       default:
+           usage();
+           break;
+       }
+    }
+    argc -= optind;
+    argv += optind;
+
+    if (argc < 1)
+       usage();
+    device = strdup(argv[0]);
+
+    if (0 == stat(device, &sb))
+       readfile_state = 1;
+    if (readfile_state) {
+       pcap_obj = pcap_open_offline(device, errbuf);
+    } else {
+       pcap_obj = pcap_open_live(device, PCAP_SNAPLEN, promisc_flag, 1000, errbuf);
+    }
+    if (NULL == pcap_obj) {
+       fprintf(stderr, "pcap_open_*: %s\n", errbuf);
+       exit(1);
+    }
+
+    if (0 == isatty(1)) {
+       if (0 == readfile_state) {
+           fprintf(stderr, "Non-interactive mode requires savefile argument\n");
+           exit(1);
+       }
+       interactive = 0;
+       print_func = printf;
+    }
+
+    memset(&fp, '\0', sizeof(fp));
+    x = pcap_compile(pcap_obj, &fp, bpf_program_str, 1, 0);
+    if (x < 0) {
+       fprintf(stderr, "pcap_compile failed\n");
+       exit(1);
+    }
+    x = pcap_setfilter(pcap_obj, &fp);
+    if (x < 0) {
+       fprintf(stderr, "pcap_setfilter failed\n");
+       exit(1);
+    }
+
+    /*
+     * non-blocking call added for Mac OS X bugfix.  Sent by Max Horn.
+     * ref http://www.tcpdump.org/lists/workers/2002/09/msg00033.html
+     */
+    x = pcap_setnonblock(pcap_obj, 1, errbuf);
+    if (x < 0) {
+       fprintf(stderr, "pcap_setnonblock failed: %s\n", errbuf);
+       exit(1);
+    }
+
+    switch (pcap_datalink(pcap_obj)) {
+    case DLT_EN10MB:
+       handle_datalink = handle_ether;
+       break;
+#if HAVE_NET_IF_PPP_H
+    case DLT_PPP:
+       handle_datalink = handle_ppp;
+       break;
+#endif
+#ifdef DLT_LOOP
+    case DLT_LOOP:
+       handle_datalink = handle_loop;
+       break;
+#endif
+#ifdef DLT_RAW
+    case DLT_RAW:
+       handle_datalink = handle_raw;
+       break;
+#endif
+    case DLT_NULL:
+       handle_datalink = handle_null;
+       break;
+    default:
+       fprintf(stderr, "unsupported data link type %d\n",
+           pcap_datalink(pcap_obj));
+       return 1;
+       break;
+    }
+    if (interactive) {
+       init_curses();
+       while (0 == Quit) {
+           if (readfile_state < 2) {
+               /*
+                * On some OSes select() might return 0 even when
+                * there are packets to process.  Thus, we always
+                * ignore its return value and just call pcap_dispatch()
+                * anyway.
+                */
+               if (0 == readfile_state)        /* interactive */
+                   pcap_select(pcap_obj, 1, 0);
+               x = pcap_dispatch(pcap_obj, 50, handle_pcap, NULL);
+           }
+           if (0 == x && 1 == readfile_state) {
+               /* block on keyboard until user quits */
+               readfile_state++;
+               nodelay(w, 0);
+           }
+           keyboard();
+           cron_pre();
+           report();
+           cron_post();
+       }
+       endwin();               /* klin, Thu Nov 28 08:56:51 2002 */
+    } else {
+       while (pcap_dispatch(pcap_obj, 50, handle_pcap, NULL))
+               (void) 0;
+       cron_pre();
+       Sources_report(); print_func("\n");
+       Destinatioreport(); print_func("\n");
+       Qtypes_report(); print_func("\n");
+       Opcodes_report(); print_func("\n");
+       Tld_report(); print_func("\n");
+       Sld_report(); print_func("\n");
+       Nld_report(); print_func("\n");
+       SldBySource_report();
+    }
+
+    pcap_close(pcap_obj);
+    return 0;
+} /* static int main(int argc, char *argv[]) */
+#endif
+/*
+ * vim:shiftwidth=4:tabstop=8:softtabstop=4
+ */
diff --git a/src/utils_dns.h b/src/utils_dns.h
new file mode 100644 (file)
index 0000000..56213af
--- /dev/null
@@ -0,0 +1,93 @@
+#ifndef COLLECTD_UTILS_DNS_H
+#define COLLECTD_UTILS_DNS_H 1
+/*
+ * collectd - src/utils_dns.h
+ * Copyright (C) 2006  Florian octo Forster
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of The Measurement Factory nor the names of its
+ *    contributors may be used to endorse or promote products derived from this
+ *    software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ */
+
+#include "config.h"
+
+#include <arpa/nameser.h>
+#include <stdint.h>
+
+#if HAVE_PCAP_H
+# include <pcap.h>
+#endif
+
+#define DNS_MSG_HDR_SZ 12
+
+#define T_MAX 65536
+#define OP_MAX 16
+#define C_MAX 65536
+#define MAX_QNAME_SZ 512
+
+struct rfc1035_header_s {
+    uint16_t id;
+    unsigned int qr:1;
+    unsigned int opcode:4;
+    unsigned int aa:1;
+    unsigned int tc:1;
+    unsigned int rd:1;
+    unsigned int ra:1;
+    unsigned int z:1;
+    unsigned int ad:1;
+    unsigned int cd:1;
+    unsigned int rcode:4;
+    uint16_t qdcount;
+    uint16_t ancount;
+    uint16_t nscount;
+    uint16_t arcount;
+    uint16_t qtype;
+    uint16_t qclass;
+    char     qname[MAX_QNAME_SZ];
+    uint16_t length;
+};
+typedef struct rfc1035_header_s rfc1035_header_t;
+
+extern int qtype_counts[T_MAX];
+extern int opcode_counts[OP_MAX];
+extern int qclass_counts[C_MAX];
+
+#if HAVE_PCAP_H
+void dnstop_set_pcap_obj (pcap_t *po);
+#endif
+void dnstop_set_callback (void (*cb) (const rfc1035_header_t *));
+
+void ignore_list_add_name (const char *name);
+#if HAVE_PCAP_H
+void handle_pcap (u_char * udata, const struct pcap_pkthdr *hdr, const u_char * pkt);
+#endif
+
+const char *qtype_str(int t);
+const char *opcode_str(int o);
+const char *rcode_str (int r);
+
+#endif /* !COLLECTD_UTILS_DNS_H */
diff --git a/src/utils_fbhash.c b/src/utils_fbhash.c
new file mode 100644 (file)
index 0000000..d20b7e3
--- /dev/null
@@ -0,0 +1,270 @@
+/**
+ * collectd - src/utils_fbhash.c
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "plugin.h"
+
+#include <pthread.h>
+
+#include "utils_fbhash.h"
+#include "utils_avltree.h"
+
+struct fbhash_s
+{
+  char *filename;
+  time_t mtime;
+
+  pthread_mutex_t lock;
+  c_avl_tree_t *tree;
+};
+
+/* 
+ * Private functions
+ */
+static void fbh_free_tree (c_avl_tree_t *tree) /* {{{ */
+{
+  int status;
+
+  if (tree == NULL)
+    return;
+
+  while (42)
+  {
+    char *key = NULL;
+    char *value = NULL;
+
+    status = c_avl_pick (tree, (void *) &key, (void *) &value);
+    if (status != 0)
+      break;
+
+    free (key);
+    free (value);
+  }
+
+  c_avl_destroy (tree);
+} /* }}} void fbh_free_tree */
+
+static int fbh_read_file (fbhash_t *h) /* {{{ */
+{
+  FILE *fh;
+  char buffer[4096];
+  struct flock fl;
+  c_avl_tree_t *tree;
+  int status;
+
+  fh = fopen (h->filename, "r");
+  if (fh == NULL)
+    return (-1);
+
+  memset (&fl, 0, sizeof (fl));
+  fl.l_type = F_RDLCK;
+  fl.l_whence = SEEK_SET;
+  fl.l_start = 0;
+  fl.l_len = 0; /* == entire file */
+  /* TODO: Lock file? -> fcntl */
+
+  status = fcntl (fileno (fh), F_SETLK, &fl);
+  if (status != 0)
+  {
+    fclose (fh);
+    return (-1);
+  }
+
+  tree = c_avl_create ((void *) strcmp);
+  if (tree == NULL)
+  {
+    fclose (fh);
+    return (-1);
+  }
+
+  /* Read `fh' into `tree' */
+  while (fgets (buffer, sizeof (buffer), fh) != NULL) /* {{{ */
+  {
+    size_t len;
+    char *key;
+    char *value;
+
+    char *key_copy;
+    char *value_copy;
+
+    buffer[sizeof (buffer) - 1] = 0;
+    len = strlen (buffer);
+
+    /* Remove trailing newline characters. */
+    while ((len > 0)
+        && ((buffer[len - 1] == '\n') || (buffer[len - 1] == '\r')))
+    {
+      len--;
+      buffer[len] = 0;
+    }
+
+    /* Seek first non-space character */
+    key = buffer;
+    while ((*key != 0) && isspace ((int) *key))
+      key++;
+
+    /* Skip empty lines and comments */
+    if ((key[0] == 0) || (key[0] == '#'))
+      continue;
+
+    /* Seek first colon */
+    value = strchr (key, ':');
+    if (value == NULL)
+      continue;
+
+    /* Null-terminate `key'. */
+    *value = 0;
+    value++;
+
+    /* Skip leading whitespace */
+    while ((*value != 0) && isspace ((int) *value))
+      value++;
+
+    /* Skip lines without value */
+    if (value[0] == 0)
+      continue;
+
+    key_copy = strdup (key);
+    value_copy = strdup (value);
+
+    if ((key_copy == NULL) || (value_copy == NULL))
+    {
+      free (key_copy);
+      free (value_copy);
+      continue;
+    }
+
+    status = c_avl_insert (tree, key_copy, value_copy);
+    if (status != 0)
+    {
+      free (key_copy);
+      free (value_copy);
+      continue;
+    }
+
+    DEBUG ("utils_fbhash: fbh_read_file: key = %s; value = %s;",
+        key, value);
+  } /* }}} while (fgets) */
+
+  fclose (fh);
+
+  fbh_free_tree (h->tree);
+  h->tree = tree;
+
+  return (0);
+} /* }}} int fbh_read_file */
+
+static int fbh_check_file (fbhash_t *h) /* {{{ */
+{
+  struct stat statbuf;
+  int status;
+
+  memset (&statbuf, 0, sizeof (statbuf));
+
+  status = stat (h->filename, &statbuf);
+  if (status != 0)
+    return (-1);
+
+  if (h->mtime >= statbuf.st_mtime)
+    return (0);
+
+  status = fbh_read_file (h);
+  if (status == 0)
+    h->mtime = statbuf.st_mtime;
+
+  return (status);
+} /* }}} int fbh_check_file */
+
+/* 
+ * Public functions
+ */
+fbhash_t *fbh_create (const char *file) /* {{{ */
+{
+  fbhash_t *h;
+  int status;
+
+  if (file == NULL)
+    return (NULL);
+
+  h = malloc (sizeof (*h));
+  if (h == NULL)
+    return (NULL);
+  memset (h, 0, sizeof (*h));
+
+  h->filename = strdup (file);
+  if (h->filename == NULL)
+  {
+    free (h);
+    return (NULL);
+  }
+
+  h->mtime = 0;
+  pthread_mutex_init (&h->lock, /* attr = */ NULL);
+
+  status = fbh_check_file (h);
+  if (status != 0)
+  {
+    fbh_destroy (h);
+    return (NULL);
+  }
+
+  return (h);
+} /* }}} fbhash_t *fbh_create */
+
+void fbh_destroy (fbhash_t *h) /* {{{ */
+{
+  if (h == NULL)
+    return;
+
+  free (h->filename);
+  fbh_free_tree (h->tree);
+} /* }}} void fbh_destroy */
+
+char *fbh_get (fbhash_t *h, const char *key) /* {{{ */
+{
+  char *value;
+  char *value_copy;
+  int status;
+
+  if ((h == NULL) || (key == NULL))
+    return (NULL);
+
+  value = NULL;
+  value_copy = NULL;
+
+  pthread_mutex_lock (&h->lock);
+
+  /* TODO: Checking this everytime may be a bit much..? */
+  fbh_check_file (h);
+
+  status = c_avl_get (h->tree, key, (void *) &value);
+  if (status == 0)
+  {
+    assert (value != NULL);
+    value_copy = strdup (value);
+  }
+
+  pthread_mutex_unlock (&h->lock);
+
+  return (value_copy);
+} /* }}} char *fbh_get */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/utils_fbhash.h b/src/utils_fbhash.h
new file mode 100644 (file)
index 0000000..0a0305e
--- /dev/null
@@ -0,0 +1,47 @@
+/**
+ * collectd - src/utils_fbhash.h
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef UTILS_FBHASH_H
+#define UTILS_FBHASH_H 1
+
+/*
+ * File-backed hash
+ *
+ * This module reads a file of the form
+ *   key: value
+ * into a hash, which can then be queried. The file is given to `fbh_create',
+ * the hash is queried using `fbh_get'. If the file is changed during runtime,
+ * it will automatically be re-read.
+ */
+
+struct fbhash_s;
+typedef struct fbhash_s fbhash_t;
+
+fbhash_t *fbh_create (const char *file);
+void fbh_destroy (fbhash_t *h);
+
+/* Returns the value as a newly allocated `char *'. It's the caller's
+ * responsibility to free this memory. */
+char *fbh_get (fbhash_t *h, const char *key);
+
+#endif /* UTILS_FBHASH_H */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/utils_format_json.c b/src/utils_format_json.c
new file mode 100644 (file)
index 0000000..2a5526b
--- /dev/null
@@ -0,0 +1,382 @@
+/**
+ * collectd - src/utils_format_json.c
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "plugin.h"
+#include "common.h"
+
+#include "utils_cache.h"
+#include "utils_format_json.h"
+
+static int escape_string (char *buffer, size_t buffer_size, /* {{{ */
+    const char *string)
+{
+  size_t src_pos;
+  size_t dst_pos;
+
+  if ((buffer == NULL) || (string == NULL))
+    return (-EINVAL);
+
+  if (buffer_size < 3)
+    return (-ENOMEM);
+
+  dst_pos = 0;
+
+#define BUFFER_ADD(c) do { \
+  if (dst_pos >= (buffer_size - 1)) { \
+    buffer[buffer_size - 1] = 0; \
+    return (-ENOMEM); \
+  } \
+  buffer[dst_pos] = (c); \
+  dst_pos++; \
+} while (0)
+
+  /* Escape special characters */
+  BUFFER_ADD ('"');
+  for (src_pos = 0; string[src_pos] != 0; src_pos++)
+  {
+    if ((string[src_pos] == '"')
+        || (string[src_pos] == '\\'))
+    {
+      BUFFER_ADD ('\\');
+      BUFFER_ADD (string[src_pos]);
+    }
+    else if (string[src_pos] <= 0x001F)
+      BUFFER_ADD ('?');
+    else
+      BUFFER_ADD (string[src_pos]);
+  } /* for */
+  BUFFER_ADD ('"');
+  buffer[dst_pos] = 0;
+
+#undef BUFFER_ADD
+
+  return (0);
+} /* }}} int buffer_add_string */
+
+static int values_to_json (char *buffer, size_t buffer_size, /* {{{ */
+                const data_set_t *ds, const value_list_t *vl, int store_rates)
+{
+  size_t offset = 0;
+  int i;
+  gauge_t *rates = NULL;
+
+  memset (buffer, 0, buffer_size);
+
+#define BUFFER_ADD(...) do { \
+  int status; \
+  status = ssnprintf (buffer + offset, buffer_size - offset, \
+      __VA_ARGS__); \
+  if (status < 1) \
+  { \
+    sfree(rates); \
+    return (-1); \
+  } \
+  else if (((size_t) status) >= (buffer_size - offset)) \
+  { \
+    sfree(rates); \
+    return (-ENOMEM); \
+  } \
+  else \
+    offset += ((size_t) status); \
+} while (0)
+
+  BUFFER_ADD ("[");
+  for (i = 0; i < ds->ds_num; i++)
+  {
+    if (i > 0)
+      BUFFER_ADD (",");
+
+    if (ds->ds[i].type == DS_TYPE_GAUGE)
+    {
+      if(isfinite (vl->values[i].gauge))
+        BUFFER_ADD ("%g", vl->values[i].gauge);
+      else
+        BUFFER_ADD ("null");
+    }
+    else if (store_rates)
+    {
+      if (rates == NULL)
+        rates = uc_get_rate (ds, vl);
+      if (rates == NULL)
+      {
+        WARNING ("utils_format_json: uc_get_rate failed.");
+        sfree(rates);
+        return (-1);
+      }
+
+      if(isfinite (rates[i]))
+        BUFFER_ADD ("%g", rates[i]);
+      else
+        BUFFER_ADD ("null");
+    }
+    else if (ds->ds[i].type == DS_TYPE_COUNTER)
+      BUFFER_ADD ("%llu", vl->values[i].counter);
+    else if (ds->ds[i].type == DS_TYPE_DERIVE)
+      BUFFER_ADD ("%"PRIi64, vl->values[i].derive);
+    else if (ds->ds[i].type == DS_TYPE_ABSOLUTE)
+      BUFFER_ADD ("%"PRIu64, vl->values[i].absolute);
+    else
+    {
+      ERROR ("format_json: Unknown data source type: %i",
+          ds->ds[i].type);
+      sfree (rates);
+      return (-1);
+    }
+  } /* for ds->ds_num */
+  BUFFER_ADD ("]");
+
+#undef BUFFER_ADD
+
+  DEBUG ("format_json: values_to_json: buffer = %s;", buffer);
+  sfree(rates);
+  return (0);
+} /* }}} int values_to_json */
+
+static int dstypes_to_json (char *buffer, size_t buffer_size, /* {{{ */
+                const data_set_t *ds, const value_list_t *vl)
+{
+  size_t offset = 0;
+  int i;
+
+  memset (buffer, 0, buffer_size);
+
+#define BUFFER_ADD(...) do { \
+  int status; \
+  status = ssnprintf (buffer + offset, buffer_size - offset, \
+      __VA_ARGS__); \
+  if (status < 1) \
+    return (-1); \
+  else if (((size_t) status) >= (buffer_size - offset)) \
+    return (-ENOMEM); \
+  else \
+    offset += ((size_t) status); \
+} while (0)
+
+  BUFFER_ADD ("[");
+  for (i = 0; i < ds->ds_num; i++)
+  {
+    if (i > 0)
+      BUFFER_ADD (",");
+
+    BUFFER_ADD ("\"%s\"", DS_TYPE_TO_STRING (ds->ds[i].type));
+  } /* for ds->ds_num */
+  BUFFER_ADD ("]");
+
+#undef BUFFER_ADD
+
+  DEBUG ("format_json: dstypes_to_json: buffer = %s;", buffer);
+
+  return (0);
+} /* }}} int dstypes_to_json */
+
+static int dsnames_to_json (char *buffer, size_t buffer_size, /* {{{ */
+                const data_set_t *ds, const value_list_t *vl)
+{
+  size_t offset = 0;
+  int i;
+
+  memset (buffer, 0, buffer_size);
+
+#define BUFFER_ADD(...) do { \
+  int status; \
+  status = ssnprintf (buffer + offset, buffer_size - offset, \
+      __VA_ARGS__); \
+  if (status < 1) \
+    return (-1); \
+  else if (((size_t) status) >= (buffer_size - offset)) \
+    return (-ENOMEM); \
+  else \
+    offset += ((size_t) status); \
+} while (0)
+
+  BUFFER_ADD ("[");
+  for (i = 0; i < ds->ds_num; i++)
+  {
+    if (i > 0)
+      BUFFER_ADD (",");
+
+    BUFFER_ADD ("\"%s\"", ds->ds[i].name);
+  } /* for ds->ds_num */
+  BUFFER_ADD ("]");
+
+#undef BUFFER_ADD
+
+  DEBUG ("format_json: dsnames_to_json: buffer = %s;", buffer);
+
+  return (0);
+} /* }}} int dsnames_to_json */
+
+static int value_list_to_json (char *buffer, size_t buffer_size, /* {{{ */
+                const data_set_t *ds, const value_list_t *vl, int store_rates)
+{
+  char temp[512];
+  size_t offset = 0;
+  int status;
+
+  memset (buffer, 0, buffer_size);
+
+#define BUFFER_ADD(...) do { \
+  status = ssnprintf (buffer + offset, buffer_size - offset, \
+      __VA_ARGS__); \
+  if (status < 1) \
+    return (-1); \
+  else if (((size_t) status) >= (buffer_size - offset)) \
+    return (-ENOMEM); \
+  else \
+    offset += ((size_t) status); \
+} while (0)
+
+  /* All value lists have a leading comma. The first one will be replaced with
+   * a square bracket in `format_json_finalize'. */
+  BUFFER_ADD (",{");
+
+  status = values_to_json (temp, sizeof (temp), ds, vl, store_rates);
+  if (status != 0)
+    return (status);
+  BUFFER_ADD ("\"values\":%s", temp);
+
+  status = dstypes_to_json (temp, sizeof (temp), ds, vl);
+  if (status != 0)
+    return (status);
+  BUFFER_ADD (",\"dstypes\":%s", temp);
+
+  status = dsnames_to_json (temp, sizeof (temp), ds, vl);
+  if (status != 0)
+    return (status);
+  BUFFER_ADD (",\"dsnames\":%s", temp);
+
+  BUFFER_ADD (",\"time\":%.3f", CDTIME_T_TO_DOUBLE (vl->time));
+  BUFFER_ADD (",\"interval\":%.3f", CDTIME_T_TO_DOUBLE (vl->interval));
+
+#define BUFFER_ADD_KEYVAL(key, value) do { \
+  status = escape_string (temp, sizeof (temp), (value)); \
+  if (status != 0) \
+    return (status); \
+  BUFFER_ADD (",\"%s\":%s", (key), temp); \
+} while (0)
+
+  BUFFER_ADD_KEYVAL ("host", vl->host);
+  BUFFER_ADD_KEYVAL ("plugin", vl->plugin);
+  BUFFER_ADD_KEYVAL ("plugin_instance", vl->plugin_instance);
+  BUFFER_ADD_KEYVAL ("type", vl->type);
+  BUFFER_ADD_KEYVAL ("type_instance", vl->type_instance);
+
+  BUFFER_ADD ("}");
+
+#undef BUFFER_ADD_KEYVAL
+#undef BUFFER_ADD
+
+  DEBUG ("format_json: value_list_to_json: buffer = %s;", buffer);
+
+  return (0);
+} /* }}} int value_list_to_json */
+
+static int format_json_value_list_nocheck (char *buffer, /* {{{ */
+    size_t *ret_buffer_fill, size_t *ret_buffer_free,
+    const data_set_t *ds, const value_list_t *vl,
+    int store_rates, size_t temp_size)
+{
+  char temp[temp_size];
+  int status;
+
+  status = value_list_to_json (temp, sizeof (temp), ds, vl, store_rates);
+  if (status != 0)
+    return (status);
+  temp_size = strlen (temp);
+
+  memcpy (buffer + (*ret_buffer_fill), temp, temp_size + 1);
+  (*ret_buffer_fill) += temp_size;
+  (*ret_buffer_free) -= temp_size;
+
+  return (0);
+} /* }}} int format_json_value_list_nocheck */
+
+int format_json_initialize (char *buffer, /* {{{ */
+    size_t *ret_buffer_fill, size_t *ret_buffer_free)
+{
+  size_t buffer_fill;
+  size_t buffer_free;
+
+  if ((buffer == NULL) || (ret_buffer_fill == NULL) || (ret_buffer_free == NULL))
+    return (-EINVAL);
+
+  buffer_fill = *ret_buffer_fill;
+  buffer_free = *ret_buffer_free;
+
+  buffer_free = buffer_fill + buffer_free;
+  buffer_fill = 0;
+
+  if (buffer_free < 3)
+    return (-ENOMEM);
+
+  memset (buffer, 0, buffer_free);
+  *ret_buffer_fill = buffer_fill;
+  *ret_buffer_free = buffer_free;
+
+  return (0);
+} /* }}} int format_json_initialize */
+
+int format_json_finalize (char *buffer, /* {{{ */
+    size_t *ret_buffer_fill, size_t *ret_buffer_free)
+{
+  size_t pos;
+
+  if ((buffer == NULL) || (ret_buffer_fill == NULL) || (ret_buffer_free == NULL))
+    return (-EINVAL);
+
+  if (*ret_buffer_free < 2)
+    return (-ENOMEM);
+
+  /* Replace the leading comma added in `value_list_to_json' with a square
+   * bracket. */
+  if (buffer[0] != ',')
+    return (-EINVAL);
+  buffer[0] = '[';
+
+  pos = *ret_buffer_fill;
+  buffer[pos] = ']';
+  buffer[pos+1] = 0;
+
+  (*ret_buffer_fill)++;
+  (*ret_buffer_free)--;
+
+  return (0);
+} /* }}} int format_json_finalize */
+
+int format_json_value_list (char *buffer, /* {{{ */
+    size_t *ret_buffer_fill, size_t *ret_buffer_free,
+    const data_set_t *ds, const value_list_t *vl, int store_rates)
+{
+  if ((buffer == NULL)
+      || (ret_buffer_fill == NULL) || (ret_buffer_free == NULL)
+      || (ds == NULL) || (vl == NULL))
+    return (-EINVAL);
+
+  if (*ret_buffer_free < 3)
+    return (-ENOMEM);
+
+  return (format_json_value_list_nocheck (buffer,
+        ret_buffer_fill, ret_buffer_free, ds, vl,
+        store_rates, (*ret_buffer_free) - 2));
+} /* }}} int format_json_value_list */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/utils_format_json.h b/src/utils_format_json.h
new file mode 100644 (file)
index 0000000..c902e27
--- /dev/null
@@ -0,0 +1,36 @@
+/**
+ * collectd - src/utils_format_json.c
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef UTILS_FORMAT_JSON_H
+#define UTILS_FORMAT_JSON_H 1
+
+#include "collectd.h"
+#include "plugin.h"
+
+int format_json_initialize (char *buffer,
+    size_t *ret_buffer_fill, size_t *ret_buffer_free);
+int format_json_value_list (char *buffer,
+    size_t *ret_buffer_fill, size_t *ret_buffer_free,
+    const data_set_t *ds, const value_list_t *vl, int store_rates);
+int format_json_finalize (char *buffer,
+    size_t *ret_buffer_fill, size_t *ret_buffer_free);
+
+#endif /* UTILS_FORMAT_JSON_H */
diff --git a/src/utils_heap.c b/src/utils_heap.c
new file mode 100644 (file)
index 0000000..f8f7405
--- /dev/null
@@ -0,0 +1,226 @@
+/**
+ * collectd - src/utils_heap.c
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <pthread.h>
+
+#include "utils_heap.h"
+
+struct c_heap_s
+{
+  pthread_mutex_t lock;
+  int (*compare) (const void *, const void *);
+
+  void **list;
+  size_t list_len; /* # entries used */
+  size_t list_size; /* # entries allocated */
+};
+
+enum reheap_direction
+{
+  DIR_UP,
+  DIR_DOWN
+};
+
+static void reheap (c_heap_t *h, size_t root, enum reheap_direction dir)
+{
+  size_t left;
+  size_t right;
+  size_t min;
+  int status;
+
+  /* Calculate the positions of the children */
+  left = (2 * root) + 1;
+  if (left >= h->list_len)
+    left = 0;
+
+  right = (2 * root) + 2;
+  if (right >= h->list_len)
+    right = 0;
+
+  /* Check which one of the children is smaller. */
+  if ((left == 0) && (right == 0))
+    return;
+  else if (left == 0)
+    min = right;
+  else if (right == 0)
+    min = left;
+  else
+  {
+    status = h->compare (h->list[left], h->list[right]);
+    if (status > 0)
+      min = right;
+    else
+      min = left;
+  }
+
+  status = h->compare (h->list[root], h->list[min]);
+  if (status <= 0)
+  {
+    /* We didn't need to change anything, so the rest of the tree should be
+     * okay now. */
+    return;
+  }
+  else /* if (status > 0) */
+  {
+    void *tmp;
+
+    tmp = h->list[root];
+    h->list[root] = h->list[min];
+    h->list[min] = tmp;
+  }
+
+  if ((dir == DIR_UP) && (root == 0))
+    return;
+
+  if (dir == DIR_UP)
+    reheap (h, (root - 1) / 2, dir);
+  else if (dir == DIR_DOWN)
+    reheap (h, min, dir);
+} /* void reheap */
+
+c_heap_t *c_heap_create (int (*compare) (const void *, const void *))
+{
+  c_heap_t *h;
+
+  if (compare == NULL)
+    return (NULL);
+
+  h = malloc (sizeof (*h));
+  if (h == NULL)
+    return (NULL);
+
+  memset (h, 0, sizeof (*h));
+  pthread_mutex_init (&h->lock, /* attr = */ NULL);
+  h->compare = compare;
+  
+  h->list = NULL;
+  h->list_len = 0;
+  h->list_size = 0;
+
+  return (h);
+} /* c_heap_t *c_heap_create */
+
+void c_heap_destroy (c_heap_t *h)
+{
+  if (h == NULL)
+    return;
+
+  h->list_len = 0;
+  h->list_size = 0;
+  free (h->list);
+  h->list = NULL;
+
+  pthread_mutex_destroy (&h->lock);
+
+  free (h);
+} /* void c_heap_destroy */
+
+int c_heap_insert (c_heap_t *h, void *ptr)
+{
+  size_t index;
+
+  if ((h == NULL) || (ptr == NULL))
+    return (-EINVAL);
+
+  pthread_mutex_lock (&h->lock);
+
+  assert (h->list_len <= h->list_size);
+  if (h->list_len == h->list_size)
+  {
+    void **tmp;
+
+    tmp = realloc (h->list, (h->list_size + 16) * sizeof (*h->list));
+    if (tmp == NULL)
+    {
+      pthread_mutex_unlock (&h->lock);
+      return (-ENOMEM);
+    }
+
+    h->list = tmp;
+    h->list_size += 16;
+  }
+
+  /* Insert the new node as a leaf. */
+  index = h->list_len;
+  h->list[index] = ptr;
+  h->list_len++;
+
+  /* Reorganize the heap from bottom up. */
+  reheap (h, /* parent of this node = */ (index - 1) / 2, DIR_UP);
+  
+  pthread_mutex_unlock (&h->lock);
+  return (0);
+} /* int c_heap_insert */
+
+void *c_heap_get_root (c_heap_t *h)
+{
+  void *ret = NULL;
+
+  if (h == NULL)
+    return (NULL);
+
+  pthread_mutex_lock (&h->lock);
+
+  if (h->list_len == 0)
+  {
+    pthread_mutex_unlock (&h->lock);
+    return (NULL);
+  }
+  else if (h->list_len == 1)
+  {
+    ret = h->list[0];
+    h->list[0] = NULL;
+    h->list_len = 0;
+  }
+  else /* if (h->list_len > 1) */
+  {
+    ret = h->list[0];
+    h->list[0] = h->list[h->list_len - 1];
+    h->list[h->list_len - 1] = NULL;
+    h->list_len--;
+
+    reheap (h, /* root = */ 0, DIR_DOWN);
+  }
+
+  /* free some memory */
+  if ((h->list_len + 32) < h->list_size)
+  {
+    void **tmp;
+
+    tmp = realloc (h->list, (h->list_len + 16) * sizeof (*h->list));
+    if (tmp != NULL)
+    {
+      h->list = tmp;
+      h->list_size = h->list_len + 16;
+    }
+  }
+
+  pthread_mutex_unlock (&h->lock);
+
+  return (ret);
+} /* void *c_heap_get_root */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/utils_heap.h b/src/utils_heap.h
new file mode 100644 (file)
index 0000000..6428006
--- /dev/null
@@ -0,0 +1,96 @@
+/**
+ * collectd - src/utils_heap.h
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef UTILS_HEAP_H
+#define UTILS_HEAP_H 1
+
+struct c_heap_s;
+typedef struct c_heap_s c_heap_t;
+
+/*
+ * NAME
+ *   c_heap_create
+ *
+ * DESCRIPTION
+ *   Allocates a new heap.
+ *
+ * PARAMETERS
+ *   `compare'  The function-pointer `compare' is used to compare two keys. It
+ *              has to return less than zero if it's first argument is smaller
+ *              then the second argument, more than zero if the first argument
+ *              is bigger than the second argument and zero if they are equal.
+ *              If your keys are char-pointers, you can use the `strcmp'
+ *              function from the libc here.
+ *
+ * RETURN VALUE
+ *   A c_heap_t-pointer upon success or NULL upon failure.
+ */
+c_heap_t *c_heap_create (int (*compare) (const void *, const void *));
+
+/*
+ * NAME
+ *   c_heap_destroy
+ *
+ * DESCRIPTION
+ *   Deallocates a heap. Stored value- and key-pointer are lost, but of course
+ *   not freed.
+ */
+void c_heap_destroy (c_heap_t *h);
+
+/*
+ * NAME
+ *   c_heap_insert
+ *
+ * DESCRIPTION
+ *   Stores the key-value-pair in the heap pointed to by `h'.
+ *
+ * PARAMETERS
+ *   `h'        Heap to store the data in.
+ *   `ptr'      Value to be stored. This is typically a pointer to a data
+ *              structure. The data structure is of course *not* copied and may
+ *              not be free'd before the pointer has been removed from the heap
+ *              again.
+ *
+ * RETURN VALUE
+ *   Zero upon success, non-zero otherwise. It's less than zero if an error
+ *   occurred or greater than zero if the key is already stored in the tree.
+ */
+int c_heap_insert (c_heap_t *h, void *ptr);
+
+/*
+ * NAME
+ *   c_heap_get_root
+ *
+ * DESCRIPTION
+ *   Removes the value at the root of the heap and returns both, key and value.
+ *
+ * PARAMETERS
+ *   `h'           Heap to remove key-value-pair from.
+ *
+ * RETURN VALUE
+ *   The pointer passed to `c_heap_insert' or NULL if there are no more
+ *   elements in the heap (or an error occurred).
+ */
+void *c_heap_get_root (c_heap_t *h);
+
+#endif /* UTILS_HEAP_H */
+/* vim: set sw=2 sts=2 et : */
diff --git a/src/utils_ignorelist.c b/src/utils_ignorelist.c
new file mode 100644 (file)
index 0000000..de42d0f
--- /dev/null
@@ -0,0 +1,364 @@
+/**
+ * collectd - src/utils_ignorelist.c
+ * Copyright (C) 2006 Lubos Stanek <lubek at users.sourceforge.net>
+ * Copyright (C) 2008 Florian Forster <octo at verplant.org>
+ *
+ * This program is free software; you can redistribute it and/
+ * or modify it under the terms of the GNU General Public Li-
+ * cence as published by the Free Software Foundation; either
+ * version 2 of the Licence, or any later version.
+ *
+ * This program is distributed in the hope that it will be use-
+ * ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ * ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public Licence for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * Licence along with this program; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
+ * USA.
+ *
+ * Authors:
+ *   Lubos Stanek <lubek at users.sourceforge.net>
+ *   Florian Forster <octo at verplant.org>
+ **/
+/**
+ * ignorelist handles plugin's list of configured collectable
+ * entries with global ignore action
+ **/
+/**
+ * Usage:
+ * 
+ * Define plugin's global pointer variable of type ignorelist_t:
+ *   ignorelist_t *myconfig_ignore;
+ * If you know the state of the global ignore (IgnoreSelected),
+ * allocate the variable with:
+ *   myconfig_ignore = ignorelist_create (YourKnownIgnore);
+ * If you do not know the state of the global ignore,
+ * initialize the global variable and set the ignore flag later:
+ *   myconfig_ignore = ignorelist_init ();
+ * Append single entries in your cf_register'ed callback function:
+ *   ignorelist_add (myconfig_ignore, newentry);
+ * When you hit the IgnoreSelected config option,
+ * offer it to the list:
+ *   ignorelist_ignore (myconfig_ignore, instantly_got_value_of_ignore);
+ * That is all for the ignorelist initialization.
+ * Later during read and write (plugin's registered functions) get
+ * the information whether this entry would be collected or not:
+ *   if (ignorelist_match (myconfig_ignore, thisentry))
+ *     return;
+ **/
+
+#if HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "common.h"
+#include "plugin.h"
+#include "utils_ignorelist.h"
+
+/*
+ * private prototypes
+ */
+struct ignorelist_item_s
+{
+#if HAVE_REGEX_H
+       regex_t *rmatch;        /* regular expression entry identification */
+#endif
+       char *smatch;           /* string entry identification */
+       struct ignorelist_item_s *next;
+};
+typedef struct ignorelist_item_s ignorelist_item_t;
+
+struct ignorelist_s
+{
+       int ignore;             /* ignore entries */
+       ignorelist_item_t *head;        /* pointer to the first entry */
+};
+
+/* *** *** *** ********************************************* *** *** *** */
+/* *** *** *** *** *** ***   private functions   *** *** *** *** *** *** */
+/* *** *** *** ********************************************* *** *** *** */
+
+static inline void ignorelist_append (ignorelist_t *il, ignorelist_item_t *item)
+{
+       assert ((il != NULL) && (item != NULL));
+
+       item->next = il->head;
+       il->head = item;
+}
+
+#if HAVE_REGEX_H
+static int ignorelist_append_regex(ignorelist_t *il, const char *entry)
+{
+       int rcompile;
+       regex_t *regtemp;
+       int errsize;
+       char *regerr = NULL;
+       ignorelist_item_t *new;
+
+       /* create buffer */
+       if ((regtemp = malloc(sizeof(regex_t))) == NULL)
+       {
+               ERROR ("cannot allocate new config entry");
+               return (1);
+       }
+       memset (regtemp, '\0', sizeof(regex_t));
+
+       /* compile regex */
+       if ((rcompile = regcomp (regtemp, entry, REG_EXTENDED)) != 0)
+       {
+               /* prepare message buffer */
+               errsize = regerror(rcompile, regtemp, NULL, 0);
+               if (errsize)
+                       regerr = smalloc(errsize);
+               /* get error message */
+               if (regerror (rcompile, regtemp, regerr, errsize))
+               {
+                       fprintf (stderr, "Cannot compile regex %s: %i/%s",
+                                       entry, rcompile, regerr);
+                       ERROR ("Cannot compile regex %s: %i/%s",
+                                       entry, rcompile, regerr);
+               }
+               else
+               {
+                       fprintf (stderr, "Cannot compile regex %s: %i",
+                                       entry, rcompile);
+                       ERROR ("Cannot compile regex %s: %i",
+                                       entry, rcompile);
+               }
+
+               if (errsize)
+                       sfree (regerr);
+               regfree (regtemp);
+               return (1);
+       }
+       DEBUG("regex compiled: %s - %i", entry, rcompile);
+
+       /* create new entry */
+       if ((new = malloc(sizeof(ignorelist_item_t))) == NULL)
+       {
+               ERROR ("cannot allocate new config entry");
+               regfree (regtemp);
+               return (1);
+       }
+       memset (new, '\0', sizeof(ignorelist_item_t));
+       new->rmatch = regtemp;
+
+       /* append new entry */
+       ignorelist_append (il, new);
+
+       return (0);
+} /* int ignorelist_append_regex(ignorelist_t *il, const char *entry) */
+#endif
+
+static int ignorelist_append_string(ignorelist_t *il, const char *entry)
+{
+       ignorelist_item_t *new;
+
+       /* create new entry */
+       if ((new = malloc(sizeof(ignorelist_item_t))) == NULL )
+       {
+               ERROR ("cannot allocate new entry");
+               return (1);
+       }
+       memset (new, '\0', sizeof(ignorelist_item_t));
+       new->smatch = sstrdup(entry);
+
+       /* append new entry */
+       ignorelist_append (il, new);
+
+       return (0);
+} /* int ignorelist_append_string(ignorelist_t *il, const char *entry) */
+
+#if HAVE_REGEX_H
+/*
+ * check list for entry regex match
+ * return 1 if found
+ */
+static int ignorelist_match_regex (ignorelist_item_t *item, const char *entry)
+{
+       assert ((item != NULL) && (item->rmatch != NULL)
+                       && (entry != NULL) && (strlen (entry) > 0));
+
+       /* match regex */
+       if (regexec (item->rmatch, entry, 0, NULL, 0) == 0)
+               return (1);
+
+       return (0);
+} /* int ignorelist_match_regex (ignorelist_item_t *item, const char *entry) */
+#endif
+
+/*
+ * check list for entry string match
+ * return 1 if found
+ */
+static int ignorelist_match_string (ignorelist_item_t *item, const char *entry)
+{
+       assert ((item != NULL) && (item->smatch != NULL)
+                       && (entry != NULL) && (strlen (entry) > 0));
+
+       if (strcmp (entry, item->smatch) == 0)
+               return (1);
+
+       return (0);
+} /* int ignorelist_match_string (ignorelist_item_t *item, const char *entry) */
+
+
+/* *** *** *** ******************************************** *** *** *** */
+/* *** *** *** *** *** ***   public functions   *** *** *** *** *** *** */
+/* *** *** *** ******************************************** *** *** *** */
+
+/*
+ * create the ignorelist_t with known ignore state
+ * return pointer to ignorelist_t
+ */
+ignorelist_t *ignorelist_create (int invert)
+{
+       ignorelist_t *il;
+
+       /* smalloc exits if it failes */
+       il = (ignorelist_t *) smalloc (sizeof (ignorelist_t));
+       memset (il, '\0', sizeof (ignorelist_t));
+
+       /*
+        * ->ignore == 0  =>  collect
+        * ->ignore == 1  =>  ignore
+        */
+       il->ignore = invert ? 0 : 1;
+
+       return (il);
+} /* ignorelist_t *ignorelist_create (int ignore) */
+
+/*
+ * free memory used by ignorelist_t
+ */
+void ignorelist_free (ignorelist_t *il)
+{
+       ignorelist_item_t *this;
+       ignorelist_item_t *next;
+
+       if (il == NULL)
+               return;
+
+       for (this = il->head; this != NULL; this = next)
+       {
+               next = this->next;
+#if HAVE_REGEX_H
+               if (this->rmatch != NULL)
+               {
+                       regfree (this->rmatch);
+                       this->rmatch = NULL;
+               }
+#endif
+               if (this->smatch != NULL)
+               {
+                       sfree (this->smatch);
+                       this->smatch = NULL;
+               }
+               sfree (this);
+       }
+
+       sfree (il);
+       il = NULL;
+} /* void ignorelist_destroy (ignorelist_t *il) */
+
+/*
+ * set ignore state of the ignorelist_t
+ */
+void ignorelist_set_invert (ignorelist_t *il, int invert)
+{
+       if (il == NULL)
+       {
+               DEBUG("ignore call with ignorelist_t == NULL");
+               return;
+       }
+
+       il->ignore = invert ? 0 : 1;
+} /* void ignorelist_set_invert (ignorelist_t *il, int ignore) */
+
+/*
+ * append entry into ignorelist_t
+ * return 1 for success
+ */
+int ignorelist_add (ignorelist_t *il, const char *entry)
+{
+       int ret;
+       size_t entry_len;
+
+       if (il == NULL)
+       {
+               DEBUG ("add called with ignorelist_t == NULL");
+               return (1);
+       }
+
+       entry_len = strlen (entry);
+
+       /* append nothing */
+       if (entry_len == 0)
+       {
+               DEBUG("not appending: empty entry");
+               return (1);
+       }
+
+#if HAVE_REGEX_H
+       /* regex string is enclosed in "/.../" */
+       if ((entry_len > 2) && (entry[0] == '/') && entry[entry_len - 1] == '/')
+       {
+               char *entry_copy;
+               size_t entry_copy_size;
+
+               /* We need to copy `entry' since it's const */
+               entry_copy_size = entry_len - 1;
+               entry_copy = smalloc (entry_copy_size);
+               sstrncpy (entry_copy, entry + 1, entry_copy_size);
+
+               DEBUG("I'm about to add regex entry: %s", entry_copy);
+               ret = ignorelist_append_regex(il, entry_copy);
+               sfree (entry_copy);
+       }
+       else
+#endif
+       {
+               DEBUG("to add entry: %s", entry);
+               ret = ignorelist_append_string(il, entry);
+       }
+
+       return (ret);
+} /* int ignorelist_add (ignorelist_t *il, const char *entry) */
+
+/*
+ * check list for entry
+ * return 1 for ignored entry
+ */
+int ignorelist_match (ignorelist_t *il, const char *entry)
+{
+       ignorelist_item_t *traverse;
+
+       /* if no entries, collect all */
+       if ((il == NULL) || (il->head == NULL))
+               return (0);
+
+       if ((entry == NULL) || (strlen (entry) == 0))
+               return (0);
+
+       /* traverse list and check entries */
+       for (traverse = il->head; traverse != NULL; traverse = traverse->next)
+       {
+#if HAVE_REGEX_H
+               if (traverse->rmatch != NULL)
+               {
+                       if (ignorelist_match_regex (traverse, entry))
+                               return (il->ignore);
+               }
+               else
+#endif
+               {
+                       if (ignorelist_match_string (traverse, entry))
+                               return (il->ignore);
+               }
+       } /* for traverse */
+
+       return (1 - il->ignore);
+} /* int ignorelist_match (ignorelist_t *il, const char *entry) */
+
diff --git a/src/utils_ignorelist.h b/src/utils_ignorelist.h
new file mode 100644 (file)
index 0000000..b47b55a
--- /dev/null
@@ -0,0 +1,70 @@
+/**
+ * collectd - src/utils_ignorelist.h
+ * Copyright (C) 2006 Lubos Stanek <lubek at users.sourceforge.net>
+ *
+ * This program is free software; you can redistribute it and/
+ * or modify it under the terms of the GNU General Public Li-
+ * cence as published by the Free Software Foundation; either
+ * version 2 of the Licence, or any later version.
+ *
+ * This program is distributed in the hope that it will be use-
+ * ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ * ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public Licence for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * Licence along with this program; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
+ * USA.
+ *
+ * Authors:
+ *   Lubos Stanek <lubek at users.sourceforge.net>
+ **/
+/**
+ * ignorelist handles plugin's list of configured collectable
+ * entries with global ignore action
+ **/
+
+#ifndef UTILS_IGNORELIST_H
+#define UTILS_IGNORELIST_H 1
+
+#include "collectd.h"
+
+#if HAVE_REGEX_H
+# include <regex.h>
+#endif
+
+/* public prototypes */
+
+struct ignorelist_s;
+typedef struct ignorelist_s ignorelist_t;
+
+/*
+ * create the ignorelist_t with known ignore state
+ * return pointer to ignorelist_t
+ */
+ignorelist_t *ignorelist_create (int invert);
+
+/*
+ * free memory used by ignorelist_t
+ */
+void ignorelist_free (ignorelist_t *il);
+
+/*
+ * set ignore state of the ignorelist_t
+ */
+void ignorelist_set_invert (ignorelist_t *il, int invert);
+
+/*
+ * append entry to ignorelist_t
+ * returns zero on success, non-zero upon failure.
+ */
+int ignorelist_add (ignorelist_t *il, const char *entry);
+
+/*
+ * check list for entry
+ * return 1 for ignored entry
+ */
+int ignorelist_match (ignorelist_t *il, const char *entry);
+
+#endif /* UTILS_IGNORELIST_H */
diff --git a/src/utils_llist.c b/src/utils_llist.c
new file mode 100644 (file)
index 0000000..6a0c6f0
--- /dev/null
@@ -0,0 +1,187 @@
+/**
+ * collectd - src/utils_llist.c
+ * Copyright (C) 2006 Florian Forster <octo at verplant.org>
+ *
+ * This program is free software; you can redistribute it and/
+ * or modify it under the terms of the GNU General Public Li-
+ * cence as published by the Free Software Foundation; only
+ * version 2 of the Licence is applicable.
+ *
+ * This program is distributed in the hope that it will be use-
+ * ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ * ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public Licence for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * Licence along with this program; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
+ * USA.
+ *
+ * Authors:
+ *   Florian Forster <octo at verplant.org>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "utils_llist.h"
+
+/*
+ * Private data types
+ */
+struct llist_s
+{
+       llentry_t *head;
+       llentry_t *tail;
+       int size;
+};
+
+/*
+ * Public functions
+ */
+llist_t *llist_create (void)
+{
+       llist_t *ret;
+
+       ret = (llist_t *) malloc (sizeof (llist_t));
+       if (ret == NULL)
+               return (NULL);
+
+       memset (ret, '\0', sizeof (llist_t));
+
+       return (ret);
+}
+
+void llist_destroy (llist_t *l)
+{
+       llentry_t *e_this;
+       llentry_t *e_next;
+
+       if (l == NULL)
+               return;
+
+       for (e_this = l->head; e_this != NULL; e_this = e_next)
+       {
+               e_next = e_this->next;
+               llentry_destroy (e_this);
+       }
+
+       free (l);
+}
+
+llentry_t *llentry_create (char *key, void *value)
+{
+       llentry_t *e;
+
+       e = (llentry_t *) malloc (sizeof (llentry_t));
+       if (e)
+       {
+               e->key   = key;
+               e->value = value;
+               e->next  = NULL;
+       }
+
+       return (e);
+}
+
+void llentry_destroy (llentry_t *e)
+{
+       free (e);
+}
+
+void llist_append (llist_t *l, llentry_t *e)
+{
+       e->next = NULL;
+
+       if (l->tail == NULL)
+               l->head = e;
+       else
+               l->tail->next = e;
+
+       l->tail = e;
+
+       ++(l->size);
+}
+
+void llist_prepend (llist_t *l, llentry_t *e)
+{
+       e->next = l->head;
+       l->head = e;
+
+       if (l->tail == NULL)
+               l->tail = e;
+
+       ++(l->size);
+}
+
+void llist_remove (llist_t *l, llentry_t *e)
+{
+       llentry_t *prev;
+
+       prev = l->head;
+       while ((prev != NULL) && (prev->next != e))
+               prev = prev->next;
+
+       if (prev != NULL)
+               prev->next = e->next;
+       if (l->head == e)
+               l->head = e->next;
+       if (l->tail == e)
+               l->tail = prev;
+
+       --(l->size);
+}
+
+int llist_size (llist_t *l)
+{
+       return (l ? l->size : 0);
+}
+
+static int llist_strcmp (llentry_t *e, void *ud)
+{
+       if ((e == NULL) || (ud == NULL))
+               return (-1);
+       return (strcmp (e->key, (const char *)ud));
+}
+
+llentry_t *llist_search (llist_t *l, const char *key)
+{
+       return (llist_search_custom (l, llist_strcmp, (void *)key));
+}
+
+llentry_t *llist_search_custom (llist_t *l,
+               int (*compare) (llentry_t *, void *), void *user_data)
+{
+       llentry_t *e;
+
+       if (l == NULL)
+               return (NULL);
+
+       e = l->head;
+       while (e != NULL) {
+               llentry_t *next = e->next;
+
+               if (compare (e, user_data) == 0)
+                       break;
+
+               e = next;
+       }
+
+       return (e);
+}
+
+llentry_t *llist_head (llist_t *l)
+{
+       if (l == NULL)
+               return (NULL);
+       return (l->head);
+}
+
+llentry_t *llist_tail (llist_t *l)
+{
+       if (l == NULL)
+               return (NULL);
+       return (l->tail);
+}
diff --git a/src/utils_llist.h b/src/utils_llist.h
new file mode 100644 (file)
index 0000000..19d8d94
--- /dev/null
@@ -0,0 +1,63 @@
+/**
+ * collectd - src/utils_llist.h
+ * Copyright (C) 2006 Florian Forster <octo at verplant.org>
+ *
+ * This program is free software; you can redistribute it and/
+ * or modify it under the terms of the GNU General Public Li-
+ * cence as published by the Free Software Foundation; only
+ * version 2 of the Licence is applicable.
+ *
+ * This program is distributed in the hope that it will be use-
+ * ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ * ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public Licence for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * Licence along with this program; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
+ * USA.
+ *
+ * Authors:
+ *   Florian Forster <octo at verplant.org>
+ */
+
+#ifndef UTILS_LLIST_H
+#define UTILS_LLIST_H 1
+
+/*
+ * Data types
+ */
+struct llentry_s
+{
+       char *key;
+       void *value;
+       struct llentry_s *next;
+};
+typedef struct llentry_s llentry_t;
+
+struct llist_s;
+typedef struct llist_s llist_t;
+
+/*
+ * Functions
+ */
+llist_t *llist_create (void);
+void llist_destroy (llist_t *l);
+
+llentry_t *llentry_create (char *key, void *value);
+void llentry_destroy (llentry_t *e);
+
+void llist_append (llist_t *l, llentry_t *e);
+void llist_prepend (llist_t *l, llentry_t *e);
+void llist_remove (llist_t *l, llentry_t *e);
+
+int llist_size (llist_t *l);
+
+llentry_t *llist_search (llist_t *l, const char *key);
+llentry_t *llist_search_custom (llist_t *l,
+               int (*compare) (llentry_t *, void *), void *user_data);
+
+llentry_t *llist_head (llist_t *l);
+llentry_t *llist_tail (llist_t *l);
+
+#endif /* UTILS_LLIST_H */
diff --git a/src/utils_match.c b/src/utils_match.c
new file mode 100644 (file)
index 0000000..062bcfe
--- /dev/null
@@ -0,0 +1,369 @@
+/**
+ * collectd - src/utils_match.c
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.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
+
+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 = (char *) malloc (sizeof (char) * (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 (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
+    {
+      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.counter++;
+      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 = (cu_match_t *) malloc (sizeof (cu_match_t));
+  if (obj == NULL)
+    return (NULL);
+  memset (obj, '\0', sizeof (cu_match_t));
+
+  status = regcomp (&obj->regex, regex, REG_EXTENDED | REG_NEWLINE);
+  if (status != 0)
+  {
+    ERROR ("Compiling the regular expression \"%s\" failed.", regex);
+    sfree (obj);
+    return (NULL);
+  }
+
+  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 = (cu_match_value_t *) malloc (sizeof (cu_match_value_t));
+  if (user_data == NULL)
+    return (NULL);
+  memset (user_data, '\0', sizeof (cu_match_value_t));
+  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_destroy (cu_match_t *obj)
+{
+  if (obj == NULL)
+    return;
+
+  if (obj->flags & UTILS_MATCH_FLAGS_FREE_USER_DATA)
+  {
+    sfree (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];
+  size_t matches_num;
+  size_t i;
+
+  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);
+
+  memset (matches, '\0', sizeof (matches));
+  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 (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..36abe30
--- /dev/null
@@ -0,0 +1,155 @@
+/**
+ * collectd - src/utils_match.h
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef UTILS_MATCH_H
+#define UTILS_MATCH_H 1
+
+#include "plugin.h"
+
+/*
+ * Defines
+ */
+#define UTILS_MATCH_DS_TYPE_GAUGE    0x10
+#define UTILS_MATCH_DS_TYPE_COUNTER  0x20
+#define UTILS_MATCH_DS_TYPE_DERIVE   0x40
+#define UTILS_MATCH_DS_TYPE_ABSOLUTE 0x80
+
+#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_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_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/utils_mount.c b/src/utils_mount.c
new file mode 100644 (file)
index 0000000..cae3706
--- /dev/null
@@ -0,0 +1,782 @@
+/**
+ * collectd - src/utils_mount.c
+ * Copyright (C) 2005,2006  Niki W. Waibel
+ *
+ * This program is free software; you can redistribute it and/
+ * or modify it under the terms of the GNU General Public Li-
+ * cence as published by the Free Software Foundation; either
+ * version 2 of the Licence, or any later version.
+ *
+ * This program is distributed in the hope that it will be use-
+ * ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ * ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public Licence for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * Licence along with this program; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
+ * USA.
+ *
+ * Author:
+ *   Niki W. Waibel <niki.waibel@gmx.net>
+**/
+
+#if HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "common.h"
+#if HAVE_XFS_XQM_H
+# include <xfs/xqm.h>
+#define XFS_SUPER_MAGIC_STR "XFSB"
+#define XFS_SUPER_MAGIC2_STR "BSFX"
+#endif
+
+#include "plugin.h"
+#include "utils_mount.h"
+
+#if HAVE_GETVFSSTAT
+#  if HAVE_SYS_TYPES_H
+#    include <sys/types.h>
+#  endif
+#  if HAVE_SYS_STATVFS_H
+#    include <sys/statvfs.h>
+#  endif
+/* #endif HAVE_GETVFSSTAT */
+
+#elif HAVE_GETFSSTAT
+#  if HAVE_SYS_PARAM_H
+#    include <sys/param.h>
+#  endif
+#  if HAVE_SYS_UCRED_H
+#    include <sys/ucred.h>
+#  endif
+#  if HAVE_SYS_MOUNT_H
+#    include <sys/mount.h>
+#  endif
+#endif /* HAVE_GETFSSTAT */
+
+#if HAVE_MNTENT_H
+#  include <mntent.h>
+#endif
+#if HAVE_SYS_MNTTAB_H
+#  include <sys/mnttab.h>
+#endif
+
+#if HAVE_PATHS_H
+#  include <paths.h>
+#endif
+
+#ifdef COLLECTD_MNTTAB
+#  undef COLLECTD_MNTTAB
+#endif
+
+#if defined(_PATH_MOUNTED) /* glibc */
+#  define COLLECTD_MNTTAB _PATH_MOUNTED
+#elif defined(MNTTAB) /* Solaris */
+#  define COLLECTD_MNTTAB MNTTAB
+#elif defined(MNT_MNTTAB)
+#  define COLLECTD_MNTTAB MNT_MNTTAB
+#elif defined(MNTTABNAME)
+#  define COLLECTD_MNTTAB MNTTABNAME
+#elif defined(KMTAB)
+#  define COLLECTD_MNTTAB KMTAB
+#else
+#  define COLLECTD_MNTTAB "/etc/mnttab"
+#endif
+
+/* *** *** *** ********************************************* *** *** *** */
+/* *** *** *** *** *** ***   private functions   *** *** *** *** *** *** */
+/* *** *** *** ********************************************* *** *** *** */
+
+/* stolen from quota-3.13 (quota-tools) */
+
+#define PROC_PARTITIONS "/proc/partitions"
+#define DEVLABELDIR     "/dev"
+#define UUID   1
+#define VOL    2
+
+static struct uuidCache_s {
+       struct uuidCache_s *next;
+       char uuid[16];
+       char *label;
+       char *device;
+} *uuidCache = NULL;
+
+#define EXT2_SUPER_MAGIC 0xEF53
+struct ext2_super_block {
+       unsigned char s_dummy1[56];
+       unsigned char s_magic[2];
+       unsigned char s_dummy2[46];
+       unsigned char s_uuid[16];
+       char s_volume_name[16];
+};
+#define ext2magic(s) ((unsigned int)s.s_magic[0] \
+       + (((unsigned int)s.s_magic[1]) << 8))
+
+#if HAVE_XFS_XQM_H
+struct xfs_super_block {
+       unsigned char s_magic[4];
+       unsigned char s_dummy[28];
+       unsigned char s_uuid[16];
+       unsigned char s_dummy2[60];
+       char s_fsname[12];
+};
+#endif /* HAVE_XFS_XQM_H */
+
+#define REISER_SUPER_MAGIC "ReIsEr2Fs"
+struct reiserfs_super_block {
+       unsigned char s_dummy1[52];
+       unsigned char s_magic[10];
+       unsigned char s_dummy2[22];
+       unsigned char s_uuid[16];
+       char s_volume_name[16];
+};
+
+/* for now, only ext2 and xfs are supported */
+static int
+get_label_uuid(const char *device, char **label, char *uuid)
+{
+       /* start with ext2 and xfs tests, taken from mount_guess_fstype */
+       /* should merge these later */
+       int fd, rv = 1;
+       size_t namesize;
+       struct ext2_super_block e2sb;
+#if HAVE_XFS_XQM_H
+       struct xfs_super_block xfsb;
+#endif
+       struct reiserfs_super_block reisersb;
+
+       fd = open(device, O_RDONLY);
+       if(fd == -1) {
+               return rv;
+       }
+
+       if(lseek(fd, 1024, SEEK_SET) == 1024
+       && read(fd, (char *)&e2sb, sizeof(e2sb)) == sizeof(e2sb)
+       && ext2magic(e2sb) == EXT2_SUPER_MAGIC) {
+               memcpy(uuid, e2sb.s_uuid, sizeof(e2sb.s_uuid));
+               namesize = sizeof(e2sb.s_volume_name);
+               *label = smalloc(namesize + 1);
+               sstrncpy(*label, e2sb.s_volume_name, namesize);
+               rv = 0;
+#if HAVE_XFS_XQM_H
+       } else if(lseek(fd, 0, SEEK_SET) == 0
+       && read(fd, (char *)&xfsb, sizeof(xfsb)) == sizeof(xfsb)
+       && (strncmp((char *)&xfsb.s_magic, XFS_SUPER_MAGIC_STR, 4) == 0 ||
+       strncmp((char *)&xfsb.s_magic, XFS_SUPER_MAGIC2_STR, 4) == 0)) {
+               memcpy(uuid, xfsb.s_uuid, sizeof(xfsb.s_uuid));
+               namesize = sizeof(xfsb.s_fsname);
+               *label = smalloc(namesize + 1);
+               sstrncpy(*label, xfsb.s_fsname, namesize);
+               rv = 0;
+#endif /* HAVE_XFS_XQM_H */
+       } else if(lseek(fd, 65536, SEEK_SET) == 65536
+       && read(fd, (char *)&reisersb, sizeof(reisersb)) == sizeof(reisersb)
+       && !strncmp((char *)&reisersb.s_magic, REISER_SUPER_MAGIC, 9)) {
+               memcpy(uuid, reisersb.s_uuid, sizeof(reisersb.s_uuid));
+               namesize = sizeof(reisersb.s_volume_name);
+               *label = smalloc(namesize + 1);
+               sstrncpy(*label, reisersb.s_volume_name, namesize);
+               rv = 0;
+       }
+       close(fd);
+       return rv;
+}
+
+static void
+uuidcache_addentry(char *device, char *label, char *uuid)
+{
+       struct uuidCache_s *last;
+
+       if(!uuidCache) {
+               last = uuidCache = smalloc(sizeof(*uuidCache));
+       } else {
+               for(last = uuidCache; last->next; last = last->next);
+               last->next = smalloc(sizeof(*uuidCache));
+               last = last->next;
+       }
+       last->next = NULL;
+       last->device = device;
+       last->label = label;
+       memcpy(last->uuid, uuid, sizeof(last->uuid));
+}
+
+static void
+uuidcache_init(void)
+{
+       char line[100];
+       char *s;
+       int ma, mi, sz;
+       static char ptname[100];
+       FILE *procpt;
+       char uuid[16], *label = NULL;
+       char device[110];
+       int firstPass;
+       int handleOnFirst;
+
+       if(uuidCache) {
+               return;
+       }
+
+       procpt = fopen(PROC_PARTITIONS, "r");
+       if(procpt == NULL) {
+               return;
+       }
+
+       for(firstPass = 1; firstPass >= 0; firstPass--) {
+               fseek(procpt, 0, SEEK_SET);
+               while(fgets(line, sizeof(line), procpt)) {
+                       if(sscanf(line, " %d %d %d %[^\n ]",
+                               &ma, &mi, &sz, ptname) != 4)
+                       {
+                               continue;
+                       }
+
+                       /* skip extended partitions (heuristic: size 1) */
+                       if(sz == 1) {
+                               continue;
+                       }
+
+                       /* look only at md devices on first pass */
+                       handleOnFirst = !strncmp(ptname, "md", 2);
+                       if(firstPass != handleOnFirst) {
+                               continue;
+                       }
+
+                       /* skip entire disk (minor 0, 64, ... on ide;
+                       0, 16, ... on sd) */
+                       /* heuristic: partition name ends in a digit */
+
+                       for(s = ptname; *s; s++);
+
+                       if(isdigit((int)s[-1])) {
+                       /*
+                       * Note: this is a heuristic only - there is no reason
+                       * why these devices should live in /dev.
+                       * Perhaps this directory should be specifiable by option.
+                       * One might for example have /devlabel with links to /dev
+                       * for the devices that may be accessed in this way.
+                       * (This is useful, if the cdrom on /dev/hdc must not
+                       * be accessed.)
+                       */
+                               ssnprintf(device, sizeof(device), "%s/%s",
+                                       DEVLABELDIR, ptname);
+                               if(!get_label_uuid(device, &label, uuid)) {
+                                       uuidcache_addentry(sstrdup(device),
+                                               label, uuid);
+                               }
+                       }
+               }
+       }
+       fclose(procpt);
+}
+
+static unsigned char
+fromhex(char c)
+{
+       if(isdigit((int)c)) {
+               return (c - '0');
+       } else if(islower((int)c)) {
+               return (c - 'a' + 10);
+       } else {
+               return (c - 'A' + 10);
+       }
+}
+
+static char *
+get_spec_by_x(int n, const char *t)
+{
+       struct uuidCache_s *uc;
+
+       uuidcache_init();
+       uc = uuidCache;
+
+       while(uc) {
+               switch(n) {
+               case UUID:
+                       if(!memcmp(t, uc->uuid, sizeof(uc->uuid))) {
+                               return sstrdup(uc->device);
+                       }
+                       break;
+               case VOL:
+                       if(!strcmp(t, uc->label)) {
+                               return sstrdup(uc->device);
+                       }
+                       break;
+               }
+               uc = uc->next;
+       }
+       return NULL;
+}
+
+static char *
+get_spec_by_uuid(const char *s)
+{
+       char uuid[16];
+       int i;
+
+       if(strlen(s) != 36
+       || s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-') {
+               goto bad_uuid;
+       }
+
+       for(i=0; i<16; i++) {
+               if(*s == '-') {
+                       s++;
+               }
+               if(!isxdigit((int)s[0]) || !isxdigit((int)s[1])) {
+                       goto bad_uuid;
+               }
+               uuid[i] = ((fromhex(s[0]) << 4) | fromhex(s[1]));
+               s += 2;
+       }
+       return get_spec_by_x(UUID, uuid);
+
+       bad_uuid:
+               DEBUG("utils_mount: Found an invalid UUID: %s", s);
+       return NULL;
+}
+
+static char *get_spec_by_volume_label(const char *s)
+{
+        return get_spec_by_x (VOL, s);
+}
+
+static char *get_device_name(const char *optstr)
+{
+       char *rc;
+
+       if (optstr == NULL)
+       {
+               return (NULL);
+       }
+       else if (strncmp (optstr, "UUID=", 5) == 0)
+       {
+               DEBUG ("utils_mount: TODO: check UUID= code!");
+               rc = get_spec_by_uuid (optstr + 5);
+       }
+       else if (strncmp (optstr, "LABEL=", 6) == 0)
+       {
+               DEBUG ("utils_mount: TODO: check LABEL= code!");
+               rc = get_spec_by_volume_label (optstr + 6);
+       }
+       else
+       {
+               rc = sstrdup (optstr);
+       }
+
+       if(!rc)
+       {
+               DEBUG ("utils_mount: Error checking device name: optstr = %s", optstr);
+       }
+       return rc;
+}
+
+/* What weird OS is this..? I can't find any info with google :/ -octo */
+#if HAVE_LISTMNTENT && 0
+static cu_mount_t *cu_mount_listmntent (void)
+{
+       cu_mount_t *last = *list;
+       struct tabmntent *p;
+       struct mntent *mnt;
+
+       struct tabmntent *mntlist;
+       if(listmntent(&mntlist, COLLECTD_MNTTAB, NULL, NULL) < 0) {
+#if COLLECT_DEBUG
+               char errbuf[1024];
+               DEBUG("utils_mount: calling listmntent() failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+#endif /* COLLECT_DEBUG */
+       }
+
+       for(p = mntlist; p; p = p->next) {
+               char *loop = NULL, *device = NULL;
+
+               mnt = p->ment;
+               loop = cu_mount_getoptionvalue(mnt->mnt_opts, "loop=");
+               if(loop == NULL) {   /* no loop= mount */
+                       device = get_device_name(mnt->mnt_fsname);
+                       if(device == NULL) {
+                               DEBUG("utils_mount: can't get devicename for fs (%s) %s (%s)"
+                                       ": ignored", mnt->mnt_type,
+                                       mnt->mnt_dir, mnt->mnt_fsname);
+                               continue;
+                       }
+               } else {
+                       device = loop;
+               }
+               if(*list == NULL) {
+                       *list = (cu_mount_t *)smalloc(sizeof(cu_mount_t));
+                       last = *list;
+               } else {
+                       while(last->next != NULL) { /* is last really last? */
+                               last = last->next;
+                       }
+                       last->next = (cu_mount_t *)smalloc(sizeof(cu_mount_t));
+                       last = last->next;
+               }
+               last->dir = sstrdup(mnt->mnt_dir);
+               last->spec_device = sstrdup(mnt->mnt_fsname);
+               last->device = device;
+               last->type = sstrdup(mnt->mnt_type);
+               last->options = sstrdup(mnt->mnt_opts);
+               last->next = NULL;
+       } /* for(p = mntlist; p; p = p->next) */
+
+       return(last);
+} /* cu_mount_t *cu_mount_listmntent(void) */
+/* #endif HAVE_LISTMNTENT */
+
+/* 4.4BSD and Mac OS X (getfsstat) or NetBSD (getvfsstat) */
+#elif HAVE_GETVFSSTAT || HAVE_GETFSSTAT
+static cu_mount_t *cu_mount_getfsstat (void)
+{
+#if HAVE_GETVFSSTAT
+#  define STRUCT_STATFS struct statvfs
+#  define CMD_STATFS    getvfsstat
+#  define FLAGS_STATFS  ST_NOWAIT
+/* #endif HAVE_GETVFSSTAT */
+#elif HAVE_GETFSSTAT
+#  define STRUCT_STATFS struct statfs
+#  define CMD_STATFS    getfsstat
+#  define FLAGS_STATFS  MNT_NOWAIT
+#endif /* HAVE_GETFSSTAT */
+
+       int bufsize;
+       STRUCT_STATFS *buf;
+
+       int num;
+       int i;
+
+       cu_mount_t *first = NULL;
+       cu_mount_t *last  = NULL;
+       cu_mount_t *new   = NULL;
+
+       /* Get the number of mounted file systems */
+       if ((bufsize = CMD_STATFS (NULL, 0, FLAGS_STATFS)) < 1)
+       {
+#if COLLECT_DEBUG
+               char errbuf[1024];
+               DEBUG ("utils_mount: getv?fsstat failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+#endif /* COLLECT_DEBUG */
+               return (NULL);
+       }
+
+       if ((buf = (STRUCT_STATFS *) malloc (bufsize * sizeof (STRUCT_STATFS)))
+                       == NULL)
+               return (NULL);
+       memset (buf, '\0', bufsize * sizeof (STRUCT_STATFS));
+
+       /* The bufsize needs to be passed in bytes. Really. This is not in the
+        * manpage.. -octo */
+       if ((num = CMD_STATFS (buf, bufsize * sizeof (STRUCT_STATFS), FLAGS_STATFS)) < 1)
+       {
+#if COLLECT_DEBUG
+               char errbuf[1024];
+               DEBUG ("utils_mount: getv?fsstat failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+#endif /* COLLECT_DEBUG */
+               free (buf);
+               return (NULL);
+       }
+
+       for (i = 0; i < num; i++)
+       {
+               if ((new = malloc (sizeof (cu_mount_t))) == NULL)
+                       break;
+               memset (new, '\0', sizeof (cu_mount_t));
+               
+               /* Copy values from `struct mnttab' */
+               new->dir         = sstrdup (buf[i].f_mntonname);
+               new->spec_device = sstrdup (buf[i].f_mntfromname);
+               new->type        = sstrdup (buf[i].f_fstypename);
+               new->options     = NULL;
+               new->device      = get_device_name (new->options);
+               new->next = NULL;
+
+               /* Append to list */
+               if (first == NULL)
+               {
+                       first = new;
+                       last  = new;
+               }
+               else
+               {
+                       last->next = new;
+                       last       = new;
+               }
+       }
+
+       free (buf);
+
+       return (first);
+}
+/* #endif HAVE_GETVFSSTAT || HAVE_GETFSSTAT */
+
+/* Solaris (SunOS 10): int getmntent(FILE *fp, struct mnttab *mp); */
+#elif HAVE_TWO_GETMNTENT || HAVE_GEN_GETMNTENT || HAVE_SUN_GETMNTENT
+static cu_mount_t *cu_mount_gen_getmntent (void)
+{
+       struct mnttab mt;
+       FILE *fp;
+
+       cu_mount_t *first = NULL;
+       cu_mount_t *last  = NULL;
+       cu_mount_t *new   = NULL;
+
+       DEBUG ("utils_mount: (void); COLLECTD_MNTTAB = %s", COLLECTD_MNTTAB);
+
+       if ((fp = fopen (COLLECTD_MNTTAB, "r")) == NULL)
+       {
+               char errbuf[1024];
+               ERROR ("fopen (%s): %s", COLLECTD_MNTTAB,
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (NULL);
+       }
+
+       while (getmntent (fp, &mt) == 0)
+       {
+               if ((new = malloc (sizeof (cu_mount_t))) == NULL)
+                       break;
+               memset (new, '\0', sizeof (cu_mount_t));
+               
+               /* Copy values from `struct mnttab' */
+               new->dir         = sstrdup (mt.mnt_mountp);
+               new->spec_device = sstrdup (mt.mnt_special);
+               new->type        = sstrdup (mt.mnt_fstype);
+               new->options     = sstrdup (mt.mnt_mntopts);
+               new->device      = get_device_name (new->options);
+               new->next = NULL;
+
+               /* Append to list */
+               if (first == NULL)
+               {
+                       first = new;
+                       last  = new;
+               }
+               else
+               {
+                       last->next = new;
+                       last       = new;
+               }
+       }
+
+       fclose (fp);
+
+       return (first);
+} /* static cu_mount_t *cu_mount_gen_getmntent (void) */
+/* #endif HAVE_TWO_GETMNTENT || HAVE_GEN_GETMNTENT || HAVE_SUN_GETMNTENT */
+
+#elif HAVE_SEQ_GETMNTENT
+#warn "This version of `getmntent' hat not yet been implemented!"
+/* #endif HAVE_SEQ_GETMNTENT */
+
+#elif HAVE_ONE_GETMNTENT
+static cu_mount_t *cu_mount_getmntent (void)
+{
+       FILE *fp;
+       struct mntent *me;
+
+       cu_mount_t *first = NULL;
+       cu_mount_t *last  = NULL;
+       cu_mount_t *new   = NULL;
+
+       DEBUG ("utils_mount: (void); COLLECTD_MNTTAB = %s", COLLECTD_MNTTAB);
+
+       if ((fp = setmntent (COLLECTD_MNTTAB, "r")) == NULL)
+       {
+               char errbuf[1024];
+               ERROR ("setmntent (%s): %s", COLLECTD_MNTTAB,
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (NULL);
+       }
+
+       while ((me = getmntent (fp)) != NULL)
+       {
+               if ((new = malloc (sizeof (cu_mount_t))) == NULL)
+                       break;
+               memset (new, '\0', sizeof (cu_mount_t));
+               
+               /* Copy values from `struct mntent *' */
+               new->dir         = sstrdup (me->mnt_dir);
+               new->spec_device = sstrdup (me->mnt_fsname);
+               new->type        = sstrdup (me->mnt_type);
+               new->options     = sstrdup (me->mnt_opts);
+               new->device      = get_device_name (new->options);
+               new->next        = NULL;
+
+               DEBUG ("utils_mount: new = {dir = %s, spec_device = %s, type = %s, options = %s, device = %s}",
+                               new->dir, new->spec_device, new->type, new->options, new->device);
+
+               /* Append to list */
+               if (first == NULL)
+               {
+                       first = new;
+                       last  = new;
+               }
+               else
+               {
+                       last->next = new;
+                       last       = new;
+               }
+       }
+
+       endmntent (fp);
+
+       DEBUG ("utils_mount: return (0x%p)", (void *) first);
+
+       return (first);
+}
+#endif /* HAVE_ONE_GETMNTENT */
+
+/* *** *** *** ******************************************** *** *** *** */
+/* *** *** *** *** *** ***   public functions   *** *** *** *** *** *** */
+/* *** *** *** ******************************************** *** *** *** */
+
+cu_mount_t *cu_mount_getlist(cu_mount_t **list)
+{
+       cu_mount_t *new;
+       cu_mount_t *first = NULL;
+       cu_mount_t *last  = NULL;
+
+       if (list == NULL)
+               return (NULL);
+
+       if (*list != NULL)
+       {
+               first = *list;
+               last  =  first;
+               while (last->next != NULL)
+                       last = last->next;
+       }
+
+#if HAVE_LISTMNTENT && 0
+       new = cu_mount_listmntent ();
+#elif HAVE_GETVFSSTAT || HAVE_GETFSSTAT
+       new = cu_mount_getfsstat ();
+#elif HAVE_TWO_GETMNTENT || HAVE_GEN_GETMNTENT || HAVE_SUN_GETMNTENT
+       new = cu_mount_gen_getmntent ();
+#elif HAVE_SEQ_GETMNTENT
+# error "This version of `getmntent' hat not yet been implemented!"
+#elif HAVE_ONE_GETMNTENT
+       new = cu_mount_getmntent ();
+#else
+# error "Could not determine how to find mountpoints."
+#endif
+
+       if (first != NULL)
+       {
+               last->next = new;
+       }
+       else
+       {
+               first = new;
+               last  = new;
+               *list = first;
+       }
+
+       while ((last != NULL) && (last->next != NULL))
+               last = last->next;
+
+       return (last);
+} /* cu_mount_t *cu_mount_getlist(cu_mount_t **list) */
+
+void cu_mount_freelist (cu_mount_t *list)
+{
+       cu_mount_t *this;
+       cu_mount_t *next;
+
+       for (this = list; this != NULL; this = next)
+       {
+               next = this->next;
+
+               sfree (this->dir);
+               sfree (this->spec_device);
+               sfree (this->device);
+               sfree (this->type);
+               sfree (this->options);
+               sfree (this);
+       }
+} /* void cu_mount_freelist(cu_mount_t *list) */
+
+char *
+cu_mount_checkoption(char *line, char *keyword, int full)
+{
+       char *line2, *l2;
+       int l = strlen(keyword);
+       char *p1, *p2;
+
+       if(line == NULL || keyword == NULL) {
+               return NULL;
+       }
+       if(full != 0) {
+               full = 1;
+       }
+
+       line2 = sstrdup(line);
+       l2 = line2;
+       while(*l2 != '\0') {
+               if(*l2 == ',') {
+                       *l2 = '\0';
+               }
+               l2++;
+       }
+
+       p1 = line - 1;
+       p2 = strchr(line, ',');
+       do {
+               if(strncmp(line2+(p1-line)+1, keyword, l+full) == 0) {
+                       free(line2);
+                       return p1+1;
+               }
+               p1 = p2;
+               if(p1 != NULL) {
+                       p2 = strchr(p1+1, ',');
+               }
+       } while(p1 != NULL);
+
+       free(line2);
+       return NULL;
+} /* char *cu_mount_checkoption(char *line, char *keyword, int full) */
+
+char *
+cu_mount_getoptionvalue(char *line, char *keyword)
+{
+       char *r;
+
+       r = cu_mount_checkoption(line, keyword, 0);
+       if(r != NULL) {
+               char *p;
+               r += strlen(keyword);
+               p = strchr(r, ',');
+               if(p == NULL) {
+                       if(strlen(r) == 0) {
+                               return NULL;
+                       }
+                       return sstrdup(r);
+               } else {
+                       char *m;
+                       if((p-r) == 1) {
+                               return NULL;
+                       }
+                       m = (char *)smalloc(p-r+1);
+                       sstrncpy(m, r, p-r+1);
+                       return m;
+               }
+       }
+       return r;
+} /* char *cu_mount_getoptionvalue(char *line, char *keyword) */
+
+int
+cu_mount_type(const char *type)
+{
+       if(strcmp(type, "ext3") == 0) return CUMT_EXT3;
+       if(strcmp(type, "ext2") == 0) return CUMT_EXT2;
+       if(strcmp(type, "ufs")  == 0) return CUMT_UFS;
+       if(strcmp(type, "vxfs") == 0) return CUMT_VXFS;
+       if(strcmp(type, "zfs")  == 0) return CUMT_ZFS;
+       return CUMT_UNKNOWN;
+} /* int cu_mount_type(const char *type) */
+
diff --git a/src/utils_mount.h b/src/utils_mount.h
new file mode 100644 (file)
index 0000000..1f2403c
--- /dev/null
@@ -0,0 +1,188 @@
+/**
+ * collectd - src/utils_mount.h
+ * Copyright (C) 2005,2006  Niki W. Waibel
+ *
+ * This program is free software; you can redistribute it and/
+ * or modify it under the terms of the GNU General Public Li-
+ * cence as published by the Free Software Foundation; either
+ * version 2 of the Licence, or any later version.
+ *
+ * This program is distributed in the hope that it will be use-
+ * ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ * ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public Licence for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * Licence along with this program; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
+ * USA.
+ *
+ * Author:
+ *   Niki W. Waibel <niki.waibel@gmx.net>
+**/
+
+/* See below for instructions how to use the public functions. */
+
+#ifndef COLLECTD_UTILS_MOUNT_H
+#define COLLECTD_UTILS_MOUNT_H 1
+
+#if HAVE_FS_INFO_H
+# include <fs_info.h>
+#endif
+#if HAVE_FSHELP_H
+# include <fshelp.h>
+#endif
+#if HAVE_PATHS_H
+# include <paths.h>
+#endif
+#if HAVE_MNTENT_H
+# include <mntent.h>
+#endif
+#if HAVE_MNTTAB_H
+# include <mnttab.h>
+#endif
+#if HAVE_SYS_FSTYP_H
+# include <sys/fstyp.h>
+#endif
+#if HAVE_SYS_FS_TYPES_H
+# include <sys/fs_types.h>
+#endif
+#if HAVE_SYS_MNTENT_H
+# include <sys/mntent.h>
+#endif
+#if HAVE_SYS_MNTTAB_H
+# include <sys/mnttab.h>
+#endif
+#if HAVE_SYS_MOUNT_H
+# include <sys/mount.h>
+#endif
+#if HAVE_SYS_STATFS_H
+# include <sys/statfs.h>
+#endif
+#if HAVE_SYS_VFS_H
+# include <sys/vfs.h>
+#endif
+#if HAVE_SYS_VFSTAB_H
+# include <sys/vfstab.h>
+#endif
+
+/* Collectd Utils Mount Type */
+#define CUMT_UNKNOWN (0)
+#define CUMT_EXT2    (1)
+#define CUMT_EXT3    (2)
+#define CUMT_XFS     (3)
+#define CUMT_UFS     (4)
+#define CUMT_VXFS    (5)
+#define CUMT_ZFS     (6)
+
+typedef struct _cu_mount_t cu_mount_t;
+struct _cu_mount_t {
+       char *dir;         /* "/sys" or "/" */
+       char *spec_device; /* "LABEL=/" or "none" or "proc" or "/dev/hda1" */
+       char *device;      /* "none" or "proc" or "/dev/hda1" */
+       char *type;        /* "sysfs" or "ext3" */
+       char *options;     /* "rw,noatime,commit=600,quota,grpquota" */
+       cu_mount_t *next;
+};
+
+cu_mount_t *cu_mount_getlist(cu_mount_t **list);
+/*
+  DESCRIPTION
+       The cu_mount_getlist() function creates a list
+       of all mountpoints.
+
+       If *list is NULL, a new list is created and *list is
+       set to point to the first entry.
+
+       If *list is not NULL, the list of mountpoints is appended
+       and *list is not changed.
+
+  RETURN VALUE
+       The cu_mount_getlist() function returns a pointer to
+       the last entry of the list, or NULL if an error has
+       occured.
+
+  NOTES
+       In case of an error, *list is not modified.
+*/
+
+void cu_mount_freelist(cu_mount_t *list);
+/*
+  DESCRIPTION
+       The cu_mount_freelist() function free()s all memory
+       allocated by *list and *list itself as well.
+*/
+
+char *cu_mount_checkoption(char *line, char *keyword, int full);
+/*
+  DESCRIPTION
+       The cu_mount_checkoption() function is a replacement of
+       char *hasmntopt(const struct mntent *mnt, const char *opt).
+       In fact hasmntopt() just looks for the first occurence of the
+       characters at opt in mnt->mnt_opts. cu_mount_checkoption()
+       checks for the *option* keyword in line, starting at the
+       first character of line or after a ','.
+
+       If full is not 0 then also the end of keyword has to match
+       either the end of line or a ',' after keyword.
+
+  RETURN VALUE
+       The cu_mount_checkoption() function returns a pointer into
+       string line if a match of keyword is found. If no match is
+       found cu_mount_checkoption() returns NULL.
+
+  NOTES
+       Do *not* try to free() the pointer which is returned! It is
+       just part of the string line.
+
+       full should be set to 0 when matching options like: rw, quota,
+       noatime. Set full to 1 when matching options like: loop=,
+       gid=, commit=.
+
+  EXAMPLES
+       If line is "rw,usrquota,grpquota", keyword is "quota", NULL
+       will be returned (independend of full).
+
+       If line is "rw,usrquota,grpquota", keyword is "usrquota",
+       a pointer to "usrquota,grpquota" is returned (independend
+       of full).
+
+       If line is "rw,loop=/dev/loop1,quota", keyword is "loop="
+       and full is 0, then a pointer to "loop=/dev/loop1,quota"
+       is returned. If full is not 0 then NULL is returned. But
+       maybe you might want to try cu_mount_getoptionvalue()...
+*/
+
+char *cu_mount_getoptionvalue(char *line, char *keyword);
+/*
+  DESCRIPTION
+       The cu_mount_getoptionvalue() function can be used to grab
+       a VALUE out of a mount option (line) like:
+               loop=VALUE
+       whereas "loop=" is the keyword.
+
+  RETURN VALUE
+       If the cu_mount_getoptionvalue() function can find the option
+       keyword in line, then memory is allocated for the value of
+       that option and a pointer to that value is returned.
+
+       If the option keyword is not found, cu_mount_getoptionvalue()
+       returns NULL;
+
+  NOTES
+       Internally it calls cu_mount_checkoption(), then it
+       allocates memory for VALUE and returns a pointer to that
+       string. So *do not forget* to free() the memory returned
+       after use!!!
+*/
+
+int cu_mount_type(const char *type);
+/*
+  DESCRIPTION
+
+  RETURN VALUE
+*/
+
+
+#endif /* !COLLECTD_UTILS_MOUNT_H */
+
diff --git a/src/utils_parse_option.c b/src/utils_parse_option.c
new file mode 100644 (file)
index 0000000..2168cd1
--- /dev/null
@@ -0,0 +1,204 @@
+/**
+ * collectd - src/utils_parse_option.c
+ * Copyright (C) 2008  Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "utils_parse_option.h"
+
+int parse_string (char **ret_buffer, char **ret_string)
+{
+  char *buffer;
+  char *string;
+
+  buffer = *ret_buffer;
+
+  /* Eat up leading spaces. */
+  string = buffer;
+  while (isspace ((int) *string))
+    string++;
+  if (*string == 0)
+    return (1);
+
+  /* A quoted string */
+  if (*string == '"')
+  {
+    char *dst;
+
+    string++;
+    if (*string == 0)
+      return (1);
+
+    dst = string;
+    buffer = string;
+    while ((*buffer != '"') && (*buffer != 0))
+    {
+      /* Un-escape backslashes */
+      if (*buffer == '\\')
+      {
+        buffer++;
+        /* Catch a backslash at the end of buffer */
+        if (*buffer == 0)
+          return (-1);
+      }
+      *dst = *buffer;
+      buffer++;
+      dst++;
+    }
+    /* No quote sign has been found */
+    if (*buffer == 0)
+      return (-1);
+
+    *dst = 0;
+    dst++;
+    *buffer = 0;
+    buffer++;
+
+    /* Check for trailing spaces. */
+    if ((*buffer != 0) && !isspace ((int) *buffer))
+      return (-1);
+  }
+  else /* an unquoted string */
+  {
+    buffer = string;
+    while ((*buffer != 0) && !isspace ((int) *buffer))
+      buffer++;
+    if (*buffer != 0)
+    {
+      *buffer = 0;
+      buffer++;
+    }
+  }
+  
+  /* Eat up trailing spaces */
+  while (isspace ((int) *buffer))
+    buffer++;
+
+  *ret_buffer = buffer;
+  *ret_string = string;
+
+  return (0);
+} /* int parse_string */
+
+/*
+ * parse_option
+ * ------------
+ *  Parses an ``option'' as used with the unixsock and exec commands. An
+ *  option is of the form:
+ *    name0="value"
+ *    name1="value with \"quotes\""
+ *    name2="value \\ backslash"
+ *  However, if the value does *not* contain a space character, you can skip
+ *  the quotes.
+ */
+int parse_option (char **ret_buffer, char **ret_key, char **ret_value)
+{
+  char *buffer;
+  char *key;
+  char *value;
+  int status;
+
+  buffer = *ret_buffer;
+
+  /* Eat up leading spaces */
+  key = buffer;
+  while (isspace ((int) *key))
+    key++;
+  if (*key == 0)
+    return (1);
+
+  /* Look for the equal sign */
+  buffer = key;
+  while (isalnum ((int) *buffer))
+    buffer++;
+  if ((*buffer != '=') || (buffer == key))
+    return (1);
+  *buffer = 0;
+  buffer++;
+  /* Empty values must be written as "" */
+  if (isspace ((int) *buffer) || (*buffer == 0))
+    return (-1);
+
+  status = parse_string (&buffer, &value);
+  if (status != 0)
+    return (-1);
+
+  /* NB: parse_string will have eaten up all trailing spaces. */
+
+  *ret_buffer = buffer;
+  *ret_key = key;
+  *ret_value = value;
+
+  return (0);
+} /* int parse_option */
+
+int escape_string (char *buffer, size_t buffer_size)
+{
+  char *temp;
+  size_t i;
+  size_t j;
+
+  /* Check if we need to escape at all first */
+  temp = strpbrk (buffer, " \t\"\\");
+  if (temp == NULL)
+    return (0);
+
+  temp = (char *) malloc (buffer_size);
+  if (temp == NULL)
+    return (-1);
+  memset (temp, 0, buffer_size);
+
+  temp[0] = '"';
+  j = 1;
+
+  for (i = 0; i < buffer_size; i++)
+  {
+    if (buffer[i] == 0)
+    {
+      break;
+    }
+    else if ((buffer[i] == '"') || (buffer[i] == '\\'))
+    {
+      if (j > (buffer_size - 4))
+        break;
+      temp[j] = '\\';
+      temp[j + 1] = buffer[i];
+      j += 2;
+    }
+    else
+    {
+      if (j > (buffer_size - 3))
+        break;
+      temp[j] = buffer[i];
+      j++;
+    }
+  }
+
+  assert ((j + 1) < buffer_size);
+  temp[j] = '"';
+  temp[j + 1] = 0;
+
+  sstrncpy (buffer, temp, buffer_size);
+  sfree (temp);
+  return (0);
+} /* int escape_string */
+
+/* vim: set sw=2 ts=8 tw=78 et : */
diff --git a/src/utils_parse_option.h b/src/utils_parse_option.h
new file mode 100644 (file)
index 0000000..1dfb3ae
--- /dev/null
@@ -0,0 +1,32 @@
+/**
+ * collectd - src/utils_parse_option.h
+ * Copyright (C) 2008  Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef UTILS_PARSE_OPTION
+#define UTILS_PARSE_OPTION 1
+
+int parse_string (char **ret_buffer, char **ret_string);
+int parse_option (char **ret_buffer, char **ret_key, char **ret_value);
+
+int escape_string (char *buffer, size_t buffer_size);
+
+#endif /* UTILS_PARSE_OPTION */
+
+/* vim: set sw=2 ts=8 tw=78 et : */
diff --git a/src/utils_rrdcreate.c b/src/utils_rrdcreate.c
new file mode 100644 (file)
index 0000000..91ac6ce
--- /dev/null
@@ -0,0 +1,439 @@
+/**
+ * collectd - src/utils_rrdcreate.c
+ * Copyright (C) 2006-2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "utils_rrdcreate.h"
+
+#include <pthread.h>
+#include <rrd.h>
+
+/*
+ * Private variables
+ */
+static int rra_timespans[] =
+{
+  3600,
+  86400,
+  604800,
+  2678400,
+  31622400
+};
+static int rra_timespans_num = STATIC_ARRAY_SIZE (rra_timespans);
+
+static char *rra_types[] =
+{
+  "AVERAGE",
+  "MIN",
+  "MAX"
+};
+static int rra_types_num = STATIC_ARRAY_SIZE (rra_types);
+
+#if !defined(HAVE_THREADSAFE_LIBRRD) || !HAVE_THREADSAFE_LIBRRD
+static pthread_mutex_t librrd_lock = PTHREAD_MUTEX_INITIALIZER;
+#endif
+
+/*
+ * Private functions
+ */
+static void rra_free (int rra_num, char **rra_def) /* {{{ */
+{
+  int i;
+
+  for (i = 0; i < rra_num; i++)
+  {
+    sfree (rra_def[i]);
+  }
+  sfree (rra_def);
+} /* }}} void rra_free */
+
+/* * * * * * * * * *
+ * WARNING:  Magic *
+ * * * * * * * * * */
+static int rra_get (char ***ret, const value_list_t *vl, /* {{{ */
+    const rrdcreate_config_t *cfg)
+{
+  char **rra_def;
+  int rra_num;
+
+  int *rts;
+  int  rts_num;
+
+  int rra_max;
+
+  int span;
+
+  int cdp_num;
+  int cdp_len;
+  int i, j;
+
+  char buffer[128];
+
+  /* The stepsize we use here: If it is user-set, use it. If not, use the
+   * interval of the value-list. */
+  int ss;
+
+  if (cfg->rrarows <= 0)
+  {
+    *ret = NULL;
+    return (-1);
+  }
+
+  if ((cfg->xff < 0) || (cfg->xff >= 1.0))
+  {
+    *ret = NULL;
+    return (-1);
+  }
+
+  if (cfg->stepsize > 0)
+    ss = cfg->stepsize;
+  else
+    ss = (int) CDTIME_T_TO_TIME_T (vl->interval);
+  if (ss <= 0)
+  {
+    *ret = NULL;
+    return (-1);
+  }
+
+  /* Use the configured timespans or fall back to the built-in defaults */
+  if (cfg->timespans_num != 0)
+  {
+    rts = cfg->timespans;
+    rts_num = cfg->timespans_num;
+  }
+  else
+  {
+    rts = rra_timespans;
+    rts_num = rra_timespans_num;
+  }
+
+  rra_max = rts_num * rra_types_num;
+
+  if ((rra_def = (char **) malloc ((rra_max + 1) * sizeof (char *))) == NULL)
+    return (-1);
+  memset (rra_def, '\0', (rra_max + 1) * sizeof (char *));
+  rra_num = 0;
+
+  cdp_len = 0;
+  for (i = 0; i < rts_num; i++)
+  {
+    span = rts[i];
+
+    if ((span / ss) < cfg->rrarows)
+      span = ss * cfg->rrarows;
+
+    if (cdp_len == 0)
+      cdp_len = 1;
+    else
+      cdp_len = (int) floor (((double) span)
+          / ((double) (cfg->rrarows * ss)));
+
+    cdp_num = (int) ceil (((double) span)
+        / ((double) (cdp_len * ss)));
+
+    for (j = 0; j < rra_types_num; j++)
+    {
+      int status;
+
+      if (rra_num >= rra_max)
+        break;
+
+      status = ssnprintf (buffer, sizeof (buffer), "RRA:%s:%.10f:%u:%u",
+          rra_types[j], cfg->xff, cdp_len, cdp_num);
+
+      if ((status < 0) || ((size_t) status >= sizeof (buffer)))
+      {
+        ERROR ("rra_get: Buffer would have been truncated.");
+        continue;
+      }
+
+      rra_def[rra_num++] = sstrdup (buffer);
+    }
+  }
+
+  *ret = rra_def;
+  return (rra_num);
+} /* }}} int rra_get */
+
+static void ds_free (int ds_num, char **ds_def) /* {{{ */
+{
+  int i;
+
+  for (i = 0; i < ds_num; i++)
+    if (ds_def[i] != NULL)
+      free (ds_def[i]);
+  free (ds_def);
+} /* }}} void ds_free */
+
+static int ds_get (char ***ret, /* {{{ */
+    const data_set_t *ds, const value_list_t *vl,
+    const rrdcreate_config_t *cfg)
+{
+  char **ds_def;
+  int ds_num;
+
+  char min[32];
+  char max[32];
+  char buffer[128];
+
+  ds_def = (char **) malloc (ds->ds_num * sizeof (char *));
+  if (ds_def == NULL)
+  {
+    char errbuf[1024];
+    ERROR ("rrdtool plugin: malloc failed: %s",
+        sstrerror (errno, errbuf, sizeof (errbuf)));
+    return (-1);
+  }
+  memset (ds_def, '\0', ds->ds_num * sizeof (char *));
+
+  for (ds_num = 0; ds_num < ds->ds_num; ds_num++)
+  {
+    data_source_t *d = ds->ds + ds_num;
+    char *type;
+    int status;
+
+    ds_def[ds_num] = NULL;
+
+    if (d->type == DS_TYPE_COUNTER)
+      type = "COUNTER";
+    else if (d->type == DS_TYPE_GAUGE)
+      type = "GAUGE";
+    else if (d->type == DS_TYPE_DERIVE)
+      type = "DERIVE";
+    else if (d->type == DS_TYPE_ABSOLUTE)
+      type = "ABSOLUTE";
+    else
+    {
+      ERROR ("rrdtool plugin: Unknown DS type: %i",
+          d->type);
+      break;
+    }
+
+    if (isnan (d->min))
+    {
+      sstrncpy (min, "U", sizeof (min));
+    }
+    else
+      ssnprintf (min, sizeof (min), "%f", d->min);
+
+    if (isnan (d->max))
+    {
+      sstrncpy (max, "U", sizeof (max));
+    }
+    else
+      ssnprintf (max, sizeof (max), "%f", d->max);
+
+    status = ssnprintf (buffer, sizeof (buffer),
+        "DS:%s:%s:%i:%s:%s",
+        d->name, type,
+        (cfg->heartbeat > 0)
+        ? cfg->heartbeat
+        : (int) CDTIME_T_TO_TIME_T (2 * vl->interval),
+        min, max);
+    if ((status < 1) || ((size_t) status >= sizeof (buffer)))
+      break;
+
+    ds_def[ds_num] = sstrdup (buffer);
+  } /* for ds_num = 0 .. ds->ds_num */
+
+  if (ds_num != ds->ds_num)
+  {
+    ds_free (ds_num, ds_def);
+    return (-1);
+  }
+
+  *ret = ds_def;
+  return (ds_num);
+} /* }}} int ds_get */
+
+#if HAVE_THREADSAFE_LIBRRD
+static int srrd_create (const char *filename, /* {{{ */
+    unsigned long pdp_step, time_t last_up,
+    int argc, const char **argv)
+{
+  int status;
+  char *filename_copy;
+
+  if ((filename == NULL) || (argv == NULL))
+    return (-EINVAL);
+
+  /* Some versions of librrd don't have the `const' qualifier for the first
+   * argument, so we have to copy the pointer here to avoid warnings. It sucks,
+   * but what else can we do? :(  -octo */
+  filename_copy = strdup (filename);
+  if (filename_copy == NULL)
+  {
+    ERROR ("srrd_create: strdup failed.");
+    return (-ENOMEM);
+  }
+
+  optind = 0; /* bug in librrd? */
+  rrd_clear_error ();
+
+  status = rrd_create_r (filename_copy, pdp_step, last_up,
+      argc, (void *) argv);
+
+  if (status != 0)
+  {
+    WARNING ("rrdtool plugin: rrd_create_r (%s) failed: %s",
+        filename, rrd_get_error ());
+  }
+
+  sfree (filename_copy);
+
+  return (status);
+} /* }}} int srrd_create */
+/* #endif HAVE_THREADSAFE_LIBRRD */
+
+#else /* !HAVE_THREADSAFE_LIBRRD */
+static int srrd_create (const char *filename, /* {{{ */
+    unsigned long pdp_step, time_t last_up,
+    int argc, const char **argv)
+{
+  int status;
+
+  int new_argc;
+  char **new_argv;
+
+  char pdp_step_str[16];
+  char last_up_str[16];
+
+  new_argc = 6 + argc;
+  new_argv = (char **) malloc ((new_argc + 1) * sizeof (char *));
+  if (new_argv == NULL)
+  {
+    ERROR ("rrdtool plugin: malloc failed.");
+    return (-1);
+  }
+
+  if (last_up == 0)
+    last_up = time (NULL) - 10;
+
+  ssnprintf (pdp_step_str, sizeof (pdp_step_str), "%lu", pdp_step);
+  ssnprintf (last_up_str, sizeof (last_up_str), "%lu", (unsigned long) last_up);
+
+  new_argv[0] = "create";
+  new_argv[1] = (void *) filename;
+  new_argv[2] = "-s";
+  new_argv[3] = pdp_step_str;
+  new_argv[4] = "-b";
+  new_argv[5] = last_up_str;
+
+  memcpy (new_argv + 6, argv, argc * sizeof (char *));
+  new_argv[new_argc] = NULL;
+
+  pthread_mutex_lock (&librrd_lock);
+  optind = 0; /* bug in librrd? */
+  rrd_clear_error ();
+
+  status = rrd_create (new_argc, new_argv);
+  pthread_mutex_unlock (&librrd_lock);
+
+  if (status != 0)
+  {
+    WARNING ("rrdtool plugin: rrd_create (%s) failed: %s",
+        filename, rrd_get_error ());
+  }
+
+  sfree (new_argv);
+
+  return (status);
+} /* }}} int srrd_create */
+#endif /* !HAVE_THREADSAFE_LIBRRD */
+
+/*
+ * Public functions
+ */
+int cu_rrd_create_file (const char *filename, /* {{{ */
+    const data_set_t *ds, const value_list_t *vl,
+    const rrdcreate_config_t *cfg)
+{
+  char **argv;
+  int argc;
+  char **rra_def;
+  int rra_num;
+  char **ds_def;
+  int ds_num;
+  int status = 0;
+  time_t last_up;
+  unsigned long stepsize;
+
+  if (check_create_dir (filename))
+    return (-1);
+
+  if ((rra_num = rra_get (&rra_def, vl, cfg)) < 1)
+  {
+    ERROR ("cu_rrd_create_file failed: Could not calculate RRAs");
+    return (-1);
+  }
+
+  if ((ds_num = ds_get (&ds_def, ds, vl, cfg)) < 1)
+  {
+    ERROR ("cu_rrd_create_file failed: Could not calculate DSes");
+    return (-1);
+  }
+
+  argc = ds_num + rra_num;
+
+  if ((argv = (char **) malloc (sizeof (char *) * (argc + 1))) == NULL)
+  {
+    char errbuf[1024];
+    ERROR ("cu_rrd_create_file failed: %s",
+        sstrerror (errno, errbuf, sizeof (errbuf)));
+    return (-1);
+  }
+
+  memcpy (argv, ds_def, ds_num * sizeof (char *));
+  memcpy (argv + ds_num, rra_def, rra_num * sizeof (char *));
+  argv[ds_num + rra_num] = NULL;
+
+  last_up = CDTIME_T_TO_TIME_T (vl->time);
+  if (last_up <= 10)
+    last_up = time (NULL);
+  last_up -= 10;
+
+  if (cfg->stepsize > 0)
+    stepsize = cfg->stepsize;
+  else
+    stepsize = (unsigned long) CDTIME_T_TO_TIME_T (vl->interval);
+
+  status = srrd_create (filename, stepsize, last_up,
+      argc, (const char **) argv);
+
+  free (argv);
+  ds_free (ds_num, ds_def);
+  rra_free (rra_num, rra_def);
+
+  if (status != 0)
+  {
+    WARNING ("cu_rrd_create_file: srrd_create (%s) returned status %i.",
+        filename, status);
+  }
+  else
+  {
+    DEBUG ("cu_rrd_create_file: Successfully created RRD file \"%s\".",
+        filename);
+  }
+
+  return (status);
+} /* }}} int cu_rrd_create_file */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/utils_rrdcreate.h b/src/utils_rrdcreate.h
new file mode 100644 (file)
index 0000000..103ca57
--- /dev/null
@@ -0,0 +1,50 @@
+/**
+ * collectd - src/utils_rrdcreate.h
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef UTILS_RRDCREATE_H
+#define UTILS_RRDCREATE_H 1
+
+#include "plugin.h"
+
+#include <stddef.h>
+
+struct rrdcreate_config_s
+{
+  unsigned long stepsize;
+  int    heartbeat;
+  int    rrarows;
+  double xff;
+
+  int *timespans;
+  size_t timespans_num;
+
+  char **consolidation_functions;
+  size_t consolidation_functions_num;
+};
+typedef struct rrdcreate_config_s rrdcreate_config_t;
+
+int cu_rrd_create_file (const char *filename,
+    const data_set_t *ds, const value_list_t *vl,
+    const rrdcreate_config_t *cfg);
+
+#endif /* UTILS_RRDCREATE_H */
+
+/* vim: set sw=2 sts=2 et : */
diff --git a/src/utils_subst.c b/src/utils_subst.c
new file mode 100644 (file)
index 0000000..a49f6db
--- /dev/null
@@ -0,0 +1,144 @@
+/**
+ * collectd - src/utils_subst.c
+ * Copyright (C) 2008  Sebastian Harl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Sebastian "tokkee" Harl <sh at tokkee.org>
+ **/
+
+/*
+ * This module provides functions for string substitution.
+ */
+
+#include "collectd.h"
+#include "common.h"
+
+char *subst (char *buf, size_t buflen, const char *string, int off1, int off2,
+               const char *replacement)
+{
+       char  *buf_ptr = buf;
+       size_t len     = buflen;
+
+       if ((NULL == buf) || (0 >= buflen) || (NULL == string)
+                       || (0 > off1) || (0 > off2) || (off1 > off2)
+                       || (NULL == replacement))
+               return NULL;
+
+       sstrncpy (buf_ptr, string,
+                       ((size_t)off1 + 1 > buflen) ? buflen : (size_t)off1 + 1);
+       buf_ptr += off1;
+       len     -= off1;
+
+       if (0 >= len)
+               return buf;
+
+       sstrncpy (buf_ptr, replacement, len);
+       buf_ptr += strlen (replacement);
+       len     -= strlen (replacement);
+
+       if (0 >= len)
+               return buf;
+
+       sstrncpy (buf_ptr, string + off2, len);
+       return buf;
+} /* subst */
+
+char *asubst (const char *string, int off1, int off2, const char *replacement)
+{
+       char *buf;
+       int   len;
+
+       char *ret;
+
+       if ((NULL == string) || (0 > off1) || (0 > off2) || (off1 > off2)
+                       || (NULL ==replacement))
+               return NULL;
+
+       len = off1 + strlen (replacement) + strlen (string) - off2 + 1;
+
+       buf = (char *)malloc (len);
+       if (NULL == buf)
+               return NULL;
+
+       ret = subst (buf, len, string, off1, off2, replacement);
+       if (NULL == ret)
+               free (buf);
+       return ret;
+} /* asubst */
+
+char *subst_string (char *buf, size_t buflen, const char *string,
+               const char *needle, const char *replacement)
+{
+       char *temp;
+       size_t needle_len;
+       size_t i;
+
+       if ((buf == NULL) || (string == NULL)
+                       || (needle == NULL) || (replacement == NULL))
+               return (NULL);
+
+       temp = (char *) malloc (buflen);
+       if (temp == NULL)
+       {
+               ERROR ("subst_string: malloc failed.");
+               return (NULL);
+       }
+
+       needle_len = strlen (needle);
+       strncpy (buf, string, buflen);
+
+       /* Limit the loop to prevent endless loops. */
+       for (i = 0; i < buflen; i++)
+       {
+               char *begin_ptr;
+               size_t begin;
+
+               /* Find `needle' in `buf'. */
+               begin_ptr = strstr (buf, needle);
+               if (begin_ptr == NULL)
+                       break;
+
+               /* Calculate the start offset. */
+               begin = begin_ptr - buf;
+
+               /* Substitute the region using `subst'. The result is stored in
+                * `temp'. */
+               begin_ptr = subst (temp, buflen, buf,
+                               begin, begin + needle_len,
+                               replacement);
+               if (begin_ptr == NULL)
+               {
+                       WARNING ("subst_string: subst failed.");
+                       break;
+               }
+
+               /* Copy the new string in `temp' to `buf' for the next round. */
+               strncpy (buf, temp, buflen);
+       }
+
+       if (i >= buflen)
+       {
+               WARNING ("subst_string: Loop exited after %zu iterations: "
+                               "string = %s; needle = %s; replacement = %s;",
+                               i, string, needle, replacement);
+       }
+
+       sfree (temp);
+       return (buf);
+} /* char *subst_string */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
+
diff --git a/src/utils_subst.h b/src/utils_subst.h
new file mode 100644 (file)
index 0000000..4387b85
--- /dev/null
@@ -0,0 +1,100 @@
+/**
+ * collectd - src/utils_subst.h
+ * Copyright (C) 2008  Sebastian Harl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Sebastian "tokkee" Harl <sh at tokkee.org>
+ **/
+
+/*
+ * This module provides functions for string substitution.
+ */
+
+#ifndef UTILS_SUBST_H
+#define UTILS_SUBST_H 1
+
+#include <stddef.h>
+
+/*
+ * subst:
+ *
+ * Replace a substring of a string with the specified replacement text. The
+ * resulting string is stored in the buffer pointed to by 'buf' of length
+ * 'buflen'. Upon success, the buffer will always be null-terminated. The
+ * result may be truncated if the buffer is too small.
+ *
+ * The substring to be replaces is identified by the two offsets 'off1' and
+ * 'off2' where 'off1' specifies the offset to the beginning of the substring
+ * and 'off2' specifies the offset to the first byte after the substring.
+ *
+ * The minimum buffer size to store the complete return value (including the
+ * terminating '\0' character) thus has to be:
+ * off1 + strlen(replacement) + strlen(string) - off2 + 1
+ *
+ * Example:
+ *
+ *             01234567890
+ *   string = "foo_____bar"
+ *                ^    ^
+ *                |    |
+ *              off1  off2
+ *
+ *   off1 = 3
+ *   off2 = 8
+ *
+ *   replacement = " - "
+ *
+ *   -> "foo - bar"
+ *
+ * The function returns 'buf' on success, NULL else.
+ */
+char *subst (char *buf, size_t buflen, const char *string, int off1, int off2,
+               const char *replacement);
+
+/*
+ * asubst:
+ *
+ * This function is very similar to subst(). It differs in that it
+ * automatically allocates the memory required for the return value which the
+ * user then has to free himself.
+ *
+ * Returns the newly allocated result string on success, NULL else.
+ */
+char *asubst (const char *string, int off1, int off2, const char *replacement);
+
+/*
+ * subst_string:
+ *
+ * Works like `subst', but instead of specifying start and end offsets you
+ * specify `needle', the string that is to be replaced. If `needle' is found
+ * in `string' (using strstr(3)), the offset is calculated and `subst' is
+ * called with the determined parameters.
+ *
+ * If the substring is not found, no error will be indicated and
+ * `subst_string' works mostly like `strncpy'.
+ *
+ * If the substring appears multiple times, all appearances will be replaced.
+ * If the substring has been found `buflen' times, an endless loop is assumed
+ * and the loop is broken. A warning is printed and the function returns
+ * success.
+ */
+char *subst_string (char *buf, size_t buflen, const char *string,
+               const char *needle, const char *replacement);
+
+#endif /* UTILS_SUBST_H */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
+
diff --git a/src/utils_tail.c b/src/utils_tail.c
new file mode 100644 (file)
index 0000000..5b7551d
--- /dev/null
@@ -0,0 +1,254 @@
+/**
+ * collectd - src/utils_tail.c
+ * Copyright (C) 2007-2008  C-Ware, Inc.
+ * Copyright (C) 2008  Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Luke Heberling <lukeh at c-ware.com>
+ *   Florian Forster <octo at verplant.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;
+  int status;
+
+  memset (&stat_buf, 0, sizeof (stat_buf));
+  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 = (cu_tail_t *) malloc (sizeof (cu_tail_t));
+       if (obj == NULL)
+               return (NULL);
+       memset (obj, '\0', sizeof (cu_tail_t));
+
+       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';
+               }
+
+               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..c479319
--- /dev/null
@@ -0,0 +1,83 @@
+/**
+ * collectd - src/utils_tail.h
+ * Copyright (C) 2007-2008  C-Ware, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * 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..8ae2208
--- /dev/null
@@ -0,0 +1,263 @@
+/*
+ * collectd - src/utils_tail_match.c
+ * Copyright (C) 2007-2008  C-Ware, Inc.
+ * Copyright (C) 2008       Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Luke Heberling <lukeh at c-ware.com>
+ *   Florian Forster <octo at verplant.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];
+};
+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;
+
+  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));
+
+  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;
+  size_t i;
+
+  for (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 = (cu_tail_match_t *) malloc (sizeof (cu_tail_match_t));
+  if (obj == NULL)
+    return (NULL);
+  memset (obj, '\0', sizeof (cu_tail_match_t));
+
+  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)
+{
+  size_t i;
+
+  if (obj == NULL)
+    return;
+
+  if (obj->tail != NULL)
+  {
+    cu_tail_destroy (obj->tail);
+    obj->tail = NULL;
+  }
+
+  for (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 = (cu_tail_match_match_t *) realloc (obj->matches,
+      sizeof (cu_tail_match_match_t) * (obj->matches_num + 1));
+  if (temp == NULL)
+    return (-1);
+
+  obj->matches = temp;
+  obj->matches_num++;
+
+  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)
+{
+  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 = (cu_tail_match_simple_t *) malloc (sizeof (cu_tail_match_simple_t));
+  if (user_data == NULL)
+  {
+    match_destroy (match);
+    return (-1);
+  }
+  memset (user_data, '\0', sizeof (cu_tail_match_simple_t));
+
+  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));
+
+  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;
+  size_t i;
+
+  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 (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..7659745
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * collectd - src/utils_tail_match.h
+ * Copyright (C) 2007-2008  C-Ware, Inc.
+ * Copyright (C) 2008       Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Luke Heberling <lukeh at c-ware.com>
+ *   Florian Forster <octo at verplant.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);
+
+/*
+ * 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 : */
diff --git a/src/utils_time.c b/src/utils_time.c
new file mode 100644 (file)
index 0000000..aac6135
--- /dev/null
@@ -0,0 +1,64 @@
+/**
+ * collectd - src/utils_time.h
+ * Copyright (C) 2010  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <ff at octo.it>
+ **/
+
+#include "collectd.h"
+#include "utils_time.h"
+#include "plugin.h"
+#include "common.h"
+
+#if HAVE_CLOCK_GETTIME
+cdtime_t cdtime (void) /* {{{ */
+{
+  int status;
+  struct timespec ts = { 0, 0 };
+
+  status = clock_gettime (CLOCK_REALTIME, &ts);
+  if (status != 0)
+  {
+    char errbuf[1024];
+    ERROR ("cdtime: clock_gettime failed: %s",
+        sstrerror (errno, errbuf, sizeof (errbuf)));
+    return (0);
+  }
+
+  return (TIMESPEC_TO_CDTIME_T (&ts));
+} /* }}} cdtime_t cdtime */
+#else
+/* Work around for Mac OS X which doesn't have clock_gettime(2). *sigh* */
+cdtime_t cdtime (void) /* {{{ */
+{
+  int status;
+  struct timeval tv = { 0, 0 };
+
+  status = gettimeofday (&tv, /* struct timezone = */ NULL);
+  if (status != 0)
+  {
+    char errbuf[1024];
+    ERROR ("cdtime: gettimeofday failed: %s",
+        sstrerror (errno, errbuf, sizeof (errbuf)));
+    return (0);
+  }
+
+  return (TIMEVAL_TO_CDTIME_T (&tv));
+} /* }}} cdtime_t cdtime */
+#endif
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/utils_time.h b/src/utils_time.h
new file mode 100644 (file)
index 0000000..0fd809a
--- /dev/null
@@ -0,0 +1,70 @@
+/**
+ * collectd - src/utils_time.h
+ * Copyright (C) 2010  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <ff at octo.it>
+ **/
+
+#ifndef UTILS_TIME_H
+#define UTILS_TIME_H 1
+
+#include "collectd.h"
+
+/*
+ * "cdtime_t" is a 64bit unsigned integer. The time is stored at a 2^-30 second
+ * resolution, i.e. the most significant 34 bit are used to store the time in
+ * seconds, the least significant bits store the sub-second part in something
+ * very close to nanoseconds. *The* big advantage of storing time in this
+ * manner is that comparing times and calculating differences is as simple as
+ * it is with "time_t", i.e. a simple integer comparison / subtraction works.
+ */
+/* 
+ * cdtime_t is defined in "collectd.h" */
+/* typedef uint64_t cdtime_t; */
+
+/* 2^30 = 1073741824 */
+#define TIME_T_TO_CDTIME_T(t) (((cdtime_t) (t)) * 1073741824)
+#define CDTIME_T_TO_TIME_T(t) ((time_t) ((t) / 1073741824))
+
+#define CDTIME_T_TO_DOUBLE(t) (((double) (t)) / 1073741824.0)
+#define DOUBLE_TO_CDTIME_T(d) ((cdtime_t) ((d) * 1073741824.0))
+
+#define MS_TO_CDTIME_T(ms) ((cdtime_t)    (((double) (ms)) * 1073741.824))
+#define CDTIME_T_TO_MS(t)  ((long)        (((double) (t))  / 1073741.824))
+#define US_TO_CDTIME_T(us) ((cdtime_t)    (((double) (us)) * 1073.741824))
+#define CDTIME_T_TO_US(t)  ((suseconds_t) (((double) (t))  / 1073.741824))
+#define NS_TO_CDTIME_T(ns) ((cdtime_t)    (((double) (ns)) * 1.073741824))
+#define CDTIME_T_TO_NS(t)  ((long)        (((double) (t))  / 1.073741824))
+
+#define CDTIME_T_TO_TIMEVAL(cdt,tvp) do {                                    \
+        (tvp)->tv_sec = CDTIME_T_TO_TIME_T (cdt);                            \
+        (tvp)->tv_usec = CDTIME_T_TO_US ((cdt) % 1073741824);                \
+} while (0)
+#define TIMEVAL_TO_CDTIME_T(tv) (TIME_T_TO_CDTIME_T ((tv)->tv_sec)           \
+    + US_TO_CDTIME_T ((tv)->tv_usec))
+
+#define CDTIME_T_TO_TIMESPEC(cdt,tsp) do {                                   \
+  (tsp)->tv_sec = CDTIME_T_TO_TIME_T (cdt);                                  \
+  (tsp)->tv_nsec = CDTIME_T_TO_NS ((cdt) % 1073741824);                      \
+} while (0)
+#define TIMESPEC_TO_CDTIME_T(ts) (TIME_T_TO_CDTIME_T ((ts)->tv_sec)           \
+    + NS_TO_CDTIME_T ((ts)->tv_nsec))
+
+cdtime_t cdtime (void);
+
+#endif /* UTILS_TIME_H */
+/* vim: set sw=2 sts=2 et : */
diff --git a/src/uuid.c b/src/uuid.c
new file mode 100644 (file)
index 0000000..cf23f5b
--- /dev/null
@@ -0,0 +1,288 @@
+/**
+ * collectd - src/uuid.c
+ * Copyright (C) 2007  Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Dan Berrange <berrange@redhat.com>
+ *   Richard W.M. Jones <rjones@redhat.com>
+ *
+ * Derived from UUID detection code by Dan Berrange <berrange@redhat.com>
+ * http://hg.et.redhat.com/virt/daemons/spectre--devel?f=f6e3a1b06433;file=lib/uuid.c
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "configfile.h"
+#include "plugin.h"
+
+#if HAVE_LIBHAL
+#include <libhal.h>
+#endif
+
+#define UUID_RAW_LENGTH 16
+#define UUID_PRINTABLE_COMPACT_LENGTH  (UUID_RAW_LENGTH * 2)
+#define UUID_PRINTABLE_NORMAL_LENGTH  (UUID_PRINTABLE_COMPACT_LENGTH + 4)
+
+#define HANDLE_PREFIX "Handle"
+#define SYSINFO_PREFIX "System Information"
+#define ALT_SYSINFO_PREFIX "\tSystem Information"
+#define UUID_PREFIX "\tUUID:"
+#define ALT_UUID_PREFIX "\t\tUUID:"
+
+static int
+looks_like_a_uuid (const char *uuid)
+{
+    int len;
+
+    if (!uuid) return 0;
+
+    len = strlen (uuid);
+
+    if (len < UUID_PRINTABLE_COMPACT_LENGTH)
+        return 0;
+
+    while (*uuid) {
+        if (!isxdigit ((int)*uuid) && *uuid != '-') return 0;
+        uuid++;
+    }
+    return 1;
+}
+
+static char *
+uuid_parse_dmidecode(FILE *file)
+{
+    char line[1024];
+    int inSysInfo = 0;
+
+    for (;;) {
+        if (!fgets(line, sizeof(line)/sizeof(char), file)) {
+            return NULL;
+        }
+        if (strncmp(line, HANDLE_PREFIX,
+                    (sizeof(HANDLE_PREFIX)/sizeof(char))-1) == 0) {
+            /*printf("Got handle %s\n", line);*/
+            inSysInfo = 0;
+        } else if (strncmp(line, SYSINFO_PREFIX,
+                           (sizeof(SYSINFO_PREFIX)/sizeof(char))-1) == 0) {
+            /*printf("Got system info %s\n", line);*/
+            inSysInfo = 1;
+        } else if (strncmp(line, ALT_SYSINFO_PREFIX,
+                           (sizeof(ALT_SYSINFO_PREFIX)/sizeof(char))-1) == 0) {
+            /*printf("Got alt system info %s\n", line);*/
+            inSysInfo = 1;
+        }
+        
+        if (inSysInfo) {
+            if (strncmp(line, UUID_PREFIX,
+                        (sizeof(UUID_PREFIX)/sizeof(char))-1) == 0) {
+                char *uuid = line + (sizeof(UUID_PREFIX)/sizeof(char));
+                /*printf("Got uuid [%s]\n", uuid);*/
+                if (looks_like_a_uuid (uuid))
+                    return strdup (uuid);
+            }
+            if (strncmp(line, ALT_UUID_PREFIX,
+                        (sizeof(ALT_UUID_PREFIX)/sizeof(char))-1) == 0) {
+                char *uuid = line + (sizeof(ALT_UUID_PREFIX)/sizeof(char));
+                /*printf("Got alt uuid [%s]\n", uuid);*/
+                if (looks_like_a_uuid (uuid))
+                    return strdup (uuid);
+            }
+        }
+    }
+    return NULL;
+}
+
+static char *
+uuid_get_from_dmidecode(void)
+{
+    FILE *dmidecode = popen("dmidecode 2>/dev/null", "r");
+    char *uuid;
+
+    if (!dmidecode) {
+        return NULL;
+    }
+    
+    uuid = uuid_parse_dmidecode(dmidecode);
+
+    pclose(dmidecode);
+    return uuid;
+}
+
+#if HAVE_LIBHAL
+
+#define UUID_PATH "/org/freedesktop/Hal/devices/computer"
+#define UUID_PROPERTY "smbios.system.uuid"
+
+static char *
+uuid_get_from_hal(void)
+{
+    LibHalContext *ctx;
+
+    DBusError error;
+    DBusConnection *con;
+
+    dbus_error_init(&error);
+
+    if (!(con = dbus_bus_get(DBUS_BUS_SYSTEM, &error)) ) {
+        goto bailout_nobus;
+    }
+
+    ctx = libhal_ctx_new();
+    libhal_ctx_set_dbus_connection(ctx, con);
+
+    if (!libhal_ctx_init(ctx, &error)) {
+        goto bailout;
+    }
+
+    if (!libhal_device_property_exists(ctx,
+                                       UUID_PATH,
+                                       UUID_PROPERTY,
+                                       &error)) {
+        goto bailout;
+    }
+
+    char *uuid  = libhal_device_get_property_string(ctx,
+                                                    UUID_PATH,
+                                                    UUID_PROPERTY,
+                                                    &error);
+    if (looks_like_a_uuid (uuid)) {
+        return uuid;
+    }
+
+ bailout:
+    {
+        DBusError ctxerror;
+        dbus_error_init(&ctxerror);
+        if (!(libhal_ctx_shutdown(ctx, &ctxerror))) {
+            dbus_error_free(&ctxerror);
+        }
+    }
+
+    libhal_ctx_free(ctx);
+    //dbus_connection_unref(con);
+
+ bailout_nobus:
+    if (dbus_error_is_set(&error)) {
+        /*printf("Error %s\n", error.name);*/
+        dbus_error_free(&error);
+    }
+    return NULL;
+}
+#endif
+
+static char *
+uuid_get_from_file(const char *path)
+{
+    FILE *file;
+    char uuid[UUID_PRINTABLE_NORMAL_LENGTH+1];
+
+    if (!(file = fopen(path, "r"))) {
+        return NULL;
+    }
+
+    if (!fgets(uuid, sizeof(uuid), file)) {
+        fclose(file);
+        return NULL;
+    }
+    fclose(file);
+
+    return strdup (uuid);
+}
+
+static char *uuidfile = NULL;
+
+static char *
+uuid_get_local(void)
+{
+    char *uuid;
+
+    /* Check /etc/uuid / UUIDFile before any other method. */
+    if ((uuid = uuid_get_from_file(uuidfile ? uuidfile : "/etc/uuid")) != NULL){
+        return uuid;
+    }
+
+#if HAVE_LIBHAL
+    if ((uuid = uuid_get_from_hal()) != NULL) {
+        return uuid;
+    }
+#endif
+
+    if ((uuid = uuid_get_from_dmidecode()) != NULL) {
+        return uuid;
+    }
+
+    if ((uuid = uuid_get_from_file("/sys/hypervisor/uuid")) != NULL) {
+        return uuid;
+    }
+
+    return NULL;
+}
+
+static const char *config_keys[] = {
+    "UUIDFile",
+    NULL
+};
+#define NR_CONFIG_KEYS ((sizeof config_keys / sizeof config_keys[0]) - 1)
+
+static int
+uuid_config (const char *key, const char *value)
+{
+    if (strcasecmp (key, "UUIDFile") == 0) {
+        if (uuidfile) {
+            ERROR ("UUIDFile given twice in configuration file");
+            return 1;
+        }
+        uuidfile = strdup (value);
+        return 0;
+    }
+    return 0;
+}
+
+static int
+uuid_init (void)
+{
+    char *uuid = uuid_get_local ();
+
+    if (uuid) {
+        sstrncpy (hostname_g, uuid, DATA_MAX_NAME_LEN);
+        sfree (uuid);
+        return 0;
+    }
+
+    WARNING ("uuid: could not read UUID using any known method");
+    return 0;
+}
+
+void module_register (void)
+{
+       plugin_register_config ("uuid", uuid_config,
+                            config_keys, NR_CONFIG_KEYS);
+       plugin_register_init ("uuid", uuid_init);
+}
+
+/*
+ * vim: set tabstop=4:
+ * vim: set shiftwidth=4:
+ * vim: set expandtab:
+ */
+/*
+ * Local variables:
+ *  indent-tabs-mode: nil
+ *  c-indent-level: 4
+ *  c-basic-offset: 4
+ *  tab-width: 4
+ * End:
+ */
diff --git a/src/varnish.c b/src/varnish.c
new file mode 100644 (file)
index 0000000..37fd4bb
--- /dev/null
@@ -0,0 +1,603 @@
+/**
+ * collectd - src/varnish.c
+ * Copyright (C) 2010 Jérôme Renard
+ * Copyright (C) 2010 Marc Fournier
+ * Copyright (C) 2010 Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Jérôme Renard <jerome.renard at gmail.com>
+ *   Marc Fournier <marc.fournier at camptocamp.com>
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+/**
+ * Current list of what is monitored and what is not monitored (yet)
+ * {{{
+ * Field name           Description                           Monitored
+ * ----------           -----------                           ---------
+ * uptime               Child uptime                              N
+ * client_conn          Client connections accepted               Y
+ * client_drop          Connection dropped, no sess               Y
+ * client_req           Client requests received                  Y
+ * cache_hit            Cache hits                                Y
+ * cache_hitpass        Cache hits for pass                       Y
+ * cache_miss           Cache misses                              Y
+ * backend_conn         Backend conn. success                     Y
+ * backend_unhealthy    Backend conn. not attempted               Y
+ * backend_busy         Backend conn. too many                    Y
+ * backend_fail         Backend conn. failures                    Y
+ * backend_reuse        Backend conn. reuses                      Y
+ * backend_toolate      Backend conn. was closed                  Y
+ * backend_recycle      Backend conn. recycles                    Y
+ * backend_unused       Backend conn. unused                      Y
+ * fetch_head           Fetch head                                Y
+ * fetch_length         Fetch with Length                         Y
+ * fetch_chunked        Fetch chunked                             Y
+ * fetch_eof            Fetch EOF                                 Y
+ * fetch_bad            Fetch had bad headers                     Y
+ * fetch_close          Fetch wanted close                        Y
+ * fetch_oldhttp        Fetch pre HTTP/1.1 closed                 Y
+ * fetch_zero           Fetch zero len                            Y
+ * fetch_failed         Fetch failed                              Y
+ * n_sess_mem           N struct sess_mem                         N
+ * n_sess               N struct sess                             N
+ * n_object             N struct object                           N
+ * n_vampireobject      N unresurrected objects                   N
+ * n_objectcore         N struct objectcore                       N
+ * n_objecthead         N struct objecthead                       N
+ * n_smf                N struct smf                              N
+ * n_smf_frag           N small free smf                          N
+ * n_smf_large          N large free smf                          N
+ * n_vbe_conn           N struct vbe_conn                         N
+ * n_wrk                N worker threads                          Y
+ * n_wrk_create         N worker threads created                  Y
+ * n_wrk_failed         N worker threads not created              Y
+ * n_wrk_max            N worker threads limited                  Y
+ * n_wrk_queue          N queued work requests                    Y
+ * n_wrk_overflow       N overflowed work requests                Y
+ * n_wrk_drop           N dropped work requests                   Y
+ * n_backend            N backends                                N
+ * n_expired            N expired objects                         N
+ * n_lru_nuked          N LRU nuked objects                       N
+ * n_lru_saved          N LRU saved objects                       N
+ * n_lru_moved          N LRU moved objects                       N
+ * n_deathrow           N objects on deathrow                     N
+ * losthdr              HTTP header overflows                     N
+ * n_objsendfile        Objects sent with sendfile                N
+ * n_objwrite           Objects sent with write                   N
+ * n_objoverflow        Objects overflowing workspace             N
+ * s_sess               Total Sessions                            Y
+ * s_req                Total Requests                            Y
+ * s_pipe               Total pipe                                Y
+ * s_pass               Total pass                                Y
+ * s_fetch              Total fetch                               Y
+ * s_hdrbytes           Total header bytes                        Y
+ * s_bodybytes          Total body bytes                          Y
+ * sess_closed          Session Closed                            N
+ * sess_pipeline        Session Pipeline                          N
+ * sess_readahead       Session Read Ahead                        N
+ * sess_linger          Session Linger                            N
+ * sess_herd            Session herd                              N
+ * shm_records          SHM records                               Y
+ * shm_writes           SHM writes                                Y
+ * shm_flushes          SHM flushes due to overflow               Y
+ * shm_cont             SHM MTX contention                        Y
+ * shm_cycles           SHM cycles through buffer                 Y
+ * sm_nreq              allocator requests                        Y
+ * sm_nobj              outstanding allocations                   Y
+ * sm_balloc            bytes allocated                           Y
+ * sm_bfree             bytes free                                Y
+ * sma_nreq             SMA allocator requests                    Y
+ * sma_nobj             SMA outstanding allocations               Y
+ * sma_nbytes           SMA outstanding bytes                     Y
+ * sma_balloc           SMA bytes allocated                       Y
+ * sma_bfree            SMA bytes free                            Y
+ * sms_nreq             SMS allocator requests                    Y
+ * sms_nobj             SMS outstanding allocations               Y
+ * sms_nbytes           SMS outstanding bytes                     Y
+ * sms_balloc           SMS bytes allocated                       Y
+ * sms_bfree            SMS bytes freed                           Y
+ * backend_req          Backend requests made                     N
+ * n_vcl                N vcl total                               N
+ * n_vcl_avail          N vcl available                           N
+ * n_vcl_discard        N vcl discarded                           N
+ * n_purge              N total active purges                     N
+ * n_purge_add          N new purges added                        N
+ * n_purge_retire       N old purges deleted                      N
+ * n_purge_obj_test     N objects tested                          N
+ * n_purge_re_test      N regexps tested against                  N
+ * n_purge_dups         N duplicate purges removed                N
+ * hcb_nolock           HCB Lookups without lock                  Y
+ * hcb_lock             HCB Lookups with lock                     Y
+ * hcb_insert           HCB Inserts                               Y
+ * esi_parse            Objects ESI parsed (unlock)               Y
+ * esi_errors           ESI parse errors (unlock)                 Y
+ * }}}
+ */
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+
+#include <varnish/varnishapi.h>
+
+/* {{{ user_config_s */
+struct user_config_s {
+       char *instance;
+
+       _Bool collect_cache;
+       _Bool collect_connections;
+       _Bool collect_esi;
+       _Bool collect_backend;
+       _Bool collect_fetch;
+       _Bool collect_hcb;
+       _Bool collect_shm;
+       _Bool collect_sma;
+       _Bool collect_sms;
+       _Bool collect_sm;
+       _Bool collect_totals;
+       _Bool collect_workers;
+};
+typedef struct user_config_s user_config_t; /* }}} */
+
+static _Bool have_instance = 0;
+
+static int varnish_submit (const char *plugin_instance, /* {{{ */
+               const char *category, const char *type, const char *type_instance, 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, "varnish", sizeof (vl.plugin));
+
+       if (plugin_instance == NULL)
+               plugin_instance = "default";
+
+       ssnprintf (vl.plugin_instance, sizeof (vl.plugin_instance),
+               "%s-%s", plugin_instance, category);
+
+       sstrncpy (vl.type, type, sizeof (vl.type));
+
+       if (type_instance != NULL)
+               sstrncpy (vl.type_instance, type_instance,
+                               sizeof (vl.type_instance));
+
+       return (plugin_dispatch_values (&vl));
+} /* }}} int varnish_submit */
+
+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));
+} /* }}} 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));
+} /* }}} int varnish_submit_derive */
+
+static void varnish_monitor (const user_config_t *conf, struct varnish_stats *VSL_stats) /* {{{ */
+{
+       if (conf->collect_cache)
+       {
+               /* Cache hits */
+               varnish_submit_derive (conf->instance, "cache", "cache_result", "hit",     VSL_stats->cache_hit);
+               /* Cache misses */
+               varnish_submit_derive (conf->instance, "cache", "cache_result", "miss",    VSL_stats->cache_miss);
+               /* Cache hits for pass */
+               varnish_submit_derive (conf->instance, "cache", "cache_result", "hitpass", VSL_stats->cache_hitpass);
+       }
+
+       if (conf->collect_connections)
+       {
+               /* Client connections accepted */
+               varnish_submit_derive (conf->instance, "connections", "connections", "accepted", VSL_stats->client_conn);
+               /* Connection dropped, no sess */
+               varnish_submit_derive (conf->instance, "connections", "connections", "dropped" , VSL_stats->client_drop);
+               /* Client requests received    */
+               varnish_submit_derive (conf->instance, "connections", "connections", "received", VSL_stats->client_req);
+       }
+
+       if (conf->collect_esi)
+       {
+               /* Objects ESI parsed (unlock) */
+               varnish_submit_derive (conf->instance, "esi", "total_operations", "parsed", VSL_stats->esi_parse);
+               /* ESI parse errors (unlock)   */
+               varnish_submit_derive (conf->instance, "esi", "total_operations", "error",  VSL_stats->esi_errors);
+       }
+
+       if (conf->collect_backend)
+       {
+               /* Backend conn. success       */
+               varnish_submit_derive (conf->instance, "backend", "connections", "success"      , VSL_stats->backend_conn);
+               /* Backend conn. not attempted */
+               varnish_submit_derive (conf->instance, "backend", "connections", "not-attempted", VSL_stats->backend_unhealthy);
+               /* Backend conn. too many      */
+               varnish_submit_derive (conf->instance, "backend", "connections", "too-many"     , VSL_stats->backend_busy);
+               /* Backend conn. failures      */
+               varnish_submit_derive (conf->instance, "backend", "connections", "failures"     , VSL_stats->backend_fail);
+               /* Backend conn. reuses        */
+               varnish_submit_derive (conf->instance, "backend", "connections", "reuses"       , VSL_stats->backend_reuse);
+               /* Backend conn. was closed    */
+               varnish_submit_derive (conf->instance, "backend", "connections", "was-closed"   , VSL_stats->backend_toolate);
+               /* Backend conn. recycles      */
+               varnish_submit_derive (conf->instance, "backend", "connections", "recycled"     , VSL_stats->backend_recycle);
+               /* Backend conn. unused        */
+               varnish_submit_derive (conf->instance, "backend", "connections", "unused"       , VSL_stats->backend_unused);
+       }
+
+       if (conf->collect_fetch)
+       {
+               /* Fetch head                */
+               varnish_submit_derive (conf->instance, "fetch", "http_requests", "head"       , VSL_stats->fetch_head);
+               /* Fetch with length         */
+               varnish_submit_derive (conf->instance, "fetch", "http_requests", "length"     , VSL_stats->fetch_length);
+               /* Fetch chunked             */
+               varnish_submit_derive (conf->instance, "fetch", "http_requests", "chunked"    , VSL_stats->fetch_chunked);
+               /* Fetch EOF                 */
+               varnish_submit_derive (conf->instance, "fetch", "http_requests", "eof"        , VSL_stats->fetch_eof);
+               /* Fetch bad headers         */
+               varnish_submit_derive (conf->instance, "fetch", "http_requests", "bad_headers", VSL_stats->fetch_bad);
+               /* Fetch wanted close        */
+               varnish_submit_derive (conf->instance, "fetch", "http_requests", "close"      , VSL_stats->fetch_close);
+               /* Fetch pre HTTP/1.1 closed */
+               varnish_submit_derive (conf->instance, "fetch", "http_requests", "oldhttp"    , VSL_stats->fetch_oldhttp);
+               /* Fetch zero len            */
+               varnish_submit_derive (conf->instance, "fetch", "http_requests", "zero"       , VSL_stats->fetch_zero);
+               /* Fetch failed              */
+               varnish_submit_derive (conf->instance, "fetch", "http_requests", "failed"     , VSL_stats->fetch_failed);
+       }
+
+       if (conf->collect_hcb)
+       {
+               /* HCB Lookups without lock */
+               varnish_submit_derive (conf->instance, "hcb", "cache_operation", "lookup_nolock", VSL_stats->hcb_nolock);
+               /* HCB Lookups with lock    */
+               varnish_submit_derive (conf->instance, "hcb", "cache_operation", "lookup_lock",   VSL_stats->hcb_lock);
+               /* HCB Inserts              */
+               varnish_submit_derive (conf->instance, "hcb", "cache_operation", "insert",        VSL_stats->hcb_insert);
+       }
+
+       if (conf->collect_shm)
+       {
+               /* SHM records                 */
+               varnish_submit_derive (conf->instance, "shm", "total_operations", "records"   , VSL_stats->shm_records);
+               /* SHM writes                  */
+               varnish_submit_derive (conf->instance, "shm", "total_operations", "writes"    , VSL_stats->shm_writes);
+               /* SHM flushes due to overflow */
+               varnish_submit_derive (conf->instance, "shm", "total_operations", "flushes"   , VSL_stats->shm_flushes);
+               /* SHM MTX contention          */
+               varnish_submit_derive (conf->instance, "shm", "total_operations", "contention", VSL_stats->shm_cont);
+               /* SHM cycles through buffer   */
+               varnish_submit_derive (conf->instance, "shm", "total_operations", "cycles"    , VSL_stats->shm_cycles);
+       }
+
+       if (conf->collect_sm)
+       {
+               /* allocator requests */
+               varnish_submit_derive (conf->instance, "sm", "total_requests", "nreq",  VSL_stats->sm_nreq);
+               /* outstanding allocations */
+               varnish_submit_gauge (conf->instance,  "sm", "requests", "outstanding", VSL_stats->sm_nobj);
+               /* bytes allocated */
+               varnish_submit_derive (conf->instance,  "sm", "total_bytes", "allocated",      VSL_stats->sm_balloc);
+               /* bytes free */
+               varnish_submit_derive (conf->instance,  "sm", "total_bytes", "free",           VSL_stats->sm_bfree);
+       }
+
+       if (conf->collect_sma)
+       {
+               /* SMA allocator requests */
+               varnish_submit_derive (conf->instance, "sma", "total_requests", "nreq",  VSL_stats->sma_nreq);
+               /* SMA outstanding allocations */
+               varnish_submit_gauge (conf->instance,  "sma", "requests", "outstanding", VSL_stats->sma_nobj);
+               /* SMA outstanding bytes */
+               varnish_submit_gauge (conf->instance,  "sma", "bytes", "outstanding",    VSL_stats->sma_nbytes);
+               /* SMA bytes allocated */
+               varnish_submit_derive (conf->instance,  "sma", "total_bytes", "allocated",      VSL_stats->sma_balloc);
+               /* SMA bytes free */
+               varnish_submit_derive (conf->instance,  "sma", "total_bytes", "free" ,          VSL_stats->sma_bfree);
+       }
+
+       if (conf->collect_sms)
+       {
+               /* SMS allocator requests */
+               varnish_submit_derive (conf->instance, "sms", "total_requests", "allocator", VSL_stats->sms_nreq);
+               /* SMS outstanding allocations */
+               varnish_submit_gauge (conf->instance,  "sms", "requests", "outstanding",     VSL_stats->sms_nobj);
+               /* SMS outstanding bytes */
+               varnish_submit_gauge (conf->instance,  "sms", "bytes", "outstanding",        VSL_stats->sms_nbytes);
+               /* SMS bytes allocated */
+               varnish_submit_derive (conf->instance,  "sms", "total_bytes", "allocated",          VSL_stats->sms_balloc);
+               /* SMS bytes freed */
+               varnish_submit_derive (conf->instance,  "sms", "total_bytes", "free",               VSL_stats->sms_bfree);
+       }
+
+       if (conf->collect_totals)
+       {
+               /* Total Sessions */
+               varnish_submit_derive (conf->instance, "totals", "total_sessions", "sessions",  VSL_stats->s_sess);
+               /* Total Requests */
+               varnish_submit_derive (conf->instance, "totals", "total_requests", "requests",  VSL_stats->s_req);
+               /* Total pipe */
+               varnish_submit_derive (conf->instance, "totals", "total_operations", "pipe",    VSL_stats->s_pipe);
+               /* Total pass */
+               varnish_submit_derive (conf->instance, "totals", "total_operations", "pass",    VSL_stats->s_pass);
+               /* Total fetch */
+               varnish_submit_derive (conf->instance, "totals", "total_operations", "fetches", VSL_stats->s_fetch);
+               /* Total header bytes */
+               varnish_submit_derive (conf->instance, "totals", "total_bytes", "header-bytes", VSL_stats->s_hdrbytes);
+               /* Total body byte */
+               varnish_submit_derive (conf->instance, "totals", "total_bytes", "body-bytes",   VSL_stats->s_bodybytes);
+       }
+
+       if (conf->collect_workers)
+       {
+               /* worker threads */
+               varnish_submit_gauge (conf->instance, "workers", "threads", "worker",            VSL_stats->n_wrk);
+               /* worker threads created */
+               varnish_submit_derive (conf->instance, "workers", "total_threads", "created",     VSL_stats->n_wrk_create);
+               /* worker threads not created */
+               varnish_submit_derive (conf->instance, "workers", "total_threads", "failed",      VSL_stats->n_wrk_failed);
+               /* worker threads limited */
+               varnish_submit_derive (conf->instance, "workers", "total_threads", "limited",     VSL_stats->n_wrk_max);
+               /* queued work requests */
+               varnish_submit_derive (conf->instance, "workers", "total_requests", "queued",     VSL_stats->n_wrk_queue);
+               /* overflowed work requests */
+               varnish_submit_derive (conf->instance, "workers", "total_requests", "overflowed", VSL_stats->n_wrk_overflow);
+               /* dropped work requests */
+               varnish_submit_derive (conf->instance, "workers", "total_requests", "dropped",    VSL_stats->n_wrk_drop);
+       }
+} /* }}} void varnish_monitor */
+
+static int varnish_read (user_data_t *ud) /* {{{ */
+{
+       struct varnish_stats *VSL_stats;
+       user_config_t *conf;
+
+       if ((ud == NULL) || (ud->data == NULL))
+               return (EINVAL);
+
+       conf = ud->data;
+
+       VSL_stats = VSL_OpenStats (conf->instance);
+       if (VSL_stats == NULL)
+       {
+               ERROR ("Varnish plugin : unable to load statistics");
+
+               return (-1);
+       }
+
+       varnish_monitor (conf, VSL_stats);
+
+    return (0);
+} /* }}} */
+
+static void varnish_config_free (void *ptr) /* {{{ */
+{
+       user_config_t *conf = ptr;
+
+       if (conf == NULL)
+               return;
+
+       sfree (conf->instance);
+       sfree (conf);
+} /* }}} */
+
+static int varnish_config_apply_default (user_config_t *conf) /* {{{ */
+{
+       if (conf == NULL)
+               return (EINVAL);
+
+       conf->collect_backend     = 1;
+       conf->collect_cache       = 1;
+       conf->collect_connections = 1;
+       conf->collect_esi         = 0;
+       conf->collect_fetch       = 0;
+       conf->collect_hcb         = 0;
+       conf->collect_shm         = 1;
+       conf->collect_sm          = 0;
+       conf->collect_sma         = 0;
+       conf->collect_sms         = 0;
+       conf->collect_totals      = 0;
+       
+       return (0);
+} /* }}} int varnish_config_apply_default */
+
+static int varnish_init (void) /* {{{ */
+{
+       user_config_t *conf;
+       user_data_t ud;
+
+       if (have_instance)
+               return (0);
+
+       conf = malloc (sizeof (*conf));
+       if (conf == NULL)
+               return (ENOMEM);
+       memset (conf, 0, sizeof (*conf));
+
+       /* Default settings: */
+       conf->instance = NULL;
+
+       varnish_config_apply_default (conf);
+
+       ud.data = conf;
+       ud.free_func = varnish_config_free;
+
+       plugin_register_complex_read (/* group = */ "varnish",
+                       /* name      = */ "varnish/localhost",
+                       /* callback  = */ varnish_read,
+                       /* interval  = */ NULL,
+                       /* user data = */ &ud);
+
+       return (0);
+} /* }}} int varnish_init */
+
+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];
+       int i;
+
+       conf = malloc (sizeof (*conf));
+       if (conf == NULL)
+               return (ENOMEM);
+       memset (conf, 0, sizeof (*conf));
+       conf->instance = NULL;
+
+       varnish_config_apply_default (conf);
+
+       if (ci->values_num == 1)
+       {
+               int status;
+
+               status = cf_util_get_string (ci, &conf->instance);
+               if (status != 0)
+               {
+                       sfree (conf);
+                       return (status);
+               }
+               assert (conf->instance != NULL);
+
+               if (strcmp ("localhost", conf->instance) == 0)
+               {
+                       sfree (conf->instance);
+                       conf->instance = NULL;
+               }
+       }
+       else if (ci->values_num > 1)
+       {
+               WARNING ("Varnish plugin: \"Instance\" blocks accept only "
+                               "one argument.");
+               return (EINVAL);
+       }
+
+       for (i = 0; i < ci->children_num; i++)
+       {
+               oconfig_item_t *child = ci->children + i;
+
+               if (strcasecmp ("CollectCache", child->key) == 0)
+                       cf_util_get_boolean (child, &conf->collect_cache);
+               else if (strcasecmp ("CollectConnections", child->key) == 0)
+                       cf_util_get_boolean (child, &conf->collect_connections);
+               else if (strcasecmp ("CollectESI", child->key) == 0)
+                       cf_util_get_boolean (child, &conf->collect_esi);
+               else if (strcasecmp ("CollectBackend", child->key) == 0)
+                       cf_util_get_boolean (child, &conf->collect_backend);
+               else if (strcasecmp ("CollectFetch", child->key) == 0)
+                       cf_util_get_boolean (child, &conf->collect_fetch);
+               else if (strcasecmp ("CollectHCB", child->key) == 0)
+                       cf_util_get_boolean (child, &conf->collect_hcb);
+               else if (strcasecmp ("CollectSHM", child->key) == 0)
+                       cf_util_get_boolean (child, &conf->collect_shm);
+               else if (strcasecmp ("CollectSMA", child->key) == 0)
+                       cf_util_get_boolean (child, &conf->collect_sma);
+               else if (strcasecmp ("CollectSMS", child->key) == 0)
+                       cf_util_get_boolean (child, &conf->collect_sms);
+               else if (strcasecmp ("CollectSM", child->key) == 0)
+                       cf_util_get_boolean (child, &conf->collect_sm);
+               else if (strcasecmp ("CollectTotals", child->key) == 0)
+                       cf_util_get_boolean (child, &conf->collect_totals);
+               else if (strcasecmp ("CollectWorkers", child->key) == 0)
+                       cf_util_get_boolean (child, &conf->collect_workers);
+               else
+               {
+                       WARNING ("Varnish plugin: Ignoring unknown "
+                                       "configuration option: \"%s\"",
+                                       child->key);
+               }
+       }
+
+       if (!conf->collect_cache
+                       && !conf->collect_connections
+                       && !conf->collect_esi
+                       && !conf->collect_backend
+                       && !conf->collect_fetch
+                       && !conf->collect_hcb
+                       && !conf->collect_shm
+                       && !conf->collect_sma
+                       && !conf->collect_sms
+                       && !conf->collect_sm
+                       && !conf->collect_totals
+                       && !conf->collect_workers)
+       {
+               WARNING ("Varnish plugin: No metric has been configured for "
+                               "instance \"%s\". Disabling this instance.",
+                               (conf->instance == NULL) ? "localhost" : conf->instance);
+               return (EINVAL);
+       }
+
+       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  = */ NULL,
+                       /* user data = */ &ud);
+
+       have_instance = 1;
+
+       return (0);
+} /* }}} int varnish_config_instance */
+
+static int varnish_config (oconfig_item_t *ci) /* {{{ */
+{
+       int i;
+
+       for (i = 0; i < ci->children_num; i++)
+       {
+               oconfig_item_t *child = ci->children + i;
+
+               if (strcasecmp ("Instance", child->key) == 0)
+                       varnish_config_instance (child);
+               else
+               {
+                       WARNING ("Varnish plugin: Ignoring unknown "
+                                       "configuration option: \"%s\"",
+                                       child->key);
+               }
+       }
+
+       return (0);
+} /* }}} int varnish_config */
+
+void module_register (void) /* {{{ */
+{
+       plugin_register_complex_config ("varnish", varnish_config);
+       plugin_register_init ("varnish", varnish_init);
+} /* }}} */
+
+/* vim: set sw=8 noet fdm=marker : */
diff --git a/src/vmem.c b/src/vmem.c
new file mode 100644 (file)
index 0000000..56997bf
--- /dev/null
@@ -0,0 +1,282 @@
+/**
+ * collectd - src/vmem.c
+ * Copyright (C) 2008-2010  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at collectd.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#if KERNEL_LINUX
+static const char *config_keys[] =
+{
+  "Verbose"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static int verbose_output = 0;
+/* #endif KERNEL_LINUX */
+
+#else
+# error "No applicable input method."
+#endif /* HAVE_LIBSTATGRAB */
+
+static void submit (const char *plugin_instance, const char *type,
+    const char *type_instance, value_t *values, int 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, "vmem", sizeof (vl.plugin));
+  if (plugin_instance != NULL)
+    sstrncpy (vl.plugin_instance, plugin_instance, 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);
+} /* void vmem_submit */
+
+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];
+
+  values[0].derive = c0;
+  values[1].derive = c1;
+
+  submit (plugin_instance, type, type_instance, values, 2);
+} /* void submit_one */
+
+static void submit_one (const char *plugin_instance, const char *type,
+    const char *type_instance, value_t value)
+{
+  submit (plugin_instance, type, type_instance, &value, 1);
+} /* void submit_one */
+
+static int vmem_config (const char *key, const char *value)
+{
+  if (strcasecmp ("Verbose", key) == 0)
+  {
+    if (IS_TRUE (value))
+      verbose_output = 1;
+    else
+      verbose_output = 0;
+  }
+  else
+  {
+    return (-1);
+  }
+
+  return (0);
+} /* int vmem_config */
+
+static int vmem_read (void)
+{
+#if KERNEL_LINUX
+  derive_t pgpgin = 0;
+  derive_t pgpgout = 0;
+  int pgpgvalid = 0;
+
+  derive_t pswpin = 0;
+  derive_t pswpout = 0;
+  int pswpvalid = 0;
+
+  derive_t pgfault = 0;
+  derive_t pgmajfault = 0;
+  int pgfaultvalid = 0;
+
+  FILE *fh;
+  char buffer[1024];
+
+  fh = fopen ("/proc/vmstat", "r");
+  if (fh == NULL)
+  {
+    char errbuf[1024];
+    ERROR ("vmem plugin: fopen (/proc/vmstat) failed: %s",
+       sstrerror (errno, errbuf, sizeof (errbuf)));
+    return (-1);
+  }
+
+  while (fgets (buffer, sizeof (buffer), fh) != NULL)
+  {
+    char *fields[4];
+    int fields_num;
+    char *key;
+    char *endptr;
+    derive_t counter;
+    gauge_t gauge;
+
+    fields_num = strsplit (buffer, fields, STATIC_ARRAY_SIZE (fields));
+    if (fields_num != 2)
+      continue;
+
+    key = fields[0];
+
+    endptr = NULL;
+    counter = strtoll (fields[1], &endptr, 10);
+    if (fields[1] == endptr)
+      continue;
+
+    endptr = NULL;
+    gauge = strtod (fields[1], &endptr);
+    if (fields[1] == endptr)
+      continue;
+
+    /* 
+     * Number of pages
+     *
+     * The total number of {inst} pages, e. g dirty pages.
+     */
+    if (strncmp ("nr_", key, strlen ("nr_")) == 0)
+    {
+      char *inst = key + strlen ("nr_");
+      value_t value = { .gauge = gauge };
+      submit_one (NULL, "vmpage_number", inst, value);
+    }
+
+    /* 
+     * Page in and page outs. For memory and swap.
+     */
+    else if (strcmp ("pgpgin", key) == 0)
+    {
+      pgpgin = counter;
+      pgpgvalid |= 0x01;
+    }
+    else if (strcmp ("pgpgout", key) == 0)
+    {
+      pgpgout = counter;
+      pgpgvalid |= 0x02;
+    }
+    else if (strcmp ("pswpin", key) == 0)
+    {
+      pswpin = counter;
+      pswpvalid |= 0x01;
+    }
+    else if (strcmp ("pswpout", key) == 0)
+    {
+      pswpout = counter;
+      pswpvalid |= 0x02;
+    }
+
+    /*
+     * Pagefaults
+     */
+    else if (strcmp ("pgfault", key) == 0)
+    {
+      pgfault = counter;
+      pgfaultvalid |= 0x01;
+    }
+    else if (strcmp ("pgmajfault", key) == 0)
+    {
+      pgmajfault = counter;
+      pgfaultvalid |= 0x02;
+    }
+
+    /*
+     * Skip the other statistics if verbose output is disabled.
+     */
+    else if (verbose_output == 0)
+      continue;
+
+    /*
+     * Number of page allocations, refills, steals and scans. This is collected
+     * ``per zone'', i. e. for DMA, DMA32, normal and possibly highmem.
+     */
+    else if (strncmp ("pgalloc_", key, strlen ("pgalloc_")) == 0)
+    {
+      char *inst = key + strlen ("pgalloc_");
+      value_t value  = { .derive = counter };
+      submit_one (inst, "vmpage_action", "alloc", value);
+    }
+    else if (strncmp ("pgrefill_", key, strlen ("pgrefill_")) == 0)
+    {
+      char *inst = key + strlen ("pgrefill_");
+      value_t value  = { .derive = counter };
+      submit_one (inst, "vmpage_action", "refill", value);
+    }
+    else if (strncmp ("pgsteal_", key, strlen ("pgsteal_")) == 0)
+    {
+      char *inst = key + strlen ("pgsteal_");
+      value_t value  = { .derive = counter };
+      submit_one (inst, "vmpage_action", "steal", value);
+    }
+    else if (strncmp ("pgscan_kswapd_", key, strlen ("pgscan_kswapd_")) == 0)
+    {
+      char *inst = key + strlen ("pgscan_kswapd_");
+      value_t value  = { .derive = counter };
+      submit_one (inst, "vmpage_action", "scan_kswapd", value);
+    }
+    else if (strncmp ("pgscan_direct_", key, strlen ("pgscan_direct_")) == 0)
+    {
+      char *inst = key + strlen ("pgscan_direct_");
+      value_t value  = { .derive = counter };
+      submit_one (inst, "vmpage_action", "scan_direct", value);
+    }
+
+    /*
+     * Page action
+     *
+     * number of pages moved to the active or inactive lists and freed, i. e.
+     * removed from either list.
+     */
+    else if (strcmp ("pgfree", key) == 0)
+    {
+      value_t value  = { .derive = counter };
+      submit_one (NULL, "vmpage_action", "free", value);
+    }
+    else if (strcmp ("pgactivate", key) == 0)
+    {
+      value_t value  = { .derive = counter };
+      submit_one (NULL, "vmpage_action", "activate", value);
+    }
+    else if (strcmp ("pgdeactivate", key) == 0)
+    {
+      value_t value  = { .derive = counter };
+      submit_one (NULL, "vmpage_action", "deactivate", value);
+    }
+  } /* while (fgets) */
+
+  fclose (fh);
+  fh = NULL;
+
+  if (pgfaultvalid == 0x03)
+    submit_two (NULL, "vmpage_faults", NULL, pgfault, pgmajfault);
+
+  if (pgpgvalid == 0x03)
+    submit_two (NULL, "vmpage_io", "memory", pgpgin, pgpgout);
+
+  if (pswpvalid == 0x03)
+    submit_two (NULL, "vmpage_io", "swap", pswpin, pswpout);
+#endif /* KERNEL_LINUX */
+
+  return (0);
+} /* int vmem_read */
+
+void module_register (void)
+{
+  plugin_register_config ("vmem", vmem_config,
+      config_keys, config_keys_num);
+  plugin_register_read ("vmem", vmem_read);
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 ts=8 : */
diff --git a/src/vserver.c b/src/vserver.c
new file mode 100644 (file)
index 0000000..d80717c
--- /dev/null
@@ -0,0 +1,362 @@
+/**
+ * collectd - src/vserver.c
+ * Copyright (C) 2006,2007  Sebastian Harl
+ * Copyright (C) 2007-2010  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the license is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Sebastian Harl <sh at tokkee.org>
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include <dirent.h>
+#include <sys/types.h>
+
+#define BUFSIZE 512
+
+#define PROCDIR "/proc/virtual"
+
+#if !KERNEL_LINUX
+# error "No applicable input method."
+#endif
+
+static int pagesize = 0;
+
+static int vserver_init (void)
+{
+       /* XXX Should we check for getpagesize () in configure?
+        * What's the right thing to do, if there is no getpagesize ()? */
+       pagesize = getpagesize ();
+
+       return (0);
+} /* static void 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;
+
+       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));
+       sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
+
+       plugin_dispatch_values (&vl);
+} /* void traffic_submit */
+
+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;
+
+       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));
+
+       plugin_dispatch_values (&vl);
+}
+
+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));
+       sstrncpy (vl.plugin, "vserver", sizeof (vl.plugin));
+       sstrncpy (vl.plugin_instance, plugin_instance, sizeof (vl.plugin_instance));
+       sstrncpy (vl.type, type, sizeof (vl.type));
+       sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
+
+       plugin_dispatch_values (&vl);
+} /* void submit_gauge */
+
+static derive_t vserver_get_sock_bytes(const char *s)
+{
+       value_t v;
+       int status;
+
+       while (s[0] != '/')
+               ++s;
+
+       /* Remove '/' */
+       ++s;
+
+       status = parse_value (s, &v, DS_TYPE_DERIVE);
+       if (status != 0)
+               return (-1);
+       return (v.derive);
+}
+
+static int vserver_read (void)
+{
+#if NAME_MAX < 1024
+# define DIRENT_BUFFER_SIZE (sizeof (struct dirent) + 1024 + 1)
+#else
+# define DIRENT_BUFFER_SIZE (sizeof (struct dirent) + NAME_MAX + 1)
+#endif
+
+       DIR                     *proc;
+       struct dirent   *dent; /* 42 */
+       char dirent_buffer[DIRENT_BUFFER_SIZE];
+
+       errno = 0;
+       proc = opendir (PROCDIR);
+       if (proc == NULL)
+       {
+               char errbuf[1024];
+               ERROR ("vserver plugin: fopen (%s): %s", PROCDIR, 
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       while (42)
+       {
+               int len;
+               char file[BUFSIZE];
+
+               FILE *fh;
+               char buffer[BUFSIZE];
+
+               struct stat statbuf;
+               char *cols[4];
+
+               int status;
+
+               status = readdir_r (proc, (struct dirent *) dirent_buffer, &dent);
+               if (status != 0)
+               {
+                       char errbuf[4096];
+                       ERROR ("vserver plugin: readdir_r failed: %s",
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       closedir (proc);
+                       return (-1);
+               }
+               else if (dent == NULL)
+               {
+                       /* end of directory */
+                       break;
+               }
+
+               if (dent->d_name[0] == '.')
+                       continue;
+
+               len = ssnprintf (file, sizeof (file), PROCDIR "/%s", dent->d_name);
+               if ((len < 0) || (len >= BUFSIZE))
+                       continue;
+               
+               status = stat (file, &statbuf);
+               if (status != 0)
+               {
+                       char errbuf[4096];
+                       WARNING ("vserver plugin: stat (%s) failed: %s",
+                                       file, sstrerror (errno, errbuf, sizeof (errbuf)));
+                       continue;
+               }
+               
+               if (!S_ISDIR (statbuf.st_mode))
+                       continue;
+
+               /* socket message accounting */
+               len = ssnprintf (file, sizeof (file),
+                               PROCDIR "/%s/cacct", dent->d_name);
+               if ((len < 0) || ((size_t) len >= sizeof (file)))
+                       continue;
+
+               if (NULL == (fh = fopen (file, "r")))
+               {
+                       char errbuf[1024];
+                       ERROR ("Cannot open '%s': %s", file,
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+               }
+
+               while ((fh != NULL) && (NULL != fgets (buffer, BUFSIZE, fh)))
+               {
+                       derive_t rx;
+                       derive_t tx;
+                       char *type_instance;
+
+                       if (strsplit (buffer, cols, 4) < 4)
+                               continue;
+
+                       if (0 == strcmp (cols[0], "UNIX:"))
+                               type_instance = "unix";
+                       else if (0 == strcmp (cols[0], "INET:"))
+                               type_instance = "inet";
+                       else if (0 == strcmp (cols[0], "INET6:"))
+                               type_instance = "inet6";
+                       else if (0 == strcmp (cols[0], "OTHER:"))
+                               type_instance = "other";
+                       else if (0 == strcmp (cols[0], "UNSPEC:"))
+                               type_instance = "unspec";
+                       else
+                               continue;
+
+                       rx = vserver_get_sock_bytes (cols[1]);
+                       tx = vserver_get_sock_bytes (cols[2]);
+                       /* cols[3] == errors */
+
+                       traffic_submit (dent->d_name, type_instance, rx, tx);
+               } /* while (fgets) */
+
+               if (fh != NULL)
+               {
+                       fclose (fh);
+                       fh = NULL;
+               }
+
+               /* thread information and load */
+               len = ssnprintf (file, sizeof (file),
+                               PROCDIR "/%s/cvirt", dent->d_name);
+               if ((len < 0) || ((size_t) len >= sizeof (file)))
+                       continue;
+
+               if (NULL == (fh = fopen (file, "r")))
+               {
+                       char errbuf[1024];
+                       ERROR ("Cannot open '%s': %s", file,
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+               }
+
+               while ((fh != NULL) && (NULL != fgets (buffer, BUFSIZE, fh)))
+               {
+                       int n = strsplit (buffer, cols, 4);
+
+                       if (2 == n)
+                       {
+                               char   *type_instance;
+                               gauge_t value;
+
+                               if (0 == strcmp (cols[0], "nr_threads:"))
+                                       type_instance = "total";
+                               else if (0 == strcmp (cols[0], "nr_running:"))
+                                       type_instance = "running";
+                               else if (0 == strcmp (cols[0], "nr_unintr:"))
+                                       type_instance = "uninterruptable";
+                               else if (0 == strcmp (cols[0], "nr_onhold:"))
+                                       type_instance = "onhold";
+                               else
+                                       continue;
+
+                               value = atof (cols[1]);
+                               submit_gauge (dent->d_name, "vs_threads", type_instance, value);
+                       }
+                       else if (4 == n) {
+                               if (0 == strcmp (cols[0], "loadavg:"))
+                               {
+                                       gauge_t snum = atof (cols[1]);
+                                       gauge_t mnum = atof (cols[2]);
+                                       gauge_t lnum = atof (cols[3]);
+                                       load_submit (dent->d_name, snum, mnum, lnum);
+                               }
+                       }
+               } /* while (fgets) */
+
+               if (fh != NULL)
+               {
+                       fclose (fh);
+                       fh = NULL;
+               }
+
+               /* processes and memory usage */
+               len = ssnprintf (file, sizeof (file),
+                               PROCDIR "/%s/limit", dent->d_name);
+               if ((len < 0) || ((size_t) len >= sizeof (file)))
+                       continue;
+
+               if (NULL == (fh = fopen (file, "r")))
+               {
+                       char errbuf[1024];
+                       ERROR ("Cannot open '%s': %s", file,
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+               }
+
+               while ((fh != NULL) && (NULL != fgets (buffer, BUFSIZE, fh)))
+               {
+                       char *type = "vs_memory";
+                       char *type_instance;
+                       gauge_t value;
+
+                       if (strsplit (buffer, cols, 2) < 2)
+                               continue;
+
+                       if (0 == strcmp (cols[0], "PROC:"))
+                       {
+                               type = "vs_processes";
+                               type_instance = "";
+                               value = atof (cols[1]);
+                       }
+                       else
+                       {
+                               if (0 == strcmp (cols[0], "VM:"))
+                                       type_instance = "vm";
+                               else if (0 == strcmp (cols[0], "VML:"))
+                                       type_instance = "vml";
+                               else if (0 == strcmp (cols[0], "RSS:"))
+                                       type_instance = "rss";
+                               else if (0 == strcmp (cols[0], "ANON:"))
+                                       type_instance = "anon";
+                               else
+                                       continue;
+
+                               value = atof (cols[1]) * pagesize;
+                       }
+
+                       submit_gauge (dent->d_name, type, type_instance, value);
+               } /* while (fgets) */
+
+               if (fh != NULL)
+               {
+                       fclose (fh);
+                       fh = NULL;
+               }
+       } /* while (readdir) */
+
+       closedir (proc);
+
+       return (0);
+} /* int vserver_read */
+
+void module_register (void)
+{
+       plugin_register_init ("vserver", vserver_init);
+       plugin_register_read ("vserver", vserver_read);
+} /* void module_register(void) */
+
+/* vim: set ts=4 sw=4 noexpandtab : */
diff --git a/src/wireless.c b/src/wireless.c
new file mode 100644 (file)
index 0000000..f7ba735
--- /dev/null
@@ -0,0 +1,168 @@
+/**
+ * collectd - src/wireless.c
+ * Copyright (C) 2006,2007  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#if !KERNEL_LINUX
+# error "No applicable input method."
+#endif
+
+#define WIRELESS_PROC_FILE "/proc/net/wireless"
+
+#if 0
+static double wireless_dbm_to_watt (double dbm)
+{
+       double watt;
+
+       /*
+        * dbm = 10 * log_{10} (1000 * power / W)
+        * power = 10^(dbm/10) * W/1000 
+        */
+
+       watt = pow (10.0, (dbm / 10.0)) / 1000.0;
+
+       return (watt);
+}
+#endif
+
+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_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));
+
+       plugin_dispatch_values (&vl);
+} /* void wireless_submit */
+
+#define POWER_MIN -90.0
+#define POWER_MAX -50.0
+static double wireless_percent_to_power (double quality)
+{
+       assert ((quality >= 0.0) && (quality <= 100.0));
+
+       return ((quality * (POWER_MAX - POWER_MIN)) + POWER_MIN);
+} /* double wireless_percent_to_power */
+
+static int wireless_read (void)
+{
+#ifdef KERNEL_LINUX
+       FILE *fh;
+       char buffer[1024];
+
+       char   *device;
+       double  quality;
+       double  power;
+       double  noise;
+       
+       char *fields[8];
+       int   numfields;
+
+       int devices_found;
+       int len;
+
+       /* there are a variety of names for the wireless device */
+       if ((fh = fopen (WIRELESS_PROC_FILE, "r")) == NULL)
+       {
+               char errbuf[1024];
+               WARNING ("wireless: fopen: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+
+       devices_found = 0;
+       while (fgets (buffer, sizeof (buffer), fh) != NULL)
+       {
+               char *endptr;
+
+               numfields = strsplit (buffer, fields, 8);
+
+               if (numfields < 5)
+                       continue;
+
+               len = strlen (fields[0]) - 1;
+               if (len < 1)
+                       continue;
+               if (fields[0][len] != ':')
+                       continue;
+               fields[0][len] = '\0';
+
+               device  = fields[0];
+
+               quality = strtod (fields[2], &endptr);
+               if (fields[2] == endptr)
+                       quality = -1.0; /* invalid */
+
+               /* power [dBm] < 0.0 */
+               power = strtod (fields[3], &endptr);
+               if (fields[3] == endptr)
+                       power = 1.0; /* invalid */
+               else if ((power >= 0.0) && (power <= 100.0))
+                       power = wireless_percent_to_power (power);
+               else if ((power > 100.0) && (power <= 256.0))
+                       power = power - 256.0;
+               else if (power > 0.0)
+                       power = 1.0; /* invalid */
+
+               /* noise [dBm] < 0.0 */
+               noise = strtod (fields[4], &endptr);
+               if (fields[4] == endptr)
+                       noise = 1.0; /* invalid */
+               else if ((noise >= 0.0) && (noise <= 100.0))
+                       noise = wireless_percent_to_power (noise);
+               else if ((noise > 100.0) && (noise <= 256.0))
+                       noise = noise - 256.0;
+               else if (noise > 0.0)
+                       noise = 1.0; /* invalid */
+
+               wireless_submit (device, "signal_quality", quality);
+               wireless_submit (device, "signal_power", power);
+               wireless_submit (device, "signal_noise", noise);
+
+               devices_found++;
+       }
+
+       fclose (fh);
+
+       /* If no wireless devices are present return an error, so the plugin
+        * code delays our read function. */
+       if (devices_found == 0)
+               return (-1);
+#endif /* KERNEL_LINUX */
+
+       return (0);
+} /* int wireless_read */
+
+void module_register (void)
+{
+       plugin_register_read ("wireless", wireless_read);
+} /* void module_register */
diff --git a/src/write_http.c b/src/write_http.c
new file mode 100644 (file)
index 0000000..3035e43
--- /dev/null
@@ -0,0 +1,598 @@
+/**
+ * collectd - src/write_http.c
+ * Copyright (C) 2009       Paul Sadauskas
+ * Copyright (C) 2009       Doug MacEachern
+ * Copyright (C) 2007-2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ *   Doug MacEachern <dougm@hyperic.com>
+ *   Paul Sadauskas <psadauskas@gmail.com>
+ **/
+
+#include "collectd.h"
+#include "plugin.h"
+#include "common.h"
+#include "utils_cache.h"
+#include "utils_parse_option.h"
+#include "utils_format_json.h"
+
+#if HAVE_PTHREAD_H
+# include <pthread.h>
+#endif
+
+#include <curl/curl.h>
+
+/*
+ * Private variables
+ */
+struct wh_callback_s
+{
+        char *location;
+
+        char *user;
+        char *pass;
+        char *credentials;
+        int   verify_peer;
+        int   verify_host;
+        char *cacert;
+        int   store_rates;
+
+#define WH_FORMAT_COMMAND 0
+#define WH_FORMAT_JSON    1
+        int format;
+
+        CURL *curl;
+        char curl_errbuf[CURL_ERROR_SIZE];
+
+        char   send_buffer[4096];
+        size_t send_buffer_free;
+        size_t send_buffer_fill;
+        cdtime_t send_buffer_init_time;
+
+        pthread_mutex_t send_lock;
+};
+typedef struct wh_callback_s wh_callback_t;
+
+static void wh_reset_buffer (wh_callback_t *cb)  /* {{{ */
+{
+        memset (cb->send_buffer, 0, sizeof (cb->send_buffer));
+        cb->send_buffer_free = sizeof (cb->send_buffer);
+        cb->send_buffer_fill = 0;
+        cb->send_buffer_init_time = cdtime ();
+
+        if (cb->format == WH_FORMAT_JSON)
+        {
+                format_json_initialize (cb->send_buffer,
+                                &cb->send_buffer_fill,
+                                &cb->send_buffer_free);
+        }
+} /* }}} wh_reset_buffer */
+
+static int wh_send_buffer (wh_callback_t *cb) /* {{{ */
+{
+        int status = 0;
+
+        curl_easy_setopt (cb->curl, CURLOPT_POSTFIELDS, cb->send_buffer);
+        status = curl_easy_perform (cb->curl);
+        if (status != 0)
+        {
+                ERROR ("write_http plugin: curl_easy_perform failed with "
+                                "status %i: %s",
+                                status, cb->curl_errbuf);
+        }
+        return (status);
+} /* }}} wh_send_buffer */
+
+static int wh_callback_init (wh_callback_t *cb) /* {{{ */
+{
+        struct curl_slist *headers;
+
+        if (cb->curl != NULL)
+                return (0);
+
+        cb->curl = curl_easy_init ();
+        if (cb->curl == NULL)
+        {
+                ERROR ("curl plugin: curl_easy_init failed.");
+                return (-1);
+        }
+
+        curl_easy_setopt (cb->curl, CURLOPT_NOSIGNAL, 1);
+        curl_easy_setopt (cb->curl, CURLOPT_USERAGENT, PACKAGE_NAME"/"PACKAGE_VERSION);
+
+        headers = NULL;
+        headers = curl_slist_append (headers, "Accept:  */*");
+        if (cb->format == WH_FORMAT_JSON)
+                headers = curl_slist_append (headers, "Content-Type: application/json");
+        else
+                headers = curl_slist_append (headers, "Content-Type: text/plain");
+        headers = curl_slist_append (headers, "Expect:");
+        curl_easy_setopt (cb->curl, CURLOPT_HTTPHEADER, headers);
+
+        curl_easy_setopt (cb->curl, CURLOPT_ERRORBUFFER, cb->curl_errbuf);
+        curl_easy_setopt (cb->curl, CURLOPT_URL, cb->location);
+
+        if (cb->user != NULL)
+        {
+                size_t credentials_size;
+
+                credentials_size = strlen (cb->user) + 2;
+                if (cb->pass != NULL)
+                        credentials_size += strlen (cb->pass);
+
+                cb->credentials = (char *) malloc (credentials_size);
+                if (cb->credentials == NULL)
+                {
+                        ERROR ("curl plugin: malloc failed.");
+                        return (-1);
+                }
+
+                ssnprintf (cb->credentials, credentials_size, "%s:%s",
+                                cb->user, (cb->pass == NULL) ? "" : cb->pass);
+                curl_easy_setopt (cb->curl, CURLOPT_USERPWD, cb->credentials);
+                curl_easy_setopt (cb->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
+        }
+
+        curl_easy_setopt (cb->curl, CURLOPT_SSL_VERIFYPEER, cb->verify_peer);
+        curl_easy_setopt (cb->curl, CURLOPT_SSL_VERIFYHOST,
+                        cb->verify_host ? 2 : 0);
+        if (cb->cacert != NULL)
+                curl_easy_setopt (cb->curl, CURLOPT_CAINFO, cb->cacert);
+
+        wh_reset_buffer (cb);
+
+        return (0);
+} /* }}} int wh_callback_init */
+
+static int wh_flush_nolock (cdtime_t timeout, wh_callback_t *cb) /* {{{ */
+{
+        int status;
+
+        DEBUG ("write_http plugin: wh_flush_nolock: timeout = %.3f; "
+                        "send_buffer_fill = %zu;",
+                        CDTIME_T_TO_DOUBLE (timeout),
+                        cb->send_buffer_fill);
+
+        /* timeout == 0  => flush unconditionally */
+        if (timeout > 0)
+        {
+                cdtime_t now;
+
+                now = cdtime ();
+                if ((cb->send_buffer_init_time + timeout) > now)
+                        return (0);
+        }
+
+        if (cb->format == WH_FORMAT_COMMAND)
+        {
+                if (cb->send_buffer_fill <= 0)
+                {
+                        cb->send_buffer_init_time = cdtime ();
+                        return (0);
+                }
+
+                status = wh_send_buffer (cb);
+                wh_reset_buffer (cb);
+        }
+        else if (cb->format == WH_FORMAT_JSON)
+        {
+                if (cb->send_buffer_fill <= 2)
+                {
+                        cb->send_buffer_init_time = cdtime ();
+                        return (0);
+                }
+
+                status = format_json_finalize (cb->send_buffer,
+                                &cb->send_buffer_fill,
+                                &cb->send_buffer_free);
+                if (status != 0)
+                {
+                        ERROR ("write_http: wh_flush_nolock: "
+                                        "format_json_finalize failed.");
+                        wh_reset_buffer (cb);
+                        return (status);
+                }
+
+                status = wh_send_buffer (cb);
+                wh_reset_buffer (cb);
+        }
+        else
+        {
+                ERROR ("write_http: wh_flush_nolock: "
+                                "Unknown format: %i",
+                                cb->format);
+                return (-1);
+        }
+
+        return (status);
+} /* }}} wh_flush_nolock */
+
+static int wh_flush (cdtime_t timeout, /* {{{ */
+                const char *identifier __attribute__((unused)),
+                user_data_t *user_data)
+{
+        wh_callback_t *cb;
+        int status;
+
+        if (user_data == NULL)
+                return (-EINVAL);
+
+        cb = user_data->data;
+
+        pthread_mutex_lock (&cb->send_lock);
+
+        if (cb->curl == NULL)
+        {
+                status = wh_callback_init (cb);
+                if (status != 0)
+                {
+                        ERROR ("write_http plugin: wh_callback_init failed.");
+                        pthread_mutex_unlock (&cb->send_lock);
+                        return (-1);
+                }
+        }
+
+        status = wh_flush_nolock (timeout, cb);
+        pthread_mutex_unlock (&cb->send_lock);
+
+        return (status);
+} /* }}} int wh_flush */
+
+static void wh_callback_free (void *data) /* {{{ */
+{
+        wh_callback_t *cb;
+
+        if (data == NULL)
+                return;
+
+        cb = data;
+
+        wh_flush_nolock (/* timeout = */ 0, cb);
+
+        curl_easy_cleanup (cb->curl);
+        sfree (cb->location);
+        sfree (cb->user);
+        sfree (cb->pass);
+        sfree (cb->credentials);
+        sfree (cb->cacert);
+
+        sfree (cb);
+} /* }}} void wh_callback_free */
+
+static int wh_write_command (const data_set_t *ds, const value_list_t *vl, /* {{{ */
+                wh_callback_t *cb)
+{
+        char key[10*DATA_MAX_NAME_LEN];
+        char values[512];
+        char command[1024];
+        size_t command_len;
+
+        int status;
+
+        if (0 != strcmp (ds->type, vl->type)) {
+                ERROR ("write_http plugin: DS type does not match "
+                                "value list type");
+                return -1;
+        }
+
+        /* Copy the identifier to `key' and escape it. */
+        status = FORMAT_VL (key, sizeof (key), vl);
+        if (status != 0) {
+                ERROR ("write_http plugin: error with format_name");
+                return (status);
+        }
+        escape_string (key, sizeof (key));
+
+        /* Convert the values to an ASCII representation and put that into
+         * `values'. */
+        status = format_values (values, sizeof (values), ds, vl, cb->store_rates);
+        if (status != 0) {
+                ERROR ("write_http plugin: error with "
+                                "wh_value_list_to_string");
+                return (status);
+        }
+
+        command_len = (size_t) ssnprintf (command, sizeof (command),
+                        "PUTVAL %s interval=%.3f %s\r\n",
+                        key,
+                        CDTIME_T_TO_DOUBLE (vl->interval),
+                        values);
+        if (command_len >= sizeof (command)) {
+                ERROR ("write_http plugin: Command buffer too small: "
+                                "Need %zu bytes.", command_len + 1);
+                return (-1);
+        }
+
+        pthread_mutex_lock (&cb->send_lock);
+
+        if (cb->curl == NULL)
+        {
+                status = wh_callback_init (cb);
+                if (status != 0)
+                {
+                        ERROR ("write_http plugin: wh_callback_init failed.");
+                        pthread_mutex_unlock (&cb->send_lock);
+                        return (-1);
+                }
+        }
+
+        if (command_len >= cb->send_buffer_free)
+        {
+                status = wh_flush_nolock (/* timeout = */ 0, cb);
+                if (status != 0)
+                {
+                        pthread_mutex_unlock (&cb->send_lock);
+                        return (status);
+                }
+        }
+        assert (command_len < cb->send_buffer_free);
+
+        /* `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);
+        cb->send_buffer_fill += command_len;
+        cb->send_buffer_free -= command_len;
+
+        DEBUG ("write_http plugin: <%s> buffer %zu/%zu (%g%%) \"%s\"",
+                        cb->location,
+                        cb->send_buffer_fill, sizeof (cb->send_buffer),
+                        100.0 * ((double) cb->send_buffer_fill) / ((double) sizeof (cb->send_buffer)),
+                        command);
+
+        /* Check if we have enough space for this command. */
+        pthread_mutex_unlock (&cb->send_lock);
+
+        return (0);
+} /* }}} int wh_write_command */
+
+static int wh_write_json (const data_set_t *ds, const value_list_t *vl, /* {{{ */
+                wh_callback_t *cb)
+{
+        int status;
+
+        pthread_mutex_lock (&cb->send_lock);
+
+        if (cb->curl == NULL)
+        {
+                status = wh_callback_init (cb);
+                if (status != 0)
+                {
+                        ERROR ("write_http plugin: wh_callback_init failed.");
+                        pthread_mutex_unlock (&cb->send_lock);
+                        return (-1);
+                }
+        }
+
+        status = format_json_value_list (cb->send_buffer,
+                        &cb->send_buffer_fill,
+                        &cb->send_buffer_free,
+                        ds, vl, cb->store_rates);
+        if (status == (-ENOMEM))
+        {
+                status = wh_flush_nolock (/* timeout = */ 0, cb);
+                if (status != 0)
+                {
+                        wh_reset_buffer (cb);
+                        pthread_mutex_unlock (&cb->send_lock);
+                        return (status);
+                }
+
+                status = format_json_value_list (cb->send_buffer,
+                                &cb->send_buffer_fill,
+                                &cb->send_buffer_free,
+                                ds, vl, cb->store_rates);
+        }
+        if (status != 0)
+        {
+                pthread_mutex_unlock (&cb->send_lock);
+                return (status);
+        }
+
+        DEBUG ("write_http plugin: <%s> buffer %zu/%zu (%g%%)",
+                        cb->location,
+                        cb->send_buffer_fill, sizeof (cb->send_buffer),
+                        100.0 * ((double) cb->send_buffer_fill) / ((double) sizeof (cb->send_buffer)));
+
+        /* Check if we have enough space for this command. */
+        pthread_mutex_unlock (&cb->send_lock);
+
+        return (0);
+} /* }}} int wh_write_json */
+
+static int wh_write (const data_set_t *ds, const value_list_t *vl, /* {{{ */
+                user_data_t *user_data)
+{
+        wh_callback_t *cb;
+        int status;
+
+        if (user_data == NULL)
+                return (-EINVAL);
+
+        cb = user_data->data;
+
+        if (cb->format == WH_FORMAT_JSON)
+                status = wh_write_json (ds, vl, cb);
+        else
+                status = wh_write_command (ds, vl, cb);
+
+        return (status);
+} /* }}} int wh_write */
+
+static int config_set_string (char **ret_string, /* {{{ */
+                oconfig_item_t *ci)
+{
+        char *string;
+
+        if ((ci->values_num != 1)
+                        || (ci->values[0].type != OCONFIG_TYPE_STRING))
+        {
+                WARNING ("write_http plugin: The `%s' config option "
+                                "needs exactly one string argument.", ci->key);
+                return (-1);
+        }
+
+        string = strdup (ci->values[0].value.string);
+        if (string == NULL)
+        {
+                ERROR ("write_http plugin: strdup failed.");
+                return (-1);
+        }
+
+        if (*ret_string != NULL)
+                free (*ret_string);
+        *ret_string = string;
+
+        return (0);
+} /* }}} int config_set_string */
+
+static int config_set_boolean (int *dest, oconfig_item_t *ci) /* {{{ */
+{
+        if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN))
+        {
+                WARNING ("write_http plugin: The `%s' config option "
+                                "needs exactly one boolean argument.", ci->key);
+                return (-1);
+        }
+
+        *dest = ci->values[0].value.boolean ? 1 : 0;
+
+        return (0);
+} /* }}} int config_set_boolean */
+
+static int config_set_format (wh_callback_t *cb, /* {{{ */
+                oconfig_item_t *ci)
+{
+        char *string;
+
+        if ((ci->values_num != 1)
+                        || (ci->values[0].type != OCONFIG_TYPE_STRING))
+        {
+                WARNING ("write_http plugin: The `%s' config option "
+                                "needs exactly one string argument.", ci->key);
+                return (-1);
+        }
+
+        string = ci->values[0].value.string;
+        if (strcasecmp ("Command", string) == 0)
+                cb->format = WH_FORMAT_COMMAND;
+        else if (strcasecmp ("JSON", string) == 0)
+                cb->format = WH_FORMAT_JSON;
+        else
+        {
+                ERROR ("write_http plugin: Invalid format string: %s",
+                                string);
+                return (-1);
+        }
+
+        return (0);
+} /* }}} int config_set_string */
+
+static int wh_config_url (oconfig_item_t *ci) /* {{{ */
+{
+        wh_callback_t *cb;
+        user_data_t user_data;
+        int i;
+
+        cb = malloc (sizeof (*cb));
+        if (cb == NULL)
+        {
+                ERROR ("write_http plugin: malloc failed.");
+                return (-1);
+        }
+        memset (cb, 0, sizeof (*cb));
+        cb->location = NULL;
+        cb->user = NULL;
+        cb->pass = NULL;
+        cb->credentials = NULL;
+        cb->verify_peer = 1;
+        cb->verify_host = 1;
+        cb->cacert = NULL;
+        cb->format = WH_FORMAT_COMMAND;
+        cb->curl = NULL;
+
+        pthread_mutex_init (&cb->send_lock, /* attr = */ NULL);
+
+        config_set_string (&cb->location, ci);
+        if (cb->location == NULL)
+                return (-1);
+
+        for (i = 0; i < ci->children_num; i++)
+        {
+                oconfig_item_t *child = ci->children + i;
+
+                if (strcasecmp ("User", child->key) == 0)
+                        config_set_string (&cb->user, child);
+                else if (strcasecmp ("Password", child->key) == 0)
+                        config_set_string (&cb->pass, child);
+                else if (strcasecmp ("VerifyPeer", child->key) == 0)
+                        config_set_boolean (&cb->verify_peer, child);
+                else if (strcasecmp ("VerifyHost", child->key) == 0)
+                        config_set_boolean (&cb->verify_host, child);
+                else if (strcasecmp ("CACert", child->key) == 0)
+                        config_set_string (&cb->cacert, child);
+                else if (strcasecmp ("Format", child->key) == 0)
+                        config_set_format (cb, child);
+                else if (strcasecmp ("StoreRates", child->key) == 0)
+                        config_set_boolean (&cb->store_rates, child);
+                else
+                {
+                        ERROR ("write_http plugin: Invalid configuration "
+                                        "option: %s.", child->key);
+                }
+        }
+
+        DEBUG ("write_http: Registering write callback with URL %s",
+                        cb->location);
+
+        memset (&user_data, 0, sizeof (user_data));
+        user_data.data = cb;
+        user_data.free_func = NULL;
+        plugin_register_flush ("write_http", wh_flush, &user_data);
+
+        user_data.free_func = wh_callback_free;
+        plugin_register_write ("write_http", wh_write, &user_data);
+
+        return (0);
+} /* }}} int wh_config_url */
+
+static int wh_config (oconfig_item_t *ci) /* {{{ */
+{
+        int i;
+
+        for (i = 0; i < ci->children_num; i++)
+        {
+                oconfig_item_t *child = ci->children + i;
+
+                if (strcasecmp ("URL", child->key) == 0)
+                        wh_config_url (child);
+                else
+                {
+                        ERROR ("write_http plugin: Invalid configuration "
+                                        "option: %s.", child->key);
+                }
+        }
+
+        return (0);
+} /* }}} int wh_config */
+
+void module_register (void) /* {{{ */
+{
+        plugin_register_complex_config ("write_http", wh_config);
+} /* }}} void module_register */
+
+/* vim: set fdm=marker sw=8 ts=8 tw=78 et : */
diff --git a/src/write_mongodb.c b/src/write_mongodb.c
new file mode 100644 (file)
index 0000000..4deb24d
--- /dev/null
@@ -0,0 +1,248 @@
+/**
+ * collectd - src/write_mongodb.c
+ * Copyright (C) 2010  Florian Forster
+ * Copyright (C) 2010  Akkarit Sangpetch
+ * Copyright (C) 2012  Chris Lundquist
+ *
+ * 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 Forster <ff at octo.it>
+ *   Akkarit Sangpetch <asangpet at andrew.cmu.edu>
+ *   Chris Lundquist <clundquist at bluebox.net>
+ **/
+
+#include "collectd.h"
+#include "plugin.h"
+#include "common.h"
+#include "configfile.h"
+
+#include <pthread.h>
+
+#if HAVE_STDINT_H
+# define MONGO_HAVE_STDINT 1
+#else
+# define MONGO_USE_LONG_LONG_INT 1
+#endif
+#include <mongo.h>
+
+struct wm_node_s
+{
+  char name[DATA_MAX_NAME_LEN];
+
+  char *host;
+  int port;
+  int timeout;
+
+  int connected;
+
+  mongo conn[1];
+  pthread_mutex_t lock;
+};
+typedef struct wm_node_s wm_node_t;
+
+/*
+ * Functions
+ */
+static int wm_write (const data_set_t *ds, /* {{{ */
+    const value_list_t *vl,
+    user_data_t *ud)
+{
+  wm_node_t *node = ud->data;
+  char collection_name[512];
+  int status;
+  int i;
+  bson record;
+
+  ssnprintf(collection_name, sizeof (collection_name), "collectd.%s", vl->plugin);
+
+  bson_init(&record);
+  bson_append_time_t(&record,"ts",CDTIME_T_TO_TIME_T(vl->time));
+  bson_append_string(&record,"h",vl->host);
+  bson_append_string(&record,"i",vl->plugin_instance);
+  bson_append_string(&record,"t",vl->type);
+  bson_append_string(&record,"ti",vl->type_instance);
+
+  for (i = 0; i < ds->ds_num; i++)
+  {
+    if (ds->ds[i].type == DS_TYPE_COUNTER)
+      bson_append_long(&record, ds->ds[i].name, vl->values[i].counter);
+    else if (ds->ds[i].type == DS_TYPE_GAUGE)
+      bson_append_double(&record, ds->ds[i].name, vl->values[i].gauge);
+    else if (ds->ds[i].type == DS_TYPE_DERIVE)
+      bson_append_long(&record, ds->ds[i].name, vl->values[i].derive);
+    else if (ds->ds[i].type == DS_TYPE_ABSOLUTE)
+      bson_append_long(&record, ds->ds[i].name, vl->values[i].absolute);
+    else
+      assert (23 == 42);
+  }
+  /* We must finish the record, other wise the insert will fail */
+  bson_finish(&record);
+
+  pthread_mutex_lock (&node->lock);
+
+  if (node->connected == 0)
+  {
+    status = mongo_connect(node->conn, node->host, node->port);
+    if (status != MONGO_OK) {
+      ERROR ("write_mongodb plugin: Connecting to host \"%s\" (port %i) failed.",
+          (node->host != NULL) ? node->host : "localhost",
+          (node->port != 0) ? node->port : MONGO_DEFAULT_PORT);
+      mongo_destroy(node->conn);
+      pthread_mutex_unlock (&node->lock);
+      return (-1);
+    } else {
+      node->connected = 1;
+    }
+  }
+
+  /* Assert if the connection has been established */
+  assert (node->connected == 1);
+
+  DEBUG ( "write_mongodb plugin: writing record");
+  /* bson_print(&record); */
+
+  status = mongo_insert(node->conn,collection_name,&record);
+
+  if(status != MONGO_OK)
+  {
+    ERROR ( "write_mongodb plugin: error inserting record: %d", node->conn->err);
+    if (node->conn->err == MONGO_BSON_INVALID)
+      ERROR ("write_mongodb plugin: %s", node->conn->errstr);
+    else if (record.err)
+      ERROR ("write_mongodb plugin: %s", record.errstr);
+  }
+
+  pthread_mutex_unlock (&node->lock);
+  /* free our resource as not to leak memory */
+  bson_destroy(&record);
+
+  return (0);
+} /* }}} int wm_write */
+
+static void wm_config_free (void *ptr) /* {{{ */
+{
+  wm_node_t *node = ptr;
+
+  if (node == NULL)
+    return;
+
+  if (node->connected != 0)
+  {
+    mongo_destroy(node->conn);
+    node->connected = 0;
+  }
+
+  sfree (node->host);
+  sfree (node);
+} /* }}} void wm_config_free */
+
+static int wm_config_node (oconfig_item_t *ci) /* {{{ */
+{
+  wm_node_t *node;
+  int status;
+  int i;
+
+  node = malloc (sizeof (*node));
+  if (node == NULL)
+    return (ENOMEM);
+  memset (node, 0, sizeof (*node));
+  node->host = NULL;
+  node->port = 0;
+  node->timeout = 1000;
+  node->connected = 0;
+  pthread_mutex_init (&node->lock, /* attr = */ NULL);
+
+  status = cf_util_get_string_buffer (ci, node->name, sizeof (node->name));
+
+  if (status != 0)
+  {
+    sfree (node);
+    return (status);
+  }
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Host", child->key) == 0)
+      status = cf_util_get_string (child, &node->host);
+    else if (strcasecmp ("Port", child->key) == 0)
+    {
+      status = cf_util_get_port_number (child);
+      if (status > 0)
+      {
+        node->port = status;
+        status = 0;
+      }
+    }
+    else if (strcasecmp ("Timeout", child->key) == 0)
+      status = cf_util_get_int (child, &node->timeout);
+    else
+      WARNING ("write_mongodb plugin: Ignoring unknown config option \"%s\".",
+          child->key);
+
+    if (status != 0)
+      break;
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  if (status == 0)
+  {
+    char cb_name[DATA_MAX_NAME_LEN];
+    user_data_t ud;
+
+    ssnprintf (cb_name, sizeof (cb_name), "write_mongodb/%s", node->name);
+
+    ud.data = node;
+    ud.free_func = wm_config_free;
+
+    status = plugin_register_write (cb_name, wm_write, &ud);
+    INFO ("write_mongodb plugin: registered write plugin %s %d",cb_name,status);
+  }
+
+  if (status != 0)
+    wm_config_free (node);
+
+  return (status);
+} /* }}} int wm_config_node */
+
+static int wm_config (oconfig_item_t *ci) /* {{{ */
+{
+  int i;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Node", child->key) == 0)
+      wm_config_node (child);
+    else
+      WARNING ("write_mongodb plugin: Ignoring unknown "
+          "configuration option \"%s\" at top level.", child->key);
+  }
+
+  return (0);
+} /* }}} int wm_config */
+
+void module_register (void)
+{
+  plugin_register_complex_config ("write_mongodb", wm_config);
+}
+
+/* vim: set sw=2 sts=2 tw=78 et fdm=marker : */
diff --git a/src/write_redis.c b/src/write_redis.c
new file mode 100644 (file)
index 0000000..58f2cae
--- /dev/null
@@ -0,0 +1,238 @@
+/**
+ * collectd - src/write_redis.c
+ * Copyright (C) 2010  Florian Forster
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *   Florian Forster <ff at octo.it>
+ **/
+
+#include "collectd.h"
+#include "plugin.h"
+#include "common.h"
+#include "configfile.h"
+
+#include <pthread.h>
+#include <credis.h>
+
+struct wr_node_s
+{
+  char name[DATA_MAX_NAME_LEN];
+
+  char *host;
+  int port;
+  int timeout;
+
+  REDIS conn;
+  pthread_mutex_t lock;
+};
+typedef struct wr_node_s wr_node_t;
+
+/*
+ * Functions
+ */
+static int wr_write (const data_set_t *ds, /* {{{ */
+    const value_list_t *vl,
+    user_data_t *ud)
+{
+  wr_node_t *node = ud->data;
+  char ident[512];
+  char key[512];
+  char value[512];
+  size_t value_size;
+  char *value_ptr;
+  int status;
+  int i;
+
+  status = FORMAT_VL (ident, sizeof (ident), vl);
+  if (status != 0)
+    return (status);
+  ssnprintf (key, sizeof (key), "collectd/%s", ident);
+
+  memset (value, 0, sizeof (value));
+  value_size = sizeof (value);
+  value_ptr = &value[0];
+
+#define APPEND(...) do {                                             \
+  status = snprintf (value_ptr, value_size, __VA_ARGS__);            \
+  if (((size_t) status) > value_size)                                \
+  {                                                                  \
+    value_ptr += value_size;                                         \
+    value_size = 0;                                                  \
+  }                                                                  \
+  else                                                               \
+  {                                                                  \
+    value_ptr += status;                                             \
+    value_size -= status;                                            \
+  }                                                                  \
+} while (0)
+
+  APPEND ("%lu", (unsigned long) vl->time);
+  for (i = 0; i < ds->ds_num; i++)
+  {
+    if (ds->ds[i].type == DS_TYPE_COUNTER)
+      APPEND ("%llu", vl->values[i].counter);
+    else if (ds->ds[i].type == DS_TYPE_GAUGE)
+      APPEND ("%g", vl->values[i].gauge);
+    else if (ds->ds[i].type == DS_TYPE_DERIVE)
+      APPEND ("%"PRIi64, vl->values[i].derive);
+    else if (ds->ds[i].type == DS_TYPE_ABSOLUTE)
+      APPEND ("%"PRIu64, vl->values[i].absolute);
+    else
+      assert (23 == 42);
+  }
+
+#undef APPEND
+
+  pthread_mutex_lock (&node->lock);
+
+  if (node->conn == NULL)
+  {
+    node->conn = credis_connect (node->host, node->port, node->timeout);
+    if (node->conn == NULL)
+    {
+      ERROR ("write_redis plugin: Connecting to host \"%s\" (port %i) failed.",
+          (node->host != NULL) ? node->host : "localhost",
+          (node->port != 0) ? node->port : 6379);
+      pthread_mutex_unlock (&node->lock);
+      return (-1);
+    }
+  }
+
+  /* "credis_zadd" doesn't handle a NULL pointer gracefully, so I'd rather
+   * have a meaningful assertion message than a normal segmentation fault. */
+  assert (node->conn != NULL);
+  status = credis_zadd (node->conn, key, (double) vl->time, value);
+
+  credis_sadd (node->conn, "collectd/values", ident);
+
+  pthread_mutex_unlock (&node->lock);
+
+  return (0);
+} /* }}} int wr_write */
+
+static void wr_config_free (void *ptr) /* {{{ */
+{
+  wr_node_t *node = ptr;
+
+  if (node == NULL)
+    return;
+
+  if (node->conn != NULL)
+  {
+    credis_close (node->conn);
+    node->conn = NULL;
+  }
+
+  sfree (node->host);
+  sfree (node);
+} /* }}} void wr_config_free */
+
+static int wr_config_node (oconfig_item_t *ci) /* {{{ */
+{
+  wr_node_t *node;
+  int status;
+  int i;
+
+  node = malloc (sizeof (*node));
+  if (node == NULL)
+    return (ENOMEM);
+  memset (node, 0, sizeof (*node));
+  node->host = NULL;
+  node->port = 0;
+  node->timeout = 1000;
+  node->conn = NULL;
+  pthread_mutex_init (&node->lock, /* attr = */ NULL);
+
+  status = cf_util_get_string_buffer (ci, node->name, sizeof (node->name));
+  if (status != 0)
+  {
+    sfree (node);
+    return (status);
+  }
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Host", child->key) == 0)
+      status = cf_util_get_string (child, &node->host);
+    else if (strcasecmp ("Port", child->key) == 0)
+    {
+      status = cf_util_get_port_number (child);
+      if (status > 0)
+      {
+        node->port = status;
+        status = 0;
+      }
+    }
+    else if (strcasecmp ("Timeout", child->key) == 0)
+      status = cf_util_get_int (child, &node->timeout);
+    else
+      WARNING ("write_redis plugin: Ignoring unknown config option \"%s\".",
+          child->key);
+
+    if (status != 0)
+      break;
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  if (status == 0)
+  {
+    char cb_name[DATA_MAX_NAME_LEN];
+    user_data_t ud;
+
+    ssnprintf (cb_name, sizeof (cb_name), "write_redis/%s", node->name);
+
+    ud.data = node;
+    ud.free_func = wr_config_free;
+
+    status = plugin_register_write (cb_name, wr_write, &ud);
+  }
+
+  if (status != 0)
+    wr_config_free (node);
+
+  return (status);
+} /* }}} int wr_config_node */
+
+static int wr_config (oconfig_item_t *ci) /* {{{ */
+{
+  int i;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Node", child->key) == 0)
+      wr_config_node (child);
+    else
+      WARNING ("write_redis plugin: Ignoring unknown "
+          "configuration option \"%s\" at top level.", child->key);
+  }
+
+  return (0);
+} /* }}} int wr_config */
+
+void module_register (void)
+{
+  plugin_register_complex_config ("write_redis", wr_config);
+}
+
+/* vim: set sw=2 sts=2 tw=78 et fdm=marker : */
diff --git a/src/xmms.c b/src/xmms.c
new file mode 100644 (file)
index 0000000..52beb65
--- /dev/null
@@ -0,0 +1,73 @@
+/**
+ * collectd - src/xmms.c
+ * Copyright (C) 2007  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "plugin.h"
+#include "common.h"
+
+#include <xmms/xmmsctrl.h>
+
+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_len = 1;
+       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+       sstrncpy (vl.plugin, "xmms", sizeof (vl.plugin));
+       sstrncpy (vl.type, type, sizeof (vl.type));
+
+       plugin_dispatch_values (&vl);
+} /* void cxmms_submit */
+
+int cxmms_read (void)
+{
+  gint rate;
+  gint freq;
+  gint nch;
+
+  if (!xmms_remote_is_running (xmms_session))
+    return (0);
+
+  xmms_remote_get_info (xmms_session, &rate, &freq, &nch);
+
+  if ((freq == 0) || (nch == 0))
+    return (-1);
+
+  cxmms_submit ("bitrate", rate);
+  cxmms_submit ("frequency", freq);
+
+  return (0);
+} /* int read */
+
+void module_register (void)
+{
+  plugin_register_read ("xmms", cxmms_read);
+} /* void module_register */
+
+/*
+ * vim: shiftwidth=2:softtabstop=2:textwidth=78
+ */
diff --git a/src/zfs_arc.c b/src/zfs_arc.c
new file mode 100644 (file)
index 0000000..8341be0
--- /dev/null
@@ -0,0 +1,165 @@
+/**
+ * collectd - src/zfs_arc.c
+ * Copyright (C) 2009  Anthony Dewhurst
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Anthony Dewhurst <dewhurst at gmail>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+/*
+ * Global variables
+ */
+static kstat_t *ksp;
+extern kstat_ctl_t *kc;
+
+static void za_submit (const char* type, const char* type_instance, value_t* values, int 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));
+
+       plugin_dispatch_values (&vl);
+}
+
+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);
+}
+
+static void za_submit_derive (const char* type, const char* type_instance, derive_t dv)
+{
+       value_t vv;
+
+       vv.derive = dv;
+       za_submit (type, type_instance, &vv, 1);
+}
+
+static void za_submit_ratio (const char* type_instance, gauge_t hits, gauge_t misses)
+{
+       gauge_t ratio = NAN;
+
+       if (!isfinite (hits) || (hits < 0.0))
+               hits = 0.0;
+       if (!isfinite (misses) || (misses < 0.0))
+               misses = 0.0;
+
+       if ((hits != 0.0) || (misses != 0.0))
+               ratio = hits / (hits + misses);
+
+       za_submit_gauge ("cache_ratio", type_instance, ratio);
+}
+
+static int za_read (void)
+{
+       gauge_t  arc_size, l2_size;
+       derive_t demand_data_hits,
+                demand_metadata_hits,
+                prefetch_data_hits,
+                prefetch_metadata_hits,
+                demand_data_misses,
+                demand_metadata_misses,
+                prefetch_data_misses,
+                prefetch_metadata_misses;
+       gauge_t  arc_hits, arc_misses, l2_hits, l2_misses;
+       value_t  l2_io[2];
+
+       get_kstat (&ksp, "zfs", 0, "arcstats");
+       if (ksp == NULL)
+       {
+               ERROR ("zfs_arc plugin: Cannot find zfs:0:arcstats kstat.");
+               return (-1);
+       }
+
+       /* Sizes */
+       arc_size   = get_kstat_value(ksp, "size");
+       l2_size    = get_kstat_value(ksp, "l2_size");
+
+       za_submit_gauge ("cache_size", "arc", arc_size);
+       za_submit_gauge ("cache_size", "L2", l2_size);
+
+       /* Hits / misses */
+       demand_data_hits       = get_kstat_value(ksp, "demand_data_hits");
+       demand_metadata_hits   = get_kstat_value(ksp, "demand_metadata_hits");
+       prefetch_data_hits     = get_kstat_value(ksp, "prefetch_data_hits");
+       prefetch_metadata_hits = get_kstat_value(ksp, "prefetch_metadata_hits");
+
+       demand_data_misses       = get_kstat_value(ksp, "demand_data_misses");
+       demand_metadata_misses   = get_kstat_value(ksp, "demand_metadata_misses");
+       prefetch_data_misses     = get_kstat_value(ksp, "prefetch_data_misses");
+       prefetch_metadata_misses = get_kstat_value(ksp, "prefetch_metadata_misses");
+
+       za_submit_derive ("cache_result", "demand_data-hit",       demand_data_hits);
+       za_submit_derive ("cache_result", "demand_metadata-hit",   demand_metadata_hits);
+       za_submit_derive ("cache_result", "prefetch_data-hit",     prefetch_data_hits);
+       za_submit_derive ("cache_result", "prefetch_metadata-hit", prefetch_metadata_hits);
+
+       za_submit_derive ("cache_result", "demand_data-miss",       demand_data_misses);
+       za_submit_derive ("cache_result", "demand_metadata-miss",   demand_metadata_misses);
+       za_submit_derive ("cache_result", "prefetch_data-miss",     prefetch_data_misses);
+       za_submit_derive ("cache_result", "prefetch_metadata-miss", prefetch_metadata_misses);
+
+       /* Ratios */
+       arc_hits   = (gauge_t) get_kstat_value(ksp, "hits");
+       arc_misses = (gauge_t) get_kstat_value(ksp, "misses");
+       l2_hits    = (gauge_t) get_kstat_value(ksp, "l2_hits");
+       l2_misses  = (gauge_t) get_kstat_value(ksp, "l2_misses");
+
+       za_submit_ratio ("arc", arc_hits, arc_misses);
+       za_submit_ratio ("L2", l2_hits, l2_misses);
+
+       /* I/O */
+       l2_io[0].derive = get_kstat_value(ksp, "l2_read_bytes");
+       l2_io[1].derive = get_kstat_value(ksp, "l2_write_bytes");
+
+       za_submit ("io_octets", "L2", l2_io, /* num values = */ 2);
+
+       return (0);
+} /* int za_read */
+
+static int za_init (void) /* {{{ */
+{
+       ksp = NULL;
+
+       /* kstats chain already opened by update_kstat (using *kc), verify everything went fine. */
+       if (kc == NULL)
+       {
+               ERROR ("zfs_arc plugin: kstat chain control structure not available.");
+               return (-1);
+       }
+
+       return (0);
+} /* }}} int za_init */
+
+void module_register (void)
+{
+       plugin_register_init ("zfs_arc", za_init);
+       plugin_register_read ("zfs_arc", za_read);
+} /* void module_register */
+
+/* vmi: set sw=8 noexpandtab fdm=marker : */
diff --git a/version-gen.sh b/version-gen.sh
new file mode 100755 (executable)
index 0000000..5bc20a1
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+DEFAULT_VERSION="5.0.2.git"
+
+VERSION="`git describe 2> /dev/null | sed -e 's/^collectd-//'`"
+
+if test -z "$VERSION"; then
+       VERSION="$DEFAULT_VERSION"
+fi
+
+VERSION="`echo \"$VERSION\" | sed -e 's/-/./g'`"
+
+if test "x`uname -s`" = "xAIX" || test "x`uname -s`" = "xSunOS" ; then
+       echo "$VERSION\c"
+else 
+       echo -n "$VERSION"
+fi