add bench
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 1m37s
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 1m37s
This commit is contained in:
183
benchmark/main.go
Normal file
183
benchmark/main.go
Normal file
@@ -0,0 +1,183 @@
|
||||
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!")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user