#!/bin/bash

#
# FCP adapter trace utility
#
# Wrapper script to start all processes
#
# Copyright IBM Corp. 2008, 2017
#
# s390-tools is free software; you can redistribute it and/or modify
# it under the terms of the MIT license. See LICENSE for details.
#

WRP_TOOLNAME="ziomon"
WRP_MSG_Q_PATH="/tmp/.zfcpstats$RANDOM";
WRP_DEBUGFS_PATH="";
WRP_MSG_Q_ID=1;
WRP_MSG_Q_UTIL_ID=1;
WRP_MSG_Q_IOERR_ID=2;
WRP_MSG_Q_BLKIOMON_ID=3;
WRP_MSG_Q_ZIOMON_ZFCPDD_ID=4;
WRP_DURATION="";
WRP_INTERVAL="";
WRP_INTERVAL_DEFAULT="60";
WRP_DEBUG=0;
WRP_ZIOMON_MGR_PID="";
WRP_ZIOMON_UTIL_PID="";
WRP_BLKTRACE_PID="";
WRP_BLKIOMON_PID="";
WRP_ZIOMON_ZFCPDD_PID="";
WRP_SIZE="";
WRP_HOST_ADAPTERS="";
WRP_DEVICES=();
WRP_LUNS=();
WRP_LOGFILE="";
WRP_BLKIOMON_VERSION="";
# limit of actual data in percent that need space on disk
WRP_SIZE_THRESHOLD="10";
WRP_FORCE=0;

function debug() {
   if [ $WRP_DEBUG -ne 0 ]; then
      echo "    === $@";
   fi
}

function ddebug() {
   if [ $WRP_DEBUG -gt 1 ]; then
      echo "    ====== $@";
   fi
}

function print_usage() {
   echo "Usage: $WRP_TOOLNAME [-h] [-V] [-v] [-f] [-l <sz_limit>] [-i n] -d n";
   echo "              -o <logfile> <device>...";
   echo;
   echo "Collect performance data for the specified zfcp devices or multipath devices.";
   echo "Example: $WRP_TOOLNAME -d 5 -o log /dev/sda";
   echo;
   echo "-h, --help            Print usage information and exit.";
   echo "-v, --version         Print version information and exit.";
   echo "-V, --verbose         Be verbose.";
   echo "-i, --interval-length Elapsed time between recording data in seconds.";
   echo "                      All samples in this timeframe will be aggregated.";
   echo "                      Must be an even number.";
   echo "                      Defaults to $WRP_INTERVAL_DEFAULT seconds.";
   echo "-d, --duration        Total duration to sample data in minutes.";
   echo "-f, --force           Force run even with insufficient disk space.";
   echo "-o, --outfile         Specify logfile name.";
   echo "-l, --size-limit      Specify an upper limit for the data output.";
   echo "                      Use suffixes M (megabytes), G (Gigabytes)";
   echo "                      or T (Terabytes) to specify a unit measure.";
   echo "                      Unit measure defaults to megabytes.";
}


function check_for_int() {
   [ $1 -gt 0 ] >/dev/null 2>&1;
   if [ $? -ne 0 ]; then
      echo "$WRP_TOOLNAME: $1 is not a valid argument to $2 - must be positive integer";
      exit 3;
   fi
}


function print_version() {
   echo "$WRP_TOOLNAME: I/O data collection utility, version %S390_TOOLS_VERSION%";
   echo "Copyright IBM Corp. 2008, 2017";
}


function parse_size() {
   local arg=$1;
   local pos;
   local size;
   local unit;

   let pos=${#arg}-1;
   unit=${arg:$pos};
   size=${arg:0:$pos};

   # default to megabytes
   if [ "`echo $unit | tr -d [0-9]`" == "" ]; then
      size=$arg;
      unit="M";
   fi

   check_for_int $size -l;

   case $unit in
      G) (( WRP_SIZE=1000*$size ));;
      M) WRP_SIZE=$size;;
      T) (( WRP_SIZE=1000000*$size ));;
      *) echo "$WRP_TOOLNAME: Unknown size suffix $unit";
         return 1;;
   esac;

   return 0;
}


