You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
180 lines
4.3 KiB
180 lines
4.3 KiB
package simulation |
|
|
|
import ( |
|
"time" |
|
) |
|
|
|
type CacheLogic struct{} |
|
|
|
type CacheEntry struct { |
|
Data string |
|
Timestamp int |
|
AccessTime int |
|
AccessCount int |
|
InsertOrder int |
|
} |
|
|
|
func (c CacheLogic) Tick(props map[string]any, queue []*Request, tick int) ([]*Request, bool) { |
|
// Extract cache properties |
|
cacheTTL := int(AsFloat64(props["cacheTTL"])) |
|
if cacheTTL == 0 { |
|
cacheTTL = 300000 // default 5 minutes in ms |
|
} |
|
|
|
maxEntries := int(AsFloat64(props["maxEntries"])) |
|
if maxEntries == 0 { |
|
maxEntries = 1000 // default max entries |
|
} |
|
|
|
evictionPolicy := AsString(props["evictionPolicy"]) |
|
if evictionPolicy == "" { |
|
evictionPolicy = "LRU" // default eviction policy |
|
} |
|
|
|
// Initialize cache data structures in props |
|
cacheData, ok := props["_cacheData"].(map[string]*CacheEntry) |
|
if !ok { |
|
cacheData = make(map[string]*CacheEntry) |
|
props["_cacheData"] = cacheData |
|
} |
|
|
|
insertCounter, ok := props["_insertCounter"].(int) |
|
if !ok { |
|
insertCounter = 0 |
|
} |
|
|
|
// Current timestamp for this tick |
|
currentTime := tick * 100 // assuming 100ms per tick |
|
|
|
// Clean up expired entries first |
|
c.cleanExpiredEntries(cacheData, currentTime, cacheTTL) |
|
|
|
output := []*Request{} |
|
|
|
for _, req := range queue { |
|
cacheKey := req.ID + "-" + req.Type // Use request ID and type as cache key |
|
|
|
// Check for cache hit |
|
entry, hit := cacheData[cacheKey] |
|
if hit && !c.isExpired(entry, currentTime, cacheTTL) { |
|
// Cache hit - return immediately with minimal latency |
|
reqCopy := *req |
|
reqCopy.LatencyMS += 1 // 1ms for in-memory access |
|
reqCopy.Path = append(reqCopy.Path, "cache-hit") |
|
|
|
// Update access tracking for eviction policies |
|
entry.AccessTime = currentTime |
|
entry.AccessCount++ |
|
|
|
output = append(output, &reqCopy) |
|
} else { |
|
// Cache miss - forward request downstream |
|
reqCopy := *req |
|
reqCopy.Path = append(reqCopy.Path, "cache-miss") |
|
|
|
// For simulation purposes, we'll cache the "response" immediately |
|
// In a real system, this would happen when the response comes back |
|
insertCounter++ |
|
newEntry := &CacheEntry{ |
|
Data: "cached-data", // In real implementation, this would be the response data |
|
Timestamp: currentTime, |
|
AccessTime: currentTime, |
|
AccessCount: 1, |
|
InsertOrder: insertCounter, |
|
} |
|
|
|
// First check if we need to evict before adding |
|
if len(cacheData) >= maxEntries { |
|
c.evictEntry(cacheData, evictionPolicy) |
|
} |
|
|
|
// Now add the new entry |
|
cacheData[cacheKey] = newEntry |
|
|
|
output = append(output, &reqCopy) |
|
} |
|
} |
|
|
|
// Update insert counter in props |
|
props["_insertCounter"] = insertCounter |
|
|
|
return output, true |
|
} |
|
|
|
func (c CacheLogic) cleanExpiredEntries(cacheData map[string]*CacheEntry, currentTime, ttl int) { |
|
for key, entry := range cacheData { |
|
if c.isExpired(entry, currentTime, ttl) { |
|
delete(cacheData, key) |
|
} |
|
} |
|
} |
|
|
|
func (c CacheLogic) isExpired(entry *CacheEntry, currentTime, ttl int) bool { |
|
return (currentTime - entry.Timestamp) > ttl |
|
} |
|
|
|
func (c CacheLogic) evictEntry(cacheData map[string]*CacheEntry, policy string) { |
|
if len(cacheData) == 0 { |
|
return |
|
} |
|
|
|
var keyToEvict string |
|
|
|
switch policy { |
|
case "LRU": |
|
// Evict least recently used |
|
oldestTime := int(^uint(0) >> 1) // Max int |
|
for key, entry := range cacheData { |
|
if entry.AccessTime < oldestTime { |
|
oldestTime = entry.AccessTime |
|
keyToEvict = key |
|
} |
|
} |
|
|
|
case "LFU": |
|
// Evict least frequently used |
|
minCount := int(^uint(0) >> 1) // Max int |
|
for key, entry := range cacheData { |
|
if entry.AccessCount < minCount { |
|
minCount = entry.AccessCount |
|
keyToEvict = key |
|
} |
|
} |
|
|
|
case "FIFO": |
|
// Evict first in (oldest insert order) |
|
minOrder := int(^uint(0) >> 1) // Max int |
|
for key, entry := range cacheData { |
|
if entry.InsertOrder < minOrder { |
|
minOrder = entry.InsertOrder |
|
keyToEvict = key |
|
} |
|
} |
|
|
|
case "random": |
|
// Evict random entry |
|
keys := make([]string, 0, len(cacheData)) |
|
for key := range cacheData { |
|
keys = append(keys, key) |
|
} |
|
if len(keys) > 0 { |
|
// Use timestamp as pseudo-random seed |
|
seed := time.Now().UnixNano() |
|
keyToEvict = keys[seed%int64(len(keys))] |
|
} |
|
|
|
default: |
|
// Default to LRU |
|
oldestTime := int(^uint(0) >> 1) |
|
for key, entry := range cacheData { |
|
if entry.AccessTime < oldestTime { |
|
oldestTime = entry.AccessTime |
|
keyToEvict = key |
|
} |
|
} |
|
} |
|
|
|
if keyToEvict != "" { |
|
delete(cacheData, keyToEvict) |
|
} |
|
}
|
|
|