#!/bin/bash
#
# Copyright (c) Authors: https://www.armbian.com/authors
#
# System monitoring and debugging utility for Armbian.
#
# Features:
# - Live system monitoring (CPU, temp, voltage, etc.)
# - Network monitoring
# - Disk I/O monitoring
# - Disk health/performance testing
# - Package integrity verification
#
# For support info collection and upload, use 'armbian-debug'.
#
# Disk Configuration Format:
# When checking disks, proposals are generated for /etc/armbianmonitor/disks.conf:
#
#   ${GUID}:${Name}:${smartctl_prefix}:${temp_call}:${CRC}:${LCC}
#
# Fields:
#   - GUID: Disk identifier from gdisk
#   - Name: Display name (defaults to device model)
#   - smartctl_prefix: USB disk type prefix (e.g., '-d sat', '-d usbjmicron')
#   - temp_call: Command to get temperature (omit to use hddtemp)
#   - CRC: S.M.A.R.T. attribute number for CRC errors
#   - LCC: S.M.A.R.T. attribute number for load cycle count
#
# Example:
#   A57BF307-7D82-4783-BD1D-B346CA8C195B:WD Green::199:193
#
############################################################################

Main() {
	export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

	# check if stdout is a terminal...
	if test -t 1; then
		# see if it supports colors...
		ncolors=$(tput colors)
		if test -n "$ncolors" && test $ncolors -ge 8; then
			BOLD="$(tput bold)"
			NC='\033[0m' # No Color
			LGREEN='\033[1;32m'
			LRED='\e[0;91m'
		fi
	fi

	[ $# -eq 0 ] && (
		DisplayUsage
		exit 0
	)

	ParseOptions "$@"
} # Main

ParseOptions() {
	while getopts 'hHuUrRmMsnNd:c:C:pPv' c; do
		case ${c} in
			H)
				# display full help test
				# export FullUsage=TRUE
				DisplayUsage
				exit 0
				;;
			h)
				# display short help
				DisplayUsage
				exit 0
				;;
			m | M | s)
				# monitoring mode, -s is for internal usage (debug log upload)
				interval=$2
				MonitorMode ${OPTARG}
				exit 0
				;;
			n | N)
				# network monitoring mode
				rf1=$2
				NetworkMonitorMode ${OPTARG}
				exit 0
				;;
			u | U)
				# DEPRECATED: Support info collection has been moved to armbian-debug
				cat <<- 'EOF'

					The -u and -U options are deprecated. This functionality has been moved to the
					dedicated 'armbian-debug' tool.
					EOF
				# Check if armbian-debug exists
				if [ -x /usr/bin/armbian-debug ]; then
					echo "Launching armbian-debug now..."
					exec /usr/bin/armbian-debug
				elif [ -x "$(dirname "$0")/armbian-debug" ]; then
					echo "Launching armbian-debug now..."
					exec "$(dirname "$0")/armbian-debug"
				else
					echo "Error: armbian-debug not found." >&2
					exit 1
				fi
				;;
			r | R)
				# REMOVED: RPi-Monitor support has been dropped
				echo "The -r and -R options (RPi-Monitor installation) have been removed." >&2
				echo "RPi-Monitor is no longer shipped with Armbian. Use 'armbianmonitor -m' for monitoring." >&2
				exit 1
				;;
			p | P)
				# REMOVED: cpuminer based performance test has been dropped
				echo "The -p and -P options (cpuminer performance test) have been removed." >&2
				echo "Please use 'sbc-bench' instead." >&2
				exit 1
				;;
			d)
				# monitors write activity to disk
				MonitorIO "${OPTARG}"
				exit 0
				;;
			c | C)
				# check card mode
				CheckCard "${OPTARG}"
				exit 0
				;;
			v)
				# Verify installation integrity
				VerifyRepairExcludes="/etc/|/boot/|cache|getty|/var/lib/smartmontools/"
				VerifyInstallation
				exit 0
				;;
			*)
				echo "Invalid flag: ${c}" >&2
				exit 7
				;;
		esac
	done
} # ParseOptions

DisplayUsage() {

	# Kept for reference.
	#	if [ ${FullUsage} ]; then
	#		echo -e "\nDetailed Description:"
	#		grep "^#" "$0" | grep -v "^#\!/bin/bash" | sed 's/^#//'
	#	fi
	echo
	echo "Usage: $(basename $0) [-h] [-c \$path] [-d \$device] [-m] [-n] [-v]"
	echo
	echo "Options:"
	echo "  -c /path/to/test   Performs disk health/performance tests"
	echo "  -d                 Monitors writes to \$device"
	echo "  -m                 Provides simple CLI monitoring - scrolling output"
	echo "  -M                 Provides simple CLI monitoring - fixed-line output"
	echo "  -n                 Provides simple CLI network monitoring - scrolling output"
	echo "  -N                 Provides simple CLI network monitoring - fixed-line output"
	echo "  -v                 Tries to verify installed package integrity"
	echo
	echo "Note: The -u/-U options for support info upload are deprecated."
	echo "      Use the 'armbian-debug' command instead."
	echo "Note: The -r/-R and -p/-P options (RPi-Monitor, cpuminer performance test) have been removed."
	echo

} # DisplayUsage

