mirror of
https://github.com/prometheus-community/postgres_exporter
synced 2025-04-21 14:35:35 +00:00
Add support for collecting runtime variables.
This commit is contained in:
parent
c931b83b7c
commit
01e4eb4e67
@ -16,6 +16,7 @@ import (
|
|||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/log"
|
"github.com/prometheus/log"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -56,9 +57,11 @@ const (
|
|||||||
COUNTER ColumnUsage = iota // Use this column as a counter
|
COUNTER ColumnUsage = iota // Use this column as a counter
|
||||||
GAUGE ColumnUsage = iota // Use this column as a gauge
|
GAUGE ColumnUsage = iota // Use this column as a gauge
|
||||||
MAPPEDMETRIC ColumnUsage = iota // Use this column with the supplied mapping of text values
|
MAPPEDMETRIC ColumnUsage = iota // Use this column with the supplied mapping of text values
|
||||||
|
DURATION ColumnUsage = iota // This column should be interpreted as a text duration (and converted to milliseconds)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Which metric mapping should be acquired using "SHOW" queries
|
||||||
|
const SHOW_METRIC = "pg_runtime_variables"
|
||||||
|
|
||||||
// User-friendly representation of a prometheus descriptor map
|
// User-friendly representation of a prometheus descriptor map
|
||||||
type ColumnMapping struct {
|
type ColumnMapping struct {
|
||||||
@ -79,10 +82,27 @@ type MetricMap struct {
|
|||||||
discard bool // Should metric be discarded during mapping?
|
discard bool // Should metric be discarded during mapping?
|
||||||
vtype prometheus.ValueType // Prometheus valuetype
|
vtype prometheus.ValueType // Prometheus valuetype
|
||||||
desc *prometheus.Desc // Prometheus descriptor
|
desc *prometheus.Desc // Prometheus descriptor
|
||||||
mapping map[string]float64 // If not nil, maps text values to float64s
|
conversion func(interface{}) (float64, bool) // Conversion function to turn PG result into float64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metric descriptors for dynamically created metrics.
|
// Metric descriptors for dynamically created metrics.
|
||||||
|
var variableMaps = map[string]map[string]ColumnMapping{
|
||||||
|
"pg_runtime_variable" : map[string]ColumnMapping {
|
||||||
|
"max_connections" : {GAUGE, "Sets the maximum number of concurrent connections." , nil},
|
||||||
|
"max_files_per_process" : {GAUGE, "Sets the maximum number of simultaneously open files for each server process.", nil },
|
||||||
|
"max_function_args" : {GAUGE, "Shows the maximum number of function arguments.", nil },
|
||||||
|
"max_identifier_length" : {GAUGE, "Shows the maximum identifier length.", nil },
|
||||||
|
"max_index_keys" : {GAUGE, "Shows the maximum number of index keys.", nil },
|
||||||
|
"max_locks_per_transaction" : {GAUGE, "Sets the maximum number of locks per transaction.", nil },
|
||||||
|
"max_pred_locks_per_transaction" : {GAUGE, "Sets the maximum number of predicate locks per transaction.", nil },
|
||||||
|
"max_prepared_transactions" : {GAUGE, "Sets the maximum number of simultaneously prepared transactions.", nil },
|
||||||
|
//"max_stack_depth" : { GAUGE, "Sets the maximum number of concurrent connections.", nil }, // No dehumanize support yet
|
||||||
|
"max_standby_archive_delay" : {DURATION, "Sets the maximum delay before canceling queries when a hot standby server is processing archived WAL data.", nil },
|
||||||
|
"max_standby_streaming_delay" : {DURATION, "Sets the maximum delay before canceling queries when a hot standby server is processing streamed WAL data.", nil },
|
||||||
|
"max_wal_senders" : {GAUGE, "Sets the maximum number of simultaneously running WAL sender processes.", nil },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
var metricMaps = map[string]map[string]ColumnMapping {
|
var metricMaps = map[string]map[string]ColumnMapping {
|
||||||
"pg_stat_bgwriter" : map[string]ColumnMapping {
|
"pg_stat_bgwriter" : map[string]ColumnMapping {
|
||||||
"checkpoints_timed" : { COUNTER, "Number of scheduled checkpoints that have been performed", nil },
|
"checkpoints_timed" : { COUNTER, "Number of scheduled checkpoints that have been performed", nil },
|
||||||
@ -149,22 +169,66 @@ func makeDescMap(metricMaps map[string]map[string]ColumnMapping) map[string]Metr
|
|||||||
case DISCARD, LABEL:
|
case DISCARD, LABEL:
|
||||||
thisMap[columnName] = MetricMap{
|
thisMap[columnName] = MetricMap{
|
||||||
discard : true,
|
discard : true,
|
||||||
|
conversion: func(in interface{}) (float64, bool) {
|
||||||
|
return math.NaN(), true
|
||||||
|
},
|
||||||
}
|
}
|
||||||
case COUNTER:
|
case COUNTER:
|
||||||
thisMap[columnName] = MetricMap{
|
thisMap[columnName] = MetricMap{
|
||||||
vtype : prometheus.CounterValue,
|
vtype : prometheus.CounterValue,
|
||||||
desc : prometheus.NewDesc(fmt.Sprintf("%s_%s", namespace, columnName), columnMapping.description, constLabels, nil),
|
desc : prometheus.NewDesc(fmt.Sprintf("%s_%s", namespace, columnName), columnMapping.description, constLabels, nil),
|
||||||
|
conversion: func(in interface{}) (float64, bool) {
|
||||||
|
return dbToFloat64(in)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
case GAUGE:
|
case GAUGE:
|
||||||
thisMap[columnName] = MetricMap{
|
thisMap[columnName] = MetricMap{
|
||||||
vtype : prometheus.GaugeValue,
|
vtype : prometheus.GaugeValue,
|
||||||
desc : prometheus.NewDesc(fmt.Sprintf("%s_%s", namespace, columnName), columnMapping.description, constLabels, nil),
|
desc : prometheus.NewDesc(fmt.Sprintf("%s_%s", namespace, columnName), columnMapping.description, constLabels, nil),
|
||||||
|
conversion: func(in interface{}) (float64, bool) {
|
||||||
|
return dbToFloat64(in)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
case MAPPEDMETRIC:
|
case MAPPEDMETRIC:
|
||||||
thisMap[columnName] = MetricMap{
|
thisMap[columnName] = MetricMap{
|
||||||
vtype : prometheus.GaugeValue,
|
vtype : prometheus.GaugeValue,
|
||||||
desc : prometheus.NewDesc(fmt.Sprintf("%s_%s", namespace, columnName), columnMapping.description, constLabels, nil),
|
desc : prometheus.NewDesc(fmt.Sprintf("%s_%s", namespace, columnName), columnMapping.description, constLabels, nil),
|
||||||
mapping: columnMapping.mapping,
|
conversion: func(in interface{}) (float64, bool) {
|
||||||
|
text, ok := in.(string)
|
||||||
|
if !ok {
|
||||||
|
return math.NaN(), false
|
||||||
|
}
|
||||||
|
|
||||||
|
val, ok := columnMapping.mapping[text]
|
||||||
|
if !ok {
|
||||||
|
return math.NaN(), false
|
||||||
|
}
|
||||||
|
return val, true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
case DURATION:
|
||||||
|
thisMap[columnName] = MetricMap{
|
||||||
|
vtype : prometheus.GaugeValue,
|
||||||
|
desc : prometheus.NewDesc(fmt.Sprintf("%s_%s_milliseconds", namespace, columnName), columnMapping.description, constLabels, nil),
|
||||||
|
conversion: func(in interface{}) (float64, bool) {
|
||||||
|
var durationString string
|
||||||
|
switch t := in.(type) {
|
||||||
|
case []byte:
|
||||||
|
durationString = string(t)
|
||||||
|
case string:
|
||||||
|
durationString = t
|
||||||
|
default:
|
||||||
|
log.Errorln("DURATION conversion metric was not a string")
|
||||||
|
return math.NaN(), false
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := time.ParseDuration(durationString)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln("Failed converting result to metric:", columnName, in, err)
|
||||||
|
return math.NaN(), false
|
||||||
|
}
|
||||||
|
return float64(d / time.Millisecond), true
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -185,6 +249,14 @@ func dbToFloat64(t interface{}) (float64, bool) {
|
|||||||
return v, true
|
return v, true
|
||||||
case time.Time:
|
case time.Time:
|
||||||
return float64(v.Unix()), true
|
return float64(v.Unix()), true
|
||||||
|
case []byte:
|
||||||
|
// Try and convert to string and then parse to a float64
|
||||||
|
strV := string(v)
|
||||||
|
result, err := strconv.ParseFloat(strV, 64)
|
||||||
|
if err != nil {
|
||||||
|
return math.NaN(), false
|
||||||
|
}
|
||||||
|
return result, true
|
||||||
case nil:
|
case nil:
|
||||||
return math.NaN(), true
|
return math.NaN(), true
|
||||||
default:
|
default:
|
||||||
@ -213,11 +285,12 @@ func dbToString(t interface{}) (string, bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exporter collects MySQL metrics. It implements prometheus.Collector.
|
// Exporter collects Postgres metrics. It implements prometheus.Collector.
|
||||||
type Exporter struct {
|
type Exporter struct {
|
||||||
dsn string
|
dsn string
|
||||||
duration, error prometheus.Gauge
|
duration, error prometheus.Gauge
|
||||||
totalScrapes prometheus.Counter
|
totalScrapes prometheus.Counter
|
||||||
|
variableMap map[string]MetricMapNamespace
|
||||||
metricMap map[string]MetricMapNamespace
|
metricMap map[string]MetricMapNamespace
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,6 +316,7 @@ func NewExporter(dsn string) *Exporter {
|
|||||||
Name: "last_scrape_error",
|
Name: "last_scrape_error",
|
||||||
Help: "Whether the last scrape of metrics from PostgreSQL resulted in an error (1 for error, 0 for success).",
|
Help: "Whether the last scrape of metrics from PostgreSQL resulted in an error (1 for error, 0 for success).",
|
||||||
}),
|
}),
|
||||||
|
variableMap : makeDescMap(variableMaps),
|
||||||
metricMap : makeDescMap(metricMaps),
|
metricMap : makeDescMap(metricMaps),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -307,6 +381,35 @@ func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
|
|||||||
}
|
}
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
|
log.Debugln("Querying SHOW variables")
|
||||||
|
for _, mapping := range e.variableMap {
|
||||||
|
for columnName, columnMapping := range mapping.columnMappings {
|
||||||
|
// Check for a discard request on this value
|
||||||
|
if columnMapping.discard {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use SHOW to get the value
|
||||||
|
row := db.QueryRow(fmt.Sprintf("SHOW %s;", columnName))
|
||||||
|
|
||||||
|
var val interface{};
|
||||||
|
err := row.Scan(&val)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln("Error scanning runtime variable:", columnName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fval, ok := columnMapping.conversion(val)
|
||||||
|
if ! ok {
|
||||||
|
e.error.Set(1)
|
||||||
|
log.Errorln("Unexpected error parsing column: ", namespace, columnName, val)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ch <- prometheus.MustNewConstMetric(columnMapping.desc, columnMapping.vtype, fval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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 () { // Don't fail on a bad scrape of one metric
|
||||||
|
Loading…
Reference in New Issue
Block a user