Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 55 additions & 6 deletions lpcpu.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
Binary file added tools/__pycache__/ip_to_netstat.cpython-314.pyc
Binary file not shown.
116 changes: 116 additions & 0 deletions tools/ip_to_netstat.py
Original file line number Diff line number Diff line change
@@ -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 <ip-s-link-output-file>

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: <LOOPBACK,UP,LOWER_UP> 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 <ip-s-link-output-file>")

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()