-
Hahn Axel (hahn) authoredHahn Axel (hahn) authored
check_disk-io 7.52 KiB
#!/bin/bash
# ======================================================================
#
# Check DISK IO over all disks
#
# data besed on /proc/diskstats
# https://www.kernel.org/doc/Documentation/iostats.txt
#
# based on /sys/block/*/stat
# https://www.kernel.org/doc/Documentation/block/stat.txt
#
# Requires: bc, lsblk
#
# ----------------------------------------------------------------------
# 2020-07-17 v1.0 <axel.hahn@iml.unibe.ch>
# 2023-07-27 v1.1 <axel.hahn@unibe.ch> shell fixes; remove unsupported warn and critical
# 2025-02-10 v1.2 <axel.hahn@unibe.ch> harden sourcing files
# 2025-04-02 v1.3 <axel.hahn@unibe.ch> add mode "measure" for read write speed
# ======================================================================
# shellcheck source=/dev/null
. "$( dirname "$0" )/inc_pluginfunctions" || exit 1
export self_APPVERSION=1.3
# ----------------------------------------------------------------------
# FUNCTIONS
# ----------------------------------------------------------------------
# diskinfo based on lsblk
# param string comma separated list of names (no spaces)
function _diskInfo(){
local _fields=$1
test -z "$_fields" && _fields='NAME,MAJ:MIN,TYPE,SIZE,FSTYPE,MOUNTPOINT,STATE,ROTA,VENDOR,MODEL,SERIAL,HCTL'
lsblk -ai --output "$_fields"
}
# get a list of local disks
function getDisks(){
_diskInfo "NAME,TYPE" | grep "disk" | awk '{ print $1 }' | sed "s#[^a-z0-9]##g"
}
function measureline(){
local mydevice="$1"
local bsize="$2"
local mode="$3"
local out="$4"
local timer
local speed
timer=$( tail -1 <<< "${out}" | cut -f 3 -d ",")
speed=$( tail -1 <<< "${out}" | cut -f 4 -d ",")
info+=" $mode $speed"
# printf "%-25s %-10s %-10s %-15s %10s\n" "$mydevice" "$mode" "$bsize" "$timer" "$speed"
# perf="$( sed "s#\ \([.]\).*#\1#g" <<< "$speed" )"
perf="$( awk '{ print $1 " " $2 }' <<< "$speed" | sed "s#\ \(.\).*#\1#g" )"
perf="$( ph.toUnit "$perf" "M" )"
ph.perfadd "$mode" "$perf"
}
# UNUSED get a list of local partitions
# function getPartitions(){
# _diskInfo "NAME,TYPE" | grep "part" | awk '{ print $1 }' | sed "s#[^a-z0-9]##g"
# }
# show help
function showHelp(){
local _self; _self="$( basename "$0" )"
cat <<EOF
$( ph.showImlHelpHeader )
Disk infos based on /sys/block/[NAME]/stat
See https://www.kernel.org/doc/Documentation/block/stat.txt
and https://www.kernel.org/doc/Documentation/iostats.txt
The system data are counters that are difficult to read.
The output of this check for each value a delta value per second since
last check.
SYNTAX:
$_self -m MODE
OPTIONS:
-h or --help show this help.
-m MODE set mode for type of output (required)
-d DIR for measure: set a directory, default: "/tmp"
-s SIZE for measure: set a size, default: "100M"
PARAMETERS:
MODE
io read I/Os, write I/Os, discard I/0s
measure measure write and read speed
ticks read ticks, write ticks, discard ticks
wait total wait time for all requests
DIR
Directory to perform read and write test.
SIZE
Block size for dd command: a number followed multiplicative suffix.
c=1, w=2, b=512, kB=1000, K=1024, MB=1000*1000, M=1024*1024, xM=M,
GB=1000*1000*1000, G=1024*1024*1024, and so on for T, P, E, Z, Y, R, Q
EXAMPLE:
$_self -m io
$_self -m measure
$_self -m measure -d /mnt/data -s 10M
EOF
}
# ----------------------------------------------------------------------
# MAIN
# ----------------------------------------------------------------------
typeset -i iDelta=0
case "$1" in
"--help"|"-h")
showHelp
exit 0
;;
*)
esac
ph.require bc lsblk
# --- set mode
sMode=$( ph.getValueWithParam '' m "$@")
if [ "$sMode" = "measure" ]; then
sDir=$( ph.getValueWithParam '/tmp' d "$@")
bsize=$( ph.getValueWithParam '100M' s "$@")
target="${sDir}/$( tr -cd 'a-f0-9' <<< /dev/urandom | head -c 32 )"
info="Disk speed in ${sDir} using $bsize - "
measureline "${sDir}" "$bsize" "WRITE" "$( dd if=/dev/zero of="${target}" bs=$bsize count=1 oflag=dsync 2>&1 )"
measureline "${sDir}" "$bsize" "READ" "$( dd if="${target}" of=/dev/null bs=$bsize count=1 oflag=dsync 2>&1 )"
rm -f "${target}"
ph.status "$info"
ph.exit
fi
# --- labels and its columns in /sys/block/$myDisk/stat
# Name units description
# ---- ----- -----------
# 1 read I/Os requests number of read I/Os processed
# 2 read merges requests number of read I/Os merged with in-queue I/O
# 3 read sectors sectors number of sectors read
# 4 read ticks milliseconds total wait time for read requests
# 5 write I/Os requests number of write I/Os processed
# 6 write merges requests number of write I/Os merged with in-queue I/O
# 7 write sectors sectors number of sectors written
# 8 write ticks milliseconds total wait time for write requests
# 9 in_flight requests number of I/Os currently in flight
# 10 io_ticks milliseconds total time this block device has been active
# 11 time_in_queue milliseconds total wait time for all requests
# 12 discard I/Os requests number of discard I/Os processed
# 13 discard merges requests number of discard I/Os merged with in-queue I/O
# 14 discard sectors sectors number of sectors discarded
# 15 discard ticks milliseconds total wait time for discard requests
case "$sMode" in
"io")
info="read I/Os, write I/Os, discard I/0s, number of I/Os currently in flight"
aNames=(ReadIO WriteIO DiscardIO FlightIO)
aColums=(1 5 12 9)
;;
"ticks")
info="ticks=total wait time [ms] --> read ticks, write ticks, discard ticks, io ticks (total time this block device has been active)"
aNames=(ReadTicks WriteTicks DiscardTicks IoTicks)
aColums=(4 8 15 10)
;;
"wait")
info="total wait time [ms] for all requests"
aNames=(Wait)
aColums=(11)
;;
*)
echo "ERROR: missing or wrong MODE parameter -m"
showHelp
exit 1
esac
tmpfile1=$(mktemp)
# --- add data for each disk
for myDisk in $(getDisks)
do
echo >>"$tmpfile1"
echo "--- $myDisk" >> "$tmpfile1"
diskdata=$( cat /sys/block/$myDisk/stat )
# echo $diskdata >> $tmpfile1
for index in ${!aNames[*]}
do
label="disk-$myDisk-${aNames[$index]}"
column=${aColums[$index]}
value=$( echo $diskdata | cut -f $column -d " " )
iDelta=$(ph.perfdeltaspeed "$label" "$value")
# typeset -i aTotals["$index"]=${aTotals["$index"]}+$iDelta
typeset -i aMax["$index"]
if [ -z "${aMax["$index"]}" ] || [ ${aMax["$index"]} -lt $iDelta ]; then
aMax["$index"]=$iDelta
fi
# echo " $label $iDelta per sec ... total: $value" >> "$tmpfile1"
printf "%30s %10d \n" "$label:" "$iDelta" >> "$tmpfile1"
done
done
# --- add total
echo >>"$tmpfile1"
echo "--- MAX" >> "$tmpfile1"
for index in ${!aNames[*]}
do
label="${aNames[$index]}"
# value=${aTotals[$index]}
value=${aMax[$index]}
# echo " $label: $value" >> $tmpfile1
printf "%30s %10d \n" "$label:" "$value" >> "$tmpfile1"
ph.perfadd "$label" "$value"
done
echo >>"$tmpfile1"
# --- output
ph.status "Disk data ... $info " # READ `toUnit $iTotalRead M` MB/s << [DISC] << `toUnit $iTotalWrite M` MB/s WRITE"
cat "$tmpfile1"
# --- cleanup and bye
rm -f "$tmpfile1"
ph.exit