function parse_params() {
   local tmp;
   local i;
   local error=0;
   local args;

   if [ $# -eq 0 ]; then
      print_usage;
      exit 1;
   fi

   args=`getopt -u -o hVd:fi:o:l:v -l help,verbose,duration:,force,interval-length:,outfile:,size-limit:,version -- $@`;
   set -- $args;

   let i=0;
   while [ $# -gt 0 ]; do
        case $1 in
            --help|-h)
                print_usage;
                exit 0;;
            --verbose|-V)
                (( WRP_DEBUG++ ));;
            --duration|-d)
                shift;
                (( WRP_DURATION = $1 * 60 ));;
            --force|-f)
                WRP_FORCE=1;;
            --interval-length|-i)
                shift;
                WRP_INTERVAL=$1;;
            --outfile|-o)
                shift;
                WRP_LOGFILE=$1;;
            --size-limit|-l)
                shift;
                parse_size $1;
                [ $? -ne 0 ] && ((error++));;
            --version|-v)
                print_version;
                exit 0;;
            --) ;;
            -*)
                echo "$WRP_TOOLNAME: Invalid option -- $1";
                echo "Try '$WRP_TOOLNAME --help' for more information.";
                exit 1;;
            *)
                echo ${WRP_DEVICES[@]} | grep -w $1 >/dev/null 2>&1
                if [ $? -ne 0 ]; then
                   WRP_DEVICES[$i]=$1;
                   (( i++ ));
                else
                   debug "Skipping duplicate: $1";
                fi;;
        esac
        shift;
   done
   if [ ${#WRP_DEVICES[@]} -eq 0 ]; then
      echo "$WRP_TOOLNAME: No devices specified";
      error=1;
   fi
   if [ "$WRP_LOGFILE" == "" ]; then
      echo "$WRP_TOOLNAME: No logfile specified";
      error=1;
   fi
   if [ $error -eq 0 ] && [ `dirname $WRP_LOGFILE` == "" ]; then
      WRP_LOGFILE=$WRP_LOGFILE;
   fi
   if [ $error -eq 0 ] && [ ! -d `dirname $WRP_LOGFILE` ]; then
      echo "$WRP_TOOLNAME: Directory `dirname $WRP_LOGFILE` does not exist";
      error=1;
   fi
   if [ "$WRP_DURATION" == "" ]; then
      echo "$WRP_TOOLNAME: No duration specified - see option '-d'";
      error=1;
   fi
   [ $error -eq 0 ] && check_for_int $WRP_DURATION -d;
   if [ $error -eq 0 ] && [ "$WRP_INTERVAL" != "" ]; then
      check_for_int $WRP_INTERVAL -i;
   else
      WRP_INTERVAL=$WRP_INTERVAL_DEFAULT;
   fi
   [ $error -eq 0 ] && [ "$WRP_SIZE" != "" ] && check_for_int $WRP_SIZE -l;

   if [ $error -eq 0 ]; then
      tmp=`expr $WRP_INTERVAL % 2`;
      if [ $tmp -ne 0 ]; then
         echo "$WRP_TOOLNAME: The interval length must be an even number";
         error=1;
      fi
   fi

   if [ $error -eq 0 ]; then
      tmp=`expr $WRP_DURATION % $WRP_INTERVAL`;
      if [ $tmp -ne 0 ]; then
         echo "$WRP_TOOLNAME: The duration must be a multiple of the interval length";
         error=1;
      fi
   fi
   if [ $error -ne 0 ]; then
      exit 1;
   fi

   debug "WRP_INTERVAL     : $WRP_INTERVAL seconds";
   debug "WRP_DURATION     : $WRP_DURATION seconds";
   debug "WRP_MSG_Q_ID     : $WRP_MSG_Q_ID";
   debug "WRP_FORCE        : $WRP_FORCE";
   debug "WRP_SIZE         : $WRP_SIZE MB";
   debug "WRP_LOGFILE      : $WRP_LOGFILE";
}


function check_for_process() {
   if [ $# -eq 0 ] || [ ! -d /proc/$1 ]; then
      echo "failed";
      return 1;
   fi

   return 0;
}


function start_trace() {
   local verbose="";
   local verbose_blk="";
   local command="";
   local blktrace_command="";
   local blkiomon_command="";
   local zfcpdd_command="";
   local size_limit="";
   local hosts_param;
   local luns_param;
   local i;
   local len;
   local end;

   while [ -e $WRP_MSG_Q_PATH ]; do
      WRP_MSG_Q_PATH="$WRP_MSG_Q_PATH$RANDOM";
   done
   mkdir $WRP_MSG_Q_PATH;
   debug "WRP_MSG_Q_PATH   : $WRP_MSG_Q_PATH";


   if [ $WRP_DEBUG -ne 0 ]; then
      verbose="-V";
      verbose_blk="-D $WRP_MSG_Q_PATH/blkiomon.log";
   fi

   # collect system data
   # we do this synchronously, as some of the volumes affected by this action
   # might be the ones to monitor
   echo -n "Collecting configuration data...";
   command="ziomon_fcpconf -o $WRP_LOGFILE";
   debug "collect system data: $command";
   $command > $WRP_MSG_Q_PATH/ziomon_fcpconf.log;
   echo "done";

   # start data manager
   echo -n "Start data collection processes...";
   if [ "$WRP_SIZE" != "" ]; then
      size_limit="-l $WRP_SIZE";
   fi
   command="ziomon_mgr $verbose $WRP_BLKIOMON_VERSION -f -i $WRP_INTERVAL -Q $WRP_MSG_Q_PATH -q $WRP_MSG_Q_ID -u $WRP_MSG_Q_UTIL_ID -r $WRP_MSG_Q_IOERR_ID -b $WRP_MSG_Q_BLKIOMON_ID -z $WRP_MSG_Q_ZIOMON_ZFCPDD_ID -o $WRP_LOGFILE $size_limit";
   debug "starting data manager: $command";
   $command > $WRP_MSG_Q_PATH/ziomon_mgr.log &
   WRP_ZIOMON_MGR_PID=$!;

   # start utilization
   for (( i=0; i<${#WRP_HOST_ADAPTERS[@]}; ++i )); do
      hosts_param="$hosts_param -a `echo ${WRP_HOST_ADAPTERS[$i]} | sed s/host//`";
   done
   for (( i=0; i<${#WRP_LUNS[@]}; ++i )); do
      luns_param="$luns_param -l ${WRP_LUNS[$i]}";
   done
   command="ziomon_util $verbose $hosts_param $luns_param -Q $WRP_MSG_Q_PATH -q $WRP_MSG_Q_ID -m $WRP_MSG_Q_UTIL_ID -L $WRP_MSG_Q_IOERR_ID -d $WRP_DURATION -i $WRP_INTERVAL";
   debug "starting ziomon_util: $command";
   $command > $WRP_MSG_Q_PATH/ziomon_util.log &
   WRP_ZIOMON_UTIL_PID=$!;

   # start blkiomon & ziomon_zfcpdd
   blktrace_command="blktrace -a issue -a drv_data -a complete -w $WRP_DURATION -o - ${WRP_DEVICES[@]}";
   blkiomon_command="blkiomon --interval=$WRP_INTERVAL -Q  $WRP_MSG_Q_PATH -q $WRP_MSG_Q_ID -m $WRP_MSG_Q_BLKIOMON_ID $verbose_blk -d -";
   zfcpdd_command="ziomon_zfcpdd -Q  $WRP_MSG_Q_PATH -q $WRP_MSG_Q_ID -m $WRP_MSG_Q_ZIOMON_ZFCPDD_ID -i $WRP_INTERVAL";
   debug "starting blktrace: $blktrace_command | $blkiomon_command | $zfcpdd_command";
   $blktrace_command 2>$WRP_MSG_Q_PATH/blktrace.err | $blkiomon_command 2>$WRP_MSG_Q_PATH/blkiomon.err | $zfcpdd_command > $WRP_MSG_Q_PATH/blktrace.log &
   i=0;
   # might take a moment to start all processes in the pipe if system under load
   while [ $i -lt 60 ]; do
      WRP_BLKTRACE_PID=`ps -o pid,args --ppid $$ | grep blktrace | awk '{ print $1 }'`;
      WRP_BLKIOMON_PID=`ps -o pid,args --ppid $$ | grep blkiomon | awk '{ print $1 }'`;
      WRP_ZIOMON_ZFCPDD_PID=`ps -o pid,args --ppid $$ | grep ziomon_zfcpdd | awk '{ print $1 }'`;
      if [ "$WRP_BLKTRACE_PID" != "" ] && [ "$WRP_BLKIOMON_PID" != "" ] && [ "$WRP_ZIOMON_ZFCPDD_PID" != "" ]; then
         break;
      fi
      sleep 1;
      (( i++ ));
   done
   check_for_process $WRP_ZIOMON_MGR_PID;
   [ $? -ne 0 ] && echo "$WRP_TOOLNAME: Failed to determine ziomon_mgr pid" && return 1;
   check_for_process $WRP_ZIOMON_UTIL_PID;
   [ $? -ne 0 ] && echo "$WRP_TOOLNAME: Failed to determine ziomon_util pid" && return 1;
   check_for_process $WRP_BLKTRACE_PID;
   [ $? -ne 0 ] && echo "$WRP_TOOLNAME: Failed to determine blktrace pid" && return 1;
   check_for_process $WRP_BLKIOMON_PID;
   [ $? -ne 0 ] && echo "$WRP_TOOLNAME: Failed to determine blkiomon pid" && return 1;
   check_for_process $WRP_ZIOMON_ZFCPDD_PID;
   [ $? -ne 0 ] && echo "$WRP_TOOLNAME: Failed to determine ziomon_zfcpdd pid" && return 1;

   echo "done";
   echo -n "Collecting data...";

   # pay extra attention to blktrace - but check every 15 seconds only since
   # otherwise checking might become too expensive
   end=`date +%s`;
   let end=$end+$WRP_DURATION;
   debug "start data collection at `date`";
   while [ `date +%s` -lt $end ]; do
      len=`cat $WRP_MSG_Q_PATH/blktrace.err 2>/dev/null | wc -l`;
      if [ $len -ne 0 ]; then
         cat $WRP_MSG_Q_PATH/blktrace.err;
         echo "Error: blktrace has errors, aborting";
         return;
      fi

      check_for_process $WRP_BLKTRACE_PID;
      if [ $? -ne 0 ]; then
         echo "Error: blktrace died unexpected";
         return;
      fi
                                          
      sleep 15;
   done
   debug "end data collection at `date`";

   # Ignore benign blkiomon early-read warnings
   len=`grep -vcE "^bad trace magic 0|^blkiomon: bad trace" $WRP_MSG_Q_PATH/blkiomon.err 2>/dev/null`;
   if [ $len -ne 0 ]; then
      cat $WRP_MSG_Q_PATH/blkiomon.err;
      echo "Error: blkiomon has errors, aborting";
      return;
   fi

   echo "done";
}


function shutdown() {
   echo "Shutting down";
   # one more second to write final result
   sleep 2;
   if [ "$WRP_ZIOMON_UTIL_PID" != "" ]; then
      [ -d /proc/$WRP_ZIOMON_UTIL_PID ] && echo "Shutting down utilization process" && kill -s SIGTERM $WRP_ZIOMON_UTIL_PID;
   fi
   if [ "$WRP_BLKTRACE_PID" != "" ]; then
      [ -d /proc/$WRP_BLKTRACE_PID ] && echo "Shutting down blktrace process" && kill -s SIGINT $WRP_BLKTRACE_PID;
   fi
   if [ "$WRP_BLKIOMON_PID" != "" ]; then
      [ -d /proc/$WRP_BLKIOMON_PID ] && echo "Shutting down blkiomon process" && kill -s SIGTERM $WRP_BLKIOMON_PID;
   fi
   if [ "$WRP_ZIOMON_ZFCPDD_PID" != "" ]; then
      [ -d /proc/$WRP_ZIOMON_ZFCPDD_PID ] && echo "Shutting down ziomon_zfcpdd process" && kill -s SIGTERM $WRP_ZIOMON_ZFCPDD_PID;
   fi
   if [ "$WRP_ZIOMON_MGR_PID" != "" ]; then
      if [ -d /proc/$WRP_ZIOMON_MGR_PID ]; then
         # don't be too fast
         sleep 1;
         echo "Shutting down data manager";
         kill -s SIGTERM $WRP_ZIOMON_MGR_PID;
      fi
   fi
   # synchronize with all children to avoid zombies
   # and to prepare for a clean subsequent re-run of ziomon
   wait

   if [ -e $WRP_MSG_Q_PATH ]; then
      if [ $WRP_DEBUG -gt 1 ]; then
         debug "Logfiles available at $WRP_MSG_Q_PATH";
      else
         rm -rf $WRP_MSG_Q_PATH;
      fi
   fi
}


function emergency_shutdown() {
   debug "signal received, aborting!";
   shutdown;
   exit 1;
}


function check_cpuplugd {
   # check if cpuplugd is running
   # If so, the whole per-cpu mechanism of blktrace gets corrupted, which
   # results in the infamous 'bad trace magic' message
   if [ -e /run/cpuplugd.pid ]; then
      echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!";
      echo "$WRP_TOOLNAME: Warning: cpuplugd is running which can corrupt the traces.";
      echo "        It is recommended to stop cpuplugd for the duration of the";
      echo "        trace using 'service cpuplugd stop'.";
      echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!";
   fi
}


# we need 2MB per device and CPU
function check_vmalloc_space() {
   local total;
   local used;
   local free;
   local num_cpus;
   local required;
   local result;
   
   num_cpus=`cat /proc/cpuinfo | grep processors | awk '{print $4}'`;
   total=`cat /proc/meminfo | grep VmallocTotal | awk '{print $2}'`;
   used=`cat /proc/meminfo | grep VmallocUsed | awk '{print $2}'`;

   (( free=$total-$used ));
   (( required=$num_cpus*${#WRP_DEVICES[@]}*2048 ));
   (( result=$free-$required ));
   debug "Required Vmalloc space: $required KBytes";
   if [ $result -lt 0 ]; then
      echo "$WRP_TOOLNAME: Not enough free Vmalloc space:";
      echo "        Required: $required KBytes";
      echo "        Free: $free KBytes";
      exit 1;
   fi
   
   return 0;
}


function check_blkiomon() {
   # check blkiomon version
   ver=`blkiomon -V | awk '{print $3}'`;
   if [ "$ver" == "0.2" ]; then
      WRP_BLKIOMON_VERSION="-x 2";
      debug "detected backlevel blkiomon, use binary format option $WRP_BLKIOMON_VERSION";
   else
      if [ "$ver" != "0.3" ]; then
         echo "$WRP_TOOLNAME: Unsupported blkiomon version $ver detected, aborting";
         exit 1;
      fi
   fi
}


function check_debugfs() {
   # check if debugfs is mounted (required for blktrace)
   WRP_DEBUGFS_PATH=$(awk '{ if ($3 == "debugfs" && $2 == \
                          "/sys/kernel/debug") print $2 }' /proc/mounts);
   if [ -z "$WRP_DEBUGFS_PATH" ]; then
      echo "$WRP_TOOLNAME: Error: Debugfs not mounted on /sys/kernel/debug.";
      exit 1;
   fi
}


function clean_devices() {
   local tmp;

   # remove empty entries from array
   tmp=( ${WRP_DEVICES[@]} );
   WRP_DEVICES=();
   WRP_DEVICES=( ${tmp[@]} );
}


function check_for_multipath_devices() {
   local i;
   local j;
   local mp_arr=();
   local line;
   local devices_basenames;
   local tmp;

   [ ${#WRP_DEVICES[@]} -eq 0 ] && return;

   # maybe multipath is not even installed?!?
   if [ -e /sbin/multipath ]; then
      while read line
      do
         mp_arr[${#mp_arr[@]}]=$line;
      done < <(/sbin/multipath -l);
   fi

   for (( j=0; j<${#WRP_DEVICES[@]}; ++j )); do
      devices_basenames[$j]=`basename ${WRP_DEVICES[$j]}`;
   done

   ddebug "check for multipath devices...";
   for (( i=0; i<${#mp_arr[@]}; ++i )); do
      if [ "${mp_arr[$i]:0:1}" != "[" ] && [ "${mp_arr[$i]:0:1}" != " " ] && [ "${mp_arr[$i]:0:1}" != "_" ]; then
         tmp=`echo ${mp_arr[$i]} | awk '{print $1}'`;
         for (( j=0; j<${#WRP_DEVICES[@]}; ++j )); do
            if [ "$tmp" == "`basename ${WRP_DEVICES[$j]}`" ]; then
               ddebug "   multipath device found: $tmp";
               WRP_DEVICES[$j]="";
               devices_basenames[$j]="";
               clean_devices;
               (( i+=2 ));
               while [[ `echo "${mp_arr[$i]:0:1}" | grep -ve "[0-9a-zA-Z]"` ]] && [ $i -lt ${#mp_arr[@]} ]; do
                  if [ `echo ${mp_arr[$i]} | grep -e "[0-9]\{1,\}:[0-9]\{1,\}:[0-9]\{1,\}:[0-9]\{1,\}" | wc -l` -ne 0 ]; then
	             line="`echo ${mp_arr[$i]} | sed 's/[^0-9]*\([0-9]\{1,\}:[0-9]\{1,\}:[0-9]\{1,\}:[0-9]\{1,\}\)/\1/'`";
                     checked_devs[${#checked_devs[@]}]=`echo $line | awk '{print "/dev/"$2}'`;
                     ddebug "      adding ${checked_devs[${#checked_devs[@]}-1]}";
                     WRP_HOST_ADAPTERS[${#WRP_HOST_ADAPTERS[@]}]="host${line%%:*}";
                     WRP_LUNS[${#WRP_LUNS[@]}]=`echo $line | awk '{print $1}'`;
                  fi
                  (( i++ ));
               done;
               (( --i ));
               break;
            fi
         done
      fi
   done
   ddebug "done";
}


function check_for_regular_devices() {
   local i;
   local j;
   local line;
   local lsscsi_arr=();
   local dev;
   local dev_gen;

   [ ${#WRP_DEVICES[@]} -eq 0 ] && return;

   while read line
   do
      lsscsi_arr[${#lsscsi_arr[@]}]=$line;
   done < <(lsscsi -g);

   ddebug "check for regular devices...";
   if [ ${#WRP_DEVICES[@]} -gt 0 ]; then
      for (( i=0; i<${#lsscsi_arr[@]}; ++i )); do
         dev=`echo ${lsscsi_arr[$i]} | awk '{print $(NF-1)}'`;
         dev_gen=`echo ${lsscsi_arr[$i]} | awk '{print $NF}'`;
         for (( j=0; j<${#WRP_DEVICES[@]}; ++j )); do
            if [ "$dev" == "${WRP_DEVICES[$j]}" ] || [ "$dev_gen" == "${WRP_DEVICES[$j]}" ]; then
               checked_devs[${#checked_devs[@]}]=${WRP_DEVICES[$j]};
               WRP_DEVICES[$j]="";
               clean_devices;
               ddebug "   add ${checked_devs[${#checked_devs[@]}-1]}";
               line=`echo ${lsscsi_arr[$i]} | awk '{print $1}'`;
               line=${line#[*};
               WRP_HOST_ADAPTERS[${#WRP_HOST_ADAPTERS[@]}]="host${line%%:*}";
               WRP_LUNS[${#WRP_LUNS[@]}]=${line%%]*};
               break;
            fi
         done
      done
   fi
   ddebug "done";
}

function check_corresponding_regular_device_symlink()
{
   local j=$1;
   local corr_dev=$2;
   local line;

   ddebug "   regular device found: $corr_dev";
   checked_devs[${#checked_devs[@]}]=${WRP_DEVICES[$j]};
   WRP_DEVICES[j]="";
   clean_devices;
   ddebug "   add ${checked_devs[${#checked_devs[@]}-1]}";
   line=$(lsscsi -g | grep -w "${corr_dev%%[0-9]*}" | awk '{print $1}');
   line=${line#[*};
   WRP_HOST_ADAPTERS[${#WRP_HOST_ADAPTERS[@]}]="host${line%%:*}";
   WRP_LUNS[${#WRP_LUNS[@]}]=${line%%]*};
}

function check_corresponding_multipath_device_symlink()
{
   local j=$1;
   local corr_dev=$2;
   local corr_dev_mpath;
   local mp_arr=();
   local line;
   local tmp;
   local k;

   # maybe multipath is not even installed?!?
   if [ -e /sbin/multipath ]; then
      # read multipath output into an array
      while IFS= read -r line
      do
         mp_arr[${#mp_arr[@]}]=$line;
      done < <(/sbin/multipath -l);
   fi

   # get corresponding device name from lsblk, remove trailing digits and '-part' suffix (if present)
   corr_dev_mpath=$(lsblk "${corr_dev}" | awk 'NR==2 {print $1}' | sed 's/[0-9]*$//' | sed 's/-part//');
   # search for matching multipath entry
   for (( k=0; k<${#mp_arr[@]}; ++k )); do
      # skip lines starting with '[', ' ', or '_'
      if [ "${mp_arr[$k]:0:1}" != "[" ] && [ "${mp_arr[$k]:0:1}" != " " ] && [ "${mp_arr[$k]:0:1}" != "_" ]; then
         # extract device name from mp_arr
         tmp=$(echo "${mp_arr[$k]}" | awk '{print $1}');
         if [ "${tmp}" == "${corr_dev_mpath}" ]; then
            ddebug "   multipath device found: ${corr_dev}";
            WRP_DEVICES[j]="";
            clean_devices;
            (( k+=2 )); # skip next two lines (usually header/info)
            # loop through lines that start with non-alphanumeric characters
            while [[ "${mp_arr[$k]:0:1}" =~ [^0-9a-zA-Z] ]] && [ $k -lt ${#mp_arr[@]} ]; do
               # check if line contains a SCSI device identifier pattern like h:c:t:l
               if [ "$(echo "${mp_arr[$k]}" | grep -ce "[0-9]\{1,\}:[0-9]\{1,\}:[0-9]\{1,\}:[0-9]\{1,\}")" -ne 0 ]; then
                  # extract the SCSI identifier
                  line="$(echo "${mp_arr[$k]}" | sed 's/[^0-9]*\([0-9]\{1,\}:[0-9]\{1,\}:[0-9]\{1,\}:[0-9]\{1,\}\)/\1/')";
                  # add corresponding /dev/ entry to checked_devs array
                  checked_devs[${#checked_devs[@]}]=$(echo "$line" | awk '{print "/dev/"$2}');
                  ddebug "      add ${checked_devs[${#checked_devs[@]}-1]}";
                  # extract and store host adapter and LUN info
                  WRP_HOST_ADAPTERS[${#WRP_HOST_ADAPTERS[@]}]="host${line%%:*}";
                  WRP_LUNS[${#WRP_LUNS[@]}]=$(echo "$line" | awk '{print $1}');
               fi
               (( k++ ));
            done;
            break;
         fi
      fi
   done
}

function check_for_symlink_devices() {
   local i;
   local j;
   local symlnk_arr=();
   local line;
   local corr_dev;

   [ ${#WRP_DEVICES[@]} -eq 0 ] && return;

   # persistent storage symlinks from /dev/disk/
   while IFS= read -r line
   do
      symlnk_arr[${#symlnk_arr[@]}]=$line;
   done < <(find /dev/disk/ -type l);

   ddebug "check for symlink devices...";
   for (( i=0; i<${#symlnk_arr[@]}; ++i )); do
      for (( j=0; j<${#WRP_DEVICES[@]}; ++j )); do
         if [ "$(basename "${symlnk_arr[$i]}")" == "$(basename "${WRP_DEVICES[$j]}")" ]; then
            ddebug "   symlink device found at: ${symlnk_arr[$i]}";
            # find corresponding device for the symlink
            corr_dev=$(readlink -f "${symlnk_arr[i]}");
            if [ "${corr_dev:0:7}" == "/dev/sd" ]; then
               check_corresponding_regular_device_symlink "$j" "${corr_dev}";
            else
               check_corresponding_multipath_device_symlink "$j" "${corr_dev}";
            fi
         fi
      done
   done
   ddebug "done";
}


function determine_host_adapters() {
   local error=0;
   local found;
   local num_s_devs;
   local s_dev_ratio;

   echo -n "Check devices...";

   # Estimate fraction of /dev/s* devices - if >50%, start with check for regular devices
   num_s_devs=`echo ${WRP_DEVICES[@]} | sed "s/ /\n/g" | grep /dev/s | wc -l`;
   (( s_dev_ratio=$num_s_devs*100/${#WRP_DEVICES[@]} ))
   if [ $s_dev_ratio -ge 50 ]; then
      check_for_regular_devices;
      check_for_multipath_devices;
      check_for_symlink_devices;
   else
      check_for_symlink_devices;
      check_for_multipath_devices;
      check_for_regular_devices;
   fi

   # anything left?
   found=0;
   for (( i=0; i<${#WRP_DEVICES[@]}; ++i )); do
      if [ "${WRP_DEVICES[$i]}" != "" ]; then
         if [ $found -eq 0 ]; then
           echo "$WRP_TOOLNAME: The following devices do not seem to exist:";
           found=1;
         fi
         echo "   ${WRP_DEVICES[$i]}";
      fi
   done
   [ $found -ne 0 ] && exit 1;

   # filter duplicates
   WRP_HOST_ADAPTERS=( `echo ${WRP_HOST_ADAPTERS[@]} | sed "s/ /\n/g" | sort | uniq` );
   WRP_DEVICES=( `echo ${checked_devs[@]} | sed "s/ /\n/g" | sort | uniq` );
   WRP_LUNS=( `echo ${WRP_LUNS[@]} | sed "s/ /\n/g" | sort | uniq` );

   echo "done";

   debug "#Devices total   : ${#WRP_DEVICES[@]}"
   debug "WRP_DEVICES      : ${WRP_DEVICES[@]}";
   debug "WRP_LUNS         : ${WRP_LUNS[@]}";
   debug "WRP_HOST_ADAPTERS: ${WRP_HOST_ADAPTERS[@]}";

   if [ ${#WRP_DEVICES[@]} -ne ${#WRP_LUNS[@]} ]; then
      echo "$WRP_TOOLNAME: Number of LUNs does not match number of devices: ${#WRP_DEVICES[@]} devices and ${#WRP_LUNS[@]} LUNs";
      exit 2;
   fi
}



# required args:
# $1 estimated size
# $2 limit
function check_free_space_mileage() {
   local history_percent;
   local estimated_size=$1;
   local limit=$2;

   if [ $estimated_size -gt $limit ]; then
      (( history_percent = $limit * 100 / $estimated_size ));
      if [ $WRP_FORCE -eq 0 ] && [ $history_percent -lt $WRP_SIZE_THRESHOLD ]; then
         if [ "$WRP_SIZE" != "" ]; then
            echo "$WRP_TOOLNAME: User-specified limit for log data too low";
            echo "Estimated maximum size of log data: $estimated_size MBytes.";
            echo "User-specified limit: $limit MBytes.";
            echo "This would equal $history_percent percent of the total data, while at least $WRP_SIZE_THRESHOLD percent is required.";
            echo "Either lower the duration, reduce the interval length, raise the limit or override using option '-f'."
         else
            echo "Error: Not enough free disk space available for log data";
            echo "Estimated maximum size of log data: $estimated_size MBytes.";
            echo "Free disk space available: $limit MBytes.";
            echo "This would equal $history_percent percent of the total data, while at least $WRP_SIZE_THRESHOLD percent is required.";
            echo "Either lower the duration, reduce the interval length or override using option '-f'."
         fi
         return 1;
      else
         echo -n "Warning: Only approx. $history_percent percent of the total log data can be recorded due to ";
         if [ "$WRP_SIZE" != "" ]; then
            echo -n "a user-specified limit. ";
         else
            echo -n "disk space limitations. ";
         fi
            echo "All data exceeding this limit must be aggregated, which means a loss of granularity.";
      fi
   else
      [ $estimated_size -eq 0 ] && estimated_size="<1";
      echo "Estimated maximum disk space required for log data: approx. $estimated_size MBytes";
   fi

   return 0;
}


function check_blktrace() {
   if ! command -v blktrace >/dev/null 2>&1; then
      echo "$WRP_TOOLNAME: Could not find blktrace. Please make sure that the blktrace package is installed and matches the level in the documentation.";
      exit 1;
   fi
}


function check_for_existing_output() {
    # avoid config mismatch. Accidental deletion doesn't harm - can be recreated from .cfg anyway
    if [ -e "$WRP_LOGFILE.config" ]; then
        debug "$WRP_LOGFILE.config exists, removing";
        rm -rf $WRP_LOGFILE.config;
    fi
    if [ -e "$WRP_LOGFILE.agg" ]; then
        debug "$WRP_LOGFILE.agg exists, removing";
        rm -rf $WRP_LOGFILE.agg;
    fi
}


function check_size_requirements() {
   local util_base_sz;
   local util_variable_sz;
   local ioerr_base_sz;
   local ioerr_variable_sz;
   local blkiotrace_sz;
   local zfcpiotrace_sz;
   local size_per_record;
   local total_num_records;
   local estimated_size;
   local free_space;
   local logpath=`dirname $WRP_LOGFILE`;

   set `ziomon_mgr -e`;
   util_base_sz=$1;
   util_variable_sz=$2;
   ioerr_base_sz=$3;
   ioerr_variable_sz=$4;
   zfcpiotrace_sz=$5;
   blkiotrace_sz=$6;

   # NOTE: Since blktrace and ziomon_zfcpdd write messages only when there is
   # traffic, the estimate is an upper boundary only
   debug "disk space requirements:";
   (( size_per_record = $util_base_sz + ${#WRP_HOST_ADAPTERS[@]} * $util_variable_sz + $ioerr_base_sz
                        + ${#WRP_DEVICES[@]} * ( $ioerr_variable_sz + $blkiotrace_sz + $zfcpiotrace_sz )
                        + ( 2 + ${#WRP_DEVICES[@]}) * 8 ));
   debug "    size per interval: $size_per_record Bytes";
   (( total_num_records = $WRP_DURATION / $WRP_INTERVAL ));
   debug "    total number of intervals: $total_num_records";
   (( estimated_size=$total_num_records * $size_per_record ));
   debug "    estimated size: $estimated_size Bytes";
   (( estimated_size=$estimated_size / 1000000 ));
   free_space=`df -P -B 1M $logpath | tail -n 1 | awk '{print $4}'`;
   debug "    free space on '$logpath': $free_space MBytes";
   if [ "$WRP_SIZE" == "" ]; then
      echo "NOTE: No size limit specified, run without a limit.";
   else
      if [ $WRP_FORCE -eq 0 ] && [ $WRP_SIZE -gt $free_space ]; then
         echo "$WRP_TOOLNAME: The user-specified limit of $WRP_SIZE MBytes for the log data exceeds the actual free space of $free_space MBytes.";
         echo "Either lower the limit, get more free space or use option '-f' to override.";
         return -1;
      fi
   fi
   if [ "$WRP_SIZE" == "" ]; then
      check_free_space_mileage $estimated_size $free_space;
   else
      check_free_space_mileage $estimated_size $WRP_SIZE;
   fi
   [ $? -ne 0 ] && return 1;

   return 0;
}


trap emergency_shutdown SIGHUP SIGTERM SIGINT SIGQUIT;


parse_params $@;

check_blktrace;

determine_host_adapters;

check_cpuplugd;

check_blkiomon;

check_for_existing_output;

check_debugfs;

check_vmalloc_space;

check_size_requirements;

[ $? -eq 0 ] && start_trace;

shutdown;

exit 0;


