#!/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}