Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ go:
script:
- make all
- make docker
- make test-integration
after_success:
- if [ "$TRAVIS_BRANCH" == "master" ]; then docker login -e $DOCKER_EMAIL -u $DOCKER_USER
-p $DOCKER_PASS ; docker push wrouesnel/postgres_exporter ; fi
Expand Down
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ vet:
test:
go test -v .

test-integration:
tests/test-smoke

# Do a self-contained docker build - we pull the official upstream container,
# then template out a dockerfile which builds the real image.
docker-build: postgres_exporter
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ 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.

### Adding new metrics via a config file

The -extend.query-path command-line argument specifies a YAML file containing additional queries to run.
Some examples are provided in [queries.yaml](queries.yaml).

### Running as non-superuser

Expand Down
79 changes: 56 additions & 23 deletions postgres_exporter.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,24 @@
package main

import (
//"bytes"
"database/sql"
"flag"
"fmt"
"io/ioutil"
"math"
"net/http"
"os"
//"regexp"
//"strconv"
//"strings"
"math"
"strconv"
"time"
"io/ioutil"

"gopkg.in/yaml.v2"

_ "github.com/lib/pq"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/log"
"strconv"
"github.com/prometheus/common/log"
)

var Version string = "0.0.0-dev"
var Version string = "0.0.1"

var (
listenAddress = flag.String(
Expand All @@ -36,6 +33,10 @@ var (
"extend.query-path", "",
"Path to custom queries to run.",
)
onlyDumpMaps = flag.Bool(
"dumpmaps", false,
"Do not run, simply dump the maps.",
)
)

// Metric name parts.
Expand Down Expand Up @@ -111,6 +112,21 @@ var variableMaps = map[string]map[string]ColumnMapping{
},
}

func dumpMaps() {
for name, cmap := range metricMaps {
query, ok := queryOverrides[name]
if ok {
fmt.Printf("%s: %s\n", name, query)
} else {
fmt.Println(name)
}
for column, details := range cmap {
fmt.Printf(" %-40s %v\n", column, details)
}
fmt.Println()
}
}

