summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: df87ab0)
raw | patch | inline | side by side (parent: df87ab0)
author | oetiker <oetiker@a5681a0c-68f1-0310-ab6d-d61299d08faa> | |
Sun, 7 Sep 2008 10:23:10 +0000 (10:23 +0000) | ||
committer | oetiker <oetiker@a5681a0c-68f1-0310-ab6d-d61299d08faa> | |
Sun, 7 Sep 2008 10:23:10 +0000 (10:23 +0000) |
182 files changed:
diff --git a/program/.indent.pro b/program/.indent.pro
--- /dev/null
+++ b/program/.indent.pro
@@ -0,0 +1,88 @@
+--blank-lines-after-declarations
+--blank-lines-after-procedures
+--break-before-boolean-operator
+--break-function-decl-args
+--no-blank-lines-after-commas
+--braces-on-if-line
+--braces-on-struct-decl-line
+--comment-indentation25
+--declaration-comment-column25
+--no-comment-delimiters-on-blank-lines
+--cuddle-else
+--continuation-indentation4
+--case-indentation0
+--else-endif-column33
+--space-after-cast
+--line-comments-indentation0
+--declaration-indentation10
+--dont-format-first-column-comments
+--dont-format-comments
+--honour-newlines
+--indent-level4
+/* changed from 0 to 4 */
+--parameter-indentation4
+--line-length78 /* changed from 75 */
+--continue-at-parentheses
+--no-space-after-function-call-names
+--dont-break-procedure-type
+--dont-star-comments
+--leave-optional-blank-lines
+--dont-space-special-semicolon
+--tab-size4
+/* additions by Mark */
+--case-brace-indentation0
+--leave-preprocessor-space
+--no-tabs
+-TFIFOqueue
+-TFnv32_t
+-TTcl_Interp
+-T_ArtRgbaSVPAlphaData
+-Tafm_fontinfo
+-Tafm_sint16
+-Tafm_sint8
+-Tafm_uint16
+-Tafm_uint8
+-Tafm_unicode
+-Tcdp_prep_t
+-Tcgi_s
+-Tds_def_t
+-Teps_font
+-Teps_state
+-Tgfx_canvas_t
+-Tgfx_char_s
+-Tgfx_color_t
+-Tgfx_node_t
+-Tgfx_string_s
+-Tgraph_desc_t
+-Timage_desc_t
+-Tinfo_t
+-Tinfoval
+-Tlive_head_t
+-Toff_t
+-Told_afm_fontinfo
+-Tpdf_buffer
+-Tpdf_font
+-Tpdf_point
+-Tpdf_state
+-Tpdp_prep_t
+-Trpn_cdefds_t
+-Trpnp_t
+-Trpnstack_t
+-Trra_def_t
+-Trra_ptr_t
+-Trrd_file_t
+-Trrd_t
+-Trrd_value_t
+-Tstat_head_t
+-Tstring_arr_t
+-Tsvg_dash
+-Ttext_prop_t
+-Ttime_t
+-Ttm
+-Tunival
+-Tva_list
+-Tvar_s
+-Tvdef_t
+-Txlab_t
+-Tygrid_scale_t
+-Tylab_t
diff --git a/program/00README b/program/00README
--- /dev/null
+++ b/program/00README
@@ -0,0 +1,6 @@
+Title: RRDtool
+Date: 2008-06-11
+Owner: Tobias Oetiker <tobi@oetiker.ch>
+Group: Software
+
+Round Robin Database Tool
diff --git a/program/CONTRIBUTORS b/program/CONTRIBUTORS
--- /dev/null
+++ b/program/CONTRIBUTORS
@@ -0,0 +1,81 @@
+I would like to thank to following people for helping to
+bring RRDtool into existence.
+
+Alan Lichty <alan_lichty with eli.net>
+Alan Milligan <alan.milligan@last-bastion.net> Python bindings
+Alex van den Bogaerdt <alex with ergens.op.het.net> (rrd_resize.c and more)
+Amos Shapira <amos with gezernet.co.il>
+Andreas Kroomaa <andre with ml.ee>
+Andrew Turner <turner with mint.net> (LAST consolidator)
+Bernard Fischer <bfischer with syslog.ch> 64bit stuff, --alt-autoscale-max
+Bernhard Fischer <rep dot dot dot nop with gmail.com> MMAP rewrite
+Bill Fenner <fenner with research.att.com>
+Blair Zajac <bzajac with geostaff.com>
+Bruce Campbell <bruce.campbell with apnic.net>
+Chin-A-Young <china with thewrittenword.com>
+Christophe VG <Christophe.VanGinneken with ubizen.com>
+Christophe Van Ginneken <Christophe.VanGinneken with ubizen.com> (--no-legend)
+Dan Dunn <dandunn with computer.org>
+Dave Bodenstab <dave@bodenstab.org> AT style time in update, tclfixes
+David Grimes <dgrimes with navisite.com> SQRT/SORT/REV/SHIFT/TREND
+David L. Barker <dave with ncomtech.com> xport function bug fixes
+Evan Miller <emiller with imvu.com> Multiplicative HW Enhancements
+Frank Strauss <strauss with escape.de> TCL bindings
+Florian octo Forster <rrdtool nospam.verplant.org> rrd_restore libxml2 rewrite deprecated function export
+Henrik Storner <henrik with hswn.dk> functions for min/max values of data in graph
+Hermann Hueni <hueni with glue.ch> (SunOS porting)
+Jakob Ilves <jilves with se.oracle.com> HPUX 11
+Jeff R. Allen <jeff.allen with acm.org> (autoconfigure, portability)
+Jeremy Fischer <jeremy with pobox.com> (Makefile changes & RPM builds)
+Jesús Couto Fandiño
+Joel Becker <jlbec with raleigh.ibm.com> AIX
+Joey Miller <joeym with inficad.com>php3 and php4 bindings
+Jost.Krieger <Jost.Krieger with ruhr-uni-bochum.de>
+Kai Siering <kai.siering with mediaways.net>
+Larry Leszczynski <larryl with furph.com>
+Mark Plaksin <happy@usg.edu> rrd_graph_v
+Matt Chambers <matthew.chambers with vanderbilt.edu> --full-size-mode for rrdgraph
+McCreary mccreary with xoanon.colorado.edu
+Mike Mitchell <mcm with unx.sas.com>
+Mike Slifcak <slif with bellsouth.net> many rrdtool-1.1.x fixes
+Oleg Cherevko <olwi with icyb.kiev.ua>
+Otmar Lendl <O.Lendl with Austria.EU.net> (lots of bugfixes)
+Patrick Cherry <patrick with bytemark.co.uk>
+Paul Joslin <Paul.Joslin with sdrc.com>
+Peter Speck <speck with vitality.dk> eps/svg/pdf file format code in rrdtool-1.x
+Peter Stamfest <peter with stamfest.at> initial multi-thread support
+Peter Breitenlohner <peb with mppmu.mpg.de> many patches for rrdtool 1.2.x
+Philippe.Simonet <Philippe.Simonet with swisscom.com> (NT porting)
+Poul-Henning Kamp <phk with freebsd.org> CDEF enhancements
+REIBENSCHUH Alfred <alfred.reibenschuh with it-austria.com> AIX
+Radoslaw Karas <rkaras with tyndall.ie>
+Rainer Bawidamann <Rainer.Bawidamann with informatik.uni-ulm.de>
+Roman Hoogant <rhoogant with ee.ethz.ch>
+Ronan Mullally <ronan in 4L.ie>
+Roger J. Meier <roger.meier in terreactive.ch> (arbitrary linelength in rrdtool)
+Russ Wright <rwwright with home.com>
+Sean Summers <sean with Fenstermaker.com> (RPM .spec)
+Selena M Brewington <smbrewin with ichips.intel.com> add_ds
+Shane O'Donnell <shaneo with opennms.org>
+Simon Leinen <simon with switch.ch>
+Steen Linden <Steen.Linden with ebone.net>
+Stefan Mueller <s.mueller with computer.org> HPUX 11
+Steve Harris <steveh with wesley.com.au> AIX portability
+Steve Rader <rader with teak.wiscnet.net> (rrd_cgi debugging and LAST)
+Terminator rAT <karl_schilke with eli.net>
+Tobias Weingartner <weingart with cs.ualberta.ca>
+Thomas Gutzler <thomas.gutzler with gmail.com> dashed lines
+Tom Crawley <Tom.Crawley with hi.riotinto.com.au> (GCC&HP configuration)
+Travis Brown <tebrown with csh.rit.edu>
+Tuc <ttsg with ttsg.com>
+Ulf Lilleengen <lulf with pvv.ntnu.no> Python binding for 'rrdtool first'
+Ulrich Schilling <schilling with netz.uni-essen.de> AIX
+Wim Heirman <wim.heirman elis.ugent.be> --units=si option
+Wolfgang Schrimm <wschrimm with uni-hd.de> xport function
+Wrolf Courtney <wrolf with wrolf.net> (HP-UX)
+hendrik visage <hvisage with is.co.za>
+Philippe Simonet <philippe.simonet with swisscom.ch> (Windows Binaries)
+Alexander Lucke (lucke with dns-net.de)
+ of DNS:NET Internet Services (www.dns-net.de) http://rrdtool.org
+Hedley Simons <heds@metahusky.net>
+Nicola Worthington <nicolaw@cpan.org>
diff --git a/program/COPYING b/program/COPYING
--- /dev/null
+++ b/program/COPYING
@@ -0,0 +1,280 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 non-commercial 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
diff --git a/program/COPYRIGHT b/program/COPYRIGHT
--- /dev/null
+++ b/program/COPYRIGHT
@@ -0,0 +1,90 @@
+RRDTOOL - Round Robin Database Tool
+A tool for fast logging of numerical data graphical display
+of this data.
+
+Copyright (c) 1998-2008 Tobias Oetiker
+All rights reserved.
+
+GNU GPL License
+===============
+
+This program is free software; 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.,
+59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+
+FLOSS License Exception
+=======================
+(Adapted from http://www.mysql.com/company/legal/licensing/foss-exception.html)
+
+I want specified Free/Libre and Open Source Software ("FLOSS")
+applications to be able to use specified GPL-licensed RRDtool
+libraries (the "Program") despite the fact that not all FLOSS licenses are
+compatible with version 2 of the GNU General Public License (the "GPL").
+
+As a special exception to the terms and conditions of version 2.0 of the GPL:
+
+You are free to distribute a Derivative Work that is formed entirely from
+the Program and one or more works (each, a "FLOSS Work") licensed under one
+or more of the licenses listed below, as long as:
+
+1. You obey the GPL in all respects for the Program and the Derivative
+Work, except for identifiable sections of the Derivative Work which are
+not derived from the Program, and which can reasonably be considered
+independent and separate works in themselves,
+
+2. all identifiable sections of the Derivative Work which are not derived
+from the Program, and which can reasonably be considered independent and
+separate works in themselves,
+
+1. are distributed subject to one of the FLOSS licenses listed
+below, and
+
+2. the object code or executable form of those sections are
+accompanied by the complete corresponding machine-readable source
+code for those sections on the same medium and under the same FLOSS
+license as the corresponding object code or executable forms of
+those sections, and
+
+3. any works which are aggregated with the Program or with a Derivative
+Work on a volume of a storage or distribution medium in accordance with
+the GPL, can reasonably be considered independent and separate works in
+themselves which are not derivatives of either the Program, a Derivative
+Work or a FLOSS Work.
+
+If the above conditions are not met, then the Program may only be copied,
+modified, distributed or used under the terms and conditions of the GPL.
+
+FLOSS License List
+==================
+License name Version(s)/Copyright Date
+Academic Free License 2.0
+Apache Software License 1.0/1.1/2.0
+Apple Public Source License 2.0
+Artistic license From Perl 5.8.0
+BSD license "July 22 1999"
+Common Public License 1.0
+GNU Library or "Lesser" General Public License (LGPL) 2.0/2.1
+IBM Public License, Version 1.0
+Jabber Open Source License 1.0
+MIT License (As listed in file MIT-License.txt) -
+Mozilla Public License (MPL) 1.0/1.1
+Open Software License 2.0
+OpenSSL license (with original SSLeay license) "2003" ("1998")
+PHP License 3.0
+Python license (CNRI Python License) -
+Python Software Foundation License 2.1.1
+Sleepycat License "1999"
+W3C License "2001"
+X11 License "2001"
+Zlib/libpng License -
+Zope Public License 2.0/2.1
diff --git a/program/MakeMakefile b/program/MakeMakefile
--- /dev/null
+++ b/program/MakeMakefile
@@ -0,0 +1,86 @@
+#!/bin/sh
+#
+# Run this script after the first cvs checkout to build
+# makefiles and friends
+
+PATH="/usr/sepp/bin:$PATH"
+export PATH
+
+vcheck (){
+ perl <<PERL
+@t = split /\./, "$1";
+@v = map { int \$_ } split /\./, (split /\s+/, \`$2\`)[3];
+print "$2 = ", (join ".",@v), " (expecting $1 or later)\n";
+\$v = \$t[0]*1000000+\$t[1]*1000+\$t[2] <= \$v[0]*1000000+\$v[1]*1000+\$v[2];
+exit \$v
+PERL
+}
+
+ERROR=0
+
+if vcheck 1.5.14 "libtool --version"
+then
+ echo "get a copy of GNU libtool >= 1.5.14"
+ ERROR=1
+fi
+
+if vcheck 1.9.5 "automake --version"
+then
+ if vcheck 1.9.5 "automake-1.9 --version"
+ then
+ echo "get a copy of GNU automake >= 1.9.5"
+ ERROR=1
+ else
+ automake=automake-1.9
+ aclocal="aclocal-1.9"
+ for d in /usr/pack/automake-1.9.5-to/share/aclocal-1.9 /usr/share/aclocal-1.9 /usr/share/aclocal /usr/pack/rrdtool-1.3svn-to/share/aclocal /usr/pack/intltool-0.37.0-to/share/aclocal ; do
+ [ -d $d ] && aclocal="$aclocal -I $d"
+ done
+ fi
+else
+ automake="automake"
+ aclocal="aclocal"
+# aclocal="aclocal -I /usr/pack/libtool-1.5.14-to/share/aclocal"
+fi
+
+if vcheck 2.59 "autoconf --version"
+then
+ echo "get a copy of GNU autoconf >= 2.59"
+ ERROR=1
+fi
+
+if vcheck 0.35.0 "intltoolize --version"
+then
+ echo "get a copy of GNU intltoolize >= 0.35.0"
+ ERROR=1
+fi
+
+if [ $ERROR -ne 0 ]
+then
+ exit 1
+fi
+
+# remove the bits we are going to recreate
+
+echo Removing old Makefiles
+touch Makefile
+find . -name "Makefile" -or -name "Makefile.in" -print0 | xargs -0 rm
+echo Cleaning out other leftovers
+for x in configure install-sh intltool-merge.in rrd_config.h.in \
+ missing intltool-update po/Makefile.in.in config.sub \
+ intltool-extract intltool-extract.in config.guess \
+ depcomp intltool-update.in autom4te.cache \
+ intltool-merge compile; do
+ [ ! -L $x -a -d $x ] && rm -r "$x"
+ [ ! -L $x -a -f $x ] && rm "$x"
+done
+set -x
+intltoolize --automake -c -f
+$aclocal
+libtoolize --copy --force
+autoheader --force
+$aclocal
+$automake --foreign --add-missing --force-missing --copy
+autoconf --force
+
+# vim: set syntax=sh :
diff --git a/program/Makefile.am b/program/Makefile.am
--- /dev/null
+++ b/program/Makefile.am
@@ -0,0 +1,62 @@
+## Process this file with automake to produce Makefile.in
+RSYNC = rsync --rsh=ssh
+
+# build the following subdirectories
+if BUILD_LIBINTL
+PO=po
+else
+PO=
+endif
+
+SUBDIRS = $(PO) src examples doc bindings
+
+ # the following files are not mentioned in any other Makefile
+EXTRA_DIST = COPYRIGHT CHANGES WIN32-BUILD-TIPS.txt TODO CONTRIBUTORS THREADS \
+ intltool-extract.in intltool-merge.in intltool-update.in \
+ rrdtool.spec favicon.ico win32/config.h win32/rrd.dsp \
+ win32/rrd.vcproj win32/rrdtool.dsp win32/rrdtool.dsw \
+ win32/rrdtool.vcproj win32/Makefile \
+ win32/rrd_config.h.msvc netware/Makefile
+
+
+
+CLEANFILES = config.cache
+
+# use relaxed rules when building dists
+AUTOMAKE_OPTIONS= foreign
+
+# where we keep local rules for automake
+
+ACLOCAL_M4= $(top_srcdir)/aclocal.m4
+#AUTOHEADER = @AUTOHEADER@ --localdir=$(top_srcdir)/config
+#AUTOCONF = @AUTOCONF@ --localdir=$(top_srcdir)/config
+
+to-docs: to-versync
+ (cd doc && $(MAKE) clean && $(MAKE) && $(MAKE) pdf)
+
+to-dist: to-docs dist
+ mv $(PACKAGE)-$(VERSION).tar.gz archive
+
+to-scp: to-dist
+ cp CHANGES archive/$(PACKAGE)-$(VERSION).tar.gz /home/oetiker/public_html/webtools/rrdtool/pub/
+ (cd /home/oetiker/public_html/webtools/rrdtool/pub; rm $(PACKAGE).tar.gz; ln -s $(PACKAGE)-$(VERSION).tar.gz $(PACKAGE).tar.gz)
+
+# $(RSYNC) CHANGES archive/$(PACKAGE)-$(VERSION).tar.gz tobi@ipn.caida.org:/ipn/web/Tools/RRDtool/pub/
+
+site-perl-inst: site-perl-install
+
+site-perl-install: all bindings/perl-piped/Makefile bindings/perl-shared/Makefile
+ cd bindings/perl-piped && $(MAKE) install
+ cd bindings/perl-shared && $(MAKE) install
+
+site-tcl-install: all
+ cd bindings/tcl && $(MAKE) tcl-install
+
+site-python-install: all
+ cd bindings/python && $(PYTHON) setup.py install
+
+# find . -name "*.c" -or -name "*.h" | xargs perl -0777 -n -e 'while (s/typedef\s+(?:unsigned\s+|signed\s+|unival\s+)?\S+\s+\*?([^{}\s;(]+)//){print "-T$1\n"}'
+indent:
+ find ./ -name "*.[ch]" | xargs indent
+
+##END##
diff --git a/program/NEWS b/program/NEWS
--- /dev/null
+++ b/program/NEWS
@@ -0,0 +1,164 @@
+RRDTOOL NEWS
+============
+
+#####################################
+Major Changes between 1.2.x and 1.3.x
+-------------------------------------
+
+NEW Fast file access methods (Bernhard Fischer / Tobi Oetiker)
+----------------------------
+* introduced file-accessor functions rrd_read/rrd_seek/rrd_write
+
+* implemented full mmap-based file access with madvise hints for
+ improved scalability, much reduced memory-footprint and much less
+ blocking while accessing the disk
+
+* implemented optional full file-descriptor access instead of FILE*
+ access
+
+NEW Graphing (Tobi Oetiker)
+------------
+* libart has been replaced by cairo/pango
+
+* pango markup is supported (--pango-markup)
+
+* full grid fitting
+
+* --graph-render-mode=mono for non anti aliased graphing
+
+* --font-render-mode=mono for non anti aliased fonts
+
+* fonts come through fontconfig, use the Pango font nameing scheme
+ -> 'Times 20' ... it is not possible to use truetype fonts
+ directly anymore.
+
+* Tabs are position independent.
+
+* TRENDNAN filter that ignores NAN values while calculating the
+ TREND data. (Timo Stripf)
+
+* --full-size-mode to specify the outer border of the image and not
+ just of the graphing canvas (Matthew Chambers)
+
+* TEXTALIGN command to alter default text alignment behavior
+
+* C API in-memory graphing with rrd_graph_v (Evan Miller)
+
+* draw dashed lines in graphs (Thomas Gutzler)
+
+* new interface graphv which returns information using the rrd_info
+ interface (Tobi Oetiker and Mark Plaksin)
+
+* improved horizontal grid. Have a bit more grid lines and y-axis
+ labels while keeping them far enough apart to not run into each
+ other.
+
+NEW Forecasting (Evan Miller)
+---------------
+* the new MHWPREDICT consolidation function uses a variation of the
+ Holt-Winters method. It is a drop-in replacement for HWPREDICT,
+ and is better suited for data whose seasonal variations grow or
+ shrink in proportion to the average.
+
+* If you create an RRD with the new MHWPREDICT function, the
+ resulting rrd file will be version 0004 and can only be used in
+ rrdtool 1.3.
+
+Rewrites
+--------
+* rrd_restore now uses libxml for parsing which makes things much
+ more tolerant towards xml variations. The old code could mostly
+ just parse the XML as it was output by rrdtool dump. See also:
+ the note at the bottom of this document. (by Florian octo
+ Forster)
+
+* rrd_update rewritten to make it more modular. Fixed two
+ longstanding HW bugs in the process (Evan Miller)
+
+Internationalization (Takao Fujiwara and Tobi Oetiker)
+--------------------
+* The help output by rrdtool has been internationalized. There are
+ no real translations included with rrdtool yet, contributions are
+ welcome.
+
+* The internationalization will only be compiled if libintl and
+ friends are available on your system. Use the configure option
+ --disable-libintl if you want to disable this feature
+
+Language Bindings
+-----------------
+* ruby rrd_fetch will return step as a last property -- Mike Perham
+
+RRDtool dump / restore incompatibility
+--------------------------------------
+* rrdtool dump 1.3 does emit completely legal XML. Basically this
+ means that it contains an XML header and a DOCTYPE definition.
+ Unfortunately this causes older versions of rrdtool restore to be
+ unhappy.
+
+* To restore a new dump with an old rrdtool restore version, either
+ remove the XML header and the doctype by hand (both on the first
+ line of the dump) or use rrdtool dump --no-header.
+
+
+######################################################################################
+Major Changes between 1.0.x and 1.2.x
+======================================================================================
+Graphing
+--------
+
+* rewritten graphics generation based on libart.
+ - anti-aliased output
+ - alpha transparency support
+ - truetype fonts
+
+* additional graphics formats: EPS, PDF, SVG
+
+* extended multi-part documentation
+
+* VDEF support; define and use variables. Find, and use, the
+ maximum rate seen by rrdtool; compute and show the average
+
+* Sliding window (trend) analysis
+ Compute a smoother average, for instance over the last 6 CDPs
+
+* percentile (95th or other)
+ Remove peaks, 95 percent of all rates are at or below the
+ returned value
+
+Logging
+-------
+* a second logging interface: rrdtool updatev
+ Verbose updating of the database; show CPDs being created
+
+* Aberrant Behavior Detection with Holt-Winters Forecasting
+ Compare current data with expected data, detect and log when
+ the rates are outside expected levels
+
+* COMPUTE data type for artificial data-sources calculating their
+ input using RPN math and data from the other data-sources.
+
+Incompatibilities
+-----------------
+* Colons in COMMENT arguments to rrdtool graph must be escaped with a backslash
+
+* the --alt-y-mrtg option is gone or rather since 1.2.7 it is back but
+ without functionality.
+
+* In pipe mode, rrdtool answers with OK only if it was successful with the
+ command. Otherwhise the answer will be ERROR...
+
+
+Behind the Scenes
+-----------------
+* In order to support Holt-Winters and Calculated Datasources,
+ the rrdtool data format has changed. While the new version of rrdtool can
+ read files created with rrdtool 1.0.x. It is not possible to read files
+ created by rrdtool-1.2.x with rrdtool-1.0.x
+
+* External libraries are not included with rrdtool anymore. This is in line
+ with todays trend of using shared libraries everywhere. With the exception
+ of the cgi library most things required by rrdtool will be found on every recent
+ system.
+
+* Memory Mapped IO support for faster logging.
diff --git a/program/README b/program/README
--- /dev/null
+++ b/program/README
@@ -0,0 +1,75 @@
+Round Robin Database Tools
+==========================
+
+It is pretty easy to gather status information from all sorts of things,
+ranging from the temperature in your office to the number of octets which
+have passed through the FDDI interface of your router. But it is not so
+trivial to store this data in a efficient and systematic manner. This is
+where RRDtool kicks in. It lets you log and analyze the data you gather from
+all kinds of data-sources (DS). The data analysis part of RRDtool is based
+on the ability to quickly generate graphical representations of the data
+values collected over a definable time period.
+
+
+To compile:
+-----------
+
+check out the instructions in doc/rrdbuild.txt
+
+Getting Started:
+----------------
+
+Either after compiling or after installing you can try the example
+RRDtool applications in the examples directory.
+
+To learn:
+---------
+
+Read the documentation in the doc directory. Start of with
+RRDtool. All documents are available as html and as ASCII text.
+
+If you are looking for a more slow paced introduction, make sure to read
+Alex van den Bogaerdt's rrdtutorial which is also available from the doc
+directory. Also read his cdeftutorial and Steve Rader's rpntutorial.
+
+If you want to know about the format of the log files check
+src/rrd_format.h there are a lot of comments in there ...
+
+How to make Tobi happy:
+-----------------------
+
+If you want to show your appreciation for RRDtool you could make me happy
+by going to http://tobi.oetiker.ch/wish and ordering a CD from
+my CD wish list ...
+
+
+How to keep in touch:
+---------------------
+
+There are 3 Mailing lists for RRDtool:
+
+rrd-announce LOW volume RRDtool Announcements List (Only Stable Releases)
+rrd-users For discussion amongst people who use RRDtool in their applications
+rrd-developers For people who actually HACK RRDtool code
+
+To subscribe to <MAILGLIST> send a message with the subject 'subscribe'
+to <MAILGLIST>-request@lists.oetiker.ch
+
+Note, that postings to rrd-announce will always be cross-posted
+to rrd-users and rrd-developers as well.
+
+To Contribute:
+--------------
+
+Contributed feature and bug patches are most welcome. But please
+send complete patches. A complete patch patches the CODE as well
+as the CHANGES, CONTRIBUTORS and the POD files.
+
+Use GNU diff --unified --recursive olddir newdir to build your patches.
+
+The latest Version:
+-------------------
+Is available from http://oss.oetiker.ch/rrdtool/
+
+
+Tobias Oetiker <tobi@oetiker.ch>
diff --git a/program/THREADS b/program/THREADS
--- /dev/null
+++ b/program/THREADS
@@ -0,0 +1,60 @@
+In order to use the librrd in multi-threaded programs you must:
+
+ * Link with librrd_th instead of with librrd
+ * Use the *_r function instead or the *-functions
+ * Never use non *_r functions unless it is explicitly documented that the
+ function is tread-safe
+
+Every thread SHOULD call rrd_get_context() before the first call to
+any librrd function in order to set up thread specific data. This is
+not strictly required, but it is the only way to test if memory
+allocation can be done by this function. Otherwise the program may die
+with a SIGSEGV in a low-memory situation.
+
+
+IMPORTANT NOTE FOR RRD CONTRIBUTORS:
+
+Some precautions must be followed when developing rrd from now on:
+
+* Only use thread-safe functions in library code. Many often used libc
+ functions aren't thread-safe. Take care if you want to use any of
+ the following functions:
+
+ + direct strerror calls must be avoided: use rrd_strerror instead,
+ it provides a per-thread error message
+ + the getpw*, getgr*, gethost* function families (and some more get*
+ functions): use the *_r variants
+ + Time functions: asctime, ctime, gmtime, localtime: use *_r variants
+ + strtok: use strtok_r
+ + tmpnam: use tmpnam_r
+ + many other (lookup documentation)
+
+As an aide(?) a header file named "rrd_is_thread_safe.h" is provided
+that works with the GNU C-preprocessor to "poison" some of the most
+common non-thread-safe functions using the "#pragma GCC poison"
+directive. Just include this header in source files you want to keep
+thread-safe.
+
+* Do not introduce global variables!
+
+ If you really, really have to use a global variable you may add a
+ new field to the rrd_context structure and modify rrd_error.c,
+ rrd_thread_safe.c and rrd_non_thread_safe.c
+
+* Do not use "getopt" or "getopt_long" in *_r (directly or indirectly)
+
+ getopt uses global variables and behaves badly in a multithreaded
+ application when called concurrently. Instead provide a *_r function
+ taking all options as function parameters. You may provide argc and
+ **argv arguments for variable length argument lists. See
+ rrd_update_r as an example.
+
+* Do not use the parsetime function!
+
+ It uses lots of global vars. You may use it in functions not
+ designed to be thread-safe like functions wrapping the _r version of some
+ operation (eg. rrd_create, but not in rrd_create_r)
+
+WIN32 Platform Note (added 04/01/03):
+
+Both rrdtool.vcproj (MSVC++ 7.0) and rrd.dsw (MSVC++ 6.0) are configured to compile with rrd_thread_safe_nt.c.
diff --git a/program/TODO b/program/TODO
--- /dev/null
+++ b/program/TODO
@@ -0,0 +1,42 @@
+Random Feature Ideas for RRDtool
+--------------------------------
+Updated: 2008-06-26
+
+add accessor functions for rrd manipulation
+
+reverse order of stacked graph entries prior to plotting ... this is to make
+plotting order more naturally fit with the ordering of the legend ...
+
+make it possible to define order of legend items independant of their order
+on the commandline ...
+
+architecture independant storage format.
+
+micro second precision
+
+2036 stable time
+
+add axis on the right and on top of the graph ...
+
+add configurable counter wrap
+
+build derivatives while plotting
+
+show trend by building moving average and represent it as an arrow
+
+offer side by side tiny box plots for each succesive hour, or some other
+means of seeing the variation rather than just the mean level
+
+circular periodic plots ... (polar)
+
+analyse data in non time domain ...
+
+add smothing functions to grapher, using rolling average ...
+
+make VRULE and HRULE with 123 versions ...
+
+have configurable role-over limits for counters
+
+align data points not to GMT but some free offset
+
+allow starting through symlinks called rrdcreate rrdtune and the like
diff --git a/program/WIN32-BUILD-TIPS.txt b/program/WIN32-BUILD-TIPS.txt
--- /dev/null
@@ -0,0 +1,335 @@
+Compiling RRDtool 1.1.x on Win32 with Microsoft Visual C++:
+---------------------------------------------------------------
+5/1/05 Tobi
+to help windows deal with the reentrant versions of many unix
+calls link with win32comp.c
+
+4/10/05 Tobi
+The windows implementation of strftime does not seem to support
+the ISO 8601 week number (%V) I have therfore included the file
+strftime.[ch] which provides strftime_ ... if you compile rrdtool
+with -Dstrftime=_strftime and link strftime.o then you will
+get propper support for %V.
+
+7/29/04 Jake Brutlag
+
+As of Jan 2004, code for libraries utilized by rrdtool
+(png, libart, freetype, and zlib) is no longer distributed with
+rrdtool. This requires some changes to the compile process on
+Win32. The solution described here is to compile rrdtool to
+link against these libraries dynamically. There is an advantage
+to this approach: namely the rrdtool distribution doesn't have to
+worry about how to compile these libraries on Win32. In theory,
+since others already provide and maintain Win32 binaries for these
+libraries the users don't have to worry about how to compile them
+either. The disadvantage of this approach is that the DLLs for
+these libraries must be available on the hosts where rrdtool will run.
+
+Here are step by step instructions for compiling rrdtool.exe and
+the perl shared library (RRDS.dll) with Microsoft Visual C++ 6.0.
+(1) Download libraries rrdtool depends on from GnuWin32:
+http://gnuwin32.sourceforge.net/
+For freetype, libpng, and zlib download the "Complete Package"; each of
+these will be a self-extracting self-installing executable.
+For libart, download both the "Binaries" and "Developer Files" packages.
+Unfortunately at this time GnuWin32 doesn't provide the "Complete Package"
+installer for libart. Perhaps by the time you are following these
+instructions GnuWin32 will have a "Complete Package" for libart.
+(2) Install the GnuWin32 libraries by running the executables for freetype,
+libpng, and zlib. These instructions and the Visual C++ project files
+distributed with rrdtool assume that you will use the default install
+location: C:\Program Files\GnuWin32. Extract the two zip files for libart,
+libart-2.3.3-bin.zip and libart-2.3.3-1-lib.zip into the GnuWin32 directory;
+the appropriate libart files will be added to the include, lib, and bin
+subdirectories.
+(3) Add C:\Program Files\GnuWin32\bin to the PATH (Control Panel ->
+System -> Advanced -> Environment Variables).
+(4) Start Microsoft Visual C++ 6.0. Load the workspace file, rrdtool.dsw,
+from the src subdirectory of your rrdtool code directory.
+(5) Compile the Release build of the rrdtool project (since rrdtool depends
+on the rrd project, the rrd library will also be compiled). At this
+time, the compile will fail in zconf.h, a zlib header file. The problem
+is a preprocessor directive that loads unistd.h. Open zconf.h in VC++
+(this file is in C:\Program Files\GnuWin32\include) and find the following
+code block:
+
+#if 1 /* HAVE_UNISTD_H -- this line is updated by ./configure */
+# include <sys/types.h> /* for off_t */
+# include <unistd.h> /* for SEEK_* and off_t */
+# ifdef VMS
+# include <unixio.h> /* for off_t */
+# endif
+# define z_off_t off_t
+#endif
+
+Change it to reads as follows (this is code from zlib-1.1.4):
+
+#if HAVE_UNISTD_H
+# include <sys/types.h> /* for off_t */
+# include <unistd.h> /* for SEEK_* and off_t */
+# ifdef VMS
+# include <unixio.h> /* for off_t */
+# endif
+# define z_off_t off_t
+#endif
+
+Note that it is actually just a one line change. Save the file and
+recompile rrdtool. By the time you are following these instructions
+this issue with zconf.h may be resolved.
+(6) At this point, you can run the executable rrdtool.exe in the
+src\toolrelease subdirectory. Note that if you wish to run rrdtool
+on other machines, you will need the following DLLs installed (on the
+path) on those machines:
+zlib1.dll
+libpng12.dll
+libart_lgpl.dll
+freetype6.dll
+msvcrt.dll
+The names of the first four DLLs might vary from what is listed here
+depending on the versions of the packages you downloaded from GnuWin32.
+The fifth DLL, msvcrt.dll, is a system DLL for most versions of Windows.
+If you are running on old version of Windows, you can install/upgrade to
+IE4.0 to get this DLL.
+(7) To compile the perl-shared library, open a Command Prompt (DOS box)
+and cd to the bindings\perl-shared subdirectory.
+(8) Run vcvars32.bat; this batch file, in your vc98\bin directory will
+set necessary environment options for command line compiling.
+(9) In bindings\perl-shared, run
+perl ntmake.pl
+nmake
+nmake test
+If nmake test succeeds, you are good to go. RRDs.dll is in
+blib\arch\auto\RRDs. If you plan to install via the Active State ppm
+tool, tar and gzip the blib directory. You can use the RRDs.ppd file
+in bindings\perl-shared directory. Remember that as in the case of
+rrdtool.exe you will need the DLLs listed in (6) on the machine where
+you are going to use RRDs.dll.
+
+Microsoft Visual C++ 7.1 (.NET 2003):
+
+Unfortunately, this is more difficult than with VC++ 6.0. The problem
+is that by default the C runtime dll for VC++ 7.1 is msvcr71.dll rather
+than msvcrt.dll. The GnuWin32 library binaries are all compiled
+to use msvcrt.dll and you can't mix msvcr71.dll and msvcrt.dll in the
+same process. One option is to download the source code for the libraries
+(available from http://gnuwin32.sourceforge.net) recompile them with
+VC++ 7.l. Then all the components will use msvcr71.dll. Once you are
+going to go this route, you can also use static multi-threaded libraries
+and use static linking between rrdtool (or RRDs.dll) and its dependencies.
+
+To use the GnuWin32 library binaries, you need to trick VC++ 7.1 into
+compiling rrdtool to use the older msvcrt.dll. Follow steps (1) - (3)
+as above, then:
+(4) Obtain a different version of the msvcrt.lib import library that
+is compatible with vc7 and points to msvcrt.dll:
+msvcrtlib_for_vc7.zip from http://xchat.org/win32/testing
+Backup msvcrt.lib in your vc7\lib directory
+(\Program Files\Microsoft Visual Studio .NET 2003\vc7\lib)
+Then extract the msvcrt.lib from the zip file into the vc7\lib directory.
+WARNING: Use this msvcrt.lib at your own risk! This is not a Microsoft
+supplied file nor a file supported by anyone associated with rrdtool.
+(5) Start Microsoft Visual C++ 7.1. Load the solution file, rrdtool.sln,
+from the src subdirectory of your rrdtool code directory. Edit zconf.h,
+as needed, as described under (5) above. Compile the release build of
+the rrdtool project.
+Proceed with steps (6) - (9) as above, if you are using/picking up
+the wrong msvcrt.lib import library then nmake test for perl-shared
+will fail.
+
+Note: it is possible in the future that GnuWin32 will provide Win32
+binaries that utilize msvcr71.dll rather than msvcrt.dll.
+
+5/14/02 Jake Brutlag
+
+These notes share some insight I gained compiling 1.1.x with
+MS Visual C++ 6.0 (using project files). This information may or
+may not be accurate at the time you are reading this.
+
+(1) freetype and rrdtool cannot use precompiled headers (which are
+enabled by default for MSVC++ projects). MSVC++ 6.0 does not
+support precompiled headers if #include directives contain MACROS.
+(2) Compile Release build with Default optimization, not the
+Maximize Speed optimization. I encountered some strange errors
+(related to argument processing for complex commands like graph--
+perhaps the getopt stuff is too blame) with Maximize Speed.
+(3) libart relies upon config.h (ostensibly generated by the
+configure script-- but of course not on Win32 platforms). ..\..\confignt
+(which contains a static Win32 version of config.h) should be on
+the include path.
+(4) Fonts are located in the %windir%\fonts, so the default font
+is c:\winnt\fonts\cour.ttf. (6/19/02) At Kerry Calvert's suggestion
+this setting was moved to confignt\config.h.
+(5) libart requires a custom build step to generate art_config.h; this
+is done manually via the commands:
+cl -I..\..\confignt gen_art_config.c
+gen_art_config.exe > art_config.h
+
+Currently, to compile rrd.lib and rrdtool.exe using
+the MSVC++ project files, first start MSVC++ 6.0. Open the rrdtool
+workspace (rrdtool.dsw in the src directory). The active project/
+configuration should be rrdtool-Win32 Release. Select Rebuild All
+from the Build menu. The static link library (rrd.lib) will
+be generated in src\release directory and executable will be generated
+in the src\toolrelease directory.
+
+Compiling RRDtool on NT ... work in progress
+---------------------------------------------------------------
+ by Tamas Kovacshazy (khazy@mit.bme.hu)
+
+Persisting Problems with the current NT port:
+
+Unfortunately, the RRD perl modules does not work with Perl
+(ActivePerl) using the current distribution.
+
+The RRD shared perl module can be compiled after some
+modification...
+
+Follow these steps:
+
+0. Install perl if you do not have it!
+ Visit http://www.ActiveState.com/pw32/ for a complete distribution.
+
+1. Copy ..\gd1.2\release\gd.lib to ..\gd1.2\
+2. Copy ..\src\release\rrd.lib to ..\src
+3. perl Makefile.pl
+
+In this step the system complains about something I do not
+understand. The error message is the following:
+
+Note (probably harmless): No library found for '-lm'
+
+Is a library missing? But it does not stop with an error...
+
+4. nmake test (You must have Visual C++ on the machine!)
+
+After these steps it generates the test files (svgs and rrds),
+and they seem to be good.
+
+The real problem in the shared perl modul is the following:
+
+I do not know how this installation stuff works. The problem is
+that the installation stuff looks for the gd.lib and the
+rrd.lib in the ..\gd1.2 and ..\src directory. The UNIX compile
+puts the files into these directories, but the NT compile does
+not.
+
+It is all for today,
+
+khazy
+
+Tamas Kovacshazy E-mail: khazy@mit.bme.hu
+WWW: http://www.mit.bme.hu/~khazy
+Technical University of Budapest
+Department of Measurement and Information Systems
+
+
+Compiling RRDtool 1.2.x on Win32 with MingW32 gcc:
+---------------------------------------------------------------
+
+1. Obtain and install the current version of the MingW package.
+
+ http://www.mingw.org/download.shtml
+
+ In the MinGW set you will need the gcc and binutils as a minimum.
+
+2. Obtain either of the following awk versions and install in a directory
+ on your System Path:
+
+ - awk.exe
+
+ http://cm.bell-labs.com/cm/cs/awkbook/index.html
+
+ Note: This version has no dependencies to other libs.
+
+ - gawk.exe (GnuWin32 version)
+
+ http://gnuwin32.sourceforge.net/packages/gawk.htm
+
+ Note: Also fetch the dependant libraries for it from the same page.
+
+3. If you plan to create a 'distribution' release of the RRD Tools, the
+ Makefile.Win32 will copy all the needed files to an output directory and
+ then zip the entire directory. A suitable zip utility can be obtained here:
+
+ http://www.info-zip.org/
+
+ Install in a directory on your System Path.
+
+4. Obtain the following libraries, ideally install them all under a common
+ directory:
+
+ = zlib
+
+ http://oss.oetiker.ch/rrdtool/pub/libs/zlib-1.2.3.tar.gz
+ http://www.zlib.net/
+
+ = libpng
+
+ http://oss.oetiker.ch/rrdtool/pub/libs/libpng-1.2.12.tar.gz
+ http://libpng.sourceforge.net/
+
+ = freetype
+
+ http://oss.oetiker.ch/rrdtool/pub/libs/freetype-2.2.1.tar.gz
+ http://freetype.sourceforge.net/index2.html
+
+ = libart_lgpl
+
+ http://oss.oetiker.ch/rrdtool/pub/libs/libart_lgpl-2.3.17.tar.gz
+ http://www.levien.com/libart/
+
+ Note: libart_lgpl needs a special tweak because the archive contains
+ only the base directory, but the libart headers are usually included with
+ a directory prefix; therefore create a subfolder 'libart_lgpl' and move
+ all files into this subfolder.
+
+5. Set up for DOS environment.
+
+ Add MingW\bin and MSYS\bin directories to your System path.
+
+ If the libraries share a common directory set the following environment var:
+
+ set LIBBASE=<shared director>
+ e.g set LIBBASE=C:\Libraries
+
+ If the libraries are scattered, set the following environment vers:
+
+ set ZLIBSDK=<path to zlib>
+ e.g set ZLIBSDK=C:\mytest\zlib-1.2.3
+ set LIBPNG=<path to libpng>
+ set LIBFT2=<path to freetype>
+ set LIBART=<path to libart>
+
+ If using the Gnu Awk (gawk.exe), edit the Makefile.Win32 and change the line:
+
+ AWK = awk
+
+ to
+
+ AWK = gawk
+
+6. Compile the project.
+
+ All dependent libs are statically linked in. This has the benefit that the
+ binaries do not depend on any other DLLs.
+ In order to build the static freetype lib enter the freetype base directory
+ and type 'make'. If everything is fine a message appears that gcc is detected,
+ and that you should again type 'make'. Follow that in order to build freetype.
+ All other libs are build from the sources with the RRDTool Makefile.Win32.
+
+ Switch to the RRDTOOL .\src directory. Then:
+
+ make -f Makefile.Win32 help
+
+ to see the build options, or
+
+ make -f Makefile.Win32 all
+
+ should build the entire package.
+
+6. Happy Graphing!
+
+
+written by normw & gk.
+
+
diff --git a/program/acinclude.m4 b/program/acinclude.m4
--- /dev/null
+++ b/program/acinclude.m4
@@ -0,0 +1,548 @@
+dnl Helper Functions for the RRDtool configure.ac script
+dnl
+dnl this file gets included into aclocal.m4 when runnning aclocal
+dnl
+dnl
+dnl
+dnl Check for the presence of a particular library and its header files
+dnl if this check fails set the environment variable EX_CHECK_ALL_ERR to YES
+dnl and prints out a helful message
+dnl
+dnl
+dnl EX_CHECK_ALL(library, function, header, pkgconf name, tested-version, homepage, cppflags)
+dnl $1 $2 $3 $4 $5 $6 $7
+dnl
+dnl
+AC_DEFUN([EX_CHECK_ALL],
+[
+ AC_LANG_PUSH(C)
+ EX_CHECK_STATE=NO
+ ex_check_save_LIBS=${LIBS}
+ ex_check_save_CPPFLAGS=${CPPFLAGS}
+ ex_check_save_LDFLAGS=${LDFLAGS}
+ if test "x$7" != "x"; then
+ CPPFLAGS="$CPPFLAGS -I$7"
+ fi
+ dnl try compiling naked first
+ AC_CHECK_LIB($1,$2, [
+ AC_CHECK_HEADER($3,[LIBS="-l$1 ${LIBS}";EX_CHECK_STATE=YES],[])],[])
+ if test $EX_CHECK_STATE = NO; then
+ dnl now asking pkg-config for help
+ AC_CHECK_PROGS(PKGCONFIG,[pkg-config],no)
+ if test "$PKGCONFIG" != "no"; then
+ if $PKGCONFIG --exists $4; then
+ CPPFLAGS=${CPPFLAGS}" "`$PKGCONFIG --cflags $4`
+ LDFLAGS=${LDFLAGS}" "`$PKGCONFIG --libs-only-L $4`
+ LDFLAGS=${LDFLAGS}" "`$PKGCONFIG --libs-only-other $4`
+ LIBS=${LIBS}" "`$PKGCONFIG --libs-only-l $4`
+ dnl remove the cached value and test again
+ unset ac_cv_lib_`echo $1 | sed ['s/[^_a-zA-Z0-9]/_/g;s/^[0-9]/_/']`_$2
+ AC_CHECK_LIB($1,$2,[
+ unset ac_cv_header_`echo $3 | sed ['s/[^_a-zA-Z0-9]/_/g;s/^[0-9]/_/']`
+ AC_CHECK_HEADER($3,[EX_CHECK_STATE=YES],[])
+ ],[])
+ else
+ AC_MSG_WARN([
+----------------------------------------------------------------------------
+* I found a copy of pkgconfig, but there is no $4.pc file around.
+ You may want to set the PKG_CONFIG_PATH variable to point to its
+ location.
+----------------------------------------------------------------------------
+ ])
+ fi
+ fi
+ fi
+
+ if test ${EX_CHECK_STATE} = NO; then
+ AC_MSG_WARN([
+----------------------------------------------------------------------------
+* I could not find a working copy of $4. Check config.log for hints on why
+ this is the case. Maybe you need to set LDFLAGS and CPPFLAGS appropriately
+ so that compiler and the linker can find lib$1 and its header files. If
+ you have not installed $4, you can get it either from its original home on
+
+ $6
+
+ You can find also find an archive copy on
+
+ http://oss.oetiker.ch/rrdtool/pub/libs
+
+ The last tested version of $4 is $5.
+
+ LIBS=$LIBS
+ LDFLAGS=$LDFLAGS
+ CPPFLAGS=$CPPFLAGS
+
+----------------------------------------------------------------------------
+ ])
+ EX_CHECK_ALL_ERR=YES
+ LIBS="${ex_check_save_LIBS}"
+ CPPFLAGS="${ex_check_save_CPPFLAGS}"
+ LDFLAGS="${ex_check_save_LDFLAGS}"
+ fi
+ AC_LANG_POP(C)
+]
+)
+
+dnl
+dnl Ptherad check from http://autoconf-archive.cryp.to/acx_pthread.m4
+dnl
+dnl @synopsis ACX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]])
+dnl
+dnl This macro figures out how to build C programs using POSIX threads.
+dnl It sets the PTHREAD_LIBS output variable to the threads library and
+dnl linker flags, and the PTHREAD_CFLAGS output variable to any special
+dnl C compiler flags that are needed. (The user can also force certain
+dnl compiler flags/libs to be tested by setting these environment
+dnl variables.)
+dnl
+dnl Also sets PTHREAD_CC to any special C compiler that is needed for
+dnl multi-threaded programs (defaults to the value of CC otherwise).
+dnl (This is necessary on AIX to use the special cc_r compiler alias.)
+dnl
+dnl NOTE: You are assumed to not only compile your program with these
+dnl flags, but also link it with them as well. e.g. you should link
+dnl with $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS
+dnl $LIBS
+dnl
+dnl If you are only building threads programs, you may wish to use
+dnl these variables in your default LIBS, CFLAGS, and CC:
+dnl
+dnl LIBS="$PTHREAD_LIBS $LIBS"
+dnl CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+dnl CC="$PTHREAD_CC"
+dnl
+dnl In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute
+dnl constant has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to
+dnl that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX).
+dnl
+dnl ACTION-IF-FOUND is a list of shell commands to run if a threads
+dnl library is found, and ACTION-IF-NOT-FOUND is a list of commands to
+dnl run it if it is not found. If ACTION-IF-FOUND is not specified, the
+dnl default action will define HAVE_PTHREAD.
+dnl
+dnl Please let the authors know if this macro fails on any platform, or
+dnl if you have any other suggestions or comments. This macro was based
+dnl on work by SGJ on autoconf scripts for FFTW (www.fftw.org) (with
+dnl help from M. Frigo), as well as ac_pthread and hb_pthread macros
+dnl posted by Alejandro Forero Cuervo to the autoconf macro repository.
+dnl We are also grateful for the helpful feedback of numerous users.
+dnl
+dnl @category InstalledPackages
+dnl @author Steven G. Johnson <stevenj@alum.mit.edu>
+dnl @version 2005-01-14
+dnl @license GPLWithACException
+
+AC_DEFUN([ACX_PTHREAD], [
+AC_REQUIRE([AC_CANONICAL_HOST])
+AC_LANG_PUSH(C)
+acx_pthread_ok=no
+
+# We used to check for pthread.h first, but this fails if pthread.h
+# requires special compiler flags (e.g. on True64 or Sequent).
+# It gets checked for in the link test anyway.
+
+# First of all, check if the user has set any of the PTHREAD_LIBS,
+# etcetera environment variables, and if threads linking works using
+# them:
+if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then
+ save_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+ save_LIBS="$LIBS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+ AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS])
+ AC_TRY_LINK_FUNC(pthread_join, acx_pthread_ok=yes)
+ AC_MSG_RESULT($acx_pthread_ok)
+ if test x"$acx_pthread_ok" = xno; then
+ PTHREAD_LIBS=""
+ PTHREAD_CFLAGS=""
+ fi
+ LIBS="$save_LIBS"
+ CFLAGS="$save_CFLAGS"
+fi
+
+# We must check for the threads library under a number of different
+# names; the ordering is very important because some systems
+# (e.g. DEC) have both -lpthread and -lpthreads, where one of the
+# libraries is broken (non-POSIX).
+
+# Create a list of thread flags to try. Items starting with a "-" are
+# C compiler flags, and other items are library names, except for "none"
+# which indicates that we try without any flags at all, and "pthread-config"
+# which is a program returning the flags for the Pth emulation library.
+
+acx_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config"
+
+# The ordering *is* (sometimes) important. Some notes on the
+# individual items follow:
+
+# pthreads: AIX (must check this before -lpthread)
+# none: in case threads are in libc; should be tried before -Kthread and
+# other compiler flags to prevent continual compiler warnings
+# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h)
+# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able)
+# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread)
+# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads)
+# -pthreads: Solaris/gcc
+# -mthreads: Mingw32/gcc, Lynx/gcc
+# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it
+# doesn't hurt to check since this sometimes defines pthreads too;
+# also defines -D_REENTRANT)
+# pthread: Linux, etcetera
+# --thread-safe: KAI C++
+# pthread-config: use pthread-config program (for GNU Pth library)
+
+case "${host_cpu}-${host_os}" in
+ *solaris*)
+
+ # On Solaris (at least, for some versions), libc contains stubbed
+ # (non-functional) versions of the pthreads routines, so link-based
+ # tests will erroneously succeed. (We need to link with -pthread or
+ # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather
+ # a function called by this macro, so we could check for that, but
+ # who knows whether they'll stub that too in a future libc.) So,
+ # we'll just look for -pthreads and -lpthread first:
+
+ acx_pthread_flags="-pthread -pthreads pthread -mt $acx_pthread_flags"
+ ;;
+esac
+
+if test x"$acx_pthread_ok" = xno; then
+for flag in $acx_pthread_flags; do
+
+ case $flag in
+ none)
+ AC_MSG_CHECKING([whether pthreads work without any flags])
+ ;;
+
+ -*)
+ AC_MSG_CHECKING([whether pthreads work with $flag])
+ PTHREAD_CFLAGS="$flag"
+ ;;
+
+ pthread-config)
+ AC_CHECK_PROG(acx_pthread_config, pthread-config, yes, no)
+ if test x"$acx_pthread_config" = xno; then continue; fi
+ PTHREAD_CFLAGS="`pthread-config --cflags`"
+ PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`"
+ ;;
+
+ *)
+ AC_MSG_CHECKING([for the pthreads library -l$flag])
+ PTHREAD_LIBS="-l$flag"
+ ;;
+ esac
+
+ save_LIBS="$LIBS"
+ save_CFLAGS="$CFLAGS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+
+ # Check for various functions. We must include pthread.h,
+ # since some functions may be macros. (On the Sequent, we
+ # need a special flag -Kthread to make this header compile.)
+ # We check for pthread_join because it is in -lpthread on IRIX
+ # while pthread_create is in libc. We check for pthread_attr_init
+ # due to DEC craziness with -lpthreads. We check for
+ # pthread_cleanup_push because it is one of the few pthread
+ # functions on Solaris that doesn't have a non-functional libc stub.
+ # We try pthread_create on general principles.
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <pthread.h>]], [[pthread_t th; pthread_join(th, 0);
+ pthread_attr_init(0); pthread_cleanup_push(0, 0);
+ pthread_create(0,0,0,0); pthread_cleanup_pop(0); ]])],[acx_pthread_ok=yes],[])
+
+ LIBS="$save_LIBS"
+ CFLAGS="$save_CFLAGS"
+
+ AC_MSG_RESULT($acx_pthread_ok)
+ if test "x$acx_pthread_ok" = xyes; then
+ break;
+ fi
+
+ PTHREAD_LIBS=""
+ PTHREAD_CFLAGS=""
+done
+fi
+
+# Various other checks:
+if test "x$acx_pthread_ok" = xyes; then
+ save_LIBS="$LIBS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+ save_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+
+ # Detect AIX lossage: JOINABLE attribute is called UNDETACHED.
+ AC_MSG_CHECKING([for joinable pthread attribute])
+ attr_name=unknown
+ for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <pthread.h>]], [[int attr=$attr;]])],[attr_name=$attr; break],[])
+ done
+ AC_MSG_RESULT($attr_name)
+ if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then
+ AC_DEFINE_UNQUOTED(PTHREAD_CREATE_JOINABLE, $attr_name,
+ [Define to necessary symbol if this constant
+ uses a non-standard name on your system.])
+ fi
+
+ AC_MSG_CHECKING([if more special flags are required for pthreads])
+ x_rflag=no
+ case "${host_cpu}-${host_os}" in
+ *-aix* | *-freebsd* | *-darwin*) x_rflag="-D_THREAD_SAFE";;
+ *solaris* | *-osf* | *-hpux*) x_rflag="-D_REENTRANT";;
+ *-linux*)
+ if test x"$PTHREAD_CFLAGS" = "x-pthread"; then
+ # For Linux/gcc "-pthread" implies "-lpthread". We need, however, to make this explicit
+ # in PTHREAD_LIBS such that a shared library to be built properly depends on libpthread.
+ PTHREAD_LIBS="-lpthread $PTHREAD_LIBS"
+ fi;;
+ esac
+ AC_MSG_RESULT(${x_rflag})
+ if test "x$x_rflag" != xno; then
+ PTHREAD_CFLAGS="$x_rflag $PTHREAD_CFLAGS"
+ fi
+
+ LIBS="$save_LIBS"
+ CFLAGS="$save_CFLAGS"
+
+ # More AIX lossage: must compile with cc_r
+ AC_CHECK_PROG(PTHREAD_CC, cc_r, cc_r, ${CC})
+else
+ PTHREAD_CC="$CC"
+fi
+
+AC_SUBST(PTHREAD_LIBS)
+AC_SUBST(PTHREAD_CFLAGS)
+AC_SUBST(PTHREAD_CC)
+
+# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND:
+if test x"$acx_pthread_ok" = xyes; then
+ ifelse([$1],,AC_DEFINE(HAVE_PTHREAD,1,[Define if you have POSIX threads libraries and header files.]),[$1])
+ :
+else
+ acx_pthread_ok=no
+ $2
+fi
+AC_LANG_POP(C)
+])dnl ACX_PTHREAD
+
+
+dnl
+dnl determine how to get IEEE math working
+dnl AC_IEEE(MESSAGE, set rd_cv_ieee_[var] variable, INCLUDES,
+dnl FUNCTION-BODY, [ACTION-IF-FOUND [,ACTION-IF-NOT-FOUND]])
+dnl
+
+dnl substitute them in all the files listed in AC_OUTPUT
+AC_SUBST(PERLFLAGS)
+
+AC_DEFUN([AC_IEEE], [
+AC_MSG_CHECKING([if IEEE math works $1])
+AC_CACHE_VAL([rd_cv_ieee_$2],
+[AC_RUN_IFELSE([AC_LANG_SOURCE([[$3
+
+#if HAVE_MATH_H
+# include <math.h>
+#endif
+
+#if HAVE_FLOAT_H
+# include <float.h>
+#endif
+
+#if HAVE_IEEEFP_H
+# include <ieeefp.h>
+#endif
+
+#if HAVE_FP_CLASS_H
+# include <fp_class.h>
+#endif
+
+/* Solaris */
+#if (! defined(HAVE_ISINF) && defined(HAVE_FPCLASS))
+# define HAVE_ISINF 1
+# define isinf(a) (fpclass(a) == FP_NINF || fpclass(a) == FP_PINF)
+#endif
+
+/* solaris 10 it defines isnan such that only forte can compile it ... bad bad */
+#if (defined(HAVE_ISNAN) && defined(isnan) && defined(HAVE_FPCLASS))
+# undef isnan
+# define isnan(a) (fpclass(a) == FP_SNAN || fpclass(a) == FP_QNAN)
+#endif
+
+/* Digital UNIX */
+#if (! defined(HAVE_ISINF) && defined(HAVE_FP_CLASS) && defined(HAVE_FP_CLASS_H))
+# define HAVE_ISINF 1
+# define isinf(a) (fp_class(a) == FP_NEG_INF || fp_class(a) == FP_POS_INF)
+#endif
+
+/* AIX */
+#if (! defined(HAVE_ISINF) && defined(HAVE_CLASS))
+# define HAVE_ISINF 1
+# define isinf(a) (class(a) == FP_MINUS_INF || class(a) == FP_PLUS_INF)
+#endif
+
+#if (! defined(HAVE_ISINF) && defined(HAVE_FPCLASSIFY) && defined(FP_PLUS_INF) && defined(FP_MINUS_INF))
+# define HAVE_ISINF 1
+# define isinf(a) (fpclassify(a) == FP_MINUS_INF || fpclassify(a) == FP_PLUS_INF)
+#endif
+
+#if (! defined(HAVE_ISINF) && defined(HAVE_FPCLASSIFY) && defined(FP_INFINITE))
+# define HAVE_ISINF 1
+# define isinf(a) (fpclassify(a) == FP_INFINITE)
+#endif
+
+#include <stdio.h>
+int main(void){
+ double rrdnan,rrdinf,rrdc,rrdzero;
+ $4;
+ /* some math to see if we get a floating point exception */
+ rrdzero=sin(0.0); /* don't let the compiler optimize us away */
+ rrdnan=0.0/rrdzero; /* especially here */
+ rrdinf=1.0/rrdzero; /* and here. I want to know if it can do the magic */
+ /* at run time without sig fpe */
+ rrdc = rrdinf + rrdnan;
+ rrdc = rrdinf / rrdnan;
+ if (! isnan(rrdnan)) {printf ("not isnan(NaN) ... "); return 1;}
+ if (rrdnan == rrdnan) {printf ("nan == nan ... "); return 1;}
+ if (! isinf(rrdinf)) {printf ("not isinf(oo) ... "); return 1;}
+ if (! isinf(-rrdinf)) {printf ("not isinf(-oo) ... "); return 1;}
+ if (! rrdinf > 0) {printf ("not inf > 0 ... "); return 1;}
+ if (! -rrdinf < 0) {printf ("not -inf < 0 ... "); return 1;}
+ return 0;
+ }]])],[rd_cv_ieee_$2=yes],[rd_cv_ieee_$2=no],[:])])
+dnl these we run regardles is cached or not
+if test x${rd_cv_ieee_$2} = "xyes"; then
+ AC_MSG_RESULT(yes)
+ $5
+else
+ AC_MSG_RESULT(no)
+ $6
+fi
+
+])
+
+AC_DEFUN([AC_FULL_IEEE],[
+AC_LANG_PUSH(C)
+_cflags=${CFLAGS}
+AC_IEEE([out of the box], works, , , ,
+ [CFLAGS="$_cflags -ieee"
+ AC_IEEE([with the -ieee switch], switch, , , ,
+ [CFLAGS="$_cflags -qfloat=nofold"
+ AC_IEEE([with the -qfloat=nofold switch], nofold, , , ,
+ [CFLAGS="$_cflags -w -qflttrap=enable:zerodivide"
+ AC_IEEE([with the -w -qflttrap=enable:zerodivide], flttrap, , , ,
+ [CFLAGS="$_cflags -mieee"
+ AC_IEEE([with the -mieee switch], mswitch, , , ,
+ [CFLAGS="$_cflags -q float=rndsngl"
+ AC_IEEE([with the -q float=rndsngl switch], qswitch, , , ,
+ [CFLAGS="$_cflags -OPT:IEEE_NaN_inf=ON"
+ AC_IEEE([with the -OPT:IEEE_NaN_inf=ON switch], ieeenaninfswitch, , , ,
+ [CFLAGS="$_cflags -OPT:IEEE_comparisons=ON"
+ AC_IEEE([with the -OPT:IEEE_comparisons=ON switch], ieeecmpswitch, , , ,
+ [CFLAGS=$_cflags
+ AC_IEEE([with fpsetmask(0)], mask,
+ [#include <floatingpoint.h>], [fpsetmask(0)],
+ [AC_DEFINE(MUST_DISABLE_FPMASK)
+ PERLFLAGS="CCFLAGS=-DMUST_DISABLE_FPMASK"],
+ [AC_IEEE([with signal(SIGFPE,SIG_IGN)], sigfpe,
+ [#include <signal.h>], [signal(SIGFPE,SIG_IGN)],
+ [AC_DEFINE(MUST_DISABLE_SIGFPE)
+ PERLFLAGS="CCFLAGS=-DMUST_DISABLE_SIGFPE"],
+ AC_MSG_ERROR([
+Your Compiler does not do propper IEEE math ... Please find out how to
+make IEEE math work with your compiler and let me know (tobi@oetiker.ch).
+Check config.log to see what went wrong ...
+]))])])])])])])])])])
+
+AC_LANG_POP(C)
+
+])
+
+
+dnl a macro to check for ability to create python extensions
+dnl AM_CHECK_PYTHON_HEADERS([ACTION-IF-POSSIBLE], [ACTION-IF-NOT-POSSIBLE])
+dnl function also defines PYTHON_INCLUDES
+AC_DEFUN([AM_CHECK_PYTHON_HEADERS],
+[AC_REQUIRE([AM_PATH_PYTHON])
+AC_MSG_CHECKING(for headers required to compile python extensions)
+dnl deduce PYTHON_INCLUDES
+py_prefix=`$PYTHON -c "import sys; print sys.prefix"`
+py_exec_prefix=`$PYTHON -c "import sys; print sys.exec_prefix"`
+PYTHON_INCLUDES="-I${py_prefix}/include/python${PYTHON_VERSION}"
+if test "$py_prefix" != "$py_exec_prefix"; then
+ PYTHON_INCLUDES="$PYTHON_INCLUDES -I${py_exec_prefix}/include/python${PYTHON_VERSION}"
+fi
+AC_SUBST(PYTHON_INCLUDES)
+dnl check if the headers exist:
+save_CPPFLAGS="$CPPFLAGS"
+CPPFLAGS="$CPPFLAGS $PYTHON_INCLUDES"
+AC_TRY_CPP([#include <Python.h>],dnl
+[AC_MSG_RESULT(found)
+$1],dnl
+[AC_MSG_RESULT(not found)
+$2])
+CPPFLAGS="$save_CPPFLAGS"
+])
+
+dnl a macro to add some color to the build process.
+dnl CONFIGURE_PART(MESSAGE)
+
+AC_DEFUN([CONFIGURE_PART],[
+case $TERM in
+ # for the most important terminal types we directly know the sequences
+ xterm|xterm*|vt220|vt220*)
+ T_MD=`awk 'BEGIN { printf("%c%c%c%c", 27, 91, 49, 109); }' </dev/null 2>/dev/null`
+ T_ME=`awk 'BEGIN { printf("%c%c%c", 27, 91, 109); }' </dev/null 2>/dev/null`
+ ;;
+ vt100|vt100*|cygwin)
+ T_MD=`awk 'BEGIN { printf("%c%c%c%c%c%c", 27, 91, 49, 109, 0, 0); }' </dev/null 2>/dev/null`
+ T_ME=`awk 'BEGIN { printf("%c%c%c%c%c", 27, 91, 109, 0, 0); }' </dev/null 2>/dev/null`
+ ;;
+ *)
+ T_MD=''
+ T_ME=''
+ ;;
+esac
+ AC_MSG_RESULT()
+ AC_MSG_RESULT([${T_MD}$1${T_ME}])
+])
+
+
+dnl ---------------------------------------------------------------------------
+dnl CF_DISABLE_ECHO version: 10 updated: 2003/04/17 22:27:11
+dnl ---------------
+dnl stolen from xterm aclocal.m4
+dnl
+dnl You can always use "make -n" to see the actual options, but it's hard to
+dnl pick out/analyze warning messages when the compile-line is long.
+dnl
+dnl Sets:
+dnl ECHO_LT - symbol to control if libtool is verbose
+dnl ECHO_LD - symbol to prefix "cc -o" lines
+dnl RULE_CC - symbol to put before implicit "cc -c" lines (e.g., .c.o)
+dnl SHOW_CC - symbol to put before explicit "cc -c" lines
+dnl ECHO_CC - symbol to put before any "cc" line
+dnl
+AC_DEFUN([CF_DISABLE_ECHO],[
+AC_MSG_CHECKING(if you want to see long compiling messages)
+CF_ARG_DISABLE(echo,
+ [ --disable-echo display "compiling" commands],
+ [
+ ECHO_LT='--silent'
+ ECHO_LD='@echo linking [$]@;'
+ RULE_CC=' @echo compiling [$]<'
+ SHOW_CC=' @echo compiling [$]@'
+ ECHO_CC='@'
+],[
+ ECHO_LT=''
+ ECHO_LD=''
+ RULE_CC='# compiling'
+ SHOW_CC='# compiling'
+ ECHO_CC=''
+])
+AC_MSG_RESULT($enableval)
+AC_SUBST(ECHO_LT)
+AC_SUBST(ECHO_LD)
+AC_SUBST(RULE_CC)
+AC_SUBST(SHOW_CC)
+AC_SUBST(ECHO_CC)
+])dnl
diff --git a/program/bindings/Makefile.am b/program/bindings/Makefile.am
--- /dev/null
@@ -0,0 +1,56 @@
+.PHONY: python ruby
+
+if BUILD_TCL
+SUB_tcl = tcl
+endif
+
+SUBDIRS = $(SUB_tcl)
+
+# the following files are not mentioned in any other Makefile
+EXTRA_DIST = perl-piped/MANIFEST perl-piped/README perl-piped/Makefile.PL perl-piped/RRDp.pm perl-piped/t/base.t \
+ perl-shared/ntmake.pl perl-shared/MANIFEST perl-shared/README perl-shared/Makefile.PL perl-shared/RRDs.pm perl-shared/RRDs.xs perl-shared/t/base.t \
+ ruby/CHANGES ruby/README ruby/extconf.rb ruby/main.c ruby/test.rb \
+ python/ACKNOWLEDGEMENT python/AUTHORS python/COPYING python/README python/rrdtoolmodule.c python/setup.py
+
+
+# add the following to the all target
+all-local: @COMP_PERL@ @COMP_RUBY@ @COMP_PYTHON@
+
+install-data-local:
+ test -f perl-piped/Makefile && cd perl-piped && $(MAKE) install || true
+ test -f perl-shared/Makefile && cd perl-shared && $(MAKE) install || true
+ test -f ruby/Makefile && cd ruby && $(MAKE) EPREFIX=$(DESTDIR)$(exec_prefix) $(RUBY_MAKE_OPTIONS) install || true
+ test -d python/build && cd python && env BUILDLIBDIR=../../src/.libs $(PYTHON) setup.py install --skip-build --prefix=$(DESTDIR)$(prefix) --exec-prefix=$(DESTDIR)$(exec_prefix) || true
+
+# rules for buildung the ruby module
+# RUBYARCHDIR= is to work around in a makefile quirk not sure
+# it is is the right thing todo, but it makes rrdtool build on freebsd as well
+ruby:
+ cd ruby && $(RUBY) extconf.rb && $(MAKE) EPREFIX=$(exec_prefix) $(RUBY_MAKE_OPTIONS) RUBYARCHDIR=
+
+# rules for buildung the pyton module
+python:
+ cd python && env BUILDLIBDIR=../../src/.libs $(PYTHON) setup.py build_ext --rpath=$(libdir) && env LIBDIR=../../src/.libs $(PYTHON) setup.py build
+
+# rules for building the perl module
+perl_piped: perl-piped/Makefile
+ cd perl-piped && $(MAKE)
+
+perl-piped/Makefile: perl-piped/Makefile.PL
+ cd perl-piped && $(PERL) Makefile.PL $(PERL_MAKE_OPTIONS)
+
+perl_shared: perl-shared/Makefile
+ cd perl-shared && $(MAKE)
+
+perl-shared/Makefile: perl-shared/Makefile.PL
+ cd perl-shared && $(PERL) Makefile.PL $(PERLFLAGS) $(PERL_MAKE_OPTIONS) RPATH=$(libdir)
+# LIBS="$(LDFLAGS) $(LIBS)" $(PERLFLAGS) $(PERL_MAKE_OPTIONS)
+
+clean-local:
+ test -f perl-piped/Makefile && cd perl-piped && $(MAKE) clean || true
+ test -f perl-piped/Makefile && rm perl-piped/Makefile || true
+ test -f perl-shared/Makefile && cd perl-shared && $(MAKE) clean || true
+ test -f perl-shared/Makefile && rm -f perl-shared/Makefile || true
+ test -f ruby/Makefile && cd ruby && $(MAKE) clean && rm Makefile || true
+ test -d python/build && cd python && rm -rf build || true
+##END##
diff --git a/program/bindings/perl-piped/MANIFEST b/program/bindings/perl-piped/MANIFEST
--- /dev/null
@@ -0,0 +1,5 @@
+MANIFEST
+README
+Makefile.PL
+RRDp.pm
+t/base.t
diff --git a/program/bindings/perl-piped/Makefile.PL b/program/bindings/perl-piped/Makefile.PL
--- /dev/null
@@ -0,0 +1,10 @@
+use ExtUtils::MakeMaker;
+
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+WriteMakefile(
+ 'NAME' => 'RRDp',
+ 'VERSION' => '0.99.0', # finds $VERSION
+ 'linkext' => {LINKTYPE => ''},
+ 'dist' => {COMPRESS=>'gzip', SUFFIX=>'gz'},
+);
diff --git a/program/bindings/perl-piped/README b/program/bindings/perl-piped/README
--- /dev/null
@@ -0,0 +1,5 @@
+This is a Perl module for using RRDtool process via a set of pipes.
+
+perl Makefile.PL
+make test
+make install
diff --git a/program/bindings/perl-piped/RRDp.pm b/program/bindings/perl-piped/RRDp.pm
--- /dev/null
@@ -0,0 +1,203 @@
+package RRDp;
+
+=head1 NAME
+
+RRDp - Attach RRDtool from within a perl script via a set of pipes;
+
+=head1 SYNOPSIS
+
+use B<RRDp>
+
+B<RRDp::start> I<path to RRDtool executable>
+
+B<RRDp::cmd> I<rrdtool commandline>
+
+$answer = B<RRD::read>
+
+$status = B<RRD::end>
+
+B<$RRDp::user>, B<$RRDp::sys>, B<$RRDp::real>, B<$RRDp::error_mode>, B<$RRDp::error>
+
+=head1 DESCRIPTION
+
+With this module you can safely communicate with the RRDtool.
+
+After every B<RRDp::cmd> you have to issue an B<RRDp::read> command to get
+B<RRDtool>s answer to your command. The answer is returned as a pointer,
+in order to speed things up. If the last command did not return any
+data, B<RRDp::read> will return an undefined variable.
+
+If you import the PERFORMANCE variables into your namespace,
+you can access RRDtool's internal performance measurements.
+
+=over 8
+
+=item use B<RRDp>
+
+Load the RRDp::pipe module.
+
+=item B<RRDp::start> I<path to RRDtool executable>
+
+start RRDtool. The argument must be the path to the RRDtool executable
+
+=item B<RRDp::cmd> I<rrdtool commandline>
+
+pass commands on to RRDtool. check the RRDtool documentation for
+more info on the RRDtool commands.
+
+=item $answer = B<RRDp::read>
+
+read RRDtool's response to your command. Note that the $answer variable will
+only contain a pointer to the returned data. The reason for this is, that
+RRDtool can potentially return quite excessive amounts of data
+and we don't want to copy this around in memory. So when you want to
+access the contents of $answer you have to use $$answer which dereferences
+the variable.
+
+=item $status = B<RRDp::end>
+
+terminates RRDtool and returns RRDtool's status ...
+
+=item B<$RRDp::user>, B<$RRDp::sys>, B<$RRDp::real>
+
+these variables will contain totals of the user time, system time and
+real time as seen by RRDtool. User time is the time RRDtool is
+running, System time is the time spend in system calls and real time
+is the total time RRDtool has been running.
+
+The difference between user + system and real is the time spent
+waiting for things like the hard disk and new input from the perl
+script.
+
+=item B<$RRDp::error_mode> and B<$RRDp::error>
+
+If you set the variable $RRDp::error_mode to the value 'catch' before you run RRDp::read a potential
+ERROR message will not cause the program to abort but will be returned in this variable. If no error
+occurs the variable will be empty.
+
+ $RRDp::error_mode = 'catch';
+ RRDp::cmd qw(info file.rrd);
+ print $RRDp::error if $RRDp::error;
+
+=back
+
+
+=head1 EXAMPLE
+
+ use RRDp;
+ RRDp::start "/usr/local/bin/rrdtool";
+ RRDp::cmd qw(create demo.rrd --step 100
+ DS:in:GAUGE:100:U:U
+ RRA:AVERAGE:0.5:1:10);
+ $answer = RRDp::read;
+ print $$answer;
+ ($usertime,$systemtime,$realtime) = ($RRDp::user,$RRDp::sys,$RRDp::real);
+
+=head1 SEE ALSO
+
+For more information on how to use RRDtool, check the manpages.
+
+=head1 AUTHOR
+
+Tobias Oetiker <tobi@oetiker.ch>
+
+=cut
+
+#' this is to make cperl.el happy
+
+use strict;
+use Fcntl;
+use Carp;
+use IO::Handle;
+use IPC::Open2;
+use vars qw($Sequence $RRDpid $VERSION);
+my $Sequence;
+my $RRDpid;
+
+# Prototypes
+
+sub start ($);
+sub cmd (@);
+sub end ();
+sub read ();
+
+$VERSION=1.3002;
+
+sub start ($){
+ croak "rrdtool is already running"
+ if defined $Sequence;
+ $Sequence = 'S';
+ my $rrdtool = shift @_;
+ $RRDpid = open2 \*RRDreadHand,\*RRDwriteHand, $rrdtool,"-"
+ or croak "Can't Start rrdtool: $!";
+ RRDwriteHand->autoflush(); #flush after every write
+ fcntl RRDreadHand, F_SETFL,O_NONBLOCK|O_NDELAY; #make readhandle NON BLOCKING
+ return $RRDpid;
+}
+
+
+sub read () {
+ croak "RRDp::read can only be called after RRDp::cmd"
+ unless $Sequence eq 'C';
+ $RRDp::error = undef;
+ $Sequence = 'R';
+ my $inmask = 0;
+ my $srbuf;
+ my $minibuf;
+ my $buffer;
+ my $nfound;
+ my $timeleft;
+ vec($inmask,fileno(RRDreadHand),1) = 1; # setup select mask for Reader
+ while (1) {
+ my $rout;
+ $nfound = select($rout=$inmask,undef,undef,2);
+ if ($nfound == 0 ) {
+ # here, we could do something sensible ...
+ next;
+ }
+ sysread(RRDreadHand,$srbuf,4096);
+ $minibuf .= $srbuf;
+ while ($minibuf =~ s|^(.+?)\n||s) {
+ my $line = $1;
+ # print $line,"\n";
+ $RRDp::error = undef;
+ if ($line =~ m|^ERROR|) {
+ $RRDp::error_mode eq 'catch' ? $RRDp::error = $line : croak $line;
+ $RRDp::sys = undef;
+ $RRDp::user = undef;
+ $RRDp::real = undef;
+ return undef;
+ }
+ elsif ($line =~ m|^OK(?: u:([\d\.]+) s:([\d\.]+) r:([\d\.]+))?|){
+ ($RRDp::sys,$RRDp::user,$RRDp::real)=($1,$2,$3);
+ return \$buffer;
+ } else {
+ $buffer .= $line. "\n";
+ }
+ }
+ }
+}
+
+sub cmd (@){
+ croak "RRDp::cmd can only be called after RRDp::read or RRDp::start"
+ unless $Sequence eq 'R' or $Sequence eq 'S';
+ $Sequence = 'C';
+ my $cmd = join " ", @_;
+ if ($Sequence ne 'S') {
+ }
+ $cmd =~ s/\n/ /gs;
+ $cmd =~ s/\s/ /gs;
+ print RRDwriteHand "$cmd\n";
+}
+
+sub end (){
+ croak "RRDp::end can only be called after RRDp::start"
+ unless $Sequence;
+ close RRDwriteHand;
+ close RRDreadHand;
+ $Sequence = undef;
+ waitpid $RRDpid,0;
+ return $?
+}
+
+1;
diff --git a/program/bindings/perl-piped/leaktest.pl b/program/bindings/perl-piped/leaktest.pl
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/perl -w
+$ENV{PATH}="/usr/ucb";
+use strict;
+use RRDp;
+my $rrdfile='/tmp/test.rrd';
+RRDp::start '/home/oetiker/data/projects/AABN-rrdtool/src/rrdtool';
+print grep /rrdtool/,`ps au`;
+print grep /rrdtool/,`ps au`;
+my $i=0;
+while ($i<1000) {
+ RRDp::cmd 'info /tmp/test.rrd';
+ $_ = RRDp::read;
+ $i++;
+}
+$_ = RRDp::end;
+print grep /rrdtool/,`ps au`;
diff --git a/program/bindings/perl-piped/rrdpl.dsp b/program/bindings/perl-piped/rrdpl.dsp
--- /dev/null
@@ -0,0 +1,115 @@
+# Microsoft Developer Studio Project File - Name="rrd" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 5.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=rrd - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "rrdpl.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "rrdpl.mak" CFG="rrd - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "rrd - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "rrd - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "rrd - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c
+# ADD CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o NUL /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o NUL /win32
+# ADD BASE RSC /l 0x100c /d "NDEBUG"
+# ADD RSC /l 0x100c /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386
+
+!ELSEIF "$(CFG)" == "rrd - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "C:\perl\lib\site\auto\RRD\"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /c
+# ADD CPP /W3 /I "C:\perl\lib\CORE" /D "WIN32" /D VERSION=\"0.02\" /D XS_VERSION=\"0.02\" /D "_DEBUG" /D "_CONSOLE" /FR -I../src/ -I../gd1.2 RRD.c /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o NUL /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o NUL /win32
+# ADD BASE RSC /l 0x100c /d "_DEBUG"
+# ADD RSC /l 0x100c /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 c:\perl\lib\core\perl.lib ..\src\debug\rrd.lib ..\gd1.2\debug\gd.lib /dll /incremental:no /debug /machine:IX86 /out:"C:\perl\lib\site\auto\RRD\rrd.dll"
+# SUBTRACT LINK32 /pdb:none
+
+!ENDIF
+
+# Begin Target
+
+# Name "rrd - Win32 Release"
+# Name "rrd - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\RRD.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\RRD.xs
+
+!IF "$(CFG)" == "rrd - Win32 Release"
+
+!ELSEIF "$(CFG)" == "rrd - Win32 Debug"
+
+# Begin Custom Build
+InputPath=.\RRD.xs
+
+"rrd.c" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"
+ C:\Perl\bin\perl -Ic:\perl\lib -Ic:\perl\lib C:\perl\lib\ExtUtils/xsubpp\
+ -typemap C:\perl\lib\ExtUtils\typemap RRD.xs >RRD.tc && C:\Perl\bin\perl\
+ -Ic:\perl\lib -Ic:\perl\lib -MExtUtils::Command -e mv RRD.tc RRD.c
+
+# End Custom Build
+
+!ENDIF
+
+# End Source File
+# End Target
+# End Project
diff --git a/program/bindings/perl-piped/rrdpl.dsw b/program/bindings/perl-piped/rrdpl.dsw
--- /dev/null
@@ -0,0 +1,29 @@
+Microsoft Developer Studio Workspace File, Format Version 5.00
+# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
+
+###############################################################################
+
+Project: "rrd"=".\rrd.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Global:
+
+Package=<5>
+{{{
+}}}
+
+Package=<3>
+{{{
+}}}
+
+###############################################################################
+
diff --git a/program/bindings/perl-piped/t/base.t b/program/bindings/perl-piped/t/base.t
--- /dev/null
@@ -0,0 +1,42 @@
+#! /usr/bin/perl
+
+# this exercises just the perl module .. not RRDtool as such ...
+
+BEGIN { $| = 1; print "1..5\n"; }
+END {
+ print "not ok 1\n" unless $loaded;
+ unlink "demo.rrd";
+}
+
+sub ok
+{
+ $ok_count++;
+ my($what, $result) = @_ ;
+ print "not " unless $result;
+ print "ok $ok_count $what\n";
+}
+
+use RRDp;
+
+$loaded = 1;
+$ok_count = 1;
+
+print "ok 1 module load\n";
+
+ok("RRDp::start", RRDp::start "../../src/rrdtool" > 0);
+
+$now=time();
+RRDp::cmd qw(create demo.rrd --start ), $now, qw(--step 100 ),
+ qw( DS:in:GAUGE:100:U:U RRA:AVERAGE:0.5:1:10 );
+
+$answer = RRDp::read;
+ok("RRDp::cmd", -s "demo.rrd" );
+
+RRDp::cmd qw(last demo.rrd);
+$answer = RRDp::read;
+
+ok("RRDp::read", $$answer =~ /$now/);
+
+$status = RRDp::end;
+
+ok("RRDp::end", $status == 0);
diff --git a/program/bindings/perl-shared/MANIFEST b/program/bindings/perl-shared/MANIFEST
--- /dev/null
@@ -0,0 +1,7 @@
+ntmake.pl
+MANIFEST
+README
+Makefile.PL
+RRDs.pm
+RRDs.xs
+t/base.t
diff --git a/program/bindings/perl-shared/Makefile.PL b/program/bindings/perl-shared/Makefile.PL
--- /dev/null
@@ -0,0 +1,39 @@
+use ExtUtils::MakeMaker;
+use Config;
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+
+# if the last argument when calling Makefile.PL is RPATH=/... and ... is the
+# path to librrd.so then the Makefile will be written such that RRDs.so knows
+# where to find librrd.so later on ...
+my $R="";
+if ($ARGV[-1] =~ /RPATH=(\S+)/){
+ pop @ARGV;
+ my $rp = $1;
+ for ($^O){
+ /linux/ && do{ $R = "-Wl,--rpath -Wl,$rp"};
+ /hpux/ && do{ $R = "+b$rp"};
+ /solaris/ && do{ $R = "-R$rp"};
+ /aix/ && do{ $R = "-Wl,-blibpath:$rp"};
+ }
+}
+
+# darwin works without this because librrd contains its
+# install_name which will includes the final location of the
+# library after it is installed. This install_name gets transfered
+# to the perl shared object.
+
+my $librrd = "-L../../src/.libs/ $R -lrrd";
+
+WriteMakefile(
+ 'NAME' => 'RRDs',
+ 'VERSION_FROM' => 'RRDs.pm', # finds $VERSION
+ 'DEFINE' => "-DPERLPATCHLEVEL=$Config{PATCHLEVEL}",
+ 'INC' => '-I../../src',
+ # Perl will figure out which one is valid
+ #'dynamic_lib' => {'OTHERLDFLAGS' => "$librrd -lm"},
+ 'depend' => {'RRDs.c' => "../../src/librrd.la"},
+ 'LDFROM' => '$(OBJECT) '.$librrd,
+ 'realclean' => {FILES => 't/demo?.rrd t/demo?.png' }
+);
+
diff --git a/program/bindings/perl-shared/README b/program/bindings/perl-shared/README
--- /dev/null
@@ -0,0 +1,12 @@
+These are the Perl bindings for rrdtool as a shared library. To compile do
+the following:
+
+perl Makefile.PL
+make test
+
+(win32 users try perl ntmake.pl)
+
+* if dynamic linking does not work, try
+
+perl Makefile.PL LINKTYPE=static
+make test
diff --git a/program/bindings/perl-shared/RRDs.pm b/program/bindings/perl-shared/RRDs.pm
--- /dev/null
@@ -0,0 +1,151 @@
+package RRDs;
+
+use strict;
+use vars qw(@ISA $VERSION);
+
+@ISA = qw(DynaLoader);
+
+require DynaLoader;
+
+$VERSION=1.3002;
+
+bootstrap RRDs $VERSION;
+
+1;
+__END__
+
+=head1 NAME
+
+RRDs - Access RRDtool as a shared module
+
+=head1 SYNOPSIS
+
+ use RRDs;
+ RRDs::error
+ RRDs::last ...
+ RRDs::info ...
+ RRDs::create ...
+ RRDs::update ...
+ RRDs::updatev ...
+ RRDs::graph ...
+ RRDs::fetch ...
+ RRDs::tune ...
+ RRDs::times(start, end)
+ RRDs::dump ...
+ RRDs::restore ...
+
+=head1 DESCRIPTION
+
+=head2 Calling Sequence
+
+This module accesses RRDtool functionality directly from within perl. The
+arguments to the functions listed in the SYNOPSIS are explained in the regular
+RRDtool documentation. The commandline call
+
+ rrdtool update mydemo.rrd --template in:out N:12:13
+
+gets turned into
+
+ RRDs::update ("mydemo.rrd", "--template", "in:out", "N:12:13");
+
+Note that
+
+ --template=in:out
+
+is also valid.
+
+The RRDs::times function takes two parameters: a "start" and "end" time.
+These should be specified in the B<AT-STYLE TIME SPECIFICATION> format
+used by RRDtool. See the B<rrdfetch> documentation for a detailed
+explanation on how to specify time.
+
+=head2 Error Handling
+
+The RRD functions will not abort your program even when they can not make
+sense out of the arguments you fed them.
+
+The function RRDs::error should be called to get the error status
+after each function call. If RRDs::error does not return anything
+then the previous function has completed its task successfully.
+
+ use RRDs;
+ RRDs::update ("mydemo.rrd","N:12:13");
+ my $ERR=RRDs::error;
+ die "ERROR while updating mydemo.rrd: $ERR\n" if $ERR;
+
+=head2 Return Values
+
+The functions RRDs::last, RRDs::graph, RRDs::info, RRDs::fetch and RRDs::times
+return their findings.
+
+B<RRDs::last> returns a single INTEGER representing the last update time.
+
+ $lastupdate = RRDs::last ...
+
+B<RRDs::graph> returns an ARRAY containing the x-size and y-size of the
+created image and a pointer to an array with the results of the PRINT arguments.
+
+ ($result_arr,$xsize,$ysize) = RRDs::graph ...
+ print "Imagesize: ${xsize}x${ysize}\n";
+ print "Averages: ", (join ", ", @$averages);
+
+B<RRDs::info> returns a pointer to a hash. The keys of the hash
+represent the property names of the RRD and the values of the hash are
+the values of the properties.
+
+ $hash = RRDs::info "example.rrd";
+ foreach my $key (keys %$hash){
+ print "$key = $$hash{$key}\n";
+ }
+
+B<RRDs::graphv> takes the same paramters as B<RRDs::graph> but it returns a
+pointer to hash. The hash returned contains meta information about the
+graph. Like its size as well as the position of the graph area on the image.
+When calling with and empty filename than the contents of the graph will be
+returned in the hash as well (key 'image').
+
+B<RRDs::updatev> also returns a pointer to hash. The keys of the hash
+are concatenated strings of a timestamp, RRA index, and data source name for
+each consolidated data point (CDP) written to disk as a result of the
+current update call. The hash values are CDP values.
+
+B<RRDs::fetch> is the most complex of
+the pack regarding return values. There are 4 values. Two normal
+integers, a pointer to an array and a pointer to a array of pointers.
+
+ my ($start,$step,$names,$data) = RRDs::fetch ...
+ print "Start: ", scalar localtime($start), " ($start)\n";
+ print "Step size: $step seconds\n";
+ print "DS names: ", join (", ", @$names)."\n";
+ print "Data points: ", $#$data + 1, "\n";
+ print "Data:\n";
+ for my $line (@$data) {
+ print " ", scalar localtime($start), " ($start) ";
+ $start += $step;
+ for my $val (@$line) {
+ printf "%12.1f ", $val;
+ }
+ print "\n";
+ }
+
+B<RRDs::times> returns two integers which are the number of seconds since
+epoch (1970-01-01) for the supplied "start" and "end" arguments, respectively.
+
+See the examples directory for more ways to use this extension.
+
+=head1 NOTE
+
+If you are manipulating the TZ variable you should also call the posixs
+function tzset to initialize all internal state of the library for properly
+operating in the timezone of your choice.
+
+ use POSIX qw(tzset);
+ $ENV{TZ} = 'CET';
+ POSIX::tzset();
+
+
+=head1 AUTHOR
+
+Tobias Oetiker E<lt>tobi@oetiker.chE<gt>
+
+=cut
diff --git a/program/bindings/perl-shared/RRDs.ppd b/program/bindings/perl-shared/RRDs.ppd
--- /dev/null
@@ -0,0 +1,10 @@
+<SOFTPKG NAME="RRDs" VERSION="1,100001,0,0">
+ <TITLE>RRDs</TITLE>
+ <ABSTRACT>Round Robin Database Tool</ABSTRACT>
+ <AUTHOR>Tobias Oetiker (tobi@oetiker.ch)</AUTHOR>
+ <IMPLEMENTATION>
+ <OS NAME="MSWin32" />
+ <ARCHITECTURE NAME="MSWin32-x86-multi-thread-5.8" />
+ <CODEBASE HREF="RRDs.tar.gz" />
+ </IMPLEMENTATION>
+</SOFTPKG>
diff --git a/program/bindings/perl-shared/RRDs.xs b/program/bindings/perl-shared/RRDs.xs
--- /dev/null
@@ -0,0 +1,446 @@
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "EXTERN.h"
+#include "perl.h"
+#include "XSUB.h"
+
+#ifdef __cplusplus
+}
+#endif
+
+/*
+ * rrd_tool.h includes config.h, but at least on Ubuntu Breezy Badger
+ * 5.10 with gcc 4.0.2, the C preprocessor picks up Perl's config.h
+ * which is included from the Perl includes and never reads rrdtool's
+ * config.h. Without including rrdtool's config.h, this module does
+ * not compile, so include it here with an explicit path.
+ *
+ * Because rrdtool's config.h redefines VERSION which is originally
+ * set via Perl's Makefile.PL and passed down to the C compiler's
+ * command line, save the original value and reset it after the
+ * includes.
+ */
+#define VERSION_SAVED VERSION
+#undef VERSION
+#include "../../rrd_config.h"
+#include "../../src/rrd_tool.h"
+#undef VERSION
+#define VERSION VERSION_SAVED
+#undef VERSION_SAVED
+
+/* perl 5.004 compatibility */
+#if PERLPATCHLEVEL < 5
+#define PL_sv_undef sv_undef
+#endif
+
+
+#define rrdcode(name) \
+ argv = (char **) malloc((items+1)*sizeof(char *));\
+ argv[0] = "dummy";\
+ for (i = 0; i < items; i++) { \
+ STRLEN len; \
+ char *handle= SvPV(ST(i),len);\
+ /* actually copy the data to make sure possible modifications \
+ on the argv data does not backfire into perl */ \
+ argv[i+1] = (char *) malloc((strlen(handle)+1)*sizeof(char)); \
+ strcpy(argv[i+1],handle); \
+ } \
+ rrd_clear_error();\
+ RETVAL=name(items+1,argv); \
+ for (i=0; i < items; i++) {\
+ free(argv[i+1]);\
+ } \
+ free(argv);\
+ \
+ if (rrd_test_error()) XSRETURN_UNDEF;
+
+#define hvs(VAL) hv_store_ent(hash, sv_2mortal(newSVpv(data->key,0)),VAL,0)
+
+#define rrdinfocode(name) \
+ /* prepare argument list */ \
+ argv = (char **) malloc((items+1)*sizeof(char *)); \
+ argv[0] = "dummy"; \
+ for (i = 0; i < items; i++) { \
+ STRLEN len; \
+ char *handle= SvPV(ST(i),len); \
+ /* actually copy the data to make sure possible modifications \
+ on the argv data does not backfire into perl */ \
+ argv[i+1] = (char *) malloc((strlen(handle)+1)*sizeof(char)); \
+ strcpy(argv[i+1],handle); \
+ } \
+ rrd_clear_error(); \
+ data=name(items+1, argv); \
+ for (i=0; i < items; i++) { \
+ free(argv[i+1]); \
+ } \
+ free(argv); \
+ if (rrd_test_error()) XSRETURN_UNDEF; \
+ hash = newHV(); \
+ save=data; \
+ while (data) { \
+ /* the newSV will get copied by hv so we create it as a mortal \
+ to make sure it does not keep hanging round after the fact */ \
+ switch (data->type) { \
+ case RD_I_VAL: \
+ if (isnan(data->value.u_val)) \
+ hvs(&PL_sv_undef); \
+ else \
+ hvs(newSVnv(data->value.u_val)); \
+ break; \
+ case RD_I_INT: \
+ hvs(newSViv(data->value.u_int)); \
+ break; \
+ case RD_I_CNT: \
+ hvs(newSViv(data->value.u_cnt)); \
+ break; \
+ case RD_I_STR: \
+ hvs(newSVpv(data->value.u_str,0)); \
+ break; \
+ case RD_I_BLO: \
+ hvs(newSVpv(data->value.u_blo.ptr,data->value.u_blo.size)); \
+ break; \
+ } \
+ data = data->next; \
+ } \
+ rrd_info_free(save); \
+ RETVAL = newRV_noinc((SV*)hash);
+
+/*
+ * should not be needed if libc is linked (see ntmake.pl)
+#ifdef WIN32
+ #define free free
+ #define malloc malloc
+ #define realloc realloc
+#endif
+*/
+
+
+MODULE = RRDs PACKAGE = RRDs PREFIX = rrd_
+
+BOOT:
+#ifdef MUST_DISABLE_SIGFPE
+ signal(SIGFPE,SIG_IGN);
+#endif
+#ifdef MUST_DISABLE_FPMASK
+ fpsetmask(0);
+#endif
+
+
+SV*
+rrd_error()
+ CODE:
+ if (! rrd_test_error()) XSRETURN_UNDEF;
+ RETVAL = newSVpv(rrd_get_error(),0);
+ OUTPUT:
+ RETVAL
+
+
+int
+rrd_last(...)
+ PROTOTYPE: @
+ PREINIT:
+ int i;
+ char **argv;
+ CODE:
+ rrdcode(rrd_last);
+ OUTPUT:
+ RETVAL
+
+int
+rrd_first(...)
+ PROTOTYPE: @
+ PREINIT:
+ int i;
+ char **argv;
+ CODE:
+ rrdcode(rrd_first);
+ OUTPUT:
+ RETVAL
+
+
+int
+rrd_create(...)
+ PROTOTYPE: @
+ PREINIT:
+ int i;
+ char **argv;
+ CODE:
+ rrdcode(rrd_create);
+ RETVAL = 1;
+ OUTPUT:
+ RETVAL
+
+
+int
+rrd_update(...)
+ PROTOTYPE: @
+ PREINIT:
+ int i;
+ char **argv;
+ CODE:
+ rrdcode(rrd_update);
+ RETVAL = 1;
+ OUTPUT:
+ RETVAL
+
+
+int
+rrd_tune(...)
+ PROTOTYPE: @
+ PREINIT:
+ int i;
+ char **argv;
+ CODE:
+ rrdcode(rrd_tune);
+ RETVAL = 1;
+ OUTPUT:
+ RETVAL
+
+
+SV *
+rrd_graph(...)
+ PROTOTYPE: @
+ PREINIT:
+ char **calcpr=NULL;
+ int i,xsize,ysize;
+ double ymin,ymax;
+ char **argv;
+ AV *retar;
+ PPCODE:
+ argv = (char **) malloc((items+1)*sizeof(char *));
+ argv[0] = "dummy";
+ for (i = 0; i < items; i++) {
+ STRLEN len;
+ char *handle = SvPV(ST(i),len);
+ /* actually copy the data to make sure possible modifications
+ on the argv data does not backfire into perl */
+ argv[i+1] = (char *) malloc((strlen(handle)+1)*sizeof(char));
+ strcpy(argv[i+1],handle);
+ }
+ rrd_clear_error();
+ rrd_graph(items+1,argv,&calcpr,&xsize,&ysize,NULL,&ymin,&ymax);
+ for (i=0; i < items; i++) {
+ free(argv[i+1]);
+ }
+ free(argv);
+
+ if (rrd_test_error()) {
+ if(calcpr)
+ for(i=0;calcpr[i];i++)
+ rrd_freemem(calcpr[i]);
+ XSRETURN_UNDEF;
+ }
+ retar=newAV();
+ if(calcpr){
+ for(i=0;calcpr[i];i++){
+ av_push(retar,newSVpv(calcpr[i],0));
+ rrd_freemem(calcpr[i]);
+ }
+ rrd_freemem(calcpr);
+ }
+ EXTEND(sp,4);
+ PUSHs(sv_2mortal(newRV_noinc((SV*)retar)));
+ PUSHs(sv_2mortal(newSViv(xsize)));
+ PUSHs(sv_2mortal(newSViv(ysize)));
+
+SV *
+rrd_fetch(...)
+ PROTOTYPE: @
+ PREINIT:
+ time_t start,end;
+ unsigned long step, ds_cnt,i,ii;
+ rrd_value_t *data,*datai;
+ char **argv;
+ char **ds_namv;
+ AV *retar,*line,*names;
+ PPCODE:
+ argv = (char **) malloc((items+1)*sizeof(char *));
+ argv[0] = "dummy";
+ for (i = 0; i < items; i++) {
+ STRLEN len;
+ char *handle= SvPV(ST(i),len);
+ /* actually copy the data to make sure possible modifications
+ on the argv data does not backfire into perl */
+ argv[i+1] = (char *) malloc((strlen(handle)+1)*sizeof(char));
+ strcpy(argv[i+1],handle);
+ }
+ rrd_clear_error();
+ rrd_fetch(items+1,argv,&start,&end,&step,&ds_cnt,&ds_namv,&data);
+ for (i=0; i < items; i++) {
+ free(argv[i+1]);
+ }
+ free(argv);
+ if (rrd_test_error()) XSRETURN_UNDEF;
+ /* convert the ds_namv into perl format */
+ names=newAV();
+ for (ii = 0; ii < ds_cnt; ii++){
+ av_push(names,newSVpv(ds_namv[ii],0));
+ rrd_freemem(ds_namv[ii]);
+ }
+ rrd_freemem(ds_namv);
+ /* convert the data array into perl format */
+ datai=data;
+ retar=newAV();
+ for (i = start+step; i <= end; i += step){
+ line = newAV();
+ for (ii = 0; ii < ds_cnt; ii++){
+ av_push(line,(isnan(*datai) ? &PL_sv_undef : newSVnv(*datai)));
+ datai++;
+ }
+ av_push(retar,newRV_noinc((SV*)line));
+ }
+ rrd_freemem(data);
+ EXTEND(sp,5);
+ PUSHs(sv_2mortal(newSViv(start+step)));
+ PUSHs(sv_2mortal(newSViv(step)));
+ PUSHs(sv_2mortal(newRV_noinc((SV*)names)));
+ PUSHs(sv_2mortal(newRV_noinc((SV*)retar)));
+
+SV *
+rrd_times(start, end)
+ char *start
+ char *end
+ PREINIT:
+ rrd_time_value_t start_tv, end_tv;
+ char *parsetime_error = NULL;
+ time_t start_tmp, end_tmp;
+ PPCODE:
+ rrd_clear_error();
+ if ((parsetime_error = rrd_parsetime(start, &start_tv))) {
+ rrd_set_error("start time: %s", parsetime_error);
+ XSRETURN_UNDEF;
+ }
+ if ((parsetime_error = rrd_parsetime(end, &end_tv))) {
+ rrd_set_error("end time: %s", parsetime_error);
+ XSRETURN_UNDEF;
+ }
+ if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
+ XSRETURN_UNDEF;
+ }
+ EXTEND(sp,2);
+ PUSHs(sv_2mortal(newSVuv(start_tmp)));
+ PUSHs(sv_2mortal(newSVuv(end_tmp)));
+
+int
+rrd_xport(...)
+ PROTOTYPE: @
+ PREINIT:
+ time_t start,end;
+ int xsize;
+ unsigned long step, col_cnt,row_cnt,i,ii;
+ rrd_value_t *data,*ptr;
+ char **argv,**legend_v;
+ AV *retar,*line,*names;
+ PPCODE:
+ argv = (char **) malloc((items+1)*sizeof(char *));
+ argv[0] = "dummy";
+ for (i = 0; i < items; i++) {
+ STRLEN len;
+ char *handle = SvPV(ST(i),len);
+ /* actually copy the data to make sure possible modifications
+ on the argv data does not backfire into perl */
+ argv[i+1] = (char *) malloc((strlen(handle)+1)*sizeof(char));
+ strcpy(argv[i+1],handle);
+ }
+ rrd_clear_error();
+ rrd_xport(items+1,argv,&xsize,&start,&end,&step,&col_cnt,&legend_v,&data);
+ for (i=0; i < items; i++) {
+ free(argv[i+1]);
+ }
+ free(argv);
+ if (rrd_test_error()) XSRETURN_UNDEF;
+
+ /* convert the legend_v into perl format */
+ names=newAV();
+ for (ii = 0; ii < col_cnt; ii++){
+ av_push(names,newSVpv(legend_v[ii],0));
+ rrd_freemem(legend_v[ii]);
+ }
+ rrd_freemem(legend_v);
+
+ /* convert the data array into perl format */
+ ptr=data;
+ retar=newAV();
+ for (i = start+step; i <= end; i += step){
+ line = newAV();
+ for (ii = 0; ii < col_cnt; ii++){
+ av_push(line,(isnan(*ptr) ? &PL_sv_undef : newSVnv(*ptr)));
+ ptr++;
+ }
+ av_push(retar,newRV_noinc((SV*)line));
+ }
+ rrd_freemem(data);
+
+ EXTEND(sp,7);
+ PUSHs(sv_2mortal(newSViv(start+step)));
+ PUSHs(sv_2mortal(newSViv(end)));
+ PUSHs(sv_2mortal(newSViv(step)));
+ PUSHs(sv_2mortal(newSViv(col_cnt)));
+ PUSHs(sv_2mortal(newRV_noinc((SV*)names)));
+ PUSHs(sv_2mortal(newRV_noinc((SV*)retar)));
+
+SV*
+rrd_info(...)
+ PROTOTYPE: @
+ PREINIT:
+ rrd_info_t *data,*save;
+ int i;
+ char **argv;
+ HV *hash;
+ CODE:
+ rrdinfocode(rrd_info);
+ OUTPUT:
+ RETVAL
+
+SV*
+rrd_updatev(...)
+ PROTOTYPE: @
+ PREINIT:
+ rrd_info_t *data,*save;
+ int i;
+ char **argv;
+ HV *hash;
+ CODE:
+ rrdinfocode(rrd_update_v);
+ OUTPUT:
+ RETVAL
+
+SV*
+rrd_graphv(...)
+ PROTOTYPE: @
+ PREINIT:
+ rrd_info_t *data,*save;
+ int i;
+ char **argv;
+ HV *hash;
+ CODE:
+ rrdinfocode(rrd_graph_v);
+ OUTPUT:
+ RETVAL
+
+int
+rrd_dump(...)
+ PROTOTYPE: @
+ PREINIT:
+ int i;
+ char **argv;
+ CODE:
+ rrdcode(rrd_dump);
+ RETVAL = 1;
+ OUTPUT:
+ RETVAL
+
+int
+rrd_restore(...)
+ PROTOTYPE: @
+ PREINIT:
+ int i;
+ char **argv;
+ CODE:
+ rrdcode(rrd_restore);
+ RETVAL = 1;
+ OUTPUT:
+ RETVAL
+
diff --git a/program/bindings/perl-shared/ntmake.pl b/program/bindings/perl-shared/ntmake.pl
--- /dev/null
@@ -0,0 +1,27 @@
+use ExtUtils::MakeMaker;
+use Config;
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+# Run VCVARS32.BAT before generating makefile/compiling.
+WriteMakefile(
+ 'NAME' => 'RRDs',
+ 'VERSION_FROM' => 'RRDs.pm',
+# 'DEFINE' => "-DPERLPATCHLEVEL=$Config{PATCHLEVEL}",
+# keep compatible w/ ActiveState 5xx builds
+ 'DEFINE' => "-DPERLPATCHLEVEL=5",
+
+ 'INC' => '-I../../src/ "-I/Program Files/GnuWin32/include"',
+# Since we are now using GnuWin32 libraries dynamically (instead of static
+# complile with code redistributed with rrdtool), use /MD instead of /MT.
+# Yes, this means we need msvcrt.dll but GnuWin32 dlls already require it
+# and it is available on most versions of Windows.
+ 'OPTIMIZE' => '-O2 -MD',
+ 'LIBS' => '../../src/release/rrd.lib "/Program Files/GnuWin32/lib/libart_lgpl.lib" "/Program Files/GnuWin32/lib/libz.lib" "/Program Files/GnuWin32/lib/libpng.lib" "/Program Files/GnuWin32/lib/libfreetype.lib"',
+ 'realclean' => {FILES => 't/demo?.rrd t/demo?.png' },
+ ($] ge '5.005') ? (
+ 'AUTHOR' => 'Tobias Oetiker (tobi@oetiker.ch)',
+ 'ABSTRACT' => 'Round Robin Database Tool',
+ ) : ()
+
+
+);
diff --git a/program/bindings/perl-shared/t/base.t b/program/bindings/perl-shared/t/base.t
--- /dev/null
@@ -0,0 +1,171 @@
+#! /usr/bin/perl
+
+BEGIN { $| = 1; print "1..7\n"; }
+END {
+ print "not ok 1\n" unless $loaded;
+ unlink "demo.rrd";
+}
+
+sub ok
+{
+ my($what, $result) = @_ ;
+ $ok_count++;
+ print "not " unless $result;
+ print "ok $ok_count $what\n";
+}
+
+use strict;
+use vars qw(@ISA $loaded);
+
+use RRDs;
+$loaded = 1;
+my $ok_count = 1;
+
+ok("loading",1);
+
+######################### End of black magic.
+
+my $STEP = 100;
+my $RUNS = 500;
+my $GRUNS = 4;
+my $RRD1 = "demo1.rrd";
+my $RRD2 = "demo2.rrd";
+my $PNG1 = "demo1.png";
+my $PNG2 = "demo2.png";
+my $time = 30*int(time/30);
+my $START = $time-$RUNS*$STEP;
+
+my @options = ("-b", $START, "-s", $STEP,
+ "DS:a:GAUGE:2000:U:U",
+ "DS:b:GAUGE:200:U:U",
+ "DS:c:GAUGE:200:U:U",
+ "DS:d:GAUGE:200:U:U",
+ "DS:e:DERIVE:200:U:U",
+ "RRA:AVERAGE:0.5:1:5000",
+ "RRA:AVERAGE:0.5:10:500");
+
+print "* Creating RRD $RRD1 starting at $time.\n\n";
+RRDs::create $RRD1, @options;
+
+my $ERROR = RRDs::error;
+ok("create 1", !$ERROR); # 2
+if ($ERROR) {
+ die "$0: unable to create `$RRD1': $ERROR\n";
+}
+
+print "* Creating RRD $RRD2 starting at $time.\n\n";
+RRDs::create $RRD2, @options;
+
+$ERROR= RRDs::error;
+ok("create 2",!$ERROR); # 3
+if ($ERROR) {
+ die "$0: unable to create `$RRD2': $ERROR\n";
+}
+
+my $last = RRDs::last $RRD1;
+if ($ERROR = RRDs::error) {
+ die "$0: unable to get last `$RRD1': $ERROR\n";
+}
+ok("last 1", $last == $START); # 4
+
+$last = RRDs::last $RRD2;
+if ($ERROR = RRDs::error) {
+ die "$0: unable to get last `$RRD2': $ERROR\n";
+}
+ok("last 2", $last == $START); # 5
+
+print "* Filling $RRD1 and $RRD2 with $RUNS*5 values. One moment please ...\n";
+print "* If you are running over NFS this will take *MUCH* longer\n\n";
+
+srand(int($time / 100));
+
+@options = ();
+
+my $counter = 1e7;
+for (my $t=$START+1;
+ $t<$START+$STEP*$RUNS;
+ $t+=$STEP+int((rand()-0.5)*7)){
+ $counter += int(2500*sin($t/2000)*$STEP);
+ my $data = (1000+500*sin($t/1000)).":".
+ (1000+900*sin($t/2330)).":".
+ (2000*cos($t/1550)).":".
+ (3220*sin($t/3420)).":$counter";
+ push(@options, "$t:$data");
+ RRDs::update $RRD1, "$t:$data";
+ if ($ERROR = RRDs::error) {
+ warn "$0: unable to update `$RRD1': $ERROR\n";
+ }
+}
+
+ok("update 1",!$ERROR); # 3
+
+RRDs::update $RRD2, @options;
+
+ok("update 2",!$ERROR); # 3
+
+if ($ERROR = RRDs::error) {
+ die "$0: unable to update `$RRD2': $ERROR\n";
+}
+
+print "* Creating $GRUNS graphs: $PNG1 & $PNG2\n\n";
+my $now = $time;
+for (my $i=0;$i<$GRUNS;$i++) {
+ my @rrd_pngs = ($RRD1, $PNG1, $RRD2, $PNG2);
+ while (@rrd_pngs) {
+ my $RRD = shift(@rrd_pngs);
+ my $PNG = shift(@rrd_pngs);
+ my ($graphret,$xs,$ys) = RRDs::graph $PNG, "--title", 'Test GRAPH',
+ "--vertical-label", 'Dummy Units', "--start", (-$RUNS*$STEP),
+ "DEF:alpha=$RRD:a:AVERAGE",
+ "DEF:beta=$RRD:b:AVERAGE",
+ "DEF:gamma=$RRD:c:AVERAGE",
+ "DEF:delta=$RRD:d:AVERAGE",
+ "DEF:epsilon=$RRD:e:AVERAGE",
+ "CDEF:calc=alpha,beta,+,2,/",
+ "AREA:alpha#0022e9:Short",
+ "STACK:beta#00b871:Demo Text",
+ "LINE1:gamma#ff0000:Line 1",
+ "LINE2:delta#888800:Line 2",
+ "LINE3:calc#00ff44:Line 3",
+ "LINE3:epsilon#000000:Line 4",
+ "HRULE:1500#ff8800:Horizontal Line at 1500",
+ "PRINT:alpha:AVERAGE:Average Alpha %1.2lf",
+ "PRINT:alpha:MIN:Min Alpha %1.2lf %s",
+ "PRINT:alpha:MIN:Min Alpha %1.2lf",
+ "PRINT:alpha:MAX:Max Alpha %1.2lf",
+ "GPRINT:calc:AVERAGE:Average calc %1.2lf %s",
+ "GPRINT:calc:AVERAGE:Average calc %1.2lf",
+ "GPRINT:calc:MAX:Max calc %1.2lf",
+ "GPRINT:calc:MIN:Min calc %1.2lf",
+ "VRULE:".($now-3600)."#008877:60 Minutes ago",
+ "VRULE:".($now-7200)."#008877:120 Minutes ago";
+
+ if ($ERROR = RRDs::error) {
+ print "ERROR: $ERROR\n";
+ } else {
+ print "Image Size: ${xs}x${ys}\n";
+ print "Graph Return:\n",(join "\n", @$graphret),"\n\n";
+ }
+ }
+}
+
+
+
+my ($start,$step,$names,$array) = RRDs::fetch $RRD1, "AVERAGE";
+$ERROR = RRDs::error;
+print "ERROR: $ERROR\n" if $ERROR ;
+print "start=$start, step=$step\n";
+print " ";
+map {printf("%12s",$_)} @$names ;
+foreach my $line (@$array){
+ print "".localtime($start)," ";
+ $start += $step;
+ foreach my $val (@$line) {
+ if (not defined $val){
+ printf "%12s", "UNKNOWN";
+ } else {
+ printf "%12.1f", $val;
+ }
+ }
+ print "\n";
+}
diff --git a/program/bindings/python/ACKNOWLEDGEMENT b/program/bindings/python/ACKNOWLEDGEMENT
--- /dev/null
@@ -0,0 +1,7 @@
+ACKNOWLEDGMENT
+==============
+
+This is a list of people who have made contributions to py-rrdtool.
+
+Matthew W. Samsonoff <mws@rochester.rr.com>
+Brian E. Gallew <geek+python@cmu.edu>
diff --git a/program/bindings/python/AUTHORS b/program/bindings/python/AUTHORS
--- /dev/null
@@ -0,0 +1 @@
+Hye-Shik Chang <perky@fallin.lv>
diff --git a/program/bindings/python/COPYING b/program/bindings/python/COPYING
--- /dev/null
@@ -0,0 +1,504 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+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 and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+\f
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+\f
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, 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 library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete 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 distribute a copy of this License along with the
+Library.
+
+ 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.
+\f
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, 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) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+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 Library, 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 Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+\f
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you 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.
+
+ If distribution of 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 satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+\f
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be 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.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+\f
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library 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.
+
+ 9. 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 Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+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 with
+this License.
+\f
+ 11. 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 Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library 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 Library.
+
+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.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library 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.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser 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 Library
+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 Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+\f
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+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
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "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
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. 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 LIBRARY 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
+LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. 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 library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library 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; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that 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 library; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/program/bindings/python/README b/program/bindings/python/README
--- /dev/null
@@ -0,0 +1,34 @@
+Based on Python-RRDtool 0.2.1
+-----------------------------
+
+The python-rrdtool provides a interface to rrdtool, the wonderful
+graphing and logging utility. This wrapper implementation has
+worked from the scratch (without SWIG), and it's under LGPL.
+
+This module have not documented yet. Please refer pydoc.
+
+
+Project
+-------
+
+Homepage: http://www.nongnu.org/py-rrdtool/
+
+Mailing Lists:
+
+ CVS Checkins py-rrdtool-cvs@nongnu.org
+ Users & Hackers py-rrdtool-users@nongnu.org
+
+
+Original Author
+---------------
+
+Hye-Shik Chang <perky@FreeBSD.org>
+
+Any comments, suggestions, and/or patches are very welcome.
+Thank you for using py-rrdtool!
+
+
+CHANGES
+-------
+2008-05-19 - tobi
+* rewrote the info method to conform to rrdtool info standard
diff --git a/program/bindings/python/rrdtoolmodule.c b/program/bindings/python/rrdtoolmodule.c
--- /dev/null
@@ -0,0 +1,570 @@
+/*
+ * rrdtoolmodule.c
+ *
+ * RRDTool Python binding
+ *
+ * Author : Hye-Shik Chang <perky@fallin.lv>
+ * Date : $Date: 2003/02/22 07:41:19 $
+ * Created : 23 May 2002
+ *
+ * $Revision: 1.14 $
+ *
+ * ==========================================================================
+ * This file is part of py-rrdtool.
+ *
+ * py-rrdtool 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * py-rrdtool is distributed in the hope that 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 Foobar; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifdef UNUSED
+#elif defined(__GNUC__)
+# define UNUSED(x) x __attribute__((unused))
+#elif defined(__LCLINT__)
+# define UNUSED(x) /*@unused@*/ x
+#else
+# define UNUSED(x) x
+#endif
+
+static const char *__version__ = "$Revision: 1.14 $";
+
+#include "Python.h"
+#include "../../src/rrd_tool.h"
+//#include "rrd.h"
+//#include "rrd_extra.h"
+
+static PyObject *ErrorObject;
+extern int optind;
+extern int opterr;
+
+/* forward declaration to keep compiler happy */
+void initrrdtool(
+ void);
+
+static int create_args(
+ char *command,
+ PyObject * args,
+ int *argc,
+ char ***argv)
+{
+ PyObject *o;
+ int size, i;
+
+ size = PyTuple_Size(args);
+ *argv = PyMem_New(char *,
+ size + 1);
+
+ if (*argv == NULL)
+ return -1;
+
+ for (i = 0; i < size; i++) {
+ o = PyTuple_GET_ITEM(args, i);
+ if (PyString_Check(o))
+ (*argv)[i + 1] = PyString_AS_STRING(o);
+ else {
+ PyMem_Del(*argv);
+ PyErr_Format(PyExc_TypeError, "argument %d must be string", i);
+ return -1;
+ }
+ }
+ (*argv)[0] = command;
+ *argc = size + 1;
+
+ /* reset getopt state */
+ opterr = optind = 0;
+
+ return 0;
+}
+
+static void destroy_args(
+ char ***argv)
+{
+ PyMem_Del(*argv);
+ *argv = NULL;
+}
+
+static char PyRRD_create__doc__[] =
+ "create(args..): Set up a new Round Robin Database\n\
+ create filename [--start|-b start time] \
+[--step|-s step] [DS:ds-name:DST:heartbeat:min:max] \
+[RRA:CF:xff:steps:rows]";
+
+static PyObject *PyRRD_create(
+ PyObject UNUSED(*self),
+ PyObject * args)
+{
+ PyObject *r;
+ char **argv;
+ int argc;
+
+ if (create_args("create", args, &argc, &argv) < 0)
+ return NULL;
+
+ if (rrd_create(argc, argv) == -1) {
+ PyErr_SetString(ErrorObject, rrd_get_error());
+ rrd_clear_error();
+ r = NULL;
+ } else {
+ Py_INCREF(Py_None);
+ r = Py_None;
+ }
+
+ destroy_args(&argv);
+ return r;
+}
+
+static char PyRRD_update__doc__[] =
+ "update(args..): Store a new set of values into the rrd\n"
+ " update filename [--template|-t ds-name[:ds-name]...] "
+ "N|timestamp:value[:value...] [timestamp:value[:value...] ...]";
+
+static PyObject *PyRRD_update(
+ PyObject UNUSED(*self),
+ PyObject * args)
+{
+ PyObject *r;
+ char **argv;
+ int argc;
+
+ if (create_args("update", args, &argc, &argv) < 0)
+ return NULL;
+
+ if (rrd_update(argc, argv) == -1) {
+ PyErr_SetString(ErrorObject, rrd_get_error());
+ rrd_clear_error();
+ r = NULL;
+ } else {
+ Py_INCREF(Py_None);
+ r = Py_None;
+ }
+
+ destroy_args(&argv);
+ return r;
+}
+
+static char PyRRD_fetch__doc__[] =
+ "fetch(args..): fetch data from an rrd.\n"
+ " fetch filename CF [--resolution|-r resolution] "
+ "[--start|-s start] [--end|-e end]";
+
+static PyObject *PyRRD_fetch(
+ PyObject UNUSED(*self),
+ PyObject * args)
+{
+ PyObject *r;
+ rrd_value_t *data, *datai;
+ unsigned long step, ds_cnt;
+ time_t start, end;
+ int argc;
+ char **argv, **ds_namv;
+
+ if (create_args("fetch", args, &argc, &argv) < 0)
+ return NULL;
+
+ if (rrd_fetch(argc, argv, &start, &end, &step,
+ &ds_cnt, &ds_namv, &data) == -1) {
+ PyErr_SetString(ErrorObject, rrd_get_error());
+ rrd_clear_error();
+ r = NULL;
+ } else {
+ /* Return :
+ ((start, end, step), (name1, name2, ...), [(data1, data2, ..), ...]) */
+ PyObject *range_tup, *dsnam_tup, *data_list, *t;
+ unsigned long i, j, row;
+ rrd_value_t dv;
+
+ row = (end - start) / step;
+
+ r = PyTuple_New(3);
+ range_tup = PyTuple_New(3);
+ dsnam_tup = PyTuple_New(ds_cnt);
+ data_list = PyList_New(row);
+ PyTuple_SET_ITEM(r, 0, range_tup);
+ PyTuple_SET_ITEM(r, 1, dsnam_tup);
+ PyTuple_SET_ITEM(r, 2, data_list);
+
+ datai = data;
+
+ PyTuple_SET_ITEM(range_tup, 0, PyInt_FromLong((long) start));
+ PyTuple_SET_ITEM(range_tup, 1, PyInt_FromLong((long) end));
+ PyTuple_SET_ITEM(range_tup, 2, PyInt_FromLong((long) step));
+
+ for (i = 0; i < ds_cnt; i++)
+ PyTuple_SET_ITEM(dsnam_tup, i, PyString_FromString(ds_namv[i]));
+
+ for (i = 0; i < row; i++) {
+ t = PyTuple_New(ds_cnt);
+ PyList_SET_ITEM(data_list, i, t);
+
+ for (j = 0; j < ds_cnt; j++) {
+ dv = *(datai++);
+ if (isnan(dv)) {
+ PyTuple_SET_ITEM(t, j, Py_None);
+ Py_INCREF(Py_None);
+ } else {
+ PyTuple_SET_ITEM(t, j, PyFloat_FromDouble((double) dv));
+ }
+ }
+ }
+
+ for (i = 0; i < ds_cnt; i++)
+ rrd_freemem(ds_namv[i]);
+ rrd_freemem(ds_namv); /* rrdtool don't use PyMem_Malloc :) */
+ rrd_freemem(data);
+ }
+
+ destroy_args(&argv);
+ return r;
+}
+
+static char PyRRD_graph__doc__[] =
+ "graph(args..): Create a graph based on data from one or several RRD\n"
+ " graph filename [-s|--start seconds] "
+ "[-e|--end seconds] [-x|--x-grid x-axis grid and label] "
+ "[-y|--y-grid y-axis grid and label] [--alt-y-grid] [--alt-y-mrtg] "
+ "[--alt-autoscale] [--alt-autoscale-max] [--units-exponent] value "
+ "[-v|--vertical-label text] [-w|--width pixels] [-h|--height pixels] "
+ "[-i|--interlaced] "
+ "[-f|--imginfo formatstring] [-a|--imgformat GIF|PNG|GD] "
+ "[-B|--background value] [-O|--overlay value] "
+ "[-U|--unit value] [-z|--lazy] [-o|--logarithmic] "
+ "[-u|--upper-limit value] [-l|--lower-limit value] "
+ "[-g|--no-legend] [-r|--rigid] [--step value] "
+ "[-b|--base value] [-c|--color COLORTAG#rrggbb] "
+ "[-t|--title title] [DEF:vname=rrd:ds-name:CF] "
+ "[CDEF:vname=rpn-expression] [PRINT:vname:CF:format] "
+ "[GPRINT:vname:CF:format] [COMMENT:text] "
+ "[HRULE:value#rrggbb[:legend]] [VRULE:time#rrggbb[:legend]] "
+ "[LINE{1|2|3}:vname[#rrggbb[:legend]]] "
+ "[AREA:vname[#rrggbb[:legend]]] " "[STACK:vname[#rrggbb[:legend]]]";
+
+static PyObject *PyRRD_graph(
+ PyObject UNUSED(*self),
+ PyObject * args)
+{
+ PyObject *r;
+ char **argv, **calcpr;
+ int argc, xsize, ysize, i;
+ double ymin, ymax;
+
+ if (create_args("graph", args, &argc, &argv) < 0)
+ return NULL;
+
+ if (rrd_graph(argc, argv, &calcpr, &xsize, &ysize, NULL, &ymin, &ymax) ==
+ -1) {
+ PyErr_SetString(ErrorObject, rrd_get_error());
+ rrd_clear_error();
+ r = NULL;
+ } else {
+ r = PyTuple_New(3);
+
+ PyTuple_SET_ITEM(r, 0, PyInt_FromLong((long) xsize));
+ PyTuple_SET_ITEM(r, 1, PyInt_FromLong((long) ysize));
+
+ if (calcpr) {
+ PyObject *e, *t;
+
+ e = PyList_New(0);
+ PyTuple_SET_ITEM(r, 2, e);
+
+ for (i = 0; calcpr[i]; i++) {
+ t = PyString_FromString(calcpr[i]);
+ PyList_Append(e, t);
+ Py_DECREF(t);
+ rrd_freemem(calcpr[i]);
+ }
+ rrd_freemem(calcpr);
+ } else {
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(r, 2, Py_None);
+ }
+ }
+
+ destroy_args(&argv);
+ return r;
+}
+
+static char PyRRD_tune__doc__[] =
+ "tune(args...): Modify some basic properties of a Round Robin Database\n"
+ " tune filename [--heartbeat|-h ds-name:heartbeat] "
+ "[--minimum|-i ds-name:min] [--maximum|-a ds-name:max] "
+ "[--data-source-type|-d ds-name:DST] [--data-source-rename|-r old-name:new-name]";
+
+static PyObject *PyRRD_tune(
+ PyObject UNUSED(*self),
+ PyObject * args)
+{
+ PyObject *r;
+ char **argv;
+ int argc;
+
+ if (create_args("tune", args, &argc, &argv) < 0)
+ return NULL;
+
+ if (rrd_tune(argc, argv) == -1) {
+ PyErr_SetString(ErrorObject, rrd_get_error());
+ rrd_clear_error();
+ r = NULL;
+ } else {
+ Py_INCREF(Py_None);
+ r = Py_None;
+ }
+
+ destroy_args(&argv);
+ return r;
+}
+
+static char PyRRD_first__doc__[] =
+ "first(filename): Return the timestamp of the first data sample in an RRD";
+
+static PyObject *PyRRD_first(
+ PyObject UNUSED(*self),
+ PyObject * args)
+{
+ PyObject *r;
+ int argc, ts;
+ char **argv;
+
+ if (create_args("first", args, &argc, &argv) < 0)
+ return NULL;
+
+ if ((ts = rrd_first(argc, argv)) == -1) {
+ PyErr_SetString(ErrorObject, rrd_get_error());
+ rrd_clear_error();
+ r = NULL;
+ } else
+ r = PyInt_FromLong((long) ts);
+
+ destroy_args(&argv);
+ return r;
+}
+
+static char PyRRD_last__doc__[] =
+ "last(filename): Return the timestamp of the last data sample in an RRD";
+
+static PyObject *PyRRD_last(
+ PyObject UNUSED(*self),
+ PyObject * args)
+{
+ PyObject *r;
+ int argc, ts;
+ char **argv;
+
+ if (create_args("last", args, &argc, &argv) < 0)
+ return NULL;
+
+ if ((ts = rrd_last(argc, argv)) == -1) {
+ PyErr_SetString(ErrorObject, rrd_get_error());
+ rrd_clear_error();
+ r = NULL;
+ } else
+ r = PyInt_FromLong((long) ts);
+
+ destroy_args(&argv);
+ return r;
+}
+
+static char PyRRD_resize__doc__[] =
+ "resize(args...): alters the size of an RRA.\n"
+ " resize filename rra-num GROW|SHRINK rows";
+
+static PyObject *PyRRD_resize(
+ PyObject UNUSED(*self),
+ PyObject * args)
+{
+ PyObject *r;
+ char **argv;
+ int argc, ts;
+
+ if (create_args("resize", args, &argc, &argv) < 0)
+ return NULL;
+
+ if ((ts = rrd_resize(argc, argv)) == -1) {
+ PyErr_SetString(ErrorObject, rrd_get_error());
+ rrd_clear_error();
+ r = NULL;
+ } else {
+ Py_INCREF(Py_None);
+ r = Py_None;
+ }
+
+ destroy_args(&argv);
+ return r;
+}
+
+static PyObject *PyDict_FromInfo(
+ rrd_info_t * data)
+{
+ PyObject *r;
+
+ r = PyDict_New();
+ while (data) {
+ PyObject *val = NULL;
+
+ switch (data->type) {
+ case RD_I_VAL:
+ val = isnan(data->value.u_val)
+ ? (Py_INCREF(Py_None), Py_None)
+ : PyFloat_FromDouble(data->value.u_val);
+ break;
+ case RD_I_CNT:
+ val = PyLong_FromUnsignedLong(data->value.u_cnt);
+ break;
+ case RD_I_INT:
+ val = PyLong_FromLong(data->value.u_int);
+ break;
+ case RD_I_STR:
+ val = PyString_FromString(data->value.u_str);
+ break;
+ case RD_I_BLO:
+ val =
+ PyString_FromStringAndSize((char *) data->value.u_blo.ptr,
+ data->value.u_blo.size);
+ break;
+ }
+ if (val) {
+ PyDict_SetItemString(r, data->key, val);
+ }
+ data = data->next;
+ }
+ return r;
+}
+
+static char PyRRD_info__doc__[] =
+ "info(filename): extract header information from an rrd";
+
+static PyObject *PyRRD_info(
+ PyObject UNUSED(*self),
+ PyObject * args)
+{
+ PyObject *r;
+ int argc;
+ char **argv;
+ rrd_info_t *data;
+
+ if (create_args("info", args, &argc, &argv) < 0)
+ return NULL;
+
+ if ((data = rrd_info(argc, argv)) == NULL) {
+ PyErr_SetString(ErrorObject, rrd_get_error());
+ rrd_clear_error();
+ return NULL;
+ }
+ r = PyDict_FromInfo(data);
+ rrd_info_free(data);
+ return r;
+}
+
+static char PyRRD_graphv__doc__[] =
+ "graphv is called in the same manner as graph";
+
+static PyObject *PyRRD_graphv(
+ PyObject UNUSED(*self),
+ PyObject * args)
+{
+ PyObject *r;
+ int argc;
+ char **argv;
+ rrd_info_t *data;
+
+ if (create_args("graphv", args, &argc, &argv) < 0)
+ return NULL;
+
+ if ((data = rrd_graph_v(argc, argv)) == NULL) {
+ PyErr_SetString(ErrorObject, rrd_get_error());
+ rrd_clear_error();
+ return NULL;
+ }
+ r = PyDict_FromInfo(data);
+ rrd_info_free(data);
+ return r;
+}
+
+static char PyRRD_updatev__doc__[] =
+ "updatev is called in the same manner as update";
+
+static PyObject *PyRRD_updatev(
+ PyObject UNUSED(*self),
+ PyObject * args)
+{
+ PyObject *r;
+ int argc;
+ char **argv;
+ rrd_info_t *data;
+
+ if (create_args("updatev", args, &argc, &argv) < 0)
+ return NULL;
+
+ if ((data = rrd_update_v(argc, argv)) == NULL) {
+ PyErr_SetString(ErrorObject, rrd_get_error());
+ rrd_clear_error();
+ return NULL;
+ }
+ r = PyDict_FromInfo(data);
+ rrd_info_free(data);
+ return r;
+}
+
+/* List of methods defined in the module */
+#define meth(name, func, doc) {name, (PyCFunction)func, METH_VARARGS, doc}
+
+static PyMethodDef _rrdtool_methods[] = {
+ meth("create", PyRRD_create, PyRRD_create__doc__),
+ meth("update", PyRRD_update, PyRRD_update__doc__),
+ meth("fetch", PyRRD_fetch, PyRRD_fetch__doc__),
+ meth("graph", PyRRD_graph, PyRRD_graph__doc__),
+ meth("tune", PyRRD_tune, PyRRD_tune__doc__),
+ meth("first", PyRRD_first, PyRRD_first__doc__),
+ meth("last", PyRRD_last, PyRRD_last__doc__),
+ meth("resize", PyRRD_resize, PyRRD_resize__doc__),
+ meth("info", PyRRD_info, PyRRD_info__doc__),
+ meth("graphv", PyRRD_graphv, PyRRD_graphv__doc__),
+ meth("updatev", PyRRD_updatev, PyRRD_updatev__doc__),
+ {NULL, NULL, 0, NULL}
+};
+
+#define SET_INTCONSTANT(dict, value) \
+ t = PyInt_FromLong((long)value); \
+ PyDict_SetItemString(dict, #value, t); \
+ Py_DECREF(t);
+#define SET_STRCONSTANT(dict, value) \
+ t = PyString_FromString(value); \
+ PyDict_SetItemString(dict, #value, t); \
+ Py_DECREF(t);
+
+/* Initialization function for the module */
+void initrrdtool(
+ void)
+{
+ PyObject *m, *d, *t;
+
+ /* Create the module and add the functions */
+ m = Py_InitModule("rrdtool", _rrdtool_methods);
+
+ /* Add some symbolic constants to the module */
+ d = PyModule_GetDict(m);
+
+ SET_STRCONSTANT(d, __version__);
+ ErrorObject = PyErr_NewException("rrdtool.error", NULL, NULL);
+ PyDict_SetItemString(d, "error", ErrorObject);
+
+ /* Check for errors */
+ if (PyErr_Occurred())
+ Py_FatalError("can't initialize the rrdtool module");
+}
+
+/*
+ * $Id: _rrdtoolmodule.c,v 1.14 2003/02/22 07:41:19 perky Exp $
+ * ex: ts=8 sts=4 et
+ */
diff --git a/program/bindings/python/setup.py b/program/bindings/python/setup.py
--- /dev/null
@@ -0,0 +1,55 @@
+#! /usr/bin/env python
+#
+# setup.py
+#
+# py-rrdtool distutil setup
+#
+# Author : Hye-Shik Chang <perky@fallin.lv>
+# Date : $Date: 2003/02/14 02:38:16 $
+# Created : 24 May 2002
+#
+# $Revision: 1.7 $
+#
+# ==========================================================================
+# This file is part of py-rrdtool.
+#
+# py-rrdtool 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; either version 2 of the License, or
+# (at your option) any later version.
+#
+# py-rrdtool is distributed in the hope that 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 Foobar; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+from distutils.core import setup, Extension
+import sys, os
+
+RRDBASE = os.environ.get('LOCALBASE', '../../src')
+library_dir = os.environ.get('BUILDLIBDIR', os.path.join(RRDBASE, '.libs'))
+include_dir = os.environ.get('INCDIR', RRDBASE)
+
+setup(name = "py-rrdtool",
+ version = "0.2.1",
+ description = "Python Interface to RRDTool",
+ author = "Hye-Shik Chang",
+ author_email = "perky@fallin.lv",
+ license = "LGPL",
+ url = "http://oss.oetiker.ch/rrdtool",
+ #packages = ['rrdtool'],
+ ext_modules = [
+ Extension(
+ "rrdtoolmodule",
+ ["rrdtoolmodule.c"],
+ libraries=['rrd'],
+ library_dirs=[library_dir],
+ include_dirs=[include_dir],
+ )
+ ]
+)
diff --git a/program/bindings/ruby/CHANGES b/program/bindings/ruby/CHANGES
--- /dev/null
@@ -0,0 +1,3 @@
+2006-07-25 Loïs Lherbier
+ add y_min and y_max parameters to rrd_graph
+ update test.rb to send only strings to RRD.fetch
diff --git a/program/bindings/ruby/README b/program/bindings/ruby/README
--- /dev/null
@@ -0,0 +1,22 @@
+#
+# ruby librrd bindings
+# author: Miles Egan <miles@caddr.com>
+#
+
+- Introduction
+
+This module provides ruby bindings for librrd, with functionality
+comparable to the native perl bindings. See test.rb for a script that
+exercises all ruby-librrd functionality.
+
+- Installation
+
+Installation is standard. Simply run:
+
+ruby extconf.rb
+make
+make install
+
+I hope this works for you. Please let me know if you have any
+problems or suggestions. Someday when I'm feeling less lazy I'll
+actually document this thing. Thanks to Tobi for rrdtool!
diff --git a/program/bindings/ruby/extconf.rb b/program/bindings/ruby/extconf.rb
--- /dev/null
@@ -0,0 +1,18 @@
+# $Id: extconf.rb,v 1.2 2001/11/28 18:30:16 miles Exp $
+# Lost ticket pays maximum rate.
+
+require 'mkmf'
+
+if /linux/ =~ RUBY_PLATFORM
+ $LDFLAGS += '-Wl,--rpath -Wl,$(EPREFIX)/lib'
+elsif /solaris/ =~ RUBY_PLATFORM
+ $LDFLAGS += '-R$(EPREFIX)/lib'
+elsif /hpux/ =~ RUBY_PLATFORM
+ $LDFLAGS += '+b$(EPREFIX)/lib'
+elsif /aix/ =~ RUBY_PLATFORM
+ $LDFLAGS += '-Wl,-blibpath:$(EPREFIX)/lib'
+end
+
+dir_config("rrd","../../src","../../src/.libs")
+have_library("rrd", "rrd_create")
+create_makefile("RRD")
diff --git a/program/bindings/ruby/main.c b/program/bindings/ruby/main.c
--- /dev/null
@@ -0,0 +1,324 @@
+/* $Id$
+ * Substantial penalty for early withdrawal.
+ */
+
+#include <unistd.h>
+#include <ruby.h>
+#include "../../src/rrd_tool.h"
+
+typedef struct string_arr_t {
+ int len;
+ char **strings;
+} string_arr;
+
+VALUE mRRD;
+VALUE rb_eRRDError;
+
+typedef int (
+ *RRDFUNC) (
+ int argc,
+ char **argv);
+
+#define RRD_CHECK_ERROR \
+ if (rrd_test_error()) \
+ rb_raise(rb_eRRDError, rrd_get_error()); \
+ rrd_clear_error();
+
+string_arr string_arr_new(
+ VALUE rb_strings)
+{
+ string_arr a;
+ char buf[64];
+ int i;
+
+ Check_Type(rb_strings, T_ARRAY);
+ a.len = RARRAY(rb_strings)->len + 1;
+
+ a.strings = malloc(a.len * sizeof(char *));
+ a.strings[0] = "dummy"; /* first element is a dummy element */
+
+ for (i = 0; i < a.len - 1; i++) {
+ VALUE v = rb_ary_entry(rb_strings, i);
+
+ switch (TYPE(v)) {
+ case T_STRING:
+ a.strings[i + 1] = strdup(STR2CSTR(v));
+ break;
+ case T_FIXNUM:
+ snprintf(buf, 63, "%d", FIX2INT(v));
+ a.strings[i + 1] = strdup(buf);
+ break;
+ default:
+ rb_raise(rb_eTypeError,
+ "invalid argument - %s, expected T_STRING or T_FIXNUM on index %d",
+ rb_class2name(CLASS_OF(v)), i);
+ break;
+ }
+ }
+
+ return a;
+}
+
+void string_arr_delete(
+ string_arr a)
+{
+ int i;
+
+ /* skip dummy first entry */
+ for (i = 1; i < a.len; i++) {
+ free(a.strings[i]);
+ }
+
+ free(a.strings);
+}
+
+void reset_rrd_state(
+ )
+{
+ optind = 0;
+ opterr = 0;
+ rrd_clear_error();
+}
+
+/* Simple Calls */
+
+VALUE rrd_call(
+ RRDFUNC func,
+ VALUE args)
+{
+ string_arr a;
+
+ a = string_arr_new(args);
+ reset_rrd_state();
+ func(a.len, a.strings);
+ string_arr_delete(a);
+
+ RRD_CHECK_ERROR return Qnil;
+}
+
+VALUE rb_rrd_create(
+ VALUE self,
+ VALUE args)
+{
+ return rrd_call(rrd_create, args);
+}
+
+VALUE rb_rrd_dump(
+ VALUE self,
+ VALUE args)
+{
+ return rrd_call(rrd_dump, args);
+}
+
+VALUE rb_rrd_resize(
+ VALUE self,
+ VALUE args)
+{
+ return rrd_call(rrd_resize, args);
+}
+
+VALUE rb_rrd_restore(
+ VALUE self,
+ VALUE args)
+{
+ return rrd_call(rrd_restore, args);
+}
+
+VALUE rb_rrd_tune(
+ VALUE self,
+ VALUE args)
+{
+ return rrd_call(rrd_tune, args);
+}
+
+VALUE rb_rrd_update(
+ VALUE self,
+ VALUE args)
+{
+ return rrd_call(rrd_update, args);
+}
+
+
+/* Calls Returning Data via the Info Interface */
+
+VALUE rb_rrd_infocall(
+ RRDFUNC func,
+ VALUE args)
+{
+ string_arr a;
+ rrd_info_t *p, *data;
+ VALUE result;
+
+ a = string_arr_new(args);
+ data = func(a.len, a.strings);
+ string_arr_delete(a);
+
+ RRD_CHECK_ERROR result = rb_hash_new();
+
+ p = data;
+ while (data) {
+ VALUE key = rb_str_new2(data->key);
+
+ switch (data->type) {
+ case RD_I_VAL:
+ if (isnan(data->value.u_val)) {
+ rb_hash_aset(result, key, Qnil);
+ } else {
+ rb_hash_aset(result, key, rb_float_new(data->value.u_val));
+ }
+ break;
+ case RD_I_CNT:
+ rb_hash_aset(result, key, INT2FIX(data->value.u_cnt));
+ break;
+ case RD_I_STR:
+ rb_hash_aset(result, key, rb_str_new2(data->value.u_str));
+ break;
+ case RD_I_BLO:
+ rb_hash_aset(result, key,
+ rb_str_new(data->value.u_blo.ptr,
+ data->value.u_blo.size));
+ break;
+ }
+ data = data->next;
+ }
+ rrd_info_free(p);
+ return result;
+}
+
+VALUE rb_rrd_info(
+ VALUE self,
+ VALUE args)
+{
+ return rb_rrd_infocall(rrd_info, args);
+}
+
+VALUE rb_rrd_updatev(
+ VALUE self,
+ VALUE args)
+{
+ return rb_rrd_infocall(rrd_update_v, args);
+}
+
+VALUE rb_rrd_graphv(
+ VALUE self,
+ VALUE args)
+{
+ return rb_rrd_infocall(rrd_graph_v, args);
+}
+
+
+/* Other Calls */
+
+VALUE rb_rrd_fetch(
+ VALUE self,
+ VALUE args)
+{
+ string_arr a;
+ unsigned long i, j, k, step, ds_cnt;
+ rrd_value_t *raw_data;
+ char **raw_names;
+ VALUE data, names, result;
+ time_t start, end;
+
+ a = string_arr_new(args);
+ reset_rrd_state();
+ rrd_fetch(a.len, a.strings, &start, &end, &step, &ds_cnt, &raw_names,
+ &raw_data);
+ string_arr_delete(a);
+
+ RRD_CHECK_ERROR names = rb_ary_new();
+
+ for (i = 0; i < ds_cnt; i++) {
+ rb_ary_push(names, rb_str_new2(raw_names[i]));
+ rrd_freemem(raw_names[i]);
+ }
+ rrd_freemem(raw_names);
+
+ k = 0;
+ data = rb_ary_new();
+ for (i = start; i <= end; i += step) {
+ VALUE line = rb_ary_new2(ds_cnt);
+
+ for (j = 0; j < ds_cnt; j++) {
+ rb_ary_store(line, j, rb_float_new(raw_data[k]));
+ k++;
+ }
+ rb_ary_push(data, line);
+ }
+ rrd_freemem(raw_data);
+
+ result = rb_ary_new2(5);
+ rb_ary_store(result, 0, INT2NUM(start));
+ rb_ary_store(result, 1, INT2NUM(end));
+ rb_ary_store(result, 2, names);
+ rb_ary_store(result, 3, data);
+ rb_ary_store(result, 4, INT2FIX(step));
+ return result;
+}
+
+VALUE rb_rrd_graph(
+ VALUE self,
+ VALUE args)
+{
+ string_arr a;
+ char **calcpr, **p;
+ VALUE result, print_results;
+ int xsize, ysize;
+ double ymin, ymax;
+
+ a = string_arr_new(args);
+ reset_rrd_state();
+ rrd_graph(a.len, a.strings, &calcpr, &xsize, &ysize, NULL, &ymin, &ymax);
+ string_arr_delete(a);
+
+ RRD_CHECK_ERROR result = rb_ary_new2(3);
+
+ print_results = rb_ary_new();
+ p = calcpr;
+ for (p = calcpr; p && *p; p++) {
+ rb_ary_push(print_results, rb_str_new2(*p));
+ rrd_freemem(*p);
+ }
+ rrd_freemem(calcpr);
+ rb_ary_store(result, 0, print_results);
+ rb_ary_store(result, 1, INT2FIX(xsize));
+ rb_ary_store(result, 2, INT2FIX(ysize));
+ return result;
+}
+
+
+VALUE rb_rrd_last(
+ VALUE self,
+ VALUE args)
+{
+ string_arr a;
+ time_t last;
+
+ a = string_arr_new(args);
+ reset_rrd_state();
+ last = rrd_last(a.len, a.strings);
+ string_arr_delete(a);
+
+ RRD_CHECK_ERROR
+ return rb_funcall(rb_cTime, rb_intern("at"), 1, INT2FIX(last));
+}
+
+void Init_RRD(
+ )
+{
+ mRRD = rb_define_module("RRD");
+ rb_eRRDError = rb_define_class("RRDError", rb_eStandardError);
+
+ rb_define_module_function(mRRD, "create", rb_rrd_create, -2);
+ rb_define_module_function(mRRD, "dump", rb_rrd_dump, -2);
+ rb_define_module_function(mRRD, "fetch", rb_rrd_fetch, -2);
+ rb_define_module_function(mRRD, "graph", rb_rrd_graph, -2);
+ rb_define_module_function(mRRD, "last", rb_rrd_last, -2);
+ rb_define_module_function(mRRD, "resize", rb_rrd_resize, -2);
+ rb_define_module_function(mRRD, "restore", rb_rrd_restore, -2);
+ rb_define_module_function(mRRD, "tune", rb_rrd_tune, -2);
+ rb_define_module_function(mRRD, "update", rb_rrd_update, -2);
+ rb_define_module_function(mRRD, "info", rb_rrd_info, -2);
+ rb_define_module_function(mRRD, "updatev", rb_rrd_updatev, -2);
+ rb_define_module_function(mRRD, "graphv", rb_rrd_graphv, -2);
+}
diff --git a/program/bindings/ruby/test.rb b/program/bindings/ruby/test.rb
--- /dev/null
@@ -0,0 +1,52 @@
+#!/usr/bin/env ruby
+# $Id: test.rb,v 1.2 2002/10/22 17:34:00 miles Exp $
+# Driver does not carry cash.
+
+$: << '/scratch/rrd12build/lib/ruby/1.8/i386-linux/'
+
+require "RRD"
+
+name = "test"
+rrd = "#{name}.rrd"
+start = Time.now.to_i
+
+puts "creating #{rrd}"
+RRD.create(
+ rrd,
+ "--start", "#{start - 1}",
+ "--step", "300",
+ "DS:a:GAUGE:600:U:U",
+ "DS:b:GAUGE:600:U:U",
+ "RRA:AVERAGE:0.5:1:300")
+puts
+
+puts "updating #{rrd}"
+start.to_i.step(start.to_i + 300 * 300, 300) { |i|
+ RRD.update(rrd, "#{i}:#{rand(100)}:#{Math.sin(i / 800) * 50 + 50}")
+}
+puts
+
+puts "fetching data from #{rrd}"
+(fstart, fend, data) = RRD.fetch(rrd, "--start", start.to_s, "--end", (start + 300 * 300).to_s, "AVERAGE")
+puts "got #{data.length} data points from #{fstart} to #{fend}"
+puts
+
+puts "generating graph #{name}.png"
+RRD.graph(
+ "#{name}.png",
+ "--title", " RubyRRD Demo",
+ "--start", "#{start+3600}",
+ "--end", "start + 1000 min",
+ "--interlace",
+ "--imgformat", "PNG",
+ "--width=450",
+ "DEF:a=#{rrd}:a:AVERAGE",
+ "DEF:b=#{rrd}:b:AVERAGE",
+ "CDEF:line=TIME,2400,%,300,LT,a,UNKN,IF",
+ "AREA:b#00b6e4:beta",
+ "AREA:line#0022e9:alpha",
+ "LINE3:line#ff0000")
+puts
+
+print "This script has created #{name}.png in the current directory\n";
+print "This demonstrates the use of the TIME and % RPN operators\n";
diff --git a/program/bindings/tcl/Makefile.am b/program/bindings/tcl/Makefile.am
--- /dev/null
@@ -0,0 +1,57 @@
+
+EXTRA_DIST = README tclrrd.c
+
+VERSION = @VERSION@
+
+AM_CFLAGS = @CFLAGS@
+
+TCL_PREFIX = @TCL_PREFIX@
+TCL_SHLIB_LD = @TCL_SHLIB_LD@
+TCL_SHLIB_CFLAGS = @TCL_SHLIB_CFLAGS@
+TCL_SHLIB_SUFFIX = @TCL_SHLIB_SUFFIX@
+TCL_PACKAGE_PATH = @TCL_PACKAGE_PATH@
+TCL_LD_SEARCH_FLAGS = @TCL_LD_SEARCH_FLAGS@
+TCL_STUB_LIB_SPEC = @TCL_STUB_LIB_SPEC@
+TCL_INCLUDE_SPEC = @TCL_INCLUDE_SPEC@
+
+CLEANFILES = tclrrd.o tclrrd.so
+
+SRC_DIR = $(top_srcdir)/src
+AM_CPPFLAGS = $(TCL_INCLUDE_SPEC) -I$(SRC_DIR) -DUSE_TCL_STUBS
+LIBDIRS = -L$(top_builddir)/src/.libs -L$(top_builddir)/src -L$(libdir)
+LIB_RUNTIME_DIR = $(libdir)
+
+if BUILD_TCL_SITE
+tclpkgdir = @TCL_PACKAGE_DIR@
+tclpkg_DATA = pkgIndex.tcl
+tclpkg_SCRIPTS = ifOctets.tcl
+else
+pkglib_DATA = pkgIndex.tcl
+pkglib_SCRIPTS = ifOctets.tcl
+endif
+
+# Automake doen't like `tclrrd$(VERSION)$(TCL_SHLIB_SUFFIX)' as
+# library name. So we build and install this library `by hand'.
+#
+# We do, however, specify a lib_LIBRARIES target such that
+# automake creates the directory (if neecessary).
+#
+TCL_RRD_LIB = tclrrd$(VERSION)$(TCL_SHLIB_SUFFIX)
+
+lib_LIBRARIES =
+
+all-local: $(TCL_RRD_LIB)
+
+$(TCL_RRD_LIB): tclrrd.o
+ $(TCL_SHLIB_LD) $(TCL_LD_SEARCH_FLAGS) $(LIBDIRS) $< -o $@ -lrrd_th -lm $(TCL_STUB_LIB_SPEC) $(LDFLAGS) $(LIBS)
+
+tclrrd.o: tclrrd.c
+ $(CC) $(AM_CFLAGS) $(CFLAGS) $(TCL_SHLIB_CFLAGS) $(AM_CPPFLAGS) -c tclrrd.c -DVERSION=\"$(VERSION)\"
+
+pkgIndex.tcl:
+ echo "package ifneeded Rrd $(VERSION) \"load $(libdir)/tclrrd$(VERSION)[info sharedlibextension]\"" > $@
+
+install-exec-local: $(TCL_RRD_LIB)
+ @$(NORMAL_INSTALL)
+ $(INSTALL_PROGRAM) $(TCL_RRD_LIB) $(DESTDIR)$(libdir)/$(TCL_RRD_LIB)
+
diff --git a/program/bindings/tcl/README b/program/bindings/tcl/README
--- /dev/null
@@ -0,0 +1,31 @@
+TCLRRD -- A TCL interpreter extension to access the RRD library,
+ contributed to Tobias Oetiker's RRD tools.
+
+Copyright (c) 1999,2000 Frank Strauss, Technical University of Braunschweig.
+
+See the file "COPYING" for information on usage and redistribution
+of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+
+TCLRRD adds a dynamically loadable package to the Tcl 8.x interpreter
+to access all RRD functions as of RRDtool 1.0.13. All command names
+and arguments are equal to those of RRDtool. They are assigned to the
+namespace `Rrd', e.g. `Rrd::create'. Return values are a bit
+different from plain RRDtool behavior to enable more native Tcl
+usage. Errors are mapped to the TCL_ERROR return code together with
+the RRD error strings.
+
+TCLRRD makes it easy to combine RRD use with advanced SNMP functionality
+of scotty (http://wwwsnmp.cs.utwente.nl/~schoenw/scotty/). E.g., it's easy
+to use some scotty code to get the counters of some interfaces by their
+interface name and then use Rrd::update to store the values. Furthermore,
+data source types (see RRD::create documentation) and integer value ranges
+could be easily retrieved from MIB information.
+
+TCLRRD has been written on a Linux system for use with Tcl 8.x. It should
+work on many other platforms, although it has not been tested. There are
+no fool proof installation procedures. Take a look at Makefile.am and
+adapt it, if required.
+
+TCLRRD has been written for RRD 1.0.13.
+
+ Frank Strauss <strauss@ibr.cs.tu-bs.de>, 09-Mar-2000
diff --git a/program/bindings/tcl/ifOctets.tcl.in b/program/bindings/tcl/ifOctets.tcl.in
--- /dev/null
@@ -0,0 +1,45 @@
+#!/bin/sh
+# the next line restarts using tclsh -*- tcl -*- \
+exec tclsh@TCL_VERSION@ "$0" "$@"
+
+#package require Tnm 3.0
+package require Rrd @VERSION@
+
+set rrdfile "[lindex $argv 0]-[lindex $argv 1].rrd"
+
+# create rrdfile if not yet existent
+if {[file exists $rrdfile] == 0} {
+ Rrd::create $rrdfile --step 5 \
+ DS:inOctets:COUNTER:10:U:U DS:outOctets:COUNTER:10:U:U \
+ RRA:AVERAGE:0.5:1:12
+}
+
+# get an snmp session context
+set session [Tnm::snmp generator -address [lindex $argv 0]]
+
+# walk through the ifDescr column to find the right interface
+$session walk descr IF-MIB!ifDescr {
+
+ # is this the right interface?
+ if {"[Tnm::snmp value $descr 0]" == "[lindex $argv 1]"} {
+
+ # get the instance part of this table row
+ set inst [lindex [Tnm::mib split [Tnm::snmp oid $descr 0]] 1]
+
+ # get the two interface's octet counter values
+ set in [lindex [lindex [$session get IF-MIB!ifInOctets.$inst] 0] 2]
+ set out [lindex [lindex [$session get IF-MIB!ifOutOctets.$inst] 0] 2]
+
+ # write the values to the rrd
+ puts "$in $out"
+ Rrd::update $rrdfile --template inOctets:outOctets N:$in:$out
+
+ Rrd::graph gaga.png --title "gaga" \
+ DEF:in=$rrdfile:inOctets:AVERAGE \
+ DEF:out=$rrdfile:outOctets:AVERAGE \
+ AREA:in#0000FF:inOctets \
+ LINE2:out#00C000:outOctets
+
+ #puts [Rrd::fetch $rrdfile AVERAGE]
+ }
+}
diff --git a/program/bindings/tcl/tclrrd.c b/program/bindings/tcl/tclrrd.c
--- /dev/null
@@ -0,0 +1,729 @@
+/*
+ * tclrrd.c -- A TCL interpreter extension to access the RRD library.
+ *
+ * Copyright (c) 1999,2000 Frank Strauss, Technical University of Braunschweig.
+ *
+ * Thread-safe code copyright (c) 2005 Oleg Derevenetz, CenterTelecom Voronezh ISP.
+ *
+ * See the file "COPYING" for information on usage and redistribution
+ * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ *
+ * $Id$
+ */
+
+
+
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <tcl.h>
+#include <stdlib.h>
+#include "../../src/rrd_tool.h"
+#include "../../src/rrd_format.h"
+
+/* support pre-8.4 tcl */
+
+#ifndef CONST84
+# define CONST84
+#endif
+
+extern int Tclrrd_Init(
+ Tcl_Interp *interp);
+extern int Tclrrd_SafeInit(
+ Tcl_Interp *interp);
+
+
+/*
+ * some rrd_XXX() and new thread-safe versions of Rrd_XXX()
+ * functions might modify the argv strings passed to it.
+ * Hence, we need to do some preparation before
+ * calling the rrd library functions.
+ */
+static char **getopt_init(
+ int argc,
+ CONST84 char *argv[])
+{
+ char **argv2;
+ int i;
+
+ argv2 = calloc(argc, sizeof(char *));
+ for (i = 0; i < argc; i++) {
+ argv2[i] = strdup(argv[i]);
+ }
+ return argv2;
+}
+
+static void getopt_cleanup(
+ int argc,
+ char **argv2)
+{
+ int i;
+
+ for (i = 0; i < argc; i++) {
+ if (argv2[i] != NULL) {
+ free(argv2[i]);
+ }
+ }
+ free(argv2);
+}
+
+static void getopt_free_element(
+ char *argv2[],
+ int argn)
+{
+ if (argv2[argn] != NULL) {
+ free(argv2[argn]);
+ argv2[argn] = NULL;
+ }
+}
+
+static void getopt_squieeze(
+ int *argc,
+ char *argv2[])
+{
+ int i, null_i = 0, argc_tmp = *argc;
+
+ for (i = 0; i < argc_tmp; i++) {
+ if (argv2[i] == NULL) {
+ (*argc)--;
+ } else {
+ argv2[null_i++] = argv2[i];
+ }
+ }
+}
+
+
+
+/* Thread-safe version */
+static int Rrd_Create(
+ ClientData clientData,
+ Tcl_Interp *interp,
+ int argc,
+ CONST84 char *argv[])
+{
+ int argv_i;
+ char **argv2;
+ char *parsetime_error = NULL;
+ time_t last_up = time(NULL) - 10;
+ long int long_tmp;
+ unsigned long int pdp_step = 300;
+ rrd_time_value_t last_up_tv;
+
+ argv2 = getopt_init(argc, argv);
+
+ for (argv_i = 1; argv_i < argc; argv_i++) {
+ if (!strcmp(argv2[argv_i], "--start") || !strcmp(argv2[argv_i], "-b")) {
+ if (argv_i++ >= argc) {
+ Tcl_AppendResult(interp, "RRD Error: option '",
+ argv2[argv_i - 1], "' needs an argument",
+ (char *) NULL);
+ getopt_cleanup(argc, argv2);
+ return TCL_ERROR;
+ }
+ if ((parsetime_error = rrd_parsetime(argv2[argv_i], &last_up_tv))) {
+ Tcl_AppendResult(interp, "RRD Error: invalid time format: '",
+ argv2[argv_i], "'", (char *) NULL);
+ getopt_cleanup(argc, argv2);
+ return TCL_ERROR;
+ }
+ if (last_up_tv.type == RELATIVE_TO_END_TIME ||
+ last_up_tv.type == RELATIVE_TO_START_TIME) {
+ Tcl_AppendResult(interp,
+ "RRD Error: specifying time relative to the 'start' ",
+ "or 'end' makes no sense here",
+ (char *) NULL);
+ getopt_cleanup(argc, argv2);
+ return TCL_ERROR;
+ }
+ last_up = mktime(&last_up_tv.tm) +last_up_tv.offset;
+ if (last_up < 3600 * 24 * 365 * 10) {
+ Tcl_AppendResult(interp,
+ "RRD Error: the first entry to the RRD should be after 1980",
+ (char *) NULL);
+ getopt_cleanup(argc, argv2);
+ return TCL_ERROR;
+ }
+ getopt_free_element(argv2, argv_i - 1);
+ getopt_free_element(argv2, argv_i);
+ } else if (!strcmp(argv2[argv_i], "--step")
+ || !strcmp(argv2[argv_i], "-s")) {
+ if (argv_i++ >= argc) {
+ Tcl_AppendResult(interp, "RRD Error: option '",
+ argv2[argv_i - 1], "' needs an argument",
+ (char *) NULL);
+ getopt_cleanup(argc, argv2);
+ return TCL_ERROR;
+ }
+ long_tmp = atol(argv2[argv_i]);
+ if (long_tmp < 1) {
+ Tcl_AppendResult(interp,
+ "RRD Error: step size should be no less than one second",
+ (char *) NULL);
+ getopt_cleanup(argc, argv2);
+ return TCL_ERROR;
+ }
+ pdp_step = long_tmp;
+ getopt_free_element(argv2, argv_i - 1);
+ getopt_free_element(argv2, argv_i);
+ } else if (!strcmp(argv2[argv_i], "--")) {
+ getopt_free_element(argv2, argv_i);
+ break;
+ } else if (argv2[argv_i][0] == '-') {
+ Tcl_AppendResult(interp, "RRD Error: unknown option '",
+ argv2[argv_i], "'", (char *) NULL);
+ getopt_cleanup(argc, argv2);
+ return TCL_ERROR;
+ }
+ }
+
+ getopt_squieeze(&argc, argv2);
+
+ if (argc < 2) {
+ Tcl_AppendResult(interp, "RRD Error: needs rrd filename",
+ (char *) NULL);
+ getopt_cleanup(argc, argv2);
+ return TCL_ERROR;
+ }
+
+ rrd_create_r(argv2[1], pdp_step, last_up, argc - 2, argv2 + 2);
+
+ getopt_cleanup(argc, argv2);
+
+ if (rrd_test_error()) {
+ Tcl_AppendResult(interp, "RRD Error: ",
+ rrd_get_error(), (char *) NULL);
+ rrd_clear_error();
+ return TCL_ERROR;
+ }
+
+ return TCL_OK;
+}
+
+
+
+/* Thread-safe version */
+static int Rrd_Dump(
+ ClientData clientData,
+ Tcl_Interp *interp,
+ int argc,
+ CONST84 char *argv[])
+{
+ if (argc < 2) {
+ Tcl_AppendResult(interp, "RRD Error: needs rrd filename",
+ (char *) NULL);
+ return TCL_ERROR;
+ }
+
+ rrd_dump_r(argv[1], NULL);
+
+ /* NOTE: rrd_dump() writes to stdout. No interaction with TCL. */
+
+ if (rrd_test_error()) {
+ Tcl_AppendResult(interp, "RRD Error: ",
+ rrd_get_error(), (char *) NULL);
+ rrd_clear_error();
+ return TCL_ERROR;
+ }
+
+ return TCL_OK;
+}
+
+
+
+/* Thread-safe version */
+static int Rrd_Last(
+ ClientData clientData,
+ Tcl_Interp *interp,
+ int argc,
+ CONST84 char *argv[])
+{
+ time_t t;
+
+ if (argc < 2) {
+ Tcl_AppendResult(interp, "RRD Error: needs rrd filename",
+ (char *) NULL);
+ return TCL_ERROR;
+ }
+
+ t = rrd_last_r(argv[1]);
+
+ if (rrd_test_error()) {
+ Tcl_AppendResult(interp, "RRD Error: ",
+ rrd_get_error(), (char *) NULL);
+ rrd_clear_error();
+ return TCL_ERROR;
+ }
+
+ Tcl_SetIntObj(Tcl_GetObjResult(interp), t);
+
+ return TCL_OK;
+}
+
+
+
+/* Thread-safe version */
+static int Rrd_Update(
+ ClientData clientData,
+ Tcl_Interp *interp,
+ int argc,
+ CONST84 char *argv[])
+{
+ int argv_i;
+ char **argv2, *template = NULL;
+
+ argv2 = getopt_init(argc, argv);
+
+ for (argv_i = 1; argv_i < argc; argv_i++) {
+ if (!strcmp(argv2[argv_i], "--template")
+ || !strcmp(argv2[argv_i], "-t")) {
+ if (argv_i++ >= argc) {
+ Tcl_AppendResult(interp, "RRD Error: option '",
+ argv2[argv_i - 1], "' needs an argument",
+ (char *) NULL);
+ if (template != NULL) {
+ free(template);
+ }
+ getopt_cleanup(argc, argv2);
+ return TCL_ERROR;
+ }
+ if (template != NULL) {
+ free(template);
+ }
+ template = strdup(argv2[argv_i]);
+ getopt_free_element(argv2, argv_i - 1);
+ getopt_free_element(argv2, argv_i);
+ } else if (!strcmp(argv2[argv_i], "--")) {
+ getopt_free_element(argv2, argv_i);
+ break;
+ } else if (argv2[argv_i][0] == '-') {
+ Tcl_AppendResult(interp, "RRD Error: unknown option '",
+ argv2[argv_i], "'", (char *) NULL);
+ if (template != NULL) {
+ free(template);
+ }
+ getopt_cleanup(argc, argv2);
+ return TCL_ERROR;
+ }
+ }
+
+ getopt_squieeze(&argc, argv2);
+
+ if (argc < 2) {
+ Tcl_AppendResult(interp, "RRD Error: needs rrd filename",
+ (char *) NULL);
+ if (template != NULL) {
+ free(template);
+ }
+ getopt_cleanup(argc, argv2);
+ return TCL_ERROR;
+ }
+
+ rrd_update_r(argv2[1], template, argc - 2, argv2 + 2);
+
+ if (template != NULL) {
+ free(template);
+ }
+ getopt_cleanup(argc, argv2);
+
+ if (rrd_test_error()) {
+ Tcl_AppendResult(interp, "RRD Error: ",
+ rrd_get_error(), (char *) NULL);
+ rrd_clear_error();
+ return TCL_ERROR;
+ }
+
+ return TCL_OK;
+}
+
+static int Rrd_Lastupdate(
+ ClientData clientData,
+ Tcl_Interp *interp,
+ int argc,
+ CONST84 char *argv[])
+{
+ time_t last_update;
+ char **argv2;
+ char **ds_namv;
+ char **last_ds;
+ char s[30];
+ Tcl_Obj *listPtr;
+ unsigned long ds_cnt, i;
+
+ argv2 = getopt_init(argc, argv);
+ if (rrd_lastupdate(argc - 1, argv2, &last_update,
+ &ds_cnt, &ds_namv, &last_ds) == 0) {
+ listPtr = Tcl_GetObjResult(interp);
+ for (i = 0; i < ds_cnt; i++) {
+ sprintf(s, " %28s", ds_namv[i]);
+ Tcl_ListObjAppendElement(interp, listPtr,
+ Tcl_NewStringObj(s, -1));
+ sprintf(s, "\n\n%10lu:", last_update);
+ Tcl_ListObjAppendElement(interp, listPtr,
+ Tcl_NewStringObj(s, -1));
+ for (i = 0; i < ds_cnt; i++) {
+ sprintf(s, " %s", last_ds[i]);
+ Tcl_ListObjAppendElement(interp, listPtr,
+ Tcl_NewStringObj(s, -1));
+ free(last_ds[i]);
+ free(ds_namv[i]);
+ }
+ sprintf(s, "\n");
+ Tcl_ListObjAppendElement(interp, listPtr,
+ Tcl_NewStringObj(s, -1));
+ free(last_ds);
+ free(ds_namv);
+ }
+ }
+ return TCL_OK;
+}
+
+static int Rrd_Fetch(
+ ClientData clientData,
+ Tcl_Interp *interp,
+ int argc,
+ CONST84 char *argv[])
+{
+ time_t start, end, j;
+ unsigned long step, ds_cnt, i, ii;
+ rrd_value_t *data, *datai;
+ char **ds_namv;
+ Tcl_Obj *listPtr;
+ char s[30];
+ char **argv2;
+
+ argv2 = getopt_init(argc, argv);
+ if (rrd_fetch(argc, argv2, &start, &end, &step,
+ &ds_cnt, &ds_namv, &data) != -1) {
+ datai = data;
+ listPtr = Tcl_GetObjResult(interp);
+ for (j = start; j <= end; j += step) {
+ for (ii = 0; ii < ds_cnt; ii++) {
+ sprintf(s, "%.2f", *(datai++));
+ Tcl_ListObjAppendElement(interp, listPtr,
+ Tcl_NewStringObj(s, -1));
+ }
+ }
+ for (i = 0; i < ds_cnt; i++)
+ free(ds_namv[i]);
+ free(ds_namv);
+ free(data);
+ }
+ getopt_cleanup(argc, argv2);
+
+ if (rrd_test_error()) {
+ Tcl_AppendResult(interp, "RRD Error: ",
+ rrd_get_error(), (char *) NULL);
+ rrd_clear_error();
+ return TCL_ERROR;
+ }
+
+ return TCL_OK;
+}
+
+
+
+static int Rrd_Graph(
+ ClientData clientData,
+ Tcl_Interp *interp,
+ int argc,
+ CONST84 char *argv[])
+{
+ Tcl_Channel channel;
+ int mode, fd2;
+ ClientData fd1;
+ FILE *stream = NULL;
+ char **calcpr = NULL;
+ int rc, xsize, ysize;
+ double ymin, ymax;
+ char dimensions[50];
+ char **argv2;
+ CONST84 char *save;
+
+ /*
+ * If the "filename" is a Tcl fileID, then arrange for rrd_graph() to write to
+ * that file descriptor. Will this work with windoze? I have no idea.
+ */
+ if ((channel = Tcl_GetChannel(interp, argv[1], &mode)) != NULL) {
+ /*
+ * It >is< a Tcl fileID
+ */
+ if (!(mode & TCL_WRITABLE)) {
+ Tcl_AppendResult(interp, "channel \"", argv[1],
+ "\" wasn't opened for writing", (char *) NULL);
+ return TCL_ERROR;
+ }
+ /*
+ * Must flush channel to make sure any buffered data is written before
+ * rrd_graph() writes to the stream
+ */
+ if (Tcl_Flush(channel) != TCL_OK) {
+ Tcl_AppendResult(interp, "flush failed for \"", argv[1], "\": ",
+ strerror(Tcl_GetErrno()), (char *) NULL);
+ return TCL_ERROR;
+ }
+ if (Tcl_GetChannelHandle(channel, TCL_WRITABLE, &fd1) != TCL_OK) {
+ Tcl_AppendResult(interp,
+ "cannot get file descriptor associated with \"",
+ argv[1], "\"", (char *) NULL);
+ return TCL_ERROR;
+ }
+ /*
+ * Must dup() file descriptor so we can fclose(stream), otherwise the fclose()
+ * would close Tcl's file descriptor
+ */
+ if ((fd2 = dup((int) fd1)) == -1) {
+ Tcl_AppendResult(interp,
+ "dup() failed for file descriptor associated with \"",
+ argv[1], "\": ", strerror(errno), (char *) NULL);
+ return TCL_ERROR;
+ }
+ /*
+ * rrd_graph() wants a FILE*
+ */
+ if ((stream = fdopen(fd2, "wb")) == NULL) {
+ Tcl_AppendResult(interp,
+ "fdopen() failed for file descriptor associated with \"",
+ argv[1], "\": ", strerror(errno), (char *) NULL);
+ close(fd2); /* plug potential file descriptor leak */
+ return TCL_ERROR;
+ }
+
+ save = argv[1];
+ argv[1] = "-";
+ argv2 = getopt_init(argc, argv);
+ argv[1] = save;
+ } else {
+ Tcl_ResetResult(interp); /* clear error from Tcl_GetChannel() */
+ argv2 = getopt_init(argc, argv);
+ }
+
+ rc = rrd_graph(argc, argv2, &calcpr, &xsize, &ysize, stream, &ymin,
+ &ymax);
+ getopt_cleanup(argc, argv2);
+
+ if (stream != NULL)
+ fclose(stream); /* plug potential malloc & file descriptor leak */
+
+ if (rc != -1) {
+ sprintf(dimensions, "%d %d", xsize, ysize);
+ Tcl_AppendResult(interp, dimensions, (char *) NULL);
+ if (calcpr) {
+#if 0
+ int i;
+
+ for (i = 0; calcpr[i]; i++) {
+ printf("%s\n", calcpr[i]);
+ free(calcpr[i]);
+ }
+#endif
+ free(calcpr);
+ }
+ }
+
+ if (rrd_test_error()) {
+ Tcl_AppendResult(interp, "RRD Error: ",
+ rrd_get_error(), (char *) NULL);
+ rrd_clear_error();
+ return TCL_ERROR;
+ }
+
+ return TCL_OK;
+}
+
+
+
+static int Rrd_Tune(
+ ClientData clientData,
+ Tcl_Interp *interp,
+ int argc,
+ CONST84 char *argv[])
+{
+ char **argv2;
+
+ argv2 = getopt_init(argc, argv);
+ rrd_tune(argc, argv2);
+ getopt_cleanup(argc, argv2);
+
+ if (rrd_test_error()) {
+ Tcl_AppendResult(interp, "RRD Error: ",
+ rrd_get_error(), (char *) NULL);
+ rrd_clear_error();
+ return TCL_ERROR;
+ }
+
+ return TCL_OK;
+}
+
+
+
+static int Rrd_Resize(
+ ClientData clientData,
+ Tcl_Interp *interp,
+ int argc,
+ CONST84 char *argv[])
+{
+ char **argv2;
+
+ argv2 = getopt_init(argc, argv);
+ rrd_resize(argc, argv2);
+ getopt_cleanup(argc, argv2);
+
+ if (rrd_test_error()) {
+ Tcl_AppendResult(interp, "RRD Error: ",
+ rrd_get_error(), (char *) NULL);
+ rrd_clear_error();
+ return TCL_ERROR;
+ }
+
+ return TCL_OK;
+}
+
+
+
+static int Rrd_Restore(
+ ClientData clientData,
+ Tcl_Interp *interp,
+ int argc,
+ CONST84 char *argv[])
+{
+ char **argv2;
+
+ argv2 = getopt_init(argc, argv);
+ rrd_restore(argc, argv2);
+ getopt_cleanup(argc, argv2);
+
+ if (rrd_test_error()) {
+ Tcl_AppendResult(interp, "RRD Error: ",
+ rrd_get_error(), (char *) NULL);
+ rrd_clear_error();
+ return TCL_ERROR;
+ }
+
+ return TCL_OK;
+}
+
+
+
+/*
+ * The following structure defines the commands in the Rrd extension.
+ */
+
+typedef struct {
+ char *name; /* Name of the command. */
+ Tcl_CmdProc *proc; /* Procedure for command. */
+ int hide; /* Hide if safe interpreter */
+} CmdInfo;
+
+static CmdInfo rrdCmds[] = {
+ {"Rrd::create", Rrd_Create, 1}, /* Thread-safe version */
+ {"Rrd::dump", Rrd_Dump, 0}, /* Thread-safe version */
+ {"Rrd::last", Rrd_Last, 0}, /* Thread-safe version */
+ {"Rrd::lastupdate", Rrd_Lastupdate, 0}, /* Thread-safe version */
+ {"Rrd::update", Rrd_Update, 1}, /* Thread-safe version */
+ {"Rrd::fetch", Rrd_Fetch, 0},
+ {"Rrd::graph", Rrd_Graph, 1}, /* Due to RRD's API, a safe
+ interpreter cannot create
+ a graph since it writes to
+ a filename supplied by the
+ caller */
+ {"Rrd::tune", Rrd_Tune, 1},
+ {"Rrd::resize", Rrd_Resize, 1},
+ {"Rrd::restore", Rrd_Restore, 1},
+ {(char *) NULL, (Tcl_CmdProc *) NULL, 0}
+};
+
+
+
+static int init(
+ Tcl_Interp *interp,
+ int safe)
+{
+ CmdInfo *cmdInfoPtr;
+ Tcl_CmdInfo info;
+
+ if (Tcl_InitStubs(interp, TCL_VERSION, 0) == NULL)
+ return TCL_ERROR;
+
+ if (Tcl_PkgRequire(interp, "Tcl", TCL_VERSION, 1) == NULL) {
+ return TCL_ERROR;
+ }
+
+ /*
+ * Why a global array? In keeping with the Rrd:: namespace, why
+ * not simply create a normal variable Rrd::version and set it?
+ */
+ Tcl_SetVar2(interp, "rrd", "version", VERSION, TCL_GLOBAL_ONLY);
+
+ for (cmdInfoPtr = rrdCmds; cmdInfoPtr->name != NULL; cmdInfoPtr++) {
+ /*
+ * Check if the command already exists and return an error
+ * to ensure we detect name clashes while loading the Rrd
+ * extension.
+ */
+ if (Tcl_GetCommandInfo(interp, cmdInfoPtr->name, &info)) {
+ Tcl_AppendResult(interp, "command \"", cmdInfoPtr->name,
+ "\" already exists", (char *) NULL);
+ return TCL_ERROR;
+ }
+ if (safe && cmdInfoPtr->hide) {
+#if 0
+ /*
+ * Turns out the one cannot hide a command in a namespace
+ * due to a limitation of Tcl, one can only hide global
+ * commands. Thus, if we created the commands without
+ * the Rrd:: namespace in a safe interpreter, then the
+ * "unsafe" commands could be hidden -- which would allow
+ * an owning interpreter either un-hiding them or doing
+ * an "interp invokehidden". If the Rrd:: namespace is
+ * used, then it's still possible for the owning interpreter
+ * to fake out the missing commands:
+ *
+ * # Make all Rrd::* commands available in master interperter
+ * package require Rrd
+ * set safe [interp create -safe]
+ * # Make safe Rrd::* commands available in safe interperter
+ * interp invokehidden $safe -global load ./tclrrd1.2.11.so
+ * # Provide the safe interpreter with the missing commands
+ * $safe alias Rrd::update do_update $safe
+ * proc do_update {which_interp $args} {
+ * # Do some checking maybe...
+ * :
+ * return [eval Rrd::update $args]
+ * }
+ *
+ * Our solution for now is to just not create the "unsafe"
+ * commands in a safe interpreter.
+ */
+ if (Tcl_HideCommand(interp, cmdInfoPtr->name, cmdInfoPtr->name) !=
+ TCL_OK)
+ return TCL_ERROR;
+#endif
+ } else
+ Tcl_CreateCommand(interp, cmdInfoPtr->name, cmdInfoPtr->proc,
+ (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
+ }
+
+ if (Tcl_PkgProvide(interp, "Rrd", VERSION) != TCL_OK) {
+ return TCL_ERROR;
+ }
+
+ return TCL_OK;
+}
+
+int Tclrrd_Init(
+ Tcl_Interp *interp)
+{
+ return init(interp, 0);
+}
+
+/*
+ * See the comments above and note how few commands are considered "safe"...
+ * Using rrdtool in a safe interpreter has very limited functionality. It's
+ * tempting to just return TCL_ERROR and forget about it.
+ */
+int Tclrrd_SafeInit(
+ Tcl_Interp *interp)
+{
+ return init(interp, 1);
+}
diff --git a/program/configure.ac b/program/configure.ac
--- /dev/null
+++ b/program/configure.ac
@@ -0,0 +1,983 @@
+dnl RRDtool AutoConf script ...
+dnl ---------------------------
+dnl
+dnl Created by Jeff Allen, Tobi Oetiker, Blair Zajac
+dnl
+dnl Inspiration from http://autoconf-archive.cryp.to
+
+dnl tell automake the this script is for rrdtool
+
+dnl the official version number is
+dnl a.b.c
+AC_INIT([rrdtool],[1.3.2])
+
+dnl for testing a numberical version number comes handy
+dnl the released version are
+dnl a.bccc
+dnl the devel versions will be something like
+dnl a.b999yymmddhh
+NUMVERS=1.3002
+AC_SUBST(NUMVERS)
+
+dnl for the linker to understand which versions the library are compatible with
+dnl each other we must keep a separate library version cout of the format c:r:a.
+dnl - if only implementation changed but all interfaces are kept, do r++
+dnl - if only functionality was added do c++,r=0,a++
+dnl - if any functionality was removed do c++,r=0,a=0.
+dnl
+dnl see http://sourceware.org/autobook/autobook/autobook_91.html
+dnl
+LIBVERS=4:1:0
+AC_SUBST(LIBVERS)
+
+AC_CANONICAL_TARGET
+AM_INIT_AUTOMAKE
+AM_MAINTAINER_MODE
+
+AC_CONFIG_HEADERS([rrd_config.h])
+
+dnl all our local stuff like install scripts and include files
+dnl is in there
+
+
+dnl determine the type of system we are running on
+
+AC_SUBST(VERSION)
+
+AC_PREFIX_DEFAULT( /usr/local/rrdtool-$PACKAGE_VERSION )
+
+dnl Minimum Autoconf version required.
+AC_PREREQ(2.59)
+
+dnl At the TOP of the HEADER
+
+AH_TOP([
+
+#ifndef RRD_CONFIG_H
+#define RRD_CONFIG_H
+/* IEEE can be prevented from raising signals with fpsetmask(0) */
+#undef MUST_DISABLE_FPMASK
+
+/* IEEE math only works if SIGFPE gets actively set to IGNORE */
+
+#undef MUST_DISABLE_SIGFPE
+
+/* realloc does not support NULL as argument */
+#undef NO_NULL_REALLOC
+
+/* lets enable madvise defines in NetBSD */
+#if defined(__NetBSD__)
+# if !defined(_NETBSD_SOURCE)
+# define _NETBSD_SOURCE
+# endif
+#endif
+
+ ])
+
+AH_BOTTOM([
+/* make sure that we pickup the correct stuff from all headers */
+#ifdef HAVE_FEATURES_H
+#undef _XOPEN_SOURCE /* keep unmodified */
+#undef _BSD_SOURCE /* keep unmodified */
+#define _XOPEN_SOURCE 600
+#define _BSD_SOURCE 1
+# include <features.h>
+#endif
+
+/* FreeBSD 4.8 wants this included BEFORE sys/types.h */
+#ifdef HAVE_SYS_MMAN_H
+# include <sys/mman.h>
+#endif
+
+#ifdef HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+
+#ifdef HAVE_SYS_PARAM_H
+# include <sys/param.h>
+#endif
+#ifndef MAXPATH
+# ifdef PATH_MAX
+# define MAXPATH PATH_MAX
+# endif
+#endif
+#ifndef MAXPATH
+/* else try the BSD variant */
+# ifdef MAXPATHLEN
+# define MAXPATH MAXPATHLEN
+# endif
+#endif
+
+#ifdef HAVE_ERRNO_H
+# include <errno.h>
+#endif
+
+#if !defined HAVE_MADVISE && defined HAVE_POSIX_MADVISE
+/* use posix_madvise family */
+# define madvise posix_madvise
+# define MADV_NORMAL POSIX_MADV_NORMAL
+# define MADV_RANDOM POSIX_MADV_RANDOM
+# define MADV_SEQUENTIAL POSIX_MADV_SEQUENTIAL
+# define MADV_WILLNEED POSIX_MADV_WILLNEED
+# define MADV_DONTNEED POSIX_MADV_DONTNEED
+#endif
+#if defined HAVE_MADVISE || defined HAVE_POSIX_MADVISE
+# define USE_MADVISE 1
+#endif
+
+#ifdef HAVE_SYS_STAT_H
+# include <sys/stat.h>
+#endif
+
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#ifdef TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#ifdef HAVE_SYS_TIMES_H
+# include <sys/times.h>
+#endif
+
+#ifdef HAVE_SYS_RESOURCE_H
+# include <sys/resource.h>
+#if (defined(__svr4__) && defined(__sun__))
+/* Solaris headers (pre 2.6) do not have a getrusage prototype.
+ Use this instead. */
+extern int getrusage(int, struct rusage *);
+#endif /* __svr4__ && __sun__ */
+#endif
+
+
+/* define strrchr, strchr and memcpy, memmove in terms of bsd funcs
+ make sure you are NOT using bcopy, index or rindex in the code */
+
+#ifdef STDC_HEADERS
+# include <string.h>
+#else
+# ifndef HAVE_STRCHR
+# define strchr index
+# define strrchr rindex
+# endif
+char *strchr (), *strrchr ();
+# ifndef HAVE_MEMMOVE
+# define memcpy(d, s, n) bcopy ((s), (d), (n))
+# define memmove(d, s, n) bcopy ((s), (d), (n))
+# endif
+#endif
+
+#ifdef NO_NULL_REALLOC
+# define rrd_realloc(a,b) ( (a) == NULL ? malloc( (b) ) : realloc( (a) , (b) ))
+#else
+# define rrd_realloc(a,b) realloc((a), (b))
+#endif
+
+#ifdef NEED_MALLOC_MALLOC_H
+# include <malloc/malloc.h>
+#endif
+
+#ifdef HAVE_STDIO_H
+# include <stdio.h>
+#endif
+
+#ifdef HAVE_STDLIB_H
+# include <stdlib.h>
+#endif
+
+#ifdef HAVE_CTYPE_H
+# include <ctype.h>
+#endif
+
+#ifdef HAVE_DIRENT_H
+# include <dirent.h>
+# define NAMLEN(dirent) strlen((dirent)->d_name)
+#else
+# define dirent direct
+# define NAMLEN(dirent) (dirent)->d_namlen
+# ifdef HAVE_SYS_NDIR_H
+# include <sys/ndir.h>
+# endif
+# ifdef HAVE_SYS_DIR_H
+# include <sys/dir.h>
+# endif
+# ifdef HAVE_NDIR_H
+# include <ndir.h>
+# endif
+#endif
+
+#ifdef MUST_DISABLE_SIGFPE
+# include <signal.h>
+#endif
+
+#ifdef MUST_DISABLE_FPMASK
+# include <floatingpoint.h>
+#endif
+
+
+#ifdef HAVE_MATH_H
+# include <math.h>
+#endif
+
+#ifdef HAVE_FLOAT_H
+# include <float.h>
+#endif
+
+#ifdef HAVE_IEEEFP_H
+# include <ieeefp.h>
+#endif
+
+#ifdef HAVE_FP_CLASS_H
+# include <fp_class.h>
+#endif
+
+/* for Solaris */
+#if (! defined(HAVE_ISINF) && defined(HAVE_FPCLASS))
+# define HAVE_ISINF 1
+# ifdef isinf
+# undef isinf /* confuse autoconf */
+# endif
+# define isinf(a) (fpclass(a) == FP_NINF || fpclass(a) == FP_PINF)
+#endif
+
+/* solaris 10 it defines isnan such that only forte can compile it ... bad bad */
+#if (defined(HAVE_ISNAN) && defined(isnan) && defined(HAVE_FPCLASS))
+# undef isnan /* confuse autoconf to NOT remove this */
+# define isnan(a) (fpclass(a) == FP_SNAN || fpclass(a) == FP_QNAN)
+#endif
+
+/* for OSF1 Digital Unix */
+#if (! defined(HAVE_ISINF) && defined(HAVE_FP_CLASS) && defined(HAVE_FP_CLASS_H))
+# define HAVE_ISINF 1
+# define isinf(a) (fp_class(a) == FP_NEG_INF || fp_class(a) == FP_POS_INF)
+#endif
+
+#if (! defined(HAVE_ISINF) && defined(HAVE_FPCLASSIFY) && defined(FP_PLUS_INF) && defined(FP_MINUS_INF))
+# define HAVE_ISINF 1
+# define isinf(a) (fpclassify(a) == FP_MINUS_INF || fpclassify(a) == FP_PLUS_INF)
+#endif
+
+#if (! defined(HAVE_ISINF) && defined(HAVE_FPCLASSIFY) && defined(FP_INFINITE))
+# define HAVE_ISINF 1
+# define isinf(a) (fpclassify(a) == FP_INFINITE)
+#endif
+
+/* for AIX */
+#if (! defined(HAVE_ISINF) && defined(HAVE_CLASS))
+# define HAVE_ISINF 1
+# define isinf(a) (class(a) == FP_MINUS_INF || class(a) == FP_PLUS_INF)
+#endif
+
+#if (! defined (HAVE_FINITE) && defined (HAVE_ISFINITE))
+# define HAVE_FINITE 1
+# define finite(a) isfinite(a)
+#endif
+
+#if (! defined(HAVE_FINITE) && defined(HAVE_ISNAN) && defined(HAVE_ISINF))
+# define HAVE_FINITE 1
+# define finite(a) (! isnan(a) && ! isinf(a))
+#endif
+
+#ifndef HAVE_FINITE
+#error "Can't compile without finite function"
+#endif
+
+#ifndef HAVE_ISINF
+#error "Can't compile without isinf function"
+#endif
+
+#if (! defined(HAVE_FDATASYNC) && defined(HAVE_FSYNC))
+#define fdatasync fsync
+#endif
+
+#if (!defined(HAVE_FDATASYNC) && !defined(HAVE_FSYNC))
+#error "Can't compile with without fsync and fdatasync"
+#endif
+
+#endif /* RRD_CONFIG_H */
+])
+
+dnl Process Special Options
+dnl -----------------------------------
+
+dnl How the vertical axis label is printed
+AC_ARG_VAR(RRDGRAPH_YLEGEND_ANGLE,
+ [Vertical label angle: -90.0 (default) or 90.0])
+AC_DEFINE_UNQUOTED(RRDGRAPH_YLEGEND_ANGLE,${RRDGRAPH_YLEGEND_ANGLE:-90.0},
+ [Vertical label angle: -90.0 (default) or 90.0])
+
+AC_ARG_ENABLE(rrdcgi,[ --disable-rrdcgi disable building of rrdcgi],
+[],[enable_rrdcgi=yes])
+
+dnl Check if we run on a system that has fonts
+AC_ARG_WITH(rrd-default-font,
+[ --with-rrd-default-font=[OPTIONS] set the full path to your default font.],
+[RRD_DEFAULT_FONT=$withval],[
+ if test -d ${WINDIR:-nodir}/cour.ttf ; then
+ RRD_DEFAULT_FONT=`cd $WINDIR;pwd`/cour.ttf
+ else
+ RRD_DEFAULT_FONT='"DejaVu Sans Mono,Bitstream Vera Sans Mono,monospace,Courier"'
+ fi
+])
+
+dnl Use mmap in rrd_update instead of seek+write
+AC_ARG_ENABLE([mmap],
+[ --disable-mmap disable mmap in rrd_update, use seek+write instead],
+[],
+[enable_mmap=yes])
+
+AC_ARG_ENABLE(pthread,[ --disable-pthread disable multithread support],
+[],[enable_pthread=yes])
+
+AC_ARG_ENABLE(static-programs,
+ [ --enable-static-programs Build static programs],
+ [case "${enableval}" in
+ yes) staticprogs=yes ;;
+ no) staticprogs=no ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --enable-static-programs) ;;
+ esac],[staticprogs=no])
+AM_CONDITIONAL(STATIC_PROGRAMS,[test "x$staticprogs" = "xyes"])
+
+
+CONFIGURE_PART(Audit Compilation Environment)
+
+
+dnl Check for the compiler and static/shared library creation.
+AC_PROG_CC
+AC_PROG_CPP
+AC_PROG_LIBTOOL
+
+dnl Try to detect/use GNU features
+CFLAGS="$CFLAGS -D_GNU_SOURCE"
+
+dnl which flags does the compiler support?
+if test "x$GCC" = "xyes"; then
+ for flag in -fno-strict-aliasing -Wall -std=c99 -pedantic -Wundef -Wshadow -Wpointer-arith -Wcast-align -Wmissing-prototypes -Wmissing-declarations -Wnested-externs -Winline -Wold-style-definition -W; do
+ oCFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS $flag"
+ cachename=rd_cv_gcc_flag_`echo $flag|sed 's/[[^A-Za-z]]/_/g'`
+ AC_CACHE_CHECK([if gcc likes the $flag flag], $cachename,
+ [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[return 0 ]])],[eval $cachename=yes],[eval $cachename=no])])
+ if eval test \$$cachename = no; then
+ CFLAGS="$oCFLAGS"
+ fi
+ done
+fi
+
+
+
+AC_SUBST(RRD_DEFAULT_FONT)
+
+CONFIGURE_PART(Checking for Header Files)
+
+dnl Checks for header files.
+AC_HEADER_STDC
+AC_HEADER_DIRENT
+AC_CHECK_HEADERS(features.h sys/stat.h sys/types.h fcntl.h locale.h fp_class.h malloc.h unistd.h ieeefp.h math.h sys/times.h sys/param.h sys/resource.h signal.h float.h stdio.h stdlib.h errno.h string.h ctype.h)
+
+dnl Checks for typedefs, structures, and compiler characteristics.
+AC_C_CONST
+AC_HEADER_TIME
+AC_STRUCT_TM
+
+CONFIGURE_PART(Test Library Functions)
+
+dnl Checks for libraries.
+AC_CHECK_FUNC(acos, , AC_CHECK_LIB(m, acos))
+
+
+dnl add pic flag in any case this makes sure all our code is relocatable
+eval `./libtool --config | grep pic_flag`
+CFLAGS="$CFLAGS $pic_flag"
+
+
+dnl Checks for library functions.
+AC_FUNC_STRFTIME
+AC_FUNC_VPRINTF
+
+AC_C_BIGENDIAN
+
+dnl for each function found we get a definition in config.h
+dnl of the form HAVE_FUNCTION
+
+AC_CHECK_FUNCS(tzset fsync mbstowcs opendir readdir chdir chroot getuid setlocale strerror strerror_r snprintf vsnprintf fpclass class fp_class isnan memmove strchr mktime getrusage gettimeofday)
+
+CONFIGURE_PART(Map/Fadvis/Madvise checking)
+
+dnl Could use these to know if we need to provide a prototype
+dnl AC_CHECK_DECLS(fdatasync, [], [], [#include <unistd.h>])
+
+dnl check for fdatasync. Solaris has fdatasync in the librt
+
+AC_CHECK_FUNCS(fdatasync, [], AC_CHECK_LIB(rt, fdatasync, [LIBS="${LIBS} -lrt"; AC_DEFINE(HAVE_FDATASYNC)],[]))
+dnl if there is no fdatasync we may get lucky with fsync
+AC_CHECK_FUNCS(fsync)
+
+
+dnl XXX: dunno about windows.. add AC_CHECK_FUNCS(munmap) there too?
+if test "x$enable_mmap" = "xyes"; then
+ case "$host" in
+ *cygwin*)
+ # the normal mmap test does not work in cygwin
+ AC_CHECK_FUNCS(mmap)
+ if test "x$ac_cv_func_mmap" = "xyes"; then
+ ac_cv_func_mmap_fixed_mapped=yes
+ fi
+ ;;
+ *)
+ AC_CHECK_HEADERS(sys/mman.h)
+ AC_FUNC_MMAP
+ AC_CHECK_FUNCS(mmap munmap msync)
+ AC_CHECK_DECLS(madvise, [], [], [#ifdef HAVE_SYS_MMAN_H
+ # include <sys/mman.h>
+ #endif])
+ if test "x$ac_cv_have_decl_madvise" = "xyes";
+ then
+ AC_CHECK_FUNCS(madvise)
+ else
+ AC_CHECK_FUNCS(posix_madvise)
+ if test "x$ac_cv_func_posix_madvise" != "xyes"; then
+ AC_MSG_WARN([madvise() nor posix_madvise() found.])
+ fi
+ fi
+ ;;
+ esac
+ if test "x$ac_cv_func_mmap" != "xyes";
+ then
+ AC_MSG_ERROR([--enable-mmap requested but mmap() was not detected])
+dnl enable_mmap="no"
+ fi
+fi
+
+dnl can we use posix_fadvise
+AC_CHECK_DECLS(posix_fadvise, [], [], [#define _XOPEN_SOURCE 600
+#include <fcntl.h>])
+AC_CHECK_FUNCS(posix_fadvise)
+
+CONFIGURE_PART(Libintl Processing)
+
+
+dnl gettext
+GETTEXT_PACKAGE=rrdtool
+AC_SUBST(GETTEXT_PACKAGE)
+AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE,"$GETTEXT_PACKAGE",[Gettext package])
+
+AM_GLIB_GNU_GETTEXT()
+
+AC_ARG_ENABLE(libintl,[ --disable-libintl i18n support (libintl)],
+[],[enable_libintl=yes])
+
+if test x$enable_libintl = xyes; then
+ IT_PROG_INTLTOOL([0.35.0],[no-xml])
+fi
+
+if test x$enable_libintl = xyes -a x$MSGFMT = xno; then
+ AC_MSG_WARN(I could not find msgfmt. Diabeling libintl build.)
+ enable_libintl=no
+fi
+
+if test x$enable_libintl = xyes; then
+ AC_CHECK_HEADERS(libintl.h,[],[AC_MSG_RESULT(disabeling libintl build); enable_libintl=no])
+fi
+
+if test x$enable_libintl = xyes ; then
+ dnl it seems bsd synstems need to link against libintl
+ dnl when compiling rrdupdate. lets check
+ AC_CHECK_LIB(intl, libintl_gettext,[LIB_LIBINTL="-lintl"])
+fi
+
+dnl use for linking rrdupdate
+AC_SUBST(LIB_LIBINTL)
+
+dnl do not touch the po stuff if we are not going to build intl
+AM_CONDITIONAL(BUILD_LIBINTL,[test x$enable_libintl = xyes])
+
+if test x$enable_libintl = xyes; then
+ AC_DEFINE([BUILD_LIBINTL], [], [Use this in code sections to mark them for libintl build])
+fi
+
+CONFIGURE_PART(IEEE Math Checks)
+
+
+dnl actual code to check if this works
+AC_CHECK_FUNCS(fpclassify, ,
+ [AC_MSG_CHECKING(for fpclassify with <math.h>)
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <math.h>
+volatile int x;volatile float f; ]], [[x = fpclassify(f)]])],[AC_MSG_RESULT(yes)
+ AC_DEFINE(HAVE_FPCLASSIFY)],[AC_MSG_RESULT(no)])])
+
+AC_CHECK_FUNCS(isinf, ,
+ [AC_MSG_CHECKING(for isinf with <math.h>)
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <math.h>
+volatile int x;volatile float f; ]], [[x = isinf(f)]])],[AC_MSG_RESULT(yes)
+ AC_DEFINE(HAVE_ISINF)],[AC_MSG_RESULT(no)])])
+
+dnl finite is BSD, isfinite is C99, so prefer the latter
+AC_CACHE_CHECK([whether isfinite is broken],[have_broken_isfinite],[
+AC_TRY_RUN([
+#ifdef HAVE_MATH_H
+#include <math.h>
+#endif
+#ifdef HAVE_FLOAT_H
+#include <float.h>
+#endif
+int main ()
+{
+#ifdef isfinite
+#ifdef LDBL_MAX
+ if (!isfinite(LDBL_MAX)) return 1;
+#endif
+#ifdef DBL_MAX
+ if (!isfinite(DBL_MAX)) return 1;
+#endif
+#endif
+return 0;
+}],[
+have_broken_isfinite=no],have_broken_isfinite=yes,[
+case "${target}" in
+ hppa*-*-hpux*) have_broken_isfinite=yes ;;
+ *-sun-solaris2.8) have_broken_isfinite=yes ;;
+ *) have_broken_isfinite=no ;;
+esac])
+])
+if test "x$have_broken_isfinite" = "xno"; then
+ AC_DEFINE(HAVE_ISFINITE)
+else
+AC_CHECK_FUNCS(finite,[],
+ [AC_CHECK_FUNCS(isfinite,[],
+ [AC_MSG_CHECKING(for isfinite with <math.h>)
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <math.h>
+volatile int x;volatile float f; ]],[[x = isfinite(f)]])],[AC_MSG_RESULT(yes)
+ AC_DEFINE(HAVE_ISFINITE)],[AC_MSG_RESULT(no)])])])
+fi
+
+AC_FULL_IEEE
+
+CONFIGURE_PART(Resolve Portability Issues)
+
+dnl Do we need getopt_long
+
+build_getopt=no
+RRD_GETOPT_LONG="#"
+AC_CHECK_FUNC(getopt_long,[],[
+RRD_GETOPT_LONG="getopt_long"
+build_getopt=yes
+])
+AC_SUBST(RRD_GETOPT_LONG)
+AM_CONDITIONAL(BUILD_GETOPT,[test $build_getopt = yes])
+
+dnl what does realloc do if it gets called with a NULL pointer
+
+AC_CACHE_CHECK([if realloc can deal with NULL], rd_cv_null_realloc,
+[AC_RUN_IFELSE([AC_LANG_SOURCE([[#include <stdlib.h>
+ int main(void){
+ char *x = NULL;
+ x = realloc (x,10);
+ if (x==NULL) return 1;
+ return 0;
+ }]])],[rd_cv_null_realloc=yes],[rd_cv_null_realloc=nope],[:])])
+
+if test x"$rd_cv_null_realloc" = xnope; then
+AC_DEFINE(NO_NULL_REALLOC)
+fi
+
+AC_LANG_PUSH(C)
+dnl solaris has some odd defines it needs in order to propperly compile ctime_r
+AC_MSG_CHECKING([if ctime_r need special care to act posixly correct])
+AC_LINK_IFELSE(
+ AC_LANG_PROGRAM(
+ [[#include <time.h>]],
+ [[ctime_r(NULL,NULL,0)]]
+ ),
+ [ CPPFLAGS="$CPPFLAGS -D_POSIX_PTHREAD_SEMANTICS"
+ AC_LINK_IFELSE(
+ AC_LANG_PROGRAM(
+ [[#include <time.h>]],
+ [[ctime_r(NULL,NULL)]]
+ ),
+ [AC_MSG_RESULT([yes, this seems to be solaris style])],
+ [AC_MSG_ERROR([Can't figure how to compile ctime_r])]
+ )
+ ],
+ [ AC_LINK_IFELSE(
+ AC_LANG_PROGRAM(
+ [[#include <time.h>]],
+ [[ctime_r(NULL,NULL)]]
+ ),
+ [AC_MSG_RESULT(no)],
+ [AC_MSG_ERROR([Can't figure how to compile ctime_r])]
+ )
+ ]
+)
+AC_LANG_POP(C)
+
+dnl Check for pthreads
+dnl http://autoconf-archive.cryp.to/acx_pthread.m4
+
+AC_SUBST(MULTITHREAD_CFLAGS)
+AC_SUBST(MULTITHREAD_LDFLAGS)
+
+if test $enable_pthread != no; then
+ ACX_PTHREAD([
+ MULTITHREAD_CFLAGS=$PTHREAD_CFLAGS
+ MULTITHREAD_LDFLAGS=$PTHREAD_LIBS
+ ],
+ [])
+fi
+
+dnl since we use lots of *_r functions all over the code we better
+dnl make sure they are known
+
+if test "x$x_rflag" != "xno"; then
+ CPPFLAGS="$CPPFLAGS $x_rflag"
+fi
+
+AM_CONDITIONAL(BUILD_MULTITHREAD,[test $enable_pthread != no])
+
+AC_LANG_PUSH(C)
+dnl see if we have to include malloc/malloc.h
+AC_MSG_CHECKING([do we need malloc/malloc.h])
+AC_LINK_IFELSE(
+ AC_LANG_PROGRAM(
+ [[#include <stdlib.h>]],
+ [[malloc(1)]]
+ ),
+ [ AC_MSG_RESULT([nope, works out of the box]) ],
+ [ AC_LINK_IFELSE(
+ AC_LANG_PROGRAM(
+ [[#include <stdlib.h>
+ #include <malloc/malloc.h>]],
+ [[malloc(1)]]
+ ),
+ [AC_DEFINE(NEED_MALLOC_MALLOC_H)
+ AC_MSG_RESULT([yes we do])],
+ [AC_MSG_ERROR([Can not figure how to compile malloc])]
+ )
+ ]
+)
+AC_LANG_POP(C)
+
+CONFIGURE_PART(Find 3rd-Party Libraries)
+
+
+AM_CONDITIONAL(BUILD_RRDCGI,[test $enable_rrdcgi != no])
+
+
+CORE_LIBS="$LIBS"
+
+dnl EX_CHECK_ALL(z, zlibVersion, zlib.h, zlib, 1.2.3, http://www.gzip.org/zlib/, "")
+dnl EX_CHECK_ALL(png, png_access_version_number, png.h, libpng, 1.2.10, http://prdownloads.sourceforge.net/libpng/, "")
+dnl EX_CHECK_ALL(freetype, FT_Init_FreeType, ft2build.h, freetype2, 2.1.10, http://prdownloads.sourceforge.net/freetype/, /usr/include/freetype2)
+dnl EX_CHECK_ALL(fontconfig, FcInit, fontconfig.h, fontconfig, 2.3.1, http://fontconfig.org/release/, /usr/include)
+EX_CHECK_ALL(cairo, cairo_font_options_create, cairo.h, cairo-png, 1.4.6, http://cairographics.org/releases/, "")
+EX_CHECK_ALL(cairo, cairo_svg_surface_create, cairo-svg.h, cairo-svg, 1.4.6, http://cairographics.org/releases/, "")
+EX_CHECK_ALL(cairo, cairo_pdf_surface_create, cairo-pdf.h, cairo-pdf, 1.4.6, http://cairographics.org/releases/, "")
+EX_CHECK_ALL(cairo, cairo_ps_surface_create, cairo-ps.h, cairo-ps, 1.4.6, http://cairographics.org/releases/, "")
+dnl EX_CHECK_ALL(glib-2.0, glib_check_version, glib.h, glib-2.0, 2.12.12, ftp://ftp.gtk.org/pub/glib/2.12/, "")
+EX_CHECK_ALL(pango-1.0, pango_cairo_context_set_font_options, pango/pango.h, pangocairo, 1.17, http://ftp.gnome.org/pub/GNOME/sources/pango/1.17, "")
+EX_CHECK_ALL(xml2, xmlParseFile, libxml/parser.h, libxml-2.0, 2.6.31, http://xmlsoft.org/downloads.html, /usr/include/libxml2)
+
+if test "$EX_CHECK_ALL_ERR" = "YES"; then
+ AC_MSG_ERROR([Please fix the library issues listed above and try again.])
+fi
+
+ALL_LIBS="$LIBS"
+LIBS=
+
+AC_SUBST(CORE_LIBS)
+AC_SUBST(ALL_LIBS)
+
+CONFIGURE_PART(Prep for Building Language Bindings)
+
+dnl Check for Perl and friends
+PATH=$PATH:/usr/perl5/bin
+export PATH
+AC_PATH_PROG(PERL, perl, no)
+AC_PATH_PROG(POD2MAN, pod2man, no)
+AC_PATH_PROG(POD2HTML, pod2html, no)
+
+
+AC_ARG_ENABLE(perl,[ --disable-perl do not build the perl modules],
+[],[enable_perl=yes])
+
+
+AC_ARG_VAR(PERLCC, [[] C compiler for Perl modules])
+AC_ARG_VAR(PERLCCFLAGS, [[] CC flags for Perl modules])
+AC_ARG_VAR(PERLLD, [[same as PERLCC] Linker for Perl modules])
+AC_ARG_VAR(PERLLDFLAGS, [[] LD flags for Perl modules])
+
+if test "x$PERL" = "xno" -o x$enable_perl = xno; then
+ COMP_PERL=
+else
+ COMP_PERL="perl_piped perl_shared"
+ AC_MSG_CHECKING(for the perl version you are running)
+ PERL_VERSION=`$PERL -MConfig -e 'print $Config{version}'`
+ AC_MSG_RESULT($PERL_VERSION)
+ if test -z "$PERLCC"; then
+ AC_MSG_CHECKING(for the C compiler perl wants to use to build its modules)
+ perlcc=`$PERL -MConfig -e 'print $Config{cc}'`
+ AC_MSG_RESULT($perlcc)
+ if test ! -x "$perlcc"; then
+ AC_PATH_PROG(PERL_CC, ${perlcc}, no)
+ if test "$PERL_CC" = "no"; then
+ AC_MSG_WARN([
+I would not find the Compiler ($perlcc) that was originally used to compile
+your perl binary. You should either make sure that this compiler is
+available on your system, pick an other compiler and set PERLCC
+appropriately, or use a different perl setup that was compiled locally.
+
+I will disable the compilation of the RRDs perl module for now.
+])
+ COMP_PERL="perl_piped"
+ fi
+ fi
+ fi
+fi
+
+AC_MSG_CHECKING(Perl Modules to build)
+AC_MSG_RESULT(${COMP_PERL:-No Perl Modules will be built})
+
+# Options to pass when configuring perl module
+ppref=$prefix
+test "$ppref" = "NONE" && ppref=$ac_default_prefix
+
+PERL_MAKE_OPTIONS="PREFIX=$ppref LIB=$ppref/lib/perl/$PERL_VERSION"
+
+dnl pass additional perl options when generating Makefile from Makefile.PL
+AC_ARG_ENABLE(perl-site-install,
+[ --enable-perl-site-install by default the rrdtool perl modules are installed
+ together with rrdtool in $prefix/lib/perl. You have to
+ put a 'use lib qw($prefix/lib/perl)' into your scripts
+ when you want to use them. When you set this option
+ the perl modules will get installed wherever
+ your perl setup thinks it is best.],
+[PERL_MAKE_OPTIONS=],[])
+
+if test ! -z "$PERLCC"; then
+ PERL_MAKE_OPTIONS="$PERL_MAKE_OPTIONS CC=$PERLCC"
+
+ if test ! -z "$PERLCCFLAGS"; then
+ PERL_MAKE_OPTIONS="$PERL_MAKE_OPTIONS CCFLAGS=$PERLCCFLAGS"
+ fi
+
+ if test -z "$PERLLD"; then
+ PERLLD=$PERLCC
+ fi
+ PERL_MAKE_OPTIONS="$PERL_MAKE_OPTIONS LD=$PERLLD"
+
+ if test ! -z "$PERLLDFLAGS"; then
+ PERL_MAKE_OPTIONS="$PERL_MAKE_OPTIONS LDFLAGS=$PERLLDFLAGS"
+ fi
+fi
+
+AC_ARG_WITH(perl-options,
+[ --with-perl-options=[OPTIONS] options to pass on command-line when
+ generating Makefile from Makefile.PL. If you set this
+ option, interesting things may happen unless you know
+ what you are doing!],
+[PERL_MAKE_OPTIONS=$withval])
+
+AC_SUBST(PERL_MAKE_OPTIONS)
+AC_SUBST(PERL)
+AC_SUBST(COMP_PERL)
+AC_SUBST(PERL_VERSION)
+
+dnl Check for Ruby.
+AC_PATH_PROG(RUBY, ruby, no)
+
+AC_ARG_ENABLE(ruby,[ --disable-ruby do not build the ruby modules],
+[],[enable_ruby=yes])
+
+AC_MSG_CHECKING(if ruby modules can be built)
+
+if test "x$RUBY" = "xno" -o x$enable_ruby = xno; then
+ COMP_RUBY=
+ AC_MSG_RESULT(No .. Ruby not found or disabled)
+else
+ if $RUBY -e 'require "mkmf"' >/dev/null 2>&1; then
+ COMP_RUBY="ruby"
+ AC_MSG_RESULT(YES)
+ else
+ COMP_RUBY=
+ AC_MSG_RESULT(Ruby found but mkmf is missing! Install the -dev package)
+ fi
+fi
+
+
+dnl pass additional ruby options when generating Makefile from Makefile.PL
+AC_ARG_ENABLE(ruby-site-install,
+[ --enable-ruby-site-install by default the rrdtool ruby modules are installed
+ together with rrdtool in $prefix/lib/ruby. You have to
+ add $prefix/lib/ruby/$ruby_version/$sitearch to you $: variable
+ for ruby to find the RRD.so file.],
+[RUBY_MAKE_OPTIONS=],[RUBY_MAKE_OPTIONS="sitedir="'$(DESTDIR)'"$prefix/lib/ruby"])
+
+
+AC_ARG_WITH(ruby-options,
+[ --with-ruby-options=[OPTIONS] options to pass on command-line when
+ generating Makefile from extconf.rb. If you set this
+ option, interesting things may happen unless you know
+ what you are doing!],
+[RUBY_MAKE_OPTIONS=$withval])
+
+AC_SUBST(RUBY_MAKE_OPTIONS)
+AC_SUBST(RUBY)
+AC_SUBST(COMP_RUBY)
+
+
+enable_tcl_site=no
+
+AC_ARG_ENABLE(tcl,[ --disable-tcl do not build the tcl modules],
+[],[enable_tcl=yes])
+
+if test "$enable_tcl" = "yes"; then
+ dnl Check for Tcl.
+ withval=""
+ AC_ARG_WITH(tcllib,[ --with-tcllib=DIR location of the tclConfig.sh])
+ enable_tcl=no
+ for dir in $withval /usr/lib /usr/local/lib /usr/lib/tcl8.4 /usr/lib/tcl8.3 ; do
+ AC_MSG_CHECKING(for tclConfig.sh in $dir)
+ if test -f "$dir/tclConfig.sh" ; then
+ tcl_config=$dir/tclConfig.sh
+ enable_tcl=yes
+ AC_MSG_RESULT(yes)
+ break
+ else
+ AC_MSG_RESULT(no)
+ fi
+ done
+
+ if test "$enable_tcl" = "no"; then
+ AC_MSG_WARN([tclConfig.sh not found - Tcl interface will not be built])
+ else
+ . $tcl_config
+ TCL_PACKAGE_DIR="$TCL_PACKAGE_PATH/tclrrd$VERSION"
+ if test -n "$TCL_INC_DIR"; then
+ TCL_INCLUDE_SPEC="$TCL_INCLUDE_SPEC -I$TCL_INC_DIR"
+ fi
+ fi
+ AC_ARG_ENABLE(tcl,[ --enable-tcl-site install the tcl extension in the tcl tree],
+ [],[enable_tcl_site=yes])
+
+fi
+
+AM_CONDITIONAL(BUILD_TCL, test "$enable_tcl" = "yes" )
+AM_CONDITIONAL(BUILD_TCL_SITE, test "$enable_tcl_site" = "yes" )
+
+
+AC_SUBST(TCL_PREFIX)
+AC_SUBST(TCL_SHLIB_CFLAGS)
+AC_SUBST(TCL_SHLIB_LD)
+AC_SUBST(TCL_SHLIB_SUFFIX)
+AC_SUBST(TCL_PACKAGE_PATH)
+AC_SUBST(TCL_LD_SEARCH_FLAGS)
+AC_SUBST(TCL_STUB_LIB_SPEC)
+AC_SUBST(TCL_VERSION)
+AC_SUBST(TCL_PACKAGE_DIR)
+AC_SUBST(TCL_INCLUDE_SPEC)
+
+AC_ARG_ENABLE(python,[ --disable-python do not build the python modules],
+[],[enable_python=yes])
+
+if test "$enable_python" = "yes"; then
+dnl Check for python
+AM_PATH_PYTHON(2.3,[],[enable_python=no])
+AM_CHECK_PYTHON_HEADERS(,[enable_python=no;AC_MSG_WARN(could not find Python headers)])
+fi
+
+if test x$enable_python = xno; then
+ COMP_PYTHON=
+else
+ COMP_PYTHON="python"
+fi
+
+AC_SUBST(COMP_PYTHON)
+
+dnl Check for nroff
+AC_PATH_PROGS(NROFF, gnroff nroff)
+AC_PATH_PROGS(TROFF, groff troff)
+
+AC_ARG_VAR(RRDDOCDIR, [[DATADIR/doc/PACKAGE-VERSION] Documentation directory])
+if test -z "$RRDDOCDIR"; then
+ RRDDOCDIR='${datadir}/doc/${PACKAGE}-${VERSION}'; fi
+
+
+CONFIGURE_PART(Apply Configuration Information)
+
+AC_CONFIG_FILES([examples/shared-demo.pl])
+AC_CONFIG_FILES([examples/piped-demo.pl])
+AC_CONFIG_FILES([examples/stripes.pl])
+AC_CONFIG_FILES([examples/bigtops.pl])
+AC_CONFIG_FILES([examples/minmax.pl])
+AC_CONFIG_FILES([examples/4charts.pl])
+AC_CONFIG_FILES([examples/perftest.pl])
+AC_CONFIG_FILES([examples/Makefile])
+AC_CONFIG_FILES([doc/Makefile])
+AC_CONFIG_FILES([po/Makefile.in])
+AC_CONFIG_FILES([src/Makefile])
+AC_CONFIG_FILES([src/librrd.sym])
+AC_CONFIG_FILES([bindings/Makefile])
+AC_CONFIG_FILES([bindings/tcl/Makefile])
+AC_CONFIG_FILES([bindings/tcl/ifOctets.tcl])
+AC_CONFIG_FILES([Makefile])
+
+AC_CONFIG_COMMANDS([default],[[ chmod +x examples/*.pl]],[[]])
+AC_OUTPUT
+
+AC_MSG_CHECKING(in)
+AC_MSG_RESULT(and out again)
+
+echo $ECHO_N "ordering CD from http://tobi.oetiker.ch/wish $ECHO_C" 1>&6
+sleep 1
+echo $ECHO_N ".$ECHO_C" 1>&6
+sleep 1
+echo $ECHO_N ".$ECHO_C" 1>&6
+sleep 1
+echo $ECHO_N ".$ECHO_C" 1>&6
+sleep 1
+echo $ECHO_N ".$ECHO_C" 1>&6
+sleep 1
+AC_MSG_RESULT([ just kidding ;-)])
+echo
+echo "----------------------------------------------------------------"
+echo "Config is DONE!"
+echo
+echo " With MMAP IO: $enable_mmap"
+echo " Build rrd_getopt: $build_getopt"
+echo " Static programs: $staticprogs"
+echo " Perl Modules: $COMP_PERL"
+echo " Perl Binary: $PERL"
+echo " Perl Version: $PERL_VERSION"
+echo " Perl Options: $PERL_MAKE_OPTIONS"
+echo " Ruby Modules: $COMP_RUBY"
+echo " Ruby Binary: $RUBY"
+echo " Ruby Options: $RUBY_MAKE_OPTIONS"
+echo " Build Tcl Bindings: $enable_tcl"
+echo " Build Python Bindings: $enable_python"
+echo " Build rrdcgi: $enable_rrdcgi"
+echo " Build librrd MT: $enable_pthread"
+echo " Link with libintl: $enable_libintl"
+echo
+echo " Libraries: $ALL_LIBS"
+echo
+echo "Type 'make' to compile the software and use 'make install' to "
+echo "install everything to: $prefix."
+echo
+echo " ... that wishlist is NO JOKE. If you find RRDtool useful"
+echo "make me happy. Go to http://tobi.oetiker.ch/wish and"
+echo "place an order."
+echo
+echo " -- Tobi Oetiker <tobi@oetiker.ch>"
+echo "----------------------------------------------------------------"
diff --git a/program/debian/README.Debian b/program/debian/README.Debian
--- /dev/null
@@ -0,0 +1,33 @@
+rrdtool for Debian
+----------------------
+
+The RRDtool distribution is split into several packages:
+
+
+Package: rrdtool
+ Command-line utilities and documentation
+
+Package: librrd0
+ Shared library
+
+Package: librrd0-dev
+ Static library
+
+Package: librrds-perl
+ Perl interface using the shared library (RRDs)
+
+Package: librrdp-perl
+ Perl interface using command pipes to the rrdtool program (RRDp)
+
+Package: rrdtool-tcl
+ TCL interface using the shared library (librrd)
+
+
+Original by
+
+ -- Matt Zimmerman <mdz@csh.rit.edu>, Fri, 10 Sep 1999 10:53:19 -0700
+
+Hacked for 1.1.x by
+
+ -- Mike Slifcak <slif@bellsouth.net>, Tue , 18 May 2004 20:38:40 +0200
+
diff --git a/program/debian/build_freetype.sh b/program/debian/build_freetype.sh
--- /dev/null
@@ -0,0 +1,42 @@
+:
+# Build freetype2 for rrdtool on Debian/Linux
+# 12-May-2004 Mike Slifcak
+
+FOUND=`find /lib /usr/lib /usr/local/lib -name libfreetype.a | wc -l`
+FOUND=`echo $FOUND`
+if [ $FOUND -lt 1 ] ; then
+########################################
+## Build the independent object freetype2
+########################################
+cd /tmp
+rm -rf freetype*/
+if [ ! -e freetype*gz ] ; then
+ echo "get freetype-2.1.8 or stable from http://freetype.sf.net/"
+ exit 1
+fi
+tar tzf freetype*gz > /dev/null 2>&1
+RC=$?
+if [ $RC -ne 0 ] ; then
+ echo "Need one good freetype*gz. Just one. In /tmp. Thanks!"
+ exit 1
+fi
+
+echo -n "Testing "
+ls freetype*gz
+tar xzf freetype*gz
+echo "Building freetype"
+cd freetype-*/
+./configure --disable-shared > cfg.out 2>&1
+make > make.out 2>&1
+make install > inst.out 2>&1
+grep Error *.out
+if [ $? -ne 1 ] ; then
+ echo "Building freetype failed. See `pwd`/*.out for details"
+ exit 1
+fi
+cd ..
+fi ## skip freetype build
+
+echo "Building freetype succeeded."
+
+exit 0
diff --git a/program/debian/changelog b/program/debian/changelog
--- /dev/null
+++ b/program/debian/changelog
@@ -0,0 +1,379 @@
+rrdtool (1.1.0-1) unstable; urgency=low
+
+ * Fixed build dependencies
+ * Added libfreetype6-dev to build dependencies
+ * Changed rules file to compile against Debian version of freetype.
+
+ -- Peter Hirdina <Peter.Hirdina@gmx.net> Mon, 7 Jun 2004 17:10:10 +0200
+
+rrdtool (1.1.0) unstable; urgency=low
+
+ * grafted "debian" directory from 1.0.46 source
+ * no tcl package support due to build failures (see ##NO_TCL )
+
+ -- Mike Slifcak <> Wed, 12 May 2004 20:53:16 -0400
+
+rrdtool (1.0.46-3) unstable; urgency=low
+
+ * Add dependencies to librrd0-dev to ensure that static linking is
+ possible: libgd-gif1-dev, zlib1g-dev
+ * #include <stdio.h> in rrd.h (Closes: #238849)
+ * Only link rrdcgi with -lcgi, not librrd
+
+ -- Matt Zimmerman <mdz@debian.org> Fri, 19 Mar 2004 11:38:39 -0800
+
+rrdtool (1.0.46-2) unstable; urgency=medium
+
+ * Fix tcl module installation (Closes: #231171)
+
+ -- Matt Zimmerman <mdz@debian.org> Wed, 4 Feb 2004 15:35:46 -0800
+
+rrdtool (1.0.46-1) unstable; urgency=low
+
+ * New upstream release
+
+ -- Matt Zimmerman <mdz@debian.org> Mon, 12 Jan 2004 23:22:09 -0800
+
+rrdtool (1.0.45-1) unstable; urgency=low
+
+ * New upstream release
+ * Deal with changed tclrrd naming scheme
+ * Build with tcl8.4
+ * Remove old dpkg-dev build-dependency; even woody is new enough
+
+ -- Matt Zimmerman <mdz@debian.org> Wed, 17 Dec 2003 14:09:11 -0800
+
+rrdtool (1.0.42-2) unstable; urgency=low
+
+ * Build-Depends: libpng12-dev instead of libpng3-dev (when will the madness end?)
+ (Closes: #195224
+ I hope this will also fix weird version mismatch problems (Closes: #194900)
+
+ -- Matt Zimmerman <mdz@debian.org> Sat, 31 May 2003 15:06:42 -0400
+
+rrdtool (1.0.42-1) unstable; urgency=low
+
+ * New upstream release
+ * librrd0-dev Section: libdevel
+ * librrd[sp]-perl Section: perl
+ * Build with libpng3 (Closes: #189493)
+
+ -- Matt Zimmerman <mdz@debian.org> Thu, 15 May 2003 19:44:37 -0400
+
+rrdtool (1.0.40-2) unstable; urgency=low
+
+ * Correctly suppress non-image output when writing image to stdout
+ (Closes: #182217)
+
+ -- Matt Zimmerman <mdz@debian.org> Sat, 1 Mar 2003 16:59:39 -0500
+
+rrdtool (1.0.40-1) unstable; urgency=low
+
+ * New upstream release
+ * Remove unnecessary rrd_free to avoid crash on invalid option
+ (Closes: #166156)
+ * Clean example source code; some libtool cruft was getting installed in
+ /usr/share/doc/examples
+
+ -- Matt Zimmerman <mdz@debian.org> Tue, 17 Dec 2002 22:11:46 -0500
+
+rrdtool (1.0.39-2) unstable; urgency=low
+
+ * Rebuild for perl 5.8 (Closes: #158728)
+
+ -- Matt Zimmerman <mdz@debian.org> Sun, 25 Aug 2002 17:29:41 -0400
+
+rrdtool (1.0.39-1) unstable; urgency=low
+
+ * New upstream release
+
+ -- Matt Zimmerman <mdz@debian.org> Wed, 31 Jul 2002 00:40:00 -0400
+
+rrdtool (1.0.38-1) unstable; urgency=low
+
+ * New upstream release (Closes: #148486)
+
+ -- Matt Zimmerman <mdz@debian.org> Wed, 29 May 2002 12:28:28 -0400
+
+rrdtool (1.0.35-2) unstable; urgency=low
+
+ * Remove CVS directories with rm -rf. This broke autobuilds entirely.
+ (Closes: #140075)
+
+ -- Matt Zimmerman <mdz@debian.org> Tue, 26 Mar 2002 17:15:01 -0500
+
+rrdtool (1.0.35-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Matt Zimmerman <mdz@debian.org> Sun, 24 Mar 2002 11:29:10 -0500
+
+rrdtool (1.0.33-9) unstable; urgency=low
+
+ * Call dh_shlibdeps with -l to allow it to find dependencies correctly
+ (Closes: #118629)
+ * Add call to ldconfig in postrm of librrd0
+ * Remove .cvsignore files from installed examples
+
+ -- Matt Zimmerman <mdz@debian.org> Sat, 10 Nov 2001 14:14:51 -0500
+
+rrdtool (1.0.33-8) unstable; urgency=low
+
+ * Use AM_MAINTAINER_MODE to keep the makefiles from trying to rebuild
+ autoconf/automake stuff during the build (Closes: #117599)
+ * Regenerated everything AGAIN.
+ * Use DESTDIR instead of prefix= during "make install"
+
+ -- Matt Zimmerman <mdz@debian.org> Mon, 29 Oct 2001 22:22:07 -0500
+
+rrdtool (1.0.33-7) unstable; urgency=low
+
+ * Add an example to the rrddump documentation showing what is necessary
+ to transfer an RRD from one architecture to another. (Closes: #117147)
+ * Regenerated autoconf/automake configs
+
+ -- Matt Zimmerman <mdz@debian.org> Mon, 29 Oct 2001 02:28:16 -0500
+
+rrdtool (1.0.33-6) unstable; urgency=low
+
+ * Spelling corrections from Martin Schulze (Closes: #110784)
+
+ -- Matt Zimmerman <mdz@debian.org> Sat, 8 Sep 2001 13:13:22 -0400
+
+rrdtool (1.0.33-5) unstable; urgency=low
+
+ * Ship with pre-generated files from autoconf2.50/automake, rather than
+ pulling them in at build-time, in order to work around the current
+ libtool mess. This makes the diff less manageable, but should make
+ the build more stable.
+ * debian/control: remove build-dependencies on autoconf/automake/libtool
+ * debian/rules: don't call autoconf/automake
+ * Fix shlibs file (it was trying to use a substitution)
+ * configure.in: remove makefiles that no longer exist from AC_OUTPUT.
+ Apparently this is an error in autoconf 2.50.
+ * configure.in: Remove wacky logic to pull PIC flags out of libtool, and
+ let libtool do the right thing.
+ * debian/rules: shlibs.local hackery is no longer necessary, removed
+
+ -- Matt Zimmerman <mdz@debian.org> Fri, 3 Aug 2001 17:36:43 -0400
+
+rrdtool (1.0.33-4) unstable; urgency=low
+
+ * Add build-dependencies on libtool and autoconf (Closes: #105655)
+
+ -- Matt Zimmerman <mdz@debian.org> Tue, 17 Jul 2001 15:44:25 -0400
+
+rrdtool (1.0.33-3) unstable; urgency=low
+
+ * Update config.{sub,guess} from autotools-dev 20010702.1
+ (Closes: #105037)
+ * Update ltmain.sh for good measure, from libtool 1.4-1
+ * Build-Depend on autoconf, and run autoconf and aclocal from
+ debian/rules (in addition to automake)
+
+ -- Matt Zimmerman <mdz@debian.org> Sat, 14 Jul 2001 03:19:22 -0400
+
+rrdtool (1.0.33-2) unstable; urgency=low
+
+ * Rebuild with latest tcl8.3-dev, to fix shared object extension
+
+ -- Matt Zimmerman <mdz@debian.org> Sat, 3 Mar 2001 17:28:24 -0500
+
+rrdtool (1.0.33-1) unstable; urgency=low
+
+ * New upstream version.
+ * This is yet another patch release to fix some distribution/compilation
+ problems. There should be no visible changes for Debian users from
+ 1.0.32-3.
+
+ -- Matt Zimmerman <mdz@debian.org> Thu, 1 Mar 2001 02:27:53 -0500
+
+rrdtool (1.0.32-3) unstable; urgency=low
+
+ * Fix the tcl extension module to build against tcl8.3, rather than
+ tcl8.0 (Closes: #87357)
+ * debian/control: Build-depend on tcl8.3-dev
+ * debian/rules: tclconfigdir = /usr/lib/tcl8.3
+ * tcl/Makefile.am: (upstream) -I/usr/include/tcl@TCL_VERSION@
+ * configure.in: (upstream) AC_SUBST(TCL_VERSION)
+
+ -- Matt Zimmerman <mdz@debian.org> Sat, 24 Feb 2001 02:25:46 -0500
+
+rrdtool (1.0.32-2) unstable; urgency=low
+
+ * List changes to upstream source in the copyright file (as well as the
+ changelog)
+ * Link against libgd-gif instead of libgd, to restore GIF support
+
+ -- Matt Zimmerman <mdz@debian.org> Fri, 23 Feb 2001 15:27:37 -0500
+
+rrdtool (1.0.32-1) unstable; urgency=low
+
+ * New upstream version. Hopefully this one will last more than 24
+ hours (Closes: #75771)
+
+ -- Matt Zimmerman <mdz@debian.org> Wed, 21 Feb 2001 20:13:09 -0500
+
+rrdtool (1.0.27-7) unstable; urgency=low
+
+ * debian/rules: Updated for new Perl policy
+ * debian/rules: Don't use install-stamp; it causes more problems than it
+ prevents
+ * debian/rules: Don't call dh_suidregister; we didn't use it anyway
+ * debian/control: Build-depend on the new debhelper for dh_perl
+ * debian/control: rrdtool-tcl only seems to work with 8.0, so depend on
+ tcl8.0
+ * debian/control: Build-depend on perl 5.6.0+
+ * debian/librrd?-perl: Updated for new Perl filesystem layout
+
+ -- Matt Zimmerman <mdz@debian.org> Thu, 15 Feb 2001 19:04:11 -0500
+
+rrdtool (1.0.27-6) unstable; urgency=low
+
+ * debian/rules: Handle DEB_BUILD_OPTIONS debug, nostrip
+ * debian/rules: Separate out "configure" target
+ * debian/rules: Miscellaneous cleanup
+ * Recompile librrds-perl against perl-5.6
+
+ -- Matt Zimmerman <mdz@debian.org> Tue, 19 Dec 2000 21:23:55 -0500
+
+rrdtool (1.0.27-5) unstable; urgency=low
+
+ * Fix Build-Depends to reflect our dependency on dpkg-dev >= 1.7.0 |
+ librrd0. The old dpkg can only calculate our dependencies correctly
+ if the runtime library is installed on the system, so potato users
+ must either install the potato librrd0 or the new dpkg-dev
+ (Closes: #77479)
+
+ -- Matt Zimmerman <mdz@debian.org> Mon, 20 Nov 2000 20:39:55 -0500
+
+rrdtool (1.0.27-4) unstable; urgency=low
+
+ * Updated to comply with policy 3.2.1.0:
+ - Don't build with -g by default
+ * Build-Depend on tcl8.0-dev (not tcl-dev), since that is the only
+ version we currently work with.
+ * Added automake to Build-Depends (oops)
+
+ -- Matt Zimmerman <mdz@debian.org> Sat, 18 Nov 2000 22:29:02 -0500
+
+rrdtool (1.0.27-3) unstable; urgency=low
+
+ * Big packaging cleanup release.
+ * Fixed perl module compilation to link with the shared library, rather
+ than including a copy of the static library inside the .so (also fixes
+ a lintian error, shlib-with-non-pic-code in librrds-perl)
+ * Fixed tcl module compilation to link with the shared library, rather
+ than including a copy of the static library inside the .so (also fixes
+ a lintian error, shlib-with-non-pic-code in rrdtool-tcl)
+ * The above shrunk the size of the perl and tcl library packages from
+ 60k each to about 20k (perl) and 16k (tcl)
+ * We can now use shlibdeps to calculate the dependencies for
+ librrds-perl, so those will be more correct now
+ * Rather than include Makefile.in changes in the .diff.gz, just keep our
+ modifications to Makefile.am and run automake in debian/rules. This
+ makes the .diff.gz much smaller and cleaner
+ * Fixed shared library dependencies all around to be smarter
+ * rrdtool package now Suggests librrds-perl (rather than depending on
+ perl), as it is only needed for an auxiliary script for converting
+ mrtg data
+
+ -- Matt Zimmerman <mdz@debian.org> Fri, 10 Nov 2000 19:34:05 -0500
+
+rrdtool (1.0.27-2) unstable; urgency=low
+
+ * Updated maintainer email address (now official maintainer)
+
+ -- Matt Zimmerman <mdz@debian.org> Wed, 1 Nov 2000 22:44:41 -0500
+
+rrdtool (1.0.27-1) unstable; urgency=low
+
+ * New upstream version.
+
+ -- Matt Zimmerman <mdz@csh.rit.edu> Wed, 13 Sep 2000 13:10:14 -0400
+
+rrdtool (1.0.26-1) unstable; urgency=low
+
+ * New upstream version.
+
+ -- Matt Zimmerman <mdz@csh.rit.edu> Sun, 10 Sep 2000 16:34:08 -0400
+
+rrdtool (1.0.25-1) unstable; urgency=low
+
+ * New upstream version.
+
+ -- Matt Zimmerman <mdz@csh.rit.edu> Sun, 23 Jul 2000 02:23:53 -0400
+
+rrdtool (1.0.24-2) unstable; urgency=low
+
+ * Updated for libgd 1.8.3 (build depends, recompile)
+
+ -- Matt Zimmerman <mdz@csh.rit.edu> Fri, 7 Jul 2000 19:33:29 -0400
+
+rrdtool (1.0.24-1) unstable; urgency=low
+
+ * New upstream version (Closes: #61997).
+
+ -- Matt Zimmerman <mdz@csh.rit.edu> Fri, 30 Jun 2000 13:53:27 -0400
+
+rrdtool (1.0.17-1) unstable; urgency=low
+
+ * New upstream version.
+ * Now creates rrdtool-tcl, containing tcl bindings.
+
+ -- Matt Zimmerman <mdz@csh.rit.edu> Wed, 26 Apr 2000 13:16:24 -0700
+
+rrdtool (1.0.16-1) unstable; urgency=low
+
+ * New upstream version.
+ * Not released.
+
+ -- Matt Zimmerman <mdz@csh.rit.edu> Wed, 26 Apr 2000 10:53:36 -0700
+
+rrdtool (1.0.13-2) unstable; urgency=low
+
+ * Build-Depends: cgilib, zlib1g-dev, libpng2-dev, libgd1g-dev, groff,
+ debhelper. Hopefully that's everything.
+
+ -- Matt Zimmerman <mdz@csh.rit.edu> Fri, 7 Apr 2000 14:16:55 -0700
+
+rrdtool (1.0.13-1) unstable; urgency=low
+
+ * New upstream version (Closes: #58583)
+ * Added Build-Depends for cgilib (Closes: #55270)
+
+ -- Matt Zimmerman <mdz@csh.rit.edu> Thu, 23 Mar 2000 15:55:37 -0800
+
+rrdtool (1.0.10-1) unstable; urgency=low
+
+ * New upstream version.
+
+ -- Matt Zimmerman <mdz@csh.rit.edu> Mon, 10 Jan 2000 16:46:33 -0800
+
+rrdtool (1.0.7-3) unstable; urgency=low
+
+ * Non-i386 fixes, thanks to Christopher C Chimelis <chris@debian.org>:
+ * Changed 'clean' target in debian/rules to remove the auto-generated
+ Makefiles for the Perl modules
+ * Changed Depends: for librrds-perl to use shlibdeps
+
+ -- Matt Zimmerman <mdz@csh.rit.edu> Mon, 22 Nov 1999 10:47:13 -0800
+
+rrdtool (1.0.7-2) unstable; urgency=low
+
+ * Linked RRDs.so against libpng, closes: #49701
+
+ -- Matt Zimmerman <mdz@csh.rit.edu> Tue, 9 Nov 1999 15:02:40 -0800
+
+rrdtool (1.0.7-1) unstable; urgency=low
+
+ * Initial Release.
+ * Upstream source ships with cgilib-0.4, gd-1.3, libpng-1.0.3, and
+ zlib-1.3.3 (!). Build process is modified to skip building these, and
+ instead link with the Debian versions.
+ * Modifications for gd 1.6.1 included removal of GIF support (no longer
+ included with gd as of 1.6)
+
+ -- Matt Zimmerman <mdz@csh.rit.edu> Sat, 11 Sep 1999 13:29:29 -0700
+
+
diff --git a/program/debian/control b/program/debian/control
--- /dev/null
+++ b/program/debian/control
@@ -0,0 +1,98 @@
+Source: rrdtool
+Section: utils
+Priority: extra
+Maintainer: Matt Zimmerman <mdz@debian.org>
+Standards-Version: 3.2.1
+Build-Depends: debhelper (>= 3.0.5), cgilib (>= 0.5), zlib1g-dev (>= 1.2.1), tcl8.4-dev, perl (>= 5.8.0), libart-2.0-dev (>= 2.3.16), libpng12-dev (>= 1.2.5), libfreetype6-dev (>= 2.1.7)
+
+Package: rrdtool
+Architecture: any
+Depends: ${shlibs:Depends}
+Suggests: librrds-perl
+Description: Time-series data storage and display system (programs)
+ RRD is the Acronym for Round Robin Database. RRD is a system to store and
+ display time-series data (i.e. network bandwidth, machine-room temperature,
+ server load average). It stores the data in a very compact way that will
+ not expand over time, and it presents useful graphs by processing the data
+ to enforce a certain data density. It can be used either via simple wrapper
+ scripts (from shell or Perl) or via frontends that poll network devices and
+ put friendly user interface on it.
+ .
+ This package contains command line programs used to access and manipulate
+ RRDs.
+
+Package: librrd0
+Architecture: any
+Section: libs
+Priority: optional
+Depends: ${shlibs:Depends}
+Description: Time-series data storage and display system (runtime)
+ RRD is the Acronym for Round Robin Database. RRD is a system to store and
+ display time-series data (i.e. network bandwidth, machine-room temperature,
+ server load average). It stores the data in a very compact way that will
+ not expand over time, and it presents useful graphs by processing the data
+ to enforce a certain data density. It can be used either via simple wrapper
+ scripts (from shell or Perl) or via frontends that poll network devices and
+ put friendly user interface on it.
+ .
+ This package contains shared libraries used to access and manipulate RRDs.
+
+Package: librrd0-dev
+Architecture: any
+Section: libdevel
+Depends: librrd0 (= ${Source-Version}), ${shlibs:Depends}
+Description: Time-series data storage and display system (development)
+ RRD is the Acronym for Round Robin Database. RRD is a system to store and
+ display time-series data (i.e. network bandwidth, machine-room temperature,
+ server load average). It stores the data in a very compact way that will
+ not expand over time, and it presents useful graphs by processing the data
+ to enforce a certain data density. It can be used either via simple wrapper
+ scripts (from shell or Perl) or via frontends that poll network devices and
+ put friendly user interface on it.
+ .
+ This package contains libraries used to develop software that uses RRDs.
+
+Package: librrds-perl
+Architecture: any
+Section: perl
+Depends: ${perl:Depends}, ${shlibs:Depends}
+Description: Time-series data storage and display system (perl-shared)
+ RRD is the Acronym for Round Robin Database. RRD is a system to store and
+ display time-series data (i.e. network bandwidth, machine-room temperature,
+ server load average). It stores the data in a very compact way that will
+ not expand over time, and it presents useful graphs by processing the data
+ to enforce a certain data density. It can be used either via simple wrapper
+ scripts (from shell or Perl) or via frontends that poll network devices and
+ put friendly user interface on it.
+ .
+ This package contains a Perl interface to RRDs using a shared library.
+
+Package: librrdp-perl
+Architecture: all
+Section: perl
+Depends: ${perl:Depends}, rrdtool
+Description: Time-series data storage and display system (perl-piped)
+ RRD is the Acronym for Round Robin Database. RRD is a system to store and
+ display time-series data (i.e. network bandwidth, machine-room temperature,
+ server load average). It stores the data in a very compact way that will
+ not expand over time, and it presents useful graphs by processing the data
+ to enforce a certain data density. It can be used either via simple wrapper
+ scripts (from shell or Perl) or via frontends that poll network devices and
+ put friendly user interface on it.
+ .
+ This package contains a Perl interface to RRDs using command pipes.
+
+#NO_TCL Package: rrdtool-tcl
+#NO_TCL Architecture: any
+#NO_TCL Section: utils
+#NO_TCL Depends: ${shlibs:Depends}, tcl8.4
+#NO_TCL Description: Time-series data storage and display system (tcl)
+#NO_TCL RRD is the Acronym for Round Robin Database. RRD is a system to store and
+#NO_TCL display time-series data (i.e. network bandwidth, machine-room temperature,
+#NO_TCL server load average). It stores the data in a very compact way that will
+#NO_TCL not expand over time, and it presents useful graphs by processing the data
+#NO_TCL to enforce a certain data density. It can be used either via simple wrapper
+#NO_TCL scripts (from shell or Perl) or via frontends that poll network devices and
+#NO_TCL put friendly user interface on it.
+#NO_TCL .
+#NO_TCL This package contains a tcl interface to RRDs.
diff --git a/program/debian/copyright b/program/debian/copyright
--- /dev/null
+++ b/program/debian/copyright
@@ -0,0 +1,30 @@
+This package was debianized by Matt Zimmerman <mdz@debian.org> on
+Fri, 10 Sep 1999 10:53:19 -0700.
+
+Copied from 1.0.46 to 1.1.0 snapshot by Mike Slifcak on Wed May 12 20:53:16 EDT 2004
+
+The source package was downloaded from
+ http://oss.oetiker.ch/rrdtool/pub/
+
+Upstream Author(s): Tobias Oetiker <tobi@oetiker.ch>
+
+Modifications to upstream source:
+ - Build and install shared libraries
+ - Link everything dynamically
+ - Link with Debian versions of libraries rather than using the
+ supplied (old) versions
+
+Copyright:
+
+ RRDTOOL - Round Robin Database Tool
+ A tool for fast logging of numerical data graphical display
+ of this data.
+
+ Copyright (c) 1998, 1999 Tobias Oetiker
+ All rights reserved.
+
+ RRDtool is distributed under the terms of the GNU General Public License,
+ version 2.
+
+ On Debian GNU/Linux systems, you can find a copy of the GNU General Public
+ License in `/usr/share/common-licenses/GPL'.
diff --git a/program/debian/librrd0-dev.files b/program/debian/librrd0-dev.files
--- /dev/null
@@ -0,0 +1,5 @@
+usr/lib/librrd.a
+usr/lib/librrd.so
+usr/lib/librrd_th.a
+usr/lib/librrd_th.so
+usr/include/*
diff --git a/program/debian/librrd0.files b/program/debian/librrd0.files
--- /dev/null
@@ -0,0 +1,6 @@
+usr/lib/librrd.so.1.0.0
+usr/lib/librrd.so.1
+usr/lib/librrd.la
+usr/lib/librrd_th.so.1.0.0
+usr/lib/librrd_th.so.1
+usr/lib/librrd_th.la
diff --git a/program/debian/librrd0.postinst b/program/debian/librrd0.postinst
--- /dev/null
@@ -0,0 +1,49 @@
+#! /bin/sh
+# postinst script for #PACKAGE#
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <postinst> `configure' <most-recently-configured-version>
+# * <old-postinst> `abort-upgrade' <new version>
+# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
+# <new-version>
+# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
+# <failed-install-package> <version> `removing'
+# <conflicting-package> <version>
+# for details, see /usr/doc/packaging-manual/
+#
+# quoting from the policy:
+# Any necessary prompting should almost always be confined to the
+# post-installation script, and should be protected with a conditional
+# so that unnecessary prompting doesn't happen if a package's
+# installation fails and the `postinst' is called with `abort-upgrade',
+# `abort-remove' or `abort-deconfigure'.
+
+case "$1" in
+ configure)
+
+ ldconfig
+
+ ;;
+
+ abort-upgrade|abort-remove|abort-deconfigure)
+
+ ;;
+
+ *)
+ echo "postinst called with unknown argument \`$1'" >&2
+ exit 0
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/program/debian/librrd0.postrm b/program/debian/librrd0.postrm
--- /dev/null
@@ -0,0 +1,37 @@
+#! /bin/sh
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <postrm> `remove'
+# * <postrm> `purge'
+# * <old-postrm> `upgrade' <new-version>
+# * <new-postrm> `failed-upgrade' <old-version>
+# * <new-postrm> `abort-install'
+# * <new-postrm> `abort-install' <old-version>
+# * <new-postrm> `abort-upgrade' <old-version>
+# * <disappearer's-postrm> `disappear' <r>overwrit>r> <new-version>
+# for details, see /usr/share/doc/packaging-manual/
+
+case "$1" in
+ remove)
+ ldconfig
+ ;;
+
+ purge|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
+ ;;
+
+ *)
+ echo "postrm called with unknown argument \`$1'" >&2
+ exit 0
+
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+
diff --git a/program/debian/librrd0.shlibs b/program/debian/librrd0.shlibs
--- /dev/null
@@ -0,0 +1 @@
+librrd 0 librrd0 (>= 1.0.10-1)
diff --git a/program/debian/librrdp-perl.files b/program/debian/librrdp-perl.files
--- /dev/null
@@ -0,0 +1,2 @@
+usr/lib/perl5/RRDp.pm
+usr/share/man/man3/RRDp.3pm
diff --git a/program/debian/librrds-perl.files b/program/debian/librrds-perl.files
--- /dev/null
@@ -0,0 +1,3 @@
+usr/lib/perl5/auto
+usr/lib/perl5/RRDs.pm
+usr/share/man/man3/RRDs.3pm
diff --git a/program/debian/rrdtool-tcl.files b/program/debian/rrdtool-tcl.files
--- /dev/null
@@ -0,0 +1 @@
+usr/lib/tclrrd*
diff --git a/program/debian/rrdtool.files b/program/debian/rrdtool.files
--- /dev/null
@@ -0,0 +1,4 @@
+usr/bin
+usr/share/man/man1
+usr/share/doc/rrdtool
+usr/share/rrdtool
diff --git a/program/debian/rules b/program/debian/rules
--- /dev/null
+++ b/program/debian/rules
@@ -0,0 +1,178 @@
+#!/usr/bin/make -f
+#-*- makefile -*-
+# Made with the aid of dh_make, by Craig Small
+# Sample debian/rules that uses debhelper. GNU copyright 1997 by Joey Hess.
+# This version is for a hypothetical package that builds an
+# architecture-dependant package, as well as an architecture-independant
+# package.
+
+package:=rrdtool
+
+top_srcdir:=$(shell pwd)
+tmp:=$(top_srcdir)/debian/tmp
+
+# TCL installation stuff (must match tcl/Makefile.am)
+tclconfigdir = /usr/lib/tcl8.4
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+# This is the debhelper compatability version to use.
+export DH_COMPAT=2
+
+# Defaults
+CFLAGS := -O2
+
+# Handle DEB_BUILD_OPTIONS
+ifneq "$(findstring debug,$(DEB_BUILD_OPTIONS))" ""
+CFLAGS += -g
+endif
+
+# Whack the flags to compile additional dependent pieces
+CFLAGS += -I/usr/include/libart-2.0 -I/usr/include/freetype2
+
+##NO_TCL CFLAGS += -I/usr/include/tcl8.4
+
+configure: configure-stamp
+configure-stamp:
+ dh_testdir
+ # Make sure we can find tcl stuff (configure won't fail)
+ test -f $(tclconfigdir)/tclConfig.sh
+
+ # match the configure script to the build environment, as needed
+ #aclocal
+ #automake
+ #autoconf
+
+ # Configure C and Tcl stuff
+##NO_TCL CFLAGS="$(CFLAGS)" ./configure --prefix=/usr --enable-shared=yes --enable-static=yes --with-tcllib=$(tclconfigdir) --enable-local-libpng=yes --enable-local-zlib=yes
+ CFLAGS="$(CFLAGS)" ./configure --prefix=/usr --enable-shared=yes --enable-static=yes --enable-local-libpng=yes --enable-local-zlib=yes
+
+ # Configure Perl stuff
+ cd bindings/perl-piped && \
+ rm -f Makefile && \
+ perl Makefile.PL INSTALLDIRS=vendor
+
+ cd bindings/perl-shared && \
+ rm -f Makefile && \
+ perl Makefile.PL INSTALLDIRS=vendor
+
+ touch configure-stamp
+
+build: build-stamp
+build-stamp: configure-stamp
+ dh_testdir
+
+ # Build most stuff
+ $(MAKE)
+
+ # We now build perl-piped and perl-shared separately
+ cd bindings/perl-piped && make OPTIMIZE="$(CFLAGS)"
+ cd bindings/perl-shared && make OPTIMIZE="$(CFLAGS)"
+
+ touch build-stamp
+
+clean:
+ dh_testdir
+ dh_testroot
+ rm -f build-stamp configure-stamp
+
+ # Add here commands to clean up after the build process.
+ -$(MAKE) distclean
+
+ -rm bindings/perl*/Makefile.old bindings/perl*/Makefile
+
+ dh_clean
+
+install: build-stamp
+ dh_testdir
+ dh_testroot
+ dh_clean -k
+ dh_installdirs
+
+ # Add here commands to install the package into debian/tmp.
+##NO_TCL $(MAKE) install site-tcl-install DESTDIR=$(tmp)
+ $(MAKE) install DESTDIR=$(tmp)
+
+ mkdir -p $(tmp)/usr/share/doc
+ mv $(tmp)/usr/doc $(tmp)/usr/share/doc/rrdtool
+ mkdir -p $(tmp)/usr/share/rrdtool
+ mv $(tmp)/usr/examples $(tmp)/usr/share/rrdtool
+ mv $(tmp)/usr/html $(tmp)/usr/share/rrdtool
+
+ ## touch-up perl docs
+ mv $(tmp)/usr/man $(tmp)/usr/share
+ mkdir -p $(tmp)/usr/share/man/man3
+ mv $(tmp)/usr/share/man/man1/RRDs.1 $(tmp)/usr/share/man/man3/RRDs.3pm
+ mv $(tmp)/usr/share/man/man1/RRDp.1 $(tmp)/usr/share/man/man3/RRDp.3pm
+ mv $(tmp)/usr/lib/perl $(tmp)/usr/lib/perl5
+
+ ## trim off the doc sources
+ find $(tmp) -name "*.pod" | xargs rm -f
+
+ dh_movefiles
+
+# Build architecture-independent files here.
+binary-indep: build install
+# dh_testversion
+ dh_testdir -i
+ dh_testroot -i
+ dh_installdocs -i
+# dh_installexamples -i
+# dh_installmenu -i
+# dh_installemacsen -i
+# dh_installpam -i
+# dh_installinit -i
+# dh_installcron -i
+# dh_installmanpages -i
+# dh_installinfo -i
+# dh_undocumented
+#LATER dh_installchangelogs -i ChangeLog
+ dh_link -i
+ dh_compress -i
+ dh_fixperms -i
+ # You may want to make some executables suid here.
+# dh_suidregister -i
+ dh_installdeb -i
+ dh_perl -i
+ dh_gencontrol -i
+ dh_md5sums -i
+ dh_builddeb -i
+
+# Build architecture-dependent files here.
+binary-arch: build install
+# dh_testversion
+ dh_testdir -a
+ dh_testroot -a
+ dh_installdocs -a
+# dh_installexamples -a
+# dh_installmenu -a
+# dh_installemacsen -a
+# dh_installpam -a
+# dh_installinit -a
+# dh_installcron -a
+# dh_installmanpages -a
+# dh_installinfo -a
+# dh_undocumented
+#LATER dh_installchangelogs -a ChangeLog
+ifeq "$(findstring nostrip,$(DEB_BUILD_OPTIONS))" ""
+ dh_strip -a
+endif
+ dh_link -a
+ dh_compress -a -Xexamples/
+ dh_fixperms -a
+ # You may want to make some executables suid here.
+# dh_suidregister -a
+# dh_makeshlibs -a
+ dh_installdeb -a
+ dh_perl -a
+ dh_shlibdeps -a -ldebian/librrd0/usr/lib
+ dh_gencontrol -a
+ dh_md5sums -a
+ dh_builddeb -a
+
+source diff:
+ @echo >&2 'source and diff are obsolete - use dpkg-source -b'; false
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary install
diff --git a/program/debian/watch b/program/debian/watch
--- /dev/null
+++ b/program/debian/watch
@@ -0,0 +1 @@
+http://oss.oetiker.ch /rrdtool/pub rrdtool-(.*)\.tar\.gz debian
diff --git a/program/doc/Makefile.am b/program/doc/Makefile.am
--- /dev/null
+++ b/program/doc/Makefile.am
@@ -0,0 +1,72 @@
+## Process this file with automake to produce Makefile.in
+
+SUFFIXES = .pod .1 .man .html .txt .pm .pdf .inc
+
+#AUTOMAKE_OPTIONS = foreign
+
+#ACLOCAL_M4 = $(top_srcdir)/config/aclocal.m4
+
+CLEANFILES = *.1 *.html *.txt *-dircache RRD?.pod *.pdf *~ core *itemcache *.rej *.orig *.tmp
+
+POD = bin_dec_hex.pod rrddump.pod rrdgraph_examples.pod rrdrestore.pod rrdupdate.pod \
+ cdeftutorial.pod rrdfetch.pod rrdgraph_graph.pod rrdthreads.pod rrdxport.pod \
+ rpntutorial.pod rrdfirst.pod rrdgraph_rpn.pod rrdtool.pod \
+ rrd-beginners.pod rrdinfo.pod rrdtune.pod rrdbuild.pod \
+ rrdcgi.pod rrdgraph.pod rrdlast.pod rrdlastupdate.pod \
+ rrdcreate.pod rrdgraph_data.pod rrdresize.pod rrdtutorial.pod
+
+
+PMP = RRDs.pod RRDp.pod
+
+MAN = $(POD:.pod=.1)
+TXT = $(MAN:.1=.txt)
+HTML = $(POD:.pod=.html) $(PMP:.pod=.html)
+PDF = $(MAN:.1=.pdf)
+
+# what should go into the distribution
+EXTRA_DIST= $(POD) $(HTML) $(MAN) $(TXT) rrdtool-dump.dtd rrdtool-xport.dtd
+
+idocdir = $(RRDDOCDIR)/txt
+idoc_DATA = $(POD) $(TXT)
+ihtmldir = $(RRDDOCDIR)/html
+ihtml_DATA = $(HTML)
+imandir = $(mandir)/man1
+iman_DATA = $(MAN)
+
+all-local: link txt man html-local
+
+.src.pod:
+ perl -n -e 'if (/^=include\s+(\S+)/){open F,"$$1.inc" || die $$?;print <F>; close F} else {print}' $< > $@
+
+.pod.1 .pm.1 .pl.1:
+ @POD2MAN@ --release=$(VERSION) --center=rrdtool $< > $@
+
+.1.txt:
+ GROFF_NO_SGR=1 @NROFF@ -man -Tlp $< > $@
+
+.1.pdf:
+ @TROFF@ -man $< | ps2pdf - $@
+
+.pm.html .pod.html .pl.html:
+ @POD2HTML@ --infile=$< --outfile=$@ --noindex --htmlroot=. --podpath=. --title=$*
+
+RRDs.pod:
+ $(LN_S) $(top_srcdir)/bindings/perl-shared/RRDs.pm RRDs.pod
+
+RRDp.pod:
+ $(LN_S) $(top_srcdir)/bindings/perl-piped/RRDp.pm RRDp.pod
+
+link: RRDp.pod RRDs.pod
+
+man: $(MAN)
+
+html-local: $(HTML)
+
+txt: $(TXT)
+
+pdf-local: $(PDF)
+
+pod: $(POD)
+
+install-data-hook:
+ cd $(DESTDIR)$(ihtmldir) && rm -f index.html && $(LN_S) rrdtool.html index.html
diff --git a/program/doc/bin_dec_hex.pod b/program/doc/bin_dec_hex.pod
--- /dev/null
@@ -0,0 +1,371 @@
+=head1 NAME
+
+bin_dec_hex - How to use binary, decimal, and hexadecimal notation.
+
+=for html <div align="right"><a href="bin_dec_hex.pdf">PDF</a> version.</div>
+
+=head1 DESCRIPTION
+
+Most people use the decimal numbering system. This system uses ten
+symbols to represent numbers. When those ten symbols are used up, they
+start all over again and increment the position to the left. The
+digit 0 is only shown if it is the only symbol in the sequence, or if
+it is not the first one.
+
+If this sounds cryptic to you, this is what I've just said in numbers:
+
+ 0
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+
+and so on.
+
+Each time the digit nine is incremented, it is reset to 0 and the
+position before (to the left) is incremented (from 0 to 1). Then
+number 9 can be seen as "00009" and when we should increment 9, we
+reset it to zero and increment the digit just before the 9 so the
+number becomes "00010". Leading zeros we don't write except if it is
+the only digit (number 0). And of course, we write zeros if they occur
+anywhere inside or at the end of a number:
+
+ "00010" -> " 0010" -> " 010" -> " 10", but not " 1 ".
+
+This was pretty basic, you already knew this. Why did I tell it?
+Well, computers usually do not represent numbers with 10 different
+digits. They only use two different symbols, namely "0" and "1". Apply
+the same rules to this set of digits and you get the binary numbering
+system:
+
+ 0
+ 1
+ 10
+ 11
+ 100
+ 101
+ 110
+ 111
+ 1000
+ 1001
+ 1010
+ 1011
+ 1100
+ 1101
+
+and so on.
+
+If you count the number of rows, you'll see that these are again 14
+different numbers. The numbers are the same and mean the same as in
+the first list, we just used a different representation. This means
+that you have to know the representation used, or as it is called the
+numbering system or base. Normally, if we do not explicitly specify
+the numbering system used, we implicitly use the decimal system. If we
+want to use any other numbering system, we'll have to make that
+clear. There are a few widely adopted methods to do so. One common
+form is to write 1010(2) which means that you wrote down a number in
+its binary representation. It is the number ten. If you would write
+1010 without specifying the base, the number is interpreted as one
+thousand and ten using base 10.
+
+In books, another form is common. It uses subscripts (little
+characters, more or less in between two rows). You can leave out the
+parentheses in that case and write down the number in normal
+characters followed by a little two just behind it.
+
+As the numbering system used is also called the base, we talk of the
+number 1100 base 2, the number 12 base 10.
+
+Within the binary system, it is common to write leading zeros. The
+numbers are written down in series of four, eight or sixteen depending
+on the context.
+
+We can use the binary form when talking to computers
+(...programming...), but the numbers will have large
+representations. The number 65'535 (often in the decimal system a ' is
+used to separate blocks of three digits for readability) would be
+written down as 1111111111111111(2) which is 16 times the digit 1.
+This is difficult and prone to errors. Therefore, we usually would use
+another base, called hexadecimal. It uses 16 different symbols. First
+the symbols from the decimal system are used, thereafter we continue
+with alphabetic characters. We get 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+A, B, C, D, E and F. This system is chosen because the hexadecimal
+form can be converted into the binary system very easily (and back).
+
+There is yet another system in use, called the octal system. This was
+more common in the old days, but is not used very often anymore. As
+you might find it in use sometimes, you should get used to it and
+we'll show it below. It's the same story as with the other
+representations, but with eight different symbols.
+
+ Binary (2)
+ Octal (8)
+ Decimal (10)
+ Hexadecimal (16)
+
+ (2) (8) (10) (16)
+ 00000 0 0 0
+ 00001 1 1 1
+ 00010 2 2 2
+ 00011 3 3 3
+ 00100 4 4 4
+ 00101 5 5 5
+ 00110 6 6 6
+ 00111 7 7 7
+ 01000 10 8 8
+ 01001 11 9 9
+ 01010 12 10 A
+ 01011 13 11 B
+ 01100 14 12 C
+ 01101 15 13 D
+ 01110 16 14 E
+ 01111 17 15 F
+ 10000 20 16 10
+ 10001 21 17 11
+ 10010 22 18 12
+ 10011 23 19 13
+ 10100 24 20 14
+ 10101 25 21 15
+
+Most computers used nowadays are using bytes of eight bits. This means
+that they store eight bits at a time. You can see why the octal system
+is not the most practical for that: You'd need three digits to represent
+the eight bits and this means that you'd have to use one complete digit
+to represent only two bits (2+3+3=8). This is a waste. For hexadecimal
+digits, you need only two digits which are used completely:
+
+ (2) (8) (10) (16)
+ 11111111 377 255 FF
+
+You can see why binary and hexadecimal can be converted quickly: For
+each hexadecimal digit there are exactly four binary digits. Take a
+binary number: take four digits from the right and make a hexadecimal
+digit from it (see the table above). Repeat this until there are no
+more digits. And the other way around: Take a hexadecimal number. For
+each digit, write down its binary equivalent.
+
+Computers (or rather the parsers running on them) would have a hard
+time converting a number like 1234(16). Therefore hexadecimal numbers
+are specified with a prefix. This prefix depends on the language
+you're writing in. Some of the prefixes are "0x" for C, "$" for
+Pascal, "#" for HTML. It is common to assume that if a number starts
+with a zero, it is octal. It does not matter what is used as long as
+you know what it is. I will use "0x" for hexadecimal, "%" for binary
+and "0" for octal. The following numbers are all the same, just their represenatation (base) is different: 021 0x11 17 %00010001
+
+To do arithmetics and conversions you need to understand one more thing.
+It is something you already know but perhaps you do not "see" it yet:
+
+If you write down 1234, (no prefix, so it is decimal) you are talking
+about the number one thousand, two hundred and thirty four. In sort of
+a formula:
+
+ 1 * 1000 = 1000
+ 2 * 100 = 200
+ 3 * 10 = 30
+ 4 * 1 = 4
+
+This can also be written as:
+
+ 1 * 10^3
+ 2 * 10^2
+ 3 * 10^1
+ 4 * 10^0
+
+where ^ means "to the power of".
+
+We are using the base 10, and the positions 0,1,2 and 3.
+The right-most position should NOT be multiplied with 10. The second
+from the right should be multiplied one time with 10. The third from
+the right is multiplied with 10 two times. This continues for whatever
+positions are used.
+
+It is the same in all other representations:
+
+0x1234 will be
+
+ 1 * 16^3
+ 2 * 16^2
+ 3 * 16^1
+ 4 * 16^0
+
+01234 would be
+
+ 1 * 8^3
+ 2 * 8^2
+ 3 * 8^1
+ 4 * 8^0
+
+This example can not be done for binary as that system only uses two
+symbols. Another example:
+
+%1010 would be
+
+ 1 * 2^3
+ 0 * 2^2
+ 1 * 2^1
+ 0 * 2^0
+
+It would have been easier to convert it to its hexadecimal form and
+just translate %1010 into 0xA. After a while you get used to it. You will
+not need to do any calculations anymore, but just know that 0xA means 10.
+
+To convert a decimal number into a hexadecimal you could use the next
+method. It will take some time to be able to do the estimates, but it
+will be easier when you use the system more frequently. We'll look at
+yet another way afterwards.
+
+First you need to know how many positions will be used in the other
+system. To do so, you need to know the maximum numbers you'll be
+using. Well, that's not as hard as it looks. In decimal, the maximum
+number that you can form with two digits is "99". The maximum for
+three: "999". The next number would need an extra position. Reverse
+this idea and you will see that the number can be found by taking 10^3
+(10*10*10 is 1000) minus 1 or 10^2 minus one.
+
+This can be done for hexadecimal as well:
+
+ 16^4 = 0x10000 = 65536
+ 16^3 = 0x1000 = 4096
+ 16^2 = 0x100 = 256
+ 16^1 = 0x10 = 16
+
+If a number is smaller than 65'536 it will fit in four positions.
+If the number is bigger than 4'095, you must use position 4.
+How many times you can subtract 4'096 from the number without going below
+zero is the first digit you write down. This will always be a number
+from 1 to 15 (0x1 to 0xF). Do the same for the other positions.
+
+Let's try with 41'029. It is smaller than 16^4 but bigger than 16^3-1. This
+means that we have to use four positions.
+We can subtract 16^3 from 41'029 ten times without going below zero.
+The left-most digit will therefore be "A", so we have 0xA????.
+The number is reduced to 41'029 - 10*4'096 = 41'029-40'960 = 69.
+69 is smaller than 16^3 but not bigger than 16^2-1. The second digit
+is therefore "0" and we now have 0xA0??.
+69 is smaller than 16^2 and bigger than 16^1-1. We can subtract 16^1
+(which is just plain 16) four times and write down "4" to get 0xA04?.
+Subtract 64 from 69 (69 - 4*16) and the last digit is 5 --> 0xA045.
+
+The other method builds ub the number from the right. Let's try 41'029
+again. Divide by 16 and do not use fractions (only whole numbers).
+
+ 41'029 / 16 is 2'564 with a remainder of 5. Write down 5.
+ 2'564 / 16 is 160 with a remainder of 4. Write the 4 before the 5.
+ 160 / 16 is 10 with no remainder. Prepend 45 with 0.
+ 10 / 16 is below one. End here and prepend 0xA. End up with 0xA045.
+
+Which method to use is up to you. Use whatever works for you. I use
+them both without being able to tell what method I use in each case,
+it just depends on the number, I think. Fact is, some numbers will
+occur frequently while programming. If the number is close to one I am
+familiar with, then I will use the first method (like 32'770 which is
+into 32'768 + 2 and I just know that it is 0x8000 + 0x2 = 0x8002).
+
+For binary the same approach can be used. The base is 2 and not 16,
+and the number of positions will grow rapidly. Using the second method
+has the advantage that you can see very easily if you should write down
+a zero or a one: if you divide by two the remainder will be zero if it
+is an even number and one if it is an odd number:
+
+ 41029 / 2 = 20514 remainder 1
+ 20514 / 2 = 10257 remainder 0
+ 10257 / 2 = 5128 remainder 1
+ 5128 / 2 = 2564 remainder 0
+ 2564 / 2 = 1282 remainder 0
+ 1282 / 2 = 641 remainder 0
+ 641 / 2 = 320 remainder 1
+ 320 / 2 = 160 remainder 0
+ 160 / 2 = 80 remainder 0
+ 80 / 2 = 40 remainder 0
+ 40 / 2 = 20 remainder 0
+ 20 / 2 = 10 remainder 0
+ 10 / 2 = 5 remainder 0
+ 5 / 2 = 2 remainder 1
+ 2 / 2 = 1 remainder 0
+ 1 / 2 below 0 remainder 1
+
+Write down the results from right to left: %1010000001000101
+
+Group by four:
+
+ %1010000001000101
+ %101000000100 0101
+ %10100000 0100 0101
+ %1010 0000 0100 0101
+
+Convert into hexadecimal: 0xA045
+
+Group %1010000001000101 by three and convert into octal:
+
+ %1010000001000101
+ %1010000001000 101
+ %1010000001 000 101
+ %1010000 001 000 101
+ %1010 000 001 000 101
+ %1 010 000 001 000 101
+ %001 010 000 001 000 101
+ 1 2 0 1 0 5 --> 0120105
+
+ So: %1010000001000101 = 0120105 = 0xA045 = 41029
+ Or: 1010000001000101(2) = 120105(8) = A045(16) = 41029(10)
+ Or: 1010000001000101(2) = 120105(8) = A045(16) = 41029
+
+
+At first while adding numbers, you'll convert them to their decimal
+form and then back into their original form after doing the addition.
+If you use the other numbering system often, you will see that you'll
+be able to do arithmetics directly in the base that is used.
+In any representation it is the same, add the numbers on the right,
+write down the right-most digit from the result, remember the other
+digits and use them in the next round. Continue with the second digit
+from the right and so on:
+
+ %1010 + %0111 --> 10 + 7 --> 17 --> %00010001
+
+will become
+
+ %1010
+ %0111 +
+ ||||
+ |||+-- add 0 + 1, result is 1, nothing to remember
+ ||+--- add 1 + 1, result is %10, write down 0 and remember 1
+ |+---- add 0 + 1 + 1(remembered), result = 0, remember 1
+ +----- add 1 + 0 + 1(remembered), result = 0, remember 1
+ nothing to add, 1 remembered, result = 1
+ --------
+ %10001 is the result, I like to write it as %00010001
+
+For low values, try to do the calculations yourself, then check them with
+a calculator. The more you do the calculations yourself, the more you'll
+find that you didn't make mistakes. In the end, you'll do calculi in
+other bases as easily as you do them in decimal.
+
+When the numbers get bigger, you'll have to realize that a computer is
+not called a computer just to have a nice name. There are many
+different calculators available, use them. For Unix you could use "bc"
+which is short for Binary Calculator. It calculates not only in
+decimal, but in all bases you'll ever want to use (among them Binary).
+
+For people on Windows:
+Start the calculator (start->programs->accessories->calculator)
+and if necessary click view->scientific. You now have a scientific
+calculator and can compute in binary or hexadecimal.
+
+=head1 AUTHOR
+
+I hope you enjoyed the examples and their descriptions. If you do, help
+other people by pointing them to this document when they are asking
+basic questions. They will not only get their answer, but at the same
+time learn a whole lot more.
+
+Alex van den Bogaerdt E<lt>alex@ergens.op.het.netE<gt>
diff --git a/program/doc/cdeftutorial.pod b/program/doc/cdeftutorial.pod
--- /dev/null
@@ -0,0 +1,889 @@
+=head1 NAME
+
+cdeftutorial - Alex van den Bogaerdt's CDEF tutorial
+
+=head1 DESCRIPTION
+
+Intention of this document: to provide some examples of the commonly
+used parts of RRDtool's CDEF language.
+
+If you think some important feature is not explained properly, and if
+adding it to this document would benefit most users, please do ask me
+to add it. I will then try to provide an answer in the next release
+of this tutorial. No feedback equals no changes! Additions to
+this document are also welcome. -- Alex van den Bogaerdt
+E<lt>alex@ergens.op.het.netE<gt>
+
+=head2 Why this tutorial?
+
+One of the powerful parts of RRDtool is its ability to do all sorts
+of calculations on the data retrieved from its databases. However,
+RRDtool's many options and syntax make it difficult for the average
+user to understand. The manuals are good at explaining what these
+options do; however they do not (and should not) explain in detail
+why they are useful. As with my RRDtool tutorial: if you want a
+simple document in simple language you should read this tutorial.
+If you are happy with the official documentation, you may find this
+document too simple or even boring. If you do choose to read this
+tutorial, I also expect you to have read and fully understand my
+other tutorial.
+
+=head2 More reading
+
+If you have difficulties with the way I try to explain it please read
+Steve Rader's L<rpntutorial>. It may help you understand how this all works.
+
+=head1 What are CDEFs?
+
+When retrieving data from an RRD, you are using a "DEF" to work with
+that data. Think of it as a variable that changes over time (where
+time is the x-axis). The value of this variable is what is found in
+the database at that particular time and you can't do any
+modifications on it. This is what CDEFs are for: they takes values
+from DEFs and perform calculations on them.
+
+=head1 Syntax
+
+ DEF:var_name_1=some.rrd:ds_name:CF
+ CDEF:var_name_2=RPN_expression
+
+You first define "var_name_1" to be data collected from data source
+"ds_name" found in RRD "some.rrd" with consolidation function "CF".
+
+Assume the ifInOctets SNMP counter is saved in mrtg.rrd as the DS "in".
+Then the following DEF defines a variable for the average of that
+data source:
+
+ DEF:inbytes=mrtg.rrd:in:AVERAGE
+
+Say you want to display bits per second (instead of bytes per second
+as stored in the database.) You have to define a calculation
+(hence "CDEF") on variable "inbytes" and use that variable (inbits)
+instead of the original:
+
+ CDEF:inbits=inbytes,8,*
+
+This tells RRDtool to multiply inbytes by eight to get inbits. I'll
+explain later how this works. In the graphing or printing functions,
+you can now use inbits where you would use inbytes otherwise.
+
+Note that the variable name used in the CDEF (inbits) must not be the
+same as the variable named in the DEF (inbytes)!
+
+=head1 RPN-expressions
+
+RPN is short-hand for Reverse Polish Notation. It works as follows.
+You put the variables or numbers on a stack. You also put operations
+(things-to-do) on the stack and this stack is then processed. The result
+will be placed on the stack. At the end, there should be exactly one
+number left: the outcome of the series of operations. If there is not
+exactly one number left, RRDtool will complain loudly.
+
+Above multiplication by eight will look like:
+
+=over 4
+
+=item 1.
+
+Start with an empty stack
+
+=item 2.
+
+Put the content of variable inbytes on the stack
+
+=item 3.
+
+Put the number eight on the stack
+
+=item 4.
+
+Put the operation multiply on the stack
+
+=item 5.
+
+Process the stack
+
+=item 6.
+
+Retrieve the value from the stack and put it in variable inbits
+
+=back
+
+We will now do an example with real numbers. Suppose the variable
+inbytes would have value 10, the stack would be:
+
+=over 4
+
+=item 1.
+
+||
+
+=item 2.
+
+|10|
+
+=item 3.
+
+|10|8|
+
+=item 4.
+
+|10|8|*|
+
+=item 5.
+
+|80|
+
+=item 6.
+
+||
+
+=back
+
+Processing the stack (step 5) will retrieve one value from the stack
+(from the right at step 4). This is the operation multiply and this
+takes two values off the stack as input. The result is put back on the
+stack (the value 80 in this case). For multiplication the order doesn't
+matter, but for other operations like subtraction and division it does.
+Generally speaking you have the following order:
+
+ y = A - B --> y=minus(A,B) --> CDEF:y=A,B,-
+
+This is not very intuitive (at least most people don't think so). For
+the function f(A,B) you reverse the position of "f", but you do not
+reverse the order of the variables.
+
+=head1 Converting your wishes to RPN
+
+First, get a clear picture of what you want to do. Break down the problem
+in smaller portions until they cannot be split anymore. Then it is rather
+simple to convert your ideas into RPN.
+
+Suppose you have several RRDs and would like to add up some counters in
+them. These could be, for instance, the counters for every WAN link you
+are monitoring.
+
+You have:
+
+ router1.rrd with link1in link2in
+ router2.rrd with link1in link2in
+ router3.rrd with link1in link2in
+
+Suppose you would like to add up all these counters, except for link2in
+inside router2.rrd. You need to do:
+
+(in this example, "router1.rrd:link1in" means the DS link1in inside the
+RRD router1.rrd)
+
+ router1.rrd:link1in
+ router1.rrd:link2in
+ router2.rrd:link1in
+ router3.rrd:link1in
+ router3.rrd:link2in
+ -------------------- +
+ (outcome of the sum)
+
+As a mathematical function, this could be written:
+
+C<add(router1.rrd:link1in , router1.rrd:link2in , router2.rrd:link1in , router3.rrd:link1in , router3.rrd:link2.in)>
+
+With RRDtool and RPN, first, define the inputs:
+
+ DEF:a=router1.rrd:link1in:AVERAGE
+ DEF:b=router1.rrd:link2in:AVERAGE
+ DEF:c=router2.rrd:link1in:AVERAGE
+ DEF:d=router3.rrd:link1in:AVERAGE
+ DEF:e=router3.rrd:link2in:AVERAGE
+
+Now, the mathematical function becomes: C<add(a,b,c,d,e)>
+
+In RPN, there's no operator that sums more than two values so you need
+to do several additions. You add a and b, add c to the result, add d
+to the result and add e to the result.
+
+ push a: a stack contains the value of a
+ push b and add: b,+ stack contains the result of a+b
+ push c and add: c,+ stack contains the result of a+b+c
+ push d and add: d,+ stack contains the result of a+b+c+d
+ push e and add: e,+ stack contains the result of a+b+c+d+e
+
+What was calculated here would be written down as:
+
+ ( ( ( (a+b) + c) + d) + e) >
+
+This is in RPN: C<CDEF:result=a,b,+,c,+,d,+,e,+>
+
+This is correct but it can be made more clear to humans. It does
+not matter if you add a to b and then add c to the result or first
+add b to c and then add a to the result. This makes it possible to
+rewrite the RPN into C<CDEF:result=a,b,c,d,e,+,+,+,+> which is
+evaluated differently:
+
+ push value of variable a on the stack: a
+ push value of variable b on the stack: a b
+ push value of variable c on the stack: a b c
+ push value of variable d on the stack: a b c d
+ push value of variable e on the stack: a b c d e
+ push operator + on the stack: a b c d e +
+ and process it: a b c P (where P == d+e)
+ push operator + on the stack: a b c P +
+ and process it: a b Q (where Q == c+P)
+ push operator + on the stack: a b Q +
+ and process it: a R (where R == b+Q)
+ push operator + on the stack: a R +
+ and process it: S (where S == a+R)
+
+As you can see the RPN expression C<a,b,c,d,e,+,+,+,+,+> will evaluate in
+C<((((d+e)+c)+b)+a)> and it has the same outcome as C<a,b,+,c,+,d,+,e,+>.
+This is called the commutative law of addition,
+but you may forget this right away, as long as you remember what it
+means.
+
+Now look at an expression that contains a multiplication:
+
+First in normal math: C<let result = a+b*c>. In this case you can't
+choose the order yourself, you have to start with the multiplication
+and then add a to it. You may alter the position of b and c, you must
+not alter the position of a and b.
+
+You have to take this in consideration when converting this expression
+into RPN. Read it as: "Add the outcome of b*c to a" and then it is
+easy to write the RPN expression: C<result=a,b,c,*,+>
+Another expression that would return the same: C<result=b,c,*,a,+>
+
+In normal math, you may encounter something like "a*(b+c)" and this
+can also be converted into RPN. The parenthesis just tell you to first
+add b and c, and then multiply a with the result. Again, now it is
+easy to write it in RPN: C<result=a,b,c,+,*>. Note that this is very
+similar to one of the expressions in the previous paragraph, only the
+multiplication and the addition changed places.
+
+When you have problems with RPN or when RRDtool is complaining, it's
+usually a good thing to write down the stack on a piece of paper
+and see what happens. Have the manual ready and pretend to be RRDtool.
+Just do all the math by hand to see what happens, I'm sure this will
+solve most, if not all, problems you encounter.
+
+=head1 Some special numbers
+
+=head2 The unknown value
+
+Sometimes collecting your data will fail. This can be very common,
+especially when querying over busy links. RRDtool can be configured
+to allow for one (or even more) unknown value(s) and calculate the missing
+update. You can, for instance, query your device every minute. This is
+creating one so called PDP or primary data point per minute. If you
+defined your RRD to contain an RRA that stores 5-minute values, you need
+five of those PDPs to create one CDP (consolidated data point).
+These PDPs can become unknown in two cases:
+
+=over 4
+
+=item 1.
+
+The updates are too far apart. This is tuned using the "heartbeat" setting.
+
+=item 2.
+
+The update was set to unknown on purpose by inserting no value (using the
+template option) or by using "U" as the value to insert.
+
+=back
+
+When a CDP is calculated, another mechanism determines if this CDP is valid
+or not. If there are too many PDPs unknown, the CDP is unknown as well.
+This is determined by the xff factor. Please note that one unknown counter
+update can result in two unknown PDPs! If you only allow for one unknown
+PDP per CDP, this makes the CDP go unknown!
+
+Suppose the counter increments with one per second and you retrieve it
+every minute:
+
+ counter value resulting rate
+ 10'000
+ 10'060 1; (10'060-10'000)/60 == 1
+ 10'120 1; (10'120-10'060)/60 == 1
+ unknown unknown; you don't know the last value
+ 10'240 unknown; you don't know the previous value
+ 10'300 1; (10'300-10'240)/60 == 1
+
+If the CDP was to be calculated from the last five updates, it would get
+two unknown PDPs and three known PDPs. If xff would have been set to 0.5
+which by the way is a commonly used factor, the CDP would have a known
+value of 1. If xff would have been set to 0.2 then the resulting CDP
+would be unknown.
+
+You have to decide the proper values for heartbeat, number of PDPs per
+CDP and the xff factor. As you can see from the previous text they define
+the behavior of your RRA.
+
+=head2 Working with unknown data in your database
+
+As you have read in the previous chapter, entries in an RRA can be
+set to the unknown value. If you do calculations with this type of
+value, the result has to be unknown too. This means that an expression
+such as C<result=a,b,+> will be unknown if either a or b is unknown.
+It would be wrong to just ignore the unknown value and return the value
+of the other parameter. By doing so, you would assume "unknown" means "zero"
+and this is not true.
+
+There has been a case where somebody was collecting data for over a year.
+A new piece of equipment was installed, a new RRD was created and the
+scripts were changed to add a counter from the old database and a counter
+from the new database. The result was disappointing, a large part of
+the statistics seemed to have vanished mysteriously ...
+They of course didn't, values from the old database (known values) were
+added to values from the new database (unknown values) and the result was
+unknown.
+
+In this case, it is fairly reasonable to use a CDEF that alters unknown
+data into zero. The counters of the device were unknown (after all, it
+wasn't installed yet!) but you know that the data rate through the device
+had to be zero (because of the same reason: it was not installed).
+
+There are some examples below that make this change.
+
+=head2 Infinity
+
+Infinite data is another form of a special number. It cannot be
+graphed because by definition you would never reach the infinite
+value. You can think of positive and negative infinity depending on
+the position relative to zero.
+
+RRDtool is capable of representing (-not- graphing!) infinity by stopping
+at its current maximum (for positive infinity) or minimum (for negative
+infinity) without knowing this maximum (minimum).
+
+Infinity in RRDtool is mostly used to draw an AREA without knowing its
+vertical dimensions. You can think of it as drawing an AREA with an
+infinite height and displaying only the part that is visible in the
+current graph. This is probably a good way to approximate infinity
+and it sure allows for some neat tricks. See below for examples.
+
+=head2 Working with unknown data and infinity
+
+Sometimes you would like to discard unknown data and pretend it is zero
+(or any other value for that matter) and sometimes you would like to
+pretend that known data is unknown (to discard known-to-be-wrong data).
+This is why CDEFs have support for unknown data. There are also examples
+available that show unknown data by using infinity.
+
+=head1 Some examples
+
+=head2 Example: using a recently created RRD
+
+You are keeping statistics on your router for over a year now. Recently
+you installed an extra router and you would like to show the combined
+throughput for these two devices.
+
+If you just add up the counters from router.rrd and router2.rrd, you
+will add known data (from router.rrd) to unknown data (from router2.rrd) for
+the bigger part of your stats. You could solve this in a few ways:
+
+=over 4
+
+=item *
+
+While creating the new database, fill it with zeros from the start to now.
+You have to make the database start at or before the least recent time in
+the other database.
+
+=item *
+
+Alternatively, you could use CDEF and alter unknown data to zero.
+
+=back
+
+Both methods have their pros and cons. The first method is troublesome and
+if you want to do that you have to figure it out yourself. It is not
+possible to create a database filled with zeros, you have to put them in
+manually. Implementing the second method is described next:
+
+What we want is: "if the value is unknown, replace it with zero". This
+could be written in pseudo-code as: if (value is unknown) then (zero)
+else (value). When reading the L<rrdgraph> manual you notice the "UN"
+function that returns zero or one. You also notice the "IF" function
+that takes zero or one as input.
+
+First look at the "IF" function. It takes three values from the stack,
+the first value is the decision point, the second value is returned to
+the stack if the evaluation is "true" and if not, the third value is
+returned to the stack. We want the "UN" function to decide what happens
+so we combine those two functions in one CDEF.
+
+Lets write down the two possible paths for the "IF" function:
+
+ if true return a
+ if false return b
+
+In RPN: C<result=x,a,b,IF> where "x" is either true or false.
+
+Now we have to fill in "x", this should be the "(value is unknown)" part
+and this is in RPN: C<result=value,UN>
+
+We now combine them: C<result=value,UN,a,b,IF> and when we fill in the
+appropriate things for "a" and "b" we're finished:
+
+C<CDEF:result=value,UN,0,value,IF>
+
+You may want to read Steve Rader's RPN guide if you have difficulties
+with the way I explained this last example.
+
+If you want to check this RPN expression, just mimic RRDtool behavior:
+
+ For any known value, the expression evaluates as follows:
+ CDEF:result=value,UN,0,value,IF (value,UN) is not true so it becomes 0
+ CDEF:result=0,0,value,IF "IF" will return the 3rd value
+ CDEF:result=value The known value is returned
+
+ For the unknown value, this happens:
+ CDEF:result=value,UN,0,value,IF (value,UN) is true so it becomes 1
+ CDEF:result=1,0,value,IF "IF" sees 1 and returns the 2nd value
+ CDEF:result=0 Zero is returned
+
+Of course, if you would like to see another value instead of zero, you
+can use that other value.
+
+Eventually, when all unknown data is removed from the RRD, you may want
+to remove this rule so that unknown data is properly displayed.
+
+=head2 Example: better handling of unknown data, by using time
+
+The above example has one drawback. If you do log unknown data in
+your database after installing your new equipment, it will also be
+translated into zero and therefore you won't see that there was a
+problem. This is not good and what you really want to do is:
+
+=over 4
+
+=item *
+
+If there is unknown data, look at the time that this sample was taken.
+
+=item *
+
+If the unknown value is before time xxx, make it zero.
+
+=item *
+
+If it is after time xxx, leave it as unknown data.
+
+=back
+
+This is doable: you can compare the time that the sample was taken
+to some known time. Assuming you started to monitor your device on
+Friday September 17, 1999, 00:35:57 MET DST. Translate this time in seconds
+since 1970-01-01 and it becomes 937'521'357. If you process unknown values
+that were received after this time, you want to leave them unknown and
+if they were "received" before this time, you want to translate them
+into zero (so you can effectively ignore them while adding them to your
+other routers counters).
+
+Translating Friday September 17, 1999, 00:35:57 MET DST into 937'521'357 can
+be done by, for instance, using gnu date:
+
+ date -d "19990917 00:35:57" +%s
+
+You could also dump the database and see where the data starts to be
+known. There are several other ways of doing this, just pick one.
+
+Now we have to create the magic that allows us to process unknown
+values different depending on the time that the sample was taken.
+This is a three step process:
+
+=over 4
+
+=item 1.
+
+If the timestamp of the value is after 937'521'357, leave it as is.
+
+=item 2.
+
+If the value is a known value, leave it as is.
+
+=item 3.
+
+Change the unknown value into zero.
+
+=back
+
+Lets look at part one:
+
+ if (true) return the original value
+
+We rewrite this:
+
+ if (true) return "a"
+ if (false) return "b"
+
+We need to calculate true or false from step 1. There is a function
+available that returns the timestamp for the current sample. It is
+called, how surprisingly, "TIME". This time has to be compared to
+a constant number, we need "GT". The output of "GT" is true or false
+and this is good input to "IF". We want "if (time > 937521357) then
+(return a) else (return b)".
+
+This process was already described thoroughly in the previous chapter
+so lets do it quick:
+
+ if (x) then a else b
+ where x represents "time>937521357"
+ where a represents the original value
+ where b represents the outcome of the previous example
+
+ time>937521357 --> TIME,937521357,GT
+
+ if (x) then a else b --> x,a,b,IF
+ substitute x --> TIME,937521357,GT,a,b,IF
+ substitute a --> TIME,937521357,GT,value,b,IF
+ substitute b --> TIME,937521357,GT,value,value,UN,0,value,IF,IF
+
+We end up with:
+C<CDEF:result=TIME,937521357,GT,value,value,UN,0,value,IF,IF>
+
+This looks very complex, however, as you can see, it was not too hard to
+come up with.
+
+=head2 Example: Pretending weird data isn't there
+
+Suppose you have a problem that shows up as huge spikes in your graph.
+You know this happens and why, so you decide to work around the problem.
+Perhaps you're using your network to do a backup at night and by doing
+so you get almost 10mb/s while the rest of your network activity does
+not produce numbers higher than 100kb/s.
+
+There are two options:
+
+=over 4
+
+=item 1.
+
+If the number exceeds 100kb/s it is wrong and you want it masked out
+by changing it into unknown.
+
+=item 2.
+
+You don't want the graph to show more than 100kb/s.
+
+=back
+
+Pseudo code: if (number > 100) then unknown else number
+or
+Pseudo code: if (number > 100) then 100 else number.
+
+The second "problem" may also be solved by using the rigid option of
+RRDtool graph, however this has not the same result. In this example
+you can end up with a graph that does autoscaling. Also, if you use
+the numbers to display maxima they will be set to 100kb/s.
+
+We use "IF" and "GT" again. "if (x) then (y) else (z)" is written
+down as "CDEF:result=x,y,z,IF"; now fill in x, y and z.
+For x you fill in "number greater than 100kb/s" becoming
+"number,100000,GT" (kilo is 1'000 and b/s is what we measure!).
+The "z" part is "number" in both cases and the "y" part is either
+"UNKN" for unknown or "100000" for 100kb/s.
+
+The two CDEF expressions would be:
+
+ CDEF:result=number,100000,GT,UNKN,number,IF
+ CDEF:result=number,100000,GT,100000,number,IF
+
+=head2 Example: working on a certain time span
+
+If you want a graph that spans a few weeks, but would only want to
+see some routers' data for one week, you need to "hide" the rest of
+the time frame. Don't ask me when this would be useful, it's just
+here for the example :)
+
+We need to compare the time stamp to a begin date and an end date.
+Comparing isn't difficult:
+
+ TIME,begintime,GE
+ TIME,endtime,LE
+
+These two parts of the CDEF produce either 0 for false or 1 for true.
+We can now check if they are both 0 (or 1) using a few IF statements
+but, as Wataru Satoh pointed out, we can use the "*" or "+" functions
+as logical AND and logical OR.
+
+For "*", the result will be zero (false) if either one of the two
+operators is zero. For "+", the result will only be false (0) when
+two false (0) operators will be added. Warning: *any* number not
+equal to 0 will be considered "true". This means that, for instance,
+"-1,1,+" (which should be "true or true") will become FALSE ...
+In other words, use "+" only if you know for sure that you have positive
+numbers (or zero) only.
+
+Let's compile the complete CDEF:
+
+ DEF:ds0=router1.rrd:AVERAGE
+ CDEF:ds0modified=TIME,begintime,GE,TIME,endtime,LE,*,UNKN,ds0,IF
+
+This will return the value of ds0 if both comparisons return true. You
+could also do it the other way around:
+
+ DEF:ds0=router1.rrd:AVERAGE
+ CDEF:ds0modified=TIME,begintime,LT,TIME,endtime,GT,+,UNKN,ds0,IF
+
+This will return an UNKNOWN if either comparison returns true.
+
+=head2 Example: You suspect to have problems and want to see unknown data.
+
+Suppose you add up the number of active users on several terminal servers.
+If one of them doesn't give an answer (or an incorrect one) you get "NaN"
+in the database ("Not a Number") and NaN is evaluated as Unknown.
+
+In this case, you would like to be alerted to it and the sum of the
+remaining values is of no value to you.
+
+It would be something like:
+
+ DEF:users1=location1.rrd:onlineTS1:LAST
+ DEF:users2=location1.rrd:onlineTS2:LAST
+ DEF:users3=location2.rrd:onlineTS1:LAST
+ DEF:users4=location2.rrd:onlineTS2:LAST
+ CDEF:allusers=users1,users2,users3,users4,+,+,+
+
+If you now plot allusers, unknown data in one of users1..users4 will
+show up as a gap in your graph. You want to modify this to show a
+bright red line, not a gap.
+
+Define an extra CDEF that is unknown if all is okay and is infinite if
+there is an unknown value:
+
+ CDEF:wrongdata=allusers,UN,INF,UNKN,IF
+
+"allusers,UN" will evaluate to either true or false, it is the (x) part
+of the "IF" function and it checks if allusers is unknown.
+The (y) part of the "IF" function is set to "INF" (which means infinity)
+and the (z) part of the function returns "UNKN".
+
+The logic is: if (allusers == unknown) then return INF else return UNKN.
+
+You can now use AREA to display this "wrongdata" in bright red. If it
+is unknown (because allusers is known) then the red AREA won't show up.
+If the value is INF (because allusers is unknown) then the red AREA will
+be filled in on the graph at that particular time.
+
+ AREA:allusers#0000FF:combined user count
+ AREA:wrongdata#FF0000:unknown data
+
+=head2 Same example useful with STACKed data:
+
+If you use stack in the previous example (as I would do) then you don't
+add up the values. Therefore, there is no relationship between the
+four values and you don't get a single value to test.
+Suppose users3 would be unknown at one point in time: users1 is plotted,
+users2 is stacked on top of users1, users3 is unknown and therefore
+nothing happens, users4 is stacked on top of users2.
+Add the extra CDEFs anyway and use them to overlay the "normal" graph:
+
+ DEF:users1=location1.rrd:onlineTS1:LAST
+ DEF:users2=location1.rrd:onlineTS2:LAST
+ DEF:users3=location2.rrd:onlineTS1:LAST
+ DEF:users4=location2.rrd:onlineTS2:LAST
+ CDEF:allusers=users1,users2,users3,users4,+,+,+
+ CDEF:wrongdata=allusers,UN,INF,UNKN,IF
+ AREA:users1#0000FF:users at ts1
+ STACK:users2#00FF00:users at ts2
+ STACK:users3#00FFFF:users at ts3
+ STACK:users4#FFFF00:users at ts4
+ AREA:wrongdata#FF0000:unknown data
+
+If there is unknown data in one of users1..users4, the "wrongdata" AREA
+will be drawn and because it starts at the X-axis and has infinite height
+it will effectively overwrite the STACKed parts.
+
+You could combine the two CDEF lines into one (we don't use "allusers")
+if you like. But there are good reasons for writing two CDEFS:
+
+=over 4
+
+=item *
+
+It improves the readability of the script.
+
+=item *
+
+It can be used inside GPRINT to display the total number of users.
+
+=back
+
+If you choose to combine them, you can substitute the "allusers" in the
+second CDEF with the part after the equal sign from the first line:
+
+ CDEF:wrongdata=users1,users2,users3,users4,+,+,+,UN,INF,UNKN,IF
+
+If you do so, you won't be able to use these next GPRINTs:
+
+ COMMENT:"Total number of users seen"
+ GPRINT:allusers:MAX:"Maximum: %6.0lf"
+ GPRINT:allusers:MIN:"Minimum: %6.0lf"
+ GPRINT:allusers:AVERAGE:"Average: %6.0lf"
+ GPRINT:allusers:LAST:"Current: %6.0lf\n"
+
+=head1 The examples from the RRD graph manual page
+
+=head2 Degrees Celsius vs. Degrees Fahrenheit
+
+To convert Celsius into Fahrenheit use the formula
+F=9/5*C+32
+
+ rrdtool graph demo.png --title="Demo Graph" \
+ DEF:cel=demo.rrd:exhaust:AVERAGE \
+ CDEF:far=9,5,/,cel,*,32,+ \
+ LINE2:cel#00a000:"D. Celsius" \
+ LINE2:far#ff0000:"D. Fahrenheit\c"
+
+This example gets the DS called "exhaust" from database "demo.rrd"
+and puts the values in variable "cel". The CDEF used is evaluated
+as follows:
+
+ CDEF:far=9,5,/,cel,*,32,+
+ 1. push 9, push 5
+ 2. push function "divide" and process it
+ the stack now contains 9/5
+ 3. push variable "cel"
+ 4. push function "multiply" and process it
+ the stack now contains 9/5*cel
+ 5. push 32
+ 6. push function "plus" and process it
+ the stack contains now the temperature in Fahrenheit
+
+=head2 Changing unknown into zero
+
+ rrdtool graph demo.png --title="Demo Graph" \
+ DEF:idat1=interface1.rrd:ds0:AVERAGE \
+ DEF:idat2=interface2.rrd:ds0:AVERAGE \
+ DEF:odat1=interface1.rrd:ds1:AVERAGE \
+ DEF:odat2=interface2.rrd:ds1:AVERAGE \
+ CDEF:agginput=idat1,UN,0,idat1,IF,idat2,UN,0,idat2,IF,+,8,* \
+ CDEF:aggoutput=odat1,UN,0,odat1,IF,odat2,UN,0,odat2,IF,+,8,* \
+ AREA:agginput#00cc00:Input Aggregate \
+ LINE1:aggoutput#0000FF:Output Aggregate
+
+These two CDEFs are built from several functions. It helps to split
+them when viewing what they do. Starting with the first CDEF we would
+get:
+
+ idat1,UN --> a
+ 0 --> b
+ idat1 --> c
+ if (a) then (b) else (c)
+
+The result is therefore "0" if it is true that "idat1" equals "UN".
+If not, the original value of "idat1" is put back on the stack.
+Lets call this answer "d". The process is repeated for the next
+five items on the stack, it is done the same and will return answer
+"h". The resulting stack is therefore "d,h".
+The expression has been simplified to "d,h,+,8,*" and it will now be
+easy to see that we add "d" and "h", and multiply the result with eight.
+
+The end result is that we have added "idat1" and "idat2" and in the
+process we effectively ignored unknown values. The result is multiplied
+by eight, most likely to convert bytes/s to bits/s.
+
+=head2 Infinity demo
+
+ rrdtool graph example.png --title="INF demo" \
+ DEF:val1=some.rrd:ds0:AVERAGE \
+ DEF:val2=some.rrd:ds1:AVERAGE \
+ DEF:val3=some.rrd:ds2:AVERAGE \
+ DEF:val4=other.rrd:ds0:AVERAGE \
+ CDEF:background=val4,POP,TIME,7200,%,3600,LE,INF,UNKN,IF \
+ CDEF:wipeout=val1,val2,val3,val4,+,+,+,UN,INF,UNKN,IF \
+ AREA:background#F0F0F0 \
+ AREA:val1#0000FF:Value1 \
+ STACK:val2#00C000:Value2 \
+ STACK:val3#FFFF00:Value3 \
+ STACK:val4#FFC000:Value4 \
+ AREA:whipeout#FF0000:Unknown
+
+This demo demonstrates two ways to use infinity. It is a bit tricky
+to see what happens in the "background" CDEF.
+
+ "val4,POP,TIME,7200,%,3600,LE,INF,UNKN,IF"
+
+This RPN takes the value of "val4" as input and then immediately
+removes it from the stack using "POP". The stack is now empty but
+as a side effect we now know the time that this sample was taken.
+This time is put on the stack by the "TIME" function.
+
+"TIME,7200,%" takes the modulo of time and 7'200 (which is two hours).
+The resulting value on the stack will be a number in the range from
+0 to 7199.
+
+For people who don't know the modulo function: it is the remainder
+after an integer division. If you divide 16 by 3, the answer would
+be 5 and the remainder would be 1. So, "16,3,%" returns 1.
+
+We have the result of "TIME,7200,%" on the stack, lets call this
+"a". The start of the RPN has become "a,3600,LE" and this checks
+if "a" is less or equal than "3600". It is true half of the time.
+We now have to process the rest of the RPN and this is only a simple
+"IF" function that returns either "INF" or "UNKN" depending on the
+time. This is returned to variable "background".
+
+The second CDEF has been discussed earlier in this document so we
+won't do that here.
+
+Now you can draw the different layers. Start with the background
+that is either unknown (nothing to see) or infinite (the whole
+positive part of the graph gets filled).
+
+Next you draw the data on top of this background, it will overlay
+the background. Suppose one of val1..val4 would be unknown, in that
+case you end up with only three bars stacked on top of each other.
+You don't want to see this because the data is only valid when all
+four variables are valid. This is why you use the second CDEF, it
+will overlay the data with an AREA so the data cannot be seen anymore.
+
+If your data can also have negative values you also need to overwrite
+the other half of your graph. This can be done in a relatively simple
+way: what you need is the "wipeout" variable and place a negative
+sign before it: "CDEF:wipeout2=wipeout,-1,*"
+
+=head2 Filtering data
+
+You may do some complex data filtering:
+
+ MEDIAN FILTER: filters shot noise
+
+ DEF:var=database.rrd:traffic:AVERAGE
+ CDEF:prev1=PREV(var)
+ CDEF:prev2=PREV(prev1)
+ CDEF:prev3=PREV(prev2)
+ CDEF:median=prev1,prev2,prev3,+,+,3,/
+ LINE3:median#000077:filtered
+ LINE1:prev2#007700:'raw data'
+
+
+ DERIVATE:
+
+ DEF:var=database.rrd:traffic:AVERAGE
+ CDEF:prev1=PREV(var)
+ CDEF:time=TIME
+ CDEF:prevtime=PREV(time)
+ CDEF:derivate=var,prev1,-,time,prevtime,-,/
+ LINE3:derivate#000077:derivate
+ LINE1:var#007700:'raw data'
+
+
+=head1 Out of ideas for now
+
+This document was created from questions asked by either myself or by
+other people on the RRDtool mailing list. Please let me know if you
+find errors in it or if you have trouble understanding it. If you
+think there should be an addition, mail me:
+E<lt>alex@ergens.op.het.netE<gt>
+
+Remember: B<No feedback equals no changes!>
+
+=head1 SEE ALSO
+
+The RRDtool manpages
+
+=head1 AUTHOR
+
+Alex van den Bogaerdt
+E<lt>alex@ergens.op.het.netE<gt>
diff --git a/program/doc/name.inc b/program/doc/name.inc
--- /dev/null
+++ b/program/doc/name.inc
@@ -0,0 +1,11 @@
+=head1 NAME
+
+=cut
+
+WARNING: DO NOT EDIT THE POD FILES. THEY ARE AUTO-GENERATED
+
+=pod
+
+rrdtool graph - Round Robin Database tool grapher functions
+
+Documentation for version 1.2.0
diff --git a/program/doc/rpntutorial.pod b/program/doc/rpntutorial.pod
--- /dev/null
@@ -0,0 +1,198 @@
+=head1 NAME
+
+rpntutorial - Reading RRDtool RPN Expressions by Steve Rader
+
+=head1 DESCRIPTION
+
+This tutorial should help you get to grips with RRDtool RPN expressions
+as seen in CDEF arguments of RRDtool graph.
+
+=head1 Reading Comparison Operators
+
+The LT, LE, GT, GE and EQ RPN logic operators are not as tricky as
+they appear. These operators act on the two values on the stack
+preceding them (to the left). Read these two values on the stack
+from left to right inserting the operator in the middle. If the
+resulting statement is true, then replace the three values from the
+stack with "1". If the statement if false, replace the three values
+with "0".
+
+For example, think about "2,1,GT". This RPN expression could be
+read as "is two greater than one?" The answer to that question is
+"true". So the three values should be replaced with "1". Thus the
+RPN expression 2,1,GT evaluates to 1.
+
+Now consider "2,1,LE". This RPN expression could be read as "is
+two less than or equal to one?". The natural response is "no"
+and thus the RPN expression 2,1,LE evaluates to 0.
+
+=head1 Reading the IF Operator
+
+The IF RPN logic operator can be straightforward also. The key
+to reading IF operators is to understand that the condition part
+of the traditional "if X than Y else Z" notation has *already*
+been evaluated. So the IF operator acts on only one value on the
+stack: the third value to the left of the IF value. The second
+value to the left of the IF corresponds to the true ("Y") branch.
+And the first value to the left of the IF corresponds to the false
+("Z") branch. Read the RPN expression "X,Y,Z,IF" from left to
+right like so: "if X then Y else Z".
+
+For example, consider "1,10,100,IF". It looks bizarre to me.
+But when I read "if 1 then 10 else 100" it's crystal clear: 1 is true
+so the answer is 10. Note that only zero is false; all other values
+are true. "2,20,200,IF" ("if 2 then 20 else 200") evaluates to 20.
+And "0,1,2,IF" ("if 0 then 1 else 2) evaluates to 2.
+
+
+Notice that none of the above examples really simulate the whole
+"if X then Y else Z" statement. This is because computer programmers
+read this statement as "if Some Condition then Y else Z". So it's
+important to be able to read IF operators along with the LT, LE,
+GT, GE and EQ operators.
+
+=head1 Some Examples
+
+While compound expressions can look overly complex, they can be
+considered elegantly simple. To quickly comprehend RPN expressions,
+you must know the the algorithm for evaluating RPN expressions:
+iterate searches from the left to the right looking for an operator.
+When it's found, apply that operator by popping the operator and some
+number of values (and by definition, not operators) off the stack.
+
+For example, the stack "1,2,3,+,+" gets "2,3,+" evaluated (as "2+3")
+during the first iteration and is replaced by 5. This results in
+the stack "1,5,+". Finally, "1,5,+" is evaluated resulting in the
+answer 6. For convenience, it's useful to write this set of
+operations as:
+
+ 1) 1,2,3,+,+ eval is 2,3,+ = 5 result is 1,5,+
+ 2) 1,5,+ eval is 1,5,+ = 6 result is 6
+ 3) 6
+
+Let's use that notation to conveniently solve some complex RPN expressions
+with multiple logic operators:
+
+ 1) 20,10,GT,10,20,IF eval is 20,10,GT = 1 result is 1,10,20,IF
+
+read the eval as pop "20 is greater than 10" so push 1
+
+ 2) 1,10,20,IF eval is 1,10,20,IF = 10 result is 10
+
+read pop "if 1 then 10 else 20" so push 10. Only 10 is left so
+10 is the answer.
+
+Let's read a complex RPN expression that also has the traditional
+multiplication operator:
+
+ 1) 128,8,*,7000,GT,7000,128,8,*,IF eval 128,8,* result is 1024
+ 2) 1024,7000,GT,7000,128,8,*,IF eval 1024,7000,GT result is 0
+ 3) 0,128,8,*,IF eval 128,8,* result is 1024
+ 4) 0,7000,1024,IF result is 1024
+
+
+Now let's go back to the first example of multiple logic operators,
+but replace the value 20 with the variable "input":
+
+ 1) input,10,GT,10,input,IF eval is input,10,GT ( lets call this A )
+
+Read eval as "if input > 10 then true" and replace "input,10,GT"
+with "A":
+
+ 2) A,10,input,IF eval is A,10,input,IF
+
+read "if A then 10 else input". Now replace A with it's verbose
+description againg and--voila!--you have a easily readable description
+of the expression:
+
+ if input > 10 then 10 else input
+
+Finally, let's go back to the first most complex example and replace
+the value 128 with "input":
+
+ 1) input,8,*,7000,GT,7000,input,8,*,IF eval input,8,* result is A
+
+where A is "input * 8"
+
+ 2) A,7000,GT,7000,input,8,*,IF eval is A,7000,GT result is B
+
+where B is "if ((input * 8) > 7000) then true"
+
+ 3) B,7000,input,8,*,IF eval is input,8,* result is C
+
+where C is "input * 8"
+
+ 4) B,7000,C,IF
+
+At last we have a readable decoding of the complex RPN expression with
+a variable:
+
+ if ((input * 8) > 7000) then 7000 else (input * 8)
+
+=head1 Exercises
+
+Exercise 1:
+
+Compute "3,2,*,1,+ and "3,2,1,+,*" by hand. Rewrite them in
+traditional notation. Explain why they have different answers.
+
+Answer 1:
+
+ 3*2+1 = 7 and 3*(2+1) = 9. These expressions have
+ different answers because the altering of the plus and
+ times operators alter the order of their evaluation.
+
+
+Exercise 2:
+
+One may be tempted to shorten the expression
+
+ input,8,*,56000,GT,56000,input,*,8,IF
+
+by removing the redundant use of "input,8,*" like so:
+
+ input,56000,GT,56000,input,IF,8,*
+
+Use traditional notation to show these expressions are not the same.
+Write an expression that's equivalent to the first expression, but
+uses the LE and DIV operators.
+
+Answer 2:
+
+ if (input <= 56000/8 ) { input*8 } else { 56000 }
+ input,56000,8,DIV,LT,input,8,*,56000,IF
+
+
+Exercise 3:
+
+Briefly explain why traditional mathematic notation requires the
+use of parentheses. Explain why RPN notation does not require
+the use of parentheses.
+
+Answer 3:
+
+ Traditional mathematic expressions are evaluated by
+ doing multiplication and division first, then addition and
+ subtraction. Parentheses are used to force the evaluation of
+ addition before multiplication (etc). RPN does not require
+ parentheses because the ordering of objects on the stack
+ can force the evaluation of addition before multiplication.
+
+
+Exercise 4:
+
+Explain why it was desirable for the RRDtool developers to implement
+RPN notation instead of traditional mathematical notation.
+
+Answer 4:
+
+ The algorithm that implements traditional mathematical
+ notation is more complex then algorithm used for RPN.
+ So implementing RPN allowed Tobias Oetiker to write less
+ code! (The code is also less complex and therefore less
+ likely to have bugs.)
+
+
+=head1 AUTHOR
+
+Steve Rader E<lt>rader@wiscnet.netE<gt>
diff --git a/program/doc/rrd-beginners.pod b/program/doc/rrd-beginners.pod
--- /dev/null
@@ -0,0 +1,326 @@
+=head1 NAME
+
+rrd-beginners - RRDtool Beginners' Guide
+
+=head1 SYNOPSIS
+
+Helping new RRDtool users to understand the basics of RRDtool
+
+=head1 DESCRIPTION
+
+This manual is an attempt to assist beginners in understanding the concepts
+of RRDtool. It sheds a light on differences between RRDtool and other
+databases. With help of an example, it explains the structure of RRDtool
+database. This is followed by an overview of the "graph" feature of RRDtool.
+At the end, it has sample scripts that illustrate the
+usage/wrapping of RRDtool within Shell or Perl scripts.
+
+=head2 What makes RRDtool so special?
+
+RRDtool is GNU licensed software developed by Tobias Oetiker, a system
+manager at the Swiss Federal Institute of Technology. Though it is a
+database, there are distinct differences between RRDtool databases and other
+databases as listed below:
+
+=over
+
+=item *
+
+RRDtool stores data; that makes it a back-end tool. The RRDtool command set
+allows the creation of graphs; that makes it a front-end tool as well. Other
+databases just store data and can not create graphs.
+
+=item *
+
+In case of linear databases, new data gets appended at the bottom of
+the database table. Thus its size keeps on increasing, whereas the size of
+an RRDtool database is determined at creation time. Imagine an RRDtool
+database as the perimeter of a circle. Data is added along the
+perimeter. When new data reaches the starting point, it overwrites
+existing data. This way, the size of an RRDtool database always
+remains constant. The name "Round Robin" stems from this behavior.
+
+=item *
+
+Other databases store the values as supplied. RRDtool can be configured to
+calculate the rate of change from the previous to the current value and
+store this information instead.
+
+=item *
+
+Other databases get updated when values are supplied. The RRDtool database
+is structured in such a way that it needs data at predefined time
+intervals. If it does not get a new value during the interval, it stores an
+UNKNOWN value for that interval. So, when using the RRDtool database, it is
+imperative to use scripts that run at regular intervals to ensure a constant
+data flow to update the RRDtool database.
+
+=back
+
+RRDtool is designed to store time series of data. With every data
+update, an associated time stamp is stored. Time is always expressed
+in seconds passed since epoch (01-01-1970). RRDtool can be installed
+on Unix as well as Windows. It comes with a command set to carry out
+various operations on RRD databases. This command set can be accessed
+from the command line, as well as from Shell or Perl scripts. The
+scripts act as wrappers for accessing data stored in RRDtool
+databases.
+
+=head2 Understanding by an example
+
+The structure of an RRD database is different than other linear databases.
+Other databases define tables with columns, and many other parameters. These
+definitions sometimes are very complex, especially in large databases.
+RRDtool databases are primarily used for monitoring purposes and
+hence are very simple in structure. The parameters
+that need to be defined are variables that hold values and archives of those
+values. Being time sensitive, a couple of time related parameters are also
+defined. Because of its structure, the definition of an RRDtool database also
+includes a provision to specify specific actions to take in the absence of
+update values. Data Source (DS), heartbeat, Date Source Type (DST), Round
+Robin Archive (RRA), and Consolidation Function (CF) are some of the
+terminologies related to RRDtool databases.
+
+The structure of a database and the terminology associated with it can be
+best explained with an example.
+
+ rrdtool create target.rrd \
+ --start 1023654125 \
+ --step 300 \
+ DS:mem:GAUGE:600:0:671744 \
+ RRA:AVERAGE:0.5:12:24 \
+ RRA:AVERAGE:0.5:288:31
+
+This example creates a database named F<target.rrd>. Start time
+(1'023'654'125) is specified in total number of seconds since epoch
+(time in seconds since 01-01-1970). While updating the database, the
+update time is also specified. This update time MUST be large (later)
+then start time and MUST be in seconds since epoch.
+
+The step of 300 seconds indicates that database expects new values every
+300 seconds. The wrapper script should be scheduled to run every B<step>
+seconds so that it updates the database every B<step> seconds.
+
+DS (Data Source) is the actual variable which relates to the parameter on
+the device that is monitored. Its syntax is
+
+ DS:variable_name:DST:heartbeat:min:max
+
+B<DS> is a key word. C<variable_name> is a name under which the parameter is
+saved in the database. There can be as many DSs in a database as needed. After
+every step interval, a new value of DS is supplied to update the database.
+This value is also called Primary Data Point B<(PDP)>. In our example
+mentioned above, a new PDP is generated every 300 seconds.
+
+Note, that if you do NOT supply new datapoints exactly every 300 seconds,
+this is not a problem, RRDtool will interpolate the data accordingly.
+
+B<DST> (Data Source Type) defines the type of the DS. It can be
+COUNTER, DERIVE, ABSOLUTE, GAUGE. A DS declared as COUNTER will save
+the rate of change of the value over a step period. This assumes that
+the value is always increasing (the difference between the current and
+the previous value is greater than 0). Traffic counters on a router
+are an ideal candidate for using COUNTER as DST. DERIVE is the same as
+COUNTER, but it allows negative values as well. If you want to see the
+rate of I<change> in free diskspace on your server, then you might
+want to use the DERIVE data type. ABSOLUTE also saves the rate of
+change, but it assumes that the previous value is set to 0. The
+difference between the current and the previous value is always equal
+to the current value. Thus it just stores the current value divided by
+the step interval (300 seconds in our example). GAUGE does not save
+the rate of change. It saves the actual value itself. There are no
+divisions or calculations. Memory consumption in a server is a typical
+example of gauge. The difference between the different types DSTs can be
+explained better with the following example:
+
+ Values = 300, 600, 900, 1200
+ Step = 300 seconds
+ COUNTER DS = 1, 1, 1, 1
+ DERIVE DS = 1, 1, 1, 1
+ ABSOLUTE DS = 1, 2, 3, 4
+ GAUGE DS = 300, 600, 900, 1200
+
+The next parameter is B<heartbeat>. In our example, heartbeat is 600
+seconds. If the database does not get a new PDP within 300 seconds, it
+will wait for another 300 seconds (total 600 seconds). If it doesn't
+receive any PDP within 600 seconds, it will save an UNKNOWN value into
+the database. This UNKNOWN value is a special feature of RRDtool - it
+is much better than to assume a missing value was 0 (zero) or any
+other number which might also be a valid data value. For example, the
+traffic flow counter on a router keeps increasing. Lets say, a value
+is missed for an interval and 0 is stored instead of UNKNOWN. Now when
+the next value becomes available, it will calculate the difference
+between the current value and the previous value (0) which is not
+correct. So, inserting the value UNKNOWN makes much more sense here.
+
+The next two parameters are the minimum and maximum value,
+respectively. If the variable to be stored has predictable maximum and
+minimum values, this should be specified here. Any update value
+falling out of this range will be stored as UNKNOWN.
+
+The next line declares a round robin archive (RRA). The syntax for
+declaring an RRA is
+
+ RRA:CF:xff:step:rows
+
+RRA is the keyword to declare RRAs. The consolidation function (CF)
+can be AVERAGE, MINIMUM, MAXIMUM, and LAST. The concept of the
+consolidated data point (CDP) comes into the picture here. A CDP is
+CFed (averaged, maximum/minimum value or last value) from I<step>
+number of PDPs. This RRA will hold I<rows> CDPs.
+
+Lets have a look at the example above. For the first RRA, 12 (steps)
+PDPs (DS variables) are AVERAGEed (CF) to form one CDP. 24 (rows) of
+theses CDPs are archived. Each PDP occurs at 300 seconds. 12 PDPs
+represent 12 times 300 seconds which is 1 hour. It means 1 CDP (which
+is equal to 12 PDPs) represents data worth 1 hour. 24 such CDPs
+represent 1 day (1 hour times 24 CDPs). This means, this RRA is an
+archive for one day. After 24 CDPs, CDP number 25 will replace the 1st
+CDP. The second RRA saves 31 CDPs; each CPD represents an AVERAGE
+value for a day (288 PDPs, each covering 300 seconds = 24
+hours). Therefore this RRA is an archive for one month. A single
+database can have many RRAs. If there are multiple DSs, each
+individual RRA will save data for all the DSs in the database. For
+example, if a database has 3 DSs and daily, weekly, monthly, and
+yearly RRAs are declared, then each RRA will hold data from all 3 data
+sources.
+
+=head2 Graphical Magic
+
+Another important feature of RRDtool is its ability to create
+graphs. The "graph" command uses the "fetch" command internally to
+retrieve values from the database. With the retrieved values it draws
+graphs as defined by the parameters supplied on the command line. A
+single graph can show different DS (Data Sources) from a database. It
+is also possible to show the values from more than one database in a
+single graph. Often, it is necessary to perform some math on the
+values retrieved from the database before plotting them. For example,
+in SNMP replies, memory consumption values are usually specified in
+KBytes and traffic flow on interfaces is specified in Bytes. Graphs
+for these values will be more meaningful if values are represented in
+MBytes and mbps. The RRDtool graph command allows to define such
+conversions. Apart from mathematical calculations, it is also possible
+to perform logical operations such as greater than, less than, and
+if/then/else. If a database contains more than one RRA archive, then a
+question may arise - how does RRDtool decide which RRA archive to use
+for retrieving the values? RRDtool looks at several things when making
+its choice. First it makes sure that the RRA covers as much of the
+graphing time frame as possible. Second it looks at the resolution of
+the RRA compared to the resolution of the graph. It tries to find one
+which has the same or higher better resolution. With the "-r" option
+you can force RRDtool to assume a different resolution than the one
+calculated from the pixel width of the graph.
+
+Values of different variables can be presented in 5 different shapes
+in a graph - AREA, LINE1, LINE2, LINE3, and STACK. AREA is represented
+by a solid colored area with values as the boundary of this
+area. LINE1/2/3 (increasing width) are just plain lines representing
+the values. STACK is also an area but it is "stack"ed on top AREA or
+LINE1/2/3. Another important thing to note is that variables are
+plotted in the order they are defined in the graph command. Therefore
+care must be taken to define STACK only after defining AREA/LINE. It
+is also possible to put formatted comments within the graph. Detailed
+instructions can be found in the graph manual.
+
+=head2 Wrapping RRDtool within Shell/Perl script
+
+After understanding RRDtool it is now a time to actually use RRDtool
+in scripts. Tasks involved in network management are data collection,
+data storage, and data retrieval. In the following example, the
+previously created target.rrd database is used. Data collection and
+data storage is done using Shell scripts. Data retrieval and report
+generation is done using Perl scripts. These scripts are shown below:
+
+=head3 Shell script (collects data, updates database)
+
+ #!/bin/sh
+ a=0
+ while [ "$a" == 0 ]; do
+ snmpwalk -c public 192.168.1.250 hrSWRunPerfMem > snmp_reply
+ total_mem=`awk 'BEGIN {tot_mem=0}
+ { if ($NF == "KBytes")
+ {tot_mem=tot_mem+$(NF-1)}
+ }
+ END {print tot_mem}' snmp_reply`
+ # I can use N as a replacement for the current time
+ rrdtool update target.rrd N:$total_mem
+ # sleep until the next 300 seconds are full
+ perl -e 'sleep 300 - time % 300'
+ done # end of while loop
+
+=head3 Perl script (retrieves data from database and generates graphs and statistics)
+
+ #!/usr/bin/perl -w
+ # This script fetches data from target.rrd, creates a graph of memory
+ # consumption on the target (Dual P3 Processor 1 GHz, 656 MB RAM)
+
+ # call the RRD perl module
+ use lib qw( /usr/local/rrdtool-1.0.41/lib/perl ../lib/perl );
+ use RRDs;
+ my $cur_time = time(); # set current time
+ my $end_time = $cur_time - 86400; # set end time to 24 hours ago
+ my $start_time = $end_time - 2592000; # set start 30 days in the past
+
+ # fetch average values from the RRD database between start and end time
+ my ($start,$step,$ds_names,$data) =
+ RRDs::fetch("target.rrd", "AVERAGE",
+ "-r", "600", "-s", "$start_time", "-e", "$end_time");
+ # save fetched values in a 2-dimensional array
+ my $rows = 0;
+ my $columns = 0;
+ my $time_variable = $start;
+ foreach $line (@$data) {
+ $vals[$rows][$columns] = $time_variable;
+ $time_variable = $time_variable + $step;
+ foreach $val (@$line) {
+ $vals[$rows][++$columns] = $val;}
+ $rows++;
+ $columns = 0;
+ }
+ my $tot_time = 0;
+ my $count = 0;
+ # save the values from the 2-dimensional into a 1-dimensional array
+ for $i ( 0 .. $#vals ) {
+ $tot_mem[$count] = $vals[$i][1];
+ $count++;
+ }
+ my $tot_mem_sum = 0;
+ # calculate the total of all values
+ for $i ( 0 .. ($count-1) ) {
+ $tot_mem_sum = $tot_mem_sum + $tot_mem[$i];
+ }
+ # calculate the average of the array
+ my $tot_mem_ave = $tot_mem_sum/($count);
+ # create the graph
+ RRDs::graph ("/images/mem_$count.png", \
+ "--title= Memory Usage", \
+ "--vertical-label=Memory Consumption (MB)", \
+ "--start=$start_time", \
+ "--end=$end_time", \
+ "--color=BACK#CCCCCC", \
+ "--color=CANVAS#CCFFFF", \
+ "--color=SHADEB#9999CC", \
+ "--height=125", \
+ "--upper-limit=656", \
+ "--lower-limit=0", \
+ "--rigid", \
+ "--base=1024", \
+ "DEF:tot_mem=target.rrd:mem:AVERAGE", \
+ "CDEF:tot_mem_cor=tot_mem,0,671744,LIMIT,UN,0,tot_mem,IF,1024,/",\
+ "CDEF:machine_mem=tot_mem,656,+,tot_mem,-",\
+ "COMMENT:Memory Consumption between $start_time",\
+ "COMMENT: and $end_time ",\
+ "HRULE:656#000000:Maximum Available Memory - 656 MB",\
+ "AREA:machine_mem#CCFFFF:Memory Unused", \
+ "AREA:tot_mem_cor#6699CC:Total memory consumed in MB");
+ my $err=RRDs::error;
+ if ($err) {print "problem generating the graph: $err\n";}
+ # print the output
+ print "Average memory consumption is ";
+ printf "%5.2f",$tot_mem_ave/1024;
+ print " MB. Graphical representation can be found at /images/mem_$count.png.";
+
+=head1 AUTHOR
+
+Ketan Patel E<lt>k2pattu@yahoo.comE<gt>
+
diff --git a/program/doc/rrdbuild.pod b/program/doc/rrdbuild.pod
--- /dev/null
+++ b/program/doc/rrdbuild.pod
@@ -0,0 +1,365 @@
+=head1 NAME
+
+rrdbuild - Instructions for building RRDtool
+
+=head1 OVERVIEW
+
+If you downloaded the source of rrdtool you have to compile it. This
+document will give some information on how this is done.
+
+RRDtool relies on services of third part libraries. Some of these libraries
+may already be installed on your system. You have to compile copies of the other
+ones before you can build RRDtool.
+
+This document will tell you about all the necessary steps to get going.
+
+These instructions assume you are using a B<bash> shell. If you use csh/tcsh,
+then you can either type F<bash> to switch to bash for the compilation or if
+you know what you are doing just replace the export bits with
+setenv.
+
+We further assume that your copies of B<tar> and B<make> are actually B<GNU
+tar> and B<GNU make> respectively. It could be that they are installed as
+B<gtar> and B<gmake> on your system.
+
+=head1 OPTIMISTIC BUILD
+
+Before you start to build RRDtool, you have to decide two things:
+
+=over
+
+=item 1.
+
+In which directory you want to build the software.
+
+=item 2.
+
+Where you want to install the software.
+
+=back
+
+Once you have decided. Save the two locations into environment variables.
+
+ BUILD_DIR=/tmp/rrdbuild
+ INSTALL_DIR=/usr/local/rrdtool-1.3.2
+
+
+If your F</tmp> is mounted with the option noexec (RHEL seems todo that) you have to choose
+a different directory!
+
+Now make sure the BUILD_DIR exists and go there:
+
+ mkdir -p $BUILD_DIR
+ cd $BUILD_DIR
+
+Lets first assume you already have all the necessary libraries
+pre-installed.
+
+ wget http://oss.oetiker.ch/rrdtool/pub/rrdtool-1.3.2.tar.gz
+ gunzip -c rrdtool-1.3.2.tar.gz | tar xf -
+ cd rrdtool-1.3.2
+ ./configure --prefix=$INSTALL_DIR && make && make install
+
+Ok, this was very optimistic. This try will probably have ended with
+B<configure> complaining about several missing libraries.
+
+=head1 INSTALLING DEPENDENCIES
+
+If your OS lets you install additional packages from a software repository,
+you may get away with installing the missing packages. When the packages are
+installed, run configure again and try to compile again. Below you find some
+hints on getting your OS ready for the rrdtool compilation.
+
+Additions to this list are welcome.
+
+=head2 OpenSolaris 2008.05
+
+Just add a compiler and the gnome development package:
+
+ pkg install sunstudioexpress
+ pkg install SUNWgnome-common-devel
+
+There is a problem with F<cairo.pc> on opensolaris. It suggests that
+xrender is required for compilation with cairo. This is not true and also
+bad since opensolaris does not include an F<xrender.pc> file. Use perl to
+fix this:
+
+ perl -i~ -p -e 's/(Requires.*?)\s*xrender.*/$1/' /usr/lib/pkgconfig/cairo.pc
+
+Make sure rrdtool finds your new compiler
+
+ export PATH=/opt/SunStudioExpress/bin
+
+Since there does not seem to ba a viable msgfmt tool on opensolaris (short
+of installing it yourself). You have to call configure with the
+
+ --disable-libintl
+
+option.
+
+=head2 Debian / Ubuntu
+
+Use apt-get to make sure you have all that is required. A number
+of packages will get added through dependencies.
+
+ apt-get install libpango1.0-dev libxml2-dev
+
+=head2 Gentoo
+
+In Gentoo installing rrdtool is really simple you just need to B<emerge
+rrdtool>. All dependencies will be handled automatically by the portage
+system. The only thing you should care about are USE flags, which allow you
+fine tune features rrdtool will be built with. Currently the following USE
+flags are available:
+
+ doc - install .html and .txt documentation
+ into /usr/share/doc/rrdtool-1.x.xx/
+ perl - build and install perl language bindings
+ python - build and install python language bindings
+ ruby - build and install ruby language bindings
+ tcl - build and install tcl language bindings
+ rrdcgi - build and install rrdcgi
+
+After you've decided which USE flags you need, set them either in
+F<make.conf> or F</etc/portage/package.use> and finally run:
+
+ # emerge -va rrdtool
+
+Take a look at Gentoo handbook for further details on how to manage USE
+flags: http://www.gentoo.org/doc/en/handbook/handbook-x86.xml?part=2
+
+=head1 BUILDING DEPENDENCIES
+
+But again this may have been too optimistic still, and you actually have to
+compile your own copies of some of the required libraries. Things like
+libpng and zlib are pretty standard so you will probably have them on your
+system anyway. Freetype, Fontinst, Cairo, Pango may be installed, but it is
+possible that they are pretty old and thus don't live up to our
+expectations, so you may want to compile their latest versions.
+
+=head2 General build tips for AIX
+
+If you are working with AIX, you may find the the B<--disable-shared> option
+will cause things to break for you. In that case you may have to install the
+shared libraries into the rrdtool PREFIX and work with B<--disable-static>
+instead.
+
+Another hint to get rrdtool working on AIX is to use the IBM XL C Compiler:
+
+ export CC=/usr/vac/bin/cc
+ export PERLCC=$CC
+
+(Better instructions for AIX welcome!)
+
+=head2 Build Instructions
+
+In order to build rrdtool need a compiler on your system. Unfortunately
+compilers are not all alike. This has an effect on the CFLAGS you want to
+set. The examples below are for the popular GCC compiler suite. If you have
+an other compilers here are some ides:
+
+=over
+
+=item Sun Forte
+
+ CFLAGS="-xO3 -kPIC"
+
+=back
+
+Some libraries want to know where other libraries are. For this to work,
+set the following environment variable
+
+ export PKG_CONFIG_PATH=${INSTALL_DIR}/lib/pkgconfig
+ export PATH=$INSTALL_DIR/bin:$PATH
+
+The above relies on the presence of the F<pkgconfig> program. Below you find instructions
+on how to compile pkgconfig.
+
+Since we are compiling libraries dynamically, they must know where to find
+each other. This is done by setting an appropriate LDFLAGS. Unfortunately,
+the syntax again differs from system to system:
+
+=over
+
+=item Solaris
+
+ export LDFLAGS=-R${INSTALL_DIR}/lib
+
+=item Linux
+
+ export LDFLAGS="-Wl,--rpath -Wl,${INSTALL_DIR}/lib"
+
+=item HPUX
+
+ export LDFLAGS="+b${INSTALL_DIR}/lib"
+
+=item AIX
+
+ export LDFLAGS="-Wl,-blibpath:${INSTALL_DIR}/lib"
+
+=back
+
+If you have GNUmake installed and it is not called 'make',
+then do
+
+ export MAKE=gmake
+ export GNUMAKE=gmake
+
+otherwise just do
+
+ export MAKE=make
+
+=head3 Building pkgconfig
+
+As mentioned above, without pkgconfig the whole build process will be lots
+of pain and suffering, so make sure you have a copy on your system. If it is
+not available natively, here is how to compile it.
+
+ wget http://pkgconfig.freedesktop.org/releases/pkg-config-0.23.tar.gz
+ gunzip -c pkg-config-0.23.tar.gz | tar xf -
+ cd pkg-config-0.23
+ ./configure --prefix=$INSTALL_DIR CFLAGS="-O3 -fPIC"
+ $MAKE
+ $MAKE install
+
+=head3 Building zlib
+
+Chances are very high that you already have that on your system ...
+
+ cd $BUILD_DIR
+ wget http://oss.oetiker.ch/rrdtool/pub/libs/zlib-1.2.3.tar.gz
+ gunzip -c zlib-1.2.3.tar.gz | tar xf -
+ cd zlib-1.2.3
+ ./configure --prefix=$INSTALL_DIR CFLAGS="-O3 -fPIC" --shared
+ $MAKE
+ $MAKE install
+
+=head3 Building libpng
+
+Libpng itself requires zlib to build, so we need to help a bit. If you
+already have a copy of zlib on your system (which is very likely) you can
+drop the settings of LDFLAGS and CPPFLAGS. Note that the backslash (\) at
+the end of line 4 means that line 4 and line 5 are on one line.
+
+ cd $BUILD_DIR
+ wget http://oss.oetiker.ch/rrdtool/pub/libs/libpng-1.2.18.tar.gz
+ gunzip -c libpng-1.2.18.tar.gz | tar xf -
+ cd libpng-1.2.10
+ env CFLAGS="-O3 -fPIC" ./configure --prefix=$INSTALL_DIR
+ $MAKE
+ $MAKE install
+
+=head3 Building freetype
+
+ cd $BUILD_DIR
+ wget http://oss.oetiker.ch/rrdtool/pub/libs/freetype-2.3.5.tar.gz
+ gunzip -c freetype-2.3.5.tar.gz | tar xf -
+ cd freetype-2.3.5
+ ./configure --prefix=$INSTALL_DIR CFLAGS="-O3 -fPIC"
+ $MAKE
+ $MAKE install
+
+If you run into problems building freetype on Solaris, you may want to try to
+add the following at the start the configure line:
+
+ env EGREP=egrep
+
+=head3 Building LibXML2
+
+ cd $BUILD_DIR
+ wget http://oss.oetiker.ch/rrdtool/pub/libs/libxml2-sources-2.6.31.tar.gz
+ gunzip -c libxml2-sources-2.6.32.tar.gz | tar xf -
+ cd libxml2-sources-2.6.32
+ ./configure --prefix=$INSTALL_DIR CFLAGS="-O3 -fPIC"
+ $MAKE
+ $MAKE install
+
+=head3 Building fontconfig
+
+Note that fontconfig has a run time configuration file in INSTALL_DIR/etc you
+may want to adjust that so that fontconfig finds the fonts on your system.
+Run the fc-cache program to build the fontconfig cache after changing the
+config file.
+
+ cd $BUILD_DIR
+ wget http://oss.oetiker.ch/rrdtool/pub/libs/fontconfig-2.4.2.tar.gz
+ gunzip -c fontconfig-2.4.2.tar.gz | tar xf -
+ cd fontconfig-2.4.2
+ ./configure --prefix=$INSTALL_DIR CFLAGS="-O3 -fPIC"
+ $MAKE
+ $MAKE install
+
+=head3 Building Pixman
+
+ cd $BUILD_DIR
+ wget http://oss.oetiker.ch/rrdtool/pub/libs/pixman-0.10.0.tar.gz
+ gunzip -c pixman-0.10.0.tar.gz | tar xf -
+ cd pixman-0.10.0
+ ./configure --prefix=$INSTALL_DIR CFLAGS="-O3 -fPIC"
+ $MAKE
+ $MAKE install
+
+=head3 Building Cairo
+
+ cd $BUILD_DIR
+ wget http://oss.oetiker.ch/rrdtool/pub/libs/cairo-1.6.4.tar.gz
+ gunzip -c cairo-1.4.10.tar.gz | tar xf -
+ cd cairo-1.4.10
+ ./configure --prefix=$INSTALL_DIR \
+ --enable-xlib=no \
+ --enable-xlib-render=no \
+ --enable-win32=no \
+ CFLAGS="-O3 -fPIC"
+ $MAKE
+ $MAKE install
+
+=head3 Building Glib
+
+ cd $BUILD_DIR
+ wget http://oss.oetiker.ch/rrdtool/pub/libs/glib-2.15.4.tar.gz
+ gunzip -c glib-2.12.13.tar.gz | tar xf -
+ cd glib-2.12.13
+ ./configure --prefix=$INSTALL_DIR CFLAGS="-O3 -fPIC"
+ $MAKE
+ $MAKE install
+
+=head3 Building Pango
+
+ cd $BUILD_DIR
+ wget http://oss.oetiker.ch/rrdtool/pub/libs/pango-1.21.1.tar.gz
+ gunzip -c pango-1.21.1.tar.gz | tar xf -
+ cd pango-1.21.1
+ ./configure --prefix=$INSTALL_DIR CFLAGS="-O3 -fPIC" --without-x
+ $MAKE
+ $MAKE install
+
+=head2 Building rrdtool (second try)
+
+Now all the dependent libraries are built and you can try again. This time
+you tell configure where it should be looking for libraries and include
+files. This is done via environment variables. Depending on the shell you
+are running, the syntax for setting environment variables is different.
+
+And finally try building again. We disable the python and tcl bindings
+because it seems that a fair number of people have ill configured python and
+tcl setups that would prevent rrdtool from building if they are included in
+their current state.
+
+ cd $BUILD_DIR/rrdtool-1.3.2
+ ./configure --prefix=$INSTALL_DIR --disable-tcl --disable-python
+ $MAKE clean
+ $MAKE
+ $MAKE install
+
+SOLARIS HINT: if you want to build the perl module for the native perl (the
+one shipping with Solaris) you will need the Sun Forte compiler installed on
+your box or you have to hand-tune bindings/perl-shared/Makefile while
+building!
+
+Now go to I<$INSTALL_DIR>B</share/rrdtool/examples/> and run them to see if
+your build has been successful.
+
+=head1 AUTHOR
+
+Tobias Oetiker E<lt>tobi@oetiker.chE<gt>
+
diff --git a/program/doc/rrdcgi.pod b/program/doc/rrdcgi.pod
--- /dev/null
+++ b/program/doc/rrdcgi.pod
@@ -0,0 +1,225 @@
+=head1 NAME
+
+rrdcgi - Create web pages containing RRD graphs based on templates
+
+=head1 SYNOPSIS
+
+C<#!/path/to/>B<rrdcgi> S<[B<--filter>]>
+
+=head1 DESCRIPTION
+
+B<rrdcgi> is a sort of very limited script interpreter. Its purpose
+is to run as a cgi-program and parse a web page template containing special
+E<lt>RRD:: tags. B<rrdcgi> will interpret and act according to these tags.
+In the end it will printout a web page including the necessary CGI headers.
+
+B<rrdcgi> parses the contents of the template in 3 steps. In each step it looks
+only for a subset of tags. This allows nesting of tags.
+
+The argument parser uses the same semantics as you are used from your C-shell.
+
+=over 8
+
+=item B<--filter>
+
+Assume that rrdcgi is run as a filter and not as a cgi.
+
+=back
+
+=head2 Keywords
+
+=over 8
+
+=item RRD::CV I<name>
+
+Inserts the CGI variable of the given name.
+
+=item RRD::CV::QUOTE I<name>
+
+Inserts the CGI variable of the given name but quotes it, ready for
+use as an argument in another RRD:: tag. So even when there are spaces in the
+value of the CGI variable it will still be considered to be one argument.
+
+=item RRD::CV::PATH I<name>
+
+Inserts the CGI variable of the given name, quotes it and makes sure
+it starts neither with a '/' nor contains '..'. This is to make
+sure that no problematic pathnames can be introduced through the
+CGI interface.
+
+=item RRD::GETENV I<variable>
+
+Get the value of an environment variable.
+
+ <RRD::GETENV REMOTE_USER>
+
+might give you the name of the remote user given you are using
+some sort of access control on the directory.
+
+
+=item RRD::GOODFOR I<seconds>
+
+Specify the number of seconds this page should remain valid. This will prompt
+the rrdcgi to output a Last-Modified, an Expire and if the number of
+seconds is I<negative> a Refresh header.
+
+=item RRD::INCLUDE I<filename>
+
+Include the contents of the specified file into the page returned from the cgi.
+
+=item RRD::SETENV I<variable> I<value>
+
+If you want to present your graphs in another time zone than your own, you
+could use
+
+ <RRD::SETENV TZ UTC>
+
+to make sure everything is presented in Universal Time. Note that the
+values permitted to TZ depend on your OS.
+
+=item RRD::SETVAR I<variable> I<value>
+
+Analog to SETENV but for local variables.
+
+=item RRD::GETVAR I<variable>
+
+Analog to GETENV but for local variables.
+
+=item RRD::TIME::LAST I<rrd-file> I<strftime-format>
+
+This gets replaced by the last modification time of the selected RRD. The
+time is I<strftime>-formatted with the string specified in the second argument.
+
+=item RRD::TIME::NOW I<strftime-format>
+
+This gets replaced by the current time of day. The time is
+I<strftime>-formatted with the string specified in the argument.
+
+Note that if you return : (colons) from your strftime format you may
+have to escape them using \ if the time is to be used as an argument
+to a GRAPH command.
+
+=item RRD::TIME::STRFTIME I<START|END> I<start-spec> I<end-spec> I<strftime-format>
+
+This gets replaced by a strftime-formatted time using the format
+I<strftime-format> on either I<start-spec> or I<end-spec> depending on
+whether I<START> or I<END> is specified. Both I<start-spec> and I<end-spec>
+must be supplied as either could be relative to the other. This is intended
+to allow pretty titles on graphs with times that are easier for non RRDtool
+folks to figure out than "-2weeks".
+
+Note that again, if you return : (colon) from your strftime format,
+you may have to escape them using \ if the time is to be used as an
+argument to a GRAPH command.
+
+=item RRD::GRAPH I<rrdgraph arguments>
+
+This tag creates the RRD graph defined by its argument and then is
+replaced by an appropriate E<lt>IMG ... E<gt> tag referring to the graph.
+The B<--lazy> option in RRD graph can be used to make sure that graphs
+are only regenerated when they are out of date. The arguments
+to the B<RRD::GRAPH> tag work as described in the B<rrdgraph> manual page.
+
+Use the B<--lazy> option in your RRD::GRAPH tags, to reduce the load
+on your server. This option makes sure that graphs are only regenerated when
+the old ones are out of date.
+
+If you do not specify your own B<--imginfo> format, the following will
+be used:
+
+ <IMG SRC="%s" WIDTH="%lu" HEIGHT="%lu">
+
+Note that %s stands for the filename part of the graph generated, all
+directories given in the PNG file argument will get dropped.
+
+=item RRD::PRINT I<number>
+
+If the preceding B<RRD::GRAPH> tag contained and B<PRINT> arguments,
+then you can access their output with this tag. The I<number> argument refers to the
+number of the B<PRINT> argument. This first B<PRINT> has I<number> 0.
+
+=item RRD::INTERNAL <var>
+
+This tag gets replaced by an internal var. Currently these vars are known:
+VERSION, COMPILETIME.
+These vars represent the compiled-in values.
+
+=back
+
+=head1 EXAMPLE 1
+
+The example below creates a web pages with a single RRD graph.
+
+ #!/usr/local/bin/rrdcgi
+ <HTML>
+ <HEAD><TITLE>RRDCGI Demo</TITLE></HEAD>
+ <BODY>
+ <H1>RRDCGI Example Page</H1>
+ <P>
+ <RRD::GRAPH demo.png --lazy --title="Temperatures"
+ DEF:cel=demo.rrd:exhaust:AVERAGE
+ LINE2:cel#00a000:"D. Celsius">
+
+ </P>
+ </BODY>
+ </HTML>
+
+=head1 EXAMPLE 2
+
+This script is slightly more elaborate, it allows you to run it from
+a form which sets RRD_NAME. RRD_NAME is then used to select which RRD
+you want to use as source for your graph.
+
+ #!/usr/local/bin/rrdcgi
+ <HTML>
+ <HEAD><TITLE>RRDCGI Demo</TITLE></HEAD>
+ <BODY>
+ <H1>RRDCGI Example Page for <RRD::CV RRD_NAME></H1>
+ <H2>Selection</H2>
+ <FORM><INPUT NAME=RRD_NAME TYPE=RADIO VALUE=roomA> Room A,
+ <INPUT NAME=RRD_NAME TYPE=RADIO VALUE=roomB> Room B.
+ <INPUT TYPE=SUBMIT></FORM>
+ <H2>Graph</H2>
+ <P>
+ <RRD::GRAPH <RRD::CV::PATH RRD_NAME>.png --lazy
+ --title "Temperatures for "<RRD::CV::QUOTE RRD_NAME>
+ DEF:cel=<RRD::CV::PATH RRD_NAME>.rrd:exhaust:AVERAGE
+ LINE2:cel#00a000:"D. Celsius">
+
+ </P>
+ </BODY>
+ </HTML>
+
+=head1 EXAMPLE 3
+
+This example shows how to handle the case where the RRD, graphs and
+cgi-bins are seperate directories
+
+ #!/.../bin/rrdcgi
+ <HTML>
+ <HEAD><TITLE>RRDCGI Demo</TITLE></HEAD>
+ <BODY>
+ <H1>RRDCGI test Page</H1>
+ <RRD::GRAPH
+ /.../web/pngs/testhvt.png
+ --imginfo '<IMG SRC=/.../pngs/%s WIDTH=%lu HEIGHT=%lu >'
+ --lazy --start -1d --end now
+ DEF:http_src=/.../rrds/test.rrd:http_src:AVERAGE
+ AREA:http_src#00ff00:http_src
+ >
+ </BODY>
+ </HTML>
+
+Note 1: Replace /.../ with the relevant directories
+
+Note 2: The SRC=/.../pngs should be paths from the view of the
+webserver/browser
+
+=head1 AUTHOR
+
+Tobias Oetiker E<lt>tobi@oetiker.chE<gt>
+
+
+
+
+
diff --git a/program/doc/rrdcreate.pod b/program/doc/rrdcreate.pod
--- /dev/null
@@ -0,0 +1,593 @@
+=head1 NAME
+
+rrdcreate - Set up a new Round Robin Database
+
+=head1 SYNOPSIS
+
+B<rrdtool> B<create> I<filename>
+S<[B<--start>|B<-b> I<start time>]>
+S<[B<--step>|B<-s> I<step>]>
+S<[B<DS:>I<ds-name>B<:>I<DST>B<:>I<dst arguments>]>
+S<[B<RRA:>I<CF>B<:>I<cf arguments>]>
+
+=head1 DESCRIPTION
+
+The create function of RRDtool lets you set up new Round Robin
+Database (B<RRD>) files. The file is created at its final, full size
+and filled with I<*UNKNOWN*> data.
+
+=head2 I<filename>
+
+The name of the B<RRD> you want to create. B<RRD> files should end
+with the extension F<.rrd>. However, B<RRDtool> will accept any
+filename.
+
+=head2 B<--start>|B<-b> I<start time> (default: now - 10s)
+
+Specifies the time in seconds since 1970-01-01 UTC when the first
+value should be added to the B<RRD>. B<RRDtool> will not accept
+any data timed before or at the time specified.
+
+See also AT-STYLE TIME SPECIFICATION section in the
+I<rrdfetch> documentation for other ways to specify time.
+
+=head2 B<--step>|B<-s> I<step> (default: 300 seconds)
+
+Specifies the base interval in seconds with which data will be fed
+into the B<RRD>.
+
+=head2 B<DS:>I<ds-name>B<:>I<DST>B<:>I<dst arguments>
+
+A single B<RRD> can accept input from several data sources (B<DS>),
+for example incoming and outgoing traffic on a specific communication
+line. With the B<DS> configuration option you must define some basic
+properties of each data source you want to store in the B<RRD>.
+
+I<ds-name> is the name you will use to reference this particular data
+source from an B<RRD>. A I<ds-name> must be 1 to 19 characters long in
+the characters [a-zA-Z0-9_].
+
+I<DST> defines the Data Source Type. The remaining arguments of a
+data source entry depend on the data source type. For GAUGE, COUNTER,
+DERIVE, and ABSOLUTE the format for a data source entry is:
+
+B<DS:>I<ds-name>B<:>I<GAUGE | COUNTER | DERIVE | ABSOLUTE>B<:>I<heartbeat>B<:>I<min>B<:>I<max>
+
+For COMPUTE data sources, the format is:
+
+B<DS:>I<ds-name>B<:>I<COMPUTE>B<:>I<rpn-expression>
+
+In order to decide which data source type to use, review the
+definitions that follow. Also consult the section on "HOW TO MEASURE"
+for further insight.
+
+=over
+
+=item B<GAUGE>
+
+is for things like temperatures or number of people in a room or the
+value of a RedHat share.
+
+=item B<COUNTER>
+
+is for continuous incrementing counters like the ifInOctets counter in
+a router. The B<COUNTER> data source assumes that the counter never
+decreases, except when a counter overflows. The update function takes
+the overflow into account. The counter is stored as a per-second
+rate. When the counter overflows, RRDtool checks if the overflow
+happened at the 32bit or 64bit border and acts accordingly by adding
+an appropriate value to the result.
+
+=item B<DERIVE>
+
+will store the derivative of the line going from the last to the
+current value of the data source. This can be useful for gauges, for
+example, to measure the rate of people entering or leaving a
+room. Internally, derive works exactly like COUNTER but without
+overflow checks. So if your counter does not reset at 32 or 64 bit you
+might want to use DERIVE and combine it with a MIN value of 0.
+
+B<NOTE on COUNTER vs DERIVE>
+
+by Don Baarda E<lt>don.baarda@baesystems.comE<gt>
+
+If you cannot tolerate ever mistaking the occasional counter reset for a
+legitimate counter wrap, and would prefer "Unknowns" for all legitimate
+counter wraps and resets, always use DERIVE with min=0. Otherwise, using
+COUNTER with a suitable max will return correct values for all legitimate
+counter wraps, mark some counter resets as "Unknown", but can mistake some
+counter resets for a legitimate counter wrap.
+
+For a 5 minute step and 32-bit counter, the probability of mistaking a
+counter reset for a legitimate wrap is arguably about 0.8% per 1Mbps of
+maximum bandwidth. Note that this equates to 80% for 100Mbps interfaces, so
+for high bandwidth interfaces and a 32bit counter, DERIVE with min=0 is
+probably preferable. If you are using a 64bit counter, just about any max
+setting will eliminate the possibility of mistaking a reset for a counter
+wrap.
+
+=item B<ABSOLUTE>
+
+is for counters which get reset upon reading. This is used for fast counters
+which tend to overflow. So instead of reading them normally you reset them
+after every read to make sure you have a maximum time available before the
+next overflow. Another usage is for things you count like number of messages
+since the last update.
+
+=item B<COMPUTE>
+
+is for storing the result of a formula applied to other data sources
+in the B<RRD>. This data source is not supplied a value on update, but
+rather its Primary Data Points (PDPs) are computed from the PDPs of
+the data sources according to the rpn-expression that defines the
+formula. Consolidation functions are then applied normally to the PDPs
+of the COMPUTE data source (that is the rpn-expression is only applied
+to generate PDPs). In database software, such data sets are referred
+to as "virtual" or "computed" columns.
+
+=back
+
+I<heartbeat> defines the maximum number of seconds that may pass
+between two updates of this data source before the value of the
+data source is assumed to be I<*UNKNOWN*>.
+
+I<min> and I<max> define the expected range values for data supplied by a
+data source. If I<min> and/or I<max> any value outside the defined range
+will be regarded as I<*UNKNOWN*>. If you do not know or care about min and
+max, set them to U for unknown. Note that min and max always refer to the
+processed values of the DS. For a traffic-B<COUNTER> type DS this would be
+the maximum and minimum data-rate expected from the device.
+
+I<If information on minimal/maximal expected values is available,
+always set the min and/or max properties. This will help RRDtool in
+doing a simple sanity check on the data supplied when running update.>
+
+I<rpn-expression> defines the formula used to compute the PDPs of a
+COMPUTE data source from other data sources in the same <RRD>. It is
+similar to defining a B<CDEF> argument for the graph command. Please
+refer to that manual page for a list and description of RPN operations
+supported. For COMPUTE data sources, the following RPN operations are
+not supported: COUNT, PREV, TIME, and LTIME. In addition, in defining
+the RPN expression, the COMPUTE data source may only refer to the
+names of data source listed previously in the create command. This is
+similar to the restriction that B<CDEF>s must refer only to B<DEF>s
+and B<CDEF>s previously defined in the same graph command.
+
+=head2 B<RRA:>I<CF>B<:>I<cf arguments>
+
+The purpose of an B<RRD> is to store data in the round robin archives
+(B<RRA>). An archive consists of a number of data values or statistics for
+each of the defined data-sources (B<DS>) and is defined with an B<RRA> line.
+
+When data is entered into an B<RRD>, it is first fit into time slots
+of the length defined with the B<-s> option, thus becoming a I<primary
+data point>.
+
+The data is also processed with the consolidation function (I<CF>) of
+the archive. There are several consolidation functions that
+consolidate primary data points via an aggregate function: B<AVERAGE>,
+B<MIN>, B<MAX>, B<LAST>.
+
+=over
+
+=item AVERAGE
+
+the average of the data points is stored.
+
+=item MIN
+
+the smallest of the data points is stored.
+
+=item MAX
+
+the largest of the data points is stored.
+
+=item LAST
+
+the last data points is used.
+
+=back
+
+Note that data aggregation inevitably leads to loss of precision and
+information. The trick is to pick the aggregate function such that the
+I<interesting> properties of your data is kept across the aggregation
+process.
+
+
+The format of B<RRA> line for these
+consolidation functions is:
+
+B<RRA:>I<AVERAGE | MIN | MAX | LAST>B<:>I<xff>B<:>I<steps>B<:>I<rows>
+
+I<xff> The xfiles factor defines what part of a consolidation interval may
+be made up from I<*UNKNOWN*> data while the consolidated value is still
+regarded as known. It is given as the ratio of allowed I<*UNKNOWN*> PDPs
+to the number of PDPs in the interval. Thus, it ranges from 0 to 1 (exclusive).
+
+
+I<steps> defines how many of these I<primary data points> are used to build
+a I<consolidated data point> which then goes into the archive.
+
+I<rows> defines how many generations of data values are kept in an B<RRA>.
+Obviously, this has to be greater than zero.
+
+=head1 Aberrant Behavior Detection with Holt-Winters Forecasting
+
+In addition to the aggregate functions, there are a set of specialized
+functions that enable B<RRDtool> to provide data smoothing (via the
+Holt-Winters forecasting algorithm), confidence bands, and the
+flagging aberrant behavior in the data source time series:
+
+=over
+
+=item *
+
+B<RRA:>I<HWPREDICT>B<:>I<rows>B<:>I<alpha>B<:>I<beta>B<:>I<seasonal period>[B<:>I<rra-num>]
+
+=item *
+
+B<RRA:>I<MHWPREDICT>B<:>I<rows>B<:>I<alpha>B<:>I<beta>B<:>I<seasonal period>[B<:>I<rra-num>]
+
+=item *
+
+B<RRA:>I<SEASONAL>B<:>I<seasonal period>B<:>I<gamma>B<:>I<rra-num>[B<:smoothing-window=>I<fraction>]
+
+=item *
+
+B<RRA:>I<DEVSEASONAL>B<:>I<seasonal period>B<:>I<gamma>B<:>I<rra-num>[B<:smoothing-window=>I<fraction>]
+
+=item *
+
+B<RRA:>I<DEVPREDICT>B<:>I<rows>B<:>I<rra-num>
+
+=item *
+
+B<RRA:>I<FAILURES>B<:>I<rows>B<:>I<threshold>B<:>I<window length>B<:>I<rra-num>
+
+=back
+
+These B<RRAs> differ from the true consolidation functions in several ways.
+First, each of the B<RRA>s is updated once for every primary data point.
+Second, these B<RRAs> are interdependent. To generate real-time confidence
+bounds, a matched set of SEASONAL, DEVSEASONAL, DEVPREDICT, and either
+HWPREDICT or MHWPREDICT must exist. Generating smoothed values of the primary
+data points requires a SEASONAL B<RRA> and either an HWPREDICT or MHWPREDICT
+B<RRA>. Aberrant behavior detection requires FAILURES, DEVSEASONAL, SEASONAL,
+and either HWPREDICT or MHWPREDICT.
+
+The predicted, or smoothed, values are stored in the HWPREDICT or MHWPREDICT
+B<RRA>. HWPREDICT and MHWPREDICT are actually two variations on the
+Holt-Winters method. They are interchangeable. Both attempt to decompose data
+into three components: a baseline, a trend, and a seasonal coefficient.
+HWPREDICT adds its seasonal coefficient to the baseline to form a prediction, whereas
+MHWPREDICT multiplies its seasonal coefficient by the baseline to form a
+prediction. The difference is noticeable when the baseline changes
+significantly in the course of a season; HWPREDICT will predict the seasonality
+to stay constant as the baseline changes, but MHWPREDICT will predict the
+seasonality to grow or shrink in proportion to the baseline. The proper choice
+of method depends on the thing being modeled. For simplicity, the rest of this
+discussion will refer to HWPREDICT, but MHWPREDICT may be substituted in its
+place.
+
+The predicted deviations are stored in DEVPREDICT (think a standard deviation
+which can be scaled to yield a confidence band). The FAILURES B<RRA> stores
+binary indicators. A 1 marks the indexed observation as failure; that is, the
+number of confidence bounds violations in the preceding window of observations
+met or exceeded a specified threshold. An example of using these B<RRAs> to graph
+confidence bounds and failures appears in L<rrdgraph>.
+
+The SEASONAL and DEVSEASONAL B<RRAs> store the seasonal coefficients for the
+Holt-Winters forecasting algorithm and the seasonal deviations, respectively.
+There is one entry per observation time point in the seasonal cycle. For
+example, if primary data points are generated every five minutes and the
+seasonal cycle is 1 day, both SEASONAL and DEVSEASONAL will have 288 rows.
+
+In order to simplify the creation for the novice user, in addition to
+supporting explicit creation of the HWPREDICT, SEASONAL, DEVPREDICT,
+DEVSEASONAL, and FAILURES B<RRAs>, the B<RRDtool> create command supports
+implicit creation of the other four when HWPREDICT is specified alone and
+the final argument I<rra-num> is omitted.
+
+I<rows> specifies the length of the B<RRA> prior to wrap around. Remember
+that there is a one-to-one correspondence between primary data points and
+entries in these RRAs. For the HWPREDICT CF, I<rows> should be larger than
+the I<seasonal period>. If the DEVPREDICT B<RRA> is implicitly created, the
+default number of rows is the same as the HWPREDICT I<rows> argument. If the
+FAILURES B<RRA> is implicitly created, I<rows> will be set to the I<seasonal
+period> argument of the HWPREDICT B<RRA>. Of course, the B<RRDtool>
+I<resize> command is available if these defaults are not sufficient and the
+creator wishes to avoid explicit creations of the other specialized function
+B<RRAs>.
+
+I<seasonal period> specifies the number of primary data points in a seasonal
+cycle. If SEASONAL and DEVSEASONAL are implicitly created, this argument for
+those B<RRAs> is set automatically to the value specified by HWPREDICT. If
+they are explicitly created, the creator should verify that all three
+I<seasonal period> arguments agree.
+
+I<alpha> is the adaption parameter of the intercept (or baseline)
+coefficient in the Holt-Winters forecasting algorithm. See L<rrdtool> for a
+description of this algorithm. I<alpha> must lie between 0 and 1. A value
+closer to 1 means that more recent observations carry greater weight in
+predicting the baseline component of the forecast. A value closer to 0 means
+that past history carries greater weight in predicting the baseline
+component.
+
+I<beta> is the adaption parameter of the slope (or linear trend) coefficient
+in the Holt-Winters forecasting algorithm. I<beta> must lie between 0 and 1
+and plays the same role as I<alpha> with respect to the predicted linear
+trend.
+
+I<gamma> is the adaption parameter of the seasonal coefficients in the
+Holt-Winters forecasting algorithm (HWPREDICT) or the adaption parameter in
+the exponential smoothing update of the seasonal deviations. It must lie
+between 0 and 1. If the SEASONAL and DEVSEASONAL B<RRAs> are created
+implicitly, they will both have the same value for I<gamma>: the value
+specified for the HWPREDICT I<alpha> argument. Note that because there is
+one seasonal coefficient (or deviation) for each time point during the
+seasonal cycle, the adaptation rate is much slower than the baseline. Each
+seasonal coefficient is only updated (or adapts) when the observed value
+occurs at the offset in the seasonal cycle corresponding to that
+coefficient.
+
+If SEASONAL and DEVSEASONAL B<RRAs> are created explicitly, I<gamma> need not
+be the same for both. Note that I<gamma> can also be changed via the
+B<RRDtool> I<tune> command.
+
+I<smoothing-window> specifies the fraction of a season that should be
+averaged around each point. By default, the value of I<smoothing-window> is
+0.05, which means each value in SEASONAL and DEVSEASONAL will be occasionally
+replaced by averaging it with its (I<seasonal period>*0.05) nearest neighbors.
+Setting I<smoothing-window> to zero will disable the running-average smoother
+altogether.
+
+I<rra-num> provides the links between related B<RRAs>. If HWPREDICT is
+specified alone and the other B<RRAs> are created implicitly, then
+there is no need to worry about this argument. If B<RRAs> are created
+explicitly, then carefully pay attention to this argument. For each
+B<RRA> which includes this argument, there is a dependency between
+that B<RRA> and another B<RRA>. The I<rra-num> argument is the 1-based
+index in the order of B<RRA> creation (that is, the order they appear
+in the I<create> command). The dependent B<RRA> for each B<RRA>
+requiring the I<rra-num> argument is listed here:
+
+=over
+
+=item *
+
+HWPREDICT I<rra-num> is the index of the SEASONAL B<RRA>.
+
+=item *
+
+SEASONAL I<rra-num> is the index of the HWPREDICT B<RRA>.
+
+=item *
+
+DEVPREDICT I<rra-num> is the index of the DEVSEASONAL B<RRA>.
+
+=item *
+
+DEVSEASONAL I<rra-num> is the index of the HWPREDICT B<RRA>.
+
+=item *
+
+FAILURES I<rra-num> is the index of the DEVSEASONAL B<RRA>.
+
+=back
+
+I<threshold> is the minimum number of violations (observed values outside
+the confidence bounds) within a window that constitutes a failure. If the
+FAILURES B<RRA> is implicitly created, the default value is 7.
+
+I<window length> is the number of time points in the window. Specify an
+integer greater than or equal to the threshold and less than or equal to 28.
+The time interval this window represents depends on the interval between
+primary data points. If the FAILURES B<RRA> is implicitly created, the
+default value is 9.
+
+=head1 The HEARTBEAT and the STEP
+
+Here is an explanation by Don Baarda on the inner workings of RRDtool.
+It may help you to sort out why all this *UNKNOWN* data is popping
+up in your databases:
+
+RRDtool gets fed samples/updates at arbitrary times. From these it builds Primary
+Data Points (PDPs) on every "step" interval. The PDPs are
+then accumulated into the RRAs.
+
+The "heartbeat" defines the maximum acceptable interval between
+samples/updates. If the interval between samples is less than "heartbeat",
+then an average rate is calculated and applied for that interval. If
+the interval between samples is longer than "heartbeat", then that
+entire interval is considered "unknown". Note that there are other
+things that can make a sample interval "unknown", such as the rate
+exceeding limits, or a sample that was explicitly marked as unknown.
+
+The known rates during a PDP's "step" interval are used to calculate
+an average rate for that PDP. If the total "unknown" time accounts for
+more than B<half> the "step", the entire PDP is marked
+as "unknown". This means that a mixture of known and "unknown" sample
+times in a single PDP "step" may or may not add up to enough "known"
+time to warrent for a known PDP.
+
+The "heartbeat" can be short (unusual) or long (typical) relative to
+the "step" interval between PDPs. A short "heartbeat" means you
+require multiple samples per PDP, and if you don't get them mark the
+PDP unknown. A long heartbeat can span multiple "steps", which means
+it is acceptable to have multiple PDPs calculated from a single
+sample. An extreme example of this might be a "step" of 5 minutes and a
+"heartbeat" of one day, in which case a single sample every day will
+result in all the PDPs for that entire day period being set to the
+same average rate. I<-- Don Baarda E<lt>don.baarda@baesystems.comE<gt>>
+
+ time|
+ axis|
+ begin__|00|
+ |01|
+ u|02|----* sample1, restart "hb"-timer
+ u|03| /
+ u|04| /
+ u|05| /
+ u|06|/ "hbt" expired
+ u|07|
+ |08|----* sample2, restart "hb"
+ |09| /
+ |10| /
+ u|11|----* sample3, restart "hb"
+ u|12| /
+ u|13| /
+ step1_u|14| /
+ u|15|/ "swt" expired
+ u|16|
+ |17|----* sample4, restart "hb", create "pdp" for step1 =
+ |18| / = unknown due to 10 "u" labled secs > 0.5 * step
+ |19| /
+ |20| /
+ |21|----* sample5, restart "hb"
+ |22| /
+ |23| /
+ |24|----* sample6, restart "hb"
+ |25| /
+ |26| /
+ |27|----* sample7, restart "hb"
+ step2__|28| /
+ |22| /
+ |23|----* sample8, restart "hb", create "pdp" for step1, create "cdp"
+ |24| /
+ |25| /
+
+graphics by I<vladimir.lavrov@desy.de>.
+
+
+=head1 HOW TO MEASURE
+
+Here are a few hints on how to measure:
+
+=over
+
+
+=item Temperature
+
+Usually you have some type of meter you can read to get the temperature.
+The temperature is not really connected with a time. The only connection is
+that the temperature reading happened at a certain time. You can use the
+B<GAUGE> data source type for this. RRDtool will then record your reading
+together with the time.
+
+=item Mail Messages
+
+Assume you have a method to count the number of messages transported by
+your mailserver in a certain amount of time, giving you data like '5
+messages in the last 65 seconds'. If you look at the count of 5 like an
+B<ABSOLUTE> data type you can simply update the RRD with the number 5 and the
+end time of your monitoring period. RRDtool will then record the number of
+messages per second. If at some later stage you want to know the number of
+messages transported in a day, you can get the average messages per second
+from RRDtool for the day in question and multiply this number with the
+number of seconds in a day. Because all math is run with Doubles, the
+precision should be acceptable.
+
+=item It's always a Rate
+
+RRDtool stores rates in amount/second for COUNTER, DERIVE and ABSOLUTE
+data. When you plot the data, you will get on the y axis
+amount/second which you might be tempted to convert to an absolute
+amount by multiplying by the delta-time between the points. RRDtool
+plots continuous data, and as such is not appropriate for plotting
+absolute amounts as for example "total bytes" sent and received in a
+router. What you probably want is plot rates that you can scale to
+bytes/hour, for example, or plot absolute amounts with another tool
+that draws bar-plots, where the delta-time is clear on the plot for
+each point (such that when you read the graph you see for example GB
+on the y axis, days on the x axis and one bar for each day).
+
+=back
+
+
+=head1 EXAMPLE
+
+ rrdtool create temperature.rrd --step 300 \
+ DS:temp:GAUGE:600:-273:5000 \
+ RRA:AVERAGE:0.5:1:1200 \
+ RRA:MIN:0.5:12:2400 \
+ RRA:MAX:0.5:12:2400 \
+ RRA:AVERAGE:0.5:12:2400
+
+This sets up an B<RRD> called F<temperature.rrd> which accepts one
+temperature value every 300 seconds. If no new data is supplied for
+more than 600 seconds, the temperature becomes I<*UNKNOWN*>. The
+minimum acceptable value is -273 and the maximum is 5'000.
+
+A few archive areas are also defined. The first stores the
+temperatures supplied for 100 hours (1'200 * 300 seconds = 100
+hours). The second RRA stores the minimum temperature recorded over
+every hour (12 * 300 seconds = 1 hour), for 100 days (2'400 hours). The
+third and the fourth RRA's do the same for the maximum and
+average temperature, respectively.
+
+=head1 EXAMPLE 2
+
+ rrdtool create monitor.rrd --step 300 \
+ DS:ifOutOctets:COUNTER:1800:0:4294967295 \
+ RRA:AVERAGE:0.5:1:2016 \
+ RRA:HWPREDICT:1440:0.1:0.0035:288
+
+This example is a monitor of a router interface. The first B<RRA> tracks the
+traffic flow in octets; the second B<RRA> generates the specialized
+functions B<RRAs> for aberrant behavior detection. Note that the I<rra-num>
+argument of HWPREDICT is missing, so the other B<RRAs> will implicitly be
+created with default parameter values. In this example, the forecasting
+algorithm baseline adapts quickly; in fact the most recent one hour of
+observations (each at 5 minute intervals) accounts for 75% of the baseline
+prediction. The linear trend forecast adapts much more slowly. Observations
+made during the last day (at 288 observations per day) account for only
+65% of the predicted linear trend. Note: these computations rely on an
+exponential smoothing formula described in the LISA 2000 paper.
+
+The seasonal cycle is one day (288 data points at 300 second intervals), and
+the seasonal adaption parameter will be set to 0.1. The RRD file will store 5
+days (1'440 data points) of forecasts and deviation predictions before wrap
+around. The file will store 1 day (a seasonal cycle) of 0-1 indicators in
+the FAILURES B<RRA>.
+
+The same RRD file and B<RRAs> are created with the following command,
+which explicitly creates all specialized function B<RRAs>.
+
+ rrdtool create monitor.rrd --step 300 \
+ DS:ifOutOctets:COUNTER:1800:0:4294967295 \
+ RRA:AVERAGE:0.5:1:2016 \
+ RRA:HWPREDICT:1440:0.1:0.0035:288:3 \
+ RRA:SEASONAL:288:0.1:2 \
+ RRA:DEVPREDICT:1440:5 \
+ RRA:DEVSEASONAL:288:0.1:2 \
+ RRA:FAILURES:288:7:9:5
+
+Of course, explicit creation need not replicate implicit create, a
+number of arguments could be changed.
+
+=head1 EXAMPLE 3
+
+ rrdtool create proxy.rrd --step 300 \
+ DS:Total:DERIVE:1800:0:U \
+ DS:Duration:DERIVE:1800:0:U \
+ DS:AvgReqDur:COMPUTE:Duration,Requests,0,EQ,1,Requests,IF,/ \
+ RRA:AVERAGE:0.5:1:2016
+
+This example is monitoring the average request duration during each 300 sec
+interval for requests processed by a web proxy during the interval.
+In this case, the proxy exposes two counters, the number of requests
+processed since boot and the total cumulative duration of all processed
+requests. Clearly these counters both have some rollover point, but using the
+DERIVE data source also handles the reset that occurs when the web proxy is
+stopped and restarted.
+
+In the B<RRD>, the first data source stores the requests per second rate
+during the interval. The second data source stores the total duration of all
+requests processed during the interval divided by 300. The COMPUTE data source
+divides each PDP of the AccumDuration by the corresponding PDP of
+TotalRequests and stores the average request duration. The remainder of the
+RPN expression handles the divide by zero case.
+
+=head1 AUTHOR
+
+Tobias Oetiker E<lt>tobi@oetiker.chE<gt>
diff --git a/program/doc/rrddump.pod b/program/doc/rrddump.pod
--- /dev/null
+++ b/program/doc/rrddump.pod
@@ -0,0 +1,68 @@
+=head1 NAME
+
+rrddump - dump the contents of an RRD to XML format
+
+=head1 SYNOPSIS
+
+B<rrdtool> B<dump> S<[B<--no-header>|B<-n>]> I<filename.rrd> E<gt> I<filename.xml>
+
+or
+
+B<rrdtool> B<dump> S<[B<--no-header>|B<-n>]> I<filename.rrd> I<filename.xml>
+
+=head1 DESCRIPTION
+
+The B<dump> function writes the contents of an B<RRD> in human
+readable (?) XML format to a file or to stdout. This format can
+be read by rrdrestore. Together they allow you to transfer your
+files from one computer architecture to another as well to
+manipulate the contents of an B<RRD> file in a somewhat more
+convenient manner.
+
+
+=over 8
+
+=item I<filename.rrd>
+
+The name of the B<RRD> you want to dump.
+
+=item I<filename.xml>
+
+The (optional) filename that you want to write the XML output to.
+If not specified, the XML will be printed to stdout.
+
+=item S<[B<--no-header>|B<-n>]>
+
+In rrdtool 1.3, the dump function started producing correct xml-headers.
+Unfortunately the rrdtool restore function from the 1.2 series can not
+handle these headers. With this option you can supress the creatinon of
+the xml headers.
+
+=back
+
+=head1 EXAMPLES
+
+To transfer an RRD between architectures, follow these steps:
+
+=over 4
+
+=item 1.
+
+On the same system where the RRD was created, use B<rrdtool> B<dump>
+to export the data to XML format.
+
+=item 2.
+
+Transfer the XML dump to the target system.
+
+=item 3.
+
+Run B<rrdtool> B<restore> to create a new RRD from the XML dump. See
+B<rrdrestore> for details.
+
+=back
+
+=head1 AUTHOR
+
+Tobias Oetiker E<lt>tobi@oetiker.chE<gt>
+
diff --git a/program/doc/rrdfetch.pod b/program/doc/rrdfetch.pod
--- /dev/null
+++ b/program/doc/rrdfetch.pod
@@ -0,0 +1,262 @@
+=head1 NAME
+
+rrdfetch - Fetch data from an RRD.
+
+=head1 SYNOPSIS
+
+B<rrdtool> B<fetch> I<filename> I<CF>
+S<[B<--resolution>|B<-r> I<resolution>]>
+S<[B<--start>|B<-s> I<start>]>
+S<[B<--end>|B<-e> I<end>]>
+
+=head1 DESCRIPTION
+
+The B<fetch> function is normally used internally by the graph
+function to get data from B<RRD>s. B<fetch> will analyze the B<RRD>
+and try to retrieve the data in the resolution requested.
+The data fetched is printed to stdout. I<*UNKNOWN*> data is often
+represented by the string "NaN" depending on your OS's printf
+function.
+
+=over 8
+
+=item I<filename>
+
+the name of the B<RRD> you want to fetch the data from.
+
+=item I<CF>
+
+the consolidation function that is applied to the data you
+want to fetch (AVERAGE,MIN,MAX,LAST)
+
+=item B<--resolution>|B<-r> I<resolution> (default is the highest resolution)
+
+the interval you want the values to have (seconds per
+value). B<rrdfetch> will try to match your request, but it will return
+data even if no absolute match is possible. B<NB.> See note below.
+
+=item B<--start>|B<-s> I<start> (default end-1day)
+
+start of the time series. A time in seconds since epoch (1970-01-01)
+is required. Negative numbers are relative to the current time. By default,
+one day worth of data will be fetched. See also AT-STYLE TIME SPECIFICATION
+section for a detailed explanation on ways to specify the start time.
+
+=item B<--end>|B<-e> I<end> (default now)
+
+the end of the time series in seconds since epoch. See also AT-STYLE
+TIME SPECIFICATION section for a detailed explanation of how to
+specify the end time.
+
+=back
+
+=head2 RESOLUTION INTERVAL
+
+In order to get RRDtool to fetch anything other than the finest resolution RRA
+B<both> the start and end time must be specified on boundaries that are
+multiples of the desired resolution. Consider the following example:
+
+ rrdtool create subdata.rrd -s 10 DS:ds0:GAUGE:300:0:U \
+ RRA:AVERAGE:0.5:30:3600 \
+ RRA:AVERAGE:0.5:90:1200 \
+ RRA:AVERAGE:0.5:360:1200 \
+ RRA:MAX:0.5:360:1200 \
+ RRA:AVERAGE:0.5:8640:600 \
+ RRA:MAX:0.5:8640:600
+
+This RRD collects data every 10 seconds and stores its averages over 5
+minutes, 15 minutes, 1 hour, and 1 day, as well as the maxima for 1 hour
+and 1 day.
+
+Consider now that you want to fetch the 15 minute average data for the
+last hour. You might try
+
+ rrdtool fetch subdata.rrd AVERAGE -r 900 -s -1h
+
+However, this will almost always result in a time series that is
+B<NOT> in the 15 minute RRA. Therefore, the highest resolution RRA,
+i.e. 5 minute averages, will be chosen which in this case is not
+what you want.
+
+Hence, make sure that
+
+=over 3
+
+=item 1.
+
+both start and end time are a multiple of 900
+
+=item 2.
+
+both start and end time are within the desired RRA
+
+=back
+
+So, if time now is called "t", do
+
+ end time == int(t/900)*900,
+ start time == end time - 1hour,
+ resolution == 900.
+
+Using the bash shell, this could look be:
+
+ TIME=$(date +%s)
+ RRDRES=900
+ rrdtool fetch subdata.rrd AVERAGE -r $RRDRES \
+ -e $(($TIME/$RRDRES*$RRDRES)) -s e-1h
+
+Or in Perl:
+
+ perl -e '$ctime = time; $rrdres = 900; \
+ system "rrdtool fetch subdata.rrd AVERAGE \
+ -r $rrdres -e @{[int($ctime/$rrdres)*$rrdres]} -s e-1h"'
+
+
+=head2 AT-STYLE TIME SPECIFICATION
+
+Apart from the traditional I<Seconds since epoch>, RRDtool does also
+understand at-style time specification. The specification is called
+"at-style" after the Unix command at(1) that has moderately complex
+ways to specify time to run your job at a certain date and time. The
+at-style specification consists of two parts: the B<TIME REFERENCE>
+specification and the B<TIME OFFSET> specification.
+
+=head2 TIME REFERENCE SPECIFICATION
+
+The time reference specification is used, well, to establish a reference
+moment in time (to which the time offset is then applied to). When present,
+it should come first, when omitted, it defaults to B<now>. On its own part,
+time reference consists of a I<time-of-day> reference (which should come
+first, if present) and a I<day> reference.
+
+The I<time-of-day> can be specified as B<HH:MM>, B<HH.MM>,
+or just B<HH>. You can suffix it with B<am> or B<pm> or use
+24-hours clock. Some special times of day are understood as well,
+including B<midnight> (00:00), B<noon> (12:00) and British
+B<teatime> (16:00).
+
+The I<day> can be specified as I<month-name> I<day-of-the-month> and
+optional a 2- or 4-digit I<year> number (e.g. March 8 1999). Alternatively,
+you can use I<day-of-week-name> (e.g. Monday), or one of the words:
+B<yesterday>, B<today>, B<tomorrow>. You can also specify the I<day> as a
+full date in several numerical formats, including B<MM/DD/[YY]YY>,
+B<DD.MM.[YY]YY>, or B<YYYYMMDD>.
+
+I<NOTE1>: this is different from the original at(1) behavior, where a
+single-number date is interpreted as MMDD[YY]YY.
+
+I<NOTE2>: if you specify the I<day> in this way, the I<time-of-day> is
+REQUIRED as well.
+
+Finally, you can use the words B<now>, B<start>, or B<end> as your time
+reference. B<Now> refers to the current moment (and is also the default
+time reference). B<Start> (B<end>) can be used to specify a time
+relative to the start (end) time for those tools that use these
+categories (B<rrdfetch>, L<rrdgraph>).
+
+Month and day of the week names can be used in their naturally
+abbreviated form (e.g., Dec for December, Sun for Sunday, etc.). The
+words B<now>, B<start>, B<end> can be abbreviated as B<n>, B<s>, B<e>.
+
+=head2 TIME OFFSET SPECIFICATION
+
+The time offset specification is used to add/subtract certain time
+intervals to/from the time reference moment. It consists of a I<sign>
+(S<B<+> or B<->>) and an I<amount>. The following time units can be
+used to specify the I<amount>: B<years>, B<months>, B<weeks>, B<days>,
+B<hours>, B<minutes>, or B<seconds>. These units can be used in
+singular or plural form, and abbreviated naturally or to a single
+letter (e.g. +3days, -1wk, -3y). Several time units can be combined
+(e.g., -5mon1w2d) or concatenated (e.g., -5h45min = -5h-45min =
+-6h+15min = -7h+1h30m-15min, etc.)
+
+I<NOTE3>: If you specify time offset in days, weeks, months, or years,
+you will end with the time offset that may vary depending on your time
+reference, because all those time units have no single well defined
+time interval value (S<1 year> contains either 365 or 366 days, S<1 month>
+is 28 to 31 days long, and even S<1 day> may be not equal to 24 hours
+twice a year, when DST-related clock adjustments take place).
+To cope with this, when you use days, weeks, months, or years
+as your time offset units your time reference date is adjusted
+accordingly without too much further effort to ensure anything
+about it (in the hope that mktime(3) will take care of this later).
+This may lead to some surprising (or even invalid!) results,
+e.g. S<'May 31 -1month'> = S<'Apr 31'> (meaningless) = S<'May 1'>
+(after mktime(3) normalization); in the EET timezone
+'3:30am Mar 29 1999 -1 day' yields '3:30am Mar 28 1999' (Sunday)
+which is an invalid time/date combination (because of 3am -> 4am DST
+forward clock adjustment, see the below example).
+
+In contrast, hours, minutes, and seconds are well defined time
+intervals, and these are guaranteed to always produce time offsets
+exactly as specified (e.g. for EET timezone, S<'8:00 Mar 27 1999 +2
+days'> = S<'8:00 Mar 29 1999'>, but since there is 1-hour DST forward
+clock adjustment that occurs around S<3:00 Mar 28 1999>, the actual
+time interval between S<8:00 Mar 27 1999> and S<8:00 Mar 29 1999>
+equals 47 hours; on the other hand, S<'8:00 Mar 27 1999 +48 hours'> =
+S<'9:00 Mar 29 1999'>, as expected)
+
+I<NOTE4>: The single-letter abbreviation for both B<months> and B<minutes>
+is B<m>. To disambiguate them, the parser tries to read your S<mind :)>
+by applying the following two heuristics:
+
+=over 3
+
+=item 1
+
+If B<m> is used in context of (i.e. right after the) years,
+months, weeks, or days it is assumed to mean B<months>, while
+in the context of hours, minutes, and seconds it means minutes.
+(e.g., in -1y6m or +3w1m B<m> is interpreted as B<months>, while in
+-3h20m or +5s2m B<m> the parser decides for B<minutes>).
+
+=item 2
+
+Out of context (i.e. right after the B<+> or B<-> sign) the
+meaning of B<m> is guessed from the number it directly follows.
+Currently, if the number's absolute value is below 25 it is assumed
+that B<m> means B<months>, otherwise it is treated as B<minutes>.
+(e.g., -25m == -25 minutes, while +24m == +24 months)
+
+=back
+
+I<Final NOTES>: Time specification is case-insensitive.
+Whitespace can be inserted freely or omitted altogether.
+There are, however, cases when whitespace is required
+(e.g., S<'midnight Thu'>). In this case you should either quote the
+whole phrase to prevent it from being taken apart by your shell or use
+'_' (underscore) or ',' (comma) which also count as whitespace
+(e.g., midnight_Thu or midnight,Thu).
+
+
+=head2 TIME SPECIFICATION EXAMPLES
+
+I<Oct 12> -- October 12 this year
+
+I<-1month> or I<-1m> -- current time of day, only a month before
+(may yield surprises, see NOTE3 above).
+
+I<noon yesterday -3hours> -- yesterday morning; can also be specified
+as I<9am-1day>.
+
+I<23:59 31.12.1999> -- 1 minute to the year 2000.
+
+I<12/31/99 11:59pm> -- 1 minute to the year 2000 for imperialists.
+
+I<12am 01/01/01> -- start of the new millennium
+
+I<end-3weeks> or I<e-3w> -- 3 weeks before end time
+(may be used as start time specification).
+
+I<start+6hours> or I<s+6h> -- 6 hours after start time
+(may be used as end time specification).
+
+I<931225537> -- 18:45 July 5th, 1999
+(yes, seconds since 1970 are valid as well).
+
+I<19970703 12:45> -- 12:45 July 3th, 1997
+(my favorite, and its even got an ISO number (8601)).
+
+=head1 AUTHOR
+
+Tobias Oetiker <tobi@oetiker.ch>
diff --git a/program/doc/rrdfirst.pod b/program/doc/rrdfirst.pod
--- /dev/null
+++ b/program/doc/rrdfirst.pod
@@ -0,0 +1,32 @@
+=head1 NAME
+
+rrdfirst - Return the date of the first data sample in an RRA within an RRD
+
+=head1 SYNOPSIS
+
+B<rrdtool> B<first> I<filename> [I<--rraindex number>]
+
+=head1 DESCRIPTION
+
+The B<first> function returns the UNIX timestamp of the first data
+sample entered into the specified RRA of the RRD file.
+
+=over 8
+
+=item I<filename>
+
+The name of the B<RRD> that contains the data.
+
+=item I<--rraindex number>
+
+The index number of the B<RRA> that is to be examined. If not specified, the
+index defaults to zero. B<RRA> index numbers can be determined through
+B<rrdtool info>.
+
+=back
+
+=head1 AUTHOR
+
+Burton Strauss <Burton@ntopSupport.com>
+
+
diff --git a/program/doc/rrdgraph-old.pod b/program/doc/rrdgraph-old.pod
--- /dev/null
@@ -0,0 +1,664 @@
+=head1 NAME
+
+rrdtool graph - Create a graph based on data from one or several RRD
+
+=for html <div align="right"><a href="rrdgraph.pdf">PDF</a> version.</div>
+
+=head1 SYNOPSIS
+
+B<rrdtool> B<graph> I<filename>
+S<[B<-s>|B<--start> I<seconds>]>
+S<[B<-e>|B<--end> I<seconds>]>
+S<[B<-x>|B<--x-grid> I<x-axis grid and label>]>
+S<[B<-y>|B<--y-grid> I<y-axis grid and label>]>
+S<[B<-Y>|B<--alt-y-grid>]>
+S<[B<-A>|B<--alt-autoscale>]>
+S<[B<-M>|B<--alt-autoscale-max>]>
+S<[B<-X>|B<--units-exponent>]> I<value>]>
+S<[B<-v>|B<--vertical-label> I<text>]>
+S<[B<-w>|B<--width> I<pixels>]>
+S<[B<-h>|B<--height> I<pixels>]>
+S<[B<-i>|B<--interlaced>]>
+S<[B<-f>|B<--imginfo> I<formatstring>]>
+S<[B<-a>|B<--imgformat> B<SVG>|B<PNG>]>
+S<[B<-z>|B<--lazy>]>
+S<[B<-o>|B<--logarithmic>]>
+S<[B<-u>|B<--upper-limit> I<value>]>
+S<[B<-l>|B<--lower-limit> I<value>]>
+S<[B<-g>|B<--no-legend>]>
+S<[B<-r>|B<--rigid>]>
+S<[B<-S>|B<--step> I<value>]>
+S<[B<-b>|B<--base> I<value>]>
+S<[B<-c>|B<--color> I<COLORTAG>B<#>I<rrggbb>]>
+S<[B<-t>|B<--title> I<title>]>
+S<[B<DEF:>I<vname>B<=>I<rrd>B<:>I<ds-name>B<:>I<CF>]>
+S<[B<CDEF:>I<vname>B<=>I<rpn-expression>]>
+S<[B<PRINT:>I<vname>B<:>I<CF>B<:>I<format>]>
+S<[B<GPRINT:>I<vname>B<:>I<CF>B<:>I<format>]>
+S<[B<COMMENT:>I<text>]>
+S<[B<HRULE:>I<value>B<#>I<rrggbb>[B<:>I<legend>]]>
+S<[B<VRULE:>I<time>B<#>I<rrggbb>[B<:>I<legend>]]>
+S<[B<LINE>{B<1>|B<2>|B<3>}B<:>I<vname>[B<#>I<rrggbb>[B<:>I<legend>]]]>
+S<[B<AREA:>I<vname>[B<#>I<rrggbb>[B<:>I<legend>]]]>
+S<[B<STACK:>I<vname>[B<#>I<rrggbb>[B<:>I<legend>]]]>
+S<[B<TICK:>I<vname>B<#>I<rrggbb>[B<:>I<axis-fraction>[B<:>I<legend>]]]>
+
+=head1 DESCRIPTION
+
+The B<graph> functions main purpose is to create graphical
+representations of the data stored in one or several B<RRD>s. Apart
+from generating graphs, it can also extract numerical reports.
+
+=over
+
+=item I<filename>
+
+The name of the graph to generate. Since B<RRDtool> outputs
+SVGs and PNGs, it's recommended that the filename end in either
+F<.svg> or F<.png>. B<RRDtool> does not enforce this, however.
+If the I<filename> is set to '-' the image file will be written
+to standard out. All other output will get suppressed.
+
+If no graph functions are called, the graph will not be created.
+
+=item B<-s>|B<--start> I<seconds> (default end-1day)
+
+The time when the graph should begin. Time in seconds since
+epoch (1970-01-01) is required. Negative numbers are relative to the
+current time. By default one day worth of data will be graphed.
+See also AT-STYLE TIME SPECIFICATION section in the I<rrdfetch>
+documentation for a detailed explanation on how to specify time.
+
+=item B<-e>|B<--end> I<seconds> (default now)
+
+The time when the graph should end. Time in seconds since epoch.
+See also AT-STYLE TIME SPECIFICATION section in the I<rrdfetch>
+documentation for a detailed explanation of ways to specify time.
+
+=item B<-x>|B<--x-grid> I<x-axis grid and label> (default autoconfigure)
+
+The x-axis label is quite complex to configure. So if you don't have
+very special needs, you can rely on the autoconfiguration to get this
+right.
+
+If you want no x-grid at all, use the magic setting B<none>.
+
+The x-axis label and grid can be configured, using the following format:
+
+I<GTM>B<:>I<GST>B<:>I<MTM>B<:>I<MST>B<:>I<LTM>:I<LST>B<:>I<LPR>B<:>I<LFM>
+
+You have to configure three elements making up the x-axis labels and
+grid. The base grid (I<G??>), the major grid (I<M??>) and the labels
+(I<L??>). The configuration is based on the idea that you first
+specify a well known amount of time (I<?TM>) and then say how many
+times it has to pass between each grid line or label (I<?ST>). For the
+label you have to define two additional items: The precision of the
+label in seconds (I<LPR>) and the strftime format used to generate the
+text of the label (I<LFM>).
+
+The I<?TM> elements must be one of the following keywords: B<SECOND>,
+B<MINUTE>, B<HOUR>, B<DAY>, B<WEEK>, B<MONTH> or B<YEAR>.
+
+If you wanted a graph with a base grid every 10 minutes and a major
+one every hour, with labels every hour you would use the following
+x-axis definition.
+
+C<MINUTE:10:HOUR:1:HOUR:1:0:%X>
+
+The precision in this example is 0 because the %X format is exact. If
+the label was the name of the day, we would have had a precision of 24
+hours, because when you say something like 'Monday' you mean the whole
+day and not Monday morning 00:00. Thus the label should be positioned
+at noon. By defining a precision of 24 hours or rather 86400 seconds,
+you make sure that this happens.
+
+=item B<-y>|B<--y-grid> I<grid step>:I<label factor> (default autoconfigure)
+
+Makes vertical grid lines appear at I<grid step> interval. Every
+I<label factor> gridstep, a major grid line is printed, along with
+label showing the value of the grid line.
+
+If you want no y-grid at all set specify the magic word B<none>.
+
+=item B<--alt-y-grid>
+
+Place Y grid dynamically based on graph Y range. Algorithm ensures
+that you always have grid, that there are enough but not too many
+grid lines and the grid is metric. That is grid lines are placed
+every 1, 2, 5 or 10 units. (contributed by Sasha Mikheev)
+
+
+=item B<--alt-autoscale>
+
+Compute Y range based on function absolute minimum and
+maximum values. Default algorithm uses predefined set of ranges.
+This is good in many cases but it fails miserably when you need
+to graph something like 260 + 0.001 * sin(x). Default algorithm
+will use Y range from 250 to 300 and on the graph you will see
+almost straight line. With --alt-autoscale Y range will be
+from slightly less the 260 - 0.001 to slightly more then 260 + 0.001
+and periodic behavior will be seen. (contributed by Sasha Mikheev)
+
+=item B<--alt-autoscale-max>
+
+Where --alt-autoscale will modify both the absolute maximum AND minimum
+values, this option will only affect the maximum value. The minimum
+value, if not defined on the command line, will be 0. This option can
+be useful when graphing router traffic when the WAN line uses compression,
+and thus the throughput may be higher than the WAN line speed.
+
+=item B<--units-exponent> I<value> (default autoconfigure)
+
+This sets the 10**exponent scaling of the y-axis values. Normally
+values will be scaled to the appropriate units (k, M, etc.). However
+you may wish to display units always in k (Kilo, 10e3) even if the data
+is in the M (Mega, 10e6) range for instance. Value should be an
+integer which is a multiple of 3 between -18 and 18 inclusive. It is
+the exponent on the units you which to use. For example, use 3 to
+display the y-axis values in k (Kilo, 10e3, thousands), use -6 to
+display the y-axis values in u (Micro, 10e-6, millionths). Use a value
+of 0 to prevent any scaling of the y-axis values.
+
+=item B<-v>|B<--vertical-label> I<text>
+
+vertical label on the left side of the graph. This is normally used to
+specify the units used.
+
+=item B<-w>|B<--width> I<pixels> (default 400 pixel)
+
+Width of the drawing area within the graph. This affects the size of the
+image.
+
+=item B<-h>|B<--height> I<pixels> (default 100 pixel)
+
+Width of the drawing area within the graph. This affects the size of the
+image.
+
+=item B<-i>|B<--interlaced> (default: false)
+
+If you set this option, then the resulting image will be interlaced.
+Most web browsers display these incrementally as they load. If
+you do not use this option, the image defaults to being progressive
+scanned. The only effect of this option is to control the format
+of the image on disk. It makes no changes to the layout or contents
+of the graph.
+
+=item B<-f>|B<--imginfo> I<formatstring>
+
+After the image has been created, the graph function uses printf
+together with this format string to create output similar to the PRINT
+function, only that the printf is supplied with the parameters
+I<filename>, I<xsize> and I<ysize>. In order to generate an B<IMG> tag
+suitable for including the graph into a web page, the command line
+would look like this:
+
+ --imginfo '<IMG SRC="/img/%s" WIDTH="%lu" HEIGHT="%lu" ALT="Demo">'
+
+=item B<-a>|B<--imgformat> B<SVG>|B<PNG> (default: PNG)
+
+Allows you to produce PNG output from RRDtool.
+
+=item B<-z>|B<--lazy> (default: false)
+
+Only generate the graph, if the current image is out of date or not
+existent.
+
+=item B<-u>|B<--upper-limit> I<value> (default autoconfigure)
+
+Defines the value normally located at the upper border of the
+graph. If the graph contains higher values, the upper border will
+move upward to accommodate these values as well.
+
+If you want to define an upper-limit which will not move in any
+event you have to set the B<--rigid> option as well.
+
+=item B<-l>|B<--lower-limit> I<value> (default autoconfigure)
+
+This is not the lower limit of a graph. But rather, this is the
+maximum lower bound of a graph. For example, the value -100 will
+result in a graph that has a lower limit of -100 or less. Use this
+keyword to expand graphs down.
+
+=item B<-r>|B<--rigid>
+
+rigid boundaries mode. Normally rrdgraph will automatically expand the
+lower and upper limit if the graph contains a value outside the valid
+range. With the r option you can disable this behavior
+
+=item B<-b>|B<--base> I<value>
+
+if you are graphing memory (and NOT network traffic) this switch
+should be set to 1024 so that one Kb is 1024 byte. For traffic
+measurement, 1 kb/s is 1000 b/s.
+
+=item B<-o>|B<--logarithmic>
+
+logarithmic y-axis scaling
+
+=item B<-c>|B<--color> I<COLORTAG>B<#>I<rrggbb> (default colors)
+
+override the colors for the standard elements of the graph. The I<COLORTAG>
+must be one of the following symbolic names: B<BACK> ground, B<CANVAS>,
+B<SHADEA> left/top border, B<SHADEB> right/bottom border, B<GRID>, B<MGRID>
+major grid, B<FONT>, B<FRAME> and axis of the graph or B<ARROW>. This option
+can be called multiple times to set several colors.
+
+=item B<-g>|B<--no-legend>
+
+Suppress generation of legend; only render the graph.
+
+=item B<-t>|B<--title> I<text> (default no title)
+
+Define a title to be written into the graph
+
+=item B<--step> I<value> (default automatic)
+
+By default rrdgraph calculates the width of one pixel in the time domain and
+tries to get data at that resolution from the RRD. With this switch you can
+override this behaviour. If you want rrdgraph to get data at 1 hour
+resolution from the RRD, then you can set the step to 3600 seconds. Note,
+that a step smaller than 1 pixel will be silently ignored.
+
+=item B<DEF:>I<vname>B<=>I<rrd>B<:>I<ds-name>B<:>I<CF>
+
+Define virtual name for a data source. This name can then be used
+in the functions explained below. The
+DEF call automatically chooses an B<RRA> which contains I<CF> consolidated data in a
+resolution appropriate for the size of the graph to be drawn. Ideally
+this means that one data point from the B<RRA> should be represented
+by one pixel in the graph. If the resolution of the B<RRA> is higher
+than the resolution of the graph, the data in the RRA will be further
+consolidated according to the consolidation function (I<CF>) chosen.
+
+=item B<CDEF:>I<vname>B<=>I<rpn-expression>
+
+Create a new virtual data source by evaluating a mathematical expression,
+specified in Reverse Polish Notation (RPN). If you have ever used a traditional
+HP calculator you already know RPN. The idea behind RPN notation is,
+that you have a stack and push your data onto this stack. When ever
+you execute an operation, it takes as many data values from the stack
+as needed. The pushing of data is implicit, so when ever you specify a number
+or a variable, it gets pushed automatically.
+
+If this is all a big load of incomprehensible words for you, maybe an
+example helps (a more complete explanation is given in [1]): The
+expression I<vname+3/2> becomes C<vname,3,2,/,+> in RPN. First the three
+values get pushed onto the stack (which now contains (the current
+value of) vname, a 3 and a 2). Then the / operator pops two values
+from the stack (3 and 2), divides the first argument by the second
+(3/2) and pushes the result (1.5) back onto the stack. Then the +
+operator pops two values (vname and 1.5) from the stack; both values
+are added up and the result gets pushes back onto the stack. In the
+end there is only one value left on the stack: The result of the
+expression.
+
+The I<rpn-expression> in the B<CDEF> function takes both, constant values
+as well as I<vname> variables. The following operators can be used on these
+values:
+
+=over
+
+=item +, -, *, /, %
+
+pops two values from the stack applies the selected operator and pushes
+the result back onto the stack. The % operator stands for the modulo
+operation.
+
+=item SIN, COS, LOG, EXP, FLOOR, CEIL
+
+pops one value from the stack, applies the selected function and pushes
+the result back onto the stack.
+
+=item LT, LE, GT, GE, EQ
+
+pops two values from the stack, compares them according to the selected
+condition and pushes either 1 back onto the stack if the condition is true
+and 0 if the condition was not true.
+
+=item IF
+
+pops three values from the stack. If the last value is not 0, the
+second value will be pushed back onto the stack, otherwise the
+first value is pushed back.
+
+If the stack contains the values A, B, C, D, E are presently on the
+stack, the IF operator will pop the values E D and C of the stack. It will
+look at C and if it is not 0 it will push D back onto the stack, otherwise
+E will be sent back to the stack.
+
+=item MIN, MAX
+
+selects the lesser or larger of the two top stack values respectively
+
+=item LIMIT
+
+replaces the value with I<*UNKNOWN*> if it is outside the limits specified
+by the two values above it on the stack.
+
+ CDEF:a=alpha,0,100,LIMIT
+
+=item DUP, EXC, POP
+
+These manipulate the stack directly. DUP will duplicate the top of the
+stack, pushing the result back onto the stack. EXC will exchange the top
+two elements of the stack, and POP will pop off the top element of the
+stack. Having insufficient elements on the stack for these operations is
+an error.
+
+=item UN
+
+Pops one value off the stack, if it is I<*UNKNOWN*>, 1 will be pushed
+back otherwise 0.
+
+=item UNKN
+
+Push an I<*UNKNOWN*> value onto the stack.
+
+=item PREV
+
+Push I<*UNKNOWN*> if its at the first value of a data set or otherwise
+the value of this CDEF at the previous time step. This allows you to
+perform calculations across the data.
+
+=item COUNT
+
+Pushes the number 1 if it is at the first value of the data set, the
+number 2 if it is at the second, and so on. This special value, allows
+you to make calculations based on the position of the value within
+the data set.
+
+=item INF, NEGINF
+
+Push a positive or negative infinite (oo) value onto the stack. When
+drawing an infinite number it appears right at the top or bottom edge of the
+graph, depending whether you have a positive or negative infinite number.
+
+=item NOW
+
+Push the current (real world) time onto the stack.
+
+=item TIME
+
+Push the time the current sample was taken onto the stack. This is the
+number of non-skip seconds since 0:00:00 January 1, 1970.
+
+=item LTIME
+
+This is like TIME B<+ current timezone offset in seconds>. The current
+offset takes daylight saving time into account, given your OS supports
+this. If you were looking at a sample, in Zurich, in summer, the
+offset would be 2*3600 seconds, as Zurich at that time of year is 2
+hours ahead of UTC.
+
+Note that the timezone offset is always calculated for the time the
+current sample was taken at. It has nothing to do with the time you are
+doing the calculation.
+
+=back
+
+Please note that you may only use I<vname> variables that you
+previously defined by either B<DEF> or B<CDEF>. Furthermore, as of
+this writing (version 0.99.25), you must use at least one I<vname>
+per expression, that is "CDEF:fourtytwo=2,40,+" will yield an error
+message but not a I<vname> fourtytwo that's always equal to 42.
+
+=item B<PRINT:>I<vname>B<:>I<CF>B<:>I<format>
+
+Calculate the chosen consolidation function I<CF> over the data-source
+variable I<vname> and C<printf> the result to stdout using I<format>.
+In the I<format> string there should be a '%lf', '%le' or'%lg' marker in the
+place where the number should be printed.
+
+If an additional '%s' is found AFTER the marker, the value will be scaled
+and an appropriate SI magnitude unit will be printed in place of the '%s'
+marker. The scaling will take the '--base' argument into consideration!
+
+If a '%S' is used instead of a '%s', then instead of calculating the
+appropriate SI magnitude unit for this value, the previously calculated
+SI magnitude unit will be used. This is useful if you want all the values
+in a PRINT statement to have the same SI magnitude unit. If there was
+no previous SI magnitude calculation made, then '%S' behaves like a '%s',
+unless the value is 0, in which case it does not remember a SI magnitude
+unit and a SI magnitude unit will only be calculated when the next '%s' is
+seen or the next '%S' for a non-zero value.
+
+If you want to put a '%' into your PRINT string, use '%%' instead.
+
+=item B<GPRINT:>I<vname>B<:>I<CF>B<:>I<format>
+
+Same as B<PRINT> but the result is printed into the graph below the legend.
+
+=back
+
+B<Caveat:> When using the B<PRINT> and B<GRPRINT> functions to
+calculate data summaries over time periods bounded by the current
+time, it is important to note that the last sample will almost always
+yield a value of UNKNOWN as it lies after the last update time. This
+can result in slight data skewing, particularly with the B<AVERAGE>
+function. In order to avoid this, make sure that your end time is at
+least one heartbeat prior to the current time.
+
+=over
+
+
+=item B<COMMENT:>I<text>
+
+Like B<GPRINT> but the I<text> is simply printed into the graph.
+
+=item B<HRULE:>I<value>B<#>I<rrggbb>[B<:>I<legend>]
+
+Draw a horizontal rule into the graph and optionally add a legend
+
+=item B<VRULE:>I<time>B<#>I<rrggbb>[B<:>I<legend>]
+
+Draw a vertical rule into the graph and optionally add a legend
+
+=item B<LINE>{B<1>|B<2>|B<3>}B<:>I<vname>[B<#>I<rrggbb>[B<:>I<legend>]]
+
+Plot for the requested data, using the color specified. Write a legend
+into the graph. The 3 possible keywords B<LINE1>, B<LINE2>, and B<LINE3>
+generate increasingly wide lines. If no color is defined,
+the drawing is done 'blind' this is useful in connection with the
+B<STACK> function when you want to ADD the values of two
+data-sources without showing it in the graph.
+
+=item B<AREA>:I<vname>[B<#>I<rrggbb>[B<:>I<legend>]]
+
+Does the same as B<LINE?>, but the area between 0 and
+the graph will be filled with the color specified.
+
+=item B<STACK>:I<vname>[B<#>I<rrggbb>[B<:>I<legend>]]
+
+Does the same as B<LINE?>, but the graph gets stacked on top of the previous
+B<LINE?>, B<AREA> or B<STACK> graph. Depending on the type of the
+previous graph, the B<STACK> will be either a B<LINE?> or an B<AREA>.
+This obviously implies that the first B<STACK> must be preceded by an
+B<AREA> or B<LINE?> -- you need something to stack something onto in
+the first place ;)
+
+Note, that when you STACK onto *UNKNOWN* data, RRDtool will not draw
+any graphics ... *UNKNOWN* is not zero ... if you want it to be zero
+then you might want to use a CDEF argument with IF and UN functions to
+turn *UNKNOWN* into zero ...
+
+=item B<TICK:>I<vname>B<#>I<rrggbb>[B<:>I<axis-fraction>[B<:>I<legend>]]
+
+Plot a tick mark (a vertical line) for each value of I<vname> that is
+non-zero and not *UNKNOWN*. The I<axis-fraction> argument specifies the
+length of the tick mark as a fraction of the y-axis; the default value
+is 0.1 (10% of the axis). Note that the color specification is not
+optional.
+
+=back
+
+=head1 NOTES on legend arguments
+
+=head2 Escaping the colon
+
+In a ':' in a I<legend> argument will mark the end of the legend. To
+enter a ':' into a legend, the colon must be escaped with a backslash '\:'.
+Beware, that many environments look for backslashes themselves, so it may
+be necessary to write two backslashes so that one is passed onto rrd_graph.
+
+=head2 String Formatting
+
+The text printed below the actual graph can be formated by appending special
+escaped characters at the end of a text. When ever such a character occurs,
+all pending text is pushed onto the graph according to the character
+specified.
+
+Valid markers are: B<\j> for justified, B<\l> for left aligned, B<\r> for
+right aligned and B<\c> for centered. In the next section there is an
+example showing how to use centered formating.
+
+Normally there are two space characters inserted between every two items
+printed into the graph. The space following a string can be suppressed by
+putting a B<\g> at the end of the string. The B<\g> also ignores any space
+inside the string if it is at the very end of the string. This can be used
+in connection with B<%s> to suppress empty unit strings.
+
+ GPRINT:a:MAX:%lf%s\g
+
+A special case is COMMENT:B<\s> this inserts some additional vertical space
+before placing the next row of legends.
+
+=head1 NOTE on Return Values
+
+Whenever rrd_graph gets called, it prints a line telling the size of
+the image it has just created to stdout. This line looks like this: XSIZExYSIZE.
+
+=head1 EXAMPLE 1
+
+ rrdtool graph demo.png --title="Demo Graph" \
+ DEF:cel=demo.rrd:exhaust:AVERAGE \
+ "CDEF:far=cel,1.8,*,32,+"" \
+ LINE2:cel#00a000:"D. Celsius" \
+ LINE2:far#ff0000:"D. Fahrenheit\c"
+
+=head1 EXAMPLE 2
+
+This example demonstrates the syntax for using IF and UN to set
+I<*UNKNOWN*> values to 0. This technique is useful if you are
+aggregating interface data where the start dates of the data sets
+doesn't match.
+
+ rrdtool graph demo.png --title="Demo Graph" \
+ DEF:idat1=interface1.rrd:ds0:AVERAGE \
+ DEF:idat2=interface2.rrd:ds0:AVERAGE \
+ DEF:odat1=interface1.rrd:ds1:AVERAGE \
+ DEF:odat2=interface2.rrd:ds1:AVERAGE \
+ CDEF:agginput=idat1,UN,0,idat1,IF,idat2,UN,0,idat2,IF,+,8,* \
+ CDEF:aggoutput=odat1,UN,0,odat1,IF,odat2,UN,0,odat2,IF,+,8,* \
+ AREA:agginput#00cc00:Input Aggregate \
+ LINE1:agginput#0000FF:Output Aggregate
+
+Assuming that idat1 has a data value of I<*UNKNOWN*>, the CDEF expression
+
+ idat1,UN,0,idat1,IF
+
+leaves us with a stack with contents of 1,0,NaN and the IF function
+will pop off the 3 values and replace them with 0. If idat1 had a
+real value like 7942099, then the stack would have 0,0,7942099 and the
+real value would be the replacement.
+
+=head1 EXAMPLE 3
+
+This example shows two ways to use the INF function. First it makes
+the background change color during half of the hours. Then, it uses
+AREA and STACK to draw a picture. If one of the inputs was UNKNOWN,
+all inputs are overlaid with another AREA.
+
+ rrdtool graph example.png --title="INF demo" \
+ DEF:val1=some.rrd:ds0:AVERAGE \
+ DEF:val2=some.rrd:ds1:AVERAGE \
+ DEF:val3=some.rrd:ds2:AVERAGE \
+ DEF:val4=other.rrd:ds0:AVERAGE \
+ CDEF:background=val4,POP,TIME,7200,%,3600,LE,INF,UNKN,IF \
+ CDEF:wipeout=val1,val2,val3,val4,+,+,+,UN,INF,UNKN,IF \
+ AREA:background#F0F0F0 \
+ AREA:val1#0000FF:Value1 \
+ STACK:val2#00C000:Value2 \
+ STACK:val3#FFFF00:Value3 \
+ STACK:val4#FFC000:Value4 \
+ AREA:wipeout#FF0000:Unknown
+
+The first CDEF uses val4 as a dummy value. It's value is removed immediately
+from the stack. Then a decision is made based on the time that a sample was
+taken. If it is an even hour (UTC time !) then the area will be filled. If
+it is not, the value is set to UNKN and is not plotted.
+
+The second CDEF looks if any of val1,val2,val3,val4 is unknown. It does so by
+checking the outcome of sum(val1,val2,val3,val4). Again, INF is returned when
+the condition is true, UNKN is used to not plot the data.
+
+The different items are plotted in a particular order. First do the background, then use a
+normal area to overlay it with data. Stack the other data until they are all plotted. Last but
+not least, overlay everything with eye-hurting red
+to signal any unknown data.
+
+Note that this example assumes that your data is in the positive half of the y-axis
+otherwise you would would have to add NEGINF in order to extend the coverage
+of the area to whole graph.
+
+=head1 EXAMPLE 4
+
+If the specialized function B<RRAs> exist for aberrant behavior detection, they
+can be used to generate the graph of a time series with confidence bands and
+failures.
+
+ rrdtool graph example.png \
+ DEF:obs=monitor.rrd:ifOutOctets:AVERAGE \
+ DEF:pred=monitor.rrd:ifOutOctets:HWPREDICT \
+ DEF:dev=monitor.rrd:ifOutOctets:DEVPREDICT \
+ DEF:fail=monitor.rrd:ifOutOctets:FAILURES \
+ TICK:fail#ffffa0:1.0:"Failures\: Average bits out" \
+ CDEF:scaledobs=obs,8,* \
+ CDEF:upper=pred,dev,2,*,+ \
+ CDEF:lower=pred,dev,2,*,- \
+ CDEF:scaledupper=upper,8,* \
+ CDEF:scaledlower=lower,8,* \
+ LINE2:scaledobs#0000ff:"Average bits out" \
+ LINE1:scaledupper#ff0000:"Upper Confidence Bound: Average bits out" \
+ LINE1:scaledlower#ff0000:"Lower Confidence Bound: Average bits out"
+
+This example generates a graph of the data series in blue (LINE2 with the scaledobs
+virtual data source), confidence bounds in red (scaledupper and scaledlower virtual
+data sources), and potential failures (i.e. potential aberrant aberrant behavior)
+marked by vertical yellow lines (the fail data source).
+
+The raw data comes from an AVERAGE B<RRA>, the finest resolution of the observed
+time series (one consolidated data point per primary data point). The predicted
+(or smoothed) values are stored in the HWPREDICT B<RRA>. The predicted deviations
+(think standard deviation) values are stored in the DEVPREDICT B<RRA>. Finally,
+the FAILURES B<RRA> contains indicators, with 1 denoting a potential failure.
+
+All of the data is rescaled to bits (instead of Octets) by multiplying by 8.
+The confidence bounds are computed by an offset of 2 deviations both above
+and below the predicted values (the CDEFs upper and lower). Vertical lines
+indicated potential failures are graphed via the TICK graph element, which
+converts non-zero values in an B<RRA> into tick marks. Here an axis-fraction
+argument of 1.0 means the tick marks span the entire y-axis, and hence become
+vertical lines on the graph.
+
+The choice of 2 deviations (a scaling factor) matches the default used internally
+by the FAILURES B<RRA>. If the internal value is changed (see L<rrdtune>), this
+graphing command should be changed to be consistent.
+
+=head2 A note on data reduction:
+
+The B<rrdtool> I<graph> command is designed to plot data at a specified temporal
+resolution, regardless of the actually resolution of the data in the RRD file.
+This can present a problem for the specialized consolidation functions which
+maintain a one-to-one mapping between primary data points and consolidated
+data points. If a graph insists on viewing the contents of these B<RRAs> on a
+coarser temporal scale, the I<graph> command tries to do something intelligent,
+but the confidence bands and failures no longer have the same meaning and may
+be misleading.
+
+=head1 AUTHOR
+
+Tobias Oetiker E<lt>tobi@oetiker.chE<gt>
+
+=head1 REFERENCES
+
+[1] http://www.dotpoint.com/xnumber/rpn_or_adl.htm
diff --git a/program/doc/rrdgraph.pod b/program/doc/rrdgraph.pod
--- /dev/null
+++ b/program/doc/rrdgraph.pod
@@ -0,0 +1,485 @@
+=head1 NAME
+
+rrdgraph - Round Robin Database tool grapher functions
+
+=head1 SYNOPSIS
+
+B<rrdtool graph|graphv> I<filename>
+[I<L<option|rrdgraph/OPTIONS>> ...]
+[I<L<data definition|rrdgraph_data/DEF>> ...]
+[I<L<data calculation|rrdgraph_data/CDEF>> ...]
+[I<L<variable definition|rrdgraph_data/VDEF>> ...]
+[I<L<graph element|rrdgraph_graph/GRAPH>> ...]
+[I<L<print element|rrdgraph_graph/PRINT>> ...]
+
+=head1 DESCRIPTION
+
+The B<graph> function of B<RRDtool> is used to present the
+data from an B<RRD> to a human viewer. Its main purpose is to
+create a nice graphical representation, but it can also generate
+a numerical report.
+
+=head1 OVERVIEW
+
+B<rrdtool graph> needs data to work with, so you must use one or more
+B<L<data definition|rrdgraph_data/DEF>> statements to collect this
+data. You are not limited to one database, it's perfectly legal to
+collect data from two or more databases (one per statement, though).
+
+If you want to display averages, maxima, percentiles, etcetera
+it is best to collect them now using the
+B<L<variable definition|rrdgraph_data/VDEF>> statement.
+Currently this makes no difference, but in a future version
+of rrdtool you may want to collect these values before consolidation.
+
+The data fetched from the B<RRA> is then B<consolidated> so that
+there is exactly one datapoint per pixel in the graph. If you do
+not take care yourself, B<RRDtool> will expand the range slightly
+if necessary. Note, in that case the first and/or last pixel may very
+well become unknown!
+
+Sometimes data is not exactly in the format you would like to display
+it. For instance, you might be collecting B<bytes> per second, but
+want to display B<bits> per second. This is what the B<L<data
+calculation|rrdgraph_data/CDEF>> command is designed for. After
+B<consolidating> the data, a copy is made and this copy is modified
+using a rather powerful B<L<RPN|rrdgraph_rpn>> command set.
+
+When you are done fetching and processing the data, it is time to
+graph it (or print it). This ends the B<rrdtool graph> sequence.
+
+=head1 OPTIONS
+
+
+=head2 B<graphv>
+
+This alternate version of B<graph> takes the same arguments and performs the
+same function. The I<v> stands for I<verbose>, which describes the output
+returned. B<graphv> will return a lot of information about the graph using
+the same format as rrdtool info (key = value). See the bottom of the document for more information.
+
+
+=head2 I<filename>
+
+The name and path of the graph to generate. It is recommended to
+end this in C<.png>, C<.svg> or C<.eps>, but B<RRDtool> does not enforce this.
+
+I<filename> can be 'C<->' to send the image to C<stdout>. In
+this case, no other output is generated.
+
+=head2 Time range
+
+[B<-s>|B<--start> I<time>]
+[B<-e>|B<--end> I<time>]
+[B<-S>|B<--step> I<seconds>]
+
+The start and end of the time series you would like to display, and which
+B<RRA> the data should come from. Defaults are: 1 day ago until
+now, with the best possible resolution. B<Start> and B<end> can
+be specified in several formats, see
+L<AT-STYLE TIME SPECIFICATION|rrdfetch> and L<rrdgraph_examples>.
+By default, B<rrdtool graph> calculates the width of one pixel in
+the time domain and tries to get data from an B<RRA> with that
+resolution. With the B<step> option you can alter this behaviour.
+If you want B<rrdtool graph> to get data at a one-hour resolution
+from the B<RRD>, set B<step> to 3'600. Note: a step smaller than
+one pixel will silently be ignored.
+
+=head2 Labels
+
+[B<-t>|B<--title> I<string>]
+[B<-v>|B<--vertical-label> I<string>]
+
+A horizontal string at the top of the graph and/or a vertically
+placed string at the left hand side of the graph.
+
+=head2 Size
+
+[B<-w>|B<--width> I<pixels>]
+[B<-h>|B<--height> I<pixels>]
+[B<-j>|B<--only-graph>]
+[B<-D>|B<--full-size-mode>]
+
+By default, the width and height of the B<canvas> (the part with
+the actual data and such). This defaults to 400 pixels by 100 pixels.
+
+If you specify the B<--full-size-mode> option, the width and height
+specify the final dimensions of the output image and the canvas
+is automatically resized to fit.
+
+If you specify the B<--only-graph> option and set the height E<lt> 32
+pixels you will get a tiny graph image (thumbnail) to use as an icon
+for use in an overview, for example. All labeling will be stripped off
+the graph.
+
+=head2 Limits
+
+[B<-u>|B<--upper-limit> I<value>]
+[B<-l>|B<--lower-limit> I<value>]
+[B<-r>|B<--rigid>]
+
+By default the graph will be autoscaling so that it will adjust the
+y-axis to the range of the data. You can change this behaviour by
+explicitly setting the limits. The displayed y-axis will then range at
+least from B<lower-limit> to B<upper-limit>. Autoscaling will still
+permit those boundaries to be stretched unless the B<rigid> option is
+set.
+
+[B<-A>|B<--alt-autoscale>]
+
+Sometimes the default algorithm for selecting the y-axis scale is not
+satisfactory. Normally the scale is selected from a predefined
+set of ranges and this fails miserably when you need to graph something
+like C<260 + 0.001 * sin(x)>. This option calculates the minimum and
+maximum y-axis from the actual minimum and maximum data values. Our example
+would display slightly less than C<260-0.001> to slightly more than
+C<260+0.001> (this feature was contributed by Sasha Mikheev).
+
+[B<-J>|B<--alt-autoscale-min>]
+
+Where C<--alt-autoscale> will modify both the absolute maximum AND minimum
+values, this option will only affect the minimum value. The maximum
+value, if not defined on the command line, will be 0. This option can
+be useful when graphing router traffic when the WAN line uses compression,
+and thus the throughput may be higher than the WAN line speed.
+
+[B<-M>|B<--alt-autoscale-max>]
+
+Where C<--alt-autoscale> will modify both the absolute maximum AND minimum
+values, this option will only affect the maximum value. The minimum
+value, if not defined on the command line, will be 0. This option can
+be useful when graphing router traffic when the WAN line uses compression,
+and thus the throughput may be higher than the WAN line speed.
+
+[B<-N>|B<--no-gridfit>]
+
+In order to avoid anti-aliasing blurring effects rrdtool snaps
+points to device resolution pixels, this results in a crisper
+aperance. If this is not to your liking, you can use this switch
+to turn this behaviour off.
+
+Gridfitting is turned off for PDF, EPS, SVG output by default.
+
+=head2 Grid
+
+=over
+
+=item X-Axis
+
+[B<-x>|B<--x-grid> I<GTM>B<:>I<GST>B<:>I<MTM>B<:>I<MST>B<:>I<LTM>B<:>I<LST>B<:>I<LPR>B<:>I<LFM>]
+
+[B<-x>|B<--x-grid> B<none>]
+
+The x-axis label is quite complex to configure. If you don't have
+very special needs it is probably best to rely on the autoconfiguration
+to get this right. You can specify the string C<none> to suppress the grid
+and labels altogether.
+
+The grid is defined by specifying a certain amount of time in the I<?TM>
+positions. You can choose from C<SECOND>, C<MINUTE>, C<HOUR>, C<DAY>,
+C<WEEK>, C<MONTH> or C<YEAR>. Then you define how many of these should
+pass between each line or label. This pair (I<?TM:?ST>) needs to be
+specified for the base grid (I<G??>), the major grid (I<M??>) and the
+labels (I<L??>). For the labels you also must define a precision
+in I<LPR> and a I<strftime> format string in I<LFM>. I<LPR> defines
+where each label will be placed. If it is zero, the label will be
+placed right under the corresponding line (useful for hours, dates
+etcetera). If you specify a number of seconds here the label is
+centered on this interval (useful for Monday, January etcetera).
+
+ --x-grid MINUTE:10:HOUR:1:HOUR:4:0:%X
+
+This places grid lines every 10 minutes, major grid lines every hour,
+and labels every 4 hours. The labels are placed under the major grid
+lines as they specify exactly that time.
+
+ --x-grid HOUR:8:DAY:1:DAY:1:86400:%A
+
+This places grid lines every 8 hours, major grid lines and labels
+each day. The labels are placed exactly between two major grid lines
+as they specify the complete day and not just midnight.
+
+=item Y-Axis
+
+[B<-y>|B<--y-grid> I<grid step>B<:>I<label factor>]
+
+[B<-y>|B<--y-grid> B<none>]
+
+Y-axis grid lines appear at each I<grid step> interval. Labels are
+placed every I<label factor> lines. You can specify C<-y none> to
+suppress the grid and labels altogether. The default for this option is
+to automatically select sensible values.
+
+If you have set --y-grid to 'none' not only the labels get supressed, also
+the space reserved for the labels is removed. You can still add space
+manually if you use the --units-length command to explicitly reserve space.
+
+[B<-Y>|B<--alt-y-grid>]
+
+Place the Y grid dynamically based on the graph's Y range. The algorithm
+ensures that you always have a grid, that there are enough but not too many
+grid lines, and that the grid is metric. That is the grid lines are placed
+every 1, 2, 5 or 10 units. This parameter will also ensure that you get
+enough decimals displayed even if your graph goes from 69.998 to 70.001.
+(contributed by Sasha Mikheev).
+
+[B<-o>|B<--logarithmic>]
+
+Logarithmic y-axis scaling.
+
+[B<-X>|B<--units-exponent> I<value>]
+
+This sets the 10**exponent scaling of the y-axis values. Normally,
+values will be scaled to the appropriate units (k, M, etc.). However,
+you may wish to display units always in k (Kilo, 10e3) even if the data
+is in the M (Mega, 10e6) range, for instance. Value should be an
+integer which is a multiple of 3 between -18 and 18 inclusively. It is
+the exponent on the units you wish to use. For example, use 3 to
+display the y-axis values in k (Kilo, 10e3, thousands), use -6 to
+display the y-axis values in u (Micro, 10e-6, millionths). Use a value
+of 0 to prevent any scaling of the y-axis values.
+
+This option is very effective at confusing the heck out of the default
+rrdtool autoscaler and grid painter. If rrdtool detects that it is not
+successful in labeling the graph under the given circumstances, it will switch
+to the more robust B<--alt-y-grid> mode.
+
+[B<-L>|B<--units-length> I<value>]
+
+How many digits should rrdtool assume the y-axis labels to be? You
+may have to use this option to make enough space once you start
+fideling with the y-axis labeling.
+
+[B<--units=si>]
+
+With this option y-axis values on logarithmic graphs will be scaled to
+the appropriate units (k, M, etc.) instead of using exponential notation.
+Note that for linear graphs, SI notation is used by default.
+
+=back
+
+=head2 Miscellaneous
+
+[B<-z>|B<--lazy>]
+
+Only generate the graph if the current graph is out of date or not existent.
+Note, that only the image size will be returned, if you run with lazy even
+when using graphv and even when using PRINT.
+
+
+[B<-f>|B<--imginfo> I<printfstr>]
+
+After the image has been created, the graph function uses printf
+together with this format string to create output similar to the PRINT
+function, only that the printf function is supplied with the parameters
+I<filename>, I<xsize> and I<ysize>. In order to generate an B<IMG> tag
+suitable for including the graph into a web page, the command line
+would look like this:
+
+ --imginfo '<IMG SRC="/img/%s" WIDTH="%lu" HEIGHT="%lu" ALT="Demo">'
+
+[B<-c>|B<--color> I<COLORTAG>#I<rrggbb>[I<aa>]]
+
+Override the default colors for the standard elements of the graph. The
+I<COLORTAG> is one of C<BACK> background, C<CANVAS> for the background of
+the actual graph, C<SHADEA> for the left and top border, C<SHADEB> for the
+right and bottom border, C<GRID>, C<MGRID> for the major grid, C<FONT> for
+the color of the font, C<AXIS> for the axis of the graph, C<FRAME> for the
+line around the color spots and finally C<ARROW> for the arrow head pointing
+up and forward. Each color is composed out of three hexadecimal numbers
+specifying its rgb color component (00 is off, FF is maximum) of red, green
+and blue. Optionally you may add another hexadecimal number specifying the
+transparency (FF is solid). You may set this option several times to alter
+multiple defaults.
+
+A green arrow is made by: C<--color ARROW#00FF00>
+
+[B<--zoom> I<factor>]
+
+Zoom the graphics by the given amount. The factor must be E<gt> 0
+
+[B<-n>|B<--font> I<FONTTAG>B<:>I<size>B<:>[I<font>]]
+
+This lets you customize which font to use for the various text
+elements on the RRD graphs. C<DEFAULT> sets the default value for all
+elements, C<TITLE> for the title, C<AXIS> for the axis labels, C<UNIT>
+for the vertical unit label, C<LEGEND> for the graph legend.
+
+Use Times for the title: C<--font TITLE:13:Times>
+
+If you do not give a font string you can modify just the sice of the default font:
+C<--font TITLE:13:>.
+
+If you specify the size 0 then you can modify just the font without touching
+the size. This is especially usefull for altering the default font without
+resetting the default fontsizes: C<--font DEFAULT:0:Courier>.
+
+RRDtool comes with a preset default font. You can set the environment
+variable C<RRD_DEFAULT_FONT> if you want to change this.
+
+RRDtool uses Pango for its font handling. This means you can to use
+the full Pango syntax when selecting your font:
+
+The font name has the form "[I<FAMILY-LIST>] [I<STYLE-OPTIONS>] [I<SIZE>]",
+where I<FAMILY-LIST> is a comma separated list of families optionally
+terminated by a comma, I<STYLE_OPTIONS> is a whitespace separated list of
+words where each WORD describes one of style, variant, weight, stretch, or
+gravity, and I<SIZE> is a decimal number (size in points) or optionally
+followed by the unit modifier "px" for absolute size. Any one of the options
+may be absent.
+
+[B<-R>|B<--font-render-mode> {B<normal>,B<light>,B<mono>}]
+
+There are 3 font render modes:
+
+B<normal>: Full Hinting and Antialiasing (default)
+
+B<light>: Slight Hinting and Antialiasing
+
+B<mono>: Full Hinting and NO Antialiasing
+
+
+[B<-B>|B<--font-smoothing-threshold> I<size>]
+
+(this gets ignored in 1.3 for now!)
+
+This specifies the largest font size which will be rendered
+bitmapped, that is, without any font smoothing. By default,
+no text is rendered bitmapped.
+
+[B<-P>|B<--pango-markup>]
+
+All text in rrdtool is rendered using Pango. With the B<--pango-markup> option, all
+text will be processed by pango markup. This allows to embed some simple html
+like markup tags using
+
+ <span key="value">text</span>
+
+Apart from the verbose syntax, there are also the following short tags available.
+
+ b Bold
+ big Makes font relatively larger, equivalent to <span size="larger">
+ i Italic
+ s Strikethrough
+ sub Subscript
+ sup Superscript
+ small Makes font relatively smaller, equivalent to <span size="smaller">
+ tt Monospace font
+ u Underline
+
+More details on L<http://developer.gnome.org/doc/API/2.0/pango/PangoMarkupFormat.html>.
+
+[B<-G>|B<--graph-render-mode> {B<normal>,B<mono>}]
+
+There are 2 render modes:
+
+B<normal>: Graphs are fully Antialiased (default)
+
+B<mono>: No Antialiasing
+
+[B<-E>|B<--slope-mode>]
+
+RRDtool graphs are composed of stair case curves by default. This is in line with
+the way RRDtool calculates its data. Some people favor a more 'organic' look
+for their graphs even though it is not all that true.
+
+[B<-a>|B<--imgformat> B<PNG>|B<SVG>|B<EPS>|B<PDF>]
+
+Image format for the generated graph. For the vector formats you can
+choose among the standard Postscript fonts Courier-Bold,
+Courier-BoldOblique, Courier-Oblique, Courier, Helvetica-Bold,
+Helvetica-BoldOblique, Helvetica-Oblique, Helvetica, Symbol,
+Times-Bold, Times-BoldItalic, Times-Italic, Times-Roman, and ZapfDingbats.
+
+[B<-i>|B<--interlaced>]
+
+(this gets ignored in 1.3 for now!)
+
+If images are interlaced they become visible on browsers more quickly.
+
+[B<-g>|B<--no-legend>]
+
+Suppress generation of the legend; only render the graph.
+
+[B<-F>|B<--force-rules-legend>]
+
+Force the generation of HRULE and VRULE legends even if those HRULE or
+VRULE will not be drawn because out of graph boundaries (mimics
+behaviour of pre 1.0.42 versions).
+
+[B<-T>|B<--tabwidth> I<value>]
+
+By default the tab-width is 40 pixels, use this option to change it.
+
+[B<-b>|B<--base> I<value>]
+
+If you are graphing memory (and NOT network traffic) this switch
+should be set to 1024 so that one Kb is 1024 byte. For traffic
+measurement, 1 kb/s is 1000 b/s.
+
+[B<-W>|B<--watermark> I<string>]
+
+Adds the given string as a watermark, horizontally centred, at the bottom
+of the graph.
+
+=head2 Data and variables
+
+B<DEF:>I<vname>B<=>I<rrdfile>B<:>I<ds-name>B<:>I<CF>[B<:step=>I<step>][B<:start=>I<time>][B<:end=>I<time>]
+
+B<CDEF:>I<vname>B<=>I<RPN expression>
+
+B<VDEF:>I<vname>B<=>I<RPN expression>
+
+You need at least one B<DEF> statement to generate anything. The
+other statements are useful but optional.
+See L<rrdgraph_data> and L<rrdgraph_rpn> for the exact format.
+
+NOTE: B<Graph and print elements>
+
+You need at least one graph element to generate an image and/or
+at least one print statement to generate a report.
+See L<rrdgraph_graph> for the exact format.
+
+=head2 graphv
+
+Calling rrdtool with the graphv option will return information in the
+rrdtool info format. On the command line this means that all output will be
+in key=value format. When used from the perl and ruby bindings a hash
+pointer will be returned from the call.
+
+When the filename '-' is given, the contents of the graph itself will also
+be returned through this interface (hash key 'image'). On the command line
+the output will look like this:
+
+ print[0] = "0.020833"
+ print[1] = "0.0440833"
+ graph_left = 51
+ graph_top = 22
+ graph_width = 400
+ graph_height = 100
+ image_width = 481
+ image_height = 154
+ value_min = 0.0000000000e+00
+ value_max = 4.0000000000e-02
+ image = BLOB_SIZE:8196
+ [... 8196 bytes of image data ...]
+
+There is more information returned than in the standard interface.
+Especially the 'graph_*' keys are new. They help applications that want to
+know what is where on the graph.
+
+=head1 SEE ALSO
+
+L<rrdgraph> gives an overview of how B<rrdtool graph> works.
+L<rrdgraph_data> describes B<DEF>,B<CDEF> and B<VDEF> in detail.
+L<rrdgraph_rpn> describes the B<RPN> language used in the B<?DEF> statements.
+L<rrdgraph_graph> page describes all of the graph and print functions.
+
+Make sure to read L<rrdgraph_examples> for tipsE<amp>tricks.
+
+=head1 AUTHOR
+
+Program by Tobias Oetiker E<lt>tobi@oetiker.chE<gt>
+
+This manual page by Alex van den Bogaerdt E<lt>alex@ergens.op.het.netE<gt>
+
diff --git a/program/doc/rrdgraph_data.pod b/program/doc/rrdgraph_data.pod
--- /dev/null
@@ -0,0 +1,106 @@
+=head1 NAME
+
+rrdgraph_data - preparing data for graphing in rrdtool graph
+
+=head1 SYNOPSIS
+
+B<DEF:>I<E<lt>vnameE<gt>>=I<E<lt>rrdfileE<gt>>:I<E<lt>ds-nameE<gt>>:I<E<lt>CFE<gt>>[:step=I<E<lt>stepE<gt>>][:start=I<E<lt>timeE<gt>>][:end=I<E<lt>timeE<gt>>][:reduce=I<E<lt>B<CF>E<gt>>]
+
+B<VDEF>:I<vname>=I<RPN expression>
+
+B<CDEF>:I<vname>=I<RPN expression>
+
+=head1 DESCRIPTION
+
+These three instructions extract data values out of the B<RRD> files,
+optionally altering them (think, for example, of a bytes to bits
+conversion). If so desired, you can also define variables containing
+useful information such as maximum, minimum etcetera. Two of the
+instructions use a language called B<RPN> which is described in its
+own manual page.
+
+Variable names (I<vname>) must be made up strings of the following characters
+C<A-Z, a-z, 0-9, -,_> and a maximum length of 255 characters.
+
+When picking variable names, make sure you do not choose a name that is
+already taken by an RPN operator. A safe bet it to use lowercase or
+mixedcase names for variables since operators will always be in uppercase.
+
+=head1 DEF
+
+B<DEF:>I<E<lt>vnameE<gt>>=I<E<lt>rrdfileE<gt>>:I<E<lt>ds-nameE<gt>>:I<E<lt>CFE<gt>>[:step=I<E<lt>stepE<gt>>][:start=I<E<lt>timeE<gt>>][:end=I<E<lt>timeE<gt>>][:reduce=I<E<lt>B<CF>E<gt>>]
+
+This command fetches data from an B<RRD> file. The virtual name
+I<vname> can then be used throughout the rest of the script. By
+default, an B<RRA> which contains the correct consolidated data
+at an appropriate resolution will be chosen. The resolution can
+be overridden with the L<--step|rrdgraph/item_Time> option.
+The resolution can again be overridden by specifying the B<step size>.
+The time span of this data is the same as for the graph by default,
+you can override this by specifying B<start and end>. Remember to
+escape colons in the time specification!
+
+If the resolution of the data is higher than the resolution of the
+graph, the data will be further consolidated. This may result in
+a graph that spans slightly more time than requested.
+Ideally each point in the graph should correspond with one B<CDP>
+from an B<RRA>. For instance, if your B<RRD> has an B<RRA> with
+a resolution of 1800 seconds per B<CDP>, you should create an
+image with width 400 and time span 400*1800 seconds (use appropriate
+start and end times, such as C<--start end-8days8hours>).
+
+If consolidation needs to be done, the B<CF> of the B<RRA> specified in the
+B<DEF> itself will be used to reduce the data density. This behaviour can
+be changed using C<:reduce=I<E<lt>B<CF>E<gt>>>. This optional parameter
+specifies the B<CF> to use during the data reduction phase.
+
+Example:
+
+ DEF:ds0=router.rrd:ds0:AVERAGE
+ DEF:ds0weekly=router.rrd:ds0:AVERAGE:step=7200
+ DEF:ds0weekly=router.rrd:ds0:AVERAGE:start=end-1h
+ DEF:ds0weekly=router.rrd:ds0:AVERAGE:start=11\:00:end=start+1h
+
+=head1 VDEF
+
+B<VDEF>:I<vname>=I<RPN expression>
+
+This command returns a value and/or a time according to the B<RPN>
+statements used. The resulting I<vname> will, depending on the
+functions used, have a value and a time component. When you use
+this I<vname> in another B<RPN> expression, you are effectively
+inserting its value just as if you had put a number at that place.
+The variable can also be used in the various graph and print
+elements.
+
+Example: C<VDEF:avg=mydata,AVERAGE>
+
+Note that currently only agregation functions work in VDEF rpn expressions.
+Patches to change this are welcome.
+
+=head1 CDEF
+
+B<CDEF>:I<vname>=I<RPN expression>
+
+This command creates a new set of data points (in memory only, not
+in the B<RRD> file) out of one or more other data series. The B<RPN>
+instructions are used to evaluate a mathematical function on each
+data point. The resulting I<vname> can then be used further on in
+the script, just as if it were generated by a B<DEF> instruction.
+
+Example: C<CDEF:mydatabits=mydata,8,*>
+
+=head1 SEE ALSO
+
+L<rrdgraph> gives an overview of how B<rrdtool graph> works.
+L<rrdgraph_data> describes B<DEF>,B<CDEF> and B<VDEF> in detail.
+L<rrdgraph_rpn> describes the B<RPN> language used in the B<?DEF> statements.
+L<rrdgraph_graph> page describes all of the graph and print functions.
+
+Make sure to read L<rrdgraph_examples> for tipsE<amp>tricks.
+
+=head1 AUTHOR
+
+Program by Tobias Oetiker E<lt>tobi@oetiker.chE<gt>
+
+This manual page by Alex van den Bogaerdt E<lt>alex@ergens.op.het.netE<gt>
diff --git a/program/doc/rrdgraph_examples.pod b/program/doc/rrdgraph_examples.pod
--- /dev/null
@@ -0,0 +1,211 @@
+=head1 NAME
+
+rrdgraph_examples - Examples for rrdtool graph
+
+=head1 SYNOPSIS
+
+B<rrdtool graph /home/httpd/html/test.png --img-format PNG>
+
+followed by any of the examples below
+
+=head1 DESCRIPTION
+
+For your convenience some of the commands are explained here
+by using detailed examples. They are not always cut-and-paste
+ready because comments are intermixed with the examples.
+
+=head1 EXAMPLES
+
+=head2 Data with multiple resolutions
+
+ --end now --start end-120000s --width 400
+ DEF:ds0a=/home/rrdtool/data/router1.rrd:ds0:AVERAGE
+ DEF:ds0b=/home/rrdtool/data/router1.rrd:ds0:AVERAGE:step=1800
+ DEF:ds0c=/home/rrdtool/data/router1.rrd:ds0:AVERAGE:step=7200
+ LINE1:ds0a#0000FF:"default resolution\l"
+ LINE1:ds0b#00CCFF:"resolution 1800 seconds per interval\l"
+ LINE1:ds0c#FF00FF:"resolution 7200 seconds per interval\l"
+
+=head2 Nicely formatted legend section
+
+ DEF:ds0=/home/rrdtool/data/router1.rrd:ds0:AVERAGE
+ DEF:ds1=/home/rrdtool/data/router1.rrd:ds1:AVERAGE
+ VDEF:ds0max=ds0,MAXIMUM
+ VDEF:ds0avg=ds0,AVERAGE
+ VDEF:ds0min=ds0,MINIMUM
+ VDEF:ds0pct=ds0,95,PERCENT
+ VDEF:ds1max=ds1,MAXIMUM
+ VDEF:ds1avg=ds1,AVERAGE
+ VDEF:ds1min=ds1,MINIMUM
+ VDEF:ds1pct=ds1,95,PERCENT
+
+Note: consolidation occurs here.
+
+ CDEF:ds0bits=ds0,8,*
+ CDEF:ds1bits=ds1,8,*
+
+Note: 10 spaces to move text to the right
+
+ COMMENT:" "
+
+Note: the column titles have to be as wide as the columns
+
+ COMMENT:"Maximum "
+ COMMENT:"Average "
+ COMMENT:"Minimum "
+
+ COMMENT:"95th percentile\l"
+ AREA:ds0bits#00C000:"Inbound "
+ GPRINT:ds0max:"%6.2lf %Sbps"
+ GPRINT:ds0avg:"%6.2lf %Sbps"
+ GPRINT:ds0min:"%6.2lf %Sbps"
+ GPRINT:ds0pct:"%6.2lf %Sbps\l"
+ LINE1:ds1bits#0000FF:"Outbound"
+ GPRINT:ds1max:"%6.2lf %Sbps"
+ GPRINT:ds1avg:"%6.2lf %Sbps"
+ GPRINT:ds1min:"%6.2lf %Sbps"
+ GPRINT:ds1pct:"%6.2lf %Sbps\l"
+
+=head2 Offsetting a line on the y-axis
+
+Depending on your needs you can do this in two ways:
+
+=over
+
+=item *
+
+Offset the data, then graph this
+
+ DEF:mydata=my.rrd:ds:AVERAGE
+
+Note: this will also influence any other command that uses "data"
+
+ CDEF:data=mydata,100,+
+ LINE1:data#FF0000:"Data with offset"
+
+=item *
+
+Graph the original data, with an offset
+
+ DEF:mydata=my.rrd:ds:AVERAGE
+
+Note: no color in the first line so it is not visible
+
+ LINE1:100
+
+Note: the second line gets stacked on top of the first one
+
+ LINE1:data#FF0000:"Data with offset":STACK
+
+=back
+
+=head2 Drawing dashed lines
+
+Also works for HRULE and VRULE
+
+=over
+
+=item *
+
+default style: - - - - -
+ LINE1:data#FF0000:"dashed line":dashes
+
+=item *
+
+more fancy style with offset: - - --- - --- -
+ LINE1:data#FF0000:"another dashed line":dashes=15,5,5,10:dash-offset=10
+
+=back
+
+=head2 Time ranges
+
+ Last four weeks: --start end-4w --end 00:00
+ January 2001: --start 20010101 --end start+31d
+ January 2001: --start 20010101 --end 20010201
+ Last hour: --start end-1h
+ Last 24 hours: <nothing at all>
+ Yesterday: --end 00:00
+
+=head2 Viewing the current and previous week together
+
+ --end now --start end-1w
+ DEF:thisweek=router.rrd:ds0:AVERAGE
+ DEF:lastweek=router.rrd:ds0:AVERAGE:end=now-1w:start=end-1w
+
+Shift the data forward by one week (604800 seconds)
+
+ SHIFT:lastweek:604800
+ [ more of the usual VDEF and CDEF stuff if you like ]
+ AREA:lastweek#0000FF:Last\ week
+ LINE1:thisweek#FF0000:This\ week
+
+=head2 Aberrant Behaviour Detection
+
+If the specialized function B<RRAs> exist for aberrant behavior detection, they
+can be used to generate the graph of a time series with confidence bands and
+failures.
+
+ rrdtool graph example.png \
+ DEF:obs=monitor.rrd:ifOutOctets:AVERAGE \
+ DEF:pred=monitor.rrd:ifOutOctets:HWPREDICT \
+ DEF:dev=monitor.rrd:ifOutOctets:DEVPREDICT \
+ DEF:fail=monitor.rrd:ifOutOctets:FAILURES \
+ TICK:fail#ffffa0:1.0:"Failures\: Average bits out" \
+ CDEF:scaledobs=obs,8,* \
+ CDEF:upper=pred,dev,2,*,+ \
+ CDEF:lower=pred,dev,2,*,- \
+ CDEF:scaledupper=upper,8,* \
+ CDEF:scaledlower=lower,8,* \
+ LINE2:scaledobs#0000ff:"Average bits out" \
+ LINE1:scaledupper#ff0000:"Upper Confidence Bound: Average bits out" \
+ LINE1:scaledlower#ff0000:"Lower Confidence Bound: Average bits out"
+
+This example generates a graph of the data series in blue (LINE2 with the scaledobs
+virtual data source), confidence bounds in red (scaledupper and scaledlower virtual
+data sources), and potential failures (i.e. potential aberrant aberrant behavior)
+marked by vertical yellow lines (the fail data source).
+
+The raw data comes from an AVERAGE B<RRA>, the finest resolution of the observed
+time series (one consolidated data point per primary data point). The predicted
+(or smoothed) values are stored in the HWPREDICT B<RRA>. The predicted deviations
+(think standard deviation) values are stored in the DEVPREDICT B<RRA>. Finally,
+the FAILURES B<RRA> contains indicators, with 1 denoting a potential failure.
+
+All of the data is rescaled to bits (instead of Octets) by multiplying by 8.
+The confidence bounds are computed by an offset of 2 deviations both above
+and below the predicted values (the CDEFs upper and lower). Vertical lines
+indicated potential failures are graphed via the TICK graph element, which
+converts non-zero values in an B<RRA> into tick marks. Here an axis-fraction
+argument of 1.0 means the tick marks span the entire y-axis, and hence become
+vertical lines on the graph.
+
+The choice of 2 deviations (a scaling factor) matches the default used internally
+by the FAILURES B<RRA>. If the internal value is changed (see L<rrdtune>), this
+graphing command should be changed to be consistent.
+
+=head3 A note on data reduction:
+
+The B<rrdtool> I<graph> command is designed to plot data at a specified temporal
+resolution, regardless of the actually resolution of the data in the RRD file.
+This can present a problem for the specialized consolidation functions which
+maintain a one-to-one mapping between primary data points and consolidated
+data points. If a graph insists on viewing the contents of these B<RRAs> on a
+coarser temporal scale, the I<graph> command tries to do something intelligent,
+but the confidence bands and failures no longer have the same meaning and may
+be misleading.
+
+
+=head1 SEE ALSO
+
+L<rrdgraph> gives an overview of how B<rrdtool graph> works.
+L<rrdgraph_data> describes B<DEF>,B<CDEF> and B<VDEF> in detail.
+L<rrdgraph_rpn> describes the B<RPN> language used in the B<xDEF> statements.
+L<rrdgraph_graph> page describes all the graph and print functions.
+
+Make sure to read L<rrdgraph_examples> for tipsE<amp>tricks.
+
+=head1 AUTHOR
+
+Program by Tobias Oetiker E<lt>tobi@oetiker.chE<gt>
+
+This manual page by Alex van den Bogaerdt E<lt>alex@ergens.op.het.netE<gt>
diff --git a/program/doc/rrdgraph_graph.pod b/program/doc/rrdgraph_graph.pod
--- /dev/null
@@ -0,0 +1,521 @@
+=pod
+
+=head1 NAME
+
+rrdgraph_graph - rrdtool graph command reference
+
+=head1 SYNOPSIS
+
+B<PRINT>B<:>I<vname>B<:>I<format>
+
+B<GPRINT>B<:>I<vname>B<:>I<format>
+
+B<COMMENT>B<:>I<text>
+
+B<VRULE>B<:>I<time>B<#>I<color>[B<:>I<legend>][B<:dashes>[B<=>I<on_s>[,I<off_s>[,I<on_s>,I<off_s>]...]][B<:dash-offset=>I<offset>]]
+
+B<HRULE>B<:>I<value>B<#>I<color>[B<:>I<legend>][B<:dashes>[B<=>I<on_s>[,I<off_s>[,I<on_s>,I<off_s>]...]][B<:dash-offset=>I<offset>]]
+
+B<LINE>[I<width>]B<:>I<value>[B<#>I<color>][B<:>[I<legend>][B<:STACK>]][B<:dashes>[B<=>I<on_s>[,I<off_s>[,I<on_s>,I<off_s>]...]][B<:dash-offset=>I<offset>]]
+
+B<AREA>B<:>I<value>[B<#>I<color>][B<:>[I<legend>][B<:STACK>]]
+
+B<TICK>B<:>I<vname>B<#>I<rrggbb>[I<aa>][B<:>I<fraction>[B<:>I<legend>]]
+
+B<SHIFT>B<:>I<vname>B<:>I<offset>
+
+B<TEXTALIGN>B<:>{B<left>|B<right>|B<justified>|B<center>}
+
+B<PRINT>B<:>I<vname>B<:>I<CF>B<:>I<format> (deprecated)
+
+B<GPRINT>B<:>I<vname>B<:>I<CF>B<:>I<format> (deprecated)
+
+B<STACK>B<:>I<vname>B<#>I<color>[B<:>I<legend>] (deprecated)
+
+=head1 DESCRIPTION
+
+These instructions allow you to generate your image or report.
+If you don't use any graph elements, no graph is generated.
+Similarly, no report is generated if you don't use print options.
+
+=head2 PRINT
+
+=head3 B<PRINT:>I<vname>B<:>I<format>[B<:strftime>]
+
+Depending on the context, either the value component or the time
+component of a B<VDEF> is printed using I<format>. It is an error
+to specify a I<vname> generated by a B<DEF> or B<CDEF>.
+
+Any text in I<format> is printed literally with one exception:
+The percent character introduces a formatter string. This string
+can be:
+
+For printing values:
+
+=over
+
+=item B<%%>
+
+just prints a literal '%' character
+
+=item B<%#.#le>
+
+prints numbers like 1.2346e+04. The optional integers # denote field
+width and decimal precision.
+
+=item B<%#.#lf>
+
+prints numbers like 12345.6789, with optional field width
+and precision.
+
+=item B<%s>
+
+place this after B<%le>, B<%lf> or B<%lg>. This will be replaced by the
+appropriate SI magnitude unit and the value will be scaled
+accordingly (123456 -> 123.456 k).
+
+=item B<%S>
+
+is similar to B<%s>. It does, however, use a previously defined
+magnitude unit. If there is no such unit yet, it tries to define
+one (just like B<%s>) unless the value is zero, in which case the magnitude
+unit stays undefined. Thus, formatter strings using B<%S> and no B<%s>
+will all use the same magnitude unit except for zero values.
+
+=back
+
+If you PRINT a VDEF value, you can also print the time associated with it by appending the string
+B<:strftime> to the format. Note that rrdtool uses the strftime function of your OSs clibrary. This means that
+the conversion specifier may vary. Check the manual page if you are uncertain. The following is a list of
+conversion specifiers usually supported across the board.
+
+=over
+
+=item B<%a>
+
+The abbreviated weekday name according to the current locale.
+
+=item B<%A>
+
+The full weekday name according to the current locale.
+
+=item B<%b>
+
+The abbreviated month name according to the current locale.
+
+=item B<%B>
+
+The full month name according to the current locale.
+
+=item B<%c>
+
+The preferred date and time representation for the current locale.
+
+=item B<%d>
+
+The day of the month as a decimal number (range 01 to 31).
+
+=item B<%H>
+
+The hour as a decimal number using a 24-hour clock (range 00 to 23).
+
+=item B<%I>
+
+The hour as a decimal number using a 12-hour clock (range 01 to 12).
+
+=item B<%j>
+
+The day of the year as a decimal number (range 001 to 366).
+
+=item B<%m>
+
+The month as a decimal number (range 01 to 12).
+
+=item B<%M>
+
+The minute as a decimal number (range 00 to 59).
+
+=item B<%p>
+
+Either `AM' or `PM' according to the given time value, or the corresponding
+strings for the current locale. Noon is treated as `pm' and midnight as
+`am'. Note that in many locales and `pm' notation is unsupported and in
+such cases %p will return an empty string.
+
+=item B<%s>
+
+The second as a decimal number (range 00 to 61).
+
+=item B<%S>
+
+The seconds since the epoch (1.1.1970) (libc dependant non standard!)
+
+=item B<%U>
+
+The week number of the current year as a decimal number, range 00 to 53, starting with the
+first Sunday as the first day of week 01. See also %V and %W.
+
+=item B<%V>
+
+The ISO 8601:1988 week number of the current year as a decimal number, range 01 to 53, where
+week 1 is the first week that has at least 4 days in the current year, and with Monday as the
+first day of the week. See also %U and %W.
+
+=item B<%w>
+
+The day of the week as a decimal, range 0 to 6, Sunday being 0. See also %u.
+
+=item B<%W>
+
+The week number of the current year as a decimal number, range 00 to 53, starting with the
+first Monday as the first day of week 01.
+
+=item B<%x>
+
+The preferred date representation for the current locale without the time.
+
+=item B<%X>
+
+The preferred time representation for the current locale without the date.
+
+=item B<%y>
+
+The year as a decimal number without a century (range 00 to 99).
+
+=item B<%Y>
+
+The year as a decimal number including the century.
+
+=item B<%Z>
+
+The time zone or name or abbreviation.
+
+=item B<%%>
+
+A literal `%' character.
+
+=back
+
+=head3 B<PRINT:>I<vname>B<:>I<CF>B<:>I<format>
+
+I<Deprecated. Use the new form of this command in new scripts.>
+The first form of this command is to be used with B<CDEF> I<vname>s.
+
+
+=head2 GRAPH
+
+=head3 B<GPRINT>B<:>I<vname>B<:>I<format>
+
+This is the same as C<PRINT>, but printed inside the graph.
+
+=head3 B<GPRINT>B<:>I<vname>B<:>I<CF>B<:>I<format>
+
+I<Deprecated. Use the new form of this command in new scripts.>
+This is the same as C<PRINT>, but printed inside the graph.
+
+=head3 B<COMMENT>B<:>I<text>
+
+Text is printed literally in the legend section of the graph. Note that in
+RRDtool 1.2 you have to escape colons in COMMENT text in the same way you
+have to escape them in B<*PRINT> commands by writing B<'\:'>.
+
+=head3 B<VRULE>B<:>I<time>B<#>I<color>[B<:>I<legend>][B<:dashes>[B<=>I<on_s>[,I<off_s>[,I<on_s>,I<off_s>]...]][B<:dash-offset=>I<offset>]]
+
+Draw a vertical line at I<time>. Its color is composed from three
+hexadecimal numbers specifying the rgb color components (00 is off, FF is
+maximum) red, green and blue followed by an optional alpha. Optionally, a legend box and string is
+printed in the legend section. I<time> may be a number or a variable
+from a B<VDEF>. It is an error to use I<vname>s from B<DEF> or B<CDEF> here.
+Dashed lines can be drawn using the B<dashes> modifier. See B<LINE> for more
+details.
+
+=head3 B<HRULE>B<:>I<value>B<#>I<color>[B<:>I<legend>][B<:dashes>[B<=>I<on_s>[,I<off_s>[,I<on_s>,I<off_s>]...]][B<:dash-offset=>I<offset>]]
+
+Draw a horizontal line at I<value>. HRULE acts much like LINE except that
+will have no effect on the scale of the graph. If a HRULE is outside the
+graphing area it will just not be visible.
+
+=head3 B<LINE>[I<width>]B<:>I<value>[B<#>I<color>][B<:>[I<legend>][B<:STACK>]][B<:dashes>[B<=>I<on_s>[,I<off_s>[,I<on_s>,I<off_s>]...]][B<:dash-offset=>I<offset>]]
+
+Draw a line of the specified width onto the graph. I<width> can be a
+floating point number. If the color is not specified, the drawing is done
+'invisibly'. This is useful when stacking something else on top of this
+line. Also optional is the legend box and string which will be printed in
+the legend section if specified. The B<value> can be generated by B<DEF>,
+B<VDEF>, and B<CDEF>. If the optional B<STACK> modifier is used, this line
+is stacked on top of the previous element which can be a B<LINE> or an
+B<AREA>.
+
+The B<dashes> modifier enables dashed line style. Without any further options
+a symmetric dashed line with a segment length of 5 pixels will be drawn. The
+dash pattern can be changed if the B<dashes=> parameter is followed by either
+one value or an even number (1, 2, 4, 6, ...) of positive values. Each value
+provides the length of alternate I<on_s> and I<off_s> portions of the
+stroke. The B<dash-offset> parameter specifies an I<offset> into the pattern
+at which the stroke begins.
+
+When you do not specify a color, you cannot specify a legend. Should
+you want to use STACK, use the "LINEx:<value>::STACK" form.
+
+=head3 B<AREA>B<:>I<value>[B<#>I<color>][B<:>[I<legend>][B<:STACK>]]
+
+See B<LINE>, however the area between the x-axis and the line will
+be filled.
+
+=head3 B<TICK>B<:>I<vname>B<#>I<rrggbb>[I<aa>][B<:>I<fraction>[B<:>I<legend>]]
+
+Plot a tick mark (a vertical line) for each value of I<vname> that is
+non-zero and not *UNKNOWN*. The I<fraction> argument specifies the length of
+the tick mark as a fraction of the y-axis; the default value is 0.1 (10% of
+the axis). Note that the color specification is not optional. The TICK marks normaly
+start at the lower edge of the graphing area. If the fraction is negative they start
+at the upper border of the graphing area.
+
+=head3 B<SHIFT>B<:>I<vname>B<:>I<offset>
+
+Using this command B<RRDtool> will graph the following elements
+with the specified offset. For instance, you can specify an
+offset of S<( 7*24*60*60 = ) 604'800 seconds> to "look back" one
+week. Make sure to tell the viewer of your graph you did this ...
+As with the other graphing elements, you can specify a number or
+a variable here.
+
+=head3 B<TEXTALIGN>B<:>{B<left>|B<right>|B<justified>|B<center>}
+
+Labels are placed below the graph. When they overflow to the left, they wrap
+to the next line. By default, lines are justified left and right. The
+B<TEXTALIGN> function lets you change this default. This is a command and
+not an option, so that you can change the default several times in your
+argument list.
+
+=cut
+
+# This section describes the curruently defunct
+# PieChart code.
+#
+# =item B<PART>B<:>I<vname>B<#>I<rrggbb>[I<aa>][B<:>I<legend>]
+#
+# B<RRDtool> has now support for B<pie charts>. If you include the
+# B<PART> command, the canvas is extended to make room for a chart.
+# The size of the canvas is determined by the lesser of
+# L<width and height|rrdgraph/item_Size>.
+#
+# Pie parts will be concatenated, the first one will start at the
+# top and parts will be created clockwise. The size of the part
+# is defined by the value part of the L<VDEF|rrdgraph_data/VDEF>
+# function. It should return a number between 0 and 100, being a
+# percentage. Providing wrong input will produce undefined results.
+#
+#
+
+=pod
+
+=head3 B<STACK>B<:>I<vname>B<#>I<color>[B<:>I<legend>]
+
+I<Deprecated. Use the B<STACK> modifiers on the other commands instead!>
+
+B<Some notes on stacking>
+
+When stacking, an element is not placed above the X-axis but rather
+on top of the previous element. There must be something to stack
+upon.
+
+You can use an B<invisible> LINE or AREA to stacked upon.
+
+An B<unknown> value makes the entire stack unknown from that moment on.
+You don't know where to begin (the unknown value) and therefore do
+not know where to end.
+
+If you want to make sure you will be displaying a certain variable,
+make sure never to stack upon the unknown value. Use a CDEF instruction
+with B<IF> and B<UN> to do so.
+
+=head1 NOTES on legend arguments
+
+=head2 Escaping the colon
+
+A colon ':' in a I<legend> argument will mark the end of the
+legend. To enter a ':' as part of a legend, the colon must be escaped
+with a backslash '\:'. Beware that many environments process
+backslashes themselves, so it may be necessary to write two
+backslashes in order to one being passed onto rrd_graph.
+
+=head2 String Formatting
+
+The text printed below the actual graph can be formatted by appending special
+escape characters at the end of a text. When ever such a character occurs,
+all pending text is pushed onto the graph according to the character
+specified.
+
+Valid markers are: B<\j> for justified, B<\l> for left aligned, B<\r> for
+right aligned, and B<\c> for centered. In the next section there is an
+example showing how to use centered formatting.
+
+B<\n> is a valid alias for B<\l> since incomplete parsing in earlier
+versions of rrdtool lead to this behaviour and a number of people has been using it.
+
+Normally there are two space characters inserted between every two items
+printed into the graph. The space following a string can be suppressed by
+putting a B<\g> at the end of the string. The B<\g> also ignores any space
+inside the string if it is at the very end of the string. This can be used
+in connection with B<%s> to suppress empty unit strings.
+
+ GPRINT:a:MAX:%lf%s\g
+
+A special case is COMMENT:B<\s> which inserts some additional vertical space
+before placing the next row of legends.
+
+If you are using the proportional font in your graph, you can use tab
+characters or the sequence B<\t> to line-up legend elements. Note that
+the tabs inserted are relative to the start of the current legend
+element!
+
+Since RRDtool 1.3 is using Pango for rending text, you can use Pango markup.
+Pango uses the xml B<span> tags for inline formatting instructions.:
+
+A simple example of a marked-up string might be:
+
+ <span foreground="blue" size="x-large">Blue text</span> is <i>cool</i>!
+
+The complete list of attributes for the span tag (taken from the pango documentation):
+
+=over
+
+=item B<font_desc>
+
+A font description string, such as "Sans Italic 12"; note that any other span attributes will override this description. So if you have "Sans Italic" and also a style="normal" attribute, you will get Sans normal, not italic.
+
+=item B<font_family>
+
+A font family name
+
+=item B<face>
+
+Synonym for font_family
+
+=item B<size>
+
+Font size in 1024ths of a point, or one of the absolute sizes 'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large', or one of the relative sizes 'smaller' or 'larger'. If you want to specify a absolute size, it's usually easier to take advantage of the ability to specify a partial font description using 'font_desc'; you can use font_desc='12.5' rather than size='12800'.
+
+=item B<style>
+
+One of 'normal', 'oblique', 'italic'
+
+=item B<weight>
+
+One of 'ultralight', 'light', 'normal', 'bold', 'ultrabold', 'heavy', or a numeric weight
+
+=item B<variant>
+
+'normal' or 'smallcaps'
+
+=item B<stretch>
+
+One of 'ultracondensed', 'extracondensed', 'condensed', 'semicondensed', 'normal', 'semiexpanded', 'expanded', 'extraexpanded', 'ultraexpanded'
+
+=item B<foreground>
+
+An RGB color specification such as '#00FF00' or a color name such as 'red'
+
+=item B<background>
+
+An RGB color specification such as '#00FF00' or a color name such as 'red'
+
+=item B<underline>
+
+One of 'none', 'single', 'double', 'low', 'error'
+
+=item B<underline_color>
+
+The color of underlines; an RGB color specification such as '#00FF00' or a color name such as 'red'
+
+=item B<rise>
+
+Vertical displacement, in 10000ths of an em. Can be negative for subscript, positive for superscript.
+
+=item B<strikethrough>
+
+'true' or 'false' whether to strike through the text
+
+=item B<strikethrough_color>
+
+The color of strikethrough lines; an RGB color specification such as '#00FF00' or a color name such as 'red'
+
+=item B<fallback>
+
+'true' or 'false' whether to enable fallback. If disabled, then characters will only be used from the closest matching font on the system. No fallback will be done to other fonts on the system that might contain the characters in the text. Fallback is enabled by default. Most applications should not disable fallback.
+
+=item B<lang>
+
+A language code, indicating the text language
+
+=item B<letter_spacing>
+
+Inter-letter spacing in 1024ths of a point.
+
+=item B<gravity>
+
+One of 'south', 'east', 'north', 'west', 'auto'.
+
+=item B<gravity_hint>
+
+One of 'natural', 'strong', 'line'.
+
+=back
+
+To save you some typing, there are also some shortcuts:
+
+=over
+
+=item B<b>
+
+Bold
+
+=item B<big>
+
+Makes font relatively larger, equivalent to <span size="larger">
+
+=item B<i>
+
+Italic
+
+=item B<s>
+
+Strikethrough
+
+=item B<sub>
+
+Subscript
+
+=item B<sup>
+
+Superscript
+
+=item B<small>
+
+Makes font relatively smaller, equivalent to <span size="smaller">
+
+=item B<tt>
+
+Monospace font
+
+=item B<u>
+
+Underline
+
+=back
+
+=head1 SEE ALSO
+
+L<rrdgraph> gives an overview of how B<rrdtool graph> works.
+L<rrdgraph_data> describes B<DEF>,B<CDEF> and B<VDEF> in detail.
+L<rrdgraph_rpn> describes the B<RPN> language used in the B<?DEF> statements.
+L<rrdgraph_graph> page describes all of the graph and print functions.
+
+Make sure to read L<rrdgraph_examples> for tipsE<amp>tricks.
+
+=head1 AUTHOR
+
+Program by Tobias Oetiker E<lt>tobi@oetiker.chE<gt>
+
+This manual page by Alex van den Bogaerdt E<lt>alex@ergens.op.het.netE<gt>
diff --git a/program/doc/rrdgraph_rpn.pod b/program/doc/rrdgraph_rpn.pod
--- /dev/null
@@ -0,0 +1,329 @@
+=head1 NAME
+
+rrdgraph_rpn - About RPN Math in rrdtool graph
+
+=head1 SYNOPSIS
+
+I<RPN expression>:=I<vname>|I<operator>|I<value>[,I<RPN expression>]
+
+=head1 DESCRIPTION
+
+If you have ever used a traditional HP calculator you already know
+B<RPN>. The idea behind B<RPN> is that you have a stack and push
+your data onto this stack. Whenever you execute an operation, it
+takes as many elements from the stack as needed. Pushing is done
+implicitly, so whenever you specify a number or a variable, it gets
+pushed onto the stack automatically.
+
+At the end of the calculation there should be one and only one value left on
+the stack. This is the outcome of the function and this is what is put into
+the I<vname>. For B<CDEF> instructions, the stack is processed for each
+data point on the graph. B<VDEF> instructions work on an entire data set in
+one run. Note, that currently B<VDEF> instructions only support a limited
+list of functions.
+
+Example: C<VDEF:maximum=mydata,MAXIMUM>
+
+This will set variable "maximum" which you now can use in the rest
+of your RRD script.
+
+Example: C<CDEF:mydatabits=mydata,8,*>
+
+This means: push variable I<mydata>, push the number 8, execute
+the operator I<*>. The operator needs two elements and uses those
+to return one value. This value is then stored in I<mydatabits>.
+As you may have guessed, this instruction means nothing more than
+I<mydatabits = mydata * 8>. The real power of B<RPN> lies in the
+fact that it is always clear in which order to process the input.
+For expressions like C<a = b + 3 * 5> you need to multiply 3 with
+5 first before you add I<b> to get I<a>. However, with parentheses
+you could change this order: C<a = (b + 3) * 5>. In B<RPN>, you
+would do C<a = b, 3, +, 5, *> without the need for parentheses.
+
+=head1 OPERATORS
+
+=over 4
+
+=item Boolean operators
+
+B<LT, LE, GT, GE, EQ, NE>
+
+Pop two elements from the stack, compare them for the selected condition
+and return 1 for true or 0 for false. Comparing an I<unknown> or an
+I<infinite> value will always result in 0 (false).
+
+B<UN, ISINF>
+
+Pop one element from the stack, compare this to I<unknown> respectively
+to I<positive or negative infinity>. Returns 1 for true or 0 for false.
+
+B<IF>
+
+Pops three elements from the stack. If the element popped last is 0
+(false), the value popped first is pushed back onto the stack,
+otherwise the value popped second is pushed back. This does, indeed,
+mean that any value other than 0 is considered to be true.
+
+Example: C<A,B,C,IF> should be read as C<if (A) then (B) else (C)>
+
+Z<>
+
+=item Comparing values
+
+B<MIN, MAX>
+
+Pops two elements from the stack and returns the smaller or larger,
+respectively. Note that I<infinite> is larger than anything else.
+If one of the input numbers is I<unknown> then the result of the operation will be
+I<unknown> too.
+
+B<LIMIT>
+
+Pops two elements from the stack and uses them to define a range.
+Then it pops another element and if it falls inside the range, it
+is pushed back. If not, an I<unknown> is pushed.
+
+The range defined includes the two boundaries (so: a number equal
+to one of the boundaries will be pushed back). If any of the three
+numbers involved is either I<unknown> or I<infinite> this function
+will always return an I<unknown>
+
+Example: C<CDEF:a=alpha,0,100,LIMIT> will return I<unknown> if
+alpha is lower than 0 or if it is higher than 100.
+
+Z<>
+
+=item Arithmetics
+
+B<+, -, *, /, %>
+
+Add, subtract, multiply, divide, modulo
+
+B<ADDNAN>
+
+NAN-safe addition. If one parameter is NAN/UNKNOWN it'll be treated as
+zero. If both parameters are NAN/UNKNOWN, NAN/UNKNOWN will be returned.
+
+B<SIN, COS, LOG, EXP, SQRT>
+
+Sine and cosine (input in radians), log and exp (natural logarithm),
+square root.
+
+B<ATAN>
+
+Arctangent (output in radians).
+
+B<ATAN2>
+
+Arctangent of y,x components (output in radians).
+This pops one element from the stack, the x (cosine) component, and then
+a second, which is the y (sine) component.
+It then pushes the arctangent of their ratio, resolving the ambiguity between
+quadrants.
+
+Example: C<CDEF:angle=Y,X,ATAN2,RAD2DEG> will convert C<X,Y>
+components into an angle in degrees.
+
+B<FLOOR, CEIL>
+
+Round down or up to the nearest integer.
+
+B<DEG2RAD, RAD2DEG>
+
+Convert angle in degrees to radians, or radians to degrees.
+
+B<ABS>
+
+Take the absolute value.
+
+=item Set Operations
+
+B<SORT, REV>
+
+Pop one element from the stack. This is the I<count> of items to be sorted
+(or reversed). The top I<count> of the remaining elements are then sorted
+(or reversed) in place on the stack.
+
+Example: C<CDEF:x=v1,v2,v3,v4,v5,v6,6,SORT,POP,5,REV,POP,+,+,+,4,/> will
+compute the average of the values v1 to v6 after removing the smallest and
+largest.
+
+B<AVG>
+
+Pop one element (I<count>) from the stack. Now pop I<count> elements and build the
+average, ignoring all UNKNOWN values in the process.
+
+Example: C<CDEF:x=a,b,c,d,4,AVG>
+
+B<TREND, TRENDNAN>
+
+Create a "sliding window" average of another data series.
+
+Usage:
+CDEF:smoothed=x,1800,TREND
+
+This will create a half-hour (1800 second) sliding window average of x. The
+average is essentially computed as shown here:
+
+ +---!---!---!---!---!---!---!---!--->
+ now
+ delay t0
+ <--------------->
+ delay t1
+ <--------------->
+ delay t2
+ <--------------->
+
+
+ Value at sample (t0) will be the average between (t0-delay) and (t0)
+ Value at sample (t1) will be the average between (t1-delay) and (t1)
+ Value at sample (t2) will be the average between (t2-delay) and (t2)
+
+TRENDNAN is - in contrast to TREND - NAN-safe. If you use TREND and one
+source value is NAN the complete sliding window is affected. The TRENDNAN
+operation ignores all NAN-values in a sliding window and computes the
+average of the remaining values.
+
+
+=item Special values
+
+B<UNKN>
+
+Pushes an unknown value on the stack
+
+B<INF, NEGINF>
+
+Pushes a positive or negative infinite value on the stack. When
+such a value is graphed, it appears at the top or bottom of the
+graph, no matter what the actual value on the y-axis is.
+
+B<PREV>
+
+Pushes an I<unknown> value if this is the first value of a data
+set or otherwise the result of this B<CDEF> at the previous time
+step. This allows you to do calculations across the data. This
+function cannot be used in B<VDEF> instructions.
+
+B<PREV(vname)>
+
+Pushes an I<unknown> value if this is the first value of a data
+set or otherwise the result of the vname variable at the previous time
+step. This allows you to do calculations across the data. This
+function cannot be used in B<VDEF> instructions.
+
+B<COUNT>
+
+Pushes the number 1 if this is the first value of the data set, the
+number 2 if it is the second, and so on. This special value allows
+you to make calculations based on the position of the value within
+the data set. This function cannot be used in B<VDEF> instructions.
+
+=item Time
+
+Time inside RRDtool is measured in seconds since the epoch. The
+epoch is defined to be S<C<Thu Jan 1 00:00:00 UTC 1970>>.
+
+B<NOW>
+
+Pushes the current time on the stack.
+
+B<TIME>
+
+Pushes the time the currently processed value was taken at onto the stack.
+
+B<LTIME>
+
+Takes the time as defined by B<TIME>, applies the time zone offset
+valid at that time including daylight saving time if your OS supports
+it, and pushes the result on the stack. There is an elaborate example
+in the examples section below on how to use this.
+
+=item Processing the stack directly
+
+B<DUP, POP, EXC>
+
+Duplicate the top element, remove the top element, exchange the two
+top elements.
+
+Z<>
+
+=back
+
+=head1 VARIABLES
+
+These operators work only on B<VDEF> statements. Note that currently ONLY these work for B<VDEF>.
+
+=over 4
+
+=item MAXIMUM, MINIMUM, AVERAGE
+
+Return the corresponding value, MAXIMUM and MINIMUM also return
+the first occurrence of that value in the time component.
+
+Example: C<VDEF:avg=mydata,AVERAGE>
+
+=item STDEV
+
+Returns the standard deviation of the values.
+
+Example: C<VDEF:stdev=mydata,STDEV>
+
+=item LAST, FIRST
+
+Return the last/first value including its time. The time for
+FIRST is actually the start of the corresponding interval, whereas
+LAST returns the end of the corresponding interval.
+
+Example: C<VDEF:first=mydata,FIRST>
+
+=item TOTAL
+
+Returns the rate from each defined time slot multiplied with the
+step size. This can, for instance, return total bytes transfered
+when you have logged bytes per second. The time component returns
+the number of seconds.
+
+Example: C<VDEF:total=mydata,TOTAL>
+
+=item PERCENT
+
+This should follow a B<DEF> or B<CDEF> I<vname>. The I<vname> is popped,
+another number is popped which is a certain percentage (0..100). The
+data set is then sorted and the value returned is chosen such that
+I<percentage> percent of the values is lower or equal than the result.
+I<Unknown> values are considered lower than any finite number for this
+purpose so if this operator returns an I<unknown> you have quite a lot
+of them in your data. B<Inf>inite numbers are lesser, or more, than the
+finite numbers and are always more than the I<Unknown> numbers.
+(NaN E<lt> -INF E<lt> finite values E<lt> INF)
+
+Example: C<VDEF:perc95=mydata,95,PERCENT>
+
+=item LSLSLOPE, LSLINT, LSLCORREL
+
+Return the parameters for a B<L>east B<S>quares B<L>ine I<(y = mx +b)>
+which approximate the provided dataset. LSLSLOPE is the slope I<(m)> of
+the line related to the COUNT position of the data. LSLINT is the
+y-intercept I<(b)>, which happens also to be the first data point on the
+graph. LSLCORREL is the Correlation Coefficient (also know as Pearson's
+Product Moment Correlation Coefficient). It will range from 0 to +/-1
+and represents the quality of fit for the approximation.
+
+Example: C<VDEF:slope=mydata,LSLSLOPE>
+
+=back
+
+=head1 SEE ALSO
+
+L<rrdgraph> gives an overview of how B<rrdtool graph> works.
+L<rrdgraph_data> describes B<DEF>,B<CDEF> and B<VDEF> in detail.
+L<rrdgraph_rpn> describes the B<RPN> language used in the B<?DEF> statements.
+L<rrdgraph_graph> page describes all of the graph and print functions.
+
+Make sure to read L<rrdgraph_examples> for tipsE<amp>tricks.
+
+=head1 AUTHOR
+
+Program by Tobias Oetiker E<lt>tobi@oetiker.chE<gt>
+
+This manual page by Alex van den Bogaerdt E<lt>alex@ergens.op.het.netE<gt>
diff --git a/program/doc/rrdinfo.pod b/program/doc/rrdinfo.pod
--- /dev/null
+++ b/program/doc/rrdinfo.pod
@@ -0,0 +1,62 @@
+=head1 NAME
+
+rrdinfo - extract header information from an RRD
+
+=head1 SYNOPSIS
+
+B<rrdtool> B<info> I<filename.rrd>
+
+=head1 DESCRIPTION
+
+The B<info> function prints the header information from an RRD in
+a parsing friendly format.
+
+Check L<rrdcreate> if you are uncertain about the meaning of the
+individual keys.
+
+=head1 EXAMPLE
+
+This is the output generated by running B<info> on a simple RRD which
+contains two data sources and one RRA. Note that the number after the
+I<last_update> keyword is in seconds since 1970. The string B<NaN>
+stands for I<*UNKNOWN*> data. In the example it means that this RRD
+has neither minimum nor maximum values defined for either of its
+data sources.
+
+ filename = "random.rrd"
+ rrd_version = "0001"
+ step = 300
+ last_update = 955892996
+ ds[a].type = "GAUGE"
+ ds[a].minimal_heartbeat = 600
+ ds[a].min = NaN
+ ds[a].max = NaN
+ ds[a].last_ds = "UNKN"
+ ds[a].value = 2.1824421548e+04
+ ds[a].unknown_sec = 0
+ ds[b].type = "GAUGE"
+ ds[b].minimal_heartbeat = 600
+ ds[b].min = NaN
+ ds[b].max = NaN
+ ds[b].last_ds = "UNKN"
+ ds[b].value = 3.9620838224e+03
+ ds[b].unknown_sec = 0
+ rra[0].cf = "AVERAGE"
+ rra[0].pdp_per_row = 1
+ rra[0].cdp_prep[0].value = nan
+ rra[0].cdp_prep[0].unknown_datapoints = 0
+ rra[0].cdp_prep[1].value = nan
+ rra[0].cdp_prep[1].unknown_datapoints = 0
+
+=over 8
+
+=item I<filename.rrd>
+
+The name of the B<RRD> you want to examine.
+
+=back
+
+=head1 AUTHOR
+
+Tobias Oetiker E<lt>tobi@oetiker.chE<gt>
+
diff --git a/program/doc/rrdlast.pod b/program/doc/rrdlast.pod
--- /dev/null
+++ b/program/doc/rrdlast.pod
@@ -0,0 +1,27 @@
+=head1 NAME
+
+rrdlast - Return the date of the last data sample in an RRD
+
+=head1 SYNOPSIS
+
+B<rrdtool> B<last> I<filename>
+
+=head1 DESCRIPTION
+
+The B<last> function returns the UNIX timestamp of the most recent
+update of the RRD.
+
+=over 8
+
+=item I<filename>
+
+The name of the B<RRD> that contains the data.
+
+=back
+
+=head1 AUTHOR
+
+Russ Wright <rwwright@home.com>
+
+
+
diff --git a/program/doc/rrdlastupdate.pod b/program/doc/rrdlastupdate.pod
--- /dev/null
@@ -0,0 +1,27 @@
+=head1 NAME
+
+rrdlastupdate - Return the most recent update to an RRD
+
+=head1 SYNOPSIS
+
+B<rrdtool> B<lastupdate> I<filename>
+
+=head1 DESCRIPTION
+
+The B<lastupdate> function returns the UNIX timestamp and the
+value stored for each datum in the most recent update of an RRD.
+
+=over 8
+
+=item I<filename>
+
+The name of the B<RRD> that contains the data.
+
+=back
+
+=head1 AUTHOR
+
+Andy Riebs <andy.riebs@hp.com>
+
+
+
diff --git a/program/doc/rrdpython.pod b/program/doc/rrdpython.pod
--- /dev/null
@@ -0,0 +1,60 @@
+=head1 NAME
+
+rrdpython - About the RRD Python bindings
+
+=head1 SYNOPSIS
+
+ import rrdtool
+ rrdtool.create('/tmp/test.rrd', 'DS:foo:GUAGE:20:0:U')
+
+=head1 DESCRIPTION
+
+The B<rrdtool> functions are directly callable via the Python programming
+language. This wrapper implementation has been written from the scratch
+(without SWIG)
+
+The API's simply expects string parameters to the functions. Please refer
+to the other B<rrdtool> documentation for functions and valid arguments.
+
+=head1 EXAMPLE
+
+ import sys
+ sys.path.append('/path/to/rrdtool/lib/python2.3/site-packages/')
+ import rrdtool, tempfile
+
+ DAY = 86400
+ YEAR = 365 * DAY
+ fd,path = tempfile.mkstemp('.png')
+
+ rrdtool.graph(path,
+ '--imgformat', 'PNG',
+ '--width', '540',
+ '--height', '100',
+ '--start', "-%i" % YEAR,
+ '--end', "-1",
+ '--vertical-label', 'Downloads/Day',
+ '--title', 'Annual downloads',
+ '--lower-limit', '0',
+ 'DEF:downloads=downloads.rrd:downloads:AVERAGE',
+ 'AREA:downloads#990033:Downloads')
+
+ info = rrdtool.info('downloads.rrd')
+ print info['last_update']
+ print info['ds']['downloads']['minimal_heartbeat']
+
+If you use the B<site-python-install> make target you can drop to first sys.path.append
+line since the rrdtool module will be available everywhere.
+
+If rrdtool runs into trouble, it will throw an exception which you might want to catch.
+
+=head1 SEE ALSO
+
+rrdcreate, rrdupdate, rrdgraph, rrddump, rrdfetch, rrdtune, rrdlast,
+rrdxport, rrdinfo
+
+=head1 AUTHOR
+
+Hye-Shik Chang E<lt>perky@i18n.orgE<gt>
+
+Alan Milligan E<lt>alan.milligan@last-bastion.netE<gt>
+
diff --git a/program/doc/rrdresize.pod b/program/doc/rrdresize.pod
--- /dev/null
@@ -0,0 +1,54 @@
+=head1 NAME
+
+rrdresize - alters the size of an RRA and creates a new .rrd file
+
+=head1 SYNOPSIS
+
+B<rrdtool> B<resize> I<filename> I<rra-num> B<GROW>I<|>B<SHRINK> I<rows>
+
+=head1 DESCRIPTION
+
+The B<resize> function is used to modify the number of rows in
+an B<RRA>.
+
+=over 8
+
+=item I<filename>
+
+the name of the B<RRD> you want to alter.
+
+=item I<rra-num>
+
+the B<RRA> you want to alter. You can find the number using B<rrdtool info>.
+
+=item B<GROW>
+
+used if you want to add extra rows to an RRA. The extra rows will be inserted
+as the rows that are oldest.
+
+=item B<SHRINK>
+
+used if you want to remove rows from an RRA. The rows that will be removed
+are the oldest rows.
+
+=item I<rows>
+
+the number of rows you want to add or remove.
+
+=back
+
+=head1 NOTES
+
+The new .rrd file, with the modified RRAs, is written to the file
+B<resize.rrd> in the current directory. B<The original .rrd file is not
+modified>.
+
+It is possible to abuse this tool and get strange results
+by first removing some rows and then reinserting the same amount (effectively
+clearing them to be Unknown). You may thus end up with unknown data in one
+RRA while at the same timestamp this data is available in another RRA.
+
+=head1 AUTHOR
+
+Alex van den Bogaerdt <alex@ergens.op.het.net>
+
diff --git a/program/doc/rrdrestore.pod b/program/doc/rrdrestore.pod
--- /dev/null
@@ -0,0 +1,38 @@
+=head1 NAME
+
+rrdrestore - Restore the contents of an RRD from its XML dump format
+
+=head1 SYNOPSIS
+
+B<rrdtool> B<restore> I<filename.xml> I<filename.rrd>
+S<[B<--range-check>|B<-r>]>
+
+=head1 DESCRIPTION
+
+The B<restore> function reads the XML representation of an RRD and converts
+it to the native B<RRD> format.
+
+=over 8
+
+=item I<filename.xml>
+
+The name of the B<XML> file you want to restore.
+
+=item I<filename.rrd>
+
+The name of the B<RRD> to restore.
+
+=item B<--range-check>|B<-r>
+
+Make sure the values in the RRAs do not exceed the limits defined for
+the various data sources.
+
+=item B<--force-overwrite>|B<-f>
+
+Allows B<RRDtool> to overwrite the destination B<RRD>.
+
+=back
+
+=head1 AUTHOR
+
+Tobias Oetiker <tobi@oetiker.ch>
diff --git a/program/doc/rrdruby.pod b/program/doc/rrdruby.pod
--- /dev/null
+++ b/program/doc/rrdruby.pod
@@ -0,0 +1,86 @@
+=head1 NAME
+
+rrdruby - About the RRD Ruby bindings
+
+=head1 SYNOPSIS
+
+ require "RRD"
+ RRD.create(
+ rrd,
+ "--step", "300",
+ "DS:a:GAUGE:600:U:U",
+ "DS:b:GAUGE:600:U:U",
+ "RRA:AVERAGE:0.5:1:300")
+
+=head1 DESCRIPTION
+
+The B<rrdtool> functions are directly callable via the Ruby programming
+language. This wrapper implementation has been written from the scratch
+(without SWIG)
+
+The API's simply expects string parameters to the functions. Please refer
+to the other B<rrdtool> documentation for functions and valid arguments.
+
+=head1 EXAMPLE
+
+ $: << '/path/to/rrdtool/lib/ruby/1.8/i386-linux'
+ require "RRD"
+
+ name = "test"
+ rrd = "#{name}.rrd"
+ start = Time.now.to_i
+
+ RRD.create(
+ rrd,
+ "--start", "#{start - 1}",
+ "--step", "300",
+ "DS:a:GAUGE:600:U:U",
+ "DS:b:GAUGE:600:U:U",
+ "RRA:AVERAGE:0.5:1:300")
+ puts
+
+ puts "updating #{rrd}"
+ start.to_i.step(start.to_i + 300 * 300, 300) { |i|
+ RRD.update(rrd, "#{i}:#{rand(100)}:#{Math.sin(i / 800) * 50 + 50}")
+ }
+ puts
+
+ puts "fetching data from #{rrd}"
+ (fstart, fend, data, step) = RRD.fetch(rrd, "--start", start.to_s, "--end",
+ (start + 300 * 300).to_s, "AVERAGE")
+ puts "got #{data.length} data points from #{fstart} to #{fend}"
+ puts
+
+ puts "generating graph #{name}.png"
+ RRD.graph(
+ "#{name}.png",
+ "--title", " RubyRRD Demo",
+ "--start", "#{start+3600}",
+ "--end", "start + 1000 min",
+ "--interlace",
+ "--imgformat", "PNG",
+ "--width=450",
+ "DEF:a=#{rrd}:a:AVERAGE",
+ "DEF:b=#{rrd}:b:AVERAGE",
+ "CDEF:line=TIME,2400,%,300,LT,a,UNKN,IF",
+ "AREA:b#00b6e4:beta",
+ "AREA:line#0022e9:alpha",
+ "LINE3:line#ff0000")
+ puts
+
+If you use the B<--ruby-site-install> configure option you can drop the $:
+line since the rrdtool module will be found automatically.
+
+If rrdtool runs into trouble, it will throw an exception which you might
+want to catch.
+
+=head1 SEE ALSO
+
+rrdcreate, rrdupdate, rrdgraph, rrddump, rrdfetch, rrdtune, rrdlast,
+rrdxport, rrdinfo
+
+=head1 AUTHOR
+
+Loïs Lherbier E<lt>lois.lherbier@covadis.chE<gt>
+
+Miles Egan E<lt>miles@caddr.comE<gt>
diff --git a/program/doc/rrdthreads.pod b/program/doc/rrdthreads.pod
--- /dev/null
@@ -0,0 +1,144 @@
+=head1 NAME
+
+rrdthreads - Provisions for linking the RRD library to use in multi-threaded programs
+
+=head1 SYNOPSIS
+
+Using librrd in multi-threaded programs requires some extra
+precautions, as the RRD library in its original form was not
+thread-safe at all. This document describes requirements and pitfalls
+on the way to use the multi-threaded version of librrd in your own
+programs. It also gives hints for future RRD development to keep the
+library thread-safe.
+
+Currently only some RRD operations are implemented in a thread-safe
+way. They all end in the usual "C<_r>" suffix.
+
+=head1 DESCRIPTION
+
+In order to use librrd in multi-threaded programs you must:
+
+=over
+
+=item *
+
+Link with F<librrd_th> instead of F<librrd> (use C<-lrrd_th> when
+linking)
+
+=item *
+
+Use the "C<_r>" functions instead of the normal API-functions
+
+=item *
+
+Do not use any at-style time specifications. Parsing of such time
+specifications is terribly non-thread-safe.
+
+=item *
+
+Never use non *C<_r> functions unless it is explicitly documented that
+the function is tread-safe.
+
+=item *
+
+Every thread SHOULD call C<rrd_get_context()> before its first call to
+any C<librrd_th> function in order to set up thread specific data. This
+is not strictly required, but it is the only way to test if memory
+allocation can be done by this function. Otherwise the program may die
+with a SIGSEGV in a low-memory situation.
+
+=item *
+
+Always call C<rrd_error_clear()> before any call to the
+library. Otherwise the call might fail due to some earlier error.
+
+=back
+
+=head2 NOTES FOR RRD CONTRIBUTORS
+
+Some precautions must be followed when developing RRD from now on:
+
+=over
+
+=item *
+
+Only use thread-safe functions in library code. Many often used libc
+functions aren't thread-safe. Take care in the following
+situations or when using the following library functions:
+
+=over
+
+=item *
+
+Direct calls to C<strerror()> must be avoided: use C<rrd_strerror()>
+instead, it provides a per-thread error message.
+
+=item *
+
+The C<getpw*>, C<getgr*>, C<gethost*> function families (and some more
+C<get*> functions) are not thread-safe: use the *C<_r> variants
+
+=item *
+
+Time functions: C<asctime>, C<ctime>, C<gmtime>, C<localtime>: use
+*C<_r> variants
+
+=item *
+
+C<strtok>: use C<strtok_r>
+
+=item *
+
+C<tmpnam>: use C<tmpnam_r>
+
+=item *
+
+Many others (lookup documentation)
+
+=back
+
+=item *
+
+A header file named F<rrd_is_thread_safe.h> is provided
+that works with the GNU C-preprocessor to "poison" some of the most
+common non-thread-safe functions using the C<#pragma GCC poison>
+directive. Just include this header in source files you want to keep
+thread-safe.
+
+=item *
+
+Do not introduce global variables!
+
+If you really, really have to use a global variable you may add a new
+field to the C<rrd_context> structure and modify F<rrd_error.c>,
+F<rrd_thread_safe.c> and F<rrd_non_thread_safe.c>
+
+=item *
+
+Do not use C<getopt> or C<getopt_long> in *C<_r> (neither directly nor
+indirectly).
+
+C<getopt> uses global variables and behaves badly in a multi-threaded
+application when called concurrently. Instead provide a *_r function
+taking all options as function parameters. You may provide argc and
+**argv arguments for variable length argument lists. See
+C<rrd_update_r> as an example.
+
+=item *
+
+Do not use the C<rrd_parsetime> function!
+
+It uses lots of global variables. You may use it in functions not designed
+to be thread-safe, like in functions wrapping the C<_r> version of some
+operation (e.g., C<rrd_create>, but not in C<rrd_create_r>)
+
+=back
+
+=head2 CURRENTLY IMPLEMENTED THREAD SAFE FUNCTIONS
+
+Currently there exist thread-safe variants of C<rrd_update>,
+C<rrd_create>, C<rrd_dump>, C<rrd_info>, C<rrd_last>, and C<rrd_fetch>.
+
+=head1 AUTHOR
+
+Peter Stamfest E<lt>peter@stamfest.atE<gt>
diff --git a/program/doc/rrdtool-dump.dtd b/program/doc/rrdtool-dump.dtd
--- /dev/null
@@ -0,0 +1,39 @@
+<!-- rrdtool-dump.dtd -->
+<!-- wolfgang{dot}schrimm{at}urz{dot}uni-heidelberg{dot}de -->
+
+<!-- root element -->
+<!ELEMENT rrd (version, step, lastupdate, ds+, rra+)>
+
+<!-- rrd's children -->
+<!ELEMENT version (#PCDATA)>
+<!ELEMENT step (#PCDATA)>
+<!ELEMENT lastupdate (#PCDATA)>
+<!-- There are two different elements with the same name -->
+<!-- /rrd/ds and /rrd/rra/cdp_prep/ds -->
+<!ELEMENT ds ((name, type, minimal_heartbeat, min, max, last_ds, value,
+unknown_sec)|(value, unknown_datapoints))>
+<!ELEMENT rra (cf, pdp_per_row, xff, cdp_prep, database)>
+
+<!-- ds's children -->
+<!ELEMENT name (#PCDATA)>
+<!ELEMENT type (#PCDATA)>
+<!ELEMENT minimal_heartbeat (#PCDATA)>
+<!ELEMENT min (#PCDATA)>
+<!ELEMENT max (#PCDATA)>
+<!ELEMENT last_ds (#PCDATA)>
+<!ELEMENT unknown_sec (#PCDATA)>
+<!ELEMENT unknown_datapoints (#PCDATA)>
+<!-- There are two different elements with the same name -->
+<!-- /rrd/ds/value and /rrd/rra/cdp_prep/ds/value -->
+<!ELEMENT value (#PCDATA)>
+
+<!-- rra's children -->
+<!ELEMENT cf (#PCDATA)>
+<!ELEMENT pdp_per_row (#PCDATA)>
+<!ELEMENT xff (#PCDATA)>
+<!ELEMENT cdp_prep (ds+)>
+<!ELEMENT database (row+)>
+
+<!-- database's children -->
+<!ELEMENT row (v+)>
+<!ELEMENT v (#PCDATA)>
diff --git a/program/doc/rrdtool-xport.dtd b/program/doc/rrdtool-xport.dtd
--- /dev/null
@@ -0,0 +1,32 @@
+<!-- rrdtool-xport.dtd -->
+<!-- the attributes of the row and the t elements are used -->
+<!-- in the examples/shared-demo.pl, but not in the output -->
+<!-- of the native xport command. -->
+<!-- wolfgang{dot}schrimm{at}urz{dot}uni-heidelberg{dot}de -->
+
+<!-- root element -->
+<!ELEMENT xport (meta, data)>
+
+<!-- root's children -->
+<!ELEMENT meta (start, step, end, rows, columns, legend)>
+<!ELEMENT data (row+)>
+
+<!-- meta's children -->
+<!ELEMENT start (#PCDATA)>
+<!ELEMENT step (#PCDATA)>
+<!ELEMENT end (#PCDATA)>
+<!ELEMENT rows (#PCDATA)>
+<!ELEMENT columns (#PCDATA)>
+<!ELEMENT legend (entry+)>
+
+<!-- legend's children -->
+<!ELEMENT entry (#PCDATA)>
+
+<!-- data's children -->
+<!ELEMENT row (t, v+)>
+<!ATTLIST row id CDATA #IMPLIED>
+
+<!-- row's children -->
+<!ELEMENT t (#PCDATA)>
+<!ATTLIST t is CDATA #IMPLIED>
+<!ELEMENT v (#PCDATA)>
diff --git a/program/doc/rrdtool.pod b/program/doc/rrdtool.pod
--- /dev/null
+++ b/program/doc/rrdtool.pod
@@ -0,0 +1,312 @@
+=head1 NAME
+
+rrdtool - Round Robin Database Tool
+
+=head1 SYNOPSIS
+
+B<rrdtool> B<-> [workdir]| I<function>
+
+=head1 DESCRIPTION
+
+=head2 OVERVIEW
+
+It is pretty easy to gather status information from all sorts of
+things, ranging from the temperature in your office to the number of
+octets which have passed through the FDDI interface of your
+router. But it is not so trivial to store this data in an efficient and
+systematic manner. This is where B<RRDtool> comes in handy. It lets you
+I<log and analyze> the data you gather from all kinds of data-sources
+(B<DS>). The data analysis part of RRDtool is based on the ability to
+quickly generate graphical representations of the data values
+collected over a definable time period.
+
+In this man page you will find general information on the design and
+functionality of the Round Robin Database Tool (RRDtool). For a more
+detailed description of how to use the individual functions of
+B<RRDtool> check the corresponding man page.
+
+For an introduction to the usage of RRDtool make sure you consult the
+L<rrdtutorial>.
+
+=head2 FUNCTIONS
+
+While the man pages talk of command line switches you have to set in
+order to make B<RRDtool> work it is important to note that
+B<RRDtool> can be remotely controlled through a set of pipes. This
+saves a considerable amount of startup time when you plan to make
+B<RRDtool> do a lot of things quickly. Check the section on L<Remote_Control>
+further down. There is also a number of language bindings
+for RRDtool which allow you to use it directly from perl, python, tcl,
+php, etc.
+
+=over 8
+
+=item B<create>
+
+Set up a new Round Robin Database (RRD). Check L<rrdcreate>.
+
+=item B<update>
+
+Store new data values into an RRD. Check L<rrdupdate>.
+
+=item B<updatev>
+
+Operationally equivalent to B<update> except for output. Check L<rrdupdate>.
+
+=item B<graph>
+
+Create a graph from data stored in one or several RRDs. Apart from
+generating graphs, data can also be extracted to stdout. Check L<rrdgraph>.
+
+=item B<dump>
+
+Dump the contents of an RRD in plain ASCII. In connection with restore
+you can use this to move an RRD from one computer architecture to
+another. Check L<rrddump>.
+
+=item B<restore>
+
+Restore an RRD in XML format to a binary RRD. Check L<rrdrestore>
+
+=item B<fetch>
+
+Get data for a certain time period from a RRD. The graph function
+uses fetch to retrieve its data from an RRD. Check L<rrdfetch>.
+
+=item B<tune>
+
+Alter setup of an RRD. Check L<rrdtune>.
+
+=item B<last>
+
+Find the last update time of an RRD. Check L<rrdlast>.
+
+=item B<info>
+
+Get information about an RRD. Check L<rrdinfo>.
+
+=item B<rrdresize>
+
+Change the size of individual RRAs. This is dangerous! Check L<rrdresize>.
+
+=item B<xport>
+
+Export data retrieved from one or several RRDs. Check L<rrdxport>
+
+=item B<rrdcgi>
+
+This is a standalone tool for producing RRD graphs on the fly. Check
+L<rrdcgi>.
+
+=back
+
+=head2 HOW DOES RRDTOOL WORK?
+
+=over 8
+
+=item Data Acquisition
+
+When monitoring the state of a system, it is convenient to have the
+data available at a constant time interval. Unfortunately, you may not
+always be able to fetch data at exactly the time you want
+to. Therefore B<RRDtool> lets you update the logfile at any time you
+want. It will automatically interpolate the value of the data-source
+(B<DS>) at the latest official time-slot (intervall) and write this
+interpolated value to the log. The original value you have supplied is
+stored as well and is also taken into account when interpolating the
+next log entry.
+
+=item Consolidation
+
+You may log data at a 1 minute interval, but you might also be
+interested to know the development of the data over the last year. You
+could do this by simply storing the data in 1 minute intervals for the
+whole year. While this would take considerable disk space it would
+also take a lot of time to analyze the data when you wanted to create
+a graph covering the whole year. B<RRDtool> offers a solution to this
+problem through its data consolidation feature. When setting up an
+Round Robin Database (B<RRD>), you can define at which interval this
+consolidation should occur, and what consolidation function (B<CF>)
+(average, minimum, maximum, total, last) should be used to build the
+consolidated values (see rrdcreate). You can define any number of
+different consolidation setups within one B<RRD>. They will all be
+maintained on the fly when new data is loaded into the B<RRD>.
+
+=item Round Robin Archives
+
+Data values of the same consolidation setup are stored into Round
+Robin Archives (B<RRA>). This is a very efficient manner to store data
+for a certain amount of time, while using a known and constant amount
+of storage space.
+
+It works like this: If you want to store 1'000 values in 5 minute
+interval, B<RRDtool> will allocate space for 1'000 data values and a
+header area. In the header it will store a pointer telling which slots
+(value) in the storage area was last written to. New values are
+written to the Round Robin Archive in, you guessed it, a round robin
+manner. This automatically limits the history to the last 1'000 values
+(in our example). Because you can define several B<RRA>s within a
+single B<RRD>, you can setup another one, for storing 750 data values
+at a 2 hour interval, for example, and thus keep a log for the last
+two months at a lower resolution.
+
+The use of B<RRA>s guarantees that the B<RRD> does not grow over
+time and that old data is automatically eliminated. By using the
+consolidation feature, you can still keep data for a very long time,
+while gradually reducing the resolution of the data along the time
+axis.
+
+Using different consolidation functions (B<CF>) allows you to store
+exactly the type of information that actually interests you: the maximum
+one minute traffic on the LAN, the minimum temperature of your wine cellar,
+the total minutes of down time, etc.
+
+=item Unknown Data
+
+As mentioned earlier, the B<RRD> stores data at a constant
+interval. Sometimes it may happen that no new data is available when a
+value has to be written to the B<RRD>. Data acquisition may not be
+possible for one reason or other. With B<RRDtool> you can handle these
+situations by storing an I<*UNKNOWN*> value into the database. The
+value 'I<*UNKNOWN*>' is supported through all the functions of the
+tool. When consolidating a data set, the amount of I<*UNKNOWN*> data
+values is accounted for and when a new consolidated value is ready to
+be written to its Round Robin Archive (B<RRA>), a validity check is
+performed to make sure that the percentage of unknown values in the
+data point is above a configurable level. If not, an I<*UNKNOWN*> value
+will be written to the B<RRA>.
+
+=item Graphing
+
+B<RRDtool> allows you to generate reports in numerical and
+graphical form based on the data stored in one or several
+B<RRD>s. The graphing feature is fully configurable. Size, color and
+contents of the graph can be defined freely. Check L<rrdgraph>
+for more information on this.
+
+=item Aberrant Behavior Detection
+
+by Jake Brutlag
+
+B<RRDtool> provides the building blocks for near real-time aberrant
+behavior detection. These components include:
+
+=over
+
+=item *
+
+An algorithm for predicting the value of a time series one time step
+into the future.
+
+=item *
+
+A measure of deviation between predicted and observed values.
+
+=item *
+
+A mechanism to decide if and when an observed value or sequence of
+observed values is I<too deviant> from the predicted value(s).
+
+=back
+
+Here is a brief explanation of these components:
+
+The Holt-Winters time series forecasting algorithm is an on-line (or
+incremental) algorithm that adaptively predicts future observations in
+a time series. Its forecast is the sum of three components: a baseline
+(or intercept), a linear trend over time (or slope), and a seasonal
+coefficient (a periodic effect, such as a daily cycle). There is one
+seasonal coefficient for each time point in the period (cycle). After
+a value is observed, each of these components is updated via
+exponential smoothing. This means that the algorithm "learns" from
+past values and uses them to predict the future. The rate of
+adaptation is governed by 3 parameters, alpha (intercept), beta
+(slope), and gamma (seasonal). The prediction can also be viewed as a
+smoothed value for the time series.
+
+The measure of deviation is a seasonal weighted absolute
+deviation. The term I<seasonal> means deviation is measured separately
+for each time point in the seasonal cycle. As with Holt-Winters
+forecasting, deviation is predicted using the measure computed from
+past values (but only at that point in the seasonal cycle). After the
+value is observed, the algorithm learns from the observed value via
+exponential smoothing. Confidence bands for the observed time series
+are generated by scaling the sequence of predicted deviation values
+(we usually think of the sequence as a continuous line rather than a
+set of discrete points).
+
+Aberrant behavior (a potential failure) is reported whenever the
+number of times the observed value violates the confidence bands meets
+or exceeds a specified threshold within a specified temporal window
+(e.g. 5 violations during the past 45 minutes with a value observed
+every 5 minutes).
+
+This functionality is embedded in a set of related B<RRAs>. In
+particular, a FAILURES B<RRA> logs potential failures. With these data
+you could, for example, use a front-end application to B<RRDtool> to
+initiate real-time alerts.
+
+For a detailed description on how to set this up, see L<rrdcreate>.
+
+=back
+
+=head2 REMOTE CONTROL
+
+When you start B<RRDtool> with the command line option 'B<->' it waits
+for input via standard input (STDIN). With this feature you can
+improve performance by attaching B<RRDtool> to another process (MRTG
+is one example) through a set of pipes. Over these pipes B<RRDtool>
+accepts the same arguments as on the command line and some special
+commands like B<quit, cd, mkdir> and B<ls>. For detailed help on the
+server commands type:
+
+ rrdtool help cd|mkdir|pwd|ls|quit
+
+When a command is completed, RRDtool will print the string 'C<OK>',
+followed by timing information of the form B<u:>I<usertime>
+B<s:>I<systemtime>. Both values are the running totals of seconds since
+RRDtool was started. If an error occurs, a line of the form 'C<ERROR:>
+I<Description of error>' will be printed instead. B<RRDtool> will not abort,
+unless something realy serious happens. If
+a B<workdir> is specified and the UID is 0, RRDtool will do a chroot to that
+workdir. If the UID is not 0, RRDtool only changes the current directory to
+B<workdir>.
+
+=head2 RRD Server
+
+If you want to create a RRD-Server, you must choose a TCP/IP Service
+number and add them to I</etc/services> like this:
+
+ rrdsrv 13900/tcp # RRD server
+
+Attention: the TCP port 13900 isn't officially registered for
+rrdsrv. You can use any unused port in your services file, but the
+server and the client system must use the same port, of course.
+
+With this configuration you can add RRDtool as meta-server to
+I</etc/inetd.conf>. For example:
+
+ rrdsrv stream tcp nowait root /opt/rrd/bin/rrdtool rrdtool - /var/rrd
+
+Don't forget to create the database directory /var/rrd and
+reinitialize your inetd.
+
+If all was setup correctly, you can access the server with perl
+sockets, tools like netcat, or in a quick interactive test by using
+'telnet localhost rrdsrv'.
+
+B<NOTE:> that there is no authentication with this feature! Do not setup
+such a port unless you are sure what you are doing.
+
+=head1 SEE ALSO
+
+rrdcreate, rrdupdate, rrdgraph, rrddump, rrdfetch, rrdtune, rrdlast, rrdxport
+
+=head1 BUGS
+
+Bugs? Features!
+
+=head1 AUTHOR
+
+Tobias Oetiker <tobi@oetiker.ch>
+
diff --git a/program/doc/rrdtune.pod b/program/doc/rrdtune.pod
--- /dev/null
+++ b/program/doc/rrdtune.pod
@@ -0,0 +1,183 @@
+=head1 NAME
+
+rrdtune - Modify some basic properties of a Round Robin Database
+
+=head1 SYNOPSIS
+
+B<rrdtool> B<tune> I<filename>
+S<[B<--heartbeat>|B<-h> I<ds-name>:I<heartbeat>]>
+S<[B<--minimum>|B<-i> I<ds-name>:I<min>]>
+S<[B<--maximum>|B<-a> I<ds-name>:I<max>]>
+S<[B<--data-source-type>|B<-d> I<ds-name>:I<DST>]>
+S<[B<--data-source-rename>|B<-r> I<old-name>:I<new-name>]>
+S<[B<--deltapos> I<scale-value>]>
+S<[B<--deltaneg> I<scale-value>]>
+S<[B<--failure-threshold> I<failure-threshold>]>
+S<[B<--window-length> I<window-length>]>
+S<[B<--alpha> I<adaption-parameter>]>
+S<[B<--beta> I<adaption-parameter>]>
+S<[B<--gamma> I<adaption-parameter>]>
+S<[B<--gamma-deviation> I<adaption-parameter>]>
+S<[B<--smoothing-window> I<fraction-of-season>]>
+S<[B<--smoothing-window-deviation> I<fraction-of-season>]>
+S<[B<--aberrant-reset> I<ds-name>]>
+
+=head1 DESCRIPTION
+
+The tune option allows you to alter some of the basic configuration
+values stored in the header area of a Round Robin Database (B<RRD>).
+
+One application of the B<tune> function is to relax the
+validation rules on an B<RRD>. This allows to fill a new B<RRD> with
+data available in larger intervals than what you would normally want
+to permit. Be very careful with tune operations for COMPUTE data sources.
+Setting the I<min>, I<max>, and I<heartbeat> for a COMPUTE data source
+without changing the data source type to a non-COMPUTE B<DST> WILL corrupt
+the data source header in the B<RRD>.
+
+A second application of the B<tune> function is to set or alter parameters
+used by the specialized function B<RRAs> for aberrant behavior detection.
+
+=over 8
+
+=item I<filename>
+
+The name of the B<RRD> you want to tune.
+
+=item S<B<--heartbeat>|B<-h> I<ds-name>:I<heartbeat>>
+
+modify the I<heartbeat> of a data source. By setting this to a high
+value the RRD will accept things like one value per day.
+
+=item S<B<--minimum>|B<-i> I<ds-name>:I<min>>
+
+alter the minimum value acceptable as input from the data source.
+Setting I<min> to 'U' will disable this limit.
+
+=item S<B<--maximum>|B<-a> I<ds-name>:I<max>>
+
+alter the maximum value acceptable as input from the data source.
+Setting I<max> to 'U' will disable this limit.
+
+=item S<B<--data-source-type>|B<-d> I<ds-name>:I<DST>>
+
+alter the type B<DST> of a data source.
+
+=item S<B<--data-source-rename>|B<-r> I<old-name>:I<new-name>>
+
+rename a data source.
+
+=item S<B<--deltapos> I<scale-value>>
+
+Alter the deviation scaling factor for the upper bound of the
+confidence band used internally to calculate violations for the
+FAILURES B<RRA>. The default value is 2. Note that this parameter is
+not related to graphing confidence bounds which must be specified as a
+CDEF argument to generate a graph with confidence bounds. The graph
+scale factor need not to agree with the value used internally by the
+FAILURES B<RRA>.
+
+=item S<B<--deltaneg> I<scale-value>>
+
+Alter the deviation scaling factor for the lower bound of the confidence band
+used internally to calculate violations for the FAILURES B<RRA>. The default
+value is 2. As with B<--deltapos>, this argument is unrelated to the scale
+factor chosen when graphing confidence bounds.
+
+=item S<B<--failure-threshold> I<failure-threshold>>
+
+Alter the number of confidence bound violations that constitute a failure for
+purposes of the FAILURES B<RRA>. This must be an integer less than or equal to
+the window length of the FAILURES B<RRA>. This restriction is not verified by
+the tune option, so one can reset failure-threshold and window-length
+simultaneously. Setting this option will reset the count of violations to 0.
+
+=item S<B<--window-length> I<window-length>>
+
+Alter the number of time points in the temporal window for determining
+failures. This must be an integer greater than or equal to the window
+length of the FAILURES B<RRA> and less than or equal to 28. Setting
+this option will reset the count of violations to 0.
+
+=item S<B<--alpha> I<adaption-parameter>>
+
+Alter the intercept adaptation parameter for the Holt-Winters
+forecasting algorithm. This parameter must be between 0 and 1.
+
+=item S<B<--beta> I<adaption-parameter>>
+
+Alter the slope adaptation parameter for the Holt-Winters forecasting
+algorithm. This parameter must be between 0 and 1.
+
+=item S<B<--gamma> I<adaption-parameter>>
+
+Alter the seasonal coefficient adaptation parameter for the SEASONAL
+B<RRA>. This parameter must be between 0 and 1.
+
+=item S<B<--gamma-deviation> I<adaption-parameter>>
+
+Alter the seasonal deviation adaptation parameter for the DEVSEASONAL
+B<RRA>. This parameter must be between 0 and 1.
+
+=item S<B<--smoothing-window> I<fraction-of-season>>
+
+Alter the size of the smoothing window for the SEASONAL B<RRA>. This must
+be between 0 and 1.
+
+=item S<B<--smoothing-window-deviation> I<fraction-of-season>>
+
+Alter the size of the smoothing window for the DEVSEASONAL B<RRA>. This must
+be between 0 and 1.
+
+=item S<B<--aberrant-reset> I<ds-name>>
+
+This option causes the aberrant behavior detection algorithm to reset
+for the specified data source; that is, forget all it is has learnt so far.
+Specifically, for the HWPREDICT or MHWPREDICT B<RRA>, it sets the intercept and
+slope coefficients to unknown. For the SEASONAL B<RRA>, it sets all seasonal
+coefficients to unknown. For the DEVSEASONAL B<RRA>, it sets all seasonal
+deviation coefficients to unknown. For the FAILURES B<RRA>, it erases the
+violation history. Note that reset does not erase past predictions
+(the values of the HWPREDICT or MHWPREDICT B<RRA>), predicted deviations (the
+values of the DEVPREDICT B<RRA>), or failure history (the values of the
+FAILURES B<RRA>). This option will function even if not all the listed
+B<RRAs> are present.
+
+Due to the implementation of this option, there is an indirect impact on
+other data sources in the RRD. A smoothing algorithm is applied to
+SEASONAL and DEVSEASONAL values on a periodic basis. During bootstrap
+initialization this smoothing is deferred. For efficiency, the implementation
+of smoothing is not data source specific. This means that utilizing
+reset for one data source will delay running the smoothing algorithm
+for all data sources in the file. This is unlikely to have serious
+consequences, unless the data being collected for the non-reset data sources
+is unusually volatile during the reinitialization period of the reset
+data source.
+
+Use of this tuning option is advised when the behavior of the data source
+time series changes in a drastic and permanent manner.
+
+=back
+
+=head1 EXAMPLE 1
+
+C<rrdtool tune data.rrd -h in:100000 -h out:100000 -h through:100000>
+
+Set the minimum required heartbeat for data sources 'in', 'out'
+and 'through' to 10'000 seconds which is a little over one day in data.rrd.
+This would allow to feed old data from MRTG-2.0 right into
+RRDtool without generating *UNKNOWN* entries.
+
+=head1 EXAMPLE 2
+
+C<rrdtool tune monitor.rrd --window-length 5 --failure-threshold 3>
+
+If the FAILURES B<RRA> is implicitly created, the default
+window-length is 9 and the default failure-threshold is 7. This
+command now defines a failure as 3 or more violations in a temporal
+window of 5 time points.
+
+=head1 AUTHOR
+
+Tobias Oetiker <tobi@oetiker.ch>
+
diff --git a/program/doc/rrdtutorial.es.pod b/program/doc/rrdtutorial.es.pod
--- /dev/null
@@ -0,0 +1,1183 @@
+=head1 NAME
+
+rrdtutorial - Tutorial sobre RRDtool por Alex van den Bogaerdt
+(Traducido al castellano por Jesús Couto Fandiño)
+
+=for html <div align="right">Versión <a href="rrdtutorial.es.pdf">PDF</a></div>
+
+=for html <div align="right"><a href="rrdtutorial.html">English</a></div>
+
+=head1 DESCRIPTION / DESCRIPCIÓN
+
+RRDtool es un programa escrito por Tobias Oetiker con la
+colaboración de muchas personas en diversas partes del mundo. Alex van
+den Bogaerdt escribió este documento para ayudarte a entender que es
+RRDtool y que es lo que puede hacer por ti.
+
+La documentación que viene con RRDtool puede ser demasiado técnica
+para algunos. Este tutorial existe para ayudarte a entender las
+funciones básicas de RRdtool. Debe servirte de preparación para leer la
+documentación, y además explica algunas ideas generales sobre
+estadÃstica, con un enfoque particular hacia las redes.
+
+=head1 TUTORIAL
+
+=head2 Importante
+
+¡Por favor, no te adelantes en la lectura de este documento! Esta
+primera parte explica los fundamentos básicos. Puede ser aburrida,
+pero si te saltas los fundamentos, los ejemplos no te van a tener
+mucho sentido.
+
+=head2 ¿Qué es RRDtool?
+
+RRDtool significa "herramienta de bases de datos en round robin".
+"Round robin" es una técnica que implica un número fijo de datos, y un
+apuntador al elemento más reciente. Piensa en un circulo con unos
+cuantos puntos dibujados alrededor del borde; estos puntos son los
+lugares donde se pueden guardar los datos. Dibuja ahora una flecha
+desde el centro del cÃrculo a uno de los puntos; este es el apuntador.
+Cuando se lee o escribe el dato actualmente apuntado, la flecha se
+mueve al próximo elemento. Como estamos en un cÃrculo, no hay ni
+principio ni fin; siempre puedes seguir, eternamente. Al cabo de un
+tiempo ya se habrán usado todas las posiciones disponibles y el
+proceso empieza a reutilizar las antiguas. De esta forma, la base de datos
+no crece en tamaño y, por lo tanto, no requiere ningún mantenimiento.
+RRDtool trabaja con estas bases de datos en "round-robin", guardando y
+recuperando datos de ellas.
+
+=head2 ¿Qué datos pueden guardarse en una RRD?
+
+Lo que se te ocurra. Debes poder medir algún valor dado en distintos
+momentos en el tiempo y proveer a RRDtool de estos valores. Si puedes
+hacer esto, RRDtool puede guardar los datos. Los valores tienen que
+ser numéricos, pero no necesariamente enteros, como en MRTG.
+
+Muchos ejemplos mencionan SNMP, que es el acrónimo de
+"Simple Network Management Protocol" (Protocolo Simple de
+Administración de Redes). Lo de "simple" se refiere al protocolo - no
+se supone que sea fácil administrar o monitorizar una red. Cuando
+hayas terminado con este documento, deberás saber lo suficiente para
+entender cuando oigas a otros hablar sobre
+SNMP. Por ahora, simplemente considera a
+SNMP como una forma de preguntarle a los dispositivos
+por los valores de ciertos contadores que mantienen. Son estos valores
+de estos contadores los que vamos a almacenar en la RRD.
+
+=head2 ¿Qué puedo hacer con esta herramienta?
+
+RRDtool se deriva de MRTG (Multi Router
+Traffic Grapher, Graficador De Tráfico de Múltiples Enrutadores).
+MRTG empezó como un pequeño script para poder
+graficar el uso de una conexión a la Internet. Luego evolucionó,
+permitiendo graficar otras fuentes de datos, como temperatura,
+velocidad, voltajes, cantidad de páginas impresas, etc... Lo más
+probable es que empieces a usar RRDtool para guardar y procesar datos
+conseguidos a través de SNMP, y que los datos
+sean el número de bytes (o bits) transferidos desde y hacia una red u
+ordenador. RRDtool te permite crear una base de datos, guardar los
+datos en ellas, recuperarlos y crear gráficos en formato SVG o PNG,
+para mostrarlos en un navegador web. Esas imágenes dependen de los
+datos que hayas guardado y pueden, por ejemplo, ser un sumario del
+promedio de uso de la red, o los picos de tráfico que ocurrieron.
+También lo puedes usar para mostrar el nivel de las mareas, la
+radiación solar, el consumo de electricidad, el número de visitantes
+en una exposición en un momento dado, los niveles de ruido cerca del
+aeropuerto, la temperatura en tu lugar de vacaciones favorito, o en
+la nevera, o cualquier otra cosa que te puedas imaginar, mientras
+tengas algún sensor con el cual medir los datos y seas capaz de
+pasarle los números a RRDtool.
+
+=head2 ¿Y si aún tengo problemas después de leer este documento?
+
+Lo primero, ¡léelo otra vez!. Puede que te hayas perdido de algo.
+Si no puedes compilar el código fuente y usas un sistema operativo
+bastante común, casi seguro que no es la culpa de RRDtool.
+Probablemente consigas versiones pre-compiladas por la Internet. Si
+provienen de una fuente confiable, úsalas. Si, por otro lado, el
+programa funciona, pero no te da los resultados que tu esperabas,
+puede ser un problema con la configuración; revÃsala y
+compárala con los ejemplos.
+
+Hay una lista de correo electrónico y una archivo de la misma. Lee
+la lista durante unas cuantas semanas, y busca en el archivo. Es
+descortés hacer una pregunta sin haber revisado el archivo; ¡puede que
+tu problema ya haya sido resuelto antes! Normalmente ocurre asà en todas
+las listas de correo, no sólo esta. Examina la documentación que vino
+con RRDtool para ver donde está el archivo y como usarlo.
+
+Te sugiero que te tomes un momento y te subscribas a la lista ahora
+mismo, enviando un mensaje a rrd-users-request@lists.oetiker.ch
+con tÃtulo C<subscribe>. Si eventualmente deseas salirte de la lista,
+envÃa otro correo a la misma dirección, con tÃtulo C<unsubscribe>.
+
+=head2 ¿Cómo me vas a ayudar?
+
+Dándote descripciones y ejemplos detallados. Asumimos que el seguir
+las instrucciones en el orden en que se presentan aquà te dará
+suficiente conocimiento de RRDtool como para que experimentes por tu
+cuenta. Si no funciona a la primera, puede que te hallas saltado algo;
+siguiendo los ejemplos obtendrás algo de experiencia práctica y, lo
+que es más importante, un poco de información sobre como funciona el
+programa.
+
+Necesitarás saber algo sobre números hexadecimales. Si no, empieza
+por leer "bin_dec_hex" antes de continuar.
+
+=head2 Tu primera base de datos en round-robin
+
+En mi opinión, la mejor forma de aprender algo es haciéndolo. ¿Por
+qué no empezamos ya? Vamos a crear una base de datos, poner unos cuantos
+valores en ella y extraerlos después. La salida que obtengas debe ser
+igual a la que aparece en este documento.
+
+Empezaremos con algo fácil, comparando un coche con un enrutador, o
+por decirlo de otra forma, comparando kilómetros con bits y bytes. A
+nosotros nos da lo mismo; son unos números obtenidos en un espacio de tiempo.
+
+Asumamos que tenemos un dispositivo que transfiere bytes desde y
+hacia la Internet. Este dispositivo tiene un contador que empieza en 0
+al encenderse y se incrementa con cada byte transferido. Este contador
+tiene un valor máximo; si ese valor se alcanza y se cuenta un byte
+más, el contador vuelve a empezar desde cero. Esto es exactamente lo
+mismo que pasa con muchos contadores, como el cuentakilómetros del
+coche. En muchas de las disertaciones sobre redes se habla de bits por
+segundo, asà que empezaremos por acostumbrarnos a esto. Asumamos que un
+byte son 8 bits y empecemos a pensar en bits y no en bytes. ¡El
+contador, sin embargo, sigue contando en bytes! En el mundo
+SNMP, la mayorÃa de los contadores tienen una
+longitud de 32 bits. Esto significa que pueden contar desde 0 hasta
+4294967295. Usaremos estos valores en los ejemplos. El dispositivo, cuando
+le preguntamos, retorna el valor actual del contador. Como sabemos el
+tiempo transcurrido desde la última vez que le preguntamos, sabemos
+cuantos bytes se han transferido C<***en promedio***> por
+segundo. Esto no es muy difÃcil de calcular; primero en palabras,
+luego en operaciones:
+
+=over 4
+
+=item 1.
+
+Toma el valor actual del contador y réstale el valor anterior
+
+=item 2.
+
+Haz lo mismo con la fecha
+
+=item 3.
+
+Divide el resultado del paso (1) por el resultado del paso (2).
+El resultado es la cantidad de bytes por segundo. Si lo
+multiplicas por ocho obtienes la cantidad de bits por segundo
+
+=back
+
+ bps = (contador_actual - contador_anterior) / (fecha_actual - fecha_anterior) * 8
+
+Para algunos será de ayuda traducir esto a un ejemplo automotor.
+No prueben estas velocidades en la práctica, y si lo hacen, no me
+echen la culpa por los resultados.
+
+Usaremos las siguientes abreviaturas:
+
+ M: metros
+ KM: kilómetros (= 1000 metros).
+ H: horas
+ S: segundos
+ KM/H: kilómetros por hora
+ M/S: metros por segundo
+
+
+Vas conduciendo un coche. A las 12:05, miras el contador en el
+salpicadero y ves que el coche ha recorrido 12345
+KM. A las 12:10 vuelves a mirar otra vez, y dice
+12357 KM. Quiere decir, que has recorrido 12
+KM en cinco minutos. Un cientÃfico convertirÃa
+esto en metros por segundos; esto es bastante parecido al problema de
+pasar de bytes transferidos en 5 minutos a bits por segundo.
+
+Viajamos 12 kilómetros, que son 12000 metros. Tardamos 5 minutos, o
+sea 300 segundos. Nuestra velocidad es 12000M / 300S igual a 40 M/S.
+
+También podemos calcular la velocidad en KM/H: 12 veces 5 minutos
+es una hora, asà que multiplicando los 12 KM por 12 obtenemos 144
+KM/H. No intentes esto en casa, o por donde vivo :-)
+
+Recuerda que estos números son tan sólo promedios. No hay forma de
+deducir, viendo sólo los números, si fuiste a una velocidad constante.
+Hay un ejemplo más adelante en el tutorial que explica esto.
+
+Espero que entiendas que no hay diferencia entre calcular la
+velocidad en M/S o bps; sólo la forma en que
+recogemos los datos es distinta. Inclusive, la K de kilo en este
+caso es exactamente la misma, ya que en redes k es 1000
+
+Ahora vamos a crear una base de datos en la que guardar todos estos
+interesantes valores. El método a usar para arrancar el programa puede
+variar de un sistema de operación a otro, pero asumamos que lo puedes
+resolver tu mismo en caso que se diferente en el sistema que usas.
+Asegúrate de no sobreescribir ningún archivo en tu sistema al
+ejecutarlo y escribe todo como una sola lÃnea (tuve que partirlo para
+que fuera legible), saltándote todos los caracteres '\'
+
+ rrdtool create test.rrd \
+ --start 920804400 \
+ DS:speed:COUNTER:600:U:U \
+ RRA:AVERAGE:0.5:1:24 \
+ RRA:AVERAGE:0.5:6:10
+
+(o sea, escribe: C<rrdtool create test.rrd --start 920804400 DS ...>)
+
+=head2 ¿Qué hemos creado?
+
+Hemos creado una base de datos en round robin llamada test
+(test.rrd), que empieza desde el mediodÃa del dÃa en que empecé a
+escribir este documento (7 de marzo de 1999). En ella se guarda una
+fuente de datos (DS), llamada "speed", que se
+lee de un contador. En la misma base de datos se guardan dos archivos
+en round robin (RRAs), uno promedia los datos cada vez que se leen (o
+sea, no hay nada que promediar), y mantiene 24 muestras (24 por 5
+minutos = 2 horas de muestras). El otro promedia 6 muestras (media
+hora), y guarda 10 de estos promedios (o sea, 5 horas). Las opciones
+restantes las veremos más adelante.
+
+RRDtool usa un formato de "fecha" especial que viene del mundo de
+UNIX. Estas "fechas" son el número de segundos
+que han pasado desde el primero de enero de 1970, zona UTC. Este
+número de segundos se convierte luego en la fecha local, por lo que
+varia según la franja horaria.
+
+Lo más probable es que tu no vivas en la misma parte del mundo que
+yo, por lo que tu franja horaria será diferente. En los ejemplos,
+cuando mencione horas, puede que no sean las mismas para ti; esto no
+afecta mucho los resultados, sólo tienes que corregir las horas
+mientras lees. Por ejemplo, las 12:05 para mà son las 11:05 para los
+amigos en la Gran Bretaña.
+
+Ahora tenemos que llenar nuestra base de datos con valores. Vamos a
+suponer que leÃmos estos datos:
+
+ 12:05 12345 KM
+ 12:10 12357 KM
+ 12:15 12363 KM
+ 12:20 12363 KM
+ 12:25 12363 KM
+ 12:30 12373 KM
+ 12:35 12383 KM
+ 12:40 12393 KM
+ 12:45 12399 KM
+ 12:50 12405 KM
+ 12:55 12411 KM
+ 13:00 12415 KM
+ 13:05 12420 KM
+ 13:10 12422 KM
+ 13:15 12423 KM
+
+Llenaremos la base de datos asÃ:
+
+ rrdtool update test.rrd 920804700:12345 920805000:12357 920805300:12363
+ rrdtool update test.rrd 920805600:12363 920805900:12363 920806200:12373
+ rrdtool update test.rrd 920806500:12383 920806800:12393 920807100:12399
+ rrdtool update test.rrd 920807400:12405 920807700:12411 920808000:12415
+ rrdtool update test.rrd 920808300:12420 920808600:12422 920808900:12423
+
+Lo que significa: actualiza nuestra base de datos test con los
+siguientes valores:
+
+ fecha 920804700, valor 12345
+ fecha 920805000, valor 12357
+
+ etcétera.
+
+Como ves, pueden introducirse más de un valor en la base de datos
+por ejecución del comando. Yo los agrupo de tres en tres para hacerlo
+legible, pero en realidad el máximo depende del sistema de operación.
+
+Ahora podemos recuperar los datos usando ``rrdtool fetch'':
+
+ rrdtool fetch test.rrd AVERAGE --start 920804400 --end 920809200
+
+Debes obtener esto como salida:
+
+ speed
+
+ 920804400: NaN
+ 920804700: NaN
+ 920805000: 4.0000000000e-02
+ 920805300: 2.0000000000e-02
+ 920805600: 0.0000000000e+00
+ 920805900: 0.0000000000e+00
+ 920806200: 3.3333333333e-02
+ 920806500: 3.3333333333e-02
+ 920806800: 3.3333333333e-02
+ 920807100: 2.0000000000e-02
+ 920807400: 2.0000000000e-02
+ 920807700: 2.0000000000e-02
+ 920808000: 1.3333333333e-02
+ 920808300: 1.6666666667e-02
+ 920808600: 6.6666666667e-03
+ 920808900: 3.3333333333e-03
+ 920809200: NaN
+
+Si no, hay algo mal. Probablemente tu sistema de operación muestre ``NaN''
+de otra forma; representa "Not a Number", o sea "No es un número". Si
+aparece ``U'' o ``UNKN'' o algo parecido, es lo mismo. Si hay alguna otra
+diferencia, probablemente te equivocaste al introducir algún P valor
+(asumiendo que mi tutorial está bien, por supuesto :-). En ese caso, borra
+la base de datos y prueba de nuevo.
+
+Lo que representa exactamente esta salida lo vamos más adelante en el tutorial.
+
+=head2 Hora de hacer algunos gráficos
+
+Prueba este comando:
+
+ rrdtool graph speed.png \
+ --start 920804400 --end 920808000 \
+ DEF:myspeed=test.rrd:speed:AVERAGE \
+ LINE2:myspeed#FF0000
+
+Este comando crea speed.png, un gráfico de los datos desde las
+12:00 hasta las 13:00. Contiene una definición de la variable myspeed
+y define el color como rojo. Notarás que el gráfico no comienza
+exactamente a las 12:00 sino a las 12:05, y es porque no tenemos datos
+suficientes como para calcular el promedio de velocidad antes de ese
+momento. Esto sólo ocurre en caso de que se pierdan algún muestreo, lo
+que esperamos que no debe ocurrir muy a menudo.
+
+Si ha funcionado, ¡felicitaciones!. Si no, revisa qué puede estar mal.
+
+La definición de colores se construye a partir del rojo, verde y
+azul. Especificas cuanto de cada uno de estos componentes vas a usar
+en hexadecimal: 00 significa "nada de este color" y FF significa
+"este color a máxima intensidad". El "color" blanco es la mezcla
+del rojo, verde y azul a toda intensidad:
+FFFFFF; el negro es la ausencia de todos los colores: 000000.
+
+ rojo #FF0000
+ verde #00FF00
+ azul #0000FF
+ violeta #FF00FF (mezcla de rojo y azul)
+ gris #555555 (un tercio de cada uno de los colores)
+
+El archivo PNG que acabas de crear puede
+verse con tu visor de archivos de imagen favorito. Los navegadores lo
+mostrarán usando la URL
+``file://el/camino/de/directorios/hasta/speed.png''
+
+=head2 Gráficos con un poco de matemática
+
+Cuando veas la imagen, notarás que el eje horizontal tiene unas
+etiquetas marcando las 12:10, 12:20, 12:30, 12:40 y 12:50. Los otros
+dos momentos (12:00 y 13:00) no se pueden mostrar bien por falta de datos, asà que
+el programa se los salta. El eje vertical muestra el rango de los valores que
+entramos. Introdujimos los kilómetros y luego dividimos entre 300
+segundos, por lo que obtuvimos valores bastante bajos. Para ser
+exactos, el primer valor, 12 (12357-12345), dividido entre 300 da
+0.04, lo que RRDtool muestra como ``40m'', o sea ``40/1000''. ¡La
+``m''' no tiene nada que ver con metros, kilómetros o milÃmetros!.
+RRDtool no sabe nada de unidades, el sólo trabaja con números, no con
+metros.
+
+Donde nos equivocamos fue en que debimos medir en metros. AsÃ,
+(12357000-12345000)/300 = 12000/300 = 40.
+
+Vamos a corregirlo. PodrÃamos recrear la base de datos con los
+valores correctos, pero hay una forma mejor: ¡haciendo los cálculos
+mientras creamos el archivo png!
+
+ rrdtool graph speed2.png \
+ --start 920804400 --end 920808000 \
+ --vertical-label m/s \
+ DEF:myspeed=test.rrd:speed:AVERAGE \
+ CDEF:realspeed=myspeed,1000,* \
+ LINE2:realspeed#FF0000
+
+Cuando veas esta imagen, notarás que la ``m'' ha desaparecido, y
+ahora tienes los resultados correctos. Además hemos añadido una
+etiqueta a la imagen. Apartando esto, el archivo PNG es el mismo.
+
+Las operaciones están en la sección del CDEF
+y están escritas en Notación Polaca Inversa (Reverse Polish Notation o
+``RPN''). En palabras, dice: "toma la fuente de
+datos myspeed y el numero 1000, y multiplÃcalos". No te molestes en
+meterte con RPN todavÃa, la veremos con más
+detalle más adelante. Además, puede que quieras leer mi tutorial sobre
+los CDEF y el tutorial de Steve Rader sobre RPN, pero primero terminemos con este.
+
+¡Un momento! Si podemos multiplicar los valores por mil, entonces,
+¡también deberÃa ser posible el mostrar la velocidad en kilómetros por
+hora usando los mismos datos!
+
+Para cambiar el valor que medimos en metros por segundo, calculamos
+los metros por hora (valor * 3600) y dividimos entre 1000 para sacar
+los kilómetros por hora. Todo junto hace valor * (3600/1000) == valor
+* 3.6.
+
+Como en nuestra base de datos cometimos un error guardando los
+valores en kilómetros, debemos compensar por ello, multiplicando por
+100, por lo que al aplicar esta corrección nos queda valor * 3600.
+
+Ahora vamos a crear este png, agreándole un poco más de magia...
+
+ rrdtool graph speed3.png \
+ --start 920804400 --end 920808000 \
+ --vertical-label km/h \
+ DEF:myspeed=test.rrd:speed:AVERAGE \
+ "CDEF:kmh=myspeed,3600,*" \
+ CDEF:fast=kmh,100,GT,kmh,0,IF \
+ CDEF:good=kmh,100,GT,0,kmh,IF \
+ HRULE:100#0000FF:"Maximum allowed" \
+ AREA:good#00FF00:"Good speed" \
+ AREA:fast#FF0000:"Too fast"
+
+Esto luce mucho mejor. La velocidad en KM/H,
+y además tenemos una lÃnea extra mostrando la velocidad máxima
+permitida (en el camino por donde conduzco). También le cambie los
+colores de la velocidad, y ahora paso de ser una lÃnea a un área.
+
+Los cálculos son más complejos ahora. Para calcular la velocidad "aceptable":
+
+ Verifica si la velocidad en kmh es mayor que 100 ( kmh,100 ) GT
+ Si es asÃ, retorna 0, si no, retorna la velocidad ((( kmh,100 ) GT ), 0, kmh) IF
+
+Para calcular la parte de velocidad "excesiva":
+
+ Verifica si la velocidad en kmh es mayor que 100 ( kmh,100 ) GT
+ Si es asÃ, retorna la velocidad, si no, retorna 0 ((( kmh,100) GT ), kmh, 0) IF
+
+=head2 Magia gráfica
+
+Me gusta creer que virtualmente no hay limites para lo que RRDtool puede
+hacer con los datos. No voy a explicarlo en detalle, pero mira este PNG:
+
+ rrdtool graph speed4.png \
+ --start 920804400 --end 920808000 \
+ --vertical-label km/h \
+ DEF:myspeed=test.rrd:speed:AVERAGE \
+ "CDEF:kmh=myspeed,3600,*" \
+ CDEF:fast=kmh,100,GT,100,0,IF \
+ CDEF:over=kmh,100,GT,kmh,100,-,0,IF \
+ CDEF:good=kmh,100,GT,0,kmh,IF \
+ HRULE:100#0000FF:"Maximum allowed" \
+ AREA:good#00FF00:"Good speed" \
+ AREA:fast#550000:"Too fast" \
+ STACK:over#FF0000:"Over speed"
+
+Vamos a crear una página HTML simple para ver los tres archivos PNG:
+
+ <HTML><HEAD><TITLE>Velocidad</TITLE></HEAD><BODY>
+ <IMG src="speed2.png" alt="Speed in meters per second">
+ <BR>
+ <IMG src="speed3.png" alt="Speed in kilometers per hour">
+ <BR>
+ <IMG src="speed4.png" alt="Traveled too fast?">
+ </BODY></HTML>
+
+Guárdalo como ``speed.html'' o algo parecido, y examÃnalo con un navegador.
+
+Ahora, todo lo que tienes que hacer es medir los datos regularmente
+y actualizar la base de datos. Cuando quieras verlos, vuelve a crear
+los archivos PNG y asegúrate que se carguen de nuevo en tu navegador
+(Nota: presionar el botón de "refrescar" puede no ser suficiente; en
+particular, Netscape tiene un problema al respecto, por lo que
+necesitaras darle al botón mientras presionas la tecla de mayúsculas.
+
+=head2 Actualizaciones de verdad
+
+Ya hemos usado el comando ``update''; vimos que recibia uno o más
+parámetros en el formato: ``E<lt>fechaE<gt>:E<lt>valorE<gt>''. Para
+facilitarte las cosas, puedes obtener la fecha actual colocando
+``N'' en la fecha. También podrÃas usar la función
+``time'' de Perl para obtenerla. El ejemplo más corto de todo el
+tutorial :)
+
+ perl -e 'print time, "\n" '
+
+Ahora, la forma de poner a correr un programa a intervalos
+regulares de tiempo depende del sistema de operación. La
+actualización, en pseudo-código, serÃa:
+
+ Toma el valor, colócalo en la variable "$speed"
+ rrdtool update speed.rrd N:$speed
+
+(Pero no lo hagas sobre nuestra base de datos de pruebas, que aún
+la vamos a usar en otros ejemplos.
+
+Eso es todo. Ejecutando este script cada 5 minutos, lo único que
+tienes que hacer para ver los gráficos actuales es correr los ejemplos
+anteriores, que también puedes poner en un script. Luego de correrlo,
+basta con cargar index.html
+
+=head2 Unas palabras sobre SNMP
+
+Me imagino que muy pocas personas serán capaces de obtener en su
+ordenador datos reales de su coche cada 5 minutos; los demás nos
+tendremos que conformar con algún otro contador. Puedes, por ejemplo,
+medir la cantidad de páginas que ha hecho una impresora, cuanto café
+has hecho con la cafetera, el medidor del consumo de electricidad, o
+cualquier otra cosa. Cualquier contador incremental puede
+monitorizarse y graficarse con lo que has aprendido hasta ahora. Más
+adelante, veremos también como monitorizar otro tipo de valores, como
+la temperatura. La mayorÃa usaremos alguna vez un contador que lleve
+la cuenta de cuantos octetos (bytes) a transferido un dispositivo de
+red, asà que vamos a ver como hacer esto. Empezaremos describiendo
+como recoger los datos. Hay quien dirá que hay herramientas que pueden
+recoger estos datos por ti. ¡Es cierto! Pero, creo que es importante
+darse cuenta de que no son necesarias. Cuando tienes que determinar
+porqué algo no funciona, necesitas saber cómo funciona en primer lugar.
+
+Una herramienta que mencionamos brevemente al principio del
+documento es SNMP. SNMP es una forma de comunicarse con tus equipos.
+La herramienta particular que voy a usar más adelante se llama
+``snmpget'', y funciona asÃ:
+
+ snmpget dispositivo clave OID
+
+En "dispositivo" colocas el nombre o dirección IP del equipo a
+monitorizar. En clave, colocas la "cadena de caracteres de la
+comunidad de lectura", como se le denomina en el mundillo SNMP.
+Muchos dispositivos aceptarán "public" como
+cadena por defecto, pero por razones de privacidad y seguridad esta
+clave puede estar deshabilitada. Consulta la documentación
+correspondiente al dispositivo o programa.
+
+Luego esta el tercer parámetro, llamado OID
+(Object IDentifier, identificador de objeto).
+
+Al principio, cuando empiezas a aprender sobre SNMP, parece muy
+confuso. No lo es tanto cuando le hechas una ojeada a los
+``MIB'' (Manager Information Base, o Base de
+Información Administrativa). Es un árbol invertido que describe los
+datos, empezando en un nodo raÃz desde el que parten varias ramas.
+Cada rama termina en otro nodo y puede abrir nuevas sub-ramas. Cada
+rama tiene un nombre, y forman un camino que nos lleva hasta el fondo
+del árbol. En este ejemplo, las ramas que vamos a tomar se llaman iso,
+org, dod, internet, mgmt y mib-2. También pueden accederse por su
+número relativo; en este caso, estos números son 1, 3, 6, 1, 2 y 1:
+
+ iso.org.dod.internet.mgmt.mib-2 (1.3.6.1.2.1)
+
+En algunos programas se usa un punto al iniciar el OID. Esto puede
+ser confuso; no hay ningún punto inicial en la especificación de los
+OID... sin embargo, algunos programas usan por defecto un prefijo
+inicial. Para indicar la diferencia entre los OID abreviados (o sea, a
+los que se le pondrá el prefijo inicial) y los completos, estos
+programas necesitan que los OID completos empiecen por un punto. Para
+empeorar las cosas, se usan varios prefijos distintos...
+
+De acuerdo, sigamos con el inicio de nuestro OID: tenÃamos
+1.3.6.1.2.1 . Ahora, nos interesa la rama ``interfaces'', que tiene el
+número dos (o sea, 1.3.6.1.2.1.2, o 1.3.6.1.2.1.interfaces).
+
+Lo primero es hacernos con un programa SNMP. Busca algún
+paquete pre-compilado para tu plataforma, si no, puedes
+buscar el código fuente y compilarlo tu mismo. En Internet encontrarás
+muchos programas, búscalos con un motor de búsqueda o como prefieras.
+Mi sugerencia es que busques el paquete CMU-SNMP, que esta bastante difundido.
+
+Asumamos que ya tienes el programa. Empecemos por tomar ciertos
+datos que están disponibles en la mayorÃa de los sistemas. Recuerda:
+hay un nombre abreviado para la parte del árbol que más nos interesa.
+
+Voy a usar la versión corta, ya que creo que este documento ya es
+lo bastante largo. Si no te funciona, añádele el prefijo .1.3.6.1.2.1
+y prueba de nuevo. O prueba leyendo el manual; sáltate las partes que
+no entiendas aún, y busca las secciones que hablan de como arrancar y
+usar el programa.
+
+ snmpget myrouter public system.sysdescr.0
+
+El dispositivo deberá contestarte con una descripción, probablemente
+vacÃa, de sà mismo. Si no consigues una respuesta válida, prueba con
+otra "clave" u otro dispositivo; no podemos seguir hasta tener un
+resultado.
+
+ snmpget myrouter public interfaces.ifnumber.0
+
+Con suerte, usando este comando obtendrás un número como resultado:
+el número de interfaces del dispositivo. Si es asÃ, seguiremos
+adelante con otro programa, llamado "snmpwalk"
+
+ snmpwalk myrouter public interfaces.iftable.ifentry.ifdescr
+
+Si obtienes una lista de interfaces, ya casi hemos llegado. AquÃ
+tienes un ejemplo del resultado:
+
+ [user@host /home/alex]$ snmpwalk cisco public 2.2.1.2
+ interfaces.ifTable.ifEntry.ifDescr.1 = "BRI0: B-Channel 1"
+ interfaces.ifTable.ifEntry.ifDescr.2 = "BRI0: B-Channel 2"
+ interfaces.ifTable.ifEntry.ifDescr.3 = "BRI0" Hex: 42 52 49 30
+ interfaces.ifTable.ifEntry.ifDescr.4 = "Ethernet0"
+ interfaces.ifTable.ifEntry.ifDescr.5 = "Loopback0"
+
+En este equipo CISCO, quiero monitorizar la interfaz "Ethernet0".
+Viendo que es la cuarta, pruebo con:
+
+ [user@host /home/alex]$ snmpget cisco public 2.2.1.10.4 2.2.1.16.4
+
+ interfaces.ifTable.ifEntry.ifInOctets.4 = 2290729126
+ interfaces.ifTable.ifEntry.ifOutOctets.4 = 1256486519
+
+Entonces, tengo 2 OIDs que monitorizar, y son (en el formato largo, ahora):
+
+ 1.3.6.1.2.1.2.2.1.10
+
+ y
+
+ 1.3.6.1.2.1.2.2.1.16
+
+, ambas con el número de interfaz de 4
+
+No te engañes, esto no lo logre yo al primer intento. Me tomó un
+tiempo entender lo que significaban todos estos números; ayuda cuando
+se traducen en un texto descriptivo... por lo menos, cuando oigas
+hablar de MIBs y OIDs, ahora sabrás de qué se trata. No te olvides
+del número de interfaz (0 si el valor no depende de una interfaz), y
+prueba con snmpwalk si no obtienes una respuesta clara con snmpget.
+
+Si entendiste todo esto, y obtienes resultados del dispositivo con
+el que estás probando, sigue adelante con el tutorial. Si no, vuelve a
+leer esta sección; es importante
+
+=head2 Un ejemplo real
+
+Ok, empecemos con la diversión. Primero, crea una base de datos
+nueva. Vamos a guardar en ella 2 contadores, "input" y "ouput". Los
+datos los vamos a guardar en archivos que los promediarán, tomando
+grupos de 1, 6, 24 o 288 muestras. También archivaremos los valores
+máximos. Lo explicaremos con más detalle después. El intervalo de
+tiempo entre las muestras será de 300 segundos (5 minutos).
+
+ 1 muestra "promediada" sigue siendo 1 muestra cada 5 minutos
+ 6 muestras promediadas son un promedio de cada 30 minutos
+ 24 muestras promediadas son un promedio de cada 2 horas
+ 288 muestras promediadas son un promedio de cada dÃa
+
+Vamos a tratar de ser compatibles con MRTG, que guarda más o menos
+esta cantidad de datos:
+
+ 600 muestras de 5 minutos: 2 dÃas y 2 horas
+ 600 promedios de 30 minutos: 12.5 dÃas
+ 600 promedios de 2 horas: 50 dÃas
+ 600 promedios de 1 dÃa: 732 dÃas
+
+Uniendo todos estos rangos tenemos que en total guardamos datos de
+unos 797 dÃas. RRDtool guarda los datos de una forma distinta a MRTG;
+no empieza el archivo "semanal" donde acaba el "diario", sino que
+ambos archivos contienen la información más reciente, ¡por lo que con
+RRDtool archivamos más datos que con MRTG!
+
+Necesitaremos:
+
+ 600 muestras de 5 minutos (2 dÃas y 2 horas)
+ 700 entradas de 30 minutos (2 dÃas y 2 horas, más 12.5 dÃas)
+ 775 entradas de 2 horas (lo anterior + 50 dÃas)
+ 797 entradas de 1 dÃa (lo anterior + 732 dÃas, redondeando)
+
+ rrdtool create myrouter.rrd \
+ DS:input:COUNTER:600:U:U \
+ DS:output:COUNTER:600:U:U \
+ RRA:AVERAGE:0.5:1:600 \
+ RRA:AVERAGE:0.5:6:700 \
+ RRA:AVERAGE:0.5:24:775 \
+ RRA:AVERAGE:0.5:288:797 \
+ RRA:MAX:0.5:1:600 \
+ RRA:MAX:0.5:6:700 \
+ RRA:MAX:0.5:24:775 \
+ RRA:MAX:0.5:288:797
+
+Lo siguiente es recoger los datos y guardarlos, como en el ejemplo
+siguiente. Esta parcialmente en pseudo-código, por lo que tendrás que
+buscar exactamente como hacerlo funcionar en tu sistema operativo.
+
+ mientras no sea el fin del universo
+ hacer
+ tomar el resultado de
+ snmpget router community 2.2.1.10.4
+ en la variable $in
+ tomar el resultado de
+ snmpget router community 2.2.1.16.4
+ en la variable $out
+ rrdtool update myrouter.rrd N:$in:$out
+ esperar 5 minutos
+ hecho
+
+Luego, tras recoger datos por un dÃa, crea una imagen, usando:
+
+ rrdtool graph myrouter-day.png --start -86400 \
+ DEF:inoctets=myrouter.rrd:input:AVERAGE \
+ DEF:outoctets=myrouter.rrd:output:AVERAGE \
+ AREA:inoctets#00FF00:"In traffic" \
+ LINE1:outoctets#0000FF:"Out traffic"
+
+Este comando debe producir un gráfico del tráfico del dÃa. Un dÃa
+son 24 horas, de 60 minutos, de 60 segundos: 24*60*60=86400, o sea que
+empezamos a "ahora" menos 86400 segundos. Definimos (con los DEFs)
+"inoctets" y "outoctets" como los valores promedio de la base da datos
+myrouter.rrd, dibujando un área para el tráfico de entrada y una lÃnea
+para el tráfico de salida.
+
+Mira la imagen y sigue recogiendo datos por unos cuantos dÃas. Si
+lo deseas, puedes probar con los ejemplos de la base de datos de
+pruebas y ver si puedes hacer trabajar las diversas opciones y
+operaciones.
+
+Sugerencia:
+
+Haz un gráfico que muestre el tráfico en bytes por segundo y en
+bits por segundo. Colorea el tráfico Ethernet rojo si sobrepasa los
+cuatro megabits por segundo.
+
+=head2 Funciones de consolidación
+
+Unos cuantos párrafos atrás hablábamos sobre la posibilidad de
+guardar el valor máximo en vez del promedio. Profundicemos un poco en
+este tema.
+
+Recordemos lo que hablábamos sobre la velocidad de un coche.
+Supongamos que manejamos a 144 KM/H durante 5
+minutos y luego nos detiene la policÃa durante unos 25 minutos. Al
+finalizar el regaño, tomamos nuestro portátil y creamos una imagen
+desde nuestra base de datos. Si visualizamos la segunda RRA que
+creamos, tendremos el promedio de 6 muestreos. Las velocidades
+registradas serian 144+0+0+0+0+0=144, lo que en promedio nos da una
+velocidad de 24 KM/H., con lo que nos igual nos
+pondrÃan una multa, sólo que no por exceso de velocidad.
+
+Obviamente, en este caso, no deberÃamos tomar en cuenta los
+promedios. Estos son útiles en varios casos. Por ejemplo, si queremos
+ver cuantos KM hemos viajado, este serÃa el
+gráfico más indicado. Pero por otro lado, para ver la velocidad ha la
+que hemos viajado, los valores máximos son más adecuados.
+
+Es lo mismo con los datos que recogemos. Si quieres saber la
+cantidad total, mira los promedios. Si quieres ver la velocidad, mira
+los máximos. Con el tiempo, ambas cantidades se separan cada vez más.
+En la última base de datos que creamos, habÃa dos archivos que
+guardaban los datos de cada dÃa. El archivo que guarda los promedios
+mostrará valores bajos, mientras que el de máximos mostrará valores más
+altos. Para mi coche, mostrarÃa valores promedio de 96/24=4 KM/H
+(viajo unos 96 kilómetros por dÃa), y máximos de 1220 KM/H (la
+velocidad máxima que alcanzo cada dÃa)
+
+Como ves, una gran diferencia. No mires el segundo gráfico para
+estimar la distancia que recorro, ni al primero para estimar la
+velocidad a la que voy. Esto sólo funciona con muestras muy cercanas,
+pero no si sacas promedios.
+
+Algunas veces, hago un viaje largo. Si hago un recorrido por
+Europa, conduciendo por unas 12 horas, el primer gráfico subirá
+a unos 60 KM/H. El segundo mostrará unos 180 KM/H. Esto significa que
+recorrà unos 60 KM/H por 24 horas = 1440 KM. Muestra además que fui a
+una velocidad promedio mayor a la normal y a un máximo de 180 KM/H,
+¡no que fui 8 horas a una velocidad fija de 180 KM/H! Este es un
+ejemplo real: tengo que seguir la corriente en las autopistas de
+Alemania, detenerme por gasolina y café de vez en cuando, manejar más
+lentamente por Austria y Holanda, e ir con cuidado en las montañas y
+las villas. Si viéramos los gráficos de los promedios de cada 5
+minutos, la imagen serÃa completamente distinta; verÃamos los mismos
+valores de promedio y de máxima. (suponiendo que las mediciones fueran
+cada 300 segundos). Se podrÃa ver cuando paré, cuando iba en
+primera, cuando iba por las autopistas, etc. La granularidad de los
+datos es más alta, por lo que se tiene más información. Sin embargo,
+esto nos lleva unas 12 muestras por hora, o 288 al dÃa, lo cual es
+mucho para guardar por un periodo de tiempo largo. Por lo tanto,
+sacamos el promedio, guardando eventualmente un solo valor por dÃa.
+Con este único valor, no podemos ver mucho.
+
+Es importante comprender lo que expuesto en estos últimos párrafos.
+Unos ejes y unas lÃneas no tienen ningún valor por si mismos; hay que
+saber que representan e interpretar correctamente los valores
+obtenidos. Sean cuales sean los datos, esto siempre será cierto.
+
+El mayor error que puedes cometer es usar los datos recogidos para
+algo para lo cual no sirven. En ese caso, seria hasta mejor no tener
+gráfico alguno.
+
+=head2 Repasemos lo que sabemos
+
+Ahora ya sabes como crear una base de datos. Puedes guardar valores
+en ella, extraerlos creando un gráfico, hacer operaciones matemáticas
+con ellos desde la base de datos y visualizar los resultados de estas
+en vez de los datos originales. Vimos la diferencia entre los
+promedios y los máximos y cuando debemos usar cada uno (o al menos una
+idea de ello)
+
+RRDtool puede hacer más de lo que hemos visto hasta ahora. Pero
+antes de continuar, te recomiendo que releas el texto desde el
+principio y pruebes a hacerle algunas modificaciones a los ejemplos.
+Asegúrate de entenderlo todo. El esfuerzo valdrá la pena, y te ayudará,
+no sólo con el resto del documento, sino en tu trabajo diario de
+monitorización, mucho después de terminar con esta introducción.
+
+=head2 Tipos de fuentes de datos
+
+De acuerdo, quieres continuar. Bienvenido de vuelta otra vez y
+prepárate; voy a ir más rápido con los ejemplos y explicaciones.
+
+Ya vimos que, para ver el cambio de un contador a lo largo del
+tiempo, tenemos que tomar dos números y dividir la diferencia entre el
+tiempo transcurrido entre las mediciones. Para los ejemplos que hemos
+visto es lo lógico, pero hay otras posibilidades. Por ejemplo, mi
+enrutador me puede dar la temperatura actual en tres puntos distintos,
+la entrada de aire, el llamado "punto caliente" y la salida de
+ventilación. Estos valores no son contadores; si tomo los valores de
+dos muestreos y lo divido entre 300 segundos, obtendré el cambio de
+temperatura por segundo. ¡Esperemos que sea cero, o tendrÃamos un
+incendio en el cuarto de ordenadores! :)
+
+Entonces, ¿que hacemos? Podemos decirle a RRDtool que guarde los
+valores tal como los medimos (esto no es exactamente asÃ, pero se
+aproxima bastante a la verdad). AsÃ, los gráficos se verán mucho
+mejor. Puedo ver cuando el enrutador está trabajando más (en serio,
+funciona; como usa más electricidad, genera más calor y sube la
+temperatura), puedo saber cuando me he dejado las puertas abiertas (el
+cuarto de ordenadores tiene aire acondicionado; con las puertas
+abiertas el aire caliente del resto del edificion entra y sube la
+temperatura en la entrada de aire del enrutador), etc. Antes usamos un
+tipo de datos de "contador", ahora usaremos un tipo de datos
+diferente, con un nombre diferente, GAUGE.
+Tenemos otros tipos:
+
+ - COUNTER este ya lo conocemos
+ - GAUGE este acabamos de verlo
+ - DERIVE
+ - ABSOLUTE
+
+Los otros dos tipos son DERIVE y ABSOLUTE. ABSOLUTE puede usarse
+igual que COUNTER, con una diferencia; RRDtool asume que el contador
+se reinicia cada vez que se lee. O en otras palabras; el delta entre
+los valores no hay que calcularlo, mientras que con COUNTER RRDtool
+tiene que sacar él la cuenta. Por ejemplo, nuestro primer ejemplo,
+(12345, 12357, 12363, 12363), serÃa (unknown, 12, 6, 0) en ABSOLUTE.
+El otro tipo, DERIVE, es como COUNTER, pero al contrario de COUNTER,
+este valor también puede decrecer, por lo que puede tenerse un delta
+negativo.
+
+Vamos a probarlos todos:
+
+ rrdtool create all.rrd --start 978300900 \
+ DS:a:COUNTER:600:U:U \
+ DS:b:GAUGE:600:U:U \
+ DS:c:DERIVE:600:U:U \
+ DS:d:ABSOLUTE:600:U:U \
+ RRA:AVERAGE:0.5:1:10
+ rrdtool update all.rrd \
+ 978301200:300:1:600:300 \
+ 978301500:600:3:1200:600 \
+ 978301800:900:5:1800:900 \
+ 978302100:1200:3:2400:1200 \
+ 978302400:1500:1:2400:1500 \
+ 978302700:1800:2:1800:1800 \
+ 978303000:2100:4:0:2100 \
+ 978303300:2400:6:600:2400 \
+ 978303600:2700:4:600:2700 \
+ 978303900:3000:2:1200:3000
+ rrdtool graph all1.png -s 978300600 -e 978304200 -h 400 \
+ DEF:linea=all.rrd:a:AVERAGE LINE3:linea#FF0000:"Line A" \
+ DEF:lineb=all.rrd:b:AVERAGE LINE3:lineb#00FF00:"Line B" \
+ DEF:linec=all.rrd:c:AVERAGE LINE3:linec#0000FF:"Line C" \
+ DEF:lined=all.rrd:d:AVERAGE LINE3:lined#000000:"Line D"
+
+=head2 RRDtool bajo el microscopio
+
+=over 4
+
+=item *
+
+La lÃnea A es un contador, por lo que
+debe incrementarse continuamente y RRDtool tiene que calcular las
+diferencias. Además RRDtool tiene que dividir la diferencia entre
+el tiempo transcurrido. Esto deberÃa terminar con una lÃnea recta
+en 1 (los deltas son 300, y los intervalos son de 300)
+
+=item *
+
+La lÃnea B es de tipo GAUGE. Estos son
+los valores "reales", asà que el gráfico debe mostrar lo mismo que
+los valores que introducimos: una especie de onda
+Z<>
+
+=item *
+
+La lÃnea C es de tipo DERIVE. Es un
+contador, y puede decrecer. Va entre 2400 y 0, con 1800 en el medio.
+
+=item *
+
+La lÃnea D es de tipo ABSOLUTE. Esto es,
+es un contador pero no hay que calcular las diferencias. Los
+números son iguales a la lÃnea A, y espero
+que puedas ver la diferencia en los gráficos.
+
+=back
+
+Esto equivale a los valores siguientes, empezando a las 23:10 y
+terminando a las 00:10 (las U significan desconocido).
+
+
+ - LÃnea A: u u 1 1 1 1 1 1 1 1 1 u
+ - LÃnea B: u 1 3 5 3 1 2 4 6 4 2 u
+ - LÃnea C: u u 2 2 2 0 -2 -6 2 0 2 u
+ - LÃnea D: u 1 2 3 4 5 6 7 8 9 10 u
+
+Si tu archivo PNG muestra todo esto, has
+entrado los datos correctamente, tu programa RRDtool está funcionando
+bien, el visor de gráficos no te engaña y hemos entrado en el 2000 sin
+problemas :) Puedes probar el mismo ejemplo cuatro veces, una por cada lÃnea.
+
+Revisemos los datos otra vez:
+
+=over 4
+
+=item *
+
+LÃnea A: 300, 600, 900 , etc.
+La diferencia del contador es siempre 300, igual que el intervalo de
+tiempo transcurrido entre mediciones. Por lo tanto, el promedio
+siempre es 1. Pero, ¿por qué el primer punto tiene un valor de
+"desconocido"? ¿Acaso no era conocido el valor que pusimos en la
+base de datos? ¡Si! Pero no tenÃamos un valor inicial para
+calcular la diferencia. SerÃa un error asumir que el contador
+empezaba en 0, asà que no conocemos el valor de la diferencia
+
+=item *
+
+LÃnea B:
+No hay nada que calcular, los valores son los mismos que se
+introdujeron en la base de datos.
+
+=item *
+
+LÃnea C:
+De nuevo, no conocemos el valor
+inicial antes de la primera medición, asà que se aplica el mismo
+razonamiento que para la lÃnea A. En este
+caso las diferencias no son constantes, asà que la lÃnea no es
+recta. Si hubiésemos puesto los mismos valores que en la lÃnea
+A, el gráfico serÃa el mismo. Al contrario
+que COUNTER, el valor puede decrecer, y espero mostrarte más
+adelante el por que de la diferencia entre ambos tipos.
+
+=item *
+
+LÃnea D: En este caso, el dispositivo nos
+da las diferencias por sà mismo. Por lo tanto, conocemos la
+diferencia inicial, y podemos graficarla. Tenemos los mismos
+valores que en la lÃnea A, pero su
+significado es distinto, por lo que el gráfico también lo es. En
+este caso, las diferencias se incrementan en 300 cada vez,
+mientras que el intervalo de tiempo permanece constante en 300
+segundos, por lo que la división nos da resultados cada vez mayores.
+
+=back
+
+=head2 Reinicialización de los contadores
+
+TodavÃa nos quedan algunas cosas por ver. Nos quedan algunas
+opciones importantes por cubrir, y aun no hemos hablado de la
+reinicialización de contadores. Empecemos por ahÃ: Estamos en nuestro
+coche, vemos el contador y muestra 999987. Andamos unos 20 KM, asà que
+el contador debe subir a 1000007. Desafortunadamente, el contador
+sólo tiene 6 dÃgitos, asà que en realidad nos muestra 000007. Si
+estuviéramos guardando los valores en un tipo DERIVE, esto
+significarÃa que el contador retrocedió unos 999980 KM. Por supuesto
+esto no es cierto, por lo que necesitamos alguna protección contra estos
+casos. Esta protección sólo la tenemos para el tipo COUNTER, el cual
+de todas formas era el que tenÃamos que haber usado para este tipo de
+contador. ¿Cómo funciona? Los valores tipo COUNTER no deben decrecer
+nunca, ¡por lo que RRDtool asume en ese caso que el contador se ha
+reinicializado! Si la diferencia es negativa, esto se compensa sumando
+el valor máximo del contador + 1. Para nuestro coche, tendrÃamos:
+
+ Delta = 7 - 999987 = -999980 (en vez de 1000007-999987=20)
+
+ Delta real= -999980 + 999999 + 1 = 20
+
+Al momento de escribir este documento, RRDtool maneja contadores de
+32 o 64 bits de tamaño. Estos contadores pueden manejar los siguientes
+valores:
+
+ - 32 bits: 0 .. 4294967295
+ - 64 bits: 0 .. 18446744073709551615
+
+Si estos valores te parecen raros, podemos verlos en formato hexadecimal:
+
+ - 32 bits: 0 .. FFFFFFFF
+ - 64 bits: 0 .. FFFFFFFFFFFFFFFF
+
+RRDtool maneja ambos contadores de la misma manera. Si ocurre un
+desbordamiento y la diferencia es negativa, RRDtool le suma primero
+el máximo del contador "menor" (32 bits) + 1 a la diferencia. Si aún
+asà la diferencia es negativa, entonces el contador reinicializado era
+mayor (64 bits), por lo que se le suma el valor máximo del contador
+"largo" + 1 y se le resta el máximo del contador "pequeño" que sumamos
+erróneamente. Hay un problema con esto: supongamos que un contador
+largo se ha reinicializado al sumársele una diferencia muy grande;
+entonces es posible que al añadir el valor máximo del contador pequeño
+la diferencia nos dé positivo. En este caso poco probable, los valores
+resultantes no serian correctos. Para que ocurra esto, el incremento
+tiene que ser casi tan grande como el valor máximo del contador, por
+lo que de ocurrir es muy probable que halla varios problemas más en
+la configuración y no merezca la pena preocuparse sólo por este. Aún
+asÃ, he incluido un ejemplo de este caso para que lo puedas juzgar por
+ti mismo.
+
+A continuación, unos ejemplos de reinicialización de los
+contadores. Prueba de hacer los cálculos por ti mismo, o acepta mis
+resultados si tu calculadora no puede con los números :)
+
+Números de corrección:
+
+ - 32 bits: (4294967295+1) = 4294967296
+ - 64 bits: (18446744073709551615+1)-correction1 = 18446744069414584320
+
+ Antes: 4294967200
+ Incremento: 100
+ DeberÃa ser: 4294967300
+ Pero es: 4
+ Diferencia: -4294967196
+ Corrección #1: -4294967196 + 4294967296 = 100
+
+ Antes: 18446744073709551000
+ Incremento: 800
+ DeberÃa ser: 18446744073709551800
+ Pero es: 184
+ Diferencia: -18446744073709550816
+ Corrección #1: -18446744073709550816 +4294967296 = -18446744069414583520
+ Corrección #2: -18446744069414583520 +18446744069414584320 = 800
+
+ Antes: 18446744073709551615 ( valor máximo )
+ Incremento: 18446744069414584320 ( incremento absurdo,
+ DeberÃa ser: 36893488143124135935 mÃnimo para que
+ Pero es: 18446744069414584319 funcione el ejemplo)
+ Diferencia: -4294967296
+ Corrección #1: -4294967296 + 4294967296 = 0 (positivo,
+ por tanto no se hace
+ la segunda corrección)
+
+ Antes: 18446744073709551615 ( valor máximo )
+ Incremento: 18446744069414584319
+ DeberÃa ser: 36893488143124135934
+ Pero es: 18446744069414584318
+ Diferencia: -4294967297
+ Corrección #1: -4294967297 +4294967296 = -1
+ Corrección #2: -1 +18446744069414584320 = 18446744069414584319
+
+Como puede verse en los últimos ejemplos, necesitas unos valores
+bastante extraños para hacer que RRDtool falle (asumiendo que no tenga
+ningún error el programa, por supuesto), asà que esto no deberÃa
+ocurrir. Sin embargo, SNMP o cualquier otro
+método que uses de recogida de datos puede también reportar algún
+valor erróneo ocasionalmente. No podemos prevenir todos los errores,
+pero podemos tomar algunas medidas. El comando "create" de RRDtool
+tiene dos parámetros especialmente para esto, que definen los valores
+mÃnimo y máximo permitidos. Hasta ahora hemos usado "U",
+"desconocido". Si le pasas valores para uno o ambos parámetros y
+RRDtool recibe un valor fuera de esos lÃmites, los ignorará. Para un
+termómetro en grados Celsius, el mÃnimo absoluto es -273. Para mi
+enrutador, puedo asumir que ese mÃnimo es mucho mayor, digamos que 10.
+La temperatura máxima la pondrÃa en unos 80 grados; más alto y
+el aparato no funcionarÃa. Para mi coche, nunca esperarÃa obtener
+valores negativos, y tampoco esperarÃa valores mayores a 230.
+Cualquier otra cosa serÃa un error. Pero recuerda, lo contrario no es
+cierto: si los valores pasan este examen no quiere decir que sean los
+correctos. Siempre examina bien el gráfico si los valores parecen
+extraños.
+
+
+=head2 Remuestreo de los datos
+
+Hay una funcionalidad importante de RRDtool que no hemos explicado
+todavÃa: es virtualmente imposible recoger los datos y pasarselos a
+RRDtool a intervalos exactos de tiempo. Por tanto, RRDtool interpola
+los datos a los intervalos exactos. Si no sabes que significa esto o
+como se hace, he aquà la ayuda que necesitas:
+
+Supongamos un contador se incremente exactamente en 1 cada segundo.
+Queremos medirlo cada 300 segundos, por lo que deberÃamos tener
+valores separados exactamente en 300. Sin embargo, por varias
+circunstancias llegamos unos segundos tarde y el intervalo es 303. La
+diferencia será por tanto 303. Obviamente, RRDtool no debe colocar 303
+en la base de datos y dar asà la impresión de que el contador se
+incrementó 303 en 300 segundos. Aquà es donde RRDtool interpola:
+alterá el valor 303 al valor que tendrÃa 3 segundos antes y guarda 300
+en 300 segundos. Digamos que la próxima vez llegamos justo a tiempo;
+por tanto, el intervalo actual es 297 segundos, por lo que el contador
+deberÃa ser 297. De nuevo, RRDtool altera el valor y guarda 300, como
+debe ser.
+
+ en RRD en realidad
+ tiempo+000: 0 delta="U" tiempo+000: 0 delta="U"
+ tiempo+300: 300 delta=300 tiempo+300: 300 delta=300
+ tiempo+600: 600 delta=300 tiempo+603: 603 delta=303
+ tiempo+900: 900 delta=300 tiempo+900: 900 delta=297
+
+Creemos dos bases de datos idénticas. He escogido el rango de
+tiempo entre 920805000 y 920805900.
+
+ rrdtool create seconds1.rrd \
+ --start 920804700 \
+ DS:seconds:COUNTER:600:U:U \
+ RRA:AVERAGE:0.5:1:24
+
+ para Unix: cp seconds1.rrd seconds2.rrd
+ para DOS: copy seconds1.rrd seconds2.rrd
+ para VMS: y yo que sé :)
+
+ rrdtool update seconds1.rrd \
+ 920805000:000 920805300:300 920805600:600 920805900:900
+ rrdtool update seconds2.rrd \
+ 920805000:000 920805300:300 920805603:603 920805900:900
+
+ rrdtool graph seconds1.png \
+ --start 920804700 --end 920806200 \
+ --height 200 \
+ --upper-limit 1.05 --lower-limit 0.95 --rigid \
+ DEF:seconds=seconds1.rrd:seconds:AVERAGE \
+ CDEF:unknown=seconds,UN \
+ LINE2:seconds#0000FF \
+ AREA:unknown#FF0000
+ rrdtool graph seconds2.png \
+ --start 920804700 --end 920806200 \
+ --height 200 \
+ --upper-limit 1.05 --lower-limit 0.95 --rigid \
+ DEF:seconds=seconds2.rrd:seconds:AVERAGE \
+ CDEF:unknown=seconds,UN \
+ LINE2:seconds#0000FF \
+ AREA:unknown#FF0000
+
+Los dos gráficos debe ser iguales.
+
+=head1 RESUMEN
+
+Es hora de concluir este documento. Ahora debes conocer lo básico
+como para trabajar con RRDtool y leer la documentación. Aún hay mucho
+más por descubrir acerca de RRDtool, y le encontrarás; más y más usos
+para la herramienta. Con los ejemplos y la herramienta puedes crear
+fácilmente muchos gráficos; también puedes usar las interfaces
+disponibles.
+
+=head1 LISTA DE CORREO
+
+Recuerda subscribirte a la lista de correo. Aunque no contestes los
+correos que aparecen en ella, te servirá de ayuda a ti y a los demás.
+Mucho de lo que se sobre MRTG (y por tanto sobre RRDtool), lo aprendÃ
+tan sólo con leer la lista, sin escribir. No hay por que preguntar las
+preguntas básicas, que ya tienen su respuesta en la FAQ (¡léela!). Con
+miles de usuarios a lo largo del mundo, siempre hay preguntas que tu
+puedes responder con lo aprendido en este y otros documentos.
+
+=head1 VER TAMBIÉN
+
+Las páginas del manual de RRDtool
+
+=head1 AUTOR
+
+Espero que hayas disfrutado con los ejemplos y las descripciones.
+Si es asÃ, ayuda a otros refiriéndolos a este documento cuando te
+hagan preguntas básicas. No sólo obtendrán la respuesta, sino que
+aprenderán muchas otras cosas.
+
+Alex van den Bogaerdt <alex@ergens.op.het.net>
diff --git a/program/doc/rrdtutorial.pod b/program/doc/rrdtutorial.pod
--- /dev/null
@@ -0,0 +1,1177 @@
+=head1 NAME
+
+rrdtutorial - Alex van den Bogaerdt's RRDtool tutorial
+
+=head1 DESCRIPTION
+
+RRDtool is written by Tobias Oetiker E<lt>tobi@oetiker.chE<gt> with
+contributions from many people all around the world. This document is
+written by Alex van den Bogaerdt E<lt>alex@ergens.op.het.netE<gt> to help you
+understand what RRDtool is and what it can do for you.
+
+The documentation provided with RRDtool can be too technical for some
+people. This tutorial is here to help you understand the basics of
+RRDtool. It should prepare you to read the documentation yourself.
+It also explains the general things about statistics with a focus on
+networking.
+
+=head1 TUTORIAL
+
+=head2 Important
+
+Please don't skip ahead in this document! The first part of this
+document explains the basics and may be boring. But if you don't
+understand the basics, the examples will not be as meaningful to you.
+
+=head2 What is RRDtool?
+
+RRDtool refers to Round Robin Database tool.
+Round robin is a technique that works with a fixed amount of data, and a
+pointer to the current element. Think of a circle with some dots plotted
+on the edge -- these dots are the places where data can be stored. Draw an
+arrow from the center of the circle to one of the dots -- this is the pointer.
+When the current data is read or written, the pointer moves to the next
+element. As we are on a circle there is neither a beginning nor an end, you can
+go on and on and on. After a while, all the available places will be used and
+the process automatically reuses old locations. This way, the dataset
+will not grow in size and therefore requires no maintenance.
+RRDtool works with with Round Robin Databases (RRDs). It stores and retrieves
+data from them.
+
+=head2 What data can be put into an RRD?
+
+You name it, it will probably fit as long as it is some sort of time-series
+data. This means you have to be able to measure some value at several points in time and
+provide this information to RRDtool. If you can do this, RRDtool will be
+able to store it. The values must be numerical but don't have to be
+integers, as is the case with MRTG (the next section will give more details
+on this more specialized application).
+
+Many examples below talk about SNMP which is an acronym for Simple Network
+Management Protocol. "Simple" refers to the protocol -- it does not
+mean it is simple to manage or monitor a network. After working your
+way through this document, you should know enough to be able to
+understand what people are talking about. For now, just realize that
+SNMP can be used to query devices for the values of counters they keep. It
+is the value from those counters that we want to store in the RRD.
+
+=head2 What can I do with this tool?
+
+RRDtool originated from MRTG (Multi Router Traffic Grapher). MRTG
+started as a tiny little script for graphing the use of a university's
+connection to the Internet. MRTG was later (ab-)used as a tool for
+graphing other data sources including temperature, speed, voltage,
+number of printouts and the like.
+
+Most likely you will start to use RRDtool to store and process data
+collected via SNMP. The data will most likely be bytes (or bits)
+transfered from and to a network or a computer. But it can also be
+used to display tidal waves, solar radiation, power consumption,
+number of visitors at an exhibition, noise levels near an airport,
+temperature on your favorite holiday location, temperature in the
+fridge and whatever you imagination can come up with.
+
+You only need a sensor to measure the data and be able to feed the
+numbers into RRDtool. RRDtool then lets you create a database, store
+data in it, retrieve that data and create graphs in PNG format for
+display on a web browser. Those PNG images are dependent on the data
+you collected and could be, for instance, an overview of the average
+network usage, or the peaks that occurred.
+
+=head2 What if I still have problems after reading this document?
+
+First of all: read it again! You may have missed something.
+If you are unable to compile the sources and you have a fairly common
+OS, it will probably not be the fault of RRDtool. There may be pre-compiled
+versions around on the Internet. If they come from trusted sources, get
+one of those.
+
+If on the other hand the program works but does not give you the
+expected results, it will be a problem with configuring it. Review
+your configuration and compare it with the examples that follow.
+
+There is a mailing list and an archive of it. Read the list for a few
+weeks and search the archive. It is considered rude to just ask
+a question without searching the archives: your problem may already have been
+solved for somebody else! This is true for most, if not all, mailing lists
+and not only for this particular one. Look in the documentation that
+came with RRDtool for the location and usage of the list.
+
+I suggest you take a moment to subscribe to the mailing list right now
+by sending an email to E<lt>rrd-users-request@lists.oetiker.chE<gt> with a
+subject of "subscribe". If you ever want to leave this list, just write
+an email to the same address but now with a subject of "unsubscribe".
+
+=head2 How will you help me?
+
+By giving you some detailed descriptions with detailed examples.
+I assume that following the instructions in the order presented
+will give you enough knowledge of RRDtool to experiment for yourself.
+If it doesn't work the first time, don't give up. Reread the stuff that
+you did understand, you may have missed something.
+
+By following the examples you get some hands-on experience and, even
+more important, some background information of how it works.
+
+You will need to know something about hexadecimal numbers. If you don't
+then start with reading L<bin_dec_hex> before you continue here.
+
+=head2 Your first Round Robin Database
+
+In my opinion the best way to learn something is to actually do it.
+Why not start right now? We will create a database, put some values
+in it and extract this data again. Your output should be the same
+as the output that is included in this document.
+
+We will start with some easy stuff and compare a car with a router,
+or compare kilometers (miles if you wish) with bits and bytes. It's
+all the same: some number over some time.
+
+Assume we have a device that transfers bytes to and from the Internet.
+This device keeps a counter that starts at zero when it is turned on,
+increasing with every byte that is transfered. This counter will probably have
+a maximum value. If this value is reached and an extra byte is counted,
+the counter starts over at zero. This is the same as many counters
+in the world such as the mileage counter in a car.
+
+Most discussions about networking talk about bits per second so lets
+get used to that right away. Assume a byte is eight bits and start to
+think in bits not bytes. The counter, however, still counts bytes!
+In the SNMP world most of the counters are 32 bits. That means they are
+counting from 0 to 4'294'967'295. We will use these values in the examples.
+The device, when asked, returns the current value of the counter. We
+know the time that has passes since we last asked so we now know how
+many bytes have been transfered ***on average*** per second. This is
+not very hard to calculate. First in words, then in calculations:
+
+=over 3
+
+=item 1.
+
+Take the current counter, subtract the previous value from it.
+
+=item 2.
+
+Do the same with the current time and the previous time (in seconds).
+
+=item 3.
+
+Divide the outcome of (1) by the outcome of (2), the result is
+the amount of bytes per second. Multiply by eight to get the
+number of bits per second (bps).
+
+=back
+
+ bps = (counter_now - counter_before) / (time_now - time_before) * 8
+
+For some people it may help to translate this to an automobile example.
+Do not try this example, and if you do, don't blame me for the results!
+
+People who are not used to think in kilometers per hour can translate
+most into miles per hour by dividing km by 1.6 (close enough).
+I will use the following abbreviations:
+
+ M: meter
+ KM: kilometer (= 1'000 meters).
+ H: hour
+ S: second
+ KM/H: kilometers per hour
+ M/S: meters per second
+
+You are driving a car. At 12:05 you read the counter in the dashboard
+and it tells you that the car has moved 12'345 KM until that moment.
+At 12:10 you look again, it reads 12'357 KM. This means you have
+traveled 12 KM in five minutes. A scientist would translate that
+into meters per second and this makes a nice comparison toward the
+problem of (bytes per five minutes) versus (bits per second).
+
+We traveled 12 kilometers which is 12'000 meters. We did that in five
+minutes or 300 seconds. Our speed is 12'000M / 300S or 40 M/S.
+
+We could also calculate the speed in KM/H: 12 times 5 minutes
+is an hour, so we have to multiply 12 KM by 12 to get 144 KM/H.
+For our native English speaking friends: that's 90 MPH so don't
+try this example at home or where I live :)
+
+Remember: these numbers are averages only. There is no way to figure out
+from the numbers, if you drove at a constant speed. There is an example
+later on in this tutorial that explains this.
+
+I hope you understand that there is no difference in calculating M/S or
+bps; only the way we collect the data is different. Even the K from kilo
+is the same as in networking terms k also means 1'000.
+
+We will now create a database where we can keep all these interesting
+numbers. The method used to start the program may differ slightly from
+OS to OS, but I assume you can figure it out if it works different on
+your's. Make sure you do not overwrite any file on your system when
+executing the following command and type the whole line as one long
+line (I had to split it for readability)
+and skip all of the '\' characters.
+
+ rrdtool create test.rrd \
+ --start 920804400 \
+ DS:speed:COUNTER:600:U:U \
+ RRA:AVERAGE:0.5:1:24 \
+ RRA:AVERAGE:0.5:6:10
+
+(So enter: C<rrdtool create test.rrd --start 920804400 DS ...>)
+
+=head2 What has been created?
+
+We created the round robin database called test (test.rrd) which starts at
+noon the day I started writing this document, 7th of March, 1999 (this date
+translates to 920'804'400 seconds as explained below). Our database holds
+one data source (DS) named "speed" that represents a counter. This counter
+is read every five minutes (this is the default therefore you don't have to
+put C<--step=300>). In the same database two round robin archives (RRAs)
+are kept, one averages the data every time it is read (e.g., there's nothing
+to average) and keeps 24 samples (24 times 5 minutes is 2 hours). The other
+averages 6 values (half hour) and contains 10 such averages (e.g. 5 hours).
+
+RRDtool works with special time stamps coming from the UNIX world.
+This time stamp is the number of seconds that passed since January
+1st 1970 UTC. The time stamp value is translated into local time and
+it will therefore look different for different time zones.
+
+Chances are that you are not in the same part of the world as I am.
+This means your time zone is different. In all examples where I talk
+about time, the hours may be wrong for you. This has little effect on
+the results of the examples, just correct the hours while reading.
+As an example: where I will see "12:05" the UK folks will see "11:05".
+
+We now have to fill our database with some numbers. We'll pretend to
+have read the following numbers:
+
+ 12:05 12345 KM
+ 12:10 12357 KM
+ 12:15 12363 KM
+ 12:20 12363 KM
+ 12:25 12363 KM
+ 12:30 12373 KM
+ 12:35 12383 KM
+ 12:40 12393 KM
+ 12:45 12399 KM
+ 12:50 12405 KM
+ 12:55 12411 KM
+ 13:00 12415 KM
+ 13:05 12420 KM
+ 13:10 12422 KM
+ 13:15 12423 KM
+
+We fill the database as follows:
+
+ rrdtool update test.rrd 920804700:12345 920805000:12357 920805300:12363
+ rrdtool update test.rrd 920805600:12363 920805900:12363 920806200:12373
+ rrdtool update test.rrd 920806500:12383 920806800:12393 920807100:12399
+ rrdtool update test.rrd 920807400:12405 920807700:12411 920808000:12415
+ rrdtool update test.rrd 920808300:12420 920808600:12422 920808900:12423
+
+This reads: update our test database with the following numbers
+
+ time 920804700, value 12345
+ time 920805000, value 12357
+
+etcetera.
+
+As you can see, it is possible to feed more than one value into the
+database in one command. I had to stop at three for readability but
+the real maximum per line is OS dependent.
+
+We can now retrieve the data from our database using "rrdtool fetch":
+
+ rrdtool fetch test.rrd AVERAGE --start 920804400 --end 920809200
+
+It should return the following output:
+
+ speed
+
+ 920804700: nan
+ 920805000: 4.0000000000e-02
+ 920805300: 2.0000000000e-02
+ 920805600: 0.0000000000e+00
+ 920805900: 0.0000000000e+00
+ 920806200: 3.3333333333e-02
+ 920806500: 3.3333333333e-02
+ 920806800: 3.3333333333e-02
+ 920807100: 2.0000000000e-02
+ 920807400: 2.0000000000e-02
+ 920807700: 2.0000000000e-02
+ 920808000: 1.3333333333e-02
+ 920808300: 1.6666666667e-02
+ 920808600: 6.6666666667e-03
+ 920808900: 3.3333333333e-03
+ 920809200: nan
+
+If it doesn't, something may be wrong. Perhaps your OS will print
+"NaN" in a different form. "NaN" stands for "Not A Number". If your OS
+writes "U" or "UNKN" or something similar that's okay. If something
+else is wrong, it will probably be due to an error you made (assuming
+that my tutorial is correct of course :-). In that case: delete the
+database and try again. Sometimes things change. This example used
+to provide numbers like "0.04" in stead of "4.00000e-02". Those are
+really the same numbers, just written down differently. Don't be
+alarmed if a future version of rrdtool displays a slightly different
+form of output. The examples in this document are correct for version
+1.2.0 of RRDtool.
+
+The meaning of the above output will become clear below.
+
+=head2 Time to create some graphics
+
+Try the following command:
+
+ rrdtool graph speed.png \
+ --start 920804400 --end 920808000 \
+ DEF:myspeed=test.rrd:speed:AVERAGE \
+ LINE2:myspeed#FF0000
+
+This will create speed.png which starts at 12:00 and ends at 13:00.
+There is a definition of a variable called myspeed, using the data from RRA
+"speed" out of database "test.rrd". The line drawn is 2 pixels high
+and represents the variable myspeed. The color is red (specified by
+its rgb-representation, see below).
+
+You'll notice that the start of the graph is not at 12:00 but at 12:05.
+This is because we have insufficient data to tell the average before
+that time. This will only happen when you miss some samples, this will
+not happen a lot, hopefully.
+
+If this has worked: congratulations! If not, check what went wrong.
+
+
+The colors are built up from red, green and blue. For each of the
+components, you specify how much to use in hexadecimal where 00 means
+not included and FF means fully included.
+The "color" white is a mixture of red, green and blue: FFFFFF
+The "color" black is all colors off: 000000
+
+ red #FF0000
+ green #00FF00
+ blue #0000FF
+ magenta #FF00FF (mixed red with blue)
+ gray #555555 (one third of all components)
+
+Additionally you can add an alpha channel (transparency). The default
+will be "FF" which means non-transparent.
+
+The PNG you just created can be displayed using your favorite image
+viewer. Web browsers will display the PNG via the URL
+"file:///the/path/to/speed.png"
+
+=head2 Graphics with some math
+
+When looking at the image, you notice that the horizontal axis is labeled
+12:10, 12:20, 12:30, 12:40 and 12:50. Sometimes a label doesn't fit (12:00
+and 13:00 would be candidates) so they are skipped.
+
+The vertical axis displays the range we entered. We provided
+kilometers and when divided by 300 seconds, we get very small
+numbers. To be exact, the first value was 12 (12'357-12'345) and divided
+by 300 this makes 0.04, which is displayed by RRDtool as "40 m"
+meaning "40/1'000". The "m" (milli) has nothing to do with meters,
+kilometers or millimeters! RRDtool doesn't know about the physical
+units of our data, it just works with dimensionless numbers.
+
+If we had measured our distances in meters, this would have been
+(12'357'000-12'345'000)/300 = 12'000/300 = 40.
+
+As most people have a better feel for numbers in this range, we'll
+correct that. We could recreate our database and store the correct
+data, but there is a better way: we do some calculations while creating
+the png file!
+
+ rrdtool graph speed2.png \
+ --start 920804400 --end 920808000 \
+ --vertical-label m/s \
+ DEF:myspeed=test.rrd:speed:AVERAGE \
+ CDEF:realspeed=myspeed,1000,\* \
+ LINE2:realspeed#FF0000
+
+Note: Make sure not to forget the backslash \ in front of the
+multiplication operator * above. The backslash is needed to "escape"
+the * as some operating systems might interpret and expand * instead
+of passing it to the rrdtool command.
+
+After viewing this PNG, you notice the "m" (milli) has
+disappeared. This it what the correct result would be. Also, a label
+has been added to the image. Apart from the things mentioned above,
+the PNG should look the same.
+
+The calculations are specified in the CDEF part above and are in
+Reverse Polish Notation ("RPN"). What we requested RRDtool to do is:
+"take the data source myspeed and the number 1000; multiply
+those". Don't bother with RPN yet, it will be explained later on in
+more detail. Also, you may want to read my tutorial on CDEFs and Steve
+Rader's tutorial on RPN. But first finish this tutorial.
+
+Hang on! If we can multiply values with 1'000, it should also be possible
+to display kilometers per hour from the same data!
+
+To change a value that is measured in meters per second:
+
+ Calculate meters per hour: value * 3'600
+ Calculate kilometers per hour: value / 1'000
+ Together this makes: value * (3'600/1'000) or value * 3.6
+
+In our example database we made a mistake and we need to compensate for
+this by multiplying with 1'000. Applying that correction:
+
+ value * 3.6 * 1'000 == value * 3'600
+
+Now let's create this PNG, and add some more magic ...
+
+ rrdtool graph speed3.png \
+ --start 920804400 --end 920808000 \
+ --vertical-label km/h \
+ DEF:myspeed=test.rrd:speed:AVERAGE \
+ "CDEF:kmh=myspeed,3600,*" \
+ CDEF:fast=kmh,100,GT,kmh,0,IF \
+ CDEF:good=kmh,100,GT,0,kmh,IF \
+ HRULE:100#0000FF:"Maximum allowed" \
+ AREA:good#00FF00:"Good speed" \
+ AREA:fast#FF0000:"Too fast"
+
+Note: here we use another means to escape the * operator by enclosing
+the whole string in double quotes.
+
+This graph looks much better. Speed is shown in KM/H and there is even
+an extra line with the maximum allowed speed (on the road I travel
+on). I also changed the colors used to display speed and changed it
+from a line into an area.
+
+The calculations are more complex now. For speed measurements within
+the speed limit they are:
+
+ Check if kmh is greater than 100 ( kmh,100 ) GT
+ If so, return 0, else kmh ((( kmh,100 ) GT ), 0, kmh) IF
+
+For values above the speed limit:
+
+ Check if kmh is greater than 100 ( kmh,100 ) GT
+ If so, return kmh, else return 0 ((( kmh,100) GT ), kmh, 0) IF
+
+=head2 Graphics Magic
+
+I like to believe there are virtually no limits to how RRDtool graph
+can manipulate data. I will not explain how it works, but look at the
+following PNG:
+
+ rrdtool graph speed4.png \
+ --start 920804400 --end 920808000 \
+ --vertical-label km/h \
+ DEF:myspeed=test.rrd:speed:AVERAGE \
+ "CDEF:kmh=myspeed,3600,*" \
+ CDEF:fast=kmh,100,GT,100,0,IF \
+ CDEF:over=kmh,100,GT,kmh,100,-,0,IF \
+ CDEF:good=kmh,100,GT,0,kmh,IF \
+ HRULE:100#0000FF:"Maximum allowed" \
+ AREA:good#00FF00:"Good speed" \
+ AREA:fast#550000:"Too fast" \
+ STACK:over#FF0000:"Over speed"
+
+Let's create a quick and dirty HTML page to view the three PNGs:
+
+ <HTML><HEAD><TITLE>Speed</TITLE></HEAD><BODY>
+ <IMG src="speed2.png" alt="Speed in meters per second">
+ <BR>
+ <IMG src="speed3.png" alt="Speed in kilometers per hour">
+ <BR>
+ <IMG src="speed4.png" alt="Traveled too fast?">
+ </BODY></HTML>
+
+Name the file "speed.html" or similar, and look at it in your web browser.
+
+Now, all you have to do is measure the values regularly and update the
+database. When you want to view the data, recreate the PNGs and make
+sure to refresh them in your browser. (Note: just clicking reload may
+not be enough, especially when proxies are involved. Try shift-reload
+or ctrl-F5).
+
+=head2 Updates in Reality
+
+We've already used the C<update> command: it took one or more
+parameters in the form of "<time>:<value>". You'll be glad to know
+that you can specify the current time by filling in a "N" as the time.
+Or you could use the "time" function in Perl (the shortest example in
+this tutorial):
+
+ perl -e 'print time, "\n" '
+
+How to run a program on regular intervals is OS specific. But here is
+an example in pseudo code:
+
+ - Get the value and put it in variable "$speed"
+ - rrdtool update speed.rrd N:$speed
+
+(do not try this with our test database, we'll use it in further examples)
+
+This is all. Run the above script every five minutes. When you need to know
+what the graphs look like, run the examples above. You could put them
+in a script as well. After running that script, view the page
+index.html we created above.
+
+=head2 Some words on SNMP
+
+I can imagine very few people that will be able to get real data from
+their car every five minutes. All other people will have to settle for
+some other kind of counter. You could measure the number of pages
+printed by a printer, for example, the cups of coffee made by the
+coffee machine, a device that counts the electricity used,
+whatever. Any incrementing counter can be monitored and graphed using
+the stuff you learned so far. Later on we will also be able to monitor
+other types of values like temperature.
+
+Most (?) people interested in RRDtool will use the counter that keeps track
+of octets (bytes) transfered by a network device. So let's do just
+that next. We will start with a description of how to collect data.
+
+Some people will make a remark that there are tools which can do this data
+collection for you. They are right! However, I feel it is important that
+you understand they are not necessary. When you have to determine why
+things went wrong you need to know how they work.
+
+One tool used in the example has been talked about very briefly in the
+beginning of this document, it is called SNMP. It is a way of talking
+to networked equipment. The tool I use below is called "snmpget" and
+this is how it works:
+
+ snmpget device password OID
+
+or
+
+ snmpget -v[version] -c[password] device OID
+
+For device you substitute the name, or the IP address, of your device.
+For password you use the "community read string" as it is called in the
+SNMP world. For some devices the default of "public" might work, however
+this can be disabled, altered or protected for privacy and security
+reasons. Read the documentation that comes with your device or program.
+
+Then there is this parameter, called OID, which means "object identifier".
+
+When you start to learn about SNMP it looks very confusing. It isn't
+all that difficult when you look at the Management Information Base
+("MIB"). It is an upside-down tree that describes data, with a single node
+as the root and from there a number of branches. These branches end
+up in another node, they branch out, etc. All the branches have a name
+and they form the path that we follow all the way down. The branches
+that we follow are named: iso, org, dod, internet, mgmt and mib-2.
+These names can also be written down as numbers and are 1 3 6 1 2 1.
+
+ iso.org.dod.internet.mgmt.mib-2 (1.3.6.1.2.1)
+
+There is a lot of confusion about the leading dot that some programs
+use. There is *no* leading dot in an OID. However, some programs
+can use the above part of OIDs as a default. To indicate the difference
+between abbreviated OIDs and full OIDs they need a leading dot when
+you specify the complete OID. Often those programs will leave out
+the default portion when returning the data to you. To make things
+worse, they have several default prefixes ...
+
+Ok, lets continue to the start of our OID: we had 1.3.6.1.2.1
+From there, we are especially interested in the branch "interfaces"
+which has number 2 (e.g., 1.3.6.1.2.1.2 or 1.3.6.1.2.1.interfaces).
+
+First, we have to get some SNMP program. First look if there is a
+pre-compiled package available for your OS. This is the preferred way.
+If not, you will have to get the sources yourself and compile those.
+The Internet is full of sources, programs etc. Find information using
+a search engine or whatever you prefer.
+
+Assume you got the program. First try to collect some data that is
+available on most systems. Remember: there is a short name for the
+part of the tree that interests us most in the world we live in!
+
+I will give an example which can be used on Fedora Core 3. If it
+doesn't work for you, work your way through the manual of snmp and
+adapt the example to make it work.
+
+ snmpget -v2c -c public myrouter system.sysDescr.0
+
+The device should answer with a description of itself, perhaps an
+empty one. Until you got a valid answer from a device, perhaps using a
+different "password", or a different device, there is no point in
+continuing.
+
+ snmpget -v2c -c public myrouter interfaces.ifNumber.0
+
+Hopefully you get a number as a result, the number of interfaces.
+If so, you can carry on and try a different program called "snmpwalk".
+
+ snmpwalk -v2c -c public myrouter interfaces.ifTable.ifEntry.ifDescr
+
+If it returns with a list of interfaces, you're almost there.
+Here's an example:
+ [user@host /home/alex]$ snmpwalk -v2c -c public cisco 2.2.1.2
+
+ interfaces.ifTable.ifEntry.ifDescr.1 = "BRI0: B-Channel 1"
+ interfaces.ifTable.ifEntry.ifDescr.2 = "BRI0: B-Channel 2"
+ interfaces.ifTable.ifEntry.ifDescr.3 = "BRI0" Hex: 42 52 49 30
+ interfaces.ifTable.ifEntry.ifDescr.4 = "Ethernet0"
+ interfaces.ifTable.ifEntry.ifDescr.5 = "Loopback0"
+
+On this cisco equipment, I would like to monitor the "Ethernet0"
+interface and from the above output I see that it is number four. I try:
+
+ [user@host /home/alex]$ snmpget -v2c -c public cisco 2.2.1.10.4 2.2.1.16.4
+
+ interfaces.ifTable.ifEntry.ifInOctets.4 = 2290729126
+ interfaces.ifTable.ifEntry.ifOutOctets.4 = 1256486519
+
+So now I have two OIDs to monitor and they are (in full, this time):
+
+ 1.3.6.1.2.1.2.2.1.10
+
+and
+
+ 1.3.6.1.2.1.2.2.1.16
+
+both with an interface number of 4.
+
+Don't get fooled, this wasn't my first try. It took some time for me too
+to understand what all these numbers mean. It does help a lot when they
+get translated into descriptive text... At least, when people are talking
+about MIBs and OIDs you know what it's all about.
+Do not forget the interface number (0 if it is not interface dependent)
+and try snmpwalk if you don't get an answer from snmpget.
+
+If you understand the above section and get numbers from your device, continue
+on with this tutorial. If not, then go back and re-read this part.
+
+=head2 A Real World Example
+
+Let the fun begin. First, create a new database. It contains data from
+two counters, called input and output. The data is put into archives
+that average it. They take 1, 6, 24 or 288 samples at a time.
+They also go into archives that keep the maximum numbers. This will be
+explained later on. The time in-between samples is 300 seconds, a good
+starting point, which is the same as five minutes.
+
+ 1 sample "averaged" stays 1 period of 5 minutes
+ 6 samples averaged become one average on 30 minutes
+ 24 samples averaged become one average on 2 hours
+ 288 samples averaged become one average on 1 day
+
+Lets try to be compatible with MRTG which stores about the following
+amount of data:
+
+ 600 5-minute samples: 2 days and 2 hours
+ 600 30-minute samples: 12.5 days
+ 600 2-hour samples: 50 days
+ 732 1-day samples: 732 days
+
+These ranges are appended, so the total amount of data stored in the
+database is approximately 797 days. RRDtool stores the data
+differently, it doesn't start the "weekly" archive where the "daily"
+archive stopped. For both archives the most recent data will be near
+"now" and therefore we will need to keep more data than MRTG does!
+
+We will need:
+
+ 600 samples of 5 minutes (2 days and 2 hours)
+ 700 samples of 30 minutes (2 days and 2 hours, plus 12.5 days)
+ 775 samples of 2 hours (above + 50 days)
+ 797 samples of 1 day (above + 732 days, rounded up to 797)
+
+ rrdtool create myrouter.rrd \
+ DS:input:COUNTER:600:U:U \
+ DS:output:COUNTER:600:U:U \
+ RRA:AVERAGE:0.5:1:600 \
+ RRA:AVERAGE:0.5:6:700 \
+ RRA:AVERAGE:0.5:24:775 \
+ RRA:AVERAGE:0.5:288:797 \
+ RRA:MAX:0.5:1:600 \
+ RRA:MAX:0.5:6:700 \
+ RRA:MAX:0.5:24:775 \
+ RRA:MAX:0.5:288:797
+
+Next thing to do is to collect data and store it. Here is an example.
+It is written partially in pseudo code, you will have to find out what
+to do exactly on your OS to make it work.
+
+ while not the end of the universe
+ do
+ get result of
+ snmpget router community 2.2.1.10.4
+ into variable $in
+ get result of
+ snmpget router community 2.2.1.16.4
+ into variable $out
+
+ rrdtool update myrouter.rrd N:$in:$out
+
+ wait for 5 minutes
+ done
+
+Then, after collecting data for a day, try to create an image using:
+
+ rrdtool graph myrouter-day.png --start -86400 \
+ DEF:inoctets=myrouter.rrd:input:AVERAGE \
+ DEF:outoctets=myrouter.rrd:output:AVERAGE \
+ AREA:inoctets#00FF00:"In traffic" \
+ LINE1:outoctets#0000FF:"Out traffic"
+
+This should produce a picture with one day worth of traffic.
+One day is 24 hours of 60 minutes of 60 seconds: 24*60*60=86'400, we
+start at now minus 86'400 seconds. We define (with DEFs) inoctets and
+outoctets as the average values from the database myrouter.rrd and draw
+an area for the "in" traffic and a line for the "out" traffic.
+
+View the image and keep logging data for a few more days.
+If you like, you could try the examples from the test database and
+see if you can get various options and calculations to work.
+
+Suggestion: Display in bytes per second and in bits per second. Make
+the Ethernet graphics go red if they are over four megabits per
+second.
+
+=head2 Consolidation Functions
+
+A few paragraphs back I mentioned the possibility of keeping
+the maximum values instead of the average values. Let's go
+into this a bit more.
+
+Recall all the stuff about the speed of the car. Suppose we drove at 144
+KM/H during 5 minutes and then were stopped by the police for 25 minutes.
+At the end of the lecture we would take our laptop and create and view the
+image taken from the database. If we look at the second RRA we did
+create, we would have the average from 6 samples. The samples measured
+would be 144+0+0+0+0+0=144, divided by 30 minutes, corrected for the
+error by 1000, translated into KM/H, with a result of 24 KM/H.
+I would still get a ticket but not for speeding anymore :)
+
+Obviously, in this case we shouldn't look at the averages. In some
+cases they are handy. If you want to know how many KM you had traveled,
+the averaged picture would be the right one to look at. On the other hand, for
+the speed that we traveled at, the maximum numbers seen is much more
+interesting. Later we will see more types.
+
+It is the same for data. If you want to know the amount, look at the
+averages. If you want to know the rate, look at the maximum.
+Over time, they will grow apart more and more. In the last database
+we have created, there are two archives that keep data per day. The
+archive that keeps averages will show low numbers, the archive that
+shows maxima will have higher numbers.
+
+For my car this would translate in averages per day of 96/24=4 KM/H
+(as I travel about 94 kilometers on a day) during working days, and
+maxima of 120 KM/H (my top speed that I reach every day).
+
+Big difference. Do not look at the second graph to estimate the
+distances that I travel and do not look at the first graph to
+estimate my speed. This will work if the samples are close together,
+as they are in five minutes, but not if you average.
+
+On some days, I go for a long ride. If I go across Europe and travel
+for 12 hours, the first graph will rise to about 60 KM/H. The second
+one will show 180 KM/H. This means that I traveled a distance of 60
+KM/H times 24 H = 1440 KM. I did this with a higher speed and a
+maximum around 180 KM/H. However, it probably doesn't mean that I
+traveled for 8 hours at a constant speed of 180 KM/H!
+
+This is a real example: go with the flow through Germany (fast!) and stop
+a few times for gas and coffee. Drive slowly through Austria and the
+Netherlands. Be careful in the mountains and villages. If you would
+look at the graphs created from the five-minute averages you would
+get a totally different picture. You would see the same values on the
+average and maximum graphs (provided I measured every 300 seconds).
+You would be able to see when I stopped, when I was in top gear, when
+I drove over fast highways etc. The granularity of the data is much
+higher, so you can see more. However, this takes 12 samples per hour,
+or 288 values per day, so it would be a lot of data over a longer
+period of time. Therefore we average it, eventually to one value per
+day. From this one value, we cannot see much detail, of course.
+
+Make sure you understand the last few paragraphs. There is no value
+in only a line and a few axis, you need to know what they mean and
+interpret the data in ana appropriate way. This is true for all data.
+
+The biggest mistake you can make is to use the collected data for
+something that it is not suitable for. You would be better off if
+you didn't have the graph at all.
+
+
+=head2 Let's review what you now should know
+
+You know how to create a database and can put data in it. You can get
+the numbers out again by creating an image, do math on the data from
+the database and view the resulte instead of the raw data. You know
+about the difference between averages and maxima, and when to use
+which (or at least you should have an idea).
+
+RRDtool can do more than what we have learned up to now. Before you
+continue with the rest of this doc, I recommend that you reread from
+the start and try some modifications on the examples. Make sure you
+fully understand everything. It will be worth the effort and helps
+you not only with the rest of this tutorial, but also in your day to day
+monitoring long after you read this introduction.
+
+=head2 Data Source Types
+
+All right, you feel like continuing. Welcome back and get ready
+for an increased speed in the examples and explanations.
+
+You know that in order to view a counter over time, you have to
+take two numbers and divide the difference of them between the
+time lapsed. This makes sense for the examples I gave you but there
+are other possibilities. For instance, I'm able to retrieve the
+temperature from my router in three places namely the inlet, the
+so called hot-spot and the exhaust. These values are not counters.
+If I take the difference of the two samples and divide that by
+300 seconds I would be asking for the temperature change per second.
+Hopefully this is zero! If not, the computer room is probably on fire :)
+
+So, what can we do? We can tell RRDtool to store the values we measure
+directly as they are (this is not entirely true but close enough). The
+graphs we make will look much better, they will show a rather constant
+value. I know when the router is busy (it
+works -> it uses more electricity -> it generates more heat -> the
+temperature rises). I know when the doors are left open (the room is
+air conditioned) -> the warm air from the rest of the building flows into the
+computer room -> the inlet temperature rises). Etc. The data type we
+use when creating the database before was counter, we now have a
+different data type and thus a different name for it. It is called
+GAUGE. There are more such data types:
+
+ - COUNTER we already know this one
+ - GAUGE we just learned this one
+ - DERIVE
+ - ABSOLUTE
+
+The two additional types are DERIVE and ABSOLUTE. Absolute can be used like
+counter with one difference: RRDtool assumes the counter is reset when
+it's read. That is: its delta is known without calculation by RRDtool
+whereas RRDtool needs to calculate it for the counter type.
+Example: our first example (12'345, 12'357, 12'363, 12'363) would read:
+unknown, 12, 6, 0. The rest of the calculations stay the same.
+The other one, derive, is like counter. Unlike counter, it can also
+decrease so it can have a negative delta. Again, the rest of the
+calculations stay the same.
+
+Let's try them all:
+
+ rrdtool create all.rrd --start 978300900 \
+ DS:a:COUNTER:600:U:U \
+ DS:b:GAUGE:600:U:U \
+ DS:c:DERIVE:600:U:U \
+ DS:d:ABSOLUTE:600:U:U \
+ RRA:AVERAGE:0.5:1:10
+ rrdtool update all.rrd \
+ 978301200:300:1:600:300 \
+ 978301500:600:3:1200:600 \
+ 978301800:900:5:1800:900 \
+ 978302100:1200:3:2400:1200 \
+ 978302400:1500:1:2400:1500 \
+ 978302700:1800:2:1800:1800 \
+ 978303000:2100:4:0:2100 \
+ 978303300:2400:6:600:2400 \
+ 978303600:2700:4:600:2700 \
+ 978303900:3000:2:1200:3000
+ rrdtool graph all1.png -s 978300600 -e 978304200 -h 400 \
+ DEF:linea=all.rrd:a:AVERAGE LINE3:linea#FF0000:"Line A" \
+ DEF:lineb=all.rrd:b:AVERAGE LINE3:lineb#00FF00:"Line B" \
+ DEF:linec=all.rrd:c:AVERAGE LINE3:linec#0000FF:"Line C" \
+ DEF:lined=all.rrd:d:AVERAGE LINE3:lined#000000:"Line D"
+
+=head2 RRDtool under the Microscope
+
+=over 2
+
+=item *
+
+Line A is a COUNTER type, so it should continuously increment and RRDtool
+must calculate the differences. Also, RRDtool needs to divide the
+difference by the amount of time lapsed. This should end up as a
+straight line at 1 (the deltas are 300, the time is 300).
+
+=item *
+
+Line B is of type GAUGE. These are "real" values so they should match
+what we put in: a sort of a wave.
+
+=item *
+
+Line C is of type DERIVE. It should be a counter that can decrease. It does
+so between 2'400 and 0, with 1'800 in-between.
+
+=item *
+
+Line D is of type ABSOLUTE. This is like counter but it works on
+values without calculating the difference. The numbers are the same
+and as you can see (hopefully) this has a different result.
+
+=back
+
+This translates in the following values, starting at 23:10 and ending
+at 00:10 the next day (where "u" means unknown/unplotted):
+
+ - Line A: u u 1 1 1 1 1 1 1 1 1 u
+ - Line B: u 1 3 5 3 1 2 4 6 4 2 u
+ - Line C: u u 2 2 2 0 -2 -6 2 0 2 u
+ - Line D: u 1 2 3 4 5 6 7 8 9 10 u
+
+If your PNG shows all this, you know you have entered the data correctly,
+the RRDtool executable is working properly, your viewer doesn't fool you,
+and you successfully entered the year 2000 :)
+
+You could try the same example four times, each time with only one of
+the lines.
+
+Let's go over the data again:
+
+=over 2
+
+=item *
+
+Line A: 300,600,900 and so on. The counter delta is a constant 300 and
+so is the time delta. A number divided by itself is always 1 (except
+when dividing by zero which is undefined/illegal).
+
+Why is it that the first point is unknown? We do know what we put into
+the database, right? True, But we didn't have a value to calculate the delta
+from, so we don't know where we started. It would be wrong to assume we
+started at zero so we don't!
+
+=item *
+
+Line B: There is nothing to calculate. The numbers are as they are.
+
+=item *
+
+Line C: Again, the start-out value is unknown. The same story is holds
+as for line A. In this case the deltas are not constant, therefore the line
+is not either. If we would put the same numbers in the database as we did for
+line A, we would have gotten the same line. Unlike type counter,
+this type can decrease and I hope to show you later on why
+this makes a difference.
+
+=item *
+
+Line D: Here the device calculates the deltas. Therefore we DO know the
+first delta and it is plotted. We had the same input as with line A, but
+the meaning of this input is different and thus the line is different.
+In this case the deltas increase each time with 300. The time delta
+stays at a constant 300 and therefore the division of the two gives
+increasing values.
+
+=back
+
+=head2 Counter Wraps
+
+There are a few more basics to show. Some important options are still to
+be covered and we haven't look at counter wraps yet. First the counter wrap:
+In our car we notice that the counter shows 999'987. We travel 20 KM and
+the counter should go to 1'000'007. Unfortunately, there are only six digits
+on our counter so it really shows 000'007. If we would plot that on a type
+DERIVE, it would mean that the counter was set back 999'980 KM. It wasn't,
+and there has to be some protection for this. This protection is only
+available for type COUNTER which should be used for this kind of counter
+anyways. How does it work? Type counter should never decrease and
+therefore RRDtool must assume it wrapped if it does decrease!
+If the delta is negative, this can be compensated for by adding the
+maximum value of the counter + 1. For our car this would be:
+
+ Delta = 7 - 999'987 = -999'980 (instead of 1'000'007-999'987=20)
+
+ Real delta = -999'980 + 999'999 + 1 = 20
+
+At the time of writing this document, RRDtool knows of counters that
+are either 32 bits or 64 bits of size. These counters can handle the
+following different values:
+
+ - 32 bits: 0 .. 4'294'967'295
+ - 64 bits: 0 .. 18'446'744'073'709'551'615
+
+If these numbers look strange to you, you can view them in
+their hexadecimal form:
+
+ - 32 bits: 0 .. FFFFFFFF
+ - 64 bits: 0 .. FFFFFFFFFFFFFFFF
+
+RRDtool handles both counters the same. If an overflow occurs and
+the delta would be negative, RRDtool first adds the maximum of a small
+counter + 1 to the delta. If the delta is still negative, it had to be
+the large counter that wrapped. Add the maximum possible value of the
+large counter + 1 and subtract the erroneously added small value.
+
+There is a risk in this: suppose the large counter wrapped while adding
+a huge delta, it could happen, theoretically, that adding the smaller value
+would make the delta positive. In this unlikely case the results would
+not be correct. The increase should be nearly as high as the maximum
+counter value for that to happen, so chances are you would have several
+other problems as well and this particular problem would not even be
+worth thinking about. Even though, I did include an example, so you
+can judge for yourself.
+
+The next section gives you some numerical examples for counter-wraps.
+Try to do the calculations yourself or just believe me if your calculator
+can't handle the numbers :)
+
+Correction numbers:
+
+ - 32 bits: (4'294'967'295 + 1) = 4'294'967'296
+ - 64 bits: (18'446'744'073'709'551'615 + 1)
+ - correction1 = 18'446'744'069'414'584'320
+
+ Before: 4'294'967'200
+ Increase: 100
+ Should become: 4'294'967'300
+ But really is: 4
+ Delta: -4'294'967'196
+ Correction1: -4'294'967'196 + 4'294'967'296 = 100
+
+ Before: 18'446'744'073'709'551'000
+ Increase: 800
+ Should become: 18'446'744'073'709'551'800
+ But really is: 184
+ Delta: -18'446'744'073'709'550'816
+ Correction1: -18'446'744'073'709'550'816
+ + 4'294'967'296 = -18'446'744'069'414'583'520
+ Correction2: -18'446'744'069'414'583'520
+ + 18'446'744'069'414'584'320 = 800
+
+ Before: 18'446'744'073'709'551'615 ( maximum value )
+ Increase: 18'446'744'069'414'584'320 ( absurd increase, minimum for
+ Should become: 36'893'488'143'124'135'935 this example to work )
+ But really is: 18'446'744'069'414'584'319
+ Delta: -4'294'967'296
+ Correction1: -4'294'967'296 + 4'294'967'296 = 0
+ (not negative -> no correction2)
+
+ Before: 18'446'744'073'709'551'615 ( maximum value )
+ Increase: 18'446'744'069'414'584'319 ( one less increase )
+ Should become: 36'893'488'143'124'135'934
+ But really is: 18'446'744'069'414'584'318
+ Delta: -4'294'967'297
+ Correction1: -4'294'967'297 + 4'294'967'296 = -1
+ Correction2: -1 + 18'446'744'069'414'584'320 = 18'446'744'069'414'584'319
+
+As you can see from the last two examples, you need strange numbers
+for RRDtool to fail (provided it's bug free of course), so this should
+not happen. However, SNMP or whatever method you choose to collect the
+data, might also report wrong numbers occasionally. We can't prevent all
+errors, but there are some things we can do. The RRDtool "create" command
+takes two special parameters for this. They define
+the minimum and maximum allowed values. Until now, we used "U", meaning
+"unknown". If you provide values for one or both of them and if RRDtool
+receives data points that are outside these limits, it will ignore those
+values. For a thermometer in degrees Celsius, the absolute minimum is
+just under -273. For my router, I can assume this minimum is much higher
+so I would set it to 10, where as the maximum temperature I would
+set to 80. Any higher and the device would be out of order.
+
+For the speed of my car, I would never expect negative numbers and
+also I would not expect a speed higher than 230. Anything else,
+and there must have been an error. Remember: the opposite is not true,
+if the numbers pass this check, it doesn't mean that they are
+correct. Always judge the graph with a healthy dose of suspicion if it
+seems weird to you.
+
+=head2 Data Resampling
+
+One important feature of RRDtool has not been explained yet: it is
+virtually impossible to collect data and feed it into RRDtool on exact
+intervals. RRDtool therefore interpolates the data, so they are stored
+on exact intervals. If you do not know what this means or how it
+works, then here's the help you seek:
+
+Suppose a counter increases by exactly one for every second. You want
+to measure it in 300 seconds intervals. You should retrieve values
+that are exactly 300 apart. However, due to various circumstances you
+are a few seconds late and the interval is 303. The delta will also be
+303 in that case. Obviously, RRDtool should not put 303 in the database
+and make you believe that the counter increased by 303 in 300 seconds.
+This is where RRDtool interpolates: it alters the 303 value as if it
+would have been stored earlier and it will be 300 in 300 seconds.
+Next time you are at exactly the right time. This means that the current
+interval is 297 seconds and also the counter increased by 297. Again,
+RRDtool interpolates and stores 300 as it should be.
+
+ in the RRD in reality
+
+ time+000: 0 delta="U" time+000: 0 delta="U"
+ time+300: 300 delta=300 time+300: 300 delta=300
+ time+600: 600 delta=300 time+603: 603 delta=303
+ time+900: 900 delta=300 time+900: 900 delta=297
+
+Let's create two identical databases. I've chosen the time range 920'805'000
+to 920'805'900 as this goes very well with the example numbers.
+
+ rrdtool create seconds1.rrd \
+ --start 920804700 \
+ DS:seconds:COUNTER:600:U:U \
+ RRA:AVERAGE:0.5:1:24
+
+Make a copy
+
+ for Unix: cp seconds1.rrd seconds2.rrd
+ for Dos: copy seconds1.rrd seconds2.rrd
+ for vms: how would I know :)
+
+Put in some data
+
+ rrdtool update seconds1.rrd \
+ 920805000:000 920805300:300 920805600:600 920805900:900
+ rrdtool update seconds2.rrd \
+ 920805000:000 920805300:300 920805603:603 920805900:900
+
+Create output
+
+ rrdtool graph seconds1.png \
+ --start 920804700 --end 920806200 \
+ --height 200 \
+ --upper-limit 1.05 --lower-limit 0.95 --rigid \
+ DEF:seconds=seconds1.rrd:seconds:AVERAGE \
+ CDEF:unknown=seconds,UN \
+ LINE2:seconds#0000FF \
+ AREA:unknown#FF0000
+ rrdtool graph seconds2.png \
+ --start 920804700 --end 920806200 \
+ --height 200 \
+ --upper-limit 1.05 --lower-limit 0.95 --rigid \
+ DEF:seconds=seconds2.rrd:seconds:AVERAGE \
+ CDEF:unknown=seconds,UN \
+ LINE2:seconds#0000FF \
+ AREA:unknown#FF0000
+
+View both images together (add them to your index.html file)
+and compare. Both graphs should show the same, despite the
+input being different.
+
+=head1 WRAPUP
+
+It's time now to wrap up this tutorial. We covered all the basics for
+you to be able to work with RRDtool and to read the additional
+documentation available. There is plenty more to discover about
+RRDtool and you will find more and more uses for this package. You can
+easly create graphs using just the examples provided and using only
+RRDtool. You can also use one of the front ends to RRDtool that are
+available.
+
+=head1 MAILINGLIST
+
+Remember to subscribe to the RRDtool mailing list. Even if you are not
+answering to mails that come by, it helps both you and the rest of the
+users. A lot of the stuff that I know about MRTG (and therefore about
+RRDtool) I've learned while just reading the list without posting to
+it. I did not need to ask the basic questions as they are answered in
+the FAQ (read it!) and in various mails by other users. With
+thousands of users all over the world, there will always be people who
+ask questions that you can answer because you read this and other
+documentation and they didn't.
+
+=head1 SEE ALSO
+
+The RRDtool manpages
+
+=head1 AUTHOR
+
+I hope you enjoyed the examples and their descriptions. If you do, help
+other people by pointing them to this document when they are asking
+basic questions. They will not only get their answers, but at the same
+time learn a whole lot more.
+
+Alex van den Bogaerdt
+E<lt>alex@ergens.op.het.netE<gt>
+
diff --git a/program/doc/rrdupdate.pod b/program/doc/rrdupdate.pod
--- /dev/null
@@ -0,0 +1,101 @@
+=head1 NAME
+
+rrdupdate - Store a new set of values into the RRD
+
+=head1 SYNOPSIS
+
+B<rrdtool> {B<update> | B<updatev>} I<filename>
+S<[B<--template>|B<-t> I<ds-name>[B<:>I<ds-name>]...]>
+S<B<N>|I<timestamp>B<:>I<value>[B<:>I<value>...]>
+S<I<at-timestamp>B<@>I<value>[B<:>I<value>...]>
+S<[I<timestamp>B<:>I<value>[B<:>I<value>...] ...]>
+
+=head1 DESCRIPTION
+
+The B<update> function feeds new data values into an B<RRD>. The data
+is time aligned (interpolated) according to the properties of the
+B<RRD> to which the data is written.
+
+=over 8
+
+=item B<updatev>
+
+This alternate version of B<update> takes the same arguments and
+performs the same function. The I<v> stands for I<verbose>, which
+describes the output returned. B<updatev> returns a list of any and all
+consolidated data points (CDPs) written to disk as a result of the
+invocation of update. The values are indexed by timestamp (time_t),
+RRA (consolidation function and PDPs per CDP), and data source (name).
+Note that depending on the arguments of the current and previous call to
+update, the list may have no entries or a large number of entries.
+
+=item I<filename>
+
+The name of the B<RRD> you want to update.
+
+=item B<--template>|B<-t> I<ds-name>[B<:>I<ds-name>]...
+
+By default, the B<update> function expects its data input in the order
+the data sources are defined in the RRD, excluding any COMPUTE data
+sources (i.e. if the third data source B<DST> is COMPUTE, the third
+input value will be mapped to the fourth data source in the B<RRD> and
+so on). This is not very error resistant, as you might be sending the
+wrong data into an RRD.
+
+The template switch allows you to specify which data sources you are
+going to update and in which order. If the data sources specified in
+the template are not available in the RRD file, the update process
+will abort with an error message.
+
+While it appears possible with the template switch to update data sources
+asynchronously, B<RRDtool> implicitly assigns non-COMPUTE data sources missing
+from the template the I<*UNKNOWN*> value.
+
+Do not specify a value for a COMPUTE B<DST> in the B<update>
+function. If this is done accidentally (and this can only be done
+using the template switch), B<RRDtool> will ignore the value specified
+for the COMPUTE B<DST>.
+
+=item B<N>|I<timestamp>B<:>I<value>[B<:>I<value>...]
+
+The data used for updating the RRD was acquired at a certain
+time. This time can either be defined in seconds since 1970-01-01 or
+by using the letter 'N', in which case the update time is set to be
+the current time. Negative time values are subtracted from the current
+time. An AT_STYLE TIME SPECIFICATION (see the I<rrdfetch>
+documentation) may also be used by delimiting the end of the time
+specification with the '@' character instead of a ':'. Getting the
+timing right to the second is especially important when you are
+working with data-sources of type B<COUNTER>, B<DERIVE> or
+B<ABSOLUTE>.
+
+The remaining elements of the argument are DS updates. The order of
+this list is the same as the order the data sources were defined in
+the RRA. If there is no data for a certain data-source, the letter
+B<U> (e.g., N:0.1:U:1) can be specified.
+
+The format of the value acquired from the data source is dependent on
+the data source type chosen. Normally it will be numeric, but the data
+acquisition modules may impose their very own parsing of this
+parameter as long as the colon (B<:>) remains the data source value
+separator.
+
+=back
+
+=head1 EXAMPLE
+
+C<rrdtool update demo1.rrd N:3.44:3.15:U:23>
+
+Update the database file demo1.rrd with 3 known and one I<*UNKNOWN*>
+value. Use the current time as the update time.
+
+C<rrdtool update demo2.rrd 887457267:U 887457521:22 887457903:2.7>
+
+Update the database file demo2.rrd which expects data from a single
+data-source, three times. First with an I<*UNKNOWN*> value then with two
+regular readings. The update interval seems to be around 300 seconds.
+
+=head1 AUTHOR
+
+Tobias Oetiker <tobi@oetiker.ch>
+
diff --git a/program/doc/rrdxport.pod b/program/doc/rrdxport.pod
--- /dev/null
+++ b/program/doc/rrdxport.pod
@@ -0,0 +1,144 @@
+=head1 NAME
+
+rrdxport - Export data in XML format based on data from one or several RRD
+
+=head1 SYNOPSIS
+
+B<rrdtool> B<xport>
+S<[B<-s>|B<--start> I<seconds>]>
+S<[B<-e>|B<--end> I<seconds>]>
+S<[B<-m>|B<--maxrows> I<rows>]>
+S<[B<--step> I<value>]>
+S<[B<DEF:>I<vname>B<=>I<rrd>B<:>I<ds-name>B<:>I<CF>]>
+S<[B<CDEF:>I<vname>B<=>I<rpn-expression>]>
+S<[B<XPORT>B<:>I<vname>[B<:>I<legend>]]>
+
+=head1 DESCRIPTION
+
+The B<xport> function's main purpose is to write an XML formatted
+representation of the data stored in one or several B<RRD>s. It
+can also extract numerical reports.
+
+If no I<XPORT> statements are found, there will be no output.
+
+=over
+
+=item B<-s>|B<--start> I<seconds> (default end-1day)
+
+The time when the exported range should begin. Time in seconds since
+epoch (1970-01-01) is required. Negative numbers are relative to the
+current time. By default one day worth of data will be printed.
+See also AT-STYLE TIME SPECIFICATION section in the I<rrdfetch>
+documentation for a detailed explanation on how to specify time.
+
+=item B<-e>|B<--end> I<seconds> (default now)
+
+The time when the exported range should end. Time in seconds since epoch.
+See also AT-STYLE TIME SPECIFICATION section in the I<rrdfetch>
+documentation for a detailed explanation of ways to specify time.
+
+=item B<-m>|B<--maxrows> I<rows> (default 400 rows)
+
+This works like the B<-w>|B<--width> parameter of I<rrdgraph>.
+In fact it is exactly the same, but the parameter was renamed to
+describe its purpose in this module. See I<rrdgraph> documentation
+for details.
+
+=item B<--step> I<value> (default automatic)
+
+See L<rrdgraph> documentation.
+
+=item B<--enumds>
+
+The generated xml should contain the data values in enumerated tags.
+
+ <v0>val</v0><v1>val</v1>
+
+=item B<DEF:>I<vname>B<=>I<rrd>B<:>I<ds-name>B<:>I<CF>
+
+See I<rrdgraph> documentation.
+
+=item B<CDEF:>I<vname>B<=>I<rpn-expression>
+
+See I<rrdgraph> documentation.
+
+=item B<XPORT:>I<vname>B<:>B<:>I<legend>
+
+At least one I<XPORT> statement should be present. The values
+referenced by I<vname> are printed. Optionally add a legend.
+
+=back
+
+=head1 Output format
+
+The output is enclosed in an B<xport> element and contains two
+blocks. The first block is enclosed by a B<meta> element and
+contains some meta data. The second block is enclosed by a
+B<data> element and contains the data rows.
+
+Let's assume that the I<xport> command looks like this:
+
+ rrdtool xport \
+ --start now-1h --end now \
+ DEF:xx=host-inout.lo.rrd:output:AVERAGE \
+ DEF:yy=host-inout.lo.rrd:input:AVERAGE \
+ CDEF:aa=xx,yy,+,8,* \
+ XPORT:xx:"out bytes" \
+ XPORT:aa:"in and out bits"
+
+The resulting meta data section is (the values will depend on the
+RRD characteristics):
+
+ <meta>
+ <start>1020611700</start>
+ <step>300</step>
+ <end>1020615600</end>
+ <rows>14</rows>
+ <columns>2</columns>
+ <legend>
+ <entry>out bytes</entry>
+ <entry>in and out bits</entry>
+ </legend>
+ </meta>
+
+The resulting data section is:
+
+ <data>
+ <row><t>1020611700</t><v>3.4000000000e+00</v><v>5.4400000000e+01</v></row>
+ <row><t>1020612000</t><v>3.4000000000e+00</v><v>5.4400000000e+01</v></row>
+ <row><t>1020612300</t><v>3.4000000000e+00</v><v>5.4400000000e+01</v></row>
+ <row><t>1020612600</t><v>3.4113333333e+00</v><v>5.4581333333e+01</v></row>
+ <row><t>1020612900</t><v>3.4000000000e+00</v><v>5.4400000000e+01</v></row>
+ <row><t>1020613200</t><v>3.4000000000e+00</v><v>5.4400000000e+01</v></row>
+ <row><t>1020613500</t><v>3.4000000000e+00</v><v>5.4400000000e+01</v></row>
+ <row><t>1020613800</t><v>3.4000000000e+00</v><v>5.4400000000e+01</v></row>
+ <row><t>1020614100</t><v>3.4000000000e+00</v><v>5.4400000000e+01</v></row>
+ <row><t>1020614400</t><v>3.4000000000e+00</v><v>5.4400000000e+01</v></row>
+ <row><t>1020614700</t><v>3.7333333333e+00</v><v>5.9733333333e+01</v></row>
+ <row><t>1020615000</t><v>3.4000000000e+00</v><v>5.4400000000e+01</v></row>
+ <row><t>1020615300</t><v>3.4000000000e+00</v><v>5.4400000000e+01</v></row>
+ <row><t>1020615600</t><v>NaN</v><v>NaN</v></row>
+ </data>
+
+
+=head1 EXAMPLE 1
+
+ rrdtool xport \
+ DEF:out=if1-inouts.rrd:outoctets:AVERAGE \
+ XPORT:out:"out bytes"
+
+=head1 EXAMPLE 2
+
+ rrdtool xport \
+ DEF:out1=if1-inouts.rrd:outoctets:AVERAGE \
+ DEF:out2=if2-inouts.rrd:outoctets:AVERAGE \
+ CDEF:sum=out1,out2,+ \
+ XPORT:out1:"if1 out bytes" \
+ XPORT:out2:"if2 out bytes" \
+ XPORT:sum:"output sum"
+
+
+=head1 AUTHOR
+
+Tobias Oetiker E<lt>tobi@oetiker.chE<gt>
+
diff --git a/program/examples/4charts.pl.in b/program/examples/4charts.pl.in
--- /dev/null
@@ -0,0 +1,124 @@
+#! @PERL@
+
+#makes things work when run without install
+use lib qw( @prefix@/lib/perl );
+
+use RRDs;
+
+my $start=time;
+my $rrd="randome.rrd";
+my $name = $0;
+$name =~ s/.*\///g;
+$name =~ s/\.pl.*//g;
+
+RRDs::create ($rrd, "--start",$start-1, "--step",300,
+ "DS:a:GAUGE:600:U:U",
+ "DS:b:GAUGE:600:U:U",
+ "RRA:AVERAGE:0.5:1:300",
+ "RRA:MIN:0.5:12:300",
+ "RRA:MAX:0.5:12:300",
+);
+
+my $ERROR = RRDs::error;
+die "$0: unable to create `$rrd': $ERROR\n" if $ERROR;
+
+# dropt some data into the rrd
+my $t;
+for ($t=$start; $t<$start+300*300; $t+=300){
+ RRDs::update $rrd, "$t:".(sin($t/3000)*50+50).":".(sin($t/2500)*50+50);
+ if ($ERROR = RRDs::error) {
+ die "$0: unable to update `$rrd': $ERROR\n";
+ }
+}
+
+my $c1="f57912a0";
+my $c2="2a79e9a0";
+my $w=300;
+my $h=140;
+
+RRDs::graph "$name-L.png",
+ "--title", "2 LINES",
+ "--start", "now",
+ "--end", "start+15h",
+ "--lower-limit=0",
+ "--interlace",
+ "--imgformat","PNG",
+ "--width=$w",
+ "--height=$h",
+ "DEF:a=$rrd:a:AVERAGE",
+ "DEF:b=$rrd:b:AVERAGE",
+ "LINE1:a#$c1:Value A",
+ "LINE3:b#$c2:Value B",
+;
+
+RRDs::graph "$name-A.png",
+ "--title", "LINE and AREA",
+ "--start", "now",
+ "--end", "start+15h",
+ "--lower-limit=0",
+ "--interlace",
+ "--imgformat","PNG",
+ "--width=$w",
+ "--height=$h",
+ "DEF:a=$rrd:a:AVERAGE",
+ "DEF:b=$rrd:b:AVERAGE",
+ "AREA:a#$c1:Value A",
+ "LINE2:b#$c2:Value B",
+;
+
+RRDs::graph "$name-S.png",
+ "--title", "STACKED AREAS",
+ "--start", "now",
+ "--end", "start+15h",
+ "--lower-limit=0",
+ "--interlace",
+ "--imgformat","PNG",
+ "--width=$w",
+ "--height=$h",
+ "DEF:a=$rrd:a:AVERAGE",
+ "DEF:b=$rrd:b:AVERAGE",
+ "AREA:a#$c1:Value A",
+ "STACK:b#$c2:Value B",
+;
+
+
+RRDs::graph "$name-M.png",
+ "--title", "RPN Magic",
+ "--start", "now",
+ "--end", "start+15h",
+ "--lower-limit=0",
+ "--interlace",
+ "--imgformat","PNG",
+ "--width=$w",
+ "--height=$h",
+ "DEF:a=$rrd:a:AVERAGE",
+ "DEF:b=$rrd:b:AVERAGE",
+ "CDEF:alpha=TIME,3600,%,1800,LT,a,UNKN,IF",
+ "CDEF:beta=TIME,3600,%,1800,GE,b,UNKN,IF",
+ "AREA:alpha#$c1:Value A",
+ "LINE1:a#$c1",
+ "AREA:beta#$c2:Value B",
+ "LINE1:b#$c2",
+;
+
+RRDs::graph "$name-sample.png",
+ "--title", "Sample",
+ "--start", "now",
+ "--end", "start+15h",
+ "--lower-limit=0",
+ "--interlace",
+ "--imgformat","PNG",
+ "--width=600",
+ "--height=50",
+ "DEF:a=$rrd:a:AVERAGE",
+ "DEF:b=$rrd:a:MAX",
+ "AREA:a#00ff00:Incoming",
+ "LINE1:b#ff0000:Max Incoming",
+;
+
+if ($ERROR = RRDs::error) {
+ die "ERROR: $ERROR\n";
+};
+
+print "This script has created $name.png in the current directory\n";
+print "This demonstrates the use of the TIME and % RPN operators\n";
diff --git a/program/examples/Makefile.am b/program/examples/Makefile.am
--- /dev/null
@@ -0,0 +1,16 @@
+## Process this file with automake to produce Makefile.in
+
+#AUTOMAKE_OPTIONS = foreign
+
+#ACLOCAL_M4 = $(top_srcdir)/config/aclocal.m4
+
+EXTRA_DIST = cgi-demo.cgi.in
+
+examplesdir = $(pkgdatadir)/examples
+examples_SCRIPTS = cgi-demo.cgi piped-demo.pl shared-demo.pl \
+ stripes.pl bigtops.pl minmax.pl 4charts.pl perftest.pl
+
+cgi-demo.cgi: @srcdir@/cgi-demo.cgi.in $(top_builddir)/config.status
+ sed 's,@''exec_prefix@,$(exec_prefix),' @srcdir@/cgi-demo.cgi.in > $@
+ chmod a+x $@
+
diff --git a/program/examples/bigtops.pl.in b/program/examples/bigtops.pl.in
--- /dev/null
@@ -0,0 +1,50 @@
+#! @PERL@
+# this is for after install
+use lib qw( @prefix@/lib/perl );
+
+use RRDs;
+my $start=time;
+my $rrd="randome.rrd";
+my $name = $0;
+$name =~ s/.*\///g;
+$name =~ s/\.pl.*//g;
+
+RRDs::create ($rrd, "--start",$start-1, "--step",300,
+ "DS:a:GAUGE:600:U:U",
+ "DS:b:GAUGE:600:U:U",
+ "RRA:AVERAGE:0.5:1:300");
+my $ERROR = RRDs::error;
+die "$0: unable to create `$rrd': $ERROR\n" if $ERROR;
+
+# dropt some data into the rrd
+my $t;
+for ($t=$start; $t<$start+300*300; $t+=300){
+ RRDs::update $rrd, "$t:".rand(100).":".(sin($t/800)*50+50);
+ if ($ERROR = RRDs::error) {
+ die "$0: unable to update `$rrd': $ERROR\n";
+ }
+}
+
+RRDs::graph "$name.png",
+ "--title", uc($name)." Demo",
+ "--start", "$start + 1 h",
+ "--end", "start + 1000 min",
+ "--interlace",
+ "--imgformat","PNG",
+ "--width=450",
+ "DEF:a=$rrd:a:AVERAGE",
+ "DEF:b=$rrd:b:AVERAGE",
+ "CDEF:line=TIME,2400,%,300,LT,a,UNKN,IF",
+ "AREA:b#00b6e4:beta",
+ "AREA:line#0022e9:alpha",
+ "LINE3:line#ff0000",
+
+;
+
+if ($ERROR = RRDs::error) {
+ die "ERROR: $ERROR\n";
+};
+
+
+print "This script has created $name.png in the current directory\n";
+print "This demonstrates the use of the TIME and % RPN operators\n";
diff --git a/program/examples/cgi-demo.cgi.in b/program/examples/cgi-demo.cgi.in
--- /dev/null
@@ -0,0 +1,40 @@
+#! @exec_prefix@/bin/rrdcgi
+
+<HTML>
+<HEAD>
+<TITLE>RRDCGI Demo</TITLE>
+</HEAD>
+<BODY>
+Note: This Demo will only work if have previously run
+the <TT>shared-demo.pl</TT>.
+
+<H1>This is NOT traffic</H1>
+
+
+<P><RRD::GRAPH cgi-demo1.png
+ --lower-limit 0
+ --start 'end-10h'
+ --title "Graph in Localtime <RRD::TIME::NOW %c>"
+ DEF:alpha=shared-demo.rrd:a:AVERAGE
+ DEF:beta=shared-demo.rrd:b:AVERAGE
+ AREA:alpha#0022e9:"Trees on Mars"
+ STACK:beta#00b871:"Elchs in Norway">
+</P>
+
+<P><RRD::SETENV TZ UTC>
+ <RRD::GRAPH cgi-demo2.png
+ --lower-limit 0
+ --start 'end-10h'
+ --title "Graph in UTC"
+ DEF:alpha=shared-demo.rrd:a:AVERAGE
+ DEF:beta=shared-demo.rrd:b:AVERAGE
+ AREA:alpha#0022e9:"Trees on Mars"
+ STACK:beta#00b871:"Elchs in Norway">
+</P>
+
+</BODY>
+</HTML>
+
+
+
+
diff --git a/program/examples/minmax.pl.in b/program/examples/minmax.pl.in
--- /dev/null
@@ -0,0 +1,52 @@
+#! @PERL@
+
+use lib qw( @prefix@/lib/perl );
+
+use RRDs;
+my $start=time;
+my $rrd="randome.rrd";
+my $name = $0;
+$name =~ s/.*\///g;
+$name =~ s/\.pl.*//g;
+
+RRDs::create ($rrd, "--start",$start-1, "--step",300,
+ "DS:a:GAUGE:600:U:U",
+ "RRA:AVERAGE:0.5:1:300",
+ "RRA:MIN:0.5:12:300",
+ "RRA:MAX:0.5:12:300",
+);
+my $ERROR = RRDs::error;
+die "$0: unable to create `$rrd': $ERROR\n" if $ERROR;
+
+# dropt some data into the rrd
+my $t;
+for ($t=$start; $t<$start+300*300; $t+=300){
+ RRDs::update $rrd, "$t:".(sin($t/3000)*50+50);
+ if ($ERROR = RRDs::error) {
+ die "$0: unable to update `$rrd': $ERROR\n";
+ }
+}
+
+RRDs::graph "$name.png",
+ "--title", uc($name)." Demo",
+ "--start", "now",
+ "--end", "start+1d",
+ "--lower-limit=0",
+ "--interlace",
+ "--imgformat","PNG",
+ "--width=450",
+ "DEF:a=$rrd:a:AVERAGE",
+ "DEF:b=$rrd:a:MIN",
+ "DEF:c=$rrd:a:MAX",
+ "AREA:a#00b6e4:real",
+ "LINE1:b#0022e9:min",
+ "LINE1:c#00ee22:max",
+;
+
+if ($ERROR = RRDs::error) {
+ die "ERROR: $ERROR\n";
+};
+
+
+print "This script has created $name.png in the current directory\n";
+print "This demonstrates the use of MIN and MAX archives\n";
diff --git a/program/examples/perftest.pl.in b/program/examples/perftest.pl.in
--- /dev/null
@@ -0,0 +1,214 @@
+#! @PERL@
+#
+# $Id:$
+#
+# Created By Tobi Oetiker <tobi@oetiker.ch>
+# Date 2006-10-27
+#
+#makes programm work AFTER install
+
+my $Chunk = shift @ARGV || 10000;
+
+use lib qw( ../bindings/perl-shared/blib/lib ../bindings/perl-shared/blib/arch @prefix@/lib/perl );
+
+print <<NOTE;
+
+RRDtool Performance Tester
+--------------------------
+Running on $RRDs::VERSION;
+
+RRDtool update performance is ultimately disk-bound. Since very little data
+does actually get written to disk in a single update, the performance
+is highly dependent on the cache situation of your machine.
+
+This test tries to cater for this. It works like this:
+
+1) Create $Chunk RRD files in a tree
+
+2) For $Chunk -> Update RRD file, Sync
+
+3) goto 1)
+
+The numbers at the start of the row, show which
+RRA is being updated. So if several RRAs are being updated,
+you should see a slowdown as data has to be read from disk.
+
+The growning number in the second column shows how many RRD have been
+updated ... If everything is in cache, the number will Jump to $Chunk almost
+immediately. Then the system will seem to hang as 'sync' runs, to make sure
+all data has been written to disk prior to the next perftest run. This may
+not be 100% real-life, so you may want to remove the sync just for fun
+(then it is even less real-life, but different)
+
+NOTE
+
+use strict;
+use Time::HiRes qw(time);
+use RRDs;
+use IO::File;
+use Time::HiRes qw( usleep );
+
+sub create($$){
+ my $file = shift;
+ my $time = shift;
+ my $start = time; #since we loaded HiRes
+ RRDs::create ( $file.".rrd", "-b$time", qw(
+ -s300
+ DS:in:GAUGE:400:U:U
+ DS:out:GAUGE:400:U:U
+ RRA:AVERAGE:0.5:1:600
+ RRA:AVERAGE:0.5:6:600
+ RRA:MAX:0.5:6:600
+ RRA:AVERAGE:0.5:24:600
+ RRA:MAX:0.5:24:600
+ RRA:AVERAGE:0.5:144:600
+ RRA:MAX:0.5:144:600
+ ));
+ my $total = time - $start;
+ my $error = RRDs::error;
+ die $error if $error;
+ return $total;
+}
+
+sub update($$){
+ my $file = shift;
+ my $time = shift;
+ my $in = rand(1000);
+ my $out = rand(1000);
+ my $start = time;
+ my $ret = RRDs::updatev($file.".rrd", $time.":$in:$out");
+ my $total = time - $start;
+ my $error = RRDs::error;
+ die $error if $error;
+ return $total;
+}
+
+sub tune($){
+ my $file = shift;
+ my $start = time;
+ RRDs::tune ($file.".rrd", "-a","in:U","-a","out:U","-d","in:GAUGE","-d","out:GAUGE");
+ my $total = time - $start;
+ my $error = RRDs::error;
+ die $error if $error;
+ return $total;
+}
+
+sub infofetch($){
+ my $file = shift;
+ my $start = time;
+ my $info = RRDs::info ($file.".rrd");
+ my $error = RRDs::error;
+ die $error if $error;
+ my $lasttime = $info->{last_update} - $info->{last_update} % $info->{step};
+ my $fetch = RRDs::fetch ($file.".rrd",'AVERAGE','-s',$lasttime-1,'-e',$lasttime);
+ my $total = time - $start;
+ my $error = RRDs::error;
+ die $error if $error;
+ return $total;
+}
+
+sub stddev ($$$){ #http://en.wikipedia.org/wiki/Standard_deviation
+ my $sum = shift;
+ my $squaresum = shift;
+ my $count = shift;
+ return sqrt( 1 / $count * ( $squaresum - $sum*$sum / $count ))
+}
+
+sub makerrds($$$$){
+ my $count = shift;
+ my $total = shift;
+ my $list = shift;
+ my $time = shift;
+ my @files;
+ my $now = int(time);
+ for (1..$count){
+ my $id = sprintf ("%07d",$total);
+ $id =~ s/^(.)(.)(.)(.)(.)//;
+ push @$list, "$1/$2/$3/$4/$5/$id";
+ -d "$1" or mkdir "$1";
+ -d "$1/$2" or mkdir "$1/$2";
+ -d "$1/$2/$3" or mkdir "$1/$2/$3";
+ -d "$1/$2/$3/$4" or mkdir "$1/$2/$3/$4";
+ -d "$1/$2/$3/$4/$5" or mkdir "$1/$2/$3/$4/$5";
+ push @files, $list->[$total];
+ create $list->[$total++],$time-2;
+ if ($now < int(time)){
+ $now = int(time);
+ print STDERR "Creating RRDs: ", $count - $_," rrds to go. \r";
+ }
+ }
+ return $count;
+}
+
+sub main (){
+ mkdir "db-$$" or die $!;
+ chdir "db-$$";
+
+ my $step = $Chunk; # number of rrds to creat for every round
+
+ my @path;
+ my $time=int(time);
+
+ my $tracksize = 0;
+ my $uppntr = 0;
+
+
+ my %squaresum = ( cr => 0, up => 0 );
+ my %sum = ( cr => 0, up => 0 );
+ my %count =( cr => 0, up => 0 );
+
+ my $printtime = time;
+ my %step;
+ for (qw(1 6 24 144)){
+ $step{$_} = int($time / 300 / $_);
+ }
+
+ for (0..2) {
+ # enhance the track
+ $time += 300;
+ $tracksize += makerrds $step,$tracksize,\@path,$time;
+ # run benchmark
+
+ for (0..50){
+ $time += 300;
+ my $count = 0;
+ my $sum = 0;
+ my $squaresum = 0;
+ my $prefix = "";
+ for (qw(1 6 24 144)){
+ if (int($time / 300 / $_) > $step{$_}) {
+ $prefix .= "$_ ";
+ $step{$_} = int($time / 300 / $_);
+ }
+ else {
+ $prefix .= (" " x length("$_")) . " ";
+ }
+ }
+ my $now = int(time);
+ for (my $i = 0; $i<$tracksize;$i ++){
+ my $ntime = int(time);
+ if ($now < $ntime or $i == $tracksize){
+ printf STDERR "$prefix %7d \r",$i;
+ $now = $ntime;
+ }
+ my $elapsed = update($path[$i],$time);
+ $sum += $elapsed;
+ $squaresum += $elapsed**2;
+ $count++;
+ };
+ my $startsync = time;
+ print STDERR 's';
+ system "sync";
+ print STDERR "\h";
+ my $synctime = time-$startsync;
+ $sum += $synctime;
+ $squaresum += $synctime**2;
+ my $ups = $count/$sum;
+ my $sdv = stddev($sum,$squaresum,$count);
+ printf STDERR "$prefix %7d %6.0f Up/s (%6.5f sdv)\n",$count,$ups,$sdv;
+ }
+ print STDERR "\n";
+ }
+}
+
+main;
diff --git a/program/examples/piped-demo.pl.in b/program/examples/piped-demo.pl.in
--- /dev/null
@@ -0,0 +1,148 @@
+#! @PERL@
+
+use lib qw( @prefix@/lib/perl );
+
+use RRDp;
+
+# this simpulates a standard mrtg-2.x setup ... we can use this to
+# compare performance ...
+
+$main::DEBUG=0;
+$STEP = 300;
+$RUNS = 12*24*30*6;
+$GRUNS = 20;
+$RRD = "piped-demo.rrd";
+$SVG = "piped-demo.svg";
+$PNG = "piped-demo.png";
+
+# some magic to find the correct rrdtol executable
+$prefix="@prefix@";
+
+if ( -x "@exec_prefix@/bin/rrdtool") {
+ RRDp::start "@exec_prefix@/bin/rrdtool";
+} elsif ( -x "../../../bin/rrdtool") {
+ RRDp::start "../../../bin/rrdtool";
+} else {
+ RRDp::start "../src/rrdtool";
+}
+
+print "* Creating RRD with properties equivalent to mrtg-2.x logfile\n\n";
+
+$START = time()-$RUNS*$STEP;
+
+RRDp::cmd "create $RRD -b $START -s $STEP
+ DS:in:GAUGE:400:U:U
+ DS:out:GAUGE:400:U:U
+ RRA:AVERAGE:0.5:1:600
+ RRA:AVERAGE:0.5:6:600
+ RRA:MAX:0.5:6:600
+ RRA:AVERAGE:0.5:24:600
+ RRA:MAX:0.5:24:600
+ RRA:AVERAGE:0.5:144:600
+ RRA:MAX:0.5:144:600";
+
+$answer = RRDp::read;
+($user,$sys,$real) = ($RRDp::user,$RRDp::sys,$RRDp::real);
+
+print "* Filling RRD with $RUNS Values. One moment please ...\n";
+print " If you are running over NFS this will take *MUCH* longer\n\n";
+
+for ($i=$START+1;
+ $i<$START+$STEP*$RUNS;
+ $i+=$STEP+int((rand()-0.5)*7)){
+
+ $line = "update $RRD $i:".int(rand(100000)).":".int(rand(100000));
+ RRDp::cmd $line;
+ $answer = RRDp::read;
+}
+
+($user1,$sys1,$real1) = ($RRDp::user,$RRDp::sys,$RRDp::real);
+
+printf "-- performance analysis Update test\n".
+ " usr/upd: %1.5fs sys/upd: %1.5fs real/upd: %1.5fs upd/sec: %1.0f\n",
+ ($user1-$user)/($RUNS), ($sys1-$sys)/($RUNS),
+ ($real1-$real)/($RUNS), ($RUNS)/($real1-$real);
+print "\n";
+# creating some graphs
+
+print "* Creating $GRUNS SVG graphs: $SVG\n\n";
+$now = time;
+$localtime = scalar localtime(time);
+$localtime = s/:/\\:/g;
+for ($i=0;$i<$GRUNS;$i++) {
+RRDp::cmd "graph $SVG ", "--title 'Test GRAPH' ",
+ "--imgformat SVG --height 150 --vertical-label 'Dummy Units' ".
+ "--start now".(-$RUNS*$STEP),
+ "--color ARROW#bfbfbf",
+ "DEF:alpha=$RRD:in:AVERAGE",
+ "DEF:beta=$RRD:out:AVERAGE",
+ "CDEF:calc=alpha,beta,+,1.5,/",
+ "AREA:alpha#0022e9:Alpha",
+ "STACK:beta#00b871:Beta",
+ "STACK:calc#ff0091:Calc\\j",
+ "PRINT:alpha:AVERAGE:'Average Alpha\\: %1.2lf %S'",
+ "PRINT:alpha:MIN:'Min Alpha\\: %1.2lf %S'",
+ "PRINT:alpha:MAX:'Max Alpha\\: %1.2lf %S'",
+ "GPRINT:calc:AVERAGE:'Average calc\\: %1.2lf %S\\r'",
+ "GPRINT:calc:MIN:'Min calc\\: %1.2lf %S'",
+ "GPRINT:calc:MAX:'Max calc\\: %1.2lf %S'",
+ "VRULE:".($now-3600)."#008877:'60 Minutes ago'",
+ "COMMENT:'\\s'",
+ "COMMENT:'Graph created on\\: ".$localtime."\\c'";
+
+$answer = RRDp::read;
+}
+($user2,$sys2,$real2) = ($RRDp::user,$RRDp::sys,$RRDp::real);
+
+print "ANSWER:\n$$answer";
+
+printf "\n-- average Time for one Graph\n".
+ " usr/grf: %1.5fs sys/grf: %1.5fs real/grf: %1.5fs graphs/sec: %1.2f\n",
+ ($user2-$user1)/$GRUNS,
+ ($sys2-$sys1)/$GRUNS,
+ ($real2-$real1)/$GRUNS,
+ $GRUNS/($real2-$real1);
+
+print "\n\n* Creating $GRUNS PNG graphs: $PNG\n\n";
+
+$now = time;
+($user1,$sys1,$real1) = ($RRDp::user,$RRDp::sys,$RRDp::real);
+my $local = "".localtime(time());
+$local =~ s/:/\\:/g;
+
+for ($i=0;$i<$GRUNS;$i++) {
+RRDp::cmd "graph $PNG ", "--title 'Test GRAPH' ",
+ "--imgformat PNG --height 150 --vertical-label 'Dummy Units' ".
+ "--start now".(-$RUNS*$STEP),
+ "--color ARROW#bfbfbf",
+ "DEF:alpha=$RRD:in:AVERAGE",
+ "DEF:beta=$RRD:out:AVERAGE",
+ "CDEF:calc=alpha,beta,+,1.5,/",
+ "AREA:alpha#0022e9:Alpha",
+ "STACK:beta#00b871:Beta",
+ "STACK:calc#ff0091:Calc\\j",
+ "PRINT:alpha:AVERAGE:'Average Alpha\\: %1.2lf %S'",
+ "PRINT:alpha:MIN:'Min Alpha\\: %1.2lf %S'",
+ "PRINT:alpha:MAX:'Max Alpha\\: %1.2lf %S'",
+ "GPRINT:calc:AVERAGE:'Average calc\\: %1.2lf %S\\r'",
+ "GPRINT:calc:MIN:'Min calc\\: %1.2lf %S'",
+ "GPRINT:calc:MAX:'Max calc\\: %1.2lf %S'",
+ "VRULE:".($now-3600)."#008877:'60 Minutes ago'",
+ "COMMENT:'\\s'",
+ "COMMENT:'Graph created on\\: $local\\c'";
+
+$answer = RRDp::read;
+}
+($user2,$sys2,$real2) = ($RRDp::user,$RRDp::sys,$RRDp::real);
+
+print "ANSWER:\n$$answer";
+
+printf "\n-- average Time for one PNG Graph\n".
+ " usr/grf: %1.5fs sys/grf: %1.5fs real/grf: %1.5fs".
+ " graphs/sec: %1.2f\n\n",
+ ($user2-$user1)/$GRUNS,
+ ($sys2-$sys1)/$GRUNS,
+ ($real2-$real1)/$GRUNS,
+ $GRUNS/($real2-$real1);
+
+RRDp::end;
diff --git a/program/examples/shared-demo.pl.in b/program/examples/shared-demo.pl.in
--- /dev/null
@@ -0,0 +1,215 @@
+#! @PERL@
+
+
+END {
+ print "not ok 1\n" unless $loaded;
+ unlink "demo.rrd";
+}
+
+sub ok
+{
+ my($what, $result) = @_ ;
+ $ok_count++;
+ print "not " unless $result;
+ print "ok $ok_count $what\n";
+}
+
+#makes programm work AFTER install
+use lib qw( @prefix@/lib/perl );
+
+use strict;
+use vars qw(@ISA $loaded);
+
+use RRDs;
+$loaded = 1;
+my $ok_count = 1;
+
+ok("loading",1);
+
+######################### End of black magic.
+
+my $STEP = 100;
+my $RUNS = 500;
+my $GRUNS = 4;
+my $RRD1 = "shared-demo.rrd";
+my $RRD2 = "shared-demob.rrd";
+my $PNG1 = "shared-demo1.png";
+my $PNG2 = "shared-demo2.png";
+my $time = 30*int(time/30);
+my $START = $time-$RUNS*$STEP;
+
+my @options = ("-b", $START, "-s", $STEP,
+ "DS:a:GAUGE:2000:U:U",
+ "DS:b:GAUGE:200:U:U",
+ "DS:c:GAUGE:200:U:U",
+ "DS:d:GAUGE:200:U:U",
+ "DS:e:DERIVE:200:U:U",
+ "RRA:AVERAGE:0.5:1:5000",
+ "RRA:AVERAGE:0.5:10:500");
+
+print "* Creating RRD $RRD1 starting at $time.\n\n";
+RRDs::create $RRD1, @options;
+
+my $ERROR = RRDs::error;
+ok("create A", !$ERROR); # 2
+if ($ERROR) {
+ die "$0: unable to create `$RRD1': $ERROR\n";
+}
+
+print "* Creating RRD $RRD2 starting at $time.\n\n";
+RRDs::create $RRD2, @options;
+
+$ERROR= RRDs::error;
+ok("create B",!$ERROR); # 3
+if ($ERROR) {
+ die "$0: unable to create `$RRD2': $ERROR\n";
+}
+
+my $last = RRDs::last $RRD1;
+if ($ERROR = RRDs::error) {
+ die "$0: unable to get last `$RRD1': $ERROR\n";
+}
+ok("last A", $last == $START); # 4
+
+$last = RRDs::last $RRD2;
+if ($ERROR = RRDs::error) {
+ die "$0: unable to get last `$RRD2': $ERROR\n";
+}
+ok("last B", $last == $START); # 5
+
+print "* Filling $RRD1 and $RRD2 with $RUNS*5 values. One moment please ...\n";
+print "* If you are running over NFS this will take *MUCH* longer\n\n";
+
+srand(int($time / 100));
+
+@options = ();
+
+my $counter = 1e7;
+for (my $t=$START+1;
+ $t<$START+$STEP*$RUNS;
+ $t+=$STEP+int((rand()-0.5)*7)){
+ $counter += int(2500*sin($t/2000)*$STEP);
+ my $data = (1000+500*sin($t/1000)).":".
+ (1000+900*sin($t/2330)).":".
+ (2000*cos($t/1550)).":".
+ (3220*sin($t/3420)).":$counter";
+ push(@options, "$t:$data");
+ RRDs::update $RRD1, "$t:$data";
+ if ($ERROR = RRDs::error) {
+ die "$0: unable to update `$RRD1': $ERROR\n";
+ }
+}
+
+RRDs::update $RRD2, @options;
+
+if ($ERROR = RRDs::error) {
+ die "$0: unable to update `$RRD2': $ERROR\n";
+}
+
+print "* Creating $GRUNS graphs: $PNG1 & $PNG2\n\n";
+my $now = $time;
+for (my $i=0;$i<$GRUNS;$i++) {
+ my @rrd_pngs = ($RRD1, $PNG1, $RRD2, $PNG2);
+ while (@rrd_pngs) {
+ my $RRD = shift(@rrd_pngs);
+ my $PNG = shift(@rrd_pngs);
+ my ($graphret,$xs,$ys) = RRDs::graph $PNG, "--title", 'Test GRAPH',
+ '--base', '1024',
+ "--vertical-label", 'Dummy Units', "--start", (-$RUNS*$STEP),
+ "--end", $time,
+ "--interlace", "--imgformat","PNG",
+ "DEF:alpha=$RRD:a:AVERAGE",
+ "DEF:beta=$RRD:b:AVERAGE",
+ "DEF:gamma=$RRD:c:AVERAGE",
+ "DEF:delta=$RRD:d:AVERAGE",
+ "DEF:epsilon=$RRD:e:AVERAGE",
+ "CDEF:calc=alpha,beta,+,2,/,100,*,102,/",
+ "AREA:alpha#0022e9:Short",
+ "GPRINT:calc:MAX:Max calc %1.2lf",
+ "STACK:beta#00b871:Demo Text",
+ "GPRINT:calc:AVERAGE:Average calc %1.2lf",
+ "STACK:beta#0ad871:Demo Text 2",
+ "LINE1:gamma#ff0000:Line 1",
+ "LINE2:delta#888800:Line 2",
+ "LINE3:calc#00ff44:Line 3",
+ "LINE3:epsilon#000000:Line 4",
+ "HRULE:1500#ff8800:Horizontal Line at 1500",
+ "PRINT:alpha:AVERAGE:Average Alpha %1.2lf",
+ "PRINT:alpha:MIN:Min Alpha %1.2lf",
+ "PRINT:alpha:MAX:Max Alpha %1.2lf",
+ "GPRINT:calc:MIN:Min calc %1.2lf",
+ "VRULE:".($now-3600)."#008877:60 Minutes ago",
+ "VRULE:".($now-7200)."#008877:120 Minutes ago";
+
+ if ($ERROR = RRDs::error) {
+ die "ERROR: $ERROR\n";
+ } else {
+ print "Image Size: ${xs}x${ys}\n";
+ print "Graph Return:\n",(join "\n", @$graphret),"\n\n";
+ }
+ }
+}
+
+
+
+my ($start,$step,$names,$array) = RRDs::fetch $RRD1, "AVERAGE";
+$ERROR = RRDs::error;
+die "ERROR: $ERROR\n" if $ERROR ;
+print "start=$start, step=$step\n";
+print " ";
+map {printf("%12s",$_)} @$names ;
+print "\n";
+foreach my $line (@$array){
+ print "".localtime($start)," ";
+ $start += $step;
+ foreach my $val (@$line) {
+ printf "%12.1f", $val;
+ }
+ print "\n";
+}
+
+
+
+my ($start,$end,$step,$col_cnt,$legend,$data) =
+ RRDs::xport ("-m", 400,
+ "--start", "now-1day",
+ "--end", "now",
+ "DEF:alpha=$RRD1:a:AVERAGE",
+ "DEF:beta=$RRD1:d:AVERAGE",
+ "CDEF:calc=alpha,beta,+,2,/,100,*,102,/",
+ "XPORT:alpha:original ds",
+ "XPORT:calc:calculated values",
+ );
+
+my $ERROR = RRDs::error;
+die "$0: unable to xport: $ERROR\n" if $ERROR;
+
+print "\nrrdxport test:\n\n";
+print "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n\n";
+print "<xport>\n";
+print " <meta>\n";
+print " <start>$start</start>\n";
+print " <step>$step</step>\n";
+print " <end>$end</end>\n";
+print " <rows>", $#$data + 1, "</rows>\n";
+print " <columns>$col_cnt</columns>\n";
+print " <legend>\n";
+foreach my $entry (@$legend) {
+ print " <entry>$entry</entry>\n";
+}
+print " </legend>\n";
+print " </meta>\n";
+print " <data>\n";
+my $row_counter = 0;
+foreach my $row (@$data) {
+ $row_counter++;
+ print " <row id=\"$row_counter\"><t is=\"", scalar localtime($start), "\">$start</t>";
+ $start += $step;
+ foreach my $val (@$row) {
+ printf ("<v>%1.10e</v>",$val) if $val ne '';
+ print "<v>NaN</v>" if $val eq '';
+ }
+ print "</row>\n";
+}
+print " </data>\n";
+print "</xport>\n";
diff --git a/program/examples/stripes.pl.in b/program/examples/stripes.pl.in
--- /dev/null
@@ -0,0 +1,46 @@
+#! @PERL@
+use lib qw( @prefix@/lib/perl );
+
+use strict;
+use vars qw(@ISA $loaded);
+
+use RRDs;
+my $start=time;
+my $rrd="random.rrd";
+RRDs::create ($rrd, "--start",$start-1, "--step",300,
+ "DS:a:GAUGE:600:U:U",
+ "DS:b:GAUGE:600:U:U",
+ "RRA:AVERAGE:0.5:1:200");
+my $ERROR = RRDs::error;
+die "$0: unable to create `$rrd': $ERROR\n" if $ERROR;
+my $t;
+for ($t=$start; $t<$start+200*300; $t+=300){
+ RRDs::update $rrd, "$t:".rand(100).":".(sin($t/800)*50+50);
+ if ($ERROR = RRDs::error) {
+ die "$0: unable to update `$rrd': $ERROR\n";
+ }
+}
+RRDs::graph "stripes.png",
+ "--title", "Stripes Demo",
+ "--start", $start,
+ "--end", "start + 400 min",
+ "--interlace",
+ "--imgformat","PNG",
+ "--width=450",
+ "DEF:a=$rrd:a:AVERAGE",
+ "DEF:b=$rrd:b:AVERAGE",
+ "CDEF:alpha=TIME,1200,%,600,LT,a,UNKN,IF",
+ "CDEF:beta=TIME,1200,%,600,GE,b,UNKN,IF",
+ "AREA:alpha#0022e9:alpha",
+ "AREA:beta#00b674:beta",
+ "LINE1:b#ff4400:beta envelope\\c",
+ "COMMENT:\\s",
+ "COMMENT:alpha=TIME,1200,%,600,LT,a,UNKN,IF",
+ "COMMENT:beta=TIME,1200,%,600,GE,b,UNKN,IF\\j";
+if ($ERROR = RRDs::error) {
+ die "ERROR: $ERROR\n";
+};
+
+
+print "This script has created stripes.png in the current directory\n";
+print "This demonstrates the use of the TIME and % RPN operators\n";
diff --git a/program/favicon.ico b/program/favicon.ico
new file mode 100644 (file)
index 0000000..7d08dd4
Binary files /dev/null and b/program/favicon.ico differ
index 0000000..7d08dd4
Binary files /dev/null and b/program/favicon.ico differ
diff --git a/program/netware/Makefile b/program/netware/Makefile
--- /dev/null
+++ b/program/netware/Makefile
@@ -0,0 +1,555 @@
+# Gnu Makefile for NetWare target
+# for use with gcc/nlmconv or Metrowerks CodeWarrior compiler
+# use with: make -f Makefile [help|all|clean|dev|devclean|dist|distclean]
+#
+# $id: $
+#
+
+DESCR = Round Robin Database Tool $(RRD_VERSION_STR)
+COPYR = Copyright (c) 1997-2007 by Tobias Oetiker
+WWWURL = http://www.rrdtool.org/
+MTSAFE = YES
+#SCREEN = $(DESCR)
+STACK = 65535
+# Comment the line below if you dont want to load protected automatically.
+#LDRING = 3
+
+# You can set the default font used in graphs.
+# If not set here RRD defaults to DejaVuSansMono-Roman.ttf
+#RRD_DEFAULT_FONT = "sys:/java/nwgfx/lib/x11/fonts/ttf/tt0003m_.ttf"
+#RRD_DEFAULT_FONT = "VeraMono.ttf"
+
+# Vertical label angle: 90.0 (default) or 270.0
+RRDGRAPH_YLEGEND_ANGLE = 90.0
+
+# Set to one if you want to have piecharts.
+WITH_PIECHART = 0
+
+# Set the extension used for rrdcgi.
+ifndef CGIEXT
+CGIEXT = nlm
+endif
+
+# Edit the path below to point to your Novell NDK.
+ifndef NDKBASE
+NDKBASE = c:/novell
+endif
+
+# Base for the lib sources
+ifndef LIBBASE
+LIBBASE = ../..
+endif
+# All library code is statically linked to avoid problems with other lib NLMs.
+# Edit the path below to point to your libpng sources or set environment var.
+ifndef LIBPNG
+LIBPNG = $(LIBBASE)/libpng-1.2.16
+endif
+# Edit the path below to point to your freetype sources or set environment var.
+ifndef LIBFT2
+LIBFT2 = $(LIBBASE)/freetype-2.3.4
+endif
+# Edit the path below to point to your libart sources or set environment var.
+ifndef LIBART
+LIBART = $(LIBBASE)/libart_lgpl-2.3.17
+endif
+# Edit the path below to point to your zlib sources or set environment var.
+ifndef ZLIBSDK
+ZLIBSDK = $(LIBBASE)/zlib-1.2.3
+endif
+
+# Edit the path below to point to your distribution folder.
+ifndef DISTDIR
+DISTDIR = rrdtool-$(RRD_VERSION_STR)-nw
+endif
+DISTARC = $(DISTDIR).zip
+
+# Edit the path below to point to your distribution folder.
+ifndef DEVLDIR
+DEVLDIR = rrdtool-$(RRD_VERSION_STR)-sdk-nw
+endif
+DEVLARC = $(DEVLDIR).zip
+
+# whatever...
+# NO_NULL_REALLOC = 1
+
+# Edit the var below to point to your lib architecture.
+ifndef LIBARCH
+# LIBARCH = CLIB
+LIBARCH = LIBC
+endif
+
+# The following line defines your compiler.
+ifdef METROWERKS
+ CC = mwccnlm
+else
+ CC = gcc
+endif
+# RM = rm -f
+CP = cp -afv
+# if you want to mark the target as MTSAFE you will need a tool for
+# generating the xdc data for the linker; here's a minimal tool:
+# http://www.gknw.net/development/prgtools/mkxdc.zip
+MPKXDC = mkxdc
+# CodeWarrior is too stupid to set the internal name properly when
+# the extension is not a NLM and not a registered type. So we need
+# to fix that after linking (since CGI isnt a known type - argh!):
+# http://www.gknw.net/development/prgtools/fixnlmname.zip
+FIXNLMN = fixnlmname #-q
+# Here you can find a native Win32 binary of the original awk:
+# http://www.gknw.net/development/prgtools/awk.zip
+AWK = awk
+ZIP = zip -qzr9
+MV = mv -fv
+
+# must be equal to DEBUG or NDEBUG
+DB = NDEBUG
+# DB = DEBUG
+# Optimization: -O<n> or debugging: -g
+ifeq ($(DB),NDEBUG)
+ OPT = -O2
+ OBJDIR = release
+else
+ OPT = -g
+ OBJDIR = debug
+endif
+
+# Project root
+PROOT = ..
+
+# Include the version info retrieved from source.
+-include $(OBJDIR)/version.inc
+
+# Global flags for all compilers
+CFLAGS = $(OPT) -D$(DB) -nostdinc -DNETWARE -DN_PLAT_NLM -D_POSIX_SOURCE
+CFLAGS += -DHAVE_CONFIG_H
+
+ifeq ($(CC),mwccnlm)
+LD = mwldnlm
+LDFLAGS = -nostdlib $^ $(PRELUDE) $(LDLIBS) -o $@ -commandfile
+AR = $(LD)
+ARFLAGS = -nostdlib -type library -o
+LIBEXT = lib
+#RANLIB =
+CFLAGS += -gccinc -inline off -opt nointrinsics -proc 586
+CFLAGS += -relax_pointers
+#CFLAGS += -w on,nounused,nounusedexpr # -ansi strict
+ifeq ($(LIBARCH),LIBC)
+ PRELUDE = $(SDK_LIBC)/imports/libcpre.o
+ CFLAGS += -align 4
+else
+ PRELUDE = "$(METROWERKS)/Novell Support/libraries/runtime/prelude.obj"
+ CFLAGS += -include "$(METROWERKS)/Novell Support/headers/nlm_prefix.h"
+ CFLAGS += -align 1
+endif
+else
+LD = nlmconv
+LDFLAGS = -T
+AR = ar
+ARFLAGS = -cq
+LIBEXT = a
+RANLIB = ranlib
+CFLAGS += -fno-builtin -fpcc-struct-return -fno-strict-aliasing
+CFLAGS += -Wall -Wno-unused # -pedantic
+ifeq ($(LIBARCH),LIBC)
+ PRELUDE = $(SDK_LIBC)/imports/libcpre.gcc.o
+else
+ PRELUDE = $(NDK_ROOT)/pre/prelude.o
+ CFLAGS += -include $(NDKBASE)/nlmconv/genlm.h
+endif
+endif
+
+ifeq ($(findstring linux,$(OSTYPE)),linux)
+#include $(NDKBASE)/nlmconv/ncpfs.inc
+DL = '
+DS = /
+else
+DS = \\
+endif
+
+ifeq ($(MTSAFE),YES)
+ XDCOPT = -n
+endif
+ifeq ($(MTSAFE),NO)
+ XDCOPT = -u
+endif
+ifndef DESCR
+ DESCR = $(notdir $(@:.def=)) Command Extension
+endif
+DESCR += ($(LIBARCH)) - $(CC) build
+
+NDK_ROOT = $(NDKBASE)/ndk
+SDK_CLIB = $(NDK_ROOT)/nwsdk
+SDK_LIBC = $(NDK_ROOT)/libc
+
+INCLUDES += -I$(PROOT) -I$(PROOT)/src -I$(LIBPNG) -I$(LIBFT2)/include -I$(LIBART) -I$(ZLIBSDK)
+
+ifeq ($(LIBARCH),LIBC)
+ INCLUDES += -I$(SDK_LIBC)/include -I$(SDK_LIBC)/include/nks
+else
+ INCLUDES += -I$(SDK_CLIB)/include/nlm -I$(SDK_CLIB)/include
+endif
+
+CFLAGS += $(INCLUDES)
+
+vpath %.c $(PROOT)/src $(LIBPNG) $(LIBART)/libart_lgpl $(ZLIBSDK)
+
+RRDLIBOBJS = \
+ $(OBJDIR)/rrd_afm.o \
+ $(OBJDIR)/rrd_afm_data.o \
+ $(OBJDIR)/rrd_create.o \
+ $(OBJDIR)/rrd_diff.o \
+ $(OBJDIR)/rrd_dump.o \
+ $(OBJDIR)/rrd_error.o \
+ $(OBJDIR)/rrd_fetch.o \
+ $(OBJDIR)/rrd_first.o \
+ $(OBJDIR)/rrd_format.o \
+ $(OBJDIR)/rrd_gfx.o \
+ $(OBJDIR)/rrd_graph.o \
+ $(OBJDIR)/rrd_graph_helper.o \
+ $(OBJDIR)/rrd_hw.o \
+ $(OBJDIR)/rrd_info.o \
+ $(OBJDIR)/rrd_last.o \
+ $(OBJDIR)/rrd_lastupdate.o \
+ $(OBJDIR)/rrd_nan_inf.o \
+ $(OBJDIR)/rrd_open.o \
+ $(OBJDIR)/rrd_resize.o \
+ $(OBJDIR)/rrd_restore.o \
+ $(OBJDIR)/rrd_rpncalc.o \
+ $(OBJDIR)/rrd_tune.o \
+ $(OBJDIR)/rrd_update.o \
+ $(OBJDIR)/rrd_version.o \
+ $(OBJDIR)/rrd_xport.o \
+ $(OBJDIR)/rrd_thread_safe.o \
+ $(EOLIST)
+
+XLIBOBJS = \
+ $(OBJDIR)/rrd_getopt.o \
+ $(OBJDIR)/rrd_getopt1.o \
+ $(OBJDIR)/art_rgba_svp.o \
+ $(OBJDIR)/hash_32.o \
+ $(OBJDIR)/rrd_parsetime.o \
+ $(OBJDIR)/pngsize.o \
+ $(EOLIST)
+
+PNGLIBOBJS = \
+ $(OBJDIR)/png.o \
+ $(OBJDIR)/pngerror.o \
+ $(OBJDIR)/pngget.o \
+ $(OBJDIR)/pngmem.o \
+ $(OBJDIR)/pngpread.o \
+ $(OBJDIR)/pngread.o \
+ $(OBJDIR)/pngrio.o \
+ $(OBJDIR)/pngrtran.o \
+ $(OBJDIR)/pngrutil.o \
+ $(OBJDIR)/pngset.o \
+ $(OBJDIR)/pngtrans.o \
+ $(OBJDIR)/pngwio.o \
+ $(OBJDIR)/pngwrite.o \
+ $(OBJDIR)/pngwtran.o \
+ $(OBJDIR)/pngwutil.o \
+ $(EOLIST)
+ifeq "$(wildcard $(LIBPNG)/pnggccrd.c)" "$(LIBPNG)/pnggccrd.c"
+PNGLIBOBJS += \
+ $(OBJDIR)/pnggccrd.o \
+ $(OBJDIR)/pngvcrd.o \
+ $(EOLIST)
+endif
+
+ZLIBOBJS = \
+ $(OBJDIR)/adler32.o \
+ $(OBJDIR)/compress.o \
+ $(OBJDIR)/crc32.o \
+ $(OBJDIR)/deflate.o \
+ $(OBJDIR)/inflate.o \
+ $(OBJDIR)/inffast.o \
+ $(OBJDIR)/inftrees.o \
+ $(OBJDIR)/trees.o \
+ $(OBJDIR)/zutil.o \
+ $(EOLIST)
+ifeq "$(wildcard $(ZLIBSDK)/infblock.c)" "$(ZLIBSDK)/infblock.c"
+ZLIBOBJS += \
+ $(OBJDIR)/infblock.o \
+ $(OBJDIR)/infcodes.o \
+ $(OBJDIR)/infutil.o \
+ $(EOLIST)
+endif
+
+ARTLIBOBJS = \
+ $(patsubst $(LIBART)/libart_lgpl/%.c,$(OBJDIR)/%.o,$(wildcard $(LIBART)/libart_lgpl/art_*.c))
+
+OBJS := $(RRDLIBOBJS) $(XLIBOBJS) $(PNGLIBOBJS) $(ARTLIBOBJS) $(ZLIBOBJS)
+OBJCGI := $(OBJS) $(OBJDIR)/rrd_cgi.o
+OBJTOOL := $(OBJS) $(OBJDIR)/rrd_tool.o
+
+LDLIBS += $(LIBFT2)/builds/netware/libc/libft2.$(LIBEXT)
+
+
+all: rrdtool rrdcgi
+
+rrdtool: $(OBJDIR) $(PROOT)/rrd_config.h $(OBJDIR)/rrdtool.nlm
+rrdcgi: $(OBJDIR) $(PROOT)/rrd_config.h $(OBJDIR)/rrdcgi.$(CGIEXT)
+librrd: $(OBJDIR) $(PROOT)/rrd_config.h $(OBJDIR)/librrd.$(LIBEXT)
+
+FORCE: ;
+
+dist: all $(DISTDIR) $(DISTDIR)/readme.txt
+ @-$(CP) $(OBJDIR)/rrdcgi.$(CGIEXT) $(DISTDIR)
+ @-$(CP) $(OBJDIR)/rrdtool.nlm $(DISTDIR)
+ @-$(CP) $(PROOT)/src/*.ttf $(DISTDIR)
+ @-$(CP) $(PROOT)/CHANGES $(DISTDIR)
+ @-$(CP) $(PROOT)/COPYING $(DISTDIR)
+ @-$(CP) $(PROOT)/COPYRIGHT $(DISTDIR)
+ @-$(CP) $(PROOT)/NEWS $(DISTDIR)
+ @-$(CP) $(PROOT)/README $(DISTDIR)
+ @echo Creating $(DISTARC)
+ @$(ZIP) $(DISTARC) $(DISTDIR)/* < $(DISTDIR)/readme.txt
+
+dev: librrd $(DEVLDIR) $(DEVLDIR)/readme.txt
+ @-mkdir $(DEVLDIR)$(DS)include
+ @-mkdir $(DEVLDIR)$(DS)lib
+ @-mkdir $(DEVLDIR)$(DS)src
+ @-$(CP) $(OBJDIR)/librrd.$(LIBEXT) $(DEVLDIR)/lib
+ @-$(CP) $(PROOT)/rrd_config.h $(DEVLDIR)/include
+ @-$(CP) $(PROOT)/src/rrd.h $(DEVLDIR)/include
+ @-$(CP) $(PROOT)/src/*.ttf $(DEVLDIR)/src
+ @-$(CP) $(PROOT)/CHANGES $(DEVLDIR)
+ @-$(CP) $(PROOT)/COPYING $(DEVLDIR)
+ @-$(CP) $(PROOT)/COPYRIGHT $(DEVLDIR)
+ @-$(CP) $(PROOT)/NEWS $(DEVLDIR)
+ @-$(CP) $(PROOT)/README $(DEVLDIR)
+ @echo Creating $(DEVLARC)
+ @$(ZIP) $(DEVLARC) $(DEVLDIR)/* < $(DEVLDIR)/readme.txt
+
+clean:
+ -$(RM) -r $(OBJDIR)
+ -$(RM) $(PROOT)/rrd_config.h
+
+distclean: clean
+ -$(RM) -r $(DISTDIR)
+ -$(RM) $(DISTARC)
+
+devclean: clean
+ -$(RM) -r $(DEVLDIR)
+ -$(RM) $(DEVLARC)
+
+$(OBJDIR):
+ @mkdir $@
+
+$(DISTDIR):
+ @mkdir $@
+
+$(DEVLDIR):
+ @mkdir $@
+
+$(OBJDIR)/version.inc: $(PROOT)/configure.ac $(OBJDIR) $(PROOT)/src/get_ver.awk
+ @echo Creating $@
+ @$(AWK) -f $(PROOT)/src/get_ver.awk $< > $@
+
+$(OBJDIR)/%.o: %.c
+ @echo Compiling $<
+ @$(CC) $(CFLAGS) -c $< -o $@
+
+$(OBJDIR)/rrdcgi.$(CGIEXT): $(OBJCGI) $(OBJDIR)/rrdcgi.def $(OBJDIR)/rrdcgi.xdc
+ @echo Linking $@
+ @-$(RM) $@
+ @$(LD) $(LDFLAGS) $(@:.$(CGIEXT)=.def)
+ifeq ($(LD),nlmconv)
+ @$(MV) $(notdir $@) $@
+endif
+ifneq ($(CGIEXT),nlm)
+ifeq ($(LD),mwldnlm)
+ @$(FIXNLMN) $@
+endif
+endif
+
+$(OBJDIR)/rrdtool.nlm: $(OBJTOOL) $(OBJDIR)/rrdtool.def $(OBJDIR)/rrdtool.xdc
+ @echo Linking $@
+ @-$(RM) $@
+ @$(LD) $(LDFLAGS) $(@:.nlm=.def)
+ifeq ($(LD),nlmconv)
+ @$(MV) $(notdir $@) $@
+endif
+
+$(OBJDIR)/librrd.$(LIBEXT): $(OBJS)
+ @echo Creating $@
+ @-$(RM) $@
+ @$(AR) $(ARFLAGS) $@ $^
+ifdef RANLIB
+ @$(RANLIB) $@
+endif
+
+$(OBJDIR)/%.xdc: Makefile
+ @echo Creating $@
+ @$(MPKXDC) $(XDCOPT) $@
+
+$(OBJDIR)/%.def: Makefile $(OBJDIR)/version.inc
+ @echo $(DL)# DEF Linker File for use with gcc and nlmconv$(DL) > $@
+ @echo $(DL)# or with Codewarrior command line compiler.$(DL) >> $@
+ @echo $(DL)# Do not edit this file - it is created by make!$(DL) >> $@
+ @echo $(DL)# All your changes will be lost!!$(DL) >> $@
+ @echo $(DL)#$(DL) >> $@
+ @echo $(DL)copyright "$(COPYR)"$(DL) >> $@
+ @echo $(DL)description "$(DESCR)"$(DL) >> $@
+ @echo $(DL)version $(RRD_VERSION)$(DL) >> $@
+ifdef NLMTYPE
+ @echo $(DL)type $(NLMTYPE)$(DL) >> $@
+else
+ @echo $(DL)type 0$(DL) >> $@
+endif
+ifdef STACK
+ @echo $(DL)stack $(STACK)$(DL) >> $@
+endif
+ @echo $(DL)threadname "$(notdir $(@:.def=))"$(DL) >> $@
+ifdef SCREEN
+ @echo $(DL)screenname "$(SCREEN)"$(DL) >> $@
+else
+ @echo $(DL)screenname "DEFAULT"$(DL) >> $@
+endif
+ifeq ($(DB),DEBUG)
+ @echo $(DL)debug$(DL) >> $@
+endif
+ifeq ($(LIBARCH),CLIB)
+ @echo $(DL)start _Prelude$(DL) >> $@
+ @echo $(DL)exit _Stop$(DL) >> $@
+ @echo $(DL)import @$(NDK_ROOT)/nwsdk/imports/clib.imp$(DL) >> $@
+ @echo $(DL)import @$(NDK_ROOT)/nwsdk/imports/threads.imp$(DL) >> $@
+ @echo $(DL)import @$(NDK_ROOT)/nwsdk/imports/nlmlib.imp$(DL) >> $@
+ @echo $(DL)module clib$(DL) >> $@
+else
+ @echo $(DL)start _LibCPrelude$(DL) >> $@
+ @echo $(DL)exit _LibCPostlude$(DL) >> $@
+ @echo $(DL)check _LibCCheckUnload$(DL) >> $@
+ @echo $(DL)import @$(NDK_ROOT)/libc/imports/libc.imp$(DL) >> $@
+ @echo $(DL)import @$(NDK_ROOT)/libc/imports/netware.imp$(DL) >> $@
+ @echo $(DL)module libc$(DL) >> $@
+ @echo $(DL)pseudopreemption$(DL) >> $@
+ @echo $(DL)flag_on 64$(DL) >> $@
+endif
+ifeq ($(LDRING),0)
+ @echo $(DL)flag_on 16$(DL) >> $@
+endif
+ifeq ($(LDRING),3)
+ @echo $(DL)flag_on 512$(DL) >> $@
+endif
+ifdef XDCOPT
+ @echo $(DL)xdcdata $(@:.def=.xdc)$(DL) >> $@
+endif
+ifeq ($(LD),nlmconv)
+ @echo $(DL)input $(OBJS) $(LDLIBS)$(DL) >> $@
+ @echo $(DL)input $(PRELUDE)$(DL) >> $@
+ @echo $(DL)output $(notdir $(@:.def=.nlm))$(DL) >> $@
+endif
+
+$(PROOT)/rrd_config.h: FORCE Makefile $(OBJDIR)/version.inc
+ @echo Creating $@
+ @echo $(DL)/* $(notdir $@) for NetWare target.$(DL) > $@
+ @echo $(DL)** Do not edit this file - it is created by make!$(DL) >> $@
+ @echo $(DL)** All your changes will be lost!!$(DL) >> $@
+ @echo $(DL)*/$(DL) >> $@
+ @echo $(DL)#ifndef NETWARE$(DL) >> $@
+ @echo $(DL)#error This $(notdir $@) is created for NetWare platform!$(DL) >> $@
+ @echo $(DL)#endif$(DL) >> $@
+ @echo $(DL)#ifndef RRD_CONFIG_H$(DL) >> $@
+ @echo $(DL)#define RRD_CONFIG_H$(DL) >> $@
+ @echo $(DL)#define OS "i586-pc-NetWare"$(DL) >> $@
+ @echo $(DL)#define PACKAGE_VERSION "$(RRD_VERSION_STR)"$(DL) >> $@
+ @echo $(DL)#define PACKAGE_BUGREPORT "tobi@oetiker.ch"$(DL) >> $@
+ @echo $(DL)#define NUMVERS $(RRD_NUMVERS)$(DL) >> $@
+ @echo $(DL)#define HAVE_ASSERT_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_DLFCN_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_DLOPEN 1$(DL) >> $@
+ @echo $(DL)#define HAVE_ERR_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_ERRNO_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_FCNTL_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_FIONBIO 1$(DL) >> $@
+ @echo $(DL)#define HAVE_FLOAT_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_GETTIMEOFDAY 1$(DL) >> $@
+ @echo $(DL)#define HAVE_INTTYPES_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_LIMITS_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_LONGLONG 1$(DL) >> $@
+ @echo $(DL)#define HAVE_LOCALE_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_MALLOC_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_MATH_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_MBSTOWCS 1$(DL) >> $@
+ @echo $(DL)#define HAVE_MEMMOVE 1$(DL) >> $@
+ @echo $(DL)#define HAVE_MKTIME 1$(DL) >> $@
+ @echo $(DL)#define HAVE_SELECT 1$(DL) >> $@
+ @echo $(DL)#define HAVE_SETLOCALE 1$(DL) >> $@
+ @echo $(DL)#define HAVE_SETJMP_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_SNPRINTF 1$(DL) >> $@
+ @echo $(DL)#define HAVE_STDARG_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_STDDEF_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_STDINT_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_STDLIB_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_STRCASECMP 1$(DL) >> $@
+ @echo $(DL)#define HAVE_STRDUP 1$(DL) >> $@
+ @echo $(DL)#define HAVE_STRFTIME 1$(DL) >> $@
+ @echo $(DL)#define HAVE_STRING_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_STRLCAT 1$(DL) >> $@
+ @echo $(DL)#define HAVE_STRLCPY 1$(DL) >> $@
+ @echo $(DL)#define HAVE_STRSTR 1$(DL) >> $@
+ @echo $(DL)#define HAVE_SYS_PARAM_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_SYS_SELECT_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_SYS_STAT_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_SYS_TIME_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_SYS_TYPES_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_TIME_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_TZSET 1$(DL) >> $@
+ @echo $(DL)#define HAVE_UNAME 1$(DL) >> $@
+ @echo $(DL)#define HAVE_VSNPRINTF 1$(DL) >> $@
+ @echo $(DL)#define STDC_HEADERS 1$(DL) >> $@
+ @echo $(DL)#define TIME_WITH_SYS_TIME 1$(DL) >> $@
+ @echo $(DL)#define HAVE_ZLIB_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_LIBZ 1$(DL) >> $@
+ifdef NO_NULL_REALLOC
+ @echo $(DL)#define NO_NULL_REALLOC 1$(DL) >> $@
+ @echo $(DL)#define rrd_realloc(a,b) ( (a) == NULL ? malloc( (b) ) : realloc( (a) , (b) ))$(DL) >> $@
+else
+ @echo $(DL)#define rrd_realloc(a,b) realloc((a), (b))$(DL) >> $@
+endif
+ifdef RRD_DEFAULT_FONT
+ @echo $(DL)#define RRD_DEFAULT_FONT $(RRD_DEFAULT_FONT)$(DL) >> $@
+endif
+ @echo $(DL)#define RRDGRAPH_YLEGEND_ANGLE $(RRDGRAPH_YLEGEND_ANGLE)$(DL) >> $@
+ifdef WITH_PIECHART
+ @echo $(DL)#define WITH_PIECHART $(WITH_PIECHART)$(DL) >> $@
+endif
+ @echo $(DL)#endif /* RRD_CONFIG_H */$(DL) >> $@
+
+$(DISTDIR)/readme.txt: Makefile
+ @echo Creating $@
+ @echo $(DL)This is a binary distribution for NetWare platform.$(DL) > $@
+ @echo $(DL)RRDTool version $(RRD_VERSION_STR)$(DL) >> $@
+ @echo $(DL)Please download the complete RRDTool package for$(DL) >> $@
+ @echo $(DL)any further documentation:$(DL) >> $@
+ @echo $(DL)$(WWWURL)$(DL) >> $@
+
+$(DEVLDIR)/readme.txt: Makefile
+ @echo Creating $@
+ @echo $(DL)This is a development distribution for NetWare platform.$(DL) > $@
+ @echo $(DL)RRDTool version $(RRD_VERSION_STR)$(DL) >> $@
+ @echo $(DL)Please download the complete RRDTool package for$(DL) >> $@
+ @echo $(DL)any further documentation:$(DL) >> $@
+ @echo $(DL)$(WWWURL)$(DL) >> $@
+
+help:
+ @echo $(DL)===========================================================$(DL)
+ @echo $(DL)Novell NDK Base = $(NDKBASE)$(DL)
+ @echo $(DL)libpng Source = $(LIBPNG)$(DL)
+ @echo $(DL)libart Source = $(LIBART)$(DL)
+ @echo $(DL)Freetype 2 SDK = $(LIBFT2)$(DL)
+ @echo $(DL)Zlib SDK = $(ZLIBSDK)$(DL)
+ @echo $(DL)===========================================================$(DL)
+ @echo $(DL)RRDTool $(RRD_VERSION_STR) - available targets are:$(DL)
+ @echo $(DL)$(MAKE) all$(DL)
+ @echo $(DL)$(MAKE) rrdtool$(DL)
+ @echo $(DL)$(MAKE) rrdcgi$(DL)
+ @echo $(DL)$(MAKE) librrd$(DL)
+ @echo $(DL)$(MAKE) clean$(DL)
+ @echo $(DL)$(MAKE) dev$(DL)
+ @echo $(DL)$(MAKE) devclean$(DL)
+ @echo $(DL)$(MAKE) dist$(DL)
+ @echo $(DL)$(MAKE) distclean$(DL)
+ @echo $(DL)===========================================================$(DL)
+
+
diff --git a/program/po/ChangeLog b/program/po/ChangeLog
--- /dev/null
+++ b/program/po/ChangeLog
@@ -0,0 +1 @@
+# no change log yet
diff --git a/program/po/LINGUAS b/program/po/LINGUAS
--- /dev/null
+++ b/program/po/LINGUAS
@@ -0,0 +1,2 @@
+de
+
diff --git a/program/po/POTFILES.in b/program/po/POTFILES.in
--- /dev/null
+++ b/program/po/POTFILES.in
@@ -0,0 +1,2 @@
+src/rrd_getopt.c
+src/rrd_tool.c
diff --git a/program/po/de.po b/program/po/de.po
--- /dev/null
+++ b/program/po/de.po
@@ -0,0 +1,334 @@
+#: ../src/rrd_getopt.c:615
+#, c-format
+msgid "%s: option `%s' is ambiguous\n"
+msgstr ""
+
+#: ../src/rrd_getopt.c:637
+#, c-format
+msgid "%s: option `--%s' doesn't allow an argument\n"
+msgstr ""
+
+#: ../src/rrd_getopt.c:643
+#, c-format
+msgid "%s: option `%c%s' doesn't allow an argument\n"
+msgstr ""
+
+#: ../src/rrd_getopt.c:658 ../src/rrd_getopt.c:811
+#, c-format
+msgid "%s: option `%s' requires an argument\n"
+msgstr ""
+
+#. --option
+#: ../src/rrd_getopt.c:684
+#, c-format
+msgid "%s: unrecognized option `--%s'\n"
+msgstr ""
+
+#. +option or -option
+#: ../src/rrd_getopt.c:688
+#, c-format
+msgid "%s: unrecognized option `%c%s'\n"
+msgstr ""
+
+#. 1003.2 specifies the format of this message.
+#: ../src/rrd_getopt.c:712
+#, c-format
+msgid "%s: illegal option -- %c\n"
+msgstr ""
+
+#: ../src/rrd_getopt.c:715
+#, c-format
+msgid "%s: invalid option -- %c\n"
+msgstr ""
+
+#: ../src/rrd_getopt.c:741 ../src/rrd_getopt.c:849
+#, c-format
+msgid "%s: option requires an argument -- %c\n"
+msgstr ""
+
+#: ../src/rrd_getopt.c:783
+#, c-format
+msgid "%s: option `-W %s' is ambiguous\n"
+msgstr ""
+
+#: ../src/rrd_getopt.c:798
+#, c-format
+msgid "%s: option `-W %s' doesn't allow an argument\n"
+msgstr ""
+
+#: ../src/rrd_tool.c:49
+#, c-format
+msgid ""
+"RRDtool %s Copyright 1997-2007 by Tobias Oetiker <tobi@oetiker.ch>\n"
+" Compiled %s %s\n"
+"\n"
+"Usage: rrdtool [options] command command_options\n"
+"\n"
+msgstr ""
+
+#: ../src/rrd_tool.c:55
+msgid ""
+"Valid commands: create, update, updatev, graph, dump, restore,\n"
+"\t\tlast, lastupdate, first, info, fetch, tune,\n"
+"\t\tresize, xport\n"
+"\n"
+msgstr ""
+
+#: ../src/rrd_tool.c:60
+msgid ""
+"Valid remote commands: quit, ls, cd, mkdir, pwd\n"
+"\n"
+msgstr ""
+
+#: ../src/rrd_tool.c:64
+msgid ""
+"* create - create a new RRD\n"
+"\n"
+"\trrdtool create filename [--start|-b start time]\n"
+"\t\t[--step|-s step]\n"
+"\t\t[DS:ds-name:DST:dst arguments]\n"
+"\t\t[RRA:CF:cf arguments]\n"
+"\n"
+msgstr ""
+
+#: ../src/rrd_tool.c:71
+msgid ""
+"* dump - dump an RRD to XML\n"
+"\n"
+"\trrdtool dump filename.rrd >filename.xml\n"
+"\n"
+msgstr ""
+
+#: ../src/rrd_tool.c:75
+msgid ""
+"* info - returns the configuration and status of the RRD\n"
+"\n"
+"\trrdtool info filename.rrd\n"
+"\n"
+msgstr ""
+
+#: ../src/rrd_tool.c:79
+msgid ""
+"* restore - restore an RRD file from its XML form\n"
+"\n"
+"\trrdtool restore [--range-check|-r] [--force-overwrite|-f] filename.xml "
+"filename.rrd\n"
+"\n"
+msgstr ""
+
+#: ../src/rrd_tool.c:83
+msgid ""
+"* last - show last update time for RRD\n"
+"\n"
+"\trrdtool last filename.rrd\n"
+"\n"
+msgstr ""
+
+#: ../src/rrd_tool.c:87
+msgid ""
+"* lastupdate - returns the most recent datum stored for\n"
+" each DS in an RRD\n"
+"\n"
+"\trrdtool lastupdate filename.rrd\n"
+"\n"
+msgstr ""
+
+#: ../src/rrd_tool.c:91
+msgid ""
+"* first - show first update time for RRA within an RRD\n"
+"\n"
+"\trrdtool first filename.rrd [--rraindex number]\n"
+"\n"
+msgstr ""
+
+#: ../src/rrd_tool.c:95
+msgid ""
+"* update - update an RRD\n"
+"\n"
+"\trrdtool update filename\n"
+"\t\t--template|-t ds-name:ds-name:...\n"
+"\t\ttime|N:value[:value...]\n"
+"\n"
+"\t\tat-time@value[:value...]\n"
+"\n"
+"\t\t[ time:value[:value...] ..]\n"
+"\n"
+msgstr ""
+
+#: ../src/rrd_tool.c:103
+msgid ""
+"* updatev - a verbose version of update\n"
+"\treturns information about values, RRAs, and datasources updated\n"
+"\n"
+"\trrdtool updatev filename\n"
+"\t\t--template|-t ds-name:ds-name:...\n"
+"\t\ttime|N:value[:value...]\n"
+"\n"
+"\t\tat-time@value[:value...]\n"
+"\n"
+"\t\t[ time:value[:value...] ..]\n"
+"\n"
+msgstr ""
+
+#: ../src/rrd_tool.c:112
+msgid ""
+"* fetch - fetch data out of an RRD\n"
+"\n"
+"\trrdtool fetch filename.rrd CF\n"
+"\t\t[-r|--resolution resolution]\n"
+"\t\t[-s|--start start] [-e|--end end]\n"
+"\n"
+msgstr ""
+
+#: ../src/rrd_tool.c:120
+msgid ""
+"* graph - generate a graph from one or several RRD\n"
+"\n"
+"\trrdtool graph filename [-s|--start seconds] [-e|--end seconds]\n"
+"\t\t[-x|--x-grid x-axis grid and label]\n"
+"\t\t[-Y|--alt-y-grid]\n"
+"\t\t[-y|--y-grid y-axis grid and label]\n"
+"\t\t[-v|--vertical-label string] [-w|--width pixels]\n"
+"\t\t[-h|--height pixels] [-o|--logarithmic]\n"
+"\t\t[-u|--upper-limit value] [-z|--lazy]\n"
+"\t\t[-l|--lower-limit value] [-r|--rigid]\n"
+"\t\t[-g|--no-legend]\n"
+"\t\t[-F|--force-rules-legend]\n"
+"\t\t[-j|--only-graph]\n"
+msgstr ""
+
+#: ../src/rrd_tool.c:132
+msgid ""
+"\t\t[-n|--font FONTTAG:size:font]\n"
+"\t\t[-m|--zoom factor]\n"
+"\t\t[-A|--alt-autoscale]\n"
+"\t\t[-M|--alt-autoscale-max]\n"
+"\t\t[-R|--font-render-mode {normal,light,mono}]\n"
+"\t\t[-B|--font-smoothing-threshold size]\n"
+"\t\t[-E|--slope-mode]\n"
+"\t\t[-N|--no-gridfit]\n"
+"\t\t[-X|--units-exponent value]\n"
+"\t\t[-L|--units-length value]\n"
+"\t\t[-S|--step seconds]\n"
+"\t\t[-f|--imginfo printfstr]\n"
+"\t\t[-a|--imgformat PNG]\n"
+"\t\t[-c|--color COLORTAG#rrggbb[aa]] [-t|--title string]\n"
+"\t\t[-W|--watermark string]\n"
+"\t\t[DEF:vname=rrd:ds-name:CF]\n"
+msgstr ""
+
+#: ../src/rrd_tool.c:148
+msgid ""
+"\t\t[CDEF:vname=rpn-expression]\n"
+"\t\t[VDEF:vdefname=rpn-expression]\n"
+"\t\t[PRINT:vdefname:format]\n"
+"\t\t[GPRINT:vdefname:format]\n"
+"\t\t[COMMENT:text]\n"
+"\t\t[SHIFT:vname:offset]\n"
+"\t\t[TICK:vname#rrggbb[aa][:[fraction][:legend]]]\n"
+"\t\t[HRULE:value#rrggbb[aa][:legend]]\n"
+"\t\t[VRULE:value#rrggbb[aa][:legend]]\n"
+"\t\t[LINE[width]:vname[#rrggbb[aa][:[legend][:STACK]]]]\n"
+"\t\t[AREA:vname[#rrggbb[aa][:[legend][:STACK]]]]\n"
+"\t\t[PRINT:vname:CF:format] (deprecated)\n"
+"\t\t[GPRINT:vname:CF:format] (deprecated)\n"
+"\t\t[STACK:vname[#rrggbb[aa][:legend]]] (deprecated)\n"
+"\n"
+msgstr ""
+
+#: ../src/rrd_tool.c:164
+msgid ""
+" * tune - Modify some basic properties of an RRD\n"
+"\n"
+"\trrdtool tune filename\n"
+"\t\t[--heartbeat|-h ds-name:heartbeat]\n"
+"\t\t[--data-source-type|-d ds-name:DST]\n"
+"\t\t[--data-source-rename|-r old-name:new-name]\n"
+"\t\t[--minimum|-i ds-name:min] [--maximum|-a ds-name:max]\n"
+"\t\t[--deltapos scale-value] [--deltaneg scale-value]\n"
+"\t\t[--failure-threshold integer]\n"
+"\t\t[--window-length integer]\n"
+"\t\t[--alpha adaptation-parameter]\n"
+msgstr ""
+
+#: ../src/rrd_tool.c:175
+msgid ""
+" * tune - Modify some basic properties of an RRD\n"
+"\n"
+"\t\t[--beta adaptation-parameter]\n"
+"\t\t[--gamma adaptation-parameter]\n"
+"\t\t[--gamma-deviation adaptation-parameter]\n"
+"\t\t[--aberrant-reset ds-name]\n"
+"\n"
+msgstr ""
+
+#: ../src/rrd_tool.c:182
+msgid ""
+" * resize - alter the length of one of the RRAs in an RRD\n"
+"\n"
+"\trrdtool resize filename rranum GROW|SHRINK rows\n"
+"\n"
+msgstr ""
+
+#: ../src/rrd_tool.c:186
+msgid ""
+"* xport - generate XML dump from one or several RRD\n"
+"\n"
+"\trrdtool xport [-s|--start seconds] [-e|--end seconds]\n"
+"\t\t[-m|--maxrows rows]\n"
+"\t\t[--step seconds]\n"
+"\t\t[--enumds]\n"
+"\t\t[DEF:vname=rrd:ds-name:CF]\n"
+"\t\t[CDEF:vname=rpn-expression]\n"
+"\t\t[XPORT:vname:legend]\n"
+"\n"
+msgstr ""
+
+#: ../src/rrd_tool.c:195
+msgid ""
+" * quit - closing a session in remote mode\n"
+"\n"
+"\trrdtool quit\n"
+"\n"
+msgstr ""
+
+#: ../src/rrd_tool.c:198
+msgid ""
+" * ls - lists all *.rrd files in current directory\n"
+"\n"
+"\trrdtool ls\n"
+"\n"
+msgstr ""
+
+#: ../src/rrd_tool.c:202
+msgid ""
+" * cd - changes the current directory\n"
+"\n"
+"\trrdtool cd new directory\n"
+"\n"
+msgstr ""
+
+#: ../src/rrd_tool.c:206
+msgid ""
+" * mkdir - creates a new directory\n"
+"\n"
+"\trrdtool mkdir newdirectoryname\n"
+"\n"
+msgstr ""
+
+#: ../src/rrd_tool.c:210
+msgid ""
+" * pwd - returns the current working directory\n"
+"\n"
+"\trrdtool pwd\n"
+"\n"
+msgstr ""
+
+#: ../src/rrd_tool.c:214
+msgid ""
+"RRDtool is distributed under the Terms of the GNU General\n"
+"Public License Version 2. (www.gnu.org/copyleft/gpl.html)\n"
+"\n"
+"For more information read the RRD manpages\n"
+"\n"
+msgstr ""
diff --git a/program/rrdtool-1.2-release b/program/rrdtool-1.2-release
--- /dev/null
@@ -0,0 +1,37 @@
+#!/bin/sh
+set -e
+VERSION=`perl -n -e 'm/\QAC_INIT([rrdtool],[\E(.+?)\Q])\E/ && print $1' configure.ac`
+PERLVERS=`perl -n -e 'm/NUMVERS=(\d+\.\d+)/ && print $1' configure.ac`
+set -x
+perl -i -p -e 's/^\$VERSION.+/\$VERSION='$PERLVERS';/' bindings/perl-*/*.pm
+perl -i -p -e 's/RRDtool 1\S+/RRDtool '$VERSION'/ && s/Copyright.+?Oetiker.+\d{4}/Copyright by Tobi Oetiker, 1997-2007/' src/*.h src/*.c
+perl -i -p -e 's/^Version:.+/Version: '$VERSION'/' rrdtool.spec
+perl -i -p -e 's/rrdtool-[\.\d]+\d(pre\d+)?(rc\d+)?/rrdtool-'$VERSION'/g' doc/rrdbuild.pod
+svn diff
+echo "Tagging and releasing rrdtool $VERSION ($PERLVERS). Press any Key to continue."
+read somekey
+svn commit -m "prepare for the release of rrdtool-$VERSION"
+mkdir /tmp/rrdtool-$$
+OPWD=`pwd`
+cd /tmp/rrdtool-$$
+svn checkout svn://svn.oetiker.ch/rrdtool/branches/1.2/program .
+svn log --stop-on-copy --xml --verbose svn://svn.oetiker.ch/rrdtool/branches/1.2/program | \
+ xsltproc --stringparam strip-prefix branches/1.2/program $OPWD/svn2cl.xsl - >CHANGES
+sh MakeMakefile
+PKG_CONFIG_PATH=/usr/pack/rrdtool-1.2svn-to/i686-debian-linux3.1/lib/pkgconfig/
+export PKG_CONFIG_PATH
+./configure
+make dist
+# do a test build
+tar zxvf rrdtool*.tar.gz
+cd rrdtool-$VERSION
+./configure
+make
+src/rrdtool
+cd ..
+scp CHANGES rrdtool*.tar.gz oposs@james:public_html/rrdtool/pub/
+ssh oposs@james "cd public_html/rrdtool/pub/;rm rrdtool.tar.gz;ln -s rrdtool-$VERSION.tar.gz rrdtool.tar.gz"
+cd ..
+rm -rf rrdtool-$$
+svn copy -m "tagging version $VERSION" svn://svn.oetiker.ch/rrdtool/branches/1.2/program svn://svn.oetiker.ch/rrdtool/tags/$VERSION
+
diff --git a/program/rrdtool-1.3-release b/program/rrdtool-1.3-release
--- /dev/null
@@ -0,0 +1,37 @@
+#!/bin/sh
+set -e
+VERSION=`perl -n -e 'm/\QAC_INIT([rrdtool],[\E(.+?)\Q])\E/ && print $1' configure.ac`
+PERLVERS=`perl -n -e 'm/NUMVERS=(\d+\.\d+)/ && print $1' configure.ac`
+set -x
+perl -i -p -e 's/^\$VERSION.+/\$VERSION='$PERLVERS';/' bindings/perl-*/*.pm
+perl -i -p -e 's/RRDtool 1\S+/RRDtool '$VERSION'/ && s/Copyright.+?Oetiker.+\d{4}/Copyright by Tobi Oetiker, 1997-2008/' src/*.h src/*.c
+perl -i -p -e 's/^Version:.+/Version: '$VERSION'/' rrdtool.spec
+perl -i -p -e 's/rrdtool-[\.\d]+\d(pre\d+)?(rc\d+)?/rrdtool-'$VERSION'/g' doc/rrdbuild.pod
+svn diff
+echo "Tagging and releasing rrdtool $VERSION ($PERLVERS). Press any Key to continue."
+read somekey
+svn commit -m "prepare for the release of rrdtool-$VERSION"
+OPWD=`pwd`
+cd /tmp
+svn export svn://svn.oetiker.ch/rrdtool/trunk/program rrdtool-$$
+cd rrdtool-$$
+svn log --stop-on-copy --xml --verbose svn://svn.oetiker.ch/rrdtool/trunk | \
+ xsltproc --stringparam strip-prefix trunk/program $OPWD/svn2cl.xsl - >CHANGES
+sh MakeMakefile
+#PKG_CONFIG_PATH=/usr/pack/rrdtool-1.3svn-to/i686-debian-linux3.1/lib/pkgconfig/
+#export PKG_CONFIG_PATH
+./configure --enable-maintainer-mode
+make dist
+# do a test build
+tar zxvf rrdtool*.tar.gz
+cd rrdtool-$VERSION
+./configure
+make
+src/rrdtool
+cd ..
+scp CHANGES rrdtool*.tar.gz oposs@james:public_html/rrdtool/pub
+ssh oposs@james "cd public_html/rrdtool/pub/;rm rrdtool.tar.gz;ln -s rrdtool-$VERSION.tar.gz rrdtool.tar.gz"
+cd ..
+rm -rf rrdtool-$$
+svn copy -m "tagging version $VERSION" svn://svn.oetiker.ch/rrdtool/trunk/program svn://svn.oetiker.ch/rrdtool/tags/$VERSION
+
diff --git a/program/rrdtool.spec b/program/rrdtool.spec
--- /dev/null
+++ b/program/rrdtool.spec
@@ -0,0 +1,595 @@
+%define with_python %{?_without_python: 0} %{?!_without_python: 1}
+%define with_php %{?_without_php: 0} %{?!_without_php: 1}
+%define with_tcl %{?_without_tcl: 0} %{?!_without_tcl: 1}
+%define with_ruby %{?_without_ruby: 0} %{?!_without_ruby: 1}
+%define php_extdir %(php-config --extension-dir 2>/dev/null || echo %{_libdir}/php4)
+%define svnrev r1190
+%define pre rc9
+#define pretag 1.2.99908020600
+
+Summary: Round Robin Database Tool to store and display time-series data
+Name: rrdtool
+Version: 1.3.2
+Release: 0.20%{?pre:.%{pre}}%{?dist}
+License: GPLv2+ with exceptions
+Group: Applications/Databases
+URL: http://oss.oetiker.ch/rrdtool/
+#Source0: http://oss.oetiker.ch/%{name}/pub/%{name}-%{version}.tar.gz
+Source0: http://oss.oetiker.ch/rrdtool/pub/beta/%{name}-%{version}%{pre}.tar.gz
+%if %{with_php}
+Source1: php4-%{svnrev}.tar.gz
+Patch1: rrdtool-1.3.0-beta4-fix-rrd_update-in-php-bindings.patch
+%endif
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
+Requires: dejavu-lgc-fonts
+BuildRequires: gcc-c++, openssl-devel, freetype-devel
+BuildRequires: libpng-devel, zlib-devel, intltool >= 0.35.0
+BuildRequires: cairo-devel >= 1.4.6, pango-devel >= 1.17
+BuildRequires: libtool, groff
+BuildRequires: gettext, libxml2-devel
+%if 0%{?fedora} >= 7
+BuildRequires: perl-ExtUtils-MakeMaker perl-devel
+%endif
+
+%description
+RRD is the Acronym for Round Robin Database. RRD is a system to store and
+display time-series data (i.e. network bandwidth, machine-room temperature,
+server load average). It stores the data in a very compact way that will not
+expand over time, and it presents useful graphs by processing the data to
+enforce a certain data density. It can be used either via simple wrapper
+scripts (from shell or Perl) or via frontends that poll network devices and
+put a friendly user interface on it.
+
+%package devel
+Summary: RRDtool libraries and header files
+Group: Development/Libraries
+Requires: %{name} = %{version}-%{release}
+
+%description devel
+RRD is the Acronym for Round Robin Database. RRD is a system to store and
+display time-series data (i.e. network bandwidth, machine-room temperature,
+server load average). This package allow you to use directly this library.
+
+%package doc
+Summary: RRDtool documentation
+Group: Documentation
+
+%description doc
+RRD is the Acronym for Round Robin Database. RRD is a system to store and
+display time-series data (i.e. network bandwidth, machine-room temperature,
+server load average). This package contains documentation on using RRD.
+
+%package perl
+Summary: Perl RRDtool bindings
+Group: Development/Languages
+Requires: %{name} = %{version}-%{release}
+Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version))
+Obsoletes: perl-%{name} < %{version}-%{release}
+Provides: perl-%{name} = %{version}-%{release}
+
+%description perl
+The Perl RRDtool bindings
+
+%if %{with_python}
+%{!?python_sitearch: %define python_sitearch %(%{__python} -c 'from distutils import sysconfig; print sysconfig.get_python_lib(1)')}
+# eval to 2.3 if python isn't yet present, workaround for no python in fc4 minimal buildroot
+%{!?python_version: %define python_version %(%{__python} -c 'import sys; print sys.version.split(" ")[0]' || echo "2.3")}
+
+%package python
+Summary: Python RRDtool bindings
+Group: Development/Languages
+BuildRequires: python-devel >= 2.3
+Requires: python >= %{python_version}
+Requires: %{name} = %{version}-%{release}
+Obsoletes: python-%{name} < %{version}-%{release}
+Provides: python-%{name} = %{version}-%{release}
+
+%description python
+Python RRDtool bindings.
+%endif
+
+%ifarch ppc64
+# php bits busted on ppc64 at the moment
+%define with_php 0
+%endif
+
+%if %{with_php}
+%package php
+Summary: PHP RRDtool bindings
+Group: Development/Languages
+BuildRequires: php-devel >= 4.0
+Requires: php >= 4.0
+Requires: %{name} = %{version}-%{release}
+%if 0%{?php_zend_api}
+Requires: php(zend-abi) = %{php_zend_api}
+Requires: php(api) = %{php_core_api}
+%else
+Requires: php-api = %{php_apiver}
+%endif
+Obsoletes: php-%{name} < %{version}-%{release}
+Provides: php-%{name} = %{version}-%{release}
+Provides: php-pecl(rrdtool)
+
+%description php
+The %{name}-php package includes a dynamic shared object (DSO) that adds
+RRDtool bindings to the PHP HTML-embedded scripting language.
+%endif
+
+%if %{with_tcl}
+%package tcl
+Summary: Tcl RRDtool bindings
+Group: Development/Languages
+BuildRequires: tcl-devel >= 8.0
+Requires: tcl >= 8.0
+Requires: %{name} = %{version}-%{release}
+Obsoletes: tcl-%{name} < %{version}-%{release}
+Provides: tcl-%{name} = %{version}-%{release}
+
+%description tcl
+The %{name}-tcl package includes RRDtool bindings for Tcl.
+%endif
+
+%if %{with_ruby}
+%{!?ruby_sitearch: %define ruby_sitearch %(ruby -rrbconfig -e 'puts Config::CONFIG["sitearchdir"]')}
+
+%package ruby
+Summary: Ruby RRDtool bindings
+Group: Development/Languages
+BuildRequires: ruby, ruby-devel
+Requires: ruby(abi) = 1.8
+Requires: %{name} = %{version}-%{release}
+
+%description ruby
+The %{name}-ruby package includes RRDtool bindings for Ruby.
+%endif
+
+%prep
+%if %{with_php}
+%setup -q -n %{name}-%{version}%{pre} -a 1
+%patch1 -p1
+%else
+%setup -q -n %{name}-%{version}%{pre}
+%endif
+
+# Fix to find correct python dir on lib64
+%{__perl} -pi -e 's|get_python_lib\(0,0,prefix|get_python_lib\(1,0,prefix|g' \
+ configure
+
+# Most edits shouldn't be necessary when using --libdir, but
+# w/o, some introduce hardcoded rpaths where they shouldn't
+%{__perl} -pi.orig -e 's|/lib\b|/%{_lib}|g' configure Makefile.in*
+%if %{with_php}
+%{__perl} -pi.orig -e 's|/lib\b|/%{_lib}|g' php4/configure php4/ltconfig*
+%endif
+
+# Perl 5.10 seems to not like long version strings, hack around it
+%{__perl} -pi.orig -e 's|1.299907080300|1.29990708|' \
+ bindings/perl-shared/RRDs.pm bindings/perl-piped/RRDp.pm
+
+#
+# fix config files for php4 bindings
+# workaround needed due to https://bugzilla.redhat.com/show_bug.cgi?id=211069
+%if %{with_php}
+cp -p /usr/lib/rpm/config.{guess,sub} php4/
+%endif
+
+
+%build
+%configure \
+ --with-perl-options='INSTALLDIRS="vendor"' \
+%if %{with_tcl}
+ --enable-tcl-site \
+ --with-tcllib=%{_libdir} \
+%else
+ --disable-tcl \
+%endif
+%if %{with_python}
+ --enable-python \
+%else
+ --disable-python \
+%endif
+%if %{with_ruby}
+ --enable-ruby \
+%endif
+ --disable-static \
+ --with-pic
+
+# Fix another rpath issue
+%{__perl} -pi.orig -e 's|-Wl,--rpath -Wl,\$rp||g' \
+ bindings/perl-shared/Makefile.PL
+
+# Force RRDp bits where we want 'em, not sure yet why the
+# --with-perl-options and --libdir don't take
+pushd bindings/perl-piped/
+%{__perl} Makefile.PL INSTALLDIRS=vendor
+%{__perl} -pi.orig -e 's|/lib/perl|/%{_lib}/perl|g' Makefile
+popd
+
+#{__make} %{?_smp_mflags}
+make
+
+# Build the php module, the tmp install is required
+%if %{with_php}
+%define rrdtmp %{_tmppath}/%{name}-%{version}-tmpinstall
+%{__make} install DESTDIR="%{rrdtmp}"
+pushd php4/
+%configure \
+ --with-rrdtool="%{rrdtmp}%{_prefix}" \
+ --disable-static
+#{__make} %{?_smp_mflags}
+make
+popd
+%{__rm} -rf %{rrdtmp}
+%endif
+
+# Fix @perl@ and @PERL@
+find examples/ -type f \
+ -exec %{__perl} -pi -e 's|^#! \@perl\@|#!%{__perl}|gi' {} \;
+find examples/ -name "*.pl" \
+ -exec %{__perl} -pi -e 's|\015||gi' {} \;
+
+%install
+rm -rf $RPM_BUILD_ROOT
+make DESTDIR="$RPM_BUILD_ROOT" install
+
+# Install the php module
+%if %{with_php}
+%{__install} -D -m0755 php4/modules/rrdtool.so \
+ %{buildroot}%{php_extdir}/rrdtool.so
+# Clean up the examples for inclusion as docs
+%{__rm} -rf php4/examples/.svn
+# Put the php config bit into place
+%{__mkdir_p} %{buildroot}%{_sysconfdir}/php.d
+%{__cat} << __EOF__ > %{buildroot}%{_sysconfdir}/php.d/rrdtool.ini
+; Enable rrdtool extension module
+extension=rrdtool.so
+__EOF__
+%endif
+
+# Pesky RRDp.pm...
+%{__mv} $RPM_BUILD_ROOT%{perl_vendorarch}/../RRDp.pm $RPM_BUILD_ROOT%{perl_vendorarch}/
+
+# Dunno why this is getting installed here...
+%{__rm} -f $RPM_BUILD_ROOT%{perl_vendorarch}/../leaktest.pl
+
+# We only want .txt and .html files for the main documentation
+%{__mkdir_p} doc2/html doc2/txt
+%{__cp} -a doc/*.txt doc2/txt/
+%{__cp} -a doc/*.html doc2/html/
+
+# Put perl docs in perl package
+%{__mkdir_p} doc3/html
+%{__mv} doc2/html/RRD*.html doc3/html/
+
+# Clean up the examples
+%{__rm} -f examples/Makefile* examples/*.in
+
+# This is so rpm doesn't pick up perl module dependencies automatically
+find examples/ -type f -exec chmod 0644 {} \;
+
+# Clean up the buildroot
+%{__rm} -rf $RPM_BUILD_ROOT%{_docdir}/%{name}-* \
+ $RPM_BUILD_ROOT%{perl_vendorarch}/ntmake.pl \
+ $RPM_BUILD_ROOT%{perl_archlib}/perllocal.pod \
+ $RPM_BUILD_ROOT%{_datadir}/%{name}/examples \
+ $RPM_BUILD_ROOT%{perl_vendorarch}/auto/*/{.packlist,*.bs}
+
+%clean
+%{__rm} -rf $RPM_BUILD_ROOT
+
+%post -p /sbin/ldconfig
+
+%postun -p /sbin/ldconfig
+
+%files
+%defattr(-,root,root,-)
+%{_bindir}/*
+%{_libdir}/*.so.*
+%{_datadir}/%{name}
+%{_mandir}/man1/*
+
+%files devel
+%defattr(-,root,root,-)
+%{_includedir}/*.h
+%exclude %{_libdir}/*.la
+%{_libdir}/*.so
+
+%files doc
+%defattr(-,root,root,-)
+%doc CONTRIBUTORS COPYING COPYRIGHT README TODO NEWS THREADS
+%doc examples doc2/html doc2/txt
+
+%files perl
+%defattr(-,root,root,-)
+%doc doc3/html
+%{_mandir}/man3/*
+%{perl_vendorarch}/*.pm
+%attr(0755,root,root) %{perl_vendorarch}/auto/RRDs/
+
+%if %{with_python}
+%files python
+%defattr(-,root,root,-)
+%doc bindings/python/AUTHORS bindings/python/COPYING bindings/python/README
+%{python_sitearch}/rrdtoolmodule.so
+%{python_sitearch}/py_rrdtool-*.egg-info
+%endif
+
+%if %{with_php}
+%files php
+%defattr(-,root,root,0755)
+%doc php4/examples php4/README
+%config(noreplace) %{_sysconfdir}/php.d/rrdtool.ini
+%{php_extdir}/rrdtool.so
+%endif
+
+%if %{with_tcl}
+%files tcl
+%defattr(-,root,root,-)
+%doc bindings/tcl/README
+%{_libdir}/tclrrd*.so
+%{_libdir}/rrdtool/*.tcl
+%endif
+
+%if %{with_ruby}
+%files ruby
+%defattr(-,root,root,-)
+%doc bindings/ruby/README
+%{ruby_sitearch}/RRD.so
+%endif
+
+%changelog
+* Sun Jun 08 2008 Jarod Wilson <jwilson@redhat.com> 1.3-0.20.rc9
+- Update to rrdtool 1.3 rc9
+- Minor spec tweaks to permit building on older EL
+
+* Wed Jun 04 2008 Chris Ricker <kaboom@oobleck.net> 1.3-0.19.rc7
+- Update to rrdtool 1.3 rc7
+
+* Tue May 27 2008 Chris Ricker <kaboom@oobleck.net> 1.3-0.18.rc6
+- Update to rrdtool 1.3 rc6
+
+* Wed May 21 2008 Chris Ricker <kaboom@oobleck.net> 1.3-0.17.rc4
+- Bump version and rebuild
+
+* Wed May 21 2008 Chris Ricker <kaboom@oobleck.net> 1.3-0.16.rc4
+- Fix php bindings compile on x86_64
+
+* Mon May 19 2008 Chris Ricker <kaboom@oobleck.net> 1.3-0.15.rc4
+- Update to rrdtool 1.3 rc4
+
+* Tue May 13 2008 Jarod Wilson <jwilson@redhat.com> 1.3-0.15.rc1
+- Update to rrdtool 1.3 rc1
+- Fix versioning in changelog entries, had an extra 0 in there...
+- Drop cairo and python patches, they're in 1.3 rc1
+- Add Requires: gettext and libxml2-devel for new translations
+
+* Wed Apr 30 2008 Jarod Wilson <jwilson@redhat.com> 1.3-0.14.beta4
+- Drop some conditional flags, they're not working at the moment...
+
+* Wed Apr 30 2008 Jarod Wilson <jwilson@redhat.com> 1.3-0.13.beta4
+- Fix problem with cairo_save/cairo_restore (#444827)
+
+* Wed Apr 23 2008 Jarod Wilson <jwilson@redhat.com> 1.3-0.12.beta4
+- Fix python bindings rrdtool info implementation (#435468)
+
+* Tue Apr 08 2008 Jarod Wilson <jwilson@redhat.com> 1.3-0.11.beta4
+- Work around apparent version string length issue w/perl 5.10 (#441359)
+
+* Sat Apr 05 2008 Jarod Wilson <jwilson@redhat.com> 1.3-0.10.beta4
+- Fix use of rrd_update in php bindings (#437558)
+
+* Mon Mar 3 2008 Tom "spot" Callaway <tcallawa@redhat.com> 1.3-0.9.beta4
+- rebuild for new perl (again)
+
+* Wed Feb 13 2008 Jarod Wilson <jwilson@redhat.com> 1.3-0.8.beta4
+- Update to rrdtool 1.3 beta4
+
+* Tue Feb 05 2008 Tom "spot" Callaway <tcallawa@redhat.com> 1.3-0.7.beta3
+- rebuild for new perl (and fix license tag)
+
+* Mon Feb 04 2008 Jarod Wilson <jwilson@redhat.com> 1.3-0.6.beta3
+- Plug memory leak (#430879)
+
+* Mon Jan 07 2008 Jarod Wilson <jwilson@redhat.com> 1.3-0.5.beta3
+- Fix right-aligned text alignment and scaling (Resolves: #427609)
+
+* Wed Jan 02 2008 Jarod Wilson <jwilson@redhat.com> 1.3-0.4.beta3
+- Add newly built python egg to %%files
+
+* Wed Jan 02 2008 Jarod Wilson <jwilson@redhat.com> 1.3-0.3.beta3
+- Update to rrdtool 1.3 beta3
+- Return properly from errors in RRDp.pm (Resolves: #427040)
+- Requires: dejavu-lgc-fonts (Resolves: #426935)
+
+* Thu Dec 06 2007 Jarod Wilson <jwilson@redhat.com> 1.3-0.2.beta2
+- Update to rrdtool 1.3 beta2
+
+* Wed Aug 08 2007 Jarod Wilson <jwilson@redhat.com> 1.3-0.1.beta1
+- Update to rrdtool 1.3 beta1
+
+* Tue Jul 10 2007 Jarod Wilson <jwilson@redhat.com> 1.2.999-0.3.r1144
+- Update to latest rrdtool pre-1.3 svn snapshot (svn r1144)
+- Add php abi check (Resolves: #247339)
+
+* Fri Jun 15 2007 Jarod Wilson <jwilson@redhat.com> 1.2.999-0.2.r1127
+- Fix up BuildRequires
+
+* Fri Jun 15 2007 Jarod Wilson <jwilson@redhat.com> 1.2.999-0.1.r1127
+- Update to rrdtool pre-1.3 svn snapshot (svn r1127)
+
+* Mon May 21 2007 Jarod Wilson <jwilson@redhat.com> 1.2.23-5
+- BR: ruby so %%ruby_sitearch gets set
+
+* Mon May 21 2007 Jarod Wilson <jwilson@redhat.com> 1.2.23-4
+- Build ruby bindings
+
+* Thu May 03 2007 Jarod Wilson <jwilson@redhat.com> 1.2.23-3
+- Disable php bits on ppc64 for now, they fail to build
+
+* Thu May 03 2007 Jarod Wilson <jwilson@redhat.com> 1.2.23-2
+- Add BR: perl-devel for Fedora 7 and later
+
+* Tue May 01 2007 Jarod Wilson <jwilson@redhat.com> 1.2.23-1
+- New upstream release
+
+* Tue May 01 2007 Jarod Wilson <jwilson@redhat.com> 1.2.21-1
+- New upstream release
+
+* Wed Apr 25 2007 Jarod Wilson <jwilson@redhat.com> 1.2.19-2
+- Define %%python_version *before* its needed (#237826)
+
+* Mon Apr 09 2007 Jarod Wilson <jwilson@redhat.com> 1.2.19-1
+- New upstream release
+
+* Tue Jan 23 2007 Jarod Wilson <jwilson@redhat.com> 1.2.18-1
+- New upstream release
+
+* Mon Jan 22 2007 Jarod Wilson <jwilson@redhat.com> 1.2.17-1
+- New upstream release
+
+* Tue Jan 02 2007 Jarod Wilson <jwilson@redhat.com> 1.2.15-9
+- Fix crash with long error strings (upstream
+ changesets 929 and 935)
+
+* Thu Dec 14 2006 Jarod Wilson <jwilson@redhat.com> 1.2.15-8
+- Fix for log grid memory leak (#201241)
+
+* Tue Dec 12 2006 Jarod Wilson <jwilson@redhat.com> 1.2.15-7
+- Rebuild for python 2.5
+
+* Tue Nov 14 2006 Jarod Wilson <jwilson@redhat.com> 1.2.15-6
+- Conditionalize python, php and tcl bits (Resolves #203275)
+
+* Wed Oct 25 2006 Jarod Wilson <jwilson@redhat.com> 1.2.15-5
+- Add tcl sub-package (#203275)
+
+* Tue Sep 05 2006 Jarod Wilson <jwilson@redhat.com> 1.2.15-4
+- Rebuild for new glibc
+
+* Wed Aug 02 2006 Jarod Wilson <jwilson@redhat.com> 1.2.15-3
+- One more addition to initrrdtool patch, to fully revert
+ and correct upstream changeset 839
+- Fix for no python in minimal fc4 buildroots
+
+* Tue Aug 1 2006 Mihai Ibanescu <misa@redhat.com> 1.2.15-2
+- Fixed rrdtool-python to import the module properly (patch
+ rrdtool-1.2.15-initrrdtool.patch)
+
+* Mon Jul 17 2006 Jarod Wilson <jwilson@redhat.com> 1.2.15-1
+- Update to 1.2.15
+- Minor spec cleanups
+
+* Sat Jun 24 2006 Jarod Wilson <jwilson@redhat.com> 1.2.13-7
+- Fix up Obsoletes
+
+* Mon Jun 19 2006 Jarod Wilson <jwilson@redhat.com> 1.2.13-6
+- Flip perl, php and python sub-package names around to
+ conform with general practices
+
+* Sat Jun 10 2006 Jarod Wilson <jwilson@redhat.com> 1.2.13-5
+- Minor fixes to make package own created directories
+
+* Wed Jun 07 2006 Jarod Wilson <jwilson@redhat.com> 1.2.13-4
+- Add php bits back into the mix
+
+* Mon Jun 05 2006 Jarod Wilson <jwilson@redhat.com> 1.2.13-3
+- Merge spec fixes from bz 185909
+
+* Sun Jun 04 2006 Jarod Wilson <jwilson@redhat.com> 1.2.13-2
+- Remove explicit perl dep, version grabbing using rpm during
+ rpmbuild not guaranteed to work (fails on ppc in plague),
+ and auto-gen perl deps are sufficient
+
+* Sat Jun 03 2006 Jarod Wilson <jwilson@redhat.com> 1.2.13-1
+- Update to release 1.2.13
+- Merge spec changes from dag, atrpms and mdk builds
+- Additional hacktastic contortions for lib64 & rpath messiness
+- Add missing post/postun ldconfig
+- Fix a bunch of rpmlint errors
+- Disable static libs, per FE guidelines
+- Split off docs
+
+* Wed Apr 19 2006 Chris Ricker <kaboom@oobleck.net> 1.2.12-1
+- Rev to 1.2
+
+* Fri May 20 2005 Matthias Saou <http://freshrpms.net/> 1.0.49-5
+- Include patch from Michael to fix perl module compilation on FC4 (#156242).
+
+* Fri May 20 2005 Matthias Saou <http://freshrpms.net/> 1.0.49-4
+- Fix for the php module patch (Joe Pruett, Dag Wieers), #156716.
+- Update source URL to new location since 1.2 is now the default stable.
+- Don't (yet) update to 1.0.50, as it introduces some changes in the perl
+ modules install.
+
+* Mon Jan 31 2005 Matthias Saou <http://freshrpms.net/> 1.0.49-3
+- Put perl modules in vendor_perl and not site_perl. #146513
+
+* Thu Jan 13 2005 Matthias Saou <http://freshrpms.net/> 1.0.49-2
+- Minor cleanups.
+
+* Thu Aug 25 2004 Dag Wieers <dag@wieers.com> - 1.0.49-1
+- Updated to release 1.0.49.
+
+* Wed Aug 25 2004 Dag Wieers <dag@wieers.com> - 1.0.48-3
+- Fixes for x86_64. (Garrick Staples)
+
+* Fri Jul 2 2004 Matthias Saou <http://freshrpms.net/> 1.0.48-3
+- Actually apply the patch for fixing the php module, doh!
+
+* Thu May 27 2004 Matthias Saou <http://freshrpms.net/> 1.0.48-2
+- Added php.d config entry to load the module once installed.
+
+* Thu May 13 2004 Dag Wieers <dag@wieers.com> - 1.0.48-1
+- Updated to release 1.0.48.
+
+* Tue Apr 06 2004 Dag Wieers <dag@wieers.com> - 1.0.47-1
+- Updated to release 1.0.47.
+
+* Thu Mar 4 2004 Matthias Saou <http://freshrpms.net/> 1.0.46-2
+- Change the strict dependency on perl to fix problem with the recent
+ update.
+
+* Mon Jan 5 2004 Matthias Saou <http://freshrpms.net/> 1.0.46-1
+- Update to 1.0.46.
+- Use system libpng and zlib instead of bundled ones.
+- Added php-rrdtool sub-package for the php4 module.
+
+* Fri Dec 5 2003 Matthias Saou <http://freshrpms.net/> 1.0.45-4
+- Added epoch to the perl dependency to work with rpm > 4.2.
+- Fixed the %% escaping in the perl dep.
+
+* Mon Nov 17 2003 Matthias Saou <http://freshrpms.net/> 1.0.45-2
+- Rebuild for Fedora Core 1.
+
+* Sun Aug 3 2003 Matthias Saou <http://freshrpms.net/>
+- Update to 1.0.45.
+
+* Wed Apr 16 2003 Matthias Saou <http://freshrpms.net/>
+- Update to 1.0.42.
+
+* Mon Mar 31 2003 Matthias Saou <http://freshrpms.net/>
+- Rebuilt for Red Hat Linux 9.
+
+* Wed Mar 5 2003 Matthias Saou <http://freshrpms.net/>
+- Added explicit perl version dependency.
+
+* Sun Feb 23 2003 Matthias Saou <http://freshrpms.net/>
+- Update to 1.0.41.
+
+* Fri Jan 31 2003 Matthias Saou <http://freshrpms.net/>
+- Update to 1.0.40.
+- Spec file cleanup.
+
+* Fri Jul 05 2002 Henri Gomez <hgomez@users.sourceforge.net>
+- 1.0.39
+
+* Mon Jun 03 2002 Henri Gomez <hgomez@users.sourceforge.net>
+- 1.0.38
+
+* Fri Apr 19 2002 Henri Gomez <hgomez@users.sourceforge.net>
+- 1.0.37
+
+* Tue Mar 12 2002 Henri Gomez <hgomez@users.sourceforge.net>
+- 1.0.34
+- rrdtools include zlib 1.1.4 which fix vulnerabilities in 1.1.3
+
diff --git a/program/src/Makefile.am b/program/src/Makefile.am
--- /dev/null
+++ b/program/src/Makefile.am
@@ -0,0 +1,107 @@
+## Process this file with automake to produce Makefile.in
+
+#AUTOMAKE_OPTIONS = foreign
+#
+#ACLOCAL_M4 = $(top_srcdir)/config/aclocal.m4
+#AUTOHEADER = @AUTOHEADER@ --localdir=$(top_srcdir)/config
+
+if STATIC_PROGRAMS
+AM_LDFLAGS = -all-static
+endif
+
+INCLUDES = -DLOCALEDIR="\"$(datadir)/locale\""
+RRD_DEFAULT_FONT=@RRD_DEFAULT_FONT@
+AM_CPPFLAGS = -DRRD_DEFAULT_FONT=\"$(RRD_DEFAULT_FONT)\" -DNUMVERS=@NUMVERS@
+
+UPD_C_FILES = \
+ rrd_parsetime.c \
+ rrd_hw.c \
+ rrd_hw_math.c \
+ rrd_hw_update.c \
+ rrd_diff.c \
+ rrd_format.c \
+ rrd_info.c \
+ rrd_error.c \
+ rrd_open.c \
+ rrd_nan_inf.c \
+ rrd_rpncalc.c \
+ rrd_update.c
+
+RRD_C_FILES = \
+ hash_32.c \
+ pngsize.c \
+ rrd_create.c \
+ rrd_graph.c \
+ rrd_graph_helper.c \
+ rrd_version.c \
+ rrd_last.c \
+ rrd_lastupdate.c \
+ rrd_first.c \
+ rrd_restore.c \
+ rrd_xport.c \
+ rrd_gfx.c \
+ rrd_dump.c \
+ rrd_fetch.c \
+ rrd_resize.c \
+ rrd_tune.c
+
+noinst_HEADERS = \
+ unused.h \
+ rrd_getopt.h rrd_parsetime.h \
+ rrd_i18n.h \
+ rrd_format.h rrd_tool.h rrd_xport.h rrd.h rrd_rpncalc.h \
+ rrd_hw.h rrd_hw_math.h rrd_hw_update.h \
+ fnv.h rrd_graph.h \
+ rrd_is_thread_safe.h
+
+if BUILD_GETOPT
+noinst_HEADERS += rrd_getopt.h
+UPD_C_FILES += rrd_getopt.c rrd_getopt1.c
+endif
+
+noinst_LTLIBRARIES = librrdupd.la
+
+lib_LTLIBRARIES = librrd.la
+if BUILD_MULTITHREAD
+lib_LTLIBRARIES += librrd_th.la
+endif
+
+librrdupd_la_SOURCES = $(UPD_C_FILES) rrd_not_thread_safe.c
+librrdupd_la_LIBADD = $(CORE_LIBS) @LIB_LIBINTL@
+
+librrd_la_SOURCES = $(RRD_C_FILES)
+librrd_la_DEPENDENCIES = librrdupd.la librrd.sym
+librrd_la_LIBADD = librrdupd.la $(ALL_LIBS)
+librrd_la_LDFLAGS = -version-info @LIBVERS@
+librrd_la_LDFLAGS += -export-symbols librrd.sym
+
+librrd_th_la_SOURCES = $(UPD_C_FILES) $(RRD_C_FILES) rrd_thread_safe.c
+librrd_th_la_DEPENDENCIES = librrd.sym
+librrd_th_la_CFLAGS = $(MULTITHREAD_CFLAGS)
+librrd_th_la_LDFLAGS = $(MULTITHREAD_LDFLAGS) -version-info @LIBVERS@
+librrd_th_la_LDFLAGS += -export-symbols librrd.sym
+librrd_th_la_LIBADD = $(ALL_LIBS)
+
+include_HEADERS = rrd.h rrd_format.h
+
+bin_PROGRAMS = rrdtool rrdupdate
+
+if BUILD_RRDCGI
+bin_PROGRAMS += rrdcgi
+endif
+
+rrdcgi_SOURCES = rrd_cgi.c
+rrdcgi_LDADD = librrd.la
+
+rrdupdate_SOURCES = rrdupdate.c
+rrdupdate_LDADD = librrdupd.la
+
+rrdtool_SOURCES = rrd_tool.c
+rrdtool_DEPENDENCIES = librrd.la
+rrdtool_LDADD = librrd.la
+
+# strftime is here because we do not usually need it. unices have propper
+# iso date support
+EXTRA_DIST= strftime.c strftime.h rrd_getopt.c rrd_getopt1.c rrd_getopt.h \
+ win32comp.c rrd_thread_safe_nt.c get_ver.awk librrd.sym
+
diff --git a/program/src/compile_afm.pl b/program/src/compile_afm.pl
--- /dev/null
@@ -0,0 +1,479 @@
+#!/usr/local/bin/perl -w
+
+require 5.005;
+use strict;
+
+# The glyps list can be downloaded from
+# http://partners.adobe.com/asn/developer/type/glyphlist.txt
+# This URL is from this page:
+# http://partners.adobe.com/asn/developer/type/unicodegn.html
+# which is refered from
+# http://partners.adobe.com/asn/developer/technotes/fonts.html
+
+my $onlyHelvetica = 0;
+
+my %globalName2Unicode;
+my %font_code = ();
+
+my $indent0 = "";
+my $indent1 = " ";
+my $indent2 = $indent1 x 3;
+
+my $q = 0;
+my $qU = 0;
+
+sub read_glyphlist
+{
+ my $fn ="glyphlist.txt";
+ open(FH, $fn)
+ || die "Can't read $fn\n";
+ my %seen = ();
+ while (<FH>) {
+ next if /^\s*#/;
+ next unless /^([0-9A-F]{4});(\w+);/;
+ my $unicode = 0 + hex($1);
+ my $name = $2;
+ next if ($globalName2Unicode{$name});
+ $globalName2Unicode{$name} = $unicode;
+ }
+ close(FH);
+}
+
+sub process_all_fonts
+{
+ my $dir = ".";
+ my $wc = "*.afm";
+ $wc = "Helvetica.afm" if $onlyHelvetica;
+ $wc = "ZapfDin.afm" if 0;
+ $wc = "Helve*.afm" if 0;
+ $wc = "Times-BoldItalic.afm" if 0;
+ foreach my $fn (glob("$dir/$wc")) {
+ process_font($fn);
+ }
+}
+
+sub process_font
+{
+ my ($fn) = @_;
+ print STDERR "Compiling afm file: $fn\n";
+ my %fi = (); # font info
+ my $c = "";
+ $fi{C} = \$c;
+ $fi{ligaturesR} = {};
+ $fi{FontSpecificUnicodeNameToChar} = {};
+ $fi{filename} = $fn;
+ $fi{filename} =~ s/.*\///;
+ open(FH, $fn) || die "Can't open $fn\n";
+ print STDERR "Reads global font info\n" if $q;
+ while (<FH>) {
+ chomp;
+ next if /^\s*$/ || /^\s*#/;
+ last if /^StartCharMetrics/;
+ next unless (/^(\S+)\s+(\S(.*\S)?)/);
+ my $id = $1;
+ my $value = $2;
+ $value =~ s/\s+/ /g;
+ $fi{"Afm$id"} = $value;
+ }
+ my $fontName = $fi{AfmFontName};
+ $c .= "\n\n/* ". ("-" x 66) . "*/\n";
+ $c .= "/* FontName: $fontName */\n";
+ $c .= "/* FullName: $fi{AfmFullName} */\n";
+ $c .= "/* FamilyName: $fi{AfmFamilyName} */\n";
+ $fi{cName} = $fontName;
+ $fi{cName} =~ s/\W/_/g;
+ my %charMetrics = ();
+ my %kerning = ();
+ read_charmetrics(\%fi, \%charMetrics);
+ while (<FH>) {
+ read_kerning(\%fi, \%kerning) if /^StartKernPairs/;
+ }
+ if (0) {
+ my @names = keys %charMetrics;
+ print STDERR "Did read ", ($#names + 1), " font metrics\n";
+ }
+ write_font(\%fi, \%charMetrics, \%kerning);
+}
+
+sub read_charmetrics
+{
+ my ($fiR, $charMetricsR) = @_;
+ print STDERR "Reads char metric info\n" if $q;
+ my $isZapfDingbats = $$fiR{AfmFontName} eq "ZapfDingbats";
+ my $ligaturesR = $$fiR{ligaturesR};
+ my %ligatures = ();
+ my %seenUnicodes = ();
+ while (<FH>) {
+ chomp;
+ next if /^\s*$/ || /^\s*#/;
+ last if /^EndCharMetrics/;
+#next unless /N S / || /N comma /;
+#next unless /N ([sfil]|fi) /;
+#print "$_\n";
+ my $line = $_;
+# C 102 ; WX 333 ; N f ; B -169 -205 446 698 ; L i fi ; L l fl ;
+ my ($width, $unicode, $name, @charLigatures);
+ foreach (split/\s*;\s*/, $line) {
+ if (/^C\s+(-?\d+)/) {
+ $unicode = 0 + $1;
+ } elsif (/^N\s+(\w+)/) {
+ $name = $1;
+ } elsif (/^WX?\s+(-?\d+)/) {
+ $width = normalize_width($1, 0);
+ } elsif (/^L\s+(\w+)\s+(\w+)/) {
+ push(@charLigatures, $1, $2);
+ }
+ }
+ if ($unicode < 0) {
+ unless (defined $name) {
+ print STDERR "Glyph missing name and code: $_\n";
+ next;
+ }
+ $unicode = name2uni($fiR, $name);
+ print STDERR "name2uni: $name -> $unicode\n" if $qU && 0;
+ } elsif (defined $name) {
+ my $std = $globalName2Unicode{$name};
+ if (!defined $std) {
+ print STDERR "Adds unicode mapping: ",
+ "$name -> $unicode\n" if $qU;
+ ${$$fiR{FontSpecificUnicodeNameToChar}}{$name} = $unicode;
+ } else {
+ $unicode = $std;
+ }
+ }
+ if (!defined($unicode) || $unicode <= 0) {
+ next if $isZapfDingbats && $name =~ /^a(\d+)$/;
+ next if $$fiR{AfmFontName} eq "Symbol" && $name eq "apple";
+ print STDERR "Glyph '$name' has unknown unicode: $_\n";
+ next;
+ }
+ unless (defined $width) {
+ print STDERR "Glyph '$name' missing width: $_\n";
+ next;
+ }
+ if ($seenUnicodes{$unicode}) {
+ print STDERR "Duplicate character: unicode = $unicode, ",
+ "$name and ", $seenUnicodes{$unicode},
+ " (might be due to Adobe charset remapping)\n";
+ next;
+ }
+ $seenUnicodes{$unicode} = $name;
+ my %c = ();
+ $c{name} = $name;
+ $c{unicode} = $unicode;
+ $c{width} = $width;
+ $$charMetricsR{$unicode} = \%c;
+ $ligatures{$unicode} = \@charLigatures if $#charLigatures >= 0;
+ }
+ foreach my $unicode (keys %ligatures) {
+ my $aR = $ligatures{$unicode};
+ my $unicode2 = name2uni($fiR, $$aR[0]);
+ my $unicode3 = name2uni($fiR, $$aR[1]);
+ unless ($unicode2) {
+ print STDERR "Missing ligature char 1: $$aR[0]\n";
+ next;
+ }
+ unless ($unicode3) {
+ print STDERR "Missing ligature char 2: $$aR[1]\n";
+ next;
+ }
+ my $key = sprintf("%04d;%04d", $unicode, $unicode2);
+ $$ligaturesR{$key} = $unicode3;
+ }
+}
+
+sub name2uni
+{
+ my ($fiR, $name) = @_;
+ my $fontMapR = $$fiR{FontSpecificUnicodeNameToChar};
+ return $globalName2Unicode{$name} || $$fontMapR{$name};
+}
+
+sub read_kerning
+{
+ my ($fiR, $kerningR) = @_;
+ print STDERR "Reads kerning info\n" if $q;
+ while (<FH>) {
+ chomp;
+ next if /^\s*$/ || /^\s*#/;
+ last if /^EndKernPairs/;
+ unless (/^KPX\s+(\w+)\s+(\w+)\s+(-?\d+)\s*$/) {
+ print STDERR "Can't parse kern spec: $_\n";
+ next;
+ }
+ my $name1 = $1;
+ my $name2 = $2;
+ my $delta = normalize_width($3, 1);
+ next unless $delta;
+ my $unicode1 = name2uni($fiR, $name1);
+ my $unicode2 = name2uni($fiR, $name2);
+ unless ($unicode1 && $unicode2) {
+ print "Unknown kern pair: $name1 and $name2\n";
+ next;
+ }
+ my $charR = $$kerningR{$unicode1};
+ unless (defined $charR) {
+ my %c = ();
+ $charR = \%c;
+ $$kerningR{$unicode1} = $charR;
+ }
+ $$charR{$unicode2} = $delta;
+ }
+}
+
+sub write_font
+{
+ my ($fiR, $charMetricsR, $kerningR) = @_;
+ print STDERR "Writes font\n" if $q;
+ my $cR = $$fiR{C};
+ $$fiR{widthsA} = make_array();
+ $$fiR{kerning_indexA} = make_array();
+ $$fiR{kerning_dataA} = make_array();
+ $$fiR{highchars_indexA} = make_array();
+ $$fiR{ligaturesA} = make_array();
+ write_font_metrics($fiR, $charMetricsR, $kerningR);
+ write_ligatures($fiR);
+ my $widths_count = array_size($$fiR{widthsA});
+ my $kerning_index_count = array_size($$fiR{kerning_indexA});
+ my $kerning_data_count = array_size($$fiR{kerning_dataA});
+ my $highchars_count = array_size($$fiR{highchars_indexA});
+ my $ligatures_count = array_size($$fiR{ligaturesA}) / 3;
+ my $info_code = "";
+ my $i2 = $indent2;
+ my $packedSize = $widths_count + 2 * $kerning_index_count +
+ $kerning_data_count + 2 * $highchars_count +
+ 3 * 2 * $ligatures_count;
+ $info_code .= $indent1 . "{ /* $$fiR{filename} $packedSize bytes */\n";
+ $info_code .= $i2 . "\"$$fiR{AfmFontName}\",";
+ $info_code .= " \"$$fiR{AfmFullName}\",\n";
+ $info_code .= $i2 . $$fiR{widthsACName} . ",\n";
+ $info_code .= $i2 . $$fiR{kerning_indexACName} . ",\n";
+ $info_code .= $i2 . $$fiR{kerning_dataACName} . ",\n";
+ $info_code .= $i2 . $$fiR{highchars_indexACName} . ", ";
+ $info_code .= $highchars_count . ",\n";
+ $info_code .= $i2 . $$fiR{ligaturesACName} . ", ";
+ $info_code .= $ligatures_count;
+ $info_code .= "},\n";
+ $font_code{$$fiR{AfmFullName}} = { TABLES => $$cR, INFO => $info_code};
+}
+
+sub write_font_metrics
+{
+ my ($fiR, $charMetricsR, $kerningR) = @_;
+ print STDERR "Writes font metrics\n" if $q;
+ my $lastUnicode = 31;
+ my $cR = $$fiR{C};
+ my $widthsA = $$fiR{widthsA};
+ my $kerning_indexA = $$fiR{kerning_indexA};
+ my $kerning_dataA = $$fiR{kerning_dataA};
+ my $highchars_indexA = $$fiR{highchars_indexA};
+ my @uniArray = sort { $a <=> $b } keys %$charMetricsR;
+ my $highchars_count = 0;
+ my $had_kerning = 0;
+ while (1) {
+ my $fill = 0;
+ if ($#uniArray < 0) {
+ last if $lastUnicode > 126;
+ $fill = 1;
+ } elsif ($lastUnicode < 126 && $uniArray[0] > $lastUnicode + 1) {
+ $fill = 1;
+ }
+ if ($fill) {
+ $lastUnicode++;
+#print STDERR "fill for $lastUnicode, $#uniArray, $uniArray[0]\n";
+ append_to_array($widthsA, 0);
+ append_to_array($kerning_indexA, 0);
+ next;
+ }
+ my $unicode = shift @uniArray;
+ next if $unicode < 32;
+ $lastUnicode = $unicode;
+ my $metricsR = $$charMetricsR{$unicode};
+ if ($unicode > 126) {
+ append_to_array($highchars_indexA, $unicode);
+ $highchars_count++;
+ }
+ my $m = $$metricsR{width};
+ $m = "/* ".array_size($widthsA)."=$unicode */". $m if 0;
+ append_to_array($widthsA, $m);
+ my $kerningInfoR = $$kerningR{$unicode};
+ my $kerning_index = 0;
+ if (defined $kerningInfoR) {
+ my @kerns = ();
+ foreach my $unicode2 (sort { $a <=> $b } keys %$kerningInfoR) {
+ my $delta = $$kerningInfoR{$unicode2};
+ append_escaped_16bit_int(\@kerns, $unicode2);
+ push(@kerns, $delta);
+ $had_kerning = 1;
+ }
+ $kerning_index = append_8bit_subarray($kerning_dataA, 2, @kerns);
+ }
+ append_to_array($kerning_indexA, $kerning_index);
+ }
+ $$fiR{kerning_indexA} = make_array() if !$had_kerning;
+ write_array($fiR, "widths", "afm_cuint8");
+ write_array($fiR, "kerning_index", "afm_sint16");
+ write_array($fiR, "kerning_data", "afm_cuint8");
+ write_array($fiR, "highchars_index", "afm_cuint16");
+}
+
+sub write_ligatures
+{
+ my ($fiR) = @_;
+ print STDERR "Writes font ligatures\n" if $q;
+ my $ligaturesA = $$fiR{ligaturesA};
+ my $ligaturesR = $$fiR{ligaturesR};
+ foreach (sort keys %$ligaturesR) {
+ unless (/^(\w{4});(\w{4})$/) {
+ die "Invalid ligature key: $_";
+ }
+ append_to_array($ligaturesA, $1 + 0, $2 + 0, $$ligaturesR{$_});
+ }
+ write_array($fiR, "ligatures", "afm_cunicode");
+}
+
+sub indent
+{
+ my ($num) = @_;
+ return " " x $num;
+}
+
+sub make_array
+{
+ my @a = ();
+ return \@a;
+}
+
+sub append_to_array
+{
+ my ($aR, @newElements) = @_;
+ my $z1 = array_size($aR);
+ push(@$aR, @newElements);
+ my $z2 = array_size($aR);
+ my $zz = $#newElements +1;
+}
+
+sub append_8bit_subarray
+{
+ my ($aR, $elementsPerItem, @newElements) = @_;
+ push(@$aR, 42) if !array_size($aR); # initial dummy value
+ my $idx = $#{$aR} + 1;
+#print "append_8bit_subarray ", ($#newElements+1), " = (", join(", ", @newElements), ") -> $idx\n";
+ append_escaped_16bit_int($aR, ($#newElements + 1) / $elementsPerItem);
+ push(@$aR, @newElements);
+ die "Can't handle that big sub array, sorry...\n" if $idx > 50000;
+ return $idx;
+}
+
+sub append_escaped_16bit_int
+{
+ my ($aR, $count) = @_;
+ die "Invalid count = 0\n" unless $count;
+ if ($count >= 510) {
+ push(@$aR, 1, int($count / 256), int($count % 256));
+ print STDERR "full: $count\n" if 0;
+ } elsif ($count >= 254) {
+ push(@$aR, 0, $count - 254);
+ print STDERR "semi: $count\n" if 0;
+ } else {
+ push(@$aR, $count + 1);
+ }
+}
+
+sub array_size
+{
+ my ($aR) = @_;
+ return $#{$aR} + 1;
+}
+
+sub write_array
+{
+ my ($fiR, $name, $type) = @_;
+ my $aR = $$fiR{$name."A"};
+ my $cName = $$fiR{cName};
+ my $num = $#{$aR} + 1;
+ my $array_name_key = $name."ACName";
+ if ($num == 0) {
+ $$fiR{$array_name_key} = "NULL";
+ return;
+ }
+ my $cR = $$fiR{C};
+ my $array_name = "afm_" . $cName . "_" . $name;
+ $$fiR{$array_name_key} = $array_name;
+ $$cR .= "static $type $array_name" . "[] = { /* $num */\n";
+ my $line = $indent1;
+ for (my $i = 0; $i < $num; $i++) {
+ $line .= "," if $i > 0;
+ if (length($line) > 65) {
+ $line .= "\n";
+ $$cR .= $line;
+ $line = $indent1;
+ }
+ $line .= $$aR[$i];
+ }
+ $line .= "\n";
+ $$cR .= $line;
+ $$cR .= "};\n";
+}
+
+sub normalize_width
+{
+ my ($w, $signed) = @_;
+ my $n = int(($w + 3) / 6);
+ if ($signed) {
+ $n = -128 if $n < -128;
+ $n = 127 if $n > 127;
+ $n = 256 + $n if $n < 0; # make unsigned.
+ } else {
+ $n = 0 if $n < 0;
+ $n = 255 if $n > 255;
+ }
+ return $n;
+}
+
+sub main
+{
+ my $cfn = "../../src/rrd_afm_data.c";
+ read_glyphlist();
+ process_all_fonts();
+ my @fonts = sort keys %font_code;
+ unless ($#fonts >= 0) {
+ die "You must have at least 1 font.\n";
+ }
+ open(CFILE, ">$cfn") || die "Can't create $cfn\n";
+ print CFILE header($cfn);
+ print CFILE ${$font_code{$_}}{TABLES} foreach @fonts;
+ print CFILE "const afm_fontinfo afm_fontinfolist[] = {\n";
+ print CFILE ${$font_code{$_}}{INFO} foreach @fonts;
+ print CFILE $indent1 . "{ 0, 0, 0 }\n";
+ print CFILE $indent0 . "};\n";
+ print CFILE $indent0 . "const int afm_fontinfo_count = ",
+ ($#fonts + 1), ";\n";
+ close(CFILE);
+ print STDERR "Compiled ", ($#fonts+1), " fonts.\n";
+}
+
+sub header
+{
+ my ($fn) = @_;
+ $fn =~ s/.*\///;
+ my $h = $fn;
+ $h =~ s/\.c$/.h/;
+ return <<"END";
+/****************************************************************************
+ * RRDtool 1.1.x Copyright Tobias Oetiker, 1997 - 2002
+ ****************************************************************************
+ * $fn Encoded afm (Adobe Font Metrics) for selected fonts.
+ ****************************************************************************
+ *
+ * THIS FILE IS AUTOGENERATED BY PERL. DO NOT EDIT.
+ *
+ ****************************************************************************/
+
+#include "$h"
+#include <stdlib.h>
+
+END
+}
+
+main();
diff --git a/program/src/fnv.h b/program/src/fnv.h
--- /dev/null
+++ b/program/src/fnv.h
@@ -0,0 +1,114 @@
+/*
+ * fnv - Fowler/Noll/Vo- hash code
+ *
+ * @(#) $Revision$
+ * @(#) $Id$
+ * @(#) $Source$
+ *
+ ***
+ *
+ * Fowler/Noll/Vo- hash
+ *
+ * The basis of this hash algorithm was taken from an idea sent
+ * as reviewer comments to the IEEE POSIX P1003.2 committee by:
+ *
+ * Phong Vo (http://www.research.att.com/info/kpv/)
+ * Glenn Fowler (http://www.research.att.com/~gsf/)
+ *
+ * In a subsequent ballot round:
+ *
+ * Landon Curt Noll (http://reality.sgi.com/chongo/)
+ *
+ * improved on their algorithm. Some people tried this hash
+ * and found that it worked rather well. In an EMail message
+ * to Landon, they named it the ``Fowler/Noll/Vo'' or FNV hash.
+ *
+ * FNV hashes are architected to be fast while maintaining a low
+ * collision rate. The FNV speed allows one to quickly hash lots
+ * of data while maintaining a reasonable collision rate. See:
+ *
+ * http://reality.sgi.com/chongo/tech/comp/fnv/
+ *
+ * for more details as well as other forms of the FNV hash.
+ *
+ ***
+ *
+ * NOTE: The FNV-0 historic hash is not recommended. One should use
+ * the FNV-1 hash instead.
+ *
+ * To use the 32 bit FNV-0 historic hash, pass FNV0_32_INIT as the
+ * Fnv32_t hashval argument to fnv_32_buf() or fnv_32_str().
+ *
+ * To use the 64 bit FNV-0 historic hash, pass FNV0_64_INIT as the
+ * Fnv64_t hashval argument to fnv_64_buf() or fnv_64_str().
+ *
+ * To use the recommended 32 bit FNV-1 hash, pass FNV1_32_INIT as the
+ * Fnv32_t hashval argument to fnv_32_buf() or fnv_32_str().
+ *
+ * To use the recommended 64 bit FNV-1 hash, pass FNV1_64_INIT as the
+ * Fnv64_t hashval argument to fnv_64_buf() or fnv_64_str().
+ *
+ ***
+ *
+ * Please do not copyright this code. This code is in the public domain.
+ *
+ * LANDON CURT NOLL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
+ * EVENT SHALL LANDON CURT NOLL BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
+ * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+ * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ *
+ * By:
+ * chongo <Landon Curt Noll> /\oo/\
+ * http://reality.sgi.com/chongo/
+ * EMail: chongo_fnv at prime dot engr dot sgi dot com
+ *
+ * Share and Enjoy! :-)
+ */
+
+#if !defined(__FNV_H__)
+#define __FNV_H__
+
+
+/*
+ * 32 bit FNV-0 hash type
+ */
+typedef unsigned long Fnv32_t;
+
+
+/*
+ * 32 bit FNV-0 zero initial basis
+ *
+ * This historic hash is not recommended. One should use
+ * the FNV-1 hash and inital basis instead.
+ */
+#define FNV0_32_INIT ((Fnv32_t)0)
+
+
+/*
+ * 32 bit FNV-1 non-zero initial basis
+ *
+ * The FNV-1 initial basis is the FNV-0 hash of the following 32 octets:
+ *
+ * chongo <Landon Curt Noll> /\../\
+ *
+ * Note that the \'s above are not back-slashing escape characters.
+ * They are literal ASCII backslash 0x5c characters.
+ */
+#define FNV1_32_INIT ((Fnv32_t)0x811c9dc5)
+
+Fnv32_t fnv_32_buf(
+ const void *,
+ size_t,
+ Fnv32_t);
+
+Fnv32_t fnv_32_str(
+ const char *,
+ Fnv32_t);
+
+unsigned long FnvHash(
+ const char *);
+
+#endif /* __FNV_H__ */
diff --git a/program/src/get_ver.awk b/program/src/get_ver.awk
--- /dev/null
+++ b/program/src/get_ver.awk
@@ -0,0 +1,40 @@
+# ****************************************************************************
+# RRDtool 1.2.19 Copyright by Tobi Oetiker, 1997-2007
+# ****************************************************************************
+# get_ver.awk AWK Script for non-configure builds
+# ****************************************************************************
+# $Id: get_ver.awk 1000 2007-14-02 05:51:34Z oetiker $
+# ****************************************************************************
+BEGIN {
+ # fetch rrdtool version number from input file and write them to STDOUT
+ while ((getline < ARGV[1]) > 0) {
+ if (match ($0, /^AC_INIT/)) {
+ split($1, t, ",");
+ my_ver_str = substr(t[2],2,length(t[2])-3);
+ split(my_ver_str, v, ".");
+ gsub("[^0-9].*$", "", v[3]);
+ my_ver = v[1] "," v[2] "," v[3];
+ }
+ if (match ($0, /^NUMVERS=/)) {
+ split($1, t, "=");
+ my_ver_num = t[2];
+ }
+ }
+ # read from from input file, replace placeholders, and write to STDOUT
+ if (ARGV[2]) {
+ while ((getline < ARGV[2]) > 0) {
+ if (match ($0, /@@NUMVERS@@/)) {
+ gsub("@@NUMVERS@@", my_ver_num, $0);
+ }
+ if (match ($0, /@@PACKAGE_VERSION@@/)) {
+ gsub("@@PACKAGE_VERSION@@", "" my_ver_str "", $0);
+ }
+ print;
+ }
+ } else {
+ print "RRD_VERSION = " my_ver "";
+ print "RRD_VERSION_STR = " my_ver_str "";
+ print "RRD_NUMVERS = " my_ver_num "";
+ }
+}
+
diff --git a/program/src/hash_32.c b/program/src/hash_32.c
--- /dev/null
+++ b/program/src/hash_32.c
@@ -0,0 +1,156 @@
+/*
+ * hash_32 - 32 bit Fowler/Noll/Vo hash code
+ *
+ *
+ ***
+ *
+ * Fowler/Noll/Vo hash
+ *
+ * The basis of this hash algorithm was taken from an idea sent
+ * as reviewer comments to the IEEE POSIX P1003.2 committee by:
+ *
+ * Phong Vo (http://www.research.att.com/info/kpv/)
+ * Glenn Fowler (http://www.research.att.com/~gsf/)
+ *
+ * In a subsequent ballot round:
+ *
+ * Landon Curt Noll (http://reality.sgi.com/chongo/)
+ *
+ * improved on their algorithm. Some people tried this hash
+ * and found that it worked rather well. In an EMail message
+ * to Landon, they named it the ``Fowler/Noll/Vo'' or FNV hash.
+ *
+ * FNV hashes are architected to be fast while maintaining a low
+ * collision rate. The FNV speed allows one to quickly hash lots
+ * of data while maintaining a reasonable collision rate. See:
+ *
+ * http://reality.sgi.com/chongo/tech/comp/fnv/
+ *
+ * for more details as well as other forms of the FNV hash.
+ ***
+ *
+ * NOTE: The FNV-0 historic hash is not recommended. One should use
+ * the FNV-1 hash instead.
+ *
+ * To use the 32 bit FNV-0 historic hash, pass FNV0_32_INIT as the
+ * Fnv32_t hashval argument to fnv_32_buf() or fnv_32_str().
+ *
+ * To use the recommended 32 bit FNV-1 hash, pass FNV1_32_INIT as the
+ * Fnv32_t hashval argument to fnv_32_buf() or fnv_32_str().
+ *
+ ***
+ *
+ * Please do not copyright this code. This code is in the public domain.
+ *
+ * LANDON CURT NOLL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
+ * EVENT SHALL LANDON CURT NOLL BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
+ * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+ * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ *
+ * By:
+ * chongo <Landon Curt Noll> /\oo/\
+ * http://reality.sgi.com/chongo/
+ * EMail: chongo_fnv at prime dot engr dot sgi dot com
+ *
+ * Share and Enjoy! :-)
+ */
+
+#include <stdlib.h>
+#include "fnv.h"
+
+
+/*
+ * 32 bit magic FNV-0 and FNV-1 prime
+ */
+#define FNV_32_PRIME ((Fnv32_t)0x01000193)
+
+
+/*
+ * fnv_32_buf - perform a 32 bit Fowler/Noll/Vo hash on a buffer
+ *
+ * input:
+ * buf - start of buffer to hash
+ * len - length of buffer in octets
+ * hval - previous hash value or 0 if first call
+ *
+ * returns:
+ * 32 bit hash as a static hash type
+ *
+ * NOTE: To use the 32 bit FNV-0 historic hash, use FNV0_32_INIT as the hval
+ * argument on the first call to either fnv_32_buf() or fnv_32_str().
+ *
+ * NOTE: To use the recommended 32 bit FNV-1 hash, use FNV1_32_INIT as the hval
+ * argument on the first call to either fnv_32_buf() or fnv_32_str().
+ */
+Fnv32_t fnv_32_buf(
+ const void *buf,
+ size_t len,
+ Fnv32_t hval)
+{
+ const unsigned char *bp = (const unsigned char *) buf; /* start of buffer */
+ const unsigned char *be = bp + len; /* beyond end of buffer */
+
+ /*
+ * FNV-1 hash each octet in the buffer
+ */
+ while (bp < be) {
+
+ /* multiply by the 32 bit FNV magic prime mod 2^64 */
+ hval *= FNV_32_PRIME;
+
+ /* xor the bottom with the current octet */
+ hval ^= (Fnv32_t) *bp++;
+ }
+
+ /* return our new hash value */
+ return hval;
+}
+
+
+/*
+ * fnv_32_str - perform a 32 bit Fowler/Noll/Vo hash on a string
+ *
+ * input:
+ * str - string to hash
+ * hval - previous hash value or 0 if first call
+ *
+ * returns:
+ * 32 bit hash as a static hash type
+ *
+ * NOTE: To use the 32 bit FNV-0 historic hash, use FNV0_32_INIT as the hval
+ * argument on the first call to either fnv_32_buf() or fnv_32_str().
+ *
+ * NOTE: To use the recommended 32 bit FNV-1 hash, use FNV1_32_INIT as the hval
+ * argument on the first call to either fnv_32_buf() or fnv_32_str().
+ */
+Fnv32_t fnv_32_str(
+ const char *str,
+ Fnv32_t hval)
+{
+ const unsigned char *s = (const unsigned char *) str; /* unsigned string */
+
+ /*
+ * FNV-1 hash each octet in the buffer
+ */
+ while (*s) {
+
+ /* multiply by the 32 bit FNV magic prime mod 2^64 */
+ hval *= FNV_32_PRIME;
+
+ /* xor the bottom with the current octet */
+ hval ^= (Fnv32_t) *s++;
+ }
+
+ /* return our new hash value */
+ return hval;
+}
+
+/* a wrapper function for fnv_32_str */
+unsigned long FnvHash(
+ const char *str)
+{
+ return fnv_32_str(str, FNV1_32_INIT);
+}
diff --git a/program/src/librrd.sym.in b/program/src/librrd.sym.in
--- /dev/null
@@ -0,0 +1,51 @@
+rrd_clear_error
+rrd_close
+rrd_create
+rrd_create_r
+rrd_dontneed
+rrd_dump
+rrd_dump_r
+rrd_fetch
+rrd_fetch_r
+rrd_first
+rrd_first_r
+rrd_flush
+rrd_free
+rrd_free_context
+rrd_freemem
+rrd_get_context
+rrd_get_error
+rrd_graph
+rrd_graph_v
+rrd_info
+rrd_info_free
+rrd_info_print
+rrd_info_push
+rrd_init
+rrd_last
+rrd_last_r
+rrd_lastupdate
+rrd_lock
+rrd_new_context
+rrd_open
+rrd_parsetime
+rrd_proc_start_end
+rrd_read
+rrd_resize
+rrd_restore
+rrd_seek
+rrd_set_error
+rrd_set_to_DINF
+rrd_set_to_DNAN
+rrd_strerror
+rrd_strversion
+rrd_tell
+rrd_test_error
+rrd_tune
+rrd_update
+rrd_update_r
+rrd_update_v
+rrd_version
+rrd_write
+rrd_xport
+@RRD_GETOPT_LONG@
diff --git a/program/src/pngsize.c b/program/src/pngsize.c
--- /dev/null
+++ b/program/src/pngsize.c
@@ -0,0 +1,56 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ *****************************************************************************
+ * pngsize.c determine the size of a PNG image
+ *****************************************************************************/
+
+#include <png.h>
+#include "rrd_tool.h"
+
+int PngSize(
+ FILE * fd,
+ long *width,
+ long *height)
+{
+ png_structp png_read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
+ (png_voidp) NULL,
+ /* we would need to point to error handlers
+ here to do it properly */
+ (png_error_ptr) NULL,
+ (png_error_ptr) NULL);
+
+ png_infop info_ptr = png_create_info_struct(png_read_ptr);
+
+ (*width) = 0;
+ (*height) = 0;
+
+/* this is to make compile on aix work since they seem to define jmpbuf
+ to be _jmpbuf which breaks compilation */
+
+#ifndef png_jmpbuf
+#ifdef PNG_SETJMP_SUPPORTED
+# define png_jmpbuf(png_ptr) ((png_ptr)->PNG_jmpbuf)
+#else
+#ifdef jmpbuf
+#undef jmpbuf
+#endif
+# define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf)
+#endif
+#endif
+
+ if (setjmp(png_jmpbuf(png_read_ptr))) {
+ png_destroy_read_struct(&png_read_ptr, &info_ptr, (png_infopp) NULL);
+ return 0;
+ }
+
+ png_init_io(png_read_ptr, fd);
+ png_read_info(png_read_ptr, info_ptr);
+ (*width) = png_get_image_width(png_read_ptr, info_ptr);
+ (*height) = png_get_image_height(png_read_ptr, info_ptr);
+
+ png_destroy_read_struct(&png_read_ptr, &info_ptr, NULL);
+ if (*width > 0 && *height > 0)
+ return 1;
+ else
+ return 0;
+}
diff --git a/program/src/rrd.h b/program/src/rrd.h
--- /dev/null
+++ b/program/src/rrd.h
@@ -0,0 +1,377 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ *****************************************************************************
+ * rrdlib.h Public header file for librrd
+ *****************************************************************************
+ * $Id$
+ * $Log$
+ * Revision 1.9 2005/02/13 16:13:33 oetiker
+ * let rrd_graph return the actual value range it picked ...
+ * -- Henrik Stoerner <henrik@hswn.dk>
+ *
+ * Revision 1.8 2004/05/26 22:11:12 oetiker
+ * reduce compiler warnings. Many small fixes. -- Mike Slifcak <slif@bellsouth.net>
+ *
+ * Revision 1.7 2003/11/12 22:14:26 oetiker
+ * allow to pass an open filehandle into rrd_graph as an extra argument
+ *
+ * Revision 1.6 2003/11/11 19:46:21 oetiker
+ * replaced time_value with rrd_time_value as MacOS X introduced a struct of that name in their standard headers
+ *
+ * Revision 1.5 2003/04/25 18:35:08 jake
+ * Alternate update interface, updatev. Returns info about CDPs written to disk as result of update. Output format is similar to rrd_info, a hash of key-values.
+ *
+ * Revision 1.4 2003/04/01 22:52:23 jake
+ * Fix Win32 build. VC++ 6.0 and 7.0 now use the thread-safe code.
+ *
+ * Revision 1.3 2003/02/13 07:05:27 oetiker
+ * Find attached the patch I promised to send to you. Please note that there
+ * are three new source files (src/rrd_is_thread_safe.h, src/rrd_thread_safe.c
+ * and src/rrd_not_thread_safe.c) and the introduction of librrd_th. This
+ * library is identical to librrd, but it contains support code for per-thread
+ * global variables currently used for error information only. This is similar
+ * to how errno per-thread variables are implemented. librrd_th must be linked
+ * alongside of libpthred
+ *
+ * There is also a new file "THREADS", holding some documentation.
+ *
+ * -- Peter Stamfest <peter@stamfest.at>
+ *
+ * Revision 1.2 2002/05/07 21:58:32 oetiker
+ * new command rrdtool xport integrated
+ * -- Wolfgang Schrimm <Wolfgang.Schrimm@urz.uni-heidelberg.de>
+ *
+ * Revision 1.1.1.1 2001/02/25 22:25:05 oetiker
+ * checkin
+ *
+ *****************************************************************************/
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef _RRDLIB_H
+#define _RRDLIB_H
+
+#include <sys/types.h> /* for off_t */
+#include <unistd.h> /* for off_t */
+#include <time.h>
+#include <stdio.h> /* for FILE */
+
+
+/* Formerly rrd_nan_inf.h */
+#ifndef DNAN
+# define DNAN rrd_set_to_DNAN()
+#endif
+
+#ifndef DINF
+# define DINF rrd_set_to_DINF()
+#endif
+ double rrd_set_to_DNAN(
+ void);
+ double rrd_set_to_DINF(
+ void);
+/* end of rrd_nan_inf.h */
+
+/* Transplanted from rrd_format.h */
+ typedef double rrd_value_t; /* the data storage type is
+ * double */
+/* END rrd_format.h */
+
+/* information about an rrd file */
+ typedef struct rrd_file_t {
+ int fd; /* file descriptor if this rrd file */
+ char *file_start; /* start address of an open rrd file */
+ off_t header_len; /* length of the header of this rrd file */
+ off_t file_len; /* total size of the rrd file */
+ off_t pos; /* current pos in file */
+ } rrd_file_t;
+
+/* rrd info interface */
+ typedef struct rrd_blob_t {
+ unsigned long size; /* size of the blob */
+ unsigned char *ptr; /* pointer */
+ } rrd_blob_t;
+
+ typedef enum rrd_info_type { RD_I_VAL = 0,
+ RD_I_CNT,
+ RD_I_STR,
+ RD_I_INT,
+ RD_I_BLO
+ } rrd_info_type_t;
+
+ typedef union rrd_infoval {
+ unsigned long u_cnt;
+ rrd_value_t u_val;
+ char *u_str;
+ int u_int;
+ rrd_blob_t u_blo;
+ } rrd_infoval_t;
+
+ typedef struct rrd_info_t {
+ char *key;
+ rrd_info_type_t type;
+ rrd_infoval_t value;
+ struct rrd_info_t *next;
+ } rrd_info_t;
+
+
+/* main function blocks */
+ int rrd_create(
+ int,
+ char **);
+ rrd_info_t *rrd_info(
+ int,
+ char **);
+ rrd_info_t *rrd_info_push(
+ rrd_info_t *,
+ char *,
+ rrd_info_type_t,
+ rrd_infoval_t);
+ void rrd_info_print(
+ rrd_info_t * data);
+ void rrd_info_free(
+ rrd_info_t *);
+ int rrd_update(
+ int,
+ char **);
+ rrd_info_t *rrd_update_v(
+ int,
+ char **);
+ int rrd_graph(
+ int,
+ char **,
+ char ***,
+ int *,
+ int *,
+ FILE *,
+ double *,
+ double *);
+ rrd_info_t *rrd_graph_v(
+ int,
+ char **);
+
+ int rrd_fetch(
+ int,
+ char **,
+ time_t *,
+ time_t *,
+ unsigned long *,
+ unsigned long *,
+ char ***,
+ rrd_value_t **);
+ int rrd_restore(
+ int,
+ char **);
+ int rrd_dump(
+ int,
+ char **);
+ int rrd_tune(
+ int,
+ char **);
+ time_t rrd_last(
+ int,
+ char **);
+ int rrd_lastupdate(
+ int argc,
+ char **argv,
+ time_t *last_update,
+ unsigned long *ds_cnt,
+ char ***ds_namv,
+ char ***last_ds);
+ time_t rrd_first(
+ int,
+ char **);
+ int rrd_resize(
+ int,
+ char **);
+ char *rrd_strversion(
+ void);
+ double rrd_version(
+ void);
+ int rrd_xport(
+ int,
+ char **,
+ int *,
+ time_t *,
+ time_t *,
+ unsigned long *,
+ unsigned long *,
+ char ***,
+ rrd_value_t **);
+
+ void rrd_freemem(
+ void *mem);
+
+/* thread-safe (hopefully) */
+ int rrd_create_r(
+ const char *filename,
+ unsigned long pdp_step,
+ time_t last_up,
+ int argc,
+ const char **argv);
+/* NOTE: rrd_update_r are only thread-safe if no at-style time
+ specifications get used!!! */
+
+ int rrd_update_r(
+ const char *filename,
+ const char *_template,
+ int argc,
+ const char **argv);
+ int rrd_fetch_r(
+ const char *filename,
+ const char *cf,
+ time_t *start,
+ time_t *end,
+ unsigned long *step,
+ unsigned long *ds_cnt,
+ char ***ds_namv,
+ rrd_value_t **data);
+ int rrd_dump_r(
+ const char *filename,
+ char *outname);
+ time_t rrd_last_r(
+ const char *filename);
+ time_t rrd_first_r(
+ const char *filename,
+ int rraindex);
+
+/* Transplanted from rrd_parsetime.h */
+ typedef enum {
+ ABSOLUTE_TIME,
+ RELATIVE_TO_START_TIME,
+ RELATIVE_TO_END_TIME
+ } rrd_timetype_t;
+
+#define TIME_OK NULL
+
+ typedef struct rrd_time_value {
+ rrd_timetype_t type;
+ long offset;
+ struct tm tm;
+ } rrd_time_value_t;
+
+ char *rrd_parsetime(
+ const char *spec,
+ rrd_time_value_t * ptv);
+/* END rrd_parsetime.h */
+
+ typedef struct rrd_context {
+ char lib_errstr[256];
+ char rrd_error[4096];
+ } rrd_context_t;
+
+/* returns the current per-thread rrd_context */
+ rrd_context_t *rrd_get_context(
+ void);
+
+
+ int rrd_proc_start_end(
+ rrd_time_value_t *,
+ rrd_time_value_t *,
+ time_t *,
+ time_t *);
+
+/* HELPER FUNCTIONS */
+ void rrd_set_error(
+ char *,
+ ...);
+ void rrd_clear_error(
+ void);
+ int rrd_test_error(
+ void);
+ char *rrd_get_error(
+ void);
+
+ /* rrd_strerror is thread safe, but still it uses a global buffer
+ (but one per thread), thus subsequent calls within a single
+ thread overwrite the same buffer */
+ const char *rrd_strerror(
+ int err);
+
+/** MULTITHREADED HELPER FUNCTIONS */
+ rrd_context_t *rrd_new_context(
+ void);
+ void rrd_free_context(
+ rrd_context_t * buf);
+
+/* void rrd_set_error_r (rrd_context_t *, char *, ...); */
+/* void rrd_clear_error_r(rrd_context_t *); */
+/* int rrd_test_error_r (rrd_context_t *); */
+/* char *rrd_get_error_r (rrd_context_t *); */
+
+/*
+ * The following functions are _internal_ functions needed to read the raw RRD
+ * files. Since they are _internal_ they may change with the file format and
+ * will be replaced with a more general interface in RRDTool 1.4. Don't use
+ * these functions unless you have good reasons to do so. If you do use these
+ * functions you will have to adapt your code for RRDTool 1.4!
+ *
+ * To enable the deprecated functions define `RRD_EXPORT_DEPRECATED' before
+ * including <rrd_test.h>. You have been warned! If you come back to the
+ * RRDTool mailing list and whine about your broken application, you will get
+ * hit with something smelly!
+ */
+#if defined(_RRD_TOOL_H) || defined(RRD_EXPORT_DEPRECATED)
+
+# if defined(_RRD_TOOL_H)
+# include "rrd_format.h"
+# else
+# include <rrd_format.h>
+# endif
+
+#if defined(__GNUC__) && defined (RRD_EXPORT_DEPRECATED)
+# define RRD_DEPRECATED __attribute__((deprecated))
+#else
+# define RRD_DEPRECATED /**/
+#endif
+ void rrd_free(
+ rrd_t *rrd)
+ RRD_DEPRECATED;
+ void rrd_init(
+ rrd_t *rrd)
+ RRD_DEPRECATED;
+
+ rrd_file_t *rrd_open(
+ const char *const file_name,
+ rrd_t *rrd,
+ unsigned rdwr)
+ RRD_DEPRECATED;
+
+ void rrd_dontneed(
+ rrd_file_t *rrd_file,
+ rrd_t *rrd)
+ RRD_DEPRECATED;
+ int rrd_close(
+ rrd_file_t *rrd_file)
+ RRD_DEPRECATED;
+ ssize_t rrd_read(
+ rrd_file_t *rrd_file,
+ void *buf,
+ size_t count)
+ RRD_DEPRECATED;
+ ssize_t rrd_write(
+ rrd_file_t *rrd_file,
+ const void *buf,
+ size_t count)
+ RRD_DEPRECATED;
+ void rrd_flush(
+ rrd_file_t *rrd_file)
+ RRD_DEPRECATED;
+ off_t rrd_seek(
+ rrd_file_t *rrd_file,
+ off_t off,
+ int whence)
+ RRD_DEPRECATED;
+ off_t rrd_tell(
+ rrd_file_t *rrd_file)
+ RRD_DEPRECATED;
+ int rrd_lock(
+ rrd_file_t *file)
+ RRD_DEPRECATED;
+#endif /* defined(_RRD_TOOL_H) || defined(RRD_EXPORT_DEPRECATED) */
+
+#endif /* _RRDLIB_H */
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/program/src/rrd_cgi.c b/program/src/rrd_cgi.c
--- /dev/null
+++ b/program/src/rrd_cgi.c
@@ -0,0 +1,1625 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ *****************************************************************************
+ * rrd_cgi.c RRD Web Page Generator
+ *****************************************************************************/
+
+#include "rrd_tool.h"
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+
+#define MEMBLK 1024
+/*#define DEBUG_PARSER
+#define DEBUG_VARS*/
+
+typedef struct var_s {
+ char *name, *value;
+} s_var;
+
+typedef struct cgi_s {
+ s_var **vars;
+} s_cgi;
+
+/* in arg[0] find tags beginning with arg[1] call arg[2] on them
+ and replace by result of arg[2] call */
+int parse(
+ char **,
+ long,
+ char *,
+ char * (*)(long,
+ const char **));
+
+/**************************************************/
+/* tag replacers ... they are called from parse */
+/* through function pointers */
+/**************************************************/
+
+/* return cgi var named arg[0] */
+char *cgiget(
+ long,
+ const char **);
+
+/* return a quoted cgi var named arg[0] */
+char *cgigetq(
+ long,
+ const char **);
+
+/* return a quoted and sanitized cgi variable */
+char *cgigetqp(
+ long,
+ const char **);
+
+/* call rrd_graph and insert appropriate image tag */
+char *drawgraph(
+ long,
+ const char **);
+
+/* return PRINT functions from last rrd_graph call */
+char *drawprint(
+ long,
+ const char **);
+
+/* pretty-print the <last></last> value for some.rrd via strftime() */
+char *printtimelast(
+ long,
+ const char **);
+
+/* pretty-print current time */
+char *printtimenow(
+ long,
+ const char **);
+
+/* set an environment variable */
+char *rrdsetenv(
+ long,
+ const char **);
+
+/* get an environment variable */
+char *rrdgetenv(
+ long,
+ const char **);
+
+/* include the named file at this point */
+char *includefile(
+ long,
+ const char **);
+
+/* for how long is the output of the cgi valid ? */
+char *rrdgoodfor(
+ long,
+ const char **);
+
+/* return rrdcgi version string */
+char *rrdgetinternal(
+ long,
+ const char **);
+
+char *rrdstrip(
+ char *buf);
+char *scanargs(
+ char *line,
+ int *argc,
+ char ***args);
+
+/* format at-time specified times using strftime */
+char *printstrftime(
+ long,
+ const char **);
+
+/** HTTP protocol needs special format, and GMT time **/
+char *http_time(
+ time_t *);
+
+/* return a pointer to newly allocated copy of this string */
+char *stralloc(
+ const char *);
+
+/* global variable for rrdcgi */
+s_cgi *rrdcgiArg;
+
+/* rrdcgiHeader
+ *
+ * Prints a valid CGI Header (Content-type...) etc.
+ */
+void rrdcgiHeader(
+ void);
+
+/* rrdcgiDecodeString
+ * decode html escapes
+ */
+
+char *rrdcgiDecodeString(
+ char *text);
+
+/* rrdcgiDebug
+ *
+ * Set/unsets debugging
+ */
+void rrdcgiDebug(
+ int level,
+ int where);
+
+/* rrdcgiInit
+ *
+ * Reads in variables set via POST or stdin.
+ */
+s_cgi *rrdcgiInit(
+ void);
+
+/* rrdcgiGetValue
+ *
+ * Returns the value of the specified variable or NULL if it's empty
+ * or doesn't exist.
+ */
+char *rrdcgiGetValue(
+ s_cgi * parms,
+ const char *name);
+
+/* rrdcgiFreeList
+ *
+ * Frees a list as returned by rrdcgiGetVariables()
+ */
+void rrdcgiFreeList(
+ char **list);
+
+/* rrdcgiFree
+ *
+ * Frees the internal data structures
+ */
+void rrdcgiFree(
+ s_cgi * parms);
+
+/* rrdcgiReadVariables()
+ *
+ * Read from stdin if no string is provided via CGI. Variables that
+ * doesn't have a value associated with it doesn't get stored.
+ */
+s_var **rrdcgiReadVariables(
+ void);
+
+
+int rrdcgiDebugLevel = 0;
+int rrdcgiDebugStderr = 1;
+char *rrdcgiHeaderString = NULL;
+char *rrdcgiType = NULL;
+
+/* rrd interface to the variable functions {put,get}var() */
+char *rrdgetvar(
+ long argc,
+ const char **args);
+char *rrdsetvar(
+ long argc,
+ const char **args);
+char *rrdsetvarconst(
+ long argc,
+ const char **args);
+
+
+/* variable store: put/get key-value pairs */
+static int initvar(
+ );
+static void donevar(
+ );
+static const char *getvar(
+ const char *varname);
+static const char *putvar(
+ const char *name,
+ const char *value,
+ int is_const);
+
+/* key value pair that makes up an entry in the variable store */
+typedef struct {
+ int is_const; /* const variable or not */
+ const char *name; /* variable name */
+ const char *value; /* variable value */
+} vardata;
+
+/* the variable heap:
+ start with a heapsize of 10 variables */
+#define INIT_VARSTORE_SIZE 10
+static vardata *varheap = NULL;
+static size_t varheap_size = 0;
+
+/* allocate and initialize variable heap */
+static int initvar(
+ void)
+{
+ varheap = (vardata *) malloc(sizeof(vardata) * INIT_VARSTORE_SIZE);
+ if (varheap == NULL) {
+ fprintf(stderr, "ERROR: unable to initialize variable store\n");
+ return -1;
+ }
+ memset(varheap, 0, sizeof(vardata) * INIT_VARSTORE_SIZE);
+ varheap_size = INIT_VARSTORE_SIZE;
+ return 0;
+}
+
+/* cleanup: free allocated memory */
+static void donevar(
+ void)
+{
+ int i;
+
+ if (varheap) {
+ for (i = 0; i < (int) varheap_size; i++) {
+ if (varheap[i].name) {
+ free((char *) varheap[i].name);
+ }
+ if (varheap[i].value) {
+ free((char *) varheap[i].value);
+ }
+ }
+ free(varheap);
+ }
+}
+
+/* Get a variable from the variable store.
+ Return NULL in case the requested variable was not found. */
+static const char *getvar(
+ const char *name)
+{
+ int i;
+
+ for (i = 0; i < (int) varheap_size && varheap[i].name; i++) {
+ if (0 == strcmp(name, varheap[i].name)) {
+#ifdef DEBUG_VARS
+ printf("<!-- getvar(%s) -> %s -->\n", name, varheap[i].value);
+#endif
+ return varheap[i].value;
+ }
+ }
+#ifdef DEBUG_VARS
+ printf("<!-- getvar(%s) -> Not found-->\n", name);
+#endif
+ return NULL;
+}
+
+/* Put a variable into the variable store. If a variable by that
+ name exists, it's value is overwritten with the new value unless it was
+ marked as 'const' (initialized by RRD::SETCONSTVAR).
+ Returns a copy the newly allocated value on success, NULL on error. */
+static const char *putvar(
+ const char *name,
+ const char *value,
+ int is_const)
+{
+ int i;
+
+ for (i = 0; i < (int) varheap_size && varheap[i].name; i++) {
+ if (0 == strcmp(name, varheap[i].name)) {
+ /* overwrite existing entry */
+ if (varheap[i].is_const) {
+#ifdef DEBUG_VARS
+ printf("<!-- setver(%s, %s): not assigning: "
+ "const variable -->\n", name, value);
+#endif
+ return varheap[i].value;
+ }
+#ifdef DEBUG_VARS
+ printf("<!-- setvar(%s, %s): overwriting old value (%s) -->\n",
+ name, value, varheap[i].value);
+#endif
+ /* make it possible to promote a variable to readonly */
+ varheap[i].is_const = is_const;
+ free((char *) varheap[i].value);
+ varheap[i].value = stralloc(value);
+ return varheap[i].value;
+ }
+ }
+
+ /* no existing variable found by that name, add it */
+ if (i == (int) varheap_size) {
+ /* ran out of heap: resize heap to double size */
+ size_t new_size = varheap_size * 2;
+
+ varheap = (vardata *) (realloc(varheap, sizeof(vardata) * new_size));
+ if (!varheap) {
+ fprintf(stderr, "ERROR: Unable to realloc variable heap\n");
+ return NULL;
+ }
+ /* initialize newly allocated memory */ ;
+ memset(&varheap[varheap_size], 0, sizeof(vardata) * varheap_size);
+ varheap_size = new_size;
+ }
+ varheap[i].is_const = is_const;
+ varheap[i].name = stralloc(name);
+ varheap[i].value = stralloc(value);
+
+#ifdef DEBUG_VARS
+ printf("<!-- setvar(%s, %s): adding new variable -->\n", name, value);
+#endif
+ return varheap[i].value;
+}
+
+/* expand those RRD:* directives that can be used recursivly */
+static char *rrd_expand_vars(
+ char *buffer)
+{
+ int i;
+
+#ifdef DEBUG_PARSER
+ printf("expanding variables in '%s'\n", buffer);
+#endif
+
+ for (i = 0; buffer[i]; i++) {
+ if (buffer[i] != '<')
+ continue;
+ parse(&buffer, i, "<RRD::CV", cgiget);
+ parse(&buffer, i, "<RRD::CV::QUOTE", cgigetq);
+ parse(&buffer, i, "<RRD::CV::PATH", cgigetqp);
+ parse(&buffer, i, "<RRD::GETENV", rrdgetenv);
+ parse(&buffer, i, "<RRD::GETVAR", rrdgetvar);
+ parse(&buffer, i, "<RRD::TIME::LAST", printtimelast);
+ parse(&buffer, i, "<RRD::TIME::NOW", printtimenow);
+ parse(&buffer, i, "<RRD::TIME::STRFTIME", printstrftime);
+ parse(&buffer, i, "<RRD::INTERNAL", rrdgetinternal);
+ }
+ return buffer;
+}
+
+static long goodfor = 0;
+static char **calcpr = NULL;
+static void calfree(
+ void)
+{
+ if (calcpr) {
+ long i;
+
+ for (i = 0; calcpr[i]; i++) {
+ if (calcpr[i]) {
+ free(calcpr[i]);
+ }
+ }
+ if (calcpr) {
+ free(calcpr);
+ }
+ }
+}
+
+/* create freeable version of the string */
+char *stralloc(
+ const char *str)
+{
+ char *nstr;
+
+ if (!str) {
+ return NULL;
+ }
+ nstr = malloc((strlen(str) + 1));
+ strcpy(nstr, str);
+ return (nstr);
+}
+
+static int readfile(
+ const char *file_name,
+ char **buffer,
+ int skipfirst)
+{
+ long writecnt = 0, totalcnt = MEMBLK;
+ long offset = 0;
+ FILE *input = NULL;
+ char c;
+
+ if ((strcmp("-", file_name) == 0)) {
+ input = stdin;
+ } else {
+ if ((input = fopen(file_name, "rb")) == NULL) {
+ rrd_set_error("opening '%s': %s", file_name, rrd_strerror(errno));
+ return (-1);
+ }
+ }
+ if (skipfirst) {
+ do {
+ c = getc(input);
+ offset++;
+ } while (c != '\n' && !feof(input));
+ }
+ if (strcmp("-", file_name)) {
+ fseek(input, 0, SEEK_END);
+ /* have extra space for detecting EOF without realloc */
+ totalcnt = (ftell(input) + 1) / sizeof(char) - offset;
+ if (totalcnt < MEMBLK)
+ totalcnt = MEMBLK; /* sanitize */
+ fseek(input, offset * sizeof(char), SEEK_SET);
+ }
+ if (((*buffer) = (char *) malloc((totalcnt + 4) * sizeof(char))) == NULL) {
+ perror("Allocate Buffer:");
+ exit(1);
+ };
+ do {
+ writecnt +=
+ fread((*buffer) + writecnt, 1,
+ (totalcnt - writecnt) * sizeof(char), input);
+ if (writecnt >= totalcnt) {
+ totalcnt += MEMBLK;
+ if (((*buffer) =
+ rrd_realloc((*buffer),
+ (totalcnt + 4) * sizeof(char))) == NULL) {
+ perror("Realloc Buffer:");
+ exit(1);
+ };
+ }
+ } while (!feof(input));
+ (*buffer)[writecnt] = '\0';
+ if (strcmp("-", file_name) != 0) {
+ fclose(input);
+ };
+ return writecnt;
+}
+
+int main(
+ int argc,
+ char *argv[])
+{
+ long length;
+ char *buffer;
+ char *server_url = NULL;
+ long i;
+ long filter = 0;
+ struct option long_options[] = {
+ {"filter", no_argument, 0, 'f'},
+ {0, 0, 0, 0}
+ };
+
+#ifdef MUST_DISABLE_SIGFPE
+ signal(SIGFPE, SIG_IGN);
+#endif
+#ifdef MUST_DISABLE_FPMASK
+ fpsetmask(0);
+#endif
+ optind = 0;
+ opterr = 0; /* initialize getopt */
+
+ /* what do we get for cmdline arguments?
+ for (i=0;i<argc;i++)
+ printf("%d-'%s'\n",i,argv[i]); */
+ while (1) {
+ int option_index = 0;
+ int opt;
+
+ opt = getopt_long(argc, argv, "f", long_options, &option_index);
+ if (opt == EOF) {
+ break;
+ }
+
+ switch (opt) {
+ case 'f':
+ filter = 1;
+ break;
+ case '?':
+ printf("unknown commandline option '%s'\n", argv[optind - 1]);
+ return -1;
+ }
+ }
+
+ if (!filter) {
+ rrdcgiDebug(0, 0);
+ rrdcgiArg = rrdcgiInit();
+ server_url = getenv("SERVER_URL");
+ }
+
+ /* make sure we have one extra argument,
+ if there are others, we do not care Apache gives several */
+
+ /* if ( (optind != argc-2
+ && strstr( getenv("SERVER_SOFTWARE"),"Apache/2") != NULL)
+ && optind != argc-1) { */
+
+ if (optind >= argc) {
+ fprintf(stderr, "ERROR: expected a filename\n");
+ exit(1);
+ } else {
+ length = readfile(argv[optind], &buffer, 1);
+ }
+
+ if (rrd_test_error()) {
+ fprintf(stderr, "ERROR: %s\n", rrd_get_error());
+ exit(1);
+ }
+
+ /* initialize variable heap */
+ initvar();
+
+#ifdef DEBUG_PARSER
+ /* some fake header for testing */
+ printf("Content-Type: text/html\nContent-Length: 10000000\n\n\n");
+#endif
+
+
+ /* expand rrd directives in buffer recursivly */
+ for (i = 0; buffer[i]; i++) {
+ if (buffer[i] != '<')
+ continue;
+ if (!filter) {
+ parse(&buffer, i, "<RRD::CV", cgiget);
+ parse(&buffer, i, "<RRD::CV::PATH", cgigetqp);
+ parse(&buffer, i, "<RRD::CV::QUOTE", cgigetq);
+ parse(&buffer, i, "<RRD::GETENV", rrdgetenv);
+ }
+ parse(&buffer, i, "<RRD::GETVAR", rrdgetvar);
+ parse(&buffer, i, "<RRD::GOODFOR", rrdgoodfor);
+ parse(&buffer, i, "<RRD::GRAPH", drawgraph);
+ parse(&buffer, i, "<RRD::INCLUDE", includefile);
+ parse(&buffer, i, "<RRD::PRINT", drawprint);
+ parse(&buffer, i, "<RRD::SETCONSTVAR", rrdsetvarconst);
+ parse(&buffer, i, "<RRD::SETENV", rrdsetenv);
+ parse(&buffer, i, "<RRD::SETVAR", rrdsetvar);
+ parse(&buffer, i, "<RRD::TIME::LAST", printtimelast);
+ parse(&buffer, i, "<RRD::TIME::NOW", printtimenow);
+ parse(&buffer, i, "<RRD::TIME::STRFTIME", printstrftime);
+ parse(&buffer, i, "<RRD::INTERNAL", rrdgetinternal);
+ }
+
+ if (!filter) {
+ printf("Content-Type: text/html\n"
+ "Content-Length: %zd\n", strlen(buffer));
+
+ if (labs(goodfor) > 0) {
+ time_t now;
+
+ now = time(NULL);
+ printf("Last-Modified: %s\n", http_time(&now));
+ now += labs(goodfor);
+ printf("Expires: %s\n", http_time(&now));
+ if (goodfor < 0) {
+ printf("Refresh: %ld\n", labs(goodfor));
+ }
+ }
+ printf("\n");
+ }
+
+ /* output result */
+ printf("%s", buffer);
+
+ /* cleanup */
+ calfree();
+ if (buffer) {
+ free(buffer);
+ }
+ donevar();
+ exit(0);
+}
+
+/* remove occurrences of .. this is a general measure to make
+ paths which came in via cgi do not go UP ... */
+
+char *rrdsetenv(
+ long argc,
+ const char **args)
+{
+ if (argc >= 2) {
+ char *xyz = malloc((strlen(args[0]) + strlen(args[1]) + 2));
+
+ if (xyz == NULL) {
+ return stralloc("[ERROR: allocating setenv buffer]");
+ };
+ sprintf(xyz, "%s=%s", args[0], args[1]);
+ if (putenv(xyz) == -1) {
+ free(xyz);
+ return stralloc("[ERROR: failed to do putenv]");
+ };
+ return stralloc("");
+ }
+ return stralloc("[ERROR: setenv failed because not enough "
+ "arguments were defined]");
+}
+
+/* rrd interface to the variable function putvar() */
+char *rrdsetvar(
+ long argc,
+ const char **args)
+{
+ if (argc >= 2) {
+ const char *result = putvar(args[0], args[1], 0 /* not const */ );
+
+ if (result) {
+ /* setvar does not return the value set */
+ return stralloc("");
+ }
+ return stralloc("[ERROR: putvar failed]");
+ }
+ return stralloc("[ERROR: putvar failed because not enough arguments "
+ "were defined]");
+}
+
+/* rrd interface to the variable function putvar() */
+char *rrdsetvarconst(
+ long argc,
+ const char **args)
+{
+ if (argc >= 2) {
+ const char *result = putvar(args[0], args[1], 1 /* const */ );
+
+ if (result) {
+ /* setvar does not return the value set */
+ return stralloc("");
+ }
+ return stralloc("[ERROR: putvar failed]");
+ }
+ return stralloc("[ERROR: putvar failed because not enough arguments "
+ "were defined]");
+}
+
+char *rrdgetenv(
+ long argc,
+ const char **args)
+{
+ char buf[128];
+ const char *envvar;
+
+ if (argc != 1) {
+ return stralloc("[ERROR: getenv failed because it did not "
+ "get 1 argument only]");
+ };
+ envvar = getenv(args[0]);
+ if (envvar) {
+ return stralloc(envvar);
+ } else {
+ snprintf(buf, sizeof(buf), "[ERROR:_getenv_'%s'_failed", args[0]);
+ return stralloc(buf);
+ }
+}
+
+char *rrdgetvar(
+ long argc,
+ const char **args)
+{
+ char buf[128];
+ const char *value;
+
+ if (argc != 1) {
+ return stralloc("[ERROR: getvar failed because it did not "
+ "get 1 argument only]");
+ };
+ value = getvar(args[0]);
+ if (value) {
+ return stralloc(value);
+ } else {
+ snprintf(buf, sizeof(buf), "[ERROR:_getvar_'%s'_failed", args[0]);
+ return stralloc(buf);
+ }
+}
+
+char *rrdgoodfor(
+ long argc,
+ const char **args)
+{
+ if (argc == 1) {
+ goodfor = atol(args[0]);
+ } else {
+ return stralloc("[ERROR: goodfor expected 1 argument]");
+ }
+
+ if (goodfor == 0) {
+ return stralloc("[ERROR: goodfor value must not be 0]");
+ }
+
+ return stralloc("");
+}
+
+char *rrdgetinternal(
+ long argc,
+ const char **args)
+{
+ if (argc == 1) {
+ if (strcasecmp(args[0], "VERSION") == 0) {
+ return stralloc(PACKAGE_VERSION);
+ } else if (strcasecmp(args[0], "COMPILETIME") == 0) {
+ return stralloc(__DATE__ " " __TIME__);
+ } else {
+ return stralloc("[ERROR: internal unknown argument]");
+ }
+ } else {
+ return stralloc("[ERROR: internal expected 1 argument]");
+ }
+}
+
+/* Format start or end times using strftime. We always need both the
+ * start and end times, because, either might be relative to the other.
+ * */
+#define MAX_STRFTIME_SIZE 256
+char *printstrftime(
+ long argc,
+ const char **args)
+{
+ rrd_time_value_t start_tv, end_tv;
+ char *parsetime_error = NULL;
+ char formatted[MAX_STRFTIME_SIZE];
+ struct tm *the_tm;
+ time_t start_tmp, end_tmp;
+
+ /* Make sure that we were given the right number of args */
+ if (argc != 4) {
+ rrd_set_error("wrong number of args %d", argc);
+ return stralloc("");
+ }
+
+ /* Init start and end time */
+ rrd_parsetime("end-24h", &start_tv);
+ rrd_parsetime("now", &end_tv);
+
+ /* Parse the start and end times we were given */
+ if ((parsetime_error = rrd_parsetime(args[1], &start_tv))) {
+ rrd_set_error("start time: %s", parsetime_error);
+ return stralloc("");
+ }
+ if ((parsetime_error = rrd_parsetime(args[2], &end_tv))) {
+ rrd_set_error("end time: %s", parsetime_error);
+ return stralloc("");
+ }
+ if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
+ return stralloc("");
+ }
+
+ /* Do we do the start or end */
+ if (strcasecmp(args[0], "START") == 0) {
+ the_tm = localtime(&start_tmp);
+ } else if (strcasecmp(args[0], "END") == 0) {
+ the_tm = localtime(&end_tmp);
+ } else {
+ rrd_set_error("start/end not found in '%s'", args[0]);
+ return stralloc("");
+ }
+
+ /* now format it */
+ if (strftime(formatted, MAX_STRFTIME_SIZE, args[3], the_tm)) {
+ return (stralloc(formatted));
+ } else {
+ rrd_set_error("strftime failed");
+ return stralloc("");
+ }
+}
+
+char *includefile(
+ long argc,
+ const char **args)
+{
+ char *buffer;
+
+ if (argc >= 1) {
+ const char *filename = args[0];
+
+ readfile(filename, &buffer, 0);
+ if (rrd_test_error()) {
+ char *err = malloc((strlen(rrd_get_error()) + DS_NAM_SIZE));
+
+ sprintf(err, "[ERROR: %s]", rrd_get_error());
+ rrd_clear_error();
+ return err;
+ } else {
+ return buffer;
+ }
+ } else {
+ return stralloc("[ERROR: No Inclue file defined]");
+ }
+}
+
+/* make a copy of buf and replace open/close brackets with '_' */
+char *rrdstrip(
+ char *buf)
+{
+ char *p;
+
+ if (buf == NULL) {
+ return NULL;
+ }
+ /* make a copy of the buffer */
+ buf = stralloc(buf);
+ if (buf == NULL) {
+ return NULL;
+ }
+
+ p = buf;
+ while (*p) {
+ if (*p == '<' || *p == '>') {
+ *p = '_';
+ }
+ p++;
+ }
+ return buf;
+}
+
+char *cgigetq(
+ long argc,
+ const char **args)
+{
+ if (argc >= 1) {
+ char *buf = rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
+ char *buf2;
+ char *c, *d;
+ int qc = 0;
+
+ if (buf == NULL)
+ return NULL;
+
+ for (c = buf; *c != '\0'; c++)
+ if (*c == '"')
+ qc++;
+ if ((buf2 = malloc((strlen(buf) + 4 * qc + 4))) == NULL) {
+ perror("Malloc Buffer");
+ exit(1);
+ };
+ c = buf;
+ d = buf2;
+ *(d++) = '"';
+ while (*c != '\0') {
+ if (*c == '"') {
+ *(d++) = '"';
+ *(d++) = '\'';
+ *(d++) = '"';
+ *(d++) = '\'';
+ }
+ *(d++) = *(c++);
+ }
+ *(d++) = '"';
+ *(d) = '\0';
+ free(buf);
+ return buf2;
+ }
+
+ return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
+}
+
+/* remove occurrences of .. this is a general measure to make
+ paths which came in via cgi do not go UP ... */
+
+char *cgigetqp(
+ long argc,
+ const char **args)
+{
+ char *buf;
+ char *buf2;
+ char *p;
+ char *d;
+
+ if (argc < 1) {
+ return stralloc("[ERROR: not enough arguments for RRD::CV::PATH]");
+ }
+
+ buf = rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
+ if (!buf) {
+ return NULL;
+ }
+
+ buf2 = malloc(strlen(buf) + 1);
+ if (!buf2) {
+ perror("cgigetqp(): Malloc Path Buffer");
+ exit(1);
+ };
+
+ p = buf;
+ d = buf2;
+
+ while (*p) {
+ /* prevent mallicious paths from entering the system */
+ if (p[0] == '.' && p[1] == '.') {
+ p += 2;
+ *d++ = '_';
+ *d++ = '_';
+ } else {
+ *d++ = *p++;
+ }
+ }
+
+ *d = 0;
+ free(buf);
+
+ /* Make sure the path is relative, e.g. does not start with '/' */
+ p = buf2;
+ while ('/' == *p) {
+ *p++ = '_';
+ }
+
+ return buf2;
+}
+
+
+char *cgiget(
+ long argc,
+ const char **args)
+{
+ if (argc >= 1)
+ return rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
+ else
+ return stralloc("[ERROR: not enough arguments for RRD::CV]");
+}
+
+
+
+char *drawgraph(
+ long argc,
+ const char **args)
+{
+ int i, xsize, ysize;
+ double ymin, ymax;
+
+ for (i = 0; i < argc; i++)
+ if (strcmp(args[i], "--imginfo") == 0 || strcmp(args[i], "-g") == 0)
+ break;
+ if (i == argc) {
+ args[argc++] = "--imginfo";
+ args[argc++] = "<IMG SRC=\"./%s\" WIDTH=\"%lu\" HEIGHT=\"%lu\">";
+ }
+ calfree();
+ if (rrd_graph
+ (argc + 1, (char **) args - 1, &calcpr, &xsize, &ysize, NULL, &ymin,
+ &ymax) != -1) {
+ return stralloc(calcpr[0]);
+ } else {
+ if (rrd_test_error()) {
+ char *err =
+ malloc((strlen(rrd_get_error()) +
+ DS_NAM_SIZE) * sizeof(char));
+ sprintf(err, "[ERROR: %s]", rrd_get_error());
+ rrd_clear_error();
+ calfree();
+ return err;
+ }
+ }
+ return NULL;
+}
+
+char *drawprint(
+ long argc,
+ const char **args)
+{
+ if (argc == 1 && calcpr) {
+ long i = 0;
+
+ while (calcpr[i] != NULL)
+ i++; /*determine number lines in calcpr */
+ if (atol(args[0]) < i - 1)
+ return stralloc(calcpr[atol(args[0]) + 1]);
+ }
+ return stralloc("[ERROR: RRD::PRINT argument error]");
+}
+
+char *printtimelast(
+ long argc,
+ const char **args)
+{
+ time_t last;
+ struct tm tm_last;
+ char *buf;
+
+ if (argc == 2) {
+ buf = malloc(255);
+ if (buf == NULL) {
+ return stralloc("[ERROR: allocating strftime buffer]");
+ };
+ last = rrd_last(argc + 1, (char **) args - 1);
+ if (rrd_test_error()) {
+ char *err =
+ malloc((strlen(rrd_get_error()) +
+ DS_NAM_SIZE) * sizeof(char));
+ sprintf(err, "[ERROR: %s]", rrd_get_error());
+ rrd_clear_error();
+ return err;
+ }
+ tm_last = *localtime(&last);
+ strftime(buf, 254, args[1], &tm_last);
+ return buf;
+ }
+ if (argc < 2) {
+ return stralloc("[ERROR: too few arguments for RRD::TIME::LAST]");
+ }
+ return stralloc("[ERROR: not enough arguments for RRD::TIME::LAST]");
+}
+
+char *printtimenow(
+ long argc,
+ const char **args)
+{
+ time_t now = time(NULL);
+ struct tm tm_now;
+ char *buf;
+
+ if (argc == 1) {
+ buf = malloc(255);
+ if (buf == NULL) {
+ return stralloc("[ERROR: allocating strftime buffer]");
+ };
+ tm_now = *localtime(&now);
+ strftime(buf, 254, args[0], &tm_now);
+ return buf;
+ }
+ if (argc < 1) {
+ return stralloc("[ERROR: too few arguments for RRD::TIME::NOW]");
+ }
+ return stralloc("[ERROR: not enough arguments for RRD::TIME::NOW]");
+}
+
+/* Scan buffer until an unescaped '>' arives.
+ * Update argument array with arguments found.
+ * Return end cursor where parsing stopped, or NULL in case of failure.
+ *
+ * FIXME:
+ * To allow nested constructs, we call rrd_expand_vars() for arguments
+ * that contain RRD::x directives. These introduce a small memory leak
+ * since we have to stralloc the arguments the way parse() works.
+ */
+char *scanargs(
+ char *line,
+ int *argument_count,
+ char ***arguments)
+{
+ char *getP; /* read cursor */
+ char *putP; /* write cursor */
+ char Quote; /* type of quote if in quoted string, 0 otherwise */
+ int tagcount; /* open tag count */
+ int in_arg; /* if we currently are parsing an argument or not */
+ int argsz; /* argument array size */
+ int curarg_contains_rrd_directives;
+
+ /* local array of arguments while parsing */
+ int argc = 0;
+ char **argv;
+
+#ifdef DEBUG_PARSER
+ printf("<-- scanargs(%s) -->\n", line);
+#endif
+
+ *arguments = NULL;
+ *argument_count = 0;
+
+ /* create initial argument array of char pointers */
+ argsz = 32;
+ argv = (char **) malloc(argsz * sizeof(char *));
+ if (!argv) {
+ return NULL;
+ }
+
+ /* skip leading blanks */
+ while (isspace((int) *line)) {
+ line++;
+ }
+
+ getP = line;
+ putP = line;
+
+ Quote = 0;
+ in_arg = 0;
+ tagcount = 0;
+
+ curarg_contains_rrd_directives = 0;
+
+ /* start parsing 'line' for arguments */
+ while (*getP) {
+ unsigned char c = *getP++;
+
+ if (c == '>' && !Quote && !tagcount) {
+ /* this is our closing tag, quit scanning */
+ break;
+ }
+
+ /* remove all special chars */
+ if (c < ' ') {
+ c = ' ';
+ }
+
+ switch (c) {
+ case ' ':
+ if (Quote || tagcount) {
+ /* copy quoted/tagged (=RRD expanded) string */
+ *putP++ = c;
+ } else if (in_arg) {
+ /* end argument string */
+ *putP++ = 0;
+ in_arg = 0;
+ if (curarg_contains_rrd_directives) {
+ argv[argc - 1] =
+ rrd_expand_vars(stralloc(argv[argc - 1]));
+ curarg_contains_rrd_directives = 0;
+ }
+ }
+ break;
+
+ case '"': /* Fall through */
+ case '\'':
+ if (Quote != 0) {
+ if (Quote == c) {
+ Quote = 0;
+ } else {
+ /* copy quoted string */
+ *putP++ = c;
+ }
+ } else {
+ if (!in_arg) {
+ /* reference start of argument string in argument array */
+ argv[argc++] = putP;
+ in_arg = 1;
+ }
+ Quote = c;
+ }
+ break;
+
+ default:
+ if (!in_arg) {
+ /* start new argument */
+ argv[argc++] = putP;
+ in_arg = 1;
+ }
+ if (c == '>') {
+ if (tagcount) {
+ tagcount--;
+ }
+ }
+ if (c == '<') {
+ tagcount++;
+ if (0 == strncmp(getP, "RRD::", strlen("RRD::"))) {
+ curarg_contains_rrd_directives = 1;
+ }
+ }
+ *putP++ = c;
+ break;
+ }
+
+ /* check if our argument array is still large enough */
+ if (argc == argsz) {
+ /* resize argument array */
+ argsz *= 2;
+ argv = rrd_realloc(argv, argsz * sizeof(char *));
+ if (*argv == NULL) {
+ return NULL;
+ }
+ }
+ }
+
+ /* terminate last argument found */
+ *putP = '\0';
+ if (curarg_contains_rrd_directives) {
+ argv[argc - 1] = rrd_expand_vars(stralloc(argv[argc - 1]));
+ }
+#ifdef DEBUG_PARSER
+ if (argc > 0) {
+ int n;
+
+ printf("<-- arguments found [%d]\n", argc);
+ for (n = 0; n < argc; n++) {
+ printf("arg %02d: '%s'\n", n, argv[n]);
+ }
+ printf("-->\n");
+ } else {
+ printf("<!-- No arguments found -->\n");
+ }
+#endif
+
+ /* update caller's notion of the argument array and it's size */
+ *arguments = argv;
+ *argument_count = argc;
+
+ if (Quote) {
+ return NULL;
+ }
+
+ /* Return new scanning cursor:
+ pointer to char after closing bracket */
+ return getP;
+}
+
+
+/*
+ * Parse(): scan current portion of buffer for given tag.
+ * If found, parse tag arguments and call 'func' for it.
+ * The result of func is inserted at the current position
+ * in the buffer.
+ */
+int parse(
+ char **buf, /* buffer */
+ long i, /* offset in buffer */
+ char *tag, /* tag to handle */
+ char * (*func) (long,
+ const char **) /* function to call for 'tag' */
+ )
+{
+ /* the name of the vairable ... */
+ char *val;
+ long valln;
+ char **args;
+ char *end;
+ long end_offset;
+ int argc;
+ size_t taglen = strlen(tag);
+
+ /* Current position in buffer should start with 'tag' */
+ if (strncmp((*buf) + i, tag, taglen) != 0) {
+ return 0;
+ }
+ /* .. and match exactly (a whitespace following 'tag') */
+ if (!isspace(*((*buf) + i + taglen))) {
+ return 0;
+ }
+#ifdef DEBUG_PARSER
+ printf("parse(): handling tag '%s'\n", tag);
+#endif
+
+ /* Scan for arguments following the tag;
+ scanargs() puts \0 into *buf ... so after scanargs it is probably
+ not a good time to use strlen on buf */
+ end = scanargs((*buf) + i + taglen, &argc, &args);
+ if (end) {
+ /* got arguments, call function for 'tag' with arguments */
+ val = func(argc, (const char **) args);
+ free(args);
+ } else {
+ /* unable to parse arguments, undo 0-termination by scanargs */
+ for (; argc > 0; argc--) {
+ *((args[argc - 1]) - 1) = ' ';
+ }
+
+ /* next call, try parsing at current offset +1 */
+ end = (*buf) + i + 1;
+
+ val = stralloc("[ERROR: Parsing Problem with the following text\n"
+ " Check original file. This may have been altered "
+ "by parsing.]\n\n");
+ }
+
+ /* remember offset where we have to continue parsing */
+ end_offset = end - (*buf);
+
+ valln = 0;
+ if (val) {
+ valln = strlen(val);
+ }
+
+ /* Optionally resize buffer to hold the replacement value:
+ Calculating the new length of the buffer is simple. add current
+ buffer pos (i) to length of string after replaced tag to length
+ of replacement string and add 1 for the final zero ... */
+ if (end - (*buf) < (i + valln)) {
+ /* make sure we do not shrink the mallocd block */
+ size_t newbufsize = i + strlen(end) + valln + 1;
+
+ *buf = rrd_realloc(*buf, newbufsize);
+
+ if (*buf == NULL) {
+ perror("Realoc buf:");
+ exit(1);
+ };
+ }
+
+ /* Update new end pointer:
+ make sure the 'end' pointer gets moved along with the
+ buf pointer when realloc moves memory ... */
+ end = (*buf) + end_offset;
+
+ /* splice the variable:
+ step 1. Shift pending data to make room for 'val' */
+ memmove((*buf) + i + valln, end, strlen(end) + 1);
+
+ /* step 2. Insert val */
+ if (val) {
+ memmove((*buf) + i, val, valln);
+ free(val);
+ }
+ return (valln > 0 ? valln - 1 : valln);
+}
+
+char *http_time(
+ time_t *now)
+{
+ struct tm *tmptime;
+ static char buf[60];
+
+ tmptime = gmtime(now);
+ strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", tmptime);
+ return (buf);
+}
+
+void rrdcgiHeader(
+ void)
+{
+ if (rrdcgiType)
+ printf("Content-type: %s\n", rrdcgiType);
+ else
+ printf("Content-type: text/html\n");
+ if (rrdcgiHeaderString)
+ printf("%s", rrdcgiHeaderString);
+ printf("\n");
+}
+
+void rrdcgiDebug(
+ int level,
+ int where)
+{
+ if (level > 0)
+ rrdcgiDebugLevel = level;
+ else
+ rrdcgiDebugLevel = 0;
+ if (where)
+ rrdcgiDebugStderr = 0;
+ else
+ rrdcgiDebugStderr = 1;
+}
+
+char *rrdcgiDecodeString(
+ char *text)
+{
+ char *cp, *xp;
+
+ for (cp = text, xp = text; *cp; cp++) {
+ if (*cp == '%') {
+ if (strchr("0123456789ABCDEFabcdef", *(cp + 1))
+ && strchr("0123456789ABCDEFabcdef", *(cp + 2))) {
+ if (islower(*(cp + 1)))
+ *(cp + 1) = toupper(*(cp + 1));
+ if (islower(*(cp + 2)))
+ *(cp + 2) = toupper(*(cp + 2));
+ *(xp) =
+ (*(cp + 1) >=
+ 'A' ? *(cp + 1) - 'A' + 10 : *(cp + 1) - '0') * 16 +
+ (*(cp + 2) >=
+ 'A' ? *(cp + 2) - 'A' + 10 : *(cp + 2) - '0');
+ xp++;
+ cp += 2;
+ }
+ } else {
+ *(xp++) = *cp;
+ }
+ }
+ memset(xp, 0, cp - xp);
+ return text;
+}
+
+/* rrdcgiReadVariables()
+ *
+ * Read from stdin if no string is provided via CGI. Variables that
+ * doesn't have a value associated with it doesn't get stored.
+ */
+s_var **rrdcgiReadVariables(
+ void)
+{
+ int length;
+ char *line = NULL;
+ int numargs;
+ char *cp, *ip, *esp, *sptr;
+ s_var **result;
+ int i, k, len;
+ char tmp[101];
+
+ cp = getenv("REQUEST_METHOD");
+ ip = getenv("CONTENT_LENGTH");
+
+ if (cp && !strcmp(cp, "POST")) {
+ if (ip) {
+ length = atoi(ip);
+ if ((line = (char *) malloc(length + 2)) == NULL)
+ return NULL;
+ fgets(line, length + 1, stdin);
+ } else
+ return NULL;
+ } else if (cp && !strcmp(cp, "GET")) {
+ esp = getenv("QUERY_STRING");
+ if (esp && strlen(esp)) {
+ if ((line = (char *) malloc(strlen(esp) + 2)) == NULL)
+ return NULL;
+ sprintf(line, "%s", esp);
+ } else
+ return NULL;
+ } else {
+ length = 0;
+ printf("(offline mode: enter name=value pairs on standard input)\n");
+ memset(tmp, 0, sizeof(tmp));
+ while ((cp = fgets(tmp, 100, stdin)) != NULL) {
+ if (strlen(tmp)) {
+ if (tmp[strlen(tmp) - 1] == '\n')
+ tmp[strlen(tmp) - 1] = '&';
+ if (length) {
+ length += strlen(tmp);
+ len = (length + 1) * sizeof(char);
+ if ((line = (char *) realloc(line, len)) == NULL)
+ return NULL;
+ strcat(line, tmp);
+ } else {
+ length = strlen(tmp);
+ len = (length + 1) * sizeof(char);
+ if ((line = (char *) malloc(len)) == NULL)
+ return NULL;
+ memset(line, 0, len);
+ strcpy(line, tmp);
+ }
+ }
+ memset(tmp, 0, sizeof(tmp));
+ }
+ if (!line)
+ return NULL;
+ if (line[strlen(line) - 1] == '&')
+ line[strlen(line) - 1] = '\0';
+ }
+
+ /*
+ * From now on all cgi variables are stored in the variable line
+ * and look like foo=bar&foobar=barfoo&foofoo=
+ */
+
+ if (rrdcgiDebugLevel > 0) {
+ if (rrdcgiDebugStderr)
+ fprintf(stderr, "Received cgi input: %s\n", line);
+ else
+ printf
+ ("<b>Received cgi input</b><br>\n<pre>\n--\n%s\n--\n</pre>\n\n",
+ line);
+ }
+
+ for (cp = line; *cp; cp++)
+ if (*cp == '+')
+ *cp = ' ';
+
+ if (strlen(line)) {
+ for (numargs = 1, cp = line; *cp; cp++)
+ if (*cp == '&')
+ numargs++;
+ } else
+ numargs = 0;
+ if (rrdcgiDebugLevel > 0) {
+ if (rrdcgiDebugStderr)
+ fprintf(stderr, "%d cgi variables found.\n", numargs);
+ else
+ printf("%d cgi variables found.<br>\n", numargs);
+ }
+
+ len = (numargs + 1) * sizeof(s_var *);
+ if ((result = (s_var **) malloc(len)) == NULL)
+ return NULL;
+ memset(result, 0, len);
+
+ cp = line;
+ i = 0;
+ while (*cp) {
+ if ((ip = (char *) strchr(cp, '&')) != NULL) {
+ *ip = '\0';
+ } else
+ ip = cp + strlen(cp);
+
+ if ((esp = (char *) strchr(cp, '=')) == NULL) {
+ cp = ++ip;
+ continue;
+ }
+
+ if (!strlen(esp)) {
+ cp = ++ip;
+ continue;
+ }
+
+ if (i < numargs) {
+
+ /* try to find out if there's already such a variable */
+ for (k = 0; k < i && (strncmp(result[k]->name, cp, esp - cp)
+ || !(strlen(result[k]->name) ==
+ (size_t) (esp - cp))); k++);
+
+ if (k == i) { /* No such variable yet */
+ if ((result[i] = (s_var *) malloc(sizeof(s_var))) == NULL)
+ return NULL;
+ if ((result[i]->name =
+ (char *) malloc((esp - cp + 1) * sizeof(char))) == NULL)
+ return NULL;
+ memset(result[i]->name, 0, esp - cp + 1);
+ strncpy(result[i]->name, cp, esp - cp);
+ cp = ++esp;
+ if ((result[i]->value =
+ (char *) malloc((ip - esp + 1) * sizeof(char))) == NULL)
+ return NULL;
+ memset(result[i]->value, 0, ip - esp + 1);
+ strncpy(result[i]->value, cp, ip - esp);
+ result[i]->value = rrdcgiDecodeString(result[i]->value);
+ if (rrdcgiDebugLevel) {
+ if (rrdcgiDebugStderr)
+ fprintf(stderr, "%s: %s\n", result[i]->name,
+ result[i]->value);
+ else
+ printf("<h3>Variable %s</h3>\n<pre>\n%s\n</pre>\n\n",
+ result[i]->name, result[i]->value);
+ }
+ i++;
+ } else { /* There is already such a name, suppose a mutiple field */
+ cp = ++esp;
+ len =
+ (strlen(result[k]->value) + (ip - esp) +
+ 2) * sizeof(char);
+ if ((sptr = (char *) malloc(len)) == NULL)
+ return NULL;
+ memset(sptr, 0, len);
+ sprintf(sptr, "%s\n", result[k]->value);
+ strncat(sptr, cp, ip - esp);
+ free(result[k]->value);
+ result[k]->value = rrdcgiDecodeString(sptr);
+ }
+ }
+ cp = ++ip;
+ }
+ return result;
+}
+
+/* rrdcgiInit()
+ *
+ * Read from stdin if no string is provided via CGI. Variables that
+ * doesn't have a value associated with it doesn't get stored.
+ */
+s_cgi *rrdcgiInit(
+ void)
+{
+ s_cgi *res;
+ s_var **vars;
+
+ vars = rrdcgiReadVariables();
+
+ if (!vars)
+ return NULL;
+
+ if ((res = (s_cgi *) malloc(sizeof(s_cgi))) == NULL)
+ return NULL;
+ res->vars = vars;
+
+ return res;
+}
+
+char *rrdcgiGetValue(
+ s_cgi * parms,
+ const char *name)
+{
+ int i;
+
+ if (!parms || !parms->vars)
+ return NULL;
+ for (i = 0; parms->vars[i]; i++)
+ if (!strcmp(name, parms->vars[i]->name)) {
+ if (rrdcgiDebugLevel > 0) {
+ if (rrdcgiDebugStderr)
+ fprintf(stderr, "%s found as %s\n", name,
+ parms->vars[i]->value);
+ else
+ printf("%s found as %s<br>\n", name,
+ parms->vars[i]->value);
+ }
+ return parms->vars[i]->value;
+ }
+ if (rrdcgiDebugLevel) {
+ if (rrdcgiDebugStderr)
+ fprintf(stderr, "%s not found\n", name);
+ else
+ printf("%s not found<br>\n", name);
+ }
+ return NULL;
+}
+
+void rrdcgiFreeList(
+ char **list)
+{
+ int i;
+
+ for (i = 0; list[i] != NULL; i++)
+ free(list[i]);
+ free(list);
+}
+
+void rrdcgiFree(
+ s_cgi * parms)
+{
+ int i;
+
+ if (!parms)
+ return;
+ if (parms->vars) {
+ for (i = 0; parms->vars[i]; i++) {
+ if (parms->vars[i]->name)
+ free(parms->vars[i]->name);
+ if (parms->vars[i]->value)
+ free(parms->vars[i]->value);
+ free(parms->vars[i]);
+ }
+ free(parms->vars);
+ }
+ free(parms);
+
+ if (rrdcgiHeaderString) {
+ free(rrdcgiHeaderString);
+ rrdcgiHeaderString = NULL;
+ }
+ if (rrdcgiType) {
+ free(rrdcgiType);
+ rrdcgiType = NULL;
+ }
+}
diff --git a/program/src/rrd_create.c b/program/src/rrd_create.c
--- /dev/null
+++ b/program/src/rrd_create.c
@@ -0,0 +1,838 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ *****************************************************************************
+ * rrd_create.c creates new rrds
+ *****************************************************************************/
+
+#include <stdlib.h>
+#include <time.h>
+#include <locale.h>
+
+#include "rrd_tool.h"
+#include "rrd_rpncalc.h"
+#include "rrd_hw.h"
+
+#include "rrd_is_thread_safe.h"
+
+unsigned long FnvHash(
+ const char *str);
+int create_hw_contingent_rras(
+ rrd_t *rrd,
+ unsigned short period,
+ unsigned long hashed_name);
+void parseGENERIC_DS(
+ const char *def,
+ rrd_t *rrd,
+ int ds_idx);
+long int rra_random_row(
+ rra_def_t *);
+
+static void rrd_free2(
+ rrd_t *rrd); /* our onwn copy, immmune to mmap */
+
+int rrd_create(
+ int argc,
+ char **argv)
+{
+ struct option long_options[] = {
+ {"start", required_argument, 0, 'b'},
+ {"step", required_argument, 0, 's'},
+ {0, 0, 0, 0}
+ };
+ int option_index = 0;
+ int opt;
+ time_t last_up = time(NULL) - 10;
+ unsigned long pdp_step = 300;
+ rrd_time_value_t last_up_tv;
+ char *parsetime_error = NULL;
+ long long_tmp;
+ int rc;
+
+ optind = 0;
+ opterr = 0; /* initialize getopt */
+
+ while (1) {
+ opt = getopt_long(argc, argv, "b:s:", long_options, &option_index);
+
+ if (opt == EOF)
+ break;
+
+ switch (opt) {
+ case 'b':
+ if ((parsetime_error = rrd_parsetime(optarg, &last_up_tv))) {
+ rrd_set_error("start time: %s", parsetime_error);
+ return (-1);
+ }
+ if (last_up_tv.type == RELATIVE_TO_END_TIME ||
+ last_up_tv.type == RELATIVE_TO_START_TIME) {
+ rrd_set_error("specifying time relative to the 'start' "
+ "or 'end' makes no sense here");
+ return (-1);
+ }
+
+ last_up = mktime(&last_up_tv.tm) +last_up_tv.offset;
+
+ if (last_up < 3600 * 24 * 365 * 10) {
+ rrd_set_error
+ ("the first entry to the RRD should be after 1980");
+ return (-1);
+ }
+ break;
+
+ case 's':
+ long_tmp = atol(optarg);
+ if (long_tmp < 1) {
+ rrd_set_error("step size should be no less than one second");
+ return (-1);
+ }
+ pdp_step = long_tmp;
+ break;
+
+ case '?':
+ if (optopt != 0)
+ rrd_set_error("unknown option '%c'", optopt);
+ else
+ rrd_set_error("unknown option '%s'", argv[optind - 1]);
+ return (-1);
+ }
+ }
+ if (optind == argc) {
+ rrd_set_error("need name of an rrd file to create");
+ return -1;
+ }
+ rc = rrd_create_r(argv[optind],
+ pdp_step, last_up,
+ argc - optind - 1, (const char **) (argv + optind + 1));
+
+ return rc;
+}
+
+/* #define DEBUG */
+int rrd_create_r(
+ const char *filename,
+ unsigned long pdp_step,
+ time_t last_up,
+ int argc,
+ const char **argv)
+{
+ rrd_t rrd;
+ long i;
+ int offset;
+ char *token;
+ char dummychar1[2], dummychar2[2];
+ unsigned short token_idx, error_flag, period = 0;
+ unsigned long hashed_name;
+
+ /* init rrd clean */
+ rrd_init(&rrd);
+ /* static header */
+ if ((rrd.stat_head = calloc(1, sizeof(stat_head_t))) == NULL) {
+ rrd_set_error("allocating rrd.stat_head");
+ rrd_free2(&rrd);
+ return (-1);
+ }
+
+ /* live header */
+ if ((rrd.live_head = calloc(1, sizeof(live_head_t))) == NULL) {
+ rrd_set_error("allocating rrd.live_head");
+ rrd_free2(&rrd);
+ return (-1);
+ }
+
+ /* set some defaults */
+ strcpy(rrd.stat_head->cookie, RRD_COOKIE);
+ strcpy(rrd.stat_head->version, RRD_VERSION3); /* by default we are still version 3 */
+ rrd.stat_head->float_cookie = FLOAT_COOKIE;
+ rrd.stat_head->ds_cnt = 0; /* this will be adjusted later */
+ rrd.stat_head->rra_cnt = 0; /* ditto */
+ rrd.stat_head->pdp_step = pdp_step; /* 5 minute default */
+
+ /* a default value */
+ rrd.ds_def = NULL;
+ rrd.rra_def = NULL;
+
+ rrd.live_head->last_up = last_up;
+
+ /* optind points to the first non-option command line arg,
+ * in this case, the file name. */
+ /* Compute the FNV hash value (used by SEASONAL and DEVSEASONAL
+ * arrays. */
+ hashed_name = FnvHash(filename);
+ for (i = 0; i < argc; i++) {
+ unsigned int ii;
+
+ if (strncmp(argv[i], "DS:", 3) == 0) {
+ size_t old_size = sizeof(ds_def_t) * (rrd.stat_head->ds_cnt);
+
+ if ((rrd.ds_def = rrd_realloc(rrd.ds_def,
+ old_size + sizeof(ds_def_t))) ==
+ NULL) {
+ rrd_set_error("allocating rrd.ds_def");
+ rrd_free2(&rrd);
+ return (-1);
+ }
+ memset(&rrd.ds_def[rrd.stat_head->ds_cnt], 0, sizeof(ds_def_t));
+ /* extract the name and type */
+ switch (sscanf(&argv[i][3],
+ DS_NAM_FMT "%1[:]" DST_FMT "%1[:]%n",
+ rrd.ds_def[rrd.stat_head->ds_cnt].ds_nam,
+ dummychar1,
+ rrd.ds_def[rrd.stat_head->ds_cnt].dst,
+ dummychar2, &offset)) {
+ case 0:
+ case 1:
+ rrd_set_error("Invalid DS name");
+ break;
+ case 2:
+ case 3:
+ rrd_set_error("Invalid DS type");
+ break;
+ case 4: /* (%n may or may not be counted) */
+ case 5: /* check for duplicate datasource names */
+ for (ii = 0; ii < rrd.stat_head->ds_cnt; ii++)
+ if (strcmp(rrd.ds_def[rrd.stat_head->ds_cnt].ds_nam,
+ rrd.ds_def[ii].ds_nam) == 0)
+ rrd_set_error("Duplicate DS name: %s",
+ rrd.ds_def[ii].ds_nam);
+ /* DS_type may be valid or not. Checked later */
+ break;
+ default:
+ rrd_set_error("invalid DS format");
+ }
+ if (rrd_test_error()) {
+ rrd_free2(&rrd);
+ return -1;
+ }
+
+ /* parse the remainder of the arguments */
+ switch (dst_conv(rrd.ds_def[rrd.stat_head->ds_cnt].dst)) {
+ case DST_COUNTER:
+ case DST_ABSOLUTE:
+ case DST_GAUGE:
+ case DST_DERIVE:
+ parseGENERIC_DS(&argv[i][offset + 3], &rrd,
+ rrd.stat_head->ds_cnt);
+ break;
+ case DST_CDEF:
+ parseCDEF_DS(&argv[i][offset + 3], &rrd,
+ rrd.stat_head->ds_cnt);
+ break;
+ default:
+ rrd_set_error("invalid DS type specified");
+ break;
+ }
+
+ if (rrd_test_error()) {
+ rrd_free2(&rrd);
+ return -1;
+ }
+ rrd.stat_head->ds_cnt++;
+ } else if (strncmp(argv[i], "RRA:", 4) == 0) {
+ char *argvcopy;
+ char *tokptr;
+ size_t old_size = sizeof(rra_def_t) * (rrd.stat_head->rra_cnt);
+ int row_cnt;
+
+ if ((rrd.rra_def = rrd_realloc(rrd.rra_def,
+ old_size + sizeof(rra_def_t))) ==
+ NULL) {
+ rrd_set_error("allocating rrd.rra_def");
+ rrd_free2(&rrd);
+ return (-1);
+ }
+ memset(&rrd.rra_def[rrd.stat_head->rra_cnt], 0,
+ sizeof(rra_def_t));
+
+ argvcopy = strdup(argv[i]);
+ token = strtok_r(&argvcopy[4], ":", &tokptr);
+ token_idx = error_flag = 0;
+ while (token != NULL) {
+ switch (token_idx) {
+ case 0:
+ if (sscanf(token, CF_NAM_FMT,
+ rrd.rra_def[rrd.stat_head->rra_cnt].cf_nam) !=
+ 1)
+ rrd_set_error("Failed to parse CF name");
+ switch (cf_conv
+ (rrd.rra_def[rrd.stat_head->rra_cnt].cf_nam)) {
+ case CF_MHWPREDICT:
+ strcpy(rrd.stat_head->version, RRD_VERSION); /* MHWPREDICT causes Version 4 */
+ case CF_HWPREDICT:
+ /* initialize some parameters */
+ rrd.rra_def[rrd.stat_head->rra_cnt].par[RRA_hw_alpha].
+ u_val = 0.1;
+ rrd.rra_def[rrd.stat_head->rra_cnt].par[RRA_hw_beta].
+ u_val = 1.0 / 288;
+ rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_dependent_rra_idx].u_cnt =
+ rrd.stat_head->rra_cnt;
+ break;
+ case CF_DEVSEASONAL:
+ case CF_SEASONAL:
+ /* initialize some parameters */
+ rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_seasonal_gamma].u_val = 0.1;
+ rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_seasonal_smoothing_window].u_val = 0.05;
+ /* fall through */
+ case CF_DEVPREDICT:
+ rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_dependent_rra_idx].u_cnt = -1;
+ break;
+ case CF_FAILURES:
+ rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_delta_pos].u_val = 2.0;
+ rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_delta_neg].u_val = 2.0;
+ rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_window_len].u_cnt = 3;
+ rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_failure_threshold].u_cnt = 2;
+ rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_dependent_rra_idx].u_cnt = -1;
+ break;
+ /* invalid consolidation function */
+ case -1:
+ rrd_set_error
+ ("Unrecognized consolidation function %s",
+ rrd.rra_def[rrd.stat_head->rra_cnt].cf_nam);
+ default:
+ break;
+ }
+ /* default: 1 pdp per cdp */
+ rrd.rra_def[rrd.stat_head->rra_cnt].pdp_cnt = 1;
+ break;
+ case 1:
+ switch (cf_conv
+ (rrd.rra_def[rrd.stat_head->rra_cnt].cf_nam)) {
+ case CF_HWPREDICT:
+ case CF_MHWPREDICT:
+ case CF_DEVSEASONAL:
+ case CF_SEASONAL:
+ case CF_DEVPREDICT:
+ case CF_FAILURES:
+ row_cnt = atoi(token);
+ if (row_cnt <= 0)
+ rrd_set_error("Invalid row count: %i", row_cnt);
+ rrd.rra_def[rrd.stat_head->rra_cnt].row_cnt = row_cnt;
+ break;
+ default:
+ rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_cdp_xff_val].u_val = atof(token);
+ if (rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_cdp_xff_val].u_val < 0.0
+ || rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_cdp_xff_val].u_val >= 1.0)
+ rrd_set_error
+ ("Invalid xff: must be between 0 and 1");
+ break;
+ }
+ break;
+ case 2:
+ switch (cf_conv
+ (rrd.rra_def[rrd.stat_head->rra_cnt].cf_nam)) {
+ case CF_HWPREDICT:
+ case CF_MHWPREDICT:
+ rrd.rra_def[rrd.stat_head->rra_cnt].par[RRA_hw_alpha].
+ u_val = atof(token);
+ if (atof(token) <= 0.0 || atof(token) >= 1.0)
+ rrd_set_error
+ ("Invalid alpha: must be between 0 and 1");
+ break;
+ case CF_DEVSEASONAL:
+ case CF_SEASONAL:
+ rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_seasonal_gamma].u_val = atof(token);
+ if (atof(token) <= 0.0 || atof(token) >= 1.0)
+ rrd_set_error
+ ("Invalid gamma: must be between 0 and 1");
+ rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_seasonal_smooth_idx].u_cnt =
+ hashed_name %
+ rrd.rra_def[rrd.stat_head->rra_cnt].row_cnt;
+ break;
+ case CF_FAILURES:
+ /* specifies the # of violations that constitutes the failure threshold */
+ rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_failure_threshold].u_cnt = atoi(token);
+ if (atoi(token) < 1
+ || atoi(token) > MAX_FAILURES_WINDOW_LEN)
+ rrd_set_error
+ ("Failure threshold is out of range %d, %d",
+ 1, MAX_FAILURES_WINDOW_LEN);
+ break;
+ case CF_DEVPREDICT:
+ /* specifies the index (1-based) of CF_DEVSEASONAL array
+ * associated with this CF_DEVPREDICT array. */
+ rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_dependent_rra_idx].u_cnt =
+ atoi(token) - 1;
+ break;
+ default:
+ rrd.rra_def[rrd.stat_head->rra_cnt].pdp_cnt =
+ atoi(token);
+ if (atoi(token) < 1)
+ rrd_set_error("Invalid step: must be >= 1");
+ break;
+ }
+ break;
+ case 3:
+ switch (cf_conv
+ (rrd.rra_def[rrd.stat_head->rra_cnt].cf_nam)) {
+ case CF_HWPREDICT:
+ case CF_MHWPREDICT:
+ rrd.rra_def[rrd.stat_head->rra_cnt].par[RRA_hw_beta].
+ u_val = atof(token);
+ if (atof(token) < 0.0 || atof(token) > 1.0)
+ rrd_set_error
+ ("Invalid beta: must be between 0 and 1");
+ break;
+ case CF_DEVSEASONAL:
+ case CF_SEASONAL:
+ /* specifies the index (1-based) of CF_HWPREDICT array
+ * associated with this CF_DEVSEASONAL or CF_SEASONAL array.
+ * */
+ rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_dependent_rra_idx].u_cnt =
+ atoi(token) - 1;
+ break;
+ case CF_FAILURES:
+ /* specifies the window length */
+ rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_window_len].u_cnt = atoi(token);
+ if (atoi(token) < 1
+ || atoi(token) > MAX_FAILURES_WINDOW_LEN)
+ rrd_set_error
+ ("Window length is out of range %d, %d", 1,
+ MAX_FAILURES_WINDOW_LEN);
+ /* verify that window length exceeds the failure threshold */
+ if (rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_window_len].u_cnt <
+ rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_failure_threshold].u_cnt)
+ rrd_set_error
+ ("Window length is shorter than the failure threshold");
+ break;
+ case CF_DEVPREDICT:
+ /* shouldn't be any more arguments */
+ rrd_set_error
+ ("Unexpected extra argument for consolidation function DEVPREDICT");
+ break;
+ default:
+ row_cnt = atoi(token);
+ if (row_cnt <= 0)
+ rrd_set_error("Invalid row count: %i", row_cnt);
+ rrd.rra_def[rrd.stat_head->rra_cnt].row_cnt = row_cnt;
+ break;
+ }
+ break;
+ case 4:
+ switch (cf_conv
+ (rrd.rra_def[rrd.stat_head->rra_cnt].cf_nam)) {
+ case CF_FAILURES:
+ /* specifies the index (1-based) of CF_DEVSEASONAL array
+ * associated with this CF_DEVFAILURES array. */
+ rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_dependent_rra_idx].u_cnt =
+ atoi(token) - 1;
+ break;
+ case CF_DEVSEASONAL:
+ case CF_SEASONAL:
+ /* optional smoothing window */
+ if (sscanf(token, "smoothing-window=%lf",
+ &(rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_seasonal_smoothing_window].
+ u_val))) {
+ strcpy(rrd.stat_head->version, RRD_VERSION); /* smoothing-window causes Version 4 */
+ if (rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_seasonal_smoothing_window].u_val < 0.0
+ || rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_seasonal_smoothing_window].u_val >
+ 1.0) {
+ rrd_set_error
+ ("Invalid smoothing-window %f: must be between 0 and 1",
+ rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_seasonal_smoothing_window].
+ u_val);
+ }
+ } else {
+ rrd_set_error("Invalid option %s", token);
+ }
+ break;
+ case CF_HWPREDICT:
+ case CF_MHWPREDICT:
+ /* length of the associated CF_SEASONAL and CF_DEVSEASONAL arrays. */
+ period = atoi(token);
+ if (period >
+ rrd.rra_def[rrd.stat_head->rra_cnt].row_cnt)
+ rrd_set_error
+ ("Length of seasonal cycle exceeds length of HW prediction array");
+ break;
+ default:
+ /* shouldn't be any more arguments */
+ rrd_set_error
+ ("Unexpected extra argument for consolidation function %s",
+ rrd.rra_def[rrd.stat_head->rra_cnt].cf_nam);
+ break;
+ }
+ break;
+ case 5:
+ /* If we are here, this must be a CF_HWPREDICT RRA.
+ * Specifies the index (1-based) of CF_SEASONAL array
+ * associated with this CF_HWPREDICT array. If this argument
+ * is missing, then the CF_SEASONAL, CF_DEVSEASONAL, CF_DEVPREDICT,
+ * CF_FAILURES.
+ * arrays are created automatically. */
+ rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_dependent_rra_idx].u_cnt = atoi(token) - 1;
+ break;
+ default:
+ /* should never get here */
+ rrd_set_error("Unknown error");
+ break;
+ } /* end switch */
+ if (rrd_test_error()) {
+ /* all errors are unrecoverable */
+ free(argvcopy);
+ rrd_free2(&rrd);
+ return (-1);
+ }
+ token = strtok_r(NULL, ":", &tokptr);
+ token_idx++;
+ } /* end while */
+ free(argvcopy);
+#ifdef DEBUG
+ fprintf(stderr,
+ "Creating RRA CF: %s, dep idx %lu, current idx %lu\n",
+ rrd.rra_def[rrd.stat_head->rra_cnt].cf_nam,
+ rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_dependent_rra_idx].u_cnt, rrd.stat_head->rra_cnt);
+#endif
+ /* should we create CF_SEASONAL, CF_DEVSEASONAL, and CF_DEVPREDICT? */
+ if ((cf_conv(rrd.rra_def[rrd.stat_head->rra_cnt].cf_nam) ==
+ CF_HWPREDICT
+ || cf_conv(rrd.rra_def[rrd.stat_head->rra_cnt].cf_nam) ==
+ CF_MHWPREDICT)
+ && rrd.rra_def[rrd.stat_head->rra_cnt].
+ par[RRA_dependent_rra_idx].u_cnt == rrd.stat_head->rra_cnt) {
+#ifdef DEBUG
+ fprintf(stderr, "Creating HW contingent RRAs\n");
+#endif
+ if (create_hw_contingent_rras(&rrd, period, hashed_name) ==
+ -1) {
+ rrd_set_error("creating contingent RRA");
+ rrd_free2(&rrd);
+ return -1;
+ }
+ }
+ rrd.stat_head->rra_cnt++;
+ } else {
+ rrd_set_error("can't parse argument '%s'", argv[i]);
+ rrd_free2(&rrd);
+ return -1;
+ }
+ }
+
+
+ if (rrd.stat_head->rra_cnt < 1) {
+ rrd_set_error("you must define at least one Round Robin Archive");
+ rrd_free2(&rrd);
+ return (-1);
+ }
+
+ if (rrd.stat_head->ds_cnt < 1) {
+ rrd_set_error("you must define at least one Data Source");
+ rrd_free2(&rrd);
+ return (-1);
+ }
+ return rrd_create_fn(filename, &rrd);
+}
+
+void parseGENERIC_DS(
+ const char *def,
+ rrd_t *rrd,
+ int ds_idx)
+{
+ char minstr[DS_NAM_SIZE], maxstr[DS_NAM_SIZE];
+ char *old_locale;
+
+ /*
+ int temp;
+
+ temp = sscanf(def,"%lu:%18[^:]:%18[^:]",
+ &(rrd -> ds_def[ds_idx].par[DS_mrhb_cnt].u_cnt),
+ minstr,maxstr);
+ */
+ old_locale = setlocale(LC_NUMERIC, "C");
+ if (sscanf(def, "%lu:%18[^:]:%18[^:]",
+ &(rrd->ds_def[ds_idx].par[DS_mrhb_cnt].u_cnt),
+ minstr, maxstr) == 3) {
+ if (minstr[0] == 'U' && minstr[1] == 0)
+ rrd->ds_def[ds_idx].par[DS_min_val].u_val = DNAN;
+ else
+ rrd->ds_def[ds_idx].par[DS_min_val].u_val = atof(minstr);
+
+ if (maxstr[0] == 'U' && maxstr[1] == 0)
+ rrd->ds_def[ds_idx].par[DS_max_val].u_val = DNAN;
+ else
+ rrd->ds_def[ds_idx].par[DS_max_val].u_val = atof(maxstr);
+
+ if (!isnan(rrd->ds_def[ds_idx].par[DS_min_val].u_val) &&
+ !isnan(rrd->ds_def[ds_idx].par[DS_max_val].u_val) &&
+ rrd->ds_def[ds_idx].par[DS_min_val].u_val
+ >= rrd->ds_def[ds_idx].par[DS_max_val].u_val) {
+ rrd_set_error("min must be less than max in DS definition");
+ setlocale(LC_NUMERIC, old_locale);
+ return;
+ }
+ } else {
+ rrd_set_error("failed to parse data source %s", def);
+ }
+ setlocale(LC_NUMERIC, old_locale);
+}
+
+/* Create the CF_DEVPREDICT, CF_DEVSEASONAL, CF_SEASONAL, and CF_FAILURES RRAs
+ * associated with a CF_HWPREDICT RRA. */
+int create_hw_contingent_rras(
+ rrd_t *rrd,
+ unsigned short period,
+ unsigned long hashed_name)
+{
+ size_t old_size;
+ rra_def_t *current_rra;
+
+ /* save index to CF_HWPREDICT */
+ unsigned long hw_index = rrd->stat_head->rra_cnt;
+
+ /* advance the pointer */
+ (rrd->stat_head->rra_cnt)++;
+ /* allocate the memory for the 4 contingent RRAs */
+ old_size = sizeof(rra_def_t) * (rrd->stat_head->rra_cnt);
+ if ((rrd->rra_def = rrd_realloc(rrd->rra_def,
+ old_size + 4 * sizeof(rra_def_t))) ==
+ NULL) {
+ rrd_free2(rrd);
+ rrd_set_error("allocating rrd.rra_def");
+ return (-1);
+ }
+ /* clear memory */
+ memset(&(rrd->rra_def[rrd->stat_head->rra_cnt]), 0,
+ 4 * sizeof(rra_def_t));
+
+ /* create the CF_SEASONAL RRA */
+ current_rra = &(rrd->rra_def[rrd->stat_head->rra_cnt]);
+ strcpy(current_rra->cf_nam, "SEASONAL");
+ current_rra->row_cnt = period;
+ current_rra->par[RRA_seasonal_smooth_idx].u_cnt = hashed_name % period;
+ current_rra->pdp_cnt = 1;
+ current_rra->par[RRA_seasonal_gamma].u_val =
+ rrd->rra_def[hw_index].par[RRA_hw_alpha].u_val;
+ current_rra->par[RRA_dependent_rra_idx].u_cnt = hw_index;
+ rrd->rra_def[hw_index].par[RRA_dependent_rra_idx].u_cnt =
+ rrd->stat_head->rra_cnt;
+
+ /* create the CF_DEVSEASONAL RRA */
+ (rrd->stat_head->rra_cnt)++;
+ current_rra = &(rrd->rra_def[rrd->stat_head->rra_cnt]);
+ strcpy(current_rra->cf_nam, "DEVSEASONAL");
+ current_rra->row_cnt = period;
+ current_rra->par[RRA_seasonal_smooth_idx].u_cnt = hashed_name % period;
+ current_rra->pdp_cnt = 1;
+ current_rra->par[RRA_seasonal_gamma].u_val =
+ rrd->rra_def[hw_index].par[RRA_hw_alpha].u_val;
+ current_rra->par[RRA_dependent_rra_idx].u_cnt = hw_index;
+
+ /* create the CF_DEVPREDICT RRA */
+ (rrd->stat_head->rra_cnt)++;
+ current_rra = &(rrd->rra_def[rrd->stat_head->rra_cnt]);
+ strcpy(current_rra->cf_nam, "DEVPREDICT");
+ current_rra->row_cnt = (rrd->rra_def[hw_index]).row_cnt;
+ current_rra->pdp_cnt = 1;
+ current_rra->par[RRA_dependent_rra_idx].u_cnt = hw_index + 2; /* DEVSEASONAL */
+
+ /* create the CF_FAILURES RRA */
+ (rrd->stat_head->rra_cnt)++;
+ current_rra = &(rrd->rra_def[rrd->stat_head->rra_cnt]);
+ strcpy(current_rra->cf_nam, "FAILURES");
+ current_rra->row_cnt = period;
+ current_rra->pdp_cnt = 1;
+ current_rra->par[RRA_delta_pos].u_val = 2.0;
+ current_rra->par[RRA_delta_neg].u_val = 2.0;
+ current_rra->par[RRA_failure_threshold].u_cnt = 7;
+ current_rra->par[RRA_window_len].u_cnt = 9;
+ current_rra->par[RRA_dependent_rra_idx].u_cnt = hw_index + 2; /* DEVSEASONAL */
+ return 0;
+}
+
+/* create and empty rrd file according to the specs given */
+
+int rrd_create_fn(
+ const char *file_name,
+ rrd_t *rrd)
+{
+ unsigned long i, ii;
+ int rrd_file;
+ rrd_value_t *unknown;
+ int unkn_cnt;
+ rrd_file_t *rrd_file_dn;
+ rrd_t rrd_dn;
+ unsigned flags = O_WRONLY | O_CREAT | O_TRUNC;
+
+#if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
+ flags |= O_BINARY;
+#endif
+
+ if ((rrd_file = open(file_name, flags, 0666)) < 0) {
+ rrd_set_error("creating '%s': %s", file_name, rrd_strerror(errno));
+ rrd_free2(rrd);
+ return (-1);
+ }
+
+ write(rrd_file, rrd->stat_head, sizeof(stat_head_t));
+
+ write(rrd_file, rrd->ds_def, sizeof(ds_def_t) * rrd->stat_head->ds_cnt);
+
+ write(rrd_file, rrd->rra_def,
+ sizeof(rra_def_t) * rrd->stat_head->rra_cnt);
+
+ write(rrd_file, rrd->live_head, sizeof(live_head_t));
+
+ if ((rrd->pdp_prep = calloc(1, sizeof(pdp_prep_t))) == NULL) {
+ rrd_set_error("allocating pdp_prep");
+ rrd_free2(rrd);
+ close(rrd_file);
+ return (-1);
+ }
+
+ strcpy(rrd->pdp_prep->last_ds, "U");
+
+ rrd->pdp_prep->scratch[PDP_val].u_val = 0.0;
+ rrd->pdp_prep->scratch[PDP_unkn_sec_cnt].u_cnt =
+ rrd->live_head->last_up % rrd->stat_head->pdp_step;
+
+ for (i = 0; i < rrd->stat_head->ds_cnt; i++)
+ write(rrd_file, rrd->pdp_prep, sizeof(pdp_prep_t));
+
+ if ((rrd->cdp_prep = calloc(1, sizeof(cdp_prep_t))) == NULL) {
+ rrd_set_error("allocating cdp_prep");
+ rrd_free2(rrd);
+ close(rrd_file);
+ return (-1);
+ }
+
+
+ for (i = 0; i < rrd->stat_head->rra_cnt; i++) {
+ switch (cf_conv(rrd->rra_def[i].cf_nam)) {
+ case CF_HWPREDICT:
+ case CF_MHWPREDICT:
+ init_hwpredict_cdp(rrd->cdp_prep);
+ break;
+ case CF_SEASONAL:
+ case CF_DEVSEASONAL:
+ init_seasonal_cdp(rrd->cdp_prep);
+ break;
+ case CF_FAILURES:
+ /* initialize violation history to 0 */
+ for (ii = 0; ii < MAX_CDP_PAR_EN; ii++) {
+ /* We can zero everything out, by setting u_val to the
+ * NULL address. Each array entry in scratch is 8 bytes
+ * (a double), but u_cnt only accessed 4 bytes (long) */
+ rrd->cdp_prep->scratch[ii].u_val = 0.0;
+ }
+ break;
+ default:
+ /* can not be zero because we don't know anything ... */
+ rrd->cdp_prep->scratch[CDP_val].u_val = DNAN;
+ /* startup missing pdp count */
+ rrd->cdp_prep->scratch[CDP_unkn_pdp_cnt].u_cnt =
+ ((rrd->live_head->last_up -
+ rrd->pdp_prep->scratch[PDP_unkn_sec_cnt].u_cnt)
+ % (rrd->stat_head->pdp_step
+ * rrd->rra_def[i].pdp_cnt)) / rrd->stat_head->pdp_step;
+ break;
+ }
+
+ for (ii = 0; ii < rrd->stat_head->ds_cnt; ii++) {
+ write(rrd_file, rrd->cdp_prep, sizeof(cdp_prep_t));
+ }
+ }
+
+ /* now, we must make sure that the rest of the rrd
+ struct is properly initialized */
+
+ if ((rrd->rra_ptr = calloc(1, sizeof(rra_ptr_t))) == NULL) {
+ rrd_set_error("allocating rra_ptr");
+ rrd_free2(rrd);
+ close(rrd_file);
+ return (-1);
+ }
+
+ /* changed this initialization to be consistent with
+ * rrd_restore. With the old value (0), the first update
+ * would occur for cur_row = 1 because rrd_update increments
+ * the pointer a priori. */
+ for (i = 0; i < rrd->stat_head->rra_cnt; i++) {
+ rrd->rra_ptr->cur_row = rra_random_row(&rrd->rra_def[i]);
+ write(rrd_file, rrd->rra_ptr, sizeof(rra_ptr_t));
+ }
+
+ /* write the empty data area */
+ if ((unknown = (rrd_value_t *) malloc(512 * sizeof(rrd_value_t))) == NULL) {
+ rrd_set_error("allocating unknown");
+ rrd_free2(rrd);
+ close(rrd_file);
+ return (-1);
+ }
+ for (i = 0; i < 512; ++i)
+ unknown[i] = DNAN;
+
+ unkn_cnt = 0;
+ for (i = 0; i < rrd->stat_head->rra_cnt; i++)
+ unkn_cnt += rrd->stat_head->ds_cnt * rrd->rra_def[i].row_cnt;
+
+ while (unkn_cnt > 0) {
+ write(rrd_file, unknown, sizeof(rrd_value_t) * min(unkn_cnt, 512));
+
+ unkn_cnt -= 512;
+ }
+ free(unknown);
+ fdatasync(rrd_file);
+ rrd_free2(rrd);
+ if (close(rrd_file) == -1) {
+ rrd_set_error("creating rrd: %s", rrd_strerror(errno));
+ return -1;
+ }
+ /* flush all we don't need out of the cache */
+ rrd_file_dn = rrd_open(file_name, &rrd_dn, RRD_READONLY);
+ rrd_dontneed(rrd_file_dn, &rrd_dn);
+ rrd_free(&rrd_dn);
+ rrd_close(rrd_file_dn);
+ return (0);
+}
+
+
+static void rrd_free2(
+ rrd_t *rrd)
+{
+ free(rrd->live_head);
+ free(rrd->stat_head);
+ free(rrd->ds_def);
+ free(rrd->rra_def);
+ free(rrd->rra_ptr);
+ free(rrd->pdp_prep);
+ free(rrd->cdp_prep);
+ free(rrd->rrd_value);
+}
+
+static int rand_init = 0;
+
+long int rra_random_row(
+ rra_def_t *rra)
+{
+ if (!rand_init) {
+ srandom((unsigned int) time(NULL) + (unsigned int) getpid());
+ rand_init++;
+ }
+
+ return random() % rra->row_cnt;
+}
diff --git a/program/src/rrd_datalang.c b/program/src/rrd_datalang.c
--- /dev/null
@@ -0,0 +1,34 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ *****************************************************************************
+ * rrd_datalang A system for passing named and typed parameters between
+ * the different parts of rrdtool
+ *
+ * In rrdtool thre are a number of places where large and complex
+ * data structures have to be passed from one function to another
+ * eg when rrd_info returns its findings, but also when a function like
+ * rrd_graph get called.
+ *
+ * At the moment function calling is done with an argc/argv type interface
+ * it's special property is that all data is passed as strings, which can lead
+ * to unnecessary conversions being performed when rrdtool functions are called
+ * from a typed language
+ *
+ * Data returns from functions is not standardized at all, which is
+ * efficient in the sense that the data return interface can be tailord to
+ * the specific needs of the function at hand, but it also leads to
+ * increassed probability for implementation errors as things have to be
+ * reinvented for each function. Also adding new functions into all the
+ * language bindings is quite cumbersom.
+ *
+ * Therefore I want to develop a standardized interface for passing named
+ * and typed data into functions and for returning data from functions to
+ * their callers. I am thinking about working of the code in rrd_info.c ...
+ *
+ * Does anyone have experiance in this field or any pointers to read up on
+ * related work ? Or maybe even an existing library for this ?
+ *
+ * Cheers
+ * tobi / 2001-03-10
+ *
+ *****************************************************************************/
diff --git a/program/src/rrd_diff.c b/program/src/rrd_diff.c
--- /dev/null
+++ b/program/src/rrd_diff.c
@@ -0,0 +1,122 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ * This code is stolen from rateup (mrtg-2.x) by Dave Rand
+ *****************************************************************************
+ * diff calculate the difference between two very long integers available as
+ * strings
+ *****************************************************************************
+ * $Id$
+ * $Log$
+ * Revision 1.4 2003/03/10 00:30:34 oetiker
+ * handle cases with two negative numbers
+ * -- Sasha Mikheev <sasha@avalon-net.co.il>
+ *
+ * Revision 1.3 2002/04/01 18:31:22 oetiker
+ * "!" takes a higher preference than "||" this means rrd_update N:: would
+ * segfault -- Oliver Cook <ollie@uk.clara.net>
+ *
+ * Revision 1.2 2002/02/01 20:34:49 oetiker
+ * fixed version number and date/time
+ *
+ * Revision 1.1.1.1 2001/02/25 22:25:05 oetiker
+ * checkin
+ *
+ * Revision 1.1 1998/10/08 18:21:45 oetiker
+ * Initial revision
+ *
+ * Revision 1.3 1998/02/06 21:10:52 oetiker
+ * removed max define .. it is now in rrd_tool.h
+ *
+ * Revision 1.2 1997/12/07 20:38:03 oetiker
+ * ansified
+ *
+ * Revision 1.1 1997/11/28 23:31:59 oetiker
+ * Initial revision
+ *
+ *****************************************************************************/
+
+#include "rrd_tool.h"
+
+double rrd_diff(
+ char *a,
+ char *b)
+{
+ char res[LAST_DS_LEN + 1], *a1, *b1, *r1, *fix;
+ int c, x, m;
+ char a_neg = 0, b_neg = 0;
+ double result;
+
+ while (!(isdigit((int) *a) || *a == 0)) {
+ if (*a == '-')
+ a_neg = 1;
+ a++;
+ }
+ fix = a;
+ while (isdigit((int) *fix))
+ fix++;
+ *fix = 0; /* maybe there is some non digit data in the string */
+ while (!(isdigit((int) *b) || *b == 0)) {
+ if (*b == '-')
+ b_neg = 1;
+ b++;
+ }
+ fix = b;
+ while (isdigit((int) *fix))
+ fix++;
+ *fix = 0; /* maybe there is some non digit data in the string */
+ if (!isdigit((int) *a) || !isdigit((int) *b))
+ return DNAN;
+ if (a_neg + b_neg == 1) /* can not handle numbers with different signs yet */
+ return DNAN;
+ a1 = &a[strlen(a) - 1];
+ m = max(strlen(a), strlen(b));
+ if (m > LAST_DS_LEN)
+ return DNAN; /* result string too short */
+
+ r1 = &res[m + 1];
+ for (b1 = res; b1 <= r1; b1++)
+ *b1 = ' ';
+ b1 = &b[strlen(b) - 1];
+ r1[1] = 0; /* Null terminate result */
+ c = 0;
+ for (x = 0; x < m; x++) {
+ if (a1 >= a && b1 >= b) {
+ *r1 = ((*a1 - c) - *b1) + '0';
+ } else if (a1 >= a) {
+ *r1 = (*a1 - c);
+ } else {
+ *r1 = ('0' - *b1 - c) + '0';
+ }
+ if (*r1 < '0') {
+ *r1 += 10;
+ c = 1;
+ } else if (*r1 > '9') { /* 0 - 10 */
+ *r1 -= 10;
+ c = 1;
+ } else {
+ c = 0;
+ }
+ a1--;
+ b1--;
+ r1--;
+ }
+ if (c) {
+ r1 = &res[m + 1];
+ for (x = 0; isdigit((int) *r1) && x < m; x++, r1--) {
+ *r1 = ('9' - *r1 + c) + '0';
+ if (*r1 > '9') {
+ *r1 -= 10;
+ c = 1;
+ } else {
+ c = 0;
+ }
+ }
+ result = -atof(res);
+ } else
+ result = atof(res);
+
+ if (a_neg + b_neg == 2) /* both are negatives, reverse sign */
+ result = -result;
+
+ return result;
+}
diff --git a/program/src/rrd_dump.c b/program/src/rrd_dump.c
--- /dev/null
+++ b/program/src/rrd_dump.c
@@ -0,0 +1,489 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ *****************************************************************************
+ * rrd_dump Display a RRD
+ *****************************************************************************
+ * $Id$
+ * $Log$
+ * Revision 1.7 2004/05/25 20:53:21 oetiker
+ * prevent small leak when resources are exhausted -- Mike Slifcak
+ *
+ * Revision 1.6 2004/05/25 20:51:49 oetiker
+ * Update displayed copyright messages to be consistent. -- Mike Slifcak
+ *
+ * Revision 1.5 2003/02/13 07:05:27 oetiker
+ * Find attached the patch I promised to send to you. Please note that there
+ * are three new source files (src/rrd_is_thread_safe.h, src/rrd_thread_safe.c
+ * and src/rrd_not_thread_safe.c) and the introduction of librrd_th. This
+ * library is identical to librrd, but it contains support code for per-thread
+ * global variables currently used for error information only. This is similar
+ * to how errno per-thread variables are implemented. librrd_th must be linked
+ * alongside of libpthred
+ *
+ * There is also a new file "THREADS", holding some documentation.
+ *
+ * -- Peter Stamfest <peter@stamfest.at>
+ *
+ * Revision 1.4 2002/02/01 20:34:49 oetiker
+ * fixed version number and date/time
+ *
+ * Revision 1.3 2001/03/10 23:54:39 oetiker
+ * Support for COMPUTE data sources (CDEF data sources). Removes the RPN
+ * parser and calculator from rrd_graph and puts then in a new file,
+ * rrd_rpncalc.c. Changes to core files rrd_create and rrd_update. Some
+ * clean-up of aberrant behavior stuff, including a bug fix.
+ * Documentation update (rrdcreate.pod, rrdupdate.pod). Change xml format.
+ * -- Jake Brutlag <jakeb@corp.webtv.net>
+ *
+ * Revision 1.2 2001/03/04 13:01:55 oetiker
+ *
+ * Revision 1.1.1.1 2001/02/25 22:25:05 oetiker
+ * checkin
+ *
+ *****************************************************************************/
+#include "rrd_tool.h"
+#include "rrd_rpncalc.h"
+
+#if !(defined(NETWARE) || defined(WIN32))
+extern char *tzname[2];
+#endif
+
+
+int rrd_dump_opt_r(
+ const char *filename,
+ char *outname,
+ int opt_noheader)
+{
+ unsigned int i, ii, ix, iii = 0;
+ time_t now;
+ char somestring[255];
+ rrd_value_t my_cdp;
+ off_t rra_base, rra_start, rra_next;
+ rrd_file_t *rrd_file;
+ FILE *out_file;
+ rrd_t rrd;
+ rrd_value_t value;
+ struct tm tm;
+
+ rrd_file = rrd_open(filename, &rrd, RRD_READONLY | RRD_READAHEAD);
+ if (rrd_file == NULL) {
+ rrd_free(&rrd);
+ return (-1);
+ }
+
+ out_file = NULL;
+ if (outname) {
+ if (!(out_file = fopen(outname, "w"))) {
+ return (-1);
+ }
+ } else {
+ out_file = stdout;
+ }
+
+ if (!opt_noheader) {
+ fputs("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n", out_file);
+ fputs
+ ("<!DOCTYPE rrd SYSTEM \"http://oss.oetiker.ch/rrdtool/rrdtool.dtd\">\n",
+ out_file);
+ }
+ fputs("<!-- Round Robin Database Dump -->", out_file);
+ fputs("<rrd>", out_file);
+ if (atoi(rrd.stat_head->version) <= 3) {
+ fprintf(out_file, "\t<version> %s </version>\n", RRD_VERSION3);
+ } else {
+ fprintf(out_file, "\t<version> %s </version>\n", RRD_VERSION);
+ }
+ fprintf(out_file, "\t<step> %lu </step> <!-- Seconds -->\n",
+ rrd.stat_head->pdp_step);
+#if HAVE_STRFTIME
+ localtime_r(&rrd.live_head->last_up, &tm);
+ strftime(somestring, 200, "%Y-%m-%d %H:%M:%S %Z", &tm);
+#else
+# error "Need strftime"
+#endif
+ fprintf(out_file, "\t<lastupdate> %lu </lastupdate> <!-- %s -->\n\n",
+ (unsigned long) rrd.live_head->last_up, somestring);
+ for (i = 0; i < rrd.stat_head->ds_cnt; i++) {
+ fprintf(out_file, "\t<ds>\n");
+ fprintf(out_file, "\t\t<name> %s </name>\n", rrd.ds_def[i].ds_nam);
+ fprintf(out_file, "\t\t<type> %s </type>\n", rrd.ds_def[i].dst);
+ if (dst_conv(rrd.ds_def[i].dst) != DST_CDEF) {
+ fprintf(out_file,
+ "\t\t<minimal_heartbeat> %lu </minimal_heartbeat>\n",
+ rrd.ds_def[i].par[DS_mrhb_cnt].u_cnt);
+ if (isnan(rrd.ds_def[i].par[DS_min_val].u_val)) {
+ fprintf(out_file, "\t\t<min> NaN </min>\n");
+ } else {
+ fprintf(out_file, "\t\t<min> %0.10e </min>\n",
+ rrd.ds_def[i].par[DS_min_val].u_val);
+ }
+ if (isnan(rrd.ds_def[i].par[DS_max_val].u_val)) {
+ fprintf(out_file, "\t\t<max> NaN </max>\n");
+ } else {
+ fprintf(out_file, "\t\t<max> %0.10e </max>\n",
+ rrd.ds_def[i].par[DS_max_val].u_val);
+ }
+ } else { /* DST_CDEF */
+ char *str = NULL;
+
+ rpn_compact2str((rpn_cdefds_t *) &(rrd.ds_def[i].par[DS_cdef]),
+ rrd.ds_def, &str);
+ fprintf(out_file, "\t\t<cdef> %s </cdef>\n", str);
+ free(str);
+ }
+ fprintf(out_file, "\n\t\t<!-- PDP Status -->\n");
+ fprintf(out_file, "\t\t<last_ds> %s </last_ds>\n",
+ rrd.pdp_prep[i].last_ds);
+ if (isnan(rrd.pdp_prep[i].scratch[PDP_val].u_val)) {
+ fprintf(out_file, "\t\t<value> NaN </value>\n");
+ } else {
+ fprintf(out_file, "\t\t<value> %0.10e </value>\n",
+ rrd.pdp_prep[i].scratch[PDP_val].u_val);
+ }
+ fprintf(out_file, "\t\t<unknown_sec> %lu </unknown_sec>\n",
+ rrd.pdp_prep[i].scratch[PDP_unkn_sec_cnt].u_cnt);
+
+ fprintf(out_file, "\t</ds>\n\n");
+ }
+
+ fputs("<!-- Round Robin Archives -->", out_file);
+
+ rra_base = rrd_file->header_len;
+ rra_next = rra_base;
+
+ for (i = 0; i < rrd.stat_head->rra_cnt; i++) {
+
+ long timer = 0;
+
+ rra_start = rra_next;
+ rra_next += (rrd.stat_head->ds_cnt
+ * rrd.rra_def[i].row_cnt * sizeof(rrd_value_t));
+ fprintf(out_file, "\t<rra>\n");
+ fprintf(out_file, "\t\t<cf> %s </cf>\n", rrd.rra_def[i].cf_nam);
+ fprintf(out_file,
+ "\t\t<pdp_per_row> %lu </pdp_per_row> <!-- %lu seconds -->\n\n",
+ rrd.rra_def[i].pdp_cnt,
+ rrd.rra_def[i].pdp_cnt * rrd.stat_head->pdp_step);
+ /* support for RRA parameters */
+ fprintf(out_file, "\t\t<params>\n");
+ switch (cf_conv(rrd.rra_def[i].cf_nam)) {
+ case CF_HWPREDICT:
+ case CF_MHWPREDICT:
+ fprintf(out_file, "\t\t<hw_alpha> %0.10e </hw_alpha>\n",
+ rrd.rra_def[i].par[RRA_hw_alpha].u_val);
+ fprintf(out_file, "\t\t<hw_beta> %0.10e </hw_beta>\n",
+ rrd.rra_def[i].par[RRA_hw_beta].u_val);
+ fprintf(out_file,
+ "\t\t<dependent_rra_idx> %lu </dependent_rra_idx>\n",
+ rrd.rra_def[i].par[RRA_dependent_rra_idx].u_cnt);
+ break;
+ case CF_SEASONAL:
+ case CF_DEVSEASONAL:
+ fprintf(out_file,
+ "\t\t<seasonal_gamma> %0.10e </seasonal_gamma>\n",
+ rrd.rra_def[i].par[RRA_seasonal_gamma].u_val);
+ fprintf(out_file,
+ "\t\t<seasonal_smooth_idx> %lu </seasonal_smooth_idx>\n",
+ rrd.rra_def[i].par[RRA_seasonal_smooth_idx].u_cnt);
+ if (atoi(rrd.stat_head->version) >= 4) {
+ fprintf(out_file,
+ "\t\t<smoothing_window> %0.10e </smoothing_window>\n",
+ rrd.rra_def[i].par[RRA_seasonal_smoothing_window].
+ u_val);
+ }
+ fprintf(out_file,
+ "\t\t<dependent_rra_idx> %lu </dependent_rra_idx>\n",
+ rrd.rra_def[i].par[RRA_dependent_rra_idx].u_cnt);
+ break;
+ case CF_FAILURES:
+ fprintf(out_file, "\t\t<delta_pos> %0.10e </delta_pos>\n",
+ rrd.rra_def[i].par[RRA_delta_pos].u_val);
+ fprintf(out_file, "\t\t<delta_neg> %0.10e </delta_neg>\n",
+ rrd.rra_def[i].par[RRA_delta_neg].u_val);
+ fprintf(out_file, "\t\t<window_len> %lu </window_len>\n",
+ rrd.rra_def[i].par[RRA_window_len].u_cnt);
+ fprintf(out_file,
+ "\t\t<failure_threshold> %lu </failure_threshold>\n",
+ rrd.rra_def[i].par[RRA_failure_threshold].u_cnt);
+ /* fall thru */
+ case CF_DEVPREDICT:
+ fprintf(out_file,
+ "\t\t<dependent_rra_idx> %lu </dependent_rra_idx>\n",
+ rrd.rra_def[i].par[RRA_dependent_rra_idx].u_cnt);
+ break;
+ case CF_AVERAGE:
+ case CF_MAXIMUM:
+ case CF_MINIMUM:
+ case CF_LAST:
+ default:
+ fprintf(out_file, "\t\t<xff> %0.10e </xff>\n",
+ rrd.rra_def[i].par[RRA_cdp_xff_val].u_val);
+ break;
+ }
+ fprintf(out_file, "\t\t</params>\n");
+ fprintf(out_file, "\t\t<cdp_prep>\n");
+ for (ii = 0; ii < rrd.stat_head->ds_cnt; ii++) {
+ unsigned long ivalue;
+
+ fprintf(out_file, "\t\t\t<ds>\n");
+ /* support for exporting all CDP parameters */
+ /* parameters common to all CFs */
+ /* primary_val and secondary_val do not need to be saved between updates
+ * so strictly speaking they could be omitted.
+ * However, they can be useful for diagnostic purposes, so are included here. */
+ value = rrd.cdp_prep[i * rrd.stat_head->ds_cnt
+ + ii].scratch[CDP_primary_val].u_val;
+ if (isnan(value)) {
+ fprintf(out_file,
+ "\t\t\t<primary_value> NaN </primary_value>\n");
+ } else {
+ fprintf(out_file,
+ "\t\t\t<primary_value> %0.10e </primary_value>\n",
+ value);
+ }
+ value =
+ rrd.cdp_prep[i * rrd.stat_head->ds_cnt +
+ ii].scratch[CDP_secondary_val].u_val;
+ if (isnan(value)) {
+ fprintf(out_file,
+ "\t\t\t<secondary_value> NaN </secondary_value>\n");
+ } else {
+ fprintf(out_file,
+ "\t\t\t<secondary_value> %0.10e </secondary_value>\n",
+ value);
+ }
+ switch (cf_conv(rrd.rra_def[i].cf_nam)) {
+ case CF_HWPREDICT:
+ case CF_MHWPREDICT:
+ value =
+ rrd.cdp_prep[i * rrd.stat_head->ds_cnt +
+ ii].scratch[CDP_hw_intercept].u_val;
+ if (isnan(value)) {
+ fprintf(out_file, "\t\t\t<intercept> NaN </intercept>\n");
+ } else {
+ fprintf(out_file,
+ "\t\t\t<intercept> %0.10e </intercept>\n", value);
+ }
+ value =
+ rrd.cdp_prep[i * rrd.stat_head->ds_cnt +
+ ii].scratch[CDP_hw_last_intercept].u_val;
+ if (isnan(value)) {
+ fprintf(out_file,
+ "\t\t\t<last_intercept> NaN </last_intercept>\n");
+ } else {
+ fprintf(out_file,
+ "\t\t\t<last_intercept> %0.10e </last_intercept>\n",
+ value);
+ }
+ value =
+ rrd.cdp_prep[i * rrd.stat_head->ds_cnt +
+ ii].scratch[CDP_hw_slope].u_val;
+ if (isnan(value)) {
+ fprintf(out_file, "\t\t\t<slope> NaN </slope>\n");
+ } else {
+ fprintf(out_file, "\t\t\t<slope> %0.10e </slope>\n",
+ value);
+ }
+ value =
+ rrd.cdp_prep[i * rrd.stat_head->ds_cnt +
+ ii].scratch[CDP_hw_last_slope].u_val;
+ if (isnan(value)) {
+ fprintf(out_file,
+ "\t\t\t<last_slope> NaN </last_slope>\n");
+ } else {
+ fprintf(out_file,
+ "\t\t\t<last_slope> %0.10e </last_slope>\n",
+ value);
+ }
+ ivalue =
+ rrd.cdp_prep[i * rrd.stat_head->ds_cnt +
+ ii].scratch[CDP_null_count].u_cnt;
+ fprintf(out_file, "\t\t\t<nan_count> %lu </nan_count>\n",
+ ivalue);
+ ivalue =
+ rrd.cdp_prep[i * rrd.stat_head->ds_cnt +
+ ii].scratch[CDP_last_null_count].u_cnt;
+ fprintf(out_file,
+ "\t\t\t<last_nan_count> %lu </last_nan_count>\n",
+ ivalue);
+ break;
+ case CF_SEASONAL:
+ case CF_DEVSEASONAL:
+ value =
+ rrd.cdp_prep[i * rrd.stat_head->ds_cnt +
+ ii].scratch[CDP_hw_seasonal].u_val;
+ if (isnan(value)) {
+ fprintf(out_file, "\t\t\t<seasonal> NaN </seasonal>\n");
+ } else {
+ fprintf(out_file, "\t\t\t<seasonal> %0.10e </seasonal>\n",
+ value);
+ }
+ value =
+ rrd.cdp_prep[i * rrd.stat_head->ds_cnt +
+ ii].scratch[CDP_hw_last_seasonal].u_val;
+ if (isnan(value)) {
+ fprintf(out_file,
+ "\t\t\t<last_seasonal> NaN </last_seasonal>\n");
+ } else {
+ fprintf(out_file,
+ "\t\t\t<last_seasonal> %0.10e </last_seasonal>\n",
+ value);
+ }
+ ivalue =
+ rrd.cdp_prep[i * rrd.stat_head->ds_cnt +
+ ii].scratch[CDP_init_seasonal].u_cnt;
+ fprintf(out_file, "\t\t\t<init_flag> %lu </init_flag>\n",
+ ivalue);
+ break;
+ case CF_DEVPREDICT:
+ break;
+ case CF_FAILURES:
+ {
+ unsigned short vidx;
+ char *violations_array = (char *) ((void *)
+ rrd.cdp_prep[i *
+ rrd.
+ stat_head->
+ ds_cnt +
+ ii].
+ scratch);
+ fprintf(out_file, "\t\t\t<history> ");
+ for (vidx = 0;
+ vidx < rrd.rra_def[i].par[RRA_window_len].u_cnt;
+ ++vidx) {
+ fprintf(out_file, "%d", violations_array[vidx]);
+ }
+ fprintf(out_file, " </history>\n");
+ }
+ break;
+ case CF_AVERAGE:
+ case CF_MAXIMUM:
+ case CF_MINIMUM:
+ case CF_LAST:
+ default:
+ value =
+ rrd.cdp_prep[i * rrd.stat_head->ds_cnt +
+ ii].scratch[CDP_val].u_val;
+ if (isnan(value)) {
+ fprintf(out_file, "\t\t\t<value> NaN </value>\n");
+ } else {
+ fprintf(out_file, "\t\t\t<value> %0.10e </value>\n",
+ value);
+ }
+ fprintf(out_file,
+ "\t\t\t<unknown_datapoints> %lu </unknown_datapoints>\n",
+ rrd.cdp_prep[i * rrd.stat_head->ds_cnt +
+ ii].scratch[CDP_unkn_pdp_cnt].u_cnt);
+ break;
+ }
+ fprintf(out_file, "\t\t\t</ds>\n");
+ }
+ fprintf(out_file, "\t\t</cdp_prep>\n");
+
+ fprintf(out_file, "\t\t<database>\n");
+ rrd_seek(rrd_file, (rra_start + (rrd.rra_ptr[i].cur_row + 1)
+ * rrd.stat_head->ds_cnt
+ * sizeof(rrd_value_t)), SEEK_SET);
+ timer = -(rrd.rra_def[i].row_cnt - 1);
+ ii = rrd.rra_ptr[i].cur_row;
+ for (ix = 0; ix < rrd.rra_def[i].row_cnt; ix++) {
+ ii++;
+ if (ii >= rrd.rra_def[i].row_cnt) {
+ rrd_seek(rrd_file, rra_start, SEEK_SET);
+ ii = 0; /* wrap if max row cnt is reached */
+ }
+ now = (rrd.live_head->last_up
+ - rrd.live_head->last_up
+ % (rrd.rra_def[i].pdp_cnt * rrd.stat_head->pdp_step))
+ + (timer * rrd.rra_def[i].pdp_cnt * rrd.stat_head->pdp_step);
+
+ timer++;
+#if HAVE_STRFTIME
+ localtime_r(&now, &tm);
+ strftime(somestring, 200, "%Y-%m-%d %H:%M:%S %Z", &tm);
+#else
+# error "Need strftime"
+#endif
+ fprintf(out_file, "\t\t\t<!-- %s / %d --> <row>", somestring,
+ (int) now);
+ for (iii = 0; iii < rrd.stat_head->ds_cnt; iii++) {
+ rrd_read(rrd_file, &my_cdp, sizeof(rrd_value_t) * 1);
+ if (isnan(my_cdp)) {
+ fprintf(out_file, "<v> NaN </v>");
+ } else {
+ fprintf(out_file, "<v> %0.10e </v>", my_cdp);
+ };
+ }
+ fprintf(out_file, "</row>\n");
+ }
+ fprintf(out_file, "\t\t</database>\n\t</rra>\n");
+
+ }
+ fprintf(out_file, "</rrd>\n");
+ rrd_free(&rrd);
+ if (out_file != stdout) {
+ fclose(out_file);
+ }
+ return rrd_close(rrd_file);
+}
+
+/* backward compatibility with 1.2.x */
+int rrd_dump_r(
+ const char *filename,
+ char *outname)
+{
+ return rrd_dump_opt_r(filename, outname, 0);
+}
+
+int rrd_dump(
+ int argc,
+ char **argv)
+{
+ int rc;
+ int opt_noheader = 0;
+
+ /* init rrd clean */
+
+ optind = 0;
+ opterr = 0; /* initialize getopt */
+
+ while (42) {
+ int opt;
+ int option_index = 0;
+ static struct option long_options[] = {
+ {"no-header", no_argument, 0, 'n'},
+ {0, 0, 0, 0}
+ };
+
+ opt = getopt_long(argc, argv, "n", long_options, &option_index);
+
+ if (opt == EOF)
+ break;
+
+ switch (opt) {
+ case 'n':
+ opt_noheader = 1;
+ break;
+
+ default:
+ rrd_set_error("usage rrdtool %s [--no-header|-n] "
+ "file.rrd [file.xml]", argv[0]);
+ return (-1);
+ break;
+ }
+ } /* while (42) */
+
+ if ((argc - optind) < 1 || (argc - optind) > 2) {
+ rrd_set_error("usage rrdtool %s [--no-header|-n] "
+ "file.rrd [file.xml]", argv[0]);
+ return (-1);
+ }
+
+ if ((argc - optind) == 2) {
+ rc = rrd_dump_opt_r(argv[optind], argv[optind + 1], opt_noheader);
+ } else {
+ rc = rrd_dump_opt_r(argv[optind], NULL, opt_noheader);
+ }
+
+ return rc;
+}
diff --git a/program/src/rrd_error.c b/program/src/rrd_error.c
--- /dev/null
+++ b/program/src/rrd_error.c
@@ -0,0 +1,149 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ *****************************************************************************
+ * rrd_error.c Common Header File
+ *****************************************************************************
+ * $Id$
+ * $Log$
+ * Revision 1.4 2003/02/22 21:57:03 oetiker
+ * a patch to avoid a memory leak and a Makefile.am patch to
+ * distribute all required source files -- Peter Stamfest <peter@stamfest.at>
+ *
+ * Revision 1.3 2003/02/13 07:05:27 oetiker
+ * Find attached the patch I promised to send to you. Please note that there
+ * are three new source files (src/rrd_is_thread_safe.h, src/rrd_thread_safe.c
+ * and src/rrd_not_thread_safe.c) and the introduction of librrd_th. This
+ * library is identical to librrd, but it contains support code for per-thread
+ * global variables currently used for error information only. This is similar
+ * to how errno per-thread variables are implemented. librrd_th must be linked
+ * alongside of libpthred
+ *
+ * There is also a new file "THREADS", holding some documentation.
+ *
+ * -- Peter Stamfest <peter@stamfest.at>
+ *
+ * Revision 1.2 2002/02/01 20:34:49 oetiker
+ * fixed version number and date/time
+ *
+ * Revision 1.1.1.1 2001/02/25 22:25:05 oetiker
+ * checkin
+ *
+ *************************************************************************** */
+
+#include "rrd_tool.h"
+#include <stdarg.h>
+
+#define MAXLEN 4096
+#define ERRBUFLEN 256
+#define CTX (rrd_get_context())
+
+void rrd_set_error(
+ char *fmt,
+ ...)
+{
+ va_list argp;
+
+ rrd_clear_error();
+ va_start(argp, fmt);
+#ifdef HAVE_VSNPRINTF
+ vsnprintf(CTX->rrd_error, sizeof(CTX->rrd_error), fmt, argp);
+#else
+ vsprintf(CTX->rrd_error, fmt, argp);
+#endif
+ va_end(argp);
+}
+
+int rrd_test_error(
+ void)
+{
+ return CTX->rrd_error[0] != '\0';
+}
+
+void rrd_clear_error(
+ void)
+{
+ CTX->rrd_error[0] = '\0';
+}
+
+char *rrd_get_error(
+ void)
+{
+ return CTX->rrd_error;
+}
+
+#if 0
+/* PS: Keep this stuff around, maybe we want it again if we use
+ rrd_contexts to really associate them with single RRD files and
+ operations on them... Then a single thread may use more than one
+ context. Using these functions would require to change each and
+ every function containing any of the non _r versions... */
+void rrd_set_error_r(
+ rrd_context_t * rrd_ctx,
+ char *fmt,
+ ...)
+{
+ va_list argp;
+
+ rrd_clear_error_r(rrd_ctx);
+ va_start(argp, fmt);
+#ifdef HAVE_VSNPRINTF
+ vsnprintf(rrd_ctx->rrd_error, sizeof(rrd_ctx->rrd_error), fmt, argp);
+ rrd_ctx->rrd_error[sizeof(rrd_ctx->rrd_error) - 1] = '\0';
+#else
+ vsprintf(rrd_ctx->rrd_error, fmt, argp);
+#endif
+ va_end(argp);
+}
+
+int rrd_test_error_r(
+ rrd_context_t * rrd_ctx)
+{
+ return rrd_ctx->rrd_error[0] != '\0';
+}
+
+void rrd_clear_error_r(
+ rrd_context_t * rrd_ctx)
+{
+ rrd_ctx->rrd_error[0] = '\0';
+}
+
+char *rrd_get_error_r(
+ rrd_context_t * rrd_ctx)
+{
+ return rrd_ctx->rrd_error;
+}
+#endif
+
+/* PS: Should we move this to some other file? It is not really error
+ related. */
+rrd_context_t *rrd_new_context(
+ void)
+{
+ rrd_context_t *rrd_ctx = (rrd_context_t *) malloc(sizeof(rrd_context_t));
+
+ if (!rrd_ctx) {
+ return NULL;
+ }
+
+ rrd_ctx->rrd_error[0] = '\0';
+ rrd_ctx->lib_errstr[0] = '\0';
+ return rrd_ctx;
+}
+
+void rrd_free_context(
+ rrd_context_t * rrd_ctx)
+{
+ if (rrd_ctx) {
+ free(rrd_ctx);
+ }
+}
+
+#if 0
+void rrd_globalize_error(
+ rrd_context_t * rrd_ctx)
+{
+ if (rrd_ctx) {
+ rrd_set_error(rrd_ctx->rrd_error);
+ }
+}
+#endif
diff --git a/program/src/rrd_fetch.c b/program/src/rrd_fetch.c
--- /dev/null
+++ b/program/src/rrd_fetch.c
@@ -0,0 +1,450 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ *****************************************************************************
+ * rrd_fetch.c read date from an rrd to use for further processing
+ *****************************************************************************
+ * $Id$
+ * $Log$
+ * Revision 1.8 2004/05/18 18:53:03 oetiker
+ * big spell checking patch -- slif@bellsouth.net
+ *
+ * Revision 1.7 2003/11/11 19:46:21 oetiker
+ * replaced time_value with rrd_time_value as MacOS X introduced a struct of that name in their standard headers
+ *
+ * Revision 1.6 2003/01/16 23:27:54 oetiker
+ * fix border condition in rra selection of rrd_fetch
+ * -- Stanislav Sinyagin <ssinyagin@yahoo.com>
+ *
+ * Revision 1.5 2002/06/23 22:29:40 alex
+ * Added "step=1800" and such to "DEF"
+ * Cleaned some of the signed vs. unsigned problems
+ *
+ * Revision 1.4 2002/02/01 20:34:49 oetiker
+ * fixed version number and date/time
+ *
+ * Revision 1.3 2001/12/24 06:51:49 alex
+ * A patch of size 44Kbytes... in short:
+ *
+ * Found and repaired the off-by-one error in rrd_fetch_fn().
+ * As a result I had to remove the hacks in rrd_fetch_fn(),
+ * rrd_tool.c, vdef_calc(), data_calc(), data_proc() and
+ * reduce_data(). There may be other places which I didn't
+ * find so be careful.
+ *
+ * Enhanced debugging in rrd_fetch_fn(), it shows the RRA selection
+ * process.
+ *
+ * Added the ability to print VDEF timestamps. At the moment it
+ * is a hack, I needed it now to fix the off-by-one error.
+ * If the format string is "%c" (and nothing else!), the time
+ * will be printed by both ctime() and as a long int.
+ *
+ * Moved some code around (slightly altering it) from rrd_graph()
+ * initializing now in rrd_graph_init()
+ * options parsing now in rrd_graph_options()
+ * script parsing now in rrd_graph_script()
+ *
+ * Revision 1.2 2001/12/17 12:48:43 oetiker
+ * fix overflow error ...
+ *
+ * Revision 1.1.1.1 2001/02/25 22:25:05 oetiker
+ * checkin
+ *
+ *****************************************************************************/
+
+#include "rrd_tool.h"
+
+#include "rrd_is_thread_safe.h"
+/*#define DEBUG*/
+
+int rrd_fetch(
+ int argc,
+ char **argv,
+ time_t *start,
+ time_t *end, /* which time frame do you want ?
+ * will be changed to represent reality */
+ unsigned long *step, /* which stepsize do you want?
+ * will be changed to represent reality */
+ unsigned long *ds_cnt, /* number of data sources in file */
+ char ***ds_namv, /* names of data sources */
+ rrd_value_t **data)
+{ /* two dimensional array containing the data */
+ long step_tmp = 1;
+ time_t start_tmp = 0, end_tmp = 0;
+ const char *cf;
+
+ rrd_time_value_t start_tv, end_tv;
+ char *parsetime_error = NULL;
+ struct option long_options[] = {
+ {"resolution", required_argument, 0, 'r'},
+ {"start", required_argument, 0, 's'},
+ {"end", required_argument, 0, 'e'},
+ {0, 0, 0, 0}
+ };
+
+ optind = 0;
+ opterr = 0; /* initialize getopt */
+
+ /* init start and end time */
+ rrd_parsetime("end-24h", &start_tv);
+ rrd_parsetime("now", &end_tv);
+
+ while (1) {
+ int option_index = 0;
+ int opt;
+
+ opt = getopt_long(argc, argv, "r:s:e:", long_options, &option_index);
+
+ if (opt == EOF)
+ break;
+
+ switch (opt) {
+ case 's':
+ if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
+ rrd_set_error("start time: %s", parsetime_error);
+ return -1;
+ }
+ break;
+ case 'e':
+ if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
+ rrd_set_error("end time: %s", parsetime_error);
+ return -1;
+ }
+ break;
+ case 'r':
+ step_tmp = atol(optarg);
+ break;
+ case '?':
+ rrd_set_error("unknown option '-%c'", optopt);
+ return (-1);
+ }
+ }
+
+
+ if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
+ return -1;
+ }
+
+
+ if (start_tmp < 3600 * 24 * 365 * 10) {
+ rrd_set_error("the first entry to fetch should be after 1980");
+ return (-1);
+ }
+
+ if (end_tmp < start_tmp) {
+ rrd_set_error("start (%ld) should be less than end (%ld)", start_tmp,
+ end_tmp);
+ return (-1);
+ }
+
+ *start = start_tmp;
+ *end = end_tmp;
+
+ if (step_tmp < 1) {
+ rrd_set_error("step must be >= 1 second");
+ return -1;
+ }
+ *step = step_tmp;
+
+ if (optind + 1 >= argc) {
+ rrd_set_error("not enough arguments");
+ return -1;
+ }
+
+ cf = argv[optind + 1];
+
+ if (rrd_fetch_r(argv[optind], cf, start, end, step, ds_cnt, ds_namv, data)
+ != 0)
+ return (-1);
+ return (0);
+}
+
+int rrd_fetch_r(
+ const char *filename, /* name of the rrd */
+ const char *cf, /* which consolidation function ? */
+ time_t *start,
+ time_t *end, /* which time frame do you want ?
+ * will be changed to represent reality */
+ unsigned long *step, /* which stepsize do you want?
+ * will be changed to represent reality */
+ unsigned long *ds_cnt, /* number of data sources in file */
+ char ***ds_namv, /* names of data_sources */
+ rrd_value_t **data)
+{ /* two dimensional array containing the data */
+ enum cf_en cf_idx;
+
+ if ((int) (cf_idx = cf_conv(cf)) == -1) {
+ return -1;
+ }
+
+ return (rrd_fetch_fn
+ (filename, cf_idx, start, end, step, ds_cnt, ds_namv, data));
+}
+
+int rrd_fetch_fn(
+ const char *filename, /* name of the rrd */
+ enum cf_en cf_idx, /* which consolidation function ? */
+ time_t *start,
+ time_t *end, /* which time frame do you want ?
+ * will be changed to represent reality */
+ unsigned long *step, /* which stepsize do you want?
+ * will be changed to represent reality */
+ unsigned long *ds_cnt, /* number of data sources in file */
+ char ***ds_namv, /* names of data_sources */
+ rrd_value_t **data)
+{ /* two dimensional array containing the data */
+ long i, ii;
+ time_t cal_start, cal_end, rra_start_time, rra_end_time;
+ long best_full_rra = 0, best_part_rra = 0, chosen_rra =
+ 0, rra_pointer = 0;
+ long best_full_step_diff = 0, best_part_step_diff =
+ 0, tmp_step_diff = 0, tmp_match = 0, best_match = 0;
+ long full_match, rra_base;
+ long start_offset, end_offset;
+ int first_full = 1;
+ int first_part = 1;
+ rrd_t rrd;
+ rrd_file_t *rrd_file;
+ rrd_value_t *data_ptr;
+ unsigned long rows;
+
+#ifdef DEBUG
+ fprintf(stderr, "Entered rrd_fetch_fn() searching for the best match\n");
+ fprintf(stderr, "Looking for: start %10lu end %10lu step %5lu\n",
+ *start, *end, *step);
+#endif
+
+ rrd_file = rrd_open(filename, &rrd, RRD_READONLY);
+ if (rrd_file == NULL)
+ goto err_free;
+
+ /* when was the really last update of this file ? */
+
+ if (((*ds_namv) =
+ (char **) malloc(rrd.stat_head->ds_cnt * sizeof(char *))) == NULL) {
+ rrd_set_error("malloc fetch ds_namv array");
+ goto err_close;
+ }
+
+ for (i = 0; (unsigned long) i < rrd.stat_head->ds_cnt; i++) {
+ if ((((*ds_namv)[i]) = malloc(sizeof(char) * DS_NAM_SIZE)) == NULL) {
+ rrd_set_error("malloc fetch ds_namv entry");
+ goto err_free_ds_namv;
+ }
+ strncpy((*ds_namv)[i], rrd.ds_def[i].ds_nam, DS_NAM_SIZE - 1);
+ (*ds_namv)[i][DS_NAM_SIZE - 1] = '\0';
+
+ }
+
+ /* find the rra which best matches the requirements */
+ for (i = 0; (unsigned) i < rrd.stat_head->rra_cnt; i++) {
+ if (cf_conv(rrd.rra_def[i].cf_nam) == cf_idx) {
+
+ cal_end = (rrd.live_head->last_up - (rrd.live_head->last_up
+ % (rrd.rra_def[i].pdp_cnt
+ *
+ rrd.stat_head->
+ pdp_step)));
+ cal_start =
+ (cal_end -
+ (rrd.rra_def[i].pdp_cnt * rrd.rra_def[i].row_cnt *
+ rrd.stat_head->pdp_step));
+
+ full_match = *end - *start;
+#ifdef DEBUG
+ fprintf(stderr, "Considering: start %10lu end %10lu step %5lu ",
+ cal_start, cal_end,
+ rrd.stat_head->pdp_step * rrd.rra_def[i].pdp_cnt);
+#endif
+ /* we need step difference in either full or partial case */
+ tmp_step_diff = labs(*step - (rrd.stat_head->pdp_step
+ * rrd.rra_def[i].pdp_cnt));
+ /* best full match */
+ if (cal_start <= *start) {
+ if (first_full || (tmp_step_diff < best_full_step_diff)) {
+ first_full = 0;
+ best_full_step_diff = tmp_step_diff;
+ best_full_rra = i;
+#ifdef DEBUG
+ fprintf(stderr, "best full match so far\n");
+ } else {
+ fprintf(stderr, "full match, not best\n");
+#endif
+ }
+
+ } else {
+ /* best partial match */
+ tmp_match = full_match;
+ if (cal_start > *start)
+ tmp_match -= (cal_start - *start);
+ if (first_part ||
+ (best_match < tmp_match) ||
+ (best_match == tmp_match &&
+ tmp_step_diff < best_part_step_diff)) {
+#ifdef DEBUG
+ fprintf(stderr, "best partial so far\n");
+#endif
+ first_part = 0;
+ best_match = tmp_match;
+ best_part_step_diff = tmp_step_diff;
+ best_part_rra = i;
+ } else {
+#ifdef DEBUG
+ fprintf(stderr, "partial match, not best\n");
+#endif
+ }
+ }
+ }
+ }
+
+ /* lets see how the matching went. */
+ if (first_full == 0)
+ chosen_rra = best_full_rra;
+ else if (first_part == 0)
+ chosen_rra = best_part_rra;
+ else {
+ rrd_set_error
+ ("the RRD does not contain an RRA matching the chosen CF");
+ goto err_free_all_ds_namv;
+ }
+
+ /* set the wish parameters to their real values */
+ *step = rrd.stat_head->pdp_step * rrd.rra_def[chosen_rra].pdp_cnt;
+ *start -= (*start % *step);
+ *end += (*step - *end % *step);
+ rows = (*end - *start) / *step + 1;
+
+#ifdef DEBUG
+ fprintf(stderr,
+ "We found: start %10lu end %10lu step %5lu rows %lu\n",
+ *start, *end, *step, rows);
+#endif
+
+/* Start and end are now multiples of the step size. The amount of
+** steps we want is (end-start)/step and *not* an extra one.
+** Reasoning: if step is s and we want to graph from t to t+s,
+** we need exactly ((t+s)-t)/s rows. The row to collect from the
+** database is the one with time stamp (t+s) which means t to t+s.
+*/
+ *ds_cnt = rrd.stat_head->ds_cnt;
+ if (((*data) = malloc(*ds_cnt * rows * sizeof(rrd_value_t))) == NULL) {
+ rrd_set_error("malloc fetch data area");
+ goto err_free_all_ds_namv;
+ }
+
+ data_ptr = (*data);
+
+ /* find base address of rra */
+ rra_base = rrd_file->header_len;
+ for (i = 0; i < chosen_rra; i++)
+ rra_base += (*ds_cnt * rrd.rra_def[i].row_cnt * sizeof(rrd_value_t));
+
+ /* find start and end offset */
+ rra_end_time = (rrd.live_head->last_up
+ - (rrd.live_head->last_up % *step));
+ rra_start_time = (rra_end_time
+ - (*step * (rrd.rra_def[chosen_rra].row_cnt - 1)));
+ /* here's an error by one if we don't be careful */
+ start_offset = (long) (*start + *step - rra_start_time) / (long) *step;
+ end_offset = (long) (rra_end_time - *end) / (long) *step;
+#ifdef DEBUG
+ fprintf(stderr,
+ "rra_start %lu, rra_end %lu, start_off %li, end_off %li\n",
+ rra_start_time, rra_end_time, start_offset, end_offset);
+#endif
+
+ /* fill the gap at the start if needs be */
+
+ if (start_offset <= 0)
+ rra_pointer = rrd.rra_ptr[chosen_rra].cur_row + 1;
+ else
+ rra_pointer = rrd.rra_ptr[chosen_rra].cur_row + 1 + start_offset;
+
+ if (rrd_seek(rrd_file, (rra_base + (rra_pointer * (*ds_cnt)
+ * sizeof(rrd_value_t))),
+ SEEK_SET) != 0) {
+ rrd_set_error("seek error in RRA");
+ goto err_free_data;
+ }
+#ifdef DEBUG
+ fprintf(stderr, "First Seek: rra_base %lu rra_pointer %lu\n",
+ rra_base, rra_pointer);
+#endif
+ /* step trough the array */
+
+ for (i = start_offset;
+ i < (signed) rrd.rra_def[chosen_rra].row_cnt - end_offset; i++) {
+ /* no valid data yet */
+ if (i < 0) {
+#ifdef DEBUG
+ fprintf(stderr, "pre fetch %li -- ", i);
+#endif
+ for (ii = 0; (unsigned) ii < *ds_cnt; ii++) {
+ *(data_ptr++) = DNAN;
+#ifdef DEBUG
+ fprintf(stderr, "%10.2f ", *(data_ptr - 1));
+#endif
+ }
+ }
+ /* past the valid data area */
+ else if (i >= (signed) rrd.rra_def[chosen_rra].row_cnt) {
+#ifdef DEBUG
+ fprintf(stderr, "past fetch %li -- ", i);
+#endif
+ for (ii = 0; (unsigned) ii < *ds_cnt; ii++) {
+ *(data_ptr++) = DNAN;
+#ifdef DEBUG
+ fprintf(stderr, "%10.2f ", *(data_ptr - 1));
+#endif
+ }
+ } else {
+ /* OK we are inside the valid area but the pointer has to
+ * be wrapped*/
+ if (rra_pointer >= (signed) rrd.rra_def[chosen_rra].row_cnt) {
+ rra_pointer -= rrd.rra_def[chosen_rra].row_cnt;
+ if (rrd_seek(rrd_file, (rra_base + rra_pointer * (*ds_cnt)
+ * sizeof(rrd_value_t)),
+ SEEK_SET) != 0) {
+ rrd_set_error("wrap seek in RRA did fail");
+ goto err_free_data;
+ }
+#ifdef DEBUG
+ fprintf(stderr, "wrap seek ...\n");
+#endif
+ }
+
+ if (rrd_read(rrd_file, data_ptr, sizeof(rrd_value_t) * (*ds_cnt))
+ != (ssize_t) (sizeof(rrd_value_t) * (*ds_cnt))) {
+ rrd_set_error("fetching cdp from rra");
+ goto err_free_data;
+ }
+#ifdef DEBUG
+ fprintf(stderr, "post fetch %li -- ", i);
+ for (ii = 0; ii < *ds_cnt; ii++)
+ fprintf(stderr, "%10.2f ", *(data_ptr + ii));
+#endif
+ data_ptr += *ds_cnt;
+ rra_pointer++;
+ }
+#ifdef DEBUG
+ fprintf(stderr, "\n");
+#endif
+
+ }
+
+ rrd_close(rrd_file);
+ return (0);
+ err_free_data:
+ free(*data);
+ *data = NULL;
+ err_free_all_ds_namv:
+ for (i = 0; (unsigned long) i < rrd.stat_head->ds_cnt; ++i)
+ free((*ds_namv)[i]);
+ err_free_ds_namv:
+ free(*ds_namv);
+ err_close:
+ rrd_close(rrd_file);
+ err_free:
+ rrd_free(&rrd);
+ return (-1);
+}
diff --git a/program/src/rrd_first.c b/program/src/rrd_first.c
--- /dev/null
+++ b/program/src/rrd_first.c
@@ -0,0 +1,96 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ *****************************************************************************
+ * rrd_first Return
+ *****************************************************************************
+ * Initial version by Burton Strauss, ntopSupport.com - 3/2005
+ *****************************************************************************/
+
+#include "rrd_tool.h"
+
+
+time_t rrd_first(
+ int argc,
+ char **argv)
+{
+ int target_rraindex = 0;
+ char *endptr;
+ struct option long_options[] = {
+ {"rraindex", required_argument, 0, 129},
+ {0, 0, 0, 0}
+ };
+
+ optind = 0;
+ opterr = 0; /* initialize getopt */
+
+ while (1) {
+ int option_index = 0;
+ int opt;
+
+ opt = getopt_long(argc, argv, "", long_options, &option_index);
+
+ if (opt == EOF)
+ break;
+
+ switch (opt) {
+ case 129:
+ target_rraindex = strtol(optarg, &endptr, 0);
+ if (target_rraindex < 0) {
+ rrd_set_error("invalid rraindex number");
+ return (-1);
+ }
+ break;
+ default:
+ rrd_set_error("usage rrdtool %s [--rraindex number] file.rrd",
+ argv[0]);
+ return (-1);
+ }
+ }
+
+ if (optind >= argc) {
+ rrd_set_error("not enough arguments");
+ return -1;
+ }
+
+ return (rrd_first_r(argv[optind], target_rraindex));
+}
+
+
+time_t rrd_first_r(
+ const char *filename,
+ const int rraindex)
+{
+ off_t rra_start, timer;
+ time_t then = -1;
+ rrd_t rrd;
+ rrd_file_t *rrd_file;
+
+ rrd_file = rrd_open(filename, &rrd, RRD_READONLY);
+ if (rrd_file == NULL) {
+ goto err_free;
+ }
+
+ if ((rraindex < 0) || (rraindex >= (int) rrd.stat_head->rra_cnt)) {
+ rrd_set_error("invalid rraindex number");
+ goto err_close;
+ }
+
+ rra_start = rrd_file->header_len;
+ rrd_seek(rrd_file,
+ (rra_start +
+ (rrd.rra_ptr[rraindex].cur_row + 1) *
+ rrd.stat_head->ds_cnt * sizeof(rrd_value_t)), SEEK_SET);
+ timer = -(rrd.rra_def[rraindex].row_cnt - 1);
+ if (rrd.rra_ptr[rraindex].cur_row + 1 > rrd.rra_def[rraindex].row_cnt) {
+ rrd_seek(rrd_file, rra_start, SEEK_SET);
+ }
+ then = (rrd.live_head->last_up -
+ rrd.live_head->last_up %
+ (rrd.rra_def[rraindex].pdp_cnt * rrd.stat_head->pdp_step)) +
+ (timer * rrd.rra_def[rraindex].pdp_cnt * rrd.stat_head->pdp_step);
+ err_close:
+ rrd_close(rrd_file);
+ err_free:
+ rrd_free(&rrd);
+ return (then);
+}
diff --git a/program/src/rrd_format.c b/program/src/rrd_format.c
--- /dev/null
+++ b/program/src/rrd_format.c
@@ -0,0 +1,100 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ *****************************************************************************
+ * rrd_format.c RRD Database Format helper functions
+ *****************************************************************************
+ * $Id$
+ * $Log$
+ * Revision 1.5 2004/05/18 18:53:03 oetiker
+ * big spell checking patch -- slif@bellsouth.net
+ *
+ * Revision 1.4 2003/02/13 07:05:27 oetiker
+ * Find attached the patch I promised to send to you. Please note that there
+ * are three new source files (src/rrd_is_thread_safe.h, src/rrd_thread_safe.c
+ * and src/rrd_not_thread_safe.c) and the introduction of librrd_th. This
+ * library is identical to librrd, but it contains support code for per-thread
+ * global variables currently used for error information only. This is similar
+ * to how errno per-thread variables are implemented. librrd_th must be linked
+ * alongside of libpthred
+ *
+ * There is also a new file "THREADS", holding some documentation.
+ *
+ * -- Peter Stamfest <peter@stamfest.at>
+ *
+ * Revision 1.3 2002/02/01 20:34:49 oetiker
+ * fixed version number and date/time
+ *
+ * Revision 1.2 2001/03/10 23:54:39 oetiker
+ * Support for COMPUTE data sources (CDEF data sources). Removes the RPN
+ * parser and calculator from rrd_graph and puts then in a new file,
+ * rrd_rpncalc.c. Changes to core files rrd_create and rrd_update. Some
+ * clean-up of aberrant behavior stuff, including a bug fix.
+ * Documentation update (rrdcreate.pod, rrdupdate.pod). Change xml format.
+ * -- Jake Brutlag <jakeb@corp.webtv.net>
+ *
+ * Revision 1.1.1.1 2001/02/25 22:25:05 oetiker
+ * checkin
+ *
+ * Revision 1.3 1998/03/08 12:35:11 oetiker
+ * checkpointing things because the current setup seems to work
+ * according to the things said in the manpages
+ *
+ * Revision 1.2 1998/02/26 22:58:22 oetiker
+ * fixed define
+ *
+ * Revision 1.1 1998/02/21 16:14:41 oetiker
+ * Initial revision
+ *
+ *
+ *****************************************************************************/
+#include "rrd_tool.h"
+
+#define converter(VV,VVV) \
+ if (strcmp(#VV, string) == 0) return VVV;
+
+/* conversion functions to allow symbolic entry of enumerations */
+enum dst_en dst_conv(
+ char *string)
+{
+ converter(COUNTER, DST_COUNTER)
+ converter(ABSOLUTE, DST_ABSOLUTE)
+ converter(GAUGE, DST_GAUGE)
+ converter(DERIVE, DST_DERIVE)
+ converter(COMPUTE, DST_CDEF)
+ rrd_set_error("unknown data acquisition function '%s'", string);
+ return (-1);
+}
+
+
+enum cf_en cf_conv(
+ const char *string)
+{
+
+ converter(AVERAGE, CF_AVERAGE)
+ converter(MIN, CF_MINIMUM)
+ converter(MAX, CF_MAXIMUM)
+ converter(LAST, CF_LAST)
+ converter(HWPREDICT, CF_HWPREDICT)
+ converter(MHWPREDICT, CF_MHWPREDICT)
+ converter(DEVPREDICT, CF_DEVPREDICT)
+ converter(SEASONAL, CF_SEASONAL)
+ converter(DEVSEASONAL, CF_DEVSEASONAL)
+ converter(FAILURES, CF_FAILURES)
+ rrd_set_error("unknown consolidation function '%s'", string);
+ return (-1);
+}
+
+#undef converter
+
+long ds_match(
+ rrd_t *rrd,
+ char *ds_nam)
+{
+ unsigned long i;
+
+ for (i = 0; i < rrd->stat_head->ds_cnt; i++)
+ if ((strcmp(ds_nam, rrd->ds_def[i].ds_nam)) == 0)
+ return i;
+ rrd_set_error("unknown data source name '%s'", ds_nam);
+ return -1;
+}
diff --git a/program/src/rrd_format.h b/program/src/rrd_format.h
--- /dev/null
+++ b/program/src/rrd_format.h
@@ -0,0 +1,428 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ *****************************************************************************
+ * rrd_format.h RRD Database Format header
+ *****************************************************************************/
+
+#ifndef _RRD_FORMAT_H
+#define _RRD_FORMAT_H
+
+/*
+ * _RRD_TOOL_H
+ * We're building RRDTool itself.
+ *
+ * RRD_EXPORT_DEPRECATED
+ * User is requesting internal function which need this struct. They have
+ * been told that this will change and have agreed to adapt their programs.
+ */
+#if !defined(_RRD_TOOL_H) && !defined(RRD_EXPORT_DEPRECATED)
+# error "Do not include rrd_format.h directly. Include rrd.h instead!"
+#endif
+
+#include "rrd.h"
+
+/*****************************************************************************
+ * put this in your /usr/lib/magic file (/etc/magic on HPUX)
+ *
+ * # rrd database format
+ * 0 string RRD\0 rrd file
+ * >5 string >\0 version '%s'
+ *
+ *****************************************************************************/
+
+#define RRD_COOKIE "RRD"
+/* #define RRD_VERSION "0002" */
+/* changed because microsecond precision requires another field */
+#define RRD_VERSION "0004"
+#define RRD_VERSION3 "0003"
+#define FLOAT_COOKIE 8.642135E130
+
+typedef union unival {
+ unsigned long u_cnt;
+ rrd_value_t u_val;
+} unival;
+
+
+/****************************************************************************
+ * The RRD Database Structure
+ * ---------------------------
+ *
+ * In oder to properly describe the database structure lets define a few
+ * new words:
+ *
+ * ds - Data Source (ds) providing input to the database. A Data Source (ds)
+ * can be a traffic counter, a temperature, the number of users logged
+ * into a system. The rrd database format can handle the input of
+ * several Data Sources (ds) in a singe database.
+ *
+ * dst - Data Source Type (dst). The Data Source Type (dst) defines the rules
+ * applied to Build Primary Data Points from the input provided by the
+ * data sources (ds).
+ *
+ * pdp - Primary Data Point (pdp). After the database has accepted the
+ * input from the data sources (ds). It starts building Primary
+ * Data Points (pdp) from the data. Primary Data Points (pdp)
+ * are evenly spaced along the time axis (pdp_step). The values
+ * of the Primary Data Points are calculated from the values of
+ * the data source (ds) and the exact time these values were
+ * provided by the data source (ds).
+ *
+ * pdp_st - PDP Start (pdp_st). The moments (pdp_st) in time where
+ * these steps occur are defined by the moments where the
+ * number of seconds since 1970-jan-1 modulo pdp_step equals
+ * zero (pdp_st).
+ *
+ * cf - Consolidation Function (cf). An arbitrary Consolidation Function (cf)
+ * (averaging, min, max) is applied to the primary data points (pdp) to
+ * calculate the consolidated data point.
+ *
+ * cdp - Consolidated Data Point (cdp) is the long term storage format for data
+ * in the rrd database. Consolidated Data Points represent one or
+ * several primary data points collected along the time axis. The
+ * Consolidated Data Points (cdp) are stored in Round Robin Archives
+ * (rra).
+ *
+ * rra - Round Robin Archive (rra). This is the place where the
+ * consolidated data points (cdp) get stored. The data is
+ * organized in rows (row) and columns (col). The Round Robin
+ * Archive got its name from the method data is stored in
+ * there. An RRD database can contain several Round Robin
+ * Archives. Each Round Robin Archive can have a different row
+ * spacing along the time axis (pdp_cnt) and a different
+ * consolidation function (cf) used to build its consolidated
+ * data points (cdp).
+ *
+ * rra_st - RRA Start (rra_st). The moments (rra_st) in time where
+ * Consolidated Data Points (cdp) are added to an rra are
+ * defined by the moments where the number of seconds since
+ * 1970-jan-1 modulo pdp_cnt*pdp_step equals zero (rra_st).
+ *
+ * row - Row (row). A row represent all consolidated data points (cdp)
+ * in a round robin archive who are of the same age.
+ *
+ * col - Column (col). A column (col) represent all consolidated
+ * data points (cdp) in a round robin archive (rra) who
+ * originated from the same data source (ds).
+ *
+ */
+
+/****************************************************************************
+ * POS 1: stat_head_t static header of the database
+ ****************************************************************************/
+
+typedef struct stat_head_t {
+
+ /* Data Base Identification Section ** */
+ char cookie[4]; /* RRD */
+ char version[5]; /* version of the format */
+ double float_cookie; /* is it the correct double
+ * representation ? */
+
+ /* Data Base Structure Definition **** */
+ unsigned long ds_cnt; /* how many different ds provide
+ * input to the rrd */
+ unsigned long rra_cnt; /* how many rras will be maintained
+ * in the rrd */
+ unsigned long pdp_step; /* pdp interval in seconds */
+
+ unival par[10]; /* global parameters ... unused
+ at the moment */
+} stat_head_t;
+
+
+/****************************************************************************
+ * POS 2: ds_def_t (* ds_cnt) Data Source definitions
+ ****************************************************************************/
+
+enum dst_en { DST_COUNTER = 0, /* data source types available */
+ DST_ABSOLUTE,
+ DST_GAUGE,
+ DST_DERIVE,
+ DST_CDEF
+};
+
+enum ds_param_en { DS_mrhb_cnt = 0, /* minimum required heartbeat. A
+ * data source must provide input at
+ * least every ds_mrhb seconds,
+ * otherwise it is regarded dead and
+ * will be set to UNKNOWN */
+ DS_min_val, /* the processed input of a ds must */
+ DS_max_val, /* be between max_val and min_val
+ * both can be set to UNKNOWN if you
+ * do not care. Data outside the limits
+ * set to UNKNOWN */
+ DS_cdef = DS_mrhb_cnt
+}; /* pointer to encoded rpn
+ * expression only applies to DST_CDEF */
+
+/* The magic number here is one less than DS_NAM_SIZE */
+#define DS_NAM_FMT "%19[a-zA-Z0-9_-]"
+#define DS_NAM_SIZE 20
+
+#define DST_FMT "%19[A-Z]"
+#define DST_SIZE 20
+
+typedef struct ds_def_t {
+ char ds_nam[DS_NAM_SIZE]; /* Name of the data source (null terminated) */
+ char dst[DST_SIZE]; /* Type of data source (null terminated) */
+ unival par[10]; /* index of this array see ds_param_en */
+} ds_def_t;
+
+/****************************************************************************
+ * POS 3: rra_def_t ( * rra_cnt) one for each store to be maintained
+ ****************************************************************************/
+enum cf_en { CF_AVERAGE = 0, /* data consolidation functions */
+ CF_MINIMUM,
+ CF_MAXIMUM,
+ CF_LAST,
+ CF_HWPREDICT,
+ /* An array of predictions using the seasonal
+ * Holt-Winters algorithm. Requires an RRA of type
+ * CF_SEASONAL for this data source. */
+ CF_SEASONAL,
+ /* An array of seasonal effects. Requires an RRA of
+ * type CF_HWPREDICT for this data source. */
+ CF_DEVPREDICT,
+ /* An array of deviation predictions based upon
+ * smoothed seasonal deviations. Requires an RRA of
+ * type CF_DEVSEASONAL for this data source. */
+ CF_DEVSEASONAL,
+ /* An array of smoothed seasonal deviations. Requires
+ * an RRA of type CF_HWPREDICT for this data source.
+ * */
+ CF_FAILURES,
+ /* HWPREDICT that follows a moving baseline */
+ CF_MHWPREDICT
+ /* new entries must come last !!! */
+};
+
+ /* A binary array of failure indicators: 1 indicates
+ * that the number of violations in the prescribed
+ * window exceeded the prescribed threshold. */
+
+#define MAX_RRA_PAR_EN 10
+enum rra_par_en { RRA_cdp_xff_val = 0, /* what part of the consolidated
+ * datapoint must be known, to produce a
+ * valid entry in the rra */
+ /* CF_HWPREDICT: */
+ RRA_hw_alpha = 1,
+ /* exponential smoothing parameter for the intercept in
+ * the Holt-Winters prediction algorithm. */
+ RRA_hw_beta = 2,
+ /* exponential smoothing parameter for the slope in
+ * the Holt-Winters prediction algorithm. */
+
+ RRA_dependent_rra_idx = 3,
+ /* For CF_HWPREDICT: index of the RRA with the seasonal
+ * effects of the Holt-Winters algorithm (of type
+ * CF_SEASONAL).
+ * For CF_DEVPREDICT: index of the RRA with the seasonal
+ * deviation predictions (of type CF_DEVSEASONAL).
+ * For CF_SEASONAL: index of the RRA with the Holt-Winters
+ * intercept and slope coefficient (of type CF_HWPREDICT).
+ * For CF_DEVSEASONAL: index of the RRA with the
+ * Holt-Winters prediction (of type CF_HWPREDICT).
+ * For CF_FAILURES: index of the CF_DEVSEASONAL array.
+ * */
+
+ /* CF_SEASONAL and CF_DEVSEASONAL: */
+ RRA_seasonal_gamma = 1,
+ /* exponential smoothing parameter for seasonal effects. */
+
+ RRA_seasonal_smoothing_window = 2,
+ /* fraction of the season to include in the running average
+ * smoother */
+
+ /* RRA_dependent_rra_idx = 3, */
+
+ RRA_seasonal_smooth_idx = 4,
+ /* an integer between 0 and row_count - 1 which
+ * is index in the seasonal cycle for applying
+ * the period smoother. */
+
+ /* CF_FAILURES: */
+ RRA_delta_pos = 1, /* confidence bound scaling parameters */
+ RRA_delta_neg = 2,
+ /* RRA_dependent_rra_idx = 3, */
+ RRA_window_len = 4,
+ RRA_failure_threshold = 5,
+ /* For CF_FAILURES, number of violations within the last
+ * window required to mark a failure. */
+};
+
+ /* For CF_FAILURES, the length of the window for measuring
+ * failures. */
+
+#define CF_NAM_FMT "%19[A-Z]"
+#define CF_NAM_SIZE 20
+
+typedef struct rra_def_t {
+ char cf_nam[CF_NAM_SIZE]; /* consolidation function (null term) */
+ unsigned long row_cnt; /* number of entries in the store */
+ unsigned long pdp_cnt; /* how many primary data points are
+ * required for a consolidated data
+ * point?*/
+ unival par[MAX_RRA_PAR_EN]; /* index see rra_param_en */
+
+} rra_def_t;
+
+
+/****************************************************************************
+ ****************************************************************************
+ ****************************************************************************
+ * LIVE PART OF THE HEADER. THIS WILL BE WRITTEN ON EVERY UPDATE *
+ ****************************************************************************
+ ****************************************************************************
+ ****************************************************************************/
+/****************************************************************************
+ * POS 4: live_head_t
+ ****************************************************************************/
+
+typedef struct live_head_t {
+ time_t last_up; /* when was rrd last updated */
+ long last_up_usec; /* micro seconds part of the
+ update timestamp. Always >= 0 */
+} live_head_t;
+
+
+/****************************************************************************
+ * POS 5: pdp_prep_t (* ds_cnt) here we prepare the pdps
+ ****************************************************************************/
+#define LAST_DS_LEN 30 /* DO NOT CHANGE THIS ... */
+
+enum pdp_par_en { PDP_unkn_sec_cnt = 0, /* how many seconds of the current
+ * pdp value is unknown data? */
+
+ PDP_val
+}; /* current value of the pdp.
+ this depends on dst */
+
+typedef struct pdp_prep_t {
+ char last_ds[LAST_DS_LEN]; /* the last reading from the data
+ * source. this is stored in ASCII
+ * to cater for very large counters
+ * we might encounter in connection
+ * with SNMP. */
+ unival scratch[10]; /* contents according to pdp_par_en */
+} pdp_prep_t;
+
+/* data is passed from pdp to cdp when seconds since epoch modulo pdp_step == 0
+ obviously the updates do not occur at these times only. Especially does the
+ format allow for updates to occur at different times for each data source.
+ The rules which makes this work is as follows:
+
+ * DS updates may only occur at ever increasing points in time
+ * When any DS update arrives after a cdp update time, the *previous*
+ update cycle gets executed. All pdps are transfered to cdps and the
+ cdps feed the rras where necessary. Only then the new DS value
+ is loaded into the PDP. */
+
+
+/****************************************************************************
+ * POS 6: cdp_prep_t (* rra_cnt * ds_cnt ) data prep area for cdp values
+ ****************************************************************************/
+#define MAX_CDP_PAR_EN 10
+#define MAX_CDP_FAILURES_IDX 8
+/* max CDP scratch entries avail to record violations for a FAILURES RRA */
+#define MAX_FAILURES_WINDOW_LEN 28
+enum cdp_par_en { CDP_val = 0,
+ /* the base_interval is always an
+ * average */
+ CDP_unkn_pdp_cnt,
+ /* how many unknown pdp were
+ * integrated. This and the cdp_xff
+ * will decide if this is going to
+ * be a UNKNOWN or a valid value */
+ CDP_hw_intercept,
+ /* Current intercept coefficient for the Holt-Winters
+ * prediction algorithm. */
+ CDP_hw_last_intercept,
+ /* Last iteration intercept coefficient for the Holt-Winters
+ * prediction algorihtm. */
+ CDP_hw_slope,
+ /* Current slope coefficient for the Holt-Winters
+ * prediction algorithm. */
+ CDP_hw_last_slope,
+ /* Last iteration slope coeffient. */
+ CDP_null_count,
+ /* Number of sequential Unknown (DNAN) values + 1 preceding
+ * the current prediction.
+ * */
+ CDP_last_null_count,
+ /* Last iteration count of Unknown (DNAN) values. */
+ CDP_primary_val = 8,
+ /* optimization for bulk updates: the value of the first CDP
+ * value to be written in the bulk update. */
+ CDP_secondary_val = 9,
+ /* optimization for bulk updates: the value of subsequent
+ * CDP values to be written in the bulk update. */
+ CDP_hw_seasonal = CDP_hw_intercept,
+ /* Current seasonal coefficient for the Holt-Winters
+ * prediction algorithm. This is stored in CDP prep to avoid
+ * redundant seek operations. */
+ CDP_hw_last_seasonal = CDP_hw_last_intercept,
+ /* Last iteration seasonal coeffient. */
+ CDP_seasonal_deviation = CDP_hw_intercept,
+ CDP_last_seasonal_deviation = CDP_hw_last_intercept,
+ CDP_init_seasonal = CDP_null_count
+};
+
+ /* init_seasonal is a flag which when > 0, forces smoothing updates
+ * to occur when rra_ptr.cur_row == 0 */
+
+typedef struct cdp_prep_t {
+ unival scratch[MAX_CDP_PAR_EN];
+ /* contents according to cdp_par_en *
+ * init state should be NAN */
+
+} cdp_prep_t;
+
+/****************************************************************************
+ * POS 7: rra_ptr_t (* rra_cnt) pointers to the current row in each rra
+ ****************************************************************************/
+
+typedef struct rra_ptr_t {
+ unsigned long cur_row; /* current row in the rra */
+} rra_ptr_t;
+
+
+/****************************************************************************
+ ****************************************************************************
+ * One single struct to hold all the others. For convenience.
+ ****************************************************************************
+ ****************************************************************************/
+typedef struct rrd_t {
+ stat_head_t *stat_head; /* the static header */
+ ds_def_t *ds_def; /* list of data source definitions */
+ rra_def_t *rra_def; /* list of round robin archive def */
+ live_head_t *live_head; /* rrd v >= 3 last_up with us */
+ time_t *legacy_last_up; /* rrd v < 3 last_up time */
+ pdp_prep_t *pdp_prep; /* pdp data prep area */
+ cdp_prep_t *cdp_prep; /* cdp prep area */
+ rra_ptr_t *rra_ptr; /* list of rra pointers */
+ rrd_value_t *rrd_value; /* list of rrd values */
+} rrd_t;
+
+/****************************************************************************
+ ****************************************************************************
+ * AFTER the header section we have the DATA STORAGE AREA it is made up from
+ * Consolidated Data Points organized in Round Robin Archives.
+ ****************************************************************************
+ ****************************************************************************
+
+ *RRA 0
+ (0,0) .................... ( ds_cnt -1 , 0)
+ .
+ .
+ .
+ (0, row_cnt -1) ... (ds_cnt -1, row_cnt -1)
+
+ *RRA 1
+ *RRA 2
+
+ *RRA rra_cnt -1
+
+ ****************************************************************************/
+
+
+#endif
diff --git a/program/src/rrd_getopt.c b/program/src/rrd_getopt.c
--- /dev/null
+++ b/program/src/rrd_getopt.c
@@ -0,0 +1,952 @@
+/* Getopt for GNU.
+ NOTE: getopt is now part of the C library, so if you don't know what
+ "Keep this file name-space clean" means, talk to roland@gnu.ai.mit.edu
+ before changing it!
+
+ Copyright (C) 1987, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97
+ Free Software Foundation, Inc.
+
+ This file is part of the GNU C Library. Its master source is NOT part of
+ the C library, however. The master source lives in /gd/gnu/lib.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the GNU C Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA. */
+\f
+/* This tells Alpha OSF/1 not to define a getopt prototype in <stdio.h>.
+ Ditto for AIX 3.2 and <stdlib.h>. */
+#ifndef _NO_PROTO
+#define _NO_PROTO
+#endif
+
+#if !defined (__STDC__) || !__STDC__
+/* This is a separate conditional since some stdc systems
+ reject `defined (const)'. */
+#ifndef const
+#define const
+#endif
+#endif
+
+
+#ifdef HAVE_CONFIG_H
+#include "../rrd_config.h"
+#endif
+
+#include "rrd_i18n.h"
+
+
+#include <stdio.h>
+
+/* Comment out all this code if we are using the GNU C Library, and are not
+ actually compiling the library itself. This code is part of the GNU C
+ Library, but also included in many other GNU distributions. Compiling
+ and linking in this code is a waste when using the GNU C library
+ (especially if it is a shared library). Rather than having every GNU
+ program understand `configure --with-gnu-libc' and omit the object files,
+ it is simpler to just do this in the source for each such file. */
+
+#define GETOPT_INTERFACE_VERSION 2
+#if !defined (_LIBC) && defined (__GLIBC__) && __GLIBC__ >= 2
+#include <gnu-versions.h>
+#if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION
+#define ELIDE_CODE
+#endif
+#endif
+
+#ifndef ELIDE_CODE
+
+
+/* This needs to come after some library #include
+ to get __GNU_LIBRARY__ defined. */
+#ifdef __GNU_LIBRARY__
+/* Don't include stdlib.h for non-GNU C libraries because some of them
+ contain conflicting prototypes for getopt. */
+#include <stdlib.h>
+#include <unistd.h>
+#endif /* GNU C library. */
+
+#ifdef VMS
+#include <unixlib.h>
+#if HAVE_STRING_H - 0
+#include <string.h>
+#endif
+#endif
+
+#if defined (_WIN32) && !defined (__CYGWIN32__)
+/* It's not Unix, really. See? Capital letters. */
+#include <windows.h>
+#define getpid() GetCurrentProcessId()
+#endif
+
+/* This version of `getopt' appears to the caller like standard Unix `getopt'
+ but it behaves differently for the user, since it allows the user
+ to intersperse the options with the other arguments.
+
+ As `getopt' works, it permutes the elements of ARGV so that,
+ when it is done, all the options precede everything else. Thus
+ all application programs are extended to handle flexible argument order.
+
+ Setting the environment variable POSIXLY_CORRECT disables permutation.
+ Then the behavior is completely standard.
+
+ GNU application programs can use a third alternative mode in which
+ they can distinguish the relative order of options and other arguments. */
+
+#include "rrd_getopt.h"
+
+/* For communication from `getopt' to the caller.
+ When `getopt' finds an option that takes an argument,
+ the argument value is returned here.
+ Also, when `ordering' is RETURN_IN_ORDER,
+ each non-option ARGV-element is returned here. */
+
+char *optarg = NULL;
+
+/* Index in ARGV of the next element to be scanned.
+ This is used for communication to and from the caller
+ and for communication between successive calls to `getopt'.
+
+ On entry to `getopt', zero means this is the first call; initialize.
+
+ When `getopt' returns -1, this is the index of the first of the
+ non-option elements that the caller should itself scan.
+
+ Otherwise, `optind' communicates from one call to the next
+ how much of ARGV has been scanned so far. */
+
+/* 1003.2 says this must be 1 before any call. */
+int optind = 1;
+
+/* Formerly, initialization of getopt depended on optind==0, which
+ causes problems with re-calling getopt as programs generally don't
+ know that. */
+
+int __getopt_initialized = 0;
+
+/* The next char to be scanned in the option-element
+ in which the last option character we returned was found.
+ This allows us to pick up the scan where we left off.
+
+ If this is zero, or a null string, it means resume the scan
+ by advancing to the next ARGV-element. */
+
+static char *nextchar;
+
+/* Callers store zero here to inhibit the error message
+ for unrecognized options. */
+
+int opterr = 1;
+
+/* Set to an option character which was unrecognized.
+ This must be initialized on some systems to avoid linking in the
+ system's own getopt implementation. */
+
+int optopt = '?';
+
+/* Describe how to deal with options that follow non-option ARGV-elements.
+
+ If the caller did not specify anything,
+ the default is REQUIRE_ORDER if the environment variable
+ POSIXLY_CORRECT is defined, PERMUTE otherwise.
+
+ REQUIRE_ORDER means don't recognize them as options;
+ stop option processing when the first non-option is seen.
+ This is what Unix does.
+ This mode of operation is selected by either setting the environment
+ variable POSIXLY_CORRECT, or using `+' as the first character
+ of the list of option characters.
+
+ PERMUTE is the default. We permute the contents of ARGV as we scan,
+ so that eventually all the non-options are at the end. This allows options
+ to be given in any order, even with programs that were not written to
+ expect this.
+
+ RETURN_IN_ORDER is an option available to programs that were written
+ to expect options and other ARGV-elements in any order and that care about
+ the ordering of the two. We describe each non-option ARGV-element
+ as if it were the argument of an option with character code 1.
+ Using `-' as the first character of the list of option characters
+ selects this mode of operation.
+
+ The special argument `--' forces an end of option-scanning regardless
+ of the value of `ordering'. In the case of RETURN_IN_ORDER, only
+ `--' can cause `getopt' to return -1 with `optind' != ARGC. */
+
+static enum {
+ REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER
+} ordering;
+
+/* Value of POSIXLY_CORRECT environment variable. */
+static char *posixly_correct;
+\f
+/* we must include string as there are warnings without it ... */
+#include <string.h>
+
+#ifdef __GNU_LIBRARY__
+/* We want to avoid inclusion of string.h with non-GNU libraries
+ because there are many ways it can cause trouble.
+ On some systems, it contains special magic macros that don't work
+ in GCC. */
+#define my_index strchr
+#else
+
+/* Avoid depending on library functions or files
+ whose names are inconsistent. */
+
+char *getenv(
+ );
+
+static char *my_index(
+ str,
+ chr)
+ const char *str;
+ int chr;
+{
+ while (*str) {
+ if (*str == chr)
+ return (char *) str;
+ str++;
+ }
+ return 0;
+}
+
+/* If using GCC, we can safely declare strlen this way.
+ If not using GCC, it is ok not to declare it. */
+#ifdef __GNUC__
+/* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h.
+ That was relevant to code that was here before. */
+#if !defined (__STDC__) || !__STDC__
+/* gcc with -traditional declares the built-in strlen to return int,
+ and has done so at least since version 2.4.5. -- rms. */
+extern int strlen(
+ const char *);
+#endif /* not __STDC__ */
+#endif /* __GNUC__ */
+
+#endif /* not __GNU_LIBRARY__ */
+\f
+/* Handle permutation of arguments. */
+
+/* Describe the part of ARGV that contains non-options that have
+ been skipped. `first_nonopt' is the index in ARGV of the first of them;
+ `last_nonopt' is the index after the last of them. */
+
+static int first_nonopt;
+static int last_nonopt;
+
+#ifdef _LIBC
+/* Bash 2.0 gives us an environment variable containing flags
+ indicating ARGV elements that should not be considered arguments. */
+
+static const char *nonoption_flags;
+static int nonoption_flags_len;
+
+static int original_argc;
+static char *const *original_argv;
+
+/* Make sure the environment variable bash 2.0 puts in the environment
+ is valid for the getopt call we must make sure that the ARGV passed
+ to getopt is that one passed to the process. */
+static void store_args(
+ int argc,
+ char *const *argv) __attribute__ ((unused));
+static void store_args(
+ int argc,
+ char *const *argv)
+{
+ /* XXX This is no good solution. We should rather copy the args so
+ that we can compare them later. But we must not use malloc(3). */
+ original_argc = argc;
+ original_argv = argv;
+}
+
+text_set_element(__libc_subinit, store_args);
+#endif
+
+/* Exchange two adjacent subsequences of ARGV.
+ One subsequence is elements [first_nonopt,last_nonopt)
+ which contains all the non-options that have been skipped so far.
+ The other is elements [last_nonopt,optind), which contains all
+ the options processed since those non-options were skipped.
+
+ `first_nonopt' and `last_nonopt' are relocated so that they describe
+ the new indices of the non-options in ARGV after they are moved. */
+
+#if defined (__STDC__) && __STDC__
+static void exchange(
+ char **);
+#endif
+
+static void exchange(
+ argv)
+ char **argv;
+{
+ int bottom = first_nonopt;
+ int middle = last_nonopt;
+ int top = optind;
+ char *tem;
+
+ /* Exchange the shorter segment with the far end of the longer segment.
+ That puts the shorter segment into the right place.
+ It leaves the longer segment in the right place overall,
+ but it consists of two parts that need to be swapped next. */
+
+ while (top > middle && middle > bottom) {
+ if (top - middle > middle - bottom) {
+ /* Bottom segment is the short one. */
+ int len = middle - bottom;
+ register int i;
+
+ /* Swap it with the top part of the top segment. */
+ for (i = 0; i < len; i++) {
+ tem = argv[bottom + i];
+ argv[bottom + i] = argv[top - (middle - bottom) + i];
+ argv[top - (middle - bottom) + i] = tem;
+ }
+ /* Exclude the moved bottom segment from further swapping. */
+ top -= len;
+ } else {
+ /* Top segment is the short one. */
+ int len = top - middle;
+ register int i;
+
+ /* Swap it with the bottom part of the bottom segment. */
+ for (i = 0; i < len; i++) {
+ tem = argv[bottom + i];
+ argv[bottom + i] = argv[middle + i];
+ argv[middle + i] = tem;
+ }
+ /* Exclude the moved top segment from further swapping. */
+ bottom += len;
+ }
+ }
+
+ /* Update records for the slots the non-options now occupy. */
+
+ first_nonopt += (optind - last_nonopt);
+ last_nonopt = optind;
+}
+
+/* Initialize the internal data when the first call is made. */
+
+#if defined (__STDC__) && __STDC__
+static const char *_getopt_initialize(
+ int,
+ char *const *,
+ const char *);
+#endif
+static const char *_getopt_initialize(
+ argc,
+ argv,
+ optstring)
+ int argc;
+ char *const *argv;
+ const char *optstring;
+{
+ /* Start processing options with ARGV-element 1 (since ARGV-element 0
+ is the program name); the sequence of previously skipped
+ non-option ARGV-elements is empty. */
+
+ first_nonopt = last_nonopt = optind = 1;
+
+ nextchar = NULL;
+
+ posixly_correct = getenv("POSIXLY_CORRECT");
+
+ /* Determine how to handle the ordering of options and nonoptions. */
+
+ if (optstring[0] == '-') {
+ ordering = RETURN_IN_ORDER;
+ ++optstring;
+ } else if (optstring[0] == '+') {
+ ordering = REQUIRE_ORDER;
+ ++optstring;
+ } else if (posixly_correct != NULL)
+ ordering = REQUIRE_ORDER;
+ else
+ ordering = PERMUTE;
+
+#ifdef _LIBC
+ if (posixly_correct == NULL
+ && argc == original_argc && argv == original_argv) {
+ /* Bash 2.0 puts a special variable in the environment for each
+ command it runs, specifying which ARGV elements are the results of
+ file name wildcard expansion and therefore should not be
+ considered as options. */
+ char var[100];
+
+ sprintf(var, "_%d_GNU_nonoption_argv_flags_", getpid());
+ nonoption_flags = getenv(var);
+ if (nonoption_flags == NULL)
+ nonoption_flags_len = 0;
+ else
+ nonoption_flags_len = strlen(nonoption_flags);
+ } else
+ nonoption_flags_len = 0;
+#endif
+
+ return optstring;
+}
+\f
+/* Scan elements of ARGV (whose length is ARGC) for option characters
+ given in OPTSTRING.
+
+ If an element of ARGV starts with '-', and is not exactly "-" or "--",
+ then it is an option element. The characters of this element
+ (aside from the initial '-') are option characters. If `getopt'
+ is called repeatedly, it returns successively each of the option characters
+ from each of the option elements.
+
+ If `getopt' finds another option character, it returns that character,
+ updating `optind' and `nextchar' so that the next call to `getopt' can
+ resume the scan with the following option character or ARGV-element.
+
+ If there are no more option characters, `getopt' returns -1.
+ Then `optind' is the index in ARGV of the first ARGV-element
+ that is not an option. (The ARGV-elements have been permuted
+ so that those that are not options now come last.)
+
+ OPTSTRING is a string containing the legitimate option characters.
+ If an option character is seen that is not listed in OPTSTRING,
+ return '?' after printing an error message. If you set `opterr' to
+ zero, the error message is suppressed but we still return '?'.
+
+ If a char in OPTSTRING is followed by a colon, that means it wants an arg,
+ so the following text in the same ARGV-element, or the text of the following
+ ARGV-element, is returned in `optarg'. Two colons mean an option that
+ wants an optional arg; if there is text in the current ARGV-element,
+ it is returned in `optarg', otherwise `optarg' is set to zero.
+
+ If OPTSTRING starts with `-' or `+', it requests different methods of
+ handling the non-option ARGV-elements.
+ See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above.
+
+ Long-named options begin with `--' instead of `-'.
+ Their names may be abbreviated as long as the abbreviation is unique
+ or is an exact match for some defined option. If they have an
+ argument, it follows the option name in the same ARGV-element, separated
+ from the option name by a `=', or else the in next ARGV-element.
+ When `getopt' finds a long-named option, it returns 0 if that option's
+ `flag' field is nonzero, the value of the option's `val' field
+ if the `flag' field is zero.
+
+ The elements of ARGV aren't really const, because we permute them.
+ But we pretend they're const in the prototype to be compatible
+ with other systems.
+
+ LONGOPTS is a vector of `struct option' terminated by an
+ element containing a name which is zero.
+
+ LONGIND returns the index in LONGOPT of the long-named option found.
+ It is only valid when a long-named option has been found by the most
+ recent call.
+
+ If LONG_ONLY is nonzero, '-' as well as '--' can introduce
+ long-named options. */
+
+int _getopt_internal(
+ argc,
+ argv,
+ optstring,
+ longopts,
+ longind,
+ long_only)
+ int argc;
+ char *const *argv;
+ const char *optstring;
+ const struct option *longopts;
+ int *longind;
+ int long_only;
+{
+ optarg = NULL;
+
+ if (!__getopt_initialized || optind == 0) {
+ optstring = _getopt_initialize(argc, argv, optstring);
+ optind = 1; /* Don't scan ARGV[0], the program name. */
+ __getopt_initialized = 1;
+ }
+
+ /* Test whether ARGV[optind] points to a non-option argument.
+ Either it does not have option syntax, or there is an environment flag
+ from the shell indicating it is not an option. The later information
+ is only used when the used in the GNU libc. */
+#ifdef _LIBC
+#define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0' \
+ || (optind < nonoption_flags_len \
+ && nonoption_flags[optind] == '1'))
+#else
+#define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0')
+#endif
+
+ if (nextchar == NULL || *nextchar == '\0') {
+ /* Advance to the next ARGV-element. */
+
+ /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been
+ moved back by the user (who may also have changed the arguments). */
+ if (last_nonopt > optind)
+ last_nonopt = optind;
+ if (first_nonopt > optind)
+ first_nonopt = optind;
+
+ if (ordering == PERMUTE) {
+ /* If we have just processed some options following some non-options,
+ exchange them so that the options come first. */
+
+ if (first_nonopt != last_nonopt && last_nonopt != optind)
+ exchange((char **) argv);
+ else if (last_nonopt != optind)
+ first_nonopt = optind;
+
+ /* Skip any additional non-options
+ and extend the range of non-options previously skipped. */
+
+ while (optind < argc && NONOPTION_P)
+ optind++;
+ last_nonopt = optind;
+ }
+
+ /* The special ARGV-element `--' means premature end of options.
+ Skip it like a null option,
+ then exchange with previous non-options as if it were an option,
+ then skip everything else like a non-option. */
+
+ if (optind != argc && !strcmp(argv[optind], "--")) {
+ optind++;
+
+ if (first_nonopt != last_nonopt && last_nonopt != optind)
+ exchange((char **) argv);
+ else if (first_nonopt == last_nonopt)
+ first_nonopt = optind;
+ last_nonopt = argc;
+
+ optind = argc;
+ }
+
+ /* If we have done all the ARGV-elements, stop the scan
+ and back over any non-options that we skipped and permuted. */
+
+ if (optind == argc) {
+ /* Set the next-arg-index to point at the non-options
+ that we previously skipped, so the caller will digest them. */
+ if (first_nonopt != last_nonopt)
+ optind = first_nonopt;
+ return -1;
+ }
+
+ /* If we have come to a non-option and did not permute it,
+ either stop the scan or describe it to the caller and pass it by. */
+
+ if (NONOPTION_P) {
+ if (ordering == REQUIRE_ORDER)
+ return -1;
+ optarg = argv[optind++];
+ return 1;
+ }
+
+ /* We have found another option-ARGV-element.
+ Skip the initial punctuation. */
+
+ nextchar = (argv[optind] + 1
+ + (longopts != NULL && argv[optind][1] == '-'));
+ }
+
+ /* Decode the current option-ARGV-element. */
+
+ /* Check whether the ARGV-element is a long option.
+
+ If long_only and the ARGV-element has the form "-f", where f is
+ a valid short option, don't consider it an abbreviated form of
+ a long option that starts with f. Otherwise there would be no
+ way to give the -f short option.
+
+ On the other hand, if there's a long option "fubar" and
+ the ARGV-element is "-fu", do consider that an abbreviation of
+ the long option, just like "--fu", and not "-f" with arg "u".
+
+ This distinction seems to be the most useful approach. */
+
+ if (longopts != NULL
+ && (argv[optind][1] == '-' || (long_only && (argv[optind][2]
+ || !my_index(optstring,
+ argv[optind]
+ [1]))))) {
+ char *nameend;
+ const struct option *p;
+ const struct option *pfound = NULL;
+ int exact = 0;
+ int ambig = 0;
+ int indfound = -1;
+ int option_index;
+
+ for (nameend = nextchar; *nameend && *nameend != '='; nameend++)
+ /* Do nothing. */ ;
+
+ /* Test all long options for either exact match
+ or abbreviated matches. */
+ for (p = longopts, option_index = 0; p->name; p++, option_index++)
+ if (!strncmp(p->name, nextchar, nameend - nextchar)) {
+ if ((unsigned int) (nameend - nextchar)
+ == (unsigned int) strlen(p->name)) {
+ /* Exact match found. */
+ pfound = p;
+ indfound = option_index;
+ exact = 1;
+ break;
+ } else if (pfound == NULL) {
+ /* First nonexact match found. */
+ pfound = p;
+ indfound = option_index;
+ } else
+ /* Second or later nonexact match found. */
+ ambig = 1;
+ }
+
+ if (ambig && !exact) {
+ if (opterr)
+ fprintf(stderr, _("%s: option `%s' is ambiguous\n"),
+ argv[0], argv[optind]);
+ nextchar += strlen(nextchar);
+ optind++;
+ optopt = 0;
+ return '?';
+ }
+
+ if (pfound != NULL) {
+ option_index = indfound;
+ optind++;
+ if (*nameend) {
+ /* Don't test has_arg with >, because some C compilers don't
+ allow it to be used on enums. */
+ if (pfound->has_arg)
+ optarg = nameend + 1;
+ else {
+ if (opterr) {
+ if (argv[optind - 1][1] == '-')
+ /* --option */
+ fprintf(stderr,
+ _
+ ("%s: option `--%s' doesn't allow an argument\n"),
+ argv[0], pfound->name);
+ else
+ /* +option or -option */
+ fprintf(stderr,
+ _
+ ("%s: option `%c%s' doesn't allow an argument\n"),
+ argv[0], argv[optind - 1][0],
+ pfound->name);
+ }
+ nextchar += strlen(nextchar);
+
+ optopt = pfound->val;
+ return '?';
+ }
+ } else if (pfound->has_arg == 1) {
+ if (optind < argc)
+ optarg = argv[optind++];
+ else {
+ if (opterr)
+ fprintf(stderr,
+ _("%s: option `%s' requires an argument\n"),
+ argv[0], argv[optind - 1]);
+ nextchar += strlen(nextchar);
+ optopt = pfound->val;
+ return optstring[0] == ':' ? ':' : '?';
+ }
+ }
+ nextchar += strlen(nextchar);
+ if (longind != NULL)
+ *longind = option_index;
+ if (pfound->flag) {
+ *(pfound->flag) = pfound->val;
+ return 0;
+ }
+ return pfound->val;
+ }
+
+ /* Can't find it as a long option. If this is not getopt_long_only,
+ or the option starts with '--' or is not a valid short
+ option, then it's an error.
+ Otherwise interpret it as a short option. */
+ if (!long_only || argv[optind][1] == '-'
+ || my_index(optstring, *nextchar) == NULL) {
+ if (opterr) {
+ if (argv[optind][1] == '-')
+ /* --option */
+ fprintf(stderr, _("%s: unrecognized option `--%s'\n"),
+ argv[0], nextchar);
+ else
+ /* +option or -option */
+ fprintf(stderr, _("%s: unrecognized option `%c%s'\n"),
+ argv[0], argv[optind][0], nextchar);
+ }
+ nextchar = (char *) "";
+ optind++;
+ optopt = 0;
+ return '?';
+ }
+ }
+
+ /* Look at and handle the next short option-character. */
+
+ {
+ char c = *nextchar++;
+ char *temp = my_index(optstring, c);
+
+ /* Increment `optind' when we start to process its last character. */
+ if (*nextchar == '\0')
+ ++optind;
+
+ if (temp == NULL || c == ':') {
+ if (opterr) {
+ if (posixly_correct)
+ /* 1003.2 specifies the format of this message. */
+ fprintf(stderr, _("%s: illegal option -- %c\n"),
+ argv[0], c);
+ else
+ fprintf(stderr, _("%s: invalid option -- %c\n"),
+ argv[0], c);
+ }
+ optopt = c;
+ return '?';
+ }
+ /* Convenience. Treat POSIX -W foo same as long option --foo */
+ if (temp[0] == 'W' && temp[1] == ';') {
+ char *nameend;
+ const struct option *p;
+ const struct option *pfound = NULL;
+ int exact = 0;
+ int ambig = 0;
+ int indfound = 0;
+ int option_index;
+
+ /* This is an option that requires an argument. */
+ if (*nextchar != '\0') {
+ optarg = nextchar;
+ /* If we end this ARGV-element by taking the rest as an arg,
+ we must advance to the next element now. */
+ optind++;
+ } else if (optind == argc) {
+ if (opterr) {
+ /* 1003.2 specifies the format of this message. */
+ fprintf(stderr,
+ _("%s: option requires an argument -- %c\n"),
+ argv[0], c);
+ }
+ optopt = c;
+ if (optstring[0] == ':')
+ c = ':';
+ else
+ c = '?';
+ return c;
+ } else
+ /* We already incremented `optind' once;
+ increment it again when taking next ARGV-elt as argument. */
+ optarg = argv[optind++];
+
+ /* optarg is now the argument, see if it's in the
+ table of longopts. */
+
+ for (nextchar = nameend = optarg; *nameend && *nameend != '=';
+ nameend++)
+ /* Do nothing. */ ;
+
+ /* Test all long options for either exact match
+ or abbreviated matches. */
+ for (p = longopts, option_index = 0; p->name; p++, option_index++)
+ if (!strncmp(p->name, nextchar, nameend - nextchar)) {
+ if ((unsigned int) (nameend - nextchar) ==
+ strlen(p->name)) {
+ /* Exact match found. */
+ pfound = p;
+ indfound = option_index;
+ exact = 1;
+ break;
+ } else if (pfound == NULL) {
+ /* First nonexact match found. */
+ pfound = p;
+ indfound = option_index;
+ } else
+ /* Second or later nonexact match found. */
+ ambig = 1;
+ }
+ if (ambig && !exact) {
+ if (opterr)
+ fprintf(stderr, _("%s: option `-W %s' is ambiguous\n"),
+ argv[0], argv[optind]);
+ nextchar += strlen(nextchar);
+ optind++;
+ return '?';
+ }
+ if (pfound != NULL) {
+ option_index = indfound;
+ if (*nameend) {
+ /* Don't test has_arg with >, because some C compilers don't
+ allow it to be used on enums. */
+ if (pfound->has_arg)
+ optarg = nameend + 1;
+ else {
+ if (opterr)
+ fprintf(stderr, _("\
+%s: option `-W %s' doesn't allow an argument\n"), argv[0], pfound->name);
+
+ nextchar += strlen(nextchar);
+ return '?';
+ }
+ } else if (pfound->has_arg == 1) {
+ if (optind < argc)
+ optarg = argv[optind++];
+ else {
+ if (opterr)
+ fprintf(stderr,
+ _
+ ("%s: option `%s' requires an argument\n"),
+ argv[0], argv[optind - 1]);
+ nextchar += strlen(nextchar);
+ return optstring[0] == ':' ? ':' : '?';
+ }
+ }
+ nextchar += strlen(nextchar);
+ if (longind != NULL)
+ *longind = option_index;
+ if (pfound->flag) {
+ *(pfound->flag) = pfound->val;
+ return 0;
+ }
+ return pfound->val;
+ }
+ nextchar = NULL;
+ return 'W'; /* Let the application handle it. */
+ }
+ if (temp[1] == ':') {
+ if (temp[2] == ':') {
+ /* This is an option that accepts an argument optionally. */
+ if (*nextchar != '\0') {
+ optarg = nextchar;
+ optind++;
+ } else
+ optarg = NULL;
+ nextchar = NULL;
+ } else {
+ /* This is an option that requires an argument. */
+ if (*nextchar != '\0') {
+ optarg = nextchar;
+ /* If we end this ARGV-element by taking the rest as an arg,
+ we must advance to the next element now. */
+ optind++;
+ } else if (optind == argc) {
+ if (opterr) {
+ /* 1003.2 specifies the format of this message. */
+ fprintf(stderr,
+ _("%s: option requires an argument -- %c\n"),
+ argv[0], c);
+ }
+ optopt = c;
+ if (optstring[0] == ':')
+ c = ':';
+ else
+ c = '?';
+ } else
+ /* We already incremented `optind' once;
+ increment it again when taking next ARGV-elt as argument. */
+ optarg = argv[optind++];
+ nextchar = NULL;
+ }
+ }
+ return c;
+ }
+}
+
+int getopt(
+ argc,
+ argv,
+ optstring)
+ int argc;
+ char *const *argv;
+ const char *optstring;
+{
+ return _getopt_internal(argc, argv, optstring,
+ (const struct option *) 0, (int *) 0, 0);
+}
+
+#endif /* Not ELIDE_CODE. */
+\f
+#ifdef TEST
+
+/* Compile with -DTEST to make an executable for use in testing
+ the above definition of `getopt'. */
+
+int main(
+ argc,
+ argv)
+ int argc;
+ char **argv;
+{
+ int c;
+ int digit_optind = 0;
+
+ while (1) {
+ int this_option_optind = optind ? optind : 1;
+
+ c = getopt(argc, argv, "abc:d:0123456789");
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (digit_optind != 0 && digit_optind != this_option_optind)
+ printf("digits occur in two different argv-elements.\n");
+ digit_optind = this_option_optind;
+ printf("option %c\n", c);
+ break;
+
+ case 'a':
+ printf("option a\n");
+ break;
+
+ case 'b':
+ printf("option b\n");
+ break;
+
+ case 'c':
+ printf("option c with value `%s'\n", optarg);
+ break;
+
+ case '?':
+ break;
+
+ default:
+ printf("?? getopt returned character code 0%o ??\n", c);
+ }
+ }
+
+ if (optind < argc) {
+ printf("non-option ARGV-elements: ");
+ while (optind < argc)
+ printf("%s ", argv[optind++]);
+ printf("\n");
+ }
+
+ exit(0);
+}
+
+#endif /* TEST */
diff --git a/program/src/rrd_getopt.h b/program/src/rrd_getopt.h
--- /dev/null
+++ b/program/src/rrd_getopt.h
@@ -0,0 +1,149 @@
+/* Declarations for getopt.
+ Copyright (C) 1989,90,91,92,93,94,96,97 Free Software Foundation, Inc.
+
+ This file is part of the GNU C Library. Its master source is NOT part of
+ the C library, however. The master source lives in /gd/gnu/lib.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the GNU C Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA. */
+
+#ifndef _GETOPT_H
+#define _GETOPT_H 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* For communication from `getopt' to the caller.
+ When `getopt' finds an option that takes an argument,
+ the argument value is returned here.
+ Also, when `ordering' is RETURN_IN_ORDER,
+ each non-option ARGV-element is returned here. */
+
+ extern char *optarg;
+
+/* Index in ARGV of the next element to be scanned.
+ This is used for communication to and from the caller
+ and for communication between successive calls to `getopt'.
+
+ On entry to `getopt', zero means this is the first call; initialize.
+
+ When `getopt' returns -1, this is the index of the first of the
+ non-option elements that the caller should itself scan.
+
+ Otherwise, `optind' communicates from one call to the next
+ how much of ARGV has been scanned so far. */
+
+ extern int optind;
+
+/* Callers store zero here to inhibit the error message `getopt' prints
+ for unrecognized options. */
+
+ extern int opterr;
+
+/* Set to an option character which was unrecognized. */
+
+ extern int optopt;
+
+/* Describe the long-named options requested by the application.
+ The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector
+ of `struct option' terminated by an element containing a name which is
+ zero.
+
+ The field `has_arg' is:
+ no_argument (or 0) if the option does not take an argument,
+ required_argument (or 1) if the option requires an argument,
+ optional_argument (or 2) if the option takes an optional argument.
+
+ If the field `flag' is not NULL, it points to a variable that is set
+ to the value given in the field `val' when the option is found, but
+ left unchanged if the option is not found.
+
+ To have a long-named option do something other than set an `int' to
+ a compiled-in constant, such as set a value from `optarg', set the
+ option's `flag' field to zero and its `val' field to a nonzero
+ value (the equivalent single-letter option character, if there is
+ one). For long options that have a zero `flag' field, `getopt'
+ returns the contents of the `val' field. */
+
+ struct option {
+#if defined (__STDC__) && __STDC__
+ const char *name;
+#else
+ char *name;
+#endif
+ /* has_arg can't be an enum because some compilers complain about
+ type mismatches in all the code that assumes it is an int. */
+ int has_arg;
+ int *flag;
+ int val;
+ };
+
+/* Names for the values of the `has_arg' field of `struct option'. */
+
+#define no_argument 0
+#define required_argument 1
+#define optional_argument 2
+
+#if defined (__STDC__) && __STDC__
+#ifdef __GNU_LIBRARY__
+/* Many other libraries have conflicting prototypes for getopt, with
+ differences in the consts, in stdlib.h. To avoid compilation
+ errors, only prototype getopt for the GNU C library. */
+ extern int getopt(
+ int argc,
+ char *const *argv,
+ const char *shortopts);
+#else /* not __GNU_LIBRARY__ */
+ extern int getopt(
+ );
+#endif /* __GNU_LIBRARY__ */
+ extern int getopt_long(
+ int argc,
+ char *const *argv,
+ const char *shortopts,
+ const struct option *longopts,
+ int *longind);
+ extern int getopt_long_only(
+ int argc,
+ char *const *argv,
+ const char *shortopts,
+ const struct option *longopts,
+ int *longind);
+
+/* Internal only. Users should not call this directly. */
+ extern int _getopt_internal(
+ int argc,
+ char *const *argv,
+ const char *shortopts,
+ const struct option *longopts,
+ int *longind,
+ int long_only);
+#else /* not __STDC__ */
+ extern int getopt(
+ );
+ extern int getopt_long(
+ );
+ extern int getopt_long_only(
+ );
+
+ extern int _getopt_internal(
+ );
+#endif /* __STDC__ */
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* _GETOPT_H */
diff --git a/program/src/rrd_getopt1.c b/program/src/rrd_getopt1.c
--- /dev/null
@@ -0,0 +1,195 @@
+/* getopt_long and getopt_long_only entry points for GNU getopt.
+ Copyright (C) 1987,88,89,90,91,92,93,94,96,97 Free Software Foundation, Inc.
+
+ This file is part of the GNU C Library. Its master source is NOT part of
+ the C library, however. The master source lives in /gd/gnu/lib.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the GNU C Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA. */
+\f
+
+#if !defined (__STDC__) || !__STDC__
+/* This is a separate conditional since some stdc systems
+ reject `defined (const)'. */
+#ifndef const
+#define const
+#endif
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "../rrd_config.h"
+#endif
+
+#include "rrd_getopt.h"
+
+#include <stdio.h>
+
+/* Comment out all this code if we are using the GNU C Library, and are not
+ actually compiling the library itself. This code is part of the GNU C
+ Library, but also included in many other GNU distributions. Compiling
+ and linking in this code is a waste when using the GNU C library
+ (especially if it is a shared library). Rather than having every GNU
+ program understand `configure --with-gnu-libc' and omit the object files,
+ it is simpler to just do this in the source for each such file. */
+
+#define GETOPT_INTERFACE_VERSION 2
+#if !defined (_LIBC) && defined (__GLIBC__) && __GLIBC__ >= 2
+#include <gnu-versions.h>
+#if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION
+#define ELIDE_CODE
+#endif
+#endif
+
+#ifndef ELIDE_CODE
+
+
+/* This needs to come after some library #include
+ to get __GNU_LIBRARY__ defined. */
+#ifdef __GNU_LIBRARY__
+#include <stdlib.h>
+#endif
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+int getopt_long(
+ argc,
+ argv,
+ options,
+ long_options,
+ opt_index)
+ int argc;
+ char *const *argv;
+ const char *options;
+ const struct option *long_options;
+ int *opt_index;
+{
+ return _getopt_internal(argc, argv, options, long_options, opt_index, 0);
+}
+
+/* Like getopt_long, but '-' as well as '--' can indicate a long option.
+ If an option that starts with '-' (not '--') doesn't match a long option,
+ but does match a short option, it is parsed as a short option
+ instead. */
+
+int getopt_long_only(
+ argc,
+ argv,
+ options,
+ long_options,
+ opt_index)
+ int argc;
+ char *const *argv;
+ const char *options;
+ const struct option *long_options;
+ int *opt_index;
+{
+ return _getopt_internal(argc, argv, options, long_options, opt_index, 1);
+}
+
+
+#endif /* Not ELIDE_CODE. */
+\f
+#ifdef TEST
+
+#include <stdio.h>
+
+int main(
+ argc,
+ argv)
+ int argc;
+ char **argv;
+{
+ int c;
+ int digit_optind = 0;
+ struct option long_options[] = {
+ {"add", 1, 0, 0},
+ {"append", 0, 0, 0},
+ {"delete", 1, 0, 0},
+ {"verbose", 0, 0, 0},
+ {"create", 0, 0, 0},
+ {"file", 1, 0, 0},
+ {0, 0, 0, 0}
+ };
+
+ while (1) {
+ int this_option_optind = optind ? optind : 1;
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "abc:d:0123456789",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 0:
+ printf("option %s", long_options[option_index].name);
+ if (optarg)
+ printf(" with arg %s", optarg);
+ printf("\n");
+ break;
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (digit_optind != 0 && digit_optind != this_option_optind)
+ printf("digits occur in two different argv-elements.\n");
+ digit_optind = this_option_optind;
+ printf("option %c\n", c);
+ break;
+
+ case 'a':
+ printf("option a\n");
+ break;
+
+ case 'b':
+ printf("option b\n");
+ break;
+
+ case 'c':
+ printf("option c with value `%s'\n", optarg);
+ break;
+
+ case 'd':
+ printf("option d with value `%s'\n", optarg);
+ break;
+
+ case '?':
+ break;
+
+ default:
+ printf("?? getopt returned character code 0%o ??\n", c);
+ }
+ }
+
+ if (optind < argc) {
+ printf("non-option ARGV-elements: ");
+ while (optind < argc)
+ printf("%s ", argv[optind++]);
+ printf("\n");
+ }
+
+ exit(0);
+}
+
+#endif /* TEST */
diff --git a/program/src/rrd_gfx.c b/program/src/rrd_gfx.c
--- /dev/null
+++ b/program/src/rrd_gfx.c
@@ -0,0 +1,318 @@
+/****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ ****************************************************************************
+ * rrd_gfx.c graphics wrapper for rrdtool
+ **************************************************************************/
+
+/* #define DEBUG */
+
+/* stupid MSVC doesnt support variadic macros = no debug for now! */
+#ifdef _MSC_VER
+# define RRDPRINTF()
+#else
+# ifdef DEBUG
+# define RRDPRINTF(...) fprintf(stderr, __VA_ARGS__);
+# else
+# define RRDPRINTF(...)
+# endif /* DEBUG */
+#endif /* _MSC_VER */
+
+#include "rrd_tool.h"
+#include "rrd_graph.h"
+
+
+/* create a new line */
+void gfx_line(
+ image_desc_t *im,
+ double X0,
+ double Y0,
+ double X1,
+ double Y1,
+ double width,
+ gfx_color_t color)
+{
+ gfx_dashed_line(im, X0, Y0, X1, Y1, width, color, 0, 0);
+}
+
+void gfx_dashed_line(
+ image_desc_t *im,
+ double X0,
+ double Y0,
+ double X1,
+ double Y1,
+ double width,
+ gfx_color_t color,
+ double dash_on,
+ double dash_off)
+{
+ cairo_t *cr = im->cr;
+ double dashes[2];
+ double x = 0;
+ double y = 0;
+
+ dashes[0] = dash_on;
+ dashes[1] = dash_off;
+
+ cairo_save(cr);
+ cairo_new_path(cr);
+ cairo_set_line_width(cr, width);
+ gfx_line_fit(im, &x, &y);
+ gfx_line_fit(im, &X0, &Y0);
+ cairo_move_to(cr, X0, Y0);
+ gfx_line_fit(im, &X1, &Y1);
+ cairo_line_to(cr, X1, Y1);
+ if (dash_on > 0 || dash_off > 0)
+ cairo_set_dash(cr, dashes, 2, x);
+ cairo_set_source_rgba(cr, color.red, color.green, color.blue,
+ color.alpha);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+}
+
+/* create a new area */
+void gfx_new_area(
+ image_desc_t *im,
+ double X0,
+ double Y0,
+ double X1,
+ double Y1,
+ double X2,
+ double Y2,
+ gfx_color_t color)
+{
+ cairo_t *cr = im->cr;
+
+ cairo_new_path(cr);
+ gfx_area_fit(im, &X0, &Y0);
+ cairo_move_to(cr, X0, Y0);
+ gfx_area_fit(im, &X1, &Y1);
+ cairo_line_to(cr, X1, Y1);
+ gfx_area_fit(im, &X2, &Y2);
+ cairo_line_to(cr, X2, Y2);
+ cairo_set_source_rgba(cr, color.red, color.green, color.blue,
+ color.alpha);
+}
+
+/* add a point to a line or to an area */
+void gfx_add_point(
+ image_desc_t *im,
+ double x,
+ double y)
+{
+ cairo_t *cr = im->cr;
+
+ gfx_area_fit(im, &x, &y);
+ cairo_line_to(cr, x, y);
+}
+
+void gfx_close_path(
+ image_desc_t *im)
+{
+ cairo_t *cr = im->cr;
+
+ cairo_close_path(cr);
+ cairo_fill(cr);
+}
+
+/* create a text node */
+static PangoLayout *gfx_prep_text(
+ image_desc_t *im,
+ double x,
+ gfx_color_t color,
+ PangoFontDescription *font_desc,
+ double tabwidth,
+ const char *text)
+{
+ PangoLayout *layout = im->layout;
+ PangoFontDescription *pfd;
+ cairo_t *cr = im->cr;
+
+ static double last_tabwidth = -1;
+
+
+
+ /* for performance reasons we might
+ want todo that only once ... tabs will always
+ be the same */
+ long i;
+ long tab_count = strlen(text);
+ long tab_shift = fmod(x, tabwidth);
+ int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
+
+ gchar *utf8_text;
+
+ if (last_tabwidth < 0 || last_tabwidth != tabwidth){
+ PangoTabArray *tab_array;
+ // fprintf(stderr,"t");
+ last_tabwidth = tabwidth;
+ tab_array = pango_tab_array_new(tab_count, (gboolean) (1));
+ for (i = 1; i <= tab_count; i++) {
+ pango_tab_array_set_tab(tab_array,
+ i, PANGO_TAB_LEFT,
+ tabwidth * i - tab_shift + border);
+ }
+ pango_layout_set_tabs(layout, tab_array);
+ pango_tab_array_free(tab_array);
+ }
+ pfd = pango_layout_get_font_description(layout);
+
+ if (!pfd || !pango_font_description_equal (pfd,font_desc)){
+ pango_layout_set_font_description(layout, font_desc);
+ }
+// fprintf(stderr,"%s\n",pango_font_description_to_string(pango_layout_get_font_description(layout)));
+
+ cairo_new_path(cr);
+ cairo_set_source_rgba(cr, color.red, color.green, color.blue,
+ color.alpha);
+/* layout = pango_cairo_create_layout(cr); */
+
+// pango_cairo_context_set_font_options(pango_context, im->font_options);
+// pango_cairo_context_set_resolution(pango_context, 100);
+
+/* pango_cairo_update_context(cr, pango_context); */
+
+
+ /* pango expects the string to be utf-8 encoded */
+ utf8_text = g_locale_to_utf8((const gchar *) text, -1, NULL, NULL, NULL);
+
+ /* In case of an error, i.e. utf8_text == NULL (locale settings messed
+ * up?), we fall back to a possible "invalid UTF-8 string" warning instead
+ * of provoking a failed assertion in libpango. */
+ if (im->with_markup)
+ pango_layout_set_markup(layout, utf8_text ? utf8_text : text, -1);
+ else
+ pango_layout_set_text(layout, utf8_text ? utf8_text : text, -1);
+
+ g_free(utf8_text);
+ return layout;
+}
+
+/* Size Text Node */
+double gfx_get_text_width(
+ image_desc_t *im,
+ double start,
+ PangoFontDescription *font_desc,
+ double tabwidth,
+ char *text)
+{
+ PangoLayout *layout;
+ PangoRectangle log_rect;
+ gfx_color_t color = { 0, 0, 0, 0 };
+ layout = gfx_prep_text(im, start, color, font_desc, tabwidth, text);
+ pango_layout_get_pixel_extents(layout, NULL, &log_rect);
+/* g_object_unref(layout); */
+ return log_rect.width;
+}
+
+void gfx_text(
+ image_desc_t *im,
+ double x,
+ double y,
+ gfx_color_t color,
+ PangoFontDescription *font_desc,
+ double tabwidth,
+ double angle,
+ enum gfx_h_align_en h_align,
+ enum gfx_v_align_en v_align,
+ const char *text)
+{
+ PangoLayout *layout;
+ PangoRectangle log_rect;
+ cairo_t *cr = im->cr;
+ double sx = 0;
+ double sy = 0;
+
+ cairo_save(cr);
+ cairo_translate(cr, x, y);
+/* gfx_line(cr,-2,0,2,0,1,color);
+ gfx_line(cr,0,-2,0,2,1,color); */
+ layout = gfx_prep_text(im, x, color, font_desc, tabwidth, text);
+ pango_layout_get_pixel_extents(layout, NULL, &log_rect);
+ cairo_rotate(cr, -angle * G_PI / 180.0);
+ sx = log_rect.x;
+ switch (h_align) {
+ case GFX_H_RIGHT:
+ sx -= log_rect.width;
+ break;
+ case GFX_H_CENTER:
+ sx -= log_rect.width / 2;
+ break;
+ case GFX_H_LEFT:
+ break;
+ case GFX_H_NULL:
+ break;
+ }
+ sy = log_rect.y;
+ switch (v_align) {
+ case GFX_V_TOP:
+ break;
+ case GFX_V_CENTER:
+ sy -= log_rect.height / 2;
+ break;
+ case GFX_V_BOTTOM:
+ sy -= log_rect.height;
+ break;
+ case GFX_V_NULL:
+ break;
+ }
+ pango_cairo_update_layout(cr, layout);
+ cairo_move_to(cr, sx, sy);
+ pango_cairo_show_layout(cr, layout);
+/* g_object_unref(layout); */
+ cairo_restore(cr);
+
+}
+
+/* convert color */
+struct gfx_color_t gfx_hex_to_col(
+ long unsigned int color)
+{
+ struct gfx_color_t gfx_color;
+
+ gfx_color.red = 1.0 / 255.0 * ((color & 0xff000000) >> (3 * 8));
+ gfx_color.green = 1.0 / 255.0 * ((color & 0x00ff0000) >> (2 * 8));
+ gfx_color.blue = 1.0 / 255.0 * ((color & 0x0000ff00) >> (1 * 8));
+ gfx_color.alpha = 1.0 / 255.0 * (color & 0x000000ff);
+ return gfx_color;
+}
+
+/* gridfit_lines */
+
+void gfx_line_fit(
+ image_desc_t *im,
+ double *x,
+ double *y)
+{
+ cairo_t *cr = im->cr;
+ double line_width;
+ double line_height;
+
+ if (!im->gridfit)
+ return;
+ cairo_user_to_device(cr, x, y);
+ line_width = cairo_get_line_width(cr);
+ line_height = line_width;
+ cairo_user_to_device_distance(cr, &line_width, &line_height);
+ line_width = line_width / 2.0 - (long) (line_width / 2.0);
+ line_height = line_height / 2.0 - (long) (line_height / 2.0);
+ *x = (double) ((long) (*x + 0.5)) - line_width;
+ *y = (double) ((long) (*y + 0.5)) + line_height;
+ cairo_device_to_user(cr, x, y);
+}
+
+/* gridfit_areas */
+
+void gfx_area_fit(
+ image_desc_t *im,
+ double *x,
+ double *y)
+{
+ cairo_t *cr = im->cr;
+
+ if (!im->gridfit)
+ return;
+ cairo_user_to_device(cr, x, y);
+ *x = (double) ((long) (*x + 0.5));
+ *y = (double) ((long) (*y + 0.5));
+ cairo_device_to_user(cr, x, y);
+}
diff --git a/program/src/rrd_graph.c b/program/src/rrd_graph.c
--- /dev/null
+++ b/program/src/rrd_graph.c
@@ -0,0 +1,4702 @@
+/****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ ****************************************************************************
+ * rrd__graph.c produce graphs from data in rrdfiles
+ ****************************************************************************/
+
+
+#include <sys/stat.h>
+
+#ifdef WIN32
+#include "strftime.h"
+#endif
+#include "rrd_tool.h"
+
+#if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
+#include <io.h>
+#include <fcntl.h>
+#endif
+
+#ifdef HAVE_TIME_H
+#include <time.h>
+#endif
+
+#ifdef HAVE_LOCALE_H
+#include <locale.h>
+#endif
+
+#include "rrd_graph.h"
+
+/* some constant definitions */
+
+
+
+#ifndef RRD_DEFAULT_FONT
+/* there is special code later to pick Cour.ttf when running on windows */
+#define RRD_DEFAULT_FONT "DejaVu Sans Mono,Bitstream Vera Sans Mono,monospace,Courier"
+#endif
+
+text_prop_t text_prop[] = {
+ {8.0, RRD_DEFAULT_FONT,NULL}
+ , /* default */
+ {9.0, RRD_DEFAULT_FONT,NULL}
+ , /* title */
+ {7.0, RRD_DEFAULT_FONT,NULL}
+ , /* axis */
+ {8.0, RRD_DEFAULT_FONT,NULL}
+ , /* unit */
+ {8.0, RRD_DEFAULT_FONT,NULL} /* legend */
+ ,
+ {5.5, RRD_DEFAULT_FONT,NULL} /* watermark */
+};
+
+xlab_t xlab[] = {
+ {0, 0, TMT_SECOND, 30, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
+ ,
+ {2, 0, TMT_MINUTE, 1, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
+ ,
+ {5, 0, TMT_MINUTE, 2, TMT_MINUTE, 10, TMT_MINUTE, 10, 0, "%H:%M"}
+ ,
+ {10, 0, TMT_MINUTE, 5, TMT_MINUTE, 20, TMT_MINUTE, 20, 0, "%H:%M"}
+ ,
+ {30, 0, TMT_MINUTE, 10, TMT_HOUR, 1, TMT_HOUR, 1, 0, "%H:%M"}
+ ,
+ {60, 0, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 2, 0, "%H:%M"}
+ ,
+ {60, 24 * 3600, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 6, 0, "%a %H:%M"}
+ ,
+ {180, 0, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 6, 0, "%H:%M"}
+ ,
+ {180, 24 * 3600, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 12, 0, "%a %H:%M"}
+ ,
+ /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly */
+ {600, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%a"}
+ ,
+ {1200, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%d"}
+ ,
+ {1800, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a %d"}
+ ,
+ {2400, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a"}
+ ,
+ {3600, 0, TMT_DAY, 1, TMT_WEEK, 1, TMT_WEEK, 1, 7 * 24 * 3600, "Week %V"}
+ ,
+ {3 * 3600, 0, TMT_WEEK, 1, TMT_MONTH, 1, TMT_WEEK, 2, 7 * 24 * 3600,
+ "Week %V"}
+ ,
+ {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
+ "%b"}
+ ,
+ {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
+ "%b"}
+ ,
+ {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
+ ,
+ {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
+ 365 * 24 * 3600, "%y"}
+ ,
+ {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
+};
+
+/* sensible y label intervals ...*/
+
+ylab_t ylab[] = {
+ {0.1, {1, 2, 5, 10}
+ }
+ ,
+ {0.2, {1, 5, 10, 20}
+ }
+ ,
+ {0.5, {1, 2, 4, 10}
+ }
+ ,
+ {1.0, {1, 2, 5, 10}
+ }
+ ,
+ {2.0, {1, 5, 10, 20}
+ }
+ ,
+ {5.0, {1, 2, 4, 10}
+ }
+ ,
+ {10.0, {1, 2, 5, 10}
+ }
+ ,
+ {20.0, {1, 5, 10, 20}
+ }
+ ,
+ {50.0, {1, 2, 4, 10}
+ }
+ ,
+ {100.0, {1, 2, 5, 10}
+ }
+ ,
+ {200.0, {1, 5, 10, 20}
+ }
+ ,
+ {500.0, {1, 2, 4, 10}
+ }
+ ,
+ {0.0, {0, 0, 0, 0}
+ }
+};
+
+
+gfx_color_t graph_col[] = /* default colors */
+{
+ {1.00, 1.00, 1.00, 1.00}, /* canvas */
+ {0.95, 0.95, 0.95, 1.00}, /* background */
+ {0.81, 0.81, 0.81, 1.00}, /* shade A */
+ {0.62, 0.62, 0.62, 1.00}, /* shade B */
+ {0.56, 0.56, 0.56, 0.75}, /* grid */
+ {0.87, 0.31, 0.31, 0.60}, /* major grid */
+ {0.00, 0.00, 0.00, 1.00}, /* font */
+ {0.50, 0.12, 0.12, 1.00}, /* arrow */
+ {0.12, 0.12, 0.12, 1.00}, /* axis */
+ {0.00, 0.00, 0.00, 1.00} /* frame */
+};
+
+
+/* #define DEBUG */
+
+#ifdef DEBUG
+# define DPRINT(x) (void)(printf x, printf("\n"))
+#else
+# define DPRINT(x)
+#endif
+
+
+/* initialize with xtr(im,0); */
+int xtr(
+ image_desc_t *im,
+ time_t mytime)
+{
+ static double pixie;
+
+ if (mytime == 0) {
+ pixie = (double) im->xsize / (double) (im->end - im->start);
+ return im->xorigin;
+ }
+ return (int) ((double) im->xorigin + pixie * (mytime - im->start));
+}
+
+/* translate data values into y coordinates */
+double ytr(
+ image_desc_t *im,
+ double value)
+{
+ static double pixie;
+ double yval;
+
+ if (isnan(value)) {
+ if (!im->logarithmic)
+ pixie = (double) im->ysize / (im->maxval - im->minval);
+ else
+ pixie =
+ (double) im->ysize / (log10(im->maxval) - log10(im->minval));
+ yval = im->yorigin;
+ } else if (!im->logarithmic) {
+ yval = im->yorigin - pixie * (value - im->minval);
+ } else {
+ if (value < im->minval) {
+ yval = im->yorigin;
+ } else {
+ yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
+ }
+ }
+ return yval;
+}
+
+
+
+/* conversion function for symbolic entry names */
+
+
+#define conv_if(VV,VVV) \
+ if (strcmp(#VV, string) == 0) return VVV ;
+
+enum gf_en gf_conv(
+ char *string)
+{
+
+ conv_if(PRINT, GF_PRINT);
+ conv_if(GPRINT, GF_GPRINT);
+ conv_if(COMMENT, GF_COMMENT);
+ conv_if(HRULE, GF_HRULE);
+ conv_if(VRULE, GF_VRULE);
+ conv_if(LINE, GF_LINE);
+ conv_if(AREA, GF_AREA);
+ conv_if(STACK, GF_STACK);
+ conv_if(TICK, GF_TICK);
+ conv_if(TEXTALIGN, GF_TEXTALIGN);
+ conv_if(DEF, GF_DEF);
+ conv_if(CDEF, GF_CDEF);
+ conv_if(VDEF, GF_VDEF);
+ conv_if(XPORT, GF_XPORT);
+ conv_if(SHIFT, GF_SHIFT);
+
+ return (-1);
+}
+
+enum gfx_if_en if_conv(
+ char *string)
+{
+
+ conv_if(PNG, IF_PNG);
+ conv_if(SVG, IF_SVG);
+ conv_if(EPS, IF_EPS);
+ conv_if(PDF, IF_PDF);
+
+ return (-1);
+}
+
+enum tmt_en tmt_conv(
+ char *string)
+{
+
+ conv_if(SECOND, TMT_SECOND);
+ conv_if(MINUTE, TMT_MINUTE);
+ conv_if(HOUR, TMT_HOUR);
+ conv_if(DAY, TMT_DAY);
+ conv_if(WEEK, TMT_WEEK);
+ conv_if(MONTH, TMT_MONTH);
+ conv_if(YEAR, TMT_YEAR);
+ return (-1);
+}
+
+enum grc_en grc_conv(
+ char *string)
+{
+
+ conv_if(BACK, GRC_BACK);
+ conv_if(CANVAS, GRC_CANVAS);
+ conv_if(SHADEA, GRC_SHADEA);
+ conv_if(SHADEB, GRC_SHADEB);
+ conv_if(GRID, GRC_GRID);
+ conv_if(MGRID, GRC_MGRID);
+ conv_if(FONT, GRC_FONT);
+ conv_if(ARROW, GRC_ARROW);
+ conv_if(AXIS, GRC_AXIS);
+ conv_if(FRAME, GRC_FRAME);
+
+ return -1;
+}
+
+enum text_prop_en text_prop_conv(
+ char *string)
+{
+
+ conv_if(DEFAULT, TEXT_PROP_DEFAULT);
+ conv_if(TITLE, TEXT_PROP_TITLE);
+ conv_if(AXIS, TEXT_PROP_AXIS);
+ conv_if(UNIT, TEXT_PROP_UNIT);
+ conv_if(LEGEND, TEXT_PROP_LEGEND);
+ conv_if(WATERMARK, TEXT_PROP_WATERMARK);
+ return -1;
+}
+
+
+#undef conv_if
+
+int im_free(
+ image_desc_t *im)
+{
+ unsigned long i, ii;
+ cairo_status_t status = 0;
+
+ if (im == NULL)
+ return 0;
+ for (i = 0; i < (unsigned) im->gdes_c; i++) {
+ if (im->gdes[i].data_first) {
+ /* careful here, because a single pointer can occur several times */
+ free(im->gdes[i].data);
+ if (im->gdes[i].ds_namv) {
+ for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
+ free(im->gdes[i].ds_namv[ii]);
+ free(im->gdes[i].ds_namv);
+ }
+ }
+ /* free allocated memory used for dashed lines */
+ if (im->gdes[i].p_dashes != NULL)
+ free(im->gdes[i].p_dashes);
+
+ free(im->gdes[i].p_data);
+ free(im->gdes[i].rpnp);
+ }
+ free(im->gdes);
+ if (im->font_options)
+ cairo_font_options_destroy(im->font_options);
+
+ if (im->cr) {
+ status = cairo_status(im->cr);
+ cairo_destroy(im->cr);
+ }
+ if (im->rendered_image) {
+ free(im->rendered_image);
+ }
+
+ if (im->layout) {
+ g_object_unref (im->layout);
+ }
+
+ if (im->surface)
+ cairo_surface_destroy(im->surface);
+
+ if (status)
+ fprintf(stderr, "OOPS: Cairo has issues it can't even die: %s\n",
+ cairo_status_to_string(status));
+
+ return 0;
+}
+
+/* find SI magnitude symbol for the given number*/
+void auto_scale(
+ image_desc_t *im, /* image description */
+ double *value,
+ char **symb_ptr,
+ double *magfact)
+{
+
+ char *symbol[] = { "a", /* 10e-18 Atto */
+ "f", /* 10e-15 Femto */
+ "p", /* 10e-12 Pico */
+ "n", /* 10e-9 Nano */
+ "u", /* 10e-6 Micro */
+ "m", /* 10e-3 Milli */
+ " ", /* Base */
+ "k", /* 10e3 Kilo */
+ "M", /* 10e6 Mega */
+ "G", /* 10e9 Giga */
+ "T", /* 10e12 Tera */
+ "P", /* 10e15 Peta */
+ "E"
+ }; /* 10e18 Exa */
+
+ int symbcenter = 6;
+ int sindex;
+
+ if (*value == 0.0 || isnan(*value)) {
+ sindex = 0;
+ *magfact = 1.0;
+ } else {
+ sindex = floor(log(fabs(*value)) / log((double) im->base));
+ *magfact = pow((double) im->base, (double) sindex);
+ (*value) /= (*magfact);
+ }
+ if (sindex <= symbcenter && sindex >= -symbcenter) {
+ (*symb_ptr) = symbol[sindex + symbcenter];
+ } else {
+ (*symb_ptr) = "?";
+ }
+}
+
+
+static char si_symbol[] = {
+ 'a', /* 10e-18 Atto */
+ 'f', /* 10e-15 Femto */
+ 'p', /* 10e-12 Pico */
+ 'n', /* 10e-9 Nano */
+ 'u', /* 10e-6 Micro */
+ 'm', /* 10e-3 Milli */
+ ' ', /* Base */
+ 'k', /* 10e3 Kilo */
+ 'M', /* 10e6 Mega */
+ 'G', /* 10e9 Giga */
+ 'T', /* 10e12 Tera */
+ 'P', /* 10e15 Peta */
+ 'E', /* 10e18 Exa */
+};
+static const int si_symbcenter = 6;
+
+/* find SI magnitude symbol for the numbers on the y-axis*/
+void si_unit(
+ image_desc_t *im /* image description */
+ )
+{
+
+ double digits, viewdigits = 0;
+
+ digits =
+ floor(log(max(fabs(im->minval), fabs(im->maxval))) /
+ log((double) im->base));
+
+ if (im->unitsexponent != 9999) {
+ /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
+ viewdigits = floor(im->unitsexponent / 3);
+ } else {
+ viewdigits = digits;
+ }
+
+ im->magfact = pow((double) im->base, digits);
+
+#ifdef DEBUG
+ printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
+#endif
+
+ im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
+
+ if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
+ ((viewdigits + si_symbcenter) >= 0))
+ im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
+ else
+ im->symbol = '?';
+}
+
+/* move min and max values around to become sensible */
+
+void expand_range(
+ image_desc_t *im)
+{
+ double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
+ 600.0, 500.0, 400.0, 300.0, 250.0,
+ 200.0, 125.0, 100.0, 90.0, 80.0,
+ 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
+ 25.0, 20.0, 10.0, 9.0, 8.0,
+ 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
+ 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
+ 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
+ };
+
+ double scaled_min, scaled_max;
+ double adj;
+ int i;
+
+
+
+#ifdef DEBUG
+ printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
+ im->minval, im->maxval, im->magfact);
+#endif
+
+ if (isnan(im->ygridstep)) {
+ if (im->extra_flags & ALTAUTOSCALE) {
+ /* measure the amplitude of the function. Make sure that
+ graph boundaries are slightly higher then max/min vals
+ so we can see amplitude on the graph */
+ double delt, fact;
+
+ delt = im->maxval - im->minval;
+ adj = delt * 0.1;
+ fact = 2.0 * pow(10.0,
+ floor(log10
+ (max(fabs(im->minval), fabs(im->maxval)) /
+ im->magfact)) - 2);
+ if (delt < fact) {
+ adj = (fact - delt) * 0.55;
+#ifdef DEBUG
+ printf
+ ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
+ im->minval, im->maxval, delt, fact, adj);
+#endif
+ }
+ im->minval -= adj;
+ im->maxval += adj;
+ } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
+ /* measure the amplitude of the function. Make sure that
+ graph boundaries are slightly lower than min vals
+ so we can see amplitude on the graph */
+ adj = (im->maxval - im->minval) * 0.1;
+ im->minval -= adj;
+ } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
+ /* measure the amplitude of the function. Make sure that
+ graph boundaries are slightly higher than max vals
+ so we can see amplitude on the graph */
+ adj = (im->maxval - im->minval) * 0.1;
+ im->maxval += adj;
+ } else {
+ scaled_min = im->minval / im->magfact;
+ scaled_max = im->maxval / im->magfact;
+
+ for (i = 1; sensiblevalues[i] > 0; i++) {
+ if (sensiblevalues[i - 1] >= scaled_min &&
+ sensiblevalues[i] <= scaled_min)
+ im->minval = sensiblevalues[i] * (im->magfact);
+
+ if (-sensiblevalues[i - 1] <= scaled_min &&
+ -sensiblevalues[i] >= scaled_min)
+ im->minval = -sensiblevalues[i - 1] * (im->magfact);
+
+ if (sensiblevalues[i - 1] >= scaled_max &&
+ sensiblevalues[i] <= scaled_max)
+ im->maxval = sensiblevalues[i - 1] * (im->magfact);
+
+ if (-sensiblevalues[i - 1] <= scaled_max &&
+ -sensiblevalues[i] >= scaled_max)
+ im->maxval = -sensiblevalues[i] * (im->magfact);
+ }
+ }
+ } else {
+ /* adjust min and max to the grid definition if there is one */
+ im->minval = (double) im->ylabfact * im->ygridstep *
+ floor(im->minval / ((double) im->ylabfact * im->ygridstep));
+ im->maxval = (double) im->ylabfact * im->ygridstep *
+ ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
+ }
+
+#ifdef DEBUG
+ fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
+ im->minval, im->maxval, im->magfact);
+#endif
+}
+
+
+void apply_gridfit(
+ image_desc_t *im)
+{
+ if (isnan(im->minval) || isnan(im->maxval))
+ return;
+ ytr(im, DNAN);
+ if (im->logarithmic) {
+ double ya, yb, ypix, ypixfrac;
+ double log10_range = log10(im->maxval) - log10(im->minval);
+
+ ya = pow((double) 10, floor(log10(im->minval)));
+ while (ya < im->minval)
+ ya *= 10;
+ if (ya > im->maxval)
+ return; /* don't have y=10^x gridline */
+ yb = ya * 10;
+ if (yb <= im->maxval) {
+ /* we have at least 2 y=10^x gridlines.
+ Make sure distance between them in pixels
+ are an integer by expanding im->maxval */
+ double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
+ double factor = y_pixel_delta / floor(y_pixel_delta);
+ double new_log10_range = factor * log10_range;
+ double new_ymax_log10 = log10(im->minval) + new_log10_range;
+
+ im->maxval = pow(10, new_ymax_log10);
+ ytr(im, DNAN); /* reset precalc */
+ log10_range = log10(im->maxval) - log10(im->minval);
+ }
+ /* make sure first y=10^x gridline is located on
+ integer pixel position by moving scale slightly
+ downwards (sub-pixel movement) */
+ ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
+ ypixfrac = ypix - floor(ypix);
+ if (ypixfrac > 0 && ypixfrac < 1) {
+ double yfrac = ypixfrac / im->ysize;
+
+ im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
+ im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
+ ytr(im, DNAN); /* reset precalc */
+ }
+ } else {
+ /* Make sure we have an integer pixel distance between
+ each minor gridline */
+ double ypos1 = ytr(im, im->minval);
+ double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
+ double y_pixel_delta = ypos1 - ypos2;
+ double factor = y_pixel_delta / floor(y_pixel_delta);
+ double new_range = factor * (im->maxval - im->minval);
+ double gridstep = im->ygrid_scale.gridstep;
+ double minor_y, minor_y_px, minor_y_px_frac;
+
+ if (im->maxval > 0.0)
+ im->maxval = im->minval + new_range;
+ else
+ im->minval = im->maxval - new_range;
+ ytr(im, DNAN); /* reset precalc */
+ /* make sure first minor gridline is on integer pixel y coord */
+ minor_y = gridstep * floor(im->minval / gridstep);
+ while (minor_y < im->minval)
+ minor_y += gridstep;
+ minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
+ minor_y_px_frac = minor_y_px - floor(minor_y_px);
+ if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
+ double yfrac = minor_y_px_frac / im->ysize;
+ double range = im->maxval - im->minval;
+
+ im->minval = im->minval - yfrac * range;
+ im->maxval = im->maxval - yfrac * range;
+ ytr(im, DNAN); /* reset precalc */
+ }
+ calc_horizontal_grid(im); /* recalc with changed im->maxval */
+ }
+}
+
+/* reduce data reimplementation by Alex */
+
+void reduce_data(
+ enum cf_en cf, /* which consolidation function ? */
+ unsigned long cur_step, /* step the data currently is in */
+ time_t *start, /* start, end and step as requested ... */
+ time_t *end, /* ... by the application will be ... */
+ unsigned long *step, /* ... adjusted to represent reality */
+ unsigned long *ds_cnt, /* number of data sources in file */
+ rrd_value_t **data)
+{ /* two dimensional array containing the data */
+ int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
+ unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
+ 0;
+ rrd_value_t *srcptr, *dstptr;
+
+ (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
+ dstptr = *data;
+ srcptr = *data;
+ row_cnt = ((*end) - (*start)) / cur_step;
+
+#ifdef DEBUG
+#define DEBUG_REDUCE
+#endif
+#ifdef DEBUG_REDUCE
+ printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
+ row_cnt, reduce_factor, *start, *end, cur_step);
+ for (col = 0; col < row_cnt; col++) {
+ printf("time %10lu: ", *start + (col + 1) * cur_step);
+ for (i = 0; i < *ds_cnt; i++)
+ printf(" %8.2e", srcptr[*ds_cnt * col + i]);
+ printf("\n");
+ }
+#endif
+
+ /* We have to combine [reduce_factor] rows of the source
+ ** into one row for the destination. Doing this we also
+ ** need to take care to combine the correct rows. First
+ ** alter the start and end time so that they are multiples
+ ** of the new step time. We cannot reduce the amount of
+ ** time so we have to move the end towards the future and
+ ** the start towards the past.
+ */
+ end_offset = (*end) % (*step);
+ start_offset = (*start) % (*step);
+
+ /* If there is a start offset (which cannot be more than
+ ** one destination row), skip the appropriate number of
+ ** source rows and one destination row. The appropriate
+ ** number is what we do know (start_offset/cur_step) of
+ ** the new interval (*step/cur_step aka reduce_factor).
+ */
+#ifdef DEBUG_REDUCE
+ printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
+ printf("row_cnt before: %lu\n", row_cnt);
+#endif
+ if (start_offset) {
+ (*start) = (*start) - start_offset;
+ skiprows = reduce_factor - start_offset / cur_step;
+ srcptr += skiprows * *ds_cnt;
+ for (col = 0; col < (*ds_cnt); col++)
+ *dstptr++ = DNAN;
+ row_cnt -= skiprows;
+ }
+#ifdef DEBUG_REDUCE
+ printf("row_cnt between: %lu\n", row_cnt);
+#endif
+
+ /* At the end we have some rows that are not going to be
+ ** used, the amount is end_offset/cur_step
+ */
+ if (end_offset) {
+ (*end) = (*end) - end_offset + (*step);
+ skiprows = end_offset / cur_step;
+ row_cnt -= skiprows;
+ }
+#ifdef DEBUG_REDUCE
+ printf("row_cnt after: %lu\n", row_cnt);
+#endif
+
+/* Sanity check: row_cnt should be multiple of reduce_factor */
+/* if this gets triggered, something is REALLY WRONG ... we die immediately */
+
+ if (row_cnt % reduce_factor) {
+ printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
+ row_cnt, reduce_factor);
+ printf("BUG in reduce_data()\n");
+ exit(1);
+ }
+
+ /* Now combine reduce_factor intervals at a time
+ ** into one interval for the destination.
+ */
+
+ for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
+ for (col = 0; col < (*ds_cnt); col++) {
+ rrd_value_t newval = DNAN;
+ unsigned long validval = 0;
+
+ for (i = 0; i < reduce_factor; i++) {
+ if (isnan(srcptr[i * (*ds_cnt) + col])) {
+ continue;
+ }
+ validval++;
+ if (isnan(newval))
+ newval = srcptr[i * (*ds_cnt) + col];
+ else {
+ switch (cf) {
+ case CF_HWPREDICT:
+ case CF_MHWPREDICT:
+ case CF_DEVSEASONAL:
+ case CF_DEVPREDICT:
+ case CF_SEASONAL:
+ case CF_AVERAGE:
+ newval += srcptr[i * (*ds_cnt) + col];
+ break;
+ case CF_MINIMUM:
+ newval = min(newval, srcptr[i * (*ds_cnt) + col]);
+ break;
+ case CF_FAILURES:
+ /* an interval contains a failure if any subintervals contained a failure */
+ case CF_MAXIMUM:
+ newval = max(newval, srcptr[i * (*ds_cnt) + col]);
+ break;
+ case CF_LAST:
+ newval = srcptr[i * (*ds_cnt) + col];
+ break;
+ }
+ }
+ }
+ if (validval == 0) {
+ newval = DNAN;
+ } else {
+ switch (cf) {
+ case CF_HWPREDICT:
+ case CF_MHWPREDICT:
+ case CF_DEVSEASONAL:
+ case CF_DEVPREDICT:
+ case CF_SEASONAL:
+ case CF_AVERAGE:
+ newval /= validval;
+ break;
+ case CF_MINIMUM:
+ case CF_FAILURES:
+ case CF_MAXIMUM:
+ case CF_LAST:
+ break;
+ }
+ }
+ *dstptr++ = newval;
+ }
+ srcptr += (*ds_cnt) * reduce_factor;
+ row_cnt -= reduce_factor;
+ }
+ /* If we had to alter the endtime, we didn't have enough
+ ** source rows to fill the last row. Fill it with NaN.
+ */
+ if (end_offset)
+ for (col = 0; col < (*ds_cnt); col++)
+ *dstptr++ = DNAN;
+#ifdef DEBUG_REDUCE
+ row_cnt = ((*end) - (*start)) / *step;
+ srcptr = *data;
+ printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
+ row_cnt, *start, *end, *step);
+ for (col = 0; col < row_cnt; col++) {
+ printf("time %10lu: ", *start + (col + 1) * (*step));
+ for (i = 0; i < *ds_cnt; i++)
+ printf(" %8.2e", srcptr[*ds_cnt * col + i]);
+ printf("\n");
+ }
+#endif
+}
+
+
+/* get the data required for the graphs from the
+ relevant rrds ... */
+
+int data_fetch(
+ image_desc_t *im)
+{
+ int i, ii;
+ int skip;
+
+ /* pull the data from the rrd files ... */
+ for (i = 0; i < (int) im->gdes_c; i++) {
+ /* only GF_DEF elements fetch data */
+ if (im->gdes[i].gf != GF_DEF)
+ continue;
+
+ skip = 0;
+ /* do we have it already ? */
+ for (ii = 0; ii < i; ii++) {
+ if (im->gdes[ii].gf != GF_DEF)
+ continue;
+ if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
+ && (im->gdes[i].cf == im->gdes[ii].cf)
+ && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
+ && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
+ && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
+ && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
+ /* OK, the data is already there.
+ ** Just copy the header portion
+ */
+ im->gdes[i].start = im->gdes[ii].start;
+ im->gdes[i].end = im->gdes[ii].end;
+ im->gdes[i].step = im->gdes[ii].step;
+ im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
+ im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
+ im->gdes[i].data = im->gdes[ii].data;
+ im->gdes[i].data_first = 0;
+ skip = 1;
+ }
+ if (skip)
+ break;
+ }
+ if (!skip) {
+ unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
+
+ if ((rrd_fetch_fn(im->gdes[i].rrd,
+ im->gdes[i].cf,
+ &im->gdes[i].start,
+ &im->gdes[i].end,
+ &ft_step,
+ &im->gdes[i].ds_cnt,
+ &im->gdes[i].ds_namv,
+ &im->gdes[i].data)) == -1) {
+ return -1;
+ }
+ im->gdes[i].data_first = 1;
+
+ if (ft_step < im->gdes[i].step) {
+ reduce_data(im->gdes[i].cf_reduce,
+ ft_step,
+ &im->gdes[i].start,
+ &im->gdes[i].end,
+ &im->gdes[i].step,
+ &im->gdes[i].ds_cnt, &im->gdes[i].data);
+ } else {
+ im->gdes[i].step = ft_step;
+ }
+ }
+
+ /* lets see if the required data source is really there */
+ for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
+ if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
+ im->gdes[i].ds = ii;
+ }
+ }
+ if (im->gdes[i].ds == -1) {
+ rrd_set_error("No DS called '%s' in '%s'",
+ im->gdes[i].ds_nam, im->gdes[i].rrd);
+ return -1;
+ }
+
+ }
+ return 0;
+}
+
+/* evaluate the expressions in the CDEF functions */
+
+/*************************************************************
+ * CDEF stuff
+ *************************************************************/
+
+long find_var_wrapper(
+ void *arg1,
+ char *key)
+{
+ return find_var((image_desc_t *) arg1, key);
+}
+
+/* find gdes containing var*/
+long find_var(
+ image_desc_t *im,
+ char *key)
+{
+ long ii;
+
+ for (ii = 0; ii < im->gdes_c - 1; ii++) {
+ if ((im->gdes[ii].gf == GF_DEF
+ || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
+ && (strcmp(im->gdes[ii].vname, key) == 0)) {
+ return ii;
+ }
+ }
+ return -1;
+}
+
+/* find the largest common denominator for all the numbers
+ in the 0 terminated num array */
+long lcd(
+ long *num)
+{
+ long rest;
+ int i;
+
+ for (i = 0; num[i + 1] != 0; i++) {
+ do {
+ rest = num[i] % num[i + 1];
+ num[i] = num[i + 1];
+ num[i + 1] = rest;
+ } while (rest != 0);
+ num[i + 1] = num[i];
+ }
+/* return i==0?num[i]:num[i-1]; */
+ return num[i];
+}
+
+/* run the rpn calculator on all the VDEF and CDEF arguments */
+int data_calc(
+ image_desc_t *im)
+{
+
+ int gdi;
+ int dataidx;
+ long *steparray, rpi;
+ int stepcnt;
+ time_t now;
+ rpnstack_t rpnstack;
+
+ rpnstack_init(&rpnstack);
+
+ for (gdi = 0; gdi < im->gdes_c; gdi++) {
+ /* Look for GF_VDEF and GF_CDEF in the same loop,
+ * so CDEFs can use VDEFs and vice versa
+ */
+ switch (im->gdes[gdi].gf) {
+ case GF_XPORT:
+ break;
+ case GF_SHIFT:{
+ graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
+
+ /* remove current shift */
+ vdp->start -= vdp->shift;
+ vdp->end -= vdp->shift;
+
+ /* vdef */
+ if (im->gdes[gdi].shidx >= 0)
+ vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
+ /* constant */
+ else
+ vdp->shift = im->gdes[gdi].shval;
+
+ /* normalize shift to multiple of consolidated step */
+ vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
+
+ /* apply shift */
+ vdp->start += vdp->shift;
+ vdp->end += vdp->shift;
+ break;
+ }
+ case GF_VDEF:
+ /* A VDEF has no DS. This also signals other parts
+ * of rrdtool that this is a VDEF value, not a CDEF.
+ */
+ im->gdes[gdi].ds_cnt = 0;
+ if (vdef_calc(im, gdi)) {
+ rrd_set_error("Error processing VDEF '%s'",
+ im->gdes[gdi].vname);
+ rpnstack_free(&rpnstack);
+ return -1;
+ }
+ break;
+ case GF_CDEF:
+ im->gdes[gdi].ds_cnt = 1;
+ im->gdes[gdi].ds = 0;
+ im->gdes[gdi].data_first = 1;
+ im->gdes[gdi].start = 0;
+ im->gdes[gdi].end = 0;
+ steparray = NULL;
+ stepcnt = 0;
+ dataidx = -1;
+
+ /* Find the variables in the expression.
+ * - VDEF variables are substituted by their values
+ * and the opcode is changed into OP_NUMBER.
+ * - CDEF variables are analized for their step size,
+ * the lowest common denominator of all the step
+ * sizes of the data sources involved is calculated
+ * and the resulting number is the step size for the
+ * resulting data source.
+ */
+ for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
+ if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
+ im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
+ long ptr = im->gdes[gdi].rpnp[rpi].ptr;
+
+ if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
+#if 0
+ printf
+ ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
+ im->gdes[gdi].vname, im->gdes[ptr].vname);
+ printf("DEBUG: value from vdef is %f\n",
+ im->gdes[ptr].vf.val);
+#endif
+ im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
+ im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
+ } else { /* normal variables and PREF(variables) */
+
+ /* add one entry to the array that keeps track of the step sizes of the
+ * data sources going into the CDEF. */
+ if ((steparray =
+ rrd_realloc(steparray,
+ (++stepcnt +
+ 1) * sizeof(*steparray))) == NULL) {
+ rrd_set_error("realloc steparray");
+ rpnstack_free(&rpnstack);
+ return -1;
+ };
+
+ steparray[stepcnt - 1] = im->gdes[ptr].step;
+
+ /* adjust start and end of cdef (gdi) so
+ * that it runs from the latest start point
+ * to the earliest endpoint of any of the
+ * rras involved (ptr)
+ */
+
+ if (im->gdes[gdi].start < im->gdes[ptr].start)
+ im->gdes[gdi].start = im->gdes[ptr].start;
+
+ if (im->gdes[gdi].end == 0 ||
+ im->gdes[gdi].end > im->gdes[ptr].end)
+ im->gdes[gdi].end = im->gdes[ptr].end;
+
+ /* store pointer to the first element of
+ * the rra providing data for variable,
+ * further save step size and data source
+ * count of this rra
+ */
+ im->gdes[gdi].rpnp[rpi].data =
+ im->gdes[ptr].data + im->gdes[ptr].ds;
+ im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
+ im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
+
+ /* backoff the *.data ptr; this is done so
+ * rpncalc() function doesn't have to treat
+ * the first case differently
+ */
+ } /* if ds_cnt != 0 */
+ } /* if OP_VARIABLE */
+ } /* loop through all rpi */
+
+ /* move the data pointers to the correct period */
+ for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
+ if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
+ im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
+ long ptr = im->gdes[gdi].rpnp[rpi].ptr;
+ long diff =
+ im->gdes[gdi].start - im->gdes[ptr].start;
+
+ if (diff > 0)
+ im->gdes[gdi].rpnp[rpi].data +=
+ (diff / im->gdes[ptr].step) *
+ im->gdes[ptr].ds_cnt;
+ }
+ }
+
+ if (steparray == NULL) {
+ rrd_set_error("rpn expressions without DEF"
+ " or CDEF variables are not supported");
+ rpnstack_free(&rpnstack);
+ return -1;
+ }
+ steparray[stepcnt] = 0;
+ /* Now find the resulting step. All steps in all
+ * used RRAs have to be visited
+ */
+ im->gdes[gdi].step = lcd(steparray);
+ free(steparray);
+ if ((im->gdes[gdi].data = malloc(((im->gdes[gdi].end -
+ im->gdes[gdi].start)
+ / im->gdes[gdi].step)
+ * sizeof(double))) == NULL) {
+ rrd_set_error("malloc im->gdes[gdi].data");
+ rpnstack_free(&rpnstack);
+ return -1;
+ }
+
+ /* Step through the new cdef results array and
+ * calculate the values
+ */
+ for (now = im->gdes[gdi].start + im->gdes[gdi].step;
+ now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
+ rpnp_t *rpnp = im->gdes[gdi].rpnp;
+
+ /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
+ * in this case we are advancing by timesteps;
+ * we use the fact that time_t is a synonym for long
+ */
+ if (rpn_calc(rpnp, &rpnstack, (long) now,
+ im->gdes[gdi].data, ++dataidx) == -1) {
+ /* rpn_calc sets the error string */
+ rpnstack_free(&rpnstack);
+ return -1;
+ }
+ } /* enumerate over time steps within a CDEF */
+ break;
+ default:
+ continue;
+ }
+ } /* enumerate over CDEFs */
+ rpnstack_free(&rpnstack);
+ return 0;
+}
+
+/* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
+/* yes we are loosing precision by doing tos with floats instead of doubles
+ but it seems more stable this way. */
+
+static int AlmostEqual2sComplement(
+ float A,
+ float B,
+ int maxUlps)
+{
+
+ int aInt = *(int *) &A;
+ int bInt = *(int *) &B;
+ int intDiff;
+
+ /* Make sure maxUlps is non-negative and small enough that the
+ default NAN won't compare as equal to anything. */
+
+ /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
+
+ /* Make aInt lexicographically ordered as a twos-complement int */
+
+ if (aInt < 0)
+ aInt = 0x80000000l - aInt;
+
+ /* Make bInt lexicographically ordered as a twos-complement int */
+
+ if (bInt < 0)
+ bInt = 0x80000000l - bInt;
+
+ intDiff = abs(aInt - bInt);
+
+ if (intDiff <= maxUlps)
+ return 1;
+
+ return 0;
+}
+
+/* massage data so, that we get one value for each x coordinate in the graph */
+int data_proc(
+ image_desc_t *im)
+{
+ long i, ii;
+ double pixstep = (double) (im->end - im->start)
+ / (double) im->xsize; /* how much time
+ passes in one pixel */
+ double paintval;
+ double minval = DNAN, maxval = DNAN;
+
+ unsigned long gr_time;
+
+ /* memory for the processed data */
+ for (i = 0; i < im->gdes_c; i++) {
+ if ((im->gdes[i].gf == GF_LINE) ||
+ (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
+ if ((im->gdes[i].p_data = malloc((im->xsize + 1)
+ * sizeof(rrd_value_t))) == NULL) {
+ rrd_set_error("malloc data_proc");
+ return -1;
+ }
+ }
+ }
+
+ for (i = 0; i < im->xsize; i++) { /* for each pixel */
+ long vidx;
+
+ gr_time = im->start + pixstep * i; /* time of the current step */
+ paintval = 0.0;
+
+ for (ii = 0; ii < im->gdes_c; ii++) {
+ double value;
+
+ switch (im->gdes[ii].gf) {
+ case GF_LINE:
+ case GF_AREA:
+ case GF_TICK:
+ if (!im->gdes[ii].stack)
+ paintval = 0.0;
+ value = im->gdes[ii].yrule;
+ if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
+ /* The time of the data doesn't necessarily match
+ ** the time of the graph. Beware.
+ */
+ vidx = im->gdes[ii].vidx;
+ if (im->gdes[vidx].gf == GF_VDEF) {
+ value = im->gdes[vidx].vf.val;
+ } else
+ if (((long int) gr_time >=
+ (long int) im->gdes[vidx].start)
+ && ((long int) gr_time <=
+ (long int) im->gdes[vidx].end)) {
+ value = im->gdes[vidx].data[(unsigned long)
+ floor((double)
+ (gr_time -
+ im->gdes[vidx].
+ start)
+ /
+ im->gdes[vidx].step)
+ * im->gdes[vidx].ds_cnt +
+ im->gdes[vidx].ds];
+ } else {
+ value = DNAN;
+ }
+ };
+
+ if (!isnan(value)) {
+ paintval += value;
+ im->gdes[ii].p_data[i] = paintval;
+ /* GF_TICK: the data values are not
+ ** relevant for min and max
+ */
+ if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
+ if ((isnan(minval) || paintval < minval) &&
+ !(im->logarithmic && paintval <= 0.0))
+ minval = paintval;
+ if (isnan(maxval) || paintval > maxval)
+ maxval = paintval;
+ }
+ } else {
+ im->gdes[ii].p_data[i] = DNAN;
+ }
+ break;
+ case GF_STACK:
+ rrd_set_error
+ ("STACK should already be turned into LINE or AREA here");
+ return -1;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /* if min or max have not been asigned a value this is because
+ there was no data in the graph ... this is not good ...
+ lets set these to dummy values then ... */
+
+ if (im->logarithmic) {
+ if (isnan(minval) || isnan(maxval) || maxval <= 0) {
+ minval = 0.0; /* catching this right away below */
+ maxval = 5.1;
+ }
+ /* in logarithm mode, where minval is smaller or equal
+ to 0 make the beast just way smaller than maxval */
+ if (minval <= 0) {
+ minval = maxval / 10e8;
+ }
+ } else {
+ if (isnan(minval) || isnan(maxval)) {
+ minval = 0.0;
+ maxval = 1.0;
+ }
+ }
+
+ /* adjust min and max values given by the user */
+ /* for logscale we add something on top */
+ if (isnan(im->minval)
+ || ((!im->rigid) && im->minval > minval)
+ ) {
+ if (im->logarithmic)
+ im->minval = minval / 2.0;
+ else
+ im->minval = minval;
+ }
+ if (isnan(im->maxval)
+ || (!im->rigid && im->maxval < maxval)
+ ) {
+ if (im->logarithmic)
+ im->maxval = maxval * 2.0;
+ else
+ im->maxval = maxval;
+ }
+
+ /* make sure min is smaller than max */
+ if (im->minval > im->maxval) {
+ if (im->minval > 0)
+ im->minval = 0.99 * im->maxval;
+ else
+ im->minval = 1.01 * im->maxval;
+ }
+
+ /* make sure min and max are not equal */
+ if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
+ if (im->maxval > 0)
+ im->maxval *= 1.01;
+ else
+ im->maxval *= 0.99;
+
+ /* make sure min and max are not both zero */
+ if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
+ im->maxval = 1.0;
+ }
+ }
+ return 0;
+}
+
+
+
+/* identify the point where the first gridline, label ... gets placed */
+
+time_t find_first_time(
+ time_t start, /* what is the initial time */
+ enum tmt_en baseint, /* what is the basic interval */
+ long basestep /* how many if these do we jump a time */
+ )
+{
+ struct tm tm;
+
+ localtime_r(&start, &tm);
+
+ switch (baseint) {
+ case TMT_SECOND:
+ tm. tm_sec -= tm.tm_sec % basestep;
+
+ break;
+ case TMT_MINUTE:
+ tm. tm_sec = 0;
+ tm. tm_min -= tm.tm_min % basestep;
+
+ break;
+ case TMT_HOUR:
+ tm. tm_sec = 0;
+ tm. tm_min = 0;
+ tm. tm_hour -= tm.tm_hour % basestep;
+
+ break;
+ case TMT_DAY:
+ /* we do NOT look at the basestep for this ... */
+ tm. tm_sec = 0;
+ tm. tm_min = 0;
+ tm. tm_hour = 0;
+
+ break;
+ case TMT_WEEK:
+ /* we do NOT look at the basestep for this ... */
+ tm. tm_sec = 0;
+ tm. tm_min = 0;
+ tm. tm_hour = 0;
+ tm. tm_mday -= tm.tm_wday - 1; /* -1 because we want the monday */
+
+ if (tm.tm_wday == 0)
+ tm. tm_mday -= 7; /* we want the *previous* monday */
+
+ break;
+ case TMT_MONTH:
+ tm. tm_sec = 0;
+ tm. tm_min = 0;
+ tm. tm_hour = 0;
+ tm. tm_mday = 1;
+ tm. tm_mon -= tm.tm_mon % basestep;
+
+ break;
+
+ case TMT_YEAR:
+ tm. tm_sec = 0;
+ tm. tm_min = 0;
+ tm. tm_hour = 0;
+ tm. tm_mday = 1;
+ tm. tm_mon = 0;
+ tm. tm_year -= (
+ tm.tm_year + 1900) %basestep;
+
+ }
+ return mktime(&tm);
+}
+
+/* identify the point where the next gridline, label ... gets placed */
+time_t find_next_time(
+ time_t current, /* what is the initial time */
+ enum tmt_en baseint, /* what is the basic interval */
+ long basestep /* how many if these do we jump a time */
+ )
+{
+ struct tm tm;
+ time_t madetime;
+
+ localtime_r(¤t, &tm);
+
+ do {
+ switch (baseint) {
+ case TMT_SECOND:
+ tm. tm_sec += basestep;
+
+ break;
+ case TMT_MINUTE:
+ tm. tm_min += basestep;
+
+ break;
+ case TMT_HOUR:
+ tm. tm_hour += basestep;
+
+ break;
+ case TMT_DAY:
+ tm. tm_mday += basestep;
+
+ break;
+ case TMT_WEEK:
+ tm. tm_mday += 7 * basestep;
+
+ break;
+ case TMT_MONTH:
+ tm. tm_mon += basestep;
+
+ break;
+ case TMT_YEAR:
+ tm. tm_year += basestep;
+ }
+ madetime = mktime(&tm);
+ } while (madetime == -1); /* this is necessary to skip impssible times
+ like the daylight saving time skips */
+ return madetime;
+
+}
+
+
+/* calculate values required for PRINT and GPRINT functions */
+
+int print_calc(
+ image_desc_t *im)
+{
+ long i, ii, validsteps;
+ double printval;
+ struct tm tmvdef;
+ int graphelement = 0;
+ long vidx;
+ int max_ii;
+ double magfact = -1;
+ char *si_symb = "";
+ char *percent_s;
+ int prline_cnt = 0;
+
+ /* wow initializing tmvdef is quite a task :-) */
+ time_t now = time(NULL);
+
+ localtime_r(&now, &tmvdef);
+ for (i = 0; i < im->gdes_c; i++) {
+ vidx = im->gdes[i].vidx;
+ switch (im->gdes[i].gf) {
+ case GF_PRINT:
+ case GF_GPRINT:
+ /* PRINT and GPRINT can now print VDEF generated values.
+ * There's no need to do any calculations on them as these
+ * calculations were already made.
+ */
+ if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
+ printval = im->gdes[vidx].vf.val;
+ localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
+ } else { /* need to calculate max,min,avg etcetera */
+ max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
+ / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
+ printval = DNAN;
+ validsteps = 0;
+ for (ii = im->gdes[vidx].ds;
+ ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
+ if (!finite(im->gdes[vidx].data[ii]))
+ continue;
+ if (isnan(printval)) {
+ printval = im->gdes[vidx].data[ii];
+ validsteps++;
+ continue;
+ }
+
+ switch (im->gdes[i].cf) {
+ case CF_HWPREDICT:
+ case CF_MHWPREDICT:
+ case CF_DEVPREDICT:
+ case CF_DEVSEASONAL:
+ case CF_SEASONAL:
+ case CF_AVERAGE:
+ validsteps++;
+ printval += im->gdes[vidx].data[ii];
+ break;
+ case CF_MINIMUM:
+ printval = min(printval, im->gdes[vidx].data[ii]);
+ break;
+ case CF_FAILURES:
+ case CF_MAXIMUM:
+ printval = max(printval, im->gdes[vidx].data[ii]);
+ break;
+ case CF_LAST:
+ printval = im->gdes[vidx].data[ii];
+ }
+ }
+ if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
+ if (validsteps > 1) {
+ printval = (printval / validsteps);
+ }
+ }
+ } /* prepare printval */
+
+ if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
+ /* Magfact is set to -1 upon entry to print_calc. If it
+ * is still less than 0, then we need to run auto_scale.
+ * Otherwise, put the value into the correct units. If
+ * the value is 0, then do not set the symbol or magnification
+ * so next the calculation will be performed again. */
+ if (magfact < 0.0) {
+ auto_scale(im, &printval, &si_symb, &magfact);
+ if (printval == 0.0)
+ magfact = -1.0;
+ } else {
+ printval /= magfact;
+ }
+ *(++percent_s) = 's';
+ } else if (strstr(im->gdes[i].format, "%s") != NULL) {
+ auto_scale(im, &printval, &si_symb, &magfact);
+ }
+
+ if (im->gdes[i].gf == GF_PRINT) {
+ rrd_infoval_t prline;
+
+ if (im->gdes[i].strftm) {
+ prline.u_str = malloc((FMT_LEG_LEN + 2) * sizeof(char));
+ strftime(prline.u_str,
+ FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
+ } else if (bad_format(im->gdes[i].format)) {
+ rrd_set_error
+ ("bad format for PRINT in '%s'", im->gdes[i].format);
+ return -1;
+ } else {
+ prline.u_str =
+ sprintf_alloc(im->gdes[i].format, printval, si_symb);
+ }
+ grinfo_push(im,
+ sprintf_alloc
+ ("print[%ld]", prline_cnt++), RD_I_STR, prline);
+ free(prline.u_str);
+ } else {
+ /* GF_GPRINT */
+
+ if (im->gdes[i].strftm) {
+ strftime(im->gdes[i].legend,
+ FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
+ } else {
+ if (bad_format(im->gdes[i].format)) {
+ rrd_set_error
+ ("bad format for GPRINT in '%s'",
+ im->gdes[i].format);
+ return -1;
+ }
+#ifdef HAVE_SNPRINTF
+ snprintf(im->gdes[i].legend,
+ FMT_LEG_LEN - 2,
+ im->gdes[i].format, printval, si_symb);
+#else
+ sprintf(im->gdes[i].legend,
+ im->gdes[i].format, printval, si_symb);
+#endif
+ }
+ graphelement = 1;
+ }
+ break;
+ case GF_LINE:
+ case GF_AREA:
+ case GF_TICK:
+ graphelement = 1;
+ break;
+ case GF_HRULE:
+ if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
+ im->gdes[i].yrule = im->gdes[vidx].vf.val;
+ };
+ graphelement = 1;
+ break;
+ case GF_VRULE:
+ if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
+ im->gdes[i].xrule = im->gdes[vidx].vf.when;
+ };
+ graphelement = 1;
+ break;
+ case GF_COMMENT:
+ case GF_TEXTALIGN:
+ case GF_DEF:
+ case GF_CDEF:
+ case GF_VDEF:
+#ifdef WITH_PIECHART
+ case GF_PART:
+#endif
+ case GF_SHIFT:
+ case GF_XPORT:
+ break;
+ case GF_STACK:
+ rrd_set_error
+ ("STACK should already be turned into LINE or AREA here");
+ return -1;
+ break;
+ }
+ }
+ return graphelement;
+}
+
+
+/* place legends with color spots */
+int leg_place(
+ image_desc_t *im,
+ int *gY)
+{
+ /* graph labels */
+ int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
+ int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
+ int fill = 0, fill_last;
+ int leg_c = 0;
+ int leg_x = border;
+ int leg_y = im->yimg;
+ int leg_y_prev = im->yimg;
+ int leg_cc;
+ int glue = 0;
+ int i, ii, mark = 0;
+ char prt_fctn; /*special printfunctions */
+ char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
+ int *legspace;
+ char *tab;
+
+ if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
+ if ((legspace = malloc(im->gdes_c * sizeof(int))) == NULL) {
+ rrd_set_error("malloc for legspace");
+ return -1;
+ }
+
+ if (im->extra_flags & FULL_SIZE_MODE)
+ leg_y = leg_y_prev =
+ leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size * 1.8);
+ for (i = 0; i < im->gdes_c; i++) {
+ fill_last = fill;
+ /* hide legends for rules which are not displayed */
+ if (im->gdes[i].gf == GF_TEXTALIGN) {
+ default_txtalign = im->gdes[i].txtalign;
+ }
+
+ if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
+ if (im->gdes[i].gf == GF_HRULE
+ && (im->gdes[i].yrule <
+ im->minval || im->gdes[i].yrule > im->maxval))
+ im->gdes[i].legend[0] = '\0';
+ if (im->gdes[i].gf == GF_VRULE
+ && (im->gdes[i].xrule <
+ im->start || im->gdes[i].xrule > im->end))
+ im->gdes[i].legend[0] = '\0';
+ }
+
+ /* turn \\t into tab */
+ while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
+ memmove(tab, tab + 1, strlen(tab));
+ tab[0] = (char) 9;
+ }
+ leg_cc = strlen(im->gdes[i].legend);
+ /* is there a controle code ant the end of the legend string ? */
+ if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
+ prt_fctn = im->gdes[i].legend[leg_cc - 1];
+ leg_cc -= 2;
+ im->gdes[i].legend[leg_cc] = '\0';
+ } else {
+ prt_fctn = '\0';
+ }
+ /* only valid control codes */
+ if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
+ prt_fctn != 'r' &&
+ prt_fctn != 'j' &&
+ prt_fctn != 'c' &&
+ prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
+ free(legspace);
+ rrd_set_error
+ ("Unknown control code at the end of '%s\\%c'",
+ im->gdes[i].legend, prt_fctn);
+ return -1;
+ }
+ /* \n -> \l */
+ if (prt_fctn == 'n') {
+ prt_fctn = 'l';
+ }
+
+ /* remove exess space from the end of the legend for \g */
+ while (prt_fctn == 'g' &&
+ leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
+ leg_cc--;
+ im->gdes[i].legend[leg_cc] = '\0';
+ }
+
+ if (leg_cc != 0) {
+
+ /* no interleg space if string ends in \g */
+ legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
+ if (fill > 0) {
+ fill += legspace[i];
+ }
+ fill +=
+ gfx_get_text_width(im,
+ fill + border,
+ im->
+ text_prop
+ [TEXT_PROP_LEGEND].
+ font_desc,
+ im->tabwidth, im->gdes[i].legend);
+ leg_c++;
+ } else {
+ legspace[i] = 0;
+ }
+ /* who said there was a special tag ... ? */
+ if (prt_fctn == 'g') {
+ prt_fctn = '\0';
+ }
+
+ if (prt_fctn == '\0') {
+ if (i == im->gdes_c - 1 || fill > im->ximg - 2 * border) {
+ /* just one legend item is left right or center */
+ switch (default_txtalign) {
+ case TXA_RIGHT:
+ prt_fctn = 'r';
+ break;
+ case TXA_CENTER:
+ prt_fctn = 'c';
+ break;
+ case TXA_JUSTIFIED:
+ prt_fctn = 'j';
+ break;
+ default:
+ prt_fctn = 'l';
+ break;
+ }
+ }
+ /* is it time to place the legends ? */
+ if (fill > im->ximg - 2 * border) {
+ if (leg_c > 1) {
+ /* go back one */
+ i--;
+ fill = fill_last;
+ leg_c--;
+ }
+ }
+ if (leg_c == 1 && prt_fctn == 'j') {
+ prt_fctn = 'l';
+ }
+ }
+
+
+ if (prt_fctn != '\0') {
+ leg_x = border;
+ if (leg_c >= 2 && prt_fctn == 'j') {
+ glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
+ } else {
+ glue = 0;
+ }
+ if (prt_fctn == 'c')
+ leg_x = (im->ximg - fill) / 2.0;
+ if (prt_fctn == 'r')
+ leg_x = im->ximg - fill - border;
+ for (ii = mark; ii <= i; ii++) {
+ if (im->gdes[ii].legend[0] == '\0')
+ continue; /* skip empty legends */
+ im->gdes[ii].leg_x = leg_x;
+ im->gdes[ii].leg_y = leg_y;
+ leg_x +=
+ gfx_get_text_width(im, leg_x,
+ im->
+ text_prop
+ [TEXT_PROP_LEGEND].
+ font_desc,
+ im->tabwidth, im->gdes[ii].legend)
+ + legspace[ii]
+ + glue;
+ }
+ leg_y_prev = leg_y;
+ if (im->extra_flags & FULL_SIZE_MODE) {
+ /* only add y space if there was text on the line */
+ if (leg_x > border || prt_fctn == 's')
+ leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
+ if (prt_fctn == 's')
+ leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
+ } else {
+ if (leg_x > border || prt_fctn == 's')
+ leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
+ if (prt_fctn == 's')
+ leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
+ }
+ fill = 0;
+ leg_c = 0;
+ mark = ii;
+ }
+ }
+
+ if (im->extra_flags & FULL_SIZE_MODE) {
+ if (leg_y != leg_y_prev) {
+ *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
+ im->yorigin =
+ leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
+ }
+ } else {
+ im->yimg =
+ leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8 +
+ border * 0.6;
+ }
+ free(legspace);
+ }
+ return 0;
+}
+
+/* create a grid on the graph. it determines what to do
+ from the values of xsize, start and end */
+
+/* the xaxis labels are determined from the number of seconds per pixel
+ in the requested graph */
+
+int calc_horizontal_grid(
+ image_desc_t
+ *im)
+{
+ double range;
+ double scaledrange;
+ int pixel, i;
+ int gridind = 0;
+ int decimals, fractionals;
+
+ im->ygrid_scale.labfact = 2;
+ range = im->maxval - im->minval;
+ scaledrange = range / im->magfact;
+ /* does the scale of this graph make it impossible to put lines
+ on it? If so, give up. */
+ if (isnan(scaledrange)) {
+ return 0;
+ }
+
+ /* find grid spaceing */
+ pixel = 1;
+ if (isnan(im->ygridstep)) {
+ if (im->extra_flags & ALTYGRID) {
+ /* find the value with max number of digits. Get number of digits */
+ decimals =
+ ceil(log10
+ (max(fabs(im->maxval), fabs(im->minval)) *
+ im->viewfactor / im->magfact));
+ if (decimals <= 0) /* everything is small. make place for zero */
+ decimals = 1;
+ im->ygrid_scale.gridstep =
+ pow((double) 10,
+ floor(log10(range * im->viewfactor / im->magfact))) /
+ im->viewfactor * im->magfact;
+ if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
+ im->ygrid_scale.gridstep = 0.1;
+ /* should have at least 5 lines but no more then 15 */
+ if (range / im->ygrid_scale.gridstep < 5
+ && im->ygrid_scale.gridstep >= 30)
+ im->ygrid_scale.gridstep /= 10;
+ if (range / im->ygrid_scale.gridstep > 15)
+ im->ygrid_scale.gridstep *= 10;
+ if (range / im->ygrid_scale.gridstep > 5) {
+ im->ygrid_scale.labfact = 1;
+ if (range / im->ygrid_scale.gridstep > 8
+ || im->ygrid_scale.gridstep <
+ 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
+ im->ygrid_scale.labfact = 2;
+ } else {
+ im->ygrid_scale.gridstep /= 5;
+ im->ygrid_scale.labfact = 5;
+ }
+ fractionals =
+ floor(log10
+ (im->ygrid_scale.gridstep *
+ (double) im->ygrid_scale.labfact * im->viewfactor /
+ im->magfact));
+ if (fractionals < 0) { /* small amplitude. */
+ int len = decimals - fractionals + 1;
+
+ if (im->unitslength < len + 2)
+ im->unitslength = len + 2;
+ sprintf(im->ygrid_scale.labfmt,
+ "%%%d.%df%s", len,
+ -fractionals, (im->symbol != ' ' ? " %c" : ""));
+ } else {
+ int len = decimals + 1;
+
+ if (im->unitslength < len + 2)
+ im->unitslength = len + 2;
+ sprintf(im->ygrid_scale.labfmt,
+ "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
+ }
+ } else { /* classic rrd grid */
+ for (i = 0; ylab[i].grid > 0; i++) {
+ pixel = im->ysize / (scaledrange / ylab[i].grid);
+ gridind = i;
+ if (pixel >= 5)
+ break;
+ }
+
+ for (i = 0; i < 4; i++) {
+ if (pixel * ylab[gridind].lfac[i] >=
+ 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
+ im->ygrid_scale.labfact = ylab[gridind].lfac[i];
+ break;
+ }
+ }
+
+ im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
+ }
+ } else {
+ im->ygrid_scale.gridstep = im->ygridstep;
+ im->ygrid_scale.labfact = im->ylabfact;
+ }
+ return 1;
+}
+
+int draw_horizontal_grid(
+ image_desc_t
+ *im)
+{
+ int i;
+ double scaledstep;
+ char graph_label[100];
+ int nlabels = 0;
+ double X0 = im->xorigin;
+ double X1 = im->xorigin + im->xsize;
+ int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
+ int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
+ double MaxY;
+
+ scaledstep =
+ im->ygrid_scale.gridstep /
+ (double) im->magfact * (double) im->viewfactor;
+ MaxY = scaledstep * (double) egrid;
+ for (i = sgrid; i <= egrid; i++) {
+ double Y0 = ytr(im,
+ im->ygrid_scale.gridstep * i);
+ double YN = ytr(im,
+ im->ygrid_scale.gridstep * (i + 1));
+
+ if (floor(Y0 + 0.5) >=
+ im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
+ /* Make sure at least 2 grid labels are shown, even if it doesn't agree
+ with the chosen settings. Add a label if required by settings, or if
+ there is only one label so far and the next grid line is out of bounds. */
+ if (i % im->ygrid_scale.labfact == 0
+ || (nlabels == 1
+ && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
+ if (im->symbol == ' ') {
+ if (im->extra_flags & ALTYGRID) {
+ sprintf(graph_label,
+ im->ygrid_scale.labfmt,
+ scaledstep * (double) i);
+ } else {
+ if (MaxY < 10) {
+ sprintf(graph_label, "%4.1f",
+ scaledstep * (double) i);
+ } else {
+ sprintf(graph_label, "%4.0f",
+ scaledstep * (double) i);
+ }
+ }
+ } else {
+ char sisym = (i == 0 ? ' ' : im->symbol);
+
+ if (im->extra_flags & ALTYGRID) {
+ sprintf(graph_label,
+ im->ygrid_scale.labfmt,
+ scaledstep * (double) i, sisym);
+ } else {
+ if (MaxY < 10) {
+ sprintf(graph_label, "%4.1f %c",
+ scaledstep * (double) i, sisym);
+ } else {
+ sprintf(graph_label, "%4.0f %c",
+ scaledstep * (double) i, sisym);
+ }
+ }
+ }
+ nlabels++;
+ gfx_text(im,
+ X0 -
+ im->
+ text_prop[TEXT_PROP_AXIS].
+ size, Y0,
+ im->graph_col[GRC_FONT],
+ im->
+ text_prop[TEXT_PROP_AXIS].
+ font_desc,
+ im->tabwidth, 0.0,
+ GFX_H_RIGHT, GFX_V_CENTER, graph_label);
+ gfx_line(im, X0 - 2, Y0, X0, Y0,
+ MGRIDWIDTH, im->graph_col[GRC_MGRID]);
+ gfx_line(im, X1, Y0, X1 + 2, Y0,
+ MGRIDWIDTH, im->graph_col[GRC_MGRID]);
+ gfx_dashed_line(im, X0 - 2, Y0,
+ X1 + 2, Y0,
+ MGRIDWIDTH,
+ im->
+ graph_col
+ [GRC_MGRID],
+ im->grid_dash_on, im->grid_dash_off);
+ } else if (!(im->extra_flags & NOMINOR)) {
+ gfx_line(im,
+ X0 - 2, Y0,
+ X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
+ gfx_line(im, X1, Y0, X1 + 2, Y0,
+ GRIDWIDTH, im->graph_col[GRC_GRID]);
+ gfx_dashed_line(im, X0 - 1, Y0,
+ X1 + 1, Y0,
+ GRIDWIDTH,
+ im->
+ graph_col[GRC_GRID],
+ im->grid_dash_on, im->grid_dash_off);
+ }
+ }
+ }
+ return 1;
+}
+
+/* this is frexp for base 10 */
+double frexp10(
+ double,
+ double *);
+double frexp10(
+ double x,
+ double *e)
+{
+ double mnt;
+ int iexp;
+
+ iexp = floor(log(fabs(x)) / log(10));
+ mnt = x / pow(10.0, iexp);
+ if (mnt >= 10.0) {
+ iexp++;
+ mnt = x / pow(10.0, iexp);
+ }
+ *e = iexp;
+ return mnt;
+}
+
+
+/* logaritmic horizontal grid */
+int horizontal_log_grid(
+ image_desc_t
+ *im)
+{
+ double yloglab[][10] = {
+ {
+ 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
+ 0.0, 0.0, 0.0}, {
+ 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
+ 0.0, 0.0, 0.0}, {
+ 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
+ 0.0, 0.0, 0.0}, {
+ 1.0, 2.0, 4.0,
+ 6.0, 8.0, 10.,
+ 0.0,
+ 0.0, 0.0, 0.0}, {
+ 1.0,
+ 2.0,
+ 3.0,
+ 4.0,
+ 5.0,
+ 6.0,
+ 7.0,
+ 8.0,
+ 9.0,
+ 10.},
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
+ };
+ int i, j, val_exp, min_exp;
+ double nex; /* number of decades in data */
+ double logscale; /* scale in logarithmic space */
+ int exfrac = 1; /* decade spacing */
+ int mid = -1; /* row in yloglab for major grid */
+ double mspac; /* smallest major grid spacing (pixels) */
+ int flab; /* first value in yloglab to use */
+ double value, tmp, pre_value;
+ double X0, X1, Y0;
+ char graph_label[100];
+
+ nex = log10(im->maxval / im->minval);
+ logscale = im->ysize / nex;
+ /* major spacing for data with high dynamic range */
+ while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
+ if (exfrac == 1)
+ exfrac = 3;
+ else
+ exfrac += 3;
+ }
+
+ /* major spacing for less dynamic data */
+ do {
+ /* search best row in yloglab */
+ mid++;
+ for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
+ mspac = logscale * log10(10.0 / yloglab[mid][i]);
+ }
+ while (mspac >
+ 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
+ if (mid)
+ mid--;
+ /* find first value in yloglab */
+ for (flab = 0;
+ yloglab[mid][flab] < 10
+ && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
+ if (yloglab[mid][flab] == 10.0) {
+ tmp += 1.0;
+ flab = 0;
+ }
+ val_exp = tmp;
+ if (val_exp % exfrac)
+ val_exp += abs(-val_exp % exfrac);
+ X0 = im->xorigin;
+ X1 = im->xorigin + im->xsize;
+ /* draw grid */
+ pre_value = DNAN;
+ while (1) {
+
+ value = yloglab[mid][flab] * pow(10.0, val_exp);
+ if (AlmostEqual2sComplement(value, pre_value, 4))
+ break; /* it seems we are not converging */
+ pre_value = value;
+ Y0 = ytr(im, value);
+ if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
+ break;
+ /* major grid line */
+ gfx_line(im,
+ X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
+ gfx_line(im, X1, Y0, X1 + 2, Y0,
+ MGRIDWIDTH, im->graph_col[GRC_MGRID]);
+ gfx_dashed_line(im, X0 - 2, Y0,
+ X1 + 2, Y0,
+ MGRIDWIDTH,
+ im->
+ graph_col
+ [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
+ /* label */
+ if (im->extra_flags & FORCE_UNITS_SI) {
+ int scale;
+ double pvalue;
+ char symbol;
+
+ scale = floor(val_exp / 3.0);
+ if (value >= 1.0)
+ pvalue = pow(10.0, val_exp % 3);
+ else
+ pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
+ pvalue *= yloglab[mid][flab];
+ if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
+ && ((scale + si_symbcenter) >= 0))
+ symbol = si_symbol[scale + si_symbcenter];
+ else
+ symbol = '?';
+ sprintf(graph_label, "%3.0f %c", pvalue, symbol);
+ } else
+ sprintf(graph_label, "%3.0e", value);
+ gfx_text(im,
+ X0 -
+ im->
+ text_prop[TEXT_PROP_AXIS].
+ size, Y0,
+ im->graph_col[GRC_FONT],
+ im->
+ text_prop[TEXT_PROP_AXIS].
+ font_desc,
+ im->tabwidth, 0.0,
+ GFX_H_RIGHT, GFX_V_CENTER, graph_label);
+ /* minor grid */
+ if (mid < 4 && exfrac == 1) {
+ /* find first and last minor line behind current major line
+ * i is the first line and j tha last */
+ if (flab == 0) {
+ min_exp = val_exp - 1;
+ for (i = 1; yloglab[mid][i] < 10.0; i++);
+ i = yloglab[mid][i - 1] + 1;
+ j = 10;
+ } else {
+ min_exp = val_exp;
+ i = yloglab[mid][flab - 1] + 1;
+ j = yloglab[mid][flab];
+ }
+
+ /* draw minor lines below current major line */
+ for (; i < j; i++) {
+
+ value = i * pow(10.0, min_exp);
+ if (value < im->minval)
+ continue;
+ Y0 = ytr(im, value);
+ if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
+ break;
+ /* draw lines */
+ gfx_line(im,
+ X0 - 2, Y0,
+ X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
+ gfx_line(im, X1, Y0, X1 + 2, Y0,
+ GRIDWIDTH, im->graph_col[GRC_GRID]);
+ gfx_dashed_line(im, X0 - 1, Y0,
+ X1 + 1, Y0,
+ GRIDWIDTH,
+ im->
+ graph_col[GRC_GRID],
+ im->grid_dash_on, im->grid_dash_off);
+ }
+ } else if (exfrac > 1) {
+ for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
+ value = pow(10.0, i);
+ if (value < im->minval)
+ continue;
+ Y0 = ytr(im, value);
+ if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
+ break;
+ /* draw lines */
+ gfx_line(im,
+ X0 - 2, Y0,
+ X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
+ gfx_line(im, X1, Y0, X1 + 2, Y0,
+ GRIDWIDTH, im->graph_col[GRC_GRID]);
+ gfx_dashed_line(im, X0 - 1, Y0,
+ X1 + 1, Y0,
+ GRIDWIDTH,
+ im->
+ graph_col[GRC_GRID],
+ im->grid_dash_on, im->grid_dash_off);
+ }
+ }
+
+ /* next decade */
+ if (yloglab[mid][++flab] == 10.0) {
+ flab = 0;
+ val_exp += exfrac;
+ }
+ }
+
+ /* draw minor lines after highest major line */
+ if (mid < 4 && exfrac == 1) {
+ /* find first and last minor line below current major line
+ * i is the first line and j tha last */
+ if (flab == 0) {
+ min_exp = val_exp - 1;
+ for (i = 1; yloglab[mid][i] < 10.0; i++);
+ i = yloglab[mid][i - 1] + 1;
+ j = 10;
+ } else {
+ min_exp = val_exp;
+ i = yloglab[mid][flab - 1] + 1;
+ j = yloglab[mid][flab];
+ }
+
+ /* draw minor lines below current major line */
+ for (; i < j; i++) {
+
+ value = i * pow(10.0, min_exp);
+ if (value < im->minval)
+ continue;
+ Y0 = ytr(im, value);
+ if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
+ break;
+ /* draw lines */
+ gfx_line(im,
+ X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
+ gfx_line(im, X1, Y0, X1 + 2, Y0,
+ GRIDWIDTH, im->graph_col[GRC_GRID]);
+ gfx_dashed_line(im, X0 - 1, Y0,
+ X1 + 1, Y0,
+ GRIDWIDTH,
+ im->
+ graph_col[GRC_GRID],
+ im->grid_dash_on, im->grid_dash_off);
+ }
+ }
+ /* fancy minor gridlines */
+ else if (exfrac > 1) {
+ for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
+ value = pow(10.0, i);
+ if (value < im->minval)
+ continue;
+ Y0 = ytr(im, value);
+ if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
+ break;
+ /* draw lines */
+ gfx_line(im,
+ X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
+ gfx_line(im, X1, Y0, X1 + 2, Y0,
+ GRIDWIDTH, im->graph_col[GRC_GRID]);
+ gfx_dashed_line(im, X0 - 1, Y0,
+ X1 + 1, Y0,
+ GRIDWIDTH,
+ im->
+ graph_col[GRC_GRID],
+ im->grid_dash_on, im->grid_dash_off);
+ }
+ }
+
+ return 1;
+}
+
+
+void vertical_grid(
+ image_desc_t *im)
+{
+ int xlab_sel; /* which sort of label and grid ? */
+ time_t ti, tilab, timajor;
+ long factor;
+ char graph_label[100];
+ double X0, Y0, Y1; /* points for filled graph and more */
+ struct tm tm;
+
+ /* the type of time grid is determined by finding
+ the number of seconds per pixel in the graph */
+ if (im->xlab_user.minsec == -1) {
+ factor = (im->end - im->start) / im->xsize;
+ xlab_sel = 0;
+ while (xlab[xlab_sel + 1].minsec !=
+ -1 && xlab[xlab_sel + 1].minsec <= factor) {
+ xlab_sel++;
+ } /* pick the last one */
+ while (xlab[xlab_sel - 1].minsec ==
+ xlab[xlab_sel].minsec
+ && xlab[xlab_sel].length > (im->end - im->start)) {
+ xlab_sel--;
+ } /* go back to the smallest size */
+ im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
+ im->xlab_user.gridst = xlab[xlab_sel].gridst;
+ im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
+ im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
+ im->xlab_user.labtm = xlab[xlab_sel].labtm;
+ im->xlab_user.labst = xlab[xlab_sel].labst;
+ im->xlab_user.precis = xlab[xlab_sel].precis;
+ im->xlab_user.stst = xlab[xlab_sel].stst;
+ }
+
+ /* y coords are the same for every line ... */
+ Y0 = im->yorigin;
+ Y1 = im->yorigin - im->ysize;
+ /* paint the minor grid */
+ if (!(im->extra_flags & NOMINOR)) {
+ for (ti = find_first_time(im->start,
+ im->
+ xlab_user.
+ gridtm,
+ im->
+ xlab_user.
+ gridst),
+ timajor =
+ find_first_time(im->start,
+ im->xlab_user.
+ mgridtm,
+ im->xlab_user.
+ mgridst);
+ ti < im->end;
+ ti =
+ find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
+ ) {
+ /* are we inside the graph ? */
+ if (ti < im->start || ti > im->end)
+ continue;
+ while (timajor < ti) {
+ timajor = find_next_time(timajor,
+ im->
+ xlab_user.
+ mgridtm, im->xlab_user.mgridst);
+ }
+ if (ti == timajor)
+ continue; /* skip as falls on major grid line */
+ X0 = xtr(im, ti);
+ gfx_line(im, X0, Y1 - 2, X0, Y1,
+ GRIDWIDTH, im->graph_col[GRC_GRID]);
+ gfx_line(im, X0, Y0, X0, Y0 + 2,
+ GRIDWIDTH, im->graph_col[GRC_GRID]);
+ gfx_dashed_line(im, X0, Y0 + 1, X0,
+ Y1 - 1, GRIDWIDTH,
+ im->
+ graph_col[GRC_GRID],
+ im->grid_dash_on, im->grid_dash_off);
+ }
+ }
+
+ /* paint the major grid */
+ for (ti = find_first_time(im->start,
+ im->
+ xlab_user.
+ mgridtm,
+ im->
+ xlab_user.
+ mgridst);
+ ti < im->end;
+ ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
+ ) {
+ /* are we inside the graph ? */
+ if (ti < im->start || ti > im->end)
+ continue;
+ X0 = xtr(im, ti);
+ gfx_line(im, X0, Y1 - 2, X0, Y1,
+ MGRIDWIDTH, im->graph_col[GRC_MGRID]);
+ gfx_line(im, X0, Y0, X0, Y0 + 3,
+ MGRIDWIDTH, im->graph_col[GRC_MGRID]);
+ gfx_dashed_line(im, X0, Y0 + 3, X0,
+ Y1 - 2, MGRIDWIDTH,
+ im->
+ graph_col
+ [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
+ }
+ /* paint the labels below the graph */
+ for (ti =
+ find_first_time(im->start -
+ im->xlab_user.
+ precis / 2,
+ im->xlab_user.
+ labtm,
+ im->xlab_user.
+ labst);
+ ti <=
+ im->end -
+ im->xlab_user.precis / 2;
+ ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
+ ) {
+ tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
+ /* are we inside the graph ? */
+ if (tilab < im->start || tilab > im->end)
+ continue;
+#if HAVE_STRFTIME
+ localtime_r(&tilab, &tm);
+ strftime(graph_label, 99, im->xlab_user.stst, &tm);
+#else
+# error "your libc has no strftime I guess we'll abort the exercise here."
+#endif
+ gfx_text(im,
+ xtr(im, tilab),
+ Y0 + 3,
+ im->graph_col[GRC_FONT],
+ im->
+ text_prop[TEXT_PROP_AXIS].
+ font_desc,
+ im->tabwidth, 0.0,
+ GFX_H_CENTER, GFX_V_TOP, graph_label);
+ }
+
+}
+
+
+void axis_paint(
+ image_desc_t *im)
+{
+ /* draw x and y axis */
+ /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
+ im->xorigin+im->xsize,im->yorigin-im->ysize,
+ GRIDWIDTH, im->graph_col[GRC_AXIS]);
+
+ gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
+ im->xorigin+im->xsize,im->yorigin-im->ysize,
+ GRIDWIDTH, im->graph_col[GRC_AXIS]); */
+
+ gfx_line(im, im->xorigin - 4,
+ im->yorigin,
+ im->xorigin + im->xsize +
+ 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
+ gfx_line(im, im->xorigin,
+ im->yorigin + 4,
+ im->xorigin,
+ im->yorigin - im->ysize -
+ 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
+ /* arrow for X and Y axis direction */
+ gfx_new_area(im, im->xorigin + im->xsize + 2, im->yorigin - 3, im->xorigin + im->xsize + 2, im->yorigin + 3, im->xorigin + im->xsize + 7, im->yorigin, /* horyzontal */
+ im->graph_col[GRC_ARROW]);
+ gfx_close_path(im);
+ gfx_new_area(im, im->xorigin - 3, im->yorigin - im->ysize - 2, im->xorigin + 3, im->yorigin - im->ysize - 2, im->xorigin, im->yorigin - im->ysize - 7, /* vertical */
+ im->graph_col[GRC_ARROW]);
+ gfx_close_path(im);
+}
+
+void grid_paint(
+ image_desc_t *im)
+{
+ long i;
+ int res = 0;
+ double X0, Y0; /* points for filled graph and more */
+ struct gfx_color_t water_color;
+
+ /* draw 3d border */
+ gfx_new_area(im, 0, im->yimg,
+ 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
+ gfx_add_point(im, im->ximg - 2, 2);
+ gfx_add_point(im, im->ximg, 0);
+ gfx_add_point(im, 0, 0);
+ gfx_close_path(im);
+ gfx_new_area(im, 2, im->yimg - 2,
+ im->ximg - 2,
+ im->yimg - 2, im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
+ gfx_add_point(im, im->ximg, 0);
+ gfx_add_point(im, im->ximg, im->yimg);
+ gfx_add_point(im, 0, im->yimg);
+ gfx_close_path(im);
+ if (im->draw_x_grid == 1)
+ vertical_grid(im);
+ if (im->draw_y_grid == 1) {
+ if (im->logarithmic) {
+ res = horizontal_log_grid(im);
+ } else {
+ res = draw_horizontal_grid(im);
+ }
+
+ /* dont draw horizontal grid if there is no min and max val */
+ if (!res) {
+ char *nodata = "No Data found";
+
+ gfx_text(im, im->ximg / 2,
+ (2 * im->yorigin -
+ im->ysize) / 2,
+ im->graph_col[GRC_FONT],
+ im->
+ text_prop[TEXT_PROP_AXIS].
+ font_desc,
+ im->tabwidth, 0.0,
+ GFX_H_CENTER, GFX_V_CENTER, nodata);
+ }
+ }
+
+ /* yaxis unit description */
+ gfx_text(im,
+ 10,
+ (im->yorigin -
+ im->ysize / 2),
+ im->graph_col[GRC_FONT],
+ im->
+ text_prop[TEXT_PROP_UNIT].
+ font_desc,
+ im->tabwidth,
+ RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
+ /* graph title */
+ gfx_text(im,
+ im->ximg / 2, 6,
+ im->graph_col[GRC_FONT],
+ im->
+ text_prop[TEXT_PROP_TITLE].
+ font_desc,
+ im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
+ /* rrdtool 'logo' */
+ water_color = im->graph_col[GRC_FONT];
+ water_color.alpha = 0.3;
+ gfx_text(im, im->ximg - 4, 5,
+ water_color,
+ im->
+ text_prop[TEXT_PROP_WATERMARK].
+ font_desc, im->tabwidth,
+ -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
+ /* graph watermark */
+ if (im->watermark[0] != '\0') {
+ gfx_text(im,
+ im->ximg / 2, im->yimg - 6,
+ water_color,
+ im->
+ text_prop[TEXT_PROP_WATERMARK].
+ font_desc, im->tabwidth, 0,
+ GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
+ }
+
+ /* graph labels */
+ if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
+ for (i = 0; i < im->gdes_c; i++) {
+ if (im->gdes[i].legend[0] == '\0')
+ continue;
+ /* im->gdes[i].leg_y is the bottom of the legend */
+ X0 = im->gdes[i].leg_x;
+ Y0 = im->gdes[i].leg_y;
+ gfx_text(im, X0, Y0,
+ im->graph_col[GRC_FONT],
+ im->
+ text_prop
+ [TEXT_PROP_LEGEND].font_desc,
+ im->tabwidth, 0.0,
+ GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
+ /* The legend for GRAPH items starts with "M " to have
+ enough space for the box */
+ if (im->gdes[i].gf != GF_PRINT &&
+ im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
+ double boxH, boxV;
+ double X1, Y1;
+
+ boxH = gfx_get_text_width(im, 0,
+ im->
+ text_prop
+ [TEXT_PROP_LEGEND].
+ font_desc,
+ im->tabwidth, "o") * 1.2;
+ boxV = boxH;
+ /* shift the box up a bit */
+ Y0 -= boxV * 0.4;
+ /* make sure transparent colors show up the same way as in the graph */
+ gfx_new_area(im,
+ X0, Y0 - boxV,
+ X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
+ gfx_add_point(im, X0 + boxH, Y0 - boxV);
+ gfx_close_path(im);
+ gfx_new_area(im, X0, Y0 - boxV, X0,
+ Y0, X0 + boxH, Y0, im->gdes[i].col);
+ gfx_add_point(im, X0 + boxH, Y0 - boxV);
+ gfx_close_path(im);
+ cairo_save(im->cr);
+ cairo_new_path(im->cr);
+ cairo_set_line_width(im->cr, 1.0);
+ X1 = X0 + boxH;
+ Y1 = Y0 - boxV;
+ gfx_line_fit(im, &X0, &Y0);
+ gfx_line_fit(im, &X1, &Y1);
+ cairo_move_to(im->cr, X0, Y0);
+ cairo_line_to(im->cr, X1, Y0);
+ cairo_line_to(im->cr, X1, Y1);
+ cairo_line_to(im->cr, X0, Y1);
+ cairo_close_path(im->cr);
+ cairo_set_source_rgba(im->cr,
+ im->
+ graph_col
+ [GRC_FRAME].
+ red,
+ im->
+ graph_col
+ [GRC_FRAME].
+ green,
+ im->
+ graph_col
+ [GRC_FRAME].
+ blue, im->graph_col[GRC_FRAME].alpha);
+ if (im->gdes[i].dash) {
+ /* make box borders in legend dashed if the graph is dashed */
+ double dashes[] = {
+ 3.0
+ };
+ cairo_set_dash(im->cr, dashes, 1, 0.0);
+ }
+ cairo_stroke(im->cr);
+ cairo_restore(im->cr);
+ }
+ }
+ }
+}
+
+
+/*****************************************************
+ * lazy check make sure we rely need to create this graph
+ *****************************************************/
+
+int lazy_check(
+ image_desc_t *im)
+{
+ FILE *fd = NULL;
+ int size = 1;
+ struct stat imgstat;
+
+ if (im->lazy == 0)
+ return 0; /* no lazy option */
+ if (strlen(im->graphfile) == 0)
+ return 0; /* inmemory option */
+ if (stat(im->graphfile, &imgstat) != 0)
+ return 0; /* can't stat */
+ /* one pixel in the existing graph is more then what we would
+ change here ... */
+ if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
+ return 0;
+ if ((fd = fopen(im->graphfile, "rb")) == NULL)
+ return 0; /* the file does not exist */
+ switch (im->imgformat) {
+ case IF_PNG:
+ size = PngSize(fd, &(im->ximg), &(im->yimg));
+ break;
+ default:
+ size = 1;
+ }
+ fclose(fd);
+ return size;
+}
+
+
+int graph_size_location(
+ image_desc_t
+ *im,
+ int elements)
+{
+ /* The actual size of the image to draw is determined from
+ ** several sources. The size given on the command line is
+ ** the graph area but we need more as we have to draw labels
+ ** and other things outside the graph area
+ */
+
+ int Xvertical = 0, Ytitle =
+ 0, Xylabel = 0, Xmain = 0, Ymain =
+ 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
+
+ if (im->extra_flags & ONLY_GRAPH) {
+ im->xorigin = 0;
+ im->ximg = im->xsize;
+ im->yimg = im->ysize;
+ im->yorigin = im->ysize;
+ ytr(im, DNAN);
+ return 0;
+ }
+
+ /** +---+--------------------------------------------+
+ ** | y |...............graph title..................|
+ ** | +---+-------------------------------+--------+
+ ** | a | y | | |
+ ** | x | | | |
+ ** | i | a | | pie |
+ ** | s | x | main graph area | chart |
+ ** | | i | | area |
+ ** | t | s | | |
+ ** | i | | | |
+ ** | t | l | | |
+ ** | l | b +-------------------------------+--------+
+ ** | e | l | x axis labels | |
+ ** +---+---+-------------------------------+--------+
+ ** |....................legends.....................|
+ ** +------------------------------------------------+
+ ** | watermark |
+ ** +------------------------------------------------+
+ */
+
+ if (im->ylegend[0] != '\0') {
+ Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
+ }
+
+ if (im->title[0] != '\0') {
+ /* The title is placed "inbetween" two text lines so it
+ ** automatically has some vertical spacing. The horizontal
+ ** spacing is added here, on each side.
+ */
+ /* if necessary, reduce the font size of the title until it fits the image width */
+ Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
+ }
+
+ if (elements) {
+ if (im->draw_x_grid) {
+ Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
+ }
+ if (im->draw_y_grid || im->forceleftspace) {
+ Xylabel =
+ gfx_get_text_width(im, 0,
+ im->
+ text_prop
+ [TEXT_PROP_AXIS].
+ font_desc,
+ im->tabwidth, "0") * im->unitslength;
+ }
+ }
+
+ if (im->extra_flags & FULL_SIZE_MODE) {
+ /* The actual size of the image to draw has been determined by the user.
+ ** The graph area is the space remaining after accounting for the legend,
+ ** the watermark, the pie chart, the axis labels, and the title.
+ */
+ im->xorigin = 0;
+ im->ximg = im->xsize;
+ im->yimg = im->ysize;
+ im->yorigin = im->ysize;
+ Xmain = im->ximg;
+ Ymain = im->yimg;
+ im->yorigin += Ytitle;
+ /* Now calculate the total size. Insert some spacing where
+ desired. im->xorigin and im->yorigin need to correspond
+ with the lower left corner of the main graph area or, if
+ this one is not set, the imaginary box surrounding the
+ pie chart area. */
+ /* Initial size calculation for the main graph area */
+ Xmain = im->ximg - (Xylabel + 2 * Xspacing);
+ if (Xmain)
+ Xmain -= Xspacing; /* put space between main graph area and right edge */
+ im->xorigin = Xspacing + Xylabel;
+ /* the length of the title should not influence with width of the graph
+ if (Xtitle > im->ximg) im->ximg = Xtitle; */
+ if (Xvertical) { /* unit description */
+ Xmain -= Xvertical;
+ im->xorigin += Xvertical;
+ }
+ im->xsize = Xmain;
+ xtr(im, 0);
+ /* The vertical size of the image is known in advance. The main graph area
+ ** (Ymain) and im->yorigin must be set according to the space requirements
+ ** of the legend and the axis labels.
+ */
+ if (im->extra_flags & NOLEGEND) {
+ /* set dimensions correctly if using full size mode with no legend */
+ im->yorigin =
+ im->yimg -
+ im->text_prop[TEXT_PROP_AXIS].size * 2.5 - Yspacing;
+ Ymain = im->yorigin;
+ } else {
+ /* Determine where to place the legends onto the image.
+ ** Set Ymain and adjust im->yorigin to match the space requirements.
+ */
+ if (leg_place(im, &Ymain) == -1)
+ return -1;
+ }
+
+
+ /* remove title space *or* some padding above the graph from the main graph area */
+ if (Ytitle) {
+ Ymain -= Ytitle;
+ } else {
+ Ymain -= 1.5 * Yspacing;
+ }
+
+ /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
+ if (im->watermark[0] != '\0') {
+ Ymain -= Ywatermark;
+ }
+
+ im->ysize = Ymain;
+ } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
+
+ /* The actual size of the image to draw is determined from
+ ** several sources. The size given on the command line is
+ ** the graph area but we need more as we have to draw labels
+ ** and other things outside the graph area.
+ */
+
+ if (im->ylegend[0] != '\0') {
+ Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
+ }
+
+
+ if (im->title[0] != '\0') {
+ /* The title is placed "inbetween" two text lines so it
+ ** automatically has some vertical spacing. The horizontal
+ ** spacing is added here, on each side.
+ */
+ /* don't care for the with of the title
+ Xtitle = gfx_get_text_width(im->canvas, 0,
+ im->text_prop[TEXT_PROP_TITLE].font,
+ im->text_prop[TEXT_PROP_TITLE].size,
+ im->tabwidth,
+ im->title, 0) + 2*Xspacing; */
+ Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
+ }
+
+ if (elements) {
+ Xmain = im->xsize;
+ Ymain = im->ysize;
+ }
+ /* Now calculate the total size. Insert some spacing where
+ desired. im->xorigin and im->yorigin need to correspond
+ with the lower left corner of the main graph area or, if
+ this one is not set, the imaginary box surrounding the
+ pie chart area. */
+
+ /* The legend width cannot yet be determined, as a result we
+ ** have problems adjusting the image to it. For now, we just
+ ** forget about it at all; the legend will have to fit in the
+ ** size already allocated.
+ */
+ im->ximg = Xylabel + Xmain + 2 * Xspacing;
+ if (Xmain)
+ im->ximg += Xspacing;
+ im->xorigin = Xspacing + Xylabel;
+ /* the length of the title should not influence with width of the graph
+ if (Xtitle > im->ximg) im->ximg = Xtitle; */
+ if (Xvertical) { /* unit description */
+ im->ximg += Xvertical;
+ im->xorigin += Xvertical;
+ }
+ xtr(im, 0);
+ /* The vertical size is interesting... we need to compare
+ ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
+ ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
+ ** in order to start even thinking about Ylegend or Ywatermark.
+ **
+ ** Do it in three portions: First calculate the inner part,
+ ** then do the legend, then adjust the total height of the img,
+ ** adding space for a watermark if one exists;
+ */
+ /* reserve space for main and/or pie */
+ im->yimg = Ymain + Yxlabel;
+ im->yorigin = im->yimg - Yxlabel;
+ /* reserve space for the title *or* some padding above the graph */
+ if (Ytitle) {
+ im->yimg += Ytitle;
+ im->yorigin += Ytitle;
+ } else {
+ im->yimg += 1.5 * Yspacing;
+ im->yorigin += 1.5 * Yspacing;
+ }
+ /* reserve space for padding below the graph */
+ im->yimg += Yspacing;
+ /* Determine where to place the legends onto the image.
+ ** Adjust im->yimg to match the space requirements.
+ */
+ if (leg_place(im, 0) == -1)
+ return -1;
+ if (im->watermark[0] != '\0') {
+ im->yimg += Ywatermark;
+ }
+ }
+
+ ytr(im, DNAN);
+ return 0;
+}
+
+static cairo_status_t cairo_output(
+ void *closure,
+ const unsigned char
+ *data,
+ unsigned int length)
+{
+ image_desc_t *im = closure;
+
+ im->rendered_image =
+ realloc(im->rendered_image, im->rendered_image_size + length);
+ if (im->rendered_image == NULL)
+ return CAIRO_STATUS_WRITE_ERROR;
+ memcpy(im->rendered_image + im->rendered_image_size, data, length);
+ im->rendered_image_size += length;
+ return CAIRO_STATUS_SUCCESS;
+}
+
+/* draw that picture thing ... */
+int graph_paint(
+ image_desc_t *im)
+{
+ int i, ii;
+ int lazy = lazy_check(im);
+ double areazero = 0.0;
+ graph_desc_t *lastgdes = NULL;
+ rrd_infoval_t info;
+
+// PangoFontMap *font_map = pango_cairo_font_map_get_default();
+
+ /* if we want and can be lazy ... quit now */
+ if (lazy) {
+ info.u_cnt = im->ximg;
+ grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
+ info.u_cnt = im->yimg;
+ grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
+ return 0;
+ }
+ /* pull the data from the rrd files ... */
+ if (data_fetch(im) == -1)
+ return -1;
+ /* evaluate VDEF and CDEF operations ... */
+ if (data_calc(im) == -1)
+ return -1;
+ /* calculate and PRINT and GPRINT definitions. We have to do it at
+ * this point because it will affect the length of the legends
+ * if there are no graph elements (i==0) we stop here ...
+ * if we are lazy, try to quit ...
+ */
+ i = print_calc(im);
+ if (i < 0)
+ return -1;
+
+ if ((i == 0) || lazy)
+ return 0;
+
+/**************************************************************
+ *** Calculating sizes and locations became a bit confusing ***
+ *** so I moved this into a separate function. ***
+ **************************************************************/
+ if (graph_size_location(im, i) == -1)
+ return -1;
+
+ info.u_cnt = im->xorigin;
+ grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
+ info.u_cnt = im->yorigin - im->ysize;
+ grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
+ info.u_cnt = im->xsize;
+ grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
+ info.u_cnt = im->ysize;
+ grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
+ info.u_cnt = im->ximg;
+ grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
+ info.u_cnt = im->yimg;
+ grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
+
+ /* get actual drawing data and find min and max values */
+ if (data_proc(im) == -1)
+ return -1;
+ if (!im->logarithmic) {
+ si_unit(im);
+ }
+
+ /* identify si magnitude Kilo, Mega Giga ? */
+ if (!im->rigid && !im->logarithmic)
+ expand_range(im); /* make sure the upper and lower limit are
+ sensible values */
+
+ info.u_val = im->minval;
+ grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
+ info.u_val = im->maxval;
+ grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
+
+ if (!calc_horizontal_grid(im))
+ return -1;
+ /* reset precalc */
+ ytr(im, DNAN);
+/* if (im->gridfit)
+ apply_gridfit(im); */
+ /* the actual graph is created by going through the individual
+ graph elements and then drawing them */
+ cairo_surface_destroy(im->surface);
+ switch (im->imgformat) {
+ case IF_PNG:
+ im->surface =
+ cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
+ im->ximg * im->zoom,
+ im->yimg * im->zoom);
+ break;
+ case IF_PDF:
+ im->gridfit = 0;
+ im->surface = strlen(im->graphfile)
+ ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
+ im->yimg * im->zoom)
+ : cairo_pdf_surface_create_for_stream
+ (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
+ break;
+ case IF_EPS:
+ im->gridfit = 0;
+ im->surface = strlen(im->graphfile)
+ ?
+ cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
+ im->yimg * im->zoom)
+ : cairo_ps_surface_create_for_stream
+ (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
+ break;
+ case IF_SVG:
+ im->gridfit = 0;
+ im->surface = strlen(im->graphfile)
+ ?
+ cairo_svg_surface_create(im->
+ graphfile,
+ im->ximg * im->zoom, im->yimg * im->zoom)
+ : cairo_svg_surface_create_for_stream
+ (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
+ cairo_svg_surface_restrict_to_version
+ (im->surface, CAIRO_SVG_VERSION_1_1);
+ break;
+ };
+ cairo_destroy(im->cr);
+ im->cr = cairo_create(im->surface);
+ cairo_set_antialias(im->cr, im->graph_antialias);
+ cairo_scale(im->cr, im->zoom, im->zoom);
+// pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
+ gfx_new_area(im, 0, 0, 0, im->yimg,
+ im->ximg, im->yimg, im->graph_col[GRC_BACK]);
+ gfx_add_point(im, im->ximg, 0);
+ gfx_close_path(im);
+ gfx_new_area(im, im->xorigin,
+ im->yorigin,
+ im->xorigin +
+ im->xsize, im->yorigin,
+ im->xorigin +
+ im->xsize,
+ im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
+ gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
+ gfx_close_path(im);
+ cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
+ im->xsize, im->ysize + 2.0);
+ cairo_clip(im->cr);
+ if (im->minval > 0.0)
+ areazero = im->minval;
+ if (im->maxval < 0.0)
+ areazero = im->maxval;
+ for (i = 0; i < im->gdes_c; i++) {
+ switch (im->gdes[i].gf) {
+ case GF_CDEF:
+ case GF_VDEF:
+ case GF_DEF:
+ case GF_PRINT:
+ case GF_GPRINT:
+ case GF_COMMENT:
+ case GF_TEXTALIGN:
+ case GF_HRULE:
+ case GF_VRULE:
+ case GF_XPORT:
+ case GF_SHIFT:
+ break;
+ case GF_TICK:
+ for (ii = 0; ii < im->xsize; ii++) {
+ if (!isnan(im->gdes[i].p_data[ii])
+ && im->gdes[i].p_data[ii] != 0.0) {
+ if (im->gdes[i].yrule > 0) {
+ gfx_line(im,
+ im->xorigin + ii,
+ im->yorigin,
+ im->xorigin + ii,
+ im->yorigin -
+ im->gdes[i].yrule *
+ im->ysize, 1.0, im->gdes[i].col);
+ } else if (im->gdes[i].yrule < 0) {
+ gfx_line(im,
+ im->xorigin + ii,
+ im->yorigin - im->ysize,
+ im->xorigin + ii,
+ im->yorigin - (1 -
+ im->gdes[i].
+ yrule) *
+ im->ysize, 1.0, im->gdes[i].col);
+ }
+ }
+ }
+ break;
+ case GF_LINE:
+ case GF_AREA:
+ /* fix data points at oo and -oo */
+ for (ii = 0; ii < im->xsize; ii++) {
+ if (isinf(im->gdes[i].p_data[ii])) {
+ if (im->gdes[i].p_data[ii] > 0) {
+ im->gdes[i].p_data[ii] = im->maxval;
+ } else {
+ im->gdes[i].p_data[ii] = im->minval;
+ }
+
+ }
+ } /* for */
+
+ /* *******************************************************
+ a ___. (a,t)
+ | | ___
+ ____| | | |
+ | |___|
+ -------|--t-1--t--------------------------------
+
+ if we know the value at time t was a then
+ we draw a square from t-1 to t with the value a.
+
+ ********************************************************* */
+ if (im->gdes[i].col.alpha != 0.0) {
+ /* GF_LINE and friend */
+ if (im->gdes[i].gf == GF_LINE) {
+ double last_y = 0.0;
+ int draw_on = 0;
+
+ cairo_save(im->cr);
+ cairo_new_path(im->cr);
+ cairo_set_line_width(im->cr, im->gdes[i].linewidth);
+ if (im->gdes[i].dash) {
+ cairo_set_dash(im->cr,
+ im->gdes[i].p_dashes,
+ im->gdes[i].ndash, im->gdes[i].offset);
+ }
+
+ for (ii = 1; ii < im->xsize; ii++) {
+ if (isnan(im->gdes[i].p_data[ii])
+ || (im->slopemode == 1
+ && isnan(im->gdes[i].p_data[ii - 1]))) {
+ draw_on = 0;
+ continue;
+ }
+ if (draw_on == 0) {
+ last_y = ytr(im, im->gdes[i].p_data[ii]);
+ if (im->slopemode == 0) {
+ double x = ii - 1 + im->xorigin;
+ double y = last_y;
+
+ gfx_line_fit(im, &x, &y);
+ cairo_move_to(im->cr, x, y);
+ x = ii + im->xorigin;
+ y = last_y;
+ gfx_line_fit(im, &x, &y);
+ cairo_line_to(im->cr, x, y);
+ } else {
+ double x = ii - 1 + im->xorigin;
+ double y =
+ ytr(im, im->gdes[i].p_data[ii - 1]);
+ gfx_line_fit(im, &x, &y);
+ cairo_move_to(im->cr, x, y);
+ x = ii + im->xorigin;
+ y = last_y;
+ gfx_line_fit(im, &x, &y);
+ cairo_line_to(im->cr, x, y);
+ }
+ draw_on = 1;
+ } else {
+ double x1 = ii + im->xorigin;
+ double y1 = ytr(im, im->gdes[i].p_data[ii]);
+
+ if (im->slopemode == 0
+ && !AlmostEqual2sComplement(y1, last_y, 4)) {
+ double x = ii - 1 + im->xorigin;
+ double y = y1;
+
+ gfx_line_fit(im, &x, &y);
+ cairo_line_to(im->cr, x, y);
+ };
+ last_y = y1;
+ gfx_line_fit(im, &x1, &y1);
+ cairo_line_to(im->cr, x1, y1);
+ };
+ }
+ cairo_set_source_rgba(im->cr,
+ im->gdes[i].
+ col.red,
+ im->gdes[i].
+ col.green,
+ im->gdes[i].
+ col.blue, im->gdes[i].col.alpha);
+ cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
+ cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
+ cairo_stroke(im->cr);
+ cairo_restore(im->cr);
+ } else {
+ int idxI = -1;
+ double *foreY =
+ (double *) malloc(sizeof(double) * im->xsize * 2);
+ double *foreX =
+ (double *) malloc(sizeof(double) * im->xsize * 2);
+ double *backY =
+ (double *) malloc(sizeof(double) * im->xsize * 2);
+ double *backX =
+ (double *) malloc(sizeof(double) * im->xsize * 2);
+ int drawem = 0;
+
+ for (ii = 0; ii <= im->xsize; ii++) {
+ double ybase, ytop;
+
+ if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
+ int cntI = 1;
+ int lastI = 0;
+
+ while (cntI < idxI
+ &&
+ AlmostEqual2sComplement(foreY
+ [lastI],
+ foreY[cntI], 4)
+ &&
+ AlmostEqual2sComplement(foreY
+ [lastI],
+ foreY
+ [cntI + 1], 4)) {
+ cntI++;
+ }
+ gfx_new_area(im,
+ backX[0], backY[0],
+ foreX[0], foreY[0],
+ foreX[cntI],
+ foreY[cntI], im->gdes[i].col);
+ while (cntI < idxI) {
+ lastI = cntI;
+ cntI++;
+ while (cntI < idxI
+ &&
+ AlmostEqual2sComplement(foreY
+ [lastI],
+ foreY[cntI], 4)
+ &&
+ AlmostEqual2sComplement(foreY
+ [lastI],
+ foreY
+ [cntI
+ + 1], 4)) {
+ cntI++;
+ }
+ gfx_add_point(im, foreX[cntI], foreY[cntI]);
+ }
+ gfx_add_point(im, backX[idxI], backY[idxI]);
+ while (idxI > 1) {
+ lastI = idxI;
+ idxI--;
+ while (idxI > 1
+ &&
+ AlmostEqual2sComplement(backY
+ [lastI],
+ backY[idxI], 4)
+ &&
+ AlmostEqual2sComplement(backY
+ [lastI],
+ backY
+ [idxI
+ - 1], 4)) {
+ idxI--;
+ }
+ gfx_add_point(im, backX[idxI], backY[idxI]);
+ }
+ idxI = -1;
+ drawem = 0;
+ gfx_close_path(im);
+ }
+ if (drawem != 0) {
+ drawem = 0;
+ idxI = -1;
+ }
+ if (ii == im->xsize)
+ break;
+ if (im->slopemode == 0 && ii == 0) {
+ continue;
+ }
+ if (isnan(im->gdes[i].p_data[ii])) {
+ drawem = 1;
+ continue;
+ }
+ ytop = ytr(im, im->gdes[i].p_data[ii]);
+ if (lastgdes && im->gdes[i].stack) {
+ ybase = ytr(im, lastgdes->p_data[ii]);
+ } else {
+ ybase = ytr(im, areazero);
+ }
+ if (ybase == ytop) {
+ drawem = 1;
+ continue;
+ }
+
+ if (ybase > ytop) {
+ double extra = ytop;
+
+ ytop = ybase;
+ ybase = extra;
+ }
+ if (im->slopemode == 0) {
+ backY[++idxI] = ybase - 0.2;
+ backX[idxI] = ii + im->xorigin - 1;
+ foreY[idxI] = ytop + 0.2;
+ foreX[idxI] = ii + im->xorigin - 1;
+ }
+ backY[++idxI] = ybase - 0.2;
+ backX[idxI] = ii + im->xorigin;
+ foreY[idxI] = ytop + 0.2;
+ foreX[idxI] = ii + im->xorigin;
+ }
+ /* close up any remaining area */
+ free(foreY);
+ free(foreX);
+ free(backY);
+ free(backX);
+ } /* else GF_LINE */
+ }
+ /* if color != 0x0 */
+ /* make sure we do not run into trouble when stacking on NaN */
+ for (ii = 0; ii < im->xsize; ii++) {
+ if (isnan(im->gdes[i].p_data[ii])) {
+ if (lastgdes && (im->gdes[i].stack)) {
+ im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
+ } else {
+ im->gdes[i].p_data[ii] = areazero;
+ }
+ }
+ }
+ lastgdes = &(im->gdes[i]);
+ break;
+ case GF_STACK:
+ rrd_set_error
+ ("STACK should already be turned into LINE or AREA here");
+ return -1;
+ break;
+ } /* switch */
+ }
+ cairo_reset_clip(im->cr);
+
+ /* grid_paint also does the text */
+ if (!(im->extra_flags & ONLY_GRAPH))
+ grid_paint(im);
+ if (!(im->extra_flags & ONLY_GRAPH))
+ axis_paint(im);
+ /* the RULES are the last thing to paint ... */
+ for (i = 0; i < im->gdes_c; i++) {
+
+ switch (im->gdes[i].gf) {
+ case GF_HRULE:
+ if (im->gdes[i].yrule >= im->minval
+ && im->gdes[i].yrule <= im->maxval) {
+ cairo_save(im->cr);
+ if (im->gdes[i].dash) {
+ cairo_set_dash(im->cr,
+ im->gdes[i].p_dashes,
+ im->gdes[i].ndash, im->gdes[i].offset);
+ }
+ gfx_line(im, im->xorigin,
+ ytr(im, im->gdes[i].yrule),
+ im->xorigin + im->xsize,
+ ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
+ cairo_stroke(im->cr);
+ cairo_restore(im->cr);
+ }
+ break;
+ case GF_VRULE:
+ if (im->gdes[i].xrule >= im->start
+ && im->gdes[i].xrule <= im->end) {
+ cairo_save(im->cr);
+ if (im->gdes[i].dash) {
+ cairo_set_dash(im->cr,
+ im->gdes[i].p_dashes,
+ im->gdes[i].ndash, im->gdes[i].offset);
+ }
+ gfx_line(im,
+ xtr(im, im->gdes[i].xrule),
+ im->yorigin, xtr(im,
+ im->
+ gdes[i].
+ xrule),
+ im->yorigin - im->ysize, 1.0, im->gdes[i].col);
+ cairo_stroke(im->cr);
+ cairo_restore(im->cr);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+
+ switch (im->imgformat) {
+ case IF_PNG:
+ {
+ cairo_status_t status;
+
+ status = strlen(im->graphfile) ?
+ cairo_surface_write_to_png(im->surface, im->graphfile)
+ : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
+ im);
+
+ if (status != CAIRO_STATUS_SUCCESS) {
+ rrd_set_error("Could not save png to '%s'", im->graphfile);
+ return 1;
+ }
+ break;
+ }
+ default:
+ if (strlen(im->graphfile)) {
+ cairo_show_page(im->cr);
+ } else {
+ cairo_surface_finish(im->surface);
+ }
+ break;
+ }
+
+ return 0;
+}
+
+
+/*****************************************************
+ * graph stuff
+ *****************************************************/
+
+int gdes_alloc(
+ image_desc_t *im)
+{
+
+ im->gdes_c++;
+ if ((im->gdes = (graph_desc_t *)
+ rrd_realloc(im->gdes, (im->gdes_c)
+ * sizeof(graph_desc_t))) == NULL) {
+ rrd_set_error("realloc graph_descs");
+ return -1;
+ }
+
+
+ im->gdes[im->gdes_c - 1].step = im->step;
+ im->gdes[im->gdes_c - 1].step_orig = im->step;
+ im->gdes[im->gdes_c - 1].stack = 0;
+ im->gdes[im->gdes_c - 1].linewidth = 0;
+ im->gdes[im->gdes_c - 1].debug = 0;
+ im->gdes[im->gdes_c - 1].start = im->start;
+ im->gdes[im->gdes_c - 1].start_orig = im->start;
+ im->gdes[im->gdes_c - 1].end = im->end;
+ im->gdes[im->gdes_c - 1].end_orig = im->end;
+ im->gdes[im->gdes_c - 1].vname[0] = '\0';
+ im->gdes[im->gdes_c - 1].data = NULL;
+ im->gdes[im->gdes_c - 1].ds_namv = NULL;
+ im->gdes[im->gdes_c - 1].data_first = 0;
+ im->gdes[im->gdes_c - 1].p_data = NULL;
+ im->gdes[im->gdes_c - 1].rpnp = NULL;
+ im->gdes[im->gdes_c - 1].p_dashes = NULL;
+ im->gdes[im->gdes_c - 1].shift = 0.0;
+ im->gdes[im->gdes_c - 1].dash = 0;
+ im->gdes[im->gdes_c - 1].ndash = 0;
+ im->gdes[im->gdes_c - 1].offset = 0;
+ im->gdes[im->gdes_c - 1].col.red = 0.0;
+ im->gdes[im->gdes_c - 1].col.green = 0.0;
+ im->gdes[im->gdes_c - 1].col.blue = 0.0;
+ im->gdes[im->gdes_c - 1].col.alpha = 0.0;
+ im->gdes[im->gdes_c - 1].legend[0] = '\0';
+ im->gdes[im->gdes_c - 1].format[0] = '\0';
+ im->gdes[im->gdes_c - 1].strftm = 0;
+ im->gdes[im->gdes_c - 1].rrd[0] = '\0';
+ im->gdes[im->gdes_c - 1].ds = -1;
+ im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
+ im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
+ im->gdes[im->gdes_c - 1].yrule = DNAN;
+ im->gdes[im->gdes_c - 1].xrule = 0;
+ return 0;
+}
+
+/* copies input untill the first unescaped colon is found
+ or until input ends. backslashes have to be escaped as well */
+int scan_for_col(
+ const char *const input,
+ int len,
+ char *const output)
+{
+ int inp, outp = 0;
+
+ for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
+ if (input[inp] == '\\'
+ && input[inp + 1] != '\0'
+ && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
+ output[outp++] = input[++inp];
+ } else {
+ output[outp++] = input[inp];
+ }
+ }
+ output[outp] = '\0';
+ return inp;
+}
+
+/* Now just a wrapper around rrd_graph_v */
+int rrd_graph(
+ int argc,
+ char **argv,
+ char ***prdata,
+ int *xsize,
+ int *ysize,
+ FILE * stream,
+ double *ymin,
+ double *ymax)
+{
+ int prlines = 0;
+ rrd_info_t *grinfo = NULL;
+ rrd_info_t *walker;
+
+ grinfo = rrd_graph_v(argc, argv);
+ if (grinfo == NULL)
+ return -1;
+ walker = grinfo;
+ (*prdata) = NULL;
+ while (walker) {
+ if (strcmp(walker->key, "image_info") == 0) {
+ prlines++;
+ if (((*prdata) =
+ rrd_realloc((*prdata),
+ (prlines + 1) * sizeof(char *))) == NULL) {
+ rrd_set_error("realloc prdata");
+ return 0;
+ }
+ /* imginfo goes to position 0 in the prdata array */
+ (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
+ + 2) * sizeof(char));
+ strcpy((*prdata)[prlines - 1], walker->value.u_str);
+ (*prdata)[prlines] = NULL;
+ }
+ /* skip anything else */
+ walker = walker->next;
+ }
+ walker = grinfo;
+ *xsize = 0;
+ *ysize = 0;
+ *ymin = 0;
+ *ymax = 0;
+ while (walker) {
+ if (strcmp(walker->key, "image_width") == 0) {
+ *xsize = walker->value.u_int;
+ } else if (strcmp(walker->key, "image_height") == 0) {
+ *ysize = walker->value.u_int;
+ } else if (strcmp(walker->key, "value_min") == 0) {
+ *ymin = walker->value.u_val;
+ } else if (strcmp(walker->key, "value_max") == 0) {
+ *ymax = walker->value.u_val;
+ } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
+ prlines++;
+ if (((*prdata) =
+ rrd_realloc((*prdata),
+ (prlines + 1) * sizeof(char *))) == NULL) {
+ rrd_set_error("realloc prdata");
+ return 0;
+ }
+ (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
+ + 2) * sizeof(char));
+ (*prdata)[prlines] = NULL;
+ strcpy((*prdata)[prlines - 1], walker->value.u_str);
+ } else if (strcmp(walker->key, "image") == 0) {
+ fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
+ (stream ? stream : stdout));
+ }
+ /* skip anything else */
+ walker = walker->next;
+ }
+ rrd_info_free(grinfo);
+ return 0;
+}
+
+
+/* Some surgery done on this function, it became ridiculously big.
+** Things moved:
+** - initializing now in rrd_graph_init()
+** - options parsing now in rrd_graph_options()
+** - script parsing now in rrd_graph_script()
+*/
+rrd_info_t *rrd_graph_v(
+ int argc,
+ char **argv)
+{
+ image_desc_t im;
+ rrd_info_t *grinfo;
+ rrd_graph_init(&im);
+ /* a dummy surface so that we can measure text sizes for placements */
+
+ rrd_graph_options(argc, argv, &im);
+ if (rrd_test_error()) {
+ rrd_info_free(im.grinfo);
+ im_free(&im);
+ return NULL;
+ }
+
+ if (optind >= argc) {
+ rrd_info_free(im.grinfo);
+ im_free(&im);
+ rrd_set_error("missing filename");
+ return NULL;
+ }
+
+ if (strlen(argv[optind]) >= MAXPATH) {
+ rrd_set_error("filename (including path) too long");
+ rrd_info_free(im.grinfo);
+ im_free(&im);
+ return NULL;
+ }
+
+ strncpy(im.graphfile, argv[optind], MAXPATH - 1);
+ im.graphfile[MAXPATH - 1] = '\0';
+
+ if (strcmp(im.graphfile, "-") == 0) {
+ im.graphfile[0] = '\0';
+ }
+
+ rrd_graph_script(argc, argv, &im, 1);
+ if (rrd_test_error()) {
+ rrd_info_free(im.grinfo);
+ im_free(&im);
+ return NULL;
+ }
+
+ /* Everything is now read and the actual work can start */
+
+ if (graph_paint(&im) == -1) {
+ rrd_info_free(im.grinfo);
+ im_free(&im);
+ return NULL;
+ }
+
+
+ /* The image is generated and needs to be output.
+ ** Also, if needed, print a line with information about the image.
+ */
+
+ if (im.imginfo) {
+ rrd_infoval_t info;
+ char *filename;
+
+ filename = im.graphfile + strlen(im.graphfile);
+ while (filename > im.graphfile) {
+ if (*(filename - 1) == '/' || *(filename - 1) == '\\')
+ break;
+ filename--;
+ }
+ info.u_str =
+ sprintf_alloc(im.imginfo,
+ filename,
+ (long) (im.zoom *
+ im.ximg), (long) (im.zoom * im.yimg));
+ grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
+ free(info.u_str);
+ }
+ if (im.rendered_image) {
+ rrd_infoval_t img;
+
+ img.u_blo.size = im.rendered_image_size;
+ img.u_blo.ptr = im.rendered_image;
+ grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
+ }
+ grinfo = im.grinfo;
+ im_free(&im);
+ return grinfo;
+}
+
+static void
+rrd_set_font_desc (
+ image_desc_t *im,int prop,char *font, double size ){
+ strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
+ im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
+ im->text_prop[prop].size = size;
+ im->text_prop[prop].font_desc = pango_font_description_from_string( font );
+ pango_font_description_set_size(im->text_prop[prop].font_desc, size * PANGO_SCALE);
+}
+
+void rrd_graph_init(
+ image_desc_t
+ *im)
+{
+ unsigned int i;
+ char *deffont = getenv("RRD_DEFAULT_FONT");
+ static PangoFontMap *fontmap = NULL;
+ PangoContext *context;
+
+#ifdef HAVE_TZSET
+ tzset();
+#endif
+#ifdef HAVE_SETLOCALE
+ setlocale(LC_TIME, "");
+#ifdef HAVE_MBSTOWCS
+ setlocale(LC_CTYPE, "");
+#endif
+#endif
+ im->base = 1000;
+ im->draw_x_grid = 1;
+ im->draw_y_grid = 1;
+ im->extra_flags = 0;
+ im->font_options = cairo_font_options_create();
+ im->forceleftspace = 0;
+ im->gdes_c = 0;
+ im->gdes = NULL;
+ im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
+ im->grid_dash_off = 1;
+ im->grid_dash_on = 1;
+ im->gridfit = 1;
+ im->grinfo = (rrd_info_t *) NULL;
+ im->grinfo_current = (rrd_info_t *) NULL;
+ im->imgformat = IF_PNG;
+ im->imginfo = NULL;
+ im->lazy = 0;
+ im->logarithmic = 0;
+ im->maxval = DNAN;
+ im->minval = 0;
+ im->minval = DNAN;
+ im->prt_c = 0;
+ im->rigid = 0;
+ im->rendered_image_size = 0;
+ im->rendered_image = NULL;
+ im->slopemode = 0;
+ im->step = 0;
+ im->symbol = ' ';
+ im->tabwidth = 40.0;
+ im->title[0] = '\0';
+ im->unitsexponent = 9999;
+ im->unitslength = 6;
+ im->viewfactor = 1.0;
+ im->watermark[0] = '\0';
+ im->with_markup = 0;
+ im->ximg = 0;
+ im->xlab_user.minsec = -1;
+ im->xorigin = 0;
+ im->xsize = 400;
+ im->ygridstep = DNAN;
+ im->yimg = 0;
+ im->ylegend[0] = '\0';
+ im->yorigin = 0;
+ im->ysize = 100;
+ im->zoom = 1;
+
+ im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
+ im->cr = cairo_create(im->surface);
+
+ for (i = 0; i < DIM(text_prop); i++) {
+ im->text_prop[i].size = -1;
+ rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
+ }
+
+ if (fontmap == NULL){
+ fontmap = pango_cairo_font_map_get_default();
+ }
+
+ context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
+
+ pango_cairo_context_set_resolution(context, 100);
+
+ pango_cairo_update_context(im->cr,context);
+
+ im->layout = pango_layout_new(context);
+
+// im->layout = pango_cairo_create_layout(im->cr);
+
+
+ cairo_font_options_set_hint_style
+ (im->font_options, CAIRO_HINT_STYLE_FULL);
+ cairo_font_options_set_hint_metrics
+ (im->font_options, CAIRO_HINT_METRICS_ON);
+ cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
+
+
+
+ for (i = 0; i < DIM(graph_col); i++)
+ im->graph_col[i] = graph_col[i];
+
+
+}
+
+
+void rrd_graph_options(
+ int argc,
+ char *argv[],
+ image_desc_t
+ *im)
+{
+ int stroff;
+ char *parsetime_error = NULL;
+ char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
+ time_t start_tmp = 0, end_tmp = 0;
+ long long_tmp;
+ rrd_time_value_t start_tv, end_tv;
+ long unsigned int color;
+ char *old_locale = "";
+
+ /* defines for long options without a short equivalent. should be bytes,
+ and may not collide with (the ASCII value of) short options */
+#define LONGOPT_UNITS_SI 255
+
+/* *INDENT-OFF* */
+ struct option long_options[] = {
+ { "start", required_argument, 0, 's'},
+ { "end", required_argument, 0, 'e'},
+ { "x-grid", required_argument, 0, 'x'},
+ { "y-grid", required_argument, 0, 'y'},
+ { "vertical-label", required_argument, 0, 'v'},
+ { "width", required_argument, 0, 'w'},
+ { "height", required_argument, 0, 'h'},
+ { "full-size-mode", no_argument, 0, 'D'},
+ { "interlaced", no_argument, 0, 'i'},
+ { "upper-limit", required_argument, 0, 'u'},
+ { "lower-limit", required_argument, 0, 'l'},
+ { "rigid", no_argument, 0, 'r'},
+ { "base", required_argument, 0, 'b'},
+ { "logarithmic", no_argument, 0, 'o'},
+ { "color", required_argument, 0, 'c'},
+ { "font", required_argument, 0, 'n'},
+ { "title", required_argument, 0, 't'},
+ { "imginfo", required_argument, 0, 'f'},
+ { "imgformat", required_argument, 0, 'a'},
+ { "lazy", no_argument, 0, 'z'},
+ { "zoom", required_argument, 0, 'm'},
+ { "no-legend", no_argument, 0, 'g'},
+ { "force-rules-legend", no_argument, 0, 'F'},
+ { "only-graph", no_argument, 0, 'j'},
+ { "alt-y-grid", no_argument, 0, 'Y'},
+ { "no-minor", no_argument, 0, 'I'},
+ { "slope-mode", no_argument, 0, 'E'},
+ { "alt-autoscale", no_argument, 0, 'A'},
+ { "alt-autoscale-min", no_argument, 0, 'J'},
+ { "alt-autoscale-max", no_argument, 0, 'M'},
+ { "no-gridfit", no_argument, 0, 'N'},
+ { "units-exponent", required_argument, 0, 'X'},
+ { "units-length", required_argument, 0, 'L'},
+ { "units", required_argument, 0, LONGOPT_UNITS_SI},
+ { "step", required_argument, 0, 'S'},
+ { "tabwidth", required_argument, 0, 'T'},
+ { "font-render-mode", required_argument, 0, 'R'},
+ { "graph-render-mode", required_argument, 0, 'G'},
+ { "font-smoothing-threshold", required_argument, 0, 'B'},
+ { "watermark", required_argument, 0, 'W'},
+ { "alt-y-mrtg", no_argument, 0, 1000}, /* this has no effect it is just here to save old apps from crashing when they use it */
+ { "pango-markup", no_argument, 0, 'P'},
+ { 0, 0, 0, 0}
+};
+/* *INDENT-ON* */
+
+ optind = 0;
+ opterr = 0; /* initialize getopt */
+ rrd_parsetime("end-24h", &start_tv);
+ rrd_parsetime("now", &end_tv);
+ while (1) {
+ int option_index = 0;
+ int opt;
+ int col_start, col_end;
+
+ opt = getopt_long(argc, argv,
+ "s:e:x:y:v:w:h:D:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:kP",
+ long_options, &option_index);
+ if (opt == EOF)
+ break;
+ switch (opt) {
+ case 'I':
+ im->extra_flags |= NOMINOR;
+ break;
+ case 'Y':
+ im->extra_flags |= ALTYGRID;
+ break;
+ case 'A':
+ im->extra_flags |= ALTAUTOSCALE;
+ break;
+ case 'J':
+ im->extra_flags |= ALTAUTOSCALE_MIN;
+ break;
+ case 'M':
+ im->extra_flags |= ALTAUTOSCALE_MAX;
+ break;
+ case 'j':
+ im->extra_flags |= ONLY_GRAPH;
+ break;
+ case 'g':
+ im->extra_flags |= NOLEGEND;
+ break;
+ case 'F':
+ im->extra_flags |= FORCE_RULES_LEGEND;
+ break;
+ case LONGOPT_UNITS_SI:
+ if (im->extra_flags & FORCE_UNITS) {
+ rrd_set_error("--units can only be used once!");
+ setlocale(LC_NUMERIC, old_locale);
+ return;
+ }
+ if (strcmp(optarg, "si") == 0)
+ im->extra_flags |= FORCE_UNITS_SI;
+ else {
+ rrd_set_error("invalid argument for --units: %s", optarg);
+ return;
+ }
+ break;
+ case 'X':
+ im->unitsexponent = atoi(optarg);
+ break;
+ case 'L':
+ im->unitslength = atoi(optarg);
+ im->forceleftspace = 1;
+ break;
+ case 'T':
+ old_locale = setlocale(LC_NUMERIC, "C");
+ im->tabwidth = atof(optarg);
+ setlocale(LC_NUMERIC, old_locale);
+ break;
+ case 'S':
+ old_locale = setlocale(LC_NUMERIC, "C");
+ im->step = atoi(optarg);
+ setlocale(LC_NUMERIC, old_locale);
+ break;
+ case 'N':
+ im->gridfit = 0;
+ break;
+ case 'P':
+ im->with_markup = 1;
+ break;
+ case 's':
+ if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
+ rrd_set_error("start time: %s", parsetime_error);
+ return;
+ }
+ break;
+ case 'e':
+ if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
+ rrd_set_error("end time: %s", parsetime_error);
+ return;
+ }
+ break;
+ case 'x':
+ if (strcmp(optarg, "none") == 0) {
+ im->draw_x_grid = 0;
+ break;
+ };
+ if (sscanf(optarg,
+ "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
+ scan_gtm,
+ &im->xlab_user.gridst,
+ scan_mtm,
+ &im->xlab_user.mgridst,
+ scan_ltm,
+ &im->xlab_user.labst,
+ &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
+ strncpy(im->xlab_form, optarg + stroff,
+ sizeof(im->xlab_form) - 1);
+ im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
+ if ((int)
+ (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
+ rrd_set_error("unknown keyword %s", scan_gtm);
+ return;
+ } else if ((int)
+ (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
+ == -1) {
+ rrd_set_error("unknown keyword %s", scan_mtm);
+ return;
+ } else if ((int)
+ (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
+ rrd_set_error("unknown keyword %s", scan_ltm);
+ return;
+ }
+ im->xlab_user.minsec = 1;
+ im->xlab_user.stst = im->xlab_form;
+ } else {
+ rrd_set_error("invalid x-grid format");
+ return;
+ }
+ break;
+ case 'y':
+
+ if (strcmp(optarg, "none") == 0) {
+ im->draw_y_grid = 0;
+ break;
+ };
+ old_locale = setlocale(LC_NUMERIC, "C");
+ if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
+ setlocale(LC_NUMERIC, old_locale);
+ if (im->ygridstep <= 0) {
+ rrd_set_error("grid step must be > 0");
+ return;
+ } else if (im->ylabfact < 1) {
+ rrd_set_error("label factor must be > 0");
+ return;
+ }
+ } else {
+ setlocale(LC_NUMERIC, old_locale);
+ rrd_set_error("invalid y-grid format");
+ return;
+ }
+ break;
+ case 'v':
+ strncpy(im->ylegend, optarg, 150);
+ im->ylegend[150] = '\0';
+ break;
+ case 'u':
+ old_locale = setlocale(LC_NUMERIC, "C");
+ im->maxval = atof(optarg);
+ setlocale(LC_NUMERIC, old_locale);
+ break;
+ case 'l':
+ old_locale = setlocale(LC_NUMERIC, "C");
+ im->minval = atof(optarg);
+ setlocale(LC_NUMERIC, old_locale);
+ break;
+ case 'b':
+ im->base = atol(optarg);
+ if (im->base != 1024 && im->base != 1000) {
+ rrd_set_error
+ ("the only sensible value for base apart from 1000 is 1024");
+ return;
+ }
+ break;
+ case 'w':
+ long_tmp = atol(optarg);
+ if (long_tmp < 10) {
+ rrd_set_error("width below 10 pixels");
+ return;
+ }
+ im->xsize = long_tmp;
+ break;
+ case 'h':
+ long_tmp = atol(optarg);
+ if (long_tmp < 10) {
+ rrd_set_error("height below 10 pixels");
+ return;
+ }
+ im->ysize = long_tmp;
+ break;
+ case 'D':
+ im->extra_flags |= FULL_SIZE_MODE;
+ break;
+ case 'i':
+ /* interlaced png not supported at the moment */
+ break;
+ case 'r':
+ im->rigid = 1;
+ break;
+ case 'f':
+ im->imginfo = optarg;
+ break;
+ case 'a':
+ if ((int)
+ (im->imgformat = if_conv(optarg)) == -1) {
+ rrd_set_error("unsupported graphics format '%s'", optarg);
+ return;
+ }
+ break;
+ case 'z':
+ im->lazy = 1;
+ break;
+ case 'E':
+ im->slopemode = 1;
+ break;
+ case 'o':
+ im->logarithmic = 1;
+ break;
+ case 'c':
+ if (sscanf(optarg,
+ "%10[A-Z]#%n%8lx%n",
+ col_nam, &col_start, &color, &col_end) == 2) {
+ int ci;
+ int col_len = col_end - col_start;
+
+ switch (col_len) {
+ case 3:
+ color =
+ (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
+ 0x011000) |
+ ((color & 0x00F)
+ * 0x001100)
+ | 0x000000FF);
+ break;
+ case 4:
+ color =
+ (((color & 0xF000) *
+ 0x11000) | ((color & 0x0F00) *
+ 0x01100) | ((color &
+ 0x00F0) *
+ 0x00110) |
+ ((color & 0x000F) * 0x00011)
+ );
+ break;
+ case 6:
+ color = (color << 8) + 0xff /* shift left by 8 */ ;
+ break;
+ case 8:
+ break;
+ default:
+ rrd_set_error("the color format is #RRGGBB[AA]");
+ return;
+ }
+ if ((ci = grc_conv(col_nam)) != -1) {
+ im->graph_col[ci] = gfx_hex_to_col(color);
+ } else {
+ rrd_set_error("invalid color name '%s'", col_nam);
+ return;
+ }
+ } else {
+ rrd_set_error("invalid color def format");
+ return;
+ }
+ break;
+ case 'n':{
+ char prop[15];
+ double size = 1;
+ int end;
+
+ old_locale = setlocale(LC_NUMERIC, "C");
+ if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
+ int sindex, propidx;
+
+ setlocale(LC_NUMERIC, old_locale);
+ if ((sindex = text_prop_conv(prop)) != -1) {
+ for (propidx = sindex;
+ propidx < TEXT_PROP_LAST; propidx++) {
+ if (size > 0) {
+ rrd_set_font_desc(im,propidx,NULL,size);
+ }
+ if ((int) strlen(optarg) > end) {
+ if (optarg[end] == ':') {
+ rrd_set_font_desc(im,propidx,optarg + end + 1,0);
+ } else {
+ rrd_set_error
+ ("expected : after font size in '%s'",
+ optarg);
+ return;
+ }
+ }
+ /* only run the for loop for DEFAULT (0) for
+ all others, we break here. woodo programming */
+ if (propidx == sindex && sindex != 0)
+ break;
+ }
+ } else {
+ rrd_set_error("invalid fonttag '%s'", prop);
+ return;
+ }
+ } else {
+ setlocale(LC_NUMERIC, old_locale);
+ rrd_set_error("invalid text property format");
+ return;
+ }
+ break;
+ }
+ case 'm':
+ old_locale = setlocale(LC_NUMERIC, "C");
+ im->zoom = atof(optarg);
+ setlocale(LC_NUMERIC, old_locale);
+ if (im->zoom <= 0.0) {
+ rrd_set_error("zoom factor must be > 0");
+ return;
+ }
+ break;
+ case 't':
+ strncpy(im->title, optarg, 150);
+ im->title[150] = '\0';
+ break;
+ case 'R':
+ if (strcmp(optarg, "normal") == 0) {
+ cairo_font_options_set_antialias
+ (im->font_options, CAIRO_ANTIALIAS_GRAY);
+ cairo_font_options_set_hint_style
+ (im->font_options, CAIRO_HINT_STYLE_FULL);
+ } else if (strcmp(optarg, "light") == 0) {
+ cairo_font_options_set_antialias
+ (im->font_options, CAIRO_ANTIALIAS_GRAY);
+ cairo_font_options_set_hint_style
+ (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
+ } else if (strcmp(optarg, "mono") == 0) {
+ cairo_font_options_set_antialias
+ (im->font_options, CAIRO_ANTIALIAS_NONE);
+ cairo_font_options_set_hint_style
+ (im->font_options, CAIRO_HINT_STYLE_FULL);
+ } else {
+ rrd_set_error("unknown font-render-mode '%s'", optarg);
+ return;
+ }
+ break;
+ case 'G':
+ if (strcmp(optarg, "normal") == 0)
+ im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
+ else if (strcmp(optarg, "mono") == 0)
+ im->graph_antialias = CAIRO_ANTIALIAS_NONE;
+ else {
+ rrd_set_error("unknown graph-render-mode '%s'", optarg);
+ return;
+ }
+ break;
+ case 'B':
+ /* not supported curently */
+ break;
+ case 'W':
+ strncpy(im->watermark, optarg, 100);
+ im->watermark[99] = '\0';
+ break;
+ case '?':
+ if (optopt != 0)
+ rrd_set_error("unknown option '%c'", optopt);
+ else
+ rrd_set_error("unknown option '%s'", argv[optind - 1]);
+ return;
+ }
+ }
+
+ pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
+ pango_layout_context_changed(im->layout);
+
+
+
+ if (im->logarithmic && im->minval <= 0) {
+ rrd_set_error
+ ("for a logarithmic yaxis you must specify a lower-limit > 0");
+ return;
+ }
+
+ if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
+ /* error string is set in rrd_parsetime.c */
+ return;
+ }
+
+ if (start_tmp < 3600 * 24 * 365 * 10) {
+ rrd_set_error
+ ("the first entry to fetch should be after 1980 (%ld)",
+ start_tmp);
+ return;
+ }
+
+ if (end_tmp < start_tmp) {
+ rrd_set_error
+ ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
+ return;
+ }
+
+ im->start = start_tmp;
+ im->end = end_tmp;
+ im->step = max((long) im->step, (im->end - im->start) / im->xsize);
+}
+
+int rrd_graph_color(
+ image_desc_t
+ *im,
+ char *var,
+ char *err,
+ int optional)
+{
+ char *color;
+ graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
+
+ color = strstr(var, "#");
+ if (color == NULL) {
+ if (optional == 0) {
+ rrd_set_error("Found no color in %s", err);
+ return 0;
+ }
+ return 0;
+ } else {
+ int n = 0;
+ char *rest;
+ long unsigned int col;
+
+ rest = strstr(color, ":");
+ if (rest != NULL)
+ n = rest - color;
+ else
+ n = strlen(color);
+ switch (n) {
+ case 7:
+ sscanf(color, "#%6lx%n", &col, &n);
+ col = (col << 8) + 0xff /* shift left by 8 */ ;
+ if (n != 7)
+ rrd_set_error("Color problem in %s", err);
+ break;
+ case 9:
+ sscanf(color, "#%8lx%n", &col, &n);
+ if (n == 9)
+ break;
+ default:
+ rrd_set_error("Color problem in %s", err);
+ }
+ if (rrd_test_error())
+ return 0;
+ gdp->col = gfx_hex_to_col(col);
+ return n;
+ }
+}
+
+
+int bad_format(
+ char *fmt)
+{
+ char *ptr;
+ int n = 0;
+
+ ptr = fmt;
+ while (*ptr != '\0')
+ if (*ptr++ == '%') {
+
+ /* line cannot end with percent char */
+ if (*ptr == '\0')
+ return 1;
+ /* '%s', '%S' and '%%' are allowed */
+ if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
+ ptr++;
+ /* %c is allowed (but use only with vdef!) */
+ else if (*ptr == 'c') {
+ ptr++;
+ n = 1;
+ }
+
+ /* or else '% 6.2lf' and such are allowed */
+ else {
+ /* optional padding character */
+ if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
+ ptr++;
+ /* This should take care of 'm.n' with all three optional */
+ while (*ptr >= '0' && *ptr <= '9')
+ ptr++;
+ if (*ptr == '.')
+ ptr++;
+ while (*ptr >= '0' && *ptr <= '9')
+ ptr++;
+ /* Either 'le', 'lf' or 'lg' must follow here */
+ if (*ptr++ != 'l')
+ return 1;
+ if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
+ ptr++;
+ else
+ return 1;
+ n++;
+ }
+ }
+
+ return (n != 1);
+}
+
+
+int vdef_parse(
+ struct graph_desc_t
+ *gdes,
+ const char *const str)
+{
+ /* A VDEF currently is either "func" or "param,func"
+ * so the parsing is rather simple. Change if needed.
+ */
+ double param;
+ char func[30];
+ int n;
+ char *old_locale;
+
+ n = 0;
+ old_locale = setlocale(LC_NUMERIC, "C");
+ sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
+ setlocale(LC_NUMERIC, old_locale);
+ if (n == (int) strlen(str)) { /* matched */
+ ;
+ } else {
+ n = 0;
+ sscanf(str, "%29[A-Z]%n", func, &n);
+ if (n == (int) strlen(str)) { /* matched */
+ param = DNAN;
+ } else {
+ rrd_set_error
+ ("Unknown function string '%s' in VDEF '%s'",
+ str, gdes->vname);
+ return -1;
+ }
+ }
+ if (!strcmp("PERCENT", func))
+ gdes->vf.op = VDEF_PERCENT;
+ else if (!strcmp("MAXIMUM", func))
+ gdes->vf.op = VDEF_MAXIMUM;
+ else if (!strcmp("AVERAGE", func))
+ gdes->vf.op = VDEF_AVERAGE;
+ else if (!strcmp("STDEV", func))
+ gdes->vf.op = VDEF_STDEV;
+ else if (!strcmp("MINIMUM", func))
+ gdes->vf.op = VDEF_MINIMUM;
+ else if (!strcmp("TOTAL", func))
+ gdes->vf.op = VDEF_TOTAL;
+ else if (!strcmp("FIRST", func))
+ gdes->vf.op = VDEF_FIRST;
+ else if (!strcmp("LAST", func))
+ gdes->vf.op = VDEF_LAST;
+ else if (!strcmp("LSLSLOPE", func))
+ gdes->vf.op = VDEF_LSLSLOPE;
+ else if (!strcmp("LSLINT", func))
+ gdes->vf.op = VDEF_LSLINT;
+ else if (!strcmp("LSLCORREL", func))
+ gdes->vf.op = VDEF_LSLCORREL;
+ else {
+ rrd_set_error
+ ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
+ return -1;
+ };
+ switch (gdes->vf.op) {
+ case VDEF_PERCENT:
+ if (isnan(param)) { /* no parameter given */
+ rrd_set_error
+ ("Function '%s' needs parameter in VDEF '%s'\n",
+ func, gdes->vname);
+ return -1;
+ };
+ if (param >= 0.0 && param <= 100.0) {
+ gdes->vf.param = param;
+ gdes->vf.val = DNAN; /* undefined */
+ gdes->vf.when = 0; /* undefined */
+ } else {
+ rrd_set_error
+ ("Parameter '%f' out of range in VDEF '%s'\n",
+ param, gdes->vname);
+ return -1;
+ };
+ break;
+ case VDEF_MAXIMUM:
+ case VDEF_AVERAGE:
+ case VDEF_STDEV:
+ case VDEF_MINIMUM:
+ case VDEF_TOTAL:
+ case VDEF_FIRST:
+ case VDEF_LAST:
+ case VDEF_LSLSLOPE:
+ case VDEF_LSLINT:
+ case VDEF_LSLCORREL:
+ if (isnan(param)) {
+ gdes->vf.param = DNAN;
+ gdes->vf.val = DNAN;
+ gdes->vf.when = 0;
+ } else {
+ rrd_set_error
+ ("Function '%s' needs no parameter in VDEF '%s'\n",
+ func, gdes->vname);
+ return -1;
+ };
+ break;
+ };
+ return 0;
+}
+
+
+int vdef_calc(
+ image_desc_t *im,
+ int gdi)
+{
+ graph_desc_t *src, *dst;
+ rrd_value_t *data;
+ long step, steps;
+ unsigned long end;
+
+ dst = &im->gdes[gdi];
+ src = &im->gdes[dst->vidx];
+ data = src->data + src->ds;
+ end =
+ src->end_orig % (long) src->step ==
+ 0 ? src->end_orig : (src->end_orig + (long) src->step -
+ src->end_orig % (long) src->step);
+
+ steps = (end - src->start) / src->step;
+#if 0
+ printf
+ ("DEBUG: start == %lu, end == %lu, %lu steps\n",
+ src->start, src->end_orig, steps);
+#endif
+ switch (dst->vf.op) {
+ case VDEF_PERCENT:{
+ rrd_value_t *array;
+ int field;
+ if ((array = malloc(steps * sizeof(double))) == NULL) {
+ rrd_set_error("malloc VDEV_PERCENT");
+ return -1;
+ }
+ for (step = 0; step < steps; step++) {
+ array[step] = data[step * src->ds_cnt];
+ }
+ qsort(array, step, sizeof(double), vdef_percent_compar);
+ field = (steps - 1) * dst->vf.param / 100;
+ dst->vf.val = array[field];
+ dst->vf.when = 0; /* no time component */
+ free(array);
+#if 0
+ for (step = 0; step < steps; step++)
+ printf("DEBUG: %3li:%10.2f %c\n",
+ step, array[step], step == field ? '*' : ' ');
+#endif
+ }
+ break;
+ case VDEF_MAXIMUM:
+ step = 0;
+ while (step != steps && isnan(data[step * src->ds_cnt]))
+ step++;
+ if (step == steps) {
+ dst->vf.val = DNAN;
+ dst->vf.when = 0;
+ } else {
+ dst->vf.val = data[step * src->ds_cnt];
+ dst->vf.when = src->start + (step + 1) * src->step;
+ }
+ while (step != steps) {
+ if (finite(data[step * src->ds_cnt])) {
+ if (data[step * src->ds_cnt] > dst->vf.val) {
+ dst->vf.val = data[step * src->ds_cnt];
+ dst->vf.when = src->start + (step + 1) * src->step;
+ }
+ }
+ step++;
+ }
+ break;
+ case VDEF_TOTAL:
+ case VDEF_STDEV:
+ case VDEF_AVERAGE:{
+ int cnt = 0;
+ double sum = 0.0;
+ double average = 0.0;
+
+ for (step = 0; step < steps; step++) {
+ if (finite(data[step * src->ds_cnt])) {
+ sum += data[step * src->ds_cnt];
+ cnt++;
+ };
+ }
+ if (cnt) {
+ if (dst->vf.op == VDEF_TOTAL) {
+ dst->vf.val = sum * src->step;
+ dst->vf.when = 0; /* no time component */
+ } else if (dst->vf.op == VDEF_AVERAGE) {
+ dst->vf.val = sum / cnt;
+ dst->vf.when = 0; /* no time component */
+ } else {
+ average = sum / cnt;
+ sum = 0.0;
+ for (step = 0; step < steps; step++) {
+ if (finite(data[step * src->ds_cnt])) {
+ sum += pow((data[step * src->ds_cnt] - average), 2.0);
+ };
+ }
+ dst->vf.val = pow(sum / cnt, 0.5);
+ dst->vf.when = 0; /* no time component */
+ };
+ } else {
+ dst->vf.val = DNAN;
+ dst->vf.when = 0;
+ }
+ }
+ break;
+ case VDEF_MINIMUM:
+ step = 0;
+ while (step != steps && isnan(data[step * src->ds_cnt]))
+ step++;
+ if (step == steps) {
+ dst->vf.val = DNAN;
+ dst->vf.when = 0;
+ } else {
+ dst->vf.val = data[step * src->ds_cnt];
+ dst->vf.when = src->start + (step + 1) * src->step;
+ }
+ while (step != steps) {
+ if (finite(data[step * src->ds_cnt])) {
+ if (data[step * src->ds_cnt] < dst->vf.val) {
+ dst->vf.val = data[step * src->ds_cnt];
+ dst->vf.when = src->start + (step + 1) * src->step;
+ }
+ }
+ step++;
+ }
+ break;
+ case VDEF_FIRST:
+ /* The time value returned here is one step before the
+ * actual time value. This is the start of the first
+ * non-NaN interval.
+ */
+ step = 0;
+ while (step != steps && isnan(data[step * src->ds_cnt]))
+ step++;
+ if (step == steps) { /* all entries were NaN */
+ dst->vf.val = DNAN;
+ dst->vf.when = 0;
+ } else {
+ dst->vf.val = data[step * src->ds_cnt];
+ dst->vf.when = src->start + step * src->step;
+ }
+ break;
+ case VDEF_LAST:
+ /* The time value returned here is the
+ * actual time value. This is the end of the last
+ * non-NaN interval.
+ */
+ step = steps - 1;
+ while (step >= 0 && isnan(data[step * src->ds_cnt]))
+ step--;
+ if (step < 0) { /* all entries were NaN */
+ dst->vf.val = DNAN;
+ dst->vf.when = 0;
+ } else {
+ dst->vf.val = data[step * src->ds_cnt];
+ dst->vf.when = src->start + (step + 1) * src->step;
+ }
+ break;
+ case VDEF_LSLSLOPE:
+ case VDEF_LSLINT:
+ case VDEF_LSLCORREL:{
+ /* Bestfit line by linear least squares method */
+
+ int cnt = 0;
+ double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
+
+ SUMx = 0;
+ SUMy = 0;
+ SUMxy = 0;
+ SUMxx = 0;
+ SUMyy = 0;
+ for (step = 0; step < steps; step++) {
+ if (finite(data[step * src->ds_cnt])) {
+ cnt++;
+ SUMx += step;
+ SUMxx += step * step;
+ SUMxy += step * data[step * src->ds_cnt];
+ SUMy += data[step * src->ds_cnt];
+ SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
+ };
+ }
+
+ slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
+ y_intercept = (SUMy - slope * SUMx) / cnt;
+ correl =
+ (SUMxy -
+ (SUMx * SUMy) / cnt) /
+ sqrt((SUMxx -
+ (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
+ if (cnt) {
+ if (dst->vf.op == VDEF_LSLSLOPE) {
+ dst->vf.val = slope;
+ dst->vf.when = 0;
+ } else if (dst->vf.op == VDEF_LSLINT) {
+ dst->vf.val = y_intercept;
+ dst->vf.when = 0;
+ } else if (dst->vf.op == VDEF_LSLCORREL) {
+ dst->vf.val = correl;
+ dst->vf.when = 0;
+ };
+ } else {
+ dst->vf.val = DNAN;
+ dst->vf.when = 0;
+ }
+ }
+ break;
+ }
+ return 0;
+}
+
+/* NaN < -INF < finite_values < INF */
+int vdef_percent_compar(
+ const void
+ *a,
+ const void
+ *b)
+{
+ /* Equality is not returned; this doesn't hurt except
+ * (maybe) for a little performance.
+ */
+
+ /* First catch NaN values. They are smallest */
+ if (isnan(*(double *) a))
+ return -1;
+ if (isnan(*(double *) b))
+ return 1;
+ /* NaN doesn't reach this part so INF and -INF are extremes.
+ * The sign from isinf() is compatible with the sign we return
+ */
+ if (isinf(*(double *) a))
+ return isinf(*(double *) a);
+ if (isinf(*(double *) b))
+ return isinf(*(double *) b);
+ /* If we reach this, both values must be finite */
+ if (*(double *) a < *(double *) b)
+ return -1;
+ else
+ return 1;
+}
+
+void grinfo_push(
+ image_desc_t *im,
+ char *key,
+ rrd_info_type_t type,
+ rrd_infoval_t value)
+{
+ im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
+ if (im->grinfo == NULL) {
+ im->grinfo = im->grinfo_current;
+ }
+}
diff --git a/program/src/rrd_graph.h b/program/src/rrd_graph.h
--- /dev/null
+++ b/program/src/rrd_graph.h
@@ -0,0 +1,459 @@
+#ifndef _RRD_GRAPH_H
+#define _RRD_GRAPH_H
+
+#define y0 cairo_y0
+#define y1 cairo_y1
+#define index cairo_index
+
+#include <cairo.h>
+#include <cairo-pdf.h>
+#include <cairo-svg.h>
+#include <cairo-ps.h>
+#include <pango/pangocairo.h>
+
+#include "rrd_tool.h"
+#include "rrd_rpncalc.h"
+
+
+#define ALTYGRID 0x01 /* use alternative y grid algorithm */
+#define ALTAUTOSCALE 0x02 /* use alternative algorithm to find lower and upper bounds */
+#define ALTAUTOSCALE_MIN 0x04 /* use alternative algorithm to find lower bounds */
+#define ALTAUTOSCALE_MAX 0x08 /* use alternative algorithm to find upper bounds */
+#define NOLEGEND 0x10 /* use no legend */
+#define NOMINOR 0x20 /* Turn off minor gridlines */
+#define ONLY_GRAPH 0x40 /* use only graph */
+#define FORCE_RULES_LEGEND 0x80 /* force printing of HRULE and VRULE legend */
+
+#define FORCE_UNITS 0x100 /* mask for all FORCE_UNITS_* flags */
+#define FORCE_UNITS_SI 0x100 /* force use of SI units in Y axis (no effect in linear graph, SI instead of E in log graph) */
+
+#define FULL_SIZE_MODE 0x200 /* -width and -height indicate the total size of the image */
+
+enum tmt_en { TMT_SECOND = 0, TMT_MINUTE, TMT_HOUR, TMT_DAY,
+ TMT_WEEK, TMT_MONTH, TMT_YEAR
+};
+
+enum grc_en { GRC_CANVAS = 0, GRC_BACK, GRC_SHADEA, GRC_SHADEB,
+ GRC_GRID, GRC_MGRID, GRC_FONT, GRC_ARROW, GRC_AXIS, GRC_FRAME, __GRC_END__
+};
+
+#define MGRIDWIDTH 0.6
+#define GRIDWIDTH 0.4
+
+enum gf_en { GF_PRINT = 0, GF_GPRINT, GF_COMMENT, GF_HRULE, GF_VRULE, GF_LINE,
+ GF_AREA, GF_STACK, GF_TICK, GF_TEXTALIGN,
+ GF_DEF, GF_CDEF, GF_VDEF, GF_SHIFT,
+ GF_XPORT
+};
+
+enum txa_en { TXA_LEFT = 0, TXA_RIGHT, TXA_CENTER, TXA_JUSTIFIED };
+
+enum vdef_op_en {
+ VDEF_MAXIMUM = 0 /* like the MAX in (G)PRINT */
+ , VDEF_MINIMUM /* like the MIN in (G)PRINT */
+ , VDEF_AVERAGE /* like the AVERAGE in (G)PRINT */
+ , VDEF_STDEV /* the standard deviation */
+ , VDEF_PERCENT /* Nth percentile */
+ , VDEF_TOTAL /* average multiplied by time */
+ , VDEF_FIRST /* first non-unknown value and time */
+ , VDEF_LAST /* last non-unknown value and time */
+ , VDEF_LSLSLOPE /* least squares line slope */
+ , VDEF_LSLINT /* least squares line y_intercept */
+ , VDEF_LSLCORREL /* least squares line correlation coefficient */
+};
+enum text_prop_en {
+ TEXT_PROP_DEFAULT = 0, /* default settings */
+ TEXT_PROP_TITLE, /* properties for the title */
+ TEXT_PROP_AXIS, /* for the numbers next to the axis */
+ TEXT_PROP_UNIT, /* for the vertical unit description */
+ TEXT_PROP_LEGEND, /* for the legend below the graph */
+ TEXT_PROP_WATERMARK, /* for the little text to the side of the graph */
+ TEXT_PROP_LAST
+};
+
+
+enum gfx_if_en { IF_PNG = 0, IF_SVG, IF_EPS, IF_PDF };
+enum gfx_en { GFX_LINE = 0, GFX_AREA, GFX_TEXT };
+enum gfx_h_align_en { GFX_H_NULL = 0, GFX_H_LEFT, GFX_H_RIGHT, GFX_H_CENTER };
+enum gfx_v_align_en { GFX_V_NULL = 0, GFX_V_TOP, GFX_V_BOTTOM, GFX_V_CENTER };
+
+/* cairo color components */
+typedef struct gfx_color_t {
+ double red;
+ double green;
+ double blue;
+ double alpha;
+} gfx_color_t;
+
+
+typedef struct text_prop_t {
+ double size;
+ char font[1024];
+ PangoFontDescription *font_desc;
+} text_prop_t;
+
+
+typedef struct vdef_t {
+ enum vdef_op_en op;
+ double param; /* parameter for function, if applicable */
+ double val; /* resulting value */
+ time_t when; /* timestamp, if applicable */
+} vdef_t;
+
+typedef struct xlab_t {
+ long minsec; /* minimum sec per pix */
+ long length; /* number of secs on the image */
+ enum tmt_en gridtm; /* grid interval in what ? */
+ long gridst; /* how many whats per grid */
+ enum tmt_en mgridtm; /* label interval in what ? */
+ long mgridst; /* how many whats per label */
+ enum tmt_en labtm; /* label interval in what ? */
+ long labst; /* how many whats per label */
+ long precis; /* label precision -> label placement */
+ char *stst; /* strftime string */
+} xlab_t;
+
+typedef struct ygrid_scale_t { /* y axis grid scaling info */
+ double gridstep;
+ int labfact;
+ char labfmt[64];
+} ygrid_scale_t;
+
+/* sensible y label intervals ...*/
+
+typedef struct ylab_t {
+ double grid; /* grid spacing */
+ int lfac[4]; /* associated label spacing */
+} ylab_t;
+
+/* this structure describes the elements which can make up a graph.
+ because they are quite diverse, not all elements will use all the
+ possible parts of the structure. */
+#ifdef HAVE_SNPRINTF
+#define FMT_LEG_LEN 200
+#else
+#define FMT_LEG_LEN 2000
+#endif
+
+typedef struct graph_desc_t {
+ enum gf_en gf; /* graphing function */
+ int stack; /* boolean */
+ int debug; /* boolean */
+ char vname[MAX_VNAME_LEN + 1]; /* name of the variable */
+ long vidx; /* gdes reference */
+ char rrd[1024]; /* name of the rrd_file containing data */
+ char ds_nam[DS_NAM_SIZE]; /* data source name */
+ long ds; /* data source number */
+ enum cf_en cf; /* consolidation function */
+ enum cf_en cf_reduce; /* consolidation function for reduce_data() */
+ struct gfx_color_t col; /* graph color */
+ char format[FMT_LEG_LEN + 5]; /* format for PRINT AND GPRINT */
+ char legend[FMT_LEG_LEN + 5]; /* legend */
+ int strftm; /* should the VDEF legend be formated with strftime */
+ double leg_x, leg_y; /* location of legend */
+ double yrule; /* value for y rule line and for VDEF */
+ time_t xrule; /* time for x rule line and for VDEF */
+ vdef_t vf; /* instruction for VDEF function */
+ rpnp_t *rpnp; /* instructions for CDEF function */
+
+ /* SHIFT implementation */
+ int shidx; /* gdes reference for offset (-1 --> constant) */
+ time_t shval; /* offset if shidx is -1 */
+ time_t shift; /* current shift applied */
+
+ /* description of data fetched for the graph element */
+ time_t start, end; /* timestaps for first and last data element */
+ time_t start_orig, end_orig; /* timestaps for first and last data element */
+ unsigned long step; /* time between samples */
+ unsigned long step_orig; /* time between samples */
+ unsigned long ds_cnt; /* how many data sources are there in the fetch */
+ long data_first; /* first pointer to this data */
+ char **ds_namv; /* name of datasources in the fetch. */
+ rrd_value_t *data; /* the raw data drawn from the rrd */
+ rrd_value_t *p_data; /* processed data, xsize elments */
+ double linewidth; /* linewideth */
+
+ /* dashed line stuff */
+ int dash; /* boolean, draw dashed line? */
+ double *p_dashes; /* pointer do dash array which keeps the lengths of dashes */
+ int ndash; /* number of dash segments */
+ double offset; /* dash offset along the line */
+
+ enum txa_en txtalign; /* change default alignment strategy for text */
+} graph_desc_t;
+
+typedef struct image_desc_t {
+
+ /* configuration of graph */
+
+ char graphfile[MAXPATH]; /* filename for graphic */
+ long xsize, ysize; /* graph area size in pixels */
+ struct gfx_color_t graph_col[__GRC_END__]; /* real colors for the graph */
+ text_prop_t text_prop[TEXT_PROP_LAST]; /* text properties */
+ char ylegend[210]; /* legend along the yaxis */
+ char title[210]; /* title for graph */
+ char watermark[110]; /* watermark for graph */
+ int draw_x_grid; /* no x-grid at all */
+ int draw_y_grid; /* no x-grid at all */
+ double grid_dash_on, grid_dash_off;
+ xlab_t xlab_user; /* user defined labeling for xaxis */
+ char xlab_form[210]; /* format for the label on the xaxis */
+
+ double ygridstep; /* user defined step for y grid */
+ int ylabfact; /* every how many y grid shall a label be written ? */
+ double tabwidth; /* tabwdith */
+ time_t start, end; /* what time does the graph cover */
+ unsigned long step; /* any preference for the default step ? */
+ rrd_value_t minval, maxval; /* extreme values in the data */
+ int rigid; /* do not expand range even with
+ values outside */
+ ygrid_scale_t ygrid_scale; /* calculated y axis grid info */
+ int gridfit; /* adjust y-axis range etc so all
+ grindlines falls in integer pixel values */
+ char *imginfo; /* construct an <IMG ... tag and return
+ as first retval */
+ enum gfx_if_en imgformat; /* image format */
+ int lazy; /* only update the image if there is
+ reasonable probablility that the
+ existing one is out of date */
+ int slopemode; /* connect the dots of the curve directly, not using a stair */
+ int logarithmic; /* scale the yaxis logarithmic */
+ double force_scale_min; /* Force a scale--min */
+ double force_scale_max; /* Force a scale--max */
+
+ /* status information */
+ int with_markup;
+ long xorigin, yorigin; /* where is (0,0) of the graph */
+ long ximg, yimg; /* total size of the image */
+ size_t rendered_image_size;
+ double zoom;
+ double magfact; /* numerical magnitude */
+ long base; /* 1000 or 1024 depending on what we graph */
+ char symbol; /* magnitude symbol for y-axis */
+ float viewfactor; /* how should the numbers on the y-axis be scaled for viewing ? */
+ int unitsexponent; /* 10*exponent for units on y-asis */
+ int unitslength; /* width of the yaxis labels */
+ int forceleftspace; /* do not kill the space to the left of the y-axis if there is no grid */
+
+ int extra_flags; /* flags for boolean options */
+ /* data elements */
+
+ unsigned char *rendered_image;
+ long prt_c; /* number of print elements */
+ long gdes_c; /* number of graphics elements */
+ graph_desc_t *gdes; /* points to an array of graph elements */
+ cairo_surface_t *surface; /* graphics library */
+ cairo_t *cr; /* drawin context */
+ cairo_font_options_t *font_options; /* cairo font options */
+ cairo_antialias_t graph_antialias; /* antialiasing for the graph */
+ PangoLayout *layout; /* the pango layout we use for writing fonts */
+ rrd_info_t *grinfo; /* root pointer to extra graph info */
+ rrd_info_t *grinfo_current; /* pointing to current entry */
+} image_desc_t;
+
+/* Prototypes */
+int xtr(
+ image_desc_t *,
+ time_t);
+double ytr(
+ image_desc_t *,
+ double);
+enum gf_en gf_conv(
+ char *);
+enum gfx_if_en if_conv(
+ char *);
+enum tmt_en tmt_conv(
+ char *);
+enum grc_en grc_conv(
+ char *);
+enum text_prop_en text_prop_conv(
+ char *);
+int im_free(
+ image_desc_t *);
+void auto_scale(
+ image_desc_t *,
+ double *,
+ char **,
+ double *);
+void si_unit(
+ image_desc_t *);
+void expand_range(
+ image_desc_t *);
+void apply_gridfit(
+ image_desc_t *);
+void reduce_data(
+ enum cf_en,
+ unsigned long,
+ time_t *,
+ time_t *,
+ unsigned long *,
+ unsigned long *,
+ rrd_value_t **);
+int data_fetch(
+ image_desc_t *);
+long find_var(
+ image_desc_t *,
+ char *);
+long find_var_wrapper(
+ void *arg1,
+ char *key);
+long lcd(
+ long *);
+int data_calc(
+ image_desc_t *);
+int data_proc(
+ image_desc_t *);
+time_t find_first_time(
+ time_t,
+ enum tmt_en,
+ long);
+time_t find_next_time(
+ time_t,
+ enum tmt_en,
+ long);
+int print_calc(
+ image_desc_t *);
+int leg_place(
+ image_desc_t *,
+ int *);
+int calc_horizontal_grid(
+ image_desc_t *);
+int draw_horizontal_grid(
+ image_desc_t *);
+int horizontal_log_grid(
+ image_desc_t *);
+void vertical_grid(
+ image_desc_t *);
+void axis_paint(
+ image_desc_t *);
+void grid_paint(
+ image_desc_t *);
+int lazy_check(
+ image_desc_t *);
+int graph_paint(
+ image_desc_t *);
+
+int gdes_alloc(
+ image_desc_t *);
+int scan_for_col(
+ const char *const,
+ int,
+ char *const);
+void rrd_graph_init(
+ image_desc_t *);
+
+void rrd_graph_options(
+ int,
+ char **,
+ image_desc_t *);
+void rrd_graph_script(
+ int,
+ char **,
+ image_desc_t *,
+ int);
+int rrd_graph_color(
+ image_desc_t *,
+ char *,
+ char *,
+ int);
+int bad_format(
+ char *);
+int vdef_parse(
+ struct graph_desc_t *,
+ const char *const);
+int vdef_calc(
+ image_desc_t *,
+ int);
+int vdef_percent_compar(
+ const void *,
+ const void *);
+int graph_size_location(
+ image_desc_t *,
+ int);
+
+
+/* create a new line */
+void gfx_line(
+ image_desc_t *im,
+ double X0,
+ double Y0,
+ double X1,
+ double Y1,
+ double width,
+ gfx_color_t color);
+
+void gfx_dashed_line(
+ image_desc_t *im,
+ double X0,
+ double Y0,
+ double X1,
+ double Y1,
+ double width,
+ gfx_color_t color,
+ double dash_on,
+ double dash_off);
+
+/* create a new area */
+void gfx_new_area(
+ image_desc_t *im,
+ double X0,
+ double Y0,
+ double X1,
+ double Y1,
+ double X2,
+ double Y2,
+ gfx_color_t color);
+
+/* add a point to a line or to an area */
+void gfx_add_point(
+ image_desc_t *im,
+ double x,
+ double y);
+
+/* close current path so it ends at the same point as it started */
+void gfx_close_path(
+ image_desc_t *im);
+
+
+/* create a text node */
+void gfx_text(
+ image_desc_t *im,
+ double x,
+ double y,
+ gfx_color_t color,
+ PangoFontDescription *font_desc,
+ double tabwidth,
+ double angle,
+ enum gfx_h_align_en h_align,
+ enum gfx_v_align_en v_align,
+ const char *text);
+
+/* measure width of a text string */
+double gfx_get_text_width(
+ image_desc_t *im,
+ double start,
+ PangoFontDescription *font_desc,
+ double tabwidth,
+ char *text);
+
+
+/* convert color */
+gfx_color_t gfx_hex_to_col(
+ long unsigned int);
+
+void gfx_line_fit(
+ image_desc_t *im,
+ double *x,
+ double *y);
+
+void gfx_area_fit(
+ image_desc_t *im,
+ double *x,
+ double *y);
+
+#endif
+
+void grinfo_push(
+ image_desc_t *im,
+ char *key,
+ rrd_info_type_t type, rrd_infoval_t value);
diff --git a/program/src/rrd_graph_helper.c b/program/src/rrd_graph_helper.c
--- /dev/null
@@ -0,0 +1,1157 @@
+/****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ ****************************************************************************
+ * rrd_graph_helper.c commandline parser functions
+ * this code initially written by Alex van den Bogaerdt
+ ****************************************************************************/
+
+#include "rrd_graph.h"
+
+#define dprintf if (gdp->debug) printf
+
+/* NOTE ON PARSING:
+ *
+ * we use the following:
+ *
+ * i=0; sscanf(&line[*eaten], "what to find%n", variables, &i)
+ *
+ * Usually you want to find a separator as well. Example:
+ * i=0; sscanf(&line[*eaten], "%li:%n", &someint, &i)
+ *
+ * When the separator is not found, i is not set and thus remains zero.
+ * Another way would be to compare strlen() to i
+ *
+ * Why is this important? Because 12345abc should not be matched as
+ * integer 12345 ...
+ */
+
+/* NOTE ON VNAMES:
+ *
+ * "if ((gdp->vidx=find_var(im, l))!=-1)" is not good enough, at least
+ * not by itself.
+ *
+ * A vname as a result of a VDEF is quite different from a vname
+ * resulting of a DEF or CDEF.
+ */
+
+/* NOTE ON VNAMES:
+ *
+ * A vname called "123" is not to be parsed as the number 123
+ */
+
+
+/* Define prototypes for the parsing methods.
+ Inputs:
+ const char *const line - a fixed pointer to a fixed string
+ unsigned int *const eaten - a fixed pointer to a changing index in that line
+ graph_desc_t *const gdp - a fixed pointer to a changing graph description
+ image_desc_t *const im - a fixed pointer to a changing image description
+*/
+
+int rrd_parse_find_gf(
+ const char *const,
+ unsigned int *const,
+ graph_desc_t *const);
+
+int rrd_parse_legend(
+ const char *const,
+ unsigned int *const,
+ graph_desc_t *const);
+
+int rrd_parse_color(
+ const char *const,
+ graph_desc_t *const);
+
+int rrd_parse_textalign(
+ const char *const,
+ unsigned int *const,
+ graph_desc_t *const);
+
+
+int rrd_parse_CF(
+ const char *const,
+ unsigned int *const,
+ graph_desc_t *const,
+ enum cf_en *const);
+
+int rrd_parse_print(
+ const char *const,
+ unsigned int *const,
+ graph_desc_t *const,
+ image_desc_t *const);
+
+int rrd_parse_shift(
+ const char *const,
+ unsigned int *const,
+ graph_desc_t *const,
+ image_desc_t *const);
+
+int rrd_parse_xport(
+ const char *const,
+ unsigned int *const,
+ graph_desc_t *const,
+ image_desc_t *const);
+
+int rrd_parse_PVHLAST(
+ const char *const,
+ unsigned int *const,
+ graph_desc_t *const,
+ image_desc_t *const);
+
+int rrd_parse_make_vname(
+ const char *const,
+ unsigned int *const,
+ graph_desc_t *const,
+ image_desc_t *const);
+
+int rrd_parse_find_vname(
+ const char *const,
+ unsigned int *const,
+ graph_desc_t *const,
+ image_desc_t *const);
+
+int rrd_parse_def(
+ const char *const,
+ unsigned int *const,
+ graph_desc_t *const,
+ image_desc_t *const);
+
+int rrd_parse_vdef(
+ const char *const,
+ unsigned int *const,
+ graph_desc_t *const,
+ image_desc_t *const);
+
+int rrd_parse_cdef(
+ const char *const,
+ unsigned int *const,
+ graph_desc_t *const,
+ image_desc_t *const);
+
+int rrd_parse_find_gf(
+ const char *const line,
+ unsigned int *const eaten,
+ graph_desc_t *const gdp)
+{
+ char funcname[11], c1 = 0;
+ int i = 0;
+
+ /* start an argument with DEBUG to be able to see how it is parsed */
+ sscanf(&line[*eaten], "DEBUG%n", &i);
+ if (i) {
+ gdp->debug = 1;
+ (*eaten) += i;
+ i = 0;
+ dprintf("Scanning line '%s'\n", &line[*eaten]);
+ }
+ i = 0;
+ c1 = '\0';
+ sscanf(&line[*eaten], "%10[A-Z]%n%c", funcname, &i, &c1);
+ if (!i) {
+ rrd_set_error("Could not make sense out of '%s'", line);
+ return 1;
+ }
+ (*eaten) += i;
+ if ((int) (gdp->gf = gf_conv(funcname)) == -1) {
+ rrd_set_error("'%s' is not a valid function name", funcname);
+ return 1;
+ } else {
+ dprintf("- found function name '%s'\n", funcname);
+ }
+
+ if (c1 == '\0') {
+ rrd_set_error("Function %s needs parameters. Line: %s\n", funcname,
+ line);
+ return 1;
+ }
+ if (c1 == ':')
+ (*eaten)++;
+
+ /* Some commands have a parameter before the colon
+ * (currently only LINE)
+ */
+ switch (gdp->gf) {
+ case GF_LINE:
+ if (c1 == ':') {
+ gdp->linewidth = 1;
+ dprintf("- using default width of 1\n");
+ } else {
+ i = 0;
+ sscanf(&line[*eaten], "%lf:%n", &gdp->linewidth, &i);
+ if (!i) {
+ rrd_set_error("Cannot parse line width '%s' in line '%s'\n",
+ &line[*eaten], line);
+ return 1;
+ } else {
+ dprintf("- scanned width %f\n", gdp->linewidth);
+ if (isnan(gdp->linewidth)) {
+ rrd_set_error
+ ("LINE width '%s' is not a number in line '%s'\n",
+ &line[*eaten], line);
+ return 1;
+ }
+ if (isinf(gdp->linewidth)) {
+ rrd_set_error
+ ("LINE width '%s' is out of range in line '%s'\n",
+ &line[*eaten], line);
+ return 1;
+ }
+ if (gdp->linewidth < 0) {
+ rrd_set_error
+ ("LINE width '%s' is less than 0 in line '%s'\n",
+ &line[*eaten], line);
+ return 1;
+ }
+ }
+ (*eaten) += i;
+ }
+ break;
+ default:
+ if (c1 == ':')
+ break;
+ rrd_set_error("Malformed '%s' command in line '%s'\n", &line[*eaten],
+ line);
+ return 1;
+ }
+ if (line[*eaten] == '\0') {
+ rrd_set_error("Expected some arguments after '%s'\n", line);
+ return 1;
+ }
+ return 0;
+}
+
+int rrd_parse_legend(
+ const char *const line,
+ unsigned int *const eaten,
+ graph_desc_t *const gdp)
+{
+ int i;
+
+ if (line[*eaten] == '\0' || line[*eaten] == ':') {
+ dprintf("- no (or: empty) legend found\n");
+ return 0;
+ }
+
+ i = scan_for_col(&line[*eaten], FMT_LEG_LEN, gdp->legend);
+
+ (*eaten) += i;
+
+ if (line[*eaten] != '\0' && line[*eaten] != ':') {
+ rrd_set_error("Legend too long");
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+int rrd_parse_color(
+ const char *const string,
+ graph_desc_t *const gdp)
+{
+ unsigned int r = 0, g = 0, b = 0, a = 0, i;
+
+ /* matches the following formats:
+ ** RGB
+ ** RGBA
+ ** RRGGBB
+ ** RRGGBBAA
+ */
+
+ i = 0;
+ while (string[i] && isxdigit((unsigned int) string[i]))
+ i++;
+ if (string[i] != '\0')
+ return 1; /* garbage follows hexdigits */
+ switch (i) {
+ case 3:
+ case 4:
+ sscanf(string, "%1x%1x%1x%1x", &r, &g, &b, &a);
+ r *= 0x11;
+ g *= 0x11;
+ b *= 0x11;
+ a *= 0x11;
+ if (i == 3)
+ a = 0xFF;
+ break;
+ case 6:
+ case 8:
+ sscanf(string, "%02x%02x%02x%02x", &r, &g, &b, &a);
+ if (i == 6)
+ a = 0xFF;
+ break;
+ default:
+ return 1; /* wrong number of digits */
+ }
+ gdp->col = gfx_hex_to_col(r << 24 | g << 16 | b << 8 | a);
+ return 0;
+}
+
+int rrd_parse_CF(
+ const char *const line,
+ unsigned int *const eaten,
+ graph_desc_t *const gdp,
+ enum cf_en *cf)
+{
+ char symname[CF_NAM_SIZE];
+ int i = 0;
+
+ sscanf(&line[*eaten], CF_NAM_FMT "%n", symname, &i);
+ if ((!i) || ((line[(*eaten) + i] != '\0') && (line[(*eaten) + i] != ':'))) {
+ rrd_set_error("Cannot parse CF in '%s'", line);
+ return 1;
+ }
+ (*eaten) += i;
+ dprintf("- using CF '%s'\n", symname);
+
+ if ((int) (*cf = cf_conv(symname)) == -1) {
+ rrd_set_error("Unknown CF '%s' in '%s'", symname, line);
+ return 1;
+ }
+
+ if (line[*eaten] != '\0')
+ (*eaten)++;
+ return 0;
+}
+
+/* Try to match next token as a vname.
+ *
+ * Returns:
+ * -1 an error occured and the error string is set
+ * other the vname index number
+ *
+ * *eaten is incremented only when a vname is found.
+ */
+int rrd_parse_find_vname(
+ const char *const line,
+ unsigned int *const eaten,
+ graph_desc_t *const gdp,
+ image_desc_t *const im)
+{
+ char tmpstr[MAX_VNAME_LEN + 1];
+ int i;
+ long vidx;
+
+ i = 0;
+ sscanf(&line[*eaten], DEF_NAM_FMT "%n", tmpstr, &i);
+ if (!i) {
+ rrd_set_error("Could not parse line '%s'", line);
+ return -1;
+ }
+ if (line[*eaten + i] != ':' && line[*eaten + i] != '\0') {
+ rrd_set_error("Could not parse line '%s'", line);
+ return -1;
+ }
+ dprintf("- Considering '%s'\n", tmpstr);
+
+ if ((vidx = find_var(im, tmpstr)) < 0) {
+ dprintf("- Not a vname\n");
+ rrd_set_error("Not a valid vname: %s in line %s", tmpstr, line);
+ return -1;
+ }
+ dprintf("- Found vname '%s' vidx '%li'\n", tmpstr, gdp->vidx);
+ if (line[*eaten + i] == ':')
+ i++;
+ (*eaten) += i;
+ return vidx;
+}
+
+/* Parsing old-style xPRINT and new-style xPRINT */
+int rrd_parse_print(
+ const char *const line,
+ unsigned int *const eaten,
+ graph_desc_t *const gdp,
+ image_desc_t *const im)
+{
+ /* vname:CF:format in case of DEF-based vname
+ ** vname:CF:format in case of CDEF-based vname
+ ** vname:format[:strftime] in case of VDEF-based vname
+ */
+ if ((gdp->vidx = rrd_parse_find_vname(line, eaten, gdp, im)) < 0)
+ return 1;
+
+ switch (im->gdes[gdp->vidx].gf) {
+ case GF_DEF:
+ case GF_CDEF:
+ dprintf("- vname is of type DEF or CDEF, looking for CF\n");
+ if (rrd_parse_CF(line, eaten, gdp, &gdp->cf))
+ return 1;
+ break;
+ case GF_VDEF:
+ dprintf("- vname is of type VDEF\n");
+ break;
+ default:
+ rrd_set_error("Encountered unknown type variable '%s'",
+ im->gdes[gdp->vidx].vname);
+ return 1;
+ }
+
+ if (rrd_parse_legend(line, eaten, gdp))
+ return 1;
+ /* for *PRINT the legend itself gets rendered later. We only
+ get the format at this juncture */
+ strcpy(gdp->format, gdp->legend);
+ gdp->legend[0] = '\0';
+ /* this is a very crud test, parsing :style flags should be in a function */
+ if (im->gdes[gdp->vidx].gf == GF_VDEF
+ && strcmp(line + (*eaten), ":strftime") == 0) {
+ gdp->strftm = 1;
+ (*eaten) += strlen(":strftime");
+ }
+ return 0;
+}
+
+/* SHIFT:_def_or_cdef:_vdef_or_number_
+ */
+int rrd_parse_shift(
+ const char *const line,
+ unsigned int *const eaten,
+ graph_desc_t *const gdp,
+ image_desc_t *const im)
+{
+ int i;
+
+ if ((gdp->vidx = rrd_parse_find_vname(line, eaten, gdp, im)) < 0)
+ return 1;
+
+ switch (im->gdes[gdp->vidx].gf) {
+ case GF_DEF:
+ case GF_CDEF:
+ dprintf("- vname is of type DEF or CDEF, OK\n");
+ break;
+ case GF_VDEF:
+ rrd_set_error("Cannot shift a VDEF: '%s' in line '%s'\n",
+ im->gdes[gdp->vidx].vname, line);
+ return 1;
+ default:
+ rrd_set_error("Encountered unknown type variable '%s' in line '%s'",
+ im->gdes[gdp->vidx].vname, line);
+ return 1;
+ }
+
+ if ((gdp->shidx = rrd_parse_find_vname(line, eaten, gdp, im)) >= 0) {
+ switch (im->gdes[gdp->shidx].gf) {
+ case GF_DEF:
+ case GF_CDEF:
+ rrd_set_error("Offset cannot be a (C)DEF: '%s' in line '%s'\n",
+ im->gdes[gdp->shidx].vname, line);
+ return 1;
+ case GF_VDEF:
+ dprintf("- vname is of type VDEF, OK\n");
+ break;
+ default:
+ rrd_set_error
+ ("Encountered unknown type variable '%s' in line '%s'",
+ im->gdes[gdp->vidx].vname, line);
+ return 1;
+ }
+ } else {
+ long time_tmp = 0;
+
+ rrd_clear_error();
+ i = 0;
+ sscanf(&line[*eaten], "%li%n", &time_tmp, &i);
+ gdp->shval = time_tmp;
+ if (i != (int) strlen(&line[*eaten])) {
+ rrd_set_error("Not a valid offset: %s in line %s", &line[*eaten],
+ line);
+ return 1;
+ }
+ (*eaten) += i;
+ dprintf("- offset is number %li\n", gdp->shval);
+ gdp->shidx = -1;
+ }
+ return 0;
+}
+
+/* XPORT:_def_or_cdef[:legend]
+ */
+int rrd_parse_xport(
+ const char *const line,
+ unsigned int *const eaten,
+ graph_desc_t *const gdp,
+ image_desc_t *const im)
+{
+ if ((gdp->vidx = rrd_parse_find_vname(line, eaten, gdp, im)) < 0)
+ return 1;
+
+ switch (im->gdes[gdp->vidx].gf) {
+ case GF_DEF:
+ case GF_CDEF:
+ dprintf("- vname is of type DEF or CDEF, OK\n");
+ break;
+ case GF_VDEF:
+ rrd_set_error("Cannot xport a VDEF: '%s' in line '%s'\n",
+ im->gdes[gdp->vidx].vname, line);
+ return 1;
+ default:
+ rrd_set_error("Encountered unknown type variable '%s' in line '%s'",
+ im->gdes[gdp->vidx].vname, line);
+ return 1;
+ }
+ dprintf("- looking for legend in '%s'\n", &line[*eaten]);
+ if (rrd_parse_legend(line, eaten, gdp))
+ return 1;
+ return 0;
+}
+
+int rrd_parse_textalign(
+ const char *const line,
+ unsigned int *const eaten,
+ graph_desc_t *const gdp)
+{
+ if (strcmp(&line[*eaten], "left") == 0) {
+ gdp->txtalign = TXA_LEFT;
+ } else if (strcmp(&line[*eaten], "right") == 0) {
+ gdp->txtalign = TXA_RIGHT;
+ } else if (strcmp(&line[*eaten], "justified") == 0) {
+ gdp->txtalign = TXA_JUSTIFIED;
+ } else if (strcmp(&line[*eaten], "center") == 0) {
+ gdp->txtalign = TXA_CENTER;
+ } else {
+ rrd_set_error("Unknown alignement type '%s'", &line[*eaten]);
+ return 1;
+ }
+ *eaten += strlen(&line[*eaten]);
+ return 0;
+}
+
+
+/* Parsing of VRULE, HRULE, LINE, AREA, STACK and TICK
+** is done in one function.
+**
+** Stacking VRULE, HRULE or TICK is not allowed.
+**
+** If a number (which is valid to enter) is more than a
+** certain amount of characters, it is caught as an error.
+** While this is arguable, so is entering fixed numbers
+** with more than MAX_VNAME_LEN significant digits.
+*/
+int rrd_parse_PVHLAST(
+ const char *const line,
+ unsigned int *const eaten,
+ graph_desc_t *const gdp,
+ image_desc_t *const im)
+{
+ int i, j, k;
+ int colorfound = 0;
+ char tmpstr[MAX_VNAME_LEN + 10]; /* vname#RRGGBBAA\0 */
+ static int spacecnt = 0;
+
+ if (spacecnt == 0) {
+ float one_space = gfx_get_text_width(im, 0,
+ im->
+ text_prop[TEXT_PROP_LEGEND].
+ font_desc,
+ im->tabwidth, " ") / 4.0;
+ float target_space = gfx_get_text_width(im, 0,
+ im->
+ text_prop
+ [TEXT_PROP_LEGEND].font_desc,
+ im->tabwidth, "oo");
+
+ spacecnt = target_space / one_space;
+ dprintf("- spacecnt: %i onespace: %f targspace: %f\n", spacecnt,
+ one_space, target_space);
+ }
+
+
+ dprintf("- parsing '%s'\n", &line[*eaten]);
+
+ /* have simpler code in the drawing section */
+ if (gdp->gf == GF_STACK) {
+ gdp->stack = 1;
+ }
+
+ i = scan_for_col(&line[*eaten], MAX_VNAME_LEN + 9, tmpstr);
+ if (line[*eaten + i] != '\0' && line[*eaten + i] != ':') {
+ rrd_set_error("Cannot parse line '%s'", line);
+ return 1;
+ }
+
+ j = i;
+ while (j > 0 && tmpstr[j] != '#')
+ j--;
+
+ if (j) {
+ tmpstr[j] = '\0';
+ }
+ /* We now have:
+ * tmpstr[0] containing vname
+ * tmpstr[j] if j!=0 then containing color
+ * i size of vname + color
+ * j if j!=0 then size of vname
+ */
+
+ /* Number or vname ?
+ * If it is an existing vname, that's OK, provided that it is a
+ * valid type (need time for VRULE, not a float)
+ * Else see if it parses as a number.
+ */
+ dprintf("- examining string '%s'\n", tmpstr);
+ if ((gdp->vidx = find_var(im, tmpstr)) >= 0) {
+ dprintf("- found vname: '%s' vidx %li\n", tmpstr, gdp->vidx);
+ switch (gdp->gf) {
+ case GF_VRULE:
+ case GF_HRULE:
+ if (im->gdes[gdp->vidx].gf != GF_VDEF) {
+ rrd_set_error("Using vname %s of wrong type in line %s\n",
+ im->gdes[gdp->gf].vname, line);
+ return 1;
+ }
+ break;
+ default:;
+ }
+ } else {
+ long time_tmp = 0;
+
+ dprintf("- it is not an existing vname\n");
+ switch (gdp->gf) {
+ case GF_VRULE:
+ k = 0;
+ sscanf(tmpstr, "%li%n", &time_tmp, &k);
+ gdp->xrule = time_tmp;
+ if (((j != 0) && (k == j)) || ((j == 0) && (k == i))) {
+ dprintf("- found time: %li\n", gdp->xrule);
+ } else {
+ dprintf("- is is not a valid number: %li\n", gdp->xrule);
+ rrd_set_error
+ ("parameter '%s' does not represent time in line %s\n",
+ tmpstr, line);
+ return 1;
+ }
+ default:
+ k = 0;
+ sscanf(tmpstr, "%lf%n", &gdp->yrule, &k);
+ if (((j != 0) && (k == j)) || ((j == 0) && (k == i))) {
+ dprintf("- found number: %lf\n", gdp->yrule);
+ } else {
+ dprintf("- is is not a valid number: %lf\n", gdp->yrule);
+ rrd_set_error
+ ("parameter '%s' does not represent a number in line %s\n",
+ tmpstr, line);
+ return 1;
+ }
+ }
+ }
+
+ if (j) {
+ j++;
+ dprintf("- examining color '%s'\n", &tmpstr[j]);
+ if (rrd_parse_color(&tmpstr[j], gdp)) {
+ rrd_set_error("Could not parse color in '%s'", &tmpstr[j]);
+ return 1;
+ }
+ dprintf("- parsed color %0.0f,%0.0f,%0.0f,%0.0f\n", gdp->col.red,
+ gdp->col.green, gdp->col.blue, gdp->col.alpha);
+ colorfound = 1;
+ } else {
+ dprintf("- no color present in '%s'\n", tmpstr);
+ }
+
+ (*eaten) += i; /* after vname#color */
+ if (line[*eaten] != '\0') {
+ (*eaten)++; /* after colon */
+ }
+
+ if (gdp->gf == GF_TICK) {
+ dprintf("- parsing '%s'\n", &line[*eaten]);
+ dprintf("- looking for optional TICK number\n");
+ j = 0;
+ sscanf(&line[*eaten], "%lf%n", &gdp->yrule, &j);
+ if (j) {
+ if (line[*eaten + j] != '\0' && line[*eaten + j] != ':') {
+ rrd_set_error("Cannot parse TICK fraction '%s'", line);
+ return 1;
+ }
+ dprintf("- found number %f\n", gdp->yrule);
+ if (gdp->yrule > 1.0 || gdp->yrule < -1.0) {
+ rrd_set_error("Tick factor should be <= 1.0");
+ return 1;
+ }
+ (*eaten) += j;
+ } else {
+ dprintf("- not found, defaulting to 0.1\n");
+ gdp->yrule = 0.1;
+ }
+ if (line[*eaten] == '\0') {
+ dprintf("- done parsing line\n");
+ return 0;
+ } else {
+ if (line[*eaten] == ':') {
+ (*eaten)++;
+ } else {
+ rrd_set_error("Can't make sense of that TICK line");
+ return 1;
+ }
+ }
+ }
+
+ dprintf("- parsing '%s'\n", &line[*eaten]);
+
+ /* Legend is next. A legend without a color is an error.
+ ** Stacking an item without having a legend is OK however
+ ** then an empty legend should be specified.
+ ** LINE:val#color:STACK means legend is string "STACK"
+ ** LINE:val#color::STACK means no legend, and do STACK
+ ** LINE:val:STACK is an error (legend but no color)
+ ** LINE:val::STACK means no legend, and do STACK
+ */
+ if (colorfound) {
+ int err = 0;
+ char *linecp = strdup(line);
+
+ dprintf("- looking for optional legend\n");
+
+ dprintf("- examining '%s'\n", &line[*eaten]);
+ if (linecp[*eaten] != '\0' && linecp[*eaten] != ':') {
+ int spi;
+
+ /* If the legend is not empty, it has to be prefixed with spacecnt ' ' characters. This then gets
+ * replaced by the color box later on. */
+ for (spi = 0; spi < spacecnt && (*eaten) > 1; spi++) {
+ linecp[--(*eaten)] = ' ';
+ }
+ }
+
+ if (rrd_parse_legend(linecp, eaten, gdp))
+ err = 1;
+ free(linecp);
+ if (err)
+ return 1;
+
+ dprintf("- found legend '%s'\n", &gdp->legend[2]);
+ } else {
+ dprintf("- skipping empty legend\n");
+ if (line[*eaten] != '\0' && line[*eaten] != ':') {
+ rrd_set_error("Legend set but no color: %s", &line[*eaten]);
+ return 1;
+ }
+ }
+ if (line[*eaten] == '\0') {
+ dprintf("- done parsing line\n");
+ return 0;
+ }
+ (*eaten)++; /* after colon */
+
+ /* HRULE, VRULE and TICK cannot be stacked. */
+ if ((gdp->gf != GF_HRULE)
+ && (gdp->gf != GF_VRULE)
+ && (gdp->gf != GF_TICK)) {
+
+ dprintf("- parsing '%s', looking for STACK\n", &line[*eaten]);
+ j = scan_for_col(&line[*eaten], 5, tmpstr);
+ if (!strcmp("STACK", tmpstr)) {
+ dprintf("- found STACK\n");
+ gdp->stack = 1;
+ (*eaten) += j;
+ if (line[*eaten] == ':') {
+ (*eaten) += 1;
+ } else if (line[*eaten] == '\0') {
+ dprintf("- done parsing line\n");
+ return 0;
+ } else {
+ dprintf("- found %s instead of just STACK\n", &line[*eaten]);
+ rrd_set_error("STACK expected but %s found", &line[*eaten]);
+ return 1;
+ }
+ } else
+ dprintf("- not STACKing\n");
+ }
+
+ dprintf("- still more, should be dashes[=...]\n");
+ dprintf("- parsing '%s'\n", &line[*eaten]);
+ if (line[*eaten] != '\0') {
+ /* parse dash arguments here. Possible options:
+ - dashes
+ - dashes=n_on[,n_off[,n_on,n_off]]
+ - dashes[=n_on[,n_off[,n_on,n_off]]]:dash-offset=offset
+ allowing 64 characters for definition of dash style */
+ j = scan_for_col(&line[*eaten], 64, tmpstr);
+ /* start with dashes */
+ if (strcmp(tmpstr, "dashes") == 0) {
+ /* if line was "dashes" or "dashes:dash-offset=xdashes="
+ tmpstr will be "dashes" */
+ dprintf("- found %s\n", tmpstr);
+ /* initialise all required variables we need for dashed lines
+ using default dash length of 5 pixels */
+ gdp->dash = 1;
+ gdp->p_dashes = (double *) malloc(sizeof(double));
+ gdp->p_dashes[0] = 5;
+ gdp->ndash = 1;
+ gdp->offset = 0;
+ (*eaten) += j;
+ } else if (sscanf(tmpstr, "dashes=%s", tmpstr)) {
+ /* dashes=n_on[,n_off[,n_on,n_off]] */
+ char csv[64];
+ char *pch;
+ float dsh;
+ int count = 0;
+ char *saveptr;
+
+ strcpy(csv, tmpstr);
+
+ pch = strtok_r(tmpstr, ",", &saveptr);
+ while (pch != NULL) {
+ pch = strtok_r(NULL, ",", &saveptr);
+ count++;
+ }
+ dprintf("- %d dash value(s) found: ", count);
+ if (count > 0) {
+ gdp->dash = 1;
+ gdp->ndash = count;
+ gdp->p_dashes = (double *) malloc(sizeof(double) * count);
+ pch = strtok_r(csv, ",", &saveptr);
+ count = 0;
+ while (pch != NULL) {
+ if (sscanf(pch, "%f", &dsh)) {
+ gdp->p_dashes[count] = (double) dsh;
+ dprintf("%.1f ", gdp->p_dashes[count]);
+ count++;
+ }
+ pch = strtok_r(NULL, ",", &saveptr);
+ }
+ dprintf("\n");
+ } else
+ dprintf("- syntax error. No dash lengths found!\n");
+ (*eaten) += j;
+ } else
+ dprintf("- error: expected dashes[=...], found %s\n", tmpstr);
+ if (line[*eaten] == ':') {
+ (*eaten) += 1;
+ } else if (line[*eaten] == '\0') {
+ dprintf("- done parsing line\n");
+ return 0;
+ }
+ /* dashes[=n_on[,n_off[,n_on,n_off]]]:dash-offset=offset
+ allowing 16 characters for dash-offset=....
+ => 4 characters for the offset value */
+ j = scan_for_col(&line[*eaten], 16, tmpstr);
+ if (sscanf(tmpstr, "dash-offset=%lf", &gdp->offset)) {
+ dprintf("- found dash-offset=%.1f\n", gdp->offset);
+ gdp->dash = 1;
+ (*eaten) += j;
+ if (line[*eaten] == ':')
+ (*eaten) += 1;
+ }
+ if (line[*eaten] == '\0') {
+ dprintf("- done parsing line\n");
+ return 0;
+ }
+ }
+ if (line[*eaten] == '\0') {
+ dprintf("- done parsing line\n");
+ return 0;
+ }
+ (*eaten)++;
+ dprintf("- parsing '%s'\n", &line[*eaten]);
+
+ return 0;
+}
+
+int rrd_parse_make_vname(
+ const char *const line,
+ unsigned int *const eaten,
+ graph_desc_t *const gdp,
+ image_desc_t *const im)
+{
+ char tmpstr[MAX_VNAME_LEN + 10];
+ int i = 0;
+
+ sscanf(&line[*eaten], DEF_NAM_FMT "=%n", tmpstr, &i);
+ if (!i) {
+ rrd_set_error("Cannot parse vname from '%s'", line);
+ return 1;
+ }
+ dprintf("- found candidate '%s'\n", tmpstr);
+
+ if ((gdp->vidx = find_var(im, tmpstr)) >= 0) {
+ rrd_set_error("Attempting to reuse '%s'", im->gdes[gdp->vidx].vname);
+ return 1;
+ }
+ strcpy(gdp->vname, tmpstr);
+ dprintf("- created vname '%s' vidx %lu\n", gdp->vname, im->gdes_c - 1);
+ (*eaten) += i;
+ return 0;
+}
+
+int rrd_parse_def(
+ const char *const line,
+ unsigned int *const eaten,
+ graph_desc_t *const gdp,
+ image_desc_t *const im)
+{
+ int i = 0;
+ char command[7]; /* step, start, end, reduce */
+ char tmpstr[256];
+ rrd_time_value_t start_tv, end_tv;
+ time_t start_tmp = 0, end_tmp = 0;
+ char *parsetime_error = NULL;
+
+ start_tv.type = end_tv.type = ABSOLUTE_TIME;
+ start_tv.offset = end_tv.offset = 0;
+ localtime_r(&gdp->start, &start_tv.tm);
+ localtime_r(&gdp->end, &end_tv.tm);
+
+ dprintf("- parsing '%s'\n", &line[*eaten]);
+ dprintf("- from line '%s'\n", line);
+
+ if (rrd_parse_make_vname(line, eaten, gdp, im))
+ return 1;
+ i = scan_for_col(&line[*eaten], sizeof(gdp->rrd) - 1, gdp->rrd);
+ if (line[*eaten + i] != ':') {
+ rrd_set_error("Problems reading database name");
+ return 1;
+ }
+ (*eaten) += ++i;
+ dprintf("- using file '%s'\n", gdp->rrd);
+
+ i = 0;
+ sscanf(&line[*eaten], DS_NAM_FMT ":%n", gdp->ds_nam, &i);
+ if (!i) {
+ rrd_set_error("Cannot parse DS in '%s'", line);
+ return 1;
+ }
+ (*eaten) += i;
+ dprintf("- using DS '%s'\n", gdp->ds_nam);
+
+ if (rrd_parse_CF(line, eaten, gdp, &gdp->cf))
+ return 1;
+ gdp->cf_reduce = gdp->cf;
+
+ if (line[*eaten] == '\0')
+ return 0;
+
+ while (1) {
+ dprintf("- optional parameter follows: %s\n", &line[*eaten]);
+ i = 0;
+ sscanf(&line[*eaten], "%6[a-z]=%n", command, &i);
+ if (!i) {
+ rrd_set_error("Parse error in '%s'", line);
+ return 1;
+ }
+ (*eaten) += i;
+ dprintf("- processing '%s'\n", command);
+ if (!strcmp("reduce", command)) {
+ if (rrd_parse_CF(line, eaten, gdp, &gdp->cf_reduce))
+ return 1;
+ if (line[*eaten] != '\0')
+ (*eaten)--;
+ } else if (!strcmp("step", command)) {
+ i = 0;
+ sscanf(&line[*eaten], "%lu%n", &gdp->step, &i);
+ gdp->step_orig = gdp->step;
+ (*eaten) += i;
+ dprintf("- using step %lu\n", gdp->step);
+ } else if (!strcmp("start", command)) {
+ i = scan_for_col(&line[*eaten], 255, tmpstr);
+ (*eaten) += i;
+ if ((parsetime_error = rrd_parsetime(tmpstr, &start_tv))) {
+ rrd_set_error("start time: %s", parsetime_error);
+ return 1;
+ }
+ dprintf("- done parsing: '%s'\n", &line[*eaten]);
+ } else if (!strcmp("end", command)) {
+ i = scan_for_col(&line[*eaten], 255, tmpstr);
+ (*eaten) += i;
+ if ((parsetime_error = rrd_parsetime(tmpstr, &end_tv))) {
+ rrd_set_error("end time: %s", parsetime_error);
+ return 1;
+ }
+ dprintf("- done parsing: '%s'\n", &line[*eaten]);
+ } else {
+ rrd_set_error("Parse error in '%s'", line);
+ return 1;
+ }
+ if (line[*eaten] == '\0')
+ break;
+ if (line[*eaten] != ':') {
+ dprintf("- Expected to see end of string but got '%s'\n",
+ &line[*eaten]);
+ rrd_set_error("Parse error in '%s'", line);
+ return 1;
+ }
+ (*eaten)++;
+ }
+ if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
+ /* error string is set in rrd_parsetime.c */
+ return 1;
+ }
+ if (start_tmp < 3600 * 24 * 365 * 10) {
+ rrd_set_error("the first entry to fetch should be "
+ "after 1980 (%ld)", start_tmp);
+ return 1;
+ }
+
+ if (end_tmp < start_tmp) {
+ rrd_set_error("start (%ld) should be less than end (%ld)",
+ start_tmp, end_tmp);
+ return 1;
+ }
+
+ gdp->start = start_tmp;
+ gdp->end = end_tmp;
+ gdp->start_orig = start_tmp;
+ gdp->end_orig = end_tmp;
+
+ dprintf("- start time %lu\n", gdp->start);
+ dprintf("- end time %lu\n", gdp->end);
+
+ return 0;
+}
+
+int rrd_parse_vdef(
+ const char *const line,
+ unsigned int *const eaten,
+ graph_desc_t *const gdp,
+ image_desc_t *const im)
+{
+ char tmpstr[MAX_VNAME_LEN + 1]; /* vname\0 */
+ int i = 0;
+
+ dprintf("- parsing '%s'\n", &line[*eaten]);
+ if (rrd_parse_make_vname(line, eaten, gdp, im))
+ return 1;
+
+ sscanf(&line[*eaten], DEF_NAM_FMT ",%n", tmpstr, &i);
+ if (!i) {
+ rrd_set_error("Cannot parse line '%s'", line);
+ return 1;
+ }
+ if ((gdp->vidx = find_var(im, tmpstr)) < 0) {
+ rrd_set_error("Not a valid vname: %s in line %s", tmpstr, line);
+ return 1;
+ }
+ if (im->gdes[gdp->vidx].gf != GF_DEF && im->gdes[gdp->vidx].gf != GF_CDEF) {
+ rrd_set_error("variable '%s' not DEF nor "
+ "CDEF in VDEF '%s'", tmpstr, gdp->vname);
+ return 1;
+ }
+ dprintf("- found vname: '%s' vidx %li\n", tmpstr, gdp->vidx);
+ (*eaten) += i;
+
+ dprintf("- calling vdef_parse with param '%s'\n", &line[*eaten]);
+ vdef_parse(gdp, &line[*eaten]);
+ while (line[*eaten] != '\0' && line[*eaten] != ':')
+ (*eaten)++;
+
+ return 0;
+}
+
+int rrd_parse_cdef(
+ const char *const line,
+ unsigned int *const eaten,
+ graph_desc_t *const gdp,
+ image_desc_t *const im)
+{
+ dprintf("- parsing '%s'\n", &line[*eaten]);
+ if (rrd_parse_make_vname(line, eaten, gdp, im))
+ return 1;
+ if ((gdp->rpnp = rpn_parse((void *) im, &line[*eaten], &find_var_wrapper)
+ ) == NULL) {
+ rrd_set_error("invalid rpn expression in: %s", &line[*eaten]);
+ return 1;
+ };
+ while (line[*eaten] != '\0' && line[*eaten] != ':')
+ (*eaten)++;
+ return 0;
+}
+
+void rrd_graph_script(
+ int argc,
+ char *argv[],
+ image_desc_t *const im,
+ int optno)
+{
+ int i;
+
+ /* save state for STACK backward compat function */
+ enum gf_en last_gf = GF_PRINT;
+ float last_linewidth = 0.0;
+
+ for (i = optind + optno; i < argc; i++) {
+ graph_desc_t *gdp;
+ unsigned int eaten = 0;
+
+ if (gdes_alloc(im))
+ return; /* the error string is already set */
+ gdp = &im->gdes[im->gdes_c - 1];
+#ifdef DEBUG
+ gdp->debug = 1;
+#endif
+
+ if (rrd_parse_find_gf(argv[i], &eaten, gdp))
+ return;
+
+ switch (gdp->gf) {
+ case GF_SHIFT: /* vname:value */
+ if (rrd_parse_shift(argv[i], &eaten, gdp, im))
+ return;
+ break;
+ case GF_TEXTALIGN: /* left|right|center|justified */
+ if (rrd_parse_textalign(argv[i], &eaten, gdp))
+ return;
+ break;
+ case GF_XPORT:
+ if (rrd_parse_xport(argv[i], &eaten, gdp, im))
+ return;
+ break;
+ case GF_PRINT: /* vname:CF:format -or- vname:format */
+ im->prt_c++;
+ case GF_GPRINT: /* vname:CF:format -or- vname:format */
+ if (rrd_parse_print(argv[i], &eaten, gdp, im))
+ return;
+ break;
+ case GF_COMMENT: /* text */
+ if (rrd_parse_legend(argv[i], &eaten, gdp))
+ return;
+ break;
+ case GF_VRULE: /* value#color[:legend] */
+ case GF_HRULE: /* value#color[:legend] */
+ case GF_LINE: /* vname-or-value[#color[:legend]][:STACK] */
+ case GF_AREA: /* vname-or-value[#color[:legend]][:STACK] */
+ case GF_TICK: /* vname#color[:num[:legend]] */
+ if (rrd_parse_PVHLAST(argv[i], &eaten, gdp, im))
+ return;
+ last_gf = gdp->gf;
+ last_linewidth = gdp->linewidth;
+ break;
+ case GF_STACK: /* vname-or-value[#color[:legend]] */
+ if (rrd_parse_PVHLAST(argv[i], &eaten, gdp, im))
+ return;
+ if (last_gf == GF_LINE || last_gf == GF_AREA) {
+ gdp->gf = last_gf;
+ gdp->linewidth = last_linewidth;
+ } else {
+ rrd_set_error("STACK must follow LINE or AREA! command:\n%s",
+ &argv[i][eaten], argv[i]);
+ return;
+ }
+ break;
+ /* data acquisition */
+ case GF_DEF: /* vname=x:DS:CF:[:step=#][:start=#][:end=#] */
+ if (rrd_parse_def(argv[i], &eaten, gdp, im))
+ return;
+ break;
+ case GF_CDEF: /* vname=rpn-expression */
+ if (rrd_parse_cdef(argv[i], &eaten, gdp, im))
+ return;
+ break;
+ case GF_VDEF: /* vname=rpn-expression */
+ if (rrd_parse_vdef(argv[i], &eaten, gdp, im))
+ return;
+ break;
+ }
+ if (gdp->debug) {
+ dprintf("used %i out of %zi chars\n", eaten, strlen(argv[i]));
+ dprintf("parsed line: '%s'\n", argv[i]);
+ dprintf("remaining: '%s'\n", &argv[i][eaten]);
+ if (eaten >= strlen(argv[i]))
+ dprintf("Command finished successfully\n");
+ }
+ if (eaten < strlen(argv[i])) {
+ rrd_set_error("I don't understand '%s' in command: '%s'.",
+ &argv[i][eaten], argv[i]);
+ return;
+ }
+ }
+}
diff --git a/program/src/rrd_hw.c b/program/src/rrd_hw.c
--- /dev/null
+++ b/program/src/rrd_hw.c
@@ -0,0 +1,561 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ *****************************************************************************
+ * rrd_hw.c : Support for Holt-Winters Smoothing/ Aberrant Behavior Detection
+ *****************************************************************************
+ * Initial version by Jake Brutlag, WebTV Networks, 5/1/00
+ *****************************************************************************/
+
+#include "rrd_tool.h"
+#include "rrd_hw.h"
+#include "rrd_hw_math.h"
+#include "rrd_hw_update.h"
+
+#define hw_dep_idx(rrd, rra_idx) rrd->rra_def[rra_idx].par[RRA_dependent_rra_idx].u_cnt
+
+/* #define DEBUG */
+
+/* private functions */
+static unsigned long MyMod(
+ signed long val,
+ unsigned long mod);
+
+int lookup_seasonal(
+ rrd_t *rrd,
+ unsigned long rra_idx,
+ unsigned long rra_start,
+ rrd_file_t *rrd_file,
+ unsigned long offset,
+ rrd_value_t **seasonal_coef)
+{
+ unsigned long pos_tmp;
+
+ /* rra_ptr[].cur_row points to the rra row to be written; this function
+ * reads cur_row + offset */
+ unsigned long row_idx = rrd->rra_ptr[rra_idx].cur_row + offset;
+
+ /* handle wrap around */
+ if (row_idx >= rrd->rra_def[rra_idx].row_cnt)
+ row_idx = row_idx % (rrd->rra_def[rra_idx].row_cnt);
+
+ /* rra_start points to the appropriate rra block in the file */
+ /* compute the pointer to the appropriate location in the file */
+ pos_tmp =
+ rra_start +
+ (row_idx) * (rrd->stat_head->ds_cnt) * sizeof(rrd_value_t);
+
+ /* allocate memory if need be */
+ if (*seasonal_coef == NULL)
+ *seasonal_coef =
+ (rrd_value_t *) malloc((rrd->stat_head->ds_cnt) *
+ sizeof(rrd_value_t));
+ if (*seasonal_coef == NULL) {
+ rrd_set_error("memory allocation failure: seasonal coef");
+ return -1;
+ }
+
+ if (!rrd_seek(rrd_file, pos_tmp, SEEK_SET)) {
+ if (rrd_read
+ (rrd_file, *seasonal_coef,
+ sizeof(rrd_value_t) * rrd->stat_head->ds_cnt)
+ == (ssize_t) (sizeof(rrd_value_t) * rrd->stat_head->ds_cnt)) {
+ /* success! */
+ /* we can safely ignore the rule requiring a seek operation between read
+ * and write, because this read moves the file pointer to somewhere
+ * in the file other than the next write location.
+ * */
+ return 0;
+ } else {
+ rrd_set_error("read operation failed in lookup_seasonal(): %lu\n",
+ pos_tmp);
+ }
+ } else {
+ rrd_set_error("seek operation failed in lookup_seasonal(): %lu\n",
+ pos_tmp);
+ }
+
+ return -1;
+}
+
+/* For the specified CDP prep area and the FAILURES RRA,
+ * erase all history of past violations.
+ */
+void erase_violations(
+ rrd_t *rrd,
+ unsigned long cdp_idx,
+ unsigned long rra_idx)
+{
+ unsigned short i;
+ char *violations_array;
+
+ /* check that rra_idx is a CF_FAILURES array */
+ if (cf_conv(rrd->rra_def[rra_idx].cf_nam) != CF_FAILURES) {
+#ifdef DEBUG
+ fprintf(stderr, "erase_violations called for non-FAILURES RRA: %s\n",
+ rrd->rra_def[rra_idx].cf_nam);
+#endif
+ return;
+ }
+#ifdef DEBUG
+ fprintf(stderr, "scratch buffer before erase:\n");
+ for (i = 0; i < MAX_CDP_PAR_EN; i++) {
+ fprintf(stderr, "%lu ", rrd->cdp_prep[cdp_idx].scratch[i].u_cnt);
+ }
+ fprintf(stderr, "\n");
+#endif
+
+ /* WARNING: an array of longs on disk is treated as an array of chars
+ * in memory. */
+ violations_array = (char *) ((void *) rrd->cdp_prep[cdp_idx].scratch);
+ /* erase everything in the part of the CDP scratch array that will be
+ * used to store violations for the current window */
+ for (i = rrd->rra_def[rra_idx].par[RRA_window_len].u_cnt; i > 0; i--) {
+ violations_array[i - 1] = 0;
+ }
+#ifdef DEBUG
+ fprintf(stderr, "scratch buffer after erase:\n");
+ for (i = 0; i < MAX_CDP_PAR_EN; i++) {
+ fprintf(stderr, "%lu ", rrd->cdp_prep[cdp_idx].scratch[i].u_cnt);
+ }
+ fprintf(stderr, "\n");
+#endif
+}
+
+/* Smooth a periodic array with a moving average: equal weights and
+ * length = 5% of the period. */
+int apply_smoother(
+ rrd_t *rrd,
+ unsigned long rra_idx,
+ unsigned long rra_start,
+ rrd_file_t *rrd_file)
+{
+ unsigned long i, j, k;
+ unsigned long totalbytes;
+ rrd_value_t *rrd_values;
+ unsigned long row_length = rrd->stat_head->ds_cnt;
+ unsigned long row_count = rrd->rra_def[rra_idx].row_cnt;
+ unsigned long offset;
+ FIFOqueue **buffers;
+ rrd_value_t *working_average;
+ rrd_value_t *baseline;
+
+ if (atoi(rrd->stat_head->version) >= 4) {
+ offset = floor(rrd->rra_def[rra_idx].
+ par[RRA_seasonal_smoothing_window].
+ u_val / 2 * row_count);
+ } else {
+ offset = floor(0.05 / 2 * row_count);
+ }
+
+ if (offset == 0)
+ return 0; /* no smoothing */
+
+ /* allocate memory */
+ totalbytes = sizeof(rrd_value_t) * row_length * row_count;
+ rrd_values = (rrd_value_t *) malloc(totalbytes);
+ if (rrd_values == NULL) {
+ rrd_set_error("apply smoother: memory allocation failure");
+ return -1;
+ }
+
+ /* rra_start is at the beginning of this rra */
+ if (rrd_seek(rrd_file, rra_start, SEEK_SET)) {
+ rrd_set_error("seek to rra %d failed", rra_start);
+ free(rrd_values);
+ return -1;
+ }
+ rrd_flush(rrd_file);
+ /* could read all data in a single block, but we need to
+ * check for NA values */
+ for (i = 0; i < row_count; ++i) {
+ for (j = 0; j < row_length; ++j) {
+ if (rrd_read
+ (rrd_file, &(rrd_values[i * row_length + j]),
+ sizeof(rrd_value_t) * 1)
+ != (ssize_t) (sizeof(rrd_value_t) * 1)) {
+ rrd_set_error("reading value failed: %s",
+ rrd_strerror(errno));
+ }
+ if (isnan(rrd_values[i * row_length + j])) {
+ /* can't apply smoothing, still uninitialized values */
+#ifdef DEBUG
+ fprintf(stderr,
+ "apply_smoother: NA detected in seasonal array: %ld %ld\n",
+ i, j);
+#endif
+ free(rrd_values);
+ return 0;
+ }
+ }
+ }
+
+ /* allocate queues, one for each data source */
+ buffers = (FIFOqueue **) malloc(sizeof(FIFOqueue *) * row_length);
+ for (i = 0; i < row_length; ++i) {
+ queue_alloc(&(buffers[i]), 2 * offset + 1);
+ }
+ /* need working average initialized to 0 */
+ working_average = (rrd_value_t *) calloc(row_length, sizeof(rrd_value_t));
+ baseline = (rrd_value_t *) calloc(row_length, sizeof(rrd_value_t));
+
+ /* compute sums of the first 2*offset terms */
+ for (i = 0; i < 2 * offset; ++i) {
+ k = MyMod(i - offset, row_count);
+ for (j = 0; j < row_length; ++j) {
+ queue_push(buffers[j], rrd_values[k * row_length + j]);
+ working_average[j] += rrd_values[k * row_length + j];
+ }
+ }
+
+ /* compute moving averages */
+ for (i = offset; i < row_count + offset; ++i) {
+ for (j = 0; j < row_length; ++j) {
+ k = MyMod(i, row_count);
+ /* add a term to the sum */
+ working_average[j] += rrd_values[k * row_length + j];
+ queue_push(buffers[j], rrd_values[k * row_length + j]);
+
+ /* reset k to be the center of the window */
+ k = MyMod(i - offset, row_count);
+ /* overwrite rdd_values entry, the old value is already
+ * saved in buffers */
+ rrd_values[k * row_length + j] =
+ working_average[j] / (2 * offset + 1);
+ baseline[j] += rrd_values[k * row_length + j];
+
+ /* remove a term from the sum */
+ working_average[j] -= queue_pop(buffers[j]);
+ }
+ }
+
+ for (i = 0; i < row_length; ++i) {
+ queue_dealloc(buffers[i]);
+ baseline[i] /= row_count;
+ }
+ free(buffers);
+ free(working_average);
+
+ if (cf_conv(rrd->rra_def[rra_idx].cf_nam) == CF_SEASONAL) {
+ rrd_value_t (
+ *init_seasonality) (
+ rrd_value_t seasonal_coef,
+ rrd_value_t intercept);
+
+ switch (cf_conv(rrd->rra_def[hw_dep_idx(rrd, rra_idx)].cf_nam)) {
+ case CF_HWPREDICT:
+ init_seasonality = hw_additive_init_seasonality;
+ break;
+ case CF_MHWPREDICT:
+ init_seasonality = hw_multiplicative_init_seasonality;
+ break;
+ default:
+ rrd_set_error("apply smoother: SEASONAL rra doesn't have "
+ "valid dependency: %s",
+ rrd->rra_def[hw_dep_idx(rrd, rra_idx)].cf_nam);
+ return -1;
+ }
+
+ for (j = 0; j < row_length; ++j) {
+ for (i = 0; i < row_count; ++i) {
+ rrd_values[i * row_length + j] =
+ init_seasonality(rrd_values[i * row_length + j],
+ baseline[j]);
+ }
+ /* update the baseline coefficient,
+ * first, compute the cdp_index. */
+ offset = hw_dep_idx(rrd, rra_idx) * row_length + j;
+ (rrd->cdp_prep[offset]).scratch[CDP_hw_intercept].u_val +=
+ baseline[j];
+ }
+ /* flush cdp to disk */
+ rrd_flush(rrd_file);
+ if (rrd_seek(rrd_file, sizeof(stat_head_t) +
+ rrd->stat_head->ds_cnt * sizeof(ds_def_t) +
+ rrd->stat_head->rra_cnt * sizeof(rra_def_t) +
+ sizeof(live_head_t) +
+ rrd->stat_head->ds_cnt * sizeof(pdp_prep_t), SEEK_SET)) {
+ rrd_set_error("apply_smoother: seek to cdp_prep failed");
+ free(rrd_values);
+ return -1;
+ }
+ if (rrd_write(rrd_file, rrd->cdp_prep,
+ sizeof(cdp_prep_t) *
+ (rrd->stat_head->rra_cnt) * rrd->stat_head->ds_cnt)
+ != (ssize_t) (sizeof(cdp_prep_t) * (rrd->stat_head->rra_cnt) *
+ (rrd->stat_head->ds_cnt))) {
+ rrd_set_error("apply_smoother: cdp_prep write failed");
+ free(rrd_values);
+ return -1;
+ }
+ }
+
+ /* endif CF_SEASONAL */
+ /* flush updated values to disk */
+ rrd_flush(rrd_file);
+ if (rrd_seek(rrd_file, rra_start, SEEK_SET)) {
+ rrd_set_error("apply_smoother: seek to pos %d failed", rra_start);
+ free(rrd_values);
+ return -1;
+ }
+ /* write as a single block */
+ if (rrd_write
+ (rrd_file, rrd_values, sizeof(rrd_value_t) * row_length * row_count)
+ != (ssize_t) (sizeof(rrd_value_t) * row_length * row_count)) {
+ rrd_set_error("apply_smoother: write failed to %lu", rra_start);
+ free(rrd_values);
+ return -1;
+ }
+
+ rrd_flush(rrd_file);
+ free(rrd_values);
+ free(baseline);
+ return 0;
+}
+
+/* Reset aberrant behavior model coefficients, including intercept, slope,
+ * seasonal, and seasonal deviation for the specified data source. */
+void reset_aberrant_coefficients(
+ rrd_t *rrd,
+ rrd_file_t *rrd_file,
+ unsigned long ds_idx)
+{
+ unsigned long cdp_idx, rra_idx, i;
+ unsigned long cdp_start, rra_start;
+ rrd_value_t nan_buffer = DNAN;
+
+ /* compute the offset for the cdp area */
+ cdp_start = sizeof(stat_head_t) +
+ rrd->stat_head->ds_cnt * sizeof(ds_def_t) +
+ rrd->stat_head->rra_cnt * sizeof(rra_def_t) +
+ sizeof(live_head_t) + rrd->stat_head->ds_cnt * sizeof(pdp_prep_t);
+ /* compute the offset for the first rra */
+ rra_start = cdp_start +
+ (rrd->stat_head->ds_cnt) * (rrd->stat_head->rra_cnt) *
+ sizeof(cdp_prep_t) + rrd->stat_head->rra_cnt * sizeof(rra_ptr_t);
+
+ /* loop over the RRAs */
+ for (rra_idx = 0; rra_idx < rrd->stat_head->rra_cnt; rra_idx++) {
+ cdp_idx = rra_idx * (rrd->stat_head->ds_cnt) + ds_idx;
+ switch (cf_conv(rrd->rra_def[rra_idx].cf_nam)) {
+ case CF_HWPREDICT:
+ case CF_MHWPREDICT:
+ init_hwpredict_cdp(&(rrd->cdp_prep[cdp_idx]));
+ break;
+ case CF_SEASONAL:
+ case CF_DEVSEASONAL:
+ /* don't use init_seasonal because it will reset burn-in, which
+ * means different data sources will be calling for the smoother
+ * at different times. */
+ rrd->cdp_prep[cdp_idx].scratch[CDP_hw_seasonal].u_val = DNAN;
+ rrd->cdp_prep[cdp_idx].scratch[CDP_hw_last_seasonal].u_val = DNAN;
+ /* move to first entry of data source for this rra */
+ rrd_seek(rrd_file, rra_start + ds_idx * sizeof(rrd_value_t),
+ SEEK_SET);
+ /* entries for the same data source are not contiguous,
+ * temporal entries are contiguous */
+ for (i = 0; i < rrd->rra_def[rra_idx].row_cnt; ++i) {
+ if (rrd_write(rrd_file, &nan_buffer, sizeof(rrd_value_t) * 1)
+ != sizeof(rrd_value_t) * 1) {
+ rrd_set_error
+ ("reset_aberrant_coefficients: write failed data source %lu rra %s",
+ ds_idx, rrd->rra_def[rra_idx].cf_nam);
+ return;
+ }
+ rrd_seek(rrd_file, (rrd->stat_head->ds_cnt - 1) *
+ sizeof(rrd_value_t), SEEK_CUR);
+ }
+ break;
+ case CF_FAILURES:
+ erase_violations(rrd, cdp_idx, rra_idx);
+ break;
+ default:
+ break;
+ }
+ /* move offset to the next rra */
+ rra_start += rrd->rra_def[rra_idx].row_cnt * rrd->stat_head->ds_cnt *
+ sizeof(rrd_value_t);
+ }
+ rrd_seek(rrd_file, cdp_start, SEEK_SET);
+ if (rrd_write(rrd_file, rrd->cdp_prep,
+ sizeof(cdp_prep_t) *
+ (rrd->stat_head->rra_cnt) * rrd->stat_head->ds_cnt)
+ != (ssize_t) (sizeof(cdp_prep_t) * (rrd->stat_head->rra_cnt) *
+ (rrd->stat_head->ds_cnt))) {
+ rrd_set_error("reset_aberrant_coefficients: cdp_prep write failed");
+ }
+}
+
+void init_hwpredict_cdp(
+ cdp_prep_t *cdp)
+{
+ cdp->scratch[CDP_hw_intercept].u_val = DNAN;
+ cdp->scratch[CDP_hw_last_intercept].u_val = DNAN;
+ cdp->scratch[CDP_hw_slope].u_val = DNAN;
+ cdp->scratch[CDP_hw_last_slope].u_val = DNAN;
+ cdp->scratch[CDP_null_count].u_cnt = 1;
+ cdp->scratch[CDP_last_null_count].u_cnt = 1;
+}
+
+void init_seasonal_cdp(
+ cdp_prep_t *cdp)
+{
+ cdp->scratch[CDP_hw_seasonal].u_val = DNAN;
+ cdp->scratch[CDP_hw_last_seasonal].u_val = DNAN;
+ cdp->scratch[CDP_init_seasonal].u_cnt = 1;
+}
+
+int update_aberrant_CF(
+ rrd_t *rrd,
+ rrd_value_t pdp_val,
+ enum cf_en current_cf,
+ unsigned long cdp_idx,
+ unsigned long rra_idx,
+ unsigned long ds_idx,
+ unsigned short CDP_scratch_idx,
+ rrd_value_t *seasonal_coef)
+{
+ static hw_functions_t hw_multiplicative_functions = {
+ hw_multiplicative_calculate_prediction,
+ hw_multiplicative_calculate_intercept,
+ hw_calculate_slope,
+ hw_multiplicative_calculate_seasonality,
+ hw_multiplicative_init_seasonality,
+ hw_calculate_seasonal_deviation,
+ hw_init_seasonal_deviation,
+ 1.0 /* identity value */
+ };
+
+ static hw_functions_t hw_additive_functions = {
+ hw_additive_calculate_prediction,
+ hw_additive_calculate_intercept,
+ hw_calculate_slope,
+ hw_additive_calculate_seasonality,
+ hw_additive_init_seasonality,
+ hw_calculate_seasonal_deviation,
+ hw_init_seasonal_deviation,
+ 0.0 /* identity value */
+ };
+
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = pdp_val;
+ switch (current_cf) {
+ case CF_HWPREDICT:
+ return update_hwpredict(rrd, cdp_idx, rra_idx, ds_idx,
+ CDP_scratch_idx, &hw_additive_functions);
+ case CF_MHWPREDICT:
+ return update_hwpredict(rrd, cdp_idx, rra_idx, ds_idx,
+ CDP_scratch_idx,
+ &hw_multiplicative_functions);
+ case CF_DEVPREDICT:
+ return update_devpredict(rrd, cdp_idx, rra_idx, ds_idx,
+ CDP_scratch_idx);
+ case CF_SEASONAL:
+ switch (cf_conv(rrd->rra_def[hw_dep_idx(rrd, rra_idx)].cf_nam)) {
+ case CF_HWPREDICT:
+ return update_seasonal(rrd, cdp_idx, rra_idx, ds_idx,
+ CDP_scratch_idx, seasonal_coef,
+ &hw_additive_functions);
+ case CF_MHWPREDICT:
+ return update_seasonal(rrd, cdp_idx, rra_idx, ds_idx,
+ CDP_scratch_idx, seasonal_coef,
+ &hw_multiplicative_functions);
+ default:
+ return -1;
+ }
+ case CF_DEVSEASONAL:
+ switch (cf_conv(rrd->rra_def[hw_dep_idx(rrd, rra_idx)].cf_nam)) {
+ case CF_HWPREDICT:
+ return update_devseasonal(rrd, cdp_idx, rra_idx, ds_idx,
+ CDP_scratch_idx, seasonal_coef,
+ &hw_additive_functions);
+ case CF_MHWPREDICT:
+ return update_devseasonal(rrd, cdp_idx, rra_idx, ds_idx,
+ CDP_scratch_idx, seasonal_coef,
+ &hw_multiplicative_functions);
+ default:
+ return -1;
+ }
+ case CF_FAILURES:
+ switch (cf_conv
+ (rrd->rra_def[hw_dep_idx(rrd, hw_dep_idx(rrd, rra_idx))].
+ cf_nam)) {
+ case CF_HWPREDICT:
+ return update_failures(rrd, cdp_idx, rra_idx, ds_idx,
+ CDP_scratch_idx, &hw_additive_functions);
+ case CF_MHWPREDICT:
+ return update_failures(rrd, cdp_idx, rra_idx, ds_idx,
+ CDP_scratch_idx,
+ &hw_multiplicative_functions);
+ default:
+ return -1;
+ }
+ case CF_AVERAGE:
+ default:
+ return 0;
+ }
+ return -1;
+}
+
+static unsigned long MyMod(
+ signed long val,
+ unsigned long mod)
+{
+ unsigned long new_val;
+
+ if (val < 0)
+ new_val = ((unsigned long) abs(val)) % mod;
+ else
+ new_val = (val % mod);
+
+ if (val < 0)
+ return (mod - new_val);
+ else
+ return (new_val);
+}
+
+/* a standard fixed-capacity FIF0 queue implementation
+ * No overflow checking is performed. */
+int queue_alloc(
+ FIFOqueue **q,
+ int capacity)
+{
+ *q = (FIFOqueue *) malloc(sizeof(FIFOqueue));
+ if (*q == NULL)
+ return -1;
+ (*q)->queue = (rrd_value_t *) malloc(sizeof(rrd_value_t) * capacity);
+ if ((*q)->queue == NULL) {
+ free(*q);
+ return -1;
+ }
+ (*q)->capacity = capacity;
+ (*q)->head = capacity;
+ (*q)->tail = 0;
+ return 0;
+}
+
+int queue_isempty(
+ FIFOqueue *q)
+{
+ return (q->head % q->capacity == q->tail);
+}
+
+void queue_push(
+ FIFOqueue *q,
+ rrd_value_t value)
+{
+ q->queue[(q->tail)++] = value;
+ q->tail = q->tail % q->capacity;
+}
+
+rrd_value_t queue_pop(
+ FIFOqueue *q)
+{
+ q->head = q->head % q->capacity;
+ return q->queue[(q->head)++];
+}
+
+void queue_dealloc(
+ FIFOqueue *q)
+{
+ free(q->queue);
+ free(q);
+}
diff --git a/program/src/rrd_hw.h b/program/src/rrd_hw.h
--- /dev/null
+++ b/program/src/rrd_hw.h
@@ -0,0 +1,65 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ *****************************************************************************
+ * rrd_hw.h : Support for Holt-Winters Smoothing/ Aberrant Behavior Detection
+ *****************************************************************************/
+
+/* functions implemented in rrd_hw.c */
+int update_aberrant_CF(
+ rrd_t *rrd,
+ rrd_value_t pdp_val,
+ enum cf_en current_cf,
+ unsigned long cdp_idx,
+ unsigned long rra_idx,
+ unsigned long ds_idx,
+ unsigned short CDP_scratch_idx,
+ rrd_value_t *seasonal_coef);
+int create_hw_contingent_rras(
+ rrd_t *rrd,
+ unsigned short period,
+ unsigned long hashed_name);
+int lookup_seasonal(
+ rrd_t *rrd,
+ unsigned long rra_idx,
+ unsigned long rra_start,
+ rrd_file_t *rrd_file,
+ unsigned long offset,
+ rrd_value_t **seasonal_coef);
+void erase_violations(
+ rrd_t *rrd,
+ unsigned long cdp_idx,
+ unsigned long rra_idx);
+int apply_smoother(
+ rrd_t *rrd,
+ unsigned long rra_idx,
+ unsigned long rra_start,
+ rrd_file_t *rrd_file);
+void reset_aberrant_coefficients(
+ rrd_t *rrd,
+ rrd_file_t *rrd_file,
+ unsigned long ds_idx);
+void init_hwpredict_cdp(
+ cdp_prep_t *);
+void init_seasonal_cdp(
+ cdp_prep_t *);
+
+#define BURNIN_CYCLES 3
+
+/* a standard fixed-capacity FIFO queue implementation */
+typedef struct FIFOqueue {
+ rrd_value_t *queue;
+ int capacity, head, tail;
+} FIFOqueue;
+
+int queue_alloc(
+ FIFOqueue **q,
+ int capacity);
+void queue_dealloc(
+ FIFOqueue *q);
+void queue_push(
+ FIFOqueue *q,
+ rrd_value_t value);
+int queue_isempty(
+ FIFOqueue *q);
+rrd_value_t queue_pop(
+ FIFOqueue *q);
diff --git a/program/src/rrd_hw_math.c b/program/src/rrd_hw_math.c
--- /dev/null
@@ -0,0 +1,144 @@
+/*****************************************************************************
+ * rrd_hw_math.c Math functions for Holt-Winters computations
+ *****************************************************************************/
+
+#include "rrd_tool.h"
+#include "rrd_hw_math.h"
+#include "rrd_config.h"
+
+/*****************************************************************************
+ * RRDtool supports both the additive and multiplicative Holt-Winters methods.
+ * The additive method makes predictions by adding seasonality to the baseline,
+ * whereas the multiplicative method multiplies the seasonality coefficient by
+ * the baseline to make a prediction. This file contains all the differences
+ * between the additive and multiplicative methods, as well as a few math
+ * functions common to them both.
+ ****************************************************************************/
+
+/*****************************************************************************
+ * Functions for additive Holt-Winters
+ *****************************************************************************/
+
+rrd_value_t hw_additive_calculate_prediction(
+ rrd_value_t intercept,
+ rrd_value_t slope,
+ int null_count,
+ rrd_value_t seasonal_coef)
+{
+ return intercept + slope * null_count + seasonal_coef;
+}
+
+rrd_value_t hw_additive_calculate_intercept(
+ rrd_value_t hw_alpha,
+ rrd_value_t observed,
+ rrd_value_t seasonal_coef,
+ unival *coefs)
+{
+ return hw_alpha * (observed - seasonal_coef)
+ + (1 - hw_alpha) * (coefs[CDP_hw_intercept].u_val
+ +
+ (coefs[CDP_hw_slope].u_val) *
+ (coefs[CDP_null_count].u_cnt));
+}
+
+rrd_value_t hw_additive_calculate_seasonality(
+ rrd_value_t hw_gamma,
+ rrd_value_t observed,
+ rrd_value_t intercept,
+ rrd_value_t seasonal_coef)
+{
+ return hw_gamma * (observed - intercept)
+ + (1 - hw_gamma) * seasonal_coef;
+}
+
+rrd_value_t hw_additive_init_seasonality(
+ rrd_value_t seasonal_coef,
+ rrd_value_t intercept)
+{
+ return seasonal_coef - intercept;
+}
+
+/*****************************************************************************
+ * Functions for multiplicative Holt-Winters
+ *****************************************************************************/
+
+rrd_value_t hw_multiplicative_calculate_prediction(
+ rrd_value_t intercept,
+ rrd_value_t slope,
+ int null_count,
+ rrd_value_t seasonal_coef)
+{
+ return (intercept + slope * null_count) * seasonal_coef;
+}
+
+rrd_value_t hw_multiplicative_calculate_intercept(
+ rrd_value_t hw_alpha,
+ rrd_value_t observed,
+ rrd_value_t seasonal_coef,
+ unival *coefs)
+{
+ if (seasonal_coef <= 0) {
+ return DNAN;
+ }
+
+ return hw_alpha * (observed / seasonal_coef)
+ + (1 - hw_alpha) * (coefs[CDP_hw_intercept].u_val
+ +
+ (coefs[CDP_hw_slope].u_val) *
+ (coefs[CDP_null_count].u_cnt));
+}
+
+rrd_value_t hw_multiplicative_calculate_seasonality(
+ rrd_value_t hw_gamma,
+ rrd_value_t observed,
+ rrd_value_t intercept,
+ rrd_value_t seasonal_coef)
+{
+ if (intercept <= 0) {
+ return DNAN;
+ }
+
+ return hw_gamma * (observed / intercept)
+ + (1 - hw_gamma) * seasonal_coef;
+}
+
+rrd_value_t hw_multiplicative_init_seasonality(
+ rrd_value_t seasonal_coef,
+ rrd_value_t intercept)
+{
+ if (intercept <= 0) {
+ return DNAN;
+ }
+
+ return seasonal_coef / intercept;
+}
+
+/*****************************************************************************
+ * Math functions common to additive and multiplicative Holt-Winters
+ *****************************************************************************/
+
+rrd_value_t hw_calculate_slope(
+ rrd_value_t hw_beta,
+ unival *coefs)
+{
+ return hw_beta * (coefs[CDP_hw_intercept].u_val -
+ coefs[CDP_hw_last_intercept].u_val)
+ + (1 - hw_beta) * coefs[CDP_hw_slope].u_val;
+}
+
+rrd_value_t hw_calculate_seasonal_deviation(
+ rrd_value_t hw_gamma,
+ rrd_value_t prediction,
+ rrd_value_t observed,
+ rrd_value_t last)
+{
+ return hw_gamma * fabs(prediction - observed)
+ + (1 - hw_gamma) * last;
+}
+
+rrd_value_t hw_init_seasonal_deviation(
+ rrd_value_t prediction,
+ rrd_value_t observed)
+{
+ return fabs(prediction - observed);
+}
diff --git a/program/src/rrd_hw_math.h b/program/src/rrd_hw_math.h
--- /dev/null
@@ -0,0 +1,132 @@
+/*****************************************************************************
+ * rrd_hw_math.h Math functions for Holt-Winters computations
+ *****************************************************************************/
+
+#include "rrd.h"
+#include "rrd_format.h"
+
+/* since /usr/include/bits/mathcalls.h:265 defines gamma already */
+#define gamma hw_gamma
+
+/*****************************************************************************
+ * Functions for additive Holt-Winters
+ *****************************************************************************/
+
+rrd_value_t hw_additive_calculate_prediction(
+ rrd_value_t intercept,
+ rrd_value_t slope,
+ int null_count,
+ rrd_value_t seasonal_coef);
+
+rrd_value_t hw_additive_calculate_intercept(
+ rrd_value_t alpha,
+ rrd_value_t scratch,
+ rrd_value_t seasonal_coef,
+ unival *coefs);
+
+rrd_value_t hw_additive_calculate_seasonality(
+ rrd_value_t gamma,
+ rrd_value_t scratch,
+ rrd_value_t intercept,
+ rrd_value_t seasonal_coef);
+
+rrd_value_t hw_additive_init_seasonality(
+ rrd_value_t seasonal_coef,
+ rrd_value_t intercept);
+
+/*****************************************************************************
+ * Functions for multiplicative Holt-Winters
+ *****************************************************************************/
+
+rrd_value_t hw_multiplicative_calculate_prediction(
+ rrd_value_t intercept,
+ rrd_value_t slope,
+ int null_count,
+ rrd_value_t seasonal_coef);
+
+rrd_value_t hw_multiplicative_calculate_intercept(
+ rrd_value_t alpha,
+ rrd_value_t scratch,
+ rrd_value_t seasonal_coef,
+ unival *coefs);
+
+rrd_value_t hw_multiplicative_calculate_seasonality(
+ rrd_value_t gamma,
+ rrd_value_t scratch,
+ rrd_value_t intercept,
+ rrd_value_t seasonal_coef);
+
+rrd_value_t hw_multiplicative_init_seasonality(
+ rrd_value_t seasonal_coef,
+ rrd_value_t intercept);
+
+/*****************************************************************************
+ * Math functions common to additive and multiplicative Holt-Winters
+ *****************************************************************************/
+
+rrd_value_t hw_calculate_slope(
+ rrd_value_t beta,
+ unival *coefs);
+
+rrd_value_t hw_calculate_seasonal_deviation(
+ rrd_value_t gamma,
+ rrd_value_t prediction,
+ rrd_value_t observed,
+ rrd_value_t last);
+
+rrd_value_t hw_init_seasonal_deviation(
+ rrd_value_t prediction,
+ rrd_value_t observed);
+
+
+/* Function container */
+
+typedef struct hw_functions_t {
+ rrd_value_t (
+ *predict) (
+ rrd_value_t intercept,
+ rrd_value_t slope,
+ int null_count,
+ rrd_value_t seasonal_coef);
+
+ rrd_value_t (
+ *intercept) (
+ rrd_value_t alpha,
+ rrd_value_t observed,
+ rrd_value_t seasonal_coef,
+ unival *coefs);
+
+ rrd_value_t (
+ *slope) (
+ rrd_value_t beta,
+ unival *coefs);
+
+ rrd_value_t (
+ *seasonality) (
+ rrd_value_t gamma,
+ rrd_value_t observed,
+ rrd_value_t intercept,
+ rrd_value_t seasonal_coef);
+
+ rrd_value_t (
+ *init_seasonality) (
+ rrd_value_t seasonal_coef,
+ rrd_value_t intercept);
+
+ rrd_value_t (
+ *seasonal_deviation) (
+ rrd_value_t gamma,
+ rrd_value_t prediction,
+ rrd_value_t observed,
+ rrd_value_t last);
+
+ rrd_value_t (
+ *init_seasonal_deviation) (
+ rrd_value_t prediction,
+ rrd_value_t observed);
+
+ rrd_value_t identity;
+} hw_functions_t;
+
+
+#undef gamma
diff --git a/program/src/rrd_hw_update.c b/program/src/rrd_hw_update.c
--- /dev/null
@@ -0,0 +1,476 @@
+/*****************************************************************************
+ * rrd_hw_update.c Functions for updating a Holt-Winters RRA
+ ****************************************************************************/
+
+#include "rrd_tool.h"
+#include "rrd_format.h"
+#include "rrd_config.h"
+#include "rrd_hw_math.h"
+#include "rrd_hw_update.h"
+
+static void init_slope_intercept(
+ unival *coefs,
+ unsigned short CDP_scratch_idx)
+{
+#ifdef DEBUG
+ fprintf(stderr, "Initialization of slope/intercept\n");
+#endif
+ coefs[CDP_hw_intercept].u_val = coefs[CDP_scratch_idx].u_val;
+ coefs[CDP_hw_last_intercept].u_val = coefs[CDP_scratch_idx].u_val;
+ /* initialize the slope to 0 */
+ coefs[CDP_hw_slope].u_val = 0.0;
+ coefs[CDP_hw_last_slope].u_val = 0.0;
+ /* initialize null count to 1 */
+ coefs[CDP_null_count].u_cnt = 1;
+ coefs[CDP_last_null_count].u_cnt = 1;
+}
+
+static int hw_is_violation(
+ rrd_value_t observed,
+ rrd_value_t prediction,
+ rrd_value_t deviation,
+ rrd_value_t delta_pos,
+ rrd_value_t delta_neg)
+{
+ return (observed > prediction + delta_pos * deviation
+ || observed < prediction - delta_neg * deviation);
+}
+
+int update_hwpredict(
+ rrd_t *rrd,
+ unsigned long cdp_idx,
+ unsigned long rra_idx,
+ unsigned long ds_idx,
+ unsigned short CDP_scratch_idx,
+ hw_functions_t * functions)
+{
+ rrd_value_t prediction;
+ unsigned long dependent_rra_idx, seasonal_cdp_idx;
+ unival *coefs = rrd->cdp_prep[cdp_idx].scratch;
+ rra_def_t *current_rra = &(rrd->rra_def[rra_idx]);
+ rrd_value_t seasonal_coef;
+
+ /* save coefficients from current prediction */
+ coefs[CDP_hw_last_intercept].u_val = coefs[CDP_hw_intercept].u_val;
+ coefs[CDP_hw_last_slope].u_val = coefs[CDP_hw_slope].u_val;
+ coefs[CDP_last_null_count].u_cnt = coefs[CDP_null_count].u_cnt;
+
+ /* retrieve the current seasonal coef */
+ dependent_rra_idx = current_rra->par[RRA_dependent_rra_idx].u_cnt;
+ seasonal_cdp_idx = dependent_rra_idx * (rrd->stat_head->ds_cnt) + ds_idx;
+
+ seasonal_coef = (dependent_rra_idx < rra_idx)
+ ? rrd->cdp_prep[seasonal_cdp_idx].scratch[CDP_hw_last_seasonal].u_val
+ : rrd->cdp_prep[seasonal_cdp_idx].scratch[CDP_hw_seasonal].u_val;
+
+ /* compute the prediction */
+ if (isnan(coefs[CDP_hw_intercept].u_val)
+ || isnan(coefs[CDP_hw_slope].u_val)
+ || isnan(seasonal_coef)) {
+ prediction = DNAN;
+
+ /* bootstrap initialization of slope and intercept */
+ if (isnan(coefs[CDP_hw_intercept].u_val) &&
+ !isnan(coefs[CDP_scratch_idx].u_val)) {
+ init_slope_intercept(coefs, CDP_scratch_idx);
+ }
+ /* if seasonal coefficient is NA, then don't update intercept, slope */
+ } else {
+ prediction = functions->predict(coefs[CDP_hw_intercept].u_val,
+ coefs[CDP_hw_slope].u_val,
+ coefs[CDP_null_count].u_cnt,
+ seasonal_coef);
+#ifdef DEBUG
+ fprintf(stderr,
+ "computed prediction: %f (intercept %f, slope %f, season %f)\n",
+ prediction, coefs[CDP_hw_intercept].u_val,
+ coefs[CDP_hw_slope].u_val, seasonal_coef);
+#endif
+ if (isnan(coefs[CDP_scratch_idx].u_val)) {
+ /* NA value, no updates of intercept, slope;
+ * increment the null count */
+ (coefs[CDP_null_count].u_cnt)++;
+ } else {
+ /* update the intercept */
+ coefs[CDP_hw_intercept].u_val =
+ functions->intercept(current_rra->par[RRA_hw_alpha].u_val,
+ coefs[CDP_scratch_idx].u_val,
+ seasonal_coef, coefs);
+
+ /* update the slope */
+ coefs[CDP_hw_slope].u_val =
+ functions->slope(current_rra->par[RRA_hw_beta].u_val, coefs);
+
+ /* reset the null count */
+ coefs[CDP_null_count].u_cnt = 1;
+#ifdef DEBUG
+ fprintf(stderr, "Updating intercept = %f, slope = %f\n",
+ coefs[CDP_hw_intercept].u_val, coefs[CDP_hw_slope].u_val);
+#endif
+ }
+ }
+
+ /* store the prediction for writing */
+ coefs[CDP_scratch_idx].u_val = prediction;
+ return 0;
+}
+
+int update_seasonal(
+ rrd_t *rrd,
+ unsigned long cdp_idx,
+ unsigned long rra_idx,
+ unsigned long ds_idx,
+ unsigned short CDP_scratch_idx,
+ rrd_value_t *seasonal_coef,
+ hw_functions_t * functions)
+{
+/* TODO: extract common if subblocks in the wake of I/O optimization */
+ rrd_value_t intercept, seasonal;
+ rra_def_t *current_rra = &(rrd->rra_def[rra_idx]);
+ rra_def_t *hw_rra =
+ &(rrd->rra_def[current_rra->par[RRA_dependent_rra_idx].u_cnt]);
+
+ /* obtain cdp_prep index for HWPREDICT */
+ unsigned long hw_cdp_idx = (current_rra->par[RRA_dependent_rra_idx].u_cnt)
+ * (rrd->stat_head->ds_cnt) + ds_idx;
+ unival *coefs = rrd->cdp_prep[hw_cdp_idx].scratch;
+
+ /* update seasonal coefficient in cdp prep areas */
+ seasonal = rrd->cdp_prep[cdp_idx].scratch[CDP_hw_seasonal].u_val;
+ rrd->cdp_prep[cdp_idx].scratch[CDP_hw_last_seasonal].u_val = seasonal;
+ rrd->cdp_prep[cdp_idx].scratch[CDP_hw_seasonal].u_val =
+ seasonal_coef[ds_idx];
+
+ if (isnan(rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val)) {
+ /* no update, store the old value unchanged,
+ * doesn't matter if it is NA */
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = seasonal;
+ return 0;
+ }
+
+ /* update seasonal value for disk */
+ if (current_rra->par[RRA_dependent_rra_idx].u_cnt < rra_idx) {
+ /* associated HWPREDICT has already been updated */
+ /* check for possible NA values */
+ if (isnan(coefs[CDP_hw_last_intercept].u_val)
+ || isnan(coefs[CDP_hw_last_slope].u_val)) {
+ /* this should never happen, as HWPREDICT was already updated */
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = DNAN;
+ } else if (isnan(seasonal)) {
+ /* initialization: intercept is not currently being updated */
+#ifdef DEBUG
+ fprintf(stderr, "Initialization of seasonal coef %lu\n",
+ rrd->rra_ptr[rra_idx].cur_row);
+#endif
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
+ functions->init_seasonality(rrd->cdp_prep[cdp_idx].
+ scratch[CDP_scratch_idx].u_val,
+ coefs[CDP_hw_last_intercept].
+ u_val);
+ } else {
+ intercept = coefs[CDP_hw_intercept].u_val;
+
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
+ functions->seasonality(current_rra->par[RRA_seasonal_gamma].
+ u_val,
+ rrd->cdp_prep[cdp_idx].
+ scratch[CDP_scratch_idx].u_val,
+ intercept, seasonal);
+#ifdef DEBUG
+ fprintf(stderr,
+ "Updating seasonal = %f (params: gamma %f, new intercept %f, old seasonal %f)\n",
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val,
+ current_rra->par[RRA_seasonal_gamma].u_val,
+ intercept, seasonal);
+#endif
+ }
+ } else {
+ /* SEASONAL array is updated first, which means the new intercept
+ * hasn't be computed; so we compute it here. */
+
+ /* check for possible NA values */
+ if (isnan(coefs[CDP_hw_intercept].u_val)
+ || isnan(coefs[CDP_hw_slope].u_val)) {
+ /* Initialization of slope and intercept will occur.
+ * force seasonal coefficient to 0 or 1. */
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
+ functions->identity;
+ } else if (isnan(seasonal)) {
+ /* initialization: intercept will not be updated
+ * CDP_hw_intercept = CDP_hw_last_intercept; just need to
+ * subtract/divide by this baseline value. */
+#ifdef DEBUG
+ fprintf(stderr, "Initialization of seasonal coef %lu\n",
+ rrd->rra_ptr[rra_idx].cur_row);
+#endif
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
+ functions->init_seasonality(rrd->cdp_prep[cdp_idx].
+ scratch[CDP_scratch_idx].u_val,
+ coefs[CDP_hw_intercept].u_val);
+ } else {
+ /* Note that we must get CDP_scratch_idx from SEASONAL array, as CDP_scratch_idx
+ * for HWPREDICT array will be DNAN. */
+ intercept = functions->intercept(hw_rra->par[RRA_hw_alpha].u_val,
+ rrd->cdp_prep[cdp_idx].
+ scratch[CDP_scratch_idx].u_val,
+ seasonal, coefs);
+
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
+ functions->seasonality(current_rra->par[RRA_seasonal_gamma].
+ u_val,
+ rrd->cdp_prep[cdp_idx].
+ scratch[CDP_scratch_idx].u_val,
+ intercept, seasonal);
+ }
+ }
+#ifdef DEBUG
+ fprintf(stderr, "seasonal coefficient set= %f\n",
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val);
+#endif
+ return 0;
+}
+
+int update_devpredict(
+ rrd_t *rrd,
+ unsigned long cdp_idx,
+ unsigned long rra_idx,
+ unsigned long ds_idx,
+ unsigned short CDP_scratch_idx)
+{
+ /* there really isn't any "update" here; the only reason this information
+ * is stored separately from DEVSEASONAL is to preserve deviation predictions
+ * for a longer duration than one seasonal cycle. */
+ unsigned long seasonal_cdp_idx =
+ (rrd->rra_def[rra_idx].par[RRA_dependent_rra_idx].u_cnt)
+ * (rrd->stat_head->ds_cnt) + ds_idx;
+
+ if (rrd->rra_def[rra_idx].par[RRA_dependent_rra_idx].u_cnt < rra_idx) {
+ /* associated DEVSEASONAL array already updated */
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val
+ =
+ rrd->cdp_prep[seasonal_cdp_idx].
+ scratch[CDP_last_seasonal_deviation].u_val;
+ } else {
+ /* associated DEVSEASONAL not yet updated */
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val
+ =
+ rrd->cdp_prep[seasonal_cdp_idx].scratch[CDP_seasonal_deviation].
+ u_val;
+ }
+ return 0;
+}
+
+int update_devseasonal(
+ rrd_t *rrd,
+ unsigned long cdp_idx,
+ unsigned long rra_idx,
+ unsigned long ds_idx,
+ unsigned short CDP_scratch_idx,
+ rrd_value_t *seasonal_dev,
+ hw_functions_t * functions)
+{
+ rrd_value_t prediction = 0, seasonal_coef = DNAN;
+ rra_def_t *current_rra = &(rrd->rra_def[rra_idx]);
+
+ /* obtain cdp_prep index for HWPREDICT */
+ unsigned long hw_rra_idx = current_rra->par[RRA_dependent_rra_idx].u_cnt;
+ unsigned long hw_cdp_idx = hw_rra_idx * (rrd->stat_head->ds_cnt) + ds_idx;
+ unsigned long seasonal_cdp_idx;
+ unival *coefs = rrd->cdp_prep[hw_cdp_idx].scratch;
+
+ rrd->cdp_prep[cdp_idx].scratch[CDP_last_seasonal_deviation].u_val =
+ rrd->cdp_prep[cdp_idx].scratch[CDP_seasonal_deviation].u_val;
+ /* retrieve the next seasonal deviation value, could be NA */
+ rrd->cdp_prep[cdp_idx].scratch[CDP_seasonal_deviation].u_val =
+ seasonal_dev[ds_idx];
+
+ /* retrieve the current seasonal_coef (not to be confused with the
+ * current seasonal deviation). Could make this more readable by introducing
+ * some wrapper functions. */
+ seasonal_cdp_idx =
+ (rrd->rra_def[hw_rra_idx].par[RRA_dependent_rra_idx].u_cnt)
+ * (rrd->stat_head->ds_cnt) + ds_idx;
+ if (rrd->rra_def[hw_rra_idx].par[RRA_dependent_rra_idx].u_cnt < rra_idx)
+ /* SEASONAL array already updated */
+ seasonal_coef =
+ rrd->cdp_prep[seasonal_cdp_idx].scratch[CDP_hw_last_seasonal].
+ u_val;
+ else
+ /* SEASONAL array not yet updated */
+ seasonal_coef =
+ rrd->cdp_prep[seasonal_cdp_idx].scratch[CDP_hw_seasonal].u_val;
+
+ /* compute the abs value of the difference between the prediction and
+ * observed value */
+ if (hw_rra_idx < rra_idx) {
+ /* associated HWPREDICT has already been updated */
+ if (isnan(coefs[CDP_hw_last_intercept].u_val) ||
+ isnan(coefs[CDP_hw_last_slope].u_val) || isnan(seasonal_coef)) {
+ /* one of the prediction values is uinitialized */
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = DNAN;
+ return 0;
+ } else {
+ prediction =
+ functions->predict(coefs[CDP_hw_last_intercept].u_val,
+ coefs[CDP_hw_last_slope].u_val,
+ coefs[CDP_last_null_count].u_cnt,
+ seasonal_coef);
+ }
+ } else {
+ /* associated HWPREDICT has NOT been updated */
+ if (isnan(coefs[CDP_hw_intercept].u_val) ||
+ isnan(coefs[CDP_hw_slope].u_val) || isnan(seasonal_coef)) {
+ /* one of the prediction values is uinitialized */
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = DNAN;
+ return 0;
+ } else {
+ prediction = functions->predict(coefs[CDP_hw_intercept].u_val,
+ coefs[CDP_hw_slope].u_val,
+ coefs[CDP_null_count].u_cnt,
+ seasonal_coef);
+ }
+ }
+
+ if (isnan(rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val)) {
+ /* no update, store existing value unchanged, doesn't
+ * matter if it is NA */
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
+ rrd->cdp_prep[cdp_idx].scratch[CDP_last_seasonal_deviation].u_val;
+ } else
+ if (isnan
+ (rrd->cdp_prep[cdp_idx].scratch[CDP_last_seasonal_deviation].
+ u_val)) {
+ /* initialization */
+#ifdef DEBUG
+ fprintf(stderr, "Initialization of seasonal deviation\n");
+#endif
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
+ functions->init_seasonal_deviation(prediction,
+ rrd->cdp_prep[cdp_idx].
+ scratch[CDP_scratch_idx].
+ u_val);
+ } else {
+ /* exponential smoothing update */
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
+ functions->seasonal_deviation(rrd->rra_def[rra_idx].
+ par[RRA_seasonal_gamma].u_val,
+ prediction,
+ rrd->cdp_prep[cdp_idx].
+ scratch[CDP_scratch_idx].u_val,
+ rrd->cdp_prep[cdp_idx].
+ scratch
+ [CDP_last_seasonal_deviation].
+ u_val);
+ }
+ return 0;
+}
+
+/* Check for a failure based on a threshold # of violations within the specified
+ * window. */
+int update_failures(
+ rrd_t *rrd,
+ unsigned long cdp_idx,
+ unsigned long rra_idx,
+ unsigned long ds_idx,
+ unsigned short CDP_scratch_idx,
+ hw_functions_t * functions)
+{
+ /* detection of a violation depends on 3 RRAs:
+ * HWPREDICT, SEASONAL, and DEVSEASONAL */
+ rra_def_t *current_rra = &(rrd->rra_def[rra_idx]);
+ unsigned long dev_rra_idx = current_rra->par[RRA_dependent_rra_idx].u_cnt;
+ rra_def_t *dev_rra = &(rrd->rra_def[dev_rra_idx]);
+ unsigned long hw_rra_idx = dev_rra->par[RRA_dependent_rra_idx].u_cnt;
+ rra_def_t *hw_rra = &(rrd->rra_def[hw_rra_idx]);
+ unsigned long seasonal_rra_idx = hw_rra->par[RRA_dependent_rra_idx].u_cnt;
+ unsigned long temp_cdp_idx;
+ rrd_value_t deviation = DNAN;
+ rrd_value_t seasonal_coef = DNAN;
+ rrd_value_t prediction = DNAN;
+ char violation = 0;
+ unsigned short violation_cnt = 0, i;
+ char *violations_array;
+
+ /* usual checks to determine the order of the RRAs */
+ temp_cdp_idx = dev_rra_idx * (rrd->stat_head->ds_cnt) + ds_idx;
+ if (rra_idx < seasonal_rra_idx) {
+ /* DEVSEASONAL not yet updated */
+ deviation =
+ rrd->cdp_prep[temp_cdp_idx].scratch[CDP_seasonal_deviation].u_val;
+ } else {
+ /* DEVSEASONAL already updated */
+ deviation =
+ rrd->cdp_prep[temp_cdp_idx].scratch[CDP_last_seasonal_deviation].
+ u_val;
+ }
+ if (!isnan(deviation)) {
+
+ temp_cdp_idx = seasonal_rra_idx * (rrd->stat_head->ds_cnt) + ds_idx;
+ if (rra_idx < seasonal_rra_idx) {
+ /* SEASONAL not yet updated */
+ seasonal_coef =
+ rrd->cdp_prep[temp_cdp_idx].scratch[CDP_hw_seasonal].u_val;
+ } else {
+ /* SEASONAL already updated */
+ seasonal_coef =
+ rrd->cdp_prep[temp_cdp_idx].scratch[CDP_hw_last_seasonal].
+ u_val;
+ }
+ /* in this code block, we know seasonal coef is not DNAN, because deviation is not
+ * null */
+
+ temp_cdp_idx = hw_rra_idx * (rrd->stat_head->ds_cnt) + ds_idx;
+ if (rra_idx < hw_rra_idx) {
+ /* HWPREDICT not yet updated */
+ prediction =
+ functions->predict(rrd->cdp_prep[temp_cdp_idx].
+ scratch[CDP_hw_intercept].u_val,
+ rrd->cdp_prep[temp_cdp_idx].
+ scratch[CDP_hw_slope].u_val,
+ rrd->cdp_prep[temp_cdp_idx].
+ scratch[CDP_null_count].u_cnt,
+ seasonal_coef);
+ } else {
+ /* HWPREDICT already updated */
+ prediction =
+ functions->predict(rrd->cdp_prep[temp_cdp_idx].
+ scratch[CDP_hw_last_intercept].u_val,
+ rrd->cdp_prep[temp_cdp_idx].
+ scratch[CDP_hw_last_slope].u_val,
+ rrd->cdp_prep[temp_cdp_idx].
+ scratch[CDP_last_null_count].u_cnt,
+ seasonal_coef);
+ }
+
+ /* determine if the observed value is a violation */
+ if (!isnan(rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val)) {
+ if (hw_is_violation
+ (rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val,
+ prediction, deviation, current_rra->par[RRA_delta_pos].u_val,
+ current_rra->par[RRA_delta_neg].u_val)) {
+ violation = 1;
+ }
+ } else {
+ violation = 1; /* count DNAN values as violations */
+ }
+
+ }
+
+ /* determine if a failure has occurred and update the failure array */
+ violation_cnt = violation;
+ violations_array = (char *) ((void *) rrd->cdp_prep[cdp_idx].scratch);
+ for (i = current_rra->par[RRA_window_len].u_cnt; i > 1; i--) {
+ /* shift */
+ violations_array[i - 1] = violations_array[i - 2];
+ violation_cnt += violations_array[i - 1];
+ }
+ violations_array[0] = violation;
+
+ if (violation_cnt < current_rra->par[RRA_failure_threshold].u_cnt)
+ /* not a failure */
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = 0.0;
+ else
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = 1.0;
+
+ return (rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val);
+}
diff --git a/program/src/rrd_hw_update.h b/program/src/rrd_hw_update.h
--- /dev/null
@@ -0,0 +1,44 @@
+/*****************************************************************************
+ * rrd_hw_update.h Functions for updating a Holt-Winters RRA
+ ****************************************************************************/
+
+int update_hwpredict(
+ rrd_t *rrd,
+ unsigned long cdp_idx,
+ unsigned long rra_idx,
+ unsigned long ds_idx,
+ unsigned short CDP_scratch_idx,
+ hw_functions_t * functions);
+
+int update_seasonal(
+ rrd_t *rrd,
+ unsigned long cdp_idx,
+ unsigned long rra_idx,
+ unsigned long ds_idx,
+ unsigned short CDP_scratch_idx,
+ rrd_value_t *seasonal_coef,
+ hw_functions_t * functions);
+
+int update_devpredict(
+ rrd_t *rrd,
+ unsigned long cdp_idx,
+ unsigned long rra_idx,
+ unsigned long ds_idx,
+ unsigned short CDP_scratch_idx);
+
+int update_devseasonal(
+ rrd_t *rrd,
+ unsigned long cdp_idx,
+ unsigned long rra_idx,
+ unsigned long ds_idx,
+ unsigned short CDP_scratch_idx,
+ rrd_value_t *seasonal_dev,
+ hw_functions_t * functions);
+
+int update_failures(
+ rrd_t *rrd,
+ unsigned long cdp_idx,
+ unsigned long rra_idx,
+ unsigned long ds_idx,
+ unsigned short CDP_scratch_idx,
+ hw_functions_t * functions);
diff --git a/program/src/rrd_i18n.h b/program/src/rrd_i18n.h
--- /dev/null
+++ b/program/src/rrd_i18n.h
@@ -0,0 +1,31 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Takao Fujiwara, 2008
+ *****************************************************************************
+ * rrd_i18n.h Common Header File
+ *****************************************************************************/
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#ifndef _RRD_I18N_H
+#define _RRD_I18N_H
+
+#ifndef _
+/* This is for other GNU distributions with internationalized messages.
+ When compiling libc, the _ macro is predefined. */
+#if defined(HAVE_LIBINTL_H) && defined(BUILD_LIBINTL)
+# include <libintl.h>
+#define _(String) gettext (String)
+#else
+#define _(String) (String)
+#endif
+#define N_(String) (String)
+#endif
+
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/program/src/rrd_info.c b/program/src/rrd_info.c
--- /dev/null
+++ b/program/src/rrd_info.c
@@ -0,0 +1,392 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ *****************************************************************************
+ * rrd_info Get Information about the configuration of an RRD
+ *****************************************************************************/
+
+#include "rrd_tool.h"
+#include "rrd_rpncalc.h"
+#include <stdarg.h>
+
+/* proto */
+rrd_info_t *rrd_info(
+ int,
+ char **);
+rrd_info_t *rrd_info_r(
+ char *filename);
+
+/* allocate memory for string */
+char *sprintf_alloc(
+ char *fmt,
+ ...)
+{
+ int maxlen = 1024 + strlen(fmt);
+ char *str = NULL;
+ va_list argp;
+ str = malloc(sizeof(char) * (maxlen + 1));
+ if (str != NULL) {
+ va_start(argp, fmt);
+#ifdef HAVE_VSNPRINTF
+ vsnprintf(str, maxlen, fmt, argp);
+#else
+ vsprintf(str, fmt, argp);
+#endif
+ }
+ va_end(argp);
+ return str;
+}
+
+/* the function formerly known as push was renamed to info_push and later
+ * rrd_info_push because it is now used outside the scope of this file */
+rrd_info_t
+ * rrd_info_push(rrd_info_t * info,
+ char *key, rrd_info_type_t type, rrd_infoval_t value)
+{
+ rrd_info_t *next;
+
+ next = malloc(sizeof(*next));
+ next->next = (rrd_info_t *) 0;
+ if (info)
+ info->next = next;
+ next->type = type;
+ next->key = key;
+ switch (type) {
+ case RD_I_VAL:
+ next->value.u_val = value.u_val;
+ break;
+ case RD_I_CNT:
+ next->value.u_cnt = value.u_cnt;
+ break;
+ case RD_I_INT:
+ next->value.u_int = value.u_int;
+ break;
+ case RD_I_STR:
+ next->value.u_str = malloc(sizeof(char) * (strlen(value.u_str) + 1));
+ strcpy(next->value.u_str, value.u_str);
+ break;
+ case RD_I_BLO:
+ next->value.u_blo.size = value.u_blo.size;
+ next->value.u_blo.ptr =
+ malloc(sizeof(unsigned char) * value.u_blo.size);
+ memcpy(next->value.u_blo.ptr, value.u_blo.ptr, value.u_blo.size);
+ break;
+ }
+ return (next);
+}
+
+
+rrd_info_t *rrd_info(
+ int argc,
+ char **argv)
+{
+ rrd_info_t *info;
+
+ if (argc < 2) {
+ rrd_set_error("please specify an rrd");
+ return NULL;
+ }
+
+ info = rrd_info_r(argv[1]);
+
+ return (info);
+}
+
+
+
+rrd_info_t *rrd_info_r(
+ char *filename)
+{
+ unsigned int i, ii = 0;
+ rrd_t rrd;
+ rrd_info_t *data = NULL, *cd;
+ rrd_infoval_t info;
+ rrd_file_t *rrd_file;
+ enum cf_en current_cf;
+ enum dst_en current_ds;
+
+ rrd_file = rrd_open(filename, &rrd, RRD_READONLY);
+ if (rrd_file == NULL)
+ goto err_free;
+
+ info.u_str = filename;
+ cd = rrd_info_push(NULL, sprintf_alloc("filename"), RD_I_STR, info);
+ data = cd;
+
+ info.u_str = rrd.stat_head->version;
+ cd = rrd_info_push(cd, sprintf_alloc("rrd_version"), RD_I_STR, info);
+
+ info.u_cnt = rrd.stat_head->pdp_step;
+ cd = rrd_info_push(cd, sprintf_alloc("step"), RD_I_CNT, info);
+
+ info.u_cnt = rrd.live_head->last_up;
+ cd = rrd_info_push(cd, sprintf_alloc("last_update"), RD_I_CNT, info);
+
+ for (i = 0; i < rrd.stat_head->ds_cnt; i++) {
+
+ info.u_str = rrd.ds_def[i].dst;
+ cd = rrd_info_push(cd, sprintf_alloc("ds[%s].type",
+ rrd.ds_def[i].ds_nam),
+ RD_I_STR, info);
+
+ current_ds = dst_conv(rrd.ds_def[i].dst);
+ switch (current_ds) {
+ case DST_CDEF:
+ {
+ char *buffer = NULL;
+
+ rpn_compact2str((rpn_cdefds_t *) &(rrd.ds_def[i].par[DS_cdef]),
+ rrd.ds_def, &buffer);
+ info.u_str = buffer;
+ cd = rrd_info_push(cd,
+ sprintf_alloc("ds[%s].cdef",
+ rrd.ds_def[i].ds_nam), RD_I_STR,
+ info);
+ free(buffer);
+ }
+ break;
+ default:
+ info.u_cnt = rrd.ds_def[i].par[DS_mrhb_cnt].u_cnt;
+ cd = rrd_info_push(cd,
+ sprintf_alloc("ds[%s].minimal_heartbeat",
+ rrd.ds_def[i].ds_nam), RD_I_CNT,
+ info);
+
+ info.u_val = rrd.ds_def[i].par[DS_min_val].u_val;
+ cd = rrd_info_push(cd,
+ sprintf_alloc("ds[%s].min",
+ rrd.ds_def[i].ds_nam), RD_I_VAL,
+ info);
+
+ info.u_val = rrd.ds_def[i].par[DS_max_val].u_val;
+ cd = rrd_info_push(cd,
+ sprintf_alloc("ds[%s].max",
+ rrd.ds_def[i].ds_nam), RD_I_VAL,
+ info);
+ break;
+ }
+
+ info.u_str = rrd.pdp_prep[i].last_ds;
+ cd = rrd_info_push(cd,
+ sprintf_alloc("ds[%s].last_ds",
+ rrd.ds_def[i].ds_nam), RD_I_STR,
+ info);
+
+ info.u_val = rrd.pdp_prep[i].scratch[PDP_val].u_val;
+ cd = rrd_info_push(cd,
+ sprintf_alloc("ds[%s].value",
+ rrd.ds_def[i].ds_nam), RD_I_VAL,
+ info);
+
+ info.u_cnt = rrd.pdp_prep[i].scratch[PDP_unkn_sec_cnt].u_cnt;
+ cd = rrd_info_push(cd,
+ sprintf_alloc("ds[%s].unknown_sec",
+ rrd.ds_def[i].ds_nam), RD_I_CNT,
+ info);
+ }
+
+ for (i = 0; i < rrd.stat_head->rra_cnt; i++) {
+ info.u_str = rrd.rra_def[i].cf_nam;
+ cd = rrd_info_push(cd, sprintf_alloc("rra[%d].cf", i), RD_I_STR,
+ info);
+ current_cf = cf_conv(rrd.rra_def[i].cf_nam);
+
+ info.u_cnt = rrd.rra_def[i].row_cnt;
+ cd = rrd_info_push(cd, sprintf_alloc("rra[%d].rows", i), RD_I_CNT,
+ info);
+
+ info.u_cnt = rrd.rra_ptr[i].cur_row;
+ cd = rrd_info_push(cd, sprintf_alloc("rra[%d].cur_row", i), RD_I_CNT,
+ info);
+
+ info.u_cnt = rrd.rra_def[i].pdp_cnt;
+ cd = rrd_info_push(cd, sprintf_alloc("rra[%d].pdp_per_row", i),
+ RD_I_CNT, info);
+
+ switch (current_cf) {
+ case CF_HWPREDICT:
+ case CF_MHWPREDICT:
+ info.u_val = rrd.rra_def[i].par[RRA_hw_alpha].u_val;
+ cd = rrd_info_push(cd, sprintf_alloc("rra[%d].alpha", i),
+ RD_I_VAL, info);
+ info.u_val = rrd.rra_def[i].par[RRA_hw_beta].u_val;
+ cd = rrd_info_push(cd, sprintf_alloc("rra[%d].beta", i), RD_I_VAL,
+ info);
+ break;
+ case CF_SEASONAL:
+ case CF_DEVSEASONAL:
+ info.u_val = rrd.rra_def[i].par[RRA_seasonal_gamma].u_val;
+ cd = rrd_info_push(cd, sprintf_alloc("rra[%d].gamma", i),
+ RD_I_VAL, info);
+ if (atoi(rrd.stat_head->version) >= 4) {
+ info.u_val =
+ rrd.rra_def[i].par[RRA_seasonal_smoothing_window].u_val;
+ cd = rrd_info_push(cd,
+ sprintf_alloc("rra[%d].smoothing_window",
+ i), RD_I_VAL, info);
+ }
+ break;
+ case CF_FAILURES:
+ info.u_val = rrd.rra_def[i].par[RRA_delta_pos].u_val;
+ cd = rrd_info_push(cd, sprintf_alloc("rra[%d].delta_pos", i),
+ RD_I_VAL, info);
+ info.u_val = rrd.rra_def[i].par[RRA_delta_neg].u_val;
+ cd = rrd_info_push(cd, sprintf_alloc("rra[%d].delta_neg", i),
+ RD_I_VAL, info);
+ info.u_cnt = rrd.rra_def[i].par[RRA_failure_threshold].u_cnt;
+ cd = rrd_info_push(cd,
+ sprintf_alloc("rra[%d].failure_threshold", i),
+ RD_I_CNT, info);
+ info.u_cnt = rrd.rra_def[i].par[RRA_window_len].u_cnt;
+ cd = rrd_info_push(cd, sprintf_alloc("rra[%d].window_length", i),
+ RD_I_CNT, info);
+ break;
+ case CF_DEVPREDICT:
+ break;
+ default:
+ info.u_val = rrd.rra_def[i].par[RRA_cdp_xff_val].u_val;
+ cd = rrd_info_push(cd, sprintf_alloc("rra[%d].xff", i), RD_I_VAL,
+ info);
+ break;
+ }
+
+ for (ii = 0; ii < rrd.stat_head->ds_cnt; ii++) {
+ switch (current_cf) {
+ case CF_HWPREDICT:
+ case CF_MHWPREDICT:
+ info.u_val =
+ rrd.cdp_prep[i * rrd.stat_head->ds_cnt +
+ ii].scratch[CDP_hw_intercept].u_val;
+ cd = rrd_info_push(cd,
+ sprintf_alloc
+ ("rra[%d].cdp_prep[%d].intercept", i, ii),
+ RD_I_VAL, info);
+ info.u_val =
+ rrd.cdp_prep[i * rrd.stat_head->ds_cnt +
+ ii].scratch[CDP_hw_slope].u_val;
+ cd = rrd_info_push(cd,
+ sprintf_alloc("rra[%d].cdp_prep[%d].slope",
+ i, ii), RD_I_VAL, info);
+ info.u_cnt =
+ rrd.cdp_prep[i * rrd.stat_head->ds_cnt +
+ ii].scratch[CDP_null_count].u_cnt;
+ cd = rrd_info_push(cd,
+ sprintf_alloc
+ ("rra[%d].cdp_prep[%d].NaN_count", i, ii),
+ RD_I_CNT, info);
+ break;
+ case CF_SEASONAL:
+ info.u_val =
+ rrd.cdp_prep[i * rrd.stat_head->ds_cnt +
+ ii].scratch[CDP_hw_seasonal].u_val;
+ cd = rrd_info_push(cd,
+ sprintf_alloc
+ ("rra[%d].cdp_prep[%d].seasonal", i, ii),
+ RD_I_VAL, info);
+ break;
+ case CF_DEVSEASONAL:
+ info.u_val =
+ rrd.cdp_prep[i * rrd.stat_head->ds_cnt +
+ ii].scratch[CDP_seasonal_deviation].u_val;
+ cd = rrd_info_push(cd,
+ sprintf_alloc
+ ("rra[%d].cdp_prep[%d].deviation", i, ii),
+ RD_I_VAL, info);
+ break;
+ case CF_DEVPREDICT:
+ break;
+ case CF_FAILURES:
+ {
+ unsigned short j;
+ char *violations_array;
+ char history[MAX_FAILURES_WINDOW_LEN + 1];
+
+ violations_array =
+ (char *) rrd.cdp_prep[i * rrd.stat_head->ds_cnt +
+ ii].scratch;
+ for (j = 0; j < rrd.rra_def[i].par[RRA_window_len].u_cnt; ++j)
+ history[j] = (violations_array[j] == 1) ? '1' : '0';
+ history[j] = '\0';
+ info.u_str = history;
+ cd = rrd_info_push(cd,
+ sprintf_alloc
+ ("rra[%d].cdp_prep[%d].history", i, ii),
+ RD_I_STR, info);
+ }
+ break;
+ default:
+ info.u_val =
+ rrd.cdp_prep[i * rrd.stat_head->ds_cnt +
+ ii].scratch[CDP_val].u_val;
+ cd = rrd_info_push(cd,
+ sprintf_alloc("rra[%d].cdp_prep[%d].value",
+ i, ii), RD_I_VAL, info);
+ info.u_cnt =
+ rrd.cdp_prep[i * rrd.stat_head->ds_cnt +
+ ii].scratch[CDP_unkn_pdp_cnt].u_cnt;
+ cd = rrd_info_push(cd,
+ sprintf_alloc
+ ("rra[%d].cdp_prep[%d].unknown_datapoints",
+ i, ii), RD_I_CNT, info);
+ break;
+ }
+ }
+ }
+
+ rrd_close(rrd_file);
+ err_free:
+ rrd_free(&rrd);
+ return (data);
+}
+
+
+void rrd_info_print(
+ rrd_info_t * data)
+{
+ while (data) {
+ printf("%s = ", data->key);
+
+ switch (data->type) {
+ case RD_I_VAL:
+ if (isnan(data->value.u_val))
+ printf("NaN\n");
+ else
+ printf("%0.10e\n", data->value.u_val);
+ break;
+ case RD_I_CNT:
+ printf("%lu\n", data->value.u_cnt);
+ break;
+ case RD_I_INT:
+ printf("%d\n", data->value.u_int);
+ break;
+ case RD_I_STR:
+ printf("\"%s\"\n", data->value.u_str);
+ break;
+ case RD_I_BLO:
+ printf("BLOB_SIZE:%lu\n", data->value.u_blo.size);
+ fwrite(data->value.u_blo.ptr, data->value.u_blo.size, 1, stdout);
+ break;
+ }
+ data = data->next;
+ }
+}
+
+void rrd_info_free(
+ rrd_info_t * data)
+{
+ rrd_info_t *save;
+
+ while (data) {
+ save = data;
+ if (data->key) {
+ if (data->type == RD_I_STR) {
+ free(data->value.u_str);
+ }
+ if (data->type == RD_I_BLO) {
+ free(data->value.u_blo.ptr);
+ }
+ free(data->key);
+ }
+ data = data->next;
+ free(save);
+ }
+}
diff --git a/program/src/rrd_is_thread_safe.h b/program/src/rrd_is_thread_safe.h
--- /dev/null
@@ -0,0 +1,28 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ * This file: Copyright 2003 Peter Stamfest <peter@stamfest.at>
+ * & Tobias Oetiker
+ * Distributed under the GPL
+ *****************************************************************************
+ * rrd_is_thread_safe.c Poisons some nasty function calls using GNU cpp
+ *****************************************************************************
+ * $Id$
+ *************************************************************************** */
+
+#ifndef _RRD_IS_THREAD_SAFE_H
+#define _RRD_IS_THREAD_SAFE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#undef strerror
+
+#if( 2 < __GNUC__ )
+#pragma GCC poison strtok asctime ctime gmtime localtime tmpnam strerror
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+#endif /*_RRD_IS_THREAD_SAFE_H */
diff --git a/program/src/rrd_last.c b/program/src/rrd_last.c
--- /dev/null
+++ b/program/src/rrd_last.c
@@ -0,0 +1,39 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ *****************************************************************************
+ * rrd_last.c
+ *****************************************************************************
+ * Initial version by Russ Wright, @Home Network, 9/28/98
+ *****************************************************************************/
+
+#include "rrd_tool.h"
+
+time_t rrd_last(
+ int argc,
+ char **argv)
+{
+ if (argc < 2) {
+ rrd_set_error("please specify an rrd");
+ return (-1);
+ }
+
+ return (rrd_last_r(argv[1]));
+}
+
+
+time_t rrd_last_r(
+ const char *filename)
+{
+ time_t lastup = -1;
+ rrd_file_t *rrd_file;
+
+ rrd_t rrd;
+
+ rrd_file = rrd_open(filename, &rrd, RRD_READONLY);
+ if (rrd_file != NULL) {
+ lastup = rrd.live_head->last_up;
+ rrd_close(rrd_file);
+ }
+ rrd_free(&rrd);
+ return (lastup);
+}
diff --git a/program/src/rrd_lastupdate.c b/program/src/rrd_lastupdate.c
--- /dev/null
@@ -0,0 +1,65 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ *****************************************************************************
+ * rrd_lastupdate Get the last datum entered for each DS
+ *****************************************************************************/
+
+#include "rrd_tool.h"
+#include "rrd_rpncalc.h"
+#include <stdarg.h>
+
+int rrd_lastupdate(
+ int argc,
+ char **argv,
+ time_t *last_update,
+ unsigned long *ds_cnt,
+ char ***ds_namv,
+ char ***last_ds)
+{
+ unsigned long i = 0;
+ char *filename;
+ rrd_t rrd;
+ rrd_file_t *rrd_file;
+
+ if (argc < 2) {
+ rrd_set_error("please specify an rrd");
+ goto err_out;
+ }
+ filename = argv[1];
+
+ rrd_file = rrd_open(filename, &rrd, RRD_READONLY);
+ if (rrd_file == NULL)
+ goto err_free;
+
+ *last_update = rrd.live_head->last_up;
+ *ds_cnt = rrd.stat_head->ds_cnt;
+ if (((*ds_namv) =
+ (char **) malloc(rrd.stat_head->ds_cnt * sizeof(char *))) == NULL) {
+ rrd_set_error("malloc fetch ds_namv array");
+ goto err_close;
+ }
+
+ if (((*last_ds) =
+ (char **) malloc(rrd.stat_head->ds_cnt * sizeof(char *))) == NULL) {
+ rrd_set_error("malloc fetch last_ds array");
+ goto err_free_ds_namv;
+ }
+
+ for (i = 0; i < rrd.stat_head->ds_cnt; i++) {
+ (*ds_namv)[i] = sprintf_alloc("%s", rrd.ds_def[i].ds_nam);
+ (*last_ds)[i] = sprintf_alloc("%s", rrd.pdp_prep[i].last_ds);
+ }
+
+ rrd_free(&rrd);
+ rrd_close(rrd_file);
+ return (0);
+
+ err_free_ds_namv:
+ free(*ds_namv);
+ err_close:
+ rrd_close(rrd_file);
+ err_free:
+ rrd_free(&rrd);
+ err_out:
+ return (-1);
+}
diff --git a/program/src/rrd_nan_inf.c b/program/src/rrd_nan_inf.c
--- /dev/null
@@ -0,0 +1,40 @@
+int done_nan = 0;
+int done_inf = 0;
+
+double dnan;
+double dinf;
+
+#if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
+#include <math.h>
+#include "rrd.h"
+
+#define NAN_FUNC (double)fmod(0.0,0.0)
+#define INF_FUNC (double)fabs((double)log(0.0))
+
+#else
+#include "rrd.h"
+
+#define NAN_FUNC (double)(0.0/0.0)
+#define INF_FUNC (double)(1.0/0.0)
+
+#endif
+
+double rrd_set_to_DNAN(
+ void)
+{
+ if (!done_nan) {
+ dnan = NAN_FUNC;
+ done_nan = 1;
+ }
+ return dnan;
+}
+
+double rrd_set_to_DINF(
+ void)
+{
+ if (!done_inf) {
+ dinf = INF_FUNC;
+ done_inf = 1;
+ }
+ return dinf;
+}
diff --git a/program/src/rrd_not_thread_safe.c b/program/src/rrd_not_thread_safe.c
--- /dev/null
@@ -0,0 +1,42 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ * This file: Copyright 2003 Peter Stamfest <peter@stamfest.at>
+ * & Tobias Oetiker
+ * Distributed under the GPL
+ *****************************************************************************
+ * rrd_not_thread_safe.c Contains routines used when thread safety is not
+ * an issue
+ *****************************************************************************
+ * $Id$
+ *************************************************************************** */
+#include "rrd.h"
+#include "rrd_tool.h"
+#define MAXLEN 4096
+#define ERRBUFLEN 256
+
+/* The global context is very useful in the transition period to even
+ more thread-safe stuff, it can be used whereever we need a context
+ and do not need to worry about concurrency. */
+static rrd_context_t global_ctx = {
+ "",
+ ""
+};
+
+/* #include <stdarg.h> */
+
+rrd_context_t *rrd_get_context(
+ void)
+{
+ return &global_ctx;
+}
+
+/* how ugly that is!!! - make sure strerror is what it should be. It
+ might be redefined to help in keeping other modules thread safe by
+ silently turning misplaced strerror into rrd_strerror, but here
+ this turns recursive! */
+#undef strerror
+const char *rrd_strerror(
+ int err)
+{
+ return strerror(err);
+}
diff --git a/program/src/rrd_open.c b/program/src/rrd_open.c
--- /dev/null
+++ b/program/src/rrd_open.c
@@ -0,0 +1,568 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ *****************************************************************************
+ * rrd_open.c Open an RRD File
+ *****************************************************************************
+ * $Id$
+ *****************************************************************************/
+
+#include "rrd_tool.h"
+#include "unused.h"
+#define MEMBLK 8192
+
+/* DEBUG 2 prints information obtained via mincore(2) */
+#define DEBUG 1
+/* do not calculate exact madvise hints but assume 1 page for headers and
+ * set DONTNEED for the rest, which is assumed to be data */
+/* Avoid calling madvise on areas that were already hinted. May be benefical if
+ * your syscalls are very slow */
+
+#ifdef HAVE_MMAP
+/* the cast to void* is there to avoid this warning seen on ia64 with certain
+ versions of gcc: 'cast increases required alignment of target type'
+*/
+#define __rrd_read(dst, dst_t, cnt) \
+ (dst) = (dst_t*)(void*) (data + offset); \
+ offset += sizeof(dst_t) * (cnt)
+#else
+#define __rrd_read(dst, dst_t, cnt) \
+ if ((dst = malloc(sizeof(dst_t)*(cnt))) == NULL) { \
+ rrd_set_error(#dst " malloc"); \
+ goto out_nullify_head; \
+ } \
+ offset += read (rrd_file->fd, dst, sizeof(dst_t)*(cnt))
+#endif
+
+/* get the address of the start of this page */
+#if defined USE_MADVISE || defined HAVE_POSIX_FADVISE
+#ifndef PAGE_START
+#define PAGE_START(addr) ((addr)&(~(_page_size-1)))
+#endif
+#endif
+
+/* Open a database file, return its header and an open filehandle,
+ * positioned to the first cdp in the first rra.
+ * In the error path of rrd_open, only rrd_free(&rrd) has to be called
+ * before returning an error. Do not call rrd_close upon failure of rrd_open.
+ */
+
+rrd_file_t *rrd_open(
+ const char *const file_name,
+ rrd_t *rrd,
+ unsigned rdwr)
+{
+ int flags = 0;
+ mode_t mode = S_IRUSR;
+ int version;
+
+#ifdef HAVE_MMAP
+ ssize_t _page_size = sysconf(_SC_PAGESIZE);
+ int mm_prot = PROT_READ, mm_flags = 0;
+ char *data;
+#endif
+ off_t offset = 0;
+ struct stat statb;
+ rrd_file_t *rrd_file = NULL;
+ off_t newfile_size = 0;
+
+ if (rdwr & RRD_CREAT) {
+ /* yes bad inline signaling alert, we are using the
+ floatcookie to pass the size in ... only used in resize */
+ newfile_size = (off_t) rrd->stat_head->float_cookie;
+ free(rrd->stat_head);
+ }
+ rrd_init(rrd);
+ rrd_file = malloc(sizeof(rrd_file_t));
+ if (rrd_file == NULL) {
+ rrd_set_error("allocating rrd_file descriptor for '%s'", file_name);
+ return NULL;
+ }
+ memset(rrd_file, 0, sizeof(rrd_file_t));
+
+#ifdef DEBUG
+ if ((rdwr & (RRD_READONLY | RRD_READWRITE)) ==
+ (RRD_READONLY | RRD_READWRITE)) {
+ /* Both READONLY and READWRITE were given, which is invalid. */
+ rrd_set_error("in read/write request mask");
+ exit(-1);
+ }
+#endif
+ if (rdwr & RRD_READONLY) {
+ flags |= O_RDONLY;
+#ifdef HAVE_MMAP
+ mm_flags = MAP_PRIVATE;
+# ifdef MAP_NORESERVE
+ mm_flags |= MAP_NORESERVE; /* readonly, so no swap backing needed */
+# endif
+#endif
+ } else {
+ if (rdwr & RRD_READWRITE) {
+ mode |= S_IWUSR;
+ flags |= O_RDWR;
+#ifdef HAVE_MMAP
+ mm_flags = MAP_SHARED;
+ mm_prot |= PROT_WRITE;
+#endif
+ }
+ if (rdwr & RRD_CREAT) {
+ flags |= (O_CREAT | O_TRUNC);
+ }
+ }
+ if (rdwr & RRD_READAHEAD) {
+#ifdef MAP_POPULATE
+ mm_flags |= MAP_POPULATE; /* populate ptes and data */
+#endif
+#if defined MAP_NONBLOCK
+ mm_flags |= MAP_NONBLOCK; /* just populate ptes */
+#endif
+ }
+#if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
+ flags |= O_BINARY;
+#endif
+
+ if ((rrd_file->fd = open(file_name, flags, mode)) < 0) {
+ rrd_set_error("opening '%s': %s", file_name, rrd_strerror(errno));
+ goto out_free;
+ }
+
+ /* Better try to avoid seeks as much as possible. stat may be heavy but
+ * many concurrent seeks are even worse. */
+ if (newfile_size == 0 && ((fstat(rrd_file->fd, &statb)) < 0)) {
+ rrd_set_error("fstat '%s': %s", file_name, rrd_strerror(errno));
+ goto out_close;
+ }
+ if (newfile_size == 0) {
+ rrd_file->file_len = statb.st_size;
+ } else {
+ rrd_file->file_len = newfile_size;
+ lseek(rrd_file->fd, newfile_size - 1, SEEK_SET);
+ write(rrd_file->fd, "\0", 1); /* poke */
+ lseek(rrd_file->fd, 0, SEEK_SET);
+ }
+#ifdef HAVE_POSIX_FADVISE
+ /* In general we need no read-ahead when dealing with rrd_files.
+ When we stop reading, it is highly unlikely that we start up again.
+ In this manner we actually save time and diskaccess (and buffer cache).
+ Thanks to Dave Plonka for the Idea of using POSIX_FADV_RANDOM here. */
+ posix_fadvise(rrd_file->fd, 0, 0, POSIX_FADV_RANDOM);
+#endif
+
+/*
+ if (rdwr & RRD_READWRITE)
+ {
+ if (setvbuf((rrd_file->fd),NULL,_IONBF,2)) {
+ rrd_set_error("failed to disable the stream buffer\n");
+ return (-1);
+ }
+ }
+*/
+#ifdef HAVE_MMAP
+ data = mmap(0, rrd_file->file_len, mm_prot, mm_flags,
+ rrd_file->fd, offset);
+
+ /* lets see if the first read worked */
+ if (data == MAP_FAILED) {
+ rrd_set_error("mmaping file '%s': %s", file_name,
+ rrd_strerror(errno));
+ goto out_close;
+ }
+ rrd_file->file_start = data;
+ if (rdwr & RRD_CREAT) {
+ memset(data, DNAN, newfile_size - 1);
+ goto out_done;
+ }
+#endif
+ if (rdwr & RRD_CREAT)
+ goto out_done;
+#ifdef USE_MADVISE
+ if (rdwr & RRD_COPY) {
+ /* We will read everything in a moment (copying) */
+ madvise(data, rrd_file->file_len, MADV_WILLNEED | MADV_SEQUENTIAL);
+ } else {
+ /* We do not need to read anything in for the moment */
+ madvise(data, rrd_file->file_len, MADV_RANDOM);
+ /* the stat_head will be needed soonish, so hint accordingly */
+ madvise(data, sizeof(stat_head_t), MADV_WILLNEED | MADV_RANDOM);
+ }
+#endif
+
+ __rrd_read(rrd->stat_head, stat_head_t,
+ 1);
+
+ /* lets do some test if we are on track ... */
+ if (memcmp(rrd->stat_head->cookie, RRD_COOKIE, sizeof(RRD_COOKIE)) != 0) {
+ rrd_set_error("'%s' is not an RRD file", file_name);
+ goto out_nullify_head;
+ }
+
+ if (rrd->stat_head->float_cookie != FLOAT_COOKIE) {
+ rrd_set_error("This RRD was created on another architecture");
+ goto out_nullify_head;
+ }
+
+ version = atoi(rrd->stat_head->version);
+
+ if (version > atoi(RRD_VERSION)) {
+ rrd_set_error("can't handle RRD file version %s",
+ rrd->stat_head->version);
+ goto out_nullify_head;
+ }
+#if defined USE_MADVISE
+ /* the ds_def will be needed soonish, so hint accordingly */
+ madvise(data + PAGE_START(offset),
+ sizeof(ds_def_t) * rrd->stat_head->ds_cnt, MADV_WILLNEED);
+#endif
+ __rrd_read(rrd->ds_def, ds_def_t,
+ rrd->stat_head->ds_cnt);
+
+#if defined USE_MADVISE
+ /* the rra_def will be needed soonish, so hint accordingly */
+ madvise(data + PAGE_START(offset),
+ sizeof(rra_def_t) * rrd->stat_head->rra_cnt, MADV_WILLNEED);
+#endif
+ __rrd_read(rrd->rra_def, rra_def_t,
+ rrd->stat_head->rra_cnt);
+
+ /* handle different format for the live_head */
+ if (version < 3) {
+ rrd->live_head = (live_head_t *) malloc(sizeof(live_head_t));
+ if (rrd->live_head == NULL) {
+ rrd_set_error("live_head_t malloc");
+ goto out_close;
+ }
+#if defined USE_MADVISE
+ /* the live_head will be needed soonish, so hint accordingly */
+ madvise(data + PAGE_START(offset), sizeof(time_t), MADV_WILLNEED);
+#endif
+ __rrd_read(rrd->legacy_last_up, time_t,
+ 1);
+
+ rrd->live_head->last_up = *rrd->legacy_last_up;
+ rrd->live_head->last_up_usec = 0;
+ } else {
+#if defined USE_MADVISE
+ /* the live_head will be needed soonish, so hint accordingly */
+ madvise(data + PAGE_START(offset),
+ sizeof(live_head_t), MADV_WILLNEED);
+#endif
+ __rrd_read(rrd->live_head, live_head_t,
+ 1);
+ }
+ __rrd_read(rrd->pdp_prep, pdp_prep_t,
+ rrd->stat_head->ds_cnt);
+ __rrd_read(rrd->cdp_prep, cdp_prep_t,
+ rrd->stat_head->rra_cnt * rrd->stat_head->ds_cnt);
+ __rrd_read(rrd->rra_ptr, rra_ptr_t,
+ rrd->stat_head->rra_cnt);
+
+ rrd_file->header_len = offset;
+ rrd_file->pos = offset;
+ out_done:
+ return (rrd_file);
+ out_nullify_head:
+ rrd->stat_head = NULL;
+ out_close:
+ close(rrd_file->fd);
+ out_free:
+ free(rrd_file);
+ return NULL;
+}
+
+
+#if defined DEBUG && DEBUG > 1
+/* Print list of in-core pages of a the current rrd_file. */
+static
+void mincore_print(
+ rrd_file_t *rrd_file,
+ char *mark)
+{
+#ifdef HAVE_MMAP
+ /* pretty print blocks in core */
+ off_t off;
+ unsigned char *vec;
+ ssize_t _page_size = sysconf(_SC_PAGESIZE);
+
+ off = rrd_file->file_len +
+ ((rrd_file->file_len + _page_size - 1) / _page_size);
+ vec = malloc(off);
+ if (vec != NULL) {
+ memset(vec, 0, off);
+ if (mincore(rrd_file->file_start, rrd_file->file_len, vec) == 0) {
+ int prev;
+ unsigned is_in = 0, was_in = 0;
+
+ for (off = 0, prev = 0; off < rrd_file->file_len; ++off) {
+ is_in = vec[off] & 1; /* if lsb set then is core resident */
+ if (off == 0)
+ was_in = is_in;
+ if (was_in != is_in) {
+ fprintf(stderr, "%s: %sin core: %p len %ld\n", mark,
+ was_in ? "" : "not ", vec + prev, off - prev);
+ was_in = is_in;
+ prev = off;
+ }
+ }
+ fprintf(stderr,
+ "%s: %sin core: %p len %ld\n", mark,
+ was_in ? "" : "not ", vec + prev, off - prev);
+ } else
+ fprintf(stderr, "mincore: %s", rrd_strerror(errno));
+ }
+#else
+ fprintf(stderr, "sorry mincore only works with mmap");
+#endif
+}
+#endif /* defined DEBUG && DEBUG > 1 */
+
+
+/* drop cache except for the header and the active pages */
+void rrd_dontneed(
+ rrd_file_t *rrd_file,
+ rrd_t *rrd)
+{
+#if defined USE_MADVISE || defined HAVE_POSIX_FADVISE
+ unsigned long dontneed_start;
+ unsigned long rra_start;
+ unsigned long active_block;
+ unsigned long i;
+ ssize_t _page_size = sysconf(_SC_PAGESIZE);
+
+#if defined DEBUG && DEBUG > 1
+ mincore_print(rrd_file, "before");
+#endif
+
+ /* ignoring errors from RRDs that are smaller then the file_len+rounding */
+ rra_start = rrd_file->header_len;
+ dontneed_start = PAGE_START(rra_start) + _page_size;
+ for (i = 0; i < rrd->stat_head->rra_cnt; ++i) {
+ active_block =
+ PAGE_START(rra_start
+ + rrd->rra_ptr[i].cur_row
+ * rrd->stat_head->ds_cnt * sizeof(rrd_value_t));
+ if (active_block > dontneed_start) {
+#ifdef USE_MADVISE
+ madvise(rrd_file->file_start + dontneed_start,
+ active_block - dontneed_start - 1, MADV_DONTNEED);
+#endif
+/* in linux at least only fadvise DONTNEED seems to purge pages from cache */
+#ifdef HAVE_POSIX_FADVISE
+ posix_fadvise(rrd_file->fd, dontneed_start,
+ active_block - dontneed_start - 1,
+ POSIX_FADV_DONTNEED);
+#endif
+ }
+ dontneed_start = active_block;
+ /* do not release 'hot' block if update for this RAA will occur
+ * within 10 minutes */
+ if (rrd->stat_head->pdp_step * rrd->rra_def[i].pdp_cnt -
+ rrd->live_head->last_up % (rrd->stat_head->pdp_step *
+ rrd->rra_def[i].pdp_cnt) < 10 * 60) {
+ dontneed_start += _page_size;
+ }
+ rra_start +=
+ rrd->rra_def[i].row_cnt * rrd->stat_head->ds_cnt *
+ sizeof(rrd_value_t);
+ }
+#ifdef USE_MADVISE
+ madvise(rrd_file->file_start + dontneed_start,
+ rrd_file->file_len - dontneed_start, MADV_DONTNEED);
+#endif
+#ifdef HAVE_POSIX_FADVISE
+ posix_fadvise(rrd_file->fd, dontneed_start,
+ rrd_file->file_len - dontneed_start, POSIX_FADV_DONTNEED);
+#endif
+#if defined DEBUG && DEBUG > 1
+ mincore_print(rrd_file, "after");
+#endif
+#endif /* without madvise and posix_fadvise ist does not make much sense todo anything */
+}
+
+
+
+
+
+int rrd_close(
+ rrd_file_t *rrd_file)
+{
+ int ret;
+
+#ifdef HAVE_MMAP
+ ret = msync(rrd_file->file_start, rrd_file->file_len, MS_ASYNC);
+ if (ret != 0)
+ rrd_set_error("msync rrd_file: %s", rrd_strerror(errno));
+ ret = munmap(rrd_file->file_start, rrd_file->file_len);
+ if (ret != 0)
+ rrd_set_error("munmap rrd_file: %s", rrd_strerror(errno));
+#endif
+ ret = close(rrd_file->fd);
+ if (ret != 0)
+ rrd_set_error("closing file: %s", rrd_strerror(errno));
+ free(rrd_file);
+ rrd_file = NULL;
+ return ret;
+}
+
+
+/* Set position of rrd_file. */
+
+off_t rrd_seek(
+ rrd_file_t *rrd_file,
+ off_t off,
+ int whence)
+{
+ off_t ret = 0;
+
+#ifdef HAVE_MMAP
+ if (whence == SEEK_SET)
+ rrd_file->pos = off;
+ else if (whence == SEEK_CUR)
+ rrd_file->pos += off;
+ else if (whence == SEEK_END)
+ rrd_file->pos = rrd_file->file_len + off;
+#else
+ ret = lseek(rrd_file->fd, off, whence);
+ if (ret < 0)
+ rrd_set_error("lseek: %s", rrd_strerror(errno));
+ rrd_file->pos = ret;
+#endif
+ /* mimic fseek, which returns 0 upon success */
+ return ret < 0; /*XXX: or just ret to mimic lseek */
+}
+
+
+/* Get current position in rrd_file. */
+
+off_t rrd_tell(
+ rrd_file_t *rrd_file)
+{
+ return rrd_file->pos;
+}
+
+
+/* Read count bytes into buffer buf, starting at rrd_file->pos.
+ * Returns the number of bytes read or <0 on error. */
+
+ssize_t rrd_read(
+ rrd_file_t *rrd_file,
+ void *buf,
+ size_t count)
+{
+#ifdef HAVE_MMAP
+ size_t _cnt = count;
+ ssize_t _surplus;
+
+ if (rrd_file->pos > rrd_file->file_len || _cnt == 0) /* EOF */
+ return 0;
+ if (buf == NULL)
+ return -1; /* EINVAL */
+ _surplus = rrd_file->pos + _cnt - rrd_file->file_len;
+ if (_surplus > 0) { /* short read */
+ _cnt -= _surplus;
+ }
+ if (_cnt == 0)
+ return 0; /* EOF */
+ buf = memcpy(buf, rrd_file->file_start + rrd_file->pos, _cnt);
+
+ rrd_file->pos += _cnt; /* mimmic read() semantics */
+ return _cnt;
+#else
+ ssize_t ret;
+
+ ret = read(rrd_file->fd, buf, count);
+ if (ret > 0)
+ rrd_file->pos += ret; /* mimmic read() semantics */
+ return ret;
+#endif
+}
+
+
+/* Write count bytes from buffer buf to the current position
+ * rrd_file->pos of rrd_file->fd.
+ * Returns the number of bytes written or <0 on error. */
+
+ssize_t rrd_write(
+ rrd_file_t *rrd_file,
+ const void *buf,
+ size_t count)
+{
+#ifdef HAVE_MMAP
+ if (count == 0)
+ return 0;
+ if (buf == NULL)
+ return -1; /* EINVAL */
+ memcpy(rrd_file->file_start + rrd_file->pos, buf, count);
+ rrd_file->pos += count;
+ return count; /* mimmic write() semantics */
+#else
+ ssize_t _sz = write(rrd_file->fd, buf, count);
+
+ if (_sz > 0)
+ rrd_file->pos += _sz;
+ return _sz;
+#endif
+}
+
+
+/* flush all data pending to be written to FD. */
+
+void rrd_flush(
+ rrd_file_t *rrd_file)
+{
+ if (fdatasync(rrd_file->fd) != 0) {
+ rrd_set_error("flushing fd %d: %s", rrd_file->fd,
+ rrd_strerror(errno));
+ }
+}
+
+
+/* Initialize RRD header. */
+
+void rrd_init(
+ rrd_t *rrd)
+{
+ rrd->stat_head = NULL;
+ rrd->ds_def = NULL;
+ rrd->rra_def = NULL;
+ rrd->live_head = NULL;
+ rrd->legacy_last_up = NULL;
+ rrd->rra_ptr = NULL;
+ rrd->pdp_prep = NULL;
+ rrd->cdp_prep = NULL;
+ rrd->rrd_value = NULL;
+}
+
+
+/* free RRD header data. */
+
+#ifdef HAVE_MMAP
+void rrd_free(
+ rrd_t *rrd)
+{
+ if (rrd->legacy_last_up) { /* this gets set for version < 3 only */
+ free(rrd->live_head);
+ }
+}
+#else
+void rrd_free(
+ rrd_t *rrd)
+{
+ free(rrd->live_head);
+ free(rrd->stat_head);
+ free(rrd->ds_def);
+ free(rrd->rra_def);
+ free(rrd->rra_ptr);
+ free(rrd->pdp_prep);
+ free(rrd->cdp_prep);
+ free(rrd->rrd_value);
+}
+#endif
+
+
+/* routine used by external libraries to free memory allocated by
+ * rrd library */
+
+void rrd_freemem(
+ void *mem)
+{
+ free(mem);
+}
diff --git a/program/src/rrd_parsetime.c b/program/src/rrd_parsetime.c
--- /dev/null
@@ -0,0 +1,1043 @@
+/*
+ * rrd_parsetime.c - parse time for at(1)
+ * Copyright (C) 1993, 1994 Thomas Koenig
+ *
+ * modifications for English-language times
+ * Copyright (C) 1993 David Parsons
+ *
+ * A lot of modifications and extensions
+ * (including the new syntax being useful for RRDB)
+ * Copyright (C) 1999 Oleg Cherevko (aka Olwi Deer)
+ *
+ * severe structural damage inflicted by Tobi Oetiker in 1999
+ *
+ * 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. The name of the author(s) may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) 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, WETHER 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.
+ */
+
+/* NOTE: nothing in here is thread-safe!!!! Not even the localtime
+ calls ... */
+
+/*
+ * The BNF-like specification of the time syntax parsed is below:
+ *
+ * As usual, [ X ] means that X is optional, { X } means that X may
+ * be either omitted or specified as many times as needed,
+ * alternatives are separated by |, brackets are used for grouping.
+ * (# marks the beginning of comment that extends to the end of line)
+ *
+ * TIME-SPECIFICATION ::= TIME-REFERENCE [ OFFSET-SPEC ] |
+ * OFFSET-SPEC |
+ * ( START | END ) OFFSET-SPEC
+ *
+ * TIME-REFERENCE ::= NOW | TIME-OF-DAY-SPEC [ DAY-SPEC-1 ] |
+ * [ TIME-OF-DAY-SPEC ] DAY-SPEC-2
+ *
+ * TIME-OF-DAY-SPEC ::= NUMBER (':') NUMBER [am|pm] | # HH:MM
+ * 'noon' | 'midnight' | 'teatime'
+ *
+ * DAY-SPEC-1 ::= NUMBER '/' NUMBER '/' NUMBER | # MM/DD/[YY]YY
+ * NUMBER '.' NUMBER '.' NUMBER | # DD.MM.[YY]YY
+ * NUMBER # Seconds since 1970
+ * NUMBER # YYYYMMDD
+ *
+ * DAY-SPEC-2 ::= MONTH-NAME NUMBER [NUMBER] | # Month DD [YY]YY
+ * 'yesterday' | 'today' | 'tomorrow' |
+ * DAY-OF-WEEK
+ *
+ *
+ * OFFSET-SPEC ::= '+'|'-' NUMBER TIME-UNIT { ['+'|'-'] NUMBER TIME-UNIT }
+ *
+ * TIME-UNIT ::= SECONDS | MINUTES | HOURS |
+ * DAYS | WEEKS | MONTHS | YEARS
+ *
+ * NOW ::= 'now' | 'n'
+ *
+ * START ::= 'start' | 's'
+ * END ::= 'end' | 'e'
+ *
+ * SECONDS ::= 'seconds' | 'second' | 'sec' | 's'
+ * MINUTES ::= 'minutes' | 'minute' | 'min' | 'm'
+ * HOURS ::= 'hours' | 'hour' | 'hr' | 'h'
+ * DAYS ::= 'days' | 'day' | 'd'
+ * WEEKS ::= 'weeks' | 'week' | 'wk' | 'w'
+ * MONTHS ::= 'months' | 'month' | 'mon' | 'm'
+ * YEARS ::= 'years' | 'year' | 'yr' | 'y'
+ *
+ * MONTH-NAME ::= 'jan' | 'january' | 'feb' | 'february' | 'mar' | 'march' |
+ * 'apr' | 'april' | 'may' | 'jun' | 'june' | 'jul' | 'july' |
+ * 'aug' | 'august' | 'sep' | 'september' | 'oct' | 'october' |
+ * 'nov' | 'november' | 'dec' | 'december'
+ *
+ * DAY-OF-WEEK ::= 'sunday' | 'sun' | 'monday' | 'mon' | 'tuesday' | 'tue' |
+ * 'wednesday' | 'wed' | 'thursday' | 'thu' | 'friday' | 'fri' |
+ * 'saturday' | 'sat'
+ *
+ *
+ * As you may note, there is an ambiguity with respect to
+ * the 'm' time unit (which can mean either minutes or months).
+ * To cope with this, code tries to read users mind :) by applying
+ * certain heuristics. There are two of them:
+ *
+ * 1. If 'm' is used in context of (i.e. right after the) years,
+ * months, weeks, or days it is assumed to mean months, while
+ * in the context of hours, minutes, and seconds it means minutes.
+ * (e.g., in -1y6m or +3w1m 'm' means 'months', while in
+ * -3h20m or +5s2m 'm' means 'minutes')
+ *
+ * 2. Out of context (i.e. right after the '+' or '-' sign) the
+ * meaning of 'm' is guessed from the number it directly follows.
+ * Currently, if the number absolute value is below 25 it is assumed
+ * that 'm' means months, otherwise it is treated as minutes.
+ * (e.g., -25m == -25 minutes, while +24m == +24 months)
+ *
+ */
+
+/* System Headers */
+
+/* Local headers */
+
+#include "rrd_tool.h"
+#include <stdarg.h>
+
+/* Structures and unions */
+
+enum { /* symbols */
+ MIDNIGHT, NOON, TEATIME,
+ PM, AM, YESTERDAY, TODAY, TOMORROW, NOW, START, END,
+ SECONDS, MINUTES, HOURS, DAYS, WEEKS, MONTHS, YEARS,
+ MONTHS_MINUTES,
+ NUMBER, PLUS, MINUS, DOT, COLON, SLASH, ID, JUNK,
+ JAN, FEB, MAR, APR, MAY, JUN,
+ JUL, AUG, SEP, OCT, NOV, DEC,
+ SUN, MON, TUE, WED, THU, FRI, SAT
+};
+
+/* the below is for plus_minus() */
+#define PREVIOUS_OP (-1)
+
+/* parse translation table - table driven parsers can be your FRIEND!
+ */
+struct SpecialToken {
+ char *name; /* token name */
+ int value; /* token id */
+};
+static const struct SpecialToken VariousWords[] = {
+ {"midnight", MIDNIGHT}, /* 00:00:00 of today or tomorrow */
+ {"noon", NOON}, /* 12:00:00 of today or tomorrow */
+ {"teatime", TEATIME}, /* 16:00:00 of today or tomorrow */
+ {"am", AM}, /* morning times for 0-12 clock */
+ {"pm", PM}, /* evening times for 0-12 clock */
+ {"tomorrow", TOMORROW},
+ {"yesterday", YESTERDAY},
+ {"today", TODAY},
+ {"now", NOW},
+ {"n", NOW},
+ {"start", START},
+ {"s", START},
+ {"end", END},
+ {"e", END},
+
+ {"jan", JAN},
+ {"feb", FEB},
+ {"mar", MAR},
+ {"apr", APR},
+ {"may", MAY},
+ {"jun", JUN},
+ {"jul", JUL},
+ {"aug", AUG},
+ {"sep", SEP},
+ {"oct", OCT},
+ {"nov", NOV},
+ {"dec", DEC},
+ {"january", JAN},
+ {"february", FEB},
+ {"march", MAR},
+ {"april", APR},
+ {"may", MAY},
+ {"june", JUN},
+ {"july", JUL},
+ {"august", AUG},
+ {"september", SEP},
+ {"october", OCT},
+ {"november", NOV},
+ {"december", DEC},
+ {"sunday", SUN},
+ {"sun", SUN},
+ {"monday", MON},
+ {"mon", MON},
+ {"tuesday", TUE},
+ {"tue", TUE},
+ {"wednesday", WED},
+ {"wed", WED},
+ {"thursday", THU},
+ {"thu", THU},
+ {"friday", FRI},
+ {"fri", FRI},
+ {"saturday", SAT},
+ {"sat", SAT},
+ {NULL, 0} /*** SENTINEL ***/
+};
+
+static const struct SpecialToken TimeMultipliers[] = {
+ {"second", SECONDS}, /* seconds multiplier */
+ {"seconds", SECONDS}, /* (pluralized) */
+ {"sec", SECONDS}, /* (generic) */
+ {"s", SECONDS}, /* (short generic) */
+ {"minute", MINUTES}, /* minutes multiplier */
+ {"minutes", MINUTES}, /* (pluralized) */
+ {"min", MINUTES}, /* (generic) */
+ {"m", MONTHS_MINUTES}, /* (short generic) */
+ {"hour", HOURS}, /* hours ... */
+ {"hours", HOURS}, /* (pluralized) */
+ {"hr", HOURS}, /* (generic) */
+ {"h", HOURS}, /* (short generic) */
+ {"day", DAYS}, /* days ... */
+ {"days", DAYS}, /* (pluralized) */
+ {"d", DAYS}, /* (short generic) */
+ {"week", WEEKS}, /* week ... */
+ {"weeks", WEEKS}, /* (pluralized) */
+ {"wk", WEEKS}, /* (generic) */
+ {"w", WEEKS}, /* (short generic) */
+ {"month", MONTHS}, /* week ... */
+ {"months", MONTHS}, /* (pluralized) */
+ {"mon", MONTHS}, /* (generic) */
+ {"year", YEARS}, /* year ... */
+ {"years", YEARS}, /* (pluralized) */
+ {"yr", YEARS}, /* (generic) */
+ {"y", YEARS}, /* (short generic) */
+ {NULL, 0} /*** SENTINEL ***/
+};
+
+/* File scope variables */
+
+/* context dependent list of specials for parser to recognize,
+ * required for us to be able distinguish between 'mon' as 'month'
+ * and 'mon' as 'monday'
+ */
+static const struct SpecialToken *Specials;
+
+static const char **scp; /* scanner - pointer at arglist */
+static char scc; /* scanner - count of remaining arguments */
+static const char *sct; /* scanner - next char pointer in current argument */
+static int need; /* scanner - need to advance to next argument */
+
+static char *sc_token = NULL; /* scanner - token buffer */
+static size_t sc_len; /* scanner - length of token buffer */
+static int sc_tokid; /* scanner - token id */
+
+/* Local functions */
+static void EnsureMemFree(
+ void);
+
+static void EnsureMemFree(
+ void)
+{
+ if (sc_token) {
+ free(sc_token);
+ sc_token = NULL;
+ }
+}
+
+/*
+ * A hack to compensate for the lack of the C++ exceptions
+ *
+ * Every function func that might generate parsing "exception"
+ * should return TIME_OK (aka NULL) or pointer to the error message,
+ * and should be called like this: try(func(args));
+ *
+ * if the try is not successful it will reset the token pointer ...
+ *
+ * [NOTE: when try(...) is used as the only statement in the "if-true"
+ * part of the if statement that also has an "else" part it should be
+ * either enclosed in the curly braces (despite the fact that it looks
+ * like a single statement) or NOT followed by the ";"]
+ */
+#define try(b) { \
+ char *_e; \
+ if((_e=(b))) \
+ { \
+ EnsureMemFree(); \
+ return _e; \
+ } \
+ }
+
+/*
+ * The panic() function was used in the original code to die, we redefine
+ * it as macro to start the chain of ascending returns that in conjunction
+ * with the try(b) above will simulate a sort of "exception handling"
+ */
+
+#define panic(e) { \
+ return (e); \
+ }
+
+/*
+ * ve() and e() are used to set the return error,
+ * the most appropriate use for these is inside panic(...)
+ */
+#define MAX_ERR_MSG_LEN 1024
+static char errmsg[MAX_ERR_MSG_LEN];
+
+static char *ve(
+ char *fmt,
+ va_list ap)
+{
+#ifdef HAVE_VSNPRINTF
+ vsnprintf(errmsg, MAX_ERR_MSG_LEN, fmt, ap);
+#else
+ vsprintf(errmsg, fmt, ap);
+#endif
+ EnsureMemFree();
+ return (errmsg);
+}
+
+static char *e(
+ char *fmt,
+ ...)
+{
+ char *err;
+ va_list ap;
+
+ va_start(ap, fmt);
+ err = ve(fmt, ap);
+ va_end(ap);
+ return (err);
+}
+
+/* Compare S1 and S2, ignoring case, returning less than, equal to or
+ greater than zero if S1 is lexicographically less than,
+ equal to or greater than S2. -- copied from GNU libc*/
+static int mystrcasecmp(
+ const char *s1,
+ const char *s2)
+{
+ const unsigned char *p1 = (const unsigned char *) s1;
+ const unsigned char *p2 = (const unsigned char *) s2;
+ unsigned char c1, c2;
+
+ if (p1 == p2)
+ return 0;
+
+ do {
+ c1 = tolower(*p1++);
+ c2 = tolower(*p2++);
+ if (c1 == '\0')
+ break;
+ }
+ while (c1 == c2);
+
+ return c1 - c2;
+}
+
+/*
+ * parse a token, checking if it's something special to us
+ */
+static int parse_token(
+ char *arg)
+{
+ int i;
+
+ for (i = 0; Specials[i].name != NULL; i++)
+ if (mystrcasecmp(Specials[i].name, arg) == 0)
+ return sc_tokid = Specials[i].value;
+
+ /* not special - must be some random id */
+ return sc_tokid = ID;
+} /* parse_token */
+
+
+
+/*
+ * init_scanner() sets up the scanner to eat arguments
+ */
+static char *init_scanner(
+ int argc,
+ const char **argv)
+{
+ scp = argv;
+ scc = argc;
+ need = 1;
+ sc_len = 1;
+ while (argc-- > 0)
+ sc_len += strlen(*argv++);
+
+ sc_token = (char *) malloc(sc_len * sizeof(char));
+ if (sc_token == NULL)
+ return "Failed to allocate memory";
+ return TIME_OK;
+} /* init_scanner */
+
+/*
+ * token() fetches a token from the input stream
+ */
+static int token(
+ void)
+{
+ int idx;
+
+ while (1) {
+ memset(sc_token, '\0', sc_len);
+ sc_tokid = EOF;
+ idx = 0;
+
+ /* if we need to read another argument, walk along the argument list;
+ * when we fall off the arglist, we'll just return EOF forever
+ */
+ if (need) {
+ if (scc < 1)
+ return sc_tokid;
+ sct = *scp;
+ scp++;
+ scc--;
+ need = 0;
+ }
+ /* eat whitespace now - if we walk off the end of the argument,
+ * we'll continue, which puts us up at the top of the while loop
+ * to fetch the next argument in
+ */
+ while (isspace((unsigned char) *sct) || *sct == '_' || *sct == ',')
+ ++sct;
+ if (!*sct) {
+ need = 1;
+ continue;
+ }
+
+ /* preserve the first character of the new token
+ */
+ sc_token[0] = *sct++;
+
+ /* then see what it is
+ */
+ if (isdigit((unsigned char) (sc_token[0]))) {
+ while (isdigit((unsigned char) (*sct)))
+ sc_token[++idx] = *sct++;
+ sc_token[++idx] = '\0';
+ return sc_tokid = NUMBER;
+ } else if (isalpha((unsigned char) (sc_token[0]))) {
+ while (isalpha((unsigned char) (*sct)))
+ sc_token[++idx] = *sct++;
+ sc_token[++idx] = '\0';
+ return parse_token(sc_token);
+ } else
+ switch (sc_token[0]) {
+ case ':':
+ return sc_tokid = COLON;
+ case '.':
+ return sc_tokid = DOT;
+ case '+':
+ return sc_tokid = PLUS;
+ case '-':
+ return sc_tokid = MINUS;
+ case '/':
+ return sc_tokid = SLASH;
+ default:
+ /*OK, we did not make it ... */
+ sct--;
+ return sc_tokid = EOF;
+ }
+ } /* while (1) */
+} /* token */
+
+
+/*
+ * expect2() gets a token and complains if it's not the token we want
+ */
+static char *expect2(
+ int desired,
+ char *complain_fmt,
+ ...)
+{
+ va_list ap;
+
+ va_start(ap, complain_fmt);
+ if (token() != desired) {
+ panic(ve(complain_fmt, ap));
+ }
+ va_end(ap);
+ return TIME_OK;
+
+} /* expect2 */
+
+
+/*
+ * plus_minus() is used to parse a single NUMBER TIME-UNIT pair
+ * for the OFFSET-SPEC.
+ * It also applies those m-guessing heuristics.
+ */
+static char *plus_minus(
+ rrd_time_value_t * ptv,
+ int doop)
+{
+ static int op = PLUS;
+ static int prev_multiplier = -1;
+ int delta;
+
+ if (doop >= 0) {
+ op = doop;
+ try(expect2
+ (NUMBER, "There should be number after '%c'",
+ op == PLUS ? '+' : '-'));
+ prev_multiplier = -1; /* reset months-minutes guessing mechanics */
+ }
+ /* if doop is < 0 then we repeat the previous op
+ * with the prefetched number */
+
+ delta = atoi(sc_token);
+
+ if (token() == MONTHS_MINUTES) {
+ /* hard job to guess what does that -5m means: -5mon or -5min? */
+ switch (prev_multiplier) {
+ case DAYS:
+ case WEEKS:
+ case MONTHS:
+ case YEARS:
+ sc_tokid = MONTHS;
+ break;
+
+ case SECONDS:
+ case MINUTES:
+ case HOURS:
+ sc_tokid = MINUTES;
+ break;
+
+ default:
+ if (delta < 6) /* it may be some other value but in the context
+ * of RRD who needs less than 6 min deltas? */
+ sc_tokid = MONTHS;
+ else
+ sc_tokid = MINUTES;
+ }
+ }
+ prev_multiplier = sc_tokid;
+ switch (sc_tokid) {
+ case YEARS:
+ ptv->tm. tm_year += (
+ op == PLUS) ? delta : -delta;
+
+ return TIME_OK;
+ case MONTHS:
+ ptv->tm. tm_mon += (
+ op == PLUS) ? delta : -delta;
+
+ return TIME_OK;
+ case WEEKS:
+ delta *= 7;
+ /* FALLTHRU */
+ case DAYS:
+ ptv->tm. tm_mday += (
+ op == PLUS) ? delta : -delta;
+
+ return TIME_OK;
+ case HOURS:
+ ptv->offset += (op == PLUS) ? delta * 60 * 60 : -delta * 60 * 60;
+ return TIME_OK;
+ case MINUTES:
+ ptv->offset += (op == PLUS) ? delta * 60 : -delta * 60;
+ return TIME_OK;
+ case SECONDS:
+ ptv->offset += (op == PLUS) ? delta : -delta;
+ return TIME_OK;
+ default: /*default unit is seconds */
+ ptv->offset += (op == PLUS) ? delta : -delta;
+ return TIME_OK;
+ }
+ panic(e("well-known time unit expected after %d", delta));
+ /* NORETURN */
+ return TIME_OK; /* to make compiler happy :) */
+} /* plus_minus */
+
+
+/*
+ * tod() computes the time of day (TIME-OF-DAY-SPEC)
+ */
+static char *tod(
+ rrd_time_value_t * ptv)
+{
+ int hour, minute = 0;
+ int tlen;
+
+ /* save token status in case we must abort */
+ int scc_sv = scc;
+ const char *sct_sv = sct;
+ int sc_tokid_sv = sc_tokid;
+
+ tlen = strlen(sc_token);
+
+ /* first pick out the time of day - we assume a HH (COLON|DOT) MM time
+ */
+ if (tlen > 2) {
+ return TIME_OK;
+ }
+
+ hour = atoi(sc_token);
+
+ token();
+ if (sc_tokid == SLASH || sc_tokid == DOT) {
+ /* guess we are looking at a date */
+ scc = scc_sv;
+ sct = sct_sv;
+ sc_tokid = sc_tokid_sv;
+ sprintf(sc_token, "%d", hour);
+ return TIME_OK;
+ }
+ if (sc_tokid == COLON) {
+ try(expect2(NUMBER,
+ "Parsing HH:MM syntax, expecting MM as number, got none"));
+ minute = atoi(sc_token);
+ if (minute > 59) {
+ panic(e("parsing HH:MM syntax, got MM = %d (>59!)", minute));
+ }
+ token();
+ }
+
+ /* check if an AM or PM specifier was given
+ */
+ if (sc_tokid == AM || sc_tokid == PM) {
+ if (hour > 12) {
+ panic(e("there cannot be more than 12 AM or PM hours"));
+ }
+ if (sc_tokid == PM) {
+ if (hour != 12) /* 12:xx PM is 12:xx, not 24:xx */
+ hour += 12;
+ } else {
+ if (hour == 12) /* 12:xx AM is 00:xx, not 12:xx */
+ hour = 0;
+ }
+ token();
+ } else if (hour > 23) {
+ /* guess it was not a time then ... */
+ scc = scc_sv;
+ sct = sct_sv;
+ sc_tokid = sc_tokid_sv;
+ sprintf(sc_token, "%d", hour);
+ return TIME_OK;
+ }
+ ptv->tm. tm_hour = hour;
+ ptv->tm. tm_min = minute;
+ ptv->tm. tm_sec = 0;
+
+ if (ptv->tm.tm_hour == 24) {
+ ptv->tm. tm_hour = 0;
+ ptv->tm. tm_mday++;
+ }
+ return TIME_OK;
+} /* tod */
+
+
+/*
+ * assign_date() assigns a date, adjusting year as appropriate
+ */
+static char *assign_date(
+ rrd_time_value_t * ptv,
+ long mday,
+ long mon,
+ long year)
+{
+ if (year > 138) {
+ if (year > 1970)
+ year -= 1900;
+ else {
+ panic(e("invalid year %d (should be either 00-99 or >1900)",
+ year));
+ }
+ } else if (year >= 0 && year < 38) {
+ year += 100; /* Allow year 2000-2037 to be specified as */
+ }
+ /* 00-37 until the problem of 2038 year will */
+ /* arise for unices with 32-bit time_t :) */
+ if (year < 70) {
+ panic(e("won't handle dates before epoch (01/01/1970), sorry"));
+ }
+
+ ptv->tm. tm_mday = mday;
+ ptv->tm. tm_mon = mon;
+ ptv->tm. tm_year = year;
+
+ return TIME_OK;
+} /* assign_date */
+
+
+/*
+ * day() picks apart DAY-SPEC-[12]
+ */
+static char *day(
+ rrd_time_value_t * ptv)
+{
+ /* using time_t seems to help portability with 64bit oses */
+ time_t mday = 0, wday, mon, year = ptv->tm.tm_year;
+ int tlen;
+
+ switch (sc_tokid) {
+ case YESTERDAY:
+ ptv->tm. tm_mday--;
+
+ /* FALLTRHU */
+ case TODAY: /* force ourselves to stay in today - no further processing */
+ token();
+ break;
+ case TOMORROW:
+ ptv->tm. tm_mday++;
+
+ token();
+ break;
+
+ case JAN:
+ case FEB:
+ case MAR:
+ case APR:
+ case MAY:
+ case JUN:
+ case JUL:
+ case AUG:
+ case SEP:
+ case OCT:
+ case NOV:
+ case DEC:
+ /* do month mday [year]
+ */
+ mon = (sc_tokid - JAN);
+ try(expect2(NUMBER, "the day of the month should follow month name"));
+ mday = atol(sc_token);
+ if (token() == NUMBER) {
+ year = atol(sc_token);
+ token();
+ } else
+ year = ptv->tm.tm_year;
+
+ try(assign_date(ptv, mday, mon, year));
+ break;
+
+ case SUN:
+ case MON:
+ case TUE:
+ case WED:
+ case THU:
+ case FRI:
+ case SAT:
+ /* do a particular day of the week
+ */
+ wday = (sc_tokid - SUN);
+ ptv->tm. tm_mday += (
+ wday - ptv->tm.tm_wday);
+
+ token();
+ break;
+ /*
+ mday = ptv->tm.tm_mday;
+ mday += (wday - ptv->tm.tm_wday);
+ ptv->tm.tm_wday = wday;
+
+ try(assign_date(ptv, mday, ptv->tm.tm_mon, ptv->tm.tm_year));
+ break;
+ */
+
+ case NUMBER:
+ /* get numeric <sec since 1970>, MM/DD/[YY]YY, or DD.MM.[YY]YY
+ */
+ tlen = strlen(sc_token);
+ mon = atol(sc_token);
+ if (mon > 10 * 365 * 24 * 60 * 60) {
+ ptv->tm = *localtime(&mon);
+
+ token();
+ break;
+ }
+
+ if (mon > 19700101 && mon < 24000101) { /*works between 1900 and 2400 */
+ char cmon[3], cmday[3], cyear[5];
+
+ strncpy(cyear, sc_token, 4);
+ cyear[4] = '\0';
+ year = atol(cyear);
+ strncpy(cmon, &(sc_token[4]), 2);
+ cmon[2] = '\0';
+ mon = atol(cmon);
+ strncpy(cmday, &(sc_token[6]), 2);
+ cmday[2] = '\0';
+ mday = atol(cmday);
+ token();
+ } else {
+ token();
+
+ if (mon <= 31 && (sc_tokid == SLASH || sc_tokid == DOT)) {
+ int sep;
+
+ sep = sc_tokid;
+ try(expect2(NUMBER, "there should be %s number after '%c'",
+ sep == DOT ? "month" : "day",
+ sep == DOT ? '.' : '/'));
+ mday = atol(sc_token);
+ if (token() == sep) {
+ try(expect2
+ (NUMBER, "there should be year number after '%c'",
+ sep == DOT ? '.' : '/'));
+ year = atol(sc_token);
+ token();
+ }
+
+ /* flip months and days for European timing
+ */
+ if (sep == DOT) {
+ long x = mday;
+
+ mday = mon;
+ mon = x;
+ }
+ }
+ }
+
+ mon--;
+ if (mon < 0 || mon > 11) {
+ panic(e("did you really mean month %d?", mon + 1));
+ }
+ if (mday < 1 || mday > 31) {
+ panic(e("I'm afraid that %d is not a valid day of the month",
+ mday));
+ }
+ try(assign_date(ptv, mday, mon, year));
+ break;
+ } /* case */
+ return TIME_OK;
+} /* month */
+
+
+/* Global functions */
+
+
+/*
+ * rrd_parsetime() is the external interface that takes tspec, parses
+ * it and puts the result in the rrd_time_value structure *ptv.
+ * It can return either absolute times (these are ensured to be
+ * correct) or relative time references that are expected to be
+ * added to some absolute time value and then normalized by
+ * mktime() The return value is either TIME_OK (aka NULL) or
+ * the pointer to the error message in the case of problems
+ */
+char *rrd_parsetime(
+ const char *tspec,
+ rrd_time_value_t * ptv)
+{
+ time_t now = time(NULL);
+ int hr = 0;
+
+ /* this MUST be initialized to zero for midnight/noon/teatime */
+
+ Specials = VariousWords; /* initialize special words context */
+
+ try(init_scanner(1, &tspec));
+
+ /* establish the default time reference */
+ ptv->type = ABSOLUTE_TIME;
+ ptv->offset = 0;
+ ptv->tm = *localtime(&now);
+ ptv->tm. tm_isdst = -1; /* mk time can figure dst by default ... */
+
+ token();
+ switch (sc_tokid) {
+ case PLUS:
+ case MINUS:
+ break; /* jump to OFFSET-SPEC part */
+
+ case START:
+ ptv->type = RELATIVE_TO_START_TIME;
+ goto KeepItRelative;
+ case END:
+ ptv->type = RELATIVE_TO_END_TIME;
+ KeepItRelative:
+ ptv->tm. tm_sec = 0;
+ ptv->tm. tm_min = 0;
+ ptv->tm. tm_hour = 0;
+ ptv->tm. tm_mday = 0;
+ ptv->tm. tm_mon = 0;
+ ptv->tm. tm_year = 0;
+
+ /* FALLTHRU */
+ case NOW:
+ {
+ int time_reference = sc_tokid;
+
+ token();
+ if (sc_tokid == PLUS || sc_tokid == MINUS)
+ break;
+ if (time_reference != NOW) {
+ panic(e("'start' or 'end' MUST be followed by +|- offset"));
+ } else if (sc_tokid != EOF) {
+ panic(e("if 'now' is followed by a token it must be +|- offset"));
+ }
+ };
+ break;
+
+ /* Only absolute time specifications below */
+ case NUMBER:
+ {
+ long hour_sv = ptv->tm.tm_hour;
+ long year_sv = ptv->tm.tm_year;
+
+ ptv->tm. tm_hour = 30;
+ ptv->tm. tm_year = 30000;
+
+ try(tod(ptv))
+ try(day(ptv))
+ if (ptv->tm.tm_hour == 30 && ptv->tm.tm_year != 30000) {
+ try(tod(ptv))
+ }
+ if (ptv->tm.tm_hour == 30) {
+ ptv->tm. tm_hour = hour_sv;
+ }
+ if (ptv->tm.tm_year == 30000) {
+ ptv->tm. tm_year = year_sv;
+ }
+ };
+ break;
+ /* fix month parsing */
+ case JAN:
+ case FEB:
+ case MAR:
+ case APR:
+ case MAY:
+ case JUN:
+ case JUL:
+ case AUG:
+ case SEP:
+ case OCT:
+ case NOV:
+ case DEC:
+ try(day(ptv));
+ if (sc_tokid != NUMBER)
+ break;
+ try(tod(ptv))
+ break;
+
+ /* evil coding for TEATIME|NOON|MIDNIGHT - we've initialized
+ * hr to zero up above, then fall into this case in such a
+ * way so we add +12 +4 hours to it for teatime, +12 hours
+ * to it for noon, and nothing at all for midnight, then
+ * set our rettime to that hour before leaping into the
+ * month scanner
+ */
+ case TEATIME:
+ hr += 4;
+ /* FALLTHRU */
+ case NOON:
+ hr += 12;
+ /* FALLTHRU */
+ case MIDNIGHT:
+ /* if (ptv->tm.tm_hour >= hr) {
+ ptv->tm.tm_mday++;
+ ptv->tm.tm_wday++;
+ } *//* shifting does not makes sense here ... noon is noon */
+ ptv->tm. tm_hour = hr;
+ ptv->tm. tm_min = 0;
+ ptv->tm. tm_sec = 0;
+
+ token();
+ try(day(ptv));
+ break;
+ default:
+ panic(e("unparsable time: %s%s", sc_token, sct));
+ break;
+ } /* ugly case statement */
+
+ /*
+ * the OFFSET-SPEC part
+ *
+ * (NOTE, the sc_tokid was prefetched for us by the previous code)
+ */
+ if (sc_tokid == PLUS || sc_tokid == MINUS) {
+ Specials = TimeMultipliers; /* switch special words context */
+ while (sc_tokid == PLUS || sc_tokid == MINUS || sc_tokid == NUMBER) {
+ if (sc_tokid == NUMBER) {
+ try(plus_minus(ptv, PREVIOUS_OP));
+ } else
+ try(plus_minus(ptv, sc_tokid));
+ token(); /* We will get EOF eventually but that's OK, since
+ token() will return us as many EOFs as needed */
+ }
+ }
+
+ /* now we should be at EOF */
+ if (sc_tokid != EOF) {
+ panic(e("unparsable trailing text: '...%s%s'", sc_token, sct));
+ }
+
+ if (ptv->type == ABSOLUTE_TIME)
+ if (mktime(&ptv->tm) == -1) { /* normalize & check */
+ /* can happen for "nonexistent" times, e.g. around 3am */
+ /* when winter -> summer time correction eats a hour */
+ panic(e("the specified time is incorrect (out of range?)"));
+ }
+ EnsureMemFree();
+ return TIME_OK;
+} /* rrd_parsetime */
+
+
+int rrd_proc_start_end(
+ rrd_time_value_t * start_tv,
+ rrd_time_value_t * end_tv,
+ time_t *start,
+ time_t *end)
+{
+ if (start_tv->type == RELATIVE_TO_END_TIME && /* same as the line above */
+ end_tv->type == RELATIVE_TO_START_TIME) {
+ rrd_set_error("the start and end times cannot be specified "
+ "relative to each other");
+ return -1;
+ }
+
+ if (start_tv->type == RELATIVE_TO_START_TIME) {
+ rrd_set_error
+ ("the start time cannot be specified relative to itself");
+ return -1;
+ }
+
+ if (end_tv->type == RELATIVE_TO_END_TIME) {
+ rrd_set_error("the end time cannot be specified relative to itself");
+ return -1;
+ }
+
+ if (start_tv->type == RELATIVE_TO_END_TIME) {
+ struct tm tmtmp;
+
+ *end = mktime(&(end_tv->tm)) + end_tv->offset;
+ tmtmp = *localtime(end); /* reinit end including offset */
+ tmtmp.tm_mday += start_tv->tm.tm_mday;
+ tmtmp.tm_mon += start_tv->tm.tm_mon;
+ tmtmp.tm_year += start_tv->tm.tm_year;
+
+ *start = mktime(&tmtmp) + start_tv->offset;
+ } else {
+ *start = mktime(&(start_tv->tm)) + start_tv->offset;
+ }
+ if (end_tv->type == RELATIVE_TO_START_TIME) {
+ struct tm tmtmp;
+
+ *start = mktime(&(start_tv->tm)) + start_tv->offset;
+ tmtmp = *localtime(start);
+ tmtmp.tm_mday += end_tv->tm.tm_mday;
+ tmtmp.tm_mon += end_tv->tm.tm_mon;
+ tmtmp.tm_year += end_tv->tm.tm_year;
+
+ *end = mktime(&tmtmp) + end_tv->offset;
+ } else {
+ *end = mktime(&(end_tv->tm)) + end_tv->offset;
+ }
+ return 0;
+} /* rrd_proc_start_end */
diff --git a/program/src/rrd_parsetime.h b/program/src/rrd_parsetime.h
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef __PARSETIME_H__
+#define __PARSETIME_H__
+
+#include <stdio.h>
+
+#include "rrd.h"
+
+#endif
diff --git a/program/src/rrd_resize.c b/program/src/rrd_resize.c
--- /dev/null
+++ b/program/src/rrd_resize.c
@@ -0,0 +1,255 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ *****************************************************************************
+ * rrd_resize.c Alters size of an RRA
+ *****************************************************************************
+ * Initial version by Alex van den Bogaerdt
+ *****************************************************************************/
+
+#include "rrd_tool.h"
+
+int rrd_resize(
+ int argc,
+ char **argv)
+{
+ char *infilename, outfilename[11] = "resize.rrd";
+ rrd_t rrdold, rrdnew;
+ rrd_value_t buffer;
+ int version;
+ unsigned long l, rra;
+ long modify;
+ unsigned long target_rra;
+ int grow = 0, shrink = 0;
+ char *endptr;
+ rrd_file_t *rrd_file, *rrd_out_file;
+
+ infilename = argv[1];
+ if (!strcmp(infilename, "resize.rrd")) {
+ rrd_set_error("resize.rrd is a reserved name");
+ return (-1);
+ }
+ if (argc != 5) {
+ rrd_set_error("wrong number of parameters");
+ return (-1);
+ }
+
+ target_rra = strtol(argv[2], &endptr, 0);
+
+ if (!strcmp(argv[3], "GROW"))
+ grow = 1;
+ else if (!strcmp(argv[3], "SHRINK"))
+ shrink = 1;
+ else {
+ rrd_set_error("I can only GROW or SHRINK");
+ return (-1);
+ }
+
+ modify = strtol(argv[4], &endptr, 0);
+
+ if ((modify < 1)) {
+ rrd_set_error("Please grow or shrink with at least 1 row");
+ return (-1);
+ }
+
+ if (shrink)
+ modify = -modify;
+
+
+ rrd_file = rrd_open(infilename, &rrdold, RRD_READWRITE | RRD_COPY);
+ if (rrd_file == NULL) {
+ rrd_free(&rrdold);
+ return (-1);
+ }
+ if (rrd_lock(rrd_file) != 0) {
+ rrd_set_error("could not lock original RRD");
+ rrd_free(&rrdold);
+ rrd_close(rrd_file);
+ return (-1);
+ }
+
+ if (target_rra >= rrdold.stat_head->rra_cnt) {
+ rrd_set_error("no such RRA in this RRD");
+ rrd_free(&rrdold);
+ rrd_close(rrd_file);
+ return (-1);
+ }
+
+ if (modify < 0)
+ if ((long) rrdold.rra_def[target_rra].row_cnt <= -modify) {
+ rrd_set_error("This RRA is not that big");
+ rrd_free(&rrdold);
+ rrd_close(rrd_file);
+ return (-1);
+ }
+ /* the size of the new file */
+ /* yes we are abusing the float cookie for this, aargh */
+ if ((rrdnew.stat_head = calloc(1, sizeof(stat_head_t))) == NULL) {
+ rrd_set_error("allocating stat_head for new RRD");
+ rrd_free(&rrdold);
+ rrd_close(rrd_file);
+ return (-1);
+ }
+ rrdnew.stat_head->float_cookie = rrd_file->file_len +
+ (rrdold.stat_head->ds_cnt * sizeof(rrd_value_t) * modify);
+ rrd_out_file = rrd_open(outfilename, &rrdnew, RRD_READWRITE | RRD_CREAT);
+ if (rrd_out_file == NULL) {
+ rrd_set_error("Can't create '%s': %s", outfilename,
+ rrd_strerror(errno));
+ rrd_free(&rrdnew);
+ return (-1);
+ }
+ if (rrd_lock(rrd_out_file) != 0) {
+ rrd_set_error("could not lock new RRD");
+ rrd_free(&rrdold);
+ rrd_close(rrd_file);
+ rrd_close(rrd_out_file);
+ return (-1);
+ }
+/*XXX: do one write for those parts of header that are unchanged */
+ rrdnew.stat_head = rrdold.stat_head;
+ rrdnew.ds_def = rrdold.ds_def;
+ rrdnew.rra_def = rrdold.rra_def;
+ rrdnew.live_head = rrdold.live_head;
+ rrdnew.pdp_prep = rrdold.pdp_prep;
+ rrdnew.cdp_prep = rrdold.cdp_prep;
+ rrdnew.rra_ptr = rrdold.rra_ptr;
+
+ version = atoi(rrdold.stat_head->version);
+ switch (version) {
+ case 3:
+ break;
+ case 1:
+ rrdold.stat_head->version[3] = '3';
+ break;
+ default:
+ rrd_set_error("Do not know how to handle RRD version %s",
+ rrdold.stat_head->version);
+ rrd_close(rrd_file);
+ rrd_free(&rrdold);
+ return (-1);
+ break;
+ }
+
+/* XXX: Error checking? */
+ rrd_write(rrd_out_file, rrdnew.stat_head, sizeof(stat_head_t) * 1);
+ rrd_write(rrd_out_file, rrdnew.ds_def,
+ sizeof(ds_def_t) * rrdnew.stat_head->ds_cnt);
+ rrd_write(rrd_out_file, rrdnew.rra_def,
+ sizeof(rra_def_t) * rrdnew.stat_head->rra_cnt);
+ rrd_write(rrd_out_file, rrdnew.live_head, sizeof(live_head_t) * 1);
+ rrd_write(rrd_out_file, rrdnew.pdp_prep,
+ sizeof(pdp_prep_t) * rrdnew.stat_head->ds_cnt);
+ rrd_write(rrd_out_file, rrdnew.cdp_prep,
+ sizeof(cdp_prep_t) * rrdnew.stat_head->ds_cnt *
+ rrdnew.stat_head->rra_cnt);
+ rrd_write(rrd_out_file, rrdnew.rra_ptr,
+ sizeof(rra_ptr_t) * rrdnew.stat_head->rra_cnt);
+
+ /* Move the CDPs from the old to the new database.
+ ** This can be made (much) faster but isn't worth the effort. Clarity
+ ** is much more important.
+ */
+
+ /* Move data in unmodified RRAs
+ */
+ l = 0;
+ for (rra = 0; rra < target_rra; rra++) {
+ l += rrdnew.stat_head->ds_cnt * rrdnew.rra_def[rra].row_cnt;
+ }
+ while (l > 0) {
+ rrd_read(rrd_file, &buffer, sizeof(rrd_value_t) * 1);
+ rrd_write(rrd_out_file, &buffer, sizeof(rrd_value_t) * 1);
+ l--;
+ }
+ /* Move data in this RRA, either removing or adding some rows
+ */
+ if (modify > 0) {
+ /* Adding extra rows; insert unknown values just after the
+ ** current row number.
+ */
+ l = rrdnew.stat_head->ds_cnt *
+ (rrdnew.rra_ptr[target_rra].cur_row + 1);
+ while (l > 0) {
+ rrd_read(rrd_file, &buffer, sizeof(rrd_value_t) * 1);
+ rrd_write(rrd_out_file, &buffer, sizeof(rrd_value_t) * 1);
+ l--;
+ }
+#ifndef HAVE_MMAP
+ buffer = DNAN;
+ l = rrdnew.stat_head->ds_cnt * modify;
+ while (l > 0) {
+ rrd_write(rrd_out_file, &buffer, sizeof(rrd_value_t) * 1);
+ l--;
+ }
+#else
+ /* for the mmap case, we did already fill the whole new file with DNAN
+ * before we copied the old values, so nothing to do here. */
+#endif
+ } else {
+ /* Removing rows. Normally this would be just after the cursor
+ ** however this may also mean that we wrap to the beginning of
+ ** the array.
+ */
+ signed long int remove_end = 0;
+
+ remove_end =
+ (rrdnew.rra_ptr[target_rra].cur_row -
+ modify) % rrdnew.rra_def[target_rra].row_cnt;
+ if (remove_end <=
+ (signed long int) rrdnew.rra_ptr[target_rra].cur_row) {
+ while (remove_end >= 0) {
+ rrd_seek(rrd_file,
+ sizeof(rrd_value_t) * rrdnew.stat_head->ds_cnt,
+ SEEK_CUR);
+ rrdnew.rra_ptr[target_rra].cur_row--;
+ rrdnew.rra_def[target_rra].row_cnt--;
+ remove_end--;
+ modify++;
+ }
+ remove_end = rrdnew.rra_def[target_rra].row_cnt - 1;
+ }
+ for (l = 0; l <= rrdnew.rra_ptr[target_rra].cur_row; l++) {
+ unsigned int tmp;
+
+ for (tmp = 0; tmp < rrdnew.stat_head->ds_cnt; tmp++) {
+ rrd_read(rrd_file, &buffer, sizeof(rrd_value_t) * 1);
+ rrd_write(rrd_out_file, &buffer, sizeof(rrd_value_t) * 1);
+ }
+ }
+ while (modify < 0) {
+ rrd_seek(rrd_file,
+ sizeof(rrd_value_t) * rrdnew.stat_head->ds_cnt,
+ SEEK_CUR);
+ rrdnew.rra_def[target_rra].row_cnt--;
+ modify++;
+ }
+ }
+ /* Move the rest of the CDPs
+ */
+ while (1) {
+ if (rrd_read(rrd_file, &buffer, sizeof(rrd_value_t) * 1) <= 0)
+ break;
+ rrd_write(rrd_out_file, &buffer, sizeof(rrd_value_t) * 1);
+ }
+ rrdnew.rra_def[target_rra].row_cnt += modify;
+ rrd_seek(rrd_out_file,
+ sizeof(stat_head_t) +
+ sizeof(ds_def_t) * rrdnew.stat_head->ds_cnt, SEEK_SET);
+ rrd_write(rrd_out_file, rrdnew.rra_def,
+ sizeof(rra_def_t) * rrdnew.stat_head->rra_cnt);
+ rrd_seek(rrd_out_file, sizeof(live_head_t), SEEK_CUR);
+ rrd_seek(rrd_out_file, sizeof(pdp_prep_t) * rrdnew.stat_head->ds_cnt,
+ SEEK_CUR);
+ rrd_seek(rrd_out_file,
+ sizeof(cdp_prep_t) * rrdnew.stat_head->ds_cnt *
+ rrdnew.stat_head->rra_cnt, SEEK_CUR);
+ rrd_write(rrd_out_file, rrdnew.rra_ptr,
+ sizeof(rra_ptr_t) * rrdnew.stat_head->rra_cnt);
+
+ rrd_free(&rrdold);
+ rrd_close(rrd_file);
+
+ rrd_close(rrd_out_file);
+
+ return (0);
+}
diff --git a/program/src/rrd_restore.c b/program/src/rrd_restore.c
--- /dev/null
@@ -0,0 +1,1101 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ * This file: Copyright 2008 Florian octo Forster
+ * Distributed under the GPL
+ *****************************************************************************
+ * rrd_restore.c Contains logic to parse XML input and create an RRD file
+ *****************************************************************************
+ * $Id$
+ *************************************************************************** */
+
+/*
+ * This program is free software; 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 (t 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 <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <fcntl.h>
+#if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
+# include <io.h>
+# define open _open
+# define close _close
+#endif
+#include <libxml/parser.h>
+#include "rrd_tool.h"
+#include "rrd_rpncalc.h"
+#define ARRAY_LENGTH(a) (sizeof (a) / sizeof ((a)[0]))
+static int opt_range_check = 0;
+static int opt_force_overwrite = 0;
+
+/*
+ * Auxiliary functions
+ */
+static int get_string_from_node(
+ xmlDoc * doc,
+ xmlNode * node,
+ char *buffer,
+ size_t buffer_size)
+{
+ xmlChar *temp0;
+ char *begin_ptr;
+ char *end_ptr;
+
+ temp0 = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+ if (temp0 == NULL) {
+ rrd_set_error("get_string_from_node: xmlNodeListGetString failed.");
+ return (-1);
+ }
+
+ begin_ptr = (char *) temp0;
+ while ((begin_ptr[0] != 0) && (isspace(begin_ptr[0])))
+ begin_ptr++;
+
+ if (begin_ptr[0] == 0) {
+ xmlFree(temp0);
+ buffer[0] = 0;
+ return (0);
+ }
+
+ end_ptr = begin_ptr;
+ while ((end_ptr[0] != 0) && (!isspace(end_ptr[0])))
+ end_ptr++;
+ end_ptr[0] = 0;
+
+ strncpy(buffer, begin_ptr, buffer_size);
+ buffer[buffer_size - 1] = 0;
+
+ xmlFree(temp0);
+
+ return (0);
+} /* int get_string_from_node */
+
+static int get_int_from_node(
+ xmlDoc * doc,
+ xmlNode * node,
+ int *value)
+{
+ int temp;
+ char *str_ptr;
+ char *end_ptr;
+
+ str_ptr = (char *) xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+ if (str_ptr == NULL) {
+ rrd_set_error("get_int_from_node: xmlNodeListGetString failed.");
+ return (-1);
+ }
+
+ end_ptr = NULL;
+ temp = strtol(str_ptr, &end_ptr, 0);
+ xmlFree(str_ptr);
+
+ if (str_ptr == end_ptr) {
+ rrd_set_error("get_int_from_node: Cannot parse buffer as int: %s",
+ str_ptr);
+ return (-1);
+ }
+
+ *value = temp;
+
+ return (0);
+} /* int get_int_from_node */
+
+static int get_double_from_node(
+ xmlDoc * doc,
+ xmlNode * node,
+ double *value)
+{
+ double temp;
+ char *str_ptr;
+ char *end_ptr;
+
+ str_ptr = (char *) xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+ if (str_ptr == NULL) {
+ rrd_set_error("get_double_from_node: xmlNodeListGetString failed.");
+ return (-1);
+ }
+
+ end_ptr = NULL;
+ temp = strtod(str_ptr, &end_ptr);
+ xmlFree(str_ptr);
+
+ if (str_ptr == end_ptr) {
+ rrd_set_error
+ ("get_double_from_node: Cannot parse buffer as double: %s",
+ str_ptr);
+ return (-1);
+ }
+
+ *value = temp;
+
+ return (0);
+} /* int get_double_from_node */
+
+static int value_check_range(
+ rrd_value_t *rrd_value,
+ const ds_def_t *ds_def)
+{
+ double min;
+ double max;
+
+ if (opt_range_check == 0)
+ return (0);
+
+ min = ds_def->par[DS_min_val].u_val;
+ max = ds_def->par[DS_max_val].u_val;
+
+ if (((!isnan(min)) && (*rrd_value < min))
+ || ((!isnan(max)) && (*rrd_value > max)))
+ *rrd_value = DNAN;
+
+ return (0);
+} /* int value_check_range */
+
+/*
+ * Parse the <database> block within an RRA definition
+ */
+static int parse_tag_rra_database_row(
+ xmlDoc * doc,
+ xmlNode * node,
+ rrd_t *rrd,
+ rrd_value_t *rrd_value)
+{
+ unsigned int values_count = 0;
+ xmlNode *child;
+ int status;
+
+ status = 0;
+ for (child = node->xmlChildrenNode; child != NULL; child = child->next) {
+ if ((xmlStrcmp(child->name, (const xmlChar *) "comment") == 0)
+ || (xmlStrcmp(child->name, (const xmlChar *) "text") == 0))
+ /* ignore */ ;
+ else if (xmlStrcmp(child->name, (const xmlChar *) "v") == 0) {
+ if (values_count < rrd->stat_head->ds_cnt) {
+ status =
+ get_double_from_node(doc, child,
+ rrd_value + values_count);
+ if (status == 0)
+ value_check_range(rrd_value + values_count,
+ rrd->ds_def + values_count);
+ }
+
+ values_count++;
+ } else {
+ rrd_set_error("parse_tag_rra_database_row: Unknown tag: %s",
+ child->name);
+ status = -1;
+ }
+
+ if (status != 0)
+ break;
+ } /* for (child = node->xmlChildrenNode) */
+
+ if (values_count != rrd->stat_head->ds_cnt) {
+ rrd_set_error("parse_tag_rra_database_row: Row has %u values "
+ "and RRD has %lu data sources.",
+ values_count, rrd->stat_head->ds_cnt);
+ status = -1;
+ }
+
+ return (status);
+} /* int parse_tag_rra_database_row */
+
+static int parse_tag_rra_database(
+ xmlDoc * doc,
+ xmlNode * node,
+ rrd_t *rrd)
+{
+ rra_def_t *cur_rra_def;
+ unsigned int total_row_cnt;
+ xmlNode *child;
+ int status;
+ int i;
+
+ total_row_cnt = 0;
+ for (i = 0; i < (((int) rrd->stat_head->rra_cnt) - 1); i++)
+ total_row_cnt += rrd->rra_def[i].row_cnt;
+
+ cur_rra_def = rrd->rra_def + i;
+
+ status = 0;
+ for (child = node->xmlChildrenNode; child != NULL; child = child->next) {
+ if ((xmlStrcmp(child->name, (const xmlChar *) "comment") == 0)
+ || (xmlStrcmp(child->name, (const xmlChar *) "text") == 0))
+ /* ignore */ ;
+ else if (xmlStrcmp(child->name, (const xmlChar *) "row") == 0) {
+ rrd_value_t *temp;
+ rrd_value_t *cur_rrd_value;
+ unsigned int total_values_count = rrd->stat_head->ds_cnt
+ * (total_row_cnt + 1);
+
+ /* Allocate space for the new values.. */
+ temp = (rrd_value_t *) realloc(rrd->rrd_value,
+ sizeof(rrd_value_t) *
+ total_values_count);
+ if (temp == NULL) {
+ rrd_set_error("parse_tag_rra_database: realloc failed.");
+ status = -1;
+ break;
+ }
+ rrd->rrd_value = temp;
+ cur_rrd_value = rrd->rrd_value
+ + (rrd->stat_head->ds_cnt * total_row_cnt);
+ memset(cur_rrd_value, '\0',
+ sizeof(rrd_value_t) * rrd->stat_head->ds_cnt);
+ total_row_cnt++;
+ cur_rra_def->row_cnt++;
+
+ status =
+ parse_tag_rra_database_row(doc, child, rrd, cur_rrd_value);
+ } /* if (xmlStrcmp (child->name, (const xmlChar *) "row") == 0) */
+ else {
+ rrd_set_error("parse_tag_rra_database: Unknown tag: %s",
+ child->name);
+ status = -1;
+ }
+
+ if (status != 0)
+ break;
+ } /* for (child = node->xmlChildrenNode) */
+
+ return (status);
+} /* int parse_tag_rra_database */
+
+/*
+ * Parse the <cdp_prep> block within an RRA definition
+ */
+static int parse_tag_rra_cdp_prep_ds_history(
+ xmlDoc * doc,
+ xmlNode * node,
+ cdp_prep_t *cdp_prep)
+{
+ /* Make `history_buffer' the same size as the scratch area, plus the
+ * terminating NULL byte. */
+ char history_buffer[sizeof(((cdp_prep_t *)0)->scratch) + 1];
+ char *history_ptr;
+ int status;
+ int i;
+
+ status = get_string_from_node(doc, node,
+ history_buffer, sizeof(history_buffer));
+ if (status != 0)
+ return (-1);
+
+ history_ptr = (char *) (&cdp_prep->scratch[0]);
+ for (i = 0; history_buffer[i] != '\0'; i++)
+ history_ptr[i] = (history_buffer[i] == '1') ? 1 : 0;
+
+ return (0);
+} /* int parse_tag_rra_cdp_prep_ds_history */
+
+static int parse_tag_rra_cdp_prep_ds(
+ xmlDoc * doc,
+ xmlNode * node,
+ rrd_t *rrd,
+ cdp_prep_t *cdp_prep)
+{
+ xmlNode *child;
+ int status;
+
+ memset(cdp_prep, '\0', sizeof(cdp_prep_t));
+
+ status = 0;
+ for (child = node->xmlChildrenNode; child != NULL; child = child->next) {
+ if (atoi(rrd->stat_head->version) == 1) {
+ cdp_prep->scratch[CDP_primary_val].u_val = 0.0;
+ cdp_prep->scratch[CDP_secondary_val].u_val = 0.0;
+ }
+ if ((xmlStrcmp(child->name, (const xmlChar *) "comment") == 0)
+ || (xmlStrcmp(child->name, (const xmlChar *) "text") == 0))
+ /* ignore */ ;
+ else if (xmlStrcmp(child->name, (const xmlChar *) "primary_value") ==
+ 0)
+ status =
+ get_double_from_node(doc, child,
+ &cdp_prep->scratch[CDP_primary_val].
+ u_val);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "secondary_value")
+ == 0)
+ status =
+ get_double_from_node(doc, child,
+ &cdp_prep->scratch[CDP_secondary_val].
+ u_val);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "intercept") == 0)
+ status = get_double_from_node(doc, child,
+ &cdp_prep->
+ scratch[CDP_hw_intercept].u_val);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "last_intercept") ==
+ 0)
+ status =
+ get_double_from_node(doc, child,
+ &cdp_prep->
+ scratch[CDP_hw_last_intercept].u_val);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "slope") == 0)
+ status = get_double_from_node(doc, child,
+ &cdp_prep->scratch[CDP_hw_slope].
+ u_val);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "last_slope") == 0)
+ status = get_double_from_node(doc, child,
+ &cdp_prep->
+ scratch[CDP_hw_last_slope].u_val);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "nan_count") == 0)
+ status = get_int_from_node(doc, child,
+ (int *) &cdp_prep->
+ scratch[CDP_null_count].u_cnt);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "last_nan_count") ==
+ 0)
+ status =
+ get_int_from_node(doc, child,
+ (int *) &cdp_prep->
+ scratch[CDP_last_null_count].u_cnt);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "seasonal") == 0)
+ status = get_double_from_node(doc, child,
+ &cdp_prep->scratch[CDP_hw_seasonal].
+ u_val);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "last_seasonal") ==
+ 0)
+ status =
+ get_double_from_node(doc, child,
+ &cdp_prep->scratch[CDP_hw_last_seasonal].
+ u_val);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "init_flag") == 0)
+ status = get_int_from_node(doc, child,
+ (int *) &cdp_prep->
+ scratch[CDP_init_seasonal].u_cnt);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "history") == 0)
+ status = parse_tag_rra_cdp_prep_ds_history(doc, child, cdp_prep);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "value") == 0)
+ status = get_double_from_node(doc, child,
+ &cdp_prep->scratch[CDP_val].u_val);
+ else if (xmlStrcmp(child->name,
+ (const xmlChar *) "unknown_datapoints") == 0)
+ status = get_int_from_node(doc, child,
+ (int *) &cdp_prep->
+ scratch[CDP_unkn_pdp_cnt].u_cnt);
+ else {
+ rrd_set_error("parse_tag_rra_cdp_prep: Unknown tag: %s",
+ child->name);
+ status = -1;
+ }
+
+ if (status != 0)
+ break;
+ }
+
+ return (status);
+} /* int parse_tag_rra_cdp_prep_ds */
+
+static int parse_tag_rra_cdp_prep(
+ xmlDoc * doc,
+ xmlNode * node,
+ rrd_t *rrd,
+ cdp_prep_t *cdp_prep)
+{
+ xmlNode *child;
+ int status;
+
+ unsigned int ds_count = 0;
+
+ status = 0;
+ for (child = node->xmlChildrenNode; child != NULL; child = child->next) {
+ if ((xmlStrcmp(child->name, (const xmlChar *) "comment") == 0)
+ || (xmlStrcmp(child->name, (const xmlChar *) "text") == 0))
+ /* ignore */ ;
+ else if (xmlStrcmp(child->name, (const xmlChar *) "ds") == 0) {
+ if (ds_count >= rrd->stat_head->ds_cnt)
+ status = -1;
+ else {
+ status = parse_tag_rra_cdp_prep_ds(doc, child, rrd,
+ cdp_prep + ds_count);
+ ds_count++;
+ }
+ } else {
+ rrd_set_error("parse_tag_rra_cdp_prep: Unknown tag: %s",
+ child->name);
+ status = -1;
+ }
+
+ if (status != 0)
+ break;
+ }
+
+ if (ds_count != rrd->stat_head->ds_cnt) {
+ rrd_set_error("parse_tag_rra_cdp_prep: There are %i data sources in "
+ "the RRD file, but %i in this cdp_prep block!",
+ (int) rrd->stat_head->ds_cnt, ds_count);
+ status = -1;
+ }
+
+ return (status);
+} /* int parse_tag_rra_cdp_prep */
+
+/*
+ * Parse the <params> block within an RRA definition
+ */
+static int parse_tag_rra_params(
+ xmlDoc * doc,
+ xmlNode * node,
+ rra_def_t *rra_def)
+{
+ xmlNode *child;
+ int status;
+
+ status = 0;
+ for (child = node->xmlChildrenNode; child != NULL; child = child->next) {
+ if ((xmlStrcmp(child->name, (const xmlChar *) "comment") == 0)
+ || (xmlStrcmp(child->name, (const xmlChar *) "text") == 0))
+ /* ignore */ ;
+ /*
+ * Parameters for CF_HWPREDICT
+ */
+ else if (xmlStrcmp(child->name, (const xmlChar *) "hw_alpha") == 0)
+ status = get_double_from_node(doc, child,
+ &rra_def->par[RRA_hw_alpha].u_val);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "hw_beta") == 0)
+ status = get_double_from_node(doc, child,
+ &rra_def->par[RRA_hw_beta].u_val);
+ else if (xmlStrcmp(child->name,
+ (const xmlChar *) "dependent_rra_idx") == 0)
+ status = get_int_from_node(doc, child,
+ (int *) &rra_def->
+ par[RRA_dependent_rra_idx].u_cnt);
+ /*
+ * Parameters for CF_SEASONAL and CF_DEVSEASONAL
+ */
+ else if (xmlStrcmp(child->name, (const xmlChar *) "seasonal_gamma") ==
+ 0)
+ status =
+ get_double_from_node(doc, child,
+ &rra_def->par[RRA_seasonal_gamma].u_val);
+ else if (xmlStrcmp
+ (child->name, (const xmlChar *) "seasonal_smooth_idx") == 0)
+ status =
+ get_int_from_node(doc, child,
+ (int *) &rra_def->
+ par[RRA_seasonal_smooth_idx].u_cnt);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "smoothing_window")
+ == 0)
+ status =
+ get_double_from_node(doc, child,
+ &rra_def->
+ par[RRA_seasonal_smoothing_window].
+ u_val);
+ /* else if (dependent_rra_idx) ...; */
+ /*
+ * Parameters for CF_FAILURES
+ */
+ else if (xmlStrcmp(child->name, (const xmlChar *) "delta_pos") == 0)
+ status = get_double_from_node(doc, child,
+ &rra_def->par[RRA_delta_pos].u_val);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "delta_neg") == 0)
+ status = get_double_from_node(doc, child,
+ &rra_def->par[RRA_delta_neg].u_val);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "window_len") == 0)
+ status = get_int_from_node(doc, child,
+ (int *) &rra_def->par[RRA_window_len].
+ u_cnt);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "failure_threshold")
+ == 0)
+ status =
+ get_int_from_node(doc, child,
+ (int *) &rra_def->
+ par[RRA_failure_threshold].u_cnt);
+ /*
+ * Parameters for CF_AVERAGE, CF_MAXIMUM, CF_MINIMUM, and CF_LAST
+ */
+ else if (xmlStrcmp(child->name, (const xmlChar *) "xff") == 0)
+ status = get_double_from_node(doc, child,
+ &rra_def->par[RRA_cdp_xff_val].
+ u_val);
+ /*
+ * Compatibility code for 1.0.49
+ */
+ else if (xmlStrcmp(child->name, (const xmlChar *) "value") == 0) { /* {{{ */
+ unsigned int i = 0;
+
+ while (42) {
+ if (i >= ARRAY_LENGTH(rra_def->par)) {
+ status = -1;
+ break;
+ }
+
+ if ((i == RRA_dependent_rra_idx)
+ || (i == RRA_seasonal_smooth_idx)
+ || (i == RRA_failure_threshold))
+ status = get_int_from_node(doc, child,
+ (int *) &rra_def->par[i].
+ u_cnt);
+ else
+ status = get_double_from_node(doc, child,
+ &rra_def->par[i].u_val);
+
+ if (status != 0)
+ break;
+
+ /* When this loops exits (sucessfully) `child' points to the last
+ * `value' tag in the list. */
+ if ((child->next == NULL)
+ || (xmlStrcmp(child->name, (const xmlChar *) "value") !=
+ 0))
+ break;
+
+ child = child->next;
+ i++;
+ }
+ } /* }}} */
+ else {
+ rrd_set_error("parse_tag_rra_params: Unknown tag: %s",
+ child->name);
+ status = -1;
+ }
+
+ if (status != 0)
+ break;
+ }
+
+ return (status);
+} /* int parse_tag_rra_params */
+
+/*
+ * Parse an RRA definition
+ */
+static int parse_tag_rra_cf(
+ xmlDoc * doc,
+ xmlNode * node,
+ rra_def_t *rra_def)
+{
+ int status;
+
+ status = get_string_from_node(doc, node,
+ rra_def->cf_nam, sizeof(rra_def->cf_nam));
+ if (status != 0)
+ return (-1);
+
+ status = cf_conv(rra_def->cf_nam);
+ if (status == -1) {
+ rrd_set_error("parse_tag_rra_cf: Unknown consolidation function: %s",
+ rra_def->cf_nam);
+ return (-1);
+ }
+
+ return (0);
+} /* int parse_tag_rra_cf */
+
+static int parse_tag_rra(
+ xmlDoc * doc,
+ xmlNode * node,
+ rrd_t *rrd)
+{
+ xmlNode *child;
+ int status;
+
+ rra_def_t *cur_rra_def;
+ cdp_prep_t *cur_cdp_prep;
+ rra_ptr_t *cur_rra_ptr;
+
+ /* Allocate more rra_def space for this RRA */
+ { /* {{{ */
+ rra_def_t *temp;
+
+ temp = (rra_def_t *) realloc(rrd->rra_def,
+ sizeof(rra_def_t) *
+ (rrd->stat_head->rra_cnt + 1));
+ if (temp == NULL) {
+ rrd_set_error("parse_tag_rra: realloc failed.");
+ return (-1);
+ }
+ rrd->rra_def = temp;
+ cur_rra_def = rrd->rra_def + rrd->stat_head->rra_cnt;
+ memset(cur_rra_def, '\0', sizeof(rra_def_t));
+ } /* }}} */
+
+ /* allocate cdp_prep_t */
+ { /* {{{ */
+ cdp_prep_t *temp;
+
+ temp = (cdp_prep_t *) realloc(rrd->cdp_prep, sizeof(cdp_prep_t)
+ * rrd->stat_head->ds_cnt
+ * (rrd->stat_head->rra_cnt + 1));
+ if (temp == NULL) {
+ rrd_set_error("parse_tag_rra: realloc failed.");
+ return (-1);
+ }
+ rrd->cdp_prep = temp;
+ cur_cdp_prep = rrd->cdp_prep
+ + (rrd->stat_head->ds_cnt * rrd->stat_head->rra_cnt);
+ memset(cur_cdp_prep, '\0',
+ sizeof(cdp_prep_t) * rrd->stat_head->ds_cnt);
+ } /* }}} */
+
+ /* allocate rra_ptr_t */
+ { /* {{{ */
+ rra_ptr_t *temp;
+
+ temp = (rra_ptr_t *) realloc(rrd->rra_ptr,
+ sizeof(rra_ptr_t) *
+ (rrd->stat_head->rra_cnt + 1));
+ if (temp == NULL) {
+ rrd_set_error("parse_tag_rra: realloc failed.");
+ return (-1);
+ }
+ rrd->rra_ptr = temp;
+ cur_rra_ptr = rrd->rra_ptr + rrd->stat_head->rra_cnt;
+ memset(cur_rra_ptr, '\0', sizeof(rra_ptr_t));
+ } /* }}} */
+
+ /* All space successfully allocated, increment number of RRAs. */
+ rrd->stat_head->rra_cnt++;
+
+ status = 0;
+ for (child = node->xmlChildrenNode; child != NULL; child = child->next) {
+ if ((xmlStrcmp(child->name, (const xmlChar *) "comment") == 0)
+ || (xmlStrcmp(child->name, (const xmlChar *) "text") == 0))
+ /* ignore */ ;
+ else if (xmlStrcmp(child->name, (const xmlChar *) "cf") == 0)
+ status = parse_tag_rra_cf(doc, child, cur_rra_def);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "pdp_per_row") == 0)
+ status = get_int_from_node(doc, child,
+ (int *) &cur_rra_def->pdp_cnt);
+ else if (atoi(rrd->stat_head->version) == 1
+ && xmlStrcmp(child->name, (const xmlChar *) "xff") == 0)
+ status = get_double_from_node(doc, child,
+ (double *) &cur_rra_def->
+ par[RRA_cdp_xff_val].u_val);
+ else if (atoi(rrd->stat_head->version) >= 2
+ && xmlStrcmp(child->name, (const xmlChar *) "params") == 0)
+ status = parse_tag_rra_params(doc, child, cur_rra_def);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "cdp_prep") == 0)
+ status = parse_tag_rra_cdp_prep(doc, child, rrd, cur_cdp_prep);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "database") == 0)
+ status = parse_tag_rra_database(doc, child, rrd);
+ else {
+ rrd_set_error("parse_tag_rra: Unknown tag: %s", child->name);
+ status = -1;
+ }
+
+ if (status != 0)
+ break;
+ }
+
+ /* Set the RRA pointer to a random location */
+ cur_rra_ptr->cur_row = random() % cur_rra_def->row_cnt;
+
+ return (status);
+} /* int parse_tag_rra */
+
+/*
+ * Parse a DS definition
+ */
+static int parse_tag_ds_cdef(
+ xmlDoc * doc,
+ xmlNode * node,
+ rrd_t *rrd)
+{
+ char buffer[1024];
+ int status;
+
+ status = get_string_from_node(doc, node, buffer, sizeof(buffer));
+ if (status != 0)
+ return (-1);
+
+ /* We're always working on the last DS that has been added to the structure
+ * when we get here */
+ parseCDEF_DS(buffer, rrd, rrd->stat_head->ds_cnt - 1);
+
+ return (0);
+} /* int parse_tag_ds_cdef */
+
+static int parse_tag_ds_type(
+ xmlDoc * doc,
+ xmlNode * node,
+ ds_def_t *ds_def)
+{
+ int status;
+
+ status = get_string_from_node(doc, node,
+ ds_def->dst, sizeof(ds_def->dst));
+ if (status != 0)
+ return (-1);
+
+ status = dst_conv(ds_def->dst);
+ if (status == -1) {
+ rrd_set_error("parse_tag_ds_type: Unknown data source type: %s",
+ ds_def->dst);
+ return (-1);
+ }
+
+ return (0);
+} /* int parse_tag_ds_type */
+
+static int parse_tag_ds(
+ xmlDoc * doc,
+ xmlNode * node,
+ rrd_t *rrd)
+{
+ xmlNode *child;
+ int status;
+
+ ds_def_t *cur_ds_def;
+ pdp_prep_t *cur_pdp_prep;
+
+ /*
+ * If there are DS definitions after RRA definitions the number of values,
+ * cdp_prep areas and so on will be calculated wrong. Thus, enforce a
+ * specific order in this case.
+ */
+ if (rrd->stat_head->rra_cnt > 0) {
+ rrd_set_error("parse_tag_ds: All data source definitions MUST "
+ "precede the RRA definitions!");
+ return (-1);
+ }
+
+ /* Allocate space for the new DS definition */
+ { /* {{{ */
+ ds_def_t *temp;
+
+ temp = (ds_def_t *) realloc(rrd->ds_def,
+ sizeof(ds_def_t) *
+ (rrd->stat_head->ds_cnt + 1));
+ if (temp == NULL) {
+ rrd_set_error("parse_tag_ds: malloc failed.");
+ return (-1);
+ }
+ rrd->ds_def = temp;
+ cur_ds_def = rrd->ds_def + rrd->stat_head->ds_cnt;
+ memset(cur_ds_def, '\0', sizeof(ds_def_t));
+ } /* }}} */
+
+ /* Allocate pdp_prep space for the new DS definition */
+ { /* {{{ */
+ pdp_prep_t *temp;
+
+ temp = (pdp_prep_t *) realloc(rrd->pdp_prep,
+ sizeof(pdp_prep_t) *
+ (rrd->stat_head->ds_cnt + 1));
+ if (temp == NULL) {
+ rrd_set_error("parse_tag_ds: malloc failed.");
+ return (-1);
+ }
+ rrd->pdp_prep = temp;
+ cur_pdp_prep = rrd->pdp_prep + rrd->stat_head->ds_cnt;
+ memset(cur_pdp_prep, '\0', sizeof(pdp_prep_t));
+ } /* }}} */
+
+ /* All allocations successful, let's increment the number of DSes. */
+ rrd->stat_head->ds_cnt++;
+
+ status = 0;
+ for (child = node->xmlChildrenNode; child != NULL; child = child->next) {
+ if ((xmlStrcmp(child->name, (const xmlChar *) "comment") == 0)
+ || (xmlStrcmp(child->name, (const xmlChar *) "text") == 0))
+ /* ignore */ ;
+ else if (xmlStrcmp(child->name, (const xmlChar *) "name") == 0)
+ status = get_string_from_node(doc, child,
+ cur_ds_def->ds_nam,
+ sizeof(cur_ds_def->ds_nam));
+ else if (xmlStrcmp(child->name, (const xmlChar *) "type") == 0)
+ status = parse_tag_ds_type(doc, child, cur_ds_def);
+ else if (xmlStrcmp(child->name,
+ (const xmlChar *) "minimal_heartbeat") == 0)
+ status = get_int_from_node(doc, child,
+ (int *) &cur_ds_def->par[DS_mrhb_cnt].
+ u_cnt);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "min") == 0)
+ status = get_double_from_node(doc, child,
+ &cur_ds_def->par[DS_min_val].u_val);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "max") == 0)
+ status = get_double_from_node(doc, child,
+ &cur_ds_def->par[DS_max_val].u_val);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "cdef") == 0)
+ status = parse_tag_ds_cdef(doc, child, rrd);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "last_ds") == 0)
+ status = get_string_from_node(doc, child,
+ cur_pdp_prep->last_ds,
+ sizeof(cur_pdp_prep->last_ds));
+ else if (xmlStrcmp(child->name, (const xmlChar *) "value") == 0)
+ status = get_double_from_node(doc, child,
+ &cur_pdp_prep->scratch[PDP_val].
+ u_val);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "unknown_sec") == 0)
+ status = get_int_from_node(doc, child,
+ (int *) &cur_pdp_prep->
+ scratch[PDP_unkn_sec_cnt].u_cnt);
+ else {
+ rrd_set_error("parse_tag_ds: Unknown tag: %s", child->name);
+ status = -1;
+ }
+
+ if (status != 0)
+ break;
+ }
+
+ return (status);
+} /* int parse_tag_ds */
+
+/*
+ * Parse root nodes
+ */
+static int parse_tag_rrd(
+ xmlDoc * doc,
+ xmlNode * node,
+ rrd_t *rrd)
+{
+ xmlNode *child;
+ int status;
+
+ status = 0;
+ for (child = node->xmlChildrenNode; child != NULL; child = child->next) {
+ if ((xmlStrcmp(child->name, (const xmlChar *) "comment") == 0)
+ || (xmlStrcmp(child->name, (const xmlChar *) "text") == 0))
+ /* ignore */ ;
+ else if (xmlStrcmp(child->name, (const xmlChar *) "version") == 0)
+ status = get_string_from_node(doc, child,
+ rrd->stat_head->version,
+ sizeof(rrd->stat_head->version));
+ else if (xmlStrcmp(child->name, (const xmlChar *) "step") == 0)
+ status = get_int_from_node(doc, child,
+ (int *) &rrd->stat_head->pdp_step);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "lastupdate") == 0)
+ status = get_int_from_node(doc, child,
+ (int *) &rrd->live_head->last_up);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "ds") == 0)
+ status = parse_tag_ds(doc, child, rrd);
+ else if (xmlStrcmp(child->name, (const xmlChar *) "rra") == 0)
+ status = parse_tag_rra(doc, child, rrd);
+ else {
+ rrd_set_error("parse_tag_rrd: Unknown tag: %s", child->name);
+ status = -1;
+ }
+
+ if (status != 0)
+ break;
+ }
+
+ return (status);
+} /* int parse_tag_rrd */
+
+static rrd_t *parse_file(
+ const char *filename)
+{
+ xmlDoc *doc;
+ xmlNode *cur;
+ int status;
+
+ rrd_t *rrd;
+
+ doc = xmlParseFile(filename);
+ if (doc == NULL) {
+ rrd_set_error("Document not parsed successfully.");
+ return (NULL);
+ }
+
+ cur = xmlDocGetRootElement(doc);
+ if (cur == NULL) {
+ rrd_set_error("Document is empty.");
+ xmlFreeDoc(doc);
+ return (NULL);
+ }
+
+ if (xmlStrcmp(cur->name, (const xmlChar *) "rrd") != 0) {
+ rrd_set_error
+ ("Document of the wrong type, root node is not \"rrd\".");
+ xmlFreeDoc(doc);
+ return (NULL);
+ }
+
+ rrd = (rrd_t *) malloc(sizeof(rrd_t));
+ if (rrd == NULL) {
+ rrd_set_error("parse_file: malloc failed.");
+ xmlFreeDoc(doc);
+ return (NULL);
+ }
+ memset(rrd, '\0', sizeof(rrd_t));
+
+ rrd->stat_head = (stat_head_t *) malloc(sizeof(stat_head_t));
+ if (rrd->stat_head == NULL) {
+ rrd_set_error("parse_tag_rrd: malloc failed.");
+ xmlFreeDoc(doc);
+ free(rrd);
+ return (NULL);
+ }
+ memset(rrd->stat_head, '\0', sizeof(stat_head_t));
+
+ strncpy(rrd->stat_head->cookie, "RRD", sizeof(rrd->stat_head->cookie));
+ rrd->stat_head->float_cookie = FLOAT_COOKIE;
+
+ rrd->live_head = (live_head_t *) malloc(sizeof(live_head_t));
+ if (rrd->live_head == NULL) {
+ rrd_set_error("parse_tag_rrd: malloc failed.");
+ xmlFreeDoc(doc);
+ free(rrd->stat_head);
+ free(rrd);
+ return (NULL);
+ }
+ memset(rrd->live_head, '\0', sizeof(live_head_t));
+
+ status = parse_tag_rrd(doc, cur, rrd);
+
+ xmlFreeDoc(doc);
+ if (status != 0) {
+ rrd_free(rrd);
+ rrd = NULL;
+ }
+
+ return (rrd);
+} /* rrd_t *parse_file */
+
+static int write_file(
+ const char *file_name,
+ rrd_t *rrd)
+{
+ FILE *fh;
+ unsigned int i;
+ unsigned int rra_offset;
+
+ if (strcmp("-", file_name) == 0)
+ fh = stdout;
+ else {
+ int fd_flags = O_WRONLY | O_CREAT;
+ int fd;
+
+#if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
+ fd_flags |= O_BINARY;
+#endif
+
+ if (opt_force_overwrite == 0)
+ fd_flags |= O_EXCL;
+
+ fd = open(file_name, fd_flags, 0666);
+ if (fd == -1) {
+ rrd_set_error("creating '%s': %s", file_name,
+ rrd_strerror(errno));
+ return (-1);
+ }
+
+ fh = fdopen(fd, "wb");
+ if (fh == NULL) {
+ rrd_set_error("fdopen failed: %s", rrd_strerror(errno));
+ close(fd);
+ return (-1);
+ }
+ }
+ if (atoi(rrd->stat_head->version) < 3) {
+ /* we output 3 or higher */
+ strcpy(rrd->stat_head->version, "0003");
+ }
+ fwrite(rrd->stat_head, sizeof(stat_head_t), 1, fh);
+ fwrite(rrd->ds_def, sizeof(ds_def_t), rrd->stat_head->ds_cnt, fh);
+ fwrite(rrd->rra_def, sizeof(rra_def_t), rrd->stat_head->rra_cnt, fh);
+ fwrite(rrd->live_head, sizeof(live_head_t), 1, fh);
+ fwrite(rrd->pdp_prep, sizeof(pdp_prep_t), rrd->stat_head->ds_cnt, fh);
+ fwrite(rrd->cdp_prep, sizeof(cdp_prep_t),
+ rrd->stat_head->rra_cnt * rrd->stat_head->ds_cnt, fh);
+ fwrite(rrd->rra_ptr, sizeof(rra_ptr_t), rrd->stat_head->rra_cnt, fh);
+
+ /* calculate the number of rrd_values to dump */
+ rra_offset = 0;
+ for (i = 0; i < rrd->stat_head->rra_cnt; i++) {
+ unsigned long num_rows = rrd->rra_def[i].row_cnt;
+ unsigned long cur_row = rrd->rra_ptr[i].cur_row;
+ unsigned long ds_cnt = rrd->stat_head->ds_cnt;
+
+ fwrite(rrd->rrd_value +
+ (rra_offset + num_rows - 1 - cur_row) * ds_cnt,
+ sizeof(rrd_value_t), (cur_row + 1) * ds_cnt, fh);
+
+ fwrite(rrd->rrd_value + rra_offset * ds_cnt,
+ sizeof(rrd_value_t), (num_rows - 1 - cur_row) * ds_cnt, fh);
+
+ rra_offset += num_rows;
+ }
+
+ /* lets see if we had an error */
+ if (ferror(fh)) {
+ rrd_set_error("a file error occurred while creating '%s'", file_name);
+ fclose(fh);
+ return (-1);
+ }
+
+ fclose(fh);
+ return (0);
+} /* int write_file */
+
+int rrd_restore(
+ int argc,
+ char **argv)
+{
+ rrd_t *rrd;
+
+ srandom((unsigned int) time(NULL) + (unsigned int) getpid());
+ /* init rrd clean */
+ optind = 0;
+ opterr = 0; /* initialize getopt */
+ while (42) {
+ int opt;
+ int option_index = 0;
+ static struct option long_options[] = {
+ {"range-check", no_argument, 0, 'r'},
+ {"force-overwrite", no_argument, 0, 'f'},
+ {0, 0, 0, 0}
+ };
+
+ opt = getopt_long(argc, argv, "rf", long_options, &option_index);
+
+ if (opt == EOF)
+ break;
+
+ switch (opt) {
+ case 'r':
+ opt_range_check = 1;
+ break;
+
+ case 'f':
+ opt_force_overwrite = 1;
+ break;
+
+ default:
+ rrd_set_error("usage rrdtool %s [--range-check|-r] "
+ "[--force-overwrite/-f] file.xml file.rrd",
+ argv[0]);
+ return (-1);
+ break;
+ }
+ } /* while (42) */
+
+ if ((argc - optind) != 2) {
+ rrd_set_error("usage rrdtool %s [--range-check/-r] "
+ "[--force-overwrite/-f] file.xml file.rrd", argv[0]);
+ return (-1);
+ }
+
+ rrd = parse_file(argv[optind]);
+ if (rrd == NULL)
+ return (-1);
+
+ if (write_file(argv[optind + 1], rrd) != 0) {
+ rrd_free(rrd);
+ return (-1);
+ }
+
+ rrd_free(rrd);
+ return (0);
+} /* int rrd_restore */
+
+/* vim: set sw=2 sts=2 ts=8 et fdm=marker : */
diff --git a/program/src/rrd_rpncalc.c b/program/src/rrd_rpncalc.c
--- /dev/null
@@ -0,0 +1,894 @@
+/****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ ****************************************************************************
+ * rrd_rpncalc.c RPN calculator functions
+ ****************************************************************************/
+
+#include "rrd_tool.h"
+#include "rrd_rpncalc.h"
+// #include "rrd_graph.h"
+#include <limits.h>
+#include <locale.h>
+
+short addop2str(
+ enum op_en op,
+ enum op_en op_type,
+ char *op_str,
+ char **result_str,
+ unsigned short *offset);
+int tzoffset(
+ time_t); /* used to implement LTIME */
+
+short rpn_compact(
+ rpnp_t *rpnp,
+ rpn_cdefds_t **rpnc,
+ short *count)
+{
+ short i;
+
+ *count = 0;
+ /* count the number of rpn nodes */
+ while (rpnp[*count].op != OP_END)
+ (*count)++;
+ if (++(*count) > DS_CDEF_MAX_RPN_NODES) {
+ rrd_set_error("Maximum %d RPN nodes permitted",
+ DS_CDEF_MAX_RPN_NODES);
+ return -1;
+ }
+
+ /* allocate memory */
+ *rpnc = (rpn_cdefds_t *) calloc(*count, sizeof(rpn_cdefds_t));
+ for (i = 0; rpnp[i].op != OP_END; i++) {
+ (*rpnc)[i].op = (char) rpnp[i].op;
+ if (rpnp[i].op == OP_NUMBER) {
+ /* rpnp.val is a double, rpnc.val is a short */
+ double temp = floor(rpnp[i].val);
+
+ if (temp < SHRT_MIN || temp > SHRT_MAX) {
+ rrd_set_error
+ ("constants must be integers in the interval (%d, %d)",
+ SHRT_MIN, SHRT_MAX);
+ free(*rpnc);
+ return -1;
+ }
+ (*rpnc)[i].val = (short) temp;
+ } else if (rpnp[i].op == OP_VARIABLE || rpnp[i].op == OP_PREV_OTHER) {
+ (*rpnc)[i].val = (short) rpnp[i].ptr;
+ }
+ }
+ /* terminate the sequence */
+ (*rpnc)[(*count) - 1].op = OP_END;
+ return 0;
+}
+
+rpnp_t *rpn_expand(
+ rpn_cdefds_t *rpnc)
+{
+ short i;
+ rpnp_t *rpnp;
+
+ /* DS_CDEF_MAX_RPN_NODES is small, so at the expense of some wasted
+ * memory we avoid any reallocs */
+ rpnp = (rpnp_t *) calloc(DS_CDEF_MAX_RPN_NODES, sizeof(rpnp_t));
+ if (rpnp == NULL)
+ return NULL;
+ for (i = 0; rpnc[i].op != OP_END; ++i) {
+ rpnp[i].op = (long) rpnc[i].op;
+ if (rpnp[i].op == OP_NUMBER) {
+ rpnp[i].val = (double) rpnc[i].val;
+ } else if (rpnp[i].op == OP_VARIABLE || rpnp[i].op == OP_PREV_OTHER) {
+ rpnp[i].ptr = (long) rpnc[i].val;
+ }
+ }
+ /* terminate the sequence */
+ rpnp[i].op = OP_END;
+ return rpnp;
+}
+
+/* rpn_compact2str: convert a compact sequence of RPN operator nodes back
+ * into a CDEF string. This function is used by rrd_dump.
+ * arguments:
+ * rpnc: an array of compact RPN operator nodes
+ * ds_def: a pointer to the data source definition section of an RRD header
+ * for lookup of data source names by index
+ * str: out string, memory is allocated by the function, must be freed by the
+ * the caller */
+void rpn_compact2str(
+ rpn_cdefds_t *rpnc,
+ ds_def_t *ds_def,
+ char **str)
+{
+ unsigned short i, offset = 0;
+ char buffer[7]; /* short as a string */
+
+ for (i = 0; rpnc[i].op != OP_END; i++) {
+ if (i > 0)
+ (*str)[offset++] = ',';
+
+#define add_op(VV,VVV) \
+ if (addop2str(rpnc[i].op, VV, VVV, str, &offset) == 1) continue;
+
+ if (rpnc[i].op == OP_NUMBER) {
+ /* convert a short into a string */
+#if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
+ _itoa(rpnc[i].val, buffer, 10);
+#else
+ sprintf(buffer, "%d", rpnc[i].val);
+#endif
+ add_op(OP_NUMBER, buffer)
+ }
+
+ if (rpnc[i].op == OP_VARIABLE) {
+ char *ds_name = ds_def[rpnc[i].val].ds_nam;
+
+ add_op(OP_VARIABLE, ds_name)
+ }
+
+ if (rpnc[i].op == OP_PREV_OTHER) {
+ char *ds_name = ds_def[rpnc[i].val].ds_nam;
+
+ add_op(OP_VARIABLE, ds_name)
+ }
+#undef add_op
+
+#define add_op(VV,VVV) \
+ if (addop2str(rpnc[i].op, VV, #VVV, str, &offset) == 1) continue;
+
+ add_op(OP_ADD, +)
+ add_op(OP_SUB, -)
+ add_op(OP_MUL, *)
+ add_op(OP_DIV, /)
+ add_op(OP_MOD, %)
+ add_op(OP_SIN, SIN)
+ add_op(OP_COS, COS)
+ add_op(OP_LOG, LOG)
+ add_op(OP_FLOOR, FLOOR)
+ add_op(OP_CEIL, CEIL)
+ add_op(OP_EXP, EXP)
+ add_op(OP_DUP, DUP)
+ add_op(OP_EXC, EXC)
+ add_op(OP_POP, POP)
+ add_op(OP_LT, LT)
+ add_op(OP_LE, LE)
+ add_op(OP_GT, GT)
+ add_op(OP_GE, GE)
+ add_op(OP_EQ, EQ)
+ add_op(OP_IF, IF)
+ add_op(OP_MIN, MIN)
+ add_op(OP_MAX, MAX)
+ add_op(OP_LIMIT, LIMIT)
+ add_op(OP_UNKN, UNKN)
+ add_op(OP_UN, UN)
+ add_op(OP_NEGINF, NEGINF)
+ add_op(OP_NE, NE)
+ add_op(OP_PREV, PREV)
+ add_op(OP_INF, INF)
+ add_op(OP_ISINF, ISINF)
+ add_op(OP_NOW, NOW)
+ add_op(OP_LTIME, LTIME)
+ add_op(OP_TIME, TIME)
+ add_op(OP_ATAN2, ATAN2)
+ add_op(OP_ATAN, ATAN)
+ add_op(OP_SQRT, SQRT)
+ add_op(OP_SORT, SORT)
+ add_op(OP_REV, REV)
+ add_op(OP_TREND, TREND)
+ add_op(OP_TRENDNAN, TRENDNAN)
+ add_op(OP_RAD2DEG, RAD2DEG)
+ add_op(OP_DEG2RAD, DEG2RAD)
+ add_op(OP_AVG, AVG)
+ add_op(OP_ABS, ABS)
+ add_op(OP_ADDNAN, ADDNAN)
+#undef add_op
+ }
+ (*str)[offset] = '\0';
+
+}
+
+short addop2str(
+ enum op_en op,
+ enum op_en op_type,
+ char *op_str,
+ char **result_str,
+ unsigned short *offset)
+{
+ if (op == op_type) {
+ short op_len;
+
+ op_len = strlen(op_str);
+ *result_str = (char *) rrd_realloc(*result_str,
+ (op_len + 1 +
+ *offset) * sizeof(char));
+ if (*result_str == NULL) {
+ rrd_set_error("failed to alloc memory in addop2str");
+ return -1;
+ }
+ strncpy(&((*result_str)[*offset]), op_str, op_len);
+ *offset += op_len;
+ return 1;
+ }
+ return 0;
+}
+
+void parseCDEF_DS(
+ const char *def,
+ rrd_t *rrd,
+ int ds_idx)
+{
+ rpnp_t *rpnp = NULL;
+ rpn_cdefds_t *rpnc = NULL;
+ short count, i;
+
+ rpnp = rpn_parse((void *) rrd, def, &lookup_DS);
+ if (rpnp == NULL) {
+ rrd_set_error("failed to parse computed data source");
+ return;
+ }
+ /* Check for OP nodes not permitted in COMPUTE DS.
+ * Moved this check from within rpn_compact() because it really is
+ * COMPUTE DS specific. This is less efficient, but creation doesn't
+ * occur too often. */
+ for (i = 0; rpnp[i].op != OP_END; i++) {
+ if (rpnp[i].op == OP_TIME || rpnp[i].op == OP_LTIME ||
+ rpnp[i].op == OP_PREV || rpnp[i].op == OP_COUNT) {
+ rrd_set_error
+ ("operators time, ltime, prev and count not supported with DS COMPUTE");
+ free(rpnp);
+ return;
+ }
+ }
+ if (rpn_compact(rpnp, &rpnc, &count) == -1) {
+ free(rpnp);
+ return;
+ }
+ /* copy the compact rpn representation over the ds_def par array */
+ memcpy((void *) &(rrd->ds_def[ds_idx].par[DS_cdef]),
+ (void *) rpnc, count * sizeof(rpn_cdefds_t));
+ free(rpnp);
+ free(rpnc);
+}
+
+/* lookup a data source name in the rrd struct and return the index,
+ * should use ds_match() here except:
+ * (1) need a void * pointer to the rrd
+ * (2) error handling is left to the caller
+ */
+long lookup_DS(
+ void *rrd_vptr,
+ char *ds_name)
+{
+ unsigned int i;
+ rrd_t *rrd;
+
+ rrd = (rrd_t *) rrd_vptr;
+
+ for (i = 0; i < rrd->stat_head->ds_cnt; ++i) {
+ if (strcmp(ds_name, rrd->ds_def[i].ds_nam) == 0)
+ return i;
+ }
+ /* the caller handles a bad data source name in the rpn string */
+ return -1;
+}
+
+/* rpn_parse : parse a string and generate a rpnp array; modified
+ * str2rpn() originally included in rrd_graph.c
+ * arguments:
+ * key_hash: a transparent argument passed to lookup(); conceptually this
+ * is a hash object for lookup of a numeric key given a variable name
+ * expr: the string RPN expression, including variable names
+ * lookup(): a function that retrieves a numeric key given a variable name
+ */
+rpnp_t *rpn_parse(
+ void *key_hash,
+ const char *const expr_const,
+ long (*lookup) (void *,
+ char *))
+{
+ int pos = 0;
+ char *expr;
+ long steps = -1;
+ rpnp_t *rpnp;
+ char vname[MAX_VNAME_LEN + 10];
+ char *old_locale;
+
+ old_locale = setlocale(LC_NUMERIC, "C");
+
+ rpnp = NULL;
+ expr = (char *) expr_const;
+
+ while (*expr) {
+ if ((rpnp = (rpnp_t *) rrd_realloc(rpnp, (++steps + 2) *
+ sizeof(rpnp_t))) == NULL) {
+ setlocale(LC_NUMERIC, old_locale);
+ return NULL;
+ }
+
+ else if ((sscanf(expr, "%lf%n", &rpnp[steps].val, &pos) == 1)
+ && (expr[pos] == ',')) {
+ rpnp[steps].op = OP_NUMBER;
+ expr += pos;
+ }
+#define match_op(VV,VVV) \
+ else if (strncmp(expr, #VVV, strlen(#VVV))==0 && ( expr[strlen(#VVV)] == ',' || expr[strlen(#VVV)] == '\0' )){ \
+ rpnp[steps].op = VV; \
+ expr+=strlen(#VVV); \
+ }
+
+#define match_op_param(VV,VVV) \
+ else if (sscanf(expr, #VVV "(" DEF_NAM_FMT ")",vname) == 1) { \
+ int length = 0; \
+ if ((length = strlen(#VVV)+strlen(vname)+2, \
+ expr[length] == ',' || expr[length] == '\0') ) { \
+ rpnp[steps].op = VV; \
+ rpnp[steps].ptr = (*lookup)(key_hash,vname); \
+ if (rpnp[steps].ptr < 0) { \
+ free(rpnp); \
+ return NULL; \
+ } else expr+=length; \
+ } \
+ }
+
+ match_op(OP_ADD, +)
+ match_op(OP_SUB, -)
+ match_op(OP_MUL, *)
+ match_op(OP_DIV, /)
+ match_op(OP_MOD, %)
+ match_op(OP_SIN, SIN)
+ match_op(OP_COS, COS)
+ match_op(OP_LOG, LOG)
+ match_op(OP_FLOOR, FLOOR)
+ match_op(OP_CEIL, CEIL)
+ match_op(OP_EXP, EXP)
+ match_op(OP_DUP, DUP)
+ match_op(OP_EXC, EXC)
+ match_op(OP_POP, POP)
+ match_op(OP_LTIME, LTIME)
+ match_op(OP_LT, LT)
+ match_op(OP_LE, LE)
+ match_op(OP_GT, GT)
+ match_op(OP_GE, GE)
+ match_op(OP_EQ, EQ)
+ match_op(OP_IF, IF)
+ match_op(OP_MIN, MIN)
+ match_op(OP_MAX, MAX)
+ match_op(OP_LIMIT, LIMIT)
+ /* order is important here ! .. match longest first */
+ match_op(OP_UNKN, UNKN)
+ match_op(OP_UN, UN)
+ match_op(OP_NEGINF, NEGINF)
+ match_op(OP_NE, NE)
+ match_op(OP_COUNT, COUNT)
+ match_op_param(OP_PREV_OTHER, PREV)
+ match_op(OP_PREV, PREV)
+ match_op(OP_INF, INF)
+ match_op(OP_ISINF, ISINF)
+ match_op(OP_NOW, NOW)
+ match_op(OP_TIME, TIME)
+ match_op(OP_ATAN2, ATAN2)
+ match_op(OP_ATAN, ATAN)
+ match_op(OP_SQRT, SQRT)
+ match_op(OP_SORT, SORT)
+ match_op(OP_REV, REV)
+ match_op(OP_TREND, TREND)
+ match_op(OP_TRENDNAN, TRENDNAN)
+ match_op(OP_RAD2DEG, RAD2DEG)
+ match_op(OP_DEG2RAD, DEG2RAD)
+ match_op(OP_AVG, AVG)
+ match_op(OP_ABS, ABS)
+ match_op(OP_ADDNAN, ADDNAN)
+#undef match_op
+ else if ((sscanf(expr, DEF_NAM_FMT "%n", vname, &pos) == 1)
+ && ((rpnp[steps].ptr = (*lookup) (key_hash, vname)) !=
+ -1)) {
+ rpnp[steps].op = OP_VARIABLE;
+ expr += pos;
+ }
+
+ else {
+ setlocale(LC_NUMERIC, old_locale);
+ free(rpnp);
+ return NULL;
+ }
+
+ if (*expr == 0)
+ break;
+ if (*expr == ',')
+ expr++;
+ else {
+ setlocale(LC_NUMERIC, old_locale);
+ free(rpnp);
+ return NULL;
+ }
+ }
+ rpnp[steps + 1].op = OP_END;
+ setlocale(LC_NUMERIC, old_locale);
+ return rpnp;
+}
+
+void rpnstack_init(
+ rpnstack_t *rpnstack)
+{
+ rpnstack->s = NULL;
+ rpnstack->dc_stacksize = 0;
+ rpnstack->dc_stackblock = 100;
+}
+
+void rpnstack_free(
+ rpnstack_t *rpnstack)
+{
+ if (rpnstack->s != NULL)
+ free(rpnstack->s);
+ rpnstack->dc_stacksize = 0;
+}
+
+static int rpn_compare_double(
+ const void *x,
+ const void *y)
+{
+ double diff = *((const double *) x) - *((const double *) y);
+
+ return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
+}
+
+/* rpn_calc: run the RPN calculator; also performs variable substitution;
+ * moved and modified from data_calc() originally included in rrd_graph.c
+ * arguments:
+ * rpnp : an array of RPN operators (including variable references)
+ * rpnstack : the initialized stack
+ * data_idx : when data_idx is a multiple of rpnp.step, the rpnp.data pointer
+ * is advanced by rpnp.ds_cnt; used only for variable substitution
+ * output : an array of output values; OP_PREV assumes this array contains
+ * the "previous" value at index position output_idx-1; the definition of
+ * "previous" depends on the calling environment
+ * output_idx : an index into the output array in which to store the output
+ * of the RPN calculator
+ * returns: -1 if the computation failed (also calls rrd_set_error)
+ * 0 on success
+ */
+short rpn_calc(
+ rpnp_t *rpnp,
+ rpnstack_t *rpnstack,
+ long data_idx,
+ rrd_value_t *output,
+ int output_idx)
+{
+ int rpi;
+ long stptr = -1;
+
+ /* process each op from the rpn in turn */
+ for (rpi = 0; rpnp[rpi].op != OP_END; rpi++) {
+ /* allocate or grow the stack */
+ if (stptr + 5 > rpnstack->dc_stacksize) {
+ /* could move this to a separate function */
+ rpnstack->dc_stacksize += rpnstack->dc_stackblock;
+ rpnstack->s = rrd_realloc(rpnstack->s,
+ (rpnstack->dc_stacksize) *
+ sizeof(*(rpnstack->s)));
+ if (rpnstack->s == NULL) {
+ rrd_set_error("RPN stack overflow");
+ return -1;
+ }
+ }
+#define stackunderflow(MINSIZE) \
+ if(stptr<MINSIZE){ \
+ rrd_set_error("RPN stack underflow"); \
+ return -1; \
+ }
+
+ switch (rpnp[rpi].op) {
+ case OP_NUMBER:
+ rpnstack->s[++stptr] = rpnp[rpi].val;
+ break;
+ case OP_VARIABLE:
+ case OP_PREV_OTHER:
+ /* Sanity check: VDEFs shouldn't make it here */
+ if (rpnp[rpi].ds_cnt == 0) {
+ rrd_set_error("VDEF made it into rpn_calc... aborting");
+ return -1;
+ } else {
+ /* make sure we pull the correct value from
+ * the *.data array. Adjust the pointer into
+ * the array acordingly. Advance the ptr one
+ * row in the rra (skip over non-relevant
+ * data sources)
+ */
+ if (rpnp[rpi].op == OP_VARIABLE) {
+ rpnstack->s[++stptr] = *(rpnp[rpi].data);
+ } else {
+ if ((output_idx) <= 0) {
+ rpnstack->s[++stptr] = DNAN;
+ } else {
+ rpnstack->s[++stptr] =
+ *(rpnp[rpi].data - rpnp[rpi].ds_cnt);
+ }
+
+ }
+ if (data_idx % rpnp[rpi].step == 0) {
+ rpnp[rpi].data += rpnp[rpi].ds_cnt;
+ }
+ }
+ break;
+ case OP_COUNT:
+ rpnstack->s[++stptr] = (output_idx + 1); /* Note: Counter starts at 1 */
+ break;
+ case OP_PREV:
+ if ((output_idx) <= 0) {
+ rpnstack->s[++stptr] = DNAN;
+ } else {
+ rpnstack->s[++stptr] = output[output_idx - 1];
+ }
+ break;
+ case OP_UNKN:
+ rpnstack->s[++stptr] = DNAN;
+ break;
+ case OP_INF:
+ rpnstack->s[++stptr] = DINF;
+ break;
+ case OP_NEGINF:
+ rpnstack->s[++stptr] = -DINF;
+ break;
+ case OP_NOW:
+ rpnstack->s[++stptr] = (double) time(NULL);
+ break;
+ case OP_TIME:
+ /* HACK: this relies on the data_idx being the time,
+ ** which the within-function scope is unaware of */
+ rpnstack->s[++stptr] = (double) data_idx;
+ break;
+ case OP_LTIME:
+ rpnstack->s[++stptr] =
+ (double) tzoffset(data_idx) + (double) data_idx;
+ break;
+ case OP_ADD:
+ stackunderflow(1);
+ rpnstack->s[stptr - 1] = rpnstack->s[stptr - 1]
+ + rpnstack->s[stptr];
+ stptr--;
+ break;
+ case OP_ADDNAN:
+ stackunderflow(1);
+ if (isnan(rpnstack->s[stptr - 1])) {
+ rpnstack->s[stptr - 1] = rpnstack->s[stptr];
+ } else if (isnan(rpnstack->s[stptr])) {
+ /* NOOP */
+ /* rpnstack->s[stptr - 1] = rpnstack->s[stptr - 1]; */
+ } else {
+ rpnstack->s[stptr - 1] = rpnstack->s[stptr - 1]
+ + rpnstack->s[stptr];
+ }
+
+ stptr--;
+ break;
+ case OP_SUB:
+ stackunderflow(1);
+ rpnstack->s[stptr - 1] = rpnstack->s[stptr - 1]
+ - rpnstack->s[stptr];
+ stptr--;
+ break;
+ case OP_MUL:
+ stackunderflow(1);
+ rpnstack->s[stptr - 1] = (rpnstack->s[stptr - 1])
+ * (rpnstack->s[stptr]);
+ stptr--;
+ break;
+ case OP_DIV:
+ stackunderflow(1);
+ rpnstack->s[stptr - 1] = rpnstack->s[stptr - 1]
+ / rpnstack->s[stptr];
+ stptr--;
+ break;
+ case OP_MOD:
+ stackunderflow(1);
+ rpnstack->s[stptr - 1] = fmod(rpnstack->s[stptr - 1]
+ , rpnstack->s[stptr]);
+ stptr--;
+ break;
+ case OP_SIN:
+ stackunderflow(0);
+ rpnstack->s[stptr] = sin(rpnstack->s[stptr]);
+ break;
+ case OP_ATAN:
+ stackunderflow(0);
+ rpnstack->s[stptr] = atan(rpnstack->s[stptr]);
+ break;
+ case OP_RAD2DEG:
+ stackunderflow(0);
+ rpnstack->s[stptr] = 57.29577951 * rpnstack->s[stptr];
+ break;
+ case OP_DEG2RAD:
+ stackunderflow(0);
+ rpnstack->s[stptr] = 0.0174532952 * rpnstack->s[stptr];
+ break;
+ case OP_ATAN2:
+ stackunderflow(1);
+ rpnstack->s[stptr - 1] = atan2(rpnstack->s[stptr - 1],
+ rpnstack->s[stptr]);
+ stptr--;
+ break;
+ case OP_COS:
+ stackunderflow(0);
+ rpnstack->s[stptr] = cos(rpnstack->s[stptr]);
+ break;
+ case OP_CEIL:
+ stackunderflow(0);
+ rpnstack->s[stptr] = ceil(rpnstack->s[stptr]);
+ break;
+ case OP_FLOOR:
+ stackunderflow(0);
+ rpnstack->s[stptr] = floor(rpnstack->s[stptr]);
+ break;
+ case OP_LOG:
+ stackunderflow(0);
+ rpnstack->s[stptr] = log(rpnstack->s[stptr]);
+ break;
+ case OP_DUP:
+ stackunderflow(0);
+ rpnstack->s[stptr + 1] = rpnstack->s[stptr];
+ stptr++;
+ break;
+ case OP_POP:
+ stackunderflow(0);
+ stptr--;
+ break;
+ case OP_EXC:
+ stackunderflow(1);
+ {
+ double dummy;
+
+ dummy = rpnstack->s[stptr];
+ rpnstack->s[stptr] = rpnstack->s[stptr - 1];
+ rpnstack->s[stptr - 1] = dummy;
+ }
+ break;
+ case OP_EXP:
+ stackunderflow(0);
+ rpnstack->s[stptr] = exp(rpnstack->s[stptr]);
+ break;
+ case OP_LT:
+ stackunderflow(1);
+ if (isnan(rpnstack->s[stptr - 1]));
+ else if (isnan(rpnstack->s[stptr]))
+ rpnstack->s[stptr - 1] = rpnstack->s[stptr];
+ else
+ rpnstack->s[stptr - 1] = rpnstack->s[stptr - 1] <
+ rpnstack->s[stptr] ? 1.0 : 0.0;
+ stptr--;
+ break;
+ case OP_LE:
+ stackunderflow(1);
+ if (isnan(rpnstack->s[stptr - 1]));
+ else if (isnan(rpnstack->s[stptr]))
+ rpnstack->s[stptr - 1] = rpnstack->s[stptr];
+ else
+ rpnstack->s[stptr - 1] = rpnstack->s[stptr - 1] <=
+ rpnstack->s[stptr] ? 1.0 : 0.0;
+ stptr--;
+ break;
+ case OP_GT:
+ stackunderflow(1);
+ if (isnan(rpnstack->s[stptr - 1]));
+ else if (isnan(rpnstack->s[stptr]))
+ rpnstack->s[stptr - 1] = rpnstack->s[stptr];
+ else
+ rpnstack->s[stptr - 1] = rpnstack->s[stptr - 1] >
+ rpnstack->s[stptr] ? 1.0 : 0.0;
+ stptr--;
+ break;
+ case OP_GE:
+ stackunderflow(1);
+ if (isnan(rpnstack->s[stptr - 1]));
+ else if (isnan(rpnstack->s[stptr]))
+ rpnstack->s[stptr - 1] = rpnstack->s[stptr];
+ else
+ rpnstack->s[stptr - 1] = rpnstack->s[stptr - 1] >=
+ rpnstack->s[stptr] ? 1.0 : 0.0;
+ stptr--;
+ break;
+ case OP_NE:
+ stackunderflow(1);
+ if (isnan(rpnstack->s[stptr - 1]));
+ else if (isnan(rpnstack->s[stptr]))
+ rpnstack->s[stptr - 1] = rpnstack->s[stptr];
+ else
+ rpnstack->s[stptr - 1] = rpnstack->s[stptr - 1] ==
+ rpnstack->s[stptr] ? 0.0 : 1.0;
+ stptr--;
+ break;
+ case OP_EQ:
+ stackunderflow(1);
+ if (isnan(rpnstack->s[stptr - 1]));
+ else if (isnan(rpnstack->s[stptr]))
+ rpnstack->s[stptr - 1] = rpnstack->s[stptr];
+ else
+ rpnstack->s[stptr - 1] = rpnstack->s[stptr - 1] ==
+ rpnstack->s[stptr] ? 1.0 : 0.0;
+ stptr--;
+ break;
+ case OP_IF:
+ stackunderflow(2);
+ rpnstack->s[stptr - 2] = (isnan(rpnstack->s[stptr - 2])
+ || rpnstack->s[stptr - 2] ==
+ 0.0) ? rpnstack->s[stptr] : rpnstack->
+ s[stptr - 1];
+ stptr--;
+ stptr--;
+ break;
+ case OP_MIN:
+ stackunderflow(1);
+ if (isnan(rpnstack->s[stptr - 1]));
+ else if (isnan(rpnstack->s[stptr]))
+ rpnstack->s[stptr - 1] = rpnstack->s[stptr];
+ else if (rpnstack->s[stptr - 1] > rpnstack->s[stptr])
+ rpnstack->s[stptr - 1] = rpnstack->s[stptr];
+ stptr--;
+ break;
+ case OP_MAX:
+ stackunderflow(1);
+ if (isnan(rpnstack->s[stptr - 1]));
+ else if (isnan(rpnstack->s[stptr]))
+ rpnstack->s[stptr - 1] = rpnstack->s[stptr];
+ else if (rpnstack->s[stptr - 1] < rpnstack->s[stptr])
+ rpnstack->s[stptr - 1] = rpnstack->s[stptr];
+ stptr--;
+ break;
+ case OP_LIMIT:
+ stackunderflow(2);
+ if (isnan(rpnstack->s[stptr - 2]));
+ else if (isnan(rpnstack->s[stptr - 1]))
+ rpnstack->s[stptr - 2] = rpnstack->s[stptr - 1];
+ else if (isnan(rpnstack->s[stptr]))
+ rpnstack->s[stptr - 2] = rpnstack->s[stptr];
+ else if (rpnstack->s[stptr - 2] < rpnstack->s[stptr - 1])
+ rpnstack->s[stptr - 2] = DNAN;
+ else if (rpnstack->s[stptr - 2] > rpnstack->s[stptr])
+ rpnstack->s[stptr - 2] = DNAN;
+ stptr -= 2;
+ break;
+ case OP_UN:
+ stackunderflow(0);
+ rpnstack->s[stptr] = isnan(rpnstack->s[stptr]) ? 1.0 : 0.0;
+ break;
+ case OP_ISINF:
+ stackunderflow(0);
+ rpnstack->s[stptr] = isinf(rpnstack->s[stptr]) ? 1.0 : 0.0;
+ break;
+ case OP_SQRT:
+ stackunderflow(0);
+ rpnstack->s[stptr] = sqrt(rpnstack->s[stptr]);
+ break;
+ case OP_SORT:
+ stackunderflow(0);
+ {
+ int spn = (int) rpnstack->s[stptr--];
+
+ stackunderflow(spn - 1);
+ qsort(rpnstack->s + stptr - spn + 1, spn, sizeof(double),
+ rpn_compare_double);
+ }
+ break;
+ case OP_REV:
+ stackunderflow(0);
+ {
+ int spn = (int) rpnstack->s[stptr--];
+ double *p, *q;
+
+ stackunderflow(spn - 1);
+
+ p = rpnstack->s + stptr - spn + 1;
+ q = rpnstack->s + stptr;
+ while (p < q) {
+ double x = *q;
+
+ *q-- = *p;
+ *p++ = x;
+ }
+ }
+ break;
+ case OP_TREND:
+ case OP_TRENDNAN:
+ stackunderflow(1);
+ if ((rpi < 2) || (rpnp[rpi - 2].op != OP_VARIABLE)) {
+ rrd_set_error("malformed trend arguments");
+ return -1;
+ } else {
+ time_t dur = (time_t) rpnstack->s[stptr];
+ time_t step = (time_t) rpnp[rpi - 2].step;
+
+ if (output_idx > (int) ceil((float) dur / (float) step)) {
+ int ignorenan = (rpnp[rpi].op == OP_TREND);
+ double accum = 0.0;
+ int i = 0;
+ int count = 0;
+
+ do {
+ double val =
+ rpnp[rpi - 2].data[rpnp[rpi - 2].ds_cnt * i--];
+ if (ignorenan || !isnan(val)) {
+ accum += val;
+ ++count;
+ }
+
+ dur -= step;
+ } while (dur > 0);
+
+ rpnstack->s[--stptr] =
+ (count == 0) ? DNAN : (accum / count);
+ } else
+ rpnstack->s[--stptr] = DNAN;
+ }
+ break;
+ case OP_AVG:
+ stackunderflow(0);
+ {
+ int i = (int) rpnstack->s[stptr--];
+ double sum = 0;
+ int count = 0;
+
+ stackunderflow(i - 1);
+ while (i > 0) {
+ double val = rpnstack->s[stptr--];
+
+ i--;
+ if (isnan(val)) {
+ continue;
+ }
+ count++;
+ sum += val;
+ }
+ /* now push the result back on stack */
+ if (count > 0) {
+ rpnstack->s[++stptr] = sum / count;
+ } else {
+ rpnstack->s[++stptr] = DNAN;
+ }
+ }
+ break;
+ case OP_ABS:
+ stackunderflow(0);
+ rpnstack->s[stptr] = fabs(rpnstack->s[stptr]);
+ break;
+ case OP_END:
+ break;
+ }
+#undef stackunderflow
+ }
+ if (stptr != 0) {
+ rrd_set_error("RPN final stack size != 1");
+ return -1;
+ }
+
+ output[output_idx] = rpnstack->s[0];
+ return 0;
+}
+
+/* figure out what the local timezone offset for any point in
+ time was. Return it in seconds */
+int tzoffset(
+ time_t now)
+{
+ int gm_sec, gm_min, gm_hour, gm_yday, gm_year,
+ l_sec, l_min, l_hour, l_yday, l_year;
+ struct tm t;
+ int off;
+
+ gmtime_r(&now, &t);
+ gm_sec = t.tm_sec;
+ gm_min = t.tm_min;
+ gm_hour = t.tm_hour;
+ gm_yday = t.tm_yday;
+ gm_year = t.tm_year;
+ localtime_r(&now, &t);
+ l_sec = t.tm_sec;
+ l_min = t.tm_min;
+ l_hour = t.tm_hour;
+ l_yday = t.tm_yday;
+ l_year = t.tm_year;
+ off =
+ (l_sec - gm_sec) + (l_min - gm_min) * 60 + (l_hour - gm_hour) * 3600;
+ if (l_yday > gm_yday || l_year > gm_year) {
+ off += 24 * 3600;
+ } else if (l_yday < gm_yday || l_year < gm_year) {
+ off -= 24 * 3600;
+ }
+ return off;
+}
diff --git a/program/src/rrd_rpncalc.h b/program/src/rrd_rpncalc.h
--- /dev/null
@@ -0,0 +1,86 @@
+/****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ ****************************************************************************
+ * rrd_rpncalc.h RPN calculator functions
+ ****************************************************************************/
+#ifndef _RRD_RPNCALC_H
+#define _RRD_RPNCALC_H
+
+/* WARNING: if new operators are added, they MUST be added at the very end of the list.
+ * This is because COMPUTE (CDEF) DS store OP nodes by number (name is not
+ * an option due to limited par array size). OP nodes must have the same
+ * numeric values, otherwise the stored numbers will mean something different. */
+enum op_en { OP_NUMBER = 0, OP_VARIABLE, OP_INF, OP_PREV, OP_NEGINF,
+ OP_UNKN, OP_NOW, OP_TIME, OP_ADD, OP_MOD, OP_SUB, OP_MUL,
+ OP_DIV, OP_SIN, OP_DUP, OP_EXC, OP_POP,
+ OP_COS, OP_LOG, OP_EXP, OP_LT, OP_LE, OP_GT, OP_GE, OP_EQ, OP_IF,
+ OP_MIN, OP_MAX, OP_LIMIT, OP_FLOOR, OP_CEIL,
+ OP_UN, OP_END, OP_LTIME, OP_NE, OP_ISINF, OP_PREV_OTHER, OP_COUNT,
+ OP_ATAN, OP_SQRT, OP_SORT, OP_REV, OP_TREND, OP_TRENDNAN,
+ OP_ATAN2, OP_RAD2DEG, OP_DEG2RAD,
+ OP_AVG, OP_ABS, OP_ADDNAN
+};
+
+typedef struct rpnp_t {
+ enum op_en op;
+ double val; /* value for a OP_NUMBER */
+ long ptr; /* pointer into the gdes array for OP_VAR */
+ double *data; /* pointer to the current value from OP_VAR DAS */
+ long ds_cnt; /* data source count for data pointer */
+ long step; /* time step for OP_VAR das */
+} rpnp_t;
+
+/* a compact representation of rpnp_t for computed data sources */
+typedef struct rpn_cdefds_t {
+ char op; /* rpn operator type */
+ short val; /* used by OP_NUMBER and OP_VARIABLE */
+} rpn_cdefds_t;
+
+#define MAX_VNAME_LEN 255
+#define DEF_NAM_FMT "%255[-_A-Za-z0-9]"
+
+/* limit imposed by sizeof(rpn_cdefs_t) and rrd.ds_def.par */
+#define DS_CDEF_MAX_RPN_NODES 26
+
+typedef struct rpnstack_t {
+ double *s;
+ long dc_stacksize;
+ long dc_stackblock;
+} rpnstack_t;
+
+void rpnstack_init(
+ rpnstack_t *rpnstack);
+void rpnstack_free(
+ rpnstack_t *rpnstack);
+
+void parseCDEF_DS(
+ const char *def,
+ rrd_t *rrd,
+ int ds_idx);
+long lookup_DS(
+ void *rrd_vptr,
+ char *ds_name);
+
+short rpn_compact(
+ rpnp_t *rpnp,
+ rpn_cdefds_t **rpnc,
+ short *count);
+rpnp_t *rpn_expand(
+ rpn_cdefds_t *rpnc);
+void rpn_compact2str(
+ rpn_cdefds_t *rpnc,
+ ds_def_t *ds_def,
+ char **str);
+rpnp_t *rpn_parse(
+ void *key_hash,
+ const char *const expr,
+ long (*lookup) (void *,
+ char *));
+short rpn_calc(
+ rpnp_t *rpnp,
+ rpnstack_t *rpnstack,
+ long data_idx,
+ rrd_value_t *output,
+ int output_idx);
+
+#endif
diff --git a/program/src/rrd_thread_safe.c b/program/src/rrd_thread_safe.c
--- /dev/null
@@ -0,0 +1,83 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ * This file: Copyright 2003 Peter Stamfest <peter@stamfest.at>
+ * & Tobias Oetiker
+ * Distributed under the GPL
+ *****************************************************************************
+ * rrd_thread_safe.c Contains routines used when thread safety is required
+ *****************************************************************************
+ * $Id$
+ *************************************************************************** */
+
+#include <pthread.h>
+#include <string.h>
+/* #include <error.h> */
+#include "rrd.h"
+#include "rrd_tool.h"
+
+/* Key for the thread-specific rrd_context */
+static pthread_key_t context_key;
+
+/* Once-only initialisation of the key */
+static pthread_once_t context_key_once = PTHREAD_ONCE_INIT;
+
+/* Free the thread-specific rrd_context - we might actually use
+ rrd_free_context instead...
+ */
+static void context_destroy_context(
+ void *ctx_)
+{
+ rrd_context_t *ctx = ctx_;
+
+ if (ctx)
+ rrd_free_context(ctx);
+}
+
+/* Allocate the key */
+static void context_get_key(
+ void)
+{
+ pthread_key_create(&context_key, context_destroy_context);
+}
+
+rrd_context_t *rrd_get_context(
+ void)
+{
+ rrd_context_t *ctx;
+
+ pthread_once(&context_key_once, context_get_key);
+ ctx = pthread_getspecific(context_key);
+ if (!ctx) {
+ ctx = rrd_new_context();
+ pthread_setspecific(context_key, ctx);
+ }
+ return ctx;
+}
+
+#ifdef HAVE_STRERROR_R
+const char *rrd_strerror(
+ int err)
+{
+ rrd_context_t *ctx = rrd_get_context();
+
+ if (strerror_r(err, ctx->lib_errstr, sizeof(ctx->lib_errstr)))
+ return "strerror_r failed. sorry!";
+ else
+ return ctx->lib_errstr;
+}
+#else
+#undef strerror
+const char *rrd_strerror(
+ int err)
+{
+ static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
+ rrd_context_t *ctx;
+
+ ctx = rrd_get_context();
+ pthread_mutex_lock(&mtx);
+ strncpy(ctx->lib_errstr, strerror(err), sizeof(ctx->lib_errstr));
+ ctx->lib_errstr[sizeof(ctx->lib_errstr) - 1] = '\0';
+ pthread_mutex_unlock(&mtx);
+ return ctx->lib_errstr;
+}
+#endif
diff --git a/program/src/rrd_thread_safe_nt.c b/program/src/rrd_thread_safe_nt.c
--- /dev/null
@@ -0,0 +1,164 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ * This file: Copyright 2003 Peter Stamfest <peter@stamfest.at>
+ * & Tobias Oetiker
+ * Distributed under the GPL
+ *****************************************************************************
+ * rrd_thread_safe.c Contains routines used when thread safety is required
+ * for win32
+ *****************************************************************************
+ * $Id$
+ *************************************************************************** */
+
+#include <windows.h>
+#include <string.h>
+/* #include <error.h> */
+#include "rrd.h"
+#include "rrd_tool.h"
+
+/* Key for the thread-specific rrd_context */
+static DWORD context_key;
+static CRITICAL_SECTION CriticalSection;
+
+
+/* Once-only initialisation of the key */
+static DWORD context_key_once = 0;
+
+
+/* Free the thread-specific rrd_context - we might actually use
+ rrd_free_context instead...
+ */
+static void context_destroy_context(
+ void)
+{
+ DeleteCriticalSection(&CriticalSection);
+ TlsFree(context_key);
+ context_key_once = 0;
+}
+static void context_init_context(
+ void)
+{
+ if (!InterlockedExchange(&context_key_once, 1)) {
+ context_key = TlsAlloc();
+ InitializeCriticalSection(&CriticalSection);
+ atexit(context_destroy_context);
+ }
+}
+rrd_context_t *rrd_get_context(
+ void)
+{
+ rrd_context_t *ctx;
+
+ context_init_context();
+
+ ctx = TlsGetValue(context_key);
+ if (!ctx) {
+ ctx = rrd_new_context();
+ TlsSetValue(context_key, ctx);
+ }
+ return ctx;
+}
+
+#undef strerror
+const char *rrd_strerror(
+ int err)
+{
+ rrd_context_t *ctx;
+
+ context_init_context();
+
+ ctx = rrd_get_context();
+
+ EnterCriticalSection(&CriticalSection);
+ strncpy(ctx->lib_errstr, strerror(err), sizeof(ctx->lib_errstr));
+ ctx->lib_errstr[sizeof(ctx->lib_errstr) - 1] = '\0';
+ LeaveCriticalSection(&CriticalSection);
+
+ return ctx->lib_errstr;
+}
+
+/*
+ * there much be a re-entrant version of these somewhere in win32 land
+ */
+struct tm *localtime_r(
+ const time_t *timep,
+ struct tm *result)
+{
+ struct tm *local;
+
+ context_init_context();
+
+ EnterCriticalSection(&CriticalSection);
+ local = localtime(timep);
+ memcpy(result, local, sizeof(struct tm));
+ LeaveCriticalSection(&CriticalSection);
+ return result;
+}
+
+char *ctime_r(
+ const time_t *timep,
+ char *result)
+{
+ char *local;
+
+ context_init_context();
+
+ EnterCriticalSection(&CriticalSection);
+ local = ctime(timep);
+ strcpy(result, local);
+ LeaveCriticalSection(&CriticalSection);
+ return result;
+}
+
+struct tm *gmtime_r(
+ const time_t *timep,
+ struct tm *result)
+{
+ struct tm *local;
+
+ context_init_context();
+
+ EnterCriticalSection(&CriticalSection);
+ local = gmtime(timep);
+ memcpy(result, local, sizeof(struct tm));
+ LeaveCriticalSection(&CriticalSection);
+ return result;
+}
+
+/* implementation from Apache's APR library */
+char *strtok_r(
+ char *str,
+ const char *sep,
+ char **last)
+{
+ char *token;
+
+ context_init_context();
+
+
+ if (!str) /* subsequent call */
+ str = *last; /* start where we left off */
+
+ /* skip characters in sep (will terminate at '\0') */
+ while (*str && strchr(sep, *str))
+ ++str;
+
+ if (!*str) /* no more tokens */
+ return NULL;
+
+ token = str;
+
+ /* skip valid token characters to terminate token and
+ * prepare for the next call (will terminate at '\0)
+ */
+ *last = token + 1;
+ while (**last && !strchr(sep, **last))
+ ++ * last;
+
+ if (**last) {
+ **last = '\0';
+ ++*last;
+ }
+
+ return token;
+}
diff --git a/program/src/rrd_tool.c b/program/src/rrd_tool.c
--- /dev/null
+++ b/program/src/rrd_tool.c
@@ -0,0 +1,921 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ *****************************************************************************
+ * rrd_tool.c Startup wrapper
+ *****************************************************************************/
+
+#if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__) && !defined(HAVE_CONFIG_H)
+#include "../win32/config.h"
+#else
+#ifdef HAVE_CONFIG_H
+#include "../rrd_config.h"
+#endif
+#endif
+
+#include "rrd_tool.h"
+#include "rrd_xport.h"
+#include "rrd_i18n.h"
+
+#ifdef HAVE_LOCALE_H
+#include <locale.h>
+#endif
+
+void PrintUsage(
+ char *cmd);
+int CountArgs(
+ char *aLine);
+int CreateArgs(
+ char *,
+ char *,
+ int,
+ char **);
+int HandleInputLine(
+ int,
+ char **,
+ FILE *);
+int RemoteMode = 0;
+int ChangeRoot = 0;
+
+#define TRUE 1
+#define FALSE 0
+#define MAX_LENGTH 10000
+
+
+void PrintUsage(
+ char *cmd)
+{
+
+ const char *help_main =
+ N_("RRDtool %s"
+ " Copyright 1997-2008 by Tobias Oetiker <tobi@oetiker.ch>\n"
+ " Compiled %s %s\n\n"
+ "Usage: rrdtool [options] command command_options\n\n");
+
+ const char *help_list =
+ N_
+ ("Valid commands: create, update, updatev, graph, graphv, dump, restore,\n"
+ "\t\tlast, lastupdate, first, info, fetch, tune,\n"
+ "\t\tresize, xport\n\n");
+
+ const char *help_listremote =
+ N_("Valid remote commands: quit, ls, cd, mkdir, pwd\n\n");
+
+
+ const char *help_create =
+ N_("* create - create a new RRD\n\n"
+ "\trrdtool create filename [--start|-b start time]\n"
+ "\t\t[--step|-s step]\n"
+ "\t\t[DS:ds-name:DST:dst arguments]\n"
+ "\t\t[RRA:CF:cf arguments]\n\n");
+
+ const char *help_dump =
+ N_("* dump - dump an RRD to XML\n\n"
+ "\trrdtool dump filename.rrd >filename.xml\n\n");
+
+ const char *help_info =
+ N_("* info - returns the configuration and status of the RRD\n\n"
+ "\trrdtool info filename.rrd\n\n");
+
+ const char *help_restore =
+ N_("* restore - restore an RRD file from its XML form\n\n"
+ "\trrdtool restore [--range-check|-r] [--force-overwrite|-f] filename.xml filename.rrd\n\n");
+
+ const char *help_last =
+ N_("* last - show last update time for RRD\n\n"
+ "\trrdtool last filename.rrd\n\n");
+
+ const char *help_lastupdate =
+ N_("* lastupdate - returns the most recent datum stored for\n"
+ " each DS in an RRD\n\n" "\trrdtool lastupdate filename.rrd\n\n");
+
+ const char *help_first =
+ N_("* first - show first update time for RRA within an RRD\n\n"
+ "\trrdtool first filename.rrd [--rraindex number]\n\n");
+
+ const char *help_update =
+ N_("* update - update an RRD\n\n"
+ "\trrdtool update filename\n"
+ "\t\t--template|-t ds-name:ds-name:...\n"
+ "\t\ttime|N:value[:value...]\n\n"
+ "\t\tat-time@value[:value...]\n\n"
+ "\t\t[ time:value[:value...] ..]\n\n");
+
+ const char *help_updatev =
+ N_("* updatev - a verbose version of update\n"
+ "\treturns information about values, RRAs, and datasources updated\n\n"
+ "\trrdtool updatev filename\n"
+ "\t\t--template|-t ds-name:ds-name:...\n"
+ "\t\ttime|N:value[:value...]\n\n"
+ "\t\tat-time@value[:value...]\n\n"
+ "\t\t[ time:value[:value...] ..]\n\n");
+
+ const char *help_fetch =
+ N_("* fetch - fetch data out of an RRD\n\n"
+ "\trrdtool fetch filename.rrd CF\n"
+ "\t\t[-r|--resolution resolution]\n"
+ "\t\t[-s|--start start] [-e|--end end]\n\n");
+
+/* break up very large strings (help_graph, help_tune) for ISO C89 compliance*/
+
+ const char *help_graph0 =
+ N_("* graph - generate a graph from one or several RRD\n\n"
+ "\trrdtool graph filename [-s|--start seconds] [-e|--end seconds]\n");
+ const char *help_graphv0 =
+ N_("* graphv - generate a graph from one or several RRD\n"
+ " with meta data printed before the graph\n\n"
+ "\trrdtool graphv filename [-s|--start seconds] [-e|--end seconds]\n");
+ const char *help_graph1 =
+ N_("\t\t[-x|--x-grid x-axis grid and label]\n"
+ "\t\t[-Y|--alt-y-grid]\n"
+ "\t\t[-y|--y-grid y-axis grid and label]\n"
+ "\t\t[-v|--vertical-label string] [-w|--width pixels]\n"
+ "\t\t[-h|--height pixels] [-o|--logarithmic]\n"
+ "\t\t[-u|--upper-limit value] [-z|--lazy]\n"
+ "\t\t[-l|--lower-limit value] [-r|--rigid]\n"
+ "\t\t[-g|--no-legend]\n"
+ "\t\t[-F|--force-rules-legend]\n" "\t\t[-j|--only-graph]\n");
+ const char *help_graph2 =
+ N_("\t\t[-n|--font FONTTAG:size:font]\n"
+ "\t\t[-m|--zoom factor]\n"
+ "\t\t[-A|--alt-autoscale]\n"
+ "\t\t[-M|--alt-autoscale-max]\n"
+ "\t\t[-R|--font-render-mode {normal,light,mono}]\n"
+ "\t\t[-B|--font-smoothing-threshold size]\n"
+ "\t\t[-T|--tabwidth width]\n"
+ "\t\t[-E|--slope-mode]\n"
+ "\t\t[-N|--no-gridfit]\n"
+ "\t\t[-X|--units-exponent value]\n"
+ "\t\t[-L|--units-length value]\n"
+ "\t\t[-S|--step seconds]\n"
+ "\t\t[-f|--imginfo printfstr]\n"
+ "\t\t[-a|--imgformat PNG]\n"
+ "\t\t[-c|--color COLORTAG#rrggbb[aa]] [-t|--title string]\n"
+ "\t\t[-W|--watermark string]\n"
+ "\t\t[DEF:vname=rrd:ds-name:CF]\n");
+ const char *help_graph3 =
+ N_("\t\t[CDEF:vname=rpn-expression]\n"
+ "\t\t[VDEF:vdefname=rpn-expression]\n"
+ "\t\t[PRINT:vdefname:format]\n"
+ "\t\t[GPRINT:vdefname:format]\n" "\t\t[COMMENT:text]\n"
+ "\t\t[SHIFT:vname:offset]\n"
+ "\t\t[TICK:vname#rrggbb[aa][:[fraction][:legend]]]\n"
+ "\t\t[HRULE:value#rrggbb[aa][:legend]]\n"
+ "\t\t[VRULE:value#rrggbb[aa][:legend]]\n"
+ "\t\t[LINE[width]:vname[#rrggbb[aa][:[legend][:STACK]]]]\n"
+ "\t\t[AREA:vname[#rrggbb[aa][:[legend][:STACK]]]]\n"
+ "\t\t[PRINT:vname:CF:format] (deprecated)\n"
+ "\t\t[GPRINT:vname:CF:format] (deprecated)\n"
+ "\t\t[STACK:vname[#rrggbb[aa][:legend]]] (deprecated)\n\n");
+ const char *help_tune1 =
+ N_(" * tune - Modify some basic properties of an RRD\n\n"
+ "\trrdtool tune filename\n"
+ "\t\t[--heartbeat|-h ds-name:heartbeat]\n"
+ "\t\t[--data-source-type|-d ds-name:DST]\n"
+ "\t\t[--data-source-rename|-r old-name:new-name]\n"
+ "\t\t[--minimum|-i ds-name:min] [--maximum|-a ds-name:max]\n"
+ "\t\t[--deltapos scale-value] [--deltaneg scale-value]\n"
+ "\t\t[--failure-threshold integer]\n"
+ "\t\t[--window-length integer]\n"
+ "\t\t[--alpha adaptation-parameter]\n");
+ const char *help_tune2 =
+ N_(" * tune - Modify some basic properties of an RRD\n\n"
+ "\t\t[--beta adaptation-parameter]\n"
+ "\t\t[--gamma adaptation-parameter]\n"
+ "\t\t[--gamma-deviation adaptation-parameter]\n"
+ "\t\t[--aberrant-reset ds-name]\n\n");
+ const char *help_resize =
+ N_
+ (" * resize - alter the length of one of the RRAs in an RRD\n\n"
+ "\trrdtool resize filename rranum GROW|SHRINK rows\n\n");
+ const char *help_xport =
+ N_("* xport - generate XML dump from one or several RRD\n\n"
+ "\trrdtool xport [-s|--start seconds] [-e|--end seconds]\n"
+ "\t\t[-m|--maxrows rows]\n" "\t\t[--step seconds]\n"
+ "\t\t[--enumds]\n" "\t\t[DEF:vname=rrd:ds-name:CF]\n"
+ "\t\t[CDEF:vname=rpn-expression]\n"
+ "\t\t[XPORT:vname:legend]\n\n");
+ const char *help_quit =
+ N_(" * quit - closing a session in remote mode\n\n"
+ "\trrdtool quit\n\n");
+ const char *help_ls =
+ N_(" * ls - lists all *.rrd files in current directory\n\n"
+ "\trrdtool ls\n\n");
+ const char *help_cd =
+ N_(" * cd - changes the current directory\n\n"
+ "\trrdtool cd new directory\n\n");
+ const char *help_mkdir =
+ N_(" * mkdir - creates a new directory\n\n"
+ "\trrdtool mkdir newdirectoryname\n\n");
+ const char *help_pwd =
+ N_(" * pwd - returns the current working directory\n\n"
+ "\trrdtool pwd\n\n");
+ const char *help_lic =
+ N_("RRDtool is distributed under the Terms of the GNU General\n"
+ "Public License Version 2. (www.gnu.org/copyleft/gpl.html)\n\n"
+ "For more information read the RRD manpages\n\n");
+ enum { C_NONE, C_CREATE, C_DUMP, C_INFO, C_RESTORE, C_LAST,
+ C_LASTUPDATE, C_FIRST, C_UPDATE, C_FETCH, C_GRAPH, C_GRAPHV,
+ C_TUNE,
+ C_RESIZE, C_XPORT, C_QUIT, C_LS, C_CD, C_MKDIR, C_PWD,
+ C_UPDATEV
+ };
+ int help_cmd = C_NONE;
+
+ if (cmd) {
+ if (!strcmp(cmd, "create"))
+ help_cmd = C_CREATE;
+ else if (!strcmp(cmd, "dump"))
+ help_cmd = C_DUMP;
+ else if (!strcmp(cmd, "info"))
+ help_cmd = C_INFO;
+ else if (!strcmp(cmd, "restore"))
+ help_cmd = C_RESTORE;
+ else if (!strcmp(cmd, "last"))
+ help_cmd = C_LAST;
+ else if (!strcmp(cmd, "lastupdate"))
+ help_cmd = C_LASTUPDATE;
+ else if (!strcmp(cmd, "first"))
+ help_cmd = C_FIRST;
+ else if (!strcmp(cmd, "update"))
+ help_cmd = C_UPDATE;
+ else if (!strcmp(cmd, "updatev"))
+ help_cmd = C_UPDATEV;
+ else if (!strcmp(cmd, "fetch"))
+ help_cmd = C_FETCH;
+ else if (!strcmp(cmd, "graph"))
+ help_cmd = C_GRAPH;
+ else if (!strcmp(cmd, "graphv"))
+ help_cmd = C_GRAPHV;
+ else if (!strcmp(cmd, "tune"))
+ help_cmd = C_TUNE;
+ else if (!strcmp(cmd, "resize"))
+ help_cmd = C_RESIZE;
+ else if (!strcmp(cmd, "xport"))
+ help_cmd = C_XPORT;
+ else if (!strcmp(cmd, "quit"))
+ help_cmd = C_QUIT;
+ else if (!strcmp(cmd, "ls"))
+ help_cmd = C_LS;
+ else if (!strcmp(cmd, "cd"))
+ help_cmd = C_CD;
+ else if (!strcmp(cmd, "mkdir"))
+ help_cmd = C_MKDIR;
+ else if (!strcmp(cmd, "pwd"))
+ help_cmd = C_PWD;
+ }
+ fprintf(stdout, _(help_main), PACKAGE_VERSION, __DATE__, __TIME__);
+ fflush(stdout);
+ switch (help_cmd) {
+ case C_NONE:
+ fputs(_(help_list), stdout);
+ if (RemoteMode) {
+ fputs(_(help_listremote), stdout);
+ }
+ break;
+ case C_CREATE:
+ fputs(_(help_create), stdout);
+ break;
+ case C_DUMP:
+ fputs(_(help_dump), stdout);
+ break;
+ case C_INFO:
+ fputs(_(help_info), stdout);
+ break;
+ case C_RESTORE:
+ fputs(_(help_restore), stdout);
+ break;
+ case C_LAST:
+ fputs(_(help_last), stdout);
+ break;
+ case C_LASTUPDATE:
+ fputs(_(help_lastupdate), stdout);
+ break;
+ case C_FIRST:
+ fputs(_(help_first), stdout);
+ break;
+ case C_UPDATE:
+ fputs(_(help_update), stdout);
+ break;
+ case C_UPDATEV:
+ fputs(_(help_updatev), stdout);
+ break;
+ case C_FETCH:
+ fputs(_(help_fetch), stdout);
+ break;
+ case C_GRAPH:
+ fputs(_(help_graph0), stdout);
+ fputs(_(help_graph1), stdout);
+ fputs(_(help_graph2), stdout);
+ fputs(_(help_graph3), stdout);
+ break;
+ case C_GRAPHV:
+ fputs(_(help_graphv0), stdout);
+ fputs(_(help_graph1), stdout);
+ fputs(_(help_graph2), stdout);
+ fputs(_(help_graph3), stdout);
+ break;
+ case C_TUNE:
+ fputs(_(help_tune1), stdout);
+ fputs(_(help_tune2), stdout);
+ break;
+ case C_RESIZE:
+ fputs(_(help_resize), stdout);
+ break;
+ case C_XPORT:
+ fputs(_(help_xport), stdout);
+ break;
+ case C_QUIT:
+ fputs(_(help_quit), stdout);
+ break;
+ case C_LS:
+ fputs(_(help_ls), stdout);
+ break;
+ case C_CD:
+ fputs(_(help_cd), stdout);
+ break;
+ case C_MKDIR:
+ fputs(_(help_mkdir), stdout);
+ break;
+ case C_PWD:
+ fputs(_(help_pwd), stdout);
+ break;
+ }
+ fputs(_(help_lic), stdout);
+}
+
+static char *fgetslong(
+ char **aLinePtr,
+ FILE * stream)
+{
+ char *linebuf;
+ size_t bufsize = MAX_LENGTH;
+ int eolpos = 0;
+
+ if (feof(stream))
+ return *aLinePtr = 0;
+ if (!(linebuf = malloc(bufsize))) {
+ perror("fgetslong: malloc");
+ exit(1);
+ }
+ linebuf[0] = '\0';
+ while (fgets(linebuf + eolpos, MAX_LENGTH, stream)) {
+ eolpos += strlen(linebuf + eolpos);
+ if (linebuf[eolpos - 1] == '\n')
+ return *aLinePtr = linebuf;
+ bufsize += MAX_LENGTH;
+ if (!(linebuf = realloc(linebuf, bufsize))) {
+ free(linebuf);
+ perror("fgetslong: realloc");
+ exit(1);
+ }
+ }
+ if (linebuf[0]){
+ return *aLinePtr = linebuf;
+ }
+ free(linebuf);
+ return *aLinePtr = 0;
+}
+
+int main(
+ int argc,
+ char *argv[])
+{
+ char **myargv;
+ char *aLine;
+ char *firstdir = "";
+
+#ifdef MUST_DISABLE_SIGFPE
+ signal(SIGFPE, SIG_IGN);
+#endif
+#ifdef MUST_DISABLE_FPMASK
+ fpsetmask(0);
+#endif
+#ifdef HAVE_LOCALE_H
+ setlocale(LC_ALL, "");
+#endif
+
+#if defined(HAVE_LIBINTL_H) && defined(BUILD_LIBINTL)
+ bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
+ bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+ textdomain(GETTEXT_PACKAGE);
+#endif
+ if (argc == 1) {
+ PrintUsage("");
+ return 0;
+ }
+
+ if (((argc == 2) || (argc == 3)) && !strcmp("-", argv[1])) {
+#if HAVE_GETRUSAGE
+ struct rusage myusage;
+ struct timeval starttime;
+ struct timeval currenttime;
+
+ gettimeofday(&starttime, NULL);
+#endif
+ RemoteMode = 1;
+ if ((argc == 3) && strcmp("", argv[2])) {
+
+ if (
+#ifdef HAVE_GETUID
+ getuid()
+#else
+ 1
+#endif
+ == 0) {
+
+#ifdef HAVE_CHROOT
+ if (chroot(argv[2]) != 0){
+ fprintf(stderr, "ERROR: chroot %s: %s\n", argv[2],rrd_strerror(errno));
+ exit(errno);
+ }
+ ChangeRoot = 1;
+ firstdir = "/";
+#else
+ fprintf(stderr,
+ "ERROR: change root is not supported by your OS "
+ "or at least by this copy of rrdtool\n");
+ exit(1);
+#endif
+ } else {
+ firstdir = argv[2];
+ }
+ }
+ if (strcmp(firstdir, "")) {
+ if (chdir(firstdir) != 0){
+ fprintf(stderr, "ERROR: chdir %s %s\n", firstdir,rrd_strerror(errno));
+ exit(errno);
+ }
+ }
+
+ while (fgetslong(&aLine, stdin)) {
+ if ((argc = CountArgs(aLine)) == 0) {
+ free(aLine);
+ printf("ERROR: not enough arguments\n");
+ }
+ if ((myargv = (char **) malloc((argc + 1) *
+ sizeof(char *))) == NULL) {
+ perror("malloc");
+ exit(1);
+ }
+ if ((argc = CreateArgs(argv[0], aLine, argc, myargv)) < 0) {
+ free(aLine);
+ free(myargv);
+ printf("ERROR: creating arguments\n");
+ } else {
+ int ret = HandleInputLine(argc, myargv, stdout);
+
+ free(myargv);
+ if (ret == 0) {
+#if HAVE_GETRUSAGE
+ getrusage(RUSAGE_SELF, &myusage);
+ gettimeofday(¤ttime, NULL);
+ printf("OK u:%1.2f s:%1.2f r:%1.2f\n",
+ (double) myusage.ru_utime.tv_sec +
+ (double) myusage.ru_utime.tv_usec / 1000000.0,
+ (double) myusage.ru_stime.tv_sec +
+ (double) myusage.ru_stime.tv_usec / 1000000.0,
+ (double) (currenttime.tv_sec - starttime.tv_sec)
+ + (double) (currenttime.tv_usec -
+ starttime.tv_usec)
+ / 1000000.0);
+#else
+ printf("OK\n");
+#endif
+ }
+ }
+ fflush(stdout); /* this is important for pipes to work */
+ free(aLine);
+ }
+ } else if (argc == 2) {
+ PrintUsage(argv[1]);
+ exit(0);
+ } else if (argc == 3 && !strcmp(argv[1], "help")) {
+ PrintUsage(argv[2]);
+ exit(0);
+ } else {
+ exit(HandleInputLine(argc, argv, stderr));
+ }
+ return 0;
+}
+
+/* HandleInputLine is NOT thread safe - due to readdir issues,
+ resolving them portably is not really simple. */
+int HandleInputLine(
+ int argc,
+ char **argv,
+ FILE * out)
+{
+#if defined(HAVE_OPENDIR) && defined (HAVE_READDIR)
+ DIR *curdir; /* to read current dir with ls */
+ struct dirent *dent;
+#endif
+#if defined(HAVE_SYS_STAT_H)
+ struct stat st;
+#endif
+ char *cwd; /* To hold current working dir on call to pwd */
+
+ /* Reset errno to 0 before we start.
+ */
+ if (RemoteMode) {
+ if (argc > 1 && strcmp("quit", argv[1]) == 0) {
+ if (argc > 2) {
+ printf("ERROR: invalid parameter count for quit\n");
+ return (1);
+ }
+ exit(0);
+ }
+#if defined(HAVE_OPENDIR) && defined(HAVE_READDIR) && defined(HAVE_CHDIR)
+ if (argc > 1 && strcmp("cd", argv[1]) == 0) {
+ if (argc > 3) {
+ printf("ERROR: invalid parameter count for cd\n");
+ return (1);
+ }
+#if ! defined(HAVE_CHROOT) || ! defined(HAVE_GETUID)
+ if (getuid() == 0 && !ChangeRoot) {
+ printf
+ ("ERROR: chdir security problem - rrdtool is running as "
+ "root but not chroot!\n");
+ return (1);
+ }
+#endif
+ if (chdir(argv[2]) != 0){
+ printf("ERROR: chdir %s %s\n", argv[2], rrd_strerror(errno));
+ return (1);
+ }
+ return (0);
+ }
+ if (argc > 1 && strcmp("pwd", argv[1]) == 0) {
+ if (argc > 2) {
+ printf("ERROR: invalid parameter count for pwd\n");
+ return (1);
+ }
+ cwd = getcwd(NULL, MAXPATH);
+ if (cwd == NULL) {
+ printf("ERROR: getcwd %s\n", rrd_strerror(errno));
+ return (1);
+ }
+ printf("%s\n", cwd);
+ free(cwd);
+ return (0);
+ }
+ if (argc > 1 && strcmp("mkdir", argv[1]) == 0) {
+ if (argc > 3) {
+ printf("ERROR: invalid parameter count for mkdir\n");
+ return (1);
+ }
+#if ! defined(HAVE_CHROOT) || ! defined(HAVE_GETUID)
+ if (getuid() == 0 && !ChangeRoot) {
+ printf
+ ("ERROR: mkdir security problem - rrdtool is running as "
+ "root but not chroot!\n");
+ return (1);
+ }
+#endif
+ if(mkdir(argv[2], 0777)!=0){
+ printf("ERROR: mkdir %s: %s\n", argv[2],rrd_strerror(errno));
+ return (1);
+ }
+ return (0);
+ }
+ if (argc > 1 && strcmp("ls", argv[1]) == 0) {
+ if (argc > 2) {
+ printf("ERROR: invalid parameter count for ls\n");
+ return (1);
+ }
+ if ((curdir = opendir(".")) != NULL) {
+ while ((dent = readdir(curdir)) != NULL) {
+ if (!stat(dent->d_name, &st)) {
+ if (S_ISDIR(st.st_mode)) {
+ printf("d %s\n", dent->d_name);
+ }
+ if (strlen(dent->d_name) > 4 && S_ISREG(st.st_mode)) {
+ if (!strcmp
+ (dent->d_name + NAMLEN(dent) - 4, ".rrd")
+ || !strcmp(dent->d_name + NAMLEN(dent) - 4,
+ ".RRD")) {
+ printf("- %s\n", dent->d_name);
+ }
+ }
+ }
+ }
+ closedir(curdir);
+ } else {
+ printf("ERROR: opendir .: %s\n", rrd_strerror(errno));
+ return (errno);
+ }
+ return (0);
+ }
+#endif /* opendir and readdir */
+
+ }
+ if (argc < 3
+ || strcmp("help", argv[1]) == 0
+ || strcmp("--help", argv[1]) == 0
+ || strcmp("-help", argv[1]) == 0
+ || strcmp("-?", argv[1]) == 0 || strcmp("-h", argv[1]) == 0) {
+ PrintUsage("");
+ return 0;
+ }
+
+ if (strcmp("create", argv[1]) == 0)
+ rrd_create(argc - 1, &argv[1]);
+ else if (strcmp("dump", argv[1]) == 0)
+ rrd_dump(argc - 1, &argv[1]);
+ else if (strcmp("info", argv[1]) == 0 || strcmp("updatev", argv[1]) == 0) {
+ rrd_info_t *data;
+
+ if (strcmp("info", argv[1]) == 0)
+
+ data = rrd_info(argc - 1, &argv[1]);
+ else
+ data = rrd_update_v(argc - 1, &argv[1]);
+ rrd_info_print(data);
+ rrd_info_free(data);
+ }
+
+ else if (strcmp("--version", argv[1]) == 0 ||
+ strcmp("version", argv[1]) == 0 ||
+ strcmp("v", argv[1]) == 0 ||
+ strcmp("-v", argv[1]) == 0 || strcmp("-version", argv[1]) == 0)
+ printf("RRDtool " PACKAGE_VERSION
+ " Copyright by Tobi Oetiker, 1997-2008 (%f)\n",
+ rrd_version());
+ else if (strcmp("restore", argv[1]) == 0)
+ rrd_restore(argc - 1, &argv[1]);
+ else if (strcmp("resize", argv[1]) == 0)
+ rrd_resize(argc - 1, &argv[1]);
+ else if (strcmp("last", argv[1]) == 0)
+ printf("%ld\n", rrd_last(argc - 1, &argv[1]));
+ else if (strcmp("lastupdate", argv[1]) == 0) {
+ time_t last_update;
+ char **ds_namv;
+ char **last_ds;
+ unsigned long ds_cnt, i;
+
+ if (rrd_lastupdate(argc - 1, &argv[1], &last_update,
+ &ds_cnt, &ds_namv, &last_ds) == 0) {
+ for (i = 0; i < ds_cnt; i++)
+ printf(" %s", ds_namv[i]);
+ printf("\n\n");
+ printf("%10lu:", last_update);
+ for (i = 0; i < ds_cnt; i++) {
+ printf(" %s", last_ds[i]);
+ free(last_ds[i]);
+ free(ds_namv[i]);
+ }
+ printf("\n");
+ free(last_ds);
+ free(ds_namv);
+ }
+ } else if (strcmp("first", argv[1]) == 0)
+ printf("%ld\n", rrd_first(argc - 1, &argv[1]));
+ else if (strcmp("update", argv[1]) == 0)
+ rrd_update(argc - 1, &argv[1]);
+ else if (strcmp("fetch", argv[1]) == 0) {
+ time_t start, end, ti;
+ unsigned long step, ds_cnt, i, ii;
+ rrd_value_t *data, *datai;
+ char **ds_namv;
+
+ if (rrd_fetch
+ (argc - 1, &argv[1], &start, &end, &step, &ds_cnt, &ds_namv,
+ &data) != -1) {
+ datai = data;
+ printf(" ");
+ for (i = 0; i < ds_cnt; i++)
+ printf("%20s", ds_namv[i]);
+ printf("\n\n");
+ for (ti = start + step; ti <= end; ti += step) {
+ printf("%10lu:", ti);
+ for (ii = 0; ii < ds_cnt; ii++)
+ printf(" %0.10e", *(datai++));
+ printf("\n");
+ }
+ for (i = 0; i < ds_cnt; i++)
+ free(ds_namv[i]);
+ free(ds_namv);
+ free(data);
+ }
+ } else if (strcmp("xport", argv[1]) == 0) {
+ int xxsize;
+ unsigned long int j = 0;
+ time_t start, end, ti;
+ unsigned long step, col_cnt, row_cnt;
+ rrd_value_t *data, *ptr;
+ char **legend_v;
+ int enumds = 0;
+ int i;
+ size_t vtag_s = strlen(COL_DATA_TAG) + 10;
+ char *vtag = malloc(vtag_s);
+
+ for (i = 2; i < argc; i++) {
+ if (strcmp("--enumds", argv[i]) == 0)
+ enumds = 1;
+ }
+
+ if (rrd_xport
+ (argc - 1, &argv[1], &xxsize, &start, &end, &step, &col_cnt,
+ &legend_v, &data) != -1) {
+ row_cnt = (end - start) / step;
+ ptr = data;
+ printf("<?xml version=\"1.0\" encoding=\"%s\"?>\n\n",
+ XML_ENCODING);
+ printf("<%s>\n", ROOT_TAG);
+ printf(" <%s>\n", META_TAG);
+ printf(" <%s>%lu</%s>\n", META_START_TAG,
+ (unsigned long) start + step, META_START_TAG);
+ printf(" <%s>%lu</%s>\n", META_STEP_TAG, step, META_STEP_TAG);
+ printf(" <%s>%lu</%s>\n", META_END_TAG, (unsigned long) end,
+ META_END_TAG);
+ printf(" <%s>%lu</%s>\n", META_ROWS_TAG, row_cnt,
+ META_ROWS_TAG);
+ printf(" <%s>%lu</%s>\n", META_COLS_TAG, col_cnt,
+ META_COLS_TAG);
+ printf(" <%s>\n", LEGEND_TAG);
+ for (j = 0; j < col_cnt; j++) {
+ char *entry = NULL;
+
+ entry = legend_v[j];
+ printf(" <%s>%s</%s>\n", LEGEND_ENTRY_TAG, entry,
+ LEGEND_ENTRY_TAG);
+ free(entry);
+ }
+ free(legend_v);
+ printf(" </%s>\n", LEGEND_TAG);
+ printf(" </%s>\n", META_TAG);
+ printf(" <%s>\n", DATA_TAG);
+ for (ti = start + step; ti <= end; ti += step) {
+ printf(" <%s>", DATA_ROW_TAG);
+ printf("<%s>%lu</%s>", COL_TIME_TAG, ti, COL_TIME_TAG);
+ for (j = 0; j < col_cnt; j++) {
+ rrd_value_t newval = DNAN;
+
+ if (enumds == 1)
+
+ snprintf(vtag, vtag_s, "%s%lu", COL_DATA_TAG, j);
+ else
+ snprintf(vtag, vtag_s, "%s", COL_DATA_TAG);
+ newval = *ptr;
+ if (isnan(newval)) {
+ printf("<%s>NaN</%s>", vtag, vtag);
+ } else {
+ printf("<%s>%0.10e</%s>", vtag, newval, vtag);
+ };
+ ptr++;
+ }
+ printf("</%s>\n", DATA_ROW_TAG);
+ }
+ free(data);
+ printf(" </%s>\n", DATA_TAG);
+ printf("</%s>\n", ROOT_TAG);
+ }
+ free(vtag);
+ } else if (strcmp("graph", argv[1]) == 0) {
+ char **calcpr;
+
+#ifdef notused /*XXX*/
+ const char *imgfile = argv[2]; /* rrd_graph changes argv pointer */
+#endif
+ int xsize, ysize;
+ double ymin, ymax;
+ int i;
+ int tostdout = (strcmp(argv[2], "-") == 0);
+ int imginfo = 0;
+
+ for (i = 2; i < argc; i++) {
+ if (strcmp(argv[i], "--imginfo") == 0
+ || strcmp(argv[i], "-f") == 0) {
+ imginfo = 1;
+ break;
+ }
+ }
+ if (rrd_graph
+ (argc - 1, &argv[1], &calcpr, &xsize, &ysize, NULL, &ymin,
+ &ymax) != -1) {
+ if (!tostdout && !imginfo)
+ printf("%dx%d\n", xsize, ysize);
+ if (calcpr) {
+ for (i = 0; calcpr[i]; i++) {
+ if (!tostdout)
+ printf("%s\n", calcpr[i]);
+ free(calcpr[i]);
+ }
+ free(calcpr);
+ }
+ }
+
+ } else if (strcmp("graphv", argv[1]) == 0) {
+ rrd_info_t *grinfo = NULL; /* 1 to distinguish it from the NULL that rrd_graph sends in */
+
+ grinfo = rrd_graph_v(argc - 1, &argv[1]);
+ if (grinfo) {
+ rrd_info_print(grinfo);
+ rrd_info_free(grinfo);
+ }
+
+ } else if (strcmp("tune", argv[1]) == 0)
+ rrd_tune(argc - 1, &argv[1]);
+ else {
+ rrd_set_error("unknown function '%s'", argv[1]);
+ }
+ if (rrd_test_error()) {
+ fprintf(out, "ERROR: %s\n", rrd_get_error());
+ rrd_clear_error();
+ return 1;
+ }
+ return (0);
+}
+
+int CountArgs(
+ char *aLine)
+{
+ int i = 0;
+ int aCount = 0;
+ int inarg = 0;
+
+ while (aLine[i] == ' ')
+ i++;
+ while (aLine[i] != 0) {
+ if ((aLine[i] == ' ') && inarg) {
+ inarg = 0;
+ }
+ if ((aLine[i] != ' ') && !inarg) {
+ inarg = 1;
+ aCount++;
+ }
+ i++;
+ }
+ return aCount;
+}
+
+/*
+ * CreateArgs - take a string (aLine) and tokenize
+ */
+int CreateArgs(
+ char *pName,
+ char *aLine,
+ int argc,
+ char **argv)
+{
+ char *getP, *putP;
+ char **pargv = argv;
+ char Quote = 0;
+ int inArg = 0;
+ int len;
+
+ len = strlen(aLine);
+ /* remove trailing space and newlines */
+ while (len && aLine[len] <= ' ') {
+ aLine[len] = 0;
+ len--;
+ }
+ /* sikp leading blanks */
+ while (*aLine && *aLine <= ' ')
+ aLine++;
+ pargv[0] = pName;
+ argc = 1;
+ getP = aLine;
+ putP = aLine;
+ while (*getP) {
+ switch (*getP) {
+ case ' ':
+ if (Quote) {
+ *(putP++) = *getP;
+ } else if (inArg) {
+ *(putP++) = 0;
+ inArg = 0;
+ }
+ break;
+ case '"':
+ case '\'':
+ if (Quote != 0) {
+ if (Quote == *getP)
+ Quote = 0;
+ else {
+ *(putP++) = *getP;
+ }
+ } else {
+ if (!inArg) {
+ pargv[argc++] = putP;
+ inArg = 1;
+ }
+ Quote = *getP;
+ }
+ break;
+ default:
+ if (!inArg) {
+ pargv[argc++] = putP;
+ inArg = 1;
+ }
+ *(putP++) = *getP;
+ break;
+ }
+ getP++;
+ }
+
+ *putP = '\0';
+ if (Quote)
+ return -1;
+ else
+ return argc;
+}
diff --git a/program/src/rrd_tool.h b/program/src/rrd_tool.h
--- /dev/null
+++ b/program/src/rrd_tool.h
@@ -0,0 +1,117 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ *****************************************************************************
+ * rrd_tool.h Common Header File
+ *****************************************************************************/
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef _RRD_TOOL_H
+#define _RRD_TOOL_H
+
+#ifdef HAVE_CONFIG_H
+#include "../rrd_config.h"
+#elif defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
+#include "../win32/config.h"
+#endif
+
+#include "rrd.h"
+
+#if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
+
+/* Win32 only includes */
+
+#include <float.h> /* for _isnan */
+#include <io.h> /* for chdir */
+
+ struct tm *localtime_r(
+ const time_t *timep,
+ struct tm *result);
+ char *ctime_r(
+ const time_t *timep,
+ char *result);
+ struct tm *gmtime_r(
+ const time_t *timep,
+ struct tm *result);
+ char *strtok_r(
+ char *str,
+ const char *sep,
+ char **last);
+
+#else
+
+/* unix-only includes */
+#if !defined isnan && !defined HAVE_ISNAN
+ int isnan(
+ double value);
+#endif
+
+#endif
+
+/* local include files -- need to be after the system ones */
+#ifdef HAVE_GETOPT_LONG
+#define _GNU_SOURCE
+#include <getopt.h>
+#else
+#include "rrd_getopt.h"
+#endif
+
+#include "rrd_format.h"
+
+#ifndef max
+#define max(a,b) ((a) > (b) ? (a) : (b))
+#endif
+
+#ifndef min
+#define min(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+#define DIM(x) (sizeof(x)/sizeof(x[0]))
+
+ char *sprintf_alloc(
+ char *,
+ ...);
+
+/* HELPER FUNCTIONS */
+
+ int PngSize(
+ FILE *,
+ long *,
+ long *);
+
+ int rrd_create_fn(
+ const char *file_name,
+ rrd_t *rrd);
+ int rrd_fetch_fn(
+ const char *filename,
+ enum cf_en cf_idx,
+ time_t *start,
+ time_t *end,
+ unsigned long *step,
+ unsigned long *ds_cnt,
+ char ***ds_namv,
+ rrd_value_t **data);
+
+#define RRD_READONLY (1<<0)
+#define RRD_READWRITE (1<<1)
+#define RRD_CREAT (1<<2)
+#define RRD_READAHEAD (1<<3)
+#define RRD_COPY (1<<4)
+
+ enum cf_en cf_conv(
+ const char *string);
+ enum dst_en dst_conv(
+ char *string);
+ long ds_match(
+ rrd_t *rrd,
+ char *ds_nam);
+ double rrd_diff(
+ char *a,
+ char *b);
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/program/src/rrd_tune.c b/program/src/rrd_tune.c
--- /dev/null
+++ b/program/src/rrd_tune.c
@@ -0,0 +1,466 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ *****************************************************************************
+ * change header parameters of an rrd
+ *****************************************************************************
+ * $Id$
+ * $Log$
+ * Revision 1.6 2004/05/26 22:11:12 oetiker
+ * reduce compiler warnings. Many small fixes. -- Mike Slifcak <slif@bellsouth.net>
+ *
+ * Revision 1.5 2002/02/01 20:34:49 oetiker
+ * fixed version number and date/time
+ *
+ * Revision 1.4 2001/08/22 22:29:07 jake
+ * Contents of this patch:
+ * (1) Adds/revises documentation for rrd tune in rrd_tool.c and pod files.
+ * (2) Moves some initialization code from rrd_create.c to rrd_hw.c.
+ * (3) Adds another pass to smoothing for SEASONAL and DEVSEASONAL RRAs.
+ * This pass computes the coefficients as deviations from an average; the
+ * average is added the baseline coefficient of HWPREDICT. Statistical texts
+ * suggest this to preserve algorithm stability. It will not invalidate
+ * RRD files created and smoothed with the old code.
+ * (4) Adds the aberrant-reset flag to rrd tune. This operation, which is
+ * specified for a single data source, causes the holt-winters algorithm to
+ * forget everthing it has learned and start over.
+ * (5) Fixes a few out-of-date code comments.
+ *
+ * Revision 1.3 2001/03/07 21:21:54 oetiker
+ * complete rewrite of rrdgraph documentation. This also includs info
+ * on upcomming/planned changes to the rrdgraph interface and functionality
+ * -- Alex van den Bogaerdt <alex@slot.hollandcasino.nl>
+ *
+ * Revision 1.2 2001/03/04 13:01:55 oetiker
+ * Aberrant Behavior Detection support. A brief overview added to rrdtool.pod.
+ * Major updates to rrd_update.c, rrd_create.c. Minor update to other core files.
+ * This is backwards compatible! But new files using the Aberrant stuff are not readable
+ * by old rrdtool versions. See http://cricket.sourceforge.net/aberrant/rrd_hw.htm
+ * -- Jake Brutlag <jakeb@corp.webtv.net>
+ *
+ *****************************************************************************/
+
+#include "rrd_tool.h"
+#include "rrd_rpncalc.h"
+#include "rrd_hw.h"
+#include <locale.h>
+
+int set_hwarg(
+ rrd_t *rrd,
+ enum cf_en cf,
+ enum rra_par_en rra_par,
+ char *arg);
+int set_deltaarg(
+ rrd_t *rrd,
+ enum rra_par_en rra_par,
+ char *arg);
+int set_windowarg(
+ rrd_t *rrd,
+ enum rra_par_en,
+ char *arg);
+
+int rrd_tune(
+ int argc,
+ char **argv)
+{
+ rrd_t rrd;
+ int matches;
+ int optcnt = 0;
+ long ds;
+ char ds_nam[DS_NAM_SIZE];
+ char ds_new[DS_NAM_SIZE];
+ long heartbeat;
+ double min;
+ double max;
+ char dst[DST_SIZE];
+ rrd_file_t *rrd_file;
+ struct option long_options[] = {
+ {"heartbeat", required_argument, 0, 'h'},
+ {"minimum", required_argument, 0, 'i'},
+ {"maximum", required_argument, 0, 'a'},
+ {"data-source-type", required_argument, 0, 'd'},
+ {"data-source-rename", required_argument, 0, 'r'},
+ /* added parameter tuning options for aberrant behavior detection */
+ {"deltapos", required_argument, 0, 'p'},
+ {"deltaneg", required_argument, 0, 'n'},
+ {"window-length", required_argument, 0, 'w'},
+ {"failure-threshold", required_argument, 0, 'f'},
+ {"alpha", required_argument, 0, 'x'},
+ {"beta", required_argument, 0, 'y'},
+ {"gamma", required_argument, 0, 'z'},
+ {"gamma-deviation", required_argument, 0, 'v'},
+ {"smoothing-window", required_argument, 0, 's'},
+ {"smoothing-window-deviation", required_argument, 0, 'S'},
+ {"aberrant-reset", required_argument, 0, 'b'},
+ {0, 0, 0, 0}
+ };
+
+ optind = 0;
+ opterr = 0; /* initialize getopt */
+
+
+ rrd_file = rrd_open(argv[1], &rrd, RRD_READWRITE);
+ if (rrd_file == NULL) {
+ rrd_free(&rrd);
+ return -1;
+ }
+
+ while (1) {
+ int option_index = 0;
+ int opt;
+ char *old_locale = "";
+
+ opt = getopt_long(argc, argv, "h:i:a:d:r:p:n:w:f:x:y:z:v:b:",
+ long_options, &option_index);
+ if (opt == EOF)
+ break;
+
+ optcnt++;
+ switch (opt) {
+ case 'h':
+ old_locale = setlocale(LC_NUMERIC, "C");
+ if ((matches =
+ sscanf(optarg, DS_NAM_FMT ":%ld", ds_nam,
+ &heartbeat)) != 2) {
+ rrd_set_error("invalid arguments for heartbeat");
+ rrd_free(&rrd);
+ rrd_close(rrd_file);
+ setlocale(LC_NUMERIC, old_locale);
+ return -1;
+ }
+ setlocale(LC_NUMERIC, old_locale);
+ if ((ds = ds_match(&rrd, ds_nam)) == -1) {
+ rrd_free(&rrd);
+ rrd_close(rrd_file);
+ return -1;
+ }
+ rrd.ds_def[ds].par[DS_mrhb_cnt].u_cnt = heartbeat;
+ break;
+
+ case 'i':
+ old_locale = setlocale(LC_NUMERIC, "C");
+ if ((matches =
+ sscanf(optarg, DS_NAM_FMT ":%lf", ds_nam, &min)) < 1) {
+ rrd_set_error("invalid arguments for minimum ds value");
+ rrd_free(&rrd);
+ rrd_close(rrd_file);
+ setlocale(LC_NUMERIC, old_locale);
+ return -1;
+ }
+ setlocale(LC_NUMERIC, old_locale);
+ if ((ds = ds_match(&rrd, ds_nam)) == -1) {
+ rrd_free(&rrd);
+ rrd_close(rrd_file);
+ return -1;
+ }
+
+ if (matches == 1)
+ min = DNAN;
+ rrd.ds_def[ds].par[DS_min_val].u_val = min;
+ break;
+
+ case 'a':
+ old_locale = setlocale(LC_NUMERIC, "C");
+ if ((matches =
+ sscanf(optarg, DS_NAM_FMT ":%lf", ds_nam, &max)) < 1) {
+ rrd_set_error("invalid arguments for maximum ds value");
+ rrd_free(&rrd);
+ rrd_close(rrd_file);
+ setlocale(LC_NUMERIC, old_locale);
+ return -1;
+ }
+ setlocale(LC_NUMERIC, old_locale);
+ if ((ds = ds_match(&rrd, ds_nam)) == -1) {
+ rrd_free(&rrd);
+ rrd_close(rrd_file);
+ return -1;
+ }
+ if (matches == 1)
+ max = DNAN;
+ rrd.ds_def[ds].par[DS_max_val].u_val = max;
+ break;
+
+ case 'd':
+ if ((matches =
+ sscanf(optarg, DS_NAM_FMT ":" DST_FMT, ds_nam, dst)) != 2) {
+ rrd_set_error("invalid arguments for data source type");
+ rrd_free(&rrd);
+ rrd_close(rrd_file);
+ return -1;
+ }
+ if ((ds = ds_match(&rrd, ds_nam)) == -1) {
+ rrd_free(&rrd);
+ rrd_close(rrd_file);
+ return -1;
+ }
+ if ((int) dst_conv(dst) == -1) {
+ rrd_free(&rrd);
+ rrd_close(rrd_file);
+ return -1;
+ }
+ /* only reset when something is changed */
+ if (strncmp(rrd.ds_def[ds].dst, dst, DST_SIZE - 1) != 0) {
+ strncpy(rrd.ds_def[ds].dst, dst, DST_SIZE - 1);
+ rrd.ds_def[ds].dst[DST_SIZE - 1] = '\0';
+
+ rrd.pdp_prep[ds].last_ds[0] = 'U';
+ rrd.pdp_prep[ds].last_ds[1] = 'N';
+ rrd.pdp_prep[ds].last_ds[2] = 'K';
+ rrd.pdp_prep[ds].last_ds[3] = 'N';
+ rrd.pdp_prep[ds].last_ds[4] = '\0';
+ }
+ break;
+ case 'r':
+ if ((matches =
+ sscanf(optarg, DS_NAM_FMT ":" DS_NAM_FMT, ds_nam,
+ ds_new)) != 2) {
+ rrd_set_error("invalid arguments for data source type");
+ rrd_free(&rrd);
+ rrd_close(rrd_file);
+ return -1;
+ }
+ if ((ds = ds_match(&rrd, ds_nam)) == -1) {
+ rrd_free(&rrd);
+ rrd_close(rrd_file);
+ return -1;
+ }
+ strncpy(rrd.ds_def[ds].ds_nam, ds_new, DS_NAM_SIZE - 1);
+ rrd.ds_def[ds].ds_nam[DS_NAM_SIZE - 1] = '\0';
+ break;
+ case 'p':
+ if (set_deltaarg(&rrd, RRA_delta_pos, optarg)) {
+ rrd_free(&rrd);
+ return -1;
+ }
+ break;
+ case 'n':
+ if (set_deltaarg(&rrd, RRA_delta_neg, optarg)) {
+ rrd_free(&rrd);
+ return -1;
+ }
+ break;
+ case 'f':
+ if (set_windowarg(&rrd, RRA_failure_threshold, optarg)) {
+ rrd_free(&rrd);
+ return -1;
+ }
+ break;
+ case 'w':
+ if (set_windowarg(&rrd, RRA_window_len, optarg)) {
+ rrd_free(&rrd);
+ return -1;
+ }
+ break;
+ case 'x':
+ if (set_hwarg(&rrd, CF_HWPREDICT, RRA_hw_alpha, optarg)) {
+ if (set_hwarg(&rrd, CF_MHWPREDICT, RRA_hw_alpha, optarg)) {
+ rrd_free(&rrd);
+ return -1;
+ }
+ rrd_clear_error();
+ }
+ break;
+ case 'y':
+ if (set_hwarg(&rrd, CF_HWPREDICT, RRA_hw_beta, optarg)) {
+ if (set_hwarg(&rrd, CF_MHWPREDICT, RRA_hw_beta, optarg)) {
+ rrd_free(&rrd);
+ return -1;
+ }
+ rrd_clear_error();
+ }
+ break;
+ case 'z':
+ if (set_hwarg(&rrd, CF_SEASONAL, RRA_seasonal_gamma, optarg)) {
+ rrd_free(&rrd);
+ return -1;
+ }
+ break;
+ case 'v':
+ if (set_hwarg(&rrd, CF_DEVSEASONAL, RRA_seasonal_gamma, optarg)) {
+ rrd_free(&rrd);
+ return -1;
+ }
+ break;
+ case 'b':
+ if (sscanf(optarg, DS_NAM_FMT, ds_nam) != 1) {
+ rrd_set_error("invalid argument for aberrant-reset");
+ rrd_free(&rrd);
+ rrd_close(rrd_file);
+ return -1;
+ }
+ if ((ds = ds_match(&rrd, ds_nam)) == -1) {
+ /* ds_match handles it own errors */
+ rrd_free(&rrd);
+ rrd_close(rrd_file);
+ return -1;
+ }
+ reset_aberrant_coefficients(&rrd, rrd_file, (unsigned long) ds);
+ if (rrd_test_error()) {
+ rrd_free(&rrd);
+ rrd_close(rrd_file);
+ return -1;
+ }
+ break;
+ case 's':
+ strcpy(rrd.stat_head->version, RRD_VERSION); /* smoothing_window causes Version 4 */
+ if (set_hwarg
+ (&rrd, CF_SEASONAL, RRA_seasonal_smoothing_window, optarg)) {
+ rrd_free(&rrd);
+ return -1;
+ }
+ break;
+ case 'S':
+ strcpy(rrd.stat_head->version, RRD_VERSION); /* smoothing_window causes Version 4 */
+ if (set_hwarg
+ (&rrd, CF_DEVSEASONAL, RRA_seasonal_smoothing_window,
+ optarg)) {
+ rrd_free(&rrd);
+ return -1;
+ }
+ break;
+ case '?':
+ if (optopt != 0)
+ rrd_set_error("unknown option '%c'", optopt);
+ else
+ rrd_set_error("unknown option '%s'", argv[optind - 1]);
+ rrd_free(&rrd);
+ rrd_close(rrd_file);
+ return -1;
+ }
+ }
+ if (optcnt > 0) {
+ rrd_seek(rrd_file, 0, SEEK_SET);
+ rrd_write(rrd_file, rrd.stat_head, sizeof(stat_head_t) * 1);
+ rrd_write(rrd_file, rrd.ds_def,
+ sizeof(ds_def_t) * rrd.stat_head->ds_cnt);
+ /* need to write rra_defs for RRA parameter changes */
+ rrd_write(rrd_file, rrd.rra_def,
+ sizeof(rra_def_t) * rrd.stat_head->rra_cnt);
+ } else {
+ int i;
+
+ for (i = 0; i < (int) rrd.stat_head->ds_cnt; i++)
+ if (dst_conv(rrd.ds_def[i].dst) != DST_CDEF) {
+ printf("DS[%s] typ: %s\thbt: %ld\tmin: %1.4f\tmax: %1.4f\n",
+ rrd.ds_def[i].ds_nam,
+ rrd.ds_def[i].dst,
+ rrd.ds_def[i].par[DS_mrhb_cnt].u_cnt,
+ rrd.ds_def[i].par[DS_min_val].u_val,
+ rrd.ds_def[i].par[DS_max_val].u_val);
+ } else {
+ char *buffer = NULL;
+
+ rpn_compact2str((rpn_cdefds_t *) &
+ (rrd.ds_def[i].par[DS_cdef]), rrd.ds_def,
+ &buffer);
+ printf("DS[%s] typ: %s\tcdef: %s\n", rrd.ds_def[i].ds_nam,
+ rrd.ds_def[i].dst, buffer);
+ free(buffer);
+ }
+ }
+ rrd_close(rrd_file);
+ rrd_free(&rrd);
+ return 0;
+}
+
+int set_hwarg(
+ rrd_t *rrd,
+ enum cf_en cf,
+ enum rra_par_en rra_par,
+ char *arg)
+{
+ double param;
+ unsigned long i;
+ signed short rra_idx = -1;
+
+ /* read the value */
+ param = atof(arg);
+ if (param <= 0.0 || param >= 1.0) {
+ rrd_set_error("Holt-Winters parameter must be between 0 and 1");
+ return -1;
+ }
+ /* does the appropriate RRA exist? */
+ for (i = 0; i < rrd->stat_head->rra_cnt; ++i) {
+ if (cf_conv(rrd->rra_def[i].cf_nam) == cf) {
+ rra_idx = i;
+ break;
+ }
+ }
+ if (rra_idx == -1) {
+ rrd_set_error("Holt-Winters RRA does not exist in this RRD");
+ return -1;
+ }
+
+ /* set the value */
+ rrd->rra_def[rra_idx].par[rra_par].u_val = param;
+ return 0;
+}
+
+int set_deltaarg(
+ rrd_t *rrd,
+ enum rra_par_en rra_par,
+ char *arg)
+{
+ rrd_value_t param;
+ unsigned long i;
+ signed short rra_idx = -1;
+
+ param = atof(arg);
+ if (param < 0.1) {
+ rrd_set_error("Parameter specified is too small");
+ return -1;
+ }
+ /* does the appropriate RRA exist? */
+ for (i = 0; i < rrd->stat_head->rra_cnt; ++i) {
+ if (cf_conv(rrd->rra_def[i].cf_nam) == CF_FAILURES) {
+ rra_idx = i;
+ break;
+ }
+ }
+ if (rra_idx == -1) {
+ rrd_set_error("Failures RRA does not exist in this RRD");
+ return -1;
+ }
+
+ /* set the value */
+ rrd->rra_def[rra_idx].par[rra_par].u_val = param;
+ return 0;
+}
+
+int set_windowarg(
+ rrd_t *rrd,
+ enum rra_par_en rra_par,
+ char *arg)
+{
+ unsigned long param;
+ unsigned long i, cdp_idx;
+ signed short rra_idx = -1;
+
+ /* read the value */
+ param = atoi(arg);
+ if (param < 1 || param > MAX_FAILURES_WINDOW_LEN) {
+ rrd_set_error("Parameter must be between %d and %d",
+ 1, MAX_FAILURES_WINDOW_LEN);
+ return -1;
+ }
+ /* does the appropriate RRA exist? */
+ for (i = 0; i < rrd->stat_head->rra_cnt; ++i) {
+ if (cf_conv(rrd->rra_def[i].cf_nam) == CF_FAILURES) {
+ rra_idx = i;
+ break;
+ }
+ }
+ if (rra_idx == -1) {
+ rrd_set_error("Failures RRA does not exist in this RRD");
+ return -1;
+ }
+
+ /* set the value */
+ rrd->rra_def[rra_idx].par[rra_par].u_cnt = param;
+
+ /* erase existing violations */
+ for (i = 0; i < rrd->stat_head->ds_cnt; i++) {
+ cdp_idx = rra_idx * (rrd->stat_head->ds_cnt) + i;
+ erase_violations(rrd, cdp_idx, rra_idx);
+ }
+ return 0;
+}
diff --git a/program/src/rrd_update.c b/program/src/rrd_update.c
--- /dev/null
+++ b/program/src/rrd_update.c
@@ -0,0 +1,2046 @@
+
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ *****************************************************************************
+ * rrd_update.c RRD Update Function
+ *****************************************************************************
+ * $Id$
+ *****************************************************************************/
+
+#include "rrd_tool.h"
+
+#if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
+#include <sys/locking.h>
+#include <sys/stat.h>
+#include <io.h>
+#endif
+
+#include <locale.h>
+
+#include "rrd_hw.h"
+#include "rrd_rpncalc.h"
+
+#include "rrd_is_thread_safe.h"
+#include "unused.h"
+
+#if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
+/*
+ * WIN32 does not have gettimeofday and struct timeval. This is a quick and dirty
+ * replacement.
+ */
+#include <sys/timeb.h>
+
+#ifndef __MINGW32__
+struct timeval {
+ time_t tv_sec; /* seconds */
+ long tv_usec; /* microseconds */
+};
+#endif
+
+struct __timezone {
+ int tz_minuteswest; /* minutes W of Greenwich */
+ int tz_dsttime; /* type of dst correction */
+};
+
+static int gettimeofday(
+ struct timeval *t,
+ struct __timezone *tz)
+{
+
+ struct _timeb current_time;
+
+ _ftime(¤t_time);
+
+ t->tv_sec = current_time.time;
+ t->tv_usec = current_time.millitm * 1000;
+
+ return 0;
+}
+
+#endif
+
+/* FUNCTION PROTOTYPES */
+
+int rrd_update_r(
+ const char *filename,
+ const char *tmplt,
+ int argc,
+ const char **argv);
+int _rrd_update(
+ const char *filename,
+ const char *tmplt,
+ int argc,
+ const char **argv,
+ rrd_info_t *);
+
+static int allocate_data_structures(
+ rrd_t *rrd,
+ char ***updvals,
+ rrd_value_t **pdp_temp,
+ const char *tmplt,
+ long **tmpl_idx,
+ unsigned long *tmpl_cnt,
+ unsigned long **rra_step_cnt,
+ unsigned long **skip_update,
+ rrd_value_t **pdp_new);
+
+static int parse_template(
+ rrd_t *rrd,
+ const char *tmplt,
+ unsigned long *tmpl_cnt,
+ long *tmpl_idx);
+
+static int process_arg(
+ char *step_start,
+ rrd_t *rrd,
+ rrd_file_t *rrd_file,
+ unsigned long rra_begin,
+ time_t *current_time,
+ unsigned long *current_time_usec,
+ rrd_value_t *pdp_temp,
+ rrd_value_t *pdp_new,
+ unsigned long *rra_step_cnt,
+ char **updvals,
+ long *tmpl_idx,
+ unsigned long tmpl_cnt,
+ rrd_info_t ** pcdp_summary,
+ int version,
+ unsigned long *skip_update,
+ int *schedule_smooth);
+
+static int parse_ds(
+ rrd_t *rrd,
+ char **updvals,
+ long *tmpl_idx,
+ char *input,
+ unsigned long tmpl_cnt,
+ time_t *current_time,
+ unsigned long *current_time_usec,
+ int version);
+
+static int get_time_from_reading(
+ rrd_t *rrd,
+ char timesyntax,
+ char **updvals,
+ time_t *current_time,
+ unsigned long *current_time_usec,
+ int version);
+
+static int update_pdp_prep(
+ rrd_t *rrd,
+ char **updvals,
+ rrd_value_t *pdp_new,
+ double interval);
+
+static int calculate_elapsed_steps(
+ rrd_t *rrd,
+ unsigned long current_time,
+ unsigned long current_time_usec,
+ double interval,
+ double *pre_int,
+ double *post_int,
+ unsigned long *proc_pdp_cnt);
+
+static void simple_update(
+ rrd_t *rrd,
+ double interval,
+ rrd_value_t *pdp_new);
+
+static int process_all_pdp_st(
+ rrd_t *rrd,
+ double interval,
+ double pre_int,
+ double post_int,
+ unsigned long elapsed_pdp_st,
+ rrd_value_t *pdp_new,
+ rrd_value_t *pdp_temp);
+
+static int process_pdp_st(
+ rrd_t *rrd,
+ unsigned long ds_idx,
+ double interval,
+ double pre_int,
+ double post_int,
+ long diff_pdp_st,
+ rrd_value_t *pdp_new,
+ rrd_value_t *pdp_temp);
+
+static int update_all_cdp_prep(
+ rrd_t *rrd,
+ unsigned long *rra_step_cnt,
+ unsigned long rra_begin,
+ rrd_file_t *rrd_file,
+ unsigned long elapsed_pdp_st,
+ unsigned long proc_pdp_cnt,
+ rrd_value_t **last_seasonal_coef,
+ rrd_value_t **seasonal_coef,
+ rrd_value_t *pdp_temp,
+ unsigned long *skip_update,
+ int *schedule_smooth);
+
+static int do_schedule_smooth(
+ rrd_t *rrd,
+ unsigned long rra_idx,
+ unsigned long elapsed_pdp_st);
+
+static int update_cdp_prep(
+ rrd_t *rrd,
+ unsigned long elapsed_pdp_st,
+ unsigned long start_pdp_offset,
+ unsigned long *rra_step_cnt,
+ int rra_idx,
+ rrd_value_t *pdp_temp,
+ rrd_value_t *last_seasonal_coef,
+ rrd_value_t *seasonal_coef,
+ int current_cf);
+
+static void update_cdp(
+ unival *scratch,
+ int current_cf,
+ rrd_value_t pdp_temp_val,
+ unsigned long rra_step_cnt,
+ unsigned long elapsed_pdp_st,
+ unsigned long start_pdp_offset,
+ unsigned long pdp_cnt,
+ rrd_value_t xff,
+ int i,
+ int ii);
+
+static void initialize_cdp_val(
+ unival *scratch,
+ int current_cf,
+ rrd_value_t pdp_temp_val,
+ unsigned long elapsed_pdp_st,
+ unsigned long start_pdp_offset,
+ unsigned long pdp_cnt);
+
+static void reset_cdp(
+ rrd_t *rrd,
+ unsigned long elapsed_pdp_st,
+ rrd_value_t *pdp_temp,
+ rrd_value_t *last_seasonal_coef,
+ rrd_value_t *seasonal_coef,
+ int rra_idx,
+ int ds_idx,
+ int cdp_idx,
+ enum cf_en current_cf);
+
+static rrd_value_t initialize_average_carry_over(
+ rrd_value_t pdp_temp_val,
+ unsigned long elapsed_pdp_st,
+ unsigned long start_pdp_offset,
+ unsigned long pdp_cnt);
+
+static rrd_value_t calculate_cdp_val(
+ rrd_value_t cdp_val,
+ rrd_value_t pdp_temp_val,
+ unsigned long elapsed_pdp_st,
+ int current_cf,
+ int i,
+ int ii);
+
+static int update_aberrant_cdps(
+ rrd_t *rrd,
+ rrd_file_t *rrd_file,
+ unsigned long rra_begin,
+ unsigned long elapsed_pdp_st,
+ rrd_value_t *pdp_temp,
+ rrd_value_t **seasonal_coef);
+
+static int write_to_rras(
+ rrd_t *rrd,
+ rrd_file_t *rrd_file,
+ unsigned long *rra_step_cnt,
+ unsigned long rra_begin,
+ time_t current_time,
+ unsigned long *skip_update,
+ rrd_info_t ** pcdp_summary);
+
+static int write_RRA_row(
+ rrd_file_t *rrd_file,
+ rrd_t *rrd,
+ unsigned long rra_idx,
+ unsigned short CDP_scratch_idx,
+ rrd_info_t ** pcdp_summary,
+ time_t rra_time);
+
+static int smooth_all_rras(
+ rrd_t *rrd,
+ rrd_file_t *rrd_file,
+ unsigned long rra_begin);
+
+#ifndef HAVE_MMAP
+static int write_changes_to_disk(
+ rrd_t *rrd,
+ rrd_file_t *rrd_file,
+ int version);
+#endif
+
+/*
+ * normalize time as returned by gettimeofday. usec part must
+ * be always >= 0
+ */
+static inline void normalize_time(
+ struct timeval *t)
+{
+ if (t->tv_usec < 0) {
+ t->tv_sec--;
+ t->tv_usec += 1e6L;
+ }
+}
+
+/*
+ * Sets current_time and current_time_usec based on the current time.
+ * current_time_usec is set to 0 if the version number is 1 or 2.
+ */
+static inline void initialize_time(
+ time_t *current_time,
+ unsigned long *current_time_usec,
+ int version)
+{
+ struct timeval tmp_time; /* used for time conversion */
+
+ gettimeofday(&tmp_time, 0);
+ normalize_time(&tmp_time);
+ *current_time = tmp_time.tv_sec;
+ if (version >= 3) {
+ *current_time_usec = tmp_time.tv_usec;
+ } else {
+ *current_time_usec = 0;
+ }
+}
+
+#define IFDNAN(X,Y) (isnan(X) ? (Y) : (X));
+
+rrd_info_t *rrd_update_v(
+ int argc,
+ char **argv)
+{
+ char *tmplt = NULL;
+ rrd_info_t *result = NULL;
+ rrd_infoval_t rc;
+ struct option long_options[] = {
+ {"template", required_argument, 0, 't'},
+ {0, 0, 0, 0}
+ };
+
+ rc.u_int = -1;
+ optind = 0;
+ opterr = 0; /* initialize getopt */
+
+ while (1) {
+ int option_index = 0;
+ int opt;
+
+ opt = getopt_long(argc, argv, "t:", long_options, &option_index);
+
+ if (opt == EOF)
+ break;
+
+ switch (opt) {
+ case 't':
+ tmplt = optarg;
+ break;
+
+ case '?':
+ rrd_set_error("unknown option '%s'", argv[optind - 1]);
+ goto end_tag;
+ }
+ }
+
+ /* need at least 2 arguments: filename, data. */
+ if (argc - optind < 2) {
+ rrd_set_error("Not enough arguments");
+ goto end_tag;
+ }
+ rc.u_int = 0;
+ result = rrd_info_push(NULL, sprintf_alloc("return_value"), RD_I_INT, rc);
+ rc.u_int = _rrd_update(argv[optind], tmplt,
+ argc - optind - 1,
+ (const char **) (argv + optind + 1), result);
+ result->value.u_int = rc.u_int;
+ end_tag:
+ return result;
+}
+
+int rrd_update(
+ int argc,
+ char **argv)
+{
+ struct option long_options[] = {
+ {"template", required_argument, 0, 't'},
+ {0, 0, 0, 0}
+ };
+ int option_index = 0;
+ int opt;
+ char *tmplt = NULL;
+ int rc = -1;
+
+ optind = 0;
+ opterr = 0; /* initialize getopt */
+
+ while (1) {
+ opt = getopt_long(argc, argv, "t:", long_options, &option_index);
+
+ if (opt == EOF)
+ break;
+
+ switch (opt) {
+ case 't':
+ tmplt = strdup(optarg);
+ break;
+
+ case '?':
+ rrd_set_error("unknown option '%s'", argv[optind - 1]);
+ goto out;
+ }
+ }
+
+ /* need at least 2 arguments: filename, data. */
+ if (argc - optind < 2) {
+ rrd_set_error("Not enough arguments");
+ goto out;
+ }
+
+ rc = rrd_update_r(argv[optind], tmplt,
+ argc - optind - 1, (const char **) (argv + optind + 1));
+ out:
+ free(tmplt);
+ return rc;
+}
+
+int rrd_update_r(
+ const char *filename,
+ const char *tmplt,
+ int argc,
+ const char **argv)
+{
+ return _rrd_update(filename, tmplt, argc, argv, NULL);
+}
+
+int _rrd_update(
+ const char *filename,
+ const char *tmplt,
+ int argc,
+ const char **argv,
+ rrd_info_t * pcdp_summary)
+{
+
+ int arg_i = 2;
+
+ unsigned long rra_begin; /* byte pointer to the rra
+ * area in the rrd file. this
+ * pointer never changes value */
+ rrd_value_t *pdp_new; /* prepare the incoming data to be added
+ * to the existing entry */
+ rrd_value_t *pdp_temp; /* prepare the pdp values to be added
+ * to the cdp values */
+
+ long *tmpl_idx; /* index representing the settings
+ * transported by the tmplt index */
+ unsigned long tmpl_cnt = 2; /* time and data */
+ rrd_t rrd;
+ time_t current_time = 0;
+ unsigned long current_time_usec = 0; /* microseconds part of current time */
+ char **updvals;
+ int schedule_smooth = 0;
+
+ /* number of elapsed PDP steps since last update */
+ unsigned long *rra_step_cnt = NULL;
+
+ int version; /* rrd version */
+ rrd_file_t *rrd_file;
+ char *arg_copy; /* for processing the argv */
+ unsigned long *skip_update; /* RRAs to advance but not write */
+
+ /* need at least 1 arguments: data. */
+ if (argc < 1) {
+ rrd_set_error("Not enough arguments");
+ goto err_out;
+ }
+
+ if ((rrd_file = rrd_open(filename, &rrd, RRD_READWRITE)) == NULL) {
+ goto err_free;
+ }
+ /* We are now at the beginning of the rra's */
+ rra_begin = rrd_file->header_len;
+
+ version = atoi(rrd.stat_head->version);
+
+ initialize_time(¤t_time, ¤t_time_usec, version);
+
+ /* get exclusive lock to whole file.
+ * lock gets removed when we close the file.
+ */
+ if (rrd_lock(rrd_file) != 0) {
+ rrd_set_error("could not lock RRD");
+ goto err_close;
+ }
+
+ if (allocate_data_structures(&rrd, &updvals,
+ &pdp_temp, tmplt, &tmpl_idx, &tmpl_cnt,
+ &rra_step_cnt, &skip_update,
+ &pdp_new) == -1) {
+ goto err_close;
+ }
+
+ /* loop through the arguments. */
+ for (arg_i = 0; arg_i < argc; arg_i++) {
+ if ((arg_copy = strdup(argv[arg_i])) == NULL) {
+ rrd_set_error("failed duplication argv entry");
+ break;
+ }
+ if (process_arg(arg_copy, &rrd, rrd_file, rra_begin,
+ ¤t_time, ¤t_time_usec, pdp_temp, pdp_new,
+ rra_step_cnt, updvals, tmpl_idx, tmpl_cnt,
+ &pcdp_summary, version, skip_update,
+ &schedule_smooth) == -1) {
+ if (rrd_test_error()) { /* Should have error string always here */
+ char *save_error;
+
+ /* Prepend file name to error message */
+ if ((save_error = strdup(rrd_get_error())) != NULL) {
+ rrd_set_error("%s: %s", filename, save_error);
+ free(save_error);
+ }
+ }
+ free(arg_copy);
+ break;
+ }
+ free(arg_copy);
+ }
+
+ free(rra_step_cnt);
+
+ /* if we got here and if there is an error and if the file has not been
+ * written to, then close things up and return. */
+ if (rrd_test_error()) {
+ goto err_free_structures;
+ }
+#ifndef HAVE_MMAP
+ if (write_changes_to_disk(&rrd, rrd_file, version) == -1) {
+ goto err_free_structures;
+ }
+#endif
+
+ /* calling the smoothing code here guarantees at most one smoothing
+ * operation per rrd_update call. Unfortunately, it is possible with bulk
+ * updates, or a long-delayed update for smoothing to occur off-schedule.
+ * This really isn't critical except during the burn-in cycles. */
+ if (schedule_smooth) {
+ smooth_all_rras(&rrd, rrd_file, rra_begin);
+ }
+
+/* rrd_dontneed(rrd_file,&rrd); */
+ rrd_free(&rrd);
+ rrd_close(rrd_file);
+
+ free(pdp_new);
+ free(tmpl_idx);
+ free(pdp_temp);
+ free(skip_update);
+ free(updvals);
+ return 0;
+
+ err_free_structures:
+ free(pdp_new);
+ free(tmpl_idx);
+ free(pdp_temp);
+ free(skip_update);
+ free(updvals);
+ err_close:
+ rrd_close(rrd_file);
+ err_free:
+ rrd_free(&rrd);
+ err_out:
+ return -1;
+}
+
+/*
+ * get exclusive lock to whole file.
+ * lock gets removed when we close the file
+ *
+ * returns 0 on success
+ */
+int rrd_lock(
+ rrd_file_t *file)
+{
+ int rcstat;
+
+ {
+#if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
+ struct _stat st;
+
+ if (_fstat(file->fd, &st) == 0) {
+ rcstat = _locking(file->fd, _LK_NBLCK, st.st_size);
+ } else {
+ rcstat = -1;
+ }
+#else
+ struct flock lock;
+
+ lock.l_type = F_WRLCK; /* exclusive write lock */
+ lock.l_len = 0; /* whole file */
+ lock.l_start = 0; /* start of file */
+ lock.l_whence = SEEK_SET; /* end of file */
+
+ rcstat = fcntl(file->fd, F_SETLK, &lock);
+#endif
+ }
+
+ return (rcstat);
+}
+
+/*
+ * Allocate some important arrays used, and initialize the template.
+ *
+ * When it returns, either all of the structures are allocated
+ * or none of them are.
+ *
+ * Returns 0 on success, -1 on error.
+ */
+static int allocate_data_structures(
+ rrd_t *rrd,
+ char ***updvals,
+ rrd_value_t **pdp_temp,
+ const char *tmplt,
+ long **tmpl_idx,
+ unsigned long *tmpl_cnt,
+ unsigned long **rra_step_cnt,
+ unsigned long **skip_update,
+ rrd_value_t **pdp_new)
+{
+ unsigned i, ii;
+ if ((*updvals = (char **) malloc(sizeof(char *)
+ * (rrd->stat_head->ds_cnt + 1))) == NULL) {
+ rrd_set_error("allocating updvals pointer array.");
+ return -1;
+ }
+ if ((*pdp_temp = (rrd_value_t *) malloc(sizeof(rrd_value_t)
+ * rrd->stat_head->ds_cnt)) ==
+ NULL) {
+ rrd_set_error("allocating pdp_temp.");
+ goto err_free_updvals;
+ }
+ if ((*skip_update = (unsigned long *) malloc(sizeof(unsigned long)
+ *
+ rrd->stat_head->rra_cnt)) ==
+ NULL) {
+ rrd_set_error("allocating skip_update.");
+ goto err_free_pdp_temp;
+ }
+ if ((*tmpl_idx = (long *) malloc(sizeof(unsigned long)
+ * (rrd->stat_head->ds_cnt + 1))) == NULL) {
+ rrd_set_error("allocating tmpl_idx.");
+ goto err_free_skip_update;
+ }
+ if ((*rra_step_cnt = (unsigned long *) malloc(sizeof(unsigned long)
+ *
+ (rrd->stat_head->
+ rra_cnt))) == NULL) {
+ rrd_set_error("allocating rra_step_cnt.");
+ goto err_free_tmpl_idx;
+ }
+
+ /* initialize tmplt redirector */
+ /* default config example (assume DS 1 is a CDEF DS)
+ tmpl_idx[0] -> 0; (time)
+ tmpl_idx[1] -> 1; (DS 0)
+ tmpl_idx[2] -> 3; (DS 2)
+ tmpl_idx[3] -> 4; (DS 3) */
+ (*tmpl_idx)[0] = 0; /* time */
+ for (i = 1, ii = 1; i <= rrd->stat_head->ds_cnt; i++) {
+ if (dst_conv(rrd->ds_def[i - 1].dst) != DST_CDEF)
+ (*tmpl_idx)[ii++] = i;
+ }
+ *tmpl_cnt = ii;
+
+ if (tmplt != NULL) {
+ if (parse_template(rrd, tmplt, tmpl_cnt, *tmpl_idx) == -1) {
+ goto err_free_rra_step_cnt;
+ }
+ }
+
+ if ((*pdp_new = (rrd_value_t *) malloc(sizeof(rrd_value_t)
+ * rrd->stat_head->ds_cnt)) == NULL) {
+ rrd_set_error("allocating pdp_new.");
+ goto err_free_rra_step_cnt;
+ }
+
+ return 0;
+
+ err_free_rra_step_cnt:
+ free(*rra_step_cnt);
+ err_free_tmpl_idx:
+ free(*tmpl_idx);
+ err_free_skip_update:
+ free(*skip_update);
+ err_free_pdp_temp:
+ free(*pdp_temp);
+ err_free_updvals:
+ free(*updvals);
+ return -1;
+}
+
+/*
+ * Parses tmplt and puts an ordered list of DS's into tmpl_idx.
+ *
+ * Returns 0 on success.
+ */
+static int parse_template(
+ rrd_t *rrd,
+ const char *tmplt,
+ unsigned long *tmpl_cnt,
+ long *tmpl_idx)
+{
+ char *dsname, *tmplt_copy;
+ unsigned int tmpl_len, i;
+ int ret = 0;
+
+ *tmpl_cnt = 1; /* the first entry is the time */
+
+ /* we should work on a writeable copy here */
+ if ((tmplt_copy = strdup(tmplt)) == NULL) {
+ rrd_set_error("error copying tmplt '%s'", tmplt);
+ ret = -1;
+ goto out;
+ }
+
+ dsname = tmplt_copy;
+ tmpl_len = strlen(tmplt_copy);
+ for (i = 0; i <= tmpl_len; i++) {
+ if (tmplt_copy[i] == ':' || tmplt_copy[i] == '\0') {
+ tmplt_copy[i] = '\0';
+ if (*tmpl_cnt > rrd->stat_head->ds_cnt) {
+ rrd_set_error("tmplt contains more DS definitions than RRD");
+ ret = -1;
+ goto out_free_tmpl_copy;
+ }
+ if ((tmpl_idx[(*tmpl_cnt)++] = ds_match(rrd, dsname) + 1) == 0) {
+ rrd_set_error("unknown DS name '%s'", dsname);
+ ret = -1;
+ goto out_free_tmpl_copy;
+ }
+ /* go to the next entry on the tmplt_copy */
+ if (i < tmpl_len)
+ dsname = &tmplt_copy[i + 1];
+ }
+ }
+ out_free_tmpl_copy:
+ free(tmplt_copy);
+ out:
+ return ret;
+}
+
+/*
+ * Parse an update string, updates the primary data points (PDPs)
+ * and consolidated data points (CDPs), and writes changes to the RRAs.
+ *
+ * Returns 0 on success, -1 on error.
+ */
+static int process_arg(
+ char *step_start,
+ rrd_t *rrd,
+ rrd_file_t *rrd_file,
+ unsigned long rra_begin,
+ time_t *current_time,
+ unsigned long *current_time_usec,
+ rrd_value_t *pdp_temp,
+ rrd_value_t *pdp_new,
+ unsigned long *rra_step_cnt,
+ char **updvals,
+ long *tmpl_idx,
+ unsigned long tmpl_cnt,
+ rrd_info_t ** pcdp_summary,
+ int version,
+ unsigned long *skip_update,
+ int *schedule_smooth)
+{
+ rrd_value_t *seasonal_coef = NULL, *last_seasonal_coef = NULL;
+
+ /* a vector of future Holt-Winters seasonal coefs */
+ unsigned long elapsed_pdp_st;
+
+ double interval, pre_int, post_int; /* interval between this and
+ * the last run */
+ unsigned long proc_pdp_cnt;
+
+ if (parse_ds(rrd, updvals, tmpl_idx, step_start, tmpl_cnt,
+ current_time, current_time_usec, version) == -1) {
+ return -1;
+ }
+
+ interval = (double) (*current_time - rrd->live_head->last_up)
+ + (double) ((long) *current_time_usec -
+ (long) rrd->live_head->last_up_usec) / 1e6f;
+
+ /* process the data sources and update the pdp_prep
+ * area accordingly */
+ if (update_pdp_prep(rrd, updvals, pdp_new, interval) == -1) {
+ return -1;
+ }
+
+ elapsed_pdp_st = calculate_elapsed_steps(rrd,
+ *current_time,
+ *current_time_usec, interval,
+ &pre_int, &post_int,
+ &proc_pdp_cnt);
+
+ /* has a pdp_st moment occurred since the last run ? */
+ if (elapsed_pdp_st == 0) {
+ /* no we have not passed a pdp_st moment. therefore update is simple */
+ simple_update(rrd, interval, pdp_new);
+ } else {
+ /* an pdp_st has occurred. */
+ if (process_all_pdp_st(rrd, interval,
+ pre_int, post_int,
+ elapsed_pdp_st, pdp_new, pdp_temp) == -1) {
+ return -1;
+ }
+ if (update_all_cdp_prep(rrd, rra_step_cnt,
+ rra_begin, rrd_file,
+ elapsed_pdp_st,
+ proc_pdp_cnt,
+ &last_seasonal_coef,
+ &seasonal_coef,
+ pdp_temp,
+ skip_update, schedule_smooth) == -1) {
+ goto err_free_coefficients;
+ }
+ if (update_aberrant_cdps(rrd, rrd_file, rra_begin,
+ elapsed_pdp_st, pdp_temp,
+ &seasonal_coef) == -1) {
+ goto err_free_coefficients;
+ }
+ if (write_to_rras(rrd, rrd_file, rra_step_cnt, rra_begin,
+ *current_time, skip_update,
+ pcdp_summary) == -1) {
+ goto err_free_coefficients;
+ }
+ } /* endif a pdp_st has occurred */
+ rrd->live_head->last_up = *current_time;
+ rrd->live_head->last_up_usec = *current_time_usec;
+
+ if (version < 3) {
+ *rrd->legacy_last_up = rrd->live_head->last_up;
+ }
+ free(seasonal_coef);
+ free(last_seasonal_coef);
+ return 0;
+
+ err_free_coefficients:
+ free(seasonal_coef);
+ free(last_seasonal_coef);
+ return -1;
+}
+
+/*
+ * Parse a DS string (time + colon-separated values), storing the
+ * results in current_time, current_time_usec, and updvals.
+ *
+ * Returns 0 on success, -1 on error.
+ */
+static int parse_ds(
+ rrd_t *rrd,
+ char **updvals,
+ long *tmpl_idx,
+ char *input,
+ unsigned long tmpl_cnt,
+ time_t *current_time,
+ unsigned long *current_time_usec,
+ int version)
+{
+ char *p;
+ unsigned long i;
+ char timesyntax;
+
+ updvals[0] = input;
+ /* initialize all ds input to unknown except the first one
+ which has always got to be set */
+ for (i = 1; i <= rrd->stat_head->ds_cnt; i++)
+ updvals[i] = "U";
+
+ /* separate all ds elements; first must be examined separately
+ due to alternate time syntax */
+ if ((p = strchr(input, '@')) != NULL) {
+ timesyntax = '@';
+ } else if ((p = strchr(input, ':')) != NULL) {
+ timesyntax = ':';
+ } else {
+ rrd_set_error("expected timestamp not found in data source from %s",
+ input);
+ return -1;
+ }
+ *p = '\0';
+ i = 1;
+ updvals[tmpl_idx[i++]] = p + 1;
+ while (*(++p)) {
+ if (*p == ':') {
+ *p = '\0';
+ if (i < tmpl_cnt) {
+ updvals[tmpl_idx[i++]] = p + 1;
+ }
+ }
+ }
+
+ if (i != tmpl_cnt) {
+ rrd_set_error("expected %lu data source readings (got %lu) from %s",
+ tmpl_cnt - 1, i, input);
+ return -1;
+ }
+
+ if (get_time_from_reading(rrd, timesyntax, updvals,
+ current_time, current_time_usec,
+ version) == -1) {
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Parse the time in a DS string, store it in current_time and
+ * current_time_usec and verify that it's later than the last
+ * update for this DS.
+ *
+ * Returns 0 on success, -1 on error.
+ */
+static int get_time_from_reading(
+ rrd_t *rrd,
+ char timesyntax,
+ char **updvals,
+ time_t *current_time,
+ unsigned long *current_time_usec,
+ int version)
+{
+ double tmp;
+ char *parsetime_error = NULL;
+ char *old_locale;
+ rrd_time_value_t ds_tv;
+ struct timeval tmp_time; /* used for time conversion */
+
+ /* get the time from the reading ... handle N */
+ if (timesyntax == '@') { /* at-style */
+ if ((parsetime_error = rrd_parsetime(updvals[0], &ds_tv))) {
+ rrd_set_error("ds time: %s: %s", updvals[0], parsetime_error);
+ return -1;
+ }
+ if (ds_tv.type == RELATIVE_TO_END_TIME ||
+ ds_tv.type == RELATIVE_TO_START_TIME) {
+ rrd_set_error("specifying time relative to the 'start' "
+ "or 'end' makes no sense here: %s", updvals[0]);
+ return -1;
+ }
+ *current_time = mktime(&ds_tv.tm) +ds_tv.offset;
+ *current_time_usec = 0; /* FIXME: how to handle usecs here ? */
+ } else if (strcmp(updvals[0], "N") == 0) {
+ gettimeofday(&tmp_time, 0);
+ normalize_time(&tmp_time);
+ *current_time = tmp_time.tv_sec;
+ *current_time_usec = tmp_time.tv_usec;
+ } else {
+ old_locale = setlocale(LC_NUMERIC, "C");
+ tmp = strtod(updvals[0], 0);
+ setlocale(LC_NUMERIC, old_locale);
+ *current_time = floor(tmp);
+ *current_time_usec = (long) ((tmp - (double) *current_time) * 1e6f);
+ }
+ /* dont do any correction for old version RRDs */
+ if (version < 3)
+ *current_time_usec = 0;
+
+ if (*current_time < rrd->live_head->last_up ||
+ (*current_time == rrd->live_head->last_up &&
+ (long) *current_time_usec <= (long) rrd->live_head->last_up_usec)) {
+ rrd_set_error("illegal attempt to update using time %ld when "
+ "last update time is %ld (minimum one second step)",
+ *current_time, rrd->live_head->last_up);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Update pdp_new by interpreting the updvals according to the DS type
+ * (COUNTER, GAUGE, etc.).
+ *
+ * Returns 0 on success, -1 on error.
+ */
+static int update_pdp_prep(
+ rrd_t *rrd,
+ char **updvals,
+ rrd_value_t *pdp_new,
+ double interval)
+{
+ unsigned long ds_idx;
+ int ii;
+ char *endptr; /* used in the conversion */
+ double rate;
+ char *old_locale;
+ enum dst_en dst_idx;
+
+ for (ds_idx = 0; ds_idx < rrd->stat_head->ds_cnt; ds_idx++) {
+ dst_idx = dst_conv(rrd->ds_def[ds_idx].dst);
+
+ /* make sure we do not build diffs with old last_ds values */
+ if (rrd->ds_def[ds_idx].par[DS_mrhb_cnt].u_cnt < interval) {
+ strncpy(rrd->pdp_prep[ds_idx].last_ds, "U", LAST_DS_LEN - 1);
+ rrd->pdp_prep[ds_idx].last_ds[LAST_DS_LEN - 1] = '\0';
+ }
+
+ /* NOTE: DST_CDEF should never enter this if block, because
+ * updvals[ds_idx+1][0] is initialized to 'U'; unless the caller
+ * accidently specified a value for the DST_CDEF. To handle this case,
+ * an extra check is required. */
+
+ if ((updvals[ds_idx + 1][0] != 'U') &&
+ (dst_idx != DST_CDEF) &&
+ rrd->ds_def[ds_idx].par[DS_mrhb_cnt].u_cnt >= interval) {
+ rate = DNAN;
+
+ /* pdp_new contains rate * time ... eg the bytes transferred during
+ * the interval. Doing it this way saves a lot of math operations
+ */
+ switch (dst_idx) {
+ case DST_COUNTER:
+ case DST_DERIVE:
+ for (ii = 0; updvals[ds_idx + 1][ii] != '\0'; ii++) {
+ if ((updvals[ds_idx + 1][ii] < '0'
+ || updvals[ds_idx + 1][ii] > '9')
+ && (ii != 0 && updvals[ds_idx + 1][ii] != '-')) {
+ rrd_set_error("not a simple integer: '%s'",
+ updvals[ds_idx + 1]);
+ return -1;
+ }
+ }
+ if (rrd->pdp_prep[ds_idx].last_ds[0] != 'U') {
+ pdp_new[ds_idx] =
+ rrd_diff(updvals[ds_idx + 1],
+ rrd->pdp_prep[ds_idx].last_ds);
+ if (dst_idx == DST_COUNTER) {
+ /* simple overflow catcher. This will fail
+ * terribly for non 32 or 64 bit counters
+ * ... are there any others in SNMP land?
+ */
+ if (pdp_new[ds_idx] < (double) 0.0)
+ pdp_new[ds_idx] += (double) 4294967296.0; /* 2^32 */
+ if (pdp_new[ds_idx] < (double) 0.0)
+ pdp_new[ds_idx] += (double) 18446744069414584320.0; /* 2^64-2^32 */
+ }
+ rate = pdp_new[ds_idx] / interval;
+ } else {
+ pdp_new[ds_idx] = DNAN;
+ }
+ break;
+ case DST_ABSOLUTE:
+ old_locale = setlocale(LC_NUMERIC, "C");
+ errno = 0;
+ pdp_new[ds_idx] = strtod(updvals[ds_idx + 1], &endptr);
+ setlocale(LC_NUMERIC, old_locale);
+ if (errno > 0) {
+ rrd_set_error("converting '%s' to float: %s",
+ updvals[ds_idx + 1], rrd_strerror(errno));
+ return -1;
+ };
+ if (endptr[0] != '\0') {
+ rrd_set_error
+ ("conversion of '%s' to float not complete: tail '%s'",
+ updvals[ds_idx + 1], endptr);
+ return -1;
+ }
+ rate = pdp_new[ds_idx] / interval;
+ break;
+ case DST_GAUGE:
+ errno = 0;
+ old_locale = setlocale(LC_NUMERIC, "C");
+ pdp_new[ds_idx] =
+ strtod(updvals[ds_idx + 1], &endptr) * interval;
+ setlocale(LC_NUMERIC, old_locale);
+ if (errno) {
+ rrd_set_error("converting '%s' to float: %s",
+ updvals[ds_idx + 1], rrd_strerror(errno));
+ return -1;
+ };
+ if (endptr[0] != '\0') {
+ rrd_set_error
+ ("conversion of '%s' to float not complete: tail '%s'",
+ updvals[ds_idx + 1], endptr);
+ return -1;
+ }
+ rate = pdp_new[ds_idx] / interval;
+ break;
+ default:
+ rrd_set_error("rrd contains unknown DS type : '%s'",
+ rrd->ds_def[ds_idx].dst);
+ return -1;
+ }
+ /* break out of this for loop if the error string is set */
+ if (rrd_test_error()) {
+ return -1;
+ }
+ /* make sure pdp_temp is neither too large or too small
+ * if any of these occur it becomes unknown ...
+ * sorry folks ... */
+ if (!isnan(rate) &&
+ ((!isnan(rrd->ds_def[ds_idx].par[DS_max_val].u_val) &&
+ rate > rrd->ds_def[ds_idx].par[DS_max_val].u_val) ||
+ (!isnan(rrd->ds_def[ds_idx].par[DS_min_val].u_val) &&
+ rate < rrd->ds_def[ds_idx].par[DS_min_val].u_val))) {
+ pdp_new[ds_idx] = DNAN;
+ }
+ } else {
+ /* no news is news all the same */
+ pdp_new[ds_idx] = DNAN;
+ }
+
+
+ /* make a copy of the command line argument for the next run */
+#ifdef DEBUG
+ fprintf(stderr, "prep ds[%lu]\t"
+ "last_arg '%s'\t"
+ "this_arg '%s'\t"
+ "pdp_new %10.2f\n",
+ ds_idx, rrd->pdp_prep[ds_idx].last_ds, updvals[ds_idx + 1],
+ pdp_new[ds_idx]);
+#endif
+ strncpy(rrd->pdp_prep[ds_idx].last_ds, updvals[ds_idx + 1],
+ LAST_DS_LEN - 1);
+ rrd->pdp_prep[ds_idx].last_ds[LAST_DS_LEN - 1] = '\0';
+ }
+ return 0;
+}
+
+/*
+ * How many PDP steps have elapsed since the last update? Returns the answer,
+ * and stores the time between the last update and the last PDP in pre_time,
+ * and the time between the last PDP and the current time in post_int.
+ */
+static int calculate_elapsed_steps(
+ rrd_t *rrd,
+ unsigned long current_time,
+ unsigned long current_time_usec,
+ double interval,
+ double *pre_int,
+ double *post_int,
+ unsigned long *proc_pdp_cnt)
+{
+ unsigned long proc_pdp_st; /* which pdp_st was the last to be processed */
+ unsigned long occu_pdp_st; /* when was the pdp_st before the last update
+ * time */
+ unsigned long proc_pdp_age; /* how old was the data in the pdp prep area
+ * when it was last updated */
+ unsigned long occu_pdp_age; /* how long ago was the last pdp_step time */
+
+ /* when was the current pdp started */
+ proc_pdp_age = rrd->live_head->last_up % rrd->stat_head->pdp_step;
+ proc_pdp_st = rrd->live_head->last_up - proc_pdp_age;
+
+ /* when did the last pdp_st occur */
+ occu_pdp_age = current_time % rrd->stat_head->pdp_step;
+ occu_pdp_st = current_time - occu_pdp_age;
+
+ if (occu_pdp_st > proc_pdp_st) {
+ /* OK we passed the pdp_st moment */
+ *pre_int = (long) occu_pdp_st - rrd->live_head->last_up; /* how much of the input data
+ * occurred before the latest
+ * pdp_st moment*/
+ *pre_int -= ((double) rrd->live_head->last_up_usec) / 1e6f; /* adjust usecs */
+ *post_int = occu_pdp_age; /* how much after it */
+ *post_int += ((double) current_time_usec) / 1e6f; /* adjust usecs */
+ } else {
+ *pre_int = interval;
+ *post_int = 0;
+ }
+
+ *proc_pdp_cnt = proc_pdp_st / rrd->stat_head->pdp_step;
+
+#ifdef DEBUG
+ printf("proc_pdp_age %lu\t"
+ "proc_pdp_st %lu\t"
+ "occu_pfp_age %lu\t"
+ "occu_pdp_st %lu\t"
+ "int %lf\t"
+ "pre_int %lf\t"
+ "post_int %lf\n", proc_pdp_age, proc_pdp_st,
+ occu_pdp_age, occu_pdp_st, interval, *pre_int, *post_int);
+#endif
+
+ /* compute the number of elapsed pdp_st moments */
+ return (occu_pdp_st - proc_pdp_st) / rrd->stat_head->pdp_step;
+}
+
+/*
+ * Increment the PDP values by the values in pdp_new, or else initialize them.
+ */
+static void simple_update(
+ rrd_t *rrd,
+ double interval,
+ rrd_value_t *pdp_new)
+{
+ int i;
+
+ for (i = 0; i < (signed) rrd->stat_head->ds_cnt; i++) {
+ if (isnan(pdp_new[i])) {
+ /* this is not really accurate if we use subsecond data arrival time
+ should have thought of it when going subsecond resolution ...
+ sorry next format change we will have it! */
+ rrd->pdp_prep[i].scratch[PDP_unkn_sec_cnt].u_cnt +=
+ floor(interval);
+ } else {
+ if (isnan(rrd->pdp_prep[i].scratch[PDP_val].u_val)) {
+ rrd->pdp_prep[i].scratch[PDP_val].u_val = pdp_new[i];
+ } else {
+ rrd->pdp_prep[i].scratch[PDP_val].u_val += pdp_new[i];
+ }
+ }
+#ifdef DEBUG
+ fprintf(stderr,
+ "NO PDP ds[%i]\t"
+ "value %10.2f\t"
+ "unkn_sec %5lu\n",
+ i,
+ rrd->pdp_prep[i].scratch[PDP_val].u_val,
+ rrd->pdp_prep[i].scratch[PDP_unkn_sec_cnt].u_cnt);
+#endif
+ }
+}
+
+/*
+ * Call process_pdp_st for each DS.
+ *
+ * Returns 0 on success, -1 on error.
+ */
+static int process_all_pdp_st(
+ rrd_t *rrd,
+ double interval,
+ double pre_int,
+ double post_int,
+ unsigned long elapsed_pdp_st,
+ rrd_value_t *pdp_new,
+ rrd_value_t *pdp_temp)
+{
+ unsigned long ds_idx;
+
+ /* in pdp_prep[].scratch[PDP_val].u_val we have collected
+ rate*seconds which occurred up to the last run.
+ pdp_new[] contains rate*seconds from the latest run.
+ pdp_temp[] will contain the rate for cdp */
+
+ for (ds_idx = 0; ds_idx < rrd->stat_head->ds_cnt; ds_idx++) {
+ if (process_pdp_st(rrd, ds_idx, interval, pre_int, post_int,
+ elapsed_pdp_st * rrd->stat_head->pdp_step,
+ pdp_new, pdp_temp) == -1) {
+ return -1;
+ }
+#ifdef DEBUG
+ fprintf(stderr, "PDP UPD ds[%lu]\t"
+ "elapsed_pdp_st %lu\t"
+ "pdp_temp %10.2f\t"
+ "new_prep %10.2f\t"
+ "new_unkn_sec %5lu\n",
+ ds_idx,
+ elapsed_pdp_st,
+ pdp_temp[ds_idx],
+ rrd->pdp_prep[ds_idx].scratch[PDP_val].u_val,
+ rrd->pdp_prep[ds_idx].scratch[PDP_unkn_sec_cnt].u_cnt);
+#endif
+ }
+ return 0;
+}
+
+/*
+ * Process an update that occurs after one of the PDP moments.
+ * Increments the PDP value, sets NAN if time greater than the
+ * heartbeats have elapsed, processes CDEFs.
+ *
+ * Returns 0 on success, -1 on error.
+ */
+static int process_pdp_st(
+ rrd_t *rrd,
+ unsigned long ds_idx,
+ double interval,
+ double pre_int,
+ double post_int,
+ long diff_pdp_st, /* number of seconds in full steps passed since last update */
+ rrd_value_t *pdp_new,
+ rrd_value_t *pdp_temp)
+{
+ int i;
+
+ /* update pdp_prep to the current pdp_st. */
+ double pre_unknown = 0.0;
+ unival *scratch = rrd->pdp_prep[ds_idx].scratch;
+ unsigned long mrhb = rrd->ds_def[ds_idx].par[DS_mrhb_cnt].u_cnt;
+
+ rpnstack_t rpnstack; /* used for COMPUTE DS */
+
+ rpnstack_init(&rpnstack);
+
+
+ if (isnan(pdp_new[ds_idx])) {
+ /* a final bit of unknown to be added before calculation
+ we use a temporary variable for this so that we
+ don't have to turn integer lines before using the value */
+ pre_unknown = pre_int;
+ } else {
+ if (isnan(scratch[PDP_val].u_val)) {
+ scratch[PDP_val].u_val = 0;
+ }
+ scratch[PDP_val].u_val += pdp_new[ds_idx] / interval * pre_int;
+ }
+
+ /* if too much of the pdp_prep is unknown we dump it */
+ /* if the interval is larger thatn mrhb we get NAN */
+ if ((interval > mrhb) ||
+ (rrd->stat_head->pdp_step / 2.0 <
+ (signed) scratch[PDP_unkn_sec_cnt].u_cnt)) {
+ pdp_temp[ds_idx] = DNAN;
+ } else {
+ pdp_temp[ds_idx] = scratch[PDP_val].u_val /
+ ((double) (diff_pdp_st - scratch[PDP_unkn_sec_cnt].u_cnt) -
+ pre_unknown);
+ }
+
+ /* process CDEF data sources; remember each CDEF DS can
+ * only reference other DS with a lower index number */
+ if (dst_conv(rrd->ds_def[ds_idx].dst) == DST_CDEF) {
+ rpnp_t *rpnp;
+
+ rpnp =
+ rpn_expand((rpn_cdefds_t *) &(rrd->ds_def[ds_idx].par[DS_cdef]));
+ /* substitute data values for OP_VARIABLE nodes */
+ for (i = 0; rpnp[i].op != OP_END; i++) {
+ if (rpnp[i].op == OP_VARIABLE) {
+ rpnp[i].op = OP_NUMBER;
+ rpnp[i].val = pdp_temp[rpnp[i].ptr];
+ }
+ }
+ /* run the rpn calculator */
+ if (rpn_calc(rpnp, &rpnstack, 0, pdp_temp, ds_idx) == -1) {
+ free(rpnp);
+ rpnstack_free(&rpnstack);
+ return -1;
+ }
+ }
+
+ /* make pdp_prep ready for the next run */
+ if (isnan(pdp_new[ds_idx])) {
+ /* this is not realy accurate if we use subsecond data arival time
+ should have thought of it when going subsecond resolution ...
+ sorry next format change we will have it! */
+ scratch[PDP_unkn_sec_cnt].u_cnt = floor(post_int);
+ scratch[PDP_val].u_val = DNAN;
+ } else {
+ scratch[PDP_unkn_sec_cnt].u_cnt = 0;
+ scratch[PDP_val].u_val = pdp_new[ds_idx] / interval * post_int;
+ }
+ rpnstack_free(&rpnstack);
+ return 0;
+}
+
+/*
+ * Iterate over all the RRAs for a given DS and:
+ * 1. Decide whether to schedule a smooth later
+ * 2. Decide whether to skip updating SEASONAL and DEVSEASONAL
+ * 3. Update the CDP
+ *
+ * Returns 0 on success, -1 on error
+ */
+static int update_all_cdp_prep(
+ rrd_t *rrd,
+ unsigned long *rra_step_cnt,
+ unsigned long rra_begin,
+ rrd_file_t *rrd_file,
+ unsigned long elapsed_pdp_st,
+ unsigned long proc_pdp_cnt,
+ rrd_value_t **last_seasonal_coef,
+ rrd_value_t **seasonal_coef,
+ rrd_value_t *pdp_temp,
+ unsigned long *skip_update,
+ int *schedule_smooth)
+{
+ unsigned long rra_idx;
+
+ /* index into the CDP scratch array */
+ enum cf_en current_cf;
+ unsigned long rra_start;
+
+ /* number of rows to be updated in an RRA for a data value. */
+ unsigned long start_pdp_offset;
+
+ rra_start = rra_begin;
+ for (rra_idx = 0; rra_idx < rrd->stat_head->rra_cnt; rra_idx++) {
+ current_cf = cf_conv(rrd->rra_def[rra_idx].cf_nam);
+ start_pdp_offset =
+ rrd->rra_def[rra_idx].pdp_cnt -
+ proc_pdp_cnt % rrd->rra_def[rra_idx].pdp_cnt;
+ skip_update[rra_idx] = 0;
+ if (start_pdp_offset <= elapsed_pdp_st) {
+ rra_step_cnt[rra_idx] = (elapsed_pdp_st - start_pdp_offset) /
+ rrd->rra_def[rra_idx].pdp_cnt + 1;
+ } else {
+ rra_step_cnt[rra_idx] = 0;
+ }
+
+ if (current_cf == CF_SEASONAL || current_cf == CF_DEVSEASONAL) {
+ /* If this is a bulk update, we need to skip ahead in the seasonal arrays
+ * so that they will be correct for the next observed value; note that for
+ * the bulk update itself, no update will occur to DEVSEASONAL or SEASONAL;
+ * futhermore, HWPREDICT and DEVPREDICT will be set to DNAN. */
+ if (rra_step_cnt[rra_idx] > 1) {
+ skip_update[rra_idx] = 1;
+ lookup_seasonal(rrd, rra_idx, rra_start, rrd_file,
+ elapsed_pdp_st, last_seasonal_coef);
+ lookup_seasonal(rrd, rra_idx, rra_start, rrd_file,
+ elapsed_pdp_st + 1, seasonal_coef);
+ }
+ /* periodically run a smoother for seasonal effects */
+ if (do_schedule_smooth(rrd, rra_idx, elapsed_pdp_st)) {
+#ifdef DEBUG
+ fprintf(stderr,
+ "schedule_smooth: cur_row %lu, elapsed_pdp_st %lu, smooth idx %lu\n",
+ rrd->rra_ptr[rra_idx].cur_row, elapsed_pdp_st,
+ rrd->rra_def[rra_idx].par[RRA_seasonal_smooth_idx].
+ u_cnt);
+#endif
+ *schedule_smooth = 1;
+ }
+ }
+ if (rrd_test_error())
+ return -1;
+
+ if (update_cdp_prep
+ (rrd, elapsed_pdp_st, start_pdp_offset, rra_step_cnt, rra_idx,
+ pdp_temp, *last_seasonal_coef, *seasonal_coef,
+ current_cf) == -1) {
+ return -1;
+ }
+ rra_start +=
+ rrd->rra_def[rra_idx].row_cnt * rrd->stat_head->ds_cnt *
+ sizeof(rrd_value_t);
+ }
+ return 0;
+}
+
+/*
+ * Are we due for a smooth? Also increments our position in the burn-in cycle.
+ */
+static int do_schedule_smooth(
+ rrd_t *rrd,
+ unsigned long rra_idx,
+ unsigned long elapsed_pdp_st)
+{
+ unsigned long cdp_idx = rra_idx * (rrd->stat_head->ds_cnt);
+ unsigned long cur_row = rrd->rra_ptr[rra_idx].cur_row;
+ unsigned long row_cnt = rrd->rra_def[rra_idx].row_cnt;
+ unsigned long seasonal_smooth_idx =
+ rrd->rra_def[rra_idx].par[RRA_seasonal_smooth_idx].u_cnt;
+ unsigned long *init_seasonal =
+ &(rrd->cdp_prep[cdp_idx].scratch[CDP_init_seasonal].u_cnt);
+
+ /* Need to use first cdp parameter buffer to track burnin (burnin requires
+ * a specific smoothing schedule). The CDP_init_seasonal parameter is
+ * really an RRA level, not a data source within RRA level parameter, but
+ * the rra_def is read only for rrd_update (not flushed to disk). */
+ if (*init_seasonal > BURNIN_CYCLES) {
+ /* someone has no doubt invented a trick to deal with this wrap around,
+ * but at least this code is clear. */
+ if (seasonal_smooth_idx > cur_row) {
+ /* here elapsed_pdp_st = rra_step_cnt[rra_idx] because of 1-1 mapping
+ * between PDP and CDP */
+ return (cur_row + elapsed_pdp_st >= seasonal_smooth_idx);
+ }
+ /* can't rely on negative numbers because we are working with
+ * unsigned values */
+ return (cur_row + elapsed_pdp_st >= row_cnt
+ && cur_row + elapsed_pdp_st >= row_cnt + seasonal_smooth_idx);
+ }
+ /* mark off one of the burn-in cycles */
+ return (cur_row + elapsed_pdp_st >= row_cnt && ++(*init_seasonal));
+}
+
+/*
+ * For a given RRA, iterate over the data sources and call the appropriate
+ * consolidation function.
+ *
+ * Returns 0 on success, -1 on error.
+ */
+static int update_cdp_prep(
+ rrd_t *rrd,
+ unsigned long elapsed_pdp_st,
+ unsigned long start_pdp_offset,
+ unsigned long *rra_step_cnt,
+ int rra_idx,
+ rrd_value_t *pdp_temp,
+ rrd_value_t *last_seasonal_coef,
+ rrd_value_t *seasonal_coef,
+ int current_cf)
+{
+ unsigned long ds_idx, cdp_idx;
+
+ /* update CDP_PREP areas */
+ /* loop over data soures within each RRA */
+ for (ds_idx = 0; ds_idx < rrd->stat_head->ds_cnt; ds_idx++) {
+
+ cdp_idx = rra_idx * rrd->stat_head->ds_cnt + ds_idx;
+
+ if (rrd->rra_def[rra_idx].pdp_cnt > 1) {
+ update_cdp(rrd->cdp_prep[cdp_idx].scratch, current_cf,
+ pdp_temp[ds_idx], rra_step_cnt[rra_idx],
+ elapsed_pdp_st, start_pdp_offset,
+ rrd->rra_def[rra_idx].pdp_cnt,
+ rrd->rra_def[rra_idx].par[RRA_cdp_xff_val].u_val,
+ rra_idx, ds_idx);
+ } else {
+ /* Nothing to consolidate if there's one PDP per CDP. However, if
+ * we've missed some PDPs, let's update null counters etc. */
+ if (elapsed_pdp_st > 2) {
+ reset_cdp(rrd, elapsed_pdp_st, pdp_temp, last_seasonal_coef,
+ seasonal_coef, rra_idx, ds_idx, cdp_idx,
+ current_cf);
+ }
+ }
+
+ if (rrd_test_error())
+ return -1;
+ } /* endif data sources loop */
+ return 0;
+}
+
+/*
+ * Given the new reading (pdp_temp_val), update or initialize the CDP value,
+ * primary value, secondary value, and # of unknowns.
+ */
+static void update_cdp(
+ unival *scratch,
+ int current_cf,
+ rrd_value_t pdp_temp_val,
+ unsigned long rra_step_cnt,
+ unsigned long elapsed_pdp_st,
+ unsigned long start_pdp_offset,
+ unsigned long pdp_cnt,
+ rrd_value_t xff,
+ int i,
+ int ii)
+{
+ /* shorthand variables */
+ rrd_value_t *cdp_val = &scratch[CDP_val].u_val;
+ rrd_value_t *cdp_primary_val = &scratch[CDP_primary_val].u_val;
+ rrd_value_t *cdp_secondary_val = &scratch[CDP_secondary_val].u_val;
+ unsigned long *cdp_unkn_pdp_cnt = &scratch[CDP_unkn_pdp_cnt].u_cnt;
+
+ if (rra_step_cnt) {
+ /* If we are in this block, as least 1 CDP value will be written to
+ * disk, this is the CDP_primary_val entry. If more than 1 value needs
+ * to be written, then the "fill in" value is the CDP_secondary_val
+ * entry. */
+ if (isnan(pdp_temp_val)) {
+ *cdp_unkn_pdp_cnt += start_pdp_offset;
+ *cdp_secondary_val = DNAN;
+ } else {
+ /* CDP_secondary value is the RRA "fill in" value for intermediary
+ * CDP data entries. No matter the CF, the value is the same because
+ * the average, max, min, and last of a list of identical values is
+ * the same, namely, the value itself. */
+ *cdp_secondary_val = pdp_temp_val;
+ }
+
+ if (*cdp_unkn_pdp_cnt > pdp_cnt * xff) {
+ *cdp_primary_val = DNAN;
+ if (current_cf == CF_AVERAGE) {
+ *cdp_val =
+ initialize_average_carry_over(pdp_temp_val,
+ elapsed_pdp_st,
+ start_pdp_offset, pdp_cnt);
+ } else {
+ *cdp_val = pdp_temp_val;
+ }
+ } else {
+ initialize_cdp_val(scratch, current_cf, pdp_temp_val,
+ elapsed_pdp_st, start_pdp_offset, pdp_cnt);
+ } /* endif meets xff value requirement for a valid value */
+ /* initialize carry over CDP_unkn_pdp_cnt, this must after CDP_primary_val
+ * is set because CDP_unkn_pdp_cnt is required to compute that value. */
+ if (isnan(pdp_temp_val))
+ *cdp_unkn_pdp_cnt = (elapsed_pdp_st - start_pdp_offset) % pdp_cnt;
+ else
+ *cdp_unkn_pdp_cnt = 0;
+ } else { /* rra_step_cnt[i] == 0 */
+
+#ifdef DEBUG
+ if (isnan(*cdp_val)) {
+ fprintf(stderr, "schedule CDP_val update, RRA %d DS %d, DNAN\n",
+ i, ii);
+ } else {
+ fprintf(stderr, "schedule CDP_val update, RRA %d DS %d, %10.2f\n",
+ i, ii, *cdp_val);
+ }
+#endif
+ if (isnan(pdp_temp_val)) {
+ *cdp_unkn_pdp_cnt += elapsed_pdp_st;
+ } else {
+ *cdp_val =
+ calculate_cdp_val(*cdp_val, pdp_temp_val, elapsed_pdp_st,
+ current_cf, i, ii);
+ }
+ }
+}
+
+/*
+ * Set the CDP_primary_val and CDP_val to the appropriate initial value based
+ * on the type of consolidation function.
+ */
+static void initialize_cdp_val(
+ unival *scratch,
+ int current_cf,
+ rrd_value_t pdp_temp_val,
+ unsigned long elapsed_pdp_st,
+ unsigned long start_pdp_offset,
+ unsigned long pdp_cnt)
+{
+ rrd_value_t cum_val, cur_val;
+
+ switch (current_cf) {
+ case CF_AVERAGE:
+ cum_val = IFDNAN(scratch[CDP_val].u_val, 0.0);
+ cur_val = IFDNAN(pdp_temp_val, 0.0);
+ scratch[CDP_primary_val].u_val =
+ (cum_val + cur_val * start_pdp_offset) /
+ (pdp_cnt - scratch[CDP_unkn_pdp_cnt].u_cnt);
+ scratch[CDP_val].u_val =
+ initialize_average_carry_over(pdp_temp_val, elapsed_pdp_st,
+ start_pdp_offset, pdp_cnt);
+ break;
+ case CF_MAXIMUM:
+ cum_val = IFDNAN(scratch[CDP_val].u_val, -DINF);
+ cur_val = IFDNAN(pdp_temp_val, -DINF);
+#if 0
+#ifdef DEBUG
+ if (isnan(scratch[CDP_val].u_val) && isnan(pdp_temp)) {
+ fprintf(stderr,
+ "RRA %lu, DS %lu, both CDP_val and pdp_temp are DNAN!",
+ i, ii);
+ exit(-1);
+ }
+#endif
+#endif
+ if (cur_val > cum_val)
+ scratch[CDP_primary_val].u_val = cur_val;
+ else
+ scratch[CDP_primary_val].u_val = cum_val;
+ /* initialize carry over value */
+ scratch[CDP_val].u_val = pdp_temp_val;
+ break;
+ case CF_MINIMUM:
+ cum_val = IFDNAN(scratch[CDP_val].u_val, DINF);
+ cur_val = IFDNAN(pdp_temp_val, DINF);
+#if 0
+#ifdef DEBUG
+ if (isnan(scratch[CDP_val].u_val) && isnan(pdp_temp)) {
+ fprintf(stderr,
+ "RRA %lu, DS %lu, both CDP_val and pdp_temp are DNAN!", i,
+ ii);
+ exit(-1);
+ }
+#endif
+#endif
+ if (cur_val < cum_val)
+ scratch[CDP_primary_val].u_val = cur_val;
+ else
+ scratch[CDP_primary_val].u_val = cum_val;
+ /* initialize carry over value */
+ scratch[CDP_val].u_val = pdp_temp_val;
+ break;
+ case CF_LAST:
+ default:
+ scratch[CDP_primary_val].u_val = pdp_temp_val;
+ /* initialize carry over value */
+ scratch[CDP_val].u_val = pdp_temp_val;
+ break;
+ }
+}
+
+/*
+ * Update the consolidation function for Holt-Winters functions as
+ * well as other functions that don't actually consolidate multiple
+ * PDPs.
+ */
+static void reset_cdp(
+ rrd_t *rrd,
+ unsigned long elapsed_pdp_st,
+ rrd_value_t *pdp_temp,
+ rrd_value_t *last_seasonal_coef,
+ rrd_value_t *seasonal_coef,
+ int rra_idx,
+ int ds_idx,
+ int cdp_idx,
+ enum cf_en current_cf)
+{
+ unival *scratch = rrd->cdp_prep[cdp_idx].scratch;
+
+ switch (current_cf) {
+ case CF_AVERAGE:
+ default:
+ scratch[CDP_primary_val].u_val = pdp_temp[ds_idx];
+ scratch[CDP_secondary_val].u_val = pdp_temp[ds_idx];
+ break;
+ case CF_SEASONAL:
+ case CF_DEVSEASONAL:
+ /* need to update cached seasonal values, so they are consistent
+ * with the bulk update */
+ /* WARNING: code relies on the fact that CDP_hw_last_seasonal and
+ * CDP_last_deviation are the same. */
+ scratch[CDP_hw_last_seasonal].u_val = last_seasonal_coef[ds_idx];
+ scratch[CDP_hw_seasonal].u_val = seasonal_coef[ds_idx];
+ break;
+ case CF_HWPREDICT:
+ case CF_MHWPREDICT:
+ /* need to update the null_count and last_null_count.
+ * even do this for non-DNAN pdp_temp because the
+ * algorithm is not learning from batch updates. */
+ scratch[CDP_null_count].u_cnt += elapsed_pdp_st;
+ scratch[CDP_last_null_count].u_cnt += elapsed_pdp_st - 1;
+ /* fall through */
+ case CF_DEVPREDICT:
+ scratch[CDP_primary_val].u_val = DNAN;
+ scratch[CDP_secondary_val].u_val = DNAN;
+ break;
+ case CF_FAILURES:
+ /* do not count missed bulk values as failures */
+ scratch[CDP_primary_val].u_val = 0;
+ scratch[CDP_secondary_val].u_val = 0;
+ /* need to reset violations buffer.
+ * could do this more carefully, but for now, just
+ * assume a bulk update wipes away all violations. */
+ erase_violations(rrd, cdp_idx, rra_idx);
+ break;
+ }
+}
+
+static rrd_value_t initialize_average_carry_over(
+ rrd_value_t pdp_temp_val,
+ unsigned long elapsed_pdp_st,
+ unsigned long start_pdp_offset,
+ unsigned long pdp_cnt)
+{
+ /* initialize carry over value */
+ if (isnan(pdp_temp_val)) {
+ return DNAN;
+ }
+ return pdp_temp_val * ((elapsed_pdp_st - start_pdp_offset) % pdp_cnt);
+}
+
+/*
+ * Update or initialize a CDP value based on the consolidation
+ * function.
+ *
+ * Returns the new value.
+ */
+static rrd_value_t calculate_cdp_val(
+ rrd_value_t cdp_val,
+ rrd_value_t pdp_temp_val,
+ unsigned long elapsed_pdp_st,
+ int current_cf,
+#ifdef DEBUG
+ int i,
+ int ii
+#else
+ int UNUSED(i),
+ int UNUSED(ii)
+#endif
+ )
+{
+ if (isnan(cdp_val)) {
+ if (current_cf == CF_AVERAGE) {
+ pdp_temp_val *= elapsed_pdp_st;
+ }
+#ifdef DEBUG
+ fprintf(stderr, "Initialize CDP_val for RRA %d DS %d: %10.2f\n",
+ i, ii, pdp_temp_val);
+#endif
+ return pdp_temp_val;
+ }
+ if (current_cf == CF_AVERAGE)
+ return cdp_val + pdp_temp_val * elapsed_pdp_st;
+ if (current_cf == CF_MINIMUM)
+ return (pdp_temp_val < cdp_val) ? pdp_temp_val : cdp_val;
+ if (current_cf == CF_MAXIMUM)
+ return (pdp_temp_val > cdp_val) ? pdp_temp_val : cdp_val;
+
+ return pdp_temp_val;
+}
+
+/*
+ * For each RRA, update the seasonal values and then call update_aberrant_CF
+ * for each data source.
+ *
+ * Return 0 on success, -1 on error.
+ */
+static int update_aberrant_cdps(
+ rrd_t *rrd,
+ rrd_file_t *rrd_file,
+ unsigned long rra_begin,
+ unsigned long elapsed_pdp_st,
+ rrd_value_t *pdp_temp,
+ rrd_value_t **seasonal_coef)
+{
+ unsigned long rra_idx, ds_idx, j;
+
+ /* number of PDP steps since the last update that
+ * are assigned to the first CDP to be generated
+ * since the last update. */
+ unsigned short scratch_idx;
+ unsigned long rra_start;
+ enum cf_en current_cf;
+
+ /* this loop is only entered if elapsed_pdp_st < 3 */
+ for (j = elapsed_pdp_st, scratch_idx = CDP_primary_val;
+ j > 0 && j < 3; j--, scratch_idx = CDP_secondary_val) {
+ rra_start = rra_begin;
+ for (rra_idx = 0; rra_idx < rrd->stat_head->rra_cnt; rra_idx++) {
+ if (rrd->rra_def[rra_idx].pdp_cnt == 1) {
+ current_cf = cf_conv(rrd->rra_def[rra_idx].cf_nam);
+ if (current_cf == CF_SEASONAL || current_cf == CF_DEVSEASONAL) {
+ if (scratch_idx == CDP_primary_val) {
+ lookup_seasonal(rrd, rra_idx, rra_start, rrd_file,
+ elapsed_pdp_st + 1, seasonal_coef);
+ } else {
+ lookup_seasonal(rrd, rra_idx, rra_start, rrd_file,
+ elapsed_pdp_st + 2, seasonal_coef);
+ }
+ }
+ if (rrd_test_error())
+ return -1;
+ /* loop over data soures within each RRA */
+ for (ds_idx = 0; ds_idx < rrd->stat_head->ds_cnt; ds_idx++) {
+ update_aberrant_CF(rrd, pdp_temp[ds_idx], current_cf,
+ rra_idx * (rrd->stat_head->ds_cnt) +
+ ds_idx, rra_idx, ds_idx, scratch_idx,
+ *seasonal_coef);
+ }
+ }
+ rra_start += rrd->rra_def[rra_idx].row_cnt
+ * rrd->stat_head->ds_cnt * sizeof(rrd_value_t);
+ }
+ }
+ return 0;
+}
+
+/*
+ * Move sequentially through the file, writing one RRA at a time. Note this
+ * architecture divorces the computation of CDP with flushing updated RRA
+ * entries to disk.
+ *
+ * Return 0 on success, -1 on error.
+ */
+static int write_to_rras(
+ rrd_t *rrd,
+ rrd_file_t *rrd_file,
+ unsigned long *rra_step_cnt,
+ unsigned long rra_begin,
+ time_t current_time,
+ unsigned long *skip_update,
+ rrd_info_t ** pcdp_summary)
+{
+ unsigned long rra_idx;
+ unsigned long rra_start;
+ time_t rra_time = 0; /* time of update for a RRA */
+
+ unsigned long ds_cnt = rrd->stat_head->ds_cnt;
+
+ /* Ready to write to disk */
+ rra_start = rra_begin;
+
+ for (rra_idx = 0; rra_idx < rrd->stat_head->rra_cnt; rra_idx++) {
+ rra_def_t *rra_def = &rrd->rra_def[rra_idx];
+ rra_ptr_t *rra_ptr = &rrd->rra_ptr[rra_idx];
+
+ /* for cdp_prep */
+ unsigned short scratch_idx;
+ unsigned long step_subtract;
+
+ for (scratch_idx = CDP_primary_val,
+ step_subtract = 1;
+ rra_step_cnt[rra_idx] > 0;
+ rra_step_cnt[rra_idx]--,
+ scratch_idx = CDP_secondary_val,
+ step_subtract = 2) {
+
+ off_t rra_pos_new;
+#ifdef DEBUG
+ fprintf(stderr, " -- RRA Preseek %ld\n", rrd_file->pos);
+#endif
+ /* increment, with wrap-around */
+ if (++rra_ptr->cur_row >= rra_def->row_cnt)
+ rra_ptr->cur_row = 0;
+
+ /* we know what our position should be */
+ rra_pos_new = rra_start
+ + ds_cnt * rra_ptr->cur_row * sizeof(rrd_value_t);
+
+ /* re-seek if the position is wrong or we wrapped around */
+ if (rra_pos_new != rrd_file->pos) {
+ if (rrd_seek(rrd_file, rra_pos_new, SEEK_SET) != 0) {
+ rrd_set_error("seek error in rrd");
+ return -1;
+ }
+ }
+#ifdef DEBUG
+ fprintf(stderr, " -- RRA Postseek %ld\n", rrd_file->pos);
+#endif
+
+ if (skip_update[rra_idx])
+ continue;
+
+ if (*pcdp_summary != NULL) {
+ unsigned long step_time = rra_def->pdp_cnt * rrd->stat_head->pdp_step;
+
+ rra_time = (current_time - current_time % step_time)
+ - ((rra_step_cnt[rra_idx] - step_subtract) * step_time);
+ }
+
+ if (write_RRA_row
+ (rrd_file, rrd, rra_idx, scratch_idx,
+ pcdp_summary, rra_time) == -1)
+ return -1;
+ }
+
+ rra_start += rra_def->row_cnt * ds_cnt * sizeof(rrd_value_t);
+ } /* RRA LOOP */
+
+ return 0;
+}
+
+/*
+ * Write out one row of values (one value per DS) to the archive.
+ *
+ * Returns 0 on success, -1 on error.
+ */
+static int write_RRA_row(
+ rrd_file_t *rrd_file,
+ rrd_t *rrd,
+ unsigned long rra_idx,
+ unsigned short CDP_scratch_idx,
+ rrd_info_t ** pcdp_summary,
+ time_t rra_time)
+{
+ unsigned long ds_idx, cdp_idx;
+ rrd_infoval_t iv;
+
+ for (ds_idx = 0; ds_idx < rrd->stat_head->ds_cnt; ds_idx++) {
+ /* compute the cdp index */
+ cdp_idx = rra_idx * (rrd->stat_head->ds_cnt) + ds_idx;
+#ifdef DEBUG
+ fprintf(stderr, " -- RRA WRITE VALUE %e, at %ld CF:%s\n",
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val,
+ rrd_file->pos, rrd->rra_def[rra_idx].cf_nam);
+#endif
+ if (*pcdp_summary != NULL) {
+ iv.u_val = rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val;
+ /* append info to the return hash */
+ *pcdp_summary = rrd_info_push(*pcdp_summary,
+ sprintf_alloc
+ ("[%d]RRA[%s][%lu]DS[%s]", rra_time,
+ rrd->rra_def[rra_idx].cf_nam,
+ rrd->rra_def[rra_idx].pdp_cnt,
+ rrd->ds_def[ds_idx].ds_nam),
+ RD_I_VAL, iv);
+ }
+ if (rrd_write(rrd_file,
+ &(rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].
+ u_val), sizeof(rrd_value_t)) != sizeof(rrd_value_t)) {
+ rrd_set_error("writing rrd: %s", rrd_strerror(errno));
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Call apply_smoother for all DEVSEASONAL and SEASONAL RRAs.
+ *
+ * Returns 0 on success, -1 otherwise
+ */
+static int smooth_all_rras(
+ rrd_t *rrd,
+ rrd_file_t *rrd_file,
+ unsigned long rra_begin)
+{
+ unsigned long rra_start = rra_begin;
+ unsigned long rra_idx;
+
+ for (rra_idx = 0; rra_idx < rrd->stat_head->rra_cnt; ++rra_idx) {
+ if (cf_conv(rrd->rra_def[rra_idx].cf_nam) == CF_DEVSEASONAL ||
+ cf_conv(rrd->rra_def[rra_idx].cf_nam) == CF_SEASONAL) {
+#ifdef DEBUG
+ fprintf(stderr, "Running smoother for rra %lu\n", rra_idx);
+#endif
+ apply_smoother(rrd, rra_idx, rra_start, rrd_file);
+ if (rrd_test_error())
+ return -1;
+ }
+ rra_start += rrd->rra_def[rra_idx].row_cnt
+ * rrd->stat_head->ds_cnt * sizeof(rrd_value_t);
+ }
+ return 0;
+}
+
+#ifndef HAVE_MMAP
+/*
+ * Flush changes to disk (unless we're using mmap)
+ *
+ * Returns 0 on success, -1 otherwise
+ */
+static int write_changes_to_disk(
+ rrd_t *rrd,
+ rrd_file_t *rrd_file,
+ int version)
+{
+ /* we just need to write back the live header portion now */
+ if (rrd_seek(rrd_file, (sizeof(stat_head_t)
+ + sizeof(ds_def_t) * rrd->stat_head->ds_cnt
+ + sizeof(rra_def_t) * rrd->stat_head->rra_cnt),
+ SEEK_SET) != 0) {
+ rrd_set_error("seek rrd for live header writeback");
+ return -1;
+ }
+ if (version >= 3) {
+ if (rrd_write(rrd_file, rrd->live_head,
+ sizeof(live_head_t) * 1) != sizeof(live_head_t) * 1) {
+ rrd_set_error("rrd_write live_head to rrd");
+ return -1;
+ }
+ } else {
+ if (rrd_write(rrd_file, rrd->legacy_last_up,
+ sizeof(time_t) * 1) != sizeof(time_t) * 1) {
+ rrd_set_error("rrd_write live_head to rrd");
+ return -1;
+ }
+ }
+
+
+ if (rrd_write(rrd_file, rrd->pdp_prep,
+ sizeof(pdp_prep_t) * rrd->stat_head->ds_cnt)
+ != (ssize_t) (sizeof(pdp_prep_t) * rrd->stat_head->ds_cnt)) {
+ rrd_set_error("rrd_write pdp_prep to rrd");
+ return -1;
+ }
+
+ if (rrd_write(rrd_file, rrd->cdp_prep,
+ sizeof(cdp_prep_t) * rrd->stat_head->rra_cnt *
+ rrd->stat_head->ds_cnt)
+ != (ssize_t) (sizeof(cdp_prep_t) * rrd->stat_head->rra_cnt *
+ rrd->stat_head->ds_cnt)) {
+
+ rrd_set_error("rrd_write cdp_prep to rrd");
+ return -1;
+ }
+
+ if (rrd_write(rrd_file, rrd->rra_ptr,
+ sizeof(rra_ptr_t) * rrd->stat_head->rra_cnt)
+ != (ssize_t) (sizeof(rra_ptr_t) * rrd->stat_head->rra_cnt)) {
+ rrd_set_error("rrd_write rra_ptr to rrd");
+ return -1;
+ }
+ return 0;
+}
+#endif
diff --git a/program/src/rrd_version.c b/program/src/rrd_version.c
--- /dev/null
@@ -0,0 +1,21 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ *****************************************************************************
+ * rrd_version Return
+ *****************************************************************************
+ * Initial version by Burton Strauss, ntopSupport.com - 5/2005
+ *****************************************************************************/
+
+#include "rrd_tool.h"
+
+double rrd_version(
+ void)
+{
+ return NUMVERS;
+}
+
+char *rrd_strversion(
+ void)
+{
+ return PACKAGE_VERSION;
+}
diff --git a/program/src/rrd_xport.c b/program/src/rrd_xport.c
--- /dev/null
+++ b/program/src/rrd_xport.c
@@ -0,0 +1,340 @@
+/****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ ****************************************************************************
+ * rrd_xport.c export RRD data
+ ****************************************************************************/
+
+#include <sys/stat.h>
+
+#include "rrd_tool.h"
+#include "rrd_graph.h"
+#include "rrd_xport.h"
+#include "unused.h"
+
+#if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
+#include <io.h>
+#include <fcntl.h>
+#endif
+
+
+int rrd_xport(
+ int,
+ char **,
+ int *,
+ time_t *,
+ time_t *,
+ unsigned long *,
+ unsigned long *,
+ char ***,
+ rrd_value_t **);
+
+int rrd_xport_fn(
+ image_desc_t *,
+ time_t *,
+ time_t *,
+ unsigned long *,
+ unsigned long *,
+ char ***,
+ rrd_value_t **);
+
+
+
+
+int rrd_xport(
+ int argc,
+ char **argv,
+ int UNUSED(*xsize),
+ time_t *start,
+ time_t *end, /* which time frame do you want ?
+ * will be changed to represent reality */
+ unsigned long *step, /* which stepsize do you want?
+ * will be changed to represent reality */
+ unsigned long *col_cnt, /* number of data columns in the result */
+ char ***legend_v, /* legend entries */
+ rrd_value_t **data)
+{ /* two dimensional array containing the data */
+
+ image_desc_t im;
+ time_t start_tmp = 0, end_tmp = 0;
+ rrd_time_value_t start_tv, end_tv;
+ char *parsetime_error = NULL;
+ struct option long_options[] = {
+ {"start", required_argument, 0, 's'},
+ {"end", required_argument, 0, 'e'},
+ {"maxrows", required_argument, 0, 'm'},
+ {"step", required_argument, 0, 261},
+ {"enumds", no_argument, 0, 262}, /* these are handled in the frontend ... */
+ {0, 0, 0, 0}
+ };
+
+ optind = 0;
+ opterr = 0; /* initialize getopt */
+
+ rrd_graph_init(&im);
+
+ rrd_parsetime("end-24h", &start_tv);
+ rrd_parsetime("now", &end_tv);
+
+ while (1) {
+ int option_index = 0;
+ int opt;
+
+ opt = getopt_long(argc, argv, "s:e:m:", long_options, &option_index);
+
+ if (opt == EOF)
+ break;
+
+ switch (opt) {
+ case 261:
+ im.step = atoi(optarg);
+ break;
+ case 262:
+ break;
+ case 's':
+ if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
+ rrd_set_error("start time: %s", parsetime_error);
+ return -1;
+ }
+ break;
+ case 'e':
+ if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
+ rrd_set_error("end time: %s", parsetime_error);
+ return -1;
+ }
+ break;
+ case 'm':
+ im.xsize = atol(optarg);
+ if (im.xsize < 10) {
+ rrd_set_error("maxrows below 10 rows");
+ return -1;
+ }
+ break;
+ case '?':
+ rrd_set_error("unknown option '%s'", argv[optind - 1]);
+ return -1;
+ }
+ }
+
+ if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
+ return -1;
+ }
+
+ if (start_tmp < 3600 * 24 * 365 * 10) {
+ rrd_set_error("the first entry to fetch should be after 1980 (%ld)",
+ start_tmp);
+ return -1;
+ }
+
+ if (end_tmp < start_tmp) {
+ rrd_set_error("start (%ld) should be less than end (%ld)",
+ start_tmp, end_tmp);
+ return -1;
+ }
+
+ im.start = start_tmp;
+ im.end = end_tmp;
+ im.step = max((long) im.step, (im.end - im.start) / im.xsize);
+
+ rrd_graph_script(argc, argv, &im, 0);
+ if (rrd_test_error()) {
+ im_free(&im);
+ return -1;
+ }
+
+ if (im.gdes_c == 0) {
+ rrd_set_error("can't make a graph without contents");
+ im_free(&im);
+ return (-1);
+ }
+
+ if (rrd_xport_fn(&im, start, end, step, col_cnt, legend_v, data) == -1) {
+ im_free(&im);
+ return -1;
+ }
+
+ im_free(&im);
+ return 0;
+}
+
+
+
+int rrd_xport_fn(
+ image_desc_t *im,
+ time_t *start,
+ time_t *end, /* which time frame do you want ?
+ * will be changed to represent reality */
+ unsigned long *step, /* which stepsize do you want?
+ * will be changed to represent reality */
+ unsigned long *col_cnt, /* number of data columns in the result */
+ char ***legend_v, /* legend entries */
+ rrd_value_t **data)
+{ /* two dimensional array containing the data */
+
+ int i = 0, j = 0;
+ unsigned long *ds_cnt; /* number of data sources in file */
+ unsigned long col, dst_row, row_cnt;
+ rrd_value_t *srcptr, *dstptr;
+
+ unsigned long nof_xports = 0;
+ unsigned long xport_counter = 0;
+ int *ref_list;
+ rrd_value_t **srcptr_list;
+ char **legend_list;
+ int ii = 0;
+
+ time_t start_tmp = 0;
+ time_t end_tmp = 0;
+ unsigned long step_tmp = 1;
+
+ /* pull the data from the rrd files ... */
+ if (data_fetch(im) == -1)
+ return -1;
+
+ /* evaluate CDEF operations ... */
+ if (data_calc(im) == -1)
+ return -1;
+
+ /* how many xports? */
+ for (i = 0; i < im->gdes_c; i++) {
+ switch (im->gdes[i].gf) {
+ case GF_XPORT:
+ nof_xports++;
+ break;
+ default:
+ break;
+ }
+ }
+ if (nof_xports == 0) {
+ rrd_set_error("no XPORT found, nothing to do");
+ return -1;
+ }
+
+ /* a list of referenced gdes */
+ ref_list = malloc(sizeof(int) * nof_xports);
+ if (ref_list == NULL)
+ return -1;
+
+ /* a list to save pointers into each gdes data */
+ srcptr_list = malloc(sizeof(srcptr) * nof_xports);
+ if (srcptr_list == NULL) {
+ free(ref_list);
+ return -1;
+ }
+
+ /* a list to save pointers to the column's legend entry */
+ /* this is a return value! */
+ legend_list = malloc(sizeof(char *) * nof_xports);
+ if (legend_list == NULL) {
+ free(srcptr_list);
+ free(ref_list);
+ return -1;
+ }
+
+ /* find referenced gdes and save their index and */
+ /* a pointer into their data */
+ for (i = 0; i < im->gdes_c; i++) {
+ switch (im->gdes[i].gf) {
+ case GF_XPORT:
+ ii = im->gdes[i].vidx;
+ if (xport_counter > nof_xports) {
+ rrd_set_error("too many xports: should not happen. Hmmm");
+ free(srcptr_list);
+ free(ref_list);
+ free(legend_list);
+ return -1;
+ }
+ srcptr_list[xport_counter] = im->gdes[ii].data;
+ ref_list[xport_counter++] = i;
+ break;
+ default:
+ break;
+ }
+ }
+
+ start_tmp = im->gdes[0].start;
+ end_tmp = im->gdes[0].end;
+ step_tmp = im->gdes[0].step;
+
+ /* fill some return values */
+ *col_cnt = nof_xports;
+ *start = start_tmp;
+ *end = end_tmp;
+ *step = step_tmp;
+
+ row_cnt = ((*end) - (*start)) / (*step);
+
+ /* room for rearranged data */
+ /* this is a return value! */
+ if (((*data) =
+ malloc((*col_cnt) * row_cnt * sizeof(rrd_value_t))) == NULL) {
+ free(srcptr_list);
+ free(ref_list);
+ free(legend_list);
+ rrd_set_error("malloc xport data area");
+ return (-1);
+ }
+ dstptr = (*data);
+
+ j = 0;
+ for (i = 0; i < im->gdes_c; i++) {
+ switch (im->gdes[i].gf) {
+ case GF_XPORT:
+ /* reserve room for one legend entry */
+ /* is FMT_LEG_LEN + 5 the correct size? */
+ if ((legend_list[j] =
+ malloc(sizeof(char) * (FMT_LEG_LEN + 5))) == NULL) {
+ free(srcptr_list);
+ free(ref_list);
+ free(*data);
+ *data = NULL;
+ while (--j > -1)
+ free(legend_list[j]);
+ free(legend_list);
+ rrd_set_error("malloc xport legend entry");
+ return (-1);
+ }
+
+ if (im->gdes[i].legend)
+ /* omit bounds check, should have the same size */
+ strcpy(legend_list[j++], im->gdes[i].legend);
+ else
+ legend_list[j++][0] = '\0';
+
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* fill data structure */
+ for (dst_row = 0; (int) dst_row < (int) row_cnt; dst_row++) {
+ for (i = 0; i < (int) nof_xports; i++) {
+ j = ref_list[i];
+ ii = im->gdes[j].vidx;
+ ds_cnt = &im->gdes[ii].ds_cnt;
+
+ srcptr = srcptr_list[i];
+ for (col = 0; col < (*ds_cnt); col++) {
+ rrd_value_t newval = DNAN;
+
+ newval = srcptr[col];
+
+ if (im->gdes[ii].ds_namv && im->gdes[ii].ds_nam) {
+ if (strcmp(im->gdes[ii].ds_namv[col], im->gdes[ii].ds_nam)
+ == 0)
+ (*dstptr++) = newval;
+ } else {
+ (*dstptr++) = newval;
+ }
+
+ }
+ srcptr_list[i] += (*ds_cnt);
+ }
+ }
+
+ *legend_v = legend_list;
+ free(srcptr_list);
+ free(ref_list);
+ return 0;
+
+}
diff --git a/program/src/rrd_xport.h b/program/src/rrd_xport.h
--- /dev/null
+++ b/program/src/rrd_xport.h
@@ -0,0 +1,34 @@
+/****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ ****************************************************************************
+ * rrd_xport.h contains XML related constants
+ ****************************************************************************/
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef _RRD_XPORT_H
+#define _RRD_XPORT_H
+
+#define XML_ENCODING "ISO-8859-1"
+#define ROOT_TAG "xport"
+#define META_TAG "meta"
+#define META_START_TAG "start"
+#define META_STEP_TAG "step"
+#define META_END_TAG "end"
+#define META_ROWS_TAG "rows"
+#define META_COLS_TAG "columns"
+#define LEGEND_TAG "legend"
+#define LEGEND_ENTRY_TAG "entry"
+#define DATA_TAG "data"
+#define DATA_ROW_TAG "row"
+#define COL_TIME_TAG "t"
+#define COL_DATA_TAG "v"
+
+
+#endif
+
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/program/src/rrdupdate.c b/program/src/rrdupdate.c
--- /dev/null
+++ b/program/src/rrdupdate.c
@@ -0,0 +1,38 @@
+/*****************************************************************************
+ * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
+ *****************************************************************************
+ * rrdupdate.c Main program for the (standalone) rrdupdate utility
+ *****************************************************************************
+ * $Id$
+ *****************************************************************************/
+
+#if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__) && !defined(HAVE_CONFIG_H)
+#include "../win32/config.h"
+#else
+#ifdef HAVE_CONFIG_H
+#include "../rrd_config.h"
+#endif
+#endif
+
+#include "rrd.h"
+
+int main(
+ int argc,
+ char **argv)
+{
+ rrd_update(argc, argv);
+ if (rrd_test_error()) {
+ printf("RRDtool " PACKAGE_VERSION
+ " Copyright by Tobi Oetiker, 1997-2008\n\n"
+ "Usage: rrdupdate filename\n"
+ "\t\t\t[--template|-t ds-name:ds-name:...]\n"
+ "\t\t\ttime|N:value[:value...]\n\n"
+ "\t\t\tat-time@value[:value...]\n\n"
+ "\t\t\t[ time:value[:value...] ..]\n\n");
+
+ printf("ERROR: %s\n", rrd_get_error());
+ rrd_clear_error();
+ return 1;
+ }
+ return 0;
+}
diff --git a/program/src/strftime.c b/program/src/strftime.c
--- /dev/null
+++ b/program/src/strftime.c
@@ -0,0 +1,359 @@
+/**
+ *
+ * strftime.c
+ *
+ * implements the ansi c function strftime()
+ *
+ * written 6 september 1989 by jim nutt
+ * released into the public domain by jim nutt
+ *
+ * modified 21-Oct-89 by Rob Duff
+ *
+ * modified 08-Dec-04 by Tobi Oetiker (added %V)
+**/
+
+#include <stddef.h> /* for size_t */
+#include <stdarg.h> /* for va_arg */
+#include <time.h> /* for struct tm */
+#include "strftime.h"
+
+/* Define your own defaults in config.h if necessary */
+#if defined(TZNAME_STD) && defined(TZNAME_DST)
+char *tzname_[2] = { TZNAME_STD, TZNAME_DST };
+#else
+#define tzname_ tzname
+#endif
+
+static char *aday[] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+
+static char *day[] = {
+ "Sunday", "Monday", "Tuesday", "Wednesday",
+ "Thursday", "Friday", "Saturday"
+};
+
+static char *amonth[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+static char *month[] = {
+ "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"
+};
+
+static char buf[26];
+
+static void strfmt(
+ char *str,
+ const char *fmt,
+ ...);
+
+/**
+ *
+ * size_t strftime_(char *str,
+ * size_t maxs,
+ * const char *fmt,
+ * const struct tm *t)
+ *
+ * this functions acts much like a sprintf for time/date output.
+ * given a pointer to an output buffer, a format string and a
+ * time, it copies the time to the output buffer formatted in
+ * accordance with the format string. the parameters are used
+ * as follows:
+ *
+ * str is a pointer to the output buffer, there should
+ * be at least maxs characters available at the address
+ * pointed to by str.
+ *
+ * maxs is the maximum number of characters to be copied
+ * into the output buffer, included the '\0' terminator
+ *
+ * fmt is the format string. a percent sign (%) is used
+ * to indicate that the following character is a special
+ * format character. the following are valid format
+ * characters:
+ *
+ * %A full weekday name (Monday)
+ * %a abbreviated weekday name (Mon)
+ * %B full month name (January)
+ * %b abbreviated month name (Jan)
+ * %c standard date and time representation
+ * %d day-of-month (01-31)
+ * %H hour (24 hour clock) (00-23)
+ * %I hour (12 hour clock) (01-12)
+ * %j day-of-year (001-366)
+ * %M minute (00-59)
+ * %m month (01-12)
+ * %p local equivalent of AM or PM
+ * %S second (00-59)
+ * %U week-of-year, first day sunday (00-53)
+ * %W week-of-year, first day monday (00-53)
+ * %V ISO 8601 Week number
+ * %w weekday (0-6, sunday is 0)
+ * %X standard time representation
+ * %x standard date representation
+ * %Y year with century
+ * %y year without century (00-99)
+ * %Z timezone name
+ * %% percent sign
+ *
+ * the standard date string is equivalent to:
+ *
+ * %a %b %d %Y
+ *
+ * the standard time string is equivalent to:
+ *
+ * %H:%M:%S
+ *
+ * the standard date and time string is equivalent to:
+ *
+ * %a %b %d %H:%M:%S %Y
+ *
+ * strftime_() returns the number of characters placed in the
+ * buffer, not including the terminating \0, or zero if more
+ * than maxs characters were produced.
+ *
+**/
+
+size_t strftime_(
+ char *s,
+ size_t maxs,
+ const char *f,
+ const struct tm *t)
+{
+ int w, d;
+ char *p, *q, *r;
+
+ p = s;
+ q = s + maxs - 1;
+ while ((*f != '\0')) {
+ if (*f++ == '%') {
+ r = buf;
+ switch (*f++) {
+ case '%':
+ r = "%";
+ break;
+
+ case 'a':
+ r = aday[t->tm_wday];
+ break;
+
+ case 'A':
+ r = day[t->tm_wday];
+ break;
+
+ case 'b':
+ r = amonth[t->tm_mon];
+ break;
+
+ case 'B':
+ r = month[t->tm_mon];
+ break;
+
+ case 'c':
+ strfmt(r, "%0 %0 %2 %2:%2:%2 %4",
+ aday[t->tm_wday], amonth[t->tm_mon],
+ t->tm_mday, t->tm_hour, t->tm_min,
+ t->tm_sec, t->tm_year + 1900);
+ break;
+
+ case 'd':
+ strfmt(r, "%2", t->tm_mday);
+ break;
+
+ case 'H':
+ strfmt(r, "%2", t->tm_hour);
+ break;
+
+ case 'I':
+ strfmt(r, "%2", (t->tm_hour % 12) ? t->tm_hour % 12 : 12);
+ break;
+
+ case 'j':
+ strfmt(r, "%3", t->tm_yday + 1);
+ break;
+
+ case 'm':
+ strfmt(r, "%2", t->tm_mon + 1);
+ break;
+
+ case 'M':
+ strfmt(r, "%2", t->tm_min);
+ break;
+
+ case 'p':
+ r = (t->tm_hour > 11) ? "PM" : "AM";
+ break;
+
+ case 'S':
+ strfmt(r, "%2", t->tm_sec);
+ break;
+
+ case 'U':
+ w = t->tm_yday / 7;
+ if (t->tm_yday % 7 > t->tm_wday)
+ w++;
+ strfmt(r, "%2", w);
+ break;
+
+ case 'W':
+ w = t->tm_yday / 7;
+ if (t->tm_yday % 7 > (t->tm_wday + 6) % 7)
+ w++;
+ strfmt(r, "%2", w);
+ break;
+
+ case 'V':
+
+ /* ISO 8601 Week Of Year:
+ If the week (Monday - Sunday) containing January 1 has four or more
+ days in the new year, then it is week 1; otherwise it is week 53 of
+ the previous year and the next week is week one. */
+
+ w = (t->tm_yday + 7 - (t->tm_wday ? t->tm_wday - 1 : 6)) / 7;
+ d = (t->tm_yday + 7 - (t->tm_wday ? t->tm_wday - 1 : 6)) % 7;
+
+ if (d >= 4) {
+ w++;
+ } else if (w == 0) {
+ w = 53;
+ }
+ strfmt(r, "%2", w);
+ break;
+
+ case 'w':
+ strfmt(r, "%1", t->tm_wday);
+ break;
+
+ case 'x':
+ strfmt(r, "%3s %3s %2 %4", aday[t->tm_wday],
+ amonth[t->tm_mon], t->tm_mday, t->tm_year + 1900);
+ break;
+
+ case 'X':
+ strfmt(r, "%2:%2:%2", t->tm_hour, t->tm_min, t->tm_sec);
+ break;
+
+ case 'y':
+ strfmt(r, "%2", t->tm_year % 100);
+ break;
+
+ case 'Y':
+ strfmt(r, "%4", t->tm_year + 1900);
+ break;
+
+ case 'Z':
+ r = (t->tm_isdst && tzname_[1][0]) ? tzname_[1] : tzname_[0];
+ break;
+
+ default:
+ buf[0] = '%'; /* reconstruct the format */
+ buf[1] = f[-1];
+ buf[2] = '\0';
+ if (buf[1] == 0)
+ f--; /* back up if at end of string */
+ }
+ while (*r) {
+ if (p == q) {
+ *q = '\0';
+ return 0;
+ }
+ *p++ = *r++;
+ }
+ } else {
+ if (p == q) {
+ *q = '\0';
+ return 0;
+ }
+ *p++ = f[-1];
+ }
+ }
+ *p = '\0';
+ return p - s;
+}
+
+/*
+ * stdarg.h
+ *
+typedef void *va_list;
+#define va_start(vp,v) (vp=((char*)&v)+sizeof(v))
+#define va_arg(vp,t) (*((t*)(vp))++)
+#define va_end(vp)
+ *
+ */
+
+static int powers[5] = { 1, 10, 100, 1000, 10000 };
+
+/**
+ * static void strfmt(char *str, char *fmt);
+ *
+ * simple sprintf for strftime
+ *
+ * each format descriptor is of the form %n
+ * where n goes from zero to four
+ *
+ * 0 -- string %s
+ * 1..4 -- int %?.?d
+ *
+**/
+
+static void strfmt(
+ char *str,
+ const char *fmt,
+ ...)
+{
+ int ival, ilen;
+ char *sval;
+ va_list vp;
+
+ va_start(vp, fmt);
+ while (*fmt) {
+ if (*fmt++ == '%') {
+ ilen = *fmt++ - '0';
+ if (ilen == 0) { /* zero means string arg */
+ sval = va_arg(vp, char *);
+
+ while (*sval)
+ *str++ = *sval++;
+ } else { /* always leading zeros */
+
+ ival = va_arg(vp, int);
+
+ while (ilen) {
+ ival %= powers[ilen--];
+ *str++ = (char) ('0' + ival / powers[ilen]);
+ }
+ }
+ } else
+ *str++ = fmt[-1];
+ }
+ *str = '\0';
+ va_end(vp);
+}
+
+#ifdef TEST
+
+#include <stdio.h> /* for printf */
+#include <time.h> /* for strftime */
+
+char test[80];
+
+int main(
+ int argc,
+ char *argv[])
+{
+ int len;
+ char *fmt;
+ time_t now;
+
+ time(&now);
+
+ fmt = (argc == 1) ? "%I:%M %p\n%c\n" : argv[1];
+ len = strftime_(test, sizeof test, fmt, localtime(&now));
+ printf("%d: %s\n", len, test);
+ return !len;
+}
+
+#endif /* TEST */
diff --git a/program/src/strftime.h b/program/src/strftime.h
--- /dev/null
+++ b/program/src/strftime.h
@@ -0,0 +1,24 @@
+/*
+** STRFTIME.H - For older compilers which lack strftime()
+**
+** Note: To avoid name collision with newer compilers, the function name
+** strftime_() is used.
+*/
+
+#ifndef STRFTIME__H
+#define STRFTIME__H
+
+#include <stddef.h> /* for size_t */
+#include <time.h> /* for struct tm */
+
+size_t strftime_(
+ char *s,
+ size_t maxs,
+ const char *f,
+ const struct tm *t);
+
+#if defined(TZNAME_STD) && defined(TZNAME_DST)
+extern char *tzname_[2];
+#endif
+
+#endif /* STRFTIME__H */
diff --git a/program/src/unused.h b/program/src/unused.h
--- /dev/null
+++ b/program/src/unused.h
@@ -0,0 +1,14 @@
+/* define a macro to wrap variables that would
+ otherwise generate UNUSED variable warnings
+ Note that GCC's attribute unused only supresses the warning, so
+ it is perfectly safe to declare something unused although it is not.
+*/
+
+#ifdef UNUSED
+#elif defined(__GNUC__)
+# define UNUSED(x) x __attribute__((unused))
+#elif defined(__LCLINT__)
+# define UNUSED(x) /*@unused@*/ x
+#else
+# define UNUSED(x) x
+#endif
diff --git a/program/src/win32comp.c b/program/src/win32comp.c
--- /dev/null
+++ b/program/src/win32comp.c
@@ -0,0 +1,82 @@
+/* compatibility routines, non reentrant .... */
+
+#include <string.h>
+#include <time.h>
+
+struct tm *localtime_r(
+ const time_t *t,
+ struct tm *r)
+{
+ struct tm *temp;
+
+ temp = localtime(t);
+ memcpy(r, temp, sizeof(struct tm));
+ return (r);
+}
+
+struct tm *gmtime_r(
+ const time_t *t,
+ struct tm *r)
+{
+ struct tm *temp;
+
+ temp = gmtime(t);
+ memcpy(r, temp, sizeof(struct tm));
+ return r;
+}
+
+char *ctime_r(
+ const time_t *t,
+ char *buf)
+{
+ char *temp;
+
+ temp = asctime(localtime(t));
+ strcpy(buf, temp);
+ return (buf);
+}
+
+/*
+ s
+ Points to the string from which to extract tokens.
+
+ delim
+ Points to a null-terminated set of delimiter characters.
+
+ save_ptr
+ Is a value-return parameter used by strtok_r() to record its progress through s1.
+*/
+
+
+char *strtok_r(
+ char *s,
+ const char *delim,
+ char **save_ptr)
+{
+ char *token;
+
+ if (s == NULL)
+ s = *save_ptr;
+
+ /* Scan leading delimiters. */
+ s += strspn(s, delim);
+ if (*s == '\0') {
+ *save_ptr = s;
+ return NULL;
+ }
+
+ /* Find the end of the token. */
+ token = s;
+ s = strpbrk(token, delim);
+ if (s == NULL) {
+ /* This token finishes the string. */
+ *save_ptr = token;
+ while (**save_ptr != '\0')
+ (*save_ptr)++;
+ } else {
+ /* Terminate the token and make *SAVE_PTR point past it. */
+ *s = '\0';
+ *save_ptr = s + 1;
+ }
+ return token;
+}
diff --git a/program/svn2cl.xsl b/program/svn2cl.xsl
--- /dev/null
+++ b/program/svn2cl.xsl
@@ -0,0 +1,215 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+
+ svn2cl.xsl - xslt stylesheet for converting svn log to a normal
+ changelog
+
+ This file is based on several implementations of this conversion
+ that I was not completely happy with and some other common
+ xslt constructs found on the web.
+
+ Copyright (C) 2004 Arthur de Jong.
+
+ 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.
+
+ 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.
+
+-->
+
+<!--
+ TODO
+ - make external lookups of author names possible
+ - find a place for revision numbers
+ - mark deleted files as such
+ - combine paths
+ - make stripping of characters nicer
+-->
+
+<xsl:stylesheet
+ version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns="http://www.w3.org/1999/xhtml">
+
+ <xsl:output
+ method="text"
+ encoding="iso-8859-15"
+ media-type="text/plain"
+ omit-xml-declaration="yes"
+ standalone="yes"
+ indent="no" />
+
+ <xsl:strip-space elements="*" />
+
+ <!-- the prefix of pathnames to strip -->
+ <xsl:param name="strip-prefix" select="'/'" />
+
+ <!-- format one entry from the log -->
+ <xsl:template match="logentry">
+ <!-- date -->
+ <xsl:apply-templates select="date" />
+ <!-- two spaces -->
+ <xsl:text> </xsl:text>
+ <!-- author's name -->
+ <xsl:apply-templates select="author" />
+ <!-- two newlines -->
+ <xsl:text>
+
+</xsl:text>
+ <!-- the log message -->
+ <xsl:apply-templates select="msg" />
+ <!-- another two newlines -->
+ <xsl:text>
+
+</xsl:text>
+ </xsl:template>
+
+ <!-- format date -->
+ <xsl:template match="date">
+ <xsl:variable name="date" select="normalize-space(.)" />
+ <xsl:value-of select="substring($date,1,10)" />
+ <xsl:text> </xsl:text>
+ <xsl:value-of select="substring($date,12,5)" />
+ </xsl:template>
+
+ <!-- format author -->
+ <xsl:template match="author">
+ <xsl:value-of select="normalize-space(.)" />
+ </xsl:template>
+
+ <!-- format log message -->
+ <xsl:template match="msg">
+ <!-- first line is indented (other indents are done in wrap template) -->
+ <xsl:text> * </xsl:text>
+ <!-- get paths string -->
+ <xsl:variable name="paths">
+ <xsl:apply-templates select="../paths" />
+ </xsl:variable>
+ <!-- print the paths and message nicely wrapped -->
+ <xsl:call-template name="wrap">
+ <xsl:with-param name="txt" select="concat($paths,': ',normalize-space(.))" />
+ </xsl:call-template>
+ </xsl:template>
+
+ <!-- present paths nice -->
+ <xsl:template match="paths">
+ <xsl:for-each select="path">
+ <xsl:sort select="normalize-space(.)" data-type="text" />
+ <xsl:if test="not(position()=1)">
+ <xsl:text>, </xsl:text>
+ </xsl:if>
+ <xsl:variable name="p1" select="normalize-space(.)" />
+ <xsl:variable name="p2">
+ <xsl:choose>
+ <xsl:when test="starts-with($p1,'/')">
+ <xsl:value-of select="substring($p1,2)" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$p1" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:variable name="p3">
+ <xsl:choose>
+ <xsl:when test="starts-with($p2,$strip-prefix)">
+ <xsl:value-of select="substring($p2,1+string-length($strip-prefix))" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$p2" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:variable name="p4">
+ <xsl:choose>
+ <xsl:when test="starts-with($p3,'/')">
+ <xsl:value-of select="substring($p3,2)" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$p3" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:choose>
+ <xsl:when test="$p4 = ''">
+ <xsl:value-of select="'.'" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$p4" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+ </xsl:template>
+
+ <!-- string-wrapping template -->
+ <xsl:template name="wrap">
+ <xsl:param name="txt" />
+ <xsl:variable name="linelen" select="67" />
+ <xsl:choose>
+ <xsl:when test="(string-length($txt) < $linelen) or not(contains($txt,' '))">
+ <!-- this is easy, nothing to do -->
+ <xsl:value-of select="$txt" />
+ </xsl:when>
+ <xsl:otherwise>
+ <!-- find the first line -->
+ <xsl:variable name="tmp" select="substring($txt,1,$linelen)" />
+ <xsl:variable name="line">
+ <xsl:choose>
+ <xsl:when test="contains($tmp,' ')">
+ <xsl:call-template name="find-line">
+ <xsl:with-param name="txt" select="$tmp" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="substring-before($txt,' ')" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <!-- print line and newline -->
+ <xsl:value-of select="$line" />
+ <xsl:text>
+ </xsl:text>
+ <!-- wrap the rest of the text -->
+ <xsl:call-template name="wrap">
+ <xsl:with-param name="txt" select="normalize-space(substring($txt,string-length($line)+1))" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+ <!-- template to trim line to contain space as last char -->
+ <xsl:template name="find-line">
+ <xsl:param name="txt" />
+ <xsl:choose>
+ <xsl:when test="substring($txt,string-length($txt),1) = ' '">
+ <xsl:value-of select="normalize-space($txt)" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="find-line">
+ <xsl:with-param name="txt" select="substring($txt,1,string-length($txt)-1)" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+</xsl:stylesheet>
diff --git a/program/win32/Makefile b/program/win32/Makefile
--- /dev/null
+++ b/program/win32/Makefile
@@ -0,0 +1,460 @@
+# Gnu Makefile for Win32 target
+# for use with MingW32 gcc or Metrowerks CodeWarrior compiler
+# use with: make -f Makefile [help|all|clean|dev|devclean|dist|distclean]
+#
+# $id: $
+#
+
+DESCR = Round Robin Database Tool
+COPYR = Copyright (c) 1997-2007 by Tobias Oetiker
+WWWURL = http://www.rrdtool.org/
+ICON = $(PROOT)/favicon.ico
+
+# You can set the default font used in graphs.
+# If not set here RRD defaults to DejaVuSansMono-Roman.ttf
+#RRD_DEFAULT_FONT = "arial.ttf"
+#RRD_DEFAULT_FONT = "VeraMono.ttf"
+
+# Vertical label angle: 90.0 (default) or 270.0
+RRDGRAPH_YLEGEND_ANGLE = 90.0
+
+# Set to one if you want to have piecharts.
+WITH_PIECHART = 0
+
+# Set the extension used for rrdcgi.
+ifndef CGIEXT
+CGIEXT = exe
+endif
+
+# Base for the lib sources
+ifndef LIBBASE
+LIBBASE = ../..
+endif
+# All library code is statically linked to avoid problems with other lib DLLs.
+# Edit the path below to point to your libpng sources or set environment var.
+ifndef LIBPNG
+LIBPNG = $(LIBBASE)/libpng-1.2.16
+endif
+# Edit the path below to point to your freetype sources or set environment var.
+ifndef LIBFT2
+#LIBFT2 = $(LIBBASE)/freetype-2.3.4
+LIBFT2 = $(LIBBASE)/../mingw32/freetype-2.3.4
+endif
+# Edit the path below to point to your libart sources or set environment var.
+ifndef LIBART
+LIBART = $(LIBBASE)/libart_lgpl-2.3.17
+endif
+# Edit the path below to point to your zlib sources or set environment var.
+ifndef ZLIBSDK
+ZLIBSDK = $(LIBBASE)/zlib-1.2.3
+endif
+
+# Edit the path below to point to your distribution folder.
+ifndef DISTDIR
+DISTDIR = rrdtool-$(RRD_VERSION_STR)-w32
+endif
+DISTARC = $(DISTDIR).zip
+
+# Edit the path below to point to your distribution folder.
+ifndef DEVLDIR
+DEVLDIR = rrdtool-$(RRD_VERSION_STR)-sdk-w32
+endif
+DEVLARC = $(DEVLDIR).zip
+
+# whatever...
+NO_NULL_REALLOC = 1
+
+# The following line defines your compiler.
+ifdef METROWERKS
+ CC = mwcc
+else
+ CC = gcc
+endif
+# RM = rm -f
+CP = cp -afv
+# Here you can find a native Win32 binary of the original awk:
+# http://www.gknw.net/development/prgtools/awk.zip
+AWK = awk
+ZIP = zip -qzr9
+
+# must be equal to DEBUG or NDEBUG
+DB = NDEBUG
+# DB = DEBUG
+# Optimization: -O<n> or debugging: -g
+ifeq ($(DB),NDEBUG)
+ OPT = -O2
+ OBJDIR = release
+else
+ OPT = -g
+ OBJDIR = debug
+endif
+
+# Project root
+PROOT = ..
+
+# Include the version info retrieved from source.
+-include $(OBJDIR)/version.inc
+
+# Global flags for all compilers
+CFLAGS = $(OPT) -D$(DB) -DHAVE_CONFIG_H
+
+ifeq ($(CC),mwcc)
+LD = mwld
+RC = mwwinrc
+LDFLAGS = -nostdlib
+AR = $(LD)
+ARFLAGS = -type library -w nocmdline $(OBJS) -o
+LIBEXT = lib
+LIBPATH += -lr "$(METROWERKS)/MSL" -lr "$(METROWERKS)/Win32-x86 Support"
+LDLIBS += -lkernel32.lib -luser32.lib
+LDLIBS += -lMSL_Runtime_x86.lib -lMSL_C_x86.lib -lMSL_Extras_x86.lib
+RCFLAGS =
+CFLAGS += -DWIN32
+CFLAGS += -nostdinc -gccinc -msgstyle gcc -inline off -opt nointrinsics -proc 586
+CFLAGS += -ir "$(METROWERKS)/MSL" -ir "$(METROWERKS)/Win32-x86 Support"
+CFLAGS += -w on,nounused,nounusedexpr # -ansi strict
+else
+LD = gcc
+RC = windres
+LDFLAGS = -s
+AR = ar
+ARFLAGS = -cq
+LIBEXT = a
+RCFLAGS = -O coff -i
+CFLAGS += -fno-strict-aliasing
+CFLAGS += -Wall -Wno-unused # -pedantic
+endif
+
+ifeq ($(findstring msys,$(OSTYPE)),msys)
+DL = '
+DS = /
+else
+DS = \\
+endif
+
+ifndef DESCR
+ DESCR = $(notdir $(@:.rc=)) Command Extension
+endif
+DESCR += - $(CC) build
+
+INCLUDES += -I$(PROOT) -I$(PROOT)/src -I$(LIBPNG) -I$(LIBFT2)/include -I$(LIBART) -I$(ZLIBSDK)
+
+CFLAGS += $(INCLUDES)
+
+vpath %.c $(PROOT)/src $(LIBPNG) $(LIBART)/libart_lgpl $(ZLIBSDK)
+
+RRDLIBOBJS = \
+ $(OBJDIR)/rrd_afm.o \
+ $(OBJDIR)/rrd_afm_data.o \
+ $(OBJDIR)/rrd_create.o \
+ $(OBJDIR)/rrd_diff.o \
+ $(OBJDIR)/rrd_dump.o \
+ $(OBJDIR)/rrd_error.o \
+ $(OBJDIR)/rrd_fetch.o \
+ $(OBJDIR)/rrd_first.o \
+ $(OBJDIR)/rrd_format.o \
+ $(OBJDIR)/rrd_gfx.o \
+ $(OBJDIR)/rrd_graph.o \
+ $(OBJDIR)/rrd_graph_helper.o \
+ $(OBJDIR)/rrd_hw.o \
+ $(OBJDIR)/rrd_info.o \
+ $(OBJDIR)/rrd_last.o \
+ $(OBJDIR)/rrd_lastupdate.o \
+ $(OBJDIR)/rrd_nan_inf.o \
+ $(OBJDIR)/rrd_open.o \
+ $(OBJDIR)/rrd_resize.o \
+ $(OBJDIR)/rrd_restore.o \
+ $(OBJDIR)/rrd_rpncalc.o \
+ $(OBJDIR)/rrd_tune.o \
+ $(OBJDIR)/rrd_update.o \
+ $(OBJDIR)/rrd_version.o \
+ $(OBJDIR)/rrd_xport.o \
+ $(OBJDIR)/rrd_thread_safe_nt.o \
+ $(EOLIST)
+
+XLIBOBJS = \
+ $(OBJDIR)/rrd_getopt.o \
+ $(OBJDIR)/rrd_getopt1.o \
+ $(OBJDIR)/art_rgba_svp.o \
+ $(OBJDIR)/hash_32.o \
+ $(OBJDIR)/parsetime.o \
+ $(OBJDIR)/pngsize.o \
+ $(OBJDIR)/strftime.o \
+ $(EOLIST)
+
+PNGLIBOBJS = \
+ $(OBJDIR)/png.o \
+ $(OBJDIR)/pngerror.o \
+ $(OBJDIR)/pngget.o \
+ $(OBJDIR)/pngmem.o \
+ $(OBJDIR)/pngpread.o \
+ $(OBJDIR)/pngread.o \
+ $(OBJDIR)/pngrio.o \
+ $(OBJDIR)/pngrtran.o \
+ $(OBJDIR)/pngrutil.o \
+ $(OBJDIR)/pngset.o \
+ $(OBJDIR)/pngtrans.o \
+ $(OBJDIR)/pngwio.o \
+ $(OBJDIR)/pngwrite.o \
+ $(OBJDIR)/pngwtran.o \
+ $(OBJDIR)/pngwutil.o \
+ $(EOLIST)
+ifeq "$(wildcard $(LIBPNG)/pnggccrd.c)" "$(LIBPNG)/pnggccrd.c"
+PNGLIBOBJS += \
+ $(OBJDIR)/pnggccrd.o \
+ $(OBJDIR)/pngvcrd.o \
+ $(EOLIST)
+endif
+
+ZLIBOBJS = \
+ $(OBJDIR)/adler32.o \
+ $(OBJDIR)/compress.o \
+ $(OBJDIR)/crc32.o \
+ $(OBJDIR)/deflate.o \
+ $(OBJDIR)/inflate.o \
+ $(OBJDIR)/inffast.o \
+ $(OBJDIR)/inftrees.o \
+ $(OBJDIR)/trees.o \
+ $(OBJDIR)/zutil.o \
+ $(EOLIST)
+ifeq "$(wildcard $(ZLIBSDK)/infblock.c)" "$(ZLIBSDK)/infblock.c"
+ZLIBOBJS += \
+ $(OBJDIR)/infblock.o \
+ $(OBJDIR)/infcodes.o \
+ $(OBJDIR)/infutil.o \
+ $(EOLIST)
+endif
+
+ARTLIBOBJS = \
+ $(patsubst $(LIBART)/libart_lgpl/%.c,$(OBJDIR)/%.o,$(wildcard $(LIBART)/libart_lgpl/art_*.c))
+
+OBJS := $(RRDLIBOBJS) $(XLIBOBJS) $(PNGLIBOBJS) $(ARTLIBOBJS) $(ZLIBOBJS)
+OBJCGI := $(OBJS) $(OBJDIR)/rrd_cgi.o
+OBJTOOL := $(OBJS) $(OBJDIR)/rrd_tool.o
+
+LDLIBS += $(LIBFT2)/objs/freetype.$(LIBEXT)
+
+
+all: rrdtool rrdcgi
+
+rrdtool: $(OBJDIR) $(PROOT)/rrd_config.h $(OBJDIR)/rrdtool.exe
+rrdcgi: $(OBJDIR) $(PROOT)/rrd_config.h $(OBJDIR)/rrdcgi.$(CGIEXT)
+librrd: $(OBJDIR) $(PROOT)/rrd_config.h $(OBJDIR)/librrd.$(LIBEXT)
+
+FORCE: ;
+
+dist: all $(DISTDIR) $(DISTDIR)/readme.txt
+ @-$(CP) $(OBJDIR)/rrdcgi.$(CGIEXT) $(DISTDIR)
+ @-$(CP) $(OBJDIR)/rrdtool.exe $(DISTDIR)
+ @-$(CP) $(PROOT)/src/*.ttf $(DISTDIR)
+ @-$(CP) $(PROOT)/CHANGES $(DISTDIR)
+ @-$(CP) $(PROOT)/COPYING $(DISTDIR)
+ @-$(CP) $(PROOT)/COPYRIGHT $(DISTDIR)
+ @-$(CP) $(PROOT)/NEWS $(DISTDIR)
+ @-$(CP) $(PROOT)/README $(DISTDIR)
+ @echo Creating $(DISTARC)
+ @$(ZIP) $(DISTARC) $(DISTDIR)/* < $(DISTDIR)/readme.txt
+
+dev: librrd $(DEVLDIR) $(DEVLDIR)/readme.txt
+ @-mkdir $(DEVLDIR)$(DS)include
+ @-mkdir $(DEVLDIR)$(DS)lib
+ @-mkdir $(DEVLDIR)$(DS)src
+ @-$(CP) $(OBJDIR)/librrd.$(LIBEXT) $(DEVLDIR)/lib
+ @-$(CP) $(PROOT)/rrd_config.h $(DEVLDIR)/include
+ @-$(CP) $(PROOT)/src/rrd.h $(DEVLDIR)/include
+ @-$(CP) $(PROOT)/src/*.ttf $(DEVLDIR)/src
+ @-$(CP) $(PROOT)/CHANGES $(DEVLDIR)
+ @-$(CP) $(PROOT)/COPYING $(DEVLDIR)
+ @-$(CP) $(PROOT)/COPYRIGHT $(DEVLDIR)
+ @-$(CP) $(PROOT)/NEWS $(DEVLDIR)
+ @-$(CP) $(PROOT)/README $(DEVLDIR)
+ @echo Creating $(DEVLARC)
+ @$(ZIP) $(DEVLARC) $(DEVLDIR)/* < $(DEVLDIR)/readme.txt
+
+clean:
+ -$(RM) -r $(OBJDIR)
+ -$(RM) $(PROOT)/rrd_config.h
+
+distclean: clean
+ -$(RM) -r $(DISTDIR)
+ -$(RM) $(DISTARC)
+
+devclean: clean
+ -$(RM) -r $(DEVLDIR)
+ -$(RM) $(DEVLARC)
+
+$(OBJDIR):
+ @mkdir $@
+
+$(DISTDIR):
+ @mkdir $@
+
+$(DEVLDIR):
+ @mkdir $@
+
+$(OBJDIR)/version.inc: $(PROOT)/configure.ac $(OBJDIR) $(PROOT)/src/get_ver.awk
+ @echo Creating $@
+ @$(AWK) -f $(PROOT)/src/get_ver.awk $< > $@
+
+$(OBJDIR)/%.o: %.c
+ @echo Compiling $<
+ @$(CC) $(CFLAGS) -c $< -o $@
+
+$(OBJDIR)/rrdcgi.$(CGIEXT): $(OBJCGI) $(OBJDIR)/rrdcgi.res
+ @echo Linking $@
+ @-$(RM) $@
+ @$(LD) $(LDFLAGS) $^ -o $@ $(LIBPATH) $(LDLIBS)
+
+$(OBJDIR)/rrdtool.exe: $(OBJTOOL) $(OBJDIR)/rrdtool.res
+ @echo Linking $@
+ @-$(RM) $@
+ @$(LD) $(LDFLAGS) $^ -o $@ $(LIBPATH) $(LDLIBS)
+
+$(OBJDIR)/librrd.$(LIBEXT): $(OBJS)
+ @echo Creating $@
+ @-$(RM) $@
+ @$(AR) $(ARFLAGS) $@ $^
+
+$(OBJDIR)/%.res: $(OBJDIR)/%.rc
+ @echo Creating $@
+ @$(RC) $(RCFLAGS) $< -o $@
+
+$(OBJDIR)/%.rc: Makefile $(OBJDIR)/version.inc
+ @echo $(DL)1 VERSIONINFO$(DL) > $@
+ @echo $(DL) FILEVERSION $(RRD_VERSION),0$(DL) >> $@
+ @echo $(DL) PRODUCTVERSION $(RRD_VERSION),0$(DL) >> $@
+ @echo $(DL) FILEFLAGSMASK 0x3fL$(DL) >> $@
+ @echo $(DL) FILEOS 0x40004L$(DL) >> $@
+ @echo $(DL) FILEFLAGS 0x0L$(DL) >> $@
+ @echo $(DL) FILETYPE 0x1L$(DL) >> $@
+ @echo $(DL) FILESUBTYPE 0x0L$(DL) >> $@
+ @echo $(DL)BEGIN$(DL) >> $@
+ @echo $(DL) BLOCK "StringFileInfo"$(DL) >> $@
+ @echo $(DL) BEGIN$(DL) >> $@
+ @echo $(DL) BLOCK "040904E4"$(DL) >> $@
+ @echo $(DL) BEGIN$(DL) >> $@
+ @echo $(DL) VALUE "LegalCopyright","$(COPYR)\0"$(DL) >> $@
+ifdef COMPANY
+ @echo $(DL) VALUE "CompanyName","$(COMPANY)\0"$(DL) >> $@
+endif
+ @echo $(DL) VALUE "ProductName","$(notdir $(@:.rc=.exe))\0"$(DL) >> $@
+ @echo $(DL) VALUE "ProductVersion","$(RRD_VERSION_STR)\0"$(DL) >> $@
+ @echo $(DL) VALUE "License","Released under GPL.\0"$(DL) >> $@
+ @echo $(DL) VALUE "FileDescription","$(DESCR)\0"$(DL) >> $@
+ @echo $(DL) VALUE "FileVersion","$(RRD_VERSION_STR)\0"$(DL) >> $@
+ @echo $(DL) VALUE "InternalName","$(notdir $(@:.rc=))\0"$(DL) >> $@
+ @echo $(DL) VALUE "OriginalFilename","$(notdir $(@:.rc=.exe))\0"$(DL) >> $@
+ @echo $(DL) VALUE "WWW","$(WWWURL)\0"$(DL) >> $@
+ @echo $(DL) END$(DL) >> $@
+ @echo $(DL) END$(DL) >> $@
+ @echo $(DL) BLOCK "VarFileInfo"$(DL) >> $@
+ @echo $(DL) BEGIN$(DL) >> $@
+ @echo $(DL) VALUE "Translation", 0x409, 1252$(DL) >> $@
+ @echo $(DL) END$(DL) >> $@
+ @echo $(DL)END$(DL) >> $@
+ifdef ICON
+ @echo $(DL)10 ICON DISCARDABLE "$(ICON)"$(DL) >> $@
+endif
+
+$(PROOT)/rrd_config.h: FORCE Makefile $(OBJDIR)/version.inc
+ @echo Creating $@
+ @echo $(DL)/* $(notdir $@) for Win32 target.$(DL) > $@
+ @echo $(DL)** Do not edit this file - it is created by make!$(DL) >> $@
+ @echo $(DL)** All your changes will be lost!!$(DL) >> $@
+ @echo $(DL)*/$(DL) >> $@
+ @echo $(DL)#ifndef WIN32$(DL) >> $@
+ @echo $(DL)#error This $(notdir $@) is created for Win32 platform!$(DL) >> $@
+ @echo $(DL)#endif$(DL) >> $@
+ @echo $(DL)#ifndef RRD_CONFIG_H$(DL) >> $@
+ @echo $(DL)#define RRD_CONFIG_H$(DL) >> $@
+ @echo $(DL)#define OS "i586-pc-Win32"$(DL) >> $@
+ @echo $(DL)#define PACKAGE_VERSION "$(RRD_VERSION_STR)"$(DL) >> $@
+ @echo $(DL)#define PACKAGE_BUGREPORT "tobi@oetiker.ch"$(DL) >> $@
+ @echo $(DL)#define NUMVERS $(RRD_NUMVERS)$(DL) >> $@
+ @echo $(DL)#define HAVE_ASSERT_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_DLFCN_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_DLOPEN 1$(DL) >> $@
+ @echo $(DL)#define HAVE_ERRNO_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_FCNTL_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_FIONBIO 1$(DL) >> $@
+ @echo $(DL)#define HAVE_INTTYPES_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_LIMITS_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_LONGLONG 1$(DL) >> $@
+ @echo $(DL)#define HAVE_LOCALE_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_MALLOC_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_MATH_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_MBSTOWCS 1$(DL) >> $@
+ @echo $(DL)#define HAVE_SETJMP_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_SNPRINTF 1$(DL) >> $@
+ @echo $(DL)#define HAVE_STDARG_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_STDDEF_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_STDINT_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_STDLIB_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_STRCASECMP 1$(DL) >> $@
+ @echo $(DL)#define HAVE_STRDUP 1$(DL) >> $@
+ @echo $(DL)#define HAVE_STRFTIME 1$(DL) >> $@
+ @echo $(DL)#define HAVE_STRING_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_STRLCAT 1$(DL) >> $@
+ @echo $(DL)#define HAVE_STRLCPY 1$(DL) >> $@
+ @echo $(DL)#define HAVE_STRSTR 1$(DL) >> $@
+ @echo $(DL)#define HAVE_SYS_PARAM_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_SYS_STAT_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_SYS_TIME_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_TIME_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_VSNPRINTF 1$(DL) >> $@
+ @echo $(DL)#define STDC_HEADERS 1$(DL) >> $@
+ @echo $(DL)#define TIME_WITH_SYS_TIME 1$(DL) >> $@
+ @echo $(DL)#define HAVE_ZLIB_H 1$(DL) >> $@
+ @echo $(DL)#define HAVE_LIBZ 1$(DL) >> $@
+ifdef NO_NULL_REALLOC
+ @echo $(DL)#define NO_NULL_REALLOC 1$(DL) >> $@
+ @echo $(DL)#define rrd_realloc(a,b) ( (a) == NULL ? malloc( (b) ) : realloc( (a) , (b) ))$(DL) >> $@
+else
+ @echo $(DL)#define rrd_realloc(a,b) realloc((a), (b))$(DL) >> $@
+endif
+ifdef RRD_DEFAULT_FONT
+ @echo $(DL)#define RRD_DEFAULT_FONT $(RRD_DEFAULT_FONT)$(DL) >> $@
+endif
+ @echo $(DL)#define RRDGRAPH_YLEGEND_ANGLE $(RRDGRAPH_YLEGEND_ANGLE)$(DL) >> $@
+ @echo $(DL)#define strftime strftime_$(DL) >> $@
+ifdef WITH_PIECHART
+ @echo $(DL)#define WITH_PIECHART $(WITH_PIECHART)$(DL) >> $@
+endif
+ @echo $(DL)#endif /* RRD_CONFIG_H */$(DL) >> $@
+
+$(DISTDIR)/readme.txt: Makefile
+ @echo Creating $@
+ @echo $(DL)This is a binary distribution for Win32 platform.$(DL) > $@
+ @echo $(DL)RRDTool version $(RRD_VERSION_STR)$(DL) >> $@
+ @echo $(DL)Please download the complete RRDTool package for$(DL) >> $@
+ @echo $(DL)any further documentation:$(DL) >> $@
+ @echo $(DL)$(WWWURL)$(DL) >> $@
+
+$(DEVLDIR)/readme.txt: Makefile
+ @echo Creating $@
+ @echo $(DL)This is a development distribution for Win32 platform.$(DL) > $@
+ @echo $(DL)RRDTool version $(RRD_VERSION_STR)$(DL) >> $@
+ @echo $(DL)Please download the complete RRDTool package for$(DL) >> $@
+ @echo $(DL)any further documentation:$(DL) >> $@
+ @echo $(DL)$(WWWURL)$(DL) >> $@
+
+help:
+ @echo $(DL)===========================================================$(DL)
+ @echo $(DL)libpng Source = $(LIBPNG)$(DL)
+ @echo $(DL)libart Source = $(LIBART)$(DL)
+ @echo $(DL)Freetype 2 SDK = $(LIBFT2)$(DL)
+ @echo $(DL)Zlib SDK = $(ZLIBSDK)$(DL)
+ @echo $(DL)===========================================================$(DL)
+ @echo $(DL)RRDTool $(RRD_VERSION_STR) - available targets are:$(DL)
+ @echo $(DL)$(MAKE) all$(DL)
+ @echo $(DL)$(MAKE) rrdtool$(DL)
+ @echo $(DL)$(MAKE) rrdcgi$(DL)
+ @echo $(DL)$(MAKE) librrd$(DL)
+ @echo $(DL)$(MAKE) clean$(DL)
+ @echo $(DL)$(MAKE) dev$(DL)
+ @echo $(DL)$(MAKE) devclean$(DL)
+ @echo $(DL)$(MAKE) dist$(DL)
+ @echo $(DL)$(MAKE) distclean$(DL)
+ @echo $(DL)===========================================================$(DL)
+
+
diff --git a/program/win32/config.h b/program/win32/config.h
--- /dev/null
+++ b/program/win32/config.h
@@ -0,0 +1,56 @@
+/* config.h.msvc. Hand-tweaked config.h for MSVC compiler. */
+#ifndef CONFIG_H
+#define CONFIG_H
+
+#include <math.h>
+#include <float.h>
+#include <direct.h>
+
+/* realloc does not support NULL as argument */
+
+#define HAVE_STRFTIME 1
+#define HAVE_TIME_H 1
+#define HAVE_LOCALE_H 1
+#define HAVE_TZSET 1
+#define HAVE_SETLOCALE 1
+#define HAVE_MATH_H 1
+#define HAVE_FLOAT_H 1
+#define HAVE_MEMMOVE 1
+#define HAVE_MALLOC_H 1
+#define HAVE_MKTIME 1
+#define HAVE_STRFTIME 1
+#define HAVE_STRING_H 1
+#define HAVE_VSNPRINTF 1
+#define HAVE_SYS_TYPES_H 1
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+#define NUMVERS 1.2015
+#define PACKAGE_NAME "rrdtool"
+#define PACKAGE_VERSION "1.2.15"
+#define PACKAGE_STRING PACKAGE_NAME " " PACKAGE_VERSION
+
+#define isinf(a) (_fpclass(a) == _FPCLASS_NINF || _fpclass(a) == _FPCLASS_PINF)
+#define isnan _isnan
+#define finite _finite
+#define snprintf _snprintf
+#define vsnprintf _vsnprintf
+#define strftime strftime_
+
+#define NO_NULL_REALLOC 1
+#if NO_NULL_REALLOC
+# define rrd_realloc(a,b) ( (a) == NULL ? malloc( (b) ) : realloc( (a) , (b) ))
+#else
+# define rrd_realloc(a,b) realloc((a), (b))
+#endif
+
+/* Vertical label angle: 90.0 (default) or 270.0 */
+#define RRDGRAPH_YLEGEND_ANGLE 90.0
+
+#define RRD_DEFAULT_FONT "Courier"
+
+/* #define DEBUG 1 */
+
+#endif /* CONFIG_H */
diff --git a/program/win32/rrd.dsp b/program/win32/rrd.dsp
--- /dev/null
+++ b/program/win32/rrd.dsp
@@ -0,0 +1,247 @@
+# Microsoft Developer Studio Project File - Name="rrd" - Package Owner=<4>\r
+# Microsoft Developer Studio Generated Build File, Format Version 6.00\r
+# ** DO NOT EDIT **\r
+\r
+# TARGTYPE "Win32 (x86) Static Library" 0x0104\r
+\r
+CFG=rrd - Win32 Debug\r
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,\r
+!MESSAGE use the Export Makefile command and run\r
+!MESSAGE \r
+!MESSAGE NMAKE /f "rrd.mak".\r
+!MESSAGE \r
+!MESSAGE You can specify a configuration when running NMAKE\r
+!MESSAGE by defining the macro CFG on the command line. For example:\r
+!MESSAGE \r
+!MESSAGE NMAKE /f "rrd.mak" CFG="rrd - Win32 Debug"\r
+!MESSAGE \r
+!MESSAGE Possible choices for configuration are:\r
+!MESSAGE \r
+!MESSAGE "rrd - Win32 Release" (based on "Win32 (x86) Static Library")\r
+!MESSAGE "rrd - Win32 Debug" (based on "Win32 (x86) Static Library")\r
+!MESSAGE \r
+\r
+# Begin Project\r
+# PROP AllowPerConfigDependencies 0\r
+# PROP Scc_ProjName ""\r
+# PROP Scc_LocalPath ""\r
+CPP=cl.exe\r
+RSC=rc.exe\r
+\r
+!IF "$(CFG)" == "rrd - Win32 Release"\r
+\r
+# PROP BASE Use_MFC 0\r
+# PROP BASE Use_Debug_Libraries 0\r
+# PROP BASE Output_Dir "rrd___Wi"\r
+# PROP BASE Intermediate_Dir "rrd___Wi"\r
+# PROP BASE Target_Dir ""\r
+# PROP Use_MFC 0\r
+# PROP Use_Debug_Libraries 0\r
+# PROP Output_Dir "release"\r
+# PROP Intermediate_Dir "release"\r
+# PROP Target_Dir ""\r
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c\r
+# ADD CPP /nologo /MD /W3 /GX /I "../src" /I "../../zlib-1.2.3" /I "../../libpng-1.2.16" /I "../../libart_lgpl-2.3.17" /I "../../freetype-2.3.1/include" /D "HAVE_CONFIG_H" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_CTYPE_DISABLE_MACROS" /FD /c\r
+# SUBTRACT CPP /X /YX\r
+# ADD BASE RSC /l 0x100c\r
+# ADD RSC /l 0x409\r
+BSC32=bscmake.exe\r
+# ADD BASE BSC32 /nologo\r
+# ADD BSC32 /nologo\r
+LIB32=link.exe -lib\r
+# ADD BASE LIB32 /nologo\r
+# ADD LIB32 /nologo\r
+\r
+!ELSEIF "$(CFG)" == "rrd - Win32 Debug"\r
+\r
+# PROP BASE Use_MFC 0\r
+# PROP BASE Use_Debug_Libraries 1\r
+# PROP BASE Output_Dir "rrd___W0"\r
+# PROP BASE Intermediate_Dir "rrd___W0"\r
+# PROP BASE Target_Dir ""\r
+# PROP Use_MFC 0\r
+# PROP Use_Debug_Libraries 1\r
+# PROP Output_Dir "debug"\r
+# PROP Intermediate_Dir "debug"\r
+# PROP Target_Dir ""\r
+# ADD BASE CPP /nologo /W3 /GX /Z7 /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /c\r
+# ADD CPP /nologo /MD /W3 /Gm /GX /ZI /Od /I "../src" /I "../../zlib-1.2.3" /I "../../libpng-1.2.16" /I "../../libart_lgpl-2.3.17" /I "../../freetype-2.3.1/include" /D "HAVE_CONFIG_H" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /D "_CTYPE_DISABLE_MACROS" /FR /FD /c\r
+# SUBTRACT CPP /X /YX\r
+# ADD BASE RSC /l 0x100c\r
+# ADD RSC /l 0x409\r
+BSC32=bscmake.exe\r
+# ADD BASE BSC32 /nologo\r
+# ADD BSC32 /nologo /o"rrd.bsc"\r
+LIB32=link.exe -lib\r
+# ADD BASE LIB32 /nologo\r
+# ADD LIB32 /nologo\r
+\r
+!ENDIF \r
+\r
+# Begin Target\r
+\r
+# Name "rrd - Win32 Release"\r
+# Name "rrd - Win32 Debug"\r
+# Begin Source File\r
+\r
+SOURCE="..\src\get_ver.awk"\r
+\r
+!IF "$(CFG)" == "rrd - Win32 Release"\r
+\r
+# PROP Ignore_Default_Tool 1\r
+# Begin Custom Build - Creating ..\rrd_config.h\r
+InputPath="..\src\get_ver.awk"\r
+\r
+"..\rrd_config.h" : $(SOURCE) "..\configure.ac" "..\win32\rrd_config.h.msvc"\r
+ awk -f ..\src\get_ver.awk ..\configure.ac ..\win32\rrd_config.h.msvc > ..\rrd_config.h\r
+\r
+# End Custom Build\r
+\r
+!ELSEIF "$(CFG)" == "rrd - Win32 Debug"\r
+\r
+# PROP Ignore_Default_Tool 1\r
+# Begin Custom Build - Creating ..\rrd_config.h\r
+InputPath="..\src\get_ver.awk"\r
+\r
+"..\rrd_config.h" : $(SOURCE) "..\configure.ac" "..\win32\rrd_config.h.msvc"\r
+ awk -f ..\src\get_ver.awk ..\configure.ac ..\win32\rrd_config.h.msvc > ..\rrd_config.h\r
+\r
+# End Custom Build\r
+\r
+!ENDIF\r
+\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_afm.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_afm_data.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_create.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_diff.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_dump.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_error.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_fetch.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_first.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_format.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_gfx.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_graph.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_graph_helper.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_hw.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_info.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_last.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_lastupdate.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_nan_inf.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_open.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_resize.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_restore.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_rpncalc.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_thread_safe_nt.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_tune.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_update.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_version.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_xport.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_getopt.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_getopt1.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\art_rgba_svp.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\hash_32.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_parsetime.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\pngsize.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=..\src\strftime.c\r
+# End Source File\r
+# End Target\r
+# End Project\r
diff --git a/program/win32/rrd.vcproj b/program/win32/rrd.vcproj
--- /dev/null
+++ b/program/win32/rrd.vcproj
@@ -0,0 +1,648 @@
+<?xml version="1.0" encoding="Windows-1252"?>\r
+<VisualStudioProject\r
+ ProjectType="Visual C++"\r
+ Version="7.10"\r
+ Name="rrd"\r
+ SccProjectName=""\r
+ SccLocalPath="">\r
+ <Platforms>\r
+ <Platform\r
+ Name="Win32"/>\r
+ </Platforms>\r
+ <Configurations>\r
+ <Configuration\r
+ Name="Release|Win32"\r
+ OutputDirectory=".\release"\r
+ IntermediateDirectory=".\release"\r
+ ConfigurationType="4"\r
+ UseOfMFC="0"\r
+ ATLMinimizesCRunTimeLibraryUsage="FALSE"\r
+ CharacterSet="2">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="4"\r
+ AdditionalIncludeDirectories="\Program Files\GnuWin32\include,\Program Files\GnuWin32\include\freetype2"\r
+ PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_CTYPE_DISABLE_MACROS"\r
+ RuntimeLibrary="2"\r
+ PrecompiledHeaderFile=".\release/rrd.pch"\r
+ AssemblerListingLocation=".\release/"\r
+ ObjectFile=".\release/"\r
+ ProgramDataBaseFileName=".\release/"\r
+ WarningLevel="3"\r
+ SuppressStartupBanner="TRUE"\r
+ CompileAs="0"/>\r
+ <Tool\r
+ Name="VCCustomBuildTool"/>\r
+ <Tool\r
+ Name="VCLibrarianTool"\r
+ OutputFile=".\release\rrd.lib"\r
+ SuppressStartupBanner="TRUE"/>\r
+ <Tool\r
+ Name="VCMIDLTool"/>\r
+ <Tool\r
+ Name="VCPostBuildEventTool"/>\r
+ <Tool\r
+ Name="VCPreBuildEventTool"/>\r
+ <Tool\r
+ Name="VCPreLinkEventTool"/>\r
+ <Tool\r
+ Name="VCResourceCompilerTool"\r
+ Culture="4108"/>\r
+ <Tool\r
+ Name="VCWebServiceProxyGeneratorTool"/>\r
+ <Tool\r
+ Name="VCXMLDataGeneratorTool"/>\r
+ <Tool\r
+ Name="VCManagedWrapperGeneratorTool"/>\r
+ <Tool\r
+ Name="VCAuxiliaryManagedWrapperGeneratorTool"/>\r
+ </Configuration>\r
+ <Configuration\r
+ Name="Debug|Win32"\r
+ OutputDirectory=".\debug"\r
+ IntermediateDirectory=".\debug"\r
+ ConfigurationType="4"\r
+ UseOfMFC="0"\r
+ ATLMinimizesCRunTimeLibraryUsage="FALSE"\r
+ CharacterSet="2">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories="\Program Files\GnuWin32\include\freetype2,\Program Files\GnuWin32\include"\r
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;_CTYPE_DISABLE_MACROS"\r
+ RuntimeLibrary="2"\r
+ PrecompiledHeaderFile=".\debug/rrd.pch"\r
+ AssemblerListingLocation=".\debug/"\r
+ ObjectFile=".\debug/"\r
+ ProgramDataBaseFileName=".\debug/"\r
+ BrowseInformation="1"\r
+ WarningLevel="3"\r
+ SuppressStartupBanner="TRUE"\r
+ DebugInformationFormat="4"\r
+ CompileAs="0"/>\r
+ <Tool\r
+ Name="VCCustomBuildTool"/>\r
+ <Tool\r
+ Name="VCLibrarianTool"\r
+ OutputFile=".\debug\rrd.lib"\r
+ SuppressStartupBanner="TRUE"/>\r
+ <Tool\r
+ Name="VCMIDLTool"/>\r
+ <Tool\r
+ Name="VCPostBuildEventTool"/>\r
+ <Tool\r
+ Name="VCPreBuildEventTool"/>\r
+ <Tool\r
+ Name="VCPreLinkEventTool"/>\r
+ <Tool\r
+ Name="VCResourceCompilerTool"\r
+ Culture="4108"/>\r
+ <Tool\r
+ Name="VCWebServiceProxyGeneratorTool"/>\r
+ <Tool\r
+ Name="VCXMLDataGeneratorTool"/>\r
+ <Tool\r
+ Name="VCManagedWrapperGeneratorTool"/>\r
+ <Tool\r
+ Name="VCAuxiliaryManagedWrapperGeneratorTool"/>\r
+ </Configuration>\r
+ </Configurations>\r
+ <References>\r
+ </References>\r
+ <Files>\r
+ <File\r
+ RelativePath="getopt.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ <File\r
+ RelativePath="getopt1.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ <File\r
+ RelativePath="hash_32.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ <File\r
+ RelativePath="rrd_parsetime.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ <File\r
+ RelativePath="pngsize.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ <File\r
+ RelativePath="rrd_afm.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ <File\r
+ RelativePath="rrd_afm_data.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ <File\r
+ RelativePath="rrd_create.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ <File\r
+ RelativePath="rrd_diff.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ <File\r
+ RelativePath="rrd_dump.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ <File\r
+ RelativePath="rrd_error.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ <File\r
+ RelativePath="rrd_fetch.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ <File\r
+ RelativePath="rrd_format.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ <File\r
+ RelativePath="rrd_gfx.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ <File\r
+ RelativePath="rrd_graph.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ <File\r
+ RelativePath="rrd_graph_helper.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ <File\r
+ RelativePath="rrd_hw.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ <File\r
+ RelativePath="rrd_info.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ <File\r
+ RelativePath="rrd_last.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ <File\r
+ RelativePath="rrd_nan_inf.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ <File\r
+ RelativePath="rrd_open.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ <File\r
+ RelativePath="rrd_resize.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ <File\r
+ RelativePath="rrd_restore.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ <File\r
+ RelativePath="rrd_rpncalc.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ <File\r
+ RelativePath="rrd_thread_safe_nt.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ <File\r
+ RelativePath="rrd_tune.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ <File\r
+ RelativePath="rrd_update.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ <File\r
+ RelativePath="rrd_xport.c">\r
+ <FileConfiguration\r
+ Name="Release|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""/>\r
+ </FileConfiguration>\r
+ <FileConfiguration\r
+ Name="Debug|Win32">\r
+ <Tool\r
+ Name="VCCLCompilerTool"\r
+ Optimization="0"\r
+ AdditionalIncludeDirectories=""\r
+ PreprocessorDefinitions=""\r
+ BrowseInformation="1"/>\r
+ </FileConfiguration>\r
+ </File>\r
+ </Files>\r
+ <Globals>\r
+ </Globals>\r
+</VisualStudioProject>\r
diff --git a/program/win32/rrd_config.h.msvc b/program/win32/rrd_config.h.msvc
--- /dev/null
@@ -0,0 +1,65 @@
+/* rrd_config.h.msvc. Hand-tweaked rrd_config.h for MSVC compiler. */\r
+#ifndef WIN32 \r
+#error This rrd_config.h is created for Win32 platform! \r
+#endif \r
+#ifndef RRD_CONFIG_H\r
+#define RRD_CONFIG_H\r
+\r
+#include <math.h>\r
+#include <float.h>\r
+#include <direct.h>\r
+\r
+/* the placeholders will be filled in by get_ver.awk */\r
+/* http://cm.bell-labs.com/cm/cs/awkbook/index.html */\r
+#define NUMVERS @@NUMVERS@@\r
+#define PACKAGE_VERSION "@@PACKAGE_VERSION@@"\r
+\r
+#define PACKAGE_NAME "rrdtool"\r
+#define PACKAGE_STRING PACKAGE_NAME " " PACKAGE_VERSION\r
+\r
+#define HAVE_STRFTIME 1\r
+#define HAVE_TIME_H 1\r
+#define HAVE_LOCALE_H 1\r
+#define HAVE_TZSET 1\r
+#define HAVE_SETLOCALE 1\r
+#define HAVE_MATH_H 1\r
+#define HAVE_FLOAT_H 1\r
+#define HAVE_MEMMOVE 1\r
+#define HAVE_MALLOC_H 1\r
+#define HAVE_MKTIME 1\r
+#define HAVE_STRFTIME 1\r
+#define HAVE_STRING_H 1\r
+#define HAVE_VSNPRINTF 1\r
+#define HAVE_SYS_TYPES_H 1\r
+#define HAVE_SYS_STAT_H 1\r
+\r
+/* Define to 1 if you have the ANSI C header files. */\r
+#define STDC_HEADERS 1\r
+\r
+#define isinf(a) (_fpclass(a) == _FPCLASS_NINF || _fpclass(a) == _FPCLASS_PINF)\r
+#define isnan _isnan\r
+#define finite _finite\r
+#define snprintf _snprintf\r
+#define vsnprintf _vsnprintf\r
+#define strftime strftime_ \r
+\r
+/* realloc does not support NULL as argument */\r
+#define NO_NULL_REALLOC 1\r
+#if NO_NULL_REALLOC\r
+# define rrd_realloc(a,b) ( (a) == NULL ? malloc( (b) ) : realloc( (a) , (b) ))\r
+#else\r
+# define rrd_realloc(a,b) realloc((a), (b))\r
+#endif \r
+\r
+/* Vertical label angle: 90.0 (default) or 270.0 */\r
+#define RRDGRAPH_YLEGEND_ANGLE 90.0\r
+\r
+#define RRD_DEFAULT_FONT "arial.ttf"\r
+/* #define RRD_DEFAULT_FONT "DejaVuSansMono-Roman.ttf" */\r
+\r
+/* #define WITH_PIECHART 1 */\r
+\r
+/* #define DEBUG 1 */\r
+\r
+#endif /* RRD_CONFIG_H */\r
+\r
diff --git a/program/win32/rrdtool.dsp b/program/win32/rrdtool.dsp
--- /dev/null
@@ -0,0 +1,92 @@
+# Microsoft Developer Studio Project File - Name="rrdtool" - Package Owner=<4>\r
+# Microsoft Developer Studio Generated Build File, Format Version 6.00\r
+# ** DO NOT EDIT **\r
+\r
+# TARGTYPE "Win32 (x86) Console Application" 0x0103\r
+\r
+CFG=rrdtool - Win32 Debug\r
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,\r
+!MESSAGE use the Export Makefile command and run\r
+!MESSAGE \r
+!MESSAGE NMAKE /f "rrdtool.mak".\r
+!MESSAGE \r
+!MESSAGE You can specify a configuration when running NMAKE\r
+!MESSAGE by defining the macro CFG on the command line. For example:\r
+!MESSAGE \r
+!MESSAGE NMAKE /f "rrdtool.mak" CFG="rrdtool - Win32 Debug"\r
+!MESSAGE \r
+!MESSAGE Possible choices for configuration are:\r
+!MESSAGE \r
+!MESSAGE "rrdtool - Win32 Release" (based on "Win32 (x86) Console Application")\r
+!MESSAGE "rrdtool - Win32 Debug" (based on "Win32 (x86) Console Application")\r
+!MESSAGE \r
+\r
+# Begin Project\r
+# PROP AllowPerConfigDependencies 0\r
+# PROP Scc_ProjName ""\r
+# PROP Scc_LocalPath ""\r
+CPP=cl.exe\r
+RSC=rc.exe\r
+\r
+!IF "$(CFG)" == "rrdtool - Win32 Release"\r
+\r
+# PROP BASE Use_MFC 0\r
+# PROP BASE Use_Debug_Libraries 0\r
+# PROP BASE Output_Dir "rrdtool_"\r
+# PROP BASE Intermediate_Dir "rrdtool_"\r
+# PROP BASE Target_Dir ""\r
+# PROP Use_MFC 0\r
+# PROP Use_Debug_Libraries 0\r
+# PROP Output_Dir "toolrelease"\r
+# PROP Intermediate_Dir "toolrelease"\r
+# PROP Ignore_Export_Lib 0\r
+# PROP Target_Dir ""\r
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c\r
+# ADD CPP /nologo /MD /W3 /GX /I "../src" /I "../../zlib-1.2.3" /I "../../libpng-1.2.16" /I "../../libart_lgpl-2.3.17" /I "../../freetype-2.3.1/include" /D "HAVE_CONFIG_H" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_CTYPE_DISABLE_MACROS" /FD /c\r
+# SUBTRACT CPP /YX\r
+# ADD BASE RSC /l 0x100c /d "NDEBUG"\r
+# ADD RSC /l 0x409 /d "NDEBUG"\r
+BSC32=bscmake.exe\r
+# ADD BASE BSC32 /nologo\r
+# ADD BSC32 /nologo\r
+LINK32=link.exe\r
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386\r
+# ADD LINK32 libpng.lib zlib.lib libart.lib freetype231MT.lib kernel32.lib user32.lib /nologo /subsystem:console /incremental:yes /debug /machine:I386 /libpath:"../../libpng-1.2.16/projects/visualc6/Win32_LIB_Release" /libpath:"../../zlib-1.2.3" /libpath:"../../libart_lgpl-2.3.17/win32/release" /libpath:"../../freetype-2.3.1/objs"\r
+\r
+!ELSEIF "$(CFG)" == "rrdtool - Win32 Debug"\r
+\r
+# PROP BASE Use_MFC 0\r
+# PROP BASE Use_Debug_Libraries 1\r
+# PROP BASE Output_Dir "rrdtool0"\r
+# PROP BASE Intermediate_Dir "rrdtool0"\r
+# PROP BASE Target_Dir ""\r
+# PROP Use_MFC 0\r
+# PROP Use_Debug_Libraries 1\r
+# PROP Output_Dir "tooldebug"\r
+# PROP Intermediate_Dir "tooldebug"\r
+# PROP Ignore_Export_Lib 0\r
+# PROP Target_Dir ""\r
+# ADD BASE CPP /nologo /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c\r
+# ADD CPP /nologo /MD /W3 /Gm /GX /ZI /Od /I "../src" /I "../../zlib-1.2.3" /I "../../libpng-1.2.16" /I "../../libart_lgpl-2.3.17" /I "../../freetype-2.3.1/include" /D "HAVE_CONFIG_H" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /D "_CTYPE_DISABLE_MACROS" /FR /FD /c\r
+# SUBTRACT CPP /YX\r
+# ADD BASE RSC /l 0x100c /d "_DEBUG"\r
+# ADD RSC /l 0x409 /d "_DEBUG"\r
+BSC32=bscmake.exe\r
+# ADD BASE BSC32 /nologo\r
+# ADD BSC32 /nologo /o"rrdtool.bsc"\r
+LINK32=link.exe\r
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept\r
+# ADD LINK32 libpng.lib zlib.lib libart.lib freetype231MT.lib kernel32.lib user32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept /libpath:"../../libpng-1.2.16/projects/visualc6/Win32_LIB_Release" /libpath:"../../zlib-1.2.3" /libpath:"../../libart_lgpl-2.3.17/win32/release" /libpath:"../../freetype-2.3.1/objs"\r
+\r
+!ENDIF \r
+\r
+# Begin Target\r
+\r
+# Name "rrdtool - Win32 Release"\r
+# Name "rrdtool - Win32 Debug"\r
+# Begin Source File\r
+\r
+SOURCE=..\src\rrd_tool.c\r
+# End Source File\r
+# End Target\r
+# End Project\r
diff --git a/program/win32/rrdtool.dsw b/program/win32/rrdtool.dsw
--- /dev/null
@@ -0,0 +1,44 @@
+Microsoft Developer Studio Workspace File, Format Version 6.00\r
+# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!\r
+\r
+###############################################################################\r
+\r
+Project: "rrd"=".\rrd.dsp" - Package Owner=<4>\r
+\r
+Package=<5>\r
+{{{\r
+}}}\r
+\r
+Package=<4>\r
+{{{\r
+}}}\r
+\r
+###############################################################################\r
+\r
+Project: "rrdtool"=".\rrdtool.dsp" - Package Owner=<4>\r
+\r
+Package=<5>\r
+{{{\r
+}}}\r
+\r
+Package=<4>\r
+{{{\r
+ Begin Project Dependency\r
+ Project_Dep_Name rrd\r
+ End Project Dependency\r
+}}}\r
+\r
+###############################################################################\r
+\r
+Global:\r
+\r
+Package=<5>\r
+{{{\r
+}}}\r
+\r
+Package=<3>\r
+{{{\r
+}}}\r
+\r
+###############################################################################\r
+\r
diff --git a/program/win32/rrdtool.vcproj b/program/win32/rrdtool.vcproj
--- /dev/null
@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="7.10"
+ Name="rrdtool"
+ SccProjectName=""
+ SccLocalPath="">
+ <Platforms>
+ <Platform
+ Name="Win32"/>
+ </Platforms>
+ <Configurations>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory=".\toolrelease"
+ IntermediateDirectory=".\toolrelease"
+ ConfigurationType="1"
+ UseOfMFC="0"
+ ATLMinimizesCRunTimeLibraryUsage="FALSE"
+ CharacterSet="2">
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="4"
+ AdditionalIncludeDirectories="\Program Files\GnuWin32\include,\Program Files\GnuWin32\include\freetype2"
+ PreprocessorDefinitions="NDEBUG;_WINDOWS;WIN32;_CTYPE_DISABLE_MACROS"
+ RuntimeLibrary="2"
+ PrecompiledHeaderFile=".\toolrelease/rrdtool.pch"
+ AssemblerListingLocation=".\toolrelease/"
+ ObjectFile=".\toolrelease/"
+ ProgramDataBaseFileName=".\toolrelease/"
+ WarningLevel="3"
+ SuppressStartupBanner="TRUE"
+ CompileAs="0"/>
+ <Tool
+ Name="VCCustomBuildTool"/>
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="libpng.lib libz.lib libart_lgpl.lib libfreetype.lib"
+ OutputFile=".\toolrelease/rrdtool.exe"
+ LinkIncremental="1"
+ SuppressStartupBanner="TRUE"
+ AdditionalLibraryDirectories="\Program Files\GnuWin32\lib"
+ GenerateDebugInformation="TRUE"
+ ProgramDatabaseFile=".\toolrelease/rrdtool.pdb"
+ SubSystem="1"
+ TargetMachine="1"/>
+ <Tool
+ Name="VCMIDLTool"
+ TypeLibraryName=".\toolrelease/rrdtool.tlb"
+ HeaderFileName=""/>
+ <Tool
+ Name="VCPostBuildEventTool"/>
+ <Tool
+ Name="VCPreBuildEventTool"/>
+ <Tool
+ Name="VCPreLinkEventTool"/>
+ <Tool
+ Name="VCResourceCompilerTool"
+ PreprocessorDefinitions="NDEBUG"
+ Culture="4108"/>
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"/>
+ <Tool
+ Name="VCXMLDataGeneratorTool"/>
+ <Tool
+ Name="VCWebDeploymentTool"/>
+ <Tool
+ Name="VCManagedWrapperGeneratorTool"/>
+ <Tool
+ Name="VCAuxiliaryManagedWrapperGeneratorTool"/>
+ </Configuration>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory=".\tooldebug"
+ IntermediateDirectory=".\tooldebug"
+ ConfigurationType="1"
+ UseOfMFC="0"
+ ATLMinimizesCRunTimeLibraryUsage="FALSE"
+ CharacterSet="2">
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories="\Program Files\GnuWin32\include\freetype2,\Program Files\GnuWin32\include"
+ PreprocessorDefinitions="_DEBUG;_CONSOLE;WIN32;_CTYPE_DISABLE_MACROS"
+ RuntimeLibrary="2"
+ PrecompiledHeaderFile=".\tooldebug/rrdtool.pch"
+ AssemblerListingLocation=".\tooldebug/"
+ ObjectFile=".\tooldebug/"
+ ProgramDataBaseFileName=".\tooldebug/"
+ BrowseInformation="1"
+ WarningLevel="3"
+ SuppressStartupBanner="TRUE"
+ DebugInformationFormat="4"
+ CompileAs="0"/>
+ <Tool
+ Name="VCCustomBuildTool"/>
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="libpng.lib libz.lib libart_lgpl.lib libfreetype.lib"
+ OutputFile=".\tooldebug/rrdtool.exe"
+ LinkIncremental="1"
+ SuppressStartupBanner="TRUE"
+ AdditionalLibraryDirectories="\Program Files\GnuWin32\lib"
+ GenerateDebugInformation="TRUE"
+ ProgramDatabaseFile=".\tooldebug/rrdtool.pdb"
+ SubSystem="1"
+ TargetMachine="1"/>
+ <Tool
+ Name="VCMIDLTool"
+ TypeLibraryName=".\tooldebug/rrdtool.tlb"
+ HeaderFileName=""/>
+ <Tool
+ Name="VCPostBuildEventTool"/>
+ <Tool
+ Name="VCPreBuildEventTool"/>
+ <Tool
+ Name="VCPreLinkEventTool"/>
+ <Tool
+ Name="VCResourceCompilerTool"
+ PreprocessorDefinitions="_DEBUG"
+ Culture="4108"/>
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"/>
+ <Tool
+ Name="VCXMLDataGeneratorTool"/>
+ <Tool
+ Name="VCWebDeploymentTool"/>
+ <Tool
+ Name="VCManagedWrapperGeneratorTool"/>
+ <Tool
+ Name="VCAuxiliaryManagedWrapperGeneratorTool"/>
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <File
+ RelativePath="rrd_tool.c">
+ <FileConfiguration
+ Name="Release|Win32">
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""/>
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug|Win32">
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ BrowseInformation="1"/>
+ </FileConfiguration>
+ </File>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>