#!@PERL@
#
use lib '@amperldir@';
use strict;
use warnings;
use Time::Local;
use Text::ParseWords;
use Amanda::Util qw( :constants match_labelstr );;
use Amanda::Process;
use Amanda::Status;
use Amanda::Config qw( :init :getconf config_dir_relative );
use Amanda::Debug qw( :logging );
use Getopt::Long;
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV', 'PATH'};
$ENV{'PATH'} = "/bin:/usr/bin:/usr/sbin:/sbin"; # force known path
my $confdir="@CONFIG_DIR@";
my $prefix='@prefix@';
$prefix=$prefix; # avoid warnings about possible typo
my $exec_prefix="@exec_prefix@";
$exec_prefix=$exec_prefix; # ditto
my $sbindir="@sbindir@";
my $Amanda_process = Amanda::Process->new(0);
$Amanda_process->load_ps_table();
my $STATUS_STRANGE = 2;
my $STATUS_FAILED = 4;
my $STATUS_MISSING = 8;
my $STATUS_TAPE = 16;
my $exit_status = 0;
my $opt_detail;
my $opt_summary;
my $opt_stats;
my $opt_config;
my $opt_file;
my $opt_locale_independent_date_format = 0;
sub usage() {
print "amstatus [--file amdump_file]\n";
print " [--[no]detail] [--[no]summary] [--[no]stats]\n";
print " [--[no]locale-independent-date-format]\n";
print " [--config] <config>\n";
exit 0;
}
my $pwd = `pwd`;
chomp $pwd;
Amanda::Util::setup_application("amstatus", "server", $CONTEXT_CMDLINE, "amanda", "amanda");
my $config_overrides = new_config_overrides($#ARGV+1);
debug("Arguments: " . join(' ', @ARGV));
Getopt::Long::Configure(qw{ bundling });
GetOptions(
'detail!' => \$opt_detail,
'summary!' => \$opt_summary,
'stats|statistics!' => \$opt_stats,
# 'dumping|d!' => \$opt_dumping,
# 'waitdumping|wdumping!' => \$opt_waitdumping,
# 'waittaper|wtaper!' => \$opt_waittaper,
# 'dumpingtape|dtape!' => \$opt_dumpingtape,
# 'writingtape|wtape!' => \$opt_writingtape,
# 'finished!' => \$opt_finished,
# 'failed|error!' => \$opt_failed,
# 'estimate!' => \$opt_estimate,
# 'gestimate|gettingestimate!' => \$opt_gestimate,
'config|c:s' => \$opt_config,
'file:s' => \$opt_file,
'locale-independent-date-format!' => \$opt_locale_independent_date_format,
'o=s' => sub { add_config_override_opt($config_overrides, $_[1]); },
) or usage();
my $conf;
if( defined $opt_config ) {
$conf = $opt_config;
}
else {
if($#ARGV == 0 ) {
$conf=$ARGV[0];
}
else {
&usage();
}
}
set_config_overrides($config_overrides);
config_init_with_global($CONFIG_INIT_EXPLICIT_NAME, $conf);
my ($cfgerr_level, @cfgerr_errors) = config_errors();
if ($cfgerr_level >= $CFGERR_WARNINGS) {
config_print_errors();
if ($cfgerr_level >= $CFGERR_ERRORS) {
print STDERR "errors processing config file";
exit 1;
}
}
#untaint user input $ARGV[0]
if ($conf =~ /^([\w.-]+)$/) { # $1 is untainted
$conf = $1;
} else {
die "filename '$conf' has invalid characters.\n";
}
if ( ! -e "$confdir/$conf" ) {
print "Configuration directory '$confdir/$conf' doesn't exist\n";
exit 1;
}
if ( ! -d "$confdir/$conf" ) {
print "Configuration directory '$confdir/$conf' is not a directory\n";
exit 1;
}
my $logdir = config_dir_relative(getconf($CNF_LOGDIR));
chomp $logdir;
my $errfile="$logdir/amdump";
my $nb_options = defined($opt_detail) +
defined($opt_summary) +
defined($opt_stats);
my $set_options = (defined($opt_detail) ? $opt_detail : 0 ) +
(defined($opt_summary) ? $opt_summary : 0 ) +
(defined($opt_stats) ? $opt_stats : 0 );
Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
if ($nb_options == 0) {
$opt_detail = 1;
$opt_summary = 1;
$opt_stats = 1;
} elsif ($set_options > 0) {
$opt_detail = 0 if !defined $opt_detail;
$opt_summary = 0 if !defined $opt_summary;
$opt_stats = 0 if !defined $opt_stats;
} else {
$opt_detail = 1 if !defined $opt_detail;
$opt_summary = 1 if !defined $opt_summary;
$opt_stats = 1 if !defined $opt_stats;
}
my $unit = getconf($CNF_DISPLAYUNIT);
$unit =~ tr/A-Z/a-z/;
my $unitdivisor=1;
if($unit eq 'k') {
$unitdivisor = 1*1024;
}
elsif($unit eq 'm') {
$unitdivisor = 1024*1024;
}
elsif($unit eq 'g') {
$unitdivisor = 1024*1024*1024;
}
elsif($unit eq 't') {
$unitdivisor = 1024*1024*1024*1024;
}
else {
$unit = 'k';
$unitdivisor = 1024;
}
sub dn {
my $number = shift;
return int($number/$unitdivisor);
}
my $dead_run = 0;
if( defined $opt_file) {
if( $opt_file =~ m,^/, ) {
$errfile = $opt_file;
} else {
$errfile = "$pwd/$opt_file";
$errfile = "$logdir/$opt_file" if ( ! (-f $errfile ));
}
}
else {
$errfile="$logdir/amflush" if(! (-f $errfile));
if (! -f $errfile) {
if (-f "$logdir/amflush.1" && -f "$logdir/amdump.1" &&
-M "$logdir/amflush.1" < -M "$logdir/amdump.1") {
$errfile="$logdir/amflush.1";
} else {
$errfile="$logdir/amdump.1";
}
$dead_run = 1;
}
}
print "Using: $errfile\n";
debug("Using: $errfile");
my $status = Amanda::Status->new(filename => $errfile);
if ($status->isa("Amanda::Message")) {
print $status, "\n";
exit 1;
}
my $message = $status->current(user_msg => sub {});
if ($message->{'code'} != 1800000) {
print $message, "\n";
exit 1;
}
if ($opt_locale_independent_date_format) {
print "From $status->{'starttime-locale-independent'}\n";
} else {
print "From $status->{'datestr'}\n";
}
print "\n";
#print Data::Dumper::Dumper($status);
my $maxnamelength = 10;
my $maxlevellength = 1;
foreach my $host (keys %{$status->{'dles'}}) {
foreach my $disk (keys %{$status->{'dles'}->{$host}}) {
my $qdisk = Amanda::Util::quote_string($disk);
if (length("$host:$qdisk") > $maxnamelength) {
$maxnamelength = length("$host:$qdisk");
}
foreach my $datestamp (keys %{$status->{'dles'}->{$host}->{$disk}}) {
my $dle = $status->{'dles'}->{$host}->{$disk}->{$datestamp};
if (length("$dle->{'level'}") > $maxlevellength) {
$maxlevellength = length("$dle->{'level'}");
}
}
}
}
my $nb_storage = 0;
if (defined $status->{'storage'}) {
$nb_storage = keys %{$status->{'storage'}};
}
if ($opt_detail) {
foreach my $host (sort keys %{$status->{'dles'}}) {
foreach my $disk (sort keys %{$status->{'dles'}->{$host}}) {
foreach my $datestamp (sort keys %{$status->{'dles'}->{$host}->{$disk}}) {
my $dle = $status->{'dles'}->{$host}->{$disk}->{$datestamp};
my $taper_status;
if ($nb_storage == 1) {
if (defined $dle->{'storage'}) {
my @storage = keys %{$dle->{'storage'}};
my $storage = $storage[0];
my $dlet = $dle->{'storage'}->{$storage};
$taper_status = $dlet->{'message'};
$taper_status .= ", taping delayed because of config: $dlet->{'error'}" if defined $dlet->{'tape_config'};
}
}
my $qdisk = Amanda::Util::quote_string($disk);
printf "%-${maxnamelength}s $datestamp %-${maxlevellength}s ","$host:$qdisk", $dle->{'level'};
if (defined $dle->{'dsize'}) {
printf "%9s$unit ", dn($dle->{'dsize'});
} elsif (defined $dle->{'esize'}) {
printf "%9s$unit ", dn($dle->{'esize'});
} else {
printf "%9s ", "";
}
my $dump_status = $dle->{'message'};
if ($nb_storage == 0) {
print $dump_status;
if (defined $dle->{'wsize'} && defined $dle->{'esize'} && $dle->{'esize'} != 0) {
printf " (%s$unit done (%0.2f%%))", dn($dle->{'wsize'}),
100.0 * $dle->{'wsize'} / $dle->{'esize'};
}
if (defined $dle->{'dump_time'}) {
print " (", $status->show_time($dle->{'dump_time'}), ")";
}
} elsif ($nb_storage == 1) {
if ($dump_status and $taper_status and $dump_status ne $taper_status) {
print "$dump_status, $taper_status";
} elsif ($dump_status) {
print $dump_status;
} else {
print $taper_status;
}
if ($taper_status) {
my @storage = keys %{$dle->{'storage'}};
my $storage = $storage[0];
my $dlet = $dle->{'storage'}->{$storage};
if (defined $dlet->{'wsize'} && defined $dle->{'dsize'} && $dle->{'dsize'} != 0) {
printf " (%s$unit done (%0.2f%%))", dn($dlet->{'wsize'}),
100.0 * $dlet->{'wsize'} / $dle->{'dsize'};
} elsif (defined $dlet->{'wsize'} && defined $dle->{'esize'} && $dle->{'esize'} != 0) {
printf " (%s$unit done (%0.2f%%))", dn($dlet->{'wsize'}),
100.0 * $dlet->{'wsize'} / $dle->{'esize'};
}
if (defined $dlet->{'taper_time'}) {
print " (", $status->show_time($dlet->{'taper_time'}), ")";
}
} else {
if (defined $dle->{'wsize'} && defined $dle->{'esize'} && $dle->{'esize'} != 0) {
printf " (%s$unit done (%0.2f%%))", dn($dle->{'wsize'}),
100.0 * $dle->{'wsize'} / $dle->{'esize'};
}
if (defined $dle->{'dump_time'}) {
print " (", $status->show_time($dle->{'dump_time'}), ")";
}
}
} elsif (defined $dle->{'storage'}) {
my $first = 0;
my $dump_status_length = 0;
for my $storage (sort keys %{$dle->{'storage'}}) {
my $dlet = $dle->{'storage'}->{$storage};
my $taper_status;
$taper_status = $dlet->{'message'};
$taper_status .= ", taping delayed because of config: $dlet->{'tape_config'}" if defined $dlet->{'tape_config'};
$taper_status .= ", tape error: $dlet->{'tape_error'}" if defined $dlet->{'tape_error'};
if ($first == 0) {
if ($dump_status) {
my $time = "(" . $status->show_time($dle->{'dump_time'}) . ")";
print "$dump_status $time, ";
$dump_status_length = length($dump_status) + length($time) + 3;
}
print "($storage) $taper_status";
} else {
print "\n";
printf "%s", ' ' x ($maxnamelength + 1 + 14 + 1 + $maxlevellength + 1 + 10 + 1 + $dump_status_length);
print "($storage) $taper_status";
}
$first++;
if (defined $dlet->{'wsize'} && defined $dle->{'esize'} && $dle->{'esize'} != 0) {
printf " (%s$unit done (%0.2f%%))", dn($dlet->{'wsize'}),
100.0 * $dlet->{'wsize'} / $dle->{'esize'};
}
if (defined $dlet->{'taper_time'}) {
print " (", $status->show_time($dlet->{'taper_time'}), ")";
}
}
} else {
print $dump_status;
if (defined $dle->{'wsize'} && defined $dle->{'esize'} && $dle->{'esize'} != 0) {
printf " (%s$unit done (%0.2f%%))", dn($dle->{'wsize'}),
100.0 * $dle->{'wsize'} / $dle->{'esize'};
}
if (defined $dle->{'dump_time'}) {
print " (", $status->show_time($dle->{'dump_time'}), ")";
}
}
print "\n";
}
}
}
print "\n";
}
if ($opt_summary) {
printf "%-16s %4s %10s %10s\n", "SUMMARY", "dle", "real", "estimated";
printf "%-16s %4s %10s %10s\n", "", "", "size", "size";
printf "%-16s %4s %10s %10s\n", "----------------", "----", "---------", "---------";
summary($status, 'disk', 'disk', 0, 0, 0, 0);
summary($status, 'estimated', 'estimated', 0, 1, 0, 0);
summary_storage($status, 'flush', 'flush', 1, 0, 0, 0);
summary($status, 'dump_failed', 'dump failed', 1, 1, 1, 1);
summary($status, 'wait_for_dumping', 'wait for dumping', 0, 1, 0, 1);
summary($status, 'dumping_to_tape', 'dumping to tape', 1, 1, 1, 1);
summary($status, 'dumping', 'dumping', 1, 1, 1, 1);
summary($status, 'dumped', 'dumped', 1, 1, 1, 1);
summary_storage($status, 'wait_for_writing', 'wait for writing', 1, 1, 1, 1);
summary_storage($status, 'wait_to_flush' , 'wait to flush' , 1, 1, 1, 1);
summary_storage($status, 'writing_to_tape' , 'writing to tape' , 1, 1, 1, 1);
summary_storage($status, 'dumping_to_tape' , 'dumping to tape' , 1, 1, 1, 1);
summary_storage($status, 'failed_to_tape' , 'failed to tape' , 1, 1, 1, 1);
summary_storage($status, 'taped' , 'taped' , 1, 1, 1, 1);
print "\n";
if ($status->{'idle_dumpers'} == 0) {
printf "all dumpers active\n";
} else {
my $c1 = ($status->{'idle_dumpers'} == 1) ? "" : "s";
my $c2 = ($status->{'idle_dumpers'} < 10) ? " " : "";
my $c3 = ($status->{'idle_dumpers'} == 1) ? " " : "";
printf "%d dumper%s idle%s %s: %s\n", $status->{'idle_dumpers'},
$c1, $c2, $c3,
$status->{'status_driver'};
}
if (defined $status->{'storage'}) {
for my $storage (sort keys %{$status->{'storage'}}) {
next if !$storage;
my $taper = $status->{'storage'}->{$storage}->{'taper'};
next if !$taper;
printf "%-11s qlen: %d\n", "$storage",
$status->{'qlen'}->{'tapeq'}->{$taper} || 0;
if (defined $status->{'taper'}->{$taper}->{'worker'}) {
my @worker_status;
for my $worker (sort keys %{$status->{'taper'}->{$taper}->{'worker'}}) {
my $wworker = $status->{'taper'}->{$taper}->{'worker'}->{$worker};
my $wstatus = $wworker->{'status'};
my $wmessage = $wworker->{'message'};
my $whost = $wworker->{'host'};
my $wdisk = $wworker->{'disk'};
$worker =~ /worker\d*-(\d*)/;
my $wname = $1;
printf "%16s:", $wname;
if (defined ($wmessage)) {
if ($wmessage eq "Idle") {
print " $wmessage\n";
} else {
print " $wmessage ($whost:$wdisk)\n";
}
} else {
if (defined $whost and defined $wdisk) {
print " ($whost:$wdisk)\n";
} else {
print "\n";
}
}
}
} else {
print "Idle";
}
print "\n";
}
}
printf "%-16s: %d\n", "network free kps", $status->{'network_free_kps'};
if (defined $status->{'holding_free_space'}) {
my $hs;
if ($status->{'holding_free_space'}) {
$hs = ($status->{'holding_free_space'} * 1.0 / $status->{'holding_free_space'}) *100;
} else {
$hs = 0.0;
}
printf "%-16s: %s$unit (%0.2f%%)\n", "holding space", dn($status->{'holding_free_space'}), $hs;
}
}
my $len_storage = 0;
if (defined $status->{'taper'}) {
foreach my $taper (keys %{$status->{'taper'}}) {
my $len = length($status->{'taper'}->{$taper}->{'storage'});
if ($len > $len_storage) {
$len_storage = $len;
}
}
}
my $r;
if ($opt_stats) {
if (defined $status->{'current_time'} and
$status->{'current_time'} != $status->{'start_time'}) {
my $total_time = $status->{'current_time'} - $status->{'start_time'};
foreach my $key (sort byprocess keys %{$status->{'busy'}}) {
my $name = $key;
if ($status->{'busy'}->{$key}->{'type'} eq "taper") {
$name = $status->{'busy'}->{$key}->{'storage'};
}
$name = sprintf "%*s", $len_storage, $name;
printf "%-16s: %8s (%6.2f%%)\n",
"$name busy", &busytime($status->{'busy'}->{$key}->{'time'}),
$status->{'busy'}->{$key}->{'percent'};
}
if (defined $status->{'dumpers_actives'}) {
for (my $d = 0; $d < @{$status->{'dumpers_actives'}}; $d++) {
my $l = sprintf "%2d dumper%s busy%s : %8s (%6.2f%%)", $d,
($d == 1) ? "" : "s",
($d == 1) ? " " : "",
&busytime($status->{'busy_dumper'}->{$d}->{'time'}),
$status->{'busy_dumper'}->{$d}->{'percent'};
print "$l";
my $s1 = "";
my $s2 = " " x length($l);
if (defined $status->{'busy_dumper'}->{$d}->{'status'}) {
foreach my $key (sort keys %{$status->{'busy_dumper'}->{$d}->{'status'}}) {
printf "%s%20s: %8s (%6.2f%%)\n",
$s1,
$key,
&busytime($status->{'busy_dumper'}->{$d}->{'status'}->{$key}->{'time'}),
$status->{'busy_dumper'}->{$d}->{'status'}->{$key}->{'percent'};
$s1 = $s2;
}
}
if ($s1 eq "") {
print "\n";
}
}
}
}
}
exit $status->{'exit_status'};
sub busytime() {
my $busy = shift;
my $oneday = 24*60*60;
my $result;
if ($busy > $oneday) {
my $days = int($busy/$oneday);
$result = sprintf "%d+", $busy/$oneday;
$busy -= $days*$oneday;
} else {
$result = "";
}
my $hours = int($busy / (60*60));
$busy -= $hours * 60 * 60;
my $minutes = int($busy/60);
$busy -= $minutes * 60;
my $seconds = $busy;
$result .= sprintf("%2d:%02d:%02d", $hours, $minutes, $seconds);
return $result;
}
sub byprocess() {
my(@tmp_a) = split(/(\d*)$/, $a, 2);
my(@tmp_b) = split(/(\d*)$/, $b, 2);
return ($tmp_a[0] cmp $tmp_b[0]) || ($tmp_a[1] <=> $tmp_b[1]);
}
#sub valuesort() {
# $r->{$b} <=> $r->{$a};
#}
sub summary {
my $status = shift;
my $key = shift;
my $name = shift;
my $print_rsize = defined $status->{'stat'}->{$key}->{'real_size'};
my $print_esize = defined $status->{'stat'}->{$key}->{'estimated_size'};
my $print_rstat = defined $status->{'stat'}->{$key}->{'real_stat'};
my $print_estat = defined $status->{'stat'}->{$key}->{'estimated_stat'};
my $nb = $status->{'stat'}->{$key}->{'nb'};
my $rsize = "";
$rsize = sprintf "%8s$unit", dn($status->{'stat'}->{$key}->{'real_size'}) if defined $status->{'stat'}->{$key}->{'real_size'};
my $esize = "";
$esize = sprintf "%8s$unit", dn($status->{'stat'}->{$key}->{'estimated_size'}) if defined $status->{'stat'}->{$key}->{'estimated_size'};
my $rstat = "";
$rstat = sprintf "(%6.2f%%)", $status->{'stat'}->{$key}->{'real_stat'} if defined $status->{'stat'}->{$key}->{'real_stat'};
my $estat = "";
$estat = sprintf "(%6.2f%%)", $status->{'stat'}->{$key}->{'estimated_stat'} if defined $status->{'stat'}->{$key}->{'estimated_stat'};
my $line = sprintf "%-16s:%4d %9s %9s %9s %9s",
$status->{'stat'}->{$key}->{'name'},
$status->{'stat'}->{$key}->{'nb'},
$rsize, $esize, $rstat, $estat;
$line =~ s/ *$//g; #remove trailing space
print "$line\n";
}
sub summary_storage {
my $status = shift;
my $key = shift;
my $name = shift;
my $print_rsize = shift;
my $print_esize = shift;
my $print_rstat = shift;
my $print_estat = shift;
if (!$status->{'stat'}->{$key}->{'storage'} ||
keys %{$status->{'stat'}->{$key}->{'storage'}} == 0) {
printf "$name\n";
return;
}
if ($nb_storage > 1) {
printf "$name\n";
};
for my $storage (sort keys %{$status->{'stat'}->{$key}->{'storage'}}) {
if ($nb_storage > 1) {
$name = sprintf "%-16s", " $storage";
}
my $nb = $status->{'stat'}->{$key}->{'storage'}->{$storage}->{'nb'};
my $rsize = "";
$rsize = sprintf "%8s$unit", dn($status->{'stat'}->{$key}->{'storage'}->{$storage}->{'real_size'}) if defined $status->{'stat'}->{$key}->{'storage'}->{$storage}->{'real_size'};
my $esize = "";
$esize = sprintf "%8s$unit", dn($status->{'stat'}->{$key}->{'storage'}->{$storage}->{'estimated_size'}) if defined $status->{'stat'}->{$key}->{'storage'}->{$storage}->{'estimated_size'};
my $rstat = "";
$rstat = sprintf "(%6.2f%%)", $status->{'stat'}->{$key}->{'storage'}->{$storage}->{'real_stat'} if defined $status->{'stat'}->{$key}->{'storage'}->{$storage}->{'real_stat'};
my $estat = "";
$estat = sprintf "(%6.2f%%)", $status->{'stat'}->{$key}->{'storage'}->{$storage}->{'estimated_stat'} if defined $status->{'stat'}->{$key}->{'storage'}->{$storage}->{'estimated_stat'};
my $line = sprintf "%-16s:%4d %9s %9s %9s %9s",
$name,
$nb,
$rsize, $esize, $rstat, $estat;
$line =~ s/ *$//g; #remove trailing space
print "$line\n";
if ($key eq 'taped') {
my $taper = $status->{'storage'}->{$storage}->{'taper'};
summary_taped($status, $taper);
}
}
}
sub summary_taped {
my $status = shift;
my $taper = shift;
my $i = 0;
while ($i < $status->{'taper'}->{$taper}->{'nb_tape'}) {
my $nb = $status->{'taper'}->{$taper}->{'stat'}[$i]->{'nb_dle'};
my $real_size = $status->{'taper'}->{$taper}->{'stat'}[$i]->{'size'};
my $estimated_size = $status->{'taper'}->{$taper}->{'stat'}[$i]->{'esize'};
my $percent = $status->{'taper'}->{$taper}->{'stat'}[$i]->{'percent'};
my $label = $status->{'taper'}->{$taper}->{'stat'}[$i]->{'label'};
my $nb_part = $status->{'taper'}->{$taper}->{'stat'}[$i]->{'nb_part'};
my $tape = "tape " . ($i+1);
printf " %-12s:%4d %9s$unit %9s$unit (%6.2f%%) %s (%d parts)\n", $tape, $nb, dn($real_size), dn($estimated_size), $percent, $label, $nb_part;
$i++;
}
}