File Coverage

bin/storage2dot
Criterion Covered Total %
statement 81 160 50.6
branch 28 72 38.8
condition 6 12 50.0
subroutine 10 11 90.9
pod n/a
total 125 255 49.0


line stmt bran cond sub pod time code
1             #!/usr/bin/env perl
2             #
3             # This file is part of StorageDisplay
4             #
5             # This software is copyright (c) 2014-2023 by Vincent Danjean.
6             #
7             # This is free software; you can redistribute it and/or modify it under
8             # the same terms as the Perl 5 programming language system itself.
9             #
10              
11             # PODNAME: storage2dot
12             # ABSTRACT: analyse and generate a graphical view of a machine storage
13              
14              
15 16     16   86161 use strict;
  16         27  
  16         633  
16 16     16   66 use warnings;
  16         22  
  16         1281  
17              
18 16         5382401 our $VERSION = '2.06'; # VERSION
19              
20             # delay module loading so that local data collect can be done
21             # without extra modules
22             # use StorageDisplay;
23 16     16   11003 use StorageDisplay::Collect;
  16         126  
  16         8689  
24              
25 16         50 my $cleanup_readmode=0;
26             sub collect_from_remote {
27 0     0   0 my $remote = shift;
28 0         0 my $content='';
29 0 0       0 eval {
30 0         0 require Net::OpenSSH;
31 0         0 Net::OpenSSH->import;
32 0         0 require Term::ReadKey;
33 0         0 Term::ReadKey->import;
34 0         0 1;
35             } or die "Cannot load required modules (Net::OpenSSH and/or Term::ReadKey) for remote data collect: $!\n";
36             END {
37 16 50   16   0 if ($cleanup_readmode) {
38             # in case of bug, always restore normal mode
39 0         0 ReadMode('normal');
40             }
41             }
42 0         0 my $ssh = Net::OpenSSH->new($remote);
43 0 0       0 $ssh->error and
44             die "Couldn't establish SSH connection: ". $ssh->error;
45              
46 0         0 my ($in, $out, $pid) = $ssh->open2(
47             #'cat',
48             'perl', '--', '-',
49             );
50              
51 0         0 my $fdperlmod;
52             open($fdperlmod, '<', $INC{'StorageDisplay/Collect.pm'})
53 0 0       0 or die "Cannot open ".INC{'StorageDisplay/Collect.pm'}.": $!\n";
  0         0  
54             #use Sys::Syscall;
55             #Sys::Syscall::sendfile($in, $fdperlmod);
56             {
57 0         0 while(defined(my $line=<$fdperlmod>)) {
  0         0  
58 0 0       0 last if $line =~ m/^__END__\s*$/;
59 0         0 print $in $line;
60             }
61 0         0 close $fdperlmod;
62             }
63             #print $in "StorageDisplay::Collect::dump_collect;\n";
64 0         0 my @args = (@_, 'LocalBySSH');
65 0         0 my $cmd = "StorageDisplay::Collect::dump_collect('".join("','", @args)."');\n";
66 0         0 print STDERR 'Running through SSH: ',$cmd;
67 0         0 print $in $cmd;
68 0         0 print $in "__END__\n";
69 0         0 flush $in;
70              
71 16     16   10174 use IO::Select;
  16         32106  
  16         1253  
72 16     16   9053 use POSIX ":sys_wait_h";
  16         172556  
  16         96  
73 0         0 my $sel = IO::Select->new(\*STDIN, $out);
74 0         0 my $timeout = 1;
75 0         0 $cleanup_readmode=1;
76 0         0 ReadMode('noecho');
77 0         0 my ($in_closed,$out_closed) = (0,0);
78 0         0 while(1) {
79 0         0 $!=0;
80 0         0 my @ready = $sel->can_read($timeout);
81 0 0       0 if ($!) {
82 0         0 die "Error with select: $!\n";
83             }
84 0 0       0 if (scalar(@ready)) {
85 0         0 foreach my $fd (@ready) {
86 0 0       0 if ($fd == $out) {
87 0         0 my $line=<$out>;
88 0 0       0 if (defined($line)) {
89 0         0 $content .= $line;
90             } else {
91 0         0 $sel->remove($out);
92 0         0 close $out;
93 0         0 $out_closed=1;
94             }
95             } else {
96 0         0 my $line=<STDIN>;
97 0 0       0 if (print $in $line) {
98 0         0 flush $in;
99             } else {
100 0         0 $sel->remove(\*STDIN);
101 0         0 close $in;
102 0         0 $in_closed=1;
103             }
104             }
105             }
106             } else {
107 0         0 my $res = waitpid($pid, WNOHANG);
108 0 0       0 if ($res==-1) {
109 0         0 die "Some error occurred ".($? >> 8).": $!\n";
110             }
111 0 0       0 if ($res) {
112 0 0       0 if (!$in_closed) {
113 0         0 $sel->remove(\*STDIN);
114 0         0 close $in;
115             }
116 0         0 ReadMode('normal');
117 0         0 last;
118             }
119             #print STDERR "timeout for $pid\n";
120             }
121             }
122 0 0       0 if (!$out_closed) {
123 0         0 while (defined(my $line=<$out>)) {
124 0         0 $content .= $out;
125             }
126 0         0 $sel->remove($out);
127 0         0 close $out;
128             }
129 0         0 ReadMode('normal');
130 0         0 $cleanup_readmode=0;
131 0         0 return $content;
132             }
133              
134 16     16   47628 use Getopt::Long;
  16         206677  
  16         76  
