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 }