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 */ 33 WebInspector.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.url = profileNode.url; 49 } 50 51 WebInspector.ProfileDataGridNode.prototype = { 52 get data() 53 { 54 function formatMilliseconds(time) 55 { 56 return WebInspector.UIString("%.0f\u2009ms", time); 57 } 58 59 var data = {}; 60 61 data["function"] = this.functionName; 62 63 if (this.tree.profileView.showSelfTimeAsPercent.get()) 64 data["self"] = WebInspector.UIString("%.2f%", this.selfPercent); 65 else 66 data["self"] = formatMilliseconds(this.selfTime); 67 68 if (this.tree.profileView.showTotalTimeAsPercent.get()) 69 data["total"] = WebInspector.UIString("%.2f%", this.totalPercent); 70 else 71 data["total"] = formatMilliseconds(this.totalTime); 72 73 return data; 74 }, 75 76 /** 77 * @override 78 * @param {string} columnIdentifier 79 * @return {!Element} 80 */ 81 createCell: function(columnIdentifier) 82 { 83 var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier); 84 85 if (columnIdentifier === "self" && this._searchMatchedSelfColumn) 86 cell.addStyleClass("highlight"); 87 else if (columnIdentifier === "total" && this._searchMatchedTotalColumn) 88 cell.addStyleClass("highlight"); 89 90 if (columnIdentifier !== "function") 91 return cell; 92 93 if (this.profileNode._searchMatchedFunctionColumn) 94 cell.addStyleClass("highlight"); 95 96 if (this.profileNode.url) { 97 // FIXME(62725): profileNode should reference a debugger location. 98 var lineNumber = this.profileNode.lineNumber ? this.profileNode.lineNumber - 1 : 0; 99 var urlElement = this.tree.profileView._linkifier.linkifyLocation(this.profileNode.url, lineNumber, 0, "profile-node-file"); 100 urlElement.style.maxWidth = "75%"; 101 cell.insertBefore(urlElement, cell.firstChild); 102 } 103 104 return cell; 105 }, 106 107 select: function(supressSelectedEvent) 108 { 109 WebInspector.DataGridNode.prototype.select.call(this, supressSelectedEvent); 110 this.tree.profileView._dataGridNodeSelected(this); 111 }, 112 113 deselect: function(supressDeselectedEvent) 114 { 115 WebInspector.DataGridNode.prototype.deselect.call(this, supressDeselectedEvent); 116 this.tree.profileView._dataGridNodeDeselected(this); 117 }, 118 119 /** 120 * @param {function(Object, Object)} comparator 121 * @param {boolean} force 122 */ 123 sort: function(comparator, force) 124 { 125 var gridNodeGroups = [[this]]; 126 127 for (var gridNodeGroupIndex = 0; gridNodeGroupIndex < gridNodeGroups.length; ++gridNodeGroupIndex) { 128 var gridNodes = gridNodeGroups[gridNodeGroupIndex]; 129 var count = gridNodes.length; 130 131 for (var index = 0; index < count; ++index) { 132 var gridNode = gridNodes[index]; 133 134 // If the grid node is collapsed, then don't sort children (save operation for later). 135 // If the grid node has the same sorting as previously, then there is no point in sorting it again. 136 if (!force && (!gridNode.expanded || gridNode.lastComparator === comparator)) { 137 if (gridNode.children.length) 138 gridNode.shouldRefreshChildren = true; 139 continue; 140 } 141 142 gridNode.lastComparator = comparator; 143 144 var children = gridNode.children; 145 var childCount = children.length; 146 147 if (childCount) { 148 children.sort(comparator); 149 150 for (var childIndex = 0; childIndex < childCount; ++childIndex) 151 children[childIndex]._recalculateSiblings(childIndex); 152 153 gridNodeGroups.push(children); 154 } 155 } 156 } 157 }, 158 159 /** 160 * @param {!WebInspector.ProfileDataGridNode} profileDataGridNode 161 * @param {number} index 162 */ 163 insertChild: function(profileDataGridNode, index) 164 { 165 WebInspector.DataGridNode.prototype.insertChild.call(this, profileDataGridNode, index); 166 167 this.childrenByCallUID[profileDataGridNode.callUID] = profileDataGridNode; 168 }, 169 170 /** 171 * @param {!WebInspector.ProfileDataGridNode} profileDataGridNode 172 */ 173 removeChild: function(profileDataGridNode) 174 { 175 WebInspector.DataGridNode.prototype.removeChild.call(this, profileDataGridNode); 176 177 delete this.childrenByCallUID[profileDataGridNode.callUID]; 178 }, 179 180 removeChildren: function() 181 { 182 WebInspector.DataGridNode.prototype.removeChildren.call(this); 183 184 this.childrenByCallUID = {}; 185 }, 186 187 /** 188 * @param {!WebInspector.ProfileDataGridNode} node 189 */ 190 findChild: function(node) 191 { 192 if (!node) 193 return null; 194 return this.childrenByCallUID[node.callUID]; 195 }, 196 197 get selfPercent() 198 { 199 return this.selfTime / this.tree.totalTime * 100.0; 200 }, 201 202 get totalPercent() 203 { 204 return this.totalTime / this.tree.totalTime * 100.0; 205 }, 206 207 get _parent() 208 { 209 return this.parent !== this.dataGrid ? this.parent : this.tree; 210 }, 211 212 populate: function() 213 { 214 if (this._populated) 215 return; 216 this._populated = true; 217 218 this._sharedPopulate(); 219 220 if (this._parent) { 221 var currentComparator = this._parent.lastComparator; 222 223 if (currentComparator) 224 this.sort(currentComparator, true); 225 } 226 }, 227 228 // When focusing and collapsing we modify lots of nodes in the tree. 229 // This allows us to restore them all to their original state when we revert. 230 _save: function() 231 { 232 if (this._savedChildren) 233 return; 234 235 this._savedSelfTime = this.selfTime; 236 this._savedTotalTime = this.totalTime; 237 238 this._savedChildren = this.children.slice(); 239 }, 240 241 // When focusing and collapsing we modify lots of nodes in the tree. 242 // This allows us to restore them all to their original state when we revert. 243 _restore: function() 244 { 245 if (!this._savedChildren) 246 return; 247 248 this.selfTime = this._savedSelfTime; 249 this.totalTime = this._savedTotalTime; 250 251 this.removeChildren(); 252 253 var children = this._savedChildren; 254 var count = children.length; 255 256 for (var index = 0; index < count; ++index) { 257 children[index]._restore(); 258 this.appendChild(children[index]); 259 } 260 }, 261 262 _merge: function(child, shouldAbsorb) 263 { 264 this.selfTime += child.selfTime; 265 266 if (!shouldAbsorb) 267 this.totalTime += child.totalTime; 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 __proto__: WebInspector.DataGridNode.prototype 295 } 296 297 /** 298 * @constructor 299 * @param {WebInspector.CPUProfileView} profileView 300 * @param {ProfilerAgent.CPUProfileNode} rootProfileNode 301 */ 302 WebInspector.ProfileDataGridTree = function(profileView, rootProfileNode) 303 { 304 this.tree = this; 305 this.children = []; 306 307 this.profileView = profileView; 308 309 this.totalTime = rootProfileNode.totalTime; 310 this.lastComparator = null; 311 312 this.childrenByCallUID = {}; 313 } 314 315 WebInspector.ProfileDataGridTree.prototype = { 316 get expanded() 317 { 318 return true; 319 }, 320 321 appendChild: function(child) 322 { 323 this.insertChild(child, this.children.length); 324 }, 325 326 insertChild: function(child, index) 327 { 328 this.children.splice(index, 0, child); 329 this.childrenByCallUID[child.callUID] = child; 330 }, 331 332 removeChildren: function() 333 { 334 this.children = []; 335 this.childrenByCallUID = {}; 336 }, 337 338 findChild: WebInspector.ProfileDataGridNode.prototype.findChild, 339 sort: WebInspector.ProfileDataGridNode.prototype.sort, 340 341 _save: function() 342 { 343 if (this._savedChildren) 344 return; 345 346 this._savedTotalTime = this.totalTime; 347 this._savedChildren = this.children.slice(); 348 }, 349 350 restore: function() 351 { 352 if (!this._savedChildren) 353 return; 354 355 this.children = this._savedChildren; 356 this.totalTime = this._savedTotalTime; 357 358 var children = this.children; 359 var count = children.length; 360 361 for (var index = 0; index < count; ++index) 362 children[index]._restore(); 363 364 this._savedChildren = null; 365 } 366 } 367 368 WebInspector.ProfileDataGridTree.propertyComparators = [{}, {}]; 369 370 /** 371 * @param {string} property 372 * @param {boolean} isAscending 373 * @return {function(Object, Object)} 374 */ 375 WebInspector.ProfileDataGridTree.propertyComparator = function(property, isAscending) 376 { 377 var comparator = WebInspector.ProfileDataGridTree.propertyComparators[(isAscending ? 1 : 0)][property]; 378 379 if (!comparator) { 380 if (isAscending) { 381 comparator = function(lhs, rhs) 382 { 383 if (lhs[property] < rhs[property]) 384 return -1; 385 386 if (lhs[property] > rhs[property]) 387 return 1; 388 389 return 0; 390 } 391 } else { 392 comparator = function(lhs, rhs) 393 { 394 if (lhs[property] > rhs[property]) 395 return -1; 396 397 if (lhs[property] < rhs[property]) 398 return 1; 399 400 return 0; 401 } 402 } 403 404 WebInspector.ProfileDataGridTree.propertyComparators[(isAscending ? 1 : 0)][property] = comparator; 405 } 406 407 return comparator; 408 } 409