| package collector |
| |
| import ( |
| "context" |
| "fmt" |
| "sync" |
| |
| "github.com/jbub/pgbouncer_exporter/config" |
| "github.com/jbub/pgbouncer_exporter/domain" |
| |
| "github.com/prometheus/client_golang/prometheus" |
| "github.com/prometheus/common/log" |
| ) |
| |
| const ( |
| // Name is the name of the exporter. |
| Name = "pgbouncer_exporter" |
| ) |
| |
| // Names of the exporter subsystems. |
| const ( |
| SubsystemStats = "stats" |
| SubsystemPools = "pools" |
| SubsystemDatabases = "database" |
| SubsystemLists = "lists" |
| ) |
| |
| type gaugeVecItem struct { |
| gaugeVec *prometheus.GaugeVec |
| resolve resolveGaugeVecFunc |
| enabled bool |
| } |
| |
| type gaugeVecValueItem struct { |
| labels []string |
| count float64 |
| } |
| |
| type resolveGaugeVecFunc func(res *storeResult) []gaugeVecValueItem |
| |
| type storeResult struct { |
| stats []domain.Stat |
| pools []domain.Pool |
| databases []domain.Database |
| lists []domain.List |
| } |
| |
| // Exporter represents pgbouncer prometheus stats exporter. |
| type Exporter struct { |
| cfg config.Config |
| st domain.Store |
| mutex sync.RWMutex // guards Collect |
| gaugeVecItems []gaugeVecItem |
| } |
| |
| // New returns new Exporter. |
| func New(cfg config.Config, st domain.Store) *Exporter { |
| return &Exporter{ |
| st: st, |
| cfg: cfg, |
| gaugeVecItems: []gaugeVecItem{ |
| { |
| enabled: cfg.ExportStats, |
| gaugeVec: prometheus.NewGaugeVec(prometheus.GaugeOpts{ |
| Namespace: Name, |
| Subsystem: SubsystemStats, |
| Name: "total_requests", |
| Help: "Total number of SQL requests pooled by pgbouncer.", |
| }, []string{"database"}), |
| resolve: func(res *storeResult) (items []gaugeVecValueItem) { |
| for _, stat := range res.stats { |
| items = append(items, gaugeVecValueItem{ |
| labels: []string{stat.Database}, |
| count: float64(stat.TotalRequests), |
| }) |
| } |
| return items |
| }, |
| }, |
| { |
| enabled: cfg.ExportStats, |
| gaugeVec: prometheus.NewGaugeVec(prometheus.GaugeOpts{ |
| Namespace: Name, |
| Subsystem: SubsystemStats, |
| Name: "total_received", |
| Help: "Total number of SQL requests pooled by pgbouncer.", |
| }, []string{"database"}), |
| resolve: func(res *storeResult) (items []gaugeVecValueItem) { |
| for _, stat := range res.stats { |
| items = append(items, gaugeVecValueItem{ |
| labels: []string{stat.Database}, |
| count: float64(stat.TotalReceived), |
| }) |
| } |
| return items |
| }, |
| }, |
| { |
| enabled: cfg.ExportStats, |
| gaugeVec: prometheus.NewGaugeVec(prometheus.GaugeOpts{ |
| Namespace: Name, |
| Subsystem: SubsystemStats, |
| Name: "total_sent", |
| Help: "Total volume in bytes of network traffic sent by pgbouncer.", |
| }, []string{"database"}), |
| resolve: func(res *storeResult) (items []gaugeVecValueItem) { |
| for _, stat := range res.stats { |
| items = append(items, gaugeVecValueItem{ |
| labels: []string{stat.Database}, |
| count: float64(stat.TotalSent), |
| }) |
| } |
| return items |
| }, |
| }, |
| { |
| enabled: cfg.ExportStats, |
| gaugeVec: prometheus.NewGaugeVec(prometheus.GaugeOpts{ |
| Namespace: Name, |
| Subsystem: SubsystemStats, |
| Name: "total_query_time", |
| Help: "Total number of microseconds spent by pgbouncer when actively connected to PostgreSQL.", |
| }, []string{"database"}), |
| resolve: func(res *storeResult) (items []gaugeVecValueItem) { |
| for _, stat := range res.stats { |
| items = append(items, gaugeVecValueItem{ |
| labels: []string{stat.Database}, |
| count: float64(stat.TotalQueryTime), |
| }) |
| } |
| return items |
| }, |
| }, |
| { |
| enabled: cfg.ExportPools, |
| gaugeVec: prometheus.NewGaugeVec(prometheus.GaugeOpts{ |
| Namespace: Name, |
| Subsystem: SubsystemPools, |
| Name: "active_clients", |
| Help: "Client connections that are linked to server connection and can process queries.", |
| }, []string{"database", "user", "pool_mode"}), |
| resolve: func(res *storeResult) (items []gaugeVecValueItem) { |
| for _, pool := range res.pools { |
| items = append(items, gaugeVecValueItem{ |
| labels: []string{pool.Database, pool.User, pool.PoolMode}, |
| count: float64(pool.Active), |
| }) |
| } |
| return items |
| }, |
| }, |
| { |
| enabled: cfg.ExportPools, |
| gaugeVec: prometheus.NewGaugeVec(prometheus.GaugeOpts{ |
| Namespace: Name, |
| Subsystem: SubsystemPools, |
| Name: "waiting_clients", |
| Help: "Client connections have sent queries but have not yet got a server connection.", |
| }, []string{"database", "user", "pool_mode"}), |
| resolve: func(res *storeResult) (items []gaugeVecValueItem) { |
| for _, pool := range res.pools { |
| items = append(items, gaugeVecValueItem{ |
| labels: []string{pool.Database, pool.User, pool.PoolMode}, |
| count: float64(pool.Waiting), |
| }) |
| } |
| return items |
| }, |
| }, |
| { |
| enabled: cfg.ExportDatabases, |
| gaugeVec: prometheus.NewGaugeVec(prometheus.GaugeOpts{ |
| Namespace: Name, |
| Subsystem: SubsystemDatabases, |
| Name: "current_connections", |
| Help: "Current number of connections.", |
| }, []string{"name", "pool_mode"}), |
| resolve: func(res *storeResult) (items []gaugeVecValueItem) { |
| for _, database := range res.databases { |
| items = append(items, gaugeVecValueItem{ |
| labels: []string{database.Name, database.PoolMode}, |
| count: float64(database.CurrentConnections), |
| }) |
| } |
| return items |
| }, |
| }, |
| { |
| enabled: cfg.ExportLists, |
| gaugeVec: prometheus.NewGaugeVec(prometheus.GaugeOpts{ |
| Namespace: Name, |
| Subsystem: SubsystemLists, |
| Name: "items", |
| Help: "List of internal pgbouncer information.", |
| }, []string{"list"}), |
| resolve: func(res *storeResult) (items []gaugeVecValueItem) { |
| for _, list := range res.lists { |
| items = append(items, gaugeVecValueItem{ |
| labels: []string{list.List}, |
| count: float64(list.Items), |
| }) |
| } |
| return items |
| }, |
| }, |
| }, |
| } |
| } |
| |
| // Describe implements prometheus Collector.Describe. |
| func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { |
| for _, gaugeVecItem := range e.gaugeVecItems { |
| if gaugeVecItem.enabled { |
| gaugeVecItem.gaugeVec.Describe(ch) |
| } |
| } |
| } |
| |
| // Collect implements prometheus Collector.Collect. |
| func (e *Exporter) Collect(ch chan<- prometheus.Metric) { |
| e.mutex.Lock() |
| defer e.mutex.Unlock() |
| |
| ctx, cancel := context.WithTimeout(context.Background(), e.cfg.StoreTimeout) |
| defer cancel() |
| |
| res, err := e.getStoreResult(ctx) |
| if err != nil { |
| log.Errorf("unable to get store result: %v", err) |
| return |
| } |
| |
| for _, gaugeVecItem := range e.gaugeVecItems { |
| if !gaugeVecItem.enabled { |
| continue |
| } |
| |
| items := gaugeVecItem.resolve(res) |
| for _, item := range items { |
| gaugeVecItem.gaugeVec.WithLabelValues(item.labels...).Set(item.count) |
| } |
| |
| gaugeVecItem.gaugeVec.Collect(ch) |
| } |
| } |
| |
| func (e *Exporter) getStoreResult(ctx context.Context) (*storeResult, error) { |
| res := new(storeResult) |
| |
| if e.cfg.ExportStats { |
| stats, err := e.st.GetStats(ctx) |
| if err != nil { |
| return nil, fmt.Errorf("unable to get stats: %v", err) |
| } |
| res.stats = stats |
| } |
| |
| if e.cfg.ExportPools { |
| pools, err := e.st.GetPools(ctx) |
| if err != nil { |
| return nil, fmt.Errorf("unable to get pools: %v", err) |
| } |
| res.pools = pools |
| } |
| |
| if e.cfg.ExportDatabases { |
| databases, err := e.st.GetDatabases(ctx) |
| if err != nil { |
| return nil, fmt.Errorf("unable to get databases: %v", err) |
| } |
| res.databases = databases |
| } |
| |
| if e.cfg.ExportLists { |
| lists, err := e.st.GetLists(ctx) |
| if err != nil { |
| return nil, fmt.Errorf("unable to get lists: %v", err) |
| } |
| res.lists = lists |
| } |
| |
| return res, nil |
| } |