diff --git a/pkg/promclient/label.go b/pkg/promclient/label.go index 64b56ef9d..abc394794 100644 --- a/pkg/promclient/label.go +++ b/pkg/promclient/label.go @@ -57,6 +57,34 @@ func (c *AddLabelClient) Key() model.LabelSet { return c.Labels } +func (c *AddLabelClient) filterMatchers(matchers []string) ([]string, bool, error) { + ret := make([]string, 0, len(matchers)) + for i, matcher := range matchers { + selectors, err := parser.ParseMetricSelector(matcher) + if err != nil { + return nil, true, err + } + + // If the selector matches our value -- remove the selector + // if the selector doesn't match, return empty + for sI, s := range selectors { + if v, ok := c.Labels[model.LabelName(s.Name)]; ok { + if s.Matches(string(v)) { + selectors = append(selectors[:sI], selectors[i+1:]...) + } else { + return nil, false, nil + } + } + } + newMatcher, err := promhttputil.MatcherToString(selectors) + if err != nil { + return nil, false, err + } + ret = append(ret, newMatcher) + } + return ret, true, nil +} + // 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) { l, w, err := c.API.LabelNames(ctx, matchers, startTime, endTime) @@ -81,6 +109,14 @@ 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) { + matchers, ok, err := c.filterMatchers(matchers) + if err != nil { + return nil, nil, err + } + if !ok { + return nil, nil, nil + } + val, w, err := c.API.LabelValues(ctx, label, matchers, startTime, endTime) if err != nil { return nil, w, err diff --git a/pkg/promclient/label_test.go b/pkg/promclient/label_test.go index d0290e763..9f97ff11a 100644 --- a/pkg/promclient/label_test.go +++ b/pkg/promclient/label_test.go @@ -1,11 +1,18 @@ package promclient import ( + "context" + "fmt" "reflect" "strconv" + "strings" "testing" + "time" + v1 "github.com/prometheus/client_golang/api/prometheus/v1" model "github.com/prometheus/common/model" + "github.com/prometheus/prometheus/model/labels" + "github.com/prometheus/prometheus/promql/parser" ) func TestMergeLabelValues(t *testing.T) { @@ -85,3 +92,121 @@ func TestMergeLabelSets(t *testing.T) { }) } } + +type labelStubAPI struct { + series []model.LabelSet +} + +func (a *labelStubAPI) LabelNames(ctx context.Context, matchers []string, startTime time.Time, endTime time.Time) ([]string, v1.Warnings, error) { + return nil, nil, fmt.Errorf("not implemented") +} + +func (a *labelStubAPI) LabelValues(ctx context.Context, label string, matchers []string, startTime time.Time, endTime time.Time) (model.LabelValues, v1.Warnings, error) { + series := append([]model.LabelSet{}, a.series...) + for _, m := range matchers { + selectors, err := parser.ParseMetricSelector(m) + if err != nil { + return nil, nil, err + } + for _, selector := range selectors { + for i, s := range series { + v, ok := s[model.LabelName(selector.Name)] + if !ok || !selector.Matches(string(v)) { + series = append(series[:i], series[i+1:]...) + } + } + } + } + values := make(map[string]struct{}) + for _, s := range series { + for k, v := range s { + if strings.HasPrefix(string(k), model.ReservedLabelPrefix) { + continue + } + values[string(v)] = struct{}{} + } + } + ret := make(model.LabelValues, 0, len(values)) + for k := range values { + ret = append(ret, model.LabelValue(k)) + } + return ret, nil, nil +} + +// Query performs a query for the given time. +func (a *labelStubAPI) Query(ctx context.Context, query string, ts time.Time) (model.Value, v1.Warnings, error) { + return nil, nil, fmt.Errorf("not implemented") + +} + +// QueryRange performs a query for the given range. +func (a *labelStubAPI) QueryRange(ctx context.Context, query string, r v1.Range) (model.Value, v1.Warnings, error) { + return nil, nil, fmt.Errorf("not implemented") +} + +// Series finds series by label matchers. +func (a *labelStubAPI) Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, v1.Warnings, error) { + return nil, nil, fmt.Errorf("not implemented") + +} + +// GetValue loads the raw data for a given set of matchers in the time range +func (a *labelStubAPI) GetValue(ctx context.Context, start, end time.Time, matchers []*labels.Matcher) (model.Value, v1.Warnings, error) { + return nil, nil, fmt.Errorf("not implemented") + +} + +// Metadata returns metadata about metrics currently scraped by the metric name. +func (a *labelStubAPI) Metadata(ctx context.Context, metric, limit string) (map[string][]v1.Metadata, error) { + return nil, fmt.Errorf("not implemented") +} +func TestAddLabelClient(t *testing.T) { + + stub := &labelStubAPI{ + series: []model.LabelSet{{model.MetricNameLabel: "testmetric", "a": "1"}}, + } + + tests := []struct { + labelSet model.LabelSet + err bool + matchers []string + labelValues []string + }{ + { + labelSet: model.LabelSet{"b": "1"}, + labelValues: []string{"1"}, + }, + { + labelSet: model.LabelSet{"b": "1"}, + labelValues: []string{"1"}, + matchers: []string{`{b="1"}`}, + }, + } + + for i, test := range tests { + a := &AddLabelClient{stub, test.labelSet} + 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{}) + if err != nil != test.err { + if test.err { + t.Fatalf("missing expected err") + } else { + t.Fatalf("Unexpected Err: %v", err) + } + } + if err == nil { + if len(v) != len(test.labelValues) { + t.Fatalf("mismatch in len: \nexpected=%v\nactual=%v", test.labelValues, v) + } + + for i, actualV := range v { + if actualV != model.LabelValue(test.labelValues[i]) { + t.Fatalf("mismatch in value: \nexpected=%v\nactual=%v", test.labelValues, v) + } + } + } + }) + }) + } +}