You've already forked postgres_exporter
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:
39
README.md
39
README.md
@ -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;
|
||||||
|
```
|
||||||
|
@ -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)
|
||||||
|
Reference in New Issue
Block a user