Differences
This shows you the differences between two versions of the page.
| docker_rates.sh [2025/11/02 15:04] – created 172.18.0.1 | docker_rates.sh [2025/11/02 15:27] (current) – admin | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| ====== Docker rates.sh ====== | ====== Docker rates.sh ====== | ||
| {{tag> | {{tag> | ||
| - | |||
| This prints out a table of instantaneous network bandwidth use. | This prints out a table of instantaneous network bandwidth use. | ||
| + | ===== Python version ===== | ||
| + | < | ||
| + | # | ||
| + | """ | ||
| + | rates.py — instantaneous per-container network rates (B/s / Mbps) | ||
| + | Python 3.11.9 — no external packages required. | ||
| + | |||
| + | Usage examples: | ||
| + | ./ | ||
| + | ./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(' | ||
| + | TOP = None | ||
| + | # simple argument parsing for --top N | ||
| + | if " | ||
| + | try: | ||
| + | TOP = int(sys.argv[sys.argv.index(" | ||
| + | except Exception: | ||
| + | TOP = None | ||
| + | |||
| + | NAME_WIDTH = 30 | ||
| + | |||
| + | def run(cmd: list[str]) -> str: | ||
| + | res = subprocess.run(cmd, | ||
| + | return res.stdout.strip() | ||
| + | |||
| + | def list_container_ids() -> list[str]: | ||
| + | out = run([" | ||
| + | if not out: | ||
| + | # fallback to podman if docker missing | ||
| + | out = run([" | ||
| + | return [x for x in out.splitlines() if x.strip()] | ||
| + | |||
| + | def inspect_container(id_: | ||
| + | """ | ||
| + | out = run([" | ||
| + | if not out: | ||
| + | out = run([" | ||
| + | if not out: | ||
| + | return (id_, 0) | ||
| + | parts = out.split(" | ||
| + | name = parts[0].lstrip("/" | ||
| + | pid = int(parts[1]) if len(parts) > 1 and parts[1].isdigit() else 0 | ||
| + | return name, pid | ||
| + | |||
| + | def read_net_dev_total(pid: | ||
| + | """ | ||
| + | path = f"/ | ||
| + | try: | ||
| + | with open(path, " | ||
| + | lines = f.readlines()[2: | ||
| + | except Exception: | ||
| + | return 0, 0 | ||
| + | rx = 0 | ||
| + | tx = 0 | ||
| + | for line in lines: | ||
| + | cols = line.split() | ||
| + | # linux / | ||
| + | # 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: | ||
| + | if v >= (1<< | ||
| + | return f" | ||
| + | if v >= (1<< | ||
| + | return f" | ||
| + | if v >= (1<< | ||
| + | return f" | ||
| + | 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(" | ||
| + | |||
| + | def gather_snapshot() -> Dict[str, Tuple[str, | ||
| + | """ | ||
| + | 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"/ | ||
| + | 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, | ||
| + | global NAME_WIDTH | ||
| + | NAME_WIDTH = min(NAME_WIDTH, | ||
| + | try: | ||
| + | while True: | ||
| + | time.sleep(INTERVAL) | ||
| + | now = gather_snapshot() | ||
| + | rows = [] | ||
| + | ts = datetime.now().strftime(" | ||
| + | for cid, (name, pid, rx_now, tx_now) in now.items(): | ||
| + | _, _, rx_prev, tx_prev = prev.get(cid, | ||
| + | # 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({" | ||
| + | # sort descending by total | ||
| + | rows.sort(key=lambda r: r[" | ||
| + | if TOP: | ||
| + | rows = rows[:TOP] | ||
| + | clear_screen() | ||
| + | header = f" | ||
| + | print(header) | ||
| + | name_w = NAME_WIDTH | ||
| + | hdr = f" | ||
| + | print(hdr) | ||
| + | print(" | ||
| + | for r in rows: | ||
| + | n = truncate(r[" | ||
| + | rx_h = human_bytes(r[" | ||
| + | tx_h = human_bytes(r[" | ||
| + | tot_h = human_bytes(r[" | ||
| + | rx_m = f" | ||
| + | tx_m = f" | ||
| + | tot_m = f" | ||
| + | print(f" | ||
| + | sys.stdout.flush() | ||
| + | prev = now | ||
| + | except KeyboardInterrupt: | ||
| + | print(" | ||
| + | return | ||
| + | |||
| + | if __name__ == " | ||
| + | main() | ||
| + | </ | ||
| + | |||
| + | ===== Bash version ===== | ||
| + | |||
| < | < | ||
| # | # | ||