ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-SNMP/SNMP.pm
Revision: 1.3
Committed: Sat Apr 18 10:17:53 2009 UTC (15 years, 1 month ago) by root
Branch: MAIN
Changes since 1.2: +154 -45 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 ($count < $MIN_RECVQUEUE && @QUEUE) {
213 ++$MAX_OUTSTANDING;
214 kick_job;
215 }
216 } else {
217 # for some reason, connected-oriented transports seem to need this
218 delete $TRANSPORT{$transport+0}
219 unless --$TRANSPORT{$transport+0}[0];
220 }
221
222 $msg->error;
223 return;
224 }
225
226 # For connection-oriented Transport Domains, it is possible to
227 # "recv" an empty buffer if reassembly is required.
228 if (!$msg->length) {
229 return;
230 }
231
232 # Hand the message over to Message Processing.
233 if (!defined $MESSAGE_PROCESSING->prepare_data_elements ($msg)) {
234 $MESSAGE_PROCESSING->error;
235 return;
236 }
237
238 # Set the error if applicable.
239 $msg->error ($MESSAGE_PROCESSING->error) if $MESSAGE_PROCESSING->error;
240
241 # Notify the command generator to process the response.
242 $msg->process_response_pdu;
243
244 # Cancel the timeout.
245 my $rtimeout_w = $msg->timeout_id;
246 if ($$rtimeout_w) {
247 undef $$rtimeout_w;
248
249 --$BUSY;
250 kick_job;
251
252 unless (--$TRANSPORT{$transport+0}[0]) {
253 delete $TRANSPORT{$transport+0};
254 return;
255 }
256 }
257 }
258
259 $MAX_OUTSTANDING = (int $MAX_OUTSTANDING * 0.9) || 1;
260 });
261 }
262
263 $msg->timeout_id (\(my $rtimeout_w =
264 AnyEvent->$timer (after => $pdu->timeout, cb => sub {
265 my $rtimeout_w = $msg->timeout_id;
266 if ($$rtimeout_w) {
267 undef $$rtimeout_w;
268 delete $TRANSPORT{$transport+0}
269 unless --$TRANSPORT{$transport+0}[0];
270 }
271
272 if ($retries--) {
273 _send_pdu ($pdu, $retries);
274 } else {
275 $MESSAGE_PROCESSING->msg_handle_delete ($pdu->msg_id);
276 $pdu->status_information ("No response from remote host '%s'", $pdu->hostname);
277
278 --$BUSY;
279 kick_job;
280 }
281 })
282 ));
283 } else {
284 --$BUSY;
285 kick_job;
286 }
287 }
288
289 sub kick_job {
290 while ($BUSY < $MAX_OUTSTANDING) {
291 my $pdu = shift @QUEUE
292 or last;
293
294 ++$BUSY;
295
296 _send_pdu $pdu, $pdu->retries;
297 }
298 }
299 sub send_pdu($$$) {
300 my (undef, $pdu, $delay) = @_;
301
302 # $delay is not very sensibly implemented by AnyEvent::SNMP,
303 # but apparently it is not a very sensible feature.
304 if ($delay > 0) {
305 ++$BUSY;
306 my $delay_w; $delay_w = AnyEvent->$timer (after => $delay, cb => sub {
307 undef $delay_w;
308 --$BUSY;
309 push @QUEUE, $pdu;
310 kick_job;
311 });
312 return 1;
313 }
314
315 push @QUEUE, $pdu;
316 kick_job;
317
318 1
319 }
320
321 sub activate($) {
322 AnyEvent->one_event while $BUSY;
323 }
324
325 sub one_event($) {
326 AnyEvent->one_event;
327 }
328
329 =head1 SEE ALSO
330
331 L<AnyEvent>, L<Net::SNMP>, L<Net::SNMP::XS>, L<Net::SNMP::EV>.
332
333 =head1 AUTHOR
334
335 Marc Lehmann <schmorp@schmorp.de>
336 http://home.schmorp.de/
337
338 =cut
339
340 1
341