@@ -16,6 +16,7 @@ import (
16
16
_ "github.com/lib/pq"
17
17
"github.com/prometheus/client_golang/prometheus"
18
18
"github.com/prometheus/log"
19
+ "strconv"
19
20
)
20
21
21
22
var (
@@ -56,9 +57,11 @@ const (
56
57
COUNTER ColumnUsage = iota // Use this column as a counter
57
58
GAUGE ColumnUsage = iota // Use this column as a gauge
58
59
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)
59
61
)
60
62
61
-
63
+ // Which metric mapping should be acquired using "SHOW" queries
64
+ const SHOW_METRIC = "pg_runtime_variables"
62
65
63
66
// User-friendly representation of a prometheus descriptor map
64
67
type ColumnMapping struct {
@@ -79,10 +82,27 @@ type MetricMap struct {
79
82
discard bool // Should metric be discarded during mapping?
80
83
vtype prometheus.ValueType // Prometheus valuetype
81
84
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
83
86
}
84
87
85
88
// 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
+
86
106
var metricMaps = map [string ]map [string ]ColumnMapping {
87
107
"pg_stat_bgwriter" : map [string ]ColumnMapping {
88
108
"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
149
169
case DISCARD , LABEL :
150
170
thisMap [columnName ] = MetricMap {
151
171
discard : true ,
172
+ conversion : func (in interface {}) (float64 , bool ) {
173
+ return math .NaN (), true
174
+ },
152
175
}
153
176
case COUNTER :
154
177
thisMap [columnName ] = MetricMap {
155
178
vtype : prometheus .CounterValue ,
156
179
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
+ },
157
183
}
158
184
case GAUGE :
159
185
thisMap [columnName ] = MetricMap {
160
186
vtype : prometheus .GaugeValue ,
161
187
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
+ },
162
191
}
163
192
case MAPPEDMETRIC :
164
193
thisMap [columnName ] = MetricMap {
165
194
vtype : prometheus .GaugeValue ,
166
195
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
+ },
168
232
}
169
233
}
170
234
}
@@ -185,6 +249,14 @@ func dbToFloat64(t interface{}) (float64, bool) {
185
249
return v , true
186
250
case time.Time :
187
251
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
188
260
case nil :
189
261
return math .NaN (), true
190
262
default :
@@ -213,11 +285,12 @@ func dbToString(t interface{}) (string, bool) {
213
285
}
214
286
}
215
287
216
- // Exporter collects MySQL metrics. It implements prometheus.Collector.
288
+ // Exporter collects Postgres metrics. It implements prometheus.Collector.
217
289
type Exporter struct {
218
290
dsn string
219
291
duration , error prometheus.Gauge
220
292
totalScrapes prometheus.Counter
293
+ variableMap map [string ]MetricMapNamespace
221
294
metricMap map [string ]MetricMapNamespace
222
295
}
223
296
@@ -243,6 +316,7 @@ func NewExporter(dsn string) *Exporter {
243
316
Name : "last_scrape_error" ,
244
317
Help : "Whether the last scrape of metrics from PostgreSQL resulted in an error (1 for error, 0 for success)." ,
245
318
}),
319
+ variableMap : makeDescMap (variableMaps ),
246
320
metricMap : makeDescMap (metricMaps ),
247
321
}
248
322
}
@@ -307,6 +381,35 @@ func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
307
381
}
308
382
defer db .Close ()
309
383
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
+
310
413
for namespace , mapping := range e .metricMap {
311
414
log .Debugln ("Querying namespace: " , namespace )
312
415
func () { // Don't fail on a bad scrape of one metric
0 commit comments