mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-04 10:41:14 +00:00
327 lines
9.3 KiB
Go
327 lines
9.3 KiB
Go
package freecache
|
|
|
|
import (
|
|
"errors"
|
|
"time"
|
|
"unsafe"
|
|
)
|
|
|
|
const HASH_ENTRY_SIZE = 16
|
|
const ENTRY_HDR_SIZE = 24
|
|
|
|
var ErrLargeKey = errors.New("The key is larger than 65535")
|
|
var ErrLargeEntry = errors.New("The entry size is larger than 1/1024 of cache size")
|
|
var ErrNotFound = errors.New("Entry not found")
|
|
|
|
// entry pointer struct points to an entry in ring buffer
|
|
type entryPtr struct {
|
|
offset int64 // entry offset in ring buffer
|
|
hash16 uint16 // entries are ordered by hash16 in a slot.
|
|
keyLen uint16 // used to compare a key
|
|
reserved uint32
|
|
}
|
|
|
|
// entry header struct in ring buffer, followed by key and value.
|
|
type entryHdr struct {
|
|
accessTime uint32
|
|
expireAt uint32
|
|
keyLen uint16
|
|
hash16 uint16
|
|
valLen uint32
|
|
valCap uint32
|
|
deleted bool
|
|
slotId uint8
|
|
reserved uint16
|
|
}
|
|
|
|
// a segment contains 256 slots, a slot is an array of entry pointers ordered by hash16 value
|
|
// the entry can be looked up by hash value of the key.
|
|
type segment struct {
|
|
rb RingBuf // ring buffer that stores data
|
|
segId int
|
|
entryCount int64
|
|
totalCount int64 // number of entries in ring buffer, including deleted entries.
|
|
totalTime int64 // used to calculate least recent used entry.
|
|
totalEvacuate int64 // used for debug
|
|
overwrites int64 // used for debug
|
|
vacuumLen int64 // up to vacuumLen, new data can be written without overwriting old data.
|
|
slotLens [256]int32 // The actual length for every slot.
|
|
slotCap int32 // max number of entry pointers a slot can hold.
|
|
slotsData []entryPtr // shared by all 256 slots
|
|
}
|
|
|
|
func newSegment(bufSize int, segId int) (seg segment) {
|
|
seg.rb = NewRingBuf(bufSize, 0)
|
|
seg.segId = segId
|
|
seg.vacuumLen = int64(bufSize)
|
|
seg.slotCap = 1
|
|
seg.slotsData = make([]entryPtr, 256*seg.slotCap)
|
|
return
|
|
}
|
|
|
|
func (seg *segment) set(key, value []byte, hashVal uint64, expireSeconds int) (err error) {
|
|
if len(key) > 65535 {
|
|
return ErrLargeKey
|
|
}
|
|
maxKeyValLen := len(seg.rb.data)/4 - ENTRY_HDR_SIZE
|
|
if len(key)+len(value) > maxKeyValLen {
|
|
// Do not accept large entry.
|
|
return ErrLargeEntry
|
|
}
|
|
now := uint32(time.Now().Unix())
|
|
expireAt := uint32(0)
|
|
if expireSeconds > 0 {
|
|
expireAt = now + uint32(expireSeconds)
|
|
}
|
|
|
|
slotId := uint8(hashVal >> 8)
|
|
hash16 := uint16(hashVal >> 16)
|
|
|
|
var hdrBuf [ENTRY_HDR_SIZE]byte
|
|
hdr := (*entryHdr)(unsafe.Pointer(&hdrBuf[0]))
|
|
|
|
slotOff := int32(slotId) * seg.slotCap
|
|
slot := seg.slotsData[slotOff : slotOff+seg.slotLens[slotId] : slotOff+seg.slotCap]
|
|
idx, match := seg.lookup(slot, hash16, key)
|
|
if match {
|
|
matchedPtr := &slot[idx]
|
|
seg.rb.ReadAt(hdrBuf[:], matchedPtr.offset)
|
|
hdr.slotId = slotId
|
|
hdr.hash16 = hash16
|
|
hdr.keyLen = uint16(len(key))
|
|
hdr.accessTime = now
|
|
hdr.expireAt = expireAt
|
|
hdr.valLen = uint32(len(value))
|
|
if hdr.valCap >= hdr.valLen {
|
|
//in place overwrite
|
|
seg.totalTime += int64(hdr.accessTime) - int64(now)
|
|
seg.rb.WriteAt(hdrBuf[:], matchedPtr.offset)
|
|
seg.rb.WriteAt(value, matchedPtr.offset+ENTRY_HDR_SIZE+int64(hdr.keyLen))
|
|
seg.overwrites++
|
|
return
|
|
}
|
|
// increase capacity and limit entry len.
|
|
for hdr.valCap < hdr.valLen {
|
|
hdr.valCap *= 2
|
|
}
|
|
if hdr.valCap > uint32(maxKeyValLen-len(key)) {
|
|
hdr.valCap = uint32(maxKeyValLen - len(key))
|
|
}
|
|
} else {
|
|
hdr.slotId = slotId
|
|
hdr.hash16 = hash16
|
|
hdr.keyLen = uint16(len(key))
|
|
hdr.accessTime = now
|
|
hdr.expireAt = expireAt
|
|
hdr.valLen = uint32(len(value))
|
|
hdr.valCap = uint32(len(value))
|
|
if hdr.valCap == 0 { // avoid infinite loop when increasing capacity.
|
|
hdr.valCap = 1
|
|
}
|
|
}
|
|
|
|
entryLen := ENTRY_HDR_SIZE + int64(len(key)) + int64(hdr.valCap)
|
|
slotModified := seg.evacuate(entryLen, slotId, now)
|
|
if slotModified {
|
|
// the slot has been modified during evacuation, we need to looked up for the 'idx' again.
|
|
// otherwise there would be index out of bound error.
|
|
slot = seg.slotsData[slotOff : slotOff+seg.slotLens[slotId] : slotOff+seg.slotCap]
|
|
idx, match = seg.lookup(slot, hash16, key)
|
|
}
|
|
newOff := seg.rb.End()
|
|
if match {
|
|
seg.updateEntryPtr(slotId, hash16, slot[idx].offset, newOff)
|
|
} else {
|
|
seg.insertEntryPtr(slotId, hash16, newOff, idx, hdr.keyLen)
|
|
}
|
|
seg.rb.Write(hdrBuf[:])
|
|
seg.rb.Write(key)
|
|
seg.rb.Write(value)
|
|
seg.rb.Skip(int64(hdr.valCap - hdr.valLen))
|
|
seg.totalTime += int64(now)
|
|
seg.totalCount++
|
|
seg.vacuumLen -= entryLen
|
|
return
|
|
}
|
|
|
|
func (seg *segment) evacuate(entryLen int64, slotId uint8, now uint32) (slotModified bool) {
|
|
var oldHdrBuf [ENTRY_HDR_SIZE]byte
|
|
consecutiveEvacuate := 0
|
|
for seg.vacuumLen < entryLen {
|
|
oldOff := seg.rb.End() + seg.vacuumLen - seg.rb.Size()
|
|
seg.rb.ReadAt(oldHdrBuf[:], oldOff)
|
|
oldHdr := (*entryHdr)(unsafe.Pointer(&oldHdrBuf[0]))
|
|
oldEntryLen := ENTRY_HDR_SIZE + int64(oldHdr.keyLen) + int64(oldHdr.valCap)
|
|
if oldHdr.deleted {
|
|
consecutiveEvacuate = 0
|
|
seg.totalTime -= int64(oldHdr.accessTime)
|
|
seg.totalCount--
|
|
seg.vacuumLen += oldEntryLen
|
|
continue
|
|
}
|
|
expired := oldHdr.expireAt != 0 && oldHdr.expireAt < now
|
|
leastRecentUsed := int64(oldHdr.accessTime)*seg.totalCount <= seg.totalTime
|
|
if expired || leastRecentUsed || consecutiveEvacuate > 5 {
|
|
seg.delEntryPtr(oldHdr.slotId, oldHdr.hash16, oldOff)
|
|
if oldHdr.slotId == slotId {
|
|
slotModified = true
|
|
}
|
|
consecutiveEvacuate = 0
|
|
seg.totalTime -= int64(oldHdr.accessTime)
|
|
seg.totalCount--
|
|
seg.vacuumLen += oldEntryLen
|
|
} else {
|
|
// evacuate an old entry that has been accessed recently for better cache hit rate.
|
|
newOff := seg.rb.Evacuate(oldOff, int(oldEntryLen))
|
|
seg.updateEntryPtr(oldHdr.slotId, oldHdr.hash16, oldOff, newOff)
|
|
consecutiveEvacuate++
|
|
seg.totalEvacuate++
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (seg *segment) get(key []byte, hashVal uint64) (value []byte, err error) {
|
|
slotId := uint8(hashVal >> 8)
|
|
hash16 := uint16(hashVal >> 16)
|
|
slotOff := int32(slotId) * seg.slotCap
|
|
var slot = seg.slotsData[slotOff : slotOff+seg.slotLens[slotId] : slotOff+seg.slotCap]
|
|
idx, match := seg.lookup(slot, hash16, key)
|
|
if !match {
|
|
err = ErrNotFound
|
|
return
|
|
}
|
|
ptr := &slot[idx]
|
|
now := uint32(time.Now().Unix())
|
|
|
|
var hdrBuf [ENTRY_HDR_SIZE]byte
|
|
seg.rb.ReadAt(hdrBuf[:], ptr.offset)
|
|
hdr := (*entryHdr)(unsafe.Pointer(&hdrBuf[0]))
|
|
|
|
if hdr.expireAt != 0 && hdr.expireAt <= now {
|
|
seg.delEntryPtr(slotId, hash16, ptr.offset)
|
|
err = ErrNotFound
|
|
return
|
|
}
|
|
seg.totalTime += int64(now - hdr.accessTime)
|
|
hdr.accessTime = now
|
|
seg.rb.WriteAt(hdrBuf[:], ptr.offset)
|
|
value = make([]byte, hdr.valLen)
|
|
|
|
seg.rb.ReadAt(value, ptr.offset+ENTRY_HDR_SIZE+int64(hdr.keyLen))
|
|
return
|
|
}
|
|
|
|
func (seg *segment) del(key []byte, hashVal uint64) (affected bool) {
|
|
slotId := uint8(hashVal >> 8)
|
|
hash16 := uint16(hashVal >> 16)
|
|
slotOff := int32(slotId) * seg.slotCap
|
|
slot := seg.slotsData[slotOff : slotOff+seg.slotLens[slotId] : slotOff+seg.slotCap]
|
|
idx, match := seg.lookup(slot, hash16, key)
|
|
if !match {
|
|
return false
|
|
}
|
|
ptr := &slot[idx]
|
|
seg.delEntryPtr(slotId, hash16, ptr.offset)
|
|
return true
|
|
}
|
|
|
|
func (seg *segment) expand() {
|
|
newSlotData := make([]entryPtr, seg.slotCap*2*256)
|
|
for i := 0; i < 256; i++ {
|
|
off := int32(i) * seg.slotCap
|
|
copy(newSlotData[off*2:], seg.slotsData[off:off+seg.slotLens[i]])
|
|
}
|
|
seg.slotCap *= 2
|
|
seg.slotsData = newSlotData
|
|
}
|
|
|
|
func (seg *segment) updateEntryPtr(slotId uint8, hash16 uint16, oldOff, newOff int64) {
|
|
slotOff := int32(slotId) * seg.slotCap
|
|
slot := seg.slotsData[slotOff : slotOff+seg.slotLens[slotId] : slotOff+seg.slotCap]
|
|
idx, match := seg.lookupByOff(slot, hash16, oldOff)
|
|
if !match {
|
|
return
|
|
}
|
|
ptr := &slot[idx]
|
|
ptr.offset = newOff
|
|
}
|
|
|
|
func (seg *segment) insertEntryPtr(slotId uint8, hash16 uint16, offset int64, idx int, keyLen uint16) {
|
|
slotOff := int32(slotId) * seg.slotCap
|
|
if seg.slotLens[slotId] == seg.slotCap {
|
|
seg.expand()
|
|
slotOff *= 2
|
|
}
|
|
seg.slotLens[slotId]++
|
|
seg.entryCount++
|
|
slot := seg.slotsData[slotOff : slotOff+seg.slotLens[slotId] : slotOff+seg.slotCap]
|
|
copy(slot[idx+1:], slot[idx:])
|
|
slot[idx].offset = offset
|
|
slot[idx].hash16 = hash16
|
|
slot[idx].keyLen = keyLen
|
|
}
|
|
|
|
func (seg *segment) delEntryPtr(slotId uint8, hash16 uint16, offset int64) {
|
|
slotOff := int32(slotId) * seg.slotCap
|
|
slot := seg.slotsData[slotOff : slotOff+seg.slotLens[slotId] : slotOff+seg.slotCap]
|
|
idx, match := seg.lookupByOff(slot, hash16, offset)
|
|
if !match {
|
|
return
|
|
}
|
|
var entryHdrBuf [ENTRY_HDR_SIZE]byte
|
|
seg.rb.ReadAt(entryHdrBuf[:], offset)
|
|
entryHdr := (*entryHdr)(unsafe.Pointer(&entryHdrBuf[0]))
|
|
entryHdr.deleted = true
|
|
seg.rb.WriteAt(entryHdrBuf[:], offset)
|
|
copy(slot[idx:], slot[idx+1:])
|
|
seg.slotLens[slotId]--
|
|
seg.entryCount--
|
|
}
|
|
|
|
func entryPtrIdx(slot []entryPtr, hash16 uint16) (idx int) {
|
|
high := len(slot)
|
|
for idx < high {
|
|
mid := (idx + high) >> 1
|
|
oldEntry := &slot[mid]
|
|
if oldEntry.hash16 < hash16 {
|
|
idx = mid + 1
|
|
} else {
|
|
high = mid
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (seg *segment) lookup(slot []entryPtr, hash16 uint16, key []byte) (idx int, match bool) {
|
|
idx = entryPtrIdx(slot, hash16)
|
|
for idx < len(slot) {
|
|
ptr := &slot[idx]
|
|
if ptr.hash16 != hash16 {
|
|
break
|
|
}
|
|
match = int(ptr.keyLen) == len(key) && seg.rb.EqualAt(key, ptr.offset+ENTRY_HDR_SIZE)
|
|
if match {
|
|
return
|
|
}
|
|
idx++
|
|
}
|
|
return
|
|
}
|
|
|
|
func (seg *segment) lookupByOff(slot []entryPtr, hash16 uint16, offset int64) (idx int, match bool) {
|
|
idx = entryPtrIdx(slot, hash16)
|
|
for idx < len(slot) {
|
|
ptr := &slot[idx]
|
|
if ptr.hash16 != hash16 {
|
|
break
|
|
}
|
|
match = ptr.offset == offset
|
|
if match {
|
|
return
|
|
}
|
|
idx++
|
|
}
|
|
return
|
|
}
|