mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 18:45:53 +00:00
125 lines
2.6 KiB
Go
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
|
|
}
|
|
}
|