Added Matt's stripchart code

svn path=/trunk/boinc/; revision=583
This commit is contained in:
Eric Heien 2002-11-07 19:29:46 +00:00
parent 1a00f94e0b
commit 22e153233f
4 changed files with 1262 additions and 0 deletions

333
doc/stripchart.txt Normal file
View File

@ -0,0 +1,333 @@
Stripchart version 2.0
----------------------
Author: Matt Lebofsky
BOINC/SETI@home - University of California, Berkeley
mattl@ssl.berkeley.edu
Date of recent version: November 4, 2002
Requirements:
* a gnuplot with the ability to generate gifs
* perl
* apache or other cgi-enabled web browser
Send all thoughts and queries to: mattl@ssl.berkeley.edu
This software is free to edit, distribute and use by anybody, as long as
I get credit for it in some form or another. Thanks.
----------------------
Contents:
I. Some questions and answers
II. So how does it work?
III. Known bugs, things to do, etc.
----------------------
I. Some questions and answers
Q: What is stripchart?
A: Well, it's actually two relatively small perl programs:
1. stripchart
stripchart reads in time-based user data and, depending on a flurry of
command line options, generates a web-friendly .gif plotting the data.
The user can supply the time range, the y axis range, even the color
scheme, and more.
2. stripchart.cgi
stripchart.cgi is a web-based GUI interface that allows users to easily
select multiple data sources and various parameters to plot, allowing
fast comparisons without having to deal with a command line interface.
Q: Why do you bother writing this program?
A: Working as a systems administrator (amongst other things) for SETI@home,
we kept finding ourselves in dire problem-solving situations, i.e. Why
did the database stop working? Why is load on our web server so high?
So we started collecting data in flat files, keeping track of server
loads, database checkpoint times, even CPU temperatures. When these files
grew too large and unwieldy, I found myself writing (and rewriting) simple
scripts to generate plots on this data. Sick of constant revision whenever
a new problem arose, I wrote stripchart version 1.0.
Its usefulness became immediately apparent when I added on stripchart.cgi.
I couldn't bear to teach everybody the many command line options to
stripchart, so I wrote this CGI to do all the dirty work. Suddenly we were
able to line up several plots, look for causes and effects, or just enjoy
watching the counts in our database tables grow to impossibly high numbers.
The SETI@home network has proven to be a delicate system, and keeping track
of all the data server, user, and web statistics has proven to be quite a
life saver. So when BOINC came around we felt that any project aiming to
embark on a similar project may need this tool. So I rewrote stripchart to
be a bit more friendly and general.
Q: Why don't you make .pngs or .jpgs instead of .gifs? The latest gnuplot
doesn't support .gifs.
A: Basically gnuplot support for other graphic file formats isn't as good. For
example, you cannot control exact window size, font size, and colors unless
you make .gifs. I'm not exactly sure why this is the case, but there you have it.
Anywho, you can find older gnuplot distributions out there - you'll need to
get the gd libs first, by the way.
----------------------
II. So how does it work?
You can use stripchart as a stand alone command-line program to produce plots
whenever you like, but we highly recommend using it in conjunction with the
stripchart.cgi for ease of use. But here's how to do it both ways.
stripchart (stand alone)
Before anything, look at the section GLOBAL/DEFAULT VARS in the program
stripchart and see if you need to edit anything (usually pathnames to
executables and such).
Let's just start with the usage (obtained by typing "stripchart -h"):
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:
"xffffff x000000 xc0c0c0 x00a000 x0000a0 x2020c0"
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
...okay that's a lot to ingest, but it's really simple. Let's take a look at an
example (you'll find in the samples directory two files get_load and crontab).
You have a machine that you want to monitor it's load. Here's a script that
will output a single line containing two fields for time and the third with the
actual data. For example:
2002:11:05:12:51 1036529480 0.25
The first field is time in an arbitrary human readable format
(year:month:day:hour:minute), the second in epoch seconds (standard
unix time format - the number of seconds since 00:00 1/1/1970 GMT),
and the third is the load at this time.
And we'll start collecting data every five minutes on this particular machine
by add such a line to the crontab:
0,5,10,15,20,25,30,35,40,45,50,55 * * * * /usr/local/stripchart/samples/get_load >> /disks/matt/data/machine_load
So the file "machine_load" will quickly fill with lines such as the above.
Now you may ask yourself - why two columns representing time in two different
formats? Well sometime you just want to look at the data file itself, in which
case the human-readable first column is quite handy to have around, but when
making linear time plots, having time in epoch seconds is much faster to
manipulate. So generally, we like to have at least the two time fields first,
and the actual data in the third column. That's what stripchart expects by
default.
Note: stripchart will understand time in both epoch seconds and julian date.
If the second time field is in julian date, you should supply the command line
flag "-j" to warn stripchart so it knows how to handle it.
Okay. So you have this data file now. A very common thing to plot would be the
data over the past 24 hours. Turns out that's the default! If you type on the
command line:
stripchart -i machine_load -o machine_load.gif
you will quickly get a new file "machine_load.gif" with all the goods.
Note: you always have to supply an input file via -i. If you don't supply
an output file via "-o" it .gif gets dumped to stdout. If you supply an
output file via "-O" the output is stored in both the file and to stdout.
Now let's play with the time ranges. You can supply times in a variety of
formats on the command line:
"civil date" i.e. 2002:11:05:12:51 (YYYY:MM:DD:hh:mm)
"epoch seconds" i.e. 1036529480
"julian date" i.e. 2452583.52345
You can supply a date range using the -f and -t flags (from and to):
stripchart -i machine_load -f 2002:11:01:00:00 -t 2002:11:04:00:00
Usually the "to" time is right now, so you can quickly tell stripchart
to plot starting at some arbitrary time "ago." This is done also via the
"-f" flag - if it's negative it will assume you mean that many decimal
days from now as a starting point. So "-f -3.5" will plot from 3 and a
half days ago until now.
You can also supply a "range" centered around the from time. For example,
to plot the 24 hours centered around 2002:11:01:13:40:
stripchart -i machine_load -f 2002:11:01:13:40 -r 1
On some rare occasions you might want to plot the last number of lines
in a file, regardless of what time they were. If you supply the number
of lines via the "-l" flag, it overrides any time ranges you may have
supplied.
Moving on to some other useful flags in no particular order:
To change the default title (which is the basename of the file and
the time range being plotted), you can do so via the "-T" command.
Make sure to put the title in quotes. Within the title string the
all-uppercase string "FILE" will be replaced with the file basename,
and the string "RANGE" will be replaced by the time range. So in
essence, the default title string is "FILE RANGE".
If you have data files in different formats, you can specify the data
columns using the "-x" and "-y" flags. By default -x is 2 and -y is 3.
Sometimes we have datafiles with many columns so we actively have to tell
stripchart which is the correct data column.
However, you might want to overplot one column on top of another. If your
data file has a second data column, you can specify what that is via the
-Y flag, and this data will be overplotted onto the data from the first
data column.
Sometime you want to plot a horizontal rule or a "baseline". You can
turn this feature on by specifying the value with the "-b" flag. If you
use the "-B" flag (without any values) it automatically computes the
average over the time range and plots that as the baseline. Simple!
If you want to excise certain y values, you can do so with the dump
flags, i.e. "-d" and "-D". In particular, any values lower than the one
supplied with "-d" will be dumped, and any values higher supplied by
"-D" will be dumped.
To log the y axis, use the "-L" flag. Quite straightforward.
A very useful flag is "-s" which outputs a line of stats underneath
the plot title. It shows the current value, and the minimum, maximum
and average values during the plot range.
For verbose output to stderr, use the "-v" flag. It may not make much
sense, but it's useful for debugging.
Using the "-C" flag causes stripchart to spit out the "Content-type"
lines necessary for incorporating stripchart plots into CGIs. This
doesn't work so well now, but there it is.
Okay. That's enough about the flags, and hopefully enough to get you
playing around with stripchart and plotting some stuff. Now onto:
stripchart.cgi
First and foremost, you need to do the following before running the
CGI version of stripchart:
1. Put stripchart.cgi in a cgi-enabled web-accessible directory
2. Make a "lib" directory somewhere that the web server can read/write to
3. Edit stripchart.cgi GLOBAL/DEFAULT VARS to point to proper paths, including
the files "querylist" and "datafiles" in the aforementioned "lib" directory.
4. Edit the "lib/datafiles" file to contain entries for all your data files.
You can find an example datafiles in the samples directory. Follow the
instructions in the comment lines, adding your entries below the header.
That should be it, I think. Now go to the URL wherever your stripchart.cgi
is sitting. If all is well..
You will be immediately presented with a web form. Ignore the "select query"
pulldown menu for now. Underneath that you will see a line:
Number of stripcharts: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
By default stripchart.cgi presents you with the ability to plot 4 simultaneous
stripcharts, but you can select any number 1-20 by clicking on those numbers.
The less plots, the faster a web page gets generated.
For each plot, you get a pull down menu which should contain all the entries
you already put in "datafiles". Here you are selecting your data source.
Then you can select the time of time range: last x hours, last x days, or
an arbitrary date range. By default the last x hours radio button is selected -
to pick another type of time range make sure you select the radio button
before it. Then enter the range via the pull down menus.
Then you get a simple list of checkbox/input options. You can check to log
the y axis, baseline the average, baseline an arbitrary value (which you
enter in the window, enter a y minimum, or enter a maximum.
When everything is selected, click on the "click here" button to plot.
Depending on the speed of your machine, you should soon be presented with
all the plots your desired, and the form underneath the plots which can
edit to your heart's content. If you want to reset the form values, click
on the "reset form" link.
Note the "save images in /tmp" checkbox. If that is checked and you plot
the stripcharts, numbered .gif files will be placed in /tmp on the web
server machine so you can copy them elsewhere (files will be named:
stripchart_plot_1.gif, etc.).
On the topmost "click here" button you will note an "enter name to save
query" balloon. If you enter a name here (any old string) this exact query
will be saved into the "querylist" file which will then later appear in the
pulldown menu at the top. That way if you have a favorite set of diagnostic
plots which you check every morning, you don't have to enter the entire form
every time.
If you want to delete a query, enter the name in that same field but click
the "delete" checkbox next to it. Next time you "click here" the query will
be deleted.
----------------------
III. Known bugs, things to do, etc.
* stripchart -C flag is kind of pointless and doesn't work in practice.
* plots on data collected over small time ranges (points every few seconds, for
example) hasn't been tested.
* plots that don't work via stripchart.cgi either show ugly broken image icons
or nothing at all - either way it's ungraceful.
* pulldown menus and various plots sometimes need to be refreshed via a hard
refresh (i.e. shift-refresh).
* this readme kinda stinks.
* and many many other issues I'm failing to detail now!
If you have any problems using the product, feel free to e-mail me at:
mattl@ssl.berkeley.edu

View File

@ -12,3 +12,5 @@ Browse database:
<li><a href=db.php?show=workunit>Workunits</a>
<li><a href=db.php?show=result>Results</a>
</ul>
<a href=stripchart.cgi>Stripcharts</a>

461
html/ops/stripchart Executable file
View File

@ -0,0 +1,461 @@
#! /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;

466
html/ops/stripchart.cgi Executable file
View File

@ -0,0 +1,466 @@
#! /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.cgi - Version 2.0 by Matt Lebofsky ( November 4, 2002)
#
# Requires: stripchart
# apache or other CGI-enabled web server
#
# Stripchart.cgi is a web-based GUI interface for stripchart, allowing
# users to display multiple plots from various data sources.
#
# 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 CGI;
$query = CGI::new();
use File::Basename;
use Time::Local;
#####################
# GLOBAL/DEFAULT VARS
#####################
# Where are these unix commands located?
$grepexe = "/usr/bin/grep";
# Where is the stripchart executable located?
$stripchartexe = "/usr/local/stripchart/stripchart";
# What is the default number of stripcharts?
$defaultnumcharts = 4;
# Any default flags to send to stripchart?
$defaultflags = "-s";
# Read in list of datafiles:
$datafilelist = "/usr/local/www/share/cgi-bin/lib/datafiles";
@datafiles = `$grepexe -v '^#' $datafilelist`;
# Read in list of queries:
$queryfilelist = "/usr/local/www/share/cgi-bin/lib/querylist";
@querylist = `$grepexe -v '^#' $queryfilelist`;
# What time is it right now?
$rightnow = time;
($sec,$min,$hour,$day,$month,$year) = localtime($rightnow);
$sec = "";
$month++;
$year += 1900;
# Make file option list based on @datafiles
$optionlist = "";
foreach $element (@datafiles) {
chomp $element;
$thisop = (split /:/, $element)[1];
$optionlist .= "<option>$thisop</option>\n";
}
# Make query option list based on @querylist
$queryoptionlist = "<option value=\"stripchart_20.cgi\">------------------------------</option>";
foreach $element (@querylist) {
chomp $element;
($thisname,$thisquery) = (split /:/, $element)[0,1];
$queryoptionlist .= "<option value=\"stripchart_20.cgi?$thisquery\">$thisname</option>\n";
}
# Make year list based on time right now, and other lists
$yearlist = "";
for ($i = $year; $i> 1999; $i--) { $yearlist .= "<option>$i</option>\n" }
$monthlist = sprintf("<option>%02d</option>\n",$month);
foreach $i (01 .. 12) { $monthlist .= sprintf("<option>%02d</option>\n",$i) }
$daylist = sprintf("<option>%02d</option>\n",$day);
foreach $i (01 .. 31) { $daylist .= sprintf("<option>%02d</option>\n",$i) }
$hourlist = sprintf("<option>%02d</option>\n",$hour);
foreach $i (00 .. 23) { $hourlist .= sprintf("<option>%02d</option>\n",$i) }
$minlist = sprintf("<option>%02d</option>\n",$min);
for ($i=0; $i<59; $i+=5) { $minlist .= sprintf("<option>%02d</option>\n",$i) }
# Seconds in a day
$daysecs = 86400;
#############
# SUBS
#############
sub to_unix_time {
# same routine as in stripchart
# 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);
}
#############
# MAIN
#############
# ARE WE JUST PLOTTING A SINGLE GRAPH?
#
# stripchart.cgi calls itself via an "<IMG SRC=" tag, so we need to
# act accordingly - if all we have to do is call stripchart to plot
# a graph based on the user-selected flags, then do so and exit:
if ($query->param("flags") ne "") {
$flags = $query->param("flags");
$outfile = "/tmp/tempout$$" . "." . rand(100000);
print "Content-type: image/gif\n\n";
`$stripchartexe $flags > $outfile`;
open (OUTFILE,"$outfile");
while ($dummy=read(OUTFILE,$buffer,1024)) { print $buffer }
close (OUTFILE);
exit 0
}
# ARE WE SAVING/DELETING A QUERY?
#
# If the user chose to save or delete a query, act on that and
# then continue on with the standard plots:
if ($query->param("sqname") ne "") {
$sqname = $query->param("sqname");
# are we deleting it?
if ($query->param("delq") eq "y") {
@querylist = `$grepexe -v '^$sqname:' $queryfilelist`;
open (QUERYLIST,">$queryfilelist");
flock (QUERYLIST,2);
foreach $queryline (@querylist) {
print QUERYLIST $queryline
}
}
# must be saving it
else {
# first check to see if query already in the list
$found = 0;
foreach $checkline (@querylist) {
($key,$value) = split /:/, $checkline;
if ($key eq $sqname) { $found = 1 }
}
# not found - add it to the end
if (!$found) {
open (QUERYLIST,">>$queryfilelist");
flock (QUERYLIST,2);
$fullquery = $ENV{'QUERY_STRING'};
$fullquery =~ s/sqname=$sqname//;
print QUERYLIST "$sqname:$fullquery\n";
close (QUERYLIST);
}
}
}
# PARSE INCOMING
$datafile1 = $query->param("df1");
$numcharts = $query->param("numcharts");
if ($numcharts == 0) { $numcharts = $defaultnumcharts }
print $query->header;
print << "EOF";
<html>
<head>
<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
<META HTTP-EQUIV="Expires" CONTENT="-1">
<title>Stripchart</title>
</head>
<body>
EOF
if ($datafile1 ne "") { # stripcharts were selected, so plot them
foreach $which (1 .. $numcharts) {
$thisfile = $query->param("df$which");
if ($thisfile eq "No plot" || $thisfile =~ /^-+$/ || $thisfile eq "") { next }
if ($thisfile eq "Same as datafile 1") { $thisfile = $query->param("df1") }
$rangetype = $query->param("dr$which");
$trustnum = $which;
$from = "-f ";
$to = "";
if ($rangetype eq "sa1") { $rangetype = $query->param("dr1"); $trustnum = 1 }
if ($rangetype eq "lh") { $from .= "-" .($query->param("lhv$trustnum") / 24) }
elsif ($rangetype eq "ld") { $from .= "-" . ($query->param("ldv$trustnum")) }
else {
$fromstring = $query->param("dfy$trustnum") . ":" .
$query->param("dfm$trustnum") . ":" .
$query->param("dfd$trustnum") . ":" .
$query->param("dfh$trustnum") . ":" .
$query->param("dfn$trustnum");
$tostring = $query->param("dty$trustnum") . ":" .
$query->param("dtm$trustnum") . ":" .
$query->param("dtd$trustnum") . ":" .
$query->param("dth$trustnum") . ":" .
$query->param("dtn$trustnum");
$from .= to_unix_time($fromstring);
$to = "-t " . to_unix_time($tostring)
}
foreach $element (@datafiles) {
($maybepath,$maybefile,$maybedatacol,$maybeflags) = split /:/, $element;
if ($maybefile eq $thisfile) {
$fullpath = $maybepath;
$datacol = $maybedatacol;
$flags = $maybeflags;
}
}
$flags .= " $from $to";
if ($query->param("log$which")) { $flags .= " -L" }
if ($query->param("bavg$which")) { $flags .= " -B " }
if ($query->param("base$which")) { $flags .= " -b " . $query->param("basev$which") }
if ($query->param("ymin$which")) { $flags .= " -d " . $query->param("yminv$which") }
if ($query->param("ymax$which")) { $flags .= " -D " . $query->param("ymaxv$which") }
if ($query->param("savem")) { $flags .= " -O /tmp/stripchart_plot_$which.gif" }
$flags .= " -i $fullpath -y $datacol -T \"$thisfile RANGE\" $defaultflags";
$fixflags = $flags;
$fixflags =~ s/ /+/g;
$fixflags =~ s/"/%22/g;
print "<img src=\"stripchart_20.cgi?flags=$fixflags\"><br>\n"
}
print "<hr>";
}
# Now display the user entry form:
print << "EOF";
<form name="Saved queries">
<font size=2>
Select a saved query if you like:
</font>
<font size=1>
<select onChange="location = this.options[selectedIndex].value">
$queryoptionlist
</select>
</font>
</form>
<font size=2>
Number of stripcharts:
EOF
for ($i=1;$i<21;$i++) {
$fullquery = $ENV{'QUERY_STRING'};
$fullquery =~ s/numcharts=\d+//;
$fullquery =~ s/&+/&/g;
$fullquery =~ s/^&//;
print "<a href=\"stripchart_20.cgi?$fullquery&numcharts=$i\">$i</a>\n"
}
print << "EOF";
<p>
<form action=stripchart_20.cgi>
<input type=submit value="click here" name=submit> to plot stripcharts -
enter name:
<input type=text name=sqname size=20>
to save query (check here:
<input type=checkbox name=delq value=y>
to delete)
<input type=hidden value=$numcharts name=numcharts>
<p>
EOF
foreach $which (1 .. $numcharts) {
$thisoptionlist = $optionlist;
if ($which > 1) {
$thisoptionlist = "<option>No plot</option>\n" .
"<option>Same as datafile 1</option>\n" .
"<option>----------------------------</option>\n" . $optionlist
}
if ($dummy = $query->param("df$which")) {
$dummy = "<option>$dummy</option>";
$newoptionlist = "$dummy\n";
foreach $thisline (split /\n/, $thisoptionlist) {
if ($dummy ne $thisline) { $newoptionlist .= "$thisline\n" }
}
$thisoptionlist = $newoptionlist;
}
$lhc = "";
$ldc = "";
$ownc = "";
$sa1c = "";
if ($dummy = $query->param("dr$which")) {
if ($dummy eq "lh") { $lhc = "checked" }
elsif ($dummy eq "ld") { $ldc = "checked" }
elsif ($dummy eq "own") { $ownc = "checked" }
elsif ($dummy eq "sa1") { $sa1c = "checked" }
}
else {
if ($which > 1) { $sa1c = "checked" }
else { $lhc = "checked" }
}
$samerange = "";
if ($which > 1) {
$samerange = "<input type=radio name=dr$which value=sa1 $sa1c> Same range as datafile 1"
}
$logcheck = "";
if ($query->param("log$which") eq "y") { $logcheck = "checked" }
$bavgcheck = "";
if ($query->param("bavg$which") eq "y") { $bavgcheck = "checked" }
$basecheck = "";
if ($query->param("base$which") eq "y") { $basecheck = "checked"; $baseval = $query->param("basev$which") }
$ymincheck = "";
$ymaxcheck = "";
if ($query->param("ymin$which") eq "y") { $ymincheck = "checked"; $yminval = $query->param("yminv$which") }
if ($query->param("ymax$which") eq "y") { $ymaxcheck = "checked"; $ymaxval = $query->param("ymaxv$which") }
if ($dummy = $query->param("dfy$which")) {
$dfytop = "<option>$dummy</option>\n<option>---</option>\n" }
else { $dfytop = "" }
if ($dummy = $query->param("dfm$which")) {
$dfmtop = "<option>$dummy</option>\n<option>---</option>\n" }
else { $dfmtop = "" }
if ($dummy = $query->param("dfd$which")) {
$dfdtop = "<option>$dummy</option>\n<option>---</option>\n" }
else { $dfdtop = "" }
if ($dummy = $query->param("dfh$which")) {
$dfhtop = "<option>$dummy</option>\n<option>---</option>\n" }
else { $dfhtop = "" }
if ($dummy = $query->param("dfn$which")) {
$dfntop = "<option>$dummy</option>\n<option>---</option>\n" }
else { $dfntop = "" }
if ($dummy = $query->param("dty$which")) {
$dtytop = "<option>$dummy</option>\n<option>---</option>\n" }
else { $dtytop = "" }
if ($dummy = $query->param("dtm$which")) {
$dtmtop = "<option>$dummy</option>\n<option>---</option>\n" }
else { $dtmtop = "" }
if ($dummy = $query->param("dtd$which")) {
$dtdtop = "<option>$dummy</option>\n<option>---</option>\n" }
else { $dtdtop = "" }
if ($dummy = $query->param("dth$which")) {
$dthtop = "<option>$dummy</option>\n<option>---</option>\n" }
else { $dthtop = "" }
if ($dummy = $query->param("dtn$which")) {
$dtntop = "<option>$dummy</option>\n<option>---</option>\n" }
else { $dtntop = "" }
$lhourlist = "";
if ($query->param("lhv$which")) {
$lhourlist = "<option>" . $query->param("lhv$which") . "</option>";
}
foreach $hourval (24,36,48,60,72) {
if ($hourval != $query->param("lhv$which")) {
$lhourlist .= "<option>$hourval</option>";
}
}
$ldaylist = "";
if ($query->param("ldv$which")) {
$ldaylist = "<option>" . $query->param("ldv$which") . "</option>";
}
foreach $dayval (2,3,4,5,6,7,10,14,21,28,30,60,90,120,240,360,720) {
if ($dayval != $query->param("ldv$which")) {
$ldaylist .= "<option>$dayval</option>";
}
}
print << "EOF";
please select datafile $which:
<select name=df$which size=1>
$thisoptionlist
</select>
<br>
<input type=radio name=dr$which value=lh $lhc> Last
<font size=1>
<select name=lhv$which size=1>
$lhourlist
</select>
</font>
<font size=2>
hours&nbsp;&nbsp;&nbsp;
<input type=radio name=dr$which value=ld $ldc> Last
</font>
<font size=1>
<select name=ldv$which size=1>
$ldaylist
</select>
</font>
<font size=2>
days&nbsp;&nbsp;&nbsp;
$samerange
<br>
<input type=radio name=dr$which value=own $ownc> Enter range:
</font>
<font size=1>
<select name=dfy$which size=1>$dfytop$yearlist
</select>/<select name=dfm$which size=1>$dfmtop$monthlist
</select>/<select name=dfd$which size=1>$dfdtop$daylist
</select> <select name=dfh$which size=1>$dfhtop$hourlist
</select>:<select name=dfn$which size=1>$dfntop$minlist
</select> ->
<select name=dty$which size=1>$dtytop$yearlist
</select>/<select name=dtm$which size=1>$dtmtop$monthlist
</select>/<select name=dtd$which size=1>$dtdtop$daylist
</select> <select name=dth$which size=1>$dthtop$hourlist
</select>:<select name=dtn$which size=1>$dtntop$minlist
</select>
</font>
<font size=2>
<br>
<input type=checkbox name=log$which value=y $logcheck> Log y axis?
<input type=checkbox name=bavg$which value=y $bavgcheck> Baseline average, or
<input type=checkbox name=base$which value=y $basecheck> Baseline at:
<input type=text name=basev$which value="$baseval" size=8>
<input type=checkbox name=ymin$which value=y $ymincheck> Y min:
<input type=text name=yminv$which value="$yminval" size=8>
<input type=checkbox name=ymax$which value=y $ymaxcheck> Y max:
<input type=text name=ymaxv$which value="$ymaxval" size=8>
</font>
<hr>
EOF
} # end foreach $which
print << "EOF";
<input type=checkbox name=savem value=y> Save images in /tmp?
<br>
<input type=submit value="click here" name=submit> to plot stripcharts
</td></tr></table>
</form><p>
<a href=stripchart_20.cgi>Reset Form</a><p>
(Note: A set of stripcharts takes upwards to 10-60 seconds to generate)
</font>
</body></html>
EOF