Compare commits

..

3 Commits

Author SHA1 Message Date
Dima Kogan
fa997f2c16 changelog bump 2014-08-22 17:26:39 -07:00
Dima Kogan
62a38826ee Merge commit '8cc51461a327a6c792e81617ab57b7604b1c9187' into stir_nopersist 2014-08-22 17:26:13 -07:00
Dima Kogan
8cc51461a3 disabling --persist because on our arm board it confuses things
with --persist enabled even something basic like

 seq 5 | feedgnuplot --terminal dumb

does not work. feedgnuplot simply sits there doing nothing. This is likely a
gnuplot bug. Those boards run an older gnuplot build, and I can't easily install
a new one to test. It's possible the newer gnuplots fix this, but it's easier to
release a new feedgnuplot instead
2014-08-22 17:18:30 -07:00
4 changed files with 167 additions and 307 deletions

20
Changes
View File

@@ -1,23 +1,3 @@
feedgnuplot (1.36)
* Added --equation to plot symbolic equations
-- Dima Kogan <dima@secretsauce.net> Fri, 13 Nov 2015 11:08:26 -0800
feedgnuplot (1.35)
* replaced a 'say' with 'print'. Should work better with ancient perls
* an "exit" command now has effect even with triggered-only replotting
* More sophisticated handling of termination conditions:
- Without --exit, we always end up with an interactive plot when the
input data is exhausted or when the user sends a ^C to the pipeline
- When streaming, the first ^C does not kill feedgnuplot
* Removed threading
-- Dima Kogan <dima@secretsauce.net> Sun, 01 Nov 2015 12:50:33 -0800
feedgnuplot (1.34) feedgnuplot (1.34)
* Fix for "Use of implicit split to @_ is deprecated". Thanks to Corey * Fix for "Use of implicit split to @_ is deprecated". Thanks to Corey

View File

