github.com/thanos-io/thanos@v0.32.5/pkg/exemplars/proxy.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package exemplars 5 6 import ( 7 "context" 8 "io" 9 "strings" 10 11 "github.com/go-kit/log" 12 "github.com/go-kit/log/level" 13 "github.com/pkg/errors" 14 "github.com/prometheus/prometheus/model/labels" 15 "github.com/prometheus/prometheus/promql/parser" 16 "golang.org/x/sync/errgroup" 17 "google.golang.org/grpc" 18 "google.golang.org/grpc/codes" 19 "google.golang.org/grpc/status" 20 21 "github.com/thanos-io/thanos/pkg/exemplars/exemplarspb" 22 "github.com/thanos-io/thanos/pkg/store/storepb" 23 "github.com/thanos-io/thanos/pkg/tracing" 24 ) 25 26 // Proxy implements exemplarspb.Exemplars gRPC that fanouts requests to 27 // given exemplarspb.Exemplars. 28 type Proxy struct { 29 logger log.Logger 30 exemplars func() []*exemplarspb.ExemplarStore 31 selectorLabels labels.Labels 32 } 33 34 // RegisterExemplarsServer register exemplars server. 35 func RegisterExemplarsServer(exemplarsSrv exemplarspb.ExemplarsServer) func(*grpc.Server) { 36 return func(s *grpc.Server) { 37 exemplarspb.RegisterExemplarsServer(s, exemplarsSrv) 38 } 39 } 40 41 // NewProxy return new exemplars.Proxy. 42 func NewProxy(logger log.Logger, exemplars func() []*exemplarspb.ExemplarStore, selectorLabels labels.Labels) *Proxy { 43 return &Proxy{ 44 logger: logger, 45 exemplars: exemplars, 46 selectorLabels: selectorLabels, 47 } 48 } 49 50 type exemplarsStream struct { 51 client exemplarspb.ExemplarsClient 52 request *exemplarspb.ExemplarsRequest 53 channel chan<- *exemplarspb.ExemplarData 54 server exemplarspb.Exemplars_ExemplarsServer 55 } 56 57 func (s *Proxy) Exemplars(req *exemplarspb.ExemplarsRequest, srv exemplarspb.Exemplars_ExemplarsServer) error { 58 span, ctx := tracing.StartSpan(srv.Context(), "proxy_exemplars") 59 defer span.Finish() 60 61 expr, err := parser.ParseExpr(req.Query) 62 if err != nil { 63 return err 64 } 65 66 match, selectors := selectorsMatchesExternalLabels(parser.ExtractSelectors(expr), s.selectorLabels) 67 68 // There is no matched selectors for this thanos query. 69 if !match { 70 return nil 71 } 72 73 if len(selectors) == 0 { 74 return status.Error(codes.InvalidArgument, errors.New("no matchers specified (excluding external labels)").Error()) 75 } 76 77 var ( 78 g, gctx = errgroup.WithContext(ctx) 79 respChan = make(chan *exemplarspb.ExemplarData, 10) 80 exemplars []*exemplarspb.ExemplarData 81 ) 82 83 queryParts := make([]string, 0) 84 labelMatchers := make([]string, 0) 85 for _, st := range s.exemplars() { 86 queryParts = queryParts[:0] 87 88 Matchers: 89 for _, matchers := range selectors { 90 matcherSet := make(map[string]struct{}) 91 for _, m := range matchers { 92 for _, ls := range st.LabelSets { 93 if lv := ls.Get(m.Name); lv != "" { 94 if !m.Matches(lv) { 95 continue Matchers 96 } else { 97 // If the current matcher matches one external label, 98 // we don't add it to the current metric selector 99 // as Prometheus' Exemplars API cannot handle external labels. 100 continue 101 } 102 } 103 matcherSet[m.String()] = struct{}{} 104 } 105 } 106 107 labelMatchers = labelMatchers[:0] 108 for m := range matcherSet { 109 labelMatchers = append(labelMatchers, m) 110 } 111 112 queryParts = append(queryParts, "{"+strings.Join(labelMatchers, ", ")+"}") 113 } 114 115 // No matchers match this store. 116 if len(queryParts) == 0 { 117 continue 118 } 119 120 // Construct the query by concatenating metric selectors with '+'. 121 // We cannot preserve the original query info, but the returned 122 // results are the same. 123 query := strings.Join(queryParts, "+") 124 r := &exemplarspb.ExemplarsRequest{ 125 Start: req.Start, 126 End: req.End, 127 Query: query, 128 PartialResponseStrategy: req.PartialResponseStrategy, 129 } 130 131 es := &exemplarsStream{ 132 133 client: st.ExemplarsClient, 134 request: r, 135 channel: respChan, 136 server: srv, 137 } 138 g.Go(func() error { return es.receive(gctx) }) 139 } 140 141 go func() { 142 _ = g.Wait() 143 close(respChan) 144 }() 145 146 for resp := range respChan { 147 exemplars = append(exemplars, resp) 148 } 149 150 if err := g.Wait(); err != nil { 151 level.Error(s.logger).Log("err", err) 152 return err 153 } 154 155 for _, e := range exemplars { 156 tracing.DoInSpan(srv.Context(), "send_exemplars_response", func(_ context.Context) { 157 err = srv.Send(exemplarspb.NewExemplarsResponse(e)) 158 }) 159 if err != nil { 160 return status.Error(codes.Unknown, errors.Wrap(err, "send exemplars response").Error()) 161 } 162 } 163 164 return nil 165 } 166 167 func (stream *exemplarsStream) receive(ctx context.Context) error { 168 exemplars, err := stream.client.Exemplars(ctx, stream.request) 169 if err != nil { 170 err = errors.Wrapf(err, "fetching exemplars from exemplars client %v", stream.client) 171 172 if stream.request.PartialResponseStrategy == storepb.PartialResponseStrategy_ABORT { 173 return err 174 } 175 176 if serr := stream.server.Send(exemplarspb.NewWarningExemplarsResponse(err)); serr != nil { 177 return serr 178 } 179 // Not an error if response strategy is warning. 180 return nil 181 } 182 183 for { 184 exemplar, err := exemplars.Recv() 185 if err == io.EOF { 186 return nil 187 } 188 189 if err != nil { 190 err = errors.Wrapf(err, "receiving exemplars from exemplars client %v", stream.client) 191 192 if stream.request.PartialResponseStrategy == storepb.PartialResponseStrategy_ABORT { 193 return err 194 } 195 196 if err := stream.server.Send(exemplarspb.NewWarningExemplarsResponse(err)); err != nil { 197 return errors.Wrapf(err, "sending exemplars error to server %v", stream.server) 198 } 199 // Not an error if response strategy is warning. 200 return nil 201 } 202 203 if w := exemplar.GetWarning(); w != "" { 204 if err := stream.server.Send(exemplarspb.NewWarningExemplarsResponse(errors.New(w))); err != nil { 205 return errors.Wrapf(err, "sending exemplars warning to server %v", stream.server) 206 } 207 continue 208 } 209 210 select { 211 case stream.channel <- exemplar.GetData(): 212 case <-ctx.Done(): 213 return ctx.Err() 214 } 215 } 216 } 217 218 // matchesExternalLabels returns false if given matchers are not matching external labels. 219 // If true, matchesExternalLabels also returns Prometheus matchers without those matching external labels. 220 func matchesExternalLabels(ms []*labels.Matcher, externalLabels labels.Labels) (bool, []*labels.Matcher) { 221 if len(externalLabels) == 0 { 222 return true, ms 223 } 224 225 var newMatchers []*labels.Matcher 226 for i, tm := range ms { 227 // Validate all matchers. 228 extValue := externalLabels.Get(tm.Name) 229 if extValue == "" { 230 // Agnostic to external labels. 231 ms = append(ms[:i], ms[i:]...) 232 newMatchers = append(newMatchers, tm) 233 continue 234 } 235 236 if !tm.Matches(extValue) { 237 // External label does not match. This should not happen - it should be filtered out on query node, 238 // but let's do that anyway here. 239 return false, nil 240 } 241 } 242 return true, newMatchers 243 }