#! /usr/local/bin/perl # The contents of this file are subject to the Mozilla Public License # Version 1.0 (the "License"); you may not use this file except in # compliance with the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the # License for the specific language governing rights and limitations # under the License. # # The Original Code is the Berkeley Open Infrastructure for Network Computing. # # The Initial Developer of the Original Code is the SETI@home project. # Portions created by the SETI@home project are Copyright (C) 2002 # University of California at Berkeley. All Rights Reserved. # # Contributor(s): # # Stripchart - Version 2.0 by Matt Lebofsky ( November 4, 2002 ) # # Requires: gnuplot with .gif support (which may require gd) # perl 5.0 or later # # Stripchart is a wrapper around gnuplot which takes in time-based # data from a file and creates a web-friendly image. It is quite # useful for system diagnostic checking, especially when used with # stripchart.cgi # # You should only need to edit the variables in the section # "GLOBAL/DEFAULT VARS" below # # Type "stripchart -h" for usage # # See doc/stripchart.txt for details # use Getopt::Std; use File::Basename; use Time::Local; $|++; ##################### # GLOBAL/DEFAULT VARS ##################### # Where is gnuplot located? $gnuplot = "/usr/local/gnuplot-3.7"; # Temporary files $suffix = rand(10000); $gnudata = "/tmp/XYZzyx..indata" . $suffix; # contains input data $ticdata = "/tmp/XYZzyx..ticdata" . $suffix; # contains xtic information $gnuscript = "/tmp/XYZzyx..gnuscript" .$suffix; # contains script for gnuplot $outfile = "/tmp/XYZzyx..outfile" . $suffix; # contains output plot # Default colors for plotting (in standard RRGGBB hex format) # in order: 1. background color, 2. axis/font color, 3. grid color, # 4. point color 1, 5. point color 2, 6. point color 3 $colorvals = "xffffff x000000 xc0c0c0 x00a000 x0000a0 x2020c0"; # Default size of plot in pixels $plotwidth = 600; $plotheight = 180; # Abbreviations for months @monthabbs = ("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"); # What time is it? $rightnow = time; # Where are these unix commands located? $tailexe = "/usr/ucb/tail"; $catexe = "/usr/bin/cat"; # How many seconds in a day? $daysecs = 86400; # Local time difference? (Yes this is a wonky way to get current time zone difference) ($sec,$min,$hour,$mday,$mon,$year) = gmtime($rightnow); $tzdiff = timegm($sec,$min,$hour,$mday,$mon,$year) - timelocal($sec,$min,$hour,$mday,$mon,$year); ############# # SUBS ############# sub to_unix_time { # times should generally be in epoch seconds, but just in case they're not: # no colons and no decimal point? must already be in unix time if ($_[0] !~ /:/ && $_[0] !~ /\./ ) { return $_[0] } # colons = civil time if ($_[0] =~ /:/) { (my $year, my $month, my $day, my $hour, my $minute) = split /:/, $_[0]; $month--; return timelocal(0,$minute,$hour,$day,$month,$year) } # must be julian date return (($_[0] - 2440587.5) * $daysecs); } sub unix_to_civillabel { # convert epoch seconds to a pretty date string (my $minute, my $hour, my $day, my $month, my $year) = (localtime($_[0]))[1,2,3,4,5]; $month++; $year+=1900; $year = substr($year,2,2); my $label = sprintf("%02d/%02d/%02d %02d:%02d",$month,$day,$year,$hour,$minute); return $label } sub time_to_monthlabel { # convert epoch seconds to a simple month label (for x axis) (my $day, my $month) = (localtime($_[0]))[3,4]; my $label = $monthabbs[$month] . " $day"; return $label; } sub time_to_hourlabel { # convert epoch seconds to a simple hour label (for x axis) (my $minute, my $hour) = (localtime($_[0]))[1,2]; my $label = sprintf("%02d:%02d",$hour,$minute); return $label; } ############# # MAIN ############# # get command line options if (getopts("f:t:r:i:o:O:l:T:x:y:Y:b:Bd:D:vLc:Csj") == FALSE) { print STDERR << "EOF"; stripchart: creates stripchart .gif graphic based on data in flat files options: -i: input FILE - name of input data file (mandatory) -o: output FILE - name of output .gif file (default: STDOUT) -O: output FILE - name of output .gif file and dump to STDOUT as well -f: from TIME - stripchart with data starting at TIME (default: 24 hours ago) -t: to TIME - stripchart with data ending at TIME (default: now) -r: range RANGE - stripchart data centered around "from" time the size of RANGE (overrides -t) -l: last LINES - stripchart last number of LINES in data file (overrides -f and -t and -r) -T: title TITLE - title to put on graphic (default: FILE RANGE) -x: column X - time or "x" column (default: 2) -y: column Y - value or "y" column (default: 3) -Y: column Y' - overplot second "y" column (default: none) -b: baseline VALUE - overplot baseline of arbitrary value VALUE -B: baseline-avg - overrides -b, it plots baseline of computed average -d: dump low VALUE - ignore data less than VALUE -D: dump high VALUE - ignore data higher than VALUE -v: verbose - puts verbose runtime output to STDERR -L: log - makes y axis log scale -c: colors "COLORS" - set gnuplot colors for graph/axis/fonts/data (default: "$colorvals" in order: bground, axis/fonts, grids, pointcolor1,2,3) -C: cgi - output CGI header to STDOUT if being called as CGI -s: stats - turn extra plot stats on (current, avg, min, max) -j: julian times - time columns is in local julian date (legacy stuff) notes: * TIME either unix date, julian date, or civil date in the form: YYYY:MM:DD:HH:MM (year, month, day, hour, minute) If you enter something with colons, it assumes it is civil date If you have a decimal point, it assumes it is julian date If it is an integer, it assumes it is unix date (epoch seconds) If it is a negative number, it is in decimal days from current time (i.e. -2.5 = two and a half days ago) * All times on command line are assumed to be "local" times * All times in the data file must be in unix date (epoch seconds) * RANGE is given in decimal days (i.e. 1.25 = 1 day, 6 hours) * if LINES == 0, (i.e. -l 0) then the whole data file is read in * columns (given with -x, -y, -Y flags) start at 1 * titles given with -T can contain the following key words which will be converted: FILE - basename of input file RANGE - pretty civil date range (in local time zone) the default title is: FILE RANGE EOF exit 1 } if ($opt_C && $opt_v) { print STDERR "you cannot have -v and -C set at the same time..\n"; exit 1 } if ($opt_i) { $infile = $opt_i } else { print STDERR "need to set input file via -i. exiting..\n"; exit 1 } if ($opt_f) { if ($opt_f < 0) { $fromtime = to_unix_time($rightnow) + ($daysecs * $opt_f) } else { $fromtime = to_unix_time($opt_f) } } else { $fromtime = to_unix_time($rightnow) - $daysecs } if ($opt_t) { $totime = to_unix_time($opt_t) } else { $totime = to_unix_time($rightnow) } if ($opt_v) { print STDERR "selected time range: $fromtime - $totime\n" } if ($opt_r) { $totime = $fromtime + ($opt_r * $daysecs); $fromtime -= ($opt_r * $daysecs) } # if -l is set, override from/to with close-to-impossible all-inclusive range if (defined($opt_l)) { $fromtime = 0; $totime = 2147483647 } if ($opt_x) { $timecol = $opt_x } else { $timecol = 2 } if ($opt_y) { $datacol = $opt_y } else { $datacol = 3 } if ($opt_Y) { $extracol = $opt_Y } if ($opt_c) { $colorvals = $opt_c } # read in file if ($opt_v) { print STDERR "reading in data from $infile..\n" } if ($opt_l > 0) { $numlines = $opt_l; @stripdata = `$tailexe -$numlines $infile` or die "cannot open file: $infile"; } else { @stripdata = `$catexe $infile` or die "cannot open file: $infile"; $numlines = @stripdata; } if ($opt_v) { print STDERR "read in $numlines lines..\n" } # make gnuplot data file if ($opt_v) { print STDERR "making temp data file for gnuplot..\n" } open (GNUDATA,">$gnudata") or die "cannot write temp data file: $gnudata"; $statcurrent = 0; $statmax = "x"; $statmin = "x"; $stattotal = 0; $checkfromtime = $fromtime; if ($opt_j) { $checkfromtime = (($fromtime + $tzdiff) / $daysecs ) + 2440587.5 } $thisextra = 0; $thisbaseline = 0; if ($opt_b) { $thisbaseline = $opt_b } $numlines = 0; $firstunixdate = -1; foreach $dataline (@stripdata) { chomp $dataline; @dataarray = split /\s/, $dataline; $thistime = $dataarray[$timecol-1]; if ($thistime < $checkfromtime) { next } if ($opt_j) { $thistime = int(($dataarray[$timecol-1] - 2440587.5) * $daysecs) - $tzdiff } $thisdata = $dataarray[$datacol-1]; if ($thistime <= $totime) { if ($thistime !~ /[a-zA-Z]/ && $thisdata !~ /[a-zA-Z]/ && # ignore junky numbers $thisdata ne "" ) { if ((defined($opt_d) && ($thisdata >= $opt_d)) || !defined($opt_d)) { if ((defined($opt_D) && ($thisdata <= $opt_D)) || !defined($opt_D)) { $numlines++; if ($firstunixdate == -1) { $firstunixdate = $thistime } $lastunixdate = $thistime; if ($opt_Y) { $thisextra = $dataarray[$extracol-1]; } if ($opt_s) { $statcurrent = $thisdata; $stattotal += $thisdata; if ($statmax eq "x" || $thisdata > $statmax) { $statmax = $thisdata } if ($statmin eq "x" || $thisdata < $statmin) { $statmin = $thisdata } } print GNUDATA "$thistime $thisdata $thisextra $thisbaseline\n" } } } } } close (GNUDATA); $statavg = ($stattotal / $numlines); if (opt_B) { @tmpdata = `$catexe $gnudata`; open (GNUDATA,">$gnudata") or die "cannot write temp data file: $gnudata"; foreach $tmpline (@tmpdata) { chomp $tmpline; ($tempone,$temptwo,$tempthree,$tempfour) = (split /\s/, $tmpline); $tempfour = $statavg; print GNUDATA "$tempone $temptwo $tempthree $tempfour\n"; } close (GNUDATA); } if ($numlines == 0) { print STDERR "no data selected due to user constraints.\n"; exit 1 } if ($opt_v) { print STDERR "$numlines data points within time range..\n" } $begin = $firstunixdate; $end = $lastunixdate; if (!defined($opt_l)) { $begin = $fromtime; $end = $totime } if ($opt_v) { print STDERR "actual time range being plotted: $begin - $end\n" } # make gnuplot xtics file $daydiff = $end - $begin; if ($opt_v) { print STDERR "making xtics file..\n" } # depending on the range, pick appropriate tic steps and label steps # less than 10 minutes: label every minute, extra tics every 30 seconds if ($daydiff <= 600) { $labelstep = 60; $ticstep = 30 } # less than 50 minutes: label every 5 minutes, extra tics every minute elsif ($daydiff <= 3000) { $labelstep = 300; $ticstep = 60 } # less than 100 minutes: label every 10 minutes, extra tics every 5 minutes elsif ($daydiff <= 6000) { $labelstep = 600; $ticsstep = 300 } # less than 200 minutes: label every 20 minutes, extra tics every 10 minutes elsif ($daydiff <= 12000) { $labelstep = 1200; $ticsstep = 600 } # less than 5 hours: label every 30 minutes, extra tics every 15 minutes elsif ($daydiff <= 18000) { $labelstep = 1800; $ticstep = 900 } # less than 10 hours: label every hour, extra tics every 30 minutes elsif ($daydiff <= 36000) { $labelstep = 3600; $ticstep = 1800 } # less than 20 hours: label every 2 hours, extra tics every hour elsif ($daydiff <= 72000) { $labelstep = 7200; $ticstep = 3600 } # less than 40 hours: label every 4 hours, extra tics every hour elsif ($daydiff <= 144000) { $labelstep = 14400 ; $ticstep = 3600 } # less than 60 hours: label every 6 hours, extra tics every hour elsif ($daydiff <= 216000) { $labelstep = 21600; $ticstep = 3600 } # less than 120 hours: label every 12 hours, extra tics every 6 hours elsif ($daydiff <= 432000) { $labelstep = 43200; $ticstep = 21600 } # less than 10 days: label every day, extra tics every half day elsif ($daydiff <= 864000) { $labelstep = 86400; $ticstep = 43200 } # less than 20 days: label every 2 days, extra tics every day elsif ($daydiff <= 1728000) { $labelstep = 172800; $ticstep = 86400 } # less than 40 days: label every 4 days, extra tics every 2 days elsif ($daydiff <= 3456000) { $labelstep = 345600; $ticstep = 172800 } # less than 70 days: label every 7 days, extra tics every 7 days elsif ($daydiff <= 6048000) { $labelstep = 604800; $ticstep = 604800 } # less than 140 days: label every 14 days, extra tics every 7 days elsif ($daydiff <= 12096000) { $labelstep = 1209600; $ticstep = 604800 } # less than 280 days: label every 28 days, extra tics every 14 days elsif ($daydiff <= 24192000) { $labelstep = 2419200; $ticstep = 1209600 } # less than 600 days: label every 60 days, extra tics every 30 days elsif ($daydiff <= 51840000) { $labelstep = 5184000; $ticstep = 2592000 } # less than 1000 days: label every 100 days, extra tics every 50 days elsif ($daydiff <= 86400000) { $labelstep = 8640000; $ticstep = 4320000 } # okay, you're on your own now: every 200 days, extra tics every 100 days else { $labelstep = 17280000; $ticstep = 8640000 } if ($opt_v) { print STDERR "x label resolution: $labelstep seconds..\n"; print STDERR "x tic resolution: $ticstep seconds..\n" } open (TICDATA,">$ticdata") or die "cannot write temp tic file: $ticdata"; print TICDATA "set xtics( \\\n"; if ($daydiff >= 18000 && $daydiff <= 216000) { # round down/up to nearest hour $firstunixdate = int($firstunixdate/3600) * 3600; $lastunixdate = (int($lastunixdate/3600)+1) * 3600; } elsif ($daydiff >= 216000) { # round down/up to nearest day $firstunixdate = ((int($firstunixdate/86400)) * 86400) - $tzdiff; $lastunixdate = ((int($lastunixdate/86400)+1) * 86400) - $tzdiff; } for ($thisdate = $firstunixdate; $thisdate < $lastunixdate; $thisdate += $ticstep) { $label = '""'; if (($thisdate - $firstunixdate) % $labelstep == 0) { if ($daydiff <= 432000) { $label = '"' . time_to_hourlabel($thisdate) . '"' } else { $label = '"' . time_to_monthlabel($thisdate) . '"' } } print TICDATA $label . " " . $thisdate; if (($thisdate + $ticstep) < $lastunixdate) { print TICDATA "," } print TICDATA " \\\n"; } print TICDATA ")\n"; close (TICDATA); # make gnuplot file and run it! if ($opt_v) { print STDERR "running gnuplot to create $opt_o..\n" } if (defined($opt_l)) { # fix for title $fromtime = $begin; $totime = $end; } if ($opt_T) { $title = $opt_T } else { $title = "FILE RANGE" } $thisfile = basename($infile); $thisrange = "(" . unix_to_civillabel($fromtime) . " -> " . unix_to_civillabel($totime) . ")"; $title =~ s/FILE/$thisfile/g; $title =~ s/RANGE/$thisrange/g; if ($opt_s) { $plotheight += 12; $statlabel = sprintf("(cur: %.2f max: %.2f min: %.2f avg: %.2f)",$statcurrent,$statmax,$statmin,$statavg); $title .= "\\n$statlabel"; } open (GNUSCRIPT,">$gnuscript") or die "cannot open gnuscript file: $gnuscript"; print GNUSCRIPT "set terminal gif small size $plotwidth, $plotheight \\\n"; print GNUSCRIPT "$colorvals\n"; print GNUSCRIPT "set nokey\nset grid\nset title \"$title\"\n"; if ($opt_L) { print GNUSCRIPT "set logscale y\n" } print GNUSCRIPT "set pointsize 0.1\n"; print GNUSCRIPT "plot \"$gnudata\" using 1:2 with impulses"; if ($opt_Y) { print GNUSCRIPT ", \"$gnudata\" using 1:3 with lines" } if ($opt_b || $opt_B) { print GNUSCRIPT ", \"$gnudata\" using 1:4 with lines" } print GNUSCRIPT "\n"; close (GNUSCRIPT); if ($opt_C) { print "Content-type: image/gif\n\n"; } if ($opt_o) { `$gnuplot/gnuplot $ticdata $gnuscript > $opt_o` } else { `$gnuplot/gnuplot $ticdata $gnuscript > $outfile`; open (OUTFILE,"$outfile"); while ($bytesread=read(OUTFILE,$buffer,1024)) { print $buffer } close (OUTFILE); if ($opt_O) { `$gnuplot/gnuplot $ticdata $gnuscript > $opt_O` } } # the end. clean up. if ($opt_v) { print "done. cleaning up..\n" } unlink ($outfile); unlink ($ticdata); unlink ($gnudata); unlink ($gnuscript); exit 0;