/* * Copyright (C) 2009 280 North Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ WebInspector.ProfileDataGridNode = function(profileView, profileNode, owningTree, hasChildren) { this.profileView = profileView; this.profileNode = profileNode; WebInspector.DataGridNode.call(this, null, hasChildren); this.addEventListener("populate", this._populate, this); this.tree = owningTree; this.childrenByCallUID = {}; this.lastComparator = null; this.callUID = profileNode.callUID; this.selfTime = profileNode.selfTime; this.totalTime = profileNode.totalTime; this.functionName = profileNode.functionName; this.numberOfCalls = profileNode.numberOfCalls; this.url = profileNode.url; } WebInspector.ProfileDataGridNode.prototype = { get data() { function formatMilliseconds(time) { return Number.secondsToString(time / 1000, !Preferences.samplingCPUProfiler); } var data = {}; data["function"] = this.functionName; data["calls"] = this.numberOfCalls; if (this.profileView.showSelfTimeAsPercent) data["self"] = WebInspector.UIString("%.2f%%", this.selfPercent); else data["self"] = formatMilliseconds(this.selfTime); if (this.profileView.showTotalTimeAsPercent) data["total"] = WebInspector.UIString("%.2f%%", this.totalPercent); else data["total"] = formatMilliseconds(this.totalTime); if (this.profileView.showAverageTimeAsPercent) data["average"] = WebInspector.UIString("%.2f%%", this.averagePercent); else data["average"] = formatMilliseconds(this.averageTime); return data; }, createCell: function(columnIdentifier) { var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier); if (columnIdentifier === "self" && this._searchMatchedSelfColumn) cell.addStyleClass("highlight"); else if (columnIdentifier === "total" && this._searchMatchedTotalColumn) cell.addStyleClass("highlight"); else if (columnIdentifier === "average" && this._searchMatchedAverageColumn) cell.addStyleClass("highlight"); else if (columnIdentifier === "calls" && this._searchMatchedCallsColumn) cell.addStyleClass("highlight"); if (columnIdentifier !== "function") return cell; if (this.profileNode._searchMatchedFunctionColumn) cell.addStyleClass("highlight"); if (this.profileNode.url) { var lineNumber; if (this.profileNode.lineNumber > 0) lineNumber = this.profileNode.lineNumber; var urlElement = WebInspector.linkifyResourceAsNode(this.profileNode.url, "scripts", lineNumber, "profile-node-file"); cell.insertBefore(urlElement, cell.firstChild); } return cell; }, select: function(supressSelectedEvent) { WebInspector.DataGridNode.prototype.select.call(this, supressSelectedEvent); this.profileView._dataGridNodeSelected(this); }, deselect: function(supressDeselectedEvent) { WebInspector.DataGridNode.prototype.deselect.call(this, supressDeselectedEvent); this.profileView._dataGridNodeDeselected(this); }, sort: function(/*Function*/ comparator, /*Boolean*/ force) { var gridNodeGroups = [[this]]; for (var gridNodeGroupIndex = 0; gridNodeGroupIndex < gridNodeGroups.length; ++gridNodeGroupIndex) { var gridNodes = gridNodeGroups[gridNodeGroupIndex]; var count = gridNodes.length; for (var index = 0; index < count; ++index) { var gridNode = gridNodes[index]; // If the grid node is collapsed, then don't sort children (save operation for later). // If the grid node has the same sorting as previously, then there is no point in sorting it again. if (!force && (!gridNode.expanded || gridNode.lastComparator === comparator)) { if (gridNode.children.length) gridNode.shouldRefreshChildren = true; continue; } gridNode.lastComparator = comparator; var children = gridNode.children; var childCount = children.length; if (childCount) { children.sort(comparator); for (var childIndex = 0; childIndex < childCount; ++childIndex) children[childIndex]._recalculateSiblings(childIndex); gridNodeGroups.push(children); } } } }, insertChild: function(/*ProfileDataGridNode*/ profileDataGridNode, index) { WebInspector.DataGridNode.prototype.insertChild.call(this, profileDataGridNode, index); this.childrenByCallUID[profileDataGridNode.callUID] = profileDataGridNode; }, removeChild: function(/*ProfileDataGridNode*/ profileDataGridNode) { WebInspector.DataGridNode.prototype.removeChild.call(this, profileDataGridNode); delete this.childrenByCallUID[profileDataGridNode.callUID]; }, removeChildren: function(/*ProfileDataGridNode*/ profileDataGridNode) { WebInspector.DataGridNode.prototype.removeChildren.call(this); this.childrenByCallUID = {}; }, findChild: function(/*Node*/ node) { if (!node) return null; return this.childrenByCallUID[node.callUID]; }, get averageTime() { return this.selfTime / Math.max(1, this.numberOfCalls); }, get averagePercent() { return this.averageTime / this.tree.totalTime * 100.0; }, get selfPercent() { return this.selfTime / this.tree.totalTime * 100.0; }, get totalPercent() { return this.totalTime / this.tree.totalTime * 100.0; }, get _parent() { return this.parent !== this.dataGrid ? this.parent : this.tree; }, _populate: function(event) { this._sharedPopulate(); if (this._parent) { var currentComparator = this._parent.lastComparator; if (currentComparator) this.sort(currentComparator, true); } if (this.removeEventListener) this.removeEventListener("populate", this._populate, this); }, // When focusing and collapsing we modify lots of nodes in the tree. // This allows us to restore them all to their original state when we revert. _save: function() { if (this._savedChildren) return; this._savedSelfTime = this.selfTime; this._savedTotalTime = this.totalTime; this._savedNumberOfCalls = this.numberOfCalls; this._savedChildren = this.children.slice(); }, // When focusing and collapsing we modify lots of nodes in the tree. // This allows us to restore them all to their original state when we revert. _restore: function() { if (!this._savedChildren) return; this.selfTime = this._savedSelfTime; this.totalTime = this._savedTotalTime; this.numberOfCalls = this._savedNumberOfCalls; this.removeChildren(); var children = this._savedChildren; var count = children.length; for (var index = 0; index < count; ++index) { children[index]._restore(); this.appendChild(children[index]); } }, _merge: function(child, shouldAbsorb) { this.selfTime += child.selfTime; if (!shouldAbsorb) { this.totalTime += child.totalTime; this.numberOfCalls += child.numberOfCalls; } var children = this.children.slice(); this.removeChildren(); var count = children.length; for (var index = 0; index < count; ++index) { if (!shouldAbsorb || children[index] !== child) this.appendChild(children[index]); } children = child.children.slice(); count = children.length; for (var index = 0; index < count; ++index) { var orphanedChild = children[index], existingChild = this.childrenByCallUID[orphanedChild.callUID]; if (existingChild) existingChild._merge(orphanedChild, false); else this.appendChild(orphanedChild); } } } WebInspector.ProfileDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype; WebInspector.ProfileDataGridTree = function(profileView, profileNode) { this.tree = this; this.children = []; this.profileView = profileView; this.totalTime = profileNode.totalTime; this.lastComparator = null; this.childrenByCallUID = {}; } WebInspector.ProfileDataGridTree.prototype = { get expanded() { return true; }, appendChild: function(child) { this.insertChild(child, this.children.length); }, insertChild: function(child, index) { this.children.splice(index, 0, child); this.childrenByCallUID[child.callUID] = child; }, removeChildren: function() { this.children = []; this.childrenByCallUID = {}; }, findChild: WebInspector.ProfileDataGridNode.prototype.findChild, sort: WebInspector.ProfileDataGridNode.prototype.sort, _save: function() { if (this._savedChildren) return; this._savedTotalTime = this.totalTime; this._savedChildren = this.children.slice(); }, restore: function() { if (!this._savedChildren) return; this.children = this._savedChildren; this.totalTime = this._savedTotalTime; var children = this.children; var count = children.length; for (var index = 0; index < count; ++index) children[index]._restore(); this._savedChildren = null; } } WebInspector.ProfileDataGridTree.propertyComparators = [{}, {}]; WebInspector.ProfileDataGridTree.propertyComparator = function(/*String*/ property, /*Boolean*/ isAscending) { var comparator = this.propertyComparators[(isAscending ? 1 : 0)][property]; if (!comparator) { if (isAscending) { comparator = function(lhs, rhs) { if (lhs[property] < rhs[property]) return -1; if (lhs[property] > rhs[property]) return 1; return 0; } } else { comparator = function(lhs, rhs) { if (lhs[property] > rhs[property]) return -1; if (lhs[property] < rhs[property]) return 1; return 0; } } this.propertyComparators[(isAscending ? 1 : 0)][property] = comparator; } return comparator; }