1/* 2 * Copyright (C) 2009 280 North 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 26WebInspector.ProfileDataGridNode = function(profileView, profileNode, owningTree, hasChildren) 27{ 28 this.profileView = profileView; 29 this.profileNode = profileNode; 30 31 WebInspector.DataGridNode.call(this, null, hasChildren); 32 33 this.addEventListener("populate", this._populate, this); 34 35 this.tree = owningTree; 36 37 this.childrenByCallUID = {}; 38 this.lastComparator = null; 39 40 this.callUID = profileNode.callUID; 41 this.selfTime = profileNode.selfTime; 42 this.totalTime = profileNode.totalTime; 43 this.functionName = profileNode.functionName; 44 this.numberOfCalls = profileNode.numberOfCalls; 45 this.url = profileNode.url; 46} 47 48WebInspector.ProfileDataGridNode.prototype = { 49 get data() 50 { 51 function formatMilliseconds(time) 52 { 53 return Number.secondsToString(time / 1000, WebInspector.UIString.bind(WebInspector), true); 54 } 55 56 var data = {}; 57 58 data["function"] = this.functionName; 59 data["calls"] = this.numberOfCalls; 60 61 if (this.profileView.showSelfTimeAsPercent) 62 data["self"] = WebInspector.UIString("%.2f%%", this.selfPercent); 63 else 64 data["self"] = formatMilliseconds(this.selfTime); 65 66 if (this.profileView.showTotalTimeAsPercent) 67 data["total"] = WebInspector.UIString("%.2f%%", this.totalPercent); 68 else 69 data["total"] = formatMilliseconds(this.totalTime); 70 71 if (this.profileView.showAverageTimeAsPercent) 72 data["average"] = WebInspector.UIString("%.2f%%", this.averagePercent); 73 else 74 data["average"] = formatMilliseconds(this.averageTime); 75 76 return data; 77 }, 78 79 createCell: function(columnIdentifier) 80 { 81 var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier); 82 83 if (columnIdentifier === "self" && this._searchMatchedSelfColumn) 84 cell.addStyleClass("highlight"); 85 else if (columnIdentifier === "total" && this._searchMatchedTotalColumn) 86 cell.addStyleClass("highlight"); 87 else if (columnIdentifier === "average" && this._searchMatchedAverageColumn) 88 cell.addStyleClass("highlight"); 89 else if (columnIdentifier === "calls" && this._searchMatchedCallsColumn) 90 cell.addStyleClass("highlight"); 91 92 if (columnIdentifier !== "function") 93 return cell; 94 95 if (this.profileNode._searchMatchedFunctionColumn) 96 cell.addStyleClass("highlight"); 97 98 if (this.profileNode.url) { 99 var fileName = WebInspector.displayNameForURL(this.profileNode.url); 100 101 var urlElement = document.createElement("a"); 102 urlElement.className = "profile-node-file webkit-html-resource-link"; 103 urlElement.href = this.profileNode.url; 104 urlElement.lineNumber = this.profileNode.lineNumber; 105 106 if (this.profileNode.lineNumber > 0) 107 urlElement.textContent = fileName + ":" + this.profileNode.lineNumber; 108 else 109 urlElement.textContent = fileName; 110 111 cell.insertBefore(urlElement, cell.firstChild); 112 } 113 114 return cell; 115 }, 116 117 select: function(supressSelectedEvent) 118 { 119 WebInspector.DataGridNode.prototype.select.call(this, supressSelectedEvent); 120 this.profileView._dataGridNodeSelected(this); 121 }, 122 123 deselect: function(supressDeselectedEvent) 124 { 125 WebInspector.DataGridNode.prototype.deselect.call(this, supressDeselectedEvent); 126 this.profileView._dataGridNodeDeselected(this); 127 }, 128 129 expand: function() 130 { 131 if (!this.parent) { 132 var currentComparator = this.parent.lastComparator; 133 134 if (!currentComparator || (currentComparator === this.lastComparator)) 135 return; 136 137 this.sort(currentComparator); 138 } 139 140 WebInspector.DataGridNode.prototype.expand.call(this); 141 }, 142 143 sort: function(/*Function*/ comparator, /*Boolean*/ force) 144 { 145 var gridNodeGroups = [[this]]; 146 147 for (var gridNodeGroupIndex = 0; gridNodeGroupIndex < gridNodeGroups.length; ++gridNodeGroupIndex) { 148 var gridNodes = gridNodeGroups[gridNodeGroupIndex]; 149 var count = gridNodes.length; 150 151 for (var index = 0; index < count; ++index) { 152 var gridNode = gridNodes[index]; 153 154 // If the grid node is collapsed, then don't sort children (save operation for later). 155 // If the grid node has the same sorting as previously, then there is no point in sorting it again. 156 if (!force && !gridNode.expanded || gridNode.lastComparator === comparator) 157 continue; 158 159 gridNode.lastComparator = comparator; 160 161 var children = gridNode.children; 162 var childCount = children.length; 163 164 if (childCount) { 165 children.sort(comparator); 166 167 for (var childIndex = 0; childIndex < childCount; ++childIndex) 168 children[childIndex]._recalculateSiblings(childIndex); 169 170 gridNodeGroups.push(children); 171 } 172 } 173 } 174 }, 175 176 insertChild: function(/*ProfileDataGridNode*/ profileDataGridNode, index) 177 { 178 WebInspector.DataGridNode.prototype.insertChild.call(this, profileDataGridNode, index); 179 180 this.childrenByCallUID[profileDataGridNode.callUID] = profileDataGridNode; 181 }, 182 183 removeChild: function(/*ProfileDataGridNode*/ profileDataGridNode) 184 { 185 WebInspector.DataGridNode.prototype.removeChild.call(this, profileDataGridNode); 186 187 delete this.childrenByCallUID[profileDataGridNode.callUID]; 188 }, 189 190 removeChildren: function(/*ProfileDataGridNode*/ profileDataGridNode) 191 { 192 WebInspector.DataGridNode.prototype.removeChildren.call(this); 193 194 this.childrenByCallUID = {}; 195 }, 196 197 findChild: function(/*Node*/ node) 198 { 199 if (!node) 200 return null; 201 return this.childrenByCallUID[node.callUID]; 202 }, 203 204 get averageTime() 205 { 206 return this.selfTime / Math.max(1, this.numberOfCalls); 207 }, 208 209 get averagePercent() 210 { 211 return this.averageTime / this.tree.totalTime * 100.0; 212 }, 213 214 get selfPercent() 215 { 216 return this.selfTime / this.tree.totalTime * 100.0; 217 }, 218 219 get totalPercent() 220 { 221 return this.totalTime / this.tree.totalTime * 100.0; 222 }, 223 224 // When focusing and collapsing we modify lots of nodes in the tree. 225 // This allows us to restore them all to their original state when we revert. 226 _save: function() 227 { 228 if (this._savedChildren) 229 return; 230 231 this._savedSelfTime = this.selfTime; 232 this._savedTotalTime = this.totalTime; 233 this._savedNumberOfCalls = this.numberOfCalls; 234 235 this._savedChildren = this.children.slice(); 236 }, 237 238 // When focusing and collapsing we modify lots of nodes in the tree. 239 // This allows us to restore them all to their original state when we revert. 240 _restore: function() 241 { 242 if (!this._savedChildren) 243 return; 244 245 this.selfTime = this._savedSelfTime; 246 this.totalTime = this._savedTotalTime; 247 this.numberOfCalls = this._savedNumberOfCalls; 248 249 this.removeChildren(); 250 251 var children = this._savedChildren; 252 var count = children.length; 253 254 for (var index = 0; index < count; ++index) { 255 children[index]._restore(); 256 this.appendChild(children[index]); 257 } 258 }, 259 260 _merge: function(child, shouldAbsorb) 261 { 262 this.selfTime += child.selfTime; 263 264 if (!shouldAbsorb) { 265 this.totalTime += child.totalTime; 266 this.numberOfCalls += child.numberOfCalls; 267 } 268 269 var children = this.children.slice(); 270 271 this.removeChildren(); 272 273 var count = children.length; 274 275 for (var index = 0; index < count; ++index) { 276 if (!shouldAbsorb || children[index] !== child) 277 this.appendChild(children[index]); 278 } 279 280 children = child.children.slice(); 281 count = children.length; 282 283 for (var index = 0; index < count; ++index) { 284 var orphanedChild = children[index], 285 existingChild = this.childrenByCallUID[orphanedChild.callUID]; 286 287 if (existingChild) 288 existingChild._merge(orphanedChild, false); 289 else 290 this.appendChild(orphanedChild); 291 } 292 } 293} 294 295WebInspector.ProfileDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype; 296 297WebInspector.ProfileDataGridTree = function(profileView, profileNode) 298{ 299 this.tree = this; 300 this.children = []; 301 302 this.profileView = profileView; 303 304 this.totalTime = profileNode.totalTime; 305 this.lastComparator = null; 306 307 this.childrenByCallUID = {}; 308} 309 310WebInspector.ProfileDataGridTree.prototype = { 311 get expanded() 312 { 313 return true; 314 }, 315 316 appendChild: function(child) 317 { 318 this.insertChild(child, this.children.length); 319 }, 320 321 insertChild: function(child, index) 322 { 323 this.children.splice(index, 0, child); 324 this.childrenByCallUID[child.callUID] = child; 325 }, 326 327 removeChildren: function() 328 { 329 this.children = []; 330 this.childrenByCallUID = {}; 331 }, 332 333 findChild: WebInspector.ProfileDataGridNode.prototype.findChild, 334 sort: WebInspector.ProfileDataGridNode.prototype.sort, 335 336 _save: function() 337 { 338 if (this._savedChildren) 339 return; 340 341 this._savedTotalTime = this.totalTime; 342 this._savedChildren = this.children.slice(); 343 }, 344 345 restore: function() 346 { 347 if (!this._savedChildren) 348 return; 349 350 this.children = this._savedChildren; 351 this.totalTime = this._savedTotalTime; 352 353 var children = this.children; 354 var count = children.length; 355 356 for (var index = 0; index < count; ++index) 357 children[index]._restore(); 358 359 this._savedChildren = null; 360 } 361} 362 363WebInspector.ProfileDataGridTree.propertyComparators = [{}, {}]; 364 365WebInspector.ProfileDataGridTree.propertyComparator = function(/*String*/ property, /*Boolean*/ isAscending) 366{ 367 var comparator = this.propertyComparators[(isAscending ? 1 : 0)][property]; 368 369 if (!comparator) { 370 if (isAscending) { 371 comparator = function(lhs, rhs) 372 { 373 if (lhs[property] < rhs[property]) 374 return -1; 375 376 if (lhs[property] > rhs[property]) 377 return 1; 378 379 return 0; 380 } 381 } else { 382 comparator = function(lhs, rhs) 383 { 384 if (lhs[property] > rhs[property]) 385 return -1; 386 387 if (lhs[property] < rhs[property]) 388 return 1; 389 390 return 0; 391 } 392 } 393 394 this.propertyComparators[(isAscending ? 1 : 0)][property] = comparator; 395 } 396 397 return comparator; 398} 399