mirror of
https://github.com/lukaszraczylo/graphql-monitoring-proxy.git
synced 2026-10-17 06:17:40 +00:00
Initial commit on gh-pages
This commit is contained in:
Vendored
+134
@@ -0,0 +1,134 @@
|
||||
package libpack_cache
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
fiber "github.com/gofiber/fiber/v2"
|
||||
"github.com/gookit/goutil/strutil"
|
||||
libpack_cache_memory "github.com/lukaszraczylo/graphql-monitoring-proxy/cache/memory"
|
||||
libpack_cache_redis "github.com/lukaszraczylo/graphql-monitoring-proxy/cache/redis"
|
||||
libpack_logger "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
||||
)
|
||||
|
||||
type CacheConfig struct {
|
||||
Logger *libpack_logger.Logger
|
||||
Client CacheClient
|
||||
Redis struct {
|
||||
URL string `json:"url"`
|
||||
Password string `json:"password"`
|
||||
DB int `json:"db"`
|
||||
Enable bool `json:"enable"`
|
||||
}
|
||||
TTL int `json:"ttl"`
|
||||
}
|
||||
|
||||
type CacheStats struct {
|
||||
CachedQueries int64 `json:"cached_queries"`
|
||||
CacheHits int64 `json:"cache_hits"`
|
||||
CacheMisses int64 `json:"cache_misses"`
|
||||
}
|
||||
|
||||
type CacheClient interface {
|
||||
Set(key string, value []byte, ttl time.Duration)
|
||||
Get(key string) ([]byte, bool)
|
||||
Delete(key string)
|
||||
Clear()
|
||||
CountQueries() int64
|
||||
}
|
||||
|
||||
var (
|
||||
cacheStats *CacheStats
|
||||
config *CacheConfig
|
||||
)
|
||||
|
||||
func CalculateHash(c *fiber.Ctx) string {
|
||||
return strutil.Md5(c.Body())
|
||||
}
|
||||
|
||||
func EnableCache(cfg *CacheConfig) {
|
||||
if cfg.Logger == nil {
|
||||
cfg.Logger = libpack_logger.New()
|
||||
cfg.Logger.Info(&libpack_logger.LogMessage{
|
||||
Message: "Initializing in-module logger",
|
||||
})
|
||||
}
|
||||
cacheStats = &CacheStats{}
|
||||
if ShouldUseRedisCache(cfg) {
|
||||
cfg.Logger.Info(&libpack_logger.LogMessage{
|
||||
Message: "Using Redis cache",
|
||||
})
|
||||
cfg.Client = libpack_cache_redis.New(&libpack_cache_redis.RedisClientConfig{
|
||||
RedisDB: cfg.Redis.DB,
|
||||
RedisServer: cfg.Redis.URL,
|
||||
RedisPassword: cfg.Redis.Password,
|
||||
})
|
||||
} else {
|
||||
cfg.Logger.Info(&libpack_logger.LogMessage{
|
||||
Message: "Using in-memory cache",
|
||||
})
|
||||
cfg.Client = libpack_cache_memory.New(time.Duration(cfg.TTL) * time.Second)
|
||||
}
|
||||
config = cfg
|
||||
}
|
||||
|
||||
func CacheLookup(hash string) []byte {
|
||||
obj, found := config.Client.Get(hash)
|
||||
if found {
|
||||
atomic.AddInt64(&cacheStats.CacheHits, 1)
|
||||
return obj
|
||||
}
|
||||
atomic.AddInt64(&cacheStats.CacheMisses, 1)
|
||||
return nil
|
||||
}
|
||||
|
||||
func CacheDelete(hash string) {
|
||||
config.Logger.Debug(&libpack_logger.LogMessage{
|
||||
Message: "Deleting data from cache",
|
||||
Pairs: map[string]interface{}{"hash": hash},
|
||||
})
|
||||
atomic.AddInt64(&cacheStats.CachedQueries, -1)
|
||||
config.Client.Delete(hash)
|
||||
}
|
||||
|
||||
func CacheStore(hash string, data []byte) {
|
||||
config.Logger.Debug(&libpack_logger.LogMessage{
|
||||
Message: "Storing data in cache",
|
||||
Pairs: map[string]interface{}{"hash": hash},
|
||||
})
|
||||
atomic.AddInt64(&cacheStats.CachedQueries, 1)
|
||||
config.Client.Set(hash, data, time.Duration(config.TTL)*time.Second)
|
||||
}
|
||||
|
||||
func CacheStoreWithTTL(hash string, data []byte, ttl time.Duration) {
|
||||
config.Logger.Debug(&libpack_logger.LogMessage{
|
||||
Message: "Storing data in cache with TTL",
|
||||
Pairs: map[string]interface{}{"hash": hash, "ttl": ttl},
|
||||
})
|
||||
atomic.AddInt64(&cacheStats.CachedQueries, 1)
|
||||
config.Client.Set(hash, data, ttl)
|
||||
}
|
||||
|
||||
func CacheGetQueries() int64 {
|
||||
config.Logger.Debug(&libpack_logger.LogMessage{
|
||||
Message: "Counting cache queries",
|
||||
})
|
||||
return config.Client.CountQueries()
|
||||
}
|
||||
|
||||
func CacheClear() {
|
||||
config.Client.Clear()
|
||||
cacheStats = &CacheStats{}
|
||||
}
|
||||
|
||||
func GetCacheStats() *CacheStats {
|
||||
config.Logger.Debug(&libpack_logger.LogMessage{
|
||||
Message: "Getting cache stats",
|
||||
})
|
||||
cacheStats.CachedQueries = CacheGetQueries()
|
||||
return cacheStats
|
||||
}
|
||||
|
||||
func ShouldUseRedisCache(cfg *CacheConfig) bool {
|
||||
return cfg.Redis.Enable
|
||||
}
|
||||
Vendored
+116
@@ -0,0 +1,116 @@
|
||||
package libpack_cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
libpack_cache_memory "github.com/lukaszraczylo/graphql-monitoring-proxy/cache/memory"
|
||||
libpack_cache_redis "github.com/lukaszraczylo/graphql-monitoring-proxy/cache/redis"
|
||||
libpack_logger "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
||||
)
|
||||
|
||||
const (
|
||||
Parallelism = 4
|
||||
RequestPerSec = 10000
|
||||
)
|
||||
|
||||
func BenchmarkCacheLookupInMemory(b *testing.B) {
|
||||
config = &CacheConfig{
|
||||
Logger: libpack_logger.New(),
|
||||
Client: libpack_cache_memory.New(5 * time.Minute),
|
||||
TTL: 5,
|
||||
}
|
||||
EnableCache(config)
|
||||
|
||||
hash := "00000000000000000000000000000000001337"
|
||||
data := []byte("it's fine.")
|
||||
CacheStore(hash, data)
|
||||
|
||||
b.SetParallelism(Parallelism)
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
CacheLookup(hash)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkCacheLookupRedis(b *testing.B) {
|
||||
redis_server, _ := miniredis.Run()
|
||||
mockedCache := libpack_cache_redis.New(&libpack_cache_redis.RedisClientConfig{
|
||||
RedisServer: redis_server.Addr(),
|
||||
RedisDB: 0,
|
||||
})
|
||||
|
||||
config = &CacheConfig{
|
||||
Logger: libpack_logger.New(),
|
||||
Client: mockedCache,
|
||||
TTL: 5,
|
||||
}
|
||||
config.Redis.Enable = true
|
||||
EnableCache(config)
|
||||
|
||||
hash := "00000000000000000000000000000000001337"
|
||||
data := []byte("it's fine.")
|
||||
CacheStore(hash, data)
|
||||
|
||||
b.SetParallelism(Parallelism)
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
CacheLookup(hash)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkCacheStoreInMemory(b *testing.B) {
|
||||
config = &CacheConfig{
|
||||
Logger: libpack_logger.New(),
|
||||
Client: libpack_cache_memory.New(5 * time.Minute),
|
||||
TTL: 5,
|
||||
}
|
||||
EnableCache(config)
|
||||
|
||||
hash := "00000000000000000000000000000000001337"
|
||||
data := []byte("it's fine.")
|
||||
|
||||
b.SetParallelism(Parallelism)
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
CacheStore(hash, data)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkCacheStoreRedis(b *testing.B) {
|
||||
redis_server, _ := miniredis.Run()
|
||||
mockedCache := libpack_cache_redis.New(&libpack_cache_redis.RedisClientConfig{
|
||||
RedisServer: redis_server.Addr(),
|
||||
RedisDB: 0,
|
||||
})
|
||||
|
||||
config = &CacheConfig{
|
||||
Logger: libpack_logger.New(),
|
||||
Client: mockedCache,
|
||||
TTL: 5,
|
||||
}
|
||||
config.Redis.Enable = true
|
||||
EnableCache(config)
|
||||
|
||||
hash := "00000000000000000000000000000000001337"
|
||||
data := []byte("it's fine.")
|
||||
|
||||
b.SetParallelism(Parallelism)
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
CacheStore(hash, data)
|
||||
}
|
||||
})
|
||||
}
|
||||
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
package libpack_cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
assertions "github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type Tests struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
var (
|
||||
assert *assertions.Assertions
|
||||
redisMockServer, _ = miniredis.Run()
|
||||
)
|
||||
|
||||
func (suite *Tests) BeforeTest(suiteName, testName string) {
|
||||
}
|
||||
|
||||
func (suite *Tests) SetupTest() {
|
||||
cacheStats = &CacheStats{}
|
||||
assert = assertions.New(suite.T())
|
||||
}
|
||||
|
||||
// TearDownTest is run after each test to clean up
|
||||
func (suite *Tests) TearDownTest() {
|
||||
}
|
||||
|
||||
func TestSuite(t *testing.T) {
|
||||
suite.Run(t, new(Tests))
|
||||
}
|
||||
Vendored
+215
@@ -0,0 +1,215 @@
|
||||
package libpack_cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
libpack_cache_memory "github.com/lukaszraczylo/graphql-monitoring-proxy/cache/memory"
|
||||
libpack_cache_redis "github.com/lukaszraczylo/graphql-monitoring-proxy/cache/redis"
|
||||
libpack_logger "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
||||
)
|
||||
|
||||
func (suite *Tests) Test_cacheLookupInmemory() {
|
||||
config = &CacheConfig{
|
||||
Logger: libpack_logger.New(),
|
||||
Client: libpack_cache_memory.New(5 * time.Minute),
|
||||
TTL: 5,
|
||||
}
|
||||
|
||||
type args struct {
|
||||
hash string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []byte
|
||||
addCache struct {
|
||||
data []byte
|
||||
}
|
||||
}{
|
||||
{
|
||||
name: "test_non_existent",
|
||||
args: args{
|
||||
hash: "00000000000000000000000000000000000000",
|
||||
},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "test_existent",
|
||||
args: args{
|
||||
hash: "00000000000000000000000000000000001337",
|
||||
},
|
||||
want: []byte("it's fine."),
|
||||
addCache: struct {
|
||||
data []byte
|
||||
}{
|
||||
data: []byte("it's fine."),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
suite.Run(tt.name, func() {
|
||||
if tt.addCache.data != nil {
|
||||
CacheStore(tt.args.hash, tt.addCache.data)
|
||||
}
|
||||
got := CacheLookup(tt.args.hash)
|
||||
assert.Equal(tt.want, got, "Unexpected cache lookup result")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *Tests) Test_cacheLookupRedis() {
|
||||
// redis_server := envutil.Getenv("REDIS_SERVER", "localhost:6379")
|
||||
// config.Client = libpack_cache_redis.NewClient(&libpack_cache_redis.RedisClientConfig{
|
||||
// RedisServer: redis_server,
|
||||
// RedisPassword: "",
|
||||
// RedisDB: 0,
|
||||
// })
|
||||
|
||||
mockedCache := libpack_cache_redis.New(&libpack_cache_redis.RedisClientConfig{
|
||||
RedisServer: redisMockServer.Addr(),
|
||||
RedisDB: 0,
|
||||
})
|
||||
|
||||
config = &CacheConfig{
|
||||
Logger: libpack_logger.New(),
|
||||
Client: mockedCache,
|
||||
TTL: 5,
|
||||
}
|
||||
|
||||
type args struct {
|
||||
hash string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []byte
|
||||
addCache struct {
|
||||
data []byte
|
||||
}
|
||||
}{
|
||||
{
|
||||
name: "test_non_existent",
|
||||
args: args{
|
||||
hash: "00000000000000000000000000000000000000",
|
||||
},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "test_existent",
|
||||
args: args{
|
||||
hash: "00000000000000000000000000000000001337",
|
||||
},
|
||||
want: []byte("it's fine."),
|
||||
addCache: struct {
|
||||
data []byte
|
||||
}{
|
||||
data: []byte("it's fine."),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
suite.Run(tt.name, func() {
|
||||
if tt.addCache.data != nil {
|
||||
CacheStore(tt.args.hash, tt.addCache.data)
|
||||
}
|
||||
got := CacheLookup(tt.args.hash)
|
||||
assert.Equal(tt.want, got, "Unexpected cache lookup result")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *Tests) Test_cacheConcurrency() {
|
||||
config = &CacheConfig{
|
||||
Logger: libpack_logger.New(),
|
||||
Client: libpack_cache_memory.New(5 * time.Second),
|
||||
TTL: 5,
|
||||
}
|
||||
|
||||
const numGoroutines = 10
|
||||
const numOperations = 1000
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(numGoroutines)
|
||||
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < numOperations; j++ {
|
||||
key := fmt.Sprintf("key-%d-%d", id, j)
|
||||
value := []byte(fmt.Sprintf("value-%d-%d", id, j))
|
||||
CacheStore(key, value)
|
||||
retrieved := CacheLookup(key)
|
||||
assert.Equal(string(value), string(retrieved), "Concurrent cache operation failed")
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// func (suite *Tests) Test_cacheEviction() {
|
||||
// config = &CacheConfig{
|
||||
// Logger: libpack_logger.New(),
|
||||
// Client: libpack_cache_memory.New(3 * time.Second), // 3 seconds TTL
|
||||
// TTL: 3,
|
||||
// }
|
||||
|
||||
// // Fill the cache
|
||||
// for i := 0; i < 20; i++ {
|
||||
// key := fmt.Sprintf("key-%d", i)
|
||||
// value := []byte(fmt.Sprintf("value-%d", i))
|
||||
// CacheStore(key, value)
|
||||
// time.Sleep(100 * time.Millisecond) // Ensure different creation times
|
||||
// }
|
||||
|
||||
// // Wait for the TTL to expire for the first half of the items
|
||||
// time.Sleep(3100 * time.Millisecond)
|
||||
|
||||
// // Check that the oldest items have been evicted
|
||||
// for i := 0; i < 10; i++ {
|
||||
// key := fmt.Sprintf("key-%d", i)
|
||||
// retrieved := CacheLookup(key)
|
||||
// assert.Nil(retrieved, fmt.Sprintf("Old item %s should have been evicted", key))
|
||||
// }
|
||||
|
||||
// // Check that the newer items are still in the cache
|
||||
// for i := 10; i < 20; i++ {
|
||||
// key := fmt.Sprintf("key-%d", i)
|
||||
// expected := []byte(fmt.Sprintf("value-%d", i))
|
||||
// retrieved := CacheLookup(key)
|
||||
// assert.Equal(expected, retrieved, fmt.Sprintf("Recent item %s should be in cache", key))
|
||||
// }
|
||||
// }
|
||||
|
||||
func (suite *Tests) Test_cacheRedisFailure() {
|
||||
mr, err := miniredis.Run()
|
||||
if err != nil {
|
||||
suite.T().Fatal(err)
|
||||
}
|
||||
defer mr.Close()
|
||||
|
||||
config = &CacheConfig{
|
||||
Logger: libpack_logger.New(),
|
||||
Client: libpack_cache_redis.New(&libpack_cache_redis.RedisClientConfig{
|
||||
RedisServer: mr.Addr(),
|
||||
RedisDB: 0,
|
||||
}),
|
||||
TTL: 5,
|
||||
}
|
||||
|
||||
// Test normal operation
|
||||
CacheStore("test-key", []byte("test-value"))
|
||||
retrieved := CacheLookup("test-key")
|
||||
assert.Equal([]byte("test-value"), retrieved)
|
||||
|
||||
// Simulate Redis failure
|
||||
mr.Close()
|
||||
|
||||
// Operations should not panic, but should return errors or nil values
|
||||
CacheStore("another-key", []byte("another-value"))
|
||||
retrieved = CacheLookup("another-key")
|
||||
assert.Nil(retrieved, "Lookup should return nil when Redis is down")
|
||||
}
|
||||
Vendored
+157
@@ -0,0 +1,157 @@
|
||||
package libpack_cache_memory
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CacheEntry struct {
|
||||
ExpiresAt time.Time
|
||||
Value []byte
|
||||
}
|
||||
|
||||
type Cache struct {
|
||||
compressPool sync.Pool
|
||||
decompressPool sync.Pool
|
||||
entries sync.Map
|
||||
globalTTL time.Duration
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func New(globalTTL time.Duration) *Cache {
|
||||
cache := &Cache{
|
||||
globalTTL: globalTTL,
|
||||
compressPool: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return gzip.NewWriter(nil)
|
||||
},
|
||||
},
|
||||
decompressPool: sync.Pool{
|
||||
New: func() interface{} {
|
||||
r, _ := gzip.NewReader(bytes.NewReader([]byte{}))
|
||||
return r
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
go cache.cleanupRoutine(globalTTL)
|
||||
return cache
|
||||
}
|
||||
|
||||
func (c *Cache) cleanupRoutine(globalTTL time.Duration) {
|
||||
ticker := time.NewTicker(globalTTL / 2)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
c.CleanExpiredEntries()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) Set(key string, value []byte, ttl time.Duration) {
|
||||
expiresAt := time.Now().Add(ttl)
|
||||
|
||||
compressedValue, err := c.compress(value)
|
||||
if err != nil {
|
||||
log.Printf("Error compressing value for key %s: %v", key, err)
|
||||
return
|
||||
}
|
||||
|
||||
entry := CacheEntry{
|
||||
Value: compressedValue,
|
||||
ExpiresAt: expiresAt,
|
||||
}
|
||||
c.entries.Store(key, entry)
|
||||
}
|
||||
|
||||
func (c *Cache) Get(key string) ([]byte, bool) {
|
||||
entry, ok := c.entries.Load(key)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
cacheEntry := entry.(CacheEntry)
|
||||
if cacheEntry.ExpiresAt.Before(time.Now()) {
|
||||
c.entries.Delete(key)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
value, err := c.decompress(cacheEntry.Value)
|
||||
if err != nil {
|
||||
log.Printf("Error decompressing value for key %s: %v", key, err)
|
||||
return nil, false
|
||||
}
|
||||
return value, true
|
||||
}
|
||||
|
||||
func (c *Cache) Delete(key string) {
|
||||
c.entries.Delete(key)
|
||||
}
|
||||
|
||||
func (c *Cache) Clear() {
|
||||
c.entries.Range(func(key, value interface{}) bool {
|
||||
c.entries.Delete(key)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Cache) CountQueries() int64 {
|
||||
var count int
|
||||
c.entries.Range(func(_, _ interface{}) bool {
|
||||
count++
|
||||
return true
|
||||
})
|
||||
return int64(count)
|
||||
}
|
||||
|
||||
func (c *Cache) compress(data []byte) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
w := c.compressPool.Get().(*gzip.Writer)
|
||||
defer func() {
|
||||
w.Close()
|
||||
c.compressPool.Put(w)
|
||||
}()
|
||||
w.Reset(&buf)
|
||||
if _, err := w.Write(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (c *Cache) decompress(data []byte) ([]byte, error) {
|
||||
r, ok := c.decompressPool.Get().(*gzip.Reader)
|
||||
if !ok || r == nil {
|
||||
var err error
|
||||
r, err = gzip.NewReader(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if err := r.Reset(bytes.NewReader(data)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
r.Close()
|
||||
c.decompressPool.Put(r)
|
||||
}()
|
||||
|
||||
return io.ReadAll(r)
|
||||
}
|
||||
|
||||
func (c *Cache) CleanExpiredEntries() {
|
||||
now := time.Now()
|
||||
c.entries.Range(func(key, value interface{}) bool {
|
||||
entry := value.(CacheEntry)
|
||||
if entry.ExpiresAt.Before(now) {
|
||||
c.entries.Delete(key)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
Vendored
+82
@@ -0,0 +1,82 @@
|
||||
package libpack_cache_memory
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Assume that New function initializes the cache and it is defined somewhere in the libpack_cache package.
|
||||
|
||||
func BenchmarkMemCacheSet(b *testing.B) {
|
||||
cache := New(30 * time.Second) // Initializing the cache with a TTL of 30 seconds
|
||||
key := "benchmark-key"
|
||||
value := []byte("benchmark-value")
|
||||
|
||||
b.ResetTimer() // Reset the timer to exclude the setup time from the benchmark
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
cache.Set(key, value, 5*time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMemCacheGet(b *testing.B) {
|
||||
cache := New(30 * time.Second) // Initializing the cache
|
||||
key := "benchmark-key"
|
||||
value := []byte("benchmark-value")
|
||||
cache.Set(key, value, 5*time.Second) // Pre-set a value to retrieve
|
||||
|
||||
b.ResetTimer() // Start timing
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = cache.Get(key)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMemCacheExpire(b *testing.B) {
|
||||
key := "benchmark-expire-key"
|
||||
value := []byte("benchmark-value")
|
||||
ttl := 5 * time.Millisecond // Setting a short TTL for quick expiration
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
cache := New(30 * time.Second)
|
||||
cache.Set(key, value, ttl)
|
||||
time.Sleep(ttl) // Wait for the key to expire
|
||||
_, _ = cache.Get(key)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMemCacheStats(b *testing.B) {
|
||||
cache := New(30 * time.Second) // Initializing the cache
|
||||
key := "benchmark-key"
|
||||
value := []byte("benchmark-value")
|
||||
cache.Set(key, value, 5*time.Second) // Pre-set a value to retrieve
|
||||
cache.Get(key)
|
||||
}
|
||||
|
||||
func BenchmarkCacheSet(b *testing.B) {
|
||||
cache := New(5 * time.Second)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cache.Set(fmt.Sprintf("key-%d", i), []byte("value"), 5*time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCacheGet(b *testing.B) {
|
||||
cache := New(5 * time.Second)
|
||||
cache.Set("test-key", []byte("test-value"), 5*time.Second)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cache.Get("test-key")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCacheDelete(b *testing.B) {
|
||||
cache := New(5 * time.Second)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
key := fmt.Sprintf("key-%d", i)
|
||||
cache.Set(key, []byte("value"), 5*time.Second)
|
||||
cache.Delete(key)
|
||||
}
|
||||
}
|
||||
Vendored
+168
@@ -0,0 +1,168 @@
|
||||
package libpack_cache_memory
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type MemoryTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *MemoryTestSuite) SetupTest() {
|
||||
}
|
||||
|
||||
func TestCachingTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(MemoryTestSuite))
|
||||
}
|
||||
|
||||
func (suite *MemoryTestSuite) Test_New() {
|
||||
suite.T().Run("should return a new cache", func(t *testing.T) {
|
||||
cache := New(2 * time.Second)
|
||||
suite.NotNil(cache)
|
||||
})
|
||||
}
|
||||
|
||||
func (suite *MemoryTestSuite) Test_CacheUse() {
|
||||
cache := New(30 * time.Second)
|
||||
tests := []struct {
|
||||
name string
|
||||
cache_value string
|
||||
}{
|
||||
{
|
||||
name: "test1",
|
||||
cache_value: "test1-123",
|
||||
},
|
||||
{
|
||||
name: "test2",
|
||||
cache_value: "test2-123",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
suite.T().Run(tt.name, func(t *testing.T) {
|
||||
cache.Set(tt.name, []byte(tt.name), 5*time.Second)
|
||||
c, ok := cache.Get(tt.name)
|
||||
suite.Equal(true, ok)
|
||||
suite.Equal(tt.name, string(c))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *MemoryTestSuite) Test_CacheDelete() {
|
||||
cache := New(30 * time.Second)
|
||||
tests := []struct {
|
||||
name string
|
||||
cache_value string
|
||||
}{
|
||||
{
|
||||
name: "test1",
|
||||
cache_value: "test1-123",
|
||||
},
|
||||
{
|
||||
name: "test2",
|
||||
cache_value: "test2-123",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
suite.T().Run(tt.name, func(t *testing.T) {
|
||||
cache.Set(tt.name, []byte(tt.name), 5*time.Second)
|
||||
c, ok := cache.Get(tt.name)
|
||||
suite.Equal(true, ok)
|
||||
suite.Equal(tt.name, string(c))
|
||||
cache.Delete(tt.name)
|
||||
c, ok = cache.Get(tt.name)
|
||||
suite.Equal(false, ok)
|
||||
suite.Equal("", string(c))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *MemoryTestSuite) Test_CacheExpire() {
|
||||
cache := New(30 * time.Second)
|
||||
tests := []struct {
|
||||
name string
|
||||
cache_value string
|
||||
ttl time.Duration
|
||||
}{
|
||||
{
|
||||
name: "test1",
|
||||
cache_value: "test1-123",
|
||||
ttl: 2 * time.Second,
|
||||
},
|
||||
{
|
||||
name: "test2",
|
||||
cache_value: "test2-123",
|
||||
ttl: 5 * time.Second,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
suite.T().Run(tt.name, func(t *testing.T) {
|
||||
cache.Set(tt.name, []byte(tt.name), tt.ttl)
|
||||
c, ok := cache.Get(tt.name)
|
||||
suite.Equal(true, ok)
|
||||
suite.Equal(tt.name, string(c))
|
||||
time.Sleep(tt.ttl)
|
||||
c, ok = cache.Get(tt.name)
|
||||
suite.Equal(false, ok)
|
||||
suite.Equal("", string(c))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *MemoryTestSuite) Test_ConcurrentReadWrite() {
|
||||
cache := New(5 * time.Second)
|
||||
const numGoroutines = 100
|
||||
const numOperations = 1000
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(numGoroutines)
|
||||
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < numOperations; j++ {
|
||||
key := fmt.Sprintf("key-%d-%d", id, j)
|
||||
value := []byte(fmt.Sprintf("value-%d-%d", id, j))
|
||||
|
||||
if j%2 == 0 {
|
||||
cache.Set(key, value, 5*time.Second)
|
||||
} else {
|
||||
_, _ = cache.Get(key)
|
||||
}
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (suite *MemoryTestSuite) Test_LargeItems() {
|
||||
cache := New(5 * time.Second)
|
||||
largeValue := make([]byte, 10*1024*1024) // 10MB
|
||||
cache.Set("large-key", largeValue, 5*time.Second)
|
||||
|
||||
retrieved, found := cache.Get("large-key")
|
||||
suite.Assert().True(found)
|
||||
suite.Assert().Equal(largeValue, retrieved)
|
||||
}
|
||||
|
||||
func (suite *MemoryTestSuite) Test_ZeroTTL() {
|
||||
cache := New(5 * time.Second)
|
||||
cache.Set("zero-ttl", []byte("value"), 0)
|
||||
|
||||
_, found := cache.Get("zero-ttl")
|
||||
suite.Assert().False(found, "Item with zero TTL should not be stored")
|
||||
}
|
||||
|
||||
func (suite *MemoryTestSuite) Test_LongTTL() {
|
||||
cache := New(5 * time.Second)
|
||||
cache.Set("long-ttl", []byte("value"), 24*365*time.Hour) // 1 year
|
||||
|
||||
retrieved, found := cache.Get("long-ttl")
|
||||
suite.Assert().True(found)
|
||||
suite.Assert().Equal([]byte("value"), retrieved)
|
||||
}
|
||||
Vendored
+96
@@ -0,0 +1,96 @@
|
||||
package libpack_cache_redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"sync"
|
||||
|
||||
redis "github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type RedisConfig struct {
|
||||
ctx context.Context
|
||||
client *redis.Client
|
||||
builderPool *sync.Pool
|
||||
prefix string
|
||||
}
|
||||
|
||||
func (c *RedisConfig) prependKeyName(key string) string {
|
||||
builder := c.builderPool.Get().(*strings.Builder)
|
||||
defer c.builderPool.Put(builder)
|
||||
builder.Reset()
|
||||
builder.WriteString(c.prefix)
|
||||
builder.WriteString(key)
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
type RedisClientConfig struct {
|
||||
RedisServer string
|
||||
RedisPassword string
|
||||
Prefix string
|
||||
RedisDB int
|
||||
}
|
||||
|
||||
func New(redisClientConfig *RedisClientConfig) *RedisConfig {
|
||||
c := &RedisConfig{
|
||||
client: redis.NewClient(&redis.Options{
|
||||
Addr: redisClientConfig.RedisServer,
|
||||
Password: redisClientConfig.RedisPassword,
|
||||
DB: redisClientConfig.RedisDB,
|
||||
}),
|
||||
ctx: context.Background(),
|
||||
prefix: redisClientConfig.Prefix,
|
||||
builderPool: &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &strings.Builder{}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := c.client.Ping(c.ctx).Result()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *RedisConfig) Set(key string, value []byte, ttl time.Duration) {
|
||||
c.client.Set(c.ctx, c.prependKeyName(key), value, ttl)
|
||||
}
|
||||
|
||||
func (c *RedisConfig) Get(key string) ([]byte, bool) {
|
||||
val, err := c.client.Get(c.ctx, c.prependKeyName(key)).Result()
|
||||
if err == redis.Nil {
|
||||
return nil, false
|
||||
}
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return []byte(val), true
|
||||
}
|
||||
|
||||
func (c *RedisConfig) Delete(key string) {
|
||||
c.client.Del(c.ctx, c.prependKeyName(key))
|
||||
}
|
||||
|
||||
func (c *RedisConfig) Clear() {
|
||||
c.client.FlushDB(c.ctx)
|
||||
}
|
||||
|
||||
func (c *RedisConfig) CountQueries() int64 {
|
||||
keys, err := c.client.Keys(c.ctx, c.prependKeyName("*")).Result()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return int64(len(keys))
|
||||
}
|
||||
|
||||
func (c *RedisConfig) CountQueriesWithPattern(pattern string) int {
|
||||
keys, err := c.client.Keys(c.ctx, c.prependKeyName(pattern)).Result()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return len(keys)
|
||||
}
|
||||
Vendored
+130
@@ -0,0 +1,130 @@
|
||||
package libpack_cache_redis
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type RedisConfigSuite struct {
|
||||
suite.Suite
|
||||
redisConfig *RedisConfig
|
||||
redis_server *miniredis.Miniredis
|
||||
}
|
||||
|
||||
func (suite *RedisConfigSuite) SetupTest() {
|
||||
suite.redis_server, _ = miniredis.Run()
|
||||
suite.redisConfig = New(&RedisClientConfig{
|
||||
RedisServer: suite.redis_server.Addr(),
|
||||
RedisPassword: "",
|
||||
RedisDB: 0,
|
||||
})
|
||||
suite.redisConfig.Delete("testkey")
|
||||
}
|
||||
|
||||
func TestRedisConfigSuite(t *testing.T) {
|
||||
suite.Run(t, new(RedisConfigSuite))
|
||||
}
|
||||
|
||||
func (suite *RedisConfigSuite) TestSet() {
|
||||
key := "testkeyset"
|
||||
value := []byte("testvalue")
|
||||
suite.redisConfig.Delete(key) // Ensure the key is deleted before the test
|
||||
|
||||
// Test writing a new key-value pair
|
||||
suite.redisConfig.Set(key, value, 0)
|
||||
storedValue, found := suite.redisConfig.Get(key)
|
||||
assert.True(suite.T(), found)
|
||||
assert.Equal(suite.T(), value, storedValue)
|
||||
|
||||
// Test overwriting an existing key-value pair
|
||||
newValue := []byte("newvalue")
|
||||
suite.redisConfig.Set(key, newValue, 0)
|
||||
storedValue, found = suite.redisConfig.Get(key)
|
||||
assert.True(suite.T(), found)
|
||||
assert.Equal(suite.T(), newValue, storedValue)
|
||||
|
||||
suite.redisConfig.Delete(key) // Clean up after the test
|
||||
}
|
||||
|
||||
func (suite *RedisConfigSuite) TestSetWithExpiry() {
|
||||
key := "testkey_with_expiry"
|
||||
value := []byte("testvaluewithexpiry")
|
||||
expiry := 2 * time.Second
|
||||
suite.redisConfig.Delete(key) // Ensure the key is deleted before the test
|
||||
|
||||
// Test writing a new key-value pair
|
||||
suite.redisConfig.Set(key, value, expiry)
|
||||
storedValue, found := suite.redisConfig.Get(key)
|
||||
assert.True(suite.T(), found)
|
||||
assert.Equal(suite.T(), value, storedValue)
|
||||
_, found = suite.redisConfig.Get(key)
|
||||
assert.True(suite.T(), found, "Key should exist")
|
||||
|
||||
// Test that key expires after the specified time
|
||||
suite.redis_server.FastForward(3 * time.Second)
|
||||
_, found = suite.redisConfig.Get(key)
|
||||
assert.False(suite.T(), found, "Key should have expired after 2 seconds")
|
||||
|
||||
suite.redisConfig.Delete(key) // Clean up after the test
|
||||
}
|
||||
|
||||
func (suite *RedisConfigSuite) TestGet() {
|
||||
key := "testkeyget"
|
||||
value := []byte("testvalue")
|
||||
suite.redisConfig.Set(key, value, 0) // Set the key-value pair
|
||||
storedValue, found := suite.redisConfig.Get(key)
|
||||
assert.True(suite.T(), found)
|
||||
assert.Equal(suite.T(), value, storedValue)
|
||||
}
|
||||
|
||||
func (suite *RedisConfigSuite) TestDeleteKey() {
|
||||
key := "testkeydelete"
|
||||
value := []byte("testvalue")
|
||||
suite.redisConfig.Set(key, value, 0) // Set the key-value pair
|
||||
suite.redisConfig.Delete(key)
|
||||
_, found := suite.redisConfig.Get(key)
|
||||
assert.False(suite.T(), found)
|
||||
}
|
||||
|
||||
func (suite *RedisConfigSuite) TestCheckIfKeyExists() {
|
||||
ttl := time.Duration(10) * time.Second
|
||||
key := "testkeyifexists"
|
||||
value := []byte("testvalue")
|
||||
suite.redisConfig.Set(key, value, ttl) // Set the key-value pair
|
||||
_, found := suite.redisConfig.Get(key)
|
||||
assert.True(suite.T(), found)
|
||||
|
||||
suite.redisConfig.Delete(key)
|
||||
_, found = suite.redisConfig.Get(key)
|
||||
assert.False(suite.T(), found)
|
||||
}
|
||||
|
||||
func (suite *RedisConfigSuite) TestGetKeys() {
|
||||
ttl := time.Duration(10) * time.Second
|
||||
suite.redisConfig.Set("testkey1", []byte("testvalue1"), ttl)
|
||||
suite.redisConfig.Set("testkey2", []byte("testvalue2"), ttl)
|
||||
suite.redisConfig.Set("otherkey", []byte("othervalue"), ttl)
|
||||
|
||||
keys, _ := suite.redisConfig.client.Keys(suite.redisConfig.ctx, "testkey*").Result()
|
||||
expectedKeys := []string{"testkey1", "testkey2"}
|
||||
assert.ElementsMatch(suite.T(), expectedKeys, keys)
|
||||
|
||||
suite.redisConfig.client.Del(suite.redisConfig.ctx, "testkey1", "testkey2", "otherkey")
|
||||
}
|
||||
|
||||
func (suite *RedisConfigSuite) TestGetKeysCount() {
|
||||
ttl := time.Duration(10) * time.Second
|
||||
suite.redisConfig.Set("testkey1", []byte("testvalue1"), ttl)
|
||||
suite.redisConfig.Set("testkey2", []byte("testvalue2"), ttl)
|
||||
suite.redisConfig.Set("otherkey", []byte("othervalue"), ttl)
|
||||
|
||||
assert.Equal(suite.T(), 2, suite.redisConfig.CountQueriesWithPattern("testkey*"))
|
||||
assert.Equal(suite.T(), 1, suite.redisConfig.CountQueriesWithPattern("otherkey*"))
|
||||
assert.Equal(suite.T(), int64(3), suite.redisConfig.CountQueries())
|
||||
|
||||
suite.redisConfig.client.Del(suite.redisConfig.ctx, "testkey1", "testkey2", "otherkey")
|
||||
}
|
||||
Reference in New Issue
Block a user