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:
@@ -106,6 +106,8 @@ type Manager struct {
|
||||
// offbox (Part B): the restic-SFTP exec seam (nil → real restic) + the failure→operator-alert hook.
|
||||
offboxRunner offboxRunner
|
||||
offboxNotify func(dur time.Duration, snapshots int, err error)
|
||||
// offboxPushObserver (the upload watch) measures the push; nil = none.
|
||||
offboxPushObserver func() func()
|
||||
// offboxStreamRunner + offboxProgress: the MANUAL run's live progress (v0.147.0, 4c). The stream
|
||||
// seam scans restic's `--json` stdout line-by-line; the state is what the page polls. Both are
|
||||
// inert on the nightly path — the sink is installed only for the duration of a manual run.
|
||||
|
||||
@@ -52,6 +52,11 @@ func defaultOffboxRunner(ctx context.Context, env []string, args ...string) ([]b
|
||||
|
||||
// SetOffboxRunner overrides the restic exec (tests). SetOffboxNotify wires the failure→operator alert.
|
||||
func (m *Manager) SetOffboxRunner(r offboxRunner) { m.offboxRunner = r }
|
||||
|
||||
// SetOffboxPushObserver hands the off-site push a measurement: fn is called as the push starts and returns the
|
||||
// function that ends it (the upload watch's line sampler). Nil = none.
|
||||
func (m *Manager) SetOffboxPushObserver(fn func() func()) { m.offboxPushObserver = fn }
|
||||
|
||||
func (m *Manager) SetOffboxNotify(fn func(dur time.Duration, snapshots int, err error)) {
|
||||
m.offboxNotify = fn
|
||||
}
|
||||
@@ -1127,7 +1132,16 @@ func (m *Manager) runOffboxBackup(ctx context.Context, withProgress bool) error
|
||||
} else {
|
||||
m.logger.Printf("[INFO] [offbox] pre-push dump leg completed in %s — snapshot pair is coherent", time.Since(dumpStart).Round(time.Millisecond))
|
||||
}
|
||||
// The upload watch learns the household's upload line from this push (internal/upwatch/line.go): a
|
||||
// one-second sample of this container's own sent bytes, from here to the end of the push only.
|
||||
var endPush func()
|
||||
if m.offboxPushObserver != nil {
|
||||
endPush = m.offboxPushObserver()
|
||||
}
|
||||
runResult, runErr = m.runOffboxInternal(ctx, apps, base, env, t)
|
||||
if endPush != nil {
|
||||
endPush()
|
||||
}
|
||||
backedUp = runResult.backedUp
|
||||
missing = runResult.missing
|
||||
endStamp()
|
||||
|
||||
Reference in New Issue
Block a user