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