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_DIRis a directory with the reports;$paramis the `iostat` parameter name;$deviceis a device name from the same `iostat` output.
When we need to update the charts, we call `plot.sh`.
Required components
- Bash
iostatutility- Gnuplot
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'