@@ -7,14 +7,16 @@ use warnings;
use Getopt::Long; use Getopt::Long;
use Time::HiRes qw( usleep gettimeofday tv_interval ); use Time::HiRes qw( usleep gettimeofday tv_interval );
use IO::Handle; use IO::Handle;
use IO::Select;
use List::Util qw( first ); use List::Util qw( first );
use Scalar::Util qw( looks_like_number ); use Scalar::Util qw( looks_like_number );
use Text::ParseWords; # for shellwords use Text::ParseWords;
use threads;
use threads::shared;
use Thread::Queue;
use Pod::Usage; use Pod::Usage;
use Time::Piece; use Time::Piece;
my $VERSION = 1.36; my $VERSION = 1.34;
my %options; my %options;
interpretCommandline(); interpretCommandline();
@@ -27,11 +29,16 @@ interpretCommandline();
# with --xlen, the offsets are preserved by using $curve->{datastring_offset} to # 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 # represent the offset IN THE ORIGINAL STRING of the current start of the
# datastring # datastring
my @curves = (); my @curves = ();
# list mapping curve names to their indices in the @curves list # list mapping curve names to their indices in the @curves list
my %curveIndices = (); my %curveIndices = ();
# now start the data acquisition and plotting threads
my $dataQueue;
# Whether any new data has arrived since the last replot # Whether any new data has arrived since the last replot
my $haveNewData; my $haveNewData;
@@ -41,16 +48,40 @@ my $last_replot_time = [gettimeofday];
# whether the previous replot was timer based # whether the previous replot was timer based
my $last_replot_is_from_timer = 1; my $last_replot_is_from_timer = 1;
my $streamingFinished : shared = undef;
my $prev_timed_replot_time = [gettimeofday]; if($options{stream})
my $this_replot_is_from_timer; {
my $stdin = IO::Handle->new(); $dataQueue = Thread::Queue->new();
die "Couldn't open STDIN" unless $stdin->fdopen(fileno(STDIN),"r"); my $addThr = threads->create(\&mainThread);
my $selector = IO::Select->new( $stdin );
# 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;
mainThread(); 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;
$dataQueue->enqueue(undef);
$plotThr->join() if defined $plotThr;
$addThr->join();
}
else
{ mainThread(); }
@@ -89,7 +120,6 @@ sub interpretCommandline
$options{extracmds} = []; $options{extracmds} = [];
$options{set} = []; $options{set} = [];
$options{unset} = []; $options{unset} = [];
$options{equation} = [];
$options{curvestyleall} = ''; $options{curvestyleall} = '';
$options{styleall} = ''; $options{styleall} = '';
@@ -103,7 +133,6 @@ sub interpretCommandline
'zmin=f', 'zmax=f', 'y2=s@', 'zmin=f', 'zmax=f', 'y2=s@',
'style=s{2}', 'curvestyle=s{2}', 'curvestyleall=s', 'styleall=s', 'with=s', 'extracmds=s@', 'set=s@', 'unset=s@', 'style=s{2}', 'curvestyle=s{2}', 'curvestyleall=s', 'styleall=s', 'with=s', 'extracmds=s@', 'set=s@', 'unset=s@',
'square!', 'square_xy!', 'hardcopy=s', 'maxcurves=i', 'monotonic!', 'timefmt=s', 'square!', 'square_xy!', 'hardcopy=s', 'maxcurves=i', 'monotonic!', 'timefmt=s',
'equation=s@',
'histogram=s@', 'binwidth=f', 'histstyle=s', 'histogram=s@', 'binwidth=f', 'histstyle=s',
'terminal=s', 'terminal=s',
'rangesize=s{2}', 'rangesizeall=i', 'extraValuesPerPoint=i', 'rangesize=s{2}', 'rangesizeall=i', 'extraValuesPerPoint=i',
@@ -199,9 +228,6 @@ sub interpretCommandline
# -1 for triggered replotting # -1 for triggered replotting
# >0 for timed replotting # >0 for timed replotting
# undef if not streaming # undef if not streaming
#
# Note that '0' is not allowed, so !$options{stream} will do the expected
# thing
if(defined $options{stream}) if(defined $options{stream})
{ {
# if no streaming period is given, default to 1Hz. # if no streaming period is given, default to 1Hz.
@@ -358,7 +384,7 @@ sub interpretCommandline
{ {
if( $options{xlen} - int($options{xlen}) ) if( $options{xlen} - int($options{xlen}) )
{ {
print STDERR "When streaming --xlen MUST be an integer. Rounding up to the nearest second\n"; say STDERR "When streaming --xlen MUST be an integer. Rounding up to the nearest second";
$options{xlen} = 1 + int($options{xlen}); $options{xlen} = 1 + int($options{xlen});
} }
} }
@@ -379,6 +405,17 @@ sub getGnuplotVersion
return $gnuplotVersion; return $gnuplotVersion;
} }
sub plotUpdateThread
{
while(! $streamingFinished)
{
usleep( $options{stream} * 1e6 );
# indicate that the timer was the replot source
$dataQueue->enqueue('replot timertick');
}
}
sub sendRangeCommand sub sendRangeCommand
{ {
my ($name, $min, $max) = @_; my ($name, $min, $max) = @_;
@@ -414,63 +451,9 @@ sub makeDomainNumeric
return $domain0; return $domain0;
} }
sub getNextLine
{
while(1)
{
$this_replot_is_from_timer = undef;
# if we're not streaming, or we're doing triggered-only replotting, simply
# do a blocking read
return $stdin->getline()
if (! $options{stream} || $options{stream} < 0);
my $now = [gettimeofday];
my $time_remaining = $options{stream} - tv_interval($prev_timed_replot_time, $now);
if ( $time_remaining < 0 )
{
$prev_timed_replot_time = $now;
$this_replot_is_from_timer = 1;
return 'replot';
}
if ($selector->can_read($time_remaining))
{
return $stdin->getline();
}
}
}
sub mainThread sub mainThread
{ {
local *PIPE; local *PIPE;
my $dopersist = '';
if( getGnuplotVersion() >= 4.3 && # --persist not available before this
# --persist is needed for the "half-alive" state (see documentation for
# --exit). This state is only used with these options:
!$options{stream} && $options{exit})
{
$dopersist = '--persist';
}
# We trap SIGINT to kill the data input, but keep the plot up. see
# documentation for --exit
if ($options{stream} && !$options{exit})
{
$SIG{INT} = sub
{
print STDERR "$0 received SIGINT. Send again to quit\n";
$SIG{INT} = undef;
};
}
if(exists $options{dump}) if(exists $options{dump})
{ {
@@ -480,7 +463,7 @@ sub mainThread
{ {
my $geometry = defined $options{geometry} ? my $geometry = defined $options{geometry} ?
"-geometry $options{geometry}" : ''; "-geometry $options{geometry}" : '';
open PIPE, "|gnuplot $geometry $dopersist" or die "Can't initialize gnuplot\n"; open PIPE, "|gnuplot $geometry" or die "Can't initialize gnuplot\n";
} }
autoflush PIPE 1; autoflush PIPE 1;
@@ -638,7 +621,8 @@ sub mainThread
# number of seconds since the UNIX epoch. # number of seconds since the UNIX epoch.
my $domain0_numeric; my $domain0_numeric;
while( defined ($_ = getNextLine()) ) # 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; next if /^#/o;
@@ -652,104 +636,123 @@ sub mainThread
if(/^replot/o ) if(/^replot/o )
{ {
replot( $domain0_numeric ); # /timertick/ determines if the timer was the source of the replot
replot( $domain0_numeric, /timertick/ );
next; next;
} }
last if /^exit/o; # /exit/ is handled in the data-reading thread
} }
# parse the incoming data lines. The format is if(! /^replot/o)
# 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"
my @fields = split;
if($options{domain})
{ {
if( $options{timefmt} ) # parse the incoming data lines. The format is
{ # x id0 dat0 id1 dat1 ....
# no point if doing anything unless I have at least the domain and # where idX is the ID of the curve that datX corresponds to
# 1 piece of data #
next if @fields < $options{timefmt_Ncols}+1; # $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"
$domain[0] = join (' ', splice( @fields, 0, $options{timefmt_Ncols}) ); my @fields = split;
$domain0_numeric = makeDomainNumeric( $domain[0] );
}
elsif(!$options{'3d'})
{
# no point if doing anything unless I have at least the domain and
# 1 piece of data
next if @fields < 1+1;
$domain[0] = $domain0_numeric = shift @fields; if($options{domain})
{
if( $options{timefmt} )
{
# no point if doing anything unless I have at least the domain and
# 1 piece of data
next if @fields < $options{timefmt_Ncols}+1;
$domain[0] = join (' ', splice( @fields, 0, $options{timefmt_Ncols}) );
$domain0_numeric = makeDomainNumeric( $domain[0] );
}
elsif(!$options{'3d'})
{
# no point if doing anything unless I have at least the domain and
# 1 piece of data
next if @fields < 1+1;
$domain[0] = $domain0_numeric = shift @fields;
}
else
{
# no point if doing anything unless I have at least the domain and
# 1 piece of data
next if @fields < 2+1;
@domain = splice(@fields, 0, 2);
}
if( $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 else
{ {
# no point if doing anything unless I have at least the domain and # since $. is not meaningful in the plotting thread if we're using the data queue, we pass
# 1 piece of data # $. on the data queue in that case
next if @fields < 2+1; if(defined $dataQueue)
{
@domain = splice(@fields, 0, 2); $domain[0] = pop @fields;
}
else
{
$domain[0] = $.;
}
$domain0_numeric = makeDomainNumeric( $domain[0] );
} }
if( $options{monotonic} ) my $id = -1;
while(@fields)
{ {
if( defined $latestX && $domain0_numeric < $latestX ) if($options{dataid})
{ {
# the x-coordinate of the new point is in the past, so I wipe out $id = shift @fields;
# all the data and start anew. Before I wipe the old data, I }
# replot the old data else
replot( $domain0_numeric ); {
clearCurves(); $id++;
$latestX = undef; }
}
else # I'd like to use //, but I guess some people are still on perl 5.8
{ $latestX = $domain0_numeric; } my $rangesize = exists $options{rangesize_hash}{$id} ?
$options{rangesize_hash}{$id} :
$options{rangesize_default};
last if @fields < $rangesize;
pushPoint(getCurve($id),
join(' ',
@domain,
splice( @fields, 0, $rangesize ) ) . "\n",
$domain0_numeric);
} }
} }
else }
{
$domain[0] = $.; # if we were streaming, we're now done!
$domain0_numeric = makeDomainNumeric( $domain[0] ); if( $options{stream} )
} {
return;
my $id = -1;
while(@fields)
{
if($options{dataid})
{
$id = shift @fields;
}
else
{
$id++;
}
# I'd like to use //, but I guess some people are still on perl 5.8
my $rangesize = exists $options{rangesize_hash}{$id} ?
$options{rangesize_hash}{$id} :
$options{rangesize_default};
last if @fields < $rangesize;
pushPoint(getCurve($id),
join(' ',
@domain,
splice( @fields, 0, $rangesize ) ) . "\n",
$domain0_numeric);
}
} }
# finished reading in all. Plot what we have # finished reading in all. Plot what we have
plotStoredData() unless $options{stream}; plotStoredData();
if ( defined $options{hardcopy}) if ( defined $options{hardcopy})
{ {
@@ -770,13 +773,6 @@ sub mainThread
return; return;
} }
# data exhausted. If we're killed now, then we should peacefully die.
if($options{stream} && !$options{exit})
{
print STDERR "Input data exhausted\n";
$SIG{INT} = undef;
}
# we persist gnuplot, so we shouldn't need this sleep. However, once # we persist gnuplot, so we shouldn't need this sleep. However, once
# gnuplot exits, but the persistent window sticks around, you can no # gnuplot exits, but the persistent window sticks around, you can no
# longer interactively zoom the plot. So we still sleep # longer interactively zoom the plot. So we still sleep
@@ -820,9 +816,7 @@ sub plotStoredData
my @nonemptyCurves = grep { $_->{datastring} } @curves; my @nonemptyCurves = grep { $_->{datastring} } @curves;
my @extraopts = map {$_->{options}} @nonemptyCurves; my @extraopts = map {$_->{options}} @nonemptyCurves;
my $body = join('', map { "$_," } @{$options{equation}}); my $body = join(', ' , map({ "'-' $_" } @extraopts) );
$body .= join(', ' , map({ "'-' $_" } @extraopts) );
if($options{'3d'}) { print PIPE "splot $body\n"; } if($options{'3d'}) { print PIPE "splot $body\n"; }
else { print PIPE "plot $body\n"; } else { print PIPE "plot $body\n"; }
@@ -976,7 +970,7 @@ sub replot
# } # }
my ($domain0_numeric) = @_; my ($domain0_numeric, $replot_is_from_timer) = @_;
my $now = [gettimeofday]; my $now = [gettimeofday];
@@ -986,7 +980,7 @@ sub replot
# if the last replot was timer-based, but this one isn't, force a replot. # 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 # This makes sure that a replot happens for a domain rollover shortly
# after a timer replot # after a timer replot
!$this_replot_is_from_timer && $last_replot_is_from_timer || !$replot_is_from_timer && $last_replot_is_from_timer ||
# if enough time has elapsed since the last replot, it's ok to replot # if enough time has elapsed since the last replot, it's ok to replot
tv_interval ( $last_replot_time, $now ) > 0.8*$options{stream} ) tv_interval ( $last_replot_time, $now ) > 0.8*$options{stream} )
@@ -1012,7 +1006,7 @@ sub replot
# update replot state # update replot state
$last_replot_time = $now; $last_replot_time = $now;
$last_replot_is_from_timer = $this_replot_is_from_timer; $last_replot_is_from_timer = $replot_is_from_timer;
} }
} }
@@ -1614,34 +1608,6 @@ times.
=item =item
C<--equation xxx>
Gnuplot can plot both data and symbolic equations. C<feedgnuplot> generally
plots data, but with this option can plot symbolic equations /also/. This is
generally intended to augment data plots, since for equation-only plots you
don't need C<feedgnuplot>. C<--equation> can be passed multiple times for
multiple equations. The given strings are passed to gnuplot directly without any
thing added or removed, so styling and such should be applied in the string. A
basic example:
seq 100 | awk '{print $1/10, $1/100}' |
feedgnuplot --with 'lines lw 3' --domain --ymax 1
--equation 'sin(x)/x' --equation 'cos(x)/x with lines lw 4'
Here I plot the incoming data (points along a line) with the given style (a line
with thickness 3), /and/ I plot two damped sinusoids on the same plot. The
sinusoids are not affected by C<feedgnuplot> styling, so their styles are set
separately, as in this example. More complicated example:
seq 360 | perl -nE '$th=$_/360 * 3.14*2; $c=cos($th); $s=sin($th); say "$c $s"' |
feedgnuplot --domain --square
--set parametric --set "trange [0:2*3.14]" --equation "sin(t),cos(t)"
Here the data I generate is points along the unit circle. I plot these as
points, and I /also/ plot a true circle as a parametric equation.
=item
C<--square> C<--square>
Plot data with aspect ratio 1. For 3D plots, this controls the aspect ratio for Plot data with aspect ratio 1. For 3D plots, this controls the aspect ratio for
@@ -1727,80 +1693,10 @@ is possible to send the output produced this way to gnuplot directly.
C<--exit> C<--exit>
This controls the details of what happens when the input data is exhausted, or Terminate the feedgnuplot process after passing data to gnuplot. The window will
when some part of the C<feedgnuplot> pipeline is killed. This option does persist but will not be interactive. Without this option feedgnuplot keeps
different things depending on whether C<--stream> is active, so read this running and must be killed by the user. Note that this option works only with
closely. later versions of gnuplot and only with some gnuplot terminals.
With interactive gnuplot terminals (qt, x11, wxt), the plot windows live in a
separate process from the main C<gnuplot> process. It is thus possible for the
main C<gnuplot> process to exit, while leaving the plot windows up (a caveat is
that such decapitated windows aren't interactive). To be clear, there are 3
possible states:
=over
=item Alive: C<feedgnuplot>, C<gnuplot> alive, plot window process alive, no
shell prompt (shell busy with C<feedgnuplot>)
=item Half-alive: C<feedgnuplot>, C<gnuplot> dead, plot window process alive
(but non-interactive), shell prompt available
=item Dead: C<feedgnuplot>, C<gnuplot> dead, plot window process dead, shell
prompt available
=back
The C<--exit> option controls the details of this behavior. The possibilities
are:
=over
=item No C<--stream>, input pipe is exhausted (all data read in)
=over
=item default; no C<--exit>
Alive. Need to Ctrl-C to get back into the shell
=item C<--exit>
Half-alive. Non-interactive prompt up, and the shell accepts new commands.
Without C<--stream> the goal is to show a plot, so a Dead state is not useful
here.
=back
=item C<--stream>, input pipe is exhausted (all data read in) or the
C<feedgnuplot> process terminated
=over
=item default; no C<--exit>
Alive. Need to Ctrl-C to get back into the shell
=item C<--exit>
Dead. No plot is shown, and the shell accepts new commands. With C<--stream> the
goal is to show a plot as the data comes in, which we have been doing. Now that
we're done, we can clean up everything.
=back
=back
Note that one usually invokes C<feedgnuplot> as a part of a shell pipeline:
$ write_data | feedgnuplot
If the user terminates this pipeline with ^C, then I<all> the processes in the
pipeline receive SIGINT. This normally kills C<feedgnuplot> and all its
C<gnuplot> children, and we let this happen unless C<--stream> and no C<--exit>.
If C<--stream> and no C<--exit>, then we ignore the first ^C. The data feeder
dies, and we behave as if the input data was exhausted. A second ^C kills us
also.
=item =item
@@ -1854,12 +1750,10 @@ in a Thinkpad.
$ while true; do cat /proc/acpi/ibm/thermal | awk '{$1=""; print}' ; sleep 1; done | $ 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)' feedgnuplot --stream --xlen 100 --lines --autolegend --ymax 100 --ymin 20 --ylabel 'Temperature (deg C)'
=head2 Plotting a histogram of file sizes in a directory, granular to 10MB =head2 Plotting a histogram of file sizes in a directory
$ ls -l | awk '{print $5/1e6}' | $ ls -l | awk '{print $5/1e6}' |
feedgnuplot --histogram 0 --with boxes feedgnuplot --histogram 0 --with boxes --ymin 0 --xlabel 'File size (MB)' --ylabel Frequency
--binwidth 10 --set 'style fill solid'
--ymin 0 --xlabel 'File size (MB)' --ylabel Frequency
=head1 ACKNOWLEDGEMENT =head1 ACKNOWLEDGEMENT

