#!/bin/bash
######################################################
##### #####
##### Configuration section #####
##### #####
######################################################
## Path to smartctl binary of smartmontools (version has to be >= 5.39)
## If unset systemwide smartctl is used if found.
SMARTCTL="./smartctl"
## Path to logfile. Leave empty or comment out to disable logging.
LOGFILE="./ssdlog.csv"
## Unit used for logging of read/written data.
## Possible values: B, KB, MB, GB, KiB, MiB, GiB, b, Kb, Mb, Gb, Kib, Mib, Gib
## Small "b" refers to bit, capital "B" to Byte.
## Units with an "i" are binary based (1 KiB = 1024 Byte)
## whereas units without are decimal based (1 KB = 1000 Byte)
LOG_SIZE_UNIT="MiB"
## Number of decimals used for logging of read/written data.
LOG_SIZE_DEZIMALS=0
######################################################
##### #####
##### END OF #####
##### Configuration section #####
##### #####
######################################################
check_root() {
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root" 1>&2
exit 1
fi
}
check_device() {
if [[ -z "$1" ]]
then
DEVICE="/dev/sda"
else
DEVICE="$1"
fi
if [ ! -b "${DEVICE}" ]
then
if [ ! -b "/dev/${DEVICE}" ]
then
echo "No device given or given device not a block device."
echo "Call this script with the devicename of the SSD."
echo "E.g.: ./ssd.sh /dev/sda"
exit 1
else
DEVICE="/dev/${DEVICE}"
fi
fi
}
change_path() {
OLDDIR=`pwd`
abspath="$(cd "${0%/*}" 2>/dev/null; echo "$PWD"/"${0##*/}")"
path_only=`dirname "$abspath"`
cd ${path_only}
}
check_smartctl() {
if [[ ! -e "${SMARTCTL}" ]]
then
SMARTCTL=`which smartctl`
if [[ "$?" == "1" ]]
then
echo "Path to smartctl not set and smartctl not found in PATH."
echo "ssd_status.sh requires smartctl from smartmontools to run."
exit 1
fi
fi
SMARTCTL_VERSION=`${SMARTCTL} --version | grep "smartctl [0-9]\.[0-9][0-9]" | cut -d\ -f2`
NEWER_539=`echo "${SMARTCTL_VERSION} >= 5.39" | bc -l`
if [[ "${NEWER_539}" == 0 ]]
then
echo "smartctl binary is older than 5.39 (${SMARTCTL} is ${SMARTCTL_VERSION})."
echo "ssd_status.sh requires smartctl to be at least version 5.39 to run."
exit 1
fi
}
get_timedate() {
DATE=`date "+%Y-%m-%d"`
TIME=`date "+%H:%M"`
}
gather_smartinfo() {
SMARTINFO=`${SMARTCTL} ${DEVICE} -i | grep ": " | sed -r 's/ +/*/g'`
for LINE in ${SMARTINFO}
do
if [[ -n `echo ${LINE} | grep -i "device\*model"` ]]
then
MODEL=`echo ${LINE} | cut -d* -f3`
fi
if [[ -n `echo ${LINE} | grep -i "serial\*number"` ]]
then
SERIAL=`echo ${LINE} | cut -d* -f3`
fi
if [[ -n `echo ${LINE} | grep -i "firmware\*version"` ]]
then
FIRMWARE=`echo ${LINE} | cut -d* -f3`
fi
if [[ -n `echo ${LINE} | grep -i "user\*capacity"` ]]
then
SIZE=`echo ${LINE} | cut -d* -f3 | sed 's/\.//g' | sed 's/,//g'`
fi
done
SERIAL_PUBLIC=`echo ${SERIAL} | sed -r "s/.{5}$/*****/"`
}
gather_smartvalues() {
SMARTVALUES=`${SMARTCTL} ${DEVICE} -A -v N,raw64 | grep 0x0000 | sed 's/0x0000.*-//' | sed -r 's/^ +//' | sed -r 's/ +/*/g'`
for LINE in ${SMARTVALUES}
do
ID=`echo ${LINE} | cut -d* -f1`
VALUE=`echo ${LINE} | cut -d* -f3`
if [ "${ID}" = "1" ]; then
RAW_READ_ERROR_RATE=${VALUE}
fi
if [ "${ID}" = "9" ]; then
POWER_ON_HOURS=${VALUE}
fi
if [ "${ID}" = "12" ]; then
POWER_CYCLE_COUNT=${VALUE}
fi
if [ "${ID}" = "184" ]; then
INITIAL_BAD_BLOCK_COUNT=${VALUE}
fi
if [ "${ID}" = "195" ]; then
PROGRAM_FAILURE_BLK_CT=${VALUE}
fi
if [ "${ID}" = "196" ]; then
ERASE_FAILURE_BLK_CT=${VALUE}
fi
if [ "${ID}" = "197" ]; then
READ_FAILURE_BLK_CT=${VALUE}
fi
if [ "${ID}" = "198" ]; then
READ_SECTORS_TOT_CT=${VALUE}
fi
if [ "${ID}" = "199" ]; then
WRITE_SECTORS_TOT_CT=${VALUE}
fi
if [ "${ID}" = "200" ]; then
READ_COMMANDS_TOT_CT=${VALUE}
fi
if [ "${ID}" = "201" ]; then
WRITE_COMMANDS_TOT_CT=${VALUE}
fi
if [ "${ID}" = "202" ]; then
ERROR_BITS_FLASH_TOT_CT=${VALUE}
fi
if [ "${ID}" = "203" ]; then
CORR_READ_ERRORS_TOT_CT=${VALUE}
fi
if [ "${ID}" = "204" ]; then
BAD_BLOCK_FULL_FLAG=${VALUE}
fi
if [ "${ID}" = "205" ]; then
MAX_PE_COUNT_SPEC=${VALUE}
fi
if [ "${ID}" = "206" ]; then
MIN_ERASE_COUNT=${VALUE}
fi
if [ "${ID}" = "207" ]; then
MAX_ERASE_COUNT=${VALUE}
fi
if [ "${ID}" = "208" ]; then
AVERAGE_ERASE_COUNT=${VALUE}
fi
if [ "${ID}" = "209" ]; then
REMAINING_LIFE_TIME=${VALUE}
fi
done
SIZE_B=${SIZE}
SIZE_GiB=`echo "scale=3; ${SIZE_B}/1024/1024/1024" | bc -l`
READ_B=`echo "scale=0; ${READ_SECTORS_TOT_CT}*512" | bc -l`
WRITTEN_B=`echo "scale=0; ${WRITE_SECTORS_TOT_CT}*512" | bc -l`
READ_GiB=`echo "scale=3; ${READ_B}/1024/1024/1024" | bc -l`
WRITTEN_GiB=`echo "scale=3; ${WRITTEN_B}/1024/1024/1024" | bc -l`
}
gather_triminfo() {
TRIMINFO=`hdparm -I ${DEVICE} | grep TRIM`
if [[ -n "${TRIMINFO}" ]]
then
TRIM="Yes"
else
TRIM="No"
fi
}
console_print() {
echo "Model: ${MODEL}"
echo "Serialnumber: ${SERIAL_PUBLIC}"
echo "Firmware / TRIM: ${FIRMWARE} / ${TRIM}"
echo "Size: ${SIZE_GiB} GiB"
echo "Status: ${REMAINING_LIFE_TIME}%"
echo "Power on time: ${POWER_ON_HOURS} hours"
echo "Power cycles: ${POWER_CYCLE_COUNT}"
echo "Cell wear level: ${AVERAGE_ERASE_COUNT} (${MIN_ERASE_COUNT} / ${MAX_ERASE_COUNT}) [avg (min / max)]"
echo "Read: ${READ_GiB} GiB"
echo "Written: ${WRITTEN_GiB} GiB"
}
file_writeheader() {
# echo "SSD status logfile" > ${LOGFILE}
# echo "Generic information:;Model;Serial;Firmware;TRIM;Size" >> ${LOGFILE}
# echo ";${MODEL};${SERIAL_PUBLIC};${FIRMWARE};${TRIM};${SIZE_B}" >> ${LOGFILE}
# echo "" >> ${LOGFILE}
# echo "Log information:" >> ${LOGFILE}
echo "Date;Time;Read;Write;Erase cnt;;;Remaining;Power on;Power;Raw read;Program failure;Erase failure;Read failure;Sectors;;Read;Write;Error bits;Corrected" >> ${LOGFILE}
echo ";;(${LOG_SIZE_UNIT});(${LOG_SIZE_UNIT});Avg;Min;Max;life time;time (hours);cycle cnt;error rate;block count;block count;block count;read;written;commands;commands;flash;read errors" >> ${LOGFILE}
}
file_calculatedatasize() {
case "${LOG_SIZE_UNIT}" in
"B" )
LOG_SIZE_CALC="";;
"KiB" )
LOG_SIZE_CALC="/1024";;
"MiB" )
LOG_SIZE_CALC="/1024/1024";;
"GiB" )
LOG_SIZE_CALC="/1024/1024/1024";;
"KB" )
LOG_SIZE_CALC="/1000";;
"MB" )
LOG_SIZE_CALC="/1000/1000";;
"GB" )
LOG_SIZE_CALC="/1000/1000/1000";;
"b" )
LOG_SIZE_CALC="*8";;
"Kib" )
LOG_SIZE_CALC="*8/1024";;
"Mib" )
LOG_SIZE_CALC="*8/1024/1024";;
"Gib" )
LOG_SIZE_CALC="*8/1024/1024/1024";;
"Kb" )
LOG_SIZE_CALC="*8/1000";;
"Mb" )
LOG_SIZE_CALC="*8/1000/1000";;
"Gb" )
LOG_SIZE_CALC="*8/1000/1000/1000";;
esac
READ=`echo "scale=${LOG_SIZE_DEZIMALS}; ${READ_B}${LOG_SIZE_CALC}" | bc -l`
WRITTEN=`echo "scale=${LOG_SIZE_DEZIMALS}; ${WRITTEN_B}${LOG_SIZE_CALC}" | bc -l`
}
file_writelogentry() {
echo "${DATE};${TIME};${READ};${WRITTEN};${AVERAGE_ERASE_COUNT};${MIN_ERASE_COUNT};${MAX_ERASE_COUNT};${REMAINING_LIFE_TIME};${POWER_ON_HOURS};${POWER_CYCLE_COUNT};${RAW_READ_ERROR_RATE};${PROGRAM_FAILURE_BLK_CT};${ERASE_FAILURE_BLK_CT};${READ_FAILURE_BLK_CT};${READ_SECTORS_TOT_CT};${WRITE_SECTORS_TOT_CT};${READ_COMMANDS_TOT_CT};${WRITE_COMMANDS_TOT_CT};${ERROR_BITS_FLASH_TOT_CT};${CORR_READ_ERRORS_TOT_CT}" >> ${LOGFILE}
}
file_log() {
if [[ ! -e "${LOGFILE}" ]]
then
file_writeheader
fi
file_writelogentry
}
check_root
check_device
change_path
check_smartctl
get_timedate
gather_smartinfo
gather_smartvalues
gather_triminfo
file_calculatedatasize
console_print
if [[ -n "${LOGFILE}" ]]
then
file_log
fi
cd ${OLDDIR}