Add cache operations via API.

This commit is contained in:
2024-06-10 11:34:30 +01:00
parent 9291ac03db
commit 468cd959a7
5 changed files with 142 additions and 1 deletions
+5
View File
@@ -236,6 +236,11 @@ To do so - you need to enable the api by setting env variable `ENABLE_API=true`
* `POST /api/user-ban` - ban the user from accessing the application
* `POST /api/user-unban` - unban the user from accessing the application
#### Cache operations
* `POST /api/cache-clear` - clear the cache
* `GET /api/cache-stats` - get the cache statistics ( hits, misses, size )
Both endpoints require the `user_id` parameter to be present in the request body and allow you to provide the reason for the ban.
Example request:
+17
View File
@@ -23,6 +23,8 @@ func enableApi() {
api := apiserver.Group("/api")
api.Post("/user-ban", apiBanUser)
api.Post("/user-unban", apiUnbanUser)
api.Post("/cache-clear", apiClearCache)
api.Get("/cache-stats", apiCacheStats)
go periodicallyReloadBannedUsers()
err := apiserver.Listen(fmt.Sprintf(":%d", cfg.Server.ApiPort))
@@ -50,6 +52,21 @@ func checkIfUserIsBanned(c *fiber.Ctx, userID string) bool {
return found
}
func apiClearCache(c *fiber.Ctx) error {
cfg.Logger.Debug("Clearing cache via API", nil)
cfg.Cache.CacheClient.ClearCache()
cfg.Logger.Info("Cache cleared via API", nil)
c.Status(200).SendString("OK: cache cleared")
return nil
}
func apiCacheStats(c *fiber.Ctx) error {
stats := cfg.Cache.CacheClient.ShowStats()
cfg.Logger.Debug("Getting cache stats via API", map[string]interface{}{"stats": stats})
c.JSON(stats)
return nil
}
type apiBanUserRequest struct {
UserID string `json:"user_id"`
Reason string `json:"reason"`
+33 -1
View File
@@ -18,6 +18,8 @@ type Cache struct {
globalTTL time.Duration
compressPool sync.Pool
decompressPool sync.Pool
cacheHits int
cacheMisses int
sync.RWMutex // Reintroduced to provide lock methods
}
@@ -75,14 +77,16 @@ func (c *Cache) Get(key string) ([]byte, bool) {
entry, ok := c.entries.Load(key)
if !ok || entry.(CacheEntry).ExpiresAt.Before(time.Now()) {
c.cacheMisses++
return nil, false
}
compressedValue := entry.(CacheEntry).Value
value, err := c.decompress(compressedValue)
if err != nil {
c.cacheMisses++
return nil, false
}
c.cacheHits++
return value, true
}
@@ -109,6 +113,34 @@ func (c *Cache) CleanExpiredEntries() {
})
}
type CacheStats struct {
CachedQueries int `json:"cached_queries"`
CacheHits int `json:"cache_hits"`
CacheMisses int `json:"cache_misses"`
}
func (c *Cache) ShowStats() CacheStats {
c.RLock()
defer c.RUnlock()
var count int
c.entries.Range(func(_, _ interface{}) bool {
count++
return true
})
cs := CacheStats{
CachedQueries: count,
CacheHits: c.cacheHits,
CacheMisses: c.cacheMisses,
}
return cs
}
func (c *Cache) ClearCache() {
c.cacheHits = 0
c.cacheMisses = 0
c.entries = sync.Map{}
}
func (c *Cache) compress(data []byte) ([]byte, error) {
w := c.compressPool.Get().(*gzip.Writer)
defer c.compressPool.Put(w)
+54
View File
@@ -0,0 +1,54 @@
package libpack_cache
import (
"testing"
"time"
)
// Assume that New function initializes the cache and it is defined somewhere in the libpack_cache package.
func BenchmarkCacheSet(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 BenchmarkCacheGet(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 BenchmarkCacheExpire(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 BenchmarkCacheStats(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)
}
+33
View File
@@ -110,3 +110,36 @@ func (suite *CacheTestSuite) Test_CacheExpire() {
})
}
}
func (suite *CacheTestSuite) Test_CacheStats() {
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))
})
}
cache.Get("non-existent-non-cached-key")
stats := cache.ShowStats()
suite.Equal(2, stats.CacheHits, "CacheHits")
suite.Equal(1, stats.CacheMisses, "CacheMisses")
suite.Equal(2, stats.CachedQueries, "CachedQueries")
}