Back to the main page

Shell script for storage reporting

If you provide space for customer's data you probably get request from management to provide info about usable and allocated disk/storage space. Usable is what you provide to customer (if you have two mirrored 300G disks, you provide 300G or something less, since some space goes for metadata). Allocated is what is used at the moment and this is actually what you charge (billable storage).

Since I had variety of configurations and storages, like internal disks, DAS (directly attached storage), and SAN (storage area network), it was convenient for me to write script that easily generates storage report.
I had UFS/ZFS file systems. The UFS was on the top of SVM (Solaris Volume Manager).
I also had different file systems within one device, so script has to sum them and report one disk size as total for that system.
The idea was to have a configuration file with HOSTNAME:METADEVICE_OR_ZFS:DESC:UFS/ZFS and to have script that reads configuration file and generate the report.

The config file can be like:
# HOSTNAME:METADEVICE_OR_ZFS:DESC:UFS/ZFS
#-------------------------------------
# --------- sds1 StorEdge 6120
kingkong:t1_sds1-s0:sds1:zfs

# --------- sds2 StorEdge 6120
trex:fc.san.hrc.r10.sds2-s1:sds2:zfs

# --------- sds3 StorEdge 6120
trex:fc.san.hrc.r10.sds3-s0:sds3:zfs

# --------- sds4 StorEdge 6120
kingkong:sds4-s1:sds4:zfs

# --------- sds5 StorEdge 6120
kingkong:sds5-s0:sds5:zfs
snake:sds5-1:sds5:zfs

# --------- sds6 StorEdge 6120
trex:sds6-s2:sds6:zfs

# --------- sds7 StorEdge 6120
kingkong:sds7-s0:sds7:zfs

# --------- sds8 StorEdge 6120
kingkong:space:sds8:zfs

# ---------- tip-0 StorEdge 3510
gorilla:d100:tip-0:ufs

# ---------- tip-1 StorEdge 3510
gorilla:d200:tip-1:ufs

# --------- gorilla-internal SunFire X4200
gorilla:ora01:gorilla-internal:zfs

# ---------- winston StorEdge 6130
unicorn:d100:winston:ufs

# ----------  lucky StorEdge 6130
unicorn:d200:lucky:ufs

# --------- unicorn-internal SunFire X4200
unicorn:ora01:unicorn-internal:zfs

# --------- monkey SunFire X4500
monkey:pool.0:monkey:zfs
monkey:pool.1:monkey:zfs
monkey:pool.2:monkey:zfs

The script is

# cat reportstorage.sh
#!/bin/sh
#set -x
# -----------------------------------------------
# Gets storage report for manager's name
# -----------------------------------------------

# configuration file
global_infile=storage.conf

# prepare input file, ignore comments and blank lines, uniq (maybe duplicated entries), sort
cat ${global_infile} | sed -e '/^#/d' -e '/^$/d' | sort | uniq > ${global_infile}.$$

# separate input temporary file for ufs/zfs
ufs_infile=ufs_infile.$$
zfs_infile=zfs_infile.$$
cat ${global_infile}.$$ | awk -F: '$4=="ufs"' > ${ufs_infile}
cat ${global_infile}.$$ | awk -F: '$4=="zfs"' > ${zfs_infile}


# ---------------------------------
# functions
# -------------------------------

