1 /* 2 * Copyright (C) 2013 Google Inc. All rights reserved. 3 * Copyright (C) 2012 Intel Inc. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * * Redistributions in binary form must reproduce the above 12 * copyright notice, this list of conditions and the following disclaimer 13 * in the documentation and/or other materials provided with the 14 * distribution. 15 * * Neither the name of Google Inc. nor the names of its 16 * contributors may be used to endorse or promote products derived from 17 * this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 /** 33 * @constructor 34 * @extends {WebInspector.Object} 35 * @param {!WebInspector.TimelineModel} model 36 * @param {!WebInspector.TimelineUIUtils} uiUtils 37 */ 38 WebInspector.TimelinePresentationModel = function(model, uiUtils) 39 { 40 this._model = model; 41 this._uiUtils = uiUtils; 42 this._filters = []; 43 /** 44 * @type {!Map.<!WebInspector.TimelineModel.Record, !WebInspector.TimelinePresentationModel.Record>} 45 */ 46 this._recordToPresentationRecord = new Map(); 47 this.reset(); 48 } 49 50 WebInspector.TimelinePresentationModel.prototype = { 51 /** 52 * @param {number} startTime 53 * @param {number} endTime 54 */ 55 setWindowTimes: function(startTime, endTime) 56 { 57 this._windowStartTime = startTime; 58 this._windowEndTime = endTime; 59 }, 60 61 /** 62 * @param {?WebInspector.TimelineModel.Record} record 63 * @return {?WebInspector.TimelinePresentationModel.Record} 64 */ 65 toPresentationRecord: function(record) 66 { 67 return record ? this._recordToPresentationRecord.get(record) || null : null; 68 }, 69 70 /** 71 * @return {!WebInspector.TimelinePresentationModel.Record} 72 */ 73 rootRecord: function() 74 { 75 return this._rootRecord; 76 }, 77 78 reset: function() 79 { 80 this._recordToPresentationRecord.clear(); 81 this._rootRecord = new WebInspector.TimelinePresentationModel.RootRecord(); 82 /** @type {!Object.<string, !WebInspector.TimelinePresentationModel.Record>} */ 83 this._coalescingBuckets = {}; 84 }, 85 86 /** 87 * @param {!WebInspector.TimelineModel.Record} record 88 */ 89 addRecord: function(record) 90 { 91 if (this._uiUtils.isProgram(record)) { 92 var records = record.children(); 93 for (var i = 0; i < records.length; ++i) 94 this._innerAddRecord(this._rootRecord, records[i]); 95 } else { 96 this._innerAddRecord(this._rootRecord, record); 97 } 98 }, 99 100 /** 101 * @param {!WebInspector.TimelinePresentationModel.Record} parentRecord 102 * @param {!WebInspector.TimelineModel.Record} record 103 */ 104 _innerAddRecord: function(parentRecord, record) 105 { 106 var coalescingBucket; 107 108 // On main thread, only coalesce if the last event is of same type. 109 if (parentRecord === this._rootRecord) 110 coalescingBucket = record.thread() ? record.type() : "mainThread"; 111 var coalescedRecord = this._findCoalescedParent(record, parentRecord, coalescingBucket); 112 if (coalescedRecord) 113 parentRecord = coalescedRecord; 114 115 var formattedRecord = new WebInspector.TimelinePresentationModel.ActualRecord(record, parentRecord); 116 this._recordToPresentationRecord.put(record, formattedRecord); 117 118 formattedRecord._collapsed = parentRecord === this._rootRecord; 119 if (coalescingBucket) 120 this._coalescingBuckets[coalescingBucket] = formattedRecord; 121 122 for (var i = 0; record.children() && i < record.children().length; ++i) 123 this._innerAddRecord(formattedRecord, record.children()[i]); 124 125 if (parentRecord.coalesced()) 126 this._updateCoalescingParent(formattedRecord); 127 }, 128 129 /** 130 * @param {!WebInspector.TimelineModel.Record} record 131 * @param {!WebInspector.TimelinePresentationModel.Record} newParent 132 * @param {string=} bucket 133 * @return {?WebInspector.TimelinePresentationModel.Record} 134 */ 135 _findCoalescedParent: function(record, newParent, bucket) 136 { 137 const coalescingThresholdMillis = 5; 138 139 var lastRecord = bucket ? this._coalescingBuckets[bucket] : newParent._presentationChildren.peekLast(); 140 if (lastRecord && lastRecord.coalesced()) 141 lastRecord = lastRecord._presentationChildren.peekLast(); 142 var startTime = record.startTime(); 143 var endTime = record.endTime(); 144 if (!lastRecord) 145 return null; 146 if (lastRecord.record().type() !== record.type()) 147 return null; 148 if (!this._uiUtils.isCoalescable(record.type())) 149 return null; 150 if (lastRecord.record().endTime() + coalescingThresholdMillis < startTime) 151 return null; 152 if (endTime + coalescingThresholdMillis < lastRecord.record().startTime()) 153 return null; 154 if (lastRecord.presentationParent().coalesced()) 155 return lastRecord.presentationParent(); 156 return this._replaceWithCoalescedRecord(lastRecord); 157 }, 158 159 /** 160 * @param {!WebInspector.TimelinePresentationModel.Record} presentationRecord 161 * @return {!WebInspector.TimelinePresentationModel.Record} 162 */ 163 _replaceWithCoalescedRecord: function(presentationRecord) 164 { 165 var record = presentationRecord.record(); 166 var parent = presentationRecord._presentationParent; 167 var coalescedRecord = new WebInspector.TimelinePresentationModel.CoalescedRecord(record); 168 169 coalescedRecord._collapsed = true; 170 coalescedRecord._presentationChildren.push(presentationRecord); 171 presentationRecord._presentationParent = coalescedRecord; 172 if (presentationRecord.hasWarnings() || presentationRecord.childHasWarnings()) 173 coalescedRecord._childHasWarnings = true; 174 175 coalescedRecord._presentationParent = parent; 176 parent._presentationChildren[parent._presentationChildren.indexOf(presentationRecord)] = coalescedRecord; 177 WebInspector.TimelineUIUtils.aggregateTimeByCategory(coalescedRecord.presentationAggregatedStats(), presentationRecord.presentationAggregatedStats()); 178 179 return coalescedRecord; 180 }, 181 182 /** 183 * @param {!WebInspector.TimelinePresentationModel.Record} presentationRecord 184 */ 185 _updateCoalescingParent: function(presentationRecord) 186 { 187 var parentRecord = presentationRecord._presentationParent; 188 WebInspector.TimelineUIUtils.aggregateTimeByCategory(parentRecord.presentationAggregatedStats(), presentationRecord.presentationAggregatedStats()); 189 if (parentRecord.endTime() < presentationRecord.endTime()) 190 parentRecord._endTime = presentationRecord.endTime(); 191 }, 192 193 /** 194 * @param {?RegExp} textFilter 195 */ 196 setTextFilter: function(textFilter) 197 { 198 this._textFilter = textFilter; 199 }, 200 201 invalidateFilteredRecords: function() 202 { 203 delete this._filteredRecords; 204 }, 205 206 /** 207 * @return {!Array.<!WebInspector.TimelinePresentationModel.Record>} 208 */ 209 filteredRecords: function() 210 { 211 if (this._filteredRecords) 212 return this._filteredRecords; 213 214 var recordsInWindow = []; 215 216 var stack = [{children: this._rootRecord._presentationChildren, index: 0, parentIsCollapsed: false, parentRecord: {}}]; 217 var revealedDepth = 0; 218 219 function revealRecordsInStack() { 220 for (var depth = revealedDepth + 1; depth < stack.length; ++depth) { 221 if (stack[depth - 1].parentIsCollapsed) { 222 stack[depth].parentRecord._presentationParent._expandable = true; 223 return; 224 } 225 stack[depth - 1].parentRecord._collapsed = false; 226 recordsInWindow.push(stack[depth].parentRecord); 227 stack[depth].windowLengthBeforeChildrenTraversal = recordsInWindow.length; 228 stack[depth].parentIsRevealed = true; 229 revealedDepth = depth; 230 } 231 } 232 233 while (stack.length) { 234 var entry = stack[stack.length - 1]; 235 var records = entry.children; 236 if (records && entry.index < records.length) { 237 var record = records[entry.index]; 238 ++entry.index; 239 if (record.startTime() < this._windowEndTime && record.endTime() > this._windowStartTime) { 240 if (this._model.isVisible(record.record())) { 241 record._presentationParent._expandable = true; 242 if (this._textFilter) 243 revealRecordsInStack(); 244 if (!entry.parentIsCollapsed) { 245 recordsInWindow.push(record); 246 revealedDepth = stack.length; 247 entry.parentRecord._collapsed = false; 248 } 249 } 250 } 251 252 record._expandable = false; 253 254 stack.push({children: record._presentationChildren, 255 index: 0, 256 parentIsCollapsed: entry.parentIsCollapsed || (record._collapsed && (!this._textFilter || record._expandedOrCollapsedWhileFiltered)), 257 parentRecord: record, 258 windowLengthBeforeChildrenTraversal: recordsInWindow.length}); 259 } else { 260 stack.pop(); 261 revealedDepth = Math.min(revealedDepth, stack.length - 1); 262 entry.parentRecord._visibleChildrenCount = recordsInWindow.length - entry.windowLengthBeforeChildrenTraversal; 263 } 264 } 265 266 this._filteredRecords = recordsInWindow; 267 return recordsInWindow; 268 }, 269 270 __proto__: WebInspector.Object.prototype 271 } 272 273 /** 274 * @constructor 275 * @param {?WebInspector.TimelinePresentationModel.Record} parentRecord 276 */ 277 WebInspector.TimelinePresentationModel.Record = function(parentRecord) 278 { 279 /** 280 * @type {!Array.<!WebInspector.TimelinePresentationModel.Record>} 281 */ 282 this._presentationChildren = []; 283 284 if (parentRecord) { 285 this._presentationParent = parentRecord; 286 parentRecord._presentationChildren.push(this); 287 } 288 } 289 290 WebInspector.TimelinePresentationModel.Record.prototype = { 291 /** 292 * @return {number} 293 */ 294 startTime: function() 295 { 296 throw new Error("Not implemented."); 297 }, 298 299 /** 300 * @return {number} 301 */ 302 endTime: function() 303 { 304 throw new Error("Not implemented."); 305 }, 306 307 /** 308 * @return {number} 309 */ 310 selfTime: function() 311 { 312 throw new Error("Not implemented."); 313 }, 314 315 /** 316 * @return {!WebInspector.TimelineModel.Record} 317 */ 318 record: function() 319 { 320 throw new Error("Not implemented."); 321 }, 322 323 /** 324 * @return {!Object.<string, number>} 325 */ 326 presentationAggregatedStats: function() 327 { 328 throw new Error("Not implemented."); 329 }, 330 331 /** 332 * @return {!Array.<!WebInspector.TimelinePresentationModel.Record>} 333 */ 334 presentationChildren: function() 335 { 336 return this._presentationChildren; 337 }, 338 339 /** 340 * @return {boolean} 341 */ 342 coalesced: function() 343 { 344 return false; 345 }, 346 347 /** 348 * @return {boolean} 349 */ 350 collapsed: function() 351 { 352 return this._collapsed; 353 }, 354 355 /** 356 * @param {boolean} collapsed 357 */ 358 setCollapsed: function(collapsed) 359 { 360 this._collapsed = collapsed; 361 this._expandedOrCollapsedWhileFiltered = true; 362 }, 363 364 /** 365 * @return {?WebInspector.TimelinePresentationModel.Record} 366 */ 367 presentationParent: function() 368 { 369 return this._presentationParent || null; 370 }, 371 372 /** 373 * @return {number} 374 */ 375 visibleChildrenCount: function() 376 { 377 return this._visibleChildrenCount || 0; 378 }, 379 380 /** 381 * @return {boolean} 382 */ 383 expandable: function() 384 { 385 return !!this._expandable; 386 }, 387 388 /** 389 * @return {boolean} 390 */ 391 hasWarnings: function() 392 { 393 return false; 394 }, 395 396 /** 397 * @return {boolean} 398 */ 399 childHasWarnings: function() 400 { 401 return this._childHasWarnings; 402 }, 403 404 /** 405 * @return {?WebInspector.TimelineRecordListRow} 406 */ 407 listRow: function() 408 { 409 return this._listRow; 410 }, 411 412 /** 413 * @param {!WebInspector.TimelineRecordListRow} listRow 414 */ 415 setListRow: function(listRow) 416 { 417 this._listRow = listRow; 418 }, 419 420 /** 421 * @return {?WebInspector.TimelineRecordGraphRow} 422 */ 423 graphRow: function() 424 { 425 return this._graphRow; 426 }, 427 428 /** 429 * @param {!WebInspector.TimelineRecordGraphRow} graphRow 430 */ 431 setGraphRow: function(graphRow) 432 { 433 this._graphRow = graphRow; 434 } 435 } 436 437 /** 438 * @constructor 439 * @extends {WebInspector.TimelinePresentationModel.Record} 440 * @param {!WebInspector.TimelineModel.Record} record 441 * @param {?WebInspector.TimelinePresentationModel.Record} parentRecord 442 */ 443 WebInspector.TimelinePresentationModel.ActualRecord = function(record, parentRecord) 444 { 445 WebInspector.TimelinePresentationModel.Record.call(this, parentRecord); 446 this._record = record; 447 448 if (this.hasWarnings()) { 449 for (var parent = this._presentationParent; parent && !parent._childHasWarnings; parent = parent._presentationParent) 450 parent._childHasWarnings = true; 451 } 452 } 453 454 WebInspector.TimelinePresentationModel.ActualRecord.prototype = { 455 /** 456 * @return {number} 457 */ 458 startTime: function() 459 { 460 return this._record.startTime(); 461 }, 462 463 /** 464 * @return {number} 465 */ 466 endTime: function() 467 { 468 return this._record.endTime(); 469 }, 470 471 /** 472 * @return {number} 473 */ 474 selfTime: function() 475 { 476 return this._record.selfTime(); 477 }, 478 479 /** 480 * @return {!WebInspector.TimelineModel.Record} 481 */ 482 record: function() 483 { 484 return this._record; 485 }, 486 487 /** 488 * @return {!Object.<string, number>} 489 */ 490 presentationAggregatedStats: function() 491 { 492 return this._record.aggregatedStats(); 493 }, 494 495 /** 496 * @return {boolean} 497 */ 498 hasWarnings: function() 499 { 500 return !!this._record.warnings(); 501 }, 502 503 __proto__: WebInspector.TimelinePresentationModel.Record.prototype 504 } 505 506 /** 507 * @constructor 508 * @extends {WebInspector.TimelinePresentationModel.Record} 509 * @param {!WebInspector.TimelineModel.Record} record 510 */ 511 WebInspector.TimelinePresentationModel.CoalescedRecord = function(record) 512 { 513 WebInspector.TimelinePresentationModel.Record.call(this, null); 514 this._startTime = record.startTime(); 515 this._endTime = record.endTime(); 516 this._aggregatedStats = {}; 517 } 518 519 WebInspector.TimelinePresentationModel.CoalescedRecord.prototype = { 520 /** 521 * @return {number} 522 */ 523 startTime: function() 524 { 525 return this._startTime; 526 }, 527 528 /** 529 * @return {number} 530 */ 531 endTime: function() 532 { 533 return this._endTime; 534 }, 535 536 /** 537 * @return {number} 538 */ 539 selfTime: function() 540 { 541 return 0; 542 }, 543 544 /** 545 * @return {!WebInspector.TimelineModel.Record} 546 */ 547 record: function() 548 { 549 return this._presentationChildren[0].record(); 550 }, 551 552 /** 553 * @return {!Object.<string, number>} 554 */ 555 presentationAggregatedStats: function() 556 { 557 return this._aggregatedStats; 558 }, 559 560 /** 561 * @return {boolean} 562 */ 563 coalesced: function() 564 { 565 return true; 566 }, 567 568 /** 569 * @return {boolean} 570 */ 571 hasWarnings: function() 572 { 573 return false; 574 }, 575 576 __proto__: WebInspector.TimelinePresentationModel.Record.prototype 577 } 578 579 /** 580 * @constructor 581 * @extends {WebInspector.TimelinePresentationModel.Record} 582 */ 583 WebInspector.TimelinePresentationModel.RootRecord = function() 584 { 585 WebInspector.TimelinePresentationModel.Record.call(this, null); 586 this._aggregatedStats = {}; 587 } 588 589 WebInspector.TimelinePresentationModel.RootRecord.prototype = { 590 /** 591 * @return {!Object.<string, number>} 592 */ 593 presentationAggregatedStats: function() 594 { 595 return this._aggregatedStats; 596 }, 597 598 /** 599 * @return {boolean} 600 */ 601 hasWarnings: function() 602 { 603 return false; 604 }, 605 606 __proto__: WebInspector.TimelinePresentationModel.Record.prototype 607 } 608