Skip to content

Instantly share code, notes, and snippets.

@NQevxvEtg
Created April 14, 2026 15:02
Show Gist options
  • Select an option

  • Save NQevxvEtg/307caf156966d0da10ec837bb5d30135 to your computer and use it in GitHub Desktop.

Select an option

Save NQevxvEtg/307caf156966d0da10ec837bb5d30135 to your computer and use it in GitHub Desktop.
#!/bin/bash
#
# networkmonitor.sh - Per-process network bandwidth monitor (nethogs alternative)
#
# Samples 'ss -tupni' byte counters (bytes_acked / bytes_received) per connection,
# maps them to PIDs, and computes upload/download rates.
#
# Usage: ./networkmonitor.sh [interval_seconds]
# Run as root (sudo) for full visibility of all processes.
INTERVAL=${1:-2}
if ! command -v ss &>/dev/null; then
echo "Error: 'ss' command not found." >&2
exit 1
fi
if (( EUID != 0 )); then
echo "Note: Run as root (sudo) to see all processes." >&2
fi
declare -A _CTX _CRX _CN _PTX _PRX _PN
parse_ss() {
_CTX=(); _CRX=(); _CN=()
local pid="" name="" tx=0 rx=0
while IFS= read -r line; do
if [[ "$line" =~ ^(tcp|udp) ]]; then
[[ -n "$pid" ]] && {
_CTX[$pid]=$(( ${_CTX[$pid]:-0} + tx ))
_CRX[$pid]=$(( ${_CRX[$pid]:-0} + rx ))
_CN[$pid]="$name"
}
tx=0; rx=0; pid=""
[[ "$line" =~ users:\(\(\"([^\"]+)\",pid=([0-9]+) ]] && {
name="${BASH_REMATCH[1]}"; pid="${BASH_REMATCH[2]}"
}
else
[[ "$line" =~ bytes_acked:([0-9]+) ]] && tx="${BASH_REMATCH[1]}"
[[ "$line" =~ bytes_received:([0-9]+) ]] && rx="${BASH_REMATCH[1]}"
fi
done < <(ss -tupni 2>/dev/null)
[[ -n "$pid" ]] && {
_CTX[$pid]=$(( ${_CTX[$pid]:-0} + tx ))
_CRX[$pid]=$(( ${_CRX[$pid]:-0} + rx ))
_CN[$pid]="$name"
}
}
fmt_rate() {
awk -v b="$1" 'BEGIN {
if (b >= 1073741824) printf "%.1f GB/s", b / 1073741824
else if (b >= 1048576) printf "%.1f MB/s", b / 1048576
else if (b >= 1024) printf "%.1f KB/s", b / 1024
else printf "%d B/s", b
}'
}
fmt_bytes() {
awk -v b="$1" 'BEGIN {
if (b >= 1073741824) printf "%.1f GB", b / 1073741824
else if (b >= 1048576) printf "%.1f MB", b / 1048576
else if (b >= 1024) printf "%.1f KB", b / 1024
else printf "%d B", b
}'
}
copy_curr_to_prev() {
_PTX=(); _PRX=(); _PN=()
for p in "${!_CTX[@]}"; do
_PTX[$p]=${_CTX[$p]}
_PRX[$p]=${_CRX[$p]}
_PN[$p]=${_CN[$p]}
done
}
total_net_dev() {
awk 'NR>2 && $1 !~ /lo:/ {
gsub(/:/, "", $1)
rx += $2; tx += $10
} END { print rx, tx }' /proc/net/dev
}
trap 'tput cnorm 2>/dev/null; echo; exit 0' INT TERM
tput civis 2>/dev/null
parse_ss
copy_curr_to_prev
read prev_total_rx prev_total_tx < <(total_net_dev)
while true; do
sleep "$INTERVAL"
parse_ss
read curr_total_rx curr_total_tx < <(total_net_dev)
clear
printf "\n Network Monitor — %s (interval: %ds)\n\n" "$(date '+%H:%M:%S')" "$INTERVAL"
printf " %-7s %-20s %12s %12s\n" "PID" "PROCESS" "UPLOAD" "DOWNLOAD"
printf " %55s\n" "" | tr ' ' ─
tmpf=$(mktemp)
for p in "${!_CTX[@]}"; do
dtx=$(( ${_CTX[$p]:-0} - ${_PTX[$p]:-0} ))
drx=$(( ${_CRX[$p]:-0} - ${_PRX[$p]:-0} ))
(( dtx < 0 )) && dtx=0
(( drx < 0 )) && drx=0
rtx=$(( dtx / INTERVAL ))
rrx=$(( drx / INTERVAL ))
echo "$((dtx + drx)) $p $rtx $rrx" >> "$tmpf"
done
has_output=false
while IFS=' ' read -r _ p rtx rrx; do
[[ -n "$p" ]] || continue
(( rtx > 0 || rrx > 0 )) || continue
has_output=true
nm="${_CN[$p]:-${_PN[$p]:-unknown}}"
printf " %-7s %-20s %12s %12s\n" \
"$p" "${nm:0:20}" "$(fmt_rate "$rtx")" "$(fmt_rate "$rrx")"
done < <(sort -rn "$tmpf")
rm -f "$tmpf"
[[ "$has_output" == "false" ]] && printf " (no active TCP/UDP traffic)\n"
printf "\n %55s\n" "" | tr ' ' ─
total_sys_tx=$(( curr_total_tx - prev_total_tx ))
total_sys_rx=$(( curr_total_rx - prev_total_rx ))
printf " %-28s %12s %12s\n" "SYSTEM TOTAL" \
"$(fmt_rate "$((total_sys_tx / INTERVAL))")" \
"$(fmt_rate "$((total_sys_rx / INTERVAL))")"
prev_total_tx=$curr_total_tx
prev_total_rx=$curr_total_rx
copy_curr_to_prev
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment