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 }