#!/bin/bash

: << =cut

=head1 NAME

  io_disk - Multigraph plugin to monitor disks for Solaris.

  These functions are implemented:
     ops     : similar to iostat r/s, w/s
     bytes   : similar to iostat kr/s, kw/s 
     busy    : similar to iostat %b, %w (%w usually indicates 0 in Sol10,11)
     queue   : similar to iostat actv, wait
     latency : similar to iostat asvc_t, wsvc_t
     size    : average io size

  This plugin is improved from Solaris io_busy_, io_ops_, io_bytes_ plugins.
  Any device found in /usr/bin/kstat can be monitored.

  Tested with Solaris 10 and 11, and it should work with Solaris 8, 9.

=head1 CONFIGURATION

  Make symlink:
    cd /path/to/munin/etc/plugins
    ln -s /path/to/munin/lib/plugins/io_disk .

  The RRD files generated by io_busy_, io_ops_, io_bytes_ can be taken over 
  by this plugin.
  Thus, please remove symlinks of those plugins before using this plugin.

  By default, this plugin monitors disk devices. And also it can monitor 
  NFS and Tape devices as much as io_* plugins with setting environments.

  Note that instance names of nfs (e.g. nfs1) can be changed after reboot or
  remount, this plugin may not monitor nfs properly. (same as original ones)

=head1 ENVIRONMENT VARIABLES

  env.class - kstat class. See man kstat -c option.
    example:  env.class  /disk|nfs|tape/
    default:  disk

  env.module - Module name. Only used in internal graph name.  
    example:  env.module  something
    default:  sd

    If you would not like to take over RRDs generated by io_*_sd plugins,
    please change it to another name.

    And also, if you would like to take over RRDs of nfs, tape devices,
    please set it 'nfs' or 'tape' (and set env.class, env.module_regex).
    It may be good to link like ln -s /path/to/io_disk io_nfs if necessary.

  env.module_regex - kstat module. See man kstat -m option.
    example:  env.module_regex  /^(s?sd|vdc|zvblk|dad|md|nfs|st)$/
    default:  /^(s?sd|vdc|zvblk|dad|md)$/

  env.title_type - Device type shown in graph title.
    example:  env.title_type  Disk Device, NFS, Tape
    default:  Disk Device

  env.exclude - Device instance name(s) to exclude seperated by white-space.
    example:  env.exclude  sd0 ssd1
    default:  none

  env.graph_width - Graph width in pixel
    example:  env.graph_width  450
    default:  none (usually 400)

=head1 AUTHOR

  Unknown Author
  Improved by K.Cima https://github.com/shakemid

=head1 LICENSE

  GPLv2

=head1 MAGIC MARKERS

  #%# family=auto
  #%# capabilities=autoconf

=cut

# Include plugin.sh
. "${MUNIN_LIBDIR:-}/plugins/plugin.sh"
is_multigraph "$@"

# Shell options
set -o nounset  # Like perl use strict;

# Set environment variables
plugin_name=io
functions='ops bytes busy queue latency size'
: "${class:=disk}"
: "${module:=sd}"
: "${module_regex:=/^(s?sd|vdc|zvblk|dad|md)\$/}"
: "${title_type:=Disk Device}"
: "${exclude:=}"
: "${graph_width:=}"

# Create map of instance name (e.g. sd0) and logical device name (e.g. c0t0d0)
#   Example: 
#     name_sd1=c0t0d0
#     name_ssd2=c0tAB_1234d0  (shorten long target)
#     ...
instance_names=$( iostat -x | sed -e '1,2d' | awk '{print $1}' \
    | sed -e 's/^/name_/' )
logical_device_names=$( iostat -xn | sed -e '1,2d' | awk '{print $NF}' \
    | sed -e 's/^\(c[0-9]*\)\(t.\{2\}\).*\(.\{4\}\)\(d[0-9]*\)$/\1\2_\3\4/' )
declare $( paste -d= <( echo "$instance_names" ) <( echo "$logical_device_names" ) )

# Functions

preconfig() {
    local func
    func=$1

    case "$func" in
    ops)
        conf_title='I/O Operations'
        conf_gargs='--base 1000'
        conf_vlabel='Ops per second write (-) / read (+)'
        conf_in=reads
        conf_out=writes
        ;;
    bytes)
        conf_title='I/O Throughput'
        conf_gargs='--base 1024'
        conf_vlabel='Bytes per second write (-) / read (+)'
        conf_in=nread
        conf_out=nwritten
        ;;
    busy)
        conf_title='Busy & Wait'
        conf_gargs='--base 1000 --lower-limit 0 --upper-limit 100'
        conf_vlabel='% wait (-) / busy (+)'
        conf_in=rtime
        conf_out=wtime
        ;;
    queue)
        conf_title='Queue Length'
        conf_gargs='--base 1000'
        conf_vlabel='Queue length wait (-) / actv (+)'
        conf_in=rlentime
        conf_out=wlentime
        ;;
    latency)
        conf_title='Latency'
        conf_gargs='--base 1000'
        conf_vlabel='Seconds wsvc_t (-) / asvc_t (+)'
        conf_in=rlentime
        conf_out=wlentime
        ;;
    size)
        conf_title='I/O Size'
        conf_gargs='--base 1024'
        conf_vlabel='Average size write (-) / read (+)'
        conf_in=nread
        conf_out=nwritten
        ;;
    *)
        echo "Unknown function: $func"
        exit 1
        ;;
    esac
}

