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 26/** 27 * @constructor 28 * @extends {WebInspector.DataGridNode} 29 * @param {!ProfilerAgent.CPUProfileNode} profileNode 30 * @param {!WebInspector.TopDownProfileDataGridTree} owningTree 31 * @param {boolean} hasChildren 32 */ 33WebInspector.ProfileDataGridNode = function(profileNode, owningTree, hasChildren) 34{ 35 this._target = /** @type {!WebInspector.Target} */ (WebInspector.targetManager.activeTarget()); 36 this.profileNode = profileNode; 37 38 WebInspector.DataGridNode.call(this, null, hasChildren); 39 40 this.tree = owningTree; 41 42 this.childrenByCallUID = {}; 43 this.lastComparator = null; 44 45 this.callUID = profileNode.callUID; 46 this.selfTime = profileNode.selfTime; 47 this.totalTime = profileNode.totalTime; 48 this.functionName = profileNode.functionName; 49 this._deoptReason = (!profileNode.deoptReason || profileNode.deoptReason === "no reason") ? "" : profileNode.deoptReason; 50 this.url = profileNode.url; 51} 52 53WebInspector.ProfileDataGridNode.prototype = { 54 /** 55 * @override 56 * @param {string} columnIdentifier 57 * @return {!Element} 58 */ 59 createCell: function(columnIdentifier) 60 { 61 var cell = this._createValueCell(columnIdentifier) || WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier); 62 63 if (columnIdentifier === "self" && this._searchMatchedSelfColumn) 64 cell.classList.add("highlight"); 65 else if (columnIdentifier === "total" && this._searchMatchedTotalColumn) 66 cell.classList.add("highlight"); 67 68 if (columnIdentifier !== "function") 69 return cell; 70 71 if (this._deoptReason) 72 cell.classList.add("not-optimized"); 73 74 if (this.profileNode._searchMatchedFunctionColumn) 75 cell.classList.add("highlight"); 76 77 if (this.profileNode.scriptId !== "0") { 78 var lineNumber = this.profileNode.lineNumber ? this.profileNode.lineNumber - 1 : 0; 79 var columnNumber = this.profileNode.columnNumber ? this.profileNode.columnNumber - 1 : 0; 80 var location = new WebInspector.DebuggerModel.Location(/** @type {!WebInspector.Target} */ (WebInspector.targetManager.activeTarget()), this.profileNode.scriptId, lineNumber, columnNumber); 81 var urlElement = this.tree.profileView._linkifier.linkifyRawLocation(location, "profile-node-file"); 82 if (!urlElement) 83 urlElement = this.tree.profileView._linkifier.linkifyLocation(this._target, this.profileNode.url, lineNumber, columnNumber, "profile-node-file"); 84 urlElement.style.maxWidth = "75%"; 85 cell.insertBefore(urlElement, cell.firstChild); 86 } 87 88 return cell; 89 }, 90 91 /** 92 * @param {string} columnIdentifier 93 * @return {?Element} 94 */ 95 _createValueCell: function(columnIdentifier) 96 { 97 if (columnIdentifier !== "self" && columnIdentifier !== "total") 98 return null; 99 100 var cell = document.createElement("td"); 101 cell.className = "numeric-column"; 102 var div = document.createElement("div"); 103 var valueSpan = document.createElement("span"); 104 valueSpan.textContent = this.data[columnIdentifier]; 105 div.appendChild(valueSpan); 106 var percentColumn = columnIdentifier + "-percent"; 107 if (percentColumn in this.data) { 108 var percentSpan = document.createElement("span"); 109 percentSpan.className = "percent-column"; 110 percentSpan.textContent = this.data[percentColumn]; 111 div.appendChild(percentSpan); 112 div.classList.add("profile-multiple-values"); 113 } 114 cell.appendChild(div); 115 return cell; 116 }, 117 118 buildData: function() 119 { 120 function formatMilliseconds(time) 121 { 122 return WebInspector.UIString("%.1f\u2009ms", time); 123 } 124 function formatPercent(value) 125 { 126 return WebInspector.UIString("%.2f\u2009%", value); 127 } 128 129 var functionName; 130 if (this._deoptReason) { 131 var content = document.createDocumentFragment(); 132 var marker = content.createChild("span", "profile-warn-marker"); 133 marker.title = WebInspector.UIString("Not optimized: %s", this._deoptReason); 134 content.createTextChild(this.functionName); 135 functionName = content; 136 } else { 137 functionName = this.functionName; 138 } 139 140 this.data = { 141 "function": functionName, 142 "self-percent": formatPercent(this.selfPercent), 143 "self": formatMilliseconds(this.selfTime), 144 "total-percent": formatPercent(this.totalPercent), 145 "total": formatMilliseconds(this.totalTime), 146 }; 147 }, 148 149 select: function(supressSelectedEvent) 150 { 151 WebInspector.DataGridNode.prototype.select.call(this, supressSelectedEvent); 152 this.tree.profileView._dataGridNodeSelected(this); 153 }, 154 155 deselect: function(supressDeselectedEvent) 156 { 157 WebInspector.DataGridNode.prototype.deselect.call(this, supressDeselectedEvent); 158 this.tree.profileView._dataGridNodeDeselected(this); 159 }, 160 161 /** 162 * @param {function(!T, !T)} comparator 163 * @param {boolean} force 164 * @template T 165 */ 166 sort: function(comparator, force) 167 { 168 var gridNodeGroups = [[this]]; 169 170 for (var gridNodeGroupIndex = 0; gridNodeGroupIndex < gridNodeGroups.length; ++gridNodeGroupIndex) { 171 var gridNodes = gridNodeGroups[gridNodeGroupIndex]; 172 var count = gridNodes.length; 173 174 for (var index = 0; index < count; ++index) { 175 var gridNode = gridNodes[index]; 176 177 // If the grid node is collapsed, then don't sort children (save operation for later). 178 // If the grid node has the same sorting as previously, then there is no point in sorting it again. 179 if (!force && (!gridNode.expanded || gridNode.lastComparator === comparator)) { 180 if (gridNode.children.length) 181 gridNode.shouldRefreshChildren = true; 182 continue; 183 } 184 185 gridNode.lastComparator = comparator; 186 187 var children = gridNode.children; 188 var childCount = children.length; 189 190 if (childCount) { 191 children.sort(comparator); 192 193 for (var childIndex = 0; childIndex < childCount; ++childIndex) 194 children[childIndex]._recalculateSiblings(childIndex); 195 196 gridNodeGroups.push(children); 197 } 198 } 199 } 200 }, 201 202 /** 203 * @param {!WebInspector.ProfileDataGridNode} profileDataGridNode 204 * @param {number} index 205 */ 206 insertChild: function(profileDataGridNode, index) 207 { 208 WebInspector.DataGridNode.prototype.insertChild.call(this, profileDataGridNode, index); 209 210 this.childrenByCallUID[profileDataGridNode.callUID] = profileDataGridNode; 211 }, 212 213 /** 214 * @param {!WebInspector.ProfileDataGridNode} profileDataGridNode 215 */ 216 removeChild: function(profileDataGridNode) 217 { 218 WebInspector.DataGridNode.prototype.removeChild.call(this, profileDataGridNode); 219 220 delete this.childrenByCallUID[profileDataGridNode.callUID]; 221 }, 222 223 removeChildren: function() 224 { 225 WebInspector.DataGridNode.prototype.removeChildren.call(this); 226 227 this.childrenByCallUID = {}; 228 }, 229 230 /** 231 * @param {!WebInspector.ProfileDataGridNode} node 232 * @return {?WebInspector.ProfileDataGridNode} 233 */ 234 findChild: function(node) 235 { 236 if (!node) 237 return null; 238 return this.childrenByCallUID[node.callUID]; 239 }, 240 241 get selfPercent() 242 { 243 return this.selfTime / this.tree.totalTime * 100.0; 244 }, 245 246 get totalPercent() 247 { 248 return this.totalTime / this.tree.totalTime * 100.0; 249 }, 250 251 get _parent() 252 { 253 return this.parent !== this.dataGrid ? this.parent : this.tree; 254 }, 255 256 populate: function() 257 { 258 if (this._populated) 259 return; 260 this._populated = true; 261 262 this._sharedPopulate(); 263 264 var currentComparator = this.tree.lastComparator; 265 266 if (currentComparator) 267 this.sort(currentComparator, true); 268 }, 269 270 // When focusing and collapsing we modify lots of nodes in the tree. 271 // This allows us to restore them all to their original state when we revert. 272 _save: function() 273 { 274 if (this._savedChildren) 275 return; 276 277 this._savedSelfTime = this.selfTime; 278 this._savedTotalTime = this.totalTime; 279 280 this._savedChildren = this.children.slice(); 281 }, 282 283 // When focusing and collapsing we modify lots of nodes in the tree. 284 // This allows us to restore them all to their original state when we revert. 285 _restore: function() 286 { 287 if (!this._savedChildren) 288 return; 289 290 this.selfTime = this._savedSelfTime; 291 this.totalTime = this._savedTotalTime; 292 293 this.removeChildren(); 294 295 var children = this._savedChildren; 296 var count = children.length; 297 298 for (var index = 0; index < count; ++index) { 299 children[index]._restore(); 300 this.appendChild(children[index]); 301 } 302 }, 303 304 _merge: function(child, shouldAbsorb) 305 { 306 this.selfTime += child.selfTime; 307 308 if (!shouldAbsorb) 309 this.totalTime += child.totalTime; 310 311 var children = this.children.slice(); 312 313 this.removeChildren(); 314 315 var count = children.length; 316 317 for (var index = 0; index < count; ++index) { 318 if (!shouldAbsorb || children[index] !== child) 319 this.appendChild(children[index]); 320 } 321 322 children = child.children.slice(); 323 count = children.length; 324 325 for (var index = 0; index < count; ++index) { 326 var orphanedChild = children[index], 327 existingChild = this.childrenByCallUID[orphanedChild.callUID]; 328 329 if (existingChild) 330 existingChild._merge(orphanedChild, false); 331 else 332 this.appendChild(orphanedChild); 333 } 334 }, 335 336 __proto__: WebInspector.DataGridNode.prototype 337} 338 339/** 340 * @constructor 341 * @param {!WebInspector.CPUProfileView} profileView 342 * @param {!ProfilerAgent.CPUProfileNode} rootProfileNode 343 */ 344WebInspector.ProfileDataGridTree = function(profileView, rootProfileNode) 345{ 346 this.tree = this; 347 this.children = []; 348 349 this.profileView = profileView; 350 351 this.totalTime = rootProfileNode.totalTime; 352 this.lastComparator = null; 353 354 this.childrenByCallUID = {}; 355} 356 357WebInspector.ProfileDataGridTree.prototype = { 358 get expanded() 359 { 360 return true; 361 }, 362 363 appendChild: function(child) 364 { 365 this.insertChild(child, this.children.length); 366 }, 367 368 insertChild: function(child, index) 369 { 370 this.children.splice(index, 0, child); 371 this.childrenByCallUID[child.callUID] = child; 372 }, 373 374 removeChildren: function() 375 { 376 this.children = []; 377 this.childrenByCallUID = {}; 378 }, 379 380 findChild: WebInspector.ProfileDataGridNode.prototype.findChild, 381 sort: WebInspector.ProfileDataGridNode.prototype.sort, 382 383 _save: function() 384 { 385 if (this._savedChildren) 386 return; 387 388 this._savedTotalTime = this.totalTime; 389 this._savedChildren = this.children.slice(); 390 }, 391 392 restore: function() 393 { 394 if (!this._savedChildren) 395 return; 396 397 this.children = this._savedChildren; 398 this.totalTime = this._savedTotalTime; 399 400 var children = this.children; 401 var count = children.length; 402 403 for (var index = 0; index < count; ++index) 404 children[index]._restore(); 405 406 this._savedChildren = null; 407 } 408} 409 410WebInspector.ProfileDataGridTree.propertyComparators = [{}, {}]; 411 412/** 413 * @param {string} property 414 * @param {boolean} isAscending 415 * @return {function(!Object.<string, *>, !Object.<string, *>)} 416 */ 417WebInspector.ProfileDataGridTree.propertyComparator = function(property, isAscending) 418{ 419 var comparator = WebInspector.ProfileDataGridTree.propertyComparators[(isAscending ? 1 : 0)][property]; 420 421 if (!comparator) { 422 if (isAscending) { 423 comparator = function(lhs, rhs) 424 { 425 if (lhs[property] < rhs[property]) 426 return -1; 427 428 if (lhs[property] > rhs[property]) 429 return 1; 430 431 return 0; 432 } 433 } else { 434 comparator = function(lhs, rhs) 435 { 436 if (lhs[property] > rhs[property]) 437 return -1; 438 439 if (lhs[property] < rhs[property]) 440 return 1; 441 442 return 0; 443 } 444 } 445 446 WebInspector.ProfileDataGridTree.propertyComparators[(isAscending ? 1 : 0)][property] = comparator; 447 } 448 449 return comparator; 450} 451