package enc import ( "io" "log" bin "gordenko.dev/dima/bin/little" "gordenko.dev/dima/qb" ) /* Payload data файла має таку структуру: vvvvvvvv-> <-ttttttt де v - це values, які додаються зліва направо, а читаються справа наліво; t - це timestamps, які додаються справа наліво, а читаються зліва направо. Timestamps читаються звичайними методами Get*, а писатись мають типу ByRightBound */ type TimeDeltaCompressor struct { buf []byte // останній записаний байт, якщо рахувати справа наліво pos int lastUnixtime uint32 lastDelta uint32 h byte state *TimeDeltaCapturedState } // Початкове створення, коли даних немає // Створення після page filled, коли одразу є початкове значення // Відновлення стану із снапшота. LastTimestamp передаю окермо від payload, // а lastDelta і h треба прочитати із payload (якщо є) // Кодує значення справа наліво функціями TailPut*. Читає значення зліва направо функціями Get* func NewTimeDeltaCompressor(buf []byte) *TimeDeltaCompressor { return &TimeDeltaCompressor{ buf: buf, } } func (s *TimeDeltaCompressor) RestoreState(payloadSize int, lastTimestamp uint32) { if payloadSize > 0 { s.lastUnixtime = lastTimestamp s.pos = len(s.buf) - payloadSize if payloadSize > 4 { s.h = s.buf[s.pos] u64, _, err := bin.GetVarUint64(s.buf[s.pos+1:]) if err != nil { log.Fatalf("bug: get last delta: %s", err) } s.lastDelta = uint32(u64) } } } // retrun func (s *TimeDeltaCompressor) Evaluate(unixtime uint32) (compressionWay int, requiredSpace int) { requiredSpace = len(s.buf) - s.pos if requiredSpace > 0 { delta := unixtime - s.lastUnixtime if s.lastDelta > 0 { if s.h < 128 { // run if delta == s.lastDelta && s.h < 127 { compressionWay = incrementRun } else { compressionWay = endSeries requiredSpace += bin.CountVarUint64(uint64(delta)) + hSize } } else { // literal if delta != s.lastDelta { if s.h < 255 { compressionWay = incrementLiteral requiredSpace += bin.CountVarUint64(uint64(delta)) } else { compressionWay = endSeries requiredSpace += bin.CountVarUint64(uint64(delta)) + hSize } } else { compressionWay = startRun if s.h > 128 { requiredSpace += hSize // h - end of run } } } } else { compressionWay = add1stDelta requiredSpace += bin.CountVarUint64(uint64(delta)) + hSize } } else { compressionWay = addUnixtime requiredSpace += 4 } return } // pos вказує на h byte func (s *TimeDeltaCompressor) Compress(compressionWay int, unixtime uint32) { delta := unixtime - s.lastUnixtime switch compressionWay { case incrementRun: s.h++ s.buf[s.pos] = s.h case incrementLiteral: s.lastDelta = delta s.h++ // тому що перезаписую h s.pos++ n, _ := bin.TailPutVarUint64(s.buf[:s.pos], uint64(delta)) s.pos -= n s.pos-- s.buf[s.pos] = s.h case endSeries: // start new literal (length=1) s.lastDelta = delta s.h = 128 n, _ := bin.TailPutVarUint64(s.buf[:s.pos], uint64(delta)) s.pos -= n s.pos-- s.buf[s.pos] = s.h case startRun: if s.h > 128 { s.h-- s.pos += bin.CountVarUint64(uint64(s.lastDelta)) // забираю останню дельту у literal s.buf[s.pos] = s.h // write h - end of literal (because length > 1) s.pos-- // run delta n, _ := bin.TailPutVarUint64(s.buf[:s.pos], uint64(delta)) s.pos -= n // для h s.pos-- } // start new run (length=2) s.h = 0 s.buf[s.pos] = s.h case add1stDelta: // start new literal (length=1) s.lastDelta = delta s.h = 128 n, _ := bin.TailPutVarUint64(s.buf[:s.pos], uint64(delta)) s.pos -= n s.pos-- s.buf[s.pos] = s.h case addUnixtime: s.pos -= 4 bin.PutUint32(s.buf[s.pos:], unixtime) } // 1st value or update after every update // do not write immediatelly because of update after every update s.lastUnixtime = unixtime } func (s *TimeDeltaCompressor) DeleteLast() { } // FIX - check methods type TimeDeltaCapturedState struct { H byte LastUnixtime uint32 LastDelta uint32 Payload []byte } // delta h func (s *TimeDeltaCompressor) CaptureState() { if s.state != nil { qb.Abort(qb.RepeatableLock, nil) } // позиція посувається вліво, отже може перескочити на попередній chunk s.state = &TimeDeltaCapturedState{ H: s.h, LastUnixtime: s.lastUnixtime, LastDelta: s.lastDelta, Payload: s.buf[s.pos:], } } // fix - повернути в Pool буфери func (s *TimeDeltaCompressor) ForgetCapturedState() { s.state = nil } // коли сторінка заповнена і відправляється в 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 } // fix func (s *TimeDeltaCompressor) Offset() int { // if s.state != nil { // return s.state.Pos // } return 0 } func (s *TimeDeltaCompressor) Size() int { if s.state == nil { return len(s.buf) - s.pos } else { return bin.CountVarUint64(uint64(s.state.LastDelta)) + hSize + len(s.state.Payload) } } // 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) WritePayloadTo(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) encodeTail(lastUnixtime, lastDelta uint32, h byte) []byte { // tail := make([]byte, 9) // bin.PutUint32(tail, lastUnixtime) // tail[4] = h // n, _ := bin.PutVarUint64(tail[5:], uint64(lastDelta)) // return tail[:n+5] // } func (s *TimeDeltaCompressor) Rotate(newbuf []byte) { // УВАГА! // state не чіпаємо s.buf = newbuf s.pos = len(s.buf) s.lastUnixtime = 0 s.lastDelta = 0 s.h = 0 } func (s *TimeDeltaCompressor) CreateDecompressor() qb.TimestampDecompressor { var ( h = s.h lastUnixtime = s.lastUnixtime lastDelta = s.lastDelta payload = s.buf[s.pos:] ) if s.state != nil { h = s.state.H lastUnixtime = s.state.LastUnixtime lastDelta = s.state.LastDelta payload = s.state.Payload } return NewTimeDeltaDecompressorFromState(TimeDeltaDecompressorFromStateOptions{ H: h, LastUnixtime: lastUnixtime, LastDelta: lastDelta, Payload: payload, }) } 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(buf []byte) *TimeDeltaDecompressor { s := &TimeDeltaDecompressor{ buf: buf, } if s.pos < len(s.buf) { var err error s.lastUnixtime, err = bin.GetUint32(s.buf) if err != nil { log.Fatalf("bug: get last unixtime: %s", err) } s.pos += 4 //fmt.Println("restored last unixtime", s.lastUnixtime) } else { s.done = true } return s } type TimeDeltaDecompressorFromStateOptions struct { H byte LastUnixtime uint32 LastDelta uint32 Payload []byte } func NewTimeDeltaDecompressorFromState(opt TimeDeltaDecompressorFromStateOptions) *TimeDeltaDecompressor { s := &TimeDeltaDecompressor{ buf: opt.Payload, lastUnixtime: opt.LastUnixtime, } if opt.LastDelta > 0 { s.lastDelta = opt.LastDelta s.decodeHeaderByte(opt.H) } //fmt.Println("-------------------") //fmt.Printf("restore from bound: isRun=%t, pending=%d, lastUnix=%d, lastDelta=%d\n", //s.isRun, s.pending, s.lastUnixtime, s.lastDelta) //fmt.Printf("buf: %d\n", s.buf) return s } func (s *TimeDeltaDecompressor) NextValue() (value uint32, done bool) { //fmt.Printf("NextValue(): pos: %d, isRegular: %t, pending: %d\n", s.pos, s.isRegular, s.pending) if s.done { return 0, true } // повертаю значення, що було прочитано в методі RestoreFromBound/RestoreFromEnd value = s.lastUnixtime if s.lastDelta > 0 { s.lastUnixtime -= s.lastDelta s.pending-- //fmt.Printf("lastUnix: %d, s.pending: %d\n", s.lastUnixtime, s.pending) if s.pending > 0 { // якщо в серії залишаються елементи if !s.isRun { s.readDelta() } } else if s.pos < len(s.buf) { // в серії більше немає елементів, отже перевіряє чи є ще дані в буфері. // дані є - читаю заголовок наступної серії s.readHeader() s.readDelta() } else { //s.done = true s.lastDelta = 0 } } else { // був закодований лише last unixtime s.done = true } return value, false } func (s *TimeDeltaDecompressor) readHeader() { h := s.buf[s.pos] s.pos++ s.decodeHeaderByte(h) // fmt.Println() // fmt.Println("read from pos:", s.pos) // fmt.Println("h:", h) // fmt.Println("isRun:", s.isRun) // fmt.Println("pending:", s.pending) // fmt.Println() } 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) } // fmt.Println() // fmt.Println("read from pos:", s.pos) // fmt.Println("read delta:", u64) // fmt.Println("read delta n:", n) // fmt.Println() s.pos += n s.lastDelta = uint32(u64) }