Files
multica/server/internal/metrics/realtime.go
devv-eve f864a07bd5 feat: add server Prometheus metrics endpoint
Add Prometheus metrics endpoint with local-bind listener support and baseline metrics collectors.
2026-04-28 14:29:01 +08:00

102 lines
4.9 KiB
Go

package metrics
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/multica-ai/multica/server/internal/realtime"
)
type RealtimeCollector struct {
metrics *realtime.Metrics
connectsTotal *prometheus.Desc
disconnectsTotal *prometheus.Desc
activeConnections *prometheus.Desc
slowEvictionsTotal *prometheus.Desc
messagesSentTotal *prometheus.Desc
messagesDropped *prometheus.Desc
redisConnected *prometheus.Desc
redisXAddTotal *prometheus.Desc
redisXAddErrors *prometheus.Desc
redisXReadTotal *prometheus.Desc
redisXReadErrors *prometheus.Desc
redisAckTotal *prometheus.Desc
redisMirrorErrors *prometheus.Desc
redisMirrorDiverged *prometheus.Desc
}
func NewRealtimeCollector(m *realtime.Metrics) *RealtimeCollector {
return &RealtimeCollector{
metrics: m,
connectsTotal: newRealtimeDesc("connects_total", "Total realtime WebSocket connections opened."),
disconnectsTotal: newRealtimeDesc("disconnects_total", "Total realtime WebSocket connections closed."),
activeConnections: newRealtimeDesc("active_connections", "Current realtime WebSocket connections."),
slowEvictionsTotal: newRealtimeDesc("slow_evictions_total", "Total realtime clients evicted for slow consumption."),
messagesSentTotal: newRealtimeDesc("messages_sent_total", "Total realtime messages sent."),
messagesDropped: newRealtimeDesc("messages_dropped_total", "Total realtime messages dropped."),
redisConnected: newRealtimeDesc("redis_connected", "Whether the realtime Redis relay is connected."),
redisXAddTotal: newRealtimeDesc("redis_xadd_total", "Total Redis XADD operations by the realtime relay."),
redisXAddErrors: newRealtimeDesc("redis_xadd_errors_total", "Total Redis XADD errors by the realtime relay."),
redisXReadTotal: newRealtimeDesc("redis_xread_total", "Total Redis XREAD operations by the realtime relay."),
redisXReadErrors: newRealtimeDesc("redis_xread_errors_total", "Total Redis XREAD errors by the realtime relay."),
redisAckTotal: newRealtimeDesc("redis_ack_total", "Total Redis stream acknowledgements by the realtime relay."),
redisMirrorErrors: prometheus.NewDesc("multica_realtime_redis_mirror_errors_total", "Total Redis mirror write errors by the realtime relay.", []string{"target"}, nil),
redisMirrorDiverged: newRealtimeDesc("redis_mirror_divergence_total", "Total Redis mirror divergence events by the realtime relay."),
}
}
func newRealtimeDesc(name, help string) *prometheus.Desc {
return prometheus.NewDesc("multica_realtime_"+name, help, nil, nil)
}
func (c *RealtimeCollector) Describe(ch chan<- *prometheus.Desc) {
for _, desc := range []*prometheus.Desc{
c.connectsTotal,
c.disconnectsTotal,
c.activeConnections,
c.slowEvictionsTotal,
c.messagesSentTotal,
c.messagesDropped,
c.redisConnected,
c.redisXAddTotal,
c.redisXAddErrors,
c.redisXReadTotal,
c.redisXReadErrors,
c.redisAckTotal,
c.redisMirrorErrors,
c.redisMirrorDiverged,
} {
ch <- desc
}
}
func (c *RealtimeCollector) Collect(ch chan<- prometheus.Metric) {
if c.metrics == nil {
return
}
m := c.metrics
ch <- prometheus.MustNewConstMetric(c.connectsTotal, prometheus.CounterValue, float64(m.ConnectsTotal.Load()))
ch <- prometheus.MustNewConstMetric(c.disconnectsTotal, prometheus.CounterValue, float64(m.DisconnectsTotal.Load()))
ch <- prometheus.MustNewConstMetric(c.activeConnections, prometheus.GaugeValue, float64(m.ActiveConnections.Load()))
ch <- prometheus.MustNewConstMetric(c.slowEvictionsTotal, prometheus.CounterValue, float64(m.SlowEvictionsTotal.Load()))
ch <- prometheus.MustNewConstMetric(c.messagesSentTotal, prometheus.CounterValue, float64(m.MessagesSentTotal.Load()))
ch <- prometheus.MustNewConstMetric(c.messagesDropped, prometheus.CounterValue, float64(m.MessagesDroppedTotal.Load()))
ch <- prometheus.MustNewConstMetric(c.redisConnected, prometheus.GaugeValue, boolFloat(m.RedisConnected.Load()))
ch <- prometheus.MustNewConstMetric(c.redisXAddTotal, prometheus.CounterValue, float64(m.RedisXAddTotal.Load()))
ch <- prometheus.MustNewConstMetric(c.redisXAddErrors, prometheus.CounterValue, float64(m.RedisXAddErrors.Load()))
ch <- prometheus.MustNewConstMetric(c.redisXReadTotal, prometheus.CounterValue, float64(m.RedisXReadTotal.Load()))
ch <- prometheus.MustNewConstMetric(c.redisXReadErrors, prometheus.CounterValue, float64(m.RedisXReadErrors.Load()))
ch <- prometheus.MustNewConstMetric(c.redisAckTotal, prometheus.CounterValue, float64(m.RedisAckTotal.Load()))
ch <- prometheus.MustNewConstMetric(c.redisMirrorErrors, prometheus.CounterValue, float64(m.RedisMirrorPrimaryErrors.Load()), "primary")
ch <- prometheus.MustNewConstMetric(c.redisMirrorErrors, prometheus.CounterValue, float64(m.RedisMirrorSecondaryErrors.Load()), "secondary")
ch <- prometheus.MustNewConstMetric(c.redisMirrorDiverged, prometheus.CounterValue, float64(m.RedisMirrorDivergenceTotal.Load()))
}
func boolFloat(v bool) float64 {
if v {
return 1
}
return 0
}