Created
May 1, 2019 13:42
-
-
Save cameronbourke/2c6d2bae778a6b1b3b14dc99edb29694 to your computer and use it in GitHub Desktop.
Simulating a simple network with two nodes A and B, with a buffer on the outbound link from A to B.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Constants | |
// ----------------------------------------------- | |
const DATA_PACKET_SIZE = 500 + 20 + 20; | |
const ACK_PACKET_SIZE = 0; | |
const BITS_TO_BYTE = 8; | |
const TOCK_MS = 1; | |
const LINK_RATE_BPS = 2 * Math.pow(10, 6); | |
const LINK_DELAY_MS = 50; | |
const BUFFER_SIZE = 20; | |
let maxAvgRTT = Number.NEGATIVE_INFINITY; | |
let minAvgRTT = Number.POSITIVE_INFINITY; | |
// Utils | |
// ----------------------------------------------- | |
class Queue { | |
constructor (maxsize = 100) { | |
this.maxsize = maxsize; | |
this.list = []; | |
} | |
enqueue (el) { | |
if (this.list.length >= this.maxsize) | |
throw new Error("QUEUE_OVERFLOW"); | |
this.list.push(el); | |
} | |
dequeue () { | |
if (this.list.length <= 0) | |
return null; | |
return this.list.shift(); | |
} | |
peak () { | |
if (this.isEmpty()) return null; | |
return this.list[0]; | |
} | |
isEmpty () { | |
return this.size() <= 0; | |
} | |
size () { | |
return this.list.length; | |
} | |
toString () { | |
const els = this.list.reduce((acc, packet) => { | |
if (acc === "") return `${packet.id}`; | |
return `${acc}, ${packet.id}`; | |
}, ''); | |
return `{ ${els} }`; | |
} | |
} | |
// Classes | |
// ----------------------------------------------- | |
class Packet { | |
constructor (size) { | |
this.id = Packet.id++; | |
this.enqueuedAt = -1; | |
this.transmittedAt = -1; | |
this.receivedAt = -1; | |
this.bytes = size; | |
this.size = size; | |
} | |
} | |
Packet.id = 0; | |
class Link { | |
constructor () { | |
this.transmitting = new Queue(); | |
this.transmitted = []; | |
this.speed = LINK_RATE_BPS; | |
this.delay = LINK_DELAY_MS; | |
} | |
propagate (prevTime, nextTime) { | |
while (!this.transmitting.isEmpty()) { | |
const packet = this.transmitting.peak(); | |
if (packet.transmittedAt == nextTime) break; | |
if ((packet.transmittedAt + this.delay) > nextTime) break; | |
packet.receivedAt = nextTime; | |
this.transmitted.push(this.transmitting.dequeue()); | |
} | |
} | |
receive (prevTime, nextTime) { | |
const received = [...this.transmitted]; | |
this.transmitted = []; | |
return received; | |
} | |
send (packet, time) { | |
packet.transmittedAt = time; | |
this.transmitting.enqueue(packet); | |
} | |
} | |
class InputBufferedLink extends Link { | |
constructor () { | |
super(); | |
this.buffer = new Queue(BUFFER_SIZE); | |
} | |
send (packet, time) { | |
packet.enqueuedAt = time; | |
this.buffer.enqueue(packet); | |
} | |
transmit (prevTime, nextTime) { | |
const timeElapsed = nextTime - prevTime; | |
const bytesPerMs = (this.speed / 1000) / BITS_TO_BYTE; | |
let bytes = Math.round(timeElapsed * bytesPerMs); | |
while (bytes > 0 && !this.buffer.isEmpty()) { | |
const packet = this.buffer.peak(); | |
if (bytes <= packet.bytes) { | |
packet.bytes = packet.bytes - bytes; | |
bytes = 0; | |
} | |
else { | |
bytes = bytes - packet.bytes; | |
packet.transmittedAt = nextTime; | |
packet.bytes = packet.size; | |
this.transmitting.enqueue(this.buffer.dequeue()); | |
} | |
} | |
} | |
} | |
class Node { | |
constructor ({ packetSize, outwardLink, inwardLink }) { | |
this.packetSize = packetSize; | |
this.outwardLink = outwardLink; | |
this.inwardLink = inwardLink; | |
this.receivedN = 0; | |
this.received = []; | |
} | |
receive (prevTime, nextTime) { | |
this.received = this.inwardLink.receive(); | |
this.receivedN += this.received.length; | |
} | |
clearReceived () { | |
this.received = []; | |
} | |
} | |
class SendingNode extends Node { | |
constructor (opts) { | |
super(opts); | |
this.unackedN = 0; | |
this.cwnd = 1; | |
} | |
send (prevTime, nextTime) { | |
this.unackedN -= this.received.length; | |
const sumRTT = this.received.reduce((acc, packet) => { | |
return acc + (packet.receivedAt - packet.enqueuedAt); | |
}, 0); | |
if (this.received.length) { | |
const avgRTT = sumRTT / this.received.length; | |
if (avgRTT < minAvgRTT) minAvgRTT = avgRTT; | |
if (avgRTT > maxAvgRTT) maxAvgRTT = avgRTT; | |
} | |
this.clearReceived(); | |
if (this.receivedN >= this.cwnd) { | |
this.receivedN = 0; | |
this.cwnd++; | |
} | |
while (this.unackedN < this.cwnd) { | |
this.outwardLink.send(new Packet(this.packetSize), nextTime); | |
this.unackedN++; | |
} | |
} | |
} | |
class ReceivingNode extends Node { | |
send (prevTime, nextTime) { | |
this.received.forEach(() => { | |
const ack = new Packet(ACK_PACKET_SIZE); | |
this.outwardLink.send(ack, nextTime); | |
}); | |
this.clearReceived(); | |
} | |
} | |
function run () { | |
const linkAB = new InputBufferedLink(); | |
const linkBA = new Link(); | |
const nodeA = new SendingNode({ | |
packetSize: DATA_PACKET_SIZE, | |
outwardLink: linkAB, | |
inwardLink: linkBA, | |
}); | |
const nodeB = new ReceivingNode({ | |
packetSize: ACK_PACKET_SIZE, | |
outwardLink: linkBA, | |
inwardLink: linkAB, | |
}); | |
function tick (prevTime) { | |
const nextTime = prevTime + 1; | |
try { | |
nodeA.send(prevTime, nextTime); | |
} | |
catch (e) { | |
if (e.message === "QUEUE_OVERFLOW") { | |
console.log('MAX CWND after %ds is: %d MSS', Math.round(nextTime / 1000), nodeA.cwnd - 1); | |
console.log('MIN AVG RTT is: %dms', minAvgRTT); | |
console.log('MAX AVG RTT is: %dms', maxAvgRTT); | |
} | |
else { | |
console.log('e', e); | |
console.log('e.message', e.message); | |
} | |
return; | |
} | |
linkAB.transmit(prevTime, nextTime); | |
linkAB.propagate(prevTime, nextTime); | |
nodeB.receive(prevTime, nextTime); | |
nodeB.send(prevTime, nextTime); | |
linkBA.propagate(prevTime, nextTime); | |
nodeA.receive(prevTime, nextTime); | |
// uncomment below to see log of buffer every tick | |
// console.log(linkAB.buffer.toString()); | |
setTimeout(() => tick(nextTime), TOCK_MS); | |
} | |
console.log('running sim...'); | |
tick(0); | |
} | |
run(); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment