diff --git a/lpcpu.sh b/lpcpu.sh index fef78bd..2391f2f 100755 --- a/lpcpu.sh +++ b/lpcpu.sh @@ -1138,6 +1138,55 @@ function sigint_running_trap() { #################################################################################################### +## Network statistics helper functions ############################################################# + +function has_netstat() { command -v netstat > /dev/null 2>&1; } +function has_ss() { command -v ss > /dev/null 2>&1; } +function has_nstat() { command -v nstat > /dev/null 2>&1; } +function has_iproute2() { command -v ip > /dev/null 2>&1 && ip -V > /dev/null 2>&1; } + +# Collect active socket statistics (netstat -v preferred; ss fallback) +function collect_active_socket_statistics() { + local output_file="$1" + if has_netstat; then + netstat -v > "$output_file" 2>&1 + elif has_ss; then + ss > "$output_file" 2>&1 + else + echo "netstat nor ss not available" > "$output_file" + fi +} + +# Collect kernel network protocol statistics (netstat -s preferred; nstat fallback) +function kernel_network_protocol_statistics() { + local output_file="$1" + if has_netstat; then + netstat -s > "$output_file" 2>&1 + elif has_nstat; then + nstat -az > "$output_file" 2>&1 + else + echo "netstat nor nstat not available" > "$output_file" + fi +} + +# Collect kernel interface table (netstat -in preferred; ip -s link fallback) +function kernel_interface_table() { + local output_file="$1" + local formatting_script="${LPCPUDIR}/tools/ip_to_netstat.py" + local temp_file="${output_file}.tmp" + if has_netstat; then + netstat -in > "$output_file" 2>&1 + elif has_iproute2; then + ip -s link > "$temp_file" 2>&1 + python3 "$formatting_script" "$temp_file" > "$output_file" 2>&1 + rm -f "$temp_file" + else + echo "netstat nor ip not available" > "$output_file" + fi +} + +#################################################################################################### + # main block, used to log all output { trap sigint_normal_trap SIGINT @@ -1158,9 +1207,9 @@ function sigint_running_trap() { done cat /proc/net/netstat > $LOGDIR/netstat.before - netstat -in > $LOGDIR/netstat-in.before 2>&1 - netstat -v > $LOGDIR/netstat-v.before 2>&1 - netstat -s > $LOGDIR/netstat-s.before 2>&1 + kernel_interface_table "$LOGDIR/netstat-in.before" + collect_active_socket_statistics "$LOGDIR/netstat-v.before" + kernel_network_protocol_statistics "$LOGDIR/netstat-s.before" cat /proc/interrupts > $LOGDIR/interrupts.before cat /proc/meminfo > $LOGDIR/meminfo.before if (( depth > 1 )); then @@ -1218,9 +1267,9 @@ function sigint_running_trap() { trap sigint_normal_trap SIGINT cat /proc/net/netstat > $LOGDIR/netstat.after - netstat -in > $LOGDIR/netstat-in.after 2>&1 - netstat -v > $LOGDIR/netstat-v.after 2>&1 - netstat -s > $LOGDIR/netstat-s.after 2>&1 + kernel_interface_table "$LOGDIR/netstat-in.after" + collect_active_socket_statistics "$LOGDIR/netstat-v.after" + kernel_network_protocol_statistics "$LOGDIR/netstat-s.after" cat /proc/interrupts > $LOGDIR/interrupts.after cat /proc/meminfo > $LOGDIR/meminfo.after if (( depth > 1 )); then diff --git a/tools/__pycache__/ip_to_netstat.cpython-314.pyc b/tools/__pycache__/ip_to_netstat.cpython-314.pyc new file mode 100644 index 0000000..8ef2c29 Binary files /dev/null and b/tools/__pycache__/ip_to_netstat.cpython-314.pyc differ diff --git a/tools/ip_to_netstat.py b/tools/ip_to_netstat.py new file mode 100755 index 0000000..6c14f73 --- /dev/null +++ b/tools/ip_to_netstat.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +""" +ip_to_netstat.py — convert `ip -s link` output to netstat -in column layout. + +Usage: ip_to_netstat.py + +The script reads the file produced by `ip -s link` and prints a table that +matches the column layout of `netstat -in`: + + Iface MTU RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg +""" + +import re +import sys + + +def parse_ip_s_link(path): + """Parse `ip -s link` output and return a list of interface dicts.""" + with open(path) as fh: + text = fh.read() + + # Each interface block starts with a numbered line like: + # 1: lo: mtu 65536 ... + blocks = re.split(r'(?=^\d+:)', text, flags=re.MULTILINE) + + interfaces = [] + for block in blocks: + block = block.strip() + if not block: + continue + + # Header line: index, name, flags, mtu + header_m = re.match( + r'^\d+:\s+(\S+?)(?:@\S+)?:\s+<([^>]*)>\s+mtu\s+(\d+)', + block, re.MULTILINE + ) + if not header_m: + continue + + name = header_m.group(1) + flags = header_m.group(2).split(',') + mtu = header_m.group(3) + + # RX stats line: " RX: bytes packets errors dropped missed mcast" + # followed by values on the next line. + rx_m = re.search( + r'RX:\s+bytes\s+packets\s+errors\s+dropped.*?\n\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)', + block + ) + # TX stats line + tx_m = re.search( + r'TX:\s+bytes\s+packets\s+errors\s+dropped.*?\n\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)', + block + ) + + rx_ok = rx_m.group(2) if rx_m else '0' + rx_err = rx_m.group(3) if rx_m else '0' + rx_drp = rx_m.group(4) if rx_m else '0' + + tx_ok = tx_m.group(2) if tx_m else '0' + tx_err = tx_m.group(3) if tx_m else '0' + tx_drp = tx_m.group(4) if tx_m else '0' + + # Build flags string similar to netstat (B=BROADCAST, L=LOOPBACK, + # M=MULTICAST, R=RUNNING, U=UP) + flg_map = { + 'UP': 'U', + 'LOOPBACK': 'L', + 'BROADCAST': 'B', + 'MULTICAST': 'M', + 'RUNNING': 'R', + 'POINTOPOINT': 'P', + 'PROMISC': 'N', + } + flg = ''.join(flg_map[f] for f in flg_map if f in flags) + + interfaces.append({ + 'name': name, + 'mtu': mtu, + 'rx_ok': rx_ok, + 'rx_err': rx_err, + 'rx_drp': rx_drp, + 'rx_ovr': '0', + 'tx_ok': tx_ok, + 'tx_err': tx_err, + 'tx_drp': tx_drp, + 'tx_ovr': '0', + 'flg': flg or 'U', + }) + + return interfaces + + +def main(): + if len(sys.argv) != 2: + sys.exit("Usage: ip_to_netstat.py ") + + interfaces = parse_ip_s_link(sys.argv[1]) + + header = ( + f"{'Iface':<10} {'MTU':>6} " + f"{'RX-OK':>10} {'RX-ERR':>8} {'RX-DRP':>8} {'RX-OVR':>8} " + f"{'TX-OK':>10} {'TX-ERR':>8} {'TX-DRP':>8} {'TX-OVR':>8} Flg" + ) + print(header) + + for i in interfaces: + print( + f"{i['name']:<10} {i['mtu']:>6} " + f"{i['rx_ok']:>10} {i['rx_err']:>8} {i['rx_drp']:>8} {i['rx_ovr']:>8} " + f"{i['tx_ok']:>10} {i['tx_err']:>8} {i['tx_drp']:>8} {i['tx_ovr']:>8} {i['flg']}" + ) + + +if __name__ == '__main__': + main()