MonitorMode() {
	# $1 is the time in seconds to pause between two prints, defaults to 5 seconds
	# This functions prints out endlessly:
	# - time/date
	# - average 1m load
	# - detailed CPU statistics
	# - Soc temperature if available
	# - PMIC temperature if available
	# - DC-IN voltage if available

	# Allow armbianmonitor to return back to armbian-config
	trap "echo ; exit 0" 0 1 2 3 15

	# Try to renice to 19 to not interfere with OS behaviour
	renice 19 $BASHPID > /dev/null 2>&1

	LastUserStat=0
	LastNiceStat=0
	LastSystemStat=0
	LastIdleStat=0
	LastIOWaitStat=0
	LastIrqStat=0
	LastSoftIrqStat=0
	LastCpuStatCheck=0
	LastTotal=0

	SleepInterval=${interval:-5}

	Sensors="/etc/armbianmonitor/datasources/"
	if [ -f /sys/devices/system/cpu/cpu4/cpufreq/scaling_cur_freq ]; then
		DisplayHeader="Time    CPU_cl0/CPU_cl1  load %cpu %sys %usr %nice %io %irq"
		CPUs=dual_cluster
		echo "Two CPU clusters are available for monitoring"
	elif [ -f /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq ]; then
		DisplayHeader="Time        CPU    load %cpu %sys %usr %nice %io %irq"
		CPUs=normal
	else
		DisplayHeader="Time      CPU n/a    load %cpu %sys %usr %nice %io %irq"
		CPUs=notavailable
	fi

	[ -f "${Sensors}/soctemp" ] && DisplayHeader="${DisplayHeader}   Tcpu" || SocTemp='n/a'
	[ -f "${Sensors}/pmictemp" ] && DisplayHeader="${DisplayHeader}   PMIC" || PMICTemp='n/a'
	DCIN=$(CheckDCINVoltage)
	[ -f "${DCIN}" ] && DisplayHeader="${DisplayHeader}   DC-IN" || DCIN='n/a'
	[ -f /sys/devices/virtual/thermal/cooling_device0/cur_state ] &&
		DisplayHeader="${DisplayHeader}  C.St." || CoolingState='n/a'
	echo -e "Stop monitoring using [ctrl]-[c]"
	[ $(echo "${SleepInterval} * 10" | bc | cut -d. -f1) -le 15 ] &&
		echo "Warning: High update frequency (${SleepInterval} sec) might change system behaviour!"
	echo -e "${DisplayHeader}"
	Counter=0
	while true; do
		if [ "$c" == "m" ]; then
			let Counter++
			if [ ${Counter} -eq 15 ]; then
				printf "\n\n%s" "$DisplayHeader"
				Counter=0
			fi
		elif [ "$c" == "s" ]; then
			# internal mode for debug log upload
			let Counter++
			if [ ${Counter} -eq 6 ]; then
				exit 0
			fi
		else
			printf "\x1b[1A"
		fi
		LoadAvg=$(cut -f1 -d" " < /proc/loadavg)
		case ${CPUs} in
			dual_cluster)
				Cluster1=$(awk '{printf ("%0.0f",$1/1000); }' < /sys/devices/system/cpu/cpu4/cpufreq/scaling_cur_freq) 2> /dev/null
				Cluster0=$(awk '{printf ("%0.0f",$1/1000); }' < /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq) 2> /dev/null
				ProcessStats
				printf "\n%s  %4s/%4s MHz %5s %s" "$(date "+%H:%M:%S")" "$Cluster0" "$Cluster1" "$LoadAvg" "$procStats"
				;;
			normal)
				CpuFreq=$(awk '{printf ("%0.0f",$1/1000); }' < /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq) 2> /dev/null
				ProcessStats
				printf "\n%s  %4s MHz %5s %s" "$(date "+%H:%M:%S")" "$CpuFreq" "$LoadAvg" "$procStats"
				;;
			notavailable)
				ProcessStats
				printf "\n%s    ---     %5s %s" "$(date "+%H:%M:%S")" "$LoadAvg" "$procStats"
				;;
		esac
		if [ "X${SocTemp}" != "Xn/a" ]; then
			read -r SocTemp < "${Sensors}/soctemp"
			if [ ${SocTemp} -ge 1000 ]; then
				SocTemp=$(awk '{printf ("%0.1f",$1/1000); }' <<< ${SocTemp})
			fi
			printf "  %4s °C" "$SocTemp"
		fi
		if [ "X${PMICTemp}" != "Xn/a" ]; then
			read -r PMICTemp < "${Sensors}/pmictemp"
			if [ ${PMICTemp} -ge 1000 ]; then
				PMICTemp=$(awk '{printf ("%0.1f",$1/1000); }' <<< ${PMICTemp})
			fi
			printf "  %4s °C" "$PMICTemp"
		fi
		if [ "X${DCIN}" != "Xn/a" ]; then
			case "${DCIN##*/}" in
				in_voltage2_raw)
					# Tinkerboard S
					read -r RAWvoltage < "${DCIN}"
					DCINvoltage=$(echo "(${RAWvoltage} / ((82.0/302.0) * 1023.0 / 1.8)) + 0.1" | bc -l)
					;;
				*)
					DCINvoltage=$(awk '{printf ("%0.2f",$1/1000000); }' < "${DCIN}")
					;;
			esac
			printf "  %5sV" "$DCINvoltage"
		fi
		[ "X${CoolingState}" != "Xn/a" ] &&
			printf "  %d/%d" "$(cat /sys/devices/virtual/thermal/cooling_device0/cur_state)" "$(cat /sys/devices/virtual/thermal/cooling_device0/max_state)"
		[ "$c" == "s" ] && sleep 0.3 || sleep ${SleepInterval}
	done
} # MonitorMode

