package database import ( "context" "database/sql" "fmt" "time" "github.com/prometheus/client_golang/prometheus" ) // DatabaseMetrics holds the Prometheus metrics for database connection pool monitoring type DatabaseMetrics struct { ConnectionsOpen prometheus.Gauge ConnectionsIdle prometheus.Gauge ConnectionsInUse prometheus.Gauge ConnectionsWaitCount prometheus.Counter ConnectionsWaitDuration prometheus.Histogram } // NewDatabaseMetrics creates a new set of database metrics and registers them func NewDatabaseMetrics(registerer prometheus.Registerer) *DatabaseMetrics { metrics := &DatabaseMetrics{ ConnectionsOpen: prometheus.NewGauge(prometheus.GaugeOpts{ Name: "database_connections_open", Help: "Number of open database connections", }), ConnectionsIdle: prometheus.NewGauge(prometheus.GaugeOpts{ Name: "database_connections_idle", Help: "Number of idle database connections", }), ConnectionsInUse: prometheus.NewGauge(prometheus.GaugeOpts{ Name: "database_connections_in_use", Help: "Number of database connections in use", }), ConnectionsWaitCount: prometheus.NewCounter(prometheus.CounterOpts{ Name: "database_connections_wait_count_total", Help: "Total number of times a connection had to wait", }), ConnectionsWaitDuration: prometheus.NewHistogram(prometheus.HistogramOpts{ Name: "database_connections_wait_duration_seconds", Help: "Time spent waiting for a database connection", Buckets: prometheus.DefBuckets, }), } if registerer != nil { registerer.MustRegister( metrics.ConnectionsOpen, metrics.ConnectionsIdle, metrics.ConnectionsInUse, metrics.ConnectionsWaitCount, metrics.ConnectionsWaitDuration, ) } return metrics } // monitorConnectionPool runs a background goroutine to collect connection pool metrics func monitorConnectionPool(ctx context.Context, db *sql.DB, registerer prometheus.Registerer) { if registerer == nil { return // No metrics collection if no registerer provided } metrics := NewDatabaseMetrics(registerer) ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: stats := db.Stats() metrics.ConnectionsOpen.Set(float64(stats.OpenConnections)) metrics.ConnectionsIdle.Set(float64(stats.Idle)) metrics.ConnectionsInUse.Set(float64(stats.InUse)) metrics.ConnectionsWaitCount.Add(float64(stats.WaitCount)) if stats.WaitDuration > 0 { metrics.ConnectionsWaitDuration.Observe(stats.WaitDuration.Seconds()) } // Log connection pool stats for high usage or waiting if stats.OpenConnections > 20 || stats.WaitCount > 0 { fmt.Printf("Connection pool stats: open=%d idle=%d in_use=%d wait_count=%d wait_duration=%s\n", stats.OpenConnections, stats.Idle, stats.InUse, stats.WaitCount, stats.WaitDuration) } } } }