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 |