ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-SNMP/SNMP.pm
Revision: 1.9
Committed: Wed Jan 6 10:43:20 2010 UTC (14 years, 5 months ago) by root
Branch: MAIN
CVS Tags: rel-1_0
Changes since 1.8: +8 -6 lines
Log Message:
*** empty log message ***

File Contents

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