feat: measure each direction of the speed test for 7 s (0.0.7)

This commit is contained in:
redtea committed 2026-10-13 23:12:30 +03:00
1 parent ab8751d436
commit 5155bc36cd
5 files changed
+31 -20

No files matched your search

+1 -1
View File
@@ -1 +1 @@
0.0.6 0.0.7
+16 -10
View File
@@ -13,24 +13,30 @@ import (
) )
const ( const (
// speedWindow is how long each direction is measured for. // 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 speedWindow = 3 * time.Second
speedWindowMax = 30 * time.Second
// speedWait is how long the download may take to start: in a relayed test // speedWait is how long the download may take to start: in a relayed test
// its bytes come from the browser. // its bytes come from the browser.
speedWait = 10 * time.Second 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 speedUpCap = 48 << 20
) )
// doSpeed measures the agent's HTTP link to the server: round-trip time of an // 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. // 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 { func (a *Agent) doSpeed(ctx context.Context, env proto.TaskEnvelope) proto.TaskResult {
var args proto.SysSpeedArgs var args proto.SysSpeedArgs
if len(env.Args) > 0 { if len(env.Args) > 0 {
_ = json.Unmarshal(env.Args, &args) _ = 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 { if err != nil {
return proto.TaskResult{Error: err.Error()} 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} 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} res := proto.SysSpeedResult{Relay: relay}
streams := "/api/agent/speed" streams := "/api/agent/speed"
if relay { 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 { if err != nil {
return res, err return res, err
} }
res.DownBps = down res.DownBps = down
// Up: send until the window closes or the cap is hit. // 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() start := time.Now()
resp, err := a.speedRequest(ctx, http.MethodPost, streams+"/up", body) resp, err := a.speedRequest(ctx, http.MethodPost, streams+"/up", body)
if err != nil { if err != nil {
@@ -78,9 +84,9 @@ func (a *Agent) measureSpeed(ctx context.Context, relay bool) (proto.SysSpeedRes
return res, nil 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. // 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) ctx, cancel := context.WithCancel(ctx)
defer cancel() defer cancel()
t := time.AfterFunc(speedWait, 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) k, err := resp.Body.Read(buf)
if k > 0 && start.IsZero() { if k > 0 && start.IsZero() {
start = time.Now() start = time.Now()
t.Reset(speedWindow) t.Reset(window)
} }
n += int64(k) n += int64(k)
if err != nil { if err != nil {
+3
View File
@@ -184,6 +184,9 @@ type SysSpeedArgs struct {
// Relay makes the agent use the relayed streams, which the server pipes // Relay makes the agent use the relayed streams, which the server pipes
// to and from the browser. // to and from the browser.
Relay bool `json:"relay,omitempty"` 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 // SysSpeedResult is returned for TaskSysSpeed: the agent's HTTP link to the
+9 -7
View File
@@ -20,17 +20,20 @@ const (
// statCacheFor is how long one sample answers every open status page, so // statCacheFor is how long one sample answers every open status page, so
// several tabs cost the agent a single reading. // several tabs cost the agent a single reading.
statCacheFor = 2 * time.Second 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 // 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. // 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 // speedDownCap bounds one download stream; speedDownFor ends it if the
// agent does not hang up first. // agent does not hang up first.
speedDownCap = 256 << 20 speedDownCap = 1 << 30
speedDownFor = 6 * time.Second speedDownFor = 2 * speedWindow
speedUpCap = 64 << 20 speedUpCap = 64 << 20
// speedRelayUpFor is how long the agent's upload is passed on to the // speedRelayUpFor is how long the agent's upload is passed on to the
// browser: the agent's own window with slack. // 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. // 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) http.NotFound(w, r)
return 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. // 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") != "" relay := r.URL.Query().Get("relay") != ""
m := s.meters.start(id) m := s.meters.start(id)
defer s.meters.stop(id, m) defer s.meters.stop(id, m)
var args []byte
if relay { if relay {
rl := s.relays.get(id) rl := s.relays.get(id)
if rl == nil { if rl == nil {
@@ -152,8 +154,8 @@ func (s *Server) handleAgentSpeed(w http.ResponseWriter, r *http.Request) {
return return
} }
defer s.relays.drop(id, rl) 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) res, err := s.runTask(id, proto.TaskSysSpeed, args, speedTaskTimeout)
if err != nil { if err != nil {
part, ok := m.result() part, ok := m.result()
+2 -2
View File
@@ -77,7 +77,7 @@
<h2>Скорость соединения</h2> <h2>Скорость соединения</h2>
<p class="muted">Клиент замеряет свой канал до хоста панели - тот же, по которому идут терминал, файлы и http-проброс. <p class="muted">Клиент замеряет свой канал до хоста панели - тот же, по которому идут терминал, файлы и http-проброс.
С галочкой «До браузера» данные идут дальше, через панель до этого браузера и обратно: так виден весь путь клиент - панель - браузер. С галочкой «До браузера» данные идут дальше, через панель до этого браузера и обратно: так виден весь путь клиент - панель - браузер.
Замер занимает около 7 секунд, не дольше 20, и на это время нагружает канал.</p> Замер занимает около 15 секунд, не дольше 40, и на это время нагружает канал.</p>
<div class="row"> <div class="row">
<button id="speed-btn" type="button">Замерить скорость</button> <button id="speed-btn" type="button">Замерить скорость</button>
<label class="check" title="Данные пойдут не только между клиентом и хостом панели, а через панель до этого браузера и обратно"> <label class="check" title="Данные пойдут не только между клиентом и хостом панели, а через панель до этого браузера и обратно">
@@ -225,7 +225,7 @@
return rd.read().then(function (c) { return rd.read().then(function (c) {
if (c.done) return done(); if (c.done) return done();
last = performance.now(); last = performance.now();
if (!st.start) { st.start = last; setTimeout(st.stop, 4000); onFirst(); } if (!st.start) { st.start = last; setTimeout(st.stop, {{.RelayUpMs}}); onFirst(); }
else st.n += c.value.length; else st.n += c.value.length;
return pump(); return pump();
}); });