#!/usr/bin/perl -w # afsss - afss space statistics # # Copyright 2006, 2011 Stephan Wiesand # # This program is free software; you may redistribute and/or # modifiy it under the same terms as Perl itself. # # # 2006-05-11 sw: perl 5.8.8, minor fix for AFS-2.4.0 # 2006-09-02 sw: o DNS fix from Christopher Odenbach # o ignore for "" volumes (vos move targets) # 2011-03-31 sw: use /usr/bin/perl, do not use AFS Modules use strict; use IO::Handle; use Net::Domain qw(hostname hostfqdn hostdomain); STDOUT->autoflush(1); my $opt_l = 0; my $opt_p = 0; my $opt_r = 0; my $opt_v = 0; my $opt_i = 0; my $K = 1024.; my $cell = ""; my %servers_in = (); my $aaw_main = 0; &parse_cmdline(); unless($aaw_main){ if(exists $ENV{COLUMNS}){ $aaw_main = $ENV{COLUMNS} -1; }else{ for(`stty -a 2>/dev/null`){ if(/columns (\d+)/){ $aaw_main = $1 - 1; last; } } } $aaw_main = 79 unless $aaw_main; } my %servers = (); &get_servers(); &get_partitions(); if($opt_l){ &get_volumes(); &compute_allocations(); } &do_partitions() if $opt_p; &sep("Summary") if $opt_p; &prettyprint(); exit 0; for my $s(sort keys %servers){ print "$s\n"; for my $p(sort keys %{$servers{$s}}){ print " $p\n"; print " size: ",$servers{$s}{$p}{size},"\n"; print " free: ",$servers{$s}{$p}{free},"\n"; print " vols:\n"; for my $v(sort keys %{$servers{$s}{$p}{vols}}){ print " ",$v,"\n"; } } } sub usage(){ my $me = $0; $me =~ s|.*/||; print "usage: $me [-v] [-l] [-i] [-p[r]] [-w ] [-c[ell] ] [[/]...]...\n"; print "run \"perldoc $me\" for more documentation\n"; exit 0; } sub get_partitions(){ print "querying servers for partitions: " if $opt_v; for my $s(keys %servers){ print "$s..." if $opt_v; chomp(my @l = `vos partinfo $s $cell -noauth`); die "vos partinfo $s $cell failed" if $?; for (@l){ m/\/vicep([a-z]+):\s+(\d+)\s+K.*total\s+(\d+)/ or die "cannot parse vos partinfo line: |$_|"; my ($p, $free, $size) = ($1,$2,$3); if(exists $servers_in{$s} && keys %{$servers_in{$s}}){ next unless exists $servers_in{$s}{$p}; } $servers{$s}{$p}{size} = $size / $K / $K; #/ $servers{$s}{$p}{free} = $free / $K / $K; # / $servers{$s}{$p}{vols} = (); } if(exists $servers_in{$s}){ for(keys %{$servers_in{$s}}){ die "partition not found: $s"."/".$_ unless exists $servers{$s}{$_}; } } } print "\n" if $opt_v; } sub get_servers(){ print "querying vldb for servers...\n" if $opt_v; chomp(my @l = `vos listaddrs $cell -noauth`); die "vos listaddrs $cell failed" if $?; for my $n(@l){ # bad if server in subdomain: #$n =~ s/\..*//; my $d = ".".hostdomain(); if(rindex($n, $d) == length($n) - length($d)){ $n = substr($n, 0, length($n)-length($d)); } if(keys %servers_in){ next unless exists $servers_in{$n}; } $servers{$n} = (); } for(keys %servers_in){ die "server not found: $_" unless exists $servers{$_}; } } sub get_volumes(){ print "querying servers for volumes:" if $opt_v; for my $s(keys %servers){ print " ",$s,"..." if $opt_v; for my $p(keys %{$servers{$s}}){ print "$p..." if $opt_v; chomp(my @l = `vos listvol $s $p -format $cell -noauth`); die "vos listvol $s $p -format $cell failed" if $?; my %vols = (); my $mode = 0; my $name = ""; for(@l){ if(/^BEGIN_OF_ENTRY/){ $mode = 1; next; }elsif(/^END_OF_ENTRY/){ $mode = 0; $name = ""; next; } next unless $mode; if(/^name\s+(\S+)/){ $name = $1; }elsif(/^diskused\s+(\d+)/){ $vols{$name}{size} = $1; }elsif(/^maxquota\s+(\d+)/){ $vols{$name}{quota} = $1; } } my @backups = (); my @readonlys = (); for my $v(keys %vols){ #print " |",$v,"|\n"; if($v =~ s/\.backup$//){ push @backups, $v; next; } if($v =~ s/\.readonly$//){ push @readonlys, $v; next; } $servers{$s}{$p}{vols}{$v}{size} = $vols{$v}{size} / $K / $K; # / $servers{$s}{$p}{vols}{$v}{quota} = $vols{$v}{quota} / $K / $K; # / } for(@backups){ die "backup without parent on $s $p: $_" unless exists($servers{$s}{$p}{vols}{$_}); $servers{$s}{$p}{vols}{$_}{backup} = 1; } for(@readonlys){ if(exists($servers{$s}{$p}{vols}{$_})){ $servers{$s}{$p}{vols}{$_}{clone} = 1; next; } my $v = $_ . ".readonly"; $servers{$s}{$p}{vols}{$v}{size} = $vols{$v}{size} / $K / $K; # / $servers{$s}{$p}{vols}{$v}{quota} = $vols{$v}{quota} / $K / $K; # / } } } print "\n" if $opt_v; } sub compute_allocations(){ for my $s(keys %servers){ for my $p(keys %{$servers{$s}}){ $servers{$s}{$p}{quota} = 0.; $servers{$s}{$p}{alloc} = 0.; for my $v(keys %{$servers{$s}{$p}{vols}}){ $servers{$s}{$p}{quota} += $servers{$s}{$p}{vols}{$v}{quota}; my $mult = 1; $mult += 1 if exists $servers{$s}{$p}{vols}{$v}{backup}; $mult += 1 if exists $servers{$s}{$p}{vols}{$v}{clone}; $servers{$s}{$p}{alloc} += $servers{$s}{$p}{vols}{$v}{quota} * $mult; $servers{$s}{$p}{vols}{$v}{alloc} = $servers{$s}{$p}{vols}{$v}{quota} * $mult; } } } } sub do_partitions(){ for my $s(sort keys %servers){ for my $p(sort keys %{$servers{$s}}){ &do_partition($s,$p); } } } sub do_partition($$){ my $s = shift; my $p = shift; print "\n"; my $tit = "\# ".$s."/".$p." \#"; print "#" x length($tit), "\n", $tit, "\n", "#" x length($tit), "\n"; my @vols = sort keys %{$servers{$s}{$p}{vols}}; unless(@vols){ print "[no volumes]\n"; return; } my $mxalloc = 0.; my $w = 0; for my $v(@vols){ my $l = length $v; $w = $l if $l > $w; $mxalloc = $servers{$s}{$p}{vols}{$v}{alloc} if $servers{$s}{$p}{vols}{$v}{alloc} > $mxalloc; } $w += 2; $mxalloc = $servers{$s}{$p}{size} unless $opt_r; my $aaw = $aaw_main - $w -3 -10 -10 - 1; my $fh = "%-".$w."s" . "%-3s". "%10s%10s"; my $fl = "%-".$w."s" . "%-3s". "%10.2f%10.2f"; printf $fh, "volume", "RB", "size", "quota"; printf "\n"; printf $fh, "=" x ($w-2), "==", "====", "====="; printf "\n"; for my $v(@vols){ my $rb = ( exists $servers{$s}{$p}{vols}{$v}{clone} ? "R" : "-"); $rb .= ( exists $servers{$s}{$p}{vols}{$v}{backup} ? "B" : "-"); printf $fl, $v, $rb, $servers{$s}{$p}{vols}{$v}{size},$servers{$s}{$p}{vols}{$v}{quota}; print " "; print &asciiart($aaw, $mxalloc, $mxalloc-$servers{$s}{$p}{vols}{$v}{size}, $servers{$s}{$p}{vols}{$v}{quota}, $servers{$s}{$p}{vols}{$v}{alloc}); printf "\n"; } } sub sep($){ print "\n"; my $tit = "\# ".shift()." \#"; print "#" x length($tit), "\n", $tit, "\n", "#" x length($tit), "\n"; } sub prettyprint(){ print "\n" if $opt_v; my $w_s = length("server"); my $w_p = length("part"); for my $s(keys %servers){ my $l = length($s); $w_s = $l if $l > $w_s; for my $p(keys %{$servers{$s}}){ my $l = length($p); $w_p = $l if $l > $w_p; } } ++$w_s;++$w_s; ++$w_p;++$w_p; my $fh = "%-".$w_s."s%-".$w_p."s"."%7s%7s%5s"; my $fl = "%-".$w_s."s%-".$w_p."s"."%7d%7d%4d%%"; my $fh2 = "%7s%5s%7s%5s"; my $fl2 = "%7d%4d%%%7d%4d%%"; my $aaw = $aaw_main; $aaw = $aaw - $w_s - $w_p -7 -7 -5; if($opt_l){ $aaw = $aaw -7 -5 -7 -5; } my %srvsize = (); my %srvfree = (); my %srvquota = (); my %srvalloc = (); my $totsize = 0.; my $totfree = 0.; my $totquota = 0.; my $totalloc = 0.; my $npartmax = 0; printf $fh, "server", "part", ($opt_i ? "GB" : "GiB"), "free", "use"; printf $fh2, "quota", "", "alloc", "" if $opt_l; printf "\n"; printf $fh, "======", "====", ($opt_i ? "==" : "==="), "====", "==="; printf $fh2, "=====", "=====", "=====", "=====" if $opt_l; printf "\n" ; for my $s(sort keys %servers){ my $npart = 0; for my $p(sort keys %{$servers{$s}}){ ++$npart; $npartmax = $npart if $npart > $npartmax; printf $fl, $s, $p, $servers{$s}{$p}{size},$servers{$s}{$p}{free}, ($servers{$s}{$p}{size}-$servers{$s}{$p}{free})*100./$servers{$s}{$p}{size}; $srvsize{$s} += $servers{$s}{$p}{size}; $srvfree{$s} += $servers{$s}{$p}{free}; if($opt_l){ printf $fl2, $servers{$s}{$p}{quota}, $servers{$s}{$p}{quota}*100./$servers{$s}{$p}{size}, $servers{$s}{$p}{alloc}, $servers{$s}{$p}{alloc}*100./$servers{$s}{$p}{size}; $srvquota{$s} += $servers{$s}{$p}{quota}; $srvalloc{$s} += $servers{$s}{$p}{alloc}; print &asciiart($aaw, $servers{$s}{$p}{size}, $servers{$s}{$p}{free}, $servers{$s}{$p}{quota}, $servers{$s}{$p}{alloc}); }else{ print &asciiart($aaw, $servers{$s}{$p}{size}, $servers{$s}{$p}{free},0.,0.); } print "\n"; } $totsize += $srvsize{$s}; $totfree += $srvfree{$s}; if($opt_l){ $totquota += $srvquota{$s}; $totalloc += $srvalloc{$s}; } } if($npartmax > 1){ print "\n"; printf $fh, "server", "part", ($opt_i ? "GB" : "GiB"), "free", "use"; printf $fh2, "quota", "", "alloc", "" if $opt_l; printf "\n"; printf $fh, "======", "====", ($opt_i ? "==" : "==="), "====", "==="; printf $fh2, "=====", "=====", "=====", "=====" if $opt_l; printf "\n" ; for my $s(sort keys %servers){ printf $fl, $s, "SUM", $srvsize{$s}, $srvfree{$s}, ($srvsize{$s}-$srvfree{$s})*100./$srvsize{$s}; if($opt_l){ printf $fl2, $srvquota{$s}, $srvquota{$s}*100./$srvsize{$s}, $srvalloc{$s}, $srvalloc{$s}*100./$srvsize{$s}; print &asciiart($aaw, $srvsize{$s}, $srvfree{$s}, $srvquota{$s}, $srvalloc{$s}); }else{ print &asciiart($aaw, $srvsize{$s}, $srvfree{$s},0.,0.); } print "\n"; } } return if keys %servers == 1; print "\n"; printf $fh, "server", "part", ($opt_i ? "GB" : "GiB"), "free", "use"; printf $fh2, "quota", "", "alloc", "" if $opt_l; printf "\n"; printf $fh, "======", "====", ($opt_i ? "==" : "==="), "====", "==="; printf $fh2, "=====", "=====", "=====", "=====" if $opt_l; printf "\n" ; printf $fl, "ALL", "SUM", $totsize, $totfree, ($totsize-$totfree)*100./$totsize; # / if($opt_l){ printf $fl2, $totquota, $totquota*100./$totsize, $totalloc, $totalloc*100./$totsize; print &asciiart($aaw, $totsize, $totfree,$totquota,$totalloc); }else{ print &asciiart($aaw, $totsize, $totfree,0.,0.); } print "\n"; } sub parse_cmdline(){ my $mode = "plain"; for(@ARGV){ if($mode eq "w"){ $aaw_main = int $_; $mode = "plain"; next; } if($mode eq "c"){ $cell = "-cell $_"; $mode = "plain"; next; } if(/^(-h|--help|-help|help)$/){ &usage(); } if(/^-w$/){ $mode = "w"; next; } if(/^-c(ell)?/){ $mode = "c"; next; } if(s/^-l/-/){ $opt_l = 1; next if /^-$/; redo; } if(s/^-p/-/){ $opt_p = $opt_l = 1; next if /^-$/; redo; } if(s/^-r/-/){ $opt_r = $opt_p = $opt_l = 1; next if /^-$/; redo; } if(s/^-v/-/){ $opt_v = 1; next if /^-$/; redo; } if(s/^-i/-/){ $opt_i = 1; $K = 1000.; next if /^-$/; redo; } &usage if /^-/; unless(m|/|){ $servers_in{$_} = (); next; } s|^([^/]+)||; my $s = $1; while(s|^/([^/]+)||){ $servers_in{$s}{$1} = 1; } } if(0){ print "opt_l: $opt_l\n"; print "opt_p: $opt_p\n"; print "opt_r: $opt_p\n"; print "servers & partitions:\n"; for my $s(sort keys %servers_in){ print " $s: "; unless(keys %{$servers_in{$s}}){ print "ALL\n"; next; } print join(" ",sort keys %{$servers_in{$s}}), "\n"; } } } sub asciiart($$$$$){ # |######=====----- | # |uuuuuuqqqqqaaaaa w my $w = shift; return "" unless $w > 0; my $t = shift; my $f = shift; my $q = shift; my $a = shift; $t = 0.1 if $t < 0.1; my $u = $t - $f; my $nu = int(($u/$t)*$w+.5); my $nq = int(($q/$t)*$w+.5); my $na = int(($a/$t)*$w+.5); my $r = ""; for(my $i = 1; $i <= $w; ++$i){ my $c = " "; $c = "|" if $i == $w; $c = "|" if $i == 1; $c = "-" if ($i <= $na); $c = "=" if ($i <= $nq); $c = "#" if ($i <= $nu); $c = "-" if ($i <= $na && $na < $nu); $c = "=" if ($i <= $nq && $nq < $nu); $r .= $c; } return " ".$r; } =head1 NAME B - show afs space statistics =head1 SYNOPSIS B [-v] [-l] [-i] [-p[r]] [-w I] [-c[ell] I] [I[/I]...]... =head1 DESCRIPTION B prints information about AFS file servers, their vice partitions, and the volumes on them. The order of parameters and switches on the command line is completely arbitrary. Omitting the I argument will print information for all fileservers in the cell. If one or more Is are specified, only those are queried. Any I argument may be qualified with one or more /I arguments. For example, B will print and summarize information for all vice partitions on io, partition /vicepa on iokaste, and partitions /vicepcc, /vicepdb, /vicepdc on remus. Summaries are printed per server and for the grand total of all servers. Redundant summaries are omitted. Without the I<-l> argument, only the size and free space on the partition are queried, while with the I<-l> switch, all volumes on the affected partitions are queried as well. In this case, their quotas as well as the fact whether or not htey have backup and readonly clones on the same partition, are used to calculate the commitment of the partition: "Quota" specifies the sum of the quotas of all volumes on the partition, while "Alloc" gives the worst case allocation of space on the partition for this volume (which may be three times the quota, since the r/w, r/o and backup volume may all contain completely different data). Using the I<-l> switch may be slow, in particular if several busy fileservers are queried. The I<-v> option will provide output to spot the server causing the delays and entertain the user while waiting. By default, units are Gibibytes. The I<-i> switch changes this to SI (1 GB = 10^9 bytes). It should be obvious what the I<-cell> switch does. =head1 Visualization ASCII art gauges are drawn for each value to visualize the usage and allocation of a partition. A '#' character means space used, a '=' character means space allowed by quota, and a '-' character means space allocated to backup and readonly clones. (Notice the latter two are only available with I<-l>). Examples: B<#############=========-- |> The partition is about 40% filled, and some additional 30% are allowed by quotas, and clones may temporarily consume a bit more space, but the partition can never fill up completely. B<==============######------------> The current usage is above the sum of all quotas, due to copy-on-write. This partition is committed by 100%, or even more. B<##===========================--|> The partition is almost unused, but quotas exist that allow users to fill it up almost completely, and clones may consume almos all remaining space, although the partition is not overcommitted. If the environment variable I<$COLUMNS> is set, it will be used to calculate the maximum possible width of the gauges. The I<-w> switch allows overriding the value for I<$COLUMNS>. If the available width is insufficient, no gauges will be drawn. Hence setting it to a small but nonzero value (like 1) will disable drawing. =head1 Partition Details With the I<-p> switch, details about all volumes on each queried partition will be printed as well. Notice I<-p> implies I<-l>. Again, ASCII art gauges will be drawn to visualize the data. By default, these are normalized to the size of the partition. This allows spotting volumes of significant size or allocation. When examining partitions with many small volumes, one should give the I<-r> switch in addition, which will normalize the per-volume gages to the allocation for the largest volume on a partition. =head1 Examples =head2 afsss quick usage statistics for all servers and partitions =head2 afsss remus romulus the same, but only for these servers =head2 afsss remus romulus -l the same, with additional data on quotas and allocation =head2 afsss remus/fa romulus/ba -p detailed info about these partitions only =head2 afsss romulus/ba -pr detailed info for just one partition, with gages normalized to the most allocated volume =head1 AUTHOR 2005-03-13 Stephan Wiesand 2006-09-02 sw: - minor fixes 2011-03-31 sw: - made independent of AFS perl modules - implemented -i and -cell options