File Coverage

blib/lib/AnyEvent/SNMP.pm
Criterion Covered Total %
statement 13 102 12.7
branch 0 44 0.0
condition 0 14 0.0
subroutine 5 15 33.3
pod 1 6 16.6
total 19 181 10.5


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             AnyEvent::SNMP - adaptor to integrate Net::SNMP into AnyEvent.
4              
5             =head1 SYNOPSIS
6              
7             use AnyEvent::SNMP;
8             use Net::SNMP;
9              
10             # just use Net::SNMP and AnyEvent as you like:
11              
12             # use a condvar to transfer results, this is
13             # just an example, you can use a naked callback as well.
14             my $cv = AnyEvent->condvar;
15              
16             # ... start non-blocking snmp request(s)...
17             Net::SNMP->session (-hostname => "127.0.0.1",
18             -community => "public",
19             -nonblocking => 1)
20             ->get_request (-callback => sub { $cv->send (@_) });
21              
22             # ... do something else until the result is required
23             my @result = $cv->wait;
24              
25             =head1 DESCRIPTION
26              
27             This module implements an alternative "event dispatcher" for Net::SNMP,
28             using AnyEvent as a backend. This integrates Net::SNMP into AnyEvent. That
29             means you can make non-blocking Net::SNMP calls and as long as other
30             parts of your program also use AnyEvent (or some event loop supported by
31             AnyEvent), they will run in parallel.
32              
33             Also, the Net::SNMP scheduler is very inefficient with respect to both CPU
34             and memory usage. Most AnyEvent backends (including the pure-perl backend)
35             fare much better than the Net::SNMP dispatcher.
36              
37             Another major added feature of this module over Net::SNMP is automatic
38             rate-adjustments: Net::SNMP is so slow that firing a few thousand
39             requests can cause many timeouts simply because Net::SNMP cannot process
40             the replies in time. This module automatically adapts the send rate to
41             avoid false timeouts caused by slow reply processing.
42              
43             A potential disadvantage of this module is that replacing the dispatcher
44             is not at all a documented thing to do, so future changes in Net::SNMP
45             might break this module (or the many similar ones).
46              
47             This module does not export anything and does not require you to do
48             anything special apart from loading it I
49             requests with Net::SNMP>. It is recommended but not required to load this
50             module before C.
51              
52             =head1 GLOBAL VARIABLES
53              
54             =over 4
55              
56             =item $AnyEvent::SNMP::MAX_OUTSTANDING (default: C<50>, dynamic)
57              
58             =item AnyEvent::SNMP::set_max_outstanding $new_value
59              
60             Use this package variable to restrict the number of outstanding SNMP
61             requests at any point in time.
62              
63             Net::SNMP is very fast at creating and sending SNMP requests, but much
64             slower at parsing (big, bulk) responses. This makes it easy to request a
65             lot of data that can take many seconds to parse.
66              
67             In the best case, this can lead to unnecessary delays (and even time-outs,
68             as the data has been received but not yet processed) and in the worst
69             case, this can lead to packet loss, when the receive queue overflows and
70             the kernel can no longer accept new packets.
71              
72             To avoid this, you can (and should) limit the number of outstanding
73             requests to a number low enough so that parsing time doesn't introduce
74             noticeable delays.
75              
76             Unfortunately, this number depends not only on processing speed and load
77             of the machine running Net::SNMP, but also on the network latency and the
78             speed of your SNMP agents.
79              
80             AnyEvent::SNMP tries to dynamically adjust this number upwards and
81             downwards.
82              
83             Increasing C<$MAX_OUTSTANDING> will not automatically use the
84             extra request slots. To increase C<$MAX_OUTSTANDING> and make
85             C make use of the extra parallelity, call
86             C with the new value, e.g.:
87              
88             AnyEvent::SNMP::set_max_outstanding 500;
89              
90             Although due to the dynamic adjustment, this might have little lasting
91             effect.
92              
93             Note that you can use L to speed up parsing of responses
94             considerably.
95              
96             =item $AnyEvent::SNMP::MIN_RECVQUEUE (default: C<8>)
97              
98             =item $AnyEvent::SNMP::MAX_RECVQUEUE (default: C<64>)
99              
100             These values specify the minimum and maximum receive queue length (in
101             units of one response packet).
102              
103             When AnyEvent::SNMP handles $MAX_RECVQUEUE or more packets per iteration
104             it will reduce $MAX_OUTSTANDING. If it handles less than $MIN_RECVQUEUE,
105             it increases $MAX_OUTSTANDING.
106              
107             This has the result of adjusting the number of outstanding requests so that
108             the recv queue is between the minimum and maximum, usually.
109              
110             This algorithm works reasonably well as long as the responses, response
111             latencies and processing times are the same per packet on average.
112              
113             =back
114              
115             =head1 COMPATIBILITY
116              
117             This module may be used as a drop in replacement for the
118             Net::SNMP::Dispatcher in existing programs. You can still call
119             C to start the event-loop, but then you loose the benefit
120             of mixing Net::SNMP events with other events.
121              
122             use AnyEvent::SNMP;
123             use Net::SNMP;
124              
125             # just use Net::SNMP as before
126              
127             # ... start non-blocking snmp request(s)...
128             Net::SNMP->session (
129             -hostname => "127.0.0.1",
130             -community => "public",
131             -nonblocking => 1,
132             )->get_request (-callback => sub { ... });
133              
134             snmp_dispatcher;
135              
136             =cut
137              
138             package AnyEvent::SNMP;
139              
140 1     1   855 use common::sense;
  1         10  
  1         5  
