CPUProfileView.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935
  1. /*
  2. * Copyright (C) 2008 Apple Inc. All Rights Reserved.
  3. *
  4. * Redistribution and use in source and binary forms, with or without
  5. * modification, are permitted provided that the following conditions
  6. * are met:
  7. * 1. Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * 2. Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. *
  13. * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
  14. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  15. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  16. * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
  17. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  18. * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  19. * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  20. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
  21. * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  22. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  23. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  24. */
  25. /**
  26. * @constructor
  27. * @extends {WebInspector.View}
  28. * @param {WebInspector.CPUProfileHeader} profileHeader
  29. */
  30. WebInspector.CPUProfileView = function(profileHeader)
  31. {
  32. WebInspector.View.call(this);
  33. this.element.addStyleClass("profile-view");
  34. this.showSelfTimeAsPercent = WebInspector.settings.createSetting("cpuProfilerShowSelfTimeAsPercent", true);
  35. this.showTotalTimeAsPercent = WebInspector.settings.createSetting("cpuProfilerShowTotalTimeAsPercent", true);
  36. this.showAverageTimeAsPercent = WebInspector.settings.createSetting("cpuProfilerShowAverageTimeAsPercent", true);
  37. this._viewType = WebInspector.settings.createSetting("cpuProfilerView", WebInspector.CPUProfileView._TypeHeavy);
  38. var columns = [];
  39. columns.push({id: "self", title: WebInspector.UIString("Self"), width: "72px", sort: WebInspector.DataGrid.Order.Descending, sortable: true});
  40. columns.push({id: "total", title: WebInspector.UIString("Total"), width: "72px", sortable: true});
  41. columns.push({id: "function", title: WebInspector.UIString("Function"), disclosure: true, sortable: true});
  42. this.dataGrid = new WebInspector.DataGrid(columns);
  43. this.dataGrid.addEventListener(WebInspector.DataGrid.Events.SortingChanged, this._sortProfile, this);
  44. this.dataGrid.element.addEventListener("mousedown", this._mouseDownInDataGrid.bind(this), true);
  45. this.dataGrid.show(this.element);
  46. this.viewSelectComboBox = new WebInspector.StatusBarComboBox(this._changeView.bind(this));
  47. var options = {};
  48. options[WebInspector.CPUProfileView._TypeFlame] = this.viewSelectComboBox.createOption(WebInspector.UIString("Flame Chart"), "", WebInspector.CPUProfileView._TypeFlame);
  49. options[WebInspector.CPUProfileView._TypeHeavy] = this.viewSelectComboBox.createOption(WebInspector.UIString("Heavy (Bottom Up)"), "", WebInspector.CPUProfileView._TypeHeavy);
  50. options[WebInspector.CPUProfileView._TypeTree] = this.viewSelectComboBox.createOption(WebInspector.UIString("Tree (Top Down)"), "", WebInspector.CPUProfileView._TypeTree);
  51. var optionName = this._viewType.get() || WebInspector.CPUProfileView._TypeFlame;
  52. var option = options[optionName] || options[WebInspector.CPUProfileView._TypeFlame];
  53. this.viewSelectComboBox.select(option);
  54. this._statusBarButtonsElement = document.createElement("span");
  55. this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item");
  56. this.percentButton.addEventListener("click", this._percentClicked, this);
  57. this._statusBarButtonsElement.appendChild(this.percentButton.element);
  58. this.focusButton = new WebInspector.StatusBarButton(WebInspector.UIString("Focus selected function."), "focus-profile-node-status-bar-item");
  59. this.focusButton.setEnabled(false);
  60. this.focusButton.addEventListener("click", this._focusClicked, this);
  61. this._statusBarButtonsElement.appendChild(this.focusButton.element);
  62. this.excludeButton = new WebInspector.StatusBarButton(WebInspector.UIString("Exclude selected function."), "exclude-profile-node-status-bar-item");
  63. this.excludeButton.setEnabled(false);
  64. this.excludeButton.addEventListener("click", this._excludeClicked, this);
  65. this._statusBarButtonsElement.appendChild(this.excludeButton.element);
  66. this.resetButton = new WebInspector.StatusBarButton(WebInspector.UIString("Restore all functions."), "reset-profile-status-bar-item");
  67. this.resetButton.visible = false;
  68. this.resetButton.addEventListener("click", this._resetClicked, this);
  69. this._statusBarButtonsElement.appendChild(this.resetButton.element);
  70. this.profileHead = /** @type {?ProfilerAgent.CPUProfileNode} */ (null);
  71. this.profile = profileHeader;
  72. this._linkifier = new WebInspector.Linkifier(new WebInspector.Linkifier.DefaultFormatter(30));
  73. if (this.profile._profile) // If the profile has been loaded from file then use it.
  74. this._processProfileData(this.profile._profile);
  75. else
  76. ProfilerAgent.getCPUProfile(this.profile.uid, this._getCPUProfileCallback.bind(this));
  77. }
  78. WebInspector.CPUProfileView._TypeFlame = "Flame";
  79. WebInspector.CPUProfileView._TypeTree = "Tree";
  80. WebInspector.CPUProfileView._TypeHeavy = "Heavy";
  81. WebInspector.CPUProfileView.prototype = {
  82. /**
  83. * @param {!number} timeLeft
  84. * @param {!number} timeRight
  85. */
  86. selectRange: function(timeLeft, timeRight)
  87. {
  88. if (!this._flameChart)
  89. return;
  90. this._flameChart.selectRange(timeLeft, timeRight);
  91. },
  92. _revealProfilerNode: function(event)
  93. {
  94. var current = this.profileDataGridTree.children[0];
  95. while (current && current.profileNode !== event.data)
  96. current = current.traverseNextNode(false, null, false);
  97. if (current)
  98. current.revealAndSelect();
  99. },
  100. /**
  101. * @param {?Protocol.Error} error
  102. * @param {ProfilerAgent.CPUProfile} profile
  103. */
  104. _getCPUProfileCallback: function(error, profile)
  105. {
  106. if (error)
  107. return;
  108. if (!profile.head) {
  109. // Profiling was tentatively terminated with the "Clear all profiles." button.
  110. return;
  111. }
  112. this._processProfileData(profile);
  113. },
  114. _processProfileData: function(profile)
  115. {
  116. this.profileHead = profile.head;
  117. this.samples = profile.samples;
  118. this._calculateTimes(profile);
  119. this._assignParentsInProfile();
  120. if (this.samples)
  121. this._buildIdToNodeMap();
  122. this._changeView();
  123. this._updatePercentButton();
  124. if (this._flameChart)
  125. this._flameChart.update();
  126. },
  127. get statusBarItems()
  128. {
  129. return [this.viewSelectComboBox.element, this._statusBarButtonsElement];
  130. },
  131. /**
  132. * @return {!WebInspector.ProfileDataGridTree}
  133. */
  134. _getBottomUpProfileDataGridTree: function()
  135. {
  136. if (!this._bottomUpProfileDataGridTree)
  137. this._bottomUpProfileDataGridTree = new WebInspector.BottomUpProfileDataGridTree(this, this.profileHead);
  138. return this._bottomUpProfileDataGridTree;
  139. },
  140. /**
  141. * @return {!WebInspector.ProfileDataGridTree}
  142. */
  143. _getTopDownProfileDataGridTree: function()
  144. {
  145. if (!this._topDownProfileDataGridTree)
  146. this._topDownProfileDataGridTree = new WebInspector.TopDownProfileDataGridTree(this, this.profileHead);
  147. return this._topDownProfileDataGridTree;
  148. },
  149. willHide: function()
  150. {
  151. this._currentSearchResultIndex = -1;
  152. },
  153. refresh: function()
  154. {
  155. var selectedProfileNode = this.dataGrid.selectedNode ? this.dataGrid.selectedNode.profileNode : null;
  156. this.dataGrid.rootNode().removeChildren();
  157. var children = this.profileDataGridTree.children;
  158. var count = children.length;
  159. for (var index = 0; index < count; ++index)
  160. this.dataGrid.rootNode().appendChild(children[index]);
  161. if (selectedProfileNode)
  162. selectedProfileNode.selected = true;
  163. },
  164. refreshVisibleData: function()
  165. {
  166. var child = this.dataGrid.rootNode().children[0];
  167. while (child) {
  168. child.refresh();
  169. child = child.traverseNextNode(false, null, true);
  170. }
  171. },
  172. refreshShowAsPercents: function()
  173. {
  174. this._updatePercentButton();
  175. this.refreshVisibleData();
  176. },
  177. searchCanceled: function()
  178. {
  179. if (this._searchResults) {
  180. for (var i = 0; i < this._searchResults.length; ++i) {
  181. var profileNode = this._searchResults[i].profileNode;
  182. delete profileNode._searchMatchedSelfColumn;
  183. delete profileNode._searchMatchedTotalColumn;
  184. delete profileNode._searchMatchedFunctionColumn;
  185. profileNode.refresh();
  186. }
  187. }
  188. delete this._searchFinishedCallback;
  189. this._currentSearchResultIndex = -1;
  190. this._searchResults = [];
  191. },
  192. performSearch: function(query, finishedCallback)
  193. {
  194. // Call searchCanceled since it will reset everything we need before doing a new search.
  195. this.searchCanceled();
  196. query = query.trim();
  197. if (!query.length)
  198. return;
  199. this._searchFinishedCallback = finishedCallback;
  200. var greaterThan = (query.startsWith(">"));
  201. var lessThan = (query.startsWith("<"));
  202. var equalTo = (query.startsWith("=") || ((greaterThan || lessThan) && query.indexOf("=") === 1));
  203. var percentUnits = (query.lastIndexOf("%") === (query.length - 1));
  204. var millisecondsUnits = (query.length > 2 && query.lastIndexOf("ms") === (query.length - 2));
  205. var secondsUnits = (!millisecondsUnits && query.lastIndexOf("s") === (query.length - 1));
  206. var queryNumber = parseFloat(query);
  207. if (greaterThan || lessThan || equalTo) {
  208. if (equalTo && (greaterThan || lessThan))
  209. queryNumber = parseFloat(query.substring(2));
  210. else
  211. queryNumber = parseFloat(query.substring(1));
  212. }
  213. var queryNumberMilliseconds = (secondsUnits ? (queryNumber * 1000) : queryNumber);
  214. // Make equalTo implicitly true if it wasn't specified there is no other operator.
  215. if (!isNaN(queryNumber) && !(greaterThan || lessThan))
  216. equalTo = true;
  217. var matcher = createPlainTextSearchRegex(query, "i");
  218. function matchesQuery(/*ProfileDataGridNode*/ profileDataGridNode)
  219. {
  220. delete profileDataGridNode._searchMatchedSelfColumn;
  221. delete profileDataGridNode._searchMatchedTotalColumn;
  222. delete profileDataGridNode._searchMatchedFunctionColumn;
  223. if (percentUnits) {
  224. if (lessThan) {
  225. if (profileDataGridNode.selfPercent < queryNumber)
  226. profileDataGridNode._searchMatchedSelfColumn = true;
  227. if (profileDataGridNode.totalPercent < queryNumber)
  228. profileDataGridNode._searchMatchedTotalColumn = true;
  229. } else if (greaterThan) {
  230. if (profileDataGridNode.selfPercent > queryNumber)
  231. profileDataGridNode._searchMatchedSelfColumn = true;
  232. if (profileDataGridNode.totalPercent > queryNumber)
  233. profileDataGridNode._searchMatchedTotalColumn = true;
  234. }
  235. if (equalTo) {
  236. if (profileDataGridNode.selfPercent == queryNumber)
  237. profileDataGridNode._searchMatchedSelfColumn = true;
  238. if (profileDataGridNode.totalPercent == queryNumber)
  239. profileDataGridNode._searchMatchedTotalColumn = true;
  240. }
  241. } else if (millisecondsUnits || secondsUnits) {
  242. if (lessThan) {
  243. if (profileDataGridNode.selfTime < queryNumberMilliseconds)
  244. profileDataGridNode._searchMatchedSelfColumn = true;
  245. if (profileDataGridNode.totalTime < queryNumberMilliseconds)
  246. profileDataGridNode._searchMatchedTotalColumn = true;
  247. } else if (greaterThan) {
  248. if (profileDataGridNode.selfTime > queryNumberMilliseconds)
  249. profileDataGridNode._searchMatchedSelfColumn = true;
  250. if (profileDataGridNode.totalTime > queryNumberMilliseconds)
  251. profileDataGridNode._searchMatchedTotalColumn = true;
  252. }
  253. if (equalTo) {
  254. if (profileDataGridNode.selfTime == queryNumberMilliseconds)
  255. profileDataGridNode._searchMatchedSelfColumn = true;
  256. if (profileDataGridNode.totalTime == queryNumberMilliseconds)
  257. profileDataGridNode._searchMatchedTotalColumn = true;
  258. }
  259. }
  260. if (profileDataGridNode.functionName.match(matcher) || (profileDataGridNode.url && profileDataGridNode.url.match(matcher)))
  261. profileDataGridNode._searchMatchedFunctionColumn = true;
  262. if (profileDataGridNode._searchMatchedSelfColumn ||
  263. profileDataGridNode._searchMatchedTotalColumn ||
  264. profileDataGridNode._searchMatchedFunctionColumn)
  265. {
  266. profileDataGridNode.refresh();
  267. return true;
  268. }
  269. return false;
  270. }
  271. var current = this.profileDataGridTree.children[0];
  272. while (current) {
  273. if (matchesQuery(current)) {
  274. this._searchResults.push({ profileNode: current });
  275. }
  276. current = current.traverseNextNode(false, null, false);
  277. }
  278. finishedCallback(this, this._searchResults.length);
  279. },
  280. jumpToFirstSearchResult: function()
  281. {
  282. if (!this._searchResults || !this._searchResults.length)
  283. return;
  284. this._currentSearchResultIndex = 0;
  285. this._jumpToSearchResult(this._currentSearchResultIndex);
  286. },
  287. jumpToLastSearchResult: function()
  288. {
  289. if (!this._searchResults || !this._searchResults.length)
  290. return;
  291. this._currentSearchResultIndex = (this._searchResults.length - 1);
  292. this._jumpToSearchResult(this._currentSearchResultIndex);
  293. },
  294. jumpToNextSearchResult: function()
  295. {
  296. if (!this._searchResults || !this._searchResults.length)
  297. return;
  298. if (++this._currentSearchResultIndex >= this._searchResults.length)
  299. this._currentSearchResultIndex = 0;
  300. this._jumpToSearchResult(this._currentSearchResultIndex);
  301. },
  302. jumpToPreviousSearchResult: function()
  303. {
  304. if (!this._searchResults || !this._searchResults.length)
  305. return;
  306. if (--this._currentSearchResultIndex < 0)
  307. this._currentSearchResultIndex = (this._searchResults.length - 1);
  308. this._jumpToSearchResult(this._currentSearchResultIndex);
  309. },
  310. showingFirstSearchResult: function()
  311. {
  312. return (this._currentSearchResultIndex === 0);
  313. },
  314. showingLastSearchResult: function()
  315. {
  316. return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1));
  317. },
  318. _jumpToSearchResult: function(index)
  319. {
  320. var searchResult = this._searchResults[index];
  321. if (!searchResult)
  322. return;
  323. var profileNode = searchResult.profileNode;
  324. profileNode.revealAndSelect();
  325. },
  326. _ensureFlameChartCreated: function()
  327. {
  328. if (this._flameChart)
  329. return;
  330. this._flameChart = new WebInspector.FlameChart(this);
  331. this._flameChart.addEventListener(WebInspector.FlameChart.Events.SelectedNode, this._onSelectedNode.bind(this));
  332. },
  333. /**
  334. * @param {WebInspector.Event} event
  335. */
  336. _onSelectedNode: function(event)
  337. {
  338. var node = event.data;
  339. if (!node || !node.scriptId)
  340. return;
  341. var script = WebInspector.debuggerModel.scriptForId(node.scriptId)
  342. if (!script)
  343. return;
  344. var uiLocation = script.rawLocationToUILocation(node.lineNumber);
  345. if (!uiLocation)
  346. return;
  347. WebInspector.showPanel("scripts").showUILocation(uiLocation);
  348. },
  349. _changeView: function()
  350. {
  351. if (!this.profile)
  352. return;
  353. switch (this.viewSelectComboBox.selectedOption().value) {
  354. case WebInspector.CPUProfileView._TypeFlame:
  355. this._ensureFlameChartCreated();
  356. this.dataGrid.detach();
  357. this._flameChart.show(this.element);
  358. this._viewType.set(WebInspector.CPUProfileView._TypeFlame);
  359. this._statusBarButtonsElement.enableStyleClass("hidden", true);
  360. return;
  361. case WebInspector.CPUProfileView._TypeTree:
  362. this.profileDataGridTree = this._getTopDownProfileDataGridTree();
  363. this._sortProfile();
  364. this._viewType.set(WebInspector.CPUProfileView._TypeTree);
  365. break;
  366. case WebInspector.CPUProfileView._TypeHeavy:
  367. this.profileDataGridTree = this._getBottomUpProfileDataGridTree();
  368. this._sortProfile();
  369. this._viewType.set(WebInspector.CPUProfileView._TypeHeavy);
  370. break;
  371. }
  372. this._statusBarButtonsElement.enableStyleClass("hidden", false);
  373. if (this._flameChart)
  374. this._flameChart.detach();
  375. this.dataGrid.show(this.element);
  376. if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
  377. return;
  378. // The current search needs to be performed again. First negate out previous match
  379. // count by calling the search finished callback with a negative number of matches.
  380. // Then perform the search again the with same query and callback.
  381. this._searchFinishedCallback(this, -this._searchResults.length);
  382. this.performSearch(this.currentQuery, this._searchFinishedCallback);
  383. },
  384. _percentClicked: function(event)
  385. {
  386. var currentState = this.showSelfTimeAsPercent.get() && this.showTotalTimeAsPercent.get() && this.showAverageTimeAsPercent.get();
  387. this.showSelfTimeAsPercent.set(!currentState);
  388. this.showTotalTimeAsPercent.set(!currentState);
  389. this.showAverageTimeAsPercent.set(!currentState);
  390. this.refreshShowAsPercents();
  391. },
  392. _updatePercentButton: function()
  393. {
  394. if (this.showSelfTimeAsPercent.get() && this.showTotalTimeAsPercent.get() && this.showAverageTimeAsPercent.get()) {
  395. this.percentButton.title = WebInspector.UIString("Show absolute total and self times.");
  396. this.percentButton.toggled = true;
  397. } else {
  398. this.percentButton.title = WebInspector.UIString("Show total and self times as percentages.");
  399. this.percentButton.toggled = false;
  400. }
  401. },
  402. _focusClicked: function(event)
  403. {
  404. if (!this.dataGrid.selectedNode)
  405. return;
  406. this.resetButton.visible = true;
  407. this.profileDataGridTree.focus(this.dataGrid.selectedNode);
  408. this.refresh();
  409. this.refreshVisibleData();
  410. },
  411. _excludeClicked: function(event)
  412. {
  413. var selectedNode = this.dataGrid.selectedNode
  414. if (!selectedNode)
  415. return;
  416. selectedNode.deselect();
  417. this.resetButton.visible = true;
  418. this.profileDataGridTree.exclude(selectedNode);
  419. this.refresh();
  420. this.refreshVisibleData();
  421. },
  422. _resetClicked: function(event)
  423. {
  424. this.resetButton.visible = false;
  425. this.profileDataGridTree.restore();
  426. this._linkifier.reset();
  427. this.refresh();
  428. this.refreshVisibleData();
  429. },
  430. _dataGridNodeSelected: function(node)
  431. {
  432. this.focusButton.setEnabled(true);
  433. this.excludeButton.setEnabled(true);
  434. },
  435. _dataGridNodeDeselected: function(node)
  436. {
  437. this.focusButton.setEnabled(false);
  438. this.excludeButton.setEnabled(false);
  439. },
  440. _sortProfile: function()
  441. {
  442. var sortAscending = this.dataGrid.isSortOrderAscending();
  443. var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier();
  444. var sortProperty = {
  445. "self": "selfTime",
  446. "total": "totalTime",
  447. "function": "functionName"
  448. }[sortColumnIdentifier];
  449. this.profileDataGridTree.sort(WebInspector.ProfileDataGridTree.propertyComparator(sortProperty, sortAscending));
  450. this.refresh();
  451. },
  452. _mouseDownInDataGrid: function(event)
  453. {
  454. if (event.detail < 2)
  455. return;
  456. var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
  457. if (!cell || (!cell.hasStyleClass("total-column") && !cell.hasStyleClass("self-column") && !cell.hasStyleClass("average-column")))
  458. return;
  459. if (cell.hasStyleClass("total-column"))
  460. this.showTotalTimeAsPercent.set(!this.showTotalTimeAsPercent.get());
  461. else if (cell.hasStyleClass("self-column"))
  462. this.showSelfTimeAsPercent.set(!this.showSelfTimeAsPercent.get());
  463. else if (cell.hasStyleClass("average-column"))
  464. this.showAverageTimeAsPercent.set(!this.showAverageTimeAsPercent.get());
  465. this.refreshShowAsPercents();
  466. event.consume(true);
  467. },
  468. _calculateTimes: function(profile)
  469. {
  470. function totalHitCount(node) {
  471. var result = node.hitCount;
  472. for (var i = 0; i < node.children.length; i++)
  473. result += totalHitCount(node.children[i]);
  474. return result;
  475. }
  476. profile.totalHitCount = totalHitCount(profile.head);
  477. var durationMs = 1000 * profile.endTime - 1000 * profile.startTime;
  478. var samplingRate = profile.totalHitCount / durationMs;
  479. this.samplesPerMs = samplingRate;
  480. function calculateTimesForNode(node) {
  481. node.selfTime = node.hitCount * samplingRate;
  482. var totalTime = node.selfTime;
  483. for (var i = 0; i < node.children.length; i++)
  484. totalTime += calculateTimesForNode(node.children[i]);
  485. node.totalTime = totalTime;
  486. return totalTime;
  487. }
  488. calculateTimesForNode(profile.head);
  489. },
  490. _assignParentsInProfile: function()
  491. {
  492. var head = this.profileHead;
  493. head.parent = null;
  494. head.head = null;
  495. var nodesToTraverse = [ { parent: head, children: head.children } ];
  496. while (nodesToTraverse.length > 0) {
  497. var pair = nodesToTraverse.pop();
  498. var parent = pair.parent;
  499. var children = pair.children;
  500. var length = children.length;
  501. for (var i = 0; i < length; ++i) {
  502. children[i].head = head;
  503. children[i].parent = parent;
  504. if (children[i].children.length > 0)
  505. nodesToTraverse.push({ parent: children[i], children: children[i].children });
  506. }
  507. }
  508. },
  509. _buildIdToNodeMap: function()
  510. {
  511. var idToNode = this._idToNode = {};
  512. var stack = [this.profileHead];
  513. while (stack.length) {
  514. var node = stack.pop();
  515. idToNode[node.id] = node;
  516. for (var i = 0; i < node.children.length; i++)
  517. stack.push(node.children[i]);
  518. }
  519. var topLevelNodes = this.profileHead.children;
  520. for (var i = 0; i < topLevelNodes.length; i++) {
  521. var node = topLevelNodes[i];
  522. if (node.functionName == "(garbage collector)") {
  523. this._gcNode = node;
  524. break;
  525. }
  526. }
  527. },
  528. __proto__: WebInspector.View.prototype
  529. }
  530. /**
  531. * @constructor
  532. * @extends {WebInspector.ProfileType}
  533. * @implements {ProfilerAgent.Dispatcher}
  534. */
  535. WebInspector.CPUProfileType = function()
  536. {
  537. WebInspector.ProfileType.call(this, WebInspector.CPUProfileType.TypeId, WebInspector.UIString("Collect JavaScript CPU Profile"));
  538. InspectorBackend.registerProfilerDispatcher(this);
  539. this._recording = false;
  540. WebInspector.CPUProfileType.instance = this;
  541. }
  542. WebInspector.CPUProfileType.TypeId = "CPU";
  543. WebInspector.CPUProfileType.prototype = {
  544. /**
  545. * @override
  546. * @return {string}
  547. */
  548. fileExtension: function()
  549. {
  550. return ".cpuprofile";
  551. },
  552. get buttonTooltip()
  553. {
  554. return this._recording ? WebInspector.UIString("Stop CPU profiling.") : WebInspector.UIString("Start CPU profiling.");
  555. },
  556. /**
  557. * @override
  558. * @return {boolean}
  559. */
  560. buttonClicked: function()
  561. {
  562. if (this._recording) {
  563. this.stopRecordingProfile();
  564. return false;
  565. } else {
  566. this.startRecordingProfile();
  567. return true;
  568. }
  569. },
  570. get treeItemTitle()
  571. {
  572. return WebInspector.UIString("CPU PROFILES");
  573. },
  574. get description()
  575. {
  576. return WebInspector.UIString("CPU profiles show where the execution time is spent in your page's JavaScript functions.");
  577. },
  578. /**
  579. * @param {ProfilerAgent.ProfileHeader} profileHeader
  580. */
  581. addProfileHeader: function(profileHeader)
  582. {
  583. this.addProfile(this.createProfile(profileHeader));
  584. },
  585. isRecordingProfile: function()
  586. {
  587. return this._recording;
  588. },
  589. startRecordingProfile: function()
  590. {
  591. this._recording = true;
  592. WebInspector.userMetrics.ProfilesCPUProfileTaken.record();
  593. ProfilerAgent.start();
  594. },
  595. stopRecordingProfile: function()
  596. {
  597. this._recording = false;
  598. ProfilerAgent.stop();
  599. },
  600. /**
  601. * @param {boolean} isProfiling
  602. */
  603. setRecordingProfile: function(isProfiling)
  604. {
  605. this._recording = isProfiling;
  606. },
  607. /**
  608. * @override
  609. * @param {string=} title
  610. * @return {!WebInspector.ProfileHeader}
  611. */
  612. createTemporaryProfile: function(title)
  613. {
  614. title = title || WebInspector.UIString("Recording\u2026");
  615. return new WebInspector.CPUProfileHeader(this, title);
  616. },
  617. /**
  618. * @override
  619. * @param {ProfilerAgent.ProfileHeader} profile
  620. * @return {!WebInspector.ProfileHeader}
  621. */
  622. createProfile: function(profile)
  623. {
  624. return new WebInspector.CPUProfileHeader(this, profile.title, profile.uid);
  625. },
  626. /**
  627. * @override
  628. * @param {!WebInspector.ProfileHeader} profile
  629. */
  630. removeProfile: function(profile)
  631. {
  632. WebInspector.ProfileType.prototype.removeProfile.call(this, profile);
  633. if (!profile.isTemporary)
  634. ProfilerAgent.removeProfile(this.id, profile.uid);
  635. },
  636. /**
  637. * @override
  638. * @param {function(this:WebInspector.ProfileType, ?string, Array.<ProfilerAgent.ProfileHeader>)} populateCallback
  639. */
  640. _requestProfilesFromBackend: function(populateCallback)
  641. {
  642. ProfilerAgent.getProfileHeaders(populateCallback);
  643. },
  644. /**
  645. * @override
  646. */
  647. resetProfiles: function()
  648. {
  649. this._reset();
  650. },
  651. /** @deprecated To be removed from the protocol */
  652. addHeapSnapshotChunk: function(uid, chunk)
  653. {
  654. throw new Error("Never called");
  655. },
  656. /** @deprecated To be removed from the protocol */
  657. finishHeapSnapshot: function(uid)
  658. {
  659. throw new Error("Never called");
  660. },
  661. /** @deprecated To be removed from the protocol */
  662. reportHeapSnapshotProgress: function(done, total)
  663. {
  664. throw new Error("Never called");
  665. },
  666. __proto__: WebInspector.ProfileType.prototype
  667. }
  668. /**
  669. * @constructor
  670. * @extends {WebInspector.ProfileHeader}
  671. * @implements {WebInspector.OutputStream}
  672. * @implements {WebInspector.OutputStreamDelegate}
  673. * @param {!WebInspector.CPUProfileType} type
  674. * @param {string} title
  675. * @param {number=} uid
  676. */
  677. WebInspector.CPUProfileHeader = function(type, title, uid)
  678. {
  679. WebInspector.ProfileHeader.call(this, type, title, uid);
  680. }
  681. WebInspector.CPUProfileHeader.prototype = {
  682. onTransferStarted: function()
  683. {
  684. this._jsonifiedProfile = "";
  685. this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026 %s", Number.bytesToString(this._jsonifiedProfile.length));
  686. },
  687. /**
  688. * @param {WebInspector.ChunkedReader} reader
  689. */
  690. onChunkTransferred: function(reader)
  691. {
  692. this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026 %d\%", Number.bytesToString(this._jsonifiedProfile.length));
  693. },
  694. onTransferFinished: function()
  695. {
  696. this.sidebarElement.subtitle = WebInspector.UIString("Parsing\u2026");
  697. this._profile = JSON.parse(this._jsonifiedProfile);
  698. this._jsonifiedProfile = null;
  699. this.sidebarElement.subtitle = WebInspector.UIString("Loaded");
  700. this.isTemporary = false;
  701. },
  702. /**
  703. * @param {WebInspector.ChunkedReader} reader
  704. */
  705. onError: function(reader, e)
  706. {
  707. switch(e.target.error.code) {
  708. case e.target.error.NOT_FOUND_ERR:
  709. this.sidebarElement.subtitle = WebInspector.UIString("'%s' not found.", reader.fileName());
  710. break;
  711. case e.target.error.NOT_READABLE_ERR:
  712. this.sidebarElement.subtitle = WebInspector.UIString("'%s' is not readable", reader.fileName());
  713. break;
  714. case e.target.error.ABORT_ERR:
  715. break;
  716. default:
  717. this.sidebarElement.subtitle = WebInspector.UIString("'%s' error %d", reader.fileName(), e.target.error.code);
  718. }
  719. },
  720. /**
  721. * @param {string} text
  722. */
  723. write: function(text)
  724. {
  725. this._jsonifiedProfile += text;
  726. },
  727. close: function() { },
  728. /**
  729. * @override
  730. */
  731. createSidebarTreeElement: function()
  732. {
  733. return new WebInspector.ProfileSidebarTreeElement(this, WebInspector.UIString("Profile %d"), "profile-sidebar-tree-item");
  734. },
  735. /**
  736. * @override
  737. * @param {WebInspector.ProfilesPanel} profilesPanel
  738. */
  739. createView: function(profilesPanel)
  740. {
  741. return new WebInspector.CPUProfileView(this);
  742. },
  743. /**
  744. * @override
  745. * @return {boolean}
  746. */
  747. canSaveToFile: function()
  748. {
  749. return true;
  750. },
  751. saveToFile: function()
  752. {
  753. var fileOutputStream = new WebInspector.FileOutputStream();
  754. /**
  755. * @param {?Protocol.Error} error
  756. * @param {ProfilerAgent.CPUProfile} profile
  757. */
  758. function getCPUProfileCallback(error, profile)
  759. {
  760. if (error) {
  761. fileOutputStream.close();
  762. return;
  763. }
  764. if (!profile.head) {
  765. // Profiling was tentatively terminated with the "Clear all profiles." button.
  766. fileOutputStream.close();
  767. return;
  768. }
  769. fileOutputStream.write(JSON.stringify(profile), fileOutputStream.close.bind(fileOutputStream));
  770. }
  771. function onOpen()
  772. {
  773. ProfilerAgent.getCPUProfile(this.uid, getCPUProfileCallback.bind(this));
  774. }
  775. this._fileName = this._fileName || "CPU-" + new Date().toISO8601Compact() + this._profileType.fileExtension();
  776. fileOutputStream.open(this._fileName, onOpen.bind(this));
  777. },
  778. /**
  779. * @param {File} file
  780. */
  781. loadFromFile: function(file)
  782. {
  783. this.title = file.name;
  784. this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026");
  785. this.sidebarElement.wait = true;
  786. var fileReader = new WebInspector.ChunkedFileReader(file, 10000000, this);
  787. fileReader.start(this);
  788. },
  789. __proto__: WebInspector.ProfileHeader.prototype
  790. }