github.com/grafana/pyroscope@v1.18.0/pkg/phlaredb/querier.go (about)

     1  package phlaredb
     2  
     3  import (
     4  	"sort"
     5  
     6  	"github.com/prometheus/prometheus/model/labels"
     7  	"github.com/prometheus/prometheus/storage"
     8  
     9  	phlaremodel "github.com/grafana/pyroscope/pkg/model"
    10  	"github.com/grafana/pyroscope/pkg/phlaredb/tsdb"
    11  	"github.com/grafana/pyroscope/pkg/phlaredb/tsdb/index"
    12  )
    13  
    14  // IndexReader provides reading access of serialized index data.
    15  type IndexReader interface {
    16  	// Bounds returns the earliest and latest samples in the index
    17  	Bounds() (int64, int64)
    18  
    19  	Checksum() uint32
    20  
    21  	// Symbols return an iterator over sorted string symbols that may occur in
    22  	// series' labels and indices. It is not safe to use the returned strings
    23  	// beyond the lifetime of the index reader.
    24  	Symbols() index.StringIter
    25  
    26  	// SortedLabelValues returns sorted possible label values.
    27  	SortedLabelValues(name string, matchers ...*labels.Matcher) ([]string, error)
    28  
    29  	// LabelValues returns possible label values which may not be sorted.
    30  	LabelValues(name string, matchers ...*labels.Matcher) ([]string, error)
    31  
    32  	// Postings returns the postings list iterator for the label pairs.
    33  	// The Postings here contain the offsets to the series inside the index.
    34  	// Found IDs are not strictly required to point to a valid Series, e.g.
    35  	// during background garbage collections. Input values must be sorted.
    36  	Postings(name string, shard *index.ShardAnnotation, values ...string) (index.Postings, error)
    37  
    38  	// Series populates the given labels and chunk metas for the series identified
    39  	// by the reference.
    40  	// Returns storage.ErrNotFound if the ref does not resolve to a known series.
    41  	Series(ref storage.SeriesRef, lset *phlaremodel.Labels, chks *[]index.ChunkMeta) (uint64, error)
    42  	SeriesBy(ref storage.SeriesRef, lset *phlaremodel.Labels, chks *[]index.ChunkMeta, by ...string) (uint64, error)
    43  
    44  	// LabelNames returns all the unique label names present in the index in sorted order.
    45  	LabelNames(matchers ...*labels.Matcher) ([]string, error)
    46  
    47  	// LabelValueFor returns label value for the given label name in the series referred to by ID.
    48  	// If the series couldn't be found or the series doesn't have the requested label a
    49  	// storage.ErrNotFound is returned as error.
    50  	LabelValueFor(id storage.SeriesRef, label string) (string, error)
    51  
    52  	// LabelNamesFor returns all the label names for the series referred to by IDs.
    53  	// The names returned are sorted.
    54  	LabelNamesFor(ids ...storage.SeriesRef) ([]string, error)
    55  
    56  	// Close releases the underlying resources of the reader.
    57  	Close() error
    58  }
    59  
    60  // PostingsForMatchers assembles a single postings iterator against the index reader
    61  // based on the given matchers. The resulting postings are not ordered by series.
    62  func PostingsForMatchers(ix IndexReader, shard *index.ShardAnnotation, ms ...*labels.Matcher) (index.Postings, error) {
    63  	var its, notIts []index.Postings
    64  	// See which label must be non-empty.
    65  	// Optimization for case like {l=~".", l!="1"}.
    66  	labelMustBeSet := make(map[string]bool, len(ms))
    67  	for _, m := range ms {
    68  		if !m.Matches("") {
    69  			labelMustBeSet[m.Name] = true
    70  		}
    71  	}
    72  
    73  	for _, m := range ms {
    74  		if labelMustBeSet[m.Name] {
    75  			// If this matcher must be non-empty, we can be smarter.
    76  			matchesEmpty := m.Matches("")
    77  			isNot := m.Type == labels.MatchNotEqual || m.Type == labels.MatchNotRegexp
    78  			if isNot && matchesEmpty { // l!="foo"
    79  				// If the label can't be empty and is a Not and the inner matcher
    80  				// doesn't match empty, then subtract it out at the end.
    81  				inverse, err := m.Inverse()
    82  				if err != nil {
    83  					return nil, err
    84  				}
    85  
    86  				it, err := postingsForMatcher(ix, shard, inverse)
    87  				if err != nil {
    88  					return nil, err
    89  				}
    90  				notIts = append(notIts, it)
    91  			} else if isNot && !matchesEmpty { // l!=""
    92  				// If the label can't be empty and is a Not, but the inner matcher can
    93  				// be empty we need to use inversePostingsForMatcher.
    94  				inverse, err := m.Inverse()
    95  				if err != nil {
    96  					return nil, err
    97  				}
    98  
    99  				it, err := inversePostingsForMatcher(ix, shard, inverse)
   100  				if err != nil {
   101  					return nil, err
   102  				}
   103  				its = append(its, it)
   104  			} else { // l="a"
   105  				// Non-Not matcher, use normal postingsForMatcher.
   106  				it, err := postingsForMatcher(ix, shard, m)
   107  				if err != nil {
   108  					return nil, err
   109  				}
   110  				its = append(its, it)
   111  			}
   112  		} else { // l=""
   113  			// If the matchers for a labelname selects an empty value, it selects all
   114  			// the series which don't have the label name set too. See:
   115  			// https://github.com/prometheus/prometheus/issues/3575 and
   116  			// https://github.com/prometheus/prometheus/pull/3578#issuecomment-351653555
   117  			it, err := inversePostingsForMatcher(ix, shard, m)
   118  			if err != nil {
   119  				return nil, err
   120  			}
   121  			notIts = append(notIts, it)
   122  		}
   123  	}
   124  
   125  	// If there's nothing to subtract from, add in everything and remove the notIts later.
   126  	if len(its) == 0 && len(notIts) != 0 {
   127  		k, v := index.AllPostingsKey()
   128  		allPostings, err := ix.Postings(k, shard, v)
   129  		if err != nil {
   130  			return nil, err
   131  		}
   132  		its = append(its, allPostings)
   133  	}
   134  
   135  	it := index.Intersect(its...)
   136  
   137  	for _, n := range notIts {
   138  		it = index.Without(it, n)
   139  	}
   140  
   141  	return it, nil
   142  }
   143  
   144  func postingsForMatcher(ix IndexReader, shard *index.ShardAnnotation, m *labels.Matcher) (index.Postings, error) {
   145  	// This method will not return postings for missing labels.
   146  
   147  	// Fast-path for equal matching.
   148  	if m.Type == labels.MatchEqual {
   149  		return ix.Postings(m.Name, shard, m.Value)
   150  	}
   151  
   152  	// Fast-path for set matching.
   153  	if m.Type == labels.MatchRegexp {
   154  		setMatches := tsdb.FindSetMatches(m.GetRegexString())
   155  		if len(setMatches) > 0 {
   156  			sort.Strings(setMatches)
   157  			return ix.Postings(m.Name, shard, setMatches...)
   158  		}
   159  	}
   160  
   161  	vals, err := ix.LabelValues(m.Name)
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  
   166  	var res []string
   167  	lastVal, isSorted := "", true
   168  	for _, val := range vals {
   169  		if m.Matches(val) {
   170  			res = append(res, val)
   171  			if isSorted && val < lastVal {
   172  				isSorted = false
   173  			}
   174  			lastVal = val
   175  		}
   176  	}
   177  
   178  	if len(res) == 0 {
   179  		return index.EmptyPostings(), nil
   180  	}
   181  
   182  	if !isSorted {
   183  		sort.Strings(res)
   184  	}
   185  	return ix.Postings(m.Name, shard, res...)
   186  }
   187  
   188  // inversePostingsForMatcher returns the postings for the series with the label name set but not matching the matcher.
   189  func inversePostingsForMatcher(ix IndexReader, shard *index.ShardAnnotation, m *labels.Matcher) (index.Postings, error) {
   190  	vals, err := ix.LabelValues(m.Name)
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  
   195  	var res []string
   196  	lastVal, isSorted := "", true
   197  	for _, val := range vals {
   198  		if !m.Matches(val) {
   199  			res = append(res, val)
   200  			if isSorted && val < lastVal {
   201  				isSorted = false
   202  			}
   203  			lastVal = val
   204  		}
   205  	}
   206  
   207  	if !isSorted {
   208  		sort.Strings(res)
   209  	}
   210  	return ix.Postings(m.Name, shard, res...)
   211  }