package enc import ( "fmt" "io" "log" bin "gordenko.dev/dima/bin/little" "gordenko.dev/dima/qb" ) /* Payload data файла має таку структуру: vvvvvvvv-> <-ttttttt де v - це values, які додаються зліва направо, а читаються справа наліво; t - це timestamps, які додаються справа наліво, а читаються зліва направо. Timestamps читаються звичайними методами Get*, а писатись мають типу ByRightBound */ const ( tmpTimeSize = 7 // max when start run ) type TimeDeltaCompressor struct { buf []byte // останній записаний байт, якщо рахувати справа наліво pos int lastUnixtime uint32 lastDelta uint32 state *qb.TimeDeltaCapturedState } // Початкове створення, коли даних немає // Створення після page filled, коли одразу є початкове значення // Відновлення стану із снапшота. LastTimestamp передаю окермо від payload, // а lastDelta і h треба прочитати із payload (якщо є) // Кодує значення справа наліво функціями TailPut*. Читає значення зліва направо функціями Get* func NewTimeDeltaCompressor(buf []byte, payloadSize int) *TimeDeltaCompressor { s := &TimeDeltaCompressor{ buf: buf, pos: len(buf) - payloadSize, } if payloadSize > 0 { s.restore(payloadSize) } return s } func (s *TimeDeltaCompressor) restore(payloadSize int) { bound := len(s.buf) - 4 since, err := bin.GetUint32(s.buf[bound:]) if err != nil { log.Fatalf("bug: get since: %s", err) } if payloadSize == 4 { s.lastUnixtime = since return } var ( i = s.pos totalDelta uint64 ) for i < bound { h := s.buf[i] i++ if h < 128 { // run count := h + 2 delta, n, _ := bin.GetVarUint64(s.buf[i:]) i += n totalDelta += delta * uint64(count) } else { // literal count := (h & 127) + 1 for range count { delta, n, _ := bin.GetVarUint64(s.buf[i:]) i += n totalDelta += delta } } } u64, _, err := bin.GetVarUint64(s.buf[s.pos+1:]) if err != nil { log.Fatalf("bug: get last delta: %s", err) } s.lastDelta = uint32(u64) s.lastUnixtime = since + uint32(totalDelta) } func (s *TimeDeltaCompressor) Evaluate(tmp []byte, timestamp uint32) qb.TimeEvaluationReport { var ( delta = timestamp - s.lastUnixtime rewindOffset int i int ) if s.pos < len(s.buf) { if s.lastDelta > 0 { h := s.buf[s.pos] if h < 128 { // run if delta == s.lastDelta && h < 127 { // incrementRun tmp[i] = h + 1 i++ rewindOffset = 1 // перезапис h } else { // endSeries n, _ := bin.PutVarUint64(tmp, uint64(delta)) i += n tmp[i] = 128 // start new literal (length=1) i++ } } else { // literal if delta != s.lastDelta { if h < 255 { // incrementLiteral n, _ := bin.PutVarUint64(tmp, uint64(delta)) i += n tmp[i] = h + 1 i++ rewindOffset = 1 // перезапис h } else { // endSeries n, _ := bin.PutVarUint64(tmp, uint64(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.PutVarUint64(tmp[i:], uint64(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 { // add 1st delta n, _ := bin.PutVarUint64(tmp, uint64(delta)) i += n tmp[i] = 128 // start new literal (length=1) i++ } } else { bin.PutUint32(tmp, timestamp) // 1st timestamp (since) i += 4 } return qb.TimeEvaluationReport{ RewindOffset: rewindOffset, Offset: len(s.buf) - s.pos - rewindOffset, ChangeSize: i, TotalSpace: s.pos - rewindOffset + i, } } func (s *TimeDeltaCompressor) Append(rewindOffset int, change []byte, timestamp uint32) { if s.pos < len(s.buf) { i := s.pos + rewindOffset - 1 // -1, because s.pos always points to h byte for _, b := range change { s.buf[i] = b i-- } s.lastDelta = timestamp - s.lastUnixtime } else { copy(s.buf[len(s.buf)-4:], change) // 4b since } s.pos -= len(change) - rewindOffset s.lastUnixtime = timestamp } // func (s *TimeDeltaCompressor) DeleteLast() { // } func (s *TimeDeltaCompressor) getState() qb.TimeDeltaCapturedState { state := qb.TimeDeltaCapturedState{ LastUnixtime: s.lastUnixtime, LastDelta: s.lastDelta, } if s.pos < len(s.buf) { var bound int if s.lastDelta > 0 { state.H = s.buf[s.pos] bound = s.pos + 1 + bin.CountVarUint64(uint64(s.lastDelta)) } else { bound = s.pos // only since encoded } state.Payload = s.buf[bound:] } return state } func (s *TimeDeltaCompressor) CaptureState() { state := s.getState() s.state = &state } func (s *TimeDeltaCompressor) ForgetCapturedState() { s.state = nil } func (s *TimeDeltaCompressor) Tail(offset int) []byte { fmt.Println("time tail:", s.pos, len(s.buf), offset) return s.buf[s.pos : len(s.buf)-offset] } // коли сторінка заповнена і відправляється в txlog, since замінюю на until func (s *TimeDeltaCompressor) ReplaceSinceWithUntil() uint32 { pos := len(s.buf) - 4 since, _ := bin.GetUint32(s.buf[pos:]) bin.PutUint32(s.buf[pos:], s.lastUnixtime) return since } // для зростання буфера під час вставки даних. State не цікавить func (s *TimeDeltaCompressor) Size() int { return len(s.buf) - s.pos } func (s *TimeDeltaCompressor) CommitedSize() int { if s.state == nil { return len(s.buf) - s.pos } else { if len(s.state.Payload) > 0 { if s.state.LastDelta > 0 { return bin.CountVarUint64(uint64(s.state.LastDelta)) + 1 + len(s.state.Payload) } else { return 4 // since } } else { return 0 } } } // Snapshot - для створення снапшота. // FIX - encode firstUnixtime in 1st 4 bytes // // while page not completed - has first unixtime, after - last unixtime // func (s *TimeDeltaCompressor) Payload() []byte { // if s.state == nil { // return s.buf[s.pos:] // } else { // return s.state.Payload // } // } func (s *TimeDeltaCompressor) 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, uint64(s.state.LastDelta)) if err != nil { return } _, err = w.Write([]byte{ s.state.H, }) return } } func (s *TimeDeltaCompressor) ReplaceBuffer(newbuf []byte) { s.buf = newbuf s.pos = len(s.buf) s.lastUnixtime = 0 s.lastDelta = 0 } func (s *TimeDeltaCompressor) CreateDecompressor() qb.TimestampDecompressor { if s.pos == len(s.buf) { return nil } d := NewTimeDeltaDecompressor() if s.state != nil { d.RestoreFromState(*s.state) } else { d.RestoreFromState(s.getState()) } // fmt.Printf("h: % x\n", s.buf[s.pos]) // fmt.Printf("lastUnixtime: % x\n", s.lastUnixtime) // fmt.Printf("lastDelta: % x\n", s.lastDelta) // fmt.Printf("buf: % x\n", s.buf) // fmt.Printf("pay: % x\n", s.buf[bound:]) return d } func (s *TimeDeltaCompressor) LastTimestamp() uint32 { return s.lastUnixtime } // DECOMPRESSOR type TimeDeltaDecompressor struct { buf []byte pos int lastDelta uint32 lastUnixtime uint32 isRun bool pending int done bool } func NewTimeDeltaDecompressor() *TimeDeltaDecompressor { return new(TimeDeltaDecompressor) } // викликається для повних сторінок func (s *TimeDeltaDecompressor) RestoreFromEnd(buf []byte) { s.buf = buf if len(s.buf) > 0 { var err error s.lastUnixtime, err = bin.GetUint32(buf[len(buf)-4:]) if err != nil { log.Fatalf("bug: get last unixtime: %s", err) } if len(buf) > 4 { s.readHeader() s.readDelta() } } else { s.done = true } // fmt.Printf("h: % x\n", s.buf[s.pos]) // fmt.Printf("lastUnixtime: %d\n", s.lastUnixtime) // fmt.Printf("lastDelta: %d\n", s.lastDelta) // fmt.Printf("buf: % x\n", s.buf) } // викликається для data level tail func (s *TimeDeltaDecompressor) RestoreFromState(state qb.TimeDeltaCapturedState) { s.buf = state.Payload if len(s.buf) > 0 { s.lastUnixtime = state.LastUnixtime if state.LastDelta > 0 { s.lastDelta = state.LastDelta s.decodeHeaderByte(state.H) } } else { s.done = true } } func (s *TimeDeltaDecompressor) NextValue() (value uint32, done bool) { if s.done { return 0, true } value = s.lastUnixtime if s.lastDelta > 0 { s.lastUnixtime -= s.lastDelta s.pending-- if s.pending > 0 { if !s.isRun { s.readDelta() } } else if s.pos < len(s.buf)-4 { s.readHeader() s.readDelta() } else { s.lastDelta = 0 } } else { s.done = true } return value, false } func (s *TimeDeltaDecompressor) readHeader() { h := s.buf[s.pos] s.pos++ s.decodeHeaderByte(h) } func (s *TimeDeltaDecompressor) decodeHeaderByte(h byte) { s.isRun = h < 128 if s.isRun { s.pending = int(h) + 2 } else { s.pending = int(h&127) + 1 } } func (s *TimeDeltaDecompressor) readDelta() { u64, n, err := bin.GetVarUint64(s.buf[s.pos:]) if err != nil { log.Fatalln(err) } s.pos += n s.lastDelta = uint32(u64) }