[feedgnuplot] 01/01: test
Dima Kogan
dima at secretsauce.net
Wed Dec 4 10:31:04 UTC 2013
This is an automated email from the git hooks/post-receive script.
dkogan-guest pushed a commit to branch test
in repository feedgnuplot.
commit fe3a1af57973a83a92801eed27593c009cf7c919
Author: Dima Kogan <dima at secretsauce.net>
Date: Sat Nov 30 00:09:56 2013 -0800
test
---
README.pod | 1591 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 1590 insertions(+), 1 deletion(-)
diff --git a/README.pod b/README.pod
deleted file mode 120000
index abe0c14..0000000
--- a/README.pod
+++ /dev/null
@@ -1 +0,0 @@
-bin/feedgnuplot
\ No newline at end of file
diff --git a/README.pod b/README.pod
new file mode 100755
index 0000000..94c9912
--- /dev/null
+++ b/README.pod
@@ -0,0 +1,1590 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use Getopt::Long;
+use Time::HiRes qw( usleep gettimeofday tv_interval );
+use IO::Handle;
+use List::Util qw( first );
+use Scalar::Util qw( looks_like_number );
+use Text::ParseWords;
+use threads;
+use threads::shared;
+use Thread::Queue;
+use Pod::Usage;
+use Time::Piece;
+
+my $VERSION = 1.26;
+
+my %options;
+interpretCommandline();
+
+# list containing the plot data. Each element is a hashref of parameters.
+# $curve->{datastring} is a string of all the data in this curve that can be
+# sent directly to gnuplot. $curve->{datastring_meta} is a hashref {domain =>
+# ..., offset_start => ...}. offset_start represents a position in the
+# datastring where this particular data element begins. As the data is culled
+# with --xlen, the offsets are preserved by using $curve->{datastring_offset} to
+# represent the offset IN THE ORIGINAL STRING of the current start of the
+# datastring
+
+
+my @curves = ();
+
+# list mapping curve names to their indices in the @curves list
+my %curveIndices = ();
+
+# now start the data acquisition and plotting threads
+my $dataQueue;
+
+# Whether any new data has arrived since the last replot
+my $haveNewData;
+
+# when the last replot happened
+my $last_replot_time = [gettimeofday];
+
+# whether the previous replot was timer based
+my $last_replot_is_from_timer = 1;
+
+my $streamingFinished : shared = undef;
+
+if($options{stream})
+{
+ $dataQueue = Thread::Queue->new();
+ my $addThr = threads->create(\&mainThread);
+
+ # spawn the plot updating thread. If I'm replotting from a data trigger, I don't need this
+ my $plotThr = threads->create(\&plotUpdateThread) if $options{stream} > 0;
+
+ while(<>)
+ {
+ chomp;
+
+ last if /^exit/;
+
+ # place every line of input to the queue, so that the plotting thread can process it. if we are
+ # using an implicit domain (x = line number), then we send it on the data queue also, since
+ # $. is not meaningful in the plotting thread
+ if(!$options{domain})
+ {
+ $_ .= " $.";
+ }
+ $dataQueue->enqueue($_);
+ }
+
+ $streamingFinished = 1;
+
+ $plotThr->join() if defined $plotThr;
+ $addThr->join();
+}
+else
+{ mainThread(); }
+
+
+
+
+
+sub interpretCommandline
+{
+ # if I'm using a self-plotting data file with a #! line, then $ARGV[0] will contain ALL of the
+ # options and $ARGV[1] will contain the data file to plot. In this case I need to split $ARGV[0] so
+ # that GetOptions() can parse it correctly. On the other hand, if I'm plotting normally (not with
+ # #!) a file with spaces in the filename, I don't want to split the filename. Hopefully this logic
+ # takes care of both those cases.
+ if (exists $ARGV[0] && !-r $ARGV[0])
+ {
+ unshift @ARGV, shellwords shift @ARGV;
+ }
+
+ # everything off by default:
+ # do not stream in the data by default
+ # point plotting by default.
+ # no monotonicity checks by default
+ # normal histograms by default
+ $options{ maxcurves } = 100;
+ $options{ histstyle} = 'freq';
+
+ # Previously I was using 'legend=s%' and 'curvestyle=s%' for curve addressing. This had cleaner
+ # syntax, but disregarded the order of the given options. This resulted in arbitrarily ordered
+ # curves. I thus make parse these into lists, and then also make hashes, for later use
+
+ # needed for these to be parsed into an array-ref
+ $options{legend} = [];
+ $options{curvestyle} = [];
+ $options{histogram} = [];
+ GetOptions(\%options, 'stream:s', 'domain!', 'dataid!', '3d!', 'colormap!', 'lines!', 'points!',
+ 'circles', 'legend=s{2}', 'autolegend!', 'xlabel=s', 'ylabel=s', 'y2label=s', 'zlabel=s',
+ 'title=s', 'xlen=f', 'ymin=f', 'ymax=f', 'xmin=s', 'xmax=s', 'y2min=f', 'y2max=f',
+ 'zmin=f', 'zmax=f', 'y2=s@', 'curvestyle=s{2}', 'curvestyleall=s', 'extracmds=s@',
+ 'square!', 'square_xy!', 'hardcopy=s', 'maxcurves=i', 'monotonic!', 'timefmt=s',
+ 'histogram=s@', 'binwidth=f', 'histstyle=s',
+ 'terminal=s',
+ 'extraValuesPerPoint=i', 'help', 'dump', 'exit', 'version',
+ 'geometry=s') or pod2usage( -exitval => 1,
+ -verbose => 1, # synopsis and args
+ -output => \*STDERR );
+
+
+ # handle various cmdline-option errors
+ if ( $options{help} )
+ {
+ pod2usage( -exitval => 0,
+ -verbose => 1, # synopsis and args
+ -output => \*STDOUT );
+ }
+
+ if( $options{version} )
+ {
+ print "feedgnuplot version $VERSION\n";
+ exit 0;
+ }
+
+ # no global style if one isn't given
+ $options{curvestyleall} = '' unless defined $options{curvestyleall};
+
+ # expand options that are given as comma-separated lists
+ for my $listkey (qw(histogram y2))
+ {
+ @{$options{$listkey}} = map split('\s*,\s*', $_), @{$options{$listkey}}
+ if defined $options{$listkey};
+ }
+
+ # --legend and --curvestyle options are conceptually hashes, but are parsed as
+ # arrays in order to preserve the ordering. I parse both of these into hashes
+ # because those are useful to have later. After this I can access individual
+ # legends with $options{legend_hash}{curveid}
+ for my $listkey (qw(legend curvestyle))
+ {
+ $options{"${listkey}_hash"} = {};
+
+ my $n = scalar @{$options{$listkey}}/2;
+ foreach my $idx (0..$n-1)
+ {
+ $options{"${listkey}_hash"}{$options{$listkey}[$idx*2]} = $options{$listkey}[$idx*2 + 1];
+ }
+ }
+
+ if ( defined $options{hardcopy} && defined $options{stream} )
+ {
+ print STDERR "--stream doesn't make sense together with --hardcopy\n";
+ exit -1;
+ }
+
+ # parse stream option. Allowed only numbers >= 0 or 'trigger'. After this code
+ # $options{stream} is
+ # -1 for triggered replotting
+ # >0 for timed replotting
+ # undef if not streaming
+ if(defined $options{stream})
+ {
+ # if no streaming period is given, default to 1Hz.
+ $options{stream} = 1 if $options{stream} eq '';
+
+ if( !looks_like_number $options{stream} )
+ {
+ if($options{stream} eq 'trigger')
+ {
+ $options{stream} = 0;
+ }
+ else
+ {
+ print STDERR "--stream can only take in values >=0 or 'trigger'\n";
+ exit -1;
+ }
+ }
+
+ if ( $options{stream} == 0 )
+ {
+ $options{stream} = -1;
+ }
+ elsif ( $options{stream} <= 0)
+ {
+ print STDERR "--stream can only take in values >=0 or 'trigger'\n";
+ exit -1;
+ }
+ }
+
+ if ($options{colormap})
+ {
+ # colormap styles all curves with palette. Seems like there should be a way to do this with a
+ # global setting, but I can't get that to work
+ $options{curvestyleall} .= ' palette';
+ }
+
+ if ( $options{'3d'} )
+ {
+ if ( !$options{domain} )
+ {
+ print STDERR "--3d only makes sense with --domain\n";
+ exit -1;
+ }
+
+ if ( $options{timefmt} )
+ {
+ print STDERR "--3d makes no sense with --timefmt\n";
+ exit -1;
+ }
+
+ if ( defined $options{y2min} || defined $options{y2max} || defined $options{y2} )
+ {
+ print STDERR "--3d does not make sense with --y2...\n";
+ exit -1;
+ }
+
+ if ( defined $options{xlen} )
+ {
+ print STDERR "--3d does not make sense with --xlen\n";
+ exit -1;
+ }
+
+ if ( defined $options{monotonic} )
+ {
+ print STDERR "--3d does not make sense with --monotonic\n";
+ exit -1;
+ }
+
+ if ( defined $options{binwidth} || @{$options{histogram}} )
+ {
+ print STDERR "--3d does not make sense with histograms\n";
+ exit -1;
+ }
+
+ if ( defined $options{circles} )
+ {
+ print STDERR "--3d does not make sense with circles (gnuplot doesn't support this)\n";
+ exit -1;
+ }
+ }
+ else
+ {
+ if ( $options{timefmt} && !$options{domain} )
+ {
+ print STDERR "--timefmt makes sense only with --domain\n";
+ exit -1;
+ }
+
+ if(!$options{colormap})
+ {
+ if ( defined $options{zmin} || defined $options{zmax} || defined $options{zlabel} )
+ {
+ print STDERR "--zmin/zmax/zlabel only makes sense with --3d or --colormap\n";
+ exit -1;
+ }
+ }
+
+ if ( defined $options{square_xy} )
+ {
+ print STDERR "--square_xy only makes sense with --3d\n";
+ exit -1;
+ }
+ }
+
+ if(defined $options{xlen} && !$options{stream} )
+ {
+ print STDERR "--xlen does not make sense without --stream\n";
+ exit -1;
+ }
+
+ if($options{stream} && defined $options{xlen} &&
+ ( defined $options{xmin} || defined $options{xmax}))
+ {
+ print STDERR "With --stream and --xlen the X bounds are set, so neither --xmin nor --xmax make sense\n";
+ exit -1;
+ }
+
+ # --xlen implies an order to the data, so I force monotonicity
+ $options{monotonic} = 1 if defined $options{xlen};
+
+ if( $options{histstyle} !~ /freq|cum|uniq|cnorm/ )
+ {
+ print STDERR "unknown histstyle. Allowed are 'freq...', 'cum...', 'uniq...', 'cnorm...'\n";
+ exit -1;
+ }
+
+ # deal with timefmt
+ if ( $options{timefmt} )
+ {
+ # I need to compute a regex to match the time field and I need to count how
+ # many whilespace-separated fields there are.
+
+ # strip leading and trailing whitespace
+ $options{timefmt} =~ s/^\s*//;
+ $options{timefmt} =~ s/\s*$//;
+
+ my $Nfields = scalar split( ' ', $options{timefmt});
+ $options{timefmt_Ncols} = $Nfields;
+ my $regex_str = join( '\s+', ('\S+') x $Nfields );
+ $options{timefmt_regex} = qr/$regex_str/;
+
+ # make sure --xlen is an integer. With a timefmt xlen goes through strptime
+ # and strftime, and those are integer-only
+ if( defined $options{xlen} )
+ {
+ if( $options{xlen} - int($options{xlen}) )
+ {
+ say STDERR "When streaming --xlen MUST be an integer. Rounding up to the nearest second";
+ $options{xlen} = 1 + int($options{xlen});
+ }
+ }
+ }
+}
+
+sub getGnuplotVersion
+{
+ open(GNUPLOT_VERSION, 'gnuplot --version |') or die "Couldn't run gnuplot";
+ my ($gnuplotVersion) = <GNUPLOT_VERSION> =~ /gnuplot\s*(\d*\.\d*)/;
+ if (!$gnuplotVersion)
+ {
+ print STDERR "Couldn't find the version of gnuplot. Does it work? Trying anyway...\n";
+ $gnuplotVersion = 0;
+ }
+ close(GNUPLOT_VERSION);
+
+ return $gnuplotVersion;
+}
+
+sub plotUpdateThread
+{
+ while(! $streamingFinished)
+ {
+ usleep( $options{stream} * 1e6 );
+
+ # indicate that the timer was the replot source
+ $dataQueue->enqueue('replot timertick');
+ }
+
+ $dataQueue->enqueue(undef);
+}
+
+sub sendRangeCommand
+{
+ my ($name, $min, $max) = @_;
+
+ return unless defined $min || defined $max;
+
+ if( defined $min )
+ { $min = "\"$min\""; }
+ else
+ { $min = ''; }
+
+ if( defined $max )
+ { $max = "\"$max\""; }
+ else
+ { $max = ''; }
+
+ my $cmd = "set $name [$min:$max]\n";
+ print PIPE $cmd;
+}
+
+sub makeDomainNumeric
+{
+ my ($domain0) = @_;
+
+ if ( $options{timefmt} )
+ {
+ my $timepiece = Time::Piece->strptime( $domain0, $options{timefmt} )
+ or die "Couldn't parse time format. String '$domain0' doesn't fit format '$options{timefmt}'";
+
+ return $timepiece->epoch();
+ }
+
+ return $domain0;
+}
+
+sub mainThread
+{
+ my $valuesPerPoint = 1;
+ if($options{extraValuesPerPoint}) { $valuesPerPoint += $options{extraValuesPerPoint}; }
+ if($options{colormap}) { $valuesPerPoint++; }
+ if($options{circles} ) { $valuesPerPoint++; }
+
+ local *PIPE;
+ my $dopersist = '';
+
+ if( !$options{stream} && getGnuplotVersion() >= 4.3)
+ {
+ $dopersist = '--persist';
+ }
+
+ if(exists $options{dump})
+ {
+ *PIPE = *STDOUT;
+ }
+ else
+ {
+ my $geometry = defined $options{geometry} ?
+ "-geometry $options{geometry}" : '';
+ open PIPE, "|gnuplot $geometry $dopersist" or die "Can't initialize gnuplot\n";
+ }
+ autoflush PIPE 1;
+
+ my $outputfile;
+ my $outputfileType;
+ if( defined $options{hardcopy})
+ {
+ $outputfile = $options{hardcopy};
+ if( $outputfile =~ /^[^|] # starts with anything other than |
+ .* # stuff in the middle
+ \.(eps|ps|pdf|png|svg)$/ix) # ends with a known extension
+ {
+ $outputfileType = lc $1;
+ }
+
+ my %terminalOpts =
+ ( eps => 'postscript solid color enhanced eps',
+ ps => 'postscript solid color landscape 10',
+ pdf => 'pdfcairo solid color font ",10" size 11in,8.5in',
+ png => 'png size 1280,1024',
+ svg => 'svg');
+
+ if( !defined $options{terminal} &&
+ defined $outputfileType &&
+ $terminalOpts{$outputfileType} )
+ {
+ $options{terminal} = $terminalOpts{$outputfileType};
+ }
+
+ die "Asked to plot to file '$outputfile', but I don't know which terminal to use, and no --terminal given"
+ unless $options{terminal};
+ }
+ print PIPE "set terminal $options{terminal}\n" if $options{terminal};
+ print PIPE "set output \"$outputfile\"\n" if $outputfile;
+
+ # set up plotting style
+ my $style = '';
+ if($options{lines}) { $style .= 'lines';}
+ if($options{points}) { $style .= 'points';}
+ if($options{circles})
+ {
+ $options{curvestyleall} = "with circles $options{curvestyleall}";
+ }
+
+ print PIPE "set style data $style\n" if $style;
+ print PIPE "set grid\n";
+
+ print(PIPE "set xlabel \"$options{xlabel }\"\n") if defined $options{xlabel};
+ print(PIPE "set ylabel \"$options{ylabel }\"\n") if defined $options{ylabel};
+ print(PIPE "set zlabel \"$options{zlabel }\"\n") if defined $options{zlabel};
+ print(PIPE "set y2label \"$options{y2label}\"\n") if defined $options{y2label};
+ print(PIPE "set title \"$options{title }\"\n") if defined $options{title};
+
+ if($options{square})
+ {
+ # set a square aspect ratio. Gnuplot does this differently for 2D and 3D plots
+ if(! $options{'3d'})
+ {
+ print(PIPE "set size ratio -1\n");
+ }
+ else
+ {
+ print(PIPE "set view equal xyz\n");
+ }
+ }
+
+ if($options{square_xy})
+ {
+ print(PIPE "set view equal xy\n");
+ }
+
+# For the specified values, set the legend entries to 'title "blah blah"'
+ if(defined $options{legend} && @{$options{legend}})
+ {
+ # @{$options{legend}} is a list where consecutive pairs are (curveID,
+ # legend). I use $options{legend} here instead of $options{legend_hash}
+ # because I create a new curve when I see a new one, and the hash is
+ # unordered, thus messing up the ordering
+ my $n = scalar @{$options{legend}}/2;
+ foreach my $idx (0..$n-1)
+ {
+ setCurveLabel($options{legend}[$idx*2 ],
+ $options{legend}[$idx*2 + 1]);
+ }
+ }
+
+# add the extra curve options
+ if(defined $options{curvestyle} && @{$options{curvestyle}})
+ {
+ # @{$options{curvestyle}} is a list where consecutive pairs are (curveID,
+ # style). I use $options{curvestyle} here instead of
+ # $options{curvestyle_hash} because I create a new curve when I see a new
+ # one, and the hash is unordered, thus messing up the ordering
+ my $n = scalar @{$options{curvestyle}}/2;
+ foreach my $idx (0..$n-1)
+ {
+ addCurveOption($options{curvestyle}[$idx*2 ],
+ $options{curvestyle}[$idx*2 + 1]);
+ }
+ }
+
+# For the values requested to be printed on the y2 axis, set that
+ if( defined $options{y2} )
+ {
+ foreach (@{$options{y2}})
+ {
+ addCurveOption($_, 'axes x1y2');
+ }
+ }
+
+# timefmt
+ if( $options{timefmt} )
+ {
+ print(PIPE "set timefmt '$options{timefmt}'\n");
+ print(PIPE "set xdata time\n");
+ }
+
+# add the extra global options
+ if(defined $options{extracmds})
+ {
+ foreach (@{$options{extracmds}})
+ {
+ print(PIPE "$_\n");
+ }
+ }
+
+# set up histograms
+ if( defined $options{histogram} )
+ {
+ $options{binwidth} ||= 1; # if no binwidth given, set it to 1
+ print PIPE
+ "set boxwidth $options{binwidth}\n" .
+ "histbin(x) = $options{binwidth} * floor(0.5 + x/$options{binwidth})\n";
+ foreach (@{$options{histogram}})
+ {
+ setCurveAsHistogram( $_ );
+ }
+ }
+
+ # regexp for a possibly floating point, possibly scientific notation number
+ my $numRE = '-?\d*\.?\d+(?:[Ee][-+]?\d+)?';
+ my $domainRE = $options{timefmt_regex} || $numRE;
+
+
+ # a point may be preceded by an id
+ my $pointRE = $options{dataid} ? '(\S+)\s+' : '()';
+ $pointRE .= '(' . join('\s+', ($numRE) x $valuesPerPoint) . ')';
+ $pointRE = qr/$pointRE/;
+
+# set all the axis ranges
+ # If a bound isn't given I want to set it to the empty string, so I can communicate it simply to
+ # gnuplot
+ print PIPE "set xtics\n";
+
+ if($options{y2})
+ {
+ print PIPE "set ytics nomirror\n";
+ print PIPE "set y2tics\n";
+ # if any of the ranges are given, set the range
+ sendRangeCommand( "y2range", $options{y2min}, $options{y2max} );
+ }
+
+ # if any of the ranges are given, set the range
+ sendRangeCommand( "xrange", $options{xmin}, $options{xmax} );
+ sendRangeCommand( "yrange", $options{ymin}, $options{ymax} );
+ sendRangeCommand( "zrange", $options{zmin}, $options{zmax} );
+ sendRangeCommand( "cbrange", $options{zmin}, $options{zmax} ) if($options{colormap});
+
+
+
+
+ # latest domain variable present in our data
+ my $latestX;
+
+ # The domain of the current point
+ my @domain;
+
+ # The x-axis domain represented as a number. This is exactly the same as
+ # $domain[0] unless the x-axis domain uses a timefmt. Then this is the
+ # number of seconds since the UNIX epoch.
+ my $domain0_numeric;
+
+ # I should be using the // operator, but I'd like to be compatible with perl 5.8
+ while( $_ = (defined $dataQueue ? $dataQueue->dequeue() : <>))
+ {
+ next if /^#/o;
+
+ if( $options{stream} )
+ {
+ if(/^clear/o )
+ {
+ clearCurves();
+ next;
+ }
+
+ if(/^replot/o )
+ {
+ # /timertick/ determines if the timer was the source of the replot
+ replot( $domain0_numeric, /timertick/ );
+ next;
+ }
+
+ # /exit/ is handled in the data-reading thread
+ }
+
+ if(! /^replot/o)
+ {
+ # parse the incoming data lines. The format is
+ # x id0 dat0 id1 dat1 ....
+ # where idX is the ID of the curve that datX corresponds to
+ #
+ # $options{domain} indicates whether the initial 'x' is given or not (if not, the line
+ # number is used)
+ # $options{dataid} indicates whether idX is given or not (if not, the point order in the
+ # line is used)
+ # 3d plots require $options{domain}, and dictate "x y" for the domain instead of just "x"
+
+ if($options{domain})
+ {
+ /($domainRE)/go or next;
+ $domain[0] = $1;
+ $domain0_numeric = makeDomainNumeric( $domain[0] );
+
+ if($options{'3d'})
+ {
+ /($numRE)/go or next;
+ $domain[1] = $1;
+ }
+ elsif( $options{monotonic} )
+ {
+ if( defined $latestX && $domain0_numeric < $latestX )
+ {
+ # the x-coordinate of the new point is in the past, so I wipe out
+ # all the data and start anew. Before I wipe the old data, I
+ # replot the old data
+ replot( $domain0_numeric );
+ clearCurves();
+ $latestX = undef;
+ }
+ else
+ { $latestX = $domain0_numeric; }
+ }
+
+ }
+ else
+ {
+ # since $. is not meaningful in the plotting thread if we're using the data queue, we pass
+ # $. on the data queue in that case
+ if(defined $dataQueue)
+ {
+ s/ ([\d]+)$//o;
+ $domain[0] = $1;
+ }
+ else
+ {
+ $domain[0] = $.;
+ }
+ $domain0_numeric = makeDomainNumeric( $domain[0] );
+ }
+
+ my $id = -1;
+ while (/$pointRE/go)
+ {
+ if($1 ne '') {$id = $1;}
+ else {$id++; }
+
+ pushPoint(getCurve($id),
+ "@domain $2\n", $domain0_numeric);
+ }
+ }
+ }
+
+ # if we were streaming, we're now done!
+ if( $options{stream} )
+ {
+ return;
+ }
+
+ # finished reading in all. Plot what we have
+ plotStoredData();
+
+ if ( defined $options{hardcopy})
+ {
+ print PIPE "set output\n";
+
+ # sleep until the plot file exists, and it is closed. Sometimes the output
+ # is still being written at this point. If the output filename starts with
+ # '|', gnuplot pipes the output to that process, instead of writing to a
+ # file. In that case I don't make sure the file exists, since there IS not
+ # file
+ if( $options{hardcopy} !~ /^\|/ )
+ {
+ usleep(100_000) until -e $outputfile;
+ usleep(100_000) until(system("fuser -s \"$outputfile\""));
+ }
+
+ print "Wrote output to $outputfile\n";
+ return;
+ }
+
+ # we persist gnuplot, so we shouldn't need this sleep. However, once
+ # gnuplot exits, but the persistent window sticks around, you can no
+ # longer interactively zoom the plot. So we still sleep
+ sleep(100000) unless $options{dump} || $options{exit};
+}
+
+sub pruneOldData
+{
+ my ($oldestx) = @_;
+
+ foreach my $curve (@curves)
+ {
+ next unless $curve->{datastring};
+
+ my $meta = $curve->{datastring_meta};
+
+ my $firstInWindow = first {$meta->[$_]{domain} >= $oldestx} 0..$#$meta;
+ if ( !defined $firstInWindow )
+ {
+ # everything is too old. Clear out all the data
+ $curve->{datastring} = '';
+ $curve->{datastring_meta} = [];
+ $curve->{datastring_offset} = 0;
+ }
+ elsif ( $firstInWindow >= 2 )
+ {
+ # clear out everything that's too old, except for one point. This point
+ # will be off the plot, but if we're plotting lines there will be a
+ # connecting line to it. Some of the line will be visible
+ substr( $curve->{datastring}, 0,
+ $meta->[$firstInWindow-1]{offset_start} - $curve->{datastring_offset},
+ '' );
+ $curve->{datastring_offset} = $meta->[$firstInWindow-1]{offset_start};
+ }
+ }
+}
+
+sub plotStoredData
+{
+ # get the options for those curves that havse any data
+ my @nonemptyCurves = grep { $_->{datastring} } @curves;
+ my @extraopts = map {$_->{options}} @nonemptyCurves;
+
+ my $body = join(', ' , map({ "'-' $_" } @extraopts) );
+ if($options{'3d'}) { print PIPE "splot $body\n"; }
+ else { print PIPE "plot $body\n"; }
+
+ foreach my $curve (@nonemptyCurves)
+ {
+ print PIPE $curve->{datastring};
+ print PIPE "e\n";
+ }
+}
+
+sub updateCurveOptions
+{
+ # generates the 'options' string for a curve, based on its legend title and its other options
+ # These could be integrated into a single string, but that raises an issue in the no-title
+ # case. When no title is specified, gnuplot will still add a legend entry with an unhelpful '-'
+ # label. Thus I explicitly do 'notitle' for that case
+
+ my ($curve, $id) = @_;
+
+ # use the given title, unless we're generating a legend automatically. Given titles
+ # override autolegend
+ my $title;
+ if(defined $curve->{title})
+ { $title = $curve->{title}; }
+ elsif( $options{autolegend} )
+ { $title = $id; }
+
+ my $titleoption = defined $title ? "title \"$title\"" : "notitle";
+
+ my $curvestyleall = '';
+ $curvestyleall = $options{curvestyleall}
+ if defined $options{curvestyleall} && !defined $options{curvestyle_hash}{$id};
+
+ my $histoptions = $curve->{histoptions} || '';
+
+ my $usingoptions = '';
+ if( $options{timefmt} )
+ {
+ $usingoptions = "using 1:" . ($options{timefmt_Ncols}+1);
+ }
+
+ $curve->{options} = "$histoptions $usingoptions $titleoption $curve->{extraoptions} $curvestyleall";
+}
+
+sub getCurve
+{
+ # This function returns the curve corresponding to a particular label, creating a new curve if
+ # necessary
+
+ if(scalar @curves >= $options{maxcurves})
+ {
+ print STDERR "Tried to exceed the --maxcurves setting.\n";
+ print STDERR "Invoke with a higher --maxcurves limit if you really want to do this.\n";
+ exit -1;
+ }
+
+ my ($id) = @_;
+
+ if( !exists $curveIndices{$id} )
+ {
+ push @curves, {extraoptions => ' ',
+ datastring => '',
+ datastring_meta => [],
+ datastring_offset => 0}; # push a curve with no data and no options
+ $curveIndices{$id} = $#curves;
+
+ updateCurveOptions($curves[$#curves], $id);
+ }
+ return $curves[$curveIndices{$id}];
+}
+
+sub addCurveOption
+{
+ my ($id, $str) = @_;
+
+ my $curve = getCurve($id);
+ $curve->{extraoptions} .= "$str ";
+ updateCurveOptions($curve, $id);
+}
+
+sub setCurveLabel
+{
+ my ($id, $str) = @_;
+
+ my $curve = getCurve($id);
+ $curve->{title} = $str;
+ updateCurveOptions($curve, $id);
+}
+
+sub setCurveAsHistogram
+{
+ my ($id, $str) = @_;
+
+ my $curve = getCurve($id);
+ $curve->{histoptions} = 'using (histbin($2)):(1.0) smooth ' . $options{histstyle};
+
+ updateCurveOptions($curve, $id);
+}
+
+# remove all the curve data
+sub clearCurves
+{
+ foreach my $curve(@curves)
+ {
+ $curve->{datastring} = '';
+ $curve->{datastring_meta} = [];
+ $curve->{datastring_offset} = 0;
+ }
+}
+
+sub replot
+{
+ return unless $haveNewData;
+ $haveNewData = undef;
+
+ return if !$options{stream};
+
+
+ # The logic involving domain rollover replotting due to --monotonic is a bit
+ # tricky. I want this:
+
+ # if( domain rolls over slowly )
+ # {
+ # should update on a timer;
+ # when the domain rolls over, --monotonic should force a replot
+ # }
+ # if( domain rolls over quickly )
+ # {
+ # should update when the domain rolls over,
+ # at most as quickly as the timer indicates
+ # }
+
+
+ my ($domain0_numeric, $replot_is_from_timer) = @_;
+
+ my $now = [gettimeofday];
+
+ if( # If there is no replot timer at all, replot at any indication
+ $options{stream} < 0 ||
+
+ # if the last replot was timer-based, but this one isn't, force a replot.
+ # This makes sure that a replot happens for a domain rollover shortly
+ # after a timer replot
+ !$replot_is_from_timer && $last_replot_is_from_timer ||
+
+ # if enough time has elapsed since the last replot, it's ok to replot
+ tv_interval ( $last_replot_time, $now ) > 0.8*$options{stream} )
+ {
+ # ok, then. We really need to replot
+ if ( defined $options{xlen} )
+ {
+ # we have an --xlen, so we need to clean out the old data
+ pruneOldData( $domain0_numeric - $options{xlen} );
+
+ my ($xmin, $xmax) = ($domain0_numeric - $options{xlen}, $domain0_numeric);
+ if ( defined $options{timefmt} )
+ {
+ # if we're using a timefmt, I need to convert my xmin range from
+ # seconds-since-the-epoch BACK to the timefmt. Sheesh
+ ($xmin, $xmax) = map {Time::Piece->strptime( $_, '%s' )->strftime( $options{timefmt} ) } ($xmin, $xmax);
+ }
+ sendRangeCommand( "xrange", $xmin, $xmax );
+ }
+
+ plotStoredData();
+
+
+ # update replot state
+ $last_replot_time = $now;
+ $last_replot_is_from_timer = $replot_is_from_timer;
+ }
+}
+
+# function to add a point to the plot. Assumes that the curve indexed by $idx already exists
+sub pushPoint
+{
+ my ($curve, $datastring, $domain0_numeric) = @_;
+
+ push @{$curve->{datastring_meta}}, { offset_start => length( $curve->{datastring} ) + $curve->{datastring_offset},
+ domain => $domain0_numeric };
+ $curve->{datastring} .= $datastring;
+
+ $haveNewData = 1;
+}
+
+
+=head1 NAME
+
+feedgnuplot - General purpose pipe-oriented plotting tool
+
+=head1 SYNOPSIS
+
+Simple plotting of piped data:
+
+ $ seq 5 | awk '{print 2*$1, $1*$1}'
+ 2 1
+ 4 4
+ 6 9
+ 8 16
+ 10 25
+
+ $ seq 5 | awk '{print 2*$1, $1*$1}' |
+ feedgnuplot --lines --points --legend 0 "data 0" --title "Test plot" --y2 1
+
+Simple real-time plotting example: plot how much data is received on the wlan0
+network interface in bytes/second (uses bash, awk and Linux):
+
+ $ while true; do sleep 1; cat /proc/net/dev; done |
+ gawk '/wlan0/ {if(b) {print $2-b; fflush()} b=$2}' |
+ feedgnuplot --lines --stream --xlen 10 --ylabel 'Bytes/sec' --xlabel seconds
+
+=head1 DESCRIPTION
+
+This is a flexible, command-line-oriented frontend to Gnuplot. It creates
+plots from data coming in on STDIN or given in a filename passed on the
+commandline. Various data representations are supported, as is hardcopy
+output and streaming display of live data. A simple example:
+
+ $ seq 5 | awk '{print 2*$1, $1*$1}' | feedgnuplot
+
+You should see a plot with two curves. The C<awk> command generates some data to
+plot and the C<feedgnuplot> reads it in from STDIN and generates the plot. The
+C<awk> invocation is just an example; more interesting things would be plotted
+in normal usage. No commandline-options are required for the most basic
+plotting. Input parsing is flexible; every line need not have the same number of
+points. New curves will be created as needed.
+
+The most commonly used functionality of gnuplot is supported directly by the
+script. Anything not directly supported can still be done with the
+C<--extracmds> and C<--curvestyle> options. Arbitrary gnuplot commands can be
+passed in with C<--extracmds>. For example, to turn off the grid, pass in
+C<--extracmds 'unset grid'>. As many of these options as needed can be passed
+in. To add arbitrary curve styles, use C<--curvestyle curveID extrastyle>. Pass
+these more than once to affect more than one curve. To apply an extra style to
+I<all> the curves that lack an explicit C<--curvestyle>, pass in
+C<--curvestyleall extrastyle>.
+
+=head2 Data formats
+
+By default, each value present in the incoming data represents a distinct data
+point, as demonstrated in the original example above (we had 10 numbers in the
+input and 10 points in the plot). If requested, the script supports more
+sophisticated interpretation of input data
+
+=head3 Domain selection
+
+If C<--domain> is passed in, the first value on each line of input is
+interpreted as the I<X>-value for the rest of the data on that line. Without
+C<--domain> the I<X>-value is the line number, and the first value on a line is
+a plain data point like the others. Default is C<--nodomain>. Thus the original
+example above produces 2 curves, with B<1,2,3,4,5> as the I<X>-values. If we run
+the same command with --domain:
+
+ $ seq 5 | awk '{print 2*$1, $1*$1}' | feedgnuplot --domain
+
+we get only 1 curve, with B<2,4,6,8,10> as the I<X>-values. As many points as
+desired can appear on a single line, but all points on a line are associated
+with the I<X>-value at the start of that line.
+
+=head3 Curve indexing
+
+By default, each column represents a separate curve. This is fine unless sparse
+data is to be plotted. With the C<--dataid> option, each point is represented by
+2 values: a string identifying the curve, and the value itself. If we add
+C<--dataid> to the original example:
+
+ $ seq 5 | awk '{print 2*$1, $1*$1}' | feedgnuplot --dataid --autolegend
+
+we get 5 different curves with one point in each. The first column, as produced
+by C<awk>, is B<2,4,6,8,10>. These are interpreted as the IDs of the curves to
+be plotted. The C<--autolegend> option adds a legend using the given IDs to
+label the curves. The IDs need not be numbers; generic strings are accepted. As
+many points as desired can appear on a single line. C<--domain> can be used in
+conjunction with C<--dataid>.
+
+=head3 Multi-value style support
+
+Depending on how gnuplot is plotting the data, more than one value may be needed
+to represent a single point. For example, the script has support to plot all the
+data with C<--circles>. This requires a radius to be specified for each point in
+addition to the position of the point. Thus, when plotting with C<--circles>, 2
+numbers are read for each data point instead of 1. A similar situation exists
+with C<--colormap> where each point contains the position I<and> the
+color. There are other gnuplot styles that require more data (such as error
+bars), but none of these are directly supported by the script. They can still be
+used, though, by specifying the specific style with C<--curvestyle>, and
+specifying how many extra values are needed for each point with
+C<--extraValuesPerPoint extra>. C<--extraValuesPerPoint> is ONLY needed for the
+styles not explicitly supported; supported styles set that variable
+automatically.
+
+=head3 3D data
+
+To plot 3D data, pass in C<--3d>. C<--domain> MUST be given when plotting 3D
+data to avoid domain ambiguity. If 3D data is being plotted, there are by
+definition 2 domain values instead of one (I<Z> as a function of I<X> and I<Y>
+instead of I<Y> as a function of I<X>). Thus the first 2 values on each line are
+interpreted as the domain instead of just 1. The rest of the processing happens
+the same way as before.
+
+=head3 Time/date data
+
+If the input data domain is a time/date, this can be interpreted with
+C<--timefmt>. This option takes a single argument: the format to use to parse
+the data. The format is documented in 'set timefmt' in gnuplot, although the
+common flags that C<strftime> understands are generally supported. The backslash
+sequences in the format are I<not> supported, so if you want a tab, put in a tab
+instead of \t. Whitespace in the format I<is> supported. When this flag is
+given, some other options act a little bit differently:
+
+=over
+
+=item
+
+C<--xlen> is an I<integer> in seconds
+
+=item
+
+C<--xmin> and C<--xmax> I<must> use the format passed in to C<--timefmt>
+
+=back
+
+Using this option changes both the way the input is parsed I<and> the way the
+x-axis tics are labelled. Gnuplot tries to be intelligent in this labelling, but
+it doesn't always to what the user wants. The labelling can be controlled with
+the gnuplot C<set format> command, which takes the same type of format string as
+C<--timefmt>. Example:
+
+ $ sar 1 -1 |
+ awk '$1 ~ /..:..:../ && $8 ~/^[0-9\.]*$/ {print $1,$8; fflush()}' |
+ feedgnuplot --stream --domain
+ --lines --timefmt '%H:%M:%S'
+ --extracmds 'set format x "%H:%M:%S"'
+
+This plots the 'idle' CPU consumption against time.
+
+Note that while gnuplot supports the time/date on any axis, I<feedgnuplot>
+currently supports it I<only> as the x-axis domain. This may change in the
+future.
+
+=head2 Real-time streaming data
+
+To plot real-time data, pass in the C<--stream [refreshperiod]> option. Data
+will then be plotted as it is received. The plot will be updated every
+C<refreshperiod> seconds. If the period isn't specified, a 1Hz refresh rate is
+used. To refresh at specific intervals indicated by the data, set the
+refreshperiod to 0 or to 'trigger'. The plot will then I<only> be refreshed when
+a data line 'replot' is received. This 'replot' command works in both triggered
+and timed modes, but in triggered mode, it's the only way to replot. Look in
+L</"Special data commands"> for more information.
+
+To plot only the most recent data (instead of I<all> the data), C<--xlen
+windowsize> can be given. This will create an constantly-updating, scrolling
+view of the recent past. C<windowsize> should be replaced by the desired length
+of the domain window to plot, in domain units (passed-in values if C<--domain>
+or line numbers otherwise). If the domain is a time/date via C<--timefmt>, then
+C<windowsize> is and I<integer> in seconds.
+
+=head3 Special data commands
+
+If we are reading streaming data, the input stream can contain special commands
+in addition to the raw data. Feedgnuplot looks for these at the start of every
+input line. If a command is detected, the rest of the line is discarded. These
+commands are
+
+=over
+
+=item C<replot>
+
+This command refreshes the plot right now, instead of waiting for the next
+refresh time indicated by the timer. This command works in addition to the timed
+refresh, as indicated by C<--stream [refreshperiod]>.
+
+=item C<clear>
+
+This command clears out the current data in the plot. The plotting process
+continues, however, to any data following the C<clear>.
+
+=item C<exit>
+
+This command causes feedgnuplot to exit.
+
+=back
+
+=head2 Hardcopy output
+
+The script is able to produce hardcopy output with C<--hardcopy outputfile>. The
+output type can be inferred from the filename, if B<.ps>, B<.eps>, B<.pdf>,
+B<.svg> or B<.png> is requested. If any other file type is requested,
+C<--terminal> I<must> be passed in to tell gnuplot how to make the plot.
+
+=head2 Self-plotting data files
+
+This script can be used to enable self-plotting data files. There are 2 ways of
+doing this: with a shebang (#!) or with inline perl data.
+
+=head3 Self-plotting data with a #!
+
+A self-plotting, executable data file C<data> is formatted as
+
+ $ cat data
+ #!/usr/bin/feedgnuplot --lines --points
+ 2 1
+ 4 4
+ 6 9
+ 8 16
+ 10 25
+ 12 36
+ 14 49
+ 16 64
+ 18 81
+ 20 100
+ 22 121
+ 24 144
+ 26 169
+ 28 196
+ 30 225
+
+This is the shebang (#!) line followed by the data, formatted as before. The
+data file can be plotted simply with
+
+ $ ./data
+
+The caveats here are that on Linux the whole #! line is limited to 127 charaters
+and that the full path to feedgnuplot must be given. The 127 character limit is
+a serious limitation, but this can likely be resolved with a kernel patch. I
+have only tried on Linux 2.6.
+
+=head3 Self-plotting data with perl inline data
+
+Perl supports storing data and code in the same file. This can also be used to
+create self-plotting files:
+
+ $ cat plotdata.pl
+ #!/usr/bin/perl
+ use strict;
+ use warnings;
+
+ open PLOT, "| feedgnuplot --lines --points" or die "Couldn't open plotting pipe";
+ while( <DATA> )
+ {
+ my @xy = split;
+ print PLOT "@xy\n";
+ }
+ __DATA__
+ 2 1
+ 4 4
+ 6 9
+ 8 16
+ 10 25
+ 12 36
+ 14 49
+ 16 64
+ 18 81
+ 20 100
+ 22 121
+ 24 144
+ 26 169
+ 28 196
+ 30 225
+
+This is especially useful if the logged data is not in a format directly
+supported by feedgnuplot. Raw data can be stored after the __DATA__ directive,
+with a small perl script to manipulate the data into a useable format and send
+it to the plotter.
+
+=head1 ARGUMENTS
+
+=over
+
+=item
+
+--[no]domain
+
+If enabled, the first element of each line is the domain variable. If not, the
+point index is used
+
+=item
+
+--[no]dataid
+
+If enabled, each data point is preceded by the ID of the data set that point
+corresponds to. This ID is interpreted as a string, NOT as just a number. If not
+enabled, the order of the point is used.
+
+As an example, if line 3 of the input is "0 9 1 20" then
+
+=over
+
+=item
+
+'--nodomain --nodataid' would parse the 4 numbers as points in 4 different
+curves at x=3
+
+=item
+
+'--domain --nodataid' would parse the 4 numbers as points in 3 different
+curves at x=0. Here, 0 is the x-variable and 9,1,20 are the data values
+
+=item
+
+'--nodomain --dataid' would parse the 4 numbers as points in 2 different
+curves at x=3. Here 0 and 1 are the data IDs and 9 and 20 are the
+data values
+
+=item
+
+'--domain --dataid' would parse the 4 numbers as a single point at
+x=0. Here 9 is the data ID and 1 is the data value. 20 is an extra
+value, so it is ignored. If another value followed 20, we'd get another
+point in curve ID 20
+
+=back
+
+=item
+
+--[no]3d
+
+Do [not] plot in 3D. This only makes sense with --domain. Each domain here is an
+(x,y) tuple
+
+=item
+
+--timefmt [format]
+
+Interpret the X data as a time/date, parsed with the given format
+
+=item
+
+--colormap
+
+Show a colormapped xy plot. Requires extra data for the color. zmin/zmax can be
+used to set the extents of the colors. Automatically increments
+C<--extraValuesPerPoint>
+
+=item
+
+--stream [period]
+
+Plot the data as it comes in, in realtime. If period is given, replot every
+period seconds. If no period is given, replot at 1Hz. If the period is given as
+0 or 'trigger', replot I<only> when the incoming data dictates this. See the
+L</"Real-time streaming data"> section of the man page.
+
+=item
+
+--[no]lines
+
+Do [not] draw lines to connect consecutive points
+
+=item
+
+--[no]points
+
+Do [not] draw points
+
+=item
+
+--circles
+
+Plot with circles. This requires a radius be specified for each point.
+Automatically increments C<--extraValuesPerPoint>). C<Not> supported for 3d
+plots.
+
+=item
+
+--title xxx
+
+Set the title of the plot
+
+=item
+
+--legend curveID legend
+
+Set the label for a curve plot. Use this option multiple times for multiple
+curves. With --dataid, curveID is the ID. Otherwise, it's the index of the
+curve, starting at 0
+
+=item
+
+--autolegend
+
+Use the curve IDs for the legend. Titles given with --legend override these
+
+=item
+
+--xlen xxx
+
+When using --stream, sets the size of the x-window to plot. Omit this or set it
+to 0 to plot ALL the data. Does not make sense with 3d plots. Implies
+--monotonic
+
+=item
+
+--xmin/xmax/ymin/ymax/y2min/y2max/zmin/zmax xxx
+
+Set the range for the given axis. These x-axis bounds are ignored in a streaming
+plot. The y2-axis bound do not apply in 3d plots. The z-axis bounds apply
+I<only> to 3d plots or colormaps.
+
+=item
+
+--xlabel/ylabel/y2label/zlabel xxx
+
+Label the given axis. The y2-axis label does not apply to 3d plots while the
+z-axis label applies I<only> to 3d plots.
+
+=item
+
+--y2 xxx
+
+Plot the data specified by this curve ID on the y2 axis. Without --dataid, the
+ID is just an ordered 0-based index. Does not apply to 3d plots. Can be passed
+multiple times, or passed a comma-separated list. By default the y2-axis curves
+look the same as the y-axis ones. I.e. the viewer of the resulting plot has to
+be told which is which via an axes label, legend, etc. Prior to version 1.25 of
+feedgnuplot the curves plotted on the y2 axis were drawn with a thicker line.
+This is no longer the case, but that behavior can be brought back by passing
+something like
+
+ --y2 curveid --curvestyle curveid 'linewidth 3'
+
+=item
+
+--histogram curveID
+
+
+Set up a this specific curve to plot a histogram. The bin width is given with
+the --binwidth option (assumed 1.0 if omitted). --histogram does NOT touch the
+drawing style. It is often desired to plot these with boxes, and this MUST be
+explicitly requested with --curvestyleall 'with boxes'. This works with --domain
+and/or --stream, but in those cases the x-value is used ONLY to cull old data
+because of --xlen or --monotonic. I.e. the x-values are NOT drawn in any way.
+Can be passed multiple times, or passed a comma- separated list
+
+=item
+
+--binwidth width
+
+The width of bins when making histograms. This setting applies to ALL histograms
+in the plot. Defaults to 1.0 if not given.
+
+=item
+
+--histstyle style
+
+Normally, histograms are generated with the 'smooth freq' gnuplot style.
+--histstyle can be used to select different 'smooth' settings. Allowed are
+'unique', 'cumulative' and 'cnormal'. 'unique' indicates whether a bin has at
+least one item in it: instead of counting the items, it'll always report 0 or 1.
+'cumulative' is the integral of the "normal" histogram. 'cnormal' is like
+'cumulative', but rescaled to end up at 1.0.
+
+=item
+
+--curvestyle curveID
+
+style Additional styles per curve. With --dataid, curveID is the ID. Otherwise,
+it's the index of the curve, starting at 0. Use this option multiple times for
+multiple curves. --curvestylall does NOT apply to curves that have a
+--curvestyle
+
+=item
+
+--curvestyleall xxx
+
+Additional styles for all curves that have no --curvestyle
+
+=item
+
+--extracmds xxx
+
+Additional commands. These could contain extra global styles for instance. Can
+be passed multiple times.
+
+=item
+
+--square
+
+Plot data with aspect ratio 1. For 3D plots, this controls the aspect ratio for
+all 3 axes
+
+=item
+
+--square_xy
+
+For 3D plots, set square aspect ratio for ONLY the x,y axes
+
+=item
+
+--hardcopy xxx
+
+If not streaming, output to a file specified here. Format inferred from
+filename, unless specified by --terminal
+
+=item
+
+--terminal xxx
+
+String passed to 'set terminal'. No attempts are made to validate this.
+--hardcopy sets this to some sensible defaults if --hardcopy is given .png,
+.pdf, .ps, .eps or .svg. If any other file type is desired, use both --hardcopy
+and --terminal
+
+=item
+
+--maxcurves xxx
+
+The maximum allowed number of curves. This is 100 by default, but can be reset
+with this option. This exists purely to prevent perl from allocating all of the
+system's memory when reading bogus data
+
+=item
+
+--monotonic
+
+If --domain is given, checks to make sure that the x- coordinate in the input
+data is monotonically increasing. If a given x-variable is in the past, all data
+currently cached for this curve is purged. Without --monotonic, all data is
+kept. Does not make sense with 3d plots. No --monotonic by default. The data is
+replotted before being purged
+
+=item
+
+--extraValuesPerPoint
+
+xxx How many extra values are given for each data point. Normally this is 0, and
+does not need to be specified, but sometimes we want extra data, like for colors
+or point sizes or error bars, etc. feedgnuplot options that require this
+(colormap, circles) automatically set it. This option is ONLY needed if unknown
+styles are used, with --curvestyleall for instance
+
+=item
+
+--dump
+
+Instead of printing to gnuplot, print to STDOUT. Very useful for debugging. It
+is possible to send the output produced this way to gnuplot directly.
+
+=item
+
+--exit
+
+Terminate the feedgnuplot process after passing data to gnuplot. The window will
+persist but will not be interactive. Without this option feedgnuplot keeps
+running and must be killed by the user. Note that this option works only with
+later versions of gnuplot and only with some gnuplot terminals.
+
+=item
+
+--geometry
+
+If using X11, specifies the size, position of the plot window
+
+=item
+
+--version
+
+Print the version and exit
+
+=back
+
+=head1 RECIPES
+
+=head2 Basic plotting of piped data
+
+ $ seq 5 | awk '{print 2*$1, $1*$1}'
+ 2 1
+ 4 4
+ 6 9
+ 8 16
+ 10 25
+
+ $ seq 5 | awk '{print 2*$1, $1*$1}' |
+ feedgnuplot --lines --points --legend 0 "data 0" --title "Test plot" --y2 1
+
+=head2 Realtime plot of network throughput
+
+Looks at wlan0 on Linux.
+
+ $ while true; do sleep 1; cat /proc/net/dev; done |
+ gawk '/wlan0/ {if(b) {print $2-b; fflush()} b=$2}' |
+ feedgnuplot --lines --stream --xlen 10 --ylabel 'Bytes/sec' --xlabel seconds
+
+=head2 Realtime plot of battery charge in respect to time
+
+Uses the result of the C<acpi> command.
+
+ $ while true; do acpi; sleep 15; done |
+ perl -nE 'BEGIN{ $| = 1; } /([0-9]*)%/; say join(" ", time(), $1);' |
+ feedgnuplot --stream --ymin 0 --ymax 100 --lines --domain --xlabel 'Time' --timefmt '%s' --ylabel "Battery charge (%)"
+
+=head2 Realtime plot of temperatures in an IBM Thinkpad
+
+Uses C</proc/acpi/ibm/thermal>, which reports temperatures at various locations
+in a Thinkpad.
+
+ $ while true; do cat /proc/acpi/ibm/thermal | awk '{$1=""; print}' ; sleep 1; done |
+ feedgnuplot --stream --xlen 100 --lines --autolegend --ymax 100 --ymin 20 --ylabel 'Temperature (deg C)'
+
+=head2 Plotting a histogram of file sizes in a directory
+
+ $ ls -l | awk '{print $5/1e6}' |
+ feedgnuplot --histogram 0 --curvestyleall 'with boxes' --ymin 0 --xlabel 'File size (MB)' --ylabel Frequency
+
+=head1 ACKNOWLEDGEMENT
+
+This program is originally based on the driveGnuPlots.pl script from
+Thanassis Tsiodras. It is available from his site at
+L<http://users.softlab.ece.ntua.gr/~ttsiod/gnuplotStreaming.html>
+
+=head1 REPOSITORY
+
+L<https://github.com/dkogan/feedgnuplot>
+
+=head1 AUTHOR
+
+Dima Kogan, C<< <dima at secretsauce.net> >>
+
+=head1 LICENSE AND COPYRIGHT
+
+Copyright 2011-2012 Dima Kogan.
+
+This program is free software; you can redistribute it and/or modify it
+under the terms of either: the GNU General Public License as published
+by the Free Software Foundation; or the Artistic License.
+
+See http://dev.perl.org/licenses/ for more information.
+
+=cut
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-science/packages/feedgnuplot.git
More information about the debian-science-commits
mailing list