|
|
|
@ -51,6 +51,9 @@ const ( |
|
|
|
|
FormatV1 = 1 |
|
|
|
|
// FormatV2 represents 2 version of index.
|
|
|
|
|
FormatV2 = 2 |
|
|
|
|
// FormatV3 represents 3 version of index. It adds support for
|
|
|
|
|
// paging through batches of chunks within a series
|
|
|
|
|
FormatV3 = 3 |
|
|
|
|
|
|
|
|
|
IndexFilename = "index" |
|
|
|
|
|
|
|
|
@ -58,6 +61,9 @@ const ( |
|
|
|
|
fingerprintInterval = 1 << 10 |
|
|
|
|
|
|
|
|
|
millisecondsInHour = int64(time.Hour / time.Millisecond) |
|
|
|
|
|
|
|
|
|
// The format that will be written by this process
|
|
|
|
|
LiveFormat = FormatV2 |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
type indexWriterStage uint8 |
|
|
|
@ -209,8 +215,7 @@ func NewTOCFromByteSlice(bs ByteSlice) (*TOC, error) { |
|
|
|
|
}, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// NewWriter returns a new Writer to the given filename. It serializes data in format version 2.
|
|
|
|
|
func NewWriter(ctx context.Context, fn string) (*Writer, error) { |
|
|
|
|
func NewWriterWithVersion(ctx context.Context, version int, fn string) (*Writer, error) { |
|
|
|
|
dir := filepath.Dir(fn) |
|
|
|
|
|
|
|
|
|
df, err := fileutil.OpenDir(dir) |
|
|
|
@ -243,11 +248,12 @@ func NewWriter(ctx context.Context, fn string) (*Writer, error) { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
iw := &Writer{ |
|
|
|
|
ctx: ctx, |
|
|
|
|
f: f, |
|
|
|
|
fP: fP, |
|
|
|
|
fPO: fPO, |
|
|
|
|
stage: idxStageNone, |
|
|
|
|
Version: version, |
|
|
|
|
ctx: ctx, |
|
|
|
|
f: f, |
|
|
|
|
fP: fP, |
|
|
|
|
fPO: fPO, |
|
|
|
|
stage: idxStageNone, |
|
|
|
|
|
|
|
|
|
// Reusable memory.
|
|
|
|
|
buf1: encoding.EncWrap(tsdb_enc.Encbuf{B: make([]byte, 0, 1<<22)}), |
|
|
|
@ -263,6 +269,11 @@ func NewWriter(ctx context.Context, fn string) (*Writer, error) { |
|
|
|
|
return iw, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// NewWriter returns a new Writer to the given filename. It serializes data according to the `LiveFormat` version
|
|
|
|
|
func NewWriter(ctx context.Context, fn string) (*Writer, error) { |
|
|
|
|
return NewWriterWithVersion(ctx, LiveFormat, fn) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (w *Writer) write(bufs ...[]byte) error { |
|
|
|
|
return w.f.Write(bufs...) |
|
|
|
|
} |
|
|
|
@ -435,7 +446,7 @@ func (w *Writer) ensureStage(s indexWriterStage) error { |
|
|
|
|
func (w *Writer) writeMeta() error { |
|
|
|
|
w.buf1.Reset() |
|
|
|
|
w.buf1.PutBE32(MagicIndex) |
|
|
|
|
w.buf1.PutByte(FormatV2) |
|
|
|
|
w.buf1.PutByte(byte(w.Version)) |
|
|
|
|
|
|
|
|
|
return w.write(w.buf1.Get()) |
|
|
|
|
} |
|
|
|
@ -507,54 +518,135 @@ func (w *Writer) AddSeries(ref storage.SeriesRef, lset labels.Labels, fp model.F |
|
|
|
|
w.buf2.PutUvarint32(valueIndex) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
w.buf2.PutUvarint(len(chunks)) |
|
|
|
|
w.addChunks(chunks, &w.buf2, &w.buf1, ChunkPageSize) |
|
|
|
|
|
|
|
|
|
w.buf1.Reset() |
|
|
|
|
w.buf1.PutUvarint(w.buf2.Len()) |
|
|
|
|
|
|
|
|
|
w.buf2.PutHash(w.crc32) |
|
|
|
|
|
|
|
|
|
w.lastSeries = append(w.lastSeries[:0], lset...) |
|
|
|
|
w.lastSeriesHash = labelHash |
|
|
|
|
w.lastRef = ref |
|
|
|
|
|
|
|
|
|
if ref%fingerprintInterval == 0 { |
|
|
|
|
// series references are the 16-byte aligned offsets
|
|
|
|
|
// Do NOT ask me how long I debugged this particular bit >:O
|
|
|
|
|
sRef := w.f.pos / 16 |
|
|
|
|
w.fingerprintOffsets = append(w.fingerprintOffsets, [2]uint64{sRef, labelHash}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if err := w.write(w.buf1.Get(), w.buf2.Get()); err != nil { |
|
|
|
|
return errors.Wrap(err, "write series data") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (w *Writer) addChunks(chunks []ChunkMeta, primary, scratch *encoding.Encbuf, pageSize int) { |
|
|
|
|
if w.Version > FormatV2 { |
|
|
|
|
w.addChunksV3(chunks, primary, scratch, pageSize) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
w.addChunksPriorV3(chunks, primary, scratch) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (w *Writer) addChunksPriorV3(chunks []ChunkMeta, primary, _ *encoding.Encbuf) { |
|
|
|
|
primary.PutUvarint(len(chunks)) |
|
|
|
|
|
|
|
|
|
if len(chunks) > 0 { |
|
|
|
|
c := chunks[0] |
|
|
|
|
w.toc.Metadata.EnsureBounds(c.MinTime, c.MaxTime) |
|
|
|
|
|
|
|
|
|
w.buf2.PutVarint64(c.MinTime) |
|
|
|
|
w.buf2.PutUvarint64(uint64(c.MaxTime - c.MinTime)) |
|
|
|
|
w.buf2.PutUvarint32(c.KB) |
|
|
|
|
w.buf2.PutUvarint32(c.Entries) |
|
|
|
|
w.buf2.PutBE32(c.Checksum) |
|
|
|
|
primary.PutVarint64(c.MinTime) |
|
|
|
|
primary.PutUvarint64(uint64(c.MaxTime - c.MinTime)) |
|
|
|
|
primary.PutUvarint32(c.KB) |
|
|
|
|
primary.PutUvarint32(c.Entries) |
|
|
|
|
primary.PutBE32(c.Checksum) |
|
|
|
|
t0 := c.MaxTime |
|
|
|
|
|
|
|
|
|
for _, c := range chunks[1:] { |
|
|
|
|
w.toc.Metadata.EnsureBounds(c.MinTime, c.MaxTime) |
|
|
|
|
// Encode the diff against previous chunk as varint
|
|
|
|
|
// instead of uvarint because chunks may overlap
|
|
|
|
|
w.buf2.PutVarint64(c.MinTime - t0) |
|
|
|
|
w.buf2.PutUvarint64(uint64(c.MaxTime - c.MinTime)) |
|
|
|
|
w.buf2.PutUvarint32(c.KB) |
|
|
|
|
w.buf2.PutUvarint32(c.Entries) |
|
|
|
|
primary.PutVarint64(c.MinTime - t0) |
|
|
|
|
primary.PutUvarint64(uint64(c.MaxTime - c.MinTime)) |
|
|
|
|
primary.PutUvarint32(c.KB) |
|
|
|
|
primary.PutUvarint32(c.Entries) |
|
|
|
|
t0 = c.MaxTime |
|
|
|
|
|
|
|
|
|
w.buf2.PutBE32(c.Checksum) |
|
|
|
|
primary.PutBE32(c.Checksum) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
w.buf1.Reset() |
|
|
|
|
w.buf1.PutUvarint(w.buf2.Len()) |
|
|
|
|
func (w *Writer) addChunksV3(chunks []ChunkMeta, primary, scratch *encoding.Encbuf, chunkPageSize int) { |
|
|
|
|
scratch.Reset() |
|
|
|
|
|
|
|
|
|
w.buf2.PutHash(w.crc32) |
|
|
|
|
primary.PutUvarint(len(chunks)) |
|
|
|
|
// placeholder for how long the markers section is so it can be skipped when there are few chunks present
|
|
|
|
|
markersLnOffset := primary.Len() |
|
|
|
|
primary.PutBE32(0) |
|
|
|
|
|
|
|
|
|
w.lastSeries = append(w.lastSeries[:0], lset...) |
|
|
|
|
w.lastSeriesHash = labelHash |
|
|
|
|
w.lastRef = ref |
|
|
|
|
markersStart := primary.Len() |
|
|
|
|
|
|
|
|
|
if ref%fingerprintInterval == 0 { |
|
|
|
|
// series references are the 16-byte aligned offsets
|
|
|
|
|
// Do NOT ask me how long I debugged this particular bit >:O
|
|
|
|
|
sRef := w.f.pos / 16 |
|
|
|
|
w.fingerprintOffsets = append(w.fingerprintOffsets, [2]uint64{sRef, labelHash}) |
|
|
|
|
nMarkers := len(chunks) / chunkPageSize |
|
|
|
|
if len(chunks)%chunkPageSize != 0 { |
|
|
|
|
nMarkers++ |
|
|
|
|
} |
|
|
|
|
primary.PutUvarint(nMarkers) |
|
|
|
|
|
|
|
|
|
chunksStart := scratch.Len() |
|
|
|
|
markerOffset := 0 // start of the current marker page
|
|
|
|
|
|
|
|
|
|
if len(chunks) > 0 { |
|
|
|
|
var t0 int64 |
|
|
|
|
var pageMarker chunkPageMarker |
|
|
|
|
|
|
|
|
|
for i, c := range chunks { |
|
|
|
|
pageMarker.combine(c) |
|
|
|
|
|
|
|
|
|
w.toc.Metadata.EnsureBounds(c.MinTime, c.MaxTime) |
|
|
|
|
// Encode the diff against previous chunk as varint
|
|
|
|
|
// instead of uvarint because chunks may overlap
|
|
|
|
|
scratch.PutVarint64(c.MinTime - t0) |
|
|
|
|
scratch.PutUvarint64(uint64(c.MaxTime - c.MinTime)) |
|
|
|
|
scratch.PutUvarint32(c.KB) |
|
|
|
|
scratch.PutUvarint32(c.Entries) |
|
|
|
|
t0 = c.MaxTime |
|
|
|
|
|
|
|
|
|
scratch.PutBE32(c.Checksum) |
|
|
|
|
|
|
|
|
|
// test if this is the last chunk in the page
|
|
|
|
|
if i%chunkPageSize == chunkPageSize-1 { |
|
|
|
|
pageMarker.encode(primary, markerOffset, chunkPageSize) |
|
|
|
|
pageMarker.clear() |
|
|
|
|
markerOffset = scratch.Len() - chunksStart |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if rem := len(chunks) % chunkPageSize; rem != 0 { |
|
|
|
|
// write partial page at the end
|
|
|
|
|
pageMarker.encode(primary, markerOffset, rem) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// now that we're done, we have two buffers:
|
|
|
|
|
// 1. scratch: the actual chunk data
|
|
|
|
|
// 2. primary: the chunk page markers
|
|
|
|
|
// so it's time to combine them
|
|
|
|
|
|
|
|
|
|
// first, write the length of the markers section
|
|
|
|
|
markersLn := primary.Len() - markersStart |
|
|
|
|
diff := markersLnOffset - primary.Len() |
|
|
|
|
primary.Skip(diff) |
|
|
|
|
primary.PutBE32(uint32(markersLn)) |
|
|
|
|
// -4 for the length of the u32 field we just wrote
|
|
|
|
|
primary.Skip(-diff - 4) |
|
|
|
|
|
|
|
|
|
if err := w.write(w.buf1.Get(), w.buf2.Get()); err != nil { |
|
|
|
|
return errors.Wrap(err, "write series data") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
primary.PutBytes(scratch.Get()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (w *Writer) startSymbols() error { |
|
|
|
@ -616,7 +708,7 @@ func (w *Writer) finishSymbols() error { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Load in the symbol table efficiently for the rest of the index writing.
|
|
|
|
|
w.symbols, err = NewSymbols(RealByteSlice(w.symbolFile.Bytes()), FormatV2, int(w.toc.Symbols)) |
|
|
|
|
w.symbols, err = NewSymbols(RealByteSlice(w.symbolFile.Bytes()), w.Version, int(w.toc.Symbols)) |
|
|
|
|
if err != nil { |
|
|
|
|
return errors.Wrap(err, "read symbols") |
|
|
|
|
} |
|
|
|
@ -1240,7 +1332,7 @@ func newReader(b ByteSlice, c io.Closer) (*Reader, error) { |
|
|
|
|
} |
|
|
|
|
r.version = int(r.b.Range(4, 5)[0]) |
|
|
|
|
|
|
|
|
|
if r.version != FormatV1 && r.version != FormatV2 { |
|
|
|
|
if r.version != FormatV1 && r.version != FormatV2 && r.version != FormatV3 { |
|
|
|
|
return nil, errors.Errorf("unknown index file version %d", r.version) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -1332,10 +1424,7 @@ func newReader(b ByteSlice, c io.Closer) (*Reader, error) { |
|
|
|
|
return nil, errors.Wrap(err, "loading fingerprint offsets") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
r.dec = &Decoder{ |
|
|
|
|
LookupSymbol: r.lookupSymbol, |
|
|
|
|
chunksSample: map[storage.SeriesRef]*chunkSamples{}, |
|
|
|
|
} |
|
|
|
|
r.dec = newDecoder(r.lookupSymbol, DefaultMaxChunksToBypassMarkerLookup) |
|
|
|
|
|
|
|
|
|
return r, nil |
|
|
|
|
} |
|
|
|
@ -1420,7 +1509,7 @@ func (s Symbols) Lookup(o uint32) (string, error) { |
|
|
|
|
B: s.bs.Range(0, s.bs.Len()), |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
if s.version == FormatV2 { |
|
|
|
|
if s.version >= FormatV2 { |
|
|
|
|
if int(o) >= s.seen { |
|
|
|
|
return "", errors.Errorf("unknown symbol offset %d", o) |
|
|
|
|
} |
|
|
|
@ -1476,7 +1565,7 @@ func (s Symbols) ReverseLookup(sym string) (uint32, error) { |
|
|
|
|
if lastSymbol != sym { |
|
|
|
|
return 0, errors.Errorf("unknown symbol %q", sym) |
|
|
|
|
} |
|
|
|
|
if s.version == FormatV2 { |
|
|
|
|
if s.version >= FormatV2 { |
|
|
|
|
return uint32(res), nil |
|
|
|
|
} |
|
|
|
|
return uint32(s.bs.Len() - lastLen), nil |
|
|
|
@ -1666,9 +1755,9 @@ func (r *Reader) LabelNamesFor(ids ...storage.SeriesRef) ([]string, error) { |
|
|
|
|
offsetsMap := make(map[uint32]struct{}) |
|
|
|
|
for _, id := range ids { |
|
|
|
|
offset := id |
|
|
|
|
// In version 2 series IDs are no longer exact references but series are 16-byte padded
|
|
|
|
|
// In version 2+ series IDs are no longer exact references but series are 16-byte padded
|
|
|
|
|
// and the ID is the multiple of 16 of the actual position.
|
|
|
|
|
if r.version == FormatV2 { |
|
|
|
|
if r.version >= FormatV2 { |
|
|
|
|
offset = id * 16 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -1705,9 +1794,9 @@ func (r *Reader) LabelNamesFor(ids ...storage.SeriesRef) ([]string, error) { |
|
|
|
|
// LabelValueFor returns label value for the given label name in the series referred to by ID.
|
|
|
|
|
func (r *Reader) LabelValueFor(id storage.SeriesRef, label string) (string, error) { |
|
|
|
|
offset := id |
|
|
|
|
// In version 2 series IDs are no longer exact references but series are 16-byte padded
|
|
|
|
|
// In version 2+ series IDs are no longer exact references but series are 16-byte padded
|
|
|
|
|
// and the ID is the multiple of 16 of the actual position.
|
|
|
|
|
if r.version == FormatV2 { |
|
|
|
|
if r.version >= FormatV2 { |
|
|
|
|
offset = id * 16 |
|
|
|
|
} |
|
|
|
|
d := encoding.DecWrap(tsdb_enc.NewDecbufUvarintAt(r.b, int(offset), castagnoliTable)) |
|
|
|
@ -1731,9 +1820,9 @@ func (r *Reader) LabelValueFor(id storage.SeriesRef, label string) (string, erro |
|
|
|
|
// Series reads the series with the given ID and writes its labels and chunks into lbls and chks.
|
|
|
|
|
func (r *Reader) Series(id storage.SeriesRef, from int64, through int64, lbls *labels.Labels, chks *[]ChunkMeta) (uint64, error) { |
|
|
|
|
offset := id |
|
|
|
|
// In version 2 series IDs are no longer exact references but series are 16-byte padded
|
|
|
|
|
// In version 2+ series IDs are no longer exact references but series are 16-byte padded
|
|
|
|
|
// and the ID is the multiple of 16 of the actual position.
|
|
|
|
|
if r.version == FormatV2 { |
|
|
|
|
if r.version >= FormatV2 { |
|
|
|
|
offset = id * 16 |
|
|
|
|
} |
|
|
|
|
d := encoding.DecWrap(tsdb_enc.NewDecbufUvarintAt(r.b, int(offset), castagnoliTable)) |
|
|
|
@ -1741,13 +1830,28 @@ func (r *Reader) Series(id storage.SeriesRef, from int64, through int64, lbls *l |
|
|
|
|
return 0, d.Err() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fprint, err := r.dec.Series(d.Get(), id, from, through, lbls, chks) |
|
|
|
|
fprint, err := r.dec.Series(r.version, d.Get(), id, from, through, lbls, chks) |
|
|
|
|
if err != nil { |
|
|
|
|
return 0, errors.Wrap(err, "read series") |
|
|
|
|
} |
|
|
|
|
return fprint, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (r *Reader) ChunkStats(id storage.SeriesRef, from, through int64, lbls *labels.Labels) (uint64, ChunkStats, error) { |
|
|
|
|
offset := id |
|
|
|
|
// In version 2+ series IDs are no longer exact references but series are 16-byte padded
|
|
|
|
|
// and the ID is the multiple of 16 of the actual position.
|
|
|
|
|
if r.version >= FormatV2 { |
|
|
|
|
offset = id * 16 |
|
|
|
|
} |
|
|
|
|
d := encoding.DecWrap(tsdb_enc.NewDecbufUvarintAt(r.b, int(offset), castagnoliTable)) |
|
|
|
|
if d.Err() != nil { |
|
|
|
|
return 0, ChunkStats{}, d.Err() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return r.dec.ChunkStats(r.version, d.Get(), id, from, through, lbls) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (r *Reader) Postings(name string, shard *ShardAnnotation, values ...string) (Postings, error) { |
|
|
|
|
if r.version == FormatV1 { |
|
|
|
|
e, ok := r.postingsV1[name] |
|
|
|
@ -1941,14 +2045,25 @@ func (c *chunkSamples) getChunkSampleForQueryStarting(ts int64) *chunkSample { |
|
|
|
|
return &c.chunks[i] |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Decoder provides decoding methods for the v1 and v2 index file format.
|
|
|
|
|
//
|
|
|
|
|
// Decoder provides decoding methods
|
|
|
|
|
// It currently does not contain decoding methods for all entry types but can be extended
|
|
|
|
|
// by them if there's demand.
|
|
|
|
|
type Decoder struct { |
|
|
|
|
LookupSymbol func(uint32) (string, error) |
|
|
|
|
chunksSample map[storage.SeriesRef]*chunkSamples |
|
|
|
|
chunksSampleMtx sync.RWMutex |
|
|
|
|
LookupSymbol func(uint32) (string, error) |
|
|
|
|
chunksSample map[storage.SeriesRef]*chunkSamples // used prior to v3
|
|
|
|
|
maxChunksToBypassMarkerLookup int |
|
|
|
|
chunksSampleMtx sync.RWMutex // used prior to v3
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func newDecoder( |
|
|
|
|
lookupSymbol func(uint32) (string, error), |
|
|
|
|
maxChunksToBypassMarkerLookup int, |
|
|
|
|
) *Decoder { |
|
|
|
|
return &Decoder{ |
|
|
|
|
LookupSymbol: lookupSymbol, |
|
|
|
|
maxChunksToBypassMarkerLookup: maxChunksToBypassMarkerLookup, |
|
|
|
|
chunksSample: map[storage.SeriesRef]*chunkSamples{}, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Postings returns a postings list for b and its number of elements.
|
|
|
|
@ -2088,10 +2203,11 @@ func buildChunkSamples(d encoding.Decbuf, numChunks int, info *chunkSamples) err |
|
|
|
|
return d.Err() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Series decodes a series entry from the given byte slice into lset and chks.
|
|
|
|
|
func (dec *Decoder) Series(b []byte, seriesRef storage.SeriesRef, from int64, through int64, lbls *labels.Labels, chks *[]ChunkMeta) (uint64, error) { |
|
|
|
|
func (dec *Decoder) prepSeries(b []byte, lbls *labels.Labels, chks *[]ChunkMeta) (*encoding.Decbuf, uint64, error) { |
|
|
|
|
*lbls = (*lbls)[:0] |
|
|
|
|
*chks = (*chks)[:0] |
|
|
|
|
if chks != nil { |
|
|
|
|
*chks = (*chks)[:0] |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
d := encoding.DecWrap(tsdb_enc.Decbuf{B: b}) |
|
|
|
|
|
|
|
|
@ -2103,42 +2219,277 @@ func (dec *Decoder) Series(b []byte, seriesRef storage.SeriesRef, from int64, th |
|
|
|
|
lvo := uint32(d.Uvarint()) |
|
|
|
|
|
|
|
|
|
if d.Err() != nil { |
|
|
|
|
return 0, errors.Wrap(d.Err(), "read series label offsets") |
|
|
|
|
return nil, 0, errors.Wrap(d.Err(), "read series label offsets") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ln, err := dec.LookupSymbol(lno) |
|
|
|
|
if err != nil { |
|
|
|
|
return 0, errors.Wrap(err, "lookup label name") |
|
|
|
|
return nil, 0, errors.Wrap(err, "lookup label name") |
|
|
|
|
} |
|
|
|
|
lv, err := dec.LookupSymbol(lvo) |
|
|
|
|
if err != nil { |
|
|
|
|
return 0, errors.Wrap(err, "lookup label value") |
|
|
|
|
return nil, 0, errors.Wrap(err, "lookup label value") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
*lbls = append(*lbls, labels.Label{Name: ln, Value: lv}) |
|
|
|
|
} |
|
|
|
|
return &d, fprint, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (dec *Decoder) ChunkStats(version int, b []byte, seriesRef storage.SeriesRef, from, through int64, lbls *labels.Labels) (uint64, ChunkStats, error) { |
|
|
|
|
d, fp, err := dec.prepSeries(b, lbls, nil) |
|
|
|
|
if err != nil { |
|
|
|
|
return 0, ChunkStats{}, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
stats, err := dec.readChunkStats(version, d, seriesRef, from, through) |
|
|
|
|
return fp, stats, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (dec *Decoder) readChunkStats(version int, d *encoding.Decbuf, seriesRef storage.SeriesRef, from, through int64) (ChunkStats, error) { |
|
|
|
|
if version > FormatV2 { |
|
|
|
|
return dec.readChunkStatsV3(d, from, through) |
|
|
|
|
} |
|
|
|
|
return dec.readChunkStatsPriorV3(d, seriesRef, from, through) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (dec *Decoder) readChunkStatsV3(d *encoding.Decbuf, from, through int64) (res ChunkStats, err error) { |
|
|
|
|
nChunks := d.Uvarint() |
|
|
|
|
markersLn := int(d.Be32()) // markersLn
|
|
|
|
|
startMarkers := d.Len() |
|
|
|
|
|
|
|
|
|
if nChunks < dec.maxChunksToBypassMarkerLookup { |
|
|
|
|
d.Skip(markersLn) |
|
|
|
|
return dec.accumulateChunkStats(d, nChunks, from, through) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
nMarkers := d.Uvarint() |
|
|
|
|
|
|
|
|
|
relevantPages := chunkPageMarkersPool.Get(nMarkers) |
|
|
|
|
defer chunkPageMarkersPool.Put(relevantPages) |
|
|
|
|
for i := 0; i < nMarkers; i++ { |
|
|
|
|
var marker chunkPageMarker |
|
|
|
|
marker.decode(d) |
|
|
|
|
if overlap(from, through, marker.MinTime, marker.MaxTime) { |
|
|
|
|
relevantPages = append(relevantPages, marker) |
|
|
|
|
} else if marker.MinTime >= through { |
|
|
|
|
break |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if d.Err() != nil { |
|
|
|
|
return res, errors.Wrap(d.Err(), "read chunk markers") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// guaranteed to have no matching chunks
|
|
|
|
|
if len(relevantPages) == 0 { |
|
|
|
|
return ChunkStats{}, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// consume rest of markers, if any
|
|
|
|
|
d.Skip(markersLn - (startMarkers - d.Len())) |
|
|
|
|
|
|
|
|
|
// length of buffer at beginning of chunks,
|
|
|
|
|
// later used to incrementally skip pages
|
|
|
|
|
initialLn := d.Len() |
|
|
|
|
|
|
|
|
|
for markerIdx := 0; markerIdx < len(relevantPages); markerIdx++ { |
|
|
|
|
curMarker := relevantPages[markerIdx] |
|
|
|
|
|
|
|
|
|
if curMarker.subsetOf(from, through) { |
|
|
|
|
// use aggregated stats for this page
|
|
|
|
|
res.addRaw(curMarker.ChunksInPage, curMarker.KB, curMarker.Entries) |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// skip to the offset of the page, adjusting for where we currently
|
|
|
|
|
// are, since offsets are relative to the start
|
|
|
|
|
d.Skip(curMarker.Offset - (initialLn - d.Len())) |
|
|
|
|
|
|
|
|
|
// page partially overlaps -- need to check chunks individually
|
|
|
|
|
var prevMaxT int64 |
|
|
|
|
for i := 0; i < curMarker.ChunksInPage; i++ { |
|
|
|
|
chunkMeta := &ChunkMeta{} |
|
|
|
|
|
|
|
|
|
var err error |
|
|
|
|
if i == 0 { |
|
|
|
|
// need to force the min-time because chunks are indexed
|
|
|
|
|
// with delta-encoded min-times relative to the prior chunk,
|
|
|
|
|
// but this doesn't reset at page boundaries
|
|
|
|
|
// (maybe it should for more ergonomic programming).
|
|
|
|
|
// instead, we can just force the min-time to the page's min-time
|
|
|
|
|
err = readChunkMetaWithForcedMintime(d, curMarker.MinTime, chunkMeta, true) |
|
|
|
|
} else { |
|
|
|
|
err = readChunkMeta(d, prevMaxT, chunkMeta) |
|
|
|
|
} |
|
|
|
|
if err != nil { |
|
|
|
|
return res, errors.Wrap(d.Err(), "read meta for chunk") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
prevMaxT = chunkMeta.MaxTime |
|
|
|
|
|
|
|
|
|
if overlap(from, through, chunkMeta.MinTime, chunkMeta.MaxTime) { |
|
|
|
|
// add to stats
|
|
|
|
|
res.AddChunk(chunkMeta, from, through) |
|
|
|
|
} else if chunkMeta.MinTime >= through { |
|
|
|
|
break |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return res, d.Err() |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (dec *Decoder) accumulateChunkStats(d *encoding.Decbuf, nChunks int, from, through int64) (res ChunkStats, err error) { |
|
|
|
|
var prevMaxT int64 |
|
|
|
|
chunkMeta := &ChunkMeta{} |
|
|
|
|
for i := 0; i < nChunks; i++ { |
|
|
|
|
if err := readChunkMeta(d, prevMaxT, chunkMeta); err != nil { |
|
|
|
|
return res, errors.Wrap(d.Err(), "read meta for chunk") |
|
|
|
|
} |
|
|
|
|
prevMaxT = chunkMeta.MaxTime |
|
|
|
|
|
|
|
|
|
if overlap(from, through, chunkMeta.MinTime, chunkMeta.MaxTime) { |
|
|
|
|
// add to stats
|
|
|
|
|
res.AddChunk(chunkMeta, from, through) |
|
|
|
|
} else if chunkMeta.MinTime >= through { |
|
|
|
|
break |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return res, d.Err() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (dec *Decoder) readChunkStatsPriorV3(d *encoding.Decbuf, seriesRef storage.SeriesRef, from, through int64) (res ChunkStats, err error) { |
|
|
|
|
// prior to v3, chunks needed iteration for stats aggregation
|
|
|
|
|
chks := ChunkMetasPool.Get() |
|
|
|
|
defer ChunkMetasPool.Put(chks) |
|
|
|
|
err = dec.readChunks(FormatV2, d, seriesRef, from, through, &chks) |
|
|
|
|
if err != nil { |
|
|
|
|
return ChunkStats{}, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for _, chk := range chks { |
|
|
|
|
if overlap(from, through, chk.MinTime, chk.MaxTime) { |
|
|
|
|
res.AddChunk(&chk, from, through) |
|
|
|
|
} else if chk.MinTime >= through { |
|
|
|
|
break |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return res, nil |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Series decodes a series entry from the given byte slice into lset and chks.
|
|
|
|
|
func (dec *Decoder) Series(version int, b []byte, seriesRef storage.SeriesRef, from int64, through int64, lbls *labels.Labels, chks *[]ChunkMeta) (uint64, error) { |
|
|
|
|
|
|
|
|
|
d, fprint, err := dec.prepSeries(b, lbls, chks) |
|
|
|
|
if err != nil { |
|
|
|
|
return 0, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// read chunks based on fmt
|
|
|
|
|
if err := dec.readChunks(version, d, seriesRef, from, through, chks); err != nil { |
|
|
|
|
return 0, err |
|
|
|
|
} |
|
|
|
|
return fprint, nil |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (dec *Decoder) readChunks(version int, d *encoding.Decbuf, seriesRef storage.SeriesRef, from int64, through int64, chks *[]ChunkMeta) error { |
|
|
|
|
// read chunks based on fmt
|
|
|
|
|
if version > FormatV2 { |
|
|
|
|
return dec.readChunksV3(d, from, through, chks) |
|
|
|
|
} |
|
|
|
|
return dec.readChunksPriorV3(d, seriesRef, from, through, chks) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (dec *Decoder) readChunksV3(d *encoding.Decbuf, from int64, through int64, chks *[]ChunkMeta) error { |
|
|
|
|
nChunks := d.Uvarint() |
|
|
|
|
chunksRemaining := nChunks |
|
|
|
|
|
|
|
|
|
markersLn := int(d.Be32()) // markersLn
|
|
|
|
|
|
|
|
|
|
// variables must be declared before goto, allowing us to skip
|
|
|
|
|
// using chunk pages when the chunk count is small
|
|
|
|
|
var ( |
|
|
|
|
nMarkers int |
|
|
|
|
marker chunkPageMarker |
|
|
|
|
markers chunkPageMarkers |
|
|
|
|
prevMaxT int64 |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
startMarkers := d.Len() |
|
|
|
|
if nChunks < dec.maxChunksToBypassMarkerLookup { |
|
|
|
|
d.Skip(markersLn) |
|
|
|
|
goto iterate |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
nMarkers = d.Uvarint() |
|
|
|
|
|
|
|
|
|
markers = chunkPageMarkersPool.Get(nMarkers) |
|
|
|
|
markers = markers[:nMarkers] |
|
|
|
|
defer chunkPageMarkersPool.Put(markers) |
|
|
|
|
for i := range markers { |
|
|
|
|
markers[i].decode(d) |
|
|
|
|
|
|
|
|
|
if overlap(from, through, markers[i].MinTime, markers[i].MaxTime) { |
|
|
|
|
d.Skip(markersLn - (startMarkers - d.Len())) // skip the rest of markers
|
|
|
|
|
marker = markers[i] |
|
|
|
|
d.Skip(marker.Offset) // skip to the desired chunks
|
|
|
|
|
goto iterate |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
chunksRemaining -= marker.ChunksInPage |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if d.Err() != nil { |
|
|
|
|
return errors.Wrap(d.Err(), "read chunk markers") |
|
|
|
|
} |
|
|
|
|
return nil |
|
|
|
|
|
|
|
|
|
iterate: |
|
|
|
|
for i := 0; i < chunksRemaining; i++ { |
|
|
|
|
chunkMeta := &ChunkMeta{} |
|
|
|
|
if err := readChunkMeta(d, prevMaxT, chunkMeta); err != nil { |
|
|
|
|
return errors.Wrapf(d.Err(), "read meta for chunk %d", nChunks-chunksRemaining+i) |
|
|
|
|
} |
|
|
|
|
prevMaxT = chunkMeta.MaxTime |
|
|
|
|
|
|
|
|
|
if overlap(from, through, chunkMeta.MinTime, chunkMeta.MaxTime) { |
|
|
|
|
*chks = append(*chks, *chunkMeta) |
|
|
|
|
} else if chunkMeta.MinTime >= through { |
|
|
|
|
break |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return d.Err() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (dec *Decoder) readChunksPriorV3(d *encoding.Decbuf, seriesRef storage.SeriesRef, from int64, through int64, chks *[]ChunkMeta) error { |
|
|
|
|
// Read the chunks meta data.
|
|
|
|
|
k = d.Uvarint() |
|
|
|
|
k := d.Uvarint() |
|
|
|
|
|
|
|
|
|
if k == 0 { |
|
|
|
|
return 0, d.Err() |
|
|
|
|
return d.Err() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
chunksSample, err := dec.getOrCreateChunksSample(encoding.DecWrap(tsdb_enc.Decbuf{B: d.Get()}), seriesRef, k) |
|
|
|
|
if err != nil { |
|
|
|
|
return 0, err |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
cs := chunksSample.getChunkSampleForQueryStarting(from) |
|
|
|
|
if cs == nil { |
|
|
|
|
return fprint, nil |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
d.Skip(cs.offset) |
|
|
|
|
|
|
|
|
|
chunkMeta := &ChunkMeta{} |
|
|
|
|
if err := readChunkMeta(&d, cs.prevChunkMaxt, chunkMeta); err != nil { |
|
|
|
|
return 0, errors.Wrapf(d.Err(), "read meta for chunk %d", cs.idx) |
|
|
|
|
if err := readChunkMeta(d, cs.prevChunkMaxt, chunkMeta); err != nil { |
|
|
|
|
return errors.Wrapf(d.Err(), "read meta for chunk %d", cs.idx) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if overlap(from, through, chunkMeta.MinTime, chunkMeta.MaxTime) { |
|
|
|
@ -2147,26 +2498,34 @@ func (dec *Decoder) Series(b []byte, seriesRef storage.SeriesRef, from int64, th |
|
|
|
|
t0 := chunkMeta.MaxTime |
|
|
|
|
|
|
|
|
|
for i := cs.idx + 1; i < k; i++ { |
|
|
|
|
if err := readChunkMeta(&d, t0, chunkMeta); err != nil { |
|
|
|
|
return 0, errors.Wrapf(d.Err(), "read meta for chunk %d", cs.idx) |
|
|
|
|
if err := readChunkMeta(d, t0, chunkMeta); err != nil { |
|
|
|
|
return errors.Wrapf(d.Err(), "read meta for chunk %d", cs.idx) |
|
|
|
|
} |
|
|
|
|
t0 = chunkMeta.MaxTime |
|
|
|
|
|
|
|
|
|
if !overlap(from, through, chunkMeta.MinTime, chunkMeta.MaxTime) { |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
if chunkMeta.MinTime >= through { |
|
|
|
|
if overlap(from, through, chunkMeta.MinTime, chunkMeta.MaxTime) { |
|
|
|
|
*chks = append(*chks, *chunkMeta) |
|
|
|
|
} else if chunkMeta.MinTime >= through { |
|
|
|
|
break |
|
|
|
|
} |
|
|
|
|
*chks = append(*chks, *chunkMeta) |
|
|
|
|
} |
|
|
|
|
return fprint, d.Err() |
|
|
|
|
return d.Err() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func readChunkMeta(d *encoding.Decbuf, prevChunkMaxt int64, chunkMeta *ChunkMeta) error { |
|
|
|
|
// Decode the diff against previous chunk as varint
|
|
|
|
|
// instead of uvarint because chunks may overlap
|
|
|
|
|
chunkMeta.MinTime = d.Varint64() + prevChunkMaxt |
|
|
|
|
mint := d.Varint64() + prevChunkMaxt |
|
|
|
|
return readChunkMetaWithForcedMintime(d, mint, chunkMeta, false) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func readChunkMetaWithForcedMintime(d *encoding.Decbuf, mint int64, chunkMeta *ChunkMeta, decodeMinT bool) error { |
|
|
|
|
if decodeMinT { |
|
|
|
|
// skip the mint delta since we're forcing, but still need to
|
|
|
|
|
// remove the bytes from our buffer
|
|
|
|
|
d.Varint64() |
|
|
|
|
} |
|
|
|
|
chunkMeta.MinTime = mint |
|
|
|
|
chunkMeta.MaxTime = int64(d.Uvarint64()) + chunkMeta.MinTime |
|
|
|
|
chunkMeta.KB = uint32(d.Uvarint()) |
|
|
|
|
chunkMeta.Entries = uint32(d.Uvarint64()) |
|
|
|
@ -2183,6 +2542,8 @@ func yoloString(b []byte) string { |
|
|
|
|
return *((*string)(unsafe.Pointer(&b))) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func overlap(aFrom, aThrough, bFrom, bThrough int64) bool { |
|
|
|
|
return aFrom < bThrough && aThrough > bFrom |
|
|
|
|
func overlap(from, through, chkFrom, chkThrough int64) bool { |
|
|
|
|
// note: chkThrough is inclusive as it represents the last
|
|
|
|
|
// sample timestamp in the chunk, whereas through is exclusive
|
|
|
|
|
return from <= chkThrough && through > chkFrom |
|
|
|
|
} |
|
|
|
|