Skip to content
This repository has been archived by the owner on Apr 19, 2024. It is now read-only.

Commit

Permalink
Add a metric for monitoring how many evictions involve unexpired items
Browse files Browse the repository at this point in the history
  • Loading branch information
dennison committed Feb 18, 2022
1 parent 47b7b2d commit 0038668
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.0.0-rc.15] - 2022-02-18
## Changes
* Added metric to track number of cache evictions which involve unexpired entries.

## [2.0.0-rc.14] - 2022-02-17
## Changes
* Added performance optimizations to ensure batching behavior does not cause
Expand Down
12 changes: 12 additions & 0 deletions lrucache.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ var accessMetric = prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "gubernator_cache_access_count",
Help: "Cache access counts. Label \"type\" = hit|miss.",
}, []string{"type"})
var unexpiredEvictionsMetric = prometheus.NewCounter(prometheus.CounterOpts{
Name: "gubernator_unexpired_evictions_count",
Help: "Count the number of cache items which were evicted while unexpired.",
})

// New creates a new Cache with a maximum size.
func NewLRUCache(maxSize int) *LRUCache {
Expand Down Expand Up @@ -144,6 +148,12 @@ func (c *LRUCache) Remove(key string) {
func (c *LRUCache) removeOldest() {
ele := c.ll.Back()
if ele != nil {
entry := ele.Value.(*CacheItem)

if MillisecondNow() < entry.ExpireAt {
unexpiredEvictionsMetric.Add(1)
}

c.removeElement(ele)
}
}
Expand Down Expand Up @@ -192,13 +202,15 @@ func (collector *LRUCacheCollector) AddCache(cache Cache) {
func (collector *LRUCacheCollector) Describe(ch chan<- *prometheus.Desc) {
sizeMetric.Describe(ch)
accessMetric.Describe(ch)
unexpiredEvictionsMetric.Describe(ch)
}

// Collect fetches metric counts and gauges from the cache
func (collector *LRUCacheCollector) Collect(ch chan<- prometheus.Metric) {
sizeMetric.Set(collector.getSize())
sizeMetric.Collect(ch)
accessMetric.Collect(ch)
unexpiredEvictionsMetric.Collect(ch)
}

func (collector *LRUCacheCollector) getSize() float64 {
Expand Down
94 changes: 94 additions & 0 deletions lrucache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package gubernator_test

import (
"fmt"
"math/rand"
"strconv"
"sync"
Expand All @@ -28,6 +29,8 @@ import (
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

dto "github.com/prometheus/client_model/go"
)

func TestLRUCache(t *testing.T) {
Expand Down Expand Up @@ -332,6 +335,97 @@ func TestLRUCache(t *testing.T) {
launchWg.Done()
doneWg.Wait()
})

t.Run("Check gubernator_unexpired_evictions_count metric is not incremented when expired item is evicted", func(t *testing.T) {
defer clock.Freeze(clock.Now()).Unfreeze()

promRegister := prometheus.NewRegistry()

// The LRU cache for storing rate limits.
cacheCollector := gubernator.NewLRUCacheCollector()
err := promRegister.Register(cacheCollector)
require.NoError(t, err)

cache := gubernator.NewLRUCache(10)
cacheCollector.AddCache(cache)

// fill cache with short duration cache items
for i := 0; i < 10; i++ {
cache.Add(&gubernator.CacheItem{
Algorithm: gubernator.Algorithm_LEAKY_BUCKET,
Key: fmt.Sprintf("short-expiry-%d", i),
Value: "bar",
ExpireAt: clock.Now().Add(5 * time.Minute).UnixMilli(),
})
}

// jump forward in time to expire all short duration keys
clock.Advance(6 * time.Minute)

// add a new cache item to force eviction
cache.Add(&gubernator.CacheItem{
Algorithm: gubernator.Algorithm_LEAKY_BUCKET,
Key: "evict1",
Value: "bar",
ExpireAt: clock.Now().Add(1 * time.Hour).UnixMilli(),
})

collChan := make(chan prometheus.Metric, 64)
cacheCollector.Collect(collChan)
// Check metrics to verify evicted cache key is expired
<-collChan
<-collChan
<-collChan
m := <-collChan // gubernator_unexpired_evictions_count
met := new(dto.Metric)
m.Write(met)
assert.Contains(t, m.Desc().String(), "gubernator_unexpired_evictions_count")
assert.Equal(t, 0, int(*met.Counter.Value))
})

t.Run("Check gubernator_unexpired_evictions_count metric is incremented when unexpired item is evicted", func(t *testing.T) {
defer clock.Freeze(clock.Now()).Unfreeze()

promRegister := prometheus.NewRegistry()

// The LRU cache for storing rate limits.
cacheCollector := gubernator.NewLRUCacheCollector()
err := promRegister.Register(cacheCollector)
require.NoError(t, err)

cache := gubernator.NewLRUCache(10)
cacheCollector.AddCache(cache)

// fill cache with long duration cache items
for i := 0; i < 10; i++ {
cache.Add(&gubernator.CacheItem{
Algorithm: gubernator.Algorithm_LEAKY_BUCKET,
Key: fmt.Sprintf("long-expiry-%d", i),
Value: "bar",
ExpireAt: clock.Now().Add(1 * time.Hour).UnixMilli(),
})
}

// add a new cache item to force eviction
cache.Add(&gubernator.CacheItem{
Algorithm: gubernator.Algorithm_LEAKY_BUCKET,
Key: "evict2",
Value: "bar",
ExpireAt: clock.Now().Add(1 * time.Hour).UnixMilli(),
})

// Check metrics to verify evicted cache key is *NOT* expired
collChan := make(chan prometheus.Metric, 64)
cacheCollector.Collect(collChan)
<-collChan
<-collChan
<-collChan
m := <-collChan // gubernator_unexpired_evictions_count
met := new(dto.Metric)
m.Write(met)
assert.Contains(t, m.Desc().String(), "gubernator_unexpired_evictions_count")
assert.Equal(t, 1, int(*met.Counter.Value))
})
}

func BenchmarkLRUCache(b *testing.B) {
Expand Down
2 changes: 1 addition & 1 deletion version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v2.0.0-rc.14
v2.0.0-rc.15

0 comments on commit 0038668

Please sign in to comment.