ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-SNMP/SNMP.pm
Revision: 1.4
Committed: Sun Apr 19 11:06:21 2009 UTC (15 years, 1 month ago) by root
Branch: MAIN
Changes since 1.3: +5 -0 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 Use this package variable to restrict the number of outstanding SNMP
55 requests at any point in time.
56
57 Net::SNMP is very fast at creating and sending SNMP requests, but much
58 slower at parsing (big, bulk) responses. This makes it easy to request a
59 lot of data that can take many seconds to parse.
60
61 In the best case, this can lead to unnecessary delays (and even time-outs,
62 as the data has been received but not yet processed) and in the worst
63 case, this can lead to packet loss, when the receive queue overflows and
64 the kernel can no longer accept new packets.
65
66 To avoid this, you can (and should) limit the number of outstanding requests
67 to a number low enough so that parsing time doesn't introduce noticable delays.
68
69 Unfortunately, this number depends not only on processing speed and load
70 of the machine running Net::SNMP, but also on the network latency and the
71 speed of your SNMP agents.
72
73 AnyEvent::SNMP tries to dynamically adjust this number dynamically upwards
74 and downwards.
75
76 Note that you can use L<Net::SNMP::XS> to speed up parsing of responses
77 considerably.
78
79 =item $AnyEvent::SNMP::MIN_RECVQUEUE (default: C<4>)
80
81 =item $AnyEvent::SNMP::MAX_RECVQUEUE (default: C<64>)
82
83 These values specify the minimum and maximum receive queue length (in
84 units of one response packet).
85
86 When AnyEvent::SNMP handles $MAX_RECVQUEUE or more packets per iteration
87 it will reduce $MAX_OUTSTANDING. If it handles less than $MIN_RECVQUEUE,
88 it increases $MAX_OUTSTANDING.
89
90 This has the result of adjusting the number of outstanding requests so that
91 the recv queue is between the minimum and maximu, usually.
92
93 This algorithm works reasonably well as long as the responses, response
94 latencies and processing times are the same size per packet on average.
95
96 =back
97
98 =head1 COMPATIBILITY
99
100 This module may be used as a drop in replacement for the
101 Net::SNMP::Dispatcher in existing programs. You can still call
102 C<snmp_dispatcher> to start the event-loop, but then you loose the benefit
103 of mixing Net::SNMP events with other events.
104
105 use AnyEvent::SNMP;
106 use Net::SNMP;
107
108 # just use Net::SNMP as before
109
110 # ... start non-blocking snmp request(s)...
111 Net::SNMP->session (
112 -hostname => "127.0.0.1",
113 -community => "public",
114 -nonblocking => 1,
115 )->get_request (-callback => sub { ... });
116
117 snmp_dispatcher;
118
119 =cut
120
121 package AnyEvent::SNMP;
122
123 no warnings;
124 use strict qw(subs vars);
125
126 # it is possible to do this without loading
127 # Net::SNMP::Dispatcher, but much more awkward.
128 use Net::SNMP::Dispatcher;
129
130 sub Net::SNMP::Dispatcher::instance {
131 AnyEvent::SNMP::
132 }
133
134 use Net::SNMP ();
135 use AnyEvent ();
136
137 our $VERSION = '0.2';
138
139 $Net::SNMP::DISPATCHER = instance Net::SNMP::Dispatcher;
140
141 our $MESSAGE_PROCESSING = $Net::SNMP::Dispatcher::MESSAGE_PROCESSING;
142
143 # avoid the method call
144 my $timer = sub { shift->timer (@_) };
145 AnyEvent::post_detect { $timer = AnyEvent->can ("timer") };
146
147 our $BUSY;
148 our %TRANSPORT; # address => [count, watcher]
149 our @QUEUE;
150 our $MAX_OUTSTANDING = 50;
151 our $MIN_RECVQUEUE = 4;
152 our $MAX_RECVQUEUE = 64;
153
154 sub kick_job;
155
156 sub _send_pdu {
157 my ($pdu, $retries) = @_;
158
159 # mostly copied from Net::SNMP::Dispatch
160
161 # Pass the PDU to Message Processing so that it can
162 # create the new outgoing message.
163 my $msg = $MESSAGE_PROCESSING->prepare_outgoing_msg ($pdu);
164
165 if (!defined $msg) {
166 --$BUSY;
167 kick_job;
168 # Inform the command generator about the Message Processing error.
169 $pdu->status_information ($MESSAGE_PROCESSING->error);
170 return;
171 }
172
173 # Actually send the message.
174 if (!defined $msg->send) {
175 $MESSAGE_PROCESSING->msg_handle_delete ($pdu->msg_id)
176 if $pdu->expect_response;
177
178 # A crude attempt to recover from temporary failures.
179 if ($retries-- > 0 && ($!{EAGAIN} || $!{EWOULDBLOCK} || $!{ENOSPC})) {
180 my $retry_w; $retry_w = AnyEvent->$timer (after => $pdu->timeout, cb => sub {
181 undef $retry_w;
182 _send_pdu ($pdu, $retries);
183 });
184 } else {
185 --$BUSY;
186 kick_job;
187 }
188
189 # Inform the command generator about the send() error.
190 $pdu->status_information ($msg->error);
191 return;
192 }
193
194 # Schedule the timeout handler if the message expects a response.
195 if ($pdu->expect_response) {
196 my $transport = $msg->transport;
197
198 # register the transport
199 unless ($TRANSPORT{$transport+0}[0]++) {
200 $TRANSPORT{$transport+0}[1] = AnyEvent->io (fh => $transport->socket, poll => 'r', cb => sub {
201 for my $count (1..$MAX_RECVQUEUE) { # handle up to this many requests in one go
202 # Create a new Message object to receive the response
203 my ($msg, $error) = Net::SNMP::Message->new (-transport => $transport);
204
205 if (!defined $msg) {
206 die sprintf 'Failed to create Message object [%s]', $error;
207 }
208
209 # Read the message from the Transport Layer
210 if (!defined $msg->recv) {
211 if ($transport->connectionless) {
212 # if we handled very few replies and we have queued work, try
213 # to increase the parallelity as we probably can handle more.
214 if ($count < $MIN_RECVQUEUE && @QUEUE) {
215 ++$MAX_OUTSTANDING;
216 kick_job;
217 }
218 } else {
219 # for some reason, connected-oriented transports seem to need this
220 delete $TRANSPORT{$transport+0}
221 unless --$TRANSPORT{$transport+0}[0];
222 }
223
224 $msg->error;
225 return;
226 }
227
228 # For connection-oriented Transport Domains, it is possible to
229 # "recv" an empty buffer if reassembly is required.
230 if (!$msg->length) {
231 return;
232 }
233
234 # Hand the message over to Message Processing.
235 if (!defined $MESSAGE_PROCESSING->prepare_data_elements ($msg)) {
236 $MESSAGE_PROCESSING->error;
237 return;
238 }
239
240 # Set the error if applicable.
241 $msg->error ($MESSAGE_PROCESSING->error) if $MESSAGE_PROCESSING->error;
242
243 # Notify the command generator to process the response.
244 $msg->process_response_pdu;
245
246 # Cancel the timeout.
247 my $rtimeout_w = $msg->timeout_id;
248 if ($$rtimeout_w) {
249 undef $$rtimeout_w;
250
251 --$BUSY;
252 kick_job;
253
254 unless (--$TRANSPORT{$transport+0}[0]) {
255 delete $TRANSPORT{$transport+0};
256 return;
257 }
258 }
259 }
260
261 # when we end up here, we successfully handled $MAX_RECVQUEUE
262 # replies in one iteration, so assume we are overloaded
263 # and reduce the amount of parallelity.
264 $MAX_OUTSTANDING = (int $MAX_OUTSTANDING * 0.9) || 1;
265 });
266 }
267
268 $msg->timeout_id (\(my $rtimeout_w =
269 AnyEvent->$timer (after => $pdu->timeout, cb => sub {
270 my $rtimeout_w = $msg->timeout_id;
271 if ($$rtimeout_w) {
272 undef $$rtimeout_w;
273 delete $TRANSPORT{$transport+0}
274 unless --$TRANSPORT{$transport+0}[0];
275 }
276
277 if ($retries--) {
278 _send_pdu ($pdu, $retries);
279 } else {
280 $MESSAGE_PROCESSING->msg_handle_delete ($pdu->msg_id);
281 $pdu->status_information ("No response from remote host '%s'", $pdu->hostname);
282
283 --$BUSY;
284 kick_job;
285 }
286 })
287 ));
288 } else {
289 --$BUSY;
290 kick_job;
291 }
292 }
293
294 sub kick_job {
295 while ($BUSY < $MAX_OUTSTANDING) {
296 my $pdu = shift @QUEUE
297 or last;
298
299 ++$BUSY;
300
301 _send_pdu $pdu, $pdu->retries;
302 }
303 }
304 sub send_pdu($$$) {
305 my (undef, $pdu, $delay) = @_;
306
307 # $delay is not very sensibly implemented by AnyEvent::SNMP,
308 # but apparently it is not a very sensible feature.
309 if ($delay > 0) {
310 ++$BUSY;
311 my $delay_w; $delay_w = AnyEvent->$timer (after => $delay, cb => sub {
312 undef $delay_w;
313 --$BUSY;
314 push @QUEUE, $pdu;
315 kick_job;
316 });
317 return 1;
318 }
319
320 push @QUEUE, $pdu;
321 kick_job;
322
323 1
324 }
325
326 sub activate($) {
327 AnyEvent->one_event while $BUSY;
328 }
329
330 sub one_event($) {
331 AnyEvent->one_event;
332 }
333
334 =head1 SEE ALSO
335
336 L<AnyEvent>, L<Net::SNMP>, L<Net::SNMP::XS>, L<Net::SNMP::EV>.
337
338 =head1 AUTHOR
339
340 Marc Lehmann <schmorp@schmorp.de>
341 http://home.schmorp.de/
342
343 =cut
344
345 1
346