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