@@ -80,6 +80,7 @@ const (
80
80
GAUGE ColumnUsage = iota // Use this column as a gauge
81
81
MAPPEDMETRIC ColumnUsage = iota // Use this column with the supplied mapping of text values
82
82
DURATION ColumnUsage = iota // This column should be interpreted as a text duration (and converted to milliseconds)
83
+ HISTOGRAM ColumnUsage = iota // Use this column as a histogram
83
84
)
84
85
85
86
// UnmarshalYAML implements the yaml.Unmarshaller interface.
@@ -169,6 +170,7 @@ type MetricMapNamespace struct {
169
170
// be mapped to by the collector
170
171
type MetricMap struct {
171
172
discard bool // Should metric be discarded during mapping?
173
+ histogram bool // Should metric be treated as a histogram?
172
174
vtype prometheus.ValueType // Prometheus valuetype
173
175
desc * prometheus.Desc // Prometheus descriptor
174
176
conversion func (interface {}) (float64 , bool ) // Conversion function to turn PG result into float64
@@ -650,6 +652,27 @@ func makeDescMap(pgVersion semver.Version, serverLabels prometheus.Labels, metri
650
652
return dbToFloat64 (in )
651
653
},
652
654
}
655
+ case HISTOGRAM :
656
+ thisMap [columnName ] = MetricMap {
657
+ histogram : true ,
658
+ vtype : prometheus .UntypedValue ,
659
+ desc : prometheus .NewDesc (fmt .Sprintf ("%s_%s" , namespace , columnName ), columnMapping .description , variableLabels , serverLabels ),
660
+ conversion : func (in interface {}) (float64 , bool ) {
661
+ return dbToFloat64 (in )
662
+ },
663
+ }
664
+ thisMap [columnName + "_bucket" ] = MetricMap {
665
+ histogram : true ,
666
+ discard : true ,
667
+ }
668
+ thisMap [columnName + "_sum" ] = MetricMap {
669
+ histogram : true ,
670
+ discard : true ,
671
+ }
672
+ thisMap [columnName + "_count" ] = MetricMap {
673
+ histogram : true ,
674
+ discard : true ,
675
+ }
653
676
case MAPPEDMETRIC :
654
677
thisMap [columnName ] = MetricMap {
655
678
vtype : prometheus .GaugeValue ,
@@ -721,6 +744,9 @@ func stringToColumnUsage(s string) (ColumnUsage, error) {
721
744
case "GAUGE" :
722
745
u = GAUGE
723
746
747
+ case "HISTOGRAM" :
748
+ u = HISTOGRAM
749
+
724
750
case "MAPPEDMETRIC" :
725
751
u = MAPPEDMETRIC
726
752
@@ -772,6 +798,46 @@ func dbToFloat64(t interface{}) (float64, bool) {
772
798
}
773
799
}
774
800
801
+ // Convert database.sql types to uint64 for Prometheus consumption. Null types are mapped to 0. string and []byte
802
+ // types are mapped as 0 and !ok
803
+ func dbToUint64 (t interface {}) (uint64 , bool ) {
804
+ switch v := t .(type ) {
805
+ case uint64 :
806
+ return v , true
807
+ case int64 :
808
+ return uint64 (v ), true
809
+ case float64 :
810
+ return uint64 (v ), true
811
+ case time.Time :
812
+ return uint64 (v .Unix ()), true
813
+ case []byte :
814
+ // Try and convert to string and then parse to a uint64
815
+ strV := string (v )
816
+ result , err := strconv .ParseUint (strV , 10 , 64 )
817
+ if err != nil {
818
+ log .Infoln ("Could not parse []byte:" , err )
819
+ return 0 , false
820
+ }
821
+ return result , true
822
+ case string :
823
+ result , err := strconv .ParseUint (v , 10 , 64 )
824
+ if err != nil {
825
+ log .Infoln ("Could not parse string:" , err )
826
+ return 0 , false
827
+ }
828
+ return result , true
829
+ case bool :
830
+ if v {
831
+ return 1 , true
832
+ }
833
+ return 0 , true
834
+ case nil :
835
+ return 0 , true
836
+ default :
837
+ return 0 , false
838
+ }
839
+ }
840
+
775
841
// Convert database.sql to string for Prometheus labels. Null types are mapped to empty strings.
776
842
func dbToString (t interface {}) (string , bool ) {
777
843
switch v := t .(type ) {
@@ -1304,13 +1370,68 @@ func queryNamespaceMapping(server *Server, namespace string, mapping MetricMapNa
1304
1370
continue
1305
1371
}
1306
1372
1307
- value , ok := dbToFloat64 (columnData [idx ])
1308
- if ! ok {
1309
- nonfatalErrors = append (nonfatalErrors , errors .New (fmt .Sprintln ("Unexpected error parsing column: " , namespace , columnName , columnData [idx ])))
1310
- continue
1373
+ if metricMapping .histogram {
1374
+ var keys []float64
1375
+ err = pq .Array (& keys ).Scan (columnData [idx ])
1376
+ if err != nil {
1377
+ return []prometheus.Metric {}, []error {}, errors .New (fmt .Sprintln ("Error retrieving" , columnName , "buckets:" , namespace , err ))
1378
+ }
1379
+
1380
+ var values []int64
1381
+ valuesIdx , ok := columnIdx [columnName + "_bucket" ]
1382
+ if ! ok {
1383
+ nonfatalErrors = append (nonfatalErrors , errors .New (fmt .Sprintln ("Missing column: " , namespace , columnName + "_bucket" )))
1384
+ continue
1385
+ }
1386
+ err = pq .Array (& values ).Scan (columnData [valuesIdx ])
1387
+ if err != nil {
1388
+ return []prometheus.Metric {}, []error {}, errors .New (fmt .Sprintln ("Error retrieving" , columnName , "bucket values:" , namespace , err ))
1389
+ }
1390
+
1391
+ buckets := make (map [float64 ]uint64 , len (keys ))
1392
+ for i , key := range keys {
1393
+ if i >= len (values ) {
1394
+ break
1395
+ }
1396
+ buckets [key ] = uint64 (values [i ])
1397
+ }
1398
+
1399
+ idx , ok = columnIdx [columnName + "_sum" ]
1400
+ if ! ok {
1401
+ nonfatalErrors = append (nonfatalErrors , errors .New (fmt .Sprintln ("Missing column: " , namespace , columnName + "_sum" )))
1402
+ continue
1403
+ }
1404
+ sum , ok := dbToFloat64 (columnData [idx ])
1405
+ if ! ok {
1406
+ nonfatalErrors = append (nonfatalErrors , errors .New (fmt .Sprintln ("Unexpected error parsing column: " , namespace , columnName + "_sum" , columnData [idx ])))
1407
+ continue
1408
+ }
1409
+
1410
+ idx , ok = columnIdx [columnName + "_count" ]
1411
+ if ! ok {
1412
+ nonfatalErrors = append (nonfatalErrors , errors .New (fmt .Sprintln ("Missing column: " , namespace , columnName + "_count" )))
1413
+ continue
1414
+ }
1415
+ count , ok := dbToUint64 (columnData [idx ])
1416
+ if ! ok {
1417
+ nonfatalErrors = append (nonfatalErrors , errors .New (fmt .Sprintln ("Unexpected error parsing column: " , namespace , columnName + "_count" , columnData [idx ])))
1418
+ continue
1419
+ }
1420
+
1421
+ metric = prometheus .MustNewConstHistogram (
1422
+ metricMapping .desc ,
1423
+ count , sum , buckets ,
1424
+ labels ... ,
1425
+ )
1426
+ } else {
1427
+ value , ok := dbToFloat64 (columnData [idx ])
1428
+ if ! ok {
1429
+ nonfatalErrors = append (nonfatalErrors , errors .New (fmt .Sprintln ("Unexpected error parsing column: " , namespace , columnName , columnData [idx ])))
1430
+ continue
1431
+ }
1432
+ // Generate the metric
1433
+ metric = prometheus .MustNewConstMetric (metricMapping .desc , metricMapping .vtype , value , labels ... )
1311
1434
}
1312
- // Generate the metric
1313
- metric = prometheus .MustNewConstMetric (metricMapping .desc , metricMapping .vtype , value , labels ... )
1314
1435
} else {
1315
1436
// Unknown metric. Report as untyped if scan to float64 works, else note an error too.
1316
1437
metricLabel := fmt .Sprintf ("%s_%s" , namespace , columnName )
0 commit comments