1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

View file

@ -0,0 +1,137 @@
# PgBouncer Input Plugin
The `pgbouncer` plugin provides metrics for your PgBouncer load balancer.
More information about the meaning of these metrics can be found in the
[PgBouncer Documentation](https://pgbouncer.github.io/usage.html).
- PgBouncer minimum tested version: 1.5
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
In addition to the plugin-specific configuration settings, plugins support
additional global and plugin configuration settings. These settings are used to
modify metrics, tags, and field or create aliases and configure ordering, etc.
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
## Configuration
```toml @sample.conf
# Read metrics from one or many pgbouncer servers
[[inputs.pgbouncer]]
## specify address via a url matching:
## postgres://[pqgotest[:password]]@host:port[/dbname]\
## ?sslmode=[disable|verify-ca|verify-full]
## or a simple string:
## host=localhost port=5432 user=pqgotest password=... sslmode=... dbname=app_production
##
## All connection parameters are optional.
##
address = "host=localhost user=pgbouncer sslmode=disable"
## Specify which "show" commands to gather metrics for.
## Choose from: "stats", "pools", "lists", "databases"
# show_commands = ["stats", "pools"]
```
### `address`
Specify address via a postgresql connection string:
```text
host=/run/postgresql port=6432 user=telegraf database=pgbouncer
```
Or via an url matching:
```text
postgres://[pqgotest[:password]]@host:port[/dbname]?sslmode=[disable|verify-ca|verify-full]
```
All connection parameters are optional.
Without the dbname parameter, the driver will default to a database with the
same name as the user. This dbname is just for instantiating a connection with
the server and doesn't restrict the databases we are trying to grab metrics for.
## Metrics
- pgbouncer
- tags:
- db
- server
- fields:
- avg_query_count
- avg_query_time
- avg_wait_time
- avg_xact_count
- avg_xact_time
- total_query_count
- total_query_time
- total_received
- total_sent
- total_wait_time
- total_xact_count
- total_xact_time
- pgbouncer_pools
- tags:
- db
- pool_mode
- server
- user
- fields:
- cl_active
- cl_waiting
- maxwait
- maxwait_us
- sv_active
- sv_idle
- sv_login
- sv_tested
- sv_used
- pgbouncer_lists
- tags:
- db
- server
- user
- fields:
- databases
- users
- pools
- free_clients
- used_clients
- login_clients
- free_servers
- used_servers
- dns_names
- dns_zones
- dns_queries
- pgbouncer_databases
- tags:
- db
- pg_dbname
- server
- user
- fields:
- current_connections
- pool_size
- min_pool_size
- reserve_pool
- max_connections
- paused
- disabled
## Example Output
```text
pgbouncer,db=pgbouncer,server=host\=debian-buster-postgres\ user\=dbn\ port\=6432\ dbname\=pgbouncer\ avg_query_count=0i,avg_query_time=0i,avg_wait_time=0i,avg_xact_count=0i,avg_xact_time=0i,total_query_count=26i,total_query_time=0i,total_received=0i,total_sent=0i,total_wait_time=0i,total_xact_count=26i,total_xact_time=0i 1581569936000000000
pgbouncer_pools,db=pgbouncer,pool_mode=statement,server=host\=debian-buster-postgres\ user\=dbn\ port\=6432\ dbname\=pgbouncer\ ,user=pgbouncer cl_active=1i,cl_waiting=0i,maxwait=0i,maxwait_us=0i,sv_active=0i,sv_idle=0i,sv_login=0i,sv_tested=0i,sv_used=0i 1581569936000000000
pgbouncer_lists,db=pgbouncer,server=host\=debian-buster-postgres\ user\=dbn\ port\=6432\ dbname\=pgbouncer\ ,user=pgbouncer databases=1i,dns_names=0i,dns_queries=0i,dns_zones=0i,free_clients=47i,free_servers=0i,login_clients=0i,pools=1i,used_clients=3i,used_servers=0i,users=4i 1581569936000000000
pgbouncer_databases,db=pgbouncer,pg_dbname=pgbouncer,server=host\=debian-buster-postgres\ user\=dbn\ port\=6432\ dbname\=pgbouncer\ name=pgbouncer disabled=0i,pool_size=2i,current_connections=0i,min_pool_size=0i,reserve_pool=0i,max_connections=0i,paused=0i 1581569936000000000
pgbouncer_databases,db=postgres,pg_dbname=postgres,server=host\=debian-buster-postgres\ user\=dbn\ port\=6432\ dbname\=pgbouncer\ name=postgres current_connections=0i,disabled=0i,pool_size=20i,min_pool_size=0i,reserve_pool=0i,paused=0i,max_connections=0i 1581569936000000000
```

View file

@ -0,0 +1,317 @@
//go:generate ../../../tools/readme_config_includer/generator
package pgbouncer
import (
"bytes"
"database/sql"
_ "embed"
"fmt"
"strconv"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/common/postgresql"
"github.com/influxdata/telegraf/plugins/inputs"
)
//go:embed sample.conf
var sampleConfig string
var ignoredColumns = map[string]bool{"user": true, "database": true, "pool_mode": true,
"avg_req": true, "avg_recv": true, "avg_sent": true, "avg_query": true,
"force_user": true, "host": true, "port": true, "name": true,
}
type PgBouncer struct {
ShowCommands []string `toml:"show_commands"`
postgresql.Config
service *postgresql.Service
}
func (*PgBouncer) SampleConfig() string {
return sampleConfig
}
func (p *PgBouncer) Init() error {
// Set defaults and check settings
if len(p.ShowCommands) == 0 {
p.ShowCommands = []string{"stats", "pools"}
}
for _, cmd := range p.ShowCommands {
switch cmd {
case "stats", "pools", "lists", "databases":
default:
return fmt.Errorf("invalid setting %q for 'show_command'", cmd)
}
}
// Create a postgres service for the queries
service, err := p.Config.CreateService()
if err != nil {
return err
}
p.service = service
return nil
}
func (p *PgBouncer) Start(_ telegraf.Accumulator) error {
return p.service.Start()
}
func (p *PgBouncer) Gather(acc telegraf.Accumulator) error {
for _, cmd := range p.ShowCommands {
switch cmd {
case "stats":
if err := p.showStats(acc); err != nil {
return err
}
case "pools":
if err := p.showPools(acc); err != nil {
return err
}
case "lists":
if err := p.showLists(acc); err != nil {
return err
}
case "databases":
if err := p.showDatabase(acc); err != nil {
return err
}
}
}
return nil
}
func (p *PgBouncer) Stop() {
p.service.Stop()
}
func (p *PgBouncer) accRow(row *sql.Rows, columns []string) (map[string]string, map[string]*interface{}, error) {
var dbname bytes.Buffer
// this is where we'll store the column name with its *interface{}
columnMap := make(map[string]*interface{})
for _, column := range columns {
columnMap[column] = new(interface{})
}
columnVars := make([]interface{}, 0, len(columnMap))
// populate the array of interface{} with the pointers in the right order
for i := 0; i < len(columnMap); i++ {
columnVars = append(columnVars, columnMap[columns[i]])
}
// deconstruct array of variables and send to Scan
err := row.Scan(columnVars...)
if err != nil {
return nil, nil, fmt.Errorf("couldn't copy the data: %w", err)
}
if columnMap["database"] != nil {
// extract the database name from the column map
name, ok := (*columnMap["database"]).(string)
if !ok {
return nil, nil, fmt.Errorf("database not a string, but %T", *columnMap["database"])
}
dbname.WriteString(name)
} else {
dbname.WriteString("pgbouncer")
}
// Return basic tags and the mapped columns
return map[string]string{"server": p.service.SanitizedAddress, "db": dbname.String()}, columnMap, nil
}
func (p *PgBouncer) showStats(acc telegraf.Accumulator) error {
// STATS
rows, err := p.service.DB.Query(`SHOW STATS`)
if err != nil {
return fmt.Errorf("execution error 'show stats': %w", err)
}
defer rows.Close()
// grab the column information from the result
columns, err := rows.Columns()
if err != nil {
return fmt.Errorf("don't get column names 'show stats': %w", err)
}
for rows.Next() {
tags, columnMap, err := p.accRow(rows, columns)
if err != nil {
return err
}
fields := make(map[string]interface{})
for col, val := range columnMap {
_, ignore := ignoredColumns[col]
if ignore {
continue
}
switch v := (*val).(type) {
case int64:
// Integer fields are returned in pgbouncer 1.5 through 1.9
fields[col] = v
case string:
// Integer fields are returned in pgbouncer 1.12
integer, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return fmt.Errorf("couldn't convert metrics 'show stats': %w", err)
}
fields[col] = integer
}
}
acc.AddFields("pgbouncer", fields, tags)
}
return rows.Err()
}
func (p *PgBouncer) showPools(acc telegraf.Accumulator) error {
// POOLS
poolRows, err := p.service.DB.Query(`SHOW POOLS`)
if err != nil {
return fmt.Errorf("execution error 'show pools': %w", err)
}
defer poolRows.Close()
// grab the column information from the result
columns, err := poolRows.Columns()
if err != nil {
return fmt.Errorf("don't get column names 'show pools': %w", err)
}
for poolRows.Next() {
tags, columnMap, err := p.accRow(poolRows, columns)
if err != nil {
return err
}
if user, ok := columnMap["user"]; ok {
if s, ok := (*user).(string); ok && s != "" {
tags["user"] = s
}
}
if poolMode, ok := columnMap["pool_mode"]; ok {
if s, ok := (*poolMode).(string); ok && s != "" {
tags["pool_mode"] = s
}
}
fields := make(map[string]interface{})
for col, val := range columnMap {
_, ignore := ignoredColumns[col]
if !ignore {
fields[col] = *val
}
}
acc.AddFields("pgbouncer_pools", fields, tags)
}
return poolRows.Err()
}
func (p *PgBouncer) showLists(acc telegraf.Accumulator) error {
// LISTS
rows, err := p.service.DB.Query(`SHOW LISTS`)
if err != nil {
return fmt.Errorf("execution error 'show lists': %w", err)
}
defer rows.Close()
// grab the column information from the result
columns, err := rows.Columns()
if err != nil {
return fmt.Errorf("don't get column names 'show lists': %w", err)
}
fields := make(map[string]interface{})
tags := make(map[string]string)
for rows.Next() {
tag, columnMap, err := p.accRow(rows, columns)
if err != nil {
return err
}
name, ok := (*columnMap["list"]).(string)
if !ok {
return fmt.Errorf("metric name(show lists) not a string, but %T", *columnMap["list"])
}
if name != "dns_pending" {
value, ok := (*columnMap["items"]).(int64)
if !ok {
return fmt.Errorf("metric value(show lists) not a int64, but %T", *columnMap["items"])
}
fields[name] = value
tags = tag
}
}
acc.AddFields("pgbouncer_lists", fields, tags)
return rows.Err()
}
func (p *PgBouncer) showDatabase(acc telegraf.Accumulator) error {
// DATABASES
rows, err := p.service.DB.Query(`SHOW DATABASES`)
if err != nil {
return fmt.Errorf("execution error 'show database': %w", err)
}
defer rows.Close()
// grab the column information from the result
columns, err := rows.Columns()
if err != nil {
return fmt.Errorf("don't get column names 'show database': %w", err)
}
for rows.Next() {
tags, columnMap, err := p.accRow(rows, columns)
if err != nil {
return err
}
// SHOW DATABASES displays pgbouncer database name under name column,
// while using database column to store Postgres database name.
if database, ok := columnMap["database"]; ok {
if s, ok := (*database).(string); ok && s != "" {
tags["pg_dbname"] = s
}
}
// pass it under db tag to be compatible with the rest of the measurements
if name, ok := columnMap["name"]; ok {
if s, ok := (*name).(string); ok && s != "" {
tags["db"] = s
}
}
fields := make(map[string]interface{})
for col, val := range columnMap {
_, ignore := ignoredColumns[col]
if !ignore {
fields[col] = *val
}
}
acc.AddFields("pgbouncer_databases", fields, tags)
}
return rows.Err()
}
func init() {
inputs.Add("pgbouncer", func() telegraf.Input {
return &PgBouncer{
Config: postgresql.Config{
MaxIdle: 1,
MaxOpen: 1,
IsPgBouncer: true,
},
}
})
}

