Files
2026-02-28 16:37:37 +08:00

84 lines
2.2 KiB
Go

package observability
import (
"sync/atomic"
"time"
)
type Snapshot struct {
GeneratedAtUTC string `json:"generated_at_utc"`
UptimeSeconds int64 `json:"uptime_seconds"`
TotalRequests uint64 `json:"total_requests"`
ClientErrors uint64 `json:"client_errors"`
ServerErrors uint64 `json:"server_errors"`
ClientErrorRatePct float64 `json:"client_error_rate_pct"`
ServerErrorRatePct float64 `json:"server_error_rate_pct"`
AvgLatencyMs float64 `json:"avg_latency_ms"`
MaxLatencyMs float64 `json:"max_latency_ms"`
}
type Collector struct {
startedAt time.Time
totalRequests uint64
clientErrors uint64
serverErrors uint64
totalLatencyNs uint64
maxLatencyNs uint64
}
func NewCollector() *Collector {
return &Collector{startedAt: time.Now()}
}
func (c *Collector) Observe(status int, latency time.Duration) {
atomic.AddUint64(&c.totalRequests, 1)
atomic.AddUint64(&c.totalLatencyNs, uint64(latency.Nanoseconds()))
if status >= 500 {
atomic.AddUint64(&c.serverErrors, 1)
} else if status >= 400 {
atomic.AddUint64(&c.clientErrors, 1)
}
latNs := uint64(latency.Nanoseconds())
for {
old := atomic.LoadUint64(&c.maxLatencyNs)
if latNs <= old {
break
}
if atomic.CompareAndSwapUint64(&c.maxLatencyNs, old, latNs) {
break
}
}
}
func (c *Collector) Snapshot() Snapshot {
now := time.Now()
total := atomic.LoadUint64(&c.totalRequests)
clientErr := atomic.LoadUint64(&c.clientErrors)
serverErr := atomic.LoadUint64(&c.serverErrors)
totalLatencyNs := atomic.LoadUint64(&c.totalLatencyNs)
maxLatencyNs := atomic.LoadUint64(&c.maxLatencyNs)
var clientRate float64
var serverRate float64
var avgLatencyMs float64
if total > 0 {
clientRate = float64(clientErr) * 100 / float64(total)
serverRate = float64(serverErr) * 100 / float64(total)
avgLatencyMs = float64(totalLatencyNs) / float64(total) / 1e6
}
return Snapshot{
GeneratedAtUTC: now.UTC().Format(time.RFC3339),
UptimeSeconds: int64(now.Sub(c.startedAt).Seconds()),
TotalRequests: total,
ClientErrors: clientErr,
ServerErrors: serverErr,
ClientErrorRatePct: clientRate,
ServerErrorRatePct: serverRate,
AvgLatencyMs: avgLatencyMs,
MaxLatencyMs: float64(maxLatencyNs) / 1e6,
}
}