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 }