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  }