#! /bin/bash
# vim: cindent:shiftwidth=4:tabstop=4:smarttab:textwidth=100

set -o posix
set -o errexit
set -o pipefail
set -o nounset
#set -o xtrace

#$title$ network interface driver errors
#$check$ error counters (rx_over_errors rx_frame_errors rx_fifo_errors, etc.) not incrementing
#$ref$ KB:InterfaceBonding, 'ethtool --statistics'
#$author$ Rafal Rzeczkowski
#$version$ 0.7.1

level_check short

#CHANGELOG
#0.5.0	initial, based on nic-errors 0.72
#0.5.1	ignore interfaces without stats
#0.5.2	ignore rx_csum_offload_errors (to match rx_queue_?_csum_err)
#0.5.3	recognize statistic error keys on sky2 driver
#0.5.4	ignore fdir_overflow (Flow Director tring to match RX and TX queues)
#0.5.5	replace version variable with a text changelog
#0.5.6	correct shellcheck issues
#0.5.7	restyle according to https://kb.clearcable.ca/KB/ProgrammingStyleStandards
#0.5.8	remove (empty) temporary file when there is no output ("via-rhine:no stats available")
#0.5.9	enumerate physical network interfaces based on sysfs symlink name components
#0.6.0	exclude CAN bus devices ("no stats available")
#0.7.0	blacklist Intel 82576
#0.7.1	improve robustness of udevadm parser

declare -r SYSFS='/sys/class/net'

declare -r NIC_PHY=($(find $SYSFS -type l \
	-not -lname '*/devices/virtual/*'\
	-not -lname '*/devices/vif*'\
	-not -lname '*/net/can*'\
	-printf '%f\n' | sort))

TMP_ETHTOOL_ALL=$(mktemp)
cd $SYSFS
for iface in ${NIC_PHY[@]}; do
	eval $(udevadm info "/sys/class/net/$iface" | awk '
BEGIN { FS="E:[[:space:]]" }
{ if (index($2,"ID_")==1) {
	sub("=","=\"",$2)
	sub("$","\"",$2)
	print $2
}}')

	# process blacklist
	echodebug "$iface $ID_VENDOR_ID/$ID_MODEL_ID"
	case "$ID_VENDOR_ID/$ID_MODEL_ID" in
		'0x8086/0x10c9') continue;;
	esac

	TMP_ETHTOOL=$(mktemp)
	if ethtool --statistics "$iface" > "$TMP_ETHTOOL"; then
		awk --assign iface="$iface" '
		BEGIN {FS=":[[:space:]]"}
		{ if (NR==1){next}; sub("^[[:space:]]+","",$1); print iface":"$1":"$2}
		' "$TMP_ETHTOOL" >> "$TMP_ETHTOOL_ALL"
	fi
	rm $TMP_ETHTOOL
done

if [[ ! -s "$TMP_ETHTOOL_ALL" ]]; then
	rm $TMP_ETHTOOL_ALL
	unknown
	exit
fi

#$ sudo ethtool --statistics eth3;echo $?
#no stats available
#94

#$ sudo ethtool --statistics eth0
#NIC statistics:
#     rx_packets: 12070381703
#     tx_packets: 28371884230
#     rx_bytes: 1267454846021
#     tx_bytes: 40428210448569
#     rx_broadcast: 2095356
#     tx_broadcast: 34
#     rx_multicast: 1136438
#     tx_multicast: 9170
#     multicast: 1136438
#     collisions: 0
#     rx_crc_errors: 0

#$ ethtool --statistics eth0
#NIC statistics:
#     collisions: 0
#     late_collision: 0
#     aborted: 0
#     single_collisions: 0
#     multi_collisions: 0
#     rx_short: 0
#     rx_runt: 0
#     rx_too_long: 0
#     rx_fifo_overflow: 0
#     rx_jabber: 0
#     rx_fcs_error: 0
#     tx_fifo_underrun: 0

TMP_FIELDS=$(mktemp)
TMP_COUNTERS=$(mktemp)
awk '
BEGIN { FS=":" }
{
	dev=$1
	field=$2
	counter=$3
	if (field~/^(rx_csum_offload_errors|fdir_overflow)$/) {
		next
	} else if (field~/_(sync|errors?|failed|drops|collisions?|overflow|underrun)$/) {
		devs[dev]=dev
		fields[field]=field
		errors[dev"_"field]=counter
	}
}
END {
	for (field in fields) {
		printf(" " field) > "/dev/stderr"
	}
	print "" > "/dev/stderr"
	for (dev in devs) {
		printf(dev)
		for (field in fields) {
			val=errors[dev"_"field]
			if (length(val)==0)
				val="U"
			#print dev":"field":"val
			printf("\t" val)
		}
		print ""

	}
}
' "$TMP_ETHTOOL_ALL" 1>"$TMP_COUNTERS" 2>"$TMP_FIELDS"
#cat "$TMP_FIELDS" "$TMP_COUNTERS"

export OBJECT_DESC='network interface driver'
export FIELD_LIST=($(<"$TMP_FIELDS"))
export STATE_CUR=$(<"$TMP_COUNTERS")
export HELPMSG='verify {PHY and low level MAC settings}; replace cable; change switch port'
rm "$TMP_ETHTOOL_ALL" "$TMP_COUNTERS" "$TMP_FIELDS"

check_error_counter_list
