Files
aether/metrics.go
Hugo Nijhuis e3dbe3d52d
All checks were successful
CI / build (push) Successful in 19s
[Issue #22] Add EventBroadcaster metrics (#49)
2026-01-10 18:52:32 +00:00

259 lines
7.4 KiB
Go

package aether
import (
"sync"
"sync/atomic"
)
// BroadcasterMetrics provides observability metrics for EventBroadcaster implementations.
// All methods are safe for concurrent use.
type BroadcasterMetrics interface {
// EventsPublished returns the total number of events published per namespace.
EventsPublished(namespaceID string) int64
// EventsReceived returns the total number of events received per namespace.
// For EventBus this equals events delivered to subscribers.
// For NATSEventBus this includes events received from NATS.
EventsReceived(namespaceID string) int64
// ActiveSubscriptions returns the current number of active subscriptions per namespace.
ActiveSubscriptions(namespaceID string) int64
// TotalActiveSubscriptions returns the total number of active subscriptions across all namespaces.
TotalActiveSubscriptions() int64
// PublishErrors returns the total number of publish errors per namespace.
PublishErrors(namespaceID string) int64
// SubscribeErrors returns the total number of subscribe errors per namespace.
SubscribeErrors(namespaceID string) int64
// DroppedEvents returns the total number of events dropped (e.g., full channel) per namespace.
DroppedEvents(namespaceID string) int64
// Namespaces returns a list of all namespaces that have metrics.
Namespaces() []string
// Reset resets all metrics. Useful for testing.
Reset()
}
// MetricsCollector provides methods for collecting metrics.
// This interface is implemented internally and used by EventBus implementations.
type MetricsCollector interface {
BroadcasterMetrics
// RecordPublish records a successful publish event.
RecordPublish(namespaceID string)
// RecordReceive records a received event.
RecordReceive(namespaceID string)
// RecordSubscribe records a new subscription.
RecordSubscribe(namespaceID string)
// RecordUnsubscribe records a removed subscription.
RecordUnsubscribe(namespaceID string)
// RecordPublishError records a publish error.
RecordPublishError(namespaceID string)
// RecordSubscribeError records a subscribe error.
RecordSubscribeError(namespaceID string)
// RecordDroppedEvent records a dropped event (e.g., channel full).
RecordDroppedEvent(namespaceID string)
}
// namespaceMetrics holds counters for a single namespace.
type namespaceMetrics struct {
eventsPublished int64
eventsReceived int64
activeSubscriptions int64
publishErrors int64
subscribeErrors int64
droppedEvents int64
}
// DefaultMetricsCollector is the default implementation of MetricsCollector.
// It uses atomic operations for thread-safe counter updates.
type DefaultMetricsCollector struct {
mu sync.RWMutex
namespaces map[string]*namespaceMetrics
}
// NewMetricsCollector creates a new DefaultMetricsCollector.
func NewMetricsCollector() *DefaultMetricsCollector {
return &DefaultMetricsCollector{
namespaces: make(map[string]*namespaceMetrics),
}
}
// getOrCreateNamespace returns metrics for a namespace, creating if needed.
func (m *DefaultMetricsCollector) getOrCreateNamespace(namespaceID string) *namespaceMetrics {
m.mu.RLock()
ns, exists := m.namespaces[namespaceID]
m.mu.RUnlock()
if exists {
return ns
}
m.mu.Lock()
defer m.mu.Unlock()
// Double-check after acquiring write lock
if ns, exists = m.namespaces[namespaceID]; exists {
return ns
}
ns = &namespaceMetrics{}
m.namespaces[namespaceID] = ns
return ns
}
// EventsPublished returns the total number of events published for a namespace.
func (m *DefaultMetricsCollector) EventsPublished(namespaceID string) int64 {
m.mu.RLock()
ns, exists := m.namespaces[namespaceID]
m.mu.RUnlock()
if !exists {
return 0
}
return atomic.LoadInt64(&ns.eventsPublished)
}
// EventsReceived returns the total number of events received for a namespace.
func (m *DefaultMetricsCollector) EventsReceived(namespaceID string) int64 {
m.mu.RLock()
ns, exists := m.namespaces[namespaceID]
m.mu.RUnlock()
if !exists {
return 0
}
return atomic.LoadInt64(&ns.eventsReceived)
}
// ActiveSubscriptions returns the current number of active subscriptions for a namespace.
func (m *DefaultMetricsCollector) ActiveSubscriptions(namespaceID string) int64 {
m.mu.RLock()
ns, exists := m.namespaces[namespaceID]
m.mu.RUnlock()
if !exists {
return 0
}
return atomic.LoadInt64(&ns.activeSubscriptions)
}
// TotalActiveSubscriptions returns the total number of active subscriptions across all namespaces.
func (m *DefaultMetricsCollector) TotalActiveSubscriptions() int64 {
m.mu.RLock()
defer m.mu.RUnlock()
var total int64
for _, ns := range m.namespaces {
total += atomic.LoadInt64(&ns.activeSubscriptions)
}
return total
}
// PublishErrors returns the total number of publish errors for a namespace.
func (m *DefaultMetricsCollector) PublishErrors(namespaceID string) int64 {
m.mu.RLock()
ns, exists := m.namespaces[namespaceID]
m.mu.RUnlock()
if !exists {
return 0
}
return atomic.LoadInt64(&ns.publishErrors)
}
// SubscribeErrors returns the total number of subscribe errors for a namespace.
func (m *DefaultMetricsCollector) SubscribeErrors(namespaceID string) int64 {
m.mu.RLock()
ns, exists := m.namespaces[namespaceID]
m.mu.RUnlock()
if !exists {
return 0
}
return atomic.LoadInt64(&ns.subscribeErrors)
}
// DroppedEvents returns the total number of dropped events for a namespace.
func (m *DefaultMetricsCollector) DroppedEvents(namespaceID string) int64 {
m.mu.RLock()
ns, exists := m.namespaces[namespaceID]
m.mu.RUnlock()
if !exists {
return 0
}
return atomic.LoadInt64(&ns.droppedEvents)
}
// Namespaces returns a list of all namespaces that have metrics.
func (m *DefaultMetricsCollector) Namespaces() []string {
m.mu.RLock()
defer m.mu.RUnlock()
namespaces := make([]string, 0, len(m.namespaces))
for ns := range m.namespaces {
namespaces = append(namespaces, ns)
}
return namespaces
}
// Reset resets all metrics.
func (m *DefaultMetricsCollector) Reset() {
m.mu.Lock()
defer m.mu.Unlock()
m.namespaces = make(map[string]*namespaceMetrics)
}
// RecordPublish records a successful publish event.
func (m *DefaultMetricsCollector) RecordPublish(namespaceID string) {
ns := m.getOrCreateNamespace(namespaceID)
atomic.AddInt64(&ns.eventsPublished, 1)
}
// RecordReceive records a received event.
func (m *DefaultMetricsCollector) RecordReceive(namespaceID string) {
ns := m.getOrCreateNamespace(namespaceID)
atomic.AddInt64(&ns.eventsReceived, 1)
}
// RecordSubscribe records a new subscription.
func (m *DefaultMetricsCollector) RecordSubscribe(namespaceID string) {
ns := m.getOrCreateNamespace(namespaceID)
atomic.AddInt64(&ns.activeSubscriptions, 1)
}
// RecordUnsubscribe records a removed subscription.
func (m *DefaultMetricsCollector) RecordUnsubscribe(namespaceID string) {
ns := m.getOrCreateNamespace(namespaceID)
atomic.AddInt64(&ns.activeSubscriptions, -1)
}
// RecordPublishError records a publish error.
func (m *DefaultMetricsCollector) RecordPublishError(namespaceID string) {
ns := m.getOrCreateNamespace(namespaceID)
atomic.AddInt64(&ns.publishErrors, 1)
}
// RecordSubscribeError records a subscribe error.
func (m *DefaultMetricsCollector) RecordSubscribeError(namespaceID string) {
ns := m.getOrCreateNamespace(namespaceID)
atomic.AddInt64(&ns.subscribeErrors, 1)
}
// RecordDroppedEvent records a dropped event.
func (m *DefaultMetricsCollector) RecordDroppedEvent(namespaceID string) {
ns := m.getOrCreateNamespace(namespaceID)
atomic.AddInt64(&ns.droppedEvents, 1)
}