bury all dead whitespace, better off to just do it in one command. i wonder why ss...
[torrus-plus.git] / src / lib / Torrus / Collector / SNMP.pm
blob5fb501ef39ade243cb30eb975e63fa217282ef3e
1 # Copyright (C) 2002-2011 Stanislav Sinyagin
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
17 # Stanislav Sinyagin <ssinyagin@yahoo.com>
19 # this policy complains about accessing private methods in Net::SNMP
20 ## no critic (Variables::ProtectPrivateVars)
22 # this policy complains about regexp result untested, although it is tested
23 ## no critic (RegularExpressions::ProhibitCaptureWithoutTest)
25 package Torrus::Collector::SNMP;
26 use strict;
27 use warnings;
29 use Torrus::Collector::SNMP_Params;
30 use Torrus::ConfigTree;
31 use Torrus::Log;
32 use Torrus::SNMP_Failures;
34 use Net::hostent;
35 use Socket;
36 use Net::SNMP qw(:snmp);
37 use Math::BigInt;
39 our $VERSION = 1.0;
41 # Register the collector type
42 $Torrus::Collector::collectorTypes{'snmp'} = 1;
45 # List of needed parameters and default values
47 $Torrus::Collector::params{'snmp'} = {
48 'snmp-ipversion' => undef,
49 'snmp-transport' => undef,
50 'snmp-version' => undef,
51 'snmp-port' => undef,
52 'snmp-community' => undef,
53 'snmp-username' => undef,
54 'snmp-authkey' => undef,
55 'snmp-authpassword' => undef,
56 'snmp-authprotocol' => 'md5',
57 'snmp-privkey' => undef,
58 'snmp-privpassword' => undef,
59 'snmp-privprotocol' => 'des',
60 'snmp-timeout' => undef,
61 'snmp-retries' => undef,
62 'domain-name' => undef,
63 'snmp-host' => undef,
64 'snmp-localaddr' => undef,
65 'snmp-localport' => undef,
66 'snmp-object' => undef,
67 'snmp-oids-per-pdu' => undef,
68 'snmp-object-type' => 'OTHER',
69 'snmp-check-sysuptime' => 'yes',
70 'snmp-max-msg-size' => undef,
71 'snmp-maxrepetitions' => 10,
72 'snmp-ignore-mib-errors' => undef,
75 my $sysUpTime = '1.3.6.1.2.1.1.3.0';
77 # Hosts that are running SNMPv1. We do not reresh maps on them, as
78 # they are too slow
79 my %snmpV1Hosts;
81 # SNMP tables lookup maps
82 my %maps;
84 # Old lookup maps, used temporarily during refresh cycle
85 my %oldMaps;
87 # How frequent we refresh the SNMP mapping
88 our $mapsRefreshPeriod;
90 # Random factor in refresh period
91 our $mapsRefreshRandom;
93 # Time period after configuration re-compile when we refresh existing mappings
94 our $mapsUpdateInterval;
96 # how often we check for expired maps
97 our $mapsExpireCheckPeriod;
99 # expiration time for each map
100 my %mapsExpire;
102 # Lookups scheduled for execution
103 my %mapLookupScheduled;
105 # SNMP session objects for map lookups
106 my @mappingSessions;
109 # Timestamps of hosts last found unreachable
110 my %hostUnreachableSeen;
112 # Last time we tried to reach an unreachable host
113 my %hostUnreachableRetry;
115 # Hosts that were deleted because of unreachability for too long
116 my %unreachableHostDeleted;
119 our $db_failures;
121 # Flush stats after a restart or recompile
122 $Torrus::Collector::initCollectorGlobals{'snmp'} =
123 \&Torrus::Collector::SNMP::initCollectorGlobals;
125 sub initCollectorGlobals
127 my $tree = shift;
128 my $instance = shift;
130 if( not defined( $db_failures ) )
132 $db_failures =
133 Torrus::SNMP_Failures->new( -Tree => $tree,
134 -Instance => $instance,
135 -WriteAccess => 1 );
138 if( defined( $db_failures ) )
140 $db_failures->init();
143 # re-init counters and collect garbage
144 %oldMaps = ();
145 %hostUnreachableSeen = ();
146 %hostUnreachableRetry = ();
147 %unreachableHostDeleted = ();
149 # Configuration re-compile was probably caused by new object instances
150 # appearing on the monitored devices. Here we force the maps to refresh
151 # soon enough in order to catch up with the changes
153 my $now = time();
154 for my $maphash ( keys %mapsExpire )
156 $mapsExpire{$maphash} = int( $now + rand( $mapsUpdateInterval ) );
158 return;
162 # This is first executed per target
164 $Torrus::Collector::initTarget{'snmp'} = \&Torrus::Collector::SNMP::initTarget;
168 sub initTarget
170 my $collector = shift;
171 my $token = shift;
173 my $tref = $collector->tokenData( $token );
175 $collector->registerDeleteCallback
176 ( $token, \&Torrus::Collector::SNMP::deleteTarget );
178 my $hosthash = getHostHash( $collector, $token );
179 if( not defined( $hosthash ) )
181 return 0;
184 $tref->{'hosthash'} = $hosthash;
186 return Torrus::Collector::SNMP::initTargetAttributes( $collector, $token );
190 sub initTargetAttributes
192 my $collector = shift;
193 my $token = shift;
195 &Torrus::DB::checkInterrupted();
197 my $tref = $collector->tokenData( $token );
198 my $cref = $collector->collectorData( 'snmp' );
200 my $hosthash = $tref->{'hosthash'};
202 my $version = $collector->param($token, 'snmp-version');
203 if( $version eq '1' )
205 $snmpV1Hosts{$hosthash} = 1;
208 # If the object is defined as a map, retrieve the whole map
209 # and cache it.
211 if( isHostDead( $collector, $hosthash ) )
213 return 0;
216 if( not checkUnreachableRetry( $collector, $hosthash ) )
218 $cref->{'needsRemapping'}{$token} = 1;
219 return 1;
222 my $oid = $collector->param($token, 'snmp-object');
223 $oid = expandOidMappings( $collector, $token, $hosthash, $oid );
225 if( not $oid )
227 if( $unreachableHostDeleted{$hosthash} )
229 # we tried our best, but the target is dead
230 return 0;
232 else
234 # we return OK status, to let the storage initiate
235 $cref->{'needsRemapping'}{$token} = 1;
236 return 1;
239 elsif( $oid eq 'notfound' )
241 return 0;
244 # Collector should be able to find the target
245 # by host, port, community, and oid.
246 # There can be several targets with the same host|port|community+oid set.
248 $cref->{'targets'}{$hosthash}{$oid}{$token} = 1;
249 $cref->{'activehosts'}{$hosthash} = 1;
251 $tref->{'oid'} = $oid;
253 $cref->{'oids_per_pdu'}{$hosthash} =
254 $collector->param($token, 'snmp-oids-per-pdu');
256 if( $collector->paramString($token, 'snmp-object-type') eq 'COUNTER64' )
258 $cref->{'64bit_oid'}{$oid} = 1;
261 if( $collector->paramDisabled($token, 'snmp-check-sysuptime') )
263 $cref->{'nosysuptime'}{$hosthash} = 1;
266 if( $collector->paramEnabled($token, 'snmp-ignore-mib-errors') )
268 $cref->{'ignoremiberrors'}{$hosthash}{$oid} = 1;
271 return 1;
275 sub getHostHash
277 my $collector = shift;
278 my $token = shift;
280 my $hostname = $collector->param($token, 'snmp-host');
281 my $domain = $collector->paramString($token, 'domain-name');
283 if( $domain ne '' and
284 index($hostname, '.') < 0 and
285 index($hostname, ':') < 0 )
287 $hostname .= '.' . $domain;
290 my $port = $collector->param($token, 'snmp-port');
291 my $version = $collector->param($token, 'snmp-version');
293 my $community;
294 if( $version eq '1' or $version eq '2c' )
296 $community = $collector->param($token, 'snmp-community');
298 else
300 # We use community string to identify the agent.
301 # For SNMPv3, it's the user name
302 $community = $collector->param($token, 'snmp-username');
305 return join('|', $hostname, $port, $community);
309 sub snmpSessionArgs
311 my $collector = shift;
312 my $token = shift;
313 my $hosthash = shift;
315 my $cref = $collector->collectorData( 'snmp' );
316 if( defined( $cref->{'snmpargs'}{$hosthash} ) )
318 return $cref->{'snmpargs'}{$hosthash};
321 my $transport = $collector->param($token, 'snmp-transport') . '/ipv' .
322 $collector->param($token, 'snmp-ipversion');
324 my ($hostname, $port, $community) = split(/\|/o, $hosthash);
326 my $version = $collector->param($token, 'snmp-version');
327 my $ret = [ -domain => $transport,
328 -hostname => $hostname,
329 -port => $port,
330 -timeout => $collector->param($token, 'snmp-timeout'),
331 -retries => $collector->param($token, 'snmp-retries'),
332 -version => $version ];
334 for my $arg ( qw(-localaddr -localport) )
336 my $val = $collector->param($token, 'snmp' . $arg);
337 if( defined( $val ) )
339 push( @{$ret}, $arg, $val );
343 if( $version eq '1' or $version eq '2c' )
345 push( @{$ret}, '-community', $community );
347 else
349 push( @{$ret}, -username, $community);
351 for my $arg ( qw(-authkey -authpassword -authprotocol
352 -privkey -privpassword -privprotocol) )
354 my $val = $collector->param($token, 'snmp' . $arg);
355 if( defined( $val ) )
357 push( @{$ret}, $arg, $val );
362 $cref->{'snmpargs'}{$hosthash} = $ret;
363 return $ret;
368 sub openBlockingSession
370 my $collector = shift;
371 my $token = shift;
372 my $hosthash = shift;
374 my $args = snmpSessionArgs( $collector, $token, $hosthash );
375 my ($session, $error) =
376 Net::SNMP->session( @{$args},
377 -nonblocking => 0,
378 -translate => ['-all', 0, '-octetstring', 1] );
379 if( not defined($session) )
381 Error('Cannot create SNMP session for ' . $hosthash . ': ' . $error);
383 else
385 my $maxmsgsize = $collector->param($token, 'snmp-max-msg-size');
386 if( defined( $maxmsgsize ) and $maxmsgsize > 0 )
388 $session->max_msg_size( $maxmsgsize );
392 return $session;
395 sub openNonblockingSession
397 my $collector = shift;
398 my $token = shift;
399 my $hosthash = shift;
401 my $args = snmpSessionArgs( $collector, $token, $hosthash );
403 my ($session, $error) =
404 Net::SNMP->session( @{$args},
405 -nonblocking => 0x1,
406 -translate => ['-timeticks' => 0] );
407 if( not defined($session) )
409 Error('Cannot create SNMP session for ' . $hosthash . ': ' . $error);
410 return
413 if( $collector->param($token, 'snmp-transport') eq 'udp' )
415 # We set SO_RCVBUF only once, because Net::SNMP shares
416 # one UDP socket for all sessions.
418 my $sock_name = $session->transport()->sock_name();
419 my $refcount = $Net::SNMP::Transport::SOCKETS->{
420 $sock_name}->[&Net::SNMP::Transport::_SHARED_REFC()];
422 if( defined($refcount) and $refcount == 1 )
424 my $buflen = int($Torrus::Collector::SNMP::RxBuffer);
425 my $socket = $session->transport()->socket();
426 my $ok = $socket->sockopt( SO_RCVBUF, $buflen );
427 if( not $ok )
429 Error('Could not set SO_RCVBUF to ' .
430 $buflen . ': ' . $!);
432 else
434 Debug('Set SO_RCVBUF to ' . $buflen);
439 my $maxmsgsize = $collector->param($token, 'snmp-max-msg-size');
440 if( defined( $maxmsgsize ) and $maxmsgsize > 0 )
442 $session->max_msg_size( $maxmsgsize );
446 return $session;
450 sub expandOidMappings
452 my $collector = shift;
453 my $token = shift;
454 my $hosthash = shift;
455 my $oid_in = shift;
457 my $cref = $collector->collectorData( 'snmp' );
459 my $oid = $oid_in;
461 # Process Map statements
463 while( index( $oid, 'M(' ) >= 0 )
465 if( not $oid =~ m/^(.*)M\(\s*([0-9\.]+)\s*,\s*([^\)]+)\)(.*)$/o )
467 Error("Error in OID mapping syntax: $oid");
468 return
471 my $head = $1;
472 my $map = $2;
473 my $key = $3;
474 my $tail = $4;
476 # Remove trailing space from key
477 $key =~ s/\s+$//o;
479 my $value =
480 lookupMap( $collector, $token, $hosthash, $map, $key );
482 if( defined( $value ) )
484 if( $value eq 'notfound' )
486 return 'notfound';
488 else
490 $oid = $head . $value . $tail;
493 else
495 return
499 # process value lookups
501 while( index( $oid, 'V(' ) >= 0 )
503 if( not $oid =~ /^(.*)V\(\s*([0-9\.]+)\s*\)(.*)$/o )
505 Error("Error in OID value lookup syntax: $oid");
506 return
509 my $head = $1;
510 my $key = $2;
511 my $tail = $4;
513 my $value;
515 if( not defined( $cref->{'value-lookups'}
516 {$hosthash}{$key} ) )
518 # Retrieve the OID value from host
520 my $session = openBlockingSession( $collector, $token, $hosthash );
521 if( not defined($session) )
523 return
526 my $result = $session->get_request( -varbindlist => [$key] );
527 $session->close();
528 if( defined $result and defined($result->{$key}) )
530 $value = $result->{$key};
531 $cref->{'value-lookups'}{$hosthash}{$key} = $value;
533 else
535 Error("Error retrieving $key from $hosthash: " .
536 $session->error());
537 probablyDead( $collector, $hosthash );
538 return
541 else
543 $value =
544 $cref->{'value-lookups'}{$hosthash}{$key};
546 if( defined( $value ) )
548 $oid = $head . $value . $tail;
550 else
552 return 'notfound';
556 # Debug('OID expanded: ' . $oid_in . ' -> ' . $oid');
557 return $oid;
560 # Look up table index in a map by value
562 sub lookupMap
564 my $collector = shift;
565 my $token = shift;
566 my $hosthash = shift;
567 my $map = shift;
568 my $key = shift;
570 my $cref = $collector->collectorData( 'snmp' );
571 my $maphash = join('#', $hosthash, $map);
573 if( not defined( $maps{$hosthash}{$map} ) )
575 my $ret;
577 if( defined( $oldMaps{$hosthash}{$map} ) and
578 defined( $key ) )
580 $ret = $oldMaps{$hosthash}{$map}{$key};
583 if( $mapLookupScheduled{$maphash} )
585 return $ret;
588 if( not $collector->didNotRun()
590 (scalar(@mappingSessions) >=
591 $Torrus::Collector::SNMP::maxSessionsPerDispatcher) )
593 snmp_dispatcher();
594 @mappingSessions = ();
595 %mapLookupScheduled = ();
598 # Retrieve map from host
599 Debug('Retrieving map ' . $map . ' from ' . $hosthash);
601 my $session = openNonblockingSession( $collector, $token, $hosthash );
602 if( not defined($session) )
604 return $ret;
606 else
608 push( @mappingSessions, $session );
611 # Retrieve the map table
613 my $maxrepetitions = $collector->param($token, 'snmp-maxrepetitions');
614 $session->get_table( -baseoid => $map,
615 -maxrepetitions => $maxrepetitions,
616 -callback => [\&mapLookupCallback,
617 $collector, $hosthash, $map] );
619 $mapLookupScheduled{$maphash} = 1;
621 if( not $snmpV1Hosts{$hosthash} )
623 $mapsExpire{$maphash} =
624 int( time() + $mapsRefreshPeriod +
625 rand( $mapsRefreshPeriod * $mapsRefreshRandom ) );
628 return $ret;
631 if( defined( $key ) )
633 my $value = $maps{$hosthash}{$map}{$key};
634 if( not defined $value )
636 Error("Cannot find value $key in map $map for $hosthash in ".
637 $collector->path($token));
638 if( defined ( $maps{$hosthash}{$map} ) )
640 Error('Current map follows');
641 while( my($mkey, $mval) = each
642 %{$maps{$hosthash}{$map}} )
644 Error("'$mkey' => '$mval'");
647 return 'notfound';
649 else
651 if( not $snmpV1Hosts{$hosthash} )
653 $cref->{'mapsDependentTokens'}{$maphash}{$token} = 1;
654 $cref->{'mapsRelatedMaps'}{$token}{$maphash} = 1;
657 return $value;
660 else
662 return
667 sub mapLookupCallback
669 my $session = shift;
670 my $collector = shift;
671 my $hosthash = shift;
672 my $map = shift;
674 &Torrus::DB::checkInterrupted();
676 Debug('Received mapping PDU from ' . $hosthash);
678 my $result = $session->var_bind_list();
679 if( defined $result )
681 my $preflen = length($map) + 1;
683 while( my( $oid, $key ) = each %{$result} )
685 my $val = substr($oid, $preflen);
686 $maps{$hosthash}{$map}{$key} = $val;
687 # Debug("Map $map discovered: '$key' -> '$val'");
690 else
692 Error("Error retrieving table $map from $hosthash: " .
693 $session->error());
694 $session->close();
695 probablyDead( $collector, $hosthash );
696 return
698 return;
702 sub activeMappingSessions
704 return scalar( @mappingSessions );
707 # The target host is unreachable. We try to reach it few more times and
708 # give it the final diagnose.
710 sub probablyDead
712 my $collector = shift;
713 my $hosthash = shift;
715 my $cref = $collector->collectorData( 'snmp' );
717 # Stop all collection for this host, until next initTargetAttributes
718 # is successful
719 delete $cref->{'activehosts'}{$hosthash};
721 my $probablyAlive = 1;
723 if( defined( $hostUnreachableSeen{$hosthash} ) )
725 if( $Torrus::Collector::SNMP::unreachableTimeout > 0 and
726 time() -
727 $hostUnreachableSeen{$hosthash} >
728 $Torrus::Collector::SNMP::unreachableTimeout )
730 $probablyAlive = 0;
733 else
735 $hostUnreachableSeen{$hosthash} = time();
737 if( defined( $db_failures ) )
739 $db_failures->host_failure('unreachable', $hosthash);
740 $db_failures->set_counter('unreachable',
741 scalar( keys %hostUnreachableSeen));
745 if( $probablyAlive )
747 Info('Target host is unreachable. Will try again later: ' . $hosthash);
749 else
751 # It is dead indeed. Delete all tokens associated with this host
752 Info('Target host is unreachable during last ' .
753 $Torrus::Collector::SNMP::unreachableTimeout .
754 ' seconds. Giving it up: ' . $hosthash);
755 my @deleteTargets = ();
756 while( my ($oid, $ref1) =
757 each %{$cref->{'targets'}{$hosthash}} )
759 while( my ($token, $dummy) = each %{$ref1} )
761 push( @deleteTargets, $token );
765 Debug('Deleting ' . scalar( @deleteTargets ) . ' tokens');
766 for my $token ( @deleteTargets )
768 $collector->deleteTarget($token);
771 delete $hostUnreachableSeen{$hosthash};
772 delete $hostUnreachableRetry{$hosthash};
773 $unreachableHostDeleted{$hosthash} = 1;
775 if( defined( $db_failures ) )
777 $db_failures->host_failure('deleted', $hosthash);
778 $db_failures->set_counter('unreachable',
779 scalar( keys %hostUnreachableSeen));
780 $db_failures->set_counter('deleted',
781 scalar( keys %unreachableHostDeleted));
785 return $probablyAlive;
788 # Return false if the try is too early
790 sub checkUnreachableRetry
792 my $collector = shift;
793 my $hosthash = shift;
795 my $ret = 1;
796 if( $hostUnreachableSeen{$hosthash} )
798 my $lastRetry = $hostUnreachableRetry{$hosthash};
800 if( not defined( $lastRetry ) )
802 $lastRetry = $hostUnreachableSeen{$hosthash};
805 if( time() < $lastRetry +
806 $Torrus::Collector::SNMP::unreachableRetryDelay )
808 $ret = 0;
810 else
812 $hostUnreachableRetry{$hosthash} = time();
816 return $ret;
820 sub isHostDead
822 my $collector = shift;
823 my $hosthash = shift;
825 return $unreachableHostDeleted{$hosthash};
829 sub hostReachableAgain
831 my $collector = shift;
832 my $hosthash = shift;
834 if( exists( $hostUnreachableSeen{$hosthash} ) )
836 delete $hostUnreachableSeen{$hosthash};
837 if( defined( $db_failures ) )
839 $db_failures->remove_host($hosthash);
840 $db_failures->set_counter('unreachable',
841 scalar( keys %hostUnreachableSeen));
844 return;
848 # Callback executed by Collector
850 sub deleteTarget
852 my $collector = shift;
853 my $token = shift;
855 my $tref = $collector->tokenData( $token );
856 my $cref = $collector->collectorData( 'snmp' );
858 my $hosthash = $tref->{'hosthash'};
859 my $oid = $tref->{'oid'};
861 if( defined($oid) )
863 delete $cref->{'targets'}{$hosthash}{$oid}{$token};
864 if( scalar(keys %{$cref->{'targets'}{$hosthash}{$oid}}) == 0 )
866 delete $cref->{'targets'}{$hosthash}{$oid};
868 if( scalar(keys %{$cref->{'targets'}{$hosthash}}) == 0 )
870 delete $cref->{'targets'}{$hosthash};
875 delete $cref->{'needsRemapping'}{$token};
877 for my $maphash ( keys %{$cref->{'mapsRelatedMaps'}{$token}} )
879 delete $cref->{'mapsDependentTokens'}{$maphash}{$token};
881 delete $cref->{'mapsRelatedMaps'}{$token};
882 return;
885 # Main collector cycle
887 $Torrus::Collector::runCollector{'snmp'} =
888 \&Torrus::Collector::SNMP::runCollector;
890 sub runCollector
892 my $collector = shift;
893 my $cref = shift;
895 # Info(sprintf('runCollector() Offset: %d, active hosts: %d, maps: %d',
896 # $collector->offset(),
897 # scalar( keys %{$cref->{'activehosts'}} ),
898 # scalar(keys %maps)));
900 # Create one SNMP session per host address.
901 # We assume that version, timeout and retries are the same
902 # within one address
904 # We limit the number of sessions per snmp_dispatcher run
905 # because of some strange bugs: with more than 400 sessions per
906 # dispatcher, some requests are not sent out
908 my @hosts = keys %{$cref->{'activehosts'}};
910 while( scalar(@mappingSessions) + scalar(@hosts) > 0 )
912 my @batch = ();
913 while( ( scalar(@mappingSessions) + scalar(@batch) <
914 $Torrus::Collector::SNMP::maxSessionsPerDispatcher )
916 scalar(@hosts) > 0 )
918 push( @batch, pop( @hosts ) );
921 &Torrus::DB::checkInterrupted();
923 my @sessions;
925 for my $hosthash ( @batch )
927 my @oids = sort keys %{$cref->{'targets'}{$hosthash}};
929 # Info(sprintf('Host %s: %d OIDs',
930 # $hosthash,
931 # scalar(@oids)));
933 # Find one representative token for the host
935 if( scalar( @oids ) == 0 )
937 next;
940 my @reptokens = keys %{$cref->{'targets'}{$hosthash}{$oids[0]}};
941 if( scalar( @reptokens ) == 0 )
943 next;
945 my $reptoken = $reptokens[0];
947 my $session =
948 openNonblockingSession( $collector, $reptoken, $hosthash );
950 &Torrus::DB::checkInterrupted();
952 if( not defined($session) )
954 next;
956 else
958 Debug('Created SNMP session for ' . $hosthash);
959 push( @sessions, $session );
962 my $oids_per_pdu = $cref->{'oids_per_pdu'}{$hosthash};
964 my @pdu_oids = ();
965 my $delay = 0;
967 while( scalar( @oids ) > 0 )
969 my $oid = shift @oids;
970 push( @pdu_oids, $oid );
972 if( scalar( @oids ) == 0 or
973 ( scalar( @pdu_oids ) >= $oids_per_pdu ) )
975 if( not $cref->{'nosysuptime'}{$hosthash} )
977 # We insert sysUpTime into every PDU, because
978 # we need it in further processing
979 push( @pdu_oids, $sysUpTime );
982 if( Torrus::Log::isDebug() )
984 Debug('Sending SNMP PDU to ' . $hosthash . ':');
985 for my $oid ( @pdu_oids )
987 Debug($oid);
991 # Generate the list of tokens that form this PDU
992 my $pdu_tokens = {};
993 for my $oid ( @pdu_oids )
995 if( defined( $cref->{'targets'}{$hosthash}{$oid} ) )
997 for my $token
998 ( keys %{$cref->{'targets'}{$hosthash}{$oid}} )
1000 $pdu_tokens->{$oid}{$token} = 1;
1004 my $result =
1005 $session->
1006 get_request( -delay => $delay,
1007 -callback =>
1008 [ \&Torrus::Collector::SNMP::callback,
1009 $collector, $pdu_tokens, $hosthash ],
1010 -varbindlist => \@pdu_oids );
1011 if( not defined $result )
1013 Error('Cannot create SNMP request: ' .
1014 $session->error);
1016 @pdu_oids = ();
1017 $delay += 0.01;
1022 &Torrus::DB::checkInterrupted();
1024 snmp_dispatcher();
1026 # Check if there were pending map lookup sessions
1028 if( scalar( @mappingSessions ) > 0 )
1030 @mappingSessions = ();
1031 %mapLookupScheduled = ();
1034 return;
1038 sub callback
1040 my $session = shift;
1041 my $collector = shift;
1042 my $pdu_tokens = shift;
1043 my $hosthash = shift;
1045 &Torrus::DB::checkInterrupted();
1047 my $cref = $collector->collectorData( 'snmp' );
1049 Debug('SNMP Callback executed for ' . $hosthash);
1051 if( not defined( $session->var_bind_list() ) )
1053 Error('SNMP Error for ' . $hosthash . ': ' . $session->error() .
1054 ' when retrieving ' . join(' ', sort keys %{$pdu_tokens}));
1056 probablyDead( $collector, $hosthash );
1058 # Clear the mapping
1059 delete $maps{$hosthash};
1060 for my $oid ( keys %{$pdu_tokens} )
1062 for my $token ( keys %{$pdu_tokens->{$oid}} )
1064 $cref->{'needsRemapping'}{$token} = 1;
1067 return;
1069 else
1071 hostReachableAgain( $collector, $hosthash );
1074 my $timestamp = time();
1076 my $checkUptime = not $cref->{'nosysuptime'}{$hosthash};
1077 my $doSetValue = 1;
1079 my $uptime = 0;
1081 if( $checkUptime )
1083 my $uptimeTicks = $session->var_bind_list()->{$sysUpTime};
1084 if( defined $uptimeTicks )
1086 $uptime = $uptimeTicks / 100;
1087 Debug('Uptime: ' . $uptime);
1089 else
1091 Error('Did not receive sysUpTime for ' . $hosthash);
1094 if( $uptime < $collector->period() or
1095 ( defined($cref->{'knownUptime'}{$hosthash})
1097 $uptime + $collector->period() <
1098 $cref->{'knownUptime'}{$hosthash} ) )
1100 # The agent has reloaded. Clean all maps and push UNDEF
1101 # values to the storage
1103 Info('Agent rebooted: ' . $hosthash);
1104 delete $maps{$hosthash};
1106 $timestamp -= $uptime;
1107 for my $oid ( keys %{$pdu_tokens} )
1109 for my $token ( keys %{$pdu_tokens->{$oid}} )
1111 $collector->setValue( $token, 'U', $timestamp, $uptime );
1112 $cref->{'needsRemapping'}{$token} = 1;
1116 $doSetValue = 0;
1118 $cref->{'knownUptime'}{$hosthash} = $uptime;
1121 if( $doSetValue )
1123 while( my ($oid, $value) = each %{ $session->var_bind_list() } )
1125 # Debug("OID=$oid, VAL=$value");
1126 if( $value eq 'noSuchObject' or
1127 $value eq 'noSuchInstance' or
1128 $value eq 'endOfMibView' )
1130 if( not $cref->{'ignoremiberrors'}{$hosthash}{$oid} )
1132 Error("Error retrieving $oid from $hosthash: $value");
1134 for my $token ( keys %{$pdu_tokens->{$oid}} )
1136 if( defined( $db_failures ) )
1138 $db_failures->mib_error
1139 ($hosthash, $collector->path($token));
1142 $collector->deleteTarget($token);
1146 else
1148 if( $cref->{'64bit_oid'}{$oid} )
1150 $value = Math::BigInt->new($value);
1153 for my $token ( keys %{$pdu_tokens->{$oid}} )
1155 $collector->setValue( $token, $value,
1156 $timestamp, $uptime );
1161 return;
1165 # Execute this after the collector has finished
1167 $Torrus::Collector::postProcess{'snmp'} =
1168 \&Torrus::Collector::SNMP::postProcess;
1170 sub postProcess
1172 my $collector = shift;
1173 my $cref = shift;
1175 # It could happen that postProcess is called for a collector which
1176 # has no targets, and therefore it's the only place where we can
1177 # initialize these variables
1179 if( not defined( $cref->{'mapsLastExpireChecked'} ) )
1181 $cref->{'mapsLastExpireChecked'} = 0;
1184 if( not defined( $cref->{'mapsRefreshed'} ) )
1186 $cref->{'mapsRefreshed'} = [];
1189 # look if some maps are ready after last expiration check
1190 if( scalar( @{$cref->{'mapsRefreshed'}} ) > 0 )
1192 for my $maphash ( @{$cref->{'mapsRefreshed'}} )
1194 for my $token
1195 ( keys %{$cref->{'mapsDependentTokens'}{$maphash}} )
1197 $cref->{'needsRemapping'}{$token} = 1;
1200 $cref->{'mapsRefreshed'} = [];
1203 my $now = time();
1205 if( $cref->{'mapsLastExpireChecked'} + $mapsExpireCheckPeriod <= $now )
1207 $cref->{'mapsLastExpireChecked'} = $now;
1209 # Check the maps expiration and arrange lookup for expired
1211 while( my ( $maphash, $expire ) = each %mapsExpire )
1213 if( $expire <= $now and not $mapLookupScheduled{$maphash} )
1215 &Torrus::DB::checkInterrupted();
1217 my ( $hosthash, $map ) = split( /\#/o, $maphash );
1219 if( $unreachableHostDeleted{$hosthash} )
1221 # This host is no longer polled. Remove the leftovers
1223 delete $mapsExpire{$maphash};
1224 delete $maps{$hosthash};
1226 else
1228 # Find one representative token for the map
1229 my @tokens =
1230 keys %{$cref->{'mapsDependentTokens'}{$maphash}};
1231 if( scalar( @tokens ) == 0 )
1233 next;
1235 my $reptoken = $tokens[0];
1237 # save the map for the time of refresh
1238 $oldMaps{$hosthash}{$map} = $maps{$hosthash}{$map};
1239 delete $maps{$hosthash}{$map};
1241 # this will schedule the map retrieval for the next
1242 # collector cycle
1243 Debug('Refreshing map: ' . $maphash);
1245 lookupMap( $collector, $reptoken,
1246 $hosthash, $map, undef );
1248 # After the next collector period, the maps will be
1249 # ready and tokens may be updated without losing the data
1250 push( @{$cref->{'mapsRefreshed'}}, $maphash );
1256 for my $token ( keys %{$cref->{'needsRemapping'}} )
1258 &Torrus::DB::checkInterrupted();
1260 delete $cref->{'needsRemapping'}{$token};
1261 if( not Torrus::Collector::SNMP::initTargetAttributes
1262 ( $collector, $token ) )
1264 $collector->deleteTarget($token);
1267 return;
1271 #### ==== SNMP Reachability Collector ====
1273 # Register the collector type (it should always run after snmp collector)
1274 $Torrus::Collector::collectorTypes{'snmp-reachable'} = 2;
1277 # List of needed parameters and default values
1279 $Torrus::Collector::params{'snmp-reachable'} = {
1280 'snmp-version' => undef,
1281 'snmp-port' => undef,
1282 'snmp-community' => undef,
1283 'snmp-username' => undef,
1284 'domain-name' => undef,
1285 'snmp-host' => undef,
1289 # This is first executed per target
1291 $Torrus::Collector::initTarget{'snmp-reachable'} = \&reachable_initTarget;
1294 sub reachable_initTarget
1296 my $collector = shift;
1297 my $token = shift;
1299 my $cref = $collector->collectorData('snmp-reachable');
1300 my $tref = $collector->tokenData( $token );
1301 my $hosthash = getHostHash( $collector, $token );
1302 if( not defined( $hosthash ) )
1304 return 0;
1307 $tref->{'hosthash'} = $hosthash;
1308 $cref->{'targets'}{$hosthash}{$token} = 1;
1310 return 1;
1314 # Main collector cycle
1316 $Torrus::Collector::runCollector{'snmp-reachable'} = \&reachable_runCollector;
1318 sub reachable_runCollector
1320 my $collector = shift;
1321 my $cref = shift;
1323 if( not defined( $db_failures ) )
1325 Error('$db_failures is not initialized. ' .
1326 'snmp-reachable collector cannot continue');
1327 return;
1330 my $timestamp = time();
1332 while(my ($hosthash, $r) = each %{$cref->{'targets'}} )
1334 for my $token (keys %{$r})
1336 my $val = $db_failures->is_host_available($hosthash) ? 100:0;
1337 $collector->setValue( $token, $val, $timestamp, 0 );
1340 return;
1347 # Local Variables:
1348 # mode: perl
1349 # indent-tabs-mode: nil
1350 # perl-indent-level: 4
1351 # End: