mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-24 22:07:23 +01:00
cc2647d212
Change the return values for these functions - now they return the unmarshaled result plus the size of the unmarshaled result in bytes, so the caller could re-slice the src for further unmarshaling. This improves performance of these functions in hot loops of VictoriaLogs a bit.
332 lines
9.2 KiB
Go
332 lines
9.2 KiB
Go
package logstorage
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
|
|
)
|
|
|
|
// marshalStringsBlock marshals a and appends the result to dst.
|
|
//
|
|
// The marshaled strings block can be unmarshaled with stringsBlockUnmarshaler.
|
|
func marshalStringsBlock(dst []byte, a []string) []byte {
|
|
// Encode string lengths
|
|
u64s := encoding.GetUint64s(len(a))
|
|
aLens := u64s.A[:0]
|
|
for _, s := range a {
|
|
aLens = append(aLens, uint64(len(s)))
|
|
}
|
|
u64s.A = aLens
|
|
dst = marshalUint64Block(dst, u64s.A)
|
|
encoding.PutUint64s(u64s)
|
|
|
|
// Encode strings
|
|
bb := bbPool.Get()
|
|
b := bb.B
|
|
for _, s := range a {
|
|
b = append(b, s...)
|
|
}
|
|
bb.B = b
|
|
dst = marshalBytesBlock(dst, bb.B)
|
|
bbPool.Put(bb)
|
|
|
|
return dst
|
|
}
|
|
|
|
// stringsBlockUnmarshaler is used for unmarshaling the block returned from marshalStringsBlock()
|
|
//
|
|
// use getStringsBlockUnmarshaler() for obtaining the unmarshaler from the pool in order to save memory allocations.
|
|
type stringsBlockUnmarshaler struct {
|
|
// data contains the data for the unmarshaled values
|
|
data []byte
|
|
}
|
|
|
|
func (sbu *stringsBlockUnmarshaler) reset() {
|
|
sbu.data = sbu.data[:0]
|
|
}
|
|
|
|
func (sbu *stringsBlockUnmarshaler) copyString(s string) string {
|
|
dataLen := len(sbu.data)
|
|
sbu.data = append(sbu.data, s...)
|
|
return bytesutil.ToUnsafeString(sbu.data[dataLen:])
|
|
}
|
|
|
|
func (sbu *stringsBlockUnmarshaler) appendFields(dst, src []Field) []Field {
|
|
for _, f := range src {
|
|
dst = append(dst, Field{
|
|
Name: sbu.copyString(f.Name),
|
|
Value: sbu.copyString(f.Value),
|
|
})
|
|
}
|
|
return dst
|
|
}
|
|
|
|
// unmarshal unmarshals itemsCount strings from src, appends them to dst and returns the result.
|
|
//
|
|
// The returned strings are valid until sbu.reset() call.
|
|
func (sbu *stringsBlockUnmarshaler) unmarshal(dst []string, src []byte, itemsCount uint64) ([]string, error) {
|
|
u64s := encoding.GetUint64s(0)
|
|
defer encoding.PutUint64s(u64s)
|
|
|
|
// Decode string lengths
|
|
var tail []byte
|
|
var err error
|
|
u64s.A, tail, err = unmarshalUint64Block(u64s.A[:0], src, itemsCount)
|
|
if err != nil {
|
|
return dst, fmt.Errorf("cannot unmarshal string lengths: %w", err)
|
|
}
|
|
aLens := u64s.A
|
|
src = tail
|
|
|
|
// Read bytes block into sbu.data
|
|
dataLen := len(sbu.data)
|
|
sbu.data, tail, err = unmarshalBytesBlock(sbu.data, src)
|
|
if err != nil {
|
|
return dst, fmt.Errorf("cannot unmarshal bytes block with strings: %w", err)
|
|
}
|
|
if len(tail) > 0 {
|
|
return dst, fmt.Errorf("unexpected non-empty tail after reading bytes block with strings; len(tail)=%d", len(tail))
|
|
}
|
|
|
|
// Decode strings from sbu.data into dst
|
|
data := sbu.data[dataLen:]
|
|
for _, sLen := range aLens {
|
|
if uint64(len(data)) < sLen {
|
|
return dst, fmt.Errorf("cannot unmarshal a string with the length %d bytes from %d bytes", sLen, len(data))
|
|
}
|
|
s := bytesutil.ToUnsafeString(data[:sLen])
|
|
data = data[sLen:]
|
|
dst = append(dst, s)
|
|
}
|
|
|
|
return dst, nil
|
|
}
|
|
|
|
// marshalUint64Block appends marshaled a to dst and returns the result.
|
|
func marshalUint64Block(dst []byte, a []uint64) []byte {
|
|
bb := bbPool.Get()
|
|
bb.B = marshalUint64Items(bb.B[:0], a)
|
|
dst = marshalBytesBlock(dst, bb.B)
|
|
bbPool.Put(bb)
|
|
return dst
|
|
}
|
|
|
|
// unmarshalUint64Block appends unmarshaled from src itemsCount uint64 items to dst and returns the result.
|
|
func unmarshalUint64Block(dst []uint64, src []byte, itemsCount uint64) ([]uint64, []byte, error) {
|
|
bb := bbPool.Get()
|
|
defer bbPool.Put(bb)
|
|
|
|
// Unmarshal the underlying bytes block
|
|
var err error
|
|
bb.B, src, err = unmarshalBytesBlock(bb.B[:0], src)
|
|
if err != nil {
|
|
return dst, src, fmt.Errorf("cannot unmarshal bytes block: %w", err)
|
|
}
|
|
|
|
// Unmarshal the items from bb.
|
|
dst, err = unmarshalUint64Items(dst, bb.B, itemsCount)
|
|
if err != nil {
|
|
return dst, src, fmt.Errorf("cannot unmarshal %d uint64 items from bytes block of length %d bytes: %w", itemsCount, len(bb.B), err)
|
|
}
|
|
return dst, src, nil
|
|
}
|
|
|
|
const (
|
|
uintBlockType8 = 0
|
|
uintBlockType16 = 1
|
|
uintBlockType32 = 2
|
|
uintBlockType64 = 3
|
|
)
|
|
|
|
// marshalUint64Items appends the marshaled a items to dst and returns the result.
|
|
func marshalUint64Items(dst []byte, a []uint64) []byte {
|
|
// Do not marshal len(a), since it is expected that unmarshaler knows it.
|
|
nMax := uint64(0)
|
|
for _, n := range a {
|
|
if n > nMax {
|
|
nMax = n
|
|
}
|
|
}
|
|
switch {
|
|
case nMax < (1 << 8):
|
|
dst = append(dst, uintBlockType8)
|
|
for _, n := range a {
|
|
dst = append(dst, byte(n))
|
|
}
|
|
case nMax < (1 << 16):
|
|
dst = append(dst, uintBlockType16)
|
|
for _, n := range a {
|
|
dst = encoding.MarshalUint16(dst, uint16(n))
|
|
}
|
|
case nMax < (1 << 32):
|
|
dst = append(dst, uintBlockType32)
|
|
for _, n := range a {
|
|
dst = encoding.MarshalUint32(dst, uint32(n))
|
|
}
|
|
default:
|
|
dst = append(dst, uintBlockType64)
|
|
for _, n := range a {
|
|
dst = encoding.MarshalUint64(dst, uint64(n))
|
|
}
|
|
}
|
|
return dst
|
|
}
|
|
|
|
// unmarshalUint64Items appends unmarshaled from src itemsCount uint64 items to dst and returns the result.
|
|
func unmarshalUint64Items(dst []uint64, src []byte, itemsCount uint64) ([]uint64, error) {
|
|
// Unmarshal block type
|
|
if len(src) < 1 {
|
|
return dst, fmt.Errorf("cannot unmarshal uint64 block type from empty src")
|
|
}
|
|
blockType := src[0]
|
|
src = src[1:]
|
|
|
|
switch blockType {
|
|
case uintBlockType8:
|
|
// A block with items smaller than 1<<8 bytes
|
|
if uint64(len(src)) != itemsCount {
|
|
return dst, fmt.Errorf("unexpected block length for %d items; got %d bytes; want %d bytes", itemsCount, len(src), itemsCount)
|
|
}
|
|
for _, v := range src {
|
|
dst = append(dst, uint64(v))
|
|
}
|
|
case uintBlockType16:
|
|
// A block with items smaller than 1<<16 bytes
|
|
if uint64(len(src)) != 2*itemsCount {
|
|
return dst, fmt.Errorf("unexpected block length for %d items; got %d bytes; want %d bytes", itemsCount, len(src), 2*itemsCount)
|
|
}
|
|
for len(src) > 0 {
|
|
v := encoding.UnmarshalUint16(src)
|
|
src = src[2:]
|
|
dst = append(dst, uint64(v))
|
|
}
|
|
case uintBlockType32:
|
|
// A block with items smaller than 1<<32 bytes
|
|
if uint64(len(src)) != 4*itemsCount {
|
|
return dst, fmt.Errorf("unexpected block length for %d items; got %d bytes; want %d bytes", itemsCount, len(src), 4*itemsCount)
|
|
}
|
|
for len(src) > 0 {
|
|
v := encoding.UnmarshalUint32(src)
|
|
src = src[4:]
|
|
dst = append(dst, uint64(v))
|
|
}
|
|
case uintBlockType64:
|
|
// A block with items smaller than 1<<64 bytes
|
|
if uint64(len(src)) != 8*itemsCount {
|
|
return dst, fmt.Errorf("unexpected block length for %d items; got %d bytes; want %d bytes", itemsCount, len(src), 8*itemsCount)
|
|
}
|
|
for len(src) > 0 {
|
|
v := encoding.UnmarshalUint64(src)
|
|
src = src[8:]
|
|
dst = append(dst, v)
|
|
}
|
|
default:
|
|
return dst, fmt.Errorf("unexpected uint64 block type: %d; want 0, 1, 2 or 3", blockType)
|
|
}
|
|
return dst, nil
|
|
}
|
|
|
|
const (
|
|
marshalBytesTypePlain = 0
|
|
marshalBytesTypeZSTD = 1
|
|
)
|
|
|
|
func marshalBytesBlock(dst, src []byte) []byte {
|
|
if len(src) < 128 {
|
|
// Marshal the block in plain without compression
|
|
dst = append(dst, marshalBytesTypePlain)
|
|
dst = append(dst, byte(len(src)))
|
|
return append(dst, src...)
|
|
}
|
|
|
|
// Compress the block
|
|
dst = append(dst, marshalBytesTypeZSTD)
|
|
bb := bbPool.Get()
|
|
bb.B = encoding.CompressZSTDLevel(bb.B[:0], src, 1)
|
|
dst = encoding.MarshalVarUint64(dst, uint64(len(bb.B)))
|
|
dst = append(dst, bb.B...)
|
|
bbPool.Put(bb)
|
|
return dst
|
|
}
|
|
|
|
func unmarshalBytesBlock(dst, src []byte) ([]byte, []byte, error) {
|
|
if len(src) < 1 {
|
|
return dst, src, fmt.Errorf("cannot unmarshal block type from empty src")
|
|
}
|
|
blockType := src[0]
|
|
src = src[1:]
|
|
switch blockType {
|
|
case marshalBytesTypePlain:
|
|
// Plain block
|
|
|
|
// Read block length
|
|
if len(src) < 1 {
|
|
return dst, src, fmt.Errorf("cannot unmarshal plain block size from empty src")
|
|
}
|
|
blockLen := int(src[0])
|
|
src = src[1:]
|
|
if len(src) < blockLen {
|
|
return dst, src, fmt.Errorf("cannot read plain block with the size %d bytes from %b bytes", blockLen, len(src))
|
|
}
|
|
|
|
// Copy the block to dst
|
|
dst = append(dst, src[:blockLen]...)
|
|
src = src[blockLen:]
|
|
return dst, src, nil
|
|
case marshalBytesTypeZSTD:
|
|
// Compressed block
|
|
|
|
// Read block length
|
|
blockLen, nSize := encoding.UnmarshalVarUint64(src)
|
|
if nSize <= 0 {
|
|
return dst, src, fmt.Errorf("cannot unmarshal compressed block size")
|
|
}
|
|
src = src[nSize:]
|
|
if uint64(len(src)) < blockLen {
|
|
return dst, src, fmt.Errorf("cannot read compressed block with the size %d bytes from %d bytes", blockLen, len(src))
|
|
}
|
|
compressedBlock := src[:blockLen]
|
|
src = src[blockLen:]
|
|
|
|
// Decompress the block
|
|
bb := bbPool.Get()
|
|
var err error
|
|
bb.B, err = encoding.DecompressZSTD(bb.B[:0], compressedBlock)
|
|
if err != nil {
|
|
return dst, src, fmt.Errorf("cannot decompress block: %w", err)
|
|
}
|
|
|
|
// Copy the decompressed block to dst.
|
|
dst = append(dst, bb.B...)
|
|
bbPool.Put(bb)
|
|
return dst, src, nil
|
|
default:
|
|
return dst, src, fmt.Errorf("unexpected block type: %d; supported types: 0, 1", blockType)
|
|
}
|
|
}
|
|
|
|
var bbPool bytesutil.ByteBufferPool
|
|
|
|
// getStringsBlockUnmarshaler returns stringsBlockUnmarshaler from the pool.
|
|
//
|
|
// Return back the stringsBlockUnmarshaler to the pool by calling putStringsBlockUnmarshaler().
|
|
func getStringsBlockUnmarshaler() *stringsBlockUnmarshaler {
|
|
v := sbuPool.Get()
|
|
if v == nil {
|
|
return &stringsBlockUnmarshaler{}
|
|
}
|
|
return v.(*stringsBlockUnmarshaler)
|
|
}
|
|
|
|
// putStringsBlockUnmarshaler returns back sbu to the pool.
|
|
//
|
|
// sbu mustn't be used after returning to the pool.
|
|
func putStringsBlockUnmarshaler(sbu *stringsBlockUnmarshaler) {
|
|
sbu.reset()
|
|
sbuPool.Put(sbu)
|
|
}
|
|
|
|
var sbuPool sync.Pool
|