Files
qb/enc/value_delta.go
2026-06-19 07:25:46 +03:00

446 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package enc
import (
"io"
"log"
"math"
bin "gordenko.dev/dima/bin/little"
"gordenko.dev/dima/qb"
)
const (
tmpValueSize = 19
)
type ValueDeltaCompressor struct {
buf []byte
coef float64
pos int // payload size
baseValue float64
lastDelta uint64
// (baseValue, coef, value) => delta
calcDelta func(float64, float64, float64) uint64
// (value, coef) => uint64
toUint64 func(float64, float64) uint64
// (value, coef) => uint64
toFloat64 func(uint64, float64) float64
state *qb.ValueDeltaCapturedState
}
// Після відновлення із снапшота
func NewValueDeltaCompressor(metricType qb.MetricType, fracDigits byte, buf []byte, payloadSize int) *ValueDeltaCompressor {
s := &ValueDeltaCompressor{
buf: buf,
coef: 1,
pos: payloadSize,
}
if fracDigits > 0 {
s.coef = math.Pow(10, float64(fracDigits))
}
if metricType == qb.Cumulative {
s.calcDelta = calcCumulativeDelta
s.toUint64 = toCumulativeUint64
s.toFloat64 = toCumulativeFloat64
} else {
s.calcDelta = calcInstantDelta
s.toUint64 = toInstantUint64
s.toFloat64 = toInstantFloat64
}
if payloadSize > 0 {
u64, _, err := bin.GetVarUint64(s.buf)
if err != nil {
log.Fatalf("bug: get base value: %s", err)
}
s.baseValue = s.toFloat64(u64, s.coef)
s.lastDelta, _, err = bin.ReverseGetVarUint64(s.buf[:s.pos-1]) // skip h byte
if err != nil {
log.Fatalf("bug: get last delta: %s", err)
}
//fmt.Printf("lastDelta: %d\n", s.lastDelta)
}
return s
}
// можна виділити буфер максимального розміру, що може бути змінено під час кодування
// закодувати на етапі evaluate значення і повернути buf + offset. Причому буфер може бути
// спільний на всі metrics
// arr -
func (s *ValueDeltaCompressor) Evaluate(tmp []byte, value float64) qb.ValueEvaluationReport {
var (
delta uint64
rewindOffset int
i int
)
if s.pos > 0 {
delta = s.calcDelta(s.baseValue, s.coef, value)
h := s.buf[s.pos-1]
if h < 128 {
// run
if delta == s.lastDelta && h < 127 {
// incrementRun
tmp[i] = h + 1
i++
rewindOffset = 1 // перезапис h
} else {
// endSeries
n, _ := bin.ReversePutVarUint64(tmp, delta)
i += n
tmp[i] = 128 // start new literal (length=1)
i++
}
} else {
// literal
if delta != s.lastDelta {
if h < 255 {
// incrementLiteral
n, _ := bin.ReversePutVarUint64(tmp, delta)
i += n
tmp[i] = h + 1
i++
rewindOffset = 1 // перезапис h
} else {
// endSeries
n, _ := bin.ReversePutVarUint64(tmp, delta)
i += n
tmp[i] = 128 // start new literal (length=1)
i++
}
} else {
// startRun
if h > 128 {
tmp[i] = h - 1 // зменшую довжину попередньої серії на 1
i++
n, _ := bin.ReversePutVarUint64(tmp[i:], delta)
i += n
tmp[i] = 0 // start new run (length=2)
i++
rewindOffset = 1 + n // перезапис пари delta/h
} else {
tmp[i] = 0 // change literal (length=1) to run (length=2)
i++
rewindOffset = 1
}
}
}
} else {
n, _ := bin.PutVarUint64(tmp, s.toUint64(value, s.coef))
i += n
n, _ = bin.ReversePutVarUint64(tmp[i:], 0) // delta
i += n
tmp[i] = 128 // start new literal (length=1)
i++
}
return qb.ValueEvaluationReport{
RewindOffset: rewindOffset,
Offset: s.pos - rewindOffset,
ChangeSize: i,
TotalSpace: s.pos - rewindOffset + i,
Delta: delta,
}
}
// pos завжди вказує на h
func (s *ValueDeltaCompressor) Append(rewindOffset int, change []byte, value float64, delta uint64) {
// fmt.Printf("append value:\n")
// fmt.Printf("rewind offset: %d\n", rewindOffset)
// fmt.Printf("change: % x\n", change)
// fmt.Printf("value: %v\n", value)
// fmt.Printf("bufsize: %d\n", len(s.buf))
// fmt.Printf("s.pos: %d\n", s.pos)
//fmt.Printf("delta: %d\n", delta)
if s.pos > 0 {
s.lastDelta = delta
} else {
s.baseValue = value
}
//fmt.Printf("buf before: % x\n", s.buf[:s.pos])
copy(s.buf[s.pos-rewindOffset:], change)
s.pos += len(change) - rewindOffset
//fmt.Printf("v size %d\n", s.pos)
//fmt.Printf("buf after: % x\n", s.buf[:s.pos])
//fmt.Printf("buf after: % x\n", s.buf[:s.pos])
}
func (s *ValueDeltaCompressor) DeleteLast() {
}
func (s *ValueDeltaCompressor) getState() qb.ValueDeltaCapturedState {
if s.pos > 0 {
// fmt.Println("getState")
// fmt.Printf("pos: %d\n", s.pos)
// fmt.Printf("buf: % x\n", s.buf[:s.pos])
// fmt.Printf("h: %d\n", s.buf[s.pos-1])
// fmt.Printf("lastDelta: %d\n", s.lastDelta)
bound := s.pos - 1 - bin.CountVarUint64(s.lastDelta)
return qb.ValueDeltaCapturedState{
H: s.buf[s.pos-1],
LastDelta: s.lastDelta,
Payload: s.buf[:bound],
}
} else {
return qb.ValueDeltaCapturedState{}
}
}
func (s *ValueDeltaCompressor) CaptureState() {
state := s.getState()
s.state = &state
}
func (s *ValueDeltaCompressor) ForgetCapturedState() {
s.state = nil
}
func (s *ValueDeltaCompressor) Tail(offset int) []byte {
return s.buf[offset:s.pos]
}
// для зростання буфера під час вставки даних. State не цікавить
func (s *ValueDeltaCompressor) Size() int {
return s.pos
}
func (s *ValueDeltaCompressor) CommitedSize() int {
if s.state == nil {
return s.pos
} else {
if len(s.state.Payload) > 0 {
return bin.CountVarUint64(uint64(s.state.LastDelta)) + 1 + len(s.state.Payload)
} else {
return 0
}
}
}
// Snapshot - для створення снапшота.
// func (s *ValueDeltaCompressor) Payload() []byte {
// if s.state == nil {
// return s.buf[:s.pos]
// } else {
// return s.state.Payload
// }
// }
func (s *ValueDeltaCompressor) WriteCommitedTo(w io.Writer) (err error) {
if s.state == nil {
_, err = w.Write(s.buf[:s.pos])
return
} else {
_, err = w.Write(s.state.Payload)
if err != nil {
return
}
_, err = bin.WriteVarUint64(w, s.state.LastDelta)
if err != nil {
return
}
_, err = w.Write([]byte{
s.state.H,
})
return
}
}
func (s *ValueDeltaCompressor) ReplaceBuffer(newbuf []byte) {
//fmt.Printf("value replace buffer: new size %d\n", len(newbuf))
s.buf = newbuf
s.pos = 0
s.baseValue = 0
s.lastDelta = 0
}
func (s *ValueDeltaCompressor) LastValue() float64 {
// FIX
// var delta uint64
// if s.state == nil {
// delta = s.lastDelta
// } else {
// delta = s.state.LastDelta
// }
// return s.baseValue + s.toFloat64(delta, s.coef)
return 0
}
// Decompressor читає виключно збережені на диск дані
func (s *ValueDeltaCompressor) CreateDecompressor(metricType qb.MetricType, fracDigits byte) qb.ValueDecompressor {
if s.pos == 0 {
return nil
}
d := NewValueDeltaDecompressor(metricType, fracDigits)
if s.state != nil {
d.RestoreFromState(*s.state)
} else {
d.RestoreFromState(s.getState())
}
return d
}
// DECOMPRESSOR
type ValueDeltaDecompressor struct {
buf []byte
coef float64
pos int
bound int
baseValue float64
lastValue float64
isRun bool
pending int
done bool
// (value, coef) => uint64
toFloat64 func(uint64, float64) float64
}
func NewValueDeltaDecompressor(metricType qb.MetricType, fracDigits byte) *ValueDeltaDecompressor {
s := &ValueDeltaDecompressor{
coef: 1,
}
if fracDigits > 0 {
s.coef = math.Pow(10, float64(fracDigits))
}
if metricType == qb.Cumulative {
s.toFloat64 = toCumulativeFloat64
} else {
s.toFloat64 = toInstantFloat64
}
return s
}
func (s *ValueDeltaDecompressor) RestoreFromEnd(buf []byte) {
s.buf = buf
s.pos = len(buf) // first free
if len(buf) > 0 {
s.readBaseValue()
s.readHeader()
s.readValue()
} else {
s.done = true
}
}
func (s *ValueDeltaDecompressor) RestoreFromState(state qb.ValueDeltaCapturedState) {
//fmt.Printf("%#v\n", state)
s.buf = state.Payload
s.pos = len(s.buf) // first free
if len(s.buf) > 0 {
s.readBaseValue()
s.lastValue = s.baseValue + s.toFloat64(state.LastDelta, s.coef)
s.decodeHeaderByte(state.H)
} else {
s.done = true
}
}
func (s *ValueDeltaDecompressor) readBaseValue() {
u64, n, err := bin.GetVarUint64(s.buf)
if err != nil {
log.Fatalf("bug: get base value: %s", err)
}
s.baseValue = s.toFloat64(u64, s.coef)
s.bound = n
}
func (s *ValueDeltaDecompressor) NextValue() (value float64, done bool) {
//fmt.Printf("v pos: %d of %d\n", s.pos, len(s.buf))
if s.done {
return 0, true
}
value = s.lastValue
s.pending--
if s.pending > 0 {
if !s.isRun {
s.readValue()
}
} else if s.pos > s.bound {
s.readHeader()
s.readValue()
} else {
s.done = true
}
return value, false
}
func (s *ValueDeltaDecompressor) readHeader() {
s.pos--
h := s.buf[s.pos]
s.decodeHeaderByte(h)
}
func (s *ValueDeltaDecompressor) readValue() {
u64, n, err := bin.ReverseGetVarUint64(s.buf[:s.pos])
if err != nil {
log.Fatalln(err)
}
s.pos -= n
s.lastValue = s.baseValue + s.toFloat64(u64, s.coef)
}
func (s *ValueDeltaDecompressor) decodeHeaderByte(h byte) {
s.isRun = h < 128
if s.isRun {
s.pending = int(h) + 2
} else {
s.pending = int(h&127) + 1
}
}
// HELPERS
func calcCumulativeDelta(baseValue float64, coef float64, value float64) uint64 {
return uint64((value-baseValue)*coef + eps)
}
func calcInstantDelta(baseValue float64, coef float64, value float64) uint64 {
f64 := (value - baseValue) * coef
if f64 > 0 {
f64 += eps
} else {
f64 -= eps
}
return bin.EncodeZigZag(int64(f64))
}
func toCumulativeUint64(value float64, coef float64) uint64 {
return uint64(value * coef)
}
func toInstantUint64(value float64, coef float64) uint64 {
return bin.EncodeZigZag(int64(value * coef))
}
func toCumulativeFloat64(value uint64, coef float64) float64 {
return float64(value) / coef
}
func toInstantFloat64(value uint64, coef float64) float64 {
return float64(bin.DecodeZigZag(value)) / coef
}
/*
Формат:
base value (var u64)
delta (var u64)
q (qty of previous deltas; msb=0 - series, msb=1 - non series, low 7 bits - qty 1..128)
delta
delta
delta
q
Дельта рахується від base value.
Декодування у зворотньому порядку.
Після base value слідують run або literal блоки.
Run блок - це delta + header byte в кінці.
Literal блок - це від одної до N дельт + header byte в кінці.
Спочатку створюється literal блок.
Якщо для останної дельти додається дублікат, literal блок модифікується -
лічильник зменшується до 1. А остання дельта переміщюється в новий run блок.
Причому лічильник 0 - означає 2 елементи. Приклад:
До:
v1 v2 v3 h-byte(literal, 3) <- v3
Після:
v1 v2 h-byte(literal, 2) v3 h-byte(run, 2)
*/