blob: 252cbf061a73dfe16dfcf618cde34ce857b12c5e [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @constructor
* @param {!WebInspector.TracingManager} tracingManager
* @param {!WebInspector.TracingModel} tracingModel
* @param {!WebInspector.TimelineModel.Filter} recordFilter
* @extends {WebInspector.TimelineModel}
*/
WebInspector.TracingTimelineModel = function(tracingManager, tracingModel, recordFilter)
{
WebInspector.TimelineModel.call(this);
this._tracingManager = tracingManager;
this._tracingModel = tracingModel;
this._recordFilter = recordFilter;
this._tracingManager.addEventListener(WebInspector.TracingManager.Events.TracingStarted, this._onTracingStarted, this);
this._tracingManager.addEventListener(WebInspector.TracingManager.Events.EventsCollected, this._onEventsCollected, this);
this._tracingManager.addEventListener(WebInspector.TracingManager.Events.TracingComplete, this._onTracingComplete, this);
this.reset();
}
WebInspector.TracingTimelineModel.RecordType = {
Program: "Program",
EventDispatch: "EventDispatch",
GPUTask: "GPUTask",
RequestMainThreadFrame: "RequestMainThreadFrame",
BeginFrame: "BeginFrame",
BeginMainThreadFrame: "BeginMainThreadFrame",
ActivateLayerTree: "ActivateLayerTree",
DrawFrame: "DrawFrame",
ScheduleStyleRecalculation: "ScheduleStyleRecalculation",
RecalculateStyles: "RecalculateStyles",
InvalidateLayout: "InvalidateLayout",
Layout: "Layout",
UpdateLayer: "UpdateLayer",
UpdateLayerTree: "UpdateLayerTree",
PaintSetup: "PaintSetup",
Paint: "Paint",
PaintImage: "PaintImage",
Rasterize: "Rasterize",
RasterTask: "RasterTask",
ScrollLayer: "ScrollLayer",
CompositeLayers: "CompositeLayers",
ParseHTML: "ParseHTML",
TimerInstall: "TimerInstall",
TimerRemove: "TimerRemove",
TimerFire: "TimerFire",
XHRReadyStateChange: "XHRReadyStateChange",
XHRLoad: "XHRLoad",
EvaluateScript: "EvaluateScript",
MarkLoad: "MarkLoad",
MarkDOMContent: "MarkDOMContent",
MarkFirstPaint: "MarkFirstPaint",
TimeStamp: "TimeStamp",
ConsoleTime: "ConsoleTime",
ResourceSendRequest: "ResourceSendRequest",
ResourceReceiveResponse: "ResourceReceiveResponse",
ResourceReceivedData: "ResourceReceivedData",
ResourceFinish: "ResourceFinish",
FunctionCall: "FunctionCall",
GCEvent: "GCEvent",
JSFrame: "JSFrame",
JSSample: "JSSample",
UpdateCounters: "UpdateCounters",
RequestAnimationFrame: "RequestAnimationFrame",
CancelAnimationFrame: "CancelAnimationFrame",
FireAnimationFrame: "FireAnimationFrame",
WebSocketCreate : "WebSocketCreate",
WebSocketSendHandshakeRequest : "WebSocketSendHandshakeRequest",
WebSocketReceiveHandshakeResponse : "WebSocketReceiveHandshakeResponse",
WebSocketDestroy : "WebSocketDestroy",
EmbedderCallback : "EmbedderCallback",
CallStack: "CallStack",
SetLayerTreeId: "SetLayerTreeId",
TracingStartedInPage: "TracingStartedInPage",
TracingSessionIdForWorker: "TracingSessionIdForWorker",
DecodeImage: "Decode Image",
ResizeImage: "Resize Image",
DrawLazyPixelRef: "Draw LazyPixelRef",
DecodeLazyPixelRef: "Decode LazyPixelRef",
LazyPixelRef: "LazyPixelRef",
LayerTreeHostImplSnapshot: "cc::LayerTreeHostImpl",
PictureSnapshot: "cc::Picture"
};
/**
* @constructor
* @param {string} name
*/
WebInspector.TracingTimelineModel.VirtualThread = function(name)
{
this.name = name;
/** @type {!Array.<!WebInspector.TracingModel.Event>} */
this.events = [];
/** @type {!Array.<!Array.<!WebInspector.TracingModel.Event>>} */
this.asyncEvents = [];
}
WebInspector.TracingTimelineModel.prototype = {
/**
* @param {boolean} captureStacks
* @param {boolean} captureMemory
* @param {boolean} capturePictures
*/
startRecording: function(captureStacks, captureMemory, capturePictures)
{
function disabledByDefault(category)
{
return "disabled-by-default-" + category;
}
var categoriesArray = [
"-*",
disabledByDefault("devtools.timeline"),
disabledByDefault("devtools.timeline.frame"),
WebInspector.TracingModel.ConsoleEventCategory
];
if (captureStacks) {
categoriesArray.push(disabledByDefault("devtools.timeline.stack"));
if (WebInspector.experimentsSettings.timelineJSCPUProfile.isEnabled()) {
this._jsProfilerStarted = true;
this._currentTarget = WebInspector.context.flavor(WebInspector.Target);
this._configureCpuProfilerSamplingInterval();
this._currentTarget.profilerAgent().start();
}
}
if (capturePictures) {
categoriesArray = categoriesArray.concat([
disabledByDefault("devtools.timeline.layers"),
disabledByDefault("devtools.timeline.picture"),
disabledByDefault("blink.graphics_context_annotations")]);
}
var categories = categoriesArray.join(",");
this._startRecordingWithCategories(categories);
},
stopRecording: function()
{
this._stopCallbackBarrier = new CallbackBarrier();
if (this._jsProfilerStarted) {
this._currentTarget.profilerAgent().stop(this._stopCallbackBarrier.createCallback(this._didStopRecordingJSSamples.bind(this)));
this._jsProfilerStarted = false;
}
this._tracingManager.stop();
},
/**
* @param {!Array.<!WebInspector.TracingManager.EventPayload>} events
*/
setEventsForTest: function(events)
{
this._onTracingStarted();
this._tracingModel.addEvents(events);
this._onTracingComplete();
},
_configureCpuProfilerSamplingInterval: function()
{
var intervalUs = WebInspector.settings.highResolutionCpuProfiling.get() ? 100 : 1000;
this._currentTarget.profilerAgent().setSamplingInterval(intervalUs, didChangeInterval);
function didChangeInterval(error)
{
if (error)
WebInspector.console.error(error);
}
},
/**
* @param {string} categories
*/
_startRecordingWithCategories: function(categories)
{
this._tracingManager.start(categories, "");
},
_onTracingStarted: function()
{
this.reset();
this._tracingModel.reset();
this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStarted);
},
/**
* @param {!WebInspector.Event} event
*/
_onEventsCollected: function(event)
{
var traceEvents = /** @type {!Array.<!WebInspector.TracingManager.EventPayload>} */ (event.data);
this._tracingModel.addEvents(traceEvents);
},
_onTracingComplete: function()
{
this._tracingModel.tracingComplete();
if (this._stopCallbackBarrier)
this._stopCallbackBarrier.callWhenDone(this._didStopRecordingTraceEvents.bind(this));
else
this._didStopRecordingTraceEvents();
},
/**
* @param {?Protocol.Error} error
* @param {?ProfilerAgent.CPUProfile} cpuProfile
*/
_didStopRecordingJSSamples: function(error, cpuProfile)
{
if (error)
WebInspector.console.error(error);
this._cpuProfile = cpuProfile;
},
_didStopRecordingTraceEvents: function()
{
this._stopCallbackBarrier = null;
var events = this._tracingModel.devtoolsPageMetadataEvents();
var workerMetadataEvents = this._tracingModel.devtoolsWorkerMetadataEvents();
this._resetProcessingState();
for (var i = 0, length = events.length; i < length; i++) {
var event = events[i];
var process = event.thread.process();
var startTime = event.startTime;
var endTime = Infinity;
if (i + 1 < length)
endTime = events[i + 1].startTime;
var threads = process.sortedThreads();
for (var j = 0; j < threads.length; j++) {
var thread = threads[j];
if (thread.name() === "WebCore: Worker" && !workerMetadataEvents.some(function(e) { return e.args["data"]["workerThreadId"] === thread.id(); }))
continue;
this._processThreadEvents(startTime, endTime, event.thread, thread);
}
}
this._resetProcessingState();
this._inspectedTargetEvents.sort(WebInspector.TracingModel.Event.compareStartTime);
if (this._cpuProfile) {
var jsSamples = WebInspector.TimelineJSProfileProcessor.generateTracingEventsFromCpuProfile(this, this._cpuProfile);
this._inspectedTargetEvents = this._inspectedTargetEvents.mergeOrdered(jsSamples, WebInspector.TracingModel.Event.orderedCompareStartTime);
this._setMainThreadEvents(this.mainThreadEvents().mergeOrdered(jsSamples, WebInspector.TracingModel.Event.orderedCompareStartTime));
this._cpuProfile = null;
}
this._buildTimelineRecords();
this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStopped);
},
/**
* @return {number}
*/
minimumRecordTime: function()
{
return this._tracingModel.minimumRecordTime();
},
/**
* @return {number}
*/
maximumRecordTime: function()
{
return this._tracingModel.maximumRecordTime();
},
/**
* @return {!Array.<!WebInspector.TracingModel.Event>}
*/
inspectedTargetEvents: function()
{
return this._inspectedTargetEvents;
},
/**
* @return {!Array.<!WebInspector.TracingModel.Event>}
*/
mainThreadEvents: function()
{
return this._mainThreadEvents;
},
/**
* @param {!Array.<!WebInspector.TracingModel.Event>} events
*/
_setMainThreadEvents: function(events)
{
this._mainThreadEvents = events;
},
/**
* @return {!Array.<!Array.<!WebInspector.TracingModel.Event>>}
*/
mainThreadAsyncEvents: function()
{
return this._mainThreadAsyncEvents;
},
/**
* @return {!Array.<!WebInspector.TracingTimelineModel.VirtualThread>}
*/
virtualThreads: function()
{
return this._virtualThreads;
},
/**
* @param {!WebInspector.ChunkedFileReader} fileReader
* @param {!WebInspector.Progress} progress
* @return {!WebInspector.OutputStream}
*/
createLoader: function(fileReader, progress)
{
return new WebInspector.TracingModelLoader(this, fileReader, progress);
},
/**
* @param {!WebInspector.OutputStream} stream
*/
writeToStream: function(stream)
{
var saver = new WebInspector.TracingTimelineSaver(stream);
this._tracingModel.writeToStream(stream, saver);
},
reset: function()
{
this._virtualThreads = [];
this._mainThreadEvents = [];
this._mainThreadAsyncEvents = [];
this._inspectedTargetEvents = [];
WebInspector.TimelineModel.prototype.reset.call(this);
},
_buildTimelineRecords: function()
{
var recordStack = [];
var mainThreadEvents = this.mainThreadEvents();
/**
* @param {!WebInspector.TracingTimelineModel.TraceEventRecord} record
*/
function copyChildrenToParent(record)
{
var parent = record.parent;
var parentChildren = parent.children();
var children = record.children();
for (var j = 0; j < children.length; ++j)
children[j].parent = parent;
parentChildren.splice.apply(parentChildren, [parentChildren.indexOf(record), 1].concat(children));
}
for (var i = 0, size = mainThreadEvents.length; i < size; ++i) {
var event = mainThreadEvents[i];
while (recordStack.length) {
var top = recordStack.peekLast();
// When we've got a not-yet-complete async event at the top of the stack,
// see if we can close it by a matching end event. If this doesn't happen
// before end of top-level event (presumably, a "Program"), pretend the
// async event never happened.
if (!top._event.endTime) {
if (event.phase !== WebInspector.TracingModel.Phase.AsyncEnd && recordStack[0]._event.endTime >= event.startTime)
break;
if (event.phase === WebInspector.TracingModel.Phase.AsyncEnd) {
if (top._event.name === event.name) {
top.setEndTime(event.startTime);
recordStack.pop();
}
break;
}
// Delete incomplete async record from parent and adopt its children.
recordStack.pop();
copyChildrenToParent(top);
continue;
} else if (top._event.endTime >= event.startTime) {
break;
}
recordStack.pop();
if (!recordStack.length)
this._addTopLevelRecord(top);
}
if (event.phase === WebInspector.TracingModel.Phase.AsyncEnd)
continue;
var record = new WebInspector.TracingTimelineModel.TraceEventRecord(this, event);
if (WebInspector.TracingTimelineUIUtils.isMarkerEvent(event))
this._eventDividerRecords.push(record);
if (!this._recordFilter.accept(record))
continue;
var parentRecord = recordStack.peekLast();
if (parentRecord)
parentRecord._addChild(record);
if (event.endTime || (event.phase === WebInspector.TracingModel.Phase.AsyncBegin && parentRecord))
recordStack.push(record);
}
// Close all remaining incomplete async events.
while (recordStack.length > 1) {
var top = recordStack.pop();
if (!top._event.endTime) {
// Delete incomplete async record from parent and adopt its children.
copyChildrenToParent(top);
}
}
if (recordStack.length)
this._addTopLevelRecord(recordStack[0]);
},
/**
* @param {!WebInspector.TracingTimelineModel.TraceEventRecord} record
*/
_addTopLevelRecord: function(record)
{
this._updateBoundaries(record);
this._records.push(record);
if (record.type() === WebInspector.TracingTimelineModel.RecordType.Program)
this._mainThreadTasks.push(record);
if (record.type() === WebInspector.TracingTimelineModel.RecordType.GPUTask)
this._gpuThreadTasks.push(record);
this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordAdded, record);
},
_resetProcessingState: function()
{
this._sendRequestEvents = {};
this._timerEvents = {};
this._requestAnimationFrameEvents = {};
this._layoutInvalidate = {};
this._lastScheduleStyleRecalculation = {};
this._webSocketCreateEvents = {};
this._paintImageEventByPixelRefId = {};
this._lastPaintForLayer = {};
this._lastRecalculateStylesEvent = null;
this._currentScriptEvent = null;
this._eventStack = [];
},
/**
* @param {number} startTime
* @param {?number} endTime
* @param {!WebInspector.TracingModel.Thread} mainThread
* @param {!WebInspector.TracingModel.Thread} thread
*/
_processThreadEvents: function(startTime, endTime, mainThread, thread)
{
var events = thread.events();
var length = events.length;
var i = events.lowerBound(startTime, function (time, event) { return time - event.startTime });
var threadEvents;
if (thread === mainThread) {
threadEvents = this._mainThreadEvents;
this._mainThreadAsyncEvents = this._mainThreadAsyncEvents.concat(thread.asyncEvents());
} else {
var virtualThread = new WebInspector.TracingTimelineModel.VirtualThread(thread.name());
threadEvents = virtualThread.events;
virtualThread.asyncEvents = virtualThread.asyncEvents.concat(thread.asyncEvents());
this._virtualThreads.push(virtualThread);
}
this._eventStack = [];
for (; i < length; i++) {
var event = events[i];
if (endTime && event.startTime >= endTime)
break;
this._processEvent(event);
threadEvents.push(event);
this._inspectedTargetEvents.push(event);
}
},
/**
* @param {!WebInspector.TracingModel.Event} event
*/
_processEvent: function(event)
{
var recordTypes = WebInspector.TracingTimelineModel.RecordType;
var eventStack = this._eventStack;
while (eventStack.length && eventStack.peekLast().endTime < event.startTime)
eventStack.pop();
var duration = event.duration;
if (duration) {
if (eventStack.length) {
var parent = eventStack.peekLast();
parent.selfTime -= duration;
}
event.selfTime = duration;
eventStack.push(event);
}
if (this._currentScriptEvent && event.startTime > this._currentScriptEvent.endTime)
this._currentScriptEvent = null;
switch (event.name) {
case recordTypes.CallStack:
var lastMainThreadEvent = this.mainThreadEvents().peekLast();
if (lastMainThreadEvent && event.args["stack"] && event.args["stack"].length)
lastMainThreadEvent.stackTrace = event.args["stack"];
break;
case recordTypes.ResourceSendRequest:
this._sendRequestEvents[event.args["data"]["requestId"]] = event;
event.imageURL = event.args["data"]["url"];
break;
case recordTypes.ResourceReceiveResponse:
case recordTypes.ResourceReceivedData:
case recordTypes.ResourceFinish:
event.initiator = this._sendRequestEvents[event.args["data"]["requestId"]];
if (event.initiator)
event.imageURL = event.initiator.imageURL;
break;
case recordTypes.TimerInstall:
this._timerEvents[event.args["data"]["timerId"]] = event;
break;
case recordTypes.TimerFire:
event.initiator = this._timerEvents[event.args["data"]["timerId"]];
break;
case recordTypes.RequestAnimationFrame:
this._requestAnimationFrameEvents[event.args["data"]["id"]] = event;
break;
case recordTypes.FireAnimationFrame:
event.initiator = this._requestAnimationFrameEvents[event.args["data"]["id"]];
break;
case recordTypes.ScheduleStyleRecalculation:
this._lastScheduleStyleRecalculation[event.args["frame"]] = event;
break;
case recordTypes.RecalculateStyles:
event.initiator = this._lastScheduleStyleRecalculation[event.args["frame"]];
this._lastRecalculateStylesEvent = event;
break;
case recordTypes.InvalidateLayout:
// Consider style recalculation as a reason for layout invalidation,
// but only if we had no earlier layout invalidation records.
var layoutInitator = event;
var frameId = event.args["frame"];
if (!this._layoutInvalidate[frameId] && this._lastRecalculateStylesEvent && this._lastRecalculateStylesEvent.endTime > event.startTime)
layoutInitator = this._lastRecalculateStylesEvent.initiator;
this._layoutInvalidate[frameId] = layoutInitator;
break;
case recordTypes.Layout:
var frameId = event.args["beginData"]["frame"];
event.initiator = this._layoutInvalidate[frameId];
event.backendNodeId = event.args["endData"]["rootNode"];
event.highlightQuad = event.args["endData"]["root"];
this._layoutInvalidate[frameId] = null;
if (this._currentScriptEvent)
event.warning = WebInspector.UIString("Forced synchronous layout is a possible performance bottleneck.");
break;
case recordTypes.WebSocketCreate:
this._webSocketCreateEvents[event.args["data"]["identifier"]] = event;
break;
case recordTypes.WebSocketSendHandshakeRequest:
case recordTypes.WebSocketReceiveHandshakeResponse:
case recordTypes.WebSocketDestroy:
event.initiator = this._webSocketCreateEvents[event.args["data"]["identifier"]];
break;
case recordTypes.EvaluateScript:
case recordTypes.FunctionCall:
if (!this._currentScriptEvent)
this._currentScriptEvent = event;
break;
case recordTypes.SetLayerTreeId:
this._inspectedTargetLayerTreeId = event.args["layerTreeId"];
break;
case recordTypes.Paint:
event.highlightQuad = event.args["data"]["clip"];
event.backendNodeId = event.args["data"]["nodeId"];
var layerUpdateEvent = this._findAncestorEvent(recordTypes.UpdateLayer);
if (!layerUpdateEvent || layerUpdateEvent.args["layerTreeId"] !== this._inspectedTargetLayerTreeId)
break;
// Only keep layer paint events, skip paints for subframes that get painted to the same layer as parent.
if (!event.args["data"]["layerId"])
break;
this._lastPaintForLayer[layerUpdateEvent.args["layerId"]] = event;
break;
case recordTypes.PictureSnapshot:
var layerUpdateEvent = this._findAncestorEvent(recordTypes.UpdateLayer);
if (!layerUpdateEvent || layerUpdateEvent.args["layerTreeId"] !== this._inspectedTargetLayerTreeId)
break;
var paintEvent = this._lastPaintForLayer[layerUpdateEvent.args["layerId"]];
if (paintEvent)
paintEvent.picture = event;
break;
case recordTypes.ScrollLayer:
event.backendNodeId = event.args["data"]["nodeId"];
break;
case recordTypes.PaintImage:
event.backendNodeId = event.args["data"]["nodeId"];
event.imageURL = event.args["data"]["url"];
break;
case recordTypes.DecodeImage:
case recordTypes.ResizeImage:
var paintImageEvent = this._findAncestorEvent(recordTypes.PaintImage);
if (!paintImageEvent) {
var decodeLazyPixelRefEvent = this._findAncestorEvent(recordTypes.DecodeLazyPixelRef);
paintImageEvent = decodeLazyPixelRefEvent && this._paintImageEventByPixelRefId[decodeLazyPixelRefEvent.args["LazyPixelRef"]];
}
if (!paintImageEvent)
break;
event.backendNodeId = paintImageEvent.backendNodeId;
event.imageURL = paintImageEvent.imageURL;
break;
case recordTypes.DrawLazyPixelRef:
var paintImageEvent = this._findAncestorEvent(recordTypes.PaintImage);
if (!paintImageEvent)
break;
this._paintImageEventByPixelRefId[event.args["LazyPixelRef"]] = paintImageEvent;
event.backendNodeId = paintImageEvent.backendNodeId;
event.imageURL = paintImageEvent.imageURL;
break;
}
},
/**
* @param {string} name
* @return {?WebInspector.TracingModel.Event}
*/
_findAncestorEvent: function(name)
{
for (var i = this._eventStack.length - 1; i >= 0; --i) {
var event = this._eventStack[i];
if (event.name === name)
return event;
}
return null;
},
__proto__: WebInspector.TimelineModel.prototype
}
/**
* @interface
*/
WebInspector.TracingTimelineModel.Filter = function() { }
WebInspector.TracingTimelineModel.Filter.prototype = {
/**
* @param {!WebInspector.TracingModel.Event} event
* @return {boolean}
*/
accept: function(event) { }
}
/**
* @constructor
* @implements {WebInspector.TracingTimelineModel.Filter}
* @param {!Array.<string>} eventNames
*/
WebInspector.TracingTimelineModel.EventNameFilter = function(eventNames)
{
this._eventNames = eventNames.keySet();
}
WebInspector.TracingTimelineModel.EventNameFilter.prototype = {
/**
* @param {!WebInspector.TracingModel.Event} event
* @return {boolean}
*/
accept: function(event)
{
throw new Error("Not implemented.");
}
}
/**
* @constructor
* @extends {WebInspector.TracingTimelineModel.EventNameFilter}
* @param {!Array.<string>} includeNames
*/
WebInspector.TracingTimelineModel.InclusiveEventNameFilter = function(includeNames)
{
WebInspector.TracingTimelineModel.EventNameFilter.call(this, includeNames)
}
WebInspector.TracingTimelineModel.InclusiveEventNameFilter.prototype = {
/**
* @override
* @param {!WebInspector.TracingModel.Event} event
* @return {boolean}
*/
accept: function(event)
{
return event.category === WebInspector.TracingModel.ConsoleEventCategory || !!this._eventNames[event.name];
},
__proto__: WebInspector.TracingTimelineModel.EventNameFilter.prototype
}
/**
* @constructor
* @extends {WebInspector.TracingTimelineModel.EventNameFilter}
* @param {!Array.<string>} excludeNames
*/
WebInspector.TracingTimelineModel.ExclusiveEventNameFilter = function(excludeNames)
{
WebInspector.TracingTimelineModel.EventNameFilter.call(this, excludeNames)
}
WebInspector.TracingTimelineModel.ExclusiveEventNameFilter.prototype = {
/**
* @override
* @param {!WebInspector.TracingModel.Event} event
* @return {boolean}
*/
accept: function(event)
{
return !this._eventNames[event.name];
},
__proto__: WebInspector.TracingTimelineModel.EventNameFilter.prototype
}
/**
* @constructor
* @implements {WebInspector.TimelineModel.Record}
* @param {!WebInspector.TimelineModel} model
* @param {!WebInspector.TracingModel.Event} traceEvent
*/
WebInspector.TracingTimelineModel.TraceEventRecord = function(model, traceEvent)
{
this._model = model;
this._event = traceEvent;
traceEvent._timelineRecord = this;
this._children = [];
}
WebInspector.TracingTimelineModel.TraceEventRecord.prototype = {
/**
* @return {?Array.<!ConsoleAgent.CallFrame>}
*/
callSiteStackTrace: function()
{
var initiator = this._event.initiator;
return initiator ? initiator.stackTrace : null;
},
/**
* @return {?WebInspector.TimelineModel.Record}
*/
initiator: function()
{
var initiator = this._event.initiator;
return initiator ? initiator._timelineRecord : null;
},
/**
* @return {?WebInspector.Target}
*/
target: function()
{
return this._event.thread.target();
},
/**
* @return {number}
*/
selfTime: function()
{
return this._event.selfTime;
},
/**
* @return {!Array.<!WebInspector.TimelineModel.Record>}
*/
children: function()
{
return this._children;
},
/**
* @return {number}
*/
startTime: function()
{
return this._event.startTime;
},
/**
* @return {string}
*/
thread: function()
{
// FIXME: Should return the actual thread name.
return WebInspector.TimelineModel.MainThreadName;
},
/**
* @return {number}
*/
endTime: function()
{
return this._endTime || this._event.endTime || this._event.startTime;
},
/**
* @param {number} endTime
*/
setEndTime: function(endTime)
{
this._endTime = endTime;
},
/**
* @return {!Object}
*/
data: function()
{
return this._event.args["data"];
},
/**
* @return {string}
*/
type: function()
{
if (this._event.category === WebInspector.TracingModel.ConsoleEventCategory)
return WebInspector.TracingTimelineModel.RecordType.ConsoleTime;
return this._event.name;
},
/**
* @return {string}
*/
frameId: function()
{
switch (this._event.name) {
case WebInspector.TracingTimelineModel.RecordType.ScheduleStyleRecalculation:
case WebInspector.TracingTimelineModel.RecordType.RecalculateStyles:
case WebInspector.TracingTimelineModel.RecordType.InvalidateLayout:
return this._event.args["frameId"];
case WebInspector.TracingTimelineModel.RecordType.Layout:
return this._event.args["beginData"]["frameId"];
default:
var data = this._event.args["data"];
return (data && data["frame"]) || "";
}
},
/**
* @return {?Array.<!ConsoleAgent.CallFrame>}
*/
stackTrace: function()
{
return this._event.stackTrace;
},
/**
* @param {string} key
* @return {?Object}
*/
getUserObject: function(key)
{
if (key === "TimelineUIUtils::preview-element")
return this._event.previewElement;
throw new Error("Unexpected key: " + key);
},
/**
* @param {string} key
* @param {?Object|undefined} value
*/
setUserObject: function(key, value)
{
if (key !== "TimelineUIUtils::preview-element")
throw new Error("Unexpected key: " + key);
this._event.previewElement = /** @type {?Element} */ (value);
},
/**
* @return {?Array.<string>}
*/
warnings: function()
{
if (this._event.warning)
return [this._event.warning];
return null;
},
/**
* @return {!WebInspector.TracingModel.Event}
*/
traceEvent: function()
{
return this._event;
},
/**
* @param {!WebInspector.TracingTimelineModel.TraceEventRecord} child
*/
_addChild: function(child)
{
this._children.push(child);
child.parent = this;
},
/**
* @return {!WebInspector.TimelineModel}
*/
timelineModel: function()
{
return this._model;
}
}
/**
* @constructor
* @implements {WebInspector.OutputStream}
* @param {!WebInspector.TracingTimelineModel} model
* @param {!{cancel: function()}} reader
* @param {!WebInspector.Progress} progress
*/
WebInspector.TracingModelLoader = function(model, reader, progress)
{
this._model = model;
this._reader = reader;
this._progress = progress;
this._buffer = "";
this._firstChunk = true;
this._loader = new WebInspector.TracingModel.Loader(model._tracingModel);
}
WebInspector.TracingModelLoader.prototype = {
/**
* @param {string} chunk
*/
write: function(chunk)
{
var data = this._buffer + chunk;
var lastIndex = 0;
var index;
do {
index = lastIndex;
lastIndex = WebInspector.TextUtils.findBalancedCurlyBrackets(data, index);
} while (lastIndex !== -1)
var json = data.slice(0, index) + "]";
this._buffer = data.slice(index);
if (!index)
return;
if (this._firstChunk) {
this._model._onTracingStarted();
} else {
var commaIndex = json.indexOf(",");
if (commaIndex !== -1)
json = json.slice(commaIndex + 1);
json = "[" + json;
}
var items;
try {
items = /** @type {!Array.<!WebInspector.TracingManager.EventPayload>} */ (JSON.parse(json));
} catch (e) {
this._reportErrorAndCancelLoading("Malformed timeline data: " + e);
return;
}
if (this._firstChunk) {
this._firstChunk = false;
if (this._looksLikeAppVersion(items[0])) {
this._reportErrorAndCancelLoading("Old Timeline format is not supported.");
return;
}
}
try {
this._loader.loadNextChunk(items);
} catch(e) {
this._reportErrorAndCancelLoading("Malformed timeline data: " + e);
return;
}
},
_reportErrorAndCancelLoading: function(messsage)
{
WebInspector.console.error(messsage);
this._model._onTracingComplete();
this._model.reset();
this._reader.cancel();
this._progress.done();
},
_looksLikeAppVersion: function(item)
{
return typeof item === "string" && item.indexOf("Chrome") !== -1;
},
close: function()
{
this._loader.finish();
this._model._onTracingComplete();
}
}
/**
* @constructor
* @param {!WebInspector.OutputStream} stream
* @implements {WebInspector.OutputStreamDelegate}
*/
WebInspector.TracingTimelineSaver = function(stream)
{
this._stream = stream;
}
WebInspector.TracingTimelineSaver.prototype = {
onTransferStarted: function()
{
this._stream.write("[");
},
onTransferFinished: function()
{
this._stream.write("]");
},
/**
* @param {!WebInspector.ChunkedReader} reader
*/
onChunkTransferred: function(reader) { },
/**
* @param {!WebInspector.ChunkedReader} reader
* @param {!Event} event
*/
onError: function(reader, event) { },
}