#!/usr/bin/perl -w 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 = ' Wed Apr 5 01:14:16 PDT 2006 '; # # # gnuplot a snort.stat file a bunch of different ways. # # now it supports other file personas and # # # use Time::HiRes(qw(time sleep)); # can be removed, but don't. $|=0; 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 ($loop) = 1; 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 %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 $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 $gbf; sub get_buffer{ my ($buffer,$metadata); # global args, reentrant and etc. if ($poll_src){ if ($poll_src =~ '\A(file://)?(\S+)\Z' ){ my ($prefix,$filename) = ($1,$2); $debug and print "prefix ($prefix) filename($filename)\n"; if ($persona{$persona}{'delimiter'} eq ''){ #the whole file. if (open INFILE , "<$filename"){ local $/; $buffer = ; close INFILE; }else{ die("ERROR : transcoder unable to open file ($filename) ($!) quiting\n"); } }else{ die("NI"); } }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. }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; } 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. # # 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 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 }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' } ; }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 =~ /(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 = (); } # # # 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; } # # 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; } # # 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.6 /proc/meminfo reader' ,'ssfile' => '' # we require transcodeing ,'sample_mode' => 'poll' ,'figure_colums' => undef # ,'parse_data_function' => \&pdf_proc_stat ,'parse_data_function' => \&pdf_keyvalue ,'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 ,'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)' ] } ,'ints' => { 'description' => "some interupts" ,'txt' => [ 'ints.total=deltat(intr.total)' .', ints.0=deltat(intr.0)' .', ints.1=deltat(intr.1)' .', ints.2=deltat(intr.2)' .', ints.3=deltat(intr.3)' .', ints.4=deltat(intr.0)' .', ints.5=deltat(intr.1)' .', ints.6=deltat(intr.2)' .', ints.7=deltat(intr.3)' .', ints.8=deltat(intr.0)' .', ints.9=deltat(intr.1)' .', ints.10=deltat(intr.10)' .', ints.11=deltat(intr.11)' .', ints.12=deltat(intr.12)' .', ints.13=deltat(intr.13)' .', ints.14=deltat(intr.14)' .', ints.15=deltat(intr.15)' ] } } }; $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)] ] } }; 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'}; 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{ # 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) = ""; $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"; } sub show_personalities{ print "\n\nPersonalities : \n\n"; printf "%-15s %-15s %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 %-15s %s\n" , $indent ,$k , $desc; } sort keys %persona; print "\n\n"; } sub dograph{ # # open us up a gnuplot handle, and get it kinda happy. # 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\n$gpreoccur_pre\n\n"; print gen_gnuplot_txt(); print "$gpreoccur_post\n\n"; exit(); }else{ print $gp "$gpstartup \n\n"; } my ($firsttime)=1; my ($oldmtime) = 0; my ($done) = 0; 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"; if ($debug) { print "gpt ($gpt)\n"; } print $gp $gpt; $firsttime = 0; } binmode $gp; # flush the buffer. if ($loop){ if (0< $looptime ){ sleep $looptime; } }else{ $done =1; } } } sub main{ args(@ARGV); dograph(); exit; } sub gen_gnuplot_txt{ my ($txt); $txt = ''; my ($dcmd) = "< cat $ssfile | " . ' tr "," " " ' ; if ($tailn){ $dcmd = "< tail -$tailn $ssfile | " . ' tr "," " " ' ; } # '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 = 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"; select OUTFILE; $| =1; select STDOUT; }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; 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. # call the transcoder, if ($tc){ my $datahash; my $replace_time = 1; # print "buffer ($buffer)\n"; if (defined $persona{$persona}{'parse_data_function'} ){ $datahash = &{$persona{$persona}{'parse_data_function'}}($buffer); }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); if ($replace_time){ $$datahash{'time_t'} = {'data' => $t , 'type' => 'time_t' , 'units' => 'seconds' }; } 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 coyp... hoepfully enough. # print "dumper : ", Dumper($datahash) , "\n\n"; # sucks we do it each time... $columname{'time_t'} = 1; $columtype{'time_t'} = 'time_t'; $columnum2key{1} = 'time_t'; my $savedtime = $$datahash{'time_t'}{'data'}; delete $$datahash{'time_t'}; # 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 "; } while (defined ($key = $columnum2key{$n++})){ # print "columnum2key ($key) n ($n) \n"; my $s; if (defined $$datahash{$key}{'data'} and $$datahash{$key}{'data'} ne ''){ $s= " $$datahash{$key}{'data'}"; # fix? use map 1..n ? }else{ $s= " nan"; } $str .= $s; $names .= " $key ($s) "; } # print "str ($str)\n"; $debug and print "names ($names)\n"; $debug and print "str ($str)\n"; print OUTFILE $str, "\n"; }else{ 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; } } # 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"; map { my $key = $_; $debug and print "key ($key)\n"; if (not defined $columname{$key}){ $debug and print "new colum ($key) $ncolums ($$datahash{$key}{'data'})\n"; $columname{$key} = $ncolums; $columtype{$key} = $$datahash{$key}{'type'}; $columnum2key{$ncolums} = $key; $ncolums++; } } sort keys %{$datahash}; # stricly speaking not nessisary, but it adds to a more determanistics solution. $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 might work, I'd have no way of knowing. "; 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 vudo, 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)' 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. 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 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(); }