====== Docker rates.sh ====== {{tag>docker commands}} This prints out a table of instantaneous network bandwidth use. ===== Python version ===== #!/usr/bin/env python3 """ rates.py — instantaneous per-container network rates (B/s / Mbps) Python 3.11.9 — no external packages required. Usage examples: ./rates.py # default 1s interval, show all containers ./rates.py 2 --top 10 # 2s interval, show top 10 """ from __future__ import annotations import subprocess, time, sys, shutil, signal, os from datetime import datetime from typing import Dict, Tuple INTERVAL = float(sys.argv[1]) if len(sys.argv) > 1 and sys.argv[1].replace('.','',1).isdigit() else 1.0 TOP = None # simple argument parsing for --top N if "--top" in sys.argv: try: TOP = int(sys.argv[sys.argv.index("--top") + 1]) except Exception: TOP = None NAME_WIDTH = 30 def run(cmd: list[str]) -> str: res = subprocess.run(cmd, capture_output=True, text=True) return res.stdout.strip() def list_container_ids() -> list[str]: out = run(["docker", "ps", "-q"]) if not out: # fallback to podman if docker missing out = run(["podman", "ps", "-q"]) return [x for x in out.splitlines() if x.strip()] def inspect_container(id_: str) -> Tuple[str, int]: """Return (name, pid). name without leading slash.""" out = run(["docker", "inspect", "--format", "{{.Name}}\t{{.State.Pid}}", id_]) if not out: out = run(["podman", "inspect", "--format", "{{.Name}}\t{{.State.Pid}}", id_]) if not out: return (id_, 0) parts = out.split("\t") name = parts[0].lstrip("/") if parts else id_ pid = int(parts[1]) if len(parts) > 1 and parts[1].isdigit() else 0 return name, pid def read_net_dev_total(pid: int) -> Tuple[int,int]: """Return (rx_total, tx_total) bytes summed across interfaces for a PID's net namespace.""" path = f"/proc/{pid}/net/dev" try: with open(path, "r") as f: lines = f.readlines()[2:] except Exception: return 0, 0 rx = 0 tx = 0 for line in lines: cols = line.split() # linux /proc/net/dev fields: iface: rx_bytes ... tx_bytes ... # after split, rx_bytes is at index 1, tx_bytes at index 9 (0-based) if len(cols) >= 10: try: rx += int(cols[1]) tx += int(cols[9]) except ValueError: continue return rx, tx def human_bytes(v: int) -> str: if v >= (1<<30): return f"{v/(1<<30):.2f} GB" if v >= (1<<20): return f"{v/(1<<20):.2f} MB" if v >= (1<<10): return f"{v/(1<<10):.2f} KB" return f"{v} B" def mbps(v: int) -> float: return v / 125000.0 def truncate(s: str, w: int) -> str: return (s if len(s) <= w else s[:w-3] + "...") def clear_screen(): print("\x1b[2J\x1b[H", end="") def gather_snapshot() -> Dict[str, Tuple[str,int,int,int]]: """ return dict: id -> (name, pid, rx_total, tx_total) """ ids = list_container_ids() snap = {} for cid in ids: name, pid = inspect_container(cid) rx, tx = (0,0) if pid > 0 and os.path.exists(f"/proc/{pid}/net/dev"): rx, tx = read_net_dev_total(pid) snap[cid] = (name, pid, rx, tx) return snap def main(): prev = gather_snapshot() # honour terminal width for name column cols = shutil.get_terminal_size((120, 20)).columns global NAME_WIDTH NAME_WIDTH = min(NAME_WIDTH, max(12, cols - 70)) try: while True: time.sleep(INTERVAL) now = gather_snapshot() rows = [] ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S") for cid, (name, pid, rx_now, tx_now) in now.items(): _, _, rx_prev, tx_prev = prev.get(cid, ("",0,0,0)) # if container restarted, prev might be absent -> diffs are rx_now-0 which is fine but might be large; # handle negative or weird results drx = rx_now - rx_prev dtx = tx_now - tx_prev if drx < 0: drx = 0 if dtx < 0: dtx = 0 # convert to per-second (interval may not be exactly constant) rx_rate = int(drx / INTERVAL) tx_rate = int(dtx / INTERVAL) total = rx_rate + tx_rate rows.append({"name": name, "rx": rx_rate, "tx": tx_rate, "tot": total}) # sort descending by total rows.sort(key=lambda r: r["tot"], reverse=True) if TOP: rows = rows[:TOP] clear_screen() header = f"{ts} (interval {INTERVAL}s) containers: {len(rows)}" print(header) name_w = NAME_WIDTH hdr = f"{'NAME':{name_w}} {'RX':>12} {'TX':>12} {'TOT':>12} {'RX(Mbps)':>9} {'TX(Mbps)':>9} {'TOT(Mbps)':>9}" print(hdr) print("-" * len(hdr)) for r in rows: n = truncate(r["name"], name_w) rx_h = human_bytes(r["rx"]) tx_h = human_bytes(r["tx"]) tot_h = human_bytes(r["tot"]) rx_m = f"{mbps(r['rx']):6.2f}" tx_m = f"{mbps(r['tx']):6.2f}" tot_m = f"{mbps(r['tot']):6.2f}" print(f"{n:{name_w}} {rx_h:>12} {tx_h:>12} {tot_h:>12} {rx_m:>9} {tx_m:>9} {tot_m:>9}") sys.stdout.flush() prev = now except KeyboardInterrupt: print("\nexiting.") return if __name__ == "__main__": main() ===== Bash version ===== #!/usr/bin/env bash # Instantaneous per-container network rates (B/s). No extra packages. INTERVAL=1 TMPDIR=${TMPDIR:-/tmp} PREV="$TMPDIR/net.prev.$$.txt" NOW="$TMPDIR/net.now.$$.txt" OUT="$TMPDIR/net.out.$$.txt" cleanup(){ rm -f "$PREV" "$NOW" "$OUT"; exit 0; } trap cleanup INT TERM collect() { target="$1" : > "$target" docker ps -q | while read -r id; do [ -z "$id" ] && continue name=$(docker inspect --format '{{.Name}}' "$id" 2>/dev/null | sed 's#/##') pid=$(docker inspect --format '{{.State.Pid}}' "$id" 2>/dev/null) if [ -r "/proc/$pid/net/dev" ]; then read rx tx < <(awk 'NR>2{rx+=$2;tx+=$10}END{print rx,tx}' /proc/"$pid"/net/dev 2>/dev/null || echo "0 0") else rx=0; tx=0 fi # idnamepidrxtx printf '%s\t%s\t%s\t%s\t%s\n' "$id" "$name" "$pid" "$rx" "$tx" >> "$target" done } # seed previous snapshot collect "$PREV" sleep "$INTERVAL" while true; do ts=$(date +"%F %T") collect "$NOW" # load previous counters into associative arrays declare -A PRX PTX PNAME while IFS=$'\t' read -r id name pid rx tx; do PRX["$id"]=$rx PTX["$id"]=$tx PNAME["$id"]=$name done < "$PREV" : > "$OUT" while IFS=$'\t' read -r id name pid arx atx; do brx=${PRX[$id]:-0} btx=${PTX[$id]:-0} rx_rate=$((arx - brx)) tx_rate=$((atx - btx)) [ "$rx_rate" -lt 0 ] && rx_rate=0 [ "$tx_rate" -lt 0 ] && tx_rate=0 total=$((rx_rate + tx_rate)) # totalnamerx_ratetx_ratetotal printf '%d\t%s\t%d\t%d\t%d\n' "$total" "$name" "$rx_rate" "$tx_rate" "$total" >> "$OUT" done < "$NOW" echo "==== $ts (rates over ${INTERVAL}s) ====" if [ -s "$OUT" ]; then sort -t $'\t' -nr -k1,1 "$OUT" | awk -F'\t' '{printf "%-28s RX:%8d B/s TX:%8d B/s TOT:%8d B/s\n", $2, $3, $4, $5}' else echo "No containers found." fi mv "$NOW" "$PREV" sleep "$INTERVAL" done