#!/usr/bin/env bash # # traffic-peak-monitor.sh # ------------------------ # Ermittelt fuer ein Netzwerk-Interface die stuendlichen Spitzenwerte # (Peaks) der ein- und ausgehenden Datenrate und schreibt sie stuendlich # in eine Log-Datei. # # AUFRUF-MODI: # # 1) OHNE Parameter -> "Launcher-Modus" # ./traffic-peak-monitor.sh # Durchsucht das Verzeichnis "conf/" nach allen passenden # Konfigurationsdateien (traffic-peak-monitor-*.conf), z.B.: # conf/traffic-peak-monitor-lan4.conf # conf/traffic-peak-monitor-lan4.11.conf # und startet fuer JEDE gefundene Konfiguration einen eigenen # Hintergrundprozess (das Script ruft sich dabei selbst im # Worker-Modus auf, einmal pro Interface). # # 2) MIT Interface-Namen als Parameter -> "Worker-Modus" # ./traffic-peak-monitor.sh lan4 # Ueberwacht genau dieses eine Interface dauerhaft. Wird normal- # erweise nicht manuell aufgerufen, sondern automatisch vom # Launcher-Modus gestartet. # # KONFIGURATION: # conf/-.conf # Erwartete Variablen darin (alle optional, siehe Defaults unten): # INTERVAL - Messintervall in Sekunden # LINE_SPEED_IN_MBIT - Download-Kapazitaet der Leitung in Mbit/s # LINE_SPEED_OUT_MBIT - Upload-Kapazitaet der Leitung in Mbit/s # # LOGS: # Liegen unter /var/log/traffic-peak-monitor/ (wird beim Start # automatisch angelegt, falls nicht vorhanden). Pro Interface gibt es # eine AKTIVE Log-Datei ohne Datumsendung, z.B.: # /var/log/traffic-peak-monitor/traffic-peak-monitor-lan4.log # Sobald ein neuer Kalendertag beginnt, wird diese Datei mit dem Datum # des Tages, zu dem ihre Eintraege gehoeren, umbenannt (= "rotiert"), # z.B. in: # /var/log/traffic-peak-monitor/traffic-peak-monitor-lan4.log.2026-08-03 # Danach wird wieder in eine frische, aktive Log-Datei geschrieben. # # BERECHTIGUNGEN: # Das Script muss als root (UID 0) laufen. Ein "sudo" ist dafuer nicht # noetig, wenn es bereits als root gestartet wird (z.B. per systemd # oder root-Crontab). set -u # --- Grundlegende Pfade ermitteln -------------------------------------- # realpath loest auch Symlinks auf, damit das Script von ueberall aus # aufgerufen werden kann (z.B. per systemd mit vollem Pfad) und trotzdem # sein eigenes Verzeichnis korrekt findet. script_name="$(basename "$(realpath "$0")")" working_dir="$(dirname "$(realpath "$0")")" conf_dir="${working_dir}/conf" # Feste Log-Basis unter /var/log - unabhaengig vom Script-Verzeichnis, # damit Logs an der ueblichen Stelle fuer Systemd-Dienste liegen und # z.B. von logrotate/Backup-Tools leichter gefunden werden. log_dir="/var/log/traffic-peak-monitor" # Basisname ohne Endung, z.B. "traffic-peak-monitor" aus # "traffic-peak-monitor.sh" - wird fuer Config- und Log-Dateinamen # gebraucht. base_name="${script_name%%.*}" # Log-Verzeichnis anlegen, falls es noch nicht existiert (inkl. evtl. # fehlender Elternverzeichnisse, "-p" macht das automatisch und meldet # auch keinen Fehler, falls es schon existiert) mkdir -p "$log_dir" # --- Sicherheitscheck: muss als root laufen ---------------------------- if [ "$(id -u)" -ne 0 ]; then echo "Fehler: Dieses Script muss als root (UID 0) laufen." >&2 exit 1 fi # ======================================================================== # WORKER: ueberwacht genau ein Interface dauerhaft # ======================================================================== run_worker() { local IFACE=$1 local conf_file="${conf_dir}/${base_name}-${IFACE}.conf" local log_file="${log_dir}/${base_name}-${IFACE}.log" # Default-Werte, falls in der Konfigurationsdatei nicht gesetzt local INTERVAL=2 local LINE_SPEED_IN_MBIT=100 local LINE_SPEED_OUT_MBIT=100 # Konfigurationsdatei einlesen - ueberschreibt die obigen Defaults # mit den Werten aus der .conf Datei (einfaches "source", da die # Config-Datei nur Variablenzuweisungen wie in einem Bash-Script # enthaelt, z.B. LINE_SPEED_IN_MBIT=250) if [ -f "$conf_file" ]; then # shellcheck disable=SC1090 source "$conf_file" else echo "Warnung: Konfigurationsdatei $conf_file nicht gefunden, nutze Default-Werte." >&2 fi # Leitungskapazitaet von Mbit/s in kbit/s umrechnen, damit die Einheiten # beim spaeteren Prozent-Vergleich zusammenpassen local LINE_SPEED_IN_KBIT=$(( LINE_SPEED_IN_MBIT * 1000 )) local LINE_SPEED_OUT_KBIT=$(( LINE_SPEED_OUT_MBIT * 1000 )) # Erste Messung als Ausgangsbasis local PREV_RX PREV_TX MAX_RX MAX_TX CUR_HOUR HOUR_START HOUR_END CUR_DAY PREV_RX=$(cat "/sys/class/net/${IFACE}/statistics/rx_bytes") PREV_TX=$(cat "/sys/class/net/${IFACE}/statistics/tx_bytes") MAX_RX=0 MAX_TX=0 CUR_HOUR=$(date +%H) HOUR_START=$(date '+%Y-%m-%d %H:00') HOUR_END=$(date -d '+1 hour' '+%H:00') # CUR_DAY merkt sich, zu welchem Kalendertag die aktuell gesammelten # Daten (und damit die aktive, noch nicht rotierte Log-Datei) gehoeren. CUR_DAY=$(date '+%Y-%m-%d') # Haupt-Schleife: laeuft dauerhaft, bis der Prozess beendet wird while true; do sleep "$INTERVAL" local RX TX RX_RATE TX_RATE NOW_HOUR RX=$(cat "/sys/class/net/${IFACE}/statistics/rx_bytes") TX=$(cat "/sys/class/net/${IFACE}/statistics/tx_bytes") # Differenz * 8 = Byte -> Bit, / INTERVAL = Bit pro Sekunde RX_RATE=$(( (RX - PREV_RX) * 8 / INTERVAL )) TX_RATE=$(( (TX - PREV_TX) * 8 / INTERVAL )) [ "$RX_RATE" -gt "$MAX_RX" ] && MAX_RX=$RX_RATE [ "$TX_RATE" -gt "$MAX_TX" ] && MAX_TX=$TX_RATE NOW_HOUR=$(date +%H) if [ "$NOW_HOUR" != "$CUR_HOUR" ]; then local MAX_RX_KBIT MAX_TX_KBIT RX_PCT TX_PCT MAX_RX_KBIT=$(( MAX_RX / 1000 )) MAX_TX_KBIT=$(( MAX_TX / 1000 )) RX_PCT=$(awk -v v="$MAX_RX_KBIT" -v c="$LINE_SPEED_IN_KBIT" 'BEGIN{printf "%.1f", (v/c)*100}') TX_PCT=$(awk -v v="$MAX_TX_KBIT" -v c="$LINE_SPEED_OUT_KBIT" 'BEGIN{printf "%.1f", (v/c)*100}') # Eintrag fuer die gerade abgeschlossene Stunde in die AKTIVE # Log-Datei schreiben. Wichtig: das muss VOR einer moeglichen # Tages-Rotation passieren, da dieser Eintrag noch zu CUR_DAY # gehoert (z.B. der Eintrag "23:00-00:00" gehoert noch zum alten Tag). echo "${HOUR_START}-${HOUR_END} peak_rx=${MAX_RX_KBIT}kbit/s (${RX_PCT}%) peak_tx=${MAX_TX_KBIT}kbit/s (${TX_PCT}%)" >> "$log_file" MAX_RX=0 MAX_TX=0 CUR_HOUR=$NOW_HOUR HOUR_START=$(date '+%Y-%m-%d %H:00') HOUR_END=$(date -d '+1 hour' '+%H:00') # --- Tages-Rotation pruefen --------------------------------------- # Hat sich der Kalendertag geaendert (z.B. Uebergang 23:xx -> 00:xx)? # Falls ja: die bisherige aktive Log-Datei bekommt als Endung das # Datum des Tages, dessen Eintraege sie enthaelt (CUR_DAY, also der # ALTE Tag - nicht der gerade begonnene). Anschliessend beginnt eine # neue, leere aktive Log-Datei fuer den neuen Tag. local NEW_DAY NEW_DAY=$(date '+%Y-%m-%d') if [ "$NEW_DAY" != "$CUR_DAY" ]; then if [ -f "$log_file" ]; then mv "$log_file" "${log_file}.${CUR_DAY}" fi CUR_DAY=$NEW_DAY fi fi PREV_RX=$RX PREV_TX=$TX done } # ======================================================================== # LAUNCHER: kein Parameter angegeben -> alle Konfigurationen suchen # und je einen Worker-Prozess starten # ======================================================================== if [ $# -eq 0 ]; then # nullglob sorgt dafuer, dass das Array leer bleibt, wenn keine # passenden Dateien existieren (statt des woertlichen Musters) shopt -s nullglob conf_files=("${conf_dir}/${base_name}"-*.conf) shopt -u nullglob if [ ${#conf_files[@]} -eq 0 ]; then echo "Keine Konfigurationsdateien in ${conf_dir} gefunden (erwartet: ${base_name}-.conf)" >&2 exit 1 fi pids=() for cf in "${conf_files[@]}"; do # Interface-Namen aus dem Dateinamen herausschneiden: # aus "traffic-peak-monitor-lan4.11.conf" wird "lan4.11" cf_base=$(basename "$cf" .conf) iface="${cf_base#${base_name}-}" echo "Starte Monitoring fuer Interface '${iface}' (Konfiguration: ${cf})" # Script ruft sich selbst im Worker-Modus auf, im Hintergrund. # stdout/stderr des Worker-Prozesses gehen in eine eigene .out Datei, # damit unerwartete Fehler (z.B. falscher Interface-Name) auffindbar sind. "$0" "$iface" >> "${log_dir}/${base_name}-${iface}.out" 2>&1 & pids+=("$!") done echo "Gestartete Worker-Prozesse (PIDs): ${pids[*]}" # "wait" haelt den Launcher-Prozess am Leben, solange mindestens ein # Worker laeuft. Das ist wichtig, falls der Launcher selbst z.B. per # systemd ueberwacht werden soll (siehe systemd-Unit) - ohne "wait" # wuerde der Launcher sofort beendet sein, obwohl die Worker im # Hintergrund weiterlaufen. wait exit 0 fi # ======================================================================== # WORKER-MODUS: Interface als Parameter uebergeben # ======================================================================== run_worker "$1"