1
0
mirror of https://github.com/prometheus-community/postgres_exporter.git synced 2025-07-30 09:43:10 +03:00

Added support for collecting arbitrary sql queries.

Added metrics for locks per database per mode, pg_stat_activity, pg_stat_replication.
Updated README.md with possible solution for running as non-superuser.
This commit is contained in:
fiknytte
2016-01-06 20:19:41 +01:00
parent 01e4eb4e67
commit 7e8fbdf07f
2 changed files with 270 additions and 161 deletions

View File

@ -41,3 +41,42 @@ for l in StringIO(x):
``` ```
Adjust the value of the resultant prometheus value type appropriately. This helps build Adjust the value of the resultant prometheus value type appropriately. This helps build
rich self-documenting metrics for the exporter. rich self-documenting metrics for the exporter.
### Running as non-superuser
To be able to collect metrics from pg_stat_activity and pg_stat_replication as non-superuser you have to create functions and views to do so.
```sql
CREATE USER postgres_exporter PASSWORD 'password';
ALTER USER postgres_exporter SET SEARCH_PATH TO postgres_exporter,pg_catalog;
CREATE SCHEMA postgres_exporter AUTHORIZATION postgres_exporter;
CREATE FUNCTION postgres_exporter.f_select_pg_stat_activity()
RETURNS setof pg_catalog.pg_stat_activity
LANGUAGE sql
SECURITY DEFINER
AS $$
SELECT * from pg_catalog.pg_stat_activity;
$$;
CREATE FUNCTION postgres_exporter.f_select_pg_stat_replication()
RETURNS setof pg_catalog.pg_stat_replication
LANGUAGE sql
SECURITY DEFINER
AS $$
SELECT * from pg_catalog.pg_stat_replication;
$$;
CREATE VIEW postgres_exporter.pg_stat_replication
AS
SELECT * FROM postgres_exporter.f_select_pg_stat_replication();
CREATE VIEW postgres_exporter.pg_stat_activity
AS
SELECT * FROM postgres_exporter.f_select_pg_stat_activity();
GRANT SELECT ON postgres_exporter.pg_stat_replication TO postgres_exporter;
GRANT SELECT ON postgres_exporter.pg_stat_activity TO postgres_exporter;
```

View File

@ -10,8 +10,8 @@ import (
//"regexp" //"regexp"
//"strconv" //"strconv"
//"strings" //"strings"
"time"
"math" "math"
"time"
_ "github.com/lib/pq" _ "github.com/lib/pq"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
@ -36,7 +36,6 @@ const (
namespace = "pg" namespace = "pg"
// Subsystems. // Subsystems.
exporter = "exporter" exporter = "exporter"
) )
// landingPage contains the HTML served at '/'. // landingPage contains the HTML served at '/'.
@ -51,6 +50,7 @@ var landingPage = []byte(`<html>
`) `)
type ColumnUsage int type ColumnUsage int
const ( const (
DISCARD ColumnUsage = iota // Ignore this column DISCARD ColumnUsage = iota // Ignore this column
LABEL ColumnUsage = iota // Use this column as a label LABEL ColumnUsage = iota // Use this column as a label
@ -147,6 +147,70 @@ var metricMaps = map[string]map[string]ColumnMapping {
"confl_bufferpin": {COUNTER, "Number of queries in this database that have been canceled due to pinned buffers", nil}, "confl_bufferpin": {COUNTER, "Number of queries in this database that have been canceled due to pinned buffers", nil},
"confl_deadlock": {COUNTER, "Number of queries in this database that have been canceled due to deadlocks", nil}, "confl_deadlock": {COUNTER, "Number of queries in this database that have been canceled due to deadlocks", nil},
}, },
"pg_locks": map[string]ColumnMapping{
"datname": {LABEL, "Name of this database", nil},
"mode": {LABEL, "Type of Lock", nil},
"count": {GAUGE, "Number of locks", nil},
},
"pg_stat_replication": map[string]ColumnMapping{
"pid": {DISCARD, "Process ID of a WAL sender process", nil},
"usesysid": {DISCARD, "OID of the user logged into this WAL sender process", nil},
"usename": {DISCARD, "Name of the user logged into this WAL sender process", nil},
"application_name": {DISCARD, "Name of the application that is connected to this WAL sender", nil},
"client_addr": {LABEL, "IP address of the client connected to this WAL sender. If this field is null, it indicates that the client is connected via a Unix socket on the server machine.", nil},
"client_hostname": {DISCARD, "Host name of the connected client, as reported by a reverse DNS lookup of client_addr. This field will only be non-null for IP connections, and only when log_hostname is enabled.", nil},
"client_port": {DISCARD, "TCP port number that the client is using for communication with this WAL sender, or -1 if a Unix socket is used", nil},
"backend_start": {DISCARD, "with time zone Time when this process was started, i.e., when the client connected to this WAL sender", nil},
"backend_xmin": {DISCARD, "The current backend's xmin horizon.", nil},
"state": {LABEL, "Current WAL sender state", nil},
"sent_location": {DISCARD, "Last transaction log position sent on this connection", nil},
"write_location": {DISCARD, "Last transaction log position written to disk by this standby server", nil},
"flush_location": {DISCARD, "Last transaction log position flushed to disk by this standby server", nil},
"replay_location": {DISCARD, "Last transaction log position replayed into the database on this standby server", nil},
"sync_priority": {DISCARD, "Priority of this standby server for being chosen as the synchronous standby", nil},
"sync_state": {DISCARD, "Synchronous state of this standby server", nil},
"pg_current_xlog_location": {DISCARD, "pg_current_xlog_location", nil},
"pg_xlog_location_diff": {GAUGE, "Lag in bytes between master and slave", nil},
},
"pg_stat_activity": map[string]ColumnMapping{
"datname": {LABEL, "Name of this database", nil},
"state": {LABEL, "connection state", nil},
"count": {GAUGE, "number of connections in this state", nil},
"max_tx_duration": {GAUGE, "max duration in seconds any active transaction has been running", nil},
},
}
// Overriding queries for namespaces above.
var queryOverrides = map[string]string{
"pg_locks": `
SELECT pg_database.datname,tmp.mode,COALESCE(count,0) as count FROM
(VALUES ('accesssharelock'),('rowsharelock'),('rowexclusivelock'),('shareupdateexclusivelock'),('sharelock'),('sharerowexclusivelock'),('exclusivelock'),('accessexclusivelock')) AS tmp(mode) CROSS JOIN pg_database
LEFT JOIN
(SELECT database, lower(mode) AS mode,count(*) AS count
FROM pg_locks WHERE database IS NOT NULL
GROUP BY database, lower(mode)
) AS tmp2
ON tmp.mode=tmp2.mode and pg_database.oid = tmp2.database ORDER BY 1`,
"pg_stat_replication": `
SELECT *, pg_current_xlog_location(), pg_xlog_location_diff(pg_current_xlog_location(), replay_location)::float FROM pg_stat_replication`,
"pg_stat_activity": `
SELECT
pg_database.datname,
tmp.state,
COALESCE(count,0) as count,
COALESCE(max_tx_duration,0) as max_tx_duration
FROM
(VALUES ('active'),('idle'),('idle in transaction'),('idle in transaction (aborted)'),('fastpath function call'),('disabled')) as tmp(state) CROSS JOIN pg_database
LEFT JOIN
(SELECT
datname,
state,
count(*) AS count,
MAX(EXTRACT(EPOCH FROM now() - xact_start))::float AS max_tx_duration
FROM pg_stat_activity GROUP BY datname,state) as tmp2
ON tmp.state = tmp2.state AND pg_database.datname = tmp2.datname`,
} }
// Turn the MetricMap column mapping into a prometheus descriptor mapping. // Turn the MetricMap column mapping into a prometheus descriptor mapping.
@ -294,7 +358,7 @@ type Exporter struct {
metricMap map[string]MetricMapNamespace metricMap map[string]MetricMapNamespace
} }
// NewExporter returns a new MySQL exporter for the provided DSN. // NewExporter returns a new PostgreSQL exporter for the provided DSN.
func NewExporter(dsn string) *Exporter { func NewExporter(dsn string) *Exporter {
return &Exporter{ return &Exporter{
dsn: dsn, dsn: dsn,
@ -392,7 +456,7 @@ func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
// Use SHOW to get the value // Use SHOW to get the value
row := db.QueryRow(fmt.Sprintf("SHOW %s;", columnName)) row := db.QueryRow(fmt.Sprintf("SHOW %s;", columnName))
var val interface{}; var val interface{}
err := row.Scan(&val) err := row.Scan(&val)
if err != nil { if err != nil {
log.Errorln("Error scanning runtime variable:", columnName, err) log.Errorln("Error scanning runtime variable:", columnName, err)
@ -412,8 +476,14 @@ func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
for namespace, mapping := range e.metricMap { for namespace, mapping := range e.metricMap {
log.Debugln("Querying namespace: ", namespace) log.Debugln("Querying namespace: ", namespace)
func () { // Don't fail on a bad scrape of one metric func() {
rows, err := db.Query(fmt.Sprintf("SELECT * FROM %s;", namespace)) query, er := queryOverrides[namespace]
if er == false {
query = fmt.Sprintf("SELECT * FROM %s;", namespace)
}
// Don't fail on a bad scrape of one metric
rows, err := db.Query(query)
if err != nil { if err != nil {
log.Println("Error running query on database: ", namespace, err) log.Println("Error running query on database: ", namespace, err)
e.error.Set(1) e.error.Set(1)