Files
qb/enc/time_delta.go
2026-06-15 05:32:15 +03:00

416 lines
9.8 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 (
"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: len(s.buf) - 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) {
//fmt.Printf("time replace buffer: new size %d\n", len(newbuf))
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) FirstTimestamp() uint32 {
pos := len(s.buf) - 4
timestamp, _ := bin.GetUint32(s.buf[pos:])
return timestamp
}
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)
}