StatCollector.cpp 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  2. // SPDX-License-Identifier: MIT
  3. #include <Jolt.h>
  4. #include <Core/StatCollector.h>
  5. #include <Core/Color.h>
  6. #include <Core/StringTools.h>
  7. #include <fstream>
  8. #ifdef JPH_STAT_COLLECTOR
  9. namespace JPH {
  10. StatCollector StatCollector::sInstance;
  11. string StatCollector::Variant::ToString() const
  12. {
  13. switch (mType)
  14. {
  15. case EType::Float:
  16. return ConvertToString(mFloat);
  17. case EType::Int:
  18. return ConvertToString(mInt);
  19. case EType::Bool:
  20. return ConvertToString(mBool);
  21. case EType::Undefined:
  22. default:
  23. JPH_ASSERT(false);
  24. return "";
  25. }
  26. }
  27. void StatCollector::SetNextFrame()
  28. {
  29. lock_guard lock(mMutex);
  30. if (mIsCapturing)
  31. mCurrentFrame = &mFrames[mCurrentFrameNumber++];
  32. }
  33. void StatCollector::ResetInternal()
  34. {
  35. mCurrentFrameNumber = 0;
  36. mCurrentFrame = nullptr;
  37. mFrames.clear();
  38. mKeys.clear();
  39. mNextKey = 0;
  40. }
  41. void StatCollector::Reset()
  42. {
  43. lock_guard lock(mMutex);
  44. ResetInternal();
  45. }
  46. void StatCollector::StartCapture()
  47. {
  48. lock_guard lock(mMutex);
  49. ResetInternal();
  50. mIsCapturing = true;
  51. }
  52. void StatCollector::AddItem(const string &inName, const Variant &inValue)
  53. {
  54. lock_guard lock(mMutex);
  55. JPH_ASSERT(mCurrentFrame != nullptr, "Don't forget to call SetFrame(...)");
  56. // Determine key for inName
  57. pair<KeyIDMap::iterator, bool> p = mKeys.insert(KeyIDMap::value_type(inName, mNextKey));
  58. // Increment key inName was new
  59. if (p.second)
  60. ++mNextKey;
  61. // Take key from map
  62. int key = p.first->second;
  63. // Store value
  64. (*mCurrentFrame)[key] = inValue;
  65. }
  66. void StatCollector::AddItem(const string &inName, Vec3Arg inValue)
  67. {
  68. AddItem(inName + ".X", inValue.GetX());
  69. AddItem(inName + ".Y", inValue.GetY());
  70. AddItem(inName + ".Z", inValue.GetZ());
  71. }
  72. void StatCollector::AddItem(const string &inName, QuatArg inValue)
  73. {
  74. Vec3 axis;
  75. float angle;
  76. inValue.GetAxisAngle(axis, angle);
  77. AddItem(inName + ".Axis", axis);
  78. AddItem(inName + ".Angle", RadiansToDegrees(angle));
  79. }
  80. struct StatTreeNode
  81. {
  82. using Children = map<string, StatTreeNode>;
  83. int mIndex = -1;
  84. Children mChildren;
  85. };
  86. static void sWriteStatTree(ofstream &ioStream, const StatTreeNode &inNode)
  87. {
  88. bool first = true;
  89. for (const StatTreeNode::Children::value_type &c : inNode.mChildren)
  90. {
  91. // Write comma if this is not the first line
  92. if (!first)
  93. ioStream << ",";
  94. first = false;
  95. // Write title
  96. ioStream << "{title:\"" + c.first + "\"";
  97. // Write key
  98. ioStream << ",key:\"" + ConvertToString(c.second.mIndex) + "\"";
  99. // Write children
  100. if (!c.second.mChildren.empty())
  101. {
  102. ioStream << ",children:[";
  103. sWriteStatTree(ioStream, c.second);
  104. ioStream << "]";
  105. }
  106. ioStream << "}";
  107. }
  108. }
  109. void StatCollector::StopCapture(const char *inFileName)
  110. {
  111. lock_guard lock(mMutex);
  112. // Stop capturing
  113. mIsCapturing = false;
  114. // Open file
  115. ofstream f;
  116. f.open(inFileName, ofstream::out | ofstream::trunc);
  117. if (!f.is_open())
  118. return;
  119. // Start html file
  120. f << R"(<!DOCTYPE html>
  121. <html>
  122. <head>
  123. <title>Stats</title>
  124. <script type="text/javascript" src="WebIncludes/jquery-3.2.1.min.js"></script>
  125. <script src="WebIncludes/dygraph.min.js"></script>
  126. <link rel="stylesheet" href="WebIncludes/dygraph.min.css"/>
  127. <script src="WebIncludes/jquery.fancytree-all-deps.min.js"></script>
  128. <link rel="stylesheet" href="WebIncludes/ui.fancytree.min.css"/>
  129. <style>
  130. #labelsdiv>span { display: block; }
  131. ul.fancytree-container { border: 0px; }
  132. </style>
  133. </head>
  134. <body>
  135. <div style="width: 100%; height: 50vh;">
  136. <div id="graphdiv" style="float: left; width:60%; height: 50vh; overflow: hidden;"></div>
  137. <div id="labelsdiv" style="float: right; width:39%; height: 50vh; overflow-x: hidden; overflow-y: scroll;"></div>
  138. </div>
  139. <p>
  140. <button id="btnSelectAll">Select All</button> &nbsp;
  141. <button id="btnDeselectAll">Deselect All</button> &nbsp;
  142. <input id="search" placeholder="Filter..." autocomplete="off">
  143. <button id="btnResetSearch">&times;</button>
  144. <span id="matches"></span>
  145. </p>
  146. <div style="width:100%; height: 40vh; overflow-x: hidden; overflow-y: scroll;">
  147. <div id="tree" style="width:100%;">
  148. </div>
  149. </div>
  150. <script type="text/javascript">
  151. "use strict";
  152. )";
  153. // Write all data points
  154. f << "var point_data = [";
  155. bool first = true;
  156. for (const FrameMap::value_type &entry : mFrames)
  157. {
  158. // Don't write empty samples
  159. if (entry.second.empty())
  160. continue;
  161. // Write comma at start of each next line
  162. if (!first)
  163. f << ",";
  164. first = false;
  165. // Write frame number
  166. f << "[" << entry.first;
  167. // Write all columns
  168. for (const KeyIDMap::value_type &key : mKeys)
  169. {
  170. KeyValueMap::const_iterator v = entry.second.find(key.second);
  171. if (v == entry.second.end())
  172. f << ",NaN";
  173. else
  174. f << "," << v->second.ToString();
  175. }
  176. f << "]\n";
  177. }
  178. f << "];\n";
  179. // Write labels
  180. f << "var labels_data = [\"Frame\"";
  181. for (const KeyIDMap::value_type &key : mKeys)
  182. f << ",\"" << key.first << "\"";
  183. f << "];\n";
  184. // Write colors
  185. f << "var colors_data = ['rgb(0,0,0)'";
  186. for (int i = 0; i < (int)mKeys.size() - 1; ++i)
  187. {
  188. Color c = Color::sGetDistinctColor(i);
  189. f << ",'rgb(" << (int)c.r << "," << (int)c.g << "," << (int)c.b << ")'";
  190. }
  191. f << "];\n";
  192. // Calculate tree
  193. StatTreeNode root;
  194. int index = 0;
  195. for (const KeyIDMap::value_type &key : mKeys)
  196. {
  197. // Split parts of key
  198. vector<string> parts;
  199. StringToVector(key.first, parts, ".");
  200. // Create tree nodes
  201. StatTreeNode *cur = &root;
  202. for (string p : parts)
  203. cur = &cur->mChildren[p];
  204. // Set index on leaf node
  205. cur->mIndex = index;
  206. ++index;
  207. }
  208. // Output tree
  209. f << "var tree_data = [";
  210. sWriteStatTree(f, root);
  211. f << "];\n";
  212. // Write main script
  213. f << R"-(
  214. var graph = new Dygraph(
  215. document.getElementById("graphdiv"),
  216. point_data,
  217. {
  218. labels: labels_data,
  219. colors: colors_data,
  220. labelsDiv: labelsdiv,
  221. hideOverlayOnMouseOut: false,
  222. showRangeSelector: true,
  223. xlabel: "Frame",
  224. ylabel: "Value"
  225. });
  226. function sync_enabled_series(tree, graph) {
  227. var is_visible = [];
  228. for (var i = 0; i < graph.numColumns() - 1; ++i)
  229. is_visible[i] = false;
  230. var selected_nodes = tree.getSelectedNodes(false);
  231. for (var i = 0; i < selected_nodes.length; ++i)
  232. {
  233. var key = parseInt(selected_nodes[i].key);
  234. if (key >= 0)
  235. is_visible[key] = true;
  236. }
  237. graph.setVisibility(is_visible);
  238. };
  239. $(function() {
  240. $("#tree").fancytree({
  241. extensions: ["filter"],
  242. quicksearch: true,
  243. source: tree_data,
  244. icon: false,
  245. checkbox: true,
  246. selectMode: 3,
  247. keyboard: true,
  248. quicksearch: true,
  249. filter: {
  250. autoExpand: true,
  251. mode: "hide"
  252. },
  253. select: function(event, data) {
  254. sync_enabled_series(tree, graph);
  255. }
  256. });
  257. var tree = $("#tree").fancytree("getTree");
  258. var no_events = { noEvents: true };
  259. tree.enableUpdate(false);
  260. tree.visit(function(node) {
  261. node.setExpanded(true);
  262. node.setSelected(true, no_events);
  263. });
  264. tree.enableUpdate(true);
  265. $("#search").keyup(function(e) {
  266. var match = $(this).val();
  267. if (e && e.which === $.ui.keyCode.ESCAPE || $.trim(match) === "") {
  268. $("#btnResetSearch").click();
  269. return;
  270. }
  271. var n = tree.filterBranches.call(tree, match, { autoExpand: true });
  272. $("#btnResetSearch").attr("disabled", false);
  273. $("#matches").text("(" + n + " matches)");
  274. }).focus();
  275. $("#btnResetSearch").click(function(e) {
  276. $("#search").val("");
  277. $("#btnResetSearch").attr("disabled", true);
  278. $("#matches").text("");
  279. tree.clearFilter();
  280. }).attr("disabled", true);
  281. $("#btnDeselectAll").click(function() {
  282. tree.enableUpdate(false);
  283. tree.visit(function(node) {
  284. if (node.isMatched())
  285. node.setSelected(false, no_events);
  286. });
  287. tree.enableUpdate(true);
  288. sync_enabled_series(tree, graph);
  289. return false;
  290. });
  291. $("#btnSelectAll").click(function() {
  292. tree.enableUpdate(false);
  293. tree.visit(function(node) {
  294. if (node.isMatched())
  295. node.setSelected(true, no_events);
  296. });
  297. tree.enableUpdate(true);
  298. sync_enabled_series(tree, graph);
  299. return false;
  300. });
  301. });
  302. </script>
  303. </body>
  304. </html>)-";
  305. // Remove all collected data
  306. ResetInternal();
  307. }
  308. } // JPH
  309. #endif // JPH_STAT_COLLECTOR