CheckDCINVoltage() {
	for i in /sys/devices/platform/sunxi-i2c.0/i2c-0/0-0034/axp20-supplyer.28/power_supply/usb/voltage_now \
		/sys/power/axp_pmu/vbus/voltage \
		/sys/devices/platform/sunxi-i2c.0/i2c-0/0-0034/axp20-supplyer.28/power_supply/ac/voltage_now \
		/sys/power/axp_pmu/ac/voltage '/sys/bus/iio/devices/iio:device0/in_voltage2_raw'; do
		if [ -f $i ]; then
			read -r DCINvoltage < $i 2> /dev/null
			if [ ${DCINvoltage} -gt 4080000 ]; then
				echo $i
				break
			fi
		fi
	done
} # CheckDCINVoltage

ProcessStats() {
	if [ -f /tmp/cpustat ]; then
		# RPi-Monitor/Armbianmonitor already running and providing processed values
		set $(awk -F" " '{print $1"\t"$2"\t"$3"\t"$4"\t"$5"\t"$6}' < /tmp/cpustat)
		CPULoad=$1
		SystemLoad=$2
		UserLoad=$3
		NiceLoad=$4
		IOWaitLoad=$5
		IrqCombinedLoad=$6
	else
		procStatLine=($(sed -n 's/^cpu\s//p' /proc/stat))
		UserStat=${procStatLine[0]}
		NiceStat=${procStatLine[1]}
		SystemStat=${procStatLine[2]}
		IdleStat=${procStatLine[3]}
		IOWaitStat=${procStatLine[4]}
		IrqStat=${procStatLine[5]}
		SoftIrqStat=${procStatLine[6]}

		Total=0
		for eachstat in "${procStatLine[@]}"; do
			Total=$((Total + eachstat))
		done

		UserDiff=$((UserStat - LastUserStat))
		NiceDiff=$((NiceStat - LastNiceStat))
		SystemDiff=$((SystemStat - LastSystemStat))
		IOWaitDiff=$((IOWaitStat - LastIOWaitStat))
		IrqDiff=$((IrqStat - LastIrqStat))
		SoftIrqDiff=$((SoftIrqStat - LastSoftIrqStat))

		diffIdle=$((IdleStat - LastIdleStat))
		diffTotal=$((Total - LastTotal))
		diffX=$((diffTotal - diffIdle))
		CPULoad=$((diffX * 100 / diffTotal))
		UserLoad=$((UserDiff * 100 / diffTotal))
		SystemLoad=$((SystemDiff * 100 / diffTotal))
		NiceLoad=$((NiceDiff * 100 / diffTotal))
		IOWaitLoad=$((IOWaitDiff * 100 / diffTotal))
		IrqCombined=$((IrqDiff + SoftIrqDiff))
		IrqCombinedLoad=$((IrqCombined * 100 / diffTotal))

		LastUserStat=${UserStat}
		LastNiceStat=${NiceStat}
		LastSystemStat=${SystemStat}
		LastIdleStat=${IdleStat}
		LastIOWaitStat=${IOWaitStat}
		LastIrqStat=${IrqStat}
		LastSoftIrqStat=${SoftIrqStat}
		LastTotal=${Total}
	fi
	procStats=$(printf "%3s%%%4s%%%4s%%%4s%%%4s%%%4s%%\n" "$CPULoad" "$SystemLoad" "$UserLoad" "$NiceLoad" "$IOWaitLoad" "$IrqCombinedLoad")
} # ProcessStats

MonitorIO() {
	LastPagesOut=$(awk '/pgpgout/ {print $2}' < /proc/vmstat)
	if grep -q "$1" /proc/diskstats; then
		LastWrite=$(awk -v d="$1" '{if($3 == d) print $8}' < /proc/diskstats)
	else
		echo "Bad argument: [$1]"
		echo "Disks valid for monitoring: $(
			awk '{if($8 != 0) printf "%s ", $3}' /proc/diskstats
		)"
		exit 1
	fi
	LastTimeChecked=$(date "+%s")
	while true; do
		CurrentWrite=$(awk -v d="$1" '{if($3 == d) print $8}' < /proc/diskstats)
		if [ ${CurrentWrite} -gt ${LastWrite} ]; then
			PagesOut=$(awk '/pgpgout/ {print $2}' < /proc/vmstat)
			TimeNow=$(date "+%s")
			PagesWritten=$((CurrentWrite - LastWrite))
			PageOuts=$((PagesOut - LastPagesOut))
			echo -e "$(LANG=C date)$(printf "%8s" ${PagesWritten})/${PageOuts} pages written after $((TimeNow - LastTimeChecked)) sec"
			LastTimeChecked=${TimeNow}
			LastPagesOut=${PagesOut}
			LastWrite=${CurrentWrite}
		fi
		sleep 1
	done
} # MonitorIO

