From db345818e94a6ed712b6be68c3204e60c6972773 Mon Sep 17 00:00:00 2001 From: "Alan J. Pippin" Date: Sat, 1 Mar 2008 10:31:26 -0700 Subject: [PATCH] Initial commit/revision of a number of ZFS scripts and utilities. --- zfs-autosnap | 111 ++++++++++++++++++++++++++++++++ zfs-autosnap-all | 27 ++++++++ zfs-diff | 150 ++++++++++++++++++++++++++++++++++++++++++++ zfs-log-parser | 94 +++++++++++++++++++++++++++ zfs-replicate | 111 ++++++++++++++++++++++++++++++++ zfs-replicate-all | 61 ++++++++++++++++++ zfs-scrub | 31 +++++++++ zfs-snapshot-totals | 46 ++++++++++++++ 8 files changed, 631 insertions(+) create mode 100755 zfs-autosnap create mode 100755 zfs-autosnap-all create mode 100755 zfs-diff create mode 100755 zfs-log-parser create mode 100755 zfs-replicate create mode 100755 zfs-replicate-all create mode 100755 zfs-scrub create mode 100755 zfs-snapshot-totals diff --git a/zfs-autosnap b/zfs-autosnap new file mode 100755 index 0000000..7273e57 --- /dev/null +++ b/zfs-autosnap @@ -0,0 +1,111 @@ +#!/bin/bash + +# Author: Carl Baldwin & Alan Pippin +# Description: This script takes a snapshot of the given zfs filesystem. +# It also employs an intelligent algorithm to roll off, +# or destroy, old snapshots. + +test=0 + +[ $test == 0 ] && exec >> /var/log/zfs-autosnap.log 2>&1 + +# This script makes the following assumptions/requirements: +# * this script only handles one zfs filesystem, a wrapper should be created +# to handle more +# * this script handles all snapshots that are named in this format: +# YYYY-MM-DD.hh.mm +# * It ignores other snapshots that don't follow this naming convention + +# This converts the YYYY-MM-DD.hh.mm format to an integer. +datetime_to_minutes() { + perl -n -e '/(\d+)-(\d+)-(\d+)\.(\d+)\.(\d+)/; print $1 * 527040 + $2 * 44640 + $3 * 1440 + $4 * 60 + $5,"\n"' +} + +datetime_to_minutes2() { + perl -n -e '/(\d+)-(\d+)-(\d+)\.(\d+)\.(\d+)/; $monthadj=int(($2-1)/3)-1; $minadj=int($5/15); $houradj=int($4/3); print $1 * 1048576 + ( $2 + $monthadj ) * 65536 + $3 * 2048 + ( $4 + $houradj ) * 64 + $5 + $minadj,"\n"' +} + +# test: if set to 1, no zfs snapshot commands will be run, they will only be echoed +# filesystem: This is the zfs filesystem to snapshot +# mountpoint: This is the mountpoint of the zfs filesystem to snapshot +# numsnapshots: This number is the number of equally spaced snapshots that should exist over any given period in the past +# maxagedays: This is the maximum number of days to keep any snapshot around for (0=infinite) (default=0). +filesystem=$1 +mountpoint=$2 +numsnapshots=${3-20} +maxagedays=${4-0} + +lockdir="/tmp/zfs-admin-lock" +pool=`echo "$filesystem" | awk -F '/' '{ print $1 }'` + +if [ -z "$filesystem" ] || [ -z "$mountpoint" ] || [ -z "$numsnapshots" ] || [ -z "$maxagedays" ]; then + echo "-E- Usage: $0 " + exit 1 +fi + +if [ ! -d "$mountpoint" ]; then + echo "-E- Unable to find the mountpoint: $mountpoint" + exit 1 +fi + +snapshotdir="${mountpoint}/.zfs/snapshot" +if [ ! -d "$snapshotdir" ]; then + echo "-E- Unable to find the snapshotdir: $snapshotdir" + exit 1 +fi + +# Check to see if this zfs pool has a scrub being performed on it now. +# If it does, we cannot perform any snapshot create or destroy operations. +zpool status $pool | grep scrub: | grep "in progress" > /dev/null 2>&1 +if [ $? == 0 ]; then + echo "-W- The zfs pool '$pool' is currently being scrubbed. Skipping all snapshot operations." + exit 0 +fi + +# Get the various components of the date +datetime=${ZFSDATETIME:-$(date +%Y-%m-%d.%H.%M)} + +# Create the snapshot for this minute +echo "-I- Creating ${filesystem}@${datetime}" +[ $test == 0 ] && zfs snapshot "${filesystem}@${datetime}" + +minutes=$(echo $datetime | datetime_to_minutes) + +# Check to ensure the zfs filesystem has not been locked down. +# If it has, we cannot perform any snapshot destroy operations. +if [ -d "$lockdir" ]; then + echo "-W- The zfs filesystem has been locked down. Skipping snapshot cleanup." + exit 0 +fi + +# Trim them down +snapshots=$(ls -d ${snapshotdir}/????-??-??.??.?? 2>/dev/null) +for snapshot in $snapshots; do + snapminutes=$(echo "$snapshot" | sed 's,.*/,,' | datetime_to_minutes) + snapminutes2=$(echo "$snapshot" | sed 's,.*/,,' | datetime_to_minutes2) + age=$((minutes - snapminutes)) + window=1 + while true; do + if [ $age -lt $((window * numsnapshots)) ]; then + case $((snapminutes2 % window)) in + 0) ;; + *) + snapname=${filesystem}$(echo "$snapshot" | + sed 's,/\(.*\)/.zfs/snapshot/\(.*\),@\2,') + echo "-I- Destroying $snapname" + [ $test == 0 ] && zfs destroy "$snapname" + ;; + esac + break + fi + window=$((window*2)) + done + if [ $maxagedays -gt 0 ] && [ $age -gt $((maxagedays * 24 * 60)) ]; then + snapname=${filesystem}$(echo "$snapshot" | + sed 's,/\(.*\)/.zfs/snapshot/\(.*\),@\2,') + echo "-I- Destroying old $snapname" + [ $test == 0 ] && zfs destroy "$snapname" + fi +done + +true diff --git a/zfs-autosnap-all b/zfs-autosnap-all new file mode 100755 index 0000000..5892976 --- /dev/null +++ b/zfs-autosnap-all @@ -0,0 +1,27 @@ +#!/bin/bash + +# Author: Alan J. Pippin +# Description: This script is a wrapper script that calls zfs-autosnap +# for each filesystem provided below. + +# Setup some default values +autosnap="/usr/local/etc/bin/zfs-autosnap" +logfile="/var/log/zfs-autosnap.log" +numsnapshots=20 +maxagedays=365 + +# Auto snapshot every zfs filesystem on the system specified below +date >> $logfile + +# Special filesystems +$autosnap storage /storage $numsnapshots 15 +$autosnap tank/usr/videos /usr/videos $numsnapshots 15 + +# Normal filesystems +$autosnap tank / $numsnapshots $maxagedays +$autosnap tank/backup /backup $numsnapshots $maxagedays +$autosnap tank/usr /usr $numsnapshots $maxagedays +$autosnap tank/usr/home /usr/home $numsnapshots $maxagedays +$autosnap tank/usr/local /usr/local $numsnapshots $maxagedays +$autosnap tank/usr/local/etc /usr/local/etc $numsnapshots $maxagedays + diff --git a/zfs-diff b/zfs-diff new file mode 100755 index 0000000..44f355c --- /dev/null +++ b/zfs-diff @@ -0,0 +1,150 @@ +#!/usr/bin/perl + +use warnings; +use strict; +use File::Temp qw/ tempfile tempdir /; + +my $SSLPATH = '/usr/bin/openssl'; + +#Usage +sub print_usage { + print "ZFS Snapshot diff\n"; + print "\t$0 [-dhirv] [filename]\n\n"; + print " -d Display the lines that are different (diff output)\n"; + print " -h Display this usage\n"; + print " -i Ignore files that don't exist in the snapshot (only necessary for recursing)\n"; + print " -r Recursively diff every file in the snapshot (filename not required)\n"; + print " -v Verbose mode\n\n"; + + print " [filename] is the filename RELATIVE to the ZFS snapshot root. For example, if\n"; + print " I had a filesystem snapshot called pool/data/zone\@initial. The filename '/etc/passwd'\n"; + print " would refer to the filename /pool/data/zone/etc/passwd in the filesystem and filename\n"; + print " /pool/data/zone/.zfs/snapshot/initial/etc/passwd in the snapshot.\n\n"; + + print " A couple of examples:\n"; + print "\t$0 -v -r -i pool/zones/lava2019\@Fri\n"; + print "\t\tChecks the current pool/zones/lava2019 filesystem against the snapshot\n"; + print "\t\treturning the md5sum difference of any files (ignore files that don't\n"; + print "\t\texist in the snapshot). With verbose mode\n\n"; + + print "\t$0 -d pool/zones/lava2019\@Mon /root/etc/passwd\n"; + print "\t\tCheck the md5sum for /pool/zones/lava2019/root/etc/passwd and compare\n"; + print "\t\tit to /pool/zones/lava2019/.zfs/snapshot/Mon/root/etc/passwd. Display\n"; + print "\t\tthe lines that are different also.\n\n"; + + exit(0); +} + +use Getopt::Long; +my %options = (); +my $verbose; + +GetOptions("h" => \$options{help}, + "r!" => \$options{recurse}, + "d!" => \$options{diff}, + "i!" => \$options{ignore}, + "v!" => \$verbose + ); + +if ($options{help}) { + print_usage(); +} + +if ($options{recurse}) { + recurse_diff(shift || die "Need a ZFS snapshot name\n"); +} else { + my $zfsname = shift || die "Need a ZFS snapshot name\n"; + my $file = shift || die "Need a filename\n"; + diff_single_file($zfsname,$file); +} + +exit(0); + +sub recurse_diff { + my $zfssnap = shift; + print "Recursive diff on $zfssnap\n" if $verbose; + + $zfssnap =~ /(.+)\@(.+)/i; + my $fsname = "/" . $1; + my $snapname = $2; + if(! -d $fsname && $fsname =~ /\/\S+?(\/.*)/) { $fsname = $1; } + elsif(! -d $fsname && $fsname =~ /\/\S+?/) { $fsname = "/"; } + print "Filesystem: $fsname, Snapshot: $snapname\n" if $verbose; + + my $snappath = $fsname . "/.zfs/snapshot/" . $snapname . "/"; + my $fspath = $fsname . "/"; + $fspath =~ s/\/\//\//gi; + $snappath =~ s/\/\//\//gi; + print "Comparing: $fspath\nto: $snappath\n" if $verbose; + + my $dir = tempdir( CLEANUP => 0 ); + my ($fh, $filename) = tempfile( DIR => $dir ); + + `find $fspath -name "*" -type f > $filename`; + + foreach my $file (<$fh>) { + chomp($file); + $file =~ /(.*)\/(.*)/; + my $shortname = $2; + $file =~ /$fspath(.*)/; + my $diff = $snappath . $1; + if (!-e $diff) { + print "$file does not exist in snapshot\n" if !$options{ignore}; + next; + } + + # do the md5 sums + my $orig = `$SSLPATH md5 $file`; + my $snap = `$SSLPATH md5 $diff`; + $orig =~ /[\s\S]+= (.+)/; + my $sum1 = $1; + $snap =~ /[\s\S]+= (.+)/; + my $sum2 = $1; + if ($sum1 ne $sum2) { + print "** $file is different\n"; + print "** $orig** $snap" if $verbose; + } + if ($options{diff}) { + system("diff $file $diff"); + } + } +} + +sub diff_single_file { + my $zfssnap = shift; + my $filename = shift; + print "Single-file diff on $zfssnap, file: $filename\n" if $verbose; + + $zfssnap =~ /(.+)\@(.+)/i; + my $fsname = "/" . $1 . "/"; + my $snapname = $2; + if(! -d $fsname && $fsname =~ /\/\S+?(\/.*)/) { $fsname = $1; } + print "Filesystem: $fsname, Snapshot: $snapname\n" if $verbose; + + my $fspath; + if($filename !~ /^\//) { $fspath = $ENV{'PWD'} . "/" . $filename; } + else { $fspath = $filename; } + + my $snapfspath = $fspath; + $snapfspath =~ s/$fsname//g; + my $snappath = $fsname . "/.zfs/snapshot/" . $snapname . "/" . $snapfspath; + $fspath =~ s/\/\//\//gi; + $snappath =~ s/\/\//\//gi; + print "Comparing: $fspath\nto: $snappath\n" if $verbose; + if(! -f $fspath) { print "-E- Cannot find source file: $fspath\n"; exit 1; } + if(! -f $snappath) { print "-E- Cannot find source file: $snappath\n"; exit 1; } + + my $orig = `$SSLPATH md5 $fspath`; + my $snap = `$SSLPATH md5 $snappath`; + $orig =~ /[\s\S]+= (.+)/; + my $sum1 = $1; + $snap =~ /[\s\S]+= (.+)/; + my $sum2 = $1; + if ($sum1 ne $sum2) { + print "** Files are different\n"; + print "** $orig** $snap" if $verbose; + } + if ($options{diff}) { + system("diff $fspath $snappath"); + } +} diff --git a/zfs-log-parser b/zfs-log-parser new file mode 100755 index 0000000..7149609 --- /dev/null +++ b/zfs-log-parser @@ -0,0 +1,94 @@ +#!/usr/bin/perl + +# Author: Alan J. Pippin +# Description: This script parses logfiles generated by the following zfs scripts: +# zfs-replicate + +$logfile=shift(@ARGV); +$startdate=shift(@ARGV); + +sub usage { + print "Usage: $0 \n"; + exit 0; +} +if(! -f "$logfile") { &usage; } + +$kilo = 1024; +$mega = 1024 * 1024; +$giga = 1024 * 1024 * 1024; + +sub time_to_seconds { + my ($hour,$minute,$sec) = @_; + $seconds = ($hour * 60 * 60) + ($minute * 60) + ($sec); + return($seconds); +} + +sub adjust_duration { + my ($duration) = @_; + if($duration > 3600) { $duration=int($duration/3600); $duration.="h"; } + elsif($duration > 60) { $duration=int($duration/60); $duration.="m"; } + else { $duration.="s"; } + return $duration; +} + +sub adjust_data { + my ($data) = @_; + if($data > ($giga)) { $data = int($data / $giga); $data = "$data"."Gb"; } + elsif($data > ($mega)) { $data = int($data / $mega); $data = "$data"."Mb"; } + elsif($data > ($kilo)) { $data = int($data / $kilo); $data = "$data"."Kb"; } + return $data; +} + +sub parse_replicate_logfile { + $in_replicate=0; + $date=""; + %totals=(); + while() { + $line = $_; + if(($in_replicate == 0) && ("$startdate" ne "") && ($line !~ /$startdate/)) { next; } + if($line =~ /(\S+)\s+(\S+)\s+(\d+)\s+(\d+):(\d+):(\d+)\s+(\S+)\s+(\S+)/) { + $dayname=$1; $month=$2; $daynum=$3; $hour=$4; $minute=$5; $sec=$6; $year=$8; + if(($in_replicate == 0) && ($line =~ /lock obtained/)) { + $in_replicate = 1; + $date="$dayname $month $daynum $hour:$minute:$sec $year"; + $totals{$date}{data} = 0; + $totals{$date}{transfertime} = 0; + $totals{$date}{duration} = time_to_seconds($hour,$minute,$sec); + } + elsif(($in_replicate == 1) && ($line=~ /lock released/)) { + $in_replicate = 0; + $totals{$date}{duration} = time_to_seconds($hour,$minute,$sec) - $totals{$date}{duration}; + } + } + if(($in_replicate == 1) && ($line =~ /received ([\d\.]+)(\w+)/)) { + $data = $1; $size = $2; + if($size =~ /Kb/i) { $data = $data * $kilo; } + if($size =~ /Mb/i) { $data = $data * $mega; } + if($size =~ /Gb/i) { $data = $data * $giga; } + chomp($line); + $totals{$date}{data} += $data; + } + if(($in_replicate == 1) && ($line =~ /in (\d+) seconds/)) { + $transfertime = $1; + $totals{$date}{transfertime} += $transfertime; + } + } + + foreach $date (keys %totals) { + $duration=adjust_duration($totals{$date}{duration}); + $data=adjust_data($totals{$date}{data}); + $transfertime=adjust_duration($totals{$date}{transfertime}); + $rate = adjust_data(int($totals{$date}{data}/$totals{$date}{transfertime})); + print "$date: data=${data} transfertime=$transfertime rate=${rate}/sec duration=$duration\n"; + } +} + +######### +# MAIN +######### +#print "-> Parsing $logfile\n"; +open(FILE,"$logfile") || die "-E- Unable to open $logfile\n"; + +if($logfile =~ /replicate/) { parse_replicate_logfile(); } + +close(FILE); diff --git a/zfs-replicate b/zfs-replicate new file mode 100755 index 0000000..f878518 --- /dev/null +++ b/zfs-replicate @@ -0,0 +1,111 @@ +#!/bin/bash + +# Author: Carl Baldwin & Alan Pippin +# Description: This script replicates a given zfs filesystem to a given zfs pool. +# This script will keep all snapshots in sync, removing the ones +# that have been deleted since the last replicate was performed. +# This script will only send the new, or missing, snapshots since +# the last replicate was performed. + +# In test mode (test=1) commands are echoed, not executed +test=0 + +[ $test == 0 ] && exec >> /var/log/zfs-replicate.log 2>&1 + +# Usage: zfs-backup [filesystem] [destination_pool] +# This script has a limitation with children under a given filesystem. +# You must initially backup the parent filesystems first using this script +# before backing up any of the children filesystems. + +fs=$1 +fs=${fs%/} +fsname=${1#*/} +srcpool=${1%%/*} +srcfs="${srcpool}/$fsname" +srcfs=${srcfs%/} +dstpool=$2 +dstfs="${dstpool}/$srcfs" +dstfs=${dstfs%/} +nodstsnaps=0 +common="" + +if [ $test == 1 ]; then + echo "fs: $fs" + echo "fsname: $fsname" + echo "srcpool: $srcpool" + echo "srcfs: $srcfs" + echo "dstpool: $dstpool" + echo "dstfs: $dstfs" +fi + +if ! zpool list -H "$srcpool" >/dev/null 2>&1; then + echo >&2 "-E- The source pool, '$srcpool' doesn't seem to exist." + exit 1 +fi + +if ! zpool list -H "$dstpool" >/dev/null 2>&1; then + echo >&2 "-E- The destination pool, '$dstpool' doesn't seem to exist." + exit 1 +fi + +if ! zfs list -rH -t snapshot "$dstfs" 2>&1 | grep "$dstfs@" > /dev/null 2>&1; then + echo >&2 "-W- No snapshots detected on the destination drive for this filesystem" + nodstsnaps=1 +fi + +if [ $nodstsnaps == 0 ]; then + zfs list -rH -t snapshot $srcfs | grep "$srcfs@" | awk '{print $1}' > /tmp/source-list + zfs list -rH -t snapshot $dstfs | grep "$dstfs@" | sed "s,$dstpool/,," | awk '{print $1}' > /tmp/destination-list + diff -u /tmp/source-list /tmp/destination-list | grep -v '^+++' | awk '/^\+/ {print}' | sed "s,^\+,$dstpool/," > /tmp/obsolete-snapshots + rm -f /tmp/source-list /tmp/destination-list + + echo >&2 "Removing obsolete backups from the destination pool" + for snapshot in $(cat /tmp/obsolete-snapshots); do + echo >&2 "Removing '$snapshot' from destination." + [ $test == 0 ] && zfs destroy "$snapshot" + done + + echo >&2 "Rolling back to the most recent snapshot on the destination." + [ $test == 0 ] && zfs rollback $(zfs list -rH -t snapshot $dstfs | grep "$dstfs@" | awk '{snap=$1} END {print snap}') + + echo >&2 "Calculating the most recent common snapshot between the two filesystems." + if zfs list -H "$dstfs" > /dev/null 2>&1; then + for snap in $(zfs list -rH -t snapshot "$dstfs" | grep "$dstfs@" | + sed 's,.*@,,' | awk '{print$1}'); do + if zfs list -rH -t snapshot "$fs" | grep "$fs@" | sed 's,.*@,,' | awk '{print$1}' | grep "^${snap}$" >/dev/null 2>&1; then + common=$snap + fi + done + fi +fi + +base=$common +foundcommon=false +if [ -z "$common" ]; then + foundcommon=true +fi + +for snap in $(zfs list -rH -t snapshot "$fs" | grep "$fs@" | + sed 's,.*@,,' | awk '{print$1}'); do + if [ "$snap" = "$common" ]; then + foundcommon=true + continue + fi + + if $foundcommon; then + if [ -z "$base" ]; then + echo >&2 "Sending '$fs@$snap'" + [ $test == 0 ] && zfs set readonly=on "$dstpool" + [ $test == 0 ] && zfs set atime=off "$dstpool" + [ $test == 0 ] && zfs set sharenfs=off "$dstpool" + [ $test == 0 ] && zfs set mountpoint=legacy "$dstpool" + [ $test == 0 ] && zfs send "$fs@$snap" | zfs recv -v "$dstfs" + else + echo >&2 "Sending '$fs@$base' -> '$fs@$snap'" + [ $test == 0 ] && zfs send -i "$fs@$base" "$fs@$snap" | zfs recv -v "$dstfs" + fi + base=$snap + fi +done + +true diff --git a/zfs-replicate-all b/zfs-replicate-all new file mode 100755 index 0000000..4d7ff14 --- /dev/null +++ b/zfs-replicate-all @@ -0,0 +1,61 @@ +#!/bin/bash + +# Author: Alan J. Pippin +# Description: This script calls zfs-replicate for each filesystem needing +# to be backed up, or replicated, to another ZFS pool. + +# Setup some default values +replicate="/usr/local/etc/bin/zfs-replicate" +logfile_parser="/usr/local/etc/bin/zfs-log-parser" +logfile="/var/log/zfs-replicate.log" +lockdir="/tmp/zfs-admin-lock" +destpool="backups" +maxsleeptime=60 +released_lock_date=0 + +# Setup our cleanup and exit trap +cleanup() { + rm -rf "$lockdir" + if [ $released_lock_date == 0 ]; then + echo `date` ZFS admin lock released >> $logfile + fi +} +trap cleanup EXIT + +# Auto snapshot every zfs filesystem on the system specified below +date=`date`; +echo "$date Polling for ZFS admin lock" >> $logfile + +# Poll for a lock on the zfs subsystem, and make the lock once we can do so +while true; do + if ! mkdir "$lockdir" >/dev/null 2>&1; then + # Another zfs admin tool is running. + # Wait a random amount of time and try again + ransleep=$(($RANDOM % $maxsleeptime)) + sleep $ransleep + else + # No other zfs admin tool is running, we can now. + break + fi +done +date=`date`; +echo "$date ZFS admin lock obtained" >> $logfile + +# List the filesystems to replicate +# The parent filesystems MUST be listed ahead +# of the children filesystems. +# Pool root filesystems must end with a slash. +$replicate tank/ $destpool +$replicate tank/usr $destpool +$replicate tank/usr/home $destpool +$replicate tank/usr/videos $destpool +$replicate tank/usr/local $destpool +$replicate tank/usr/local/etc $destpool +$replicate tank/backup $destpool + +# Release our lock +released_lock_date=1 +echo `date` ZFS admin lock released >> $logfile + +# Parse the log file and extract our backup stats +$logfile_parser "$logfile" "$date" >> $logfile diff --git a/zfs-scrub b/zfs-scrub new file mode 100755 index 0000000..f022a54 --- /dev/null +++ b/zfs-scrub @@ -0,0 +1,31 @@ +#!/bin/bash + +# Author: Alan J. Pippin +# Description: This script will attempt to scrub each zfs pool +# given to it in the for loop. This script ensures +# That only 1 scrub operation is running at any given time. +# This serializes the zfs scrub process for each pool. + +maxsleeptime=360 + +for i in tank storage +do + # Check to see if this zfs filesystem has a scrub being performed on it now. + # If it does, we cannot perform more than one scrub operation at a time. + while true; do + /sbin/zpool status | grep scrub: | grep "in progress" > /dev/null 2>&1 + if [ $? == 0 ]; then + # Another zpool scrub operation is already running + # Wait until it is done before continuing + ransleep=$(($RANDOM % $maxsleeptime)) + sleep $ransleep + else + # Another zpool scrub operation is not running + break + fi + done + + echo "Scrubing zfs pool $i" + /sbin/zpool scrub $i + +done diff --git a/zfs-snapshot-totals b/zfs-snapshot-totals new file mode 100755 index 0000000..53bcd7b --- /dev/null +++ b/zfs-snapshot-totals @@ -0,0 +1,46 @@ +#!/usr/bin/perl + +@snapshots=`zfs list -t snapshot`; + +$kilo = 1024; +$mega = 1024 * 1024; +$giga = 1024 * 1024 * 1024; + +sub adjust_size +{ + my ($size) = @_; + if($size > ($giga)) { $size = int($size / $giga); $size = "$size"."G"; } + elsif($size > ($mega)) { $size = int($size / $mega); $size = "$size"."M"; } + elsif($size > ($kilo)) { $size = int($size / $kilo); $size = "$size"."K"; } + return $size; +} + +foreach $snapshot (@snapshots) +{ + chomp($snapshot); + if($snapshot =~ /(\S+)\@(\S+)\s+(\S+)\s+/) { + $filesystem = $1; + $size = $3; + if($size =~ /k/i) { $size = $size * $kilo; } + if($size =~ /m/i) { $size = $size * $mega; } + if($size =~ /g/i) { $size = $size * $giga; } + $totals{$filesystem}{size} += $size; + $totals{$filesystem}{snapshots}++; + } +} + +printf "%-20s %-15s %-10s\n","ZFS Filesystem","Snapshots Size","Num Snapshots"; +printf "%-20s %-15s %-10s\n","--","--","--"; +foreach $key (keys %totals) +{ + $size = $totals{$key}{size}; + $total_size += $size; + $total_snapshots += $totals{$key}{snapshots}; + $size = &adjust_size($size); + printf "%-20s %-15s %-10s\n", $key, $size, $totals{$key}{snapshots}; +} + +$total_size = &adjust_size($total_size); +printf "%-20s %-15s %-10s\n","--","--","--"; +printf "%-20s %-15s %-10s\n","Total Snapshots",$total_size,$total_snapshots; + -- 2.34.1