Implemented qt port + e2e

This commit is contained in:
Martino Ferrari
2026-06-26 09:11:10 +02:00
parent 0d7d8f396b
commit 4702d0a217
146 changed files with 57272 additions and 128 deletions
+177 -10
View File
@@ -44,6 +44,9 @@ var (
durSec = flag.Float64("dur", 4.0, "live recording duration (s)")
timeout = flag.Duration("timeout", 90*time.Second, "overall timeout")
verbose = flag.Bool("v", false, "log every event")
mode = flag.String("mode", "checks", "checks | stress")
reqrate = flag.Float64("reqrate", 0, "stress: sustained zoom requests/sec (0 = liveness only)")
clientID = flag.Int("clientid", 0, "stress: parallel client index (output suffix)")
)
// ── wire types ────────────────────────────────────────────────────────────
@@ -173,13 +176,14 @@ type client struct {
ws *websocket.Conn
deadline time.Time
mu sync.Mutex
sources []sourceInfo
configs map[string][]signalInfo
pushes []*pushFrame
zooms map[uint32]map[string]points
trigSt []string
captures []*captureFrame
mu sync.Mutex
sources []sourceInfo
configs map[string][]signalInfo
pushes []*pushFrame
zooms map[uint32]map[string]points
zoomArrival map[uint32]time.Time
trigSt []string
captures []*captureFrame
readErr error
done chan struct{}
@@ -266,6 +270,7 @@ func (c *client) handle(mt int, data []byte) {
if json.Unmarshal(data, &body) == nil {
c.mu.Lock()
c.zooms[ev.ReqID] = body.Signals
c.zoomArrival[ev.ReqID] = time.Now()
c.mu.Unlock()
}
case "triggerState":
@@ -491,9 +496,10 @@ func main() {
c := &client{
ws: ws, deadline: time.Now().Add(*timeout),
configs: map[string][]signalInfo{},
zooms: map[uint32]map[string]points{},
done: make(chan struct{}),
configs: map[string][]signalInfo{},
zooms: map[uint32]map[string]points{},
zoomArrival: map[uint32]time.Time{},
done: make(chan struct{}),
}
go c.reader()
out := checksOut{Scenario: *scenario}
@@ -522,6 +528,13 @@ func main() {
return true
})
// stress mode: record liveness + sustained zoom latency, then exit. The
// correctness checks below are skipped (a separate gate framework).
if *mode == "stress" {
c.runStress(*scenario, *clientID, *durSec, *reqrate, *outDir)
return
}
// 2. live recording — the reader goroutine accumulates pushes in the
// background, so we simply wait out the recording window (or an early
// reader death).
@@ -647,6 +660,160 @@ func main() {
fmt.Printf("OK chain-client %s: %s + %s\n", *scenario, filepath.Base(recvPath), filepath.Base(cj))
}
// ── stress mode ──────────────────────────────────────────────────────────────
type stressOut struct {
Scenario string `json:"scenario"`
ClientID int `json:"clientId"`
Frames int `json:"frames"`
Signals int `json:"signals"`
Monotonic bool `json:"monotonic"`
WallClock bool `json:"wallclock"`
DurationS float64 `json:"duration"`
ReqRate float64 `json:"reqRate"`
ZoomCount int `json:"zoomCount"`
ZoomFail int `json:"zoomFail"`
ZoomP50ms float64 `json:"zoomP50ms"`
ZoomP95ms float64 `json:"zoomP95ms"`
ZoomMaxms float64 `json:"zoomMaxms"`
Key string `json:"key"`
}
// busiestKey returns the full "src:sig" key carrying the most samples so far.
func (c *client) busiestKey() string {
m := c.merged()
var busy string
bn := 0
for k, p := range m {
if len(p.T) > bn {
bn, busy = len(p.T), k
}
}
return busy
}
// maxTimeFull returns the latest timestamp seen for a full "src:sig" key.
func (c *client) maxTimeFull(full string) (float64, bool) {
c.mu.Lock()
defer c.mu.Unlock()
var mx float64
found := false
for _, f := range c.pushes {
if !strings.HasPrefix(full, f.sourceID+":") {
continue
}
name := full[len(f.sourceID)+1:]
p, ok := f.signals[name]
if !ok {
continue
}
for _, t := range p.T {
if !found || t > mx {
mx, found = t, true
}
}
}
return mx, found
}
// runStress records liveness for the duration while (optionally) issuing zoom
// queries at a sustained rate, measuring round-trip latency. One request is in
// flight at a time, so latency reflects the hub's serialised zoom service time
// under whatever concurrent live/zoom load the matrix imposes.
func (c *client) runStress(scenario string, clientID int, dur, reqrate float64, outDir string) {
if !c.waitFor(20*time.Second, func() bool { return len(c.pushes) > 0 }) {
fatal("stress: no live push within timeout")
}
// brief warmup so the busiest signal and a usable time window exist.
c.waitFor(500*time.Millisecond, func() bool { return false })
key := c.busiestKey()
start := time.Now()
end := start.Add(time.Duration(dur * float64(time.Second)))
var lat []float64
zoomFail := 0
var reqID uint32 = 5000
var interval time.Duration
if reqrate > 0 {
interval = time.Duration(float64(time.Second) / reqrate)
}
for reqrate > 0 && key != "" && time.Now().Before(end) {
tick := time.Now()
t1, ok := c.maxTimeFull(key)
if !ok {
c.waitFor(50*time.Millisecond, func() bool { return false })
continue
}
reqID++
c.send(map[string]interface{}{
"type": "zoom", "reqId": reqID, "t0": t1 - 0.5, "t1": t1,
"n": 300, "signals": key,
})
sendT := time.Now()
got := c.waitFor(3*time.Second, func() bool {
_, ok := c.zoomArrival[reqID]
return ok
})
if got {
c.mu.Lock()
at := c.zoomArrival[reqID]
c.mu.Unlock()
lat = append(lat, at.Sub(sendT).Seconds()*1000.0)
} else {
zoomFail++
}
if rem := interval - time.Since(tick); rem > 0 {
c.waitFor(rem, func() bool { return false })
}
}
// if no reqrate, simply wait out the remaining liveness window.
if reqrate <= 0 {
c.waitFor(time.Until(end), func() bool { return false })
}
m := c.merged()
now := float64(time.Now().UnixNano()) / 1e9
mono, wall := true, true
for _, p := range m {
for i, t := range p.T {
if math.Abs(t-now) > 60.0 {
wall = false
}
if i > 0 && t < p.T[i-1]-1e-9 {
mono = false
}
}
}
sort.Float64s(lat)
pct := func(q float64) float64 {
if len(lat) == 0 {
return 0
}
idx := int(q * float64(len(lat)-1))
return lat[idx]
}
so := stressOut{
Scenario: scenario, ClientID: clientID,
Frames: c.nPushes(), Signals: len(m), Monotonic: mono,
WallClock: wall, DurationS: dur, ReqRate: reqrate, Key: key,
ZoomCount: len(lat), ZoomFail: zoomFail,
ZoomP50ms: pct(0.50), ZoomP95ms: pct(0.95),
}
if len(lat) > 0 {
so.ZoomMaxms = lat[len(lat)-1]
}
path := filepath.Join(outDir,
fmt.Sprintf("stress_%s_c%d.json", scenario, clientID))
b, _ := json.MarshalIndent(so, "", " ")
if err := os.WriteFile(path, b, 0o644); err != nil {
fatal("write stress: %v", err)
}
log.Printf("stress: frames=%d signals=%d zoom n=%d fail=%d p50=%.1fms p95=%.1fms max=%.1fms",
so.Frames, so.Signals, so.ZoomCount, so.ZoomFail, so.ZoomP50ms, so.ZoomP95ms, so.ZoomMaxms)
fmt.Printf("OK stress %s c%d: %s\n", scenario, clientID, filepath.Base(path))
}
// runTrigger configures one edge/mode trigger, arms it, and records the result.
func (c *client) runTrigger(key, edge, mode string, thr float64) trigCheck {
tc := trigCheck{Edge: edge, Mode: mode, Key: key}