View file

@ -0,0 +1,215 @@
package pgbouncer
import (
"fmt"
"testing"
"github.com/docker/go-connections/nat"
"github.com/stretchr/testify/require"
"github.com/testcontainers/testcontainers-go/wait"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/plugins/common/postgresql"
"github.com/influxdata/telegraf/testutil"
)
func TestPgBouncerGeneratesMetricsIntegration(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
postgresServicePort := "5432"
pgBouncerServicePort := "6432"
backend := testutil.Container{
Image: "postgres:alpine",
ExposedPorts: []string{postgresServicePort},
Env: map[string]string{
"POSTGRES_HOST_AUTH_METHOD": "trust",
},
WaitingFor: wait.ForLog("database system is ready to accept connections").WithOccurrence(2),
}
require.NoError(t, backend.Start(), "failed to start container")
defer backend.Terminate()
container := testutil.Container{
Image: "z9pascal/pgbouncer-container:1.23.1-latest",
ExposedPorts: []string{pgBouncerServicePort},
Env: map[string]string{
"PG_ENV_POSTGRESQL_USER": "pgbouncer",
"PG_ENV_POSTGRESQL_PASS": "pgbouncer",
},
WaitingFor: wait.ForAll(
wait.ForListeningPort(nat.Port(pgBouncerServicePort)),
wait.ForLog("LOG process up"),
),
}
require.NoError(t, container.Start(), "failed to start container")
defer container.Terminate()
addr := fmt.Sprintf(
"host=%s user=pgbouncer password=pgbouncer dbname=pgbouncer port=%s sslmode=disable",
container.Address,
container.Ports[pgBouncerServicePort],
)
p := &PgBouncer{
Config: postgresql.Config{
Address: config.NewSecret([]byte(addr)),
IsPgBouncer: true,
},
}
require.NoError(t, p.Init())
var acc testutil.Accumulator
require.NoError(t, p.Start(&acc))
defer p.Stop()
require.NoError(t, p.Gather(&acc))
intMetricsPgBouncer := []string{
"total_received",
"total_sent",
"total_query_time",
"avg_query_count",
"avg_query_time",
"avg_wait_time",
}
intMetricsPgBouncerPools := []string{
"cl_active",
"cl_waiting",
"sv_active",
"sv_idle",
"sv_used",
"sv_tested",
"sv_login",
"maxwait",
}
metricsCounted := 0
for _, metric := range intMetricsPgBouncer {
require.True(t, acc.HasInt64Field("pgbouncer", metric))
metricsCounted++
}
for _, metric := range intMetricsPgBouncerPools {
require.True(t, acc.HasInt64Field("pgbouncer_pools", metric))
metricsCounted++
}
require.Positive(t, metricsCounted)
require.Equal(t, len(intMetricsPgBouncer)+len(intMetricsPgBouncerPools), metricsCounted)
}
func TestPgBouncerGeneratesMetricsIntegrationShowCommands(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
postgresServicePort := "5432"
pgBouncerServicePort := "6432"
backend := testutil.Container{
Image: "postgres:alpine",
ExposedPorts: []string{postgresServicePort},
Env: map[string]string{
"POSTGRES_HOST_AUTH_METHOD": "trust",
},
WaitingFor: wait.ForLog("database system is ready to accept connections").WithOccurrence(2),
}
err := backend.Start()
require.NoError(t, err, "failed to start container")
defer backend.Terminate()
container := testutil.Container{
Image: "z9pascal/pgbouncer-container:1.23.1-latest",
ExposedPorts: []string{pgBouncerServicePort},
Env: map[string]string{
"PG_ENV_POSTGRESQL_USER": "pgbouncer",
"PG_ENV_POSTGRESQL_PASS": "pgbouncer",
},
WaitingFor: wait.ForAll(
wait.ForListeningPort(nat.Port(pgBouncerServicePort)),
wait.ForLog("LOG process up"),
),
}
err = container.Start()
require.NoError(t, err, "failed to start container")
defer container.Terminate()
addr := fmt.Sprintf(
"host=%s user=pgbouncer password=pgbouncer dbname=pgbouncer port=%s sslmode=disable",
container.Address,
container.Ports[pgBouncerServicePort],
)
p := &PgBouncer{
Config: postgresql.Config{
Address: config.NewSecret([]byte(addr)),
IsPgBouncer: true,
},
ShowCommands: []string{"pools", "lists", "databases"},
}
require.NoError(t, p.Init())
var acc testutil.Accumulator
require.NoError(t, p.Start(&acc))
defer p.Stop()
require.NoError(t, p.Gather(&acc))
intMetricsPgBouncerPools := []string{
"cl_active",
"cl_waiting",
"sv_active",
"sv_idle",
"sv_used",
"sv_tested",
"sv_login",
"maxwait",
}
intMetricsPgBouncerLists := []string{
"databases",
"users",
"pools",
"free_clients",
"used_clients",
"login_clients",
"free_servers",
"used_servers",
"dns_names",
"dns_zones",
"dns_queries",
}
intMetricsPgBouncerDatabases := []string{
"pool_size",
"min_pool_size",
"reserve_pool",
"max_connections",
"current_connections",
"paused",
"disabled",
}
metricsCounted := 0
for _, metric := range intMetricsPgBouncerPools {
require.True(t, acc.HasInt64Field("pgbouncer_pools", metric))
metricsCounted++
}
for _, metric := range intMetricsPgBouncerLists {
require.True(t, acc.HasInt64Field("pgbouncer_lists", metric))
metricsCounted++
}
for _, metric := range intMetricsPgBouncerDatabases {
require.True(t, acc.HasInt64Field("pgbouncer_databases", metric))
metricsCounted++
}
require.Positive(t, metricsCounted)
require.Equal(t, len(intMetricsPgBouncerPools)+len(intMetricsPgBouncerLists)+len(intMetricsPgBouncerDatabases), metricsCounted)
}

View file

@ -0,0 +1,15 @@
# Read metrics from one or many pgbouncer servers
[[inputs.pgbouncer]]
## specify address via a url matching:
## postgres://[pqgotest[:password]]@host:port[/dbname]\
## ?sslmode=[disable|verify-ca|verify-full]
## or a simple string:
## host=localhost port=5432 user=pqgotest password=... sslmode=... dbname=app_production
##
## All connection parameters are optional.
##
address = "host=localhost user=pgbouncer sslmode=disable"
## Specify which "show" commands to gather metrics for.
## Choose from: "stats", "pools", "lists", "databases"
# show_commands = ["stats", "pools"]