On Fri, Feb 13, 2004 at 01:22:16AM +0100, Josef Wolf wrote:
> I have written a script to print amanda tape labels. I think this script
> might be interesting to other amanda users, so I offer to include it in
> the amanda distribution.
OK, here it comes:
-------------------------snip-----------------------
#! /usr/bin/perl -w
# A utility to print amanda tape labels for DAT and CD.
#
# 2004-02-12 Josef Wolf (jw AT raven.inka DOT de)
#
# Portions of this program which I authored may be used for any purpose
# so long as this notice is left intact.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# Comments, suggestions, bugfixes and improvements are welcome.
# I wrote this program because I was dissatisfied with the original label
# printing program that comes with the amanda distribution. I wanted to see
# from one glance on the newest tape which tapes in which order I need to
# recover a specific DLE.
#
# This program prints tapelabels for the amanda backup system. The output
# can be in plain ASCII or in postscript. The postscript output is formatted
# so that it can be folded to fit into a DAT case or into a CD jewel case.
#
# An example ASCII output (somewhat stripped to make it fit into 80 columns)
# is attached below. Here is an explanation of the example output:
#
# The columns in the output have following meanings:
#
# date: This name seems to be intuitive, but unfortunately, it is somewhat
# misleading. Actually, this is the name of the logfile that provided
# the corresponding information.
# label: The label of the tape.
# fn: File number on the tape.
# fm: Filemark
# Osize: Original (that is, uncompressed) size of the dump(s).
# Dsize: Size of the dump(s). This is usually pretty close to Tsize so it
# is of very little interest.
# Tsize: The size of dump(s) on the tape.
# Dtime: Dumper time.
# Ttime: Taper Time.
# Dspd: Dumper speed.
# Tspd: Taper speed.
# DLE: Disk list entry.
# lv: Dump-level.
# dpl: "Dumps per level". This is a list of dump levels (starting with
# level 0).
# error: An error message.
#
# The output is split into four sections. The first section (if present)
# lists errors. In the example below we can see that there were two taper
# errors and a warning that a DLE must be flushed to tape.
#
# The second section contains only one line with date, label and amount of
# data on the tape.
#
# The third section lists all the tapes that are needed to recover all
# DLEs. In the example below we can see that three level0, four level1
# and one level3 from tape VOL01 are needed to do a full restore of all
# DLEs. In addition, three level0, one level1 and one level2 from VOL09
# are still needed. Everything else on VOL09 is obsoleted by the dumps
# on VOL01. VOL08 contains two level0 and one leve1 that are not obsoleted
# by newer tapes. Finally, VOL07 contain one level0 dump that is not
# obsolted by newer tapes.
# Tapes that contain only obsoleted data are not mentioned at all unless you
# supply the -t command line option.
#
# The fourth section is the main section and is itself split into multiple
# sections, one for each DLE. In this section we can see which tapes we
# need to recover a specific DLE. For example, we can see that in order
# to recover raven:/u4 we need file 1 from VOL07, file 6 from VOL08,
# file 7 from VOL09 and file 1 from VOL01, in this order.
#
# Here is the example output:
#
# date Tsize label fm lv error
# 2004-02-20.0 1499M VOL06 8 ? writing file: No space left on device
# 2004-02-23.0 1499M VOL01 9 ? writing file: No space left on device
# 2004-02-23.0 670M ??? ? 0 raven:/m/u1 not on tape yet
#
# 2004-02-23.0 VOL01 Total size: 834M
#
# date label Osize Tsize Ttime dpl
# 2004-02-23.0 VOL01 1616M 834M 0:01:06 3/4/0/1
# 2004-02-22.0 VOL09 2885M 1164M 0:01:12 3/1/1
# 2004-02-21.0 VOL08 3427M 1127M 0:01:12 2/1
# 2004-02-20.1 VOL07 2077M 1376M 0:01:24 1
#
# Dspd Tspd Dtime Osize Tsize Ttime date label:fn lv DLE
# 461k 105M 0:00:06 15M 2963k 0:00:00 2004-02-23.0 VOL01:04 1 raven:/
# 803k 11M 0:09:13 1163M 434M 0:00:37 2004-02-21.0 VOL08:08 0 raven:/
#
# 0k 1647k 0:00:00 10k 1k 0:00:00 2004-02-23.0 VOL01:03 1 raven:/boot
# 2515k 40M 0:00:01 4520k 3487k 0:00:00 2004-02-22.0 VOL09:03 0 raven:/boot
#
# 190k 25M 0:00:00 290k 22k 0:00:00 2004-02-22.0 VOL09:02 1 raven:/u1
# 971k 19M 0:11:46 1757M 670M 0:00:34 2004-02-21.0 VOL08:09 0 raven:/u1
#
# 0k 1237k 0:00:00 10k 1k 0:00:00 2004-02-23.0 VOL01:02 0 raven:/u2
#
# 925k 13M 0:00:40 94M 36M 0:00:02 2004-02-23.0 VOL01:06 1 raven:/u3
# 2913k 13M 0:03:56 884M 672M 0:00:49 2004-02-22.0 VOL09:08 0 raven:/u3
#
# 222k 54M 0:00:00 830k 113k 0:00:00 2004-02-23.0 VOL01:01 3 raven:/u4
# 373k 47M 0:00:50 459M 18M 0:00:00 2004-02-22.0 VOL09:07 2 raven:/u4
# 314k 42M 0:01:14 505M 22M 0:00:00 2004-02-21.0 VOL08:06 1 raven:/u4
# 3254k 16M 0:07:13 2077M 1376M 0:01:24 2004-02-20.1 VOL07:01 0 raven:/u4
#
# 270k 113M 0:00:14 39M 3885k 0:00:00 2004-02-23.0 VOL01:05 1 raven:/u5
# 593k 21M 0:13:29 1536M 469M 0:00:22 2004-02-22.0 VOL09:09 0 raven:/u5
#
# 1003k 13M 0:06:04 863M 356M 0:00:26 2004-02-23.0 VOL01:08 0 raven:/usr
#
# 1960k 11M 0:03:46 602M 434M 0:00:36 2004-02-23.0 VOL01:07 0 raven:/var
# bugs:
# - Parses amanda's log files instead of reading its database.
# - Depends on assumption that only one amdump per day is run.
# - Nuber of output lines limited to paper size.
# - Output formats hardcoded.
# - Postscript output is not split onto multiple sheets.
# todo:
# - remove some kludges in the code.
use strict;
use Data::Dumper;
use PostScript::Simple;
use Getopt::Std; $Getopt::Std::STANDARD_HELP_VERSION=1;
my $version=0.1;
sub VERSION_MESSAGE {
my ($fh) = @_;
print $fh "$0 Version $version\n";
}
sub help_message {
return
"Usage: $0 [-t] [-p <file> [-f <format>]] <conf>\n" .
" -f <format>: Format postscript for DAT/DDS/CD/DVD cover.\n" .
" -p <file>: Output postscript into <file>.\n" .
" -i <cnt>: Ignore <cnt> newest logfiles. Used to print older\n" .
" tapelabels.\n";
" -t: Output all dumps available on tapes instead of only\n" .
" the latest for every dumplevel.\n";
}
sub HELP_MESSAGE {
my ($fh) = @_;
print $fh &help_message;
}
sub usage {
print STDERR &help_message;
exit 1;
}
my %opt; getopts("tp:f:i:", \%opt);
my $postscript = $opt{p};
my $format = $opt{f};
my $ignore = $opt{i};
my $verbose = exists $opt{t};
my $config = shift;
&usage if !defined $config || (defined $format && !defined $postscript);
$format="DAT" unless defined $format;
$format="DAT" if $format eq "DDS";
my $LOGDIR = `amgetconf $config logdir`; chomp $LOGDIR;
$LOGDIR= "/m/c/misc" unless -d $LOGDIR; # FIXME: to be removed
my @logfiles; # sorted names of logfiles from current dumpcycle
my $curlabel; # the label of the latest (i.e. current) tape
my %dumper; # info about dumped disks
my %taper; # info about tapings of disks
my %tape; # info about tape contents
my %dumped; # log-filename with the latest dump of a disk at a level
my %taped; # log-filename with the latest taping of a disk at a level
my %needed; # still needed taping levels from given logfile
my $xoff=3; # offset of x-coordinate in postscript
my $yoff=29; # offset of y-coordinate in postscript
my $width=7.3; # width of boxes in postscript output.
my $tapedest="top"; # Destination of the tape summaries
# Default formatting for DAT
#
my %out=(
top=>{
ys=> 0.13, # distance of lines
x => 0.25, # x offset
y =>-0.35, # y offset
fs=>4, # font size
la=>5.5, # length of area
st=>0.1, # shrink stepping
V =>[], # array of output sections
},
bot=>{ys=> 0.13,x=>-2.25,y=>-1.5,fs=>4,la=> 5.5,st=>0.1,V=>[]},
err=>{ys=> 0.2 ,x=> 8, y=>-0.5,fs=>6,la=>120, st=>0.1,V=>[]},
);
# change formatting for CD
#
if ($format eq "CD" || $format eq "DVD") {
$width = 12;
$tapedest = "bot";
$out{"top"}{la}=12; $out{"bot"}{la}=11;
$out{"top"}{fs}=5; $out{"bot"}{fs}=5;
$out{"top"}{ys}=0.15; $out{"bot"}{ys}=0.15;
$out{"err"}{x}=0.25; $out{"bot"}{x}=0.25;
}
# Insert separation into an output field.
#
sub sep {
my ($which) = @_;
my $w = $out{$which};
my $y = $w->{ys}*4/4 + $w->{y};
$w->{y} = $y;
push (@{$w->{V}}, $w->{v});
$w->{v} = [];
}
# Output a line.
#
sub out {
my ($which, $val) = @_;
my $w = $out{$which};
my $y = $w->{ys} + $w->{y};
if (!exists $w->{v}) {
$w->{v} = [];
if ($which eq "err") {
&sep ("err");
&out ("err", sprintf("%12s %5s %6s %2s %2s %s",
qw/date Tsize label fm lv error/));
&out;
return;
}
}
my $v=$w->{v};
$w->{y} = $y;
push (@$v, {x=>$w->{x}, y=>$w->{y}, v=>$val, la=>$w->{la}});
}
# Determine which logfiles belong to the current dump cycle. This is done by
# searching backwards from newest to oldest logfiles for already seen
# tape-labels.
#
{
my %labels; # which tape labels we already have seen
FILE: foreach my $logfile (reverse sort <$LOGDIR/log.*>) {
next if defined $ignore && $ignore-- > 0;
open (IN, $logfile) or next;
while (my $l=<IN>) {
if ($l=~/^START taper\s+.*label\s+(\S+)\s+/) {
last FILE if exists $labels{$1};
$labels{$1}=1;
unshift (@logfiles, $logfile);
}
}
close (IN);
}
}
# Parse the logfiles. This constructs %dumper, %taper, %dumped, %taped, %tape
# and $curlabel. In addition, any errors from taper are remembered.
#
foreach my $logfile (@logfiles) {
open (IN, $logfile) or next;
my $label;
my $nr=0;
$logfile =~ s/^.*(\d\d\d\d)(\d\d)(\d\d)\.(\d+)$/$1-$2-$3.$4/;
while (my $line=<IN>) {
chomp $line;
if ($line=~/^STRANGE dumper (.*)/) {
my ($host, $filesystem, $level, $rest) = split (/\s+/, $1, 4);
my $disk="$host:$filesystem";
$dumped{$disk,$level} = $logfile;
$dumper{$disk}{$level} = {%{&hash($rest)}};
}
if ($line=~/^SUCCESS dumper (.*)/) {
my ($host, $filesystem, $d, $level, $rest) = split (/\s+/, $1, 5);
my $disk="$host:$filesystem";
$dumped{$disk,$level} = $logfile;
$dumper{$disk}{$level} = {%{&hash($rest)}};
}
if ($line=~/^START taper\s+(.*)/) {
my $hash = &hash ($1, (files=>{}));
$label = $hash->{"label"};
$curlabel = $label;
$tape{$label}{"kb"} = 0;
# $tape{$label}{"date"} = $hash->{"datestamp"};
# $tape{$label}{"date"} =~ s/^(....)(..)(..)/$1-$2-$3/;
$tape{$label}{"date"} = $logfile;
}
if ($line=~/^SUCCESS taper (.*)/) {
my ($host, $filesystem, $d, $level, $rest) = split (/\s+/, $1, 5);
my $disk="$host:$filesystem";
$taper{$disk}{$logfile} = {
%{&hash($rest, label=>$label, level=>$level, nr=>++$nr,
dump=>$dumper{$disk}{$level})}};
$taped{$disk,$level} = $logfile;
$tape{$label}{"kb"} += $taper{$disk}{$logfile}{"kb"};
$tape{$label}{"count"}{$level}++;
}
if ($line=~/^INFO taper (.*)/) {
my ($d1, $t, $d2, $kb, $d3, $fm, $rest) = split (/\s+/, $1, 7);
if ($rest ne "[OK]") {
&out("err",sprintf("%12s %5s %6s %2s %2s %s",
$tape{$t}{"date"}, &kb($kb),
$t, $fm, "?", $rest));
}
}
}
close (IN);
}
&out("bot", sprintf("%5s %5s %8s %5s %5s %5s %8s %-12s %6s:%2s %2s %s",
qw/Dspd Tspd Dtime Dsize Osize Tsize Ttime date label fn lv DLE/));
# Determine dumps/tapes that are needed in addition to the newest tape in order
# to make a full restore.
#
foreach my $disk (sort keys %taper) {
my $lastlevel=10000;
foreach my $logfile (reverse sort keys %{$taper{$disk}}) {
my $taping=$taper{$disk}{$logfile};
next if !$verbose && $taping->{"level"}>=$lastlevel;
$lastlevel = $taping->{"level"};
&out("bot",
sprintf ("%5s %5s %8s %5s %5s %5s %8s %-12s %6s:%02d %2s %s",
&kb ($taping->{"dump"}{"kps"}),
&kb ($taping->{"kps"}),
&sec($taping->{"dump"}{"sec"}),
&kb ($taping->{"dump"}{"kb"}),
&kb ($taping->{"dump"}{"orig-kb"}),
&kb ($taping->{"kb"}),
&sec($taping->{"sec"}), $logfile,
$taping->{"label"},
$taping->{nr},
$taping->{"level"}, $disk
));
$needed{$logfile}{"label"} = $taping->{"label"};
$needed{$logfile}{"sec"} += $taping->{"sec"};
$needed{$logfile}{"kb"} += $taping->{"kb"};
$needed{$logfile}{"okb"} += $taping->{"dump"}{"orig-kb"};
$needed{$logfile}{"level"}[$lastlevel]++;
}
&sep("bot");
}
# Output the number of still needed tapings for every tape.
#
{
my $headline = sprintf ("%12s %6s %5s %5s %8s %s",
qw/date label Osize Tsize Ttime dpl/);
my $dir = !defined $postscript || $tapedest eq "bot";
&out($tapedest, $headline) if $dir==1;
foreach my $logfile (reverse sort keys %needed) {
my $t=$needed{$logfile};
foreach my $l (@{$t->{"level"}}) {
$l=0 unless defined $l;
}
&out($tapedest, sprintf ("%12s %6s %5s %5s %8s %s",
$logfile, $t->{"label"}, &kb($t->{"okb"}),
&kb($t->{"kb"}), &sec($t->{"sec"}),
join("/", @{$t->{"level"}})));
}
&out($tapedest, $headline) if $dir!=1;
&sep ($tapedest);
}
# Output the dumps that are not written to a tape yet.
{
my $printed=0;
foreach my $d (sort keys %dumped) {
if (!defined $taped{$d} || $taped{$d} lt $dumped{$d}) {
my ($disk, $level) = split (/$;/, $d);
if (!$printed) {
$printed=1;
}
&out ("err", sprintf("%12s %5s %6s %2s %2s %s",
$dumped{$d},
&kb($dumper{$disk}{$level}{kb}),
"???", "?",
$level, "$disk not on tape yet"));
}
}
}
# Start output.
#
my $out;
if (defined $postscript) {
$out = new PostScript::Simple(papersize => "A4", units => "cm");
$out->setcolour("black");
$out->setlinewidth(0.01);
}
# Dispatch the output blocks into appropriate output fields.
#
my ($htop, @topbox) = (0);
if ($tapedest eq "top") {
($htop, @topbox) = &shrinking_boxes ($out{top}, @{$out{top}{V}});
}
my ($hbot, @botbox) = &shrinking_boxes ($out{bot}, @{$out{bot}{V}});
# output errors and warnings
#
my $w=$out{"err"};
foreach my $o (@{$w->{v}}) {
if (defined $postscript) {
$out->setfont ("Courier-Bold", $w->{fs});
$out->text ($xoff+$o->{x}, $yoff-$o->{y}, $o->{v});
} else {
print "$o->{v}\n";
}
}
if ($#{$w->{v}} >= 0) {
if (defined $postscript) {
if ($w->{x} < 8) {
$yoff -= ($w->{ys} * ($#{$w->{v}}));
}
} else {
print "\n";
}
}
# Print date, label and total size
#
if (defined $postscript) {
$out->box ($xoff, $yoff-$htop, $xoff+$width, $yoff-$htop-1.2);
$out->setfont ("Helvetica-Bold", 20);
$out->text($xoff+2.7,$yoff-$htop-0.9, $curlabel);
$out->setfont ("Helvetica-Bold", 11);
$out->text($xoff+0.25,$yoff-$htop-0.9, $tape{$curlabel}{"date"});
$out->setfont ("Courier-Bold", 6);
$out->text($xoff+0.25,$yoff-$htop-0.52,
"Total size: " . &kb($tape{$curlabel}{"kb"}));
} else {
print "$tape{$curlabel}{'date'} $curlabel Total size: ",
&kb($tape{$curlabel}{"kb"}), "\n\n";
}
# Print resulting output boxes.
#
if ($tapedest eq "top") {
&draw_boxes ($xoff, $yoff, $xoff+$out{top}->{x}, 1,
$out{top}->{fs}, @topbox);
}
&draw_boxes ($xoff, $yoff-$htop-1.2, $xoff+$out{bot}->{x}, -1,
$out{bot}->{fs}, @botbox);
$out->output($postscript) if defined $postscript;
# Dispatch output fields into a set of shrinking boxes that can be folded.
#
sub shrinking_boxes {
my ($conf, @contents) = @_;
my ($h, @box) = (0);
for (my $bh=$conf->{la}; $#contents>=0; $bh-=$conf->{st}) {
my @l;
while ($#contents>=0 && ($#l+1+$#{$contents[0]}+2)*0.13<$bh) {
push(@l, @{shift @contents}, {v=>""});
}
if ($#l<0) {
push(@l, splice(@{$contents[0]},0,int($bh/0.13)-2));
}
push(@box, {h=>$bh,w=>$width,ys=>0.13,lines=>[@l]});
$h += $bh;
}
return ($h, @box);
}
sub draw_boxes {
my ($xoff, $yoff, $toff, $dir, $font, @box) = @_;
my $y;
if (defined $postscript) {
$out->setfont ("Courier-Bold", $font);
}
foreach my $b (@box) {
if (defined $postscript) {
$out->box($xoff, $yoff, $xoff+$b->{w}, $yoff-$b->{h});
}
$yoff-=$b->{h};
foreach my $i (0..$#{$b->{lines}}) {
if ($dir>0) {
$y=$yoff+0.05+$i*$b->{ys};
} else {
$y=$yoff-0.26-$i*$b->{ys}+$b->{h};
}
if (defined $postscript) {
$out->text ($toff, $y, $b->{lines}[$i]{v});
} else {
print "$b->{lines}[$i]{v}\n";
}
}
}
}
# construct an associative array from key/value pairs which amanda puts into
# sqare brackets.
#
sub hash {
my ($input)=shift;
$input =~ s/[\[\]]//g;
$input =~ s/\{.*\}//;
$input =~ s/\s+$//;
my %h = (@_, split (/\s+/, $input));
return \%h;
}
# translate seconds into h:mm:ss
#
sub sec {
my ($v)=@_;
my $h = int ($v / 3600);
my $m = int ($v / 60) % 60;
my $s = $v % 60;
return sprintf "%2d:%02d:%02d", $h, $m, $s;
}
# translate kbytes into a unit with 2..4 digits
#
sub kb {
my ($v)=@_;
my $app = "k";
if ($v>9999) { $app="M"; $v /= 1024; }
if ($v>9999) { $app="G"; $v /= 1024; }
if ($v>9999) { $app="T"; $v /= 1024; }
return int ($v) . $app;
}
-------------------------snip-----------------------
--
-- Josef Wolf -- jw AT raven.inka DOT de --
|