Files
pulse/internal/api/chartapi/payload_cache.go
T
2026-08-30 12:55:50 +01:00

125 lines
2.6 KiB
Go

package chartapi
import (
"sync"
"time"
)
type cachedChartPayload struct {
payload []byte
expiresAt time.Time
sequence uint64
}
// boundedChartPayloadCache keeps the short-lived chart response caches from
// retaining an unbounded set of expired query variants. Chart payloads can be
// several MiB on large estates, so both cardinality and bytes are bounded.
type boundedChartPayloadCache struct {
mu sync.Mutex
entries map[string]cachedChartPayload
maxEntries int
maxBytes int
bytes int
sequence uint64
}
func newBoundedChartPayloadCache(maxEntries, maxBytes int) boundedChartPayloadCache {
if maxEntries < 0 {
maxEntries = 0
}
if maxBytes < 0 {
maxBytes = 0
}
return boundedChartPayloadCache{
entries: make(map[string]cachedChartPayload, maxEntries),
maxEntries: maxEntries,
maxBytes: maxBytes,
}
}
func (c *boundedChartPayloadCache) get(key string, now time.Time) ([]byte, bool) {
if c == nil || key == "" {
return nil, false
}
c.mu.Lock()
defer c.mu.Unlock()
entry, ok := c.entries[key]
if !ok {
return nil, false
}
if !now.Before(entry.expiresAt) {
c.removeLocked(key, entry)
return nil, false
}
return entry.payload, true
}
func (c *boundedChartPayloadCache) put(key string, payload []byte, expiresAt, now time.Time) {
if c == nil || key == "" || len(payload) == 0 {
return
}
c.mu.Lock()
defer c.mu.Unlock()
if c.entries == nil {
c.entries = make(map[string]cachedChartPayload, c.maxEntries)
}
c.pruneExpiredLocked(now)
if previous, ok := c.entries[key]; ok {
c.removeLocked(key, previous)
}
if c.maxEntries == 0 || c.maxBytes == 0 || len(payload) > c.maxBytes {
return
}
for len(c.entries) >= c.maxEntries || c.bytes+len(payload) > c.maxBytes {
if !c.removeOldestLocked() {
return
}
}
c.sequence++
c.entries[key] = cachedChartPayload{
payload: payload,
expiresAt: expiresAt,
sequence: c.sequence,
}
c.bytes += len(payload)
}
func (c *boundedChartPayloadCache) pruneExpiredLocked(now time.Time) {
for key, entry := range c.entries {
if !now.Before(entry.expiresAt) {
c.removeLocked(key, entry)
}
}
}
func (c *boundedChartPayloadCache) removeOldestLocked() bool {
var (
oldestKey string
oldestEntry cachedChartPayload
found bool
)
for key, entry := range c.entries {
if !found || entry.sequence < oldestEntry.sequence {
oldestKey = key
oldestEntry = entry
found = true
}
}
if !found {
return false
}
c.removeLocked(oldestKey, oldestEntry)
return true
}
func (c *boundedChartPayloadCache) removeLocked(key string, entry cachedChartPayload) {
delete(c.entries, key)
c.bytes -= len(entry.payload)
if c.bytes < 0 {
c.bytes = 0
}
}