1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/mirrors-Gubernator

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
lrucache_test.go 14 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Shawn Poulson Отправлено 20.11.2023 20:12 8f472c0
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
/*
Copyright 2018-2022 Mailgun Technologies Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package gubernator_test
import (
"fmt"
"math/rand"
"strconv"
"sync"
"testing"
"time"
gubernator "github.com/mailgun/gubernator/v2"
"github.com/mailgun/holster/v4/clock"
"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) {
const iterations = 1000
const concurrency = 100
expireAt := clock.Now().Add(1 * time.Hour).UnixMilli()
var mutex sync.Mutex
t.Run("Happy path", func(t *testing.T) {
cache := gubernator.NewLRUCache(0)
// Populate cache.
for i := 0; i < iterations; i++ {
key := strconv.Itoa(i)
item := &gubernator.CacheItem{
Key: key,
Value: i,
ExpireAt: expireAt,
}
mutex.Lock()
exists := cache.Add(item)
mutex.Unlock()
assert.False(t, exists)
}
// Validate cache.
assert.Equal(t, int64(iterations), cache.Size())
for i := 0; i < iterations; i++ {
key := strconv.Itoa(i)
mutex.Lock()
item, ok := cache.GetItem(key)
mutex.Unlock()
require.True(t, ok)
require.NotNil(t, item)
assert.Equal(t, item.Value, i)
}
// Clear cache.
for i := 0; i < iterations; i++ {
key := strconv.Itoa(i)
mutex.Lock()
cache.Remove(key)
mutex.Unlock()
}
assert.Zero(t, cache.Size())
})
t.Run("Update an existing key", func(t *testing.T) {
cache := gubernator.NewLRUCache(0)
const key = "foobar"
// Add key.
item1 := &gubernator.CacheItem{
Key: key,
Value: "initial value",
ExpireAt: expireAt,
}
exists1 := cache.Add(item1)
require.False(t, exists1)
// Update same key.
item2 := &gubernator.CacheItem{
Key: key,
Value: "new value",
ExpireAt: expireAt,
}
exists2 := cache.Add(item2)
require.True(t, exists2)
// Verify.
verifyItem, ok := cache.GetItem(key)
require.True(t, ok)
assert.Equal(t, item2, verifyItem)
})
t.Run("Concurrent reads", func(t *testing.T) {
cache := gubernator.NewLRUCache(0)
// Populate cache.
for i := 0; i < iterations; i++ {
key := strconv.Itoa(i)
item := &gubernator.CacheItem{
Key: key,
Value: i,
ExpireAt: expireAt,
}
exists := cache.Add(item)
assert.False(t, exists)
}
assert.Equal(t, int64(iterations), cache.Size())
var launchWg, doneWg sync.WaitGroup
launchWg.Add(1)
for thread := 0; thread < concurrency; thread++ {
doneWg.Add(1)
go func() {
defer doneWg.Done()
launchWg.Wait()
for i := 0; i < iterations; i++ {
key := strconv.Itoa(i)
mutex.Lock()
item, ok := cache.GetItem(key)
mutex.Unlock()
assert.True(t, ok)
require.NotNil(t, item)
assert.Equal(t, item.Value, i)
}
}()
}
// Wait for goroutines to finish.
launchWg.Done()
doneWg.Wait()
})
t.Run("Concurrent writes", func(t *testing.T) {
cache := gubernator.NewLRUCache(0)
expireAt := clock.Now().Add(1 * time.Hour).UnixMilli()
var launchWg, doneWg sync.WaitGroup
launchWg.Add(1)
for thread := 0; thread < concurrency; thread++ {
doneWg.Add(1)
go func() {
defer doneWg.Done()
launchWg.Wait()
for i := 0; i < iterations; i++ {
key := strconv.Itoa(i)
item := &gubernator.CacheItem{
Key: key,
Value: i,
ExpireAt: expireAt,
}
mutex.Lock()
cache.Add(item)
mutex.Unlock()
}
}()
}
// Wait for goroutines to finish.
launchWg.Done()
doneWg.Wait()
})
t.Run("Concurrent reads and writes", func(t *testing.T) {
cache := gubernator.NewLRUCache(0)
// Populate cache.
for i := 0; i < iterations; i++ {
key := strconv.Itoa(i)
item := &gubernator.CacheItem{
Key: key,
Value: i,
ExpireAt: expireAt,
}
mutex.Lock()
exists := cache.Add(item)
mutex.Unlock()
assert.False(t, exists)
}
assert.Equal(t, int64(iterations), cache.Size())
var launchWg, doneWg sync.WaitGroup
launchWg.Add(1)
for thread := 0; thread < concurrency; thread++ {
doneWg.Add(2)
go func() {
defer doneWg.Done()
launchWg.Wait()
for i := 0; i < iterations; i++ {
key := strconv.Itoa(i)
mutex.Lock()
item, ok := cache.GetItem(key)
mutex.Unlock()
assert.True(t, ok)
require.NotNil(t, item)
assert.Equal(t, item.Value, i)
}
}()
go func() {
defer doneWg.Done()
launchWg.Wait()
for i := 0; i < iterations; i++ {
key := strconv.Itoa(i)
item := &gubernator.CacheItem{
Key: key,
Value: i,
ExpireAt: expireAt,
}
mutex.Lock()
cache.Add(item)
mutex.Unlock()
}
}()
}
// Wait for goroutines to finish.
launchWg.Done()
doneWg.Wait()
})
t.Run("Collect metrics during concurrent reads/writes", func(t *testing.T) {
cache := gubernator.NewLRUCache(0)
// Populate cache.
for i := 0; i < iterations; i++ {
key := strconv.Itoa(i)
item := &gubernator.CacheItem{
Key: key,
Value: i,
ExpireAt: expireAt,
}
mutex.Lock()
cache.Add(item)
mutex.Unlock()
}
assert.Equal(t, int64(iterations), cache.Size())
var launchWg, doneWg sync.WaitGroup
launchWg.Add(1)
for thread := 0; thread < concurrency; thread++ {
doneWg.Add(3)
go func() {
defer doneWg.Done()
launchWg.Wait()
for i := 0; i < iterations; i++ {
// Get, cache hit.
key := strconv.Itoa(i)
mutex.Lock()
_, _ = cache.GetItem(key)
mutex.Unlock()
// Get, cache miss.
key2 := strconv.Itoa(rand.Intn(1000) + 10000)
mutex.Lock()
_, _ = cache.GetItem(key2)
mutex.Unlock()
}
}()
go func() {
defer doneWg.Done()
launchWg.Wait()
for i := 0; i < iterations; i++ {
// Add existing.
key := strconv.Itoa(i)
item := &gubernator.CacheItem{
Key: key,
Value: i,
ExpireAt: expireAt,
}
mutex.Lock()
cache.Add(item)
mutex.Unlock()
// Add new.
key2 := strconv.Itoa(rand.Intn(1000) + 20000)
item2 := &gubernator.CacheItem{
Key: key2,
Value: i,
ExpireAt: expireAt,
}
mutex.Lock()
cache.Add(item2)
mutex.Unlock()
}
}()
collector := gubernator.NewLRUCacheCollector()
collector.AddCache(cache)
go func() {
defer doneWg.Done()
launchWg.Wait()
for i := 0; i < iterations; i++ {
// Get metrics.
ch := make(chan prometheus.Metric, 10)
collector.Collect(ch)
}
}()
}
// Wait for goroutines to finish.
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) {
var mutex sync.Mutex
b.Run("Sequential reads", func(b *testing.B) {
cache := gubernator.NewLRUCache(b.N)
expireAt := clock.Now().Add(1 * time.Hour).UnixMilli()
// Populate cache.
for i := 0; i < b.N; i++ {
key := strconv.Itoa(i)
item := &gubernator.CacheItem{
Key: key,
Value: i,
ExpireAt: expireAt,
}
exists := cache.Add(item)
assert.False(b, exists)
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
key := strconv.Itoa(i)
mutex.Lock()
_, _ = cache.GetItem(key)
mutex.Unlock()
}
})
b.Run("Sequential writes", func(b *testing.B) {
cache := gubernator.NewLRUCache(0)
expireAt := clock.Now().Add(1 * time.Hour).UnixMilli()
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
key := strconv.Itoa(i)
item := &gubernator.CacheItem{
Key: key,
Value: i,
ExpireAt: expireAt,
}
mutex.Lock()
cache.Add(item)
mutex.Unlock()
}
})
b.Run("Concurrent reads", func(b *testing.B) {
cache := gubernator.NewLRUCache(b.N)
expireAt := clock.Now().Add(1 * time.Hour).UnixMilli()
// Populate cache.
for i := 0; i < b.N; i++ {
key := strconv.Itoa(i)
item := &gubernator.CacheItem{
Key: key,
Value: i,
ExpireAt: expireAt,
}
exists := cache.Add(item)
assert.False(b, exists)
}
var launchWg, doneWg sync.WaitGroup
launchWg.Add(1)
for i := 0; i < b.N; i++ {
key := strconv.Itoa(i)
doneWg.Add(1)
go func() {
defer doneWg.Done()
launchWg.Wait()
mutex.Lock()
_, _ = cache.GetItem(key)
mutex.Unlock()
}()
}
b.ReportAllocs()
b.ResetTimer()
launchWg.Done()
doneWg.Wait()
})
b.Run("Concurrent writes", func(b *testing.B) {
cache := gubernator.NewLRUCache(0)
expireAt := clock.Now().Add(1 * time.Hour).UnixMilli()
var launchWg, doneWg sync.WaitGroup
launchWg.Add(1)
for i := 0; i < b.N; i++ {
key := strconv.Itoa(i)
doneWg.Add(1)
go func(i int) {
defer doneWg.Done()
launchWg.Wait()
item := &gubernator.CacheItem{
Key: key,
Value: i,
ExpireAt: expireAt,
}
mutex.Lock()
cache.Add(item)
mutex.Unlock()
}(i)
}
b.ReportAllocs()
b.ResetTimer()
launchWg.Done()
doneWg.Wait()
})
b.Run("Concurrent reads and writes of existing keys", func(b *testing.B) {
cache := gubernator.NewLRUCache(0)
expireAt := clock.Now().Add(1 * time.Hour).UnixMilli()
var launchWg, doneWg sync.WaitGroup
launchWg.Add(1)
// Populate cache.
for i := 0; i < b.N; i++ {
key := strconv.Itoa(i)
item := &gubernator.CacheItem{
Key: key,
Value: i,
ExpireAt: expireAt,
}
exists := cache.Add(item)
assert.False(b, exists)
}
for i := 0; i < b.N; i++ {
key := strconv.Itoa(i)
doneWg.Add(2)
go func() {
defer doneWg.Done()
launchWg.Wait()
mutex.Lock()
_, _ = cache.GetItem(key)
mutex.Unlock()
}()
go func(i int) {
defer doneWg.Done()
launchWg.Wait()
item := &gubernator.CacheItem{
Key: key,
Value: i,
ExpireAt: expireAt,
}
mutex.Lock()
cache.Add(item)
mutex.Unlock()
}(i)
}
b.ReportAllocs()
b.ResetTimer()
launchWg.Done()
doneWg.Wait()
})
b.Run("Concurrent reads and writes of non-existent keys", func(b *testing.B) {
cache := gubernator.NewLRUCache(0)
expireAt := clock.Now().Add(1 * time.Hour).UnixMilli()
var launchWg, doneWg sync.WaitGroup
launchWg.Add(1)
for i := 0; i < b.N; i++ {
doneWg.Add(2)
go func(i int) {
defer doneWg.Done()
launchWg.Wait()
key := strconv.Itoa(i)
mutex.Lock()
_, _ = cache.GetItem(key)
mutex.Unlock()
}(i)
go func(i int) {
defer doneWg.Done()
launchWg.Wait()
key := "z" + strconv.Itoa(i)
item := &gubernator.CacheItem{
Key: key,
Value: i,
ExpireAt: expireAt,
}
mutex.Lock()
cache.Add(item)
mutex.Unlock()
}(i)
}
b.ReportAllocs()
b.ResetTimer()
launchWg.Done()
doneWg.Wait()
})
}

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/mirrors-Gubernator.git
git@api.gitlife.ru:oschina-mirror/mirrors-Gubernator.git
oschina-mirror
mirrors-Gubernator
mirrors-Gubernator
master