CheckDisks() {
	# This function walks through all block devices whose name starts with sd* and
	# then gets the name hddtemp expects, the model name from smartctl, looks whether
	# the drive only lists one temperature value and patches hddtemp.db if necessary
	# and also tries to get CRC and LCC S.M.A.R.T. attributes to provide the user
	# with the necessary config file contents for /etc/armbianmonitor/disks.conf:

	ls /sys/block/sd* > /dev/null 2>&1 || exit 0

	for i in /sys/block/sd*; do
		DeviceNode=/dev/${i##*/}
		# get GUID/UUID for disk and check whether a partition table is existent. If
		# not GUID will always be random
		gdisk -l "${DeviceNode}" > "${MyTempDir}/gdisk.txt"
		GUID=$(awk -F" " '/^Disk identifier/ {print $4}' < "${MyTempDir}/gdisk.txt")
		CountOfUnavailablePartitionTables=$(grep ': not present' "${MyTempDir}/gdisk.txt" | wc -l)
		if [ ${CountOfUnavailablePartitionTables} -eq 4 ]; then
			echo -e "\nSkipping ${DeviceNode} due to missing partition table. Use parted to create one."
			break
		else
			printf "\nExamining %s with GUID %s" "$DeviceNode" "$GUID"
		fi

		# get name hddtemp needs
		HddtempName="$(hddtemp --debug "${DeviceNode}" | awk -F": " '/^Model: / {print $2}' |
			cut -c-40 | sed 's/^[ \t]*//;s/[ \t]*$//')"
		# store smartctl output in temporary file
		smartctl -q noserial -s on -a "${DeviceNode}" > "${MyTempDir}/smartctl.txt" 2>&1
		DeviceModel="$(awk -F": " '/^Device Model/ {print $2}' < "${MyTempDir}/smartctl.txt" |
			sed 's/^[ \t]*//;s/[ \t]*$//')"
		if [ "X${DeviceModel}" = "X" ]; then
			# Reading S.M.A.R.T. failed, we try autodetect mode iterating through all
			# known smartctl modes (-d auto|sat|usbcypress|usbjmicron|usbprolific|usbsunplus)
			SMARTPrefix="$(CheckSMARTModes ${DeviceNode} 2> /dev/null)"
			if [ "X${SMARTPrefix}" = "X" ]; then
				# we can't query the disk. Time to give up
				echo -e "\nUnable to query the disk through S.M.A.R.T.\nPlease investigate manually using smartctl\n"
				break
			fi
		fi

		# user feedback
		if [ "X${SMARTPrefix}" = "X" ]; then
			echo -e " \n(accessible through S.M.A.R.T.)"
		else
			echo -e " \n(can be queried with \"-d ${SMARTPrefix}\" through S.M.A.R.T.)"
		fi

		# check for CRC and LCC attributes
		CRCAttribute=$(awk -F" " '/CRC_Error_Count/ {print $1}' < "${MyTempDir}/smartctl.txt")
		LCCAttribute=$(grep -i "load.cycle" "${MyTempDir}/smartctl.txt" | awk -F" " '{print $1}')

		# check whether /etc/hddtemp.db should be patched
		grep -q "${HddtempName}" /etc/hddtemp.db
		if [ $? -ne 0 ]; then
			# No entry into hddtemp database, we've a look whether there's a 'temperature'
			# attribute available (we take the 1st we find) and if that's the case we use this
			DiskTemp=$(awk -F" " '/Temperature/ {print $1}' < "${MyTempDir}/smartctl.txt" | head -n1)
			if [[ ${DiskTemp} -gt 0 ]]; then
				echo -e "\"${HddtempName}\" ${DiskTemp} C \"${DeviceModel}\"" >> /etc/hddtemp.db
				echo -e "\nAdded disk \"${DeviceModel}\"/\"${HddtempName}\" to /etc/hddtemp.db using S.M.A.R.T. attribute ${DiskTemp}\nbased on the following available thermal values:"
				grep "Temperature" "${MyTempDir}/smartctl.txt"
				# check hddtemp result
				HddtempResult=$(hddtemp -n ${DeviceNode} | grep -v 'not available' | awk -F" " '{print $1}')
				if [ "X${HddtempResult}" != "X"${DeviceNode}":" ]; then
					# hddtemp isn't able to query the disk
					HddtempStatus="does not work. Please check with smartctl and adjust config accordingly"
					echo -e "\nhddtemp output: $(hddtemp "${DeviceNode}")"
					echo -e "\nIt seems we can not rely on hddtemp to query this disk. Please try smartctl instead\n"
				else
					HddtempStatus="will work"
					echo -e "\nhddtemp output: ${HddtempResult})"
					echo -e "\nIn case this seems not to be correct please adjust /etc/hddtemp.db manually\n"
				fi
			else
				HddtempStatus="does not work. Please check with smartctl and adjust config accordingly"
			fi
		else
			HddtempStatus="will work"
		fi

		# check for firmware updates
		FirmwareUpdate="$(grep "^http" "${MyTempDir}/smartctl.txt")"

		# Check whether the disk (based on GUID) is already configured in our config file
		# /etc/armbianmonitor/disks.conf or not

		grep -q "^${GUID}:" /etc/armbianmonitor/disks.conf > /dev/null 2> /dev/null
		case $? in
			0)
				# already listed, we provide just infos:
				echo -e "Disk is already configured by the following monitoring config:\n$(grep "^${GUID}:" /etc/armbianmonitor/disks.conf)\n"
				;;
			*)
				# new disk, we recommend an entry for /etc/armbianmonitor/disks.conf
				echo -e "Disk not configured for monitoring. We were able to extract the following \ninformation:\n   GUID: ${GUID}"
				if [ "X${SMARTPrefix}" != "X" ]; then
					echo -e "   QueryMode: -d ${SMARTPrefix}"
				fi
				echo -e "   hddtemp: ${HddtempStatus}\n   CRC attribute: ${CRCAttribute}\n   LCC Attribute: ${LCCAttribute}"
				case ${HddtempStatus} in
					"will work")
						echo -e "If you want to monitor the disk please add to /etc/armbianmonitor/disks.conf:\n${GUID}:${DeviceModel}:${SMARTPrefix}::${CRCAttribute}:${LCCAttribute}"
						;;
					*)
						echo -e "Proposal for /etc/armbianmonitor/disks.conf:\n${GUID}:${DeviceModel}:${SMARTPrefix}:FIXME:${CRCAttribute}:${LCCAttribute}"
						echo -e "You have to figure out how to query the disk for its thermal sensor."
						echo -e "Please check the output of \"hddtemp --debug "${DeviceNode}"\" and smartctl\n"
						;;
				esac
				;;
		esac
		if [ "X${FirmwareUpdate}" != "X" ]; then
			echo -e "\nWARNING: A firmware update seems to be available:\n${FirmwareUpdate}\n"
		fi
	done
} # CheckDisks

