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 }