Files
qb/enc/time_delta.go
2026-06-10 06:18:45 +03:00

409 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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, payloadSize int) *TimeDeltaCompressor {
s := &TimeDeltaCompressor{
buf: buf,
}
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)
}
}
return s
}
// FIX - restire lastUnixtime
// func (s *TimeDeltaCompressor) RestoreState() {
// }
// 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)
}