/[rrd-simple-monitoring]/bin/rrd-server.pl
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Annotation of /bin/rrd-server.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1 - (hide annotations)
Thu Jul 16 18:48:19 2009 UTC (14 years, 9 months ago) by dpavlin
File MIME type: text/plain
File size: 16281 byte(s)
import upstream http://rrd.me.uk/rrd-simple-monitoring.tar.gz

without prerequisities

1 dpavlin 1 #!/bin/env perl
2     ############################################################
3     #
4     # $Id: rrd-server.pl 1092 2008-01-23 14:23:51Z nicolaw $
5     # rrd-server.pl - Data gathering script for RRD::Simple
6     #
7     # Copyright 2006, 2007, 2008 Nicola Worthington
8     #
9     # Licensed under the Apache License, Version 2.0 (the "License");
10     # you may not use this file except in compliance with the License.
11     # You may obtain a copy of the License at
12     #
13     # http://www.apache.org/licenses/LICENSE-2.0
14     #
15     # Unless required by applicable law or agreed to in writing, software
16     # distributed under the License is distributed on an "AS IS" BASIS,
17     # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18     # See the License for the specific language governing permissions and
19     # limitations under the License.
20     #
21     ############################################################
22     # vim:ts=4:sw=4:tw=78
23    
24     BEGIN {
25     # User defined constants
26     use constant BASEDIR => '/home/nicolaw/webroot/www/rrd.me.uk';
27     use constant THEME => ('BACK#F5F5FF','SHADEA#C8C8FF','SHADEB#9696BE',
28     'ARROW#61B51B','GRID#404852','MGRID#67C6DE');
29     }
30    
31    
32    
33     BEGIN {
34     # Ensure we can find RRDs.so for RRDs.pm
35     eval "use RRDs";
36     if ($@ && !defined $ENV{LD_LIBRARY_PATH}) {
37     $ENV{LD_LIBRARY_PATH} = BASEDIR.'/lib';
38     exec($0,@ARGV);
39     }
40     }
41    
42     use 5.004;
43     use strict;
44     use warnings;
45     use lib qw(../lib);
46     use RRD::Simple 1.41;
47     use RRDs;
48     use Memoize;
49     use Getopt::Std qw();
50     use File::Basename qw(basename);
51     use File::Path qw();
52     use Config::General qw();
53     use File::Spec::Functions qw(catfile catdir);
54     use vars qw($VERSION);
55    
56     $VERSION = '1.43' || sprintf('%d', q$Revision: 1092 $ =~ /(\d+)/g);
57    
58     # Get command line options
59     my %opt = ();
60     $Getopt::Std::STANDARD_HELP_VERSION = 1;
61     $Getopt::Std::STANDARD_HELP_VERSION = 1;
62     Getopt::Std::getopts('u:G:T:gthvVf?', \%opt);
63    
64     $opt{g} ||= $opt{G};
65     $opt{t} ||= $opt{T};
66    
67     # Display help or version
68     (VERSION_MESSAGE() && exit) if defined $opt{v};
69     (HELP_MESSAGE() && exit) if defined $opt{h} || defined $opt{'?'} ||
70     !(defined $opt{u} || defined $opt{g} || defined $opt{t});
71    
72     # cd to the righr location and define directories
73     chdir BASEDIR || die sprintf("Unable to chdir to '%s': %s", BASEDIR, $!);
74     my %dir = map { ( $_ => BASEDIR."/$_" ) } qw(bin data etc graphs cgi-bin thumbnails);
75    
76     # Create an RRD::Simple object
77     my $rrd = RRD::Simple->new(rrdtool => "$dir{bin}/rrdtool");
78    
79     # Cache results from read_create_data()
80     memoize('read_create_data');
81     memoize('read_graph_data');
82     memoize('basename');
83     memoize('graph_def');
84    
85     # Update the RRD if we've been asked to
86     my $hostname = defined $opt{u} ? update_rrd($rrd,\%dir,$opt{u}) : undef;
87    
88     # Generate some graphs
89     my @hosts;
90     for my $host (($hostname, $opt{G}, $opt{T})) {
91     next unless defined $host;
92     for (split(/\s*[,:]\s*/,$host)) {
93     push(@hosts, $_) if defined($_) && length($_);
94     }
95     }
96     @hosts = list_dir($dir{data}) unless @hosts;
97    
98     for my $hostname (@hosts) {
99     create_thumbnails($rrd,\%dir,$hostname) if defined $opt{t};
100     create_graphs($rrd,\%dir,$hostname) if defined $opt{g};
101     }
102    
103     exit;
104    
105    
106    
107    
108     sub create_graphs {
109     my ($rrd,$dir,$hostname,@options) = @_;
110    
111     my ($caller) = ((caller(1))[3] || '') =~ /.*::(.+)$/;
112     my $thumbnails = defined $caller && $caller eq 'create_thumbnails' ? 1 : 0;
113     my $destdir = $thumbnails ? $dir->{thumbnails} : $dir->{graphs};
114    
115     my @colour_theme = (color => [ THEME ]);
116     my $gdefs = read_graph_data("$dir->{etc}/graph.defs");
117     my @hosts = defined $hostname ? ($hostname)
118     : grep { -d catdir($dir->{data}, $_) } list_dir("$dir->{data}");
119    
120     # For each hostname
121     for my $hostname (sort @hosts) {
122     # Create the graph directory for this hostname
123     my $destination = "$destdir/$hostname";
124     File::Path::mkpath($destination) unless -d $destination;
125    
126     # For each RRD
127     for my $file (grep { $_ =~ /\.rrd$/i && !-d catfile($dir->{data},$hostname,$_) }
128     list_dir(catdir($dir->{data},$hostname))
129     ) {
130    
131     # next unless $file =~ /cpu_utilisation/;
132    
133     my $rrdfile = catfile($dir->{data},$hostname,$file);
134     my $graph = basename($file,'.rrd');
135     my $gdef = graph_def($gdefs,$graph);
136    
137     # Make sure we parse these raw commands with care
138     my @raw_cmd_list = qw(DEF CDEF VDEF TEXTALIGN AREA STACK LINE\d* HRULE\d* VRULE\d* TICK SHIFT GPRINT PRINT COMMENT);
139     my $raw_cmd_regex = '('.join('|',@raw_cmd_list).')';
140     # my $raw_cmd_regex = qr/^(?:[VC]?DEF|G?PRINT|COMMENT|[HV]RULE\d*|LINE\d*|AREA|TICK|SHIFT|STACK|TEXTALIGN)$/i;
141     my @raw_commands;
142     my @def_sources;
143     my @def_sources_draw;
144    
145     # Allow users to put raw commands in the graph.defs file
146     for my $raw_cmd (@raw_cmd_list) {
147     for my $cmd (grep(/^$raw_cmd$/i, keys %{$gdef})) {
148     my $values = $gdef->{$cmd};
149     $values = [($values)] unless ref($values);
150     for my $v (@{$values}) {
151     push @raw_commands, (sprintf('%s:%s', uc($cmd), $v) => '');
152     if ($cmd =~ /^[CV]?DEF$/i && $v =~ /^([a-z0-9\_\-]{1,30})=/) {
153     push @def_sources, $1;
154     } elsif ($cmd =~ /^(?:LINE\d*|AREA|G?PRINT|TICK|STACK)$/i && $v =~ /^([a-z0-9\_\-]{1,30})[#:]/) {
155     push @def_sources_draw, $1;
156     }
157     }
158     }
159     }
160    
161     # Wrap the RRD::Simple calls in an eval() block just in case
162     # the explode in a big nasty smelly heap!
163     eval {
164    
165     # Anything that doesn't start with ^source(?:s|_) should just
166     # be pushed on to the RRD::Simple->graph option stack (So this
167     # would NOT include the "sources" option).
168     my @graph_opts = map { ($_ => $gdef->{$_}) }
169     grep(!/^source(s|_)/ && !/^$raw_cmd_regex$/i, keys %{$gdef});
170    
171     # Anything that starts with ^source_ should be split up and passed
172     # as a hash reference in to the RRD::Simple->graph option stack
173     # (This would NOT include the "sources" option).
174     push @graph_opts, map {
175     # If we see a value from a key/value pair that looks
176     # like it might be quoted and comma seperated,
177     # "like this", 'then we should','split especially'
178     if ($gdef->{$_} =~ /["']\s*,\s*["']/) {
179     ($_ => [ split(/\s*["']\s*,\s*["']\s*/,$gdef->{$_}) ])
180    
181     # Otherwise just split on whitespace like the old
182     # version of rrd-server.pl used to do.
183     } else {
184     ($_ => [ split(/\s+/,$gdef->{$_}) ])
185     }
186     } grep(/^source_/,keys %{$gdef});
187    
188     # By default we want to tell RRDtool to be lazy and only generate
189     # graphs when it's actually necessary. If we have the -f for force
190     # flag then we won't let RRDtool be economical.
191     push @graph_opts, ('lazy','') unless exists $opt{f};
192    
193     # Only draw the sources we've been told to, and only
194     # those that actually exist in the RRD file
195     my @rrd_sources = $rrd->sources($rrdfile);
196     if (defined $gdef->{sources}) {
197     my @sources;
198     for my $ds (split(/(?:\s+|\s*,\s*)/,$gdef->{sources})) {
199     push @sources, $ds if grep(/^$ds$/,@rrd_sources);
200     }
201     push @graph_opts, ('sources',\@sources);
202     } elsif (!@def_sources && !@def_sources_draw) {
203     push @graph_opts, ('sources', [ sort @rrd_sources ]);
204     } else {
205     push @graph_opts, ('sources', undef);
206     }
207    
208     printf "Generating %s/%s/%s ...\n",
209     $hostname,
210     ($thumbnails ? 'thumbnails' : 'graphs'),
211     $graph if $opt{V};
212    
213     # Generate the graph and capture the results to
214     # write the text file output in the same directory
215     my @stack = ($rrdfile);
216     push @stack, @raw_commands if @raw_commands;
217     push @stack, ( destination => $destination );
218     push @stack, ( timestamp => 'both' );
219     push @stack, @colour_theme if @colour_theme;
220     push @stack, @options if @options;
221     push @stack, @graph_opts if @graph_opts;
222     write_txt($rrd->graph(@stack));
223    
224     my $glob = catfile($destination,"$graph*.png");
225     my @images = glob($glob);
226     warn "[Warning] $rrdfile: Looks like \$rrd->graph() failed to generate any images in '$glob'\n."
227     unless @images;
228     };
229     warn "[Warning] $rrdfile: => $@" if $@;
230     }
231     }
232     }
233    
234     sub graph_def {
235     my ($gdefs,$graph) = @_;
236    
237     my $rtn = {};
238     for (keys %{$gdefs->{graph}}) {
239     my $graph_key = qr(^$_$);
240     if (my ($var) = $graph =~ /$graph_key/) {
241     $rtn = { %{$gdefs->{graph}->{$_}} };
242     unless (defined $var && "$var" ne "1") {
243     ($var) = $graph =~ /_([^_]+)$/;
244     }
245     for my $key (keys %{$rtn}) {
246     $rtn->{$key} =~ s/\$1/$var/g;
247     }
248     last;
249     }
250     }
251    
252     return $rtn;
253     }
254    
255     sub list_dir {
256     my $dir = shift;
257     my @items = ();
258     opendir(DH,$dir) || die "Unable to open file handle for directory '$dir': $!";
259     @items = grep(!/^\./,readdir(DH));
260     closedir(DH) || die "Unable to close file handle for directory '$dir': $!";
261     return @items;
262     }
263    
264     sub create_thumbnails {
265     my ($rrd,$dir,$hostname) = @_;
266     my @thumbnail_options = (only_graph => '', width => 125, height => 32);
267     create_graphs($rrd,$dir,$hostname,@thumbnail_options);
268     }
269    
270     sub update_rrd {
271     my ($rrd,$dir,$hostname) = @_;
272     my $filename = shift @ARGV || undef;
273    
274     # Check out the input data
275     die "Input data file '$filename' does not exist.\n"
276     if defined $filename && !-f $filename;
277     die "No data recieved while expecting STDIN data from rrd-client.pl.\n"
278     if !$filename && !key_ready();
279    
280     # Check the hostname is sane
281     die "Hostname '$hostname' contains disallowed characters.\n"
282     if $hostname =~ /[^\w\-\.\d]/ || $hostname =~ /^\.|\.$/;
283    
284     # Create the data directory for the RRD file if it doesn't exist
285     File::Path::mkpath(catdir($dir->{data},$hostname)) unless -d catdir($dir->{data},$hostname);
286    
287     # Open the input file if specified
288     if (defined $filename) {
289     open(FH,'<',$filename) || die "[Error] $rrd: Unable to open file handle for file '$filename': $!";
290     select FH;
291     };
292    
293     # Parse the data
294     my %data = ();
295     while (local $_ = <>) {
296     my ($path,$value) = split(/\s+/,$_);
297     my ($time,@path) = split(/\./,$path);
298     my $key = pop @path;
299    
300     # Check that none of the data is bogus or bollocks
301     my $bogus = 0;
302     $bogus++ unless $time =~ /^\d+$/;
303     $bogus++ unless $value =~ /^[\d\.]+$/;
304     for (@path) {
305     $bogus++ unless /^[\w\-\_\.\d]+$/;
306     }
307     next if $bogus;
308    
309     my $rrdfile = catfile($dir->{data},$hostname,join('_',@path).'.rrd');
310     $data{$rrdfile}->{$time}->{$key} = $value;
311     }
312    
313     # Process the data
314     for my $rrdfile (sort keys %data) {
315     for my $time (sort keys %{$data{$rrdfile}}) {
316     eval {
317     create_rrd($rrd,$dir,$rrdfile,$data{$rrdfile}->{$time})
318     unless -f $rrdfile;
319     $rrd->update($rrdfile, $time, %{$data{$rrdfile}->{$time}});
320     };
321     warn "[Warning] $rrdfile: $@" if $@;
322     }
323     }
324    
325     # Close the input file if specified
326     if (defined $filename) {
327     select STDOUT;
328     close(FH) || warn "[Warning] $rrd: Unable to close file handle for file '$filename': $!";
329     }
330    
331     return $hostname;
332     }
333    
334     sub create_rrd {
335     my ($rrd,$dir,$rrdfile,$data) = @_;
336     my $defs = read_create_data(catfile($dir->{etc},'create.defs'));
337    
338     # Figure out what DS types to use
339     my %create = map { ($_ => 'GAUGE') } sort keys %{$data};
340     while (my ($match,$def) = each %{$defs}) {
341     next unless basename($rrdfile,qw(.rrd)) =~ /$match/;
342     for my $ds (keys %create) {
343     $create{$ds} = $def->{'*'}->{type} if defined $def->{'*'}->{type};
344     $create{$ds} = $def->{lc($ds)}->{type} if defined $def->{lc($ds)}->{type};
345     }
346     }
347    
348     # Create the RRD file
349     $rrd->create($rrdfile, %create);
350    
351     # Tune to use min and max values if specified
352     while (my ($match,$def) = each %{$defs}) {
353     next unless basename($rrdfile,qw(.rrd)) =~ /$match/;
354     for my $ds ($rrd->sources($rrdfile)) {
355     my $min = defined $def->{lc($ds)}->{min} ? $def->{lc($ds)}->{min} :
356     defined $def->{'*'}->{min} ? $def->{'*'}->{min} : undef;
357     RRDs::tune($rrdfile,'-i',"$ds:$min") if defined $min;
358    
359     my $max = defined $def->{lc($ds)}->{max} ? $def->{lc($ds)}->{max} :
360     defined $def->{'*'}->{max} ? $def->{'*'}->{max} : undef;
361     RRDs::tune($rrdfile,'-a',"$ds:$max") if defined $max;
362     }
363     }
364     }
365    
366     sub HELP_MESSAGE {
367     print qq{Syntax: rrd-server.pl <-u hostname,-g,-t,-V|-h|-v> [inputfile]
368     -u <hostname> Update RRD data for <hostname>
369     -g Create graphs from RRD data
370     -t Create thumbnails from RRD data
371     -V Display verbose progress information
372     -v Display version information
373     -h Display this help\n};
374     }
375    
376     # Display version
377     sub VERSION { &VERSION_MESSAGE; }
378     sub VERSION_MESSAGE {
379     print "$0 version $VERSION ".'($Id: rrd-server.pl 1092 2008-01-23 14:23:51Z nicolaw $)'."\n";
380     }
381    
382     sub key_ready {
383     my ($rin, $nfd) = ('','');
384     vec($rin, fileno(STDIN), 1) = 1;
385     return $nfd = select($rin,undef,undef,3);
386     }
387    
388     sub read_graph_data {
389     my $filename = shift || undef;
390    
391     my %config = ();
392     eval {
393     my $conf = new Config::General(
394     -ConfigFile => $filename,
395     -LowerCaseNames => 1,
396     -UseApacheInclude => 1,
397     -IncludeRelative => 1,
398     -MergeDuplicateBlocks => 1,
399     -AllowMultiOptions => 1,
400     -AutoTrue => 1,
401     );
402     %config = $conf->getall;
403     };
404     warn "[Warning] $@" if $@;
405    
406     return \%config;
407     }
408    
409     sub read_create_data {
410     my $filename = shift || undef;
411     my %defs = ();
412    
413     # Open the input file if specified
414     my @data;
415     if (defined $filename && -f $filename) {
416     open(FH,'<',$filename) || die "Unable to open file handle for file '$filename': $!";
417     @data = <FH>;
418     close(FH) || warn "Unable to close file handle for file '$filename': $!";
419     } else {
420     @data = <DATA>;
421     }
422    
423     # Parse the file that you've just selected
424     for (@data) {
425     last if /^__END__\s*$/;
426     next if /^\s*$/ || /^\s*#/;
427    
428     my %def = ();
429     @def{qw(rrdfile ds type min max)} = split(/\s+/,$_);
430     next unless defined $def{ds};
431     $def{ds} = lc($def{ds});
432     $def{rrdfile} = qr($def{rrdfile});
433     for (keys %def) {
434     if (!defined $def{$_} || $def{$_} eq '-') {
435     delete $def{$_};
436     } elsif ($_ =~ /^(min|max)$/ && $def{$_} !~ /^[\d\.]+$/) {
437     delete $def{$_};
438     } elsif ($_ eq 'type' && $def{$_} !~ /^(GAUGE|COUNTER|DERIVE|ABSOLUTE|COMPUTE)$/i) {
439     delete $def{$_};
440     }
441     }
442    
443     $defs{$def{rrdfile}}->{$def{ds}} = {
444     map { ($_ => $def{$_}) } grep(!/^(rrdfile|ds)$/,keys %def)
445     };
446     }
447    
448     return \%defs;
449     }
450    
451    
452    
453    
454     ##
455     ## This processing and robustness of this routine is pretty
456     ## bloody dire and awful. It needs to be rewritten with crap
457     ## input data in mind rather than patching it every time I
458     ## find a new scenario for the data to not be as expected!! ;-)
459     ##
460    
461     sub write_txt {
462     my %rtn = @_;
463     while (my ($period,$data) = each %rtn) {
464     my $filename = shift @{$data};
465     last if $filename =~ m,/thumbnails/,;
466    
467     my %values = ();
468     my $max_len = 0;
469     for (@{$data->[0]}) {
470     my ($ds,$k,$v) = split(/\s+/,$_);
471     next unless defined($ds) && length($ds) && defined($k);
472     $values{$ds}->{$k} = $v;
473     $max_len = length($ds) if length($ds) > $max_len;
474     }
475    
476     if (open(FH,'>',"$filename.txt")) {
477     printf FH "%s (%dx%d) %dK\n\n",
478     basename($filename),
479     (defined($data->[1]) ? $data->[1] : -1),
480     (defined($data->[2]) ? $data->[2] : -1),
481     (-e $filename ? (stat($filename))[7]/1024 : 0);
482    
483     for my $ds (sort keys %values) {
484     for (qw(min max last)) {
485     $values{$ds}->{$_} = ''
486     unless defined $values{$ds}->{$_};
487     }
488     printf FH "%-${max_len}s min: %s, max: %s, last: %s\n", $ds,
489     $values{$ds}->{min}, $values{$ds}->{max}, $values{$ds}->{last};
490     }
491     close(FH);
492     }
493     }
494     }
495    
496    
497    
498    
499     1;
500    
501    
502     __DATA__
503    
504     # * means all
505     # - means undef/na
506    
507     # rrdfile ds type min max
508     ^net_traffic_.+ Transmit DERIVE 0 -
509     ^net_traffic_.+ Receive DERIVE 0 -
510    
511     # 10000000000 = 10 gigabit
512     # 1000000000 = 1 gigabit
513     # 100000000 = 100 megabit
514     # rrdfile ds type min max
515     ^switch_traffic$ ifInOctets COUNTER 0 10000000000
516     ^switch_traffic$ ifOutOctets COUNTER 0 10000000000
517     ^switch_traffic_port\d+$ ifInOctets COUNTER 0 1000000000
518     ^switch_traffic_port\d+$ ifOutOctets COUNTER 0 1000000000
519    
520     # rrdfile ds type min max
521     ^hw_irq_interrupts_cpu\d+$ * DERIVE 0 -
522    
523     # rrdfile ds type min max
524     ^hdd_io_.+ * DERIVE 0 -
525    
526     # rrdfile ds type min max
527     ^net_nfs_operations$ * DERIVE 0 -
528    
529     # rrdfile ds type min max
530     ^apache_status$ ReqPerSec DERIVE 0 -
531     ^apache_status$ BytesPerSec DERIVE 0 -
532     ^apache_logs$ * DERIVE 0 -
533    
534     # rrdfile ds type min max
535     ^db_mysql_activity$ * DERIVE 0 -
536     ^db_mysql_activity_com$ * DERIVE 0 -
537     ^db_mysql_activity_scan$ * DERIVE 0 -
538     ^db_mysql_threads$ Threads_created DERIVE 0 -
539     ^db_mysql_traffic$ * DERIVE 0 -
540     ^db_mysql_connections$ * DERIVE 0 -
541     ^db_mysql_created_tmp$ * DERIVE 0 -
542    
543     # rrdfile ds type min max
544     ^mail_postfix_traffic$ * ABSOLUTE 0 -
545    
546     __END__
547    
548    

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.26