Skip to content

Commit 01e4eb4

Browse files
William RouesnelWilliam Rouesnel
authored andcommitted
Add support for collecting runtime variables.
1 parent c931b83 commit 01e4eb4

File tree

1 file changed

+107
-4
lines changed

1 file changed

+107
-4
lines changed

postgres_exporter.go

Lines changed: 107 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
_ "github.com/lib/pq"
1717
"github.com/prometheus/client_golang/prometheus"
1818
"github.com/prometheus/log"
19+
"strconv"
1920
)
2021

2122
var (
@@ -56,9 +57,11 @@ const (
5657
COUNTER ColumnUsage = iota // Use this column as a counter
5758
GAUGE ColumnUsage = iota // Use this column as a gauge
5859
MAPPEDMETRIC ColumnUsage = iota // Use this column with the supplied mapping of text values
60+
DURATION ColumnUsage = iota // This column should be interpreted as a text duration (and converted to milliseconds)
5961
)
6062

61-
63+
// Which metric mapping should be acquired using "SHOW" queries
64+
const SHOW_METRIC = "pg_runtime_variables"
6265

6366
// User-friendly representation of a prometheus descriptor map
6467
type ColumnMapping struct {
@@ -79,10 +82,27 @@ type MetricMap struct {
7982
discard bool // Should metric be discarded during mapping?
8083
vtype prometheus.ValueType // Prometheus valuetype
8184
desc *prometheus.Desc // Prometheus descriptor
82-
mapping map[string]float64 // If not nil, maps text values to float64s
85+
conversion func(interface{}) (float64, bool) // Conversion function to turn PG result into float64
8386
}
8487

8588
// Metric descriptors for dynamically created metrics.
89+
var variableMaps = map[string]map[string]ColumnMapping{
90+
"pg_runtime_variable" : map[string]ColumnMapping {
91+
"max_connections" : {GAUGE, "Sets the maximum number of concurrent connections." , nil},
92+
"max_files_per_process" : {GAUGE, "Sets the maximum number of simultaneously open files for each server process.", nil },
93+
"max_function_args" : {GAUGE, "Shows the maximum number of function arguments.", nil },
94+
"max_identifier_length" : {GAUGE, "Shows the maximum identifier length.", nil },
95+
"max_index_keys" : {GAUGE, "Shows the maximum number of index keys.", nil },
96+
"max_locks_per_transaction" : {GAUGE, "Sets the maximum number of locks per transaction.", nil },
97+
"max_pred_locks_per_transaction" : {GAUGE, "Sets the maximum number of predicate locks per transaction.", nil },
98+
"max_prepared_transactions" : {GAUGE, "Sets the maximum number of simultaneously prepared transactions.", nil },
99+
//"max_stack_depth" : { GAUGE, "Sets the maximum number of concurrent connections.", nil }, // No dehumanize support yet
100+
"max_standby_archive_delay" : {DURATION, "Sets the maximum delay before canceling queries when a hot standby server is processing archived WAL data.", nil },
101+
"max_standby_streaming_delay" : {DURATION, "Sets the maximum delay before canceling queries when a hot standby server is processing streamed WAL data.", nil },
102+
"max_wal_senders" : {GAUGE, "Sets the maximum number of simultaneously running WAL sender processes.", nil },
103+
},
104+
}
105+
86106
var metricMaps = map[string]map[string]ColumnMapping {
87107
"pg_stat_bgwriter" : map[string]ColumnMapping {
88108
"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
149169
case DISCARD, LABEL:
150170
thisMap[columnName] = MetricMap{
151171
discard : true,
172+
conversion: func(in interface{}) (float64, bool) {
173+
return math.NaN(), true
174+
},
152175
}
153176
case COUNTER:
154177
thisMap[columnName] = MetricMap{
155178
vtype : prometheus.CounterValue,
156179
desc : prometheus.NewDesc(fmt.Sprintf("%s_%s", namespace, columnName), columnMapping.description, constLabels, nil),
180+
conversion: func(in interface{}) (float64, bool) {
181+
return dbToFloat64(in)
182+
},
157183
}
158184
case GAUGE:
159185
thisMap[columnName] = MetricMap{
160186
vtype : prometheus.GaugeValue,
161187
desc : prometheus.NewDesc(fmt.Sprintf("%s_%s", namespace, columnName), columnMapping.description, constLabels, nil),
188+
conversion: func(in interface{}) (float64, bool) {
189+
return dbToFloat64(in)
190+
},
162191
}
163192
case MAPPEDMETRIC:
164193
thisMap[columnName] = MetricMap{
165194
vtype : prometheus.GaugeValue,
166195
desc : prometheus.NewDesc(fmt.Sprintf("%s_%s", namespace, columnName), columnMapping.description, constLabels, nil),
167-
mapping: columnMapping.mapping,
196+
conversion: func(in interface{}) (float64, bool) {
197+
text, ok := in.(string)
198+
if !ok {
199+
return math.NaN(), false
200+
}
201+
202+
val, ok := columnMapping.mapping[text]
203+
if !ok {
204+
return math.NaN(), false
205+
}
206+
return val, true
207+
},
208+
}
209+
case DURATION:
210+
thisMap[columnName] = MetricMap{
211+
vtype : prometheus.GaugeValue,
212+
desc : prometheus.NewDesc(fmt.Sprintf("%s_%s_milliseconds", namespace, columnName), columnMapping.description, constLabels, nil),
213+
conversion: func(in interface{}) (float64, bool) {
214+
var durationString string
215+
switch t := in.(type) {
216+
case []byte:
217+
durationString = string(t)
218+
case string:
219+
durationString = t
220+
default:
221+
log.Errorln("DURATION conversion metric was not a string")
222+
return math.NaN(), false
223+
}
224+
225+
d, err := time.ParseDuration(durationString)
226+
if err != nil {
227+
log.Errorln("Failed converting result to metric:", columnName, in, err)
228+
return math.NaN(), false
229+
}
230+
return float64(d / time.Millisecond), true
231+
},
168232
}
169233
}
170234
}
@@ -185,6 +249,14 @@ func dbToFloat64(t interface{}) (float64, bool) {
185249
return v, true
186250
case time.Time:
187251
return float64(v.Unix()), true
252+
case []byte:
253+
// Try and convert to string and then parse to a float64
254+
strV := string(v)
255+
result, err := strconv.ParseFloat(strV, 64)
256+
if err != nil {
257+
return math.NaN(), false
258+
}
259+
return result, true
188260
case nil:
189261
return math.NaN(), true
190262
default:
@@ -213,11 +285,12 @@ func dbToString(t interface{}) (string, bool) {
213285
}
214286
}
215287

216-
// Exporter collects MySQL metrics. It implements prometheus.Collector.
288+
// Exporter collects Postgres metrics. It implements prometheus.Collector.
217289
type Exporter struct {
218290
dsn string
219291
duration, error prometheus.Gauge
220292
totalScrapes prometheus.Counter
293+
variableMap map[string]MetricMapNamespace
221294
metricMap map[string]MetricMapNamespace
222295
}
223296

@@ -243,6 +316,7 @@ func NewExporter(dsn string) *Exporter {
243316
Name: "last_scrape_error",
244317
Help: "Whether the last scrape of metrics from PostgreSQL resulted in an error (1 for error, 0 for success).",
245318
}),
319+
variableMap : makeDescMap(variableMaps),
246320
metricMap : makeDescMap(metricMaps),
247321
}
248322
}
@@ -307,6 +381,35 @@ func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
307381
}
308382
defer db.Close()
309383

384+
log.Debugln("Querying SHOW variables")
385+
for _, mapping := range e.variableMap {
386+
for columnName, columnMapping := range mapping.columnMappings {
387+
// Check for a discard request on this value
388+
if columnMapping.discard {
389+
continue
390+
}
391+
392+
// Use SHOW to get the value
393+
row := db.QueryRow(fmt.Sprintf("SHOW %s;", columnName))
394+
395+
var val interface{};
396+
err := row.Scan(&val)
397+
if err != nil {
398+
log.Errorln("Error scanning runtime variable:", columnName, err)
399+
continue
400+
}
401+
402+
fval, ok := columnMapping.conversion(val)
403+
if ! ok {
404+
e.error.Set(1)
405+
log.Errorln("Unexpected error parsing column: ", namespace, columnName, val)
406+
continue
407+
}
408+
409+
ch <- prometheus.MustNewConstMetric(columnMapping.desc, columnMapping.vtype, fval)
410+
}
411+
}
412+
310413
for namespace, mapping := range e.metricMap {
311414
log.Debugln("Querying namespace: ", namespace)
312415
func () { // Don't fail on a bad scrape of one metric

0 commit comments

Comments
 (0)