more features
This commit is contained in:
@@ -62,6 +62,8 @@ type StatsCollector struct {
|
|||||||
backendMethodStats map[string]map[string][]time.Duration // Track durations by backend and method
|
backendMethodStats map[string]map[string][]time.Duration // Track durations by backend and method
|
||||||
backendWins map[string]int // Track how many times each backend responded first
|
backendWins map[string]int // Track how many times each backend responded first
|
||||||
methodBackendWins map[string]map[string]int // Track wins per method per backend
|
methodBackendWins map[string]map[string]int // Track wins per method per backend
|
||||||
|
firstResponseDurations []time.Duration // Track durations of first successful responses
|
||||||
|
methodFirstResponseDurations map[string][]time.Duration // Track first response durations by method
|
||||||
totalRequests int
|
totalRequests int
|
||||||
errorCount int
|
errorCount int
|
||||||
wsConnections []WebSocketStats // Track websocket connections
|
wsConnections []WebSocketStats // Track websocket connections
|
||||||
@@ -83,6 +85,8 @@ func NewStatsCollector(summaryInterval time.Duration) *StatsCollector {
|
|||||||
backendMethodStats: make(map[string]map[string][]time.Duration),
|
backendMethodStats: make(map[string]map[string][]time.Duration),
|
||||||
backendWins: make(map[string]int),
|
backendWins: make(map[string]int),
|
||||||
methodBackendWins: make(map[string]map[string]int),
|
methodBackendWins: make(map[string]map[string]int),
|
||||||
|
firstResponseDurations: make([]time.Duration, 0, 1000),
|
||||||
|
methodFirstResponseDurations: make(map[string][]time.Duration),
|
||||||
appStartTime: now,
|
appStartTime: now,
|
||||||
intervalStartTime: now,
|
intervalStartTime: now,
|
||||||
summaryInterval: summaryInterval,
|
summaryInterval: summaryInterval,
|
||||||
@@ -200,6 +204,15 @@ func (sc *StatsCollector) AddStats(stats []ResponseStats, totalDuration time.Dur
|
|||||||
sc.methodBackendWins[method] = make(map[string]int)
|
sc.methodBackendWins[method] = make(map[string]int)
|
||||||
}
|
}
|
||||||
sc.methodBackendWins[method][fastestBackend]++
|
sc.methodBackendWins[method][fastestBackend]++
|
||||||
|
|
||||||
|
// Track first response duration
|
||||||
|
sc.firstResponseDurations = append(sc.firstResponseDurations, fastestDuration)
|
||||||
|
|
||||||
|
// Track first response duration by method
|
||||||
|
if _, exists := sc.methodFirstResponseDurations[method]; !exists {
|
||||||
|
sc.methodFirstResponseDurations[method] = make([]time.Duration, 0, 100)
|
||||||
|
}
|
||||||
|
sc.methodFirstResponseDurations[method] = append(sc.methodFirstResponseDurations[method], fastestDuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add stats to the collection
|
// Add stats to the collection
|
||||||
@@ -441,6 +454,39 @@ func (sc *StatsCollector) printSummary() {
|
|||||||
"Backend", "Count", "Min", "Avg", "Max", "p50", "p90", "p99")
|
"Backend", "Count", "Min", "Avg", "Max", "p50", "p90", "p99")
|
||||||
fmt.Printf("%s\n", strings.Repeat("-", 100))
|
fmt.Printf("%s\n", strings.Repeat("-", 100))
|
||||||
|
|
||||||
|
// First, show the "First Response" merged statistics
|
||||||
|
if len(sc.firstResponseDurations) > 0 {
|
||||||
|
firstRespDurations := make([]time.Duration, len(sc.firstResponseDurations))
|
||||||
|
copy(firstRespDurations, sc.firstResponseDurations)
|
||||||
|
|
||||||
|
sort.Slice(firstRespDurations, func(i, j int) bool {
|
||||||
|
return firstRespDurations[i] < firstRespDurations[j]
|
||||||
|
})
|
||||||
|
|
||||||
|
var sum time.Duration
|
||||||
|
for _, d := range firstRespDurations {
|
||||||
|
sum += d
|
||||||
|
}
|
||||||
|
|
||||||
|
avg := sum / time.Duration(len(firstRespDurations))
|
||||||
|
min := firstRespDurations[0]
|
||||||
|
max := firstRespDurations[len(firstRespDurations)-1]
|
||||||
|
|
||||||
|
p50idx := len(firstRespDurations) * 50 / 100
|
||||||
|
p90idx := len(firstRespDurations) * 90 / 100
|
||||||
|
p99idx := minInt(len(firstRespDurations)-1, len(firstRespDurations)*99/100)
|
||||||
|
|
||||||
|
p50 := firstRespDurations[p50idx]
|
||||||
|
p90 := firstRespDurations[p90idx]
|
||||||
|
p99 := firstRespDurations[p99idx]
|
||||||
|
|
||||||
|
fmt.Printf("%-20s %10d %10s %10s %10s %10s %10s %10s\n",
|
||||||
|
"First Response", len(firstRespDurations),
|
||||||
|
formatDuration(min), formatDuration(avg), formatDuration(max),
|
||||||
|
formatDuration(p50), formatDuration(p90), formatDuration(p99))
|
||||||
|
fmt.Printf("%s\n", strings.Repeat("-", 100))
|
||||||
|
}
|
||||||
|
|
||||||
for _, backend := range backendNames {
|
for _, backend := range backendNames {
|
||||||
durations := backendDurations[backend]
|
durations := backendDurations[backend]
|
||||||
if len(durations) == 0 {
|
if len(durations) == 0 {
|
||||||
@@ -666,6 +712,46 @@ func (sc *StatsCollector) printSummary() {
|
|||||||
formatDuration(min), formatDuration(avg), formatDuration(max),
|
formatDuration(min), formatDuration(avg), formatDuration(max),
|
||||||
formatDuration(p50), formatDuration(p90), formatDuration(p99))
|
formatDuration(p50), formatDuration(p90), formatDuration(p99))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show First Response statistics for this method
|
||||||
|
if methodFirstDurations, exists := sc.methodFirstResponseDurations[method]; exists && len(methodFirstDurations) > 0 {
|
||||||
|
// Make a copy and sort
|
||||||
|
durations := make([]time.Duration, len(methodFirstDurations))
|
||||||
|
copy(durations, methodFirstDurations)
|
||||||
|
|
||||||
|
sort.Slice(durations, func(i, j int) bool {
|
||||||
|
return durations[i] < durations[j]
|
||||||
|
})
|
||||||
|
|
||||||
|
var sum time.Duration
|
||||||
|
for _, d := range durations {
|
||||||
|
sum += d
|
||||||
|
}
|
||||||
|
|
||||||
|
avg := sum / time.Duration(len(durations))
|
||||||
|
min := durations[0]
|
||||||
|
max := durations[len(durations)-1]
|
||||||
|
|
||||||
|
p50 := min
|
||||||
|
p90 := min
|
||||||
|
p99 := min
|
||||||
|
|
||||||
|
if len(durations) >= 2 {
|
||||||
|
p50idx := len(durations) * 50 / 100
|
||||||
|
p90idx := len(durations) * 90 / 100
|
||||||
|
p99idx := minInt(len(durations)-1, len(durations)*99/100)
|
||||||
|
|
||||||
|
p50 = durations[p50idx]
|
||||||
|
p90 = durations[p90idx]
|
||||||
|
p99 = durations[p99idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf(" %-20s %10d %10s %10s %10s %10s %10s %10s\n",
|
||||||
|
"First Response", len(durations),
|
||||||
|
formatDuration(min), formatDuration(avg), formatDuration(max),
|
||||||
|
formatDuration(p50), formatDuration(p90), formatDuration(p99))
|
||||||
|
fmt.Printf(" %s\n", strings.Repeat("-", 98))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -714,6 +800,16 @@ func (sc *StatsCollector) printSummary() {
|
|||||||
sc.backendWins = make(map[string]int)
|
sc.backendWins = make(map[string]int)
|
||||||
sc.methodBackendWins = make(map[string]map[string]int)
|
sc.methodBackendWins = make(map[string]map[string]int)
|
||||||
|
|
||||||
|
// Reset first response statistics
|
||||||
|
if len(sc.firstResponseDurations) > 1000 {
|
||||||
|
sc.firstResponseDurations = sc.firstResponseDurations[len(sc.firstResponseDurations)-1000:]
|
||||||
|
} else {
|
||||||
|
sc.firstResponseDurations = sc.firstResponseDurations[:0]
|
||||||
|
}
|
||||||
|
for method := range sc.methodFirstResponseDurations {
|
||||||
|
sc.methodFirstResponseDurations[method] = sc.methodFirstResponseDurations[method][:0]
|
||||||
|
}
|
||||||
|
|
||||||
// Reset CU counters for the next interval
|
// Reset CU counters for the next interval
|
||||||
sc.totalCU = 0
|
sc.totalCU = 0
|
||||||
sc.methodCU = make(map[string]int)
|
sc.methodCU = make(map[string]int)
|
||||||
|
|||||||
Reference in New Issue
Block a user