Skip to content

Commit

Permalink
add impl for lru sized cache
Browse files Browse the repository at this point in the history
  • Loading branch information
ahrav committed Sep 27, 2024
1 parent 5f3b452 commit 16c1db1
Show file tree
Hide file tree
Showing 7 changed files with 616 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ require (
github.com/coinbase/waas-client-library-go v1.0.8
github.com/couchbase/gocb/v2 v2.9.2
github.com/crewjam/rfc5424 v0.1.0
github.com/dgraph-io/ristretto v1.0.0
github.com/dustin/go-humanize v1.0.1
github.com/elastic/go-elasticsearch/v8 v8.15.0
github.com/envoyproxy/protoc-gen-validate v1.1.0
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@ github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnG
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/ristretto v1.0.0 h1:SYG07bONKMlFDUYu5pEu3DGAh8c2OFNzKm6G9J4Si84=
github.com/dgraph-io/ristretto v1.0.0/go.mod h1:jTi2FiYEhQ1NsMmA7DeBykizjOuY88NhKBkepyu1jPc=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
Expand Down
64 changes: 64 additions & 0 deletions pkg/cache/decorator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package cache

// WithMetrics is a decorator that adds metrics collection to any Cache implementation.
type WithMetrics[T any] struct {
wrapped Cache[T]
metrics BaseMetricsCollector
cacheName string
}

// NewCacheWithMetrics creates a new WithMetrics decorator that wraps the provided Cache
// and collects metrics using the provided BaseMetricsCollector.
// The cacheName parameter is used to identify the cache in the collected metrics.
func NewCacheWithMetrics[T any](wrapped Cache[T], metrics BaseMetricsCollector, cacheName string) *WithMetrics[T] {
return &WithMetrics[T]{
wrapped: wrapped,
metrics: metrics,
cacheName: cacheName,
}
}

// Set sets the value for the given key in the cache. It also records a set metric
// for the cache using the provided metrics collector and cache name.
func (c *WithMetrics[T]) Set(key string, val T) {
c.metrics.RecordSet(c.cacheName)
c.wrapped.Set(key, val)
}

// Get retrieves the value for the given key from the underlying cache. It also records
// a hit or miss metric for the cache using the provided metrics collector and cache name.
func (c *WithMetrics[T]) Get(key string) (T, bool) {
val, found := c.wrapped.Get(key)
if found {
c.metrics.RecordHit(c.cacheName)
} else {
c.metrics.RecordMiss(c.cacheName)
}
return val, found
}

// Exists checks if the given key exists in the cache. It records a hit or miss metric
// for the cache using the provided metrics collector and cache name.
func (c *WithMetrics[T]) Exists(key string) bool {
found := c.wrapped.Exists(key)
if found {
c.metrics.RecordHit(c.cacheName)
} else {
c.metrics.RecordMiss(c.cacheName)
}
return found
}

// Delete removes the value for the given key from the cache. It also records a delete metric
// for the cache using the provided metrics collector and cache name.
func (c *WithMetrics[T]) Delete(key string) {
c.wrapped.Delete(key)
c.metrics.RecordDelete(c.cacheName)
}

// Clear removes all entries from the cache. It also records a clear metric
// for the cache using the provided metrics collector and cache name.
func (c *WithMetrics[T]) Clear() {
c.wrapped.Clear()
c.metrics.RecordClear(c.cacheName)
}
107 changes: 107 additions & 0 deletions pkg/cache/metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package cache

import (
"sync"

"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)

// BaseMetricsCollector defines the interface for recording cache metrics.
// Each method corresponds to a specific cache-related operation.
type BaseMetricsCollector interface {
RecordHit(cacheName string)
RecordMiss(cacheName string)
RecordSet(cacheName string)
RecordDelete(cacheName string)
RecordClear(cacheName string)
}

// MetricsCollector encapsulates all Prometheus metrics with labels.
// It holds Prometheus counters for cache operations, which help track
// the performance and usage of the cache.
type MetricsCollector struct {
// Base metrics.
hits *prometheus.CounterVec
misses *prometheus.CounterVec
sets *prometheus.CounterVec
deletes *prometheus.CounterVec
clears *prometheus.CounterVec
}

var (
collectorOnce sync.Once // Ensures that the collector is initialized only once.
collector *MetricsCollector
)

// InitializeMetrics initializes the singleton MetricsCollector.
// It sets up Prometheus counters for cache operations (hits, misses, sets, deletes, clears).
// Should be called once at the start of the application.
func InitializeMetrics(namespace, subsystem string) {
collectorOnce.Do(func() {
collector = &MetricsCollector{
hits: promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "hits_total",
Help: "Total number of cache hits.",
}, []string{"cache_name"}),

misses: promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "misses_total",
Help: "Total number of cache misses.",
}, []string{"cache_name"}),

sets: promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "sets_total",
Help: "Total number of cache set operations.",
}, []string{"cache_name"}),

