@@ -80,6 +80,7 @@ const (
8080GAUGE  ColumnUsage  =  iota  // Use this column as a gauge 
8181MAPPEDMETRIC  ColumnUsage  =  iota  // Use this column with the supplied mapping of text values 
8282DURATION  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  {
171172discard  bool  // Should metric be discarded during mapping? 
173+ histogram  bool  // Should metric be treated as a histogram? 
172174vtype  prometheus.ValueType  // Prometheus valuetype 
173175desc  * prometheus.Desc  // Prometheus descriptor 
174176conversion  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
650652return  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+ }
653676case  MAPPEDMETRIC :
654677thisMap [columnName ] =  MetricMap {
655678vtype : prometheus .GaugeValue ,
@@ -721,6 +744,9 @@ func stringToColumnUsage(s string) (ColumnUsage, error) {
721744case  "GAUGE" :
722745u  =  GAUGE 
723746
747+ case  "HISTOGRAM" :
748+ u  =  HISTOGRAM 
749+ 
724750case  "MAPPEDMETRIC" :
725751u  =  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 ) {
777843switch  v  :=  t .(type ) {
@@ -1304,13 +1370,68 @@ func queryNamespaceMapping(server *Server, namespace string, mapping MetricMapNa
13041370continue 
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. 
13161437metricLabel  :=  fmt .Sprintf ("%s_%s" , namespace , columnName )
0 commit comments