package enc import ( "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 } // Кодує значення справа наліво футнкціями TailPut*. Читає значення зліва направо функціями Get* func NewTimeDeltaCompressor(buf []byte, payloadSize int) *TimeDeltaCompressor { s := &TimeDeltaCompressor{ buf: buf, pos: len(buf) - payloadSize, } if payloadSize > 0 { var err error s.lastUnixtime, err = bin.GetUint32(s.buf[s.pos:]) if err != nil { log.Fatalf("bug: get last unixtime: %s", err) } s.pos += 4 if s.pos < len(buf) { s.h = s.buf[s.pos] s.pos++ u64, n, err := bin.GetVarUint64(s.buf[s.pos:]) if err != nil { log.Fatalf("bug: get last delta: %s", err) } s.lastDelta = uint32(u64) s.pos += n } } return s } // func (s *TimeDeltaCompressor) Size() int { // return len(s.buf) - s.pos // } // retrun func (s *TimeDeltaCompressor) Evaluate(unixtime uint32) (compressionWay int, requiredSpace int) { requiredSpace = 4 // last unixtime if s.lastUnixtime > 0 { delta := unixtime - s.lastUnixtime if s.lastDelta > 0 { // 3rd value if s.h < 128 { // run if delta == s.lastDelta && s.h < 127 { compressionWay = incrementRun requiredSpace += bin.CountVarUint64(uint64(s.lastDelta)) + // current delta hSize } else { compressionWay = endSeries requiredSpace += bin.CountVarUint64(uint64(s.lastDelta)) + // previous delta hSize + // h - end of run bin.CountVarUint64(uint64(delta)) + // new literal hSize // new h } } else { // literal if delta != s.lastDelta { if s.h < 255 { compressionWay = incrementLiteral requiredSpace += bin.CountVarUint64(uint64(s.lastDelta)) + // previous delta bin.CountVarUint64(uint64(delta)) + // new delta hSize } else { compressionWay = endSeries requiredSpace += bin.CountVarUint64(uint64(s.lastDelta)) + // previous delta hSize + // h - end of literal bin.CountVarUint64(uint64(delta)) + // new literal hSize // new h } } else { compressionWay = startRun if s.h > 128 { requiredSpace += hSize // h - end of literal } requiredSpace += bin.CountVarUint64(uint64(delta)) + // new delta hSize // new h } } } else { compressionWay = add1stDelta requiredSpace += bin.CountVarUint64(uint64(delta)) + // new literal hSize // new h } } else { compressionWay = addUnixtime } return } func (s *TimeDeltaCompressor) Compress(compressionWay int, unixtime uint32) { delta := unixtime - s.lastUnixtime switch compressionWay { case incrementRun: s.h++ case incrementLiteral: // write previous delta and increment counter n, _ := bin.TailPutVarUint64(s.buf[:s.pos], uint64(s.lastDelta)) s.pos -= n s.lastDelta = delta s.h++ case endSeries: n, _ := bin.TailPutVarUint64(s.buf[:s.pos], uint64(s.lastDelta)) s.pos -= n // write h - end of run s.pos-- s.buf[s.pos] = s.h // start new literal (length=1) s.lastDelta = delta s.h = 128 case startRun: if s.h > 128 { // write h - end of literal (because length > 1) s.h-- s.pos-- s.buf[s.pos] = s.h } // start new run (length=2) s.h = 0 case add1stDelta: // start new literal (length=1) s.lastDelta = delta s.h = 128 } // 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 } // fix func (s *TimeDeltaCompressor) Offset() int { // if s.state != nil { // return s.state.Pos // } return 0 } // Snapshot - для створення снапшота. func (s *TimeDeltaCompressor) Snapshot() (left []byte, right []byte) { if s.state == nil { left = s.encodeTail(s.lastUnixtime, s.lastDelta, s.h) right = s.buf[s.pos:] } else { left = s.encodeTail(s.state.LastUnixtime, s.state.LastDelta, s.state.H) right = s.state.Payload } 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) (payloadSize int) { var n int n, _ = bin.TailPutVarUint64(s.buf[:s.pos], uint64(s.lastDelta)) s.pos -= n s.pos-- s.buf[s.pos] = s.h n, _ = bin.TailPutVarUint64(s.buf[:s.pos], uint64(s.lastUnixtime)) s.pos -= n payloadSize = len(s.buf) - s.pos // УВАГА! // state не чіпаємо s.buf = newbuf s.pos = len(s.buf) s.lastUnixtime = 0 s.lastDelta = 0 s.h = 0 return } 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) Chunks() []byte { return s.buf } // 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) }