From e547e639b14072bc238cf161669bc3d94a8d6e13 Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Fri, 10 Jul 2009 13:04:17 +0100 Subject: [PATCH] Working version of virt-df. --- df/virt-df.pl | 255 ++++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 213 insertions(+), 42 deletions(-) diff --git a/df/virt-df.pl b/df/virt-df.pl index a4664a8..2d666a0 100755 --- a/df/virt-df.pl +++ b/df/virt-df.pl @@ -39,16 +39,34 @@ virt-df - Display free space on virtual filesystems virt-df [--options] - virt-df [--options] guest [guest ...] + virt-df [--options] domname virt-df [--options] disk.img [disk.img ...] =head1 DESCRIPTION +C is a command line tool to display free space on virtual +machine filesystems. Unlike other tools, it doesn't just display the +amount of space allocated to a virtual machine, but can look inside +the virtual machine to see how much space is really being used. +It is like the L command, but for virtual machines, except that +it also works for Windows virtual machines. +If used without any arguments, C checks with libvirt to get a +list of all active and inactive guests, and performs a C-type +operation on each one in turn, printing out the results. +If used with any argument(s), C performs a C-type +operation on either the single named libvirt domain, or on the disk +image(s) listed on the command line (which must all belong to a single +VM). In this mode (with arguments), C will I. If you want to run on multiple guests, then you have +to invoke C multiple times. +Use the C<--csv> option to get a format which can be easily parsed by +other programs. Other options are mostly similar to standard C +options. See below for the complete list. =head1 OPTIONS @@ -68,60 +86,216 @@ my $uri; =item B<--connect URI> | B<-c URI> -If using libvirt, connect to the given I. If omitted, -then we connect to the default libvirt hypervisor. +If using libvirt, connect to the given I. If omitted, then we +connect to the default libvirt hypervisor. -Libvirt is only used if you specify a C on the -command line. If you specify guest block devices directly, -then libvirt is not used at all. +If you specify guest block devices directly, then libvirt is not used +at all. =cut -GetOptions ("help|?" => \$help, - "connect|c=s" => \$uri, - ) or pod2usage (2); -pod2usage (1) if $help; -pod2usage ("$0: no image or VM names given") if @ARGV == 0; +my $csv; -# my $g; -# if ($uri) { -# $g = open_guest (\@ARGV, rw => $rw, address => $uri); -# } else { -# $g = open_guest (\@ARGV, rw => $rw); -# } +=item B<--csv> -# $g->launch (); -# $g->wait_ready (); +Write out the results in CSV format (comma-separated values). +This format can be imported easily into databases and spreadsheets. -# # List of possible filesystems. -# my @partitions = get_partitions ($g); +=cut -# # Now query each one to build up a picture of what's in it. -# my %fses = -# inspect_all_partitions ($g, \@partitions, -# use_windows_registry => $windows_registry); +my $human; -# #print "fses -----------\n"; -# #print Dumper(\%fses); +=item B<--human-readable> | B<-h> -# my $oses = inspect_operating_systems ($g, \%fses); +Print sizes in human-readable format. -# #print "oses -----------\n"; -# #print Dumper($oses); +=cut + +my $inodes; -# # Mount up the disks and check for applications. +=item B<--inodes> | B<-i> -# my $root_dev; -# foreach $root_dev (sort keys %$oses) { -# my $os = $oses->{$root_dev}; -# mount_operating_system ($g, $os); -# inspect_in_detail ($g, $os); -# $g->umount_all (); -# } +Print inodes instead of blocks. + +=back + +=cut + +GetOptions ("help|?" => \$help, + "connect|c=s" => \$uri, + "csv" => \$csv, + "human-readable|human|h" => \$human, + "inodes|i" => \$inodes, + ) or pod2usage (2); +pod2usage (1) if $help; + +# Open the guest handle. + +if (@ARGV == 0) { + my $conn; + + if ($uri) { + $conn = Sys::Virt->new (readonly => 1, address => $uri); + } else { + $conn = Sys::Virt->new (readonly => 1); + } + + my @doms = $conn->list_defined_domains (); + push @doms, $conn->list_domains (); + + my @domnames = map { $_->get_name () } @doms; + + if (@domnames) { + print_title (); + foreach (@domnames) { + do_df ($_); + } + } +} else { + print_title (); + do_df (@ARGV); +} + +sub do_df +{ + my $g; + + if ($uri) { + $g = open_guest (\@_, address => $uri); + } else { + $g = open_guest (\@_); + } + + $g->launch (); + $g->wait_ready (); + + my @partitions = get_partitions ($g); + + # Think of a printable name for this domain. Just choose the + # first parameter passed to this function, which will work for + # most cases (it'll either be the domain name or the first disk + # image name). + my $domname = $_[0]; + + # Mount each partition in turn, and if mountable, do a statvfs on it. + foreach my $partition (@partitions) { + my %stat; + eval { + $g->mount_ro ($partition, "/"); + %stat = $g->statvfs ("/"); + }; + if (!$@) { + print_stat ($domname, $partition, \%stat); + } + $g->umount_all (); + } +} + +sub print_stat +{ + my $domname = shift; + my $partition = shift; + my $stat = shift; + + my @cols = ($domname, $partition); + + if (!$inodes) { + my $bsize = $stat->{bsize}; # block size + my $blocks = $stat->{blocks}; # total number of blocks + my $bfree = $stat->{bfree}; # blocks free (total) + my $bavail = $stat->{bavail}; # blocks free (for non-root users) + + my $factor = $bsize / 1024; + + push @cols, $blocks*$factor; # total 1K blocks + push @cols, ($blocks-$bfree)*$factor; # total 1K blocks used + push @cols, $bavail*$factor; # total 1K blocks available + + # XXX %used column comes out different from the native 'df' + # program. Need to check how 'df' calculates this. + push @cols, 100.0 - 100.0 * $bavail / $blocks; + + if ($human) { + $cols[2] = human_size ($cols[2]); + $cols[3] = human_size ($cols[3]); + $cols[4] = human_size ($cols[4]); + } + } else { + my $files = $stat->{files}; # total number of inodes + my $ffree = $stat->{ffree}; # inodes free (total) + my $favail = $stat->{favail}; # inodes free (for non-root users) + + push @cols, $files; + push @cols, $files-$ffree; + push @cols, $ffree; + + # XXX %used column comes out different from the native 'df' + # program. Need to check how 'df' calculates this. + push @cols, 100.0 - 100.0 * $favail / $files; + } + + print_cols (@cols); +} + +sub print_title +{ + my @cols = ("Virtual Machine", "Filesystem"); + if (!$inodes) { + if (!$human) { + push @cols, "1K-blocks"; + } else { + push @cols, "Size"; + } + push @cols, "Used"; + push @cols, "Available"; + push @cols, "Use%"; + } else { + push @cols, "Inodes"; + push @cols, "IUsed"; + push @cols, "IFree"; + push @cols, "IUse%"; + } + + if (!$csv) { + # ignore $cols[0] in this mode + printf "%-36s%10s %10s %10s %5s\n", + $cols[1], $cols[2], $cols[3], $cols[4], $cols[5]; + } else { + print (join (",", @cols), "\n"); + } +} + +sub print_cols +{ + if (!$csv) { + my $label = sprintf "%s:%s", $_[0], $_[1]; + + printf ("%-36s", $label); + print "\n"," "x32 if length ($label) > 36; + + my $percent = sprintf "%3.1f%%", $_[5]; + printf ("%10s %10s %10s %5s\n", $_[2], $_[3], $_[4], $percent); + } else { + printf ("\"%s\",\"%s\",%d,%d,%d,%.1f%%\n", @_); + } +} + +# Convert a number of 1K blocks to a human-readable number. +sub human_size +{ + local $_ = shift; + + if ($_ < 1024) { + sprintf "%dK", $_; + } elsif ($_ < 1024 * 1024) { + sprintf "%.1fM", ($_ / 1024); + } else { + sprintf "%.1fG", ($_ / 1024 / 1024); + } +} =head1 SEE ALSO -L, L, L, L, @@ -129,9 +303,6 @@ L, L, L. -For Windows registry parsing we require the C program -from L. - =head1 AUTHOR Richard W.M. Jones L -- 1.8.3.1