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) }