|
|
|
@ -1,5 +1,17 @@ |
|
|
|
package simulation |
|
|
|
package simulation |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
|
|
|
"math/rand" |
|
|
|
|
|
|
|
"sort" |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type CacheEntry struct { |
|
|
|
|
|
|
|
Value interface{} |
|
|
|
|
|
|
|
Timestamp int |
|
|
|
|
|
|
|
ExpireAt int |
|
|
|
|
|
|
|
AccessCount int |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
type CacheNode struct { |
|
|
|
type CacheNode struct { |
|
|
|
ID string |
|
|
|
ID string |
|
|
|
Label string |
|
|
|
Label string |
|
|
|
@ -7,10 +19,11 @@ type CacheNode struct { |
|
|
|
MaxEntries int |
|
|
|
MaxEntries int |
|
|
|
EvictionPolicy string |
|
|
|
EvictionPolicy string |
|
|
|
CurrentLoad int |
|
|
|
CurrentLoad int |
|
|
|
Queue []Request |
|
|
|
Queue []*Request |
|
|
|
Cache map[string]interface{} |
|
|
|
Cache map[string]CacheEntry |
|
|
|
Alive bool |
|
|
|
Alive bool |
|
|
|
Targets []string |
|
|
|
Targets []string |
|
|
|
|
|
|
|
Output []*Request |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (c *CacheNode) GetID() string { |
|
|
|
func (c *CacheNode) GetID() string { |
|
|
|
@ -21,42 +34,74 @@ func (c *CacheNode) Type() string { |
|
|
|
return "cache" |
|
|
|
return "cache" |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (c *CacheNode) GetQueue() []Request { |
|
|
|
func (c *CacheNode) IsAlive() bool { |
|
|
|
return c.Queue |
|
|
|
return c.Alive |
|
|
|
} |
|
|
|
} |
|
|
|
func (c *CacheNode) Tick(tick int) { |
|
|
|
|
|
|
|
|
|
|
|
func (c *CacheNode) Tick(tick int, currentTimeMs int) { |
|
|
|
|
|
|
|
for key, entry := range c.Cache { |
|
|
|
|
|
|
|
if currentTimeMs > entry.ExpireAt { |
|
|
|
|
|
|
|
delete(c.Cache, key) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if len(c.Cache) > c.MaxEntries { |
|
|
|
if len(c.Cache) > c.MaxEntries { |
|
|
|
evictCount := len(c.Cache) - c.MaxEntries |
|
|
|
evictCount := len(c.Cache) - c.MaxEntries |
|
|
|
|
|
|
|
keys := make([]string, 0, len(c.Cache)) |
|
|
|
|
|
|
|
for k := range c.Cache { |
|
|
|
|
|
|
|
keys = append(keys, k) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
i := 0 |
|
|
|
switch c.EvictionPolicy { |
|
|
|
|
|
|
|
case "Random": |
|
|
|
|
|
|
|
rand.Shuffle(len(keys), func(i, j int) { keys[i], keys[j] = keys[j], keys[i] }) |
|
|
|
|
|
|
|
case "LRU": |
|
|
|
|
|
|
|
sort.Slice(keys, func(i, j int) bool { |
|
|
|
|
|
|
|
return c.Cache[keys[i]].Timestamp < c.Cache[keys[j]].Timestamp |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
case "LFU": |
|
|
|
|
|
|
|
sort.Slice(keys, func(i, j int) bool { |
|
|
|
|
|
|
|
return c.Cache[keys[i]].AccessCount < c.Cache[keys[j]].AccessCount |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for k := range c.Cache { |
|
|
|
for i := 0; i < evictCount && i < len(keys); i++ { |
|
|
|
delete(c.Cache, k) |
|
|
|
delete(c.Cache, keys[i]) |
|
|
|
i++ |
|
|
|
} |
|
|
|
if i >= evictCount { |
|
|
|
} |
|
|
|
break |
|
|
|
|
|
|
|
|
|
|
|
toProcess := min(len(c.Queue), 10) |
|
|
|
|
|
|
|
for i := 0; i < toProcess; i++ { |
|
|
|
|
|
|
|
req := c.Queue[i] |
|
|
|
|
|
|
|
if entry, found := c.Cache[req.ID]; found && currentTimeMs <= entry.ExpireAt { |
|
|
|
|
|
|
|
// Cache hit
|
|
|
|
|
|
|
|
req.LatencyMS += 2 |
|
|
|
|
|
|
|
req.Path = append(req.Path, c.ID+"(hit)") |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
// Cache miss
|
|
|
|
|
|
|
|
req.LatencyMS += 5 |
|
|
|
|
|
|
|
req.Path = append(req.Path, c.ID+"(miss)") |
|
|
|
|
|
|
|
c.Cache[req.ID] = CacheEntry{ |
|
|
|
|
|
|
|
Value: req, |
|
|
|
|
|
|
|
Timestamp: currentTimeMs, |
|
|
|
|
|
|
|
ExpireAt: currentTimeMs + c.CacheTTL*1000, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
c.Output = append(c.Output, req) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
c.Queue = c.Queue[toProcess:] |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (c *CacheNode) Receive(req *Request) { |
|
|
|
func (c *CacheNode) Receive(req *Request) { |
|
|
|
c.Queue = append(c.Queue, *req) |
|
|
|
c.Queue = append(c.Queue, req) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (c *CacheNode) Emit() []*Request { |
|
|
|
func (c *CacheNode) Emit() []*Request { |
|
|
|
var out []*Request |
|
|
|
out := append([]*Request(nil), c.Output...) |
|
|
|
|
|
|
|
c.Output = c.Output[:0] |
|
|
|
// emit everything in a single tick.
|
|
|
|
|
|
|
|
for _, req := range c.Queue { |
|
|
|
|
|
|
|
// check cache
|
|
|
|
|
|
|
|
if _, ok := c.Cache[req.ID]; !ok { |
|
|
|
|
|
|
|
c.Cache[req.ID] = struct{}{} |
|
|
|
|
|
|
|
out = append(out, &req) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
c.Queue = nil |
|
|
|
|
|
|
|
return out |
|
|
|
return out |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (c *CacheNode) IsAlive() bool { return c.Alive } |
|
|
|
func (c *CacheNode) AddTarget(targetID string) { |
|
|
|
|
|
|
|
c.Targets = append(c.Targets, targetID) |
|
|
|
|
|
|
|
} |
|
|
|
|