Make chunk iterators more DRY

This finally extracts all the common code of the two chunk iterators
into one. Any future chunk encodings with fast access by index can use
the same iterator by simply providing an indexAccessor. Other future
chunk encodings without fast index access (like Gorilla-style) can
still implement the chunkIterator interface as usual.
pull/1477/head
beorn7 2016-03-07 20:23:14 +01:00
parent 32f280a3cd
commit c13b1ecfe9
3 changed files with 191 additions and 247 deletions

View File

@ -17,6 +17,7 @@ import (
"container/list" "container/list"
"fmt" "fmt"
"io" "io"
"sort"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -342,3 +343,102 @@ func newChunkForEncoding(encoding chunkEncoding) (chunk, error) {
return nil, fmt.Errorf("unknown chunk encoding: %v", encoding) return nil, fmt.Errorf("unknown chunk encoding: %v", encoding)
} }
} }
// indexAccessor allows accesses to samples by index.
type indexAccessor interface {
timestampAtIndex(int) model.Time
sampleValueAtIndex(int) model.SampleValue
err() error
}
// indexAccessingChunkIterator is a chunk iterator for chunks for which an
// indexAccessor implementation exists.
type indexAccessingChunkIterator struct {
len int
pos int
lastValue model.SamplePair
acc indexAccessor
}
func newIndexAccessingChunkIterator(len int, acc indexAccessor) *indexAccessingChunkIterator {
return &indexAccessingChunkIterator{
len: len,
pos: -1,
lastValue: ZeroSamplePair,
acc: acc,
}
}
// lastTimestamp implements chunkIterator.
func (it *indexAccessingChunkIterator) lastTimestamp() (model.Time, error) {
return it.acc.timestampAtIndex(it.len - 1), it.acc.err()
}
// valueAtOrBeforeTime implements chunkIterator.
func (it *indexAccessingChunkIterator) valueAtOrBeforeTime(t model.Time) (model.SamplePair, error) {
i := sort.Search(it.len, func(i int) bool {
return it.acc.timestampAtIndex(i).After(t)
})
if i == 0 || it.acc.err() != nil {
return ZeroSamplePair, it.acc.err()
}
it.pos = i - 1
it.lastValue = model.SamplePair{
Timestamp: it.acc.timestampAtIndex(i - 1),
Value: it.acc.sampleValueAtIndex(i - 1),
}
return it.lastValue, it.acc.err()
}
// rangeValues implements chunkIterator.
func (it *indexAccessingChunkIterator) rangeValues(in metric.Interval) ([]model.SamplePair, error) {
oldest := sort.Search(it.len, func(i int) bool {
return !it.acc.timestampAtIndex(i).Before(in.OldestInclusive)
})
newest := sort.Search(it.len, func(i int) bool {
return it.acc.timestampAtIndex(i).After(in.NewestInclusive)
})
if oldest == it.len || it.acc.err() != nil {
return nil, it.acc.err()
}
result := make([]model.SamplePair, 0, newest-oldest)
for i := oldest; i < newest; i++ {
it.pos = i
it.lastValue = model.SamplePair{
Timestamp: it.acc.timestampAtIndex(i),
Value: it.acc.sampleValueAtIndex(i),
}
result = append(result, it.lastValue)
}
return result, it.acc.err()
}
// contains implements chunkIterator.
func (it *indexAccessingChunkIterator) contains(t model.Time) (bool, error) {
return !t.Before(it.acc.timestampAtIndex(0)) &&
!t.After(it.acc.timestampAtIndex(it.len-1)), it.acc.err()
}
// scan implements chunkIterator.
func (it *indexAccessingChunkIterator) scan() bool {
it.pos++
if it.pos >= it.len {
return false
}
it.lastValue = model.SamplePair{
Timestamp: it.acc.timestampAtIndex(it.pos),
Value: it.acc.sampleValueAtIndex(it.pos),
}
return it.acc.err() == nil
}
// value implements chunkIterator.
func (it *indexAccessingChunkIterator) value() model.SamplePair {
return it.lastValue
}
// err implements chunkIterator.
func (it *indexAccessingChunkIterator) err() error {
return it.acc.err()
}

