Last active
December 18, 2015 20:29
-
-
Save msbarry/5841033 to your computer and use it in GitHub Desktop.
MMN vs. NxMM1
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
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/> | |
<title>MMN vs. NxMM1</title> | |
<style type="text/css"> | |
body { | |
font-size: 12px; | |
font-family: Arial; | |
width: 960px; | |
margin: 1em auto 0.3em auto; | |
} | |
.controls { | |
margin: 10px 0 0 10px; | |
} | |
#chart { | |
width: 100%; | |
margin-top: 10px; | |
} | |
#chart svg { | |
margin-top: 10px; | |
} | |
.notshown { | |
margin-left: 20px; | |
} | |
svg .server { | |
fill-opacity: 0; | |
stroke-opacity: 1; | |
stroke: #000; | |
shape-rendering: crispEdges; | |
} | |
.axis line, .axis path { | |
fill: none; | |
stroke: #000; | |
shape-rendering: crispEdges; | |
} | |
.axis text { | |
text-shadow: 0 1px 0 #fff; | |
} | |
line.avg { | |
stroke-width: 3px; | |
} | |
circle { | |
transition: fill-opacity 750ms ease; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="controls"> | |
<p>Arrivals per second: <input type="number" step="0.1" value="5" id="arr"></input> | |
Seconds to service: <input type="number" step="0.1" value="2" id="ts"></input></p> | |
</div> | |
<div id="chart"></div> | |
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> | |
<script> | |
var margin = {top: 1, right: 20, bottom: 1, left: 20}, | |
outerWidth = 960, | |
outerHeight = 500 - 12 - 26 - 12, | |
w = outerWidth - margin.left - margin.right, | |
h = outerHeight - margin.top - margin.bottom; | |
var numServers = 10, | |
displayNumResults = 50, | |
packetPadding = 3, | |
packetRadius = 5, | |
serverSize = packetPadding + 2 * packetRadius, | |
serverX = w - 150; | |
var id = 0; | |
var serverIds = d3.range(numServers); | |
var mm1y = d3.scale.ordinal().domain(serverIds).rangePoints([0, h / 2], 2); | |
var mmNy = d3.scale.ordinal().domain(serverIds).rangePoints([h / 2, h], 2); | |
var mm1EnterY = h / 4, mmNEnterY = 3 * h / 4; | |
var resultsY = d3.scale.linear().range([h - 20, 20]).domain([0, 1]).nice(5); | |
var resultOpacityScale = d3.scale.linear().domain([0, displayNumResults]).range([0.15, 0]); | |
var svg = d3.select("#chart") | |
.append("svg") | |
.attr("width", outerWidth) | |
.attr("height", outerHeight) | |
.append("g") | |
.attr("transform", "translate(" + [margin.left, margin.top].join(",") + ")"); | |
function createServerRects(yScale) { | |
svg.append("g") | |
.selectAll("rect") | |
.data(serverIds) | |
.enter() | |
.append("rect") | |
.attr("class", "server") | |
.attr("width", serverSize) | |
.attr("height", serverSize) | |
.attr("x", serverX - serverSize / 2) | |
.attr("y", function (d) { return yScale(d) - serverSize / 2; }); | |
} | |
createServerRects(mm1y); | |
createServerRects(mmNy); | |
// utilities | |
function schedulePoisson(f, averageRatePerSecond) { | |
var delay = (1000 * -Math.log(Math.random()) / averageRatePerSecond) || 1000; | |
setTimeout(f, delay); | |
return delay; | |
} | |
function averageServiceTimeInSeconds() { | |
return +d3.select("#ts").node().value; | |
} | |
function arrivalRatePerSecond() { | |
return +d3.select("#arr").node().value; | |
} | |
function keyById(d) { | |
return d.id; | |
} | |
function atTheEarliest(time, property) { | |
return function (d) { | |
return Math.max(0, (d[property] + time) - new Date().getTime()); | |
}; | |
} | |
function roundToPower(num, base) { | |
return Math.pow(base, Math.ceil(Math.log(num) / Math.log(base))); | |
} | |
</script> | |
<script src="latency.js"></script> | |
<script src="NxMM1.js"></script> | |
<script src="MMN.js"></script> | |
<script> | |
// start the simulation | |
function packetArrival() { | |
schedulePoisson(packetArrival, arrivalRatePerSecond()); | |
mm1PacketArrival(); | |
mmNPacketArrival(); | |
} | |
schedulePoisson(packetArrival, arrivalRatePerSecond()); | |
</script> | |
</body> | |
</html> |
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
// handle displaying of latencies | |
var results = []; | |
var resultsAxis = d3.svg.axis().orient("right").scale(resultsY); | |
var axis = svg.append("g") | |
.attr("transform", "translate(" + [w - 20, 0] + ")") | |
.append("g") | |
.attr("class", "axis") | |
.call(resultsAxis); | |
axis.append("text") | |
.attr("text-anchor", "middle") | |
.attr("y", 9) | |
.text("Latency (s)"); | |
var mm1avgline = axis.append("line") | |
.attr("class", "avg") | |
.attr("y1", -100).attr("y2", -100) | |
.attr("x1", -20).attr("x2", 0) | |
.style("stroke", "blue"); | |
var mmNavgline = axis.append("line") | |
.attr("class", "avg") | |
.attr("y1", -100).attr("y2", -100) | |
.attr("x1", -20).attr("x2", 0) | |
.style("stroke", "red"); | |
function latency(d) { | |
return d.latency; | |
} | |
function displayResult(circle) { | |
var lastMax = d3.max(results, latency); | |
var datum = circle.datum(); | |
datum.latencyMS = new Date().getTime() - datum.enqueueTime; | |
datum.latency = datum.latencyMS / 1000; | |
results.unshift(datum); | |
if (results.length > displayNumResults) { results.splice(displayNumResults, 1); } | |
var newMax = d3.max(results, latency); | |
var mmNs = results.filter(function (d) { return typeof d.server === "number"; }); | |
var mm1s = results.filter(function (d) { return typeof d.server !== "number"; }); | |
var mm1avg = d3.mean(mm1s, latency); | |
var mmNavg = d3.mean(mmNs, latency); | |
if (mm1avg) { | |
mm1avgline.transition() | |
.attr("y1", resultsY(mm1avg)) | |
.attr("y2", resultsY(mm1avg)); | |
} | |
if (mmNavg) { | |
mmNavgline.transition() | |
.attr("y1", resultsY(mmNavg)) | |
.attr("y2", resultsY(mmNavg)); | |
} | |
circle.classed("result", true); | |
var circles = svg.selectAll("circle.result") | |
.data(results, keyById); | |
var toTransition = circle; | |
if (newMax !== lastMax) { | |
resultsY.domain([0, roundToPower(d3.max(results, latency), 1.618)]); | |
axis.transition().call(resultsAxis); | |
toTransition = circles; // on rescale, transition ALL circles, not just one | |
} | |
toTransition.transition() | |
.delay(atTheEarliest(250, 'serveTime')) | |
.style("fill-opacity", function (d, i) { return resultOpacityScale(i); }) | |
.attr("cx", function (d) { return w - 30 - (typeof d.server === "number" ? packetRadius * 2 : 0); }) | |
.attr("cy", function (d) { return resultsY(d.latency); }); | |
circles.exit().remove(); | |
} |
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
// MMN simulation (load balancer) | |
var mmNServers = d3.range(numServers).map(function () { return null; }); | |
var mmNQueue = []; | |
var mmNGroup = svg.append("g"); | |
function styleMMNPacket(packet) { | |
packet | |
.attr("cx", function (d, i) { return serverX - (i + 3) * 2 * (packetRadius + packetPadding); }) | |
.attr("cy", mmNEnterY); | |
} | |
function mmNPacketArrival() { | |
mmNQueue.push({ | |
id: id++, | |
enqueueTime: new Date().getTime() | |
}); | |
var packets = mmNGroup.selectAll("circle.active") | |
.data(mmNQueue, keyById); | |
// move in new packets | |
packets.enter().append("circle") | |
.each(function (d) { d.circle = this; }) | |
.attr("r", packetRadius) | |
.attr("cx", -packetRadius) | |
.attr("cy", mmNEnterY) | |
.attr("fill", "red") | |
.classed("active", true) | |
.transition() | |
.ease('cubic-out') // fast-in slow-out | |
.call(styleMMNPacket); | |
servicePacketsOnIdleServers(); | |
} | |
function servicePacketsOnIdleServers() { | |
var change = false; | |
for (var i = 0; mmNQueue.length > 0 && i < numServers; i++) { | |
if (!mmNServers[i]) { | |
servicePacketOnServer(i); | |
change = true; | |
} | |
} | |
if (change) { | |
var packets = mmNGroup.selectAll("circle.active") | |
.data(mmNQueue, keyById); | |
// update existing packets | |
packets.transition() | |
.delay(atTheEarliest(250, 'enqueueTime')) | |
.call(styleMMNPacket); | |
// move packets out of the queue and into one of the servers | |
packets.exit() | |
.classed("active", false) | |
.transition() | |
.delay(atTheEarliest(250, 'enqueueTime')) | |
.attr("cy", function (d) { return mmNy(d.server); }) | |
.attr("cx", serverX); | |
} | |
} | |
function servicePacketOnServer(i) { | |
var pkt = mmNQueue.shift(); | |
mmNServers[i] = d3.select(pkt.circle); | |
pkt.server = i; | |
pkt.serveTime = Math.max(pkt.enqueueTime + 250, new Date().getTime()); | |
schedulePoisson(finishServicingOnServer(i), 1 / averageServiceTimeInSeconds()); | |
} | |
function finishServicingOnServer(i) { | |
return function () { | |
displayResult(mmNServers[i]); | |
mmNServers[i] = null; | |
servicePacketsOnIdleServers(); | |
}; | |
} |
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
// NxMM1 simulation | |
var mm1Queues = serverIds.map(function () { return []; }); | |
var mm1ServerId = 0; | |
var mm1Groups = svg.append("g").selectAll("g") | |
.data(serverIds) | |
.enter() | |
.append("g"); | |
function styleMM1Packet(packet) { | |
packet | |
.attr("cx", function (d, i) { return serverX - i * 2 * (packetRadius + packetPadding); }) | |
.attr("cy", function (d) { return d.y; }); | |
} | |
function mm1PacketArrival() { | |
var queue = mm1Queues[mm1ServerId]; | |
var group = mm1Groups.filter(function (d) { return d === mm1ServerId; }); | |
queue.push({ | |
id: id++, | |
y: mm1y(mm1ServerId), | |
enqueueTime: new Date().getTime() | |
}); | |
var packets = group.selectAll("circle.active") | |
.data(queue, keyById); | |
// move in new packets | |
packets.enter().append("circle") | |
.each(function (d) { d.circle = this; }) | |
.classed("active", true) | |
.attr("r", packetRadius) | |
.attr("cx", -packetRadius) | |
.attr("cy", mm1EnterY) | |
.attr("fill", "blue") | |
.transition() | |
.ease('cubic-out') // fast-in slow-out | |
.call(styleMM1Packet); | |
// kick the server to start working if it was previously idle | |
if (queue.length === 1) { | |
queue[0].serveTime = queue[0].enqueueTime; | |
schedulePoisson(mm1PacketService(queue, group), 1 / averageServiceTimeInSeconds()); | |
} | |
// schedule next packet | |
mm1ServerId = (mm1ServerId + 1) % numServers; | |
} | |
function mm1PacketService(queue, group) { | |
return function result() { | |
queue.shift(); | |
if (queue[0]) { | |
queue[0].serveTime = Math.max(queue[0].enqueueTime + 250, new Date().getTime()); | |
} | |
var packets = group.selectAll("circle.active") | |
.data(queue, keyById); | |
// update existing packets | |
packets | |
.transition() | |
.delay(atTheEarliest(250, 'enqueueTime')) | |
.call(styleMM1Packet); | |
// move out old (serviced) packets | |
packets.exit() | |
.classed("active", false) | |
.call(displayResult); | |
if (queue.length > 0) { | |
schedulePoisson(result, 1 / averageServiceTimeInSeconds()); | |
} | |
}; | |
} | |
// start the simulations | |
schedulePoisson(mm1PacketArrival, arrivalRatePerSecond()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment