2015-01-21 11:07:45 -08:00
|
|
|
// Copyright 2014 The Prometheus Authors
|
2014-09-19 09:18:44 -07:00
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2014-09-16 06:47:24 -07:00
|
|
|
package local
|
2014-06-06 02:55:53 -07:00
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/binary"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"math"
|
|
|
|
|
2015-08-20 08:18:46 -07:00
|
|
|
"github.com/prometheus/common/model"
|
2014-06-06 02:55:53 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
// The 21-byte header of a delta-encoded chunk looks like:
|
|
|
|
//
|
|
|
|
// - time delta bytes: 1 bytes
|
|
|
|
// - value delta bytes: 1 bytes
|
|
|
|
// - is integer: 1 byte
|
|
|
|
// - base time: 8 bytes
|
|
|
|
// - base value: 8 bytes
|
|
|
|
// - used buf bytes: 2 bytes
|
|
|
|
const (
|
|
|
|
deltaHeaderBytes = 21
|
|
|
|
|
|
|
|
deltaHeaderTimeBytesOffset = 0
|
|
|
|
deltaHeaderValueBytesOffset = 1
|
|
|
|
deltaHeaderIsIntOffset = 2
|
|
|
|
deltaHeaderBaseTimeOffset = 3
|
|
|
|
deltaHeaderBaseValueOffset = 11
|
|
|
|
deltaHeaderBufLenOffset = 19
|
|
|
|
)
|
|
|
|
|
2014-08-19 09:14:44 -07:00
|
|
|
// A deltaEncodedChunk adaptively stores sample timestamps and values with a
|
2015-03-04 04:40:18 -08:00
|
|
|
// delta encoding of various types (int, float) and bit widths. However, once 8
|
2014-08-19 09:14:44 -07:00
|
|
|
// bytes would be needed to encode a delta value, a fall-back to the absolute
|
|
|
|
// numbers happens (so that timestamps are saved directly as int64 and values as
|
2014-09-16 06:47:24 -07:00
|
|
|
// float64). It implements the chunk interface.
|
2015-03-04 04:40:18 -08:00
|
|
|
type deltaEncodedChunk []byte
|
2014-06-06 02:55:53 -07:00
|
|
|
|
2014-09-16 06:47:24 -07:00
|
|
|
// newDeltaEncodedChunk returns a newly allocated deltaEncodedChunk.
|
2015-03-04 04:40:18 -08:00
|
|
|
func newDeltaEncodedChunk(tb, vb deltaBytes, isInt bool, length int) *deltaEncodedChunk {
|
|
|
|
if tb < 1 {
|
|
|
|
panic("need at least 1 time delta byte")
|
|
|
|
}
|
|
|
|
if length < deltaHeaderBytes+16 {
|
|
|
|
panic(fmt.Errorf(
|
|
|
|
"chunk length %d bytes is insufficient, need at least %d",
|
|
|
|
length, deltaHeaderBytes+16,
|
|
|
|
))
|
|
|
|
}
|
|
|
|
c := make(deltaEncodedChunk, deltaHeaderIsIntOffset+1, length)
|
2014-06-06 02:55:53 -07:00
|
|
|
|
2015-03-04 04:40:18 -08:00
|
|
|
c[deltaHeaderTimeBytesOffset] = byte(tb)
|
|
|
|
c[deltaHeaderValueBytesOffset] = byte(vb)
|
2014-08-19 09:14:44 -07:00
|
|
|
if vb < d8 && isInt { // Only use int for fewer than 8 value delta bytes.
|
2015-03-04 04:40:18 -08:00
|
|
|
c[deltaHeaderIsIntOffset] = 1
|
2014-06-06 02:55:53 -07:00
|
|
|
} else {
|
2015-03-04 04:40:18 -08:00
|
|
|
c[deltaHeaderIsIntOffset] = 0
|
2014-06-06 02:55:53 -07:00
|
|
|
}
|
|
|
|
|
2015-03-04 04:40:18 -08:00
|
|
|
return &c
|
2014-06-06 02:55:53 -07:00
|
|
|
}
|
|
|
|
|
2014-09-16 06:47:24 -07:00
|
|
|
// add implements chunk.
|
Handle errors caused by data corruption more gracefully
This requires all the panic calls upon unexpected data to be converted
into errors returned. This pollute the function signatures quite
lot. Well, this is Go...
The ideas behind this are the following:
- panic only if it's a programming error. Data corruptions happen, and
they are not programming errors.
- If we detect a data corruption, we "quarantine" the series,
essentially removing it from the database and putting its data into
a separate directory for forensics.
- Failure during writing to a series file is not considered corruption
automatically. It will call setDirty, though, so that a
crashrecovery upon the next restart will commence and check for
that.
- Series quarantining and setDirty calls are logged and counted in
metrics, but are hidden from the user of the interfaces in
interface.go, whith the notable exception of Append(). The reasoning
is that we treat corruption by removing the corrupted series, i.e. a
query for it will return no results on its next call anyway, so
return no results right now. In the case of Append(), we want to
tell the user that no data has been appended, though.
Minor side effects:
- Now consistently using filepath.* instead of path.*.
- Introduced structured logging where I touched it. This makes things
less consistent, but a complete change to structured logging would
be out of scope for this PR.
2016-02-25 03:23:42 -08:00
|
|
|
func (c deltaEncodedChunk) add(s model.SamplePair) ([]chunk, error) {
|
2016-03-12 12:34:51 -08:00
|
|
|
// TODO(beorn7): Since we return &c, this method might cause an unnecessary allocation.
|
2015-03-06 03:53:00 -08:00
|
|
|
if c.len() == 0 {
|
2015-03-04 04:40:18 -08:00
|
|
|
c = c[:deltaHeaderBytes]
|
|
|
|
binary.LittleEndian.PutUint64(c[deltaHeaderBaseTimeOffset:], uint64(s.Timestamp))
|
|
|
|
binary.LittleEndian.PutUint64(c[deltaHeaderBaseValueOffset:], math.Float64bits(float64(s.Value)))
|
2014-06-06 02:55:53 -07:00
|
|
|
}
|
|
|
|
|
2015-03-04 04:40:18 -08:00
|
|
|
remainingBytes := cap(c) - len(c)
|
2014-06-06 02:55:53 -07:00
|
|
|
sampleSize := c.sampleSize()
|
|
|
|
|
|
|
|
// Do we generally have space for another sample in this chunk? If not,
|
2014-08-19 09:14:44 -07:00
|
|
|
// overflow into a new one.
|
2014-06-06 02:55:53 -07:00
|
|
|
if remainingBytes < sampleSize {
|
2016-03-12 12:34:51 -08:00
|
|
|
return addToOverflowChunk(&c, s)
|
2014-06-06 02:55:53 -07:00
|
|
|
}
|
|
|
|
|
2015-03-06 07:03:03 -08:00
|
|
|
baseValue := c.baseValue()
|
2014-06-06 02:55:53 -07:00
|
|
|
dt := s.Timestamp - c.baseTime()
|
2015-07-13 12:12:27 -07:00
|
|
|
if dt < 0 {
|
Handle errors caused by data corruption more gracefully
This requires all the panic calls upon unexpected data to be converted
into errors returned. This pollute the function signatures quite
lot. Well, this is Go...
The ideas behind this are the following:
- panic only if it's a programming error. Data corruptions happen, and
they are not programming errors.
- If we detect a data corruption, we "quarantine" the series,
essentially removing it from the database and putting its data into
a separate directory for forensics.
- Failure during writing to a series file is not considered corruption
automatically. It will call setDirty, though, so that a
crashrecovery upon the next restart will commence and check for
that.
- Series quarantining and setDirty calls are logged and counted in
metrics, but are hidden from the user of the interfaces in
interface.go, whith the notable exception of Append(). The reasoning
is that we treat corruption by removing the corrupted series, i.e. a
query for it will return no results on its next call anyway, so
return no results right now. In the case of Append(), we want to
tell the user that no data has been appended, though.
Minor side effects:
- Now consistently using filepath.* instead of path.*.
- Introduced structured logging where I touched it. This makes things
less consistent, but a complete change to structured logging would
be out of scope for this PR.
2016-02-25 03:23:42 -08:00
|
|
|
return nil, fmt.Errorf("time delta is less than zero: %v", dt)
|
2015-07-13 12:12:27 -07:00
|
|
|
}
|
|
|
|
|
2015-03-06 07:03:03 -08:00
|
|
|
dv := s.Value - baseValue
|
2014-08-19 09:14:44 -07:00
|
|
|
tb := c.timeBytes()
|
|
|
|
vb := c.valueBytes()
|
2015-03-13 07:49:07 -07:00
|
|
|
isInt := c.isInt()
|
2014-06-06 02:55:53 -07:00
|
|
|
|
|
|
|
// If the new sample is incompatible with the current encoding, reencode the
|
|
|
|
// existing chunk data into new chunk(s).
|
2015-03-06 07:03:03 -08:00
|
|
|
|
2015-03-13 07:49:07 -07:00
|
|
|
ntb, nvb, nInt := tb, vb, isInt
|
|
|
|
if isInt && !isInt64(dv) {
|
|
|
|
// int->float.
|
|
|
|
nvb = d4
|
|
|
|
nInt = false
|
2015-08-20 08:18:46 -07:00
|
|
|
} else if !isInt && vb == d4 && baseValue+model.SampleValue(float32(dv)) != s.Value {
|
2015-03-13 07:49:07 -07:00
|
|
|
// float32->float64.
|
|
|
|
nvb = d8
|
|
|
|
} else {
|
|
|
|
if tb < d8 {
|
|
|
|
// Maybe more bytes for timestamp.
|
|
|
|
ntb = max(tb, bytesNeededForUnsignedTimestampDelta(dt))
|
|
|
|
}
|
|
|
|
if c.isInt() && vb < d8 {
|
|
|
|
// Maybe more bytes for sample value.
|
|
|
|
nvb = max(vb, bytesNeededForIntegerSampleValueDelta(dv))
|
|
|
|
}
|
2015-03-06 07:03:03 -08:00
|
|
|
}
|
2015-03-13 07:49:07 -07:00
|
|
|
if tb != ntb || vb != nvb || isInt != nInt {
|
|
|
|
if len(c)*2 < cap(c) {
|
|
|
|
return transcodeAndAdd(newDeltaEncodedChunk(ntb, nvb, nInt, cap(c)), &c, s)
|
|
|
|
}
|
|
|
|
// Chunk is already half full. Better create a new one and save the transcoding efforts.
|
2016-03-12 12:34:51 -08:00
|
|
|
return addToOverflowChunk(&c, s)
|
2014-06-06 02:55:53 -07:00
|
|
|
}
|
2015-03-13 07:49:07 -07:00
|
|
|
|
2015-03-04 04:40:18 -08:00
|
|
|
offset := len(c)
|
|
|
|
c = c[:offset+sampleSize]
|
2014-06-06 02:55:53 -07:00
|
|
|
|
2014-08-19 09:14:44 -07:00
|
|
|
switch tb {
|
|
|
|
case d1:
|
2015-03-04 04:40:18 -08:00
|
|
|
c[offset] = byte(dt)
|
2014-08-19 09:14:44 -07:00
|
|
|
case d2:
|
2015-03-04 04:40:18 -08:00
|
|
|
binary.LittleEndian.PutUint16(c[offset:], uint16(dt))
|
2014-08-19 09:14:44 -07:00
|
|
|
case d4:
|
2015-03-04 04:40:18 -08:00
|
|
|
binary.LittleEndian.PutUint32(c[offset:], uint32(dt))
|
2014-08-19 09:14:44 -07:00
|
|
|
case d8:
|
|
|
|
// Store the absolute value (no delta) in case of d8.
|
2015-03-04 04:40:18 -08:00
|
|
|
binary.LittleEndian.PutUint64(c[offset:], uint64(s.Timestamp))
|
2014-08-19 09:14:44 -07:00
|
|
|
default:
|
Handle errors caused by data corruption more gracefully
This requires all the panic calls upon unexpected data to be converted
into errors returned. This pollute the function signatures quite
lot. Well, this is Go...
The ideas behind this are the following:
- panic only if it's a programming error. Data corruptions happen, and
they are not programming errors.
- If we detect a data corruption, we "quarantine" the series,
essentially removing it from the database and putting its data into
a separate directory for forensics.
- Failure during writing to a series file is not considered corruption
automatically. It will call setDirty, though, so that a
crashrecovery upon the next restart will commence and check for
that.
- Series quarantining and setDirty calls are logged and counted in
metrics, but are hidden from the user of the interfaces in
interface.go, whith the notable exception of Append(). The reasoning
is that we treat corruption by removing the corrupted series, i.e. a
query for it will return no results on its next call anyway, so
return no results right now. In the case of Append(), we want to
tell the user that no data has been appended, though.
Minor side effects:
- Now consistently using filepath.* instead of path.*.
- Introduced structured logging where I touched it. This makes things
less consistent, but a complete change to structured logging would
be out of scope for this PR.
2016-02-25 03:23:42 -08:00
|
|
|
return nil, fmt.Errorf("invalid number of bytes for time delta: %d", tb)
|
2014-06-06 02:55:53 -07:00
|
|
|
}
|
|
|
|
|
2014-08-19 09:14:44 -07:00
|
|
|
offset += int(tb)
|
2014-06-06 02:55:53 -07:00
|
|
|
|
|
|
|
if c.isInt() {
|
2014-08-19 09:14:44 -07:00
|
|
|
switch vb {
|
|
|
|
case d0:
|
2014-06-06 02:55:53 -07:00
|
|
|
// No-op. Constant value is stored as base value.
|
2014-08-19 09:14:44 -07:00
|
|
|
case d1:
|
2015-07-13 02:19:11 -07:00
|
|
|
c[offset] = byte(int8(dv))
|
2014-08-19 09:14:44 -07:00
|
|
|
case d2:
|
2015-07-13 02:19:11 -07:00
|
|
|
binary.LittleEndian.PutUint16(c[offset:], uint16(int16(dv)))
|
2014-08-19 09:14:44 -07:00
|
|
|
case d4:
|
2015-07-13 02:19:11 -07:00
|
|
|
binary.LittleEndian.PutUint32(c[offset:], uint32(int32(dv)))
|
2014-08-19 09:14:44 -07:00
|
|
|
// d8 must not happen. Those samples are encoded as float64.
|
2014-06-06 02:55:53 -07:00
|
|
|
default:
|
Handle errors caused by data corruption more gracefully
This requires all the panic calls upon unexpected data to be converted
into errors returned. This pollute the function signatures quite
lot. Well, this is Go...
The ideas behind this are the following:
- panic only if it's a programming error. Data corruptions happen, and
they are not programming errors.
- If we detect a data corruption, we "quarantine" the series,
essentially removing it from the database and putting its data into
a separate directory for forensics.
- Failure during writing to a series file is not considered corruption
automatically. It will call setDirty, though, so that a
crashrecovery upon the next restart will commence and check for
that.
- Series quarantining and setDirty calls are logged and counted in
metrics, but are hidden from the user of the interfaces in
interface.go, whith the notable exception of Append(). The reasoning
is that we treat corruption by removing the corrupted series, i.e. a
query for it will return no results on its next call anyway, so
return no results right now. In the case of Append(), we want to
tell the user that no data has been appended, though.
Minor side effects:
- Now consistently using filepath.* instead of path.*.
- Introduced structured logging where I touched it. This makes things
less consistent, but a complete change to structured logging would
be out of scope for this PR.
2016-02-25 03:23:42 -08:00
|
|
|
return nil, fmt.Errorf("invalid number of bytes for integer delta: %d", vb)
|
2014-06-06 02:55:53 -07:00
|
|
|
}
|
|
|
|
} else {
|
2014-08-19 09:14:44 -07:00
|
|
|
switch vb {
|
|
|
|
case d4:
|
2015-03-04 04:40:18 -08:00
|
|
|
binary.LittleEndian.PutUint32(c[offset:], math.Float32bits(float32(dv)))
|
2014-08-19 09:14:44 -07:00
|
|
|
case d8:
|
|
|
|
// Store the absolute value (no delta) in case of d8.
|
2015-03-04 04:40:18 -08:00
|
|
|
binary.LittleEndian.PutUint64(c[offset:], math.Float64bits(float64(s.Value)))
|
2014-06-06 02:55:53 -07:00
|
|
|
default:
|
Handle errors caused by data corruption more gracefully
This requires all the panic calls upon unexpected data to be converted
into errors returned. This pollute the function signatures quite
lot. Well, this is Go...
The ideas behind this are the following:
- panic only if it's a programming error. Data corruptions happen, and
they are not programming errors.
- If we detect a data corruption, we "quarantine" the series,
essentially removing it from the database and putting its data into
a separate directory for forensics.
- Failure during writing to a series file is not considered corruption
automatically. It will call setDirty, though, so that a
crashrecovery upon the next restart will commence and check for
that.
- Series quarantining and setDirty calls are logged and counted in
metrics, but are hidden from the user of the interfaces in
interface.go, whith the notable exception of Append(). The reasoning
is that we treat corruption by removing the corrupted series, i.e. a
query for it will return no results on its next call anyway, so
return no results right now. In the case of Append(), we want to
tell the user that no data has been appended, though.
Minor side effects:
- Now consistently using filepath.* instead of path.*.
- Introduced structured logging where I touched it. This makes things
less consistent, but a complete change to structured logging would
be out of scope for this PR.
2016-02-25 03:23:42 -08:00
|
|
|
return nil, fmt.Errorf("invalid number of bytes for floating point delta: %d", vb)
|
2014-06-06 02:55:53 -07:00
|
|
|
}
|
|
|
|
}
|
Handle errors caused by data corruption more gracefully
This requires all the panic calls upon unexpected data to be converted
into errors returned. This pollute the function signatures quite
lot. Well, this is Go...
The ideas behind this are the following:
- panic only if it's a programming error. Data corruptions happen, and
they are not programming errors.
- If we detect a data corruption, we "quarantine" the series,
essentially removing it from the database and putting its data into
a separate directory for forensics.
- Failure during writing to a series file is not considered corruption
automatically. It will call setDirty, though, so that a
crashrecovery upon the next restart will commence and check for
that.
- Series quarantining and setDirty calls are logged and counted in
metrics, but are hidden from the user of the interfaces in
interface.go, whith the notable exception of Append(). The reasoning
is that we treat corruption by removing the corrupted series, i.e. a
query for it will return no results on its next call anyway, so
return no results right now. In the case of Append(), we want to
tell the user that no data has been appended, though.
Minor side effects:
- Now consistently using filepath.* instead of path.*.
- Introduced structured logging where I touched it. This makes things
less consistent, but a complete change to structured logging would
be out of scope for this PR.
2016-02-25 03:23:42 -08:00
|
|
|
return []chunk{&c}, nil
|
2014-06-06 02:55:53 -07:00
|
|
|
}
|
|
|
|
|
2015-03-13 07:49:07 -07:00
|
|
|
// clone implements chunk.
|
|
|
|
func (c deltaEncodedChunk) clone() chunk {
|
|
|
|
clone := make(deltaEncodedChunk, len(c), cap(c))
|
|
|
|
copy(clone, c)
|
|
|
|
return &clone
|
2014-06-06 02:55:53 -07:00
|
|
|
}
|
|
|
|
|
2015-03-13 07:49:07 -07:00
|
|
|
// firstTime implements chunk.
|
2015-08-20 08:18:46 -07:00
|
|
|
func (c deltaEncodedChunk) firstTime() model.Time {
|
2015-04-14 04:46:38 -07:00
|
|
|
return c.baseTime()
|
2015-03-13 07:49:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// newIterator implements chunk.
|
|
|
|
func (c *deltaEncodedChunk) newIterator() chunkIterator {
|
2016-03-07 11:23:14 -08:00
|
|
|
return newIndexAccessingChunkIterator(c.len(), &deltaEncodedIndexAccessor{
|
|
|
|
c: *c,
|
|
|
|
baseT: c.baseTime(),
|
|
|
|
baseV: c.baseValue(),
|
|
|
|
tBytes: c.timeBytes(),
|
|
|
|
vBytes: c.valueBytes(),
|
|
|
|
isInt: c.isInt(),
|
|
|
|
})
|
2015-03-13 07:49:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// marshal implements chunk.
|
|
|
|
func (c deltaEncodedChunk) marshal(w io.Writer) error {
|
|
|
|
if len(c) > math.MaxUint16 {
|
|
|
|
panic("chunk buffer length would overflow a 16 bit uint.")
|
|
|
|
}
|
|
|
|
binary.LittleEndian.PutUint16(c[deltaHeaderBufLenOffset:], uint16(len(c)))
|
|
|
|
|
|
|
|
n, err := w.Write(c[:cap(c)])
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if n != cap(c) {
|
2016-01-25 07:36:36 -08:00
|
|
|
return fmt.Errorf("wanted to write %d bytes, wrote %d", cap(c), n)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// marshalToBuf implements chunk.
|
|
|
|
func (c deltaEncodedChunk) marshalToBuf(buf []byte) error {
|
|
|
|
if len(c) > math.MaxUint16 {
|
|
|
|
panic("chunk buffer length would overflow a 16 bit uint")
|
|
|
|
}
|
|
|
|
binary.LittleEndian.PutUint16(c[deltaHeaderBufLenOffset:], uint16(len(c)))
|
|
|
|
|
|
|
|
n := copy(buf, c)
|
|
|
|
if n != len(c) {
|
|
|
|
return fmt.Errorf("wanted to copy %d bytes to buffer, copied %d", len(c), n)
|
2015-03-13 07:49:07 -07:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// unmarshal implements chunk.
|
|
|
|
func (c *deltaEncodedChunk) unmarshal(r io.Reader) error {
|
|
|
|
*c = (*c)[:cap(*c)]
|
2015-04-13 11:20:26 -07:00
|
|
|
if _, err := io.ReadFull(r, *c); err != nil {
|
|
|
|
return err
|
2015-03-13 07:49:07 -07:00
|
|
|
}
|
Handle errors caused by data corruption more gracefully
This requires all the panic calls upon unexpected data to be converted
into errors returned. This pollute the function signatures quite
lot. Well, this is Go...
The ideas behind this are the following:
- panic only if it's a programming error. Data corruptions happen, and
they are not programming errors.
- If we detect a data corruption, we "quarantine" the series,
essentially removing it from the database and putting its data into
a separate directory for forensics.
- Failure during writing to a series file is not considered corruption
automatically. It will call setDirty, though, so that a
crashrecovery upon the next restart will commence and check for
that.
- Series quarantining and setDirty calls are logged and counted in
metrics, but are hidden from the user of the interfaces in
interface.go, whith the notable exception of Append(). The reasoning
is that we treat corruption by removing the corrupted series, i.e. a
query for it will return no results on its next call anyway, so
return no results right now. In the case of Append(), we want to
tell the user that no data has been appended, though.
Minor side effects:
- Now consistently using filepath.* instead of path.*.
- Introduced structured logging where I touched it. This makes things
less consistent, but a complete change to structured logging would
be out of scope for this PR.
2016-02-25 03:23:42 -08:00
|
|
|
l := binary.LittleEndian.Uint16((*c)[deltaHeaderBufLenOffset:])
|
|
|
|
if int(l) > cap(*c) {
|
|
|
|
return fmt.Errorf("chunk length exceeded during unmarshaling: %d", l)
|
|
|
|
}
|
|
|
|
*c = (*c)[:l]
|
2015-03-13 07:49:07 -07:00
|
|
|
return nil
|
2014-06-06 02:55:53 -07:00
|
|
|
}
|
|
|
|
|
2015-04-13 11:20:26 -07:00
|
|
|
// unmarshalFromBuf implements chunk.
|
Handle errors caused by data corruption more gracefully
This requires all the panic calls upon unexpected data to be converted
into errors returned. This pollute the function signatures quite
lot. Well, this is Go...
The ideas behind this are the following:
- panic only if it's a programming error. Data corruptions happen, and
they are not programming errors.
- If we detect a data corruption, we "quarantine" the series,
essentially removing it from the database and putting its data into
a separate directory for forensics.
- Failure during writing to a series file is not considered corruption
automatically. It will call setDirty, though, so that a
crashrecovery upon the next restart will commence and check for
that.
- Series quarantining and setDirty calls are logged and counted in
metrics, but are hidden from the user of the interfaces in
interface.go, whith the notable exception of Append(). The reasoning
is that we treat corruption by removing the corrupted series, i.e. a
query for it will return no results on its next call anyway, so
return no results right now. In the case of Append(), we want to
tell the user that no data has been appended, though.
Minor side effects:
- Now consistently using filepath.* instead of path.*.
- Introduced structured logging where I touched it. This makes things
less consistent, but a complete change to structured logging would
be out of scope for this PR.
2016-02-25 03:23:42 -08:00
|
|
|
func (c *deltaEncodedChunk) unmarshalFromBuf(buf []byte) error {
|
2015-04-13 11:20:26 -07:00
|
|
|
*c = (*c)[:cap(*c)]
|
|
|
|
copy(*c, buf)
|
Handle errors caused by data corruption more gracefully
This requires all the panic calls upon unexpected data to be converted
into errors returned. This pollute the function signatures quite
lot. Well, this is Go...
The ideas behind this are the following:
- panic only if it's a programming error. Data corruptions happen, and
they are not programming errors.
- If we detect a data corruption, we "quarantine" the series,
essentially removing it from the database and putting its data into
a separate directory for forensics.
- Failure during writing to a series file is not considered corruption
automatically. It will call setDirty, though, so that a
crashrecovery upon the next restart will commence and check for
that.
- Series quarantining and setDirty calls are logged and counted in
metrics, but are hidden from the user of the interfaces in
interface.go, whith the notable exception of Append(). The reasoning
is that we treat corruption by removing the corrupted series, i.e. a
query for it will return no results on its next call anyway, so
return no results right now. In the case of Append(), we want to
tell the user that no data has been appended, though.
Minor side effects:
- Now consistently using filepath.* instead of path.*.
- Introduced structured logging where I touched it. This makes things
less consistent, but a complete change to structured logging would
be out of scope for this PR.
2016-02-25 03:23:42 -08:00
|
|
|
l := binary.LittleEndian.Uint16((*c)[deltaHeaderBufLenOffset:])
|
|
|
|
if int(l) > cap(*c) {
|
|
|
|
return fmt.Errorf("chunk length exceeded during unmarshaling: %d", l)
|
|
|
|
}
|
|
|
|
*c = (*c)[:l]
|
|
|
|
return nil
|
2015-04-13 11:20:26 -07:00
|
|
|
}
|
|
|
|
|
2015-03-13 07:49:07 -07:00
|
|
|
// encoding implements chunk.
|
|
|
|
func (c deltaEncodedChunk) encoding() chunkEncoding { return delta }
|
|
|
|
|
|
|
|
func (c deltaEncodedChunk) timeBytes() deltaBytes {
|
|
|
|
return deltaBytes(c[deltaHeaderTimeBytesOffset])
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c deltaEncodedChunk) valueBytes() deltaBytes {
|
|
|
|
return deltaBytes(c[deltaHeaderValueBytesOffset])
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c deltaEncodedChunk) isInt() bool {
|
|
|
|
return c[deltaHeaderIsIntOffset] == 1
|
|
|
|
}
|
|
|
|
|
2015-08-20 08:18:46 -07:00
|
|
|
func (c deltaEncodedChunk) baseTime() model.Time {
|
|
|
|
return model.Time(binary.LittleEndian.Uint64(c[deltaHeaderBaseTimeOffset:]))
|
2015-03-13 07:49:07 -07:00
|
|
|
}
|
|
|
|
|
2015-08-20 08:18:46 -07:00
|
|
|
func (c deltaEncodedChunk) baseValue() model.SampleValue {
|
|
|
|
return model.SampleValue(math.Float64frombits(binary.LittleEndian.Uint64(c[deltaHeaderBaseValueOffset:])))
|
2015-03-13 07:49:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c deltaEncodedChunk) sampleSize() int {
|
|
|
|
return int(c.timeBytes() + c.valueBytes())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c deltaEncodedChunk) len() int {
|
|
|
|
if len(c) < deltaHeaderBytes {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return (len(c) - deltaHeaderBytes) / c.sampleSize()
|
|
|
|
}
|
|
|
|
|
2016-03-07 11:23:14 -08:00
|
|
|
// deltaEncodedIndexAccessor implements indexAccessor.
|
|
|
|
type deltaEncodedIndexAccessor struct {
|
2015-04-14 04:46:38 -07:00
|
|
|
c deltaEncodedChunk
|
2015-08-20 08:18:46 -07:00
|
|
|
baseT model.Time
|
|
|
|
baseV model.SampleValue
|
2015-04-14 04:46:38 -07:00
|
|
|
tBytes, vBytes deltaBytes
|
|
|
|
isInt bool
|
2016-03-07 10:50:13 -08:00
|
|
|
lastErr error
|
2014-06-06 02:55:53 -07:00
|
|
|
}
|
|
|
|
|
2016-03-07 11:23:14 -08:00
|
|
|
func (acc *deltaEncodedIndexAccessor) err() error {
|
|
|
|
return acc.lastErr
|
2016-03-07 10:50:13 -08:00
|
|
|
}
|
|
|
|
|
2016-03-07 11:23:14 -08:00
|
|
|
func (acc *deltaEncodedIndexAccessor) timestampAtIndex(idx int) model.Time {
|
|
|
|
offset := deltaHeaderBytes + idx*int(acc.tBytes+acc.vBytes)
|
2015-04-14 04:46:38 -07:00
|
|
|
|
2016-03-07 11:23:14 -08:00
|
|
|
switch acc.tBytes {
|
2015-04-14 04:46:38 -07:00
|
|
|
case d1:
|
2016-03-07 11:23:14 -08:00
|
|
|
return acc.baseT + model.Time(uint8(acc.c[offset]))
|
2015-04-14 04:46:38 -07:00
|
|
|
case d2:
|
2016-03-07 11:23:14 -08:00
|
|
|
return acc.baseT + model.Time(binary.LittleEndian.Uint16(acc.c[offset:]))
|
2015-04-14 04:46:38 -07:00
|
|
|
case d4:
|
2016-03-07 11:23:14 -08:00
|
|
|
return acc.baseT + model.Time(binary.LittleEndian.Uint32(acc.c[offset:]))
|
2015-04-14 04:46:38 -07:00
|
|
|
case d8:
|
|
|
|
// Take absolute value for d8.
|
2016-03-07 11:23:14 -08:00
|
|
|
return model.Time(binary.LittleEndian.Uint64(acc.c[offset:]))
|
2015-04-14 04:46:38 -07:00
|
|
|
default:
|
2016-03-07 11:23:14 -08:00
|
|
|
acc.lastErr = fmt.Errorf("invalid number of bytes for time delta: %d", acc.tBytes)
|
2016-03-17 06:37:24 -07:00
|
|
|
return model.Earliest
|
2015-04-14 04:46:38 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-07 11:23:14 -08:00
|
|
|
func (acc *deltaEncodedIndexAccessor) sampleValueAtIndex(idx int) model.SampleValue {
|
|
|
|
offset := deltaHeaderBytes + idx*int(acc.tBytes+acc.vBytes) + int(acc.tBytes)
|
2015-04-14 04:46:38 -07:00
|
|
|
|
2016-03-07 11:23:14 -08:00
|
|
|
if acc.isInt {
|
|
|
|
switch acc.vBytes {
|
2015-04-14 04:46:38 -07:00
|
|
|
case d0:
|
2016-03-07 11:23:14 -08:00
|
|
|
return acc.baseV
|
2015-04-14 04:46:38 -07:00
|
|
|
case d1:
|
2016-03-07 11:23:14 -08:00
|
|
|
return acc.baseV + model.SampleValue(int8(acc.c[offset]))
|
2015-04-14 04:46:38 -07:00
|
|
|
case d2:
|
2016-03-07 11:23:14 -08:00
|
|
|
return acc.baseV + model.SampleValue(int16(binary.LittleEndian.Uint16(acc.c[offset:])))
|
2015-04-14 04:46:38 -07:00
|
|
|
case d4:
|
2016-03-07 11:23:14 -08:00
|
|
|
return acc.baseV + model.SampleValue(int32(binary.LittleEndian.Uint32(acc.c[offset:])))
|
2015-04-14 04:46:38 -07:00
|
|
|
// No d8 for ints.
|
|
|
|
default:
|
2016-03-07 11:23:14 -08:00
|
|
|
acc.lastErr = fmt.Errorf("invalid number of bytes for integer delta: %d", acc.vBytes)
|
2016-03-17 06:43:00 -07:00
|
|
|
return 0
|
2015-04-14 04:46:38 -07:00
|
|
|
}
|
|
|
|
} else {
|
2016-03-07 11:23:14 -08:00
|
|
|
switch acc.vBytes {
|
2015-04-14 04:46:38 -07:00
|
|
|
case d4:
|
2016-03-07 11:23:14 -08:00
|
|
|
return acc.baseV + model.SampleValue(math.Float32frombits(binary.LittleEndian.Uint32(acc.c[offset:])))
|
2015-04-14 04:46:38 -07:00
|
|
|
case d8:
|
|
|
|
// Take absolute value for d8.
|
2016-03-07 11:23:14 -08:00
|
|
|
return model.SampleValue(math.Float64frombits(binary.LittleEndian.Uint64(acc.c[offset:])))
|
2015-04-14 04:46:38 -07:00
|
|
|
default:
|
2016-03-07 11:23:14 -08:00
|
|
|
acc.lastErr = fmt.Errorf("invalid number of bytes for floating point delta: %d", acc.vBytes)
|
2016-03-17 06:37:24 -07:00
|
|
|
return 0
|
2015-04-14 04:46:38 -07:00
|
|
|
}
|
|
|
|
}
|
2014-06-06 02:55:53 -07:00
|
|
|
}
|