resultsCtrl.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. var app = angular.module('Results', []);
  2. app.controller('ResultsCtrl', function ($scope) {
  3. // Grab results from nodeJS served page
  4. $scope.phantomasResults = window._phantomas_results;
  5. $scope.phantomasMetadata = window._phantomas_metadata.metrics;
  6. $scope.view = 'summary';
  7. if ($scope.phantomasResults.metrics && $scope.phantomasResults.offenders && $scope.phantomasResults.offenders.javascriptExecutionTree) {
  8. // Get the execution tree from the offenders
  9. $scope.javascript = JSON.parse($scope.phantomasResults.offenders.javascriptExecutionTree);
  10. initSummaryView();
  11. initExecutionView();
  12. initMetricsView();
  13. }
  14. $scope.setView = function(viewName) {
  15. $scope.view = viewName;
  16. };
  17. $scope.onNodeDetailsClick = function(node) {
  18. var isOpen = node.data.showDetails;
  19. if (!isOpen) {
  20. // Close all other nodes
  21. $scope.javascript.children.forEach(function(currentNode) {
  22. currentNode.data.showDetails = false;
  23. });
  24. // Parse the backtrace
  25. if (!node.data.parsedBacktrace) {
  26. node.data.parsedBacktrace = parseBacktrace(node.data.backtrace);
  27. }
  28. }
  29. node.data.showDetails = !isOpen;
  30. };
  31. function initSummaryView() {
  32. // Read the main elements of the tree and sum the total time
  33. $scope.totalJSTime = 0;
  34. treeRunner($scope.javascript, function(node) {
  35. if (node.data.time) {
  36. $scope.totalJSTime += node.data.time;
  37. }
  38. if (node.data.type !== 'main') {
  39. // Don't check the children
  40. return false;
  41. }
  42. });
  43. // Read all the duplicated queries and calculate a more appropriated score
  44. $scope.duplicatedQueriesCountAll = 0;
  45. if ($scope.phantomasResults.offenders.DOMqueriesDuplicated) {
  46. var regex = /\): *(\d+) queries$/;
  47. $scope.phantomasResults.offenders.DOMqueriesDuplicated.forEach(function(query) {
  48. var regexResult = regex.exec(query);
  49. if (regexResult) {
  50. $scope.duplicatedQueriesCountAll += parseInt(regexResult[1], 10) - 1;
  51. }
  52. });
  53. }
  54. $scope.notations = {
  55. domComplexity: 'A',
  56. domManipulations: 'A',
  57. duplicatedDomQueries: 'A',
  58. eventsBound: 'A',
  59. badPractices: 'A',
  60. scripts: 'A',
  61. jQueryLoading: 'A'
  62. };
  63. var domComplexityScore = $scope.phantomasResults.metrics.DOMelementsCount +
  64. Math.pow($scope.phantomasResults.metrics.DOMelementMaxDepth, 2) +
  65. $scope.phantomasResults.metrics.iframesCount * 50;
  66. if (domComplexityScore > 1000) {
  67. $scope.notations.domComplexity = 'B';
  68. }
  69. if (domComplexityScore > 1500) {
  70. $scope.notations.domComplexity = 'C';
  71. }
  72. if (domComplexityScore > 2000) {
  73. $scope.notations.domComplexity = 'D';
  74. }
  75. if (domComplexityScore > 3000) {
  76. $scope.notations.domComplexity = 'E';
  77. }
  78. if (domComplexityScore > 4000) {
  79. $scope.notations.domComplexity = 'F';
  80. }
  81. var domManipulationsScore = $scope.phantomasResults.metrics.DOMinserts +
  82. $scope.phantomasResults.metrics.DOMqueries * 0.5 +
  83. $scope.totalJSTime;
  84. if (domManipulationsScore > 100) {
  85. $scope.notations.domManipulations = 'B';
  86. }
  87. if (domManipulationsScore > 200) {
  88. $scope.notations.domManipulations = 'C';
  89. }
  90. if (domManipulationsScore > 300) {
  91. $scope.notations.domManipulations = 'D';
  92. }
  93. if (domManipulationsScore > 500) {
  94. $scope.notations.domManipulations = 'E';
  95. }
  96. if (domManipulationsScore > 800) {
  97. $scope.notations.domManipulations = 'F';
  98. }
  99. var duplicatedDomQueries = $scope.duplicatedQueriesCountAll;
  100. if (duplicatedDomQueries > 10) {
  101. $scope.notations.duplicatedDomQueries = 'B';
  102. }
  103. if (duplicatedDomQueries > 50) {
  104. $scope.notations.duplicatedDomQueries = 'C';
  105. }
  106. if (duplicatedDomQueries > 100) {
  107. $scope.notations.duplicatedDomQueries = 'D';
  108. }
  109. if (duplicatedDomQueries > 200) {
  110. $scope.notations.duplicatedDomQueries = 'E';
  111. }
  112. if (duplicatedDomQueries > 500) {
  113. $scope.notations.duplicatedDomQueries = 'F';
  114. }
  115. var eventsBoundScore = $scope.phantomasResults.metrics.eventsBound;
  116. if (eventsBoundScore > 50) {
  117. $scope.notations.eventsBound = 'B';
  118. }
  119. if (eventsBoundScore > 100) {
  120. $scope.notations.eventsBound = 'C';
  121. }
  122. if (eventsBoundScore > 200) {
  123. $scope.notations.eventsBound = 'D';
  124. }
  125. if (eventsBoundScore > 500) {
  126. $scope.notations.eventsBound = 'E';
  127. }
  128. if (eventsBoundScore > 1000) {
  129. $scope.notations.eventsBound = 'F';
  130. }
  131. var badPracticesScore = $scope.phantomasResults.metrics.documentWriteCalls * 3 +
  132. $scope.phantomasResults.metrics.evalCalls * 3 +
  133. $scope.phantomasResults.metrics.jsErrors * 10 +
  134. $scope.phantomasResults.metrics.consoleMessages;
  135. if (badPracticesScore > 5) {
  136. $scope.notations.badPractices = 'B';
  137. }
  138. if (badPracticesScore > 10) {
  139. $scope.notations.badPractices = 'C';
  140. }
  141. if (badPracticesScore > 15) {
  142. $scope.notations.badPractices = 'D';
  143. }
  144. if (badPracticesScore > 25) {
  145. $scope.notations.badPractices = 'E';
  146. }
  147. if (badPracticesScore > 40) {
  148. $scope.notations.badPractices = 'F';
  149. }
  150. var scriptsScore = $scope.phantomasResults.metrics.jsCount;
  151. if (scriptsScore > 4) {
  152. $scope.notations.scripts = 'B';
  153. }
  154. if (scriptsScore > 8) {
  155. $scope.notations.scripts = 'C';
  156. }
  157. if (scriptsScore > 12) {
  158. $scope.notations.scripts = 'D';
  159. }
  160. if (scriptsScore > 16) {
  161. $scope.notations.scripts = 'E';
  162. }
  163. if (scriptsScore > 20) {
  164. $scope.notations.scripts = 'F';
  165. }
  166. $scope.notations.jQueryLoading = 'NA';
  167. if ($scope.phantomasResults.metrics.jQueryDifferentVersions > 1) {
  168. $scope.notations.jQueryLoading = 'F';
  169. } else if ($scope.phantomasResults.metrics.jQueryVersion) {
  170. if ($scope.phantomasResults.metrics.jQueryVersion.indexOf('1.10.') === 0 ||
  171. $scope.phantomasResults.metrics.jQueryVersion.indexOf('1.11.') === 0 ||
  172. $scope.phantomasResults.metrics.jQueryVersion.indexOf('1.12.') === 0 ||
  173. $scope.phantomasResults.metrics.jQueryVersion.indexOf('2.0.') === 0 ||
  174. $scope.phantomasResults.metrics.jQueryVersion.indexOf('2.1.') === 0 ||
  175. $scope.phantomasResults.metrics.jQueryVersion.indexOf('2.2.') === 0) {
  176. $scope.notations.jQueryLoading = 'A';
  177. } else if ($scope.phantomasResults.metrics.jQueryVersion.indexOf('1.8.') === 0 ||
  178. $scope.phantomasResults.metrics.jQueryVersion.indexOf('1.9.') === 0) {
  179. $scope.notations.jQueryLoading = 'B';
  180. } else if ($scope.phantomasResults.metrics.jQueryVersion.indexOf('1.6.') === 0 ||
  181. $scope.phantomasResults.metrics.jQueryVersion.indexOf('1.7.') === 0) {
  182. $scope.notations.jQueryLoading = 'C';
  183. } else if ($scope.phantomasResults.metrics.jQueryVersion.indexOf('1.4.') === 0 ||
  184. $scope.phantomasResults.metrics.jQueryVersion.indexOf('1.5.') === 0) {
  185. $scope.notations.jQueryLoading = 'D';
  186. } else if ($scope.phantomasResults.metrics.jQueryVersion.indexOf('1.2.') === 0 ||
  187. $scope.phantomasResults.metrics.jQueryVersion.indexOf('1.3.') === 0) {
  188. $scope.notations.jQueryLoading = 'E';
  189. }
  190. }
  191. }
  192. function initExecutionView() {
  193. $scope.slowRequestsOn = false;
  194. $scope.slowRequestsLimit = 5;
  195. // Now read the tree and display it on a timeline
  196. // Split the timeline into 200 intervals
  197. var numberOfIntervals = 200;
  198. var lastEvent = $scope.javascript.children[$scope.javascript.children.length - 1];
  199. var endTime = lastEvent.data.timestamp + (lastEvent.data.time || 0);
  200. $scope.timelineIntervalDuration = endTime / numberOfIntervals;
  201. // Pre-filled array of 100 elements
  202. $scope.timeline = Array.apply(null, new Array(numberOfIntervals)).map(Number.prototype.valueOf,0);
  203. treeRunner($scope.javascript, function(node) {
  204. if (node.data.time) {
  205. // If a node is between two intervals, split it. That's the meaning of the following dirty algorithm.
  206. var startInterval = Math.floor(node.data.timestamp / $scope.timelineIntervalDuration);
  207. var endInterval = Math.floor((node.data.timestamp + node.data.time) / $scope.timelineIntervalDuration);
  208. if (startInterval === endInterval) {
  209. $scope.timeline[startInterval] += node.data.time;
  210. } else {
  211. var timeToDispatch = node.data.time;
  212. var startIntervalPart = ((startInterval + 1) * $scope.timelineIntervalDuration) - node.data.timestamp;
  213. $scope.timeline[startInterval] += startIntervalPart;
  214. timeToDispatch -= startIntervalPart;
  215. var currentInterval = startInterval;
  216. while(currentInterval < endInterval && currentInterval + 1 < numberOfIntervals) {
  217. currentInterval ++;
  218. var currentIntervalPart = Math.min(timeToDispatch, $scope.timelineIntervalDuration);
  219. $scope.timeline[currentInterval] = currentIntervalPart;
  220. timeToDispatch -= currentIntervalPart;
  221. }
  222. }
  223. }
  224. if (node.data.type !== 'main') {
  225. // Don't check the children
  226. return false;
  227. }
  228. });
  229. $scope.timelineMax = Math.max.apply(Math, $scope.timeline);
  230. }
  231. function initMetricsView() {
  232. // Get the Phantomas modules from metadata
  233. $scope.metricsModule = {};
  234. for (var metricName in $scope.phantomasMetadata) {
  235. var metric = $scope.phantomasMetadata[metricName];
  236. if (!$scope.metricsModule[metric.module]) {
  237. $scope.metricsModule[metric.module] = {};
  238. }
  239. $scope.metricsModule[metric.module][metricName] = metric;
  240. }
  241. }
  242. function parseBacktrace(str) {
  243. if (!str) {
  244. return null;
  245. }
  246. var out = [];
  247. var splited = str.split(' / ');
  248. splited.forEach(function(trace) {
  249. var result = /^(\S*)\s?\(?(https?:\/\/\S+):(\d+)\)?$/g.exec(trace);
  250. if (result && result[2].length > 0) {
  251. var filePath = result[2];
  252. var chunks = filePath.split('/');
  253. var fileName = chunks[chunks.length - 1];
  254. out.push({
  255. fnName: result[1],
  256. fileName: fileName,
  257. filePath: filePath,
  258. line: result[3]
  259. });
  260. }
  261. });
  262. return out;
  263. }
  264. // Goes on every node of the tree and calls the function fn. If fn returns false on a node, its children won't be checked.
  265. function treeRunner(node, fn) {
  266. if (fn(node) !== false && node.children) {
  267. node.children.forEach(function(child) {
  268. treeRunner(child, fn);
  269. });
  270. }
  271. }
  272. });