File Coverage

blib/lib/App/RecordStream/Operation/tognuplot.pm
Criterion Covered Total %
statement 117 153 76.4
branch 21 50 42.0
condition 3 12 25.0
subroutine 12 17 70.5
pod 0 7 0.0
total 153 239 64.0


line stmt bran cond sub pod time code
1             package App::RecordStream::Operation::tognuplot;
2              
3             our $VERSION = "4.0.23";
4              
5 2     2   1096 use strict;
  2         6  
  2         49  
6 2     2   10 use warnings;
  2         5  
  2         50  
7              
8 2     2   10 use base qw(App::RecordStream::Operation);
  2         4  
  2         129  
9              
10 2     2   619 use File::Temp qw(tempfile);
  2         15357  
  2         2600  
11              
12             sub init {
13 1     1 0 3 my $this = shift;
14 1         3 my $args = shift;
15              
16 1         2 my $bar_graph;
17 1         2 my $dump_to_screen = 0;
18 1         3 my $gnuplot_command = 'gnuplot';
19 1         2 my $lines;
20 1         2 my $png_file = 'tognuplot.png';
21 1         7 my $title;
22             my @labels;
23 1         0 my @plots;
24 1         0 my @precommands;
25 1         0 my @using;
26              
27 1         9 my $key_groups = App::RecordStream::KeyGroups->new();
28              
29             my $spec = {
30             "file=s" => \$png_file,
31 1     1   1685 "key|k|fields|f=s" => sub { $key_groups->add_groups($_[1]); },
32 0     0   0 "label=s" => sub { push @labels, split(/,/, $_[1]); },
33 0     0   0 "plot=s" => sub { push @plots, split(/,/, $_[1]); },
34 0     0   0 "precommand=s" => sub { push @precommands, split(/,/, $_[1]); },
35             "title=s" => \$title,
36 0     0   0 "using=s" => sub { push @using, $_[1]; },
37 1         17 'bargraph' => \$bar_graph,
38             'dump-to-screen' => \$dump_to_screen,
39             'gnuplot-command=s' => \$gnuplot_command,
40             'lines' => \$lines,
41             $this->site_args(),
42             };
43              
44 1         8 $this->parse_options($args, $spec);
45              
46 1 50       5 die 'Must specify at least one field' unless ( $key_groups->has_any_group() );
47              
48 1 50 33     12 if ( $bar_graph && $lines ) {
49 0         0 die 'Must specify one of --bargraph or --lines';
50             }
51              
52 1 50       7 $png_file .= '.png' unless ( $png_file =~ m/\.png$/ );
53              
54 1 50       5 if ( ! $dump_to_screen ) {
55 0 0       0 if ( open(my $fh, '|-', $gnuplot_command) ) {
56 0         0 close $fh;
57             }
58             else {
59 0         0 warn "Could not run gnuplot command: $gnuplot_command: $!\n";
60 0         0 warn "May want to specify a binary with --gnuplot-command\n";
61 0         0 exit 0;
62             }
63             }
64              
65 1         6 my ($tempfh, $tempfile) = tempfile();
66              
67 1         583 $this->{'BAR_GRAPH'} = $bar_graph;
68 1         3 $this->{'DUMP_TO_SCREEN'} = $dump_to_screen;
69 1         3 $this->{'FIRST_RECORD'} = 1;
70 1         3 $this->{'GNUPLOT_COMMAND'} = $gnuplot_command;
71 1         4 $this->{'KEY_GROUPS'} = $key_groups;
72 1         3 $this->{'LABELS'} = \@labels;
73 1         2 $this->{'LINES'} = $lines;
74 1         3 $this->{'PLOTS'} = \@plots;
75 1         3 $this->{'PNG_FILE'} = $png_file;
76 1         3 $this->{'PRECOMMANDS'} = \@precommands;
77 1         5 $this->{'TEMPFH'} = $tempfh;
78 1         4 $this->{'TEMPFILE'} = $tempfile;
79 1         6 $this->{'TITLE'} = $title;
80 1         29 $this->{'USING'} = \@using;
81             }
82              
83             sub init_fields {
84 1     1 0 3 my $this = shift;
85 1         2 my $record = shift;
86              
87 1         6 my $specs = $this->{'KEY_GROUPS'}->get_keyspecs($record);
88 1         3 my $using = $this->{'USING'};
89 1         3 my $bar_graph = $this->{'BAR_GRAPH'};
90 1         3 my $lines = $this->{'LINES'};
91 1         2 my $title = $this->{'TITLE'};
92              
93 1 50 33     7 if ( ! $bar_graph && !$lines ) {
94 0 0 0     0 die 'Must specify using if more than 2 fields' if ( scalar @$specs > 2 ) && (! scalar @$using > 0);
95             }
96              
97 1 50       4 if ( ! $title ) {
98 1         3 $title = join(', ', @$specs);
99             }
100              
101 1 50       4 if ( scalar @$using == 0 ) {
102 1 50 33     7 if ( $bar_graph || $lines ) {
    0          
    0          
103 1         4 my $using_spec = "1 title \"$specs->[0]\"";
104              
105 1         4 foreach my $idx (2..@$specs) {
106 0         0 my $title = $specs->[$idx-1];
107 0         0 $using_spec .= ", '' using $idx title \"$title\"";
108             }
109              
110 1         3 push @$using, $using_spec;
111             }
112             elsif ( scalar @$specs == 1 ) {
113 0         0 push @$using, "1";
114             }
115             elsif ( scalar @$specs == 2 ) {
116 0         0 push @$using, "1:2";
117             }
118             }
119              
120 1         3 $this->{'FIELDS'} = $specs;
121 1         4 $this->{'TITLE'} = $title;
122             }
123              
124             # hook for additional args
125       1 0   sub site_args {
126             }
127              
128             sub accept_record {
129 9     9 0 21 my ($this, $record) = @_;
130              
131 9 100       23 if ( $this->{'FIRST_RECORD'} ) {
132 1         3 $this->{'FIRST_RECORD'} = 0;
133 1         4 $this->init_fields($record);
134             }
135              
136 9         18 my $line = '';
137 9         14 foreach my $key (@{$this->{'FIELDS'}}) {
  9         22  
138 9         17 my $value = ${$record->guess_key_from_spec($key)};
  9         25  
139 9 50       25 $value = 0 if not defined $value;
140 9         46 $line .= "$value ";
141             }
142              
143 9         21 chop $line;
144 9 50       22 if ( $this->{'DUMP_TO_SCREEN'} ) {
145 9         33 $this->push_line($line);
146             }
147             else {
148 0         0 my $tempfh = $this->{'TEMPFH'};
149 0         0 print $tempfh $line . "\n";
150             }
151              
152 9         55 return 1;
153             }
154              
155             sub stream_done {
156 1     1 0 3 my ($this) = @_;
157              
158 1         13 close $this->{'TEMPFH'};
159              
160 1         3 my $plot_script = '';
161 1         4 $plot_script .= "set terminal png\n";
162 1         5 $plot_script .= "set output '" . $this->{'PNG_FILE'} . "'\n";
163 1         3 $plot_script .= "set title '" . $this->{'TITLE'} . "'\n";
164              
165 1 50       7 if ( $this->{'BAR_GRAPH'} ) {
    50          
166 0         0 $plot_script .= <
167             set style data histogram
168             set style histogram cluster gap 1
169             set style fill solid border -1
170             CMDS
171             }
172             elsif ( $this->{'LINES'} ) {
173 1         3 $plot_script .= "set style data linespoints\n";
174             }
175              
176 1         9 foreach my $command (@{$this->{'PRECOMMANDS'}}) {
  1         4  
177 0         0 $plot_script .= $command . "\n";
178             }
179              
180 1         3 my $plot_cmd = 'plot ';
181              
182 1         2 my $index = 0;
183 1         3 my $default_label = join(', ', @{$this->{'FIELDS'}});
  1         3  
184              
185 1         2 foreach my $use_spec (@{$this->{'USING'}}) {
  1         3  
186 1 50       4 if ( $this->{'DUMP_TO_SCREEN'} ) {
187 1         3 $plot_cmd .= "'screen' using $use_spec ";
188             }
189             else {
190 0         0 $plot_cmd .= "'" . $this->{'TEMPFILE'} . "' using $use_spec ";
191             }
192              
193 1 50       6 if ( not ($use_spec =~ m/title/) ) {
194 0         0 my $label = $default_label;
195              
196 0 0       0 if ( $this->{'LABELS'}->[$index] ) {
197 0         0 $label = $this->{'LABELS'}->[$index];
198             }
199              
200 0         0 $plot_cmd .= "title '$label'";
201             }
202              
203 1         3 $plot_cmd .= ', ';
204 1         2 $index++;
205             }
206              
207 1         6 chop $plot_cmd;
208 1         2 chop $plot_cmd;
209              
210 1 50       2 if ( @{$this->{'PLOTS'}} ) {
  1         5  
211 0         0 $plot_cmd .= ', ' . join(', ', @{$this->{'PLOTS'}});
  0         0  
212             }
213              
214 1         3 $plot_script .= $plot_cmd;
215              
216 1 50       3 if ( $this->{'DUMP_TO_SCREEN'} ) {
217 1         4 $this->push_line($plot_script);
218             }
219             else {
220 0         0 open(my $plot, '|-', $this->{'GNUPLOT_COMMAND'});
221 0         0 print $plot $plot_script;
222 0         0 close $plot;
223             }
224              
225 1 50       5 if ( $? ) {
226 0         0 warn "Gnuplot failed, bailing!\n";
227 0         0 $this->_set_exit_value($?);
228 0         0 return;
229             }
230              
231 1         6 $this->push_line("Wrote graph file: " . $this->{'PNG_FILE'});
232             }
233              
234             sub DESTROY {
235 1     1   485 my ($this) = @_;
236              
237 1 50       5 if ( $this->{'TEMPFH'} ) {
238 1         3 close $this->{'TEMPFH'};
239             }
240              
241 1 50       5 if ( $this->{'TEMPFILE'} ) {
242 1         97 unlink $this->{'TEMPFILE'};
243             }
244             }
245              
246             sub add_help_types {
247 1     1 0 65 my $this = shift;
248 1         16 $this->use_help_type('keyspecs');
249 1         6 $this->use_help_type('keygroups');
250 1         4 $this->use_help_type('keys');
251             }
252              
253             sub usage {
254 0     0 0   my $this = shift;
255              
256 0           my $options = [
257             ['key|-k ', 'May be specified multiple times, may be comma separated. These are the keys to graph. If you have more than 2 keys, you must specify a --using statement or use --bargraph or --lines May be a keyspec or keygroup, see \'--help-keys\' for more information'],
258             ['using ', 'A \'using\' string passed directly to gnuplot, you can use keys specified with --key in the order specified. For instance --key count,date,avg with --using \'3:2\' would plot avg vs. date. May be specified multiple times'],
259             ['plot ', 'May be specified multiple times, may be comma separated. A directive passed directly to plot, e.g. --plot \'5 title "threshold"\''],
260             ['precommand ', 'May be specified multiple times, may be comma separated. A command executed by gnuplot before executing plot, e.g. --precommand \'set xlabel "foo"\''],
261             ['title ', 'Specify a title for the entire graph'], </td> </tr> <tr> <td class="h" > <a name="262">262</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> ['label <label>', 'Labels each --using line with the indicated label'], </td> </tr> <tr> <td class="h" > <a name="263">263</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> ['file <filename>', 'Name of output png file. Will append .png if not present Defaults to tognuplot.png'], </td> </tr> <tr> <td class="h" > <a name="264">264</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> ['lines', 'Draw lines between points, may specify more than 2 key, each field is a line'], </td> </tr> <tr> <td class="h" > <a name="265">265</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> ['bargraph', 'Draw a bar graph, each field is a bar, may specify than 2 key, each field is a bar'], </td> </tr> <tr> <td class="h" > <a name="266">266</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> ['gnuplot-command', 'Location of gnuplot binary if not on path'], </td> </tr> <tr> <td class="h" > <a name="267">267</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> ['dump-to-screen', 'Instead of making a graph, dump the generated gnuplot script to STDOUT'], </td> </tr> <tr> <td class="h" > <a name="268">268</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> ]; </td> </tr> <tr> <td class="h" > <a name="269">269</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="270">270</a> </td> <td class="c0" > <a href="#272"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $args_string = $this->options_string($options); </td> </tr> <tr> <td class="h" > <a name="271">271</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="272">272</a> </td> <td class="c0" > 0 </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> return <<USAGE; </td> </tr> <tr> <td class="h" > <a name="273">273</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Usage: recs-tognuplot <args> [<files>] </td> </tr> <tr> <td class="h" > <a name="274">274</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> __FORMAT_TEXT__ </td> </tr> <tr> <td class="h" > <a name="275">275</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Create a graph of points from a record stream using GNU Plot. Defaults to </td> </tr> <tr> <td class="h" > <a name="276">276</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> creatinga scatterplot of points, can also create a bar or line graph </td> </tr> <tr> <td class="h" > <a name="277">277</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="278">278</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> For the --using and --plot arguments, you may want to reference a GNU Plot </td> </tr> <tr> <td class="h" > <a name="279">279</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> tutorial, though it can get quite complex, here is one example: </td> </tr> <tr> <td class="h" > <a name="280">280</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="281">281</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> http://www.gnuplot.info/docs/node100.html </td> </tr> <tr> <td class="h" > <a name="282">282</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> __FORMAT_TEXT__ </td> </tr> <tr> <td class="h" > <a name="283">283</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="284">284</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Arguments: </td> </tr> <tr> <td class="h" > <a name="285">285</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $args_string </td> </tr> <tr> <td class="h" > <a name="286">286</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="287">287</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Graph the count field </td> </tr> <tr> <td class="h" > <a name="288">288</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> recs-tognuplot --field count </td> </tr> <tr> <td class="h" > <a name="289">289</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Graph count vs. date with a threshold line </td> </tr> <tr> <td class="h" > <a name="290">290</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> recs-tognuplot --field count,date --plot "5 title 'threshold'" </td> </tr> <tr> <td class="h" > <a name="291">291</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Graph a complicated expression, with a label </td> </tr> <tr> <td class="h" > <a name="292">292</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> recs-tognuplot --field count,date,adjust --using '(\$1-\$3):2' --label "counts" </td> </tr> <tr> <td class="h" > <a name="293">293</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Graph count vs. date, with a title </td> </tr> <tr> <td class="h" > <a name="294">294</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> recs-tognuplot --field count,date --title 'counts over time' </td> </tr> <tr> <td class="h" > <a name="295">295</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Graph count1, count2, count3 as 3 different bars in a bar graph </td> </tr> <tr> <td class="h" > <a name="296">296</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> recs-tognuplot --field count1,count2,count3 </td> </tr> <tr> <td class="h" > <a name="297">297</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> USAGE </td> </tr> <tr> <td class="h" > <a name="298">298</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> } </td> </tr> <tr> <td class="h" > <a name="299">299</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="300">300</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> 1; </td> </tr> </table> </body> </html>