github.com/grafana/pyroscope@v1.18.0/pkg/querybackend/query.go (about) 1 package querybackend 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 8 "github.com/go-kit/log" 9 "github.com/go-kit/log/level" 10 "github.com/iancoleman/strcase" 11 "github.com/opentracing/opentracing-go" 12 otlog "github.com/opentracing/opentracing-go/log" 13 "golang.org/x/sync/errgroup" 14 15 metastorev1 "github.com/grafana/pyroscope/api/gen/proto/go/metastore/v1" 16 queryv1 "github.com/grafana/pyroscope/api/gen/proto/go/query/v1" 17 "github.com/grafana/pyroscope/pkg/block" 18 "github.com/grafana/pyroscope/pkg/util" 19 ) 20 21 // TODO(kolesnikovae): We have a procedural definition of our queries, 22 // thus we have handlers. Instead, in order to enable pipelining and 23 // reduce the boilerplate, we should define query execution plans. 24 25 const ( 26 // maxProfileIDsToLog is the maximum number of profile IDs to log in trace spans. 27 maxProfileIDsToLog = 10 28 ) 29 30 var ( 31 handlerMutex = new(sync.RWMutex) 32 queryHandlers = map[queryv1.QueryType]queryHandler{} 33 ) 34 35 type queryHandler func(*queryContext, *queryv1.Query) (*queryv1.Report, error) 36 37 func registerQueryHandler(t queryv1.QueryType, h queryHandler) { 38 handlerMutex.Lock() 39 defer handlerMutex.Unlock() 40 if _, ok := queryHandlers[t]; ok { 41 panic(fmt.Sprintf("%s: handler already registered", t)) 42 } 43 queryHandlers[t] = h 44 } 45 46 func getQueryHandler(t queryv1.QueryType) (queryHandler, error) { 47 handlerMutex.RLock() 48 defer handlerMutex.RUnlock() 49 handler, ok := queryHandlers[t] 50 if !ok { 51 return nil, fmt.Errorf("unknown query type %s", t) 52 } 53 return handler, nil 54 } 55 56 var ( 57 depMutex = new(sync.RWMutex) 58 queryDependencies = map[queryv1.QueryType][]block.Section{} 59 ) 60 61 func registerQueryDependencies(t queryv1.QueryType, deps ...block.Section) { 62 depMutex.Lock() 63 defer depMutex.Unlock() 64 if _, ok := queryDependencies[t]; ok { 65 panic(fmt.Sprintf("%s: dependencies already registered", t)) 66 } 67 queryDependencies[t] = deps 68 } 69 70 func registerQueryType( 71 qt queryv1.QueryType, 72 rt queryv1.ReportType, 73 q queryHandler, 74 a aggregatorProvider, 75 alwaysAggregate bool, // this option will always call the aggregate method for this report type, so it will also run when there is only one report 76 deps ...block.Section, 77 ) { 78 registerQueryReportType(qt, rt) 79 registerQueryHandler(qt, q) 80 registerQueryDependencies(qt, deps...) 81 registerAggregator(rt, a, alwaysAggregate) 82 } 83 84 type blockContext struct { 85 ctx context.Context 86 log log.Logger 87 req *request 88 agg *reportAggregator 89 obj *block.Object 90 grp *errgroup.Group 91 } 92 93 func (b *blockContext) execute() error { 94 var span opentracing.Span 95 span, b.ctx = opentracing.StartSpanFromContext(b.ctx, "blockContext.execute") 96 defer span.Finish() 97 98 if idx := b.datasetIndex(); idx != nil { 99 if err := b.lookupDatasets(idx); err != nil { 100 if b.obj.IsNotExists(err) { 101 level.Warn(b.log).Log("msg", "object not found", "err", err) 102 return nil 103 } 104 return fmt.Errorf("failed to lookup datasets: %w", err) 105 } 106 } 107 108 for _, ds := range b.obj.Metadata().Datasets { 109 q := b.newQueryContext(ds) 110 for _, query := range b.req.src.Query { 111 q.grp.Go(util.RecoverPanic(func() error { 112 return q.execute(query) 113 })) 114 } 115 if err := q.grp.Wait(); err != nil { 116 return err 117 } 118 } 119 120 return nil 121 } 122 123 // datasetIndex returns the dataset index if it is present in 124 // the metadata and the query needs to lookup datasets. 125 func (b *blockContext) datasetIndex() *metastorev1.Dataset { 126 md := b.obj.Metadata() 127 if len(md.Datasets) != 1 { 128 // The blocks metadata explicitly lists datasets to be queried. 129 return nil 130 } 131 ds := md.Datasets[0] 132 if block.DatasetFormat(ds.Format) != block.DatasetFormat1 { 133 return nil 134 } 135 136 // If the query only requires TSDB data, we can serve 137 // it using the dataset index. 138 s := (&queryContext{blockContext: b}).sections() 139 indexOnly := len(s) == 1 && s[0] == block.SectionTSDB 140 if indexOnly { 141 opentracing.SpanFromContext(b.ctx).SetTag("dataset_index_query_index_only", indexOnly) 142 return nil 143 } 144 145 return ds 146 } 147 148 func (b *blockContext) lookupDatasets(ds *metastorev1.Dataset) error { 149 opentracing.SpanFromContext(b.ctx).SetTag("dataset_index_query", true) 150 151 // As query execution has not started yet, 152 // we can safely open datasets. 153 idx := block.NewDataset(ds, b.obj) 154 defer func() { 155 _ = idx.Close() 156 }() 157 158 g, ctx := errgroup.WithContext(b.ctx) 159 var md *metastorev1.BlockMeta 160 g.Go(func() (err error) { 161 md, err = b.obj.ReadMetadata(ctx) 162 return err 163 }) 164 g.Go(func() error { 165 return idx.Open(ctx, block.SectionDatasetIndex) 166 }) 167 if err := g.Wait(); err != nil { 168 return err 169 } 170 171 datasetIDs, err := getSeriesIDs(idx.Index(), b.req.matchers...) 172 if err != nil { 173 return err 174 } 175 176 var j int 177 for i := range md.Datasets { 178 if _, ok := datasetIDs[uint32(i)]; ok { 179 md.Datasets[j] = md.Datasets[i] 180 j++ 181 } 182 } 183 md.Datasets = md.Datasets[:j] 184 b.obj.SetMetadata(md) 185 186 opentracing.SpanFromContext(b.ctx). 187 LogFields(otlog.String("msg", "dataset tsdb index lookup complete")) 188 189 return nil 190 } 191 192 func (b *blockContext) newQueryContext(ds *metastorev1.Dataset) *queryContext { 193 q := &queryContext{blockContext: b, ds: block.NewDataset(ds, b.obj)} 194 q.grp, q.ctx = errgroup.WithContext(b.ctx) 195 return q 196 } 197 198 type queryContext struct { 199 *blockContext 200 ctx context.Context 201 grp *errgroup.Group 202 ds *block.Dataset 203 } 204 205 func (q *queryContext) execute(query *queryv1.Query) error { 206 var span opentracing.Span 207 span, q.ctx = opentracing.StartSpanFromContext(q.ctx, "executeQuery."+strcase.ToCamel(query.QueryType.String())) 208 defer span.Finish() 209 handle, err := getQueryHandler(query.QueryType) 210 if err != nil { 211 return err 212 } 213 214 if err = q.ds.Open(q.ctx, q.sections()...); err != nil { 215 if q.obj.IsNotExists(err) { 216 level.Warn(q.log).Log("msg", "object not found", "err", err) 217 return nil 218 } 219 return fmt.Errorf("failed to initialize query context: %w", err) 220 } 221 defer func() { 222 _ = q.ds.CloseWithError(err) 223 }() 224 225 r, err := handle(q, query) 226 if err != nil { 227 return err 228 } 229 if r != nil { 230 r.ReportType = QueryReportType(query.QueryType) 231 return q.agg.aggregateReport(r) 232 } 233 234 return nil 235 } 236 237 func (q *queryContext) sections() []block.Section { 238 sections := make(map[block.Section]struct{}, 3) 239 for _, qt := range q.req.src.Query { 240 for _, s := range queryDependencies[qt.QueryType] { 241 sections[s] = struct{}{} 242 } 243 } 244 unique := make([]block.Section, 0, len(sections)) 245 for s := range sections { 246 unique = append(unique, s) 247 } 248 return unique 249 }