1# 2# Copyright (c) 2001-2010 Willem Dijkstra 3# All rights reserved. 4# 5# Redistribution and use in source and binary forms, with or without 6# modification, are permitted provided that the following conditions 7# are met: 8# 9# - Redistributions of source code must retain the above copyright 10# notice, this list of conditions and the following disclaimer. 11# - Redistributions in binary form must reproduce the above 12# copyright notice, this list of conditions and the following 13# disclaimer in the documentation and/or other materials provided 14# with the distribution. 15# 16# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 19# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 20# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 21# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 22# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 26# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27# POSSIBILITY OF SUCH DAMAGE. 28# 29 30package SymuxClient; 31 32use Carp; 33use IO::Socket; 34 35my $streamitem = 36 {cpu => {user => 1, nice => 2, system => 3, interrupt => 4, idle => 5}, 37 cpuiow => {user => 1, nice => 2, system => 3, interrupt => 4, idle => 5, iowait => 6}, 38 mem => {real_active => 1, real_total => 2, free => 3, swap_used => 4, 39 swap_total =>5}, 40 if => {packets_in => 1, packets_out => 2, bytes_in => 3, bytes_out => 4, 41 multicasts_in => 5, multicasts_out => 6, errors_in => 7, 42 errors_out => 8, collisions => 9, drops => 10}, 43 io1 => {total_transfers => 1, total_seeks => 2, total_bytes => 3}, 44 pf => {bytes_v4_in => 1, bytes_v4_out => 2, bytes_v6_in => 3, 45 bytes_v6_out => 4, packets_v4_in_pass => 5, 46 packets_v4_in_drop => 6, packets_v4_out_pass => 7, 47 packets_v4_out_drop => 8, packets_v6_in_pass => 9, 48 packets_v6_in_drop => 10, packets_v6_out_pass => 11, 49 packets_v6_out_drop => 12, states_entries => 13, 50 states_searches => 14, states_inserts => 15, 51 states_removals => 16, counters_match => 17, 52 counters_badoffset => 18, counters_fragment => 19, 53 counters_short => 20, counters_normalize => 21, 54 counters_memory => 22}, 55 debug => {debug0 => 1, debug1 => 2, debug3 => 3, debug4 => 4, debug5 => 5, 56 debug6 => 6, debug7 => 7, debug8 => 8, debug9 => 9, 57 debug10 => 10, debug11 => 11, debug12 => 12, debug13 => 13, 58 debug14 => 14, debug15 => 15, debug16 => 16, debug17 => 17, 59 debug18 => 18, debug19 => 19}, 60 proc => {number => 1, uticks => 2, sticks => 3, iticks => 4, cpusec => 5, 61 cpupct => 6, procsz => 7, rsssz => 8}, 62 mbuf => {totmbufs => 1, mt_data => 2, mt_oobdata => 3, mt_control => 4, 63 mt_header => 5, mt_ftable => 6, mt_soname => 7, mt_soopts => 8, 64 pgused => 9, pgtotal => 10, totmem => 11, totpct => 12, 65 m_drops => 13, m_wait => 14, m_drain => 15 }, 66 sensor => {value => 1}, 67 io => {total_rxfers => 1, total_wxfers => 2, total_seeks => 3, 68 total_rbytes => 4, total_wbytes => 5 }, 69 pfq => {sent_bytes => 1, sent_packets => 2, drop_bytes => 3, 70 drop_packets => 4}, 71 df => {blocks => 1, bfree => 2, bavail => 3, files => 4, ffree => 5, 72 syncwrites => 6, asyncwrites => 7}, 73 smart => {read_error_rate => 1, reallocated_sectors => 2, spin_retries => 3, 74 air_flow_temp => 4, temperature => 5, reallocations => 6, 75 current_pending => 7, uncorrectables => 8, 76 soft_read_error_rate => 9, g_sense_error_rate => 10, 77 temperature2 => 10, free_fall_protection => 11}, 78 load => {load1 => 1, load5 => 2, load15 => 3} 79}; 80 81sub new { 82 my ($class, %arg) = @_; 83 my $self; 84 85 (defined $arg{host} && defined $arg{port}) or 86 croak "error: need a host and a port to connect to."; 87 88 ($self->{host}, $self->{port}) = ($arg{host}, $arg{port}); 89 90 $self->{retry} = (defined $arg{retry}? $arg{retry} : 10); 91 92 bless($self, $class); 93 94 $self->connect(); 95 96 return $self; 97} 98 99sub DESTROY { 100 my $self = shift; 101 102 if (defined $self->{sock}) { 103 close($self->{sock}); 104 } 105} 106 107sub connect { 108 my $self = shift; 109 110 if (defined $self->{sock} && $self->{sock}->connected() ne '') { 111 return; 112 } else { 113 close($self->{sock}); 114 } 115 116 $self->{sock} = new 117 IO::Socket::INET(PeerAddr => $self->{host}, 118 PeerPort => $self->{port}, 119 Proto => "tcp", 120 Type => SOCK_STREAM) 121 or croak "error: could not connect to $self->{host}:$self->{port}"; 122} 123 124sub getdata { 125 my $self = shift; 126 my $sock; 127 my $data; 128 my $tries; 129 130 $tries = 0; 131 132 while (1) { 133 $self->connect(); 134 $sock = $self->{sock}; 135 $data = <$sock>; 136 if ((index($data, "\012") != -1) && (index($data, ';') != -1)) { 137 $self->{rawdata} = $data; 138 return $data; 139 } else { 140 croak "error: tried to get data $tries times and failed" 141 if (++$tries == $self->{retry}); 142 } 143 } 144} 145 146sub parse { 147 my $self = shift; 148 my ($stream, @streams, $name, $arg, @data, $number); 149 150 $self->getdata() if ! defined $self->{rawdata}; 151 152 $number = 0; 153 undef $self->{data}; 154 undef $self->{datasource}; 155 chop $self->{rawdata}; # remove ';\n' 156 chop $self->{rawdata}; 157 158 @streams = split(/;/, $self->{rawdata}); 159 croak "error: expected a symux dataline with ';' delimited streams" 160 if ($#streams < 1); 161 162 $self->{datasource} = shift @streams; 163 164 foreach $stream (@streams) { 165 ($name, $arg, @data) = split(':', $stream); 166 167 croak "error: expected a symux stream with ':' delimited values" 168 if ($#data < 1); 169 170 $name .= '('.$arg.')' if ($arg ne ''); 171 172 @{$self->{data}{$name}} = @data; 173 $number++; 174 } 175 176 $self->{rawdata} = ''; 177 return $number; 178} 179 180sub getcacheditem { 181 my ($self, $host, $streamname, $item) = @_; 182 my ($streamtype, @addr, $element); 183 184 return undef if not defined $self->{datasource}; 185 return undef if (($host ne $self->{datasource}) && 186 ($host ne "*")); 187 188 croak "error: source $host does not contain stream $streamname" 189 if not defined $self->{data}{$streamname}; 190 191 ($streamtype = $streamname) =~ s/^([a-z]+).*/\1/; 192 193 if ($item eq 'timestamp') { 194 $element = 0; 195 } elsif (not defined $$streamitem{$streamtype}{$item}) { 196 croak "error: unknown stream item '$item' - check symux(8) for names"; 197 } else { 198 $element = $$streamitem{$streamtype}{$item}; 199 } 200 201 return $self->{data}{$streamname}[$element]; 202} 203 204sub getitem { 205 my ($self, $host, $streamname, $item) = @_; 206 my $data; 207 my %hosts = (); 208 209 undef $data; 210 while (! defined $data) { 211 $self->getdata(); 212 $self->parse(); 213 $hosts{$self->{datasource}} += 1; 214 if ($hosts{$self->{datasource}} > $self->{retry}) { 215 croak "error: seen a lot of data ($tries strings), but none that matches $host"; 216 } 217 $data = $self->getcacheditem($host, $streamname, $item); 218 return $data if defined $data; 219 $tries++; 220 } 221} 222 223sub source { 224 my $self = shift; 225 226 return $self->{datasource}; 227} 2281; 229 230__END__ 231 232=head1 NAME 233 234SymuxClient - Object client interface for symux 235 236=head1 SYNOPSIS 237 238use SymuxClient; 239 240=head1 DESCRIPTION 241 242C<SymuxClient> provides an object client interface for listening to, and 243parsing of, symux data. 244 245=head1 CONSTRUCTOR 246 247=over 4 248 249=item new ( ARGS ) 250 251Creates a new C<SymuxClient> object that holds both the connection to a symux 252and data it receives from that connection. Arguments are: 253 254 host dotted quad ipv4 hostaddress 255 port tcp port that symux is on 256 retry* maximum number of retries; used to limit number 257 of connection attempts and number of successive 258 read attempts 259 260Arguments marked with * are optional. 261 262Example: 263 $sc = new SymuxClient(host => '127.0.0.1', 264 port => 2100); 265 266=back 267 268=head2 METHODS 269 270=over 4 271 272=item getitem (host, stream, item) 273 274=back 275 276Refresh the measured data and get an item from a stream for a particular 277host. Note that successive calls for this function deal with successive 278measurements of B<symon>. Set C<host> to '*' if data about any host is of 279interest. Any errors are sent out on STDOUT prepended with 'error: '. 280 281=over 4 282 283=item getcacheditem (host, stream, item) 284 285=back 286 287Get an item from a stream for a particular host. Returns C<undef> if no data is 288cached, or if the data cached does not match the B<host>. Can be called 289multiple times to obtain items from the same measurement. Set C<host> to '*' if 290data about any host is of interest. Any errors are sent out on STDOUT prepended 291with 'error: '. 292 293=over 4 294 295=item getsource () 296 297=back 298 299Get the symon source host of the currently cached information. Usefull to see 300what host's data getcacheditem is working on. 301 302Example: 303 $sc = new SymuxClient(host => 127.0.0.1, 304 port => 2100); 305 306 print $sc->getitem("127.0.0.1", "if(de0)", 307 "packets_out"); 308 309 # get more data from that measurement 310 print $sc->getcacheditem("127.0.0.1","if(de0)", 311 "packets_in"); 312 313 # start a new measurement 314 print $sc->getitem("*", "if(de0)", 315 "packets_out"); 316 # which hosts packets_out was that? 317 print $sc->getsource(); 318 319=cut 320