135 16     16   12739 use Pod::Usage;
  16         1094463  
  16         2569  
136 16     16   157 use Data::Dumper;
  16         32  
  16         39808  
137 16         49 $Data::Dumper::Sortkeys = 1;
138 16         40 $Data::Dumper::Purity = 1;
139              
140             #use Carp::Always;
141              
142 16         187 my $remote;
143             my $data;
144 16         0 my $output;
145              
146 16         0 my $collect;
147 16         0 my $recordfile;
148 16         0 my $replayfile;
149              
150 16         0 my $verbose;
151 16         0 my $help;
152 16         0 my $man;
153              
154 16 50       780 GetOptions ("d|data=s" => \$data, # string
155             "r|remote=s" => \$remote, # string
156             "o|output=s" => \$output, # string
157             "c|collect-only" => \$collect, # flag
158             "record-file=s" => \$recordfile, # string
159             "replay-file=s" => \$replayfile, # string
160             "verbose" => \$verbose, # flag
161             "h|help" => \$help, # flag
162             "man" => \$man, # flag
163             ) or pod2usage(2);
164              
165             sub main() {
166 16 100   16   148 pod2usage(-exitval => 0, -verbose => 1) if $help;
167 15 100       85 pod2usage(-exitval => 0, -verbose => 2) if $man;
168              
169 14 50 33     111 if (defined($data) && (
      66        
170             defined($remote)
171             || defined($recordfile)
172             || defined($replayfile)
173             )) {
174 0         0 die "E: --data cannot be used with --remote, --record, nor --replay\n";
175             }
176              
177 14 50 33     107 if (defined($replayfile) && (
      66        
178             defined($remote)
179             || defined($recordfile)
180             )) {
181 0         0 die "E: --replay cannot be used with --remote, nor --record\n";
182             }
183              
184 14 100       49 if (!$collect) {
185             require StorageDisplay or
186 7 50       4442 die "Cannot load the StorageDisplay module to handle collected data: $!\n";
187             }
188              
189 14         338 my $infos;
190              
191 14 100       81 if ($replayfile) {
192             require StorageDisplay::Collect::CMD::Replay or
193 7 50       4523 die "Replay requested, but unable to load the StorageDisplay::Collect::CMD::Replay module: $!\n";
194 7         34 my $dh;
195 7 50       363 open($dh, "<", $replayfile)
196             or die "Cannot open '$replayfile': $!" ;
197 7         115162 my $replay=join('', <$dh>);
198 7         7746 my $replaydata;
199 7         155 close($dh);
200             {
201 7         22 my $VAR1;
  7         15  
202 7         3257556 eval($replay); ## no critic (ProhibitStringyEval)
203             #print STDERR "c: $content\n";
204 7         136 $replaydata = $VAR1;
205             }
206 7         163 $infos = StorageDisplay::Collect->new(
207             'Replay', 'replay-data' => $replaydata)->collect();
208             }
209              
210 14         71 my $contents;
211             my @recorder;
212 14 50       74 if (defined($recordfile)) {
213 0         0 @recorder = ('Proxy::Recorder', 'recorder-reader');
214             }
215 14 100       101 if (defined($data)) {
    50          
    50          
216 7         41 my $dh;
217 7 50       729 open($dh, "<", $data)
218             or die "Cannot open '$data': $!" ;
219 7         61260 $contents=join('', <$dh>);
220 7         4074 close($dh);
221             } elsif (defined($remote)) {
222 0         0 $contents = collect_from_remote($remote, @recorder);
223             } elsif (not defined($infos)) {
224 0         0 $infos = StorageDisplay::Collect->new(@recorder, 'Local')->collect();
225             }
226              
227             # data are in $contents (if got through Data::Dumper) or directly in $infos
228 14 100       98 if (defined($contents)) {
229             # moving data from $contents to $infos
230             {
231 7         17 my $VAR1;
  7         16  
232 7         3044302 eval($contents); ## no critic (ProhibitStringyEval)
233             #print STDERR "c: $content\n";
234 7         702 $infos = $VAR1;
235             }
236             }
237              
238 14 50       305 if (defined($recordfile)) {
239 0 0       0 if (! exists($infos->{'recorder'})) {
240 0         0 print STDERR "W: skpping recording: no records!\n";
241             } else {
242 0         0 my $dh;
243 0 0       0 open($dh, ">", $recordfile)
244             or die "Cannot open '$data': $!";
245 0         0 print $dh Dumper($infos->{'recorder'});
246 0         0 close($dh);
247             }
248             }
249 14         64 delete($infos->{'recorder'});
250              
251 14         67 my $oldout;
252 14 50       56 if (defined($output)) {
253             # dzil do not want Two-argument "open"
254             # so, commented-out as we do not use it
255             # if this change, a way to write this would have to be found
256             # open(my $oldout, ">&STDOUT") or die "Can't dup STDOUT: $!";
257 14 50       4212 open(STDOUT, '>', $output) or die "Can't redirect STDOUT to $output: $!";
258             }
259              
260 14 100       109 if ($collect) {
261 7         46 print Dumper($infos);
262 7         317148 return;
263             }
264 7         202 my $st=StorageDisplay->new('infos' => $infos);
265              
266 7         11877 $st->createElems();
267 7         1319 $st->display;
268             }
269              
270 16         27207 main
271              
272             __END__
273              
274             =pod
275              
276             =encoding UTF-8
277              
278             =head1 NAME
279              
280             storage2dot - analyse and generate a graphical view of a machine storage
281              
282             =head1 VERSION
283              
284             version 2.06
285              
286             =head1 SYNOPSIS
287              
288             B<storage2dot [OPTIONS]>
289              
290             Options:
291             --remote|-r MACHINE collect data on MACHINE (through SSH)
292             --collect-only|-c generate plain data instead of dot file
293             --data|-d FILE use FILE as data source
294             --output|-o FILE write output into FILE
295             --record-file FILE record shell commandsinto FILE [for tests]
296             --replay-file FILE collect data from FILE [for tests]
297             --help|-h brief documentation
298             --man full documentation
299              
300             This program can be used to collect data about the storage state from
301             local or remote machines (through SSH) and use them to generate a DOT
302             graphic representing them.
303              
304             =head1 OPTIONS
305              
306             =over 8
307              
308             =item B<--remote MACHINE>
309              
310             Collect storage data on MACHINE (through SSH). By default, local
311             storage data are collected (without SSH).
312              
313             =item B<--collect-only>
314              
315             By default, a DOT file is generated from the storage data. With this
316             option, the program do not create the DOT file but only output the
317             raw collected data for later analyze.
318              
319             =item B<--data FILE>
320              
321             In order to generate the DOT file, use the provided data. B<FILE> must
322             have been created with the help of the previous option. No new data
323             are collected when this option is used.
324              
325             =item B<--output FILE>
326              
327             Write generated data (DOT by default) into B<FILE> instead of the
328             standard output.
329              
330             =item B<--record-file FILE>
331              
332             Write shell commands (and their output) that are used to collect data
333             into B<FILE>. This is mainly used for reproducibility during tests.
334              
335             =item B<--replay-file FILE>
336              
337             Use information from B<FILE> instead of running real shell commands in
338             order to collect data. B<FILE> must be This is mainly used for
339             reproducibility during tests.
340              
341             =item B<--help>
342              
343             Print a brief help message and exits.
344              
345             =item B<--man>
346              
347             Prints the manual page and exits.
348              
349             =back
350              
351             =head1 EXAMPLES
352              
353             =over 8
354              
355             =item B<storage2dot -o state.dot>
356              
357             Generate a DOT file representing the state of the storage on the local machine.
358              
359             =item B<storage2dot -r host -o state.dot>
360              
361             Generate a DOT file representing the state of the storage on the
362             remote B<host> machine. Only perl (and its standard modules) are
363             required on the remote machine. Of course, a SSH account is also
364             required.
365              
366             =item B<storage2dot -c -o state.data>
367              
368             Just collect data on current machine without generating a DOT file.
369             Only perl (and its standard modules) are required on the current
370             machine.
371              
372             =item B<storage2dot --data state.data -o state.dot>
373              
374             Generate a DOT file representing the state of the storage recorded in
375             the state.data file. Extra perl modules are required for this command.
376              
377             =item B<dot -Tpdf state.dot >>B< state.pdf>
378              
379             Generate a PDF from the DOT file using dot(1).
380              
381             =back
382              
383             =head1 AUTHOR
384              
385             Vincent Danjean <Vincent.Danjean@ens-lyon.org>
386              
387             =head1 COPYRIGHT AND LICENSE
388              
389             This software is copyright (c) 2014-2023 by Vincent Danjean.
390              
391             This is free software; you can redistribute it and/or modify it under
392             the same terms as the Perl 5 programming language system itself.
393              
394             =cut