/**
* @constructor
* @implements {WebInspector.FlameChartDataProvider}
- * @implements {WebInspector.TimelineFlameChart.SelectionProvider}
- * @param {!WebInspector.TimelineModelImpl} model
+ * @param {!WebInspector.TracingTimelineModel} model
* @param {!WebInspector.TimelineFrameModelBase} frameModel
*/
WebInspector.TimelineFlameChartDataProvider = function(model, frameModel)
{
WebInspector.FlameChartDataProvider.call(this);
+ this.reset();
this._model = model;
this._frameModel = frameModel;
this._font = "12px " + WebInspector.fontFamily();
this._linkifier = new WebInspector.Linkifier();
+ this._filters = [];
+ this.addFilter(WebInspector.TracingTimelineUIUtils.hiddenEventsFilter());
+ this.addFilter(new WebInspector.TracingTimelineModel.ExclusiveEventNameFilter([WebInspector.TracingTimelineModel.RecordType.Program]));
}
+WebInspector.TimelineFlameChartDataProvider.InstantEventVisibleDurationMs = 0.01;
+WebInspector.TimelineFlameChartDataProvider.JSFrameCoalsceThresholdMs = 1.1;
+
WebInspector.TimelineFlameChartDataProvider.prototype = {
/**
* @return {number}
*/
entryTitle: function(entryIndex)
{
- var record = this._records[entryIndex];
- if (record === this._cpuThreadRecord)
- return WebInspector.UIString("CPU");
- else if (record === this._gpuThreadRecord)
- return WebInspector.UIString("GPU");
- var details = WebInspector.TimelineUIUtilsImpl.buildDetailsNode(record, this._linkifier, this._model.loadedFromFile());
- var title = WebInspector.TimelineUIUtilsImpl.recordTitle(record);
- return details ? WebInspector.UIString("%s (%s)", title, details.textContent) : title;
+ var event = this._entryEvents[entryIndex];
+ if (event) {
+ var name = WebInspector.TracingTimelineUIUtils.styleForTraceEvent(event.name).title;
+ // TODO(yurys): support event dividers
+ var details = WebInspector.TracingTimelineUIUtils.buildDetailsNodeForTraceEvent(event, this._linkifier);
+ if (event.name === WebInspector.TracingTimelineModel.RecordType.JSFrame && details)
+ return details.textContent;
+ return details ? WebInspector.UIString("%s (%s)", name, details.textContent) : name;
+ }
+ var title = this._entryIndexToTitle[entryIndex];
+ if (!title) {
+ title = WebInspector.UIString("Unexpected entryIndex %d", entryIndex);
+ console.error(title);
+ }
+ return title;
},
/**
*/
dividerOffsets: function(startTime, endTime)
{
- // While we have tracing and timeline flame chart on screen at a time,
- // we don't want to render frame-based grid.
return null;
},
- reset: function()
- {
- this._timelineData = null;
- },
-
- /**
- * @return {!WebInspector.FlameChart.TimelineData}
- */
- timelineData: function()
- {
- if (this._timelineData)
- return this._timelineData;
-
- this._linkifier.reset();
-
- /**
- * @type {?WebInspector.FlameChart.TimelineData}
- */
- this._timelineData = {
- entryLevels: [],
- entryTotalTimes: [],
- entryStartTimes: []
- };
-
- this._records = [];
- this._entryThreadDepths = {};
- this._minimumBoundary = this._model.minimumRecordTime();
-
- var cpuThreadRecordPayload = { type: WebInspector.TimelineModel.RecordType.Program };
- this._cpuThreadRecord = new WebInspector.TimelineModel.RecordImpl(this._model, /** @type {!TimelineAgent.TimelineEvent} */ (cpuThreadRecordPayload), null);
- this._pushRecord(this._cpuThreadRecord, 0, this.minimumBoundary(), Math.max(this._model.maximumRecordTime(), this.totalTime() + this.minimumBoundary()));
-
- this._gpuThreadRecord = null;
-
- var records = this._model.records();
- for (var i = 0; i < records.length; ++i) {
- var record = records[i];
- var thread = record.thread();
- if (thread === "gpu")
- continue;
- if (!thread) {
- for (var j = 0; j < record.children().length; ++j)
- this._appendRecord(record.children()[j], 1);
- } else {
- var visible = this._appendRecord(records[i], 1);
- if (visible && !this._gpuThreadRecord) {
- var gpuThreadRecordPayload = { type: WebInspector.TimelineModel.RecordType.Program };
- this._gpuThreadRecord = new WebInspector.TimelineModel.RecordImpl(this._model, /** @type {!TimelineAgent.TimelineEvent} */ (gpuThreadRecordPayload), null);
- this._pushRecord(this._gpuThreadRecord, 0, this.minimumBoundary(), Math.max(this._model.maximumRecordTime(), this.totalTime() + this.minimumBoundary()));
- }
- }
- }
-
- var cpuStackDepth = Math.max(4, this._entryThreadDepths[undefined]);
- delete this._entryThreadDepths[undefined];
- this._maxStackDepth = cpuStackDepth;
-
- if (this._gpuThreadRecord) {
- // We have multiple threads, update levels.
- var threadBaselines = {};
- var threadBaseline = cpuStackDepth + 2;
-
- for (var thread in this._entryThreadDepths) {
- threadBaselines[thread] = threadBaseline;
- threadBaseline += this._entryThreadDepths[thread];
- }
- this._maxStackDepth = threadBaseline;
-
- for (var i = 0; i < this._records.length; ++i) {
- var record = this._records[i];
- var level = this._timelineData.entryLevels[i];
- if (record === this._cpuThreadRecord)
- level = 0;
- else if (record === this._gpuThreadRecord)
- level = cpuStackDepth + 2;
- else if (record.thread())
- level += threadBaselines[record.thread()];
- this._timelineData.entryLevels[i] = level;
- }
- }
-
- return this._timelineData;
- },
-
- /**
- * @return {number}
- */
- minimumBoundary: function()
- {
- return this._minimumBoundary;
- },
-
- /**
- * @return {number}
- */
- totalTime: function()
- {
- return Math.max(1000, this._model.maximumRecordTime() - this._model.minimumRecordTime());
- },
-
- /**
- * @return {number}
- */
- maxStackDepth: function()
- {
- return this._maxStackDepth;
- },
-
/**
- * @param {!WebInspector.TimelineModel.Record} record
- * @param {number} level
- * @return {boolean}
- */
- _appendRecord: function(record, level)
- {
- var result = false;
- if (!this._model.isVisible(record)) {
- for (var i = 0; i < record.children().length; ++i)
- result = this._appendRecord(record.children()[i], level) || result;
- return result;
- }
-
- this._pushRecord(record, level, record.startTime(), record.endTime());
- for (var i = 0; i < record.children().length; ++i)
- this._appendRecord(record.children()[i], level + 1);
- return true;
- },
-
- /**
- * @param {!WebInspector.TimelineModel.Record} record
- * @param {number} level
- * @param {number} startTime
- * @param {number} endTime
- * @return {number}
+ * @override
+ * @param {number} index
+ * @return {string}
*/
- _pushRecord: function(record, level, startTime, endTime)
+ markerColor: function(index)
{
- var index = this._records.length;
- this._records.push(record);
- this._timelineData.entryStartTimes[index] = startTime;
- this._timelineData.entryLevels[index] = level;
- this._timelineData.entryTotalTimes[index] = endTime - startTime;
- this._entryThreadDepths[record.thread()] = Math.max(level, this._entryThreadDepths[record.thread()] || 0);
- return index;
+ var event = this._markerEvents[index];
+ return WebInspector.TracingTimelineUIUtils.markerEventColor(event.name);
},
/**
- * @param {number} entryIndex
- * @return {?Array.<!{title: string, text: string}>}
+ * @override
+ * @param {number} index
+ * @return {string}
*/
- prepareHighlightedEntryInfo: function(entryIndex)
+ markerTitle: function(index)
{
- return null;
+ var event = this._markerEvents[index];
+ return WebInspector.TracingTimelineUIUtils.eventTitle(event, this._model);
},
- /**
- * @param {number} entryIndex
- * @return {boolean}
- */
- canJumpToEntry: function(entryIndex)
+ reset: function()
{
- return false;
+ this._timelineData = null;
+ /** @type {!Array.<!WebInspector.TracingModel.Event>} */
+ this._entryEvents = [];
+ this._entryIndexToTitle = {};
+ this._markerEvents = [];
+ this._entryIndexToFrame = {};
},
/**
- * @param {number} entryIndex
- * @return {string}
+ * @return {!WebInspector.FlameChart.TimelineData}
*/
- entryColor: function(entryIndex)
+ timelineData: function()
{
- var record = this._records[entryIndex];
- if (record === this._cpuThreadRecord || record === this._gpuThreadRecord)
- return "#555";
+ if (this._timelineData)
+ return this._timelineData;
- if (record.type() === WebInspector.TimelineModel.RecordType.JSFrame)
- return WebInspector.TimelineFlameChartDataProvider.jsFrameColorGenerator().colorForID(record.data()["functionName"]);
+ this._timelineData = new WebInspector.FlameChart.TimelineData([], [], []);
- return record.category().fillColorStop1;
+ this._minimumBoundary = this._model.minimumRecordTime();
+ this._timeSpan = this._model.isEmpty() ? 1000 : this._model.maximumRecordTime() - this._minimumBoundary;
+ this._currentLevel = 0;
+ this._appendFrameBars(this._frameModel.frames());
+ this._appendThreadTimelineData(WebInspector.UIString("Main Thread"), this._model.mainThreadEvents());
+ var threads = this._model.virtualThreads();
+ for (var i = 0; i < threads.length; i++) {
+ var thread = threads[i];
+ this._appendThreadTimelineData(thread.name, thread.events);
+ }
+ return this._timelineData;
},
-
/**
- * @param {number} entryIndex
- * @param {!CanvasRenderingContext2D} context
- * @param {?string} text
- * @param {number} barX
- * @param {number} barY
- * @param {number} barWidth
- * @param {number} barHeight
- * @param {function(number):number} offsetToPosition
- * @return {boolean}
+ * @param {string} headerName
+ * @param {!Array.<!WebInspector.TracingModel.Event>} traceEvents
*/
- decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, barHeight, offsetToPosition)
+ _appendThreadTimelineData: function(headerName, traceEvents)
{
- if (barWidth < 5)
- return false;
-
- var record = this._records[entryIndex];
- var timelineData = this._timelineData;
-
- var category = record.category();
- // Paint text using white color on dark background.
- if (text) {
- context.save();
- context.fillStyle = "white";
- context.shadowColor = "rgba(0, 0, 0, 0.1)";
- context.shadowOffsetX = 1;
- context.shadowOffsetY = 1;
- context.font = this._font;
- context.fillText(text, barX + this.textPadding(), barY + barHeight - this.textBaseline());
- context.restore();
+ var maxStackDepth = 0;
+ var openEvents = [];
+ var headerAppended = false;
+ var events = traceEvents;
+ if (WebInspector.experimentsSettings.timelineJSCPUProfile.isEnabled()) {
+ var jsFrameEvents = this._generateJSFrameEvents(traceEvents);
+ events = jsFrameEvents.mergeOrdered(traceEvents, WebInspector.TracingModel.Event.orderedCompareStartTime);
}
-
- if (record.children().length) {
- var entryStartTime = timelineData.entryStartTimes[entryIndex];
- var barSelf = offsetToPosition(entryStartTime + record.selfTime())
-
- context.beginPath();
- context.fillStyle = category.backgroundColor;
- context.rect(barSelf, barY, barX + barWidth - barSelf, barHeight);
- context.fill();
-
- // Paint text using dark color on light background.
- if (text) {
- context.save();
- context.clip();
- context.fillStyle = category.borderColor;
- context.shadowColor = "rgba(0, 0, 0, 0.1)";
- context.shadowOffsetX = 1;
- context.shadowOffsetY = 1;
- context.fillText(text, barX + this.textPadding(), barY + barHeight - this.textBaseline());
- context.restore();
+ for (var i = 0; i < events.length; ++i) {
+ var e = events[i];
+ // FIXME: clean up once phase name is unified between Blink and Chromium.
+ if (!e.endTime && e.phase !== WebInspector.TracingModel.Phase.Instant && e.phase !== "I")
+ continue;
+ if (WebInspector.TracingTimelineUIUtils.isMarkerEvent(e)) {
+ this._markerEvents.push(e);
+ this._timelineData.markerTimestamps.push(e.startTime);
}
+ if (!this._isVisible(e))
+ continue;
+ while (openEvents.length && openEvents.peekLast().endTime <= e.startTime)
+ openEvents.pop();
+ if (!headerAppended) {
+ this._appendHeaderRecord(headerName, this._currentLevel++);
+ headerAppended = true;
+ }
+ this._appendEvent(e, this._currentLevel + openEvents.length);
+ maxStackDepth = Math.max(maxStackDepth, openEvents.length + 1);
+ if (e.endTime)
+ openEvents.push(e);
}
-
- if (record.warnings()) {
- context.save();
-
- context.rect(barX, barY, barWidth, this.barHeight());
- context.clip();
-
- context.beginPath();
- context.fillStyle = record.warnings() ? "red" : "rgba(255, 0, 0, 0.5)";
- context.moveTo(barX + barWidth - 15, barY + 1);
- context.lineTo(barX + barWidth - 1, barY + 1);
- context.lineTo(barX + barWidth - 1, barY + 15);
- context.fill();
-
- context.restore();
- }
-
- return true;
+ this._currentLevel += maxStackDepth;
+ if (headerAppended)
+ ++this._currentLevel;
},
/**
- * @param {number} entryIndex
- * @return {boolean}
+ * @param {!Array.<!WebInspector.TimelineFrame>} frames
*/
- forceDecoration: function(entryIndex)
+ _appendFrameBars: function(frames)
{
- var record = this._records[entryIndex];
- return !!record.warnings();
+ this._frameBarsLevel = this._currentLevel++;
+ for (var i = 0; i < frames.length; ++i)
+ this._appendFrame(frames[i]);
},
/**
- * @param {number} entryIndex
- * @return {?{startTime: number, endTime: number}}
+ * @param {!Array.<!WebInspector.TracingModel.Event>} events
+ * @return {!Array.<!WebInspector.TracingModel.Event>}
*/
- highlightTimeRange: function(entryIndex)
+ _generateJSFrameEvents: function(events)
{
- var record = this._records[entryIndex];
- if (record === this._cpuThreadRecord || record === this._gpuThreadRecord)
- return null;
- return {
- startTime: record.startTime(),
- endTime: record.endTime()
- };
- },
-
- /**
- * @return {number}
- */
- paddingLeft: function()
- {
- return 0;
- },
-
- /**
- * @param {number} entryIndex
- * @return {string}
- */
- textColor: function(entryIndex)
- {
- return "white";
- },
-
- /**
- * @param {number} entryIndex
- * @return {?WebInspector.TimelineSelection}
- */
- createSelection: function(entryIndex)
- {
- var record = this._records[entryIndex];
- if (record instanceof WebInspector.TimelineModel.RecordImpl) {
- this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromRecord(record), entryIndex);
- return this._lastSelection.timelineSelection;
+ function equalFrames(frame1, frame2)
+ {
+ return frame1.scriptId === frame2.scriptId && frame1.functionName === frame2.functionName;
}
- return null;
- },
-
- /**
- * @param {?WebInspector.TimelineSelection} selection
- * @return {number}
- */
- entryIndexForSelection: function(selection)
- {
- if (!selection || selection.type() !== WebInspector.TimelineSelection.Type.Record)
- return -1;
- var record = /** @type{!WebInspector.TimelineModel.Record} */ (selection.object());
- if (this._lastSelection && this._lastSelection.timelineSelection.object() === record)
- return this._lastSelection.entryIndex;
- var entryRecords = this._records;
- for (var entryIndex = 0; entryIndex < entryRecords.length; ++entryIndex) {
- if (entryRecords[entryIndex] === record) {
- this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromRecord(record), entryIndex);
- return entryIndex;
- }
+ function eventEndTime(e)
+ {
+ return e.endTime || e.startTime + WebInspector.TimelineFlameChartDataProvider.InstantEventVisibleDurationMs;
}
- return -1;
- }
-}
-
-/**
- * @constructor
- * @implements {WebInspector.FlameChartDataProvider}
- * @implements {WebInspector.TimelineFlameChart.SelectionProvider}
- * @param {!WebInspector.TracingTimelineModel} model
- * @param {!WebInspector.TimelineFrameModelBase} frameModel
- * @param {!WebInspector.Target} target
- */
-WebInspector.TracingBasedTimelineFlameChartDataProvider = function(model, frameModel, target)
-{
- WebInspector.FlameChartDataProvider.call(this);
- this._model = model;
- this._frameModel = frameModel;
- this._target = target;
- this._font = "12px " + WebInspector.fontFamily();
- this._linkifier = new WebInspector.Linkifier();
- this._palette = new WebInspector.TraceViewPalette();
- this._entryIndexToTitle = {};
-}
-
-WebInspector.TracingBasedTimelineFlameChartDataProvider.prototype = {
- /**
- * @return {number}
- */
- barHeight: function()
- {
- return 20;
- },
-
- /**
- * @return {number}
- */
- textBaseline: function()
- {
- return 6;
- },
-
- /**
- * @return {number}
- */
- textPadding: function()
- {
- return 5;
- },
-
- /**
- * @param {number} entryIndex
- * @return {string}
- */
- entryFont: function(entryIndex)
- {
- return this._font;
- },
-
- /**
- * @param {number} entryIndex
- * @return {?string}
- */
- entryTitle: function(entryIndex)
- {
- var event = this._entryEvents[entryIndex];
- if (event) {
- var name = WebInspector.TracingTimelineUIUtils.styleForTraceEvent(event.name).title;
- // TODO(yurys): support event dividers
- var details = WebInspector.TracingTimelineUIUtils.buildDetailsNodeForTraceEvent(event, this._linkifier, false, this._target);
- return details ? WebInspector.UIString("%s (%s)", name, details.textContent) : name;
+ function isJSInvocationEvent(e)
+ {
+ switch (e.name) {
+ case WebInspector.TracingTimelineModel.RecordType.FunctionCall:
+ case WebInspector.TracingTimelineModel.RecordType.EvaluateScript:
+ return true;
+ }
+ return false;
}
- var title = this._entryIndexToTitle[entryIndex];
- if (!title) {
- title = WebInspector.UIString("Unexpected entryIndex %d", entryIndex);
- console.error(title);
+ var jsFrameEvents = [];
+ var stackTraceOpenEvents = [];
+ var currentJSInvocationEndTime = 0;
+ var coalesceThresholdMs = WebInspector.TimelineFlameChartDataProvider.JSFrameCoalsceThresholdMs;
+ for (var i = 0; i < events.length; ++i) {
+ var e = events[i];
+ if (e.startTime >= currentJSInvocationEndTime) {
+ stackTraceOpenEvents.length = 0;
+ currentJSInvocationEndTime = 0;
+ }
+ if (isJSInvocationEvent(e))
+ currentJSInvocationEndTime = e.endTime;
+ if (!currentJSInvocationEndTime)
+ continue;
+ if (!e.stackTrace)
+ continue;
+ while (stackTraceOpenEvents.length && eventEndTime(stackTraceOpenEvents.peekLast()) + coalesceThresholdMs <= e.startTime)
+ stackTraceOpenEvents.pop();
+ var numFrames = e.stackTrace.length;
+ for (var j = 0; j < numFrames && j < stackTraceOpenEvents.length; ++j) {
+ var frame = e.stackTrace[numFrames - 1 - j];
+ if (!equalFrames(frame, stackTraceOpenEvents[j].args["data"]))
+ break;
+ stackTraceOpenEvents[j].endTime = Math.max(stackTraceOpenEvents[j].endTime, eventEndTime(e));
+ stackTraceOpenEvents[j].duration = stackTraceOpenEvents[j].endTime - stackTraceOpenEvents[j].startTime;
+ }
+ stackTraceOpenEvents.length = j;
+ var timestampUs = e.startTime * 1000;
+ var durationUs = (e.duration || WebInspector.TimelineFlameChartDataProvider.InstantEventVisibleDurationMs) * 1000;
+ for (; j < numFrames; ++j) {
+ var frame = e.stackTrace[numFrames - 1 - j];
+ var payload = /** @type {!WebInspector.TracingModel.EventPayload} */ ({
+ ph: e.phase,
+ cat: WebInspector.TracingModel.DevToolsMetadataEventCategory,
+ name: WebInspector.TracingTimelineModel.RecordType.JSFrame,
+ ts: timestampUs,
+ dur: durationUs,
+ args: {
+ data: frame
+ }
+ });
+ var jsFrameEvent = new WebInspector.TracingModel.Event(payload, 0, e.thread);
+ stackTraceOpenEvents.push(jsFrameEvent);
+ jsFrameEvents.push(jsFrameEvent);
+ }
}
- return title;
+ return jsFrameEvents;
},
/**
- * @param {number} startTime
- * @param {number} endTime
- * @return {?Array.<number>}
+ * @param {!WebInspector.TracingTimelineModel.Filter} filter
*/
- dividerOffsets: function(startTime, endTime)
+ addFilter: function(filter)
{
- return null;
- },
-
- reset: function()
- {
- this._timelineData = null;
- /** @type {!Array.<!WebInspector.TracingModel.Event>} */
- this._entryEvents = [];
- this._entryIndexToTitle = {};
+ this._filters.push(filter);
},
/**
- * @return {!WebInspector.FlameChart.TimelineData}
+ * @param {!WebInspector.TracingModel.Event} event
+ * @return {boolean}
*/
- timelineData: function()
+ _isVisible: function(event)
{
- if (this._timelineData)
- return this._timelineData;
-
- /**
- * @type {?WebInspector.FlameChart.TimelineData}
- */
- this._timelineData = {
- entryLevels: [],
- entryTotalTimes: [],
- entryStartTimes: []
- };
-
- this._currentLevel = 0;
- this._minimumBoundary = this._model.minimumRecordTime();
- this._timeSpan = Math.max(this._model.maximumRecordTime() - this._minimumBoundary, 1000);
- this._appendHeaderRecord("CPU");
- var events = this._model.mainThreadEvents();
- var maxStackDepth = 0;
- for (var eventIndex = 0; eventIndex < events.length; ++eventIndex) {
- var event = events[eventIndex];
- var category = event.category;
- if (category !== "disabled-by-default-devtools.timeline" && category !== "devtools")
- continue;
- if (event.duration || event.phase === WebInspector.TracingModel.Phase.Instant) {
- this._appendEvent(event);
- if (maxStackDepth < event.level)
- maxStackDepth = event.level;
- }
- }
- this._currentLevel += maxStackDepth + 1;
-
- this._appendHeaderRecord("GPU");
- return this._timelineData;
+ return this._filters.every(function (filter) { return filter.accept(event); });
},
/**
{
var event = this._entryEvents[entryIndex];
if (!event)
- return "#555";
+ return this._entryIndexToFrame[entryIndex] ? "white" : "#555";
+ if (event.name === WebInspector.TracingTimelineModel.RecordType.JSFrame)
+ return WebInspector.TimelineFlameChartDataProvider.jsFrameColorGenerator().colorForID(event.args["data"]["functionName"]);
var style = WebInspector.TracingTimelineUIUtils.styleForTraceEvent(event.name);
return style.category.fillColorStop1;
},
*/
decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, barHeight, offsetToPosition)
{
+ var frame = this._entryIndexToFrame[entryIndex];
+ if (frame) {
+ context.save();
+
+ context.translate(0.5, 0.5);
+
+ context.beginPath();
+ context.moveTo(barX, barY);
+ context.lineTo(barX, context.canvas.height);
+ context.strokeStyle = "rgba(100, 100, 100, 0.4)";
+ context.setLineDash([5]);
+ context.stroke();
+ context.setLineDash([]);
+
+
+ var padding = 4 * window.devicePixelRatio;
+ barX += padding;
+ barWidth -= 2 * padding;
+ barY += padding;
+ barHeight -= 2 * padding;
+
+ var cornerRadis = 3;
+ var radiusY = cornerRadis;
+ var radiusX = Math.min(cornerRadis, barWidth / 2);
+
+ context.beginPath();
+ context.moveTo(barX + radiusX, barY);
+ context.lineTo(barX + barWidth - radiusX, barY);
+ context.quadraticCurveTo(barX + barWidth, barY, barX + barWidth, barY + radiusY);
+ context.lineTo(barX + barWidth, barY + barHeight - radiusY);
+ context.quadraticCurveTo(barX + barWidth, barY + barHeight, barX + barWidth - radiusX, barY + barHeight);
+ context.lineTo(barX + radiusX, barY + barHeight);
+ context.quadraticCurveTo(barX, barY + barHeight, barX, barY + barHeight - radiusY);
+ context.lineTo(barX, barY + radiusY);
+ context.quadraticCurveTo(barX, barY, barX + radiusX, barY);
+ context.closePath();
+
+ context.fillStyle = "rgba(200, 200, 200, 0.8)";
+ context.fill();
+ context.strokeStyle = "rgba(150, 150, 150, 0.8)";
+ context.stroke();
+
+ var frameDurationText = Number.millisToString(frame.duration, true);
+ var textWidth = context.measureText(frameDurationText).width;
+ if (barWidth > textWidth) {
+ context.fillStyle = "#555";
+ context.fillText(frameDurationText, barX + ((barWidth - textWidth) >> 1), barY + barHeight - 2);
+ }
+ context.restore();
+ return true;
+ }
if (barWidth < 5)
return false;
- var timelineData = this._timelineData;
-
// Paint text using white color on dark background.
if (text) {
context.save();
{
var event = this._entryEvents[entryIndex];
if (!event)
- return false;
+ return !!this._entryIndexToFrame[entryIndex];
return !!event.warning;
},
*/
highlightTimeRange: function(entryIndex)
{
- var event = this._entryEvents[entryIndex];
+ var event = this._entryEvents[entryIndex] || this._entryIndexToFrame[entryIndex];
if (!event)
return null;
return {
startTime: event.startTime,
- endTime: event.endTime
+ endTime: event.endTime || event.startTime + WebInspector.TimelineFlameChartDataProvider.InstantEventVisibleDurationMs
}
},
/**
* @param {string} title
+ * @param {number} level
*/
- _appendHeaderRecord: function(title)
+ _appendHeaderRecord: function(title, level)
{
var index = this._entryEvents.length;
this._entryIndexToTitle[index] = title;
this._entryEvents.push(null);
- this._timelineData.entryLevels[index] = this._currentLevel++;
+ this._timelineData.entryLevels[index] = level;
this._timelineData.entryTotalTimes[index] = this._timeSpan;
this._timelineData.entryStartTimes[index] = this._minimumBoundary;
},
/**
* @param {!WebInspector.TracingModel.Event} event
+ * @param {number} level
*/
- _appendEvent: function(event)
+ _appendEvent: function(event, level)
{
var index = this._entryEvents.length;
this._entryEvents.push(event);
- this._timelineData.entryLevels[index] = this._currentLevel + event.level;
- this._timelineData.entryTotalTimes[index] = event.duration || 1;
+ this._timelineData.entryLevels[index] = level;
+ this._timelineData.entryTotalTimes[index] = event.duration || WebInspector.TimelineFlameChartDataProvider.InstantEventVisibleDurationMs;
this._timelineData.entryStartTimes[index] = event.startTime;
},
/**
+ * @param {!WebInspector.TimelineFrame} frame
+ */
+ _appendFrame: function(frame)
+ {
+ var index = this._entryEvents.length;
+ this._entryEvents.push(null);
+ this._entryIndexToFrame[index] = frame;
+ this._entryIndexToTitle[index] = Number.millisToString(frame.duration, true);
+ this._timelineData.entryLevels[index] = this._frameBarsLevel;
+ this._timelineData.entryTotalTimes[index] = frame.duration;
+ this._timelineData.entryStartTimes[index] = frame.startTime;
+ },
+
+ /**
* @param {number} entryIndex
* @return {?WebInspector.TimelineSelection}
*/
createSelection: function(entryIndex)
{
var event = this._entryEvents[entryIndex];
- if (!event)
- return null;
- this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromTraceEvent(event), entryIndex);
- return this._lastSelection.timelineSelection;
+ if (event) {
+ this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromTraceEvent(event), entryIndex);
+ return this._lastSelection.timelineSelection;
+ }
+ var frame = this._entryIndexToFrame[entryIndex];
+ if (frame) {
+ this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromFrame(frame), entryIndex);
+ return this._lastSelection.timelineSelection;
+ }
+ return null;
},
/**
*/
entryIndexForSelection: function(selection)
{
- if (!selection || selection.type() !== WebInspector.TimelineSelection.Type.TraceEvent)
+ if (!selection)
return -1;
- var event = /** @type{!WebInspector.TracingModel.Event} */ (selection.object());
- if (this._lastSelection && this._lastSelection.timelineSelection.object() === event)
+
+ if (this._lastSelection && this._lastSelection.timelineSelection.object() === selection.object())
return this._lastSelection.entryIndex;
- var entryEvents = this._entryEvents;
- for (var entryIndex = 0; entryIndex < entryEvents.length; ++entryIndex) {
- if (entryEvents[entryIndex] === event) {
- this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromTraceEvent(event), entryIndex);
- return entryIndex;
+ switch (selection.type()) {
+ case WebInspector.TimelineSelection.Type.TraceEvent:
+ var event = /** @type{!WebInspector.TracingModel.Event} */ (selection.object());
+ var entryEvents = this._entryEvents;
+ for (var entryIndex = 0; entryIndex < entryEvents.length; ++entryIndex) {
+ if (entryEvents[entryIndex] === event) {
+ this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromTraceEvent(event), entryIndex);
+ return entryIndex;
+ }
}
+ break;
+ case WebInspector.TimelineSelection.Type.Frame:
+ var frame = /** @type {!WebInspector.TimelineFrame} */ (selection.object());
+ for (var frameIndex in this._entryIndexToFrame) {
+ if (this._entryIndexToFrame[frameIndex] === frame) {
+ this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromFrame(frame), Number(frameIndex));
+ return Number(frameIndex);
+ }
+ }
+ break;
}
return -1;
}
* @implements {WebInspector.TimelineModeView}
* @implements {WebInspector.FlameChartDelegate}
* @param {!WebInspector.TimelineModeViewDelegate} delegate
- * @param {!WebInspector.TimelineModel} model
- * @param {?WebInspector.TracingTimelineModel} tracingModel
+ * @param {!WebInspector.TracingTimelineModel} tracingModel
* @param {!WebInspector.TimelineFrameModelBase} frameModel
*/
-WebInspector.TimelineFlameChart = function(delegate, model, tracingModel, frameModel)
+WebInspector.TimelineFlameChart = function(delegate, tracingModel, frameModel)
{
WebInspector.VBox.call(this);
this.element.classList.add("timeline-flamechart");
this.registerRequiredCSS("flameChart.css");
this._delegate = delegate;
- this._model = model;
- this._dataProvider = tracingModel
- ? new WebInspector.TracingBasedTimelineFlameChartDataProvider(tracingModel, frameModel, model.target())
- : new WebInspector.TimelineFlameChartDataProvider(/** @type {!WebInspector.TimelineModelImpl} */(model), frameModel);
+ this._model = tracingModel;
+ this._dataProvider = new WebInspector.TimelineFlameChartDataProvider(tracingModel, frameModel)
this._mainView = new WebInspector.FlameChart(this._dataProvider, this, true);
this._mainView.show(this.element);
this._model.addEventListener(WebInspector.TimelineModel.Events.RecordingStarted, this._onRecordingStarted, this);
refreshRecords: function(textFilter)
{
this._dataProvider.reset();
- this._mainView._scheduleUpdate();
+ this._mainView.scheduleUpdate();
},
wasShown: function()
{
- this._mainView._scheduleUpdate();
+ this._mainView.scheduleUpdate();
},
-
/**
* @return {!WebInspector.View}
*/
this._automaticallySizeWindow = false;
this._delegate.requestWindowTimes(minimumRecordTime, minimumRecordTime + 1000);
}
- this._mainView._scheduleUpdate();
+ this._mainView.scheduleUpdate();
} else {
if (!this._pendingUpdateTimer)
this._pendingUpdateTimer = window.setTimeout(this._updateOnAddRecord.bind(this), 300);
_updateOnAddRecord: function()
{
delete this._pendingUpdateTimer;
- this._mainView._scheduleUpdate();
+ this._mainView.scheduleUpdate();
},
/**
this.timelineSelection = selection;
this.entryIndex = entryIndex;
}
-
-/**
- * @interface
- */
-WebInspector.TimelineFlameChart.SelectionProvider = function() { }
-
-WebInspector.TimelineFlameChart.SelectionProvider.prototype = {
- /**
- * @param {number} entryIndex
- * @return {?WebInspector.TimelineSelection}
- */
- createSelection: function(entryIndex) { },
- /**
- * @param {?WebInspector.TimelineSelection} selection
- * @return {number}
- */
- entryIndexForSelection: function(selection) { }
-}