v0.342.0 (unreleased): the upload and CPU watch — an app that sends or burns far more than its own numbers explain is one operator-only alarm; the line learnt from the off-site push; qBittorrent's default upload limit (never over the household's)
gates / gates (push) Successful in 1m17s
gates / gates (push) Successful in 1m17s
internal/upwatch: exact per-container counters from the Docker Engine API, per-app judging (upload >= max(5 Mbit/s, 25% of the line) for 15 min; CPU >= max(150%, 50% x cores) for 30 min), explainers qBittorrent / Transmission / Plex, app_unexplained_upload / app_unexplained_cpu + _cleared (hub 0.169.0). The off-site push is sampled once a second for the line (a lower bound). qBittorrent limit = 50% of the line, the household's limit always wins. 20 red-proofs. Spike + design: felhom.eu audits/SPIKE-upload-watch-2026-10-23.md, architecture/08 section 9. Not released.
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
"time"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/upwatch"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/i18n"
|
||||
)
|
||||
@@ -882,6 +883,33 @@ func (n *Notifier) NotifyAppRepairedAfterCrash(d AppRepairDetails) {
|
||||
}
|
||||
}
|
||||
|
||||
// NotifyUnexplained sends the upload and CPU watch's events (internal/upwatch; felhom.eu `08` §9). OPERATOR-ONLY on the
|
||||
// hub (notify.operatorOnlyEvents there, registered in the same commit as the types): the household can do nothing
|
||||
// about a hacked app but wait for support (the R-388 rule). Edge-triggered by the watch: one event when the condition
|
||||
// is judged, one `_cleared` (info, recorded) when it stops.
|
||||
func (n *Notifier) NotifyUnexplained(eventType, _ string, d upwatch.Details) {
|
||||
// The severity is fixed per type (alarm = warning, clear = info) — never the caller's word, so the hub's vocabulary
|
||||
// contract (TestR329_EveryEmittedSeverityIsInTheHubVocabulary) can read every value from this function.
|
||||
switch eventType {
|
||||
case "app_unexplained_upload":
|
||||
if d.AppMbps >= 0 {
|
||||
n.pushEventMsg(eventType, "warning", "event.app_unexplained_upload", d, d.App, d.RateMbps, d.Minutes, d.AppMbps)
|
||||
} else {
|
||||
n.pushEventMsg(eventType, "warning", "event.app_unexplained_upload_noapp", d, d.App, d.RateMbps, d.Minutes)
|
||||
}
|
||||
case "app_unexplained_upload_cleared":
|
||||
n.pushEventMsg(eventType, "info", "event.app_unexplained_upload_cleared", d, d.App)
|
||||
case "app_unexplained_cpu":
|
||||
n.pushEventMsg(eventType, "warning", "event.app_unexplained_cpu", d, d.App, d.CPUPercent, d.Minutes)
|
||||
case "app_unexplained_cpu_cleared":
|
||||
n.pushEventMsg(eventType, "info", "event.app_unexplained_cpu_cleared", d, d.App)
|
||||
default:
|
||||
if n.logger != nil {
|
||||
n.logger.Printf("[ERROR] NotifyUnexplained: unknown event type %q — not sent", eventType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type OOMStormDetails struct {
|
||||
App string `json:"app"`
|
||||
StackName string `json:"stack_name"`
|
||||
|
||||
Reference in New Issue
Block a user