Like Prometheus, but for logs.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
loki/pkg/bloomgateway/util.go

175 lines
4.5 KiB

package bloomgateway
import (
"sort"
"github.com/prometheus/common/model"
"golang.org/x/exp/slices"
"github.com/grafana/loki/v3/pkg/logproto"
v1 "github.com/grafana/loki/v3/pkg/storage/bloom/v1"
"github.com/grafana/loki/v3/pkg/storage/config"
"github.com/grafana/loki/v3/pkg/storage/stores/shipper/bloomshipper"
)
func truncateDay(ts model.Time) model.Time {
return model.TimeFromUnix(ts.Time().Truncate(Day).Unix())
}
// daysForRange returns a list of model.Time truncated to the start of each day
// for the inclusive range [from, through]
func daysForRange(from, through model.Time) []model.Time {
fromDay, throughDay := truncateDay(from), truncateDay(through)
// Trim the last day if it's the same as the through time,
// but preserve at least 1 day
if throughDay.Equal(through) && !fromDay.Equal(throughDay) {
throughDay = throughDay.Add(-Day)
}
days := make([]model.Time, 0, int(throughDay.Sub(fromDay)/Day)+1)
for day := fromDay; !day.After(throughDay); day = day.Add(Day) {
days = append(days, day)
}
return days
}
// getFromThrough assumes a list of ShortRefs sorted by From time
func getFromThrough(refs []*logproto.ShortRef) (model.Time, model.Time) {
if len(refs) == 0 {
return model.Earliest, model.Latest
}
if len(refs) == 1 {
return refs[0].From, refs[0].Through
}
maxItem := slices.MaxFunc(refs, func(a, b *logproto.ShortRef) int {
return int(a.Through) - int(b.Through)
})
return refs[0].From, maxItem.Through
}
// convertToChunkRefs converts a []*logproto.ShortRef into v1.ChunkRefs
func convertToChunkRefs(refs []*logproto.ShortRef) v1.ChunkRefs {
result := make(v1.ChunkRefs, 0, len(refs))
for i := range refs {
result = append(result, v1.ChunkRef(*refs[i]))
}
return result
}
type blockWithTasks struct {
ref bloomshipper.BlockRef
tasks []Task
}
func partitionTasksByBlock(tasks []Task, blocks []bloomshipper.BlockRef) []blockWithTasks {
result := make([]blockWithTasks, 0, len(blocks))
for _, block := range blocks {
bounded := blockWithTasks{
ref: block,
}
for _, task := range tasks {
refs := task.series
min := sort.Search(len(refs), func(i int) bool {
return block.Cmp(refs[i].Fingerprint) > v1.Before
})
max := sort.Search(len(refs), func(i int) bool {
return block.Cmp(refs[i].Fingerprint) == v1.After
})
// All fingerprints fall outside of the consumer's range
if min == len(refs) || max == 0 || min == max {
continue
}
bounded.tasks = append(bounded.tasks, task.Copy(refs[min:max]))
}
if len(bounded.tasks) > 0 {
result = append(result, bounded)
}
}
return result
}
type seriesWithInterval struct {
day config.DayTime
series []*logproto.GroupedChunkRefs
interval bloomshipper.Interval
}
func partitionRequest(req *logproto.FilterChunkRefRequest) []seriesWithInterval {
return partitionSeriesByDay(req.From, req.Through, req.Refs)
}
func partitionSeriesByDay(from, through model.Time, seriesWithChunks []*logproto.GroupedChunkRefs) []seriesWithInterval {
result := make([]seriesWithInterval, 0)
for _, day := range daysForRange(from, through) {
minTs, maxTs := model.Latest, model.Earliest
res := make([]*logproto.GroupedChunkRefs, 0, len(seriesWithChunks))
for _, series := range seriesWithChunks {
chunks := series.Refs
var relevantChunks []*logproto.ShortRef
minTs, maxTs, relevantChunks = overlappingChunks(day, day.Add(Day), minTs, maxTs, chunks)
if len(relevantChunks) == 0 {
continue
}
res = append(res, &logproto.GroupedChunkRefs{
Fingerprint: series.Fingerprint,
Tenant: series.Tenant,
Refs: relevantChunks,
})
}
if len(res) > 0 {
result = append(result, seriesWithInterval{
interval: bloomshipper.Interval{
Start: minTs,
End: maxTs,
},
day: config.NewDayTime(day),
series: res,
})
}
}
return result
}
func overlappingChunks(from, through, minTs, maxTs model.Time, chunks []*logproto.ShortRef) (model.Time, model.Time, []*logproto.ShortRef) {
// chunks are ordered first by `From`. Can disregard all chunks
// that start later than the search range ends
maxIdx := sort.Search(len(chunks), func(i int) bool {
return chunks[i].From > through
})
res := make([]*logproto.ShortRef, 0, len(chunks[:maxIdx]))
for _, chunk := range chunks[:maxIdx] {
// if chunk ends before the search range starts, skip
if from.After(chunk.Through) {
continue
}
// Bound min & max ranges to the search range
minTs = max(min(minTs, chunk.From), from)
maxTs = min(max(maxTs, chunk.Through), through)
res = append(res, chunk)
}
return minTs, maxTs, res
}