mirror of
https://github.com/luxfi/cache.git
synced 2026-09-24 00:24:18 +00:00
Initial release
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package cache provides caching interfaces and implementations.
|
||||
package cache
|
||||
|
||||
// Cacher acts as a best effort key value store.
|
||||
type Cacher[K comparable, V any] interface {
|
||||
// Put inserts an element into the cache.
|
||||
Put(key K, value V)
|
||||
|
||||
// Get returns the entry with the key, if it exists.
|
||||
Get(key K) (V, bool)
|
||||
|
||||
// Evict removes the specified entry from the cache.
|
||||
Evict(key K)
|
||||
|
||||
// Flush removes all entries from the cache.
|
||||
Flush()
|
||||
|
||||
// Len returns the number of elements in the cache.
|
||||
Len() int
|
||||
|
||||
// PortionFilled returns fraction of cache currently filled (0 --> 1).
|
||||
PortionFilled() float64
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
module github.com/luxfi/cache
|
||||
|
||||
go 1.25
|
||||
|
||||
require github.com/luxfi/metric v1.4.5
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/prometheus/client_golang v1.23.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.65.0 // indirect
|
||||
github.com/prometheus/procfs v0.17.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
google.golang.org/protobuf v1.36.7 // indirect
|
||||
)
|
||||
@@ -0,0 +1,36 @@
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/luxfi/metric v1.4.5 h1:fD6gtpzB5ebGB06/m4K7e5+tOV5RiVHSZCV+Zb8ZDxk=
|
||||
github.com/luxfi/metric v1.4.5/go.mod h1:AUQ7NSNz9WndAcr/SKnOkP7XSFFnBXOa+ihtJYfDaQY=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
|
||||
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
|
||||
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
|
||||
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
|
||||
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package lru provides LRU cache implementations.
|
||||
package lru
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"sync"
|
||||
|
||||
"github.com/luxfi/cache"
|
||||
)
|
||||
|
||||
var _ cache.Cacher[struct{}, struct{}] = (*Cache[struct{}, struct{}])(nil)
|
||||
|
||||
// entry is a cache entry.
|
||||
type entry[K comparable, V any] struct {
|
||||
key K
|
||||
value V
|
||||
}
|
||||
|
||||
// Cache is a thread-safe LRU cache.
|
||||
type Cache[K comparable, V any] struct {
|
||||
lock sync.Mutex
|
||||
size int
|
||||
elements map[K]*list.Element
|
||||
order *list.List
|
||||
}
|
||||
|
||||
// NewCache creates a new LRU cache with the specified size.
|
||||
func NewCache[K comparable, V any](size int) *Cache[K, V] {
|
||||
if size <= 0 {
|
||||
size = 1
|
||||
}
|
||||
return &Cache[K, V]{
|
||||
size: size,
|
||||
elements: make(map[K]*list.Element),
|
||||
order: list.New(),
|
||||
}
|
||||
}
|
||||
|
||||
// Put inserts an element into the cache.
|
||||
func (c *Cache[K, V]) Put(key K, value V) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if elem, ok := c.elements[key]; ok {
|
||||
// Update existing entry
|
||||
elem.Value.(*entry[K, V]).value = value
|
||||
c.order.MoveToFront(elem)
|
||||
return
|
||||
}
|
||||
|
||||
// Evict oldest if at capacity
|
||||
if c.order.Len() >= c.size {
|
||||
oldest := c.order.Back()
|
||||
if oldest != nil {
|
||||
c.removeElement(oldest)
|
||||
}
|
||||
}
|
||||
|
||||
// Add new entry
|
||||
e := &entry[K, V]{key: key, value: value}
|
||||
elem := c.order.PushFront(e)
|
||||
c.elements[key] = elem
|
||||
}
|
||||
|
||||
// Get returns the entry with the key, if it exists.
|
||||
func (c *Cache[K, V]) Get(key K) (V, bool) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if elem, ok := c.elements[key]; ok {
|
||||
c.order.MoveToFront(elem)
|
||||
return elem.Value.(*entry[K, V]).value, true
|
||||
}
|
||||
var zero V
|
||||
return zero, false
|
||||
}
|
||||
|
||||
// Evict removes the specified entry from the cache.
|
||||
func (c *Cache[K, V]) Evict(key K) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if elem, ok := c.elements[key]; ok {
|
||||
c.removeElement(elem)
|
||||
}
|
||||
}
|
||||
|
||||
// Flush removes all entries from the cache.
|
||||
func (c *Cache[K, V]) Flush() {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
c.elements = make(map[K]*list.Element)
|
||||
c.order.Init()
|
||||
}
|
||||
|
||||
// Len returns the number of elements in the cache.
|
||||
func (c *Cache[K, V]) Len() int {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
return c.order.Len()
|
||||
}
|
||||
|
||||
// PortionFilled returns fraction of cache currently filled.
|
||||
func (c *Cache[K, V]) PortionFilled() float64 {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
return float64(c.order.Len()) / float64(c.size)
|
||||
}
|
||||
|
||||
func (c *Cache[K, V]) removeElement(elem *list.Element) {
|
||||
e := elem.Value.(*entry[K, V])
|
||||
delete(c.elements, e.key)
|
||||
c.order.Remove(elem)
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package metercacher provides metered cache implementations.
|
||||
package metercacher
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/cache"
|
||||
"github.com/luxfi/metric"
|
||||
)
|
||||
|
||||
var _ cache.Cacher[struct{}, struct{}] = (*Cache[struct{}, struct{}])(nil)
|
||||
|
||||
// Cache wraps a Cacher with metrics.
|
||||
type Cache[K comparable, V any] struct {
|
||||
cache.Cacher[K, V]
|
||||
metrics *cacheMetrics
|
||||
}
|
||||
|
||||
// New creates a new metered cache wrapper.
|
||||
func New[K comparable, V any](
|
||||
namespace string,
|
||||
registry metric.Registry,
|
||||
c cache.Cacher[K, V],
|
||||
) (*Cache[K, V], error) {
|
||||
metrics, err := newMetrics(namespace, registry)
|
||||
return &Cache[K, V]{
|
||||
Cacher: c,
|
||||
metrics: metrics,
|
||||
}, err
|
||||
}
|
||||
|
||||
func (c *Cache[K, V]) Put(key K, value V) {
|
||||
start := time.Now()
|
||||
c.Cacher.Put(key, value)
|
||||
putDuration := time.Since(start)
|
||||
|
||||
c.metrics.putCount.Inc()
|
||||
c.metrics.putTime.Add(float64(putDuration))
|
||||
c.metrics.len.Set(float64(c.Cacher.Len()))
|
||||
c.metrics.portionFilled.Set(c.Cacher.PortionFilled())
|
||||
}
|
||||
|
||||
func (c *Cache[K, V]) Get(key K) (V, bool) {
|
||||
start := time.Now()
|
||||
value, has := c.Cacher.Get(key)
|
||||
getDuration := time.Since(start)
|
||||
|
||||
if has {
|
||||
c.metrics.getCount.With(hitLabels).Inc()
|
||||
c.metrics.getTime.With(hitLabels).Add(float64(getDuration))
|
||||
} else {
|
||||
c.metrics.getCount.With(missLabels).Inc()
|
||||
c.metrics.getTime.With(missLabels).Add(float64(getDuration))
|
||||
}
|
||||
|
||||
return value, has
|
||||
}
|
||||
|
||||
func (c *Cache[K, _]) Evict(key K) {
|
||||
c.Cacher.Evict(key)
|
||||
c.metrics.len.Set(float64(c.Cacher.Len()))
|
||||
c.metrics.portionFilled.Set(c.Cacher.PortionFilled())
|
||||
}
|
||||
|
||||
func (c *Cache[_, _]) Flush() {
|
||||
c.Cacher.Flush()
|
||||
c.metrics.len.Set(float64(c.Cacher.Len()))
|
||||
c.metrics.portionFilled.Set(c.Cacher.PortionFilled())
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package metercacher
|
||||
|
||||
import (
|
||||
"github.com/luxfi/metric"
|
||||
)
|
||||
|
||||
const (
|
||||
resultLabel = "result"
|
||||
hitResult = "hit"
|
||||
missResult = "miss"
|
||||
)
|
||||
|
||||
var (
|
||||
resultLabels = []string{resultLabel}
|
||||
hitLabels = metric.Labels{
|
||||
resultLabel: hitResult,
|
||||
}
|
||||
missLabels = metric.Labels{
|
||||
resultLabel: missResult,
|
||||
}
|
||||
)
|
||||
|
||||
type cacheMetrics struct {
|
||||
getCount metric.CounterVec
|
||||
getTime metric.GaugeVec
|
||||
|
||||
putCount metric.Counter
|
||||
putTime metric.Gauge
|
||||
|
||||
len metric.Gauge
|
||||
portionFilled metric.Gauge
|
||||
}
|
||||
|
||||
func newMetrics(
|
||||
namespace string,
|
||||
registry metric.Registry,
|
||||
) (*cacheMetrics, error) {
|
||||
metricsInstance := metric.NewWithRegistry(namespace, registry)
|
||||
|
||||
m := &cacheMetrics{
|
||||
getCount: metricsInstance.NewCounterVec(
|
||||
"get_count",
|
||||
"number of get calls",
|
||||
resultLabels,
|
||||
),
|
||||
getTime: metricsInstance.NewGaugeVec(
|
||||
"get_time",
|
||||
"time spent (ns) in get calls",
|
||||
resultLabels,
|
||||
),
|
||||
putCount: metricsInstance.NewCounter(
|
||||
"put_count",
|
||||
"number of put calls",
|
||||
),
|
||||
putTime: metricsInstance.NewGauge(
|
||||
"put_time",
|
||||
"time spent (ns) in put calls",
|
||||
),
|
||||
len: metricsInstance.NewGauge(
|
||||
"len",
|
||||
"number of entries",
|
||||
),
|
||||
portionFilled: metricsInstance.NewGauge(
|
||||
"portion_filled",
|
||||
"fraction of cache filled",
|
||||
),
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
Reference in New Issue
Block a user