Showing posts with label script. Show all posts
Showing posts with label script. Show all posts

30 March 2016

Making charts from iostat output

I've decided to make some charts from the output of the iostat command. Just for fun =)

The idea is simple: a script named `report.sh` runs periodically in background(via `cron`) collecting `iostat` data into $REPORT_DIR/$param/$device files, where

  • $REPORT_DIR is a directory with the reports;
  • $param is the `iostat` parameter name;
  • $device is a device name from the same `iostat` output.

When we need to update the charts, we call `plot.sh`.

Required components

Scripts

util.sh
#!/bin/bash -

# $1 - Optional error message.
function die
{
  [ $# -gt 0 ] && echo >&2 $1
  exit 1
}

function warning
{
  echo >&2 "Warning: $1"
}


function getSafeFilename
{
  r=${1//\//_}
  echo ${r/\%/}
}

#vim: ts=2 sts=2 sw=2 et
report.sh
#!/bin/bash -
# Generates iostat data files for processing with the GNU plot utility.

HEADER=
TIME_FORMAT="%Y/%m/%d/%H:%M"
TIME=$(date "+$TIME_FORMAT")
DIR=$(cd $(dirname "$0"); pwd)
REPORT_DIR="${DIR}/r"


#####################################################################
# Functions

source $DIR/util.sh


# $1 - exit code
function usage
{
  echo "
Generates iostat data files for processing with the GNU plot utility(plot.sh).

Usage: $0 OPTIONS

OPTIONS:
-h, --help            Display help message
-t, --time-format     Time format. Default: ${TIME_FORMAT}.
-i, --report-dir      Directory with reports. Default: ${REPORT_DIR}.
"
  exit $1
}

#####################################################################
# Parsing CLI options

OPTS=$(getopt -o t:i:h -l time-format:,report-dir:,help -- "$@")
[ $? -eq 0 ] || usage 1
eval set -- "$OPTS"

while true
do
  case "$1" in
    -t|--time-format)
      TIME_FORMAT="$2"
      shift 2;;
    -i|--report-dir)
      REPORT_DIR="$2"
      shift 2;;
    -h|--help)
      usage 0
      shift;;
    --)
      shift
      break;;
    ?)
      usage 1;;
    *)
      die "Internal error: failed to parse CLI args"
  esac
done

echo TIME_FORMAT: $TIME_FORMAT REPORT_DIR: $REPORT_DIR

#####################################################################

mkdir -p $REPORT_DIR
[ $? -eq 0 ] || die "Report directory '$REPORT_DIR' is inaccessible"