is_excluded() {
    local arg i
    arg=$1

    for i in ${exclude}
    do
        if [ "$arg" = "$i" ]; then
            return 0
        fi
    done

    return 1
}

do_config() {
    local func dev ref devname stat

    func=$1
    preconfig "$func"
    echo "multigraph ${plugin_name}_${func}_${module}"

    echo "graph_title $title_type $conf_title"
    echo 'graph_category disk'
    echo "graph_args $conf_gargs"
    echo "graph_vlabel $conf_vlabel"
    if [ -n "$graph_width" ]; then
        echo "graph_width $graph_width"
    fi

    # Get device instance names by kstat
    kstat -p -c "$class" -m "$module_regex" -s "/^${conf_in}\$/" \
    | sed 's/:/ /g' | awk '{ print $3 }' \
    | while read -r dev
    do
        is_excluded "$dev" && continue

        # Replace instance name to logical device name if exists
        ref="name_$dev"
        devname=${!ref:-}  # ${!varname} means indirect evaluation (similar to eval)

        # reads and writes are necessary to calculate latency, size
        case "$func" in
        latency|size)
            for stat in reads writes
            do
                echo "${dev}_${stat}.label dummy" 
                echo "${dev}_${stat}.graph no"
                echo "${dev}_${stat}.type DERIVE"
                echo "${dev}_${stat}.min 0"
            done
            ;;
        esac

        # Set CDEF
        case "$func" in
        busy)
            conf_in_cdef="${dev}_${conf_in},100,*"
            conf_out_cdef="${dev}_${conf_out},100,*"
            ;;
        latency)
            # rlentime / ( reads + writes )
            conf_in_cdef="${dev}_${conf_in},${dev}_reads,${dev}_writes,+,/"
            conf_out_cdef="${dev}_${conf_out},${dev}_reads,${dev}_writes,+,/"
            ;;
        size)
            conf_in_cdef="${dev}_${conf_in},${dev}_reads,/"
            conf_out_cdef="${dev}_${conf_out},${dev}_writes,/"
            ;;
        *)
            conf_in_cdef=
            conf_out_cdef=
            ;;
        esac

        # Print data attributes
        echo "${dev}_${conf_out}.label dummy" 
        echo "${dev}_${conf_out}.graph no"
        echo "${dev}_${conf_out}.type DERIVE"
        echo "${dev}_${conf_out}.min 0"
        if [ -n "$conf_out_cdef" ]; then
            echo "${dev}_${conf_out}.cdef ${conf_out_cdef}"
        fi

        echo "${dev}_${conf_in}.label ${devname:-${dev}}"
        echo "${dev}_${conf_in}.negative ${dev}_${conf_out}"
        echo "${dev}_${conf_in}.type DERIVE"
        echo "${dev}_${conf_in}.min 0"
        if [ -n "$conf_in_cdef" ]; then
            echo "${dev}_${conf_in}.cdef ${conf_in_cdef}"
        fi
    done

    echo
}

do_fetch() {
    local func stat_regex dev stat value

    func=$1
    preconfig "$func"
    echo "multigraph ${plugin_name}_${func}_${module}"

    case "$func" in
    latency|size)
        stat_regex="/^($conf_in|$conf_out|reads|writes)\$/"
        ;;
    *)
        stat_regex="/^($conf_in|$conf_out)\$/"
        ;;
    esac

    # Get device instance names, stat names and values by kstat

    # kstat output example:
    #   $ kstat -p -c disk -m '/^sd$/' -s '/^reads$/'
    #   sd:0:sd0:reads  51432610
    #   sd:1:sd1:reads  52671435
    #   ...

    kstat -p -c "$class" -m "$module_regex" -s "$stat_regex" \
    | sed 's/:/ /g' | awk '{ print $3,$4,$5 }' \
    | while read -r dev stat value
    do
        is_excluded "$dev" && continue

        echo "${dev}_${stat}.value ${value%.*}"
    done

    echo
}

autoconf() {
    if [ -x /usr/bin/kstat ]; then
        echo yes
        exit 0
    else
        echo "no (/usr/bin/kstat not found)"
        exit 0
    fi
}

config() {
    local func

    for func in $functions
    do
        do_config "$func"
    done
}

fetch() {
    local func

    for func in $functions
    do
        do_fetch "$func"
    done
}

# Main
case ${1:-} in
autoconf)
    autoconf
    ;;
config)
    config
    [ "${MUNIN_CAP_DIRTYCONFIG:-}" = "1" ] && fetch
    ;;
*)
    fetch
    ;;
esac

exit 0
