github.com/thanos-io/thanos@v0.32.5/pkg/exemplars/exemplars.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  	"sort"
     9  	"sync"
    10  
    11  	"github.com/pkg/errors"
    12  	"github.com/prometheus/prometheus/storage"
    13  	"github.com/thanos-io/thanos/pkg/exemplars/exemplarspb"
    14  	"github.com/thanos-io/thanos/pkg/store/labelpb"
    15  	"github.com/thanos-io/thanos/pkg/tracing"
    16  )
    17  
    18  var _ UnaryClient = &GRPCClient{}
    19  
    20  // UnaryClient is gRPC exemplarspb.Exemplars client which expands streaming exemplars API. Useful for consumers that does not
    21  // support streaming.
    22  type UnaryClient interface {
    23  	Exemplars(ctx context.Context, req *exemplarspb.ExemplarsRequest) ([]*exemplarspb.ExemplarData, storage.Warnings, error)
    24  }
    25  
    26  // GRPCClient allows to retrieve exemplars from local gRPC streaming server implementation.
    27  // TODO(bwplotka): Switch to native gRPC transparent client->server adapter once available.
    28  type GRPCClient struct {
    29  	proxy exemplarspb.ExemplarsServer
    30  
    31  	replicaLabels map[string]struct{}
    32  }
    33  
    34  type exemplarsServer struct {
    35  	// This field just exist to pseudo-implement the unused methods of the interface.
    36  	exemplarspb.Exemplars_ExemplarsServer
    37  	ctx context.Context
    38  
    39  	warnings []error
    40  	data     []*exemplarspb.ExemplarData
    41  	mu       sync.Mutex
    42  }
    43  
    44  func (srv *exemplarsServer) Send(res *exemplarspb.ExemplarsResponse) error {
    45  	if res.GetWarning() != "" {
    46  		srv.mu.Lock()
    47  		defer srv.mu.Unlock()
    48  		srv.warnings = append(srv.warnings, errors.New(res.GetWarning()))
    49  		return nil
    50  	}
    51  
    52  	if res.GetData() == nil {
    53  		return errors.New("empty exemplars data")
    54  	}
    55  
    56  	srv.mu.Lock()
    57  	defer srv.mu.Unlock()
    58  	srv.data = append(srv.data, res.GetData())
    59  	return nil
    60  }
    61  
    62  func (srv *exemplarsServer) Context() context.Context {
    63  	return srv.ctx
    64  }
    65  
    66  func NewGRPCClient(es exemplarspb.ExemplarsServer) *GRPCClient {
    67  	return NewGRPCClientWithDedup(es, nil)
    68  }
    69  
    70  func NewGRPCClientWithDedup(es exemplarspb.ExemplarsServer, replicaLabels []string) *GRPCClient {
    71  	c := &GRPCClient{
    72  		proxy:         es,
    73  		replicaLabels: map[string]struct{}{},
    74  	}
    75  
    76  	for _, label := range replicaLabels {
    77  		c.replicaLabels[label] = struct{}{}
    78  	}
    79  	return c
    80  }
    81  
    82  func (rr *GRPCClient) Exemplars(ctx context.Context, req *exemplarspb.ExemplarsRequest) ([]*exemplarspb.ExemplarData, storage.Warnings, error) {
    83  	span, ctx := tracing.StartSpan(ctx, "exemplar_grpc_request")
    84  	defer span.Finish()
    85  
    86  	resp := &exemplarsServer{ctx: ctx}
    87  
    88  	if err := rr.proxy.Exemplars(req, resp); err != nil {
    89  		return nil, nil, errors.Wrap(err, "proxy Exemplars")
    90  	}
    91  
    92  	if resp.data == nil {
    93  		return make([]*exemplarspb.ExemplarData, 0), resp.warnings, nil
    94  	}
    95  
    96  	resp.data = dedupExemplarsResponse(resp.data, rr.replicaLabels)
    97  	return resp.data, resp.warnings, nil
    98  }
    99  
   100  func dedupExemplarsResponse(exemplarsData []*exemplarspb.ExemplarData, replicaLabels map[string]struct{}) []*exemplarspb.ExemplarData {
   101  	if len(exemplarsData) == 0 {
   102  		return exemplarsData
   103  	}
   104  
   105  	// Deduplicate series labels.
   106  	hashToExemplar := make(map[uint64]*exemplarspb.ExemplarData)
   107  	for _, e := range exemplarsData {
   108  		if len(e.Exemplars) == 0 {
   109  			continue
   110  		}
   111  		e.SeriesLabels.Labels = removeReplicaLabels(e.SeriesLabels.Labels, replicaLabels)
   112  		h := labelpb.ZLabelsToPromLabels(e.SeriesLabels.Labels).Hash()
   113  		if ref, ok := hashToExemplar[h]; ok {
   114  			ref.Exemplars = append(ref.Exemplars, e.Exemplars...)
   115  		} else {
   116  			hashToExemplar[h] = e
   117  		}
   118  	}
   119  
   120  	res := make([]*exemplarspb.ExemplarData, 0, len(hashToExemplar))
   121  	for _, e := range hashToExemplar {
   122  		// Dedup exemplars with the same series labels.
   123  		e.Exemplars = dedupExemplars(e.Exemplars)
   124  		res = append(res, e)
   125  	}
   126  
   127  	// Sort by series labels.
   128  	sort.Slice(res, func(i, j int) bool {
   129  		return res[i].Compare(res[j]) < 0
   130  	})
   131  	return res
   132  }
   133  
   134  func dedupExemplars(exemplars []*exemplarspb.Exemplar) []*exemplarspb.Exemplar {
   135  	for _, e := range exemplars {
   136  		sort.Slice(e.Labels.Labels, func(i, j int) bool {
   137  			return e.Labels.Labels[i].Compare(e.Labels.Labels[j]) < 0
   138  		})
   139  	}
   140  
   141  	sort.Slice(exemplars, func(i, j int) bool {
   142  		return exemplars[i].Compare(exemplars[j]) < 0
   143  	})
   144  
   145  	i := 0
   146  	for j := 1; j < len(exemplars); j++ {
   147  		if exemplars[i].Compare(exemplars[j]) != 0 {
   148  			// Effectively retain exemplars[j] in the resulting slice.
   149  			i++
   150  			exemplars[i] = exemplars[j]
   151  		}
   152  	}
   153  
   154  	return exemplars[:i+1]
   155  }
   156  
   157  func removeReplicaLabels(labels []labelpb.ZLabel, replicaLabels map[string]struct{}) []labelpb.ZLabel {
   158  	if len(replicaLabels) == 0 {
   159  		return labels
   160  	}
   161  	newLabels := make([]labelpb.ZLabel, 0, len(labels))
   162  	for _, l := range labels {
   163  		if _, ok := replicaLabels[l.Name]; !ok {
   164  			newLabels = append(newLabels, l)
   165  		}
   166  	}
   167  
   168  	return newLabels
   169  }