feat: speed test through the browser, client - panel - browser (0.0.6)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b51363bf72
commit
87921a1381
9 files changed
+344
-33
No files matched your search
+56
-17
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
@@ -14,14 +15,22 @@ import (
|
||||
const (
|
||||
// speedWindow is how long each direction is measured for.
|
||||
speedWindow = 3 * 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 = 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.
|
||||
func (a *Agent) doSpeed(ctx context.Context) proto.TaskResult {
|
||||
res, err := a.measureSpeed(ctx)
|
||||
// empty request, then how much flows each way in speedWindow. 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)
|
||||
if err != nil {
|
||||
return proto.TaskResult{Error: err.Error()}
|
||||
}
|
||||
@@ -29,8 +38,12 @@ func (a *Agent) doSpeed(ctx context.Context) proto.TaskResult {
|
||||
return proto.TaskResult{OK: true, Data: data}
|
||||
}
|
||||
|
||||
func (a *Agent) measureSpeed(ctx context.Context) (proto.SysSpeedResult, error) {
|
||||
var res proto.SysSpeedResult
|
||||
func (a *Agent) measureSpeed(ctx context.Context, relay bool) (proto.SysSpeedResult, error) {
|
||||
res := proto.SysSpeedResult{Relay: relay}
|
||||
streams := "/api/agent/speed"
|
||||
if relay {
|
||||
streams += "/relay"
|
||||
}
|
||||
// Ping: the first request may pay for a new connection, so keep the best.
|
||||
for i := 0; i < 4; i++ {
|
||||
start := time.Now()
|
||||
@@ -46,24 +59,16 @@ func (a *Agent) measureSpeed(ctx context.Context) (proto.SysSpeedResult, error)
|
||||
}
|
||||
}
|
||||
|
||||
// Down: read what the server streams until the window closes.
|
||||
dctx, cancel := context.WithTimeout(ctx, speedWindow)
|
||||
resp, err := a.speedRequest(dctx, http.MethodGet, "/api/agent/speed/down", nil)
|
||||
down, err := a.speedDown(ctx, streams+"/down")
|
||||
if err != nil {
|
||||
cancel()
|
||||
return res, err
|
||||
}
|
||||
start := time.Now()
|
||||
n, _ := io.Copy(io.Discard, resp.Body)
|
||||
elapsed := time.Since(start)
|
||||
resp.Body.Close()
|
||||
cancel()
|
||||
res.DownBps = bps(n, elapsed)
|
||||
res.DownBps = down
|
||||
|
||||
// Up: send until the window closes or the cap is hit.
|
||||
body := &speedSource{deadline: time.Now().Add(speedWindow), left: speedUpCap}
|
||||
start = time.Now()
|
||||
resp, err = a.speedRequest(ctx, http.MethodPost, "/api/agent/speed/up", body)
|
||||
start := time.Now()
|
||||
resp, err := a.speedRequest(ctx, http.MethodPost, streams+"/up", body)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
@@ -73,6 +78,40 @@ func (a *Agent) measureSpeed(ctx context.Context) (proto.SysSpeedResult, error)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// speedDown reads what the server streams for speedWindow, counted from the
|
||||
// first byte, and returns the rate.
|
||||
func (a *Agent) speedDown(ctx context.Context, path string) (float64, error) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
t := time.AfterFunc(speedWait, cancel)
|
||||
defer t.Stop()
|
||||
resp, err := a.speedRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var (
|
||||
n int64
|
||||
start time.Time
|
||||
buf = make([]byte, 64<<10)
|
||||
)
|
||||
for {
|
||||
k, err := resp.Body.Read(buf)
|
||||
if k > 0 && start.IsZero() {
|
||||
start = time.Now()
|
||||
t.Reset(speedWindow)
|
||||
}
|
||||
n += int64(k)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if start.IsZero() {
|
||||
return 0, errors.New("нет данных для замера")
|
||||
}
|
||||
return bps(n, time.Since(start)), nil
|
||||
}
|
||||
|
||||
func (a *Agent) speedRequest(ctx context.Context, method, path string, body io.Reader) (*http.Response, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, method, a.cfg.Host+path, body)
|
||||
if err != nil {
|
||||
|
||||
Reference in new issue
Block a user