# --- FUNCTION: process hosts with ufs/metadevices
process_ufs() {

        # get list of hosts that has metadevices
        ufs_hostlist=`cat ${ufs_infile} | awk -F: '{print $1}' | sort | uniq`

        # process host
        for ufs_host in ${ufs_hostlist}
        do

                fping -q ${ufs_host} #-q=quite
                if [ $? -eq 0 ]
                then

                        # for system in the loop, get mount point of metadevice, from ${ufs_infile}
                        metadevice=`cat ${ufs_infile} | awk -F: '$1=="'${ufs_host}'" {print $2}'`

                        # process all metadevices in a system
                        for md in ${metadevice}
                        do

                                # find mount point of metadevice
                                mount=`ssh ${ufs_host} mount | gegrep -w  "/dev/md/dsk/${md}" | awk '{print $1}'`

                                # find total size of md
                                usable=`ssh ${ufs_host} df -k ${mount} | gegrep -v "^Filesystem" | awk '{print $2}'` # total

                                # find used size of md
                                alocated=`ssh ${ufs_host} df -k ${mount} | gegrep -v "^Filesystem" | awk '{print $3}'` #used

                                # from ${ufs_infile} get description of metadevice
                                ufs_desc=`cat ${ufs_infile} | awk -F: '$1=="'${ufs_host}'" && $2=="'${md}'" {print $3}'`

                                # screen output FYI
                                printf "%-20s %-20s %-20s %-20s %-20s \n" \
                                "Host:${ufs_host}" "Mount:${mount}" " Desc:${ufs_desc}" \
                                "Total[KB]=${usable}" "Used[KB]=${alocated}"

                                # feed tmp file for further processing
                                echo ${ufs_desc} >> ufs_desc.$$
                                echo "${ufs_desc} ${usable} ${alocated}" >> ufs_of.$$

                        done
                else
                        echo "Host ${ufs_host} is not reachable."
                fi

        done
}

# --- FUNCTION: process hosts with zfs
process_zfs() {

        # get list of hosts that has zfs
        zfs_hostlist=`cat ${zfs_infile} | awk -F: '{print $1}' | sort | uniq`

        # process host
        for zfs_host in ${zfs_hostlist}
        do

                fping -q ${zfs_host} #-q=quite
                if [ $? -eq 0 ]
                then

                        # for system in the loop, get zfs, from ${zfs_infile}
                        zetafs=`cat ${zfs_infile} | awk -F: '$1=="'${zfs_host}'" {print $2}'`

                        # process all zfs in a system
                        for zfs in ${zetafs}
                        do

                                # find used, avail, total size of zfs
                                used=`ssh ${zfs_host} zfs get -H -p used ${zfs} | awk '{print $3}'` # allocated in bytes
                                avail=`ssh ${zfs_host} zfs get -H -p avail ${zfs} | awk '{print $3}'` # free in bytes
                                total=`(echo "${used}+${avail}" | bc -l)` # total in bytes

                                # from ${zfs_infile} get description of zfs
                                zfs_desc=`cat ${zfs_infile} | awk -F: '$1=="'${zfs_host}'" && $2=="'${zfs}'" {print $3}'`

                                # screen output FYI
                                printf "%-20s %-20s %-20s %-20s %-20s \n" \
                                "Host:${zfs_host}" "ZFS:${zfs}" " Desc:${zfs_desc}" "Total[B]:${total}" "Used[B]:${used}"

                                # feed tmp file for further processing
                                echo ${zfs_desc} >> zfs_desc.$$
                                echo "${zfs_desc} ${total} ${used}" >> zfs_of.$$
                        done
                else
                        echo "Host ${zfs_host} is not reachable."
                fi
        done
}

# --- FUNCTION: clean temp files
tmp_file_cleaning() {
        [ -f ${global_infile}.$$ ] && rm ${global_infile}.$$
        [ -f ${ufs_infile} ] && rm ${ufs_infile}
        [ -f ufs_desc.$$ ] && rm ufs_desc.$$
        [ -f ufs_of.$$ ] && rm ufs_of.$$
        [ -f ${zfs_infile} ] && rm ${zfs_infile}
        [ -f zfs_desc.$$ ] && rm zfs_desc.$$
        [ -f zfs_of.$$ ] && rm zfs_of.$$
}

# --- cleaning in case of script termination and regular exit
trap tmp_file_cleaning HUP INT QUIT ABRT EXIT

# Process UFS and ZFS
process_ufs ; process_zfs

# ----- LOOK FOR SAME DESCRIPTION AND SUM THEIR VALUES

# remove duplicated description from tmp file
cat ufs_desc.$$ | sort| uniq > ufs_desc.$$.tmp ; mv ufs_desc.$$.tmp ufs_desc.$$
cat zfs_desc.$$ | sort| uniq > zfs_desc.$$.tmp ; mv zfs_desc.$$.tmp zfs_desc.$$

# print title on screen FYI
printf "------------------------------------------------------------------------- \n"
printf "%-15s %15s %15s \n" "Description" "Usable/Total[GB]" "Allocated/Used[GB]"
printf "------------------------------------------------------------------------- \n"

# for each description, calculate sum values, print on screen
# UFS - received in KB
for description in `cat ufs_desc.$$`
do
        cat ufs_of.$$ | \
        awk '$1=="'${description}'" {sumusable+=$2; sumalocated+=$3} \
        END {printf "%-15s %11.0f %15.0f \n", "'${description}'", sumusable/1024/1024, sumalocated/1024/1024, "\n" }'
done
# ZFS - received in B
for description in `cat zfs_desc.$$`
do
        cat zfs_of.$$ | \
        awk '$1=="'${description}'" {sumusable+=$2; sumalocated+=$3} \
        END {printf "%-15s %11.0f %15.0f \n", "'${description}'", sumusable/1024/1024/1024, sumalocated/1024/1024/1024, "\n" }'
done

exit 0

Output during processing is like:

# sh reportstorage.sh
Host:gorilla           Mount:/ora02          Desc:tip-0          Total[KB]=351683990  Used[KB]=51386171
Host:gorilla           Mount:/ora03          Desc:tip-1          Total[KB]=351683990  Used[KB]=104905219
Host:tiger             Mount:/.0             Desc:tiger-internal   Total[KB]=10326524   Used[KB]=1580099
Host:tiger             Mount:/shipping       Desc:tiger-internal   Total[KB]=10326524   Used[KB]=10307
Host:tiger             Mount:/viewstore      Desc:tiger-internal   Total[KB]=103269854  Used[KB]=66251208
Host:tiger             Mount:/buildstore     Desc:tiger-internal   Total[KB]=103269854  Used[KB]=14611264
Host:tiger             Mount:/ccase_rls      Desc:tiger-internal   Total[KB]=20653809   Used[KB]=13518282
Host:tiger             Mount:/vobstore       Desc:tiger-internal   Total[KB]=359090412  Used[KB]=263585448
Host:unicorn         Mount:/.100           Desc:winston        Total[KB]=207028354  Used[KB]=139473176
Host:unicorn         Mount:/.200           Desc:lucky          Total[KB]=207028354  Used[KB]=59108156
Host:trex           ZFS:fc.san.hrc.r10.sds2-s1  Desc:sds2           Total[B]:930128855040 Used[B]:745045099008
Host:trex           ZFS:fc.san.hrc.r10.sds3-s0  Desc:sds3           Total[B]:930128855040 Used[B]:871588045824
Host:trex           ZFS:scsi.das.zfs.r60.d1-12  Desc:trex-das      Total[B]:2342762053632 Used[B]:1355473254762
Host:trex           ZFS:scsi.int.zpool.r1.d2d3  Desc:trex-internal Total[B]:71873593344 Used[B]:599597056
Host:trex           ZFS:sds6-s2           Desc:sds6           Total[B]:1910992011264 Used[B]:1310114274816
Host:pigeon          ZFS:pool.0            Desc:pigeon         Total[B]:14571842568192 Used[B]:6553773370872
Host:borax           ZFS:bob.0             Desc:bob0-4.div.w.5 Total[B]:1175344644096 Used[B]:722208151552
Host:borax           ZFS:space.1           Desc:borax-das-1    Total[B]:1752909742080 Used[B]:1562364591690
Host:borax           ZFS:space.2           Desc:borax-das-2    Total[B]:1752909742080 Used[B]:1286738422500
Host:clearvoice      ZFS:space0            Desc:clearvoice-internal Total[B]:293836161024 Used[B]:270715788800

The result on the screen is like:

----------------------------------------------------
Description     Usable/Total[GB] Allocated/Used[GB]
----------------------------------------------------
tiger-internal            579             343
lucky                   197              56
tip-0                   335              49
tip-1                   335             100
winston                 197             133
trex-das              2182            1262
trex-internal           67               1
pigeon                13571            6104

Back to the main page