@@ -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
@@ -630,6 +632,27 @@ func makeDescMap(pgVersion semver.Version, serverLabels prometheus.Labels, metri
630
632
return dbToFloat64 (in )
631
633
},
632
634
}
635
+ case HISTOGRAM :
636
+ thisMap [columnName ] = MetricMap {
637
+ histogram : true ,
638
+ vtype : prometheus .UntypedValue ,
639
+ desc : prometheus .NewDesc (fmt .Sprintf ("%s_%s" , namespace , columnName ), columnMapping .description , variableLabels , serverLabels ),
640
+ conversion : func (in interface {}) (float64 , bool ) {
641
+ return dbToFloat64 (in )
642
+ },
643
+ }
644
+ thisMap [columnName + "_bucket" ] = MetricMap {
645
+ histogram : true ,
646
+ discard : true ,
647
+ }
648
+ thisMap [columnName + "_sum" ] = MetricMap {
649
+ histogram : true ,
650
+ discard : true ,
651
+ }
652
+ thisMap [columnName + "_count" ] = MetricMap {
653
+ histogram : true ,
654
+ discard : true ,
655
+ }
633
656
case MAPPEDMETRIC :
634
657
thisMap [columnName ] = MetricMap {
635
658
vtype : prometheus .GaugeValue ,
@@ -701,6 +724,9 @@ func stringToColumnUsage(s string) (ColumnUsage, error) {
701
724
case "GAUGE" :
702
725
u = GAUGE
703
726
727
+ case "HISTOGRAM" :
728
+ u = HISTOGRAM
729
+
704
730
case "MAPPEDMETRIC" :
705
731
u = MAPPEDMETRIC
706
732
@@ -752,6 +778,46 @@ func dbToFloat64(t interface{}) (float64, bool) {
752
778
}
753
779
}
754
780
781
+ // Convert database.sql types to uint64 for Prometheus consumption. Null types are mapped to 0. string and []byte
782
+ // types are mapped as 0 and !ok
783
+ func dbToUint64 (t interface {}) (uint64 , bool ) {
784
+ switch v := t .(type ) {
785
+ case uint64 :
786
+ return v , true
787
+ case int64 :
788
+ return uint64 (v ), true
789
+ case float64 :
790
+ return uint64 (v ), true
791
+ case time.Time :
792
+ return uint64 (v .Unix ()), true
793
+ case []byte :
794
+ // Try and convert to string and then parse to a uint64
795
+ strV := string (v )
796
+ result , err := strconv .ParseUint (strV , 10 , 64 )
797
+ if err != nil {
798
+ log .Infoln ("Could not parse []byte:" , err )
799
+ return 0 , false
800
+ }
801
+ return result , true
802
+ case string :
803
+ result , err := strconv .ParseUint (v , 10 , 64 )
804
+ if err != nil {
805
+ log .Infoln ("Could not parse string:" , err )
806
+ return 0 , false
807
+ }
808
+ return result , true
809
+ case bool :
810
+ if v {
811
+ return 1 , true
812
+ }
813
+ return 0 , true
814
+ case nil :
815
+ return 0 , true
816
+ default :
817
+ return 0 , false
818
+ }
819
+ }
820
+
755
821
// Convert database.sql to string for Prometheus labels. Null types are mapped to empty strings.
756
822
func dbToString (t interface {}) (string , bool ) {
757
823
switch v := t .(type ) {
@@ -1284,13 +1350,68 @@ func queryNamespaceMapping(server *Server, namespace string, mapping MetricMapNa
1284
1350
continue
1285
1351
}
1286
1352
1287
- value , ok := dbToFloat64 (columnData [idx ])
1288
- if ! ok {
1289
- nonfatalErrors = append (nonfatalErrors , errors .New (fmt .Sprintln ("Unexpected error parsing column: " , namespace , columnName , columnData [idx ])))
1290
- continue
1353
+ if metricMapping .histogram {
1354
+ var keys []float64
1355
+ err = pq .Array (& keys ).Scan (columnData [idx ])
1356
+ if err != nil {
1357
+ return []prometheus.Metric {}, []error {}, errors .New (fmt .Sprintln ("Error retrieving" , columnName , "buckets:" , namespace , err ))
1358
+ }
1359
+
1360
+ var values []int64
1361
+ valuesIdx , ok := columnIdx [columnName + "_bucket" ]
1362
+ if ! ok {
1363
+ nonfatalErrors = append (nonfatalErrors , errors .New (fmt .Sprintln ("Missing column: " , namespace , columnName + "_bucket" )))
1364
+ continue
1365
+ }
1366
+ err = pq .Array (& values ).Scan (columnData [valuesIdx ])
1367
+ if err != nil {
1368
+ return []prometheus.Metric {}, []error {}, errors .New (fmt .Sprintln ("Error retrieving" , columnName , "bucket values:" , namespace , err ))
1369
+ }
1370
+
1371
+ buckets := make (map [float64 ]uint64 , len (keys ))
1372
+ for i , key := range keys {
1373
+ if i >= len (values ) {
1374
+ break
1375
+ }
1376
+ buckets [key ] = uint64 (values [i ])
1377
+ }
1378
+
1379
+ idx , ok = columnIdx [columnName + "_sum" ]
1380
+ if ! ok {
1381
+ nonfatalErrors = append (nonfatalErrors , errors .New (fmt .Sprintln ("Missing column: " , namespace , columnName + "_sum" )))
1382
+ continue
1383
+ }
1384
+ sum , ok := dbToFloat64 (columnData [idx ])
1385
+ if ! ok {
1386
+ nonfatalErrors = append (nonfatalErrors , errors .New (fmt .Sprintln ("Unexpected error parsing column: " , namespace , columnName + "_sum" , columnData [idx ])))
1387
+ continue
1388
+ }
1389
+
1390
+ idx , ok = columnIdx [columnName + "_count" ]
1391
+ if ! ok {
1392
+ nonfatalErrors = append (nonfatalErrors , errors .New (fmt .Sprintln ("Missing column: " , namespace , columnName + "_count" )))
1393
+ continue
1394
+ }
1395
+ count , ok := dbToUint64 (columnData [idx ])
1396
+ if ! ok {
1397
+ nonfatalErrors = append (nonfatalErrors , errors .New (fmt .Sprintln ("Unexpected error parsing column: " , namespace , columnName + "_count" , columnData [idx ])))
1398
+ continue
1399
+ }
1400
+
1401
+ metric = prometheus .MustNewConstHistogram (
1402
+ metricMapping .desc ,
1403
+ count , sum , buckets ,
1404
+ labels ... ,
1405
+ )
1406
+ } else {
1407
+ value , ok := dbToFloat64 (columnData [idx ])
1408
+ if ! ok {
1409
+ nonfatalErrors = append (nonfatalErrors , errors .New (fmt .Sprintln ("Unexpected error parsing column: " , namespace , columnName , columnData [idx ])))
1410
+ continue
1411
+ }
1412
+ // Generate the metric
1413
+ metric = prometheus .MustNewConstMetric (metricMapping .desc , metricMapping .vtype , value , labels ... )
1291
1414
}
1292
- // Generate the metric
1293
- metric = prometheus .MustNewConstMetric (metricMapping .desc , metricMapping .vtype , value , labels ... )
1294
1415
} else {
1295
1416
// Unknown metric. Report as untyped if scan to float64 works, else note an error too.
1296
1417
metricLabel := fmt .Sprintf ("%s_%s" , namespace , columnName )
0 commit comments