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

     1  package readpath
     2  
     3  import (
     4  	"context"
     5  	"time"
     6  
     7  	"connectrpc.com/connect"
     8  	"github.com/go-kit/log"
     9  	"github.com/go-kit/log/level"
    10  	"github.com/grafana/dskit/tenant"
    11  	"github.com/prometheus/common/model"
    12  	"golang.org/x/sync/errgroup"
    13  
    14  	querierv1 "github.com/grafana/pyroscope/api/gen/proto/go/querier/v1"
    15  	"github.com/grafana/pyroscope/api/gen/proto/go/querier/v1/querierv1connect"
    16  	typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1"
    17  	phlaremodel "github.com/grafana/pyroscope/pkg/model"
    18  )
    19  
    20  type Overrides interface {
    21  	ReadPathOverrides(tenantID string) Config
    22  }
    23  
    24  // Router is a proxy that routes queries to the query frontend.
    25  //
    26  // If the query backend is enabled, it routes queries to the new
    27  // query frontend, otherwise it routes queries to the old query
    28  // frontend.
    29  //
    30  // If the query targets a time range that spans the enablement of
    31  // the new query backend, it splits the query into two parts and
    32  // sends them to the old and new query frontends.
    33  type Router struct {
    34  	logger    log.Logger
    35  	overrides Overrides
    36  
    37  	oldFrontend querierv1connect.QuerierServiceClient
    38  	newFrontend querierv1connect.QuerierServiceClient
    39  }
    40  
    41  func NewRouter(
    42  	logger log.Logger,
    43  	overrides Overrides,
    44  	oldFrontend querierv1connect.QuerierServiceClient,
    45  	newFrontend querierv1connect.QuerierServiceClient,
    46  ) *Router {
    47  	return &Router{
    48  		logger:      logger,
    49  		overrides:   overrides,
    50  		oldFrontend: oldFrontend,
    51  		newFrontend: newFrontend,
    52  	}
    53  }
    54  
    55  // Query routes a query to the appropriate query frontend.
    56  // Before the call to the frontend is made, the requests
    57  // are sanitized: any of the arguments can be nil, but not
    58  // both. If the query was split, the responses are aggregated.
    59  func Query[Req, Resp any](
    60  	ctx context.Context,
    61  	router *Router,
    62  	req *connect.Request[Req],
    63  	sanitize func(a, b *Req),
    64  	aggregate func(a, b *Resp) (*Resp, error),
    65  ) (*connect.Response[Resp], error) {
    66  	tenantIDs, err := tenant.TenantIDs(ctx)
    67  	if err != nil {
    68  		return nil, connect.NewError(connect.CodeInvalidArgument, err)
    69  	}
    70  	if len(tenantIDs) != 1 {
    71  		level.Warn(router.logger).Log("msg", "ignoring inter-tenant query overrides", "tenants", tenantIDs)
    72  	}
    73  	tenantID := tenantIDs[0]
    74  
    75  	// Verbose but explicit. Note that limits, error handling, etc.,
    76  	// are delegated to the callee.
    77  	overrides := router.overrides.ReadPathOverrides(tenantID)
    78  	if !overrides.EnableQueryBackend {
    79  		sanitize(req.Msg, nil)
    80  		return query[Req, Resp](ctx, router.oldFrontend, req)
    81  	}
    82  	// Note: the old read path includes both start and end: [start, end].
    83  	// The new read path does not include end: [start, end).
    84  	split := model.TimeFromUnixNano(overrides.EnableQueryBackendFrom.UnixNano())
    85  	queryRange := phlaremodel.GetSafeTimeRange(time.Now(), req.Msg)
    86  	if split.After(queryRange.End) {
    87  		sanitize(req.Msg, nil)
    88  		return query[Req, Resp](ctx, router.oldFrontend, req)
    89  	}
    90  	if split.Before(queryRange.Start) {
    91  		sanitize(nil, req.Msg)
    92  		return query[Req, Resp](ctx, router.newFrontend, req)
    93  	}
    94  
    95  	// We need to send requests both to the old and new read paths:
    96  	// [start, split](split, end), which translates to
    97  	// [start, split-1][split, end).
    98  	c, ok := (any)(req.Msg).(interface{ CloneVT() *Req })
    99  	if !ok {
   100  		return nil, connect.NewError(connect.CodeUnimplemented, nil)
   101  	}
   102  	cloned := c.CloneVT()
   103  	phlaremodel.SetTimeRange(req.Msg, queryRange.Start, split-1)
   104  	phlaremodel.SetTimeRange(cloned, split, queryRange.End)
   105  	sanitize(req.Msg, cloned)
   106  
   107  	var a, b *connect.Response[Resp]
   108  	g, ctx := errgroup.WithContext(ctx)
   109  	g.Go(func() error {
   110  		var err error
   111  		a, err = query[Req, Resp](ctx, router.oldFrontend, req)
   112  		return err
   113  	})
   114  	g.Go(func() error {
   115  		var err error
   116  		b, err = query[Req, Resp](ctx, router.newFrontend, connect.NewRequest(cloned))
   117  		return err
   118  	})
   119  	if err = g.Wait(); err != nil {
   120  		return nil, err
   121  	}
   122  
   123  	resp, err := aggregate(a.Msg, b.Msg)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  
   128  	return connect.NewResponse(resp), nil
   129  }
   130  
   131  func query[Req, Resp any](
   132  	ctx context.Context,
   133  	svc querierv1connect.QuerierServiceClient,
   134  	req *connect.Request[Req],
   135  ) (*connect.Response[Resp], error) {
   136  	var resp any
   137  	var err error
   138  
   139  	switch r := (any)(req).(type) {
   140  	case *connect.Request[querierv1.ProfileTypesRequest]:
   141  		resp, err = svc.ProfileTypes(ctx, r)
   142  	case *connect.Request[typesv1.GetProfileStatsRequest]:
   143  		resp, err = svc.GetProfileStats(ctx, r)
   144  	case *connect.Request[querierv1.AnalyzeQueryRequest]:
   145  		resp, err = svc.AnalyzeQuery(ctx, r)
   146  
   147  	case *connect.Request[typesv1.LabelNamesRequest]:
   148  		resp, err = svc.LabelNames(ctx, r)
   149  	case *connect.Request[typesv1.LabelValuesRequest]:
   150  		resp, err = svc.LabelValues(ctx, r)
   151  	case *connect.Request[querierv1.SeriesRequest]:
   152  		resp, err = svc.Series(ctx, r)
   153  
   154  	case *connect.Request[querierv1.SelectMergeStacktracesRequest]:
   155  		resp, err = svc.SelectMergeStacktraces(ctx, r)
   156  	case *connect.Request[querierv1.SelectMergeSpanProfileRequest]:
   157  		resp, err = svc.SelectMergeSpanProfile(ctx, r)
   158  	case *connect.Request[querierv1.SelectMergeProfileRequest]:
   159  		resp, err = svc.SelectMergeProfile(ctx, r)
   160  	case *connect.Request[querierv1.SelectSeriesRequest]:
   161  		resp, err = svc.SelectSeries(ctx, r)
   162  	case *connect.Request[querierv1.DiffRequest]:
   163  		resp, err = svc.Diff(ctx, r)
   164  
   165  	default:
   166  		return nil, connect.NewError(connect.CodeUnimplemented, nil)
   167  	}
   168  
   169  	if err != nil || resp == nil {
   170  		return nil, err
   171  	}
   172  
   173  	return resp.(*connect.Response[Resp]), nil
   174  }