Skip to content

Commit

Permalink
Timeline: use RAIL phases to color input events
Browse files Browse the repository at this point in the history
- color input events according to the RAIL phase we detected based on those events;
- visualize main thread delay by a thin red line at the bottom of the bar;

BUG=554215

Review-Url: https://codereview.chromium.org/1938083002
Cr-Commit-Position: refs/heads/master@{#391108}
  • Loading branch information
caseq authored and Commit bot committed May 3, 2016
1 parent 8939357 commit 0af8110
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 40 deletions.
40 changes: 27 additions & 13 deletions front_end/timeline/TimelineFlameChart.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,13 +316,14 @@ WebInspector.TimelineFlameChartDataProvider = function(model, frameModel, irMode
};

this._interactionsHeaderLevel2 = {
padding: 4,
padding: 2,
height: 17,
collapsible: true,
color: WebInspector.themeSupport.patchColor("#222", WebInspector.ThemeSupport.ColorUsage.Foreground),
font: this._font,
backgroundColor: WebInspector.themeSupport.patchColor("white", WebInspector.ThemeSupport.ColorUsage.Background),
nestingLevel: 1
nestingLevel: 1,
shareHeaderLine: true
};
}

Expand Down Expand Up @@ -384,9 +385,12 @@ WebInspector.TimelineFlameChartDataProvider.prototype = {
this._entryTypeByLevel = [];
/** @type {!Array<string>} */
this._entryIndexToTitle = [];
/** @type {!Array.<!WebInspector.TimelineFlameChartMarker>} */
/** @type {!Array<!WebInspector.TimelineFlameChartMarker>} */
this._markers = [];
this._asyncColorByCategory = {};
/** @type {!Map<!WebInspector.TimelineCategory, string>} */
this._asyncColorByCategory = new Map();
/** @type {!Map<!WebInspector.TimelineIRModel.Phases, string>} */
this._asyncColorByInteractionPhase = new Map();
},

