You've already forked postgres_exporter
mirror of
https://github.com/prometheus-community/postgres_exporter.git
synced 2025-08-06 17:22:43 +03:00
240 lines
7.4 KiB
Go
240 lines
7.4 KiB
Go
package metricmaps
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/blang/semver"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
"github.com/prometheus/common/log"
|
|
"math"
|
|
"github.com/wrouesnel/postgres_exporter/exporter/dbconv"
|
|
"time"
|
|
)
|
|
|
|
// ColumnUsage should be one of several enum values which describe how a
|
|
// queried row is to be converted to a Prometheus metric.
|
|
type ColumnUsage int
|
|
|
|
// nolint: golint
|
|
const (
|
|
DISCARD ColumnUsage = iota // Ignore this column
|
|
LABEL ColumnUsage = iota // Use this column as a label
|
|
COUNTER ColumnUsage = iota // Use this column as a counter
|
|
GAUGE ColumnUsage = iota // Use this column as a gauge
|
|
MAPPEDMETRIC ColumnUsage = iota // Use this column with the supplied mapping of text values
|
|
DURATION ColumnUsage = iota // This column should be interpreted as a text duration (and converted to milliseconds)
|
|
)
|
|
|
|
// convert a string to the corresponding ColumnUsage
|
|
func stringToColumnUsage(s string) (ColumnUsage, error) {
|
|
var u ColumnUsage
|
|
var err error
|
|
switch s {
|
|
case "DISCARD":
|
|
u = DISCARD
|
|
|
|
case "LABEL":
|
|
u = LABEL
|
|
|
|
case "COUNTER":
|
|
u = COUNTER
|
|
|
|
case "GAUGE":
|
|
u = GAUGE
|
|
|
|
case "MAPPEDMETRIC":
|
|
u = MAPPEDMETRIC
|
|
|
|
case "DURATION":
|
|
u = DURATION
|
|
|
|
default:
|
|
err = fmt.Errorf("wrong ColumnUsage given : %s", s)
|
|
}
|
|
|
|
return u, err
|
|
}
|
|
|
|
// UnmarshalYAML implements the yaml.Unmarshaller interface.
|
|
func (cu *ColumnUsage) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
var value string
|
|
if err := unmarshal(&value); err != nil {
|
|
return err
|
|
}
|
|
|
|
columnUsage, err := stringToColumnUsage(value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
*cu = columnUsage
|
|
return nil
|
|
}
|
|
|
|
//map[string]map[string]ColumnMapping
|
|
|
|
//type ColumnMap
|
|
|
|
// ColumnMapping is the user-friendly representation of a prometheus descriptor map
|
|
type ColumnMapping struct {
|
|
usage ColumnUsage `yaml:"usage"`
|
|
description string `yaml:"description"`
|
|
mapping map[string]float64 `yaml:"metric_mapping"` // Optional column mapping for MAPPEDMETRIC
|
|
// Semantic version ranges which are supported.
|
|
// Unsupported columns are not queried (internally converted to DISCARD).
|
|
supportedVersions semver.Range `yaml:"pg_version"`
|
|
}
|
|
|
|
// UnmarshalYAML implements yaml.Unmarshaller
|
|
func (cm *ColumnMapping) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
type plain ColumnMapping
|
|
return unmarshal((*plain)(cm))
|
|
}
|
|
|
|
type ColumnMap map[string]MetricMapping
|
|
|
|
// MetricMapNamespace groups metric maps under a shared set of Labels.
|
|
type MetricMapNamespace struct {
|
|
Labels []string // Label names for this namespace
|
|
ColumnMappings ColumnMap // Column mappings in this namespace
|
|
}
|
|
|
|
type MetricMapNamespaceMapping map[string]MetricMapNamespace
|
|
|
|
// MetricMapping stores the prometheus metric description which a given column will
|
|
// be mapped to by the exporter
|
|
type MetricMapping struct {
|
|
Discard bool // Should metric be discarded during mapping?
|
|
Vtype prometheus.ValueType // Prometheus valuetype
|
|
Desc *prometheus.Desc // Prometheus descriptor
|
|
Conversion func(interface{}) (float64, bool) // Conversion function to turn PG result into float64
|
|
}
|
|
|
|
type MetricMaps map[string]map[string]ColumnMapping
|
|
|
|
// OverrideQuerys are run in-place of simple namespace look ups, and provide
|
|
// advanced functionality. But they have a tendency to postgres version specific.
|
|
// There aren't too many versions, so we simply store customized versions using
|
|
// the semver matching we do for columns. This is the user-friendly version of the input
|
|
// data structure.
|
|
type OverrideQuery struct {
|
|
versionRange semver.Range
|
|
query string
|
|
}
|
|
|
|
// NamespaceOverrideQueryMapping is the processed structure for override queries.
|
|
type NamespaceOverrideQueryMapping map[string]string
|
|
|
|
// MakeDescMapForVersion turns high-level MetricMaps into MetricMapNamespaceMappings, which can be used to produce
|
|
// metrics from database scrapes. Each mapping is applicable only to the version of postgres it is initialized with.
|
|
func MakeDescMapForVersion(pgVersion semver.Version, metricMaps MetricMaps) MetricMapNamespaceMapping {
|
|
var metricMap = make(map[string]MetricMapNamespace)
|
|
|
|
for namespace, mappings := range metricMaps {
|
|
thisMap := make(map[string]MetricMapping)
|
|
|
|
// Get the constant Labels
|
|
var constLabels []string
|
|
for columnName, columnMapping := range mappings {
|
|
if columnMapping.usage == LABEL {
|
|
constLabels = append(constLabels, columnName)
|
|
}
|
|
}
|
|
|
|
for columnName, columnMapping := range mappings {
|
|
// Check column version compatibility for the current map
|
|
// Force to Discard if not compatible.
|
|
if columnMapping.supportedVersions != nil {
|
|
if !columnMapping.supportedVersions(pgVersion) {
|
|
// It's very useful to be able to see what columns are being
|
|
// rejected.
|
|
log.Debugln(columnName, "is being forced to Discard due to version incompatibility.")
|
|
thisMap[columnName] = MetricMapping{
|
|
Discard: true,
|
|
Conversion: func(_ interface{}) (float64, bool) {
|
|
return math.NaN(), true
|
|
},
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Determine how to convert the column based on its usage.
|
|
// nolint: dupl
|
|
switch columnMapping.usage {
|
|
case DISCARD, LABEL:
|
|
thisMap[columnName] = MetricMapping{
|
|
Discard: true,
|
|
Conversion: func(_ interface{}) (float64, bool) {
|
|
return math.NaN(), true
|
|
},
|
|
}
|
|
case COUNTER:
|
|
thisMap[columnName] = MetricMapping{
|
|
Vtype: prometheus.CounterValue,
|
|
Desc: prometheus.NewDesc(fmt.Sprintf("%s_%s", namespace, columnName), columnMapping.description, constLabels, nil),
|
|
Conversion: func(in interface{}) (float64, bool) {
|
|
return dbconv.DbToFloat64(in)
|
|
},
|
|
}
|
|
case GAUGE:
|
|
thisMap[columnName] = MetricMapping{
|
|
Vtype: prometheus.GaugeValue,
|
|
Desc: prometheus.NewDesc(fmt.Sprintf("%s_%s", namespace, columnName), columnMapping.description, constLabels, nil),
|
|
Conversion: func(in interface{}) (float64, bool) {
|
|
return dbconv.DbToFloat64(in)
|
|
},
|
|
}
|
|
case MAPPEDMETRIC:
|
|
thisMap[columnName] = MetricMapping{
|
|
Vtype: prometheus.GaugeValue,
|
|
Desc: prometheus.NewDesc(fmt.Sprintf("%s_%s", namespace, columnName), columnMapping.description, constLabels, nil),
|
|
Conversion: func(in interface{}) (float64, bool) {
|
|
text, ok := in.(string)
|
|
if !ok {
|
|
return math.NaN(), false
|
|
}
|
|
|
|
val, ok := columnMapping.mapping[text]
|
|
if !ok {
|
|
return math.NaN(), false
|
|
}
|
|
return val, true
|
|
},
|
|
}
|
|
case DURATION:
|
|
thisMap[columnName] = MetricMapping{
|
|
Vtype: prometheus.GaugeValue,
|
|
Desc: prometheus.NewDesc(fmt.Sprintf("%s_%s_milliseconds", namespace, columnName), columnMapping.description, constLabels, nil),
|
|
Conversion: func(in interface{}) (float64, bool) {
|
|
var durationString string
|
|
switch t := in.(type) {
|
|
case []byte:
|
|
durationString = string(t)
|
|
case string:
|
|
durationString = t
|
|
default:
|
|
log.Errorln("DURATION Conversion metric was not a string")
|
|
return math.NaN(), false
|
|
}
|
|
|
|
if durationString == "-1" {
|
|
return math.NaN(), false
|
|
}
|
|
|
|
d, err := time.ParseDuration(durationString)
|
|
if err != nil {
|
|
log.Errorln("Failed converting result to metric:", columnName, in, err)
|
|
return math.NaN(), false
|
|
}
|
|
return float64(d / time.Millisecond), true
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
metricMap[namespace] = MetricMapNamespace{constLabels, thisMap}
|
|
}
|
|
|
|
return metricMap
|
|
} |