View File

@ -18,11 +18,8 @@ import (
"fmt" "fmt"
"io" "io"
"math" "math"
"sort"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
"github.com/prometheus/prometheus/storage/metric"
) )
// The 21-byte header of a delta-encoded chunk looks like: // The 21-byte header of a delta-encoded chunk looks like:
@ -201,17 +198,14 @@ func (c deltaEncodedChunk) firstTime() model.Time {
// newIterator implements chunk. // newIterator implements chunk.
func (c *deltaEncodedChunk) newIterator() chunkIterator { func (c *deltaEncodedChunk) newIterator() chunkIterator {
return &deltaEncodedChunkIterator{ return newIndexAccessingChunkIterator(c.len(), &deltaEncodedIndexAccessor{
c: *c, c: *c,
len: c.len(),
baseT: c.baseTime(), baseT: c.baseTime(),
baseV: c.baseValue(), baseV: c.baseValue(),
tBytes: c.timeBytes(), tBytes: c.timeBytes(),
vBytes: c.valueBytes(), vBytes: c.valueBytes(),
isInt: c.isInt(), isInt: c.isInt(),
pos: -1, })
lastValue: ZeroSamplePair,
}
} }
// marshal implements chunk. // marshal implements chunk.
@ -305,137 +299,65 @@ func (c deltaEncodedChunk) len() int {
return (len(c) - deltaHeaderBytes) / c.sampleSize() return (len(c) - deltaHeaderBytes) / c.sampleSize()
} }
// deltaEncodedChunkIterator implements chunkIterator. // deltaEncodedIndexAccessor implements indexAccessor.
type deltaEncodedChunkIterator struct { type deltaEncodedIndexAccessor struct {
c deltaEncodedChunk c deltaEncodedChunk
len int
baseT model.Time baseT model.Time
baseV model.SampleValue baseV model.SampleValue
tBytes, vBytes deltaBytes tBytes, vBytes deltaBytes
isInt bool isInt bool
pos int
lastValue model.SamplePair
lastErr error lastErr error
} }
// lastTimestamp implements chunkIterator. func (acc *deltaEncodedIndexAccessor) err() error {
func (it *deltaEncodedChunkIterator) lastTimestamp() (model.Time, error) { return acc.lastErr
return it.timestampAtIndex(it.len - 1), it.lastErr
} }
// valueAtOrBeforeTime implements chunkIterator. func (acc *deltaEncodedIndexAccessor) timestampAtIndex(idx int) model.Time {
func (it *deltaEncodedChunkIterator) valueAtOrBeforeTime(t model.Time) (model.SamplePair, error) { offset := deltaHeaderBytes + idx*int(acc.tBytes+acc.vBytes)
i := sort.Search(it.len, func(i int) bool {
return it.timestampAtIndex(i).After(t)
})
if i == 0 || it.lastErr != nil {
return ZeroSamplePair, it.lastErr
}
it.pos = i - 1
it.lastValue = model.SamplePair{
Timestamp: it.timestampAtIndex(i - 1),
Value: it.sampleValueAtIndex(i - 1),
}
return it.lastValue, it.lastErr
}
// rangeValues implements chunkIterator. switch acc.tBytes {
func (it *deltaEncodedChunkIterator) rangeValues(in metric.Interval) ([]model.SamplePair, error) {
oldest := sort.Search(it.len, func(i int) bool {
return !it.timestampAtIndex(i).Before(in.OldestInclusive)
})
newest := sort.Search(it.len, func(i int) bool {
return it.timestampAtIndex(i).After(in.NewestInclusive)
})
if oldest == it.len || it.lastErr != nil {
return nil, it.lastErr
}
result := make([]model.SamplePair, 0, newest-oldest)
for i := oldest; i < newest; i++ {
it.pos = i
it.lastValue = model.SamplePair{
Timestamp: it.timestampAtIndex(i),
Value: it.sampleValueAtIndex(i),
}
result = append(result, it.lastValue)
}
return result, it.lastErr
}
// contains implements chunkIterator.
func (it *deltaEncodedChunkIterator) contains(t model.Time) (bool, error) {
return !t.Before(it.baseT) && !t.After(it.timestampAtIndex(it.len-1)), it.lastErr
}
// scan implements chunkIterator.
func (it *deltaEncodedChunkIterator) scan() bool {
it.pos++
if it.pos >= it.len {
return false
}
it.lastValue = model.SamplePair{
Timestamp: it.timestampAtIndex(it.pos),
Value: it.sampleValueAtIndex(it.pos),
}
return it.lastErr == nil
}
// value implements chunkIterator.
func (it *deltaEncodedChunkIterator) value() model.SamplePair {
return it.lastValue
}
// err implements chunkIterator.
func (it *deltaEncodedChunkIterator) err() error {
return it.lastErr
}
func (it *deltaEncodedChunkIterator) timestampAtIndex(idx int) model.Time {
offset := deltaHeaderBytes + idx*int(it.tBytes+it.vBytes)
switch it.tBytes {
case d1: case d1:
return it.baseT + model.Time(uint8(it.c[offset])) return acc.baseT + model.Time(uint8(acc.c[offset]))
case d2: case d2:
return it.baseT + model.Time(binary.LittleEndian.Uint16(it.c[offset:])) return acc.baseT + model.Time(binary.LittleEndian.Uint16(acc.c[offset:]))
case d4: case d4:
return it.baseT + model.Time(binary.LittleEndian.Uint32(it.c[offset:])) return acc.baseT + model.Time(binary.LittleEndian.Uint32(acc.c[offset:]))
case d8: case d8:
// Take absolute value for d8. // Take absolute value for d8.
return model.Time(binary.LittleEndian.Uint64(it.c[offset:])) return model.Time(binary.LittleEndian.Uint64(acc.c[offset:]))
default: default:
it.lastErr = fmt.Errorf("invalid number of bytes for time delta: %d", it.tBytes) acc.lastErr = fmt.Errorf("invalid number of bytes for time delta: %d", acc.tBytes)
} }
return model.Earliest return model.Earliest
} }
func (it *deltaEncodedChunkIterator) sampleValueAtIndex(idx int) model.SampleValue { func (acc *deltaEncodedIndexAccessor) sampleValueAtIndex(idx int) model.SampleValue {
offset := deltaHeaderBytes + idx*int(it.tBytes+it.vBytes) + int(it.tBytes) offset := deltaHeaderBytes + idx*int(acc.tBytes+acc.vBytes) + int(acc.tBytes)
if it.isInt { if acc.isInt {
switch it.vBytes { switch acc.vBytes {
case d0: case d0:
return it.baseV return acc.baseV
case d1: case d1:
return it.baseV + model.SampleValue(int8(it.c[offset])) return acc.baseV + model.SampleValue(int8(acc.c[offset]))
case d2: case d2:
return it.baseV + model.SampleValue(int16(binary.LittleEndian.Uint16(it.c[offset:]))) return acc.baseV + model.SampleValue(int16(binary.LittleEndian.Uint16(acc.c[offset:])))
case d4: case d4:
return it.baseV + model.SampleValue(int32(binary.LittleEndian.Uint32(it.c[offset:]))) return acc.baseV + model.SampleValue(int32(binary.LittleEndian.Uint32(acc.c[offset:])))
// No d8 for ints. // No d8 for ints.
default: default:
it.lastErr = fmt.Errorf("invalid number of bytes for integer delta: %d", it.vBytes) acc.lastErr = fmt.Errorf("invalid number of bytes for integer delta: %d", acc.vBytes)
} }
} else { } else {
switch it.vBytes { switch acc.vBytes {
case d4: case d4:
return it.baseV + model.SampleValue(math.Float32frombits(binary.LittleEndian.Uint32(it.c[offset:]))) return acc.baseV + model.SampleValue(math.Float32frombits(binary.LittleEndian.Uint32(acc.c[offset:])))
case d8: case d8:
// Take absolute value for d8. // Take absolute value for d8.
return model.SampleValue(math.Float64frombits(binary.LittleEndian.Uint64(it.c[offset:]))) return model.SampleValue(math.Float64frombits(binary.LittleEndian.Uint64(acc.c[offset:])))
default: default:
it.lastErr = fmt.Errorf("invalid number of bytes for floating point delta: %d", it.vBytes) acc.lastErr = fmt.Errorf("invalid number of bytes for floating point delta: %d", acc.vBytes)
} }
} }
return 0 return 0

View File

@ -18,11 +18,8 @@ import (
"fmt" "fmt"
"io" "io"
"math" "math"
"sort"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
"github.com/prometheus/prometheus/storage/metric"
) )
// The 37-byte header of a delta-encoded chunk looks like: // The 37-byte header of a delta-encoded chunk looks like:
@ -207,9 +204,8 @@ func (c doubleDeltaEncodedChunk) firstTime() model.Time {
// newIterator implements chunk. // newIterator implements chunk.
func (c *doubleDeltaEncodedChunk) newIterator() chunkIterator { func (c *doubleDeltaEncodedChunk) newIterator() chunkIterator {
return &doubleDeltaEncodedChunkIterator{ return newIndexAccessingChunkIterator(c.len(), &doubleDeltaEncodedIndexAccessor{
c: *c, c: *c,
len: c.len(),
baseT: c.baseTime(), baseT: c.baseTime(),
baseΔT: c.baseTimeDelta(), baseΔT: c.baseTimeDelta(),
baseV: c.baseValue(), baseV: c.baseValue(),
@ -217,9 +213,7 @@ func (c *doubleDeltaEncodedChunk) newIterator() chunkIterator {
tBytes: c.timeBytes(), tBytes: c.timeBytes(),
vBytes: c.valueBytes(), vBytes: c.valueBytes(),
isInt: c.isInt(), isInt: c.isInt(),
pos: -1, })
lastValue: ZeroSamplePair,
}
} }
// marshal implements chunk. // marshal implements chunk.
@ -411,176 +405,104 @@ func (c doubleDeltaEncodedChunk) addSecondSample(s model.SamplePair, tb, vb delt
return []chunk{&c}, nil return []chunk{&c}, nil
} }
// doubleDeltaEncodedChunkIterator implements chunkIterator. // doubleDeltaEncodedIndexAccessor implements indexAccessor.
type doubleDeltaEncodedChunkIterator struct { type doubleDeltaEncodedIndexAccessor struct {
c doubleDeltaEncodedChunk c doubleDeltaEncodedChunk
len int
baseT, baseΔT model.Time baseT, baseΔT model.Time
baseV, baseΔV model.SampleValue baseV, baseΔV model.SampleValue
tBytes, vBytes deltaBytes tBytes, vBytes deltaBytes
isInt bool isInt bool
pos int
lastValue model.SamplePair
lastErr error lastErr error
} }
// lastTimestamp implements chunkIterator. func (acc *doubleDeltaEncodedIndexAccessor) err() error {
func (it *doubleDeltaEncodedChunkIterator) lastTimestamp() (model.Time, error) { return acc.lastErr
return it.timestampAtIndex(it.len - 1), it.lastErr
} }
// valueAtOrBeforeTime implements chunkIterator. func (acc *doubleDeltaEncodedIndexAccessor) timestampAtIndex(idx int) model.Time {
func (it *doubleDeltaEncodedChunkIterator) valueAtOrBeforeTime(t model.Time) (model.SamplePair, error) {
i := sort.Search(it.len, func(i int) bool {
return it.timestampAtIndex(i).After(t)
})
if i == 0 || it.lastErr != nil {
return ZeroSamplePair, it.lastErr
}
it.pos = i - 1
it.lastValue = model.SamplePair{
Timestamp: it.timestampAtIndex(i - 1),
Value: it.sampleValueAtIndex(i - 1),
}
return it.lastValue, it.lastErr
}
// rangeValues implements chunkIterator.
func (it *doubleDeltaEncodedChunkIterator) rangeValues(in metric.Interval) ([]model.SamplePair, error) {
oldest := sort.Search(it.len, func(i int) bool {
return !it.timestampAtIndex(i).Before(in.OldestInclusive)
})
newest := sort.Search(it.len, func(i int) bool {
return it.timestampAtIndex(i).After(in.NewestInclusive)
})
if oldest == it.len || it.lastErr != nil {
return nil, it.lastErr
}
result := make([]model.SamplePair, 0, newest-oldest)
for i := oldest; i < newest; i++ {
it.pos = i
it.lastValue = model.SamplePair{
Timestamp: it.timestampAtIndex(i),
Value: it.sampleValueAtIndex(i),
}
result = append(result, it.lastValue)
}
return result, it.lastErr
}
// contains implements chunkIterator.
func (it *doubleDeltaEncodedChunkIterator) contains(t model.Time) (bool, error) {
return !t.Before(it.baseT) && !t.After(it.timestampAtIndex(it.len-1)), it.lastErr
}
// scan implements chunkIterator.
func (it *doubleDeltaEncodedChunkIterator) scan() bool {
it.pos++
if it.pos >= it.len {
return false
}
it.lastValue = model.SamplePair{
Timestamp: it.timestampAtIndex(it.pos),
Value: it.sampleValueAtIndex(it.pos),
}
return it.lastErr == nil
}
// value implements chunkIterator.
func (it *doubleDeltaEncodedChunkIterator) value() model.SamplePair {
return it.lastValue
}
// err implements chunkIterator.
func (it *doubleDeltaEncodedChunkIterator) err() error {
return it.lastErr
}
func (it *doubleDeltaEncodedChunkIterator) timestampAtIndex(idx int) model.Time {
if idx == 0 { if idx == 0 {
return it.baseT return acc.baseT
} }
if idx == 1 { if idx == 1 {
// If time bytes are at d8, the time is saved directly rather // If time bytes are at d8, the time is saved directly rather
// than as a difference. // than as a difference.
if it.tBytes == d8 { if acc.tBytes == d8 {
return it.baseΔT return acc.baseΔT
} }
return it.baseT + it.baseΔT return acc.baseT + acc.baseΔT
} }
offset := doubleDeltaHeaderBytes + (idx-2)*int(it.tBytes+it.vBytes) offset := doubleDeltaHeaderBytes + (idx-2)*int(acc.tBytes+acc.vBytes)
switch it.tBytes { switch acc.tBytes {
case d1: case d1:
return it.baseT + return acc.baseT +
model.Time(idx)*it.baseΔT + model.Time(idx)*acc.baseΔT +
model.Time(int8(it.c[offset])) model.Time(int8(acc.c[offset]))
case d2: case d2:
return it.baseT + return acc.baseT +
model.Time(idx)*it.baseΔT + model.Time(idx)*acc.baseΔT +
model.Time(int16(binary.LittleEndian.Uint16(it.c[offset:]))) model.Time(int16(binary.LittleEndian.Uint16(acc.c[offset:])))
case d4: case d4:
return it.baseT + return acc.baseT +
model.Time(idx)*it.baseΔT + model.Time(idx)*acc.baseΔT +
model.Time(int32(binary.LittleEndian.Uint32(it.c[offset:]))) model.Time(int32(binary.LittleEndian.Uint32(acc.c[offset:])))
case d8: case d8:
// Take absolute value for d8. // Take absolute value for d8.
return model.Time(binary.LittleEndian.Uint64(it.c[offset:])) return model.Time(binary.LittleEndian.Uint64(acc.c[offset:]))
default: default:
it.lastErr = fmt.Errorf("invalid number of bytes for time delta: %d", it.tBytes) acc.lastErr = fmt.Errorf("invalid number of bytes for time delta: %d", acc.tBytes)
} }
return model.Earliest return model.Earliest
} }
func (it *doubleDeltaEncodedChunkIterator) sampleValueAtIndex(idx int) model.SampleValue { func (acc *doubleDeltaEncodedIndexAccessor) sampleValueAtIndex(idx int) model.SampleValue {
if idx == 0 { if idx == 0 {
return it.baseV return acc.baseV
} }
if idx == 1 { if idx == 1 {
// If value bytes are at d8, the value is saved directly rather // If value bytes are at d8, the value is saved directly rather
// than as a difference. // than as a difference.
if it.vBytes == d8 { if acc.vBytes == d8 {
return it.baseΔV return acc.baseΔV
} }
return it.baseV + it.baseΔV return acc.baseV + acc.baseΔV
} }
offset := doubleDeltaHeaderBytes + (idx-2)*int(it.tBytes+it.vBytes) + int(it.tBytes) offset := doubleDeltaHeaderBytes + (idx-2)*int(acc.tBytes+acc.vBytes) + int(acc.tBytes)
if it.isInt { if acc.isInt {
switch it.vBytes { switch acc.vBytes {
case d0: case d0:
return it.baseV + return acc.baseV +
model.SampleValue(idx)*it.baseΔV model.SampleValue(idx)*acc.baseΔV
case d1: case d1:
return it.baseV + return acc.baseV +
model.SampleValue(idx)*it.baseΔV + model.SampleValue(idx)*acc.baseΔV +
model.SampleValue(int8(it.c[offset])) model.SampleValue(int8(acc.c[offset]))
case d2: case d2:
return it.baseV + return acc.baseV +
model.SampleValue(idx)*it.baseΔV + model.SampleValue(idx)*acc.baseΔV +
model.SampleValue(int16(binary.LittleEndian.Uint16(it.c[offset:]))) model.SampleValue(int16(binary.LittleEndian.Uint16(acc.c[offset:])))
case d4: case d4:
return it.baseV + return acc.baseV +
model.SampleValue(idx)*it.baseΔV + model.SampleValue(idx)*acc.baseΔV +
model.SampleValue(int32(binary.LittleEndian.Uint32(it.c[offset:]))) model.SampleValue(int32(binary.LittleEndian.Uint32(acc.c[offset:])))
// No d8 for ints. // No d8 for ints.
default: default:
it.lastErr = fmt.Errorf("invalid number of bytes for integer delta: %d", it.vBytes) acc.lastErr = fmt.Errorf("invalid number of bytes for integer delta: %d", acc.vBytes)
} }
} else { } else {
switch it.vBytes { switch acc.vBytes {
case d4: case d4:
return it.baseV + return acc.baseV +
model.SampleValue(idx)*it.baseΔV + model.SampleValue(idx)*acc.baseΔV +
model.SampleValue(math.Float32frombits(binary.LittleEndian.Uint32(it.c[offset:]))) model.SampleValue(math.Float32frombits(binary.LittleEndian.Uint32(acc.c[offset:])))
case d8: case d8:
// Take absolute value for d8. // Take absolute value for d8.
return model.SampleValue(math.Float64frombits(binary.LittleEndian.Uint64(it.c[offset:]))) return model.SampleValue(math.Float64frombits(binary.LittleEndian.Uint64(acc.c[offset:])))
default: default:
it.lastErr = fmt.Errorf("invalid number of bytes for floating point delta: %d", it.vBytes) acc.lastErr = fmt.Errorf("invalid number of bytes for floating point delta: %d", acc.vBytes)
} }
} }
return 0 return 0