github.com/grafana/pyroscope@v1.18.0/pkg/frontend/readpath/query_service_handler.go (about) 1 package readpath 2 3 import ( 4 "context" 5 "slices" 6 7 "connectrpc.com/connect" 8 "golang.org/x/sync/errgroup" 9 10 profilev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1" 11 querierv1 "github.com/grafana/pyroscope/api/gen/proto/go/querier/v1" 12 "github.com/grafana/pyroscope/api/gen/proto/go/querier/v1/querierv1connect" 13 typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1" 14 phlaremodel "github.com/grafana/pyroscope/pkg/model" 15 "github.com/grafana/pyroscope/pkg/pprof" 16 ) 17 18 var _ querierv1connect.QuerierServiceHandler = (*Router)(nil) 19 20 func (r *Router) LabelValues( 21 ctx context.Context, 22 c *connect.Request[typesv1.LabelValuesRequest], 23 ) (*connect.Response[typesv1.LabelValuesResponse], error) { 24 return Query[typesv1.LabelValuesRequest, typesv1.LabelValuesResponse](ctx, r, c, 25 func(_, _ *typesv1.LabelValuesRequest) {}, 26 func(a, b *typesv1.LabelValuesResponse) (*typesv1.LabelValuesResponse, error) { 27 m := phlaremodel.NewLabelMerger() 28 m.MergeLabelValues(a.Names) 29 m.MergeLabelValues(b.Names) 30 return &typesv1.LabelValuesResponse{Names: m.LabelValues()}, nil 31 }) 32 } 33 34 func (r *Router) LabelNames( 35 ctx context.Context, 36 c *connect.Request[typesv1.LabelNamesRequest], 37 ) (*connect.Response[typesv1.LabelNamesResponse], error) { 38 return Query[typesv1.LabelNamesRequest, typesv1.LabelNamesResponse](ctx, r, c, 39 func(_, _ *typesv1.LabelNamesRequest) {}, 40 func(a, b *typesv1.LabelNamesResponse) (*typesv1.LabelNamesResponse, error) { 41 m := phlaremodel.NewLabelMerger() 42 m.MergeLabelNames(a.Names) 43 m.MergeLabelNames(b.Names) 44 return &typesv1.LabelNamesResponse{Names: m.LabelNames()}, nil 45 }) 46 } 47 48 func (r *Router) Series( 49 ctx context.Context, 50 c *connect.Request[querierv1.SeriesRequest], 51 ) (*connect.Response[querierv1.SeriesResponse], error) { 52 return Query[querierv1.SeriesRequest, querierv1.SeriesResponse](ctx, r, c, 53 func(_, _ *querierv1.SeriesRequest) {}, 54 func(a, b *querierv1.SeriesResponse) (*querierv1.SeriesResponse, error) { 55 m := phlaremodel.NewLabelMerger() 56 m.MergeSeries(a.LabelsSet) 57 m.MergeSeries(b.LabelsSet) 58 return &querierv1.SeriesResponse{LabelsSet: m.Labels()}, nil 59 }) 60 } 61 62 func (r *Router) SelectMergeStacktraces( 63 ctx context.Context, 64 c *connect.Request[querierv1.SelectMergeStacktracesRequest], 65 ) (*connect.Response[querierv1.SelectMergeStacktracesResponse], error) { 66 // We always query data in the tree format and 67 // return it in the format requested by the client. 68 f := c.Msg.Format 69 c.Msg.Format = querierv1.ProfileFormat_PROFILE_FORMAT_TREE 70 resp, err := Query[querierv1.SelectMergeStacktracesRequest, querierv1.SelectMergeStacktracesResponse](ctx, r, c, 71 func(_, _ *querierv1.SelectMergeStacktracesRequest) {}, 72 func(a, b *querierv1.SelectMergeStacktracesResponse) (*querierv1.SelectMergeStacktracesResponse, error) { 73 m := phlaremodel.NewTreeMerger() 74 if err := m.MergeTreeBytes(a.Tree); err != nil { 75 return nil, err 76 } 77 if err := m.MergeTreeBytes(b.Tree); err != nil { 78 return nil, err 79 } 80 tree := m.Tree().Bytes(c.Msg.GetMaxNodes()) 81 return &querierv1.SelectMergeStacktracesResponse{Tree: tree}, nil 82 }, 83 ) 84 if err == nil && f != c.Msg.Format { 85 resp.Msg.Flamegraph = phlaremodel.NewFlameGraph( 86 phlaremodel.MustUnmarshalTree(resp.Msg.Tree), 87 c.Msg.GetMaxNodes()) 88 } 89 return resp, err 90 } 91 92 func (r *Router) SelectMergeSpanProfile( 93 ctx context.Context, 94 c *connect.Request[querierv1.SelectMergeSpanProfileRequest], 95 ) (*connect.Response[querierv1.SelectMergeSpanProfileResponse], error) { 96 // We always query data in the tree format and 97 // return it in the format requested by the client. 98 f := c.Msg.Format 99 c.Msg.Format = querierv1.ProfileFormat_PROFILE_FORMAT_TREE 100 resp, err := Query[querierv1.SelectMergeSpanProfileRequest, querierv1.SelectMergeSpanProfileResponse](ctx, r, c, 101 func(_, _ *querierv1.SelectMergeSpanProfileRequest) {}, 102 func(a, b *querierv1.SelectMergeSpanProfileResponse) (*querierv1.SelectMergeSpanProfileResponse, error) { 103 m := phlaremodel.NewTreeMerger() 104 if err := m.MergeTreeBytes(a.Tree); err != nil { 105 return nil, err 106 } 107 if err := m.MergeTreeBytes(b.Tree); err != nil { 108 return nil, err 109 } 110 tree := m.Tree().Bytes(c.Msg.GetMaxNodes()) 111 return &querierv1.SelectMergeSpanProfileResponse{Tree: tree}, nil 112 }, 113 ) 114 if err == nil && f != c.Msg.Format { 115 resp.Msg.Flamegraph = phlaremodel.NewFlameGraph( 116 phlaremodel.MustUnmarshalTree(resp.Msg.Tree), 117 c.Msg.GetMaxNodes()) 118 } 119 return resp, err 120 } 121 122 func (r *Router) SelectMergeProfile( 123 ctx context.Context, 124 c *connect.Request[querierv1.SelectMergeProfileRequest], 125 ) (*connect.Response[profilev1.Profile], error) { 126 return Query[querierv1.SelectMergeProfileRequest, profilev1.Profile](ctx, r, c, 127 func(_, _ *querierv1.SelectMergeProfileRequest) {}, 128 func(a, b *profilev1.Profile) (*profilev1.Profile, error) { 129 var m pprof.ProfileMerge 130 if err := m.Merge(a, false); err != nil { 131 return nil, err 132 } 133 if err := m.Merge(b, false); err != nil { 134 return nil, err 135 } 136 return m.Profile(), nil 137 }) 138 } 139 140 func (r *Router) SelectSeries( 141 ctx context.Context, 142 c *connect.Request[querierv1.SelectSeriesRequest], 143 ) (*connect.Response[querierv1.SelectSeriesResponse], error) { 144 limit := int(c.Msg.GetLimit()) 145 return Query[querierv1.SelectSeriesRequest, querierv1.SelectSeriesResponse](ctx, r, c, 146 func(a, b *querierv1.SelectSeriesRequest) { 147 // If both frontends are queries, the limit must 148 // be applied after merging the time series, as 149 // the function is not associative. Otherwise, each 150 // frontend applies the limit independently. 151 if a != nil && b != nil && limit > 0 { 152 a.Limit = nil 153 b.Limit = nil 154 } 155 }, 156 func(a, b *querierv1.SelectSeriesResponse) (*querierv1.SelectSeriesResponse, error) { 157 m := phlaremodel.NewTimeSeriesMerger(true) 158 m.MergeTimeSeries(a.Series) 159 m.MergeTimeSeries(b.Series) 160 return &querierv1.SelectSeriesResponse{Series: m.Top(limit)}, nil 161 }) 162 } 163 164 func (r *Router) Diff( 165 ctx context.Context, 166 c *connect.Request[querierv1.DiffRequest], 167 ) (*connect.Response[querierv1.DiffResponse], error) { 168 g, ctx := errgroup.WithContext(ctx) 169 getTree := func(dst *phlaremodel.Tree, req *querierv1.SelectMergeStacktracesRequest) func() error { 170 return func() error { 171 resp, err := r.SelectMergeStacktraces(ctx, connect.NewRequest(req)) 172 if err != nil { 173 return err 174 } 175 tree, err := phlaremodel.UnmarshalTree(resp.Msg.Tree) 176 if err != nil { 177 return err 178 } 179 *dst = *tree 180 return nil 181 } 182 } 183 184 var left, right phlaremodel.Tree 185 g.Go(getTree(&left, c.Msg.Left)) 186 g.Go(getTree(&right, c.Msg.Right)) 187 if err := g.Wait(); err != nil { 188 return nil, err 189 } 190 191 diff, err := phlaremodel.NewFlamegraphDiff(&left, &right, 0) 192 if err != nil { 193 return nil, err 194 } 195 196 return connect.NewResponse(&querierv1.DiffResponse{Flamegraph: diff}), nil 197 } 198 199 // Stubs: these methods are not supposed to be implemented 200 // and only needed to satisfy interfaces. 201 202 func (r *Router) AnalyzeQuery( 203 ctx context.Context, 204 req *connect.Request[querierv1.AnalyzeQueryRequest], 205 ) (*connect.Response[querierv1.AnalyzeQueryResponse], error) { 206 if r.oldFrontend != nil { 207 return r.oldFrontend.AnalyzeQuery(ctx, req) 208 } 209 return connect.NewResponse(&querierv1.AnalyzeQueryResponse{}), nil 210 } 211 212 func (r *Router) GetProfileStats( 213 ctx context.Context, 214 c *connect.Request[typesv1.GetProfileStatsRequest], 215 ) (*connect.Response[typesv1.GetProfileStatsResponse], error) { 216 return Query[typesv1.GetProfileStatsRequest, typesv1.GetProfileStatsResponse](ctx, r, c, 217 func(_, _ *typesv1.GetProfileStatsRequest) {}, 218 func(a, b *typesv1.GetProfileStatsResponse) (*typesv1.GetProfileStatsResponse, error) { 219 oldestProfileTime := a.OldestProfileTime 220 newestProfileTime := a.NewestProfileTime 221 if b.OldestProfileTime < oldestProfileTime { 222 oldestProfileTime = b.OldestProfileTime 223 } 224 if b.NewestProfileTime > newestProfileTime { 225 newestProfileTime = b.NewestProfileTime 226 } 227 return &typesv1.GetProfileStatsResponse{ 228 DataIngested: a.DataIngested || b.DataIngested, 229 OldestProfileTime: oldestProfileTime, 230 NewestProfileTime: newestProfileTime, 231 }, nil 232 }) 233 } 234 235 func (r *Router) ProfileTypes( 236 ctx context.Context, 237 c *connect.Request[querierv1.ProfileTypesRequest], 238 ) (*connect.Response[querierv1.ProfileTypesResponse], error) { 239 return Query[querierv1.ProfileTypesRequest, querierv1.ProfileTypesResponse](ctx, r, c, 240 func(_, _ *querierv1.ProfileTypesRequest) {}, 241 func(a, b *querierv1.ProfileTypesResponse) (*querierv1.ProfileTypesResponse, error) { 242 pTypes := a.ProfileTypes 243 for _, pType := range b.ProfileTypes { 244 if !slices.Contains(pTypes, pType) { 245 pTypes = append(pTypes, pType) 246 } 247 } 248 return &querierv1.ProfileTypesResponse{ProfileTypes: pTypes}, nil 249 }) 250 }