github.com/grafana/pyroscope@v1.18.0/pkg/querier/ingester_querier.go (about)

     1  package querier
     2  
     3  import (
     4  	"context"
     5  
     6  	"connectrpc.com/connect"
     7  	"github.com/grafana/dskit/ring"
     8  	ring_client "github.com/grafana/dskit/ring/client"
     9  	"github.com/opentracing/opentracing-go"
    10  	otlog "github.com/opentracing/opentracing-go/log"
    11  	"github.com/prometheus/prometheus/promql/parser"
    12  	"golang.org/x/sync/errgroup"
    13  
    14  	googlev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1"
    15  	ingesterv1 "github.com/grafana/pyroscope/api/gen/proto/go/ingester/v1"
    16  	querierv1 "github.com/grafana/pyroscope/api/gen/proto/go/querier/v1"
    17  	typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1"
    18  	"github.com/grafana/pyroscope/pkg/clientpool"
    19  	phlaremodel "github.com/grafana/pyroscope/pkg/model"
    20  	"github.com/grafana/pyroscope/pkg/util"
    21  )
    22  
    23  type IngesterQueryClient interface {
    24  	LabelValues(context.Context, *connect.Request[typesv1.LabelValuesRequest]) (*connect.Response[typesv1.LabelValuesResponse], error)
    25  	LabelNames(context.Context, *connect.Request[typesv1.LabelNamesRequest]) (*connect.Response[typesv1.LabelNamesResponse], error)
    26  	ProfileTypes(context.Context, *connect.Request[ingesterv1.ProfileTypesRequest]) (*connect.Response[ingesterv1.ProfileTypesResponse], error)
    27  	Series(ctx context.Context, req *connect.Request[ingesterv1.SeriesRequest]) (*connect.Response[ingesterv1.SeriesResponse], error)
    28  	MergeProfilesStacktraces(context.Context) clientpool.BidiClientMergeProfilesStacktraces
    29  	MergeProfilesLabels(ctx context.Context) clientpool.BidiClientMergeProfilesLabels
    30  	MergeProfilesPprof(ctx context.Context) clientpool.BidiClientMergeProfilesPprof
    31  	MergeSpanProfile(ctx context.Context) clientpool.BidiClientMergeSpanProfile
    32  	BlockMetadata(ctx context.Context, req *connect.Request[ingesterv1.BlockMetadataRequest]) (*connect.Response[ingesterv1.BlockMetadataResponse], error)
    33  	GetProfileStats(ctx context.Context, req *connect.Request[typesv1.GetProfileStatsRequest]) (*connect.Response[typesv1.GetProfileStatsResponse], error)
    34  	GetBlockStats(ctx context.Context, req *connect.Request[ingesterv1.GetBlockStatsRequest]) (*connect.Response[ingesterv1.GetBlockStatsResponse], error)
    35  }
    36  
    37  // IngesterQuerier helps with querying the ingesters.
    38  type IngesterQuerier struct {
    39  	ring ring.ReadRing
    40  	pool *ring_client.Pool
    41  }
    42  
    43  func NewIngesterQuerier(pool *ring_client.Pool, ring ring.ReadRing) *IngesterQuerier {
    44  	return &IngesterQuerier{
    45  		ring: ring,
    46  		pool: pool,
    47  	}
    48  }
    49  
    50  // readNoExtend is a ring.Operation that only selects instances marked as ring.ACTIVE.
    51  // This should mirror the operation used when choosing ingesters to write series to (ring.WriteNoExtend).
    52  var readNoExtend = ring.NewOp([]ring.InstanceState{ring.ACTIVE}, nil)
    53  
    54  // forAllIngesters runs f, in parallel, for all ingesters
    55  func forAllIngesters[T any](ctx context.Context, ingesterQuerier *IngesterQuerier, f QueryReplicaFn[T, IngesterQueryClient]) ([]ResponseFromReplica[T], error) {
    56  	replicationSet, err := ingesterQuerier.ring.GetReplicationSetForOperation(readNoExtend)
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  
    61  	clientFactoryFn := func(addr string) (IngesterQueryClient, error) {
    62  		client, err := ingesterQuerier.pool.GetClientFor(addr)
    63  		if err != nil {
    64  			return nil, err
    65  		}
    66  		return client.(IngesterQueryClient), nil
    67  	}
    68  	return forGivenReplicationSet(ctx, clientFactoryFn, replicationSet, f)
    69  }
    70  
    71  // forAllPlannedIngesters runs f, in parallel, for all ingesters part of the plan
    72  func forAllPlannedIngesters[T any](ctx context.Context, ingesterQuerier *IngesterQuerier, plan blockPlan, f QueryReplicaWithHintsFn[T, IngesterQueryClient]) ([]ResponseFromReplica[T], error) {
    73  	replicationSet, err := ingesterQuerier.ring.GetReplicationSetForOperation(readNoExtend)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	return forGivenPlan(ctx, plan, func(addr string) (IngesterQueryClient, error) {
    79  		client, err := ingesterQuerier.pool.GetClientFor(addr)
    80  		if err != nil {
    81  			return nil, err
    82  		}
    83  		return client.(IngesterQueryClient), nil
    84  	}, replicationSet, f)
    85  }
    86  
    87  func (q *Querier) selectTreeFromIngesters(ctx context.Context, req *querierv1.SelectMergeStacktracesRequest, plan blockPlan) (*phlaremodel.Tree, error) {
    88  	sp, ctx := opentracing.StartSpanFromContext(ctx, "SelectTree Ingesters")
    89  	defer sp.Finish()
    90  	profileType, err := phlaremodel.ParseProfileTypeSelector(req.ProfileTypeID)
    91  	if err != nil {
    92  		return nil, connect.NewError(connect.CodeInvalidArgument, err)
    93  	}
    94  	_, err = parser.ParseMetricSelector(req.LabelSelector)
    95  	if err != nil {
    96  		return nil, connect.NewError(connect.CodeInvalidArgument, err)
    97  	}
    98  	ctx, cancel := context.WithCancel(ctx)
    99  	defer cancel()
   100  
   101  	var responses []ResponseFromReplica[clientpool.BidiClientMergeProfilesStacktraces]
   102  	if plan != nil {
   103  		responses, err = forAllPlannedIngesters(ctx, q.ingesterQuerier, plan, func(ctx context.Context, ic IngesterQueryClient, hints *ingesterv1.Hints) (clientpool.BidiClientMergeProfilesStacktraces, error) {
   104  			return ic.MergeProfilesStacktraces(ctx), nil
   105  		})
   106  	} else {
   107  		responses, err = forAllIngesters(ctx, q.ingesterQuerier, func(ctx context.Context, ic IngesterQueryClient) (clientpool.BidiClientMergeProfilesStacktraces, error) {
   108  			return ic.MergeProfilesStacktraces(ctx), nil
   109  		})
   110  	}
   111  	if err != nil {
   112  		return nil, connect.NewError(connect.CodeInternal, err)
   113  	}
   114  	// send the first initial request to all ingesters.
   115  	g, _ := errgroup.WithContext(ctx)
   116  	for idx := range responses {
   117  		r := responses[idx]
   118  		blockHints, err := BlockHints(plan, r.addr)
   119  		if err != nil {
   120  			return nil, connect.NewError(connect.CodeInternal, err)
   121  		}
   122  
   123  		g.Go(util.RecoverPanic(func() error {
   124  			return r.response.Send(&ingesterv1.MergeProfilesStacktracesRequest{
   125  				Request: &ingesterv1.SelectProfilesRequest{
   126  					LabelSelector: req.LabelSelector,
   127  					Start:         req.Start,
   128  					End:           req.End,
   129  					Type:          profileType,
   130  					Hints:         &ingesterv1.Hints{Block: blockHints},
   131  				},
   132  				MaxNodes: req.MaxNodes,
   133  			})
   134  		}))
   135  	}
   136  	if err = g.Wait(); err != nil {
   137  		return nil, connect.NewError(connect.CodeInternal, err)
   138  	}
   139  
   140  	// merge all profiles
   141  	return selectMergeTree(ctx, responses)
   142  }
   143  
   144  func (q *Querier) selectProfileFromIngesters(ctx context.Context, req *querierv1.SelectMergeProfileRequest, plan blockPlan) (*googlev1.Profile, error) {
   145  	span, ctx := opentracing.StartSpanFromContext(ctx, "SelectProfile Ingesters")
   146  	defer span.Finish()
   147  	profileType, err := phlaremodel.ParseProfileTypeSelector(req.ProfileTypeID)
   148  	if err != nil {
   149  		return nil, connect.NewError(connect.CodeInvalidArgument, err)
   150  	}
   151  	_, err = parser.ParseMetricSelector(req.LabelSelector)
   152  	if err != nil {
   153  		return nil, connect.NewError(connect.CodeInvalidArgument, err)
   154  	}
   155  	ctx, cancel := context.WithCancel(ctx)
   156  	defer cancel()
   157  
   158  	var responses []ResponseFromReplica[clientpool.BidiClientMergeProfilesPprof]
   159  	if plan != nil {
   160  		responses, err = forAllPlannedIngesters(ctx, q.ingesterQuerier, plan, func(ctx context.Context, ic IngesterQueryClient, hints *ingesterv1.Hints) (clientpool.BidiClientMergeProfilesPprof, error) {
   161  			return ic.MergeProfilesPprof(ctx), nil
   162  		})
   163  	} else {
   164  		responses, err = forAllIngesters(ctx, q.ingesterQuerier, func(ctx context.Context, ic IngesterQueryClient) (clientpool.BidiClientMergeProfilesPprof, error) {
   165  			return ic.MergeProfilesPprof(ctx), nil
   166  		})
   167  	}
   168  	if err != nil {
   169  		return nil, connect.NewError(connect.CodeInternal, err)
   170  	}
   171  	// send the first initial request to all ingesters.
   172  	g, _ := errgroup.WithContext(ctx)
   173  	for idx := range responses {
   174  		r := responses[idx]
   175  		blockHints, err := BlockHints(plan, r.addr)
   176  		if err != nil {
   177  			return nil, connect.NewError(connect.CodeInternal, err)
   178  		}
   179  
   180  		g.Go(util.RecoverPanic(func() error {
   181  			return r.response.Send(&ingesterv1.MergeProfilesPprofRequest{
   182  				Request: &ingesterv1.SelectProfilesRequest{
   183  					LabelSelector: req.LabelSelector,
   184  					Start:         req.Start,
   185  					End:           req.End,
   186  					Type:          profileType,
   187  					Hints:         &ingesterv1.Hints{Block: blockHints},
   188  				},
   189  				MaxNodes:           req.MaxNodes,
   190  				StackTraceSelector: req.StackTraceSelector,
   191  			})
   192  		}))
   193  	}
   194  	if err = g.Wait(); err != nil {
   195  		return nil, connect.NewError(connect.CodeInternal, err)
   196  	}
   197  
   198  	// merge all profiles
   199  	span.LogFields(otlog.String("msg", "selectMergePprofProfile"))
   200  	return selectMergePprofProfile(ctx, profileType, responses)
   201  }
   202  
   203  func (q *Querier) selectSeriesFromIngesters(ctx context.Context, req *ingesterv1.MergeProfilesLabelsRequest, plan map[string]*blockPlanEntry) ([]ResponseFromReplica[clientpool.BidiClientMergeProfilesLabels], error) {
   204  	sp, ctx := opentracing.StartSpanFromContext(ctx, "SelectSeries Ingesters")
   205  	defer sp.Finish()
   206  	var responses []ResponseFromReplica[clientpool.BidiClientMergeProfilesLabels]
   207  	var err error
   208  
   209  	if plan != nil {
   210  		responses, err = forAllPlannedIngesters(ctx, q.ingesterQuerier, plan, func(ctx context.Context, q IngesterQueryClient, hint *ingesterv1.Hints) (clientpool.BidiClientMergeProfilesLabels, error) {
   211  			return q.MergeProfilesLabels(ctx), nil
   212  		})
   213  	} else {
   214  		responses, err = forAllIngesters(ctx, q.ingesterQuerier, func(ctx context.Context, ic IngesterQueryClient) (clientpool.BidiClientMergeProfilesLabels, error) {
   215  			return ic.MergeProfilesLabels(ctx), nil
   216  		})
   217  	}
   218  	if err != nil {
   219  		return nil, connect.NewError(connect.CodeInternal, err)
   220  	}
   221  	// send the first initial request to all ingesters.
   222  	g, _ := errgroup.WithContext(ctx)
   223  	for _, r := range responses {
   224  		r := r
   225  		blockHints, err := BlockHints(plan, r.addr)
   226  		if err != nil {
   227  			return nil, connect.NewError(connect.CodeInternal, err)
   228  		}
   229  		g.Go(util.RecoverPanic(func() error {
   230  			req := req.CloneVT()
   231  			req.Request.Hints = &ingesterv1.Hints{Block: blockHints}
   232  			return r.response.Send(req)
   233  		}))
   234  	}
   235  	if err := g.Wait(); err != nil {
   236  		return nil, connect.NewError(connect.CodeInternal, err)
   237  	}
   238  	return responses, nil
   239  }
   240  
   241  func (q *Querier) labelValuesFromIngesters(ctx context.Context, req *typesv1.LabelValuesRequest) ([]ResponseFromReplica[[]string], error) {
   242  	sp, ctx := opentracing.StartSpanFromContext(ctx, "LabelValues Ingesters")
   243  	defer sp.Finish()
   244  
   245  	responses, err := forAllIngesters(ctx, q.ingesterQuerier, func(childCtx context.Context, ic IngesterQueryClient) ([]string, error) {
   246  		res, err := ic.LabelValues(childCtx, connect.NewRequest(req))
   247  		if err != nil {
   248  			return nil, err
   249  		}
   250  		return res.Msg.Names, nil
   251  	})
   252  	if err != nil {
   253  		return nil, connect.NewError(connect.CodeInternal, err)
   254  	}
   255  	return responses, nil
   256  }
   257  
   258  func (q *Querier) labelNamesFromIngesters(ctx context.Context, req *typesv1.LabelNamesRequest) ([]ResponseFromReplica[[]string], error) {
   259  	sp, ctx := opentracing.StartSpanFromContext(ctx, "LabelNames Ingesters")
   260  	defer sp.Finish()
   261  
   262  	responses, err := forAllIngesters(ctx, q.ingesterQuerier, func(childCtx context.Context, ic IngesterQueryClient) ([]string, error) {
   263  		res, err := ic.LabelNames(childCtx, connect.NewRequest(req))
   264  		if err != nil {
   265  			return nil, err
   266  		}
   267  		return res.Msg.Names, nil
   268  	})
   269  	if err != nil {
   270  		return nil, connect.NewError(connect.CodeInternal, err)
   271  	}
   272  	return responses, nil
   273  }
   274  
   275  func (q *Querier) seriesFromIngesters(ctx context.Context, req *ingesterv1.SeriesRequest) ([]ResponseFromReplica[[]*typesv1.Labels], error) {
   276  	sp, ctx := opentracing.StartSpanFromContext(ctx, "Series Ingesters")
   277  	defer sp.Finish()
   278  
   279  	responses, err := forAllIngesters(ctx, q.ingesterQuerier, func(childCtx context.Context, ic IngesterQueryClient) ([]*typesv1.Labels, error) {
   280  		res, err := ic.Series(childCtx, connect.NewRequest(&ingesterv1.SeriesRequest{
   281  			Matchers:   req.Matchers,
   282  			LabelNames: req.LabelNames,
   283  			Start:      req.Start,
   284  			End:        req.End,
   285  		}))
   286  		if err != nil {
   287  			return nil, err
   288  		}
   289  		return res.Msg.LabelsSet, nil
   290  	})
   291  	if err != nil {
   292  		return nil, connect.NewError(connect.CodeInternal, err)
   293  	}
   294  	return responses, nil
   295  }
   296  
   297  func (q *Querier) selectSpanProfileFromIngesters(ctx context.Context, req *querierv1.SelectMergeSpanProfileRequest, plan map[string]*blockPlanEntry) (*phlaremodel.Tree, error) {
   298  	sp, ctx := opentracing.StartSpanFromContext(ctx, "SelectMergeSpanProfile Ingesters")
   299  	defer sp.Finish()
   300  	profileType, err := phlaremodel.ParseProfileTypeSelector(req.ProfileTypeID)
   301  	if err != nil {
   302  		return nil, connect.NewError(connect.CodeInvalidArgument, err)
   303  	}
   304  	_, err = parser.ParseMetricSelector(req.LabelSelector)
   305  	if err != nil {
   306  		return nil, connect.NewError(connect.CodeInvalidArgument, err)
   307  	}
   308  	ctx, cancel := context.WithCancel(ctx)
   309  	defer cancel()
   310  
   311  	var responses []ResponseFromReplica[clientpool.BidiClientMergeSpanProfile]
   312  	if plan != nil {
   313  		responses, err = forAllPlannedIngesters(ctx, q.ingesterQuerier, plan, func(ctx context.Context, ic IngesterQueryClient, hints *ingesterv1.Hints) (clientpool.BidiClientMergeSpanProfile, error) {
   314  			return ic.MergeSpanProfile(ctx), nil
   315  		})
   316  	} else {
   317  		responses, err = forAllIngesters(ctx, q.ingesterQuerier, func(ctx context.Context, ic IngesterQueryClient) (clientpool.BidiClientMergeSpanProfile, error) {
   318  			return ic.MergeSpanProfile(ctx), nil
   319  		})
   320  	}
   321  	if err != nil {
   322  		return nil, connect.NewError(connect.CodeInternal, err)
   323  	}
   324  	// send the first initial request to all ingesters.
   325  	g, _ := errgroup.WithContext(ctx)
   326  	for idx := range responses {
   327  		r := responses[idx]
   328  		blockHints, err := BlockHints(plan, r.addr)
   329  		if err != nil {
   330  			return nil, connect.NewError(connect.CodeInternal, err)
   331  		}
   332  
   333  		g.Go(util.RecoverPanic(func() error {
   334  			return r.response.Send(&ingesterv1.MergeSpanProfileRequest{
   335  				Request: &ingesterv1.SelectSpanProfileRequest{
   336  					LabelSelector: req.LabelSelector,
   337  					Start:         req.Start,
   338  					End:           req.End,
   339  					Type:          profileType,
   340  					SpanSelector:  req.SpanSelector,
   341  					Hints:         &ingesterv1.Hints{Block: blockHints},
   342  				},
   343  				MaxNodes: req.MaxNodes,
   344  			})
   345  		}))
   346  	}
   347  	if err = g.Wait(); err != nil {
   348  		return nil, connect.NewError(connect.CodeInternal, err)
   349  	}
   350  
   351  	// merge all profiles
   352  	return selectMergeSpanProfile(ctx, responses)
   353  }
   354  
   355  func (q *Querier) blockSelectFromIngesters(ctx context.Context, req *ingesterv1.BlockMetadataRequest) ([]ResponseFromReplica[[]*typesv1.BlockInfo], error) {
   356  	sp, ctx := opentracing.StartSpanFromContext(ctx, "blockSelectFromIngesters")
   357  	defer sp.Finish()
   358  
   359  	responses, err := forAllIngesters(ctx, q.ingesterQuerier, func(childCtx context.Context, ic IngesterQueryClient) ([]*typesv1.BlockInfo, error) {
   360  		res, err := ic.BlockMetadata(childCtx, connect.NewRequest(&ingesterv1.BlockMetadataRequest{
   361  			Start: req.Start,
   362  			End:   req.End,
   363  		}))
   364  		if err != nil {
   365  			return nil, err
   366  		}
   367  		return res.Msg.Blocks, nil
   368  	})
   369  	if err != nil {
   370  		return nil, connect.NewError(connect.CodeInternal, err)
   371  	}
   372  	return responses, nil
   373  }