CheckSMARTModes() {
	# This function tries to access USB disks through S.M.A.R.T. and returns the necessary
	# '-d' call as well as fills in ${MyTempDir}/smartctl.txt

	for i in auto sat usbcypress usbjmicron usbprolific usbsunplus; do
		# user feedback
		# echo -n "." >/dev/tty
		# query disk using the specific protocol
		echo -n "" > "${MyTempDir}/smartctl.txt"
		smartctl -q noserial -s on -d ${i} -a ${1} > "${MyTempDir}/smartctl.txt" 2> /dev/null
		DeviceModel="$(awk -F": " '/^Device Model/ {print $2}' < "${MyTempDir}/smartctl.txt" |
			sed 's/^[ \t]*//;s/[ \t]*$//')"
		if [ "X${DeviceModel}" != "X" ]; then
			echo ${i}
			break
		fi
	done
} # CheckSMARTModes

PreRequisits() {
	# Ensure that we're running as root since otherwise querying SATA/USB disks won't work
	if [ "$(id -u)" != "0" ]; then
		echo "This script must be run as root" >&2
		exit 1
	fi

	export PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
	unset LANG
	DISTROCODE=$(lsb_release -s -c)

	# check whether gdisk/smartctl are available and up to date
	printf "Check whether necessary software is available"
	which gdisk > /dev/null 2>&1 || (
		printf " Installing gdisk"
		apt-get -f -qq -y install gdisk
	)
	which smartctl > /dev/null 2>&1 || (
		printf " Installing smartmontools"
		apt-get -f -qq -y install smartmontools
	)
	printf " [done]\nUpdating smartmontools' drivedb"
	/usr/sbin/update-smart-drivedb > /dev/null 2>&1
	if [ $? -ne 0 -a "X${DISTROCODE}" = "Xwheezy" ]; then
		sed -i "/^SRCEXPR/{s#=.*#='http://sourceforge.net/p/smartmontools/code/HEAD/tree/\$location/smartmontools/drivedb.h?format=raw'#}" /usr/sbin/update-smart-drivedb
		/usr/sbin/update-smart-drivedb
	fi
	echo -e " [done]"
	CreateTempDir
} # PreRequisits

