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