All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 1m37s
184 lines
4.5 KiB
Go
184 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
// Konfiguration (über Flags steuerbar)
|
|
var (
|
|
targetURL string
|
|
numClients int
|
|
duration time.Duration
|
|
chunkTicks int
|
|
)
|
|
|
|
// Statistiken (Atomar für Thread-Sicherheit)
|
|
var (
|
|
totalRequests int64
|
|
totalErrors int64
|
|
totalLatency int64 // in Millisekunden
|
|
)
|
|
|
|
// Datenstrukturen für Kommunikation (Minimal)
|
|
type StartResponse struct {
|
|
SessionID string `json:"sessionId"`
|
|
}
|
|
|
|
type Input struct {
|
|
Tick int `json:"t"`
|
|
Act string `json:"act"`
|
|
}
|
|
|
|
type ValidateRequest struct {
|
|
SessionID string `json:"sessionId"`
|
|
Inputs []Input `json:"inputs"`
|
|
TotalTicks int `json:"totalTicks"`
|
|
}
|
|
|
|
func main() {
|
|
// 1. Argumente parsen
|
|
flag.StringVar(&targetURL, "url", "http://localhost:8080", "URL zum Server")
|
|
flag.IntVar(&numClients, "clients", 50, "Anzahl gleichzeitiger Spieler")
|
|
flag.DurationVar(&duration, "time", 10*time.Second, "Dauer des Tests (z.B. 10s, 1m)")
|
|
flag.IntVar(&chunkTicks, "ticks", 60, "Simulierte Ticks pro Anfrage (Last)")
|
|
flag.Parse()
|
|
|
|
fmt.Printf("🚀 Starte Benchmark auf %s\n", targetURL)
|
|
fmt.Printf("👥 Spieler (Clients): %d\n", numClients)
|
|
fmt.Printf("⏱️ Dauer: %s\n", duration)
|
|
fmt.Printf("🏋️ Last pro Request: %d Ticks Physik\n", chunkTicks)
|
|
fmt.Println("---------------------------------------------------")
|
|
|
|
// 2. Clients starten
|
|
var wg sync.WaitGroup
|
|
start := time.Now()
|
|
|
|
// Kanal um den Workern das Stop-Signal zu geben
|
|
stopChan := make(chan struct{})
|
|
|
|
// Timer für das Testende
|
|
time.AfterFunc(duration, func() {
|
|
close(stopChan)
|
|
})
|
|
|
|
for i := 0; i < numClients; i++ {
|
|
wg.Add(1)
|
|
go runClient(i, &wg, stopChan)
|
|
}
|
|
|
|
// Warten bis alle fertig sind
|
|
wg.Wait()
|
|
totalTime := time.Since(start)
|
|
|
|
// 3. Auswertung
|
|
printReport(totalTime)
|
|
}
|
|
|
|
func runClient(id int, wg *sync.WaitGroup, stopChan <-chan struct{}) {
|
|
defer wg.Done()
|
|
|
|
// A. Session Starten
|
|
sessionID, err := startSession()
|
|
if err != nil {
|
|
log.Printf("[Client %d] Start Fehler: %v", id, err)
|
|
atomic.AddInt64(&totalErrors, 1)
|
|
return
|
|
}
|
|
|
|
// B. Game Loop simulieren
|
|
client := &http.Client{Timeout: 5 * time.Second}
|
|
|
|
for {
|
|
select {
|
|
case <-stopChan:
|
|
return // Testzeit abgelaufen
|
|
default:
|
|
// Wir simulieren einen Chunk (z.B. Spieler rennt einfach geradeaus)
|
|
reqData := ValidateRequest{
|
|
SessionID: sessionID,
|
|
Inputs: []Input{}, // Keine Eingaben = Nur Rennen
|
|
TotalTicks: chunkTicks,
|
|
}
|
|
|
|
payload, _ := json.Marshal(reqData)
|
|
|
|
reqStart := time.Now()
|
|
resp, err := client.Post(targetURL+"/api/validate", "application/json", bytes.NewBuffer(payload))
|
|
latency := time.Since(reqStart).Milliseconds()
|
|
|
|
if err != nil || resp.StatusCode != 200 {
|
|
// Fehler zählen
|
|
atomic.AddInt64(&totalErrors, 1)
|
|
if resp != nil {
|
|
io.Copy(io.Discard, resp.Body) // Body leeren
|
|
resp.Body.Close()
|
|
}
|
|
} else {
|
|
// Erfolg zählen
|
|
atomic.AddInt64(&totalRequests, 1)
|
|
atomic.AddInt64(&totalLatency, latency)
|
|
io.Copy(io.Discard, resp.Body)
|
|
resp.Body.Close()
|
|
}
|
|
|
|
// Optional: Kurz warten um "echte" 60 FPS zu simulieren?
|
|
// Für Stresstests lassen wir das weg, wir wollen sehen was MAXIMAL geht.
|
|
// time.Sleep(100 * time.Millisecond)
|
|
}
|
|
}
|
|
}
|
|
|
|
func startSession() (string, error) {
|
|
resp, err := http.Post(targetURL+"/api/start", "application/json", nil)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != 200 {
|
|
return "", fmt.Errorf("status %d", resp.StatusCode)
|
|
}
|
|
|
|
var data StartResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
|
return "", err
|
|
}
|
|
return data.SessionID, nil
|
|
}
|
|
|
|
func printReport(totalTime time.Duration) {
|
|
reqs := atomic.LoadInt64(&totalRequests)
|
|
errs := atomic.LoadInt64(&totalErrors)
|
|
lat := atomic.LoadInt64(&totalLatency)
|
|
|
|
avgLat := 0.0
|
|
if reqs > 0 {
|
|
avgLat = float64(lat) / float64(reqs)
|
|
}
|
|
|
|
rps := float64(reqs) / totalTime.Seconds()
|
|
|
|
fmt.Println("\n📊 ERGEBNISSE")
|
|
fmt.Println("---------------------------------------------------")
|
|
fmt.Printf("✅ Erfolgreiche Requests: %d\n", reqs)
|
|
fmt.Printf("❌ Fehler: %d\n", errs)
|
|
fmt.Printf("⚡ Requests pro Sekunde: %.2f Req/s\n", rps)
|
|
fmt.Printf("🐢 Ø Antwortzeit: %.2f ms\n", avgLat)
|
|
fmt.Println("---------------------------------------------------")
|
|
|
|
if avgLat > 100 {
|
|
fmt.Println("⚠️ WARNUNG: Server antwortet langsam (>100ms). Spieler könnten Lags spüren.")
|
|
} else {
|
|
fmt.Println("✨ PERFEKT: Server ist schnell genug für flüssiges Spielen!")
|
|
}
|
|
}
|