141              
142             # it is possible to do this without loading
143             # Net::SNMP::Dispatcher, but much more awkward.
144 1     1   468 use Net::SNMP::Dispatcher;
  1         52301  
  1         66  
145              
146             # we could inherit fro Net:SNMP::Dispatcher, but since this is undocumented,
147             # I'd rather see it die (and reported) than silenty and subtly fail.
148             *msg_handle_alloc = \&Net::SNMP::Dispatcher::msg_handle_alloc;
149              
150             sub Net::SNMP::Dispatcher::instance {
151 2     2 0 2596 AnyEvent::SNMP::
152             }
153              
154 1     1   709 use Net::SNMP ();
  1         7942  
  1         21  
155 1     1   840 use AnyEvent ();
  1         4335  
  1         1387  
156              
157             our $VERSION = '6.02';
158              
159             $Net::SNMP::DISPATCHER = instance Net::SNMP::Dispatcher;
160              
161             our $MESSAGE_PROCESSING = $Net::SNMP::Dispatcher::MESSAGE_PROCESSING;
162              
163             our $BUSY;
164             our $DONE; # finished all jobs
165             our @TRANSPORT; # fileno => [count, watcher]
166             our @QUEUE;
167             our $MAX_OUTSTANDING = 50;
168             our $MIN_RECVQUEUE = 8;
169             our $MAX_RECVQUEUE = 64;
170              
171             sub kick_job;
172              
173             sub _send_pdu {
174 0     0     my ($pdu, $retries) = @_;
175              
176             # mostly copied from Net::SNMP::Dispatch
177              
178             # Pass the PDU to Message Processing so that it can
179             # create the new outgoing message.
180 0           my $msg = $MESSAGE_PROCESSING->prepare_outgoing_msg ($pdu);
181              
182 0 0         if (!defined $msg) {
183 0           --$BUSY;
184 0           kick_job;
185             # Inform the command generator about the Message Processing error.
186 0           $pdu->status_information ($MESSAGE_PROCESSING->error);
187 0           return;
188             }
189              
190             # Actually send the message.
191 0 0         if (!defined $msg->send) {
192 0 0         $MESSAGE_PROCESSING->msg_handle_delete ($pdu->msg_id)
193             if $pdu->expect_response;
194              
195             # A crude attempt to recover from temporary failures.
196 0 0 0       if ($retries-- > 0 && ($!{EAGAIN} || $!{EWOULDBLOCK} || $!{ENOSPC})) {
      0        
197 0           my $retry_w; $retry_w = AE::timer $pdu->timeout, 0, sub {
198 0     0     undef $retry_w;
199 0           _send_pdu ($pdu, $retries);
200 0           };
201             } else {
202 0           --$BUSY;
203 0           kick_job;
204             }
205              
206             # Inform the command generator about the send() error.
207 0           $pdu->status_information ($msg->error);
208 0           return;
209             }
210              
211             # Schedule the timeout handler if the message expects a response.
212 0 0         if ($pdu->expect_response) {
213 0           my $transport = $msg->transport;
214 0           my $fileno = $transport->fileno;
215              
216             # register the transport
217 0 0         unless ($TRANSPORT[$fileno][0]++) {
218             $TRANSPORT[$fileno][1] = AE::io $transport->socket, 0, sub {
219 0     0     for my $count (1..$MAX_RECVQUEUE) { # handle up to this many requests in one go
220             # Create a new Message object to receive the response
221 0           my ($msg, $error) = Net::SNMP::Message->new (-transport => $transport);
222              
223 0 0         if (!defined $msg) {
224 0           die sprintf 'Failed to create Message object [%s]', $error;
225             }
226              
227             # Read the message from the Transport Layer
228 0 0         if (!defined $msg->recv) {
229 0 0         if ($transport->connectionless) {
230             # if we handled very few replies and we have queued work, try
231             # to increase the parallelity as we probably can handle more.
232 0 0 0       if ($count < $MIN_RECVQUEUE && @QUEUE) {
233 0           ++$MAX_OUTSTANDING;
234 0           kick_job;
235             }
236             } else {
237             # for some reason, connected-oriented transports seem to need this
238 0 0         delete $TRANSPORT[$fileno]
239             unless --$TRANSPORT[$fileno][0];
240             }
241              
242 0           $msg->error;
243 0           return;
244             }
245              
246             # For connection-oriented Transport Domains, it is possible to
247             # "recv" an empty buffer if reassembly is required.
248 0 0         if (!$msg->length) {
249 0           return;
250             }
251              
252             # Hand the message over to Message Processing.
253 0 0         if (!defined $MESSAGE_PROCESSING->prepare_data_elements ($msg)) {
254 0           $MESSAGE_PROCESSING->error;
255 0           return;
256             }
257              
258             # Set the error if applicable.
259 0 0         $msg->error ($MESSAGE_PROCESSING->error) if $MESSAGE_PROCESSING->error;
260              
261             # Notify the command generator to process the response.
262             # Net::SNMP calls process_response_pdu, which simply calls callback_execute,
263             # but some errors cause $msg to be of type Net::SNMP::Message, not Net::SMMP::PDU,
264             # so we call the underlying callback_execute method which exists on both and
265             # seems to do the right thing.
266 0           $msg->callback_execute;
267              
268             # Cancel the timeout.
269 0           my $rtimeout_w = $msg->timeout_id;
270 0 0         if ($$rtimeout_w) {
271 0           undef $$rtimeout_w;
272              
273 0           --$BUSY;
274 0           kick_job;
275              
276 0 0         unless (--$TRANSPORT[$fileno][0]) {
277 0           delete $TRANSPORT[$fileno];
278 0           return;
279             }
280             }
281             }
282              
283             # when we end up here, we successfully handled $MAX_RECVQUEUE
284             # replies in one iteration, so assume we are overloaded
285             # and reduce the amount of parallelity.
286 0   0       $MAX_OUTSTANDING = (int $MAX_OUTSTANDING * 0.95) || 1;
287 0           };
288             }
289              
290             $msg->timeout_id (\(my $rtimeout_w =
291             AE::timer $pdu->timeout, 0, sub {
292 0     0     my $rtimeout_w = $msg->timeout_id;
293 0 0         if ($$rtimeout_w) {
294 0           undef $$rtimeout_w;
295 0 0         delete $TRANSPORT[$fileno]
296             unless --$TRANSPORT[$fileno][0];
297             }
298              
299 0 0         if ($retries--) {
300 0           _send_pdu ($pdu, $retries);
301             } else {
302 0           $MESSAGE_PROCESSING->msg_handle_delete ($pdu->msg_id);
303 0           $pdu->status_information ("No response from remote host '%s'", $pdu->hostname);
304              
305 0           --$BUSY;
306 0           kick_job;
307             }
308             })
309 0           );
310             } else {
311 0           --$BUSY;
312 0           kick_job;
313             }
314             }
315              
316             sub kick_job {
317 0     0 0   while ($BUSY < $MAX_OUTSTANDING) {
318 0 0         my $pdu = shift @QUEUE
319             or last;
320              
321 0           ++$BUSY;
322 0           _send_pdu $pdu, $pdu->retries;
323             }
324              
325 0 0 0       $DONE and $DONE->() unless $BUSY;
326             }
327              
328             sub send_pdu($$$) {
329 0     0 0   my (undef, $pdu, $delay) = @_;
330              
331             # $delay is not very sensibly implemented by AnyEvent::SNMP,
332             # but apparently it is not a very sensible feature.
333 0 0         if ($delay > 0) {
334 0           ++$BUSY;
335 0           my $delay_w; $delay_w = AE::timer $delay, 0, sub {
336 0     0     undef $delay_w;
337 0           push @QUEUE, $pdu;
338 0           --$BUSY;
339 0           kick_job;
340 0           };
341 0           return 1;
342             }
343              
344 0           push @QUEUE, $pdu;
345 0           kick_job;
346              
347 0           1
348             }
349              
350             sub loop($) {
351 0     0 0   while ($BUSY) {
352 0           $DONE = AE::cv;
353 0           $DONE->recv;
354 0           undef $DONE;
355             }
356             }
357              
358             *activate = \&loop; # 5.x compatibility?
359             *listen = \&loop; # 5.x compatibility?
360              
361             sub one_event($) {
362             # should not ever be used
363 0     0 0   AnyEvent->one_event; #d# todo
364             }
365              
366             sub set_max_outstanding($) {
367 0     0 1   $MAX_OUTSTANDING = $_[0];
368 0           kick_job;
369             }
370              
371             # not provided yet:
372             # schedule # apparently only used by Net::SNMP::Dispatcher itself
373             # register # apparently only used by Net::SNMP::Dispatcher itself
374             # deregister # apparently only used by Net::SNMP::Dispatcher itself
375             # cancel # apparently only used by Net::SNMP::Dispatcher itself
376             # return_response_pdu # apparently not used at all?
377             # error # only used by Net::SNMP::Dispatcher itself?
378             # debug # only used by Net::SNMP::Dispatcher itself?
379              
380             =head1 SEE ALSO
381              
382             L, L, L, L.
383              
384             =head1 AUTHOR
385              
386             Marc Lehmann
387             http://home.schmorp.de/
388              
389             =cut
390              
391             1
392