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