9 changed files with 484 additions and 43 deletions
@ -0,0 +1,93 @@ |
|||||||
|
package simulation |
||||||
|
|
||||||
|
type MessageQueueNode struct { |
||||||
|
ID string |
||||||
|
Label string |
||||||
|
QueueSize int |
||||||
|
MessageTTL int // TTL in milliseconds
|
||||||
|
DeadLetter bool |
||||||
|
CurrentLoad int |
||||||
|
Queue []*Request |
||||||
|
Alive bool |
||||||
|
Targets []string |
||||||
|
output []*Request |
||||||
|
deadLetterOutput []*Request |
||||||
|
} |
||||||
|
|
||||||
|
func (n *MessageQueueNode) GetID() string { return n.ID } |
||||||
|
func (n *MessageQueueNode) Type() string { return "messagequeue" } |
||||||
|
func (n *MessageQueueNode) IsAlive() bool { return n.Alive } |
||||||
|
|
||||||
|
func (n *MessageQueueNode) Tick(tick int, currentTimeMs int) { |
||||||
|
if len(n.Queue) == 0 { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// Message queues have very high throughput
|
||||||
|
maxProcessPerTick := 20 // Higher than database (3) or CDN (10)
|
||||||
|
processCount := min(len(n.Queue), maxProcessPerTick) |
||||||
|
|
||||||
|
// Check for queue overflow (simulate back pressure)
|
||||||
|
if len(n.Queue) > n.QueueSize { |
||||||
|
// Move oldest messages to dead letter queue if enabled
|
||||||
|
if n.DeadLetter { |
||||||
|
overflow := len(n.Queue) - n.QueueSize |
||||||
|
for i := 0; i < overflow; i++ { |
||||||
|
deadReq := n.Queue[i] |
||||||
|
deadReq.Type = "DEAD_LETTER" |
||||||
|
deadReq.Path = append(deadReq.Path, n.ID+"(dead)") |
||||||
|
n.deadLetterOutput = append(n.deadLetterOutput, deadReq) |
||||||
|
} |
||||||
|
n.Queue = n.Queue[overflow:] |
||||||
|
} else { |
||||||
|
// Drop messages if no dead letter queue
|
||||||
|
n.Queue = n.Queue[:n.QueueSize] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Process messages with TTL check
|
||||||
|
for i := 0; i < processCount; i++ { |
||||||
|
req := n.Queue[0] |
||||||
|
n.Queue = n.Queue[1:] |
||||||
|
|
||||||
|
// Check TTL (time to live) - use current time in milliseconds
|
||||||
|
messageAgeMs := currentTimeMs - req.Timestamp |
||||||
|
if messageAgeMs > n.MessageTTL { |
||||||
|
// Message expired
|
||||||
|
if n.DeadLetter { |
||||||
|
req.Type = "EXPIRED" |
||||||
|
req.Path = append(req.Path, n.ID+"(expired)") |
||||||
|
n.deadLetterOutput = append(n.deadLetterOutput, req) |
||||||
|
} |
||||||
|
// Otherwise drop expired message
|
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
// Message queue adds minimal latency (very fast)
|
||||||
|
req.LatencyMS += 2 |
||||||
|
req.Path = append(req.Path, n.ID) |
||||||
|
n.output = append(n.output, req) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (n *MessageQueueNode) Receive(req *Request) { |
||||||
|
if req == nil { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// Message queues have very low receive overhead
|
||||||
|
req.LatencyMS += 1 |
||||||
|
|
||||||
|
n.Queue = append(n.Queue, req) |
||||||
|
} |
||||||
|
|
||||||
|
func (n *MessageQueueNode) Emit() []*Request { |
||||||
|
// Return both normal messages and dead letter messages
|
||||||
|
allRequests := append(n.output, n.deadLetterOutput...) |
||||||
|
|
||||||
|
// Clear queues
|
||||||
|
n.output = n.output[:0] |
||||||
|
n.deadLetterOutput = n.deadLetterOutput[:0] |
||||||
|
|
||||||
|
return allRequests |
||||||
|
} |
||||||
@ -0,0 +1,103 @@ |
|||||||
|
package simulation |
||||||
|
|
||||||
|
import "math/rand" |
||||||
|
|
||||||
|
type MicroserviceNode struct { |
||||||
|
ID string |
||||||
|
Label string |
||||||
|
APIEndpoint string |
||||||
|
RateLimit int // max requests per tick
|
||||||
|
CircuitBreaker bool |
||||||
|
CircuitState string // "closed", "open", "half-open"
|
||||||
|
ErrorCount int |
||||||
|
CurrentLoad int |
||||||
|
Queue []*Request |
||||||
|
Output []*Request |
||||||
|
Alive bool |
||||||
|
Targets []string |
||||||
|
} |
||||||
|
|
||||||
|
func (n *MicroserviceNode) GetID() string { return n.ID } |
||||||
|
|
||||||
|
func (n *MicroserviceNode) Type() string { return "microservice" } |
||||||
|
|
||||||
|
func (n *MicroserviceNode) IsAlive() bool { return n.Alive } |
||||||
|
|
||||||
|
func (n *MicroserviceNode) Receive(req *Request) { |
||||||
|
n.Queue = append(n.Queue, req) |
||||||
|
} |
||||||
|
|
||||||
|
func (n *MicroserviceNode) Emit() []*Request { |
||||||
|
out := append([]*Request(nil), n.Output...) |
||||||
|
n.Output = n.Output[:0] |
||||||
|
return out |
||||||
|
} |
||||||
|
|
||||||
|
func (n *MicroserviceNode) Tick(tick int, currentTimeMs int) { |
||||||
|
if !n.Alive { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// Simulate circuit breaker state transitions
|
||||||
|
switch n.CircuitState { |
||||||
|
case "open": |
||||||
|
// Skip processing
|
||||||
|
return |
||||||
|
case "half-open": |
||||||
|
// Allow limited testing traffic
|
||||||
|
if len(n.Queue) == 0 { |
||||||
|
return |
||||||
|
} |
||||||
|
req := n.Queue[0] |
||||||
|
n.Queue = n.Queue[1:] |
||||||
|
req.LatencyMS += 15 |
||||||
|
req.Path = append(req.Path, n.ID+"(half-open)") |
||||||
|
success := simulateSuccess() |
||||||
|
if success { |
||||||
|
n.CircuitState = "closed" |
||||||
|
n.ErrorCount = 0 |
||||||
|
n.Output = append(n.Output, req) |
||||||
|
} else { |
||||||
|
n.ErrorCount++ |
||||||
|
n.CircuitState = "open" |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// "closed" circuit - normal processing
|
||||||
|
toProcess := min(len(n.Queue), n.RateLimit) |
||||||
|
for i := 0; i < toProcess; i++ { |
||||||
|
req := n.Queue[i] |
||||||
|
req.LatencyMS += 10 |
||||||
|
req.Path = append(req.Path, n.ID) |
||||||
|
|
||||||
|
if simulateFailure() { |
||||||
|
n.ErrorCount++ |
||||||
|
if n.CircuitBreaker && n.ErrorCount > 5 { |
||||||
|
n.CircuitState = "open" |
||||||
|
break |
||||||
|
} |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
n.Output = append(n.Output, req) |
||||||
|
} |
||||||
|
n.Queue = n.Queue[toProcess:] |
||||||
|
|
||||||
|
// Health check: simple example
|
||||||
|
n.Alive = n.CircuitState != "open" |
||||||
|
} |
||||||
|
|
||||||
|
func simulateFailure() bool { |
||||||
|
// Simulate 10% failure rate
|
||||||
|
return randInt(1, 100) <= 10 |
||||||
|
} |
||||||
|
|
||||||
|
func simulateSuccess() bool { |
||||||
|
// Simulate 90% success rate
|
||||||
|
return randInt(1, 100) <= 90 |
||||||
|
} |
||||||
|
|
||||||
|
func randInt(min, max int) int { |
||||||
|
return min + rand.Intn(max-min+1) |
||||||
|
} |
||||||
@ -0,0 +1,66 @@ |
|||||||
|
package simulation |
||||||
|
|
||||||
|
type MonitoringNode struct { |
||||||
|
ID string |
||||||
|
Label string |
||||||
|
Tool string |
||||||
|
AlertMetric string |
||||||
|
ThresholdValue int |
||||||
|
ThresholdUnit string |
||||||
|
Queue []*Request |
||||||
|
Alive bool |
||||||
|
Targets []string |
||||||
|
Metrics map[string]int |
||||||
|
Alerts []*Request |
||||||
|
} |
||||||
|
|
||||||
|
func (n *MonitoringNode) GetID() string { return n.ID } |
||||||
|
|
||||||
|
func (n *MonitoringNode) Type() string { return "monitoring" } |
||||||
|
|
||||||
|
func (n *MonitoringNode) IsAlive() bool { return n.Alive } |
||||||
|
|
||||||
|
func (n *MonitoringNode) Receive(req *Request) { |
||||||
|
n.Queue = append(n.Queue, req) |
||||||
|
} |
||||||
|
|
||||||
|
func (n *MonitoringNode) Emit() []*Request { |
||||||
|
out := append([]*Request(nil), n.Alerts...) |
||||||
|
n.Alerts = n.Alerts[:0] |
||||||
|
return out |
||||||
|
} |
||||||
|
|
||||||
|
func (n *MonitoringNode) Tick(tick int, currentTimeMs int) { |
||||||
|
if !n.Alive { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if n.Metrics == nil { |
||||||
|
n.Metrics = make(map[string]int) |
||||||
|
} |
||||||
|
|
||||||
|
// Simulate processing requests as metrics
|
||||||
|
for _, req := range n.Queue { |
||||||
|
// For now, pretend all requests are relevant to the AlertMetric
|
||||||
|
n.Metrics[n.AlertMetric] += 1 |
||||||
|
req.LatencyMS += 1 |
||||||
|
req.Path = append(req.Path, n.ID) |
||||||
|
|
||||||
|
if n.Metrics[n.AlertMetric] > n.ThresholdValue { |
||||||
|
alert := &Request{ |
||||||
|
ID: "alert-" + req.ID, |
||||||
|
Timestamp: currentTimeMs, |
||||||
|
Origin: n.ID, |
||||||
|
Type: "alert", |
||||||
|
LatencyMS: 0, |
||||||
|
Path: []string{n.ID, "alert"}, |
||||||
|
} |
||||||
|
n.Alerts = append(n.Alerts, alert) |
||||||
|
|
||||||
|
// Reset after alert (or you could continue accumulating)
|
||||||
|
n.Metrics[n.AlertMetric] = 0 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
n.Queue = nil |
||||||
|
} |
||||||
@ -0,0 +1,77 @@ |
|||||||
|
package simulation |
||||||
|
|
||||||
|
type ThirdPartyServiceNode struct { |
||||||
|
ID string |
||||||
|
Label string |
||||||
|
APIEndpoint string |
||||||
|
RateLimit int // Max requests per tick
|
||||||
|
RetryPolicy string // "exponential", "fixed", etc.
|
||||||
|
CurrentLoad int |
||||||
|
Queue []*Request |
||||||
|
ErrorCount int |
||||||
|
RetryCount int |
||||||
|
Alive bool |
||||||
|
Targets []string |
||||||
|
Output []*Request |
||||||
|
} |
||||||
|
|
||||||
|
// --- Interface Methods ---
|
||||||
|
|
||||||
|
func (n *ThirdPartyServiceNode) GetID() string { return n.ID } |
||||||
|
func (n *ThirdPartyServiceNode) Type() string { return "thirdpartyservice" } |
||||||
|
func (n *ThirdPartyServiceNode) IsAlive() bool { return n.Alive } |
||||||
|
func (n *ThirdPartyServiceNode) Receive(req *Request) { |
||||||
|
n.Queue = append(n.Queue, req) |
||||||
|
} |
||||||
|
func (n *ThirdPartyServiceNode) Emit() []*Request { |
||||||
|
out := append([]*Request(nil), n.Output...) |
||||||
|
n.Output = n.Output[:0] |
||||||
|
return out |
||||||
|
} |
||||||
|
|
||||||
|
// Add missing Queue method for interface compliance
|
||||||
|
func (n *ThirdPartyServiceNode) GetQueue() []*Request { |
||||||
|
return n.Queue |
||||||
|
} |
||||||
|
|
||||||
|
// --- Simulation Logic ---
|
||||||
|
|
||||||
|
func (n *ThirdPartyServiceNode) Tick(tick int, currentTimeMs int) { |
||||||
|
if !n.Alive { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// Simulate third-party call behavior with success/failure
|
||||||
|
maxProcess := min(n.RateLimit, len(n.Queue)) |
||||||
|
newQueue := n.Queue[maxProcess:] |
||||||
|
n.Queue = nil |
||||||
|
|
||||||
|
for i := 0; i < maxProcess; i++ { |
||||||
|
req := newQueue[i] |
||||||
|
success := simulateThirdPartySuccess(req) |
||||||
|
|
||||||
|
if success { |
||||||
|
req.LatencyMS += 100 + randInt(0, 50) // simulate response time
|
||||||
|
req.Path = append(req.Path, n.ID) |
||||||
|
n.Output = append(n.Output, req) |
||||||
|
} else { |
||||||
|
n.ErrorCount++ |
||||||
|
n.RetryCount++ |
||||||
|
if n.RetryPolicy == "exponential" && n.RetryCount < 3 { |
||||||
|
n.Queue = append(n.Queue, req) // retry again next tick
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Simulate degradation if too many errors
|
||||||
|
if n.ErrorCount > 10 { |
||||||
|
n.Alive = false |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// --- Helpers ---
|
||||||
|
|
||||||
|
func simulateThirdPartySuccess(req *Request) bool { |
||||||
|
// 90% success rate
|
||||||
|
return randInt(0, 100) < 90 |
||||||
|
} |
||||||
Loading…
Reference in new issue