--- /dev/null
+# 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
+*~
--- /dev/null
+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>
+
--- /dev/null
+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.
+
+Cyril Feraudet <cyril at feraudet.com>
+ - ethstat plugin.
+
+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 Hanselmann <public at hansmi.ch>
+ - md plugin.
+
+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.
+
+Scott Sanders <scott at jssjr.com>
+ - Write-Graphite 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.
+
+Thomas Meson <zllak at hycik.org>
+ - Graphite support for the AMQP 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 :)
--- /dev/null
+ 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.
--- /dev/null
+2012-04-01, Version 5.1.0
+ * Build system, iptables plugin: The shipped version of libiptc has
+ been removed.
+ * collectd-nagios: A list of value lists can now be queried using
+ "-n LIST". Thanks to Sebastian Harl for his patches.
+ * bind plugin: The "ParseTime" option has been added. It allows to use
+ the system time rather than the time reported by BIND.
+ * curl, memcachec, tail plugins: The "ExcludeRegexp" option has been
+ added. Thanks to Peter Warasin for his initial patch.
+ * ethstat plugin: The new "ethstat" plugin reads performance statistics
+ directly from ethernet cards. Thanks to Cyril Feraudet for his patch.
+ * GenericJMX plugin: Support for querying MBean "Operations" (in
+ addition to "Attributes") has been added. Thanks to Pierre-Yves
+ Ritschard for his patch.
+ * irq plugin: The selection / ignore code now uses the default
+ ignorelist infrastructure, providing the standard feature set, e.g.
+ regex matching.
+ * md plugin: The new "md" plugin reports the number of disks in various
+ states in Linux software RAID devices. Thanks to Michael Hanselmann
+ for his patch.
+ * modbus plugin: Support for signed integer register types has been
+ added.
+ * nfs plugin: Support for Solaris has been added. Thanks to Cosmin
+ Ioiart for his patch.
+ * numa plugin: The new "numa" plugin reports statistics of the
+ Non-Uniform Memory Access (NUMA) subsystem of Linux.
+ * processes plugin: Various fixes for the FreeBSD implementation.
+ Thanks to Phil Kulin for his patch.
+ * rrdcached plugin: Passing flushes to the caching daemon has been
+ added.
+ * sensors plugin: The initialization code has been improved. Thanks to
+ Henrique de Moraes Holschuh for his patch.
+ * swap plugin: The "ReportByDevice" option has been added.
+ * syslog plugin: Support for writing notifications has been added.
+ Thanks to Fabien Wernli for his patch.
+ * tcpconns plugin: Support for AIX has been added. Thanks to Manuel
+ Luis Sanmartín Rozada for his patch.
+ * threshold plugin: The "PersistOK" option has been added. Thanks to
+ Aaron Brady for his patch.
+ * varnish plugin: Support for Varnish 3.0 has been added. Thanks to
+ Jérôme Renard for his patches.
+ * write_mongodb plugin: The new "write_mongodb" plugin writes value
+ lists to MongoDB, a shema-less database. Thanks to Akkarit Sangpetch
+ and Chris Lundquist for their work.
+ * write_graphite plugin: The new "write_graphite" plugin writes value
+ lists to Carbon, the storage layer of the Graphite time-series
+ database. Thanks to Scott Sanders and Pierre-Yves Ritschard for their
+ work.
+ * zfs_arc plugin: Several new statistics have been added. Thanks to
+ Aurelien Rougemont for his patches.
+ * scale target: Support for scaling specific data sources only has been
+ added. Thanks to Gerrie Roos for his patch.
+
+
+2012-04-01, Version 5.0.4
+ * Build system: Fix the use of a libltdl macro. Thanks to Clemens Lang
+ for fixing this. Adresses some issues with building the iptables
+ plugin under Gentoo.
+ * libcollectdclient: A memory leak in the lcc_getval() function has
+ been fixed. Thanks to Jason Schmidlapp for finding and fixing this
+ issue.
+ * bind plugin: The use of 'QType" types has been fixed.
+ * df plugin: Fixed compiler issue under Mac OS X 10.7.
+ * conntrack plugin: Support zero as legitimate value. Thanks to Louis
+ Opter for his patch.
+ * memcached plugin: Increased the size of a static buffer, which was
+ truncating status messages form memcached. Thanks to Timon for the
+ patch.
+ * network plugin: Forwarding of notifications has been disabled. This
+ was a contition not checked for before, which may retult in an
+ endless loop.
+ * processes plugin: Support for process names with spaces has been
+ added to the Linux implementation. Thanks to Darrell Bishop for his
+ patch.
+ * perl plugin: A race condition in several callbacks, including log and
+ write callbacks, has been fixed. Thanks to "Rrpv" for reporting this
+ bug.
+ * snmp plugin: A bug when casting unsigned integers to gauge values has
+ been fixed: Unsigned integers would be cast to a signed integer and
+ then to a gauge, possibly resulting in a negative value.
+ * tcpconns plugin: Compilation with newer versions of the FreeBSD
+ runtime has been fixed.
+
+2012-02-19, Version 5.0.3
+ * Build system: Fix problems when building the ipvs and iptables
+ plugins. Thanks to Sebastian Harl for his patch. A bashism in the
+ version-gen.sh script has been fixed. Thanks to Jo-Philipp Wich for
+ his patch.
+ * csv and rrdtool plugins: Print a more helpful error message when the
+ DataDir is a symlink pointing to a non-existing location. Thanks to
+ Jonathan Nieder for his patch.
+ * exec plugin: Fix a problem when using select(2) to read from file
+ handles. Thanks to Gerrie Roos for his patch.
+ * network plugin: An incorrect error message in the handling of the
+ "Interface" configuration option has been fixed. Thanks to Gerrie
+ Roos for his patch.
+ * oracle plugin: A potential endless loop in the error handling has
+ been fixed.
+ * python plugin: A crash bug in the configuration handling has been
+ fixed. Thanks to Sven Trenkel for his patch.
+ * interfaces plugin: The change which was supposed to ignore "bogus"
+ interfaces has been reverted, since it ignored legit interfaces, such
+ as bonding pseudo-devices as well.
+
+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-04-01, Version 4.10.7
+ * Build system: Fix the use of a libltdl macro. Thanks to Clemens Lang
+ for fixing this. Adresses some issues with building the iptables
+ plugin under Gentoo.
+ * libcollectdclient: A memory leak in the lcc_getval() function has
+ been fixed. Thanks to Jason Schmidlapp for finding and fixing this
+ issue.
+ * bind plugin: The use of 'QType" types has been fixed.
+ * df plugin: Fixed compiler issue under Mac OS X 10.7.
+ * conntrack plugin: Support zero as legitimate value. Thanks to Louis
+ Opter for his patch.
+ * memcached plugin: Increased the size of a static buffer, which was
+ truncating status messages form memcached. Thanks to Timon for the
+ patch.
+ * network plugin: Forwarding of notifications has been disabled. This
+ was a contition not checked for before, which may retult in an
+ endless loop.
+ * processes plugin: Support for process names with spaces has been
+ added to the Linux implementation. Thanks to Darrell Bishop for his
+ patch.
+ * perl plugin: A race condition in several callbacks, including log and
+ write callbacks, has been fixed. Thanks to "Rrpv" for reporting this
+ bug.
+ * snmp plugin: A bug when casting unsigned integers to gauge values has
+ been fixed: Unsigned integers would be cast to a signed integer and
+ then to a gauge, possibly resulting in a negative value.
+ * tcpconns plugin: Compilation with newer versions of the FreeBSD
+ runtime has been fixed.
+
+2012-02-19, Version 4.10.6
+ * Build system: Fix problems when building the ipvs and iptables
+ plugins. Thanks to Sebastian Harl for his patch. A bashism in the
+ version-gen.sh script has been fixed. Thanks to Jo-Philipp Wich for
+ his patch.
+ * csv and rrdtool plugins: Print a more helpful error message when the
+ DataDir is a symlink pointing to a non-existing location. Thanks to
+ Jonathan Nieder for his patch.
+ * exec plugin: Fix a problem when using select(2) to read from file
+ handles. Thanks to Gerrie Roos for his patch.
+ * network plugin: An incorrect error message in the handling of the
+ "Interface" configuration option has been fixed. Thanks to Gerrie
+ Roos for his patch.
+ * oracle plugin: A potential endless loop in the error handling has
+ been fixed.
+ * python plugin: A crash bug in the configuration handling has been
+ fixed. Thanks to Sven Trenkel for his patch.
+ * interfaces plugin: The change which was supposed to ignore "bogus"
+ interfaces has been reverted, since it ignored legit interfaces, such
+ as bonding pseudo-devices as well.
+
+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
--- /dev/null
+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
--- /dev/null
+ 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.
+
+ - ethstat
+ Network interface card statistics.
+
+ - 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).
+
+ - md
+ Linux software-RAID device information (number of active, failed, spare
+ and missing disks).
+
+ - 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).
+
+ - numa
+ Information about Non-Uniform Memory Access (NUMA).
+
+ - 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_graphite
+ Sends data to Carbon, the storage layer of Graphite.
+
+ - 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.
+
--- /dev/null
+* 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/
--- /dev/null
+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
+
--- /dev/null
+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"
--- /dev/null
+/*
+ * 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 <octo at verplant.org>
+ */
+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 : */
--- /dev/null
+/*
+ * 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 <octo at verplant.org>
+ * @see Collectd#registerConfig(String, CollectdConfigInterface)
+ */
+public interface CollectdConfigInterface
+{
+ public int config (OConfigItem ci);
+}
--- /dev/null
+/*
+ * 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 <octo at verplant.org>
+ * @see Collectd#registerFlush
+ */
+public interface CollectdFlushInterface
+{
+ public int flush (Number timeout, String identifier);
+}
--- /dev/null
+/*
+ * 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 <octo at verplant.org>
+ * @see Collectd#registerInit
+ */
+public interface CollectdInitInterface
+{
+ public int init ();
+}
--- /dev/null
+/*
+ * 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 <octo at verplant.org>
+ * @see Collectd#registerLog
+ */
+public interface CollectdLogInterface
+{
+ public void log (int severity, String message);
+}
--- /dev/null
+/*
+ * 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 <octo at verplant.org>
+ * @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);
+}
--- /dev/null
+/*
+ * 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 <octo at verplant.org>
+ * @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 */
--- /dev/null
+/*
+ * 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 <octo at verplant.org>
+ * @see Collectd#registerNotification
+ */
+public interface CollectdNotificationInterface
+{
+ public int notification (Notification n);
+}
--- /dev/null
+/*
+ * 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 <octo at verplant.org>
+ * @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 ();
+}
--- /dev/null
+/*
+ * 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 <octo at verplant.org>
+ * @see Collectd#registerShutdown
+ */
+public interface CollectdShutdownInterface
+{
+ public int shutdown ();
+}
--- /dev/null
+/*
+ * 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 <octo at verplant.org>
+ * @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);
+}
--- /dev/null
+/*
+ * 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 <octo at verplant.org>
+ * @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 */
--- /dev/null
+/*
+ * 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 <octo at verplant.org>
+ * @see Collectd#registerWrite
+ */
+public interface CollectdWriteInterface
+{
+ public int write (ValueList vl);
+}
--- /dev/null
+/*
+ * 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 <octo at verplant.org>
+ */
+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 : */
--- /dev/null
+/*
+ * 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 : */
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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 <octo at verplant.org>
+ */
+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 : */
--- /dev/null
+/*
+ * 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 <octo at verplant.org>
+ */
+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 : */
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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 : */
--- /dev/null
+/*
+ * 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 : */
--- /dev/null
+/*
+ * 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 : */
--- /dev/null
+/*
+ * 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 : */
--- /dev/null
+/*
+ * 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 <value /> block and query functionality.
+ *
+ * This class represents a <value /> 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 : */
--- /dev/null
+/*
+ * 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 : */
--- /dev/null
+use ExtUtils::MakeMaker;
+
+WriteMakefile(
+ 'NAME' => 'Collectd',
+ 'AUTHOR' => 'Sebastian Harl <sh@tokkee.org>',
+);
+
+# vim: set sw=4 ts=4 tw=78 noexpandtab :
--- /dev/null
+# 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 :
+
--- /dev/null
+#
+# 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;
--- /dev/null
+#
+# 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;
--- /dev/null
+#
+# 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 :
--- /dev/null
+#! /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
--- /dev/null
+#! /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
--- /dev/null
+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.])
+ AC_DEFINE(_REENTRANT, 1, [Define to enable reentrancy interfaces.])
+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 md module (Linux only)
+if test "x$ac_system" = "xLinux"
+then
+ AC_CHECK_HEADERS(linux/raid/md_u.h,
+ [have_linux_raid_md_u_h="yes"],
+ [have_linux_raid_md_u_h="no"],
+[
+#include <sys/ioctl.h>
+#include <linux/major.h>
+#include <linux/types.h>
+])
+else
+ have_linux_raid_md_u_h="no"
+fi
+
+# 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 ethstat module
+AC_CHECK_HEADERS(linux/sockios.h,
+ [have_linux_sockios_h="yes"],
+ [have_linux_sockios_h="no"],
+ [
+#if HAVE_SYS_IOCTL_H
+# include <sys/ioctl.h>
+#endif
+#if HAVE_NET_IF_H
+# include <net/if.h>
+#endif
+ ])
+AC_CHECK_HEADERS(linux/ethtool.h,
+ [have_linux_ethtool_h="yes"],
+ [have_linux_ethtool_h="no"],
+ [
+#if HAVE_SYS_IOCTL_H
+# include <sys/ioctl.h>
+#endif
+#if HAVE_NET_IF_H
+# include <net/if.h>
+#endif
+#if HAVE_LINUX_SOCKIOS_H
+# include <linux/sockios.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
+])
+
+AC_CHECK_HEADERS(netinet/ip_compat.h)
+
+# 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_CHECK_HEADERS(bsd/nlist.h nlist.h)
+ 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" = "xshipped"
+ then
+ with_libiptc="own"
+ else if test "x$withval" = "xyes"
+ then
+ with_libiptc="pkgconfig"
+ else if test "x$withval" = "xno"
+ then
+ with_libiptc="no"
+ else
+ with_libiptc="yes"
+ with_libiptc_cflags="-I$withval/include"
+ with_libiptc_libs="-L$withval/lib"
+ fi; fi; fi
+],
+[
+ if test "x$ac_system" = "xLinux"
+ then
+ with_libiptc="pkgconfig"
+ else
+ with_libiptc="no (Linux only)"
+ fi
+])
+
+if test "x$with_libiptc" = "xpkgconfig" && test "x$PKG_CONFIG" = "x"
+then
+ with_libiptc="no (Don't have pkg-config)"
+fi
+
+if test "x$with_libiptc" = "xpkgconfig"
+then
+ $PKG_CONFIG --exists 'libiptc' 2>/dev/null
+ if test $? -ne 0
+ then
+ with_libiptc="no (pkg-config doesn't know libiptc)"
+ fi
+fi
+if test "x$with_libiptc" = "xpkgconfig"
+then
+ with_libiptc_cflags="`$PKG_CONFIG --cflags 'libiptc'`"
+ if test $? -ne 0
+ then
+ with_libiptc="no ($PKG_CONFIG failed)"
+ fi
+ with_libiptc_libs="`$PKG_CONFIG --libs 'libiptc'`"
+ if test $? -ne 0
+ then
+ with_libiptc="no ($PKG_CONFIG failed)"
+ fi
+fi
+
+SAVE_CPPFLAGS="$CPPFLAGS"
+CPPFLAGS="$CPPFLAGS $with_libiptc_cflags"
+
+# check whether the header file for libiptc is available.
+if test "x$with_libiptc" = "xpkgconfig"
+then
+ AC_CHECK_HEADERS(libiptc/libiptc.h libiptc/libip6tc.h, ,
+ [with_libiptc="no (header file missing)"])
+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" = "xpkgconfig"
+then
+ AC_CHECK_TYPES([iptc_handle_t, ip6tc_handle_t], [], [])
+fi
+# Check for the iptc_init symbol in the library.
+# This could be in iptc or ip4tc
+if test "x$with_libiptc" = "xpkgconfig"
+then
+ AC_SEARCH_LIBS(iptc_init, [iptc ip4tc],
+ [with_libiptc="pkgconfig"],
+ [with_libiptc="no"],
+ [$with_libiptc_libs])
+fi
+if test "x$with_libiptc" = "xpkgconfig"
+then
+ with_libiptc="yes"
+fi
+
+CPPFLAGS="$SAVE_CPPFLAGS"
+
+if test "x$with_libiptc" = "xown"
+then
+ with_libiptc_cflags=""
+ with_libiptc_libs=""
+fi
+if test "x$with_libiptc" = "xown"
+then
+ AC_CHECK_HEADERS(linux/netfilter_ipv4/ip_tables.h linux/netfilter_ipv6/ip6_tables.h linux/netfilter/x_tables.h, [],
+ [
+ with_libiptc="no (Linux iptables headers not found)"
+ ],
+ [
+#include "$srcdir/src/owniptc/ipt_kernel_headers.h"
+ ])
+fi
+AM_CONDITIONAL(BUILD_WITH_OWN_LIBIPTC, test "x$with_libiptc" = "xown")
+if test "x$with_libiptc" = "xown"
+then
+ AC_DEFINE(OWN_LIBIPTC, 1, [Define to 1 if we use the shipped iptc library.])
+ with_libiptc="yes"
+fi
+
+AM_CONDITIONAL(BUILD_WITH_LIBIPTC, test "x$with_libiptc" = "xyes")
+if test "x$with_libiptc" = "xyes"
+then
+ BUILD_WITH_LIBIPTC_CPPFLAGS="$with_libiptc_cflags"
+ BUILD_WITH_LIBIPTC_LDFLAGS="$with_libiptc_libs"
+ AC_SUBST(BUILD_WITH_LIBIPTC_CPPFLAGS)
+ AC_SUBST(BUILD_WITH_LIBIPTC_LDFLAGS)
+fi
+# }}}
+
+# --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_r 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"
+])
+SAVE_CPPFLAGS="$CPPFLAGS"
+SAVE_LDFLAGS="$LDFLAGS"
+CPPFLAGS="$CPPFLAGS $with_librabbitmq_cppflags"
+LDFLAGS="$LDFLAGS $with_librabbitmq_ldflags"
+if test "x$with_librabbitmq" = "xyes"
+then
+ AC_CHECK_HEADERS(amqp.h, [with_librabbitmq="yes"], [with_librabbitmq="no (amqp.h not found)"])
+fi
+if test "x$with_librabbitmq" = "xyes"
+then
+ # librabbitmq up to version 0.9.1 provides "library_errno", later
+ # versions use "library_error". The library does not provide a version
+ # macro :( Use "AC_CHECK_MEMBERS" (plural) for automatic defines.
+ AC_CHECK_MEMBERS([amqp_rpc_reply_t.library_errno],,,
+ [
+#if HAVE_STDLIB_H
+# include <stdlib.h>
+#endif
+#if HAVE_STDIO_H
+# include <stdio.h>
+#endif
+#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#if HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+#include <amqp.h>
+ ])
+fi
+if test "x$with_librabbitmq" = "xyes"
+then
+ AC_CHECK_LIB(rabbitmq, amqp_basic_publish, [with_librabbitmq="yes"], [with_librabbitmq="no (Symbol 'amqp_basic_publish' not found)"])
+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
+CPPFLAGS="$SAVE_CPPFLAGS"
+LDFLAGS="$SAVE_LDFLAGS"
+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_HEADERS(varnish/vsc.h,
+ [AC_DEFINE([HAVE_VARNISH_V3], [1], [Varnish 3 API support])],
+ [AC_DEFINE([HAVE_VARNISH_V2], [1], [Varnish 2 API support])])
+
+ 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_ethstat="no"
+plugin_fscache="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_numa="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_fscache="yes"
+ plugin_interface="yes"
+ plugin_irq="yes"
+ plugin_load="yes"
+ plugin_memory="yes"
+ plugin_nfs="yes"
+ plugin_numa="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_nfs="yes"
+ 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_linux_sockios_h$have_linux_ethtool_h" = "xyesyes"
+then
+ plugin_ethstat="yes"
+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([ethstat], [$plugin_ethstat], [Stats from NIC driver])
+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([md], [$have_linux_raid_md_u_h], [md (Linux software RAID) devices])
+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([numa], [$plugin_numa], [NUMA virtual memory 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_graphite], [yes], [Graphite / Carbon output plugin])
+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
+ ethstat . . . . . . . $enable_ethstat
+ 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
+ md . . . . . . . . . $enable_md
+ 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
+ numa . . . . . . . . $enable_numa
+ 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_graphite . . . $enable_write_graphite
+ 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 :
--- /dev/null
+# 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>
--- /dev/null
+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.
+
+collectd.service
+----------------
+ Service file for systemd. Please ship this file as
+ /lib/systemd/system/collectd.service in any linux package of collectd.
--- /dev/null
+#!/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
--- /dev/null
+collectd_buffersize 256
+collectd_socket /var/run/collectd-email
+collectd_timeout 2
+collectd_retries 3
+
--- /dev/null
+#!/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"
--- /dev/null
+
+%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
+
--- /dev/null
+#!/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
--- /dev/null
+[Unit]
+Description=statistics collection daemon
+Documentation=man:collectd(1)
+After=local-fs.target network.target
+Requires=local-fs.target network.target
+
+[Service]
+ExecStart=/usr/sbin/collectd -C /etc/collectd/collectd.conf -f
+Restart=always
+RestartSec=10
+StandardOutput=syslog
+StandardError=syslog
+
+[Install]
+WantedBy=multi-user.target
--- /dev/null
+#!/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);
--- /dev/null
+#! /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.
+#
+# Frank Marien (frank@apsu.be) 6 Sep 2012
+# - quick fixes for 5.1 binary protocol
+# - updated to python 3
+# - fixed for larger packet sizes (possible on lo interface)
+# - fixed comment typo (decode_network_string decodes a string)
+
+"""
+Collectd network protocol implementation.
+"""
+
+import socket,struct,sys
+try:
+ from io import StringIO
+except ImportError:
+ from cStringIO 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"""
+
+HR_TIME_DIV = (2.0**30)
+
+# Message kinds
+TYPE_HOST = 0x0000
+TYPE_TIME = 0x0001
+TYPE_TIME_HR = 0x0008
+TYPE_PLUGIN = 0x0002
+TYPE_PLUGIN_INSTANCE = 0x0003
+TYPE_TYPE = 0x0004
+TYPE_TYPE_INSTANCE = 0x0005
+TYPE_VALUES = 0x0006
+TYPE_INTERVAL = 0x0007
+TYPE_INTERVAL_HR = 0x0009
+
+# For notifications
+TYPE_MESSAGE = 0x0100
+TYPE_SEVERITY = 0x0101
+
+# DS kinds
+DS_TYPE_COUNTER = 0
+DS_TYPE_GAUGE = 1
+DS_TYPE_DERIVE = 2
+DS_TYPE_ABSOLUTE = 3
+
+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 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
+ elif dstype == DS_TYPE_DERIVE:
+ result.append((dstype, number.unpack_from(buf, off)[0]))
+ off += valskip
+ elif dstype == DS_TYPE_ABSOLUTE:
+ result.append((dstype, number.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) from collectd network format.
+ """
+ return number.unpack_from(buf, header.size)[0]
+
+
+def decode_network_string(msgtype, plen, buf):
+ """Decodes a string from 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_TIME_HR : decode_network_number,
+ TYPE_INTERVAL : decode_network_number,
+ TYPE_INTERVAL_HR : 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.items()]
+
+ @property
+ def datetime(self):
+ return datetime.fromtimestamp(self.time)
+
+ @property
+ def source(self):
+ buf = StringIO()
+ if self.host:
+ buf.write(str(self.host))
+ if self.plugin:
+ buf.write("/")
+ buf.write(str(self.plugin))
+ if self.plugininstance:
+ buf.write("/")
+ buf.write(str(self.plugininstance))
+ if self.type:
+ buf.write("/")
+ buf.write(str(self.type))
+ if self.typeinstance:
+ buf.write("/")
+ buf.write(str(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_TIME_HR:
+ vl.time = nt.time = data / HR_TIME_DIV
+ elif kind == TYPE_INTERVAL:
+ vl.interval = data
+ elif kind == TYPE_INTERVAL_HR:
+ vl.interval = data / HR_TIME_DIV
+ 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 = 16384
+
+
+ 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, str):
+ iterable = self.decode(iterable)
+ return interpret_opcodes(iterable)
--- /dev/null
+# 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
--- /dev/null
+#-*- 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)
--- /dev/null
+#!/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 "$plugin_html"</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
--- /dev/null
+datadir: "/opt/collectd/var/lib/collectd/rrd/"
+libdir: "/opt/collectd/lib/collectd/"
+
--- /dev/null
+ 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).
+
--- /dev/null
+Options +ExecCGI
+AddHandler cgi-script .cgi
--- /dev/null
+#!/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 :
--- /dev/null
+#!/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 contains_invalid_chars
+{
+ my $str = shift;
+
+ for (split (m//, $str))
+ {
+ my $n = ord ($_);
+
+ # Whitespace is allowed.
+ if (($n >= 9) && ($n <= 13))
+ {
+ next;
+ }
+ elsif ($n < 32)
+ {
+ return (1);
+ }
+ }
+
+ return;
+}
+
+sub contains_invalid_chars
+{
+ my $str = shift;
+
+ for (split (m//, $str))
+ {
+ my $n = ord ($_);
+
+ # Whitespace is allowed.
+ if (($n >= 9) && ($n <= 13))
+ {
+ next;
+ }
+ elsif ($n < 32)
+ {
+ return (1);
+ }
+ }
+
+ return;
+}
+
+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))
+ {
+ next if contains_invalid_chars ($_);
+ 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))
+ {
+ next if contains_invalid_chars ($_);
+ 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)))
+ {
+ next if contains_invalid_chars ($_);
+ 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=$_");
+ next if contains_invalid_chars ($_);
+ 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}');">←</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}');">→</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 :
--- /dev/null
+#!/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 :
--- /dev/null
+Deny from all
--- /dev/null
+#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 :
--- /dev/null
+Deny from all
--- /dev/null
+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 :
--- /dev/null
+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 :
--- /dev/null
+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 :
--- /dev/null
+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 :
--- /dev/null
+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 :
--- /dev/null
+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 ({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', $obj->getTitle ($ident), '-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 :
--- /dev/null
+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 :
--- /dev/null
+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 :
--- /dev/null
+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 :
--- /dev/null
+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 :
--- /dev/null
+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 :
--- /dev/null
+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 :
--- /dev/null
+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 :
--- /dev/null
+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 :
--- /dev/null
+Options -ExecCGI
+SetHandler none
--- /dev/null
+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 : */
--- /dev/null
+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 : */
--- /dev/null
+#!/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 :
--- /dev/null
+# /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 ()
+
--- /dev/null
+/*
+ * /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 (¬if->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) */
+
--- /dev/null
+#!/bin/bash
+
+# collectd - contrib/exec-ksm.sh
+# Copyright (C) 2011 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>
+
+HOSTNAME="${COLLECTD_HOSTNAME:-$(hostname -f)}"
+INTERVAL="${COLLECTD_INTERVAL:-60}"
+
+function read_file() {
+ local type="$1"
+ local type_instance="$2"
+ local file_name="/sys/kernel/mm/ksm/$3"
+ local ident="$HOSTNAME/exec-ksm/vmpage_number-${type_instance}"
+
+ echo "PUTVAL \"$ident\" interval=$INTERVAL N:$(< $file_name)"
+}
+
+if [[ 0 -eq $(< /sys/kernel/mm/ksm/run) ]]; then
+ echo "$0: KSM not active." >&2
+ exit 1
+fi
+
+while sleep "$INTERVAL"
+do
+ read_file vmpage_number shared pages_shared
+ read_file vmpage_number saved pages_sharing
+ read_file vmpage_number unshared pages_unshared
+ read_file vmpage_number volatile pages_volatile
+ read_file total_operations scan full_scans
+done
+
+exit 0
--- /dev/null
+AddType temperature temperature
+
+Interval 300
+
+Script /tmp/ipmisens2
+Script /tmp/munin-sensors.pl
--- /dev/null
+#!/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 :
--- /dev/null
+# Run `perldoc exec-nagios.px' for details on this config file.
+
+NRPEConfig /etc/nrpe.cfg
+
+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>
--- /dev/null
+#!/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 $NRPEMap = {};
+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:
+
+ NRPEConfig "/etc/nrpe.cfg"
+ 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<NRPEConfig> I<File>
+
+Read the NRPE config and add the command definitions to an alias table. After
+reading the file you can use the NRPE command name rather than the script's
+filename within B<Script> blocks (see below). If both, the NRPE config and the
+B<Script> block, define arguments they will be merged by concatenating the
+arguments together in the order "NRPE-args Script-args".
+
+Please note that this option is rather dumb. It does not support "command
+argument processing" (i.e. replacing C<$ARG1$> and friends), inclusion of other
+NRPE config files, include directories etc.
+
+=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. If the B<NRPEConfig> is given above the B<Script> block, you may use
+the NRPE command name rather than the script's filename. 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 parse_nrpe_conf
+{
+ my $file = shift;
+ my $fh;
+ my $status;
+
+ $status = open ($fh, '<', $file);
+ if (!$status)
+ {
+ print STDERR "Reading NRPE config from \"$file\" failed: $!\n";
+ return;
+ }
+
+ while (<$fh>)
+ {
+ my $line = $_;
+ chomp ($line);
+
+ if ($line =~ m/^\s*command\[([^\]]+)\]\s*=\s*(.+)$/)
+ {
+ my $alias = $1;
+ my $script;
+ my $arguments;
+
+ ($script, $arguments) = split (' ', $2, 2);
+
+ if ($NRPEMap->{$alias})
+ {
+ print STDERR "Warning: NRPE command \"$alias\" redefined.\n";
+ }
+
+ $NRPEMap->{$alias} = { script => $script };
+ if ($arguments)
+ {
+ $NRPEMap->{$alias}{'arguments'} = $arguments;
+ }
+ }
+ } # while (<$fh>)
+
+ close ($fh);
+} # parse_nrpe_conf
+
+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
+
+# Update the script record. This function adds the name of the script /
+# executable to the hash and merges the configured and NRPE arguments if
+# required.
+sub update_script_opts
+{
+ my $opts = shift;
+ my $script = shift;
+ my $nrpe_args = shift;
+
+ $opts->{'script'} = $script;
+
+ if ($nrpe_args)
+ {
+ if ($opts->{'arguments'})
+ {
+ $opts->{'arguments'} = $nrpe_args . ' ' . $opts->{'arguments'};
+ }
+ else
+ {
+ $opts->{'arguments'} = $nrpe_args;
+ }
+ }
+} # update_script_opts
+
+sub handle_config_script
+{
+ my $scripts = shift;
+
+ for (keys %$scripts)
+ {
+ my $script = $_;
+ my $opts = $scripts->{$script};
+
+ my $nrpe_args = '';
+
+ # Check if the script exists in the NRPE map. If so, replace the alias name
+ # with the actual script name.
+ if ($NRPEMap->{$script})
+ {
+ if ($NRPEMap->{$script}{'arguments'})
+ {
+ $nrpe_args = $NRPEMap->{$script}{'arguments'};
+ }
+ $script = $NRPEMap->{$script}{'script'};
+ }
+
+ # Check if the script exists and is executable.
+ 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
+ {
+ # Add the script to the global @$Script array.
+ if (ref ($opts) eq 'ARRAY')
+ {
+ for (@$opts)
+ {
+ my $opt = $_;
+ update_script_opts ($opt, $script, $nrpe_args);
+ push (@$Scripts, $opt);
+ }
+ }
+ else
+ {
+ update_script_opts ($opts, $script, $nrpe_args);
+ push (@$Scripts, $opts);
+ }
+ }
+ } # for (keys %$scripts)
+} # handle_config_script
+
+sub handle_config
+{
+ my $config = shift;
+
+ if (defined ($config->{'nrpeconfig'}))
+ {
+ if (ref ($config->{'nrpeconfig'}) eq 'ARRAY')
+ {
+ for (@{$config->{'nrpeconfig'}})
+ {
+ parse_nrpe_conf ($_);
+ }
+ }
+ elsif (ref ($config->{'nrpeconfig'}) eq '')
+ {
+ parse_nrpe_conf ($config->{'nrpeconfig'});
+ }
+ else
+ {
+ print STDERR "Cannot handle ref type '"
+ . ref ($config->{'nrpeconfig'}) . "' for option 'NRPEConfig'.\n";
+ }
+ }
+
+ 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 :
--- /dev/null
+#!/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
--- /dev/null
+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
--- /dev/null
+#!/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
--- /dev/null
+#!/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"
+
--- /dev/null
+#!/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);
+}
--- /dev/null
+#!/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 $RRDFilter = 'rrd_filter.px';
+
+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";
+ if (($plugin eq 'df') and ($type eq 'df'))
+ {
+ print "$RRDFilter --infile=\"$path\" --outfile=\"$dir/$plugin-$type_inst/df_complex-free.rrd\" --map free:value\n";
+ print "$RRDFilter --infile=\"$path\" --outfile=\"$dir/$plugin-$type_inst/df_complex-used.rrd\" --map used:value\n";
+ }
+ else
+ {
+ 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
+ --rrdfilter <path> Path to the rrd_filter.px script
+ Default: $RRDFilter
+
+EOF
+ exit (1);
+} # }}} sub exit_usage
+
+GetOptions ("indir|i=s" => \$InDir,
+ "rrdtool=s" => \$RRDtool,
+ "rrdfilter=s" => \$RRDFilter,
+ "help|h" => \&exit_usage) or exit_usage ();
+
+print "#!/bin/bash\n\n";
+
+scan_dir ($InDir);
+
+# vim: set sw=2 sts=2 et fdm=marker :
--- /dev/null
+#!/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)
--- /dev/null
+-- 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 :
--- /dev/null
+-- 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);
--- /dev/null
+/*
+ * 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)+'×pan='+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 += '×pan='+encodeURIComponent(timespan);
+ var now = new Date();
+ graph_src += '&ts='+now.getTime();
+ graphList.push(graph_id+' '+encodeURIComponent(graph_alt)+(logarithmic ? '&logarithmic=1' : '')+(tinyLegend ? '&tinylegend=1' : '')+'×pan='+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(/×pan=[^&]*/);
+ 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(/×pan=[^&]*/, '');
+ if (timespan)
+ newSrc += '×pan='+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(/×pan=[^&]*/, '');
+ newSrc = newSrc+(logarithmic ? '&logarithmic=1' : '')+(tinyLegend ? '&tinylegend=1' : '')+'×pan='+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.");
+}
+
--- /dev/null
+<?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');
+
+?>
--- /dev/null
+<?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);
+}
+
+?>
--- /dev/null
+<?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}: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: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}:value:MIN',
+ 'DEF:avg={file}:value:AVERAGE',
+ 'DEF:max={file}:value: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}:value:MIN',
+ 'DEF:avg={file}:value:AVERAGE',
+ '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');
+ $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'=>'value');
+ }
+
+ 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);
+}
+
+?>
--- /dev/null
+<?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;
+}
+
+?>
--- /dev/null
+<?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");
+}
+
+?>
--- /dev/null
+<?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> <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;
+}
+?>
--- /dev/null
+#!/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)
--- /dev/null
+LoadPlugin apache
+#<Plugin apache>
+# URL "http://localhost/status?auto"
+# User "www-user"
+# Password "secret"
+# CACert "/etc/ssl/ca.crt"
+#</Plugin>
+
--- /dev/null
+#
+# 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"
+
--- /dev/null
+
+%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
--- /dev/null
+LoadPlugin email
+#<Plugin email>
+# SocketFile "/usr/var/run/collectd-email"
+# SocketGroup "collectd"
+# SocketPerms "0770"
+# MaxConns 5
+#</Plugin>
+
--- /dev/null
+#!/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
--- /dev/null
+LoadPlugin mysql
+
+#<Plugin mysql>
+# Host "database.serv.er"
+# User "db_user"
+# Password "secret"
+# Database "db_name"
+#</Plugin>
+
--- /dev/null
+LoadPlugin nginx
+
+#<Plugin nginx>
+# URL "http://localhost/status?auto"
+# User "www-user"
+# Password "secret"
+# CACert "/etc/ssl/ca.crt"
+#</Plugin>
--- /dev/null
+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>
+
--- /dev/null
+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>
+
--- /dev/null
+#!/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>
+
--- /dev/null
+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
--- /dev/null
+#!/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
--- /dev/null
+<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>
--- /dev/null
+#!/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 :
--- /dev/null
+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>
+
--- /dev/null
+#!/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
--- /dev/null
+<?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>
+
--- /dev/null
+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 = -lm
+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
+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 += 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_graphite.c utils_format_graphite.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_ETHSTAT
+pkglib_LTLIBRARIES += ethstat.la
+ethstat_la_SOURCES = ethstat.c
+ethstat_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" ethstat.la
+collectd_DEPENDENCIES += ethstat.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_MD
+pkglib_LTLIBRARIES += md.la
+md_la_SOURCES = md.c
+md_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" md.la
+collectd_DEPENDENCIES += md.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_NUMA
+pkglib_LTLIBRARIES += numa.la
+numa_la_SOURCES = numa.c
+numa_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" numa.la
+collectd_DEPENDENCIES += numa.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_GRAPHITE
+pkglib_LTLIBRARIES += write_graphite.la
+write_graphite_la_SOURCES = write_graphite.c \
+ utils_format_graphite.c utils_format_graphite.h \
+ utils_format_json.c utils_format_json.h
+write_graphite_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" write_graphite.la
+collectd_DEPENDENCIES += write_graphite.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 . 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;
--- /dev/null
+/**
+ * collectd - src/amqp.c
+ * Copyright (C) 2009 Sebastien Pahl
+ * Copyright (C) 2010-2012 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 "utils_format_graphite.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_FORMAT_GRAPHITE 3
+
+#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;
+ /* publish & graphite format only */
+ char *prefix;
+ char *postfix;
+ char escape_char;
+
+ /* 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->prefix);
+ sfree (conf->postfix);
+
+
+ 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 HAVE_AMQP_RPC_REPLY_T_LIBRARY_ERRNO
+ if (r.library_errno)
+ return (sstrerror (r.library_errno, buffer, buffer_size));
+#else
+ if (r.library_error)
+ return (sstrerror (r.library_error, buffer, buffer_size));
+#endif
+ 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 */
+
+#if HAVE_AMQP_RPC_REPLY_T_LIBRARY_ERRNO
+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 */
+#else
+static int camqp_create_exchange (camqp_config_t *conf) /* {{{ */
+{
+ amqp_exchange_declare_ok_t *ed_ret;
+ amqp_table_t argument_table;
+ struct amqp_table_entry_t_ argument_table_entries[1];
+
+ if (conf->exchange_type == NULL)
+ return (0);
+
+ /* Valid arguments: "auto_delete", "internal" */
+ argument_table.num_entries = STATIC_ARRAY_SIZE (argument_table_entries);
+ argument_table.entries = argument_table_entries;
+ argument_table_entries[0].key = amqp_cstring_bytes ("auto_delete");
+ argument_table_entries[0].value.kind = AMQP_FIELD_KIND_BOOLEAN;
+ argument_table_entries[0].value.value.boolean = 1;
+
+ 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,
+ /* arguments = */ argument_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 */
+#endif
+
+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,
+ /* arguments = */ AMQP_EMPTY_TABLE
+ );
+ 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)
+ {
+ struct timespec ts_interval;
+ ERROR ("amqp plugin: camqp_connect failed. "
+ "Will sleep for %.3f seconds.",
+ CDTIME_T_TO_DOUBLE (interval_g));
+ CDTIME_T_TO_TIMESPEC (interval_g, &ts_interval);
+ nanosleep (&ts_interval, /* remaining = */ NULL);
+ continue;
+ }
+
+ status = amqp_simple_wait_frame (conf->connection, &frame);
+ if (status < 0)
+ {
+ struct timespec ts_interval;
+ ERROR ("amqp plugin: amqp_simple_wait_frame failed. "
+ "Will sleep for %.3f seconds.",
+ CDTIME_T_TO_DOUBLE (interval_g));
+ camqp_close_connection (conf);
+ CDTIME_T_TO_TIMESPEC (interval_g, &ts_interval);
+ nanosleep (&ts_interval, /* remaining = */ NULL);
+ 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);
+ return (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 if (conf->format == CAMQP_FORMAT_GRAPHITE)
+ props.content_type = amqp_cstring_bytes("text/graphite");
+ 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 if (conf->format == CAMQP_FORMAT_GRAPHITE)
+ {
+ status = format_graphite (buffer, sizeof (buffer), ds, vl,
+ conf->prefix, conf->postfix, conf->escape_char);
+ if (status != 0)
+ {
+ ERROR ("amqp plugin: format_graphite failed with status %i.",
+ status);
+ return (status);
+ }
+ }
+ 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 if (strcasecmp ("Graphite", string) == 0)
+ conf->format = CAMQP_FORMAT_GRAPHITE;
+ 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;
+ /* publish & graphite only */
+ conf->prefix = NULL;
+ conf->postfix = NULL;
+ conf->escape_char = '_';
+ /* 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 if ((strcasecmp ("GraphitePrefix", child->key) == 0) && publish)
+ status = cf_util_get_string (child, &conf->prefix);
+ else if ((strcasecmp ("GraphitePostfix", child->key) == 0) && publish)
+ status = cf_util_get_string (child, &conf->postfix);
+ else if ((strcasecmp ("GraphiteEscapeChar", child->key) == 0) && publish)
+ {
+ char *tmp_buff = NULL;
+ status = cf_util_get_string (child, &tmp_buff);
+ if (strlen (tmp_buff) > 1)
+ WARNING ("amqp plugin: The option \"GraphiteEscapeChar\" handles "
+ "only one character. Others will be ignored.");
+ conf->escape_char = tmp_buff[0];
+ sfree (tmp_buff);
+ }
+ 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 = ¤t;
+ 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, ¤t, &voltage);
+#endif
+#if HAVE_IOKIT_IOKITLIB_H
+ get_via_generic_iokit (&charge_abs, ¤t, &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 = ¤t;
+ 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 */
--- /dev/null
+/**
+ * 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;
+
+/* FIXME: Enabled by default for backwards compatibility. */
+/* TODO: Remove time parsing code. */
+static _Bool config_parse_time = 1;
+
+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;
+ if (config_parse_time)
+ vl.time = TIME_T_TO_CDTIME_T (ts);
+ sstrncpy(vl.host, hostname_g, sizeof(vl.host));
+ sstrncpy(vl.plugin, "bind", sizeof(vl.plugin));
+ if (plugin_instance) {
+ sstrncpy(vl.plugin_instance, plugin_instance,
+ sizeof(vl.plugin_instance));
+ 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"
+ };
+
+ 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);
+ } /* }}} */
+
+ /* Record types in the cache */
+ if (view->cacherrsets != 0) /* {{{ */
+ {
+ char plugin_instance[DATA_MAX_NAME_LEN];
+ list_info_ptr_t list_info =
+ {
+ plugin_instance,
+ /* type = */ "dns_qtype_cached"
+ };
+
+ 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, ¤t_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 if (strcasecmp ("ParseTime", child->key) == 0)
+ cf_util_get_boolean (child, &config_parse_time);
+ 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 : */
--- /dev/null
+=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
--- /dev/null
+=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
--- /dev/null
+=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>
+
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+=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
--- /dev/null
+=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
+
--- /dev/null
+# 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
--- /dev/null
+=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
--- /dev/null
+=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>
--- /dev/null
+=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
--- /dev/null
+/**
+ * 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;
+ size_t dirlen;
+ int status;
+
+ dir = strdup (orig_dir);
+ if (dir == NULL)
+ {
+ char errbuf[1024];
+ ERROR ("strdup failed: %s",
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ return (-1);
+ }
+
+ dirlen = strlen (dir);
+ while ((dirlen > 0) && (dir[dirlen - 1] == '/'))
+ dir[--dirlen] = '\0';
+
+ if (dirlen <= 0)
+ return (-1);
+
+ status = chdir (dir);
+ if (status == 0)
+ {
+ free (dir);
+ return (0);
+ }
+ else if (errno != ENOENT)
+ {
+ char errbuf[1024];
+ ERROR ("change_basedir: chdir (%s): %s", dir,
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ free (dir);
+ return (-1);
+ }
+
+ status = mkdir (dir, S_IRWXU | S_IRWXG | S_IRWXO);
+ if (status != 0)
+ {
+ char errbuf[1024];
+ ERROR ("change_basedir: mkdir (%s): %s", dir,
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ free (dir);
+ return (-1);
+ }
+
+ status = chdir (dir);
+ if (status != 0)
+ {
+ char errbuf[1024];
+ ERROR ("change_basedir: chdir (%s): %s", dir,
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ free (dir);
+ return (-1);
+ }
+
+ free (dir);
+ return (0);
+} /* static int change_basedir (char *dir) */
+
+#if HAVE_LIBKSTAT
+static void update_kstat (void)
+{
+ if (kc == NULL)
+ {
+ if ((kc = kstat_open ()) == NULL)
+ ERROR ("Unable to open kstat control structure");
+ }
+ else
+ {
+ kid_t kid;
+ kid = kstat_chain_update (kc);
+ if (kid > 0)
+ {
+ INFO ("kstat chain has been updated");
+ plugin_init_all ();
+ }
+ else if (kid < 0)
+ ERROR ("kstat chain update failed");
+ /* else: everything works as expected */
+ }
+
+ return;
+} /* static void update_kstat (void) */
+#endif /* HAVE_LIBKSTAT */
+
+/* TODO
+ * Remove all settings but `-f' and `-C'
+ */
+static void exit_usage (int status)
+{
+ printf ("Usage: "PACKAGE" [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 */
--- /dev/null
+#
+# 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_ETHSTAT_TRUE@LoadPlugin ethstat
+#@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_MD_TRUE@LoadPlugin md
+#@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_NUMA_TRUE@LoadPlugin numa
+#@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_GRAPHITE_TRUE@LoadPlugin write_graphite
+#@BUILD_PLUGIN_WRITE_HTTP_TRUE@LoadPlugin write_http
+#@BUILD_PLUGIN_WRITE_REDIS_TRUE@LoadPlugin write_redis
+#@BUILD_PLUGIN_WRITE_MONGODB_TRUE@LoadPlugin write_mongodb
+#@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/"
+# ParseTime false
+# 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 ethstat>
+# Interface "eth0"
+# Map "rx_csum_offload_errors" "if_rx_errors" "checksum_offload"
+# Map "multicast" "if_multicast"
+# MappedOnly false
+#</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 md>
+# Device "/dev/md0"
+# IgnoreSelected false
+#</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>
+# <Instance "local">
+# Host "127.0.0.1"
+# Port "11211"
+# </Instance>
+#</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>
+# SensorConfigFile "/etc/sensors.conf"
+# 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
+# ReportBytes true
+#</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_graphite>
+# <Carbon>
+# Host "localhost"
+# Port "2003"
+# Prefix "collectd"
+# Postfix "collectd"
+# StoreRates false
+# AlwaysAppendDS false
+# EscapeCharacter "_"
+# </Carbon>
+#</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_mongodb>
+# <Node "example">
+# Host "localhost"
+# Port "27017"
+# Timeout 1000
+# StoreRates false
+# </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>
--- /dev/null
+=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
+I<Apache> webserver. Each line contains either an option (a key and a list of
+one or more values) or a section-start or -end. Empty lines and everything
+after a non-quoted hash-symbol (C<#>) is ignored. I<Keys> are unquoted
+strings, consisting only of alphanumeric characters and the underscore (C<_>)
+character. Keys are handled case insensitive by I<collectd> itself and all
+plugins included with it. I<Values> can either be an I<unquoted string>, a
+I<quoted string> (enclosed in double-quotes) a I<number> or a I<boolean>
+expression. I<Unquoted strings> consist of only alphanumeric characters and
+underscores (C<_>) and do not need to be quoted. I<Quoted strings> are
+enclosed in double quotes (C<">). You can use the backslash character (C<\>)
+to include double quotes as part of the string. I<Numbers> can be specified in
+decimal and floating point format (using a dot C<.> as decimal separator),
+hexadecimal when using the C<0x> prefix and octal with a leading zero (C<0>).
+I<Boolean> values are either B<true> or B<false>.
+
+Lines may be wrapped by using C<\> 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. 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 appropriate 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
+ # GraphitePrefix "collectd."
+ # GraphiteEscapeChar "_"
+ </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>.
+
+If set to B<Graphite>, values are encoded in the I<Graphite> format, which is
+"<metric> <value> <timestamp>\n". The C<Content-Type> header field will be set to
+C<text/graphite>.
+
+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>.
+
+=item B<GraphitePrefix> (Publish and B<Format>=I<Graphite> only)
+
+A prefix can be added in the metric name when outputting in the I<Graphite> format.
+It's added before the I<Host> name.
+Metric name will be "<prefix><host><postfix><plugin><type><name>"
+
+=item B<GraphitePostfix> (Publish and B<Format>=I<Graphite> only)
+
+A postfix can be added in the metric name when outputting in the I<Graphite> format.
+It's added after the I<Host> name.
+Metric name will be "<prefix><host><postfix><plugin><type><name>"
+
+=item B<GraphiteEscapeChar> (Publish and B<Format>=I<Graphite> only)
+
+Specify a character to replace dots (.) in the host part of the metric name.
+In I<Graphite> metric name, dots are used as separators between different
+metric parts (host, plugin, type).
+Default is "_" (I<Underscore>).
+
+=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/"
+ ParseTime false
+ 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<ParseTime> B<true>|B<false>
+
+When set to B<true>, the time provided by BIND will be parsed and used to
+dispatch the values. When set to B<false>, the local time source is queried.
+
+This setting is set to B<true> by default for backwards compatibility; setting
+this to B<false> is I<recommended> to avoid problems with timezones and
+localization.
+
+=item B<OpCodes> B<true>|B<false>
+
+When enabled, statistics about the I<"OpCodes">, for example the number of
+C<QUERY> packets, are collected.
+
+Default: Enabled.
+
+=item B<QTypes> B<true>|B<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> B<true>|B<false>
+
+Collect global server statistics, such as requests received over IPv4 and IPv6,
+successful queries, and failed updates.
+
+Default: Enabled.
+
+=item B<ZoneMaintStats> B<true>|B<false>
+
+Collect zone maintenance statistics, mostly information about notifications
+(zone updates) and zone transfers.
+
+Default: Enabled.
+
+=item B<ResolverStats> B<true>|B<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> B<true>|B<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> B<true>|B<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> B<true>|B<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<ethstat>
+
+The I<ethstat plugin> collects information about network interface cards (NICs)
+by talking directly with the underlying kernel driver using L<ioctl(2)>.
+
+B<Synopsis:>
+
+ <Plugin "ethstat">
+ Interface "eth0"
+ Map "rx_csum_offload_errors" "if_rx_errors" "checksum_offload"
+ Map "multicast" "if_multicast"
+ </Plugin>
+
+B<Options:>
+
+=over 4
+
+=item B<Interface> I<Name>
+
+Collect statistical information about interface I<Name>.
+
+=item B<Map> I<Name> I<Type> [I<TypeInstance>]
+
+By default, the plugin will submit values as type C<derive> and I<type
+instance> set to I<Name>, the name of the metric as reported by the driver. If
+an appropriate B<Map> option exists, the given I<Type> and, optionally,
+I<TypeInstance> will be used.
+
+=item B<MappedOnly> B<true>|B<false>
+
+When set to B<true>, only metrics that can be mapped to to a I<type> will be
+collected, all other metrics will be ignored. Defaults to B<false>.
+
+=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<md>
+
+The C<md plugin> collects information from Linux Software-RAID devices (md).
+
+All reported values are of the type C<md_disks>. Reported type instances are
+I<active>, I<failed> (present but not operational), I<spare> (hot stand-by) and
+I<missing> (physically absent) disks.
+
+=over 4
+
+=item B<Device> I<Device>
+
+Select md devices based on device name. The I<device name> is the basename of
+the device, i.e. the name of the block device without the leading C</dev/>.
+See B<IgnoreSelected> for more details.
+
+=item B<IgnoreSelected> B<true>|B<false>
+
+Invert device selection: If set to B<true>, all md devices B<except> those
+listed using B<Device> are collected. If B<false> (the default), only those
+listed are collected. If no configuration is given, the B<md> plugin will
+collect data from all md devices.
+
+=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/>
+
+ <Plugin "memcached">
+ <Instance "name">
+ Host "memcache.example.com"
+ Port 11211
+ </Instance>
+ </Plugin>
+
+The plugin configuration consists of one or more B<Instance> blocks which
+specify one I<memcached> connection each. Within the B<Instance> blocks, the
+following options are allowed:
+
+=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>.
+
+=item B<Socket> I<Path>
+
+Connect to I<memcached> using the UNIX domain socket at I<Path>. If this
+setting is given, the B<Host> and B<Port> settings are ignored.
+
+=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. The following
+example will export the metrics twice: Once to an "internal" server (without
+encryption and signing) and one to an external server (with cryptographic
+signature):
+
+ <Plugin "network">
+ # Export to an internal server
+ # (demonstrates usage without additional options)
+ Server "collectd.internal.tld"
+
+ # Export to an external server
+ # (demonstrates usage with signature options)
+ <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, which is the maximum
+payload size that can be transmitted in one Ethernet frame using IPv6E<nbsp>/
+UDP.
+
+On the server side, this limit should be set to the largest value used on
+I<any> client. Likewise, the value on the client must not be larger than the
+value on the server, or data will be lost.
+
+B<Compatibility:> Versions prior to I<versionE<nbsp>4.8> used a fixed sized
+buffer of 1024E<nbsp>bytes. Versions I<4.8>, I<4.9> and I<4.10> used a default
+value of 1024E<nbsp>bytes to avoid problems when sending data to an older
+server.
+
+=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<Host> I<Host>
+
+Hostname to use when dispatching values for this database. Defaults to using
+the global hostname of the I<collectd> instance.
+
+=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<Password> I<Password>
+
+Use I<Password> to authenticate when connecting to I<Redis>.
+
+=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<SensorConfigFile> I<File>
+
+Read the I<lm_sensors> configuration from I<File>. When unset (recommended),
+the library's default will be used.
+
+=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>).
+
+=item B<ReportBytes> B<false>|B<true>
+
+When enabled, the I<swap I/O> is reported in bytes. When disabled, the default,
+I<swap I/O> is reported in pages. This option is available under Linux only.
+
+=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.
+
+=item B<NotifyLevel> B<OKAY>|B<WARNING>|B<FAILURE>
+
+Controls which notifications should be sent to syslog. The default behaviour is
+not to send any. Less severe notifications always imply logging more severe
+notifications: Setting this to B<OKAY> means all notifications will be sent to
+syslog, setting this to B<WARNING> will send B<WARNING> and B<FAILURE>
+notifications but will dismiss B<OKAY> notifications. Setting this option to
+B<FAILURE> will only send failures to syslog.
+
+=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_graphite>
+
+The C<write_graphite> plugin writes data to I<Graphite>, an open-source metrics
+storage and graphing project. The plugin connects to I<Carbon>, the data layer
+of I<Graphite>, and sends data via the "line based" protocol (per default using
+portE<nbsp>2003). The data will be sent in blocks of at most 1428 bytes to
+minimize the number of network packets.
+
+Synopsis:
+
+ <Plugin write_graphite>
+ <Carbon>
+ Host "localhost"
+ Port "2003"
+ Prefix "collectd"
+ </Carbon>
+ </Plugin>
+
+=over 4
+
+=item B<Host> I<Address>
+
+Hostname or address to connect to. Defaults to C<localhost>.
+
+=item B<Port> I<Service>
+
+Service name or port number to connect to. Defaults to C<2003>.
+
+=item B<Prefix> I<String>
+
+When set, I<String> is added in front of the host name. Dots and whitespace are
+I<not> escaped in this string (see B<EscapeCharacter> below).
+
+=item B<Postfix> I<String>
+
+When set, I<String> is appended to the host name. Dots and whitespace are
+I<not> escaped in this string (see B<EscapeCharacter> below).
+
+=item B<EscapeCharacter> I<Char>
+
+I<Carbon> uses the dot (C<.>) as escape character and doesn't allow whitespace
+in the identifier. The B<EscapeCharacter> option determines which character
+dots, whitespace and control characters are replaced with. Defaults to
+underscore (C<_>).
+
+=item B<StoreRates> B<false>|B<true>
+
+If set to B<true> (the default), convert counter values to rates. If set to
+B<false> counter values are stored as is, i.E<nbsp>e. as an increasing integer
+number.
+
+=item B<SeparateInstances> B<false>|B<true>
+
+If set to B<true>, the plugin instance and type instance will be in their own
+path component, for example C<host.cpu.0.cpu.idle>. If set to B<false> (the
+default), the plugin and plugin instance (and likewise the type and type
+instance) are put into one component, for example C<host.cpu-0.cpu-idle>.
+
+=item B<AlwaysAppendDS> B<false>|B<true>
+
+If set the B<true>, append the name of the I<Data Source> (DS) to the "metric"
+identifier. If set to B<false> (the default), this is only done when there is
+more than one DS.
+
+=back
+
+=head2 Plugin C<write_mongodb>
+
+The I<write_mongodb plugin> will send values to I<MongoDB>, a schema-less
+NoSQL database.
+
+B<Synopsis:>
+
+ <Plugin "write_mongodb">
+ <Node "default">
+ Host "localhost"
+ Port "27017"
+ Timeout 1000
+ StoreRates true
+ </Node>
+ </Plugin>
+
+The plugin can send values to multiple instances of I<MongoDB> by specifying
+one B<Node> block for each instance. Within the B<Node> blocks, the following
+options are available:
+
+=over 4
+
+=item B<Host> I<Address>
+
+Hostname or address to connect to. Defaults to C<localhost>.
+
+=item B<Port> I<Service>
+
+Service name or port number to connect to. Defaults to C<27017>.
+
+=item B<Timeout> I<Timeout>
+
+Set the timeout for each operation on I<MongoDB> to I<Timeout> milliseconds.
+Setting this option to zero means no timeout, which is the default.
+
+=item B<StoreRates> B<false>|B<true>
+
+If set to B<true> (the default), convert counter values to rates. If set to
+B<false> counter values are stored as is, i.e. as an increasing integer
+number.
+
+=back
+
+=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 THRESHOLD CONFIGURATION
+
+Starting with version C<4.3.0> 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
+collectd takes itself is to generate and dispatch a "notification". 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
+B<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<"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.
+
+Here is a configuration example to get you started. Read below for more
+information.
+
+ <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>
+ </Host>
+ </Threshold>
+
+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<nbsp>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<nbsp>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<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<Number>
+
+Delay creating the notification until the threshold has been passed I<Number>
+times. When a notification has been generated, or when a subsequent value is
+inside the threshold, the counter is reset. If, for example, a value is
+collected once every 10E<nbsp>seconds and B<Hits> is set to 3, a notification
+will be dispatched at most once every 30E<nbsp>seconds.
+
+This is useful when short bursts are not a problem. If, for example, 100% CPU
+usage for up to a minute is normal (and data is collected every
+10E<nbsp>seconds), you could set B<Hits> to B<6> to account for this.
+
+=item B<Hysteresis> I<Number>
+
+When set to non-zero, a hysteresis value is applied when checking minimum and
+maximum bounds. This is useful for values that increase slowly and fluctuate a
+bit while doing so. When these values come close to the threshold, they may
+"flap", i.e. switch between failure / warning case and okay case repeatedly.
+
+If, for example, the threshold is configures as
+
+ WarningMax 100.0
+ Hysteresis 1.0
+
+then a I<Warning> notification is created when the value exceeds I<101> and the
+corresponding I<Okay> notification is only created once the value falls below
+I<99>, thus avoiding the "flapping".
+
+=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<"FAILURE">|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
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+=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
--- /dev/null
+/**
+ * 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 (value, (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 : */
+
--- /dev/null
+=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
--- /dev/null
+/**
+ * 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 : */
+
--- /dev/null
+=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
--- /dev/null
+/**
+ * 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, S_IRWXU | S_IRWXG | S_IRWXO) == 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_orig, value_t *ret_value, int ds_type)
+{
+ char *value;
+ char *endptr = NULL;
+ size_t value_len;
+
+ if (value_orig == NULL)
+ return (EINVAL);
+
+ value = strdup (value_orig);
+ if (value == NULL)
+ return (ENOMEM);
+ value_len = strlen (value);
+
+ while ((value_len > 0) && isspace ((int) value[value_len - 1]))
+ {
+ value[value_len - 1] = 0;
+ value_len--;
+ }
+
+ 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:
+ sfree (value);
+ ERROR ("parse_value: Invalid data source type: %i.", ds_type);
+ return -1;
+ }
+
+ if (value == endptr) {
+ sfree (value);
+ 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_orig);
+
+ sfree (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 */
--- /dev/null
+/**
+ * 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);
+/* Returns the number of bytes read or negative on error. */
+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 */
--- /dev/null
+/**
+ * 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_service (const oconfig_item_t *ci, char **ret_string) /* {{{ */
+{
+ int port;
+ char *service;
+ int status;
+
+ if (ci->values_num != 1)
+ {
+ ERROR ("cf_util_get_service: The %s option requires exactly "
+ "one argument.", ci->key);
+ return (-1);
+ }
+
+ if (ci->values[0].type == OCONFIG_TYPE_STRING)
+ return (cf_util_get_string (ci, ret_string));
+ if (ci->values[0].type != OCONFIG_TYPE_NUMBER)
+ {
+ ERROR ("cf_util_get_service: The %s option requires "
+ "exactly one string or numeric argument.",
+ ci->key);
+ }
+
+ port = 0;
+ status = cf_util_get_int (ci, &port);
+ if (status != 0)
+ return (status);
+ else if ((port < 1) || (port > 65535))
+ {
+ ERROR ("cf_util_get_service: The port number given "
+ "for the %s option is out of "
+ "range (%i).", ci->key, port);
+ return (-1);
+ }
+
+ service = malloc (6);
+ if (service == NULL)
+ {
+ ERROR ("cf_util_get_service: Out of memory.");
+ return (-1);
+ }
+ ssnprintf (service, 6, "%i", port);
+
+ sfree (*ret_string);
+ *ret_string = service;
+
+ return (0);
+} /* }}} int cf_util_get_service */
+
+int cf_util_get_cdtime (const oconfig_item_t *ci, cdtime_t *ret_value) /* {{{ */
+{
+ if ((ci == NULL) || (ret_value == NULL))
+ return (EINVAL);
+
+ if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
+ {
+ ERROR ("cf_util_get_cdtime: The %s option requires "
+ "exactly one numeric argument.", ci->key);
+ return (-1);
+ }
+
+ if (ci->values[0].value.number < 0.0)
+ {
+ ERROR ("cf_util_get_cdtime: The numeric argument of the %s "
+ "option must not be negative.", ci->key);
+ return (-1);
+ }
+
+ *ret_value = DOUBLE_TO_CDTIME_T (ci->values[0].value.number);
+
+ return (0);
+} /* }}} int cf_util_get_cdtime */
+
--- /dev/null
+#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);
+
+/* Assures that the config option is either a service name (a string) or a port
+ * number (an integer in the appropriate range) and returns a newly allocated
+ * string. If ret_string points to a non-NULL pointer, it is freed before
+ * assigning a new value. */
+int cf_util_get_service (const oconfig_item_t *ci, char **ret_string);
+
+int cf_util_get_cdtime (const oconfig_item_t *ci, cdtime_t *ret_value);
+
+#endif /* defined(CONFIGFILE_H) */
--- /dev/null
+/**
+ * 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 (value_t conntrack)
+{
+ value_list_t vl = VALUE_LIST_INIT;
+
+ vl.values = &conntrack;
+ vl.values_len = 1;
+ sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+ sstrncpy (vl.plugin, "conntrack", sizeof (vl.plugin));
+ sstrncpy (vl.type, "conntrack", sizeof (vl.type));
+
+ plugin_dispatch_values (&vl);
+} /* static void conntrack_submit */
+
+static int conntrack_read (void)
+{
+ value_t conntrack;
+ FILE *fh;
+ char buffer[64];
+ size_t buffer_len;
+
+ fh = fopen (CONNTRACK_FILE, "r");
+ if (fh == NULL)
+ return (-1);
+
+ memset (buffer, 0, sizeof (buffer));
+ if (fgets (buffer, sizeof (buffer), fh) == NULL)
+ {
+ fclose (fh);
+ return (-1);
+ }
+ fclose (fh);
+
+ /* strip trailing newline. */
+ buffer_len = strlen (buffer);
+ while ((buffer_len > 0) && isspace ((int) buffer[buffer_len - 1]))
+ {
+ buffer[buffer_len - 1] = 0;
+ buffer_len--;
+ }
+
+ if (parse_value (buffer, &conntrack, DS_TYPE_GAUGE) != 0)
+ return (-1);
+
+ conntrack_submit (conntrack);
+
+ return (0);
+} /* static int conntrack_read */
+
+void module_register (void)
+{
+ plugin_register_read ("conntrack", conntrack_read);
+} /* void module_register */
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * 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);
+}
--- /dev/null
+/**
+ * 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;
+
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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
+ */
--- /dev/null
+/**
+ * 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)
+ {
+ if (strcmp (mnt_ptr->type, "rootfs") == 0)
+ continue;
+ 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 and temporary variable are needed to avoid
+ * compiler warnings.
+ * ((struct statvfs).f_bavail is unsigned (POSIX)) */
+ int64_t signed_bavail = (int64_t) statbuf.f_bavail;
+ if (signed_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 */
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * 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);
+ return ((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);
+ return ((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 : */
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * collectd - src/ethstat.c
+ * Copyright (C) 2011 Cyril Feraudet
+ * 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; 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:
+ * Cyril Feraudet <cyril at feraudet.com>
+ * Florian "octo" Forster <octo@collectd.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+#include "utils_avltree.h"
+#include "utils_complain.h"
+
+#if HAVE_SYS_IOCTL_H
+# include <sys/ioctl.h>
+#endif
+#if HAVE_NET_IF_H
+# include <net/if.h>
+#endif
+#if HAVE_LINUX_SOCKIOS_H
+# include <linux/sockios.h>
+#endif
+#if HAVE_LINUX_ETHTOOL_H
+# include <linux/ethtool.h>
+#endif
+
+struct value_map_s
+{
+ char type[DATA_MAX_NAME_LEN];
+ char type_instance[DATA_MAX_NAME_LEN];
+};
+typedef struct value_map_s value_map_t;
+
+static char **interfaces = NULL;
+static size_t interfaces_num = 0;
+
+static c_avl_tree_t *value_map = NULL;
+
+static _Bool collect_mapped_only = 0;
+
+static int ethstat_add_interface (const oconfig_item_t *ci) /* {{{ */
+{
+ char **tmp;
+ int status;
+
+ tmp = realloc (interfaces,
+ sizeof (*interfaces) * (interfaces_num + 1));
+ if (tmp == NULL)
+ return (-1);
+ interfaces = tmp;
+
+ status = cf_util_get_string (ci, interfaces + interfaces_num);
+ if (status != 0)
+ return (status);
+
+ interfaces_num++;
+ INFO("ethstat plugin: Registred interface %s",
+ interfaces[interfaces_num - 1]);
+
+ return (0);
+} /* }}} int ethstat_add_interface */
+
+static int ethstat_add_map (const oconfig_item_t *ci) /* {{{ */
+{
+ value_map_t *map;
+ int status;
+
+ if ((ci->values_num < 2)
+ || (ci->values_num > 3)
+ || (ci->values[0].type != OCONFIG_TYPE_STRING)
+ || (ci->values[1].type != OCONFIG_TYPE_STRING)
+ || ((ci->values_num == 3)
+ && (ci->values[2].type != OCONFIG_TYPE_STRING)))
+ {
+ ERROR ("ethstat plugin: The %s option requires "
+ "two or three string arguments.", ci->key);
+ return (-1);
+ }
+
+ map = malloc (sizeof (*map));
+ if (map == NULL)
+ {
+ ERROR ("ethstat plugin: malloc(3) failed.");
+ return (ENOMEM);
+ }
+ memset (map, 0, sizeof (*map));
+
+ sstrncpy (map->type, ci->values[1].value.string, sizeof (map->type));
+ if (ci->values_num == 2)
+ sstrncpy (map->type_instance, ci->values[2].value.string,
+ sizeof (map->type_instance));
+
+ if (value_map == NULL)
+ {
+ value_map = c_avl_create ((void *) strcmp);
+ if (value_map == NULL)
+ {
+ sfree (map);
+ ERROR ("ethstat plugin: c_avl_create() failed.");
+ return (-1);
+ }
+ }
+
+ status = c_avl_insert (value_map,
+ /* key = */ ci->values[0].value.string,
+ /* value = */ map);
+ if (status != 0)
+ {
+ sfree (map);
+ if (status > 0)
+ ERROR ("ethstat plugin: Multiple mappings for \"%s\".",
+ ci->values[0].value.string);
+ else
+ ERROR ("ethstat plugin: c_avl_insert(\"%s\") failed.",
+ ci->values[0].value.string);
+ return (-1);
+ }
+
+ return (0);
+} /* }}} int ethstat_add_map */
+
+static int ethstat_config (oconfig_item_t *ci) /* {{{ */
+{
+ int i;
+
+ for (i = 0; i < ci->children_num; i++)
+ {
+ oconfig_item_t *child = ci->children + i;
+
+ if (strcasecmp ("Interface", child->key) == 0)
+ ethstat_add_interface (child);
+ else if (strcasecmp ("Map", child->key) == 0)
+ ethstat_add_map (child);
+ else if (strcasecmp ("MappedOnly", child->key) == 0)
+ (void) cf_util_get_boolean (child, &collect_mapped_only);
+ else
+ WARNING ("ethstat plugin: The config option \"%s\" is unknown.",
+ child->key);
+ }
+
+ return (0);
+} /* }}} */
+
+static void ethstat_submit_value (const char *device,
+ const char *type_instance, derive_t value)
+{
+ static c_complain_t complain_no_map = C_COMPLAIN_INIT_STATIC;
+
+ value_t values[1];
+ value_list_t vl = VALUE_LIST_INIT;
+ value_map_t *map = NULL;
+
+ if (value_map != NULL)
+ c_avl_get (value_map, type_instance, (void *) &map);
+
+ /* If the "MappedOnly" option is specified, ignore unmapped values. */
+ if (collect_mapped_only && (map == NULL))
+ {
+ if (value_map == NULL)
+ c_complain (LOG_WARNING, &complain_no_map,
+ "ethstat plugin: The \"MappedOnly\" option has been set to true, "
+ "but no mapping has been configured. All values will be ignored!");
+ return;
+ }
+
+ values[0].derive = value;
+ vl.values = values;
+ vl.values_len = 1;
+
+ sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+ sstrncpy (vl.plugin, "ethstat", sizeof (vl.plugin));
+ sstrncpy (vl.plugin_instance, device, sizeof (vl.plugin_instance));
+ if (map != NULL)
+ {
+ sstrncpy (vl.type, map->type, sizeof (vl.type));
+ sstrncpy (vl.type_instance, map->type_instance,
+ sizeof (vl.type_instance));
+ }
+ else
+ {
+ sstrncpy (vl.type, "derive", sizeof (vl.type));
+ sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
+ }
+
+ plugin_dispatch_values (&vl);
+}
+
+static int ethstat_read_interface (char *device)
+{
+ int fd;
+ struct ifreq req;
+ struct ethtool_drvinfo drvinfo;
+ struct ethtool_gstrings *strings;
+ struct ethtool_stats *stats;
+ size_t n_stats;
+ size_t strings_size;
+ size_t stats_size;
+ size_t i;
+ int status;
+
+ memset (&req, 0, sizeof (req));
+ sstrncpy(req.ifr_name, device, sizeof (req.ifr_name));
+
+ fd = socket(AF_INET, SOCK_DGRAM, /* protocol = */ 0);
+ if (fd < 0)
+ {
+ char errbuf[1024];
+ ERROR("ethstat plugin: Failed to open control socket: %s",
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ return 1;
+ }
+
+ memset (&drvinfo, 0, sizeof (drvinfo));
+ drvinfo.cmd = ETHTOOL_GDRVINFO;
+ req.ifr_data = (void *) &drvinfo;
+ status = ioctl (fd, SIOCETHTOOL, &req);
+ if (status < 0)
+ {
+ char errbuf[1024];
+ close (fd);
+ ERROR ("ethstat plugin: Failed to get driver information "
+ "from %s: %s", device,
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ return (-1);
+ }
+
+ n_stats = (size_t) drvinfo.n_stats;
+ if (n_stats < 1)
+ {
+ close (fd);
+ ERROR("ethstat plugin: No stats available for %s", device);
+ return (-1);
+ }
+
+ strings_size = sizeof (struct ethtool_gstrings)
+ + (n_stats * ETH_GSTRING_LEN);
+ stats_size = sizeof (struct ethtool_stats)
+ + (n_stats * sizeof (uint64_t));
+
+ strings = malloc (strings_size);
+ stats = malloc (stats_size);
+ if ((strings == NULL) || (stats == NULL))
+ {
+ close (fd);
+ sfree (strings);
+ sfree (stats);
+ ERROR("ethstat plugin: malloc(3) failed.");
+ return (-1);
+ }
+
+ strings->cmd = ETHTOOL_GSTRINGS;
+ strings->string_set = ETH_SS_STATS;
+ strings->len = n_stats;
+ req.ifr_data = (void *) strings;
+ status = ioctl (fd, SIOCETHTOOL, &req);
+ if (status < 0)
+ {
+ char errbuf[1024];
+ close (fd);
+ free (strings);
+ free (stats);
+ ERROR ("ethstat plugin: Cannot get strings from %s: %s",
+ device,
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ return (-1);
+ }
+
+ stats->cmd = ETHTOOL_GSTATS;
+ stats->n_stats = n_stats;
+ req.ifr_data = (void *) stats;
+ status = ioctl (fd, SIOCETHTOOL, &req);
+ if (status < 0)
+ {
+ char errbuf[1024];
+ close (fd);
+ free(strings);
+ free(stats);
+ ERROR("ethstat plugin: Reading statistics from %s failed: %s",
+ device,
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ return (-1);
+ }
+
+ for (i = 0; i < n_stats; i++)
+ {
+ const char *stat_name;
+
+ stat_name = (void *) &strings->data[i * ETH_GSTRING_LEN];
+ DEBUG("ethstat plugin: device = \"%s\": %s = %"PRIu64,
+ device, stat_name, (uint64_t) stats->data[i]);
+ ethstat_submit_value (device,
+ stat_name, (derive_t) stats->data[i]);
+ }
+
+ close (fd);
+ sfree (strings);
+ sfree (stats);
+
+ return (0);
+} /* }}} ethstat_read_interface */
+
+static int ethstat_read(void)
+{
+ size_t i;
+
+ for (i = 0; i < interfaces_num; i++)
+ ethstat_read_interface (interfaces[i]);
+
+ return 0;
+}
+
+void module_register (void)
+{
+ plugin_register_complex_config ("ethstat", ethstat_config);
+ plugin_register_read ("ethstat", ethstat_read);
+}
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
--- /dev/null
+/**
+ * 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 (1)
+ {
+ int len;
+
+ status = select (highest_fd + 1, ©, NULL, NULL, NULL);
+ if (status < 0)
+ {
+ if (errno == EINTR)
+ continue;
+ break;
+ }
+
+ if (FD_ISSET(fd, ©))
+ {
+ 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, ©))
+ {
+ 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
+ */
--- /dev/null
+/**
+ * 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 :
+ */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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);
+}
--- /dev/null
+/**
+ * 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;
+ 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, "obytes")) == -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 */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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
+ */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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];
+ int cpu_count;
+ char *fields[256];
+
+ /*
+ * Example content:
+ * CPU0 CPU1 CPU2 CPU3
+ * 0: 2574 1 3 2 IO-APIC-edge timer
+ * 1: 102553 158669 218062 70587 IO-APIC-edge i8042
+ * 8: 0 0 0 1 IO-APIC-edge rtc0
+ */
+ 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);
+ }
+
+ /* Get CPU count from the first line */
+ if(fgets (buffer, sizeof (buffer), fh) != NULL) {
+ cpu_count = strsplit (buffer, fields,
+ STATIC_ARRAY_SIZE (fields));
+ } else {
+ ERROR ("irq plugin: unable to get CPU count from first line "
+ "of /proc/interrupts");
+ return (-1);
+ }
+
+ while (fgets (buffer, sizeof (buffer), fh) != NULL)
+ {
+ char *irq_name;
+ size_t irq_name_len;
+ derive_t irq_value;
+ int i;
+ int fields_num;
+ int irq_values_to_parse;
+
+ fields_num = strsplit (buffer, fields,
+ STATIC_ARRAY_SIZE (fields));
+ if (fields_num < 2)
+ continue;
+
+ /* Parse this many numeric fields, skip the rest
+ * (+1 because first there is a name of irq in each line) */
+ if (fields_num >= cpu_count + 1)
+ irq_values_to_parse = cpu_count;
+ else
+ irq_values_to_parse = fields_num - 1;
+
+ /* First field is irq name and colon */
+ 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 <= irq_values_to_parse; 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 */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+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
--- /dev/null
+/**
+ * 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;
+
+ lcc_response_free (&res);
+
+ 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 : */
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * 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 : */
+
--- /dev/null
+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}
--- /dev/null
+ 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.
--- /dev/null
+2007-02-15, Version 0.1.1
+ * src/parser.y: Fixes a memory leak.
+
+2007-02-11, Version 0.1.0
+ * Initial release.
--- /dev/null
+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
--- /dev/null
+#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 */
--- /dev/null
+/**
+ * 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
+ */
--- /dev/null
+#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 */
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * 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 */
+
--- /dev/null
+/**
+ * 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
+ */
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * 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")) {
+ log_level = parse_log_severity(value);
+ if (log_level == -1) return 1; /* to keep previous behaviour */
+ }
+ 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, ×tamp_tm);
+
+ strftime (timestamp_str, sizeof (timestamp_str), "%Y-%m-%d %H:%M:%S",
+ ×tamp_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 : */
+
--- /dev/null
+/**
+ * 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 : */
+
--- /dev/null
+/**
+ * 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);
+}
--- /dev/null
+/**
+ * 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
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
+
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
+
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * collectd - src/md.c
+ * Copyright (C) 2010,2011 Michael Hanselmann
+ *
+ * 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:
+ * Michael Hanselmann
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "utils_ignorelist.h"
+
+#include <sys/ioctl.h>
+
+#include <linux/major.h>
+#include <linux/raid/md_u.h>
+
+#define PROC_DISKSTATS "/proc/diskstats"
+#define DEV_DIR "/dev"
+
+static const char *config_keys[] =
+{
+ "Device",
+ "IgnoreSelected"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static ignorelist_t *ignorelist = NULL;
+
+static int md_config (const char *key, const char *value)
+{
+ if (ignorelist == NULL)
+ ignorelist = ignorelist_create (/* invert = */ 1);
+ if (ignorelist == NULL)
+ return (1);
+
+ if (strcasecmp (key, "Device") == 0)
+ {
+ ignorelist_add (ignorelist, value);
+ }
+ else if (strcasecmp (key, "IgnoreSelected") == 0)
+ {
+ ignorelist_set_invert (ignorelist, IS_TRUE (value) ? 0 : 1);
+ }
+ else
+ {
+ return (-1);
+ }
+
+ return (0);
+}
+
+static void md_submit (const int minor, const char *type_instance,
+ gauge_t value)
+{
+ value_t values[1];
+ value_list_t vl = VALUE_LIST_INIT;
+
+ values[0].gauge = value;
+
+ vl.values = values;
+ vl.values_len = 1;
+ sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+ sstrncpy (vl.plugin, "md", sizeof (vl.plugin));
+ ssnprintf (vl.plugin_instance, sizeof (vl.plugin_instance),
+ "%i", minor);
+ sstrncpy (vl.type, "md_disks", sizeof (vl.type));
+ sstrncpy (vl.type_instance, type_instance,
+ sizeof (vl.type_instance));
+
+ plugin_dispatch_values (&vl);
+} /* void md_submit */
+
+static void md_process (const int minor, const char *path)
+{
+ char errbuf[1024];
+ int fd;
+ struct stat st;
+ mdu_array_info_t array;
+ gauge_t disks_missing;
+
+ fd = open (path, O_RDONLY);
+ if (fd < 0)
+ {
+ WARNING ("md: open(%s): %s", path,
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ return;
+ }
+
+ if (fstat (fd, &st) < 0)
+ {
+ WARNING ("md: Unable to fstat file descriptor for %s: %s", path,
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ close (fd);
+ return;
+ }
+
+ if (! S_ISBLK (st.st_mode))
+ {
+ WARNING ("md: %s is no block device", path);
+ close (fd);
+ return;
+ }
+
+ if (st.st_rdev != makedev (MD_MAJOR, minor))
+ {
+ WARNING ("md: Major/minor of %s are %i:%i, should be %i:%i",
+ path, (int)major(st.st_rdev), (int)minor(st.st_rdev),
+ (int)MD_MAJOR, minor);
+ close (fd);
+ return;
+ }
+
+ /* Retrieve md information */
+ if (ioctl (fd, GET_ARRAY_INFO, &array) < 0) {
+ WARNING ("md: Unable to retrieve array info from %s: %s", path,
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ close (fd);
+ return;
+ }
+
+ close (fd);
+
+ /*
+ * The mdu_array_info_t structure contains numbers of disks in the array.
+ * However, disks are accounted for more than once:
+ *
+ * active: Number of active (in sync) disks.
+ * spare: Number of stand-by disks.
+ * working: Number of working disks. (active + sync)
+ * failed: Number of failed disks.
+ * nr: Number of physically present disks. (working + failed)
+ * raid: Number of disks in the RAID. This may be larger than "nr" if
+ * disks are missing and smaller than "nr" when spare disks are
+ * around.
+ */
+ md_submit (minor, "active", (gauge_t) array.active_disks);
+ md_submit (minor, "failed", (gauge_t) array.failed_disks);
+ md_submit (minor, "spare", (gauge_t) array.spare_disks);
+
+ disks_missing = 0.0;
+ if (array.raid_disks > array.nr_disks)
+ disks_missing = (gauge_t) (array.raid_disks - array.nr_disks);
+ md_submit (minor, "missing", disks_missing);
+} /* void md_process */
+
+static int md_read (void)
+{
+ FILE *fh;
+ char buffer[1024];
+
+ fh = fopen (PROC_DISKSTATS, "r");
+ if (fh == NULL) {
+ char errbuf[1024];
+ WARNING ("md: Unable to open %s: %s",
+ PROC_DISKSTATS ,
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ return (-1);
+ }
+
+ /* Iterate md devices */
+ while (fgets (buffer, sizeof (buffer), fh) != NULL)
+ {
+ char path[PATH_MAX];
+ char *fields[4];
+ char *name;
+ int major, minor;
+
+ /* Extract interesting fields */
+ if (strsplit (buffer, fields, STATIC_ARRAY_SIZE(fields)) < 3)
+ continue;
+
+ major = atoi (fields[0]);
+
+ if (major != MD_MAJOR)
+ continue;
+
+ minor = atoi (fields[1]);
+ name = fields[2];
+
+ if (ignorelist_match (ignorelist, name))
+ continue;
+
+ /* FIXME: Don't hardcode path. Walk /dev collecting major,
+ * minor and name, then use lookup table to find device.
+ * Alternatively create a temporary device file with correct
+ * major/minor, but that again can be tricky if the filesystem
+ * with the device file is mounted using the "nodev" option.
+ */
+ ssnprintf (path, sizeof (path), "%s/%s", DEV_DIR, name);
+
+ md_process (minor, path);
+ }
+
+ fclose (fh);
+
+ return (0);
+} /* int md_read */
+
+void module_register (void)
+{
+ plugin_register_config ("md", md_config, config_keys, config_keys_num);
+ plugin_register_read ("md", md_read);
+} /* void module_register */
--- /dev/null
+/**
+ * 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);
+ else 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 : */
--- /dev/null
+/**
+ * collectd - src/memcached.c, based on src/hddtemp.c
+ * Copyright (C) 2007 Antony Dovgal
+ * Copyright (C) 2007-2012 Florian Forster
+ * Copyright (C) 2009 Doug MacEachern
+ * Copyright (C) 2009 Franck Lombardi
+ * Copyright (C) 2012 Nicolas Szalay
+ *
+ * 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
+ * Nicolas Szalay
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+
+#include <netdb.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#define MEMCACHED_DEF_HOST "127.0.0.1"
+#define MEMCACHED_DEF_PORT "11211"
+
+struct memcached_s
+{
+ char *name;
+ char *socket;
+ char *host;
+ char *port;
+};
+typedef struct memcached_s memcached_t;
+
+static _Bool memcached_have_instances = 0;
+
+static void memcached_free (memcached_t *st)
+{
+ if (st == NULL)
+ return;
+
+ sfree (st->name);
+ sfree (st->socket);
+ sfree (st->host);
+ sfree (st->port);
+}
+
+static int memcached_connect_unix (memcached_t *st)
+{
+ struct sockaddr_un serv_addr;
+ int fd;
+
+ memset (&serv_addr, 0, sizeof (serv_addr));
+ serv_addr.sun_family = AF_UNIX;
+ sstrncpy (serv_addr.sun_path, st->socket,
+ sizeof (serv_addr.sun_path));
+
+ /* create our socket descriptor */
+ fd = socket (AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
+ {
+ char errbuf[1024];
+ ERROR ("memcached plugin: memcached_connect_unix: socket(2) failed: %s",
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ return (-1);
+ }
+
+ return (fd);
+} /* int memcached_connect_unix */
+
+static int memcached_connect_inet (memcached_t *st)
+{
+ char *host;
+ char *port;
+
+ struct addrinfo ai_hints;
+ struct addrinfo *ai_list, *ai_ptr;
+ int status;
+ int fd = -1;
+
+ 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;
+ ai_hints.ai_protocol = 0;
+
+ host = (st->host != NULL) ? st->host : MEMCACHED_DEF_HOST;
+ port = (st->port != NULL) ? st->port : MEMCACHED_DEF_PORT;
+
+ ai_list = NULL;
+ status = getaddrinfo (host, port, &ai_hints, &ai_list);
+ if (status != 0)
+ {
+ char errbuf[1024];
+ ERROR ("memcached plugin: memcached_connect_inet: "
+ "getaddrinfo(%s,%s) failed: %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 */
+ fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol);
+ if (fd < 0)
+ {
+ char errbuf[1024];
+ WARNING ("memcached plugin: memcached_connect_inet: "
+ "socket(2) failed: %s",
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ continue;
+ }
+
+ /* connect to the memcached daemon */
+ status = (int) connect (fd, 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);
+ return (fd);
+} /* int memcached_connect_inet */
+
+static int memcached_connect (memcached_t *st)
+{
+ if (st->socket != NULL)
+ return (memcached_connect_unix (st));
+ else
+ return (memcached_connect_inet (st));
+}
+
+static int memcached_query_daemon (char *buffer, size_t buffer_size, memcached_t *st)
+{
+ int fd = -1;
+ int status;
+ size_t buffer_fill;
+
+ fd = memcached_connect (st);
+ if (fd < 0) {
+ ERROR ("memcached plugin: Instance \"%s\" could not connect to daemon.",
+ st->name);
+ return -1;
+ }
+
+ status = (int) swrite (fd, "stats\r\n", strlen ("stats\r\n"));
+ if (status != 0)
+ {
+ char errbuf[1024];
+ ERROR ("memcached plugin: write(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 = (int) recv (fd, buffer + buffer_fill,
+ buffer_size - buffer_fill, /* flags = */ 0)) != 0)
+ {
+ char const end_token[5] = {'E', 'N', 'D', '\r', '\n'};
+ if (status < 0)
+ {
+ char errbuf[1024];
+
+ if ((errno == EAGAIN) || (errno == EINTR))
+ continue;
+
+ ERROR ("memcached: Error reading from socket: %s",
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ shutdown(fd, SHUT_RDWR);
+ close (fd);
+ return (-1);
+ }
+
+ buffer_fill += (size_t) status;
+ if (buffer_fill > buffer_size)
+ {
+ buffer_fill = buffer_size;
+ WARNING ("memcached plugin: Message was truncated.");
+ break;
+ }
+
+ /* If buffer ends in end_token, we have all the data. */
+ if (memcmp (buffer + buffer_fill - sizeof (end_token),
+ end_token, sizeof (end_token)) == 0)
+ break;
+ } /* while (recv) */
+
+ status = 0;
+ if (buffer_fill == 0)
+ {
+ WARNING ("memcached plugin: No data returned by memcached.");
+ status = -1;
+ }
+
+ shutdown(fd, SHUT_RDWR);
+ close(fd);
+ return (status);
+} /* int memcached_query_daemon */
+
+static void memcached_init_vl (value_list_t *vl, memcached_t const *st)
+{
+ sstrncpy (vl->plugin, "memcached", sizeof (vl->plugin));
+ if (strcmp (st->name, "__legacy__") == 0) /* legacy mode */
+ {
+ sstrncpy (vl->host, hostname_g, sizeof (vl->host));
+ }
+ else
+ {
+ if (st->socket != NULL)
+ sstrncpy (vl->host, hostname_g, sizeof (vl->host));
+ else
+ sstrncpy (vl->host,
+ (st->host != NULL) ? st->host : MEMCACHED_DEF_HOST,
+ sizeof (vl->host));
+ sstrncpy (vl->plugin_instance, st->name, sizeof (vl->plugin_instance));
+ }
+}
+
+static void submit_derive (const char *type, const char *type_inst,
+ derive_t value, memcached_t *st)
+{
+ value_t values[1];
+ value_list_t vl = VALUE_LIST_INIT;
+ memcached_init_vl (&vl, st);
+
+ values[0].derive = value;
+
+ vl.values = values;
+ vl.values_len = 1;
+ 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_derive2 (const char *type, const char *type_inst,
+ derive_t value0, derive_t value1, memcached_t *st)
+{
+ value_t values[2];
+ value_list_t vl = VALUE_LIST_INIT;
+ memcached_init_vl (&vl, st);
+
+ values[0].derive = value0;
+ values[1].derive = value1;
+
+ vl.values = values;
+ vl.values_len = 2;
+ 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_gauge (const char *type, const char *type_inst,
+ gauge_t value, memcached_t *st)
+{
+ value_t values[1];
+ value_list_t vl = VALUE_LIST_INIT;
+ memcached_init_vl (&vl, st);
+
+ values[0].gauge = value;
+
+ vl.values = values;
+ vl.values_len = 1;
+ 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, memcached_t *st)
+{
+ value_t values[2];
+ value_list_t vl = VALUE_LIST_INIT;
+ memcached_init_vl (&vl, st);
+
+ values[0].gauge = value0;
+ values[1].gauge = value1;
+
+ vl.values = values;
+ vl.values_len = 2;
+ 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 (user_data_t *user_data)
+{
+ char buf[4096];
+ char *fields[3];
+ char *ptr;
+ char *line;
+ char *saveptr;
+ int fields_num;
+
+ gauge_t bytes_used = NAN;
+ gauge_t bytes_total = NAN;
+ gauge_t hits = NAN;
+ gauge_t gets = NAN;
+ derive_t rusage_user = 0;
+ derive_t rusage_syst = 0;
+ derive_t octets_rx = 0;
+ derive_t octets_tx = 0;
+
+ memcached_t *st;
+ st = user_data->data;
+
+ /* get data from daemon */
+ if (memcached_query_daemon (buf, sizeof (buf), st) < 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]), st);
+ }
+
+ /*
+ * Number of items stored
+ */
+ else if (FIELD_IS ("curr_items"))
+ {
+ submit_gauge ("memcached_items", "current", atof (fields[2]), st);
+ }
+
+ /*
+ * 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]), st);
+ }
+
+ /*
+ * 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]), st);
+ 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]), st);
+ hits = atof (fields[2]);
+ }
+ else if (FIELD_IS ("get_misses"))
+ {
+ submit_derive ("memcached_ops", "misses", atoll (fields[2]), st);
+ }
+ else if (FIELD_IS ("evictions"))
+ {
+ submit_derive ("memcached_ops", "evictions", atoll (fields[2]), st);
+ }
+
+ /*
+ * 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, st);
+
+ if ((rusage_user != 0) || (rusage_syst != 0))
+ submit_derive2 ("ps_cputime", NULL, rusage_user, rusage_syst, st);
+
+ if ((octets_rx != 0) || (octets_tx != 0))
+ submit_derive2 ("memcached_octets", NULL, octets_rx, octets_tx, st);
+
+ if (!isnan (gets) && !isnan (hits))
+ {
+ gauge_t rate = NAN;
+
+ if (gets != 0.0)
+ rate = 100.0 * hits / gets;
+
+ submit_gauge ("percent", "hitratio", rate, st);
+ }
+
+ return 0;
+} /* int memcached_read */
+
+static int memcached_add_read_callback (memcached_t *st)
+{
+ user_data_t ud;
+ char callback_name[3*DATA_MAX_NAME_LEN];
+ int status;
+
+ memset (&ud, 0, sizeof (ud));
+ ud.data = st;
+ ud.free_func = (void *) memcached_free;
+
+ assert (st->name != NULL);
+ ssnprintf (callback_name, sizeof (callback_name), "memcached/%s", st->name);
+
+ status = plugin_register_complex_read (/* group = */ "memcached",
+ /* name = */ callback_name,
+ /* callback = */ memcached_read,
+ /* interval = */ NULL,
+ /* user_data = */ &ud);
+ return (status);
+} /* int memcached_add_read_callback */
+
+/* Configuration handling functiions
+ * <Plugin memcached>
+ * <Instance "instance_name">
+ * Host foo.zomg.com
+ * Port "1234"
+ * </Instance>
+ * </Plugin>
+ */
+static int config_add_instance(oconfig_item_t *ci)
+{
+ memcached_t *st;
+ int i;
+ int status = 0;
+
+ /* Disable automatic generation of default instance in the init callback. */
+ memcached_have_instances = 1;
+
+ st = malloc (sizeof (*st));
+ if (st == NULL)
+ {
+ ERROR ("memcached plugin: malloc failed.");
+ return (-1);
+ }
+
+ memset (st, 0, sizeof (*st));
+ st->name = NULL;
+ st->socket = NULL;
+ st->host = NULL;
+ st->port = NULL;
+
+ if (strcasecmp (ci->key, "Plugin") == 0) /* default instance */
+ st->name = sstrdup ("__legacy__");
+ else /* <Instance /> block */
+ status = cf_util_get_string (ci, &st->name);
+ 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 ("Socket", child->key) == 0)
+ status = cf_util_get_string (child, &st->socket);
+ else if (strcasecmp ("Host", child->key) == 0)
+ status = cf_util_get_string (child, &st->host);
+ else if (strcasecmp ("Port", child->key) == 0)
+ status = cf_util_get_service (child, &st->port);
+ else
+ {
+ WARNING ("memcached plugin: Option `%s' not allowed here.",
+ child->key);
+ status = -1;
+ }
+
+ if (status != 0)
+ break;
+ }
+
+ if (status == 0)
+ status = memcached_add_read_callback (st);
+
+ if (status != 0)
+ {
+ memcached_free(st);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int memcached_config (oconfig_item_t *ci)
+{
+ int status = 0;
+ _Bool have_instance_block = 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_instance (child);
+ have_instance_block = 1;
+ }
+ else if (!have_instance_block)
+ {
+ /* Non-instance option: Assume legacy configuration (without <Instance />
+ * blocks) and call config_add_instance() with the <Plugin /> block. */
+ return (config_add_instance (ci));
+ }
+ else
+ WARNING ("memcached 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);
+}
+
+static int memcached_init (void)
+{
+ memcached_t *st;
+ int status;
+
+ if (memcached_have_instances)
+ return (0);
+
+ /* No instances were configured, lets start a default instance. */
+ st = malloc (sizeof (*st));
+ if (st == NULL)
+ return (ENOMEM);
+ memset (st, 0, sizeof (*st));
+ st->name = sstrdup ("__legacy__");
+ st->socket = NULL;
+ st->host = NULL;
+ st->port = NULL;
+
+ status = memcached_add_read_callback (st);
+ if (status == 0)
+ memcached_have_instances = 1;
+ else
+ memcached_free (st);
+
+ return (status);
+} /* int memcached_init */
+
+void module_register (void)
+{
+ plugin_register_complex_config ("memcached", memcached_config);
+ plugin_register_init ("memcached", memcached_init);
+}
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * 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->instance != NULL)
+ ssnprintf (cb_name, sizeof (cb_name), "mysql-%s",
+ db->instance);
+ 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 */
--- /dev/null
+/**
+ * collectd - src/netapp.c
+ * Copyright (C) 2009,2010 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_internal (host_config_t *host) { /* {{{ */
+ int status;
+
+ status = cna_query_wafl (host);
+ if (status != 0)
+ return (status);
+
+ status = cna_query_disk (host);
+ if (status != 0)
+ return (status);
+
+ status = cna_query_volume_perf (host);
+ if (status != 0)
+ return (status);
+
+ status = cna_query_volume_usage (host);
+ if (status != 0)
+ return (status);
+
+ status = cna_query_system (host);
+ if (status != 0)
+ return (status);
+
+ return 0;
+} /* }}} int cna_read_internal */
+
+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);
+
+ status = cna_read_internal (host);
+ if (status != 0)
+ {
+ if (host->srv != NULL)
+ na_server_close (host->srv);
+ host->srv = NULL;
+ }
+
+ 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 : */
--- /dev/null
+/**
+ * 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 :
+ */
--- /dev/null
+/**
+ * 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;
+/* Ethernet - (IPv6 + UDP) = 1500 - (40 + 8) = 1452 */
+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 _Bool check_notify_received (const notification_t *n) /* {{{ */
+{
+ notification_meta_t *ptr;
+
+ for (ptr = n->meta; ptr != NULL; ptr = ptr->next)
+ if ((strcmp ("network:received", ptr->name) == 0)
+ && (ptr->type == NM_TYPE_BOOLEAN))
+ return ((_Bool) ptr->nm_value.nm_boolean);
+
+ return (0);
+} /* }}} _Bool check_notify_received */
+
+static _Bool check_send_notify_okay (const notification_t *n) /* {{{ */
+{
+ static c_complain_t complain_forwarding = C_COMPLAIN_INIT_STATIC;
+ _Bool received = 0;
+
+ if (n->meta == NULL)
+ return (1);
+
+ received = check_notify_received (n);
+
+ if (network_config_forward && received)
+ {
+ c_complain_once (LOG_ERR, &complain_forwarding,
+ "network plugin: A notification has been received via the network "
+ "forwarding if enabled. Forwarding of notifications is currently "
+ "not supported, because there is not loop-deteciton available. "
+ "Please contact the collectd mailing list if you need this "
+ "feature.");
+ }
+
+ /* By default, only *send* value lists that were not *received* by the
+ * network plugin. */
+ return (!received);
+} /* }}} _Bool check_send_notify_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 */
+
+static int network_dispatch_notification (notification_t *n) /* {{{ */
+{
+ int status;
+
+ assert (n->meta == NULL);
+
+ status = plugin_notification_meta_add_boolean (n, "network:received", 1);
+ if (status != 0)
+ {
+ ERROR ("network plugin: plugin_notification_meta_add_boolean failed.");
+ plugin_notification_meta_free (n->meta);
+ n->meta = NULL;
+ return (status);
+ }
+
+ status = plugin_dispatch_notification (n);
+
+ plugin_notification_meta_free (n->meta);
+ n->meta = NULL;
+
+ return (status);
+} /* }}} int network_dispatch_notification */
+
+#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 < 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 < 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 < 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
+ {
+ network_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 (se->interface != 0)
+ {
+#if defined(HAVE_IF_INDEXTONAME) && HAVE_IF_INDEXTONAME && defined(SO_BINDTODEVICE)
+ 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 \"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;
+
+ if (!check_send_notify_okay (n))
+ return (0);
+
+ 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 : */
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * 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>
+ * Cosmin Ioiart <cioiart at gmail.com>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#if HAVE_KSTAT_H
+#include <kstat.h>
+#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"
+};
+static size_t nfs2_procedures_names_num = STATIC_ARRAY_SIZE (nfs2_procedures_names);
+
+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"
+};
+static size_t nfs3_procedures_names_num = STATIC_ARRAY_SIZE (nfs3_procedures_names);
+
+#if HAVE_LIBKSTAT
+static const char *nfs4_procedures_names[] =
+{
+ "null",
+ "compound",
+ "reserved",
+ "access",
+ "close",
+ "commit",
+ "create",
+ "delegpurge",
+ "delegreturn",
+ "getattr",
+ "getfh",
+ "link",
+ "lock",
+ "lockt",
+ "locku",
+ "lookup",
+ "lookupp",
+ "nverify",
+ "open",
+ "openattr",
+ "open_confirm",
+ "open_downgrade",
+ "putfh",
+ "putpubfh",
+ "putrootfh",
+ "read",
+ "readdir",
+ "readlink",
+ "remove",
+ "rename",
+ "renew",
+ "restorefh",
+ "savefh",
+ "secinfo",
+ "setattr",
+ "setclientid",
+ "setclientid_confirm",
+ "verify",
+ "write"
+};
+static size_t nfs4_procedures_names_num = STATIC_ARRAY_SIZE (nfs4_procedures_names);
+#endif
+
+#if HAVE_LIBKSTAT
+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 KERNEL_LINUX
+static int nfs_init (void)
+{
+ return (0);
+}
+/* #endif KERNEL_LINUX */
+
+#elif HAVE_LIBKSTAT
+static int nfs_init (void)
+{
+ kstat_t *ksp_chain = NULL;
+
+ 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 (-1);
+
+ 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;
+ }
+
+ return (0);
+} /* int nfs_init */
+#endif
+
+static void nfs_procedures_submit (const char *plugin_instance,
+ const char **type_instances,
+ value_t *values, size_t values_num)
+{
+ value_list_t vl = VALUE_LIST_INIT;
+ size_t i;
+
+ 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 < values_num; i++)
+ {
+ vl.values = values + i;
+ sstrncpy (vl.type_instance, type_instances[i],
+ sizeof (vl.type_instance));
+ plugin_dispatch_values_secure (&vl);
+ }
+} /* void nfs_procedures_submit */
+
+#if KERNEL_LINUX
+static int nfs_submit_fields (int nfs_version, const char *instance,
+ char **fields, size_t fields_num,
+ const char **proc_names, size_t proc_names_num)
+{
+ char plugin_instance[DATA_MAX_NAME_LEN];
+ value_t values[fields_num];
+ size_t i;
+
+ if (fields_num != proc_names_num)
+ {
+ WARNING ("nfs plugin: Wrong number of fields for "
+ "NFSv%i %s statistics. Expected %zu, got %zu.",
+ nfs_version, instance,
+ proc_names_num, fields_num);
+ return (EINVAL);
+ }
+
+ ssnprintf (plugin_instance, sizeof (plugin_instance), "v%i%s",
+ nfs_version, instance);
+
+ for (i = 0; i < proc_names_num; i++)
+ (void) parse_value (fields[i], &values[i], DS_TYPE_DERIVE);
+
+ nfs_procedures_submit (plugin_instance, proc_names, values,
+ proc_names_num);
+
+ return (0);
+}
+
+static void nfs_read_linux (FILE *fh, char *inst)
+{
+ char buffer[1024];
+
+ char *fields[48];
+ int fields_num = 0;
+
+ if (fh == NULL)
+ return;
+
+ while (fgets (buffer, sizeof (buffer), fh) != NULL)
+ {
+ fields_num = strsplit (buffer,
+ fields, STATIC_ARRAY_SIZE (fields));
+
+ if (fields_num < 3)
+ continue;
+
+ if (strcmp (fields[0], "proc2") == 0)
+ {
+ nfs_submit_fields (/* version = */ 2, inst,
+ fields + 2, (size_t) (fields_num - 2),
+ nfs2_procedures_names,
+ nfs2_procedures_names_num);
+ }
+ else if (strncmp (fields[0], "proc3", 5) == 0)
+ {
+ nfs_submit_fields (/* version = */ 3, inst,
+ fields + 2, (size_t) (fields_num - 2),
+ nfs3_procedures_names,
+ nfs3_procedures_names_num);
+ }
+ } /* while (fgets) */
+} /* void nfs_read_linux */
+#endif /* KERNEL_LINUX */
+
+#if HAVE_LIBKSTAT
+static int nfs_read_kstat (kstat_t *ksp, int nfs_version, char *inst,
+ const char **proc_names, size_t proc_names_num)
+{
+ char plugin_instance[DATA_MAX_NAME_LEN];
+ value_t values[proc_names_num];
+ size_t i;
+
+ if (ksp == NULL)
+ return (EINVAL);
+
+ ssnprintf (plugin_instance, sizeof (plugin_instance), "v%i%s",
+ nfs_version, inst);
+
+ kstat_read(kc, ksp, NULL);
+ for (i = 0; i < proc_names_num; i++)
+ values[i].counter = (derive_t) get_kstat_value (ksp,
+ (char *)proc_names[i]);
+
+ nfs_procedures_submit (plugin_instance, proc_names, values,
+ proc_names_num);
+ return (0);
+}
+#endif
+
+#if KERNEL_LINUX
+static int nfs_read (void)
+{
+ FILE *fh;
+
+ if ((fh = fopen ("/proc/net/rpc/nfs", "r")) != NULL)
+ {
+ nfs_read_linux (fh, "client");
+ fclose (fh);
+ }
+
+ if ((fh = fopen ("/proc/net/rpc/nfsd", "r")) != NULL)
+ {
+ nfs_read_linux (fh, "server");
+ fclose (fh);
+ }
+
+ return (0);
+}
+/* #endif KERNEL_LINUX */
+
+#elif HAVE_LIBKSTAT
+static int nfs_read (void)
+{
+ nfs_read_kstat (nfs2_ksp_client, /* version = */ 2, "client",
+ nfs2_procedures_names, nfs2_procedures_names_num);
+ nfs_read_kstat (nfs2_ksp_server, /* version = */ 2, "server",
+ nfs2_procedures_names, nfs2_procedures_names_num);
+ nfs_read_kstat (nfs3_ksp_client, /* version = */ 3, "client",
+ nfs3_procedures_names, nfs3_procedures_names_num);
+ nfs_read_kstat (nfs3_ksp_server, /* version = */ 3, "server",
+ nfs3_procedures_names, nfs3_procedures_names_num);
+ nfs_read_kstat (nfs4_ksp_client, /* version = */ 4, "client",
+ nfs4_procedures_names, nfs4_procedures_names_num);
+ nfs_read_kstat (nfs4_ksp_server, /* version = */ 4, "server",
+ nfs4_procedures_names, nfs4_procedures_names_num);
+
+ return (0);
+}
+#endif /* HAVE_LIBKSTAT */
+
+void module_register (void)
+{
+ plugin_register_init ("nfs", nfs_init);
+ plugin_register_read ("nfs", nfs_read);
+} /* void module_register */
--- /dev/null
+/**
+ * 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 :
+ */
--- /dev/null
+/**
+ * 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 : */
+
--- /dev/null
+/**
+ * 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, ×tamp_tm);
+ strftime (timestamp_str, sizeof (timestamp_str), "%Y-%m-%d %H:%M:%S",
+ ×tamp_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 : */
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * collectd - src/numa.c
+ * Copyright (C) 2012 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>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#if !KERNEL_LINUX
+# error "No applicable input method."
+#endif
+
+#ifndef NUMA_ROOT_DIR
+# define NUMA_ROOT_DIR "/sys/devices/system/node"
+#endif
+
+static int max_node = -1;
+
+static void numa_dispatch_value (int node, /* {{{ */
+ const char *type_instance, value_t v)
+{
+ value_list_t vl = VALUE_LIST_INIT;
+
+ vl.values = &v;
+ vl.values_len = 1;
+
+ sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+ sstrncpy (vl.plugin, "numa", sizeof (vl.plugin));
+ ssnprintf (vl.plugin_instance, sizeof (vl.plugin_instance), "node%i", node);
+ sstrncpy (vl.type, "vmpage_action", sizeof (vl.type));
+ sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
+
+ plugin_dispatch_values (&vl);
+} /* }}} void numa_dispatch_value */
+
+static int numa_read_node (int node) /* {{{ */
+{
+ char path[PATH_MAX];
+ FILE *fh;
+ char buffer[128];
+ int status;
+ int success;
+
+ ssnprintf (path, sizeof (path), NUMA_ROOT_DIR "/node%i/numastat", node);
+
+ fh = fopen (path, "r");
+ if (fh == NULL)
+ {
+ char errbuf[1024];
+ ERROR ("numa plugin: Reading node %i failed: open(%s): %s",
+ node, path, sstrerror (errno, errbuf, sizeof (errbuf)));
+ return (-1);
+ }
+
+ success = 0;
+ while (fgets (buffer, sizeof (buffer), fh) != NULL)
+ {
+ char *fields[4];
+ value_t v;
+
+ status = strsplit (buffer, fields, STATIC_ARRAY_SIZE (fields));
+ if (status != 2)
+ {
+ WARNING ("numa plugin: Ignoring line with unexpected "
+ "number of fields (node %i).", node);
+ continue;
+ }
+
+ v.derive = 0;
+ status = parse_value (fields[1], &v, DS_TYPE_DERIVE);
+ if (status != 0)
+ continue;
+
+ numa_dispatch_value (node, fields[0], v);
+ success++;
+ }
+
+ fclose (fh);
+ return (success ? 0 : -1);
+} /* }}} int numa_read_node */
+
+static int numa_read (void) /* {{{ */
+{
+ int i;
+ int status;
+ int success;
+
+ if (max_node < 0)
+ {
+ WARNING ("numa plugin: No NUMA nodes were detected.");
+ return (-1);
+ }
+
+ success = 0;
+ for (i = 0; i <= max_node; i++)
+ {
+ status = numa_read_node (i);
+ if (status == 0)
+ success++;
+ }
+
+ return (success ? 0 : -1);
+} /* }}} int numa_read */
+
+static int numa_init (void) /* {{{ */
+{
+ /* Determine the number of nodes on this machine. */
+ while (42)
+ {
+ char path[PATH_MAX];
+ struct stat statbuf;
+ int status;
+
+ ssnprintf (path, sizeof (path), NUMA_ROOT_DIR "/node%i", max_node + 1);
+ memset (&statbuf, 0, sizeof (statbuf));
+
+ status = stat (path, &statbuf);
+ if (status == 0)
+ {
+ max_node++;
+ continue;
+ }
+ else if (errno == ENOENT)
+ {
+ break;
+ }
+ else /* ((status != 0) && (errno != ENOENT)) */
+ {
+ char errbuf[1024];
+ ERROR ("numa plugin: stat(%s) failed: %s", path,
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ return (-1);
+ }
+ }
+
+ DEBUG ("numa plugin: Found %i nodes.", max_node + 1);
+ return (0);
+} /* }}} int numa_init */
+
+void module_register (void)
+{
+ plugin_register_init ("numa", numa_init);
+ plugin_register_read ("numa", numa_read);
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 et : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 *host;
+ 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 *db_name, const char *query_name,
+ const char *what, OCIError *eh)
+{
+ char buffer[2048];
+ sb4 error_code;
+ int status;
+ unsigned int record_number;
+
+ if (db_name == NULL)
+ db_name = "(none)";
+ if (query_name == NULL)
+ query_name = "(none)";
+
+ /* 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 (db = %s, query = %s): %s failed: %s",
+ where, db_name, query_name, what, buffer);
+ }
+ else
+ {
+ ERROR ("oracle plugin: %s (db = %s, query = %s): %s failed. "
+ "Additionally, OCIErrorGet failed with status %i.",
+ where, db_name, query_name, 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_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));
+ db->name = NULL;
+ db->host = NULL;
+ db->connect_id = NULL;
+ db->username = NULL;
+ db->password = NULL;
+
+ status = cf_util_get_string (ci, &db->name);
+ 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 = cf_util_get_string (child, &db->connect_id);
+ else if (strcasecmp ("Host", child->key) == 0)
+ status = cf_util_get_string (child, &db->host);
+ else if (strcasecmp ("Username", child->key) == 0)
+ status = cf_util_get_string (child, &db->username);
+ else if (strcasecmp ("Password", child->key) == 0)
+ status = cf_util_get_string (child, &db->password);
+ else if (strcasecmp ("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", db->name,
+ udb_query_get_name (q), "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", db->name,
+ udb_query_get_name (q), "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)
+ {
+ o_report_error ("o_read_database_query", db->name, udb_query_get_name (q),
+ "OCIStmtExecute", oci_error);
+ return (-1);
+ } /* }}} */
+
+ /* Acquire the number of columns returned. */
+ do /* {{{ */
+ {
+ ub4 param_counter = 0;
+ status = OCIAttrGet (oci_statement, OCI_HTYPE_STMT, /* {{{ */
+ ¶m_counter, /* size pointer = */ NULL,
+ OCI_ATTR_PARAM_COUNT, oci_error);
+ if (status != OCI_SUCCESS)
+ {
+ o_report_error ("o_read_database_query", db->name,
+ udb_query_get_name (q), "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", db->name,
+ udb_query_get_name (q), "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", db->name,
+ udb_query_get_name (q), "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", db->name,
+ udb_query_get_name (q), "OCIDefineByPos", oci_error);
+ continue;
+ }
+ } /* for (j = 1; j <= param_counter; j++) */
+ /* }}} End of the ``define'' stuff. */
+
+ status = udb_query_prepare_result (q, prep_area,
+ (db->host != NULL) ? db->host : 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", db->name,
+ udb_query_get_name (q), "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", db->name, NULL, "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", db->name, NULL, "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))
+ {
+ char errfunc[256];
+
+ ssnprintf (errfunc, sizeof (errfunc), "OCILogon(\"%s\")", db->connect_id);
+
+ o_report_error ("o_read_database", db->name, NULL, errfunc, 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
+ */
--- /dev/null
+/**
+ * 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;
+ }
+
+ /* Assert that we're not running as the base thread. Otherwise, we might
+ * run into concurrency issues with c_ithread_create(). See
+ * https://github.com/collectd/collectd/issues/9 for details. */
+ assert (aTHX != perl_threads->head->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)
+{
+ int status;
+ 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;
+ }
+
+ /* Lock the base thread if this is not called from one of the read threads
+ * to avoid race conditions with c_ithread_create(). See
+ * https://github.com/collectd/collectd/issues/9 for details. */
+ if (aTHX == perl_threads->head->interp)
+ pthread_mutex_lock (&perl_threads->mutex);
+
+ log_debug ("perl_write: c_ithread: interp = %p (active threads: %i)",
+ aTHX, perl_threads->number_of_threads);
+ status = pplugin_call_all (aTHX_ PLUGIN_WRITE, ds, vl);
+
+ if (aTHX == perl_threads->head->interp)
+ pthread_mutex_unlock (&perl_threads->mutex);
+
+ return status;
+} /* static int perl_write (const data_set_t *, const value_list_t *) */
+
+static void perl_log (int level, const char *msg,
+ user_data_t __attribute__((unused)) *user_data)
+{
+ 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;
+ }
+
+ /* Lock the base thread if this is not called from one of the read threads
+ * to avoid race conditions with c_ithread_create(). See
+ * https://github.com/collectd/collectd/issues/9 for details. */
+ if (aTHX == perl_threads->head->interp)
+ pthread_mutex_lock (&perl_threads->mutex);
+
+ pplugin_call_all (aTHX_ PLUGIN_LOG, level, msg);
+
+ if (aTHX == perl_threads->head->interp)
+ pthread_mutex_unlock (&perl_threads->mutex);
+
+ 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 : */
+
--- /dev/null
+/**
+ * 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_service (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 : */
--- /dev/null
+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;
+}
--- /dev/null
+/**
+ * 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, ¶m_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, ¶m_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 : */
--- /dev/null
+/**
+ * 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 */
+
+int parse_log_severity (const char *severity)
+{
+ int log_level = -1;
+
+ if ((0 == strcasecmp (severity, "emerg"))
+ || (0 == strcasecmp (severity, "alert"))
+ || (0 == strcasecmp (severity, "crit"))
+ || (0 == strcasecmp (severity, "err")))
+ log_level = LOG_ERR;
+ else if (0 == strcasecmp (severity, "warning"))
+ log_level = LOG_WARNING;
+ else if (0 == strcasecmp (severity, "notice"))
+ log_level = LOG_NOTICE;
+ else if (0 == strcasecmp (severity, "info"))
+ log_level = LOG_INFO;
+#if COLLECT_DEBUG
+ else if (0 == strcasecmp (severity, "debug"))
+ log_level = LOG_DEBUG;
+#endif /* COLLECT_DEBUG */
+
+ return (log_level);
+} /* int parse_log_severity */
+
+int parse_notif_severity (const char *severity)
+{
+ int notif_severity = -1;
+
+ if (strcasecmp (severity, "FAILURE") == 0)
+ notif_severity = NOTIF_FAILURE;
+ else if (strcmp (severity, "OKAY") == 0)
+ notif_severity = NOTIF_OKAY;
+ else if ((strcmp (severity, "WARNING") == 0)
+ || (strcmp (severity, "WARN") == 0))
+ notif_severity = NOTIF_WARNING;
+
+ return (notif_severity);
+} /* int parse_notif_severity */
+
+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 : */
--- /dev/null
+#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)));
+
+/* These functions return the parsed severity or less than zero on failure. */
+int parse_log_severity (const char *severity);
+int parse_notif_severity (const char *severity);
+
+#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 */
--- /dev/null
+/**
+ * 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_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"))
+ cf_util_get_string (c, &db->host);
+ else if (0 == strcasecmp (c->key, "Port"))
+ cf_util_get_service (c, &db->port);
+ else if (0 == strcasecmp (c->key, "User"))
+ cf_util_get_string (c, &db->user);
+ else if (0 == strcasecmp (c->key, "Password"))
+ cf_util_get_string (c, &db->password);
+ else if (0 == strcasecmp (c->key, "SSLMode"))
+ cf_util_get_string (c, &db->sslmode);
+ else if (0 == strcasecmp (c->key, "KRBSrvName"))
+ cf_util_get_string (c, &db->krbsrvname);
+ else if (0 == strcasecmp (c->key, "Service"))
+ cf_util_get_string (c, &db->service);
+ 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 : */
--- /dev/null
+# 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 :
+
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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
+static int pagesize;
+/* #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
+ pagesize = getpagesize();
+/* #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 buffer_len;
+
+ char *buffer_ptr;
+ size_t name_start_pos;
+ size_t name_end_pos;
+ size_t 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);
+
+ buffer_len = read_file_contents (filename,
+ buffer, sizeof(buffer) - 1);
+ if (buffer_len <= 0)
+ return (-1);
+ buffer[buffer_len] = 0;
+
+ /* The name of the process is enclosed in parens. Since the name can
+ * contain parens itself, spaces, numbers and pretty much everything
+ * else, use these to determine the process name. We don't use
+ * strchr(3) and strrchr(3) to avoid pointer arithmetic which would
+ * otherwise be required to determine name_len. */
+ name_start_pos = 0;
+ while ((buffer[name_start_pos] != '(')
+ && (name_start_pos < buffer_len))
+ name_start_pos++;
+
+ name_end_pos = buffer_len;
+ while ((buffer[name_end_pos] != ')')
+ && (name_end_pos > 0))
+ name_end_pos--;
+
+ /* Either '(' or ')' is not found or they are in the wrong order.
+ * Anyway, something weird that shouldn't happen ever. */
+ if (name_start_pos >= name_end_pos)
+ {
+ ERROR ("processes plugin: name_start_pos = %zu >= name_end_pos = %zu",
+ name_start_pos, name_end_pos);
+ return (-1);
+ }
+
+ name_len = (name_end_pos - name_start_pos) - 1;
+ if (name_len >= sizeof (ps->name))
+ name_len = sizeof (ps->name) - 1;
+
+ sstrncpy (ps->name, &buffer[name_start_pos + 1], name_len + 1);
+
+ if ((buffer_len - name_end_pos) < 2)
+ return (-1);
+ buffer_ptr = &buffer[name_end_pos + 2];
+
+ fields_len = strsplit (buffer_ptr, fields, STATIC_ARRAY_SIZE (fields));
+ if (fields_len < 22)
+ {
+ DEBUG ("processes plugin: ps_read_process (pid = %i):"
+ " `%s' has only %i fields..",
+ (int) pid, filename, fields_len);
+ return (-1);
+ }
+
+ *state = fields[0][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[11]);
+ cpu_system_counter = atoll (fields[12]);
+ vmem_size = atoll (fields[20]);
+ vmem_rss = atoll (fields[21]);
+ ps->vmem_minflt_counter = atol (fields[7]);
+ ps->vmem_majflt_counter = atol (fields[9]);
+
+ {
+ unsigned long long stack_start = atoll (fields[25]);
+ unsigned long long stack_ptr = atoll (fields[26]);
+
+ 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];
+ struct kinfo_proc *procs; /* array of processes */
+ struct kinfo_proc *proc_ptr = NULL;
+ 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++)
+ {
+ /* Create only one process list entry per _process_, i.e.
+ * filter out threads (duplicate PID entries). */
+ if ((proc_ptr == NULL) || (proc_ptr->ki_pid != procs[i].ki_pid))
+ {
+ char cmdline[ARG_MAX] = "";
+ _Bool have_cmdline = 0;
+
+ proc_ptr = &(procs[i]);
+ /* Don't probe system processes and processes without arguments */
+ if (((procs[i].ki_flag & P_SYSTEM) == 0)
+ && (procs[i].ki_args != NULL))
+ {
+ char **argv;
+ int argc;
+ int status;
+
+ /* retrieve the arguments */
+ argv = kvm_getargv (kd, proc_ptr, /* nchr = */ 0);
+ argc = 0;
+ if ((argv != NULL) && (argv[0] != NULL))
+ {
+ 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
+ have_cmdline = 1;
+ }
+ } /* if (process has argument list) */
+
+ 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 * pagesize;
+ pse.vmem_data = procs[i].ki_dsize * pagesize;
+ pse.vmem_code = procs[i].ki_tsize * pagesize;
+ pse.stack_size = procs[i].ki_ssize * pagesize;
+ pse.vmem_minflt = 0;
+ pse.vmem_minflt_counter = procs[i].ki_rusage.ru_minflt;
+ pse.vmem_majflt = 0;
+ pse.vmem_majflt_counter = procs[i].ki_rusage.ru_majflt;
+
+ pse.cpu_user = 0;
+ pse.cpu_system = 0;
+ pse.cpu_user_counter = 0;
+ pse.cpu_system_counter = 0;
+ /*
+ * The u-area might be swapped out, and we can't get
+ * at it because we have a crashdump and no swap.
+ * If it's here fill in these fields, otherwise, just
+ * leave them 0.
+ */
+ if (procs[i].ki_flag & P_INMEM)
+ {
+ pse.cpu_user_counter = procs[i].ki_rusage.ru_utime.tv_usec
+ + (1000000lu * procs[i].ki_rusage.ru_utime.tv_sec);
+ pse.cpu_system_counter = procs[i].ki_rusage.ru_stime.tv_usec
+ + (1000000lu * procs[i].ki_rusage.ru_stime.tv_sec);
+ }
+
+ /* no I/O data */
+ pse.io_rchar = -1;
+ pse.io_wchar = -1;
+ pse.io_syscr = -1;
+ pse.io_syscw = -1;
+
+ ps_list_add (procs[i].ki_comm, have_cmdline ? cmdline : NULL, &pse);
+ } /* if ((proc_ptr == NULL) || (proc_ptr->ki_pid != procs[i].ki_pid)) */
+
+ 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;
+ }
+ }
+
+ 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 */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 */
+};
+
--- /dev/null
+/**
+ * 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);
+}
--- /dev/null
+/**
+ * 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(¬ification);
+ 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 */
+};
--- /dev/null
+/**
+ * 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>
+
+#ifndef HOST_NAME_MAX
+# define HOST_NAME_MAX _POSIX_HOST_NAME_MAX
+#endif
+
+#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];
+ char passwd[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 if (strcasecmp ("Password", option->key) == 0)
+ status = cf_util_get_string_buffer (option, rn.passwd, sizeof (rn.passwd));
+ 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;
+ }
+
+ if (strlen (rn->passwd) > 0)
+ {
+ DEBUG ("redis plugin: authenticanting node `%s' passwd(%s).", rn->name, rn->passwd);
+ status = credis_auth(rh, rn->passwd);
+ if (status != 0)
+ {
+ WARNING ("redis plugin: unable to authenticate on node `%s'.", rn->name);
+ credis_close (rh);
+ 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 : */
--- /dev/null
+/**
+ * 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_service (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 : */
--- /dev/null
+/**
+ * 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 :
+ */
--- /dev/null
+/**
+ * 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",
+ filename, 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);
+}
--- /dev/null
+/**
+ * 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
+ *
+ * Henrique de Moraes Holschuh <hmh at debian.org>
+ * - use default libsensors config file on API 0x400
+ * - config SensorConfigFile 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",
+ "SensorConfigFile"
+};
+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
+static char *conffile = SENSORS_CONF_PATH;
+/* #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;
+
+static char *conffile = NULL;
+/* #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
+
+featurelist_t *first_feature = NULL;
+static ignorelist_t *sensor_list;
+
+#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);
+
+ /* TODO: This setting exists for compatibility with old versions of
+ * lm-sensors. Remove support for those ancient versions in the next
+ * major release. */
+ if (strcasecmp (key, "SensorConfigFile") == 0)
+ {
+ char *tmp = strdup (value);
+ if (tmp != NULL)
+ {
+ sfree (conffile);
+ conffile = tmp;
+ }
+ }
+ else 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)
+{
+ static int call_once = 0;
+
+ FILE *fh = NULL;
+ featurelist_t *last_feature = NULL;
+
+ const sensors_chip_name *chip;
+ int chip_num;
+
+ int status;
+
+ if (call_once)
+ return 0;
+
+ call_once = 1;
+
+ if (conffile != NULL)
+ {
+ 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);
+ if (fh)
+ fclose (fh);
+
+ if (status != 0)
+ {
+ ERROR ("sensors plugin: Cannot initialize sensors. "
+ "Data will not be collected.");
+ return (-1);
+ }
+
+#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 */
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * 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 `Shift' 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;
+ _Bool defined = 1;
+ /* Set to true when the original SNMP type appears to have been signed. */
+ _Bool prefer_signed = 0;
+
+ 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;
+
+ if ((vl->type == ASN_INTEGER)
+ || (vl->type == ASN_GAUGE))
+ prefer_signed = 1;
+
+ DEBUG ("snmp plugin: Parsed int32 value is %"PRIu64".", tmp_unsigned);
+ }
+ 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)
+ {
+ if (!defined)
+ ret.gauge = NAN;
+ else if (prefer_signed)
+ ret.gauge = (scale * tmp_signed) + shift;
+ else
+ ret.gauge = (scale * tmp_unsigned) + shift;
+ }
+ else if (type == DS_TYPE_DERIVE)
+ {
+ if (prefer_signed)
+ ret.derive = (derive_t) tmp_signed;
+ else
+ ret.derive = (derive_t) tmp_unsigned;
+ }
+ 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 ((vb->type == SNMP_ENDOFMIBVIEW)
+ || (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
+ */
--- /dev/null
+/**
+ * collectd - src/swap.c
+ * Copyright (C) 2005-2012 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_REPORT_BY_DEVICE 1
+static derive_t pagesize;
+static _Bool report_bytes = 0;
+static _Bool report_by_device = 0;
+/* #endif KERNEL_LINUX */
+
+#elif HAVE_SWAPCTL && HAVE_SWAPCTL_TWO_ARGS
+# define SWAP_HAVE_REPORT_BY_DEVICE 1
+static derive_t pagesize;
+static _Bool report_by_device = 0;
+/* #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 */
+
+static const char *config_keys[] =
+{
+ "ReportBytes",
+ "ReportByDevice"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static int swap_config (const char *key, const char *value) /* {{{ */
+{
+ if (strcasecmp ("ReportBytes", key) == 0)
+ {
+#if KERNEL_LINUX
+ report_bytes = IS_TRUE (value) ? 1 : 0;
+#else
+ WARNING ("swap plugin: The \"ReportBytes\" option is only "
+ "valid under Linux. "
+ "The option is going to be ignored.");
+#endif
+ }
+ else if (strcasecmp ("ReportByDevice", key) == 0)
+ {
+#if SWAP_HAVE_REPORT_BY_DEVICE
+ if (IS_TRUE (value))
+ report_by_device = 1;
+ else
+ report_by_device = 0;
+#else
+ WARNING ("swap plugin: The \"ReportByDevice\" option is not "
+ "supported on this platform. "
+ "The option is going to be ignored.");
+#endif /* SWAP_HAVE_REPORT_BY_DEVICE */
+ }
+ else
+ {
+ return (-1);
+ }
+
+ return (0);
+} /* }}} int swap_config */
+
+static int swap_init (void) /* {{{ */
+{
+#if KERNEL_LINUX
+ pagesize = (derive_t) sysconf (_SC_PAGESIZE);
+/* #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);
+
+ if (report_bytes)
+ {
+ swap_in = swap_in * pagesize;
+ swap_out = swap_out * pagesize;
+ }
+
+ 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)
+{
+ plugin_register_config ("swap", swap_config,
+ config_keys, config_keys_num);
+ plugin_register_init ("swap", swap_init);
+ plugin_register_read ("swap", swap_read);
+} /* void module_register */
+
+/* vim: set fdm=marker : */
--- /dev/null
+/**
+ * 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 int notif_severity = 0;
+
+static const char *config_keys[] =
+{
+ "LogLevel",
+ "NotifyLevel",
+};
+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)
+ {
+ log_level = parse_log_severity (value);
+ if (log_level < 0)
+ return (1);
+ }
+ else if (strcasecmp (key, "NotifyLevel") == 0)
+ {
+ notif_severity = parse_notif_severity (value);
+ if (notif_severity < 0)
+ 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);
+}
+
+static int sl_notification (const notification_t *n,
+ user_data_t __attribute__((unused)) *user_data)
+{
+ char buf[1024] = "";
+ size_t offset = 0;
+ int log_severity;
+ char *severity_string;
+ int status;
+
+ if (n->severity > notif_severity)
+ return (0);
+
+ switch (n->severity)
+ {
+ case NOTIF_FAILURE:
+ severity_string = "FAILURE";
+ log_severity = LOG_ERR;
+ break;
+ case NOTIF_WARNING:
+ severity_string = "WARNING";
+ log_severity = LOG_WARNING;
+ break;
+ case NOTIF_OKAY:
+ severity_string = "OKAY";
+ log_severity = LOG_NOTICE;
+ break;
+ default:
+ severity_string = "UNKNOWN";
+ log_severity = LOG_ERR;
+ }
+
+#define BUFFER_ADD(...) do { \
+ status = ssnprintf (&buf[offset], sizeof (buf) - offset, \
+ __VA_ARGS__); \
+ if (status < 1) \
+ return (-1); \
+ else if (((size_t) status) >= (sizeof (buf) - offset)) \
+ return (-ENOMEM); \
+ else \
+ offset += ((size_t) status); \
+} while (0)
+
+#define BUFFER_ADD_FIELD(field) do { \
+ if (n->field[0]) \
+ BUFFER_ADD (", " #field " = %s", n->field); \
+} while (0)
+
+ BUFFER_ADD ("Notification: severity = %s", severity_string);
+ BUFFER_ADD_FIELD (host);
+ BUFFER_ADD_FIELD (plugin);
+ BUFFER_ADD_FIELD (plugin_instance);
+ BUFFER_ADD_FIELD (type);
+ BUFFER_ADD_FIELD (type_instance);
+ BUFFER_ADD_FIELD (message);
+
+#undef BUFFER_ADD_FIELD
+#undef BUFFER_ADD
+
+ buf[sizeof (buf) - 1] = '\0';
+
+ sl_log (log_severity, buf, NULL);
+
+ return (0);
+} /* int sl_notification */
+
+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_notification ("syslog", sl_notification, NULL);
+ plugin_register_shutdown ("syslog", sl_shutdown);
+} /* void module_register(void) */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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);
+}
--- /dev/null
+/**
+ * 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 : */
+
--- /dev/null
+/**
+ * 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 : */
+
--- /dev/null
+/**
+ * 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;
+
+ char **data_sources;
+ size_t data_sources_num;
+};
+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_config_add_data_source(ts_data_t *data, /* {{{ */
+ 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 = data->data_sources_num + ((size_t) ci->values_num);
+ temp = (char **) realloc (data->data_sources,
+ new_data_sources_num * sizeof (char *));
+ if (temp == NULL)
+ {
+ ERROR ("`value' match: realloc failed.");
+ return (-1);
+ }
+ data->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 (data->data_sources_num < new_data_sources_num);
+
+ j = data->data_sources_num;
+ data->data_sources[j] = sstrdup (ci->values[i].value.string);
+ if (data->data_sources[j] == NULL)
+ {
+ ERROR ("`value' match: sstrdup failed.");
+ continue;
+ }
+ data->data_sources_num++;
+ }
+
+ return (0);
+} /* }}} int ts_config_add_data_source */
+
+static int ts_destroy (void **user_data) /* {{{ */
+{
+ ts_data_t *data;
+
+ if (user_data == NULL)
+ return (-EINVAL);
+
+ data = (ts_data_t *) *user_data;
+
+ if ((data != NULL) && (data->data_sources != NULL))
+ {
+ size_t i;
+ for (i = 0; i < data->data_sources_num; i++)
+ sfree (data->data_sources[i]);
+ sfree (data->data_sources);
+ }
+
+ sfree (data);
+ *user_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 if (strcasecmp ("DataSource", child->key) == 0)
+ status = ts_config_add_data_source(data, 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 we've got a list of data sources, is it in the list? */
+ if (data->data_sources) {
+ size_t j;
+ for (j = 0; j < data->data_sources_num; j++)
+ if (strcasecmp(ds->ds[i].name, data->data_sources[j]) == 0)
+ break;
+
+ /* No match, ignore */
+ if (j >= data->data_sources_num)
+ continue;
+ }
+
+ 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 : */
+
--- /dev/null
+/**
+ * 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 : */
+
--- /dev/null
+/**
+ * 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 : */
+
--- /dev/null
+/**
+ * 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
+# include <asm/types.h>
+/* sys/socket.h is necessary to compile when using netlink on older systems. */
+# include <sys/socket.h>
+# include <linux/netlink.h>
+# include <linux/inet_diag.h>
+# include <sys/socket.h>
+# include <arpa/inet.h>
+/* #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>
+# if !defined(HAVE_BSD_NLIST_H) || !HAVE_BSD_NLIST_H
+# include <nlist.h>
+# else /* HAVE_BSD_NLIST_H */
+# include <bsd/nlist.h>
+# endif
+# include <kvm.h>
+/* #endif HAVE_LIBKVM_NLIST */
+
+#elif KERNEL_AIX
+# include <arpa/inet.h>
+# include <sys/socketvar.h>
+#endif /* KERNEL_AIX */
+
+#if KERNEL_LINUX
+struct nlreq {
+ struct nlmsghdr nlh;
+ struct inet_diag_req r;
+};
+
+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 uint32_t sequence_number = 0;
+
+#if KERNEL_LINUX
+enum
+{
+ SRC_DUNNO,
+ SRC_NETLINK,
+ SRC_PROC
+} linux_source = SRC_DUNNO;
+#endif
+
+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
+/* Returns zero on success, less than zero on socket error and greater than
+ * zero on other errors. */
+static int conn_read_netlink (void)
+{
+ int fd;
+ struct sockaddr_nl nladdr;
+ struct nlreq req;
+ struct msghdr msg;
+ struct iovec iov;
+ struct inet_diag_msg *r;
+ char buf[8192];
+
+ /* If this fails, it's likely a permission problem. We'll fall back to
+ * reading this information from files below. */
+ fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_INET_DIAG);
+ if (fd < 0)
+ {
+ ERROR ("tcpconns plugin: conn_read_netlink: socket(AF_NETLINK, SOCK_RAW, "
+ "NETLINK_INET_DIAG) failed: %s",
+ sstrerror (errno, buf, sizeof (buf)));
+ return (-1);
+ }
+
+ memset(&nladdr, 0, sizeof(nladdr));
+ nladdr.nl_family = AF_NETLINK;
+
+ memset(&req, 0, sizeof(req));
+ req.nlh.nlmsg_len = sizeof(req);
+ req.nlh.nlmsg_type = TCPDIAG_GETSOCK;
+ /* NLM_F_ROOT: return the complete table instead of a single entry.
+ * NLM_F_MATCH: return all entries matching criteria (not implemented)
+ * NLM_F_REQUEST: must be set on all request messages */
+ req.nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;
+ req.nlh.nlmsg_pid = 0;
+ /* The sequence_number is used to track our messages. Since netlink is not
+ * reliable, we don't want to end up with a corrupt or incomplete old
+ * message in case the system is/was out of memory. */
+ req.nlh.nlmsg_seq = ++sequence_number;
+ req.r.idiag_family = AF_INET;
+ req.r.idiag_states = 0xfff;
+ req.r.idiag_ext = 0;
+
+ memset(&iov, 0, sizeof(iov));
+ iov.iov_base = &req;
+ iov.iov_len = sizeof(req);
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_name = (void*)&nladdr;
+ msg.msg_namelen = sizeof(nladdr);
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ if (sendmsg (fd, &msg, 0) < 0)
+ {
+ ERROR ("tcpconns plugin: conn_read_netlink: sendmsg(2) failed: %s",
+ sstrerror (errno, buf, sizeof (buf)));
+ close (fd);
+ return (-1);
+ }
+
+ iov.iov_base = buf;
+ iov.iov_len = sizeof(buf);
+
+ while (1)
+ {
+ int status;
+ struct nlmsghdr *h;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_name = (void*)&nladdr;
+ msg.msg_namelen = sizeof(nladdr);
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ status = recvmsg(fd, (void *) &msg, /* flags = */ 0);
+ if (status < 0)
+ {
+ if ((errno == EINTR) || (errno == EAGAIN))
+ continue;
+
+ ERROR ("tcpconns plugin: conn_read_netlink: recvmsg(2) failed: %s",
+ sstrerror (errno, buf, sizeof (buf)));
+ close (fd);
+ return (-1);
+ }
+ else if (status == 0)
+ {
+ close (fd);
+ DEBUG ("tcpconns plugin: conn_read_netlink: Unexpected zero-sized "
+ "reply from netlink socket.");
+ return (0);
+ }
+
+ h = (struct nlmsghdr*)buf;
+ while (NLMSG_OK(h, status))
+ {
+ if (h->nlmsg_seq != sequence_number)
+ {
+ h = NLMSG_NEXT(h, status);
+ continue;
+ }
+
+ if (h->nlmsg_type == NLMSG_DONE)
+ {
+ close (fd);
+ return (0);
+ }
+ else if (h->nlmsg_type == NLMSG_ERROR)
+ {
+ struct nlmsgerr *msg_error;
+
+ msg_error = NLMSG_DATA(h);
+ WARNING ("tcpconns plugin: conn_read_netlink: Received error %i.",
+ msg_error->error);
+
+ close (fd);
+ return (1);
+ }
+
+ r = NLMSG_DATA(h);
+
+ /* This code does not (need to) distinguish between IPv4 and IPv6. */
+ conn_handle_ports (ntohs(r->id.idiag_sport),
+ ntohs(r->id.idiag_dport),
+ r->idiag_state);
+
+ h = NLMSG_NEXT(h, status);
+ } /* while (NLMSG_OK) */
+ } /* while (1) */
+
+ /* Not reached because the while() loop above handles the exit condition. */
+ return (0);
+} /* int conn_read_netlink */
+
+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 status;
+
+ conn_reset_port_entry ();
+
+ if (linux_source == SRC_NETLINK)
+ {
+ status = conn_read_netlink ();
+ }
+ else if (linux_source == SRC_PROC)
+ {
+ int errors_num = 0;
+
+ if (conn_read_file ("/proc/net/tcp") != 0)
+ errors_num++;
+ if (conn_read_file ("/proc/net/tcp6") != 0)
+ errors_num++;
+
+ if (errors_num < 2)
+ status = 0;
+ else
+ status = ENOENT;
+ }
+ else /* if (linux_source == SRC_DUNNO) */
+ {
+ /* Try to use netlink for getting this data, it is _much_ faster on systems
+ * with a large amount of connections. */
+ status = conn_read_netlink ();
+ if (status == 0)
+ {
+ INFO ("tcpconns plugin: Reading from netlink succeeded. "
+ "Will use the netlink method from now on.");
+ linux_source = SRC_NETLINK;
+ }
+ else
+ {
+ INFO ("tcpconns plugin: Reading from netlink failed. "
+ "Will read from /proc from now on.");
+ linux_source = SRC_PROC;
+
+ /* return success here to avoid the "plugin failed" message. */
+ return (0);
+ }
+ }
+
+ if (status == 0)
+ conn_submit_all ();
+ else
+ return (status);
+
+ 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 :
+ */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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);
+}
+
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
-pf_counters value:COUNTER:0:U
-pf_limits value:COUNTER:0:U
-pf_state value:COUNTER:0:U
-pf_states value:GAUGE:0:U
-pf_source value:COUNTER:0:U
-states_cur value:GAUGE:0:U
-states_tot value:COUNTER:0:U
+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_eviction value:DERIVE: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
evaluations value:COUNTER:0:U
+fanspeed value:GAUGE:0:U
+file_size value:GAUGE:0:U
+files value:GAUGE:0:U
+fork_rate value:DERIVE:0:U
+frequency_offset value:GAUGE:-1000000:1000000
+frequency value:GAUGE:0:U
+fscache_stat value:DERIVE:0:U
+gauge value:GAUGE:U:U
+hash_collisions value:DERIVE:0: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
+md_disks value:GAUGE:0:U
+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
+mutex_operations value:DERIVE:0: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
pf_bytes_in value:COUNTER:0:U
pf_bytes_out value:COUNTER:0:U
+pf_counters value:COUNTER:0:U
+pf_limits value:COUNTER:0:U
pf_packets_in value:COUNTER:0:U
pf_packets_out value:COUNTER:0:U
+pf_source value:COUNTER:0:U
+pf_states value:GAUGE:0:U
+pf_state value:COUNTER:0:U
+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_stddev value:GAUGE:0:65535
+ping 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
+states_cur value:GAUGE:0:U
+states_tot value:COUNTER:0: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
+>>>>>>> master
--- /dev/null
+=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
+
--- /dev/null
+/**
+ * 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
+ */
--- /dev/null
+#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 */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * 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) */
--- /dev/null
+/**
+ * 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)
+{
+ if (t == NULL)
+ return;
+ 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);
+}
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * 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 : */
+
--- /dev/null
+/**
+ * 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 : */
+
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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, ×, &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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * 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 : */
+
--- /dev/null
+/**
+ * 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 : */
+
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/*
+ * 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_NETINET_IP6_H
+# include <netinet/ip6.h>
+#endif
+#if HAVE_NETINET_IP_COMPAT_H
+# include <netinet/ip_compat.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_sport
+#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 c_src_addr;
+ uint16_t payload_len;
+
+ if (0 > len)
+ return (0);
+
+ offset = sizeof (struct ip6_hdr);
+ nexthdr = ipv6->ip6_nxt;
+ c_src_addr = ipv6->ip6_src;
+ payload_len = ntohs (ipv6->ip6_plen);
+
+ if (ignore_list_match (&c_src_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_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 c_src_addr;
+ struct in6_addr c_dst_addr;
+
+ if (ip->ip_v == 6)
+ return (handle_ipv6 ((void *) ip, len));
+
+ in6_addr_from_buffer (&c_src_addr, &ip->ip_src.s_addr, sizeof (ip->ip_src.s_addr), AF_INET);
+ in6_addr_from_buffer (&c_dst_addr, &ip->ip_dst.s_addr, sizeof (ip->ip_dst.s_addr), AF_INET);
+ if (ignore_list_match (&c_src_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
+ */
--- /dev/null
+#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 */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * collectd - src/utils_format_graphite.c
+ * Copyright (C) 2012 Thomas Meson
+ * 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
+ *
+ * Authors:
+ * Thomas Meson <zllak at hycik.org>
+ * Florian octo Forster <octo at collectd.org>
+ **/
+
+#include "collectd.h"
+#include "plugin.h"
+#include "common.h"
+
+#include "utils_cache.h"
+#include "utils_format_json.h"
+#include "utils_parse_option.h"
+
+/* Utils functions to format data sets in graphite format.
+ * Largely taken from write_graphite.c as it remains the same formatting */
+
+static int gr_format_values (char *ret, size_t ret_len,
+ int ds_num, const data_set_t *ds, const value_list_t *vl)
+{
+ size_t offset = 0;
+ int status;
+
+ 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) \
+ { \
+ return (-1); \
+ } \
+ else if (((size_t) status) >= (ret_len - offset)) \
+ { \
+ return (-1); \
+ } \
+ else \
+ offset += ((size_t) status); \
+} while (0)
+
+ if (ds->ds[ds_num].type == DS_TYPE_GAUGE)
+ BUFFER_ADD ("%f", vl->values[ds_num].gauge);
+ else if (ds->ds[ds_num].type == DS_TYPE_COUNTER)
+ BUFFER_ADD ("%llu", vl->values[ds_num].counter);
+ else if (ds->ds[ds_num].type == DS_TYPE_DERIVE)
+ BUFFER_ADD ("%"PRIi64, vl->values[ds_num].derive);
+ else if (ds->ds[ds_num].type == DS_TYPE_ABSOLUTE)
+ BUFFER_ADD ("%"PRIu64, vl->values[ds_num].absolute);
+ else
+ {
+ ERROR ("gr_format_values plugin: Unknown data source type: %i",
+ ds->ds[ds_num].type);
+ return (-1);
+ }
+
+#undef BUFFER_ADD
+
+ return (0);
+}
+
+static void gr_copy_escape_part (char *dst, const char *src, size_t dst_len,
+ char escape_char)
+{
+ size_t i;
+
+ memset (dst, 0, dst_len);
+
+ if (src == NULL)
+ return;
+
+ for (i = 0; i < dst_len; i++)
+ {
+ if (src[i] == 0)
+ {
+ dst[i] = 0;
+ break;
+ }
+
+ if ((src[i] == '.')
+ || isspace ((int) src[i])
+ || iscntrl ((int) src[i]))
+ dst[i] = escape_char;
+ else
+ dst[i] = src[i];
+ }
+}
+
+static int gr_format_name (char *ret, int ret_len,
+ const value_list_t *vl,
+ const char *ds_name,
+ char *prefix,
+ char *postfix,
+ char escape_char)
+{
+ char n_host[DATA_MAX_NAME_LEN];
+ char n_plugin[DATA_MAX_NAME_LEN];
+ char n_plugin_instance[DATA_MAX_NAME_LEN];
+ char n_type[DATA_MAX_NAME_LEN];
+ char n_type_instance[DATA_MAX_NAME_LEN];
+
+ char tmp_plugin[2 * DATA_MAX_NAME_LEN + 1];
+ char tmp_type[2 * DATA_MAX_NAME_LEN + 1];
+
+ if (prefix == NULL)
+ prefix = "";
+
+ if (postfix == NULL)
+ postfix = "";
+
+ gr_copy_escape_part (n_host, vl->host,
+ sizeof (n_host), escape_char);
+ gr_copy_escape_part (n_plugin, vl->plugin,
+ sizeof (n_plugin), escape_char);
+ gr_copy_escape_part (n_plugin_instance, vl->plugin_instance,
+ sizeof (n_plugin_instance), escape_char);
+ gr_copy_escape_part (n_type, vl->type,
+ sizeof (n_type), escape_char);
+ gr_copy_escape_part (n_type_instance, vl->type_instance,
+ sizeof (n_type_instance), escape_char);
+
+ if (n_plugin_instance[0] != '\0')
+ ssnprintf (tmp_plugin, sizeof (tmp_plugin), "%s%c%s",
+ n_plugin,
+ '-',
+ n_plugin_instance);
+ else
+ sstrncpy (tmp_plugin, n_plugin, sizeof (tmp_plugin));
+
+ if (n_type_instance[0] != '\0')
+ ssnprintf (tmp_type, sizeof (tmp_type), "%s%c%s",
+ n_type,
+ '-',
+ n_type_instance);
+ else
+ sstrncpy (tmp_type, n_type, sizeof (tmp_type));
+
+ if (ds_name != NULL)
+ ssnprintf (ret, ret_len, "%s%s%s.%s.%s.%s",
+ prefix, n_host, postfix, tmp_plugin, tmp_type, ds_name);
+ else
+ ssnprintf (ret, ret_len, "%s%s%s.%s.%s",
+ prefix, n_host, postfix, tmp_plugin, tmp_type);
+
+ return (0);
+}
+
+int format_graphite (char *buffer, size_t buffer_size,
+ const data_set_t *ds, const value_list_t *vl, char *prefix,
+ char *postfix, char escape_char)
+{
+ int status = 0;
+ int i;
+ int buffer_pos = 0;
+
+ for (i = 0; i < ds->ds_num; i++)
+ {
+ const char *ds_name = NULL;
+ char key[10*DATA_MAX_NAME_LEN];
+ char values[512];
+ size_t message_len;
+ char message[1024];
+
+ ds_name = ds->ds[i].name;
+
+ /* Copy the identifier to `key' and escape it. */
+ status = gr_format_name (key, sizeof (key), vl, ds_name,
+ prefix, postfix, escape_char);
+ if (status != 0)
+ {
+ ERROR ("amqp plugin: error with gr_format_name");
+ return (status);
+ }
+
+ escape_string (key, sizeof (key));
+ /* Convert the values to an ASCII representation and put that into
+ * `values'. */
+ status = gr_format_values (values, sizeof (values), i, ds, vl);
+ if (status != 0)
+ {
+ ERROR ("format_graphite: error with gr_format_values");
+ return (status);
+ }
+
+ /* Compute the graphite command */
+ message_len = (size_t) ssnprintf (message, sizeof (message),
+ "%s %s %u\r\n",
+ key,
+ values,
+ (unsigned int) CDTIME_T_TO_TIME_T (vl->time));
+ if (message_len >= sizeof (message)) {
+ ERROR ("format_graphite: message buffer too small: "
+ "Need %zu bytes.", message_len + 1);
+ return (-ENOMEM);
+ }
+
+ /* Append it in case we got multiple data set */
+ if ((buffer_pos + message_len) >= buffer_size)
+ {
+ ERROR ("format_graphite: target buffer too small");
+ return (-ENOMEM);
+ }
+ memcpy((void *) (buffer + buffer_pos), message, message_len);
+ buffer_pos += message_len;
+ }
+ return (status);
+} /* int format_graphite */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
--- /dev/null
+/**
+ * collectd - src/utils_format_graphite.h
+ * Copyright (C) 2012 Thomas Meson
+ *
+ * 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:
+ * Thomas Meson <zllak at hycik.org>
+ **/
+
+#ifndef UTILS_FORMAT_GRAPHITE_H
+#define UTILS_FORMAT_GRAPHITE_H 1
+
+#include "collectd.h"
+#include "plugin.h"
+
+int format_graphite (char *buffer,
+ size_t buffer_size, const data_set_t *ds,
+ const value_list_t *vl, const char *prefix,
+ const char *postfix, const char escape_char);
+
+#endif /* UTILS_FORMAT_GRAPHITE_H */
--- /dev/null
+/**
+ * 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 escape_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)
+{
+ 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)
+{
+ 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 meta_data_to_json (char *buffer, size_t buffer_size, /* {{{ */
+ meta_data_t *meta)
+{
+ size_t offset = 0;
+ char **keys = NULL;
+ int keys_num;
+ int status;
+ int i;
+
+ 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)
+
+ keys_num = meta_data_toc (meta, &keys);
+ for (i = 0; i < keys_num; ++i)
+ {
+ int type;
+ char *key = keys[i];
+
+ type = meta_data_type (meta, key);
+ if (type == MD_TYPE_STRING)
+ {
+ char *value = NULL;
+ if (meta_data_get_string (meta, key, &value) == 0)
+ {
+ char temp[512] = "";
+ escape_string (temp, sizeof (temp), value);
+ sfree (value);
+ BUFFER_ADD (",\"%s\":%s", key, temp);
+ }
+ }
+ else if (type == MD_TYPE_SIGNED_INT)
+ {
+ int64_t value = 0;
+ if (meta_data_get_signed_int (meta, key, &value) == 0)
+ BUFFER_ADD (",\"%s\":%"PRIi64, key, value);
+ }
+ else if (type == MD_TYPE_UNSIGNED_INT)
+ {
+ uint64_t value = 0;
+ if (meta_data_get_unsigned_int (meta, key, &value) == 0)
+ BUFFER_ADD (",\"%s\":%"PRIu64, key, value);
+ }
+ else if (type == MD_TYPE_DOUBLE)
+ {
+ double value = 0.0;
+ if (meta_data_get_double (meta, key, &value) == 0)
+ BUFFER_ADD (",\"%s\":%f", key, value);
+ }
+ else if (type == MD_TYPE_BOOLEAN)
+ {
+ _Bool value = 0;
+ if (meta_data_get_boolean (meta, key, &value) == 0)
+ BUFFER_ADD (",\"%s\":%s", key, value ? "true" : "false");
+ }
+
+ free (key);
+ } /* for (keys) */
+ free (keys);
+
+ if (offset <= 0)
+ return (ENOENT);
+
+ buffer[0] = '{'; /* replace leading ',' */
+ BUFFER_ADD ("}");
+
+#undef BUFFER_ADD
+
+ return (0);
+} /* int meta_data_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);
+ if (status != 0)
+ return (status);
+ BUFFER_ADD (",\"dstypes\":%s", temp);
+
+ status = dsnames_to_json (temp, sizeof (temp), ds);
+ 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);
+
+ if (vl->meta != NULL)
+ {
+ char meta_buffer[buffer_size];
+ memset (meta_buffer, 0, sizeof (meta_buffer));
+ status = meta_data_to_json (meta_buffer, sizeof (meta_buffer), vl->meta);
+ if (status != 0)
+ return (status);
+
+ BUFFER_ADD (",\"meta\":%s", meta_buffer);
+ } /* if (vl->meta != NULL) */
+
+ 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 : */
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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) */
+
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * 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);
+}
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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) */
+
--- /dev/null
+/**
+ * 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 */
+
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 <= 0)
+ last_up = time (NULL);
+ last_up -= 1;
+
+ 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
+
--- /dev/null
+/**
+ * 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 : */
+
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/*
+ * 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 : */
--- /dev/null
+/*
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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:
+ */
--- /dev/null
+/**
+ * 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>
+
+#if HAVE_VARNISH_V3
+# include <varnish/vsc.h>
+typedef struct VSC_C_main c_varnish_stats_t;
+#endif
+
+#if HAVE_VARNISH_V2
+typedef struct varnish_stats c_varnish_stats_t;
+#endif
+
+/* {{{ 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_sms;
+#if HAVE_VARNISH_V2
+ _Bool collect_sm;
+ _Bool collect_sma;
+#endif
+ _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, /* {{{ */
+ const c_varnish_stats_t *stats)
+{
+ if (conf->collect_cache)
+ {
+ /* Cache hits */
+ varnish_submit_derive (conf->instance, "cache", "cache_result", "hit", stats->cache_hit);
+ /* Cache misses */
+ varnish_submit_derive (conf->instance, "cache", "cache_result", "miss", stats->cache_miss);
+ /* Cache hits for pass */
+ varnish_submit_derive (conf->instance, "cache", "cache_result", "hitpass", stats->cache_hitpass);
+ }
+
+ if (conf->collect_connections)
+ {
+ /* Client connections accepted */
+ varnish_submit_derive (conf->instance, "connections", "connections", "accepted", stats->client_conn);
+ /* Connection dropped, no sess */
+ varnish_submit_derive (conf->instance, "connections", "connections", "dropped" , stats->client_drop);
+ /* Client requests received */
+ varnish_submit_derive (conf->instance, "connections", "connections", "received", stats->client_req);
+ }
+
+ if (conf->collect_esi)
+ {
+ /* ESI parse errors (unlock) */
+ varnish_submit_derive (conf->instance, "esi", "total_operations", "error", stats->esi_errors);
+#if HAVE_VARNISH_V2
+ /* Objects ESI parsed (unlock) */
+ varnish_submit_derive (conf->instance, "esi", "total_operations", "parsed", stats->esi_parse);
+#endif
+ }
+
+ if (conf->collect_backend)
+ {
+ /* Backend conn. success */
+ varnish_submit_derive (conf->instance, "backend", "connections", "success" , stats->backend_conn);
+ /* Backend conn. not attempted */
+ varnish_submit_derive (conf->instance, "backend", "connections", "not-attempted", stats->backend_unhealthy);
+ /* Backend conn. too many */
+ varnish_submit_derive (conf->instance, "backend", "connections", "too-many" , stats->backend_busy);
+ /* Backend conn. failures */
+ varnish_submit_derive (conf->instance, "backend", "connections", "failures" , stats->backend_fail);
+ /* Backend conn. reuses */
+ varnish_submit_derive (conf->instance, "backend", "connections", "reuses" , stats->backend_reuse);
+ /* Backend conn. was closed */
+ varnish_submit_derive (conf->instance, "backend", "connections", "was-closed" , stats->backend_toolate);
+ /* Backend conn. recycles */
+ varnish_submit_derive (conf->instance, "backend", "connections", "recycled" , stats->backend_recycle);
+#if HAVE_VARNISH_V2
+ /* Backend conn. unused */
+ varnish_submit_derive (conf->instance, "backend", "connections", "unused" , stats->backend_unused);
+#endif
+ }
+
+ if (conf->collect_fetch)
+ {
+ /* Fetch head */
+ varnish_submit_derive (conf->instance, "fetch", "http_requests", "head" , stats->fetch_head);
+ /* Fetch with length */
+ varnish_submit_derive (conf->instance, "fetch", "http_requests", "length" , stats->fetch_length);
+ /* Fetch chunked */
+ varnish_submit_derive (conf->instance, "fetch", "http_requests", "chunked" , stats->fetch_chunked);
+ /* Fetch EOF */
+ varnish_submit_derive (conf->instance, "fetch", "http_requests", "eof" , stats->fetch_eof);
+ /* Fetch bad headers */
+ varnish_submit_derive (conf->instance, "fetch", "http_requests", "bad_headers", stats->fetch_bad);
+ /* Fetch wanted close */
+ varnish_submit_derive (conf->instance, "fetch", "http_requests", "close" , stats->fetch_close);
+ /* Fetch pre HTTP/1.1 closed */
+ varnish_submit_derive (conf->instance, "fetch", "http_requests", "oldhttp" , stats->fetch_oldhttp);
+ /* Fetch zero len */
+ varnish_submit_derive (conf->instance, "fetch", "http_requests", "zero" , stats->fetch_zero);
+ /* Fetch failed */
+ varnish_submit_derive (conf->instance, "fetch", "http_requests", "failed" , stats->fetch_failed);
+ }
+
+ if (conf->collect_hcb)
+ {
+ /* HCB Lookups without lock */
+ varnish_submit_derive (conf->instance, "hcb", "cache_operation", "lookup_nolock", stats->hcb_nolock);
+ /* HCB Lookups with lock */
+ varnish_submit_derive (conf->instance, "hcb", "cache_operation", "lookup_lock", stats->hcb_lock);
+ /* HCB Inserts */
+ varnish_submit_derive (conf->instance, "hcb", "cache_operation", "insert", stats->hcb_insert);
+ }
+
+ if (conf->collect_shm)
+ {
+ /* SHM records */
+ varnish_submit_derive (conf->instance, "shm", "total_operations", "records" , stats->shm_records);
+ /* SHM writes */
+ varnish_submit_derive (conf->instance, "shm", "total_operations", "writes" , stats->shm_writes);
+ /* SHM flushes due to overflow */
+ varnish_submit_derive (conf->instance, "shm", "total_operations", "flushes" , stats->shm_flushes);
+ /* SHM MTX contention */
+ varnish_submit_derive (conf->instance, "shm", "total_operations", "contention", stats->shm_cont);
+ /* SHM cycles through buffer */
+ varnish_submit_derive (conf->instance, "shm", "total_operations", "cycles" , stats->shm_cycles);
+ }
+
+#if HAVE_VARNISH_V2
+ if (conf->collect_sm)
+ {
+ /* allocator requests */
+ varnish_submit_derive (conf->instance, "sm", "total_requests", "nreq", stats->sm_nreq);
+ /* outstanding allocations */
+ varnish_submit_gauge (conf->instance, "sm", "requests", "outstanding", stats->sm_nobj);
+ /* bytes allocated */
+ varnish_submit_derive (conf->instance, "sm", "total_bytes", "allocated", stats->sm_balloc);
+ /* bytes free */
+ varnish_submit_derive (conf->instance, "sm", "total_bytes", "free", stats->sm_bfree);
+ }
+
+ if (conf->collect_sma)
+ {
+ /* SMA allocator requests */
+ varnish_submit_derive (conf->instance, "sma", "total_requests", "nreq", stats->sma_nreq);
+ /* SMA outstanding allocations */
+ varnish_submit_gauge (conf->instance, "sma", "requests", "outstanding", stats->sma_nobj);
+ /* SMA outstanding bytes */
+ varnish_submit_gauge (conf->instance, "sma", "bytes", "outstanding", stats->sma_nbytes);
+ /* SMA bytes allocated */
+ varnish_submit_derive (conf->instance, "sma", "total_bytes", "allocated", stats->sma_balloc);
+ /* SMA bytes free */
+ varnish_submit_derive (conf->instance, "sma", "total_bytes", "free" , stats->sma_bfree);
+ }
+#endif
+
+ if (conf->collect_sms)
+ {
+ /* SMS allocator requests */
+ varnish_submit_derive (conf->instance, "sms", "total_requests", "allocator", stats->sms_nreq);
+ /* SMS outstanding allocations */
+ varnish_submit_gauge (conf->instance, "sms", "requests", "outstanding", stats->sms_nobj);
+ /* SMS outstanding bytes */
+ varnish_submit_gauge (conf->instance, "sms", "bytes", "outstanding", stats->sms_nbytes);
+ /* SMS bytes allocated */
+ varnish_submit_derive (conf->instance, "sms", "total_bytes", "allocated", stats->sms_balloc);
+ /* SMS bytes freed */
+ varnish_submit_derive (conf->instance, "sms", "total_bytes", "free", stats->sms_bfree);
+ }
+
+ if (conf->collect_totals)
+ {
+ /* Total Sessions */
+ varnish_submit_derive (conf->instance, "totals", "total_sessions", "sessions", stats->s_sess);
+ /* Total Requests */
+ varnish_submit_derive (conf->instance, "totals", "total_requests", "requests", stats->s_req);
+ /* Total pipe */
+ varnish_submit_derive (conf->instance, "totals", "total_operations", "pipe", stats->s_pipe);
+ /* Total pass */
+ varnish_submit_derive (conf->instance, "totals", "total_operations", "pass", stats->s_pass);
+ /* Total fetch */
+ varnish_submit_derive (conf->instance, "totals", "total_operations", "fetches", stats->s_fetch);
+ /* Total header bytes */
+ varnish_submit_derive (conf->instance, "totals", "total_bytes", "header-bytes", stats->s_hdrbytes);
+ /* Total body byte */
+ varnish_submit_derive (conf->instance, "totals", "total_bytes", "body-bytes", stats->s_bodybytes);
+ }
+
+ if (conf->collect_workers)
+ {
+ /* worker threads */
+ varnish_submit_gauge (conf->instance, "workers", "threads", "worker", stats->n_wrk);
+ /* worker threads created */
+ varnish_submit_derive (conf->instance, "workers", "total_threads", "created", stats->n_wrk_create);
+ /* worker threads not created */
+ varnish_submit_derive (conf->instance, "workers", "total_threads", "failed", stats->n_wrk_failed);
+ /* worker threads limited */
+ varnish_submit_derive (conf->instance, "workers", "total_threads", "limited", stats->n_wrk_max);
+ /* dropped work requests */
+ varnish_submit_derive (conf->instance, "workers", "total_requests", "dropped", stats->n_wrk_drop);
+#ifdef HAVE_VARNISH_V2
+ /* queued work requests */
+ varnish_submit_derive (conf->instance, "workers", "total_requests", "queued", stats->n_wrk_queue);
+ /* overflowed work requests */
+ varnish_submit_derive (conf->instance, "workers", "total_requests", "overflowed", stats->n_wrk_overflow);
+#endif
+ }
+} /* }}} void varnish_monitor */
+
+#if HAVE_VARNISH_V3
+static int varnish_read (user_data_t *ud) /* {{{ */
+{
+ struct VSM_data *vd;
+ const c_varnish_stats_t *stats;
+
+ user_config_t *conf;
+
+ if ((ud == NULL) || (ud->data == NULL))
+ return (EINVAL);
+
+ conf = ud->data;
+
+ vd = VSM_New();
+ VSC_Setup(vd);
+ if (VSM_n_Arg(vd, conf->instance) == -1)
+ {
+ ERROR ("Varnish plugin : unable to load statistics from instance");
+ return (-1);
+ }
+ if (VSC_Open (vd, /* diag = */ 1))
+ {
+ ERROR ("varnish plugin: Unable to load statistics.");
+
+ return (-1);
+ }
+
+ stats = VSC_Main(vd);
+
+ varnish_monitor (conf, stats);
+ VSM_Close (vd);
+
+ return (0);
+} /* }}} */
+#else /* if HAVE_VARNISH_V2 */
+static int varnish_read (user_data_t *ud) /* {{{ */
+{
+ const c_varnish_stats_t *stats;
+
+ user_config_t *conf;
+
+ if ((ud == NULL) || (ud->data == NULL))
+ return (EINVAL);
+
+ conf = ud->data;
+
+ stats = VSL_OpenStats (conf->instance);
+ if (stats == NULL)
+ {
+ ERROR ("Varnish plugin : unable to load statistics");
+
+ return (-1);
+ }
+
+ varnish_monitor (conf, stats);
+
+ return (0);
+} /* }}} */
+#endif
+
+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;
+#if HAVE_VARNISH_V2
+ conf->collect_sm = 0;
+ conf->collect_sma = 0;
+#endif
+ 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 ("CollectSMS", child->key) == 0)
+ cf_util_get_boolean (child, &conf->collect_sms);
+#if HAVE_VARNISH_V2
+ else if (strcasecmp ("CollectSMA", child->key) == 0)
+ cf_util_get_boolean (child, &conf->collect_sma);
+ else if (strcasecmp ("CollectSM", child->key) == 0)
+ cf_util_get_boolean (child, &conf->collect_sm);
+#endif
+ 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_sms
+#if HAVE_VARNISH_V2
+ && !conf->collect_sma
+ && !conf->collect_sm
+#endif
+ && !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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * collectd - src/write_graphite.c
+ * Copyright (C) 2012 Pierre-Yves Ritschard
+ * Copyright (C) 2011 Scott Sanders
+ * Copyright (C) 2009 Paul Sadauskas
+ * Copyright (C) 2009 Doug MacEachern
+ * Copyright (C) 2007-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
+ *
+ * Authors:
+ * Florian octo Forster <octo at collectd.org>
+ * Doug MacEachern <dougm at hyperic.com>
+ * Paul Sadauskas <psadauskas at gmail.com>
+ * Scott Sanders <scott at jssjr.com>
+ * Pierre-Yves Ritschard <pyr at spootnik.org>
+ *
+ * Based on the write_http plugin.
+ **/
+
+ /* write_graphite plugin configuation example
+ *
+ * <Plugin write_graphite>
+ * <Carbon>
+ * Host "localhost"
+ * Port "2003"
+ * Prefix "collectd"
+ * </Carbon>
+ * </Plugin>
+ */
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+
+#include "utils_cache.h"
+#include "utils_parse_option.h"
+#include "utils_format_graphite.h"
+
+/* Folks without pthread will need to disable this plugin. */
+#include <pthread.h>
+
+#include <sys/socket.h>
+#include <netdb.h>
+
+#ifndef WG_DEFAULT_NODE
+# define WG_DEFAULT_NODE "localhost"
+#endif
+
+#ifndef WG_DEFAULT_SERVICE
+# define WG_DEFAULT_SERVICE "2003"
+#endif
+
+#ifndef WG_DEFAULT_ESCAPE
+# define WG_DEFAULT_ESCAPE '_'
+#endif
+
+/* Ethernet - (IPv6 + TCP) = 1500 - (40 + 32) = 1428 */
+#ifndef WG_SEND_BUF_SIZE
+# define WG_SEND_BUF_SIZE 1428
+#endif
+
+/*
+ * Private variables
+ */
+struct wg_callback
+{
+ int sock_fd;
+
+ char *node;
+ char *service;
+ char *prefix;
+ char *postfix;
+ char escape_char;
+
+ _Bool store_rates;
+ _Bool separate_instances;
+ _Bool always_append_ds;
+
+ char send_buf[WG_SEND_BUF_SIZE];
+ size_t send_buf_free;
+ size_t send_buf_fill;
+ cdtime_t send_buf_init_time;
+
+ pthread_mutex_t send_lock;
+};
+
+
+/*
+ * Functions
+ */
+static void wg_reset_buffer (struct wg_callback *cb)
+{
+ memset (cb->send_buf, 0, sizeof (cb->send_buf));
+ cb->send_buf_free = sizeof (cb->send_buf);
+ cb->send_buf_fill = 0;
+ cb->send_buf_init_time = cdtime ();
+}
+
+static int wg_send_buffer (struct wg_callback *cb)
+{
+ ssize_t status = 0;
+
+ status = swrite (cb->sock_fd, cb->send_buf, strlen (cb->send_buf));
+ if (status < 0)
+ {
+ char errbuf[1024];
+ ERROR ("write_graphite plugin: send failed with status %zi (%s)",
+ status, sstrerror (errno, errbuf, sizeof (errbuf)));
+
+
+ close (cb->sock_fd);
+ cb->sock_fd = -1;
+
+ return (-1);
+ }
+
+ return (0);
+}
+
+/* NOTE: You must hold cb->send_lock when calling this function! */
+static int wg_flush_nolock (cdtime_t timeout, struct wg_callback *cb)
+{
+ int status;
+
+ DEBUG ("write_graphite plugin: wg_flush_nolock: timeout = %.3f; "
+ "send_buf_fill = %zu;",
+ (double)timeout,
+ cb->send_buf_fill);
+
+ /* timeout == 0 => flush unconditionally */
+ if (timeout > 0)
+ {
+ cdtime_t now;
+
+ now = cdtime ();
+ if ((cb->send_buf_init_time + timeout) > now)
+ return (0);
+ }
+
+ if (cb->send_buf_fill <= 0)
+ {
+ cb->send_buf_init_time = cdtime ();
+ return (0);
+ }
+
+ status = wg_send_buffer (cb);
+ wg_reset_buffer (cb);
+
+ return (status);
+}
+
+static int wg_callback_init (struct wg_callback *cb)
+{
+ struct addrinfo ai_hints;
+ struct addrinfo *ai_list;
+ struct addrinfo *ai_ptr;
+ int status;
+
+ const char *node = cb->node ? cb->node : WG_DEFAULT_NODE;
+ const char *service = cb->service ? cb->service : WG_DEFAULT_SERVICE;
+
+ if (cb->sock_fd > 0)
+ return (0);
+
+ 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;
+
+ ai_list = NULL;
+
+ status = getaddrinfo (node, service, &ai_hints, &ai_list);
+ if (status != 0)
+ {
+ ERROR ("write_graphite plugin: getaddrinfo (%s, %s) failed: %s",
+ node, service, gai_strerror (status));
+ return (-1);
+ }
+
+ assert (ai_list != NULL);
+ for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
+ {
+ cb->sock_fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype,
+ ai_ptr->ai_protocol);
+ if (cb->sock_fd < 0)
+ continue;
+
+ status = connect (cb->sock_fd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
+ if (status != 0)
+ {
+ close (cb->sock_fd);
+ cb->sock_fd = -1;
+ continue;
+ }
+
+ break;
+ }
+
+ freeaddrinfo (ai_list);
+
+ if (cb->sock_fd < 0)
+ {
+ char errbuf[1024];
+ ERROR ("write_graphite plugin: Connecting to %s:%s failed. "
+ "The last error was: %s", node, service,
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ close (cb->sock_fd);
+ return (-1);
+ }
+
+ wg_reset_buffer (cb);
+
+ return (0);
+}
+
+static void wg_callback_free (void *data)
+{
+ struct wg_callback *cb;
+
+ if (data == NULL)
+ return;
+
+ cb = data;
+
+ pthread_mutex_lock (&cb->send_lock);
+
+ wg_flush_nolock (/* timeout = */ 0, cb);
+
+ close(cb->sock_fd);
+ cb->sock_fd = -1;
+
+ sfree(cb->node);
+ sfree(cb->service);
+ sfree(cb->prefix);
+ sfree(cb->postfix);
+
+ pthread_mutex_destroy (&cb->send_lock);
+
+ sfree(cb);
+}
+
+static int wg_flush (cdtime_t timeout,
+ const char *identifier __attribute__((unused)),
+ user_data_t *user_data)
+{
+ struct wg_callback *cb;
+ int status;
+
+ if (user_data == NULL)
+ return (-EINVAL);
+
+ cb = user_data->data;
+
+ pthread_mutex_lock (&cb->send_lock);
+
+ if (cb->sock_fd < 0)
+ {
+ status = wg_callback_init (cb);
+ if (status != 0)
+ {
+ ERROR ("write_graphite plugin: wg_callback_init failed.");
+ pthread_mutex_unlock (&cb->send_lock);
+ return (-1);
+ }
+ }
+
+ status = wg_flush_nolock (timeout, cb);
+ pthread_mutex_unlock (&cb->send_lock);
+
+ return (status);
+}
+
+static int wg_send_message (char const *message, struct wg_callback *cb)
+{
+ int status;
+ size_t message_len;
+
+ message_len = strlen (message);
+
+ pthread_mutex_lock (&cb->send_lock);
+
+ if (cb->sock_fd < 0)
+ {
+ status = wg_callback_init (cb);
+ if (status != 0)
+ {
+ ERROR ("write_graphite plugin: wg_callback_init failed.");
+ pthread_mutex_unlock (&cb->send_lock);
+ return (-1);
+ }
+ }
+
+ if (message_len >= cb->send_buf_free)
+ {
+ status = wg_flush_nolock (/* timeout = */ 0, cb);
+ if (status != 0)
+ {
+ pthread_mutex_unlock (&cb->send_lock);
+ return (status);
+ }
+ }
+
+ /* Assert that we have enough space for this message. */
+ assert (message_len < cb->send_buf_free);
+
+ /* `message_len + 1' because `message_len' does not include the
+ * trailing null byte. Neither does `send_buffer_fill'. */
+ memcpy (cb->send_buf + cb->send_buf_fill,
+ message, message_len + 1);
+ cb->send_buf_fill += message_len;
+ cb->send_buf_free -= message_len;
+
+ DEBUG ("write_graphite plugin: [%s]:%s buf %zu/%zu (%.1f %%) \"%s\"",
+ cb->node,
+ cb->service,
+ cb->send_buf_fill, sizeof (cb->send_buf),
+ 100.0 * ((double) cb->send_buf_fill) / ((double) sizeof (cb->send_buf)),
+ message);
+
+ pthread_mutex_unlock (&cb->send_lock);
+
+ return (0);
+}
+
+static int wg_write_messages (const data_set_t *ds, const value_list_t *vl,
+ struct wg_callback *cb)
+{
+ char buffer[4096];
+ int status;
+
+ if (0 != strcmp (ds->type, vl->type))
+ {
+ ERROR ("write_graphite plugin: DS type does not match "
+ "value list type");
+ return -1;
+ }
+
+ memset (buffer, 0, sizeof (buffer));
+ status = format_graphite (buffer, sizeof (buffer), ds, vl,
+ cb->prefix, cb->postfix, cb->escape_char);
+ if (status != 0) /* error message has been printed already. */
+ return (status);
+
+ wg_send_message (buffer, cb);
+ if (status != 0)
+ {
+ ERROR ("write_graphite plugin: wg_send_message failed "
+ "with status %i.", status);
+ return (status);
+ }
+
+ return (0);
+} /* int wg_write_messages */
+
+static int wg_write (const data_set_t *ds, const value_list_t *vl,
+ user_data_t *user_data)
+{
+ struct wg_callback *cb;
+ int status;
+
+ if (user_data == NULL)
+ return (EINVAL);
+
+ cb = user_data->data;
+
+ status = wg_write_messages (ds, vl, cb);
+
+ return (status);
+}
+
+static int config_set_char (char *dest,
+ oconfig_item_t *ci)
+{
+ char buffer[4];
+ int status;
+
+ memset (buffer, 0, sizeof (buffer));
+
+ status = cf_util_get_string_buffer (ci, buffer, sizeof (buffer));
+ if (status != 0)
+ return (status);
+
+ if (buffer[0] == 0)
+ {
+ ERROR ("write_graphite plugin: Cannot use an empty string for the "
+ "\"EscapeCharacter\" option.");
+ return (-1);
+ }
+
+ if (buffer[1] != 0)
+ {
+ WARNING ("write_graphite plugin: Only the first character of the "
+ "\"EscapeCharacter\" option ('%c') will be used.",
+ (int) buffer[0]);
+ }
+
+ *dest = buffer[0];
+
+ return (0);
+}
+
+static int wg_config_carbon (oconfig_item_t *ci)
+{
+ struct wg_callback *cb;
+ user_data_t user_data;
+ char callback_name[DATA_MAX_NAME_LEN];
+ int i;
+
+ cb = malloc (sizeof (*cb));
+ if (cb == NULL)
+ {
+ ERROR ("write_graphite plugin: malloc failed.");
+ return (-1);
+ }
+ memset (cb, 0, sizeof (*cb));
+ cb->sock_fd = -1;
+ cb->node = NULL;
+ cb->service = NULL;
+ cb->prefix = NULL;
+ cb->postfix = NULL;
+ cb->escape_char = WG_DEFAULT_ESCAPE;
+ cb->store_rates = 1;
+
+ pthread_mutex_init (&cb->send_lock, /* attr = */ NULL);
+
+ for (i = 0; i < ci->children_num; i++)
+ {
+ oconfig_item_t *child = ci->children + i;
+
+ if (strcasecmp ("Host", child->key) == 0)
+ cf_util_get_string (child, &cb->node);
+ else if (strcasecmp ("Port", child->key) == 0)
+ cf_util_get_service (child, &cb->service);
+ else if (strcasecmp ("Prefix", child->key) == 0)
+ cf_util_get_string (child, &cb->prefix);
+ else if (strcasecmp ("Postfix", child->key) == 0)
+ cf_util_get_string (child, &cb->postfix);
+ else if (strcasecmp ("StoreRates", child->key) == 0)
+ cf_util_get_boolean (child, &cb->store_rates);
+ else if (strcasecmp ("SeparateInstances", child->key) == 0)
+ cf_util_get_boolean (child, &cb->separate_instances);
+ else if (strcasecmp ("AlwaysAppendDS", child->key) == 0)
+ cf_util_get_boolean (child, &cb->always_append_ds);
+ else if (strcasecmp ("EscapeCharacter", child->key) == 0)
+ config_set_char (&cb->escape_char, child);
+ else
+ {
+ ERROR ("write_graphite plugin: Invalid configuration "
+ "option: %s.", child->key);
+ }
+ }
+
+ ssnprintf (callback_name, sizeof (callback_name), "write_graphite/%s/%s",
+ cb->node != NULL ? cb->node : WG_DEFAULT_NODE,
+ cb->service != NULL ? cb->service : WG_DEFAULT_SERVICE);
+
+ memset (&user_data, 0, sizeof (user_data));
+ user_data.data = cb;
+ user_data.free_func = wg_callback_free;
+ plugin_register_write (callback_name, wg_write, &user_data);
+
+ user_data.free_func = NULL;
+ plugin_register_flush (callback_name, wg_flush, &user_data);
+
+ return (0);
+}
+
+static int wg_config (oconfig_item_t *ci)
+{
+ int i;
+
+ for (i = 0; i < ci->children_num; i++)
+ {
+ oconfig_item_t *child = ci->children + i;
+
+ if (strcasecmp ("Carbon", child->key) == 0)
+ wg_config_carbon (child);
+ else
+ {
+ ERROR ("write_graphite plugin: Invalid configuration "
+ "option: %s.", child->key);
+ }
+ }
+
+ return (0);
+}
+
+void module_register (void)
+{
+ plugin_register_complex_config ("write_graphite", wg_config);
+}
+
+/* vim: set sw=4 ts=4 sts=4 tw=78 et : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * collectd - src/write_mongodb.c
+ * Copyright (C) 2010-2012 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 "utils_cache.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;
+
+ _Bool store_rates;
+
+ mongo conn[1];
+ pthread_mutex_t lock;
+};
+typedef struct wm_node_s wm_node_t;
+
+/*
+ * Functions
+ */
+static bson *wm_create_bson (const data_set_t *ds, /* {{{ */
+ const value_list_t *vl,
+ _Bool store_rates)
+{
+ bson *ret;
+ gauge_t *rates;
+ int i;
+
+ ret = bson_create ();
+ if (ret == NULL)
+ {
+ ERROR ("write_mongodb plugin: bson_create failed.");
+ return (NULL);
+ }
+
+ if (store_rates)
+ {
+ rates = uc_get_rate (ds, vl);
+ if (rates == NULL)
+ {
+ ERROR ("write_mongodb plugin: uc_get_rate() failed.");
+ return (NULL);
+ }
+ }
+ else
+ {
+ rates = NULL;
+ }
+
+ bson_init (ret);
+ bson_append_date (ret, "time", (bson_date_t) CDTIME_T_TO_MS (vl->time));
+ bson_append_string (ret, "host", vl->host);
+ bson_append_string (ret, "plugin", vl->plugin);
+ bson_append_string (ret, "plugin_instance", vl->plugin_instance);
+ bson_append_string (ret, "type", vl->type);
+ bson_append_string (ret, "type_instance", vl->type_instance);
+
+ bson_append_start_array (ret, "values"); /* {{{ */
+ for (i = 0; i < ds->ds_num; i++)
+ {
+ char key[16];
+
+ ssnprintf (key, sizeof (key), "%i", i);
+
+ if (ds->ds[i].type == DS_TYPE_GAUGE)
+ bson_append_double(ret, key, vl->values[i].gauge);
+ else if (store_rates)
+ bson_append_double(ret, key, (double) rates[i]);
+ else if (ds->ds[i].type == DS_TYPE_COUNTER)
+ bson_append_long(ret, key, vl->values[i].counter);
+ else if (ds->ds[i].type == DS_TYPE_DERIVE)
+ bson_append_long(ret, key, vl->values[i].derive);
+ else if (ds->ds[i].type == DS_TYPE_ABSOLUTE)
+ bson_append_long(ret, key, vl->values[i].absolute);
+ else
+ assert (23 == 42);
+ }
+ bson_append_finish_array (ret); /* }}} values */
+
+ bson_append_start_array (ret, "dstypes"); /* {{{ */
+ for (i = 0; i < ds->ds_num; i++)
+ {
+ char key[16];
+
+ ssnprintf (key, sizeof (key), "%i", i);
+
+ if (store_rates)
+ bson_append_string (ret, key, "gauge");
+ else
+ bson_append_string (ret, key, DS_TYPE_TO_STRING (ds->ds[i].type));
+ }
+ bson_append_finish_array (ret); /* }}} dstypes */
+
+ bson_append_start_array (ret, "dsnames"); /* {{{ */
+ for (i = 0; i < ds->ds_num; i++)
+ {
+ char key[16];
+
+ ssnprintf (key, sizeof (key), "%i", i);
+ bson_append_string (ret, key, ds->ds[i].name);
+ }
+ bson_append_finish_array (ret); /* }}} dsnames */
+
+ bson_finish (ret);
+
+ sfree (rates);
+ return (ret);
+} /* }}} bson *wm_create_bson */
+
+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];
+ bson *bson_record;
+ int status;
+
+ ssnprintf (collection_name, sizeof (collection_name), "collectd.%s",
+ vl->plugin);
+
+ bson_record = wm_create_bson (ds, vl, node->store_rates);
+ if (bson_record == NULL)
+ return (ENOMEM);
+
+ pthread_mutex_lock (&node->lock);
+
+ if (!mongo_is_connected (node->conn))
+ {
+ INFO ("write_mongodb plugin: Connecting to [%s]:%i",
+ (node->host != NULL) ? node->host : "localhost",
+ (node->port != 0) ? node->port : MONGO_DEFAULT_PORT);
+ status = mongo_connect (node->conn, node->host, node->port);
+ if (status != MONGO_OK) {
+ ERROR ("write_mongodb plugin: Connecting to [%s]:%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);
+ }
+
+ if (node->timeout > 0) {
+ status = mongo_set_op_timeout (node->conn, node->timeout);
+ if (status != MONGO_OK) {
+ WARNING ("write_mongodb plugin: mongo_set_op_timeout(%i) failed: %s",
+ node->timeout, node->conn->errstr);
+ }
+ }
+ }
+
+ /* Assert if the connection has been established */
+ assert (mongo_is_connected (node->conn));
+
+ #if MONGO_MINOR >= 6
+ /* There was an API change in 0.6.0 as linked below */
+ /* https://github.com/mongodb/mongo-c-driver/blob/master/HISTORY.md */
+ status = mongo_insert (node->conn, collection_name, bson_record, NULL);
+ #else
+ status = mongo_insert (node->conn, collection_name, bson_record);
+ #endif
+
+ 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 (bson_record->err)
+ ERROR ("write_mongodb plugin: %s", bson_record->errstr);
+
+ /* Disconnect except on data errors. */
+ if ((node->conn->err != MONGO_BSON_INVALID)
+ && (node->conn->err != MONGO_BSON_NOT_FINISHED))
+ mongo_destroy (node->conn);
+ }
+
+ pthread_mutex_unlock (&node->lock);
+
+ /* free our resource as not to leak memory */
+ bson_dispose (bson_record);
+
+ return (0);
+} /* }}} int wm_write */
+
+static void wm_config_free (void *ptr) /* {{{ */
+{
+ wm_node_t *node = ptr;
+
+ if (node == NULL)
+ return;
+
+ if (mongo_is_connected (node->conn))
+ mongo_destroy (node->conn);
+
+ 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));
+ mongo_init (node->conn);
+ node->host = NULL;
+ node->store_rates = 1;
+ 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 if (strcasecmp ("StoreRates", child->key) == 0)
+ status = cf_util_get_boolean (child, &node->store_rates);
+ 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 : */
--- /dev/null
+/**
+ * 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 : */
--- /dev/null
+/**
+ * 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
+ */
--- /dev/null
+/**
+ * collectd - src/zfs_arc.c
+ * Copyright (C) 2009 Anthony Dewhurst
+ * Copyright (C) 2012 Aurelien Rougemont
+ *
+ * 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>
+ * Aurelien Rougemont <beorn at gandi.net>
+ **/
+
+#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 int za_read_derive (kstat_t *ksp, const char *kstat_value,
+ const char *type, const char *type_instance)
+{
+ long long tmp;
+ value_t v;
+
+ tmp = get_kstat_value (ksp, (char *)kstat_value);
+ if (tmp == -1LL)
+ {
+ ERROR ("zfs_arc plugin: Reading kstat value \"%s\" failed.", kstat_value);
+ return (-1);
+ }
+
+ v.derive = (derive_t) tmp;
+ za_submit (type, type_instance, /* values = */ &v, /* values_num = */ 1);
+ return (0);
+}
+
+static int za_read_gauge (kstat_t *ksp, const char *kstat_value,
+ const char *type, const char *type_instance)
+{
+ long long tmp;
+ value_t v;
+
+ tmp = get_kstat_value (ksp, (char *)kstat_value);
+ if (tmp == -1LL)
+ {
+ ERROR ("zfs_arc plugin: Reading kstat value \"%s\" failed.", kstat_value);
+ return (-1);
+ }
+
+ v.gauge = (gauge_t) tmp;
+ za_submit (type, type_instance, /* values = */ &v, /* values_num = */ 1);
+ return (0);
+}
+
+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_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 */
+ za_read_gauge (ksp, "size", "cache_size", "arc");
+ za_read_gauge (ksp, "l2_size", "cache_size", "L2");
+
+ /* Operations */
+ za_read_derive (ksp, "allocated","cache_operation", "allocated");
+ za_read_derive (ksp, "deleted", "cache_operation", "deleted");
+ za_read_derive (ksp, "stolen", "cache_operation", "stolen");
+
+ /* Issue indicators */
+ za_read_derive (ksp, "mutex_miss", "mutex_operation", "miss");
+ za_read_derive (ksp, "hash_collisions", "hash_collisions", "");
+
+ /* Evictions */
+ za_read_derive (ksp, "evict_l2_cached", "cache_eviction", "cached");
+ za_read_derive (ksp, "evict_l2_eligible", "cache_eviction", "eligible");
+ za_read_derive (ksp, "evict_l2_ineligible", "cache_eviction", "ineligible");
+
+ /* Hits / misses */
+ za_read_derive (ksp, "demand_data_hits", "cache_result", "demand_data-hit");
+ za_read_derive (ksp, "demand_metadata_hits", "cache_result", "demand_metadata-hit");
+ za_read_derive (ksp, "prefetch_data_hits", "cache_result", "prefetch_data-hit");
+ za_read_derive (ksp, "prefetch_metadata_hits", "cache_result", "prefetch_metadata-hit");
+ za_read_derive (ksp, "demand_data_misses", "cache_result", "demand_data-miss");
+ za_read_derive (ksp, "demand_metadata_misses", "cache_result", "demand_metadata-miss");
+ za_read_derive (ksp, "prefetch_data_misses", "cache_result", "prefetch_data-miss");
+ za_read_derive (ksp, "prefetch_metadata_misses", "cache_result", "prefetch_metadata-miss");
+
+ /* 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 : */
--- /dev/null
+#!/usr/bin/env bash
+
+DEFAULT_VERSION="5.1.0.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" ; then
+ echo "$VERSION\c"
+else
+ echo -n "$VERSION"
+fi