init
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
// Copyright (C) 2026 Dcntrlized
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published
|
||||
// by the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package realtime
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// TestBroadcastLatency measures the fan-out path on real sockets: from the
|
||||
// instant Broadcast is called to the instant BOTH subscribers have decoded the
|
||||
// frame. It excludes the HTTP action and the database insert that precede a
|
||||
// trigger in production, so it is the floor for the end-to-end number.
|
||||
//
|
||||
// It logs p50/p95/max and only fails on a gross regression (p95 > 500ms) to
|
||||
// stay useful without being flaky on a loaded machine.
|
||||
func TestBroadcastLatency(t *testing.T) {
|
||||
alertID := uuid.New()
|
||||
_, wsURL, hub := startTestServer(t, alertID)
|
||||
|
||||
first := dial(t, wsURL)
|
||||
second := dial(t, wsURL)
|
||||
waitClients(t, hub, alertID, 2)
|
||||
|
||||
const iterations = 100
|
||||
samples := make([]time.Duration, 0, iterations)
|
||||
for i := 0; i < iterations; i++ {
|
||||
eventID := uuid.NewString()
|
||||
frame := AlarmFrame{
|
||||
Type: "alarm",
|
||||
AlertID: alertID.String(),
|
||||
EventID: eventID,
|
||||
Action: "trigger",
|
||||
Message: "latency probe",
|
||||
ServerTime: time.Now().UTC().Format(time.RFC3339Nano),
|
||||
AutoStopSeconds: 300,
|
||||
}
|
||||
start := time.Now()
|
||||
if delivered := hub.Broadcast(alertID, frame); delivered != 2 {
|
||||
t.Fatalf("iteration %d: Broadcast delivered to %d clients, want 2", i, delivered)
|
||||
}
|
||||
for _, conn := range []*websocket.Conn{first, second} {
|
||||
got := readAlarm(t, conn)
|
||||
if got.EventID != eventID {
|
||||
t.Fatalf("iteration %d: event id = %q, want %q", i, got.EventID, eventID)
|
||||
}
|
||||
}
|
||||
samples = append(samples, time.Since(start))
|
||||
}
|
||||
|
||||
sort.Slice(samples, func(i, j int) bool { return samples[i] < samples[j] })
|
||||
p50 := samples[len(samples)*50/100]
|
||||
p95 := samples[len(samples)*95/100]
|
||||
max := samples[len(samples)-1]
|
||||
t.Logf("broadcast -> 2 clients decoded: n=%d p50=%v p95=%v max=%v",
|
||||
len(samples), p50, p95, max)
|
||||
if p95 > 500*time.Millisecond {
|
||||
t.Fatalf("p95 latency %v exceeds the 500ms budget", p95)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user