19
debian/changelog vendored
View File

@@ -1,21 +1,8 @@
feedgnuplot (1.36-1) unstable; urgency=medium feedgnuplot (1.34stir1-1) unstable; urgency=medium
* Upstream update: added --equation to plot symbolic equations * disabled --persist because on our arm board it confuses things
-- Dima Kogan <dima@secretsauce.net> Fri, 13 Nov 2015 11:14:30 -0800 -- Dima Kogan <dima@secretsauce.net> Fri, 22 Aug 2014 14:59:05 -0700
feedgnuplot (1.35-1) unstable; urgency=medium
* Upstream update: fancier handling of termination conditions, no more
threading code
-- Dima Kogan <dima@secretsauce.net> Mon, 02 Nov 2015 13:55:32 -0800
feedgnuplot (1.34-2) unstable; urgency=medium
* Depends now works with the 'gnuplot5' packages
-- Dima Kogan <dima@secretsauce.net> Fri, 10 Oct 2014 14:05:17 -0700
feedgnuplot (1.34-1) unstable; urgency=medium feedgnuplot (1.34-1) unstable; urgency=medium

3
debian/control vendored
View File

@@ -11,8 +11,7 @@ Vcs-Browser: http://anonscm.debian.org/gitweb/?p=debian-science/packages/feedgnu
Package: feedgnuplot Package: feedgnuplot
Architecture: all Architecture: all
Depends: ${misc:Depends}, ${perl:Depends}, Depends: ${misc:Depends}, ${perl:Depends}, gnuplot-qt | gnuplot-x11 | gnuplot-nox
gnuplot-qt | gnuplot-x11 | gnuplot-nox | gnuplot5-qt | gnuplot5-x11 | gnuplot5-nox | gnuplot
Description: Pipe-oriented frontend to Gnuplot Description: Pipe-oriented frontend to Gnuplot
Flexible, command-line-oriented frontend to Gnuplot. Creates plots from data Flexible, command-line-oriented frontend to Gnuplot. Creates plots from data
coming in on STDIN or given in a filename passed on the commandline. Various coming in on STDIN or given in a filename passed on the commandline. Various