mirror of
https://github.com/kubernetes-sigs/prometheus-adapter.git
synced 2026-04-06 01:38:10 +00:00
Moving metric naming to it's own package
This commit is contained in:
parent
c2e176bb23
commit
6030912cc0
12 changed files with 93 additions and 227 deletions
|
|
@ -2,26 +2,20 @@ package provider
|
||||||
|
|
||||||
import "errors"
|
import "errors"
|
||||||
|
|
||||||
// NewOperatorNotSupportedByPrometheusError creates an error that represents the fact that we were requested to service a query that
|
var (
|
||||||
// Prometheus would be unable to support.
|
// ErrorNewOperatorNotSupportedByPrometheus creates an error that represents the fact that we were requested to service a query that
|
||||||
func NewOperatorNotSupportedByPrometheusError() error {
|
// Prometheus would be unable to support.
|
||||||
return errors.New("operator not supported by prometheus")
|
ErrorNewOperatorNotSupportedByPrometheus = errors.New("operator not supported by prometheus")
|
||||||
}
|
|
||||||
|
|
||||||
// NewOperatorRequiresValuesError creates an error that represents the fact that we were requested to service a query
|
// ErrorNewOperatorRequiresValues creates an error that represents the fact that we were requested to service a query
|
||||||
// that was malformed in its operator/value combination.
|
// that was malformed in its operator/value combination.
|
||||||
func NewOperatorRequiresValuesError() error {
|
ErrorNewOperatorRequiresValues = errors.New("operator requires values")
|
||||||
return errors.New("operator requires values")
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewOperatorDoesNotSupportValuesError creates an error that represents the fact that we were requested to service a query
|
// ErrorNewOperatorDoesNotSupportValues creates an error that represents the fact that we were requested to service a query
|
||||||
// that was malformed in its operator/value combination.
|
// that was malformed in its operator/value combination.
|
||||||
func NewOperatorDoesNotSupportValuesError() error {
|
ErrorNewOperatorDoesNotSupportValues = errors.New("operator does not support values")
|
||||||
return errors.New("operator does not support values")
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLabelNotSpecifiedError creates an error that represents the fact that we were requested to service a query
|
// ErrorNewLabelNotSpecified creates an error that represents the fact that we were requested to service a query
|
||||||
// that was malformed in its label specification.
|
// that was malformed in its label specification.
|
||||||
func NewLabelNotSpecifiedError() error {
|
ErrorNewLabelNotSpecified = errors.New("label not specified")
|
||||||
return errors.New("label not specified")
|
)
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -48,12 +48,11 @@ func NewExternalSeriesRegistry(lister MetricListerWithNotification, mapper apime
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *externalSeriesRegistry) filterAndStoreMetrics(result MetricUpdateResult) {
|
func (r *externalSeriesRegistry) filterAndStoreMetrics(result MetricUpdateResult) {
|
||||||
|
|
||||||
newSeriesSlices := result.series
|
newSeriesSlices := result.series
|
||||||
converters := result.converters
|
converters := result.converters
|
||||||
|
|
||||||
if len(newSeriesSlices) != len(converters) {
|
if len(newSeriesSlices) != len(converters) {
|
||||||
glog.Errorf("need one set of series per converter")
|
glog.Fatal("need one set of series per converter")
|
||||||
}
|
}
|
||||||
apiMetricsCache := make([]provider.ExternalMetricInfo, 0)
|
apiMetricsCache := make([]provider.ExternalMetricInfo, 0)
|
||||||
rawMetricsCache := make(map[string]SeriesConverter)
|
rawMetricsCache := make(map[string]SeriesConverter)
|
||||||
|
|
@ -84,6 +83,7 @@ func (r *externalSeriesRegistry) filterAndStoreMetrics(result MetricUpdateResult
|
||||||
|
|
||||||
r.metrics = apiMetricsCache
|
r.metrics = apiMetricsCache
|
||||||
r.rawMetrics = rawMetricsCache
|
r.rawMetrics = rawMetricsCache
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *externalSeriesRegistry) ListAllMetrics() []provider.ExternalMetricInfo {
|
func (r *externalSeriesRegistry) ListAllMetrics() []provider.ExternalMetricInfo {
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,6 @@ func (c *metricConverter) convertVector(queryResult prom.QueryResult) (*external
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, val := range toConvert {
|
for _, val := range toConvert {
|
||||||
|
|
||||||
singleMetric, err := c.convertSample(val)
|
singleMetric, err := c.convertSample(val)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -1,159 +0,0 @@
|
||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
|
|
||||||
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
|
|
||||||
"github.com/directxman12/k8s-prometheus-adapter/pkg/config"
|
|
||||||
"github.com/directxman12/k8s-prometheus-adapter/pkg/naming"
|
|
||||||
)
|
|
||||||
|
|
||||||
var nsGroupResource = schema.GroupResource{Resource: "namespaces"}
|
|
||||||
var groupNameSanitizer = strings.NewReplacer(".", "_", "-", "_")
|
|
||||||
|
|
||||||
// MetricNamer knows how to convert Prometheus series names and label names to
|
|
||||||
// metrics API resources, and vice-versa. MetricNamers should be safe to access
|
|
||||||
// concurrently. Returned group-resources are "normalized" as per the
|
|
||||||
// MetricInfo#Normalized method. Group-resources passed as arguments must
|
|
||||||
// themselves be normalized.
|
|
||||||
type MetricNamer interface {
|
|
||||||
// Selector produces the appropriate Prometheus series selector to match all
|
|
||||||
// series handlable by this namer.
|
|
||||||
Selector() prom.Selector
|
|
||||||
// FilterSeries checks to see which of the given series match any additional
|
|
||||||
// constrains beyond the series query. It's assumed that the series given
|
|
||||||
// already matche the series query.
|
|
||||||
FilterSeries(series []prom.Series) []prom.Series
|
|
||||||
// MetricNameForSeries returns the name (as presented in the API) for a given series.
|
|
||||||
MetricNameForSeries(series prom.Series) (string, error)
|
|
||||||
// QueryForSeries returns the query for a given series (not API metric name), with
|
|
||||||
// the given namespace name (if relevant), resource, and resource names.
|
|
||||||
QueryForSeries(series string, resource schema.GroupResource, namespace string, names ...string) (prom.Selector, error)
|
|
||||||
|
|
||||||
naming.ResourceConverter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *metricNamer) Selector() prom.Selector {
|
|
||||||
return n.seriesQuery
|
|
||||||
}
|
|
||||||
|
|
||||||
// reMatcher either positively or negatively matches a regex
|
|
||||||
type reMatcher struct {
|
|
||||||
regex *regexp.Regexp
|
|
||||||
positive bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newReMatcher(cfg config.RegexFilter) (*reMatcher, error) {
|
|
||||||
if cfg.Is != "" && cfg.IsNot != "" {
|
|
||||||
return nil, fmt.Errorf("cannot have both an `is` (%q) and `isNot` (%q) expression in a single filter", cfg.Is, cfg.IsNot)
|
|
||||||
}
|
|
||||||
if cfg.Is == "" && cfg.IsNot == "" {
|
|
||||||
return nil, fmt.Errorf("must have either an `is` or `isNot` expression in a filter")
|
|
||||||
}
|
|
||||||
|
|
||||||
var positive bool
|
|
||||||
var regexRaw string
|
|
||||||
if cfg.Is != "" {
|
|
||||||
positive = true
|
|
||||||
regexRaw = cfg.Is
|
|
||||||
} else {
|
|
||||||
positive = false
|
|
||||||
regexRaw = cfg.IsNot
|
|
||||||
}
|
|
||||||
|
|
||||||
regex, err := regexp.Compile(regexRaw)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to compile series filter %q: %v", regexRaw, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &reMatcher{
|
|
||||||
regex: regex,
|
|
||||||
positive: positive,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *reMatcher) Matches(val string) bool {
|
|
||||||
return m.regex.MatchString(val) == m.positive
|
|
||||||
}
|
|
||||||
|
|
||||||
type metricNamer struct {
|
|
||||||
seriesQuery prom.Selector
|
|
||||||
metricsQuery naming.MetricsQuery
|
|
||||||
nameMatches *regexp.Regexp
|
|
||||||
nameAs string
|
|
||||||
seriesMatchers []*reMatcher
|
|
||||||
|
|
||||||
naming.ResourceConverter
|
|
||||||
}
|
|
||||||
|
|
||||||
// queryTemplateArgs are the arguments for the metrics query template.
|
|
||||||
func (n *metricNamer) FilterSeries(initialSeries []prom.Series) []prom.Series {
|
|
||||||
if len(n.seriesMatchers) == 0 {
|
|
||||||
return initialSeries
|
|
||||||
}
|
|
||||||
|
|
||||||
finalSeries := make([]prom.Series, 0, len(initialSeries))
|
|
||||||
SeriesLoop:
|
|
||||||
for _, series := range initialSeries {
|
|
||||||
for _, matcher := range n.seriesMatchers {
|
|
||||||
if !matcher.Matches(series.Name) {
|
|
||||||
continue SeriesLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finalSeries = append(finalSeries, series)
|
|
||||||
}
|
|
||||||
|
|
||||||
return finalSeries
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *metricNamer) QueryForSeries(series string, resource schema.GroupResource, namespace string, names ...string) (prom.Selector, error) {
|
|
||||||
return n.metricsQuery.Build(series, resource, namespace, nil, names...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *metricNamer) MetricNameForSeries(series prom.Series) (string, error) {
|
|
||||||
matches := n.nameMatches.FindStringSubmatchIndex(series.Name)
|
|
||||||
if matches == nil {
|
|
||||||
return "", fmt.Errorf("series name %q did not match expected pattern %q", series.Name, n.nameMatches.String())
|
|
||||||
}
|
|
||||||
outNameBytes := n.nameMatches.ExpandString(nil, n.nameAs, series.Name, matches)
|
|
||||||
return string(outNameBytes), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMetricNamer creates a MetricNamer capable of translating Prometheus series names
|
|
||||||
// into custom metric names.
|
|
||||||
func NewMetricNamer(mapping config.NameMapping) (MetricNamer, error) {
|
|
||||||
var nameMatches *regexp.Regexp
|
|
||||||
var err error
|
|
||||||
if mapping.Matches != "" {
|
|
||||||
nameMatches, err = regexp.Compile(mapping.Matches)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to compile series name match expression %q: %v", mapping.Matches, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// this will always succeed
|
|
||||||
nameMatches = regexp.MustCompile(".*")
|
|
||||||
}
|
|
||||||
nameAs := mapping.As
|
|
||||||
if nameAs == "" {
|
|
||||||
// check if we have an obvious default
|
|
||||||
subexpNames := nameMatches.SubexpNames()
|
|
||||||
if len(subexpNames) == 1 {
|
|
||||||
// no capture groups, use the whole thing
|
|
||||||
nameAs = "$0"
|
|
||||||
} else if len(subexpNames) == 2 {
|
|
||||||
// one capture group, use that
|
|
||||||
nameAs = "$1"
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("must specify an 'as' value for name matcher %q", mapping.Matches)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &metricNamer{
|
|
||||||
nameMatches: nameMatches,
|
|
||||||
nameAs: nameAs,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"k8s.io/metrics/pkg/apis/external_metrics"
|
"k8s.io/metrics/pkg/apis/external_metrics"
|
||||||
|
|
||||||
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
|
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
|
||||||
|
"github.com/directxman12/k8s-prometheus-adapter/pkg/naming"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: Make sure everything has the proper licensing disclosure at the top.
|
// TODO: Make sure everything has the proper licensing disclosure at the top.
|
||||||
|
|
@ -56,7 +57,7 @@ func (p *externalPrometheusProvider) ListAllExternalMetrics() []provider.Externa
|
||||||
|
|
||||||
func (p *externalPrometheusProvider) selectGroupResource(namespace string) schema.GroupResource {
|
func (p *externalPrometheusProvider) selectGroupResource(namespace string) schema.GroupResource {
|
||||||
if namespace == "default" {
|
if namespace == "default" {
|
||||||
return nsGroupResource
|
return naming.NsGroupResource
|
||||||
}
|
}
|
||||||
|
|
||||||
return schema.GroupResource{
|
return schema.GroupResource{
|
||||||
|
|
|
||||||
|
|
@ -92,11 +92,11 @@ func (n *queryBuilder) processQueryParts(queryParts []queryPart) ([]string, map[
|
||||||
// We obviously can't generate label filters for these cases.
|
// We obviously can't generate label filters for these cases.
|
||||||
fmt.Println("This is queryPart", qPart.labelName, qPart.operator, qPart.values)
|
fmt.Println("This is queryPart", qPart.labelName, qPart.operator, qPart.values)
|
||||||
if qPart.labelName == "" {
|
if qPart.labelName == "" {
|
||||||
return nil, nil, NewLabelNotSpecifiedError()
|
return nil, nil, ErrorNewLabelNotSpecified
|
||||||
}
|
}
|
||||||
|
|
||||||
if !n.operatorIsSupported(qPart.operator) {
|
if !n.operatorIsSupported(qPart.operator) {
|
||||||
return nil, nil, NewOperatorNotSupportedByPrometheusError()
|
return nil, nil, ErrorNewOperatorNotSupportedByPrometheus
|
||||||
}
|
}
|
||||||
|
|
||||||
matcher, err := n.selectMatcher(qPart.operator, qPart.values)
|
matcher, err := n.selectMatcher(qPart.operator, qPart.values)
|
||||||
|
|
@ -128,7 +128,7 @@ func (n *queryBuilder) selectMatcher(operator selection.Operator, values []strin
|
||||||
case selection.DoesNotExist:
|
case selection.DoesNotExist:
|
||||||
return prom.LabelEq, nil
|
return prom.LabelEq, nil
|
||||||
case selection.Equals, selection.DoubleEquals, selection.NotEquals, selection.In, selection.NotIn:
|
case selection.Equals, selection.DoubleEquals, selection.NotEquals, selection.In, selection.NotIn:
|
||||||
return nil, NewOperatorRequiresValuesError()
|
return nil, ErrorNewOperatorRequiresValues
|
||||||
}
|
}
|
||||||
} else if numValues == 1 {
|
} else if numValues == 1 {
|
||||||
switch operator {
|
switch operator {
|
||||||
|
|
@ -168,7 +168,7 @@ func (n *queryBuilder) selectTargetValue(operator selection.Operator, values []s
|
||||||
// whose value is NOT "".
|
// whose value is NOT "".
|
||||||
return "", nil
|
return "", nil
|
||||||
case selection.Equals, selection.DoubleEquals, selection.NotEquals, selection.In, selection.NotIn:
|
case selection.Equals, selection.DoubleEquals, selection.NotEquals, selection.In, selection.NotIn:
|
||||||
return "", NewOperatorRequiresValuesError()
|
return "", ErrorNewOperatorRequiresValues
|
||||||
}
|
}
|
||||||
} else if numValues == 1 {
|
} else if numValues == 1 {
|
||||||
switch operator {
|
switch operator {
|
||||||
|
|
@ -194,7 +194,7 @@ func (n *queryBuilder) selectTargetValue(operator selection.Operator, values []s
|
||||||
// for their label selector.
|
// for their label selector.
|
||||||
return strings.Join(values, "|"), nil
|
return strings.Join(values, "|"), nil
|
||||||
case selection.Exists, selection.DoesNotExist:
|
case selection.Exists, selection.DoesNotExist:
|
||||||
return "", NewOperatorDoesNotSupportValuesError()
|
return "", ErrorNewOperatorDoesNotSupportValues
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ type seriesConverter struct {
|
||||||
resourceConverter naming.ResourceConverter
|
resourceConverter naming.ResourceConverter
|
||||||
queryBuilder QueryBuilder
|
queryBuilder QueryBuilder
|
||||||
seriesFilterer SeriesFilterer
|
seriesFilterer SeriesFilterer
|
||||||
metricNamer MetricNamer
|
metricNamer naming.MetricNamer
|
||||||
mapper apimeta.RESTMapper
|
mapper apimeta.RESTMapper
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,7 +122,7 @@ func (c *seriesConverter) buildNamespaceQueryPartForSeries(namespace string) (qu
|
||||||
// If we've been given a namespace, then we need to set up
|
// If we've been given a namespace, then we need to set up
|
||||||
// the label requirements to target that namespace.
|
// the label requirements to target that namespace.
|
||||||
if namespace != "default" {
|
if namespace != "default" {
|
||||||
namespaceLbl, err := c.resourceConverter.LabelForResource(nsGroupResource)
|
namespaceLbl, err := c.resourceConverter.LabelForResource(naming.NsGroupResource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
@ -226,7 +226,7 @@ func converterFromRule(rule config.DiscoveryRule, mapper apimeta.RESTMapper) (Se
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
metricNamer, err := NewMetricNamer(rule.Name)
|
metricNamer, err := naming.NewMetricNamer(rule.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to create a MetricNamer associated with series query %q: %v", rule.SeriesQuery, err)
|
return nil, fmt.Errorf("unable to create a MetricNamer associated with series query %q: %v", rule.SeriesQuery, err)
|
||||||
}
|
}
|
||||||
|
|
@ -242,7 +242,7 @@ func converterFromRule(rule config.DiscoveryRule, mapper apimeta.RESTMapper) (Se
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *seriesConverter) buildNamespaceQueryPartForExternalSeries(namespace string) (queryPart, error) {
|
func (c *seriesConverter) buildNamespaceQueryPartForExternalSeries(namespace string) (queryPart, error) {
|
||||||
namespaceLbl, _ := c.metricNamer.LabelForResource(nsGroupResource)
|
namespaceLbl, _ := c.metricNamer.LabelForResource(naming.NsGroupResource)
|
||||||
|
|
||||||
return queryPart{
|
return queryPart{
|
||||||
labelName: string(namespaceLbl),
|
labelName: string(namespaceLbl),
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
|
|
||||||
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
|
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
|
||||||
"github.com/directxman12/k8s-prometheus-adapter/pkg/config"
|
"github.com/directxman12/k8s-prometheus-adapter/pkg/config"
|
||||||
|
"github.com/directxman12/k8s-prometheus-adapter/pkg/naming"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SeriesFilterer provides functions for filtering collections of Prometheus series
|
// SeriesFilterer provides functions for filtering collections of Prometheus series
|
||||||
|
|
@ -15,15 +16,15 @@ type SeriesFilterer interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type seriesFilterer struct {
|
type seriesFilterer struct {
|
||||||
seriesMatchers []*reMatcher
|
seriesMatchers []*naming.ReMatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSeriesFilterer creates a SeriesFilterer that will remove any series that do not
|
// NewSeriesFilterer creates a SeriesFilterer that will remove any series that do not
|
||||||
// meet the requirements of the provided RegexFilter(s).
|
// meet the requirements of the provided RegexFilter(s).
|
||||||
func NewSeriesFilterer(filters []config.RegexFilter) (SeriesFilterer, error) {
|
func NewSeriesFilterer(filters []config.RegexFilter) (SeriesFilterer, error) {
|
||||||
seriesMatchers := make([]*reMatcher, len(filters))
|
seriesMatchers := make([]*naming.ReMatcher, len(filters))
|
||||||
for i, filterRaw := range filters {
|
for i, filterRaw := range filters {
|
||||||
matcher, err := newReMatcher(filterRaw)
|
matcher, err := naming.NewReMatcher(filterRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to generate series name filter: %v", err)
|
return nil, fmt.Errorf("unable to generate series name filter: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -36,7 +37,7 @@ func NewSeriesFilterer(filters []config.RegexFilter) (SeriesFilterer, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *seriesFilterer) AddRequirement(filterRaw config.RegexFilter) error {
|
func (n *seriesFilterer) AddRequirement(filterRaw config.RegexFilter) error {
|
||||||
matcher, err := newReMatcher(filterRaw)
|
matcher, err := naming.NewReMatcher(filterRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to generate series name filter: %v", err)
|
return fmt.Errorf("unable to generate series name filter: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,16 @@
|
||||||
package provider
|
package naming
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
|
||||||
|
|
||||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
|
||||||
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
|
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
|
||||||
"github.com/directxman12/k8s-prometheus-adapter/pkg/config"
|
"github.com/directxman12/k8s-prometheus-adapter/pkg/config"
|
||||||
"github.com/directxman12/k8s-prometheus-adapter/pkg/naming"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var nsGroupResource = schema.GroupResource{Resource: "namespaces"}
|
|
||||||
var groupNameSanitizer = strings.NewReplacer(".", "_", "-", "_")
|
|
||||||
|
|
||||||
// MetricNamer knows how to convert Prometheus series names and label names to
|
// MetricNamer knows how to convert Prometheus series names and label names to
|
||||||
// metrics API resources, and vice-versa. MetricNamers should be safe to access
|
// metrics API resources, and vice-versa. MetricNamers should be safe to access
|
||||||
// concurrently. Returned group-resources are "normalized" as per the
|
// concurrently. Returned group-resources are "normalized" as per the
|
||||||
|
|
@ -35,20 +30,20 @@ type MetricNamer interface {
|
||||||
// the given namespace name (if relevant), resource, and resource names.
|
// the given namespace name (if relevant), resource, and resource names.
|
||||||
QueryForSeries(series string, resource schema.GroupResource, namespace string, names ...string) (prom.Selector, error)
|
QueryForSeries(series string, resource schema.GroupResource, namespace string, names ...string) (prom.Selector, error)
|
||||||
|
|
||||||
naming.ResourceConverter
|
ResourceConverter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *metricNamer) Selector() prom.Selector {
|
func (n *metricNamer) Selector() prom.Selector {
|
||||||
return r.seriesQuery
|
return n.seriesQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
// reMatcher either positively or negatively matches a regex
|
// ReMatcher either positively or negatively matches a regex
|
||||||
type reMatcher struct {
|
type ReMatcher struct {
|
||||||
regex *regexp.Regexp
|
regex *regexp.Regexp
|
||||||
positive bool
|
positive bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newReMatcher(cfg config.RegexFilter) (*reMatcher, error) {
|
func NewReMatcher(cfg config.RegexFilter) (*ReMatcher, error) {
|
||||||
if cfg.Is != "" && cfg.IsNot != "" {
|
if cfg.Is != "" && cfg.IsNot != "" {
|
||||||
return nil, fmt.Errorf("cannot have both an `is` (%q) and `isNot` (%q) expression in a single filter", cfg.Is, cfg.IsNot)
|
return nil, fmt.Errorf("cannot have both an `is` (%q) and `isNot` (%q) expression in a single filter", cfg.Is, cfg.IsNot)
|
||||||
}
|
}
|
||||||
|
|
@ -71,24 +66,24 @@ func newReMatcher(cfg config.RegexFilter) (*reMatcher, error) {
|
||||||
return nil, fmt.Errorf("unable to compile series filter %q: %v", regexRaw, err)
|
return nil, fmt.Errorf("unable to compile series filter %q: %v", regexRaw, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &reMatcher{
|
return &ReMatcher{
|
||||||
regex: regex,
|
regex: regex,
|
||||||
positive: positive,
|
positive: positive,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *reMatcher) Matches(val string) bool {
|
func (m *ReMatcher) Matches(val string) bool {
|
||||||
return m.regex.MatchString(val) == m.positive
|
return m.regex.MatchString(val) == m.positive
|
||||||
}
|
}
|
||||||
|
|
||||||
type metricNamer struct {
|
type metricNamer struct {
|
||||||
seriesQuery prom.Selector
|
seriesQuery prom.Selector
|
||||||
metricsQuery naming.MetricsQuery
|
metricsQuery MetricsQuery
|
||||||
nameMatches *regexp.Regexp
|
nameMatches *regexp.Regexp
|
||||||
nameAs string
|
nameAs string
|
||||||
seriesMatchers []*reMatcher
|
seriesMatchers []*ReMatcher
|
||||||
|
|
||||||
naming.ResourceConverter
|
ResourceConverter
|
||||||
}
|
}
|
||||||
|
|
||||||
// queryTemplateArgs are the arguments for the metrics query template.
|
// queryTemplateArgs are the arguments for the metrics query template.
|
||||||
|
|
@ -129,26 +124,26 @@ func NamersFromConfig(cfg *config.MetricsDiscoveryConfig, mapper apimeta.RESTMap
|
||||||
namers := make([]MetricNamer, len(cfg.Rules))
|
namers := make([]MetricNamer, len(cfg.Rules))
|
||||||
|
|
||||||
for i, rule := range cfg.Rules {
|
for i, rule := range cfg.Rules {
|
||||||
resConv, err := naming.NewResourceConverter(rule.Resources.Template, rule.Resources.Overrides, mapper)
|
resConv, err := NewResourceConverter(rule.Resources.Template, rule.Resources.Overrides, mapper)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
metricsQuery, err := naming.NewMetricsQuery(rule.MetricsQuery, resConv)
|
metricsQuery, err := NewMetricsQuery(rule.MetricsQuery, resConv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to construct metrics query associated with series query %q: %v", rule.SeriesQuery, err)
|
return nil, fmt.Errorf("unable to construct metrics query associated with series query %q: %v", rule.SeriesQuery, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
seriesMatchers := make([]*reMatcher, len(rule.SeriesFilters))
|
seriesMatchers := make([]*ReMatcher, len(rule.SeriesFilters))
|
||||||
for i, filterRaw := range rule.SeriesFilters {
|
for i, filterRaw := range rule.SeriesFilters {
|
||||||
matcher, err := newReMatcher(filterRaw)
|
matcher, err := NewReMatcher(filterRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to generate series name filter associated with series query %q: %v", rule.SeriesQuery, err)
|
return nil, fmt.Errorf("unable to generate series name filter associated with series query %q: %v", rule.SeriesQuery, err)
|
||||||
}
|
}
|
||||||
seriesMatchers[i] = matcher
|
seriesMatchers[i] = matcher
|
||||||
}
|
}
|
||||||
if rule.Name.Matches != "" {
|
if rule.Name.Matches != "" {
|
||||||
matcher, err := newReMatcher(config.RegexFilter{Is: rule.Name.Matches})
|
matcher, err := NewReMatcher(config.RegexFilter{Is: rule.Name.Matches})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to generate series name filter from name rules associated with series query %q: %v", rule.SeriesQuery, err)
|
return nil, fmt.Errorf("unable to generate series name filter from name rules associated with series query %q: %v", rule.SeriesQuery, err)
|
||||||
}
|
}
|
||||||
|
|
@ -194,3 +189,38 @@ func NamersFromConfig(cfg *config.MetricsDiscoveryConfig, mapper apimeta.RESTMap
|
||||||
|
|
||||||
return namers, nil
|
return namers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewMetricNamer creates a MetricNamer capable of translating Prometheus series names
|
||||||
|
// into custom metric names.
|
||||||
|
func NewMetricNamer(mapping config.NameMapping) (MetricNamer, error) {
|
||||||
|
var nameMatches *regexp.Regexp
|
||||||
|
var err error
|
||||||
|
if mapping.Matches != "" {
|
||||||
|
nameMatches, err = regexp.Compile(mapping.Matches)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to compile series name match expression %q: %v", mapping.Matches, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// this will always succeed
|
||||||
|
nameMatches = regexp.MustCompile(".*")
|
||||||
|
}
|
||||||
|
nameAs := mapping.As
|
||||||
|
if nameAs == "" {
|
||||||
|
// check if we have an obvious default
|
||||||
|
subexpNames := nameMatches.SubexpNames()
|
||||||
|
if len(subexpNames) == 1 {
|
||||||
|
// no capture groups, use the whole thing
|
||||||
|
nameAs = "$0"
|
||||||
|
} else if len(subexpNames) == 2 {
|
||||||
|
// one capture group, use that
|
||||||
|
nameAs = "$1"
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("must specify an 'as' value for name matcher %q", mapping.Matches)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &metricNamer{
|
||||||
|
nameMatches: nameMatches,
|
||||||
|
nameAs: nameAs,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
@ -64,7 +64,7 @@ func (q *metricsQuery) Build(series string, resource schema.GroupResource, names
|
||||||
valuesByName := map[string][]string{}
|
valuesByName := map[string][]string{}
|
||||||
|
|
||||||
if namespace != "" {
|
if namespace != "" {
|
||||||
namespaceLbl, err := q.resConverter.LabelForResource(nsGroupResource)
|
namespaceLbl, err := q.resConverter.LabelForResource(NsGroupResource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package provider
|
package naming
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -13,7 +13,7 @@ func TestReMatcherIs(t *testing.T) {
|
||||||
Is: "my_.*",
|
Is: "my_.*",
|
||||||
}
|
}
|
||||||
|
|
||||||
matcher, err := newReMatcher(filter)
|
matcher, err := NewReMatcher(filter)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
result := matcher.Matches("my_label")
|
result := matcher.Matches("my_label")
|
||||||
|
|
@ -28,7 +28,7 @@ func TestReMatcherIsNot(t *testing.T) {
|
||||||
IsNot: "my_.*",
|
IsNot: "my_.*",
|
||||||
}
|
}
|
||||||
|
|
||||||
matcher, err := newReMatcher(filter)
|
matcher, err := NewReMatcher(filter)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
result := matcher.Matches("my_label")
|
result := matcher.Matches("my_label")
|
||||||
|
|
@ -44,6 +44,6 @@ func TestEnforcesIsOrIsNotButNotBoth(t *testing.T) {
|
||||||
IsNot: "your_.*",
|
IsNot: "your_.*",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := newReMatcher(filter)
|
_, err := NewReMatcher(filter)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
@ -18,8 +18,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
groupNameSanitizer = strings.NewReplacer(".", "_", "-", "_")
|
GroupNameSanitizer = strings.NewReplacer(".", "_", "-", "_")
|
||||||
nsGroupResource = schema.GroupResource{Resource: "namespaces"}
|
NsGroupResource = schema.GroupResource{Resource: "namespaces"}
|
||||||
)
|
)
|
||||||
|
|
||||||
// ResourceConverter knows the relationship between Kubernetes group-resources and Prometheus labels,
|
// ResourceConverter knows the relationship between Kubernetes group-resources and Prometheus labels,
|
||||||
|
|
@ -118,7 +118,7 @@ func (r *resourceConverter) makeLabelForResource(resource schema.GroupResource)
|
||||||
return "", fmt.Errorf("unable to singularize resource %s: %v", resource.String(), err)
|
return "", fmt.Errorf("unable to singularize resource %s: %v", resource.String(), err)
|
||||||
}
|
}
|
||||||
convResource := schema.GroupResource{
|
convResource := schema.GroupResource{
|
||||||
Group: groupNameSanitizer.Replace(resource.Group),
|
Group: GroupNameSanitizer.Replace(resource.Group),
|
||||||
Resource: singularRes,
|
Resource: singularRes,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -177,7 +177,7 @@ func (r *resourceConverter) ResourcesForSeries(series prom.Series) ([]schema.Gro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if groupRes == nsGroupResource {
|
if groupRes == NsGroupResource {
|
||||||
namespaced = true
|
namespaced = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue