From 5155bc36cd92d3e6fc87f9233f209daae0a8c080 Mon Sep 17 00:00:00 2001 From: iRedTea Date: Tue, 13 Oct 2026 23:12:30 +0300 Subject: [PATCH] feat: measure each direction of the speed test for 7 s (0.0.7) --- VERSION.txt | 2 +- internal/agent/speed.go | 28 ++++++++++++++--------- internal/proto/proto.go | 3 +++ internal/server/handlers_panel_stat.go | 16 +++++++------ internal/server/web/templates/status.html | 4 ++-- 5 files changed, 32 insertions(+), 21 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index 1750564..5a5831a 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -0.0.6 +0.0.7 diff --git a/internal/agent/speed.go b/internal/agent/speed.go index bd4df53..ccb6409 100644 --- a/internal/agent/speed.go +++ b/internal/agent/speed.go @@ -13,24 +13,30 @@ import ( ) const ( - // speedWindow is how long each direction is measured for. - speedWindow = 3 * time.Second + // speedWindow is how long each direction is measured for, unless the + // server asks for another window; speedWindowMax bounds what it may ask. + speedWindow = 3 * time.Second + speedWindowMax = 30 * time.Second // speedWait is how long the download may take to start: in a relayed test // its bytes come from the browser. speedWait = 10 * time.Second - // speedUpCap bounds the upload, whatever the link does in speedWindow. + // speedUpCap bounds the upload, whatever the link does in the window. speedUpCap = 48 << 20 ) // doSpeed measures the agent's HTTP link to the server: round-trip time of an -// empty request, then how much flows each way in speedWindow. In a relayed +// empty request, then how much flows each way in the window. In a relayed // test the server pipes both streams to the browser, so they run end to end. func (a *Agent) doSpeed(ctx context.Context, env proto.TaskEnvelope) proto.TaskResult { var args proto.SysSpeedArgs if len(env.Args) > 0 { _ = json.Unmarshal(env.Args, &args) } - res, err := a.measureSpeed(ctx, args.Relay) + window := speedWindow + if args.WindowMs > 0 { + window = min(time.Duration(args.WindowMs)*time.Millisecond, speedWindowMax) + } + res, err := a.measureSpeed(ctx, args.Relay, window) if err != nil { return proto.TaskResult{Error: err.Error()} } @@ -38,7 +44,7 @@ func (a *Agent) doSpeed(ctx context.Context, env proto.TaskEnvelope) proto.TaskR return proto.TaskResult{OK: true, Data: data} } -func (a *Agent) measureSpeed(ctx context.Context, relay bool) (proto.SysSpeedResult, error) { +func (a *Agent) measureSpeed(ctx context.Context, relay bool, window time.Duration) (proto.SysSpeedResult, error) { res := proto.SysSpeedResult{Relay: relay} streams := "/api/agent/speed" if relay { @@ -59,14 +65,14 @@ func (a *Agent) measureSpeed(ctx context.Context, relay bool) (proto.SysSpeedRes } } - down, err := a.speedDown(ctx, streams+"/down") + down, err := a.speedDown(ctx, streams+"/down", window) if err != nil { return res, err } res.DownBps = down // Up: send until the window closes or the cap is hit. - body := &speedSource{deadline: time.Now().Add(speedWindow), left: speedUpCap} + body := &speedSource{deadline: time.Now().Add(window), left: speedUpCap} start := time.Now() resp, err := a.speedRequest(ctx, http.MethodPost, streams+"/up", body) if err != nil { @@ -78,9 +84,9 @@ func (a *Agent) measureSpeed(ctx context.Context, relay bool) (proto.SysSpeedRes return res, nil } -// speedDown reads what the server streams for speedWindow, counted from the +// speedDown reads what the server streams for the window, counted from the // first byte, and returns the rate. -func (a *Agent) speedDown(ctx context.Context, path string) (float64, error) { +func (a *Agent) speedDown(ctx context.Context, path string, window time.Duration) (float64, error) { ctx, cancel := context.WithCancel(ctx) defer cancel() t := time.AfterFunc(speedWait, cancel) @@ -99,7 +105,7 @@ func (a *Agent) speedDown(ctx context.Context, path string) (float64, error) { k, err := resp.Body.Read(buf) if k > 0 && start.IsZero() { start = time.Now() - t.Reset(speedWindow) + t.Reset(window) } n += int64(k) if err != nil { diff --git a/internal/proto/proto.go b/internal/proto/proto.go index 30080b0..ca004f9 100644 --- a/internal/proto/proto.go +++ b/internal/proto/proto.go @@ -184,6 +184,9 @@ type SysSpeedArgs struct { // Relay makes the agent use the relayed streams, which the server pipes // to and from the browser. Relay bool `json:"relay,omitempty"` + // WindowMs is how long to measure each direction for; 0 leaves it to the + // agent (3 s, all an agent older than 0.0.7 does). + WindowMs int `json:"window_ms,omitempty"` } // SysSpeedResult is returned for TaskSysSpeed: the agent's HTTP link to the diff --git a/internal/server/handlers_panel_stat.go b/internal/server/handlers_panel_stat.go index f51b9de..4c3df3a 100644 --- a/internal/server/handlers_panel_stat.go +++ b/internal/server/handlers_panel_stat.go @@ -20,17 +20,20 @@ const ( // statCacheFor is how long one sample answers every open status page, so // several tabs cost the agent a single reading. statCacheFor = 2 * time.Second + // speedWindow is how long the agent is asked to measure each direction + // for. An agent older than 0.0.7 keeps to its own 3 s. + speedWindow = 7 * time.Second // speedTaskTimeout is how long a speed test may take. One that is not done // by then is answered with what the server has counted itself. - speedTaskTimeout = 15 * time.Second + speedTaskTimeout = 35 * time.Second // speedDownCap bounds one download stream; speedDownFor ends it if the // agent does not hang up first. - speedDownCap = 256 << 20 - speedDownFor = 6 * time.Second + speedDownCap = 1 << 30 + speedDownFor = 2 * speedWindow speedUpCap = 64 << 20 // speedRelayUpFor is how long the agent's upload is passed on to the // browser: the agent's own window with slack. - speedRelayUpFor = 4 * time.Second + speedRelayUpFor = speedWindow + time.Second ) // statCache holds the last sample per agent and serializes its refresh. @@ -71,7 +74,7 @@ func (s *Server) handleStatusPage(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) return } - s.render(w, "status.html", map[string]any{"Agent": a}) + s.render(w, "status.html", map[string]any{"Agent": a, "RelayUpMs": speedRelayUpFor.Milliseconds()}) } // runTask runs a one-shot task on the agent and waits for its result. @@ -144,7 +147,6 @@ func (s *Server) handleAgentSpeed(w http.ResponseWriter, r *http.Request) { relay := r.URL.Query().Get("relay") != "" m := s.meters.start(id) defer s.meters.stop(id, m) - var args []byte if relay { rl := s.relays.get(id) if rl == nil { @@ -152,8 +154,8 @@ func (s *Server) handleAgentSpeed(w http.ResponseWriter, r *http.Request) { return } defer s.relays.drop(id, rl) - args, _ = json.Marshal(proto.SysSpeedArgs{Relay: true}) } + args, _ := json.Marshal(proto.SysSpeedArgs{Relay: relay, WindowMs: int(speedWindow.Milliseconds())}) res, err := s.runTask(id, proto.TaskSysSpeed, args, speedTaskTimeout) if err != nil { part, ok := m.result() diff --git a/internal/server/web/templates/status.html b/internal/server/web/templates/status.html index 6d90b4f..db0ad05 100644 --- a/internal/server/web/templates/status.html +++ b/internal/server/web/templates/status.html @@ -77,7 +77,7 @@

Скорость соединения

Клиент замеряет свой канал до хоста панели - тот же, по которому идут терминал, файлы и http-проброс. С галочкой «До браузера» данные идут дальше, через панель до этого браузера и обратно: так виден весь путь клиент - панель - браузер. - Замер занимает около 7 секунд, не дольше 20, и на это время нагружает канал.

+ Замер занимает около 15 секунд, не дольше 40, и на это время нагружает канал.