diff --git a/hosts/FredOS-Mediaserver.nix b/hosts/FredOS-Mediaserver.nix index 0dc518e..3b8f2dd 100644 --- a/hosts/FredOS-Mediaserver.nix +++ b/hosts/FredOS-Mediaserver.nix @@ -26,6 +26,35 @@ export PATH="${pkgs.nvd}/bin:${pkgs.coreutils}/bin:${pkgs.gnugrep}/bin:${pkgs.gnused}/bin:$PATH" exec ${pkgs.bash}/bin/bash ${../scripts/record-update.sh} "$@" '') + # Stats stream for the quickshell server monitor on the desktops: + # `ssh mediaserver qs-stats` = top's 2s batch stream, interleaved with + # one @STAT line per tick (hottest coretemp across both sockets, WAN + # byte rates from eno1). Everything rides one SSH connection. + (pkgs.writeShellScriptBin "qs-stats" '' + C=${pkgs.coreutils}/bin + export LC_ALL=C + ${pkgs.procps}/bin/top -b -d 2 -w 512 & + trap 'kill $! 2>/dev/null' EXIT + prev_rx="" + while :; do + t=0 + for h in /sys/class/hwmon/*; do + if [ "$($C/cat "$h/name" 2>/dev/null)" = coretemp ]; then + for f in "$h"/temp*_input; do + v=$($C/cat "$f" 2>/dev/null || echo 0) + [ "$v" -gt "$t" ] && t=$v + done + fi + done + read -r rx tx < <(${pkgs.gawk}/bin/awk '$1 == "eno1:" {print $2, $10}' /proc/net/dev) + if [ -n "$prev_rx" ]; then + echo "@STAT temp=$((t / 1000)) rxbps=$(( (rx - prev_rx) / 2 )) txbps=$(( (tx - prev_tx) / 2 ))" + fi + prev_rx=$rx + prev_tx=$tx + $C/sleep 2 + done + '') ]; # Basic networking diff --git a/settings/quickshell.nix b/settings/quickshell.nix index 80e1387..3b566a6 100644 --- a/settings/quickshell.nix +++ b/settings/quickshell.nix @@ -1748,11 +1748,31 @@ in property string uptime: "" property string load: "" property var procs: [] + property int tempC: -1 + property real rxBps: 0 + property real txBps: 0 + + function fmtRate(bps) { + const b = bps * 8; + if (b >= 1e9) return (b / 1e9).toFixed(2) + " Gb/s"; + if (b >= 1e6) return (b / 1e6).toFixed(1) + " Mb/s"; + if (b >= 1e3) return Math.round(b / 1e3) + " Kb/s"; + return Math.round(b) + " b/s"; + } property var _procsPending: [] property bool _collecting: false function _parseLine(line) { + if (line.indexOf("@STAT ") === 0) { + const t = line.match(/temp=(\d+)/); + if (t) tempC = parseInt(t[1]); + const rx = line.match(/rxbps=(-?\d+)/); + if (rx) rxBps = Math.max(0, parseInt(rx[1])); + const tx = line.match(/txbps=(-?\d+)/); + if (tx) txBps = Math.max(0, parseInt(tx[1])); + return; + } if (line.indexOf("top - ") === 0) { if (_collecting) procs = _procsPending; _collecting = false; @@ -1792,8 +1812,7 @@ in command: [Commands.ssh, "-o", "BatchMode=yes", "-o", "ConnectTimeout=3", "-o", "StrictHostKeyChecking=accept-new", "-o", "ServerAliveInterval=5", "-o", "ServerAliveCountMax=2", - "fred@10.0.0.1", - "env", "LC_ALL=C", "top", "-b", "-d", "2", "-w", "512"] + "fred@10.0.0.1", "qs-stats"] running: true stdout: SplitParser { onRead: data => srvWidget._parseLine(data) @@ -2867,6 +2886,46 @@ in } } + Row { + width: parent.width + spacing: 10 + SText { width: 42; text: "temp"; color: Theme.base04 } + SText { + text: srvWidget.tempC < 0 ? "--" : srvWidget.tempC + "°C" + font.weight: Font.Medium + color: srvWidget.tempC < 0 ? Theme.base03 + : srvWidget.tempC >= 85 ? Theme.base08 + : srvWidget.tempC >= 70 ? Theme.base0A : Theme.base05 + } + } + + Row { + width: parent.width + spacing: 10 + SText { width: 42; text: "wan"; color: Theme.base04 } + Row { + spacing: 14 + Row { + spacing: 4 + SIcon { text: "arrow_downward"; font.pixelSize: 14; color: Theme.base0B; anchors.verticalCenter: parent.verticalCenter } + SText { + text: srvWidget.fmtRate(srvWidget.rxBps) + font.weight: Font.Medium + anchors.verticalCenter: parent.verticalCenter + } + } + Row { + spacing: 4 + SIcon { text: "arrow_upward"; font.pixelSize: 14; color: Theme.base09; anchors.verticalCenter: parent.verticalCenter } + SText { + text: srvWidget.fmtRate(srvWidget.txBps) + font.weight: Font.Medium + anchors.verticalCenter: parent.verticalCenter + } + } + } + } + Row { width: parent.width spacing: 10