var metricMaps = map[string]map[string]ColumnMapping{
"pg_stat_bgwriter": map[string]ColumnMapping{
"checkpoints_timed": {COUNTER, "Number of scheduled checkpoints that have been performed", nil},
Expand Down Expand Up @@ -235,7 +251,6 @@ func addQueries(queriesPath string) (err error) {
return err
}


for metric, specs := range extra {
for key, value := range specs.(map[interface{}]interface{}) {
switch key.(string) {
Expand All @@ -249,14 +264,16 @@ func addQueries(queriesPath string) (err error) {

for n, a := range column {
var cmap ColumnMapping
var metric_map map[string]ColumnMapping

metric_map = make(map[string]ColumnMapping)
metric_map, ok := metricMaps[metric]
if !ok {
metric_map = make(map[string]ColumnMapping)
}

name := n.(string)

for attr_key, attr_val := range a.(map[interface{}]interface{}) {
switch(attr_key.(string)) {
switch attr_key.(string) {
case "usage":
usage, err := stringToColumnUsage(attr_val.(string))
if err != nil {
Expand Down Expand Up @@ -355,6 +372,10 @@ func makeDescMap(metricMaps map[string]map[string]ColumnMapping) map[string]Metr
return math.NaN(), false
}

if durationString == "-1" {
return math.NaN(), false
}

d, err := time.ParseDuration(durationString)
if err != nil {
log.Errorln("Failed converting result to metric:", columnName, in, err)
Expand All @@ -374,7 +395,7 @@ func makeDescMap(metricMaps map[string]map[string]ColumnMapping) map[string]Metr

// convert a string to the corresponding ColumnUsage
func stringToColumnUsage(s string) (u ColumnUsage, err error) {
switch(s) {
switch s {
case "DISCARD":
u = DISCARD

Expand Down Expand Up @@ -418,6 +439,13 @@ func dbToFloat64(t interface{}) (float64, bool) {
return math.NaN(), false
}
return result, true
case string:
result, err := strconv.ParseFloat(v, 64)
if err != nil {
log.Infoln("Could not parse string:", err)
return math.NaN(), false
}
return result, true
case nil:
return math.NaN(), true
default:
Expand Down Expand Up @@ -536,7 +564,7 @@ func (e *Exporter) scrape(ch chan<- prometheus.Metric) {

db, err := sql.Open("postgres", e.dsn)
if err != nil {
log.Println("Error opening connection to database:", err)
log.Infoln("Error opening connection to database:", err)
e.error.Set(1)
return
}
Expand Down Expand Up @@ -582,7 +610,7 @@ func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
// 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)
log.Infoln("Error running query on database: ", namespace, err)
e.error.Set(1)
return
}
Expand All @@ -591,7 +619,7 @@ func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
var columnNames []string
columnNames, err = rows.Columns()
if err != nil {
log.Println("Error retrieving column list for: ", namespace, err)
log.Infoln("Error retrieving column list for: ", namespace, err)
e.error.Set(1)
return
}
Expand All @@ -611,7 +639,7 @@ func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
for rows.Next() {
err = rows.Scan(scanArgs...)
if err != nil {
log.Println("Error retrieving rows:", namespace, err)
log.Infoln("Error retrieving rows:", namespace, err)
e.error.Set(1)
return
}
Expand Down Expand Up @@ -666,18 +694,23 @@ func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
func main() {
flag.Parse()

dsn := os.Getenv("DATA_SOURCE_NAME")
if len(dsn) == 0 {
log.Fatal("couldn't find environment variable DATA_SOURCE_NAME")
}

if *queriesPath != "" {
err := addQueries(*queriesPath)
if err != nil {
log.Warnln("Unparseable queries file - discarding merge: ", *queriesPath, err)
}
}

if *onlyDumpMaps {
dumpMaps()
return
}

dsn := os.Getenv("DATA_SOURCE_NAME")
if len(dsn) == 0 {
log.Fatal("couldn't find environment variable DATA_SOURCE_NAME")
}

exporter := NewExporter(dsn)
prometheus.MustRegister(exporter)

Expand Down
100 changes: 97 additions & 3 deletions queries.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,100 @@
pg_replication:
query: 'SELECT EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp()))::INT as lag'
query: "SELECT EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp()))::INT as lag"
metrics:
- lag:
usage: 'GAUGE'
description: 'Replication lag behind master in seconds'
usage: "GAUGE"
description: "Replication lag behind master in seconds"

pg_postmaster:
query: "SELECT pg_postmaster_start_time as start_time_seconds from pg_postmaster_start_time()"
metrics:
- start_time_seconds:
usage: "GAUGE"
description: "Time at which postmaster started"

pg_settings_shared_buffers:
query: "SELECT 8192*setting::int as bytes from pg_settings where name = 'shared_buffers'"
metrics:
- bytes:
usage: "GAUGE"
description: "Size of shared_buffers"

pg_settings_checkpoint:
query: "select (select setting::int from pg_settings where name = 'checkpoint_segments') as segments, (select setting::int from pg_settings where name = 'checkpoint_timeout') as timeout_seconds, (select setting::float from pg_settings where name = 'checkpoint_completion_target') as completion_target"
metrics:
- segments:
usage: "GAUGE"
description: "Number of checkpoint segments"
- timeout_seconds:
usage: "GAUGE"
description: "Checkpoint timeout in seconds"
- completion_target:
usage: "GAUGE"
description: "Checkpoint completion target, ranging from 0 to 1"

pg_stat_user_tables:
query: "SELECT schemaname, relname, seq_scan, seq_tup_read, idx_scan, idx_tup_fetch, n_tup_ins, n_tup_upd, n_tup_del, n_tup_hot_upd, n_live_tup, n_dead_tup, n_mod_since_analyze, last_vacuum, last_autovacuum, last_analyze, last_autoanalyze, vacuum_count, autovacuum_count, analyze_count, autoanalyze_count FROM pg_stat_user_tables"
metrics:
- schemaname:
usage: "LABEL"
description: "Name of the schema that this table is in"
- relname:
usage: "LABEL"
description: "Name of this table"
- seq_scan:
usage: "COUNTER"
description: "Number of sequential scans initiated on this table"
- seq_tup_read:
usage: "COUNTER"
description: "Number of live rows fetched by sequential scans"
- idx_scan:
usage: "COUNTER"
description: "Number of index scans initiated on this table"
- idx_tup_fetch:
usage: "COUNTER"
description: "Number of live rows fetched by index scans"
- n_tup_ins:
usage: "COUNTER"
description: "Number of rows inserted"
- n_tup_upd:
usage: "COUNTER"
description: "Number of rows updated"
- n_tup_del:
usage: "COUNTER"
description: "Number of rows deleted"
- n_tup_hot_upd:
usage: "COUNTER"
description: "Number of rows HOT updated (i.e., with no separate index update required)"
- n_live_tup:
usage: "GAUGE"
description: "Estimated number of live rows"
- n_dead_tup:
usage: "GAUGE"
description: "Estimated number of dead rows"
- n_mod_since_analyze:
usage: "GAUGE"
description: "Estimated number of rows changed since last analyze"
- last_vacuum:
usage: "GAUGE"
description: "Last time at which this table was manually vacuumed (not counting VACUUM FULL)"
- last_autovacuum:
usage: "GAUGE"
description: "Last time at which this table was vacuumed by the autovacuum daemon"
- last_analyze:
usage: "GAUGE"
description: "Last time at which this table was manually analyzed"
- last_autoanalyze:
usage: "GAUGE"
description: "Last time at which this table was analyzed by the autovacuum daemon"
- vacuum_count:
usage: "COUNTER"
description: "Number of times this table has been manually vacuumed (not counting VACUUM FULL)"
- autovacuum_count:
usage: "COUNTER"
description: "Number of times this table has been vacuumed by the autovacuum daemon"
- analyze_count:
usage: "COUNTER"
description: "Number of times this table has been manually analyzed"
- autoanalyze_count:
usage: "COUNTER"
description: "Number of times this table has been analyzed by the autovacuum daemon"
Loading