deletes: promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "deletes_total",
Help: "Total number of cache delete operations.",
}, []string{"cache_name"}),

clears: promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "clears_total",
Help: "Total number of cache clear operations.",
}, []string{"cache_name"}),
}
})
}

// GetMetricsCollector returns the singleton MetricsCollector instance.
// It panics if InitializeMetrics has not been called to ensure metrics are properly initialized.
// Must be called after InitializeMetrics to avoid runtime issues.
// If you do it before, BAD THINGS WILL HAPPEN.
func GetMetricsCollector() *MetricsCollector {
if collector == nil {
panic("MetricsCollector not initialized. Call InitializeMetrics first.")
}
return collector
}

// Implement BaseMetricsCollector interface methods.

// RecordHit increments the counter for cache hits, tracking how often cache lookups succeed.
func (m *MetricsCollector) RecordHit(cacheName string) { m.hits.WithLabelValues(cacheName).Inc() }

// RecordMiss increments the counter for cache misses, tracking how often cache lookups fail.
func (m *MetricsCollector) RecordMiss(cacheName string) { m.misses.WithLabelValues(cacheName).Inc() }

// RecordSet increments the counter for cache set operations, tracking how often items are added/updated.
func (m *MetricsCollector) RecordSet(cacheName string) { m.sets.WithLabelValues(cacheName).Inc() }

// RecordDelete increments the counter for cache delete operations, tracking how often items are removed.
func (m *MetricsCollector) RecordDelete(cacheName string) { m.deletes.WithLabelValues(cacheName).Inc() }

// RecordClear increments the counter for cache clear operations, tracking how often the cache is completely cleared.
func (m *MetricsCollector) RecordClear(cacheName string) { m.clears.WithLabelValues(cacheName).Inc() }
69 changes: 69 additions & 0 deletions pkg/cache/sizedlru/metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package sizedlru

import (
"github.com/prometheus/client_golang/prometheus"

"github.com/trufflesecurity/trufflehog/v3/pkg/cache"
)

// MetricsCollector should implement the collector interface.
var _ collector = (*MetricsCollector)(nil)

// MetricsCollector extends the BaseMetricsCollector with Sized LRU specific metrics.
// It provides methods to record cache hits, misses, and evictions.
type MetricsCollector struct {
// BaseMetricsCollector is embedded to provide the base metrics functionality.
cache.BaseMetricsCollector

totalHits *prometheus.GaugeVec
totalMisses *prometheus.GaugeVec
totalEvicts *prometheus.GaugeVec
}

// NewSizedLRUMetricsCollector initializes a new MetricsCollector with the provided namespace and subsystem.
func NewSizedLRUMetricsCollector(namespace, subsystem string) *MetricsCollector {
base := cache.GetMetricsCollector()

totalHits := prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "total_hits",
Help: "Total number of cache hits.",
}, []string{"cache_name"})

totalMisses := prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "total_misses",
Help: "Total number of cache misses.",
}, []string{"cache_name"})

totalEvicts := prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "total_evicts",
Help: "Total number of cache evictions.",
}, []string{"cache_name"})

return &MetricsCollector{
BaseMetricsCollector: base,
totalHits: totalHits,
totalMisses: totalMisses,
totalEvicts: totalEvicts,
}
}

// RecordHits updates the total hits metric for the given cache name.
func (c *MetricsCollector) RecordHits(cacheName string, hits uint64) {
c.totalHits.WithLabelValues(cacheName).Set(float64(hits))
}

// RecordMisses updates the total misses metric for the given cache name.
func (c *MetricsCollector) RecordMisses(cacheName string, misses uint64) {
c.totalMisses.WithLabelValues(cacheName).Set(float64(misses))
}

// RecordEvictions updates the total evictions metric for the given cache name.
func (c *MetricsCollector) RecordEvictions(cacheName string, evictions uint64) {
c.totalEvicts.WithLabelValues(cacheName).Set(float64(evictions))
}
Loading

0 comments on commit 16c1db1

Please sign in to comment.