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 }