package enc import ( "fmt" "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 + i - rewindOffset, 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("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("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) { 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) { 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) */