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