#!/bin/env python """Script that outputs network activity""" from pathlib import Path from json import dumps from sys import argv from time import sleep from typing import Literal, NoReturn def get_interfaces() -> list[Path]: return list(Path("/sys/class/net/").iterdir()) class TransferredBytes: def __init__(self, interface: Path, statistic: Literal["tx"] | Literal["rx"]) -> None: self._statistic: Path = interface / f"statistics/{statistic}_bytes" self._current: int = int(self._statistic.read_text()) self._previous: int = self._current def update(self) -> None: self._previous = self._current self._current = int(self._statistic.read_text()) def value(self, interval: float) -> float: return (self._current - self._previous) / interval class Status: def __init__(self, interface: Path) -> None: self._statistic: Path = interface / "operstate" def up(self) -> bool: return self._statistic.read_text().strip() == "up" def format_4_significant_digits(num: float) -> str: assert num >= 1.0, "Doesn't properly handle numbers below 1" if num < 999: return f"{num:04.3g}" if num < 1000: # Above doesn't nicely handle the special case in [999.5 1000) return "0999" return f"{num:.4g}" EBI_UNITS = ("B", "kiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "RiB", "QiB") SHORT_UNITS = ("B", "k", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q") def format_bytes(num: float) -> str: for prefix in SHORT_UNITS: if num < 1024: return f"{format_4_significant_digits(num)}{prefix}" num /= 1024 return "what the fuck" class Interface: def __init__(self, location: Path) -> None: self._location: Path = location self._tx: TransferredBytes = TransferredBytes(location, "tx") self._rx: TransferredBytes = TransferredBytes(location, "rx") self._status: Status = Status(location) @property def name(self) -> str: return self._location.name def update(self) -> None: self._tx.update() self._rx.update() def json(self, interval: float) -> dict[str, str | bool | float]: interval_tx = self._tx.value(interval) interval_rx = self._rx.value(interval) return { "tx": format_bytes(interval_tx), "rx": format_bytes(interval_rx), "combined": format_bytes(interval_tx + interval_rx), "combined_raw": interval_tx + interval_rx, "up": self._status.up(), } class Statistics: def __init__(self) -> None: self._interfaces: list[Interface] = [Interface(location) for location in get_interfaces()] def update(self) -> None: for interface in self._interfaces: interface.update() def json(self, interval: float) -> str: return dumps( { interface.name: interface.json(interval) for interface in self._interfaces }, separators=(',', ':'), ) def main(interval: float) -> NoReturn: stats = Statistics() sleep(0.5) stats.update() print(stats.json(interval)) while True: sleep(interval) stats.update() print(stats.json(interval), flush=True) if __name__ == "__main__": if len(argv) != 2: exit("Must provide 1 arg: Polling interval") main(float(argv[1]))