File Coverage

blib/lib/App/RecordStream/Operation/toptable.pm
Criterion Covered Total %
statement 296 316 93.6
branch 55 72 76.3
condition 12 17 70.5
subroutine 22 27 81.4
pod 0 6 0.0
total 385 438 87.9


line stmt bran cond sub pod time code
1             package App::RecordStream::Operation::toptable;
2              
3             our $VERSION = "4.0.25";
4              
5 2     2   951 use strict;
  2         4  
  2         39  
6 2     2   7 use warnings;
  2         3  
  2         44  
7              
8 2     2   9 use base qw(App::RecordStream::Accumulator App::RecordStream::Operation);
  2         4  
  2         441  
9              
10 2     2   11 use App::RecordStream::Record;
  2         3  
  2         4409  
11              
12             # TODO: amling, this format is so ugly it hurts. Think of something better.
13              
14             sub init {
15 10     10 0 15 my $this = shift;
16 10         14 my $args = shift;
17              
18 10         12 my %pins;
19 10         13 my $headers = 1;
20 10         14 my $full = 0;
21              
22 10         60 my $xgroup = App::RecordStream::KeyGroups->new();
23 10         22 my $ygroup = App::RecordStream::KeyGroups->new();
24 10         20 my $vgroup = App::RecordStream::KeyGroups->new();
25 10         14 my $output_records = 0;
26 10         12 my $all_at_end = 0;
27 10         13 my %sorts = ();
28              
29             my $spec = {
30 10     10   7730 "x-field|x=s" => sub { $xgroup->add_groups($_[1]); },
31 10     10   1072 "y-field|y=s" => sub { $ygroup->add_groups($_[1]); },
32 2     2   195 "v-field|v=s" => sub { $vgroup->add_groups($_[1]); },
33 3 50   3   321 "pin=s" => sub { for(split(/,/, $_[1])) { if(/^(.*)=(.*)$/) { $pins{$1} = $2; } } },
  3         21  
  3         13  
34 0     0   0 "sort=s" => sub { for(split(/,/, $_[1])) { my ($comparator, $field) = App::RecordStream::Record::get_comparator_and_field($_); $sorts{$field} = $comparator; } },
  0         0  
  0         0  
35 0     0   0 'noheaders' => sub { $headers = 0; },
36 10         122 'records|recs' => \$output_records,
37             'sort-all-to-end|sa' => \$all_at_end,
38             };
39              
40 10         38 $this->parse_options($args, $spec);
41              
42 10 50 33     29 if ( %sorts && $all_at_end ) {
43 0         0 die "Cannot specify both --sort and --sort-all-to-end\n";
44             }
45              
46 10         46 my $do_vfields = !$vgroup->has_any_group();
47              
48 10         19 $this->{'XGROUP'} = $xgroup;
49 10         13 $this->{'YGROUP'} = $ygroup;
50 10         16 $this->{'VGROUP'} = $vgroup;
51              
52 10         17 $this->{'PINS_HASH'} = \%pins;
53 10         18 $this->{'SORTS'} = \%sorts;
54 10         15 $this->{'SORT_ALL_TO_END'} = $all_at_end;
55 10         11 $this->{'HEADERS'} = $headers;
56 10         24 $this->{'DO_VFIELDS'} = $do_vfields;
57 10         134 $this->{'OUTPUT_RECORDS'} = $output_records;
58             }
59              
60             sub stream_done {
61 10     10 0 13 my $this = shift;
62              
63 10         12 my %pins = %{$this->{'PINS_HASH'}};
  10         27  
64 10         16 my $headers = $this->{'HEADERS'};
65 10         20 my $do_vfields = $this->{'DO_VFIELDS'};
66              
67 10         14 my $xgroup = $this->{'XGROUP'};
68 10         12 my $ygroup = $this->{'YGROUP'};
69 10         13 my $vgroup = $this->{'VGROUP'};
70              
71 10         15 my $xfields_hash = {};
72 10         13 my $yfields_hash = {};
73 10         14 my $vfields_hash = {};
74              
75 10         18 my $records = $this->get_records();
76              
77 10         14 my (@xfields, @yfields, @vfields);
78             # Prep x and y fields
79 10         25 foreach my $record (@$records) {
80 309         310 foreach my $spec ( @{$xgroup->get_keyspecs_for_record($record)} ) {
  309         467  
81 552 100       808 if ( !$xfields_hash->{$spec} ) {
82 18         30 $xfields_hash->{$spec} = 1;
83 18         24 push @xfields, $spec;
84             }
85             }
86 309         363 foreach my $spec ( @{$ygroup->get_keyspecs_for_record($record)} ) {
  309         483  
87 516 100       877 if ( !$yfields_hash->{$spec} ) {
88 17         23 $yfields_hash->{$spec} = 1;
89 17         27 push @yfields, $spec;
90             }
91             }
92             }
93              
94 10 100       25 if ( $this->{'SORT_ALL_TO_END' } ) {
95 1         2 foreach my $field (@xfields, @yfields) {
96 2         44 print "creating comp for $field\n";
97 2         13 my ($comparator, $comp_field) = App::RecordStream::Record::get_comparator_and_field("$field=*");
98 2         11 $this->{'SORTS'}->{$field} = $comparator;
99             }
100             }
101              
102             # Prep v fields
103 10 100       27 if($do_vfields) {
104 8         10 my %vfields;
105             my %used_first_level_keys;
106              
107 8         15 for my $record (@$records) {
108 267         479 foreach my $spec (@xfields, @yfields, keys %pins) {
109 1008         1543 my $key_list = $record->get_key_list_for_spec($spec);
110 1008 50       1394 if (scalar @$key_list > 0) {
111 1008         1488 $used_first_level_keys{$key_list->[0]} = 1;
112             }
113             }
114              
115 267         617 foreach my $field (keys(%$record)) {
116 1224 100 100     1895 if ( !exists($used_first_level_keys{$field}) &&
117             !exists($vfields{$field}) ) {
118 9         14 push @vfields, $field;
119 9         18 $vfields{$field} = 1;
120             }
121             }
122             }
123              
124             # lexically sort if user didn't explicitly specify any vfields
125 8         24 @vfields = sort @vfields;
126             }
127             else {
128 2         5 my $vfields_hash = {};
129 2         5 foreach my $record (@$records) {
130 42         39 foreach my $spec ( @{$vgroup->get_keyspecs_for_record($record)} ) {
  42         65  
131 84 100       134 if ( !$vfields_hash->{$spec} ) {
132 4         7 $vfields_hash->{$spec} = 1;
133 4         8 push @vfields, $spec;
134             }
135             }
136             }
137             }
138              
139             # pass 1: build xvals and yvals structures and break records up by vfield
140              
141 10         13 my @r2;
142             # x_values_tree represent a nested tree of all possible x value tuples
143             # i.e. {x2 => 4, y => 7, x1 => 1} has an x values tuple of (1, 4) and thus with "touch" that path in the tree
144 10         22 my $x_values_tree = _new_node();
145 10         20 my $y_values_tree = _new_node();
146 10         15 foreach my $record (@$records) {
147             # make sure records matches appropriate pins
148 309         342 my $kickout = 0;
149 309         454 foreach my $pfield (keys(%pins)) {
150 108 50       160 if($pfield eq "FIELD") {
151 0         0 next;
152             }
153              
154 108         126 my $v = '';
155              
156 108 50       176 if ( $record->has_key_spec($pfield) ) {
157 108         107 $v = ${$record->guess_key_from_spec($pfield)};
  108         173  
158             }
159              
160 108 100       231 if($pins{$pfield} ne $v) {
161 68         71 $kickout = 1;
162 68         76 last;
163             }
164             }
165 309 100       448 if($kickout) {
166 68         80 next;
167             }
168              
169 241         272 for my $vfield (@vfields) {
170             # nothing to see here
171 304 50       529 if(!$record->has_key_spec($vfield)) {
172 0         0 next;
173             }
174              
175             # if field is pinned, skip other vfields
176 304 50 33     512 if(exists($pins{"FIELD"}) && $pins{"FIELD"} ne $vfield) {
177 0         0 next;
178             }
179              
180 304         319 my @xv;
181 304         365 for my $xfield (@xfields) {
182 542         591 my $v = "";
183 542 100       993 if($xfield eq "FIELD") {
    50          
184 126         133 $v = $vfield;
185             }
186             elsif($record->has_key_spec($xfield)) {
187 416         392 $v = ${$record->guess_key_from_spec($xfield)};
  416         677  
188             }
189 542         967 push @xv, $v;
190             }
191              
192 304         324 my @yv;
193 304         360 for my $yfield (@yfields) {
194 528         595 my $v = "";
195 528 50       1054 if($yfield eq "FIELD") {
    50          
196 0         0 $v = $vfield;
197             }
198             elsif($record->has_key_spec($yfield)) {
199 528         535 $v = ${$record->guess_key_from_spec($yfield)};
  528         878  
200             }
201 528         965 push @yv, $v;
202             }
203              
204 304         364 my $v = "";
205 304 50       502 if($record->has_key_spec($vfield)) {
206 304         337 $v = ${$record->guess_key_from_spec($vfield)};
  304         556  
207             }
208              
209 304         593 _touch_node_recurse($x_values_tree, @xv);
210 304         471 _touch_node_recurse($y_values_tree, @yv);
211              
212 304         827 push @r2, [\@xv, \@yv, $v];
213             }
214             }
215              
216             # Start constructing the ASCII table
217              
218             # we dump the tree out into all possible x value tuples (saved in
219             # @x_value_list) and tag each node in the tree with the index in
220             # @x_values_list so we can look it up later
221 10         14 my @x_values_list;
222 10         69 $this->_dump_node_recurse($x_values_tree, \@x_values_list, [@xfields], []);
223              
224 10         17 my @y_values_list;
225 10         30 $this->_dump_node_recurse($y_values_tree, \@y_values_list, [@yfields], []);
226              
227             # Collected the data, if we're only outputing records, stop here.
228 10 100       26 if ( $this->{'OUTPUT_RECORDS'} ) {
229 2         14 $this->output_records(\@xfields, \@yfields, \@r2, \@x_values_list, \@y_values_list);
230 2         32 return;
231             }
232              
233              
234 8         12 my $width_offset = scalar @yfields;
235 8         9 my $height_offset = scalar @xfields;
236              
237 8 50       15 if ( $headers ) {
238 8         9 $width_offset += 1;
239 8         10 $height_offset += 1;
240             }
241              
242 8         9 my $w = $width_offset + scalar(@x_values_list);
243 8         10 my $h = $height_offset + scalar(@y_values_list);
244 8         22 my @table = map { [map { "" } (1..$w)] } (1..$h);
  88         104  
  852         1011  
245              
246 8 50       27 if ( $headers ) {
247 8         21 for(my $i = 0; $i < @xfields; ++$i) {
248 14         30 $table[$i]->[scalar(@yfields)] = $xfields[$i];
249             }
250              
251 8         18 for(my $i = 0; $i < @yfields; ++$i) {
252 13         28 $table[scalar(@xfields)]->[$i] = $yfields[$i];
253             }
254             }
255              
256 8         14 my @last_xv = map { "" } (1..@xfields);
  14         24  
257 8         22 for(my $i = 0; $i < @x_values_list; ++$i) {
258 56         60 my $xv = $x_values_list[$i];
259 56         74 for(my $j = 0; $j < @xfields; ++$j) {
260 94 100       132 if($last_xv[$j] ne $xv->[$j]) {
261 72         80 $last_xv[$j] = $xv->[$j];
262 72         96 $table[$j]->[$width_offset + $i] = $xv->[$j];
263 72         160 for(my $k = $j + 1; $k < @xfields; ++$k) {
264 16         33 $last_xv[$k] = "";
265             }
266             }
267             }
268             }
269              
270 8         16 my @last_yv = map { "" } (1..@yfields);
  13         17  
271 8         18 for(my $i = 0; $i < @y_values_list; ++$i) {
272 66         69 my $yv = $y_values_list[$i];
273 66         84 for(my $j = 0; $j < @yfields; ++$j) {
274 109 100       155 if($last_yv[$j] ne $yv->[$j]) {
275 81         83 $last_yv[$j] = $yv->[$j];
276 81         93 $table[$height_offset + $i]->[$j] = $yv->[$j];
277 81         168 for(my $k = $j + 1; $k < @yfields; ++$k) {
278 15         36 $last_yv[$k] = "";
279             }
280             }
281             }
282             }
283              
284 8         19 for my $r2 (@r2) {
285 255         341 my ($xv, $yv, $v) = @$r2;
286              
287             # now we have our x value tuple, we need to know where it was in @x_values_list so we can know its x coordinate
288 255         297 my $i = _find_index_recursive($x_values_tree, @$xv);
289              
290 255 50       338 if($i == -1) {
291 0         0 die "No index set for " . join(", " . @$xv);
292             }
293              
294 255         292 my $j = _find_index_recursive($y_values_tree, @$yv);
295              
296 255 50       329 if($j == -1) {
297 0         0 die "No index set for " . join(", " . @$yv);
298             }
299              
300 255         370 $table[$height_offset + $j]->[$width_offset + $i] = $v;
301             }
302              
303 8         22 my @w;
304              
305 8         14 for my $row (@table) {
306 88         130 while(@w < @$row) {
307 77         109 push @w, 0;
308             }
309 88         111 for(my $i = 0; $i < @$row; ++$i) {
310 852         923 my $l = length($row->[$i]);
311              
312 852 100       1273 if($l > $w[$i]) {
313 113         165 $w[$i] = $l;
314             }
315             }
316             }
317              
318 8         14 for my $row (@table) {
319 88     852   217 $this->push_line(_format_row(\@w, sub { return ("-" x $_[1]); }, "+"));
  852         1054  
320 88 50   852   229 $this->push_line(_format_row(\@w, sub { if($_[0] < @$row) { return $row->[$_[0]]; } return ""; }, "|"));
  852         1019  
  852         1038  
  0         0  
321             }
322 8     77   31 $this->push_line(_format_row(\@w, sub { return ("-" x $_[1]); }, "+"));
  77         101  
323             }
324              
325             sub output_records {
326 2     2 0 5 my ($this, $xfields, $yfields, $values, $ordered_x_values, $ordered_y_values) = @_;
327              
328 2         4 my $records = {};
329              
330             #fill in hashes
331 2         5 foreach my $y_values (@$ordered_y_values) {
332 12         17 my $key = join('-', @$y_values);
333              
334             # Fill in empties
335 12         21 foreach my $x_values (@$ordered_x_values) {
336              
337 102   100     189 my $hash = $records->{$key} ||= {};
338 102         92 my $last_hash = $hash;
339 102         84 my $last_x_value;
340              
341 102         89 my $index = -1;
342 102         106 foreach my $x_value (@$x_values) {
343 204         181 $index++;
344 204         184 my $x_name = $xfields->[$index];
345 204   100     474 $hash->{$x_name}->{$x_value} ||= {};
346 204         194 $last_hash = $hash->{$x_name};
347 204         179 $last_x_value = $x_value;
348 204         251 $hash = $hash->{$x_name}->{$x_value};
349             }
350 102         141 $last_hash->{$last_x_value} = '';
351             }
352             }
353              
354 2         9 foreach my $vector (@$values) {
355 49         72 my ($xvalues, $yvalues, $value) = @$vector;
356 49         64 my $record_key = join('-', @$yvalues);
357 49   50     63 my $record_hash = ($records->{$record_key} ||= {});
358              
359 49         47 my $index = -1;
360 49         50 foreach my $yfield (@$yfields) {
361 98         84 $index++;
362 98         123 $record_hash->{$yfield} = $yvalues->[$index];
363             }
364              
365 49         46 my $data = $record_hash;
366 49         42 $index = -1;
367 49         47 my $last_hash;
368             my $last_xvalue;
369 49         50 foreach my $xfield (@$xfields) {
370 98         91 $index++;
371 98         91 my $xvalue = $xvalues->[$index];
372              
373 98   100     180 $data->{$xfield}->{$xvalue} ||= {};
374 98         91 $last_hash = $data->{$xfield};
375 98         88 $last_xvalue = $xvalue;
376              
377 98         108 $data = $data->{$xfield}->{$xvalue};
378             }
379              
380 49         66 $last_hash->{$last_xvalue} = $value;
381             }
382              
383 2         4 foreach my $y_values (@$ordered_y_values) {
384 12         18 my $key = join('-', @$y_values);
385 12         23 $this->push_record(App::RecordStream::Record->new($records->{$key}));
386             }
387             }
388              
389             sub _format_row {
390 184     184   258 my ($w, $pfn, $delim) = @_;
391              
392 184         177 my $s = $delim;
393 184         263 for(my $i = 0; $i < @$w; ++$i) {
394 1781         1907 my $c = $pfn->($i, $w->[$i]);
395              
396 1781         2154 $c .= " " x ($w->[$i] - length($c));
397              
398 1781         2617 $s .= $c . $delim;
399             }
400              
401 184         364 return $s;
402             }
403              
404             sub _get_sort {
405 61     61   53 my $this = shift;
406 61         63 my $field = shift;
407              
408 61         75 my $comparator = $this->{'SORTS'}->{$field};
409 61 100       82 if(!defined($comparator)) {
410 59         93 return undef;
411             }
412              
413             return sub {
414 2     2   3 my @fake_records = map { App::RecordStream::Record->new($field => $_) } @_;
  19         28  
415 2         7 @fake_records = sort { $comparator->($a, $b) } @fake_records;
  41         53  
416 2         5 return map { $_->{$field} } @fake_records;
  19         30  
417 2         8 };
418             }
419              
420             sub _new_node {
421 211     211   492 return [{}, [], -1];
422             }
423              
424             sub _touch_node_recurse {
425 1678     1678   2151 my ($node, @keys) = @_;
426              
427 1678 100       2058 if(!@keys) {
428 608         821 return;
429             }
430              
431 1070         1165 my $hash = $node->[0];
432 1070         1037 my $array = $node->[1];
433              
434 1070         1079 my $key = shift @keys;
435 1070         1186 my $next_node = $hash->{$key};
436 1070 100       1291 if(!$next_node) {
437 191         237 $next_node = $hash->{$key} = _new_node();
438 191         282 push @$array, $key;
439             }
440              
441 1070         1264 _touch_node_recurse($next_node, @keys);
442             }
443              
444             sub _dump_node_recurse {
445 211     211   252 my ($this, $node, $acc, $fields_left, $values_so_far) = @_;
446              
447 211         207 my $hash = $node->[0];
448 211         204 my $array = $node->[1];
449              
450 211 100       264 if(!@$fields_left) {
451 150         154 $node->[2] = scalar(@$acc);
452 150         220 push @$acc, [@$values_so_far];
453 150         178 return;
454             }
455              
456 61         71 my $field = shift @$fields_left;
457              
458 61         98 my @field_values = @$array;
459 61         85 my $sort = $this->_get_sort($field);
460 61 100       80 if(defined($sort))
461             {
462 2         5 @field_values = $sort->(@field_values);
463             }
464              
465 61         75 foreach my $key (@field_values) {
466 191         221 push @$values_so_far, $key;
467 191         314 $this->_dump_node_recurse($hash->{$key}, $acc, $fields_left, $values_so_far);
468 191         216 pop @$values_so_far;
469             }
470              
471 61         100 unshift @$fields_left, $field;
472             }
473              
474             sub _find_index_recursive {
475 1384     1384   1611 my ($node, @path) = @_;
476              
477 1384 100       1595 if(!@path) {
478 510         775 return $node->[2];
479             }
480              
481 874         879 my $hash = $node->[0];
482              
483 874         861 my $k = shift @path;
484 874         904 my $next_node = $hash->{$k};
485              
486 874 50       1017 if(!$next_node) {
487 0         0 die "Missing key " . $k . " followed by " . join(", ", @path);
488             }
489              
490 874         959 return _find_index_recursive($next_node, @path);
491             }
492              
493             sub add_help_types {
494 10     10 0 18 my $this = shift;
495 10         31 $this->use_help_type('keyspecs');
496 10         23 $this->use_help_type('keygroups');
497 10         23 $this->use_help_type('keys');
498             $this->add_help_type(
499             'full',
500 0     0     sub { $this->full_help() },
501 10         47 'Tutorial on toptable, with many examples'
502             );
503             }
504              
505             sub full_help {
506 0     0 0   my $this = shift;
507 0           print $this->format_usage(<
508             Full Help
509              
510             __FORMAT_TEXT__
511             Lets first take a look at some examples:
512              
513             Lets take this stream, which is a portion of my recs-fromps:
514             __FORMAT_TEXT__
515             \$ recs-fromps --fields rss,pid,state,priority | recs-topn --key state -n 5 | tee /var/tmp/psrecs
516             {"priority":0,"pid":1,"rss":471040,"state":"sleep"}
517             {"priority":0,"pid":2,"rss":0,"state":"sleep"}
518             {"priority":0,"pid":3,"rss":0,"state":"sleep"}
519             {"priority":0,"pid":4,"rss":0,"state":"sleep"}
520             {"priority":19,"pid":5,"rss":0,"state":"sleep"}
521             {"priority":19,"pid":2094,"rss":8351744,"state":"run"}
522             {"priority":0,"pid":28129,"rss":4784128,"state":"run"}
523             {"priority":19,"pid":28171,"rss":405504,"state":"run"}
524              
525             __FORMAT_TEXT__
526             Ok, Now lets get a table out of this, first we'll collate into some useful information:
527             __FORMAT_TEXT__
528             \$ cat /var/tmp/psrecs | recs-collate --perfect --key priority,state -a count
529             {"priority":0,"count":4,"state":"sleep"}
530             {"priority":19,"count":1,"state":"sleep"}
531             {"priority":0,"count":1,"state":"run"}
532             {"priority":19,"count":2,"state":"run"}
533              
534             __FORMAT_TEXT__
535             And lets get a table:
536             __FORMAT_TEXT__
537             \$ cat /var/tmp/psrecs | recs-collate --perfect --key priority,state -a count | recs-toptable --x priority --y state
538             +-----+--------+-+--+
539             | |priority|0|19|
540             +-----+--------+-+--+
541             |state| | | |
542             +-----+--------+-+--+
543             |run | |1|2 |
544             +-----+--------+-+--+
545             |sleep| |4|1 |
546             +-----+--------+-+--+
547              
548             __FORMAT_TEXT__
549             So, you can see that the VALUES of priority and state are used as the columns /
550             rows. So that there is 1 process in state 'run' and priority 0, and 4 in state
551             'sleep' and priority 0
552              
553             The --cube option on recs-collate also interacts very well with toptable:
554             __FORMAT_TEXT__
555              
556             \$ cat /var/tmp/psrecs | recs-collate --perfect --key priority,state -a count --cube | recs-toptable --x priority --y state
557             +-----+--------+-+--+---+
558             | |priority|0|19|ALL|
559             +-----+--------+-+--+---+
560             |state| | | | |
561             +-----+--------+-+--+---+
562             |ALL | |5|3 |8 |
563             +-----+--------+-+--+---+
564             |run | |1|2 |3 |
565             +-----+--------+-+--+---+
566             |sleep| |4|1 |5 |
567             +-----+--------+-+--+---+
568              
569             __FORMAT_TEXT__
570             We added an ALL row and an ALL column. So from this you can see that there are
571             5 processes in priority 0, 3 processes in state 'run' and 8 processes all told
572             in the table (the ALL, ALL intersection)
573              
574             Now lets see what happens when we have more than 1 left over field. Lets also
575             sum up the rss usage of the processes with -a sum,rss on recs-collate:
576             __FORMAT_TEXT__
577              
578             \$ cat /var/tmp/psrecs | recs-collate --perfect --key priority,state -a count --cube -a sum,rss
579             {"priority":0,"count":4,"state":"sleep","sum_rss":471040}
580             {"priority":"ALL","count":5,"state":"sleep","sum_rss":471040}
581             {"priority":19,"count":1,"state":"sleep","sum_rss":0}
582             {"priority":0,"count":5,"state":"ALL","sum_rss":5255168}
583             {"priority":0,"count":1,"state":"run","sum_rss":4784128}
584             {"priority":"ALL","count":8,"state":"ALL","sum_rss":14012416}
585             {"priority":"ALL","count":3,"state":"run","sum_rss":13541376}
586             {"priority":19,"count":3,"state":"ALL","sum_rss":8757248}
587             {"priority":19,"count":2,"state":"run","sum_rss":8757248}
588              
589             __FORMAT_TEXT__
590             So now we have 2 left over fields that aren't columns, count and sum_rss. What
591             happens to our table now:
592             __FORMAT_TEXT__
593              
594             \$ cat /var/tmp/psrecs | recs-collate --perfect --key priority,state -a count --cube -a sum,rss | recs-toptable --x priority --y state
595             +-----+--------+-------+-------+--------+
596             | |priority|0 |19 |ALL |
597             +-----+--------+-------+-------+--------+
598             |state| | | | |
599             +-----+--------+-------+-------+--------+
600             |ALL | |5255168|8757248|14012416|
601             +-----+--------+-------+-------+--------+
602             |run | |4784128|8757248|13541376|
603             +-----+--------+-------+-------+--------+
604             |sleep| |471040 |0 |471040 |
605             +-----+--------+-------+-------+--------+
606              
607             __FORMAT_TEXT__
608             We now have sum_rss values in this field. What if we want the other field
609             (count) displayed? We just use --v-field to specify what value field to
610             use:
611             __FORMAT_TEXT__
612              
613             \$ cat /var/tmp/psrecs | recs-collate --perfect --key priority,state -a count --cube -a sum,rss | recs-toptable --x priority --y state --v count
614             +-----+--------+-+--+---+
615             | |priority|0|19|ALL|
616             +-----+--------+-+--+---+
617             |state| | | | |
618             +-----+--------+-+--+---+
619             |ALL | |5|3 |8 |
620             +-----+--------+-+--+---+
621             |run | |1|2 |3 |
622             +-----+--------+-+--+---+
623             |sleep| |4|1 |5 |
624             +-----+--------+-+--+---+
625              
626             __FORMAT_TEXT__
627             Ok, but what if we want to see both left over fields at the same time? What we
628             really want is to add a column or row for each of count and sum_rss. (where
629             the title of the row is count or sum_rss, not the values of the field). We can
630             do this by using the special FIELD specifier like so:
631             __FORMAT_TEXT__
632              
633             \$ cat /var/tmp/psrecs | recs-collate --perfect --key priority,state -a count --cube -a sum,rss | recs-toptable --x priority,FIELD --y state
634             +-----+--------+-----+-------+-----+-------+-----+--------+
635             | |priority|0 | |19 | |ALL | |
636             +-----+--------+-----+-------+-----+-------+-----+--------+
637             | |FIELD |count|sum_rss|count|sum_rss|count|sum_rss |
638             +-----+--------+-----+-------+-----+-------+-----+--------+
639             |state| | | | | | | |
640             +-----+--------+-----+-------+-----+-------+-----+--------+
641             |ALL | |5 |5255168|3 |8757248|8 |14012416|
642             +-----+--------+-----+-------+-----+-------+-----+--------+
643             |run | |1 |4784128|2 |8757248|3 |13541376|
644             +-----+--------+-----+-------+-----+-------+-----+--------+
645             |sleep| |4 |471040 |1 |0 |5 |471040 |
646             +-----+--------+-----+-------+-----+-------+-----+--------+
647              
648             __FORMAT_TEXT__
649             So, now in one table we can see all the intersections of state and priority
650             values with the count and sum_rss fields. Remember that the ALL field (row and
651             column) are provided by the --cube functionality of recs-collate
652              
653             Now, say you want to pin value, lets just look at processes in state run for
654             instance:
655             __FORMAT_TEXT__
656              
657             \$ cat /var/tmp/psrecs | recs-collate --perfect --cube --key priority,state -a count -a sum,rss | recs-toptable --x priority,FIELD --y state -v sum_rss,count --pin state=run
658             +-----+--------+-----+-------+-----+-------+-----+--------+
659             | |priority|0 | |19 | |ALL | |
660             +-----+--------+-----+-------+-----+-------+-----+--------+
661             | |FIELD |count|sum_rss|count|sum_rss|count|sum_rss |
662             +-----+--------+-----+-------+-----+-------+-----+--------+
663             |state| | | | | | | |
664             +-----+--------+-----+-------+-----+-------+-----+--------+
665             |run | |1 |4784128|2 |8757248|3 |13541376|
666             +-----+--------+-----+-------+-----+-------+-----+--------+
667              
668             __FORMAT_TEXT__
669             As you can see, this is basically short hand for doing a recs-grep, the transformation to recs group would look like:
670             __FORMAT_TEXT__
671              
672             \$ cat /var/tmp/psrecs | recs-collate --perfect --cube --key priority,state -a count -a sum,rss | recs-grep '\$r->{state} eq "run"' | recs-toptable --x priority,FIELD --y state -v sum_rss,count
673              
674             __FORMAT_TEXT__
675             (which produces the same table as above).
676             __FORMAT_TEXT__
677             FULL_EXAMPLES
678             }
679              
680             sub usage {
681 0     0 0   my $this = shift;
682              
683 0           my $options = [
684             ['x-field|x', 'Add a x field, values of the specified field will become columns in the table, may be a keyspec or a keygroup'],
685             ['y-field|y', 'Add a y field, values of the specified field will become rows in the table, may be a keyspec or a keygroup'],
686             ['v-field|v', 'Specify the value to display in the table, if multiple value fields are specified and FIELD is not placed in the x or y axes, then the last one wins, may be a keyspec or a keygroup. If FIELD is in an axis, then --v specifies the fields to be included in that expansion'],
687             ['pin', 'Pin a field to a certain value, only display records matching that value, very similar to doing a recs-grep before toptable. Takes value of the form: field=pinnedValue, field may be a keyspec (not a keygroup)'],
688             ['sort', 'Take sort specifications to sort X values and Y values in headers. See `recs-sort --help` for details of sort specifications, especially the * option to sort "ALL" to the end, e.g. "some_field=lex*".'],
689             ['noheaders', 'Do not print row and column headers (removes blank rows and columns)'],
690             ['records|recs', 'Instead of printing table, output records, one per row of the table.'],
691             ['sort-to-end|sa', 'Sort ALL fields to the end, equivalent to --sort FIELD=* for each --y and --y field'],
692             ];
693              
694 0           my $args_string = $this->options_string($options);
695              
696 0           return <
697             Usage: recs-toptable []
698             __FORMAT_TEXT__
699             Creates a multi-dimensional pivot table with any number of x and y axises.
700             There is additional help available through --full that includes examples
701              
702             The x and y rows and columns are the values of the field specified
703              
704             X and Y fields can take the special value 'FIELD' which uses unused field
705             names as values for the FIELD dimension
706             __FORMAT_TEXT__
707              
708             $args_string
709              
710             Simple Examples (see --full for more detailed descriptions)
711              
712             # Collate and display in a nice table
713             ... | recs-collate --key state,priority -a count | recs-toptable --x state --y priority
714              
715             # Display left over field names as columns
716             ... | recs-collate --key state,priority -a count -a sum,rss | recs-toptable --x state,FIELD --y priority
717              
718             # Specify the displayed cell values
719             ... | recs-collate --key state,priority -a count -a sum,rss | recs-toptable --x state,FIELD --y priority --value sum_rss
720             USAGE
721             }
722              
723             1;