blob: 69832f93f9a967931eb12eee089e35b2033f5107 [file] [log] [blame]
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE html>
<html>
<head>
<title>Build System Resource Usage</title>
<meta charset='utf-8'>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<style>
svg {
overflow: visible;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.area {
fill: steelblue;
}
.graphs {
text-anchor: end;
}
.timeline {
fill: steelblue;
stroke: gray;
stroke-width: 3;
}
.short {
fill: gray;
stroke: gray;
stroke-width: 3;
}
#tooltip {
z-index: 10;
position: fixed;
background: #efefef;
}
</style>
</head>
<body>
<script>
var currentResources;
/**
* Interface for a build resources JSON file.
*/
function BuildResources(data) {
if (data.version != 1) {
throw new Error("Only version 1 of the JSON format is supported.");
}
this.resources = [];
var cpu_fields = data.cpu_times_fields;
var io_fields = data.io_fields;
var virt_fields = data.virt_fields;
var swap_fields = data.swap_fields;
function convert(dest, source, key, fields) {
var i = 0;
fields.forEach(function (field) {
dest[key][field] = source[key][i];
i++;
});
}
var offset = data.start;
var cpu_times_totals = {};
cpu_fields.forEach(function (field) {
cpu_times_totals[field] = 0;
});
this.ioTotal = {};
var i = 0;
io_fields.forEach(function (field) {
this.ioTotal[field] = data.io[i];
i++;
}.bind(this));
data.resources.forEach(function (sample) {
var entry = {
start: sample.start - offset,
end: sample.end - offset,
duration: sample.duration,
cpu_percent: sample.cpu_percent,
cpu_times: {},
cpu_times_percents: {},
io: {},
virt: {},
swap: {},
};
convert(entry, sample, "cpu_times", cpu_fields);
convert(entry, sample, "io", io_fields);
convert(entry, sample, "virt", virt_fields);
convert(entry, sample, "swap", swap_fields);
var total = 0;
for (var k in entry.cpu_times) {
cpu_times_totals[k] += entry.cpu_times[k];
total += entry.cpu_times[k];
}
for (var k in entry.cpu_times) {
if (total == 0) {
if (k == "idle") {
entry.cpu_times_percents[k] = 100;
} else {
entry.cpu_times_percents[k] = 0;
}
} else {
entry.cpu_times_percents[k] = entry.cpu_times[k] / total * 100;
}
}
this.resources.push(entry);
}.bind(this));
this.cpu_times_fields = [];
// Filter out CPU fields that have no values.
for (var k in cpu_times_totals) {
var v = cpu_times_totals[k];
if (v) {
this.cpu_times_fields.push(k);
continue;
}
this.resources.forEach(function (entry) {
delete entry.cpu_times[k];
delete entry.cpu_times_percents[k];
});
}
this.offset = offset;
this.data = data;
}
BuildResources.prototype = Object.freeze({
get start() {
return this.data.start;
},
get startDate() {
return new Date(this.start * 1000);
},
get end() {
return this.data.end;
},
get endDate() {
return new Date(this.end * 1000);
},
get duration() {
return this.data.duration;
},
get sample_times() {
var times = [];
this.resources.forEach(function (sample) {
times.push(sample.start);
});
return times;
},
get cpuPercent() {
return this.data.cpu_percent;
},
get tiers() {
var t = [];
this.data.tiers.forEach(function (e) {
t.push(e.name);
});
return t;
},
getTier: function (tier) {
for (var i = 0; i < this.data.tiers.length; i++) {
var t = this.data.tiers[i];
if (t.name == tier) {
return t;
}
}
},
});
function updateResourcesGraph() {
//var selected = document.getElementById("resourceType");
//var what = selected[selected.selectedIndex].value;
var what = "cpu";
renderResources("resource_graph", currentResources, what);
document.getElementById("wall_time").innerHTML = Math.round(currentResources.duration * 100) / 100;
document.getElementById("start_date").innerHTML = currentResources.startDate.toISOString();
document.getElementById("end_date").innerHTML = currentResources.endDate.toISOString();
document.getElementById("cpu_percent").innerHTML = Math.round(currentResources.cpuPercent * 100) / 100;
document.getElementById("write_bytes").innerHTML = currentResources.ioTotal["write_bytes"];
document.getElementById("read_bytes").innerHTML = currentResources.ioTotal["read_bytes"];
document.getElementById("write_time").innerHTML = currentResources.ioTotal["write_time"];
document.getElementById("read_time").innerHTML = currentResources.ioTotal["read_time"];
}
function renderKey(key) {
d3.json("/resources/" + key, function onResource(error, response) {
if (error) {
alert("Data not available. Is the server still running?");
return;
}
currentResources = new BuildResources(response);
updateResourcesGraph();
});
}
function renderResources(id, resources, what) {
document.getElementById(id).innerHTML = "";
var margin = {top: 20, right: 20, bottom: 20, left: 50};
var width = window.innerWidth - 50 - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
var x = d3.scale.linear()
.range([0, width])
.domain(d3.extent(resources.resources, function (d) { return d.start; }))
;
var y = d3.scale.linear()
.range([height, 0])
.domain([0, 1])
;
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
;
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".0%"))
;
var area = d3.svg.area()
.x(function (d) { return x(d.start); })
.y0(function(d) { return y(d.y0); })
.y1(function(d) { return y(d.y0 + d.y); })
;
var stack = d3.layout.stack()
.values(function (d) { return d.values; })
;
// Manually control the layer order because we want it consistent and want
// to inject some sanity.
var layers = [
["nice", "#0d9fff"],
["irq", "#ff0d9f"],
["softirq", "#ff0d9f"],
["steal", "#000000"],
["guest", "#000000"],
["guest_nice", "#000000"],
["system", "#f69a5c"],
["iowait", "#ff0d25"],
["user", "#5cb9f6"],
["idle", "#e1e1e1"],
].filter(function (l) {
return resources.cpu_times_fields.indexOf(l[0]) != -1;
});
// Draw a legend.
var legend = d3.select("#" + id)
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", 15)
.append("g")
.attr("class", "legend")
;
legend.selectAll("g")
.data(layers)
.enter()
.append("g")
.each(function (d, i) {
var g = d3.select(this);
g.append("rect")
.attr("x", i * 100 + 20)
.attr("y", 0)
.attr("width", 10)
.attr("height", 10)
.style("fill", d[1])
;
g.append("text")
.attr("x", i * 100 + 40)
.attr("y", 10)
.attr("height", 10)
.attr("width", 70)
.text(d[0])
;
})
;
var svg = d3.select("#" + id).append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
;
var data = stack(layers.map(function (layer) {
return {
name: layer[0],
color: layer[1],
values: resources.resources.map(function (d) {
return {
start: d.start,
y: d.cpu_times_percents[layer[0]] / 100,
};
}),
};
}));
var graphs = svg.selectAll(".graphs")
.data(data)
.enter().append("g")
.attr("class", "graphs")
;
graphs.append("path")
.attr("class", "area")
.attr("d", function (d) { return area(d.values); })
.style("fill", function (d) { return d.color; })
;
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
;
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
;
// Now we render a timeline of sorts of the tiers
// There is a row of rectangles that visualize divisions between the
// different items. We use the same x scale as the resource graph so times
// line up properly.
svg = d3.select("#" + id).append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", 100 + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
;
var y = d3.scale.linear().range([10, 0]).domain([0, 1]);
resources.tiers.forEach(function (t, i) {
var tier = resources.getTier(t);
var x_start = x(tier.start - resources.offset);
var x_end = x(tier.end - resources.offset);
svg.append("rect")
.attr("x", x_start)
.attr("y", 20)
.attr("height", 30)
.attr("width", x_end - x_start)
.attr("class", "timeline tier")
.attr("tier", t)
;
});
function getEntry(element) {
var tier = element.getAttribute("tier");
var entry = resources.getTier(tier);
entry.tier = tier;
return entry;
}
d3.selectAll(".timeline")
.on("mouseenter", function () {
var entry = getEntry(this);
d3.select("#tt_tier").html(entry.tier);
d3.select("#tt_duration").html(entry.duration || "n/a");
d3.select("#tt_cpu_percent").html(entry.cpu_percent || "n/a");
d3.select("#tooltip").style("display", "");
})
.on("mouseleave", function () {
var tooltip = d3.select("#tooltip");
tooltip.style("display", "none");
})
.on("mousemove", function () {
var e = d3.event;
x_offset = 10;
if (e.pageX > window.innerWidth / 2) {
x_offset = -150;
}
d3.select("#tooltip")
.style("left", (e.pageX + x_offset) + "px")
.style("top", (e.pageY + 10) + "px")
;
})
;
}
document.addEventListener("DOMContentLoaded", function() {
d3.json("list", function onList(error, response) {
if (!response || !("files" in response)) {
return;
}
renderKey(response.files[0]);
});
}, false);
</script>
<h3>Build Resource Usage Report</h3>
<div id="tooltip" style="display: none;">
<table border="0">
<tr><td>Tier</td><td id="tt_tier"></td></tr>
<tr><td>Duration</td><td id="tt_duration"></td></tr>
<tr><td>CPU %</td><td id="tt_cpu_percent"></td></tr>
</table>
</div>
<!--
<select id="resourceType" onchange="updateResourcesGraph();">
<option value="cpu">CPU</option>
<option value="io_count">Disk I/O Count</option>
<option value="io_bytes">Disk I/O Bytes</option>
<option value="io_time">Disk I/O Time</option>
<option value="virt">Memory</option>
</select>
-->
<div id="resource_graph"></div>
<div id="summary" style="padding-top: 20px">
<table border="0">
<tr><td>Wall Time (s)</td><td id="wall_time"></td></tr>
<tr><td>Start Date</td><td id="start_date"></td></tr>
<tr><td>End Date</td><td id="end_date"></td></tr>
<tr><td>CPU %</td><td id="cpu_percent"></td></tr>
<tr><td>Write Bytes</td><td id="write_bytes"></td></tr>
<tr><td>Read Bytes</td><td id="read_bytes"></td></tr>
<tr><td>Write Time</td><td id="write_time"></td></tr>
<tr><td>Read Time</td><td id="read_time"></td></tr>
</table>
</div>
</body>
</html>