added one-liner for easy copy/paste
[gnt-info] / gnt-i
1 #!/usr/bin/perl
2 use warnings;
3 use strict;
4 use autodie;
5 use Storable;
6
7 my $DEBUG = $ENV{DEBUG} || 0;
8 my $INFLUX = 'http://10.60.0.89:8186/write';
9
10 my $hostname = `hostname -s`;
11 chomp $hostname;
12
13 use Data::Dumper;
14 sub XXX { $DEBUG ? warn "XXX ",Dumper( @_ ) : {} };
15
16 my $stat;
17 my $mac_to_name;
18 my $instance_tap;
19 my $tap_instance;
20 my $lines;
21
22 my @nodes = @ARGV;
23
24 @nodes = map { chomp ; s/\s+N$// ; $_ } grep { /N$/ } `gnt-node list -o name,offline --no-header` unless @nodes;
25
26 next_node:
27 my $ssh = '';
28 if ( @nodes ) {
29         $hostname = shift @nodes;
30         $ssh = "ssh $hostname ";
31         $hostname =~ s/\..+$//; # -s
32         warn "## hostname $hostname\n";
33 }
34
35 # if prefixed with _ it will be hiddden from output, _args must be last!
36 my @ps_cols = qw( user pid
37         pcpu pmem
38         vsz
39         rss
40         nlwp
41         cputime etimes 
42         maj_flt min_flt
43         psr
44
45         _args);
46
47 sub ps_cols_all { map { my $t = $_; $t =~ s/^_//; $t } @ps_cols };
48 sub ps_cols_visible { 'node', grep { ! /^_/ } @ps_cols, 'rx', 'tx' };
49
50 sub DD_hh_mm_ss {
51         my $t = shift;
52         # [[DD-]hh:]mm:ss.
53         my @f = reverse ( 24, 60, 60, 1 );
54         my @p = reverse split(/[-:]/, $t);
55         my $t_sec = 0;
56
57         for ( 0 .. $#p ) {
58                 my $i = $#p - $_;
59 #warn "### $i $p[$i] $f[$i]\n";
60                 $t_sec += $p[$i];
61                 $t_sec *= $f[$i];
62         }
63
64 #       warn "# DD-hh:mm:ss $t -> $t_sec\n" if $DEBUG;
65         return $t_sec;
66 }
67
68 sub sh {
69         my $cmd = join(' ', @_);
70         $cmd = "$ssh '$cmd'";
71         warn "## $cmd\n";
72         open(my $fh, '-|', $cmd);
73         return $fh;
74 }
75
76 my $ps = sh 'ps --no-headers axwwo ' . join(',', ps_cols_all);
77 while(<$ps>) {
78         chomp;
79         s/^\s*//;
80
81         my %h;
82         @h{@ps_cols} = split(/\s+/, $_, $#ps_cols + 1);
83         $h{cputime} = DD_hh_mm_ss( $h{cputime} );
84
85 #XXX 'h = ', \%h;
86         if ( $h{user} =~ m/gnt/ && $h{_args} =~ m/qemu.*-name\s+(\S+)/ ) {
87
88                 my $name = $1;
89                 $stat->{$name}->{$_} = $h{$_} foreach ps_cols_all;
90
91                 $stat->{$name}->{node} = $hostname;
92
93                 while ( $h{_args} =~ m/mac=([0-9a-fA-F:]+)/g ) {
94                         $mac_to_name->{$hostname}->{$1} = $name;
95                 }
96
97         } else {
98 #               warn "## SKIP [$_]\n";
99                 $stat->{ '__' . $hostname }->{$_} += $h{$_} foreach qw( pcpu pmem vsz rss
100                         nlwp cputime etimes
101                         maj_flt min_flt
102                 );
103                 
104         }
105
106 }
107
108
109 my $tap = sh('grep -H . /var/run/ganeti/kvm-hypervisor/nic/*/*');
110 while(<$tap>) {
111         chomp;
112         my @p = split(/\//,$_);
113 #       warn "## tap ", Dumper( \@p ), "$_\n";
114         push @{ $instance_tap->{$hostname}->{ $p[-2] } }, $p[-1];
115
116         my $if = $p[-1];
117         $if =~ s/\d://;
118         $tap_instance->{$hostname}->{$if} = $p[-2];
119 }
120
121 =for later
122
123 my $ip = sh('ip -s -o link');
124 while(<$ip>) {
125         chomp;
126         if ( m/master\s+(\S+).+ether\s+([0-9a-fA-F:]+).+RX:\s+.+\\\s+(\d+).+TX:\s+.+\\\s+(\d+)/ ) {
127                 my ( $if, $mac, $rx, $tx ) = ( $1, $2, $3, $4 );
128                 if ( my $name = $tap_instance->{$hostname}->{$if} ) {
129                         $stat->{$name}->{link}->{ $if } = [ $rx, $tx ];
130                         warn "## ip $name $ip $mac $rx $tx\n";
131                 } else {
132                         warn "## ip SKIP $if $mac $rx $tx\n"; # XXX if $DEBUG;
133                 }
134         } else {
135                 warn "## SKIP $_\n" if $DEBUG;
136         }
137 }
138
139 =cut
140
141 my $dev = sh 'grep : /proc/net/dev';
142 while(<$dev>) {
143         chomp;
144         s/^\s+//;
145         my @l = split(/[:\s]+/, $_);
146 #warn "XXX $_ -> ",Dumper( \@l );
147         if ( my $instance = $tap_instance->{$hostname}->{ $l[0] } ) {
148                         $stat->{$instance}->{rx} += $l[1];
149                         $stat->{$instance}->{tx} += $l[9];
150         } elsif ( $l[0] =~ m/eth/ ) { # connect hardware eth devices under node
151                         $stat->{ '__' . $hostname }->{rx} += $l[1];
152                         $stat->{ '__' . $hostname }->{tx} += $l[9];
153         }
154 }
155
156
157 goto next_node if @nodes;
158
159
160
161 # dump some useful data structures
162
163 sub tab_dump {
164         my ( $name, $hash ) = @_;
165         warn "# $name\n";
166         foreach my $key ( sort keys %$hash ) {
167                 warn $key, "\t", $hash->{$key}, "\n";
168         }
169 }
170
171
172
173 foreach my $node ( sort keys %$mac_to_name ) {
174         tab_dump "$node mac instance",  $mac_to_name->{$node};
175 }
176 #warn Dumper( $mac_to_name );
177 #tab_dump 'instance_tap', $instance_tap;
178 #warn Dumper( $instance_tap );
179 #warn Dumper( $tap_instance );
180
181
182 warn "# stat ", Dumper( $stat ) if $DEBUG;
183
184
185 # dump tablable ascii output
186
187 #XXX( @ps_cols );
188
189 sub push_line {
190         my @l = @_;
191         foreach my $i ( 0 .. $#l ) {
192                 my $len = length($l[$i]) || 0;
193                 $lines->{len}->[$i] ||= $len;
194                 $lines->{len}->[$i] = $len if $len > $lines->{len}->[$i];
195         }
196         push @{ $lines->{line} }, [ map { ! defined $_ ? '-' : $_ } @l ];
197 }
198
199 push_line '#name', ps_cols_visible;
200
201 foreach my $name ( sort keys %$stat ) {
202 #       printf "%6.2f %6.2f %8d %6d %6s %s\n", ( map { $stat->{$name}->{$_} || '' } qw( pcpu pmem vsz pid user ) ), $name;
203 #       print join("\t", $name, map { $stat->{$name}->{$_} } ps_cols_visible ), "\n";
204         push_line( $name, map { $stat->{$name}->{$_} } ps_cols_visible );
205 }
206
207 #XXX $lines;
208
209 open(my $influx_fh, '>', '/dev/shm/ganeti.influx');
210
211 my $last_lines;
212 $last_lines = retrieve('/dev/shm/ganeti.last.storable') if -e '/dev/shm/ganeti.last.storable';
213
214 my $fmt = join(' ', map { '%' . $_ . 's' } @{ $lines->{len} } ) . "\n";
215 warn "# fmt = [$fmt]" if $DEBUG;
216 foreach my $i ( 0 .. $#{ $lines->{line} } ) {
217         my $line = $lines->{line}->[$i];
218         printf $fmt, @$line;
219
220         next unless $last_lines;
221
222         my $last_line = $last_lines->[$i];
223         my @c = ( 'instance', ps_cols_visible );
224         my @influx;
225         foreach my $j ( 0 .. $#$line ) {
226                 #push @influx, $c[$j] . '=' . $line->[$j];
227                 push @influx, $c[$j] . '='
228                         . ( $j >= 9 && $j != 13 ? $line->[$j] - $last_line->[$j] : $line->[$j] )
229                         . ( $j == 3 || $j >= 6 && $j != 9 ? 'i' : '' );
230         }
231         my $influx = join(',', @influx);
232         $influx =~ s/,user=([^,]+)/ user="$1"/;
233         print $influx_fh "ganeti,$influx\n" unless $influx =~ m/(#|__)/;
234
235 }
236
237 store $lines->{line}, '/dev/shm/ganeti.last.storable';
238
239 #system "curl -i -XPOST 'http://127.0.0.1:8186/write?db=ganeti' --data-binary '@/dev/shm/ganeti.influx'";