Index: source/tools/replayprofile/graph2.html =================================================================== --- /dev/null +++ source/tools/replayprofile/graph2.html @@ -0,0 +1,38 @@ + + + + + 0 A.D. Simulation Profiling + + + + + + +
+
+

Json File

+ + + +
+
+
+ +
+
+
+
+
+
+
+
+ + 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); +} +