blob: cef4631e238bb97e239abac2c3a0bdba4b593ef9 [file] [log] [blame]
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
}