diff --git a/pkg/promclient/filtermatcher.go b/pkg/promclient/filtermatcher.go index e8cbd8e09..c3f71cd19 100644 --- a/pkg/promclient/filtermatcher.go +++ b/pkg/promclient/filtermatcher.go @@ -8,9 +8,10 @@ import ( "github.com/prometheus/prometheus/promql/parser" ) -func NewFilterMatcherVisitor(ls model.LabelSet) *FilterMatcherVisitor { +func NewFilterMatcherVisitor(ls, extLs model.LabelSet) *FilterMatcherVisitor { return &FilterMatcherVisitor{ ls: ls, + extLs: extLs, filterMatch: true, } } @@ -19,6 +20,7 @@ func NewFilterMatcherVisitor(ls model.LabelSet) *FilterMatcherVisitor { type FilterMatcherVisitor struct { l sync.Mutex ls model.LabelSet + extLs model.LabelSet filterMatch bool } @@ -26,7 +28,7 @@ type FilterMatcherVisitor struct { func (l *FilterMatcherVisitor) Visit(node parser.Node, path []parser.Node) (w parser.Visitor, err error) { switch nodeTyped := node.(type) { case *parser.VectorSelector: - filteredMatchers, ok := FilterMatchers(l.ls, nodeTyped.LabelMatchers) + filteredMatchers, ok := FilterMatchers(l.ls, l.extLs, nodeTyped.LabelMatchers) l.l.Lock() l.filterMatch = l.filterMatch && ok l.l.Unlock() @@ -37,7 +39,7 @@ func (l *FilterMatcherVisitor) Visit(node parser.Node, path []parser.Node) (w pa return nil, nil } case *parser.MatrixSelector: - filteredMatchers, ok := FilterMatchers(l.ls, nodeTyped.VectorSelector.(*parser.VectorSelector).LabelMatchers) + filteredMatchers, ok := FilterMatchers(l.ls, l.extLs, nodeTyped.VectorSelector.(*parser.VectorSelector).LabelMatchers) l.l.Lock() l.filterMatch = l.filterMatch && ok l.l.Unlock() @@ -54,7 +56,7 @@ func (l *FilterMatcherVisitor) Visit(node parser.Node, path []parser.Node) (w pa // FilterMatchers applies the matchers to the given labelset to determine if there is a match // and to return all remaining matchers to be matched -func FilterMatchers(ls model.LabelSet, matchers []*labels.Matcher) ([]*labels.Matcher, bool) { +func FilterMatchers(ls, extLs model.LabelSet, matchers []*labels.Matcher) ([]*labels.Matcher, bool) { filteredMatchers := make([]*labels.Matcher, 0, len(matchers)) // Look over the matchers passed in, if any exist in our labels, we'll do the matcher, and then strip @@ -64,6 +66,8 @@ func FilterMatchers(ls model.LabelSet, matchers []*labels.Matcher) ([]*labels.Ma if !matcher.Matches(string(localValue)) { return nil, false } + } else if v, ok := extLs[model.LabelName(matcher.Name)]; ok && matcher.Matches(string(v)) { + continue // If the selector matches the external labels, we skip it } else { filteredMatchers = append(filteredMatchers, matcher) } diff --git a/pkg/promclient/label.go b/pkg/promclient/label.go index 3b650464a..9fff911e4 100644 --- a/pkg/promclient/label.go +++ b/pkg/promclient/label.go @@ -46,18 +46,30 @@ func MergeLabelSets(a, b []model.LabelSet) []model.LabelSet { return a } -// AddLabelClient proxies a client and adds the given labels to all results -type AddLabelClient struct { +func NewAddLabelClient(api API, labels model.LabelSet, externalLabels model.LabelSet) *addLabelClient { + allLabels := labels.Clone().Merge(externalLabels) + return &addLabelClient{ + API: api, + Labels: labels, + ExternalLabels: externalLabels, + allLabels: allLabels, + } +} + +// addLabelClient proxies a client and adds the given labels to all results +type addLabelClient struct { API - Labels model.LabelSet + Labels model.LabelSet + ExternalLabels model.LabelSet + allLabels model.LabelSet // All labels, including Labels and ExternalLabels } // Key defines the labelset which identifies this client -func (c *AddLabelClient) Key() model.LabelSet { - return c.Labels +func (c *addLabelClient) Key() model.LabelSet { + return c.allLabels } -func (c *AddLabelClient) filterMatchers(matchers []string) ([]string, bool, error) { +func (c *addLabelClient) filterMatchers(matchers []string) ([]string, bool, error) { ret := make([]string, 0, len(matchers)) for _, matcher := range matchers { selectors, err := parser.ParseMetricSelector(matcher) @@ -75,6 +87,8 @@ func (c *AddLabelClient) filterMatchers(matchers []string) ([]string, bool, erro if !s.Matches(string(v)) { return nil, false, nil } + } else if v, ok := c.ExternalLabels[model.LabelName(s.Name)]; ok && s.Matches(string(v)) { + continue // If the selector matches the external labels, we skip it } else { // Otherwise if the selector isn't part of the `Labels` we add; we pass it along filteredSelectors = append(filteredSelectors, s) } @@ -93,7 +107,7 @@ func (c *AddLabelClient) filterMatchers(matchers []string) ([]string, bool, erro } // LabelNames returns all the unique label names present in the block in sorted order. -func (c *AddLabelClient) LabelNames(ctx context.Context, matchers []string, startTime time.Time, endTime time.Time) ([]string, v1.Warnings, error) { +func (c *addLabelClient) LabelNames(ctx context.Context, matchers []string, startTime time.Time, endTime time.Time) ([]string, v1.Warnings, error) { matchers, ok, err := c.filterMatchers(matchers) if err != nil { return nil, nil, err @@ -107,7 +121,7 @@ func (c *AddLabelClient) LabelNames(ctx context.Context, matchers []string, star return nil, nil, err } - for k := range c.Labels { + for k := range c.allLabels { found := false for _, labelName := range l { if labelName == string(k) { @@ -123,7 +137,7 @@ func (c *AddLabelClient) LabelNames(ctx context.Context, matchers []string, star } // LabelValues performs a query for the values of the given label. -func (c *AddLabelClient) LabelValues(ctx context.Context, label string, matchers []string, startTime time.Time, endTime time.Time) (model.LabelValues, v1.Warnings, error) { +func (c *addLabelClient) LabelValues(ctx context.Context, label string, matchers []string, startTime time.Time, endTime time.Time) (model.LabelValues, v1.Warnings, error) { matchers, ok, err := c.filterMatchers(matchers) if err != nil { return nil, nil, err @@ -138,14 +152,14 @@ func (c *AddLabelClient) LabelValues(ctx context.Context, label string, matchers } // do we have labels that match in our state - if value, ok := c.Labels[model.LabelName(label)]; ok { + if value, ok := c.allLabels[model.LabelName(label)]; ok { return MergeLabelValues(val, model.LabelValues{value}), w, nil } return val, w, nil } // Query performs a query for the given time. -func (c *AddLabelClient) Query(ctx context.Context, query string, ts time.Time) (model.Value, v1.Warnings, error) { +func (c *addLabelClient) Query(ctx context.Context, query string, ts time.Time) (model.Value, v1.Warnings, error) { // Parse out the promql query into expressions etc. e, err := parser.ParseExpr(query) if err != nil { @@ -153,7 +167,7 @@ func (c *AddLabelClient) Query(ctx context.Context, query string, ts time.Time) } // Walk the expression, to filter out any LabelMatchers that match etc. - filterVisitor := NewFilterMatcherVisitor(c.Labels) + filterVisitor := NewFilterMatcherVisitor(c.Labels, c.ExternalLabels) if _, err := parser.Walk(ctx, filterVisitor, &parser.EvalStmt{Expr: e}, e, nil, nil); err != nil { return nil, nil, err } @@ -165,14 +179,14 @@ func (c *AddLabelClient) Query(ctx context.Context, query string, ts time.Time) if err != nil { return nil, w, err } - if err := promhttputil.ValueAddLabelSet(val, c.Labels); err != nil { + if err := promhttputil.ValueAddLabelSet(val, c.Labels, c.ExternalLabels); err != nil { return nil, w, err } return val, w, nil } // QueryRange performs a query for the given range. -func (c *AddLabelClient) QueryRange(ctx context.Context, query string, r v1.Range) (model.Value, v1.Warnings, error) { +func (c *addLabelClient) QueryRange(ctx context.Context, query string, r v1.Range) (model.Value, v1.Warnings, error) { // Parse out the promql query into expressions etc. e, err := parser.ParseExpr(query) if err != nil { @@ -180,7 +194,7 @@ func (c *AddLabelClient) QueryRange(ctx context.Context, query string, r v1.Rang } // Walk the expression, to filter out any LabelMatchers that match etc. - filterVisitor := NewFilterMatcherVisitor(c.Labels) + filterVisitor := NewFilterMatcherVisitor(c.Labels, c.ExternalLabels) if _, err := parser.Walk(ctx, filterVisitor, &parser.EvalStmt{Expr: e}, e, nil, nil); err != nil { return nil, nil, err } @@ -192,14 +206,14 @@ func (c *AddLabelClient) QueryRange(ctx context.Context, query string, r v1.Rang if err != nil { return nil, w, err } - if err := promhttputil.ValueAddLabelSet(val, c.Labels); err != nil { + if err := promhttputil.ValueAddLabelSet(val, c.Labels, c.ExternalLabels); err != nil { return nil, w, err } return val, w, nil } // Series finds series by label matchers. -func (c *AddLabelClient) Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, v1.Warnings, error) { +func (c *addLabelClient) Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, v1.Warnings, error) { // Now we need to filter the matches sent to us for the labels associated with this // servergroup filteredMatches := make([]string, 0, len(matches)) @@ -211,7 +225,7 @@ func (c *AddLabelClient) Series(ctx context.Context, matches []string, startTime } // Walk the expression, to filter out any LabelMatchers that match etc. - filterVisitor := NewFilterMatcherVisitor(c.Labels) + filterVisitor := NewFilterMatcherVisitor(c.Labels, c.ExternalLabels) if _, err := parser.Walk(ctx, filterVisitor, &parser.EvalStmt{Expr: e}, e, nil, nil); err != nil { return nil, nil, err } @@ -238,14 +252,19 @@ func (c *AddLabelClient) Series(ctx context.Context, matches []string, startTime for k, v := range c.Labels { lset[k] = v } + for k, v := range c.ExternalLabels { + if _, ok := lset[k]; !ok { + lset[k] = v + } + } } return v, w, nil } // GetValue loads the raw data for a given set of matchers in the time range -func (c *AddLabelClient) GetValue(ctx context.Context, start, end time.Time, matchers []*labels.Matcher) (model.Value, v1.Warnings, error) { - filteredMatchers, ok := FilterMatchers(c.Labels, matchers) +func (c *addLabelClient) GetValue(ctx context.Context, start, end time.Time, matchers []*labels.Matcher) (model.Value, v1.Warnings, error) { + filteredMatchers, ok := FilterMatchers(c.Labels, c.ExternalLabels, matchers) if !ok { return nil, nil, nil } @@ -254,7 +273,7 @@ func (c *AddLabelClient) GetValue(ctx context.Context, start, end time.Time, mat if err != nil { return nil, w, err } - if err := promhttputil.ValueAddLabelSet(val, c.Labels); err != nil { + if err := promhttputil.ValueAddLabelSet(val, c.Labels, c.ExternalLabels); err != nil { return nil, w, err } diff --git a/pkg/promclient/label_test.go b/pkg/promclient/label_test.go index 282c0dec7..2c06b0c10 100644 --- a/pkg/promclient/label_test.go +++ b/pkg/promclient/label_test.go @@ -197,11 +197,12 @@ func TestAddLabelClient(t *testing.T) { } tests := []struct { - labelSet model.LabelSet - err bool - matchers []string - labelValues []string - labelNames []string + labelSet model.LabelSet + externalLabelSet model.LabelSet + err bool + matchers []string + labelValues []string + labelNames []string }{ { labelSet: model.LabelSet{"b": "1"}, @@ -235,7 +236,7 @@ func TestAddLabelClient(t *testing.T) { } for i, test := range tests { - a := &AddLabelClient{stub, test.labelSet} + a := NewAddLabelClient(stub, test.labelSet, test.externalLabelSet) t.Run(strconv.Itoa(i), func(t *testing.T) { t.Run("LabelNames", func(t *testing.T) { v, _, err := a.LabelValues(context.TODO(), "a", test.matchers, time.Time{}, time.Time{}) diff --git a/pkg/promclient/multi_api_test.go b/pkg/promclient/multi_api_test.go index 33f1c458c..31ea3ca04 100644 --- a/pkg/promclient/multi_api_test.go +++ b/pkg/promclient/multi_api_test.go @@ -193,7 +193,7 @@ func TestMultiAPIMerging(t *testing.T) { }, // Ensure that simple label addition works { - a: &AddLabelClient{stub, model.LabelSet{"a": "b"}}, + a: NewAddLabelClient(stub, model.LabelSet{"a": "b"}, model.LabelSet{}), labelNames: []string{"a"}, labelValues: []model.LabelValue{"b"}, v: model.Vector{ @@ -206,8 +206,8 @@ func TestMultiAPIMerging(t *testing.T) { // Ensure a single layer of multi merges { a: NewMustMultiAPI([]API{ - &AddLabelClient{stub, model.LabelSet{"a": "1"}}, - &AddLabelClient{stub, model.LabelSet{"a": "2"}}, + NewAddLabelClient(stub, model.LabelSet{"a": "1"}, model.LabelSet{}), + NewAddLabelClient(stub, model.LabelSet{"a": "2"}, model.LabelSet{}), }, model.Time(0), nil, 1, false), labelNames: []string{"a"}, labelValues: []model.LabelValue{"1", "2"}, @@ -224,12 +224,12 @@ func TestMultiAPIMerging(t *testing.T) { { a: NewMustMultiAPI([]API{ NewMustMultiAPI([]API{ - &AddLabelClient{stub, model.LabelSet{"a": "1"}}, - &AddLabelClient{stub, model.LabelSet{"a": "1"}}, + NewAddLabelClient(stub, model.LabelSet{"a": "1"}, model.LabelSet{}), + NewAddLabelClient(stub, model.LabelSet{"a": "1"}, model.LabelSet{}), }, model.Time(0), nil, 1, false), NewMustMultiAPI([]API{ - &AddLabelClient{stub, model.LabelSet{"a": "2"}}, - &AddLabelClient{stub, model.LabelSet{"a": "2"}}, + NewAddLabelClient(stub, model.LabelSet{"a": "2"}, model.LabelSet{}), + NewAddLabelClient(stub, model.LabelSet{"a": "2"}, model.LabelSet{}), }, model.Time(0), nil, 1, false), }, model.Time(0), nil, 2, false), labelNames: []string{"a"}, @@ -248,22 +248,22 @@ func TestMultiAPIMerging(t *testing.T) { a: NewMustMultiAPI([]API{ NewMustMultiAPI([]API{ NewMustMultiAPI([]API{ - &AddLabelClient{stub, model.LabelSet{"a": "1"}}, - &AddLabelClient{stub, model.LabelSet{"a": "1"}}, + NewAddLabelClient(stub, model.LabelSet{"a": "1"}, model.LabelSet{}), + NewAddLabelClient(stub, model.LabelSet{"a": "1"}, model.LabelSet{}), }, model.Time(0), nil, 1, false), NewMustMultiAPI([]API{ - &AddLabelClient{stub, model.LabelSet{"a": "2"}}, - &AddLabelClient{stub, model.LabelSet{"a": "2"}}, + NewAddLabelClient(stub, model.LabelSet{"a": "2"}, model.LabelSet{}), + NewAddLabelClient(stub, model.LabelSet{"a": "2"}, model.LabelSet{}), }, model.Time(0), nil, 1, false), }, model.Time(0), nil, 2, false), NewMustMultiAPI([]API{ NewMustMultiAPI([]API{ - &AddLabelClient{stub, model.LabelSet{"b": "1"}}, - &AddLabelClient{stub, model.LabelSet{"b": "1"}}, + NewAddLabelClient(stub, model.LabelSet{"b": "1"}, model.LabelSet{}), + NewAddLabelClient(stub, model.LabelSet{"b": "1"}, model.LabelSet{}), }, model.Time(0), nil, 1, false), NewMustMultiAPI([]API{ - &AddLabelClient{stub, model.LabelSet{"b": "2"}}, - &AddLabelClient{stub, model.LabelSet{"b": "2"}}, + NewAddLabelClient(stub, model.LabelSet{"b": "2"}, model.LabelSet{}), + NewAddLabelClient(stub, model.LabelSet{"b": "2"}, model.LabelSet{}), }, model.Time(0), nil, 1, false), }, model.Time(0), nil, 2, false), }, model.Time(0), nil, 2, false), @@ -293,12 +293,12 @@ func TestMultiAPIMerging(t *testing.T) { { a: NewMustMultiAPI([]API{ NewMustMultiAPI([]API{ - &errorAPI{&AddLabelClient{stub, model.LabelSet{"a": "1"}}, fmt.Errorf("")}, - &AddLabelClient{stub, model.LabelSet{"a": "1"}}, + &errorAPI{NewAddLabelClient(stub, model.LabelSet{"a": "1"}, model.LabelSet{}), fmt.Errorf("")}, + NewAddLabelClient(stub, model.LabelSet{"a": "1"}, model.LabelSet{}), }, model.Time(0), nil, 1, false), NewMustMultiAPI([]API{ - &errorAPI{&AddLabelClient{stub, model.LabelSet{"a": "2"}}, fmt.Errorf("")}, - &AddLabelClient{stub, model.LabelSet{"a": "2"}}, + &errorAPI{NewAddLabelClient(stub, model.LabelSet{"a": "2"}, model.LabelSet{}), fmt.Errorf("")}, + NewAddLabelClient(stub, model.LabelSet{"a": "2"}, model.LabelSet{}), }, model.Time(0), nil, 1, false), }, model.Time(0), nil, 2, false), labelNames: []string{"a"}, @@ -316,12 +316,12 @@ func TestMultiAPIMerging(t *testing.T) { { a: NewMustMultiAPI([]API{ NewMustMultiAPI([]API{ - &errorAPI{&AddLabelClient{stub, model.LabelSet{"a": "1"}}, fmt.Errorf("")}, - &errorAPI{&AddLabelClient{stub, model.LabelSet{"a": "1"}}, fmt.Errorf("")}, + &errorAPI{NewAddLabelClient(stub, model.LabelSet{"a": "1"}, model.LabelSet{}), fmt.Errorf("")}, + &errorAPI{NewAddLabelClient(stub, model.LabelSet{"a": "1"}, model.LabelSet{}), fmt.Errorf("")}, }, model.Time(0), nil, 1, false), NewMustMultiAPI([]API{ - &AddLabelClient{stub, model.LabelSet{"a": "2"}}, - &AddLabelClient{stub, model.LabelSet{"a": "2"}}, + NewAddLabelClient(stub, model.LabelSet{"a": "2"}, model.LabelSet{}), + NewAddLabelClient(stub, model.LabelSet{"a": "2"}, model.LabelSet{}), }, model.Time(0), nil, 1, false), }, model.Time(0), nil, 2, false), err: true, @@ -329,17 +329,17 @@ func TestMultiAPIMerging(t *testing.T) { // if in a multi, all that "match" error, we should error { a: NewMustMultiAPI([]API{ - &errorAPI{&AddLabelClient{stub, model.LabelSet{"a": "1"}}, fmt.Errorf("")}, - &AddLabelClient{stub, model.LabelSet{"a": "2"}}, + &errorAPI{NewAddLabelClient(stub, model.LabelSet{"a": "1"}, model.LabelSet{}), fmt.Errorf("")}, + NewAddLabelClient(stub, model.LabelSet{"a": "2"}, model.LabelSet{}), }, model.Time(0), nil, 1, false), err: true, }, // however, in a multi if a single one succeeds for a given "group" then it should pass { a: NewMustMultiAPI([]API{ - &AddLabelClient{stub, model.LabelSet{"a": "1"}}, - &errorAPI{&AddLabelClient{stub, model.LabelSet{"a": "1"}}, fmt.Errorf("")}, - &AddLabelClient{stub, model.LabelSet{"a": "2"}}, + NewAddLabelClient(stub, model.LabelSet{"a": "1"}, model.LabelSet{}), + &errorAPI{NewAddLabelClient(stub, model.LabelSet{"a": "1"}, model.LabelSet{}), fmt.Errorf("")}, + NewAddLabelClient(stub, model.LabelSet{"a": "2"}, model.LabelSet{}), }, model.Time(0), nil, 1, false), labelNames: []string{"a"}, labelValues: []model.LabelValue{"1", "2"}, @@ -356,8 +356,8 @@ func TestMultiAPIMerging(t *testing.T) { { a: NewMustMultiAPI([]API{ stub, - &AddLabelClient{stub, model.LabelSet{"a": "1"}}, - &AddLabelClient{stub, model.LabelSet{"a": "2"}}, + NewAddLabelClient(stub, model.LabelSet{"a": "1"}, model.LabelSet{}), + NewAddLabelClient(stub, model.LabelSet{"a": "2"}, model.LabelSet{}), }, model.Time(0), nil, 1, false), labelNames: []string{"a"}, labelValues: []model.LabelValue{"1", "2"}, diff --git a/pkg/promhttputil/merge.go b/pkg/promhttputil/merge.go index 5ebcd1389..4e4b911af 100644 --- a/pkg/promhttputil/merge.go +++ b/pkg/promhttputil/merge.go @@ -44,13 +44,18 @@ func (s WarningSet) Warnings() v1.Warnings { } // ValueAddLabelSet adds the labelset `l` to the value `a` -func ValueAddLabelSet(a model.Value, l model.LabelSet) error { +func ValueAddLabelSet(a model.Value, l, extL model.LabelSet) error { switch aTyped := a.(type) { case model.Vector: for _, item := range aTyped { for k, v := range l { item.Metric[k] = v } + for k, v := range extL { + if _, ok := item.Metric[k]; !ok { + item.Metric[k] = v + } + } } case model.Matrix: @@ -62,6 +67,11 @@ func ValueAddLabelSet(a model.Value, l model.LabelSet) error { for k, v := range l { item.Metric[k] = v } + for k, v := range extL { + if _, ok := item.Metric[k]; !ok { + item.Metric[k] = v + } + } } } diff --git a/pkg/servergroup/config.go b/pkg/servergroup/config.go index 7619ce5ea..b5a7547ec 100644 --- a/pkg/servergroup/config.go +++ b/pkg/servergroup/config.go @@ -67,7 +67,9 @@ type Config struct { Scheme string `yaml:"scheme"` // Labels is a set of labels that will be added to all metrics retrieved // from this server group - Labels model.LabelSet `json:"labels"` + Labels model.LabelSet `yaml:"labels"` + // ExternalLabels are the labels that will be added to all metrics retrieved same as Prometheus does. (does not overwrite existing labels) + ExternalLabels model.LabelSet `yaml:"external_labels"` // RelabelConfigs are similar in function and identical in configuration as prometheus' // relabel config for scrape jobs. The difference here being that the source labels // you can pull from are from the downstream servergroup target and the labels you are diff --git a/pkg/servergroup/servergroup.go b/pkg/servergroup/servergroup.go index 1b7a9364d..f7f195001 100644 --- a/pkg/servergroup/servergroup.go +++ b/pkg/servergroup/servergroup.go @@ -254,7 +254,7 @@ func (s *ServerGroup) loadTargetGroupMap(targetGroupMap map[string][]*targetgrou } // Add labels - apiClient = &promclient.AddLabelClient{apiClient, modelLabelSet.Merge(s.Cfg.Labels)} + apiClient = promclient.NewAddLabelClient(apiClient, modelLabelSet.Merge(s.Cfg.Labels), s.Cfg.ExternalLabels) // Add MetricRelabel if set if len(s.Cfg.MetricsRelabelConfigs) > 0 {