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.set(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 178 return coalescedRecord; 179 }, 180 181 /** 182 * @param {!WebInspector.TimelinePresentationModel.Record} presentationRecord 183 */ 184 _updateCoalescingParent: function(presentationRecord) 185 { 186 var parentRecord = presentationRecord._presentationParent; 187 if (parentRecord.endTime() < presentationRecord.endTime()) 188 parentRecord._endTime = presentationRecord.endTime(); 189 }, 190 191 /** 192 * @param {?RegExp} textFilter 193 */ 194 setTextFilter: function(textFilter) 195 { 196 var records = this._recordToPresentationRecord.values(); 197 for (var i = 0; i < records.length; ++i) 198 records[i]._expandedOrCollapsedWhileFiltered = false; 199 this._textFilter = textFilter; 200 }, 201 202 refreshRecords: function() 203 { 204 this.reset(); 205 var modelRecords = this._model.records(); 206 for (var i = 0; i < modelRecords.length; ++i) 207 this.addRecord(modelRecords[i]); 208 }, 209 210 invalidateFilteredRecords: function() 211 { 212 delete this._filteredRecords; 213 }, 214 215 /** 216 * @return {!Array.<!WebInspector.TimelinePresentationModel.Record>} 217 */ 218 filteredRecords: function() 219 { 220 if (this._filteredRecords) 221 return this._filteredRecords; 222 223 var recordsInWindow = []; 224 225 var stack = [{children: this._rootRecord._presentationChildren, index: 0, parentIsCollapsed: false, parentRecord: {}}]; 226 var revealedDepth = 0; 227 228 function revealRecordsInStack() { 229 for (var depth = revealedDepth + 1; depth < stack.length; ++depth) { 230 if (stack[depth - 1].parentIsCollapsed) { 231 stack[depth].parentRecord._presentationParent._expandable = true; 232 return; 233 } 234 stack[depth - 1].parentRecord._collapsed = false; 235 recordsInWindow.push(stack[depth].parentRecord); 236 stack[depth].windowLengthBeforeChildrenTraversal = recordsInWindow.length; 237 stack[depth].parentIsRevealed = true; 238 revealedDepth = depth; 239 } 240 } 241 242 while (stack.length) { 243 var entry = stack[stack.length - 1]; 244 var records = entry.children; 245 if (records && entry.index < records.length) { 246 var record = records[entry.index]; 247 ++entry.index; 248 if (record.startTime() < this._windowEndTime && record.endTime() > this._windowStartTime) { 249 if (this._model.isVisible(record.record())) { 250 record._presentationParent._expandable = true; 251 if (this._textFilter) 252 revealRecordsInStack(); 253 if (!entry.parentIsCollapsed) { 254 recordsInWindow.push(record); 255 revealedDepth = stack.length; 256 entry.parentRecord._collapsed = false; 257 } 258 } 259 } 260 261 record._expandable = false; 262 263 stack.push({children: record._presentationChildren, 264 index: 0, 265 parentIsCollapsed: entry.parentIsCollapsed || (record._collapsed && (!this._textFilter || record._expandedOrCollapsedWhileFiltered)), 266 parentRecord: record, 267 windowLengthBeforeChildrenTraversal: recordsInWindow.length}); 268 } else { 269 stack.pop(); 270 revealedDepth = Math.min(revealedDepth, stack.length - 1); 271 entry.parentRecord._visibleChildrenCount = recordsInWindow.length - entry.windowLengthBeforeChildrenTraversal; 272 } 273 } 274 275 this._filteredRecords = recordsInWindow; 276 return recordsInWindow; 277 }, 278 279 __proto__: WebInspector.Object.prototype 280 } 281 282 /** 283 * @constructor 284 * @param {?WebInspector.TimelinePresentationModel.Record} parentRecord 285 */ 286 WebInspector.TimelinePresentationModel.Record = function(parentRecord) 287 { 288 /** 289 * @type {!Array.<!WebInspector.TimelinePresentationModel.Record>} 290 */ 291 this._presentationChildren = []; 292 293 if (parentRecord) { 294 this._presentationParent = parentRecord; 295 parentRecord._presentationChildren.push(this); 296 } 297 } 298 299 WebInspector.TimelinePresentationModel.Record.prototype = { 300 /** 301 * @return {number} 302 */ 303 startTime: function() 304 { 305 throw new Error("Not implemented."); 306 }, 307 308 /** 309 * @return {number} 310 */ 311 endTime: function() 312 { 313 throw new Error("Not implemented."); 314 }, 315 316 /** 317 * @return {number} 318 */ 319 selfTime: function() 320 { 321 throw new Error("Not implemented."); 322 }, 323 324 /** 325 * @return {!WebInspector.TimelineModel.Record} 326 */ 327 record: function() 328 { 329 throw new Error("Not implemented."); 330 }, 331 332 /** 333 * @return {!Array.<!WebInspector.TimelinePresentationModel.Record>} 334 */ 335 presentationChildren: function() 336 { 337 return this._presentationChildren; 338 }, 339 340 /** 341 * @return {boolean} 342 */ 343 coalesced: function() 344 { 345 return false; 346 }, 347 348 /** 349 * @return {boolean} 350 */ 351 collapsed: function() 352 { 353 return this._collapsed; 354 }, 355 356 /** 357 * @param {boolean} collapsed 358 */ 359 setCollapsed: function(collapsed) 360 { 361 this._collapsed = collapsed; 362 this._expandedOrCollapsedWhileFiltered = true; 363 }, 364 365 /** 366 * @return {?WebInspector.TimelinePresentationModel.Record} 367 */ 368 presentationParent: function() 369 { 370 return this._presentationParent || null; 371 }, 372 373 /** 374 * @return {number} 375 */ 376 visibleChildrenCount: function() 377 { 378 return this._visibleChildrenCount || 0; 379 }, 380 381 /** 382 * @return {boolean} 383 */ 384 expandable: function() 385 { 386 return !!this._expandable; 387 }, 388 389 /** 390 * @return {boolean} 391 */ 392 hasWarnings: function() 393 { 394 return false; 395 }, 396 397 /** 398 * @return {boolean} 399 */ 400 childHasWarnings: function() 401 { 402 return this._childHasWarnings; 403 }, 404 405 /** 406 * @return {?WebInspector.TimelineRecordListRow} 407 */ 408 listRow: function() 409 { 410 return this._listRow; 411 }, 412 413 /** 414 * @param {!WebInspector.TimelineRecordListRow} listRow 415 */ 416 setListRow: function(listRow) 417 { 418 this._listRow = listRow; 419 }, 420 421 /** 422 * @return {?WebInspector.TimelineRecordGraphRow} 423 */ 424 graphRow: function() 425 { 426 return this._graphRow; 427 }, 428 429 /** 430 * @param {!WebInspector.TimelineRecordGraphRow} graphRow 431 */ 432 setGraphRow: function(graphRow) 433 { 434 this._graphRow = graphRow; 435 } 436 } 437 438 /** 439 * @constructor 440 * @extends {WebInspector.TimelinePresentationModel.Record} 441 * @param {!WebInspector.TimelineModel.Record} record 442 * @param {?WebInspector.TimelinePresentationModel.Record} parentRecord 443 */ 444 WebInspector.TimelinePresentationModel.ActualRecord = function(record, parentRecord) 445 { 446 WebInspector.TimelinePresentationModel.Record.call(this, parentRecord); 447 this._record = record; 448 449 if (this.hasWarnings()) { 450 for (var parent = this._presentationParent; parent && !parent._childHasWarnings; parent = parent._presentationParent) 451 parent._childHasWarnings = true; 452 } 453 } 454 455 WebInspector.TimelinePresentationModel.ActualRecord.prototype = { 456 /** 457 * @return {number} 458 */ 459 startTime: function() 460 { 461 return this._record.startTime(); 462 }, 463 464 /** 465 * @return {number} 466 */ 467 endTime: function() 468 { 469 return this._record.endTime(); 470 }, 471 472 /** 473 * @return {number} 474 */ 475 selfTime: function() 476 { 477 return this._record.selfTime(); 478 }, 479 480 /** 481 * @return {!WebInspector.TimelineModel.Record} 482 */ 483 record: function() 484 { 485 return this._record; 486 }, 487 488 /** 489 * @return {boolean} 490 */ 491 hasWarnings: function() 492 { 493 return !!this._record.warnings(); 494 }, 495 496 __proto__: WebInspector.TimelinePresentationModel.Record.prototype 497 } 498 499 /** 500 * @constructor 501 * @extends {WebInspector.TimelinePresentationModel.Record} 502 * @param {!WebInspector.TimelineModel.Record} record 503 */ 504 WebInspector.TimelinePresentationModel.CoalescedRecord = function(record) 505 { 506 WebInspector.TimelinePresentationModel.Record.call(this, null); 507 this._startTime = record.startTime(); 508 this._endTime = record.endTime(); 509 } 510 511 WebInspector.TimelinePresentationModel.CoalescedRecord.prototype = { 512 /** 513 * @return {number} 514 */ 515 startTime: function() 516 { 517 return this._startTime; 518 }, 519 520 /** 521 * @return {number} 522 */ 523 endTime: function() 524 { 525 return this._endTime; 526 }, 527 528 /** 529 * @return {number} 530 */ 531 selfTime: function() 532 { 533 return 0; 534 }, 535 536 /** 537 * @return {!WebInspector.TimelineModel.Record} 538 */ 539 record: function() 540 { 541 return this._presentationChildren[0].record(); 542 }, 543 544 /** 545 * @return {boolean} 546 */ 547 coalesced: function() 548 { 549 return true; 550 }, 551 552 /** 553 * @return {boolean} 554 */ 555 hasWarnings: function() 556 { 557 return false; 558 }, 559 560 __proto__: WebInspector.TimelinePresentationModel.Record.prototype 561 } 562 563 /** 564 * @constructor 565 * @extends {WebInspector.TimelinePresentationModel.Record} 566 */ 567 WebInspector.TimelinePresentationModel.RootRecord = function() 568 { 569 WebInspector.TimelinePresentationModel.Record.call(this, null); 570 } 571 572 WebInspector.TimelinePresentationModel.RootRecord.prototype = { 573 /** 574 * @return {boolean} 575 */ 576 hasWarnings: function() 577 { 578 return false; 579 }, 580 581 __proto__: WebInspector.TimelinePresentationModel.Record.prototype 582 } 583