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