/[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

Contents of /bin/rrd-server.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1 - (show annotations)
Thu Jul 16 18:48:19 2009 UTC (14 years, 8 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 #!/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