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  }