80 lines
1.7 KiB
Go
80 lines
1.7 KiB
Go
package metrics
|
|
|
|
import (
|
|
"math"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// RecentLatencyByKey tracks a bounded in-memory latency window per logical key.
|
|
// It is intentionally small and process-local; Prometheus histograms keep the
|
|
// lifetime view, while this gives capacity-check a recent-window signal.
|
|
type RecentLatencyByKey struct {
|
|
mu sync.Mutex
|
|
size int
|
|
windows map[string]*recentLatencyWindow
|
|
}
|
|
|
|
type recentLatencyWindow struct {
|
|
values []float64
|
|
next int
|
|
count int
|
|
}
|
|
|
|
func NewRecentLatencyByKey(size int) *RecentLatencyByKey {
|
|
if size <= 0 {
|
|
size = 1
|
|
}
|
|
return &RecentLatencyByKey{
|
|
size: size,
|
|
windows: map[string]*recentLatencyWindow{},
|
|
}
|
|
}
|
|
|
|
func (r *RecentLatencyByKey) Observe(key string, value float64) (p99 float64, samples int) {
|
|
if r == nil {
|
|
return 0, 0
|
|
}
|
|
key = strings.TrimSpace(key)
|
|
if key == "" {
|
|
key = "unknown"
|
|
}
|
|
if value < 0 || math.IsNaN(value) {
|
|
value = 0
|
|
}
|
|
if math.IsInf(value, 1) {
|
|
value = math.MaxFloat64
|
|
}
|
|
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
window := r.windows[key]
|
|
if window == nil {
|
|
window = &recentLatencyWindow{values: make([]float64, r.size)}
|
|
r.windows[key] = window
|
|
}
|
|
window.values[window.next] = value
|
|
window.next = (window.next + 1) % len(window.values)
|
|
if window.count < len(window.values) {
|
|
window.count++
|
|
}
|
|
return window.quantileLocked(0.99), window.count
|
|
}
|
|
|
|
func (w *recentLatencyWindow) quantileLocked(q float64) float64 {
|
|
if w == nil || w.count == 0 {
|
|
return 0
|
|
}
|
|
snapshot := append([]float64(nil), w.values[:w.count]...)
|
|
sort.Float64s(snapshot)
|
|
index := int(math.Ceil(q*float64(len(snapshot)))) - 1
|
|
if index < 0 {
|
|
index = 0
|
|
}
|
|
if index >= len(snapshot) {
|
|
index = len(snapshot) - 1
|
|
}
|
|
return snapshot[index]
|
|
}
|