1
0
mirror of https://github.com/prometheus-community/postgres_exporter.git synced 2025-08-06 17:22:43 +03:00

Start building out the packages.

This commit is contained in:
Will Rouesnel
2020-03-01 01:20:42 +11:00
parent 6fcfe4041a
commit 3ccdfc0777
20 changed files with 673 additions and 608 deletions

View File

@@ -3,12 +3,12 @@ package metricmaps
// Metric name parts.
const (
// Namespace for all metrics.
namespace = "pg"
ExporterNamespaceLabel = "pg"
// Subsystems.
exporter = "exporter"
ExporterSubsystemLabel = "ExporterSubsystemLabel"
// Metric label used for static string data thats handy to send to Prometheus
// e.g. version
staticLabelName = "static"
StaticLabelName = "static"
// Metric label used for server identification.
serverLabelName = "server"
)
ServerLabelName = "server"
)

View File

@@ -1,7 +1,17 @@
package metricmaps
import (
"fmt"
"github.com/blang/semver"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/log"
"github.com/wrouesnel/postgres_exporter/pkg/pgdbconv"
"math"
"time"
)
// Turn the MetricMap column mapping into a prometheus descriptor mapping.
func makeDescMap(pgVersion semver.Version, serverLabels prometheus.Labels, metricMaps map[string]intermediateMetricMap) map[string]MetricMapNamespace {
func makeDescMap(pgVersion semver.Version, serverLabels prometheus.Labels, metricMaps map[string]IntermediateMetricMap) map[string]MetricMapNamespace {
var metricMap = make(map[string]MetricMapNamespace)
for namespace, intermediateMappings := range metricMaps {
@@ -9,23 +19,23 @@ func makeDescMap(pgVersion semver.Version, serverLabels prometheus.Labels, metri
// Get the constant labels
var variableLabels []string
for columnName, columnMapping := range intermediateMappings.columnMappings {
if columnMapping.usage == queries.LABEL {
for columnName, columnMapping := range intermediateMappings.ColumnMappings {
if columnMapping.Usage == LABEL {
variableLabels = append(variableLabels, columnName)
}
}
for columnName, columnMapping := range intermediateMappings.columnMappings {
for columnName, columnMapping := range intermediateMappings.ColumnMappings {
// Check column version compatibility for the current map
// Force to discard if not compatible.
if columnMapping.supportedVersions != nil {
if !columnMapping.supportedVersions(pgVersion) {
if columnMapping.SupportedVersions != nil {
if !columnMapping.SupportedVersions.Range(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] = MetricMap{
discard: true,
conversion: func(_ interface{}) (float64, bool) {
Discard: true,
Conversion: func(_ interface{}) (float64, bool) {
return math.NaN(), true
},
}
@@ -35,52 +45,52 @@ func makeDescMap(pgVersion semver.Version, serverLabels prometheus.Labels, metri
// Determine how to convert the column based on its usage.
// nolint: dupl
switch columnMapping.usage {
case queries.DISCARD, queries.LABEL:
switch columnMapping.Usage {
case DISCARD, LABEL:
thisMap[columnName] = MetricMap{
discard: true,
conversion: func(_ interface{}) (float64, bool) {
Discard: true,
Conversion: func(_ interface{}) (float64, bool) {
return math.NaN(), true
},
}
case queries.COUNTER:
case COUNTER:
thisMap[columnName] = MetricMap{
vtype: prometheus.CounterValue,
desc: prometheus.NewDesc(fmt.Sprintf("%s_%s", namespace, columnName), columnMapping.description, variableLabels, serverLabels),
conversion: func(in interface{}) (float64, bool) {
ValueType: prometheus.CounterValue,
Desc: prometheus.NewDesc(fmt.Sprintf("%s_%s", namespace, columnName), columnMapping.Description, variableLabels, serverLabels),
Conversion: func(in interface{}) (float64, bool) {
return pgdbconv.DBToFloat64(in)
},
}
case queries.GAUGE:
case GAUGE:
thisMap[columnName] = MetricMap{
vtype: prometheus.GaugeValue,
desc: prometheus.NewDesc(fmt.Sprintf("%s_%s", namespace, columnName), columnMapping.description, variableLabels, serverLabels),
conversion: func(in interface{}) (float64, bool) {
ValueType: prometheus.GaugeValue,
Desc: prometheus.NewDesc(fmt.Sprintf("%s_%s", namespace, columnName), columnMapping.Description, variableLabels, serverLabels),
Conversion: func(in interface{}) (float64, bool) {
return pgdbconv.DBToFloat64(in)
},
}
case queries.MAPPEDMETRIC:
case MAPPEDMETRIC:
thisMap[columnName] = MetricMap{
vtype: prometheus.GaugeValue,
desc: prometheus.NewDesc(fmt.Sprintf("%s_%s", namespace, columnName), columnMapping.description, variableLabels, serverLabels),
conversion: func(in interface{}) (float64, bool) {
ValueType: prometheus.GaugeValue,
Desc: prometheus.NewDesc(fmt.Sprintf("%s_%s", namespace, columnName), columnMapping.Description, variableLabels, serverLabels),
Conversion: func(in interface{}) (float64, bool) {
text, ok := in.(string)
if !ok {
return math.NaN(), false
}
val, ok := columnMapping.mapping[text]
val, ok := columnMapping.Mapping[text]
if !ok {
return math.NaN(), false
}
return val, true
},
}
case queries.DURATION:
case DURATION:
thisMap[columnName] = MetricMap{
vtype: prometheus.GaugeValue,
desc: prometheus.NewDesc(fmt.Sprintf("%s_%s_milliseconds", namespace, columnName), columnMapping.description, variableLabels, serverLabels),
conversion: func(in interface{}) (float64, bool) {
ValueType: prometheus.GaugeValue,
Desc: prometheus.NewDesc(fmt.Sprintf("%s_%s_milliseconds", namespace, columnName), columnMapping.Description, variableLabels, serverLabels),
Conversion: func(in interface{}) (float64, bool) {
var durationString string
switch t := in.(type) {
case []byte:
@@ -108,11 +118,11 @@ func makeDescMap(pgVersion semver.Version, serverLabels prometheus.Labels, metri
}
metricMap[namespace] = MetricMapNamespace{
labels: variableLabels,
columnMappings: thisMap,
master: intermediateMappings.master,
cacheSeconds: intermediateMappings.cacheSeconds}
Labels: variableLabels,
ColumnMappings: thisMap,
Master: intermediateMappings.Master,
CacheSeconds: intermediateMappings.CacheSeconds}
}
return metricMap
}
}

View File

@@ -0,0 +1,9 @@
package metricmaps
import (
. "gopkg.in/check.v1"
"testing"
)
// Hook up gocheck into the "go test" runner.
func Test(t *testing.T) { TestingT(t) }

View File

@@ -5,6 +5,18 @@ import (
"github.com/prometheus/client_golang/prometheus"
)
// QueryMap is the global holder structure for the new unified configuration format.
type QueryMap struct {
Global *QueryConfig `yaml:"global"`
ByServer map[string]*QueryConfig `yaml:"by_server"`
}
// QueryConfig holds a specific mapping and query override config
type QueryConfig struct {
MetricMap MetricMaps `yaml:"metric_maps"`
QueryOverrides QueryOverrides `yaml:"query_override"`
}
// MappingOptions is a copy of ColumnMapping used only for parsing
type MappingOptions struct {
Usage string `yaml:"usage"`
@@ -14,7 +26,7 @@ type MappingOptions struct {
}
// QueryOverrides ensures our query types are consistent
type QueryOverrides map[string]string
type QueryOverrides map[string][]OverrideQuery
// MetricMaps is a stub type to assist with checking
type MetricMaps map[string]IntermediateMetricMap
@@ -27,20 +39,19 @@ type IntermediateMetricMap struct {
CacheSeconds uint64
}
// MetricMapNamespace groups metric maps under a shared set of labels.
type MetricMapNamespace struct {
Labels []string // Label names for this namespace
ColumnMappings map[string]MetricMap // Column mappings in this namespace
Labels []string // Label names for this ExporterNamespaceLabel
ColumnMappings map[string]MetricMap // Column mappings in this ExporterNamespaceLabel
Master bool // Call query only for master database
CacheSeconds uint64 // Number of seconds this metric namespace can be cached. 0 disables.
CacheSeconds uint64 // Number of seconds this metric ExporterNamespaceLabel can be cached. 0 disables.
}
// MetricMap stores the prometheus metric description which a given column will
// be mapped to by the collector
type MetricMap struct {
Discard bool // Should metric be discarded during mapping?
ValueType prometheus.ValueType // Prometheus valuetype
ValueType prometheus.ValueType // Prometheus valuetype
Desc *prometheus.Desc // Prometheus descriptor
Conversion func(interface{}) (float64, bool) // Conversion function to turn PG result into float64
}
}

View File

@@ -7,16 +7,15 @@ import (
// 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
type ColumnUsage string
// 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)
DISCARD ColumnUsage = "DISCARD" // Ignore this column
LABEL ColumnUsage = "LABEL" // Use this column as a label
COUNTER ColumnUsage = "COUNTER" // Use this column as a counter
GAUGE ColumnUsage = "GAUGE" // Use this column as a gauge
MAPPEDMETRIC ColumnUsage = "MAPPEDMETRIC" // Use this column with the supplied mapping of text values
DURATION ColumnUsage = "DURATION" // This column should be interpreted as a text duration (and converted to milliseconds)
)
// UnmarshalYAML implements the yaml.Unmarshaller interface.
@@ -26,51 +25,29 @@ func (cu *ColumnUsage) UnmarshalYAML(unmarshal func(interface{}) error) error {
return err
}
columnUsage, err := StringToColumnUsage(value)
if err != nil {
return err
var columnUsage ColumnUsage
switch value {
case "DISCARD",
"LABEL",
"COUNTER",
"GAUGE",
"MAPPEDMETRIC",
"DURATION":
columnUsage = ColumnUsage(value)
default:
return fmt.Errorf("value is not a valid ColumnUsage value: %s", value)
}
*cu = columnUsage
return nil
}
// StringToColumnUsage converts 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
}
// 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
SupportedVersions semver.Range `yaml:"pg_version"` // Semantic version ranges which are supported. Unsupported columns are not queried (internally converted to DISCARD).
SupportedVersions *SemverRange `yaml:"pg_version"` // Semantic version ranges which are supported. Unsupported columns are not queried (internally converted to DISCARD).
}
// UnmarshalYAML implements yaml.Unmarshaller
@@ -79,26 +56,78 @@ func (cm *ColumnMapping) UnmarshalYAML(unmarshal func(interface{}) error) error
return unmarshal((*plain)(cm))
}
// nolint: golint
type Mapping map[string]MappingOptions
// nolint: golint
type UserQuery struct {
Query string `yaml:"query"`
Metrics []Mapping `yaml:"metrics"`
Master bool `yaml:"master"` // Querying only for master database
CacheSeconds uint64 `yaml:"cache_seconds"` // Number of seconds to cache the namespace result metrics for.
CacheSeconds uint64 `yaml:"cache_seconds"` // Number of seconds to cache the ExporterNamespaceLabel result metrics for.
}
// nolint: golint
type UserQueries map[string]UserQuery
// OverrideQuery are run in-place of simple namespace look ups, and provide
// OverrideQuery are run in-place of simple ExporterNamespaceLabel 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.
type OverrideQuery struct {
VersionRange semver.Range
Query string
}
// SemverRange implements YAML marshalling for semver.Range
type SemverRange struct {
r string
semver.Range
}
// MustParseSemverRange parses a semver
func MustParseSemverRange(s string) *SemverRange {
r, err := ParseSemverRange(s)
if err != nil {
panic(err)
}
return r
}
// ParseSemverRange parses a semver
func ParseSemverRange(s string) (*SemverRange, error) {
r, err := semver.ParseRange(s)
if err != nil {
return nil, err
}
return &SemverRange{s, r}, nil
}
func (sr *SemverRange) String() string {
if sr != nil {
return sr.r
} else {
return "(any)"
}
}
// MarshalYAML implements yaml.Marshaller
func (sr *SemverRange) MarshalYAML() (interface{}, error) {
if sr == nil {
return nil, nil
}
return sr.r, nil
}
// UnmarshalYAML implements yaml.Unmarshaller
func (sr *SemverRange) UnmarshalYAML(unmarshal func(interface{}) error) error {
var err error
var rangeStr string
if err = unmarshal(&rangeStr); err != nil {
return err
}
sr, err = ParseSemverRange(rangeStr)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,21 @@
package metricmaps
import (
"fmt"
. "gopkg.in/check.v1"
"gopkg.in/yaml.v2"
)
type SemverSuite struct{}
var _ = Suite(&SemverSuite{})
func (s *SemverSuite) TestEncodeYAML(c *C) {
sr, err := ParseSemverRange(">=1.0.0")
c.Assert(err, IsNil)
fmt.Println(sr)
b, err := yaml.Marshal(&sr)
c.Check(err, IsNil)
fmt.Println(string(b))
}