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.LegacyProfileDataGridNode = function(profileNode, owningTree, hasChildren, showTimeAsPercent) 27{ 28 this.profileNode = profileNode; 29 30 WebInspector.DataGridNode.call(this, null, hasChildren); 31 32 this.addEventListener("populate", this._populate, this); 33 34 this.tree = owningTree; 35 36 this.childrenByCallUID = {}; 37 this.lastComparator = null; 38 39 this.callUID = profileNode.callUID; 40 this.selfTime = profileNode.selfTime; 41 this.totalTime = profileNode.totalTime; 42 this.functionName = profileNode.functionName; 43 this.numberOfCalls = profileNode.numberOfCalls; 44 this.url = profileNode.url; 45 46 this._showTimeAsPercent = showTimeAsPercent || false; 47} 48 49WebInspector.LegacyProfileDataGridNode.prototype = { 50 get data() 51 { 52 function formatMilliseconds(time) 53 { 54 return Number.secondsToString(time / 1000, true); 55 } 56 57 var data = {}; 58 59 data["function"] = this.functionName; 60 data["calls"] = this.numberOfCalls; 61 62 if (this._showTimeAsPercent) { 63 data["self"] = WebInspector.UIString("%.2f %%").format(this.selfPercent); 64 data["total"] = WebInspector.UIString("%.2f %%").format(this.totalPercent); 65 data["average"] = WebInspector.UIString("%.2f %%").format(this.averagePercent); 66 } else { 67 data["self"] = formatMilliseconds(this.selfTime); 68 data["total"] = formatMilliseconds(this.totalTime); 69 data["average"] = formatMilliseconds(this.averageTime); 70 } 71 72 return data; 73 }, 74 75 get showTimeAsPercent() 76 { 77 return this._showTimeAsPercent; 78 }, 79 80 refresh: function(showTimeAsPercent) 81 { 82 this._showTimeAsPercent = showTimeAsPercent; 83 84 WebInspector.DataGridNode.prototype.refresh.call(this); 85 }, 86 87 createCell: function(columnIdentifier) 88 { 89 var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier); 90 91 if (columnIdentifier === "self" && this._searchMatchedSelfColumn) 92 cell.classList.add("highlight"); 93 else if (columnIdentifier === "total" && this._searchMatchedTotalColumn) 94 cell.classList.add("highlight"); 95 else if (columnIdentifier === "average" && this._searchMatchedAverageColumn) 96 cell.classList.add("highlight"); 97 else if (columnIdentifier === "calls" && this._searchMatchedCallsColumn) 98 cell.classList.add("highlight"); 99 100 if (columnIdentifier !== "function") 101 return cell; 102 103 if (this.profileNode._searchMatchedFunctionColumn) 104 cell.classList.add("highlight"); 105 106 if (this.profileNode.url) { 107 // FIXME(62725): profileNode should reference a debugger location. 108 var lineNumber = this.profileNode.lineNumber ? this.profileNode.lineNumber - 1 : 0; 109 var urlElement = this._linkifyLocation(this.profileNode.url, lineNumber, "profile-node-file"); 110 urlElement.style.maxWidth = "75%"; 111 cell.insertBefore(urlElement, cell.firstChild); 112 } 113 114 return cell; 115 }, 116 117 sort: function(comparator, force) 118 { 119 var gridNodeGroups = [[this]]; 120 121 for (var gridNodeGroupIndex = 0; gridNodeGroupIndex < gridNodeGroups.length; ++gridNodeGroupIndex) { 122 var gridNodes = gridNodeGroups[gridNodeGroupIndex]; 123 var count = gridNodes.length; 124 125 for (var index = 0; index < count; ++index) { 126 var gridNode = gridNodes[index]; 127 128 // If the grid node is collapsed, then don't sort children (save operation for later). 129 // If the grid node has the same sorting as previously, then there is no point in sorting it again. 130 if (!force && (!gridNode.expanded || gridNode.lastComparator === comparator)) { 131 if (gridNode.children.length) 132 gridNode.shouldRefreshChildren = true; 133 continue; 134 } 135 136 gridNode.lastComparator = comparator; 137 138 var children = gridNode.children; 139 var childCount = children.length; 140 141 if (childCount) { 142 children.sort(comparator); 143 144 for (var childIndex = 0; childIndex < childCount; ++childIndex) 145 children[childIndex]._recalculateSiblings(childIndex); 146 147 gridNodeGroups.push(children); 148 } 149 } 150 } 151 }, 152 153 insertChild: function(profileDataGridNode, index) 154 { 155 WebInspector.DataGridNode.prototype.insertChild.call(this, profileDataGridNode, index); 156 157 this.childrenByCallUID[profileDataGridNode.callUID] = profileDataGridNode; 158 }, 159 160 removeChild: function(profileDataGridNode) 161 { 162 WebInspector.DataGridNode.prototype.removeChild.call(this, profileDataGridNode); 163 164 delete this.childrenByCallUID[profileDataGridNode.callUID]; 165 }, 166 167 removeChildren: function(profileDataGridNode) 168 { 169 WebInspector.DataGridNode.prototype.removeChildren.call(this); 170 171 this.childrenByCallUID = {}; 172 }, 173 174 findChild: function(node) 175 { 176 if (!node) 177 return null; 178 return this.childrenByCallUID[node.callUID]; 179 }, 180 181 get averageTime() 182 { 183 return this.selfTime / Math.max(1, this.numberOfCalls); 184 }, 185 186 get averagePercent() 187 { 188 return this.averageTime / this.tree.totalTime * 100.0; 189 }, 190 191 get selfPercent() 192 { 193 return this.selfTime / this.tree.totalTime * 100.0; 194 }, 195 196 get totalPercent() 197 { 198 return this.totalTime / this.tree.totalTime * 100.0; 199 }, 200 201 get _parent() 202 { 203 return this.parent !== this.dataGrid ? this.parent : this.tree; 204 }, 205 206 _populate: function(event) 207 { 208 this._sharedPopulate(); 209 210 if (this._parent) { 211 var currentComparator = this._parent.lastComparator; 212 213 if (currentComparator) 214 this.sort(currentComparator, true); 215 } 216 217 if (this.removeEventListener) 218 this.removeEventListener("populate", this._populate, this); 219 }, 220 221 // When focusing and collapsing we modify lots of nodes in the tree. 222 // This allows us to restore them all to their original state when we revert. 223 _save: function() 224 { 225 if (this._savedChildren) 226 return; 227 228 this._savedSelfTime = this.selfTime; 229 this._savedTotalTime = this.totalTime; 230 this._savedNumberOfCalls = this.numberOfCalls; 231 232 this._savedChildren = this.children.slice(); 233 }, 234 235 // When focusing and collapsing we modify lots of nodes in the tree. 236 // This allows us to restore them all to their original state when we revert. 237 _restore: function() 238 { 239 if (!this._savedChildren) 240 return; 241 242 this.selfTime = this._savedSelfTime; 243 this.totalTime = this._savedTotalTime; 244 this.numberOfCalls = this._savedNumberOfCalls; 245 246 this.removeChildren(); 247 248 var children = this._savedChildren; 249 var count = children.length; 250 251 for (var index = 0; index < count; ++index) { 252 children[index]._restore(); 253 this.appendChild(children[index]); 254 } 255 }, 256 257 _merge: function(child, shouldAbsorb) 258 { 259 this.selfTime += child.selfTime; 260 261 if (!shouldAbsorb) { 262 this.totalTime += child.totalTime; 263 this.numberOfCalls += child.numberOfCalls; 264 } 265 266 var children = this.children.slice(); 267 268 this.removeChildren(); 269 270 var count = children.length; 271 272 for (var index = 0; index < count; ++index) { 273 if (!shouldAbsorb || children[index] !== child) 274 this.appendChild(children[index]); 275 } 276 277 children = child.children.slice(); 278 count = children.length; 279 280 for (var index = 0; index < count; ++index) { 281 var orphanedChild = children[index], 282 existingChild = this.childrenByCallUID[orphanedChild.callUID]; 283 284 if (existingChild) 285 existingChild._merge(orphanedChild, false); 286 else 287 this.appendChild(orphanedChild); 288 } 289 }, 290 291 _linkifyLocation: function(url, lineNumber, className) 292 { 293 // FIXME: CPUProfileNode's need columnNumbers. 294 return WebInspector.linkifyLocation(url, lineNumber, 0, className); 295 } 296} 297 298WebInspector.LegacyProfileDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype; 299 300WebInspector.LegacyProfileDataGridTree = function(profileNode) 301{ 302 this.tree = this; 303 this.children = []; 304 305 this.totalTime = profileNode.totalTime; 306 this.lastComparator = null; 307 308 this.childrenByCallUID = {}; 309} 310 311WebInspector.LegacyProfileDataGridTree.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.LegacyProfileDataGridNode.prototype.findChild, 335 sort: WebInspector.LegacyProfileDataGridNode.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 364WebInspector.LegacyProfileDataGridTree.propertyComparators = [{}, {}]; 365 366WebInspector.LegacyProfileDataGridTree.propertyComparator = function(property, 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