iostat -dpxk | while read -r line
do
  if [[ -z $HEADER && $line == Device* ]]; then
    HEADER=( $line )
  elif [[ -n $HEADER ]]; then
    [[ -z $line ]] && continue

    columns=( $line )

    [[ ${#HEADER[@]} != ${#columns[@]} ]] && continue


    i=1
    for h in ${HEADER[@]:1}
    do
      param_report_dir="${REPORT_DIR}/"$(getSafeFilename $h)
      mkdir -p $param_report_dir
      [ $? -eq 0 ] || die "Parameter directory '$param_report_dir' is inaccessible"

      # $REPORT_DIR/param/device
      echo $TIME$'\t'${columns[$i]} >> $param_report_dir'/'${columns[0]}
      (( ++i ))
    done
  fi
done

echo 'Done'
plot.sh
#!/bin/bash -
# Creates visual representation of data files generated by report.sh

TIME_FORMAT="%Y/%m/%d/%H:%M"
# Report files location $REPORT_DIR/param/device
DIR=$(cd $(dirname "$0"); pwd)
REPORT_DIR="${DIR}/r"
OUTPUT_DIR="${DIR}/o"
XRANGE=
PLOT_TERMINAL="png"

#####################################################################
# Functions

source $DIR/util.sh

function getPlots
{
  param_path="$1"

  find $param_path/ -maxdepth 1 -mindepth 1 -type f | while read -r device_path; do
    # $device_path = /*/*/.../param/device
    device=$(basename $device_path)

    printf "'%s' using 1:2 t '%s' with lp pt 5, " \
      "$device_path" $device
    printf "'%s' using 1:2 notitle with impulses, " \
      "$device_path"
  done
}


# Returns default value for GNU plot xrange
function getXRange
{
  y=$(date +%Y)
  m=$(date +%m)

  (( m -= 1 ))

  if [ $m -lt 1 ]; then
    m=1
    (( y -= 1 ))
  fi

  d=$(date "+$TIME_FORMAT")

  echo "[ '$y/$m/1/00:00' : '$d' ]"
}


# $1 - exit code
function usage
{
  echo "
Creates charts from files generated by report.sh

Usage: $0 OPTIONS

OPTIONS:
-h, --help            Display help message
-x, --xrange          GNU Plot xrange value. Default: $(getXRange).
-t, --time-format     Time format. Default: ${TIME_FORMAT}.
-i, --report-dir      Directory with reports. Default: ${REPORT_DIR}.
-o, --output-dir      Output directory. Default: ${OUTPUT_DIR}.
-f, --term            GNU Plot terminal. Default: ${PLOT_TERMINAL}. See 'gnuplot set terminal'.
"
  exit $1
}

#####################################################################
# Parsing CLI options

OPTS=$(getopt -o x:t:i:o:f:h -l xrange:,time-format:,report-dir:,output-dir:,term:,help -- "$@")
[ $? -eq 0 ] || usage 1
eval set -- "$OPTS"

while true
do
  case "$1" in
    -x|--xrange)
        XRANGE="$2"
        shift 2;;
    -t|--time-format)
      TIME_FORMAT="$2"
      shift 2;;
    -i|--report-dir)
      REPORT_DIR="$2"
      shift 2;;
    -o|--output-dir)
      OUTPUT_DIR="$2"
      shift 2;;
    -f|--term)
      PLOT_TERMINAL="$2"
      shift 2;;
    -h|--help)
      usage 0
      shift;;
    --)
      shift
      break;;
    ?)
      usage 1;;
    *)
      die "Internal error: failed to parse CLI args"
  esac
done

echo XRANGE: $XRANGE TIME_FORMAT: $TIME_FORMAT OUTPUT_DIR: $OUTPUT_DIR REPORT_DIR: $REPORT_DIR

#####################################################################

[ -d $REPORT_DIR ] || die "$REPORT_DIR doesn't exist"

find $REPORT_DIR/ -maxdepth 1 -mindepth 1 -type d | while read -r param_path
do
  # $param_path = /*/*/.../param
  param=$(basename $param_path)
  plots=$(getPlots "$param_path")
  plots=${plots/%, /}

  mkdir -p $OUTPUT_DIR
  [ $? -eq 0 ] || die "Output directory '$OUTPUT_DIR' is inaccessible"

  outfile="${OUTPUT_DIR}/${param}.${PLOT_TERMINAL}"

  [[ -z $XRANGE ]] && XRANGE="$(getXRange)"

  gnuplot <<EOS
TIME_FORMAT = "$TIME_FORMAT"
set terminal $PLOT_TERMINAL
set grid nopolar
set xtics rotate
set xlabel "Date/Time"
set ylabel "Value"
set yrange [ 0 : ]
set format x TIME_FORMAT timedate
set timefmt TIME_FORMAT
set xdata time
set xrange $XRANGE
set output "$outfile"

plot $plots
EOS
done

echo 'Done'

Sample chart

Gnuplot Produced by GNUPLOT 5.0 patchlevel 1 (Gentoo revision r1) 0 0.01 0.02 0.03 0.04 0.05 0.06 0.07 0.08 2016/03/30/16:00 2016/03/30/16:30 2016/03/30/17:00 2016/03/30/17:30 2016/03/30/18:00 2016/03/30/18:30 Value Date/Time sda4 sda4 gnuplot_plot_2 sda3 sda3 gnuplot_plot_4 sda1 sda1 gnuplot_plot_6 loop1 loop1 gnuplot_plot_8 loop0 loop0 gnuplot_plot_10 sda sda gnuplot_plot_12 sda2 sda2 gnuplot_plot_14 loop2 loop2 gnuplot_plot_16