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 } // Після відновлення із снапшота func NewValueDeltaCompressor(metricType qb.MetricType, fracDigits byte, buf []byte, payloadSize int) *ValueDeltaCompressor { var coef float64 = 1 if fracDigits > 0 { coef = math.Pow(10, float64(fracDigits)) } s := &ValueDeltaCompressor{ buf: buf, coef: coef, pos: payloadSize, } 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 { // base value на початку u64, _, err := bin.GetVarUint64(s.buf) if err != nil { log.Fatalf("bug: get base value: %s", err) } s.baseValue = float64(u64) / s.coef s.lastDelta, _, err = bin.ReverseGetVarUint64(s.buf[:s.pos-2]) // skip h byte if err != nil { log.Fatalf("bug: get last delta: %s", err) } } return s } // можна виділити буфер максимального розміру, що може бути змінено під час кодування // закодувати на етапі evaluate значення і повернути buf + offset. Причому буфер може бути // спільний на всі metrics // arr - func (s *ValueDeltaCompressor) Evaluate(tmp []byte, value float64) qb.ValueEvaluationReport { var ( delta uint64 offset 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++ offset = 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++ offset = 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++ offset = 1 + n // перезапис пари delta/h } else { tmp[i] = 0 // change literal (length=1) to run (length=2) i++ offset = 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{ Offset: offset, ChangeSize: i, TotalSpace: s.pos + i - offset, Delta: delta, } } // pos завжди вказує на h func (s *ValueDeltaCompressor) Append(offset int, change []byte, value float64, delta uint64) { if s.pos > 0 { s.lastDelta = delta } else { s.baseValue = value } copy(s.buf[s.pos-offset:], change) s.pos += len(change) - offset } func (s *ValueDeltaCompressor) DeleteLast() { } func (s *ValueDeltaCompressor) CaptureState() qb.ValueDeltaCapturedState { // if s.state != nil { // qb.Abort(qb.RepeatableLock, nil) // } // позиція посувається вліво, отже може перескочити на попередній chunk pos := s.pos - 1 - bin.CountVarUint64(s.lastDelta) return qb.ValueDeltaCapturedState{ H: s.buf[s.pos-1], LastDelta: s.lastDelta, Payload: s.buf[:pos], } } // для зростання буфера під час вставки даних. State не цікавить func (s *ValueDeltaCompressor) Size() int { //if s.state == nil { return s.pos // } else { // return bin.CountVarUint64(s.state.LastDelta) + hSize + len(s.state.Payload) // } } // // Snapshot - для створення снапшота. // func (s *ValueDeltaCompressor) Payload() []byte { // if s.state == nil { // return s.buf[:s.pos] // } else { // return s.state.Payload // } // } func (s *ValueDeltaCompressor) WritePayloadTo(w io.Writer, state *qb.ValueDeltaCapturedState) (err error) { if state == nil { _, err = w.Write(s.buf[:s.pos]) return } else { _, err = w.Write(state.Payload) if err != nil { return } _, err = bin.WriteVarUint64(w, state.LastDelta) if err != nil { return } _, err = w.Write([]byte{ state.H, }) return } } func (s *ValueDeltaCompressor) Rotate(newbuf []byte) { // УВАГА! // state не чіпаємо 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 } func (s *ValueDeltaCompressor) CreateDecompressor() qb.ValueDecompressor { if s.pos == 0 { return nil } return NewValueDeltaDecompressorFromState(ValueDeltaDecompressorFromStateOptions{ Coef: s.coef, ToFloat64: s.toFloat64, H: s.buf[s.pos-1], LastDelta: s.lastDelta, Payload: s.buf[:s.pos], }) } // 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(fracDigits byte, buf []byte, toFloat64 func(uint64, float64) float64) *ValueDeltaDecompressor { var coef float64 = 1 if fracDigits > 0 { coef = math.Pow(10, float64(fracDigits)) } u64, n, err := bin.GetVarUint64(buf) if err != nil { log.Fatalf("bug: get base value: %s", err) } s := &ValueDeltaDecompressor{ buf: buf, coef: coef, pos: len(buf), // first free bound: n, toFloat64: toFloat64, } s.baseValue = s.toFloat64(u64, coef) // читаю заголовок наступної серії s.readHeader() s.readValue() return s } type ValueDeltaDecompressorFromStateOptions struct { Coef float64 // (value, coef) => uint64 ToFloat64 func(uint64, float64) float64 H byte LastDelta uint64 Payload []byte } func NewValueDeltaDecompressorFromState(opt ValueDeltaDecompressorFromStateOptions) *ValueDeltaDecompressor { u64, n, err := bin.GetVarUint64(opt.Payload) if err != nil { log.Fatalf("bug: get base value: %s", err) } s := &ValueDeltaDecompressor{ buf: opt.Payload, coef: opt.Coef, pos: len(opt.Payload), // first free bound: n, toFloat64: opt.ToFloat64, } s.baseValue = s.toFloat64(u64, s.coef) s.lastValue = s.baseValue + s.toFloat64(opt.LastDelta, s.coef) s.decodeHeaderByte(opt.H) return s } func (s *ValueDeltaDecompressor) NextValue() (value float64, done bool) { //fmt.Printf("NextValue(): bound: %d, pos: %d, pending: %d\n", s.bound, s.pos, s.pending) if s.done { return 0, true } // метод працює як do while - спочатку значення, а потім перевірка умови 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) */