#!/usr/bin/perl -wT use strict; use CGI; use DBI; use Time::Local; use Tie::IxHash; use lib '/usr/lib/perl5'; use RRDs; my $addr= $ENV{'HTTP_X_FORWARDED_FOR'} || $ENV{'REMOTE_ADDR'}; my %dscr=( adamant_office => 'Adamant office network', adamant_servers => 'Adamant server network', class_default => q|Low-speed LL's|, ); my @dflt=qw/ adamant_office adamant_servers class_default dialup /; my %rrdc=qw/ GRID b0b0b0 MGRID 000000 FRAME 000000 CANVAS f8f8f8 SHADEA ffffff SHADEB ffffff BACK ffffff /; #my %color=qw/lo 305070 hi bfdfff/; my %color=qw/lo 307050 hi bfffdf/; my $path='./images'; my $rrds='/var/spool/trafget/rrd/transmit'; my $mode='RRD'; my $css=<<'__CSS__'; p {font-family: Verdana, Arial, Helvetica; font-size: 10pt} ul {font-family: Verdana, Arial, Helvetica; font-size: 10pt} td {font-family: Verdana, Arial, Helvetica; font-size: 10pt} __CSS__ my $cgi=new CGI; my $dbh = undef; #my $dbh=DBI->connect(qw/ # dbi:mysql:database=test;host=127.0.0.1;port=3306 # test # test #/) || die ($DBI::errstr."\n"); my @cmap=&get_classmaps($dbh,$mode,$rrds); print $cgi->header( -expires => 'now', -encoding => 'koi8-r', -refresh => 300 ); print $cgi->start_html( -title => 'External channel stats - transmit', -bgcolor => '#ffffff', -text => '#000000', -style => { -code => $css } ); print $cgi->start_form( -method => 'GET' ); print <<'__HEAD__';
Client selection
__HEAD__ &put_cmap_menu($cgi,\@cmap,\%dscr,\@dflt,4); print $cgi->submit(-value => ' B U I L D G R A P H '); print <<'__BODY__';
__BODY__ &put_mode_menu($cgi); print '
'; &create_image($cgi,\@dflt,$path,\%color,\%rrdc,\%dscr,$addr); print <<'__BODY__';
__BODY__ print $cgi->end_form; print $cgi->end_html; $dbh->disconnect() if $dbh; sub create_time_menu() { } sub create_image() { my $cgi=shift; return() unless $cgi; my($dflt,$path,$color,$rrdc,$dscr,$addr)=@_; my $mode=$cgi->param('mode'); $mode||='daily'; my %rrd=( daily => [qw(-s -33h -e -10m --x-grid HOUR:1:HOUR:3:HOUR:6:0:%H:%M)], weekly => [qw(-s -8d -e -30m --x-grid HOUR:6:HOUR:24:DAY:1:86400:%a)], monthly => [qw(-s -33d -e -10m --x-grid), 'DAY:1:DAY:7:DAY:7:604800:Week %W'], yearly => [qw(-s -370d -e -10m --x-grid MONTH:1:MONTH:1:MONTH:1:2592000:%b)] ); my @rrd=( '--title',"\u\L$mode\E external channel utilization", '--vertical-label','Channel utilization, Kbps', qw/ --imgformat PNG --lower-limit 0 --width 720 --height 300 --alt-y-grid / # --y-grid 125000:2 ); push(@rrd,@{$rrd{$mode}}); my @cmap=$cgi->param('cmap'); @cmap=$cgi->param('cmap',@$dflt) if $#cmap<0; &get_cmap_rgb($cgi,$color,$#cmap); push(@rrd,'--color',"$_#$$rrdc{$_}") foreach keys %$rrdc; push(@rrd,&get_vrules($cgi)); push(@rrd,sprintf("DEF:%s=%s/%s.rrd:tx:AVERAGE",$_,$rrds,$_)) foreach @cmap; foreach (0..$#cmap) { my $cmap=$#cmap-$_; my $cdef=sprintf("CDEF:tx%i=%s",$cmap,$cmap[0]); $cdef.=sprintf(",%s,+",$cmap[$_]) foreach (1..$cmap); my $area=sprintf("AREA:tx%i\#%s:%s%s",$cmap,$$color{'area'}[$cmap], ($$dscr{$cmap[$cmap]}?"$$dscr{$cmap[$cmap]}":"\u\L$cmap[$cmap]\E")); =pod my $area=sprintf("AREA:tx%i\#%s:%s%s",$cmap,$$color{'area'}[$cmap], ($$dscr{$cmap[$cmap]}?"$$dscr{$cmap[$cmap]}":"\u\L$cmap[$cmap]\E"), ($_==$#cmap?'':'\\n')); =cut my $line=sprintf("LINE1:tx%i\#%s:",$cmap,$$color{'line'}[$cmap]); push(@rrd,$cdef,$area,$line,"COMMENT:(\\g", sprintf(qq|GPRINT:%s:MIN:Min %%.2lf%%s/\\g|,$cmap[$cmap]), sprintf(qq|GPRINT:%s:AVERAGE:Avg %%.2lf%%s/\\g|,$cmap[$cmap]), sprintf(qq|GPRINT:%s:MAX:Max %%.2lf%%s/\\g|,$cmap[$cmap]), sprintf(qq|GPRINT:%s:LAST:Current %%.2lf%%s)\\n|,$cmap[$cmap])); } RRDs::graph ("$path/$mode-$addr-$$.png",@rrd); print qq||; print RRDs::error if (RRDs::error); } sub get_classmaps() { =pod get_classmaps() retrieves class-map names from database. If $mode is "RRD", the class-map list is taken from directory with RRD databases, else from SQL table. =cut my($dbh,$mode,$rrds)=@_; my @cmap; # if ($mode=~/^rrd$/i) { opendir(DH,$rrds) or die "opendir: $!"; @cmap=grep(/\.rrd$/,readdir(DH)); closedir(DH); map { s/^(.*)\.rrd$/$1/ } @cmap; # } else { # return() unless $dbh; # my $sth=$dbh->prepare('show fields from trafget'); # $sth->execute(); # while (my $map=($sth->fetchrow_array)[0]) { # $map eq 'time' ? next : push(@cmap,$map); # } # } # return(sort(@cmap)); return(@cmap); } sub get_cmap_rgb() { =pod get_cmap_rgb() defines AREA and LINE# colours, reverts 'em into RRGGBB hexadecimal format and forms an array of 'em. =cut my $cgi=shift; return() unless $cgi; my($color,$cmap)=@_; my @rgb=qw/r g b/; push(@{$$color{'area'}},$$color{'lo'}); push(@{$$color{'line'}},$$color{'hi'}); if ($cmap>0) { my %lo=&hex_to_rgb($$color{'lo'}); my %hi=&hex_to_rgb($$color{'hi'}); my %step=map { $_,($hi{$_}-$lo{$_})/$cmap } @rgb; for my $map (1..$cmap) { %lo=map { $_,$lo{$_}+=$step{$_} } @rgb; push(@{$$color{'area'}},sprintf("%.2x%.2x%.2x",@lo{@rgb})); %hi=map { $_,$hi{$_}-=$step{$_} } @rgb; push(@{$$color{'line'}},sprintf("%.2x%.2x%.2x",@hi{@rgb})); } } } sub hex_to_rgb() { =pod hex_to_rgb() takes colour in RRGGBB hexadecimal format (as in HTML) and converts it to a hash, containing corresponding decimal values. =cut my $hex=shift; my @key=qw/r g b/; my(@rgb)=$hex=~/^([\da-f]{2})([\da-f]{2})([\da-f]{2})$/i or return(); my %rgb=map { $key[$_],hex($rgb[$_]) } (0..2); return(%rgb); } sub get_vrules() { =pod get_vrules() returns an array, containing RRDs VRULE set. Takes CGI handler as a primary argument and optionally rule colour in RRGGBB hexadecimal format (as in HTML). =cut my($cgi,$rgb)=@_; return() unless $cgi; $rgb||='FF0000'; my $now=time(); my @rule; my $mode=$cgi->param('mode'); $mode||='daily'; if ($mode eq 'daily') { push(@rule,my $rule=timelocal(0,0,0,(localtime($now))[3..5])); push(@rule,$rule-=86400); } elsif ($mode eq 'monthly' or $mode eq 'weekly') { my $wday=(localtime($now))[6]; push(@rule,my $rule=timelocal(0,0,0,(localtime($now-86400*$wday))[3..5])); push(@rule,$rule-=604800) for (0..($mode eq 'weekly'?0:3)); } map { s/($_)/sprintf("VRULE:%s#%s",$1,$rgb)/e } @rule; return(@rule); } sub put_mode_menu() { =pod put_mode_menu() builds graph mode selection menu (4 radio buttons - daily, weekly, monthly and yearly). The function requires only one argument - a CGI handler. =cut my $cgi=shift or return(); my @mode=qw/ daily weekly monthly yearly /; my %mode=map{$mode[$_],qq| \u\L$mode[$_]\E |}(0..$#mode); print $cgi->radio_group( -name => 'mode', -default => 'daily', -values => \@mode, # -linebreak => 1, -linebreak => 0, -labels => \%mode ); } sub put_cmap_menu() { =pod put_cmap_menu() builds a nice formatted table, containing group of checkboxes. For now variable format is not similar to CGI::checkbox_group, but in the future may be i'll rewrite the function. $cgi - CGI handler; $cmap - reference to an array, containing class-map names; $dscr - reference to a hash, containing client names, correspondingly to a class-map names; $dflt - reference to an array, containing default class-map names; $n - number of table columns, default is 3; $b - border width, default is 0. =cut no strict 'subs'; tie(my %addon,Tie::IxHash, cmap_all => 'Select all data sources', clear_all => 'Clear all data sources', defaults => 'Revert to defaults' ); my $cgi=shift or return(); my($cmap,$dscr,$dflt,$n,$b)=@_; return() unless $cgi; my @form=$cgi->param('cmap'); if (defined($cgi->param('clear_all'))) { $cgi->delete('cmap'); } elsif (defined($cgi->param('defaults'))) { $cgi->param('cmap',@$dflt); } elsif (defined($cgi->param('cmap_all'))) { $cgi->param('cmap',@$cmap); } $cgi->delete($_) foreach keys %addon; my $w=sprintf(qq| width="%.2f%%"|,100/abs($n||=3)); printf(qq|\n|,$b||=0); foreach (keys %addon) { printf(qq|\n|; } for my $i (0..$#$cmap) { my $k=($i+1)%$n; print "\n" if $k==1; printf("\n"; print "\n" if ($i==$#$cmap or $k==0); } print "
|,$n); print $cgi->checkbox( -name => $_, -label => " $addon{$_}" ); print qq|
", ($i==$#$cmap ? ($n+1-$k) : 1), (int($i/$n))==0 ? $w : ''); print $cgi->checkbox( -name => 'cmap', -value => $$cmap[$i], -label => ($$dscr{$cmap[$i]}?" $$dscr{$cmap[$i]}":" \u\L$cmap[$i]\E") ); print "
\n"; }