CreateTempDir() {
	# create a safe temporary dir
	MyTempDir=$(mktemp -d /tmp/${0##*/}.XXXXXX)
	if [ ! -d "${MyTempDir}" ]; then
		MyTempDir=/tmp/${0##*/}.$RANDOM.$RANDOM.$RANDOM.$$
		(umask 066 && mkdir "${MyTempDir}") || (
			echo "Failed to create temp dir. Aborting" >&2
			exit 1
		)
	fi
	chmod 711 "${MyTempDir}"
	trap "rm -rf \"${MyTempDir}\" ; exit 0" 0 1 2 3 15
	for file in smartctl.txt gdisk.txt; do
		touch "${MyTempDir}/${file}"
		chmod 644 "${MyTempDir}/${file}"
	done
} #CreateTempFiles

CheckCard() {
	if [ "$(id -u)" = "0" ]; then
		echo "Checking disks is not permitted as root or through sudo. Exiting" >&2
		exit 1
	fi

	if [ ! -d "$1" ]; then
		echo "\"$1\" does not exist or is no directory. Exiting" >&2
		exit 1
	fi
	TargetDir="$1"

	# check requirements
	which f3write > /dev/null 2>&1 || MissingTools=" f3"
	which iozone > /dev/null 2>&1 || MissingTools="${MissingTools} iozone3"
	if [ "X${MissingTools}" != "X" ]; then
		echo "Some tools are missing, please do an \"sudo apt-get -f -y install${MissingTools}\" before and try again" >&2
		exit 1
	fi

	# check provided path
	Device="$(GetDevice "$1")"
	set ${Device}
	DeviceName=$1
	FileSystem=$2
	echo "${DeviceName}" | grep -q "mmcblk0" || echo -e "\n${BOLD}WARNING:${NC} It seems you're not testing the SD card but instead ${DeviceName} (${FileSystem})\n"

	TestDir="$(mktemp -d "${TargetDir}/cardtest.XXXXXX" || exit 1)"
	date "+%s" > "${TestDir}/.starttime" || exit 1
	trap "rm -rf \"${TestDir}\" ; exit 0" 0 1 2 3 15
	LogFile="$(mktemp /tmp/armbianmonitor_checks_${DeviceName##*/}_${FileSystem}.XXXXXX)"

	# start actual test, create a small file for some space reserve
	fallocate -l 32M "${TestDir}/empty.32m" 2> /dev/null || dd if=/dev/zero of="${TestDir}/empty.32m" bs=1M count=32 status=noxfer > /dev/null 2>&1
	ShowWarning=false
	# Start writing
	echo -e "Starting to fill ${DeviceName} with test patterns, please be patient this might take a very long time"
	f3write "${TestDir}" | tee "${LogFile}"
	touch "${TestDir}/.starttime" || ShowDeviceWarning
	rm "${TestDir}/empty.32m"
	# Start verify
	echo -e "\nNow verifying the written data:"
	echo "" >> "${LogFile}"
	f3read "${TestDir}" | tee -a "${LogFile}"
	touch "${TestDir}/.starttime" || ShowDeviceWarning
	rm "${TestDir}/"*.h2w
	echo -e "\nStarting iozone tests. Be patient, this can take a very long time to complete:"
	echo "" >> "${LogFile}"
	cd "${TestDir}"
	iozone -e -I -a -s 100M -r 4k -r 512k -r 16M -i 0 -i 1 -i 2 | tee -a "${LogFile}"
	touch "${TestDir}/.starttime" || ShowDeviceWarning
	echo -e "\n${BOLD}The results from testing ${DeviceName} (${FileSystem}):${NC}"
	grep -E "Average|Data" "${LogFile}" | sort -r
	echo "                                            random    random"
	printf "reclen    write  rewrite    read    reread    read     write"
	awk -F"102400  " '/102400/ {print $2}' < "${LogFile}"

	# check health
	printf "\n${BOLD}Health summary: "
	grep -Eq "Read-only|Input/output error" "${LogFile}" && (
		echo -e "${LRED}${BOLD}${DeviceName} failed${NC}"
		exit 0
	)
	grep -q "Data LOST: 0.00 Byte" "${LogFile}" && echo -e "${LGREEN}OK" ||
		(
			echo -e "${LRED}${BOLD}${DeviceName} failed. Replace it as soon as possible!"
			grep -A3 "^Data LOST" "${LogFile}"
		)

	# check performance
	RandomSpeed=$(awk -F" " '/102400       4/ {print $7"\t"$8}' < "${LogFile}")
	if [ "X${RandomSpeed}" != "X" ]; then
		# Only continue when we're able to read out iozone results
		set ${RandomSpeed}
		RandomReadSpeed=$1
		RandomWriteSpeed=$2
		ReadSpeed=$(awk -F" " '/Average reading speed/ {print $4"\t"$5}' < "${LogFile}")
		set ${ReadSpeed}
		if [ "X$2" = "XMB/s" ]; then
			RawReadSpeed=$(echo "$1 * 1000" | bc -s | cut -f1 -d.)
		else
			RawReadSpeed=$(echo "$1" | cut -f1 -d.)
		fi
		printf "\n${NC}${BOLD}Performance summary:${NC}\nSequential reading speed:$(printf "%6s" $1) $2 "
		[ ${RawReadSpeed} -le 2500 ] && Exclamation="${LRED}${BOLD}way " || Exclamation=""
		[ ${RawReadSpeed} -le 5000 ] && Exclamation="${Exclamation}${BOLD}too "
		[ ${RawReadSpeed} -le 7500 ] && printf "(${Exclamation}low${NC})"
		echo "${Exclamation}" | grep -q "too" && ShowWarning=true
		printf "\n 4K random reading speed: %6s KB/s " "$RandomReadSpeed"
		[ ${RandomReadSpeed} -le 700 ] && Exclamation="${LRED}${BOLD}way " || Exclamation=""
		[ ${RandomReadSpeed} -le 1400 ] && Exclamation="${Exclamation}${BOLD}too "
		[ ${RandomReadSpeed} -le 2500 ] && printf "(${Exclamation}low${NC})"
		echo "${Exclamation}" | grep -q "too" && ShowWarning=true
		WriteSpeed=$(awk -F" " '/Average writing speed/ {print $4"\t"$5}' < "${LogFile}")
		set ${WriteSpeed}
		if [ "X$2" = "XMB/s" ]; then
			RawWriteSpeed=$(echo "$1 * 1000" | bc -s | cut -f1 -d.)
		else
			RawWriteSpeed=$(echo "$1" | cut -f1 -d.)
		fi
		printf "\nSequential writing speed: %6s %s " "$1" "$2"
		[ ${RawWriteSpeed} -le 2500 ] && Exclamation="${LRED}${BOLD}way " || Exclamation=""
		[ ${RawWriteSpeed} -le 4000 ] && Exclamation="${Exclamation}${BOLD}too "
		[ ${RawWriteSpeed} -le 6000 ] && printf "(${Exclamation}low${NC})"
		echo "${Exclamation}" | grep -q "too" && ShowWarning=true
		printf "\n 4K random writing speed: %6s KB/s " "$RandomWriteSpeed"
		[ ${RandomWriteSpeed} -le 400 ] && Exclamation="${LRED}${BOLD}way " || Exclamation=""
		[ ${RandomWriteSpeed} -le 750 ] && Exclamation="${Exclamation}${BOLD}too "
		[ ${RandomWriteSpeed} -lt 1000 ] && printf "(${Exclamation}low${NC})"
		echo "${Exclamation}" | grep -q "too" && ShowWarning=true
		if [ "X${ShowWarning}" = "Xtrue" ]; then
			echo -e "\n\n${BOLD}The device you tested seems to perform too slow to be used with Armbian."
			echo -e "This applies especially to desktop images where slow storage is responsible"
			echo -e "for sluggish behaviour. If you want to have fun with your device do NOT use"
			printf "this media to put the OS image or the user homedirs on.${NC}"
		fi
		echo -e "\n\nTo interpret the results above correctly or search for better storage
alternatives please refer to http://oss.digirati.com.br/f3/ and also
http://www.jeffgeerling.com/blogs/jeff-geerling/raspberry-pi-microsd-card
and http://thewirecutter.com/reviews/best-microsd-card/"
	fi
} # CheckCard

ShowDeviceWarning() {
	echo -e "\n${LRED}${BOLD}Test stopped, read-only filesystem\n\n${NC}${LRED}$(dmesg | grep 'I/O error')"
	echo -e "\n${BOLD}Please be careful using this media since it seems it's already broken. Exiting test.\n${NC}"
	exit 0
} # ShowDeviceWarning

GetDevice() {
	if TestPath=$(findmnt --noheadings --output SOURCE,FSTYPE --target "$1" --uniq); then
		echo "${TestPath}"
	else
		echo "Bad Path: $1" >&2
		exit 1
	fi
} # GetDevice

VerifyInstallation() {
	# Ensure that we're running as root since otherwise querying SATA/USB disks won't work
	if [ "$(id -u)" != "0" ]; then
		echo "This check must be run as root. Aborting." >&2
		exit 1
	fi

	echo -e "Starting package integrity check. This might take some time. Be patient please..."
	OUTPUT=$(dpkg --verify | grep -Evi "${VerifyRepairExcludes}" | awk -F" /" '{print "/"$2}')
	if [[ -z "$OUTPUT" ]]; then
		echo -e "\n${LGREEN}${BOLD}It appears you don't have any corrupt files or packages!${NC}"
	else
		echo -e "\n${LRED}${BOLD}It appears you may have corrupt packages.${NC}\n"
		echo -e "This is usually a symptom of filesystem corruption caused by SD cards or eMMC"
		echo -e "dying or burning the OS image to the installation media went wrong.\n"
		echo -e "The following changes from packaged state files were detected:\n"
		echo -e "${OUTPUT}\n"
	fi
} # VerifyInstallation

NetworkMonitorMode() {

	# Allow armbianmonitor to return back to armbian-config
	trap "echo ; exit 0" 0 1 2 3 15

	# Ensure net-tools is installed for 'route' command
	which route > /dev/null 2>&1 || (apt-get update && apt-get -f -y install net-tools)

	# Count interfaces - multiple routes causing interfaces to show up more than once, filtering...
	ifacecount=$(route -n | grep -E UG | grep -Eo '[^ ]*$' | sort | uniq)
	# If there are two ore more interfaces detected open a dynamic dialog box to select which to monitor
	if [ "$(echo -e $ifacecount | tr ' ' '\n' | wc -l)" -gt 1 ]; then
		ifacemenu=$(route -n | grep -E UG | grep -Eo '[^ ]*$' | sort | uniq | awk '{a[$1]=$1}END{for(i in a)printf i" "a[i]" "}')
		ifacefunc() {
			dialog --backtitle "Interface selector" \
				--title "Multiple network interfaces detected" \
				--menu "Choose which interface to monitor:" \
				15 50 $(route -n | grep -E UG | grep -Eo '[^ ]*$' | sort | uniq | wc -l) \
				$(echo $ifacemenu) 2>&1 > $(tty)
		}
		iface=$(ifacefunc)
	else
		# Use default behavior if one interface is found only
		iface=$(route -n | grep -E UG | grep -Eo '[^ ]*$')
	fi
	timerStart
	kickAllStatsDown

	printf "\nruntime network statistics: %s\n" "$(uname -n)"
	printf "network interface: %s\n" "$iface"
	printf "[tap 'd' to display column headings]\n"
	printf "[tap 'z' to reset counters]\n"
	printf "[use <ctrl-c> to exit]\n"
	printf "[bps: bits/s, Mbps: megabits/s, pps: packets/s, MB: megabytes]\n\n"
	printf "%-11s %-66s          %-66s\n" "$iface" "rx.stats____________________________________________________________" "tx.stats____________________________________________________________"
	printf "%-11s %-11s %-11s \u01B0.%-11s %-11s \u01B0.%-11s \u01A9.%-11s %-11s %-11s \u01B0.%-11s %-11s \u01B0.%-11s \u01A9.%-11s\n\n" "count" "bps" "Mbps" "Mbps" "pps" "pps" "MB" "bps" "Mbps" "Mbps" "pps" "pps" "MB"

	while true; do
		nss=($(sed -n 's/'$iface':\s//p' /proc/net/dev))
		rxB=${nss[0]}
		rxP=${nss[1]}
		txB=${nss[8]}
		txP=${nss[9]}
		drxB=$((rxB - prxB))
		drxb=$((drxB * 8))
		drxmb=$(echo "scale=2;$drxb/1000000" | bc)
		drxP=$((rxP - prxP))
		dtxB=$((txB - ptxB))
		dtxb=$((dtxB * 8))
		dtxmb=$(echo "scale=2;$dtxb/1000000" | bc)
		dtxP=$((txP - ptxP))
		if [ "$cnt" != "0" ]; then
			if [ "$c" == "N" ]; then
				printf "\x1b[1A"
			fi
			srxb=$((srxb + drxb))
			stxb=$((stxb + dtxb))
			srxB=$((srxB + drxB))
			stxB=$((stxB + dtxB))
			srxP=$((srxP + drxP))
			stxP=$((stxP + dtxP))
			srxMB=$(echo "scale=2;$srxB/1024^2" | bc)
			stxMB=$(echo "scale=2;$stxB/1024^2" | bc)
			arxb=$(echo "scale=2;$srxb/$cnt" | bc)
			atxb=$(echo "scale=2;$stxb/$cnt" | bc)
			arxmb=$(echo "scale=2;$arxb/1000000" | bc)
			atxmb=$(echo "scale=2;$atxb/1000000" | bc)
			arxP=$(echo "scale=0;$srxP/$cnt" | bc)
			atxP=$(echo "scale=0;$stxP/$cnt" | bc)
			printf "%-11s %-11s %-11s   %-11s %-11s   %-11s   %-11s %-11s %-11s   %-11s %-11s   %-11s   %-11s\n" "$cnt" "$drxb" "$drxmb" "$arxmb" "$drxP" "$arxP" "$srxMB" "$dtxb" "$dtxmb" "$atxmb" "$dtxP" "$atxP" "$stxMB"
		fi
		prxB="$rxB"
		prxP="$rxP"
		ptxB="$txB"
		ptxP="$txP"
		let cnt++
		timerShut
		read -r -n1 -s -t$procSecs zeroAll
		timerStart
		if [ "$zeroAll" == 'z' ]; then
			kickAllStatsDown
		fi
		if [ "$zeroAll" == 'd' ]; then
			scrollingHeader
		fi
	done
}

scrollingHeader() {
	printf "%-11s %-66s          %-66s\n" "$iface" "rx.stats____________________________________________________________" "tx.stats____________________________________________________________"
	printf "%-11s %-11s %-11s \u01B0.%-11s %-11s \u01B0.%-11s \u01A9.%-11s %-11s %-11s \u01B0.%-11s %-11s \u01B0.%-11s \u01A9.%-11s\n\n" "count" "bps" "Mbps" "Mbps" "pps" "pps" "MB" "bps" "Mbps" "Mbps" "pps" "pps" "MB"
}

timerStart() {
	read -r st0 st1 < <(date +'%s %N')
}
timerShut() {
	read -r sh0 sh1 < <(date +'%s %N')
	jusquaQuand=$(echo "scale=2;($sh0-$st0)*1000000000+($sh1-$st1)" | bc)
	procSecs=$(echo "scale=2;(1000000000-$jusquaQuand)/1000000000" | bc)
	if [ "$rf1" == "debug" ]; then
		printf "time controller adjustment: %d\n" "$procSecs"
		if [ "$c" == "N" ]; then
			printf "\x1b[1A"
		fi
	fi
}
kickAllStatsDown() {
	prxB=0
	prxP=0
	ptxB=0
	ptxP=0
	srxb=0
	stxb=0
	srxB=0
	stxB=0
	srxMB=0
	stxMB=0
	srxP=0
	stxP=0
	cnt=0
}

Main "$@"
