restrictor/query_restrictor.go (84 lines of code) (raw):

/* * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ package restrictor import ( "fmt" "strings" "github.com/prometheus/prometheus/pkg/labels" "github.com/prometheus/prometheus/promql/parser" ) // QueryRestrictor provides functionality to add restrictor labels to a // Prometheus query type QueryRestrictor struct { matchers []labels.Matcher Opts } // Opts contains optional configurations for the QueryRestrictor type Opts struct { ReplaceExistingLabel bool } var DefaultOpts = Opts{ReplaceExistingLabel: true} // NewQueryRestrictor returns a new QueryRestrictor to be built upon func NewQueryRestrictor(opts Opts) *QueryRestrictor { return &QueryRestrictor{ matchers: []labels.Matcher{}, Opts: opts, } } // AddMatcher takes a key and an arbitrary number of values. If only one value // is provided, an Equal matcher will be added to the restrictor. Otherwise a // Regex matcher with an OR of the values will be added. e.g. {label=~"value1|value2"} // If values is empty the label will be matched to the empty string e.g. {label=""} // effectively, this matches which do not contain this label func (q *QueryRestrictor) AddMatcher(key string, values ...string) *QueryRestrictor { if len(values) < 1 { q.matchers = append(q.matchers, labels.Matcher{Type: labels.MatchEqual, Name: key}) return q } if len(values) == 1 { q.matchers = append(q.matchers, labels.Matcher{Type: labels.MatchEqual, Name: key, Value: values[0]}) return q } q.matchers = append(q.matchers, labels.Matcher{Type: labels.MatchRegexp, Name: key, Value: strings.Join(values, "|")}) return q } // RestrictQuery appends a label selector to each metric in a given query so // that only metrics with those labels are returned from the query. func (q *QueryRestrictor) RestrictQuery(query string) (string, error) { if query == "" { return "", fmt.Errorf("empty query string") } promQuery, err := parser.ParseExpr(query) if err != nil { return "", fmt.Errorf("error parsing query: %v", err) } parser.Inspect(promQuery, q.addRestrictorLabels()) return promQuery.String(), nil } // Matchers returns the list of label matchers for the restrictor func (q *QueryRestrictor) Matchers() []labels.Matcher { return q.matchers } func (q *QueryRestrictor) addRestrictorLabels() func(n parser.Node, path []parser.Node) error { return func(n parser.Node, path []parser.Node) error { if n == nil { return nil } for _, matcher := range q.matchers { switch n := n.(type) { case *parser.VectorSelector: n.LabelMatchers = appendOrReplaceMatcher(n.LabelMatchers, matcher, q.ReplaceExistingLabel) case *parser.MatrixSelector: n.VectorSelector.(*parser.VectorSelector).LabelMatchers = appendOrReplaceMatcher(n.VectorSelector.(*parser.VectorSelector).LabelMatchers, matcher, q.ReplaceExistingLabel) } } return nil } } func appendOrReplaceMatcher(matchers []*labels.Matcher, newMatcher labels.Matcher, replaceExistingLabel bool) []*labels.Matcher { if replaceExistingLabel && getMatcherIndex(matchers, newMatcher.Name) >= 0 { return replaceLabelValue(matchers, newMatcher.Name, newMatcher.Value) } return append(matchers, &newMatcher) } func getMatcherIndex(matchers []*labels.Matcher, name string) int { for idx, match := range matchers { if match.Name == name { return idx } } return -1 } func replaceLabelValue(matchers []*labels.Matcher, name, value string) []*labels.Matcher { idx := getMatcherIndex(matchers, name) if idx >= -1 { matchers[idx].Value = value } return matchers }