4
4
"database/sql"
5
5
"flag"
6
6
"fmt"
7
- // "io/ioutil"
7
+ "io/ioutil"
8
8
"math"
9
9
"net/http"
10
10
"os"
@@ -13,7 +13,7 @@ import (
13
13
"regexp"
14
14
"errors"
15
15
16
- // "gopkg.in/yaml.v2"
16
+ "gopkg.in/yaml.v2"
17
17
18
18
_ "github.com/lib/pq"
19
19
"github.com/prometheus/client_golang/prometheus"
41
41
"dumpmaps" , false ,
42
42
"Do not run, simply dump the maps." ,
43
43
)
44
- expectReplicationStats = flag .Bool (
45
- "config.expect-replication-stats" , false ,
46
- "The target database has replication configured, log missing replication stats as an error." ,
47
- )
48
44
)
49
45
50
46
// Metric name parts.
@@ -71,6 +67,22 @@ var landingPage = []byte(`<html>
71
67
72
68
type ColumnUsage int
73
69
70
+ // Implements the yaml.Unmarshaller interface
71
+ func (this * ColumnUsage ) UnmarshalYAML (unmarshal func (interface {}) error ) error {
72
+ var value string
73
+ if err := unmarshal (& value ); err != nil {
74
+ return err
75
+ }
76
+
77
+ columnUsage , err := stringToColumnUsage (value )
78
+ if err != nil {
79
+ return err
80
+ }
81
+
82
+ * this = columnUsage
83
+ return nil
84
+ }
85
+
74
86
const (
75
87
DISCARD ColumnUsage = iota // Ignore this column
76
88
LABEL ColumnUsage = iota // Use this column as a label
@@ -103,10 +115,18 @@ func parseVersion(versionString string) (semver.Version, error) {
103
115
104
116
// User-friendly representation of a prometheus descriptor map
105
117
type ColumnMapping struct {
106
- usage ColumnUsage
107
- description string
108
- mapping map [string ]float64 // Optional column mapping for MAPPEDMETRIC
109
- supportedVersions semver.Range // Semantic version ranges which are supported. Unsupported columns are not queried (internally converted to DISCARD).
118
+ usage ColumnUsage `yaml:"usage"`
119
+ description string `yaml:"description"`
120
+ mapping map [string ]float64 `yaml:"metric_mapping"` // Optional column mapping for MAPPEDMETRIC
121
+ supportedVersions semver.Range `yaml:"pg_version"` // Semantic version ranges which are supported. Unsupported columns are not queried (internally converted to DISCARD).
122
+ }
123
+
124
+ func (this * ColumnMapping ) UnmarshalYAML (unmarshal func (interface {}) error ) error {
125
+ type plain ColumnMapping
126
+ if err := unmarshal ((* plain )(this )); err != nil {
127
+ return err
128
+ }
129
+ return nil
110
130
}
111
131
112
132
// Groups metric maps under a shared set of labels
@@ -364,67 +384,110 @@ func makeQueryOverrideMap(pgVersion semver.Version, queryOverrides map[string][]
364
384
return resultMap
365
385
}
366
386
367
- // Add queries to the metricMaps and queryOverrides maps
368
- //func addQueries(queriesPath string) (err error) {
369
- // var extra map[string]interface{}
370
- //
371
- // content, err := ioutil.ReadFile(queriesPath)
372
- // if err != nil {
373
- // return err
374
- // }
375
- //
376
- // err = yaml.Unmarshal(content, &extra)
377
- // if err != nil {
378
- // return err
379
- // }
380
- //
381
- // for metric, specs := range extra {
382
- // for key, value := range specs.(map[interface{}]interface{}) {
383
- // switch key.(string) {
384
- // case "query":
385
- // query := value.(string)
386
- // queryOverrides[metric] = query
387
+ // Add queries to the metricMaps and queryOverrides maps. Added queries do not
388
+ // respect version requirements, because it is assumed that the user knows
389
+ // what they are doing with their version of postgres.
387
390
//
388
- // case "metrics":
389
- // for _, c := range value.([]interface{}) {
390
- // column := c.(map[interface{}]interface{})
391
- //
392
- // for n, a := range column {
393
- // var cmap ColumnMapping
394
- //
395
- // metric_map, ok := metricMaps[metric]
396
- // if !ok {
397
- // metric_map = make(map[string]ColumnMapping)
398
- // }
399
- //
400
- // name := n.(string)
401
- //
402
- // for attr_key, attr_val := range a.(map[interface{}]interface{}) {
403
- // switch attr_key.(string) {
404
- // case "usage":
405
- // usage, err := stringToColumnUsage(attr_val.(string))
406
- // if err != nil {
407
- // return err
408
- // }
409
- // cmap.usage = usage
410
- // case "description":
411
- // cmap.description = attr_val.(string)
412
- // }
413
- // }
414
- //
415
- // cmap.mapping = nil
416
- //
417
- // metric_map[name] = cmap
418
- //
419
- // metricMaps[metric] = metric_map
420
- // }
421
- // }
422
- // }
423
- // }
424
- // }
425
- //
426
- // return
427
- //}
391
+ // This function modifies metricMap and queryOverrideMap to contain the new
392
+ // queries.
393
+ // TODO: test code for all this.
394
+ // TODO: use proper struct type system
395
+ // TODO: the YAML this supports is "non-standard" - we should move away from it.
396
+ func addQueries (queriesPath string , pgVersion semver.Version , exporterMap map [string ]MetricMapNamespace , queryOverrideMap map [string ]string ) error {
397
+ var extra map [string ]interface {}
398
+
399
+ content , err := ioutil .ReadFile (queriesPath )
400
+ if err != nil {
401
+ return err
402
+ }
403
+
404
+ err = yaml .Unmarshal (content , & extra )
405
+ if err != nil {
406
+ return err
407
+ }
408
+
409
+ // Stores the loaded map representation
410
+ metricMaps := make (map [string ]map [string ]ColumnMapping )
411
+ newQueryOverrides := make (map [string ]string )
412
+
413
+ for metric , specs := range extra {
414
+ log .Debugln ("New user metric namespace from YAML:" , metric )
415
+ for key , value := range specs .(map [interface {}]interface {}) {
416
+ switch key .(string ) {
417
+ case "query" :
418
+ query := value .(string )
419
+ newQueryOverrides [metric ] = query
420
+
421
+ case "metrics" :
422
+ for _ , c := range value .([]interface {}) {
423
+ column := c .(map [interface {}]interface {})
424
+
425
+ for n , a := range column {
426
+ var columnMapping ColumnMapping
427
+
428
+ // Fetch the metric map we want to work on.
429
+ metricMap , ok := metricMaps [metric ]
430
+ if ! ok {
431
+ // Namespace for metric not found - add it.
432
+ metricMap = make (map [string ]ColumnMapping )
433
+ metricMaps [metric ] = metricMap
434
+ }
435
+
436
+ // Get name.
437
+ name := n .(string )
438
+
439
+ for attrKey , attrVal := range a .(map [interface {}]interface {}) {
440
+ switch attrKey .(string ) {
441
+ case "usage" :
442
+ usage , err := stringToColumnUsage (attrVal .(string ))
443
+ if err != nil {
444
+ return err
445
+ }
446
+ columnMapping .usage = usage
447
+ case "description" :
448
+ columnMapping .description = attrVal .(string )
449
+ }
450
+ }
451
+
452
+ // TODO: we should support this
453
+ columnMapping .mapping = nil
454
+ // Should we support this for users?
455
+ columnMapping .supportedVersions = nil
456
+
457
+ metricMap [name ] = columnMapping
458
+ }
459
+ }
460
+ }
461
+ }
462
+ }
463
+
464
+ // Convert the loaded metric map into exporter representation
465
+ partialExporterMap := makeDescMap (pgVersion , metricMaps )
466
+
467
+ // Merge the two maps (which are now quite flatteend)
468
+ for k , v := range partialExporterMap {
469
+ _ , found := exporterMap [k ]
470
+ if found {
471
+ log .Debugln ("Overriding metric" , k , "from user YAML file." )
472
+ } else {
473
+ log .Debugln ("Adding new metric" , k , "from user YAML file." )
474
+ }
475
+ exporterMap [k ] = v
476
+ }
477
+
478
+ // Merge the query override map
479
+ for k , v := range newQueryOverrides {
480
+ _ , found := queryOverrideMap [k ]
481
+ if found {
482
+ log .Debugln ("Overriding query override" , k , "from user YAML file." )
483
+ } else {
484
+ log .Debugln ("Adding new query override" , k , "from user YAML file." )
485
+ }
486
+ queryOverrideMap [k ] = v
487
+ }
488
+
489
+ return nil
490
+ }
428
491
429
492
// Turn the MetricMap column mapping into a prometheus descriptor mapping.
430
493
func makeDescMap (pgVersion semver.Version , metricMaps map [string ]map [string ]ColumnMapping ) map [string ]MetricMapNamespace {
@@ -619,6 +682,7 @@ func dbToString(t interface{}) (string, bool) {
619
682
// Exporter collects Postgres metrics. It implements prometheus.Collector.
620
683
type Exporter struct {
621
684
dsn string
685
+ userQueriesPath string
622
686
duration , error prometheus.Gauge
623
687
totalScrapes prometheus.Counter
624
688
@@ -635,9 +699,10 @@ type Exporter struct {
635
699
}
636
700
637
701
// NewExporter returns a new PostgreSQL exporter for the provided DSN.
638
- func NewExporter (dsn string ) * Exporter {
702
+ func NewExporter (dsn string , userQueriesPath string ) * Exporter {
639
703
return & Exporter {
640
704
dsn : dsn ,
705
+ userQueriesPath : userQueriesPath ,
641
706
duration : prometheus .NewGauge (prometheus.GaugeOpts {
642
707
Namespace : namespace ,
643
708
Subsystem : exporter ,
@@ -881,6 +946,12 @@ func (e *Exporter) checkMapVersions(ch chan<- prometheus.Metric, db *sql.DB) err
881
946
e .queryOverrides = makeQueryOverrideMap (semanticVersion , queryOverrides )
882
947
e .lastMapVersion = semanticVersion
883
948
949
+ if e .userQueriesPath != "" {
950
+ if err := addQueries (e .userQueriesPath , semanticVersion , e .metricMap , e .queryOverrides ) ; err != nil {
951
+ log .Errorln ("Failed to reload user queries:" , e .userQueriesPath , err )
952
+ }
953
+ }
954
+
884
955
e .mappingMtx .Unlock ()
885
956
}
886
957
@@ -933,14 +1004,6 @@ func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
933
1004
func main () {
934
1005
flag .Parse ()
935
1006
936
- // TODO: restroe addQueries functionality
937
- //if *queriesPath != "" {
938
- // err := addQueries(*queriesPath)
939
- // if err != nil {
940
- // log.Warnln("Unparseable queries file - discarding merge: ", *queriesPath, err)
941
- // }
942
- //}
943
-
944
1007
if * onlyDumpMaps {
945
1008
dumpMaps ()
946
1009
return
@@ -951,7 +1014,7 @@ func main() {
951
1014
log .Fatal ("couldn't find environment variable DATA_SOURCE_NAME" )
952
1015
}
953
1016
954
- exporter := NewExporter (dsn )
1017
+ exporter := NewExporter (dsn , * queriesPath )
955
1018
prometheus .MustRegister (exporter )
956
1019
957
1020
http .Handle (* metricPath , prometheus .Handler ())
0 commit comments