#!/usr/bin/perl -w # # Copyright 2003, 2004 Massachusetts Institute of Technology # # Permission to use, copy, modify, and distribute this software and # its documentation for any purpose and without fee is hereby # granted, provided that both the above copyright notice and this # permission notice appear in all copies, that both the above # copyright notice and this permission notice appear in all # supporting documentation, and that the name of M.I.T. not be used # in advertising or publicity pertaining to distribution of the # software without specific, written prior permission. M.I.T. makes # no representations about the suitability of this software for any # purpose. It is provided "as is" without express or implied # warranty. # # THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS # ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE, # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT # SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT # OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # use constant OTHER => 1; use constant CONTAINER => 5; use constant SENSOR => 8; use constant MODULE => 9; use strict; use vars qw(%cisco_scale @cisco_units %cisco_relation %cisco_severity %opt $s); use Carp; use Getopt::Std; use SNMP; use Tree::DAG_Node; getopts('c:l:Su:v:', \%opt); %cisco_scale = (1 => 1e-24, 2 => 1e-21, 3 => 1e-18, 4 => 1e-15, 5 => 1e-12, 6 => 1e-9, 7 => 1e-9, 8 => 1e-3, 9 => 1, 10 => 1e3, 11 => 1e6, 12 => 1e9, 13 => 1e12, 14 => 1e15, 15 => 1e18, 16 => 1e21, 17 => 1e21); @cisco_units = (undef, 'other', 'unknown', 'VAC', 'VDC', 'A', 'W', 'Hz', 'degC', '%RH', 'RPM', 'm^3/min', 'truthvalue', 'enumeration'); # Used to begin a sentence so capitalize. %cisco_severity = (1 => 'Other', 10 => 'Minor', 20 => 'Major'); %cisco_relation = (1 => '<', 2 => '<=', 3 => '>', 4 => '>=', 5 => '==', 6 => '!='); sub iid ($) { return $_[0]->[1]; } sub val ($) { return $_[0]->[2]; } sub maketree () { # # Make sure we have the right MIBs loaded even if the user doesn't # load everything by default. It does not appear that net-snmp will # complain if you try to load a module that doesn't exist, but things # will probably get unhappy below if we can't translate the MIB objects # we need. # &SNMP::loadModules('ENTITY-MIB', 'CISCO-ENTITY-SENSOR-MIB', 'CISCO-ASSET-ENTITY-MIB', 'CISCO-ENTITY-EXT-MIB'); $s = new SNMP::Session(DestHost => $ARGV[0], AuthProto => 'SHA', (exists $opt{c} ? (Community => $opt{c}) : ()), (exists $opt{v} ? (Version => $opt{v}) : ()), (exists $opt{l} ? (SecLevel => $opt{l}) : ()), (exists $opt{u} ? (SecName => $opt{u}) : ())) or die "SNMP::Session->new: $!\n"; my (%ents); my ($cl) = $s->bulkwalk(0, 250, [['entPhysicalClass']]); if (!defined $cl) { die "bulkwalk: $s->{ErrorStr}\n"; } # entPhysicalClass is mandatory for all entities, so we can use it # to figure out all of the instances. Create each node as a trivial # tree to be glued together later. foreach my $class (@$cl) { my ($node) = new Tree::DAG_Node; $ents{iid $class} = $node; $node->attributes->{Class} = val $class; } # Now turn all of the other columns into attributes on each node. my (@cols) = qw(Descr Name VendorType ContainedIn ParentRelPos HardwareRev FirmwareRev SoftwareRev SerialNum MfgName ModelName Alias AssetID IsFRU); my (%nullable) = (Alias => 1, Name => 1, Descr => 1, SerialNum => 1, MfgName => 1, ModelName => 1, HardwareRev => 1, FirmwareRev => 1, SoftwareRev => 1, AssetID => 1); col: foreach my $column (@cols) { my ($rows) = $s->bulkwalk(0, 250, [["entPhysical$column"]]); row: foreach my $row (@$rows) { next row if (exists($nullable{$column}) and val($row) eq ''); if (!exists($ents{iid $row})) { warn "Got entPhysical$column." . iid($row) . " but not entPhysicalClass." . iid($row) . "\n"; next row; } $ents{iid $row}->attributes->{$column} = val $row; } } foreach my $index (keys %ents) { my ($node) = $ents{$index}; $node->attributes->{Index} = $index; # Set up node names... if (exists $node->attributes->{Descr}) { $node->name($node->attributes->{Descr}); } elsif (exists $node->attributes->{Name}) { $node->name($node->attributes->{Name}); } else { $node->name("[$index]"); } # Build tree if ($node->attributes->{ContainedIn} != 0) { my ($parent) = $ents{$node->attributes->{ContainedIn}}; if (defined $parent) { my (@siblings) = $parent->daughters; my ($relpos) = $node->attributes->{ParentRelPos}; # Preserve ParentRelPos ordering without making gaps. push(@siblings, $node); @siblings = (sort childsort @siblings); $parent->set_daughters(@siblings); } else { warn "Entity $index: invalid entPhysicalContainedIn\n"; } } } # Doesn't matter which one we use to find the root. We could have # figured it out above but this is easier. my ($root) = $ents{(keys %ents)[0]}->root; viztree($ARGV[0], $root); } sub childsort { my ($rv) = ($a->attributes->{Class} <=> $b->attributes->{Class}); if ($rv == 0) { return ($a->attributes->{ParentRelPos} <=> $b->attributes->{ParentRelPos}); } return $rv; } sub viztree ($$) { my ($name, $root) = @_; print "Physical entities of \"$name\":\n"; walktree("", 0, $root); } sub walktree ($$$) { my ($prefix, $morekids, $node) = @_; my ($cont) = $morekids ? " | " : " "; return if ($node->attributes->{Class} == &SENSOR and exists($opt{S})); my ($label) = join("\n$prefix ", split(/\n/, nodelabel($node))); printf STDOUT ("%s%-3d%s\n", $prefix, $node->attributes->{ParentRelPos} || 0.0, $label, "\n"); my (@children) = $node->daughters; for (my ($i) = 0; $i <= $#children; $i++) { walktree($prefix . " ", $i == $#children, $children[$i]); } print "\n" if ($node->attributes->{Class} == &CONTAINER); } sub nodelabel ($) { my ($node) = @_; my ($label) = $node->name; my ($attr) = $node->attributes; # We used Name in preference to Descr, so if both are set, give # description first. # Not any more.... # if (exists $attr->{Name} and exists $attr->{Descr} # and $attr->{Name} ne $attr->{Descr}) { # $label .= "\n$attr->{Descr}"; # } if ($attr->{IsFRU} == 1) { $label .= ' (FRU)'; } if ($attr->{VendorType} =~ /^\Q.1.3.6.1.4.1.9.12.3.1.\E/) { if ($attr->{Class} == &SENSOR) { $label .= "\n" . ciscosensor($node->attributes->{Index}); } elsif ($attr->{Class} == &OTHER) { my ($ram) = $s->get(['ceExtProcessorRam', $attr->{Index}]); if (defined($ram) and $ram ne '' and $ram ne 'NOSUCHINSTANCE') { $label .= "\nInstalled RAM: $ram bytes"; } $ram = $s->get(['ceExtNVRAMSize', $attr->{Index}]); my ($used) = $s->get(['ceExtNVRAMUsed', $attr->{Index}]); if (defined($ram) and defined($used) and $ram ne '' and $used ne '' and $ram ne 'NOSUCHINSTANCE' and $used ne 'NOSUCHINSTANCE') { $label .= "\nInstalled NVRAM: $ram bytes ($used used)"; } } elsif ($attr->{Class} == &MODULE) { my ($swid) = $s->get(['ceAssetSoftwareID', $attr->{Index}]); if (defined($swid) and $swid ne '' and $swid ne 'unknown') { $label .= "\nRunning software $swid"; } } } if (exists $attr->{Alias}) { $label .= "\nAlias: $attr->{Alias}"; } if (exists $attr->{MfgName} and exists $attr->{ModelName}) { $label .= "\n$attr->{MfgName} $attr->{ModelName}"; } if (exists $attr->{HardwareRev} or exists $attr->{FirmwareRev} or exists $attr->{SoftwareRev}) { my ($space) = ''; $label .= "\n"; if (exists $attr->{HardwareRev}) { $label .= "HW $attr->{HardwareRev}"; $space = ', '; } if (exists $attr->{FirmwareRev}) { $label .= $space . "FW $attr->{FirmwareRev}"; $space = ', '; } if (exists $attr->{SoftwareRev}) { $label .= $space . "SW $attr->{SoftwareRev}"; } } if (exists $attr->{SerialNum}) { $label .= "\nS/N $attr->{SerialNum}"; } if (exists $attr->{AssetID}) { $label .= "\nAsset Tag $attr->{AssetID}"; } return $label; } my ($boottime); sub tt2time ($) { my ($ticks) = @_; unless (defined $boottime) { my ($sut) = $s->get('sysUpTime.0'); $boottime = time() - $sut / 100.0; } return $boottime + $ticks / 100.0; } sub ciscosensor ($) { my ($iid) = @_; my ($type, $scale, $precision, $value, $status, $valuets, $valueupd) = $s->get([['entSensorType', $iid], ['entSensorScale', $iid], ['entSensorPrecision', $iid], ['entSensorValue', $iid], ['entSensorStatus', $iid], ['entSensorValueTimeStamp', $iid], ['entSensorValueUpdateRate', $iid]]); if (!defined($type) or $type eq 'NOSUCHINSTANCE') { # warn "unable to retrieve Cisco sensor information for entity $iid\n"; return ""; } if ($status == 2) { return "- sensor value is not available"; } elsif ($status == 3) { return "- sensor is not operational"; } my ($formatted) = ciscosensorvalue($type, $value, $scale, $precision); if ($valueupd != 0) { $valuets = localtime(int(tt2time($valuets))); $formatted .= " (updated every $valueupd s, last $valuets)"; } # Look for sensor thresholds... my ($sev, $rel, $val, $eval) = $s->bulkwalk(0, 10, [['entSensorThresholdSeverity', $iid], ['entSensorThresholdRelation', $iid], ['entSensorThresholdValue', $iid], ['entSensorThresholdEvaluation', $iid]]); if (!defined($sev) or $#$sev < 0) { # No thresholds defined for this entity. return $formatted; } for (my ($i) = 0; $i <= $#$sev; $i++) { $formatted .= ("\n- " . ($cisco_severity{val $sev->[$i]} || val $sev->[$i]) . " threshold: sensor value " . $cisco_relation{val $rel->[$i]} . " " . ciscosensorvalue($type, val $val->[$i], $scale, $precision) . " (currently " . (val($eval->[$i]) == 1 ? "true)" : "false)")); } return '- ' . $formatted; } sub ciscosensorvalue ($$$$) { my ($type, $value, $scale, $precision) = @_; my $formatted; if ($type == 1) { $formatted = "other: $value"; } elsif ($type == 2) { $formatted = "unknown: $value"; } elsif ($type == 12) { $formatted = ($value == 1 ? "true" : "false"); } elsif ($type == 13) { $formatted = "enumeration: $value"; } else { if (exists $cisco_scale{$scale}) { $value *= $cisco_scale{$scale}; } if ($precision > 0) { $value /= 10**$precision; } elsif ($precision < 0) { $precision = 0; # XXX how to deal with this case? } $formatted = sprintf("%.*f ", $precision, $value); $formatted .= $cisco_units[$type]; } return $formatted; } maketree();