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.23";
4              
5 2     2   982 use strict;
  2         4  
  2         48  
6 2     2   8 use warnings;
  2         4  
  2         47  
7              
8 2     2   10 use base qw(App::RecordStream::Accumulator App::RecordStream::Operation);
  2         3  
  2         441  
9              
10 2     2   12 use App::RecordStream::Record;
  2         4  
  2         4696  
11              
12             # TODO: amling, this format is so ugly it hurts. Think of something better.
13              
14             sub init {
15 10     10 0 25 my $this = shift;
16 10         20 my $args = shift;
17              
18 10         25 my %pins;
19 10         19 my $headers = 1;
20 10         21 my $full = 0;
21              
22 10         73 my $xgroup = App::RecordStream::KeyGroups->new();
23 10         34 my $ygroup = App::RecordStream::KeyGroups->new();
24 10         32 my $vgroup = App::RecordStream::KeyGroups->new();
25 10         24 my $output_records = 0;
26 10         22 my $all_at_end = 0;
27 10         29 my %sorts = ();
28              
29             my $spec = {
30 10     10   11176 "x-field|x=s" => sub { $xgroup->add_groups($_[1]); },
31 10     10   1387 "y-field|y=s" => sub { $ygroup->add_groups($_[1]); },
32 2     2   277 "v-field|v=s" => sub { $vgroup->add_groups($_[1]); },
33 3 50   3   416 "pin=s" => sub { for(split(/,/, $_[1])) { if(/^(.*)=(.*)$/) { $pins{$1} = $2; } } },
  3         22  
  3         16  
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         154 'records|recs' => \$output_records,
37             'sort-all-to-end|sa' => \$all_at_end,
38             };
39              
40 10         76 $this->parse_options($args, $spec);
41              
42 10 50 33     40 if ( %sorts && $all_at_end ) {
43 0         0 die "Cannot specify both --sort and --sort-all-to-end\n";
44             }
45              
46 10         35 my $do_vfields = !$vgroup->has_any_group();
47              
48 10         30 $this->{'XGROUP'} = $xgroup;
49 10         21 $this->{'YGROUP'} = $ygroup;
50 10         24 $this->{'VGROUP'} = $vgroup;
51              
52 10         24 $this->{'PINS_HASH'} = \%pins;
53 10         31 $this->{'SORTS'} = \%sorts;
54 10         21 $this->{'SORT_ALL_TO_END'} = $all_at_end;
55 10         22 $this->{'HEADERS'} = $headers;
56 10         25 $this->{'DO_VFIELDS'} = $do_vfields;
57 10         175 $this->{'OUTPUT_RECORDS'} = $output_records;
58             }
59              
60             sub stream_done {
61 10     10 0 20 my $this = shift;
62              
63 10         16 my %pins = %{$this->{'PINS_HASH'}};
  10         45  
64 10         22 my $headers = $this->{'HEADERS'};
65 10         21 my $do_vfields = $this->{'DO_VFIELDS'};
66              
67 10         17 my $xgroup = $this->{'XGROUP'};
68 10         21 my $ygroup = $this->{'YGROUP'};
69 10         19 my $vgroup = $this->{'VGROUP'};
70              
71 10         20 my $xfields_hash = {};
72 10         28 my $yfields_hash = {};
73 10         22 my $vfields_hash = {};
74              
75 10         26 my $records = $this->get_records();
76              
77 10         20 my (@xfields, @yfields, @vfields);
78             # Prep x and y fields
79 10         24 foreach my $record (@$records) {
80 309         488 foreach my $spec ( @{$xgroup->get_keyspecs_for_record($record)} ) {
  309         739  
81 552 100       1342 if ( !$xfields_hash->{$spec} ) {
82 18         34 $xfields_hash->{$spec} = 1;
83 18         39 push @xfields, $spec;
84             }
85             }
86 309         549 foreach my $spec ( @{$ygroup->get_keyspecs_for_record($record)} ) {
  309         797  
87 516 100       1348 if ( !$yfields_hash->{$spec} ) {
88 17         35 $yfields_hash->{$spec} = 1;
89 17         37 push @yfields, $spec;
90             }
91             }
92             }
93              
94 10 100       37 if ( $this->{'SORT_ALL_TO_END' } ) {
95 1         4 foreach my $field (@xfields, @yfields) {
96 2         304 print "creating comp for $field\n";
97 2         17 my ($comparator, $comp_field) = App::RecordStream::Record::get_comparator_and_field("$field=*");
98 2         7 $this->{'SORTS'}->{$field} = $comparator;
99             }
100             }
101              
102             # Prep v fields
103 10 100       26 if($do_vfields) {
104 8         17 my %vfields;
105             my %used_first_level_keys;
106              
107 8         20 for my $record (@$records) {
108 267         496 foreach my $spec (@xfields, @yfields, keys %pins) {
109 1008         2228 my $key_list = $record->get_key_list_for_spec($spec);
110 1008 50       2242 if (scalar @$key_list > 0) {
111 1008         2036 $used_first_level_keys{$key_list->[0]} = 1;
112             }
113             }
114              
115 267         739 foreach my $field (keys(%$record)) {
116 1224 100 100     3111 if ( !exists($used_first_level_keys{$field}) &&
117             !exists($vfields{$field}) ) {
118 9         18 push @vfields, $field;
119 9         21 $vfields{$field} = 1;
120             }
121             }
122             }
123              
124             # lexically sort if user didn't explicitly specify any vfields
125 8         29 @vfields = sort @vfields;
126             }
127             else {
128 2         8 my $vfields_hash = {};
129 2         7 foreach my $record (@$records) {
130 42         82 foreach my $spec ( @{$vgroup->get_keyspecs_for_record($record)} ) {
  42         135  
131 84 100       270 if ( !$vfields_hash->{$spec} ) {
132 4         14 $vfields_hash->{$spec} = 1;
133 4         11 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         22 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         39 my $x_values_tree = _new_node();
145 10         24 my $y_values_tree = _new_node();
146 10         28 foreach my $record (@$records) {
147             # make sure records matches appropriate pins
148 309         536 my $kickout = 0;
149 309         652 foreach my $pfield (keys(%pins)) {
150 108 50       274 if($pfield eq "FIELD") {
151 0         0 next;
152             }
153              
154 108         155 my $v = '';
155              
156 108 50       270 if ( $record->has_key_spec($pfield) ) {
157 108         151 $v = ${$record->guess_key_from_spec($pfield)};
  108         259  
158             }
159              
160 108 100       306 if($pins{$pfield} ne $v) {
161 68         118 $kickout = 1;
162 68         109 last;
163             }
164             }
165 309 100       673 if($kickout) {
166 68         125 next;
167             }
168              
169 241         404 for my $vfield (@vfields) {
170             # nothing to see here
171 304 50       766 if(!$record->has_key_spec($vfield)) {
172 0         0 next;
173             }
174              
175             # if field is pinned, skip other vfields
176 304 50 33     787 if(exists($pins{"FIELD"}) && $pins{"FIELD"} ne $vfield) {
177 0         0 next;
178             }
179              
180 304         456 my @xv;
181 304         586 for my $xfield (@xfields) {
182 542         892 my $v = "";
183 542 100       1504 if($xfield eq "FIELD") {
    50          
184 126         224 $v = $vfield;
185             }
186             elsif($record->has_key_spec($xfield)) {
187 416         640 $v = ${$record->guess_key_from_spec($xfield)};
  416         1013  
188             }
189 542         1369 push @xv, $v;
190             }
191              
192 304         510 my @yv;
193 304         535 for my $yfield (@yfields) {
194 528         846 my $v = "";
195 528 50       1581 if($yfield eq "FIELD") {
    50          
196 0         0 $v = $vfield;
197             }
198             elsif($record->has_key_spec($yfield)) {
199 528         851 $v = ${$record->guess_key_from_spec($yfield)};
  528         1252  
200             }
201 528         1384 push @yv, $v;
202             }
203              
204 304         517 my $v = "";
205 304 50       731 if($record->has_key_spec($vfield)) {
206 304         502 $v = ${$record->guess_key_from_spec($vfield)};
  304         746  
207             }
208              
209 304         866 _touch_node_recurse($x_values_tree, @xv);
210 304         700 _touch_node_recurse($y_values_tree, @yv);
211              
212 304         999 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         20 my @x_values_list;
222 10         60 $this->_dump_node_recurse($x_values_tree, \@x_values_list, [@xfields], []);
223              
224 10         24 my @y_values_list;
225 10         35 $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       43 if ( $this->{'OUTPUT_RECORDS'} ) {
229 2         11 $this->output_records(\@xfields, \@yfields, \@r2, \@x_values_list, \@y_values_list);
230 2         40 return;
231             }
232              
233              
234 8         17 my $width_offset = scalar @yfields;
235 8         14 my $height_offset = scalar @xfields;
236              
237 8 50       21 if ( $headers ) {
238 8         16 $width_offset += 1;
239 8         15 $height_offset += 1;
240             }
241              
242 8         16 my $w = $width_offset + scalar(@x_values_list);
243 8         14 my $h = $height_offset + scalar(@y_values_list);
244 8         33 my @table = map { [map { "" } (1..$w)] } (1..$h);
  88         157  
  852         1432  
245              
246 8 50       27 if ( $headers ) {
247 8         25 for(my $i = 0; $i < @xfields; ++$i) {
248 14         48 $table[$i]->[scalar(@yfields)] = $xfields[$i];
249             }
250              
251 8         28 for(my $i = 0; $i < @yfields; ++$i) {
252 13         40 $table[scalar(@xfields)]->[$i] = $yfields[$i];
253             }
254             }
255              
256 8         20 my @last_xv = map { "" } (1..@xfields);
  14         32  
257 8         29 for(my $i = 0; $i < @x_values_list; ++$i) {
258 56         87 my $xv = $x_values_list[$i];
259 56         116 for(my $j = 0; $j < @xfields; ++$j) {
260 94 100       206 if($last_xv[$j] ne $xv->[$j]) {
261 72         108 $last_xv[$j] = $xv->[$j];
262 72         126 $table[$j]->[$width_offset + $i] = $xv->[$j];
263 72         236 for(my $k = $j + 1; $k < @xfields; ++$k) {
264 16         53 $last_xv[$k] = "";
265             }
266             }
267             }
268             }
269              
270 8         21 my @last_yv = map { "" } (1..@yfields);
  13         28  
271 8         27 for(my $i = 0; $i < @y_values_list; ++$i) {
272 66         95 my $yv = $y_values_list[$i];
273 66         132 for(my $j = 0; $j < @yfields; ++$j) {
274 109 100       242 if($last_yv[$j] ne $yv->[$j]) {
275 81         119 $last_yv[$j] = $yv->[$j];
276 81         144 $table[$height_offset + $i]->[$j] = $yv->[$j];
277 81         271 for(my $k = $j + 1; $k < @yfields; ++$k) {
278 15         47 $last_yv[$k] = "";
279             }
280             }
281             }
282             }
283              
284 8         19 for my $r2 (@r2) {
285 255         446 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         463 my $i = _find_index_recursive($x_values_tree, @$xv);
289              
290 255 50       537 if($i == -1) {
291 0         0 die "No index set for " . join(", " . @$xv);
292             }
293              
294 255         481 my $j = _find_index_recursive($y_values_tree, @$yv);
295              
296 255 50       535 if($j == -1) {
297 0         0 die "No index set for " . join(", " . @$yv);
298             }
299              
300 255         509 $table[$height_offset + $j]->[$width_offset + $i] = $v;
301             }
302              
303 8         14 my @w;
304              
305 8         18 for my $row (@table) {
306 88         197 while(@w < @$row) {
307 77         171 push @w, 0;
308             }
309 88         188 for(my $i = 0; $i < @$row; ++$i) {
310 852         1336 my $l = length($row->[$i]);
311              
312 852 100       2121 if($l > $w[$i]) {
313 113         235 $w[$i] = $l;
314             }
315             }
316             }
317              
318 8         17 for my $row (@table) {
319 88     852   299 $this->push_line(_format_row(\@w, sub { return ("-" x $_[1]); }, "+"));
  852         1481  
320 88 50   852   333 $this->push_line(_format_row(\@w, sub { if($_[0] < @$row) { return $row->[$_[0]]; } return ""; }, "|"));
  852         1658  
  852         1518  
  0         0  
321             }
322 8     77   31 $this->push_line(_format_row(\@w, sub { return ("-" x $_[1]); }, "+"));
  77         140  
323             }
324              
325             sub output_records {
326 2     2 0 9 my ($this, $xfields, $yfields, $values, $ordered_x_values, $ordered_y_values) = @_;
327              
328 2         5 my $records = {};
329              
330             #fill in hashes
331 2         7 foreach my $y_values (@$ordered_y_values) {
332 12         23 my $key = join('-', @$y_values);
333              
334             # Fill in empties
335 12         19 foreach my $x_values (@$ordered_x_values) {
336              
337 102   100     260 my $hash = $records->{$key} ||= {};
338 102         142 my $last_hash = $hash;
339 102         142 my $last_x_value;
340              
341 102         141 my $index = -1;
342 102         158 foreach my $x_value (@$x_values) {
343 204         272 $index++;
344 204         300 my $x_name = $xfields->[$index];
345 204   100     698 $hash->{$x_name}->{$x_value} ||= {};
346 204         309 $last_hash = $hash->{$x_name};
347 204         280 $last_x_value = $x_value;
348 204         352 $hash = $hash->{$x_name}->{$x_value};
349             }
350 102         190 $last_hash->{$last_x_value} = '';
351             }
352             }
353              
354 2         5 foreach my $vector (@$values) {
355 49         81 my ($xvalues, $yvalues, $value) = @$vector;
356 49         91 my $record_key = join('-', @$yvalues);
357 49   50     106 my $record_hash = ($records->{$record_key} ||= {});
358              
359 49         70 my $index = -1;
360 49         80 foreach my $yfield (@$yfields) {
361 98         123 $index++;
362 98         175 $record_hash->{$yfield} = $yvalues->[$index];
363             }
364              
365 49         65 my $data = $record_hash;
366 49         71 $index = -1;
367 49         71 my $last_hash;
368             my $last_xvalue;
369 49         72 foreach my $xfield (@$xfields) {
370 98         133 $index++;
371 98         135 my $xvalue = $xvalues->[$index];
372              
373 98   100     292 $data->{$xfield}->{$xvalue} ||= {};
374 98         156 $last_hash = $data->{$xfield};
375 98         125 $last_xvalue = $xvalue;
376              
377 98         181 $data = $data->{$xfield}->{$xvalue};
378             }
379              
380 49         88 $last_hash->{$last_xvalue} = $value;
381             }
382              
383 2         6 foreach my $y_values (@$ordered_y_values) {
384 12         24 my $key = join('-', @$y_values);
385 12         34 $this->push_record(App::RecordStream::Record->new($records->{$key}));
386             }
387             }
388              
389             sub _format_row {
390 184     184   346 my ($w, $pfn, $delim) = @_;
391              
392 184         267 my $s = $delim;
393 184         417 for(my $i = 0; $i < @$w; ++$i) {
394 1781         3062 my $c = $pfn->($i, $w->[$i]);
395              
396 1781         3113 $c .= " " x ($w->[$i] - length($c));
397              
398 1781         3956 $s .= $c . $delim;
399             }
400              
401 184         548 return $s;
402             }
403              
404             sub _get_sort {
405 61     61   96 my $this = shift;
406 61         93 my $field = shift;
407              
408 61         108 my $comparator = $this->{'SORTS'}->{$field};
409 61 100       133 if(!defined($comparator)) {
410 59         106 return undef;
411             }
412              
413             return sub {
414 2     2   5 my @fake_records = map { App::RecordStream::Record->new($field => $_) } @_;
  19         44  
415 2         8 @fake_records = sort { $comparator->($a, $b) } @fake_records;
  41         83  
416 2         5 return map { $_->{$field} } @fake_records;
  19         45  
417 2         11 };
418             }
419              
420             sub _new_node {
421 211     211   641 return [{}, [], -1];
422             }
423              
424             sub _touch_node_recurse {
425 1678     1678   3285 my ($node, @keys) = @_;
426              
427 1678 100       3622 if(!@keys) {
428 608         1220 return;
429             }
430              
431 1070         1747 my $hash = $node->[0];
432 1070         1550 my $array = $node->[1];
433              
434 1070         1665 my $key = shift @keys;
435 1070         1768 my $next_node = $hash->{$key};
436 1070 100       2207 if(!$next_node) {
437 191         379 $next_node = $hash->{$key} = _new_node();
438 191         407 push @$array, $key;
439             }
440              
441 1070         2019 _touch_node_recurse($next_node, @keys);
442             }
443              
444             sub _dump_node_recurse {
445 211     211   347 my ($this, $node, $acc, $fields_left, $values_so_far) = @_;
446              
447 211         338 my $hash = $node->[0];
448 211         295 my $array = $node->[1];
449              
450 211 100       430 if(!@$fields_left) {
451 150         227 $node->[2] = scalar(@$acc);
452 150         331 push @$acc, [@$values_so_far];
453 150         257 return;
454             }
455              
456 61         101 my $field = shift @$fields_left;
457              
458 61         147 my @field_values = @$array;
459 61         128 my $sort = $this->_get_sort($field);
460 61 100       141 if(defined($sort))
461             {
462 2         6 @field_values = $sort->(@field_values);
463             }
464              
465 61         115 foreach my $key (@field_values) {
466 191         312 push @$values_so_far, $key;
467 191         457 $this->_dump_node_recurse($hash->{$key}, $acc, $fields_left, $values_so_far);
468 191         328 pop @$values_so_far;
469             }
470              
471 61         140 unshift @$fields_left, $field;
472             }
473              
474             sub _find_index_recursive {
475 1384     1384   2439 my ($node, @path) = @_;
476              
477 1384 100       2790 if(!@path) {
478 510         1070 return $node->[2];
479             }
480              
481 874         1301 my $hash = $node->[0];
482              
483 874         1254 my $k = shift @path;
484 874         1374 my $next_node = $hash->{$k};
485              
486 874 50       1694 if(!$next_node) {
487 0         0 die "Missing key " . $k . " followed by " . join(", ", @path);
488             }
489              
490 874         1529 return _find_index_recursive($next_node, @path);
491             }
492              
493             sub add_help_types {
494 10     10 0 23 my $this = shift;
495 10         52 $this->use_help_type('keyspecs');
496 10         36 $this->use_help_type('keygroups');
497 10         38 $this->use_help_type('keys');
498             $this->add_help_type(
499             'full',
500 0     0     sub { $this->full_help() },
501 10         67 '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;