###########################################################################
# $Id: zz-zfs 150 2013-06-18 22:19:38Z mtremaine $
###########################################################################
# $Log$
#
###########################################################################

###########################################################################
# zz-zfs: Output states of ZFS pools and datasets
#
#       Detail Levels:
#        0: Output list of pools and capacities
#        5: Output full pool status (automatic if any pools are not healthy)
#
###########################################################################

#######################################################
## Copyright 2011 Cloyce D. Spradling
## Covered under the included MIT/X-Consortium License:
##    http://www.opensource.org/licenses/mit-license.php
## All modifications and contributions by other persons to
## this script are assumed to have been donated to the
## Logwatch project and thus assume the above copyright
## and licensing terms.  If you want to make contributions
## under your own copyright or a different license this
## must be explicitly stated in the contribution an the
## Logwatch project reserves the right to not accept such
## contributions.  If you have made significant
## contributions to this script and want to claim
## copyright please contact logwatch-devel@lists.sourceforge.net.
#########################################################

use strict;
use vars qw($logwatch_hostname $DebugCounter);
use POSIX;

# Keep the pipes hot
$| = 1;

$ENV{PRINTING} = "y";

my $debug = $ENV{'LOGWATCH_DEBUG'} || 0;
my $detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || $ENV{'zfs_detail'} || 0;

my ($os_name, $host, $os_release, $version, $machine) = POSIX::uname();

# Check to see if we should even be looking at this...
$host =~ s/\..*//;  # Trim domain (if any)
if ($ENV{'LOGWATCH_ONLY_HOSTNAME'} && ($logwatch_hostname ne $host)) {
  exit 0;
}

my $pathto_zpool = $ENV{'pathto_zpool'} || '/usr/sbin/zpool';
my $pathto_zfs   = $ENV{'pathto_zfs'}   || '/usr/sbin/zfs';
my $summary_only = $ENV{'summary_only'} || ($detail < 5);
my $detail_only  = $ENV{'detail_only'}  || 0;

if (!-x $pathto_zpool) {
   # Doesn't support ZFS
   exit 0;
}

if ( $debug >= 5 ) {
    print STDERR "\n\nDEBUG: Inside zz-zfs\n\n";
    $DebugCounter = 1;
}

my @pools = ();
my %poolinfo = ();
my %counts = ( 'mounted'    => 0,
               'filesystem' => 0,
               'snapshot'   => 0,
               'volume'     => 0,
             );
my $total_pools = 0;

# Table for converting things to kibibytes
my %units = ( 'P' => 1024 * 1024 * 1024 * 1024,
              'T' => 1024 * 1024 * 1024,
              'G' => 1024 * 1024,
              'M' => 1024,
              'K' => 1,
            );
my $unit_re = '['.join('', keys %units).']';

# Discover the pools
open POOLS, '-|', $pathto_zpool, qw(list -H -o name,size,allocated,free,dedupratio,capacity,health) or die "Error running 'zpool list': $!\n";
while(<POOLS>) {
   chomp;
   my ($name, $size, $used, $avail, $dedup, $cap, $health) = split(/\s+/);
   next unless $name ne '';
   $size = convert_to_kb($size);
   $used = convert_to_kb($used);
   $avail = convert_to_kb($avail);
   print STDERR "\nPOOLS: \"$_\" name=\"$name\" size=$size used=$used avail=$avail dedup=$dedup cap=\"$cap\" health=\"$health\"\n" if ($debug);
   push @pools, [ $name, $size, $used, $avail, $dedup, $cap, $health ];
}
close(POOLS);

exit 0 unless @pools;   # Nothing to do?

# Read filesystem information for each pool
foreach my $type (qw( filesystem volume snapshot ) ) {
   foreach my $pool (map { $_->[0] } @pools) {
      open POOLINFO, '-|', $pathto_zfs, qw(list -H -t), $type, qw(-o name,referenced,available,mountpoint -r), $pool or die "Error running 'zfs list': $!\n";
      while(<POOLINFO>) {
         next if /no datasets available/i;
         chomp;
         my ($name, $used, $avail, $mountpoint) = split(/\s+/);
         next unless $name ne '';
         $used = convert_to_kb($used);
         $avail = convert_to_kb($avail);
         print STDERR "\nPOOLINFO: \"$_\" name=\"$name\" used=$used avail=$avail mountpoint=\"$mountpoint\"\n" if ($debug);
         push @{$poolinfo{$pool}->{$type}}, [ $name, $used, $avail, $mountpoint ];
         $counts{$type}++;
         $counts{'mounted'}++ if ($type eq 'filesystem' && $mountpoint ne 'none');
      }
      close(POOLINFO)
   }
}

print "Total ZFS pools:   ".(@pools+0)."\n";
print "Total filesystems: $counts{'filesystem'} ($counts{'mounted'} mounted)\n";
print "Total snapshots:   $counts{'snapshot'}\n";
print "Total volumes:     $counts{'volume'}\n";


if (!$detail_only) {
   my $pool_format = "%2s%-15s  %-10s  %-10s  %-10s  %-10s  %s\n";
   print "\n------------------- ZFS Pool Summary -------------------\n\n";
   printf $pool_format, '', 'Pool Name', 'Size (MiB)', 'Used (MiB)', 'Free (MiB)', 'Dedup', '';
   foreach my $poolref (@pools) {
      my ($name, $size, $used, $avail, $dedup, $cap, $health) = @{$poolref};
      my $badflag = ($health eq 'ONLINE') ? '' : '!!';
      $detail = 1000 if $badflag ne '';	# Show status if something's wrong
      printf $pool_format, $badflag, $name, convert_to_mb($size, 10), convert_to_mb($used, 10), convert_to_mb($avail, 10), $dedup, $cap;
   }
   print "\n--------------------------------------------------------\n\n";
}

if (!$summary_only || $detail > 999) {
   print "\n------------------- ZFS Pool Status -------------------\n\n";
   foreach my $pool (map { $_->[0] } @pools) {
      system $pathto_zpool, 'status', $pool;
   }
   print "\n-------------------------------------------------------\n\n";
}

sub convert_to_kb {
   my ($val) = @_;

   my ($num, $unit) = ($val =~ /([\.\d]+)\s*($unit_re)?/io);
   return 0 if $num eq '';
   $unit = uc($unit);

   return $num * $units{$unit};
}

sub convert_to_mb {
   my ($kb, $width) = @_;
   $width = 0 unless defined($width);

   $kb /= 1024;
   $kb = int(($kb * 10) + 0.5) / 10;

   return sprintf '%*.1f', $width, $kb;
}

# vi: shiftwidth=3 tabstop=3 syntax=perl et
