From 1688496f34c7d16e10a1a0331611ec0aecd4555d Mon Sep 17 00:00:00 2001 From: Dima Kogan Date: Wed, 28 May 2014 02:34:39 -0700 Subject: [PATCH 01/10] an "exit" command now has effect even with triggered-only replotting --- bin/feedgnuplot | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/feedgnuplot b/bin/feedgnuplot index 08e177b..75dd86f 100755 --- a/bin/feedgnuplot +++ b/bin/feedgnuplot @@ -75,6 +75,7 @@ if($options{stream}) } $streamingFinished = 1; + $dataQueue->enqueue(undef); $plotThr->join() if defined $plotThr; $addThr->join(); @@ -413,8 +414,6 @@ sub plotUpdateThread # indicate that the timer was the replot source $dataQueue->enqueue('replot timertick'); } - - $dataQueue->enqueue(undef); } sub sendRangeCommand From 0c32afacfd28e59bf48349f90290a4e75cd5ba2e Mon Sep 17 00:00:00 2001 From: Dima Kogan Date: Thu, 7 Aug 2014 12:46:23 -0700 Subject: [PATCH 02/10] fixed typo --- bin/feedgnuplot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/feedgnuplot b/bin/feedgnuplot index 75dd86f..b95ae77 100755 --- a/bin/feedgnuplot +++ b/bin/feedgnuplot @@ -767,7 +767,7 @@ sub mainThread # 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. In that case I don't make sure the file exists, since there IS no # file if( $options{hardcopy} !~ /^\|/ ) { From 605158b39169cdc63f179406da23b62284aa4565 Mon Sep 17 00:00:00 2001 From: Dima Kogan Date: Sat, 31 Oct 2015 16:20:52 -0700 Subject: [PATCH 03/10] replaced a 'say' with 'print' --- bin/feedgnuplot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/feedgnuplot b/bin/feedgnuplot index b95ae77..c234ae3 100755 --- a/bin/feedgnuplot +++ b/bin/feedgnuplot @@ -384,7 +384,7 @@ sub interpretCommandline { if( $options{xlen} - int($options{xlen}) ) { - say STDERR "When streaming --xlen MUST be an integer. Rounding up to the nearest second"; + print STDERR "When streaming --xlen MUST be an integer. Rounding up to the nearest second\n"; $options{xlen} = 1 + int($options{xlen}); } } From 104accdd0db747cc614a5f89e427bb3e8c0a6523 Mon Sep 17 00:00:00 2001 From: Dima Kogan Date: Sat, 31 Oct 2015 02:45:08 -0700 Subject: [PATCH 04/10] More sophisticated handling of termination conditions no --stream and no --exit: When input exhausted, keep interactive plot up, keep shell busy until user ^C no --stream and --exit: When input exhausted, keep non-interactive plot up, make shell available immediately --stream and no --exit: When input exhausted, keep interactive plot up, keep shell busy until user ^C. A user ^C before the input is exhausted is blocked from killing C, but allows the data input process to be killed, so it looks like an input exhaustion condition. --stream and --exit: When input exhausted or user ^C, shut down all plots, make shell available immediately. A user ^C is respected immediately, and C is killed --- bin/feedgnuplot | 113 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 101 insertions(+), 12 deletions(-) diff --git a/bin/feedgnuplot b/bin/feedgnuplot index c234ae3..796c2e1 100755 --- a/bin/feedgnuplot +++ b/bin/feedgnuplot @@ -456,11 +456,29 @@ sub mainThread local *PIPE; my $dopersist = ''; - if( !$options{stream} && getGnuplotVersion() >= 4.3) + 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}) { *PIPE = *STDOUT; @@ -751,14 +769,8 @@ sub mainThread } } - # if we were streaming, we're now done! - if( $options{stream} ) - { - return; - } - # finished reading in all. Plot what we have - plotStoredData(); + plotStoredData() unless $options{stream}; if ( defined $options{hardcopy}) { @@ -779,6 +791,13 @@ sub mainThread 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 # gnuplot exits, but the persistent window sticks around, you can no # longer interactively zoom the plot. So we still sleep @@ -1699,10 +1718,80 @@ is possible to send the output produced this way to gnuplot directly. C<--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. +This controls the details of what happens when the input data is exhausted, or +when some part of the C pipeline is killed. This option does +different things depending on whether C<--stream> is active, so read this +closely. + +With interactive gnuplot terminals (qt, x11, wxt), the plot windows live in a +separate process from the main C process. It is thus possible for the +main C 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, C alive, plot window process alive, no +shell prompt (shell busy with C) + +=item Half-alive: C, C dead, plot window process alive +(but non-interactive), shell prompt available + +=item Dead: C, C 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 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 as a part of a shell pipeline: + + $ write_data | feedgnuplot + +If the user terminates this pipeline with ^C, then I the processes in the +pipeline receive SIGINT. This normally kills C and all its +C 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 From 01971c243461d19a5cd5f832fe555f88dc5b516b Mon Sep 17 00:00:00 2001 From: Dima Kogan Date: Sun, 1 Nov 2015 01:49:36 -0700 Subject: [PATCH 05/10] whitespace --- bin/feedgnuplot | 2 -- 1 file changed, 2 deletions(-) diff --git a/bin/feedgnuplot b/bin/feedgnuplot index 796c2e1..85dd9d1 100755 --- a/bin/feedgnuplot +++ b/bin/feedgnuplot @@ -29,8 +29,6 @@ interpretCommandline(); # 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 From 0e7f51f3f7fe0644e161ef52450a96a9b741004e Mon Sep 17 00:00:00 2001 From: Dima Kogan Date: Sun, 1 Nov 2015 01:05:32 -0800 Subject: [PATCH 06/10] comment --- bin/feedgnuplot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/feedgnuplot b/bin/feedgnuplot index 85dd9d1..c2647f1 100755 --- a/bin/feedgnuplot +++ b/bin/feedgnuplot @@ -9,7 +9,7 @@ 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 Text::ParseWords; # for shellwords use threads; use threads::shared; use Thread::Queue; From 4cfcf0fc35f5835a2ebd7a5e7dd9458da263995f Mon Sep 17 00:00:00 2001 From: Dima Kogan Date: Sun, 1 Nov 2015 10:47:42 -0800 Subject: [PATCH 07/10] removed threading stuff It's now all in one thread with a select() loop. Much nicer --- bin/feedgnuplot | 112 ++++++++++++++++++++---------------------------- 1 file changed, 47 insertions(+), 65 deletions(-) diff --git a/bin/feedgnuplot b/bin/feedgnuplot index c2647f1..8ce4cb1 100755 --- a/bin/feedgnuplot +++ b/bin/feedgnuplot @@ -7,12 +7,10 @@ use warnings; use Getopt::Long; use Time::HiRes qw( usleep gettimeofday tv_interval ); use IO::Handle; +use IO::Select; use List::Util qw( first ); use Scalar::Util qw( looks_like_number ); use Text::ParseWords; # for shellwords -use threads; -use threads::shared; -use Thread::Queue; use Pod::Usage; use Time::Piece; @@ -34,9 +32,6 @@ 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; @@ -46,40 +41,16 @@ 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); +my $prev_timed_replot_time = [gettimeofday]; +my $this_replot_is_from_timer; +my $stdin = IO::Handle->new(); +die "Couldn't open STDIN" unless $stdin->fdopen(fileno(STDIN),"r"); +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; - 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(); } +mainThread(); @@ -226,6 +197,9 @@ sub interpretCommandline # -1 for triggered replotting # >0 for timed replotting # undef if not streaming + # + # Note that '0' is not allowed, so !$options{stream} will do the expected + # thing if(defined $options{stream}) { # if no streaming period is given, default to 1Hz. @@ -403,17 +377,6 @@ sub getGnuplotVersion return $gnuplotVersion; } -sub plotUpdateThread -{ - while(! $streamingFinished) - { - usleep( $options{stream} * 1e6 ); - - # indicate that the timer was the replot source - $dataQueue->enqueue('replot timertick'); - } -} - sub sendRangeCommand { my ($name, $min, $max) = @_; @@ -449,6 +412,36 @@ sub makeDomainNumeric 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 { local *PIPE; @@ -643,8 +636,7 @@ sub mainThread # 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() : <>)) + while( defined ($_ = getNextLine()) ) { next if /^#/o; @@ -658,12 +650,11 @@ sub mainThread if(/^replot/o ) { - # /timertick/ determines if the timer was the source of the replot - replot( $domain0_numeric, /timertick/ ); + replot( $domain0_numeric ); next; } - # /exit/ is handled in the data-reading thread + last if /^exit/o; } if(! /^replot/o) @@ -725,16 +716,7 @@ sub mainThread } 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) - { - $domain[0] = pop @fields; - } - else - { - $domain[0] = $.; - } + $domain[0] = $.; $domain0_numeric = makeDomainNumeric( $domain[0] ); } @@ -993,7 +975,7 @@ sub replot # } - my ($domain0_numeric, $replot_is_from_timer) = @_; + my ($domain0_numeric) = @_; my $now = [gettimeofday]; @@ -1003,7 +985,7 @@ sub 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 # after a timer replot - !$replot_is_from_timer && $last_replot_is_from_timer || + !$this_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} ) @@ -1029,7 +1011,7 @@ sub replot # update replot state $last_replot_time = $now; - $last_replot_is_from_timer = $replot_is_from_timer; + $last_replot_is_from_timer = $this_replot_is_from_timer; } } From 42a8218fbe5b988d66632e61ba49d8450a8cc88b Mon Sep 17 00:00:00 2001 From: Dima Kogan Date: Sun, 1 Nov 2015 12:46:30 -0800 Subject: [PATCH 08/10] removed unneeded if() This looks like a large patch, but it's 99% re-indentation --- bin/feedgnuplot | 153 ++++++++++++++++++++++++------------------------ 1 file changed, 75 insertions(+), 78 deletions(-) diff --git a/bin/feedgnuplot b/bin/feedgnuplot index 8ce4cb1..1b0e010 100755 --- a/bin/feedgnuplot +++ b/bin/feedgnuplot @@ -657,96 +657,93 @@ sub mainThread last if /^exit/o; } - 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" + + my @fields = split; + + if($options{domain}) { - # 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" - - my @fields = split; - - if($options{domain}) + if( $options{timefmt} ) { - 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; + # 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] = 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; } - } + $domain[0] = $domain0_numeric = shift @fields; } else { - $domain[0] = $.; - $domain0_numeric = makeDomainNumeric( $domain[0] ); + # 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); } - my $id = -1; - - while(@fields) + if( $options{monotonic} ) { - 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); + 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 + { + $domain[0] = $.; + $domain0_numeric = makeDomainNumeric( $domain[0] ); + } + + 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 From 238a0c19437c2210e443c78fa5f24ceb83faaab5 Mon Sep 17 00:00:00 2001 From: Dima Kogan Date: Sun, 1 Nov 2015 12:55:09 -0800 Subject: [PATCH 09/10] version bump --- Changes | 14 ++++++++++++++ bin/feedgnuplot | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index f3fa38b..89492d9 100644 --- a/Changes +++ b/Changes @@ -1,3 +1,17 @@ +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 Sun, 01 Nov 2015 12:50:33 -0800 + feedgnuplot (1.34) * Fix for "Use of implicit split to @_ is deprecated". Thanks to Corey diff --git a/bin/feedgnuplot b/bin/feedgnuplot index 1b0e010..c2d0ba8 100755 --- a/bin/feedgnuplot +++ b/bin/feedgnuplot @@ -14,7 +14,7 @@ use Text::ParseWords; # for shellwords use Pod::Usage; use Time::Piece; -my $VERSION = 1.34; +my $VERSION = 1.35; my %options; interpretCommandline(); From c19dc4aa2a58bae8dc82262c76bbea40d1062da6 Mon Sep 17 00:00:00 2001 From: Dima Kogan Date: Sat, 20 Jun 2015 12:55:35 -0700 Subject: [PATCH 10/10] slighly fancier histogram recipe --- bin/feedgnuplot | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bin/feedgnuplot b/bin/feedgnuplot index c2d0ba8..a731978 100755 --- a/bin/feedgnuplot +++ b/bin/feedgnuplot @@ -1822,10 +1822,12 @@ 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 +=head2 Plotting a histogram of file sizes in a directory, granular to 10MB $ ls -l | awk '{print $5/1e6}' | - feedgnuplot --histogram 0 --with boxes --ymin 0 --xlabel 'File size (MB)' --ylabel Frequency + feedgnuplot --histogram 0 --with boxes + --binwidth 10 --set 'style fill solid' + --ymin 0 --xlabel 'File size (MB)' --ylabel Frequency =head1 ACKNOWLEDGEMENT