#!/opt/vdops/bin/perl # Poll switches for ifInOctets, keeping track of the last time each port # has seen a delta in this parameter. Store that data in a text file. # Produce a report which lists the ports which have not seen a change in # ifInOctets for some period of time, e.g. 30 days # V Who When What # --------------------------------------------------------------------------- # 3.1.0 skendric 04-13-2011 Support HP ProCurve # 3.0.0 skendric 02-21-2011 Upgrade to Netops 1.4.0 # 2.9.1 skendric 08-27-2010 Handle newly added modules # 2.9.0 skendric 06-17-2010 Collapse all data gathering routines onto a # universal routein (gather_ifinoctets) # 2.8.0 skendric 03-30-2010 Replace Net::SNMP with SNMP.pm # 2.7.5 skendric 02-05-2010 Upgrade to perl 5.10.1 # 2.7.4 skendric 09-29-2009 Improved support for 7200 routers # 2.7.3 skendric 09-25-2009 Fix bug in grab_mib2_bytes which excluded # MIB2 devices from the report # 2.7.2 skendric 07-08-2009 Handle various bugs in data file; hack around # 7206 duplicate interface issue # 2.7.1 skendric 01-26-2009 Support new get_date/get_time functions # 2.7.0 skendric 05-14-2008 Revamp mib2 module to parse ifDescr for # chassis/slot/port # 2.6.1 skendric 05-12-2008 Experiment with NME modules # 2.6.0 skendric 11-28-2007 Conserve idleness across device reboots # 2.5.8 skendric 11-02-2007 Add NME service modules # 2.5.7 skendric 03-21-2007 Stylistic mods # 2.5.6 skendric 12-29-2006 Add support for wsc45xx running CatOS # 2.5.5 skendric 11-29-2006 Add catalyst37xxStack support # 2.5.4 skendric 04-07-2005 Convert Object Values to OIDs # 2.5.3 skendric 11-05-2005 Upgrade to new FHCRC::VDOPS module structure # 2.5.2 skendric 05-31-2005 Streamline a few loops # 2.5.1 skendric 05-22-2005 Add support for Catalyst 4000 running IOS # 2.5.0 skendric 05-09-2005 Support Netops.pm-1.2 # 2.4.5 skendric 05-08-2005 Migrate to snmpbulkwalk # 2.4.1 skendric 08-16-2004 Fiddle with log format # 2.4.0 skendric 05-09-2004 Migrate common functions to Netops.pm # 2.3.0 skendric 11-30-2003 Remove entries which fail checks # 2.2.3 skendric 11-16-2003 Use Net::Ping::External # 2.2.2 skendric 05-20-2003 Fiddle with report text # 2.2.1 skendric 04-13-2003 Fix major bug in pack_it() # 2.2.0 skendric 03-28-2003 Numerous minor updates # 2.1.0 skendric 03-24-2003 Add support for HP ProCurve and Allied # Telesyn SwitchBlade # 2.0.0 skendric 03-02-2003 Complete re-write # 1.x.x rhood ... Many updates # 1.0.0 rhood 10-13-1993 First Version # # Source: http://www.skendric.com/device # # This software is available under the GNU GENERAL PUBLIC LICENSE, see # http://www.fsf.org/licenses/gpl.html # # This script takes the following approach: # - Parses the hosts table for a list of targets (or accepts a command- # line list) # - Assume that these are layer 2 packet forwarding devices # - Query each interface on the device for ifInOctets # - Store the result in an ASCII database # - Create a report showing which ports on which slots have not # seen a change in ifInOctets for 30 days # # Requirements: # -The target(s) must be pingable # # -PERL modules: the FHCRC::Netops collection # # # Assumptions: # # # Tested on: # - perl-5.12.2 # - net-snmp-5.5 # # # Instructions: # - Customize the script for your site: poke through the 'Header Stuff' # section and modify as appropriate # - Run manually and check for errors # - Once you've ironed out bugs at your site, run from cron every night # - Wait 30 days # - Look at the report file, validate # # # Caveats: # - I only store the last six bytes of ifInOctets. This number can # become large ... 64 bit ... and I don't feel like staring at a # large number in the data file. This means that it would be # possible for the octet counter on a port to change yet for this # script to not notice ... so long as the last six bytes remain # the same. This seems unlikely to me, so I won't worry about it. # # Known Bugs: # # # To do: # -Add support for SNMPv3 # # Begin script # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Header stuff # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Load modules use strict; use warnings; use feature 'say'; use feature 'switch'; use Carp qw(carp cluck croak confess); use Data::Dumper; use DateTime; use English qw( -no_match_vars ); use Getopt::Std; use List::MoreUtils qw(any); use Time::Concise; use FHCRC::Netops::HostTools 1.0.4; use FHCRC::Netops::NetopsTools 2.2.3; use FHCRC::Netops::NetopsData 1.4.0; use FHCRC::Netops::PingTools 1.1.7; use FHCRC::Netops::SNMPTools 1.5.3; use FHCRC::Netops::Utilities 1.4.4; # Declare global variables my %active_ports; # Number of ports per device which have seen # traffic within the past $idle_days my $active_ports; # Total number of ports across all devices # which have seen traffic within the past # $idle_days my $current_time; # Time now my $date; # The localdate at which this script is running my $data_file; # Where we store data my %disk; # Data read from disk and keyed by the triplet # "target slot port" my %disk_idle; # Boolean keyed by triplet declaring whether or # not this port was idle after the last run my $gone_days; # If we haven't heard from the device in # this many days, remove it from report # (but not from data file) my $idle_days; # Number of days after which we will flag # a port 'idle', assuming we have seen no # increase in ifInOctets my %idle_ports; # Number of ports per device which have not seen # traffic within the past $idle_days my $idle_ports; # Total number of ports across all devices # which have not seen traffic within the past # $idle_days my $idle_time; # $idle_days converted into seconds since epoch my %live; # Data keyed by the triplet "target slot port" # and gathered during this pass my $log_file; # Where to log total port counts my %merged; # %live merged with %disk my %merged_idle; # Boolean keyed by triplet declaring whether or # not this port is idle now my $stale_window; # If we haven't heard from device for this many # seconds, we no longer trust our saved data, and # we consider set all ports to be active my $summary_file; # Where to write the summary my $time; # The localtime at which this script is running my %total_ports; # Number of ports per device my $total_ports; # Total number of ports across all devices # Define global variables # Debug definitions # 15 = Detailed idle # 14 = Idle # 13 = Merged # 12 = Live # 11 = Disk # 10 = ifInOctets # 9 = Database SELECT operations # 8 = Per IP/MAC/Port processing # 7 = Database INSERT/UPDATE/DELETE # 6 = Dump SNMP var # 5 = Dump snmp_packets # 4 = Grody: print big var # 3 = Verbose: print mid var # 2 = Simple: print small var # 1 = Basic: subroutine trace # 0 = Disable debugging $program_name = 'porter-report'; $usage = 'Usage: porter-report -s {yes|no} [-d {integer}] [-r] [-a | -e {expr} | -f {filename} | target1 target2 target3 ...]'; $version = '3.1.0'; # Grab date and time $date = get_date(); $time = get_time(); # Files $data_file = '/home/netops/rpts/porter-report.data'; $log_file = '/home/netops/logs/porter-report.log'; $report_file = '/home/netops/rpts/porter-report.txt'; $summary_file = '/home/netops/rpts/porter-summary.txt'; # Timing $idle_days = 30; $gone_days = 20; $current_time = time(); $idle_time = $current_time - $idle_days*24*60*60; $stale_window = 259200; # Forty-eight hours # Grab arguments getopts('ad:e:f:rs:', \%option); @target = @ARGV; # Set mode if ($option{r}) { $mode = 'report' } elsif (-t STDIN) { $mode = 'interactive' } else { $mode = 'batch' } # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Begin Main Program # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= { check_args(); # Check arguments read_config(); # Read Netops config file compile_mibs(); # Compile MIB files build_target(); # Build list of targets target_check(); # Look for errors in @target basic_info(); # Gather information sanity_check(); # Sanity check read_data(); # Read data file gather_ifinoctets(); # Gather ifInOctets from devices compare_counts(); # Update our notion of port counters identify_idle_ports(); # Figure out which ports are idle write_merged(); # Write new data file write_log(); # Log total number of ports write_report(); # Write the report file write_summary(); # Write the summary file } # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # End Main Program # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= ######################################################################## # Figure out keys ######################################################################## sub by_keys { my ($a_host, $a_slot, $a_port, $b_host, $b_slot, $b_port); ($a_host, $a_slot, $a_port) = split ($SPACE, $a); ($b_host, $b_slot, $b_port) = split ($SPACE, $b); if (($a_host cmp $b_host) != 0) { $a_host cmp $b_host; } elsif (($a_slot <=> $b_slot) != 0) { $a_slot <=> $b_slot; } elsif (($a_port <=> $b_port) != 0) { $a_port <=> $b_port; } } ######################################################################## # Compare the current counts (%live) to those from the data file (%disk) # and store the merged result in %merged. # After this routine runs, the information we've gathered and stored in # %live has been merged into %merged ######################################################################## sub compare_counts { # $live_count: ifInOctets gathered live # $disk_count: ifInOctets from disk file # $disk_ctime: Last time ifInOctets changed # $disk_vtime: Last time we asked device for ifInOctets # $event: Used for debug output # $name: Host from data file # $port: Port from data file # $slot: Slot from data file # Debug trace trace_location('begin') if $debug; # Notify operator print_it('Comparing counts...'); # Walk through %live comparing the octet counter we just acquired with # the counter stored on disk for my $triple (keys %live) { my ($name, $slot, $port, $disk_count, $disk_ctime, $disk_vtime, $event, $live_count); # Get the current data $live_count = $live{$triple}; # Grab an entry from the disk file if (exists $disk{$triple}) { ($name, $slot, $port, $disk_count, $disk_ctime, $disk_vtime) = split ($SPACE, $disk{$triple}); } # Compare if (not exists $disk{$triple} ) { # New port $event = 'New Port'; $merged{$triple} = "$triple $live_count $current_time $current_time"; } elsif ($live_count == $disk_count) { # No change in byte count $event = 'No Change'; $merged{$triple} = "$triple $disk_count $disk_ctime $current_time"; } elsif ($live_count != $disk_count) { # Recent activity $event = 'Activity'; $merged{$triple} = "$triple $live_count $current_time $current_time"; } else { say 'Should not reach here in compare_counts'; } # Debug info say "$event: merged{$triple} = $merged{$triple}" if $debug == 14; } # OK, that's good: %merged now contains an updated view of everything # captured in %live. However, what about devices for which %disk # contains information, but %live does not? i.e. devices which didn't # respond during this pass: let's handle these for my $triple (keys %disk) { my ($name, $slot, $port, $disk_count, $disk_ctime, $disk_vtime, $event); # Grab an entry from the disk file ($name, $slot, $port, $disk_count, $disk_ctime, $disk_vtime) = split ($SPACE, $disk{$triple}); # Compare if (not exists $live{$triple}) { # Port which didn't respond $event = 'Unresponsive'; $merged{$triple} = "$triple $disk_count $disk_ctime $disk_vtime"; say "$event: merged{$triple} = $merged{$triple}" if $debug == 14; } } # Make things look pretty say('') if $mode eq 'interactive'; # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Gather ifInOctets from each device. Use a case statement to pick # the appropriate data gathering subroutine. At the moment, I only have # two: Cisco and HP ProCurve. There's a chance that the Cisco one # will work for switches from other vendors, so I make it the default ######################################################################## sub gather_ifinoctets { # Debug trace trace_location('begin') if $debug; # Walk targets for my $target (@target) { say "Processing $target" if $debug > 1; # Pick data gathering routine given ($manufacturer{$target}) { when ('HP') { gather_ifinoctets_procurve($target) } default { gather_ifinoctets_cisco($target) } } # Entertain the operator print $BANG if $mode eq 'interactive'; } # Make things look pretty say "\n" if $mode eq 'interactive'; # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Gather ifInOctets from each Cisco device ######################################################################## sub gather_ifinoctets_cisco { my $host = shift; my %if_descr; my $ifDescr_ref; my $ifInOctets_ref; # Debug trace trace_location('begin') if $debug > 1; # Acquire ifDescr say 'Walking ifDescr' if $debug > 2; $ifDescr_ref = snmpWalk( {host => $host, oid => $ifDescr_oid} ); # Build if_descr for my $varbind (@{$ifDescr_ref}) { my ($iid, $type, $val); $iid = $varbind->{iid}; $type = $varbind->{type}; $val = $varbind->{val}; $val =~ s/"//g; # Debug info say "$iid, $val, $type" if $debug == 10; # Build data structure $if_descr{$iid} = $val; } # Acquire ifInOctets say 'Walking ifInOctets...' if $debug > 2; $ifInOctets_ref = snmpWalk( {host => $host, oid => 'ifInOctets'} ); # Build %live data structure PORT: for my $varbind (@{$ifInOctets_ref}) { my ($chassis, $slot, $port); my ($count, $ifDescr, $iid, $num_dots, $num_slashes, $type, $triple); # Deconstruct varbind $iid = $varbind->{iid}; $count = $varbind->{val}; $type = $varbind->{type}; next PORT unless (defined $count and defined $iid); # Debug info say "$iid, $count, $type" if $debug == 10; # Skip ports unless they are Ethernet $ifDescr = $if_descr{$iid}; unless ($ifDescr =~ /ethernet/i) { say "Skipping $ifDescr because it isn't Ethernet" if $debug == 10; next PORT; } # Figure out the number of slashes in the name $num_slashes = ($ifDescr =~ tr/\///); $num_slashes //= 0; # Skip ports with a dot in their name $num_dots = ($ifDescr =~ tr/\.//); if ($num_dots > 0) { log_it("For $host, Skipping $ifDescr because name contains a dot"); next PORT; } # Extract chassis, slot, and port. Currently, I don't use chassis given ($num_slashes) { when (0) { $slot = 0; $port = $ifDescr =~ /(\d+)$/; #$port = $ifDescr; } when (1) { ($slot, $port) = $ifDescr =~ /(\d+)\/(\d+)$/; } when (2) { ($chassis, $slot, $port) = $ifDescr =~ /(\d+)\/(\d+)\/(\d+)$/; } default { log_it("For $host, cannot find slot/port in $ifDescr"); next PORT; } } # Build triple given ($chassis) { when (defined) { $triple = "$host $chassis $port" } when (undef) { $triple = "$host $slot $port" } } say "triple = $triple" if $debug == 10; $live{$triple} = substr($count, -6, 6); # Debug info say "live{$triple} = $live{$triple}" if $debug == 12; } # End 'PORT' # Debug trace trace_location('end') if $debug > 1; return 1; } ######################################################################## # Gather ifInOctets from each HP ProCurve ######################################################################## sub gather_ifinoctets_procurve { my %alphabet; my $host = shift; my %if_descr; my $ifDescr_ref; my $ifInOctets_ref; my $num; # Debug trace trace_location('begin') if $debug > 1; # Build hash mapping letters to numbers $num = 1; for my $letter ('A' .. 'Z') { $alphabet{$letter} = $num; $num++; } # Acquire ifDescr say 'Walking ifDescr' if $debug > 2; $ifDescr_ref = snmpWalk( {host => $host, oid => $ifDescr_oid} ); # Build if_descr for my $varbind (@{$ifDescr_ref}) { my ($iid, $type, $val); $iid = $varbind->{iid}; $type = $varbind->{type}; $val = $varbind->{val}; # Debug info say "$iid, $val, $type" if $debug == 10; # Build data structure $if_descr{$iid} = $val; } # Acquire ifInOctets say 'Walking ifInOctets...' if $debug > 2; $ifInOctets_ref = snmpWalk( {host => $host, oid => 'ifInOctets'} ); # Build %live data structure PORT: for my $varbind (@{$ifInOctets_ref}) { my ($slot, $port); my ($count, $ifDescr, $iid, $type, $triple); # Deconstruct varbind $iid = $varbind->{iid}; $count = $varbind->{val}; $type = $varbind->{type}; next PORT unless (defined $count and defined $iid); # Debug info say "$iid, $count, $type" if $debug == 10; # Skip VLAN ports $ifDescr = $if_descr{$iid}; if ($ifDescr =~ /VLAN/) { say "Skipping $ifDescr because it is a VLAN port" if $debug == 10; next PORT; } # Grab slot and port ($slot, $port) = ($ifDescr =~ /([A-Z])(\d+)/); unless (defined $slot and defined $port) { say "Skipping $ifDescr because I cannot identify slot/port" if $debug == 10; next PORT; } unless (defined $alphabet{$slot}) { say "Skipping $ifDescr because I don't understand slot/port" if $debug == 10; next PORT; } # Translate slot $slot = $alphabet{$slot}; # Build triple $triple = "$host $slot $port"; say "triple = $triple" if $debug == 10; $live{$triple} = substr($count, -6, 6); # Debug info say "live{$triple} = $live{$triple}" if $debug == 12; } # End 'PORT' # Debug trace trace_location('end') if $debug > 1; return 1; } ######################################################################## # Identify idle ports, incrementing both idle and active port counters # as we go ######################################################################## sub identify_idle_ports { my $count; my $change_time; my $port; my $slot; my $target; my $verify_time; # Debug trace trace_location('begin') if $debug; # Notify operator print_it('Counting ports...'); # Initialize variables (only relevant in debugging cases) $active_ports = 0; $idle_ports = 0; $total_ports = 0; # Walk through data and count ports for my $triple (sort keys %merged) { say "Processing $triple" if $debug > 12; # Grab next entry ($target, $slot, $port, $count, $change_time, $verify_time) = split($SPACE, $merged{$triple}); # Initialize variables $active_ports{$target} = 0 unless exists $active_ports{$target}; $idle_ports{$target} = 0 unless exists $idle_ports{$target}; $total_ports{$target} = 0 unless exists $total_ports{$target}; # Increment total_ports $total_ports{$target}++; $total_ports++; # Count idle ports. If this port hasn't seen traffic in $idle_days, # count it as idle if (old_enough($change_time, $verify_time)) { $merged_idle{$triple} = 1; $idle_ports{$target}++; $idle_ports++; if ($debug > 14) { if ($verify_time - $change_time > 1261440000) { # Greater than 40 years say " $triple is idle because it is a recently added port"; } else { say " $triple is idle because of time"; } } } # How about if the device has rebooted recently and reset ifInOctets to # zero? (Cat3KIOS devices reset to 64, don't know why.) Since # the ifInOctets counter has changed, we would normally consider this # port active. But perhaps it isn't -- perhaps it has been idle for # years. # OK, if the current counter reader 0 (or 64) and if we flagged the port # as idle the last time we ran, consider it idle now, despite the delta # in ifInOctets elsif ($disk_idle{$triple}) { if ($count == 0 or $count == 64) { $merged_idle{$triple} = 1; $idle_ports{$target}++; $idle_ports++; say " $triple was idle, count = $count: still idle" if $debug == 14; } } # Let's say we just added a module. All the ports on it are probably # idle (we haven't had time yet to plug anything in). But, we don't # have any history on these ports, so normally we would be conservative # and flag them as active. # If this port is new and if the current count is 0 (or 64), flag it # as idle. Plus, lie to %merged: tell it this port has been idle since # the epoch elsif (not defined $disk_idle{$triple}) { if ($count == 0 or $count == 64) { $merged_idle{$triple} = 1; $merged{$triple} = join $SPACE, $target, $slot, $port, $count, 0, $verify_time; $idle_ports{$target}++; $idle_ports++; say " $triple is new; consider it idle" if $debug == 14; } } # Count active ports else { $merged_idle{$triple} = 0; $active_ports{$target}++; $active_ports++; say " $triple is active" if $debug == 14; } } # Debug info if ($debug > 1) { printf "%-20s %-12s %-10s %-11s\n", 'Target', 'Active Ports', 'Idle Ports', 'Total Ports'; say '-------------------- ------------ ---------- -----------'; for my $target (sort keys %total_ports) { printf "%-20s %-12s %-10s %-11s\n", $target, $active_ports{$target}, $idle_ports{$target}, $total_ports{$target}; } say "\nSummary:"; say " active_ports = $active_ports; idle_ports = $idle_ports; total_ports = $total_ports"; } # Make things look pretty say('') if $mode eq 'interactive'; # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Am I old enough? Requires two integers, both expressed as the time # in seconds since the epoch. The first integer specifies when ifInOctets # on this port last changed. The second integer specifies when we last # acquired ifInOctets from this port ######################################################################## sub old_enough { my ($a, $b) = @_; # Stale data: we haven't recorded ifInOctets from this port for # $stale_window, so we don't trust this data anymore. Consider the # port active if ( $b < ($current_time - $stale_window) ) { say ' stale' if $debug > 13; return 0 } # The counter hasn't changed for $idle_time. Consider this port idle elsif ($a <= $idle_time) { say ' old enough' if $debug > 13; return 1; } # The counter has changed recently (less than $idle_time ago). Consider # this port active elsif ($a > $idle_time) { say ' too young' if $debug > 13; return 0; } } ######################################################################## # Pack data ######################################################################## sub pack_it { my @ports = @_; my ($i, $iN, $iZ, @zero, @new, $string); # Debug trace trace_location('begin') if $debug == 15; # If we have work to do, do it; else return white space if (@ports > 0) { say " pack_it receives these idle ports @ports" if $debug > 13; # Convert interior numbers in a consecutive run to 0 for ($i = 0; $i < $#ports; ++$i) { if ($ports[$i-1] != $ports[$i]-1) { $zero[$i] = $ports[$i] } elsif ($ports[$i+1] != $ports[$i]+1) { $zero[$i] = $ports[$i] } else { $zero[$i] = 0 } } $zero[0] = $ports[0]; $zero[$#ports] = $ports[$#ports]; # Eliminate consecutive 0s $new[0] = $zero[0]; for ($iZ = 1, $iN = 1; $iZ <= $#zero; ++$iZ, ++$iN) { if ($zero[$iZ] != 0) { $new[$iN] = $zero[$iZ] } elsif ($zero[$iZ] != $new[$iN-1]) { $new[$iN] = $zero[$iZ] } else { --$iN } } # Convert single 0s to dashes $string = $new[0]; for ($i = 1; $i <= $#new; ++$i) { if (($new[$i] != 0) and ($new[$i-1] != 0)) { $string .= $COMMA } if ( $new[$i] == 0) { $string .= $DASH } else { $string .= $new[$i] } } } else { say " receiving no ports" if $debug > 13; $string = $SPACE; } # Debug info say " returning \"" . $string . "\" from pack_it" if $debug > 13; # Debug trace trace_location('end') if $debug == 15; return $string; } ######################################################################## # Read in the data file, figure out which ports were idle ######################################################################## sub read_data { # Debug trace trace_location('begin') if $debug; # Notify operator print_it("Reading data file...\n"); # Open data file open my $data, '<', $data_file or die "Cannot open $data_file: $!"; # Walk through data file LINE: while (my $line = <$data>) { my ($name, $slot, $port, $count, $change_time, $verify_time, $idle_time, $triple); # Skip blank lines next LINE if $line =~ /^\n$/; # Skip comments next LINE if $line =~ /^#/; # Read a line of data ($name, $slot, $port, $count, $change_time, $verify_time, $idle_time) = split(/\s+/, $line); # Sanity check if (not defined $name or not defined $slot or not defined $port or not defined $count or not defined $change_time or not defined $verify_time or not defined $idle_time) { log_it("Mangled line: $line"); next LINE; } if ($name eq $EMPTY_STR or $slot eq $EMPTY_STR or $port eq $EMPTY_STR or $count eq $EMPTY_STR or $count eq $EMPTY_STR or $change_time eq $EMPTY_STR or $verify_time eq $EMPTY_STR or $idle_time eq $EMPTY_STR) { log_it("Mangled line: $line"); next LINE; } # Define triple $triple = "$name $slot $port"; # Build data structure $disk{$triple} = "$triple $count $change_time $verify_time"; # Debug info say "disk{$triple} = $disk{$triple}" if $debug == 11; # Figure out which ports were idle if (old_enough($change_time, $verify_time)) { $disk_idle{$triple} = 1; } else { $disk_idle{$triple} = 0; } } close $data or warn "Cannot close $data_file: $!"; # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Sanity check ######################################################################## sub sanity_check { my @remove; my $sys_ob_id; # sysObjectID # Debug trace trace_location('begin') if $debug; # Notify operator print_it('Sanity check...'); # Verify that we have a data file die "Cannot touch $data_file: $!" unless touch_file($data_file, 0664); # Verify that the data file is readable/writeable die "Cannot read $data_file: $!" unless -r $data_file; die "Cannot write $data_file: $!" unless -w $data_file; # Make things look pretty say "\n" if $mode eq 'interactive'; # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Write new data file ######################################################################## sub write_merged { my $handle; # File handle pointing to data file my $now; # Debug trace trace_location('begin') if $debug; # Notify operator print_it('Writing data...'); # Check for blanks unless ($dome) { say 'Running in test mode: skipping data file update'; return 1; } # Find time $now = time; # Open data file unless (open $handle, '>', $data_file) { print_it("Cannot open report file $data_file: $!"); return 0; } print {$handle} < ($now - $gone_days*24*60*60); # Calculate idle time if (not defined $verify_time or not defined $change_time) { $idle_time = $QUERY; } else { $idle_time = $verify_time - $change_time; if ($idle_time == 0) { $idle_string = 0; } elsif ($idle_time > 0) { $idle_string = to_concise($idle_time); } else { say "Bizarre idle_time for $triple: $idle_time"; $idle_string = $idle_time; } } # Write this line printf {$handle} "%-17s %4s %4s %6s %11s %11s %15s\n", $name, $slot, $port, $count, $change_time, $verify_time, $idle_string; } close $handle or warn "Cannot close $data_file: $!"; # Make things look pretty say('') if $mode eq 'interactive'; # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Write summary data to a log file ######################################################################## sub write_log { my $smallTime; # Debug trace trace_location('begin') if $debug; # Notify operator print_it('Writing log...'); # Chop up $time ($smallTime) = ($time =~ /^(\d\d:\d\d)/); # Write data if ($dome) { if (open my $log, '>>', $log_file) { print {$log} "$date\t$smallTime\t$active_ports\t$total_ports\n"; close $log or warn "Cannot close $log_file: $!"; } else { warn "Cannot open $log_file: $!"; } } # Make things look pretty say('') if $mode eq 'interactive'; # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Print report ######################################################################## sub write_report { my $cHost; # 'Current host': host whose entries in the # data file we are examining my $cSlot; # 'Current slot': slot on $cHost whose entries # in the data file we are examining my $first = 1; # Hack for identifying first host in report my $fSlot; # 'First Slot': first slot in current host # (generally, though not always, equal to "1") my $handle; # File handle my $idle; # String returned from pack_it(); contains # the contents of @cHosts in 'packed' format my @iPorts; # 'Idle Ports': idle ports on $name # Parameters from current line in data file my ($name, $slot, $port, $count, $change_time, $verify_time, $idle_time); # Debug trace trace_location('begin') if $debug; # Notify operator print_it('Writing report...'); # Initialize variables $cHost = $cSlot = $fSlot = $idle = $EMPTY_STR; # Open report file unless (open $handle, '>', $report_file) { print_it("Cannot open $report_file: $!"); return 0; } print {$handle} < 13; push @iPorts, $port if $merged_idle{$triple} } # Same host and same slot as the last port we processed elsif ($cHost eq $name and $cSlot == $slot) { say " working $cSlot:$port" if $debug > 13; push @iPorts, $port if $merged_idle{$triple} } # Same host but new slot. Print previous slot's info and then examine # this port elsif ($cHost eq $name and $cSlot ne $slot) { # Send pack_it the list of idle ports $idle = pack_it(@iPorts); # If this is the first line for $cHost, print $cHost; otherwise, don't if ($cSlot == $fSlot ) { printf {$handle} "%-17s %-4s %-57s\n", $cHost, $cSlot, $idle; } else { printf {$handle} " %-4s %-57s\n", $cSlot, $idle; } # OK, now that we've printed the previous slot, let's process this # first port on this new slot $cSlot = $slot ; undef @iPorts; say " starting new slot $cSlot:$port" if $debug > 13; push @iPorts, $port if $merged_idle{$triple}; } # New host: print the previous slot's info elsif ($cHost ne $name) { # Send pack_it the list of idle ports $idle = pack_it(@iPorts); # If this is the first line for $cHost, print $cHost; otherwise, don't # (This must be the first and only slot for $cHost.) if ($cSlot == $fSlot) { printf {$handle} "%-17s %-4s %-57s\n", $cHost, $cSlot, $idle; } else { # don't print $cHost printf {$handle} " %-4s %-57s\n", $cSlot, $idle; } printf {$handle} "\n"; say " finishing $cHost:$cSlot --> $idle" if $debug > 13; # OK, now that we've printed the last slot on the previous host, # let's process the first port on the first slot of this new host $cHost = $name; $fSlot = $cSlot = $slot; undef @iPorts; say "Starting new host $cHost:$slot:$port" if $debug > 13; push @iPorts, $port if $merged_idle{$triple}; } # We should never reach this case else { given ($debug) { when (defined) { say "Problem with $merged{$triple}" } default { log_it("Problem with $merged{$triple}") } } } } # End 'walk through merged file' # Print the last slot on the last host # Send pack_it the list of idle ports $idle = pack_it(@iPorts); # If this is the first line for $cHost, print $cHost; otherwise, don't if (defined $cSlot and defined $fSlot and $cSlot ne $EMPTY_STR and $fSlot ne $EMPTY_STR) { if ($cSlot == $fSlot) { printf {$handle} "%-17s %-4s %-59s\n", $cHost, $cSlot, $idle; } else { printf {$handle} " %-4s %-59s\n", $cSlot, $idle; } say " finishing $cHost:$cSlot --> $idle" if $debug > 13; } say 'Finishing report' if $debug ; close $handle or warn "Cannot close $report_file: $!"; # Make things look pretty say('') if $mode eq 'interactive'; # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Print summary ######################################################################## sub write_summary { my $active; # Percent of active ports on this device my $handle; # File handle my $idle; # Percent of idle ports on this device my $per = "%"; # Percent sign my $total; # Sum of active_ports and idle_ports on that host # Debug trace trace_location('begin') if $debug; # Notify operator print_it('Writing summary...'); # Open report file unless (open $handle, '>', $summary_file) { print_it("Cannot open $summary_file: $!"); return 0; } print {$handle} <