Index: source/tools/replayprofile/graph2.html
===================================================================
--- /dev/null
+++ source/tools/replayprofile/graph2.html
@@ -0,0 +1,38 @@
+
+
+
+
+ 0 A.D. Simulation Profiling
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: source/tools/replayprofile/graph2.js
===================================================================
--- /dev/null
+++ source/tools/replayprofile/graph2.js
@@ -0,0 +1,218 @@
+var replayData = [];
+
+/**
+ * Number of turns between two saved profiler snapshots.
+ * Keep in sync with Replay.cpp.
+ */
+var PROFILE_TURN_INTERVAL = 20;
+
+/**
+ * These columns are never displayed.
+ */
+var filteredColumns = [
+ "sim update", // indistringuishable from Total
+ "unlogged" // Bogus
+];
+
+
+/**
+ * Determines how the the different graphs are formatted.
+ */
+var graphFormat = {
+ "time": {
+ "axisTitle": "Time per frame",
+ "unit": "milliseconds",
+ "digits": 2,
+ "scale": 1,
+ },
+ "bytes": {
+ "axisTitle": "Memory",
+ "unit": "Megabytes",
+ "digits": 2,
+ "scale": 1 / 1024 / 1024,
+ "isColumn": function(label) {
+ return label.indexOf("bytes") != -1;
+ }
+ },
+ "garbageCollection": {
+ "axisTitle": "Number of Garbace Collections",
+ "unit": "",
+ "scale": 1,
+ "digits": 0,
+ "isColumn": function(label) {
+ return label.indexOf("number of GCs") != -1;
+ }
+ }
+};
+graphFormat.total = graphFormat.time;
+
+function showReplayData()
+{
+ var displayedColumn = $("#replayGraphSelection").val();
+
+ $.plot($("#replayGraph"), getReplayGraphData(displayedColumn), {
+ "grid": {
+ "hoverable": true
+ },
+ "zoom": {
+ "interactive": true
+ },
+ "pan": {
+ "interactive": true
+ },
+ "legend": {
+ "container": $("#replayGraphLegend")
+ }
+ });
+
+ $("#replayGraph").bind("plothover", function (event, pos, item) {
+ $("#tooltip").remove();
+ if (!item)
+ return;
+
+ showTooltip(
+ item.pageX,
+ item.pageY,
+ displayedColumn,
+ item.series.label,
+ item.datapoint[0],
+ item.datapoint[1].toFixed(graphFormat[displayedColumn].digits));
+ });
+
+ $("#replayGraphDescription").html(
+ "X axis: Turn Number
" +
+ "Y axis: " + graphFormat[displayedColumn].axisTitle +
+ (graphFormat[displayedColumn].unit ? " [" + graphFormat[displayedColumn].unit + "]" : "") + "
" +
+ "Drag to pan, mouse-wheel to zoom
"
+ );
+}
+
+/**
+ * Filter the affected columns and apply the scaling and rounding.
+ */
+function getReplayGraphData(displayedColumn)
+{
+ var replayGraphData = [];
+
+ for (var i = 0; i < replayData.length; ++i)
+ {
+ var label = replayData[i].label;
+
+ if (filteredColumns.indexOf(label) != -1 ||
+ (displayedColumn == "bytes") != graphFormat.bytes.isColumn(label) ||
+ (displayedColumn == "garbageCollection") != (graphFormat.garbageCollection.isColumn(label)) ||
+ (displayedColumn == "total" && !label.match(/:total:/)))
+ continue
+
+ var data = [];
+ for (var j = 0; j < replayData[i].data.length; ++j)
+ data.push([
+ replayData[i].data[j][0] * PROFILE_TURN_INTERVAL,
+ replayData[i].data[j][1] * graphFormat[displayedColumn].scale
+ ]);
+
+ replayGraphData.push({
+ "label": label,
+ "data": data
+ });
+ }
+
+ return replayGraphData;
+}
+
+function showTooltip(x, y, displayedColumn, label, turn, value)
+{
+ $("body").append(
+ $('' + label + " at turn " + turn + ": " + value + " " + graphFormat[displayedColumn].unit + "
").css({
+ "position": "absolute",
+ "top": y + 5,
+ "left": x + 5,
+ "border": "1px solid #fdd",
+ "padding": "2px",
+ "background-color": "#fee",
+ "opacity": 0.8
+ }));
+}
+
+function loadFiles(){
+ const input = document.getElementById('fileinput');
+ for(let i = 0; i < input.files.length; i++)
+ {
+ loadFile(input.files[i]);
+ }
+}
+
+function loadAverageFiles(){
+ const input = document.getElementById('fileinput');
+ let name;
+ output = {};
+ let loadedCount = 0;
+ for(let i = 0; i < input.files.length; i++)
+ {
+ getFileJson(input.files[i], function(fileData, fileName)
+ {
+ name ||= fileName;
+ for(let i in fileData)
+ {
+ let category = fileData[i];
+ let label = category.label;
+ if(output[label])
+ {
+ for(let i in category.data)
+ {
+ output[label].data[i][1] += category.data[i][1];
+ }
+ }
+ output[label] ||= category;
+ }
+ // average
+ if(++loadedCount == input.files.length)
+ {
+ for(let i in output)
+ {
+ let sum = 0;
+ for(let j in output[i].data)
+ {
+ output[i].data[j][1] /= input.files.length;
+ sum += output[i].data[j][1];
+ }
+ replayData.push(output[i]);
+ output[i].label = fileName + ':' + output[i].label + ':' + Math.round(sum);
+ }
+ showReplayData();
+ }
+ });
+ }
+
+}
+
+
+function loadFile(file)
+{
+ getFileJson(file, function(data, fileName)
+ {
+ for(let i in jsonData)
+ {
+ category = jsonData[i];
+ let sum = 0;
+ for(let j in category.data)
+ sum += category.data[j][1];
+ category.label = file.name + ':' + category.label + ':' + Math.round(sum);
+ replayData.push(category);
+ }
+ showReplayData();
+ });
+}
+
+function getFileJson(file, next)
+{
+ const fr = new FileReader();
+ fr.onload = function(e, f)
+ {
+ const lines = e.target.result;
+ jsonData = eval(lines); // use eval instead of JSON.parse because the data is not technicly valid JSON due to unquoted names.
+ next(jsonData, file.name);
+ }
+ fr.readAsText(file);
+}
+