Add EventBroadcaster metrics for observability and debugging
All checks were successful
CI / build (pull_request) Successful in 38s
All checks were successful
CI / build (pull_request) Successful in 38s
- Add BroadcasterMetrics interface for reading metrics per namespace - Add MetricsCollector interface and DefaultMetricsCollector implementation - Track events_published and events_received counters per namespace - Track active_subscriptions gauge per namespace - Track publish_errors, subscribe_errors, and dropped_events counters - Add MetricsProvider interface for EventBroadcaster implementations - Integrate metrics tracking into EventBus and NATSEventBus - Add optional Prometheus integration via PrometheusMetricsAdapter - Add comprehensive unit tests for metrics functionality Closes #22 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
258
metrics.go
Normal file
258
metrics.go
Normal file
@@ -0,0 +1,258 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user