@@ -80,6 +80,7 @@ const (
8080 GAUGE ColumnUsage = iota // Use this column as a gauge
8181 MAPPEDMETRIC ColumnUsage = iota // Use this column with the supplied mapping of text values
8282 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
8384)
8485
8586// UnmarshalYAML implements the yaml.Unmarshaller interface.
@@ -169,6 +170,7 @@ type MetricMapNamespace struct {
169170// be mapped to by the collector
170171type MetricMap struct {
171172 discard bool // Should metric be discarded during mapping?
173+ histogram bool // Should metric be treated as a histogram?
172174 vtype prometheus.ValueType // Prometheus valuetype
173175 desc * prometheus.Desc // Prometheus descriptor
174176 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
650652 return dbToFloat64 (in )
651653 },
652654 }
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+ }
653676 case MAPPEDMETRIC :
654677 thisMap [columnName ] = MetricMap {
655678 vtype : prometheus .GaugeValue ,
@@ -721,6 +744,9 @@ func stringToColumnUsage(s string) (ColumnUsage, error) {
721744 case "GAUGE" :
722745 u = GAUGE
723746
747+ case "HISTOGRAM" :
748+ u = HISTOGRAM
749+
724750 case "MAPPEDMETRIC" :
725751 u = MAPPEDMETRIC
726752
@@ -772,6 +798,46 @@ func dbToFloat64(t interface{}) (float64, bool) {
772798 }
773799}
774800
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+
775841// Convert database.sql to string for Prometheus labels. Null types are mapped to empty strings.
776842func dbToString (t interface {}) (string , bool ) {
777843 switch v := t .(type ) {
@@ -1304,13 +1370,68 @@ func queryNamespaceMapping(server *Server, namespace string, mapping MetricMapNa
13041370 continue
13051371 }
13061372
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 ... )
13111434 }
1312- // Generate the metric
1313- metric = prometheus .MustNewConstMetric (metricMapping .desc , metricMapping .vtype , value , labels ... )
13141435 } else {
13151436 // Unknown metric. Report as untyped if scan to float64 works, else note an error too.
13161437 metricLabel := fmt .Sprintf ("%s_%s" , namespace , columnName )
0 commit comments