#!/usr/bin/perl -w -d:DProf my $owner=< 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 Library General Public License instead of this License. LICENSE ; use strict; my($licensekey); $licensekey='gulfie'; my $version; $version = ' Thu Sep 14 22:56:52 PDT 2006 '; $version = ' Fri Sep 7 22:36:31 PDT 2007 '; my $persona_versions = ''; # extra files and such # # # gnuplot a snort.stat file a bunch of different ways. # # now it supports other file personas and # # # my $profileing = 0; # make sure the program terminates normaly, otherwise there will be no perf data. ... nuts ot that. # event counters in the pdf that exit after a while can be usefull. use Time::HiRes(qw(time sleep)); # can be removed, but don't. $|=0; use IO::Handle; use Data::Dumper; use POSIX; my ($name) = $0; my ($debug) =0; my ($ssfile) = ''; my ($list) = 0; my ($showme) = 0; my (@axses) = (); my ($graph) = ''; # commandlineify these. my $gnuplot = 'gnuplot'; my $gnuplot_args = ''; my $gnuplot_plot_adendum = ''; my ($loop) = 1; my ($nodatanoloop) = 0; my ($looptime) = 2; my ($tailn) = 0; # only take the last N 0 is all my ($range) = ''; my ($terminal) = 'x11'; my ($outputfile) = 'gpss.out'; my ($title) = ''; # overriding title. my ($with) = ''; # default to , if there is time in the axes then we use linespoints, else points. my (%interactive_terminals) = map { ($_ , 1) } qw( x11 X11 linux next macintosh svga ssvgalib aqua dospc svgalib ); # there are probibly more... but ugh. my ($argstxt) = ''; my (%graphname); my ($generate_examples) =0; my (%columname); my (%columtype); my (%columunit); # hacky... fix? my (%columexample); my %columnum2key; # used for transcoding. my $ncolums =2; # for assigning colum#'z. my (%xlabels,%ylabels,%zlabels); my (@axes) = (); my @axes_funcs; my (%persona); my $persona = 'snort_stats'; # yes... it's horrible, get over it. my $persona_args = ''; my $tcfn = "$$.gpss.tmpfile"; my $tcf_append = 0; my $ptcstage = 0; my $poll_src = ''; my $poll_period = 0; # aka, not. my $used_complex_fcode = 0; # a flag to do the following. my $ssfile2tailpoll = 0; # we tried doing some math on a ssfile, so now we have to transcode it. my $batchmode = ''; # don't run gnuplot.... for gathering stats without the gnuplot overhead. my @zif_list = () ; # list of filenames to squish into one gpss file... my $zifmode = 'once'; # my (%valid_zif_modes) = ( 'once' => 1 , 'tail' => 1); my $valid_zif_modes_txt = join " ", sort keys %valid_zif_modes; my $gbf; my %gbshell = (); my %gbcol2key = (); my $gpssfnopen; # leave it undefined. my $opencmdt ; my $openfile ; my $cases = ''; # a foo variable for commetns. # # # get some data. SYNCHRONOS, can stall. # # sub get_buffer{ my ($buffer,$metadata); # global args, reentrant and etc. if ($poll_src){ #my $debug = 1; if ($poll_src =~ '\A(gpss(t)?://)(.+)\Z' ){ # a uri that means we are reading one of our own tcfn outputs. my $gpssfn = $3; my $gpssmode = $2; # we may have it open already, and we may end up reading some metadata we'll need to stuff into datastructures # that would normaly be populated by parse data functions. if (not defined $gpssfnopen){ # tail -f +0 is apparently not protable enough if ($gpssmode){ if (not open GPSSINFILE , "tail -f -n 20000000 $gpssfn |"){ die "Unable to open gpssfile ($gpssfn) ($!) quiiting\n"; } }else{ if (not open GPSSINFILE , "<$gpssfn"){ die "Unable to open gpssfile ($gpssfn) ($!) quiiting\n"; } } $gpssfnopen = 1; } # read up metadata untell we get to a line that has some data. my $done = 0; while (not $done){ my $line; $line = ; if (not defined $line){ if ($gpssmode){ die "problem reading gpssfile ($gpssfn) ($!) quiting\n"; }else{ 1 or $debug and print "gpss:// is correctly done and out of data, good close\n"; print "sleeping to let the gnuplot finish\n"; sleep 2 * $looptime; # let things get graphed. ( hopefullly) print "quiting after a sleep \n"; # idealy the transcoder mode would be smarter, for one time grpahs. # right now it'll graph the graphs multiple times before it finaly gets to # then end, and it's a race condition at that. exit(); # not sure we do this right. } } if ($line =~ /\A\s*#(.*)\Z/){ my $comment = $1; # print "reading comment ($comment)\n"; if ($comment =~ /\s*(\d+)\s*:\s*\(([^\)]+)\)\s+\(([^\)]+)\)\s+\(([^\)]+)\)\s+/){ my ($col,$key,$type,$unit) = ($1,$2,$3,$4); # $debug and print " ($col,$key,$type,$unit) ($col,$key,$type,$unit)\n"; $gbshell{$key} = { 'type' => $type , 'unit' => $unit }; $gbcol2key{$col} = $key; } }elsif($line =~ /\A\s*(\d+(\.\d+)?)\s+/){ # $debug and print "reading in dataline ($line)\n"; my $col = 1; map { my $key = $gbcol2key{$col++}; $gbshell{$key}{'data'} = $_; } split /\s+/ , $line; $buffer = $line; $done = 1; # we got our data.. time to get out. }else{ die "Unparsed gpssfile line ($line)\n"; } } # gbshell now should look just like a datahash. $metadata = \%gbshell; # tailing stuff. like the gpss code # runn a command, but keep it open, }elsif ($poll_src =~ '\A((cmdt://)(.*))\Z' ) { my ($cmd) = $3; # loop ? if (not defined $opencmdt){ open CMDTINFILE , "$cmd |" or die "Unable to open file ($cmd |) ($!)\n"; $opencmdt = 1; } # read untell seperator. my $record; $record = ; if (not defined $record){ die "ERROR, the command ($cmd) exited... nuts, quiting\n"; } $buffer = $record; }elsif ($poll_src =~ '\A((cmd://)(.*))\Z' ){ my ($uri,$cmd) = ($1,$3); #a faluse uri.. $debug and print "uri ($uri)\n"; if (defined $persona{$persona}{'poll_cmd_args'}){ $cmd .= ' ' . $persona{$persona}{'poll_cmd_args'}; } $buffer = `$cmd`; }elsif ($poll_src =~ '\A((https?://)(\S+))\Z' ){ my ($uri) = ($1); $debug and print "uri ($uri)\n"; # we only really support delimiters of '' in this case $buffer = `curl -q '$uri'`; # fix error checking and use a cpan module for http getting. }elsif ($poll_src =~ '\A(file(t)?://)?(\S+)\Z' ){ my ($prefix,$mode,$filename) = ($1,$2,$3); $debug and print "prefix ($prefix) mode ($mode) filename($filename)\n"; if (not defined $openfile){ if ($mode){ # tailing if (open FILEINFILE , "tail -n 2000000 $filename|"){ }else{ die("ERROR : transcoder unable to open file ($filename) ($!) quiting\n"); } }else{ # opeing the file. if (open FILEINFILE , "<$filename"){ }else{ die("ERROR : transcoder unable to open file ($filename) ($!) quiting\n"); } } } if ($persona{$persona}{'delimiter'} eq ''){ #the whole file. local $/; $buffer = ; close INFILE; }elsif($persona{$persona}{'delimiter'}){ local $/ = $persona{$persona}{'delimiter'}; $buffer = ; if (not defined $buffer){ # we are done... figure out how to close gracefully. print "End of data, atempting to close gracefully, sleeping \n"; sleep 2 * $looptime; } $openfile = 1; # we'll not reopen the file if there are mutliple records / file }else{ die("NI : funky delimiter in file:// code "); } }else{ die("ERROR : unrecognised/understood poll_src ($poll_src) quiting\n"); } }elsif(defined $ssfile and $ssfile){ # print "We should be using an ssfile. \n"; # fix, use the delimiter to buffer. if (not defined $gbf){ # give me all of it. my $tailtxt = '-c +0'; # the entire file. if ($tailn){ $tailtxt = "-n -$tailn"; } my $cmd = "tail $tailtxt -f '$ssfile' |"; $debug and print "tail cmd ($cmd)\n"; open $gbf , $cmd or die "Unable to open ($cmd) because ($!)\n"; $/ = $persona{$persona}{'delimiter'} ; # line seperator. select $gbf; $| =1; select STDOUT; } $buffer = <$gbf>; $buffer =~ s/\n\Z//; # eh'? # print "buffer from our tailed ssfile ($buffer) ($ssfile)\n"; }else{ print "We don't seem to have a poll source... can't do much about that\n"; # exit(1); } ($buffer,$metadata); } # # # /proc/diskstats reader (linux 2.6) # # # suffix, type, unit, description # messed up, it's unit/type.. # unit/type/ etc is still nebulously defined.... need to work on that. my (@eleven_element_stud) = ( [ 'reads' , '32int' , 'count' , '' ] # 1 ,[ 'reads.merged' , '32int' , 'count' , '' ] # 2 ,[ 'read.sectors' , '32int' , 'count' , '' ] # 3 ,[ 'reading.time' , '32int' , 'milliseconds' , '' ] # 4 ,[ 'writes' , '32int' , 'count' , '' ] # 5 ,[ 'writes.merged' , '32int' , 'count' , '' ] # 6 ,[ 'writen.sectors' , '32int' , 'count' , '' ] # 7 ,[ 'writing.time' , '32int' , 'milliseconds' , '' ] # 8 ,[ 'ios.inflight' , '32int' , 'count' , '' ] # 9 ,[ 'io.time' , '32int' , 'milliseconds' , '' ] # 10 ,[ 'io.wtime' , '32int' , 'milliseconds' , '' ] # 11 ); my (@four_element_stud) = ( [ 'reads' , '32int' , 'count' , '' ] ,[ 'reads.sectors' , '32int' , 'count' , '' ] ,[ 'writes' , '32int' , 'count' , '' ] ,[ 'writes.sectors' , '32int' , 'count' , '' ] ); my (%diskstat_fst); %diskstat_fst = ( 4 => \@four_element_stud , 11 => \@eleven_element_stud ); sub pdf_diskstats{ my ($buf) = @_; my %data = (); map { my $line = $_; my $type = 'unknown'; # There are two forms, # partition : major minor name 4 fields # device : major minor name 11 fields # we get to do system wide summing on our own. # # algorithm is two staged, # get a line and figure out which kind it is, # then if it's all zeros... ignore it. (otherwise dataexplosion # then add it in. if ($line =~ /\A\s*(\d+)\s+(\d+)\s+(\S+)\s+(.*)\Z/){ my ($major,$minor,$name, $rest) = ($1,$2,$3,$4); my @fields; my $fcnt; $name =~ s/-/_/g; # we _really_ don't like '-'z getting into identifiers, it's treated as an arithmetic '-'. if (not $rest =~ /\A(\s*0)+\s*\Z/){ @fields = grep { /\S/ } split /\s+/ , $rest; $fcnt = @fields; # print "fcnt ($fcnt) fields ( " .( join " " , @fields ) . "\n\n"; if (defined $diskstat_fst{$fcnt}[0][0]){ map { my $i = $_; $data{"$name.$diskstat_fst{$fcnt}[$i][0]"}={ 'data' => $fields[$i] , 'type' => $diskstat_fst{$fcnt}[$i][2] , 'unit' => $diskstat_fst{$fcnt}[$i][1] }; } 0 .. ($fcnt -1); }else{ print "something is very wrong with ($rest), \n"; } }else{ if ($debug){ print "line of all zeros... ignored ($rest)\n"; } } }else{ if ($debug){ print "pdf_keyvalue : unmached line ($line)\n"; } } } split /\n/ , $buf; # do some sums. # But I guess I don't want to yet. \%data; } # # # Take a ping and dredge information out of it. # # #my $case; #$case = " linux: #[root\@steamy root]# ping -c 5 www.grotto-group.com #PING grotto-group.com (216.145.18.18) 56(84) bytes of data. #64 bytes from grotto-group.com (216.145.18.18): icmp_seq=0 ttl=51 time=33.7 ms #64 bytes from grotto-group.com (216.145.18.18): icmp_seq=1 ttl=51 time=32.5 ms #64 bytes from grotto-group.com (216.145.18.18): icmp_seq=2 ttl=51 time=32.5 ms #64 bytes from grotto-group.com (216.145.18.18): icmp_seq=3 ttl=51 time=33.8 ms #64 bytes from grotto-group.com (216.145.18.18): icmp_seq=4 ttl=51 time=32.9 ms ## #--- grotto-group.com ping statistics --- ##5 packets transmitted, 5 received, 0% packet loss, time 4046ms #rtt min/avg/max/mdev = 32.556/33.145/33.882/0.558 ms, pipe 2 #[root\@steamy root]# ### # # Macos: #x-xs-Computer:~/p/gpss xx$ ping -c 3 www.grotto-group.com ##PING grotto-group.com (216.145.18.18): 56 data bytes #64 bytes from 216.145.18.18: icmp_seq=0 ttl=51 time=33.022 ms #64 bytes from 216.145.18.18: icmp_seq=1 ttl=51 time=46.83 ms #64 bytes from 216.145.18.18: icmp_seq=2 ttl=51 time=32.815 ms # #--- grotto-group.com ping statistics --- #3 packets transmitted, 3 packets received, 0% packet loss #round-trip min/avg/max = 32.815/37.555/46.83 ms #x-xs-Computer:~/p/gpss xx$ #"; # # Another macos: #xs-computer:~/f/p/gpss x$ ping -n -i 2 -c 5 www.grotto-group.com #PING www.grotto-group.com (216.145.18.19): 56 data bytes #64 bytes from 216.145.18.19: icmp_seq=0 ttl=53 time=47.733 ms #64 bytes from 216.145.18.19: icmp_seq=1 ttl=53 time=33.277 ms #64 bytes from 216.145.18.19: icmp_seq=2 ttl=53 time=39.635 ms #64 bytes from 216.145.18.19: icmp_seq=3 ttl=53 time=37.762 ms #64 bytes from 216.145.18.19: icmp_seq=4 ttl=53 time=35.173 ms #--- www.grotto-group.com ping statistics --- #5 packets transmitted, 5 packets received, 0% packet loss #round-trip min/avg/max/stddev = 33.277/38.716/47.733/5.004 ms #xs-computer:~/f/p/gpss x$ my $numbre = '(\d+(\.\d+)?)'; sub pdf_icmp_ping{ my ($buf) = @_; my %data = (); if ($buf =~ /^(\d+) packets transmitted, (\d+)( packets)? received, (\d+(\.\d+)?)\% packet loss/msi){ my ($pt,$pr,$plperc) = ($1,$2,$4); $data{'packets_sent'} = { 'data' => $pt , 'type' => 'cnt' , 'unit' => 'cnt'}; $data{'packets_received'} = { 'data' => $pr , 'type' => 'cnt' , 'unit' => 'cnt'}; $data{'loss_perc'} = { 'data' => $plperc , 'type' => 'percent' , 'unit' => '1'}; } my @rtts; my $tmpbuf = $buf; while ($tmpbuf =~ s/ time=(\d+(\.\d+)?) ms/ /){ push @rtts , $1; } if (@rtts){ my $results; $results = getstatshash(@rtts); map { my $stat = $_; my $datalabel = "${stat}_rtt"; # print "storing stats for ($tk) ($stat)\n"; $data{$datalabel} = { 'data' => $$results{$stat} , 'type' => 'ms' , 'unit' => 'ms' }; } qw (median stddiv); } my ($min,$avg,$max,$mdev); if ($buf =~ /^rtt min\/avg\/max\/mdev = $numbre\/$numbre\/$numbre\/$numbre/msi ){ ($min,$avg,$max,$mdev) = ($1,$3,$5,$7); } if ($buf =~ /^round-trip min\/avg\/max = $numbre\/$numbre\/$numbre/msi ){ ($min,$avg,$max) = ($1,$3,$5); } #round-trip min/avg/max/stddev = 33.277/38.716/47.733/5.004 ms if ($buf =~ /^round-trip min\/avg\/max\/stddev = ([^\/]+)\/([^\/]+)\/([^\/]+)\/([^\/]+)/sm){ ($min,$avg,$max) = ($1,$2,$3); } $data{'min_rtt'} = { 'data' => $min , 'type' => 'ms' , 'unit' => 'ms'}; $data{'avg_rtt'} = { 'data' => $avg , 'type' => 'ms' , 'unit' => 'ms'}; $data{'max_rtt'} = { 'data' => $max , 'type' => 'ms' , 'unit' => 'ms'}; if (defined $mdev){ $data{'mdev_rtt'} = { 'data' => $mdev , 'type' => '' , 'unit' => ''}; } \%data; } # # # Seems kinda brokeinn on macosx 10.3 ... '-q 1' causes it to stop after the first hop. (forme) # # hop (name) time ms # Atempt to order them in such a way so that the color key on the graph corisponds to the hopnumber # # Just grab the first RTT we can find. # my %traceroute_order; sub pdf_traceroute{ my ($buf) = @_; my %data = (); #my $debug = 1; $debug and print "buf ($buf)\n"; while ($buf =~ s/^\s*((\d+)\s+((\|.?|\*.?)\s+)*(\S+) \((\S+)\))\s+(\d+(\.\d+)?)\s*ms//msi){ my ($hopline,$hop,$name,$ip,$rtt) = ($1,$2,$5,$6,$7); $traceroute_order{$hop} = $name; # used in the graph maker, to order the rtts by hop if ($name eq $ip){ $hopline = "$hop $name"; # no reason to be redundant } $hopline =~ s/\n//g; $hopline =~ tr/ ()-/____/; # squish it down... it means the names are not resolveable, but they are useable. $hopline =~ s/_\Z//; $hopline =~ s/\A(\d__)/0$1/; # ugly but kinda functional. $debug and print "hopline ($hopline) name ($name) hop ($hop) rtt($rtt)\n"; $data{$hopline} = { 'data' => $rtt , 'type' => 'ms' , 'unit' => 'ms'}; $data{sprintf("hop_%0.2d",$hop)} = { 'data' => $rtt , 'type' => 'ms' , 'unit' => 'ms'}; } $debug and print " after buf ($buf)\n"; $debug and print "Dumper " , Dumper(\%data) , "\n\n"; \%data; } # # # Now with 'extra action'. # # don't just grab any old rtt, grab a set of them and do some basic stats. # # # produces 'hop_\d' , # # sub pdf_traceroute2{ my ($buf) = @_; my %data = (); #my $debug = 1; $debug and print "buf ($buf)\n"; #while ($buf =~ s/^\s*((\d+)\s+((\|.?|\*.?)\s+)*(\S+) \((\S+)\))\s+(\d+(\.\d+)?)\s*ms//msi){ while ($buf =~ s/^((\s*(\d+))\s+(.+?))(\Z|$)//msi){ my ($hoptxt,$hop,$meat) = ($2,$3,$4); my ($line) = $1; my %tmpdata = (); @{$tmpdata{sprintf("hop_%0.2d",$hop)}} = (); # make sure the key gets there, so we can use it later for stats stuff. #$debug and print "line ($line)\n"; my ($pktcnt,$dropcnt,$bangcnt) = (0,0,0); my ($hopline,$name,$ip,$rtt); #my $debug = 1 ; # # gather two sets, 1, the rtts for each hop, as well as the rtts for each responder in each hop. my $done = 0; while (not $done and $meat){ $pktcnt++; if ($meat =~ s/\A\s*(\*)//){ $dropcnt++; }elsif($meat =~ s/\A\s*\n//){ # not sure why this happens. $pktcnt--; }elsif($meat =~ s/\A\s*(!.?)//){ $bangcnt++; }elsif($meat =~ s/\A\s*((\S+)\s+\((\S+)\))\s+(\d+(\.\d+)?) ms//){ ($hopline,$name,$ip,$rtt) = ($1,$2,$3,$4); $hopline = "$hop $hopline"; if ($name eq $ip){ $hopline = "$hop $name"; # no reason to be redundant } $hopline =~ s/\n//g; $hopline =~ tr/ ()-/____/; # squish it down... it means the names are not resolveable, but they are useable. $hopline =~ s/_\Z//; $hopline =~ s/\A(\d__)/0$1/; # ugly but kinda functional. $debug and print "storing splat ($hopline) $rtt\n"; push @{$tmpdata{$hopline}} , $rtt ; push @{$tmpdata{sprintf("hop_%0.2d",$hop)}} , $rtt; }elsif($meat =~ s/\A\s*(\d+(\.\d+)) ms//){ $rtt = $1; # possibly depending on the data too much for control flow. $debug and print "storing ($hopline) $rtt\n"; push @{$tmpdata{$hopline}} , $rtt ; push @{$tmpdata{sprintf("hop_%0.2d",$hop)}} , $rtt; }elsif($meat =~ s/\A\s+\Z//){ if ($pktcnt){ $pktcnt-- }; $done =1; }else{ if ($pktcnt){ $pktcnt-- }; $done =1; # cuz we are stuck. print "no match for line ($line) ($meat)\n"; } } if ($pktcnt){ my $tk = sprintf "hop_%0.2d" , $hop; # we can add drop count here. # print "pktcnt ($pktcnt) dropcnt ($dropcnt) bangcnt ($bangcnt)\n"; my ($dropperc,$bangperc) = 0; $dropperc = 100 * ( $dropcnt / $pktcnt); $bangperc = 100 * ( $bangcnt / $pktcnt); $data{"$tk.dropperc"} = { 'data' => $dropperc , 'type' => 'cnt' , 'unit' => 'cnt' }; $data{"$tk.bangperc"} = { 'data' => $bangperc , 'type' => 'cnt' , 'unit' => 'cnt' }; $data{"$tk.badperc"} = { 'data' => ($dropperc + $bangperc) , 'type' => 'cnt' , 'unit' => 'cnt' }; $data{"$tk.goodperc"} = { 'data' => (100 - ($dropperc + $bangperc)) , 'type' => 'cnt' , 'unit' => 'cnt' }; } # now do some basic stats on the stacks of datapoints we have. map { my $tk = $_; my $results; $results = getstatshash(@{$tmpdata{$tk}}); map { my $stat = $_; my $datalabel = "$tk.$stat"; # print "storing stats for ($tk) ($stat)\n"; $data{$datalabel} = { 'data' => $$results{$stat} , 'type' => 'ms' , 'unit' => 'ms' }; } qw ( avg median ); } keys %tmpdata; # $data{$hopline} = { 'data' => $rtt , 'type' => 'ms' , 'unit' => 'ms'}; # $data{sprintf("hop_%0.2d",$hop)} = { 'data' => $rtt , 'type' => 'ms' , 'unit' => 'ms'}; } $debug and print " after buf ($buf)\n"; $debug and print "Dumper " , Dumper(\%data) , "\n\n"; \%data; } # # nominaly supplies. # cnt, avg, max, min , range, median, stddiv # # # sub getstatshash{ my (@data); @data= @_; my %results; my ($cnt,$max,$min); my $total = 0; map { my $v = $_; $total += $v; $cnt ++; } @data; my @sdata; @sdata = sort {$a <=> $b} @data; ($min,$max) = ($sdata[0],$sdata[$#sdata]); if ($cnt){ %results = ( 'cnt' => $cnt , 'avg' => ($total / $cnt) , 'max' => $max , 'min' => $min , 'range' => ($max - $min) ); my $avg = $results{'avg'}; my $median; if ($cnt % 2){ my $index; $index = int $cnt / 2; # print "cnt ($cnt) index ($index)\n"; $median = $sdata[$index]; }else{ my $index; $index = int $cnt / 2; $median = ($sdata[$index] + $sdata[$index- 1] ) / 2; } $results{'median'} = $median; if ($cnt > 5){ # anything much less than that is just not very valid. my $sum = 0 ; map { my $v = $_; $sum += ( ( ( $v - $avg ) * ($v - $avg) ) / ($cnt -1)); } @data; $results{'stddiv'} = sqrt($sum); } } \%results; } sub max{ my ($c,$d) = @_; my $r = $c; if ( not defined $c ) { $r = $d; }elsif(defined $d and $d > $c){ $r = $d; } $r; } sub min{ my ($c,$d) = @_; my $r = $c; if ( not defined $c ) { $r = $d; }elsif(defined $d and $d < $c){ $r = $d; } $r; } # # -Of # # .iso.org.dod.internet.mgmt.mib-2.interfaces.ifTable.ifEntry.ifInUcastPkts.16 = Counter32: 1569376824 # .iso.org.dod.internet.mgmt.mib-2.interfaces.ifTable.ifEntry.ifOutQLen.12 = Gauge32: 0 # .iso.org.dod.internet.mgmt.mib-2.interfaces.ifTable.ifEntry.ifSpecific.6 = OID: .iso.org.dod.internet.mgmt.mib-2.transmission.dot3 # # # # NOTE: really really really need to get back to some definition of what a unit and what a type is. # # This can probibly be much expanded and nicified... # just not sure how to paramiterize it nicely. # sub pdf_snmpget { my ($buf) = @_; my %data = (); map { my $line = $_; if ($line =~ /\A(\S+) = (\S+): (.*)\s*\Z/){ my ($oid,$type,$value) = ($1,$2,$3); my ($dtype) = 'unknown'; # squisihing the namespace somewhat $oid =~ s/.iso.org.dod.internet.mgmt.mib-2.interfaces.ifTable.ifEntry.//; $oid =~ s/.iso.org.dod.internet.mgmt.mib-2.transmission.dot3.dot3StatsTable.dot3StatsEntry.dot3Stats.//; $oid =~ s/.iso.org.dod.internet.mgmt.mib-2.transmission.dot3.dot3PauseTable.dot3PauseEntry.dot3Pause.//; $oid =~ s/.iso.org.dod.internet.mgmt.mib-2.//; if ($oid =~ /Octet/){ $dtype = 'Bytes'; } if ($oid =~ /Pkts/){ $dtype = 'Pkts'; } $data{$oid} = { 'data' => $value , 'type' => $dtype , 'unit' => $type }; } } split /\n/ , $buf; \%data; } sub pdf_keyvalue{ my ($buf) = @_; my %data = (); map { my $line = $_; my $type = 'unknown'; if ($line =~ /\A\s*(\S+)\s*([=|:])?\s*(.*)\Z/){ # $debug and print " data ($1) ($2)\n"; # try guessing the type. # though maybe we dont' want to do this _every_time, only when the field dosn't have one. #fix. my ($name,$seperator,$value) = ($1 , $2,$3); # poor hacks to get /proc/meminfo to work, # this could probibly be better generalized. $name =~ s/[:]\Z//; # clean off unwanted aptentages. $value =~ s/kB\s*\Z//g; $data{$name}={ 'data' => $value , 'type' => $type , 'unit' => 'unknown'} }else{ if ($debug){ print "pdf_keyvalue : unmached line ($line)\n"; } } } split /\n/ , $buf; \%data; } # given a buffer, returning a # hashref to {key}{'data'|'type'|'units'} # type is nebulous. # sub pdf_proc_meminfo{ my ($buf) = @_; my %data = (); my @columheaders = (); map { my $line = $_; my $type = 'unknown'; if ($line =~ /\A\s+total/i ){ @columheaders = grep {$_} map { s/://g; $_ } split /\s+/ , $line; }elsif($line =~ /\A(\S+):\s+((\d+)(\s+\d+)+)\s*\Z/){ # then it's more of a tabular, combine the colums and rows sorta thing. my ($rowname,$meat) = ($1,$2); my $cnt = 0; map { my $val = $_; my $name = "$rowname.$columheaders[$cnt++]"; $data{$name} = { 'data' => $val , 'type' => 'bytes' , 'unit' => 'bytes'} ; } grep {defined $_ and /\S/ } split /\s+/ , $meat; }elsif ($line =~ /\A\s*(\S+)\s*([=|:])?\s*(.*)\Z/){ # $debug and print " data ($1) ($2)\n"; # try guessing the type. # though maybe we dont' want to do this _every_time, only when the field dosn't have one. #fix. my ($name,$seperator,$value) = ($1 , $2,$3); # poor hacks to get /proc/meminfo to work, # this could probibly be better generalized. $name =~ s/[:]\Z//; # clean off unwanted aptentages. $value =~ s/kB\s*\Z//g; $data{$name}={ 'data' => $value , 'type' => $type , 'unit' => 'unknown'} }else{ if ($debug){ print "pdf_proc_meminfo : unmached line ($line)\n"; } } } split /\n/ , $buf; \%data; } # given a buffer, returning a # hashref to {key}{'data'|'type'|'units'} # type is nebulous. # # Baisc non time related calculations can be done here. # sub pdf_proc_stat{ my ($buf) = @_; my %data = (); #cpu 24002361 51780 2338777 390338678 #cpu0 24002361 51780 2338777 390338678 #page 231390169 263932813 #swap 15517364 9354342 #intr 659434847 416731596 4 0 0 3 0 6 0 1 48945613 148256854 0 13 0 8507054 36993703 #disk_io: (3,0):(8579304,5642213,165836096,2937091,128157268) #ctxt 423102897 #btime 1136877131 #processes 603562 # # for now a simplistic nameing scheme # map { my $line = $_; # 2.4 # check if ($line =~ /\Acpu(\d+)?\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s*\Z/){ my ($cpunum,$user,$system,$wait,$idle) = ($1,$2,$3,$4,$5); if (not defined $cpunum){ $cpunum = ".total"; } $data{"cpu$cpunum.user"} = {'data' => $user , 'type' => 'cputicks' , 'units' => 'cputicks' } ; $data{"cpu$cpunum.system"} = {'data' => $system , 'type' => 'cputicks' , 'units' => 'cputicks' } ; $data{"cpu$cpunum.wait"} = {'data' => $wait , 'type' => 'cputicks' , 'units' => 'cputicks' } ; $data{"cpu$cpunum.idle"} = {'data' => $idle , 'type' => 'cputicks' , 'units' => 'cputicks' } ; # 2.6 # from ? }elsif($line =~ /\Acpu(\d+)?\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s*\Z/){ my ($cpunum,$user,$nice,$system,$idle,$iowait,$irq,$softirq,$steal) = ($1,$2,$3,$4,$5,$6,$7,$8,$9); my ($wait); $wait = $iowait; if (not defined $cpunum){ $cpunum = ".total"; } $data{"cpu$cpunum.user"} = {'data' => $user , 'type' => 'cputicks' , 'units' => 'cputicks' } ; $data{"cpu$cpunum.system"} = {'data' => $system , 'type' => 'cputicks' , 'units' => 'cputicks' } ; $data{"cpu$cpunum.wait"} = {'data' => $wait , 'type' => 'cputicks' , 'units' => 'cputicks' } ; $data{"cpu$cpunum.idle"} = {'data' => $idle , 'type' => 'cputicks' , 'units' => 'cputicks' } ; $data{"cpu$cpunum.nice"} = {'data' => $nice , 'type' => 'cputicks' , 'units' => 'cputicks' } ; $data{"cpu$cpunum.irq"} = {'data' => $irq , 'type' => 'cputicks' , 'units' => 'cputicks' } ; $data{"cpu$cpunum.softirq"} = {'data' => $softirq , 'type' => 'cputicks' , 'units' => 'cputicks' } ; $data{"cpu$cpunum.steal"} = {'data' => $steal , 'type' => 'cputicks' , 'units' => 'cputicks' } ; # 2.6 (up 2.6.9-34.EL) ( centos ) # 2.6.9-12 at least. # .... merfh... has fewer fields.... }elsif($line =~ /\Acpu(\d+)?\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s*\Z/){ my ($cpunum,$user,$nice,$system,$idle,$iowait,$irq,$softirq) = ($1,$2,$3,$4,$5,$6,$7,$8); my ($wait); $wait = $iowait; if (not defined $cpunum){ $cpunum = ".total"; } $data{"cpu$cpunum.user"} = {'data' => $user , 'type' => 'cputicks' , 'units' => 'cputicks' } ; $data{"cpu$cpunum.system"} = {'data' => $system , 'type' => 'cputicks' , 'units' => 'cputicks' } ; $data{"cpu$cpunum.wait"} = {'data' => $wait , 'type' => 'cputicks' , 'units' => 'cputicks' } ; $data{"cpu$cpunum.idle"} = {'data' => $idle , 'type' => 'cputicks' , 'units' => 'cputicks' } ; $data{"cpu$cpunum.nice"} = {'data' => $nice , 'type' => 'cputicks' , 'units' => 'cputicks' } ; $data{"cpu$cpunum.irq"} = {'data' => $irq , 'type' => 'cputicks' , 'units' => 'cputicks' } ; $data{"cpu$cpunum.softirq"} = {'data' => $softirq , 'type' => 'cputicks' , 'units' => 'cputicks' } ; # no steal... will they only add new items to the end of the list? that'd be nice. }elsif($line =~ /(swap|page) (\d+) (\d+)/){ $data{"$1in"} = {'data' => $2 , 'type' => 'ops' , 'units' => 'ops' } ; # blocks? what? $data{"$1out"} = {'data' => $3 , 'type' => 'ops' , 'units' => 'ops' } ; }elsif($line =~ /(ctxt|processes) (\d+)/ ){ $data{$1} = {'data'=>$2 , 'type' => $1 , 'units' => 'cnt'}; }elsif($line =~ /(procs_\S+) (\d+)/){ $data{$1} = {'data'=>$2 , 'type' => $1 , 'units' => 'cnt'}; }elsif($line =~ /(btime) (\d+)/ ){ $data{$1} = {'data'=>$2 , 'type' => 'time_t' , 'units' => 'seconds'}; }elsif($line =~ /(intr) ((\d+)(( \d+)*))\s*\Z/){ my $cntr = 0; $data{'intr.total'} = { 'data'=>$3 , 'type'=>'cnt' , 'units' => 'cnt'}; map { $data{"intr.".($cntr++)} = { 'data'=>$_ , 'type'=>'cnt' , 'units' => 'cnt'}; } grep {defined $_ and length $_} split /\s+/ , $4; }else{ if ($debug){ print "pdf_proc_stat : failed to match line ($line)\n"; } } } split /\n/ , $buf; \%data; } # # needs to be cleaned slightly. # sub use_persona{ my ($p) = @_; $persona = $p; if (not $ssfile){ # we probibly havn't set it at the command line yet. $ssfile = $persona{$p}{'ssfile'}; } if (defined $persona{$p}{'graphname'}){ %graphname = %{$persona{$p}{'graphname'}}; } # polling frequency.? if (defined $persona{$p}{'sample_mode'} and $persona{$p}{'sample_mode'} eq 'poll'){ $poll_src = $persona{$p}{'poll_src'}; if ( defined $persona{$p}{'poll_period'}){ $poll_period = $persona{$p}{'poll_period'}; } $ssfile = $tcfn; #read from where we are stuffing data. my $now = time; #utime $now , $now , $ssfile; # make sure it exists `touch $tcfn`; } @axes_funcs = (); } # # Add a persona to the ... er us. # # nothing special, just an external file that we eval. # so people can keep stuff local and more sane than my one file plan. # sub load_persona_file{ my ($fname) = @_; my $txt; $txt = slurp($fname); eval($txt); # if ($@) { print "eval of persona file ($fname) failed for ($@)... quiting\n"; exit(3); } } # # # string support routines. # # sub spluz{ my ($prefix,@suffixlist) =@_; my ($str); $str = join " , " , map { $prefix.$_ } @suffixlist; # print "fcode str ($str)\n"; $str; } sub dts{ my ($prefix,@suffixlist) = @_; my ($str); $str = join " , " , map { "$prefix$_.dt=deltat($prefix$_)"; } @suffixlist; $str; } # # # Sort of a globish thing. # # We could start doing some cool stuff. # # but for now' well just leave the stub. # # sub snmpget_fcode_gen{ my ($datahash) = @_; my (%graphnames); # $graphnames{$gname} = { 'description' => $desc, 'txt' => $fcode } ; my ($desc,$fcode,$gname,$graphnames); \%graphnames; } #,'procedural_graphs' => \&fgen_proc_stat # traceroute_fcode_gen2 # # the number of interupts is variable, thus we'll need to varabalize the code sub fcode_gen_proc_stat{ my ($datahash) = @_; my (%graphnames); print "fcode_gen_proc_stat\n"; my ($ints) = ''; $ints = join ', ' , map { my $intr = $_; my $intdtname = $intr; $intdtname =~ s/intr/ints/; "$intdtname=deltat($intr)"; } sort { my ($an,$bn) = (-1,-1); if ($a =~ /intr\.(\d+)/){ $an = $1; } if ($b =~ /intr\.(\d+)/){ $bn = $1; } $an <=> $bn; }grep { /\Aintr\.(total|\d+)\Z/ } keys %$datahash; $graphnames{'ints'} = { 'description' => 'interrupts/sec vs time' , 'txt' => [ $ints ] }; \%graphnames; } # # Accept a datahash, and return a hash, like 'graphname' # # # We might want to recall this every itteration.. but not for now. # # sub traceroute_fcode_gen{ my ($datahash) = @_; my (%graphnames); print "traceroute_fcode_gen\n"; # # the first graph. anything that is \d+__ becomes is 'rtts' # my ($desc,$fcode,$gname,$graphnames); # # long names # $desc = "rtts for each hop"; $fcode = [ join " , " , sort { $b cmp $a; } grep { /\A\d+__/; } keys %$datahash ]; $gname = "rtts"; $graphnames{$gname} = { 'description' => $desc, 'txt' => $fcode } ; # # by hop # $desc = "rtts for each hop labeled as hops"; $fcode = [ join " , " , sort { $b cmp $a; } grep { /\Ahop_\d+\Z/; } keys %$datahash ]; $gname = "hops"; # rtts, nonames $graphnames{$gname} = { 'description' => $desc, 'txt' => $fcode } ; # my $debug = 1; # $debug and print "dumper graphnames ", Dumper(\%graphnames) , "\n\n"; \%graphnames; } # # # adding stats. # # sub traceroute_fcode_gen2{ my ($datahash) = @_; my (%graphnames); print "traceroute_fcode_gen\n"; # # the first graph. anything that is \d+__ becomes is 'rtts' # my ($desc,$fcode,$gname,$graphnames); map { my $postfix = $_; # print "postfix ($postfix)\n"; # # long names # $desc = "$postfix rtts for each hop"; $fcode = [ join " , " , sort { $b cmp $a; } grep { # print "datahash has ($_)\n"; /\A\d+__.+\.$postfix/; } keys %$datahash ]; $debug and print "fcode ($$fcode[0])\n"; $gname = "rtts.$postfix"; $graphnames{$gname} = { 'description' => $desc, 'txt' => $fcode } ; # # by hop # $desc = "$postfix rtts for each hop labeled as hops"; $fcode = [ join " , " , sort { $b cmp $a; } grep { # print "datahash 2 has ($_)\n"; /\Ahop_\d+\.$postfix\Z/; } keys %$datahash ]; $debug and print "fcode 2 ($$fcode[0])\n"; $gname = "hops.$postfix"; # rtts, nonames $graphnames{$gname} = { 'description' => $desc, 'txt' => $fcode } ; # my $debug = 1; # $debug and print "dumper graphnames ", Dumper(\%graphnames) , "\n\n"; } qw (avg median stddiv goodperc dropperc bangperc badperc); #postfix. \%graphnames; } # # Accept a datahash, and return a hash, like 'graphname' # # sub diskstats_fcode_gen{ my ($datahash) = @_; my (%graphnames); # # # generate a bunch of different graphs # ___.times my %devices; my %partitions; my %read_root; map { my $dk = $_; # descriminating... if ($dk =~ /\A(\S+)\.reads\Z/){ my $canidate = $1; if (defined $$datahash{"$canidate.io.time"}){ # a device $devices{$canidate} = 1; }else{ # a partition $partitions{$canidate} = 1; } $read_root{$canidate} = 1; } } keys %$datahash; # # # now generate # # there are per device things, like hda.times [hda.io.time hda.io.wtime hda.reading.time hda.writing.time] # or cross device things, like all.rw.dt [ sum( hda.reading.time.dt hdb.reading.time.dt) , somethingesle] map { my $dev = $_; my $fcode = []; my $desc = ""; my $gname; # $dev.times $desc = "Raw cumulative IO times in milliseconds for $dev."; $fcode = [ " $dev.io.time , $dev.io.wtime , $dev.reading.time , $dev.writing.time " ]; $gname = "$dev.times"; $graphnames{$gname} = { 'description' => $desc, 'txt' => $fcode } ; # $dev.times.dt $desc = "dt IO times in milliseconds for $dev."; $fcode = [ join " , " , map { "$dev$_.dt=deltat($dev$_)"; } qw( .io.time .io.wtime .reading.time .writing.time ) ] ; $gname = "$dev.times.dt"; $graphnames{$gname} = { 'description' => $desc, 'txt' => $fcode } ; # $dev.rw $desc = "Raw cumulative # reads and writes for $dev."; $fcode = [ spluz( $dev, qw ( .reads .writes .reads.merged .writes.merged )) ] ; $gname = "$dev.rw"; $graphnames{$gname} = { 'description' => $desc, 'txt' => $fcode } ; # $dev.rw.dt $desc = "# reads and writes / dt for $dev."; $fcode = [ dts( $dev, qw ( .reads .writes .reads.merged .writes.merged )) ] ; $gname = "$dev.rw.dt"; $graphnames{$gname} = { 'description' => $desc, 'txt' => $fcode } ; # $dev.if $gname = "$dev.if"; $desc = "# operations currently outstanding"; $fcode = [ spluz( $dev , ".ios.inflight" ) ] ; $graphnames{$gname} = { 'description' => $desc, 'txt' => $fcode } ; # $dev.if.dt $gname = "$dev.if.dt"; $desc = "#.dt operations currently outstanding"; $fcode = [ dts( $dev , ".ios.inflight" ) ] ; $graphnames{$gname} = { 'description' => $desc, 'txt' => $fcode } ; } keys %devices; \%graphnames; } # # # Dump out persona specific help/faq, tutoral, etc. # # #f($arg =~ /\A(--persona_(help|faq|FAQ|tutorial|tut)(_all)?)\Z/){ sub persona_multihelp{ #($2,$3); my ($subop,$all) = @_; my @personas_to_show; @personas_to_show = ($persona); if ($all){ @personas_to_show = sort keys %persona; } $subop = lc $subop; if ($subop eq 'tut'){ $subop = 'tutorial'; } map { my $p = $_; print "\n\n\nDetail on persona $p\n\n\n"; if (defined $persona{$p}){ if (defined $persona{$p}{$subop}){ print "$persona{$p}{$subop}\n"; }else{ print "persona ($p) has no ($subop)\n"; } }else{ print "persona ($p) is not yet defined, did you rememeber to use --persona_file to load it?\n"; } }@personas_to_show; print "\n\n"; exit(2); } # # # used primarily for sucking in ziped gpss files. # you could reuse one of the other personas... but... that's fraught with danger... i'm sure. # # # $persona{'none'} = { 'help' => "no persona, used mostly for manualy post processing gpss data files. Requires a --poll_src gpss://", 'description' => 'no persona, used mostly for manualy post processing gpss data files. Requires a --poll_src gpss://' ,'ssfile' => '' ,'sample_mode' => 'poll' ,'parse_data_function' => \&pdf_never_called ,'poll_src' => 'cmd://sleep 1' # also shouldn' get called. Should be overriden on the cmdline. ,'poll_period' => 0.001 # fast, because we'll be tailing files, and pending on the files. ,'delimiter' => '' ,'graphname' => { } }; sub pdf_never_called{ die "The none persona requires you to use a --poll_src gpss://\${FILENAME} to get your data. "; die "Oh Snap.. that was a mistake. you shouldn't be here (pdf_never_called) "; } # # # # $persona{'icmp_ping'} = { 'description' => 'icmp ping a host : --poll_cmd_args " -n -i 2 -c 5 www.grotto-group.com " ' ,'ssfile' => '' # we require transcodeing ,'sample_mode' => 'poll' ,'figure_colums' => undef ,'parse_data_function' => \&pdf_icmp_ping ,'poll_src' => 'cmd://ping' ,'poll_cmd_args' => '-n -i 3 -c 20 www.grotto-group.com' ,'poll_period' => 4 ,'delimiter' => '' # whole file. # ,'procedural_graphs' => \&diskstats_fcode_gen ,'graphname' => { ### 1 d 'rtt' => { # shoudl figure out a way to do boxplots. 'description' => "pingtime rtt, max/min/avg/median " ,'txt' => [ ' max_rtt, min_rtt , avg_rtt , median_rtt ' ] #order looks funky, but it colors to red blue green... which is nice. } ,'io' => { 'description' => "pktcounts sent and received" ,'txt' => [ ' packets_sent , packets_received ' ] } ### 2 d ,'rtt_drop' => { 'description' => "rtt vs loss perc" ,'txt' => [ 'loss_perc' , ' max_rtt, min_rtt , avg_rtt , median_rtt ' ] } ### 3 d ,'rtt_dropt' => { 'description' => "time vs loss perc vs rtt" ,'txt' => [ 'time_t' , 'loss_perc' , ' max_rtt, min_rtt , avg_rtt , median_rtt ' ] } } }; $persona{'ping'} = $persona{'icmp_ping'}; # # # # $persona{'traceroute'} = { 'description' => 'traceroute a host : --poll_cmd_args "-q 1 www.google.com" ' ,'ssfile' => '' # we require transcodeing ,'sample_mode' => 'poll' ,'figure_colums' => undef ,'parse_data_function' => \&pdf_traceroute2 ,'poll_src' => 'cmd://traceroute' ,'poll_cmd_args' => '-q 2 www.google.com' #only send one pkt to each. ,'poll_period' => 4 ,'delimiter' => '' # whole file. ,'procedural_graphs' => \&traceroute_fcode_gen2 ,'graphname' => { # not sure what can be done here. } }; # # # # my $cmda = '-Of -c public -v 1 192.168.1.51 ifInOctets.1 ifOutOctets.1 '; # this'll be right for no one. $persona{'snmpget'} = { 'description' => 'netsnmp : snmpget shell : --poll_cmd_args '.$cmda.'' ,'ssfile' => '' # we require transcodeing ,'sample_mode' => 'poll' ,'figure_colums' => undef ,'parse_data_function' => \&pdf_snmpget ,'poll_src' => 'cmd://snmpget' ,'poll_cmd_args' => $cmda ,'poll_period' => 4 ,'delimiter' => '' # whole file. ,'procedural_graphs' => \&snmpget_fcode_gen ,'graphname' => { # not sure what can be done here. } }; # # Note : # /usr/src/linux-2.6.12/Documentation/iostats.txt # $persona{'/proc/diskstats'} = { 'description' => 'a linux 2.6 /proc/diskstats reader aproximatly equivilant to iostat' ,'ssfile' => '' # we require transcodeing ,'sample_mode' => 'poll' ,'figure_colums' => undef ,'parse_data_function' => \&pdf_diskstats ,'poll_src' => 'file:///proc/diskstats' ,'poll_period' => 1.5 ,'delimiter' => '' # whole file. ,'procedural_graphs' => \&diskstats_fcode_gen ,'graphname' => { # most of these are generated now. } }; $persona{'/proc/meminfo'} = { 'description' => 'a linux 2.4 / 2.6 /proc/meminfo reader' ,'ssfile' => '' # we require transcodeing ,'sample_mode' => 'poll' ,'figure_colums' => undef # ,'parse_data_function' => \&pdf_proc_stat ,'parse_data_function' => \&pdf_proc_meminfo ,'poll_src' => 'file:///proc/meminfo' ,'poll_period' => 1.5 ,'delimiter' => '' # whole file. ,'graphname' => { # axes will be generated from the text definition ### 1 d 'general' => { 'description' => "Some Genral stats vtime" ,'txt' => [ ' MemTotal' .',MemFree ' .',Buffers ' .',Cached ' .',SwapCached ' .',Active ' .',Inactive ' ] } ,'generaldt' => { 'description' => "Some Genral (dt) stats vtime" ,'txt' => [ ' MemTotal.dt=deltat(MemTotal )' .',MemFree.dt=deltat( MemFree )' .',Buffers.dt=deltat( Buffers )' .',Cached.dt=deltat( Cached )' .',SwapCached.dt=deltat( SwapCached ) ' .',Active.dt=deltat( Active )' .',Inactive.dt=deltat( Inactive )' ] } ,'tf' => { 'description' => "Totals and Frees v time" ,'txt' => [ ' MemTotal' .',MemFree ' .',HighTotal ' .',HighFree ' .',LowTotal ' .',LowFree ' .',HugePages_Total ' .',HugePages_Free ' ] } ,'tfdt' => { 'description' => "Totals and Frees (dt) v time" ,'txt' => [ ' MemTotal.dt=deltat( MemTotal ) ' .',MemFree.dt=deltat( MemFree ) ' .',HighTotal.dt=deltat( HighTotal ) ' .',HighFree.dt=deltat( HighFree ) ' .',LowTotal.dt=deltat( LowTotal ) ' .',LowFree.dt=deltat( LowFree ) ' .',HugePages_Total.dt=deltat( HugePages_Total ) ' .',HugePages_Free.dt=deltat( HugePages_Free ) ' ] } ,'middle' => { 'description' => "Where did it all go? vs time" ,'txt' => [ ' Dirty' .',Writeback ' .',Mapped ' .',Slab ' .',CommitLimit ' .',Commited_AS ' .',PageTables ' ] } ,'middledt' => { 'description' => "Where did it all go? (dt) vs time" ,'txt' => [ ' Dirty.dt=deltat( Dirty ) ' .',Writeback.dt=deltat( Writeback ) ' .',Mapped.dt=deltat( Mapped ) ' .',Slab.dt=deltat( Slab ) ' .',CommitLimit.dt=deltat( CommitLimit ) ' .',Commited_AS.dt=deltat( Commited_AS ) ' .',PageTables.dt=deltat( PageTables ) ' ] } ,'vm' => { 'description' => "Vmalloc info vs time" ,'txt' => [ ' VmallocTotal' .',VmallocUsed ' .',VmallocChunk ' ] } ,'vmdt' => { 'description' => "Vmalloc info (dt) vs time" ,'txt' => [ ' VmallocTotal.dt=deltat( VmallocTotal ) ' .',VmallocUsed.dt=deltat( VmallocUsed ) ' .',VmallocChunk.dt=deltat( VmallocChunk ) ' ] } } }; $persona{'/proc/stat'} = { 'description' => 'a linux 2.4/2.6 /proc/stat reader' ,'ssfile' => '' # we require transcodeing ,'sample_mode' => 'poll' ,'figure_colums' => undef ,'parse_data_function' => \&pdf_proc_stat ,'procedural_graphs' => \&fcode_gen_proc_stat # traceroute_fcode_gen2 ,'poll_src' => 'file:///proc/stat' ,'poll_period' => 1.5 ,'delimiter' => '' # whole file. ,'graphname' => { # axes will be generated from the text definition ### 1 d 'cpu.total' => { 'description' => "\%cpu graphs, user, sys, wait, idle." ,'txt' => [ 'user=deltat(cpu.total.user) , system=deltat(cpu.total.system) , wait=deltat(cpu.total.wait) , idle=deltat(cpu.total.idle)' ] } ,'cpu.total26' => { 'description' => "\%cpu graphs, user, nice, system, iowait, idle, irq, softirq, steal" ,'txt' => [ 'user=deltat(cpu.total.user) , system=deltat(cpu.total.system) , wait=deltat(cpu.total.wait) , idle=deltat(cpu.total.idle) , irq=deltat(cpu.total.irq) , softirq=deltat(cpu.total.softirq) , nice=deltat(cpu.total.nice) , steal=deltat(cpu.total.steal)' ] } ,'io' => { 'description' => "Pageing and swaping activity" ,'txt' => [ 'pagein.ps=deltat(pagein) , pageout.ps = deltat(pageout) , swapin.ps=deltat(swapin) , swapout.ps=deltat(swapout)' ] } ,'procs' => { 'description' => "processes/sec" ,'txt' => [ 'procs=deltat(processes)' ] } } }; $persona{'/proc/vmstat'} = { 'description' => 'linux 2.x /proc/vmstat reader' ,'ssfile' => '' # we require transcodeing ,'sample_mode' => 'poll' ,'figure_colums' => undef ,'parse_data_function' => \&pdf_keyvalue ,'poll_src' => 'file:///proc/vmstat' ,'delimiter' => '' # whole file. ,'poll_period' => 3 ,'delimiter' => '' ,'graphname' => { ### 1 d 'nrdt' => { 'description' => "nr_* dt over time" # fix, this could be generated ,'txt' => [ 'nr_dirty.dt=deltat(nr_dirty) ' .', nr_writeback.dt=deltat(nr_writeback) ' .', nr_unstable.dt=deltat(nr_unstable) ' .', nr_page_table_pages.dt=deltat(nr_page_table_pages) ' .', nr_mapped.dt=deltat(nr_mapped) ' .', nr_slab.dt=deltat(nr_slab) ' .', nr_bounce.dt=deltat(nr_bounce) ' ] } ,'iodt' => { 'description' => "various agregate io dt over time" # fix, this could be generated ,'txt' => [ 'pgpgin.dt=deltat(pgpgin) ' .', pgpgout.dt=deltat(pgpgout) ' .', pswpin.dt=deltat(pswpin) ' .', pswpout.dt=deltat(pswpout) ' ] } ,'pgadt' => { 'description' => "page alloc activity over (dt) over time" # fix, this could be generated ,'txt' => [ 'pgalloc_high.dt=deltat(pgalloc_high) ' .', pgalloc_normal.dt=deltat(pgalloc_normal) ' .', pgalloc_dma.dt=deltat(pgalloc_dma) ' .', pgfree.dt=deltat(pgfree) ' ] } ,'pgbdt' => { 'description' => "page activity/deactivate and faults over (dt) over time" # fix, this could be generated ,'txt' => [ 'pgactivate.dt=deltat(pgactivate) ' .', pgdeactivate.dt=deltat(pgdeactivate) ' .', pgfault.dt=deltat(pgfault) ' .', pgmajfault.dt=deltat(pgmajfault) ' ] } ,'pgrdt' => { 'description' => "page refill (dt) over time" # fix, this could be generated ,'txt' => [ 'pgrefill_high.dt=deltat(pgrefill_high) ' .', pgrefill_normal.dt=deltat(pgrefill_normal) ' .', pgrefill_dma.dt=deltat(pgrefill_dma) ' ] } ,'pgsdt' => { 'description' => "page steal (dt) over time" # fix, this could be generated ,'txt' => [ 'pgsteal_high.dt=deltat(pgsteal_high) ' .', pgsteal_normal.dt=deltat(pgsteal_normal) ' .', pgsteal_dma.dt=deltat(pgsteal_dma) ' ,', pginodesteal.dt=deltat(pginodesteal) ' .', kswapd_steal.dt=deltat(kswapd_steal) ' .', kswapd_inodesteal.dt=deltat(kswapd_inodesteal) ' ] } ,'pgscandt' => { 'description' => "page scanning (dt) over time" # fix, this could be generated ,'txt' => [ 'pgscan_kswapd_high.dt=deltat(pgscan_kswapd_high) ' .', pgscan_kswapd_normal.dt=deltat(pgscan_kswapd_normal) ' .', pgscan_kswapd_dma.dt=deltat(pgscan_kswapd_dma) ' .', pgscan_direct_high.dt=deltat(pgscan_direct_high) ' .', pgscan_direct_normal.dt=deltat(pgscan_direct_normal) ' .', pgscan_direct_dma.dt=deltat(pgscan_direct_dma) ' .', slabs_scanned.dt=deltat(slabs_scanned) ' ] } ,'miscdt' => { 'description' => "allocstall pgrotated (dt) over time" # fix, this could be generated ,'txt' => [ 'allocstall.dt=deltat(allocstall) ' .', pgrotated.dt=deltat(pgrotated) ' ] } ,'alldt' => { 'description' => "all (dt) over time" # fix, this could be generated ,'txt' => [ 'nr_dirty.dt=deltat(nr_dirty) ' .', nr_writeback.dt=deltat(nr_writeback) ' .', nr_unstable.dt=deltat(nr_unstable) ' .', nr_page_table_pages.dt=deltat(nr_page_table_pages) ' .', nr_mapped.dt=deltat(nr_mapped) ' .', nr_slab.dt=deltat(nr_slab) ' .', nr_bounce.dt=deltat(nr_bounce) ' .', pgpgin.dt=deltat(pgpgin) ' .', pgpgout.dt=deltat(pgpgout) ' .', pswpin.dt=deltat(pswpin) ' .', pswpout.dt=deltat(pswpout) ' .', pgalloc_high.dt=deltat(pgalloc_high) ' .', pgalloc_normal.dt=deltat(pgalloc_normal) ' .', pgalloc_dma.dt=deltat(pgalloc_dma) ' .', pgfree.dt=deltat(pgfree) ' .', pgactivate.dt=deltat(pgactivate) ' .', pgdeactivate.dt=deltat(pgdeactivate) ' .', pgfault.dt=deltat(pgfault) ' .', pgmajfault.dt=deltat(pgmajfault) ' .', pgrefill_high.dt=deltat(pgrefill_high) ' .', pgrefill_normal.dt=deltat(pgrefill_normal) ' .', pgrefill_dma.dt=deltat(pgrefill_dma) ' .', pgsteal_high.dt=deltat(pgsteal_high) ' .', pgsteal_normal.dt=deltat(pgsteal_normal) ' .', pgsteal_dma.dt=deltat(pgsteal_dma) ' .', pginodesteal.dt=deltat(pginodesteal) ' .', kswapd_steal.dt=deltat(kswapd_steal) ' .', kswapd_inodesteal.dt=deltat(kswapd_inodesteal) ' .', pgscan_kswapd_high.dt=deltat(pgscan_kswapd_high) ' .', pgscan_kswapd_normal.dt=deltat(pgscan_kswapd_normal) ' .', pgscan_kswapd_dma.dt=deltat(pgscan_kswapd_dma) ' .', pgscan_direct_high.dt=deltat(pgscan_direct_high) ' .', pgscan_direct_normal.dt=deltat(pgscan_direct_normal) ' .', pgscan_direct_dma.dt=deltat(pgscan_direct_dma) ' .', slabs_scanned.dt=deltat(slabs_scanned) ' .', allocstall.dt=deltat(allocstall) ' .', pgrotated.dt=deltat(pgrotated) ' ] } ### 2 d ### 3 d } }; $persona{'keyvalue'} = { 'description' => 'accept a file filled with key = value pairs ' ,'ssfile' => '' # we require transcodeing ,'sample_mode' => 'poll' ,'figure_colums' => undef ,'parse_data_function' => \&pdf_keyvalue ,'poll_src' => '' ,'poll_period' => 3 ,'delimiter' => '' ,'graphname' => { ### 1 d 'cwmp.success' => { 'description' => "cwmp.success.* / sec " ,'txt' => [ 'BOOT=deltat(cwmp.success.BOOT) ' ,' , BOOTSTRAP=deltat(cwmp.success.BOOTSTRAP)' ,' , CONN_REQ=deltat(cwmp.success.CON_REQ)' ,' , KICKED=deltat(cwmp.success.KICKED)' ,' , PERIODIC=deltat(cwmp.success.PERIODIC)' ,' , PKGSETSTAT=deltat(cwmp.success.PKGSETSTAT)' ,' , PKGSTAT=deltat(cwmp.success.PKGSTAT)' ,' , SCHEDILED=deltat(cwmp.success.SCHEDULED)' ,' , VALUE_CHANGE=deltat(cwmp.success.VALUE_CHANGE)' ] } ,'cwmp.success.raw' => { 'description' => "cwmp.success.* " ,'txt' => [ 'BOOT=(cwmp.success.BOOT) ' ,' , BOOTSTRAP=(cwmp.success.BOOTSTRAP)' ,' , CONN_REQ=(cwmp.success.CON_REQ)' ,' , KICKED=(cwmp.success.KICKED)' ,' , PERIODIC=(cwmp.success.PERIODIC)' ,' , PKGSETSTAT=(cwmp.success.PKGSETSTAT)' ,' , PKGSTAT=(cwmp.success.PKGSTAT)' ,' , SCHEDILED=(cwmp.success.SCHEDULED)' ,' , VALUE_CHANGE=(cwmp.success.VALUE_CHANGE)' ] } ,'cwmp.time' => { 'description' => "cwmp.time.* / sec " ,'txt' => [ 'BOOT=deltat(cwmp.time.BOOT) ' ,' , BOOTSTRAP=deltat(cwmp.time.BOOTSTRAP)' ,' , CONN_REQ=deltat(cwmp.time.CON_REQ)' ,' , KICKED=deltat(cwmp.time.KICKED)' ,' , PERIODIC=deltat(cwmp.time.PERIODIC)' ,' , PKGSETSTAT=deltat(cwmp.time.PKGSETSTAT)' ,' , PKGSTAT=deltat(cwmp.time.PKGSTAT)' ,' , SCHEDILED=deltat(cwmp.time.SCHEDULED)' ,' , VALUE_CHANGE=deltat(cwmp.time.VALUE_CHANGE)' ] } ,'cwmp.time.raw' => { 'description' => "cwmp.time.* " ,'txt' => [ 'BOOT=(cwmp.time.BOOT) ' ,' , BOOTSTRAP=(cwmp.time.BOOTSTRAP)' ,' , CONN_REQ=(cwmp.time.CON_REQ)' ,' , KICKED=(cwmp.time.KICKED)' ,' , PERIODIC=(cwmp.time.PERIODIC)' ,' , PKGSETSTAT=(cwmp.time.PKGSETSTAT)' ,' , PKGSTAT=(cwmp.time.PKGSTAT)' ,' , SCHEDILED=(cwmp.time.SCHEDULED)' ,' , VALUE_CHANGE=(cwmp.time.VALUE_CHANGE)' ] } } }; $persona{'snort_stats'}{'description'} = 'The original, a personality for graphing the snort.stats file that comes from the snort IDS'; $persona{'snort_stats'}{'ssfile'} = '/var/snort/snort.stats'; $persona{'snort_stats'}{'sample_mode'} = 'tail'; $persona{'snort_stats'}{'delimiter'} = "\n"; # per record $persona{'snort_stats'}{'seperator'} = ","; # per field in record. $persona{'snort_stats'}{'figure_colums'} = \&figure_colums_snort_stats; # argument is a filename # figure colums may also refigure the graphname 'z $persona{'snort_stats'}{'graphname'} = { ### 1 d 'frags' => { 'axes' => [[qw(frag_create frag_complete frag_inserts frag_del frag_autofree frag_flushes frag_cur frag_max frag_timeouts frag_faults) ]] } ,'sessions' => { 'axes' => [[qw(new_sessions del_sessions open_sessions) ]] } ,'sessionsm' => { 'axes' => [[qw(new_sessions del_sessions open_sessions max_sessions) ]] } ,'stream' => { 'axes' => [[qw(stream_flush stream_faults stream_timeouts)]] } ,'abppg' => { 'axes' => [[qw(abpp abpp_wire abpp_ipfrag abpp_ipreass abpp_rebuilt)]] } ,'mbitsg' => { 'axes' => [[qw(mbits mbits_wire mbits_ipfrag mbits_ipreass mbits_rebuilt)]] } ,'kpktsg' => { 'axes' => [[qw(kpkts kpkts_wire kpkts_ipfrag kpkts_ipreass kpkts_rebuilt)]] } # to do this one properly, we may have to do some regexp or something .... 'smart'... ,'cpu' => { 'axes' => [[qw(cpu_0_usr cpu_0_sys cpu_0_idle)]] } ,'pktsg' => { 'axes' => [[qw(pkts_rcev pkts_drop)]] } ### 2 d ,'kbfoot' => { 'axes' => [ [qw(kpackets)] ,[qw(megabits)] ] } ,'lgraph' => { 'axes' => [ [qw(abpp )] ,[qw(kpkts)] ] } ,'lgraph_wire' => { 'axes' => [ [qw(abpp_wire)] ,[qw(kpkts_wire)] ] } ,'lgraph_rebuilt' => { 'axes' => [ [qw(abpp_rebuilt)] ,[qw(kpkts_rebuilt)] ] } ,'abppsyn' => { 'axes' => [ [qw(abpp_wire)] ,[qw(syn syn_ack)] ] } ### 3 d ,'tlgraph' => { 'axes' => [ [qw(time_t)] ,[qw(abpp )] ,[qw(kpkts)] ] } ,'slgraph' => { 'axes' => [ [qw(syn syn_ack)] ,[qw(abpp )] ,[qw(kpkts)] ] } ,'monkey' => { 'axes' => [ [qw(time_t)] ,[qw(abpp_wire)] ,[qw(cpu_0_usr)] ] } ,'chongo' => { 'axes' => [ [qw(time_t)] ,[qw(abpp_wire)] ,[qw(syn syn_ack)] ] } ,'noc1' => { 'axes' => [ [qw(time_t)] ,[qw(drops)] ,[qw(alerts)] ] } ,'noc2' => { 'axes' => [ [qw(pkts_rcev pkts_drop)] ,[qw(mbits_wire)] ,[qw( alerts )] ] } ,'noc3' => { 'axes' => [ [qw(time_t)] ,[qw(abpp_wire syn syn_ack)] ,[qw(alerts)] ] } }; # # # Where normaly external files get bunlded up into the gpss for distribution. # # # # #PERSONA INJECTION SITE {#!/usr/bin/perl -w use strict; $persona_versions .= "vmstat.pm : Tue Sep 12 01:54:55 PDT 2006 "; # # # gnuplot a snort.stat file a bunch of different ways. # # now it supports other file personas and # # # my $cases = ' [root@steamy root]# vmstat 4 procs memory swap io system cpu r b swpd free buff cache si so bi bo in cs us sy wa id 0 0 64504 11716 14132 28752 1 1 29 44 8 6 2 0 0 38 0 0 64504 11704 14132 28752 0 0 0 0 102 21 1 0 0 99 0 0 64504 11700 14132 28756 0 0 0 0 102 21 1 0 0 99 '; $persona{'vmstat'} = { 'description' => 'a linux vmstat reader' ,'ssfile' => '' # we require transcodeing ,'sample_mode' => 'poll' ,'figure_colums' => undef ,'parse_data_function' => \&pdf_vmstat ,'poll_src' => 'cmdt://vmstat ' # ,'poll_cmd_args' => '2' # poll time for vmstat ( overrideable ) ,'poll_period' => 1.5 ,'delimiter' => '' # whole file. # ,'procedural_graphs' => \&fcode_gen_pid_statm # ,'changed_triggered_oldbuf' => 1 # TESTING, not the case here ,'graphname' => { # most of these are generated now. 'cpu' => { 'description' => 'cpu clockticks by type' , 'txt' => [ 'cpu_us,cpu_sy,cpu_id,cpu_wa' ] } ,'sbio' => { 'description' => 'swap and block io' , 'txt' => [ 'swap_in,swap_out,blocks_in,blocks_out' ] } ,'mem' => { 'description' => 'memory disposition vs time' , 'txt' => [ 'mem_buf,mem_cache,mem_free,mem_swaped' ] } ,'int' => { 'description' => 'interrupts vs time' , 'txt' => [ 'interrupts' ] } ,'ctx' => { 'description' => 'context switches vs time' , 'txt' => [ 'contxts' ] } # 2d #,'spawnratevsload' => {'description' => 'load averages vs spawn rate' , 'txt' => [ 'avg1,avg2,avg3' , 'spawnrate=deltat(lastpid)' ] } } }; # # # break it up into data # # # note, the buffers will come in by lines, thus the periodic headers'll kinda get borkie. # in the case of the headers, just undef. , not an empty hash. sub pdf_vmstat{ my ($buf) = @_; my %data = (); my $debug = 1; if ($buf =~ /[a-zA-Z]/ ){ #header line. # much more reasonable decode logic should be here, to do it right, but we'll just hard code it for now. # procs memory swap io system cpu # r b swpd free buff cache si so bi bo in cs us sy wa id # 1 0 64504 12624 13740 27072 0 0 0 0 126 72 2 0 0 98 $debug and print "FAST return, no data \n"; return undef; }elsif ( $buf =~ /\A\s*((\d+)(\s+\d+){15})\s*\Z/){ my ($procs_r,$procs_b,$mem_swaped,$mem_free,$mem_buff,$mem_cache,$swap_in,$swap_out,$blocks_in,$blocks_out , $interrupts ,$contxts,$cpu_us,$cpu_sy,$cpu_wa,$cpu_id) = split /\s+/ , $1; $data{'procs_r'} = { 'data' => $procs_r , 'type' => 'procs' , 'unit' => 'cnt' }; $data{'procs_b'} = { 'data' => $procs_b , 'type' => 'procs' , 'unit' => 'cnt' }; $data{'mem_swaped'} = { 'data' => $mem_swaped , 'type' => 'KBytes', 'unit' => 'KBytes'}; $data{'mem_free'} = { 'data' => $mem_free , 'type' => 'KBytes', 'unit' => 'KBytes'}; $data{'mem_buff'} = { 'data' => $mem_buff , 'type' => 'KBytes', 'unit' => 'KBytes'}; $data{'mem_cache'} = { 'data' => $mem_cache , 'type' => 'KBytes', 'unit' => 'KBytes'}; $data{'swap_in'} = { 'data' => $swap_in , 'type' => 'KBytes' , 'unit' => 'Kbytes' }; $data{'swap_out'} = { 'data' => $swap_out , 'type' => 'KBytes' , 'unit' => 'Kbytes' }; $data{'blocks_in'} = { 'data' => $blocks_in , 'type' => 'KBytes' , 'unit' => 'Kbytes' }; $data{'blocks_out'} = { 'data' => $blocks_out , 'type' => 'KBytes' , 'unit' => 'Kbytes' }; $data{'interrupts'} = { 'data' => $interrupts , 'type' => 'interrupts' , 'unit' => 'cnt' }; $data{'contxts'} = { 'data' => $contxts , 'type' => 'contxts' , 'unit' => 'cnt' }; $data{'cpu_us'} = { 'data' => $cpu_us , 'type' => 'cputicks' , 'unit' => 'cnt'}; $data{'cpu_sy'} = { 'data' => $cpu_sy , 'type' => 'cputicks' , 'unit' => 'cnt'}; $data{'cpu_wa'} = { 'data' => $cpu_wa , 'type' => 'cputicks' , 'unit' => 'cnt'}; $data{'cpu_id'} = { 'data' => $cpu_id , 'type' => 'cputicks' , 'unit' => 'cnt'}; $debug and print "elsif data! woooo!\n"; } \%data; } 1; } {#!/usr/bin/perl -w use strict; $persona_versions .= "proc_slabinfo.pm : Tue Sep 12 01:54:55 PDT 2006 "; # # # gnuplot a snort.stat file a bunch of different ways. # # now it supports other file personas and # # # my $cases = ' mm/slab.c ohohoh, STATS... it iwll be sexxy. # 2.6.17.13 has version 2.2 # 2.6.9 something slabinfo - version: 2.0 # name : tunables : slabdata msi_cache 1 1 3840 1 1 : tunables 24 12 8 : slabdata 1 1 0 fib6_nodes 5 119 32 119 1 : tunables 120 60 8 : slabdata 1 1 0 ip6_dst_cache 4 15 256 15 1 : tunables 120 60 8 : slabdata 1 1 0 ndisc_cache 1 15 256 15 1 : tunables 120 60 8 : slabdata 1 1 0 <..> #2.4.22-1.2115 somtheing. # there are stats and SMP versions.... uck. # thus some of this is kinda blind. slabinfo - version: 1.1 kmem_cache 64 70 108 2 2 1 ip_fib_hash 15 112 32 1 1 1 urb_priv 0 0 64 0 0 1 '; $persona{'/proc/slabinfo'} = { 'description' => 'a linux /proc/slabinfo reader : versions(2.0)' ,'ssfile' => '' # we require transcodeing ,'sample_mode' => 'poll' ,'figure_colums' => undef ,'parse_data_function' => \&pdf_proc_slabinfo ,'poll_src' => 'file:///proc/slabinfo' ,'poll_period' => 1.5 ,'delimiter' => '' # whole file. # ,'procedural_graphs' => \&fcode_gen_pid_statm ,'graphname' => { # most of these are generated now. 'avg' => { 'description' => 'avgs v time ' , 'txt' => [ 'avg1,avg2,avg3'] } } }; # # # break it up into data # # sub pdf_proc_slabinfo{ my ($buf) = @_; my %data = (); my @rows; @rows = grep { /\S+/} split /\n/ , $buf; my $version; $version = shift @rows; my @headerset; # probbly should just define these tables outside the pdf_ and code selector once... oh well. if ($version =~ /\Aslabinfo - version: 2.0/){ # look into relaxing this. # 2.6.9... ish. shift @rows; # get rid of the headers @headerset = ( {'name' => 'activ_objs' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'num_objs' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'objsize' , 'type' => 'bytes' , 'unit' => 'cnt' } ,{'name' => 'objperslab' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'pagesperslab' , 'type' => 'pages' , 'unit' => 'cnt' } ,{'ignore' => 1} # the : ,{'ignore' => 1} # the tunables ,{'name' => 'batchcount', 'type' =>'cnt' , 'unit' => 'cnt'} ,{'name' => 'limit', 'type' =>'cnt' , 'unit' => 'cnt'} ,{'name' => 'sharefactor', 'type' =>'factor' , 'unit' => 'factor'} ,{'ignore' => 1} # the : ,{'ignore' => 1} # the slabdata ,{'name' => 'active_slabs', 'type' =>'cnt' , 'unit' => 'cnt'} ,{'name' => 'num_slabs', 'type' =>'cnt' , 'unit' => 'cnt'} ,{'name' => 'sharedavail', 'type' =>'cnt' , 'unit' => 'cnt'} #STATS ,{'ignore' => 1} # the : ,{'ignore' => 1} # the globalstat ,{'name' => 'listallocs' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'maxobjs' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'grown' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'reaped' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'error' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'maxfreeable' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'freelimit' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'ignore' => 1} # the : ,{'ignore' => 1} # the cpustat ,{'name' => 'allochit' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'allocmiss' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'freehit' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'freemiss' , 'type' => 'cnt' , 'unit' => 'cnt' } ); } if ($version =~ /\Aslabinfo - version: 2.1/){ # look into relaxing this. ( doing it blind, I don't have live input for this yet. shift @rows; # get rid of the headers @headerset = ( {'name' => 'activ_objs' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'num_objs' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'objsize' , 'type' => 'bytes' , 'unit' => 'cnt' } ,{'name' => 'objperslab' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'pagesperslab' , 'type' => 'pages' , 'unit' => 'cnt' } ,{'ignore' => 1} # the : ,{'ignore' => 1} # the tunables # NOTE limit and batchcount were swaped... ,{'name' => 'limit', 'type' =>'cnt' , 'unit' => 'cnt'} ,{'name' => 'batchcount', 'type' =>'cnt' , 'unit' => 'cnt'} ,{'name' => 'sharefactor', 'type' =>'factor' , 'unit' => 'factor'} ,{'ignore' => 1} # the : ,{'ignore' => 1} # the slabdata ,{'name' => 'active_slabs', 'type' =>'cnt' , 'unit' => 'cnt'} ,{'name' => 'num_slabs', 'type' =>'cnt' , 'unit' => 'cnt'} ,{'name' => 'sharedavail', 'type' =>'cnt' , 'unit' => 'cnt'} # stats.... ,{'ignore' => 1} # the : ,{'ignore' => 1} # the globalstat ,{'name' => 'listallocs' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'maxobjs' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'grown' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'reaped' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'error' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'maxfreeable' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'nodeallocs' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'remotefrees' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'alienoverflow' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'ignore' => 1} # the : ,{'ignore' => 1} # the cpustat ,{'name' => 'allochit' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'allocmiss' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'freehit' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'freemiss' , 'type' => 'cnt' , 'unit' => 'cnt' } ); } if ($version =~ /\Aslabinfo - version: 1.1/){ # look into relaxing this. ( doing it blind, I don't have live input for this yet. my ($stats,$smp) = (0,0); # guessing from the number of colum. if ($version =~ /statist/){ $stats = 1; } if ($version =~ /smp/){ $smp = 1; } @headerset = (); if (1){ # nothing else, just normal push @headerset , ( {'name' => 'activ_objs' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'num_objs' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'objsize' , 'type' => 'bytes' , 'unit' => 'cnt' } ,{'name' => 'active_slabs' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'num_slabs' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'gfporder' , 'type' => 'pages' , 'unit' => 'cnt' } ); } if ($stats){ # STATS BLOCK push @headerset, ( {'ignore' => 1} # the : ,{'name' => 'high_mark' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'num_allocs' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'grown' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'reaped' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'errors' , 'type' => 'cnt' , 'unit' => 'cnt' } ); } if ($smp){ # SMP push @headerset, ( {'ignore' => 1} # the : ,{'name' => 'limit' , 'type' => 'cnt' , 'unit' => 'cnt' } # units/type probibly wrong. ,{'name' => 'batchcount' , 'type' => 'cnt' , 'unit' => 'cnt' } ); } if ($smp and $stats){ # STATS AND SMP push @headerset, ( {'ignore' => 1} # the : ,{'name' => 'allochit' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'allocmiss' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'freehit' , 'type' => 'cnt' , 'unit' => 'cnt' } ,{'name' => 'freemiss' , 'type' => 'cnt' , 'unit' => 'cnt' } ); } } if (@headerset){ my $col =0; map { my $line = $_; my ($rowname,@rest) = split /\s+/, $line; my $name; $rowname =~ s/[-\(\)]/_/g; $col =0; map { my $v = $_; if (defined $headerset[$col]{'ignore'}){ if ($debug){ print STDERR "ignoring value ($v)\n"; } }elsif(defined $headerset[$col]{'name'}){ $name = "$rowname.$headerset[$col]{'name'}"; $debug and print STDERR "name ($name) v($v)\n"; $data{$name}= {'data' => $v , 'type' => $headerset[$col]{'type'}, 'unit' => $headerset[$col]{'unit'} }; }else{ print STDERR "A colum to far in pdf_proc_slabinfo ($line) , col ($col)\n"; } $col++; } @rest; } @rows; }else{ print STDERR "WARNING: pdf_proc_slabinfo recieved a version it dosn't understand ($version)\n"; } \%data; } 1; } {#!/usr/bin/perl -w use strict; $persona_versions .= "proc_pid_statm.pm : Tue Sep 12 01:54:55 PDT 2006 "; # # # gnuplot a snort.stat file a bunch of different ways. # # now it supports other file personas and # # # my $cases = ' 349 349 285 126 24 199 64 '; $persona{'/proc/PID/statm'} = { 'description' => 'a linux /proc/PID/statm reader' ,'ssfile' => '' # we require transcodeing ,'sample_mode' => 'poll' ,'figure_colums' => undef ,'parse_data_function' => \&pdf_proc_pid_statm ,'poll_src' => 'file:///proc/1/statm' # users'll need to fix this one. ,'poll_period' => 1.5 ,'delimiter' => '' # whole file. # ,'procedural_graphs' => \&fcode_gen_pid_statm ,'notes' => " Notes: on usage Linux 2.6 can keep threads in different tasks stored under /proc/PID/task/* To graph these subthreads, and thus there values, use --poll_src \"cmd://egrep -H '.' /proc/\$PID/statm /proc/\$PID/task/*/statm\" or if you just want the primary one. --poll_src \"file:///proc/\$PID/statm Where \$PID is the process you want to examine. Similarly you can gather info on multiple PIDs with the cmd:// idiom. Though the graphs'll be kinda funny because the first row in the buffer is labled total. Buck it up little camper, you can hack it. " ,'graphname' => { # most of these are generated now. 'raw' => { 'description' => 'raw page counts v time' , 'txt' => [ 'vsize,resident,shared,codepages,datapages,libpages,dirtypages'] } ,'pagerates' => { 'description' => 'counters dt over time' , 'txt' => [ 'vsize.dt=dt(vsize) ,resident.dt=dt(resident) ,shared.dt=dt(shared) ,codepages.dt=dt(codepages) ,datapages.dt=dt(datapages) ,libpages.dt=dt(libpages) ,dirtypages.dt=dt(dirtypages)'] } } }; # # # break it up into data # # sub pdf_proc_pid_statm{ my ($buf) = @_; my %data = (); my @bufs; @bufs = grep {/\S+/} split /\n/, $buf; my $nbufs = scalar @bufs; my $suffix = ''; # in case there is just a file:// and no hanky panky while(@bufs){ my $workbuf = shift @bufs; if ($workbuf =~ /\A((.*):)?(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s*\Z/){ my ($egrepstuff,$filename,$vsize,$resident,$shared,$codepages,$datapages,$libpages,$dirtypages) = ($1,$2,$3,$4,$5,$6,$7,$8,$9); if ($filename){ if ( $filename =~ '/proc/(\d+)/statm'){ $suffix = ".$1.pid"; }elsif($filename =~ '/proc/\d+/task/(\d+)/statm'){ $suffix = ".$1.task"; } } $data{'vsize'.$suffix} = { 'data' => $vsize, 'type' => 'pages' , 'unit' => 'cnt' }; $data{'resident'.$suffix} = { 'data' => $resident, 'type' => 'pages' , 'unit' => 'cnt' }; $data{'shared'.$suffix} = { 'data' => $shared, 'type' => 'pages' , 'unit' => 'cnt' }; $data{'codepages'.$suffix} = { 'data' => $codepages, 'type' => 'pages' , 'unit' => 'cnt' }; $data{'datapages'.$suffix} = { 'data' => $datapages, 'type' => 'pages' , 'unit' => 'cnt' }; $data{'libpages'.$suffix} = { 'data' => $libpages, 'type' => 'pages' , 'unit' => 'cnt' }; $data{'dirtypages'.$suffix} = { 'data' => $dirtypages, 'type' => 'pages' , 'unit' => 'cnt' }; } } \%data; } 1; } {#!/usr/bin/perl -w use strict; $persona_versions .= "proc_pid_stat.pm : Tue Sep 12 01:54:55 PDT 2006 "; # # # gnuplot a snort.stat file a bunch of different ways. # # now it supports other file personas and # # # my $cases = ' ( more fields) # 2.4 5976 (bash) S 5971 5976 5976 34818 6280 4194560 1301 13479 429 11540 30 3 321 31 15 0 0 0 2794996 6389760 272 4294967295 134508544 135088984 3219329808 3219329076 10763314 0 65536 3686404 1266761467 3222411533 0 0 17 0 0 0 30 3 321 31 # 2.6 27757 (bash) S 27756 27757 27757 34816 29004 4194304 1416 5927 0 0 2 2 1 3 16 0 1 0 245508330 5206016 359 4294967295 134508544 135099392 3220346272 3220344868 5588898 0 65536 3686404 1266761467 3222426611 0 0 17 1 0 0 '; $persona{'/proc/PID/stat'} = { 'description' => 'a linux /proc/PID/stat reader' ,'ssfile' => '' # we require transcodeing ,'sample_mode' => 'poll' ,'figure_colums' => undef ,'parse_data_function' => \&pdf_proc_pid_stat ,'poll_src' => 'file:///proc/1/stat' # users'll need to fix this one. ,'poll_period' => 1.5 ,'delimiter' => '' # whole file. # ,'procedural_graphs' => \&fcode_gen_pid_statm ,'notes' => " Linux 2.6 can keep threads in different tasks stored under /proc/PID/task/* To graph these subthreads, and thus there values, use --poll_src \"cmd://cat /proc/\$PID/stat /proc/\$PID/task/*/stat\" or if you just want the primary one. --poll_src \"file:///proc/\$PID/stat Where \$PID is the process you want to examine. Similarly you can gather info on multiple PIDs with the cmd:// idiom. Though the graphs'll be kinda funny because the first row in the buffer is labled total. Buck it up little camper, you can hack it. " ,'graphname' => { } }; # # # break it up into data # # my %decoration; $decoration{'2.4'} = [ #0 {'name' => 'pid' , 'type' => 'int', 'unit' => 'pid' } ,{'name' => 'command' , 'type' => 'txt', 'unit' => 'txt' } ,{'name' => 'state' , 'type' => 'txt', 'unit' => 'txt' } ,{'name' => 'ppid' , 'type' => 'int', 'unit' => 'pid' } ,{'name' => 'pgrp' , 'type' => 'int', 'unit' => 'pgid' } ,{'name' => 'session', 'type' => 'int' , 'unit' => 'sid'} ,{'name' => 'tty_num', 'type' => 'int' , 'unit' => 'dev'} ,{'name' => 'tty_pgrp', 'type' => 'int' , 'unit' => 'pgid'} ,{'name' => 'task_flags', 'type' => 'int' , 'unit' => 'flags'} ,{'name' => 'min_flt', 'type' => 'int' , 'unit' => 'flt'} ,{'name' => 'cmin_flt', 'type' => 'int' , 'unit' => 'flt'} ,{'name' => 'maj_flt', 'type' => 'int' , 'unit' => 'flt'} ,{'name' => 'cmaj_flt', 'type' => 'int' , 'unit' => 'flt'} ,{'name' => 'utime', 'type' => 'int' , 'unit' => 'clockticks'} ,{'name' => 'ctime', 'type' => 'int' , 'unit' => 'clockticks'} ,{'name' => 'cutime', 'type' => 'int' , 'unit' => 'clockticks'} ,{'name' => 'cstime', 'type' => 'int' , 'unit' => 'clockticks'} ,{'name' => 'priority', 'type' => 'int' , 'unit' => 'int'} ,{'name' => 'nice', 'type' => 'int' , 'unit' => 'int'} ,{'name' => 'zero', 'type' => 'int' , 'unit' => 'int'} ,{'name' => 'it_real_value', 'type' => 'int' , 'unit' => 'int'} ,{'name' => 'start_time', 'type' => 'int' , 'unit' => 'int'} ,{'name' => 'vsize', 'type' => 'int' , 'unit' => 'int'} # mm_struct stuff, from include/linux/sched.h .. not sure what it is. ,{'name' => 'mm.rss', 'type' => 'int' , 'unit' => 'int'} ,{'name' => 'rlimit', 'type' => 'int' , 'unit' => 'int'} ,{'name' => 'start_code', 'type' => 'int' , 'unit' => 'int'} ,{'name' => 'end_code', 'type' => 'int' , 'unit' => 'int'} ,{'name' => 'start_stack', 'type' => 'int' , 'unit' => 'int'} ,{'name' => 'esp', 'type' => 'int' , 'unit' => 'int'} ,{'name' => 'eip', 'type' => 'int' , 'unit' => 'int'} # unused. ,{'name' => 'signal_1', 'type' =>'int' , 'unit' => 'sig'} ,{'name' => 'signal_2', 'type' =>'int' , 'unit' => 'sig'} ,{'name' => 'signal_3', 'type' =>'int' , 'unit' => 'sig'} ,{'name' => 'signal_4', 'type' =>'int' , 'unit' => 'sig'} ,{'name' => 'wchan', 'type' =>'int' , 'unit' => 'int'} ,{'name' => 'nswap', 'type' =>'int' , 'unit' => 'pages'} ,{'name' => 'cnswap', 'type' =>'int' , 'unit' => 'pages'} ,{'name' => 'exitsig', 'type' =>'int' , 'unit' => 'sig'} ,{'name' => 'task_cpu', 'type' =>'int' , 'unit' => 'int'} ,{'name' => 'rt_priority', 'type' =>'int' , 'unit' => 'int'} ,{'name' => 'policy', 'type' =>'int' , 'unit' => 'int'} ,{'name' => 'gutime', 'type' =>'int' , 'unit' => 'clockticks'} ,{'name' => 'gstime', 'type' =>'int' , 'unit' => 'clockticks'} ,{'name' => 'gcutime', 'type' =>'int' , 'unit' => 'clockticks'} ,{'name' => 'gcstime', 'type' =>'int' , 'unit' => 'clockticks'} ]; $decoration{'2.6'} = [ #0 {'name' => 'pid' , 'type' => 'int', 'unit' => 'pid' } ,{'name' => 'command' , 'type' => 'txt', 'unit' => 'txt' } ,{'name' => 'state' , 'type' => 'txt', 'unit' => 'txt' } ,{'name' => 'ppid' , 'type' => 'int', 'unit' => 'pid' } ,{'name' => 'pgrp' , 'type' => 'int', 'unit' => 'pgid' } ,{'name' => 'session', 'type' => 'int' , 'unit' => 'sid'} ,{'name' => 'tty_num', 'type' => 'int' , 'unit' => 'dev'} ,{'name' => 'tty_pgrp', 'type' => 'int' , 'unit' => 'pgid'} ,{'name' => 'task_flags', 'type' => 'int' , 'unit' => 'flags'} ,{'name' => 'min_flt', 'type' => 'int' , 'unit' => 'flt'} ,{'name' => 'cmin_flt', 'type' => 'int' , 'unit' => 'flt'} ,{'name' => 'maj_flt', 'type' => 'int' , 'unit' => 'flt'} ,{'name' => 'cmaj_flt', 'type' => 'int' , 'unit' => 'flt'} ,{'name' => 'utime', 'type' => 'int' , 'unit' => 'clockticks'} ,{'name' => 'ctime', 'type' => 'int' , 'unit' => 'clockticks'} ,{'name' => 'cutime', 'type' => 'int' , 'unit' => 'clockticks'} ,{'name' => 'cstime', 'type' => 'int' , 'unit' => 'clockticks'} ,{'name' => 'priority', 'type' => 'int' , 'unit' => 'int'} ,{'name' => 'nice', 'type' => 'int' , 'unit' => 'int'} ,{'name' => 'num_threads', 'type' => 'int' , 'unit' => 'int'} ,{'name' => 'start_time', 'type' => 'int' , 'unit' => 'int'} ,{'name' => 'vsize', 'type' => 'int' , 'unit' => 'int'} # mm_struct stuff, from include/linux/sched.h .. not sure what it is. ,{'name' => 'mm.rss', 'type' => 'int' , 'unit' => 'int'} ,{'name' => 'rlimit', 'type' => 'int' , 'unit' => 'int'} ,{'name' => 'start_code', 'type' => 'int' , 'unit' => 'int'} ,{'name' => 'end_code', 'type' => 'int' , 'unit' => 'int'} ,{'name' => 'start_stack', 'type' => 'int' , 'unit' => 'int'} ,{'name' => 'esp', 'type' => 'int' , 'unit' => 'int'} ,{'name' => 'eip', 'type' => 'int' , 'unit' => 'int'} # unused. ,{'name' => 'signal_1', 'type' =>'int' , 'unit' => 'sig'} ,{'name' => 'signal_2', 'type' =>'int' , 'unit' => 'sig'} ,{'name' => 'signal_3', 'type' =>'int' , 'unit' => 'sig'} ,{'name' => 'signal_4', 'type' =>'int' , 'unit' => 'sig'} ,{'name' => 'wchan', 'type' =>'int' , 'unit' => 'int'} ,{'name' => 'zero1', 'type' =>'int' , 'unit' => 'int'} ,{'name' => 'zero2', 'type' =>'int' , 'unit' => 'int'} ,{'name' => 'exitsig', 'type' =>'int' , 'unit' => 'sig'} ,{'name' => 'task_cpu', 'type' =>'int' , 'unit' => 'int'} ,{'name' => 'rt_priority', 'type' =>'int' , 'unit' => 'int'} ,{'name' => 'policy', 'type' =>'int' , 'unit' => 'int'} ,{'name' => 'gutime', 'type' =>'int' , 'unit' => 'clockticks'} ,{'name' => 'gstime', 'type' =>'int' , 'unit' => 'clockticks'} ,{'name' => 'gcutime', 'type' =>'int' , 'unit' => 'clockticks'} ,{'name' => 'gcstime', 'type' =>'int' , 'unit' => 'clockticks'} ,{'name' => 'extra' , 'type' => 'foo', 'unit' => 'blunderticks'} ]; # # note under 2.6 there is this idea of task, or kenrel thread # so /proc/PID/stat is the agregate, and # /proc/PID/task/PID2/stat are the individual task records. # # sub pdf_proc_pid_stat{ my ($buf) = @_; my %data = (); my (@bufs); @bufs = grep { /\S+/ } split /\n/ , $buf; my %pidcnts; # the asumption is that the first of a duplicat pid/tid is in fact the PID, and the second is the tid. # figure out which is which map { if ( /\A(\d+)/){ $pidcnts{$1}++; } } @bufs; my $default_suffix = ''; # no pid no nothing. if (@bufs > 1){ $default_suffix = ".task"; } while (@bufs){ my $workbuf; $workbuf = shift @bufs; my $suffix =''; if ($workbuf =~ /\A(\d+)\s+\((.+)\)\s+(.)\s+((\d+)(\s+(-?\d+))+)\s*\Z/){ my ($pid,$comm,$state,$rest) = ($1,$2,$3,$4); if ($default_suffix){ $suffix = ".$pid$default_suffix"; } if ($pidcnts{$pid} > 1){ $pidcnts{$pid}--; $suffix = ".$pid.pid"; } my (@restarray); @restarray = grep { defined and length $_ } split /\s+/ , $rest; my (@fullline); @fullline = ($pid,$comm, $state, @restarray); my ($decor); my ($n) = scalar @fullline; if ($n == 41){ $decor = "2.6"; }elsif ($n == 45){ $decor = "2.4"; }else{ print STDERR "WARNING mismatched number of fileds in the pdf_proc_pid_stat ($n) kenerel rev?\n"; } $debug and print "workBuf ($workbuf)\n"; if ($decor){ my $i =0; while (defined $fullline[$i]){ my ($name); $name = $decoration{$decor}[$i]{'name'}; $debug and print "i ($i) v ($fullline[$i]) decor ($decor) ($name)\n"; $data{"$decoration{$decor}[$i]{'name'}$suffix"} = {'data' => $fullline[$i] , 'type' => $decoration{$decor}[$i]{'type'} , 'unit' => $decoration{$decor}[$i]{'unit'} }; $i++; } } }else{ print STDERR "Unmatched line in (pdf_proc_pid_stat) ($workbuf)\n"; } } $debug and print "datadumper " , Dumper(\%data) , "\n\n"; \%data; } 1; } {#!/usr/bin/perl -w use strict; $persona_versions .= "proc_net_dev.pm : Tue Sep 12 01:54:55 PDT 2006 "; # # # gnuplot a snort.stat file a bunch of different ways. # # now it supports other file personas and # # # my $cases = ' Inter-| Receive | Transmit face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed lo:20844036 182925 0 0 0 0 0 0 20844036 182925 0 0 0 0 0 0 eth0:442217168 3983678 0 0 0 0 0 0 226700011 2153468 0 0 0 0 0 0 sit0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 '; $persona{'/proc/net/dev'} = { 'description' => 'a linux /proc/net/dev reader' ,'ssfile' => '' # we require transcodeing ,'sample_mode' => 'poll' ,'figure_colums' => undef ,'parse_data_function' => \&pdf_proc_net_dev ,'poll_src' => 'file:///proc/net/dev' ,'poll_period' => 5.13 # the value changes infrequently... so we get chunkie chunkie chunkie. ,'delimiter' => '' # whole file. # ,'procedural_graphs' => \&fcode_gen_pid_statm # probibly a lot of this... # ,'graphname' => { # # most of these are generated now. # 'avg' => { 'description' => 'avgs v time ' , 'txt' => [ 'avg1,avg2,avg3'] # } # } ,'graphname' => { 'eth0bytes' => {'description' => 'bytes io / sec' , 'txt' => [ 'rxbytes=deltat(eth0.rxbytes),txbytes=deltat(eth0.txbytes)' ] } ,'eth0bits' => {'description' => 'bytes io / sec' , 'txt' => [ 'rxbits= (8*deltat(eth0.rxbytes)),txbits=(8* deltat(eth0.txbytes))' ] } ,'eth0abpp' => {'description' => 'avgbytes / pkt' , 'txt' => [ 'rxabpp=(deltat(eth0.rxbytes)/ deltat(eth0.rxpkts)) , txabpp=(deltat(eth0.txbytes)/ deltat(eth0.txpkts)) , abpp = ((deltat(eth0.rxbytes) + deltat(eth0.txbytes) ) / (deltat(eth0.rxpkts) + deltat(eth0.txpkts))) ' ]} ,'errs' => {'description' => 'bad things' , 'txt' => [ ' rxerrs=deltat(eth0.rxerr) , txerrs=deltat(eth0.txerr) ,txcolls=deltat(eth0.txcolls) , txcarrier=deltat(eth0.txcarrier) ']} # 2d ,'errsvbits' => {'description' => 'bad things vs bits/sec' , 'txt' => [ ' rxerrs=deltat(eth0.rxerr) , txerrs=deltat(eth0.txerr) ,txcolls=deltat(eth0.txcolls) , txcarrier=deltat(eth0.txcarrier) ' , 'bps=( 8 * (deltat(eth0.rxbytes) + deltat(eth0.txbytes))']} ,'errsvpkts' => {'description' => 'bad things vs pkts/sec' , 'txt' => [ ' rxerrs=deltat(eth0.rxerr) , txerrs=deltat(eth0.txerr) ,txcolls=deltat(eth0.txcolls) , txcarrier=deltat(eth0.txcarrier) ' , 'pps=(deltat(eth0.rxpkts) + deltat(eth0.txpkts) )']} } }; # # # break it up into data # # sub pdf_proc_net_dev{ my ($buf) = @_; my %data = (); my (@rows); @rows = grep {/\S+/} split /\n/, $buf; shift @rows; # get rid of the headers. shift @rows; while (@rows){ my $row = shift @rows; if ($row =~ /\A\s*(\S+):\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s*\Z/){ my ($interface,$rb,$rp,$re,$rd,$rfifo,$rframe,$rc,$rm,$tb,$tp,$te,$td,$tfifo,$tcolls,$tcarrier,$tcompressed) = ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17); $data{"$interface.rxbytes"} = { 'data' => $rb , 'type' => 'bytes' , 'unit' => 'bytes' }; $data{"$interface.rxpkts"} = { 'data' => $rp , 'type' => 'cnt' , 'unit' => 'cnt' }; $data{"$interface.rxerr"} = { 'data' => $re , 'type' => 'cnt' , 'unit' => 'cnt' }; $data{"$interface.rxdrop"} = { 'data' => $rd , 'type' => 'cnt' , 'unit' => 'cnt' }; $data{"$interface.rxfifo"} = { 'data' => $rfifo , 'type' => 'cnt' , 'unit' => 'cnt' }; $data{"$interface.rxcompressed"} = { 'data' => $rc , 'type' => 'cnt' , 'unit' => 'cnt' }; $data{"$interface.rxframe"} = { 'data' => $rframe , 'type' => 'cnt' , 'unit' => 'cnt' }; $data{"$interface.rxmulticast"} = { 'data' => $rm , 'type' => 'cnt' , 'unit' => 'cnt' }; $data{"$interface.txbytes"} = { 'data' => $tb , 'type' => 'bytes' , 'unit' => 'bytes' }; $data{"$interface.txpkts"} = { 'data' => $tp , 'type' => 'cnt' , 'unit' => 'cnt' }; $data{"$interface.txerr"} = { 'data' => $te , 'type' => 'cnt' , 'unit' => 'cnt' }; $data{"$interface.txdrop"} = { 'data' => $td , 'type' => 'cnt' , 'unit' => 'cnt' }; $data{"$interface.txfifo"} = { 'data' => $tfifo , 'type' => 'cnt' , 'unit' => 'cnt' }; $data{"$interface.txcompressed"} = { 'data' => $tcompressed , 'type' => 'cnt' , 'unit' => 'cnt' }; $data{"$interface.txcolls"} = { 'data' => $tcolls , 'type' => 'cnt' , 'unit' => 'cnt' }; $data{"$interface.txcarrier"} = { 'data' => $tcarrier , 'type' => 'cnt' , 'unit' => 'cnt' }; }else{ print STDERR "WARNING : pdf_proc_net_dev got a buffer it couldn't handle, row ($row)\n"; } } \%data; } 1; } {#!/usr/bin/perl -w use strict; $persona_versions .= "proc_loadavg.pm : Tue Sep 12 01:54:55 PDT 2006 "; # # # gnuplot a snort.stat file a bunch of different ways. # # now it supports other file personas and # # # my $cases = ' 0.00 0.00 0.00 1/83 9408 '; $persona{'/proc/loadavg'} = { 'description' => 'a linux /proc/laodavg reader' ,'ssfile' => '' # we require transcodeing ,'sample_mode' => 'poll' ,'figure_colums' => undef ,'parse_data_function' => \&pdf_proc_loadavg ,'poll_src' => 'file:///proc/loadavg' ,'poll_period' => 1.5 ,'delimiter' => '' # whole file. # ,'procedural_graphs' => \&fcode_gen_pid_statm ,'graphname' => { # most of these are generated now. 'avg' => { 'description' => 'avgs v time ' , 'txt' => [ 'avg1,avg2,avg3'] } ,'thread' => { 'description' => 'tasks vs time' , 'txt' => [ 'threads,running' ] } ,'spawnrate' => { 'description' => 'new pids / sec' , 'txt' => [ 'spawnrate=deltat(lastpid)' ] } # will probibly require wraping code # 2d ,'spawnratevsload' => {'description' => 'load averages vs spawn rate' , 'txt' => [ 'avg1,avg2,avg3' , 'spawnrate=deltat(lastpid)' ] } } }; # # # break it up into data # # sub pdf_proc_loadavg{ my ($buf) = @_; my %data = (); if ($buf =~ /\A\s*(\d+(\.\d+)?)\s+(\d+(\.\d+)?)\s+(\d+(\.\d+)?)\s+(\d+)\/(\d+)\s+(\d+)\s*\Z/){ my ($run1,$run2,$run3,$threads_running,$threads,$lastpid) = ($1,$3,$5,$7,$8,$9); $data{'avg1'} = { 'data' => $run1, 'type' => 'usage' , 'unit' => 'float' }; $data{'avg2'} = { 'data' => $run2, 'type' => 'usage' , 'unit' => 'float' }; $data{'avg3'} = { 'data' => $run3, 'type' => 'usage' , 'unit' => 'float' }; $data{'running'} = { 'data' => $threads_running, 'type' => 'threads' , 'unit' => 'count' }; $data{'threads'} = { 'data' => $threads, 'type' => 'threads' , 'unit' => 'count' }; $data{'lastpid'} = { 'data' => $lastpid, 'type' => 'pid' , 'unit' => 'pid' }; } \%data; } 1; } {#!/usr/bin/perl -w use strict; $persona_versions .= "proc_interupts.pm : Thu Aug 10 00:56:09 PDT 2006\n"; # # # gnuplot a snort.stat file a bunch of different ways. # # now it supports other file personas and # # # my $cases = ' $ cat /proc/interrupts CPU0 CPU1 0: 879572933 879541933 IO-APIC-edge timer 1: 765 585 IO-APIC-edge i8042 4: 81 169 IO-APIC-edge serial 8: 1 0 IO-APIC-edge rtc 9: 0 0 IO-APIC-level acpi 169: 0 0 IO-APIC-level uhci_hcd 185: 0 0 IO-APIC-level ehci_hcd, uhci_hcd 193: 436171 428700 IO-APIC-level libata, uhci_hcd 201: 0 0 IO-APIC-level uhci_hcd 217: 3731915 0 PCI-MSI eth0 NMI: 0 0 LOC: 1759093575 1759093574 ERR: 0 MIS: 0 # cat /proc/interrupts CPU0 0: 251414612 XT-PIC timer 1: 7937 XT-PIC i8042 2: 0 XT-PIC cascade 8: 1 XT-PIC rtc 9: 1694428 XT-PIC acpi, eth0 12: 3658 XT-PIC i8042 14: 360763 XT-PIC ide0 15: 2519033 XT-PIC ide1 NMI: 0 ERR: 0 # cat /proc/interrupts CPU0 0: 53764698 XT-PIC timer 1: 4 XT-PIC keyboard 2: 0 XT-PIC cascade 5: 0 XT-PIC usb-uhci 8: 183359060 XT-PIC rtc 9: 272662 XT-PIC ehci_hcd, eth1 10: 2612967 XT-PIC libata, usb-uhci, eth0 11: 0 XT-PIC usb-uhci, usb-uhci 12: 13 XT-PIC PS/2 Mouse 14: 13231571 XT-PIC ide0 15: 671483 XT-PIC ide1 NMI: 0 ERR: 0 '; $persona{'/proc/interrupts'} = { 'description' => 'a linux /proc/interrupts reader' ,'ssfile' => '' # we require transcodeing ,'sample_mode' => 'poll' ,'figure_colums' => undef ,'parse_data_function' => \&pdf_interrupts ,'poll_src' => 'file:///proc/interrupts' ,'poll_period' => 1.5 ,'delimiter' => '' # whole file. ,'procedural_graphs' => \&fcode_gen_interrupts ,'graphname' => { # most of these are generated now. } }; # # # break it up into data # # sub pdf_interrupts{ my ($buf) = @_; my %data = (); my @colum_heads= (); map { my $line = $_; if ($line =~ /\A\s+CPU\d+/){ # these become our colum headings. @colum_heads = grep {$_} split /\s+/, $line; }elsif ($line =~ /\A\s*(\d+):\s*(\d+(\s+\d+)*)\s+(\S+.*)\Z/){ # i.e. 193: 436171 428700 IO-APIC-level libata, uhci_hcd my ($intnum,$data_txt,$labels_txt) = ($1,$2,$4); # synthisize a new (and horrible) name for the interupt. # intr.$CPUN.$INTN.$TXTCRAP $labels_txt =~ s/\A\s+//; $labels_txt =~ s/(-|,|\s+)+/_/g; my $cnt =0; map { my $v = $_; my $name = "intr.$colum_heads[$cnt].$intnum.$labels_txt"; # print "pdf_interupts 1 ($name) ($v)\n"; $data{$name} = { 'data' => $v ,'type' => 'interrupt' ,'unit' => 'cnt' }; $cnt++; } grep {$_ =~ /\d/} split /\s+/ , $data_txt; }elsif ($line =~ /\A([a-zA-Z]+):\s*(\d+(\s+\d+)*)\s*\Z/){ my ($intname , $data_txt) = ($1,$2); my $cnt =0; map { my $v = $_; my $name = "intr.$colum_heads[$cnt].$intname"; # print "pdf_interupts 2 ($name) ($v)\n"; $data{$name} ={ 'data' => $v ,'type' => 'interrupt' ,'unit' => 'cnt' }; $cnt++; } grep {$_ =~ /\d/} split /\s+/ , $data_txt; }else{ print "Unmatched pdf_interrupts line ($line)\n"; } } split /\n/ , $buf; \%data; } sub isnum{ my ($txt) = @_; my ($ret) ; $ret = $txt =~ /\A(-|\+)?(\d+)/; $ret; } sub textnum{ my (@asplit,@bsplit); my $ret = 0; @asplit = split /\b/, $a; @bsplit = split /\b/, $b; while (not $ret and @asplit and @bsplit){ my ($as,$bs) = (shift @asplit, shift @bsplit); if (isnum($as) and isnum($bs)){ $ret = $as <=> $bs; }else{ $ret = $as cmp $bs; } } if (not $ret){ if (@asplit){ $ret =1 ; }else{ $ret = 0; } } $ret; } # # Accept a datahash, and return a hash, like 'graphname' # # sub fcode_gen_interrupts{ my ($datahash) = @_; my (%graphnames); # # # strait interupts are kinda useless.... but why not... # interupts dt v time # total interupts per cpu v time ? # # algorithm: # build up little chunks of fcode, then put them in piles. # then take the piles out and put them together my %piles; map { my ($dkey) = $_; my ($dkey_dt,$dkey_dtf); $dkey_dt = $dkey; $dkey_dt =~ s/intr/ints/; $dkey_dtf = "$dkey_dt=deltat($dkey)"; # print "dkey_dt($dkey_dt) dkey_dtf($dkey_dtf)\n"; $piles{'intsvt'}{$dkey_dt} = $dkey_dtf; if ($dkey =~ /\Aintr\.(CPU\d+)\.(\S+)\Z/){ my $cpu = $1; $piles{"cputotals"}{"$1.total"}{$dkey_dt} = $dkey_dtf; } } sort keys %$datahash; my $desc = "ints v time"; my $fcode; $fcode = [ join ", " , map { $piles{'intsvt'}{$_}; } keys %{$piles{'intsvt'}} ]; # values %{$piles{'intsvt'}{ keys %{$piles{'intsvt'}} }} ]; $graphnames{"ints"} = { 'description' => $desc, 'txt' => $fcode }; # per cpu totals $desc = "per cpu interrupt total / sec"; $fcode = [ join ", " , map { my $cput = $_; my $sumtxt = "$cput=( " . ( join " + " , map { "($piles{'cputotals'}{$cput}{$_})" } sort textnum keys %{$piles{'cputotals'}{$cput}} ) . ")\n"; #print "sumtxt = ($sumtxt)\n"; $sumtxt; } sort keys %{$piles{'cputotals'}} ]; $graphnames{'cputotals'} = { 'description' => $desc, 'txt' => $fcode } ; # # # $dev.times # $desc = "Raw cumulative IO times in milliseconds for $dev."; # $fcode = [ " $dev.io.time , $dev.io.wtime , $dev.reading.time , $dev.writing.time " ]; # $gname = "$dev.times"; # $graphnames{$gname} = { 'description' => $desc, 'txt' => $fcode } ; # $dev.times.dt ## $desc = "dt IO times in milliseconds for $dev."; # $fcode = [ # join " , " , map { # "$dev$_.dt=deltat($dev$_)"; # } qw( .io.time .io.wtime .reading.time .writing.time ) ] ; # # $gname = "$dev.times.dt"; # $graphnames{$gname} = { 'description' => $desc, 'txt' => $fcode } ; ## # # # $dev.rw # $desc = "Raw cumulative # reads and writes for $dev."; # $fcode = [ spluz( $dev, qw ( .reads .writes .reads.merged .writes.merged )) ] ; # $gname = "$dev.rw"; # $graphnames{$gname} = { 'description' => $desc, 'txt' => $fcode } ; # # # $dev.rw.dt # $desc = "# reads and writes / dt for $dev."; # $fcode = [ dts( $dev, qw ( .reads .writes .reads.merged .writes.merged )) ] ; # $gname = "$dev.rw.dt"; ## $graphnames{$gname} = { 'description' => $desc, 'txt' => $fcode } ; # # # # $dev.if # $gname = "$dev.if"; $desc = "# operations currently outstanding"; # $fcode = [ spluz( $dev , ".ios.inflight" ) ] ; # $graphnames{$gname} = { 'description' => $desc, 'txt' => $fcode } ; # # # $dev.if.dt # $gname = "$dev.if.dt"; $desc = "#.dt operations currently outstanding"; ## $fcode = [ dts( $dev , ".ios.inflight" ) ] ; # $graphnames{$gname} = { 'description' => $desc, 'txt' => $fcode } ; # \%graphnames; } } {#!/usr/bin/perl -w use strict; $persona_versions .= " nagios_perf : Sun Oct 29 03:06:56 PST 2006"; # # # gnuplot : read up a host-perfdata.out or a service-perfdata.out, or ... maybe even both. # # now it supports other file personas and # # # $cases = ' # tab seperated. fields. # two sorts of fields. # reconfigurable in the nagios config file so... we want to be kinda respectfull of difference in the parsing. # host_perfdata_file_template=[HOSTPERFDATA]\t$TIMET$\t$HOSTNAME$\t$HOSTEXECUTIONTIME$\t$HOSTOUTPUT$\t$HOSTPERFDATA$ service_perfdata_file_template=[SERVICEPERFDATA]\t$TIMET$\t$HOSTNAME$\t$SERVICEDESC$\t$SERVICEEXECUTIONTIME$\t$SERVICELATENCY$\t$SERVICEOUTPUT$ \t$SERVICEPERFDATA$ 1162121355 naacexch icmp-ping OK 1 HARD 4.021 0.243 PING OK - Packet loss = 0%, RTA = 0.18 ms 1162121357 naacfile icmp-ping OK 1 HARD 4.022 0.030 PING OK - Packet loss = 0%, RTA = 0.17 ms ==> /var/log/nagios/host-perfdata.out <== 0 phaines UP 1 HARD 0.000 0 seattleoperator UP 1 HARD 0.000 ==> /var/log/nagios/service-perfdata.out <== 1162121363 phaines icmp-ping UNKNOWN 4 HARD 0.017 0.147 check_ping: Invalid hostname/address - phaines 1162121360 northdocxp icmp-ping OK 1 HARD 4.022 0.067 PING OK - Packet loss = 0%, RTA = 0.22 ms '; $persona{'nagios_perf'} = { 'description' => 'a generic ish nagios_perf stats reader' ,'ssfile' => '' # we require transcodeing ,'sample_mode' => 'poll' ,'figure_colums' => undef ,'parse_data_function' => \&pdf_nagios_perf ,'poll_src' => 'filet:///var/log/nagios/service-perfdata.out' # figure out a way to get both of them in there. ,'poll_period' => 1/1000000 # go very quickly if you can . ,'delimiter' => "\n" # lines # ,'procedural_graphs' => \&fcode_gen_pid_statm ,'graphname' => { # most of these are generated now. # 'avg' => { 'description' => 'avgs v time ' , 'txt' => [ 'avg1,avg2,avg3'] } # ,'thread' => { 'description' => 'tasks vs time' , 'txt' => [ 'threads,running' ] } # ,'spawnrate' => { 'description' => 'new pids / sec' , 'txt' => [ 'spawnrate=deltat(lastpid)' ] } # will probibly require wraping code # # # 2d # ,'spawnratevsload' => {'description' => 'load averages vs spawn rate' , 'txt' => [ 'avg1,avg2,avg3' , 'spawnrate=deltat(lastpid)' ] } } }; # # # break it up into data # # my (@serviceperfcols,@hostperfcols); my ($serviceperfcols,$hostperfcols); # # # another qwerk of nagios? SERVICEDESC is several \t fields... nuts. # # $hostperfcols = '[HOSTPERFDATA]\t$TIMET$\t$HOSTNAME$\t$HOSTEXECUTIONTIME$\t$HOSTOUTPUT$\t$HOSTPERFDATA$'; $serviceperfcols = '[SERVICEPERFDATA]\t$TIMET$\t$HOSTNAME$\t$servicename$\t$servicestate$\t$checkcnt$\t$servicerigidity$\t$SERVICEEXECUTIONTIME$\t$SERVICELATENCY$\t$SERVICEOUTPUT$\t$SERVICEPERFDATA$'; $serviceperfcols =~ s/\\t/\t/g; $hostperfcols =~ s/\\t/\t/g; # # # The plan is to build up an intermediary representation. # data {'l0'} = string. # data {'l1'}{$perfcolsnames} = string. # data {'l2'}{$perfcolsnames}{ decoded fields keys } = value # data {'l3'}{$perfcolsnames}{ decoded fields keys }{ subkeys } = value; # # # there is some defualt parsing for l2 values, # and there is also a plugin interface (just use persona file to read them in... icky, fix. # the plugin interface goes off the 'servicedesc' field to pic whch one is appropreate. # # there is no solid way to figure out the difference between service and host perf, so we'll use field count. # # # linearizeabilityalizeing. # # Converting IR => gpss # # 1) add a HOSTNAME filter argument, so we can filter out things we don't want to see. # 2) generic linearization is $perfcolsname , $perfcolsname.decoded fieldskey , $perfcolsnames.decodefieldkey.subkey # so there may end up being 3 copies of the data in the kittty. # 3) we may ask the plugin for unit and type info... maybe. # # # # my $parsed_persona_args=0; my $pdf_nagios_perf_filterin =''; my $pdf_nagios_perf_filterin_check=''; my $pdf_nagios_debug = 0; my $pdf_cnt = 0; sub pdf_nagios_perf{ my ($buf) = @_; my %data = (); # my $debug = $pdf_nagios_debug or $::debug; my $debug = $pdf_nagios_debug ; if (( $debug or 0 ) and $pdf_cnt++ > 100000) { print "EXITING, due to debug in naigos_perf \n"; exit(); } if ($pdf_nagios_perf_filterin and not $buf =~ /$pdf_nagios_perf_filterin/){ $debug and print "FILTERED OUT very course on buf ($buf)\n"; return undef; }elsif($pdf_nagios_perf_filterin_check and not $buf =~ /$pdf_nagios_perf_filterin_check/){ $debug and print "FILTERED OUT (check) very course on buf ($buf)\n"; return undef; } $debug and print "pdf_nagios_perf\n"; #my $tmpbuf; #print "buf ($buf)\n"; #$tmpbuf = $buf; #$tmpbuf =~ s/\t/ -=- /g; #print "tmpbuf ($tmpbuf)\n"; if ($buf =~ /\A==>\s+.*\s+<==\s*\Z/ or $buf =~ /\A\s*\Z/){ # we figured out how to get multiple files tailed. this is the cruft. return %data; } # fix this, more generalized. if (not $parsed_persona_args){ # parse the args. they are a simple string. $parsed_persona_args = 1; my @args; @args = split /\s+/ , $persona_args; while (@args){ my $arg; $arg = shift @args; if ($arg =~ /\A(--filterin)\Z/){ $pdf_nagios_perf_filterin = shift @args; }elsif ($arg =~ /\A(--filterin_check)\Z/){ $pdf_nagios_perf_filterin_check = shift @args; }elsif ($arg =~ /\A(--debug)\Z/){ $pdf_nagios_debug = 1; }else{ die "Misunderstood argument ($arg) in pdf_nagios_perf... "; } } } # dependant on the format string, it's beyond scope to parse horrible cases... my @parts; $buf =~ s/\n/ /; # we need to leave something for the split to pick up. @parts = split /\t/, $buf; #print "buf ($buf)\n"; #$tmpbuf = $buf; #$tmpbuf =~ s/\t/ -=- /g; #print "tmpbuf ($tmpbuf)\n"; #print "parts funny (" . (join " , " , @parts) . ")\n"; # exit(1); my $nparts ; $nparts = scalar @parts; # the plan is to break the lines down into hashes based on colum name (what's in the teomplate # host_perfdata_file_template=[HOSTPERFDATA]\t$TIMET$\t$HOSTNAME$\t$HOSTEXECUTIONTIME$\t$HOSTOUTPUT$\t$HOSTPERFDATA$ # service_perfdata_file_template=[SERVICEPERFDATA]\t$TIMET$\t$HOSTNAME$\t$SERVICEDESC$\t$SERVICEEXECUTIONTIME$\t$SERVICELATENCY$\t$SERVICEOUTPUT$$SERVICEPERFDATA$' # fix peramiterize. if ( not @hostperfcols ) { @hostperfcols = map { my $col = $_; if ($col =~ /\$(\S+)\$/){ $col = lc $1; }else{ $col = ''; } $col ; } split /\t/ , $hostperfcols; } if ( not @serviceperfcols) { @serviceperfcols = map { my $col = $_; if ($col =~ /\$(\S+)\$/){ $col = lc $1; }else{ $col = ''; } $col ; } split /\t/ , $serviceperfcols; } if(0){ my $debug = 1; $debug and print "hpfc : " , (join "," , @hostperfcols ) , "\n\n"; $debug and print "spfc : " , (join "," , @serviceperfcols ) , "\n\n"; my $nserviceperfcols; $nserviceperfcols = scalar @serviceperfcols; print "nspc = ($nserviceperfcols) !=? $nparts\n"; print "parts " , ( join " | " , @parts ) , "\n"; } my %ir = (); $ir{'l0'} = $buf; my $i = 1; # # # if (scalar @serviceperfcols eq $nparts +1){ map { my $v = $_; $debug and print "i ($i) spfc ($serviceperfcols[$i])\n"; if ($serviceperfcols[$i]){ $ir{'l1'}{$serviceperfcols[$i]} = $v; } $i++; } @parts; # atempt to filter early, and filter often. if ($pdf_nagios_perf_filterin and $ir{'l1'}{'hostname'} and not $ir{'l1'}{'hostname'} =~ /\A$pdf_nagios_perf_filterin\Z/){ $debug and print "FILTERED OUT $ir{'l1'}{'hostname'} n"; return undef; }elsif($pdf_nagios_perf_filterin_check and $ir{'l1'}{'servicename'} and not $ir{'l1'}{'servicename'} =~ /\A$pdf_nagios_perf_filterin_check\Z/){ $debug and print "FILTERED OUT (check) $ir{'l1'}{'servicename'}\n"; return undef; } # plugins go here. more decode, # and adding things to $data if they like i guess. # figure out a way to coperate on 'type' and 'unit'. if (defined $ir{'l1'}{'serviceperfdata'}){ # ideal, they are ';' seperated, and key=value my @kvpairs; my $spd = $ir{'l1'}{'serviceperfdata'}; my $servicename = $ir{'l1'}{'servicename'}; if ($debug){ # so we get examples. $ir{'l1'}{"serviceperfdata.$servicename"} = $spd; } # # a fixup for a bad check_http ? # if ($ir{'l1'}{'servicename'} eq 'check_http'){ $spd =~ s/ size=/;size=/g; # a poorly writen logline. } @kvpairs = split /;/ , $spd; $debug and print "serviceperfdata : ( $spd )\n"; my $i =1; map { my $pair = $_; $debug and print "pair ($pair)\n"; if ($pair eq '' or $pair eq ' '){ }elsif($pair =~ /\A\s*(\S+)=(.*)\s*\Z/){ my ($key,$value) = ($1,$2); my ($unit) = ( '' ); if ($value =~ /\A(-?\d+(\.\d+)?)([a-zA-Z]+)\Z/){ $unit = $3; # fix, get it down there. $value = $1; } $debug and print "subkey value = $value unit ($unit)\n"; if (not defined $value){ $value = ''; } $ir{'l2'}{$servicename}{$key} = $value; }else{ $ir{'l2'}{$servicename}{$i} = $pair; } $i++; } @kvpairs; } # special case for icmp-ping if (defined $ir{'l1'}{'servicename'} and $ir{'l1'}{'servicename'} eq 'icmp-ping'){ my $serviceoutput = $ir{'l1'}{'serviceoutput'}; if ($serviceoutput =~ s/Packet loss = (\d+(\.\d+))// ){ $ir{'l2'}{'icmp-ping'}{'packet_loss'} = $1; } if ($serviceoutput =~ s/RTA = (\d+(\.\d+))// ){ $ir{'l2'}{'icmp-ping'}{'RTA'} = $1; } } }elsif(scalar @hostperfcols eq $nparts + 1){ map { my $v = $_; if ($hostperfcols[$i]){ $ir{'l1'}{$hostperfcols[$i]} = $v; } $i++; } @parts; }else{ die ("Wrong number of parts in pdf_nagios_perf ($nparts) for string ($buf)\n\n"); } $debug and print "IR \n\n" , Dumper(\%ir) , "\n\n\n"; # now linearize the ir. Generalize? if (defined $ir{'l1'}){ map { my $key = $_; # for now, no type or usage. my $name ; $name = $key; $data{$name} = {'data' => $ir{'l1'}{$key}, 'type' => 'unknown' , 'unit' => 'unknown' }; } keys %{$ir{'l1'}}; } if (defined $ir{'l2'}){ map { my $keyl1 = $_; # for now, no type or usage. map { my $keyl2 = $_; my $name ; $name = "$keyl1.$keyl2"; $data{$name} = {'data' => $ir{'l2'}{$keyl1}{$keyl2}, 'type' => 'unknown' , 'unit' => 'unknown' }; } keys %{$ir{'l2'}{$keyl1}}; } keys %{$ir{'l2'}}; } if (defined $ir{'l3'}){ map { my $keyl1 = $_; # for now, no type or usage. map { my $keyl2 = $_; map { my $keyl3 = $_; my $name ; $name = "$keyl1.$keyl2.$keyl3"; $data{$name} = {'data' => $ir{'l3'}{$keyl1}{$keyl2}{$keyl3}, 'type' => 'unknown' , 'unit' => 'unknown' }; }keys %{$ir{'l3'}{$keyl1}{$keyl2}}; } keys %{$ir{'l3'}{$keyl1}}; } keys %{$ir{'l3'}}; } if (defined $data{'timet'} and not defined $data{'time_t'} ){ $data{'time_t'} = $data{'timet'}; } $debug and print "DATA \n\n" , Dumper(\%data) , "\n\n\n"; # if we have a filter, use the filter. if ($pdf_nagios_perf_filterin and $data{'hostname'} and not $data{'hostname'}{'data'} =~ /\A$pdf_nagios_perf_filterin\Z/){ $debug and print "FILTERED OUT $data{'hostname'}{'data'} \n"; return undef; }elsif($pdf_nagios_perf_filterin_check and not $data{'servicename'}{'data'} =~ /\A$pdf_nagios_perf_filterin_check\Z/){ $debug and print "FILTERED OUT (check) $data{'servicename'}{'data'}\n"; return undef; }else{ # print "woooooo DATA !!!\n"; #my $debug = 1; $debug and print "DATA \n\n" , Dumper(\%data) , "\n\n\n"; } # fixup any 'data' with \s+ in them... cuz the gpss dosn't liek that much.. hell it dosn't even like strings much. map { my $key = $_; if ($data{$key}{'data'} =~ /\A\s*\Z/){ delete $data{$key}; }else{ $data{$key}{'data'} =~ s/\A\s+|\s+\Z//g; $data{$key}{'data'} =~ s/\s+/_/g; } my $newkey = $key; if ($newkey =~ s/\s+/_/g ) { $data{$newkey} = $data{$key}; delete $data{$key}; } } keys %data; \%data; } 1; } {#!/usr/bin/perl -w use strict; $persona_versions .= "fbsd_vmstat : Mon Oct 2 17:57:43 PDT 2006"; # # # gnuplot a snort.stat file a bunch of different ways. # # now it supports other file personas and # # # $cases = ' Mucho data. flags : -i -f -m -s -z (no z yet) -i (interupts) interrupt total rate irq1: atkbd0 1290 0 irq6: fdc0 11 0 irq14: ata0 105298 0 irq15: ata1 46 0 Total 353185334 1990 '; $persona{'fbsd_vmstat'} = { 'description' => 'a freebsd vmstat reader (flags : -i -f -m -s -z ) ' ,'ssfile' => '' # we require transcodeing ,'sample_mode' => 'poll' ,'figure_colums' => undef ,'parse_data_function' => \&pdf_fbsd_vmstat ,'poll_src' => 'cmd://vmstat -ifms' # z sucks. ,'poll_period' => 1.5 ,'delimiter' => '' # whole file. # ,'procedural_graphs' => \&fcode_gen_pid_statm ,'graphname' => { # most of these are generated now. 'avg' => { 'description' => 'avgs v time ' , 'txt' => [ 'avg1,avg2,avg3'] } ,'thread' => { 'description' => 'tasks vs time' , 'txt' => [ 'threads,running' ] } ,'spawnrate' => { 'description' => 'new pids / sec' , 'txt' => [ 'spawnrate=deltat(lastpid)' ] } # will probibly require wraping code # 2d ,'spawnratevsload' => {'description' => 'load averages vs spawn rate' , 'txt' => [ 'avg1,avg2,avg3' , 'spawnrate=deltat(lastpid)' ] } } }; # # # break it up into data # # sub pdf_fbsd_vmstat{ my ($buf) = @_; my %data = (); my $workbuf = $buf; my @lines; @lines = split /\n/ , $workbuf; my $line; my %dupes; my $mode = ''; while (@lines){ $line = shift @lines; if ($line =~ /\A(\d+)\s+(\S+forks|kthread creations),\s+(\d+) pages,average /){ # we'll handle averages, redundant data, we'll get better accuracy. my ($forks,$forkkind,$pages) = ($1, $2, $3); my ($name); $forkkind =~ s/ //g; # for obsd and kthreadcreate. $data{"$forkkind.cnt"} = {'data' => $forks, 'type' => 'cnt' , 'unit' => 'cnt' }; $data{"$forkkind.pages"} = {'data' => $pages, 'type' => 'pages' , 'unit' => 'cnt' }; }elsif($line =~ /\A\s*(.*?)\s+(\d+)\s+(\d+)(K|M|G|T)\s+(\S+)\s+(\d+)\s+/){ # the bulk of the -m case. my ($name,$inuse,$memused,$memusedmod, $high,$req) = ($1,$2,$3,$4,$5,$6); # FIXUP THE NAME $name =~ s/[ \/'-_]+/_/g; my ($bytes) = 0; $bytes = $memused; if ($memusedmod eq 'K'){ $bytes *= 1024; }elsif($memusedmod eq 'M'){ $bytes *= 2**20; }elsif($memusedmod eq 'G'){ $bytes *= 2**30; }elsif($memusedmod eq 'T'){ $bytes *= 2**40; } $data{"$name.inuse"} = { 'data'=> $inuse , 'type' => 'cnt' , 'unit' => 'cnt'} ; $data{"$name.bytes"} = { 'data'=> $bytes , 'type' => 'bytes' , 'unit' => 'cnt'} ; $data{"$name.req"} = { 'data'=> $req , 'type' => 'cnt' , 'unit' => 'cnt'} ; }elsif($line =~ /\A\s*(\d+) ([a-zA-Z()-]+)\s*\Z/){ # the bulk of the -s case. my ($val,$label) = ($1,$2); my ($type,$unit) = ('cnt','cnt'); my ($name) = $label; $name =~ s/[ ()-_]+/_/g; # this could probibly use some refineing. if ($label =~ /\Apages/){ $type = 'pages'; }elsif ($label =~ /\A.*\s+(switches|interrupts|calls|traps|faults)\s*\Z/){ $type = $1; } $data{$name} = { 'data' => $val , 'type' => $type , 'unit' => $unit }; }elsif($line =~ /\s+cache hits \((\d+)% pos + (\d+)% neg\) system (\d+)% per-directory/){ my ($pos,$neg,$sys) = ($1,$2,$3); $data{'name.cache.pos.hits'} = { 'data' => $pos , 'type' => 'cnt' , 'unit' => 'cnt' } ; $data{'name.cache.neg.hits'} = { 'data' => $neg , 'type' => 'cnt' , 'unit' => 'cnt' } ; $data{'name.cache.sys.hits'} = { 'data' => $sys , 'type' => 'cnt' , 'unit' => 'cnt' } ; }elsif($line =~ /\s+deletions (\d+)%, falsehits (\d+)%, toolong (\d+)%/){ # the tail end of -m case my ($del,$falseh,$tl) = ($1,$2,$3); $data{'name.cache.deletions'} = { 'data' => $del , 'type' => 'cnt' , 'unit' => 'cnt' } ; $data{'name.cache.false.hits'} = { 'data' => $falseh , 'type' => 'cnt' , 'unit' => 'cnt' } ; $data{'name.cache.toolong'} = { 'data' => $tl , 'type' => 'cnt' , 'unit' => 'cnt' } ; }elsif($line =~ /\A(\S+( \S+)?)\s+(\d+)\s+(\d+)\s*\Z/){ # -i cases. my ($label,$value) = ($1,$3); # again, we can probibly do rate better. my ($name) = $label; $name =~ s/[[ ()-_]+/_/g; # deduplicate if (defined $dupes{$name}){ $dupes{$name}++; $name = "${name}_$dupes{$name}"; }else{ $dupes{$name} = 1; # so the next one becomes 2 } $data{$name} = { 'data' => $value , 'type' => 'interrupts' , 'unit' => 'cnt' } ; }else{ print "Unable to match line ($line) from fbsd_vmstat\n"; } } if ($buf =~ /\A\s*(\d+(\.\d+)?)\s+(\d+(\.\d+)?)\s+(\d+(\.\d+)?)\s+(\d+)\/(\d+)\s+(\d+)\s*\Z/){ my ($run1,$run2,$run3,$threads_running,$threads,$lastpid) = ($1,$3,$5,$7,$8,$9); $data{'avg1'} = { 'data' => $run1, 'type' => 'usage' , 'unit' => 'float' }; $data{'avg2'} = { 'data' => $run2, 'type' => 'usage' , 'unit' => 'float' }; $data{'avg3'} = { 'data' => $run3, 'type' => 'usage' , 'unit' => 'float' }; $data{'running'} = { 'data' => $threads_running, 'type' => 'threads' , 'unit' => 'count' }; $data{'threads'} = { 'data' => $threads, 'type' => 'threads' , 'unit' => 'count' }; $data{'lastpid'} = { 'data' => $lastpid, 'type' => 'pid' , 'unit' => 'pid' }; } \%data; } 1; } use_persona($persona); # set the default one my $persona_txts = join " " , sort keys %persona; my $usagemsg; $usagemsg = < snort.stats.file => gpss # => gnuplot # # # becomes # whatever => Rondevuze location (file) => gpss # => gpss_transcoder => tmpfile => gnuplot # # With a poller it becomes. ( the -> means internal transfer ). # # whatever => Rondevuze location (ulr,file,cmd) => gpss # => poller -> gps_transcoder => tmpfile => gnuplot # # # A later invokation may come along and use the temp file directly # as a ssfile. Thus it needs to have enough metadata to redo what it has done. # # # --tcfn transcoded file name ( in case the user would a) like to keep a copy, and b, like to know where it is. # --tcf_append Append, don't overwrite the tmpfile. # --poll_src URI/FILE/cmd ( not yet sure how to parse that all out, seperate flags or what. # --poll_speed = HZ # --poll_period = seconds. # if (defined $ssfile and $ssfile and $used_complex_fcode ){ # we'll have to do a transcodeing. $debug and print "\n\nwe need to transcode\n\n\n"; $ptcstage = 1; } # polling_transcoder if ($ptcstage or (defined $persona{$persona}{'sample_mode'} and $persona{$persona}{'sample_mode'} eq 'poll') ){ # make sure we have all the info we need. #my $tcfn = "$$.gpss.tmpfile"; #my $ptcstage = 0; #my $poll_src = ''; #my $poll_period = 0; # aka, not. # we should have enough info to do a ptc. ptc_go(); # forks and goes of on it's own. filling the tcfn with data. # when we die, it dies. $ssfile = $tcfn; # the rest of this code really wants ssfile to be where it reads from. } if ($generate_examples){ # okay whatever, my ($html) = " Some $0 examples

Some $0 examples


    \n"; map { my ($gn) = $_; my ($fname); $fname = "example.$gn.png"; print "generating $fname\n"; my ($cmd); $cmd = "$0 " . (join " ", grep { "--generate_examples" ne $_ } @ARGV) ; $cmd .= " $gn --noloop --terminal png --outputfile $fname --title 'example $gn' "; print "cmd ($cmd)\n"; $html .= "
  • $fname $cmd

    \n\n"; print `$cmd`; print "\n\n"; } sort keys %graphname; $html .= "

\n\n"; open OUTF , ">examples.html" or die "Unable to open output html file (exmaples.html)\n\n"; print OUTF $html; close OUTF; exit(); } } # # # take the fcode text and generate/ lookup expressions for it. # # sub install_fcode{ my ($arg) = @_; # print "install_fcode ($arg)\n"; # $arg is an fcode expression bound for the Axes. my ($codetxt,@clist); ($codetxt,@clist) = do_expression($arg); push @axes, \@clist; # print "axes : " , Dumper(\@axes) , "\n\n"; # fixup clist so it's all columized and such. # sub columize_datahash # fix, find the right units, columize_datahash( { map { ($_ , { 'data' => '' , 'type' => 'unknown' , 'units' => 'unkown' } ) } @clist }) ; if($codetxt){ # print "functionalizing codetxt\n"; my $ct = ' sub { my ($state) = @_; '.$codetxt.' }; '; my $cfunc; $cfunc = eval $ct ; if ($@){ die "Internal error cuased by bad parse of ($arg) resulting in ($ct)\n"; } #print "cfunc ($cfunc)\n"; push @axes_funcs, $cfunc; # call each before the writing. } } my %vff; %vff = map { # valid fcode functions ($_ , 1) ; # aka fcode_XXXXXX } qw ( min max sum count avg floor ceil abs dt deltat delta d nroot exp log expn logn); sub fcode_nroot{ my $func = "nroot"; die("$func Not implimented.. fix\n"); } sub fcode_exp{ my $func = "exp"; die("$func Not implimented.. fix\n"); } sub fcode_log{ my $func = "log"; die("$func Not implimented.. fix\n"); } sub fcode_expn{ my $func = "expn"; die("$func Not implimented.. fix\n"); } sub fcode_logn{ my $func = "logn"; die("$func Not implimented.. fix\n"); } # maybe all these funcs should be turned inside out and joined. # for example, all the single argument ones, could just be op_1('abs') for example. # but for now, trading horses in midstream is.... lessoptimal. # like binop. # # NAN'z /undefs are ignored. # sub fcode_min{ my $func = "min"; my ($state,$sk,$resultkey,@args) = @_; my ($rv) ; $debug and print "begin fcode_$func : args ($state,$sk,$resultkey,@args)\n"; map { my $arg = $_; my $argv = $$state{'datahash'}{$arg}{'data'}; if( defined $argv){ if (defined $rv){ if($argv < $rv){ $rv = $argv; } }else{ $rv = $argv; } } } @args; $$state{'datahash'}{$resultkey}{'data'} = $rv; $debug and print "fcode_$func : result $resultkey => ($rv)\n"; } #$codetxt .= "\nfcode_binop(\$state,$statekey,'$newname', '$op' , '$lhs' , '$rhs');\n"; sub fcode_binop{ my $func = "binop"; my ($state,$sk,$resultkey,$op,@args) = @_; my ($rv) ; # my $debug = 1; $debug and print "begin fcode_$func : args ($state,$sk,$resultkey,$op,@args)\n"; my ($lhs,$rhs) = ( $args[0], $args[1] ); if (defined $lhs and defined $rhs){ ($lhs,$rhs) = ($$state{'datahash'}{$lhs}{'data'} , $$state{'datahash'}{$rhs}{'data'} ); if (defined $lhs and defined $rhs){ $debug and print " $lhs $op $rhs \n"; if ($op eq '-'){ $rv = $lhs - $rhs; }elsif($op eq '+'){ $rv = $lhs + $rhs; }elsif($op eq '*'){ $rv = $lhs * $rhs; }elsif($op eq '/'){ if ($rhs){ eval { # make divide by zero non fatal $rv = $lhs / $rhs; }; }else{ # not defined.. } }else{ die "Not understood op in fcode_binop ($op) from ($lhs $op $rhs) , dieing\n"; } }else{ $debug and print "undefined datavalues\n"; } }else{ $debug and print "failed args\n\n"; } $$state{'datahash'}{$resultkey}{'data'} = $rv; $debug and print "fcode_$func : result $resultkey => ($rv)\n"; } sub fcode_max{ my $func = "max"; my ($state,$sk,$resultkey,@args) = @_; my ($rv) ; $debug and print "begin fcode_$func : args ($state,$sk,$resultkey,@args)\n"; map { my $arg = $_; my $argv = $$state{'datahash'}{$arg}{'data'}; if( defined $argv){ if (defined $rv){ if($rv < $argv){ $rv = $argv; } }else{ $rv = $argv; } } } @args; $$state{'datahash'}{$resultkey}{'data'} = $rv; $debug and print "fcode_$func : result $resultkey => ($rv)\n"; } # sum / count sub fcode_sum{ my $func = "sum"; my ($state,$sk,$resultkey,@args) = @_; my ($rv) ; $debug and print "begin fcode_$func : args ($state,$sk,$resultkey,@args)\n"; map { my $arg = $_; my $argv = $$state{'datahash'}{$arg}{'data'}; if( defined $argv){ if (defined $rv){ $rv += $argv; }else{ $rv = $argv; } } } @args; $$state{'datahash'}{$resultkey}{'data'} = $rv; $debug and print "fcode_$func : result $resultkey => ($rv)\n"; } # # sub fcode_count{ my $func = "count"; my ($state,$sk,$resultkey,@args) = @_; my ($rv) ; $debug and print "begin fcode_$func : args ($state,$sk,$resultkey,@args)\n"; map { my $arg = $_; my $argv = $$state{'datahash'}{$arg}{'data'}; if( defined $argv){ $rv++; } } @args; $$state{'datahash'}{$resultkey}{'data'} = $rv; $debug and print "fcode_$func : result $resultkey => ($rv)\n"; } # sub fcode_avg{ my $func = "avg"; my ($state,$sk,$resultkey,@args) = @_; my ($rv); my ($n); $debug and print "begin fcode_$func : args ($state,$sk,$resultkey,@args)\n"; map { my $arg = $_; my $argv = $$state{'datahash'}{$arg}{'data'}; if( defined $argv){ $n++; if (defined $rv){ $rv += $argv; }else{ $rv = $argv; } } } @args; if (defined $n){ $rv = $rv/$n; } $$state{'datahash'}{$resultkey}{'data'} = $rv; $debug and print "fcode_$func : result $resultkey => ($rv)\n"; } # # # sub fcode_floor{ my $func = "floor"; my ($state,$sk,$resultkey,@args) = @_; my ($rv) ; $debug and print "begin fcode_$func : args ($state,$sk,$resultkey,@args)\n"; my $arg = shift @args; my $argv = $$state{'datahash'}{$arg}{'data'}; if(defined $argv){ $rv = POSIX::floor($argv); } $$state{'datahash'}{$resultkey}{'data'} = $rv; $debug and print "fcode_$func : result $resultkey => ($rv)\n"; } # # # sub fcode_ceil{ my $func = "ceil"; my ($state,$sk,$resultkey,@args) = @_; my ($rv) ; $debug and print "begin fcode_$func : args ($state,$sk,$resultkey,@args)\n"; my $arg = shift @args; my $argv = $$state{'datahash'}{$arg}{'data'}; if(defined $argv){ $rv = POSIX::ceil($argv); } $$state{'datahash'}{$resultkey}{'data'} = $rv; $debug and print "fcode_$func : result $resultkey => ($rv)\n"; } # # # sub fcode_abs{ my $func = "abs"; my ($state,$sk,$resultkey,@args) = @_; my ($rv) ; $debug and print "begin fcode_$func : args ($state,$sk,$resultkey,@args)\n"; my $arg = shift @args; my $argv = $$state{'datahash'}{$arg}{'data'}; if(defined $argv){ $rv = abs($argv); } $$state{'datahash'}{$resultkey}{'data'} = $rv; $debug and print "fcode_$func : result $resultkey => ($rv)\n"; } # # # sub fcode_literal{ my $func = "literal"; my ($state,$sk,$resultkey,@args) = @_; my ($rv) ; $debug and print "begin fcode_$func : args ($state,$sk,$resultkey,@args)\n"; my $arg = shift @args; if(defined $arg){ $rv = $arg; } $$state{'datahash'}{$resultkey}{'data'} = $rv; $debug and print "fcode_$func : result $resultkey => ($rv)\n"; } # # # delta / t # # # Adding some wrapping code, there is no good way to do this. # sub fcode_dvdt{ fcode_deltat(@_); } sub fcode_dt{ fcode_deltat(@_); } sub fcode_deltat{ my $func = "deltat"; my ($state,$sk,$resultkey,@args) = @_; my ($rv) ; $debug and print "begin fcode_$func : args ($state,$sk,$resultkey,@args)\n"; my $key; $key = shift @args; my ($now_t,$then_t,$dt); $now_t = $$state{'datahash'}{'time_t'}{'data'}; $then_t = $$state{'lastdata'}{'time_t'}{'data'}; $debug and print " now_t ($now_t) then_t ($then_t)\n"; if (defined $then_t){ $dt = $now_t - $then_t; if ($dt > 0){ my ($now_v,$then_v,$dv); $now_v = $$state{'datahash'}{$key}{'data'}; $then_v = $$state{'lastdata'}{$key}{'data'}; if (defined $now_v and defined $then_v){ # fix, check numeric? $dv = $now_v - $then_v; $rv = $dv/$dt; $debug and print "dv ($dv) dt ($dt) dv/dt ($rv) \n"; }else{ print "WARNING: deltat now_v ($key) ($now_v) and then_v ($then_v)\n"; } }else { print "WARNING: time is flowing backwards...($key) ($dt) ($now_t, $then_t)\n"; } }else{ # it's no defined. # $rv = 'nan'; but perl dosn't understand nan... so undef. } $$state{'datahash'}{$resultkey}{'data'} = $rv; $debug and print "fcode_$func : result $resultkey => ($rv)\n"; } # # # delta, no t # # sub fcode_d{ fcode_delta(@_); } sub fcode_dv{ fcode_delta(@_); } sub fcode_delta{ my $func = "delta"; my ($state,$sk,$resultkey,@args) = @_; my ($rv) ; $debug and print "begin fcode_$func : args ($state,$sk,$resultkey,@args)\n"; my $key; $key = shift @args; my ($now_v,$then_v,$dv); $now_v = $$state{'datahash'}{$key}{'data'}; $then_v = $$state{'lastdata'}{$key}{'data'}; if (defined $now_v and defined $then_v){ # fix, check numeric? $dv = $now_v - $then_v; $rv = $dv; $debug and print "dv ($dv) dv ($rv) \n"; } $$state{'datahash'}{$resultkey}{'data'} = $rv; $debug and print "fcode_$func : result $resultkey => ($rv)\n"; } sub fcode_assign{ my $func = "assign"; my ($state,$sk,$resultkey,@args) = @_; my ($rv) ; $debug and print "begin fcode_$func : args ($state,$sk,$resultkey,@args)\n"; my $key; $key = shift @args; $rv = $$state{'datahash'}{$key}{'data'}; $$state{'datahash'}{$resultkey}{'data'} = $rv; $debug and print "fcode_$func : result $resultkey => ($rv)\n"; } # # expressions : # swapin + swapout # swapin.d + swapout.d # swapin.d.dt + swapin.d.dt ( suffixes work in time). # # max(cpu0.user.d.dt ; cpu0.wait.d.dt ; cpu0.sys.d.dt) ( functions work at the same time) arguments seperated by ;'z (this may change back to ,'z # min( avg( floor( ceil( sqrt( nroot( # # # Internaly, each function returns ($key, $value) pairs. # as the funcions get used, both the values, and keys get munged. # so arugment list max (kv,kv[,kv]), becme lists... that wana be hashes. # # Our end result for any given axis is a kv list, that is each of the axis. # wraping something in ():foo tells the parser to name the results of the expression as collum 'foo' # # or maybe set them up as modified functions, foo:() ... yah that's more naturalllkinda. # and change : to = # # (deltat(swapin)):swapinps , swapout , sum( swapin , swapout ):swap.total # # swapinps=(deltat(swapin)) , swapoutps=(deltat(swapout)) , swap.total.ps=(sum(swapinps , swapoutps)) # # # # # # # sub do_expression{ my ($txt) = @_; my (@result_keylist) = (); my ($origtxt) = $txt; my ($codetxt) = ""; my $debug = 1; $debug and print "do_expression ($txt)\n"; my $done =0; my $LABEL = '[a-zA-Z0-9\.\_]+'; my $lasttxt = "$txt "; # different. my $statekey = 0; # now we can use an array.. but do we want to? while(not $done){ if ($txt =~ /\A(\s*($LABEL)\s*(,\s*($LABEL)\s*)*)\s*\Z/ ){ # what we want, a list of columz' # print "wooooho done!\n"; $done = 1; last; }else{ # print "no done yet ($txt)\n"; } $used_complex_fcode = 1; # if we have an SS file (delimited, simple colums gnuplotable file) yet we try doing math on it... we need to transcode, # this is the ugly hook to get it done. if ($lasttxt eq $txt){ die "Failure to parse ($origtxt) got to ($txt) and got stuck\n"; }else{ $lasttxt = $txt; $debug and print" txt ($txt)\n"; } # # # literals # # # # scientific notation is not yet supported. # my $literalname; if ($txt =~ s/(? 'minus' , '+' => 'plus' , '*' => 'mult' , '/' => 'div' ); #fix, migrate this loop invariant up. my ($born); # = tmp_func_result_name("binop_$binopnames{$op}" , "$lhs, $rhs" ); if ($txt =~ s/\b\s*($LABEL)\s*(\-|\+|\*|\/)\s*($LABEL)\b\s*(?!(\(|\=))/{ $born = tmp_func_result_name("binop_$binopnames{$2}" , "$1,$3"); }/e){ my ($lhs,$op,$rhs) = ($1,$2,$3); # note, state holds datahash, lastdatahash, and all the function states. $debug and print "binop ($lhs $op $rhs)\n"; $codetxt .= "fcode_binop(\$state,$statekey,'$born', '$op' , '$lhs' , '$rhs');\n"; $statekey ++; } # # assignment A=B # if ($txt =~ s/\b\s*($LABEL)\s*=\s*($LABEL)\b\s*(?!(\(|\=))/ $1 /){ my ($newname,$oldname) = ($1,$2); # note, state holds datahash, lastdatahash, and all the function states. $debug and print "asignment $newname $oldname\n"; $codetxt .= "fcode_assign(\$state,$statekey,'$newname','$oldname');\n"; $statekey ++; } # # foo=func( args ) ; # my $tfrn; if ($txt =~ s/\b\s*($LABEL)\s*\((\s*$LABEL\s*(,\s*$LABEL\s*)*)\)/{$tfrn = tmp_func_result_name($1,$2); }/e){ # fix, a function for turning a list of args into a stringie thingie my ($funcname,$args) = ($1,$2); # my ($tfrn) = tmp_func_result_name($1,$2); print " function ( funcname, args) ($funcname,$args) \n"; $args =~ s/,/','/g; $args = "'$args'"; $args =~ s/\s+//g; $codetxt .= "fcode_$funcname(\$state,$statekey,'$tfrn', $args); \n"; $statekey++; print "tfrn ($tfrn)\n"; } $debug and print "txt ($txt)\n"; # # parenthisis ( N ) (though might not be quite perfectly right with percidences at this point. # if($txt =~ s/(? $columname{$b} } keys %columname; print "\n\n\n"; print "Graphs : \n"; print "-" x 80 ."\n"; my ($axiscnt) = 0; #" . (join " " , sort keys %graphname )."\n\n"; map { my ($gn) = $_; # this is all messy... if (defined $graphname{$gn}{'axes'}){ if (defined $graphname{$gn}{'description'}){ printf "%-15s : %s\n" , $gn, $graphname{$gn}{'description'}; }else{ if ($axiscnt != @{$graphname{$gn}{'axes'}}){ $axiscnt = @{$graphname{$gn}{'axes'}}; printf "%19s\n","$axiscnt D"; } printf "%-15s : %s" , $gn , ( join " " , map { my ($array) = $_; join "," , @{$array}; } @{$graphname{$gn}{'axes'}} ) . "\n"; } }elsif(defined $graphname{$gn}{'txt'}){ if (defined $graphname{$gn}{'description'}){ printf "%-15s : %s\n" , $gn, $graphname{$gn}{'description'}; }else{ printf "%-15s : %s\n" , $gn, (join " | " , @{$graphname{$gn}{'txt'}}); } }else{ if (defined $graphname{$gn}{'description'}){ printf "%-15s : %s\n" , $gn, $graphname{$gn}{'description'}; }else{ printf "%-15s : %s\n" , $gn, "unknown"; } } } sort { my ($cnta,$cntb); $cnta = $#{$graphname{$a}{'axes'}} ; $cntb = $#{$graphname{$b}{'axes'}}; my ($ret) = 0; $ret = $cnta <=> $cntb; if (not $ret){ $ret = $a cmp $b; } $ret; } keys %graphname; print "\n\n\n"; if (defined $persona{$persona}{'notes'}){ print "Notes: on usage \n$persona{$persona}{'notes'}\n\n"; } print "\n\n\n"; } sub show_personalities{ print "\n\nPersonalities : \n\n"; printf "%-15s %-20s %s\n" , "selected" , "personality" , "description"; print "-" x 120, "\n"; map { my $k = $_; my $desc = ''; my $indent = " "; if ($k eq $persona){ $indent = " current " ; } if (defined $persona{$k}{'description'}){ $desc = $persona{$k}{'description'}; } printf "%-15s %-20s %s\n" , $indent ,$k , $desc; } sort keys %persona; print "\n\n"; } sub dograph{ # # open us up a gnuplot handle, and get it kinda happy. # if (0){ print "PENDING ON ME!\n"; sleep 20 ; print "FOR THE LOVE OF GOD\n\n"; exit(); } my ($pwd); $pwd = `pwd`; $pwd =~ s/\n//; my $gp; $| =1; #line buffer the files. if (defined $persona{$persona}{'sample_mode'} and $persona{$persona}{'sample_mode'} eq 'poll'){ sleep 0.2; # let a poller get a little bit of a head start... if there is one. # this won't really work, we need to wait .2 + poll_period. sleep $poll_period; } if (not $showme and not open $gp , "|$gnuplot $gnuplot_args" ){ die "Unable to open gnuplot ($gnuplot $gnuplot_args) on the end of a pipe, quitting.\n"; } # or die "unable to gnuplot\n"; sleep 0.1; # the gnuplot shouldn't have a problem, but it might, so slow down a little. # figure out if we are doing a different type of terminal or not. my ($interactive) = 0; # this could be smarter, to deal with options to the terminals. if (defined $interactive_terminals{$terminal}){ $interactive = 1; } my ($gpstartup) = "\n\n\n"; my ($gpreoccur_pre,$gpreoccur_post) = ('',''); if ($interactive){ $gpstartup .= "set mouse\n"; }else{ $gpreoccur_pre .= "set output '$outputfile'\n"; } $gpstartup .= "set terminal $terminal\n"; $gpstartup .= "set grid\n"; if ($title){ $gpstartup .= "set title '$title'\n"; }else{ $gpstartup .= "set title \"$pwd : $argstxt\"\n"; } # print "Dumping colum2key " , Dumper(\%columnum2key) ,"\n\n"; # used for transcoding. # print "Dumping columname " , Dumper(\%columname) ,"\n\n"; # used for transcoding. # $columname{$key} = $ncolums; $debug and show_list(); if ($showme){ print "# gnuplot code \n\n"; print "# gpstartup\n"; print "$gpstartup\n\n"; print "#gnuplot_plot_options (adendum)\n"; print "$gnuplot_plot_adendum\n\n"; # set once print "# gpreoccur_pre\n\n"; print "\n$gpreoccur_pre\n\n"; print "# gen_gnuplot_txt()\n"; print gen_gnuplot_txt(); print "\n\n#gpreoccur_post\n\n"; print "$gpreoccur_post\n\n"; exit(); }else{ print $gp "$gpstartup \n\n"; print $gp "$gnuplot_plot_adendum\n\n"; # set once } my ($firsttime)=1; my ($oldmtime) = 0; my ($done) = 0; # # # my ($old_gpt) = ''; while (not $done){ my ($gpt); # print $gp "plot $range $txt\n"; my($newdata) =0; my (@fstat); @fstat = stat($ssfile); # stat polling on our target file. (possibly transocded)? if ($fstat[9] != $oldmtime){ $newdata = 1; $oldmtime = $fstat[9]; } if ($newdata or $firsttime){ $gpt = gen_gnuplot_txt(); $gpt = "$gpreoccur_pre\n\n$gpt\n\n$gpreoccur_post\n\n"; # replot requires the original prlot to work... which sometimes it may not due to lack of data. if (0 and ($gpt eq $old_gpt and not ($gpreoccur_pre or $gpreoccur_post))){ if ($debug){ print "reploting!\n"; } print $gp "\nreplot\n"; }else{ if ($debug) { print "gpt ($gpt)\n"; } print $gp $gpt; $old_gpt = $gpt; } } binmode $gp; # flush the buffer. if ($loop){ if (0< $looptime ){ sleep $looptime; } }elsif($nodatanoloop){ # yes this is a race condition... if ($newdata or $firsttime ){ if (0 < $looptime){ sleep $looptime; } }else{ $done =1; } }else{ $done =1; } $firsttime = 0; } } sub main{ args(@ARGV); if ($batchmode){ sleep 0x7ffffff; # any transcoders should be running, pushing data into our file... if we dont' want to use it... well okay. }else{ dograph(); } exit; } # # # regenerate the gnuplot command text. # # can we get rid of the cat / tail / tr thing? # sub gen_gnuplot_txt{ my ($txt); $txt = ''; my ($dcmd); if (0){ # at leas hopefully it can go. $dcmd = "< cat $ssfile | " . ' tr "," " " ' ; if ($tailn){ $dcmd = "< tail -$tailn $ssfile | " . ' tr "," " " ' ; } }else{ $dcmd = "$ssfile"; # if the header is dumped more often we'll need to add a grep -v '#' filter. if ($tailn){ $dcmd = "< tail -$tailn $ssfile "; } } # 'ss' using XX title my ($axescnt); $axescnt = @axes; if ($debug ){ print "axescnt ($axescnt)\n"; print "axes " . ( join " " , @axes ) ."\n\n"; } my ($plottxt,$preambletxt) = ('',''); if ($axescnt == 1 ){ # 1d vs time. #note, this can now be folded into a sub case of 2d # make sure we are in time mode. ( as an option we might want to support N, where N is the data point number); $preambletxt .= "set xdata time \n" ."set timefmt '%s' \n\n"; # generate things like : '$dcmd' using 1:2 title 'foo' with linespoints # print STDERR "colname, " , Dumper (\%columname ) , "\n\n"; $plottxt = "plot $range " . (join "\\\n," , map { my ($colname) = $_; $ylabels{$columtype{$colname}} = 1; "'$dcmd' using 1:$columname{$colname} title '$colname' with linespoints " ; } @{$axes[0]}) . "\n\n"; my ($ylabel); $ylabel = join "," , keys %ylabels; if ($ylabel =~ /,/){ warn("The Y axis has more than one label on it ($ylabel), the units may not be correct and the gaph may be wrongish"); } if ($ylabel =~ /time/){ $preambletxt .= "set ydata time\n" ."set timefmt '%s'\n"; } $preambletxt .= "set xlabel 'time_t'\n" ."set ylabel '$ylabel'\n"; $txt .= $preambletxt . $plottxt; }elsif ($axescnt == 2 ){ # now this can be a special case of the next one. If we really really wanted to we could probibly turn this into a recursive thing... # 2d x vs y # kinda the same thing as 2d, but with to uses. # the big question is, join or not join.... should (A,B,C) (D,E,F) produce 3 graphs, A x D , B x E , C x F , or 9 graphs A x D , A x E , A x F , B x D , B x E , B x F , A x D , AxE ,AxF ? # I think the later will be simpler. # for now, join! my (@yfractions) = (); # produce things like [ '12' , 'open_sessions' ] @yfractions = map { my ($colname_axis_y) = $_; $ylabels{$columtype{$colname_axis_y}} =1 ; [ $columname{$colname_axis_y} , $colname_axis_y ]; } @{$axes[1]}; map { my ($colname_axis_x) = $_; $xlabels{$columtype{$colname_axis_x}} =1 ; } @{$axes[0]}; my ($mywith) = 'points'; if ($with){ $mywith = $with; }else{ if (defined $xlabels{'time_t'} or defined $ylabels{'time_t'}){ $mywith = 'linespoints'; } } $plottxt = "plot $range " . ( join "\\\n," , map { my ($colname_axis_x) = $_; my ($plotfrag); $xlabels{$columtype{$colname_axis_x}} =1 ; $plotfrag = join "\\\n," , map { my ($yf) = $_; "'$dcmd' using $columname{$colname_axis_x}:$$yf[0] title '$colname_axis_x - $$yf[1]' with $mywith"; } @yfractions; } @{$axes[0]}) . "\n\n"; my ($ylabel,$xlabel); $ylabel = join "," , keys %ylabels; $xlabel = join "," , keys %xlabels; if ($ylabel =~ /,/){ warn("The Y axis has more than one label on it ($ylabel), the units may not be correct and the gaph may be wrongish"); } if ($ylabel =~ /time/){ $preambletxt .= "set ydata time\n" ."set timefmt '%s'\n"; }else{ $preambletxt .= "set ydata\n"; } if ($xlabel =~ /,/){ warn("The X axis has more than one label on it ($ylabel), the units may not be correct and the gaph may be wrongish"); } if ($xlabel =~ /time/){ $preambletxt .= "set xdata time\n" ."set timefmt '%s'\n"; }else{ $preambletxt .= "set xdata\n"; } $preambletxt .= "set xlabel '$xlabel'\n" ."set ylabel '$ylabel'\n"; $txt .= $preambletxt . $plottxt; }elsif ($axescnt == 3 ){ # 3d x vs y vs z # "Same thing. same as the first! A whole lot louder but a whole lot worse! # # for now, join! my (@zfractions) = (); # produce things like [ '12' , 'open_sessions' ] @zfractions = map { my ($colname_axis_z) = $_; $zlabels{$columtype{$colname_axis_z}} =1 ; [ $columname{$colname_axis_z} , $colname_axis_z ]; } @{$axes[2]}; my (@yfractions) = (); # produce things like [ '12:27' , 'open_sessions - ncpu' ] @yfractions = map { my ($colname_axis_y) = $_; $ylabels{$columtype{$colname_axis_y}} =1 ; # [ $columname{$colname_axis_y} , $colname_axis_y ]; map { my ($zf) = $_; [ "$columname{$colname_axis_y}:$$zf[0]" , "$colname_axis_y - $$zf[1]" ] ; } @zfractions; } @{$axes[1]}; map{ my ($colname_axis_x) = $_; $xlabels{$columtype{$colname_axis_x}} =1 ; } @{$axes[0]}; my ($mywith) = 'points'; if ($with){ $mywith = $with; }else{ if (defined $xlabels{'time_t'} or defined $ylabels{'time_t'} or defined $zlabels{'time_t'}){ $mywith = 'linespoints'; } } $plottxt = "splot $range " . ( join "\\\n," , map { my ($colname_axis_x) = $_; my ($plotfrag); $plotfrag = join "\\\n," , map { my ($yf) = $_; "'$dcmd' using $columname{$colname_axis_x}:$$yf[0] title '$colname_axis_x - $$yf[1]' with $mywith"; } @yfractions; } @{$axes[0]}) . "\n\n"; my ($xlabel,$ylabel,$zlabel); $xlabel = join "," , keys %xlabels; $ylabel = join "," , keys %ylabels; $zlabel = join "," , keys %zlabels; ##X if ($xlabel =~ /,/){ warn("The X axis has more than one label on it ($ylabel), the units may not be correct and the gaph may be wrongish"); } if ($xlabel =~ /time/){ $preambletxt .= "set xdata time\n" ."set timefmt '%s'\n"; }else{ $preambletxt .= "set xdata\n"; } ##Y if ($ylabel =~ /,/){ warn("The Y axis has more than one label on it ($ylabel), the units may not be correct and the gaph may be wrongish"); } if ($ylabel =~ /time/){ $preambletxt .= "set ydata time\n" ."set timefmt '%s'\n"; }else{ $preambletxt .= "set ydata\n"; # my version of gnuplot is cranky. } ##Z if ($zlabel =~ /,/){ warn("The Z axis has more than one label on it ($ylabel), the units may not be correct and the gaph may be wrongish"); } if ($zlabel =~ /time/){ $preambletxt .= "set zdata time\n" ."set timefmt '%s'\n"; }else{ $preambletxt .= "set zdata\n"; } $preambletxt .= "set xlabel 'x $xlabel'\n" ."set ylabel 'y $ylabel'\n" ."set zlabel 'z $zlabel'\n"; $txt .= $preambletxt . $plottxt; }else{ usage("Wrong number of axes ($axescnt) try asking for some colums."); # die( "Wrong number of axes ($axescnt)"); } } # # # Poller transcoder. # # sub ptc_go{ $debug and print "PTC_GO!\n"; my $pid; # perl profileer dosn't seem to handle forks well. if (not $profileing){ $pid = fork(); if (not defined $pid){ die "Failed to fork!! Quiting ($!)\n"; } if ($pid){ #parent if ($debug){ print "forked successfully!\n"; } return; } } # we are now alone with our work... our sweet lushious work. #my $tcfn = "$$.gpss.tmpfile"; #my $ptcstage = 0; #my $poll_src = ''; #my $poll_period = 0; # aka, not. # we should have enough info to do a ptc. # ptc_go(); # forks and goes of on it's own. filling the tcfn with data. # when we die, it dies. # We are a poller (though it may be truned off) # and a transcoder. ( though it may be turned off) # basic algorthm is read some data, work on the data, write some data. # repeat untell dead, atempting to keep to poll_period ,or if poll_period = 0... blocking/spining. my $mode = ">"; if ($tcf_append){ $mode= ">>"; }; if (open OUTFILE , "$mode$tcfn"){ # sweeet $debug and print "opened TCFN for writing ! ($tcfn)\n"; # this dosn't seem to be flushing right... select OUTFILE; $| =1; select STDOUT; OUTFILE->autoflush(1); }else{ print STDERR "ERROR opening ($tcfn) ($!) quiting \n"; exit; } my $tc = 1; # fix, my $state = {}; my $lastdata = {}; #the datahash before this one. my $t = time; my $last_t = $t - $poll_period; my $dt = 0.1; my $avgdt = $poll_period; my $avgsleeptime = $poll_period; my $oldnkeys = 0; # my $debug = 1; while (1){ my $ppid; if (($ppid =getppid()) == 1 ){ exit(1); # our parent has died... we are probibly nolonger needed. } # note these are not a reliable way to detect parent failure. FIX. Maybe a shared pipe or something. if (not kill 0 , $ppid){ $debug and print "ppid ($ppid) seems to be gone.\n"; exit(2); } my ($buffer,$metadata); ($buffer,$metadata) = get_buffer(); # may also return some meta data # respects globals as arguments # by this time all the metadata should be setup. ( $t = time(); # we have two cases. # 1) we are just polling, no transcoding. # 2) we are just transcoideing, # note, to add time, we'll need to transcode as well. my $skip_this_one = 0; # to allow some of the functions to return junk . # call the transcoder, if ($tc){ my $datahash; my $replace_time = 1; # print "buffer ($buffer)\n"; $debug and print "Transcoder ($tc)\n"; if ($metadata){ #$lastdata = deepcopyds($datahash); # dosn't work for using poll_src gpss:// maybe if it so that's the only one that pays. # %$datahash = %$metadata; $datahash = deepcopyds($metadata); $replace_time = 0; }elsif (defined $persona{$persona}{'parse_data_function'} ){ if (defined $persona{$persona}{'changed_triggered'} and $persona{$persona}{'changed_triggered'} ) { # only give data if the values have changed... run length encoding sorta. if (not defined $persona{$persona}{'changed_triggered_oldbuf'} or $persona{$persona}{'changed_triggered_oldbuf'} ne $buffer){ $persona{$persona}{'changed_triggered_oldbuf'} = $buffer; }else{ $skip_this_one =1 ; $debug and print "SUPPRESSING redundant data\n"; } } if (not $skip_this_one){ $datahash = &{$persona{$persona}{'parse_data_function'}}($buffer); if (defined $datahash and defined $$datahash{'time_t'}){ $replace_time = 0; # paramiterize? } } if (not defined $datahash){ $skip_this_one = 1; } }else{ #$persona{'snort_stats'}{'figure_colums'} = \&figure_colums_snort_stats; # argument is a filename #$datahash = &{$persona{$persona}{'parse_data_function'}}($buffer); # seperator # fix, this should be more general than 'seperator' pobibly reuse 'figure_colums' but it can't right now becuase # ... mabe not. the only thing we'll have ss files for are simple files gnuplot can just snorkup. # note we kinda lose the ability to react to new colums.... not sure if it's worth putting in. my @data; @data = split /$persona{$persona}{'seperator'}/ , $buffer; my %hash; %hash = map { my $n = $_ ; # print "columnum ($n)\n"; ($columnum2key{$n +1 } , {'data' => $data[$n ] } ); } 0 .. $#data; $replace_time = 0; $datahash = \%hash; } # any data elements that don't have colums, should be assigned colums # $columname{$_} = $cnt; # $columtype{$_} = $types[$cnt-1]; columize_datahash($datahash); # print "replaceing time! ($replace_time) \n"; if ($replace_time){ $$datahash{'time_t'} = {'data' => $t , 'type' => 'time_t' , 'unit' => 'seconds' }; } # print "1dumper : ", Dumper($$datahash{'time_t'}) , "\n\n"; #print "1dumper : ", Dumper($$lastdata{'time_t'}) , "\n\n"; if (not $skip_this_one){ my $i = 0; # any calculation phases. while (defined $axes_funcs[$i]){ my $sub_state; $sub_state = $$state{$i}; $$sub_state{'datahash'} = $datahash; $$sub_state{'lastdata'} = $lastdata; &{$axes_funcs[$i]}( $sub_state ); $datahash = $$sub_state{'datahash'}; $$state{$i} = $sub_state; $i++; } %{$lastdata} = %{$datahash}; # surfaceish copy... hoepfully enough. # print "2dumper : ", Dumper($$datahash{'time_t'}) , "\n\n"; # print "2dumper : ", Dumper($$lastdata{'time_t'}) , "\n\n"; # sucks we do it each time... # kinda be nice to remember what that was for... $columname{'time_t'} = 1; $columtype{'time_t'} = 'time_t'; $columnum2key{1} = 'time_t'; my $savedtime = $$datahash{'time_t'}{'data'}; # write out the data in colum order. my $n =2; #1 is time my $str; my $names = "time_t "; my $key; if ($replace_time){ $str = "$t "; }else{ $str = "$savedtime "; } $debug and print "NORMAL TC TIME ($str)\n"; # # figure out if we need to print a header. # my $nkeys; $nkeys = int keys %columnum2key; if ($nkeys != $oldnkeys ){ $oldnkeys = $nkeys; $debug and print "stamping new header ($nkeys) ($oldnkeys)\n"; my $header = sprint_gpssfile_header($datahash,$buffer); print OUTFILE $header; } # we already dealt with it. .... just kidding! # delete $$datahash{'time_t'}; $str .= sprint_datahash($datahash); # print "str ($str)\n"; print OUTFILE $str, "\n"; }# skipping this one, cuz it sucked. }else{ $debug and print "no Transcoder ($tc)\n"; print OUTFILE $buffer; } # $poll_period ; is our goal. $dt = $t - $last_t; $avgdt = ($avgdt * 9 + $dt) / 10; # decaying average. # $avgsgleeptime = ($avgsleeptime *9 + ( $poll_period - ($avgdt - $avgsleeptime)) ) / 10; # decaying average; $avgsleeptime = ($avgsleeptime *9 + ( $poll_period - ($dt - $avgsleeptime)) ) / 10; # decaying average; # ^ non sleeptime ( using avgdt slows down convergance 'too much' my $sleeptime = $avgsleeptime; if ($sleeptime > 0 ){ sleep ($sleeptime); } $debug and print "sleeptime ($sleeptime) dt ($dt) avgdt ($avgdt) poll_period($poll_period) ($t)\n"; $last_t = $t; } } # sub sprint_gpssfile_header{ my ($datahash,$buffer) = @_; my $header = ""; if (not defined $buffer){ $buffer = "# no example buffer given\n"; } my $now = time(); my $now_txt; $now_txt = localtime($now); $header = "cmd : $argstxt\n"; $header .= "date : $now_txt\n"; $header .= "example buffer : \n\n$buffer\n\n"; $header .= "TOC : \n"; $header .= "colum | name | type | unit | example g\n"; $header .= join '' , map { my $col = $_; my $key = $columnum2key{$col}; my ($t,$u,$v); $t = (defined $$datahash{$key}{'type'})?$$datahash{$key}{'type'}: $columtype{$key}; $u = (defined $$datahash{$key}{'unit'})?$$datahash{$key}{'unit'}: $columunit{$key} || 'unknown'; $v = (defined $$datahash{$key}{'data'})?$$datahash{$key}{'data'}: $columexample{$key} || 'nan'; sprintf "%5.d : (%s) (%s) (%s) ie : (%s)\n" , $col , $key , $t,$u,$v; } sort { $a <=> $b} keys %columnum2key; $header .= "\n\n"; $header =~ s/(\A|^)/#/msg; $header; } sub sprint_datahash{ my ($datahash) = @_; my ($str,$names) = ('',''); my ($key); my ($n) =2; # time_t is taken care of. while (defined ($key = $columnum2key{$n++})){ if ($debug){ print "columnum2key ($key) n ($n)\n"; if (defined $$datahash{$key} and defined $$datahash{$key}{'data'}){ print " datav ($$datahash{$key}{'data'}) \n"; }else{ print "something is not defined... nanie\n"; } } my $s; if (defined $$datahash{$key}{'data'} and $$datahash{$key}{'data'} ne ''){ if (2 == $n){ $s= "$$datahash{$key}{'data'}"; # fix? use map 1..n ? }else{ $s= " $$datahash{$key}{'data'}"; # fix? use map 1..n ? } }else{ $s= " nan"; } $str .= $s; $names .= " $key ($s) "; } $debug and print "names ($names)\n"; $debug and print "str ($str)\n"; $str; } # its a sub because we reuse it. # maybe this should just be colum{$_}{'columnumber'|'type'|data} like the datahash. sub columize_datahash{ my ($datahash) = @_; # $columname{$_} = $cnt; # $columtype{$_} = $types[$cnt-1]; # columize_datahash($datahash); $debug and print "columzing datahash ($datahash)\n"; my $tmptime; if ( defined $$datahash{'time_t'}){ $tmptime = $$datahash{'time_t'}; delete $$datahash{'time_t'}; } map { my $key = $_; $debug and print "key ($key)\n"; if (not defined $columname{$key} ){ #if (not defined $columname{$key} and $key ne 'time_t'){ $debug and print "new colum ($key) $ncolums ($$datahash{$key}{'data'})\n"; $columname{$key} = $ncolums; $columtype{$key} = $$datahash{$key}{'type'}; $columunit{$key} = $$datahash{$key}{'unit'}; $columexample{$key} = $$datahash{$key}{'data'}; $columnum2key{$ncolums} = $key; $ncolums++; } } keys %{$datahash}; # stricly speaking not nessisary, but it adds to a more determanistics solution. #} sort keys %{$datahash}; # stricly speaking not nessisary, but it adds to a more determanistics solution. if ($tmptime){ $$datahash{'time_t'} = $tmptime; } $debug and print "columzing done\n"; } main(); # golem hates me. sub license { if (not defined $::owner){ $::owner = "Grotto-Group"; $::copyrightnotice = "This is a copyright work\n"; $::copyrighttext = "This is an unpublished work, don't use it\n"; } print " $::owner $::copyrightnotice $::copyrighttext "; exit(1); } sub FAQ{ print " FAQ Q: Where do I get more, or the latest version? A: http://www.grotto-group.com/~gulfie/projects/annalysis/gpss/dist/latest or http://www.grotto-group.com/~gulfie/projects/annalysis/gpss.subpage.html Q: Where to report bugs, or send feature requests? A: email : gulfie\@grotto-group.com or use the auto-bug-generator flag. Q: I'm only getting gnuplot updates about once every 30 seconds, what's wrong? A: Are you running on an NFS partition? NFS on some hosts causes... issues. Change the current working directory so that the gpss tmpfile is on some local disk and it should be much happier. Q: Are there Commercially/licensable versions available? A: There certainly could be. Q: Patches / Extensions for other IPS/IDSes? A: For legal reasons I am reticent to take patches, Ideas and sample stats files are welcome. Q: Windows support? A: It should work under cygwin, I'd have no way of knowing, and little reason to try. Q: What is with the monolithic one file design A: It is much simpler to release and distribute this way. Getting gpss running on a new machine is as simple as scp. That being said, persona files were added to make development eazier as well as the simpler integration with site specific personas. When a new version of gpss comes out, a localy maintained persona file should just keep working.... should. "; exit(); } sub tutorial{ my $personalist = join " " , sort keys %persona; print " The simplest way to get to know gpss is to just play with it. Obviously you have a copy. The first thing to figure out is which persona you'd like to try out. Current personas are : $personalist Current default persona : $persona A persona tipicly requires some data before it'll get happy. If you'd like to try out the snort.stats persona, you'll need a snort.stats file. If you are running on a box with a snort.stats file in /var/snort/snort.stats, great! If not, insert ' --file WHATEVER ' into the beginning of all of the commands below, and replace WHATEVER with your snort.stats file. If you don't have one, a sample data file can be retrieved from: http://www.grotto-group.com/~gulfie/projects/annalysis/gpss/dist/test_dataset Note: It's a funny data set taken with a mildly broken pcap, during an test run. The traffic is not what snort would normally be processing. After installing all the prerequisites, your ready to start learning. First it's time to find out what data is avalible from this persona. ./gpss --list You should see a list of avalible data colums, graphs, and personas. To use a different persona use the --persona PERSONA flag. For now we'll keep to the default snort.stats persona. Try something simple, like : ./gpss mbits A gnuplot opened up with your data. Now try packets / sec. ./gpss kpkts It is just so simple. To graph multiple columns just list them out in a comma separated list. For example : ./gpss syn,syn_ack That's it. By now one of the graphs has probably caught your interest. To examine a graph in more detail you can use the standard gnuplot commands to zip around. The mouse's right button should allow you to zoom in, the 'p' key will zoom out. the 'h' key will get you a list of other options. Another method to winnow down the data a little is the --tail flag. If you just want to see the most resent few lines of the file. If the perfmonitor is set to output data every 300 seconds, the following will give you the last days traffic. ./gpss --tail 288 mbits,mbits_wire Oh, and it'll continuously update every 300 seconds or so. By tuning the perfmonitor to push out data faster, a real time IDS/IPS monitoring solution is born. More on that later. So we have done only 1d vs time, or 2d graphs. gpss can do more. Just put it on the command line. Just separate axes with spaces. ./gpss kpackets megabits or ./gpss kpkts cpu_0_usr,cpu_0_sys or ./gpss time_t kpkts cpu_0_usr,cpu_0_sys Currently gpss only supports 3 dimetions, x,y,z. If you'd like to see all the column options, the --list flag is for you. ./gpss --list Will give a list of all the avalible colums, and the columns will change depending on how snort is configured/compiled, and what version is being run. Also listed are precanned axes sets. In stead of using the command line : ./gpss kpkts,kpkts_wire,kpkts_ipfrag,kpkts_ipreass,kpkts_rebuilt use ./gpss kpktsg It's so much less typing. Now more about that NOC. Turn up the logging speed of perfomitor so it logs once every say 15 seconds. Run a bunch of --tail -ing gpss, and sit back to watch. Not only is it eye candy, but the graphs are useful. ./gpss --tail 480 noc1 --gco '-geometry 400x400+0+0' & ./gpss --tail 480 noc2 --gco '-geometry 400x400+400+0' & ./gpss --tail 480 noc3 --gco '-geometry 400x400+800+0' & It's nothing like having a real NOC with real NOC tools. But it sure looks neat, and by looking neat you'll watch it more, and by watching it more you'll have a better understanding of the network. Other Tricks: A quick Web report version 0. ./gpss --generate_examples or ./gpss --generate_examples --tail 500 Will generate an example of every built in graph, build out a quick page named examples.html and exit. This sounds like a job for cron. A quick Web report version 1. ./gpss --terminal png --outputfile noc1.png noc1 & Add some html around a few of these and you will be done, right after writing some init scripts. Fcode tutorial: Currently its vudu, and requires transcoding... which snort_stats does not do. All the other personas do transcoding, thus something like... ./gpss --persona /proc/stat 'non_idle = deltat(cpu.total.irq + cpu.total.nice + cpu.total.softirq + cpu.total.steal+cpu.total.steal + cpu.total.system + cpu.total.user + cpu.total.wait)' And can be used in place of a data stream, so multiple ',' seperated fcode fragments will give you what you probibly want. Fcode functions : delta Take the difference in successive samples. deltat delta / time between two samples. min / max sum count avg floor ceil Fcode operators : - + * / = This needs to be fleshed out more, and documented much better. But for now, take a look at the code of gpss to get a better understanding of how fcode works, and what it can be made to do. fcode bugs: if you end up with something named __lots_of_gibberish__ instead of what you wanted, you probibly have something like 'foo = a+ b+ c'. There are some parsing and presidence problems I'll need to get back to some day. Writing your own Persona: The core of it is simple, fill out an entry in in the persona hash. It's not quite well documented. First off look at an example, a simple example like the /proc/loadavg (probibly proc_loadavg.pm) a) fill out the hash b) write a parsing function that digests a buff and returns a data hash with hash elements. persona fields: 'description' : text, a short description 'ssfile' : '' required for transcodeing... just use '' . It's kinda historic 'sample_mode' : The default way to get the data. 'poll' it is currently the only one, use it. 'figure_colums' : use undef; another historical anacronism, 'parse_data_function' : The function that can turn your buffer into a data hash. The data hash is hash of named data elements, each element has { 'data' => Whatver value it has, maybe undef 'type' => Bits, bytes, foobars, the thing that shows up on the left hand side of the graph 'unit' => meeters, feet, seconds, something like that. } note : the type and unit are hoplessly conflated at this point. It needs to get fixed. But it'll take a fair amount to fix it. fcode should treat types or units right, currently it dosn't much. for example, deltat should turn a byte counter into bytes/sec , etc. 'poll_src' : the default source for data, uri like. 'file:///proc/loadavg' works 'filet:///proc/loadavg' works for tailing a file. (forever) 'cmd://ping' works 'cmd://cat whatever file' works poorly 'cmdt:// tail -f whatever ' currently only line delimited. 'gpss://filename.gpss' a datafile dumped by gpss. 'gpsst://filename.gpss' a datafile dumped by gpss... tailed 'http://URL' 'https://URL' 'poll_period' : floating point seconds 'poll_cmd_args' : comands to pass to a cmd or cmdt command. 'changed_triggered' : if the data you have is updated less frequently than you are polling, then you wil get wierd plateaus in your data due to sampling to fast, this is one way to help combat that. It will cause gpss to only accept data when something about it is different. Note this kinda breaks other things so use it carefully. 'delimiter' : a delimiter of buffers '' means use the full file '\n' is only used by snort stats, so it might not work so well, but can be used to break a multi line file down into seperate records. 'graphname' : a poorly named field, due to historical reasons. It is a hash reference, of hash references that contain graph specifications... sounds like ick works much better and make sence when you see it. i.e. 'graphname' => { # 1d graphs ( data vs time) 'avg' => {'description' => 'avgs v time ' , 'txt' > [ 'avg1,avg2,avg3'] } ,'thread' => { 'description' => 'tasks vs time' , 'txt' => [ 'threads,running' ] } ,'spawnrate' => { 'description' => 'new pids / sec' , 'txt' => [ 'spawnrate=deltat(lastpid)' ] } # will probibly require wraping code # 2d graphs ( with data on two axes) ,'spawnratevsload' => {'description' => 'load averages vs spawn rate' , 'txt' => [ 'avg1,avg2,avg3' , 'spawnrate=deltat(lastpid)' ] } } So let's pull it apart. The first level hash is the name of the graph, that and the description are what show up with a '--list' flag. The 'txt' is an array of coma seperated list of fcode expressions. The simplest fcode is just the name of the datasource, such as avg1. So a comma seperated list of datasources is a valid 1d graph. i.e. 'avg1,avg2,avg3' Each element in the array is a different axis. So [ 'avg1' , 'spawnrate=deltat(lastpid)' ], is a 2d graph. That's it. 'procedural_graphs' : a function that returns a 'graphname' like thing when given a datahash some data sources are not very uniform and can not have staticly defined 'graphnames'. '--list' will reflect these new graphs. That's about it. Good luck! "; exit(); } sub todo{ print " Todo : document fcode and the persona interface add support for PERSONA_PATH and .gpss.persona files. write more personas deltat 31 /32 bit wraping code. fcode_units(); cleanup the types add more personas possibly remove snort_stats as the default persona. (leave it empty). add get_url'z for data polling. try to get rid of the 'cat' in gnuplot something I forgot. NOTE: Some of the personas are overly restrictive and undocumently implicet. i.e. /proc/stat is linux x86 uniproc, 2.4.x something. Over time this will get fleshed fixed out. "; exit(); } # # It's kinda funny that it needs to be running to get a bug report... hehe. # # gather up # sub make_a_bug_report{ # i'd be nice to take a full explorer output, but that's not goint happen. my (@thingstorun) = ( 'pwd' ,"which $gnuplot" ,"$gnuplot -V" #i'd be nice if this was more verbose. ,'uname -a' ,'id' ,"ls -alrt $ssfile" ); my ($date,$t); $t = time(); $date = localtime($t); my ($header) = " A gpss Bug report / Feature request gpss Version : ($version) Args : (" . (join " " , @ARGV) . ") perl : $] gpss : $0 date : $date time_t : $t persona_versions : ($persona_versions) Problem description / what do you want: This part you'll have to fill in. How to reproduce this problem: This part too, if possible. Some Details about your system : Remove any details you do not feel comforable shareing. "; my ($ttr)= ''; $ttr = join "", map { my ($cmd) = $_; my ($r,$buf); $r = `$cmd 2>&1`; $buf = "### begin ### $cmd\n$r### end ### $cmd\n"; $buf; } @thingstorun; print $header . $ttr ; exit(); } # # Slurp up a file. # sub slurp{ my ($fname) = @_; my ($buf); if (open INFILE, "<$fname"){ # $buf = join "" , ; # ick. local $/; $buf = ; close INFILE; return $buf; }else{ die "Slurp failed to read in file ($fname) cuz ($!), quiting\n"; } } # # splat out a file. # sub splat{ my ($fname,$buf) = @_; if (open OUTFILE, ">$fname"){ print OUTFILE $buf; close OUTFILE; }else{ die "Slurp failed to write in file ($fname) cuz ($!), quiting\n"; } } # # deep copy a datastructure... again.. my god how many times do I have towrite this? # # # We may be able to conver to the cpan modules 'Storable'... maybe. sub deepcopyds{ my ($d) = @_; my ($txt) = ref $d; my ($r); # print "dcds ($d)\n"; if ($txt eq ''){ $r = $d; }elsif($txt eq 'SCALAR'){ my ($foo); $foo = deepcopyds($$d); $r = \$foo; }elsif($txt eq 'ARRAY'){ my ($array); $array = [ map { deepcopyds($_); } @$d]; $r = $array; }elsif($txt eq 'HASH'){ my ($hash); $hash = { map { (deepcopyds($_) => deepcopyds($$d{$_})); } keys %$d}; $r = $hash; }elsif($txt eq 'CODE'){ # how best to do this? FIX $r = $d; }else{ die "unsupported copy of type ($txt) from ($d)\n\n\n"; } return $r; } # if (@zif_list){ # zif_transcode(); sub zif_transcode{ my ($outfile,@infiles) = @_; my (@orig_infiles) = @infiles; # algorithm, open up all the files # get a new hash from each on deck. # pick the youngest, and print it. # repeat as nessisary. open OUTFILE , ">$outfile" or die "Unable to open outfile ($outfile) ($!)\n"; select OUTFILE; $| = 1; # linebuffer / no buffer select STDOUT; my %zifin; my %zifdone; my %ingbshell; my %ingbcol2key; my $pseudo_time;# = $min_time_t; my $oldcols = -1; # my $debug = 1; $debug and print "Starting zif\n"; my $files_all_done = 0; while (not $files_all_done ){ $debug and print "Top of zif loop\n"; map{ # a uri that means we are reading one of our own tcfn outputs. my $gpssfn = $_; $debug and print "first map zif loop ($gpssfn)\n"; # we may have it open already, and we may end up reading some metadata we'll need to stuff into datastructures # that would normaly be populated by parse data functions. if (not defined $zifin{$gpssfn}){ # tail -f +0 is apparently not protable enough my $foo; if ($zifmode eq 'tail'){ if (not open $foo , "tail -f -n 20000000 $gpssfn |"){ die "Unable to open gpssfile ($gpssfn) ($!) quiiting\n"; } }else{ if (not open $foo , "<$gpssfn"){ die "Unable to open gpssfile ($gpssfn) ($!) quiting\n"; } } select $foo; $|=1; select STDOUT; $zifin{$gpssfn} = $foo; if (0){ # figure out how to do this better my $buf; my $foo2; $foo2 = $zifin{$gpssfn}; $buf = <$foo2>; print "wooooo!\n"; print "buf ($buf)\n"; } } # read up metadata untell we get to a line that has some data. my $done = 0; #iiiiiiii # in efficient... but hopefully workable. # this point is too far in the future to dispense with right now. if (defined $pseudo_time and defined $ingbshell{$gpssfn}{'time_t'} and $ingbshell{$gpssfn}{'time_t'} >= $pseudo_time){ $done =1; } if ($debug){ print "pseudo_time ($pseudo_time) ($done) \n"; } # # get a dataline, grab all the metadata on the way. # while (not $done){ my $line; $debug and print "while reading metadata\n"; my $foo; $foo = $zifin{$gpssfn}; $line = <$foo>; $debug and print "line ($line)\n"; if (not defined $line){ if ($zifmode eq 'tail'){ die "problem reading gpssfile ($gpssfn) ($!) quiting\n"; }else{ $zifdone{$gpssfn} = 1; @infiles = grep { $_ ne $gpssfn } @infiles; } }elsif ($line =~ /\A\s*#(.*)\Z/){ my $comment = $1; # print "reading comment ($comment)\n"; if ($comment =~ /\s*(\d+)\s*:\s*\(([^\)]+)\)\s+\(([^\)]+)\)\s+\(([^\)]+)\)\s+/){ my ($col,$key,$type,$unit) = ($1,$2,$3,$4); # $debug and print " ($col,$key,$type,$unit) ($col,$key,$type,$unit)\n"; $ingbshell{$gpssfn}{$key} = { 'type' => $type , 'unit' => $unit }; $ingbcol2key{$gpssfn}{$col} = $key; } }elsif($line =~ /\A\s*(\d+(\.\d+)?)\s+/){ # $debug and print "reading in dataline ($line)\n"; my $col = 1; map { my $thing = $_; $debug and print "thing ($thing)\n"; my $key = $ingbcol2key{$gpssfn}{$col++}; $ingbshell{$gpssfn}{$key}{'data'}= $thing ; } split /\s+/ , $line; # $buffer = $line; $done = 1; # we got our data.. time to get out. }else{ if (not defined $zifdone{$gpssfn}){ die "Unparsed gpssfile line ($line) ($gpssfn)\n"; }else{ $done =1; } } } # gbshell now should look just like a datahash. # $metadata = \%gbshell; # tailing stuff. like the gpss code # runn a command, but keep it open, } @infiles; # which one has the lowest time_t ? (col 1) ; my $min_time_t; my $min_time_t_fn; my %datahash = (); map { my $gpssfn = $_; $debug and print " working data and time loop ($gpssfn)\n"; # clean this all up with turning infiles into a hash, then doing keys. if (not defined $zifdone{$gpssfn} and (not defined $min_time_t or $ingbshell{$gpssfn}{'time_t'} < $min_time_t )){ $min_time_t_fn = $gpssfn; $min_time_t = $ingbshell{$gpssfn}{'time_t'}{'data'}; } # now load up the datahash %datahash = (%datahash , %{$ingbshell{$gpssfn}}); # in reverse order, first file gets presidence for nameing rights... it's a bit wonky # duplicates bork stuff up something fierce. } reverse @orig_infiles; $debug and print " out of loop ($min_time_t)\n"; $pseudo_time = $min_time_t; if (defined $pseudo_time){ # # print everthing. # # comlumize hash, columize_datahash(\%datahash); my $ncols; $ncols = scalar keys %datahash; if ($ncols != $oldcols){ # sucks we do it each time... $columname{'time_t'} = 1; $columtype{'time_t'} = 'time_t'; $columnum2key{1} = 'time_t'; print OUTFILE sprint_gpssfile_header(\%datahash); $oldcols = $ncols; } delete $datahash{'time_t'}; print OUTFILE "$pseudo_time "; print OUTFILE sprint_datahash(\%datahash) , "\n"; $debug and print "STUFF \n"; }else{ # we seem to be rather done. $files_all_done =1; } } }