github.com/thanos-io/thanos@v0.32.5/pkg/rules/rules.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package rules
     5  
     6  import (
     7  	"context"
     8  	"sort"
     9  	"sync"
    10  	"text/template"
    11  	"text/template/parse"
    12  
    13  	"github.com/pkg/errors"
    14  	"github.com/prometheus/prometheus/model/labels"
    15  	"github.com/prometheus/prometheus/promql/parser"
    16  	"github.com/prometheus/prometheus/storage"
    17  
    18  	"github.com/thanos-io/thanos/pkg/rules/rulespb"
    19  	"github.com/thanos-io/thanos/pkg/tracing"
    20  )
    21  
    22  var _ UnaryClient = &GRPCClient{}
    23  
    24  // UnaryClient is gRPC rulespb.Rules client which expands streaming rules API. Useful for consumers that does not
    25  // support streaming.
    26  type UnaryClient interface {
    27  	Rules(ctx context.Context, req *rulespb.RulesRequest) (*rulespb.RuleGroups, storage.Warnings, error)
    28  }
    29  
    30  // GRPCClient allows to retrieve rules from local gRPC streaming server implementation.
    31  // TODO(bwplotka): Switch to native gRPC transparent client->server adapter once available.
    32  type GRPCClient struct {
    33  	proxy rulespb.RulesServer
    34  
    35  	replicaLabels map[string]struct{}
    36  }
    37  
    38  func NewGRPCClient(rs rulespb.RulesServer) *GRPCClient {
    39  	return NewGRPCClientWithDedup(rs, nil)
    40  }
    41  
    42  func NewGRPCClientWithDedup(rs rulespb.RulesServer, replicaLabels []string) *GRPCClient {
    43  	c := &GRPCClient{
    44  		proxy:         rs,
    45  		replicaLabels: map[string]struct{}{},
    46  	}
    47  
    48  	for _, label := range replicaLabels {
    49  		c.replicaLabels[label] = struct{}{}
    50  	}
    51  	return c
    52  }
    53  
    54  func (rr *GRPCClient) Rules(ctx context.Context, req *rulespb.RulesRequest) (*rulespb.RuleGroups, storage.Warnings, error) {
    55  	span, ctx := tracing.StartSpan(ctx, "rules_request")
    56  	defer span.Finish()
    57  
    58  	resp := &rulesServer{ctx: ctx}
    59  
    60  	if err := rr.proxy.Rules(req, resp); err != nil {
    61  		return nil, nil, errors.Wrap(err, "proxy Rules")
    62  	}
    63  
    64  	var err error
    65  	matcherSets := make([][]*labels.Matcher, len(req.MatcherString))
    66  	for i, s := range req.MatcherString {
    67  		matcherSets[i], err = parser.ParseMetricSelector(s)
    68  		if err != nil {
    69  			return nil, nil, errors.Wrap(err, "parser ParseMetricSelector")
    70  		}
    71  	}
    72  
    73  	resp.groups = filterRules(resp.groups, matcherSets)
    74  	// TODO(bwplotka): Move to SortInterface with equal method and heap.
    75  	resp.groups = dedupGroups(resp.groups)
    76  	for _, g := range resp.groups {
    77  		g.Rules = dedupRules(g.Rules, rr.replicaLabels)
    78  	}
    79  
    80  	return &rulespb.RuleGroups{Groups: resp.groups}, resp.warnings, nil
    81  }
    82  
    83  // filterRules filters rules in a group according to given matcherSets.
    84  func filterRules(ruleGroups []*rulespb.RuleGroup, matcherSets [][]*labels.Matcher) []*rulespb.RuleGroup {
    85  	if len(matcherSets) == 0 {
    86  		return ruleGroups
    87  	}
    88  
    89  	groupCount := 0
    90  	for _, g := range ruleGroups {
    91  		ruleCount := 0
    92  		for _, r := range g.Rules {
    93  			// Filter rules based on matcher.
    94  			rl := r.GetLabels()
    95  			if matches(matcherSets, rl) {
    96  				g.Rules[ruleCount] = r
    97  				ruleCount++
    98  			}
    99  		}
   100  		g.Rules = g.Rules[:ruleCount]
   101  
   102  		// Filter groups based on number of rules.
   103  		if len(g.Rules) != 0 {
   104  			ruleGroups[groupCount] = g
   105  			groupCount++
   106  		}
   107  	}
   108  	ruleGroups = ruleGroups[:groupCount]
   109  
   110  	return ruleGroups
   111  }
   112  
   113  // matches returns whether the non-templated labels satisfy all the matchers in matcherSets.
   114  func matches(matcherSets [][]*labels.Matcher, l labels.Labels) bool {
   115  	if len(matcherSets) == 0 {
   116  		return true
   117  	}
   118  
   119  	var nonTemplatedLabels labels.Labels
   120  	labelTemplate := template.New("label")
   121  	for _, label := range l {
   122  		t, err := labelTemplate.Parse(label.Value)
   123  		// Label value is non-templated if it is one node of type NodeText.
   124  		if err == nil && len(t.Root.Nodes) == 1 && t.Root.Nodes[0].Type() == parse.NodeText {
   125  			nonTemplatedLabels = append(nonTemplatedLabels, label)
   126  		}
   127  	}
   128  
   129  	for _, matchers := range matcherSets {
   130  		for _, m := range matchers {
   131  			if v := nonTemplatedLabels.Get(m.Name); !m.Matches(v) {
   132  				return false
   133  			}
   134  		}
   135  	}
   136  	return true
   137  }
   138  
   139  // dedupRules re-sorts the set so that the same series with different replica
   140  // labels are coming right after each other.
   141  func dedupRules(rules []*rulespb.Rule, replicaLabels map[string]struct{}) []*rulespb.Rule {
   142  	if len(rules) == 0 {
   143  		return rules
   144  	}
   145  
   146  	// Remove replica labels and sort each rule's label names such that they are comparable.
   147  	for _, r := range rules {
   148  		removeReplicaLabels(r, replicaLabels)
   149  		sort.Slice(r.GetLabels(), func(i, j int) bool {
   150  			return r.GetLabels()[i].Name < r.GetLabels()[j].Name
   151  		})
   152  	}
   153  
   154  	// Sort rules globally.
   155  	sort.Slice(rules, func(i, j int) bool {
   156  		return rules[i].Compare(rules[j]) < 0
   157  	})
   158  
   159  	// Remove rules based on synthesized deduplication labels.
   160  	i := 0
   161  	for j := 1; j < len(rules); j++ {
   162  		if rules[i].Compare(rules[j]) != 0 {
   163  			// Effectively retain rules[j] in the resulting slice.
   164  			i++
   165  			rules[i] = rules[j]
   166  			continue
   167  		}
   168  
   169  		// If rules are the same, ordering is still determined depending on type.
   170  		switch {
   171  		case rules[i].GetRecording() != nil && rules[j].GetRecording() != nil:
   172  			if rules[i].GetRecording().Compare(rules[j].GetRecording()) <= 0 {
   173  				continue
   174  			}
   175  		case rules[i].GetAlert() != nil && rules[j].GetAlert() != nil:
   176  			if rules[i].GetAlert().Compare(rules[j].GetAlert()) <= 0 {
   177  				continue
   178  			}
   179  		default:
   180  			continue
   181  		}
   182  
   183  		// Swap if we found a younger recording rule or a younger firing alerting rule.
   184  		rules[i] = rules[j]
   185  	}
   186  	return rules[:i+1]
   187  }
   188  
   189  func removeReplicaLabels(r *rulespb.Rule, replicaLabels map[string]struct{}) {
   190  	lbls := r.GetLabels()
   191  	newLabels := make(labels.Labels, 0, len(lbls))
   192  	for _, l := range lbls {
   193  		if _, ok := replicaLabels[l.Name]; !ok {
   194  			newLabels = append(newLabels, l)
   195  		}
   196  	}
   197  	r.SetLabels(newLabels)
   198  }
   199  
   200  func dedupGroups(groups []*rulespb.RuleGroup) []*rulespb.RuleGroup {
   201  	if len(groups) == 0 {
   202  		return nil
   203  	}
   204  
   205  	// Sort groups such that they appear next to each other.
   206  	sort.Slice(groups, func(i, j int) bool { return groups[i].Compare(groups[j]) < 0 })
   207  
   208  	i := 0
   209  	for _, g := range groups[1:] {
   210  		if g.Compare(groups[i]) == 0 {
   211  			groups[i].Rules = append(groups[i].Rules, g.Rules...)
   212  		} else {
   213  			i++
   214  			groups[i] = g
   215  		}
   216  	}
   217  	return groups[:i+1]
   218  }
   219  
   220  type rulesServer struct {
   221  	// This field just exist to pseudo-implement the unused methods of the interface.
   222  	rulespb.Rules_RulesServer
   223  	ctx context.Context
   224  
   225  	warnings []error
   226  	groups   []*rulespb.RuleGroup
   227  	mu       sync.Mutex
   228  }
   229  
   230  func (srv *rulesServer) Send(res *rulespb.RulesResponse) error {
   231  	if res.GetWarning() != "" {
   232  		srv.mu.Lock()
   233  		defer srv.mu.Unlock()
   234  		srv.warnings = append(srv.warnings, errors.New(res.GetWarning()))
   235  		return nil
   236  	}
   237  
   238  	if res.GetGroup() == nil {
   239  		return errors.New("no group")
   240  	}
   241  
   242  	srv.mu.Lock()
   243  	defer srv.mu.Unlock()
   244  	srv.groups = append(srv.groups, res.GetGroup())
   245  	return nil
   246  }
   247  
   248  func (srv *rulesServer) Context() context.Context {
   249  	return srv.ctx
   250  }