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 }