File Coverage

blib/lib/Math/SimpleHisto/XS/CLI.pm
Criterion Covered Total %
statement 21 187 11.2
branch 0 100 0.0
condition 0 39 0.0
subroutine 7 17 41.1
pod 0 10 0.0
total 28 353 7.9


line stmt bran cond sub pod time code
1             package Math::SimpleHisto::XS::CLI;
2 1     1   22508 use 5.008001;
  1         5  
  1         40  
3 1     1   5 use strict;
  1         2  
  1         46  
4 1     1   5 use warnings;
  1         5  
  1         48  
5              
6             our $VERSION = '1.07';
7              
8 1     1   5 use constant BATCHSIZE => 1000;
  1         1  
  1         87  
9 1     1   5 use Carp 'croak';
  1         2  
  1         69  
10 1     1   5 use Exporter;
  1         2  
  1         117  
11              
12             our @ISA = qw(Exporter);
13              
14             our @EXPORT_OK = qw(
15             histogram_from_dumps_fh
16             histogram_from_random_data
17             histogram_from_fh
18             histogram_slurp_from_fh
19             minmax
20             display_histogram_using_soot
21              
22             intuit_ascii_style
23             intuit_output_size
24             draw_ascii_histogram
25             print_hist_stats
26             );
27             our %EXPORT_TAGS = (
28             'all' => \@EXPORT_OK,
29             );
30              
31 1     1   996 use Math::SimpleHisto::XS;
  1         24869  
  1         2185  
