# This is a small handy utility for screen(1) users.
# To "install" it, add the following lines to your ~/.screenrc:
# hardstatus alwayslastline # Always display the status line
# hardstatus string "... [%40`]" # The format of the status line
# backtick 40 0 0 /path/to/screen-nodestatus.pl computer1:C:10 router:R:20
# # This runs screen-nodestatus.pl with the args "computer1:C:10
# router:R:20", meaning that screen-nodestatus.pl will ping(1)
# "computer1" every 10s and "router" every 20s. If the node was
# reachable, "C" respectively "R" will be displayed, else "c"
# respectively "r".
# This program will then output something like this:
# ???? # ? -- Unknown status
# ?*?? # * -- "Note! The status is going to change!"
# ?D?? # Uppercase letter -- the host is on
# ?D?*
# ?D?t # Lowercase letter -- the host is off
# *D?t
# AD?t
# AD*t
# ADMt
# But as screen(1) will only ever display the last line, you have a nice view
# on the status of your computers! :)
# Note: You might need to change &ping (at the end of this file).
# See a screenshot of screen-nodestatus.pl in action at
# http://m19s28.vlinux.de/iblech/screen-nodestatus.png (look at the bottom
# right corner).
use v6-alpha;
# Mapping hostname => status letter.
my %letter;
# Mapping hostname => ping interval.
my %interval;
# Mapping hostname => last check timestamp.
my %last_check;
# Mapping hostname => current status
my ($ON, $OFF, $UNKNOWN, $CHANGE) = (0..3);
my %status;
# Additional callback which is called when the status of a computer changes.
my &on_change = -> Str $host, Int $new_status {
system "beep", "-r2", "-l150", "-f", $new_status == $ON ?? "1700" !! "200"
if $new_status == $ON or $new_status == $OFF;
};
# Parameter parsing.
for @*ARGS -> $arg {
my &error = -> $err {
die "While parsing the command line option \"$arg\":\n$err";
};
my ($hostname, $letter, $interval) = split ":", $arg;
$letter .= uc;
unless $hostname & $letter & $interval {
error "You didn't specify the hostname, the status letter, or the interval.";
}
if defined %letter{$hostname} {
error "Entry for host \"$hostname\" already defined.";
}
if chars $letter != 1 {
error "The status letter isn't exactly one char long.";
}
# Needed because we use the shell to spawn ping(1) later on.
if $hostname ~~ rx:Perl5/[ <>;&\\\$'"]/ { #"#--vim
error "The hostname \"$hostname\" contains shell metacharacters.";
}
%letter\ .{$hostname} = $letter;
%interval\ .{$hostname} = $interval;
%status\ .{$hostname} = $UNKNOWN;
# We want a more uniformly distributed distribution.
%last_check.{$hostname} = time - rand $interval;
}
usage() unless %letter;
# Initial status
write_status();
# The main loop
loop {
for sort keys %last_check -> $host {
# Skip this host if we've already checked it.
next if %last_check{$host} > time - %interval{$host};
%last_check{$host} = time;
# Update the status (maybe).
write_if_change $host, { %status{$host} = ping($host) ?? $ON !! $OFF };
}
# We don't want to hog the CPU.
sleep 1;
}
sub usage() {
die "Usage: $*EXECUTABLE_NAME $*PROGRAM_NAME hostdef hostdef ...
A host definition consists of three parts:
router:R:30
^ ^ ^
| | |
| | +-- The check interval.
| |
| +-- The letter to use if the host is reachable.
| It doesn't matter if you use upper- or lowercase here --
| If the host is only, the letter will be uc()ed, and if the host
| is offline, it will be lc()ed.
|
+-- The hostname of the host to ping(1).
A complete command line might look like:
$*EXECUTABLE_NAME $*PROGRAM_NAME router:R:30 printer:P:60 box:B:10
Look at the source of $*PROGRAM_NAME for more information
or see a screenshot at http://m19s28.vlinux.de/iblech/screen-nodestatus.png
(look at the bottom right corner).\n";
}
# Write the new status line iff the status of $host has changed after executing
# &callback.
sub write_if_change(Str $host, Code &callback) {
my $old_status = %status{$host};
callback();
my $new_status = %status{$host};
if $old_status != $new_status {
# Temporarily mark $host as changing (*).
%status{$host} = $CHANGE;
# Write the status line.
write_status();
# Set the correct status, call &on_change, and write the correct status.
%status{$host} = $new_status;
on_change($host, $new_status);
write_status();
}
}
# Write the status line.
sub write_status() {
for %letter.keys.sort({ %letter{$^a} cmp %letter{$^b} }) {
my $status = %status{$_};
my $char = %letter{$_};
print ({
$ON => uc $char,
$OFF => lc $char,
$UNKNOWN => "?",
$CHANGE => "*",
}{$status});
}
print "\n";
}
# Ping a host.
# Returns true/false.
sub ping(Str $host) returns Bool {
# This line might need changing.
# "Why don't you use the list form of system()?" -- Because I don't want to
# redirect STDOUT and STDERR to /dev/null own my own right now.
system "ping -q -w3 -c1 $host &>/dev/null";
# -w3 -- Wait a maximum of 3s for a ICMP PING_REPLY.
# -c1 -- Stop after at least one ICMP PING_REPLY is seen.
}