|
#!/usr/local/bin/perl
|
|
#
|
|
# Minimal monitor_disk_space
|
|
#
|
|
# Copyright 1992 - 1997 Stephen O. Lidie. All rights reserved.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify\
|
|
it under
|
|
# the same terms as Perl itself.
|
|
#
|
|
# A TCP client/server to monitor disk space and alert operators when a\
|
|
machine
|
|
# is running low. Each monitored machine runs the server
|
|
# `monitor_disk_space_daemon' which transmits a `df' output when reque\
|
|
sted by
|
|
# this client. Results are summarized and displayed as a colored bar \
|
|
graph.
|
|
#
|
|
# Typically `monitor_disk_space' (monds) is used in a central computin\
|
|
g site
|
|
# where a small number of critical servers must remain operational.
|
|
#
|
|
# monds maintains a memory-resident database containing historical dis\
|
|
k space
|
|
# usage information as an associative array indexed by hostname, which\
|
|
is used
|
|
# to see how filesystem usage varies in time.
|
|
#
|
|
# Use monitor_disk_space -full_help for full help.
|
|
|
|
require 5.002; # need Perl at least at this lev\
|
|
el
|
|
use English; # legible variable names
|
|
use strict; # be a pure as possible
|
|
|
|
# A BEGIN constructor is executed as soon as it's defined, so we
|
|
# prepend the directory name that contains our own class modules and
|
|
# `require' files to Perl's @INC variable so they can be located.
|
|
#
|
|
# Multiple BEGINs are executed in order, so once Perl knows where to f\
|
|
ind
|
|
# our private code we can:
|
|
#
|
|
# - Do command line parsing first so we don't have to load all of
|
|
# X/Tk just for a -help request.
|
|
# - Create the Perl/Tk MainWindow immediately and do Tk stuff. Since\
|
|
monds
|
|
# takes some time to load, a status display consisting of two Label\
|
|
|
|
# widgets keeps us occupied by displaying how initialization is
|
|
# progressing. The first label is an unchanging string while the s\
|
|
econd
|
|
# dynamically displays our state.
|
|
sub update_status;
|
|
my $LIBDIR; # pathname of monds auxiliary files\
|
|
directory
|
|
my $MW; # Tk Main Window
|
|
my $FS; # master grid frame for filesys\
|
|
tems
|
|
my $STATUS; # label widgets for monitoring the \
|
|
monds ...
|
|
my $STATUS_L; # ... initialization status
|
|
my $VERSION; # monds version number
|
|
use vars qw(@PDT @MM %OPT); # command line information
|
|
|
|
BEGIN {
|
|
$LIBDIR = "./lib";
|
|
unshift @INC, $LIBDIR;
|
|
}
|
|
BEGIN {
|
|
use Getopt::EvaP; # Evaluate Parameters
|
|
require "inied.pl"; # initialize Evaluate Parameter\
|
|
s data
|
|
EvaP \@PDT, \@MM, \%OPT; # evaluate command line parameters
|
|
$Getopt::EvaP::evap_embed = 1; # make Evaluate Parameters embeddab\
|
|
le now
|
|
use Tk; # define Tk objects and methods
|
|
$VERSION = '0.1';
|
|
$MW = MainWindow->new;
|
|
$MW->positionfrom('user');
|
|
$MW->geometry('+100+100');
|
|
$MW->title("Monitor Disk Space $VERSION");
|
|
$MW->iconname('monds');
|
|
$MW->iconbitmap("\@$LIBDIR/monds.xbm");
|
|
|
|
$STATUS_L = $MW->Label(-text => 'Initializing', -fg => 'b\
|
|
lue');
|
|
$STATUS_L->grid(-row => 0, -column => 0, -sticky => 'w\
|
|
');
|
|
$STATUS = $MW->Label(qw(-width 40 -anchor w -foreground blue),
|
|
-text => "monds $VERSION ...");
|
|
$STATUS->grid(-row => 0, -column => 1, -sticky => 'w')\
|
|
;
|
|
$MW->update;
|
|
}
|
|
|
|
update_status 'Class Modules';
|
|
use Carp; # better traceback
|
|
use Tk::Dialog; # Dialog objects
|
|
use Tk::ErrorDialog; # errordialog object
|
|
use FileHandle; # handle methods
|
|
use Filesystem; # Filesystem objects
|
|
update_status 'Require Files';
|
|
require "ctime.pl"; # time conversion routines
|
|
use Socket; # socket.h defines
|
|
require "stat.pl"; # file status ordinals
|
|
$::ST_MTIME = $::ST_MTIME; # suppress -w message
|
|
|
|
update_status 'Forward Declarations';
|
|
use subs qw(abort analyze_df_data check_configuration_files
|
|
construct_main_widgets display_poll_results end_monds flas\
|
|
h_widget
|
|
heuristics_say_so initialize op_msg output poll_clients re\
|
|
ad_efcf
|
|
start_tcp_clients stop_tcp_clients write_log_file);
|
|
|
|
my %CHILD; # asynchronous process information
|
|
my @COLORS; # highlight different severity leve\
|
|
ls
|
|
my $DDA; # monds About dialog
|
|
my %EXCLUDE; # lists of directories not to moni\
|
|
tior
|
|
my @FS_ATTENTION_DATA; # list of output lines with aler\
|
|
t information
|
|
my %HOSTS; # lists of `df' samples for monitore\
|
|
d hosts
|
|
my $LAST_EF_MTIME; # last modification time of exclude_\
|
|
FS file
|
|
my $MAX_DF_SAMPLES; # keep this many `df's samples per \
|
|
host
|
|
my $OLD_TIME; # time of moon's last phase chang\
|
|
e
|
|
my @PATTERNS; # list of moon phase change bitma\
|
|
ps
|
|
my $PATTERN_MODULUS; # phase change counter
|
|
my $PORT; # TCP port used by monds client/serve\
|
|
r tasks
|
|
my $READ_BITS; # select() socket read bitmap ma\
|
|
sk
|
|
my %SEVERITY; # hash of filesystem severity lev\
|
|
el cutoffs
|
|
my $THROB; # label widget with throbbing moon
|
|
|
|
# Main.
|
|
|
|
initialize; # preset monitor_disk_space
|
|
$MW->repeat($OPT{poll_interval}, \&poll_clients); # asynchronous TC\
|
|
P/IP polling
|
|
MainLoop; # process Perl/Tk X events
|
|
|
|
sub abort {
|
|
|
|
# Special `die' so we zap kids and don't trash Tk data structures.
|
|
|
|
my($msg) = @ARG;
|
|
|
|
carp "monds: $msg failed: $OS_ERROR";
|
|
end_monds;
|
|
} # end abort
|
|
sub analyze_df_data {
|
|
# Analyze the returned `df' data now:
|
|
#
|
|
# - Maintain the list of running samples for later (simple) heuris\
|
|
tics.
|
|
# - If load-leveling is enabled for this host/filesystem and the
|
|
# percentage used exceeds the threshold then initiate load-level\
|
|
ing.
|
|
# - Classify which severity level the filesystem belongs in and up\
|
|
date the
|
|
# %alerts hash, if required.
|
|
my($fs_data_ref, $alerts_ref, $them) = @ARG;
|
|
# Append new `df' samples to end of filesystem list.
|
|
my(@sample_list) = split /%/, $HOSTS{$them} if defined $HOSTS{$the\
|
|
m};
|
|
my $sample_count = @sample_list;
|
|
shift @sample_list if $sample_count >= $MAX_DF_SAMPLES;
|
|
push @sample_list, join(':', @$fs_data_ref);
|
|
$HOSTS{$them} = join '%', @sample_list;
|
|
my($fs, $filesystem, $used, $llfs, $cushion, $cushion_fs, $severit\
|
|
y_level);
|
|
|
|
PROCESS_DF_SAMPLES:
|
|
foreach $fs (@$fs_data_ref) {
|
|
($filesystem, $used) = ($fs =~ /(.*)=(.*)/);
|
|
|
|
# If possible, categorize this filesystem's severity level and\
|
|
add to
|
|
# the %alerts hash. Remember, the value of an %alerts hash en\
|
|
try is
|
|
# a reference to a list.
|
|
|
|
DETERMINE_SEVERITY:
|
|
foreach $severity_level (reverse sort(keys %SEVERITY) ) {
|
|
if ($used >= $severity_level) {
|
|
push(@{$alerts_ref->{$severity_level}},
|
|
sprintf("%s %s %s %d%%\n",
|
|
substr(&ctime(time), 0, 19), $them, $filesystem, $us\
|
|
ed))
|
|
if heuristics_say_so $severity_level, $filesystem, $\
|
|
used,
|
|
$sample_count, @sample_list;
|
|
last DETERMINE_SEVERITY;
|
|
} # ifend
|
|
} # forend DETERMINE_SEVERITY
|
|
} # forend PROCESS_DF_SAMPLES
|
|
} # end analyze_df_data
|
|
sub check_configuration_files {
|
|
# Re-read any configuration files that may have been updated on-th\
|
|
e-fly.
|
|
my $mtime = (stat $OPT{exclude_filesystem_configuration_file})
|
|
[$::ST_MTIME];
|
|
read_efcf if $mtime != $LAST_EF_MTIME;
|
|
$LAST_EF_MTIME = $mtime;
|
|
|
|
} # end check_configuration_files
|
|
|
|
sub construct_main_widgets {
|
|
|
|
# Main window with pull-down menus, a flasher and bar graphs (dyna\
|
|
mically
|
|
# created/destroyed Filesystem objects). We have three grid maste\
|
|
rs: the
|
|
# menubar frame, the filesystems frame and the percentage frame.
|
|
|
|
update_status 'Main Widgets: menus';
|
|
$MW->option('add', '*highlightThickness' => 0);
|
|
my $mb = $MW->Frame(qw(-relief raised -borderwidth 1));
|
|
my $mbf = $mb->Menubutton(qw(-text File -relief raised -bd 1));
|
|
$mbf->command(-label => 'Close', -command => [$MW => '\
|
|
iconify']);
|
|
$mbf->separator;
|
|
$mbf->command(-label => 'Quit', -command => \&end_monds);
|
|
my $mbh = $mb->Menubutton(qw(-text Help -relief raised -bd 1));
|
|
$mbh->command(-label => 'About');
|
|
|
|
update_status 'Main Widgets: moon';
|
|
$THROB = $mb->Label(
|
|
-bitmap => "\@${LIBDIR}/$PATTERNS[$PATTERN_MODULUS % 2]",
|
|
);
|
|
$FS = $MW->Frame; # Filesystem grid master
|
|
my $percentage = $MW->Label(-relief => 'ridge', -text =>
|
|
' 90 92 \
|
|
' .
|
|
'94 96 98 100');
|
|
# Global dialogs.
|
|
update_status 'Main Widgets: dialogs';
|
|
$DDA = $MW->Dialog(-title => 'About monds');
|
|
$mbh->entryconfigure('About', -command => [$DDA => 'Show'\
|
|
]);
|
|
$DDA->configure(
|
|
-wraplength => '5i',
|
|
-text => "Monitor Disk Space $VERSION. 97/01/05\n\\
|
|
nExamine " .
|
|
"the log file and display bar graphs of filesys\
|
|
tems " .
|
|
"close to capacity. The percentage full is " .
|
|
"indicated by the scale at the bottom of the " \
|
|
.
|
|
"display and the color of the bar. The flashin\
|
|
g " .
|
|
"moon rotates 180 degrees everytime the log fil\
|
|
e " .
|
|
"is updated, typically once a minute.\n\nThe en\
|
|
try " .
|
|
"widgets containing textual information can be \
|
|
" .
|
|
"scrolled by holding down button 2 on the point\
|
|
ing " .
|
|
"device.\n\nSometimes important operator messag\
|
|
es " .
|
|
"could appear.\n\nYell for an analyst when you \
|
|
" .
|
|
"start seeing red.",
|
|
);
|
|
|
|
update_status 'Main Widgets: heartbeat';
|
|
|
|
flash_widget $THROB, -background, 'azure',
|
|
($THROB->configure(-background))[4], 1000;
|
|
if ($MW->depth > 1) {
|
|
my $pixmap = $MW->Pixmap('-file' => "$LIBDIR/monds.xpm")\
|
|
;
|
|
$MW->Icon(-image => $pixmap);
|
|
}
|
|
# Kill status widgets and realize the main monds display.
|
|
$STATUS_L->destroy;
|
|
$STATUS->destroy;
|
|
|
|
$mb->grid(-sticky => 'ew'); # menubar grid master
|
|
$mbf->grid(-row => 0, -column => 0);
|
|
$THROB->grid(-row => 0, -column => 1);
|
|
$mb->gridColumnconfigure(1, -weight => 1);
|
|
$mbh->grid(-row => 0, -column => 2);
|
|
$FS->grid; # filesystems grid master
|
|
$percentage->grid; # percentage grid master
|
|
|
|
} # end construct_main_widgets
|
|
|
|
sub display_poll_results {
|
|
|
|
# Parse the @FS_ATTENTION_DATA list, derived from the monds log fi\
|
|
le, and
|
|
# create a dynamic, graphical display of disk space utilization. \
|
|
Using
|
|
# this list we:
|
|
#
|
|
# - Alter the phase of the moon.
|
|
# - Create new Filesystem objects.
|
|
# - Update date/time stamp, percent utilization and bar graphs of
|
|
# Filesystem objects.
|
|
# - Destroy Filesystem objects.
|
|
|
|
my($line, $fsr);
|
|
|
|
my(@active_Filesystem_objects) = ();
|
|
my $check_time = 1;
|
|
foreach $line (@FS_ATTENTION_DATA) {
|
|
chomp $line;
|
|
my($d1, $d2, $d3, $d4, $host, $fs, $pc) = split ' ', $line;
|
|
if ($check_time) { # rotate the moon if new `df' data h\
|
|
as arrived
|
|
$check_time = 0;
|
|
if ($OLD_TIME ne "$d1$d2$d3$d4") {
|
|
$THROB->configure(
|
|
-bitmap => "\@${LIBDIR}/${PATTERNS[++$PATTERN_MOD\
|
|
ULUS % 2]}",
|
|
);
|
|
$OLD_TIME = "$d1$d2$d3$d4";
|
|
}
|
|
} # ifend check_time
|
|
|
|
my $error = ($pc =~ /(\d+)%/) ? 0 : 1;
|
|
my $objname = "$host$fs";
|
|
if ($error) {
|
|
$objname = $line;
|
|
$objname =~ tr/ ./_/;
|
|
$objname = substr $objname, 22;
|
|
}
|
|
|
|
if (not ($fsr = Filesystem->find_object($objname))) {
|
|
$fsr = $MW->Filesystem(-object_name => $objname, -er\
|
|
ror => $error);
|
|
$fsr->grid(-in => $FS, -sticky => 'w');
|
|
}
|
|
if ($error) { # percent is not numeric, must be a `d\
|
|
f' error
|
|
$fsr->update_widget($line);
|
|
} else { # not an error line, but a "normal" `df' ou\
|
|
tput line
|
|
($pc) = $pc =~ /(\d+)%/; # remove %
|
|
$line = "$host:$fs $d1 $d2 $d3 $d4";
|
|
$fsr->update_widget($line, $pc, $COLORS[$pc - 90]);
|
|
}
|
|
push @active_Filesystem_objects, $fsr;
|
|
}; # forend all lines that need attention
|
|
|
|
Filesystem->delete_stale_objects(@active_Filesystem_objects);
|
|
|
|
} # end display_poll_results
|
|
|
|
sub end_monds {
|
|
|
|
# Quit, Ctrl/c or kill signal, finish up.
|
|
|
|
stop_tcp_clients;
|
|
$MW->destroy;
|
|
} # end end_monds
|
|
sub flash_widget {
|
|
# Flash a widget attribute periodically.
|
|
my($w, $option, $val1, $val2, $interval) = @ARG;
|
|
$w->configure($option => $val1);
|
|
return ($MW->after($interval, [\&flash_widget, $w, $option, $va\
|
|
l2, $val1,
|
|
$interval]));
|
|
} # end flash_widget
|
|
sub heuristics_say_so { # try to be smart about things
|
|
# The latest df sample shows a filesystem exceeding a particular s\
|
|
everity
|
|
# level. If the percent used appears to be static, then don't bot\
|
|
her the
|
|
# operator, who has lots of other data to look at. Only applicabl\
|
|
e if the
|
|
# severity level is 'Informative'.
|
|
#
|
|
# EXIT: 0, FALSE, do not display; 1, TRUE, display.
|
|
#
|
|
my($severity_level, $filesys, $used, $sample_count, @sample_list) \
|
|
= @ARG;
|
|
my $f; # filesystem name
|
|
my $fs; # filesystem
|
|
my $fsl; # filesystem list
|
|
my $p; # precent used
|
|
|
|
return 1 if $sample_count < $MAX_DF_SAMPLES; # display if too f\
|
|
ew samples
|
|
return 1 unless $SEVERITY{$severity_level} eq $SEVERITY{90};
|
|
INSPECT_ALL_DF_SAMPLES:
|
|
foreach $fsl (@sample_list) { # each sample of machine's filesyste\
|
|
ms
|
|
INSPECT_ALL_FILESYSTEMS_IN_THIS_SAMPLE:
|
|
foreach $fs (split /:/, $fsl) {
|
|
($f, $p) = split /=/, $fs;
|
|
next INSPECT_ALL_FILESYSTEMS_IN_THIS_SAMPLE unless
|
|
$f eq $filesys;
|
|
return 1 if $p != $used; # display if %used is changing
|
|
} # forend INSPECT_ALL_FILESYSTEMS_IN_THIS_SAMPLE
|
|
} # forend INSPECT_ALL_DF_SAMPLES
|
|
|
|
return 0; # don't dispay - %used is unchang\
|
|
ing
|
|
|
|
} # end heuristics_say_so
|
|
|
|
sub initialize { # preset monitor_disk_space
|
|
|
|
update_status 'Global Variables';
< |