From a1222025e0651e826e59ba268fdd4a96882405fc Mon Sep 17 00:00:00 2001 From: nutcas3 Date: Fri, 17 Apr 2026 15:52:53 +0300 Subject: [PATCH] feat: Add Prometheus metrics collector with comprehensive telecom system monitoring - Add MetricsCollector with HTTP middleware, subscriber tracking, and system uptime monitoring - Add HTTP request metrics: total requests counter and duration histogram by method/endpoint/status - Add subscriber metrics: total, active, and suspended subscriber gauges - Add usage metrics: data bytes, voice seconds, and SMS count counters by subscriber - Add charging metrics: credit balance gauge and payment transaction counter by --- .../api-server/internal/metrics/prometheus.go | 333 ++++++++++++++++++ 1 file changed, 333 insertions(+) create mode 100644 apps/api-server/internal/metrics/prometheus.go diff --git a/apps/api-server/internal/metrics/prometheus.go b/apps/api-server/internal/metrics/prometheus.go new file mode 100644 index 0000000..d5681d4 --- /dev/null +++ b/apps/api-server/internal/metrics/prometheus.go @@ -0,0 +1,333 @@ +package metrics + +import ( + "context" + "net/http" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +var ( + // HTTP request metrics + httpRequestsTotal = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "telecom_http_requests_total", + Help: "Total number of HTTP requests", + }, + []string{"method", "endpoint", "status_code"}, + ) + + httpRequestDuration = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "telecom_http_request_duration_seconds", + Help: "HTTP request duration in seconds", + Buckets: prometheus.DefBuckets, + }, + []string{"method", "endpoint"}, + ) + + // Subscriber metrics + subscribersTotal = prometheus.NewGauge( + prometheus.GaugeOpts{ + Name: "telecom_subscribers_total", + Help: "Total number of subscribers", + }, + ) + + subscribersActive = prometheus.NewGauge( + prometheus.GaugeOpts{ + Name: "telecom_subscribers_active", + Help: "Number of active subscribers", + }, + ) + + subscribersSuspended = prometheus.NewGauge( + prometheus.GaugeOpts{ + Name: "telecom_subscribers_suspended", + Help: "Number of suspended subscribers", + }, + ) + + // Usage metrics + dataUsageBytes = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "telecom_data_usage_bytes_total", + Help: "Total data usage in bytes", + }, + []string{"subscriber_id", "direction"}, + ) + + voiceUsageSeconds = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "telecom_voice_usage_seconds_total", + Help: "Total voice usage in seconds", + }, + []string{"subscriber_id"}, + ) + + smsUsageTotal = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "telecom_sms_usage_total", + Help: "Total SMS count", + }, + []string{"subscriber_id"}, + ) + + // Charging metrics + creditBalance = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "telecom_credit_balance", + Help: "Subscriber credit balance", + }, + []string{"subscriber_id"}, + ) + + paymentTransactionsTotal = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "telecom_payment_transactions_total", + Help: "Total payment transactions", + }, + []string{"status", "gateway"}, + ) + + // System metrics + activeSessions = prometheus.NewGauge( + prometheus.GaugeOpts{ + Name: "telecom_active_sessions", + Help: "Number of active sessions", + }, + ) + + systemUptime = prometheus.NewGauge( + prometheus.GaugeOpts{ + Name: "telecom_system_uptime_seconds", + Help: "System uptime in seconds", + }, + ) + + // Database metrics + dbConnectionsActive = prometheus.NewGauge( + prometheus.GaugeOpts{ + Name: "telecom_db_connections_active", + Help: "Number of active database connections", + }, + ) + + dbQueryDuration = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "telecom_db_query_duration_seconds", + Help: "Database query duration in seconds", + Buckets: prometheus.DefBuckets, + }, + []string{"operation"}, + ) + + // Redis metrics + redisConnectionsActive = prometheus.NewGauge( + prometheus.GaugeOpts{ + Name: "telecom_redis_connections_active", + Help: "Number of active Redis connections", + }, + ) + + redisOperationsTotal = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "telecom_redis_operations_total", + Help: "Total Redis operations", + }, + []string{"operation", "status"}, + ) + + // Chaos engineering metrics + chaosExperimentsTotal = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "telecom_chaos_experiments_total", + Help: "Total chaos experiments run", + }, + []string{"type", "status"}, + ) + + // eBPF packet gateway metrics + packetsProcessed = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "telecom_packets_processed_total", + Help: "Total packets processed by eBPF gateway", + }, + []string{"action"}, + ) + + packetsDropped = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "telecom_packets_dropped_total", + Help: "Total packets dropped by eBPF gateway", + }, + []string{"reason"}, + ) +) + +// MetricsCollector handles Prometheus metrics collection +type MetricsCollector struct { + startTime time.Time +} + +// NewMetricsCollector creates a new metrics collector +func NewMetricsCollector() *MetricsCollector { + return &MetricsCollector{ + startTime: time.Now(), + } +} + +// Register registers all metrics with Prometheus +func (mc *MetricsCollector) Register() { + prometheus.MustRegister(httpRequestsTotal) + prometheus.MustRegister(httpRequestDuration) + prometheus.MustRegister(subscribersTotal) + prometheus.MustRegister(subscribersActive) + prometheus.MustRegister(subscribersSuspended) + prometheus.MustRegister(dataUsageBytes) + prometheus.MustRegister(voiceUsageSeconds) + prometheus.MustRegister(smsUsageTotal) + prometheus.MustRegister(creditBalance) + prometheus.MustRegister(paymentTransactionsTotal) + prometheus.MustRegister(activeSessions) + prometheus.MustRegister(systemUptime) + prometheus.MustRegister(dbConnectionsActive) + prometheus.MustRegister(dbQueryDuration) + prometheus.MustRegister(redisConnectionsActive) + prometheus.MustRegister(redisOperationsTotal) + prometheus.MustRegister(chaosExperimentsTotal) + prometheus.MustRegister(packetsProcessed) + prometheus.MustRegister(packetsDropped) + + // Start system uptime goroutine + go mc.updateSystemMetrics() +} + +// updateSystemMetrics updates system-level metrics periodically +func (mc *MetricsCollector) updateSystemMetrics() { + ticker := time.NewTicker(10 * time.Second) + defer ticker.Stop() + + for range ticker.C { + systemUptime.Set(time.Since(mc.startTime).Seconds()) + } +} + +// HTTPMiddleware returns middleware to track HTTP metrics +func (mc *MetricsCollector) HTTPMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + + // Create a response writer to capture status code + rw := &responseWriter{ResponseWriter: w, statusCode: 200} + + // Process request + next.ServeHTTP(rw, r) + + // Record metrics + duration := time.Since(start).Seconds() + statusCode := rw.statusCode + + httpRequestsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode)).Inc() + httpRequestDuration.WithLabelValues(r.Method, r.URL.Path).Observe(duration) + }) +} + +// responseWriter is a wrapper to capture status code +type responseWriter struct { + http.ResponseWriter + statusCode int +} + +func (rw *responseWriter) WriteHeader(code int) { + rw.statusCode = code + rw.ResponseWriter.WriteHeader(code) +} + +// UpdateSubscriberMetrics updates subscriber-related metrics +func (mc *MetricsCollector) UpdateSubscriberMetrics(total, active, suspended int) { + subscribersTotal.Set(float64(total)) + subscribersActive.Set(float64(active)) + subscribersSuspended.Set(float64(suspended)) +} + +// RecordDataUsage records data usage for a subscriber +func (mc *MetricsCollector) RecordDataUsage(subscriberID string, direction string, bytes int64) { + dataUsageBytes.WithLabelValues(subscriberID, direction).Add(float64(bytes)) +} + +// RecordVoiceUsage records voice usage for a subscriber +func (mc *MetricsCollector) RecordVoiceUsage(subscriberID string, seconds int64) { + voiceUsageSeconds.WithLabelValues(subscriberID).Add(float64(seconds)) +} + +// RecordSMSUsage records SMS usage for a subscriber +func (mc *MetricsCollector) RecordSMSUsage(subscriberID string, count int64) { + smsUsageTotal.WithLabelValues(subscriberID).Add(float64(count)) +} + +// UpdateCreditBalance updates credit balance for a subscriber +func (mc *MetricsCollector) UpdateCreditBalance(subscriberID string, balance float64) { + creditBalance.WithLabelValues(subscriberID).Set(balance) +} + +// RecordPaymentTransaction records a payment transaction +func (mc *MetricsCollector) RecordPaymentTransaction(status, gateway string) { + paymentTransactionsTotal.WithLabelValues(status, gateway).Inc() +} + +// UpdateActiveSessions updates the active sessions count +func (mc *MetricsCollector) UpdateActiveSessions(count int) { + activeSessions.Set(float64(count)) +} + +// UpdateDBMetrics updates database metrics +func (mc *MetricsCollector) UpdateDBMetrics(activeConnections int, operation string, duration time.Duration) { + dbConnectionsActive.Set(float64(activeConnections)) + dbQueryDuration.WithLabelValues(operation).Observe(duration.Seconds()) +} + +// UpdateRedisMetrics updates Redis metrics +func (mc *MetricsCollector) UpdateRedisMetrics(activeConnections int, operation, status string) { + redisConnectionsActive.Set(float64(activeConnections)) + redisOperationsTotal.WithLabelValues(operation, status).Inc() +} + +// RecordChaosExperiment records a chaos experiment +func (mc *MetricsCollector) RecordChaosExperiment(experimentType, status string) { + chaosExperimentsTotal.WithLabelValues(experimentType, status).Inc() +} + +// RecordPacketMetrics records packet processing metrics +func (mc *MetricsCollector) RecordPacketMetrics(action string, count int64, reason string) { + if action == "dropped" { + packetsDropped.WithLabelValues(reason).Add(float64(count)) + } else { + packetsProcessed.WithLabelValues(action).Add(float64(count)) + } +} + +// Handler returns the Prometheus metrics HTTP handler +func (mc *MetricsCollector) Handler() http.Handler { + return promhttp.Handler() +} + +// StartMetricsServer starts the Prometheus metrics server +func (mc *MetricsCollector) StartMetricsServer(ctx context.Context, addr string) error { + mux := http.NewServeMux() + mux.Handle("/metrics", mc.Handler()) + + server := &http.Server{ + Addr: addr, + Handler: mux, + } + + go func() { + <-ctx.Done() + server.Shutdown(context.Background()) + }() + + return server.ListenAndServe() +}