RRD-Simple-1.44/0000777000076400007640000000000010746154056013216 5ustar nicolawnicolawRRD-Simple-1.44/LICENSE0000444000076400007640000002613610746154056014225 0ustar nicolawnicolaw Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. RRD-Simple-1.44/README0000444000076400007640000004772310746154056014105 0ustar nicolawnicolawNAME RRD::Simple - Simple interface to create and store data in RRD files SYNOPSIS use strict; use RRD::Simple (); # Create an interface object my $rrd = RRD::Simple->new( file => "myfile.rrd" ); # Create a new RRD file with 3 data sources called # bytesIn, bytesOut and faultsPerSec. $rrd->create( bytesIn => "GAUGE", bytesOut => "GAUGE", faultsPerSec => "COUNTER" ); # Put some arbitary data values in the RRD file for the same # 3 data sources called bytesIn, bytesOut and faultsPerSec. $rrd->update( bytesIn => 10039, bytesOut => 389, faultsPerSec => 0.4 ); # Generate graphs: # /var/tmp/myfile-daily.png, /var/tmp/myfile-weekly.png # /var/tmp/myfile-monthly.png, /var/tmp/myfile-annual.png my %rtn = $rrd->graph( destination => "/var/tmp", title => "Network Interface eth0", vertical_label => "Bytes/Faults", interlaced => "" ); printf("Created %s\n",join(", ",map { $rtn{$_}->[0] } keys %rtn)); # Return information about an RRD file my $info = $rrd->info; require Data::Dumper; print Data::Dumper::Dumper($info); # Get unixtime of when RRD file was last updated my $lastUpdated = $rrd->last; print "myfile.rrd was last updated at " . scalar(localtime($lastUpdated)) . "\n"; # Get list of data source names from an RRD file my @dsnames = $rrd->sources; print "Available data sources: " . join(", ", @dsnames) . "\n"; # And for the ultimately lazy, you could create and update # an RRD in one go using a one-liner like this: perl -MRRD::Simple=:all -e"update(@ARGV)" myfile.rrd bytesIn 99999 DESCRIPTION RRD::Simple provides a simple interface to RRDTool's RRDs module. This module does not currently offer a "fetch" method that is available in the RRDs module. It does however create RRD files with a sensible set of default RRA (Round Robin Archive) definitions, and can dynamically add new data source names to an existing RRD file. This module is ideal for quick and simple storage of data within an RRD file if you do not need to, nor want to, bother defining custom RRA definitions. METHODS new my $rrd = RRD::Simple->new( file => "myfile.rrd", rrdtool => "/usr/local/rrdtool-1.2.11/bin/rrdtool", tmpdir => "/var/tmp", cf => [ qw(AVERAGE MAX) ], default_dstype => "GAUGE", on_missing_ds => "add", ); The "file" parameter is currently optional but will become mandatory in future releases, replacing the optional $rrdfile parameters on subsequent methods. This parameter specifies the RRD filename to be used. The "rrdtool" parameter is optional. It specifically defines where the "rrdtool" binary can be found. If not specified, the module will search for the "rrdtool" binary in your path, an additional location relative to where the "RRDs" module was loaded from, and in /usr/local/rrdtool*. The "tmpdir" parameter is option and is only used what automatically adding a new data source to an existing RRD file. By default any temporary files will be placed in your default system temp directory (typically /tmp on Linux, or whatever your TMPDIR environment variable is set to). This parameter can be used for force any temporary files to be created in a specific directory. The "rrdtool" binary is only used by the "add_source" method, and only under certain circumstances. The "add_source" method may also be called automatically by the "update" method, if data point values for a previously undefined data source are provided for insertion. The "cf" parameter is optional, but when specified expects an array reference. The "cf" parameter defines which consolidation functions are used in round robin archives (RRAs) when creating new RRD files. Valid values are AVERAGE, MIN, MAX and LAST. The default value is AVERAGE and MAX. The "default_dstype" parameter is optional. Specifying the default data source type (DST) through the new() method allows the DST to be localised to the $rrd object instance rather than be global to the RRD::Simple package. See $RRD::Simple::DEFAULT_DSTYPE. The "on_missing_ds" parameter is optional and will default to "add" when not defined. This parameter will determine what will happen if you try to insert or update data for a data source name that does not exist in the RRD file. Valid values are "add", "ignore" and "die". create $rrd->create($rrdfile, $period, source_name => "TYPE", source_name => "TYPE", source_name => "TYPE" ); This method will create a new RRD file on disk. $rrdfile is optional and will default to using the RRD filename specified by the "new" constructor method, or "$0.rrd". (Script basename with the file extension of .rrd). $period is optional and will default to "year". Valid options are "hour", "6hour"/"quarterday", "12hour"/"halfday", "day", "week", "month", "year", "3years" and "mrtg". Specifying a data retention period value will change how long data will be retained for within the RRD file. The "mrtg" scheme will try and mimic the data retention period used by MRTG v2.13.2 (. The "mrtg" data retention period uses a data stepping resolution of 300 seconds (5 minutes) and heartbeat of 600 seconds (10 minutes), whereas all the other data retention periods use a data stepping resolution of 60 seconds (1 minute) and heartbeat of 120 seconds (2 minutes). Each data source name should specify the data source type. Valid data source types (DSTs) are GAUGE, COUNTER, DERIVE and ABSOLUTE. See the section regrading DSTs at for further information. RRD::Simple will croak and die if you try to create an RRD file that already exists. update $rrd->update($rrdfile, $unixtime, source_name => "VALUE", source_name => "VALUE", source_name => "VALUE" ); This method will update an RRD file by inserting new data point values in to the RRD file. $rrdfile is optional and will default to using the RRD filename specified by the "new" constructor method, or "$0.rrd". (Script basename with the file extension of .rrd). $unixtime is optional and will default to "time()" (the current unixtime). Specifying this value will determine the date and time that your data point values will be stored against in the RRD file. If you try to update a value for a data source that does not exist, it will automatically be added for you. The data source type will be set to whatever is contained in the $RRD::Simple::DEFAULT_DSTYPE variable. (See the VARIABLES section below). If you explicitly do not want this to happen, then you should check that you are only updating pre-existing data source names using the "sources" method. You can manually add new data sources to an RRD file by using the "add_source" method, which requires you to explicitly set the data source type. If you try to update an RRD file that does not exist, it will attept to create the RRD file for you using the same behaviour as described above. A warning message will be displayed indicating that the RRD file is being created for you if have perl warnings turned on. last my $unixtime = $rrd->last($rrdfile); This method returns the last (most recent) data point entry time in the RRD file in UNIX time (seconds since the epoch; Jan 1st 1970). This value should not be confused with the last modified time of the RRD file. $rrdfile is optional and will default to using the RRD filename specified by the "new" constructor method, or "$0.rrd". (Script basename with the file extension of .rrd). sources my @sources = $rrd->sources($rrdfile); This method returns a list of all of the data source names contained within the RRD file. $rrdfile is optional and will default to using the RRD filename specified by the "new" constructor method, or "$0.rrd". (Script basename with the file extension of .rrd). add_source $rrd->add_source($rrdfile, source_name => "TYPE" ); You may add a new data source to an existing RRD file using this method. Only one data source name can be added at a time. You must also specify the data source type. $rrdfile is optional and will default to using the RRD filename specified by the "new" constructor method, or "$0.rrd". (Script basename with the file extension of .rrd). This method can be called internally by the "update" method to automatically add missing data sources. rename_source $rrd->rename_source($rrdfile, "old_datasource", "new_datasource"); You may rename a data source in an existing RRD file using this method. $rrdfile is optional and will default to using the RRD filename specified by the "new" constructor method, or "$0.rrd". (Script basename with the file extension of .rrd). graph my %rtn = $rrd->graph($rrdfile, destination => "/path/to/write/graph/images", basename => "graph_basename", timestamp => "both", # graph, rrd, both or none periods => [ qw(week month) ], # omit to generate all graphs sources => [ qw(source_name1 source_name2 source_name3) ], source_colors => [ qw(ff0000 aa3333 000000) ], source_labels => [ ("My Source 1", "My Source Two", "Source 3") ], source_drawtypes => [ qw(LINE1 AREA LINE) ], line_thickness => 2, extended_legend => 1, rrd_graph_option => "value", rrd_graph_option => "value", rrd_graph_option => "value" ); This method will render one or more graph images that show the data in the RRD file. The number of image files that are created depends on the retention period of the RRD file. Hourly, 6 hourly, 12 hourly, daily, weekly, monthly, annual and 3year graphs will be created if there is enough data in the RRD file to accomodate them. The image filenames will start with either the basename of the RRD file, or whatever is specified by the "basename" parameter. The second part of the filename will be "-hourly", "-6hourly", "-12hourly", "-daily", "-weekly", "-monthly", "-annual" or "-3year" depending on the period that is being graphed. $rrdfile is optional and will default to using the RRD filename specified by the "new" constructor method, or "$0.rrd". (Script basename with the file extension of .rrd). Graph options specific to RRD::Simple are: destination The "destination" parameter is optional, and it will default to the same path location as that of the RRD file specified by $rrdfile. Specifying this value will force the resulting graph images to be written to this path location. (The specified path must be a valid directory with the sufficient permissions to write the graph images). basename The "basename" parameter is optional. This parameter specifies the basename of the graph image files that will be created. If not specified, it will default to the name of the RRD file. For example, if you specify a basename name of "mygraph", the following graph image files will be created in the "destination" directory: mygraph-daily.png mygraph-weekly.png mygraph-monthly.png mygraph-annual.png The default file format is "png", but this can be explicitly specified using the standard RRDs options. (See below). timestamp my %rtn = $rrd->graph($rrdfile, timestamp => "graph", # graph, rrd, both or none ); The "timestamp" parameter is optional, but will default to "graph". This parameter specifies which "last updated" timestamps should be added to the bottom right hand corner of the graph. Valid values are: "graph" - the timestamp of when the graph was last rendered will be used, "rrd" - the timestamp of when the RRD file was last updated will be used, "both" - both the timestamps of when the graph and RRD file were last updated will be used, "none" - no timestamp will be used. periods The "periods" parameter is an optional list of periods that graphs should be generated for. If omitted, all possible graphs will be generated and not restricted to any specific subset. See the create method for a list of valid time periods. sources The "sources" parameter is optional. This parameter should be an array of data source names that you want to be plotted. All data sources will be plotted by default. source_colors my %rtn = $rrd->graph($rrdfile, source_colors => [ qw(ff3333 ff00ff ffcc99) ], ); %rtn = $rrd->graph($rrdfile, source_colors => { source_name1 => "ff3333", source_name2 => "ff00ff", source_name3 => "ffcc99", }, ); The "source_colors" parameter is optional. This parameter should be an array or hash of hex triplet colors to be used for the plotted data source lines. A selection of vivid primary colors will be set by default. source_labels my %rtn = $rrd->graph($rrdfile, sources => [ qw(source_name1 source_name2 source_name3) ], source_labels => [ ("My Source 1","My Source Two","Source 3") ], ); %rtn = $rrd->graph($rrdfile, source_labels => { source_name1 => "My Source 1", source_name2 => "My Source Two", source_name3 => "Source 3", }, ); The "source_labels" parameter is optional. The parameter should be an array or hash of labels to be placed in the legend/key underneath the graph. An array can only be used if the "sources" parameter is also specified, since the label index position in the array will directly relate to the data source index position in the "sources" array. The data source names will be used in the legend/key by default if no "source_labels" parameter is specified. source_drawtypes my %rtn = $rrd->graph($rrdfile, source_drawtypes => [ qw(LINE1 AREA LINE) ], ); %rtn = $rrd->graph($rrdfile, source_colors => { source_name1 => "LINE1", source_name2 => "AREA", source_name3 => "LINE", }, ); %rtn = $rrd->graph($rrdfile, sources => [ qw(system user iowait idle) ] source_colors => [ qw(AREA STACK STACK STACK) ], ); The "source_drawtypes" parameter is optional. This parameter should be an array or hash of drawing/plotting types to be used for the plotted data source lines. By default all data sources are drawn as lines (LINE), but data sources may also be drawn as filled areas (AREA). Valid values are, LINE, LINE*n* (where *n* represents the thickness of the line in pixels), AREA or STACK. line_thickness Specifies the thickness of the data lines drawn on the graphs for any data sources that have not had a specific line thickness already specified using the "source_drawtypes" option. Valid values are 1, 2 and 3 (pixels). extended_legend If set to boolean true, prints more detailed information in the graph legend by adding the minimum, maximum and last values recorded on the graph for each data source. Common RRD graph options are: title A horizontal string at the top of the graph. vertical_label A vertically placed string at the left hand side of the graph. width The width of the canvas (the part of the graph with the actual data and such). This defaults to 400 pixels. height The height of the canvas (the part of the graph with the actual data and such). This defaults to 100 pixels. For examples on how to best use the "graph" method, refer to the example scripts that are bundled with this module in the examples/ directory. A complete list of parameters can be found at . retention_period my $seconds = $rrd->retention_period($rrdfile); This method will return the maximum period of time (in seconds) that the RRD file will store data for. $rrdfile is optional and will default to using the RRD filename specified by the "new" constructor method, or "$0.rrd". (Script basename with the file extension of .rrd). info my $info = $rrd->info($rrdfile); This method will return a complex data structure containing details about the RRD file, including RRA and data source information. $rrdfile is optional and will default to using the RRD filename specified by the "new" constructor method, or "$0.rrd". (Script basename with the file extension of .rrd). heartbeat my $heartbeat = $rrd->heartbeat($rrdfile, "dsname"); my @rtn = $rrd->heartbeat($rrdfile, "dsname", 600); This method will return the current heartbeat of a data source, or set a new heartbeat of a data source. $rrdfile is optional and will default to using the RRD filename specified by the "new" constructor method, or "$0.rrd". (Script basename with the file extension of .rrd). VARIABLES $RRD::Simple::DEBUG Debug and trace information will be printed to STDERR if this variable is set to 1 (boolean true). This variable will take its value from $ENV{DEBUG}, if it exists, otherwise it will default to 0 (boolean false). This is a normal package variable and may be safely modified at any time. $RRD::Simple::DEFAULT_DSTYPE This variable is used as the default data source type when creating or adding new data sources, when no other data source type is explicitly specified. This variable will take its value from $ENV{DEFAULT_DSTYPE}, if it exists, otherwise it will default to "GAUGE". This is a normal package variable and may be safely modified at any time. EXPORTS You can export the following functions if you do not wish to go through the extra effort of using the OO interface: create update last_update (synonym for the last() method) sources add_source rename_source graph retention_period info heartbeat The tag "all" is available to easily export everything: use RRD::Simple qw(:all); See the examples and unit tests in this distribution for more details. SEE ALSO RRD::Simple::Examples, RRDTool::OO, RRDs, , examples/*.pl, , VERSION $Id: Simple.pm 1100 2008-01-24 17:39:35Z nicolaw $ AUTHOR Nicola Worthington If you like this software, why not show your appreciation by sending the author something nice from her Amazon wishlist? ( http://www.amazon.co.uk/gp/registry/1VZXC59ESWYK0?sort=priority ) COPYRIGHT Copyright 2005,2006,2007,2008 Nicola Worthington. This software is licensed under The Apache Software License, Version 2.0. RRD-Simple-1.44/Changes0000444000076400007640000004347210746154056014515 0ustar nicolawnicolawVersion 1.44 - 24th Janurary 2008 + Fixed some small typos in the POD. + Added "on_missing_ds" parameter to the new() constructor method to detemine what action should be taken when the user tries to update or insert new data point values in an RRD for a data source name that does not exist. + Added "tmpdir" parameter to the new() constructor method to force any temporary files to be created in the specified directory. This is only used in the add_source() / _modify_source code. + Added a number of additional TRACE() statements in the code to aid debugging with the DEBUG=1 environment variable. + Changed graph() so that there is no longer an AVERAGE VDEF vname generated for each data source by default. See the updated example on drawing average lines in RRD::Simple::Examples. + Changed graph() method to allow undef or '' (empty) values to be passed in for the sources parameter. This is useful if you do not want to draw and data source values directly on the graph but instead only want to plot CDEF values. As a result and requirement of this change, the DEF statements for each and every data source in the RRD file are always defined early on in the commands passed on to RRDs.pm. + Added a new example to RRD::Simple::Examples to show how to generate predictive graphs to provide basic capacity planning. See examples/mem_usage-monthly.png for the example output. + Added a new "periods" parameter to the graph() method to restrict which graphs will be generated. For example, "weekly, month" could be specified to only generate those two graphs. This is useful if you want to use the SHIFT command to produce a capacity planning graph based on the last 6 months projecting in to the next 6 months in an annual graphing period. + Updated version of rrd.me.uk in the examples sub-directory of this distribution, inside the "rrd-server" directory. Version 1.43 - 5th March 2007 + Changed Build.PL to be more paranoid defaulting to no while using $build->y_n to work around potential removal of expected method functionality. + Added complain.txt. + Added instructions for installing rrdtool and RRDs under Debian/Ubuntu, RHEL and Fedora Core to the INSTALL file. + Various fixes and improvements to the POD. + Added another example to RRD::Simple::Examples. + Fixed typos in existing examples 1 to 5 in RRD::Simple::Examples. Version 1.42 - 27th February 2007 + Increased the spacing on the graph between the timestamps in the bottom right hand corner and the legend if the "extended-legend" parameter is NOT specified. + Added the "file" parameter to the new() constructor method to specify the RRD filename to be used for by the resulting RRD::Simple object. Eventually this will replace the (now optional) ability to pass the RRD filename as the first parameter to subsequent object methods. + Changed the POD to reflect the above changes to the new() constructor method regarding passing of the RRD filename at object creation time. + Added new unit text for the above changes to the new() constructor method. + Corrected minimum RRD::Simple version for rrd-server.pl as documented in README. + Updated the main POD to include data resolution information for each of the 6 different data retention period schemes. + Changed to an inside out object. Use the DEBUG environment variable to debug. + Updated and renamed RRD::Simple::Examples from a .pm file to a .pod file since it contains no actual code (only documentation). Version 1.41 - 11th February 2007 + Added RRD::Simple::Examples POD. + Added new requirement of POSIX to Build.PL. + Added new timestamp parameter to the graph() method. + Added heartbeat() method and POD and unit test for it. + Documented some parameters to the new() constructor method that were previously undocumented. + Updated version of examples/data_gathering/* scripts, as currently used on http://rrd.me.uk Version 1.40 - 24th August 2006 + Some small changes to POD. + Updated version of examples/data_gathering/* scripts, as currently used on http://rrd.me.uk + Changed RRDs::error calls to RRDs::error(). + Changed debug warn() calls to carp(). + Explicitly import fewer symbols from other modules. + Added rename_source() method and associated POD and unit test. + Changed the graph() method to recognise use of command (GPRINT,PRINT,COMMENT,VRULE,.. etc) over options and flags, so that they will be added to the graph definition after the data sources have been defined. + Added new unit tests for new functionality. t/35average_hrule.t tests and demonstrates how the changes to the graph() method can be used. + Fixed a bug where an uninitialised variable could cause unexpected behaviour if you tried to add a new data source using add_source() method. This only manifested itself with an versions of RRDs earlier than 1.2013, and where the rrdtool binary is not installed or the path to the rrdtool binary is unknown. Version 1.39 - 26th June 2006 + Changed the return value from the graph() method to be a hash instead of an array, so that the data inside can be easily identified as being generated by a specific graph (daily, weekly, monthly, annual or 3year). + Added the min, max and last values plotted in each graph, and the filename of the image file written to disk to the graph() method return value. + Added some more checks in unit tests t/23graph.t and t/21synopsis.t to account for the changed to the graph() method return value. + Updated POD to reflect change in the return value from the graph() method. + Fixed an uninitialised value warning that occured if the graph() method was passed too few items in the 'sources' paramater compared to the number of items passed in the 'source_drawtypes' or 'source_labels' paramaters. + Added client/server server monitoring scripts in examples/data_gathering. + Updated POD for update() method to make it clearer that updating an RRD file that does not exist will result it one being automatically created for you using default values. Version 1.38 - 15th June 2006 + Fixed a major bug introduced in v1.37 where add_source() method appends new data sources instead of prepends. This would have swapped historical RRA data with different data source names if a new source was added during use of version 1.37. + Added examples/loadavg.pl + Modified examples/meminfo.pl to create swap-*.png graphs if /proc/meminfo is available. Version 1.37 - 15th June 2006 + Changed the add_source() method so that new data sources will be appended to the existing data sources, instead of prepended as was done before. This is useful if you are updating your RRD file directly with RRDs.pm and are assuming the order of the data source names in the RRD file. + Added ability to use the STACK draw type in the graph() method. See examples/vmstat.pl and examples/vmstat-cpu-daily.png an example of how to use it and what the results can look like. + Added a couple more tests to t/26add_source.t + $RRD::Simple::DEFAULT_DSTYPE and $RRD::Simple::DEBUG will now no longer be reset/overwritten when loading RRD::Simple, if the values were already defined. The existing documented logic will still work as expected, assuming you do not explicitly set the variable before loading RRD::Simple. This behaviour is intented to reduce confusion. + Added examples/processes.pl Version 1.36 - 13th June 2006 + Changed unit tests to skip tests if they find that RRDs is not actually installed. RRDs is clearly marked as a prerequisite for RRD::Simple to work. The module should not even build if RRDs is not installed. Submitting failed CPAN tester reports because you failed to install the prerequisite modules is stupid. + Added some brief instructions to INSTALL for installing RRDtool and RRDs. + Added a README in the examples directory to help briefly describe what each script does. + Changed the following three modules used in unit tests to be reccomended instead of required for building, since the unit tests will quite happily skip if they are not installed: 'Test::Pod' => 1.20, 'Test::Pod::Coverage' => 1.06, 'Test::Deep' => 0.093, + Build.PL now makes an extra special point of warning about missing RRDs.pm, stating where a copy of RRDtool can be downloaded from, and that more details can be found in INSTALL. + Corrected a typo in examples/vmstat.pl in the command line for vmstat. Version 1.35 - 10th June 2006 + Added some more example images and scripts in the examples/ directory, and finished off examples/iostat.pl and examples/df.pl. + Added the "extended_legend" option to the graph() method, to include recent values to be printed next to the labels. + Changed the order of the default set of colours used to draw data sources in the graph() method, and altered the second and third shade cycles to make the colours slights more easily distingustable from one another. Version 1.34 - 1st June 2006 + Fixed a small gotcha whereby _ and - characters were not allowed in the "basename" paramater passed to the graph() method. + Fixed a typo in the code setting $VERSION. Version 1.33 - 1st May 2006 + Changed the default scheme from being 'year' to 'mrtg' instead. + Added the "source_drawtypes" option to the graph method, so that AREA as well as LINE types can be plotted. + Renamed the t/*.t unit test files and did a little bit of refactoring of them. It's planned to improve upon them in the future. Version 1.32 - 8th February 2006 + No longer set alt-autoscale and alt-y-grid to on by default when creating graphs. + Changed the graph() method to default the end time for the graph to the last time the RRD was updated, instead of the current time. + Changed the default RRAs that are created in RRD files to be only AVERAGE and MAX. This is to reduce the file size and update times. To create RRD files with MIN and LAST CFs you can still use the "cf" parameter in the new() constructor method. + Changed the retention schemes to be more uniform and added an "mrtg" scheme that mimics the MRTG retention priod. + Updated unit tests to cope with code changes. Version 1.31 - 19th January 2006 + Fixed POD unit tests to run the right tests + Added more unit tests. + Added retention_period() to exports and documented in the POD + Bug fix whereby the add_source() wasn't finding the best heartbeat (it was returning a source name in some internal code instead of the correct integer value). + Added functionality for the graph() method to check what RRA CFs are available before blindly graphing the AVERAGE CF type. It will select best of the CFs available, in order of preference, AVERAGE, MAX, MIN, LAST. + Added (undocumented) "cf" parameter inside the new() constructor method to specify which CFs to use when creating RRAs in a new RRD file. This may be documented later if it's considered useful, and if the parameter name is correct (ie, shouldn't be named something else), or implemented in another way. + Fixed a bug which (until this release) would not and should not have been noticed since it only effected the passing of default values to the RRD::Simple $self object when used with indirect method calls requiring the new "cf" paramater. Version 1.30 - 15th January 2006 + Removed notice about add_source() not working with latest versions of RRD from the POD. + Added Test::Pod and Test::Pod::Coverage unit tests + The graph() method now allows arrays to be passed as key value options, thus allowing many --colour values to be set + Added the "source_colors" parameter to the graph() method to specify the plotted data source line colours + Added the "source_labels" parameter to the graph() method to specify data source labels in the legend/key + Changed the add_source() method to try and use the RRDs module for all the needed rrdtool functionality, instead of having to spawn an external copy of the rrdtool binary. A patch submitted to Tobi on 2006/01/08 which should be available in the 1.2.13 release of rrdtool will allow RRDs::dump to dump to a filename instead of STDOUT, this removing the need for RRD::Simple to use the rrdtool binary at all. + Tweaked POD a little + Fixed some typos in the code where "3year" and "3years" were being mixed up. They have all been changed to be "3years" now, as per the POD. + Added the retention_period() method to return (in seconds) how long the RRD will store data for. + The graph() method will now only generate graphs which the RRD file has enough data for. (ie, a 1 week long RRD file will not try and generate a 3 year graph etc) + Fixed a bug whereby the create() method and the graph() method were both getting the same values back from the _seconds_in() function, which should not be the case. This was resulting in blocky graphs because there was not enough margin between RRA boundies to ensure that each graph was getting the highest resolution data that you would expect. Also, _seconds_in() was returning slightly too high values, which indirectly caused the X asis labels to overlap eachother in some instances. Version 1.29 - 6th January 2006 + Improved makefile version extraction. + Changed license to Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0 + Changed escaping on date in graph() to only happen with RRDs version 1.2 or greater (escaping should not be used with version 1.0) + Changed how the last values are pulled from the RRA in the last_values() method so it's more reliable now. + Fixed add_source() to work with rrdtool 1.2x and RRD version 0003 files. + Added /usr/local/rrdtool*/bin/ to the search path when looking for the rrdtool binary. + Tweaked Makefile.PL Version 1.25 - 28th December 2005 + Added Changes file. + Changed cluck warnings to only be output when perl warnings are enabled ($^W is set true). + t/04lazy_create_by_update.t t/07correct_spelling.t -- Disabled warnings being output for some unit tests so as not to alarm people. (The warnings are intentional when $^W is true, so it is deliberately set to false for those tests). + Modified Makefile.PL to send anonymous information to http://perlgirl.org.uk/lib/usage.cgi if the user agrees. This is so I can track which of my modules are most heavily used and on what platforms, so I can concentrate on maintaning the most popular ones in my limited spare time. Version 1.24 - 26th December 2005 + Added explicit specification of a data retention period (internally to RRD::Simple called a scheme). + Added checking of RRD file versions before attempting to add a new data source to an existing RRD file. add_source() can only be used on RRD version 001 files at the moment (rrdtool version 1.0.x). Unit tests have been updated to take this in to account and skip if necessary. + Tidied and improved some of the error messages that are returned if the addition of a new data source to an existing RRD file fails. + Fixed escaping of the time date stamp that is added to the graphs so that it is compatible with rrdtool version 1.2.x. + Altered parsing of arguments to the graph() method so that _ characters are converted to - characters before passing to the RRDs module. This negated the need to quote a number or arguments that are passed in to the graph() method. + Added some more comments in the code. + Added some more POD. Version 1.21 - 14th December 2005 + Added examples/ApacheAccessLogActivity.pl -- Example script to import apache access log files in to a simple RRD file for graphing + Fixed a bug in the update() method to allow explicit specification of data insertion time. Previously if no RRD file was specified, the explicitly specified insertion time would have incorrectly been used as the RRD filename. + Added an experimental last_values() method to return the last values stored in the LAST CF RRA. This is not currently documented because it does not function as you would logically expect. + Added t/09last_value.t -- This test is essentially skipped because the output is not as you might expect. It will be enabled once the last_values() method is working better. + Added the 'basename' argument to the graph() method. + Added the 'sources' argument to the graph() method. + Fixed a few minor typos in warning messages. + Added more POD. + Improved unit tests. RRD-Simple-1.44/TODO0000444000076400007640000000007210746154056013677 0ustar nicolawnicolaw+ Add a delete_source() method. + Add a fetch() method. RRD-Simple-1.44/examples/0000777000076400007640000000000010746154056015034 5ustar nicolawnicolawRRD-Simple-1.44/examples/mem_usage-monthly.png0000444000076400007640000005355410746154056021202 0ustar nicolawnicolawPNG  IHDRDrItEXtSoftwareRRDtool, Tobias Oetiker , http://tobi.oetiker.ch\a IDATx} Tű}_}Eq 5[B\1A4Ĩq7K!H J\pD 彼տ=LO߳L͝ uէܬYH沁zrii__jjjj*_BW duҢ6&&8!g}}66u6666Ф6 ޤzvՃPPPȾ (+ \m@m@m@m c6+=\9T)g:IӲ?>>RPar}W#'Z ⑆rjjjNO?4-Y^~ȱZA\A<0FFS]m4}t-ϙ3'rVW4FdG6mm[guhV\.YwD[:^E\zno=,WRZ#|ji??S,sym…57 M|tIy Yj~:pkYKFѴWFO~9=Okwti|NvmDsM6+n[#*]. mT]־ d\շo_K,=zr zUo>]{ή_*>}~_zrovݻ7͛Gzr:fbI4m93  f_=馛~gtil@k6 jӖ.]ZO~Bx`9dۥP/\ s<]wmVg---=1(6]w|>_^uWp },_'o{ۗ_Km|kyc뎺P>ɾgAkAiџnl3X\o'҈#C ď9:蠃?{Ygeu_um9^ h{M'|2O ^{Eg}vY>+#3'4ĕo}cجq;M}qǴxb;2dH.Ok?aZKÎfpy4:-ZD}TlUɓ{V\^Y+2bqo8!=3fXOW\k՚VJ+Dwuղ_UW]T|i|$^xZqysz6}\O\i+~oLpa^[6u_n|ⲅB'i)/fLXG}45f+ $Ö$v壮AeXO\]2<(19K\k@~7KK7 mt~h346M.qk~D0../^7=~>WF?ݏ3m\:c߼U+ɾ,Opﺓ8Ώn^ Rģ n5J1YM4ı6:O\THM0n7GϜ9ӂ9+>0 ti2.+&\-“³U.AX! ?|8:7*MkTqK}k=7._?t˺8o1Ώs:Bv9OA++¨O;ꨣhС84{lk68&c#-i~UF ^Oxc3=#R㬟a\Q~Z#_.?zIzoF; ƍ8P6;, '`s8?.}Z7OV| F]5X /0~C۫W/l]ڸkl9rzGx}햋)VX?|ާNj'Xƣl?#GRn,9m?s}+|*JB57pCCf[mBTq dA,`66666+"UWo\oԨF.:pxе(pqfnFDm@m6P,R:/cy=#tG)+i_͚TfI_[}zӝ-Zo  ~Ee ,eqk 燢Ӳg'\OZJ,E%rHt'+,VflDoIt'CB&?юnOx(E4èB!'[-麱-kQh;cF"XPrgtcx,KnfM_LBB*s(9$|%@ԌB8AN"6O|L.>mWqئ ^ VxInfM_W/C D,J']2CoWq6ƶ:\+@j 7mpB~_AIHH'9@+mDwڗ&JWZ麜Kڠ7r@moZ ԝDPItY2W t\.}(ڮîj,#^A\AN@-|C!Q3O ] :ZWZ4 UK>xI,JCF'95cDPWw4P+6NA\A\A %*.%r 6#&L` D.᫞R}OxɻY`*Eܿ}h%zI(COΒލZ}O\A&>xҨ,JZG}RC!+[3O ]Nr%޵V=qqeƒh!PJ _ޚ}"](+;hijyҥKk,}&=m`Z)`H(T2CWflH ;,+ytoa?dɒD0 ᄡ{R>}heΝK{i#P0ӉKtWrF"ģ8Mu9%ANA>DʠKE~!M6fϞM/ĭ̀Y={2d=6>f:餓u\:7 uF" J28eV 0 >̡'҅zePƅ%]1O>ӧGVf@cm- ۣGkG3ǥ37h}ل!Toj]}@>B*'뿔G*fdOű́СC/x#lq@=x8 dRTNB+[VdOB+mDw9$i2+&Q2-S ~5Ѕ^Xp4KOJR@e7pCy_%E/L癉*q4#m;j# |.T2CWflH{J;ڴ>ҵnv,d %G_`(hqx$ChfP}3gI$ md$CB&i҇U~~3 >p z>}#!QHIdE+mDwyΜ͚U 2CxٻO (+K=^2 -CB+flH~U:"QwmeZEZ}棍f߾HW(&iRo>UV=qb[,ov`Z)`H(T2#wS~tՃ5TޤiێP|z! Eɜ ЧE 䓥n~JWi҇ꞸxXGjxeF@ $rHHWB+Y›i1XHt Iy( tEeC-; ĺ{@S0,`9f(QT]mj _Y5S=(A=qe]Pu(`j vSLQO<t |#|饂y`Cēhr!i]7Xz@吀Q#2G6EYy%ޮ.x0 0Φ ȕ#N#-t ' tiEjku+7i5*yYH+\:簑6<zsXq*O:W5PM<|a(Zü!_QbmH^buc8&X.]>Q_7.6۔J^}:${̛rY9*Ao.=vӰ W Qn9Znύ8=*dZY9q8OXrSN)2/lV[w"m]-HWէBy\O,]y.}(ڮ`:u*6'ɣ]A\ASzp!ӺiqZ7bx .+#ӗXdf8LwaZ GQ]~ۣG+v[&L+WV.|Y&9*\7tEzLt~2(Zzh]2O7Y #馊N2Ge~tAסY·/X> '! QJ c@/++8nߐn^xEmxiQre8δ_sMe b:7dZ7ͽvqM|ͥesyW]Go#n`3hn/Ӳ w/ct'4LpڴiuE^M[%׾6+yok]]J羾w\j2-.oNӗnzE)rcsw"}fnL/.40A=\~yN=h,q ؆j /(Gt-iA7η]Y ksDmh̍iQ|3?3-9d:7dZ7ͽvqM>:21=O;;J0]9qj< Vϕ6e9nEs Oz.yEזw"J|H_??`C,q9|3K]i86q<r+,Я~U ユ~pb45N9帯 n9&y;98x>_]w2[^@Ys6Z!D_L7* T"q׿Z0#y9Q^N'PΝKy@{DJ*rZgA7PTȴ EW ^/ ;iod'BK?`1g>gt=Ӿ}ia>p铭{a\m e_Y-Xe*"-Xi%d-; ;fw],t-%8nǿL7q2L릹|xf[mU x|HO˯\29|9Ρ[~eJhQ:@|]ED |:J̙m?nTygZ뮴pOޚf`؜ ѦGTH؂5פ W1!o]ҀeP-?׏=p̜~~qaőGҟ i Ӑ!'秜B} >2S5r$-яhǒghY_=85z9o'̽?,}_03g.O31]矙  `{晕FCKkq}y"δLQ)a:x3 L;p`+[|L|Efe~e>h.[s!_ۮRϿ_;/nxc.5ה=__K(ofyy `6ʛ>-QG5;f2< ʛ2o oHΛA~c訖_Q{鸖(o Ol7Lƻ/#oy=GQxby3p x(Z>ZSVjDzdQQGQ뷿Mj5+[k5jՀZֽV0/߭^nفܵ?-xYViٲ%M5MmٞZR$QnbCgهoٗ^u?zx^2rz춖j<ɛ[N*3蚖aj@˶Ѵ~Cy7iXxy kr#uiX?K3)ý:}7GzCހ~&4`gdʛ$$o<#i,njMK/|{i]fs7N?V9o̘?<|pY8gɛ/efZ?莠ح'dn{/;[jK{$@; qC {oaZQS'֕ěyrȴn}2Of@+̞MkfR! sө4` Fq^Ci9_dž@}x|.4Bb/qCNgz!{b|y-|s!ߢ] pyHsA5kDV̽_ZJ5~ov̎y:u3X6ϽB18lܓT]yX0f@! `~Ex LðVeðdv m[owb0c KnƋ)7b9x84q~ذa̪]3ϬmcAyİ$8#]&˅NX:Ć b&lRI[.9Q7*l)EE=ۖZC{o4k4g7#6!A]XN4٭9㝡==fԶϴsdnX Č ;;wZ,-'.sDo32懜2s{:4eC:6tŤI%_|";/M oҤt\-Ƨ[ݾ kqz\zީer+3qv(`j ^+ ӌ !a{3ࣲ'ƀSkY;xd$9aE1h6h=b<5h<3xhf\m#*݌6w7EvߋlCSrSl0 3j쾜x/r4nhNx˞];8lz[ӭ[bxfsBO}M1Ğ=wmt|4'chJÞj<=pcs *&Wz7^36`s9QǬ&XOx8f< d:Lou-. 2iL&\JgdbОsNGkiF7-iBP ocu;A4q}>Et =zCZZoC8vOVwIP5PM<:q< `fn-raG7 D} #[枥2%\x *Ln fbZ,M=[fإD(Ki?HQ7hqp723O7*9tX8U/. 91'+D56rr:+b~L:G} Lá˷B3-Nag^g/Ӵ 8ܦ>ۨ4 }GӲ92ϊ'MnރOŸN?t2.(Z7߽n>6<:7>c\]]u|}Cf.=>ղG̼+UJ.>mWg~ng|'Q23t7k(]N8t8Kx7 7(+^YW8o|ӟ衇z `<8te55i, Y\#C!_w] U_9*/*MBѴW!N2˷78Mo{Qq߽֦g^uUm6D1_d5HcZזh97!`Dᴤ0JfwAסY·8pGc-Į986eo]wh\8o_z_ eݵt&6nx!qt8pÃj~x.P/ssqf2h8~ۭCCWs[ Wz#>7Sҝ[g5".MТ> ,JZ6~M+'I'i_bY\2tkvx53ܤk In>NWO uϼW\g<{GT卥ReDv+t䟒tnuA Ǚ4|\{m|f΋ ]9?xn7^7MBr,kIY9 _&W Qzc{}&i_̮gP޵ztqwC^N|a(dZh&} !t&LyIYn~Ե7*M}ΏoYQ(`a ezk'ת4>he{ 鄂x`/it37l >Ʉb=h#H#س@뷏6 Pen#׏vHx7ji,s=BB;^-Kn2 m-{Kx7?8ٝwNx'WA V0ҝֿO/0K*7.\h}ϼǻ q~\xƛ5~@A@T6 ^֋z%V>Ji2+&p9tN/C }&^R^AP|'W`C!+"iH}݅j_*]Ky/s/^lcGad lyIS]A$ߴ U>$}""iH}݅j_.(:0Kv5-Z9+hv =N [x(IJh1PK@6|{}Qct^NzolW>|}Ÿx$}""iH}݅j_ *񴉇'ƱeW7q7Hxwegii-]uVkȐ!ڴ1cƔ?(Ux<@Q]A$ߴ U>$}""iH}݅j_*]Ky>xsǫAgGv)a={ƥGg}6͘1Æ~~ ЅDBM_W/C' D,J']2CoWqލ>^ >F}޽#FؐQro[s*c'.8y- oxY>O$rdV>B/Mfq_x# pO|Μ9Hci63'j~^}oe7pCupf͢E/L JFwܸTx5YBɑIt}RC!+[3O =%ѝD mZuMVn<:풇YJ4'-{챇9Z8f\s 5(R`O8;vU<@'xKRӏet9⭆BM0_$OgEPrHj$V>B"MYJ>]Pf/i|}<7vSLԽ;aVkUVYņ\_0`aw:vm?ZAtP|'W`C!+"iH}݅j_>XKK  D&M*O̡'҅z7\@kIhr:&e?e(0WWOi`Z)`H(T2CWflH U-f mWq6{| QJQO\=q f9j>/x\>s:u*}_iv%@+D jTPrHJ֌B=2(Bڮ承hѢ2pϜ9|PA\AljA, RQIe%Do>.  efvC`Z)`H(T2CWflH '֡ <Z2Kh+UNbG4PD$ޡhrsWNwCZAq}O=oɻY@&Eܿ}h%zI(COΒ@ލڮ8@ *{ ƶHYM0;}3gޤ}IdO=/ڻ'79TWOi`q@UL*3 }&^AK] ,X`=q?dPA\A\A\[L$ޤtb%# It}i2Wr@`VO<=qx3L\' o`Y8+2CWD>ȑZi$ վ4} Ti8\7_}U}OV%*W. oxY>O$rdV>B/M dҀ˥EՖӁWjO?MK,_0]N+@j 7mpB~_AIHH'9@+mDwڗ& ʸ] ӧO]̙x,&*MAtP|'W}"# It}i2+&QRO… #A神{{'τ0OH6 }h%:߲ s(讳dP}O4]y'^V1@Uy'ij@aizěD,J']\;KWiާK-׊uGLۀtրJ _ F'҅6$!Mq %|ģ_GSW/{b`vA$TfP3g OBF(5)&zЍSWWw Ƴ$rdV>B/M dҀ˥Ez5Hu9o0 F+mDwt& ʸ }`I|F-CB+V>:K dҀ˥ExΝK/޴~Ѽyrqq8d ["VD>.w1<һx!CЃ>h{̘1tI');ϲ$Yh!PJ _ޚ}"](+;h2y|rz=JROϞ=KtcnlT2%|3h~Dw*rĈ6xbt9]y% %=U+$ؑ6MjWw6`\W/?4w oii/8:9+! F?84<;Skk;v1,d̡͂dOJ _ޤ}"# It}i2ctxbUCBo~]IPrʗAC; ~ 'رcx:dcO<|P^~f c(9W>BO*s(9$|%zkIt!mDw9$R%CJrYK864`;}% cԸ;[,կ~Uo OwͦLŝ6qSK]GqN{AOCX^ݻK:. 7@O.Y$(?#K_ ?ju7tP3;So|{u;h%@+d^Eg~㉣>Kr.8> я~d'=PŒz땟Y;쐘<pok<` U]s5tgó3v>\4t6WޠiӦzev^{͎ouSZ4i >j o߾4cƌ2tV(&7tS\a+=m_\NϢ|uUg-|x0ӝ\`Qz>w曗QLge~]<UVgqJ+d7n} yR;n\:8mvSwΓotB'kauꪫX97Iu6HqSK>X)Jo>(غ;ެo,OW1Ir IּMuQPPhP7BjfqqĨ 4 (7i5QeVOGm@m@m6 3p&&86v6T} 4 (+ \m@m@m@mIm@AI;g*z:jjjqq 4 4 y\._wu#dv"n;Zq5.\H_~y{9pXBէ|ȕŏRwM#ynϰNq2.7}D0`@Cxq.D}\bTf8bG~.Ԇ$h&2qhmIu֚H1ٗ믷nB/odR dğ}YqIc8*'j@޽{| ) 1q1V[뭭3|8+8O#@<o\n %\b۳[ٳm~w]6'n޼ytGڲ-_|ql}qrm믿> 6+V&LPհ i6yw}c~;,#^#,X@kFm7ʼ$a(w3ga2}k#Im]/˭7A$'qf=3gN{< `}}eqjqxz ͝;MZ$id .%'x:(:˃=fW(h4'M2}S$.'͏KlPiuWZ6i1b Ḿkc)VLp}'6 y> $_XO? .7h3^ɋs!&$x>kw޳g*v}w;l|ʔ)޺θMmHӀ+)?F&/nr;3h[qꋓӧWM՝nỹ)1d`a5AK~$aR=[._OX/.7ıW- F[<1Nw},fN;-ŗ^z)\ǧI04Kj>%ܯ_?k۠sO=Lʻ[im |s|nJ۷47ց'^}f tdni ^64D_tEđtRzG߭}{˻u;)+noy_4L8O['|gѦnJb]96n6+YgU5وÄnUW-ۃIc=fy^]DC{Y{'XVӧOa:b$֚ f0x`8Z5* K.]vمrK{r6qX y,wV6଀uY穧jĹKC>&kl5jCG+lѱLg8=y$-U.@>Vj.ɆQyHäg\eO ڍ :QF/‚kI=r^_gʙ4ֹ8蠃M~3> ȰYHuè~x\Q>}sTT>ܺ5*s7=%_ڀgٷ~]qyb~ԩc_ #-e=W\FiE8q:EθǍu(:jIW7 EQJSC ^ }lbc%.6`630swn²9x8כ^|ԣG*:N xf 9=<;7aqrȋk+ox ޫW/:thIo=+q:suJ+Yޯ x!;<=SO~qgR^#K7nmܴiӪٽ޽{wcRMl) { s/^\s+|\s+$//g; if ((my ($dev,$data) = $_ =~ /^(.+?):\s*(\d+.+)\s*$/) && @keys) { $update{$dev} = [ split(/\s+/,$data) ]; } else { my ($rx,$tx) = (split(/\s*\|\s*/,$_))[1,2]; @keys = (map({"RX$_"} split(/\s+/,$rx)), map{"TX$_"} split(/\s+/,$tx)); } } close(FH) || die "Unable to close '/proc/net/dev': $!"; for my $dev (keys %update) { my $rrdfile = "network-$dev.rrd"; unless (-f $rrdfile) { $rrd->create($rrdfile, map { ($_ => 'DERIVE') } @keys); RRDs::tune($rrdfile,'-i',"$_:0") for @keys; } my %tmp; for (my $i = 0; $i < @keys; $i++) { $tmp{$keys[$i]} = $update{$dev}->[$i]; } $rrd->update($rrdfile, %tmp); $rrd->graph($rrdfile, vertical_label => 'bytes/sec', #sources => [ sort grep(/.X(bytes|packets|errs)/,@keys) ], sources => [ qw(TXbytes RXbytes) ], source_labels => [ qw(transmit recieve) ], source_drawtypes => [ qw(AREA LINE) ], source_colors => [ qw(00dd00 0000dd) ], extended_legend => 1, ); } RRD-Simple-1.44/examples/meminfo-daily.png0000444000076400007640000005772110746154056020302 0ustar nicolawnicolawPNG  IHDRDrItEXtSoftwareRRDtool, Tobias Oetiker , http://tobi.oetiker.ch\a IDATx]E#4kLHWjB|JQJ U'B|D-(JM(!z `H( P}>}Ν}Νsnro̝fffw^x1 LLLLL\uUuZ4//?Cg000000:`$n[tS0g+䮿B>>20l07@7#nqma:`:`:uHHV]Lz!*vlQe$:.IWGG9>хxT1:C-OLLLׁw}׍?͟?M4):WGZͰ2LL>{'̙3=+ѹHH<VH˯{Mr<ѣ[aܯ?J+[n9_~,;)0lذZm\;5čRX: x6lp}^XX8ڬ+vi'wwTtYXh9/υ^6h#WF6dߑvu$m, ^ lكmFΝ2)ҷo_#޽{/fXwWUW]}Mշlns=󫯾~7yuH:6< DNكm亨N,Guo[wGWٳݡV_}u/} <uW=wqnmu/|vڴinww_=A|nΜ9IV7n_@+<#}_,XPI_.w&vYg5\׹W^z!ogHdUVqvXvS_XNgmֲb/ܮZicu\+:4ڿA]s5jֲcEGV\qEۤ-/n5mּ0swRHڃm2:N,z9rm-RED?ݠA[o{B}Qw{9V^z'_`wttc7c ;ÇWR`qoyUV'LPIq?ӿw6 _|^u0=O>UؑO~a9 8}VV/OroY rB-_+jʲcС4{8>܆nX# 3w9jqltfWjNBsu[lׯ_Մ}ؘ{C噌ّ2{eq"a;_k=\%L!|ݙO2ԓ0]̭yqa?."Wσ''Nt.lh}!Z^-4]gaòb,–^zJk\,.oY\7J9-Eaiy3>[e4^<@.vVe^ztllpԏA8nԨQU.?` c5Ls(sg'Yd SI#s8^ ܜJ-Os7va_ynM6jDh[_ "7ˋ˶YΪxQ8X9HI D_,WQTV 畭ٴa럳+V6t?#q,1`,,Klb t'1w 0mݑx, \oFݐײyOjZN D"65YA!INv"Q ?-o²b,xk^|4[;`;?-N@a{~3`]Ē kԩS0m{zORσt?eO?9]Q6My3w4/OC7p_Ok]ۋ/I{*Lv'tk__ΝqNO~Y^[6Lڿq98u]WEu>E/Х!CZwk8v6mgsUwLj'n[l{;+?6dØ?pCe:xҢa<7t7=P.vr?O;'O ¹yGYDzn"!:{~Zkp'N<;u]t~66;<c _]mo ZGVNǭ&|Y ~vUHRg>ӧկ~U_ۘڧAGft*y~ Φ ̽Jv2XbXXƅ]"z*{=zvh= 5{A\5]EDe*[=h$n$&fLr5#fkw"s!7=ڿ"Ž}_=q#.Ih19(;{eK/_2~ϒ*ӕH䴤qGm$n$nDa:`:`:`:Mt]w;;N&UYwc/>o}i}i:`:3>Uމ}$ {qtkMh][n?w6ؼ3#gFB^c??^~}s$6X]8$nqbL/LLq#]wJZXӁfo8X=Ɏ{%φmMaۙcm${wmM#TT9ʜ+զX|#eʦ'|ö02l كmr*rRT9JMϦLHr4.U6U2 [Pȿ |l4&T95Uvmt$lK$y =Oӝ]:9g9bar**G|?vb*gضS+ӌ?88#dlK$K>ĭ~GWKHh,euXʦQ)k.L(Uΰ1aC:,T9J9[8$;n%xˇ-nדvu?rr$4:,U\ReS()?Y"ڙQa[=.b>6JuX*FrnsBud}h7!!Fڃm{vO<~*J{1T 6,\Qۼ(6J77o6`ARت:Ħb*Gv/bf@uxxpMD&"d btʱ!aԓ^Ӥ!oz(<^hP=2x|C[.ga 0ö h3l!6T94+617o6`ARت:Ħb*G6B)FF4l;H[Qǰ-ɰ-ƇTR("nDHHfڀmIU@8`" QeނBb!쫯,X`O+ ힸ(ydXa[N96$zkT9MoŸbQ{?<B22;/mf\z饕JO6n +ѣG{QF9tf$n$pdC-̰m"ϕQ0rxH33gM:}Q2$Zk~ СCرc+pxw煫\h+_qrHHhȆ(],&*mamyTR娇m^oCl| ?OFyː;w찗_~Je{@xxBڀ-')1 C ;T9jaz qPgu{kϽ{#GVc`A7t8p曍Url98eXö=&aawb*G-Lo}!1̰.CG=QObac ApzA,~. }QFCp[~C`#WtW)t,vNDT=0O`X׺R*G}aH;SeCAڡ뮻l'^}8bʠbzÁT);YBrD ܄61LF{.FK8zc=~.`9O=wk5}3q{5f2.BE~M*98LQbsK%N"&7hMK~EWO`شs@abEbyߴV]-?W) ՋqiC,3cpe1,Nbz˼\IbzCs?5|qԛyZ1% 9,ԗ 31toyE) rƪ23YYyË[C W9R'!`8m1XsD91\*D8IP_ sKњy113D1I!2&l &q״L׋`Ҹ}RajL<*5NmDnU _ EL L 7atN%Nl.n/<$$Clb͆n5͑#gp=-A`VsAD WD/''9NYɖ,!4Gk5LlՄElZW*F"en- c!>ꆄX]#2u]!#Caǰp=$ 1de| Wxg91- 6 #[P6c-w(ɗ'3UD,@I=C[%?!Pvƌ=ޞ9sgϞF; MSϯ6R*2|)%ndI"u-a׵ab}Z`tY= 0$w`; ;O̕bxpҐdH1Gک} $wkc G2º/aK>vã2I)ag_ϳ#17K1|Γh=_]Om d ,\P^v(-L楉#G?̟Ű,6vW#,az[8MJ坬o8l;q^gC.??)ZHH\#vdx(*6؅B/I$àVH_Hr|_$.2SeU\Y#rj$Z8J.mgZڿI`^4L-|ce5噄^v֛GlL<(:0p_,lnK&7W+p{キ?װ݈fg$>Jٹ+UŴy (#6F~brjgQrԥӤOORiԞ4i[`0a̳&8n4z~;s:ԍ;ևstzw^x<>=ʉq)Rk&C^fx0£"!Ç)r$m$^<&÷da'ŸTcR)U6U.FJH5LmP$~dV7J-bQ|ʕWf׫ 'F$WQYp;N/F;6AL?6dOwygeRVZɝq>oOϞ=}ȑ#~ᱲ!n 8|FɰFq>:4rȧʦ1!fuN>SDeW_jZ)$_6,r*[$n$jm:3,Ĺ~7:}m޼ynuDRZ=s5Xû{{=(v+G/v,2d7nw_ynذa2geU;p5O**GMReSՄ;qKו0v{TuI&rL+nLo^n)"q;6'~>= qiHo qn袋; 6SNuv۹>/Iۯ_? f^}XY2F6Ƈj94EqY\kѕZ&yʦ-2ċ4X?݂^ΎjRK-jַo_BZEmۀ-[.%q &S$TTEUxDqZB% ! 5ۅ_$geoV\Jxј lQl8Jص27U6UnQi$^ES7\m;Nۅe ®(HHh֚ d%=q&Kģ0bKfFQH+Jܼļh/{s. H&6͐x;FFⱁaհzl~cD+3a꤉]J-|SeSUFZT?j?%0 [E"n.lk3 7Hcy&x,%v^WS/ۉ#7lAJ Ń<@ܹsf5`Qc$n$^4(],H6Q(l>_2z뭎9wݹX|QXH%!燐jTT9ړ**GF⠐hL*F\ȍOѓЎsV|{?)6|$?܊+X%;m4q݀*_1 5}rcy%vxtآt8#*ma`k$ކcePK(I_F5krHo_lxyM7oHZol2lIU@8`"-4C\+gk^zjXO,\G$N|\|8X8k  nIτ5AlX=T9NM\D3T9MsiDmrX b|5V | $ۺ3ICڡ뮻*|o|]zY=sFFC>#bHܰFt(U6kewYc;qIxtآt8#*ma`k$ކcePK(SYXx&vT*x1H`kzk؂@#:*5({6ϐ8b1bW:&4<uʑleKD;S1JK^bc~)fqաTRLokayN>#1h؂ $y[P3$vnE{#x1r`k;qѡTPDwu'Ov3gt/{ע #q#v#&vT*x1H`kzk؂@#:*rG}Q7w q?wxoa;H[QHF55lAJ B]n#q#v#&vT*x1H`kzk؂@#:*- 07o7m2lIU@8`"-4C\HO_zh2$޿ۻ-p iӦ9ـ*O+ѣG}*$˷ǎW\q;;/<@|zt^>EZYMYDိ3/ƨlMo [hDReCe[ټ kx~6x޽}PJyOhCÇwSLx`ɰpl{\ i}FLM riz aƤbʅӈ{ƌg͚mgr$~1cTHgϞ=rHo_lx!n 8|ͶWͱm258)ǎ=b$ElMohV j`RRӔZTEU&3r`jkVH3l6~x7|X]ė]vYtm|B伓_~vکtz^xY2F6 pō]q~7qzF5ɢY7lR1 |gݓO>__y /yv+FFaհz&;%oWu_**GReS(H/6I(k;Qo$.׳렎ဍyb#F C ;P.'77o6* UuxċAj[[ѡTPI;xoa;H[QgQxIvpnm5H;SeShmqQ(%l ARت:*nLdKe s|1_XbFML[Pȿ |l4&PI;oŶɰEh* IL}}Jސor/6I(˒lgčU+M(pFVzk 'q@ʣ23U6UނBec1rM3$>{l8:%kmz6yʦQ>D3T9m-۳d~EHH)qzb-7G^ҩeFPQy5NsVa[O#b;}tdɶFhLW1rww7Tj9eQ}st٦I^VZj̢s)v9Q(Ydx;Ͷɰ$Uိ)|͑Cq;|ʷ'֢#2tkr5ZT9*bz>°-ƇTBin<8w쾻+}q42%7/;q-pyE5Rma[?EywKsԩӮ=Ĕ**GÛMۙQcuyzNX&Sw{L. yjdeCʁZfi4:ț|۸KeIQ0\?ot1$ҧ:LaTT9\颋׽)=LX\7eOWO@nՕK+7}Z߬\7go|&LȭCV6O;# 7LX{is2IK+}[8ѕa\ψy ߝ]"~$oB9sa:Vҗ䁳 z._f3WI^^^vsK/]6{С>;oJuvL&| D݃OwJk}{zs9XySz)W3W/>߯2wn(oJ l+9/wF&J-_9y˔0rޔx@E]Z܏~okuܠ5=b#fyv޹[_-/d|{s$sзA;}uܯ/N|U׽N[Ce3c+{n̙/LCЩ2euK-zUt1sOGkJ/JoɕΕeW[yWuILG9\wW_O9sfYEK/FZ?t~wM2ѷlsҿ>Nm^f|i{ j>AZyp;siO \;ϵ[f~3D9ƕ~{m e;|^fuy+]pe;6+/nd'iNnu`(>qy]j9LO!?,}Bn |zHV`bv9߿VYJ# 98{O\8Rw73"_,i|~6JKӧO!ĬQ+TV\(عqDVr>q?*h]V(8[XaӐvΉaf)~zy5̊]LON $Bteℴ[ң= Vv~ʳuYz*y\eG*{WA+jñI/h<,i[ rɂ G{nyɑ0Vܟ8—ሽWvڃ+]~?عE^$H?E\.q!G+N{sdaDZoBe^J>9/rѻ?,`h=@*';%Y8reM)ev|T,̟\ac]?k91cʆv?$\u\+IDATYMm]Ot'IJw+~_V'6ZEZdaG3ǮD?qEmgq';v4`r)@^OّNNXxFd[dO 6|_֟H8v9`W̮Wg?Eԕ{r[bZDO9 |2Pd0&}¸dL9m3klnmG':N] "(R'ms:\ڈxS9TFt˂+8QrۡdOha.1f9)J_23{f_%s_/ƻy~ANjcJ<*CrRw18^Dk9xc$'&??A9zb1ZWMd$[HݧE7駞ܙslzx7Ÿ0 f'P9y%+dN} ={z`Gʀ`+ءɉItRb~o"ەWnۈrknrCxr'}$_̟nq4ts}sNe2 7ȳdN{ovhqD6dHdt Ԙqn<81_ŸM##Xq gS[|}D=&v9&oʠn1f S_i<5Nzo+$:I\N={;IkN2iKzvRp|{-V.<"OAۉ~/h+Ġ7uo{nb/yj硇C(:Wcz I7q?u;XS_%q 0+DYoi+:Mzwl#>|- _Ьuθnc}Pr{Y@?TOt1.ѷvm'=l:F`Cz*o?'贶b 90T=@g3|qƘcT #];C  hs"Ā=NRe})'QG _|hsu{ApbNoIO_߿ŧA7y#3dl2_"HLߓ2dʐyHCOf۰Ϸ潎hgર-zZE$m( jUvA#FCbqU;kFLMΩr Cv;Lfj۪̏)3&5O#a]007h q 2-z`e2mY6jg|ڤ,f*Gއ#?ՆCi{ !M'Àv ~{O|xcwퟸkOCq?cݜ9|OXw睟?SO݄ sܔ),{zk9UW}.37fg̛o6nܧK>nƌr9y޼Rr<5|ꮾSwuJ>"sK/}@;( C;rK]ԗn(OɲK͜>C:]zg>M q?/M__.tC]z\q1i1ȷ?8ROi#죏~nm$ɓ?i>;\ 庂e}su>w?uH]iw}ѯunԷ|iGux|.̝g?i a5 u s 0_>re Δ!?dнc|?q#X=~u#̹~Ao~r{h%6^|1x#}問8Cʣ`AI<\/#/ӏ8MX'K0ҁ-:y~iiYut0.6̝׋~/yu"=u'>|9}֦!ywA׶(&;9ȫԸN1[cqKBI Y06m fcl֩FctAz%bbe:6që#eї% e;s#φ=Nab:`:`:huH07SӁnFݴlZ*p2LLg0&$b|CnyGmeEDQ+ O'obq!L{9m3yRyKWs罫>/< YZ~=i";yyy ga+ۙ3g Dv}l#/GDQ0 /ka- lEif y fZ0/V~߶03&$DBP2PzEy, lڵOo0kZ-l)7R畑>/WVoyכog KGޜB/XnyeY0 T$(yɗF/Rwwn'dX;L~}ɛ֐wdۦ@ VV|*V U_TlǗGwk´[v^‘7l[Daȩ}*|F1bև╧v064⦷av2Yv u|8<Rf|4ߒ!s^LJ7Ǭ ^+rg6_&Q=kZn{{ WEmgM}V(+&YUӆ6=/| &Õggyv /{1lVc<{q6lWWuu bwm| &*5 ?|pM0"wvr/Q"2?Ey /kaeMĵa|[fg՟>/\ull) < CELl]2X i";ü,eJy.DdGqqUaWQ<}Y;q&C^] N-QIRUrڎMTaX7k=yg6,}q+ai?axO/Bwut؋aH\vNX/?/={|:['7?s\q~M!! !/vؘ/ȷ5a@LqMx޻5Em_7}uWp 煫ܢcm Bw:us"[#arڎ% Ŗyozt:'ߑoxixy*gvm~2&$n\[ #tt`I#q#U66tg0777000:`$M;;100h ttttꀑx78[6gk8Yčmn:`:`:`:MuHv\w^9Zmc:`:`:07@7Ձ:=zfWUyc9/FVihVk(oV>c4|vaYFo~a6xcK{7z1w'\wzfHcc$װsGh_}_nEqٴct욭@t?VnsvcM#q̦N:=8/m="fCi_>Z^|If]a-u{\㞕mWbica||_?~;]-嵨:)C嘅XTXiDb-:ܺi>U>..0_M"{ӟԿSk_+?l'w}>?E~A5m(/{sϭ+-`Gi{,|z9%/MWEi)vH߷SGa@BN`AWM܉'wdkWv$LIcZW/:B2;"'7xhbLJh71C+9&E&(ȗ9e͘1^Uy YjX(SANAEm4i4N/LɄ%ML4>nu!DrJegOX?WWd=z>8f촜FFI򛥓1çע0osa[$0k&'v#nkZvjMeѓAZt ?l-:o?Br~g]=;~ d~]wU` wW׈'̂Gj.6NaZyF[K8>h\ΑD3ܳH4|/Y?>y@ug6p@<}\U^#JY$ms۠qa6;vD6СC 6Ը4ݻwvރEw p=,^ƒGyd;=ټ*a}]`*ey2k⒵گmTv^y) 0w $뇎CH(TOt믿߱ўO;0>ΒIN[~"AяmE6ՓO5Bv:[n{'*m+OW-MzdU^{ʔ)W^^8{UVq/rϫxӔ)Oɖ Ϟ'tOB!G&;>G|XMy;P7.=3~W)I;~v]OQa9ژqCb^y*19m‘.Ȧ ó2E~X]<ټIG2,8F稚/C#3L3~xF/&׿խJ^9K/5\Ο?߭7lذJ<4i'ϵ^ΗŅW=|p7rH/WԆ7xí AuqQo=ĥ0i&0nzrIx&4v!C qcƌotjgӇ~/|e9wYg|>lYl-'6Id zuhd̈́2ꩧ;+ c6mZU/bO^!QC\Bc|뭷l]˦?VlX/¬޲NTYgX6gʬ'?_9jYEyΚ5}K_sV<9+L<QU9_j/4<Ͼb}4Eqω gϞ|5FdGWGuh|; Bω'clq;l|O:ҳ% .G=9ƽ}ܙglP͜~6hy^(u ]w 6dJueaw4O~>ٓ0<رcOO>]6ѦK.wegbʯ'H\,ᔪLW{TnԨQؚscj{L"2Xcu:t'z'&șck`ިG=yce-S4s9ڠرzd.qG˪LQy)"#faYf5;=Sii̖k1bgX5g- Sӡ'W^ye43??eyu{ܷgGe׃#03Em({ ԅ1裏US6ςb~dIԁqIls@w#q#q[Ęx07.elU#qMLLLwӎhJ԰0,LLT07@7#nqKm;.Ӂ607@7#nqm[FF7000:`$M;nI]uZme:`:`:ЦFF7000:`$M;Vm+Q°00XRucUc`:`:`:`:tGw@{陖qLoIENDB`RRD-Simple-1.44/examples/statlogs.pl0000444000076400007640000000211710746154056017224 0ustar nicolawnicolaw#!/usr/bin/perl -w ########################################################### # # $Id: statlogs.pl 965 2007-03-01 19:11:23Z nicolaw $ # statlogs.pl - Example script bundled as part of RRD::Simple # # Copyright 2006 Nicola Worthington # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################ use CGI qw(header); print header(-content_type => 'text/html'); if (opendir(DH,'/var/logs/httpd')) { for (sort grep(/(combined|access|error)/,readdir(DH))) { printf("%s %s %s
\n", $_, (stat("/var/logs/httpd/$_"))[7,9]); } } RRD-Simple-1.44/examples/processes.pl0000444000076400007640000000357410746154056017402 0ustar nicolawnicolaw#!/usr/bin/perl -w ############################################################ # # $Id: processes.pl 965 2007-03-01 19:11:23Z nicolaw $ # processes.pl - Example script bundled as part of RRD::Simple # # Copyright 2006 Nicola Worthington # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################ use strict; use RRD::Simple 1.37; my %update = (); if (-f '/bin/ps' && -x '/bin/ps') { open(PH,'-|','/bin/ps -eo pid,s') || die $!; while (local $_ = ) { if (/^\s*\d+\s+(\w+)\s*$/) { $update{$1}++; } } close(PH) || warn $!; } else { eval "use Proc::ProcessTable"; die "Please install /bin/ps or Proc::ProcessTable\n" if $@; my $p = new Proc::ProcessTable("cache_ttys" => 1 ); for (@{$p->table}) { $update{$_->{state}}++; } } my $rrdfile = 'processes.rrd'; my $rrd = new RRD::Simple; $rrd->create($rrdfile, map { ($_ => 'GAUGE') } sort keys %update ) unless -f $rrdfile; my @sources = sort $rrd->sources($rrdfile); $update{$_} ||= 0 for @sources; $rrd->update($rrdfile, %update); my %legend = (qw(D iowait R run S sleep T stopped W paging X dead Z zombie)); my @types = ('AREA'); for (my $i = 2; $i <= @sources; $i++) { push @types, 'STACK'; } $rrd->graph($rrdfile, vertical_label => 'Processes', sources => \@sources, source_drawtypes => \@types, source_labels => \%legend, extended_legend => 1, ); RRD-Simple-1.44/examples/README0000444000076400007640000000301410746154056015704 0ustar nicolawnicolawApacheAccessLogActivity.pl + Parse Apache access/combined log files, and graph a breakdown of total requests, requests from unique IP addresses, response codes (2xx,3xx,4xx,5xx) and types of files servers (images/HTML/other) ColloquyBotLogParser.pl + An example of post processing of logs to populate RRD files for graphing later graph.pl + Script to graph the data stored by ColloquyBotLogParser.pl graph2.pl, graph2-daily.png + Generate a meaningless but attractive graph network.pl + Graph network device throughput by reading /proc/net/dev meminfo.pl, meminfo-daily.png + Graph memory usage using the Sys::MemInfo module, or by reading /proc/meminfo on Linux if Sys::MemInfo isn't available loadavg.pl + Graph load average using the uptime command df.pl, disk-capacity-daily.png + Graph hard disk capacity using the df command hddtemp.pl, hddtemp-daily.png + Graph hard disk temperatures using the hddtemp command iostat.pl, iostat-hda-daily.png + Graph disk activity using the iostat command vmstat.pl, vmstat-cpu-daily.png + Graph CPU activity using the vmstat command (an example of using the STACK draw type) processes.pl + Graph running processes and what state they're in by using /bin/ps or Proc::ProcessTable mod_perl.pl, example_apache_monitoring.png + Graph various metrics from the Apache /server-status and mod_perl /perl-status scoreboards statlogs.pl + Helper CGI script that can be probed by mod_perl.pl RRD-Simple-1.44/examples/iostat-hda-daily.png0000444000076400007640000004661710746154056020707 0ustar nicolawnicolawPNG  IHDRDrItEXtSoftwareRRDtool, Tobias Oetiker , http://tobi.oetiker.ch\a IDATx}E*/$QPTżkX V]s5 w>]׀"VEDQ(U$uŭNOMOUOϙ;3pxB:UoUutlѢEJ.@iSO=bii__$߾}Kl > > > > >P> .Yh WGGqqYh{u'e!> > >P> . .+p"zK2eUEqN_ KI;c BK@\@pF)@tظq4iںu>}u: Ml%ȵ,X@{j?#\- . nuٳg*UXrVX,r2_mBGr# lϨ _U\9~*Ȯ޽.ktm]׭[i\ Gr\2?xl.}Iڛi}+PR>P:tP ]v$9l}QըQ#=Cԝwޙ6t̤]Եɒ>=eNN@\L2ϵP_|zgԁ^}՜)k;?4hyeԃ>S]3iW&u]$# g<`v\tRug}WO={T/WkV쳏*++SFإ q[hϟ=4V5jP?|S뫃:H]uU)Ƕ5HإnݺW^yCt'mW~߾tKi|%lm6_޽ޫ:u䧃:g}Yd/}D{WUN8A>@aKi 3#yMܟ2uLltmڴQ=zPiZJnZ]s5ZA7˗+ 8}]D7߬Zj}P&8}Q 6LcԻ[mٲE]x) E?Sf0N:tE}gjjuӕ/(/i1ۖt,n=BW9lɓ}yG}z''La`IGX[n,*ϠpqyMܟ8& -@ʰijС* IiTJ|YSHt4IcW[bE?M+Wy/R /.:8pB_Y'|1J vQOիW'}M+5k~EiWhi\P9LS>°2Ks_jB֗`fwsMvp =Ϛ5Kcp"\b޵Qc vӸ? yxTrtPYxXy:vh0`OfSDZc԰ۥ(!Ƀu t#Zs'A4# 5$^:`Ľ@Ɛ0a 8`@f̘1~9vԩbc7|3lD_~⋭ OׇvӠoڴxT̙ciKW'qf>/tzT|lX~>~s=4yhKe!C(,ѣG'`nG'޵kW}_trWeOej (mڴI7BUkdgsp==w1NVKsq$@e͇LyS5kTࢲoQz ǡ8艏- N|4h+FxOGƗ +Ct#9 ]:R>|?ʂ*Ǐ:_:v=cVZ-O<1Iy*TP7pCR>Ѧ aS^=CmtkѢ~+9/*w)aY& 6>xĽ`3]>w!;bN@kR(}s8 >pW6\8) \/2 ;x-'SLJw'.`]tL,?cb\q <J5qDkqzt(w&t(}x`=oqwzCC]d']}23goX xć_tqAcI8]EE|@|`W>~B.;qٍˤ`0'~!> >P>3  'ݸ (~#uOr~~qn!;q j,wռ͋6Wߊm7mŶW@{luI'&Mh'L.ro@ &߿Vl}o.]62omɼ'޼ys5vXEm޼Yկ_AىNܶaBzGֽ3RN.ڶ6vr#MC6t;qځ7kntB\@\@

^h.?tЂK˥Ffk\td\`y}Ɠ ,P{P~Ŭe˖jѢE\o^jJ~̘1iy,]TM6cǎi_mYfZn!Q셞 }hƸɥl.-.[Ĺ6ґmM,6zj>C,6~l'f}kf8BW^Z^ݺuS#G:(.\Gl=l)`/mmYZ=K[h;N.ٖh{gƬAVowOWOqi׆)SRu\i.-zpi>nN}JٶY;ڈKWʶu}W>F\:mlm < Û\AX{6mL]ZwʃPwԨQ:Ĉ{)uL:NN :ىN4cA6KBWQ\qȶ¯8nʕA/o$ = {;yshҷk[prmĥ^kĉV=PuW>4W(q)!5; = %rɥ+5[{s¥nl'pڬYv0ǿ+` LB۶lyYF:s]ͥ=mm'Nl~fkے8O? %=SՄ v 0D &D_w4?tЂK˥Ffk\td\WZfΜf̘C,w6&?{ض:' . %^P@<чf۟\:r鲑Yh5*Jk#.6WXP۲eSHxMoUY$ = {;yshҷk[prmĥM:0 xꔐ셞 S9ҕBܹ~Q 869߉ۄZ{jHp{'CD1nr K˥Ff(qtd;6+f0y$ = {;yshҷk[prmĥ ÖLʦx_kefhg8},_1)V<q)!5; = %rɥ+5[{s¥v{Xƛ MB{8q>ieO.|K˥Ff6ʘ4pmĥ#=q|mo~M4cA6KBWQ\qȶol={:餓OBi꥗^jw' . IГw7&}6ṅYh Hj)F\:hli͛cǪMj7oެׯS`/d( ڗ'do 3WO٧HIA|f͚MNuJ5wO `/d( C3O.dsit,ߚ}%εl+ iމmV?ߺu:t:}@' s(d( n]ͥI߮Mxn!do-Zʵ1 ,PbY TʕU˖-բEʽ6'.c F}[yO޿Ǹ\ldoIF\:mml'xl͚5%x>5WO.r鲑Yh;k#.Ēg dywO `/d( C3O.dsit,ߚ}%εl[xq6RuֻĎ\@\@ % . :%p{'CԾD?t&~k5w._tԟ0߮: UbE" ɏ:84[s ϕnrж-?tP!doڈKGZJ͜9SeW^m$g ֭OTSx.L.{jHp{'CD1nr K˥Ff(qtd\شiԖ-[4p#D;c'&{TVꡀx>'BON\:ڄBf6"\qhsSN/Jb#{Q.S ߦW&蚮h/yO޿WQJI'nџzO|ʕ(qnKN|ӦMV6!';qىSs0x9YCi2A8](Lu2ehZB\wl]馛~η<qqa!w Y5^Ƶؖ,!.6e˖Ky䑪}'Tx,(4. fx׶b[ׇt \aޔ)Sƛ &^zIag~믿^-_ĥ\񄛦 @7ܯ ={T&MRwq:c2jŋնmTT֭9r1|GK ,v0˸ے>ĥ\dAlPC QxwdL7ģmݺu}chKE| .b<qqB`jk[q-YC\:miz}' >y)KJR֪UKݻ)Vz>wգ|qqB`jk[q-YC\:-HÜs E2!: ;0}V>luE}}骏w=l`ˏǥA'.g-ɵO6=IAl]QpmĥƶyRl?m!'aiYA>R\pSv%f锶… S ѵkW5j(?bս{:,⋝<Zt8B]vaVqmǕɥ\Z.]62Ŷ= bbO6qyM8ʛ}~ 'O'w"{o '68ڵSx:OG}:>ctod W, ޺\ے>ĥ\$Wď:emyj@y ,v0˸ے>ĥ\Ж'VA{'Ǐ7n>R/C8Ma!w Y5^Ƶؖ,!.63cp**U}GǑ&Ti" ]@<̪2mŶdq ¹y0Mr2q܏q)4. fx׶b[ׇt \admR DrxUe\ m\A.hsiw0Mr2މ!^1ܹ~ u3ЩTC|O1R/X炉-XtÕ :|GNald +ʔo^]m?6Wl]Wڶm[ N\vⴚ 1h9Oo5me'.% p}K Q믿h ޴iemy6Ş' . NAXaVqm+ .% p}K%`]Ba8 W|_~e%Cspȩڄdnll+ ?ۊߊmyKGsB@|ƍj̙j>P|򉏷 6co+f#d^qىN&;ì/V@\lKrA+ܛ5kV7}+o6<V BqqB`jk[q-4 IDATYC\:myc` ^ <qqB`jk[q-YC\:my`)# ީSro٘\i" ]@<̪2mŶdq ¡)gx ׯ_6-O1\I=VrA]a33q`Xreu-lT<Ӳ8B ZΟ[k[ىm\A.hsUAt ޸qcKT ,v0˸ے>ĥ\ 8x!3k6gJ)8Ma!w Y5^Ƶؖ,!.6W87r'+ŋ8Ma!w~W_ame\m+/.2Y64kۦ$5133މZb' . 4h6ay\#3Ff&r̺\4q. t3%3 AKLïM4ImݺUwNAYf9mM2rxzrm+ .% p}t.tM¨ {O~?_F@\@eXȝ$ì/V@\lKPuR)UHg ڵӌ!]bHBmK'.cG=qwW&H&(:$\xzۆۈJ>{Q11c_bj۫fȋ*NxwO4-B OS\N<;>ӳNm8=*Ff .rO;1q K[5ec[q=BqXXn;t5@;6$FWRr ?3 +?`w (:peFs (6qKk;>lGS v2]~E(W&:qimtah6Z*w/N ÿ~EHQݟY~oNrc!t#; f`5`t \Q\В~fȥ. :qϽWUڽS'?dA.oC2Y2`F[ik%ugF՗dֶֆ|aZ ̇>lG!3(!%SNMR?sʻ18 wFON媃2̠ [5`]rL.}{vgnrD\A{7&Ս G0K xMNm|Ϙj=>1Ґ/+{LaRm2*7(mp(*NiӦj̙Za^n]կ_ro@ԆfR7|>AixXSTIj$/BR}$ytf`#L\2Bq]n0M& 6:;Ei-_F?;VDK!0IzQ"&[+^:.~~!BT/>qϏVl`OqTl'P"*X ճ [5ŧGv']QhnRu?qHP`A_/|狴(;sZ>DCtONʇO6~Lf{A Qգ48Ҹoz-Lɋ׭NB;=;(?_Z|wiVl4V2 Mޔ7HB9e_wztzud? u 'luSO"zίE0`z~S_Ouݿ뮫l`]޿c>Ư8<:~Q_hzT?hHW*;*׭.%tkDCubGf}Zӵa?nu;i]֮͛ա)qE!? nOOugmקD;@ OjYcQ?ΛG,SBO|įj'%ڇ|Xᢿ;Yߌٱ&-(dN@}At-D; +8VaWy7  >ǡC8 v2JZjaL潘$PB8Lx"!"ڱJ_T^Әܧ.I<͐x! X2i>t\SY."/GT> .;?8BqO?o %6}O["7hvTFyHn7ϣqJuMZIϭ{Nlj8Rzf~O_ԟ~$ű__lL}C}7~l~}쏇пL=z^k8ly-WoV漢+l&ǯNbK/{֜#& \xLzwۤAe#X{-'x5زEwUnLx=1ه{QKVOu 1vFea}W@_N آ>zт+Ncԑmgwz;Qz7 ~|$o:~D-:hjbױޘ98Ӱx9j_O^yB-h][EUCʴO᪱,0;|^\z1jj踮.'"v,jdȞ<ԳLhoːqw_g?ykS'Ԡq]ƄlC_ 4FZR%Qգ~15|ogԍ~>]}Sѣu`n y9s |eccO>fХez2OK1 OWO/9%e굦񱍹1^1j};10pjJ_Ƣu؅ [R{$0?vt%öSɧ~[ ppnݺ#Gj].P3Փ:(ۀ5˃q8=>+SZEtoO8*0 g+kPty\\:å̈́8КtqThT%,͕It3$&`yXdqiw.>]|2M:,dL`Ƣy擡m?ETȗtuLhM;? .Xcg,\G\sѕ$t^zF˴~ V WX(2P睫h/\U A;DҰJF=g#+[줞&HbwHcsh|W;b|c砻꩏9@f~x|5=7)vؕ5vxLzyj| ,{֓twdqa]V#V9@7\Ί0paGqƋezoc-o<pC)L.Vإ`0@?qA Փ<{_w+) cc;n<.CL®t]ʳ[aō7x](3 ?t?ʴ>dp m G}pD]2l{`຦j~jaj-S„ӡ'; ᥕNPsj7Qw]vd۩La8VXM--2wK Kv_-aC℣td9|?)e_v씖mԇ $-b e KWmGG]'B>.+~6ھe{N"g<|nuS7Ntpa᫠2}jf|Ѿ86xO yS;٫31f0BT2m N`|ymx‡{u["W} q21 :8I;,;ͫw:P<JZ?>M3ecjaz~Xa#>V-.bc :a'<SBa!K] kW+KM; !3h[z(hG[/m ֜dm6p+`^ ^wһ ІѰv8<JW/uR:[~5Ur KgGlkwؖ3w18B N!< e% ]vUF1b r?">љ!<`(iȥA?.&LG2tٴ2Ŷ_ĶbbLzI8So׮~:CIO) Ou0 zr_vDTȗ䓯6@#s#oq91Z0_ҩM ,C}@@\@<$BP$[JKj{?{m6ꫪs~:j~O7h@I|{1uG'H* @{|;$3޵^{qnT7,tݕw)Bv3T:IL8lwЕoQya[לWjJ5o\7$̥. x+&GD0۷|W/^ zZ}+.zW>: 8zwpww͎<^_~GOtqGz6;8z>@!eCW>GݑF@@ŢO>Y}j~+֭[OX]qڎ-tЕo!yx% ^B };n)e˖jܹ`rd ±z:u|Y$I{|+th<3lڴI}gzNwW]'ζ^x1gϞ¢~F-}G4P=mvc\@sbN8s=GMF}SiӦcR+otOsΉtOcǎz{' ӨO߫wByf 6ZJ|3W}W>:3㹲~p ωwq Φ<ˆ|+thkg3kNT~:'v?<ˆ|0KřÝY#|@@\@ > >@xvbs;Je.> > > >P> ^W+G]v>qqY cuc*s_p@tADa!mD+VvY۰n:u[dծQ(> >P>;m B ^rۙ"O&FG7>wѢE ||t [^0}tSLQ7z葳8;0jժ$۷4h -ܢb;Mh֬.XB \Bۉ+T}Q @2_|{/+ɨGq0a뮻  /!O95c w ]e.ۙH|@|@|t}h@rj֭I 6vF6vpcƌ_A>v`xTכ?~A|Ȑ!5"ç,l⃸VPVvWwwEⶣ5k֨:aSZz5Ι3իWCϳ:KT]tQ/bҧ>m@l7Vf;e(](jc<jZ'Nk׮UعQ鍊nA# > >P>3[nѾ'K]ڙK ҝ r??gq\Į7 OOJ{丢Lە@xv@e"> > > > . .+p8Y \|@|@|@|@@\@\V%%qa?$n:uldJi3Sy+_GD\裏O?P駟AYh-[F.I6EoCp'ڸqc ]^RAa<|AհaCL/"'}:L^X>}W:J۬]vt<˗/W'p_Ӆf.CJh>4vX͛ʂu)%L,GTW]uOpdGNza#|_]ܰaɓ*I&>3 0?/Me7oֻky=3J|ܹLӅ!l۶mZjjɒ%w( . 3gr96 . 1aRC| K<ݍI8G_8p袋|C캈OXHS!ILA46]Lx7!oR8FDZW^CuxuyFѭXBگ/2ڵk7xz'SN8uVݮ]Iv0oSf}%cUVɇa!G TߔCyXTPcbCy:yătA^ժU^6C~0E MXe.]8@W5_p~{ OD}ݑz-թS''LGWX #^IW裏1k:yal=P3x`m ĽNNhnQ} l2.o0xW_yOn0f=/]4 |/^  iYfx6ަ5kǏ$0yTt 0E?T5kӔ t:QIlx_^~=tŽ 2]M7*ƙ6m> i,QE#<˃8?~.[J{ǧ kwyqiPĉG=~X6v̔'89sj[8rH~Ҿ6r) Ɏ' 9KTcI=09 7o`Sp`7j߭[7:1qlpoAO<؆pL2A3ܳҥOrWMO<pWyp,K4aP5+i\2?pk~PLɄaggcцwx83 Ee.T?1bDR̥ۨ{+Xh9}@@\TKLL%%qMDb e.> > > >P> ^:ݲHJKd%X- \|@|@|@|D}@@D;nw]uJe%> > >qqYhJ4[-vWHqJH؄A > > > >P >+%EGL>M@ܘIENDB`RRD-Simple-1.44/examples/vmstat.pl0000444000076400007640000000376710746154056016716 0ustar nicolawnicolaw#!/usr/bin/perl -w ############################################################ # # $Id: vmstat.pl 965 2007-03-01 19:11:23Z nicolaw $ # vmstat.pl - Example script bundled as part of RRD::Simple # # Copyright 2005,2006 Nicola Worthington # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################ use strict; use RRD::Simple 1.37; BEGIN { warn "This may only run on Linux 2.6 kernel systems" unless `uname -s` =~ /Linux/i && `uname -r` =~ /^2\.6\./; } my $cmd = '/usr/bin/vmstat 2 3'; my $rrd = new RRD::Simple; my @keys = (); my %update = (); open(PH,'-|',$cmd) || die "Unable to open file handle PH for command '$cmd': $!"; while (local $_ = ) { next if /---/; s/^\s+|\s+$//g; if (/\d+/ && @keys) { @update{@keys} = split(/\s+/,$_); } else { @keys = split(/\s+/,$_); } } close(PH) || die "Unable to close file handle PH for command '$cmd': $!"; my @cpukeys = splice(@keys,-4,4); my %labels = (wa => 'IO wait', id => 'Idle', sy => 'System', us => 'User'); my $rrdfile = "vmstat-cpu.rrd"; $rrd->create($rrdfile, map { ($_ => 'GAUGE') } @cpukeys ) unless -f $rrdfile; $rrd->update($rrdfile, map {( $_ => $update{$_} )} @cpukeys ); $rrd->graph($rrdfile, sources => [ qw(sy us wa id) ], source_drawtypes => [ qw(AREA STACK STACK STACK) ], source_colors => [ qw(ff0000 00ff00 0000ff ffffff) ], vertical_label => '% percent', source_labels => \%labels, extended_legend => 1, upper_limit => 100, lower_limit => 0, rigid => "", ); RRD-Simple-1.44/examples/mod_perl.pl0000444000076400007640000002024210746154056017164 0ustar nicolawnicolaw#!/usr/bin/perl -w ############################################################ # # $Id: mod_perl.pl 965 2007-03-01 19:11:23Z nicolaw $ # mod_perl.pl - Example script bundled as part of RRD::Simple # # Copyright 2006 Nicola Worthington # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################ umask(0022); use 5.8.3; use strict; use LWP::UserAgent; use RRD::Simple "1.34"; use RRDs; use File::Spec; use Socket; use File::Spec::Functions qw(catdir); use Time::HiRes qw(); use constant TIMEOUT => 5; use constant RRDDIR => '/var/tmp'; use constant IMGDIR => '/var/tmp'; use constant HOSTS => qw( mod_perl1.london.company.com mod_perl2.london.company.com mod_perl3.london.company.com mod_perl1.paris.company.com mod_perl2.paris.company.com ); use vars qw($VERSION $DEBUG $VERBOSE); $VERSION = '0.02' || sprintf('%d', q$Revision: 965 $ =~ /(\d+)/g); $DEBUG = $ENV{DEBUG} ? 1 : 0; $VERBOSE = $ENV{VERBOSE} ? 1 : 0; $| = 1; $RRD::Simple::DEFAULT_DSTYPE = 'GAUGE'; our $ua = user_agent(); our $rrd = new RRD::Simple; for my $host (sort loc_server HOSTS) { my $logs = {}; TRACE("Processing $host ..."); my $start_time = Time::HiRes::time(); my $msg = "Processing $host"; $VERBOSE && printf('%s %s ', $msg, '.' x (79 - length($msg) - 10)); my ($status,$scoreboard) = parse_apache_status($ua, "http://$host:80/server-status?auto"); my ($modules) = parse_perl_status($ua, "http://$host:80/perl-status?inc"); $logs = parse_statlogs($ua, "http://$host:80/perl/statlogs.pl") unless keys(%{$logs}); my %rrdfile = ( status => catdir(RRDDIR,"$host-status.rrd"), scoreboard => catdir(RRDDIR,"$host-scoreboard.rrd"), modules => catdir(RRDDIR,"$host-modules.rrd"), logs => catdir(RRDDIR,"$host-logs.rrd"), ); if (keys %{$status}) { $status->{ReqPerSec} = $status->{TotalAccesses}; $status->{KBPerSec} = $status->{TotalkBytes}; if (!-f $rrdfile{status}) { my %def = %{$status}; for (keys %def) { $def{$_} = $_ =~ /^ReqPerSec|KBPerSec$/i ? 'DERIVE' : 'GAUGE'; } eval { $rrd->create($rrdfile{status}, %def); RRDs::tune($rrdfile{status},'-i','ReqPerSec:0','-d','ReqPerSec:DERIVE'); RRDs::tune($rrdfile{status},'-i','KBPerSec:0','-d','KBPerSec:DERIVE'); }; warn $@ if $@; } eval { $rrd->update($rrdfile{status}, %{$status}); }; warn $@ if $@; generate_graphs($rrdfile{status},$host) unless $@; } if (keys %{$scoreboard}) { eval { $rrd->update($rrdfile{scoreboard}, %{$scoreboard}); }; warn $@ if $@; generate_graphs($rrdfile{scoreboard},$host) unless $@; } if (keys %{$logs}) { if (!-f $rrdfile{logs}) { eval { $rrd->create($rrdfile{logs}, map {($_=>'DERIVE')} keys %{$logs})); RRDs::tune($rrdfile{logs},'-i',"$_:0") for $rrd->sources($rrdfile{logs}); }; warn $@ if $@; } eval { $rrd->update($rrdfile{logs}, map {($_=>$logs->{$_})} keys %{$logs})); }; warn $@ if $@; generate_graphs($rrdfile{logs},$host) unless $@; } if (keys %{$modules}) { eval { $rrd->update($rrdfile{modules}, %{$modules}); }; warn $@ if $@; generate_graphs($rrdfile{modules},$host) unless $@; } $VERBOSE && printf("[%6.2f]\n", Time::HiRes::time() - $start_time); } exit; ##################################### # Subs init sub loc_server { (split(/\./,$a))[1] cmp (split(/\./,$b))[1] || ($a =~ /^mod_perl(\d+)/)[0] <=> ($b =~ /^mod_perl(\d+)/)[0] } sub generate_graphs { my ($rrdfile,$host) = @_; eval { if ($rrdfile =~ /status/) { $rrd->graph($rrdfile, basename => "$host-status-total", destination => IMGDIR, title => "$host Total x", vertical_label => 'Total x', sources => [ grep(/Total|Uptime/i,$rrd->sources($rrdfile)) ], line_thickness => 2, ); $rrd->graph($rrdfile, basename => "$host-status-bytes2", destination => IMGDIR, title => "$host x/Sec", vertical_label => 'x/Sec', sources => [ grep(/KBPerSec|ReqPerSec/i,$rrd->sources($rrdfile)) ], line_thickness => 2, ); $rrd->graph($rrdfile, basename => "$host-status-bytes", destination => IMGDIR, title => "$host Bytes/x", vertical_label => 'Bytes/x', sources => [ grep(/BytesPerSec|BytesPerReq/i,$rrd->sources($rrdfile)) ], line_thickness => 2, ); $rrd->graph($rrdfile, basename => "$host-status-servers", destination => IMGDIR, title => "$host Servers", vertical_label => 'Children + Load', sources => [ grep(/Servers|CPULoad/i,$rrd->sources($rrdfile)) ], line_thickness => 2, ); } elsif ($rrdfile =~ /scoreboard/) { $rrd->graph($rrdfile, destination => IMGDIR, title => "$host Scoreboard", line_thickness => 2, vertical_label => 'Apache Children', source_colors => [ qw( FF0000 00FF00 0000FF FFFF00 00FFFF FF00FF 000000 AA0000 00AA00 0000AA AAAA00 00AAAA AA00AA AAAAAA 550000 005500 000055 555500 005555 550055 555555 ) ], ); } elsif ($rrdfile =~ /modules/) { $rrd->graph($rrdfile, basename => "$host-modules", destination => IMGDIR, vertical_label => 'Resident Modules', title => "$host Modules", line_thickness => 2, ); } elsif ($rrdfile =~ /logs/) { $rrd->graph($rrdfile, basename => "$host-logs", destination => IMGDIR, title => "$host Logging/Sec", line_thickness => 2, vertical_label => 'bytes/sec', sources => [ sort($rrd->sources($rrdfile)) ], ); } }; warn $@ if $@; } sub parse_statlogs { my ($ua,$url) = @_; my %logs = (); my $response = $ua->get($url); if ($response->is_success) { for (split(/\n+|\r+/,$response->content)) { my ($file,$size,$modified) = split(/\s+/,$_); $logs{$file} = $size; } } DUMP('parse_statlogs(): \%logs',\%logs); return \%logs; } sub parse_perl_status { my ($ua,$url) = @_; my %modules = map {($_=>0)} qw(usr_lib other); my $response = $ua->get($url); if ($response->is_success) { for (split(/\n+|\r+/,$response->content)) { if (my ($module,$file) = $_ =~ m,^(.+?)\s*$,) { local $_ = $file; if (m,^/usr/,) { $modules{usr_lib}++; } else { $modules{other}++; } } } } DUMP('parse_perl_status(): \%modules',\%modules); return \%modules; } sub parse_apache_status { my ($ua,$url) = @_; my %scoreboard = (); my %status = (); my %keys = (W => 'Write', G => 'GraceClose', D => 'DNS', S => 'Starting', L => 'Logging', R => 'Read', K => 'Keepalive', C => 'Closing', I => 'Idle', '_' => 'Waiting'); my $response = $ua->get($url); if ($response->is_success) { for (split(/\n+|\r+/,$response->content)) { my ($k,$v) = $_ =~ /^\s*(.+?):\s+(.+?)\s*$/; $k =~ s/\s+//g; #$k = lc($k); if ($k eq 'Scoreboard') { my %x; $x{$_}++ for split(//,$v); %scoreboard = ( map { ($keys{$_}, $x{$_}) } keys %keys ); } else { $status{$k} = $v; } } } else { TRACE("parse_apache_status(): failed to get $url; ".$response->status_line); } DUMP('parse_apache_status(): \%scoreboard',\%scoreboard); DUMP('parse_apache_status(): \%status',\%status); return (\%status,\%scoreboard); } sub user_agent { my $ua = LWP::UserAgent->new( agent => "RRD::Simple example $0 $VERSION", timeout => TIMEOUT, ); $ua->env_proxy; $ua->max_size(1024*250); return $ua; } sub ip2host { my $ip = shift; my @numbers = split(/\./, $ip); my $ip_number = pack("C4", @numbers); my ($host) = (gethostbyaddr($ip_number, 2))[0]; if (defined $host && $host) { return $host; } else { return $ip; } } sub TRACE { return unless $DEBUG; warn(shift()); } sub DUMP { return unless $DEBUG; eval { require Data::Dumper; warn(shift().': '.Data::Dumper::Dumper(shift())); } } 1; __END__ RRD-Simple-1.44/examples/rrd-server/0000777000076400007640000000000010746154056017127 5ustar nicolawnicolawRRD-Simple-1.44/examples/rrd-server/bin/0000777000076400007640000000000010746154056017677 5ustar nicolawnicolawRRD-Simple-1.44/examples/rrd-server/bin/rrd-client-switches.pl0000444000076400007640000000354110746154056024123 0ustar nicolawnicolaw#!/usr/bin/perl -w ############################################################ # # $Id$ # rrd-client-switches.pl - SNMP switch traffic data gathering script for rrd-server.pl # # Copyright 2007, 2008 Nicola Worthington # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################ # vim:ts=4:sw=4:tw=78 use strict; use constant RRD_CMD => '/home/rrd/bin/rrd-server.pl -u %s'; use constant SNMP_CMDS => ( '/usr/bin/snmpwalk -c public -v 2c %s IF-MIB::ifInOctets', '/usr/bin/snmpwalk -c public -v 2c %s IF-MIB::ifOutOctets' ); use constant HOSTS => qw( switch1.company.com switch2.company.com switch3.company.com switch4.company.com ); for my $host (HOSTS) { my %update; my $time = time; for my $cmd (SNMP_CMDS) { $cmd = sprintf($cmd,$host); print "$cmd\n"; for (qx($cmd)) { if (my ($key,$port,$value) = $_ =~ /(if(?:In|Out)Octets)\.(\d+)\s*=\s*(?:Counter32:\s*)?(\d+)/i) { $update{"$time.switch.traffic.$key"} += $value; $update{"$time.switch.traffic.port$port.$key"} = $value; } } } my $str; $str .= "$_ $update{$_}\n" for sort keys %update; my $cmd = sprintf(RRD_CMD,$host); print "$cmd\n"; open(PH,'|-',$cmd) || die "Unable to open file handle PH for command '$cmd': $!"; print PH $str; close(PH) || die "Unable to close file hadle PH for command '$cmd': $!"; } RRD-Simple-1.44/examples/rrd-server/bin/rrd-client-infrant.pl0000444000076400007640000000710110746154056023727 0ustar nicolawnicolaw#!/usr/bin/perl -w ############################################################ # # $Id$ # rrd-client-infrant.pl - Infrant ReadyNAS NV+ data gathering script for rrd-server.pl # # Copyright 2007, 2008 Nicola Worthington # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################ # vim:ts=4:sw=4:tw=78 use 5.6.1; use strict; use warnings; use LWP::UserAgent qw(); use HTML::TokeParser qw(); use HTTP::Request::Common qw(); use constant USER => 'admin'; use constant PASS => 'password'; use constant REALM => 'Control Panel'; use constant NETLOC => '192.168.0.2:443'; use constant URL => 'https://'.NETLOC.'/admin/index.cgi?button=Current&MODIFIED=0&CURRENTPAGE=Status&CURRENTTAB=health&DEBUGLEVEL=0&command=Refresh&MODE=Advanced'; use constant RRDURL => 'http://rrd.me.uk/cgi-bin/rrd-server.cgi'; my $ua = LWP::UserAgent->new; $ua->timeout(10); $ua->credentials(NETLOC, REALM, USER, PASS); my $response = $ua->get(URL); die $response->status_line unless $response->is_success; my $p = HTML::TokeParser->new(\$response->content); my %update = (); my $time = time; while (my $token = $p->get_tag('tr')) { my $text = $p->get_trimmed_text('/tr'); $text =~ s/[^a-zA-Z0-9\.\-\_\%\/\\]/ /g; if ($text =~ /\b((Disk|Fan|Temp|UPS)(?:\s+([0-9]+)\b)?.+)/) { my ($type,$num) = ($2,$3); local $_ = $1; if ($type eq 'Disk' || $type eq 'Temp') { if (/\s(([0-9\.]+)\s*C)\s/) { $update{"hdd.temp.c"}->{"${type}_${num}_C"} = $2; } if (/\s(([0-9\.]+)\s*F)\s/) { $update{"hdd.temp.f"}->{"${type}_${num}_F"} = $2; } } if ($type eq 'Fan' && /\s(([0-9\.]+)\s*RPM)\s/) { $update{"misc.fan.rpm"}->{"${type}_${num}_RPM"} = $2; } } } while (my ($graph,$ref) = each %update) { my $data = ''; while (my ($key,$value) = each %{$ref}) { $data .= "$time.$graph.$key $value\n" } update($data); } exit; sub update { my $data = shift; my $ua = LWP::UserAgent->new(agent => $0); my $resp = $ua->request(HTTP::Request::Common::POST(RRDURL, Content_Type => 'text/plain', Content => $data )); if ($resp->is_success) { printf("%s\n",$resp->content); } else { warn 'Posting Error: '.$resp->status_line; } return $resp->is_success; } __END__ nicolaw@eowyn:~$ wget -q -O - --no-check-certificate --http-user=admin --http-password=password "https://192.168.0.2/admin/index.cgi?button=Current&MODIFIED=0&CURRENTPAGE=Status&CURRENTTAB=health&DEBUGLEVEL=0&command=Refresh&MODE=Advanced" | html2text -width 300 | egrep -io " (Disk|Fan|Temp|UPS) [0-9] .*" Disk 1 Seagate ST3500630AS 465 GB, 40C / 104F, Write-cache ON, SMART+ OK Disk 2 Seagate ST3500630AS 465 GB, 41C / 105F, Write-cache ON, SMART+ OK Disk 3 Seagate ST3500630AS 465 GB, 41C / 105F, Write-cache ON, SMART+ OK Disk 4 Seagate ST3500630AS 465 GB, 39C / 102F, Write-cache ON, SMART+ OK Fan 1 1744 RPM [Unknown INPUT type] OK Temp 1 34.0C / 93F [Normal 0-60C / 32-140F] OK UPS 1 Not present OK nicolaw@eowyn:~$ RRD-Simple-1.44/examples/rrd-server/bin/spider.sh0000444000076400007640000000021510746154056021511 0ustar nicolawnicolaw#!/bin/bash mkdir -p /tmp/rrd-spider \ && cd /tmp/rrd-spider \ && wget -r -np -nd -nH -nv -A cgi http://rrd.me.uk/cgi-bin/rrd-browse.cgi RRD-Simple-1.44/examples/rrd-server/bin/rrd-server.pl0000444000076400007640000003601310746154056022324 0ustar nicolawnicolaw#!/bin/env perl ############################################################ # # $Id: rrd-server.pl 1101 2008-01-24 18:07:32Z nicolaw $ # rrd-server.pl - Data gathering script for RRD::Simple # # Copyright 2006, 2007, 2008 Nicola Worthington # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################ # vim:ts=4:sw=4:tw=78 BEGIN { # User defined constants use constant BASEDIR => '/home/nicolaw/webroot/www/rrd.me.uk'; use constant THEME => ('BACK#F5F5FF','SHADEA#C8C8FF','SHADEB#9696BE', 'ARROW#61B51B','GRID#404852','MGRID#67C6DE'); } BEGIN { # Ensure we can find RRDs.so for RRDs.pm eval "use RRDs"; if ($@ && !defined $ENV{LD_LIBRARY_PATH}) { $ENV{LD_LIBRARY_PATH} = BASEDIR.'/lib'; exec($0,@ARGV); } } use 5.004; use strict; use warnings; use lib qw(../lib); use RRD::Simple 1.41; use RRDs; use Memoize; use Getopt::Std qw(); use File::Basename qw(basename); use File::Path qw(); use Config::General qw(); use File::Spec::Functions qw(catfile catdir); use vars qw($VERSION); $VERSION = '1.43' || sprintf('%d', q$Revision: 1101 $ =~ /(\d+)/g); # Get command line options my %opt = (); $Getopt::Std::STANDARD_HELP_VERSION = 1; $Getopt::Std::STANDARD_HELP_VERSION = 1; Getopt::Std::getopts('u:G:T:gthvVf?', \%opt); $opt{g} ||= $opt{G}; $opt{t} ||= $opt{T}; # Display help or version (VERSION_MESSAGE() && exit) if defined $opt{v}; (HELP_MESSAGE() && exit) if defined $opt{h} || defined $opt{'?'} || !(defined $opt{u} || defined $opt{g} || defined $opt{t}); # cd to the righr location and define directories chdir BASEDIR || die sprintf("Unable to chdir to '%s': %s", BASEDIR, $!); my %dir = map { ( $_ => BASEDIR."/$_" ) } qw(bin data etc graphs cgi-bin thumbnails); # Create an RRD::Simple object my $rrd = RRD::Simple->new(rrdtool => "$dir{bin}/rrdtool"); # Cache results from read_create_data() memoize('read_create_data'); memoize('read_graph_data'); memoize('basename'); memoize('graph_def'); # Update the RRD if we've been asked to my $hostname = defined $opt{u} ? update_rrd($rrd,\%dir,$opt{u}) : undef; # Generate some graphs my @hosts; for my $host (($hostname, $opt{G}, $opt{T})) { next unless defined $host; for (split(/\s*[,:]\s*/,$host)) { push(@hosts, $_) if defined($_) && length($_); } } @hosts = list_dir($dir{data}) unless @hosts; for my $hostname (@hosts) { create_thumbnails($rrd,\%dir,$hostname) if defined $opt{t}; create_graphs($rrd,\%dir,$hostname) if defined $opt{g}; } exit; sub create_graphs { my ($rrd,$dir,$hostname,@options) = @_; my ($caller) = ((caller(1))[3] || '') =~ /.*::(.+)$/; my $thumbnails = defined $caller && $caller eq 'create_thumbnails' ? 1 : 0; my $destdir = $thumbnails ? $dir->{thumbnails} : $dir->{graphs}; my @colour_theme = (color => [ THEME ]); my $gdefs = read_graph_data("$dir->{etc}/graph.defs"); my @hosts = defined $hostname ? ($hostname) : grep { -d catdir($dir->{data}, $_) } list_dir("$dir->{data}"); # For each hostname for my $hostname (sort @hosts) { # Create the graph directory for this hostname my $destination = "$destdir/$hostname"; File::Path::mkpath($destination) unless -d $destination; # For each RRD for my $file (grep { $_ =~ /\.rrd$/i && !-d catfile($dir->{data},$hostname,$_) } list_dir(catdir($dir->{data},$hostname)) ) { # next unless $file =~ /cpu_utilisation/; my $rrdfile = catfile($dir->{data},$hostname,$file); my $graph = basename($file,'.rrd'); my $gdef = graph_def($gdefs,$graph); # Make sure we parse these raw commands with care my @raw_cmd_list = qw(DEF CDEF VDEF TEXTALIGN AREA STACK LINE\d* HRULE\d* VRULE\d* TICK SHIFT GPRINT PRINT COMMENT); my $raw_cmd_regex = '('.join('|',@raw_cmd_list).')'; # my $raw_cmd_regex = qr/^(?:[VC]?DEF|G?PRINT|COMMENT|[HV]RULE\d*|LINE\d*|AREA|TICK|SHIFT|STACK|TEXTALIGN)$/i; my @raw_commands; my @def_sources; my @def_sources_draw; # Allow users to put raw commands in the graph.defs file for my $raw_cmd (@raw_cmd_list) { for my $cmd (grep(/^$raw_cmd$/i, keys %{$gdef})) { my $values = $gdef->{$cmd}; $values = [($values)] unless ref($values); for my $v (@{$values}) { push @raw_commands, (sprintf('%s:%s', uc($cmd), $v) => ''); if ($cmd =~ /^[CV]?DEF$/i && $v =~ /^([a-z0-9\_\-]{1,30})=/) { push @def_sources, $1; } elsif ($cmd =~ /^(?:LINE\d*|AREA|G?PRINT|TICK|STACK)$/i && $v =~ /^([a-z0-9\_\-]{1,30})[#:]/) { push @def_sources_draw, $1; } } } } # Wrap the RRD::Simple calls in an eval() block just in case # the explode in a big nasty smelly heap! eval { # Anything that doesn't start with ^source(?:s|_) should just # be pushed on to the RRD::Simple->graph option stack (So this # would NOT include the "sources" option). my @graph_opts = map { ($_ => $gdef->{$_}) } grep(!/^source(s|_)/ && !/^$raw_cmd_regex$/i, keys %{$gdef}); # Anything that starts with ^source_ should be split up and passed # as a hash reference in to the RRD::Simple->graph option stack # (This would NOT include the "sources" option). push @graph_opts, map { # If we see a value from a key/value pair that looks # like it might be quoted and comma seperated, # "like this", 'then we should','split especially' if ($gdef->{$_} =~ /["']\s*,\s*["']/) { ($_ => [ split(/\s*["']\s*,\s*["']\s*/,$gdef->{$_}) ]) # Otherwise just split on whitespace like the old # version of rrd-server.pl used to do. } else { ($_ => [ split(/\s+/,$gdef->{$_}) ]) } } grep(/^source_/,keys %{$gdef}); # By default we want to tell RRDtool to be lazy and only generate # graphs when it's actually necessary. If we have the -f for force # flag then we won't let RRDtool be economical. push @graph_opts, ('lazy','') unless exists $opt{f}; # Only draw the sources we've been told to, and only # those that actually exist in the RRD file my @rrd_sources = $rrd->sources($rrdfile); if (defined $gdef->{sources}) { my @sources; for my $ds (split(/(?:\s+|\s*,\s*)/,$gdef->{sources})) { push @sources, $ds if grep(/^$ds$/,@rrd_sources); } push @graph_opts, ('sources',\@sources); } elsif (!@def_sources && !@def_sources_draw) { push @graph_opts, ('sources', [ sort @rrd_sources ]); } else { push @graph_opts, ('sources', undef); } printf "Generating %s/%s/%s ...\n", $hostname, ($thumbnails ? 'thumbnails' : 'graphs'), $graph if $opt{V}; # Generate the graph and capture the results to # write the text file output in the same directory my @stack = ($rrdfile); push @stack, @raw_commands if @raw_commands; push @stack, ( destination => $destination ); push @stack, ( timestamp => 'both' ); push @stack, @colour_theme if @colour_theme; push @stack, @options if @options; push @stack, @graph_opts if @graph_opts; write_txt($rrd->graph(@stack)); my $glob = catfile($destination,"$graph*.png"); my @images = glob($glob); warn "[Warning] $rrdfile: Looks like \$rrd->graph() failed to generate any images in '$glob'\n." unless @images; }; warn "[Warning] $rrdfile: => $@" if $@; } } } sub graph_def { my ($gdefs,$graph) = @_; my $rtn = {}; for (keys %{$gdefs->{graph}}) { my $graph_key = qr(^$_$); if (my ($var) = $graph =~ /$graph_key/) { $rtn = { %{$gdefs->{graph}->{$_}} }; unless (defined $var && "$var" ne "1") { ($var) = $graph =~ /_([^_]+)$/; } for my $key (keys %{$rtn}) { $rtn->{$key} =~ s/\$1/$var/g; } last; } } return $rtn; } sub list_dir { my $dir = shift; my @items = (); opendir(DH,$dir) || die "Unable to open file handle for directory '$dir': $!"; @items = grep(!/^\./,readdir(DH)); closedir(DH) || die "Unable to close file handle for directory '$dir': $!"; return @items; } sub create_thumbnails { my ($rrd,$dir,$hostname) = @_; my @thumbnail_options = (only_graph => '', width => 125, height => 32); create_graphs($rrd,$dir,$hostname,@thumbnail_options); } sub update_rrd { my ($rrd,$dir,$hostname) = @_; my $filename = shift @ARGV || undef; # Check out the input data die "Input data file '$filename' does not exist.\n" if defined $filename && !-f $filename; die "No data recieved while expecting STDIN data from rrd-client.pl.\n" if !$filename && !key_ready(); # Check the hostname is sane die "Hostname '$hostname' contains disallowed characters.\n" if $hostname =~ /[^\w\-\.\d]/ || $hostname =~ /^\.|\.$/; # Create the data directory for the RRD file if it doesn't exist File::Path::mkpath(catdir($dir->{data},$hostname)) unless -d catdir($dir->{data},$hostname); # Open the input file if specified if (defined $filename) { open(FH,'<',$filename) || die "[Error] $rrd: Unable to open file handle for file '$filename': $!"; select FH; }; # Parse the data my %data = (); while (local $_ = <>) { my ($path,$value) = split(/\s+/,$_); my ($time,@path) = split(/\./,$path); my $key = pop @path; # Check that none of the data is bogus or bollocks my $bogus = 0; $bogus++ unless $time =~ /^\d+$/; $bogus++ unless $value =~ /^[\d\.]+$/; for (@path) { $bogus++ unless /^[\w\-\_\.\d]+$/; } next if $bogus; my $rrdfile = catfile($dir->{data},$hostname,join('_',@path).'.rrd'); $data{$rrdfile}->{$time}->{$key} = $value; } # Process the data for my $rrdfile (sort keys %data) { for my $time (sort keys %{$data{$rrdfile}}) { eval { create_rrd($rrd,$dir,$rrdfile,$data{$rrdfile}->{$time}) unless -f $rrdfile; $rrd->update($rrdfile, $time, %{$data{$rrdfile}->{$time}}); }; warn "[Warning] $rrdfile: $@" if $@; } } # Close the input file if specified if (defined $filename) { select STDOUT; close(FH) || warn "[Warning] $rrd: Unable to close file handle for file '$filename': $!"; } return $hostname; } sub create_rrd { my ($rrd,$dir,$rrdfile,$data) = @_; my $defs = read_create_data(catfile($dir->{etc},'create.defs')); # Figure out what DS types to use my %create = map { ($_ => 'GAUGE') } sort keys %{$data}; while (my ($match,$def) = each %{$defs}) { next unless basename($rrdfile,qw(.rrd)) =~ /$match/; for my $ds (keys %create) { $create{$ds} = $def->{'*'}->{type} if defined $def->{'*'}->{type}; $create{$ds} = $def->{lc($ds)}->{type} if defined $def->{lc($ds)}->{type}; } } # Create the RRD file $rrd->create($rrdfile, %create); # Tune to use min and max values if specified while (my ($match,$def) = each %{$defs}) { next unless basename($rrdfile,qw(.rrd)) =~ /$match/; for my $ds ($rrd->sources($rrdfile)) { my $min = defined $def->{lc($ds)}->{min} ? $def->{lc($ds)}->{min} : defined $def->{'*'}->{min} ? $def->{'*'}->{min} : undef; RRDs::tune($rrdfile,'-i',"$ds:$min") if defined $min; my $max = defined $def->{lc($ds)}->{max} ? $def->{lc($ds)}->{max} : defined $def->{'*'}->{max} ? $def->{'*'}->{max} : undef; RRDs::tune($rrdfile,'-a',"$ds:$max") if defined $max; } } } sub HELP_MESSAGE { print qq{Syntax: rrd-server.pl <-u hostname,-g,-t,-V|-h|-v> [inputfile] -u Update RRD data for -g Create graphs from RRD data -t Create thumbnails from RRD data -V Display verbose progress information -v Display version information -h Display this help\n}; } # Display version sub VERSION { &VERSION_MESSAGE; } sub VERSION_MESSAGE { print "$0 version $VERSION ".'($Id: rrd-server.pl 1101 2008-01-24 18:07:32Z nicolaw $)'."\n"; } sub key_ready { my ($rin, $nfd) = ('',''); vec($rin, fileno(STDIN), 1) = 1; return $nfd = select($rin,undef,undef,3); } sub read_graph_data { my $filename = shift || undef; my %config = (); eval { my $conf = new Config::General( -ConfigFile => $filename, -LowerCaseNames => 1, -UseApacheInclude => 1, -IncludeRelative => 1, -MergeDuplicateBlocks => 1, -AllowMultiOptions => 1, -AutoTrue => 1, ); %config = $conf->getall; }; warn "[Warning] $@" if $@; return \%config; } sub read_create_data { my $filename = shift || undef; my %defs = (); # Open the input file if specified my @data; if (defined $filename && -f $filename) { open(FH,'<',$filename) || die "Unable to open file handle for file '$filename': $!"; @data = ; close(FH) || warn "Unable to close file handle for file '$filename': $!"; } else { @data = ; } # Parse the file that you've just selected for (@data) { last if /^__END__\s*$/; next if /^\s*$/ || /^\s*#/; my %def = (); @def{qw(rrdfile ds type min max)} = split(/\s+/,$_); next unless defined $def{ds}; $def{ds} = lc($def{ds}); $def{rrdfile} = qr($def{rrdfile}); for (keys %def) { if (!defined $def{$_} || $def{$_} eq '-') { delete $def{$_}; } elsif ($_ =~ /^(min|max)$/ && $def{$_} !~ /^[\d\.]+$/) { delete $def{$_}; } elsif ($_ eq 'type' && $def{$_} !~ /^(GAUGE|COUNTER|DERIVE|ABSOLUTE|COMPUTE)$/i) { delete $def{$_}; } } $defs{$def{rrdfile}}->{$def{ds}} = { map { ($_ => $def{$_}) } grep(!/^(rrdfile|ds)$/,keys %def) }; } return \%defs; } ## ## This processing and robustness of this routine is pretty ## bloody dire and awful. It needs to be rewritten with crap ## input data in mind rather than patching it every time I ## find a new scenario for the data to not be as expected!! ;-) ## sub write_txt { my %rtn = @_; while (my ($period,$data) = each %rtn) { my $filename = shift @{$data}; last if $filename =~ m,/thumbnails/,; my %values = (); my $max_len = 0; for (@{$data->[0]}) { my ($ds,$k,$v) = split(/\s+/,$_); next unless defined($ds) && length($ds) && defined($k); $values{$ds}->{$k} = $v; $max_len = length($ds) if length($ds) > $max_len; } if (open(FH,'>',"$filename.txt")) { printf FH "%s (%dx%d) %dK\n\n", basename($filename), (defined($data->[1]) ? $data->[1] : -1), (defined($data->[2]) ? $data->[2] : -1), (-e $filename ? (stat($filename))[7]/1024 : 0); for my $ds (sort keys %values) { for (qw(min max last)) { $values{$ds}->{$_} = '' unless defined $values{$ds}->{$_}; } printf FH "%-${max_len}s min: %s, max: %s, last: %s\n", $ds, $values{$ds}->{min}, $values{$ds}->{max}, $values{$ds}->{last}; } close(FH); } } } 1; __DATA__ # * means all # - means undef/na # rrdfile ds type min max ^net_traffic_.+ Transmit DERIVE 0 - ^net_traffic_.+ Receive DERIVE 0 - ^hdd_io_.+ * DERIVE 0 - ^hw_irq_interrupts_cpu\d+$ * DERIVE 0 - ^apache_status$ ReqPerSec DERIVE 0 - ^apache_status$ BytesPerSec DERIVE 0 - ^apache_logs$ * DERIVE 0 - ^db_mysql_activity$ * DERIVE 0 - ^db_mysql_activity_com$ * DERIVE 0 - __END__ RRD-Simple-1.44/examples/rrd-server/bin/rrd-client-mta-traffic.pl0000444000076400007640000002352010746154056024466 0ustar nicolawnicolaw#!/usr/bin/perl -w ############################################################ # # $Id$ # rrd-client-mta.pl - MTA data gathering script for rrd-server.pl # # Copyright 2007, 2008 Nicola Worthington # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################ # vim:ts=4:sw=4:tw=78 use constant MAIL_LOG => '/var/log/maillog'; use constant RRD_SERVER_URL => 'http://rrd.me.uk/cgi-bin/rrd-server.cgi'; use constant DEBUG => $ENV{DEBUG} ? 1 : 0; use constant RRD_STEPPING => 60; # seconds ############################################################ # # NO USER SERVICABLE PARTS BEYOND THIS POINT # ############################################################ use 5.6.1; use strict; use warnings; use Parse::Syslog qw(); use File::Tail qw(); use Getopt::Std qw(); use LWP::UserAgent qw(); use HTTP::Request::Common qw(); use Proc::DaemonLite qw(); our $VERSION = sprintf('%d.%02d', q$Revision: 1.1 $ =~ /(\d+)/g); my $this_minute; my %opt = ('ignore-localhost' => 1); my %sum = map { $_ => 0 } qw(sent received bounced rejected spam virus); #my $tail = File::Tail->new(name => MAIL_LOG, tail => -1); my $tail = File::Tail->new(name => MAIL_LOG, tail => 0); my $parser = new Parse::Syslog($tail, year => (localtime(time))[5]+1900, arrayref => 1, type => 'syslog' ); my $pid = Proc::DaemonLite::init_server(); while (my $sl = $parser->next) { process_line($sl); } exit; sub process_line { my $sl = shift; my $time = $sl->[0]; my $prog = $sl->[2]; my $text = $sl->[4]; if($prog eq 'exim') { if($text =~ /^[0-9a-zA-Z]{6}-[0-9a-zA-Z]{6}-[0-9a-zA-Z]{2} <= \S+/) { event($time, 'received'); } elsif($text =~ /^[0-9a-zA-Z]{6}-[0-9a-zA-Z]{6}-[0-9a-zA-Z]{2} => \S+/) { event($time, 'sent'); } # rejected after DATA: Your message scored 10.4 SpamAssassin point. Report follows: elsif($text =~ / rejected because \S+ is in a black list at \S+/) { if($opt{'rbl-is-spam'}) { event($time, 'spam'); } else { event($time, 'rejected'); } } elsif($text =~ / rejected RCPT \S+: (Sender verify failed|Unknown user)/) { event($time, 'rejected'); } } elsif($prog =~ /^postfix\/(.*)/) { my $prog = $1; if($prog eq 'smtp') { if($text =~ /\bstatus=sent\b/) { return if $opt{'ignore-localhost'} and $text =~ /\brelay=[^\s\[]*\[127\.0\.0\.1\]/; return if $opt{'ignore-host'} and $text =~ /\brelay=[^\s,]*$opt{'ignore-host'}/oi; event($time, 'sent'); } elsif($text =~ /\bstatus=bounced\b/) { event($time, 'bounced'); } } elsif($prog eq 'local') { if($text =~ /\bstatus=bounced\b/) { event($time, 'bounced'); } } elsif($prog eq 'smtpd') { if($text =~ /^[0-9A-Z]+: client=(\S+)/) { my $client = $1; return if $opt{'ignore-localhost'} and $client =~ /\[127\.0\.0\.1\]$/; return if $opt{'ignore-host'} and $client =~ /$opt{'ignore-host'}/oi; event($time, 'received'); } elsif($opt{'virbl-is-virus'} and $text =~ /^(?:[0-9A-Z]+: |NOQUEUE: )?reject: .*: 554.* blocked using virbl.dnsbl.bit.nl/) { event($time, 'virus'); } elsif($opt{'rbl-is-spam'} and $text =~ /^(?:[0-9A-Z]+: |NOQUEUE: )?reject: .*: 554.* blocked using/) { event($time, 'spam'); } elsif($text =~ /^(?:[0-9A-Z]+: |NOQUEUE: )?reject: /) { event($time, 'rejected'); } } elsif($prog eq 'error') { if($text =~ /\bstatus=bounced\b/) { event($time, 'bounced'); } } elsif($prog eq 'cleanup') { if($text =~ /^[0-9A-Z]+: (?:reject|discard): /) { event($time, 'rejected'); } } } elsif($prog eq 'sendmail' or $prog eq 'sm-mta') { if($text =~ /\bmailer=local\b/ ) { event($time, 'received'); } elsif($text =~ /\bmailer=relay\b/) { event($time, 'received'); } elsif($text =~ /\bstat=Sent\b/ ) { event($time, 'sent'); } elsif($text =~ /\bmailer=esmtp\b/ ) { event($time, 'sent'); } elsif($text =~ /\bruleset=check_XS4ALL\b/ ) { event($time, 'rejected'); } elsif($text =~ /\blost input channel\b/ ) { event($time, 'rejected'); } elsif($text =~ /\bruleset=check_rcpt\b/ ) { event($time, 'rejected'); } elsif($text =~ /\bstat=virus\b/ ) { event($time, 'virus'); } elsif($text =~ /\bruleset=check_relay\b/ ) { if (($opt{'virbl-is-virus'}) and ($text =~ /\bivirbl\b/ )) { event($time, 'virus'); } elsif ($opt{'rbl-is-spam'}) { event($time, 'spam'); } else { event($time, 'rejected'); } } elsif($text =~ /\bsender blocked\b/ ) { event($time, 'rejected'); } elsif($text =~ /\bsender denied\b/ ) { event($time, 'rejected'); } elsif($text =~ /\brecipient denied\b/ ) { event($time, 'rejected'); } elsif($text =~ /\brecipient unknown\b/ ) { event($time, 'rejected'); } elsif($text =~ /\bUser unknown$/i ) { event($time, 'bounced'); } elsif($text =~ /\bMilter:.*\breject=55/ ) { event($time, 'rejected'); } } elsif($prog eq 'amavis' || $prog eq 'amavisd') { if( $text =~ /^\([0-9-]+\) (Passed|Blocked) SPAM(?:MY)?\b/) { event($time, 'spam'); # since amavisd-new-2004xxxx } elsif($text =~ /^\([0-9-]+\) (Passed|Not-Delivered)\b.*\bquarantine spam/) { event($time, 'spam'); # amavisd-new-20030616 and earlier } ### UNCOMMENT IF YOU USE AMAVISD-NEW <= 20030616 WITHOUT QUARANTINE: #elsif($text =~ /^\([0-9-]+\) Passed, .*, Hits: (\d*\.\d*)/) { # if ($1 >= 5.0) { # amavisd-new-20030616 without quarantine # event($time, 'spam'); # } #} elsif($text =~ /^\([0-9-]+\) (Passed |Blocked )?INFECTED\b/) { if($text !~ /\btag2=/) { # ignore new per-recipient log entry (2.2.0) event($time, 'virus');# Passed|Blocked inserted since 2004xxxx } } elsif($text =~ /^\([0-9-]+\) (Passed |Blocked )?BANNED\b/) { if($text !~ /\btag2=/) { event($time, 'virus'); } } # elsif($text =~ /^\([0-9-]+\) Passed|Blocked BAD-HEADER\b/) { # event($time, 'badh'); # } elsif($text =~ /^Virus found\b/) { event($time, 'virus');# AMaViS 0.3.12 and amavisd-0.1 } } elsif($prog eq 'vagatefwd') { # Vexira antivirus (old) if($text =~ /^VIRUS/) { event($time, 'virus'); } } elsif($prog eq 'hook') { # Vexira antivirus if($text =~ /^\*+ Virus\b/) { event($time, 'virus'); } # Vexira antispam elsif($text =~ /\bcontains spam\b/) { event($time, 'spam'); } } elsif($prog eq 'avgatefwd' or $prog eq 'avmailgate.bin') { # AntiVir MailGate if($text =~ /^Alert!/) { event($time, 'virus'); } elsif($text =~ /blocked\.$/) { event($time, 'virus'); } } elsif($prog eq 'avcheck') { # avcheck if($text =~ /^infected/) { event($time, 'virus'); } } elsif($prog eq 'spamd') { if($text =~ /^(?:spamd: )?identified spam/) { event($time, 'spam'); } # ClamAV SpamAssassin-plugin elsif($text =~ /(?:result: )?CLAMAV/) { event($time, 'virus'); } } elsif($prog eq 'dspam') { if($text =~ /spam detected from/) { event($time, 'spam'); } } elsif($prog eq 'spamproxyd') { if($text =~ /^\s*SPAM/ or $text =~ /^identified spam/) { event($time, 'spam'); } } elsif($prog eq 'drweb-postfix') { # DrWeb if($text =~ /infected/) { event($time, 'virus'); } } elsif($prog eq 'BlackHole') { if($text =~ /Virus/) { event($time, 'virus'); } if($text =~ /(?:RBL|Razor|Spam)/) { event($time, 'spam'); } } elsif($prog eq 'MailScanner') { if($text =~ /(Virus Scanning: Found)/ ) { event($time, 'virus'); } elsif($text =~ /Bounce to/ ) { event($time, 'bounced'); } elsif($text =~ /^Spam Checks: Found ([0-9]+) spam messages/) { my $cnt = $1; for (my $i=0; $i<$cnt; $i++) { event($time, 'spam'); } } } elsif($prog eq 'clamsmtpd') { if($text =~ /status=VIRUS/) { event($time, 'virus'); } } elsif($prog eq 'clamav-milter') { if($text =~ /Intercepted/) { event($time, 'virus'); } } # uncommment for clamassassin: #elsif($prog eq 'clamd') { # if($text =~ /^stream: .* FOUND$/) { # event($time, 'virus'); # } #} elsif ($prog eq 'smtp-vilter') { if ($text =~ /clamd: found/) { event($time, 'virus'); } } elsif($prog eq 'avmilter') { # AntiVir Milter if($text =~ /^Alert!/) { event($time, 'virus'); } elsif($text =~ /blocked\.$/) { event($time, 'virus'); } } elsif($prog eq 'bogofilter') { if($text =~ /Spam/) { event($time, 'spam'); } } elsif($prog eq 'filter-module') { if($text =~ /\bspam_status\=(?:yes|spam)/) { event($time, 'spam'); } } elsif($prog eq 'sta_scanner') { if($text =~ /^[0-9A-F]+: virus/) { event($time, 'virus'); } } } sub event { my ($t, $type) = @_; update($t) && $sum{$type}++; } # returns 1 if $sum should be updated sub update($) { my $t = shift; my $m = $t - $t % RRD_STEPPING; $this_minute = $m unless defined $this_minute; return 1 if $m == $this_minute; return 0 if $m < $this_minute; my $data = ''; for (sort keys %sum) { $data .= "$this_minute.mail.traffic.$_ $sum{$_}\n"; } warn $data if DEBUG; my $ua = LWP::UserAgent->new(agent => $0); my $resp = $ua->request( HTTP::Request::Common::POST( RRD_SERVER_URL, Content_Type => 'text/plain', Content => $data ) ); if ($resp->is_success) { printf("%s\n",$resp->content); } else { warn 'Posting Error: '.$resp->status_line; } $this_minute = $m; $sum{$_} = 0 for keys %sum; return 1; } __END__ RRD-Simple-1.44/examples/rrd-server/bin/rrd-client.pl0000444000076400007640000011266210746154056022301 0ustar nicolawnicolaw#!/usr/bin/perl -w ############################################################ # # $Id: rrd-client.pl 1092 2008-01-23 14:23:51Z nicolaw $ # rrd-client.pl - Data gathering script for RRD::Simple # # Copyright 2006,2007,2008 Nicola Worthington # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################ # vim:ts=4:sw=4:tw=78 ############################################################ # User defined constants use constant DB_MYSQL_DSN => $ENV{DB_MYSQL_DSN} || 'DBI:mysql:mysql:localhost'; use constant DB_MYSQL_USER => $ENV{DB_MYSQL_USER} || undef; use constant DB_MYSQL_PASS => $ENV{DB_MYSQL_PASS} || undef; use constant NET_PING_HOSTS => $ENV{NET_PING_HOSTS} ? (split(/[\s,:]+/,$ENV{NET_PING_HOSTS})) : qw(); # # YOU SHOULD NOT NEED TO EDIT ANYTHING BEYOND THIS POINT # ############################################################ use 5.004; use strict; #use warnings; # comment out for release use vars qw($VERSION); $VERSION = '1.42' || sprintf('%d', q$Revision: 1092 $ =~ /(\d+)/g); $ENV{PATH} = '/bin:/usr/bin'; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; # Default list of probes my %probes = ( hw_irq_interrupts => 'Hardward IRQ Interrupts', cpu_utilisation => 'CPU Utilisation', cpu_loadavg => 'Load Average', cpu_temp => 'CPU Temperature', cpu_interrupts => 'CPU Interrupts', hdd_io => 'Hard Disk I/O', hdd_temp => 'Hard Disk Temperature', hdd_capacity => 'Disk Capacity', mem_usage => 'Memory Usage & Swap Usage', mem_swap_activity => 'Swap Activity', mem_proc_largest => 'Largest Process', proc_threads => 'Threads', proc_state => 'Processes', proc_filehandles => 'File Handles', apache_status => 'Apache Scoreboard & Apache Activity', apache_logs => 'Apache Log Activity', misc_uptime => 'Server Uptime', misc_users => 'Users Logged In', misc_ipmi_temp => 'IPMI Temperature Probes', misc_entropy => 'Available Entropy', db_mysql_activity => 'MySQL Database Activity', db_mysql_replication => 'MySQL Database Replication', mail_exim_queue => 'Exim Mail Queue', mail_postfix_queue => 'Postfix Mail Queue', mail_sendmail_queue => 'Sendmail Mail Queue', net_traffic => 'Network Traffic', net_connections => 'Network Connections', net_ping_host => 'Ping', # net_connections_ports => 'Service Connections', ); # Get command line options my %opt = (); eval "require Getopt::Std"; $Getopt::Std::STANDARD_HELP_VERSION = 1; $Getopt::Std::STANDARD_HELP_VERSION = 1; Getopt::Std::getopts('p:i:x:s:c:V:hvqlP:?', \%opt) unless $@; (HELP_MESSAGE() && exit) if defined $opt{h} || defined $opt{'?'}; (VERSION_MESSAGE() && exit) if defined $opt{v}; # Display a list of available probe names if ($opt{l}) { print "Available probes:\n"; printf " %-24s %s\n",'PROBE','DESCRIPTION'; for (sort keys %probes) { my $str = sprintf(" %-24s %s\n", $_, $probes{$_}); $str =~ s/(\S+) (\s+) /$_ = "$1 ". '.' x length($2) ." ";/e; print "$str"; } exit; } # Check to see if we are capable of SNMP queries my $snmpClient; if ($opt{s}) { eval { require Net::SNMP; $snmpClient = 'Net::SNMP'; }; if ($@) { my $c = 'snmpwalk'; # snmpget my $cmd = select_cmd("/usr/bin/$c","/usr/local/bin/$c"); die "Error: unable to query via SNMP. Please install Net::SNMP or $c.\n" unless $cmd; $snmpClient = $cmd; } $opt{c} = 'public' unless defined($opt{c}) && $opt{c} =~ /\S+/; $opt{V} = '2c' unless defined($opt{V}) && $opt{V} =~ /^(1|2c)$/; $opt{P} = 161 unless defined($opt{P}) && $opt{P} =~ /^[0-9]+$/; } # Filter on probe include list my @probes = sort keys %probes; if (defined $opt{i}) { my $inc = join('|',split(/\s*,\s*/,$opt{i})); @probes = grep(/(^|_)($inc)(_|$)/,@probes); } # Filter on probe exclude list if (defined $opt{x}) { my $exc = join('|',split(/\s*,\s*/,$opt{x})); @probes = grep(!/(^|_)($exc)(_|$)/,@probes); } # Run the probes one by one die "Error: nothing to probe!\n" unless @probes; my $post = ''; my %update_cache; for my $probe (@probes) { eval { local $SIG{ALRM} = sub { die "Timeout!\n"; }; alarm 15; my $str = report($probe,eval "$probe();"); if (defined $opt{p}) { $post .= $str; } else { print $str; } warn "Warning [$probe]: $@" if !$opt{q} && $@; alarm 0; }; warn "Warning [$probe]: $@" if !$opt{q} && $@; } # HTTP POST the data if asked to print scalar(basic_http('POST',$opt{p},30,$post))."\n" if $opt{p}; exit; # Report the data sub report { (my $probe = shift) =~ s/[_-]/\./g; my %data = @_ % 2 ? (@_,undef) : @_; my $str = ''; for my $k (sort keys %data) { #$data{$k} = 0 unless defined($data{$k}); next unless defined($data{$k}) && $data{$k} =~ /^[0-9\.]*$/; $str .= sprintf("%s.%s.%s %s\n", time(), $probe, $k, $data{$k}); } return $str; } # Display help sub HELP_MESSAGE { print qq{Syntax: rrd-client.pl [-i probe1,probe2,..|-x probe1,probe2,..] [-s host] [-c community] [-P port] [-V 1|2c] [-p URL] [-h|-v] -i Include a list of comma seperated probes -x Exclude a list of comma seperated probes -s Specify hostname to probe via SNMP -c Specify SNMP community name (defaults to public) -V Specify SNMP version to use (1 or 2c, defaults to 2c) -P Specify SNMP port to use -p HTTP POST data to the specified URL -q Suppress all warning messages -l Display a list of available probe names -v Display version information -h Display this help Examples: rrd-client.pl -x apache_status -q -p http://rrd.me.uk/cgi-bin/rrd-server.cgi rrd-client.pl -s localhost -p http://rrd.me.uk/cgi-bin/rrd-server.cgi rrd-client.pl -s server1.company.com | rrd-server.pl -u server1.company.com \n}; } # Display version sub VERSION { &VERSION_MESSAGE; } sub VERSION_MESSAGE { print "$0 version $VERSION ".'($Id: rrd-client.pl 1092 2008-01-23 14:23:51Z nicolaw $)'."\n"; } # Basic HTTP client if LWP is unavailable sub basic_http { my ($method,$url,$timeout,$data) = @_; $method ||= 'GET'; $url ||= 'http://localhost/'; $timeout ||= 5; my ($scheme,$host,$port,$path) = $url =~ m,^(https?://)([\w\d\.\-]+)(?::(\d+))?(.*),i; $scheme ||= 'http://'; $host ||= 'localhost'; $path ||= '/'; $port ||= 80; my $str = ''; eval "use Socket"; return $str if $@; eval { local $SIG{ALRM} = sub { die "TIMEOUT\n" }; alarm $timeout; my $iaddr = inet_aton($host) || die; my $paddr = sockaddr_in($port, $iaddr); my $proto = getprotobyname('tcp'); socket(SOCK, AF_INET(), SOCK_STREAM(), $proto) || die "socket: $!"; connect(SOCK, $paddr) || die "connect: $!"; select(SOCK); $| = 1; select(STDOUT); # Send the HTTP request print SOCK "$method $path HTTP/1.1\n"; print SOCK "Host: $host". ("$port" ne "80" ? ":$port" : '') ."\n"; print SOCK "User-Agent: $0 version $VERSION ".'($Id: rrd-client.pl 1092 2008-01-23 14:23:51Z nicolaw $)'."\n"; if ($data && $method eq 'POST') { print SOCK "Content-Length: ". length($data) ."\n"; print SOCK "Content-Type: application/x-www-form-urlencoded\n"; } print SOCK "\n"; print SOCK $data if $data && $method eq 'POST'; my $body = 0; while (local $_ = ) { s/[\n\n]+//g; $str .= $_ if $_ && $body; $body = 1 if /^\s*$/; } close(SOCK); alarm 0; }; warn "Warning [basic_http]: $@" if !$opt{q} && $@ && $data; return wantarray ? split(/\n/,$str) : "$str"; } # Return the most appropriate binary command sub select_cmd { foreach (@_) { if (-f $_ && -x $_ && /(\S+)/) { return $1; } } return ''; } # # Probes # sub _snmp { my $oid = [@_]; my $result = {}; # Net::SNMP if ($snmpClient eq 'Net::SNMP') { my ($session, $error) = Net::SNMP->session( -hostname => $opt{s}, -community => $opt{c}, -version => $opt{V}, -port => $opt{P}, -translate => [ -timeticks => 0x0 ], ); die $error if !defined($session); $result = $session->get_request(-varbindlist => $oid); $session->close; die $session->error if !defined($result); # snmpget / snmpwalk } else { my $oidStr = join(' ', @{$oid}); my $cmd = "$snmpClient -O n -O t -v $opt{V} -c $opt{c} $opt{s} $oidStr"; #my $cmd = "$snmpClient -O t -v $opt{V} -c $opt{c} $opt{s} $oidStr"; open(PH,'-|',"$cmd 2>&1") || die "Unable to open file handle PH for command '$cmd': $!\n"; while (local $_ = ) { s/[\r\n]+//g; s/^(?:\s+|\s+)$//g; if (/^(\.[\.0-9]+|[A-Za-z:\-\.0-9]+)\s*=\s*(?:([A-Za-z0-9]+):\s*)?["']?(\S*)["']?/) { my ($oid,$type,$value) = ($1,$2,$3); $oid = ".$oid" if $oid =~ /^[0-9][0-9\.]+$/; $result->{$oid} = $value; } else { warn "Warning [_snmp]: $_\n" unless $opt{q}; } } close(PH) || die "Unable to close file handle PH for command '$cmd': $!\n"; } return $result->{(keys(%{$result}))[0]} if !wantarray && keys(%{$result}) == 1; return $result; } sub net_ping_host { return if $opt{s}; return unless defined NET_PING_HOSTS() && scalar NET_PING_HOSTS() > 0; my $cmd = select_cmd(qw(/bin/ping /usr/bin/ping /sbin/ping /usr/sbin/ping)); return unless -f $cmd; my %update = (); my $count = 3; for my $str (NET_PING_HOSTS()) { my ($host) = $str =~ /^([\w\d_\-\.]+)$/i; next unless $host; my $cmd2 = "$cmd -c $count $host 2>&1"; open(PH,'-|',$cmd2) || die "Unable to open file handle PH for command '$cmd2': $!\n"; while (local $_ = ) { if (/\s+(\d+)%\s+packet\s+loss[\s,]/i) { $update{"$host.PacketLoss"} = $1 || 0; } elsif (my ($min,$avg,$max,$mdev) = $_ =~ /\s+([\d\.]+)\/([\d\.]+)\/([\d\.]+)\/([\d\.]+)\s+/) { $update{"$host.AvgRTT"} = $avg || 0; $update{"$host.MinRTT"} = $min || 0; $update{"$host.MaxRTT"} = $max || 0; $update{"$host.MDevRTT"} = $mdev || 0; } } close(PH) || die "Unable to close file handle PH for command '$cmd2': $!\n"; } return %update; } sub mem_proc_largest { return if $opt{s}; my $cmd = select_cmd(qw(/bin/ps /usr/bin/ps)); return unless -f $cmd; $cmd .= ' -eo vsize'; my %update = (); open(PH,'-|',$cmd) || die "Unable to open file handle PH for command '$cmd': $!\n"; while (local $_ = ) { if (/(\d+)/) { my $kb = $1; $update{LargestProc} = $kb if !defined $update{LargestProc} || (defined $update{LargestProc} && $kb > $update{LargestProc}); } } close(PH) || die "Unable to close file handle PH for command '$cmd': $!\n"; $update{LargestProc} *= 1024 if defined $update{LargestProc}; return %update; } sub proc_threads { if ($opt{s}) { my $procs = _snmp('.1.3.6.1.2.1.25.1.6.0'); # hrSystemProcesses return unless defined($procs) && $procs =~ /^[0-9]+$/; return ('Processes' => $procs, 'Threads' => 0, 'MultiThreadProcs' => 0); } return if $opt{s}; return unless ($^O eq 'linux' && `/bin/uname -r 2>&1` =~ /^2\.6\./) || ($^O eq 'solaris' && `/bin/uname -r 2>&1` =~ /^5\.9/); my %update = (); my $cmd = '/bin/ps -eo pid,nlwp'; open(PH,'-|',$cmd) || die "Unable to open file handle PH for command '$cmd': $!"; while (local $_ = ) { if (my ($pid,$nlwp) = $_ =~ /^\s*(\d+)\s+(\d+)\s*$/) { $update{Processes}++; $update{Threads} += $nlwp; $update{MultiThreadProcs}++ if $nlwp > 1; } } close(PH) || die "Unable to close file handle PH for command '$cmd': $!"; return %update; } sub mail_exim_queue { return if $opt{s}; my $spooldir = '/var/spool/exim/input'; return unless -d $spooldir && -x $spooldir && -r $spooldir; local %mail::exim::queue::update = (Messages => 0); require File::Find; File::Find::find({wanted => sub { my ($dev,$ino,$mode,$nlink,$uid,$gid); (($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) && -f _ && /^.*-D\z/s && $mail::exim::queue::update{Messages}++; }, no_chdir => 1}, $spooldir); return %mail::exim::queue::update; } sub mail_sendmail_queue { return if $opt{s}; my $spooldir = '/var/spool/mqueue'; return unless -d $spooldir && -x $spooldir && -r $spooldir; local %mail::sendmail::queue::update = (Messages => 0); require File::Find; File::Find::find({wanted => sub { my ($dev,$ino,$mode,$nlink,$uid,$gid); (($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) && -f _ && /^Qf[a-zA-Z0-9]{14}\z/s && $mail::sendmail::queue::update{Messages}++; }, no_chdir => 1}, $spooldir); return %mail::sendmail::queue::update; } sub mail_postfix_queue { return if $opt{s}; my @spooldirs = qw( /var/spool/postfix/incoming /var/spool/postfix/active /var/spool/postfix/defer /var/spool/postfix/deferred ); for my $spooldir (@spooldirs) { return unless -d $spooldir && -x $spooldir && -r $spooldir; } local %mail::postfix::queue::update = (Messages => 0); require File::Find; File::Find::find({wanted => sub { my ($dev,$ino,$mode,$nlink,$uid,$gid); (($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) && -f _ && $mail::postfix::queue::update{Messages}++; }, no_chdir => 1}, @spooldirs); return %mail::postfix::queue::update; } # DO NOT ENABLE THIS ONE YET sub mail_queue { return if $opt{s}; my $cmd = select_cmd(qw(/usr/bin/mailq /usr/sbin/mailq /usr/local/bin/mailq /usr/local/sbin/mailq /bin/mailq /sbin/mailq /usr/local/exim/bin/mailq /home/system/exim/bin/mailq)); return unless -f $cmd; my %update = (); open(PH,'-|',$cmd) || die "Unable to open file handle PH for command '$cmd': $!\n"; while (local $_ = ) { # This needs to match a single message id = currently only exim friendly if (/^\s*\S+\s+\S+\s+[a-z0-9]{6}-[a-z0-9]{6}-[a-z0-9]{2} connect(DB_MYSQL_DSN,DB_MYSQL_USER,DB_MYSQL_PASS); my $sth = $dbh->prepare('SHOW GLOBAL STATUS'); $sth->execute(); while (my @ary = $sth->fetchrow_array()) { if (grep { $ary[0] eq $_ && /^Com_/ } @cols) { $update{"com.$ary[0]"} = $ary[1]; } elsif (grep { $ary[0] eq $_ } @cols) { $update{$ary[0]} = $ary[1]; } } $sth->finish(); $dbh->disconnect(); }; return %update; } sub db_mysql_replication { return if $opt{s}; my %update = (); return %update unless (defined DB_MYSQL_DSN && defined DB_MYSQL_USER); eval { require DBI; my $dbh = DBI->connect(DB_MYSQL_DSN,DB_MYSQL_USER,DB_MYSQL_PASS); my $sth = $dbh->prepare('SHOW SLAVE STATUS'); $sth->execute(); my $row = $sth->fetchrow_hashref; $sth->finish(); $dbh->disconnect(); $update{SecondsBehind} = $row->{Seconds_Behind_Master} || 0; }; return %update; } sub misc_users { if ($opt{s}) { my $users = _snmp('.1.3.6.1.2.1.25.1.5.0'); # hrSystemNumUsers return unless defined($users) && $users =~ /^[0-9]+$/; return ('Users' => $users, 'Unique' => 0); } return if $opt{s}; my $cmd = select_cmd(qw(/usr/bin/who /bin/who /usr/bin/w /bin/w)); return unless -f $cmd; my %update = (); open(PH,'-|',$cmd) || die "Unable to open file handle PH for command '$cmd': $!\n"; my %users = (); while (local $_ = ) { next if /^\s*USERS\s*TTY/; $users{(split(/\s+/,$_))[0]}++; $update{Users}++; } close(PH) || die "Unable to close file handle PH for command '$cmd': $!\n"; $update{Unique} = keys %users if keys %users; unless (keys %update) { $cmd = -f '/usr/bin/uptime' ? '/usr/bin/uptime' : '/bin/uptime'; if (my ($users) = `$cmd` =~ /,\s*(\d+)\s*users?\s*,/i) { $update{Users} = $1; } } $update{Users} ||= 0; $update{Unique} ||= 0; return %update; } sub misc_uptime { if ($opt{s}) { my $ticks = _snmp('.1.3.6.1.2.1.25.1.1.0'); # hrSystemUptime return unless defined($ticks) && $ticks =~ /^[0-9]+$/; return ('DaysUp' => $ticks/100/60/60/24); } my $cmd = select_cmd(qw(/usr/bin/uptime /bin/uptime)); return unless -f $cmd; my %update = (); if (my ($str) = `$cmd` =~ /\s*up\s*(.+?)\s*,\s*\d+\s*users?/) { my $days = 0; if (my ($nuke,$num) = $str =~ /(\s*(\d+)\s*days?,?\s*)/) { $str =~ s/$nuke//; $days += $num; } if (my ($nuke,$mins) = $str =~ /(\s*(\d+)\s*mins?,?\s*)/) { $str =~ s/$nuke//; $days += ($mins / (60*24)); } if (my ($nuke,$hours) = $str =~ /(\s*(\d+)\s*(hour|hr)s?,?\s*)/) { $str =~ s/$nuke//; $days += ($hours / 24); } if (my ($hours,$mins) = $str =~ /\s*(\d+):(\d+)\s*,?/) { $days += ($mins / (60*24)); $days += ($hours / 24); } $update{DaysUp} = $days; } return %update; } sub cpu_temp { return if $opt{s}; my $cmd = '/usr/bin/sensors'; return unless -f $cmd; my %update = (); open(PH,'-|',"$cmd 2>&1") || die "Unable to open file handle PH for command '$cmd': $!\n"; while (local $_ = ) { if (my ($k,$v) = $_ =~ /^([^:]*\b(?:CPU|temp)\d*\b.*?):\s*\S*?([\d\.]+)\S*\s*/i) { $k =~ s/\W//g; $k =~ s/Temp$//i; $update{$k} = $v; } elsif (/(no sensors found|kernel driver|sensors-detect|error|warning)/i && !$opt{q}) { warn "Warning [cpu_temp]: $_"; } } close(PH) || die "Unable to close file handle PH for command '$cmd': $!\n"; return %update; } sub apache_logs { return if $opt{s}; my $dir = '/var/log/httpd'; return unless -d $dir; my %update = (); if (-d $dir) { opendir(DH,$dir) || die "Unable to open file handle for directory '$dir': $!\n"; my @files = grep(!/^\./,readdir(DH)); closedir(DH) || die "Unable to close file handle for directory '$dir': $!\n"; for (@files) { next if /\.(\d+|gz|bz2|Z|zip|old|bak|pid|backup)$/i || /[_\.\-]pid$/; my $file = "$dir/$_"; next unless -f $file; s/[\.\-]/_/g; $update{$_} = (stat($file))[7]; } } return %update; } sub apache_status { return if $opt{s}; my @data = (); my %update = (); my $timeout = 5; my $url = 'http://localhost/server-status?auto'; my %keys = (W => 'Write', G => 'GraceClose', D => 'DNS', S => 'Starting', L => 'Logging', R => 'Read', K => 'Keepalive', C => 'Closing', I => 'Idle', '_' => 'Waiting'); eval "use LWP::UserAgent"; unless ($@) { eval { my $ua = LWP::UserAgent->new( agent => "$0 version $VERSION ".'($Id)', timeout => $timeout); $ua->env_proxy; $ua->max_size(1024*250); my $response = $ua->get($url); if ($response->is_success) { @data = split(/\n+|\r+/,$response->content); } elsif (!$opt{q}) { warn "Warning [apache_status]: failed to get $url; ". $response->status_line ."\n"; } }; } if ($@) { @data = basic_http('GET',$url,$timeout); } for (@data) { my ($k,$v) = $_ =~ /^\s*(.+?):\s+(.+?)\s*$/; $k = '' unless defined $k; $v = '' unless defined $v; $k =~ s/\s+//g; #$k = lc($k); next unless $k; if ($k eq 'Scoreboard') { my %x; $x{$_}++ for split(//,$v); for (keys %keys) { $update{"scoreboard.$keys{$_}"} = defined $x{$_} ? $x{$_} : 0; } } else { $update{$k} = $v; } } $update{ReqPerSec} = int($update{TotalAccesses}) if defined $update{TotalAccesses}; $update{BytesPerSec} = int($update{TotalkBytes} * 1024) if defined $update{TotalkBytes}; return %update; } sub _darwin_cpu_utilisation { my $output = qx{/usr/bin/sar 4 1}; my %rv = (); if ($output =~ m/Average:\s+(\d+)\s+(\d+)\s+(\d+)/) { %rv = ( User => $1, System => $2, Idle => $3, IO_Wait => 0, # at the time of writing, sar doesn't provide this metric ); } return %rv; } sub cpu_utilisation { my %update = (); if ($opt{s}) { $update{'User'} = _snmp('.1.3.6.1.4.1.2021.11.9.0'); $update{'System'} = _snmp('.1.3.6.1.4.1.2021.11.10.0'); $update{'Idle'} = _snmp('.1.3.6.1.4.1.2021.11.11.0'); #$update{'IO_Wait'} = 100 - $update{'User'} - $update{'System'} - $update{'Idle'}; $update{'IO_Wait'} = 0; # Try querying the Windows thingie instead unless (grep(/^[0-9\.]{1,3}$/, values(%update)) == 4) { # hrProcessorLoad # .1.3.6.1.2.1.25.3.3.1.2.1 - CPU 1 # .1.3.6.1.2.1.25.3.3.1.2.2 - CPU 2 ... my $total; my $cpu; for ($cpu = 1; $cpu <= 16; $cpu++) { my $load = _snmp(".1.3.6.1.2.1.25.3.3.1.2.$cpu"); if (defined($load) && !ref($load) && $load =~ /^[0-9\.]{1,3}$/) { $total += $load; } else { last; } } return unless $total && $cpu-1; %update = ('User' => int($total / $cpu-1), 'System' => 0, 'Idle' => 0, 'IO_Wait' => 0); } return %update; } if ($^O eq 'darwin') { return _darwin_cpu_utilisation(); } my $cmd = '/usr/bin/vmstat'; return unless -f $cmd; %update = _parse_vmstat("$cmd 1 2"); my %labels = (wa => 'IO_Wait', id => 'Idle', sy => 'System', us => 'User'); $update{$_} ||= 0 for keys %labels; return ( map {( $labels{$_} || $_ => $update{$_} )} keys %labels ); } sub hw_irq_interrupts { return if $opt{s}; my @update; if (open(FH,'<','/proc/interrupts')) { local $_ = ; return unless /^\s+(CPU[0-9]+.*)/; my @cpus = split(/\s+/,$1); $_ = lc($_) for @cpus; my %data; while (local $_ = ) { if (/^\s*([0-9]{1,2}):\s+([\s0-9]+)\s+(\S+)\s+(.+?)\s*$/) { my ($irq,$ints,$path,$src) = ($1,$2,$3,$4); my @ints = split(/\s+/,$ints); for (my $i = 0; $i <= @cpus; $i++) { my $cpu = $cpus[$i]; my $int = $ints[$i]; next unless defined $cpu && defined $int; $data{$cpu}->{"$cpu.irq$irq"} = $int; } } } for my $cpu (keys %data) { if (grep(/[1-9]/,values %{$data{$cpu}})) { push @update, %{$data{$cpu}}; } } close(FH) || warn "Unable to close file handle FH for file '/proc/interrupts': $!"; } return @update; } sub cpu_interrupts { return if $opt{s}; my $cmd = '/usr/bin/vmstat'; return unless -f $cmd; my %update = _parse_vmstat("$cmd 1 2"); my %labels = (in => 'Interrupts'); return unless defined $update{in}; $update{$_} ||= 0 for keys %labels; return ( map {( $labels{$_} || $_ => $update{$_} )} keys %labels ); } sub mem_swap_activity { return if $opt{s}; my $cmd = '/usr/bin/vmstat'; return unless -f $cmd; my %update = _parse_vmstat("$cmd 1 2"); my %labels = (si => 'Swap_In', so => 'Swap_Out'); return unless defined $update{si} && defined $update{so}; $update{$_} ||= 0 for keys %labels; return ( map {( $labels{$_} || $_ => $update{$_} )} keys %labels ); } sub _parse_vmstat { my $cmd = shift; my %update; my @keys; if (exists $update_cache{vmstat}) { %update = %{$update_cache{vmstat}}; } else { open(PH,'-|',$cmd) || die "Unable to open file handle PH for command '$cmd': $!\n"; while (local $_ = ) { s/^\s+|\s+$//g; if (/\s+\d+\s+\d+\s+\d+\s+/ && @keys) { @update{@keys} = split(/\s+/,$_); } else { @keys = split(/\s+/,$_); } } close(PH) || die "Unable to close file handle PH for command '$cmd': $!\n"; $update_cache{vmstat} = \%update; } return %update; } sub _parse_ipmitool_sensor { my $cmd = shift; my %update; my @keys; if (exists $update_cache{ipmitool_sensor}) { %update = %{$update_cache{ipmitool_sensor}}; } else { if ((-e '/dev/ipmi0' || -e '/dev/ipmi/0') && open(PH,'-|',$cmd)) { while (local $_ = ) { chomp; s/(^\s+|\s+$)//g; my ($key,@ary) = split(/\s*\|\s*/,$_); $key =~ s/[^a-zA-Z0-9_]//g; $update{$key} = \@ary; } close(PH); $update_cache{ipmitool_sensor} = \%update; } } return %update; } sub misc_ipmi_temp { return if $opt{s}; my $cmd = select_cmd(qw(/usr/bin/ipmitool)); return unless -f $cmd; my %update = (); my %data = _parse_ipmitool_sensor("$cmd sensor"); for (grep(/temp/i,keys %data)) { $update{$_} = $data{$_}->[0] if $data{$_}->[0] =~ /^[0-9\.]+$/; } return unless keys %update; return %update;; } sub hdd_io { return if $opt{s}; my $cmd = select_cmd(qw(/usr/bin/iostat /usr/sbin/iostat)); return unless -f $cmd; return unless $^O eq 'linux'; $cmd .= ' -k'; my %update = (); open(PH,'-|',$cmd) || die "Unable to open file handle PH for command '$cmd': $!\n"; while (local $_ = ) { if (my ($dev,$r,$w) = $_ =~ /^([\w\d]+)\s+\S+\s+\S+\s+\S+\s+(\d+)\s+(\d+)$/) { $update{"$dev.Read"} = $r*1024; $update{"$dev.Write"} = $w*1024; } } close(PH) || die "Unable to close file handle PH for command '$cmd': $!\n"; return %update; } sub mem_usage { return if $opt{s}; my %update = (); my $cmd = select_cmd(qw(/usr/bin/free /bin/free)); my @keys = (); if ($^O eq 'linux' && -f $cmd && -x $cmd) { $cmd .= ' -b'; open(PH,'-|',$cmd) || die "Unable to open file handle PH for command '$cmd': $!\n"; while (local $_ = ) { if (@keys && /^Mem:\s*(\d+.+)\s*$/i) { my @values = split(/\s+/,$1); for (my $i = 0; $i < @values; $i++) { $update{ucfirst($keys[$i])} = $values[$i]; } $update{Used} = $update{Used} - $update{Buffers} - $update{Cached}; } elsif (@keys && /^Swap:\s*(\d+.+)\s*$/i) { my @values = split(/\s+/,$1); for (my $i = 0; $i < @values; $i++) { $update{"swap.".ucfirst($keys[$i])} = $values[$i]; } } elsif (!@keys && /^\s*([\w\s]+)\s*$/) { @keys = split(/\s+/,$1); } } close(PH) || die "Unable to close file handle PH for command '$cmd': $!\n"; } elsif ($^O eq 'darwin' && -x '/usr/sbin/sysctl') { my $swap = qx{/usr/sbin/sysctl vm.swapusage}; if ($swap =~ m/total = (.+)M used = (.+)M free = (.+)M/) { $update{"swap.Total"} = $1*1024*1024; $update{"swap.Used"} = $2*1024*1024; $update{"swap.Free"} = $3*1024*1024; } } else { eval "use Sys::MemInfo qw(totalmem freemem)"; die "Please install Sys::MemInfo so that I can get memory information.\n" if $@; @update{qw(Total Free)} = (totalmem(),freemem()); } return %update; } sub hdd_temp { return if $opt{s}; my $cmd = select_cmd(qw(/usr/sbin/hddtemp /usr/bin/hddtemp)); return unless -f $cmd; my @devs = (); for my $dev (glob('/dev/hd?'),glob('/dev/sd?')) { if ($dev =~ /^(\/dev\/\w{3})$/i) { push @devs, $1; } } $cmd .= " -q @devs 2>&1"; my %update = (); return %update unless @devs; open(PH,'-|',$cmd) || die "Unable to open file handle PH for command '$cmd': $!\n"; while (local $_ = ) { if (my ($dev,$temp) = $_ =~ m,^/dev/([a-z]+):\s+.+?:\s+(\d+)..?C,) { $update{$dev} = $temp; } elsif (!/^\s*$/ && !$opt{q}) { warn "Warning [hdd_temp]: $_"; } } close(PH) || die "Unable to close file handle PH for command '$cmd': $!\n"; return %update; } sub hdd_capacity { if ($opt{s}) { return; my $snmp = _snmp('.1.3.6.1.4.1.2021.9.1'); # dskTable return unless defined($snmp) && ref($snmp) eq 'HASH'; my %disks; #.1.3.6.1.4.1.2021.9.1.1.1 = INTEGER: 1 #.1.3.6.1.4.1.2021.9.1.2.1 = STRING: / #.1.3.6.1.4.1.2021.9.1.3.1 = STRING: /dev/sda1 #.1.3.6.1.4.1.2021.9.1.4.1 = INTEGER: 100000 #.1.3.6.1.4.1.2021.9.1.5.1 = INTEGER: -1 #.1.3.6.1.4.1.2021.9.1.6.1 = INTEGER: 7850996 #.1.3.6.1.4.1.2021.9.1.7.1 = INTEGER: 3153808 #.1.3.6.1.4.1.2021.9.1.8.1 = INTEGER: 4298376 #.1.3.6.1.4.1.2021.9.1.9.1 = INTEGER: 58 #.1.3.6.1.4.1.2021.9.1.10.1 = INTEGER: 16 #.1.3.6.1.4.1.2021.9.1.100.1 = INTEGER: 0 #.1.3.6.1.4.1.2021.9.1.101.1 = STRING: #'UCD-SNMP-MIB::dskMinimum.1' => '100000', #'UCD-SNMP-MIB::dskErrorMsg.1' => '', #'UCD-SNMP-MIB::dskIndex.1' => '1', #'UCD-SNMP-MIB::dskPath.1' => '/', #'UCD-SNMP-MIB::dskPercentNode.1' => '16', #'UCD-SNMP-MIB::dskErrorFlag.1' => '0', #'UCD-SNMP-MIB::dskAvail.1' => '3153784', #'#UCD-SNMP-MIB::dskPercent.1' => '58', #'UCD-SNMP-MIB::dskMinPercent.1' => '-1', #'UCD-SNMP-MIB::dskDevice.1' => '/dev/sda1', #'UCD-SNMP-MIB::dskUsed.1' => '4298400', #'UCD-SNMP-MIB::dskTotal.1' => '7850996' #use Data::Dumper; #warn Dumper($snmp); return; } my $cmd = select_cmd(qw(/bin/df /usr/bin/df)); return unless -f $cmd; if ($^O eq 'linux') { $cmd .= ' -P -x iso9660 -x nfs -x smbfs'; } elsif ($^O eq 'solaris') { $cmd .= ' -lk -F ufs'; } elsif ($^O eq 'darwin') { $cmd .= ' -P -T hfs,ufs'; } else { $cmd .= ' -P'; } my %update = (); my %variants = ( '' => '', 'inodes.' => ' -i ', ); for my $variant (keys %variants) { my $variant_cmd = "$cmd $variants{$variant}"; my @data = split(/\n/, `$variant_cmd`); shift @data; my @cols = qw(fs blocks used avail capacity mount unknown); for (@data) { my %data = (); @data{@cols} = split(/\s+/,$_); if ($^O eq 'darwin' || defined $data{unknown}) { @data{@cols} = $_ =~ /^(.+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)%?\s+(.+)\s*$/; } next if ($data{fs} eq 'none' || $data{mount} =~ m#^/dev/#); $data{capacity} =~ s/\%//; (my $ds = $data{mount}) =~ s/[^a-z0-9]/_/ig; $ds =~ s/__+/_/g; # McAfee SCM 4.2 bodge-o-rama fix work around next if $ds =~ /^_var_jails_d_spam_/; $update{"${variant}$ds"} = $data{capacity}; } } return %update; } sub misc_entropy { return if $opt{s}; my $file = '/proc/sys/kernel/random/entropy_avail'; return unless -f $file; my %update = (); open(FH,'<',$file) || die "Unable to open '$file': $!\n"; chomp($update{entropy_avail} = ); close(FH) || die "Unable to close '$file': $!\n"; return %update; } sub net_traffic { return if $opt{s}; return unless -f '/proc/net/dev'; my @keys = (); my %update = (); open(FH,'<','/proc/net/dev') || die "Unable to open '/proc/net/dev': $!\n"; while (local $_ = ) { s/^\s+|\s+$//g; if ((my ($dev,$data) = $_ =~ /^(.+?):\s*(\d+.+)\s*$/) && @keys) { my @values = split(/\s+/,$data); for (my $i = 0; $i < @keys; $i++) { if ($keys[$i] eq 'TXbytes') { $update{"$dev.Transmit"} = $values[$i]; } elsif ($keys[$i] eq 'RXbytes') { $update{"$dev.Receive"} = $values[$i]; } #$update{"$dev.$keys[$i]"} = $values[$i]; } } else { my ($rx,$tx) = (split(/\s*\|\s*/,$_))[1,2]; @keys = (map({"RX$_"} split(/\s+/,$rx)), map{"TX$_"} split(/\s+/,$tx)); } } close(FH) || die "Unable to close '/proc/net/dev': $!\n"; return %update; } sub proc_state { return if $opt{s}; my $cmd = select_cmd(qw(/bin/ps /usr/bin/ps)); my %update = (); my %keys = (); if (-f $cmd && -x $cmd) { if ($^O eq 'freebsd' || $^O eq 'darwin') { $cmd .= ' axo pid,state'; # %keys = (D => 'IO_Wait', R => 'Run', S => 'Sleep', T => 'Stopped', # I => 'Idle', L => 'Lock_Wait', Z => 'Zombie', W => 'Idle_Thread'); %keys = (D => 'IO_Wait', R => 'Run', S => 'Sleep', T => 'Stopped', W => 'Paging', Z => 'Zombie', I => 'Sleep'); } else {#} elsif ($^O =~ /^(linux|solaris)$/) $cmd .= ' -eo pid,s'; %keys = (D => 'IO_Wait', R => 'Run', S => 'Sleep', T => 'Stopped', W => 'Paging', X => 'Dead', Z => 'Zombie'); } my $known_keys = join('',keys %keys); open(PH,'-|',$cmd) || die "Unable to open file handle PH for command '$cmd': $!\n"; while (local $_ = ) { if (my ($pid,$state) = $_ =~ /^\s*(\d+)\s+(\S+)\s*$/) { $state =~ s/[^$known_keys]//g; $update{$keys{$state}||$state}++ if $state; } } close(PH) || die "Unable to close file handle PH for command '$cmd': $!\n"; $update{$_} ||= 0 for values %keys; } else { eval "use Proc::ProcessTable"; die "Please install /bin/ps or Proc::ProcessTable\n" if $@; my $p = new Proc::ProcessTable("cache_ttys" => 1 ); for (@{$p->table}) { $update{$_->{state}}++; } } return %update; } sub cpu_loadavg { my %update = (); if ($opt{s}) { $update{'1min'} = _snmp('.1.3.6.1.4.1.2021.10.1.3.1'); $update{'5min'} = _snmp('.1.3.6.1.4.1.2021.10.1.3.2'); $update{'15min'} = _snmp('.1.3.6.1.4.1.2021.10.1.3.3'); return %update; } my @data = (); if (-f '/proc/loadavg') { open(FH,'<','/proc/loadavg') || die "Unable to open file handle FH for file '/proc/loadavg': $!\n"; my $str = ; close(FH) || die "Unable to close file handle FH for file '/proc/loadavg': $!\n"; @data = split(/\s+/,$str); } else { my $cmd = -f '/usr/bin/uptime' ? '/usr/bin/uptime' : '/bin/uptime'; @data = `$cmd` =~ /[\s:]+([\d\.]+)[,\s]+([\d\.]+)[,\s]+([\d\.]+)\s*$/; } %update = ( "1min" => $data[0], "5min" => $data[1], "15min" => $data[2], ); return %update; } sub _parse_netstat { my $cmd = shift; my $update; my @keys = qw(local_ip local_port remote_ip remote_port); if (exists $update_cache{netstat}) { $update = $update_cache{netstat}; } else { open(PH,'-|',$cmd) || die "Unable to open file handle for command '$cmd': $!\n"; while (local $_ = ) { my %line; if (@line{qw(proto data state)} = $_ =~ /^(tcp[46]?|udp[46]?|raw)\s+(.+)\s+([A-Z_]+)\s*$/) { @line{@keys} = $line{data} =~ /(?:^|[\s\b])([:abcdef0-9\.]+):(\d{1,5})(?:[\s\b]|$)/g; push @{$update}, \%line; } } close(PH) || die "Unable to close file handle PH for command '$cmd': $!\n"; $update_cache{netstat} = $update; } return $update; } sub net_connections_ports { return if $opt{s}; my $cmd = select_cmd(qw(/bin/netstat /usr/bin/netstat /usr/sbin/netstat)); return unless -f $cmd; $cmd .= ' -na 2>&1'; my %update = (); my %listening_ports; for (@{_parse_netstat($cmd)}) { if ($_->{state} =~ /listen/i && defined $_->{local_port}) { $listening_ports{"$_->{proto}:$_->{local_port}"} = 1; $update{"$_->{proto}_$_->{local_port}"} = 0; } } for (@{_parse_netstat($cmd)}) { next if !defined $_->{state} || !defined $_->{remote_port}; $update{"$_->{proto}_$_->{remote_port}"}++ if exists $listening_ports{"$_->{proto}:$_->{remote_port}"}; } return %update; } sub net_connections { return if $opt{s}; my $cmd = select_cmd(qw(/bin/netstat /usr/bin/netstat /usr/sbin/netstat)); return unless -f $cmd; $cmd .= ' -na 2>&1'; my %update = (); for (@{_parse_netstat($cmd)}) { $update{$_->{state}}++ if defined $_->{state}; } return %update; } sub proc_filehandles { return if $opt{s}; return unless -f '/proc/sys/fs/file-nr'; my %update = (); open(FH,'<','/proc/sys/fs/file-nr') || die "Unable to open file handle FH for file '/proc/sys/fs/file-nr': $!\n"; my $str = ; close(FH) || die "Unable to close file handle FH for file '/proc/sys/fs/file-nr': $!\n"; @update{qw(Allocated Free Maximum)} = split(/\s+/,$str); $update{Used} = $update{Allocated} - $update{Free}; return %update; } RRD-Simple-1.44/examples/rrd-server/bin/snmp.sh0000444000076400007640000000321110746154056021177 0ustar nicolawnicolaw#!/bin/bash ############################################################ # # $Id$ # snmp.sh - Simple shell wrapper script for running rrd-client.pl # # Copyright 2007, 2008 Nicola Worthington # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################ # vim:ts=4:sw=4:tw=78 config_file="$1" if test -z "$config_file" then echo "Syntax: snmp.sh " exit fi if ! test -e "$config_file" then echo "Warning: configuration file '$config_file' does not exist!" exit fi if ! test -s "$config_file" then echo "Warning: configuration file '$config_file' is empty!" exit fi egrep -v '^\s*[#;]' "$config_file" | while read host community version port do if test -z "$community" then community="public" fi if test -z "$version" then version="2c" fi if test -z "$port" then port="161" fi if test -n "$host" then temp="/tmp/snmp-$host-$port-$$" echo "Probing '$host' [community=$community, version=$version, port=$port] ..." rrd-client.pl -q -s "$host" -c "$community" -V "$version" -P "$port" > "$temp" cat "$temp" | rrd-server.pl -u "$host" rm -f "$temp" fi done RRD-Simple-1.44/examples/rrd-server/bin/rrd-client-nagios-perfdata.pl0000444000076400007640000000763410746154056025345 0ustar nicolawnicolaw#!/usr/bin/perl -w ############################################################ # # $Id$ # rrd-client-nagios-perfdata.pl - Send Nagios performance data to rrd-server.cgi # # Copyright 2007, 2008 Nicola Worthington # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################ # vim:ts=4:sw=4:tw=78 use 5.6.1; use strict; use warnings; no warnings qw(redefine); use Getopt::Std qw(); use vars qw($VERSION); $VERSION = '1.42' || sprintf('%d', q$Revision: 775 $ =~ /(\d+)/g); $ENV{PATH} = '/bin:/usr/bin'; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; # Get command line options my %opt = (); $Getopt::Std::STANDARD_HELP_VERSION = 1; $Getopt::Std::STANDARD_HELP_VERSION = 1; Getopt::Std::getopts('l:e:T:H:A:s:o:e:p:vh',\%opt); (HELP_MESSAGE() && exit) if defined $opt{h} || defined $opt{'?'}; (VERSION_MESSAGE() && exit) if defined $opt{v}; (HELP_MESSAGE() && exit) unless defined $opt{l} && defined $opt{e} && defined $opt{s} && defined $opt{p}; my $time = $opt{T}; $time ||= time; (my $svc = $opt{s}) =~ s/[^a-zA-Z_0-9]//g; my $type = 'service'; # Build the data my $post; $post .= sprintf("%d.nagios.perfdata.%s.%s.latency %f\n", $time, $type, lc($svc), $opt{l}); $post .= sprintf("%d.nagios.perfdata.%s.%s.execution %f\n", $time, $type, lc($svc), $opt{e}); # HTTP POST the data if asked to print scalar(basic_http('POST',$opt{p},10,$post,$opt{q}))."\n" if $opt{p}; exit; # Display help sub HELP_MESSAGE { print qq{Syntax: rrd-client-nagios-perfdata.pl [OPTIONS] -l %SERVICELATENCY% -e -s -c -p HTTP POST data to the specified URL -q Suppress all warning messages -v Display version information -h Display this help \n}; } # Display version sub VERSION { &VERSION_MESSAGE; } sub VERSION_MESSAGE { print "$0 version $VERSION ".'($Id: rrd-client-nagios-perfdata.pl 775 2006-10-08 18:47:33Z nicolaw $)'."\n"; } # Basic HTTP client if LWP is unavailable sub basic_http { my ($method,$url,$timeout,$data,$quiet) = @_; $method ||= 'GET'; $url ||= 'http://localhost/'; $timeout ||= 5; my ($scheme,$host,$port,$path) = $url =~ m,^(https?://)([\w\d\.\-]+)(?::(\d+))?(.*),i; $scheme ||= 'http://'; $host ||= 'localhost'; $path ||= '/'; $port ||= 80; my $str = ''; eval "use Socket"; return $str if $@; eval { local $SIG{ALRM} = sub { die "TIMEOUT\n" }; alarm $timeout; my $iaddr = inet_aton($host) || die; my $paddr = sockaddr_in($port, $iaddr); my $proto = getprotobyname('tcp'); socket(SOCK, AF_INET(), SOCK_STREAM(), $proto) || die "socket: $!"; connect(SOCK, $paddr) || die "connect: $!"; select(SOCK); $| = 1; select(STDOUT); # Send the HTTP request print SOCK "$method $path HTTP/1.1\n"; print SOCK "Host: $host". ("$port" ne "80" ? ":$port" : '') ."\n"; print SOCK "User-Agent: $0 version $VERSION ".'($Id: rrd-client-nagios-perfdata.pl 775 2006-10-08 18:47:33Z nicolaw $)'."\n"; if ($data && $method eq 'POST') { print SOCK "Content-Length: ". length($data) ."\n"; print SOCK "Content-Type: application/x-www-form-urlencoded\n"; } print SOCK "\n"; print SOCK $data if $data && $method eq 'POST'; my $body = 0; while (local $_ = ) { s/[\n\n]+//g; $str .= $_ if $_ && $body; $body = 1 if /^\s*$/; } close(SOCK); alarm 0; }; warn "Warning [basic_http]: $@" if !$quiet && $@ && $data; return wantarray ? split(/\n/,$str) : "$str"; } 1; RRD-Simple-1.44/examples/rrd-server/README0000444000076400007640000000316010746154056020001 0ustar nicolawnicolawSee cgi-bin/instructions.tmpl for instructions, or http://rrd.me.uk. + rrd-client.pl does NOT have any special Perl requirements and does not need rrdtool or RRD::Simple to be installed. + rrd-server.pl DOES require RRDtool version 1.2.x or higher and RRD::Simple 1.41 or higher to be installed. + rrd-browse.cgi DOES require a number of modules (see list below). Software required: ================== perl-5.8.8.tar.gz rrdtool-1.2.13.tar.gz Modules required: ================= HTML-Template-2.8.tar.gz HTML-Template-Expr-0.07.tar.gz Parse-RecDescent-1.94.tar.gz Config-General-2.31.tar.gz Module-Build-0.2801.tar.gz RRD-Simple-1.41.tar.gz Directories: ============ /home/system/rrd/bin - perl, rrdtool, rrd-server.pl rrd-client.pl /home/system/rrd/cgi-bin - rrd-server.cgi rrd-browse.cgi /home/system/rrd/data - .rrd files /home/system/rrd/etc - graph.defs, create.defs, rrd-httpd.conf /home/system/rrd/graphs - .png and .txt files /home/system/rrd/include - rrd.h /home/system/rrd/lib - perl modules /home/system/rrd/share - rrdtool resources /home/system/rrd/thumbnails - .png thumbnails Notes: ====== # Send from the client to a remote server */5 * * * * sleep 10; /usr/local/bin/rrd-client.pl -p http://rrd.me.uk/cgi-bin/rrd-server.cgi >/dev/null 2>&1 # Pipe client to server */5 * * * * sleep 10; /home/system/rrd/bin/rrd-client.pl | /home/system/rrd/bin/rrd-server.pl -u server1.domain.com ### Send over the network using netcat ##/home/system/rrd/bin/rrd-client.pl | nc -p 4444 server1.domain.com ##nc -l -p 4444 | /home/system/rrd/bin/rrd-server.pl -u server1.domain.com RRD-Simple-1.44/examples/rrd-server/etc/0000777000076400007640000000000010746154056017702 5ustar nicolawnicolawRRD-Simple-1.44/examples/rrd-server/etc/rrd-httpd.conf0000444000076400007640000000105710746154056022456 0ustar nicolawnicolawAlias /rrd/graphs/ "/home/system/rrd/graphs/" AllowOverride None Options None Order allow,deny Allow from all Alias /rrd/thumbnails/ "/home/system/rrd/thumbnails/" AllowOverride None Options None Order allow,deny Allow from all ScriptAlias /cgi-bin/rrd/ "/home/system/rrd/cgi-bin/" AllowOverride None Options None Order allow,deny Allow from all RRD-Simple-1.44/examples/rrd-server/etc/graph.defs0000444000076400007640000002546010746154056021647 0ustar nicolawnicolaw extended_legend Yes Title "IPC::DirQueue [$1]" vertical_label "files" sources "queue data active tmp" source_drawtypes "AREA LINE1 LINE1 LINE1" source_colours "ffcc00 cc9900 ff0000 bb00bb" title "Dew Point at $1" vertical_label "Celsius" title "Humidity at $1" vertical_label "%" title "Temperature at $1" vertical_label "Celsius" title "Visibility at $1" vertical_label "Miles" title "Wind Speed at $1" vertical_label "Miles per Hour" vertical_label "Users" extended_legend Yes title "Shockwave Game Usage" vertical_label "Users" extended_legend Yes title "MiniClip Game Usage" vertical_label "Users" extended_legend Yes title "Cluster Game Usage" title "Electricity Demand" vertical_label "Watts" source_drawtypes "AREA" title "Supply Frequency" vertical_label "Hertz" upper_limit 51 lower_limit 49 rigid extended_legend Yes title "CPU Interrupts" vertical_label "interrupts/sec" source_drawtypes "AREA" source_colors "ff0000" lower_limit 0 rigid extended_legend Yes title "IRQ Interrupts for $1" vertical_label "interrupts/sec" lower_limit 0 rigid title "CPU Utilisation" vertical_label "% percent" upper_limit 100 lower_limit 0 rigid extended_legend Yes sources "System User IO_Wait Idle" source_drawtypes "AREA STACK STACK STACK" source_colors "ff0000 00ff00 0000ff ffffff" # CDEF "mycdef1=User,100,*" # CDEF "mycdef2=User,2,*" # CDEF "mycdef3=User,300,*" # LINE1 "mycdef2\#00ff00:MyCDef2" # PRINT "mycdef2:MIN:mycdef2 min %1.2lf" # PRINT "mycdef2:MAX:mycdef2 max %1.2lf" # PRINT "mycdef2:LAST:mycdef2 last %1.2lf" # GPRINT "mycdef2:MIN: min\:%10.2lf\g" # GPRINT "mycdef2:MAX: max\:%10.2lf\g" # GPRINT "mycdef2:LAST: last\:%10.2lf\l" title "Nagios PerfData: $1" vertical_label "Seconds" sources latency execution source_drawtypes "AREA STACK" source_labels "Latency Execution" title "MySQL Database Replication" vertical_label "sec behind master" source_drawtypes "AREA" source_labels "Seconds" title "MySQL Database Activity" vertical_label "queries/sec" source_drawtypes "AREA" sources "Questions" source_labels "Queries" title "MySQL Database Command Activity" vertical_label "queries/sec" source_drawtypes "AREA STACK STACK STACK STACK" sources "Com_select Com_insert Com_update Com_replace Com_delete" source_labels "SELECT INSERT UPDATE REPLACE DELETE" title "Sendmail Mail Queue" vertical_label "Messages" source_drawtypes "AREA" title "Exim Mail Queue" vertical_label "Messages" source_drawtypes "AREA" title "Postfix Mail Queue" vertical_label "Messages" source_drawtypes "AREA" extended_legend Yes title "MTA Traffic" vertical_label "messages/min" source_drawtypes "AREA STACK STACK STACK STACK STACK" sources "sent received rejected bounced virus spam" source_labels "Sent","Received","Rejected","Bounced", "Virus", "Spam Email" source_colours "000099 009900 AA0000 000000 DDBB00 999999" extended_legend Yes title "Postfix Traffic" vertical_label "messages/min" source_drawtypes "AREA LINE1 AREA AREA" sources "sent received rejected bounced" source_labels "Sent Received Rejected Bounced" source_colours "bbbbff 008800 990000 000000" extended_legend Yes extended_legend Yes Title "CRS Reviews per Hour" vertical_label "reviews/hour" sources "S P H U" source_labels "Spam Phish Ham Unknown" source_drawtypes "AREA STACK LINE1 LINE1" source_colours "ffaaaa cc7777 0000aa bb00bb" extended_legend Yes title "Available Entropy" vertical_label "Entropy" extended_legend Yes sources "entropy_avail" source_labels "Entropy" extended_legend Yes title "IPMI Temperature Probes" vertical_label "Celsius" lower_limit 10 upper_limit 80 rigid extended_legend Yes title "Fan RPM" vertical_label "RPM" extended_legend Yes title "Hard Disk Temperature" vertical_label "Fahrenheit" lower_limit 60 upper_limit 140 rigid extended_legend Yes title "Hard Disk Temperature" vertical_label "Celsius" lower_limit 15 upper_limit 60 rigid extended_legend Yes title "Disk Capacity" line_thickness 2 vertical_label "% used" units_exponent 0 upper_limit 100 lower_limit 0 rigid extended_legend Yes title "Disk Inode Capacity" line_thickness 2 vertical_label "% used" units_exponent 0 upper_limit 100 extended_legend Yes title "Load Average" vertical_label "Load" sources "1min 5min 15min" source_colors "ffbb00 cc0000 0000cc" source_drawtypes "AREA LINE1 LINE1" extended_legend Yes title "File Handles" vertical_label "Handles" sources "Maximum Allocated Used Free" source_drawtypes "LINE2 AREA LINE1 LINE1" extended_legend Yes title "Threads" vertical_label "n" sources "Threads Processes MultiThreadProcs" source_labels "Threads Processes ThreadProcs" source_drawtypes "AREA LINE1 LINE1" source_colours "9999ff 007700 cc0000" extended_legend Yes title "Processes" vertical_label "Processes" sources "Run Sleep IO_Wait Paging Stopped Zombie Dead" source_drawtypes "AREA STACK STACK STACK STACK STACK STACK" title "Apache Log Activity" vertical_label "bytes logged/sec" extended_legend Yes title "CPU Temperature" vertical_label "Celsius" title "Apache Scoreboard" vertical_label "children" extended_legend Yes title "Apache Activity" vertical_label "bytes/hits per sec" sources "BytesPerSec BytesPerReq ReqPerSec" title "Largest Process" base 1024 vertical_label "bytes" source_drawtypes AREA source_colors 0000cc lower_limit 0 rigid title "Memory Usage" base 1024 vertical_label "bytes" sources "Buffers Cached Used Free Total" source_drawtypes "AREA STACK STACK STACK LINE1" source_colors "0000ff 00dddd ffcc00 ffffff ff0000" lower_limit 0 rigid title "Swap Usage" base 1024 vertical_label "bytes" sources "Used Free Total" source_drawtypes "AREA STACK LINE1" source_colours "ffcc00 ffffff ff0000" lower_limit 0 rigid title "Users Logged In" sources "Users Unique" source_drawtypes "AREA LINE1" source_colours "00ee00 0000aa" vertical_label "Users" line_thickness 1 lower_limit 0 rigid title "Server Uptime" vertical_label "Days" line_thickness 2 extended_legend Yes title "Ping: $1" vertical_label "ms / loss %" extended_legend Yes title "Service Connections" vertical_label "Connections" lower_limit 0 rigid extended_legend Yes title "Network Connections" vertical_label "Connections" sources "LISTEN ESTABLISHED TIME_WAIT CLOSED CLOSE_WAIT CLOSING LAST_ACK SYN_RECV SYN_SENT" source_drawtypes "AREA STACK STACK STACK STACK STACK STACK STACK STACK STACK STACK" lower_limit 0 rigid extended_legend Yes title "Swap Activity" sources "Swap_Out Swap_In" source_drawtypes "AREA LINE1" source_colors "00ee00 dd0000" vertical_label "kb/sec" title "Hard Disk I/O: $1" sources "Read Write" source_drawtypes "AREA LINE1" source_colors "00ee00 dd0000" vertical_label "bytes/sec" extended_legend Yes title "Network Traffic: $1" vertical_label "bytes/sec" sources "Transmit Receive" source_drawtypes "AREA LINE" source_colors "00dd00 0000dd" extended_legend Yes title "Network Traffic: $1" vertical_label "bits/sec" sources "ifOutOctets ifInOctets" source_drawtypes "AREA LINE" source_colors "00dd00 0000dd" extended_legend Yes title "Inactive LVS Connections for $1" vertical_label "Connections" extended_legend Yes title "Active LVS Connections for $1" vertical_label "Connections" RRD-Simple-1.44/examples/rrd-server/etc/rrd.me.uk.conf0000444000076400007640000000213310746154056022347 0ustar nicolawnicolaw ServerName rrd.me.uk ServerAlias www.rrd.me.uk gametrust.rrd.me.uk ServerAdmin nicolaw@lilacup.2x4b.com DocumentRoot "/home/nicolaw/webroot/www/rrd.me.uk" ErrorLog logs/rrd.me.uk/error.log LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined CustomLog logs/rrd.me.uk/access.log combined RewriteEngine On RewriteCond %{HTTP_HOST} ^gametrust.rrd.me.uk$ RewriteRule ^/?(index.html)?$ http://gametrust.rrd.me.uk/cgi-bin/rrd-browse.cgi?template=by_host.tmpl&LIKE=gametrust [R,L] RewriteRule ^/?(index.html)?$ /cgi-bin/rrd-browse.cgi [R,L] PerlModule ModPerl::Registry Alias /cgi-bin/ /home/nicolaw/webroot/www/rrd.me.uk/cgi-bin/ Alias /rrd/ /home/nicolaw/webroot/www/rrd.me.uk/ #AllowOverride None Order allow,deny Allow from none Deny from all SetHandler perl-script PerlResponseHandler ModPerl::Registry Options +ExecCGI RRD-Simple-1.44/examples/rrd-server/etc/create.defs0000444000076400007640000000166110746154056022006 0ustar nicolawnicolaw # * means all # - means undef/na # rrdfile ds type min max ^net_traffic_.+ Transmit DERIVE 0 - ^net_traffic_.+ Receive DERIVE 0 - # 10000000000 = 10 gigabit # 1000000000 = 1 gigabit # 100000000 = 100 megabit # rrdfile ds type min max ^switch_traffic$ ifInOctets COUNTER 0 10000000000 ^switch_traffic$ ifOutOctets COUNTER 0 10000000000 ^switch_traffic_port\d+$ ifInOctets COUNTER 0 1000000000 ^switch_traffic_port\d+$ ifOutOctets COUNTER 0 1000000000 # rrdfile ds type min max ^hw_irq_interrupts_cpu\d+$ * DERIVE 0 - # rrdfile ds type min max ^hdd_io_.+ * DERIVE 0 - # rrdfile ds type min max ^apache_status$ ReqPerSec DERIVE 0 - ^apache_status$ BytesPerSec DERIVE 0 - ^apache_logs$ * DERIVE 0 - # rrdfile ds type min max ^db_mysql_activity$ * DERIVE 0 - ^db_mysql_activity_com$ * DERIVE 0 - # rrdfile ds type min max ^mail_postfix_traffic$ * ABSOLUTE 0 - RRD-Simple-1.44/examples/rrd-server/etc/xinetd-rrd-client.conf0000444000076400007640000000050510746154056024077 0ustar nicolawnicolawservice rrd-client { disable = no port = 4444 socket_type = stream wait = no only_from = 127.0.0.0/8 10.0.0.0/8 192.168.0.0/16 172.16.0.0/12 user = root server = /usr/bin/perl server_args = /home/system/rrd/bin/rrd-client.pl log_on_failure += USERID } RRD-Simple-1.44/examples/rrd-server/cgi-bin/0000777000076400007640000000000010746154056020437 5ustar nicolawnicolawRRD-Simple-1.44/examples/rrd-server/cgi-bin/by_graph.tmpl0000444000076400007640000000466510746154056023135 0ustar nicolawnicolaw

Index > > Like

[ DailyDaily | WeeklyWeekly | MonthlyMonthly | AnnualAnnual ]

RRD-Simple-1.44/examples/rrd-server/cgi-bin/rrd-server.cgi0000444000076400007640000001047310746154056023215 0ustar nicolawnicolaw#!/usr/bin/perl -w ############################################################ # # $Id: rrd-server.cgi 693 2006-06-26 19:11:42Z nicolaw $ # rrd-server.cgi - Data gathering CGI script for RRD::Simple # # Copyright 2006, 2007 Nicola Worthington # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################ # vim:ts=4:sw=4:tw=78 # User defined constants use constant BASEDIR => '/home/nicolaw/webroot/www/rrd.me.uk'; ############################################################ use 5.6.1; use warnings; use strict; use Socket; # We'll need to print a header unless we're in MOD_PERL land print "Content-type: plain/text\n\n" unless exists $ENV{MOD_PERL}; my $host; my $param = get_query($ENV{QUERY_STRING}); my $remote_addr = $ENV{REMOTE_ADDR}; # Take the host from the "target" if they know the "secret" if (defined($ENV{RRD_SECRET}) && defined($param->{secret} && defined($param->{target})) && "$ENV{RRD_SECRET}" eq "$param->{secret}") { $host = $param->{target}; } else { # Check for HTTP proxy source addresses for (qw(HTTP_X_FORWARDED_FOR HTTP_VIA HTTP_CLIENT_IP HTTP_PROXY_CONNECTION FORWARDED_FOR X_FORWARDED_FOR X_HTTP_FORWARDED_FOR HTTP_FORWARDED)) { if (defined $ENV{$_} && $ENV{$_} =~ /([\d\.]+)/) { my $ip = $1; if (isIP($ip)) { $remote_addr = $ip; last; } } } # Fail if we can't see who is sending us this data unless ($remote_addr) { print "FAILED - NO REMOTE_ADDR\n"; exit; } $host = ip2host($remote_addr); my $ip = host2ip($host); # Fail if we don't believe they are who their DNS says they are if ("$ip" ne "$remote_addr") { print "FAILED - FORWARD AND REVERSE DNS DO NOT MATCH\n"; exit; } # Custom hostname flanges $host = 'legolas.wd.tfb.net' if $host eq 'bb-87-80-233-47.ukonline.co.uk' || $ip eq '87.80.233.47'; $host = 'pippin.wd.tfb.net' if $host eq '82.153.185.41' || $ip eq '82.153.185.41'; $host = 'pippin.wd.tfb.net' if $host eq '82.153.185.40' || $ip eq '82.153.185.40'; $host = 'isle-of-cats.etla.org' if $ip eq '82.71.23.88'; } # Build a list of valid pairs my @pairs; while (<>) { #warn "$host $_"; next unless /^\d+\.[\w\.\-\_\d]+\s+[\d\.]+\s*$/; push @pairs, $_; } # Don't bother opening a pipe if there's nothing to sent unless (@pairs) { printf("OKAY - %s - no valid pairs\n", $host); } else { # Simply open a handle to the rrd-server.pl and send in the data if (open(PH,'|-', BASEDIR."/bin/rrd-server.pl -u $host")) { print PH $_ for @pairs; close(PH); printf("OKAY - %s - received %d pairs\n", $host, scalar(@pairs)); # Say if we failed the customer :) } else { print "FAILED - UNABLE TO EXECUTE\n"; } } exit; sub get_query { my $str = shift; my $kv = {}; $str =~ tr/&;/&/s; $str =~ s/^[&;]+//, $str =~ s/[&;]+$//; for (split /[&;]/, $str) { my ($k,$v) = split(/=/, $_, 2); next if $k eq ''; $kv->{url_decode($k)} = url_decode($v); } return $kv; } sub url_decode { local $_ = @_ ? shift : $_; defined or return; tr/+/ /; s/%([a-fA-F0-9]{2})/pack "H2", $1/eg; return $_; } sub ip2host { my $ip = shift; my @numbers = split(/\./, $ip); my $ip_number = pack("C4", @numbers); my ($host) = (gethostbyaddr($ip_number, 2))[0]; if (defined $host && $host) { return $host; } else { return $ip; } } sub isIP { return 0 unless defined $_[0]; return 1 if $_[0] =~ /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\. (25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\. (25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\. (25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/x; return 0; } sub resolve { return ip2host(@_) if isIP($_[0]); return host2ip(@_); } sub host2ip { my $host = shift; my @addresses = gethostbyname($host); if (@addresses > 0) { @addresses = map { inet_ntoa($_) } @addresses[4 .. $#addresses]; return wantarray ? @addresses : $addresses[0]; } else { return $host; } } 1; RRD-Simple-1.44/examples/rrd-server/cgi-bin/devel/0000777000076400007640000000000010746154056021536 5ustar nicolawnicolawRRD-Simple-1.44/examples/rrd-server/cgi-bin/devel/error.tmpl0000444000076400007640000000042510746154056023560 0ustar nicolawnicolaw

Index > > Error

There was an error:

RRD-Simple-1.44/examples/rrd-server/cgi-bin/devel/foo.tmpl0000444000076400007640000000046410746154056023215 0ustar nicolawnicolawclick here RRD-Simple-1.44/examples/rrd-server/cgi-bin/devel/export.tmpl0000444000076400007640000000076310746154056023755 0ustar nicolawnicolaw

Index >

RRD-Simple-1.44/examples/rrd-server/cgi-bin/devel/RRDBrowseCommon.pm0000444000076400007640000000564710746154056025064 0ustar nicolawnicolaw############################################################ # # $Id: rrd-common.pm 692 2006-06-26 19:11:14Z nicolaw $ # rrd-common.pm - Common shared module # # Copyright 2007 Nicola Worthington # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################ # vim:ts=4:sw=4:tw=78 package RRDBrowseCommon; use 5.6.1; use warnings; use strict; use Config::General qw(); use vars qw(@ISA @EXPORT @EXPORT_OK); require Exporter; @ISA = qw(Exporter); @EXPORT_OK = qw(slurp by_domain alpha_period list_dir graph_def read_graph_data); @EXPORT = @EXPORT_OK; # Slurp in a file from disk, yum yum sub slurp { my $rtn = $_[0]; if (open(FH,'<',$_[0])) { local $/ = undef; $rtn = ; close(FH); } return $rtn; } # Sort by domain sub by_domain { sub split_domain { local $_ = shift || ''; if (/(.*)\.(\w\w\w+)$/) { return ($2,$1); } elsif (/(.*)\.(\w+\.\w\w)$/) { return ($2,$1); } return ($_,''); } my @A = split_domain($a); my @B = split_domain($b); ($A[0] cmp $B[0]) || ($A[1] cmp $B[1]) } # Sort by time period sub alpha_period { my %order = qw(daily 0 weekly 1 monthly 2 annual 3 3year 4); ($a =~ /^(.+)\-/)[0] cmp ($b =~ /^(.+)\-/)[0] || $order{($a =~ /^.+\-(\w+)\./)[0]} <=> $order{($b =~ /^.+\-(\w+)\./)[0]} } # Return a list of items in a directory sub list_dir { my $dir = shift; opendir(DH,$dir) || die "Unable to open file handle for directory '$dir': $!"; my @items = grep(!/^\./,readdir(DH)); closedir(DH) || die "Unable to close file handle for directory '$dir': $!"; return @items; } # Pull out the most relevent graph definition sub graph_def { my ($gdefs,$graph) = @_; return {} unless defined $graph; my $rtn = {}; for (keys %{$gdefs->{graph}}) { my $graph_key = qr(^$_$); if ($graph =~ /$graph_key/) { $rtn = { %{$gdefs->{graph}->{$_}} }; my ($var) = $graph =~ /_([^_]+)$/; for my $key (keys %{$rtn}) { $rtn->{$key} =~ s/\$1/$var/g; } last; } } return $rtn; } # Read in the graph definition config file sub read_graph_data { my $filename = shift || undef; my %config = (); eval { my $conf = new Config::General( -ConfigFile => $filename, -LowerCaseNames => 1, -UseApacheInclude => 1, -IncludeRelative => 1, -MergeDuplicateBlocks => 1, -AllowMultiOptions => 1, -MergeDuplicateOptions => 1, -AutoTrue => 1, ); %config = $conf->getall; }; warn $@ if $@; return \%config; } 1; RRD-Simple-1.44/examples/rrd-server/cgi-bin/devel/edit_include.tmpl0000444000076400007640000000217210746154056025060 0ustar nicolawnicolaw
Host: boromir.rbsov.tfb.net
File:
Template:
RRD-Simple-1.44/examples/rrd-server/cgi-bin/devel/rrd-export.cgi0000444000076400007640000001675310746154056024336 0ustar nicolawnicolaw#!/usr/bin/perl -w ############################################################ # # $Id: rrd-browse.cgi 692 2006-06-26 19:11:14Z nicolaw $ # rrd-browse.cgi - Graph browser CGI script for RRD::Simple # # Copyright 2006,2007 Nicola Worthington # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################ # vim:ts=4:sw=4:tw=78 # User defined constants use constant BASEDIR => '/home/nicolaw/webroot/www/rrd.me.uk'; use constant RRDURL => ''; use constant CACHE => 1; ############################################################ use 5.8.0; use warnings; use strict; use CGI; use CGI::Carp qw(fatalsToBrowser); use HTML::Template::Expr; use File::Basename qw(basename); use File::Spec::Functions qw(tmpdir catdir catfile); use vars qw(%LIST_CACHE %GRAPH_CACHE); use lib (BASEDIR.'/cgi-bin'); use RRDBrowseCommon qw(slurp by_domain alpha_period list_dir graph_def read_graph_data); # Enable some basic caching if (CACHE) { # Cache calls to list_dir() and graph_def() require Memoize; Memoize::memoize('list_dir', LIST_CACHE => [HASH => \%LIST_CACHE]); Memoize::memoize('graph_def', SCALAR_CACHE => [HASH => \%GRAPH_CACHE]); } # Grab CGI paramaters my $cgi = new CGI; my %q = $cgi->Vars; # cd to the righr location and define directories my %dir = map { ( $_ => BASEDIR."/$_" ) } qw(data etc graphs cgi-bin thumbnails); chdir $dir{'cgi-bin'} || die sprintf("Unable to chdir to '%s': %s", $dir{'cgi-bin'}, $!); # Create the initial %tmpl data hash my %tmpl = %ENV; $tmpl{template} = defined $q{template} && -f $q{template} ? $q{template} : 'export.tmpl'; $tmpl{title} = ucfirst(basename($tmpl{template},'.tmpl')); $tmpl{title} =~ s/[_\-]/ /g; $tmpl{self_url} = $cgi->self_url(-absolute => 1, -query_string => 0, -path_info => 0); $tmpl{rrd_url} = RRDURL; # Generate and send an XLS document if ($q{HOST} && $q{RRD}) { my $xls = ''; eval { $xls = generate_xls(catfile($dir{data},$q{HOST},$q{RRD})); }; if ($@ || !defined($xls) || !length($xls)) { $tmpl{error} = $@; $tmpl{template} = 'error.tmpl'; } else { print $cgi->header( -type => 'application/vnd.ms-excel', -content_disposition => sprintf('attachment; filename=%s', 'filename.xls'), -content_length => length($xls), -cache_control => 'no-cache', -expires => '0', ); print $xls; exit; } } # Go read a bunch of stuff from disk to pump in to %tmpl in a moment my $gdefs = read_graph_data("$dir{etc}/graph.defs"); my @graphs = list_dir($dir{graphs}); my $tmpl_cache = { hosts => [], }; my $html = { last_update => 0, html => '' }; # Build the data for my $host (sort by_domain list_dir($dir{data})) { next unless -d catfile($dir{data},$host); # NEECHI-HACK! next if defined($q{HOST}) && $q{HOST} ne $host; next if defined($q{LIKE}) && $tmpl{template} =~ /^by_host\.[^\.]+$/i && $host !~ /$q{LIKE}/i; (my $node = $host) =~ s/\..*//; (my $domain = $host) =~ s/^.*?\.//; (my $domain2 = $domain) =~ s/[^a-zA-Z0-9\_]/_/g; (my $host2 = $host) =~ s/[^a-zA-Z0-9\_]/_/g; my %host = ( node => $node, host => $host, host2 => $host2, domain => $domain, domain2 => $domain2, ); # Build a hash of potential files that users can slurp() or include # in their output template on a per host basis. for my $file (grep(/\.(?:te?xt|s?html?|xslt?|xml|css|tmpl)$/i, glob("$dir{data}/$host/include*.*"))) { (my $base = basename($file)) =~ s/\./_/g; $host{$base} = $file; } push @{$tmpl_cache->{hosts}}, \%host; } # Merge cache data in $tmpl{hosts} = $tmpl_cache->{hosts}; # Render the output if (exists $q{DEBUG} && $q{DEBUG} eq 'insecure') { require Data::Dumper; $tmpl{DEBUG} = Data::Dumper::Dumper(\%tmpl); } my $template = HTML::Template::Expr->new( filename => $tmpl{template}, associate => $cgi, case_sensitive => 1, loop_context_vars => 1, max_includes => 5, global_vars => 1, die_on_bad_params => 0, functions => { slurp => \&slurp, like => sub { return defined($_[0]) && defined($_[1]) && $_[0] =~ /$_[1]/i ? 1 : 0; }, not => sub { !$_[0]; }, equal_or_like => sub { return 1 if (!defined($_[1]) || !length($_[1])) && (!defined($_[2]) || !length($_[2])); #(warn "$_[0] eq $_[1]\n" && return 1) if defined $_[1] && "$_[0]" eq "$_[1]"; (return 1) if defined $_[1] && "$_[0]" eq "$_[1]"; return 1 if defined $_[2] && "$_[0]" =~ /$_[2]/; return 0; }, }, ); $template->param(\%tmpl); $html->{html} = $template->output(); $html->{last_update} = time; print $cgi->header(-content => 'text/html'), $html->{html}; exit; 1; sub generate_xls { my $rrdfile = shift; return unless defined($rrdfile) && -f $rrdfile; require RRDs; require RRD::Simple; require Spreadsheet::WriteExcel; # Create an RRD object my $rrd = RRD::Simple->new(file => $rrdfile) || die "Unable to instanciate RRD::Simple object for file '$rrdfile'"; my @sources = $rrd->sources; my $info = $rrd->info; # Create a workbook open my $fh, '>', \my $xls or die "Failed to open filehandle: $!"; my $workbook = Spreadsheet::WriteExcel->new($fh); my %labels = ( '300-1' => 'Daily', '300-6' => 'Weekly', '300-24' => 'Monthly', '300-288' => 'Annual', ); # Create the overview worksheet my @sheet; OVERVIEW: { my $sheet = $workbook->add_worksheet('Summary'); $sheet->set_zoom(80); $sheet->freeze_panes(1, 1); my ($row, $col) = (0, 0); my @fields = sort(keys(%{$info->{rra}->[0]})); $sheet->write_row($row, $col, [( '', @fields )] ); for my $rra (@{$info->{rra}}) { $row++; my $label = sprintf('%s %s', (exists $labels{"$info->{step}-$rra->{pdp_per_row}"} ? $labels{"$info->{step}-$rra->{pdp_per_row}"} : rand(999) ), ucfirst(lc($rra->{cf}))); $sheet->write_row($row, $col, [( $label, map { $rra->{$_} } @fields )] ); } push @sheet, $sheet; } # Create the detail worksheets for my $rra (@{$info->{rra}}) { my $label = sprintf('%s %s', (exists $labels{"$info->{step}-$rra->{pdp_per_row}"} ? $labels{"$info->{step}-$rra->{pdp_per_row}"} : rand(999) ), ucfirst(lc($rra->{cf}))); my $sheet = $workbook->add_worksheet($label); $sheet->set_zoom(80); $sheet->freeze_panes(1, 1); my ($row, $col) = (0, 0); my ($start,$step,$names,$data) = RRDs::fetch($rrdfile, $rra->{cf}, '-s', 60*60*24*365*10); $sheet->write_row($row, $col, [( '', @{$names} )] ); for my $line (@{$data}) { $row++; $sheet->write_row($row, $col, [( '', @{$line} )] ); } # 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"; # foreach my $line (@$data) { # print " ", scalar localtime($start), " ($start) "; # $start += $step; # foreach my $val (@$line) { # printf "%12.1f ", $val; # } # print "\n"; # } push @sheet, $sheet; } $workbook->close; return $xls; } RRD-Simple-1.44/examples/rrd-server/cgi-bin/footer.tmpl0000444000076400007640000000162710746154056022633 0ustar nicolawnicolaw RRD-Simple-1.44/examples/rrd-server/cgi-bin/by_host.tmpl0000444000076400007640000000532010746154056022776 0ustar nicolawnicolaw

Index > > Like

[ DailyDaily | WeeklyWeekly | MonthlyMonthly | AnnualAnnual ]

class="stale"> -

No graphs available yet - please wait a few minutes. No thumbnails available yet - please wait a few minutes.
 
RRD-Simple-1.44/examples/rrd-server/cgi-bin/index.tmpl0000444000076400007640000001142610746154056022442 0ustar nicolawnicolaw

Welcome to .

If you would like your server to be monitored by , you will need to download a copy of the data gathering client script (rrd-client.pl). Click here to download a copy of rrd-client.pl. rrd-client.pl currently works best on Linux 2.6 installs or higher, but will does have limited support for Linux 2.4 distributions, BSD, Darwin and Solaris. Cross platform support will improve over time. To get the best results by monitoring as much as possible, you should make sure that you have the following programs installed:

  • /usr/bin/iostat - Monitors hard disk IO
  • /usr/bin/vmstat - Monitors CPU utilisation
  • /usr/bin/sensors - Monitor CPU temperature
  • /usr/bin/hddtemp - Monitor hard disk temperature
Tree-hugging Debian & Ubuntu users can install these using:
sudo apt-get install sysstat hddtemp lm-sensors procps

Clever-clogs Fedora, RedHat & CentOS users can install these using:
yum install sysstat hddtemp lm-sensors procps

Once you have downloaded rrd-client.pl and tested that it generates output, (try running: wget -O - -q http:///rrd-client.pl | perl -w to see if rrd-client.pl will work on your system without actually installing it on disk) you should add it to your crontab to HTTP POST the data to our server run every 4 or 5 minutes. A typical crontab entry might be:

*/4If you are not using a Vixie complaint cron daemon, or which to offset execution another minute, you may wish to use the following instead:
1,6,11,16,21,26,31,36,41,46,51,56
* * * * sleep 10;By delaying the execution of your rrd-client.pl script until 10 seconds after the minute, you will help avoid taking misrepresentative elevated data samples due to spawning cron tasks that happen on the minute. /usr/local/bin/rrd-client.pl -q -p http:///cgi-bin/rrd-server.cgi >/dev/null

To setup your own RRD::Simple Monitoring server, download the latest version of RRD::Simple (version 1.39 or higher), and follow these instructions. You can find the very latest version of the RRD::Simple Monitoring software that is in live use on , here.

If you like this software, why not show your appreciation by sending the author something nice from her Amazon wishlist?

RRD-Simple-1.44/examples/rrd-server/cgi-bin/rrd-browse.cgi0000444000076400007640000003214310746154056023206 0ustar nicolawnicolaw#!/usr/bin/perl ############################################################ # # $Id: rrd-browse.cgi 1096 2008-01-23 19:14:46Z nicolaw $ # rrd-browse.cgi - Graph browser CGI script for RRD::Simple # # Copyright 2006,2007 Nicola Worthington # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################ # vim:ts=4:sw=4:tw=78 # User defined constants use constant BASEDIR => '/home/nicolaw/webroot/www/rrd.me.uk'; use constant RRDURL => ''; # Caching use constant CACHE => 1; use constant DEFAULT_EXPIRES => '60 minutes'; # When is an RRD file regarded as stale? use constant STALE_THRESHOLD => 60*60; # 60 minutes ############################################################ use 5.6.1; use warnings; use strict; use CGI; use CGI::Carp qw(fatalsToBrowser); use HTML::Template::Expr; use File::Basename qw(basename); use Config::General qw(); use File::Spec::Functions qw(tmpdir catdir catfile); use vars qw(%LIST_CACHE %GRAPH_CACHE %SLURP_CACHE $CACHE_ROOT $CACHE $FRESHEN_CACHE %STALERRD_CACHE); # Enable some basic caching. # See notes about $tmpl_cache a little further # down in this code. if (CACHE) { # Cache calls to list_dir() and graph_def() require Memoize; Memoize::memoize('list_dir', LIST_CACHE => [HASH => \%LIST_CACHE]); Memoize::memoize('graph_def', SCALAR_CACHE => [HASH => \%GRAPH_CACHE]); Memoize::memoize('stale_rrd', SCALAR_CACHE => [HASH => \%STALERRD_CACHE]); # This isn't really necessary unless you're viewing the same page many # times over in defail view - i don't think that the extra memory utilisation # is worth the small improvement in rendering time. #Memoize::memoize('slurp', SCALAR_CACHE => [HASH => \%SLURP_CACHE]); # Try some caching on disk unless (defined($CACHE) && ref($CACHE)) { $CACHE_ROOT = catdir(tmpdir(), 'rrd-browse.cgi'); mkdir($CACHE_ROOT,0700) unless -d $CACHE_ROOT; eval { require Cache::File; $CACHE = Cache::File->new( cache_root => $CACHE_ROOT, default_expires => DEFAULT_EXPIRES ); }; warn $@ if $@; }; } # Grab CGI paramaters my $cgi = new CGI; my %q = $cgi->Vars; my $cache_key = $cgi->self_url(-absolute => 1, -query_string => 1, -path_info => 1); # cd to the righr location and define directories my %dir = map { ( $_ => BASEDIR."/$_" ) } qw(data etc graphs cgi-bin thumbnails); chdir $dir{'cgi-bin'} || die sprintf("Unable to chdir to '%s': %s", $dir{'cgi-bin'}, $!); # Create the initial %tmpl data hash my %tmpl = %ENV; $tmpl{template} = defined $q{template} && -f $q{template} ? $q{template} : 'index.tmpl'; $tmpl{PERIOD} = defined $q{PERIOD} && $q{PERIOD} =~ /^(daily|weekly|monthly|annual)$/i ? lc($q{PERIOD}) : 'daily'; $tmpl{title} = ucfirst(basename($tmpl{template},'.tmpl')); $tmpl{title} =~ s/[_\-]/ /g; $tmpl{self_url} = $cgi->self_url(-absolute => 1, -query_string => 0, -path_info => 0); $tmpl{rrd_url} = RRDURL; # Go read a bunch of stuff from disk to pump in to %tmpl in a moment my $gdefs = read_graph_data("$dir{etc}/graph.defs"); my @graphs = list_dir($dir{graphs}); # my @thumbnails = list_dir($dir{thumbnails}); # Not used anywhere # Build up the data in %tmpl by host # The $tmpl_cache structure could be cached in theory, but # the process of thawing actually uses LOTS of memory if # the source structure was quite sizable to start with. For # this reason, I'm *NOT* actually caching this structure # anymore, and am opting to cache the HTML output on a per # URL basis. This means there's less chance of a cache hit, # but it means you don't use 715MB of memory if you have # 100 or so servers with an average of 25 graphs per host. my $tmpl_cache = { graph_tmpl => {}, hosts => [], graphs => [], }; # Pull in the HTML cache (mentioned above) my $html = { last_update => 0, html => '' }; # Check if we should force an update on the cache if ($q{FRESHEN_CACHE}) { $FRESHEN_CACHE = 1 ; } # Check the mtimes of each directory for any modifications # and thereby a requirement to freshen our caches if (!defined($FRESHEN_CACHE) && !$FRESHEN_CACHE) { while (my ($k,$dir) = each %dir) { if (!defined $html->{last_update} || (stat($dir))[9] > $html->{last_update}) { $FRESHEN_CACHE = 1; warn "$k($dir) has been modified since the cache was last updated; forcing an update now\n"; } } } # Output from the cache if possible if (!$FRESHEN_CACHE) { eval { $html = $CACHE->thaw($cache_key); }; warn $@ if $@; if ($html->{html}) { #warn "Using cached version '$cache_key'\n"; $html->{html} =~ s/[ \t][ \t]+/ /g unless $q{DEBUG}; print $cgi->header(-content => 'text/html'), $html->{html}; exit; } } else { %LIST_CACHE = (); %GRAPH_CACHE = (); %STALERRD_CACHE = (); %SLURP_CACHE = (); } ####################################### # # This section of code is REALLY slow and # ineffecient. A basic work around of caching # pages based on the URL has been implemented # to try and avoid having to execute this code # at all. This is a poor work around. I need # to optimise this code. If you have any # patches to help, please send them to # nicolaw@cpan.org. # ####################################### for my $host (sort by_domain list_dir($dir{data})) { my $path = catfile($dir{data},$host); next unless -d $path || (-l $path && -d readlink($path)); # NEECHI-HACK! # This is removing some templating logic from the HTML::Template .tmpl file # themsevles and bringing it in to this loop in order to save a number of # loop cycles and speed up the pre-processing before we render the HTML. next if defined($q{HOST}) && $q{HOST} ne $host; next if defined($q{LIKE}) && $tmpl{template} =~ /^by_host\.[^\.]+$/i && $host !~ /$q{LIKE}/i; (my $node = $host) =~ s/\..*//; (my $domain = $host) =~ s/^.*?\.//; (my $domain2 = $domain) =~ s/[^a-zA-Z0-9\_]/_/g; (my $host2 = $host) =~ s/[^a-zA-Z0-9\_]/_/g; my %host = ( host => $host, host2 => $host2, node => $node, domain => $domain, domain2 => $domain2, ); # Build a hash of potential files that users can slurp() or include # in their output template on a per host basis. for my $file (grep(/\.(?:te?xt|s?html?|xslt?|xml|css|tmpl)$/i, glob("$dir{data}/$host/include*.*"))) { (my $base = basename($file)) =~ s/\./_/g; $host{$base} = $file; } if (!grep(/^$host$/,@graphs)) { $host{no_graphs} = 1; push @{$tmpl_cache->{hosts}}, \%host; } else { my $all_host_rrds_stale = 1; for (qw(thumbnails graphs)) { eval { my @ary = (); for my $img (sort alpha_period grep(/\.(png|jpe?g|gif)$/i,list_dir("$dir{$_}/$host"))) { my ($graph) = ($img =~ /^(.+)\-\w+\.\w+$/); # NEECHI-HACK! # This is another nasty hack that removed some of the logic from the # HTML::Template code by pre-excluding specific data from the template # data and thereby speeding up the rendering of the HTML. next if defined($q{GRAPH}) && $q{GRAPH} ne $graph; next if defined($q{LIKE}) && $tmpl{template} =~ /^by_graph\.[^\.]+$/i && $graph !~ /$q{LIKE}/i; my %hash = ( src => "$tmpl{rrd_url}/$_/$host/$img", period => ($img =~ /.*-(\w+)\.\w+$/), graph => $graph, ); my $gdef = graph_def($gdefs,$hash{graph}); $hash{title} = defined $gdef->{title} ? $gdef->{title} : $hash{graph}; # Is the RRD file that generated this image considered stale? my ($stale, $last_modified) = stale_rrd(catfile($dir{data},$host,"$graph.rrd")); if (defined($stale) && $stale) { $hash{stale} = $last_modified; } else { $all_host_rrds_stale = 0; } # Include the path on disk to the .txt file that is generated by the # output of the RRD::Simple->graph() method while generating the graphs $hash{txt} = catfile($dir{graphs},$host,"$img.txt") if $_ eq 'graphs' && -e catfile($dir{graphs},$host,"$img.txt") && (stat(_))[7] > 5; push @ary, \%hash; # By graph later if ($_ eq 'thumbnails' && defined $hash{graph}) { # && defined $hash{period} && $hash{period} eq 'daily') { my %hash2 = %hash; delete $hash2{title}; $hash2{host} = $host; if (defined $hash{period} && $hash{period} eq 'daily') { $tmpl_cache->{hosts_per_graph}->{$hash{graph}} = 0 unless defined $tmpl_cache->{hosts_per_graph}->{$hash{graph}}; $tmpl_cache->{hosts_per_graph}->{$hash{graph}}++; } push @{$tmpl_cache->{graph_tmpl}->{"$hash{graph}\t$hash{title}"}}, \%hash2; } } $host{$_} = \@ary; }; warn $@ if $@; } if ($all_host_rrds_stale) { $host{stale} = 1; } $host{total_graphs} = grep(/^daily$/, map { $_->{period} } @{$host{graphs}}); push @{$tmpl_cache->{hosts}}, \%host; } } # Merge cache data in $tmpl{hosts} = $tmpl_cache->{hosts}; # Merge by-graph cache data in for (sort keys %{$tmpl_cache->{graph_tmpl}}) { my ($graph,$title) = split(/\t/,$_); push @{$tmpl{graphs}}, { graph => $graph, graph_title => $title, total_hosts => $tmpl_cache->{hosts_per_graph}->{$graph}, thumbnails => $tmpl_cache->{graph_tmpl}->{$_}, }; } # Render the output if (exists $q{DEBUG} && $q{DEBUG} eq 'insecure') { require Data::Dumper; $tmpl{DEBUG} = Data::Dumper::Dumper(\%tmpl); } my $template = HTML::Template::Expr->new( filename => $tmpl{template}, # This caching doesn't work properly with # HTML::Template::Expr #cache => 1, #shared_cache => 1, #file_cache => 1, #file_cache_dir => $CACHE_ROOT, #file_cache_dir_mode => 0700, associate => $cgi, case_sensitive => 1, loop_context_vars => 1, max_includes => 5, global_vars => 1, die_on_bad_params => 0, functions => { slurp => \&slurp, like => sub { return defined($_[0]) && defined($_[1]) && $_[0] =~ /$_[1]/i ? 1 : 0; }, not => sub { return !$_[0]; }, equal_or_like => sub { return 1 if (!defined($_[1]) || !length($_[1])) && (!defined($_[2]) || !length($_[2])); #(warn "$_[0] eq $_[1]\n" && return 1) if defined $_[1] && "$_[0]" eq "$_[1]"; (return 1) if defined $_[1] && "$_[0]" eq "$_[1]"; return 1 if defined $_[2] && "$_[0]" =~ /$_[2]/; return 0; }, }, ); $template->param(\%tmpl); $html->{html} = $template->output(); $html->{html} =~ s/[ \t][ \t]+/ /g unless $q{DEBUG}; $html->{last_update} = time; eval { $CACHE->freeze($cache_key, $html); }; warn $@ if $@; print $cgi->header(-content => 'text/html'), $html->{html}; exit; # Is the RRD file that generated this image considered stale? sub stale_rrd { my $rrd_file = shift; return unless defined $rrd_file && $rrd_file; my $rrd_mtime = (stat($rrd_file))[9]; if (defined(wantarray)) { my $modified = scalar(localtime($rrd_mtime)); if (wantarray) { return (1, $modified) if time - $rrd_mtime >= STALE_THRESHOLD; return (0, $modified); } else { return 1 if time - $rrd_mtime >= STALE_THRESHOLD; return 0; } } return; } # Slurp in a file from disk, yum yum sub slurp { my $rtn = $_[0]; if (open(FH,'<',$_[0])) { local $/ = undef; $rtn = ; close(FH); } return $rtn; } # Sort by domain sub by_domain { sub split_domain { local $_ = shift || ''; if (/(.*)\.(\w\w\w+)$/) { return ($2,$1); } elsif (/(.*)\.(\w+\.\w\w)$/) { return ($2,$1); } return ($_,''); } my @A = split_domain($a); my @B = split_domain($b); ($A[0] cmp $B[0]) || ($A[1] cmp $B[1]) } # Sort by time period sub alpha_period { my %order = qw(daily 0 weekly 1 monthly 2 annual 3 3year 4); ($a =~ /^(.+)\-/)[0] cmp ($b =~ /^(.+)\-/)[0] || $order{($a =~ /^.+\-(\w+)\./)[0]} <=> $order{($b =~ /^.+\-(\w+)\./)[0]} } # Return a list of items in a directory sub list_dir { my $dir = shift; opendir(DH,$dir) || die "Unable to open file handle for directory '$dir': $!"; my @items = grep(!/^\./,readdir(DH)); closedir(DH) || die "Unable to close file handle for directory '$dir': $!"; return @items; } # Pull out the most relevent graph definition sub graph_def { my ($gdefs,$graph) = @_; return {} unless defined $graph; my $rtn = {}; for (keys %{$gdefs->{graph}}) { my $graph_key = qr(^$_$); if ($graph =~ /$graph_key/) { $rtn = { %{$gdefs->{graph}->{$_}} }; my ($var) = $graph =~ /_([^_]+)$/; for my $key (keys %{$rtn}) { $rtn->{$key} =~ s/\$1/$var/g; } last; } } return $rtn; } # Read in the graph definition config file sub read_graph_data { my $filename = shift || undef; my %config = (); eval { my $conf = new Config::General( -ConfigFile => $filename, -LowerCaseNames => 1, -UseApacheInclude => 1, -IncludeRelative => 1, # -DefaultConfig => \%default, -MergeDuplicateBlocks => 1, -AllowMultiOptions => 1, -MergeDuplicateOptions => 1, -AutoTrue => 1, ); %config = $conf->getall; }; warn $@ if $@; return \%config; } 1; RRD-Simple-1.44/examples/rrd-server/cgi-bin/list_graphs.tmpl0000444000076400007640000000054510746154056023652 0ustar nicolawnicolaw

Index >

  • - hosts
RRD-Simple-1.44/examples/rrd-server/cgi-bin/host.tmpl0000444000076400007640000001044210746154056022305 0ustar nicolawnicolaw

Index > By host > > >

[ CombinedCombined ] [ DailyDaily | WeeklyWeekly | MonthlyMonthly | AnnualAnnual ]

[ Overview ] [ DailyDaily | WeeklyWeekly | MonthlyMonthly | AnnualAnnual ]

Note: The data for all of the graphs stored against this host appear to be stale and out of date. You should check that this host is still being polled or is posting data to .
Note: The data in this graph is considered stale because it has not been updated since .
RRD-Simple-1.44/examples/rrd-server/cgi-bin/header.tmpl0000444000076400007640000000201310746154056022553 0ustar nicolawnicolaw RRD::Simple Monitoring: <TMPL_IF HOST><TMPL_VAR HOST><TMPL_ELSE><TMPL_VAR title></TMPL_IF><TMPL_IF GRAPH> > <TMPL_VAR GRAPH><TMPL_ELSE><TMPL_IF LIKE> > Like <TMPL_VAR LIKE></TMPL_IF></TMPL_IF> RRD-Simple-1.44/examples/rrd-server/cgi-bin/instructions.tmpl0000444000076400007640000003135610746154056024103 0ustar nicolawnicolaw

Index >

These instructions will help you install and configure your own RRD::Simple Monitoring server, using rrd-server.pl and rrd-server.cgi. You can find the very latest version of the RRD::Simple Monitoring software that is in live use on , here.

Pre-requisite Software (see pre-built RPMs)

If you are using a recent 5.8.x version of Perl, only the following additional modules are required to run the rrd-browse.cgi web interface:

You can enable the caching functionality of the rrd-browse.cgi web interface (and thereby improve performance for larger installations) by simply installing the following additional modules:

Likewise, if you are running a recent version of Perl, the following modules are required to run the rrd-server.pl script (which is called by rrd-server.cgi):

  • Module::Build
  • RRD::Simple - RRD::Simple requires RRDtool to be installed. See the RRD::Simple INSTALL document for details. You will need to install RRDtool version 1.2.x or higher.

Selecting an Installation Location

Select a location where you want to install the RRD monitoring software. By default, the software is configured to be installed under /home/system/rrd/, / or some other randomly bizarre location. You will most likely want to change this to something more sane like /usr/local/rrd-server/. The directory structure under that installation location should look like the following:

  • ./bin/ - should contain rrd-server.pl and rrd-client.pl
  • ./cgi-bin/ - should contain rrd-server.cgi, rrd-browse.cgi and *.tmpl templates
  • ./etc/ - should contain graph.defs, create.defs and rrd-httpd.conf
  • ./data/ - will contain the .rrd files
  • ./graphs/ - will contain the graph images
  • ./thumbnails/ - will contain the thumbnail images
Create these directories in your install directory, and copy the files in to the relevant directories. Remember to check that you have set execute permissions for the .pl and .cgi scripts.

Once the files are in place, you will need to make a couple of small changes to the following files: ./bin/rrd-server.pl, ./cgi-bin/rrd-server.cgi, ./cgi-bin/rrd-browse.cgi and ./etc/rrd-httpd.conf. You should change any occurrence of /home/system/rrd to reflect your installation directory. In the case of the three .pl and .cgi files, this will be located in one place at the top of the file, and will look something like this:

# User defined constants
use constant BASEDIR => '/home/system/rrd';

Setting up rrd-server.cgi & rrd-browse.cgi

In order for rrd-server.cgi abd rrd-browse.cgi to work, you must allow your web server to execute them as CGI scripts. The ./etc/rrd-httpd.conf file contains a number of configuration directives that will allow your Apache web server to access the CGI scripts under the /cgi-bin/rrd/ URL path on your server. You should either copy and paste the contents of this config file in to your Apache configuration file as you see fit, or, if you are running a Fedora Core or RedHat server, you might want to create a symbolic link to the configuration file like so:

ln -s /home/system/rrd/etc/rrd-httpd.conf /etc/httpd/conf.d/rrd-httpd.conf

Restarting your Apache web server afterwards should make these scripts visible.

Automating Graph & Thumbnail Creation

The rrd-server.cgi and rrd-server.pl scripts only perform data storage functions, and rrd-browse.cgi will only allow you to navigate the existing graphs and thumbnails. None of these scripts will automatically create graphs and thumbnails on their own. To generate the graphs, you will need to run the ./bin/rrd-server.pl script with the -g and -t parameters. This will create the graphs and thumbnails respectively. Add the following to your crontab to generate these on a regular basis:

8,18,28,38,48,58 * * * * /home/system/rrd/bin/rrd-server.pl -gt >/dev/null 2>&1

Pre-requisite RPMs for RedHat Enterprise Linux 4

This is a selection of pre-requisite RPMs that have been built using the cpan2rpm script. They were built under RedHat Enterprise Linux 4 ES i386 with perl 5.8.5. Some of these packages may already be available as part of the base RHEL4 install, and others may also be available from the DAG Wieers website at http://dag.wieers.com/rpm/.

Pre-requisite RPMs for RedHat Enterprise Linux 5

These RPMs were built under RedHat Enterprise Linux 5 ES i386 with perl 5.8.8.

RRD-Simple-1.44/examples/rrd-server/cgi-bin/templates/0000777000076400007640000000000010746154056022435 5ustar nicolawnicolawRRD-Simple-1.44/examples/rrd-server/cgi-bin/templates/theme-red.css0000444000076400007640000000033110746154056025010 0ustar nicolawnicolawh1 { background-color: #ffcccc; border-bottom: 2px solid #ff9999; } h2, h3 { background-color: #ffdddd; } a { color: #770000; } div.footer { background-color: #ffcccc; border-top: 1px solid #ff9999; } RRD-Simple-1.44/examples/rrd-server/cgi-bin/templates/theme-magenta.css0000444000076400007640000000033110746154056025652 0ustar nicolawnicolawh1 { background-color: #ffccff; border-bottom: 2px solid #ff99ff; } h2, h3 { background-color: #ffddff; } a { color: #770077; } div.footer { background-color: #ffccff; border-top: 1px solid #ff99ff; } RRD-Simple-1.44/examples/rrd-server/cgi-bin/templates/theme-yellow.css0000444000076400007640000000033110746154056025551 0ustar nicolawnicolawh1 { background-color: #ffffcc; border-bottom: 2px solid #ffff99; } h2, h3 { background-color: #ffffdd; } a { color: #777700; } div.footer { background-color: #ffffcc; border-top: 1px solid #ffff99; } RRD-Simple-1.44/examples/rrd-server/cgi-bin/templates/ubuntu-include.html0000444000076400007640000000116410746154056026262 0ustar nicolawnicolaw
Ubuntu Feisty Fawn Location:   RedBus Soverign House - Xcelsys Ltd.
O/S: Ubuntu Feisty Fawn 7.04 (i386)
CPU: 1x Dual-Core AMD Opteron(tm) Processor 2216 (virtual)
   
RRD-Simple-1.44/examples/rrd-server/cgi-bin/templates/theme-cyan.css0000444000076400007640000000033110746154056025170 0ustar nicolawnicolawh1 { background-color: #ccffff; border-bottom: 2px solid #99ffff; } h2, h3 { background-color: #ddffff; } a { color: #007777; } div.footer { background-color: #ccffff; border-top: 1px solid #99ffff; } RRD-Simple-1.44/examples/rrd-server/cgi-bin/templates/solaris-include.html0000444000076400007640000000115510746154056026414 0ustar nicolawnicolaw
Solaris 10 Location:   RedBus Soverign House - Xcelsys Ltd.
O/S: Solaris 10 8/07 (i386)
CPU: 1x Dual-Core AMD Opteron(tm) Processor 2216 (virtual)
   
RRD-Simple-1.44/examples/rrd-server/cgi-bin/templates/rhel-include.html0000444000076400007640000000117210746154056025671 0ustar nicolawnicolaw
RedHat Enterprise Linus 5 Location:   RedBus Soverign House - Xcelsys Ltd.
O/S: RedHat Enterprise Linux 5 ES (i386)
CPU: 1x Dual-Core AMD Opteron(tm) Processor 2216 (virtual)
   
RRD-Simple-1.44/examples/rrd-server/cgi-bin/templates/cisco-include.html0000444000076400007640000000115710746154056026042 0ustar nicolawnicolaw
Cisco Catalyst Location:   Technology House: 3rd Floor
O/S: Cisco IOS Software Release 12.2(18)EW
Device: Cisco Catalyst 4510R
   
RRD-Simple-1.44/examples/rrd-server/cgi-bin/templates/theme-ubuntu.css0000444000076400007640000000033110746154056025560 0ustar nicolawnicolawh1 { background-color: #d9bb7a; border-bottom: 2px solid #816647; } h2, h3 { background-color: #fdd99b; } a { color: #986601; } div.footer { background-color: #d9bb7a; border-top: 1px solid #816647; } RRD-Simple-1.44/examples/rrd-server/cgi-bin/templates/theme-green.css0000444000076400007640000000033110746154056025336 0ustar nicolawnicolawh1 { background-color: #ccffcc; border-bottom: 2px solid #99ff99; } h2, h3 { background-color: #ddffdd; } a { color: #007700; } div.footer { background-color: #ccffcc; border-top: 1px solid #99ff99; } RRD-Simple-1.44/examples/rrd-server/cgi-bin/templates/windows-include.html0000444000076400007640000000116610746154056026434 0ustar nicolawnicolaw
Windows Server 2003 Location:   RedBus Soverign House - Xcelsys Ltd.
O/S: Windows Server 2003 (i386)
CPU: 1x Dual-Core AMD Opteron(tm) Processor 2216 (virtual)
   
RRD-Simple-1.44/examples/rrd-server/cgi-bin/templates/netbsd-include.html0000444000076400007640000000115010746154056026212 0ustar nicolawnicolaw
NetBSD Location:   Telecity Bromma, Stockholm
O/S: NetBSD 3.1 (i386)
CPU: 1x Genuine Intel(R) CPU T1300 @ 1.66GHz
   
RRD-Simple-1.44/examples/rrd-server/cgi-bin/list_hosts.tmpl0000444000076400007640000000120210746154056023515 0ustar nicolawnicolaw

Index >

RRD-Simple-1.44/examples/ApacheAccessLogActivity.pl0000444000076400007640000001047410746154056022053 0ustar nicolawnicolaw#!/usr/bin/perl -w ############################################################ # # $Id: ApacheAccessLogActivity.pl 965 2007-03-01 19:11:23Z nicolaw $ # ApacheAccessLogActivity.pl - Example script bundled as part of RRD::Simple # # Copyright 2005,2006 Nicola Worthington # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################ use strict; use integer; use Getopt::Std qw(); use Time::Local qw(timelocal); use RRD::Simple qw(:all); $RRD::Simple::DEFAULT_DSTYPE = 'GAUGE'; my $opt = {}; Getopt::Std::getopts('hvo:r:f:', $opt); help() if exists $opt->{h}; version() if exists $opt->{v}; $opt->{o} ||= '.'; $opt->{r} ||= 'apache.rrd'; $opt->{f} ||= -f '/usr/local/apache/logs/access_log' ? '/usr/local/apache/logs/access_log' : '/var/log/httpd/access_log'; my %graphs = ( responses => [ qw(200 300 400 500) ], requests => [ qw(TotalReq UniqIP) ], filetypes => [ qw(Images HtmlPages OtherFiles) ], ); my @ds = (@{$graphs{responses}},@{$graphs{requests}},@{$graphs{filetypes}}); my %data = ( map { $_ => {} } @ds ); my $fh; if (!key_ready() && -f $opt->{f}) { require IO::File; $fh = IO::File->new("<$opt->{f}") || die "Unable to open file handle for file '$opt->{f}': $!"; } else { require IO::Handle; $fh = new IO::Handle; $fh->fdopen(fileno(STDIN),'r'); } my ($first,$processed) = (0,0); my $last = eval { last_update($opt->{r}) } || 0; seek($fh,-2048,2); <$fh>; my $last_bucket = (timestamp2unixtime(<$fh>) / 300) * 300; seek($fh,0,0); while (<$fh>) { my $time = timestamp2unixtime($_); next unless defined($time); my $bucket = ( $time / 300 ) * 300; next unless $bucket > $last; $first ||= $bucket; $data{TotalReq}->{$bucket}++; my ($ext,$resp) = $_ =~ /(?:\.(\w+?)(?:[\&\?].*)?)? HTTP\/1\.." ([2345])\d\d /; $data{"${resp}00"}->{$bucket}++; $ext ||= ''; if ($ext =~ /^jpe?g|png|tiff?|bmp|gif|img|pcx|pic$/i) { $data{"Images"}->{$bucket}++; } elsif ($ext =~ /^[jmps]?html?|jsp|stm|php[34]?|asp|bml|cgi|pl$/i) { $data{"HtmlPages"}->{$bucket}++; } else { $data{"OtherFiles"}->{$bucket}++; } $processed++; if (!($processed % 1000) || $bucket == $last_bucket) { print "$processed\n"; for (my $t = $first; $t <= $bucket; $t += 300) { my @vals; for my $type (keys %data) { push @vals, ($type,(exists $data{$type}->{$t} ? $data{$type}->{$t} : 0)); } eval { update($opt->{r},$t,@vals) }; } $last = $bucket; $first = 0; %data = ( map { $_ => {} } @ds ); } } for my $graph (keys %graphs) { graph($opt->{r}, destination => $opt->{o}, basename => $graph, sources => [ @{$graphs{$graph}} ], width => 600, title => 'Apache Activity', 'vertical-label' => 'Requests', 'line-thickness' => 1, ); } exit; ############################################## sub version { print '$Id: ApacheAccessLogActivity.pl 965 2007-03-01 19:11:23Z nicolaw $'."\n"; exit; } sub help { print < Specify the input logfile (uses STDIN by default) -r Path and filename or the RRD file to write to -o Output directory where graphs should be created EOH exit; } sub timestamp2unixtime { my %months = (qw(Jan 0 Feb 1 Mar 2 Arp 3 May 4 Jun 5 Jul 6 Aug 7 Sep 8 Oct 9 Nov 10 Dec 11)); if (my ($mday,$mon,$year,$hour,$min,$sec,$offset) = $_[0] =~ m# \[(..)/(...)/(....):(..):(..):(..) (.....)\] #) { my @val = split(/\s/,sprintf('%d %d %d %d %d %d', $sec,$min,$hour,$mday,$months{$mon},($year-1900))); return timelocal(@val); } return undef; } sub key_ready { my ($rin, $nfd) = ('',''); vec($rin, fileno(STDIN), 1) = 1; return $nfd = select($rin,undef,undef,0); } __END__ RRD-Simple-1.44/examples/loadavg.pl0000444000076400007640000000261310746154056017002 0ustar nicolawnicolaw#!/usr/bin/perl -w ############################################################ # # $Id: loadavg.pl 965 2007-03-01 19:11:23Z nicolaw $ # loadavg.pl - Example script bundled as part of RRD::Simple # # Copyright 2005,2006 Nicola Worthington # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################ use strict; use lib qw(../lib); use RRD::Simple; my $rrd = new RRD::Simple; my $rrdfile = 'load.rrd'; my @avg = `uptime` =~ /([\d\.]+)[,\s]+([\d\.]+)[,\s]+([\d\.]+)\s*$/; $rrd->create($rrdfile, map { ($_ => 'GAUGE') } qw(1min 5min 15min)) unless -f $rrdfile; $rrd->update($rrdfile, '1min' => $avg[0], '5min' => $avg[1], '15min' => $avg[2], ); $rrd->graph($rrdfile, sources => [ qw(1min 5min 15min) ], source_colors => [ qw(ffbb00 cc0000 0000cc) ], source_drawtypes => [ qw(AREA LINE1 LINE1) ], vertical_label => 'Load' ); RRD-Simple-1.44/examples/df.pl0000444000076400007640000000355210746154056015761 0ustar nicolawnicolaw#!/usr/bin/perl -w ############################################################ # # $Id: df.pl 965 2007-03-01 19:11:23Z nicolaw $ # df.pl - Example script bundled as part of RRD::Simple # # Copyright 2005,2006 Nicola Worthington # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################ use strict; use lib qw(../lib); use RRD::Simple; my $rrd = new RRD::Simple; my $rrdfile = 'disk-capacity.rrd'; my %capacity; my %labels; my @data = split(/\n/, ($^O =~ /linux/ ? `df -P -x iso9660` : `df -P`)); shift @data; for (@data) { my ($fs,$blocks,$used,$avail,$capacity,$mount) = split(/\s+/,$_); next if ($fs eq 'none' || $mount =~ m#^/dev/#); if (my ($val) = $capacity =~ /(\d+)/) { (my $ds = $mount) =~ s/\//_/g; $labels{$ds} = $mount; $capacity{$ds} = $val; } } $rrd->create($rrdfile, map { ( $_ => 'GAUGE' ) } sort keys %capacity ) unless -f $rrdfile; $rrd->update($rrdfile, %capacity); $rrd->graph($rrdfile, title => 'Disk Capacity', line_thickness => 2, vertical_label => '% used', units_exponent => 0, upper_limit => 100, sources => [ sort keys %capacity ], source_labels => [ map { $labels{$_} } sort keys %labels ], color => [ ('BACK#F5F5FF','SHADEA#C8C8FF','SHADEB#9696BE', 'ARROW#61B51B','GRID#404852','MGRID#67C6DE') ], ); RRD-Simple-1.44/examples/ColloquyBotLogParser.pl0000444000076400007640000000515210746154056021461 0ustar nicolawnicolaw#!/usr/bin/perl -w ############################################################ # # $Id: ColloquyBotLogParser.pl 965 2007-03-01 19:11:23Z nicolaw $ # ColloquyBotLogParser.pl - Example script bundled as part of RRD::Simple # # Copyright 2005,2006 Nicola Worthington # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################ use strict; use integer; use RRD::Simple; use Time::Local; my $rrd = '/home/system/colloquy/botbot/logs/botbot.rrd'; unless (-f $rrd) { RRD::Simple->create($rrd, OBSERVED => 'GAUGE', LIST => 'GAUGE', SHOUT => 'GAUGE' ); } my $hits = {OBSERVED => {}, SHOUT => {}, LIST => {}}; my ($first,$last) = (0,0); my $lastUpdate = RRD::Simple->last($rrd); my %months = (qw(Jan 0 Feb 1 Mar 2 Arp 3 May 4 Jun 5 Jul 6 Aug 7 Sep 8 Oct 9 Nov 10 Dec 11)); $|++; while (<>) { if (my ($wday,$mon,$mday,$hour,$min,$sec,$year,$type) = $_ =~ /^\[(...) (...) (..) (..):(..):(..) (....)\] \[(\S+)/) { $year -= 1900; my @val = split(/\s/,sprintf('%d %d %d %d %d %d', $sec,$min,$hour,$mday,$months{$mon},$year)); my $time = timelocal(@val); next if $lastUpdate >= $time; $first ||= $time; $last = $time; if ($type =~ /^OBSERVED|LIST|SHOUT/) { print "."; $type = 'LIST' if $type =~ /^LIST/; my $period = ($time / 300) * 300; $hits->{$type}->{$period}++; } } } print "\n"; die "No new data" unless $first > $lastUpdate; die "Wasn't anything new in the log files" unless $first && $last; $first = ($first / 300) * 300; $last = ($last / 300) * 300; print join(', ',RRD::Simple->sources($rrd))."\n"; print RRD::Simple->last($rrd)."\n"; for (my $time = $first; $time <= $last; $time += 300) { my @vals; for my $type (keys %{$hits}) { push @vals, ($type,(exists $hits->{$type}->{$time} ? $hits->{$type}->{$time} : 0)); } print "RRD::Simple->update('$rrd',$time,'".join("','",@vals)."');\n"; eval{RRD::Simple->update($rrd,$time,@vals);}; } RRD::Simple->graph($rrd, destination => '/home/system/apache/htdocs/talker', title => 'Talker Activity', width => 600, 'vertical-label' => 'Messages', 'units-exponent' => 0 ); RRD-Simple-1.44/examples/vmstat-cpu-daily.png0000444000076400007640000010243410746154056020743 0ustar nicolawnicolawPNG  IHDRNItEXtSoftwareRRDtool, Tobias Oetiker , http://tobi.oetiker.ch\a IDATx} E/݄E aBؒ/NI(3 _Y$&毬 ""YÖPsI̙s{jf:usN>ݧg㩧2iM1H9r @ʁ}+~FK+m)R@)MZS RH9r @ρTIK:iK9r @ʁ> ΐ{rFiH9HE<tr @ʁ}4RgwIH9r @ρTSOg)RH9rz]wU*^\)zpFiH9Њ@!y9OROEܙHƤ3RH9P>.\hnvyNE<qgbbbr @s'03zd]5'͊kb[*=rЉMF@cmvgWz-جnkj>J;&_|p C=d}~̅^T}>3zd}wrlŶT:s=Vp7͘1cl8?o~{J+UnfֲN3gΜ.$%x;m<࠹Z6lc~_ׅ}afp[v50'|rE?7uT*عⸯO~R2l0;~lƱ6`3`sqǵ2uYf̯hʗﲃk>{T٫+O۾E(l d\csmؖ @J_'?`wi'~yxe]f  {oyw} >G8~_odN6u]:%o}1ry7om~{#8L48Q2^`Yxqlq=sǏEq4c2FIXq{[nOʄmS͑GiXa7$K_b{3(.KMSϽkkx{饗O?3>/n^Q}}C:|ɠq6n^_qׯ_?\c/c '`xgw:pE<<"Q@1bj m֧GlQ~$w]|.m3v ?)ؖA$hwy"o*1_;֬fĈJ5k9r=x▶!<=σ4%mA\ kyPꐷ|%.4?W|>WG-\7c<c_b͝($꫕жmmF~Qo.߷|Rq軶i*⩈;E/VW^Y_+t\=Hq;G_x{+LᶨH1W=K}< A?ʻ[3x+>x[# \1_o).qSh~+q_N'44&Rosntc"^(L xe6\[j\wuLFu] s֌674>>ÝE҇!^{|Mk'+7'Nh?8>GŲG}~8IiCdh|6coݾE1v< 2_W|'\r<7a \sMM|+8}ACcR.f% M]MźR<[R^8\m܎qt|._2N?t3d+;S:cp;u%Yz|Wb~I'UlsNZ1cyxNGAhg >8ꨣiU#8C|7\ ,/V:a56p2X|_'a8gk_ZE?8u>1)}"}׶LW⩸;Õ,^:0;7ԸFƕko |W/YO~5d>Ӫ8"x;*mA$Ӻ~gz[S~nmlgXZAd1>}ӌ<ՑWox*⩈H9r @mzmn Y\§+)RHWu۳gdɞ]v)R|Xr ]Lsg~X 3RH9лr/j/,@(^RWE| `~J=H9r`iYǍgvoIx޺뮻<>Ģͫp\tp)'Ro"xڗ (^9{l˛9s2em6Yxau~ʉ)z"dI<+nnDt6l}f7n<.mz4>}3XZ?TOm)mkkCoRUeE}Rw?EYvk,6ggl Flj,cZI"!,H]I9W|ld/EF-Z']2stٔX26}n<nwpO͠/+NV*WGׁ;gN:̛[AcXl,>U=k#1jdX9}dmh?h$=5Ƶg;lbѫm,㵑s̱{֬YfɶSgq%bFlbcq_,6{b8FbyۈXk#6c}k6C$T <Ǻ[ 5x6o>>q"]U,6# ؆~Ƀy0Xll\ؼ%^b}-cSb6C$b[lpps(;P,bcq8_m؞H=C>7jm/f,{l\Sonzb$K=Xb@cq`CmF{?Z2ROE-;A;vFl".sq HlCDwX85b36el6k1 ޗ_$<*2H_{衇`1_e]frK^qϟ?ߌ;־ o?|/Ƀ (6iaX<9 ?5"^fVA½Xl,bcZƦZ=j`iO:JYݎ_ygyƼ{&u-UE|u1]D'MdfϞm3g4SLQFTu;A,b5;<3qf7t@ve_oO9j3kfl\,S[K\}هv(4#8RUwqG{+VZ2aÆB@^s9ՋcG_H NWAdtlký1/X\#6cZƦ`gZG=Z_dNJ_[b}2.~(J^tb-BV:[;4 2m0aYpgСN>ӗ+mݼkqɊp^,V i^*@dtlk; )emjlY /hd}EJڔcq"zu٤ M%N椖s%5b+7fM6ik>74ml4\;gN:{,w-Eaz4>h#6zZ᯶I_HC6lOl9{Ęg/Ed܊p*1y[+rZK8cq-cSoyj$G󋾹(/[':l^E34 0m+bgv^xںk'N4s̱Yfɓ'}Eg̘"\wKUN,V iB81Y`u6jSs&%zqYltŹ|-նcqkܞS눝'qZ'5bRu_#FXߢkEjvook>1cXqvڼXhAVC򸌯>|Vϑ"X/!F1qӱe52=E/9?b)Oe~#q"mYzW J')j~Q?ۢ:8p}&αNđ,?]&9y ~_}"#E8F6 ifES,oZbGy=E/eA1&Q'+q">ߋp?C'e|mb}>~(=]xO;ړ: |[|X)—IJ,)ۍ,9O/1XYY/!}q(cGŸ)uu|z>E8ʻy[+,:H]8Ƅ2>d%?6Qd{NuԃzMoŶUꗿnw*,S%ر>q=&/h*QM_=1+֡bڞY;Z_/eq._;`Ջ ǘhYwaW*':J=i}R&7bIM/^{-u_nmӮ"K w_RJ.6K.ד;mBJ":K V, mγo8mj~~Yi)ymj_}q8a9OӅɖjW?6W^4.qY,ur.Jk, ŶY E[zF_We[g[&N=" 7xx-CEmf5G@)ӯ_+fKI仨.6KlH mCX=߿MnKZڦU"",)p>2X-篴(>A<(XʓG䗡Ė2ضv5Jn*LRaeLr28iGC%tEXa8G-dXR=ҐTk#F\QV筶}YqyV.>2X-\J>ߋpA<(XʓG䗡Ė2t?c͍7޸TTeE;I}DZO=R]4ڨWl ۮXVĉm% ", Yp>YCۅ2~q:l|/\b)OJu_[FVʄb۬"rK5H}8ۦ6eM)#ǀ-jsxڔPօ嘤z>q"㴫RWyKp[#2șMQ\:]8lqum1s+m|/QR8$ % Ei{u7 ftFqL޵5^6;3mȱO<{XHƈ-G{Z^{땸w=O%'Ƕ 1IO$|I}+tU#ئ]MHJycG5rEXSt|eZ/W寴GKeAJu_[FVʄb۬zw=7ި'<]/D]ȝ'Nxs[rrʵ4HCZbTr!;r NŶ 1I|#_Rt_ Ѕe3OyrcG5rEXSt|eZ}2qu\FvYٞ䑆pܥXmS'eommjK/dov:ntG_E첋9rU|- bGW]]ہIX V(B8M>,CyĂxJ2mnҐT0Vlc%_R>x\BDHJ.Zv%8siGa]w|Ҟeؗ>"ۅuSeۥ,:HN%+e˴CmVͺ+{&~V&Kwt|: P_--ECvY'/];iY,i;wbS(WqMұ_b Vno+7+tH: jYuٔ>IYWxai '[eؗ>< I.eR:8ݥ PlUpRVw.;S;S}s饗vie<|}5FʾMDؙ?;v}1ה/-Eܕ<->,qZGRH)>(ShcGݤsѥ3:F:#8qK '4t@rlC|$IuiG]MAۅZV]X-þL\,eiSbv(ZJT{2qU-n9N]زtR⨓O 8(4X/V֒FO?-?H\ IDAT]8-XN)Ftw4{キm6l݉6Ã6Rę,EA90᤼nr}6/Z|XđR}Pb%O(CuȾB ,Wؑ6 a\:}jS 顯+۴I1ȗT_/:oS˦>p8}vKYڔ]81컰k?*W͐!ե,VA8Nr}P2Pl{NEz߿1c-7|}jyӧO}ϭ(\`줂^X.///Z|XđR}Pb%O(CuȾÃO_CrҎl 9NiS 鑺6mr8%׋:\6TwJ|(O}ı9lW5p|UE;/1X} q\Ǖ|I}iSʲM;.lG#[pH/^?(7ٙimFÕ9e$]}N9VSmA|3v 6\q0o9?jP^8C^H#]68>SG53X)uO}$%?smK8keH)Gc >m\֥{<.Ҧң}Xq}޼NM$r>J;r܇,mC.e6'_S=٦MFۢ\G:b>ל8&+eڡآ`E=޶ \/POǛ+c`wĉfΜ9=k,3y`py%=,L-\bR&x$\hC\mMڦ>q'^RʣOb)q )\;\ cc\:M#Fd\X9FBF7o f>I|vKYv(+ b):g6,۴JG?Z|@X*C 1}eG5r>,eipr mR^u\)'6mJYiDž8xHR %.Ǥ>|=tPlԪ22[#J|7_]p?x ; %?z̄")ǘz,GԤ <բOW)yGYI5G=QzI)>eJ52sF?Qݥǃ.e:Ę<)u)Lj#_R#T带P4qRz!z}=J9I}iSʲM;.ĹC:~8G!8Fp Ŷk]to-W\Ѯ.uOO.֞ccbF%%|&A Hɫ_XR_~7KE8mRKmSc4G_H)碾';[/Mʔ#u+yhSq<%F6u.9s8%2q,IQMJ99BvªM`$ޅRNbFy5:Z}mӎ +qƍ=Q>Di3Ǥ|]8bbqE^{m)ٗ+;7Lp,Uv=EWh8):ǁC˴;:6)OJSXʸq\(CJ9Ŗ>t!-e|Pڔ<5OKHPeA^"eF坣تDmvʓR}ʓ_4Gi>E'1<&P% }W2^d|\wW->1z/eA%5NYvqOMmO*؊.mywR(?;Æ>)e2YBX m厇qqɓx}XQ~x'G!*C\8Hþ/[ 62q^OT=q 5.#)AQOJh"Z}ca|(OJqkC >)m6R@y)U8NıM .9F *q?એu E _!'"V[- J\&%rǻI8ʅ(+4RF;ƥo'}R>p#klaL/'R|-+ev ġZ|u|diXɣnRv<)~K9HJݴ)|m#D8)Ʊ0>'oWȅ=R>ĸ ^Kȣk}:HJ'ƈ"q'%.=Y9z+_ :¾PlL?S_nv]ędm-Rd}ƧxA>q\.rԥqLRʑ|ԲAaL(/>zC}b qb(iKYH1iSGq\Sca|(O1IB_QbARpCc]2q8WcI]r!T8N*\:z>E;{|]~ޫŶY_6@_yg>`Oڬ Kϛo=[}MF+uԨ~۷-ꆧnhG6!fn_ߔ;EyJ%zH}Xs$M-K.{t#%V>gN#)ur'mp}=OCYMCXRFRM9kS邹yKYe|HWs]xVʢMׅ瞫1c9mp8WcI]r!T0h;\:0ONզMNsL嘦%8Pl_|E:h {O?Fߥ3ceZy%3;Myև)/e}WRN[bW 1ByRmr>plKJz?ل. ^]Z:$V.)u]wi6}CYM%W)I~jX->Ʊ0>'ۤqk3B,(VRX΅PqL5Kԉı-uqbsa.Rnﳏ5Zgd[SȒGi(ͪwsOkxz|E,5}C(ecg\d<)cK.}Tyr X)Ϲ681Tb}iSڶ> 8b8&y@>)I<'G ΅>PY(q8 65G >RF<āR 8PQiWɓmb$0Vʢmisƌ\E㠔a:qO/R^I%9Q1Γ2#qPHn_c:d[ ŶS/> ag/^cTij†Gb$`2(e cKQ=d?yOG./vl.[<āteR ,mKcK}Tyr X)T/zXhXGXHJ>m1_~wX8iq,2>pLQW'd8bJfAw)E]dVYeO;|3vX]wݵͺL M!!BY_ݭX4N8wX9fyta;KNd`C=m1_m> 9.7Vu.2>Āb}򑪯kC,`\ڕ8Tc(}L>e2:H*%_+'chKe@<,İ q6Ʊ1'/GU}e_Serg'ڔ8Clsrԫ奌+1RϷEK(IGP6킆b۬:7Zt'>oŷn;SOOmO?ݞX">l0\6|KCgbկ_I)Y9>^?w r.y8 ,y/_qE~4|,WZօg{9N$Gyr X)/Iɟ<;饜6>s1_"pL#Ʋ96{36ҮđO1>&0ES/q)OQ/y#oeş2r|9yJY&СC-閲ui,LLAJ&-yrb_ʱz@#}˃>|eR2"3-8M!U^=Q7G.1X<5FuXMSFRM䘯M |q Wb8&O_Tcه qn-On.v]բOǵ"(.0,l`A[nq|d5z!|c_ꄬ6<%6ICՕFMoԡf74@.❝>޶ch2w_IxVSYe'i)6.ym;?jWYSf ,'RmMz@9N55.K9G-$Jl[iaae.<5Fe/TcUiz吔G߿*[D>g:pL#Ʋ9.J9-up6ICE z3qƱH_쓗jɓTcه YF^ڕ8WS&_wcWʓR%ؘ1?H#NSQ)ɾız\AN#/cL(K*eh%KYUo]d\e|T)' x\ؖ1'xʾ>ʼn\86!%19}L[ЁLkq%FRI]q I(\cLZOhk}-۷p ||#r<&GYRʑOqΏTnWl)i( pۂ~q,Wb8&(4@5}&\89E3.yp52=Xh)oEg KlhV}3h{)ylK|5)xXyb,J47|shtM#[lODxϽaRWbHkYX%O k]cYyJ;y|8;ƈh2PϢ(uTHitg[cM=rȇ!D8>yロ뢯ye)QwQq?Ar! Ljex#PG'>1Se-cK~.c#us| [`>υlti;c9O)ډ.⫯z+VZɶ[^I"D&rmmFlwqZ8퓖(_ږm#׾>1_pAA]cM=דCZc)U_9qP+1}[u[õh,M㸋RX$ a8F,ۇI8Uz!8xlKJ>(ǜ|ԯCb}ɇ,۠)yeܞ.ym},e,R}\?^C*y4i;8 y)KJ^Yʝ<9צk$oqA]t,gQ[#}zoutj/CM9O!'KԺ}zrH|H1J_)O9(1K;.qX]cE<!G9_Jy!}nM.ٖ6%F6te'}OE{ bBԗC51|K;6?oVMW޺O;WT4[A?cҒ0:ቕԇ2~#e$NcOEgĊQǭLDe?}`IM_!'Ɏ>->hm.wQ|4ljXT{Ev隘xpT2$* ppR I]hUFⴼ=YĿ%+ IoyR:@:j]5}m%0Ɗx(xBKsG]MgcYF]O]NnTė"[wf/Qg[*SWE/Q>|MQ~owZؑYG8fn"r_1! )m#>|ZVGl-N`x8 IDAT=O[]Jc;&ls8]Ԙ2Xmӥn/[#mZgUۼy62<y2X)¾rx@_]Tc(x^6@ɗ896ٗcyp 'ڡvd9>W@_4#[/!eO{RWEA0^󬿈 ?|Aa\8?$k;8dxO^6l빐^ %/yڦ %.$Õ=~MP/)dhfԝx*uLd&oTK3plVEOJUa\rG9O)6}64_XQ?69xVeӉBGۈc!gGu y..,wa9FJ]R 'CPJnO|м;i;xA׿JXlbϼ]xdbb+}AGYla8lkYIqʓj_!υXy(}6B> &ETdx_*x"O灖 ]XJ6r9QyWi$} uI 'CPJڮ,KжxAƩ&V,"^ݙbqmK-e"n/ɹʃ/VRm#,){ĜvZk^ڃ\}% 2b%N+va"豲dv8W|~n._!Ņ>,R2>6>Ү,R7dZY]8>J/lt|+=>פX8x;e[oK;}wwCl{/ӓth)v؇  @9g=6/W$c8t+ }}% m?%#N+vaa뼝 }{niMP(}ыOVA-%|Gl m2ZOoϙW\Ѿ_\S]/a ->{l[gΜ]Geˆ+]Nj^z%ꫯ X64}l4Y)#i*KG_p0|\;9y=Qa-H8}8h_j#5cET9)"`.IeS>х+c26>.[hxZHXu}%wXqELӧ7q`L#4?*> :Զ'>ݩ&L|@XbwX\N ?cpr=Uz{#́N/.9OtɸxBE\~W눵4\w6E\Q7ևc>J[lI`JYbA}6hz᧯Sښ8nG*vE}oNTϘ1L6|7FÕơ8Nt:@'mq8׻bcqoȫzlr~ xփ1/g N[ckUq_[ϓqKǹH9NʗPI~"Y=Ul|3|b|q gePCL<~&>o<$^gZ-Ͻ'Nh:i 9sXެYɓkƥ,JLWm%,8bk N;gW>'}mwÐ/ĕ ^J+!qи0x6]WچOON2 w% YB>I]M-\XH9+OדF~yW]KQo୰ 첋k*ĭ1co7.};]1;_  Y?1N_4a|| uJ䕡8NElzTq9FJ<|PKNd1AG,6}߮ئ"߂yx|_ꝩ(v:xv\*A,kx7?a*c˓&28)o1C,#cqq Iڂ06zvٌ"wc V͏3{ݨWk/+c[bDaC8Xl*;h?]w&?iNXl,6+&;8( fYĖXG"^i*r8eQTT!NK|RP||PBlME<n;yI|#il"CG,6.ضwmj3=^ğ됿ؖ[>35"+\J͎ږ l67NwP+W(/t@Ag,6Bӆ"+;"O9yt?A}Vb8KyAceکo`;nn56|ϙC˵}K|a#RX|cK~ME`Jج3{+{2b=~Rے6i'W<766gG qC[׌ϸb7h3kq>ӨM2/HSȶ"8^mxaKQ;rxs;;,6b3\b8جĶ]s ]Wp1 s(t%xks͜El豠̀co>qC[]T[Y=7&tKwvph^%6Ţ}}Ͳx`|dQi{C<iI뛗'5L2׿e{2L2~.#!eqEST9d54&.<8{!{D q|mð|;[SɍB7c6Ͽ-ۢhb7OJˉvJ8HZ}ڡC`co즟8Rدn>~1T٤}oA·k=J^>{]s6ςjw[\gg]j1ٺme+mEVS)L$\_&-Ijw۵Fs>([Idi#ӳ9B;U_}; }] :zX*^;0mNE'mڹ4X2^{K^y\Y;e`0+]pZu%ͻb ǷiF#5]q'|IB^|2?w v7-kMyuSs7ȣ"1ؤ,KOn!>ۏ>c)6[TĺE[P盱cڷ뮅o1[{FJ;`j>7N4- {0o5Nz6*0v%kEv߳HUuKL~寝 ?|mԘI>)dd}2UW-Z A9(J}]?:&?m..|n=]x?w)}ݰƚ,_bHl@⫌C?VpLqo3*nGwUbe&}֙+!sOM "6Ldyw q.ZQW!91I|( ker|^nصfrjpXQ/7dSSuR#6X+6AqLOȿ3'f5Xe.ي?Y?瓵} VmCQć f'%ÇA7`vx~08fyakv-cVv=~Փ ֫s剒~ϳyVyQ -_1Y~`>zf_lRL;kyp.fD_^v]_̵,?>g~Oi﮽k|;00]́mntd;̩346Yh6|o v\wD5]1዆r8wa>lzg[c8`x`=+e-15Ȋ,{w VqN7"_3~mqo1#`6}}͐q>.yuͭr2 ]/W:c͍ߵK8o{R?cE!n VnFgkl>fҗ?h[1 }nELVev(}|uvϛY_59.?d;̃^O;w^rrx&ߓ,>VerXG]mYKW߯ܬ_G=Y3/_)_&ˮ :qr>PmOv2'ي_?ӌPR׉7lellb3k3/6+܇Lv3dׅ"vovw#OR}AX=rNȼظ f:I#W:NmO ֻ0~=t}ĝ b 469AO7Ĭ1mۧϠ?z4;߳u>yZhu9;t'NCv.;ggW{n.P1,N:?Bk+(NJoY_فKm`N>?`zе[u]kw]ˮCGfIWuRv1ԙfßdm>e:|g9kG~< 7MaW+)ldn8î|eviXQIʼY#Lft0k_u>iUC?>,a?RuG4/A\eM^׳0Xmp+oj~kphhvL>\ ,k'fy v:;X˙-4N4'0aC̩vYǣۛje.9v޷[>-5Q]}CQ;߶W2Qpq>iۨQ jN:`K.ZO` V+u'yeۯƇ8]8~;lrA lj3:̙/W}m,Z~ܠM+ӯ{acnܸoWh+sm~mbۚئ&2mmto"ɖ}/~ꩨ+q0˥ضbۚئ&8ئ"m]s4OJ1I1I9r @r qGO پLOO9r @HE<nw"T~JJJ9r9x*⩈H9r @́T[ʞTvnې!C e 7Ь5]vr-zWԌ1t$o]v1X[n9KѦ2qlt}|3w}ͨy e!dzBjXOw<#va[뮻.S?ձ+ضXx8D?|eA1X%fbO-:^{ͮh=c/Szް㛻}zzoswSNꊡxTׯW{GOî/~>~ŶT[\đs5= IDAT9sgm.r{z' ΚwqG3zh3o<˿ͦnj&dO4({4iIuB/vX9J5z^{>l1rzް7x`3uT382W)+>}s'ŶI/m{ͯ[Zbʽf,secӁ R%Ķm6,aZA/ۧ}EWs;^!#G}V^yĺ;IFmdm/\9o>SgTk7?ygoa?RV}s VLJ83fCci+Ǝ9m˫oȔK'/>-7xzH>~HWOy6j)o>Ƴk7/-nM.+-3]Z⊹q'͊m;g]vYҎk~M;{jB|738gƺ[JЃvac2=c'5ɓ:࣍O<ў\}}|M]sW_Vlq/Xk ,;G?Q{֊St-~6'쳙mWcHr>?UpZ4SO=yܚki;8{wd3_}M yBL#+}=1<.4V7fAx$c[o]铯i=8o:w/ma?xl_8b.G}[>Ɗxۋ8&78zePh͛gy_W)bHhhcJyzGc=x[(xlMeJzpomFyp1.Wԧ>e r%+y!\hLhV;my?-洍+vU'/x@ʶuu]M]'O9?WNznyf\ mFeYs)=1s@=]XOͩ^Q(^y+0SN7*cpl皮 d_fe*W' IyIeL?{s z.'f}n8),Wq.V'uK$뮳1c eׇvsNBq-t}{߳qf}ma3}n._|~4{Gͱ1k8^{23yM7d:kr-W%O+mc!KFv.4֊ph [ÀxC6!%/O~~ ?[s]}\+^4> ;6|ж9qerB}㊏^BȄƤn{7&o/9ꨣl!78X3>^ÇW(+/+jLM/Q=\2}3| /M7.4&u7m)t!UH IycC䛻 m]Bw`R7nf77/b.EoTlm#CKJ}(364"FmdςFmp5.'p/[b>΅'LDb؞4iC~/v#~:Ƹ4%/Q=98QW_E?ss Bc͎Ӕv>l/$㦓O>R%>+ '/lbH^{m3tPXK/d/p7Be]^av)|:lذm0fϩUB9Qf~(m՜mZGNٳ:˜tIH>NUUʁ)n4* p+ i&pviۥH9a΁^_?'=RH9r gR|RH9r@*⩈)R9xp0XRH9s9д"ޱ~[Wu?%CO멟s.4֛4r9طʘۅ^hF|)oH,3g2</S=<{LzYǼ mP.'7qlzyE<{v3]uHaz̰ç펗&1of馛> +Tu 7؃/eC42Cd'l!]kq:1fm~_.i t1Ǵ|ժ}{)+*x)͓aZ"iJ?Y?ZPEhab`AQ JBSJk'g9sf޵kgaZ3;ygZ{"M5f:.SS{{LѣM" 1Yd1=96tGG= -Lemmmb9E #Y?~y.`6@?ϛ:1Ki.D' -9İ5aOQylb}`g?oͷء%6iU3|fРAyϞ=6jǴzZ^=#>SUXĮXuYM䚖ʰDCLPavMNyd1>γkyu|Kr4 Ⱦ͔x /4(\wu&c扱25O]$O*:ޓ숥_]7oTZ(:?Sv8ݻ.1 슨yf3uԬ}ѣ,fq1kXoI{_Z=-l~~*ĘZ 'Md?`~sÇg֭[K$Y#6[I1:tȶ qYEF)Y̊rKyXB=oٲ sJ d1_fh+b @iANO}E @k`AԒ L1@ qqNbb xKqHC @`g  b Q nĖ;SÆ I <={Ɂ_cc*B+W/S_Ϋ9xT-{V+ٵQ}rs1'Nj#oܾ4ڷd1 ?jLe&\_Lj)!p e08Qt1N~LJ2=~ȏ|ЬXB-]w.S )![i2z28{extB3H0r`>hL@R^s'!d1ā)lb#+7ԯ{VQFmq_L2ŀ;ammA{n㎌kANq }XFo.]]h;p- ɔ2Zie*0QDPqMD*ӒA)5I5#f&S cZ$O4C*}pS :J/O: S; .zJyI}ƣZٳDnT榮:Xʰ"7~xK*Im5c`g{#ϛ7/4ҷKmob1 10!vҐ0kt^SqYe}\lYpپ}{FYr8F˓2͐~&yڵP|jǂZ]#y[Tʞ%?E<|&}wc؋]&`,fW`"Le.&ĞZ*VńV': |؆zĈ[o5 tj;vd7z 2|]B-ZȌ5͡CO 0TtSK9Cפ| iOBM] {VvTڈXG<cb>tPsmeyycb3R|+󐉟cFred152 Ky |&u-J d1 Zº%1@ @ ċ42%1@ 1PO-P1@ @`g$ b Q 0'gԙO:1@ qqb @iA{G䚻}?˹3zH{b[[+='fk<^DcqLc?ko51DGoNËCZLW~#K :T6T&t-Tת0[nղiVaO Xjaz·!8|n5ޠ|%x)*գQMAtŋ ﮆMbMf7FujވvpÒŬĥmWzZ^=|VcjujǰK[[[6F4ڷP|zbHl&ȑ#-㥜JRTfz^o ܹ\ "uĉ;TxK-c Y .(c|ɒ%vQ'[ѴzZ&<ͷYlC(D[ }qLG5ڷP|,ffH1c2y"xTfz^k 4)(hl'4ƎkENoHepS 6<Fx[g2ЇZM]ƘDVe1CabgH|+|,f6H g1 K2CZSq-if-:Kv-T-qˣZ=-),f&6/&eY}+Ƶ<߂s53g6 ^RGҏ_~ 0൑\"xT^:  ׬YI< Mٴiq׮][/C7Zܰ~!yڵPȕլPiƍ˗/DXzZ-+)f"c1jg`$4iEaÆu4Ƙ]DfH5A 6Xː&3NO\k eo,k.^;q g!3f0XJ߰$mcsos#/矇ʈTҼ i"ӛYfb+I5FVAbEw@ƍc)vWcvz͐j>~߲eK߱E 6qu,N 1@ ҂8Қ_Wb  ⧖#Pڀ b E 03sC 1(u\3F'b 88g1@ bAP=>czn&Olrkw-?4s7n1bz~EkP2 k=ek~JmZߕ֩Em۶xLL8 s1;l߾%EK_}U?z=kq-v =jD 6X-[fy{w}<C^8xM@O?Sov0{2}s5dz^YʵߠC 1-y~Y9/pq|y˗`եPǕ_D_&TON&ln">OСCveɱ&3򤌖zbrg'OZ}]sWc-tvvvn^8xi`[o?#[J}~m;pn}9 5~YsرövZ`ML&viW^qێ\C:qDb=)4TOoX]^y;I kǽΑ?tqeƎ}(k~2:.]j$_D={ؠ4x`3l0֍s{fŊ^=5Ď]=[3p@{o.Y9r\r%Zo>lԎWx XyO1Ḩj6@kfԋwx_{XL檫2~ys}dK9I9:~S2Q~. v&)M؊'2IC^IDAT9眓~y~y1]i ul7o^ʻQi7=\5!]kf=z\tEm5nېYÇ' '/usr/sbin/hddtemp -q /dev/hd? /dev/sd?'; my %update = (); open(PH,'-|',HDDTEMP) || die $!; while (local $_ = ) { if (my ($dev,$temp) = $_ =~ m,^/dev/([a-z]+):\s+.+?:\s+(\d+)..?C,) { $update{$dev} = $temp; } } close(PH) || warn $!; my $rrd = new RRD::Simple; $rrd->update(%update); $rrd->graph( vertical_label => 'Celsius', line_thickness => 2, sources => [ sort $rrd->sources ], extended_legend => 1, ); RRD-Simple-1.44/examples/foo.pl0000444000076400007640000000140210746154056016143 0ustar nicolawnicolaw#!/bin/env perl use strict; use RRD::Simple 1.44; my $rrd = RRD::Simple->new( file => "$ENV{HOME}/webroot/www/rrd.me.uk/data/boromir.rbsov.tfb.net/mem_usage.rrd" ); $rrd->graph( periods => [ qw( month weekly ) ], destination => "$ENV{HOME}/webroot/www/bb-207-42-158-85.fallbr.tfb.net/D:/", title => "Memory Utilisation", base => 1024, vertical_label => "bytes", sources => [ qw(Total Used) ], source_drawtypes => [ qw(AREA LINE1) ], source_colours => "dddddd 0000dd", lower_limit => 0, rigid => "", "VDEF:D=Used,LSLSLOPE" => "", "VDEF:H=Used,LSLINT" => "", "VDEF:F=Used,LSLCORREL" => "", "CDEF:Proj=Used,POP,D,COUNT,*,H,+" => "", "LINE1:Proj#dd0000: Projection" => "", "SHIFT:Total:-604800" => "", "SHIFT:Used:-604800" => "", ); RRD-Simple-1.44/examples/graph2-daily.png0000444000076400007640000012010110746154056020012 0ustar nicolawnicolawPNG  IHDR%ItEXtSoftwareRRDtool, Tobias Oetiker , http://tobi.oetiker.ch\a IDATx]޻Sٻ {oX?ޱ 6l<+ł&EPD)vT. 0+RD9%{gffg{ޅf63''9䤜4/%IH@R2*?4H>Zy}{%+)IH@R(* $?v.N?tc=J`:li˰SK6`O|([<(6OK_Vs߾}{hd7޽{g 6L4i"n\r%5ͷ~+Gqں9s<c#+R{oӆ[mYibCgϞҰaC=Z袋2^(kN7񛸙V K̻a~Q5#ְ\P(&Qg̙Һuk]>Yg9"MC*Io?tgjK-n;,ÿ4sʔ<]ScEaqra<0XD ?/Ҷm[i޼y]*$=Zk^uUf|NOS믗.4]wݥGy'|r !g-)L2EOrF&~4~i˞].]t㐋?l)\A2uTጄ='N0r88 -we£c1t>;K|{[EЍ>'xr!2m49;ꨣ/(*&~ΤIeI+8~F0&~.wk>,}pNCmU;x3aYq}w3vyg]aTxe=4q7F/x ͧ^EOvoF\n?3JAd6d 3kʷI-~;RU&<_.7uYz۟,ElXi"Gf\򩧞jڴi875q6O8+Fؽ) a>gB+IH@RJUx2+iv/Ks=úu-aaFF'H?)IH@)ˠq9n[#qoذ>Sy6̓>,,f́};T䄷ᣭ<&Mxk+=,)eJ36 +=|66ansSj<.h5uO1 oe1XY;aOBnVÉ*1YluB|{ڿ6Yԡ,yUT=TŅ lt/yzǡ]|&UH9(o+1Z{wy5RSSͪp(R[HN6WP-Th!Ug)I*q,j%NfxLk=Q QXی qcٳgO-{!gݰpO Q~ؙpqc낦+o]ZWtM+\wj"QAD}@Gd ,@ }ʂDvEE9FE?_pDIyFri,׋\vb֭EN8A ,0.ᆂDVZI4Mp`kyzaQXUܖk{ ˴b5 &N(Aϑ`7a/'BU)/aru#ro 8ΕGxL+oEw*wYa™Bt)_յEAIa@.OpxcNJ߽J"hymk$1&VR'D/a GE͚!_MW\W<&74)hGE6LEWwnrьwŵɕ|A:N2DGl[DbBN<j)HbW@P[w_:_~|W\W_{ƫ+uO]5xg:L~![+E]$M ok~gxa=;m`f'dO.aiy/E\ 'O⾕L<\m|'!N%#qt(p/++G oɅpgx~!m%otYGoSR#5X`xO.pi3)޹~]3)y8^a s 9SKG6?Y;0 .(dk"!n^"h2ߘnj R\MW\W<;նǣm40U>(*=:{JNJ t)"+z|8'`O6ԪaCKR1rMC.< -[; 8ExF"q?U6WS>;mu].6[ϛ?{ųW>%A,P)rr)*.g!O(0cAUo߾:MqOryk8BX~UIϞJ+i](yy5ѢwcoiKaFLKz̙fXz#W<&V*)[FBh\mC8h%Q֠3xR9g!~[-Pef m7LI:A/"VEſ>^j̝ں"tM+qmsppWogaX-jT B}!/o7-=TyJ=%T':}3}G`Aua|zHSIE㾈A5DF2#`4Rg@|vyXZ nuS=mw`pT+> C߆W2)*Ng!HN !=l G+gϔX7lC2D FsE^;-=غu1 rqiquc\q]H܅@hFY2=@LT+峬#Wۅj~j RY܆Z\jk\[:5ƱnVv$+u q*\NkK^%.-GTVԝ\MS푮,]#ȍEJ J- Nl%B_6){U8s1|RY屶o$'BU낦qxV킫f,iK ̃ M״k`ǻGkcd\j6 +Ƹ(9ʽW'l:w8ukQ[\Sl4!y>$˧vS#+ 9 ׎V~v.NىlSX: r6P\ִb qпk#qlYb`c.hPG1Q3YDVj<ҭ Q!UKJ(}5Z/&ا /~].hF6״2|q{C*aK\/M+x)O^P/sv1P#ꦸ(oE1*T3M23kz]H׏nuo]@ ²]oybއC2u85 o_JrᢞGE?u=T@鲪{w-T}:}Ybp;UqQ>\ #+5^]ьAVT(}X f e9ڊpˁ~^|׽o13#k'hrܻ1דxE}FY:3IQc1 nKqҁ o#?>N^3S[p9[qqYᏂ}=RN\+B<<_]B!F[!+m_GŹ/5T4]*,m?'? nf[Ou-GuϺBA\}L_5nM.T].AK͏;_k[OMl*QS~:ΰsMk!}I -nd+f7:<2sp.۔סx3/ ZW` ~ et&c/(? 2ߊ"'cmSf:6y4ib%b#\â)Ja}:_By͹0UL!7x4kr]g'mYZ-EFPBW ޒF1z[2d4كa|p)9'Tx:i馺rkg1G&zOxF %Dc%o9~wg[W-A//V/tf+`#변8Bv19o0<i˞%̾psZdwIT+DoKl) 0x;KX 5v,,$ ֔4 sjh xr aKxDwu#bWXƙ}_.|}E<2[r>wu#RV[Zj/W|sQFY|R)'hqVШƭ^v# Y*ޚwM g9J fP \΅ӕ8d[phcdRora͗s,x?l灢-Œw#F2}#̃; qM_k}o8t)rkB>xz {Txaong++: 6WZOQZWF*DW/_oX е*xw]q7^}`^a-^a^uE50'(ة9?;U&jzqtZ~Yj"M36hi'||imi{惧TLkWBAUsܤ-C3}6] 36yڞL鶛k~^^dW_}I7XlBX O\ !εGK!n ߧ`'X0z&?&W0g+a]++i+Z Ar`7;ATt1zx.4K`Z\6lX/?=Y,*qeWPW;ie{pBmt 71rr}OQ}vFpk*D'Ϣy' h2jкV*׶9mQ8u]ێC3,0z wMo<iX/KY]!R)uqhi-6XmMթLSopVTʃxC߀\56R2g!6c1{n* :A϶&> OZDp0@*m Yu, 5JPZ] `cl ,Pv[(zN`;u#NFî"3gjp-az%ߞV= |hIf_|ݻp#8Cu |:wAC(@eߵ3HmSyn 8piUOP @'8at} ͨ&LW r[|`FQ}3BF-&hl 銛 O).}´.ڤocM|V0 /X6Lʂ~c<; չ׆W2)*Ng!9ֽ^ Zc X9< ZlG 5 ͳ❋`o{7p7Syob `Zf <]M' ChSw^vx_@K m7Ļ4 | u6'yP^ S<"\ϞrDolpaFi4ĥt{Sc~ c S^gw3q--(=;x-gOv=hx^Y2ke&0<ڱnj#aX2ީqyWHZ&ihzڪ-Eѿmw%,onOo8y6ԴY[@`"_[>ϣ]qmxŲ\7YdYm#=<*Ft;{=GXX ?Phn/X\+iӰjk#X+#M丨DNWqii"G|S 8p #XW>6VG#`6S_vO!OVCIF(4 嫯6=OU1)_<\8 0~ϕ9' G~#ϰ}|5zvodT_g.EZ6#W<\GFa-{K,??^W3À@WL:Hg<=x\.f.<>wŵKQ nE?/7|s`GVhY4ȷX"oKeAb  S۰!ǡ 7CǁKH5p. :׮јo?DWrm X4cwpx(RIh0Asҙ"F&!s,C:sU.TDx6ns]-` s9W1=֟Bh<~BR ΁yOr!ν[vUy@`(Ub6QdL8C6w<`C p&r-j )aXM S~+r}wq^`je3Xh [_79cS+q~R<} 3Vͳ\~96)S;W1e.%^!4]p:AB3k¾g"]4ǡp /"f6'K?'Yv+26Z9 qؠ z)4w. rO3N7&/w/?ap4=Q PEEPam3# r 3FC^ k˜ r8ʽwB /?mS'O8zוzkQGuLYW8!czu'0۳b22f12oy0ݹx+YK2+Zc@KʧBG:zԠs+CUjR>U٫AxsWQ< *rWk`sU|Rc&]+ʁ&6ԆLYGEZJ-M0>N,?}V|G JcΩlL FI7p 4pÜ@wc^a|0sŋ ި ؓxq70qTU|"^6rز) ӶF[+z+f=&s?+Y#y*0B&khgut6?צأE瓚% zUɑųs1 3SЁ1.-aK~s_˭RS!  6tz8Yނ08 tzxWS^-mw5`[>dKm<[)VԾ}{O5yhN8qqsu-瑩z4"sOppQ7;}K/ձ=aBnMOcpVQfdl2}14 =9`|'|(?4k&0W] [iLx7aҜQ].F]ٹ1`ږ.'Զ(VtxF^(йTS!>1w:u?=R}xo[]oKGnX ,υP{Gp016& cd[5mgӐJ Kk[l|ƑI|'o!ncLX}6l 2B7¼fTZV 6G-9΅N  ]MRFI;dƒE*iI34H<&[*@{ɜ/2pZSuu3xaUę^fQG\ot0fFܭR7hf^ 1ܞ)ziz9uJp 2CtԐt=Tf]o0ߤ5yT Wׁ){d&*NB@{῭jͪg@\F3~}m%[j㙳ada[o j#ƹ&t]{7 L: 9> Ծ`#KfaV8nzgQ]cmSya9K}u\7M4 -CnM0gzr,9ifi+c?~]<%fr5,y,*xQq*:gZk7n/1!+6#Fbzo9kiq5nа|qP IDAT⑦ qyKkԀ7kM0jymL"X`@\Aԟ<~p t0fQuQg;]Rd!(G]I|WBp Ungh(ad2{*aO]8FnppvP:~!a?h5 ]Ӫ7ЧzoXx'ەGxHz"ı웏t6|wYʓ|035M4"sl8CCO=g!Nkm+ȦW_=9%Qx፴֍i@7JBiRAAj79R=M?cEvOkl,uB%[3rwjj%SGuVFWW4QnGd:T:tu/J?pk<sy Rz] `:-Ӟs-yҷM矯, 7 wr[(H1 h;xASBmnܳeZ?g=J_qrɭ8%:5ZXS>k33_A3)YjSxHf%"m=]TzrC!!P(&={ЅK5α0ATo !]z0ڍk/WLm8W]una So\n^Ry@5 ;S"V9ۉخօlgG.Ah;4a1. C?uwz)4?,ij#o{o[>kh<Ĉ.裞L\jqus O(c&ʲ,e@Wo|^orSƨWh%]y7Kh@@}(?p {݃ Fp*q5ΔxA|sc6%ț73[#]̴lfFF~%B26 Hߍ)kۖ1Tլ{VW)O6Agg8ǑQ\4NUw( Zhn O9Yz%M*UQul/܉Oi?y7tx #lgq/gr7,2W5R@O`&5x Jx*N.cVr yLAgtS*4lR6k#\4x[d0-rN`TY$xSq6*5 93%pT_Z5'|uA`b`ZfUOCoA'tb?+Qn+Onv+q1[.:h{jC9PI h>{hm{acZar,^̽5q%U$vMd8aݪӳSR/ 4]]♛U.`av =M[@ŵ).6mZP;K 7[^ )Dj׭58qCcNoFso1ۉgɞjEוGxL #- C/I+bߛ/us~9d)GaX{nΊ–֬"1)x-uqiΚLIV_w]{ ++ÿ{0-6FwJ`]Ix+h>7Ei Wrݎ#+l<71`:e)ٸ<W7M~8{t+_$o?Q΍(Ӑ/o6-)M2S?3=wfkԘ\u|c@M/MIO1 ߒ;'Ց t $/+k>++^]̷F'Z 16^>;fdVpibv%g 2?`+nzy-yKaç"ש ,H6Ok0? ߅O?}@a x-⦕J-<+Bl #\]q]Hv%GY]렭%#Cy$+Cӧ4mT 98AI&e/ĿX4mgd!Sx\< ꁣ7E:GDXx$J8.bijiv9 qowŨC]9V{{v\ZA$0p4z.4ݡ$5ۍehka4=9jn6=$sm3{嶧9SɵbĕVVrJ.oKd/y@eCVX4&x-˔0p4`O@sv8>`$nu6SRq)اšic++^]t)"f6}%yIԧ;\yd3µT7#p2J|m1kPv[Y*K"{62AF E ~ފ{.n{| 7np>0m B gwsvA0s20lnՠtpu۩wxs- ,=r{5' ͨ8\q]W\W<̧M%;+f.% 0˹׆W,dV,w Q8Ν;~$fm7Cx}KJ]7эeFFrB(+7uEl1nW MB^o p1xunYy{II]#~W~V[rwFg#HpP2j.%=c:@e^g(Hm.>`e8 q h"{Ht.pdRr8O W_k`<`~wŵy-,^8/ǢVPns<<1=x[UiMSXE&+q'VRzT+obeĈw߅Vg!g9t=/2Q_HzX;W#63J77tj0F [e,`.\q17G3O˫ yró{x=05{k;\37E}z[*O3%0}DdX 哚3oc\u1<xviAFI~=g8G!`5 `#T82"Fm"[әa&[w~%0ggػpW\?^v ɒ[& pū+a XC1c.D?r Xrmڏ#GJEE:-U: qЩSʴiӬw͏+mC!ĻqJ]ͅ# gճh)쪝pT? scq 7+10ψI.:(5 =hmN3l \pq'saXE~5e-Ȱ4m5wa${)94ъE4#Ga+?^z(L\w +]6Nl0ҥ€1StZk1ܑy"sR}gݨQd_d4wq Bt2ٲr_J򈂽G\_]W`_jQߘ4m`0nRmiK7/a/] a64k'kq@'(НKwƵ 0s\\fiFLMzCӚ wP%KwxӧElèwl W_f_ڟ10Ә 5xt勛VV{4[ F#i-Rܝ[$6˗?H]q]H{;c+3A,3Чo[MW\^`BK/c=VOo&[Tf؋w{˄P/7OTrOS:p=.j߄SmRg+TY./=}yHiDzs42IMZqKmpKj[6N> Zc\GW?O.%lxjbQ^zJd5[Ȫ  mɴqIPP6;Td睫FzUpve)LL|]>u(rHWBl^0,<-sfBU4L\ƮY0$F,++ltr;zkojKi?'$͙-8ÏZ7ağ ͈Wk<ςKصa/ܯYLsLk3MZ#c\ΝLp毮xT)js$~"pq?Z3z?aR/\\?+u^)x/uv_\7q8i8>ׁF9=BXokdNEJnQ;"Zk(_/}>:$~^m/SbfjgadQ6D["ڔO+9TuOvHR99]ϔ teJdA߄t!N.;7EDB#AUWvXxGc 8≍fY\qmxFF.A?':&mѢֽ˄ BlT`䃷qf{w=3yɳ@\V[V,,}>ȞsitcQL6*7#oE+ o>PGJ ǣ`aa *CL 6.a#Uclm)&o߂Z6(d]/o!-TpxߣGi߾=NX W_%l@)79omfN7s+r{^Ugy8Ei%ݕPDT{{Do{?tW^5)jy~EUXgZ}+٥)kQV Sezi}Ϡ04?&~_MW\޻獵8&Xazxbz #+GJSWKjdFK!ېlΗG >&Ѐ#a{&nu= u_IqӖ)_00yD穧r5`Ո]q]b6<;dY›EfZ|1N먴9u֩)X 6;ГZ.s%V-ˑ+=f{\cm EzT7+(7BZyχMкOV+ϔ?׊GµYaPW =]@?k[-~.Wކ;;]}%G\ VЦv+|iH5@ig`o`B4nZ[DO h v kJ…kZf.ў;W7(]q]J 7pMa7[LFofنG_}F)Ppx >iP@=׉ X%IOqo6%\ \aG))LisX֧t}vJpE(ڤL*0iJ6фm"l< qM56!LyÈW2ha~xN=;-=6VyD嗧q\Ki7”6\B?*z-ymS/W/i{g)$xxZBhj[hOx@/|miy*=) lꭷB 볯r2`5N;?$a^TNqa4s1b6W:xe|AH' YQOW\W<&5rM+f6(dk"B|6  *j}`ۍ՜s-̥cf6K/p/y-b/7(} 844O)0CbzPlf67a<64? .K;:~~ (,FG-\f󶶅v0Da=]Sb0uݪ-wA3$'No**t!g ţwlcꂦu&W&t$wK[3j{" cJ}$= 5o58] ?=O),0>o„HTԁ-<Dx/N؞OW\WBx[MW\W<oYm-v"B {l^c=DƏgX(]\ƺOZMYP̧RsC`Ybj֍x5H8+2OpLkx]++G ׸ic|wxO݄>N5xL$qZ![wրnuwUያ~Uo @!Oai͹tu#`eXWMW\W<4˝A![!(Y،S}'꣏ҖƾcӾk`Ts9WVW<4+GCI74dz"m`0 β7xLs>+kk|%1JEAQ$4D@4"| EʢP~ʗHBR|DTPh>_ob4ν77}g5ٓ>Mssԯz^ӻLM'SCŷ~s'ؾ]Ͽk%-2[s}&uI|6k4VSTΪ;Wu;Yuݶi pC'3 'l6Pvm^eO>qxxzM%gc'/n{jikO> K\m+D-\`[kV5Igt;VW҉h$t:;'^WA%+cǚ>:;!Y!#=u>$}SBr\{n8-5|=c+M%&`C[md +3lX&с|o8m|VZ0I[6o9'oټPg^6Sg޲yQЀԼ: s}IO\W҉.9J?7{Wo[!3oټU'8TꖺBcB̯*N\;*PY:/rUN<{i䀰+֓ ~SJ; F2y?ݺsnS'G@7+#JiZ='U 9p7KC!'M2/Mr!K+.f_Qq ӌYO~.ojVdQN|ʙ9s ^B?>ȞthS%ɷ8=Lvr@I8 !m!hVC$P&J)L8c'd̸qy qr]Up/}y dѴCGkgsj^j%nutnů\k3/׾fJ}(GB%orrׇɊVฎŋl{ԩooF\7NOhe?X3̿'/L̤%d%JeVe!?{xԉg&١=ӓg>L^LkO޲yEYG{3u뇸mN?%3SdXz'uwg$No(ӄā:-+vI跿ͭ|*r s۰||fj[r|h>{s$o&+q!$m2"z!V_ښ2궙:[[6o9v3#nÐ[^I'7fc9ƾ7X"/F^'N=16[G!GRY8i6_I'.oU;O^z)(nKݶn0n*+mc綮 vL͍uc/b Z:'Z<:&by ЉӉb bN ,uH@1Pi'.o[wbz rrBLRO틒BI'YYbgD  #Թ"9R|{,>&7?ӓ-o~֮'%,15Y=jp6thrڐߘcYz"]Uډ߉7P_N|;?\ʺqcnv]4ȼ _}>"_#=C,,>&'ۥs=G?˖GLaV66&/XYk#u^t 㭆up(ӧO7cƌە;VYq}4zh :f;$GyI wq;=oW-KNlêѪ6lЬʮXcCn>0S8R=s8'+u[?nE_ 2dHWyvx+Vډ0g iʕ+m' g{<$=3!0hN0|A*ݺuȈr JN~;́hxO88zXqz{, sL;w߽ɉ|앲m}}Ű~Yg% CH6no]4z]i'{=2r2~~zj+'95.T>vv{,>$]q?uR?(7#ױrre{Zؖ|Ra"!X&Z[G ]z饪N|Lx)WuMmv=d -M򸡟HX~$EFXkgۦiû;#3XcI6[y駟/at+O6noWy®r{v0?_ v;o*!YÍx_92aI;O~x}ƶ!~lRƇc}Cuۜnׯ_o]Gi~pS66&|1{"c񺘧nm7.^ooSE @1@'R b$ b8cd!bX ЉӉsN 1PQ ЉWp;>Ob"9'b(+j*ftb(tt✁1@ T9z^n$oFݜpXyytumA#+$O(ii!YW*``ӧO%GaopDZs̛֪ FsIkwS'@n6I>6mG}d.B3l0@pFH2=Ӆ9q8ċG. (C҉eTth >kTH+4Z9-Mɴ86. }7Z^pzUtIPBk E+V0vZ .2eJz>&lhOٞ;q(wfڴil5 ۸;ReEW 9SlСC--uAtAh#iC}wyu-}Gjʹ2z㎖1g xͨQRYeZi/mIP zlp/[]̚5\tEPwlz~?Sۛ߶{ժUx]K4ͦ]~f:wۀ*v&^&r7nh5kAdW^yFWS{6u)8Vw}U)*W!"|K]6ƀt={P?>MCِ?)mK'\yjРAA*>3gikiii̼im.H<5l'=3nzO^ڂ@sα[+i2M ]-3O{ZUF*qUji?j崴B}mt%KԼ' mɔ2=9xQ/u.7vJ_e=LKNm7Ӝ88],9^{fy1Sz'mYkFU=}/IByƎoXۄnbZ/ǽiib?,|c /;,oۊ,'sejZ+ÿii"]Y|9SS)O7Ϸ="~,_|1؅%{c/?/aR{e6:qth̘13$霻zv:fKH !feb[ʈV%-_ky`퓤iqlK1HzB W\a?>a6-b3-u;q 66q8m~W33ꪫu~⋻҉t? ={ޅϐf!G4Z9-h]P/a"da/vbE"eEܷ+g}h뭷6ƟEKKCXrZa#c#싾1P&Ol^)E~I[n-}Ȑ!իWzœ8!^z5xCP71@ t> sK烅6b(ēm4: b"9!b(+j*ftb(tt✁1@ T9^orZh'=Un=#:j!r{S#-͗nmƵ}߷ǪSc|NO&Y·LOu{Yb D>>W5^, %N9rW;~SK: 5jLWUc1 m+}S-u:!Hp' ¡/r_oVf‰kXΨux<̆#RC`G^M&& ?x*8(e؍,fjcC(DŞէitV"%ȫ ;ݶW4vtLe}1. !ugΜbM]?5+{7ku)x ъd4q>C`dB 7`;<[ ĪhlыLWhC\Vn2_kF'q⒧,h(vsC׶1+*,f^7tJ_Ŷr/a̶b&?0a… -C{̲B \s5[C-MYdZ)|] &lIy.SYȉ~4$B>IoL43ը6$oږ(Zl⇐I,fl+}kYaG^$n{>oV{9WO]v%%qhinV_ډ#G47tU<ĕ+WFJYʮ-V!,H2eu h!mK=q<1F Wv;1 x ilVZZѺv!$:ݶиY@v6vSǎ/]ԒiE{UoiiV_މc=`+ `B"/wI&mđ/Dѻwt:%xaOCyDfYzX~gdZZz ,f?tۺ k1v:&oTvP #vml3ʳ"pԨQ?i*47_ sbvWӧ jP~gCA @9NQ2VK? uC UxZe1@ ݋:q:qNbb *:3y=1@ 9'b( sz 48>52 {5+>vc]YnO ى2iiiUc%Yx~ 'V݃yi?='e{m~@'n0(L!y +,Sؖ܌^|G(bV}h-u}לW_-yz*xVHg:ƌ/~:b O(S '>qD~hcp ˴i,C R- 8(L>t "uBެY,K9 ~ r4zU4E]k՘:aVcf[^DgLK~ ]f4ep7D1=‰1ǐ?~ciY =pܡW}V3Y D2  MIDۊBcN-1yE!I2Z}>Ӓv}M~̀zH841fPD<(U؂t%gӹJ@8q;Y;Ѷ/yn^ng1E3-e$Ѱ[+s5~#)N\c ޻Pekmɒ'rJmiqnY\hoV &~fY/Gc,f{2X(:_kK<[Pn$M \Y1exG>RoOr}B<'b̽wH^{y-_kVFʖ)}1/{ć$;Q^#32-hP1]Zʾg40IY)e ;նbVȱ{ Kµ2Lg1W2C Xh>sک' sͲ=CV!=iQP1@ @aNF@'`N>p0 bhēqۡxI1@ 4:q:qNbb *:s@ @1P80`Qc,felLmiÅ9明k D8. /4Æ @LKk;~ͶULf-^=k *&xn]LaN:0!=Ԑ292%+ƅF@4}t3e d1k6Y˓e[*&7kL1]զtνw2c bݾHo5^DŸb&mi@m9",yce5/6C԰i^_KʹLW⺙,n{+XѽwI>̘/C/Ņ*hܸqfqr-裏qiLeZ"&~^u,ۢC65+G}4 7` .2,f;=bJ05dydXxчJ_ziMHR^ʅie֭[gC W^Zȴu"6C'Na1l1]l;0s!XO<ю'Oh|躕!Yjy=œx,f1A 1b0' 5AF 1@ tɶF1@ Ttt1@ Tt5\gl3W:1@ :q:qb *œx3 kG3gN 8 N!:uqB?~Ҫ;*dg#OcdM#Yxtkx)ΉÁG9~6v(NSMb sim562Z9-MɴƱck湏/=Uk/6BfVwcz+1X1cXB 3ؕukOց% g͚el֯__ҏ)LG8~ZfM{DVNKk&4jL'tYdk=+K~P_4ۢNf1uةv)p1624rĈI;Hx5\Sd[om.\hr8*!,f\r%!D&9cP۷o}rZZ+6QGe^3rH|m妕:Y,6PŞl\m :tpS 'c#4⠃+rLGC`G\P@RGCыv c=f;8k?Plw$.j崴fOn6뙙1c+ h-.ﲊ5F;~YYŶmE{] '\tÆ v--ѸbdBW{<[Pn$M qZ&.p4h} 1ڪmj}ii̼ib?g,f4V1̕ٯ,WVQd1ߕTSj'Fc[鍊nJٕ+W,F&0VjՇ~YQPn?$M 1A2tj?^bZ`˴iٳSi崴!}CԓTLc F&6hԶg"C:n{ Cwc5J[Yg1F&'O6Xu=ږMZ}~^QPҏ%{87LסjiiiECRO38F&ɽ4,)ߊ,frd sd1BfC 1P4 sE7vb b@xLP1@ U88'11@ xE W#̕1@ bNN3pb b0'>WX8?IUbСCرc4!E!I26<>]V̪3Oz);쐂 Kc< 7`;<G(A+'y5 ]+E\!.5(eAmδ><.& nn6NDl+lM,fE7 g ekDffKı+#߿b]I] >$M q:b&m[͡j\'qÙ ږl!7N`Y}Egj ga# uty] '{?ĉu1eȗ,Y+RUrP+K._)f6<콴!7빨:ɶllcvDgZcGN/M0a۠(4{ND%>^t,fn}MfO|nbgdZ-*ȏ=b>vmņbVcvOe1f;ѧ<+r/a"'œxX̮J3}芪':j( bg0Po15ҎYgπzbcNF z^b Љ'/1@ @1@'N'I 1@ @E1@'^QUq6sC @  b8-v6K}R1PE ЉӉsN 1PQ ЉWpU1\1P,9'b(+j8fR'1@ Ttt✁1@ Tt5\gl3W:1@ :q:qb *:bg'I U88g1@ xE W#̕1@ bNN3pb bNl,I}@1@'N'81@ @E1@'^QUq6sC @  b8-v6K}R1PE ЉӉsb 7{GK{9sD65ڧfh}s=C1tWj/^\W_5|Upz '>}K/#8¶夓N2|A]}GށCcǚ^x!-+v=u`[_K wm|97Z;y3f;3X%cXb9x@߭G0Iz][=<O_6ʈcy… -N19Cc=f;8+/rarvgLY~ߵҶj+3o<[_ot }:)@*O>iqo/w\O=tsk|rk8w=dN_~]NMijCĶvm*mDkz__>[^{핎5Yvp'H!ik[,I[2SD>) ϟo83h !ji!YH0kii}$ mK{w O#!xFƼW9%-s8-Pp8?6G5 ((FC~禛nJC;{ڴ뮻Ό?>.V4(wצ>H;bha[ref}"3ۨB]~f:Ff qЕ]J,;_ÏsX~l`_^mK.oR曩ylk+b2a2tƍ?}]k}P|q-wbbO'E1m>'|rn[c/N#~3Ƨ(Xa a}k8Om?>/wpԉ rh-I&냔Ej'v"YgUW}:k8j>x`#<`5.yA!Vp <օ:q` +;o.' v[rs[ꃬ,i鱴X? ^ʛ8qbݓ^Z}Z8͐!Ce)Q7` 68vCG>U88'1Ā8YUY7N881@ @E1@'^Qu̓}犑   b8 J bNN3pb bN 3pb :q:qb *:3pb 9'b(+j89'bqrglzb ^Uh$ȇ b ,;IENDB`RRD-Simple-1.44/examples/graph2.pl0000444000076400007640000000333510746154056016552 0ustar nicolawnicolaw#!/usr/bin/perl -w ############################################################ # # $Id: graph2.pl 965 2007-03-01 19:11:23Z nicolaw $ # graph2.pl - Example script bundled as part of RRD::Simple # # Copyright 2005,2006 Nicola Worthington # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################ use strict; use lib qw(../lib/ ./lib/); use RRD::Simple 1.35; my $rrdfile = 'graph2.rrd'; my $end = time(); my $start = $end - (60 * 60 * 24 * 31); my @ds = qw(nicola hannah jennifer hedley heather baya); # A salt offset for putting random shit in as the data points later my %offset = (map { $_ => (index("@ds",$_) * 2) } @ds); # Make a new object my $rrd = RRD::Simple->new(); unless (-f $rrdfile) { $rrd->create($rrdfile, map { $_ => 'GAUGE' } @ds ); for (my $t = $start; $t <= $end; $t += 300) { $rrd->update($rrdfile,$t, # Put any old random crap in as the data points :) map { $_ => cos( (($t+($offset{$_}*500))/20000)-($offset{$_}*10) ) * (100-$offset{$_}) } @ds ); } } # Graph the data $rrd->graph($rrdfile, 'title' => 'Random Graph of Some People', 'vertical-label' => 'Weirdness', 'line-thickness' => 2, 'extended-legend' => 1, ); RRD-Simple-1.44/examples/meminfo.pl0000444000076400007640000000436010746154056017020 0ustar nicolawnicolaw#!/usr/bin/perl -w ############################################################ # # $Id: meminfo.pl 965 2007-03-01 19:11:23Z nicolaw $ # meminfo.pl - Example script bundled as part of RRD::Simple # # Copyright 2006 Nicola Worthington # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################ use strict; use lib qw(../lib); use RRD::Simple 1.35; my $rrd = new RRD::Simple; my $rrdfile = 'meminfo.rrd'; my %memory = (); if (-f '/proc/meminfo') { open(FH,'<','/proc/meminfo') || die "Unable to open '/proc/meminfo': $!"; while (local $_ = ) { if (my ($key,$value,$kb) = $_ =~ /^(\w+):\s+(\d+)\s*(kB)\s*$/i) { next unless $key =~ /(memtotal|memfree|buffers|cached|swapfree|swaptotal)/i; $value *= 1024 if defined $kb; $memory{$key} = $value; } } close(FH) || warn "Unable to close '/proc/meminfo': $!"; $memory{SwapUsed} = $memory{SwapTotal} - $memory{SwapFree} if exists $memory{SwapTotal} && exists $memory{SwapFree}; } else { eval "use Sys::MemInfo qw(totalmem freemem)"; die "Please install Sys::MemInfo so that I can get memory information.\n" if $@; @memory{qw(total free)} = (totalmem(),freemem()); } $rrd->create($rrdfile, map { ( $_ => 'GAUGE' ) } sort keys %memory ) unless -f $rrdfile; $rrd->update($rrdfile, %memory); $rrd->graph($rrdfile, base => 1024, title => 'Memory Usage', line_thickness => 2, vertical_label => 'bytes', sources => [ grep(/^(memtotal|memfree|buffers|cached)$/i, keys %memory) ], ); $rrd->graph($rrdfile, basename => 'swap', base => 1024, title => 'Swap Usage', vertical_label => 'bytes', sources => [ qw(SwapTotal SwapUsed) ], source_drawtypes => [ qw(LINE2 AREA) ], ) if grep(/^SwapUsed$/, $rrd->sources($rrdfile)); RRD-Simple-1.44/examples/iostat.pl0000444000076400007640000000356410746154056016676 0ustar nicolawnicolaw#!/usr/bin/perl -w ############################################################ # # $Id: iostat.pl 965 2007-03-01 19:11:23Z nicolaw $ # iostat.pl - Example script bundled as part of RRD::Simple # # Copyright 2005,2006 Nicola Worthington # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################ use strict; use RRD::Simple 1.34; use RRDs; BEGIN { warn "This may only run on Linux 2.6 kernel systems" unless `uname -s` =~ /Linux/i && `uname -r` =~ /^2\.6\./; } my $cmd = '/usr/bin/iostat -k'; my $rrd = new RRD::Simple; my %update = (); open(PH,'-|',$cmd) || die "Unable to open file handle PH for command '$cmd': $!"; while (local $_ = ) { if (my ($dev,$r,$w) = $_ =~ /^([\w\d]+)\s+\S+\s+\S+\s+\S+\s+(\d+)\s+(\d+)$/) { $update{$dev} = { 'read' => $r, 'write' => $w }; } } close(PH) || die "Unable to close file handle PH for command '$cmd': $!"; for my $dev (keys %update) { my $rrdfile = "iostat-$dev.rrd"; unless (-f $rrdfile) { $rrd->create($rrdfile, map { ($_ => 'DERIVE') } sort keys %{$update{$dev}} ); RRDs::tune($rrdfile,'-i',"$_:0") for keys %{$update{$dev}}; } $rrd->update($rrdfile, %{$update{$dev}}); $rrd->graph($rrdfile, sources => [ qw(read write) ], source_drawtypes => [ qw(AREA LINE2) ], source_colors => [ qw(00ee00 dd0000) ], vertical_label => 'kilobytes/sec', ); } RRD-Simple-1.44/examples/hddtemp-daily.png0000444000076400007640000001713110746154056020264 0ustar nicolawnicolawPNG  IHDR)A-*PLTEȖ:@IDATx]oGcYt VsN;6`13pvv#@ 8P榃o:(Yarb@903P/}UUwW`7EOTwuիիz^u &{MD;n<nMݾw#aw7Rahnے(ɲ=0+Z/Ydqgdds;naI]KʬUNN6;ݨd ao{`sszw{=|pbL!ɜP,WOT:`C|&A`-M@&qRk!aɞk 4vlv`#0J7iv76m`6-lѵ\VۘG"]ٜ׻rm"RU 6%*%a#] G6= ka``YLd`tVSi ro16ft 8bb>4lV jyI0ْ$4eS=˙Wi{2 LFN휆vϢB|(j߳uvfQj]۳o3bnϤgwQsbƏʶi6(7E9Os ꈭnlYS'4kxlamMz5'[(} <{*-,7h b= }m ZS`rr -4<&e/jN,6}kJo#2oӶQҼiՒ4m\hϻMjh=cty{"Yg>>8<@k3!)'u5P.oU~\VXZfy64.R}% 0 q5#pOkPWR C/=pXn`? >0.?!0l!B\K\~dF V .~<6 @G?(X$xaD桀 p5EӸD2L+Hd~'fR9h"VCq; ˔2s[ ,8J'^m\)X0;:ʍ&=N㏎pc;"׫OO`X&sHew?nK=Lp X0;!%S8pG͹Bgz辞' /pgg9|,m/=G5i֠mX{oe!kZut|}j]k;8x L-z{{CˮdQ]J5v 1X:>uZv  6W~ td=Lknm ;¤%eRw:!ꏡ|VUK;2a78IYOAO+bR̿&߰EA =kkuh;*i7֣&黄_vY=+S*t,mt~89 |G򂧍2g}~w6@-ae9l76HZ<;x,y޸yדSw61;vj3_m}j U~FDg??֬c#)N bys\RxL@WOA +~w2~ zF" ǫ+()z*RG-2&؇w!xTN-xwEx.\S`i2X5fs vh$sMun1f,Thl{/´}jtq^[Ix2O\ }rBԶ<9K', >+ 7XG ]6wGjx'h{چ܎QT|rmҒ,g'}=4_F𘡑\m]s^6dkB}̉Sdan N~`w(_,(߽P&ז#J6%} _~i| 6[~Vy,66\ܦtnCY4#..Nf6ɚ䅰ʎMfGj_@sWbW K\|vsHeinH%y{fcRb YpwMI;<ɼmLbb '>w\ާTIyPD=8J'djɐ61ώ)4vyob Js65'BxvR髡J -eZh;W^ vѽ4lJctObn{8YFu{|[۹,XK|$.`Xfy,* ػ;aˣ?v5QfgB ^sJ WN,6+ٳ=656zꄷVrk?M[E|&ɋ]ΥO0)Ou6@]ybeGe쁌т&m sPeR_cs@pANK3Y*mXȄ>~pg 2o8-ɞUZ%w$6ф`ӔVMp]:l Jmb98DiO%fUhCq dVWp+jQaWXJbMcj14F\VM,`cBTX\&s6nv?qp/j6[-bb1-ҝiIuGTFiH$S.2qۼPU𚕈 S+(dKRHTd qģ eao̢ƎUP^R6zT[~T3]d`s!pRW)P]lDjc\,VR hLlmQn5p jiAy#1~ҍ `'eFuQEArT >UEVm娶Del0`j`*g۬c*<6~6/`wWk EKe^+0Y:&7KB7qM7<ߩ4Eg:d(w#~"ڧ?"lMFf`PvJI(ⵉp2^11%4ǻm8 læW.2{mVd (Ew SqӤai*>l|hbx/~k-D_%W=#[CL8`{&8ybaCӡHV}22BZZlɀӖ?P$y {qvrG|qٓv{7]IC) dxƽU(Rv$]P-`ysl.a8mz~A }5fj{r6/8JN!B:?SM㪆"&h❗5.עfRU36Ef|y錓كC[A~\g`k(`{&/#aOided{*=Les*O!㧐a'xNcNY7]9uj:vFedIwފR\:DZI~Vqd-x(wU%dӒPD;Km:1" ) 6 h-@TDQ''"-+WT}.HJ,ziӂ B"`>l}*U9cWa7=ʑf`K(LǶ89|*1OkKedw` o:1w)TNժ3= }~oWw[v> mϯ|}65ڞ_oBl~k,=Xh{~uW m~˯k{B'&KK|TȽsG̡KU ~iy\"XӊPW&$ GUJ>^jCaWH Ls'thp2A 8 "ՀMU$F GڐB6Lh).pcJh$[.# $ [q@yYMnfH 5Y"ʌsr\AgAxp`m*&i5@0 VvHsan7H8!  o3f cgX@$fsd*1)6UQQ|aQv> mϯjw@z/l[IENDB`RRD-Simple-1.44/examples/example_apache_monitoring.png0000444000076400007640000013226510746154056022746 0ustar nicolawnicolawPNG  IHDRA ; IDATxwxJ 5tDQQQaA+ TP EEXPz.M@ $֙sXv25{';sڜLYD`>`"QE5BH4jo3jkpIV9~< !a*k!6((\@I(ǀV:4IQb^lCa2F- b8$x4Fz*h8x`q ~?lv!D=D=萮AR=`'j¹vq <Ηq y%B$rԸK D8lɢ,vҪ~:jrlo*,4,G4*fkѻ9HIgxl>nXlưnca3`EgYΞ8B^ h䤨T_Y)* jGá6,an!,3Y;Q?޾/{s}[9*fl<{]d!VTXc"^q|` *3F4:2c{5b2G#78di}={ gCޭP#6R=8#:OX~#8 @URKv X{Vʌpxo4 ^ Cqk>?6?bZp}x$Ʃ``4 "22てI?gyWӅ̫i+9{^áKһ?Aƈ>*!kf(:w%},BMvF ʹa3 &Bjf18Z 6A*@EoEAd`]A=sfOW`ԥqP0l]8"70#%!* * :-AdEclHlhѢC4a)3Nˡsc-,6cX"|G_i}h~ 0DmE, mk?oQ;F=[qRhqE35CQCVDAVA #Pfd)K/Mq];u1L6>°OYN >t'%ׂ%ج6gΫI?JQXeMkҚc|'PʱNv ֬-zSͼ(VkF45Wl2AWWCh !%1+t5t5c*6P^!>5FA3eij'day6ʐ2B dT*Z :T܌7N OGM1Yz7E>˰^|8 6,ÆШLq2PآēӐ8׋;YlCq %l5C :BHm_XY `lx#<;O]G:9Xm"cШyd@L`Uf/f igduNYf~"󸺭Ӗrڐlz,QU#*Xǰ<4*f 1* tZ5&~sU2S1`_фMN`f C00z]vdw9h1g,e`]'A?ajX 2 l"hD$_Eǰq_n@"ƤU c,o\_bx X] I-I2.܅zL@R y/@ !``0ШT TNd9 6 @RXzLiib8W\p: &+DSy+b[fn1ȰSƣ1fv\68GS+kח?>w5Sƿcʐn2n_-85aq_ d4x_o]u-?# zF O . "6fcD{q`Ad 6U` J T5Oe&$몤KKC*24k$i喹xog%;߫Ͽ6ťP=rh5GQQF]ꪁk6z UKw02U<Q͑tHha;Xdц֍5BKKZ lt(+) Tp7,xՕ8*qh<;y~YGBb24pՍwA D_ܼ^, ;LQqkfhrp}!^6_FC+.]ߘ7p,mnFYs9Ӝy'zYEx̾aۿ?_ ';- q0-t"xtEe 2A <ؿz3vqN1U|v/$xҹUclwBq(|O-٢2Y˓/Ԏ;B|,X*/ q*N-FI’ /13Al2SʠUqЪ9#{Siڃ5j@# hSvAg f\yRy%d׷ǖ-<ޙi(5ߋ<~j?i.V3?rk<.C^CBy6Jo: ]>:ums=i6w* iWpݳBq~}5kU(-.vc<.ߧJqIG,ZmbMA초 z{qH8*@EteeFpg(q 8~y`,l1 }q*j1~ˏGzNcTRM1vT /- xx Y+ eXoлp *6 xc?jzMXp<֖0|~Nnb ~=+Ju=sO/ʁO` P][yñBUo/{9~ >a­MV8 l*K bsȺ$KjT8 NĦC9SJF:qM\{Hy <ǡaХucYeI:aaqC[w[5ڂMjCRqy=%eu$6hQX27tA0&]Hihh>=4t0sx@ST X+u(w+ F`/ 8g~p>ďUov} G`E}d/=S!YpC@K7Yo _ixqОb}z4vgO{vAmƽwQїb! 4o(7C?\A X{ *_`&+5h5*1j0Pj0#y>NG3Ja?\15 r 9mmϵ_⏥>\~$.4*uZh<*?a0#u/$6;SUC>]% l2C=?A^qMM@g`6͈VW="FR=iT"5cˋϾ=ḮZ@wR Z588OTT Nה2,vqwGlZ62}p XQxAsjT1۠%T!0Zvi ]35Ri'≯KP^^gBqK,S;*4!Q|B!DM[bӖAA!Bdq : @|5Lg"SyuhCԔe)$:7HHȃaf{kZ_B[G:|ߧWπGBj+湂|.~s/h}&vXuF \ >/+_,ƴ9XvN@Pt;{˼!9.>:S_}ƄW`[;o|}C9%5 T# %ޖhGr w.ģ8P~"ϏXʶcH[q`|F-a{E v64#뜏ყDP>}C|o"wڅYQiƐ^V+bЪiqs~^=2I0yX~?k/^9PR}M3|ug?{ijsOP]hN 2UONGA0uMw}&hܢ#BG|,[0]۴`_{\ƍsp9| gðx4|0Z5Bp]V+i ӱz$ m5 1_27FMuH]B~%a:~8FF6uܜ0lk93pWrGv9fܛO~߅q[pӸ_^nGzbCwrdl}c:oIY;1yӿ>_u*y|Fg-0ihO1Aw֟xnV6Luoq%\4 X8$5_vrv||EܴW}R {|??PO2`{ޓoL<:t9q7?v _I0p+pD.\7F])ݯLٖ6Ñ]?[Ǹ-^R)Ciznrڃmzk>\ǺUvW'f:&@ޱ?֧-ܲ &l\mFmonw_1N-u.<$J}pd{{_v_\o 5#rϜ%*hU-0øїE?"|>ryZj]cynɈUi ӱ>k7|%ni,:~  %68VzǴ24ꈳy=.h-Dfwr4eѓ ״e/,]eFOu(Qf8oQ~Ɂ._t=wJ[5#Fԍ=`{GmV oyZQKa3 zmqmϞr+.O9N[1P pkX3p[M `WO0y؂[%{,S1j1X%<}eҕU{} qxxyӶ|^pxpi `B婯 ;.GfYHka3xkg_Ջ1nz%f}JP E:>ۤt_p9=!i+!k9i.qxd'rm8::kG\kPoâG?|'7}i1)IwaՑ/qޣh n5N왆?97]8[_M\D#ɮ[N۾4w?U4|d<:[BA~>Oc|ڐ&l%vs 5sƍ?"vWJ+Ki4q"LBj$rȽ6UĂiGf`On!ъ`a?Z`-6\|>5m3 11 6>ܳ ?yM?=>]w&Ǥ /Cw[0ar+,%ؽkI6 p Sq~d\%6Dvrڦ +6uF!6. Zw _G&s? F(3Zmwtԣq1O~ M]s>"|t^YC/EVf65glǐCWbx& {߆S={-g`T!|߸ |럦CFz}-lysSVЭ%__ V⮫z!Q&t)eslw^[(b\ IDAT -]].Q|YK1sWU^nm&unN +# =}g;";66P"33L/YpUFW^mѬZ|R"R #p4O\]p ɉr49#'e'(w'\I}UTϩR& !R䉧NmqU]Ikܯ5vF: ]!ЈoB!B!DT.kVlB!_$GI!N|0F?/M!3?ӟIF祿 MwB!į8q4*) B,JRۅB!E!B"B!,_B!qJ >!{$B!x 4 t-!B WiB!E!B"B!D]A!Y|=2] >!q!+B!~!+ BHDQA!B!E!B"B!DBj}Ō(D*+i; -Ϛ/\W݄|DA߿c qh0z jm g!x) )$6KOKqZ2jxiҫKo@{̂FՈOMGC1mO:7oJ<>k8qih*L2Ā_jMTQS#N%Q'j},ؼ; yۢG՟%?An36 >رwJLL}&?SިσwL Y_݅~H_ݓu>+h#:fΘ}/ (oK/C`N}N#дuxiRl}l ޱcf8L|Ql%!D]ƼFJNc]-WqeAoEj?fz3^YނS0ϝG T?g4]zkIU*Hj m|厊WBB޶hhnW&U(*OQ1ڐ2PǴpUle_@/;LۉS+@۝__ڥ6x`s޻yxQp=gr{h,,]vS4>9F#5eGXZfuu_x [d F1~ַZ_ Ѳ1bz4?i˲q#HN&&[t]+<Ԟ?˻DN ^$I꒯ *mdV|htj]l2or `.܉]̴$hbѲ6/SYQY*Dzc"rAAV98/4t; &ϩ J˻f/w#=7,U{mZzo{?Kxvg1\șW'T_׾4ױW~[ru'zH,{ve^J`*a1*kZ^F-ؤ3=y5ӫ]>le1 W{.11sh\˶e8&{{ZM-]iB7R'J%)Px?UtIb|l +gnTW iEڌ֯#?AX#h7r1S|[Tm^¦+`֊돯٣C::1ZfJYi.vwvb; OZv啲3ٽ%FW|-svgey;se?K>T뛰~?v$`bG<)>۰,~u_y zNd'JMl I zy*A,ȶ,Y2=#AYvL^SwaLr9'+ee{$85{}T>W^߼ȒՕ۠/`61X7^Ud驪OUЕD|@@MIޡc$ ڟ?(ImFXYlddU+eZˎ1`c%GH4*|y͈Lkob\7*]cgBWヾξͿ}۾$;9. W}aDɼz:-P2oy%fJ}d:os4o[([Yt6gّ Ffp={e~Eny㾯e7)rEk$.iv˙wcӒ-({4N\ZuUXiCFsm.ggkmƣy^aKiPFv:z\nUM$yOL}vq*rM}uuD8S嚃n{i0y~NQZ^;vS׼:Ozvd{#?Mvi2Ev䂁 i77si=t}EdJ0$yE[1TzSǴV/JO޸=6vIL 85O4S麤~L`myIwN^* NEvzIiw$MzZTi>5q)[˔̳9_/CDmEfT&s:-XpTI~ҋԏtD^?$y9UZ>uF^>-:i%b݄H9`qwr$d_6 o~=9r)9Zpd=f(_wNKQ񡩯Q|lw|<@s4i|-sVSٵSyj]szh+9?LȺO\*z1/r*9r`*M}?--XnO/fa&S|L$}攏}߮7y+o*>}zpePrmɼ;_?&Y2BLw%J-׹ݒMG / =&>]inU9|/p-W{&Z0W\y;<}YHޯP:ZqΪ|S0y}yE׼*st/i yα}Mɱ>*_s^x5 Nҗz8[l*]c,!z#՚k>J.fWkRE?R? us[7Fr+پ2Vvf7es%v|UÿU޶zatɕi=d>L6ʋف4U9鲺 bSn ˘M0՛}_.kgr{'Jo{$zv=[%μNɫso9Kn3/c{mS9Nf}Av;ctQw~3ɽi/68Kz/P%/ޖ-lXOni&}۷UxYj}3h55wQC;+~eʘ`3S7)i[EN5{كR|A._`* OfXpVm_;jQ1Y@sܳe! =׫}> ts:3xgV"zml'Y}D8wvP4Pz}h mٻFڎ 8>(cUf>0KcbI 7?>_ib>}QֵyCUXzl/xLTFUQA73d9ٍ^e uf_Y*O߱;̃.2YBjKnЄ{Y2捷t'we2]ݷLKfZ58%6dr{{}CCwقXC{ۦ,N Zp<_v6s*gVg4f1;LcwdC~qcrY Ve uegy}7ma2@Dk[?+ziLs,)19FYi#W ɱxƺ^~;_F{hCXf YVT8#쇝=D-{u4L1yY WFٽS?`f1+96U&!klHuv9f$>(ǃ/rN%LJ76^j`R! +Ć^x<0Gַa$CDB*ش$8^,g >n, ،'`TM[C߁1rXm U !3.-;Ũg“2ݜj/"G>C1PT#µ !&+k>!B( BHDQA!B!E!B"B!DB( >!Q|B!$( BHDQA!B!#BHvYpALB!?[ ;?{G]!Q|B!$( BHDQA!B!cXY":8 .+ (|毭5iY|eY} H^h t%ꃂ0b,%M BlX'^*wd1 X[;;~±rQO51lYvk[ #iزĈnЪ0X%KO02A-FA,mxފR=MȌ/At]B)\{bL INEPZ& T KpSfTHRVW}R1.,?]#2,ưc#zyrrI򭽘GIo٪#.RXMŸthvUws/em[nO\k !Dlr}Fhce=9ձ $Inx mW1Zm`arJi跂mr-"+؆Qs/ܸn[9fO$u8=}zSϦ9O۰}M`f+cGZE (tצ-_ocefƬ"R KU^YnRu2Vv K+bKEc" Bbו6شrg3TԶm3Z+:icS㭊ҸJ#KO.a W,@!k:.2YdM/cj}1XvLstE9_ʾ|q=l/InSƾ f lqɧtYlrdܧ+N !ē}1ع|~` S72GnbPQr՟sٸ!_?>.iox-_!V:voo^K?1-e|_3{l+ǓEݢd>wԂEd:EYlr[ϵ,bIrX t1K)d`ϽʠElii9rƗ(Jh ~24}]fPVI4F7*bwJ;7BYiQZCn>Ӷ!@tom!_B|(mY}ո#h2o[%["泹Xq`lxJ=xsX8] .CcY CPh?i0I%Y $ݤX}o|iCvⱛJqHqulFc2?eQy̿mgv'XM}'DÁV;B\*8Z ,KvG:7U=D> 7}<ٓnCW$8?8|&+A?o6%<5˞'n <6)i -țoy6KiU?"!Q|B!$( BHDQA!B!E!B"Js>̙vB!$ rrr0`K ki IDAT G;!RG( >&LvB!k>!Q|B!$( BHDQA!B!E!B"B!DB( >!Q|B!$( BHDQA!B!E!B"B!DB( >!Q|B!$( BHDQA!B!E!B"B!DZI-[B`ǎ3fLR4A!TS;vvBȇCǎCB!|B!$( BHDtڅB!p*jhB1c8t8!E1c`…u"B"`.BHT5Bj8N|Tcqqq0 Q7h3\lGsFB(O%wW/Z6cݻȶ K<]A<2 ^7O_uᑚI_z_y}M }z=pԖ폮&`Fւp_Oiik6 J?Gsm?DhC7-iJvҡ'@7Fo>x}vK/io4^ǾFP/凫nCnB}[JG/O+om@v!RTPoj)t~)]p:mQSQBj35r2BdBZBH]F#BB!E|B!$BኂB!$<=Ѵ#|B!QVۃ w|@!v݄Z6|oÇ6|ڨ@b`0R:UӖvԷκ[;`۴>>oٲбcKW}h7V  ^ #~ayCix)8@v5#FBz~ Flr}F͛͛`fbfO/r&6|uR߆NYv֬Yw~moԦPc+ȗK&>u#sF.ftڅB!E]Xp!&NfR 8rd'-AnYǵLOykb9ٳ:q4vٳ1{lk/ZҴiXpa{K\m{<}cYLwl8f͒rZ_];=9_y=- t})u9NucƌڎF>7IܿiVw߲ EՙцH&R}iȒI޾ٳ|r2ThJӦ6rƜ:6i|=d0Pt0#Rؗ3&N2\[V.o=V{>iu >=3㺽Os)<u:)rso\QڎF> FqOO=uۡk d'^?!\߲\gAvmKvڦ=}<׬Yt-|6};F>W۹ooJx-7PoFWܗŽwLW: :Tۭu.QV}4թˀ3F>BB; r̞M#4G|ѪNk>"]g]ۺݺv 4|Bȇt4AԝH儯NI#᫓8R~F>B; r|D-'|uR߆h|(}mִ:HF>:1|m0s |Bᜓ'#~7"3DDno{~y)_}D=K;׳vk7k?m~痗p{x-1L˯/?eիWWʗ8?^á?Q?(M7&..ķz6H+"']ۦY3]nǦ;E׭LS|?I~E]/{o\}EĜ..1bs>Ӎ34F+cZ&ճol[=VDX%P1p| Ѩ1|rC+8=V&ճolځ܁F 6|L @'@i'gķz6!Hd8 4tCjNxzwL=?&YZ1ݚǬOmy:ɤI/kķz6y·-[s>9b8c'?ʇu"VeG1Ħn.ķz6M|gZk^- Ϡ|d.||H^M|gsnߚDmȉ(|[@!!5NV"qz9z6M|gy .2jAf=y^¶q'߷r@Dډķz6M|gSDAf=y^¶'.ݚϡǫ{l[=Kx·Yft}O|b>̜'D+ œblM;\6ճol[=Vp P>HQ>O>|Zķz6M|g#@@E+8=V&ճolȉ(|S>x)8119z~yD=[9svk?T,1g;4}|d>P> D1c&n.ķz6[xԴNdݪMn@|d !Ci=DH^M|gķz6E%3A1Y}ѳol[=Vf4|x@hL&]]Iwu%u]Q[giy2l./DDw'|~y)hD9Bb]|ј15AM|g ݉V/lʇ R>P>P>>0:TH^M|gķz6[dʇk"ia@(1xP>S>,yuc^nwslsCw򋇃;Ǔz:(Y| |{?P>u((#0ltsd|W&ճol[=-*m.((h%gķz6A\|@ Qѳol[=Vf|d.P> (Zķz6M|gE>>!3777);_}D=K;}i,-znCk?t3W_|իիWxlu8tcҎ⢺M|g<|6͚v;6p(wwwEe~}OIG?~/"wO>ٛx/z|dpu`lM;\6ճol[=Vf|d6FA?((h%gٜ÷fbinoҮf|d6>||H^M|gsN壦V|JmDx6uR@@E+8=vt"}ʇ%Of|d֏p9((XB$^f6[Wϰߢ|\9!~t]w%-Cf=z^Nv'{N=LyRIzWySs<%[1]=}/n>Kϵ.;H/}4 7<|tg; I/m9I!J*_͔G)󱟛ۘvz\u+f _sBIp6i}es9<|@\ޛ~UmJySj>|j (Cc.r窫[(`Nm|͓9 "vl2[uyvy1:-?tj`یJkɕ> ˋҹjS^LC1\v L:לXoSmCvZ1 I.1Xڲf@>mO6/M~qJPby|M/|MWرHrL}m?uQBʱI9v)m(fjCiRUlcx #~?yv]4ݘ؜צDdw_z|HZSӔ5ߓ7곙\a9e >[1_hV ll{)iKg9'"AH+|c,c<޹=FilvWWMn&ճo'N74LБ2d+0tWWr]MozicNQ> '=V|;=-)6LˁTBm]f떯|B[1O9!]| |lx<>RN&o{ [= ='Ot>69 N,vg>ߦ÷aJ}oė”p@>'\| kv 5!hRe>[=8hP]ArwSt fqJ}o0a b"O=,v0'{?mɜ|{T>N|lФ)ӵx չ7|{T··05 @U>*P TBU!|@U>*Pյ]\@ >YcXf[6J| !/&J"Yd?.}V"]R"5ٙewzp+ƒxJ%55µɚ @65YB`nq._}|E}ej|lm|jchٵmu,eieMΉ)Gnɔ|h̹]vChح|g(m|Bp$.U ovVg ,⮣>v:qwbYgoj᰷ʧG{sWiYjڝXYRʜs,B[KYUQ56BQ \'ڤ|)lWΩWc)vYn*;)0eX&dȦg,^rW϶,!yIs,1%X,v|!l9eGå>txMPu]/)]0e%ǥTI29riڱiL)w? B 夛Si1l)A!̃1pοD0K搑Kૣkl uOԲ˾2eKm)K1|bm&̮|Dz>M.SKʜn}0v.ٴO,r%tRlWM8gO*.I,)o%kL o۶6od k1*巩EJfG.Q<,r`l"я9,!mdl \>*.K}is @[om|89b~Kio'>0 >JɕK9.^Sn5\ODҟ-a[m]=T]O ٝ2|۾V q7e4PƢ'{k;ϔ'.ҧn'vysw}iY]-oc)]O| @걪÷؏6[{o<G=(IҞk4ZLpu Kis;-P >UC)K!&Kna )SR\Ƥ=lMX:vB﷔m%-᭽} >Ii'Xl$ODOM=TBU!|@U>*P TBU!|4~_d^SSҴe:49sN巜'=ק[s`*ijOhٞk|N)KvnĶ(핹9GP>fNybw %%nn.{SȽH. ]N߾|̅=vn[dr{!bIS^锁@lє}vχR?c҇CNc}Yjw|8PԤ\\*G*])micK_zR C8.0e/z,rke { v8TpAœ%6ǔlId>Tvk\p}swlb'\sGJԁ<!\C >:6'<_5ʟ3 |e|Rۘ z(>?^b>!v}/5_>)gLybyϽ䔿6s/Z⻜4S))9 @U>`  TBU!|@UyFYKq}}Uh|@U>*P9_*8x\|,e?xrV Thl&ڠ|cÀ0#Fr{ IDAT&-"œX<;s>6~8mڷ|c<5~)+҆O-[na>RΧmLر6M|0&xVY-?2j5:g ?9ڷMGI5[rY`!n u9'K_퓨s΄zO;)y1>!o(M;u=/v~)i_,ؾG`!,Q"_k֎V] L8] -Ȧc?v.C_>~K.[ ǐU[ s>:-C>fDӭ#3`ksb~|~KtWWsa[=VTqe/tQ. Ypv[6KY[=lND??;z||[:o[o!`҉HtJ{ߓ^䯾uW/b۪կH,-zMԓvI-Og|+_4syxJ?V>"/^~տxU/^_z_"ҋwX\vgm[=V&ճ%k]wwWT)w}˟$~"ҿ{󗽉7_ݰ5V\X/EDOZw)ׯED?/Y"y@"X҅e;q[M~'[-w_&?S_{N~""s© P']%;El>nķz6V|{}}}Jc,AFj|@!c|,᭶R(IJ$N/G&ճol[=C uV|P3,(ݐZ麮/޾mSϏ>zwL=i%Oa(kvܪo{vqq-p8+I[)K]M|;.ڞj9se' sKץaȹĦn.NnصvyQ$_Cvȡ;qLysl>64M O]9Ö@r1'z庤9`S3(8td]FSۤ3>Ćol>ꕚ^k v 6U[(|@ˡ4Xg}4Z-gP>FAR&ճol[=VDN@RX0W>}] <lfnvj=5r9+nw."pN,w;ݸAhs4Cs9Lp1wQ P> Qʇ. % H*FnYQI`t]|·lʜ8De]=^2|Am}!oC1yy,?nl[="(Q>P> NS|0D\bN7Mwl6㢹zJ6V|"(|@ Y=qGX 1UmW~|~KͣV"qz9z6M|g4֚W SW|{9泽OJ>)|@ QtKX (+W>|00H*}+鮮V_^>r޻+J7{+,-o(ݳˋp+bR%<>^K\ X1ĥz~ Q@P> ].ӂ+!|YxB)6"V M^f3(XUm~Bhz6MV 4|R*:~ )p|pGN@;?H^M3Gf|KUolWxKI)Dzӂ_a<cdz{j>%ZLn0ol[=VɫV|nUWz-NUmԓv[|lV8j,9Wo'Noķұ-ÜH9y z[X].17Ħn.ķz6M|gSDQ| +cDrҙt6|gķz6[|G"zjK܍᝻&N7on;xnәϾ!V&^ۅ]\0,]NǷ3ywSuLڵ٤Svq.œ|@ {п;ǰC \|d1P>Lwؠ| Gڤg|~y4ÅO_Y=i3N_v'CP>2@( fxX@ !uC$ŔN,6m*4\//<[XYhn\{oyn][=[9s>O3#^X.}|d1Δz՛شM9(#0ltsl~|wtճ[ǸXNlS's72UJvi P> Wg\Pڢvرyz96M|`0~_ճ9y=}CncߊpKpgRk[,Ɣ( "r4m[=6bV>~kU9grD HUmYOUmO[:BۭJkUbV9ɪt}>n}VP:jSaU[Vu eVuolNЊ֊!iܨ i[ʹ#It)ں^j+``GCeu`lM;\6W7"VfU~g={͑iWԴcmm P> Gz,\P>6|ЃԳol[=i@]2/mFЊG۬UV|x.xZS z>k T9Bb}sT>VCXrK˟ʕ))za\&ճolNVϳ'Xaڜhs%G̯ۄ'>2s;8&2%|lT (rЃԳol[=G'e5EK6[duʇ+PQ<|C(cx<{ٿ擓,4\$(4\fq<(X=NKrON.cܘVl2vgķz6Ab| Q)K ol[=Vf|dɠzQѳol[=Vf|$2EV[J)o6Q&jѧn :[znәj;˯/?eիWWp8!'ƤcEuM|gߎKw8;www]Qv}/ߋHݻ_&޸拞90N6e{9(Ɩشe y|gķz6[#H;]cDrl[=V&ճ"(|@(`Qn*sA٪s[zjG(XtL[+\6ճol[=Vf|dQx8@@E+8ճol[=-Da8@@E+8=V&ճolG"jzՖAicɪ:3`U[Vu M|gߎKǪj[ s> s>Aםh19 "vl2gķz6>h|$E (˰_Ƥf})md &6>s^ODcS*G# wnQn.UÇPV&ճo'N74ZT>p N+.&z[= UPб (V||TC*CU!h.uWn>-o'Or.R>6x<_)' ׷SߞyͧEB:Ɯlb'3gxRmooS0>qR|KoaJv^ .@|4L2~lv[ .sA )c3i8>qJoajv01'Ԟ;Iod=g*s\uBm>6LhRZ<Ʉ\=g*_srz[]*P TBU!|@U>*P.e.X`Z9,o;m㭂`,rUM=)yAiXϧ!}_>Z}.; @=|\|Ѕ3wq*P TBU!|@U>*P T"֘g/-{5mɤٞkw*ԭ<>2ߘkx  MJ bd;E~>|6>8rR[\&R9R ׼LiK]㕚~`q)){c e^+K8cV xħ2 r'-9)'%\|>Q(W. \CW-@sN~I)On>c#5}'HD夙b>OIy8aB@o]>*P T7oț7o4 |tXkr@#0BU!dxV97o67`l.||S,||@U>*E.0 g۶6`F17|0h!!|/ߙ!l׾p(K?qKi_m46Ac3̧mL#g~:`Lx6NWҾc҆)#v_Kqi}0ckd]_b.$`E[^RYhq"6=WR6R린W?K¹vR]穕55֯96>|:okcaC)o̜XPŗ7ޟ-)6d Ko7%3K"C$brNSj&V'TY8)Slǎcq{ qH)7Es)6MJ;vS/Ol_#f)P%qA.S&.5Ȟs`N>DxJR"-ɒ?K.[ ǐU[ s>:-C>fDӭ#3`ksڂtWWsa[=VTlJWJ6KY[=lzml݋_""'-""laְnoO?Yna?M74g*kߔw/~Z~^DD?#/_}W^^c\vgm[=V&ճ%]wwWT)w}˟$~"ҿ{󗽉7g°Krss3w1`et]:1 IDAT߷:IuUeeuV&ճolGo P>1VAP>@DډķӦ޿DM|gsQAG"C94tCjNxzwL=ͭ[giysݖ3[V­jliӉ=մoǥV[ms>ry|0D\bN7Mwl[=V&ճ"(|,X9=wl~J$N/G&ճol[='hBr<}۠|,X()CDՕtWWp{+//{9]뽕zz.z.//Zc@W>vP~|,X1c82cWV M^&ճol[= KʇK@(P>T>\ U "Drl[=V&ճ)"'G l^9a9@5R>\,s>\J$N/G&ճol[="OAF-P>`y|Ha&21X=-^nwsr=-\v=],(P>P> N J7Mwl[=V&ճ)"`i|@@E+8=V&ճolځ܁F P>2@XFAR>B|ѳol[=Vf|$2TrMZvǔogiyO?mMgn=hݖ3[Lċ_/^~>ƫWW^pCO.JӍI;泋6-`ճoǥ;EםLS|?I~E]/{o\}EϜ <kXՖ9#%6tsd|W&ճol[=Zjax#kcDrl[=V&ճ)"'G |d

` 0 O8aUֳۭzoR%|fU'P>2@X(]P>F`"ZM|gķz6Y#@5R>lP>V"qz9z6M|gټ?y@S~`y|@hTmV"qz9z6M|gٴ;ШAGL ՕtWWp{+9777EDDno{~y)_}D=K;׳|vN.x>k=WgwVeK-vtj[&. _j;`s> |,|9 *9;\2iK5l[=V&ճيoy!>#k |ؠ|lJ$N/G&ճol[=l[Yٶ)c,(c|~2IQ>O>|4n-s -\^=g-((AP>z|2:||De]=vt&0aslڵ٤P>2@XFK#|J$N/G&ճ9inl (V;P?`0cDrl[=гIճi #Ӓ[nnno*;_}D=K;}i,-on:smuۓ[Ǔv[|Vky?6WŪ6ͭ֬fkxvqq]Tifv;.]hUЋUm9Xu9!"r.i&z6M|٤l P>Qѳ(rGzΩ|mrNг"(|XXuZ\J,Q˙>(2/A&V&ճ"mS9n< Q'[ڿҋv?_ߜ>#1> #VT ޝ'd{n[v==31uxG(zGs{G3G!޷ֶpHwKip6&5~ִv;9<|@R;I0ۇۊ`gusMfI-oPб}{22'׮oaޮ~cp)T^W 94;rlM;\6ߝ(]o]^_\td-!oڱ\=٦Ut>8|kx~y)띋,X!l㫮 칎g OyomΓJjni#sN\y5G(u$+c<1 Bpa@qKs~|~as(z6͐oCĨz S\oln[d~n>@$=PϛxR}2Ɣ_P1 8L⣻Jnoc 뺢tsR]\|vq)}yj빐}UOs~l=_^P>To>P> Nqfy*D@hL0he D5mnʷ ճkkV|"M(THa *f [1T y"Dr$x[=9>~<~jûmٷSlݶD\ÀQkğAAǸ|ДLv(`@hL8g/ZO{!ճYá+~e [QS۬ƪ':އڦ̗Rϥϡh&X;tkiwn9G5Yvڲ V-L'U=dً, ipvzlic3 ɪj[ s> pՈMRly3%ɦ'D+ hhvl2g6tasuaJhͷ5m[=EP>2@rz͡c;c|h+9y|@Qѳ}^P>Lns&>(UH6-0FEpucڱ}|s𶩜ts!^ϩnw jsICLnvR߷RO[m@bב:dšY!:Vl2gķ}jolv]wK0]Ab*CJւ)]dsI|!!>`(Z 6t))"s7妌-V&Vf+m P> X#e> * Ǹ]=cta5||H^1+ə\jʛtieSvןϼRl D lDXvsU[{Xa=[sU[X[sgdj'"|S>qXOrZ8ǣj[|Vj˪.X,]jC>=4ͦ/פu mڌt aKEkU[_vrl眰>j˪0b nJgKRMhx9)|8s;Jwњ;ܓi:|0cĦn.vǍaȹ@ 3HKGc=;˦ok[WMaW&촾y%rS6t sӆ=9ag8(|@ vws)/z'Xy|Ygj𤬡ͳ|=91vYyvKp;Z+@2.`} dʳɡ2D8We <]Nԋ{y8P>@Dײ=|vyM]y{h{a˦+¾e>D醯//> Eہ)ʇf\Eqz|k~~y칃YY=. ;݇l'{fۇxZkڭ/{hwk|@o6;驧O."ry{cߗ6#9+s=4M }L) K0mϥb  t,Gy8 B>aeJӍI6e'#k?ͤ2pVqe{)0/OY 'ַGEzK:<_ϳ'~w(ӮwV mN>3D4io]%st;(M7ꪉz[m.n[ݖϔw?W>~Aa9 [ ऻxbl|NP[pN8oziIa@eZ2Po2\|V|o $[(c.r0i}}[1O91|Z-CaI&vb{>|'6% S|''t[m7LN>[X [ GÔ*#oA0((0 ;2K0S|a cOyO`9ooK|s =Uw-ԆcÄ&ENœL%s='waBU!|@U>*P TBU!b/^5䡾\mmUp tTme%ȿ%޻O9.L|9ˢ >rOf|' >?'|[fuN}r1?>Ͼ9sJnh9u_?`,VpfI}z=jy)@ΰHny}r/RV| If]'Ry`"<1aL\V|;q=0}Aͽ`D̡`,Oj]51'̥l^)97V̼%_&NG>K;^-T1DATXX!|=|J*%擐c >_v`mrLvM;;.r)nO(gn?l9"W21.?M|:ў( &b!cn>*P TBU!|@U>*P T"֘g/-{5mɤٞkw*ԭ<>2ߘkx  MJ bd;E~>|6>8rR[\&R9R ׼LiK]㕚~`q)){c e^+K8cV xħ2 r'-9)'%\|>Q(W. \CW-@sN~I)On>c#5}'HD夙b>OIy8aB@o]>*P T7oț7o4 |tXkr@#0BU!dxV97o67`l.||S,||@U>*E.0 g۶6`F17|0h!!|/ߙ!l׾p(K?qKi_m46JJUX”ƕOIۘ9Ϗve~:`Lx6NWҾc҆SZ=?2ckd]_b.$`}nhy%tIeѢ9 >b\I#OIb0_=Ce x+Cʶ9/7ܞ-_xCMizV?c_% W05|L!d12tjb ^|elcOQr_ϡǪy>~⟥Kiϡ.\ÅƘK \2Swl u[J:tR4]B)uBߔM}~!>j3?嚂 acLؐc,hg*J˷h᎗c[B[«e̼֩1ܬ~-YE1e~[cI2)8f鉥 l$Xԋr&Nm+t@WF%/[h!Y\:a׻nC {1]p?F:&hXM˽/vck*k1ߔO5־mr?JүApcM9IO[XeBI-GlϹۆK1xc7Eù!Xh bܴS/Ol_#fuKV] L8] - 9Y9+aJIoPGX&K>,l-CV|l s>*p !M|փ ]h p]]]͂oz[=ZS=D>)]mp*s,emķ۲YoeEv/^~ՋRpׯ__Zz>tbdAr?(J75ߓߖ/}S޽{'k_yD^_1^zտz?MC8~rQnL16]\Tol[=V|{8;www]Qv}/ߋHݻ_&޸拞a .uw ~ G&]UM7痗m[=V&ճيouP>2@B[Ai'gķz6!F: 0ronn :&YZ14nSO-v|niX% r nӳol[=v\:n=Ֆ90DD;S"r.i&z6M|gx9(|@vyn/{o|KX}$=V&ճo }4ɫ|{99y@ Pˇc%G,@#=VJ+y痗=J=KK=Ns_\c|}Ę;Шb; 0ES6rT8xĔpp?89!J^61kķz6M|gs5;NacYZZeGՉ().Ryj"6ķz6M|IgBG ,V9a)Rcy(%"(]->Bʇk]fd8/Mj%VYM|gķ"OAF-|, Vf=Yt[ǓvKv<A5|M$5LUmY+Xķz6M|Lj˪0Qju9x)aKsM|glڷ ".!P>P>(DxP>P>FH\fӽevto5m6[GӰis5@`f|CH"H&{9lt u·z6mA1vGk|$s>YOc~)@IDAT9X wvZڭF|ne2i<|~=6# g+C[:6&90fp LA:-%t2cĦn.MVo3e\&|x\MyּZA@'eZ|||^M|gs\DMx[9Q>Z#;֎TR< !5NAD𶩜ts?lEsx [ꙓnl,HV[\d# ȥ-.]P>F`"Z油݅lַX'~|LbSOvAPVor8^M|gsV"'~|Lm sP>|d}GP(+S>sx<ͷ}Jķz6gm .j}x[,:)@÷=NJ+yœt""U=/޾mSOæ=BiۓtSHl<>^Kl졖&^D?Ǫl[=9XMVfsu e3{2|퀚s>Jo{(ɚs3\,ɰԔa|W&ճ^υI.j|=Lpw$6G9R>)w(R P>`jS>lP>/H <|@/G&ճZOlʷFis1\e@P>%|2bG6񜼞qpǪS=.Z (quYR[u@`kwWfsվ8m\omqmP>2@Yb/]Vvc rl[=v[6 k-@ͱV,=`(ķz6ҮI mیcmviDiɭV777ŷjS/޾mSϏ>tx|ɭ5ʛnP>FmK;shZ3ÞY_jk,Z)vʴ6KY+.޷+9o V]oCچ^j+`l3c,B?#s>@D%6tsٜ}|w6[OoҮf5ߚ9G6'H;f|ddK຅v#crl[=޴]MumW]o[#kW< (g+m1Gjzrdžv:Vl2vgs6:mVL'Bմ9o]b+m P>`lyLckx||Hͪmy [=ɾV|m-}JZA٬[k%nlR8NX[=Q>çJ a۞BlJ+yœt""U=/޾mSOz=/.D~zV;iv{}{Oq<_^dڊo{,~j[&gMUm ӳ*Y&ڮlU[{n{l?W"~s>`lм!|d·H٤J懴2i԰oGTZ]м  Y!lbPS;R>A,VjNLj|L #5Q@ZA̷ٜE-Y!"|[!mCec!YDP>`1v Ax"e~)KH^QP;Җ,du-dvm QF}[f|$2\ 4tCjNxzwL=?q4K_b=˫nfg>J9y7Dv% 3בYs>96m6`i߶m!y6-6J6yʞ$;Rnlcs>F`Kl,.BcIiݕdMv6[#wFe9P>P>FJ$N/GfԷcm\ol&u(g=c Ӊ,ط"(|:gG ((E[M弗e\t"zN/va**i>s(|(̡|+y||c"Z3&k"|;M(F؜8b};qڵl P>`rP<ƣ|0g&cDrl>6Gi9ߎ{ކCz6[m|d1S(( (Z%?s 7l6n:ENԋ{y8P>6~m;<'pVKpa^k7izvq_"R.d뭿Cw8ۖK^>ʼm~/1;qre]RjU<Žlڞ#m_V59?RӮ-9x " @{鮮[y0ztNnoS)"{wwÔ[w9yvyqwp{JeM|gķz6tlu{딞h oer&n5VAapoӓrVivpԤCC)!GxTB<ķoIzL|{N'9mB)wq<l-? 1O91|Z-Ca|Yb'{ [ʃڶJ\!|'6NO|-L n }ŷhP)UFOuV|;-ԄaFA hݔ1]4mR8uǷ05 l{ȓ|jO$|dkAm>ߖL|{[ dž M2]'PK~÷L+|{N\/| Sð TBU!|@U>*P TBUQ]^kL >_>#ho;m㭂W e.앒FJe))ҼZ$`.|Gyyj%e:m#5ύromTwX' >|{?׎Ӆ<`5TK۾-aǪJ%h]P}{k?<'woC>_1.Cb0%>BұPo1f+EtR쟒k]|Z.Nw XE-u/v1ee>/?ņϷ>M|4NBUdު=6L;cCPyRf]Jбp}g)Lɢ]bs/r p6$k)' TBU!|@U>*P TBU!|4~_d^SSҴe:49s[*)Oh{O7.Z9UdߟK=׮Rߗ'@o̵|= 6sS()vswٛFEwa]prc.|beJOnyR({큆&DNyRzS^GSvٝ"?KmIR\e/S D=h4S.l.#` ))micKZF`&`!hB`=P9}]浲=;`37G|*Ch wRaNcciR2f*5.;6#CtyL@Cg}9ul>OJyrIɿ9'k?g(@B1P>|~ǵn |4B_j|RʓϘHM{)i=Rm_2Qw9iSRs> ??$zbMD>;/`!$L74DíP T%{Ǜ7o͛7eܿ_$>K _xd*9F?9Q@R|@!& `T VjL-.ݻwE/?>]g>gc"׿&g_?;O_1Ļy0|oNokv;yw"~֋Kx >DdP zǟ@쑔|]/Ev^? 3),8?o<OjgwPLq$IENDB`RRD-Simple-1.44/examples/disk-capacity-daily.png0000444000076400007640000003663010746154056021371 0ustar nicolawnicolawPNG  IHDRDrItEXtSoftwareRRDtool, Tobias Oetiker , http://tobi.oetiker.ch\a IDATx} ř=.QA( #q1A$q&!jAߙd4YGEEJ(KDQ@dG$Sj}ޯow}_k9U_NWw&sÍ}|஻f&4>@ _F}>P>@I O}J}"^3?Cf}"N8}>@TPī>c\A8Eg}>Pa> l3f B養"^a'u*l}>9=/E".:F:9}`ݺmlnyWĹ"NF}>PjXm꫋ͪU qSE(!_~y}Uu|ӤIX;|~pǿc=MӦMkGa,2͘5k>.hQ)≎&L{ӳO)v6Ņˏ?c#M{0W_n+}uwҵbmK?k' Q~w{"ڪUk(K_s\Ŕ͊'[+|\I(MbrPl{ŖN?`kbmW`>K ,[o~_)渊)_>1N5l㦛zP/V.I;G+ӬY3Ӷm;3qCoꫯ5ݺj֮) -I=x`[AY_fW'xY|u6_rB˾_W]V? u?]f۶MgQ.oGJG>6v^ ~rE>-9,Ӳar+GG6dn ۀ{w0!O*^ieΜ-k  c/jӣi._tW^y>y5יNumCxl_AA>L#5cӦOͤIE(ۉg_r$t֬ƖuqJT%wP&MO=58ѱn#:.t1JM=`zO»I$@Ix'XKm4׎äٹ$ڕqyg/\<|K#l[9Ͽ\/w8~mMm!.-N؋ETn=qʹ:\J\Z|<%ܦeL|/3"ٕq建q0)/\jW6o"1LM Kݸ,>eʳΐ{+9. j8̯kåiy=r._ q1l]y[(x)Fǃm_<~mOnoK!pq=.~\[.=?W(?w=ִl/]O.)?k{"-o#ow9&:q0)/\1]9}ԷqbZLX➾lP>\;u*Eg'TjS^>)KcVXs5 'r>S"Wn˽o OH?ɦs8"KcVNĹ瀍 }>7ANp9ץvr%$.ڵ77(>g'/A$+^T+R] &\rKnaXk~[S"*Æ ]܅x纸F,Z1oWaӴjqV&Mr6;~8bF`[|%޶m;)9VI&W 1/j>s~Q4o M-VjZ$~LnfOr v_98>#9qCVǍ{?]=n02XHPάiSp|Z6-XȭHC[ǾV2q=k6)Ajjq!XȭHC[ǾV2>])6}%|8qi^N`#~n\#-orXB)8JE\8HM3xX-r2 sriqh~ؗJPWO+|i^N`#~n\#-orX",C8^Cٯ@+VpB(lac'ib8šMr6mv*?:MiSc"~iZ&m2?ršM-X*[P)"tWUS"ޭ۩Xy7k,8vڛf6|bڷ?0ȓH_Ipq_"&#r84%˴g-1rfݺmV[φC$K4((DM' GqrӕkJq?|V̙gpĵmlNJ+s+1o7b'@ .y+U4mjZY&Mar6;~j[h6艤3N)}^i#m``&N|p"\ϠӬ.X-GBKӦX-m[[?7.GˑvRtjJ'Mz̜p‰)c9<XCػwe{<7O""X0br 5j9ȭ4rCi9`E\x9J VcE\nuiiŕcJLi`ő[i6ʇr8E\E$Z -6hZz>Fn|H1-GZlZh*8iq6 /*"+qiץA*롈q". 4G1`%jqAȭ4rCi9`C8D;M:+/N[]ZjqSZ{8ؠjqVh!ŴiqZDZn?ya9HT[E\nuiiŕcJLi`ő[i6ʇrjBuS-|owLqiեFn|H1-GZlwB\V4V-/(ec#mO=5 x؈pixsatj=G;K2=zfbo/_:N4 -V+ǀګ-V#RGm)H "?ٶmymydԩď?9蠃1x׿]=dƏdnj/E\nuiiŕcJLi`ő[i6ʇrj_^N56|i4T-n2'B;C̰a7O>Ŝuۮ]`Ua'}E2\;qiեFn|H1-GZlɃWhiuQŧH,y2׮'bf|ъ5޽{/{ygͲePĕO-ԑAjq![)ǩH#RGm)H Y Nӆwjȧi^N`#~n\#-orXܖJf̘c`".UV iwyi޼yN2z8~ŬW3#q)Ajjq!XȭHC[ǾV2q=Wak<\-#"ݯ 3~$wxQ$?N E\8HM3xX-r2 sriqh~ؗJ6'x\]<^_M 7hyz"ޮ]{F9 /ʼn)Ajjq!XȭHC[ǾV2a-IF_v]txΚq\2px˖CbCqy 5b8,ύrš]c_+[#k~%K/8_mXKO_Ōys};0 ݆Ϝ_px7[¦iSpZ6md~SBCAO$Ѧռwvζ.bpܑշ3qC6>nA+~ X`/& /MZb8InGnܸ-GZoZfަN)V\N޽}:ggSq7iŕcgaUˑGn(RLˑET+SĥVfhZ\9Ĕ^-6hZz>Fn|H1-GZlViz%0Af-+5". 4G1`%jqAȭ4rCi9`E< .k֬tp}};. -dݺmv*~ZW6|9ld6MZcb8In}Xo6Lmgo|iGJ2|-VZ6ɭs/*8@GJ2IDATr'?JMT"^ *ӭ|hš-ēw~[AU M ? mCro ݿ8b8jqhF}T~5\cwyg>S*1fhZ\9D^-6HO?rL3aVqy2,}^{ J. $~KfQ̶jE".7M oXšϺchZ{84*a=|Db[^kxyfʹCnI-`pzU8ԣjq &M453>X=#ڲqC4I,cZ'o)3eˮR\`$mf՗Mr+J嶔+3اá$5%=zfN:9h/] S^0˗Nv*[;iCrxD|V!Oy/7ځq2L?"qDn'qjJ||۵ko6noID]pȕ𥙘X-G/CnIm܆$ʕf]ņ.&E|cDe{=pq𥙘X-GA%rm2?i8"[#iC5{ mĈ_k.p[6}jӰ"\"x-f̛M+P6|iJ6MZcb8In}&C͎Z z"Li2뫩8{;Uua6qC6mܸL#Jfujq8 L_6X-ǧjqh܂q9Z8$Mom{?[i_*US"뮻ڧӻwi<@յ{٧{<==q70br X%Zlb8r+|4FbZ8P-"pW׮o"дה,>WpKK3xX-VbJkX-J=M#Q>#-6TY(qme/Fn|H1-GZlS%_TOp*` `ő[i6ʇrRSˑWn˽xtᤄ+qiץA ZnR(pKK3xX-VbJkX-J=M#Q>#-6P+@d~IR6|9ld6MZcb8٘}tʟS5Oһo޼$CMӦ8X-dr6?H:Ӆĥs4jq3n Zl㷏7Mr\MM;/7M_e#b)rfiZ@&}ǥV2|:l1S%Ib-b84ߘEퟙQG%ae#_SL&&ljx)kS*Ob8jqh{>Tn16o"T\7M#ZE(!iq~+sR+[ &+q7TizN0"K>(!iq~+sR+rUX/]qY^gW'޳R?L3xX-s2߇/XqE yOCm[SZR3X7[ w̘QĕOu|zc:5STOCoޘv.!׫3ކi*bjװ/,xۼy՗{f'jM)Q\Nor?KС?z$Hbdb8YtZ{8 aǏXXfv٘7V!.u~}Pa f!2ZX-m/~Pn﹧N/`Zm.w)S+Xce0C`]#ˆ! tqeuyU*l6X-Ǭjqh1s{Ci1=/o~bK[?b{QA8Ĝ3x'W[W.\hZlb8InRAbaa8w!<;o"޷3qCq0N$^,i^7EҴjqiM&5EnfOs,/7 핦{nu})MϞe>H4"nZѶטpVܒ|>R۬/;n-4,O_E}P15r6;!Z[=jtNڜݤJn-}>P>@D;6qP)@U*yKNO2@zjFgxR1͛9{O:[ؐ]߻}+G~Cq{cQGcѣ'ϯ|Y?tcU6s=(}u)gz|KO:;7ىV۲r(m۶,^ց{E,ޗ^΁kmݺÏ}}.ǡ/2p[c)gzCq_j~WDžC_5d5:RD;Sg8ڱoWH߽{O/})jiBOqݡAU֑8|jWnN Yf.k۰g_3x; vs|~KG6b9SYg},XVO0n/}ߥ!"pG_zi9s6msA9<֬J z>̀3c ܂oFHky7/=~Cg-1rfݺmm͛77Ҵv|WtEnq{#~e}uʔg틫\¸u|}ߥ!"pGJ mAX81\:B% 5\;sq߻}W asX^̙gpU%ʝ6g}ĝmt}K]Wޗp[YhiO8Mǡ/=reŭ;ǫy'8 rsmA·JÄa No}R=ޗp 6 ݕ~Munq7f.?ʗ<-5A̗_^dzuBn jo) "~i_3]l:w>ڌs_0]}vKx'!k_;\xasxU ]v%"u]v@h>@g}0)@E"N*xv\-^p@}"N8}>@TPīxF[3ZrJNj8Eg}>P>@ҎE}>@(P)<*x&MLm=R\;kҾ6G{wIۏx-%IK9)4ޤ}v_›ϲ_L/jj8< HZvWXloO*LN} ,;v(~|:wƳ)זK챧 F8`H_(T\z%pY U#2F8|:u:.4g7wi'v]iRxǻﭟ~o{LW]u=ݷmI'u >F-Ǐ:GRZc!B@#}onI q&Lڨ'vv#1O>L׻ qwHkڴynfť:r qoz҄iE\:YYOh\>P1"~=L>J2;'N+Mț5kع0R9U6򜽒H0ir"9Osue!iDA!0wW6L#\tUr믿c _rO>@@EOopk F}6AP)Z!9\NӟwwJ7m4$_!yiDǁkW7..L}n3 ^MF}lj~v4E<ǃ!ol|"DSQ!l6`xqz9o9O p.Oqa;:юĹ;ē"-v?'ȑ٫DxK.xVM˖w`[v|6y%J&OwqǝW6۱*'YpPIǙN}> @D\itd)vOPy/ e8)@CE"N*xv\Cm<@eE"3p}>@RWi,2΂}!}"N8}>@TPī̏msA*(q@EJ;gq\H?d5k>67xK'b)Ŷ|&߳?N)wxrGyeϾgu-ĶM#an_A7o> {Gm5~QAu.^tr}4m pxA=zf-Z%{.͛?3]v ޽WA줈SS9ϱIFH4˺\Z[޵´xԃos i}~ƿKa")|챧K/-E<)O[8(TGħߦxŶ5\WP_;ǖ0?M7[{lߦH%wך <8E<s0's6|gX!7f n}0 N;dw\Lq>KNI'uquΘ1`zǚA-|ǐd'>{= c7Zp =ᄏ#:./p.f0aPO8~u/sW>§ydJ:?O^]IyrBpXAc%Ga7oc 1GGLL6|b'nORˇxiΈ_sd}m[F"t nǥ! _0/-sx_._ `osE~"3^W8}lyN!'L!=)xIv|オ7.ǯlۺE} N xM6ǞWW7xq?4C${/ K[b}Ͱa7,xh[¶Rs*LG}@tm{Xb{Đ رG9|]9ˇo֬Y0JƪXb1z0Ҵqi8Ig\!p;YP{M8g<_^2Ƕ}8ëCv}ThIm/^wƤp~!ϻ"'}:|8'*Y WNpݽvӕ-$˗;QvۯM [!P)E9sy_ OU6~78>.V1gqVJ[꟥KWEw8 5DMlVmE<1N\җܫ ۛ^\s֭KE:|v,YwߖAe^}IyIPWZjcR}$r$kk̘'WF %\R{IyI±l_ќSS9Pؙ/z'oq2tK˗=iz},PiӦve~A{F}a桇v0X%!oQ.PNܷ (oԙysuJF] Sgsϸn>^\ޤ \#vh՝d_RK /]o.P|8R:O?M:,_?6~$4﯋WO%c{h.4"#PX\i| .c;?o^ƥQ 3>ȣev<#߯ߠo ~1w]rA{]&V@tt2p"ēKWP{Op{}l!jdո+ċFıÿIa@&6-YP' +KseЇÉ 'h n_{mY>ʇ}c< WUvζ>w&t.- ^R;I~v )/0%_ɱVű\ >@$>`}"NQQkl@N*xv\c;rHE"3p}>@RWi)9!'@c8Eg}>P>@Ҏklg<^@}SyNU*8?#%'>@hl>@ >@(Uql}>@w3r_F}:| ;::~ K:IENDB`RRD-Simple-1.44/examples/graph.pl0000444000076400007640000000225710746154056016472 0ustar nicolawnicolaw#!/usr/bin/perl -w ############################################################ # # $Id: graph.pl 965 2007-03-01 19:11:23Z nicolaw $ # graph.pl - Example script bundled as part of RRD::Simple # # Copyright 2005,2006 Nicola Worthington # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################ use strict; use RRD::Simple; use Data::Dumper; my $rrdfile = '/home/system/colloquy/botbot/logs/botbot.rrd'; my $destdir = '/home/nicolaw/webroot/www/www.neechi.co.uk'; my @rtn = RRD::Simple->graph($rrdfile, destination => $destdir, 'vertical-label' => 'Messages', 'title' => 'Talker Activity', ); print Dumper(\@rtn); RRD-Simple-1.44/META.yml0000444000076400007640000000117510746154056014465 0ustar nicolawnicolaw--- name: RRD-Simple version: 1.44 author: - 'Nicola Worthington ' abstract: Simple interface to create and store data in RRD files license: open_source requires: Carp: 0 File::Basename: 0 File::Copy: 0 File::Spec: 0 File::Temp: 0 POSIX: 0 RRDs: 0 build_requires: Test: 0 Test::More: 0 conflicts: RRDTool::Managed: 0 recommends: Test::Deep: 0.093 Test::Pod: 1.2 Test::Pod::Coverage: 1.06 provides: RRD::Simple: file: lib/RRD/Simple.pm version: 1.44 generated_by: Module::Build version 0.2808 meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.2.html version: 1.2 RRD-Simple-1.44/lib/0000777000076400007640000000000010746154056013764 5ustar nicolawnicolawRRD-Simple-1.44/lib/RRD/0000777000076400007640000000000010746154056014413 5ustar nicolawnicolawRRD-Simple-1.44/lib/RRD/Simple/0000777000076400007640000000000010746154056015644 5ustar nicolawnicolawRRD-Simple-1.44/lib/RRD/Simple/Examples.pod0000444000076400007640000001526310746154056020127 0ustar nicolawnicolaw############################################################ # # $Id: Examples.pm 756 2006-08-24 22:30:54Z nicolaw $ # RRD::Simple::Examples - Examples POD for RRD::Simple # # Copyright 2005,2006,2007 Nicola Worthington # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################ # vim:ts=4:sw=4:tw=78 =pod =head1 NAME RRD::Simple::Examples - Examples using RRD::Simple =head1 EXAMPLES =head2 Example 1: Basic Data Gathering Using vmstat use strict; use RRD::Simple; my $cmd = "/usr/bin/vmstat 2 3"; my $rrdfile = "vmstat-cpu.rrd"; my $rrd = RRD::Simple->new( file => $rrdfile ); my @keys = (); my %update = (); open(PH,"-|",$cmd) or die qq{Unable to open file handle PH for command "$cmd": $!}; while (local $_ = ) { next if /---/; s/^\s+|\s+$//g; if (/\d+/ && @keys) { @update{@keys} = split(/\s+/,$_); } else { @keys = split(/\s+/,$_); } } close(PH) or die qq{Unable to close file handle PH for command "$cmd": $!}; my @cpukeys = splice(@keys,-4,4); my %labels = (wa => "IO wait", id => "Idle", sy => "System", us => "User"); $rrd->create(map { ($_ => "GAUGE") } @cpukeys) unless -f $rrdfile; $rrd->update(map { ($_ => $update{$_}) } @cpukeys); =head2 Example 2: Setting Minimum and Maximum Value Limits This example shows how to set the minimum value to zero on a datasource using the RRDs::tune function. Use C<-i> or C<--minimum> to set the minimum value, and C<-a> or C<--maximum> to set the maximum value. See L. use strict; use RRD::Simple; use RRDs; my %update = (); my $cmd = "/usr/bin/iostat -k"; open(PH,"-|",$cmd) or die qq{Unable to open file handle PH for command "$cmd": $!}; while (local $_ = ) { if (my ($dev,$r,$w) = $_ =~ /^([\w\d]+)\s+\S+\s+\S+\s+\S+\s+(\d+)\s+(\d+)$/) { $update{$dev} = { "read" => $r, "write" => $w }; } } close(PH) or die qq{Unable to close file handle PH for command "$cmd": $!}; for my $dev (keys %update) { my $rrdfile = "iostat-$dev.rrd"; my $rrd = RRD::Simple->new( file => $rrdfile ); unless (-f $rrdfile) { $rrd->create( map { ($_ => "DERIVE") } sort keys %{$update{$dev}} ); RRDs::tune($rrdfile, "-i", "$_:0") for keys %{$update{$dev}}; } $rrd->update(%{$update{$dev}}); } =head2 Example 3: Creating RRDs with Different Data Retention Periods The second (optional) parameter to the I method is the data retention period. Valid values are "day", "week", "month", "year", "3years" and "mrtg". The default value is "mrtg". The "mrtg" data retention period uses a data stepping resolution of 300 seconds (5 minutes) and heartbeat of 600 seconds (10 minutes), whereas all the other data retention periods use a data stepping resolution of 60 seconds (1 minute) and heartbeat of 120 seconds (2 minutes). use strict; use RRD::Simple; my $rrd = RRD::Simple->new( file => "myfile.rrd" ); my @period = qw(day week month year 3years mrtg); $rrd->create($period[1], datasource1 => "GAUGE", datasource2 => "GAUGE", datasource3 => "GAUGE", ); =head2 Example 4: Drawing an Average Value Horizonal Rule on a Graph Graph parameters are preserved and should be passed through to RRDs correctly: VDEF, CDEF, DEF, GPRINT, PRINT, COMMENT, HRULE, VRULE, LINE, AREA, TICK, SHIFT and STACK. Use the VDEF and HRULE parameters to draw a horizontal rule on your graph. use strict; use RRD::Simple; my $rrd = RRD::Simple->new( file => "frequency.rrd" ); $rrd->create("day", Frequency => "GAUGE", ); my $end = time(); my $start = $end - (60 * 60 * 24); my $i = 0; my $rand = int(rand(100)); for (my $t = $start; $t <= $end; $t += 60) { $rrd->update($t, Frequency => ( cos($i += 0.01) * 100 ) + $rand, ); } $rrd->graph( sources => [ qw(Frequency) ], "VDEF:FrequencyAVERAGE=Frequency,AVERAGE" => "", "HRULE:FrequencyAVERAGE#00ff77:Average" => "", ); =head2 Example 5: Drawing a Fixed Height Stacked Graph use strict; use RRD::Simple; my $rrdfile = "vmstat-cpu.rrd"; my $rrd = RRD::Simple->new( file => $rrdfile ); $rrd->graph( title => "CPU Utilisation", vertical_label => "% percent", upper_limit => 100, lower_limit => 0, rigid => "", sources => [ qw(sy us wa id) ], source_drawtypes => [ qw(AREA STACK STACK STACK) ], extended_legend => 1, ); =head2 Example 6: Setting Custom Graph Colours The C parameter can be used to override the default colours for standard elements of the graph. Valid elements are: BACK, CANVAS, SHADEA, SHADEB, GRID, MGRID, FONT, AXIS, FRAME and ARROW. See L for further information. use strict; use RRD::Simple; my $rrd = RRD::Simple->new( file => "vmstat-cpu.rrd" ); $rrd->graph( title => "CPU Utilisation", source_colors => { sy => "ff0000", us => "00ff00", wa => "0000ff", id => "ffffff", }, color => [ ( "BACK#F5F5FF", "SHADEA#C8C8FF", "SHADEB#9696BE", "ARROW#61B51B", "GRID#404852", "MGRID#67C6DE" ) ], ); =head2 Example 7: Capacity Planning Predictions use strict; use RRD::Simple 1.44; my $rrd = RRD::Simple->new( file => "memory_usage.rrd" ); $rrd->graph( periods => [ qw(week month) ], title => "Memory Utilisation", base => 1024, vertical_label => "bytes", sources => [ qw(Total Used) ], source_drawtypes => [ qw(AREA LINE) ], source_colors => [ qw(dddddd 0000dd) ], lower_limit => 0, rigid => "", "VDEF:D=Used,LSLSLOPE" => "", "VDEF:H=Used,LSLINT" => "", "VDEF:F=Used,LSLCORREL" => "", "CDEF:Proj=Used,POP,D,COUNT,*,H,+" => "", "LINE2:Proj#800000: Projection" => "", ); =head1 COPYRIGHT Copyright 2005,2006,2007,2008 Nicola Worthington. This software is licensed under The Apache Software License, Version 2.0. L =cut RRD-Simple-1.44/lib/RRD/Simple.pm0000444000076400007640000017755510746154056016220 0ustar nicolawnicolaw############################################################ # # $Id: Simple.pm 1100 2008-01-24 17:39:35Z nicolaw $ # RRD::Simple - Simple interface to create and store data in RRD files # # Copyright 2005,2006,2007,2008 Nicola Worthington # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################ package RRD::Simple; # vim:ts=8:sw=8:tw=78 use strict; require Exporter; use RRDs; use POSIX qw(strftime); # Used for strftime in graph() method use Carp qw(croak cluck confess carp); use File::Spec qw(); # catfile catdir updir path rootdir tmpdir use File::Basename qw(fileparse dirname basename); use vars qw($VERSION $DEBUG $DEFAULT_DSTYPE @EXPORT @EXPORT_OK %EXPORT_TAGS @ISA); $VERSION = '1.44' || sprintf('%d', q$Revision: 1100 $ =~ /(\d+)/g); @ISA = qw(Exporter); @EXPORT = qw(); @EXPORT_OK = qw(create update last_update graph info rename_source add_source sources retention_period last_values heartbeat); # delete_source minimum maximum %EXPORT_TAGS = (all => \@EXPORT_OK); $DEBUG ||= $ENV{DEBUG} ? 1 : 0; $DEFAULT_DSTYPE ||= exists $ENV{DEFAULT_DSTYPE} ? $ENV{DEFAULT_DSTYPE} : 'GAUGE'; my $objstore = {}; # # Methods # # Create a new object sub new { TRACE(">>> new()"); ref(my $class = shift) && croak 'Class name required'; croak 'Odd number of elements passed when even was expected' if @_ % 2; # Conjure up an invisible object my $self = bless \(my $dummy), $class; $objstore->{_refaddr($self)} = {@_}; my $stor = $objstore->{_refaddr($self)}; #my $self = { @_ }; # - Added "file" support in 1.42 - see sub _guess_filename. # - Added "on_missing_ds"/"on_missing_source" support in 1.44 # - Added "tmpdir" support in 1.44 my @validkeys = qw(rrdtool cf default_dstype default_dst tmpdir file on_missing_ds on_missing_source); my $validkeys = join('|', @validkeys); cluck('Unrecognised parameters passed: '. join(', ',grep(!/^$validkeys$/,keys %{$stor}))) if (grep(!/^$validkeys$/,keys %{$stor}) && $^W); $stor->{rrdtool} = _find_binary(exists $stor->{rrdtool} ? $stor->{rrdtool} : 'rrdtool'); # Check that "default_dstype" isn't complete rubbish (validation from v1.44+) # GAUGE | COUNTER | DERIVE | ABSOLUTE | COMPUTE # http://oss.oetiker.ch/rrdtool/doc/rrdcreate.en.html $stor->{default_dstype} ||= $stor->{default_dst}; croak "Invalid value passed in parameter default_dstype; '$stor->{default_dstype}'" if defined $stor->{default_dstype} && $stor->{default_dstype} !~ /^(GAUGE|COUNTER|DERIVE|ABSOLUTE|COMPUTE|[A-Z]{1,10})$/i; # Check that "on_missing_ds" isn't complete rubbish. # Added "on_missing_ds"/"on_missing_source" support in 1.44 $stor->{on_missing_ds} ||= $stor->{on_missing_source}; if (defined $stor->{on_missing_ds}) { $stor->{on_missing_ds} = lc($stor->{on_missing_ds}); croak "Invalid value passed in parameter on_missing_ds; '$stor->{on_missing_ds}'" if $stor->{on_missing_ds} !~ /^\s*(add|ignore|die|croak)\s*$/i; } $stor->{on_missing_ds} ||= 'add'; # default to add #$stor->{cf} ||= [ qw(AVERAGE MIN MAX LAST) ]; # By default, now only create RRAs for AVERAGE and MAX, like # mrtg v2.13.2. This is to save disk space and processing time # during updates etc. $stor->{cf} ||= [ qw(AVERAGE MAX) ]; $stor->{cf} = [ $stor->{cf} ] if !ref($stor->{cf}); DUMP($class,$self); DUMP('$stor',$stor); return $self; } # Create a new RRD file sub create { TRACE(">>> create()"); my $self = shift; unless (ref $self && UNIVERSAL::isa($self, __PACKAGE__)) { unshift @_, $self unless $self eq __PACKAGE__; $self = new __PACKAGE__; } my $stor = $objstore->{_refaddr($self)}; # # # # Grab or guess the filename my $rrdfile = $stor->{file}; # Odd number of values and first is not a valid scheme # then the first value is likely an RRD file name. if (@_ % 2 && !_valid_scheme($_[0])) { $rrdfile = shift; # Even number of values and the second value is a valid # scheme then the first value is likely an RRD file name. } elsif (!(@_ % 2) && _valid_scheme($_[1])) { $rrdfile = shift; # If we still don't have an RRD file name then try and # guess what it is } elsif (!defined $rrdfile) { $rrdfile = _guess_filename($stor); } # # # # Barf if the rrd file already exists croak "RRD file '$rrdfile' already exists" if -f $rrdfile; TRACE("Using filename: $rrdfile"); # We've been given a scheme specifier # Until v1.32 'year' was the default. As of v1.33 'mrtg' # is the new default scheme. #my $scheme = 'year'; my $scheme = 'mrtg'; if (@_ % 2 && _valid_scheme($_[0])) { $scheme = _valid_scheme($_[0]); shift @_; } TRACE("Using scheme: $scheme"); croak 'Odd number of elements passed when even was expected' if @_ % 2; my %ds = @_; DUMP('%ds',\%ds); my $rrdDef = _rrd_def($scheme); my @def = ('-b', time - _seconds_in($scheme,120)); push @def, '-s', ($rrdDef->{step} || 300); # Add data sources for my $ds (sort keys %ds) { $ds =~ s/[^a-zA-Z0-9_-]//g; push @def, sprintf('DS:%s:%s:%s:%s:%s', substr($ds,0,19), uc($ds{$ds}), ($rrdDef->{heartbeat} || 600), 'U','U' ); } # Add RRA definitions my %cf; for my $cf (@{$stor->{cf}}) { $cf{$cf} = $rrdDef->{rra}; } for my $cf (sort keys %cf) { for my $rra (@{$cf{$cf}}) { push @def, sprintf('RRA:%s:%s:%s:%s', $cf, 0.5, $rra->{step}, $rra->{rows} ); } } DUMP('@def',\@def); # Pass to RRDs for execution my @rtn = RRDs::create($rrdfile, @def); my $error = RRDs::error(); croak($error) if $error; DUMP('RRDs::info',RRDs::info($rrdfile)); return wantarray ? @rtn : \@rtn; } # Update an RRD file with some data values sub update { TRACE(">>> update()"); my $self = shift; unless (ref $self && UNIVERSAL::isa($self, __PACKAGE__)) { unshift @_, $self unless $self eq __PACKAGE__; $self = new __PACKAGE__; } my $stor = $objstore->{_refaddr($self)}; # # # # Grab or guess the filename my $rrdfile = $stor->{file}; # Odd number of values and first is does not look # like a recent unix time stamp then the first value # is likely to be an RRD file name. if (@_ % 2 && $_[0] !~ /^[1-9][0-9]{8,10}$/i) { $rrdfile = shift; # Even number of values and the second value looks like # a recent unix time stamp then the first value is # likely to be an RRD file name. } elsif (!(@_ % 2) && $_[1] =~ /^[1-9][0-9]{8,10}$/i) { $rrdfile = shift; # If we still don't have an RRD file name then try and # guess what it is } elsif (!defined $rrdfile) { $rrdfile = _guess_filename($stor); } # # # # We've been given an update timestamp my $time = time(); if (@_ % 2 && $_[0] =~ /^([1-9][0-9]{8,10})$/i) { $time = $1; shift @_; } TRACE("Using update time: $time"); # Try to automatically create it unless (-f $rrdfile) { my $default_dstype = defined $stor->{default_dstype} ? $stor->{default_dstype} : $DEFAULT_DSTYPE; cluck("RRD file '$rrdfile' does not exist; attempting to create it ", "using default DS type of '$default_dstype'") if $^W; my @args; for (my $i = 0; $i < @_; $i++) { push @args, ($_[$i],$default_dstype) unless $i % 2; } $self->create($rrdfile,@args); } croak "RRD file '$rrdfile' does not exist" unless -f $rrdfile; TRACE("Using filename: $rrdfile"); croak 'Odd number of elements passed when even was expected' if @_ % 2; my %ds; while (my $ds = shift(@_)) { $ds =~ s/[^a-zA-Z0-9_-]//g; $ds = substr($ds,0,19); $ds{$ds} = shift(@_); $ds{$ds} = 'U' if !defined($ds{$ds}); } DUMP('%ds',\%ds); # Validate the data source names as we add them my @sources = $self->sources($rrdfile); for my $ds (sort keys %ds) { # Check the data source names if (!grep(/^$ds$/,@sources)) { TRACE("Supplied data source '$ds' does not exist in pre-existing ". "RRD data source list: ". join(', ',@sources)); # If someone got the case wrong, remind and correct them if (grep(/^$ds$/i,@sources)) { cluck("Data source '$ds' does not exist; automatically ", "correcting it to '",(grep(/^$ds$/i,@sources))[0], "' instead") if $^W; $ds{(grep(/^$ds$/i,@sources))[0]} = $ds{$ds}; delete $ds{$ds}; # If it's not just a case sensitivity typo and the data source # name really doesn't exist in this RRD file at all, regardless # of case, then ... } else { # Ignore the offending missing data source name if ($stor->{on_missing_ds} eq 'ignore') { TRACE("on_missing_ds = ignore; ignoring data supplied for missing data source '$ds'"); # Fall on our bum and die horribly if requested to do so } elsif ($stor->{on_missing_ds} eq 'die' || $stor->{on_missing_ds} eq 'croak') { croak "Supplied data source '$ds' does not exist in RRD file '$rrdfile'"; # Default behaviour is to automatically add the new data source # to the RRD file in order to preserve the existing default # functionality of RRD::Simple } else { TRACE("on_missing_ds = add (or not set at all/default); ". "automatically adding new data source '$ds'"); # Otherwise add any missing or new data sources on the fly # Decide what DS type and heartbeat to use my $info = RRDs::info($rrdfile); my $error = RRDs::error(); croak($error) if $error; my %dsTypes; for my $key (grep(/^ds\[.+?\]\.type$/,keys %{$info})) { $dsTypes{$info->{$key}}++; } DUMP('%dsTypes',\%dsTypes); my $dstype = (sort { $dsTypes{$b} <=> $dsTypes{$a} } keys %dsTypes)[0]; TRACE("\$dstype = $dstype"); $self->add_source($rrdfile,$ds,$dstype); } } } } # Build the def my @def = ('--template'); push @def, join(':',sort keys %ds); push @def, join(':',$time,map { $ds{$_} } sort keys %ds); DUMP('@def',\@def); # Pass to RRDs to execute the update my @rtn = RRDs::update($rrdfile, @def); my $error = RRDs::error(); croak($error) if $error; return wantarray ? @rtn : \@rtn; } # Get the last time an RRD was updates sub last_update { __PACKAGE__->last(@_); } sub last { TRACE(">>> last()"); my $self = shift; unless (ref $self && UNIVERSAL::isa($self, __PACKAGE__)) { unshift @_, $self unless $self eq __PACKAGE__; $self = new __PACKAGE__; } my $stor = $objstore->{_refaddr($self)}; my $rrdfile = shift || _guess_filename($stor); croak "RRD file '$rrdfile' does not exist" unless -f $rrdfile; TRACE("Using filename: $rrdfile"); my $last = RRDs::last($rrdfile); my $error = RRDs::error(); croak($error) if $error; return $last; } # Get a list of data sources from an RRD file sub sources { TRACE(">>> sources()"); my $self = shift; unless (ref $self && UNIVERSAL::isa($self, __PACKAGE__)) { unshift @_, $self unless $self eq __PACKAGE__; $self = new __PACKAGE__; } my $stor = $objstore->{_refaddr($self)}; my $rrdfile = shift || _guess_filename($stor); croak "RRD file '$rrdfile' does not exist" unless -f $rrdfile; TRACE("Using filename: $rrdfile"); my $info = RRDs::info($rrdfile); my $error = RRDs::error(); croak($error) if $error; my @ds; foreach (keys %{$info}) { if (/^ds\[(.+)?\]\.type$/) { push @ds, $1; } } return wantarray ? @ds : \@ds; } # Add a new data source to an RRD file sub add_source { TRACE(">>> add_source()"); my $self = shift; unless (ref $self && UNIVERSAL::isa($self, __PACKAGE__)) { unshift @_, $self unless $self eq __PACKAGE__; $self = new __PACKAGE__; } # Grab or guess the filename my $stor = $objstore->{_refaddr($self)}; my $rrdfile = @_ % 2 ? shift : _guess_filename($stor); unless (-f $rrdfile) { cluck("RRD file '$rrdfile' does not exist; attempting to create it") if $^W; return $self->create($rrdfile,@_); } croak "RRD file '$rrdfile' does not exist" unless -f $rrdfile; TRACE("Using filename: $rrdfile"); # Check that we will understand this RRD file version first my $info = $self->info($rrdfile); # croak "Unable to add a new data source to $rrdfile; ", # "RRD version $info->{rrd_version} is too new" # if ($info->{rrd_version}+1-1) > 1; my ($ds,$dstype) = @_; TRACE("\$ds = $ds"); TRACE("\$dstype = $dstype"); my $rrdfileBackup = "$rrdfile.bak"; confess "$rrdfileBackup already exists; please investigate" if -e $rrdfileBackup; # Decide what heartbeat to use my $heartbeat = $info->{ds}->{(sort { $info->{ds}->{$b}->{minimal_heartbeat} <=> $info->{ds}->{$b}->{minimal_heartbeat} } keys %{$info->{ds}})[0]}->{minimal_heartbeat}; TRACE("\$heartbeat = $heartbeat"); # Make a list of expected sources after the addition my $TgtSources = join(',',sort(($self->sources($rrdfile),$ds))); # Add the data source my $new_rrdfile = ''; eval { $new_rrdfile = _modify_source( $rrdfile,$stor,$ds, 'add',$dstype,$heartbeat, ); }; # Barf if the eval{} got upset if ($@) { croak "Failed to add new data source '$ds' to RRD file '$rrdfile': $@"; } # Barf of the new RRD file doesn't exist unless (-f $new_rrdfile) { croak "Failed to add new data source '$ds' to RRD file '$rrdfile': ", "new RRD file '$new_rrdfile' does not exist"; } # Barf is the new data source isn't in our new RRD file unless ($TgtSources eq join(',',sort($self->sources($new_rrdfile)))) { croak "Failed to add new data source '$ds' to RRD file '$rrdfile': ", "new RRD file '$new_rrdfile' does not contain expected data ", "source names"; } # Try and move the new RRD file in to place over the existing one # and then remove the backup RRD file if sucessfull if (File::Copy::move($rrdfile,$rrdfileBackup) && File::Copy::move($new_rrdfile,$rrdfile)) { unless (unlink($rrdfileBackup)) { cluck("Failed to remove back RRD file '$rrdfileBackup': $!") if $^W; } } else { croak "Failed to move new RRD file in to place: $!"; } } # Make a number of graphs for an RRD file sub graph { TRACE(">>> graph()"); my $self = shift; unless (ref $self && UNIVERSAL::isa($self, __PACKAGE__)) { unshift @_, $self unless $self eq __PACKAGE__; $self = new __PACKAGE__; } # Grab or guess the filename my $stor = $objstore->{_refaddr($self)}; my $rrdfile = @_ % 2 ? shift : _guess_filename($stor); # How much data do we have to graph? my $period = $self->retention_period($rrdfile); # Check at RRA CFs are available and graph the best one my $info = $self->info($rrdfile); my $cf = 'AVERAGE'; for my $rra (@{$info->{rra}}) { if ($rra->{cf} eq 'AVERAGE') { $cf = 'AVERAGE'; last; } elsif ($rra->{cf} eq 'MAX') { $cf = 'MAX'; } elsif ($rra->{cf} eq 'MIN' && $cf ne 'MAX') { $cf = 'MIN'; } elsif ($cf ne 'MAX' && $cf ne 'MIN') { $cf = $rra->{cf}; } } TRACE("graph() - \$cf = $cf"); # Create graphs which we have enough data to populate # Version 1.39 - Change the return from an array to a hash (semi backward compatible) # my @rtn; my %rtn; ## ## TODO ## 1.45 Only generate hour, 6hour and 12hour graphs if the ### data resolution (stepping) is fine enough (sub minute) ## #i my @graph_periods = qw(hour 6hour 12hour day week month year 3years); my @graph_periods; my %param = @_; if (defined $param{'periods'}) { my %map = qw(daily day weekly week monthly month annual year 3years 3years); for my $period (_convert_to_array($param{'periods'})) { $period = lc($period); if (_valid_scheme($period)) { push @graph_periods, $period; } elsif (_valid_scheme($map{$period})) { push @graph_periods, $map{$period}; } else { croak "Invalid period value passed in parameter periods; '$period'"; } } } push @graph_periods, qw(day week month year 3years) unless @graph_periods; for my $type (@graph_periods) { next if $period < _seconds_in($type); TRACE("graph() - \$type = $type"); # push @rtn, [ ($self->_create_graph($rrdfile, $type, $cf, @_)) ]; $rtn{_alt_graph_name($type)} = [ ($self->_create_graph($rrdfile, $type, $cf, @_)) ]; } # return @rtn; return wantarray ? %rtn : \%rtn; } # Rename an existing data source sub rename_source { TRACE(">>> rename_source()"); my $self = shift; unless (ref $self && UNIVERSAL::isa($self, __PACKAGE__)) { unshift @_, $self unless $self eq __PACKAGE__; $self = new __PACKAGE__; } # Grab or guess the filename my $stor = $objstore->{_refaddr($self)}; my $rrdfile = @_ % 2 ? shift : _guess_filename($stor); croak "RRD file '$rrdfile' does not exist" unless -f $rrdfile; TRACE("Using filename: $rrdfile"); my ($old,$new) = @_; croak "No old data source name specified" unless defined $old && length($old); croak "No new data source name specified" unless defined $new && length($new); croak "Data source '$old' does not exist in RRD file '$rrdfile'" unless grep($_ eq $old, $self->sources($rrdfile)); my @rtn = RRDs::tune($rrdfile,'-r',"$old:$new"); my $error = RRDs::error(); croak($error) if $error; return wantarray ? @rtn : \@rtn; } # Get or set a data source heartbeat sub heartbeat { TRACE(">>> heartbeat()"); my $self = shift; unless (ref $self && UNIVERSAL::isa($self, __PACKAGE__)) { unshift @_, $self unless $self eq __PACKAGE__; $self = new __PACKAGE__; } # Grab or guess the filename my $stor = $objstore->{_refaddr($self)}; my $rrdfile = @_ >= 3 ? shift : _isLegalDsName($_[0]) && $_[1] =~ /^[0-9]+$/ ? _guess_filename($stor) : shift; croak "RRD file '$rrdfile' does not exist" unless -f $rrdfile; TRACE("Using filename: $rrdfile"); # Explode if we get no data source name my ($ds,$new_heartbeat) = @_; croak "No data source name was specified" unless defined $ds && length($ds); # Check the data source name exists my $info = $self->info($rrdfile); my $heartbeat = $info->{ds}->{$ds}->{minimal_heartbeat}; croak "Data source '$ds' does not exist in RRD file '$rrdfile'" unless defined $heartbeat && $heartbeat; if (!defined $new_heartbeat) { return wantarray ? ($heartbeat) : $heartbeat; } my @rtn = !defined $new_heartbeat ? ($heartbeat) : (); # Redefine the data source heartbeat if (defined $new_heartbeat) { croak "New minimal heartbeat '$new_heartbeat' is not a valid positive integer" unless $new_heartbeat =~ /^[1-9][0-9]*$/; my @rtn = RRDs::tune($rrdfile,'-h',"$ds:$new_heartbeat"); my $error = RRDs::error(); croak($error) if $error; } return wantarray ? @rtn : \@rtn; } # Fetch data point information from an RRD file sub fetch { TRACE(">>> fetch()"); my $self = shift; unless (ref $self && UNIVERSAL::isa($self, __PACKAGE__)) { unshift @_, $self unless $self eq __PACKAGE__; $self = new __PACKAGE__; } # Grab or guess the filename my $stor = $objstore->{_refaddr($self)}; my $rrdfile = @_ % 2 ? shift : _guess_filename($stor); } # Fetch the last values inserted in to an RRD file sub last_values { TRACE(">>> last_values()"); my $self = shift; unless (ref $self && UNIVERSAL::isa($self, __PACKAGE__)) { unshift @_, $self unless $self eq __PACKAGE__; $self = new __PACKAGE__; } # Grab or guess the filename my $stor = $objstore->{_refaddr($self)}; my $rrdfile = @_ % 2 ? shift : _guess_filename($stor); # When was the RRD last updated? my $lastUpdated = $self->last($rrdfile); # Is there a LAST RRA? my $info = $self->info($rrdfile); my $hasLastRRA = 0; for my $rra (@{$info->{rra}}) { $hasLastRRA++ if $rra->{cf} eq 'LAST'; } return if !$hasLastRRA; # What's the largest heartbeat in the RRD file data sources? my $largestHeartbeat = 1; for (map { $info->{ds}->{$_}->{'minimal_heartbeat'} } keys(%{$info->{ds}})) { $largestHeartbeat = $_ if $_ > $largestHeartbeat; } my @def = ('LAST', '-s', $lastUpdated - ($largestHeartbeat * 2), '-e', $lastUpdated ); # Pass to RRDs to execute my ($time,$heartbeat,$ds,$data) = RRDs::fetch($rrdfile, @def); my $error = RRDs::error(); croak($error) if $error; # Put it in to a nice easy format my %rtn = (); for my $rec (reverse @{$data}) { for (my $i = 0; $i < @{$rec}; $i++) { if (defined $rec->[$i] && !exists($rtn{$ds->[$i]})) { $rtn{$ds->[$i]} = $rec->[$i]; } } } # Well, I'll be buggered if the LAST CF does what you'd think # it's meant to do. If anybody can give me some decent documentation # on what the LAST CF does, and/or how to get the last value put # in to an RRD, then I'll admit that this method exists and export # it too. return wantarray ? %rtn : \%rtn; } # Return how long this RRD retains data for sub retention_period { TRACE(">>> retention_period()"); my $self = shift; unless (ref $self && UNIVERSAL::isa($self, __PACKAGE__)) { unshift @_, $self unless $self eq __PACKAGE__; $self = new __PACKAGE__; } my $info = $self->info(@_); return if !defined($info); my $duration = $info->{step}; for my $rra (@{$info->{rra}}) { my $secs = ($rra->{pdp_per_row} * $info->{step}) * $rra->{rows}; $duration = $secs if $secs > $duration; } return wantarray ? ($duration) : $duration; } # Fetch information about an RRD file sub info { TRACE(">>> info()"); my $self = shift; unless (ref $self && UNIVERSAL::isa($self, __PACKAGE__)) { unshift @_, $self unless $self eq __PACKAGE__; $self = new __PACKAGE__; } # Grab or guess the filename my $stor = $objstore->{_refaddr($self)}; my $rrdfile = @_ % 2 ? shift : _guess_filename($stor); my $info = RRDs::info($rrdfile); my $error = RRDs::error(); croak($error) if $error; DUMP('$info',$info); my $rtn; for my $key (sort(keys(%{$info}))) { if ($key =~ /^rra\[(\d+)\]\.([a-z_]+)/) { $rtn->{rra}->[$1]->{$2} = $info->{$key}; } elsif (my (@dsKey) = $key =~ /^ds\[([[A-Za-z0-9\_]+)?\]\.([a-z_]+)/) { $rtn->{ds}->{$1}->{$2} = $info->{$key}; } elsif ($key !~ /\[[\d_a-z]+\]/i) { $rtn->{$key} = $info->{$key}; } } # Return the information DUMP('$rtn',$rtn); return $rtn; } # Convert a string or an array reference to an array sub _convert_to_array { return unless defined $_[0]; if (!ref $_[0]) { $_[0] =~ /^\s+|\s+$/g; return split(/(?:\s+|\s*,\s*)/,$_[0]); } elsif (ref($_[0]) eq 'ARRAY') { return @{$_[0]}; } return; } # Make a single graph image sub _create_graph { TRACE(">>> _create_graph()"); my $self = shift; my $rrdfile = shift; my $type = _valid_scheme(shift) || 'day'; my $cf = shift || 'AVERAGE'; my $command_regex = qr/^([VC]?DEF|G?PRINT|COMMENT|[HV]RULE\d*|LINE\d*|AREA|TICK|SHIFT|STACK):.+/; $command_regex = qr/^([VC]?DEF|G?PRINT|COMMENT|[HV]RULE\d*|LINE\d*|AREA|TICK|SHIFT|STACK|TEXTALIGN):.+/ if $RRDs::VERSION >= 1.3; # http://oss.oetiker.ch/rrdtool-trac/wiki/RRDtool13 my %param; my @command_param; while (my $k = shift) { if ($k =~ /$command_regex/) { push @command_param, $k; shift; } else { $k =~ s/_/-/g; $param{lc($k)} = shift; } } # If we get this custom parameter then it would have already # been dealt with by the calling graph() method so we should # ditch it right here and now! delete $param{'periods'}; # Specify some default values $param{'end'} ||= $self->last($rrdfile) || time(); $param{'imgformat'} ||= 'PNG'; # RRDs >1.3 now support PDF, SVG and EPS # $param{'alt-autoscale'} ||= ''; # $param{'alt-y-grid'} ||= ''; # Define what to call the image my $basename = defined $param{'basename'} && $param{'basename'} =~ /^[0-9a-z_\.-]+$/i ? $param{'basename'} : (fileparse($rrdfile,'\.[^\.]+'))[0]; delete $param{'basename'}; # Define where to write the image my $image = sprintf('%s-%s.%s',$basename, _alt_graph_name($type), lc($param{'imgformat'})); if ($param{'destination'}) { $image = File::Spec->catfile($param{'destination'},$image); } delete $param{'destination'}; # Specify timestamps- new for version 1.41 my $timestamp = !defined $param{'timestamp'} || $param{'timestamp'} !~ /^(graph|rrd|both|none)$/i ? 'graph' : lc($param{'timestamp'}); delete $param{'timestamp'}; # Specify extended legend - new for version 1.35 my $extended_legend = defined $param{'extended-legend'} && $param{'extended-legend'} ? 1 : 0; delete $param{'extended-legend'}; # Define how thick the graph lines should be my $line_thickness = defined $param{'line-thickness'} && $param{'line-thickness'} =~ /^[123]$/ ? $param{'line-thickness'} : 1; delete $param{'line-thickness'}; # Colours is an alias to colors if (exists $param{'source-colours'} && !exists $param{'source-colors'}) { $param{'source-colors'} = $param{'source-colours'}; delete $param{'source-colours'}; } # Allow source line colors to be set my @source_colors = (); my %source_colors = (); if (defined $param{'source-colors'}) { #if (ref($param{'source-colors'}) eq 'ARRAY') { # @source_colors = @{$param{'source-colors'}}; if (ref($param{'source-colors'}) eq 'HASH') { %source_colors = %{$param{'source-colors'}}; } else { @source_colors = _convert_to_array($param{'source-colors'}); } } delete $param{'source-colors'}; # Define which data sources we should plot my @rrd_sources = $self->sources($rrdfile); my @ds = !exists $param{'sources'} ? @rrd_sources #: defined $param{'sources'} && ref($param{'sources'}) eq 'ARRAY' #? @{$param{'sources'}} : defined $param{'sources'} ? _convert_to_array($param{'sources'}) : (); # Allow source legend source_labels to be set my %source_labels = (); if (defined $param{'source-labels'}) { if (ref($param{'source-labels'}) eq 'HASH') { %source_labels = %{$param{'source-labels'}}; } elsif (ref($param{'source-labels'}) eq 'ARRAY') { if (defined $param{'sources'} && ref($param{'sources'}) eq 'ARRAY') { for (my $i = 0; $i < @{$param{'source-labels'}}; $i++) { $source_labels{$ds[$i]} = $param{'source-labels'}->[$i] if defined $ds[$i]; } } elsif ($^W) { carp "source_labels may only be an array if sources is also ". "an specified and valid array"; } } } delete $param{'source-labels'}; # Allow source legend source_drawtypes to be set # ... "oops" ... yes, this is quite obviously # copy and paste code from the chunk above. I'm # sorry. I'll rationalise it some other day if # it's necessary. my %source_drawtypes = (); if (defined $param{'source-drawtypes'}) { if (ref($param{'source-drawtypes'}) eq 'HASH') { %source_drawtypes = %{$param{'source-drawtypes'}}; } elsif (ref($param{'source-drawtypes'}) eq 'ARRAY') { if (defined $param{'sources'} && ref($param{'sources'}) eq 'ARRAY') { for (my $i = 0; $i < @{$param{'source-drawtypes'}}; $i++) { $source_drawtypes{$ds[$i]} = $param{'source-drawtypes'}->[$i] if defined $ds[$i]; } } elsif ($^W) { carp "source_drawtypes may only be an array if sources is ". "also an specified and valid array" } } # Validate the values we have and set default thickness while (my ($k,$v) = each %source_drawtypes) { if ($v !~ /^(LINE[1-9]?|STACK|AREA)$/) { delete $source_drawtypes{$k}; carp "source_drawtypes may be LINE, LINEn, AREA or STACK ". "only; value '$v' is not valid" if $^W; } $source_drawtypes{$k} = uc($v); $source_drawtypes{$k} .= $line_thickness if $v eq 'LINE'; } } delete $param{'source-drawtypes'}; delete $param{'sources'}; # Specify a default start time $param{'start'} ||= $param{'end'} - _seconds_in($type,115); # Suffix the title with the period information $param{'title'} ||= basename($rrdfile); $param{'title'} .= ' - [Hourly Graph]' if $type eq 'hour'; $param{'title'} .= ' - [6 Hour Graph]' if $type eq '6hour' || $type eq 'quarterday'; $param{'title'} .= ' - [12 Hour Graph]' if $type eq '12hour' || $type eq 'halfday'; $param{'title'} .= ' - [Daily Graph]' if $type eq 'day'; $param{'title'} .= ' - [Weekly Graph]' if $type eq 'week'; $param{'title'} .= ' - [Monthly Graph]' if $type eq 'month'; $param{'title'} .= ' - [Annual Graph]' if $type eq 'year'; $param{'title'} .= ' - [3 Year Graph]' if $type eq '3years'; # Convert our parameters in to an RRDs friendly defenition my @def; while (my ($k,$v) = each %param) { if (length($k) == 1) { # Short single character options $k = '-'.uc($k); } else { # Long options $k = "--$k"; } for my $v ((ref($v) eq 'ARRAY' ? @{$v} : ($v))) { if (!defined $v || !length($v)) { push @def, $k; } else { push @def, "$k=$v"; } } } # Populate a cycling tied scalar for line colors @source_colors = qw( FF0000 00FF00 0000FF 00FFFF FF00FF FFFF00 000000 990000 009900 000099 009999 990099 999900 999999 552222 225522 222255 225555 552255 555522 555555 ) unless @source_colors > 0; # Pre 1.35 colours # FF0000 00FF00 0000FF FFFF00 00FFFF FF00FF 000000 # 550000 005500 000055 555500 005555 550055 555555 # AA0000 00AA00 0000AA AAAA00 00AAAA AA00AA AAAAAA tie my $colour, 'RRD::Simple::_Colour', \@source_colors; my $fmt = '%s:%s#%s:%s%s'; my $longest_label = 1; if ($extended_legend) { for my $ds (@ds) { my $len = length( defined $source_labels{$ds} ? $source_labels{$ds} : $ds ); $longest_label = $len if $len > $longest_label; } $fmt = "%s:%s#%s:%-${longest_label}s%s"; } ## ## ## # Create the @cmd my @cmd = ($image,@def); # Add the data sources definitions to @cmd for my $ds (@rrd_sources) { # Add the data source definition push @cmd, sprintf('DEF:%s=%s:%s:%s',$ds,$rrdfile,$ds,$cf); } # Add the data source draw commands to the grap/@cmd for my $ds (@ds) { # Stack operates differently in RRD 1.2 or higher my $drawtype = defined $source_drawtypes{$ds} ? $source_drawtypes{$ds} : "LINE$line_thickness"; my $stack = ''; if ($RRDs::VERSION >= 1.2 && $drawtype eq 'STACK') { $drawtype = 'AREA'; $stack = ':STACK'; } # Draw the line (and add to the legend) push @cmd, sprintf($fmt, $drawtype, $ds, (defined $source_colors{$ds} ? $source_colors{$ds} : $colour), (defined $source_labels{$ds} ? $source_labels{$ds} : $ds), $stack ); # New for version 1.39 # Return the min,max,last information in the graph() return @rtn if ($RRDs::VERSION >= 1.2) { push @cmd, sprintf('VDEF:%sMIN=%s,MINIMUM',$ds,$ds); push @cmd, sprintf('VDEF:%sMAX=%s,MAXIMUM',$ds,$ds); push @cmd, sprintf('VDEF:%sLAST=%s,LAST',$ds,$ds); # Don't automatically add this unless we have to # push @cmd, sprintf('VDEF:%sAVERAGE=%s,AVERAGE',$ds,$ds); push @cmd, sprintf('PRINT:%sMIN:%s min %%1.2lf',$ds,$ds); push @cmd, sprintf('PRINT:%sMAX:%s max %%1.2lf',$ds,$ds); push @cmd, sprintf('PRINT:%sLAST:%s last %%1.2lf',$ds,$ds); } else { push @cmd, sprintf('PRINT:%s:MIN:%s min %%1.2lf',$ds,$ds); push @cmd, sprintf('PRINT:%s:MAX:%s max %%1.2lf',$ds,$ds); push @cmd, sprintf('PRINT:%s:LAST:%s last %%1.2lf',$ds,$ds); } # New for version 1.35 if ($extended_legend) { if ($RRDs::VERSION >= 1.2) { # Moved the VDEFs to the block of code above which is # always run, regardless of the extended legend push @cmd, sprintf('GPRINT:%sMIN: min\:%%10.2lf\g',$ds); push @cmd, sprintf('GPRINT:%sMAX: max\:%%10.2lf\g',$ds); push @cmd, sprintf('GPRINT:%sLAST: last\:%%10.2lf\l',$ds); } else { push @cmd, sprintf('GPRINT:%s:MIN: min\:%%10.2lf\g',$ds); push @cmd, sprintf('GPRINT:%s:MAX: max\:%%10.2lf\g',$ds); push @cmd, sprintf('GPRINT:%s:LAST: last\:%%10.2lf\l',$ds); } } } # Push the post command defs on to the stack push @cmd, @command_param; # Add a comment stating when the graph was last updated if ($timestamp ne 'none') { #push @cmd, ('COMMENT:\s','COMMENT:\s','COMMENT:\s'); push @cmd, ('COMMENT:\s','COMMENT:\s'); push @cmd, 'COMMENT:\s' unless $extended_legend || !@ds; my $timefmt = '%a %d/%b/%Y %T %Z'; if ($timestamp eq 'rrd' || $timestamp eq 'both') { my $time = sprintf('RRD last updated: %s\r', strftime($timefmt,localtime((stat($rrdfile))[9])) ); $time =~ s/:/\\:/g if $RRDs::VERSION >= 1.2; # Only escape for 1.2 push @cmd, "COMMENT:$time"; } if ($timestamp eq 'graph' || $timestamp eq 'both') { my $time = sprintf('Graph last updated: %s\r', strftime($timefmt,localtime(time)) ); $time =~ s/:/\\:/g if $RRDs::VERSION >= 1.2; # Only escape for 1.2 push @cmd, "COMMENT:$time"; } } DUMP('@cmd',\@cmd); # Generate the graph my @rtn = RRDs::graph(@cmd); my $error = RRDs::error(); croak($error) if $error; return ($image,@rtn); } # # Private subroutines # no warnings 'redefine'; sub UNIVERSAL::a_sub_not_likely_to_be_here { ref($_[0]) } use warnings 'redefine'; sub _blessed ($) { local($@, $SIG{__DIE__}, $SIG{__WARN__}); return length(ref($_[0])) ? eval { $_[0]->a_sub_not_likely_to_be_here } : undef } sub _refaddr($) { my $pkg = ref($_[0]) or return undef; if (_blessed($_[0])) { bless $_[0], 'Scalar::Util::Fake'; } else { $pkg = undef; } "$_[0]" =~ /0x(\w+)/; my $i = do { local $^W; hex $1 }; bless $_[0], $pkg if defined $pkg; return $i; } sub _isLegalDsName { #rrdtool-1.0.49/src/rrd_format.h:#define DS_NAM_FMT "%19[a-zA-Z0-9_-]" #rrdtool-1.2.11/src/rrd_format.h:#define DS_NAM_FMT "%19[a-zA-Z0-9_-]" ## ## TODO ## 1.45 - Double check this with the latest 1.3 version of RRDtool ## to see if it has changed or not ## return $_[0] =~ /^[a-zA-Z0-9_-]{1,19}$/; } sub _rrd_def { croak('Pardon?!') if ref $_[0]; my $type = _valid_scheme(shift); # This is calculated the same way as mrtg v2.13.2 if ($type eq 'mrtg') { my $step = 5; # 5 minutes return { step => $step * 60, heartbeat => $step * 60 * 2, rra => [( { step => 1, rows => int(4000 / $step) }, # 800 { step => int( 30 / $step), rows => 800 }, # if $step < 30 { step => int( 120 / $step), rows => 800 }, { step => int(1440 / $step), rows => 800 }, )], }; } ## ## TODO ## 1.45 Add higher resolution for hour, 6hour and 12 hour ## my $step = 1; # 1 minute highest resolution my $rra = { step => $step * 60, heartbeat => $step * 60 * 2, rra => [( # Actual $step resolution (for 1.25 days retention) { step => 1, rows => int( _minutes_in('day',125) / $step) }, )], }; if ($type =~ /^(week|month|year|3years)$/i) { push @{$rra->{rra}}, { step => int( 30 / $step), rows => int( _minutes_in('week',125) / int(30/$step) ) }; # 30 minute average push @{$rra->{rra}}, { step => int( 120 / $step), rows => int( _minutes_in($type eq 'week' ? 'week' : 'month',125) / int(120/$step) ) }; # 2 hour average } if ($type =~ /^(year|3years)$/i) { push @{$rra->{rra}}, { step => int(1440 / $step), rows => int( _minutes_in($type,125) / int(1440/$step) ) }; # 1 day average } return $rra; } sub _odd { return $_[0] % 2; } sub _even { return !($_[0] % 2); } sub _valid_scheme { TRACE(">>> _valid_scheme()"); croak('Pardon?!') if ref $_[0]; #if ($_[0] =~ /^(day|week|month|year|3years|mrtg)$/i) { if ($_[0] =~ /^((?:6|12)?hour|(?:half)?day|week|month|year|3years|mrtg)$/i) { TRACE("'".lc($1)."' is a valid scheme."); return lc($1); } TRACE("'@_' is not a valid scheme."); return undef; } sub _hours_in { return int((_seconds_in(@_)/60)/60); } sub _minutes_in { return int(_seconds_in(@_)/60); } sub _seconds_in { croak('Pardon?!') if ref $_[0]; my $str = lc(shift); my $scale = shift || 100; return undef if !defined(_valid_scheme($str)); my %time = ( # New for version 1.44 of RRD::Simple by # popular request 'hour' => 60 * 60, '6hour' => 60 * 60 * 6, 'quarterday' => 60 * 60 * 6, '12hour' => 60 * 60 * 12, 'halfday' => 60 * 60 * 12, 'day' => 60 * 60 * 24, 'week' => 60 * 60 * 24 * 7, 'month' => 60 * 60 * 24 * 31, 'year' => 60 * 60 * 24 * 365, '3years' => 60 * 60 * 24 * 365 * 3, 'mrtg' => ( int(( 1440 / 5 )) * 800 ) * 60, # mrtg v2.13.2 ); my $rtn = $time{$str} * ($scale / 100); return $rtn; } sub _alt_graph_name { croak('Pardon?!') if ref $_[0]; my $type = _valid_scheme(shift); return unless defined $type; # New for version 1.44 of RRD::Simple by popular request return 'hourly' if $type eq 'hour'; return '6hourly' if $type eq '6hour' || $type eq 'quarterday'; return '12hourly' if $type eq '12hour' || $type eq 'halfday'; return 'daily' if $type eq 'day'; return 'weekly' if $type eq 'week'; return 'monthly' if $type eq 'month'; return 'annual' if $type eq 'year'; return '3years' if $type eq '3years'; return $type; } ## ## TODO ## 1.45 - Check to see if there is now native support in RRDtool to ## add, remove or change existing sources - and if there is ## make this code only run for onler versions that do not have ## native support. ## sub _modify_source { croak('Pardon?!') if ref $_[0]; my ($rrdfile,$stor,$ds,$action,$dstype,$heartbeat) = @_; my $rrdtool = $stor->{rrdtool}; $rrdtool = '' unless defined $rrdtool; # Decide what action we should take if ($action !~ /^(add|del)$/) { my $caller = (caller(1))[3]; $action = $caller =~ /\badd\b/i ? 'add' : $caller =~ /\bdel(ete)?\b/i ? 'del' : undef; } croak "Unknown or no action passed to method _modify_source()" unless defined $action && $action =~ /^(add|del)$/; require File::Copy; require File::Temp; # Generate an XML dump of the RRD file # - Added "tmpdir" support in 1.44 my $tmpdir = defined $stor->{tmpdir} ? $stor->{tmpdir} : File::Spec->tmpdir(); my ($tempXmlFileFH,$tempXmlFile) = File::Temp::tempfile( DIR => $tmpdir, TEMPLATE => 'rrdXXXXX', SUFFIX => '.tmp', ); # Check that we managed to get a sane temporary filename croak "File::Temp::tempfile() failed to return a temporary filename" unless defined $tempXmlFile; TRACE("_modify_source(): \$tempXmlFile = $tempXmlFile"); # Try the internal perl way first (portable) eval { # Patch to rrd_dump.c emailed to Tobi and developers # list by nicolaw/heds on 2006/01/08 if ($RRDs::VERSION >= 1.2013) { my @rtn = RRDs::dump($rrdfile,$tempXmlFile); my $error = RRDs::error(); croak($error) if $error; } }; # Do it the old fashioned way if ($@ || !-f $tempXmlFile || (stat($tempXmlFile))[7] < 200) { croak "rrdtool binary '$rrdtool' does not exist or is not executable" if !defined $rrdtool || !-f $rrdtool || !-x $rrdtool; _safe_exec(sprintf('%s dump %s > %s',$rrdtool,$rrdfile,$tempXmlFile)); } # Read in the new temporary XML dump file open(IN, "<$tempXmlFile") || croak "Unable to open '$tempXmlFile': $!"; # Open XML output file # my $tempImportXmlFile = File::Temp::tmpnam(); # - Added "tmpdir" support in 1.44 my ($tempImportXmlFileFH,$tempImportXmlFile) = File::Temp::tempfile( DIR => $tmpdir, TEMPLATE => 'rrdXXXXX', SUFFIX => '.tmp', ); open(OUT, ">$tempImportXmlFile") || croak "Unable to open '$tempImportXmlFile': $!"; # Create a marker hash ref to store temporary state my $marker = { currentDSIndex => 0, deleteDSIndex => undef, addedNewDS => 0, parse => 0, version => 1, }; # Parse the input XML file while (local $_ = ) { chomp; # Find out what index number the existing DS definition is in if ($action eq 'del' && /\s*(\S+)\s*<\/name>/) { $marker->{deleteIndex} = $marker->{currentDSIndex} if $1 eq $ds; $marker->{currentDSIndex}++; } # Add the DS definition if ($action eq 'add' && !$marker->{addedNewDS} && //) { print OUT < $ds $dstype $heartbeat 0.0000000000e+00 NaN UNKN 0.0000000000e+00 0 EndDS $marker->{addedNewDS} = 1; } # Insert DS under CDP_PREP entity if ($action eq 'add' && /<\/cdp_prep>/) { # Version 0003 RRD from rrdtool 1.2x if ($marker->{version} >= 3) { print OUT " \n"; print OUT " 0.0000000000e+00 \n"; print OUT " 0.0000000000e+00 \n"; print OUT " NaN \n"; print OUT " 0 \n"; print OUT " \n"; # Version 0001 RRD from rrdtool 1.0x } else { print OUT " NaN 0 \n"; } } # Look for the end of an RRA if (/<\/database>/) { $marker->{parse} = 0; # Find the dumped RRD version (must take from the XML, not the RRD) } elsif (/\s*([0-9\.]+)\s*<\/version>/) { $marker->{version} = ($1 + 1 - 1); } # Add the extra " NaN " under the RRAs. Just print normal lines if ($marker->{parse} == 1) { if ($_ =~ /^(.+ .+)(<\/row>.*)/) { print OUT $1; print OUT " NaN " if $action eq 'add'; print OUT $2; print OUT "\n"; } } else { print OUT "$_\n"; } # Look for the start of an RRA if (//) { $marker->{parse} = 1; } } # Close the files close(IN) || croak "Unable to close '$tempXmlFile': $!"; close(OUT) || croak "Unable to close '$tempImportXmlFile': $!"; # Import the new output file in to the old RRD filename my $new_rrdfile = File::Temp::tmpnam(); TRACE("_modify_source(): \$new_rrdfile = $new_rrdfile"); # Try the internal perl way first (portable) eval { if ($RRDs::VERSION >= 1.0049) { my @rtn = RRDs::restore($tempImportXmlFile,$new_rrdfile); my $error = RRDs::error(); croak($error) if $error; } }; # Do it the old fashioned way if ($@ || !-f $new_rrdfile || (stat($new_rrdfile))[7] < 200) { croak "rrdtool binary '$rrdtool' does not exist or is not executable" unless (-f $rrdtool && -x $rrdtool); my $cmd = sprintf('%s restore %s %s',$rrdtool,$tempImportXmlFile,$new_rrdfile); my $rtn = _safe_exec($cmd); # At least check the file is created unless (-f $new_rrdfile) { _nuke_tmp($tempXmlFile,$tempImportXmlFile); croak "Command '$cmd' failed to create the new RRD file '$new_rrdfile': $rtn"; } } # Remove the temporary files _nuke_tmp($tempXmlFile,$tempImportXmlFile); sub _nuke_tmp { for (@_) { unlink($_) || carp("Unable to unlink temporary file '$_': $!"); } } # Return the new RRD filename return wantarray ? ($new_rrdfile) : $new_rrdfile; } ## ## TODO ## 1.45 - Improve this _safe_exec function to see if it can be made ## more robust and use any better CPAN modules if that happen ## to already be installed on the users system (don't add any ## new module dependancies though) ## sub _safe_exec { croak('Pardon?!') if ref $_[0]; my $cmd = shift; if ($cmd =~ /^([\/\.\_\-a-zA-Z0-9 >]+)$/) { $cmd = $1; TRACE($cmd); system($cmd); if ($? == -1) { croak "Failed to execute command '$cmd': $!\n"; } elsif ($? & 127) { croak(sprintf("While executing command '%s', child died ". "with signal %d, %s coredump\n", $cmd, ($? & 127), ($? & 128) ? 'with' : 'without')); } my $exit_value = $? >> 8; croak "Error caught from '$cmd'" if $exit_value != 0; return $exit_value; } else { croak "Unexpected potentially unsafe command will not be executed: $cmd"; } } sub _find_binary { croak('Pardon?!') if ref $_[0]; my $binary = shift || 'rrdtool'; return $binary if -f $binary && -x $binary; my @paths = File::Spec->path(); my $rrds_path = dirname($INC{'RRDs.pm'}); push @paths, $rrds_path; push @paths, File::Spec->catdir($rrds_path, File::Spec->updir(),File::Spec->updir(),'bin'); for my $path (@paths) { my $filename = File::Spec->catfile($path,$binary); return $filename if -f $filename && -x $filename; } my $path = File::Spec->catdir(File::Spec->rootdir(),'usr','local'); if (opendir(DH,$path)) { my @dirs = sort { $b cmp $a } grep(/^rrdtool/,readdir(DH)); closedir(DH) || carp "Unable to close file handle: $!"; for my $dir (@dirs) { my $filename = File::Spec->catfile($path,$dir,'bin',$binary); return $filename if -f $filename && -x $filename; } } } sub _guess_filename { croak('Pardon?!') if !defined $_[0] || ref($_[0]) ne 'HASH'; my $stor = shift; if (defined $stor->{file}) { TRACE("_guess_filename = \$stor->{file} = $stor->{file}"); return $stor->{file}; } my ($basename, $dirname, $extension) = fileparse($0, '\.[^\.]+'); TRACE("_guess_filename = calculated = $dirname$basename.rrd"); return "$dirname$basename.rrd"; } sub DESTROY { my $self = shift; delete $objstore->{_refaddr($self)}; } sub TRACE { return unless $DEBUG; carp(shift()); } sub DUMP { return unless $DEBUG; eval { require Data::Dumper; $Data::Dumper::Indent = 2; $Data::Dumper::Terse = 1; carp(shift().': '.Data::Dumper::Dumper(shift())); } } BEGIN { eval "use RRDs"; if ($@) { carp qq{ +-----------------------------------------------------------------------------+ | ERROR! -- Could not load RRDs.pm | | | | RRD::Simple requires RRDs.pm (a part of RRDtool) in order to function. You | | can download a copy of RRDtool from http://www.rrdtool.org. See the INSTALL | | document for more details. | +-----------------------------------------------------------------------------+ } unless $ENV{AUTOMATED_TESTING}; } } 1; ############################################################### # This tie code is from Tie::Cycle # written by brian d foy, package RRD::Simple::_Colour; sub TIESCALAR { my ($class,$list_ref) = @_; my @shallow_copy = map { $_ } @$list_ref; return unless UNIVERSAL::isa( $list_ref, 'ARRAY' ); my $self = [ 0, scalar @shallow_copy, \@shallow_copy ]; bless $self, $class; } sub FETCH { my $self = shift; my $index = $$self[0]++; $$self[0] %= $self->[1]; return $self->[2]->[ $index ]; } sub STORE { my ($self,$list_ref) = @_; return unless ref $list_ref eq ref []; return unless @$list_ref > 1; $self = [ 0, scalar @$list_ref, $list_ref ]; } 1; =pod =head1 NAME RRD::Simple - Simple interface to create and store data in RRD files =head1 SYNOPSIS use strict; use RRD::Simple (); # Create an interface object my $rrd = RRD::Simple->new( file => "myfile.rrd" ); # Create a new RRD file with 3 data sources called # bytesIn, bytesOut and faultsPerSec. $rrd->create( bytesIn => "GAUGE", bytesOut => "GAUGE", faultsPerSec => "COUNTER" ); # Put some arbitary data values in the RRD file for the same # 3 data sources called bytesIn, bytesOut and faultsPerSec. $rrd->update( bytesIn => 10039, bytesOut => 389, faultsPerSec => 0.4 ); # Generate graphs: # /var/tmp/myfile-daily.png, /var/tmp/myfile-weekly.png # /var/tmp/myfile-monthly.png, /var/tmp/myfile-annual.png my %rtn = $rrd->graph( destination => "/var/tmp", title => "Network Interface eth0", vertical_label => "Bytes/Faults", interlaced => "" ); printf("Created %s\n",join(", ",map { $rtn{$_}->[0] } keys %rtn)); # Return information about an RRD file my $info = $rrd->info; require Data::Dumper; print Data::Dumper::Dumper($info); # Get unixtime of when RRD file was last updated my $lastUpdated = $rrd->last; print "myfile.rrd was last updated at " . scalar(localtime($lastUpdated)) . "\n"; # Get list of data source names from an RRD file my @dsnames = $rrd->sources; print "Available data sources: " . join(", ", @dsnames) . "\n"; # And for the ultimately lazy, you could create and update # an RRD in one go using a one-liner like this: perl -MRRD::Simple=:all -e"update(@ARGV)" myfile.rrd bytesIn 99999 =head1 DESCRIPTION RRD::Simple provides a simple interface to RRDTool's RRDs module. This module does not currently offer a C method that is available in the RRDs module. It does however create RRD files with a sensible set of default RRA (Round Robin Archive) definitions, and can dynamically add new data source names to an existing RRD file. This module is ideal for quick and simple storage of data within an RRD file if you do not need to, nor want to, bother defining custom RRA definitions. =head1 METHODS =head2 new my $rrd = RRD::Simple->new( file => "myfile.rrd", rrdtool => "/usr/local/rrdtool-1.2.11/bin/rrdtool", tmpdir => "/var/tmp", cf => [ qw(AVERAGE MAX) ], default_dstype => "GAUGE", on_missing_ds => "add", ); The C parameter is currently optional but will become mandatory in future releases, replacing the optional C<$rrdfile> parameters on subsequent methods. This parameter specifies the RRD filename to be used. The C parameter is optional. It specifically defines where the C binary can be found. If not specified, the module will search for the C binary in your path, an additional location relative to where the C module was loaded from, and in /usr/local/rrdtool*. The C parameter is option and is only used what automatically adding a new data source to an existing RRD file. By default any temporary files will be placed in your default system temp directory (typically /tmp on Linux, or whatever your TMPDIR environment variable is set to). This parameter can be used for force any temporary files to be created in a specific directory. The C binary is only used by the C method, and only under certain circumstances. The C method may also be called automatically by the C method, if data point values for a previously undefined data source are provided for insertion. The C parameter is optional, but when specified expects an array reference. The C parameter defines which consolidation functions are used in round robin archives (RRAs) when creating new RRD files. Valid values are AVERAGE, MIN, MAX and LAST. The default value is AVERAGE and MAX. The C parameter is optional. Specifying the default data source type (DST) through the new() method allows the DST to be localised to the $rrd object instance rather than be global to the RRD::Simple package. See L<$RRD::Simple::DEFAULT_DSTYPE>. The C parameter is optional and will default to "add" when not defined. This parameter will determine what will happen if you try to insert or update data for a data source name that does not exist in the RRD file. Valid values are "add", "ignore" and "die". =head2 create $rrd->create($rrdfile, $period, source_name => "TYPE", source_name => "TYPE", source_name => "TYPE" ); This method will create a new RRD file on disk. C<$rrdfile> is optional and will default to using the RRD filename specified by the C constructor method, or C<$0.rrd>. (Script basename with the file extension of .rrd). C<$period> is optional and will default to C. Valid options are C, C<6hour>/C, C<12hour>/C, C, C, C, C, C<3years> and C. Specifying a data retention period value will change how long data will be retained for within the RRD file. The C scheme will try and mimic the data retention period used by MRTG v2.13.2 (L. The C data retention period uses a data stepping resolution of 300 seconds (5 minutes) and heartbeat of 600 seconds (10 minutes), whereas all the other data retention periods use a data stepping resolution of 60 seconds (1 minute) and heartbeat of 120 seconds (2 minutes). Each data source name should specify the data source type. Valid data source types (DSTs) are GAUGE, COUNTER, DERIVE and ABSOLUTE. See the section regrading DSTs at L for further information. RRD::Simple will croak and die if you try to create an RRD file that already exists. =head2 update $rrd->update($rrdfile, $unixtime, source_name => "VALUE", source_name => "VALUE", source_name => "VALUE" ); This method will update an RRD file by inserting new data point values in to the RRD file. C<$rrdfile> is optional and will default to using the RRD filename specified by the C constructor method, or C<$0.rrd>. (Script basename with the file extension of .rrd). C<$unixtime> is optional and will default to C (the current unixtime). Specifying this value will determine the date and time that your data point values will be stored against in the RRD file. If you try to update a value for a data source that does not exist, it will automatically be added for you. The data source type will be set to whatever is contained in the C<$RRD::Simple::DEFAULT_DSTYPE> variable. (See the VARIABLES section below). If you explicitly do not want this to happen, then you should check that you are only updating pre-existing data source names using the C method. You can manually add new data sources to an RRD file by using the C method, which requires you to explicitly set the data source type. If you try to update an RRD file that does not exist, it will attept to create the RRD file for you using the same behaviour as described above. A warning message will be displayed indicating that the RRD file is being created for you if have perl warnings turned on. =head2 last my $unixtime = $rrd->last($rrdfile); This method returns the last (most recent) data point entry time in the RRD file in UNIX time (seconds since the epoch; Jan 1st 1970). This value should not be confused with the last modified time of the RRD file. C<$rrdfile> is optional and will default to using the RRD filename specified by the C constructor method, or C<$0.rrd>. (Script basename with the file extension of .rrd). =head2 sources my @sources = $rrd->sources($rrdfile); This method returns a list of all of the data source names contained within the RRD file. C<$rrdfile> is optional and will default to using the RRD filename specified by the C constructor method, or C<$0.rrd>. (Script basename with the file extension of .rrd). =head2 add_source $rrd->add_source($rrdfile, source_name => "TYPE" ); You may add a new data source to an existing RRD file using this method. Only one data source name can be added at a time. You must also specify the data source type. C<$rrdfile> is optional and will default to using the RRD filename specified by the C constructor method, or C<$0.rrd>. (Script basename with the file extension of .rrd). This method can be called internally by the C method to automatically add missing data sources. =head2 rename_source $rrd->rename_source($rrdfile, "old_datasource", "new_datasource"); You may rename a data source in an existing RRD file using this method. C<$rrdfile> is optional and will default to using the RRD filename specified by the C constructor method, or C<$0.rrd>. (Script basename with the file extension of .rrd). =head2 graph my %rtn = $rrd->graph($rrdfile, destination => "/path/to/write/graph/images", basename => "graph_basename", timestamp => "both", # graph, rrd, both or none periods => [ qw(week month) ], # omit to generate all graphs sources => [ qw(source_name1 source_name2 source_name3) ], source_colors => [ qw(ff0000 aa3333 000000) ], source_labels => [ ("My Source 1", "My Source Two", "Source 3") ], source_drawtypes => [ qw(LINE1 AREA LINE) ], line_thickness => 2, extended_legend => 1, rrd_graph_option => "value", rrd_graph_option => "value", rrd_graph_option => "value" ); This method will render one or more graph images that show the data in the RRD file. The number of image files that are created depends on the retention period of the RRD file. Hourly, 6 hourly, 12 hourly, daily, weekly, monthly, annual and 3year graphs will be created if there is enough data in the RRD file to accomodate them. The image filenames will start with either the basename of the RRD file, or whatever is specified by the C parameter. The second part of the filename will be "-hourly", "-6hourly", "-12hourly", "-daily", "-weekly", "-monthly", "-annual" or "-3year" depending on the period that is being graphed. C<$rrdfile> is optional and will default to using the RRD filename specified by the C constructor method, or C<$0.rrd>. (Script basename with the file extension of .rrd). Graph options specific to RRD::Simple are: =over 4 =item destination The C parameter is optional, and it will default to the same path location as that of the RRD file specified by C<$rrdfile>. Specifying this value will force the resulting graph images to be written to this path location. (The specified path must be a valid directory with the sufficient permissions to write the graph images). =item basename The C parameter is optional. This parameter specifies the basename of the graph image files that will be created. If not specified, it will default to the name of the RRD file. For example, if you specify a basename name of C, the following graph image files will be created in the C directory: mygraph-daily.png mygraph-weekly.png mygraph-monthly.png mygraph-annual.png The default file format is C, but this can be explicitly specified using the standard RRDs options. (See below). =item timestamp my %rtn = $rrd->graph($rrdfile, timestamp => "graph", # graph, rrd, both or none ); The C parameter is optional, but will default to "graph". This parameter specifies which "last updated" timestamps should be added to the bottom right hand corner of the graph. Valid values are: "graph" - the timestamp of when the graph was last rendered will be used, "rrd" - the timestamp of when the RRD file was last updated will be used, "both" - both the timestamps of when the graph and RRD file were last updated will be used, "none" - no timestamp will be used. =item periods The C parameter is an optional list of periods that graphs should be generated for. If omitted, all possible graphs will be generated and not restricted to any specific subset. See the L method for a list of valid time periods. =item sources The C parameter is optional. This parameter should be an array of data source names that you want to be plotted. All data sources will be plotted by default. =item source_colors my %rtn = $rrd->graph($rrdfile, source_colors => [ qw(ff3333 ff00ff ffcc99) ], ); %rtn = $rrd->graph($rrdfile, source_colors => { source_name1 => "ff3333", source_name2 => "ff00ff", source_name3 => "ffcc99", }, ); The C parameter is optional. This parameter should be an array or hash of hex triplet colors to be used for the plotted data source lines. A selection of vivid primary colors will be set by default. =item source_labels my %rtn = $rrd->graph($rrdfile, sources => [ qw(source_name1 source_name2 source_name3) ], source_labels => [ ("My Source 1","My Source Two","Source 3") ], ); %rtn = $rrd->graph($rrdfile, source_labels => { source_name1 => "My Source 1", source_name2 => "My Source Two", source_name3 => "Source 3", }, ); The C parameter is optional. The parameter should be an array or hash of labels to be placed in the legend/key underneath the graph. An array can only be used if the C parameter is also specified, since the label index position in the array will directly relate to the data source index position in the C array. The data source names will be used in the legend/key by default if no C parameter is specified. =item source_drawtypes my %rtn = $rrd->graph($rrdfile, source_drawtypes => [ qw(LINE1 AREA LINE) ], ); %rtn = $rrd->graph($rrdfile, source_colors => { source_name1 => "LINE1", source_name2 => "AREA", source_name3 => "LINE", }, ); %rtn = $rrd->graph($rrdfile, sources => [ qw(system user iowait idle) ] source_colors => [ qw(AREA STACK STACK STACK) ], ); The C parameter is optional. This parameter should be an array or hash of drawing/plotting types to be used for the plotted data source lines. By default all data sources are drawn as lines (LINE), but data sources may also be drawn as filled areas (AREA). Valid values are, LINE, LINEI (where I represents the thickness of the line in pixels), AREA or STACK. =item line_thickness Specifies the thickness of the data lines drawn on the graphs for any data sources that have not had a specific line thickness already specified using the C option. Valid values are 1, 2 and 3 (pixels). =item extended_legend If set to boolean true, prints more detailed information in the graph legend by adding the minimum, maximum and last values recorded on the graph for each data source. =back Common RRD graph options are: =over 4 =item title A horizontal string at the top of the graph. =item vertical_label A vertically placed string at the left hand side of the graph. =item width The width of the canvas (the part of the graph with the actual data and such). This defaults to 400 pixels. =item height The height of the canvas (the part of the graph with the actual data and such). This defaults to 100 pixels. =back For examples on how to best use the C method, refer to the example scripts that are bundled with this module in the examples/ directory. A complete list of parameters can be found at L. =head2 retention_period my $seconds = $rrd->retention_period($rrdfile); This method will return the maximum period of time (in seconds) that the RRD file will store data for. C<$rrdfile> is optional and will default to using the RRD filename specified by the C constructor method, or C<$0.rrd>. (Script basename with the file extension of .rrd). =head2 info my $info = $rrd->info($rrdfile); This method will return a complex data structure containing details about the RRD file, including RRA and data source information. C<$rrdfile> is optional and will default to using the RRD filename specified by the C constructor method, or C<$0.rrd>. (Script basename with the file extension of .rrd). =head2 heartbeat my $heartbeat = $rrd->heartbeat($rrdfile, "dsname"); my @rtn = $rrd->heartbeat($rrdfile, "dsname", 600); This method will return the current heartbeat of a data source, or set a new heartbeat of a data source. C<$rrdfile> is optional and will default to using the RRD filename specified by the C constructor method, or C<$0.rrd>. (Script basename with the file extension of .rrd). =head1 VARIABLES =head2 $RRD::Simple::DEBUG Debug and trace information will be printed to STDERR if this variable is set to 1 (boolean true). This variable will take its value from C<$ENV{DEBUG}>, if it exists, otherwise it will default to 0 (boolean false). This is a normal package variable and may be safely modified at any time. =head2 $RRD::Simple::DEFAULT_DSTYPE This variable is used as the default data source type when creating or adding new data sources, when no other data source type is explicitly specified. This variable will take its value from C<$ENV{DEFAULT_DSTYPE}>, if it exists, otherwise it will default to C. This is a normal package variable and may be safely modified at any time. =head1 EXPORTS You can export the following functions if you do not wish to go through the extra effort of using the OO interface: create update last_update (synonym for the last() method) sources add_source rename_source graph retention_period info heartbeat The tag C is available to easily export everything: use RRD::Simple qw(:all); See the examples and unit tests in this distribution for more details. =head1 SEE ALSO L, L, L, L, examples/*.pl, L, L =head1 VERSION $Id: Simple.pm 1100 2008-01-24 17:39:35Z nicolaw $ =head1 AUTHOR Nicola Worthington L If you like this software, why not show your appreciation by sending the author something nice from her L? ( http://www.amazon.co.uk/gp/registry/1VZXC59ESWYK0?sort=priority ) =head1 COPYRIGHT Copyright 2005,2006,2007,2008 Nicola Worthington. This software is licensed under The Apache Software License, Version 2.0. L =cut __END__ RRD-Simple-1.44/complain.txt0000444000076400007640000000611710746154056015560 0ustar nicolawnicolawConsider: "You're evil! Remove the phone home code from this module immediately unless you want to rot in hell for all eternity!" I get fed up having to respond to this when people come out with such unreasonable statements like this, so here is my stock response to each of the arguments to why I should not include phone home code in my distribution: 1) It's rude. No. The user is asked if they want to send any information. Their response is honored. The default value is "no" (do not phone home), and there is a timeout that also defaults to "no". 2) It's still rude. It's no more rude than the dozens of other distributions on CPAN that run unit tests that make network connections over the Internet without asking first. At least in my case the user is asked if they want to phone home first. 3) It will screw up automated stuff. No. It honors the AUTOMATED_TESTING environment variable, and also has a timeout that defaults to "no". 4) Build.PL and Makefile.PL shouldn't be scripts. What do you expect people to put in them other than a script? A pretty ASCII art picture maybe? 5) I don't want you to know my IP address. Then don't respond "yes" when asked if it's okay to send the information then. 6) I'll have to check the code to be sure it's safe. Why should you trust this code less than any other code written by other people just because it asks you if you would like to send some basic information to the author using an HTTP GET (the URL of which it clearly displays in the question/message)? 7) You can't possibly code defensively enough to make it always default to "no" and not phoning home. It is impossible to account for people overloading any or all parts modules that are used in the phone home, in the same way that it is impossible for me to know if someone has replaced the perl binary with a copy of lua or patched it to unlink random files from the file system, or overloaded ever core operator. I have however written the code to cope with all reasonable situations. 8) I bet people have only said you should remove the phone home code, and none said to keep it. You should therefore remove it. When council tax bands are reevaluated you only hear about the people who are not happy because they will have to pay more. That's because the people who benefit or don't mind usually don't feel the need to make their opinions known. Likewise in this situation. Plenty of people happily answer "yes" to allow phoning home. Do hear from them to tell me I should keep the phone home code? No, of course not. 9) I still don't agree. Phone home code is bad! Don't use this module and/or run the Build.PL script then. 10) I still don't agree. Phone home code is bad! Stop complaining. Other consenting adults make the decision to allow Build.PL to phone home, or not to phone home, and are able to do so without complaining. More over they're able to do this without trying to restrict how the author writes the software (which is free) or distributes it. RRD-Simple-1.44/Build.PL0000444000076400007640000000711210746154056014505 0ustar nicolawnicolaw# vim:ts=4:sw=4:tw=78 # $Id: Build.PL 965 2007-03-01 19:11:23Z nicolaw $ use strict; use Module::Build; use vars qw($build); BEGIN { eval "use RRDs"; if ($@) { warn qq{ +-----------------------------------------------------------------------------+ | ERROR! -- Could not load RRDs.pm | | | | RRD::Simple requires RRDs.pm (a part of RRDtool) in order to function. You | | can download a copy of RRDtool from http://www.rrdtool.org. See the INSTALL | | document for more details. | +-----------------------------------------------------------------------------+ } unless $ENV{AUTOMATED_TESTING}; } } my $module = 'RRD::Simple'; $build = Module::Build->new( module_name => $module, license => 'open_source', create_makefile_pl => 'passthrough', create_readme => 1, create_packlist => 1, sign => 0, requires => { 'File::Spec' => 0, # 3.15 'File::Basename' => 0, # 2.73 'File::Temp' => 0, # 0.16 'File::Copy' => 0, # 2.08 'RRDs' => 0, # 1.2013 'Carp' => 0, # 1.04 'POSIX' => 0, }, build_requires => { 'Test' => 0, # 1.25 'Test::More' => 0, # 0.60 }, recommends => { 'Test::Pod' => 1.20, 'Test::Pod::Coverage' => 1.06, 'Test::Deep' => 0.093, }, conflicts => { 'RRDTool::Managed' => 0, }, add_to_cleanup => [ qw( t/21test.rrd t/22test.rrd t/23test.rrd t/24test.rrd t/25test.rrd t/26test.rrd t/30assume_rrd_filename.rrd t/34test.rrd t/31create_assume_rrd_filename.rrd t/32test.rrd t/33test.rrd t/21test-daily.png t/21test-weekly.png t/21test-monthly.png t/21test-annual.png t/27test.rrd t/35test-daily.png ) ], ); $build->create_build_script; # Send perl and module version information home if we've been given # permission to do so by a human being - default to not send for automated # testing environments, of if the user does not respond within 20 seconds. my $url = $ENV{AUTOMATED_TESTING} ? undef : may_send_version_information(); if ($url) { my @resp = (); eval { local $SIG{ALRM} = sub { die; }; alarm 10; my $ua = LWP::UserAgent->new( agent => 'Build.PL $Revision: 965 $', timeout => 9, max_size => 500, ); $ua->env_proxy; my $response = $ua->get($url); if ($response->is_success()) { for (split(/\s*\n+\s*/, $response->content())) { push @resp, $_ if $_; } } alarm 0; }; print substr($resp[0],0,79) || "Thank you for sending this information."; print "\n\n"; } sub may_send_version_information { eval { require Config; require LWP::UserAgent; }; return undef if $@; my $str = sprintf('%s?%s=%s&%s=%s&%s=%s&%s=%s&%s=%s&%s=%s', 'http://perlgirl.org.uk/lib/usage.cgi', 'name', $module, 'version', $build->dist_version(), 'osname', $Config::Config{osname}, 'archname', $Config::Config{archname}, 'osver', $^O, 'perlver', $] ); print "\nThank you for downloading ".$build->dist_name()."\n\n"; print "I would like to find out how many people are using this software,\n"; print "and on what operating systems and Perl versions. If you have an\n"; print "internet connection, may I transmit the following information:\n\n"; print "$str\n\n"; my $send = 0; eval { local $SIG{ALRM} = sub { die; }; alarm 20; $send = $build->y_n('Send this anonymous information?','n'); alarm 0; }; return defined $send && !ref($send) && "$send" eq "1" ? $str : undef; } 1; RRD-Simple-1.44/t/0000777000076400007640000000000010746154056013461 5ustar nicolawnicolawRRD-Simple-1.44/t/21synopsis.t0000444000076400007640000000314710746154056015677 0ustar nicolawnicolaw# $Id: 21synopsis.t 965 2007-03-01 19:11:23Z nicolaw $ chdir('t') if -d 't'; my $rrdfile = -d 't' ? 't/21test.rrd' : '21test.rrd'; unlink $rrdfile if -f $rrdfile; use strict; BEGIN { use Test::More; eval "use RRDs"; plan skip_all => "RRDs.pm *MUST* be installed!" if $@; plan tests => 7 if !$@; } use lib qw(./lib ../lib); use RRD::Simple 1.42 (); # Create an interface object ok(my $rrd = RRD::Simple->new( file => $rrdfile ),'new'); # Create a new RRD file with 3 data sources called # bytesIn, bytesOut and faultsPerSec. Data retention # of a year is specified. (The data retention parameter # is optional and not required). ok($rrd->create("year", bytesIn => 'GAUGE', bytesOut => 'GAUGE', faultsPerSec => 'COUNTER' ),'create'); # Put some arbitary data values in the RRD file for same # 3 data sources called bytesIn, bytesOut and faultsPerSec. my $updated = time(); ok($rrd->update( bytesIn => 10039, bytesOut => 389, faultsPerSec => 0.4 ),'update'); # Get unixtime of when RRD file was last updated ok($rrd->last - $updated < 5 && $rrd->last, 'last'); ok(join(',',sort $rrd->sources) eq 'bytesIn,bytesOut,faultsPerSec', 'sources'); my %rtn = (); ok(%rtn = $rrd->graph( title => "Network Interface eth0", vertical_label => "Bytes/Faults", interlaced => "" ),'graph'); my $str = sprintf("Created %s",join(", ",map { $rtn{$_}->[0] } sort keys %rtn)); my $expected = "Created 21test-annual.png, 21test-daily.png, 21test-monthly.png, 21test-weekly.png"; ok("$str" eq "$expected",'created graphs'); for (map { $rtn{$_}->[0] } keys %rtn) { unlink $_ if -f $_; } unlink $rrdfile if -f $rrdfile; 1; RRD-Simple-1.44/t/20compile.t0000444000076400007640000000043610746154056015435 0ustar nicolawnicolaw# $Id: 20compile.t 965 2007-03-01 19:11:23Z nicolaw $ chdir('t') if -d 't'; BEGIN { use Test::More; eval "use RRDs"; plan skip_all => "RRDs.pm *MUST* be installed!" if $@; plan tests => 2 if !$@; } use lib qw(./lib ../lib); use_ok('RRD::Simple'); require_ok('RRD::Simple'); 1; RRD-Simple-1.44/t/11pod_coverage.t0000444000076400007640000000052010746154056016434 0ustar nicolawnicolaw# $Id: 11pod_coverage.t 965 2007-03-01 19:11:23Z nicolaw $ use Test::More; eval "use Test::Pod::Coverage 1.00"; plan skip_all => "Test::Pod::Coverage 1.00 required for testing POD Coverage" if $@; all_pod_coverage_ok({ also_private => [ qr/^[A-Z_]+$/ ], trustme => [ qw(last_values|fetch|last_update) ], }); #Ignore all caps 1; RRD-Simple-1.44/t/33correct_spelling.t0000444000076400007640000000215710746154056017351 0ustar nicolawnicolaw# $Id: 33correct_spelling.t 965 2007-03-01 19:11:23Z nicolaw $ my $rrdfile = -d 't' ? 't/33test.rrd' : '33test.rrd'; unlink $rrdfile if -f $rrdfile; use strict; BEGIN { use Test::More; eval "use RRDs"; plan skip_all => "RRDs.pm *MUST* be installed!" if $@; plan tests => 5 if !$@; } use lib qw(./lib ../lib); use RRD::Simple 1.35 (); ok(my $rrd = RRD::Simple->new(),'new'); ok($rrd->create($rrdfile, "year", bytesIn => 'GAUGE', bytesOut => 'GAUGE', faultsPerSec => 'COUNTER' ),'create'); ok($rrd->update($rrdfile, bytesIn => 10039, bytesOut => 389, faultsPerSec => 4 ),'update'); # # Updating a data source with incorrect case while perl # warnings are enabled will cause a warning message to be # printed. This might alarm people if it is output during # the unit tests, so we will disable warnings for this # part of the tests. # my $oldW = $^W; $^W = 0; ok($rrd->update($rrdfile,time+1, bytesIn => 11003, BytesOUT => 201, faultsPerSec => 2 ),'update'); $^W = $oldW; ok(join(',',sort $rrd->sources($rrdfile)) eq 'bytesIn,bytesOut,faultsPerSec', 'sources'); unlink $rrdfile if -f $rrdfile; 1; RRD-Simple-1.44/t/25info.t0000444000076400007640000000122010746154056014735 0ustar nicolawnicolaw# $Id: 25info.t 965 2007-03-01 19:11:23Z nicolaw $ my $rrdfile = -d 't' ? 't/25test.rrd' : '25test.rrd'; unlink $rrdfile if -f $rrdfile; use strict; BEGIN { use Test::More; eval "use RRDs"; plan skip_all => "RRDs.pm *MUST* be installed!" if $@; plan tests => 4 if !$@; } use lib qw(./lib ../lib); use RRD::Simple 1.35 (); ok(RRD::Simple->create($rrdfile, foo => 'GAUGE', bar => 'COUNTER' ),'create'); ok(RRD::Simple->update($rrdfile, foo => 1024, bar => 4096, ),'update'); my $info = {}; ok($info = RRD::Simple->info($rrdfile),'get info'); ok($info->{ds}->{foo}->{type} eq 'GAUGE','check info'); unlink $rrdfile if -f $rrdfile; 1; RRD-Simple-1.44/t/24retention_period.t0000444000076400007640000000207510746154056017363 0ustar nicolawnicolaw# $Id: 24retention_period.t 965 2007-03-01 19:11:23Z nicolaw $ chdir('t') if -d 't'; my $rrdfile = -d 't' ? 't/24test.rrd' : '24test.rrd'; unlink $rrdfile if -f $rrdfile; use strict; BEGIN { use Test::More; eval "use RRDs"; plan skip_all => "RRDs.pm *MUST* be installed!" if $@; plan tests => 31 if !$@; } use lib qw(./lib ../lib); use RRD::Simple 1.35 (); use vars qw($rra %retention_periods %scheme_graphs @schemes %graph_return); require 'answers.pl'; ok(my $rrd = RRD::Simple->new(),'new'); for my $p (keys %retention_periods) { ok($rrd->create($rrdfile, $p, bytesIn => 'GAUGE', bytesOut => 'GAUGE', ),"$p create"); ok($rrd->update($rrdfile, bytesIn => 100, bytesOut => 100, ),"$p update"); ok(join(',',sort $rrd->sources($rrdfile)) eq 'bytesIn,bytesOut', "$p sources"); ok(my $period = $rrd->retention_period($rrdfile),"$p retention_period"); ok($period > ($retention_periods{$p} * 0.95) && $period < ($retention_periods{$p} * 1.05), "$p retention_period result"); unlink $rrdfile if -f $rrdfile; } unlink $rrdfile if -f $rrdfile; 1; RRD-Simple-1.44/t/31create_assume_rrd_filename.t0000444000076400007640000000253610746154056021341 0ustar nicolawnicolaw# $Id: 31create_assume_rrd_filename.t 965 2007-03-01 19:11:23Z nicolaw $ my $rrdfile = -d 't' ? 't/31create_assume_rrd_filename.rrd' : '31create_assume_rrd_filename.rrd'; unlink $rrdfile if -f $rrdfile; use strict; BEGIN { use Test::More; eval "use RRDs"; plan skip_all => "RRDs.pm *MUST* be installed!" if $@; plan tests => 6 if !$@; } use lib qw(./lib ../lib); use RRD::Simple 1.35 (); my $created = time(); # # Forcing RRD::Simple to create an RRD with an update method call # while perl warnings are enabled will cause a warning message to # be displayed. This might alarm some people if it were to happen # during unit tests - for this reason we disable warnings for this # particular part of the tests. # my $oldW = $^W; $^W = 0; ok(RRD::Simple->update( ds0 => 1024, ds1 => 4096, ds2 => 512 ),'update (lazy create)'); $^W = $oldW; ok(RRD::Simple->last() - $created < 5 && RRD::Simple->last(), 'last'); ok(join(',',sort RRD::Simple->sources()) eq 'ds0,ds1,ds2', 'sources'); unlink $rrdfile if -f $rrdfile; $created = time(); $^W = 0; ok(RRD::Simple->update((time()-3600), ds3 => 1024, ds4 => 4096, ds5 => 512 ),'update (lazy create)'); $^W = $oldW; ok(RRD::Simple->last() - $created < 5 && RRD::Simple->last(), 'last'); ok(join(',',sort RRD::Simple->sources()) eq 'ds3,ds4,ds5', 'sources'); unlink $rrdfile if -f $rrdfile; 1; RRD-Simple-1.44/t/35average_hrule.t0000444000076400007640000000206010746154056016617 0ustar nicolawnicolaw# $Id: 35average_hrule.t 1100 2008-01-24 17:39:35Z nicolaw $ my $rrdfile = -d 't' ? 't/35test.rrd' : '35test.rrd'; unlink $rrdfile if -f $rrdfile; use strict; BEGIN { use Test::More; eval "use RRDs"; plan skip_all => "RRDs.pm *MUST* be installed!" if $@; plan skip_all => "RRDs version less than 1.2" if $RRDs::VERSION < 1.2; plan tests => 183 if !$@; } use lib qw(./lib ../lib); use RRD::Simple 1.40 (); ok(my $rrd = RRD::Simple->new(),'new'); my $end = time(); my $start = $end - (60 * 60 * 3); ok($rrd->create($rrdfile,'day', knickers => 'GAUGE', ),'create'); my $lastValue = 0; my $x = rand ( 10 ); for (my $t = $start; $t <= $end; $t += 60) { $lastValue = ( cos($t / 1000 ) + rand(2) ) + $x; $lastValue = 6 if $lastValue > 6; ok($rrd->update($rrdfile,$t, knickers => $lastValue, ),'update'); } $rrd->graph($rrdfile, sources => [ qw(knickers) ], 'VDEF:knickersAVERAGE=knickers,AVERAGE' => '', 'HRULE:knickersAVERAGE#00ff77:KnickersAvg' => '', ); unlink $rrdfile if -f $rrdfile; unlink '35test-daily.png' if -f '35test-daily.png'; 1; RRD-Simple-1.44/t/28heartbeat.t0000444000076400007640000000131110746154056015745 0ustar nicolawnicolaw# $Id: 28heartbeat.t 965 2007-03-01 19:11:23Z nicolaw $ my $rrdfile = -d 't' ? 't/28test.rrd' : '28test.rrd'; unlink $rrdfile if -f $rrdfile; use strict; BEGIN { use Test::More; eval "use RRDs"; plan skip_all => "RRDs.pm *MUST* be installed!" if $@; plan tests => 4 if !$@; } use lib qw(./lib ../lib); use RRD::Simple 1.40 (); ok(my $rrd = RRD::Simple->new(),'new'); ok($rrd->create($rrdfile, "year", bytesIn => 'GAUGE', bytesOut => 'GAUGE', faultsPerSec => 'COUNTER', bytesDropped => 'GAUGE' ),'create'); ok($rrd->rename_source($rrdfile,'bytesOut','knickers'),'rename_source()'); ok(grep($_ eq 'knickers',$rrd->sources($rrdfile)),'renamed source okay'); unlink $rrdfile if -f $rrdfile; 1; RRD-Simple-1.44/t/29on_missing_ds.t0000444000076400007640000000341010746154056016644 0ustar nicolawnicolaw# $Id: 26add_source.t 965 2007-03-01 19:11:23Z nicolaw $ my $rrdfile = -d 't' ? 't/29test.rrd' : '29test.rrd'; use strict; BEGIN { use Test::More; #plan skip_all => "unit test not written yet"; my $okay = 1; for (qw(RRDs File::Temp File::Copy)) { eval "use $_"; if ($@) { plan skip_all => "$_ *MUST* be installed!"; $okay = 0; } } plan tests => 28 if $okay; } use lib qw(./lib ../lib); use RRD::Simple 1.44 (); my @correct_sources = ( 'bytesDropped,bytesIn,bytesOut,faultsPerSec,totalFaults', 'bytesDropped,bytesIn,bytesOut,faultsPerSec', 'bytesDropped,bytesIn,bytesOut,faultsPerSec', 'bytesDropped,bytesIn,bytesOut,faultsPerSec,totalFaults', ); my $i = -1; for my $on_missing_ds (('add','ignore','die',undef)) { $i++; unlink $rrdfile if -f $rrdfile; ok(my $rrd = RRD::Simple->new( file => $rrdfile, on_missing_ds => $on_missing_ds ),'new'); ok($rrd->create($rrdfile, "year", bytesIn => 'GAUGE', bytesOut => 'GAUGE', faultsPerSec => 'COUNTER', bytesDropped => 'GAUGE' ),'create'); ok(join(',',sort $rrd->sources($rrdfile)) eq 'bytesDropped,bytesIn,bytesOut,faultsPerSec', 'expected sources okay'); SKIP: { my $info = {}; ok($info = $rrd->info($rrdfile),'info'); # add_source() now works on all current RRD versions # skip("RRD file version $info->{rrd_version} is too new to add data source",2) # if ($info->{rrd_version}+1-1) > 1; eval { $rrd->update($rrdfile, bytesIn => 10039, bytesOut => 389, totalFaults => 992 ); }; ok(join(',',sort $rrd->sources($rrdfile)) eq $correct_sources[$i], 'expected sources okay'); ok($rrd->add_source($rrdfile,wibble => 'DERIVE'),'add_source()'); ok(grep(/^wibble$/,$rrd->sources($rrdfile)),'added source okay') } } unlink $rrdfile if -f $rrdfile; 1; RRD-Simple-1.44/t/21synopsis_old.t0000444000076400007640000000322310746154056016530 0ustar nicolawnicolaw# $Id: 21synopsis_old.t 965 2007-03-01 19:11:23Z nicolaw $ chdir('t') if -d 't'; my $rrdfile = -d 't' ? 't/21test.rrd' : '21test.rrd'; unlink $rrdfile if -f $rrdfile; use strict; BEGIN { use Test::More; eval "use RRDs"; plan skip_all => "RRDs.pm *MUST* be installed!" if $@; plan tests => 7 if !$@; } use lib qw(./lib ../lib); use RRD::Simple 1.35 (); # Create an interface object ok(my $rrd = RRD::Simple->new(),'new'); # Create a new RRD file with 3 data sources called # bytesIn, bytesOut and faultsPerSec. Data retention # of a year is specified. (The data retention parameter # is optional and not required). ok($rrd->create($rrdfile, "year", bytesIn => 'GAUGE', bytesOut => 'GAUGE', faultsPerSec => 'COUNTER' ),'create'); # Put some arbitary data values in the RRD file for same # 3 data sources called bytesIn, bytesOut and faultsPerSec. my $updated = time(); ok($rrd->update($rrdfile, bytesIn => 10039, bytesOut => 389, faultsPerSec => 0.4 ),'update'); # Get unixtime of when RRD file was last updated ok($rrd->last($rrdfile) - $updated < 5 && $rrd->last($rrdfile), 'last'); ok(join(',',sort $rrd->sources($rrdfile)) eq 'bytesIn,bytesOut,faultsPerSec', 'sources'); my %rtn = (); ok(%rtn = $rrd->graph($rrdfile, title => "Network Interface eth0", vertical_label => "Bytes/Faults", interlaced => "" ),'graph'); my $str = sprintf("Created %s",join(", ",map { $rtn{$_}->[0] } sort keys %rtn)); my $expected = "Created 21test-annual.png, 21test-daily.png, 21test-monthly.png, 21test-weekly.png"; ok("$str" eq "$expected",'created graphs'); for (map { $rtn{$_}->[0] } keys %rtn) { unlink $_ if -f $_; } unlink $rrdfile if -f $rrdfile; 1; RRD-Simple-1.44/t/26add_source.t0000444000076400007640000000255610746154056016130 0ustar nicolawnicolaw# $Id: 26add_source.t 965 2007-03-01 19:11:23Z nicolaw $ my $rrdfile = -d 't' ? 't/26test.rrd' : '26test.rrd'; unlink $rrdfile if -f $rrdfile; use strict; BEGIN { use Test::More; my $okay = 1; for (qw(RRDs File::Temp File::Copy)) { eval "use $_"; if ($@) { plan skip_all => "$_ *MUST* be installed!"; $okay = 0; } } plan tests => 8 if $okay; } use lib qw(./lib ../lib); use RRD::Simple 1.35 (); ok(my $rrd = RRD::Simple->new(),'new'); ok($rrd->create($rrdfile, "year", bytesIn => 'GAUGE', bytesOut => 'GAUGE', faultsPerSec => 'COUNTER', bytesDropped => 'GAUGE' ),'create'); ok(join(',',sort $rrd->sources($rrdfile)) eq 'bytesDropped,bytesIn,bytesOut,faultsPerSec', 'expected sources okay'); SKIP: { my $info = {}; ok($info = $rrd->info($rrdfile),'info'); # add_source() now works on all current RRD versions # skip("RRD file version $info->{rrd_version} is too new to add data source",2) # if ($info->{rrd_version}+1-1) > 1; ok($rrd->update($rrdfile, bytesIn => 10039, bytesOut => 389, totalFaults => 992 ),'update (add_source)'); ok(join(',',sort $rrd->sources($rrdfile)) eq 'bytesDropped,bytesIn,bytesOut,faultsPerSec,totalFaults', 'expected sources okay'); ok($rrd->add_source($rrdfile,wibble => 'DERIVE'),'add_source()'); ok(grep(/^wibble$/,$rrd->sources($rrdfile)),'added source okay') } unlink $rrdfile if -f $rrdfile; 1; RRD-Simple-1.44/t/answers.pl0000444000076400007640000001002310746154056015466 0ustar nicolawnicolaw# $Id: answers.pl 965 2007-03-01 19:11:23Z nicolaw $ %scheme_graphs = ( '3years' => [ qw(daily weekly monthly annual 3years) ], 'year' => [ qw(daily weekly monthly annual) ], 'month' => [ qw(daily weekly monthly) ], 'week' => [ qw(daily weekly) ], 'day' => [ qw(daily) ], ); %retention_periods = ( '3years' => 118195200, 'mrtg' => 69120000, 'year' => 39398400, 'month' => 3348000, 'week' => 756000, 'day' => 108000,); %graph_return = ( 'daily' => [( 'bytesIn min 100.00', 'bytesIn max 100.00', 'bytesIn last 100.00', 'bytesOut min 50.00', 'bytesOut max 50.00', 'bytesOut last 50.00' )], 'weekly' => [( 'bytesIn min 100.00', 'bytesIn max 100.00', 'bytesIn last 100.00', 'bytesOut min 50.00', 'bytesOut max 50.00', 'bytesOut last 50.00' )], 'monthly' => [( 'bytesIn min nan', 'bytesIn max nan', 'bytesIn last nan', 'bytesOut min nan', 'bytesOut max nan', 'bytesOut last nan' )], 'annual' => [( 'bytesIn min nan', 'bytesIn max nan', 'bytesIn last nan', 'bytesOut min nan', 'bytesOut max nan', 'bytesOut last nan' )], '3years' => [( 'bytesIn min nan', 'bytesIn max nan', 'bytesIn last nan', 'bytesOut min nan', 'bytesOut max nan', 'bytesOut last nan' )], ); @schemes = keys %retention_periods; # Default values for 1.33 and higher $rra = [ { 'xff' => '0.5', 'pdp_per_row' => 1, 'cdp_prep' => undef, 'cf' => 'AVERAGE', 'rows' => 800 }, { 'xff' => '0.5', 'pdp_per_row' => 6, 'cdp_prep' => undef, 'cf' => 'AVERAGE', 'rows' => 800 }, { 'xff' => '0.5', 'pdp_per_row' => 24, 'cdp_prep' => undef, 'cf' => 'AVERAGE', 'rows' => 800 }, { 'xff' => '0.5', 'pdp_per_row' => 288, 'cdp_prep' => undef, 'cf' => 'AVERAGE', 'rows' => 800 }, { 'xff' => '0.5', 'pdp_per_row' => 1, 'cdp_prep' => undef, 'cf' => 'MAX', 'rows' => 800 }, { 'xff' => '0.5', 'pdp_per_row' => 6, 'cdp_prep' => undef, 'cf' => 'MAX', 'rows' => 800 }, { 'xff' => '0.5', 'pdp_per_row' => 24, 'cdp_prep' => undef, 'cf' => 'MAX', 'rows' => 800 }, { 'xff' => '0.5', 'pdp_per_row' => 288, 'cdp_prep' => undef, 'cf' => 'MAX', 'rows' => 800 }]; # Old default values for 1.32 $rra = [ { 'xff' => '0.5', 'pdp_per_row' => 1, 'cdp_prep' => undef, 'cf' => 'AVERAGE', 'rows' => 1800 }, { 'xff' => '0.5', 'pdp_per_row' => 30, 'cdp_prep' => undef, 'cf' => 'AVERAGE', 'rows' => 420 }, { 'xff' => '0.5', 'pdp_per_row' => 120, 'cdp_prep' => undef, 'cf' => 'AVERAGE', 'rows' => 465 }, { 'xff' => '0.5', 'pdp_per_row' => 1440, 'cdp_prep' => undef, 'cf' => 'AVERAGE', 'rows' => 456 }, { 'xff' => '0.5', 'pdp_per_row' => 1, 'cdp_prep' => undef, 'cf' => 'MAX', 'rows' => 1800 }, { 'xff' => '0.5', 'pdp_per_row' => 30, 'cdp_prep' => undef, 'cf' => 'MAX', 'rows' => 420 }, { 'xff' => '0.5', 'pdp_per_row' => 120, 'cdp_prep' => undef, 'cf' => 'MAX', 'rows' => 465 }, { 'xff' => '0.5', 'pdp_per_row' => 1440, 'cdp_prep' => undef, 'cf' => 'MAX', 'rows' => 456 }] if $RRD::Simple::VERSION < 1.33; 1; # perl -I./lib/ -MRRD::Simple=:all -e'for (qw(day week month year mrtg 3years)) { $x="f";unlink $x;create($x,$_,ds=>"COUNTER");print "Retention period in seconds for $_ => ".retention_period($x)."\n";}' # RRD::Simple version 1.31 or less #my %periods = ( # '3years' => 164160000, # 'year' => 54446400, # 'month' => 18000000, # 'week' => 5400000, # 'day' => 900000, # ); # RRD::Simple version 1.32 #my %periods = ( # '3years' => 118195200, # 'mrtg' => 69120000, # 'year' => 39398400, # 'month' => 3348000, # 'week' => 756000, # 'day' => 108000, # ); RRD-Simple-1.44/t/27rename_source.t0000444000076400007640000000131510746154056016640 0ustar nicolawnicolaw# $Id: 27rename_source.t 965 2007-03-01 19:11:23Z nicolaw $ my $rrdfile = -d 't' ? 't/27test.rrd' : '27test.rrd'; unlink $rrdfile if -f $rrdfile; use strict; BEGIN { use Test::More; eval "use RRDs"; plan skip_all => "RRDs.pm *MUST* be installed!" if $@; plan tests => 4 if !$@; } use lib qw(./lib ../lib); use RRD::Simple 1.40 (); ok(my $rrd = RRD::Simple->new(),'new'); ok($rrd->create($rrdfile, "year", bytesIn => 'GAUGE', bytesOut => 'GAUGE', faultsPerSec => 'COUNTER', bytesDropped => 'GAUGE' ),'create'); ok($rrd->rename_source($rrdfile,'bytesOut','knickers'),'rename_source()'); ok(grep($_ eq 'knickers',$rrd->sources($rrdfile)),'renamed source okay'); unlink $rrdfile if -f $rrdfile; 1; RRD-Simple-1.44/t/23graph.t0000444000076400007640000000402110746154056015103 0ustar nicolawnicolaw# $Id: 23graph.t 965 2007-03-01 19:11:23Z nicolaw $ chdir('t') if -d 't'; my $rrdfile = -d 't' ? 't/23test.rrd' : '23test.rrd'; unlink $rrdfile if -f $rrdfile; use strict; BEGIN { use Test::More; eval "use RRDs"; plan skip_all => "RRDs.pm *MUST* be installed!" if $@; plan tests => 226 if !$@; } use lib qw(./lib ../lib); use RRD::Simple 1.35 (); use vars qw($rra %retention_periods %scheme_graphs @schemes %graph_return); require 'answers.pl'; ok(my $rrd = RRD::Simple->new(),'new'); for my $p (keys %scheme_graphs) { ok($rrd->create($rrdfile, $p, bytesIn => 'GAUGE', bytesOut => 'GAUGE', ),"$p create"); for (my $t = 30; $t >= 1; $t--) { ok($rrd->update($rrdfile,time-(110*$t), bytesIn => 100, bytesOut => 50, ),"$p update"); } ok(join(',',sort $rrd->sources($rrdfile)) eq 'bytesIn,bytesOut', "$p sources"); mkdir '13graphs'; my %rtn = (); ok(%rtn = $rrd->graph($rrdfile, destination => './13graphs/', basename => 'foo', sources => [ qw(bytesIn bytesOut) ], source_labels => { bytesOut => 'Kbps Out' }, source_colors => [ qw(4499ff e33f00) ], source_drawtypes => [ qw(AREA LINE) ], line_thickness => 2, extended_legend => 1, ),"$p graph"); SKIP: { my $deep = 0; eval { require Test::Deep; Test::Deep->import(); $deep = 1; }; my $tests_to_skip = keys %rtn; if (!$deep || $@) { skip 'Test::Deep not available', $tests_to_skip; } for my $period (keys %rtn) { cmp_deeply( $rtn{$period}->[1], $graph_return{$period}, "graph() return hash ($period)", ); } } for my $f (@{$scheme_graphs{$p}}) { my $file = "./13graphs/foo-$f.png"; ok(-f $file,"create ./13graphs/foo-$f.png"); SKIP: { skip("./13graphs/foo-$f.png wasn't created",2) unless -f $file; ok((stat($file))[7] > 1024,"./13graphs/foo-$f.png is at least 1024 bytes"); ok(unlink($file),"unlink ./13graphs/foo-$f.png"); } } unlink $_ for glob('./13graphs/*'); rmdir '13graphs' || unlink '13graphs'; unlink $rrdfile if -f $rrdfile; } unlink $rrdfile if -f $rrdfile; 1; RRD-Simple-1.44/t/10pod.t0000444000076400007640000000027110746154056014563 0ustar nicolawnicolaw# $Id: 10pod.t 965 2007-03-01 19:11:23Z nicolaw $ use Test::More; eval "use Test::Pod 1.00"; plan skip_all => "Test::Pod 1.00 required for testing POD" if $@; all_pod_files_ok(); 1; RRD-Simple-1.44/t/22last_value.t0000444000076400007640000000245310746154056016147 0ustar nicolawnicolaw# $Id: 22last_value.t 965 2007-03-01 19:11:23Z nicolaw $ my $rrdfile = -d 't' ? 't/22test.rrd' : '22test.rrd'; unlink $rrdfile if -f $rrdfile; use strict; BEGIN { use Test::More; eval "use RRDs"; plan skip_all => "RRDs.pm *MUST* be installed!" if $@; plan tests => 368 if !$@; } use lib qw(./lib ../lib); use RRD::Simple 1.35 (); ok(my $rrd = RRD::Simple->new(cf => [ qw(AVERAGE LAST) ]),'new'); my $end = time(); my $start = $end - (60 * 60 * 6); ok($rrd->create($rrdfile,'day', foo => 'GAUGE', bar => 'GAUGE' ),'create'); my $lastValue = 0; for (my $t = $start; $t <= $end; $t += 60) { #$lastValue = int(rand(999)); $lastValue = 100; ok($rrd->update($rrdfile,$t, foo => $lastValue, bar => $lastValue+100 ),'update'); } ok($rrd->last($rrdfile) == $end, 'last'); ok(join(',',sort($rrd->sources($rrdfile))) eq join(',',sort(qw(foo bar))), 'sources'); #print "Last value inserted for 'bar' = " . ($lastValue + 100) . "\n"; #print "Last value inserted for 'foo' = " . $lastValue . "\n"; my %rtn; ok(%rtn = $rrd->last_values($rrdfile),'last_values'); SKIP: { # skip "last_values() method not yet completed", 2; ok($rtn{foo} == $lastValue, "$rtn{foo} == $lastValue (foo)"); ok($rtn{bar} == ($lastValue + 100), "$rtn{bar} == ($lastValue + 100) (bar)"); } unlink $rrdfile if -f $rrdfile; 1; RRD-Simple-1.44/t/30assume_rrd_filename.t0000444000076400007640000000145310746154056020012 0ustar nicolawnicolaw# $Id: 30assume_rrd_filename.t 965 2007-03-01 19:11:23Z nicolaw $ my $rrdfile = -d 't' ? 't/30assume_rrd_filename.rrd' : '30assume_rrd_filename.rrd'; unlink $rrdfile if -f $rrdfile; use strict; BEGIN { use Test::More; eval "use RRDs"; plan skip_all => "RRDs.pm *MUST* be installed!" if $@; plan tests => 4 if !$@; } use lib qw(./lib ../lib); use RRD::Simple 1.35 (); ok(RRD::Simple->create( bytesIn => 'GAUGE', bytesOut => 'GAUGE', faultsPerSec => 'COUNTER' ),'create'); my $updated = time(); ok(RRD::Simple->update( bytesIn => 10039, bytesOut => 389, faultsPerSec => 0.4 ),'update'); ok(RRD::Simple->last() - $updated < 5 && RRD::Simple->last(), 'last'); ok(join(',',sort RRD::Simple->sources()) eq 'bytesIn,bytesOut,faultsPerSec', 'sources'); unlink $rrdfile if -f $rrdfile; 1; RRD-Simple-1.44/t/32exported_function_interface.t0000444000076400007640000000336310746154056021571 0ustar nicolawnicolaw# $Id: 32exported_function_interface.t 965 2007-03-01 19:11:23Z nicolaw $ chdir('t') if -d 't'; my $rrdfile = -d 't' ? 't/32test.rrd' : '32test.rrd'; unlink $rrdfile if -f $rrdfile; use strict; BEGIN { use Test::More; eval "use RRDs"; plan skip_all => "RRDs.pm *MUST* be installed!" if $@; plan tests => 12 if !$@; } use lib qw(./lib ../lib); use RRD::Simple 1.35 qw(:all); use vars qw($rra %retention_periods %scheme_graphs @schemes %graph_return); require 'answers.pl'; ok(create($rrdfile, bytesIn => 'GAUGE', bytesOut => 'GAUGE', faultsPerSec => 'COUNTER' ),'create'); my $updated = time(); ok(update($rrdfile, bytesIn => 10039, bytesOut => 389, faultsPerSec => 0.4 ),'update'); ok(last_update($rrdfile) - $updated < 5 && last_update($rrdfile), 'last_update'); ok(join(',',sort(sources($rrdfile))) eq 'bytesIn,bytesOut,faultsPerSec', 'sources'); ok(my $period = retention_period($rrdfile),'retention_period'); my $default_period = $RRD::Simple::VERSION >= 1.33 ? 'mrtg' : 'year'; ok(abs($retention_periods{$default_period} - $period) < 1000,'retention_period result'); SKIP: { my $deep = 0; eval { require Test::Deep; Test::Deep->import(); $deep = 1; }; if (!$deep || $@) { skip 'Test::Deep not available', 1; } my $info = info($rrdfile); cmp_deeply( $info->{rra}, $rra, "info rra", ); } (my $imgbasename = $rrdfile) =~ s/\.rrd$//; ok(graph($rrdfile,destination => './'),'graph'); # By default we only have up to a year to graph # for unless we specify the 3 year scheme, so # dont bother checking for a 3years graph image. for (qw(daily weekly monthly annual)) { my $img = "$imgbasename-$_.png"; ok(-f $img,"$img"); unlink $img if -f $img; } unlink $_ for glob('*.png'); unlink $rrdfile if -f $rrdfile; 1; RRD-Simple-1.44/t/36no_sources.t0000444000076400007640000000460710746154056016177 0ustar nicolawnicolaw# $Id: 35average_hrule.t 965 2007-03-01 19:11:23Z nicolaw $ my $rrdfile = -d 't' ? 't/36test.rrd' : '36test.rrd'; unlink $rrdfile if -f $rrdfile; use strict; BEGIN { use Test::More; eval "use RRDs"; plan skip_all => "RRDs.pm *MUST* be installed!" if $@; plan skip_all => "RRDs version less than 1.2" if $RRDs::VERSION < 1.2; plan tests => 197 if !$@; } use lib qw(./lib ../lib); use RRD::Simple 1.44 (); ok(my $rrd = RRD::Simple->new(),'new'); my $end = time(); my $start = $end - (60 * 60 * 3); ok($rrd->create($rrdfile,'day', knickers => 'GAUGE', ),'create'); my $lastValue = 0; my $x = rand ( 10 ); for (my $t = $start; $t <= $end; $t += 60) { $lastValue = ( cos($t / 1000 ) + rand(2) ) + $x; $lastValue = 6 if $lastValue > 6; ok($rrd->update($rrdfile,$t, knickers => $lastValue, ),'update'); } for my $sources (('',undef)) { for my $file (glob('36test-*.png')) { unlink $file; } my $str = $rrd->graph($rrdfile, sources => $sources, 'CDEF:mycdef1=knickers,100,*' => '', 'CDEF:mycdef2=knickers,2,*' => '', 'CDEF:mycdef3=knickers,300,*' => '', 'LINE1:mycdef2#00ff00:MyCDef2' => '', 'PRINT:mycdef2:MIN:mycdef2 min %1.2lf' => '', 'PRINT:mycdef2:MAX:mycdef2 max %1.2lf' => '', 'PRINT:mycdef2:LAST:mycdef2 last %1.2lf' => '', 'GPRINT:mycdef2:MIN: min\:%10.2lf\g' => '', 'GPRINT:mycdef2:MAX: max\:%10.2lf\g' => '', 'GPRINT:mycdef2:LAST: last\:%10.2lf\l' => '', ); for my $p (qw(daily)) { ok($str->{$p}->[0] eq '36test-daily.png', 'graph without sources: rtn filename'); ok(defined $str->{$p}->[1]->[0] && $str->{$p}->[1]->[0] =~ /^mycdef2 min / && $str->{$p}->[1]->[0] !~ /knickers/, 'graph without sources: rtn ds values'); ok(defined $str->{$p}->[1]->[1] && $str->{$p}->[1]->[1] =~ /^mycdef2 max / && $str->{$p}->[1]->[1] !~ /knickers/, 'graph without sources: rtn ds values'); ok(defined $str->{$p}->[1]->[2] && $str->{$p}->[1]->[2] =~ /^mycdef2 last / && $str->{$p}->[1]->[2] !~ /knickers/, 'graph without sources: rtn ds values'); ok($str->{$p}->[2] =~ /^\d+$/ && $str->{$p}->[2] > 100, 'graph without sources: rtn width'); ok($str->{$p}->[3] =~ /^\d+$/ && $str->{$p}->[3] > 100, 'graph without sources: rtn height'); } ok(-e '36test-daily.png','created 36test-daily.png on disk'); } unlink $rrdfile if -f $rrdfile; unlink '36test-daily.png' if -f '36test-daily.png'; for my $file (glob('36test-*.png')) { unlink $file; } 1; RRD-Simple-1.44/t/34populate_test_data.t0000444000076400007640000000155410746154056017675 0ustar nicolawnicolaw# $Id: 34populate_test_data.t 965 2007-03-01 19:11:23Z nicolaw $ my $rrdfile = -d 't' ? 't/34test.rrd' : '34test.rrd'; unlink $rrdfile if -f $rrdfile; use strict; BEGIN { use Test::More; eval "use RRDs"; plan skip_all => "RRDs.pm *MUST* be installed!" if $@; plan tests => 5765 if !$@; } use lib qw(./lib ../lib); use RRD::Simple 1.35 (); ok(my $rrd = RRD::Simple->new(),'new'); my $end = time() - 3600; my $start = $end - (60 * 60 * 24 * 4); my @ds = qw(nicola hannah jennifer hedley heather baya); ok($rrd->create($rrdfile,'week', map { $_ => 'GAUGE' } @ds ),'create'); for (my $t = $start; $t <= $end; $t += 60) { ok($rrd->update($rrdfile,$t, map { $_ => int(rand(100)) } @ds ),'update'); } ok($rrd->last($rrdfile) == $end, 'last'); ok(join(',',sort $rrd->sources($rrdfile)) eq join(',',sort(@ds)), 'sources'); unlink $rrdfile if -f $rrdfile; 1; RRD-Simple-1.44/MANIFEST0000444000076400007640000000606710746154056014352 0ustar nicolawnicolawBuild.PL Changes complain.txt examples/ApacheAccessLogActivity.pl examples/ColloquyBotLogParser.pl examples/df.pl examples/disk-capacity-daily.png examples/example_apache_monitoring.png examples/foo.pl examples/graph.pl examples/graph2-daily.png examples/graph2.pl examples/hddtemp-daily.png examples/hddtemp.pl examples/iostat-hda-daily.png examples/iostat.pl examples/loadavg.pl examples/mem_usage-monthly.png examples/meminfo-daily.png examples/meminfo.pl examples/mod_perl.pl examples/network.pl examples/processes.pl examples/README examples/rrd-server/bin/rrd-client-infrant.pl examples/rrd-server/bin/rrd-client-mta-traffic.pl examples/rrd-server/bin/rrd-client-nagios-perfdata.pl examples/rrd-server/bin/rrd-client-switches.pl examples/rrd-server/bin/rrd-client.pl examples/rrd-server/bin/rrd-server.pl examples/rrd-server/bin/snmp.sh examples/rrd-server/bin/spider.sh examples/rrd-server/cgi-bin/by_graph.tmpl examples/rrd-server/cgi-bin/by_host.tmpl examples/rrd-server/cgi-bin/devel/edit_include.tmpl examples/rrd-server/cgi-bin/devel/error.tmpl examples/rrd-server/cgi-bin/devel/export.tmpl examples/rrd-server/cgi-bin/devel/foo.tmpl examples/rrd-server/cgi-bin/devel/rrd-export.cgi examples/rrd-server/cgi-bin/devel/RRDBrowseCommon.pm examples/rrd-server/cgi-bin/footer.tmpl examples/rrd-server/cgi-bin/header.tmpl examples/rrd-server/cgi-bin/host.tmpl examples/rrd-server/cgi-bin/index.tmpl examples/rrd-server/cgi-bin/instructions.tmpl examples/rrd-server/cgi-bin/list_graphs.tmpl examples/rrd-server/cgi-bin/list_hosts.tmpl examples/rrd-server/cgi-bin/rrd-browse.cgi examples/rrd-server/cgi-bin/rrd-server.cgi examples/rrd-server/cgi-bin/templates/cisco-include.html examples/rrd-server/cgi-bin/templates/netbsd-include.html examples/rrd-server/cgi-bin/templates/rhel-include.html examples/rrd-server/cgi-bin/templates/solaris-include.html examples/rrd-server/cgi-bin/templates/theme-cyan.css examples/rrd-server/cgi-bin/templates/theme-green.css examples/rrd-server/cgi-bin/templates/theme-magenta.css examples/rrd-server/cgi-bin/templates/theme-red.css examples/rrd-server/cgi-bin/templates/theme-ubuntu.css examples/rrd-server/cgi-bin/templates/theme-yellow.css examples/rrd-server/cgi-bin/templates/ubuntu-include.html examples/rrd-server/cgi-bin/templates/windows-include.html examples/rrd-server/etc/create.defs examples/rrd-server/etc/graph.defs examples/rrd-server/etc/rrd-httpd.conf examples/rrd-server/etc/rrd.me.uk.conf examples/rrd-server/etc/xinetd-rrd-client.conf examples/rrd-server/README examples/statlogs.pl examples/vmstat-cpu-daily.png examples/vmstat.pl INSTALL lib/RRD/Simple.pm lib/RRD/Simple/Examples.pod LICENSE Makefile.PL MANIFEST This list of files META.yml NOTICE README t/10pod.t t/11pod_coverage.t t/20compile.t t/21synopsis.t t/21synopsis_old.t t/22last_value.t t/23graph.t t/24retention_period.t t/25info.t t/26add_source.t t/27rename_source.t t/28heartbeat.t t/29on_missing_ds.t t/30assume_rrd_filename.t t/31create_assume_rrd_filename.t t/32exported_function_interface.t t/33correct_spelling.t t/34populate_test_data.t t/35average_hrule.t t/36no_sources.t t/answers.pl TODO RRD-Simple-1.44/INSTALL0000444000076400007640000000425110746154056014243 0ustar nicolawnicolawInstalling RRD::Simple ====================== If you already have the RRDs module installed (see the later sections below), then you can install the RRD::Simple module using the following commands in order: perl Makefile.PL perl Build perl Build test perl Build install Installing RRDtool from Source ============================== RRD::Simple requires the RRDs module (a part of RRDtool) to be available in the Perl @INC path. RRDtool can be downloaded from http://www.rrdtool.org or http://oss.oetiker.ch/rrdtool/. In RRDTool version 1.0.x, the RRDs module is located in the "rrdtool-1.0.xx/perl-shared/" directory. In version 1.2.x, it is located in "rrdtool-1.2.xx/bindings/perl-shared/". tar -zxf rrdtool-1.x.xx.tar.gz cd rrdtool-1.x.xx ./configure && make && make install # cd perl-shared # Version 1.0.x cd bindings/perl-shared # Version 1.2.x make clean perl Makefile.PL && make && make install echo "/usr/local/rrdtool-1.x.xx/lib" >> /etc/ld.so.conf ldconfig More recent versions of RRDtool will attempt to compile and install bindings for TCL, Python and Ruby. If you do not need nor want to compile these bindings, you should change the ./configure command as follows: ./configure --disable-tcl --disable-python --disable-ruby Installing RRDtool from Source on RHEL ====================================== If you are installing RRDtool from source under RHEL, you will probably need to install the following development RPMs before following the instructions above: zlib-devel libpng10-devel libpng-devel libart_lgpl-devel You may also want to optionally install these development RPMs if you wish to compile and install the RRDtool bindings for TCL, Python and Ruby: tcl-devel python-devel ruby-devel Installing RRDtool on Debian & Ubuntu ===================================== Run apt-get as root or with sudo as shown here to install the rrdtool and librrds-perl packages: sudo apt-get install rrdtool sudo apt-get install librrds-perl Installing RRDtool on Fedora Core ================================= Run yum as root or with sudo as shown here to install the rrdtool and rrdtool-perl packages: sudo yum install rrdtool sudo yum install rrdtool-perl RRD-Simple-1.44/Makefile.PL0000444000076400007640000000211510746154056015161 0ustar nicolawnicolaw# Note: this file was auto-generated by Module::Build::Compat version 0.03 unless (eval "use Module::Build::Compat 0.02; 1" ) { print "This module requires Module::Build to install itself.\n"; require ExtUtils::MakeMaker; my $yn = ExtUtils::MakeMaker::prompt (' Install Module::Build now from CPAN?', 'y'); unless ($yn =~ /^y/i) { die " *** Cannot install without Module::Build. Exiting ...\n"; } require Cwd; require File::Spec; require CPAN; # Save this 'cause CPAN will chdir all over the place. my $cwd = Cwd::cwd(); CPAN::Shell->install('Module::Build::Compat'); CPAN::Shell->expand("Module", "Module::Build::Compat")->uptodate or die "Couldn't install Module::Build, giving up.\n"; chdir $cwd or die "Cannot chdir() back to $cwd: $!"; } eval "use Module::Build::Compat 0.02; 1" or die $@; Module::Build::Compat->run_build_pl(args => \@ARGV); require Module::Build; Module::Build::Compat->write_makefile(build_class => 'Module::Build'); RRD-Simple-1.44/NOTICE0000444000076400007640000000013110746154056014107 0ustar nicolawnicolawContains software written by Nicola Worthington, nicolaw@cpan.org http://perlgirl.org.uk