From 69cb2a4fc80968aa32350cd615677f9e4d97aa87 Mon Sep 17 00:00:00 2001 From: Ben Charlton Date: Wed, 30 Mar 2011 19:04:35 +0100 Subject: [PATCH] initial commit --- COPYING | 339 ++++++++++++++++++ README.md | 100 ++++++ StatGraph.pm | 781 +++++++++++++++++++++++++++++++++++++++++ graphs/sg-p-small.png | Bin 0 -> 5285 bytes graphs/sg-p.png | Bin 0 -> 11736 bytes graphs/sg-p.psp | Bin 0 -> 21197 bytes graphs/sg-small.png | Bin 0 -> 4838 bytes graphs/sg.png | Bin 0 -> 10568 bytes graphs/sg.psp | Bin 0 -> 18123 bytes mkconf.pl | 12 + mkgraph.pl | 124 +++++++ statgraph.conf.example | 69 ++++ statgraph.pl | 201 +++++++++++ 13 files changed, 1626 insertions(+) create mode 100644 COPYING create mode 100644 README.md create mode 100755 StatGraph.pm create mode 100755 graphs/sg-p-small.png create mode 100755 graphs/sg-p.png create mode 100755 graphs/sg-p.psp create mode 100755 graphs/sg-small.png create mode 100755 graphs/sg.png create mode 100755 graphs/sg.psp create mode 100755 mkconf.pl create mode 100755 mkgraph.pl create mode 100755 statgraph.conf.example create mode 100755 statgraph.pl diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3afaa56 --- /dev/null +++ b/README.md @@ -0,0 +1,100 @@ += Statgraph = + +== Introduction == + +Statgraph is a simple tool for graphing usage statistic from a number of +unix hosts. + +Statgraph makes use of the statgrab tool from +http://www.i-scream.org/libstatgrab/ and has been tested on Linux, +Solaris and FreeBSD. In theory, any platform supported by libstatgrab +should work. + + +== Usage == +To make use of statgraph, you'll need perl and RRDtool, along with the +perl bindings for RRDtool. On debian, these are included in the +'librrds-perl' package. + +To collect statistics from a host, it will need the statgrab tool +installed from libstatgrab. On a debian/ubuntu system, you can simply +'apt-get install statgrab'. + +You'll need to decide how to collect statistics - either via a direct +TCP connection to the target host, or by executing a command of your +choice. This does mean you can make use of ssh with an ssh key if you +want to keep open ports to a minimum. + +=== Direct TCP === + +If you're collecting via TCP, the easiest way to set things up is to run +statgrab from inetd. + +In /etc/services, add: + + statgrab 27001/tcp + +and in /etc/inetd.conf: + + statgrab stream tcp nowait root /usr/sbin/tcpd /usr/bin/statgrab + +You don't have to run it as root, but there are some statistics that it +doesn't collect as an unprivileged user. The risk is relatively low as +statgrab doesn't accept any input, and will simply print the statistics +and exit, but there is always a risk with exposing a service. As always, +you should seriously consider firewalling access to this port to trusted +hosts only. + +=== Running a command === + +This has a bit more overhead, but does mean minimal changes to the +server you're connecting to. Any command that generates statgrab output +is fine. The simplest option is something like: + + ssh -i ssh_key user@hostname /usr/bin/statgrab + +=== Configuration File === + +The configuration for statgraph is statgraph.conf - this should be +fairly self-explanatory, and a few examples are provided in +statgraph.conf.example + +== Running == + +To check it's all working, run ./statgraph.pl manually. If that looks +good, add to cron and run once per minute. It will email you if a +connection to a host fails though, so you might want to redirect output +to a log file. I don't, since it's useful to know if a host is broken :) + +To generate graphs, run the ./mkgraph.pl script. I run this every 10 +minutes from cron, and it generates static html and .png images in +whatever you've configured the graphs to live. By default this is the +'graphs' directory inside the statgraph directory. + +This directory can be shared by a webserver and contains no dynamic code +whatsoever. + +== Known Issues == + + * Statgraph is insanely spammy if a host is down, unless you redirect + output + + * Mounts with : in the name (remote NFS mounts for example) don't show + up correctly. + + * There's no sanity checking for return values, since they could + massively vary. When I wrote statgraph the idea of 128 core machines + was unimaginable, but we're there now, so I'm reluctant to hard code + any values in. Occasionally a wonky value will make the graph scale a + bit silly. There's a tool called 'rrdtrim' that can fix these. On an + installation monitoring about 40 hosts, I probably have to fix 2 a + year. + + * Running it from cron can be a bit braindead sometime, and the + timeouts could be more effective - occasionally processes do get + wedged if the child does, but it's rare. + +== License == + +Statgraph is released under the GPLv2 license. See the COPYING file for +details. diff --git a/StatGraph.pm b/StatGraph.pm new file mode 100755 index 0000000..50a84a6 --- /dev/null +++ b/StatGraph.pm @@ -0,0 +1,781 @@ +use RRDs; +use strict; + +# Takes array ref of statgrab output. +# Returns hash structure +sub sgparse ($) { + my $text = shift; + my %tree; + foreach (@$text) { + chomp; + m/^([^=]*) = (.*)$/ or die "bad line in statgrab output"; + my @parts = split /\./, $1; + if ($#parts == 2) { + $tree{$parts[0]}{$parts[1]}{$parts[2]} = $2; + } else { + $tree{$parts[0]}{$parts[1]} = $2; + } + } + return %tree; +} + +sub confparse ($) { + my $text = shift; + my %tree; + foreach (@$text) { + chomp; + next if (m/^#/); + next if (m/^$/); + m/^([^=]*) = (.*)$/ or die "bad line in config $_"; + my @parts = split /\./, $1; + if ($#parts == 2) { + $tree{$parts[0]}{$parts[1]}{$parts[2]} = $2; + } else { + $tree{$parts[0]}{$parts[1]} = $2; + } + } + return %tree; +} + +## Connect to remote host and retrieve statgrab results +sub get_net_results ($$$) { + use IO::Socket; + my ($remote_host, $remote_port, $cache) = @_; + my ($line, $response, $socket, $flag, @res); + + $socket = IO::Socket::INET->new(PeerAddr => $remote_host, + PeerPort => $remote_port, + Proto => "tcp", + Type => SOCK_STREAM) + or die "Couldn't connect to $remote_host:$remote_port\n"; + + #while($line = <$socket>){ + # push @res, $line; + #} + @res = (<$socket>); + + my $results = join('', @res); + if ($results ne "") { + open CACHE, ">$cache"; + print CACHE $results; + close CACHE; + } else { + die "No result from $remote_host"; + } + return sgparse(\@res); +} + +## Run command on local host to retrieve statgrab results +## Can be used to get local stats, or call ssh, or similar... +sub get_exec_results ($$) { + my $command = shift; + my $cache = shift; + open STATGRAB, "$command|" or warn "$command failed: $!"; + my @res = (); + + my $results = join('', @res); + if ($results ne "") { + open CACHE, ">$cache"; + print CACHE $results; + close CACHE; + } else { + die "No result from $command"; + } + + return sgparse(\@res); + +} + +## Create RRDs where relevant +sub create_rrd ($$$$) { + my $rrdlocation = shift; + my $type = shift; + my $host = shift; + my $devname = shift; + + use RRDs; + + print " Creating RRD: $host $type $devname\n"; + + if ($type eq 'cpu') { + RRDs::create ("$rrdlocation$host.cpu.rrd", + "--step", "60", + "DS:idle:COUNTER:120:U:U", + "DS:iowait:COUNTER:120:U:U", + "DS:kernel:COUNTER:120:U:U", + "DS:nice:COUNTER:120:U:U", + "DS:swap:COUNTER:120:U:U", + "DS:total:COUNTER:120:U:U", + "DS:user:COUNTER:120:U:U", + "RRA:AVERAGE:0.5:1:2160", # 1.5 days + "RRA:AVERAGE:0.5:15:1008", # 1.5 weeks + "RRA:AVERAGE:0.5:60:1008", # 6 weeks + "RRA:AVERAGE:0.5:720:1460", # 2 years + "RRA:MAX:0.5:1:2160", # 1.5 days + "RRA:MAX:0.5:15:1008", # 1.5 weeks + "RRA:MAX:0.5:60:1008", # 6 weeks + "RRA:MAX:0.5:720:1460"); # 2 years + + } elsif ($type eq 'load') { + RRDs::create ("$rrdlocation$host.load.rrd", + "--step", "60", + "DS:min1:GAUGE:120:U:U", + "DS:min5:GAUGE:120:U:U", + "DS:min15:GAUGE:120:U:U", + "RRA:AVERAGE:0.5:1:2160", # 1.5 days + "RRA:AVERAGE:0.5:15:1008", # 1.5 weeks + "RRA:AVERAGE:0.5:60:1008", # 6 weeks + "RRA:AVERAGE:0.5:720:1460", # 2 years + "RRA:MAX:0.5:1:2160", # 1.5 days + "RRA:MAX:0.5:15:1008", # 1.5 weeks + "RRA:MAX:0.5:60:1008", # 6 weeks + "RRA:MAX:0.5:720:1460"); # 2 years + + } elsif ($type eq 'mem') { + RRDs::create ("$rrdlocation$host.mem.rrd", + "--step", "60", + "DS:cache:GAUGE:120:U:U", + "DS:free:GAUGE:120:U:U", + "DS:total:GAUGE:120:U:U", + "DS:used:GAUGE:120:U:U", + "RRA:AVERAGE:0.5:1:2160", # 1.5 days + "RRA:AVERAGE:0.5:15:1008", # 1.5 weeks + "RRA:AVERAGE:0.5:60:1008", # 6 weeks + "RRA:AVERAGE:0.5:720:1460", # 2 years + "RRA:MAX:0.5:1:2160", # 1.5 days + "RRA:MAX:0.5:15:1008", # 1.5 weeks + "RRA:MAX:0.5:60:1008", # 6 weeks + "RRA:MAX:0.5:720:1460"); # 2 years + + } elsif ($type eq 'page') { + RRDs::create ("$rrdlocation$host.page.rrd", + "--step", "60", + "DS:in:COUNTER:120:U:U", + "DS:out:COUNTER:120:U:U", + "RRA:AVERAGE:0.5:1:2160", # 1.5 days + "RRA:AVERAGE:0.5:15:1008", # 1.5 weeks + "RRA:AVERAGE:0.5:60:1008", # 6 weeks + "RRA:AVERAGE:0.5:720:1460", # 2 years + "RRA:MAX:0.5:1:2160", # 1.5 days + "RRA:MAX:0.5:15:1008", # 1.5 weeks + "RRA:MAX:0.5:60:1008", # 6 weeks + "RRA:MAX:0.5:720:1460"); # 2 years + + } elsif ($type eq 'proc') { + RRDs::create ("$rrdlocation$host.proc.rrd", + "--step", "60", + "DS:running:GAUGE:120:U:U", + "DS:sleeping:GAUGE:120:U:U", + "DS:stopped:GAUGE:120:U:U", + "DS:total:GAUGE:120:U:U", + "DS:zombie:GAUGE:120:U:U", + "RRA:AVERAGE:0.5:1:2160", # 1.5 days + "RRA:AVERAGE:0.5:15:1008", # 1.5 weeks + "RRA:AVERAGE:0.5:60:1008", # 6 weeks + "RRA:AVERAGE:0.5:720:1460", # 2 years + "RRA:MAX:0.5:1:2160", # 1.5 days + "RRA:MAX:0.5:15:1008", # 1.5 weeks + "RRA:MAX:0.5:60:1008", # 6 weeks + "RRA:MAX:0.5:720:1460"); # 2 years + + } elsif ($type eq 'user') { + RRDs::create ("$rrdlocation$host.user.rrd", + "--step", "60", + "DS:num:GAUGE:120:U:U", + "RRA:AVERAGE:0.5:1:2160", # 1.5 days + "RRA:AVERAGE:0.5:15:1008", # 1.5 weeks + "RRA:AVERAGE:0.5:60:1008", # 6 weeks + "RRA:AVERAGE:0.5:720:1460", # 2 years + "RRA:MAX:0.5:1:2160", # 1.5 days + "RRA:MAX:0.5:15:1008", # 1.5 weeks + "RRA:MAX:0.5:60:1008", # 6 weeks + "RRA:MAX:0.5:720:1460"); # 2 years + + } elsif ($type eq 'swap') { + RRDs::create ("$rrdlocation$host.swap.rrd", + "--step", "60", + "DS:free:GAUGE:120:U:U", + "DS:total:GAUGE:120:U:U", + "DS:used:GAUGE:120:U:U", + "RRA:AVERAGE:0.5:1:2160", # 1.5 days + "RRA:AVERAGE:0.5:15:1008", # 1.5 weeks + "RRA:AVERAGE:0.5:60:1008", # 6 weeks + "RRA:AVERAGE:0.5:720:1460", # 2 years + "RRA:MAX:0.5:1:2160", # 1.5 days + "RRA:MAX:0.5:15:1008", # 1.5 weeks + "RRA:MAX:0.5:60:1008", # 6 weeks + "RRA:MAX:0.5:720:1460"); # 2 years + + + } elsif ($type eq 'disk') { + RRDs::create ("$rrdlocation$host.disk.$devname.rrd", + "--step", "60", + "DS:read_bytes:COUNTER:120:U:U", + "DS:write_bytes:COUNTER:120:U:U", + "RRA:AVERAGE:0.5:1:2160", # 1.5 days + "RRA:AVERAGE:0.5:15:1008", # 1.5 weeks + "RRA:AVERAGE:0.5:60:1008", # 6 weeks + "RRA:AVERAGE:0.5:720:1460", # 2 years + "RRA:MAX:0.5:1:2160", # 1.5 days + "RRA:MAX:0.5:15:1008", # 1.5 weeks + "RRA:MAX:0.5:60:1008", # 6 weeks + "RRA:MAX:0.5:720:1460"); # 2 years + + } elsif ($type eq 'net') { + RRDs::create ("$rrdlocation$host.net.$devname.rrd", + "--step", "60", + "DS:rx:COUNTER:120:U:U", + "DS:tx:COUNTER:120:U:U", + "DS:ipackets:COUNTER:120:U:U", + "DS:opackets:COUNTER:120:U:U", + "DS:ierrors:COUNTER:120:U:U", + "DS:oerrors:COUNTER:120:U:U", + "RRA:AVERAGE:0.5:1:2160", # 1.5 days + "RRA:AVERAGE:0.5:15:1008", # 1.5 weeks + "RRA:AVERAGE:0.5:60:1008", # 6 weeks + "RRA:AVERAGE:0.5:720:1460", # 2 years + "RRA:MAX:0.5:1:2160", # 1.5 days + "RRA:MAX:0.5:15:1008", # 1.5 weeks + "RRA:MAX:0.5:60:1008", # 6 weeks + "RRA:MAX:0.5:720:1460"); # 2 years + } elsif ($type eq 'fs') { + RRDs::create ("$rrdlocation$host.fs.$devname.rrd", + "--step", "60", + "DS:used:GAUGE:120:U:U", + "DS:size:GAUGE:120:U:U", + "DS:used_inodes:GAUGE:120:U:U", + "DS:total_inodes:GAUGE:120:U:U", + "RRA:AVERAGE:0.5:1:2160", # 1.5 days + "RRA:AVERAGE:0.5:15:1008", # 1.5 weeks + "RRA:AVERAGE:0.5:60:1008", # 6 weeks + "RRA:AVERAGE:0.5:720:1460", # 2 years + "RRA:MAX:0.5:1:2160", # 1.5 days + "RRA:MAX:0.5:15:1008", # 1.5 weeks + "RRA:MAX:0.5:60:1008", # 6 weeks + "RRA:MAX:0.5:720:1460"); # 2 years + } +} + +sub update_rrd ($$) { + my $rrd = shift; + my $data = shift; + #print "$rrd, $data\n"; + RRDs::update($rrd, $data); + my $ERR=RRDs::error; + print "ERROR while updating $rrd: $ERR\n" if $ERR; +} + +sub create_graph ($$$$$$$$) { + my $rrdlocation = shift; + my $location = shift; + my $type = shift; + my $host = shift; + my $devname = shift; + my $offsets = shift; + my $friendly = shift; + my $colours = shift; + my %colours = %$colours; + + # Check colours + $colours{stack1} = '#FF0000' unless $colours{stack1}; + $colours{stack2} = '#FFFF00' unless $colours{stack2}; + $colours{stack3} = '#00FFFF' unless $colours{stack3}; + $colours{stack4} = '#00FF00' unless $colours{stack4}; + $colours{stack5} = '#0000FF' unless $colours{stack5}; + + $colours{load1} = '#CECFFF' unless $colours{load1}; + $colours{load5} = '#7375FF' unless $colours{load5}; + $colours{load15} = '#0000FF' unless $colours{load15}; + + $colours{area} = '#CECFFF' unless $colours{area}; + $colours{line} = '#0000FF' unless $colours{line}; + + $colours{in} = '#00FF00' unless $colours{in}; + $colours{out} = '#0000FF' unless $colours{out}; + + foreach my $offset (@$offsets) { + + if ($type eq 'cpu') { + create_graph_cpu($rrdlocation, $location, $host, $offset, \%colours); + } elsif ($type eq 'load') { + create_graph_load($rrdlocation, $location, $host, $offset, \%colours); + } elsif ($type eq 'mem') { + create_graph_mem($rrdlocation, $location, $host, $offset, \%colours); + } elsif ($type eq 'page') { + create_graph_page($rrdlocation, $location, $host, $offset, \%colours); + } elsif ($type eq 'proc') { + create_graph_proc($rrdlocation, $location, $host, $offset, \%colours); + } elsif ($type eq 'user') { + create_graph_user($rrdlocation, $location, $host, $offset, \%colours); + } elsif ($type eq 'swap') { + create_graph_swap($rrdlocation, $location, $host, $offset, \%colours); + } elsif ($type eq 'disk') { + create_graph_disk($rrdlocation, $location, $host, $devname, $offset, \%colours); + } elsif ($type eq 'net') { + create_graph_net($rrdlocation, $location, $host, $devname, $offset, \%colours); + } elsif ($type eq 'fs') { + create_graph_fs($rrdlocation, $location, $host, $devname, $offset, $friendly, \%colours); + } + + } +} + +sub create_graph_cpu ($$$$$) { + my $rrdlocation = shift; + my $location = shift; + my $host = shift; + my $offset = shift; + my $colours = shift; + my %colours = %{$colours}; + my $type = "cpu"; + + RRDs::graph ("$location$host.$type.$offset.png", + "--start", "-$offset", + "-l", "0", + "-a", "PNG", + "-t", "CPU usage for $host", + "--vertical-label", "% cpu used", + "DEF:idle=$rrdlocation$host.$type.rrd:idle:AVERAGE", + "DEF:iowait=$rrdlocation$host.$type.rrd:iowait:AVERAGE", + "DEF:kernel=$rrdlocation$host.$type.rrd:kernel:AVERAGE", + "DEF:nice=$rrdlocation$host.$type.rrd:nice:AVERAGE", + "DEF:swap=$rrdlocation$host.$type.rrd:swap:AVERAGE", + "DEF:user=$rrdlocation$host.$type.rrd:user:AVERAGE", + "AREA:kernel$colours{stack1}:kernel cpu", + "GPRINT:kernel:LAST:Current\\: \%8.2lf %s", + "GPRINT:kernel:AVERAGE:Average\\: \%8.2lf %s", + "GPRINT:kernel:MAX:Max\\: \%8.2lf %s\\n", + "STACK:swap$colours{stack2}:swap cpu ", + "GPRINT:swap:LAST:Current\\: \%8.2lf %s", + "GPRINT:swap:AVERAGE:Average\\: \%8.2lf %s", + "GPRINT:swap:MAX:Max\\: \%8.2lf %s\\n", + "STACK:iowait$colours{stack3}:iowait cpu", + "GPRINT:iowait:LAST:Current\\: \%8.2lf %s", + "GPRINT:iowait:AVERAGE:Average\\: \%8.2lf %s", + "GPRINT:iowait:MAX:Max\\: \%8.2lf %s\\n", + "STACK:nice$colours{stack4}:nice cpu ", + "GPRINT:nice:LAST:Current\\: \%8.2lf %s", + "GPRINT:nice:AVERAGE:Average\\: \%8.2lf %s", + "GPRINT:nice:MAX:Max\\: \%8.2lf %s\\n", + "STACK:user$colours{stack5}:user cpu ", + "GPRINT:user:LAST:Current\\: \%8.2lf %s", + "GPRINT:user:AVERAGE:Average\\: \%8.2lf %s", + "GPRINT:user:MAX:Max\\: \%8.2lf %s\\n"); + my $ERR=RRDs::error; + die "ERROR : $ERR\n" if $ERR; +} + +sub create_graph_load ($$$$$) { + my $rrdlocation = shift; + my $location = shift; + my $host = shift; + my $offset = shift; + my $colours = shift; + my %colours = %{$colours}; + my $type = "load"; + + RRDs::graph ("$location$host.$type.$offset.png", + "--start", "-$offset", + "-l", "0", + "-u", "1", + "-a", "PNG", + "-t", "load averages for $host", + "--units-exponent", "1", + "--vertical-label", "processes in run queue", + "DEF:load1=$rrdlocation$host.$type.rrd:min1:AVERAGE", + "DEF:load5=$rrdlocation$host.$type.rrd:min5:AVERAGE", + "DEF:load15=$rrdlocation$host.$type.rrd:min15:AVERAGE", + "LINE2:load1$colours{load1}:1 minute load ", + "GPRINT:load1:LAST:Current\\: \%8.2lf %s", + "GPRINT:load1:AVERAGE:Average\\: \%8.2lf %s", + "GPRINT:load1:MAX:Max\\: \%8.2lf %s\\n", + "LINE2:load5$colours{load5}:5 minute load ", + "GPRINT:load5:LAST:Current\\: \%8.2lf %s", + "GPRINT:load5:AVERAGE:Average\\: \%8.2lf %s", + "GPRINT:load5:MAX:Max\\: \%8.2lf %s\\n", + "LINE2:load15$colours{load15}:15 minute load", + "GPRINT:load15:LAST:Current\\: \%8.2lf %s", + "GPRINT:load15:AVERAGE:Average\\: \%8.2lf %s", + "GPRINT:load15:MAX:Max\\: \%8.2lf %s\\n"); + my $ERR=RRDs::error; + die "ERROR : $ERR\n" if $ERR; +} + +sub create_graph_mem ($$$$$) { + my $rrdlocation = shift; + my $location = shift; + my $host = shift; + my $offset = shift; + my $colours = shift; + my %colours = %{$colours}; + my $type = "mem"; + + RRDs::graph ("$location$host.$type.$offset.png", + "--start", "-$offset", + "-l", "0", + "-a", "PNG", + "-u", "100", + "-t", "memory usage for $host", + "--base", "1024", + "--vertical-label", "% memory used", + "DEF:free=$rrdlocation$host.$type.rrd:free:AVERAGE", + "DEF:cache=$rrdlocation$host.$type.rrd:cache:AVERAGE", + "DEF:used=$rrdlocation$host.$type.rrd:used:AVERAGE", + "DEF:total=$rrdlocation$host.$type.rrd:total:AVERAGE", + "CDEF:peruse=total,free,total,LT,free,0,IF,-,total,/,100,*", + "CDEF:percacuse=cache,total,LT,cache,0,IF,total,/,100,*", + "AREA:peruse$colours{area}:Used ", + "GPRINT:peruse:LAST:Current\\: \%8.2lf %s", + "GPRINT:peruse:AVERAGE:Average\\: \%8.2lf %s", + "GPRINT:peruse:MAX:Max\\: \%8.2lf %s\\n", + "LINE2:percacuse$colours{line}:Cache", + "GPRINT:percacuse:LAST:Current\\: \%8.2lf %s", + "GPRINT:percacuse:AVERAGE:Average\\: \%8.2lf %s", + "GPRINT:percacuse:MAX:Max\\: \%8.2lf %s\\n", + "GPRINT:total:LAST:Current total memory\\: \%.2lf %sb\\c"); + + my $ERR=RRDs::error; + die "ERROR : $ERR\n" if $ERR; +} + +sub create_graph_proc ($$$$$) { + my $rrdlocation = shift; + my $location = shift; + my $host = shift; + my $offset = shift; + my $colours = shift; + my %colours = %{$colours}; + my $type = "proc"; + + RRDs::graph ("$location$host.$type.$offset.png", + "--start", "-$offset", + "-l", "0",, + "-a", "PNG", + "-t", "processes on $host", + "--units-exponent", "1", + "--vertical-label", "num of processes", + "DEF:running=$rrdlocation$host.$type.rrd:running:AVERAGE", + "DEF:sleeping=$rrdlocation$host.$type.rrd:sleeping:AVERAGE", + "DEF:stopped=$rrdlocation$host.$type.rrd:stopped:AVERAGE", + "DEF:zombie=$rrdlocation$host.$type.rrd:zombie:AVERAGE", + "AREA:stopped$colours{stack1}:stopped processes ", + "GPRINT:stopped:LAST:Current\\: \%8.2lf %s", + "GPRINT:stopped:AVERAGE:Average\\: \%8.2lf %s", + "GPRINT:stopped:MAX:Max\\: \%8.2lf %s\\n", + "STACK:zombie$colours{stack2}:zombie processes ", + "GPRINT:zombie:LAST:Current\\: \%8.2lf %s", + "GPRINT:zombie:AVERAGE:Average\\: \%8.2lf %s", + "GPRINT:zombie:MAX:Max\\: \%8.2lf %s\\n", + "STACK:sleeping$colours{stack3}:sleeping processes", + "GPRINT:sleeping:LAST:Current\\: \%8.2lf %s", + "GPRINT:sleeping:AVERAGE:Average\\: \%8.2lf %s", + "GPRINT:sleeping:MAX:Max\\: \%8.2lf %s\\n", + "STACK:running$colours{stack4}:running processes ", + "GPRINT:running:LAST:Current\\: \%8.2lf %s", + "GPRINT:running:AVERAGE:Average\\: \%8.2lf %s", + "GPRINT:running:MAX:Max\\: \%8.2lf %s\\n"); + + my $ERR=RRDs::error; + die "ERROR : $ERR\n" if $ERR; +} + +sub create_graph_page ($$$$$) { + my $rrdlocation = shift; + my $location = shift; + my $host = shift; + my $offset = shift; + my $colours = shift; + my %colours = %{$colours}; + my $type = "page"; + + RRDs::graph ("$location$host.$type.$offset.png", + "--start", "-$offset", + "-l", "0",, + "-a", "PNG", + "-t", "paging activity on $host", + "--units-exponent", "1", + "--vertical-label", "pages/second", + "DEF:in=$rrdlocation$host.$type.rrd:in:AVERAGE", + "DEF:out=$rrdlocation$host.$type.rrd:out:AVERAGE", + "AREA:in$colours{in}:pages in ", + "GPRINT:in:LAST:Current\\: \%8.2lf %s", + "GPRINT:in:AVERAGE:Average\\: \%8.2lf %s", + "GPRINT:in:MAX:Max\\: \%8.2lf %s\\n", + "LINE2:out$colours{out}:pages out", + "GPRINT:out:LAST:Current\\: \%8.2lf %s", + "GPRINT:out:AVERAGE:Average\\: \%8.2lf %s", + "GPRINT:out:MAX:Max\\: \%8.2lf %s\\n"); + + my $ERR=RRDs::error; + die "ERROR : $ERR\n" if $ERR; +} + +sub create_graph_user ($$$$$) { + my $rrdlocation = shift; + my $location = shift; + my $host = shift; + my $offset = shift; + my $colours = shift; + my %colours = %{$colours}; + my $type = "user"; + + RRDs::graph ("$location$host.$type.$offset.png", + "--start", "-$offset", + "-l", "0", + "-u", "1", + "-a", "PNG", + "-t", "users on $host", + "--units-exponent", "1", + "--vertical-label", "users logged in", + "DEF:num=$rrdlocation$host.$type.rrd:num:AVERAGE", + "LINE2:num$colours{line}:Logged in users", + "GPRINT:num:LAST:Current\\: \%8.2lf %s", + "GPRINT:num:AVERAGE:Average\\: \%8.2lf %s", + "GPRINT:num:MAX:Max\\: \%8.2lf %s\\n"); + + my $ERR=RRDs::error; + die "ERROR : $ERR\n" if $ERR; +} + +sub create_graph_swap ($$$$$) { + my $rrdlocation = shift; + my $location = shift; + my $host = shift; + my $offset = shift; + my $colours = shift; + my %colours = %{$colours}; + my $type = "swap"; + + RRDs::graph ("$location$host.$type.$offset.png", + "--start", "-$offset", + "-l", "0", + "-a", "PNG", + "-u", "100", + "-t", "swap use on $host", + "--base", "1024", + "--vertical-label", "% swap used", + "DEF:free=$rrdlocation$host.$type.rrd:free:AVERAGE", + "DEF:used=$rrdlocation$host.$type.rrd:used:AVERAGE", + "DEF:total=$rrdlocation$host.$type.rrd:total:AVERAGE", + "CDEF:peruse=total,free,total,LT,free,0,IF,-,total,/,100,*", + "AREA:peruse$colours{area}:Used", + "GPRINT:peruse:LAST:Current\\: \%8.2lf %s", + "GPRINT:peruse:AVERAGE:Average\\: \%8.2lf %s", + "GPRINT:peruse:MAX:Max\\: \%8.2lf %s\\n", + "GPRINT:total:LAST:Current total swap\\: \%.2lf %sb\\c"); + + my $ERR=RRDs::error; + die "ERROR : $ERR\n" if $ERR; +} + +sub create_graph_disk ($$$$$$) { + my $rrdlocation = shift; + my $location = shift; + my $host = shift; + my $dev = shift; + my $offset = shift; + my $colours = shift; + my %colours = %{$colours}; + my $type = "disk"; + + RRDs::graph ("$location$host.$type.$dev.$offset.png", + "--start", "-$offset", + "-l", "0", + "-a", "PNG", + "-t", "disk io on $host for $dev", + "--base", "1024", + "--vertical-label", "bytes per second", + "DEF:read_bytes=$rrdlocation$host.$type.$dev.rrd:read_bytes:AVERAGE", + "DEF:write_bytes=$rrdlocation$host.$type.$dev.rrd:write_bytes:AVERAGE", + "AREA:read_bytes$colours{in}:read bytes ", + "GPRINT:read_bytes:LAST:Current\\: \%8.2lf %s", + "GPRINT:read_bytes:AVERAGE:Average\\: \%8.2lf %s", + "GPRINT:read_bytes:MAX:Max\\: \%8.2lf %s\\n", + "LINE2:write_bytes$colours{out}:write bytes", + "GPRINT:write_bytes:LAST:Current\\: \%8.2lf %s", + "GPRINT:write_bytes:AVERAGE:Average\\: \%8.2lf %s", + "GPRINT:write_bytes:MAX:Max\\: \%8.2lf %s\\n"); + + my $ERR=RRDs::error; + die "ERROR : $ERR\n" if $ERR; +} + +sub create_graph_net ($$$$$$) { + my $rrdlocation = shift; + my $location = shift; + my $host = shift; + my $dev = shift; + my $offset = shift; + my $colours = shift; + my %colours = %{$colours}; + my $type = "net"; + + RRDs::graph ("$location$host.$type.$dev.$offset.png", + "--start", "-$offset", + "-l", "0", + "-a", "PNG", + "-t", "network io on $host for $dev", + "--base", "1024", + "--vertical-label", "bytes per second", + "DEF:rx=$rrdlocation$host.$type.$dev.rrd:rx:AVERAGE", + "DEF:tx=$rrdlocation$host.$type.$dev.rrd:tx:AVERAGE", + "AREA:rx$colours{in}:recieved bytes ", + "GPRINT:rx:LAST:Current\\: \%8.2lf %s", + "GPRINT:rx:AVERAGE:Average\\: \%8.2lf %s", + "GPRINT:rx:MAX:Max\\: \%8.2lf %s\\n", + "LINE2:tx$colours{out}:transmitted bytes", + "GPRINT:tx:LAST:Current\\: \%8.2lf %s", + "GPRINT:tx:AVERAGE:Average\\: \%8.2lf %s", + "GPRINT:tx:MAX:Max\\: \%8.2lf %s\\n"); + + my $ERR=RRDs::error; + die "ERROR : $ERR\n" if $ERR; +} + +sub create_graph_fs ($$$$$$$) { + my $rrdlocation = shift; + my $location = shift; + my $host = shift; + my $dev = shift; + my $offset = shift; + my $mountpoint = shift || $dev; + my $colours = shift; + my %colours = %{$colours}; + my $type = "fs"; + $dev =~ s/:/\\:/g; + RRDs::graph ("$location$host.$type.$dev.$offset.png", + "--start", "-$offset", + "-l", "0", + "-a", "PNG", + "-u", "100", + "-t", "disk usage on $host for $mountpoint", + "--base", "1024", + "--vertical-label", "% used", + "DEF:used=$rrdlocation$host.$type.$dev.rrd:used:AVERAGE", + "DEF:size=$rrdlocation$host.$type.$dev.rrd:size:AVERAGE", + "DEF:used_inodes=$rrdlocation$host.$type.$dev.rrd:used_inodes:AVERAGE", + "DEF:total_inodes=$rrdlocation$host.$type.$dev.rrd:total_inodes:AVERAGE", + "CDEF:peruse=used,size,/,100,*", + "CDEF:perinode=used_inodes,total_inodes,/,100,*", + "AREA:peruse$colours{area}:space used ", + "GPRINT:peruse:LAST:Current\\: \%8.2lf %s", + "GPRINT:peruse:AVERAGE:Average\\: \%8.2lf %s", + "GPRINT:peruse:MAX:Max\\: \%8.2lf %s\\n", + "GPRINT:size:LAST:Current total space\\: \%.2lf %sb\\c", + "LINE2:perinode$colours{line}:inodes used", + "GPRINT:perinode:LAST:Current\\: \%8.2lf %s", + "GPRINT:perinode:AVERAGE:Average\\: \%8.2lf %s", + "GPRINT:perinode:MAX:Max\\: \%8.2lf %s\\n", + "GPRINT:total_inodes:LAST:Current total inodes\\: \%.2lf %s\\c"); + + my $ERR=RRDs::error; + die "ERROR : $ERR\n" if $ERR; +} + +sub htmlheader ($) { + my $title = shift; + return " + $title + "; +} + +sub htmlfooter () { + return "

