-
Notifications
You must be signed in to change notification settings - Fork 179
Add ChunksIterator method to Series interface. #665
base: master
Are you sure you want to change the base?
Changes from 4 commits
64eeed9
4ed00e1
d097d3f
c407499
af8fb41
6f6dd39
78e9e36
f09fb89
220c3dd
b84c439
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ package tsdb | |
|
||
import ( | ||
"fmt" | ||
"math" | ||
"sort" | ||
"strings" | ||
"unicode/utf8" | ||
|
@@ -54,6 +55,9 @@ type Series interface { | |
|
||
// Iterator returns a new iterator of the data of the series. | ||
Iterator() SeriesIterator | ||
|
||
// ChunkIterator returns a new iterator for the non-overlapping chunks of the series. | ||
bwplotka marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ChunkIterator() ChunkIterator | ||
} | ||
|
||
// querier aggregates querying results from time blocks within | ||
|
@@ -876,6 +880,10 @@ func (s *chunkSeries) Iterator() SeriesIterator { | |
return newChunkSeriesIterator(s.chunks, s.intervals, s.mint, s.maxt) | ||
} | ||
|
||
func (s *chunkSeries) ChunkIterator() ChunkIterator { | ||
return &chunkIterator{chunks: s.chunks} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
However not sure what mint maxt means here in detail. Should we be strict and allow mint and maxt in terms of samples inside chunks as well? By the looks of this: https://github.com/prometheus/tsdb/blob/78e9e360581153d73b1c69d31fd6e2d89e060a82/querier.go#L796 we just check if they overlap with required min or maxt here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then, we pass to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think it is fine to start with with the current interface if it covers the streaming use case. Don't see a problem to refactor when/if needed. |
||
} | ||
|
||
// SeriesIterator iterates over the data of a time series. | ||
type SeriesIterator interface { | ||
// Seek advances the iterator forward to the given timestamp. | ||
|
@@ -904,7 +912,15 @@ func (s *chainedSeries) Iterator() SeriesIterator { | |
return newChainedSeriesIterator(s.series...) | ||
} | ||
|
||
// chainedSeriesIterator implements a series iterater over a list | ||
func (s *chainedSeries) ChunkIterator() ChunkIterator { | ||
ch := &chainedChunkIterator{chain: make([]ChunkIterator, 0, len(s.series))} | ||
for _, s := range s.series { | ||
ch.chain = append(ch.chain, s.ChunkIterator()) | ||
bwplotka marked this conversation as resolved.
Show resolved
Hide resolved
bwplotka marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
return ch | ||
} | ||
|
||
// chainedSeriesIterator implements a series iterated over a list | ||
// of time-sorted, non-overlapping iterators. | ||
type chainedSeriesIterator struct { | ||
series []Series // series in time order | ||
|
@@ -975,7 +991,11 @@ func (s *verticalChainedSeries) Iterator() SeriesIterator { | |
return newVerticalMergeSeriesIterator(s.series...) | ||
} | ||
|
||
// verticalMergeSeriesIterator implements a series iterater over a list | ||
func (s *verticalChainedSeries) ChunkIterator() ChunkIterator { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be useful to also add a test for this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a test that covers this? |
||
return newVerticalMergeChunkIterator(s.series...) | ||
} | ||
|
||
// verticalMergeSeriesIterator implements a series iterator over a list | ||
// of time-sorted, time-overlapping iterators. | ||
type verticalMergeSeriesIterator struct { | ||
a, b SeriesIterator | ||
|
@@ -1055,6 +1075,141 @@ func (it *verticalMergeSeriesIterator) Err() error { | |
return it.b.Err() | ||
} | ||
|
||
type noSeekSeriesIterator struct { | ||
chunkenc.Iterator | ||
err error | ||
} | ||
|
||
func (it *noSeekSeriesIterator) Seek(t int64) bool { | ||
it.err = errors.New("not implemented: Seek method invoked for noSeekSeriesIterator") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would rather prefer having an inefficient implementation as @brian-brazil said instead of an error here. |
||
return false | ||
} | ||
|
||
func (it *noSeekSeriesIterator) Err() error { | ||
if it.err != nil { | ||
return it.err | ||
} | ||
return it.Iterator.Err() | ||
} | ||
|
||
// verticalMergeChunkIterator implements a ChunkIterator over a list | ||
// of time-sorted, time-overlapping chunk iterators for the same labels (same series). | ||
// Any overlap in chunks will be merged using verticalMergeSeriesIterator. | ||
type verticalMergeChunkIterator struct { | ||
a, b ChunkIterator | ||
aok, bok, initialized bool | ||
|
||
curMeta chunks.Meta | ||
err error | ||
|
||
aReuseIter, bReuseIter chunkenc.Iterator | ||
} | ||
|
||
func newVerticalMergeChunkIterator(s ...Series) ChunkIterator { | ||
if len(s) == 1 { | ||
return s[0].ChunkIterator() | ||
} else if len(s) == 2 { | ||
return &verticalMergeChunkIterator{ | ||
a: s[0].ChunkIterator(), | ||
b: s[1].ChunkIterator(), | ||
} | ||
} | ||
return &verticalMergeChunkIterator{ | ||
a: s[0].ChunkIterator(), | ||
b: newVerticalMergeChunkIterator(s[1:]...), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I find this really hard to understand. I know it is used in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The recursive approach is sometimes hard to follow indeed. Let's postpone cleaning this though, as we do this everywhere. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did run a debug session and the final struct is really bizarre, sort of deeply nested. But yeah a conversation for another PR. |
||
} | ||
} | ||
|
||
func (it *verticalMergeChunkIterator) Next() bool { | ||
bwplotka marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if !it.initialized { | ||
it.aok = it.a.Next() | ||
it.bok = it.b.Next() | ||
it.initialized = true | ||
} | ||
|
||
if !it.aok && !it.bok { | ||
return false | ||
} | ||
|
||
if !it.aok { | ||
it.curMeta = it.b.At() | ||
it.bok = it.b.Next() | ||
return true | ||
} | ||
if !it.bok { | ||
it.curMeta = it.a.At() | ||
it.aok = it.a.Next() | ||
return true | ||
} | ||
|
||
aCurMeta := it.a.At() | ||
bCurMeta := it.b.At() | ||
|
||
if aCurMeta.MaxTime < bCurMeta.MinTime { | ||
it.curMeta = aCurMeta | ||
it.aok = it.a.Next() | ||
return true | ||
} | ||
|
||
if bCurMeta.MaxTime < aCurMeta.MinTime { | ||
it.curMeta = bCurMeta | ||
it.bok = it.b.Next() | ||
return true | ||
} | ||
|
||
chk := chunkenc.NewXORChunk() | ||
app, err := chk.Appender() | ||
if err != nil { | ||
it.err = err | ||
return false | ||
} | ||
seriesIter := &verticalMergeSeriesIterator{ | ||
a: &noSeekSeriesIterator{Iterator: aCurMeta.Chunk.Iterator(it.aReuseIter)}, | ||
b: &noSeekSeriesIterator{Iterator: bCurMeta.Chunk.Iterator(it.bReuseIter)}, | ||
} | ||
|
||
mint := int64(math.MaxInt64) | ||
maxt := int64(0) | ||
krasi-georgiev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// TODO: This can end up being up to 240 samples per chunk, so we need to have a case to split to two. | ||
bwplotka marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for seriesIter.Next() { | ||
t, v := seriesIter.At() | ||
app.Append(t, v) | ||
|
||
maxt = t | ||
if mint == math.MaxInt64 { | ||
mint = t | ||
} | ||
} | ||
if err := seriesIter.Err(); err != nil { | ||
it.err = err | ||
return false | ||
} | ||
|
||
it.curMeta = chunks.Meta{ | ||
MinTime: mint, | ||
MaxTime: maxt, | ||
Chunk: chk, | ||
} | ||
it.aok = it.a.Next() | ||
it.bok = it.b.Next() | ||
return true | ||
} | ||
|
||
func (it *verticalMergeChunkIterator) At() chunks.Meta { | ||
return it.curMeta | ||
} | ||
|
||
func (it *verticalMergeChunkIterator) Err() error { | ||
if it.err != nil { | ||
return it.err | ||
} | ||
if it.a.Err() != nil { | ||
return it.a.Err() | ||
} | ||
return it.b.Err() | ||
} | ||
|
||
// chunkSeriesIterator implements a series iterator on top | ||
// of a list of time-sorted, non-overlapping chunks. | ||
type chunkSeriesIterator struct { | ||
|
@@ -1210,3 +1365,72 @@ type errSeriesSet struct { | |
func (s errSeriesSet) Next() bool { return false } | ||
func (s errSeriesSet) At() Series { return nil } | ||
func (s errSeriesSet) Err() error { return s.err } | ||
|
||
// ChunkIterator iterates over the chunk of a time series. | ||
type ChunkIterator interface { | ||
// At returns the meta. | ||
At() chunks.Meta | ||
// Next advances the iterator by one. | ||
Next() bool | ||
// Err returns optional error if Next is false. | ||
Err() error | ||
} | ||
|
||
type errChunkIterator struct { | ||
err error | ||
} | ||
|
||
func (s errChunkIterator) Next() bool { return false } | ||
func (s errChunkIterator) At() chunks.Meta { return chunks.Meta{} } | ||
func (s errChunkIterator) Err() error { return s.err } | ||
|
||
type chunkIterator struct { | ||
chunks []chunks.Meta // series in time order | ||
i int | ||
} | ||
|
||
func (c *chunkIterator) Next() bool { | ||
if c.i >= len(c.chunks) { | ||
return false | ||
} | ||
c.i++ | ||
return true | ||
} | ||
|
||
func (c *chunkIterator) At() chunks.Meta { | ||
return c.chunks[c.i-1] | ||
} | ||
|
||
func (c *chunkIterator) Err() error { return nil } | ||
|
||
// chainedChunkIterator implements flat iteration for chunks iterated over a list | ||
// of time-sorted, non-overlapping iterators for each series. | ||
type chainedChunkIterator struct { | ||
chain []ChunkIterator // chunk iterators for each series in time order | ||
i int | ||
err error | ||
} | ||
|
||
func (c *chainedChunkIterator) Next() bool { | ||
bwplotka marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if c.Err() != nil { | ||
return false | ||
} | ||
|
||
for c.i < len(c.chain) { | ||
if c.chain[c.i].Next() { | ||
return true | ||
} | ||
if err := c.chain[c.i].Err(); err != nil { | ||
c.err = err | ||
return false | ||
} | ||
c.i++ | ||
} | ||
return false | ||
} | ||
|
||
func (c *chainedChunkIterator) At() chunks.Meta { | ||
return c.chain[c.i].At() | ||
} | ||
|
||
func (c *chainedChunkIterator) Err() error { return c.err } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wrong paragraph?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
failed rebase