App-RecordStream
view release on metacpan or search on metacpan
lib/App/RecordStream/Operation/tognuplot.pm view on Meta::CPAN
package App::RecordStream::Operation::tognuplot;
our $VERSION = "4.0.25";
use strict;
use warnings;
use base qw(App::RecordStream::Operation);
use File::Temp qw(tempfile);
sub init {
my $this = shift;
my $args = shift;
my $bar_graph;
my $dump_to_screen = 0;
my $gnuplot_command = 'gnuplot';
my $lines;
my $png_file = 'tognuplot.png';
my $title;
my @labels;
my @plots;
my @precommands;
my @using;
my $key_groups = App::RecordStream::KeyGroups->new();
my $spec = {
"file=s" => \$png_file,
"key|k|fields|f=s" => sub { $key_groups->add_groups($_[1]); },
"label=s" => sub { push @labels, split(/,/, $_[1]); },
"plot=s" => sub { push @plots, split(/,/, $_[1]); },
"precommand=s" => sub { push @precommands, split(/,/, $_[1]); },
"title=s" => \$title,
"using=s" => sub { push @using, $_[1]; },
'bargraph' => \$bar_graph,
'dump-to-screen' => \$dump_to_screen,
'gnuplot-command=s' => \$gnuplot_command,
'lines' => \$lines,
$this->site_args(),
};
$this->parse_options($args, $spec);
die 'Must specify at least one field' unless ( $key_groups->has_any_group() );
if ( $bar_graph && $lines ) {
die 'Must specify one of --bargraph or --lines';
}
$png_file .= '.png' unless ( $png_file =~ m/\.png$/ );
if ( ! $dump_to_screen ) {
if ( open(my $fh, '|-', $gnuplot_command) ) {
close $fh;
}
else {
warn "Could not run gnuplot command: $gnuplot_command: $!\n";
warn "May want to specify a binary with --gnuplot-command\n";
exit 0;
}
}
my ($tempfh, $tempfile) = tempfile();
$this->{'BAR_GRAPH'} = $bar_graph;
$this->{'DUMP_TO_SCREEN'} = $dump_to_screen;
$this->{'FIRST_RECORD'} = 1;
$this->{'GNUPLOT_COMMAND'} = $gnuplot_command;
$this->{'KEY_GROUPS'} = $key_groups;
$this->{'LABELS'} = \@labels;
$this->{'LINES'} = $lines;
$this->{'PLOTS'} = \@plots;
$this->{'PNG_FILE'} = $png_file;
$this->{'PRECOMMANDS'} = \@precommands;
$this->{'TEMPFH'} = $tempfh;
$this->{'TEMPFILE'} = $tempfile;
$this->{'TITLE'} = $title;
$this->{'USING'} = \@using;
}
sub init_fields {
my $this = shift;
my $record = shift;
my $specs = $this->{'KEY_GROUPS'}->get_keyspecs($record);
my $using = $this->{'USING'};
my $bar_graph = $this->{'BAR_GRAPH'};
my $lines = $this->{'LINES'};
my $title = $this->{'TITLE'};
if ( ! $bar_graph && !$lines ) {
die 'Must specify using if more than 2 fields' if ( scalar @$specs > 2 ) && (! scalar @$using > 0);
}
if ( ! $title ) {
$title = join(', ', @$specs);
}
if ( scalar @$using == 0 ) {
if ( $bar_graph || $lines ) {
my $using_spec = "1 title \"$specs->[0]\"";
foreach my $idx (2..@$specs) {
my $title = $specs->[$idx-1];
$using_spec .= ", '' using $idx title \"$title\"";
}
push @$using, $using_spec;
}
elsif ( scalar @$specs == 1 ) {
push @$using, "1";
}
elsif ( scalar @$specs == 2 ) {
push @$using, "1:2";
}
}
$this->{'FIELDS'} = $specs;
$this->{'TITLE'} = $title;
}
# hook for additional args
sub site_args {
}
sub accept_record {
my ($this, $record) = @_;
if ( $this->{'FIRST_RECORD'} ) {
$this->{'FIRST_RECORD'} = 0;
$this->init_fields($record);
}
my $line = '';
foreach my $key (@{$this->{'FIELDS'}}) {
my $value = ${$record->guess_key_from_spec($key)};
$value = 0 if not defined $value;
$line .= "$value ";
}
chop $line;
if ( $this->{'DUMP_TO_SCREEN'} ) {
$this->push_line($line);
}
else {
my $tempfh = $this->{'TEMPFH'};
print $tempfh $line . "\n";
}
return 1;
}
sub stream_done {
my ($this) = @_;
close $this->{'TEMPFH'};
my $plot_script = '';
$plot_script .= "set terminal png\n";
$plot_script .= "set output '" . $this->{'PNG_FILE'} . "'\n";
$plot_script .= "set title '" . $this->{'TITLE'} . "'\n";
if ( $this->{'BAR_GRAPH'} ) {
$plot_script .= <<CMDS;
set style data histogram
set style histogram cluster gap 1
set style fill solid border -1
CMDS
}
elsif ( $this->{'LINES'} ) {
$plot_script .= "set style data linespoints\n";
}
foreach my $command (@{$this->{'PRECOMMANDS'}}) {
$plot_script .= $command . "\n";
}
my $plot_cmd = 'plot ';
my $index = 0;
my $default_label = join(', ', @{$this->{'FIELDS'}});
foreach my $use_spec (@{$this->{'USING'}}) {
if ( $this->{'DUMP_TO_SCREEN'} ) {
$plot_cmd .= "'screen' using $use_spec ";
}
else {
$plot_cmd .= "'" . $this->{'TEMPFILE'} . "' using $use_spec ";
}
if ( not ($use_spec =~ m/title/) ) {
my $label = $default_label;
if ( $this->{'LABELS'}->[$index] ) {
$label = $this->{'LABELS'}->[$index];
}
$plot_cmd .= "title '$label'";
}
$plot_cmd .= ', ';
$index++;
}
chop $plot_cmd;
chop $plot_cmd;
if ( @{$this->{'PLOTS'}} ) {
$plot_cmd .= ', ' . join(', ', @{$this->{'PLOTS'}});
}
$plot_script .= $plot_cmd;
if ( $this->{'DUMP_TO_SCREEN'} ) {
$this->push_line($plot_script);
}
else {
open(my $plot, '|-', $this->{'GNUPLOT_COMMAND'});
print $plot $plot_script;
close $plot;
}
if ( $? ) {
warn "Gnuplot failed, bailing!\n";
$this->_set_exit_value($?);
return;
}
$this->push_line("Wrote graph file: " . $this->{'PNG_FILE'});
}
sub DESTROY {
my ($this) = @_;
if ( $this->{'TEMPFH'} ) {
close $this->{'TEMPFH'};
}
if ( $this->{'TEMPFILE'} ) {
unlink $this->{'TEMPFILE'};
}
}
sub add_help_types {
my $this = shift;
$this->use_help_type('keyspecs');
$this->use_help_type('keygroups');
$this->use_help_type('keys');
}
sub usage {
my $this = shift;
my $options = [
['key|-k <keys>', '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-...
['using <using spec>', '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...
['plot <plot spec>', 'May be specified multiple times, may be comma separated. A directive passed directly to plot, e.g. --plot \'5 title "threshold"\''],
['precommand <gnuplot spec>', 'May be specified multiple times, may be comma separated. A command executed by gnuplot before executing plot, e.g. --precommand \'set xlabel "foo"\''],
['title <title>', 'Specify a title for the entire graph'],
['label <label>', 'Labels each --using line with the indicated label'],
['file <filename>', 'Name of output png file. Will append .png if not present Defaults to tognuplot.png'],
['lines', 'Draw lines between points, may specify more than 2 key, each field is a line'],
['bargraph', 'Draw a bar graph, each field is a bar, may specify than 2 key, each field is a bar'],
['gnuplot-command', 'Location of gnuplot binary if not on path'],
['dump-to-screen', 'Instead of making a graph, dump the generated gnuplot script to STDOUT'],
];
my $args_string = $this->options_string($options);
return <<USAGE;
Usage: recs-tognuplot <args> [<files>]
__FORMAT_TEXT__
Create a graph of points from a record stream using GNU Plot. Defaults to
creatinga scatterplot of points, can also create a bar or line graph
For the --using and --plot arguments, you may want to reference a GNU Plot
tutorial, though it can get quite complex, here is one example:
http://www.gnuplot.info/docs/node100.html
__FORMAT_TEXT__
Arguments:
$args_string
Graph the count field
recs-tognuplot --field count
Graph count vs. date with a threshold line
recs-tognuplot --field count,date --plot "5 title 'threshold'"
Graph a complicated expression, with a label
recs-tognuplot --field count,date,adjust --using '(\$1-\$3):2' --label "counts"
Graph count vs. date, with a title
recs-tognuplot --field count,date --title 'counts over time'
Graph count1, count2, count3 as 3 different bars in a bar graph
recs-tognuplot --field count1,count2,count3
USAGE
}
1;
( run in 1.401 second using v1.01-cache-2.11-cpan-df04353d9ac )