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
|
||||
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"
|
||||
//"strconv"
|
||||
//"strings"
|
||||
"time"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
@ -36,7 +36,6 @@ const (
|
||||
namespace = "pg"
|
||||
// Subsystems.
|
||||
exporter = "exporter"
|
||||
|
||||
)
|
||||
|
||||
// landingPage contains the HTML served at '/'.
|
||||
@ -51,6 +50,7 @@ var landingPage = []byte(`<html>
|
||||
`)
|
||||
|
||||
type ColumnUsage int
|
||||
|
||||
const (
|
||||
DISCARD ColumnUsage = iota // Ignore this column
|
||||
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_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.
|
||||
@ -294,7 +358,7 @@ type Exporter struct {
|
||||
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 {
|
||||
return &Exporter{
|
||||
dsn: dsn,
|
||||
@ -392,7 +456,7 @@ func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
|
||||
// Use SHOW to get the value
|
||||
row := db.QueryRow(fmt.Sprintf("SHOW %s;", columnName))
|
||||
|
||||
var val interface{};
|
||||
var val interface{}
|
||||
err := row.Scan(&val)
|
||||
if err != nil {
|
||||
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 {
|
||||
log.Debugln("Querying namespace: ", namespace)
|
||||
func () { // Don't fail on a bad scrape of one metric
|
||||
rows, err := db.Query(fmt.Sprintf("SELECT * FROM %s;", namespace))
|
||||
func() {
|
||||
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 {
|
||||
log.Println("Error running query on database: ", namespace, err)
|
||||
e.error.Set(1)
|
||||
|
Reference in New Issue
Block a user