github.com/grafana/pyroscope@v1.18.0/pkg/frontend/frontend_select_time_series.go (about)

     1  package frontend
     2  
     3  import (
     4  	"context"
     5  	"time"
     6  
     7  	"connectrpc.com/connect"
     8  	"github.com/grafana/dskit/tenant"
     9  	"github.com/prometheus/common/model"
    10  	"golang.org/x/sync/errgroup"
    11  
    12  	querierv1 "github.com/grafana/pyroscope/api/gen/proto/go/querier/v1"
    13  	"github.com/grafana/pyroscope/api/gen/proto/go/querier/v1/querierv1connect"
    14  	phlaremodel "github.com/grafana/pyroscope/pkg/model"
    15  	"github.com/grafana/pyroscope/pkg/util/connectgrpc"
    16  	validationutil "github.com/grafana/pyroscope/pkg/util/validation"
    17  	"github.com/grafana/pyroscope/pkg/validation"
    18  )
    19  
    20  func (f *Frontend) SelectSeries(
    21  	ctx context.Context,
    22  	c *connect.Request[querierv1.SelectSeriesRequest],
    23  ) (*connect.Response[querierv1.SelectSeriesResponse], error) {
    24  	ctx = connectgrpc.WithProcedure(ctx, querierv1connect.QuerierServiceSelectSeriesProcedure)
    25  	tenantIDs, err := tenant.TenantIDs(ctx)
    26  	if err != nil {
    27  		return nil, connect.NewError(connect.CodeInvalidArgument, err)
    28  	}
    29  
    30  	validated, err := validation.ValidateRangeRequest(f.limits, tenantIDs, model.Interval{Start: model.Time(c.Msg.Start), End: model.Time(c.Msg.End)}, model.Now())
    31  	if err != nil {
    32  		return nil, connect.NewError(connect.CodeInvalidArgument, err)
    33  	}
    34  	if validated.IsEmpty {
    35  		return connect.NewResponse(&querierv1.SelectSeriesResponse{}), nil
    36  	}
    37  	c.Msg.Start = int64(validated.Start)
    38  	c.Msg.End = int64(validated.End)
    39  
    40  	g, ctx := errgroup.WithContext(ctx)
    41  	if maxConcurrent := validationutil.SmallestPositiveNonZeroIntPerTenant(tenantIDs, f.limits.MaxQueryParallelism); maxConcurrent > 0 {
    42  		g.SetLimit(maxConcurrent)
    43  	}
    44  
    45  	m := phlaremodel.NewTimeSeriesMerger(true)
    46  	interval := validationutil.MaxDurationOrZeroPerTenant(tenantIDs, f.limits.QuerySplitDuration)
    47  	intervals := NewTimeIntervalIterator(time.UnixMilli(c.Msg.Start), time.UnixMilli(c.Msg.End), interval,
    48  		WithAlignment(time.Second*time.Duration(c.Msg.Step)))
    49  
    50  	for intervals.Next() {
    51  		r := intervals.At()
    52  		g.Go(func() error {
    53  			req := connectgrpc.CloneRequest(c, &querierv1.SelectSeriesRequest{
    54  				ProfileTypeID:      c.Msg.ProfileTypeID,
    55  				LabelSelector:      c.Msg.LabelSelector,
    56  				Start:              r.Start.UnixMilli(),
    57  				End:                r.End.UnixMilli(),
    58  				GroupBy:            c.Msg.GroupBy,
    59  				Step:               c.Msg.Step,
    60  				Aggregation:        c.Msg.Aggregation,
    61  				StackTraceSelector: c.Msg.StackTraceSelector,
    62  			})
    63  			resp, err := connectgrpc.RoundTripUnary[
    64  				querierv1.SelectSeriesRequest,
    65  				querierv1.SelectSeriesResponse](ctx, f, req)
    66  			if err != nil {
    67  				return err
    68  			}
    69  			m.MergeTimeSeries(resp.Msg.Series)
    70  			return nil
    71  		})
    72  	}
    73  
    74  	if err = g.Wait(); err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	series := m.Top(int(c.Msg.GetLimit()))
    79  	return connect.NewResponse(&querierv1.SelectSeriesResponse{Series: series}), nil
    80  }