v0.342.0 (unreleased): the upload watch reads the host's weekly line test (GET /host/line-test, agent >= 0.164.0); qBittorrent's limit = 50 % of the TESTED line only (D41), the watch uses the higher of test and push floor, the system page names the source; red-proved
gates / gates (push) Successful in 1m15s
gates / gates (push) Successful in 1m15s
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/logx"
|
||||
@@ -39,12 +40,61 @@ type Service struct {
|
||||
QBRunning func() bool
|
||||
// QBShare is the share of the measured line qBittorrent may upload at (operator decide row; CC's pick 0.5).
|
||||
QBShare float64
|
||||
Log *log.Logger
|
||||
// Measured reads the host's weekly line test (the agent's GET /host/line-test, R-1247): a 10-second iperf3 upload
|
||||
// to ep0 inside the tunnel. nil or ok=false = not measured. Read every 15 minutes (RefreshMeasured), never per
|
||||
// minute.
|
||||
Measured func(ctx context.Context) (mbps float64, at time.Time, ok bool)
|
||||
Log *log.Logger
|
||||
|
||||
mu sync.Mutex
|
||||
measured float64
|
||||
measuredAt time.Time
|
||||
measuredOK bool
|
||||
}
|
||||
|
||||
// Line is the measured upload line in Mbit/s (a lower bound); ok=false while no push counted.
|
||||
// RefreshMeasured reads the agent's line test once. A failed read keeps the last value (the agent persists it; an
|
||||
// agent restart must not drop qBittorrent's limit to „unknown").
|
||||
func (s *Service) RefreshMeasured(ctx context.Context) {
|
||||
if s.Measured == nil {
|
||||
return
|
||||
}
|
||||
mbps, at, ok := s.Measured(ctx)
|
||||
if !ok || mbps <= 0 {
|
||||
return
|
||||
}
|
||||
s.mu.Lock()
|
||||
changed := !s.measuredOK || s.measured != mbps
|
||||
s.measured, s.measuredAt, s.measuredOK = mbps, at, true
|
||||
s.mu.Unlock()
|
||||
if changed {
|
||||
logx.Infof(s.Log, "[upwatch] the host's weekly line test: %.1f Mbit/s (measured %s)", mbps, at.UTC().Format(time.RFC3339))
|
||||
}
|
||||
}
|
||||
|
||||
// MeasuredLine is the host's tested upload line (ok=false: never tested).
|
||||
func (s *Service) MeasuredLine() (float64, bool) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.measured, s.measuredOK
|
||||
}
|
||||
|
||||
// Line is the upload line the watch judges against, in Mbit/s: the higher of the weekly test and the off-site push's
|
||||
// floor (both are lower bounds of the real line; the test is the close one). ok=false while neither exists.
|
||||
func (s *Service) Line() (float64, bool) {
|
||||
return LineFromRecords(decode(s.Store.GetUpwatch()).Line)
|
||||
l, _, ok := s.line()
|
||||
return l, ok
|
||||
}
|
||||
|
||||
func (s *Service) line() (mbps float64, tested, ok bool) {
|
||||
passive, pok := LineFromRecords(decode(s.Store.GetUpwatch()).Line)
|
||||
m, mok := s.MeasuredLine()
|
||||
switch {
|
||||
case mok && m >= passive:
|
||||
return m, true, true
|
||||
case pok:
|
||||
return passive, false, true
|
||||
}
|
||||
return 0, false, false
|
||||
}
|
||||
|
||||
// RecordPush stores one off-site push's measurement.
|
||||
@@ -82,7 +132,10 @@ func (s *Service) QBLimitNow(ctx context.Context) {
|
||||
if s.QB == nil || (s.QBRunning != nil && !s.QBRunning()) {
|
||||
return
|
||||
}
|
||||
line, known := s.Line()
|
||||
// D41 (operator 2026-10-24): 50 % of the TESTED line. The push floor is not used here — on demo-hp it read 10 Mbit/s
|
||||
// on a 90 Mbit/s line, and half of that would have held a seeding qBittorrent at 5. No test yet → no limit.
|
||||
s.RefreshMeasured(ctx)
|
||||
line, known := s.MeasuredLine()
|
||||
cur := decode(s.Store.GetUpwatch())
|
||||
var st QBLimitState
|
||||
if cur.QBLimit != nil {
|
||||
@@ -105,7 +158,7 @@ func (s *Service) QBLimitNow(ctx context.Context) {
|
||||
}
|
||||
switch act {
|
||||
case "set":
|
||||
logx.Infof(s.Log, "[upwatch] qBittorrent upload limit set to %d B/s (%.0f %% of the measured line, %.1f Mbit/s)", ns.BoxSetBytes, s.QBShare*100, line)
|
||||
logx.Infof(s.Log, "[upwatch] qBittorrent upload limit set to %d B/s (%.0f %% of the tested line, %.1f Mbit/s)", ns.BoxSetBytes, s.QBShare*100, line)
|
||||
case "household":
|
||||
if !st.Household {
|
||||
logx.Infof(s.Log, "[upwatch] qBittorrent's upload limit is the household's — the box leaves it alone from now on")
|
||||
@@ -117,6 +170,7 @@ func (s *Service) QBLimitNow(ctx context.Context) {
|
||||
type View struct {
|
||||
LineMbps float64
|
||||
LineKnown bool
|
||||
LineTested bool // LineMbps is the weekly test's (else the off-site push's floor, „at least")
|
||||
QBLimitBps int64 // the limit the box set and still owns; 0 = none shown
|
||||
}
|
||||
|
||||
@@ -124,7 +178,7 @@ type View struct {
|
||||
func (s *Service) View() View {
|
||||
st := decode(s.Store.GetUpwatch())
|
||||
v := View{}
|
||||
v.LineMbps, v.LineKnown = LineFromRecords(st.Line)
|
||||
v.LineMbps, v.LineTested, v.LineKnown = s.line()
|
||||
if st.QBLimit != nil && !st.QBLimit.Household {
|
||||
v.QBLimitBps = st.QBLimit.BoxSetBytes
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user