130 lines
3.7 KiB
Go
130 lines
3.7 KiB
Go
package main
|
|
|
|
// MEASUREMENT, not a ratchet. Answers one question with numbers instead of a
|
|
// guess: how big a payload can ctx_put actually carry, and what does it cost?
|
|
//
|
|
// The whole management job is ONE WebSocket frame — the host sends
|
|
// `C` + json.Marshal({op, args}) (the host's mgmt worker),
|
|
// and the payload rides inside args[1] as base64. So the artifact is buffered
|
|
// whole, four times over: bytes -> base64 string -> JSON frame on the host,
|
|
// then frame -> decoded bytes here. Nothing streams.
|
|
//
|
|
// Kept because the delivery design rests on this number.
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gorilla/websocket"
|
|
)
|
|
|
|
// uvicornWSMaxSize (harness.go) is the host's WebSocket frame ceiling.
|
|
|
|
func TestMeasureCtxPutCapacity(t *testing.T) {
|
|
if os.Getenv("MEASURE_CTX_PUT") == "" {
|
|
t.Skip("set MEASURE_CTX_PUT=1 to run the capacity measurement")
|
|
}
|
|
root := t.TempDir()
|
|
up := websocket.Upgrader{}
|
|
|
|
// Stand-in for the host's mgmt worker: send one C frame, collect O/R until X.
|
|
// Mirrors the host's management worker frame for frame.
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
ws, err := up.Upgrade(w, r, nil)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer ws.Close()
|
|
handleMgmtConn(ws, root, "", "")
|
|
}))
|
|
defer srv.Close()
|
|
wsURL := "ws" + strings.TrimPrefix(srv.URL, "http")
|
|
|
|
sizes := []int{
|
|
1 << 10, 1 << 20, 4 << 20, 8 << 20,
|
|
12 << 20, 12_582_912, 16 << 20, 32 << 20, 64 << 20,
|
|
}
|
|
fmt.Printf("\n%-12s %-14s %-10s %-12s %-9s %s\n",
|
|
"plaintext", "frame bytes", "vs 16MiB", "wall", "heap MiB", "result")
|
|
for _, n := range sizes {
|
|
payload := make([]byte, n)
|
|
for i := range payload {
|
|
payload[i] = byte('a' + i%26)
|
|
}
|
|
b64 := base64.StdEncoding.EncodeToString(payload)
|
|
job, _ := json.Marshal(mgmtJob{Op: "ctx_put", Args: []string{fmt.Sprintf("m/%d.bin", n), b64}})
|
|
frame := append([]byte("C"), job...)
|
|
|
|
var m0, m1 runtime.MemStats
|
|
runtime.GC()
|
|
runtime.ReadMemStats(&m0)
|
|
start := time.Now()
|
|
|
|
c, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
|
|
if err != nil {
|
|
t.Fatalf("dial: %v", err)
|
|
}
|
|
res := "ok"
|
|
if err := c.WriteMessage(websocket.BinaryMessage, frame); err != nil {
|
|
res = "write: " + err.Error()
|
|
} else {
|
|
for {
|
|
_, data, err := c.ReadMessage()
|
|
if err != nil {
|
|
res = "read: " + err.Error()
|
|
break
|
|
}
|
|
if len(data) > 0 && data[0] == 'X' {
|
|
if string(data[1:]) != "0" {
|
|
res = "exit " + string(data[1:])
|
|
}
|
|
break
|
|
}
|
|
if len(data) > 0 && data[0] == 'R' {
|
|
res = "stderr: " + strings.TrimSpace(string(data[1:]))
|
|
}
|
|
}
|
|
}
|
|
wall := time.Since(start)
|
|
runtime.ReadMemStats(&m1)
|
|
c.Close()
|
|
|
|
verdict := "WITHIN uvicorn frame limit"
|
|
if len(frame) > uvicornWSMaxSize {
|
|
verdict = "OVER uvicorn ws_max_size — host would refuse"
|
|
}
|
|
fmt.Printf("%-12s %-14d %-10s %-12s %-9d %s | %s\n",
|
|
human(n), len(frame),
|
|
fmt.Sprintf("%.2fx", float64(len(frame))/float64(uvicornWSMaxSize)),
|
|
wall.Round(time.Millisecond).String(),
|
|
(m1.TotalAlloc-m0.TotalAlloc)/(1<<20), res, verdict)
|
|
}
|
|
|
|
// What a real export would cost: a real entity's staged tree measured
|
|
// 12 GiB raw.
|
|
const artifact = 13 * (1 << 30)
|
|
frames := (artifact*4/3 + uvicornWSMaxSize - 1) / uvicornWSMaxSize
|
|
fmt.Printf("\na 13 GiB artifact = %d base64 bytes = %d ctx_put frames at the 16 MiB cap\n",
|
|
artifact*4/3, frames)
|
|
fmt.Printf("at the measured 25s bastion / 30s host per-job timeout and ONE job per worker\n")
|
|
fmt.Printf("connection, that is %d sequential dispatches.\n", frames)
|
|
}
|
|
|
|
func human(n int) string {
|
|
switch {
|
|
case n >= 1<<20:
|
|
return fmt.Sprintf("%d MiB", n>>20)
|
|
case n >= 1<<10:
|
|
return fmt.Sprintf("%d KiB", n>>10)
|
|
}
|
|
return fmt.Sprintf("%d B", n)
|
|
}
|