forked from influxdata/telegraf
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add pgbouncer input plugin (influxdata#3918)
- Loading branch information
Showing
11 changed files
with
311 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# PgBouncer plugin | ||
|
||
This 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) | ||
|
||
## Configuration | ||
Specify address via a url matching: | ||
|
||
`postgres://[pqgotest[:password]]@localhost[/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. | ||
|
||
### Configuration example | ||
``` | ||
[[inputs.pgbouncer]] | ||
address = "postgres://telegraf@localhost/pgbouncer" | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
package pgbouncer | ||
|
||
import ( | ||
"bytes" | ||
"github.com/influxdata/telegraf/plugins/inputs/postgresql" | ||
|
||
// register in driver. | ||
_ "github.com/jackc/pgx/stdlib" | ||
|
||
"github.com/influxdata/telegraf" | ||
"github.com/influxdata/telegraf/internal" | ||
"github.com/influxdata/telegraf/plugins/inputs" | ||
) | ||
|
||
type PgBouncer struct { | ||
postgresql.Service | ||
} | ||
|
||
var ignoredColumns = map[string]bool{"user": true, "database": true, "pool_mode": true, | ||
"avg_req": true, "avg_recv": true, "avg_sent": true, "avg_query": true, | ||
} | ||
|
||
var sampleConfig = ` | ||
## specify address via a url matching: | ||
## postgres://[pqgotest[:password]]@localhost[/dbname]\ | ||
## ?sslmode=[disable|verify-ca|verify-full] | ||
## or a simple string: | ||
## host=localhost user=pqotest password=... sslmode=... dbname=app_production | ||
## | ||
## All connection parameters are optional. | ||
## | ||
address = "host=localhost user=pgbouncer sslmode=disable" | ||
` | ||
|
||
func (p *PgBouncer) SampleConfig() string { | ||
return sampleConfig | ||
} | ||
|
||
func (p *PgBouncer) Description() string { | ||
return "Read metrics from one or many pgbouncer servers" | ||
} | ||
|
||
func (p *PgBouncer) Gather(acc telegraf.Accumulator) error { | ||
var ( | ||
err error | ||
query string | ||
columns []string | ||
) | ||
|
||
query = `SHOW STATS` | ||
|
||
rows, err := p.DB.Query(query) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
defer rows.Close() | ||
|
||
// grab the column information from the result | ||
if columns, err = rows.Columns(); err != nil { | ||
return err | ||
} | ||
|
||
for rows.Next() { | ||
tags, columnMap, err := p.accRow(rows, acc, columns) | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
fields := make(map[string]interface{}) | ||
for col, val := range columnMap { | ||
_, ignore := ignoredColumns[col] | ||
if !ignore { | ||
fields[col] = *val | ||
} | ||
} | ||
acc.AddFields("pgbouncer", fields, tags) | ||
} | ||
|
||
query = `SHOW POOLS` | ||
|
||
poolRows, err := p.DB.Query(query) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
defer poolRows.Close() | ||
|
||
// grab the column information from the result | ||
if columns, err = poolRows.Columns(); err != nil { | ||
return err | ||
} | ||
|
||
for poolRows.Next() { | ||
tags, columnMap, err := p.accRow(poolRows, acc, columns) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if s, ok := (*columnMap["user"]).(string); ok && s != "" { | ||
tags["user"] = s | ||
} | ||
|
||
if s, ok := (*columnMap["pool_mode"]).(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() | ||
} | ||
|
||
type scanner interface { | ||
Scan(dest ...interface{}) error | ||
} | ||
|
||
func (p *PgBouncer) accRow(row scanner, acc telegraf.Accumulator, columns []string) (map[string]string, | ||
map[string]*interface{}, error) { | ||
var columnVars []interface{} | ||
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{}) | ||
} | ||
|
||
// 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, err | ||
} | ||
if columnMap["database"] != nil { | ||
// extract the database name from the column map | ||
dbname.WriteString((*columnMap["database"]).(string)) | ||
} else { | ||
dbname.WriteString("postgres") | ||
} | ||
|
||
var tagAddress string | ||
tagAddress, err = p.SanitizedAddress() | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
|
||
// Return basic tags and the mapped columns | ||
return map[string]string{"server": tagAddress, "db": dbname.String()}, columnMap, nil | ||
} | ||
|
||
func init() { | ||
inputs.Add("pgbouncer", func() telegraf.Input { | ||
return &PgBouncer{ | ||
Service: postgresql.Service{ | ||
MaxIdle: 1, | ||
MaxOpen: 1, | ||
MaxLifetime: internal.Duration{ | ||
Duration: 0, | ||
}, | ||
IsPgBouncer: true, | ||
}, | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package pgbouncer | ||
|
||
import ( | ||
"fmt" | ||
"github.com/influxdata/telegraf/plugins/inputs/postgresql" | ||
"github.com/influxdata/telegraf/testutil" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"testing" | ||
) | ||
|
||
func TestPgBouncerGeneratesMetrics(t *testing.T) { | ||
if testing.Short() { | ||
t.Skip("Skipping integration test in short mode") | ||
} | ||
|
||
p := &PgBouncer{ | ||
Service: postgresql.Service{ | ||
Address: fmt.Sprintf( | ||
"host=%s user=pgbouncer password=pgbouncer dbname=pgbouncer port=6432 sslmode=disable", | ||
testutil.GetLocalHost(), | ||
), | ||
IsPgBouncer: true, | ||
}, | ||
} | ||
|
||
var acc testutil.Accumulator | ||
require.NoError(t, p.Start(&acc)) | ||
require.NoError(t, p.Gather(&acc)) | ||
|
||
intMetrics := []string{ | ||
"total_requests", | ||
"total_received", | ||
"total_sent", | ||
"total_query_time", | ||
"avg_req", | ||
"avg_recv", | ||
"avg_sent", | ||
"avg_query", | ||
"cl_active", | ||
"cl_waiting", | ||
"sv_active", | ||
"sv_idle", | ||
"sv_used", | ||
"sv_tested", | ||
"sv_login", | ||
"maxwait", | ||
} | ||
|
||
int32Metrics := []string{} | ||
|
||
metricsCounted := 0 | ||
|
||
for _, metric := range intMetrics { | ||
assert.True(t, acc.HasInt64Field("pgbouncer", metric)) | ||
metricsCounted++ | ||
} | ||
|
||
for _, metric := range int32Metrics { | ||
assert.True(t, acc.HasInt32Field("pgbouncer", metric)) | ||
metricsCounted++ | ||
} | ||
|
||
assert.True(t, metricsCounted > 0) | ||
assert.Equal(t, len(intMetrics)+len(int32Metrics), metricsCounted) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -189,6 +189,7 @@ func init() { | |
MaxLifetime: internal.Duration{ | ||
Duration: 0, | ||
}, | ||
IsPgBouncer: false, | ||
}, | ||
} | ||
}) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -283,6 +283,7 @@ func init() { | |
MaxLifetime: internal.Duration{ | ||
Duration: 0, | ||
}, | ||
IsPgBouncer: false, | ||
}, | ||
} | ||
}) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters