From ee9e677084507a9a0cdd3e80bc4568995036d666 Mon Sep 17 00:00:00 2001 From: elexis Date: Thu, 22 Mar 2018 13:53:04 +0000 Subject: [PATCH] Simulation replay profiling graph overhaul. Support multiple graphs with different scales, units, rounding and descriptions and use it for memory allocation and number of garbage collections. Have the X axis in number of turns (instead of that divided by 20). Don't have the legend hide part of the graph. Add references to that hardcoded 20. Move graph JS to a separate JS file. Use JSON instead of JS. Add legal html structure. Comments By: Vladislav in irc yesterday This was SVN commit r21602. --- source/ps/Replay.cpp | 10 +- source/tools/replayprofile/extract.pl | 4 +- source/tools/replayprofile/graph.html | 87 +++++----------- source/tools/replayprofile/graph.js | 141 ++++++++++++++++++++++++++ 4 files changed, 177 insertions(+), 65 deletions(-) create mode 100644 source/tools/replayprofile/graph.js diff --git a/source/ps/Replay.cpp b/source/ps/Replay.cpp index 495551adf7..968e41958e 100644 --- a/source/ps/Replay.cpp +++ b/source/ps/Replay.cpp @@ -42,6 +42,12 @@ #include #include +/** + * Number of turns between two saved profiler snapshots. + * Keep in sync with source/tools/replayprofile/graph.js + */ +static const int PROFILE_TURN_INTERVAL = 20; + CReplayLogger::CReplayLogger(const ScriptInterface& scriptInterface) : m_ScriptInterface(scriptInterface), m_Stream(NULL) { @@ -234,13 +240,11 @@ void CReplayPlayer::Replay(bool serializationtest, int rejointestturn, bool oosl g_Profiler.Frame(); - if (turn % 20 == 0) + if (turn % PROFILE_TURN_INTERVAL == 0) g_ProfileViewer.SaveToFile(); } else - { debug_printf("Unrecognised replay token %s\n", type.c_str()); - } } } diff --git a/source/tools/replayprofile/extract.pl b/source/tools/replayprofile/extract.pl index 6b19f7eb90..ac3c6a7d61 100644 --- a/source/tools/replayprofile/extract.pl +++ b/source/tools/replayprofile/extract.pl @@ -33,7 +33,7 @@ while (<$f>) { } } -print "var graphData = [\n"; +print "[\n"; my $n = 0; for my $k (sort keys %s) { print ",\n" if $n++; @@ -44,4 +44,4 @@ for my $k (sort keys %s) { } print "]}"; } -print "\n];\n"; +print "\n]\n"; diff --git a/source/tools/replayprofile/graph.html b/source/tools/replayprofile/graph.html index 594b3a6aa6..5b6cfbb536 100644 --- a/source/tools/replayprofile/graph.html +++ b/source/tools/replayprofile/graph.html @@ -1,61 +1,28 @@ - - - - - - -
-

x axis: turn number / 20 -

y axis (left): time per frame / msecs -

y axis (right): bytes -

Drag to pan, mouse-wheel to zoom + + + + 0 A.D. Simulation Profiling + + + + + + + +

+ +
+
+
+
+
+
+ + diff --git a/source/tools/replayprofile/graph.js b/source/tools/replayprofile/graph.js new file mode 100644 index 0000000000..8d1de0535b --- /dev/null +++ b/source/tools/replayprofile/graph.js @@ -0,0 +1,141 @@ +/** + * Contents of data.json + */ +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 == "number of GCs"; + } + } +}; + +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))) + 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 + })); +} + +$.getJSON("data.json", function(data) { + replayData = data; + showReplayData(); +});