github.com/grafana/pyroscope@v1.18.0/pkg/storegateway/query.go (about)

     1  package storegateway
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"slices"
     7  
     8  	"connectrpc.com/connect"
     9  	"github.com/pkg/errors"
    10  	"github.com/prometheus/common/model"
    11  
    12  	ingestv1 "github.com/grafana/pyroscope/api/gen/proto/go/ingester/v1"
    13  	typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1"
    14  	"github.com/grafana/pyroscope/pkg/phlaredb"
    15  	"github.com/grafana/pyroscope/pkg/tenant"
    16  )
    17  
    18  func (s *StoreGateway) MergeProfilesStacktraces(ctx context.Context, stream *connect.BidiStream[ingestv1.MergeProfilesStacktracesRequest, ingestv1.MergeProfilesStacktracesResponse]) error {
    19  	found, err := s.forBucketStore(ctx, func(bs *BucketStore) error {
    20  		return bs.MergeProfilesStacktraces(ctx, stream)
    21  	})
    22  	if err != nil || found {
    23  		return err
    24  	}
    25  	return terminateStream(stream)
    26  }
    27  
    28  func (s *StoreGateway) MergeProfilesLabels(ctx context.Context, stream *connect.BidiStream[ingestv1.MergeProfilesLabelsRequest, ingestv1.MergeProfilesLabelsResponse]) error {
    29  	found, err := s.forBucketStore(ctx, func(bs *BucketStore) error {
    30  		return bs.MergeProfilesLabels(ctx, stream)
    31  	})
    32  	if err != nil || found {
    33  		return err
    34  	}
    35  	return terminateStream(stream)
    36  }
    37  
    38  func (s *StoreGateway) MergeProfilesPprof(ctx context.Context, stream *connect.BidiStream[ingestv1.MergeProfilesPprofRequest, ingestv1.MergeProfilesPprofResponse]) error {
    39  	found, err := s.forBucketStore(ctx, func(bs *BucketStore) error {
    40  		return bs.MergeProfilesPprof(ctx, stream)
    41  	})
    42  	if err != nil || found {
    43  		return err
    44  	}
    45  	return terminateStream(stream)
    46  }
    47  
    48  func (s *StoreGateway) ProfileTypes(ctx context.Context, req *connect.Request[ingestv1.ProfileTypesRequest]) (*connect.Response[ingestv1.ProfileTypesResponse], error) {
    49  	var res *ingestv1.ProfileTypesResponse
    50  	_, err := s.forBucketStore(ctx, func(bs *BucketStore) error {
    51  		var err error
    52  		result, err := phlaredb.ProfileTypes(ctx, req, bs.openBlocksForReading)
    53  		if err != nil {
    54  			return err
    55  		}
    56  		res = result.Msg
    57  		return nil
    58  	})
    59  	if err != nil {
    60  		return nil, connect.NewError(connect.CodeInternal, err)
    61  	}
    62  
    63  	return connect.NewResponse(res), nil
    64  }
    65  
    66  func (s *StoreGateway) LabelValues(ctx context.Context, req *connect.Request[typesv1.LabelValuesRequest]) (*connect.Response[typesv1.LabelValuesResponse], error) {
    67  	var res *typesv1.LabelValuesResponse
    68  	_, err := s.forBucketStore(ctx, func(bs *BucketStore) error {
    69  		var err error
    70  		res, err = phlaredb.LabelValues(ctx, req, bs.openBlocksForReading)
    71  		if err != nil {
    72  			return err
    73  		}
    74  		return nil
    75  	})
    76  	if err != nil {
    77  		return nil, connect.NewError(connect.CodeInternal, err)
    78  	}
    79  
    80  	return connect.NewResponse(res), nil
    81  }
    82  
    83  func (s *StoreGateway) LabelNames(ctx context.Context, req *connect.Request[typesv1.LabelNamesRequest]) (*connect.Response[typesv1.LabelNamesResponse], error) {
    84  	var res *typesv1.LabelNamesResponse
    85  	_, err := s.forBucketStore(ctx, func(bs *BucketStore) error {
    86  		var err error
    87  		res, err = phlaredb.LabelNames(ctx, req, bs.openBlocksForReading)
    88  		if err != nil {
    89  			return err
    90  		}
    91  		return nil
    92  	})
    93  	if err != nil {
    94  		return nil, connect.NewError(connect.CodeInternal, err)
    95  	}
    96  
    97  	return connect.NewResponse(res), nil
    98  }
    99  
   100  func (s *StoreGateway) Series(ctx context.Context, req *connect.Request[ingestv1.SeriesRequest]) (*connect.Response[ingestv1.SeriesResponse], error) {
   101  	var res *ingestv1.SeriesResponse
   102  	_, err := s.forBucketStore(ctx, func(bs *BucketStore) error {
   103  		var err error
   104  		res, err = phlaredb.Series(ctx, req.Msg, bs.openBlocksForReading)
   105  		if err != nil {
   106  			return err
   107  		}
   108  		return nil
   109  	})
   110  	if err != nil {
   111  		return nil, connect.NewError(connect.CodeInternal, err)
   112  	}
   113  
   114  	return connect.NewResponse(res), nil
   115  }
   116  
   117  func (s *StoreGateway) MergeSpanProfile(ctx context.Context, stream *connect.BidiStream[ingestv1.MergeSpanProfileRequest, ingestv1.MergeSpanProfileResponse]) error {
   118  	found, err := s.forBucketStore(ctx, func(bs *BucketStore) error {
   119  		return bs.MergeSpanProfile(ctx, stream)
   120  	})
   121  	if err != nil || found {
   122  		return err
   123  	}
   124  	return terminateStream(stream)
   125  }
   126  
   127  func (s *StoreGateway) BlockMetadata(ctx context.Context, req *connect.Request[ingestv1.BlockMetadataRequest]) (*connect.Response[ingestv1.BlockMetadataResponse], error) {
   128  	var res *ingestv1.BlockMetadataResponse
   129  	found, err := s.forBucketStore(ctx, func(bs *BucketStore) error {
   130  		var err error
   131  		res, err = bs.BlockMetadata(ctx, req.Msg)
   132  		return err
   133  	})
   134  	if err != nil {
   135  		return nil, connect.NewError(connect.CodeInternal, err)
   136  	}
   137  	if !found {
   138  		res = &ingestv1.BlockMetadataResponse{}
   139  	}
   140  
   141  	return connect.NewResponse(res), nil
   142  }
   143  
   144  func (s *StoreGateway) GetBlockStats(ctx context.Context, req *connect.Request[ingestv1.GetBlockStatsRequest]) (*connect.Response[ingestv1.GetBlockStatsResponse], error) {
   145  	res := &ingestv1.GetBlockStatsResponse{}
   146  	_, err := s.forBucketStore(ctx, func(bs *BucketStore) error {
   147  		bs.blocksMx.RLock()
   148  		defer bs.blocksMx.RUnlock()
   149  
   150  		for ulid, block := range bs.blocks {
   151  			if slices.Contains(req.Msg.Ulids, ulid.String()) {
   152  				res.BlockStats = append(res.BlockStats, block.meta.GetStats().ConvertToBlockStats())
   153  			}
   154  		}
   155  		return nil
   156  	})
   157  	if err != nil {
   158  		return nil, connect.NewError(connect.CodeInternal, err)
   159  	}
   160  	return connect.NewResponse(res), nil
   161  }
   162  
   163  func terminateStream[Req, Resp any](stream *connect.BidiStream[Req, Resp]) (err error) {
   164  	if _, err = stream.Receive(); err != nil {
   165  		if errors.Is(err, io.EOF) {
   166  			return connect.NewError(connect.CodeCanceled, errors.New("client closed stream"))
   167  		}
   168  		return err
   169  	}
   170  	if err = stream.Send(new(Resp)); err != nil {
   171  		return err
   172  	}
   173  	return stream.Send(new(Resp))
   174  }
   175  
   176  // forBucketStore executes the given function for the bucketstore with the given tenant ID in the context.
   177  func (s *StoreGateway) forBucketStore(ctx context.Context, f func(*BucketStore) error) (bool, error) {
   178  	tenantID, err := tenant.ExtractTenantIDFromContext(ctx)
   179  	if err != nil {
   180  		return true, connect.NewError(connect.CodeInvalidArgument, err)
   181  	}
   182  	store := s.stores.getStore(tenantID)
   183  	if store != nil {
   184  		return true, f(store)
   185  	}
   186  	return false, nil
   187  }
   188  
   189  func (s *BucketStore) openBlocksForReading(ctx context.Context, minT, maxT model.Time, hints *ingestv1.Hints) (phlaredb.Queriers, error) {
   190  	skipBlock := phlaredb.HintsToBlockSkipper(hints)
   191  	blks := s.blockSet.getFor(minT, maxT)
   192  	querier := make(phlaredb.Queriers, 0, len(blks))
   193  	for _, b := range blks {
   194  		if skipBlock(b.BlockID()) {
   195  			continue
   196  		}
   197  		querier = append(querier, b)
   198  	}
   199  	if err := querier.Open(ctx); err != nil {
   200  		return nil, err
   201  	}
   202  	return querier, nil
   203  }
   204  
   205  func (store *BucketStore) MergeProfilesStacktraces(ctx context.Context, stream *connect.BidiStream[ingestv1.MergeProfilesStacktracesRequest, ingestv1.MergeProfilesStacktracesResponse]) error {
   206  	return phlaredb.MergeProfilesStacktraces(ctx, stream, store.openBlocksForReading)
   207  }
   208  
   209  func (store *BucketStore) MergeProfilesLabels(ctx context.Context, stream *connect.BidiStream[ingestv1.MergeProfilesLabelsRequest, ingestv1.MergeProfilesLabelsResponse]) error {
   210  	return phlaredb.MergeProfilesLabels(ctx, stream, store.openBlocksForReading)
   211  }
   212  
   213  func (store *BucketStore) MergeProfilesPprof(ctx context.Context, stream *connect.BidiStream[ingestv1.MergeProfilesPprofRequest, ingestv1.MergeProfilesPprofResponse]) error {
   214  	return phlaredb.MergeProfilesPprof(ctx, stream, store.openBlocksForReading)
   215  }
   216  
   217  func (store *BucketStore) MergeSpanProfile(ctx context.Context, stream *connect.BidiStream[ingestv1.MergeSpanProfileRequest, ingestv1.MergeSpanProfileResponse]) error {
   218  	return phlaredb.MergeSpanProfile(ctx, stream, store.openBlocksForReading)
   219  }
   220  
   221  func (s *BucketStore) BlockMetadata(ctx context.Context, req *ingestv1.BlockMetadataRequest) (*ingestv1.BlockMetadataResponse, error) {
   222  	set := s.blockSet.getFor(model.Time(req.Start), model.Time(req.End))
   223  	result := &ingestv1.BlockMetadataResponse{
   224  		Blocks: make([]*typesv1.BlockInfo, len(set)),
   225  	}
   226  	for idx, b := range set {
   227  		var info typesv1.BlockInfo
   228  		b.meta.WriteBlockInfo(&info)
   229  		result.Blocks[idx] = &info
   230  	}
   231  	return result, nil
   232  }