Skip to content

Instantly share code, notes, and snippets.

@msbarry
Last active December 18, 2015 20:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save msbarry/5841033 to your computer and use it in GitHub Desktop.
Save msbarry/5841033 to your computer and use it in GitHub Desktop.
MMN vs. NxMM1
<!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>
// 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();
}
// 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();
};
}
// 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