Powered by StatGraph

+ "; +} + + +sub create_page ($$$$$) { + my $location = shift; + my $type = shift; + my $host = shift; + my $devname = shift; + my $offsets = shift; + + # print " Creating graphs: $host $type $devname\n"; + + my %nicenames = ( + 'cpu' => "CPU utilisation for $host", + 'load' => "Load averages for $host", + 'mem' => "Memory usage for $host", + 'page' => "Paging activity for $host", + 'proc' => "Processes for $host", + 'user' => "User activity for $host", + 'swap' => "Swap usage for $host", + 'disk' => "Disk IO for $host on $devname", + 'net' => "Network IO for $host on $devname", + 'fs' => "Filsystem Utilisation for $host on $devname"); + + + my $dev = ""; + $dev = ".$devname" if ($devname ne ""); + + open OUT, ">$location$host-$type$dev.html"; + print OUT htmlheader("$nicenames{$type}"); + print OUT "

$nicenames{$type}

\n"; + print OUT "

Last updated: " . nice_date(); + + foreach my $offset (@$offsets) { + print OUT "

" . nice_time($offset). "


\n"; + } + close OUT; +} + +sub nice_time ($) { + my $seconds = shift; + my $minute = '60'; + my $hour = '3600'; + my $day = 24 * $hour; + my $week = 7 * $day; + my $month = 4 * $week; + my $year = 365 * $day; + + my @stack; + + my $tmp = int($seconds / $year); + if ($tmp > 0) { + push @stack, "$tmp years"; + $seconds = $seconds % $year; + } + + $tmp = int($seconds / $month); + if ($tmp > 0) { + push @stack, "$tmp months"; + $seconds = $seconds % $month; + } + + $tmp = int($seconds / $week); + if ($tmp > 0) { + push @stack, "$tmp weeks"; + $seconds = $seconds % $week; + } + + $tmp = int($seconds / $day); + if ($tmp > 0) { + push @stack, "$tmp days"; + $seconds = $seconds % $day; + } + + $tmp = int($seconds / $hour); + if ($tmp > 0) { + push @stack, "$tmp hours"; + $seconds = $seconds % $hour; + } + + $tmp = int($seconds / $minute); + if ($tmp > 0) { + push @stack, "$tmp minutes"; + $seconds = $seconds % $minute; + } + + push (@stack, "$seconds seconds") if ($seconds > 0); + + return join (", ", @stack); +} + +sub nice_date () { + my ($seconds, $minutes, $hours, $day_of_month, $month, $year, $wday, $yday, $isdst) = localtime(time); + return sprintf("%02d:%02d:%02d-%04d/%02d/%02d\n", $hours, $minutes, $seconds, $year+1900, $month+1, $day_of_month); +} + +1; diff --git a/graphs/sg-p-small.png b/graphs/sg-p-small.png new file mode 100755 index 0000000000000000000000000000000000000000..28fc987616c66255b429134450149f8d76d75ca2 GIT binary patch literal 5285 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 literal 0 HcmV?d00001 diff --git a/graphs/sg-p.png b/graphs/sg-p.png new file mode 100755 index 0000000000000000000000000000000000000000..71b9c138e6748e523ec9415034e1ed3cd25447ac GIT binary patch literal 11736 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 Is8Qhm10p+_lK=n! literal 0 HcmV?d00001 diff --git a/graphs/sg-p.psp b/graphs/sg-p.psp new file mode 100755 index 0000000000000000000000000000000000000000..571adca122011c6e5c65f346379a186d6d88c7cb GIT binary patch literal 21197 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 z
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 M07*qoM6N<$f}mF{L;wH) literal 0 HcmV?d00001 diff --git a/graphs/sg.png b/graphs/sg.png new file mode 100755 index 0000000000000000000000000000000000000000..87eb5b32621b6a82a09668eccb9636df5fdcdd59 GIT binary patch literal 10568 zcmX9^1ymeMv&Ll!5+t}okOhLfTX1)`1+w^}!QCAa+}$le(4fKH-7UB~yuJUMb4E^2 z&DYh{)nz>qDoS6`0Ym^87#MU}8A&x57+47O*&hWCdQG!&eSqGWT%>edBp@JDmv0XC z6dK>`KrkAvAPP1%3OQ4I3U*djE($hoepWVqHVz7AR_KA+k~9VV4%tOkQ3`nu77Ys( zh)8CA18qWel+ksDfkDIm?}VLR^L>FfB3Owjiow9t#i6~JBErDX=g3NmX?QH37@{j^ zXfKobCabEbsH#{K0yUU)>U!iV440B6xk1#$ob^uJ@;KZeb#Aktiq??{mL&`s{U@m) z_0f9N z1GS-2*gW_<{-P)z*Xn2eoD)D*jFDUf%K_9&14E`K%tqQoGky;`x@F{-| z_BvRb%yR|Ni|;^?8QdrR)Fio!HzQS5oT3>CG?An-zbUoat$8i_sr^R1DRH6;2kGGyA~KYrt+#d~j%fzRn@SMc6Ro3 z(mN{*8IX`dHGBJ`EGbJwQ@t;xX5|t=;E;fB3DY_zYfih~UgzDR-@pCm=U;t&fsi=X z4FG&lSpM%tNvBA?Q#;lZUCNSDf+|*4kI%)$`%zKIWl13+>1%BZHG)pkzz-j?Q969B zA&_iA9MTTqFJD#}A~^m-7=TriD)?4lUDWw@>%P9Un|zZ774-#ga!Lk+lC0v|Hj|Pn zTVqex=C@is{N7%DS*xm=8f|y?o@&G6%pZvA{~?D&Mt%gt&;xDSaQLM9XQ8Mirtfby zr>8S7d3vX78-A=b7F9S6CjX5lXmXle3K9usP}*q8%r}G1?xyPGaoC`Q%#W9sl+0|j z|H-#|GQOzQGg@#5Q1<^ceDpx#&kM;e2^y+w|M7>VQxydQxesyZ*@XiOBO|B&6Gm>+ zUG|U#aqYFVdBKcbx1_}C`8fyl2{$;-*yFce!DUW}bMsElfVnvZ`xYWvLxXc~@5@%g`?v}zX4&T;pb7UxNLX`X zEFRrB47QmR7}RanqOa#!QX1}^foJOE>zm(6mb1RPdaA4YW$&N{9aS|Qg=odXyda8f z@*4hs5GqFYpZRF{Jv)whr5dp}h97FJPFvk%c1e^E zZMwL)C?q|*md>r~RjtPKkp!9P)s2tlF1_pM{cC0K{tOoJelS&zTcwKNBQg^7kl_+S z&^q28Am50MeDZ`&H*|sA-wHttjcWY|d!wW60s^WHiH!-XL`y^qbC=qFDRdRAug4bB zj7&}rCz+Xeteey^(a^f$;;kK>`pm8iSIwYHO8fe}c;H)v0P8C%NoX+}>ctHW#X!Fx z90DtLM!hXtrh`=B)b|zxU7Oq%RvWhV+4)LZ+Hw5@e%IZ|LYCK=5@nGEd8UCMv?HPo z+z;b}B&x6S{I#Sj%FllOXlWItvE&!Wqx-^xWz( z3@KX$J)1}g=*CM;PoEe&{n2R&fsk1ysMj~> z;g_|9RJ+YX;^l!NKa+Vu6icxF0*8E>@VTUp_>wi$B}G=VHUD)6q0WNX|fc1%nhNh2eXtgP@$lQxt8yvy_>Rm*#uY}IRu`wqd6BsL+zDh zzhB8Jx=!rG$MH1~+6vflLXsFMX-mua>D!e>{f#{yS_fsk=Lh}!=}Ri9iUs)j{Vt;_rl#Qev!;=Ib^@jrjdd1_r(8%y zSVW>9_#_S2I&$7$o?2bs8%!mxFLw+N38cPwJ)hnQc~u*A3YN2eZ%Iw7d%H^Efx5BZ z6XRKSf1Whml_w;S6YF(82|u4;sEHP>`@KI0z*_MSCj3?@$HhMfpk3n_2@1bG1Yvq! znBZV{e*2b|_#=Rzlr8t>#-y$J<!GD3 zp0Y9lhA9a%lz=jHnrM$GS$^dLwJ-6KWJUu-&1~hzL^Q=>G9nQy|;SSPEN=lB(m%e=yKX^kIh{0pg{^4{I~2H+~}^Pq@y~eRP-}l{!^AD zHRs>I;saBfb4W;9 zso~+y_V)9{B>lj^Xs`RXi;RsA?>poZEfZIkZrm+cHS$itFAj|rHpsfVA}Ce^5k1!4 z{zmxkoIEqFNc#~t9ay}NkIlgcd0Vn*^c)Rg2Z6m|@QTGCwlXoX`oBDUev$F&X6COW zBL${~v$K7la3ym?3ZzQ~mQ)l!qiKI_6Gb~9{X~+O7+5Nep+rgjWP0AEC+K8Wk(1-D z5bfu7hw%nvMe{{zOUwUN_)jdbsXF+y*UPQN88=dU_8& zzVGTLx#!RztIi1^MEtI#PW`c*j#&$=XnB+AF%qH$L*yp?x8Y(E4gc&4@|oY$$L`c< z_<$-*D_1yI%p^tiU@BBzE*r6=>h|EzM)ACQNHqK5Mc<%Aqrz1%v<2OuupVxZYBD<+!@;ApQV~&8)6f-|llpMoBHDGm=bWjAF8LNwxWm9OF zUs62lLy3qGU1#dbn!)4%VAC7;R#zSJmt2-DGxT6^zw+&4jt7He7mB+)g$K5x{B zc;4K_5TknBdU+icMQraB5lM)Ni7bCuKl1knaRwBR^E3nm=ytOHuB^Y}as=%41?;C( ze|$ju6jqBfe@y-7fl6*xNcOb0AdwVFTo;SN4DxX)3V&=mGvBkP{04>LP@i8N?4mGR z2j|AZLWVn*Vy4ijSBI=Mk4}t1f)XC3p8iWskd??`@1Xe`Co~?csYcuP)z5fA5p`(f z7vypGvpL>6`evWrLo98cdbPAJXO6K3|4USdq~{I5tir!*aF~UTVMI+-+?`i_W~4er z>L@2Gi9LXv@0nN$Eu<0JxdKZV%jpy7}L2SvpsK0cn7cT*{~M>Q7Kwh48V z7Mlqv+)r*rMGT5-uuP-l={KGP6m26TJRR4u7jRn-rPFmfZPb<19K^B@2nc;#Jjf?2 z138y>*YIdN@U4VQEI*AIhj~t*VM~CLdzklEzFfOSZ~zI@PsF^b%JOGF%eLj!0Hhj{<~gkRzk#1>Y^E8<5@ z-CI$Q9lf7T^8ID^z1hM$I>pD<*3^?IEM9e2_IP@zijc4g`!X&RPr`coQB;MUsAVD^ z*f8jT26%d;`(XFmA!jg7{Ck#xJn&z5IMg>ojqBPxOuTBdmRD|v&l-#N*cEO_N?hDp znZMG&b+J~p0bz*-3=tB)W&~1KUY@DAHBXr>u^0uScYj&jupF470jnCbrlM`2g5c)S zf=So)mbc%h$wJll&92-;ev4A=ELuKAI=J^AMO$GZ#SbBsby^e>7A!Kefcsg03NYBL z=ztSNh(Vl=1+(FMtP6bqfCGhJO>wd8I!s?fBNtZyghuLXTcVbKF7;P48*rzxvir(D z_8`h={d+~tYEl+_Do2JDzkrpO7c^Zw2o1YTnPIn&`tF1{S$D8EbxF;?rZhFK=(t3> zC41N|ue>RrOZ;S@~qor94m)))kz3LmH5Tg*K(!sWU8_3c-Xe~ejPrZM3f6>x2 zY(#2NUMWE<&hcb=`=Yv{FGQD~uUH%ADan#*J47;RB(HdLAFUY+{gUBz)cBv^_v5T} zYDv&ai$IY#DqYh7J%O>*bZp&7A44zD?nmJxyR?4o=)fq<#SQB3uTTQ6V%+UtT}3=S;d{}s%HR#FN8#61 zo@nwJaw@n_T_=ZpiCkU=acfKeO)47A{rA{YSZFKk7ELz)deM6-f41&h%@+RG8W#^; zEe7W#DaC3=5zYH?j{qMMXsOU@ZlNcd%$1TxD5uEVMVP#5&tJPtpHjt)69*5fzlE_+9Ap}#0_+R+E=0M89qgGcZvoL zmiM1^R5F5z!nmAvqyrDN`S?0C!i+U;B5wfjxiI4|u(GP^u!DqgO)&DonP3F7@@ldJ zL&nXosKmj6thRAPti@NfnBNB?p(dVKDY1Yb(bK9xjxV!;HPBCCbbLZYnS?}ea=n^? zDcJY&%Ju4Vjr!QwxS|RMlJM(|r(EQLVP%kj!ZP;sIYxk;)-D=!N~axGD>zc%8QNse_Sn|A=VcK>3E1t|9| zHC(H!C&k3vUuvXhnD$h9#}TKncv3}n&Xfo__WRYse@B_?LN}E$w;t@V$=uxJ^2i(N zN8fEvpY5r@LWFuwZ>o?GB4jt}F#r5~rQc_I+dpj<$S#~?<>ZYKRiU*V4awNy+)r*W zY?l%KxP`JY2(!p|pde)6B}u6m+xW$9f4U=qpcc!M2+{@;9+y)I{&$PhX0g%5K=;^l*vv-!7I=rEs=K+>|or`Hr*h@;gK zVQ_wT4H$1K7>%fuBoo6UUf=HRGa5=uiuWI-l%(aP1+hUyL6_F7EA1HEGw1Ten-l+bnQuXi{<;sF)TK1Fg)XtP+scfxBC(t#8o(&6 zV+E^&-f^Wtte5L9>s5XIwyiGMHlC-4=ktfd0z19a+hp8ksZM$+WBY3Ogg$}#25JD> zLMX45D(D{moQ+F_m4|25-b)%YC+pRPg=u{U>em^m}3FWRe{ju1RlP@0ze-@l7oUbH9#%wE1?u?0M$4PSyfezOyZUK+nxYZL0Fb@ zfkbJ4eUdKj!mJ;KBA%04!h1rDHF@8cDL;k9V8%7xVnqdR}1^W)+oU03sq9 zW5Ty0SEk>zK*gn5X`FAiYDBU$Q-5Wl3EMYuDnA9abTfYG^u+KXPG~OAnI^*7kBg(g z37T;dSPOAojK$Nl?hZ6eo}!8!5APz|Kke*Te_qdcUT?oLUw;fHAhcZfa<@FY&vHI% zIe@7sjO1qiRTZR~*Qu$x;@_+7cfS6PPiSq&9GF4eI5*a}mE-QN91kKiB?{NBcXx*mU;G5jM9_JVF*$PHT81#pmtOftmSFH{P&ascBPF-5Ff& zls`jr##l@emYG?Cr)7jt+68ElD`zC2WM{`NrkS>%NaEBC-lpye{-rmwc7{I~6t8Mg zn<45M90`Vu$O;MFFHs@&EEPzn$no~KAogT0*_#KkpmXVx#F*pdVg7d_Zy?U8a`jyeB(|F{W z3$1P7jriCcZnEJe(<=(6;5Bi5ND5@EMcSa*>mLjaCElJz)E8|hmgmP9R{8Rg8NZg4 z(-y)Zm5;q!MOOSX^Vy^s6UE&%PZXBVf)N?PLQ2PLd_?}I{AkjR|7J)gj(%aIYqDN` z=!m-3_kTM$Tb!B-3WbCgh|#>0pFQq5IU}{|S#f5juTJj7`h`yu6~e^LO)#1o%Y5=k zbTu`Vt0t@1ZYNw_IpWBzv@~fC_JzsFbE-TymQWd0Ah+dB@ax|kCYBqcymz@%7vHI!(c(A_eA-DdkXZnGi3v)u`bL$on z?3)H{KEKPpjWhPh!W9o+V}$kKr~v>PdkaAg^A63ual6Jj<3p-8wJ2e5UVva;kJDub zmXtH}3|I2cLcw$kW_ye+X;ci(X8tp{mneN}uM!fJ!)S}GT#=1%vG8-};xK0u%aA(h zujH!E3hd=@^FEX> zyi*wasC~I1{fx-fyazx;Wp3;H1+3u8`XH@Vwy{YfHTITxtVlT@qK@V&I>e7;@g1CE z^BPNv9a_698KU33s;QcRi7*?QIV6NdrAkWJ^f}5OvWJY_Vn93&CSaQ4fYUnL__K|9 zjDw4px@(iEw0FS7%sh7)?W~JxfcJ!v6bRX;jbaTd*3h^KpRb=R4trTvm16uZNYNq= zfJg@{!}oBh;J}|ys}g37H$LCVGMNM&buX*rssOb6xjkZNTFM7M=cK3CE%_FXkL(Pp zgrp#J&F{Dvu^4v((`?`&#bDLUVBpjsQ_q{>W_vv<7_FwzXrXk|<{d-X!Afxu}8Y4?@^ApPtCV)6JR>NcpIQ%xPDG z0*@&1H&KvE7Sla6HFe8B`GalzEJr9V`V4%8BP#YL?oF@H6h6{voaXbQrd0X^Z`8gb zD?r9dreHGd&8AQ zbzDH1rpcq@eo%mUwW8oGmyie`+oY+jM5h@Fz)sjJ7?eKaUKyScP8vH`6Hx7jH#13cJg;;FHK$(ZsNW71ySX^y_$Mg zm!$(p!$hh+jSNx$Nx{FwG?!#nn5eFuBU*eGx0ZVMY6(Hd2GK&hyTqjG18+?sa^3f9 z5q0%H-|yGRs}0h)X5a#bIIQfuSB@cT7CEDQ%}diSCOS@kCC9T#%`;?!BAUIL-9S8L z%iS+7Zg-z+wC!Ruk+ha=9jP=G1+!N=^ao(DtgTfGuf3VNK2G!4Ycy&LfF-pyOcRrn znP}~Tp=NAnCvHpr{ivjOZ%=r-*dJ9oL<6C8Pcz$!4O0Rcv23h2Dekj zSv?0@N``M8Beb(OT6;}`yY!kx=d7}Ndh=1YKTWbz$Fw))jr9dY*T#=oSk?W`9Uw$A^{Yo0_4E088fMI|0` zG(D2|v9GC#E?=A#=<(?zO%~~xjVRph?PIP_qF;H1*V`;Zb+D=m`IR;+GA{U1Edx^_ z;l-roPU7N0+ZeC8NspZa^o)ag^gWY-u4_CijhR_$yQDaCUs=2{{F6$fCvDhYb|ut^ zh$XMPWZ!oo4`79j4ObYpSn3sNtlA&HndS}4vsKiv7!WJ#Ue`3k z^v^DJhWd#vxXju$EiGue*~)wpEu9lrUbpFTE?DfKyStqujYcjkqUyMP;kW+zXVVDh z#ed$F69L&NDXU8@sx1K@M>>7@E{?ja5bi#|6JfPge>fMh$}$9DTUuJ0i9|9?ubyWw zJa?V@O|B4=kzt@?D20hz80lhrH(7G5j&31a(2s!;(e~nio;M!eFZN#tPH1oMgip?M zNQiOZ^I7q>h`K%m_^AT`U3v12IzNB@WHcDHpm5|-W8*ly?>+2W8|rb~Y@{HcuwQGX z(d`f1h9fDKDJ-0++TTy8u0~~5vu*m}h?AsmgpTmp8PZ9cBEG!LU5oC68_s!2a`$-F z8$I2G8NN4~rjIf3#oRo}@zT|`;?d!aYLl9mB*=5s=AL{xr3?n8Sy5 zjENqSp5f5LfP^fOU^l;XAv{ozPyontKuvRkk7WAg9rba|0+Ef0HNXa39rdR?T{|i( z3jf@8<4x!hW%c6E3nXZMXAJ&eB91TXk7vg{*hZUNq*E)!d^~}L%IU+82N<$=G0y%z zriu)zfFF;Y^sbVOOUDMDPAqa3)J`8fU^N^~r^YIDOp9dn&wY3PCBV5rlRXAb-y=^{%YAnV5m6ex4)#l>_AKa!Ja+2)P(Tx!RL zCiN)Abj4<;W}I{WMHDm*X_1Hy$r%~dJ7K$di2nCtQRY62*JV%#I?SWIfT2cQC^!HK ziL7-^*`;E+)2D?61i=5eP>ooo$=dR-B@_{as2b4ACR6nDpPK4-r|za_6Zv-{l4QK# zlI0`H!l|*X8D3q>yZAB;z>S8I2q&Z&XL9RjN^s z8s_3!3SHKbnO>0}i4^An^+J21H*I*8X%R8GCZ5+|ljRdLtFV+r03jyqE!G*AN#f<^ znZ_jOZ>dn(^u$*|C3D%8;qV6Jy3L3aCYruXJs4kexn@T)}Z?K@iCP9L01tL0*OAx9uBC-#^mIx5^$l{ ze(>{q@2#(|*u)Cyogu<-y*WgmS7IBpXE5ln(Vq9lbjdaqqQD8YEfUp@K*q+wQOcxu z;eT%vh0lk<_?XI!&w}uP$lBw9Uc~73KP`oymj9(@glGSJ}wuYQ@3IehtjRq6DOufsKKNR$Ycyo^<^D zXTO?}bIExB)XjkRvSh=}ng#yXqX;3*hIO@m|byb3UXD|j#)MsilbsXV3F&7z1 z8K}-X7QQxSRhP2*z25$P?5sva<`;94fm|i-3@msGYK?JeB_(2$#J)5&p_ekyoaAHb z0TXj8`67!rngFn*86%nM!Zl3dU% zq0J|^;o3c?rf)NC0Lmx@N+$8Y-06u|1k^lez literal 0 HcmV?d00001 diff --git a/graphs/sg.psp b/graphs/sg.psp new file mode 100755 index 0000000000000000000000000000000000000000..b5769966b5fe2004d3b7a00993848c03c09624cd GIT binary patch literal 18123 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 +close CONFIG; + +my %CONFIG = confparse(\@CONFIG); +my %HOSTS = %{$CONFIG{hosts}}; +%CONFIG = %{$CONFIG{config}}; + +## Set default configuration options if they've not been specified +my $defaultport = $CONFIG{defaultport} || 27001; +my $offsets = $CONFIG{offsets} || '10800 86400 604800 2419200 31536000'; +my @offsets = split(/\s+/, $offsets); + +my $rrdlocation = $CONFIG{rrdlocation} || "rrd/"; +my $graphlocation = $CONFIG{graphlocation} || "graphs/"; + + +open INDEX, ">$graphlocation/index.html.tmp"; +print INDEX htmlheader("StatGraph results"); +print INDEX "

StatGraph results

"; +print INDEX "

Last updated: " . nice_date; +print INDEX "

    "; + + +## Main loop - run once for each host +foreach my $host (sort keys %HOSTS) { + print "+ $HOSTS{$host}{displayname}\n"; + print INDEX "
  • $host"; + print INDEX " - $HOSTS{$host}{comment}
  • "; + + my %ignore; + + foreach (split(/\s+/, $HOSTS{$host}{ignore})) { + $ignore{$_} = 1; + } + + open CACHE, "$rrdlocation$host.txt" || warn "$host has no cache\n"; + my @res = (); + close CACHE; + my %results = sgparse(\@res); + + + ## Simple check that we've got reasonable statgrab data + unless ($results{const}{0} eq '0') { + warn "Bad statgrab results for $host"; + next; + } + + open SUMM, ">$graphlocation$host.html"; + print SUMM htmlheader("$host summary"); + my %colours = %{$CONFIG{colour}}; + + print SUMM "

    $host summary for last " . nice_time($offsets[0]). "

    "; + print SUMM "

    $HOSTS{$host}{comment}

    "; + print SUMM "

    Last updated: " . nice_date; + + my %nicenames = ( + 'cpu' => "CPU utilisation for $host", + 'load' => "Load averages for $host", + 'mem' => "Memory usage for $host", + 'page' => "Paging activity for $host", + 'proc' => "Processes for $host", + 'user' => "User activity for $host", + 'swap' => "Swap usage for $host"); + + ## Generate graphs + foreach ('cpu', 'load', 'mem', 'page', 'proc', 'user', 'swap') { + if (-e "$rrdlocation$host.$_.rrd") { + create_graph($rrdlocation, $graphlocation, $_, $host, '', \@offsets, '', \%colours); + create_page($graphlocation, $_, $host, '', \@offsets); + print SUMM "

    $nicenames{$_}


    \n"; + } + } + + ## net device RRDs + foreach my $dev (sort keys %{ $results{net} }) { + unless (defined $ignore{"net.$dev"}) { + if (-e "$rrdlocation$host.net.$dev.rrd") { + create_graph($rrdlocation, $graphlocation, 'net', $host, $dev, \@offsets, '', \%colours); + create_page($graphlocation, 'net', $host, $dev, \@offsets); + print SUMM "

    Network IO for $host on $dev


    \n"; + } + } + } + + ## disk device RRDs + foreach my $dev (sort keys %{ $results{disk} }) { + unless (defined $ignore{"disk.$dev"}) { + if (-e "$rrdlocation$host.disk.$dev.rrd") { + create_graph($rrdlocation, $graphlocation, 'disk', $host, $dev, \@offsets, '', \%colours); + create_page($graphlocation, 'disk', $host, $dev, \@offsets); + print SUMM "

    Disk IO for $host on $dev


    \n"; + } + } + } + + ## fs device RRDs + foreach my $dev (sort keys %{ $results{fs} }) { + unless (defined $ignore{"fs.$dev"}) { + if (-e "$rrdlocation$host.fs.$dev.rrd") { + create_graph($rrdlocation, $graphlocation, 'fs', $host, $dev, \@offsets, $results{fs}{$dev}{mnt_point}, \%colours); + create_page($graphlocation, 'fs', $host, $dev, \@offsets); + print SUMM "

    Filesystem Utilisation for $host on $dev


    \n"; + } + } + } + print SUMM htmlfooter; + close SUMM; +} +print INDEX "
"; +print INDEX htmlfooter; +close INDEX; + +move("$graphlocation/index.html.tmp", "$graphlocation/index.html"); diff --git a/statgraph.conf.example b/statgraph.conf.example new file mode 100755 index 0000000..4e5202b --- /dev/null +++ b/statgraph.conf.example @@ -0,0 +1,69 @@ +########################### +# Default config options +# +# All these are optional, and override the defaults + +## Number of seconds from "now" to generate graphs +## Default 3h 1d 1w 1m 1y +config.offsets = 10800 86400 604800 2419200 31536000 + +## Location for graphs and rrd files +config.graphlocation = graphs/ +config.rrdlocation = rrd/ + +## Default port for 'connect' method +config.defaultport = 27001 + +## Colours for graphs +# contrasty stack colours, eg cpu. +config.colour.stack1 = #FF0000 +config.colour.stack2 = #FFFF00 +config.colour.stack3 = #00FFFF +config.colour.stack4 = #00FF00 +config.colour.stack5 = #0000FF + +# load colours +config.colour.load1 = #CECFFF +config.colour.load5 = #7375FF +config.colour.load15 = #0000FF + +# colours for line on top of area graphs, like swap/mem +config.colour.area = #CECFFF +config.colour.line = #0000FF + +# in refers to rx, bytes_read, etc... +config.colour.in = #00FF00 +config.colour.out = #0000FF + +########################### +# Hosts section +# +# REQUIRED: hosts.NAME.displayname +# REQUIRED: hosts.NAME.method +# OPTIONAL: hosts.NAME.comment +# OPTIONAL: hosts.NAME.ignore +# +# if using method = exec +# hosts.NAME.execcommand is REQUIRED +# +# if using method = connect +# hosts.NAME.hostname is REQUIRED +# hosts.NAME.port is OPTIONAL + +hosts.kitten.displayname = kitten.example.com +hosts.kitten.method = connect +hosts.kitten.hostname = kitten.example.com +hosts.kitten.port = 27001 +hosts.kitten.comment = development machine +hosts.kitten.ignore = net.dummy0 net.lo + +hosts.nero.displayname = nero.example.com +hosts.nero.method = exec +hosts.nero.execcommand = ssh nero.example.com /usr/bin/statgrab +hosts.nero.comment = some random server + +hosts.localhost.displayname = localhost.example.com +hosts.localhost.method = exec +hosts.localhost.execcommand = /usr/bin/statgrab +hosts.localhost.comment = This host + diff --git a/statgraph.pl b/statgraph.pl new file mode 100755 index 0000000..f61b4ec --- /dev/null +++ b/statgraph.pl @@ -0,0 +1,201 @@ +#!/usr/bin/perl + +# statgraph: A simple host resource graphing tool +# Copyright (C) 2004-2011 Ben Charlton +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; see the file COPYING. If not, write to the Free +# Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301 USA, or see http://www.gnu.org + +use strict; +use StatGraph; + +## TODO: configure with getopt +my $config = "statgraph.conf"; + +## Get configuration +open CONFIG, $config || die "Cannot open $config: $!"; +my @CONFIG = (); +close CONFIG; + +my %CONFIG = confparse(\@CONFIG); +my %HOSTS = %{$CONFIG{hosts}}; +%CONFIG = %{$CONFIG{config}}; + +## Set default configuration options if they've not been specified +my $defaultport = $CONFIG{defaultport} || 27001; +my $rrdlocation = $CONFIG{rrdlocation} || "rrd/"; + + +my $pid = $$; +my $parent = 0; +my @kids = (); +my $host; + +## Main loop - run once for each host +FORK: foreach my $currhost (keys %HOSTS) { + + my $newpid = fork(); + if ( not defined $newpid ) { + # if return value of fork() is undef, something went wrong + die "fork didn't work: $!\n"; + } + elsif ( $newpid == 0 ) { + # if return value is 0, this is the child process + $parent = $pid; # which has a parent called $pid + $pid = $$; # and which will have a process ID of its very own + @kids = (); # the child doesn't want this baggage from the parent + $host = $currhost; + last FORK; # and we don't want the child making babies either + } + else { + # the parent process is returned the PID of the newborn by fork() + push @kids, $newpid; + } + +} + +if ( $parent ) { + # if I have a parent, i.e. if I'm the child process + my %results; + my %ignore; + + foreach (split(/\s+/, $HOSTS{$host}{ignore})) { + $ignore{$_} = 1; + } + + ## exec method + if ($HOSTS{$host}{method} eq 'exec') { + unless (defined $HOSTS{$host}{execcommand}) { + die "execcommand not specified for $host, skipping..."; + } + %results = get_exec_results($HOSTS{$host}{execcommand}, "$rrdlocation$host.txt"); + + ## Network socket connection method + } elsif ($HOSTS{$host}{method} eq 'connect') { + unless (defined $HOSTS{$host}{hostname}) { + die "hostname not specified for $host, skipping..."; + } + my $port = $HOSTS{$host}{port} || $defaultport; + %results = get_net_results($HOSTS{$host}{hostname}, $port, "$rrdlocation$host.txt"); + + ## Unknown method + } else { + die "Unknown method specified for $host, skipping..."; + } + + ## Simple check that we've got reasonable statgrab data + unless ($results{const}{0} eq '0') { + die "Bad statgrab results"; + } else { + print "got result for $host\n"; + } + + ## Update RRDs + foreach ('cpu', 'load', 'mem', 'page', 'proc', 'user', 'swap') { + unless (-e "$rrdlocation$host.$_.rrd") { + create_rrd($rrdlocation, $_, $host, ''); + } + } + update_rrd("$rrdlocation$host.cpu.rrd", sprintf("N:%s:%s:%s:%s:%s:%s:%s", + $results{cpu}{idle}, + $results{cpu}{iowait}, + $results{cpu}{kernel}, + $results{cpu}{nice}, + $results{cpu}{swap}, + $results{cpu}{total}, + $results{cpu}{user})); + + update_rrd("$rrdlocation$host.load.rrd", sprintf("N:%s:%s:%s", + $results{load}{min1}, + $results{load}{min5}, + $results{load}{min15})); + + update_rrd("$rrdlocation$host.mem.rrd", sprintf("N:%s:%s:%s:%s", + $results{mem}{cache}, + $results{mem}{free}, + $results{mem}{total}, + $results{mem}{used})); + + update_rrd("$rrdlocation$host.page.rrd", sprintf("N:%s:%s", + $results{page}{in}, + $results{page}{out})); + + update_rrd("$rrdlocation$host.proc.rrd", sprintf("N:%s:%s:%s:%s:%s", + $results{proc}{running}, + $results{proc}{sleeping}, + $results{proc}{stopped}, + $results{proc}{total}, + $results{proc}{zombie})); + + update_rrd("$rrdlocation$host.user.rrd", sprintf("N:%s", + $results{user}{num})); + + update_rrd("$rrdlocation$host.swap.rrd", sprintf("N:%s:%s:%s", + $results{swap}{free}, + $results{swap}{total}, + $results{swap}{used})); + + ## net device RRDs + foreach my $dev (keys %{ $results{net} }) { + unless (defined $ignore{"net.$dev"}) { + unless (-e "$rrdlocation$host.net.$dev.rrd") { + create_rrd($rrdlocation, 'net', $host, $dev); + } + update_rrd("$rrdlocation$host.net.$dev.rrd", sprintf("N:%s:%s:%s:%s:%s:%s", + $results{net}{$dev}{rx} || 0, + $results{net}{$dev}{tx} || 0, + $results{net}{$dev}{ipackets} || 0, + $results{net}{$dev}{opackets} || 0, + $results{net}{$dev}{ierrors} || 0, + $results{net}{$dev}{oerrors} || 0)); + } + } + + ## disk device RRDs + foreach my $dev (keys %{ $results{disk} }) { + unless (defined $ignore{"disk.$dev"}) { + unless (-e "$rrdlocation$host.disk.$dev.rrd") { + create_rrd($rrdlocation, 'disk', $host, $dev); + } + update_rrd("$rrdlocation$host.disk.$dev.rrd", sprintf("N:%s:%s", + $results{disk}{$dev}{read_bytes}, + $results{disk}{$dev}{write_bytes})); + } + } + + ## fs device RRDs + foreach my $dev (keys %{ $results{fs} }) { + unless (defined $ignore{"fs.$dev"}) { + unless (-e "$rrdlocation$host.fs.$dev.rrd") { + create_rrd($rrdlocation, 'fs', $host, $dev); + } + update_rrd("$rrdlocation$host.fs.$dev.rrd", sprintf("N:%s:%s:%s:%s", + $results{fs}{$dev}{used}, + $results{fs}{$dev}{size}, + $results{fs}{$dev}{used_inodes}, + $results{fs}{$dev}{total_inodes})); + } + } +} + +else { + # parent process needs to preside over the death of its kids + while ( my $kid = shift @kids ) { + my $reaped = waitpid( $kid, 0 ); + unless ( $reaped == $kid ) { + warn "Something's up: $?\n"; + } + } +}