| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * Copyright (C) 2014 Google 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 are | |
| 6 * met: | |
| 7 * | |
| 8 * * Redistributions of source code must retain the above copyright | |
| 9 * notice, this list of conditions and the following disclaimer. | |
| 10 * * Redistributions in binary form must reproduce the above | |
| 11 * copyright notice, this list of conditions and the following disclaimer | |
| 12 * in the documentation and/or other materials provided with the | |
| 13 * distribution. | |
| 14 * * Neither the name of Google Inc. nor the names of its | |
| 15 * contributors may be used to endorse or promote products derived from | |
| 16 * this software without specific prior written permission. | |
| 17 * | |
| 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 29 */ | |
| 30 | |
| 31 /** | |
| 32 * @implements {PerfUI.FlameChartDataProvider} | |
| 33 * @unrestricted | |
| 34 */ | |
| 35 Timeline.TimelineFlameChartDataProvider = class { | |
| 36 /** | |
| 37 * @param {!Array<!TimelineModel.TimelineModelFilter>} filters | |
| 38 */ | |
| 39 constructor(filters) { | |
| 40 this.reset(); | |
| 41 this._font = '11px ' + Host.fontFamily(); | |
| 42 this._filters = filters; | |
| 43 /** @type {?PerfUI.FlameChart.TimelineData} */ | |
| 44 this._timelineData = null; | |
| 45 this._currentLevel = 0; | |
| 46 /** @type {?Timeline.PerformanceModel} */ | |
| 47 this._performanceModel = null; | |
| 48 /** @type {?TimelineModel.TimelineModel} */ | |
| 49 this._model = null; | |
| 50 | |
| 51 this._consoleColorGenerator = | |
| 52 new PerfUI.FlameChart.ColorGenerator({min: 30, max: 55}, {min: 70, max:
100, count: 6}, 50, 0.7); | |
| 53 this._extensionColorGenerator = | |
| 54 new PerfUI.FlameChart.ColorGenerator({min: 210, max: 300}, {min: 70, max
: 100, count: 6}, 70, 0.7); | |
| 55 | |
| 56 var defaultGroupStyle = { | |
| 57 padding: 4, | |
| 58 height: 17, | |
| 59 collapsible: true, | |
| 60 color: UI.themeSupport.patchColor('#222', UI.ThemeSupport.ColorUsage.Foreg
round), | |
| 61 backgroundColor: UI.themeSupport.patchColor('white', UI.ThemeSupport.Color
Usage.Background), | |
| 62 font: this._font, | |
| 63 nestingLevel: 0, | |
| 64 shareHeaderLine: true | |
| 65 }; | |
| 66 | |
| 67 this._headerLevel1 = /** @type {!PerfUI.FlameChart.GroupStyle} */ | |
| 68 (Object.assign({}, defaultGroupStyle, {shareHeaderLine: false})); | |
| 69 this._headerLevel2 = /** @type {!PerfUI.FlameChart.GroupStyle} */ | |
| 70 (Object.assign({}, defaultGroupStyle, {padding: 2, nestingLevel: 1, coll
apsible: false})); | |
| 71 this._staticHeader = /** @type {!PerfUI.FlameChart.GroupStyle} */ | |
| 72 (Object.assign({}, defaultGroupStyle, {collapsible: false})); | |
| 73 this._interactionsHeaderLevel1 = /** @type {!PerfUI.FlameChart.GroupStyle} *
/ | |
| 74 (Object.assign({useFirstLineForOverview: true}, defaultGroupStyle)); | |
| 75 this._interactionsHeaderLevel2 = /** @type {!PerfUI.FlameChart.GroupStyle} *
/ | |
| 76 (Object.assign({}, defaultGroupStyle, {padding: 2, nestingLevel: 1})); | |
| 77 | |
| 78 /** @type {!Map<string, number>} */ | |
| 79 this._flowEventIndexById = new Map(); | |
| 80 } | |
| 81 | |
| 82 /** | |
| 83 * @param {?Timeline.PerformanceModel} performanceModel | |
| 84 */ | |
| 85 setModel(performanceModel) { | |
| 86 this.reset(); | |
| 87 this._performanceModel = performanceModel; | |
| 88 this._model = performanceModel && performanceModel.timelineModel(); | |
| 89 } | |
| 90 | |
| 91 /** | |
| 92 * @override | |
| 93 * @param {number} entryIndex | |
| 94 * @return {?string} | |
| 95 */ | |
| 96 entryTitle(entryIndex) { | |
| 97 var entryType = this._entryType(entryIndex); | |
| 98 if (entryType === Timeline.TimelineFlameChartEntryType.Event) { | |
| 99 var event = /** @type {!SDK.TracingModel.Event} */ (this._entryData[entryI
ndex]); | |
| 100 if (event.phase === SDK.TracingModel.Phase.AsyncStepInto || event.phase ==
= SDK.TracingModel.Phase.AsyncStepPast) | |
| 101 return event.name + ':' + event.args['step']; | |
| 102 if (event._blackboxRoot) | |
| 103 return Common.UIString('Blackboxed'); | |
| 104 var name = Timeline.TimelineUIUtils.eventStyle(event).title; | |
| 105 // TODO(yurys): support event dividers | |
| 106 var detailsText = Timeline.TimelineUIUtils.buildDetailsTextForTraceEvent(e
vent, this._model.targetByEvent(event)); | |
| 107 if (event.name === TimelineModel.TimelineModel.RecordType.JSFrame && detai
lsText) | |
| 108 return detailsText; | |
| 109 return detailsText ? Common.UIString('%s (%s)', name, detailsText) : name; | |
| 110 } | |
| 111 if (entryType === Timeline.TimelineFlameChartEntryType.ExtensionEvent) { | |
| 112 var event = /** @type {!SDK.TracingModel.Event} */ (this._entryData[entryI
ndex]); | |
| 113 return event.name; | |
| 114 } | |
| 115 var title = this._entryIndexToTitle[entryIndex]; | |
| 116 if (!title) { | |
| 117 title = Common.UIString('Unexpected entryIndex %d', entryIndex); | |
| 118 console.error(title); | |
| 119 } | |
| 120 return title; | |
| 121 } | |
| 122 | |
| 123 /** | |
| 124 * @override | |
| 125 * @param {number} index | |
| 126 * @return {string} | |
| 127 */ | |
| 128 textColor(index) { | |
| 129 var event = this._entryData[index]; | |
| 130 return event && event._blackboxRoot ? '#888' : Timeline.FlameChartStyle.text
Color; | |
| 131 } | |
| 132 | |
| 133 /** | |
| 134 * @override | |
| 135 * @param {number} index | |
| 136 * @return {?string} | |
| 137 */ | |
| 138 entryFont(index) { | |
| 139 return this._font; | |
| 140 } | |
| 141 | |
| 142 reset() { | |
| 143 this._currentLevel = 0; | |
| 144 this._timelineData = null; | |
| 145 /** @type {!Array<!SDK.TracingModel.Event|!TimelineModel.TimelineFrame|!Time
lineModel.TimelineIRModel.Phases>} */ | |
| 146 this._entryData = []; | |
| 147 /** @type {!Array<!Timeline.TimelineFlameChartEntryType>} */ | |
| 148 this._entryTypeByLevel = []; | |
| 149 /** @type {!Array<string>} */ | |
| 150 this._entryIndexToTitle = []; | |
| 151 /** @type {!Array<!Timeline.TimelineFlameChartMarker>} */ | |
| 152 this._markers = []; | |
| 153 /** @type {!Map<!Timeline.TimelineCategory, string>} */ | |
| 154 this._asyncColorByCategory = new Map(); | |
| 155 /** @type {!Map<!TimelineModel.TimelineIRModel.Phases, string>} */ | |
| 156 this._asyncColorByInteractionPhase = new Map(); | |
| 157 /** @type {!Array<!{title: string, model: !SDK.TracingModel}>} */ | |
| 158 this._extensionInfo = []; | |
| 159 } | |
| 160 | |
| 161 /** | |
| 162 * @override | |
| 163 * @return {number} | |
| 164 */ | |
| 165 maxStackDepth() { | |
| 166 return this._currentLevel; | |
| 167 } | |
| 168 | |
| 169 /** | |
| 170 * @override | |
| 171 * @return {!PerfUI.FlameChart.TimelineData} | |
| 172 */ | |
| 173 timelineData() { | |
| 174 if (this._timelineData) | |
| 175 return this._timelineData; | |
| 176 | |
| 177 this._timelineData = new PerfUI.FlameChart.TimelineData([], [], [], []); | |
| 178 if (!this._model) | |
| 179 return this._timelineData; | |
| 180 | |
| 181 this._flowEventIndexById.clear(); | |
| 182 | |
| 183 this._minimumBoundary = this._model.minimumRecordTime(); | |
| 184 this._timeSpan = this._model.isEmpty() ? 1000 : this._model.maximumRecordTim
e() - this._minimumBoundary; | |
| 185 this._currentLevel = 0; | |
| 186 | |
| 187 this._appendHeader(Common.UIString('Frames'), this._staticHeader); | |
| 188 this._appendFrameBars(this._performanceModel.frames()); | |
| 189 | |
| 190 this._appendHeader(Common.UIString('Interactions'), this._interactionsHeader
Level1); | |
| 191 this._appendInteractionRecords(); | |
| 192 | |
| 193 var eventEntryType = Timeline.TimelineFlameChartEntryType.Event; | |
| 194 | |
| 195 var asyncEventGroups = TimelineModel.TimelineModel.AsyncEventGroup; | |
| 196 var inputLatencies = this._model.mainThreadAsyncEvents().get(asyncEventGroup
s.input); | |
| 197 if (inputLatencies && inputLatencies.length) { | |
| 198 var title = Timeline.TimelineUIUtils.titleForAsyncEventGroup(asyncEventGro
ups.input); | |
| 199 this._appendAsyncEventsGroup(title, inputLatencies, this._interactionsHead
erLevel2, eventEntryType); | |
| 200 } | |
| 201 var animations = this._model.mainThreadAsyncEvents().get(asyncEventGroups.an
imation); | |
| 202 if (animations && animations.length) { | |
| 203 var title = Timeline.TimelineUIUtils.titleForAsyncEventGroup(asyncEventGro
ups.animation); | |
| 204 this._appendAsyncEventsGroup(title, animations, this._interactionsHeaderLe
vel2, eventEntryType); | |
| 205 } | |
| 206 var threads = this._model.virtualThreads(); | |
| 207 if (!Runtime.experiments.isEnabled('timelinePerFrameTrack')) { | |
| 208 this._appendThreadTimelineData( | |
| 209 Common.UIString('Main'), this._model.mainThreadEvents(), this._model.m
ainThreadAsyncEvents(), true); | |
| 210 } else { | |
| 211 this._appendThreadTimelineData( | |
| 212 Common.UIString('Page'), this._model.eventsForFrame(TimelineModel.Time
lineModel.PageFrame.mainFrameId), | |
| 213 this._model.mainThreadAsyncEvents(), true); | |
| 214 for (var frame of this._model.rootFrames()) { | |
| 215 // Ignore top frame itself, since it should be part of page events. | |
| 216 frame.children.forEach(this._appendFrameEvents.bind(this, 0)); | |
| 217 } | |
| 218 } | |
| 219 var compositorThreads = threads.filter(thread => thread.name.startsWith('Com
positorTileWorker')); | |
| 220 var otherThreads = threads.filter(thread => !thread.name.startsWith('Composi
torTileWorker')); | |
| 221 if (compositorThreads.length) { | |
| 222 this._appendHeader(Common.UIString('Raster'), this._headerLevel1); | |
| 223 for (var i = 0; i < compositorThreads.length; ++i) { | |
| 224 this._appendSyncEvents( | |
| 225 compositorThreads[i].events, Common.UIString('Rasterizer Thread %d',
i), this._headerLevel2, | |
| 226 eventEntryType); | |
| 227 } | |
| 228 } | |
| 229 this._appendGPUEvents(); | |
| 230 | |
| 231 otherThreads.forEach( | |
| 232 thread => this._appendThreadTimelineData( | |
| 233 thread.name || Common.UIString('Thread %d', thread.id), thread.event
s, thread.asyncEventsByGroup)); | |
| 234 | |
| 235 for (let extensionIndex = 0; extensionIndex < this._extensionInfo.length; ex
tensionIndex++) | |
| 236 this._innerAppendExtensionEvents(extensionIndex); | |
| 237 | |
| 238 /** | |
| 239 * @param {!Timeline.TimelineFlameChartMarker} a | |
| 240 * @param {!Timeline.TimelineFlameChartMarker} b | |
| 241 */ | |
| 242 function compareStartTime(a, b) { | |
| 243 return a.startTime() - b.startTime(); | |
| 244 } | |
| 245 | |
| 246 this._markers.sort(compareStartTime); | |
| 247 this._timelineData.markers = this._markers; | |
| 248 this._flowEventIndexById.clear(); | |
| 249 | |
| 250 return this._timelineData; | |
| 251 } | |
| 252 | |
| 253 /** | |
| 254 * @override | |
| 255 * @return {number} | |
| 256 */ | |
| 257 minimumBoundary() { | |
| 258 return this._minimumBoundary; | |
| 259 } | |
| 260 | |
| 261 /** | |
| 262 * @override | |
| 263 * @return {number} | |
| 264 */ | |
| 265 totalTime() { | |
| 266 return this._timeSpan; | |
| 267 } | |
| 268 | |
| 269 /** | |
| 270 * @param {number} level | |
| 271 * @param {!TimelineModel.TimelineModel.PageFrame} frame | |
| 272 */ | |
| 273 _appendFrameEvents(level, frame) { | |
| 274 var events = this._model.eventsForFrame(frame.id); | |
| 275 var clonedHeader = Object.assign({}, this._headerLevel1); | |
| 276 clonedHeader.nestingLevel = level; | |
| 277 this._appendSyncEvents( | |
| 278 events, Timeline.TimelineUIUtils.displayNameForFrame(frame), | |
| 279 /** @type {!PerfUI.FlameChart.GroupStyle} */ (clonedHeader), Timeline.Ti
melineFlameChartEntryType.Event); | |
| 280 frame.children.forEach(this._appendFrameEvents.bind(this, level + 1)); | |
| 281 } | |
| 282 | |
| 283 /** | |
| 284 * @param {string} threadTitle | |
| 285 * @param {!Array<!SDK.TracingModel.Event>} syncEvents | |
| 286 * @param {!Map<!TimelineModel.TimelineModel.AsyncEventGroup, !Array<!SDK.Trac
ingModel.AsyncEvent>>} asyncEvents | |
| 287 * @param {boolean=} forceExpanded | |
| 288 */ | |
| 289 _appendThreadTimelineData(threadTitle, syncEvents, asyncEvents, forceExpanded)
{ | |
| 290 var entryType = Timeline.TimelineFlameChartEntryType.Event; | |
| 291 this._appendAsyncEvents(asyncEvents); | |
| 292 this._appendSyncEvents(syncEvents, threadTitle, this._headerLevel1, entryTyp
e, forceExpanded); | |
| 293 } | |
| 294 | |
| 295 /** | |
| 296 * @param {!Array<!SDK.TracingModel.Event>} events | |
| 297 * @param {string} title | |
| 298 * @param {!PerfUI.FlameChart.GroupStyle} style | |
| 299 * @param {!Timeline.TimelineFlameChartEntryType} entryType | |
| 300 * @param {boolean=} forceExpanded | |
| 301 */ | |
| 302 _appendSyncEvents(events, title, style, entryType, forceExpanded) { | |
| 303 var isExtension = entryType === Timeline.TimelineFlameChartEntryType.Extensi
onEvent; | |
| 304 var openEvents = []; | |
| 305 var flowEventsEnabled = Runtime.experiments.isEnabled('timelineFlowEvents'); | |
| 306 var blackboxingEnabled = !isExtension && Runtime.experiments.isEnabled('blac
kboxJSFramesOnTimeline'); | |
| 307 var maxStackDepth = 0; | |
| 308 for (var i = 0; i < events.length; ++i) { | |
| 309 var e = events[i]; | |
| 310 if (!isExtension && TimelineModel.TimelineModel.isMarkerEvent(e)) { | |
| 311 this._markers.push(new Timeline.TimelineFlameChartMarker( | |
| 312 e.startTime, e.startTime - this._model.minimumRecordTime(), | |
| 313 Timeline.TimelineUIUtils.markerStyleForEvent(e))); | |
| 314 } | |
| 315 if (!SDK.TracingModel.isFlowPhase(e.phase)) { | |
| 316 if (!e.endTime && e.phase !== SDK.TracingModel.Phase.Instant) | |
| 317 continue; | |
| 318 if (SDK.TracingModel.isAsyncPhase(e.phase)) | |
| 319 continue; | |
| 320 if (!isExtension && !this._isVisible(e)) | |
| 321 continue; | |
| 322 } | |
| 323 while (openEvents.length && openEvents.peekLast().endTime <= e.startTime) | |
| 324 openEvents.pop(); | |
| 325 e._blackboxRoot = false; | |
| 326 if (blackboxingEnabled && this._isBlackboxedEvent(e)) { | |
| 327 var parent = openEvents.peekLast(); | |
| 328 if (parent && parent._blackboxRoot) | |
| 329 continue; | |
| 330 e._blackboxRoot = true; | |
| 331 } | |
| 332 if (title) { | |
| 333 this._appendHeader(title, style, forceExpanded); | |
| 334 title = ''; | |
| 335 } | |
| 336 | |
| 337 var level = this._currentLevel + openEvents.length; | |
| 338 if (flowEventsEnabled) | |
| 339 this._appendFlowEvent(e, level); | |
| 340 if (e.phase !== SDK.TracingModel.Phase.FlowEnd) | |
| 341 this._appendEvent(e, level); | |
| 342 if (!isExtension && TimelineModel.TimelineModel.isMarkerEvent(e)) | |
| 343 this._timelineData.entryTotalTimes[this._entryData.length] = undefined; | |
| 344 | |
| 345 maxStackDepth = Math.max(maxStackDepth, openEvents.length + 1); | |
| 346 if (e.endTime) | |
| 347 openEvents.push(e); | |
| 348 } | |
| 349 this._entryTypeByLevel.length = this._currentLevel + maxStackDepth; | |
| 350 this._entryTypeByLevel.fill(entryType, this._currentLevel); | |
| 351 this._currentLevel += maxStackDepth; | |
| 352 } | |
| 353 | |
| 354 /** | |
| 355 * @param {!SDK.TracingModel.Event} event | |
| 356 * @return {boolean} | |
| 357 */ | |
| 358 _isBlackboxedEvent(event) { | |
| 359 if (event.name !== TimelineModel.TimelineModel.RecordType.JSFrame) | |
| 360 return false; | |
| 361 var url = event.args['data']['url']; | |
| 362 return url && this._isBlackboxedURL(url); | |
| 363 } | |
| 364 | |
| 365 /** | |
| 366 * @param {string} url | |
| 367 * @return {boolean} | |
| 368 */ | |
| 369 _isBlackboxedURL(url) { | |
| 370 return Bindings.blackboxManager.isBlackboxedURL(url); | |
| 371 } | |
| 372 | |
| 373 /** | |
| 374 * @param {!Map<!TimelineModel.TimelineModel.AsyncEventGroup, !Array<!SDK.Trac
ingModel.AsyncEvent>>} asyncEvents | |
| 375 */ | |
| 376 _appendAsyncEvents(asyncEvents) { | |
| 377 var entryType = Timeline.TimelineFlameChartEntryType.Event; | |
| 378 var groups = TimelineModel.TimelineModel.AsyncEventGroup; | |
| 379 var groupArray = Object.keys(groups).map(key => groups[key]); | |
| 380 | |
| 381 groupArray.remove(groups.animation); | |
| 382 groupArray.remove(groups.input); | |
| 383 | |
| 384 for (var groupIndex = 0; groupIndex < groupArray.length; ++groupIndex) { | |
| 385 var group = groupArray[groupIndex]; | |
| 386 var events = asyncEvents.get(group); | |
| 387 if (!events) | |
| 388 continue; | |
| 389 var title = Timeline.TimelineUIUtils.titleForAsyncEventGroup(group); | |
| 390 this._appendAsyncEventsGroup(title, events, this._headerLevel1, entryType)
; | |
| 391 } | |
| 392 } | |
| 393 | |
| 394 /** | |
| 395 * @param {string} header | |
| 396 * @param {!Array<!SDK.TracingModel.AsyncEvent>} events | |
| 397 * @param {!PerfUI.FlameChart.GroupStyle} style | |
| 398 * @param {!Timeline.TimelineFlameChartEntryType} entryType | |
| 399 */ | |
| 400 _appendAsyncEventsGroup(header, events, style, entryType) { | |
| 401 var lastUsedTimeByLevel = []; | |
| 402 var groupHeaderAppended = false; | |
| 403 for (var i = 0; i < events.length; ++i) { | |
| 404 var asyncEvent = events[i]; | |
| 405 if (!this._isVisible(asyncEvent)) | |
| 406 continue; | |
| 407 if (!groupHeaderAppended) { | |
| 408 this._appendHeader(header, style); | |
| 409 groupHeaderAppended = true; | |
| 410 } | |
| 411 var startTime = asyncEvent.startTime; | |
| 412 var level; | |
| 413 for (level = 0; level < lastUsedTimeByLevel.length && lastUsedTimeByLevel[
level] > startTime; ++level) { | |
| 414 } | |
| 415 this._appendAsyncEvent(asyncEvent, this._currentLevel + level); | |
| 416 lastUsedTimeByLevel[level] = asyncEvent.endTime; | |
| 417 } | |
| 418 this._entryTypeByLevel.length = this._currentLevel + lastUsedTimeByLevel.len
gth; | |
| 419 this._entryTypeByLevel.fill(entryType, this._currentLevel); | |
| 420 this._currentLevel += lastUsedTimeByLevel.length; | |
| 421 } | |
| 422 | |
| 423 _appendGPUEvents() { | |
| 424 var eventType = Timeline.TimelineFlameChartEntryType.Event; | |
| 425 var gpuEvents = this._model.gpuEvents(); | |
| 426 if (this._appendSyncEvents(gpuEvents, Common.UIString('GPU'), this._headerLe
vel1, eventType, false)) | |
| 427 ++this._currentLevel; | |
| 428 } | |
| 429 | |
| 430 _appendInteractionRecords() { | |
| 431 this._performanceModel.interactionRecords().forEach(this._appendSegment, thi
s); | |
| 432 this._entryTypeByLevel[this._currentLevel++] = Timeline.TimelineFlameChartEn
tryType.InteractionRecord; | |
| 433 } | |
| 434 | |
| 435 /** | |
| 436 * @param {!Array.<!TimelineModel.TimelineFrame>} frames | |
| 437 */ | |
| 438 _appendFrameBars(frames) { | |
| 439 var style = Timeline.TimelineUIUtils.markerStyleForFrame(); | |
| 440 this._entryTypeByLevel[this._currentLevel] = Timeline.TimelineFlameChartEntr
yType.Frame; | |
| 441 for (var i = 0; i < frames.length; ++i) { | |
| 442 this._markers.push(new Timeline.TimelineFlameChartMarker( | |
| 443 frames[i].startTime, frames[i].startTime - this._model.minimumRecordTi
me(), style)); | |
| 444 this._appendFrame(frames[i]); | |
| 445 } | |
| 446 ++this._currentLevel; | |
| 447 } | |
| 448 | |
| 449 /** | |
| 450 * @param {number} entryIndex | |
| 451 * @return {!Timeline.TimelineFlameChartEntryType} | |
| 452 */ | |
| 453 _entryType(entryIndex) { | |
| 454 return this._entryTypeByLevel[this._timelineData.entryLevels[entryIndex]]; | |
| 455 } | |
| 456 | |
| 457 /** | |
| 458 * @override | |
| 459 * @param {number} entryIndex | |
| 460 * @return {?Element} | |
| 461 */ | |
| 462 prepareHighlightedEntryInfo(entryIndex) { | |
| 463 var time = ''; | |
| 464 var title; | |
| 465 var warning; | |
| 466 var type = this._entryType(entryIndex); | |
| 467 if (type === Timeline.TimelineFlameChartEntryType.Event) { | |
| 468 var event = /** @type {!SDK.TracingModel.Event} */ (this._entryData[entryI
ndex]); | |
| 469 var totalTime = event.duration; | |
| 470 var selfTime = event.selfTime; | |
| 471 var /** @const */ eps = 1e-6; | |
| 472 if (typeof totalTime === 'number') { | |
| 473 time = Math.abs(totalTime - selfTime) > eps && selfTime > eps ? | |
| 474 Common.UIString( | |
| 475 '%s (self %s)', Number.millisToString(totalTime, true), Number.m
illisToString(selfTime, true)) : | |
| 476 Number.millisToString(totalTime, true); | |
| 477 } | |
| 478 title = this.entryTitle(entryIndex); | |
| 479 warning = Timeline.TimelineUIUtils.eventWarning(event); | |
| 480 } else if (type === Timeline.TimelineFlameChartEntryType.Frame) { | |
| 481 var frame = /** @type {!TimelineModel.TimelineFrame} */ (this._entryData[e
ntryIndex]); | |
| 482 time = Common.UIString( | |
| 483 '%s ~ %.0f\u2009fps', Number.preciseMillisToString(frame.duration, 1),
(1000 / frame.duration)); | |
| 484 title = frame.idle ? Common.UIString('Idle Frame') : Common.UIString('Fram
e'); | |
| 485 if (frame.hasWarnings()) { | |
| 486 warning = createElement('span'); | |
| 487 warning.textContent = Common.UIString('Long frame'); | |
| 488 } | |
| 489 } else { | |
| 490 return null; | |
| 491 } | |
| 492 var element = createElement('div'); | |
| 493 var root = UI.createShadowRootWithCoreStyles(element, 'timeline/timelineFlam
echartPopover.css'); | |
| 494 var contents = root.createChild('div', 'timeline-flamechart-popover'); | |
| 495 contents.createChild('span', 'timeline-info-time').textContent = time; | |
| 496 contents.createChild('span', 'timeline-info-title').textContent = title; | |
| 497 if (warning) { | |
| 498 warning.classList.add('timeline-info-warning'); | |
| 499 contents.appendChild(warning); | |
| 500 } | |
| 501 return element; | |
| 502 } | |
| 503 | |
| 504 /** | |
| 505 * @override | |
| 506 * @param {number} entryIndex | |
| 507 */ | |
| 508 highlightEntry(entryIndex) { | |
| 509 SDK.DOMModel.hideDOMNodeHighlight(); | |
| 510 if (this._entryType(entryIndex) !== Timeline.TimelineFlameChartEntryType.Eve
nt) | |
| 511 return; | |
| 512 var event = /** @type {!SDK.TracingModel.Event} */ (this._entryData[entryInd
ex]); | |
| 513 var target = this._model.targetByEvent(event); | |
| 514 if (!target) | |
| 515 return; | |
| 516 var timelineData = TimelineModel.TimelineData.forEvent(event); | |
| 517 var backendNodeId = timelineData.backendNodeId; | |
| 518 if (!backendNodeId) | |
| 519 return; | |
| 520 new SDK.DeferredDOMNode(target, backendNodeId).highlight(); | |
| 521 } | |
| 522 | |
| 523 /** | |
| 524 * @override | |
| 525 * @param {number} entryIndex | |
| 526 * @return {string} | |
| 527 */ | |
| 528 entryColor(entryIndex) { | |
| 529 // This is not annotated due to closure compiler failure to properly infer c
ache container's template type. | |
| 530 function patchColorAndCache(cache, key, lookupColor) { | |
| 531 var color = cache.get(key); | |
| 532 if (color) | |
| 533 return color; | |
| 534 var parsedColor = Common.Color.parse(lookupColor(key)); | |
| 535 color = parsedColor.setAlpha(0.7).asString(Common.Color.Format.RGBA) || ''
; | |
| 536 cache.set(key, color); | |
| 537 return color; | |
| 538 } | |
| 539 | |
| 540 var type = this._entryType(entryIndex); | |
| 541 if (type === Timeline.TimelineFlameChartEntryType.Event) { | |
| 542 var event = /** @type {!SDK.TracingModel.Event} */ (this._entryData[entryI
ndex]); | |
| 543 if (!SDK.TracingModel.isAsyncPhase(event.phase)) | |
| 544 return Timeline.TimelineUIUtils.eventColor(event); | |
| 545 if (event.hasCategory(TimelineModel.TimelineModel.Category.Console) || | |
| 546 event.hasCategory(TimelineModel.TimelineModel.Category.UserTiming)) | |
| 547 return this._consoleColorGenerator.colorForID(event.name); | |
| 548 if (event.hasCategory(TimelineModel.TimelineModel.Category.LatencyInfo)) { | |
| 549 var phase = | |
| 550 TimelineModel.TimelineIRModel.phaseForEvent(event) || TimelineModel.
TimelineIRModel.Phases.Uncategorized; | |
| 551 return patchColorAndCache( | |
| 552 this._asyncColorByInteractionPhase, phase, Timeline.TimelineUIUtils.
interactionPhaseColor); | |
| 553 } | |
| 554 var category = Timeline.TimelineUIUtils.eventStyle(event).category; | |
| 555 return patchColorAndCache(this._asyncColorByCategory, category, () => cate
gory.color); | |
| 556 } | |
| 557 if (type === Timeline.TimelineFlameChartEntryType.Frame) | |
| 558 return 'white'; | |
| 559 if (type === Timeline.TimelineFlameChartEntryType.InteractionRecord) | |
| 560 return 'transparent'; | |
| 561 if (type === Timeline.TimelineFlameChartEntryType.ExtensionEvent) { | |
| 562 var event = /** @type {!SDK.TracingModel.Event} */ (this._entryData[entryI
ndex]); | |
| 563 return this._extensionColorGenerator.colorForID(event.name); | |
| 564 } | |
| 565 return ''; | |
| 566 } | |
| 567 | |
| 568 /** | |
| 569 * @override | |
| 570 * @param {number} entryIndex | |
| 571 * @param {!CanvasRenderingContext2D} context | |
| 572 * @param {?string} text | |
| 573 * @param {number} barX | |
| 574 * @param {number} barY | |
| 575 * @param {number} barWidth | |
| 576 * @param {number} barHeight | |
| 577 * @param {number} unclippedBarX | |
| 578 * @param {number} timeToPixels | |
| 579 * @return {boolean} | |
| 580 */ | |
| 581 decorateEntry(entryIndex, context, text, barX, barY, barWidth, barHeight, uncl
ippedBarX, timeToPixels) { | |
| 582 var data = this._entryData[entryIndex]; | |
| 583 var type = this._entryType(entryIndex); | |
| 584 if (type === Timeline.TimelineFlameChartEntryType.Frame) { | |
| 585 var /** @const */ vPadding = 1; | |
| 586 var /** @const */ hPadding = 1; | |
| 587 var frame = /** {!TimelineModel.TimelineFrame} */ (data); | |
| 588 barX += hPadding; | |
| 589 barWidth -= 2 * hPadding; | |
| 590 barY += vPadding; | |
| 591 barHeight -= 2 * vPadding + 1; | |
| 592 context.fillStyle = frame.idle ? 'white' : (frame.hasWarnings() ? '#fad1d1
' : '#d7f0d1'); | |
| 593 context.fillRect(barX, barY, barWidth, barHeight); | |
| 594 var frameDurationText = Number.preciseMillisToString(frame.duration, 1); | |
| 595 var textWidth = context.measureText(frameDurationText).width; | |
| 596 if (barWidth >= textWidth) { | |
| 597 context.fillStyle = this.textColor(entryIndex); | |
| 598 context.fillText(frameDurationText, barX + (barWidth - textWidth) / 2, b
arY + barHeight - 3); | |
| 599 } | |
| 600 return true; | |
| 601 } | |
| 602 | |
| 603 if (type === Timeline.TimelineFlameChartEntryType.InteractionRecord) { | |
| 604 var color = Timeline.TimelineUIUtils.interactionPhaseColor( | |
| 605 /** @type {!TimelineModel.TimelineIRModel.Phases} */ (data)); | |
| 606 context.fillStyle = color; | |
| 607 context.fillRect(barX, barY, barWidth - 1, 2); | |
| 608 context.fillRect(barX, barY - 3, 2, 3); | |
| 609 context.fillRect(barX + barWidth - 3, barY - 3, 2, 3); | |
| 610 return false; | |
| 611 } | |
| 612 | |
| 613 if (type === Timeline.TimelineFlameChartEntryType.Event) { | |
| 614 var event = /** @type {!SDK.TracingModel.Event} */ (data); | |
| 615 if (event.hasCategory(TimelineModel.TimelineModel.Category.LatencyInfo)) { | |
| 616 var timeWaitingForMainThread = TimelineModel.TimelineData.forEvent(event
).timeWaitingForMainThread; | |
| 617 if (timeWaitingForMainThread) { | |
| 618 context.fillStyle = 'hsla(0, 70%, 60%, 1)'; | |
| 619 var width = Math.floor(unclippedBarX - barX + timeWaitingForMainThread
* timeToPixels); | |
| 620 context.fillRect(barX, barY + barHeight - 3, width, 2); | |
| 621 } | |
| 622 } | |
| 623 if (TimelineModel.TimelineData.forEvent(event).warning) | |
| 624 paintWarningDecoration(barX, barWidth - 1.5); | |
| 625 } | |
| 626 | |
| 627 /** | |
| 628 * @param {number} x | |
| 629 * @param {number} width | |
| 630 */ | |
| 631 function paintWarningDecoration(x, width) { | |
| 632 var /** @const */ triangleSize = 8; | |
| 633 context.save(); | |
| 634 context.beginPath(); | |
| 635 context.rect(x, barY, width, barHeight); | |
| 636 context.clip(); | |
| 637 context.beginPath(); | |
| 638 context.fillStyle = 'red'; | |
| 639 context.moveTo(x + width - triangleSize, barY); | |
| 640 context.lineTo(x + width, barY); | |
| 641 context.lineTo(x + width, barY + triangleSize); | |
| 642 context.fill(); | |
| 643 context.restore(); | |
| 644 } | |
| 645 | |
| 646 return false; | |
| 647 } | |
| 648 | |
| 649 /** | |
| 650 * @override | |
| 651 * @param {number} entryIndex | |
| 652 * @return {boolean} | |
| 653 */ | |
| 654 forceDecoration(entryIndex) { | |
| 655 var type = this._entryType(entryIndex); | |
| 656 if (type === Timeline.TimelineFlameChartEntryType.Frame) | |
| 657 return true; | |
| 658 | |
| 659 if (type === Timeline.TimelineFlameChartEntryType.Event) { | |
| 660 var event = /** @type {!SDK.TracingModel.Event} */ (this._entryData[entryI
ndex]); | |
| 661 return !!TimelineModel.TimelineData.forEvent(event).warning; | |
| 662 } | |
| 663 return false; | |
| 664 } | |
| 665 | |
| 666 /** | |
| 667 * @param {!{title: string, model: !SDK.TracingModel}} entry | |
| 668 */ | |
| 669 appendExtensionEvents(entry) { | |
| 670 this._extensionInfo.push(entry); | |
| 671 if (this._timelineData) | |
| 672 this._innerAppendExtensionEvents(this._extensionInfo.length - 1); | |
| 673 } | |
| 674 | |
| 675 /** | |
| 676 * @param {number} index | |
| 677 */ | |
| 678 _innerAppendExtensionEvents(index) { | |
| 679 var entry = this._extensionInfo[index]; | |
| 680 var entryType = Timeline.TimelineFlameChartEntryType.ExtensionEvent; | |
| 681 var allThreads = [].concat(...entry.model.sortedProcesses().map(process => p
rocess.sortedThreads())); | |
| 682 if (!allThreads.length) | |
| 683 return; | |
| 684 | |
| 685 this._appendHeader(entry.title, this._headerLevel1); | |
| 686 for (let thread of allThreads) { | |
| 687 this._appendAsyncEventsGroup(thread.name(), thread.asyncEvents(), this._he
aderLevel2, entryType); | |
| 688 this._appendSyncEvents(thread.events(), thread.name(), this._headerLevel2,
entryType, false); | |
| 689 } | |
| 690 } | |
| 691 | |
| 692 /** | |
| 693 * @param {string} title | |
| 694 * @param {!PerfUI.FlameChart.GroupStyle} style | |
| 695 * @param {boolean=} expanded | |
| 696 */ | |
| 697 _appendHeader(title, style, expanded) { | |
| 698 this._timelineData.groups.push({startLevel: this._currentLevel, name: title,
expanded: expanded, style: style}); | |
| 699 } | |
| 700 | |
| 701 /** | |
| 702 * @param {!SDK.TracingModel.Event} event | |
| 703 * @param {number} level | |
| 704 */ | |
| 705 _appendEvent(event, level) { | |
| 706 var index = this._entryData.length; | |
| 707 this._entryData.push(event); | |
| 708 this._timelineData.entryLevels[index] = level; | |
| 709 this._timelineData.entryTotalTimes[index] = | |
| 710 event.duration || Timeline.TimelineFlameChartDataProvider.InstantEventVi
sibleDurationMs; | |
| 711 this._timelineData.entryStartTimes[index] = event.startTime; | |
| 712 } | |
| 713 | |
| 714 /** | |
| 715 * @param {!SDK.TracingModel.AsyncEvent} asyncEvent | |
| 716 * @param {number} level | |
| 717 */ | |
| 718 _appendAsyncEvent(asyncEvent, level) { | |
| 719 if (SDK.TracingModel.isNestableAsyncPhase(asyncEvent.phase)) { | |
| 720 // FIXME: also add steps once we support event nesting in the FlameChart. | |
| 721 this._appendEvent(asyncEvent, level); | |
| 722 return; | |
| 723 } | |
| 724 var steps = asyncEvent.steps; | |
| 725 // If we have past steps, put the end event for each range rather than start
one. | |
| 726 var eventOffset = steps.length > 1 && steps[1].phase === SDK.TracingModel.Ph
ase.AsyncStepPast ? 1 : 0; | |
| 727 for (var i = 0; i < steps.length - 1; ++i) { | |
| 728 var index = this._entryData.length; | |
| 729 this._entryData.push(steps[i + eventOffset]); | |
| 730 var startTime = steps[i].startTime; | |
| 731 this._timelineData.entryLevels[index] = level; | |
| 732 this._timelineData.entryTotalTimes[index] = steps[i + 1].startTime - start
Time; | |
| 733 this._timelineData.entryStartTimes[index] = startTime; | |
| 734 } | |
| 735 } | |
| 736 | |
| 737 /** | |
| 738 * @param {!SDK.TracingModel.Event} event | |
| 739 * @param {number} level | |
| 740 */ | |
| 741 _appendFlowEvent(event, level) { | |
| 742 var timelineData = this._timelineData; | |
| 743 /** | |
| 744 * @param {!SDK.TracingModel.Event} event | |
| 745 * @return {number} | |
| 746 */ | |
| 747 function pushStartFlow(event) { | |
| 748 var flowIndex = timelineData.flowStartTimes.length; | |
| 749 timelineData.flowStartTimes.push(event.startTime); | |
| 750 timelineData.flowStartLevels.push(level); | |
| 751 return flowIndex; | |
| 752 } | |
| 753 | |
| 754 /** | |
| 755 * @param {!SDK.TracingModel.Event} event | |
| 756 * @param {number} flowIndex | |
| 757 */ | |
| 758 function pushEndFlow(event, flowIndex) { | |
| 759 timelineData.flowEndTimes[flowIndex] = event.startTime; | |
| 760 timelineData.flowEndLevels[flowIndex] = level; | |
| 761 } | |
| 762 | |
| 763 switch (event.phase) { | |
| 764 case SDK.TracingModel.Phase.FlowBegin: | |
| 765 this._flowEventIndexById.set(event.id, pushStartFlow(event)); | |
| 766 break; | |
| 767 case SDK.TracingModel.Phase.FlowStep: | |
| 768 pushEndFlow(event, this._flowEventIndexById.get(event.id)); | |
| 769 this._flowEventIndexById.set(event.id, pushStartFlow(event)); | |
| 770 break; | |
| 771 case SDK.TracingModel.Phase.FlowEnd: | |
| 772 pushEndFlow(event, this._flowEventIndexById.get(event.id)); | |
| 773 this._flowEventIndexById.delete(event.id); | |
| 774 break; | |
| 775 } | |
| 776 } | |
| 777 | |
| 778 /** | |
| 779 * @param {!TimelineModel.TimelineFrame} frame | |
| 780 */ | |
| 781 _appendFrame(frame) { | |
| 782 var index = this._entryData.length; | |
| 783 this._entryData.push(frame); | |
| 784 this._entryIndexToTitle[index] = Number.millisToString(frame.duration, true)
; | |
| 785 this._timelineData.entryLevels[index] = this._currentLevel; | |
| 786 this._timelineData.entryTotalTimes[index] = frame.duration; | |
| 787 this._timelineData.entryStartTimes[index] = frame.startTime; | |
| 788 } | |
| 789 | |
| 790 /** | |
| 791 * @param {!Common.Segment} segment | |
| 792 */ | |
| 793 _appendSegment(segment) { | |
| 794 var index = this._entryData.length; | |
| 795 this._entryData.push(/** @type {!TimelineModel.TimelineIRModel.Phases} */ (s
egment.data)); | |
| 796 this._entryIndexToTitle[index] = /** @type {string} */ (segment.data); | |
| 797 this._timelineData.entryLevels[index] = this._currentLevel; | |
| 798 this._timelineData.entryTotalTimes[index] = segment.end - segment.begin; | |
| 799 this._timelineData.entryStartTimes[index] = segment.begin; | |
| 800 } | |
| 801 | |
| 802 /** | |
| 803 * @param {number} entryIndex | |
| 804 * @return {?Timeline.TimelineSelection} | |
| 805 */ | |
| 806 createSelection(entryIndex) { | |
| 807 var type = this._entryType(entryIndex); | |
| 808 var timelineSelection = null; | |
| 809 if (type === Timeline.TimelineFlameChartEntryType.Event) { | |
| 810 timelineSelection = Timeline.TimelineSelection.fromTraceEvent( | |
| 811 /** @type {!SDK.TracingModel.Event} */ (this._entryData[entryIndex])); | |
| 812 } else if (type === Timeline.TimelineFlameChartEntryType.Frame) { | |
| 813 timelineSelection = Timeline.TimelineSelection.fromFrame( | |
| 814 /** @type {!TimelineModel.TimelineFrame} */ (this._entryData[entryInde
x])); | |
| 815 } | |
| 816 if (timelineSelection) | |
| 817 this._lastSelection = new Timeline.TimelineFlameChartView.Selection(timeli
neSelection, entryIndex); | |
| 818 return timelineSelection; | |
| 819 } | |
| 820 | |
| 821 /** | |
| 822 * @override | |
| 823 * @param {number} value | |
| 824 * @param {number=} precision | |
| 825 * @return {string} | |
| 826 */ | |
| 827 formatValue(value, precision) { | |
| 828 return Number.preciseMillisToString(value, precision); | |
| 829 } | |
| 830 | |
| 831 /** | |
| 832 * @override | |
| 833 * @param {number} entryIndex | |
| 834 * @return {boolean} | |
| 835 */ | |
| 836 canJumpToEntry(entryIndex) { | |
| 837 return false; | |
| 838 } | |
| 839 | |
| 840 /** | |
| 841 * @param {?Timeline.TimelineSelection} selection | |
| 842 * @return {number} | |
| 843 */ | |
| 844 entryIndexForSelection(selection) { | |
| 845 if (!selection || selection.type() === Timeline.TimelineSelection.Type.Range
) | |
| 846 return -1; | |
| 847 | |
| 848 if (this._lastSelection && this._lastSelection.timelineSelection.object() ==
= selection.object()) | |
| 849 return this._lastSelection.entryIndex; | |
| 850 var index = this._entryData.indexOf( | |
| 851 /** @type {!SDK.TracingModel.Event|!TimelineModel.TimelineFrame|!Timelin
eModel.TimelineIRModel.Phases} */ | |
| 852 (selection.object())); | |
| 853 if (index !== -1) | |
| 854 this._lastSelection = new Timeline.TimelineFlameChartView.Selection(select
ion, index); | |
| 855 return index; | |
| 856 } | |
| 857 | |
| 858 /** | |
| 859 * @param {!SDK.TracingModel.Event} event | |
| 860 * @return {?Timeline.TimelineSelection} selection | |
| 861 */ | |
| 862 selectionForEvent(event) { | |
| 863 var entryIndex = this._entryData.indexOf(event); | |
| 864 return this.createSelection(entryIndex); | |
| 865 } | |
| 866 | |
| 867 /** | |
| 868 * @param {!SDK.TracingModel.Event} event | |
| 869 * @return {boolean} | |
| 870 */ | |
| 871 _isVisible(event) { | |
| 872 return this._filters.every(function(filter) { | |
| 873 return filter.accept(event); | |
| 874 }); | |
| 875 } | |
| 876 }; | |
| 877 | |
| 878 Timeline.TimelineFlameChartDataProvider.InstantEventVisibleDurationMs = 0.001; | |
| OLD | NEW |