blob: b074d9bf6b1e858f3b9b8924cfc79afc726f8b11 [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
* @extends {WebInspector.SDKObject}
*/
WebInspector.TracingModel = function(target)
{
WebInspector.SDKObject.call(this, target);
this.reset();
this._active = false;
InspectorBackend.registerTracingDispatcher(new WebInspector.TracingDispatcher(this));
}
WebInspector.TracingModel.Events = {
"BufferUsage": "BufferUsage",
"TracingStarted": "TracingStarted",
"TracingStopped": "TracingStopped",
"TracingComplete": "TracingComplete"
}
/** @typedef {!{
cat: string,
pid: number,
tid: number,
ts: number,
ph: string,
name: string,
args: !Object,
dur: number,
id: number,
s: string
}}
*/
WebInspector.TracingModel.EventPayload;
/**
* @enum {string}
*/
WebInspector.TracingModel.Phase = {
Begin: "B",
End: "E",
Complete: "X",
Instant: "i",
AsyncBegin: "S",
AsyncStepInto: "T",
AsyncStepPast: "p",
AsyncEnd: "F",
FlowBegin: "s",
FlowStep: "t",
FlowEnd: "f",
Metadata: "M",
Counter: "C",
Sample: "P",
CreateObject: "N",
SnapshotObject: "O",
DeleteObject: "D"
};
WebInspector.TracingModel.MetadataEvent = {
ProcessSortIndex: "process_sort_index",
ProcessName: "process_name",
ThreadSortIndex: "thread_sort_index",
ThreadName: "thread_name"
}
WebInspector.TracingModel.DevToolsMetadataEventCategory = "disabled-by-default-devtools.timeline";
WebInspector.TracingModel.FrameLifecycleEventCategory = "cc,devtools";
WebInspector.TracingModel.DevToolsMetadataEvent = {
TracingStartedInPage: "TracingStartedInPage",
TracingStartedInWorker: "TracingStartedInWorker",
};
WebInspector.TracingModel.prototype = {
/**
* @return {!Array.<!WebInspector.TracingModel.Event>}
*/
devtoolsPageMetadataEvents: function()
{
return this._devtoolsPageMetadataEvents;
},
/**
* @return {!Array.<!WebInspector.TracingModel.Event>}
*/
devtoolsWorkerMetadataEvents: function()
{
return this._devtoolsWorkerMetadataEvents;
},
/**
* @param {string} categoryFilter
* @param {string} options
* @param {function(?string)=} callback
*/
start: function(categoryFilter, options, callback)
{
WebInspector.profilingLock().acquire();
this.reset();
var bufferUsageReportingIntervalMs = 500;
TracingAgent.start(categoryFilter, options, bufferUsageReportingIntervalMs, callback);
this._active = true;
},
stop: function()
{
if (!this._active)
return;
TracingAgent.end(this._onStop.bind(this));
WebInspector.profilingLock().release();
},
/**
* @return {?string}
*/
sessionId: function()
{
return this._sessionId;
},
/**
* @param {string} sessionId
* @param {!Array.<!WebInspector.TracingModel.EventPayload>} events
*/
setEventsForTest: function(sessionId, events)
{
this.reset();
this._sessionId = sessionId;
this._eventsCollected(events);
},
/**
* @param {number} usage
*/
_bufferUsage: function(usage)
{
this.dispatchEventToListeners(WebInspector.TracingModel.Events.BufferUsage, usage);
},
/**
* @param {!Array.<!WebInspector.TracingModel.EventPayload>} events
*/
_eventsCollected: function(events)
{
for (var i = 0; i < events.length; ++i) {
this._addEvent(events[i]);
this._rawEvents.push(events[i]);
}
},
_tracingComplete: function()
{
this._active = false;
this.dispatchEventToListeners(WebInspector.TracingModel.Events.TracingComplete);
},
/**
* @param {string} sessionId
*/
_tracingStarted: function(sessionId)
{
this.reset();
this._active = true;
this._sessionId = sessionId;
this.dispatchEventToListeners(WebInspector.TracingModel.Events.TracingStarted);
},
_onStop: function()
{
this.dispatchEventToListeners(WebInspector.TracingModel.Events.TracingStopped);
this._active = false;
},
reset: function()
{
this._processById = {};
this._minimumRecordTime = 0;
this._maximumRecordTime = 0;
this._sessionId = null;
this._devtoolsPageMetadataEvents = [];
this._devtoolsWorkerMetadataEvents = [];
this._rawEvents = [];
},
/**
* @return {!Array.<!WebInspector.TracingModel.EventPayload>}
*/
rawEvents: function()
{
return this._rawEvents;
},
/**
* @param {!WebInspector.TracingModel.EventPayload} payload
*/
_addEvent: function(payload)
{
var process = this._processById[payload.pid];
if (!process) {
process = new WebInspector.TracingModel.Process(payload.pid);
this._processById[payload.pid] = process;
}
var thread = process.threadById(payload.tid);
if (payload.ph !== WebInspector.TracingModel.Phase.Metadata) {
var timestamp = payload.ts / 1000;
// We do allow records for unrelated threads to arrive out-of-order,
// so there's a chance we're getting records from the past.
if (timestamp && (!this._minimumRecordTime || timestamp < this._minimumRecordTime))
this._minimumRecordTime = timestamp;
if (!this._maximumRecordTime || timestamp > this._maximumRecordTime)
this._maximumRecordTime = timestamp;
var event = thread.addEvent(payload);
if (payload.ph === WebInspector.TracingModel.Phase.SnapshotObject)
process.addObject(event);
if (event && event.name === WebInspector.TracingModel.DevToolsMetadataEvent.TracingStartedInPage &&
event.category === WebInspector.TracingModel.DevToolsMetadataEventCategory &&
event.args["sessionId"] === this._sessionId)
this._devtoolsPageMetadataEvents.push(event);
if (event && event.name === WebInspector.TracingModel.DevToolsMetadataEvent.TracingStartedInWorker &&
event.category === WebInspector.TracingModel.DevToolsMetadataEventCategory &&
event.args["sessionId"] === this._sessionId)
this._devtoolsWorkerMetadataEvents.push(event);
return;
}
switch (payload.name) {
case WebInspector.TracingModel.MetadataEvent.ProcessSortIndex:
process._setSortIndex(payload.args["sort_index"]);
break;
case WebInspector.TracingModel.MetadataEvent.ProcessName:
process._setName(payload.args["name"]);
break;
case WebInspector.TracingModel.MetadataEvent.ThreadSortIndex:
thread._setSortIndex(payload.args["sort_index"]);
break;
case WebInspector.TracingModel.MetadataEvent.ThreadName:
thread._setName(payload.args["name"]);
break;
}
},
/**
* @return {number}
*/
minimumRecordTime: function()
{
return this._minimumRecordTime;
},
/**
* @return {number}
*/
maximumRecordTime: function()
{
return this._maximumRecordTime;
},
/**
* @return {!Array.<!WebInspector.TracingModel.Process>}
*/
sortedProcesses: function()
{
return WebInspector.TracingModel.NamedObject._sort(Object.values(this._processById));
},
__proto__: WebInspector.SDKObject.prototype
}
/**
* @constructor
* @param {!WebInspector.TracingModel} tracingModel
*/
WebInspector.TracingModel.Loader = function(tracingModel)
{
this._tracingModel = tracingModel;
this._events = [];
this._sessionIdFound = false;
}
WebInspector.TracingModel.Loader.prototype = {
/**
* @param {!Array.<!WebInspector.TracingModel.EventPayload>} events
*/
loadNextChunk: function(events) {
if (this._sessionIdFound) {
this._tracingModel._eventsCollected(events);
return;
}
var sessionId = null;
for (var i = 0, length = events.length; i < length; i++) {
var event = events[i];
this._events.push(event);
if (event.name === WebInspector.TracingModel.DevToolsMetadataEvent.TracingStartedInPage &&
event.cat.indexOf(WebInspector.TracingModel.DevToolsMetadataEventCategory) !== -1 &&
!this._sessionIdFound) {
sessionId = event.args["sessionId"];
this._sessionIdFound = true;
}
}
if (this._sessionIdFound) {
this._tracingModel._tracingStarted(sessionId);
this._tracingModel._eventsCollected(this._events);
}
},
finish: function()
{
if (this._sessionIdFound)
this._tracingModel._tracingComplete();
else
WebInspector.console.error(WebInspector.UIString("Trace event %s not found while loading tracing model.", WebInspector.TracingModel.DevToolsMetadataEvent.TracingStartedInPage));
}
}
/**
* @constructor
* @param {!WebInspector.TracingModel.EventPayload} payload
* @param {number} level
* @param {?WebInspector.TracingModel.Thread} thread
*/
WebInspector.TracingModel.Event = function(payload, level, thread)
{
this.name = payload.name;
this.category = payload.cat;
this.startTime = payload.ts / 1000;
if (payload.args) {
// Create a new object to avoid modifying original payload which may be saved to file.
this.args = {};
for (var name in payload.args)
this.args[name] = payload.args[name];
}
this.phase = payload.ph;
this.level = level;
if (typeof payload.dur === "number")
this._setEndTime((payload.ts + payload.dur) / 1000);
if (payload.id)
this.id = payload.id;
this.thread = thread;
/** @type {?string} */
this.warning = null;
/** @type {?WebInspector.TracingModel.Event} */
this.initiator = null;
/** @type {?Array.<!ConsoleAgent.CallFrame>} */
this.stackTrace = null;
/** @type {?Element} */
this.previewElement = null;
/** @type {?string} */
this.imageURL = null;
/** @type {number} */
this.backendNodeId = 0;
/** @type {number} */
this.selfTime = 0;
}
WebInspector.TracingModel.Event.prototype = {
/**
* @param {number} endTime
*/
_setEndTime: function(endTime)
{
if (endTime < this.startTime) {
console.assert(false, "Event out of order: " + this.name);
return;
}
this.endTime = endTime;
this.duration = endTime - this.startTime;
},
/**
* @param {!WebInspector.TracingModel.EventPayload} payload
*/
_complete: function(payload)
{
if (this.name !== payload.name) {
console.assert(false, "Open/close event mismatch: " + this.name + " vs. " + payload.name + " at " + (payload.ts / 1000));
return;
}
if (payload.args) {
for (var name in payload.args) {
if (name in this.args)
console.error("Same argument name (" + name + ") is used for begin and end phases of " + this.name);
this.args[name] = payload.args[name];
}
}
this._setEndTime(payload.ts / 1000);
}
}
/**
* @param {!WebInspector.TracingModel.Event} a
* @param {!WebInspector.TracingModel.Event} b
* @return {number}
*/
WebInspector.TracingModel.Event.compareStartTime = function (a, b)
{
return a.startTime - b.startTime;
}
/**
* @param {!WebInspector.TracingModel.Event} a
* @param {!WebInspector.TracingModel.Event} b
* @return {number}
*/
WebInspector.TracingModel.Event.orderedCompareStartTime = function (a, b)
{
// Array.mergeOrdered coalesces objects if comparator returns 0.
// To change this behavior this comparator return -1 in the case events
// startTime's are equal, so both events got placed into the result array.
return a.startTime - b.startTime || -1;
}
/**
* @constructor
*/
WebInspector.TracingModel.NamedObject = function()
{
}
WebInspector.TracingModel.NamedObject.prototype =
{
/**
* @param {string} name
*/
_setName: function(name)
{
this._name = name;
},
/**
* @return {string}
*/
name: function()
{
return this._name;
},
/**
* @param {number} sortIndex
*/
_setSortIndex: function(sortIndex)
{
this._sortIndex = sortIndex;
},
}
/**
* @param {!Array.<!WebInspector.TracingModel.NamedObject>} array
*/
WebInspector.TracingModel.NamedObject._sort = function(array)
{
/**
* @param {!WebInspector.TracingModel.NamedObject} a
* @param {!WebInspector.TracingModel.NamedObject} b
*/
function comparator(a, b)
{
return a._sortIndex !== b._sortIndex ? a._sortIndex - b._sortIndex : a.name().localeCompare(b.name());
}
return array.sort(comparator);
}
/**
* @constructor
* @extends {WebInspector.TracingModel.NamedObject}
* @param {number} id
*/
WebInspector.TracingModel.Process = function(id)
{
WebInspector.TracingModel.NamedObject.call(this);
this._setName("Process " + id);
this._threads = {};
this._objects = {};
}
WebInspector.TracingModel.Process.prototype = {
/**
* @param {number} id
* @return {!WebInspector.TracingModel.Thread}
*/
threadById: function(id)
{
var thread = this._threads[id];
if (!thread) {
thread = new WebInspector.TracingModel.Thread(this, id);
this._threads[id] = thread;
}
return thread;
},
/**
* @param {!WebInspector.TracingModel.Event} event
*/
addObject: function(event)
{
this.objectsByName(event.name).push(event);
},
/**
* @param {string} name
* @return {!Array.<!WebInspector.TracingModel.Event>}
*/
objectsByName: function(name)
{
var objects = this._objects[name];
if (!objects) {
objects = [];
this._objects[name] = objects;
}
return objects;
},
/**
* @return {!Array.<string>}
*/
sortedObjectNames: function()
{
return Object.keys(this._objects).sort();
},
/**
* @return {!Array.<!WebInspector.TracingModel.Thread>}
*/
sortedThreads: function()
{
return WebInspector.TracingModel.NamedObject._sort(Object.values(this._threads));
},
__proto__: WebInspector.TracingModel.NamedObject.prototype
}
/**
* @constructor
* @extends {WebInspector.TracingModel.NamedObject}
* @param {!WebInspector.TracingModel.Process} process
* @param {number} id
*/
WebInspector.TracingModel.Thread = function(process, id)
{
WebInspector.TracingModel.NamedObject.call(this);
this._process = process;
this._setName("Thread " + id);
this._events = [];
this._stack = [];
this._maxStackDepth = 0;
}
WebInspector.TracingModel.Thread.prototype = {
/**
* @return {?WebInspector.Target}
*/
target: function()
{
//FIXME: correctly specify target
return WebInspector.targetManager.targets()[0];
},
/**
* @param {!WebInspector.TracingModel.EventPayload} payload
* @return {?WebInspector.TracingModel.Event} event
*/
addEvent: function(payload)
{
var timestamp = payload.ts / 1000;
for (var top = this._stack.peekLast(); top;) {
// For B/E pairs, ignore time and look for top matching B event,
// otherwise, only pop event if it's definitely is in the past.
if (payload.ph === WebInspector.TracingModel.Phase.End) {
if (payload.name === top.name) {
top._complete(payload);
this._stack.pop();
return null;
}
} else if (top.phase === WebInspector.TracingModel.Phase.Begin || (top.endTime && (top.endTime > timestamp))) {
break;
}
this._stack.pop();
top = this._stack.peekLast();
}
// Quietly ignore unbalanced close events, they're legit (we could have missed start one).
if (payload.ph === WebInspector.TracingModel.Phase.End)
return null;
var event = new WebInspector.TracingModel.Event(payload, this._stack.length, this);
if (payload.ph === WebInspector.TracingModel.Phase.Begin || payload.ph === WebInspector.TracingModel.Phase.Complete) {
this._stack.push(event);
if (this._maxStackDepth < this._stack.length)
this._maxStackDepth = this._stack.length;
}
if (this._events.length && this._events.peekLast().startTime > event.startTime)
console.assert(false, "Event is our of order: " + event.name);
this._events.push(event);
return event;
},
/**
* @return {!WebInspector.TracingModel.Process}
*/
process: function()
{
return this._process;
},
/**
* @return {!Array.<!WebInspector.TracingModel.Event>}
*/
events: function()
{
return this._events;
},
/**
* @return {number}
*/
maxStackDepth: function()
{
// Reserve one for non-container events.
return this._maxStackDepth + 1;
},
__proto__: WebInspector.TracingModel.NamedObject.prototype
}
/**
* @constructor
* @implements {TracingAgent.Dispatcher}
* @param {!WebInspector.TracingModel} tracingModel
*/
WebInspector.TracingDispatcher = function(tracingModel)
{
this._tracingModel = tracingModel;
}
WebInspector.TracingDispatcher.prototype = {
/**
* @param {number} usage
*/
bufferUsage: function(usage)
{
this._tracingModel._bufferUsage(usage);
},
/**
* @param {!Array.<!WebInspector.TracingModel.EventPayload>} data
*/
dataCollected: function(data)
{
this._tracingModel._eventsCollected(data);
},
tracingComplete: function()
{
this._tracingModel._tracingComplete();
},
/**
* @param {boolean} consoleTimeline
* @param {string} sessionId
*/
started: function(consoleTimeline, sessionId)
{
this._tracingModel._tracingStarted(sessionId);
},
stopped: function()
{
this._tracingModel._onStop();
}
}