/**
Expand Down Expand Up @@ -666,21 +670,31 @@ WebInspector.TimelineFlameChartDataProvider.prototype = {
*/
entryColor: function(entryIndex)
{
// This is not annotated due to closure compiler failure to properly infer cache container's template type.
function patchColorAndCache(cache, key, lookupColor)
{
var color = cache.get(key);
if (color)
return color;
var parsedColor = WebInspector.Color.parse(lookupColor(key));
color = parsedColor.setAlpha(0.7).asString(WebInspector.Color.Format.RGBA) || "";
cache.set(key, color);
return color;
}

var type = this._entryType(entryIndex);
if (type === WebInspector.TimelineFlameChartEntryType.Event) {
var event = /** @type {!WebInspector.TracingModel.Event} */ (this._entryData[entryIndex]);
if (!WebInspector.TracingModel.isAsyncPhase(event.phase))
return WebInspector.TimelineUIUtils.eventColor(event);
if (event.hasCategory(WebInspector.TimelineModel.Category.Console) || event.hasCategory(WebInspector.TimelineModel.Category.UserTiming))
return this._consoleColorGenerator.colorForID(event.name);
if (event.hasCategory(WebInspector.TimelineModel.Category.LatencyInfo)) {
var phase = WebInspector.TimelineIRModel.phaseForEvent(event) || WebInspector.TimelineIRModel.Phases.Uncategorized;
return patchColorAndCache(this._asyncColorByInteractionPhase, phase, WebInspector.TimelineUIUtils.interactionPhaseColor);
}
var category = WebInspector.TimelineUIUtils.eventStyle(event).category;
var color = this._asyncColorByCategory[category.name];
if (color)
return color;
var parsedColor = WebInspector.Color.parse(category.color);
color = parsedColor.setAlpha(0.7).asString(WebInspector.Color.Format.RGBA) || "";
this._asyncColorByCategory[category.name] = color;
return color;
return patchColorAndCache(this._asyncColorByCategory, category, () => category.color);
}
if (type === WebInspector.TimelineFlameChartEntryType.Frame)
return "white";
Expand Down Expand Up @@ -737,9 +751,9 @@ WebInspector.TimelineFlameChartDataProvider.prototype = {
if (type === WebInspector.TimelineFlameChartEntryType.Event) {
var event = /** @type {!WebInspector.TracingModel.Event} */ (this._entryData[entryIndex]);
if (event.hasCategory(WebInspector.TimelineModel.Category.LatencyInfo) && event.timeWaitingForMainThread) {
context.fillStyle = "rgba(255, 140, 120, 0.6)";
context.fillStyle = "hsla(0, 70%, 60%, 1)";
var width = Math.floor(unclippedBarX - barX + event.timeWaitingForMainThread * timeToPixels);
context.fillRect(barX, barY, width, barHeight - 1);
context.fillRect(barX, barY + barHeight - 3, width, 2);
}
if (event.warning)
paintWarningDecoration(barX, barWidth - 1.5);
Expand Down
62 changes: 57 additions & 5 deletions front_end/timeline/TimelineIRModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ WebInspector.TimelineIRModel.Phases = {
Scroll: "Scroll",
Fling: "Fling",
Drag: "Drag",
Animation: "Animation"
Animation: "Animation",
Uncategorized: "Uncategorized"
};

/**
Expand Down Expand Up @@ -62,6 +63,17 @@ WebInspector.TimelineIRModel._mergeThresholdsMs = {
mouse: 40,
};

WebInspector.TimelineIRModel._eventIRPhase = Symbol("eventIRPhase");

/**
* @param {!WebInspector.TracingModel.Event} event
* @return {!WebInspector.TimelineIRModel.Phases}
*/
WebInspector.TimelineIRModel.phaseForEvent = function(event)
{
return event[WebInspector.TimelineIRModel._eventIRPhase];
}

WebInspector.TimelineIRModel.prototype = {
/**
* @param {!WebInspector.TimelineModel} timelineModel
Expand Down Expand Up @@ -99,6 +111,7 @@ WebInspector.TimelineIRModel.prototype = {
var phases = WebInspector.TimelineIRModel.Phases;
var thresholdsMs = WebInspector.TimelineIRModel._mergeThresholdsMs;

var scrollStart;
var flingStart;
var touchStart;
var firstTouchMove;
Expand All @@ -112,6 +125,20 @@ WebInspector.TimelineIRModel.prototype = {
console.assert(false, "Unordered input events");
var type = this._inputEventType(event.name);
switch (type) {

case eventTypes.ScrollBegin:
this._scrolls.append(this._segmentForEvent(event, phases.Scroll));
scrollStart = event;
break;

case eventTypes.ScrollEnd:
if (scrollStart)
this._scrolls.append(this._segmentForEventRange(scrollStart, event, phases.Scroll));
else
this._scrolls.append(this._segmentForEvent(event, phases.Scroll));
scrollStart = null;
break;

case eventTypes.ScrollUpdate:
touchStart = null; // Since we're scrolling now, disregard other touch gestures.
this._scrolls.append(this._segmentForEvent(event, phases.Scroll));
Expand All @@ -129,7 +156,7 @@ WebInspector.TimelineIRModel.prototype = {
// FIXME: also process renderer fling events.
if (!flingStart)
break;
this._scrolls.append(new WebInspector.Segment(flingStart.startTime, event.endTime, phases.Fling));
this._scrolls.append(this._segmentForEventRange(flingStart, event, phases.Fling));
flingStart = null;
break;

Expand All @@ -156,6 +183,7 @@ WebInspector.TimelineIRModel.prototype = {
break;
}
touchStart = event;
event.steps[0][WebInspector.TimelineIRModel._eventIRPhase] = phases.Response;
firstTouchMove = null;
break;

Expand All @@ -168,7 +196,7 @@ WebInspector.TimelineIRModel.prototype = {
this._drags.append(this._segmentForEvent(event, phases.Drag));
} else if (touchStart) {
firstTouchMove = event;
this._responses.append(new WebInspector.Segment(touchStart.startTime, event.endTime, phases.Response));
this._responses.append(this._segmentForEventRange(touchStart, event, phases.Response));
}
break;

Expand Down Expand Up @@ -199,8 +227,9 @@ WebInspector.TimelineIRModel.prototype = {
case eventTypes.MouseWheel:
// Do not consider first MouseWheel as trace viewer's implementation does -- in case of MouseWheel it's not really special.
if (mouseWheel && canMerge(thresholdsMs.mouse, mouseWheel, event))
this._scrolls.append(new WebInspector.Segment(mouseWheel.endTime, event.startTime, phases.Scroll));
this._scrolls.append(this._segmentForEvent(event, phases.Scroll));
this._scrolls.append(this._segmentForEventRange(mouseWheel, event, phases.Scroll));
else
this._scrolls.append(this._segmentForEvent(event, phases.Scroll));
mouseWheel = event;
break;
}
Expand Down Expand Up @@ -234,9 +263,32 @@ WebInspector.TimelineIRModel.prototype = {
*/
_segmentForEvent: function(event, phase)
{
this._setPhaseForEvent(event, phase);
return new WebInspector.Segment(event.startTime, event.endTime, phase);
},

/**
* @param {!WebInspector.TracingModel.AsyncEvent} startEvent
* @param {!WebInspector.TracingModel.AsyncEvent} endEvent
* @param {!WebInspector.TimelineIRModel.Phases} phase
* @return {!WebInspector.Segment}
*/
_segmentForEventRange: function(startEvent, endEvent, phase)
{
this._setPhaseForEvent(startEvent, phase);
this._setPhaseForEvent(endEvent, phase);
return new WebInspector.Segment(startEvent.startTime, endEvent.endTime, phase);
},

/**
* @param {!WebInspector.TracingModel.AsyncEvent} asyncEvent
* @param {!WebInspector.TimelineIRModel.Phases} phase
*/
_setPhaseForEvent: function(asyncEvent, phase)
{
asyncEvent.steps[0][WebInspector.TimelineIRModel._eventIRPhase] = phase;
},

/**
* @return {!Array<!WebInspector.Segment>}
*/
Expand Down
3 changes: 2 additions & 1 deletion front_end/timeline/TimelineUIUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,8 @@ WebInspector.TimelineUIUtils._interactionPhaseStyles = function()
[WebInspector.TimelineIRModel.Phases.Scroll, {color: "hsl(256, 67%, 70%)", label: WebInspector.UIString("Scroll")}],
[WebInspector.TimelineIRModel.Phases.Fling, {color: "hsl(256, 67%, 70%)", label: WebInspector.UIString("Fling")}],
[WebInspector.TimelineIRModel.Phases.Drag, {color: "hsl(256, 67%, 70%)", label: WebInspector.UIString("Drag")}],
[WebInspector.TimelineIRModel.Phases.Animation, {color: "hsl(256, 67%, 70%)", label: WebInspector.UIString("Animation")}]
[WebInspector.TimelineIRModel.Phases.Animation, {color: "hsl(256, 67%, 70%)", label: WebInspector.UIString("Animation")}],
[WebInspector.TimelineIRModel.Phases.Uncategorized, {color: "hsl(0, 0%, 87%)", label: WebInspector.UIString("Uncategorized")}]
]);
WebInspector.TimelineUIUtils._interactionPhaseStylesMap = map;
}
Expand Down
63 changes: 42 additions & 21 deletions front_end/ui_lazy/FlameChart.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ WebInspector.FlameChart = function(dataProvider, flameChartDelegate, groupExpans
/** @const */
this._arrowSide = 8;
/** @const */
this._expansionArrowX = this._headerLeftPadding + this._arrowSide / 2;
this._expansionArrowIndent = this._headerLeftPadding + this._arrowSide / 2;
/** @const */
this._headerLabelXPadding = 3;
/** @const */
Expand Down Expand Up @@ -844,10 +844,10 @@ WebInspector.FlameChart.prototype = {
*/
_toggleGroupVisibility: function(groupIndex)
{
if (!this._isGroupCollapsible(groupIndex))
return;
var groups = this._rawTimelineData.groups;
var group = groups[groupIndex];
if (!group.style.collapsible)
return;
group.expanded = !group.expanded;
this._groupExpansionState[group.name] = group.expanded;
if (this._groupExpansionSetting)
Expand Down Expand Up @@ -1368,7 +1368,7 @@ WebInspector.FlameChart.prototype = {
context.translate(0, -top);

context.fillStyle = WebInspector.themeSupport.patchColor("#eee", colorUsage.Background);
forEachGroup((offset, index, group) => {
forEachGroup.call(this, (offset, index, group) => {
var paddingHeight = group.style.padding;
if (paddingHeight < 5)
return;
Expand All @@ -1379,20 +1379,22 @@ WebInspector.FlameChart.prototype = {

context.strokeStyle = WebInspector.themeSupport.patchColor("#bbb", colorUsage.Background);
context.beginPath();
forEachGroup((offset, index, group, isFirst) => {
forEachGroup.call(this, (offset, index, group, isFirst) => {
if (isFirst || group.style.padding < 4)
return;
hLine(offset - 2.5);
});
hLine(lastGroupOffset + 0.5);
context.stroke();

forEachGroup((offset, index, group) => {
if (group.style.shareHeaderLine)
forEachGroup.call(this, (offset, index, group) => {
if (group.style.useFirstLineForOverview)
return;
if ((!group.style.collapsible || group.expanded)) {
context.fillStyle = group.style.backgroundColor;
context.fillRect(0, offset, width, group.style.height);
if (!this._isGroupCollapsible(index) || group.expanded) {
if (!group.style.shareHeaderLine) {
context.fillStyle = group.style.backgroundColor;
context.fillRect(0, offset, width, group.style.height);
}
return;
}
var nextGroup = index + 1;
Expand All @@ -1403,23 +1405,23 @@ WebInspector.FlameChart.prototype = {
});

context.save();
forEachGroup((offset, index, group) => {
forEachGroup.call(this, (offset, index, group) => {
context.font = group.style.font;
if (group.style.collapsible && !group.expanded || group.style.shareHeaderLine) {
if (this._isGroupCollapsible(index) && !group.expanded || group.style.shareHeaderLine) {
var width = this._labelWidthForGroup(context, group);
context.fillStyle = WebInspector.Color.parse(group.style.backgroundColor).setAlpha(0.7).asString(null);
context.fillRect(this._headerLeftPadding - this._headerLabelXPadding, offset + this._headerLabelYPadding, width, barHeight - 2 * this._headerLabelYPadding);
}
context.fillStyle = group.style.color;
context.fillText(group.name, Math.floor(this._expansionArrowX + this._arrowSide), offset + textBaseHeight);
context.fillText(group.name, Math.floor(this._expansionArrowIndent * (group.style.nestingLevel + 1) + this._arrowSide), offset + textBaseHeight);
});
context.restore();

context.fillStyle = WebInspector.themeSupport.patchColor("#6e6e6e", colorUsage.Foreground);
context.beginPath();
forEachGroup((offset, index, group) => {
if (group.style.collapsible)
drawExpansionArrow.call(this, this._expansionArrowX, offset + textBaseHeight - this._arrowSide / 2, !!group.expanded)
forEachGroup.call(this, (offset, index, group) => {
if (this._isGroupCollapsible(index))
drawExpansionArrow.call(this, this._expansionArrowIndent * (group.style.nestingLevel + 1), offset + textBaseHeight - this._arrowSide / 2, !!group.expanded)
});
context.fill();

Expand Down Expand Up @@ -1459,6 +1461,7 @@ WebInspector.FlameChart.prototype = {

/**
* @param {function(number, number, !WebInspector.FlameChart.Group, boolean)} callback
* @this {WebInspector.FlameChart}
*/
function forEachGroup(callback)
{
Expand All @@ -1475,7 +1478,7 @@ WebInspector.FlameChart.prototype = {
firstGroup = false;
}
var parentGroupVisible = groupStack.peekLast().visible;
var thisGroupVisible = parentGroupVisible && (!group.style.collapsible || group.expanded);
var thisGroupVisible = parentGroupVisible && (!this._isGroupCollapsible(i) || group.expanded);
groupStack.push({nestingLevel: group.style.nestingLevel, visible: thisGroupVisible});
if (!parentGroupVisible || groupTop + group.style.height < top)
continue;
Expand All @@ -1491,7 +1494,7 @@ WebInspector.FlameChart.prototype = {
*/
_labelWidthForGroup: function(context, group)
{
return this._measureWidth(context, group.name) + 1.5 * this._arrowSide + 2 * this._headerLabelXPadding;
return this._measureWidth(context, group.name) + this._expansionArrowIndent * (group.style.nestingLevel + 1) + 2 * this._headerLabelXPadding;
},

/**
Expand Down Expand Up @@ -1706,7 +1709,7 @@ WebInspector.FlameChart.prototype = {
groupStack.pop();
nextLevel = false;
}
var thisGroupIsVisible = style.collapsible ? groups[groupIndex].expanded : true;
var thisGroupIsVisible = groupIndex >= 0 && this._isGroupCollapsible(groupIndex) ? groups[groupIndex].expanded : true;
var parentGroupIsVisible = groupStack.peekLast().visible;
visible = thisGroupIsVisible && parentGroupIsVisible;
groupStack.push({nestingLevel: style.nestingLevel, visible: visible});
Expand All @@ -1716,17 +1719,35 @@ WebInspector.FlameChart.prototype = {
if (parentGroupIsVisible && !style.shareHeaderLine)
currentOffset += style.height;
}
var thisLevelIsVisible = visible || groupIndex >= 0 && groups[groupIndex].style.useFirstLineForOverview && level === groups[groupIndex].startLevel;
var isFirstOnLevel = groupIndex >= 0 && level === groups[groupIndex].startLevel;
var thisLevelIsVisible = visible || isFirstOnLevel && groups[groupIndex].style.useFirstLineForOverview;
this._visibleLevels[level] = thisLevelIsVisible;
this._visibleLevelOffsets[level] = currentOffset;
if (thisLevelIsVisible)
if (thisLevelIsVisible || (parentGroupIsVisible && style.shareHeaderLine && isFirstOnLevel))
currentOffset += this._barHeight;
}
if (groupIndex >= 0)
this._groupOffsets[groupIndex + 1] = currentOffset;
this._visibleLevelOffsets[level] = currentOffset;
},

/**
* @param {number} index
*/
_isGroupCollapsible: function(index)
{
var groups = this._rawTimelineData.groups || [];
var style = groups[index].style;
if (!style.shareHeaderLine || !style.collapsible)
return !!style.collapsible;
var isLastGroup = index + 1 >= groups.length;
if (!isLastGroup && groups[index + 1].style.nestingLevel > style.nestingLevel)
return true;
var nextGroupLevel = isLastGroup ? this._dataProvider.maxStackDepth() : groups[index + 1].startLevel;
// For groups that only have one line and share header line, pretend these are not collapsible.
return nextGroupLevel !== groups[index].startLevel + 1;
},

/**
* @param {number} entryIndex
*/
Expand Down

0 comments on commit 0af8110

Please sign in to comment.