Files
CosmicClash/server/allocator/metrics.go
T
2026-09-01 18:43:55 +01:00

100 lines
2.7 KiB
Go

package allocator
import (
"fmt"
"io"
"net/http"
"sync"
)
// Metrics is a bounded allocator-role collector. Region is the only label so
// a bad request cannot create unbounded Prometheus cardinality.
type Metrics struct {
mu sync.Mutex
regions map[string]*allocationMetric
}
type allocationMetric struct {
attempts uint64
success uint64
failure uint64
denied uint64
}
func NewMetrics() *Metrics {
return &Metrics{regions: map[string]*allocationMetric{"EU": {}, "NA": {}}}
}
func (m *Metrics) ObserveAttempt(region string) {
if metric := m.metric(region); metric != nil {
m.mu.Lock()
metric.attempts++
m.mu.Unlock()
}
}
func (m *Metrics) ObserveSuccess(region string) {
if metric := m.metric(region); metric != nil {
m.mu.Lock()
metric.success++
m.mu.Unlock()
}
}
func (m *Metrics) ObserveFailure(region string) {
if metric := m.metric(region); metric != nil {
m.mu.Lock()
metric.failure++
m.mu.Unlock()
}
}
func (m *Metrics) ObserveDenied(region string) {
if metric := m.metric(region); metric != nil {
m.mu.Lock()
metric.denied++
m.mu.Unlock()
}
}
func (m *Metrics) metric(region string) *allocationMetric {
if m == nil || (region != "EU" && region != "NA") {
return nil
}
return m.regions[region]
}
func (m *Metrics) WritePrometheus(w io.Writer) error {
if m == nil {
return nil
}
m.mu.Lock()
defer m.mu.Unlock()
if _, err := io.WriteString(w, "# TYPE cosmic_clash_allocator_allocation_attempts_total counter\n# TYPE cosmic_clash_allocator_allocations_total counter\n# TYPE cosmic_clash_allocator_allocation_failures_total counter\n# TYPE cosmic_clash_allocator_quota_denials_total counter\n"); err != nil {
return err
}
for _, region := range []string{"EU", "NA"} {
metric := m.regions[region]
labels := fmt.Sprintf(`region="%s"`, region)
if _, err := fmt.Fprintf(w, "cosmic_clash_allocator_allocation_attempts_total{%s} %d\ncosmic_clash_allocator_allocations_total{%s} %d\ncosmic_clash_allocator_allocation_failures_total{%s} %d\ncosmic_clash_allocator_quota_denials_total{%s} %d\n", labels, metric.attempts, labels, metric.success, labels, metric.failure, labels, metric.denied); err != nil {
return err
}
}
return nil
}
// MetricsHandler exposes only the read-only Prometheus endpoint. The caller
// owns the listener and can bind it to a private metrics network.
func MetricsHandler(metrics *Metrics) http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "text/plain; version=0.0.4")
_ = metrics.WritePrometheus(w)
})
return mux
}