github.com/grafana/pyroscope@v1.18.0/pkg/metastore/index/query.go (about)

     1  package index
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"slices"
     7  	"sort"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/prometheus/prometheus/model/labels"
    12  	"github.com/prometheus/prometheus/promql/parser"
    13  	"go.etcd.io/bbolt"
    14  
    15  	metastorev1 "github.com/grafana/pyroscope/api/gen/proto/go/metastore/v1"
    16  	"github.com/grafana/pyroscope/pkg/block/metadata"
    17  	"github.com/grafana/pyroscope/pkg/metastore/index/store"
    18  )
    19  
    20  type InvalidQueryError struct {
    21  	Query MetadataQuery
    22  	Err   error
    23  }
    24  
    25  func (e *InvalidQueryError) Error() string {
    26  	return fmt.Sprintf("invalid query: %v: %v", e.Query, e.Err)
    27  }
    28  
    29  type MetadataQuery struct {
    30  	Expr      string
    31  	StartTime time.Time
    32  	EndTime   time.Time
    33  	Tenant    []string
    34  	Labels    []string
    35  }
    36  
    37  func (q *MetadataQuery) String() string {
    38  	return fmt.Sprintf("start: %v, end: %v, tenants: %v, expr: %v",
    39  		q.StartTime,
    40  		q.EndTime,
    41  		strings.Join(q.Tenant, ","),
    42  		q.Expr)
    43  }
    44  
    45  type metadataQuery struct {
    46  	startTime time.Time
    47  	endTime   time.Time
    48  	tenants   []string
    49  	tenantMap map[string]struct{}
    50  	matchers  []*labels.Matcher
    51  	labels    []string
    52  	index     *Index
    53  }
    54  
    55  func newMetadataQuery(index *Index, query MetadataQuery) (*metadataQuery, error) {
    56  	if len(query.Tenant) == 0 {
    57  		return nil, &InvalidQueryError{Query: query, Err: fmt.Errorf("tenant_id is required")}
    58  	}
    59  	matchers, err := parser.ParseMetricSelector(query.Expr)
    60  	if err != nil {
    61  		return nil, &InvalidQueryError{Query: query, Err: fmt.Errorf("failed to parse label matcher: %w", err)}
    62  	}
    63  	q := &metadataQuery{
    64  		startTime: query.StartTime,
    65  		endTime:   query.EndTime,
    66  		index:     index,
    67  		matchers:  matchers,
    68  		labels:    query.Labels,
    69  	}
    70  	q.buildTenantMap(query.Tenant)
    71  	return q, nil
    72  }
    73  
    74  func (q *metadataQuery) buildTenantMap(tenants []string) {
    75  	q.tenantMap = make(map[string]struct{}, len(tenants)+1)
    76  	for _, t := range tenants {
    77  		q.tenantMap[t] = struct{}{}
    78  	}
    79  	// Always query the anonymous blocks: tenant datasets will be filtered out later.
    80  	q.tenantMap[""] = struct{}{}
    81  	q.tenants = make([]string, 0, len(q.tenantMap))
    82  	for t := range q.tenantMap {
    83  		q.tenants = append(q.tenants, t)
    84  	}
    85  	sort.Strings(q.tenants)
    86  }
    87  
    88  func (q *metadataQuery) overlaps(start, end time.Time) bool {
    89  	if q.startTime.After(end) {
    90  		return false
    91  	}
    92  	if q.endTime.Before(start) {
    93  		return false
    94  	}
    95  	return true
    96  }
    97  
    98  func (q *metadataQuery) overlapsUnixMilli(start, end int64) bool {
    99  	return q.overlaps(time.UnixMilli(start), time.UnixMilli(end))
   100  }
   101  
   102  func newBlockMetadataQuerier(tx *bbolt.Tx, q *metadataQuery) *blockMetadataQuerier {
   103  	return &blockMetadataQuerier{
   104  		query:  q,
   105  		shards: newShardIterator(tx, q.index, q.startTime, q.endTime, q.tenants...),
   106  		metas:  make([]*metastorev1.BlockMeta, 0, 256),
   107  	}
   108  }
   109  
   110  type blockMetadataQuerier struct {
   111  	query  *metadataQuery
   112  	shards *shardIterator
   113  	metas  []*metastorev1.BlockMeta
   114  }
   115  
   116  func (q *blockMetadataQuerier) queryBlocks(ctx context.Context) ([]*metastorev1.BlockMeta, error) {
   117  	for q.shards.Next() && ctx.Err() == nil {
   118  		shard := q.shards.At()
   119  		offset := len(q.metas)
   120  		if err := q.collectBlockMetadata(shard); err != nil {
   121  			return nil, err
   122  		}
   123  		slices.SortFunc(q.metas[offset:], func(a, b *metastorev1.BlockMeta) int {
   124  			return strings.Compare(a.Id, b.Id)
   125  		})
   126  	}
   127  	if err := ctx.Err(); err != nil {
   128  		return nil, err
   129  	}
   130  	return q.metas, q.shards.Err()
   131  }
   132  
   133  func (q *blockMetadataQuerier) collectBlockMetadata(s *store.Shard) error {
   134  	matcher := metadata.NewLabelMatcher(s.StringTable.Strings, q.query.matchers, q.query.labels...)
   135  	if !matcher.IsValid() {
   136  		return nil
   137  	}
   138  	blocks := s.Blocks(q.shards.tx)
   139  	if blocks == nil {
   140  		return nil
   141  	}
   142  	for blocks.Next() {
   143  		md := q.shards.index.blocks.getOrCreate(s, blocks.At())
   144  		if m := q.collectMatched(s.StringTable, matcher, md); m != nil {
   145  			q.metas = append(q.metas, m)
   146  		}
   147  	}
   148  	return nil
   149  }
   150  
   151  func (q *blockMetadataQuerier) collectMatched(
   152  	s *metadata.StringTable,
   153  	m *metadata.LabelMatcher,
   154  	md *metastorev1.BlockMeta,
   155  ) *metastorev1.BlockMeta {
   156  	if !q.query.overlapsUnixMilli(md.MinTime, md.MaxTime) {
   157  		return nil
   158  	}
   159  	var mdCopy *metastorev1.BlockMeta
   160  	var ok bool
   161  	matches := make([]int32, 0, 8)
   162  	for _, ds := range md.Datasets {
   163  		if _, ok := q.query.tenantMap[s.Lookup(ds.Tenant)]; !ok {
   164  			continue
   165  		}
   166  		if !q.query.overlapsUnixMilli(ds.MinTime, ds.MaxTime) {
   167  			continue
   168  		}
   169  		matches = matches[:0]
   170  		if matches, ok = m.CollectMatches(matches, ds.Labels); ok {
   171  			if mdCopy == nil {
   172  				mdCopy = cloneBlockMetadataForQuery(md)
   173  			}
   174  			dsCopy := cloneDatasetMetadataForQuery(ds)
   175  			if len(matches) > 0 {
   176  				dsCopy.Labels = make([]int32, len(matches))
   177  				copy(dsCopy.Labels, matches)
   178  			}
   179  			mdCopy.Datasets = append(mdCopy.Datasets, dsCopy)
   180  		}
   181  	}
   182  	if mdCopy != nil {
   183  		s.Export(mdCopy)
   184  	}
   185  	// May be nil.
   186  	return mdCopy
   187  }
   188  
   189  func cloneBlockMetadataForQuery(b *metastorev1.BlockMeta) *metastorev1.BlockMeta {
   190  	return &metastorev1.BlockMeta{
   191  		FormatVersion:   b.FormatVersion,
   192  		Id:              b.Id,
   193  		Tenant:          b.Tenant,
   194  		Shard:           b.Shard,
   195  		CompactionLevel: b.CompactionLevel,
   196  		MinTime:         b.MinTime,
   197  		MaxTime:         b.MaxTime,
   198  		CreatedBy:       b.CreatedBy,
   199  		MetadataOffset:  b.MetadataOffset,
   200  		Size:            b.Size,
   201  		//	Datasets:        b.Datasets,
   202  		//	StringTable:     b.StringTable,
   203  	}
   204  }
   205  
   206  func cloneDatasetMetadataForQuery(ds *metastorev1.Dataset) *metastorev1.Dataset {
   207  	return &metastorev1.Dataset{
   208  		Format:          ds.Format,
   209  		Tenant:          ds.Tenant,
   210  		Name:            ds.Name,
   211  		MinTime:         ds.MinTime,
   212  		MaxTime:         ds.MaxTime,
   213  		TableOfContents: ds.TableOfContents,
   214  		Size:            ds.Size,
   215  		//	Labels:          ds.Labels,
   216  	}
   217  }
   218  
   219  func newMetadataLabelQuerier(tx *bbolt.Tx, q *metadataQuery) *metadataLabelQuerier {
   220  	return &metadataLabelQuerier{
   221  		query:  q,
   222  		shards: newShardIterator(tx, q.index, q.startTime, q.endTime, q.tenants...),
   223  		labels: metadata.NewLabelsCollector(q.labels...),
   224  	}
   225  }
   226  
   227  type metadataLabelQuerier struct {
   228  	query  *metadataQuery
   229  	shards *shardIterator
   230  	labels *metadata.LabelsCollector
   231  }
   232  
   233  func (q *metadataLabelQuerier) queryLabels(ctx context.Context) (*metadata.LabelsCollector, error) {
   234  	if len(q.query.labels) == 0 {
   235  		return q.labels, nil
   236  	}
   237  	for q.shards.Next() && ctx.Err() == nil {
   238  		shard := q.shards.At()
   239  		if err := q.collectLabels(shard); err != nil {
   240  			return nil, err
   241  		}
   242  	}
   243  	if err := ctx.Err(); err != nil {
   244  		return nil, err
   245  	}
   246  	return q.labels, q.shards.Err()
   247  }
   248  
   249  func (q *metadataLabelQuerier) collectLabels(s *store.Shard) error {
   250  	matcher := metadata.NewLabelMatcher(s.StringTable.Strings, q.query.matchers, q.query.labels...)
   251  	if !matcher.IsValid() {
   252  		return nil
   253  	}
   254  	blocks := s.Blocks(q.shards.tx)
   255  	if blocks == nil {
   256  		return nil
   257  	}
   258  	for blocks.Next() {
   259  		md := q.shards.index.blocks.getOrCreate(s, blocks.At())
   260  		if q.query.overlapsUnixMilli(md.MinTime, md.MaxTime) {
   261  			for _, ds := range md.Datasets {
   262  				if _, ok := q.query.tenantMap[s.StringTable.Lookup(ds.Tenant)]; !ok {
   263  					continue
   264  				}
   265  				if q.query.overlapsUnixMilli(ds.MinTime, ds.MaxTime) {
   266  					matcher.Matches(ds.Labels)
   267  				}
   268  			}
   269  		}
   270  	}
   271  	q.labels.CollectMatches(matcher)
   272  	return nil
   273  }
   274  
   275  type shardIterator struct {
   276  	tx        *bbolt.Tx
   277  	index     *Index
   278  	tenants   []string
   279  	shards    []store.Shard
   280  	cur       *store.Shard
   281  	err       error
   282  	startTime time.Time
   283  	endTime   time.Time
   284  }
   285  
   286  func newShardIterator(tx *bbolt.Tx, index *Index, startTime, endTime time.Time, tenants ...string) *shardIterator {
   287  	// See comment in DefaultConfig.queryLookaroundPeriod.
   288  	startTime = startTime.Add(-index.config.queryLookaroundPeriod)
   289  	endTime = endTime.Add(index.config.queryLookaroundPeriod)
   290  	si := shardIterator{
   291  		tx:        tx,
   292  		tenants:   tenants,
   293  		index:     index,
   294  		startTime: startTime,
   295  		endTime:   endTime,
   296  	}
   297  	for p := range index.store.Partitions(tx) {
   298  		if !p.Overlaps(startTime, endTime) {
   299  			continue
   300  		}
   301  		q := p.Query(tx)
   302  		if q == nil {
   303  			continue
   304  		}
   305  		for _, t := range si.tenants {
   306  			for s := range q.Shards(t) {
   307  				if s.ShardIndex.Overlaps(si.startTime, si.endTime) {
   308  					si.shards = append(si.shards, s)
   309  				}
   310  			}
   311  		}
   312  	}
   313  	slices.SortFunc(si.shards, compareShards)
   314  	si.shards = slices.Compact(si.shards)
   315  	return &si
   316  }
   317  
   318  func (si *shardIterator) Err() error { return si.err }
   319  
   320  func (si *shardIterator) At() *store.Shard { return si.cur }
   321  
   322  func (si *shardIterator) Next() bool {
   323  	if si.err != nil || len(si.shards) == 0 {
   324  		return false
   325  	}
   326  	c := si.shards[0]
   327  	si.shards = si.shards[1:]
   328  	si.cur, si.err = si.index.shards.getForRead(si.tx, c.Partition, c.Tenant, c.Shard)
   329  	return si.err == nil
   330  }
   331  
   332  func compareShards(a, b store.Shard) int {
   333  	cmp := strings.Compare(a.Tenant, b.Tenant)
   334  	if cmp == 0 {
   335  		return int(a.Shard) - int(b.Shard)
   336  	}
   337  	return cmp
   338  }