32              
33             sub histogram_from_dumps_fh {
34 0     0 0   my ($fh) = @_;
35              
36 0           my $hist;
37             my $tmphist;
38             #require Math::SimpleHisto::XS::Named; # TODO implement & test using this
39 0           while (my $dump = <$fh>) {
40 0 0         next if not $dump =~ /\S/;
41 0           foreach my $type (qw(json yaml simple)) {
42 0           eval {$tmphist = Math::SimpleHisto::XS->new_from_dump($type, $dump);};
  0            
43 0 0         last if defined $tmphist;
44             }
45 0 0         if (defined $tmphist) {
46 0 0         if ($hist) { $hist->add_histogram($tmphist) }
  0            
47 0           else { $hist = $tmphist }
48             }
49             }
50 0 0         Carp::croak("Could not recreate histogram from input histogram dump string")
51             if not defined $hist;
52              
53 0           return $hist;
54             }
55              
56             sub histogram_from_random_data {
57 0     0 0   my ($histopt, $random_samples) = @_;
58 0           my %opt = %$histopt;
59 0   0       $opt{min} ||= 0;
60 0   0       $opt{max} ||= 1;
61 0 0         $random_samples = 1000 if not $random_samples;
62              
63 0           my $hist = Math::SimpleHisto::XS->new(
64             min => $opt{min},
65             max => $opt{max},
66             nbins => $opt{nbins},
67             );
68              
69 0           my $min = $hist->min;
70 0           my $width = $hist->width;
71 0           $hist->fill($min + rand($width)) for 1..$random_samples;
72              
73 0           return $hist;
74             }
75              
76             sub histogram_from_fh {
77 0     0 0   my ($histopt, $fh, $hist) = @_;
78            
79 0   0       $hist ||= Math::SimpleHisto::XS->new(map {$_ => $histopt->{$_}} qw(nbins min max));
  0            
80              
81 0           my $pos_weight = $histopt->{xw};
82 0           my (@coords, @weights);
83 0           my $i = 0;
84              
85 0           my ($rbits);
86 0           my $step_size = $histopt->{stepsize};
87 0 0         if ($step_size) {
88 0           $rbits = '';
89 0           vec($rbits, fileno($fh), 1) = 1;
90             }
91              
92 0           while (1) {
93 0 0         if ($step_size) {
94 0           my ($havedata, undef) = select($rbits, undef, undef, 0.1);
95 0 0         if (not $havedata) {
96 0 0         last if $i >= 1;
97 0           redo;
98             }
99 0           $_ = <$fh>;
100             }
101             else {
102 0           $_ = <$fh>;
103             }
104 0 0         last if not defined $_;
105 0           chomp;
106 0           my @row = split " ", $_;
107 0           ++$i;
108 0 0         if ($pos_weight) {
109 0 0         push @{ ($i % 2) ? \@coords : \@weights }, $_ for split " ", $_;
  0            
110             }
111             else {
112 0           push @coords, split " ", $_;
113             }
114 0 0         if (@coords >= BATCHSIZE) {
115 0           my $tmp;
116 0 0         $tmp = pop(@weights) if @coords != @weights;
117 0 0         $hist->fill($pos_weight ? (\@coords, \@weights) : (\@coords));
118              
119 0           @coords = ();
120 0 0         @weights = (defined($tmp) ? ($tmp) : ());
121             }
122              
123 0 0 0       last if $step_size and $i >= $step_size;
124             }
125              
126 0 0         $hist->fill($pos_weight ? (\@coords, \@weights) : (\@coords))
    0          
127             if @coords;
128              
129 0           return $hist;
130             }
131              
132             # modifies input options
133             sub histogram_slurp_from_fh {
134 0     0 0   my ($histopt, $fh) = @_;
135              
136 0           my $pos_weight = $histopt->{xw};
137 0           my $hist;
138 0           my (@coords, @weights);
139 0           my $i = 0;
140 0           while () {
141 0           chomp;
142 0           s/^\s+//; s/\s+$//;
  0            
143 0 0         if ($pos_weight) {
144 0 0         push @{ (++$i % 2) ? \@coords : \@weights }, $_ for split " ", $_;
  0            
145             }
146             else {
147 0           push @coords, split " ", $_;
148             }
149             }
150              
151             # Without input and configured histogram boundaries, we can't make one
152             # TODO: should this be silent "success" or an empty histogram (for dump
153             # output mode) or an exception?
154 0 0         exit(0) if not @coords;
155 0           my ($min, $max) = minmax(@coords);
156 0 0         $histopt->{min} = $min if not defined $histopt->{min};
157 0 0         $histopt->{max} = $max if not defined $histopt->{max};
158              
159 0           $hist = Math::SimpleHisto::XS->new(map {$_ => $histopt->{$_}} qw(nbins min max));
  0            
160 0 0         $hist->fill($pos_weight ? (\@coords, \@weights) : (\@coords));
161              
162 0           return $hist;
163             }
164              
165             sub minmax {
166 0     0 0   my ($min, $max);
167 0           for (@_) {
168 0 0 0       $min = $_ if not defined $min or $min > $_;
169 0 0 0       $max = $_ if not defined $max or $max < $_;
170             }
171 0           return($min, $max);
172             }
173              
174             sub display_histogram_using_soot {
175 0     0 0   my ($hist) = @_;
176 0           my $h = $hist->to_soot;
177 0           my $cv = TCanvas->new;
178 0           $h->Draw();
179 0           my $app = $SOOT::gApplication = $SOOT::gApplication; # silence warnings
180 0           $app->Run();
181 0           exit;
182             }
183              
184              
185             our %AsciiStyles = (
186             '-' => {character => '-', end_character => '>'},
187             '=' => {character => '=', end_character => '>'},
188             '~' => {character => '~', end_character => '>'},
189             );
190              
191             # Determine the style to use for drawing the histogram
192             sub intuit_ascii_style {
193 0     0 0   my ($style_option) = @_;
194 0 0         $style_option = '~' if not defined $style_option;
195 0 0         if (not exists $AsciiStyles{$style_option}) {
196 0 0         if (length($style_option) == 1) {
197 0           $AsciiStyles{$style_option} = {character => $style_option, end_character => $style_option};
198             }
199             else {
200 0           die "Invalid histogram style '$style_option'. Valid styles: '"
201             . join("', '", keys %AsciiStyles), "' and any single character.\n";
202             }
203             }
204              
205 0           my $styledef = $AsciiStyles{$style_option};
206 0           return $styledef;
207             }
208              
209              
210             sub intuit_output_size {
211 0     0 0   my ($ofh) = @_;
212              
213 0   0       $ofh ||= \*STDOUT;
214             # figure out output width
215 0           my ($terminal_columns, $terminal_rows);
216 0 0         if (-t $ofh) {
217 0           ($terminal_columns, $terminal_rows) = Term::Size::chars($ofh);
218             }
219             else {
220 0           $terminal_columns = 80;
221 0           $terminal_rows = 10;
222             }
223              
224 0           return ($terminal_columns, $terminal_rows);
225             }
226              
227             sub print_hist_stats {
228 0     0 0   my ($ofh, $hist, $histopt) = @_;
229            
230 0   0       my $v_total_width = $histopt->{width} || (intuit_output_size($ofh))[0] - 2;
231             # Total: X Fills: X Mean: X Median: X
232 0           my ($tot, $nfills, $mean, $median) = map $hist->$_, qw(total nfills mean median);
233 0           my $str = sprintf("Total: %f NFills: %u Mean: %f Median %f\n", $tot, $nfills, $mean, $median);
234 0           $str = substr($str, 0, $v_total_width);
235 0           print $ofh $str;
236             }
237              
238             # relevant options:
239             # - sort
240             # - width
241             # - min
242             # - max
243             # - numeric-format
244             # - show-numeric
245             # - timestamp
246             # - log
247             # - style
248             sub draw_ascii_histogram {
249 0     0 0   my ($ofh, $rows, $histopt) = @_;
250              
251 0           my $convert_timestamps = $histopt->{timestamp};
252 0           my $show_numeric = $histopt->{"show-numeric"};
253 0           my $numeric_format = $histopt->{"numeric-format"};
254 0           my $logscale = $histopt->{log};
255 0           my $styledef = $histopt->{style};
256              
257             # extract min/max/width info from input data
258             # The $v_ prefixed variables below refer to "visible" widths in columns.
259 0           my $v_desc_width = 0;
260 0           my $v_numeric_value_width = 0;
261 0           my $hist_total = 0;
262              
263 0           my ($hist_max, $hist_min);
264 0           foreach my $row (@$rows) {
265 0           my ($description, $value) = @$row;
266 0 0         $row->[0] = $description = localtime(int($description)) if $convert_timestamps;
267              
268 0           my $formatted_value = sprintf($numeric_format, $value);
269              
270 0 0         $v_desc_width = length($description) if length($description) > $v_desc_width;
271 0 0         $v_numeric_value_width = length($formatted_value) if length($formatted_value) > $v_numeric_value_width;
272 0 0 0       $hist_min = $value if !defined $hist_min or $value < $hist_min;
273 0 0 0       $hist_max = $value if !defined $hist_max or $value > $hist_max;
274 0           $hist_total += $value;
275             # extend each row by the formatted numeric value -- just in case.
276 0 0         push @$row, $show_numeric ? $formatted_value : '';
277             }
278              
279             # sort by value if desired
280 0 0         @$rows = sort {$a->[1] <=> $b->[1]} @$rows if $histopt->{sort};
  0            
281              
282 0   0       my $v_total_width = $histopt->{width} || (intuit_output_size($ofh))[0] - 2;
283              
284 0 0         if ($v_total_width < $v_desc_width + 3) {
285 0           warn "Terminal or desired width is insufficient.\n";
286 0           $v_total_width = $v_desc_width + 3;
287             }
288              
289 0 0         $v_numeric_value_width = $show_numeric ? $v_numeric_value_width+2 : 0;
290             # The total output width is comprised of the bin description, possibly
291             # the width of the numeric bin content, and the width of the actual
292             # histogram.
293 0           my $v_hist_width = $v_total_width - $v_desc_width - $v_numeric_value_width - 3;
294              
295             # figure out the range of values in the visible part of the histogram
296 0   0       my $min_display_value = $histopt->{min} || 0;
297 0 0         if ($min_display_value =~ /^auto$/i) {
298 0           $min_display_value = $hist_min;
299             }
300 0 0 0       $min_display_value = log($min_display_value||$hist_min*0.99||1e-9) if $logscale;
301              
302 0           my $max_display_value = $histopt->{max};
303 0 0 0       if (not defined $max_display_value or $max_display_value =~ /^auto$/) {
    0          
304 0           $max_display_value = $hist_max;
305             }
306             elsif ($max_display_value =~ /^total$/i) {
307 0           $max_display_value = $hist_total;
308             }
309 0 0         $max_display_value = log($max_display_value) if $logscale;
310              
311 0           my $display_value_range = $max_display_value - $min_display_value;
312              
313             # format the output
314 0           my $format = "%${v_desc_width}s: %${v_numeric_value_width}s|%-${v_hist_width}s|\n";
315 0           my $hchar_body = $styledef->{character};
316 0           my $hchar_end = $styledef->{end_character};
317 0           my $hchar_end_len = length($hchar_end);
318              
319             # The actual output loop
320 0           foreach my $row (@$rows) {
321 0           my ($desc, $value, $formatted_value) = @$row;
322 0 0 0       $value = log($value||1e-15) if $logscale;
323              
324 0           my $hlen = int(($value-$min_display_value) / $display_value_range * $v_hist_width);
325 0 0         $hlen = 0 if $hlen < 0;
326 0 0         $hlen = $v_hist_width if $hlen > $v_hist_width;
327              
328 0 0         if ($hlen >= $hchar_end_len) {
329 0           printf($format, $desc, $formatted_value, ($hchar_body x ($hlen-$hchar_end_len)) . $hchar_end);
330             }
331             else {
332 0           printf($format, $desc, $formatted_value, ($hchar_body x $hlen));
333             }
334             }
335              
336             }
337              
338              
339             1;
340             __END__