NS-3 Script
kns-3-sim.cc is an NS-3 network simulation that models the communication performance of a Kubernetes cluster. For a candidate pod placement node, it simulates how a newly placed pod (the client) would reach all existing services running on other nodes (the servers). Each node is scored based on the simulated network metrics, and the results are returned to the KNS-3 scheduler to guide its placement decision.
Directorykns-3-sim/
- kns-3-sim.cc Main simulation logic, entry point
- kns-3-sim.h Struct definitions
Directorylib/
- json.h JSON parsing / serialisation helpers
- json.cc
Directorynlohmann/
- json.hpp nlohmann/json single-header library
Data Structures (kns-3-sim.h)
Section titled “Data Structures (kns-3-sim.h)”NodeMetrics
Section titled “NodeMetrics”Represents a single Kubernetes worker node. Populated from the JSON input provided by the FastAPI service.
| Field | Type | Description |
|---|---|---|
nodeName |
string |
Kubernetes node hostname |
latency |
double |
Round-trip latency in milliseconds |
packetLoss |
double |
Packet loss ratio (0.0 – 1.0) |
bandwidth |
double |
Raw interface capacity in bytes/sec |
congestion |
double |
Current transmit rate in bytes/sec |
ScoringWeights
Section titled “ScoringWeights”Controls how the four metrics contribute to the final placement score. All fields are required in the JSON if weights is present; otherwise defaults are used:
| Weight | Default | Description |
|---|---|---|
latency |
0.40 |
Higher weight = latency matters more |
throughput |
0.25 |
|
packetLoss |
0.20 |
|
utilization |
0.15 |
Weights are normalised at scoring time so they do not need to sum to exactly 1.0.
ScoringBounds
Section titled “ScoringBounds”Controls the normalisation range for each metric. Values outside their bound are clamped to 0 or 100. Defaults:
| Field | Default | Description |
|---|---|---|
maxLatency |
500.0 |
Latency (ms) at which latency score reaches 0 |
maxThroughput |
100.0 |
Throughput (Mbps) at which throughput score is 100 |
maxPacketLoss |
0.10 |
Packet loss ratio at which packet loss score is 0 |
maxUtilization |
0.50 |
Utilisation ratio at which utilisation score is 0 |
SimulationConfig
Section titled “SimulationConfig”Top-level input parsed from JSON:
| Field | Required | Default | Description |
|---|---|---|---|
nodes |
Yes | - | List of NodeMetrics (maximum 30) |
simulationTime |
No | 10 |
Simulation duration in seconds |
podPlacementNode |
No | "" |
If set, only this node is evaluated; otherwise all nodes are tested |
weights |
No | see above | Scoring weights (ScoringWeights) |
bounds |
No | see above | Normalisation bounds (ScoringBounds) |
SimulationResult
Section titled “SimulationResult”Produced per evaluated node:
| Field | Description |
|---|---|
nodeName |
Node hostname |
avgLatency |
Mean per-packet latency in ms |
minLatency |
Minimum observed latency in ms |
maxLatency |
Maximum observed latency in ms |
packetLossRate |
Fraction of transmitted packets that were lost |
throughput |
Throughput in Mbps |
avgUtilization |
Mean link utilisation across all paths |
packetsSent |
Total packets transmitted |
packetsReceived |
Total packets received |
score |
Composite placement score (0 – 100) |
success |
Whether the simulation completed without error |
error |
Error message if success is false |
Network Topology
Section titled “Network Topology”The simulation uses a full-mesh point-to-point topology. Every node has a direct link to every other node. This results in O(N²) links but provides the most realistic representation of intra-cluster pod-to-pod reachability.
Each link is configured independently using the metrics of the two connected nodes:
| Link Parameter | Derivation |
|---|---|
| Delay | Average of both nodes’ latency values (converted to ms) |
| Data Rate | Minimum bandwidth of the two nodes × available fraction |
| Packet Loss | Maximum packet loss of the two nodes (conservative) |
| Queue Size | Bandwidth-delay product / 1500 bytes, minimum 50 packets, scaled up under congestion |
Available Bandwidth Calculation
Section titled “Available Bandwidth Calculation”utilization = min(congestion / bandwidth, 0.95)availableBandwidth = bandwidth × (1 − utilization)A 5% floor is enforced (0.05 × bandwidth) so links do not become completely saturated in the model.
IP Addressing
Section titled “IP Addressing”Subnets are allocated sequentially in the 10.x.y.0/24 space, one per link. Routing tables are populated using Ipv4GlobalRoutingHelper::PopulateRoutingTables().
Traffic Model
Section titled “Traffic Model”Pod Placement Node → (client) → all other nodesAll other nodes → (server) → receives trafficThe placement candidate acts as the client (new pod), and every other node acts as a server (existing service, database, API, cache, etc.). This models whether a newly placed pod can efficiently reach all services already running in the cluster.
Application Layer
Section titled “Application Layer”- Protocol: UDP echo (UdpEchoServer / UdpEchoClient)
- Packet size: 1024 bytes
- Send interval: 10 ms (100 packets/second per server)
- Per-server start time: staggered by
2.0 + i × 0.1seconds to avoid a simultaneous burst - Max packets: computed from
runTime / interval, floored at 100
Packet Loss Simulation
Section titled “Packet Loss Simulation”An ns3::RateErrorModel is attached to both ends of each link when the computed packetLoss > 0. The error unit is per-packet, so the error rate maps directly to the Prometheus-derived packet loss ratio.
Statistics Collection
Section titled “Statistics Collection”NS-3’s FlowMonitor is installed across all nodes and polled after Simulator::Run() completes.
Aggregation over all flows:
avgLatency = sum(flow.delaySum × flow.rxPackets) / totalRxPackets [ms]packetLossRate = totalLostPackets / totalTxPacketsthroughput = (totalRxPackets × 1024 × 8) / (simulationTime × 1e6) [Mbps]avgUtilization = mean(congestion_i / bandwidth_ij) across all server pathsScoring Algorithm
Section titled “Scoring Algorithm”CalculatePlacementScore() converts raw metrics into a single 0–100 score. The normalisation ranges are configurable via ScoringBounds (defaults shown):
| Metric | Direction | Default range | Formula |
|---|---|---|---|
| Latency | Lower better | 0 – 500 ms | 100 × max(0, 1 − latency / maxLatency) |
| Throughput | Higher better | 0 – 100 Mbps | min(100, throughput / maxThroughput × 100) |
| Packet Loss | Lower better | 0 – 10% | 100 × max(0, 1 − lossRate / maxPacketLoss) |
| Utilization | Lower better | 0 – 50% | 100 × max(0, 1 − utilization / maxUtilization) |
score = (latencyScore × w_latency) + (throughputScore × w_throughput) + (packetLossScore × w_packetLoss) + (utilizationScore × w_utilization)Weights are normalised before use:
w_latency = latency / (latency + throughput + packetLoss + utilization)CLI Parameters
Section titled “CLI Parameters”The binary accepts the following command-line arguments:
| Parameter | Default | Description |
|---|---|---|
--jsonInput |
required | JSON input string, or - to read from stdin |
--jsonOutput |
true |
Emit results as JSON (set to false for human-readable output) |
--seed |
1 |
RNG seed for reproducible packet loss simulation |
The FastAPI service always passes --jsonInput=- and pipes the request JSON to stdin.
Execution Flow
Section titled “Execution Flow”main() └── Set RNG seed (--seed) └── ParseJsonInput() # Deserialise JSON from stdin or --jsonInput arg └── RunSimulation() # Loop over candidate nodes (max 30) └── RunSimulationForPlacement() # Full NS-3 sim for one placement ├── Create NodeContainer ├── Install InternetStack ├── Build full-mesh P2P topology ├── Apply RateErrorModel per link ├── Assign 10.x.y.0/24 subnets ├── PopulateRoutingTables ├── Install UdpEchoServer on all non-placement nodes ├── Install UdpEchoClient on placement node ├── Install FlowMonitor ├── Simulator::Run() └── Collect & aggregate FlowMonitor stats └── CalculatePlacementScore() └── Sort results by score (descending) └── GenerateJsonOutput() # Emit JSON to stdoutInput / Output
Section titled “Input / Output”Input (stdin via --jsonInput=-)
Section titled “Input (stdin via --jsonInput=-)”{ "nodes": [ { "name": "worker-node-1", "latency": 10.0, "packetLoss": 0.001, "bandwidth": 1250000000, "congestion": 48000000 }, { "name": "worker-node-2", "latency": 15.0, "packetLoss": 0.002, "bandwidth": 1250000000, "congestion": 60000000 } ], "simulationTime": 20, "podPlacementNode": "worker-node-1", "weights": { "latency": 0.4, "throughput": 0.25, "packetLoss": 0.2, "utilization": 0.15 }, "bounds": { "maxLatency": 500, "maxThroughput": 100, "maxPacketLoss": 0.1, "maxUtilization": 0.5 }}Output (stdout)
Section titled “Output (stdout)”{ "success": true, "performance": { "simulationDuration": 1.42, "nodesEvaluated": 2, "weights": { "latency": 0.4, "throughput": 0.25, "packetLoss": 0.2, "utilization": 0.15 }, "bounds": { "maxLatency": 500.0, "maxThroughput": 100.0, "maxPacketLoss": 0.1, "maxUtilization": 0.5 } }, "results": [ { "nodeName": "worker-node-1", "metrics": { "avgLatency": 12.3, "minLatency": 8.5, "maxLatency": 19.4, "packetLossRate": 0.002, "throughput": 43.1, "avgUtilization": 0.038, "packetsSent": 950, "packetsReceived": 948, "score": 84.7 } } ]}Results are ordered by score descending. The FastAPI service deserialises this output directly into a SimulationResponse.
The simulation is built with CMake against an NS-3 release pinned, with its
checksum, in simulator/ns3.env. From simulator/:
make build # fetch + verify + configure ns-3 once, then build kns-3-simexport NS3_SCRIPT=$(make -s ns3-script)The binary is installed to simulator/bin/kns-3-sim. The Docker build runs the
same scripts/ns3.sh with the same configure flags, and installs the binary at
/code/kns-3-sim inside the container.
