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.
151 lines
3.8 KiB
151 lines
3.8 KiB
package simulation |
|
|
|
import ( |
|
"systemdesigngame/internal/design" |
|
) |
|
|
|
type Request struct { |
|
ID string |
|
Timestamp int |
|
LatencyMS int |
|
Origin string |
|
Type string |
|
Path []string |
|
} |
|
|
|
type SimulationNode interface { |
|
GetID() string |
|
Type() string |
|
Tick(tick int, currentTimeMs int) |
|
Receive(req *Request) |
|
Emit() []*Request |
|
IsAlive() bool |
|
} |
|
|
|
type Engine struct { |
|
Nodes map[string]SimulationNode |
|
Timeline []TickSnapshot |
|
Duration int |
|
TickMs int |
|
} |
|
|
|
type TickSnapshot struct { |
|
TickMs int |
|
QueueSizes map[string]int |
|
NodeHealth map[string]string |
|
} |
|
|
|
func NewEngineFromDesign(design design.Design, duration int, tickMs int) *Engine { |
|
nodeMap := make(map[string]SimulationNode) |
|
|
|
for _, n := range design.Nodes { |
|
var simNode SimulationNode |
|
|
|
switch n.Type { |
|
case "webserver": |
|
simNode = &WebServerNode{ |
|
ID: n.ID, |
|
Alive: true, |
|
Queue: []*Request{}, |
|
} |
|
case "cache": |
|
simNode = &CacheNode{ |
|
ID: n.ID, |
|
Label: n.Props["label"].(string), |
|
CacheTTL: int(n.Props["cacheTTL"].(float64)), |
|
MaxEntries: int(n.Props["maxEntries"].(float64)), |
|
EvictionPolicy: n.Props["evictionPolicy"].(string), |
|
CurrentLoad: 0, |
|
Queue: []*Request{}, |
|
Cache: make(map[string]CacheEntry), |
|
Alive: true, |
|
} |
|
case "database": |
|
simNode = &DatabaseNode{ |
|
ID: n.ID, |
|
Label: n.Props["label"].(string), |
|
Replication: int(n.Props["replication"].(float64)), |
|
Queue: []*Request{}, |
|
Alive: true, |
|
} |
|
case "cdn": |
|
simNode = &CDNNode{ |
|
ID: n.ID, |
|
Label: n.Props["label"].(string), |
|
TTL: int(n.Props["ttl"].(float64)), |
|
GeoReplication: n.Props["geoReplication"].(string), |
|
CachingStrategy: n.Props["cachingStrategy"].(string), |
|
Compression: n.Props["compression"].(string), |
|
HTTP2: n.Props["http2"].(string), |
|
Queue: []*Request{}, |
|
Alive: true, |
|
output: []*Request{}, |
|
missQueue: []*Request{}, |
|
} |
|
case "messageQueue": |
|
simNode = &MessageQueueNode{ |
|
ID: n.ID, |
|
Label: n.Props["label"].(string), |
|
QueueSize: int(n.Props["maxSize"].(float64)), |
|
MessageTTL: int(n.Props["retentionSeconds"].(float64)), |
|
DeadLetter: false, |
|
Queue: []*Request{}, |
|
Alive: true, |
|
} |
|
case "microservice": |
|
simNode = &MicroserviceNode{ |
|
ID: n.ID, |
|
Label: n.Props["label"].(string), |
|
APIEndpoint: n.Props["apiVersion"].(string), |
|
RateLimit: int(n.Props["rpsCapacity"].(float64)), |
|
CircuitBreaker: true, |
|
Queue: []*Request{}, |
|
CircuitState: "closed", |
|
Alive: true, |
|
} |
|
case "third party service": |
|
simNode = &ThirdPartyServiceNode{ |
|
ID: n.ID, |
|
Label: n.Props["label"].(string), |
|
APIEndpoint: n.Props["provider"].(string), |
|
RateLimit: int(n.Props["latency"].(float64)), |
|
RetryPolicy: "exponential", |
|
Queue: []*Request{}, |
|
Alive: true, |
|
} |
|
case "data pipeline": |
|
simNode = &DataPipelineNode{ |
|
ID: n.ID, |
|
Label: n.Props["label"].(string), |
|
BatchSize: int(n.Props["batchSize"].(float64)), |
|
Transformation: n.Props["transformation"].(string), |
|
Queue: []*Request{}, |
|
Alive: true, |
|
} |
|
case "monitoring/alerting": |
|
// Simulation not implemented yet; optionally skip |
|
continue |
|
default: |
|
continue |
|
} |
|
|
|
if simNode != nil { |
|
nodeMap[simNode.GetID()] = simNode |
|
} |
|
} |
|
|
|
// Wire up connections |
|
for _, conn := range design.Connections { |
|
if sourceNode, ok := nodeMap[conn.Source]; ok { |
|
if targetSetter, ok := sourceNode.(interface{ AddTarget(string) }); ok { |
|
targetSetter.AddTarget(conn.Target) |
|
} |
|
} |
|
} |
|
|
|
return &Engine{ |
|
Nodes: nodeMap, |
|
Duration: duration, |
|
TickMs: tickMs, |
|
} |
|
}
|
|
|