| Index: chrome/browser/resources/tracing/timeline_model.js
|
| diff --git a/chrome/browser/resources/tracing/timeline_model.js b/chrome/browser/resources/tracing/timeline_model.js
|
| index b2cc3803385886531005a6ae79a052232d9d6b43..a309f17a20aeffac7b20b4ad33da7902246391d1 100644
|
| --- a/chrome/browser/resources/tracing/timeline_model.js
|
| +++ b/chrome/browser/resources/tracing/timeline_model.js
|
| @@ -21,18 +21,8 @@
|
| */
|
| cr.define('tracing', function() {
|
| /**
|
| - * A TimelineSlice represents an interval of time on a given resource plus
|
| - * parameters associated with that interval.
|
| - *
|
| - * A slice is typically associated with a specific trace event pair on a
|
| - * specific thread.
|
| - * For example,
|
| - * TRACE_EVENT_BEGIN1("x","myArg", 7) at time=0.1ms
|
| - * TRACE_EVENT_END() at time=0.3ms
|
| - * This results in a single timeline slice from 0.1 with duration 0.2 on a
|
| - * specific thread.
|
| - *
|
| - * A slice can also be an interval of time on a Cpu on a TimelineCpu.
|
| + * A TimelineSlice represents an interval of time plus parameters associated
|
| + * with that interval.
|
| *
|
| * All time units are stored in milliseconds.
|
| * @constructor
|
| @@ -43,7 +33,6 @@ cr.define('tracing', function() {
|
| this.colorId = colorId;
|
| this.args = args;
|
| this.didNotFinish = false;
|
| - this.subSlices = [];
|
| if (opt_duration !== undefined)
|
| this.duration = opt_duration;
|
| }
|
| @@ -59,18 +48,81 @@ cr.define('tracing', function() {
|
| };
|
|
|
| /**
|
| + * A TimelineThreadSlice represents an interval of time on a thread resource
|
| + * with associated nestinged slice information.
|
| + *
|
| + * ThreadSlices are typically associated with a specific trace event pair on a
|
| + * specific thread.
|
| + * For example,
|
| + * TRACE_EVENT_BEGIN1("x","myArg", 7) at time=0.1ms
|
| + * TRACE_EVENT_END0() at time=0.3ms
|
| + * This results in a single timeline slice from 0.1 with duration 0.2 on a
|
| + * specific thread.
|
| + *
|
| + * @constructor
|
| + */
|
| + function TimelineThreadSlice(title, colorId, start, args, opt_duration) {
|
| + TimelineSlice.call(this, title, colorId, start, args, opt_duration);
|
| + this.subSlices = [];
|
| + }
|
| +
|
| + TimelineThreadSlice.prototype = {
|
| + __proto__: TimelineSlice.prototype
|
| + };
|
| +
|
| + /**
|
| + * A TimelineAsyncSlice represents an interval of time during which an
|
| + * asynchronous operation is in progress. An AsyncSlice consumes no CPU time
|
| + * itself and so is only associated with Threads at its start and end point.
|
| + *
|
| + * @constructor
|
| + */
|
| + function TimelineAsyncSlice(title, colorId, start, args) {
|
| + TimelineSlice.call(this, title, colorId, start, args);
|
| + };
|
| +
|
| + TimelineAsyncSlice.prototype = {
|
| + __proto__: TimelineSlice.prototype,
|
| +
|
| + id: undefined,
|
| +
|
| + startThread: undefined,
|
| +
|
| + endThread: undefined
|
| + };
|
| +
|
| + /**
|
| * A TimelineThread stores all the trace events collected for a particular
|
| - * thread. We organize the slices on a thread by "subrows," where subrow 0
|
| - * has all the root slices, subrow 1 those nested 1 deep, and so on. There
|
| - * is also a set of non-nested subrows.
|
| + * thread. We organize the synchronous slices on a thread by "subrows," where
|
| + * subrow 0 has all the root slices, subrow 1 those nested 1 deep, and so on.
|
| + * The asynchronous slices are stored in an TimelineAsyncSliceGroup object.
|
| + *
|
| + * The slices stored on a TimelineThread should be instances of
|
| + * TimelineThreadSlice.
|
| *
|
| * @constructor
|
| */
|
| function TimelineThread(parent, tid) {
|
| + if (!parent)
|
| + throw 'Parent must be provided.';
|
| this.parent = parent;
|
| this.tid = tid;
|
| this.subRows = [[]];
|
| - this.nonNestedSubRows = [];
|
| + this.asyncSlices = new TimelineAsyncSliceGroup(this.ptid);
|
| + }
|
| +
|
| + var ptidMap = {};
|
| +
|
| + /**
|
| + * @return {String} A string that can be used as a unique key for a specific
|
| + * thread within a process.
|
| + */
|
| + TimelineThread.getPTIDFromPidAndTid = function(pid, tid) {
|
| + if (!ptidMap[pid])
|
| + ptidMap[pid] = {};
|
| + if (!ptidMap[pid][tid])
|
| + ptidMap[pid][tid] = pid + ':' + tid;
|
| + return ptidMap[pid][tid];
|
| }
|
|
|
| TimelineThread.prototype = {
|
| @@ -79,27 +131,46 @@ cr.define('tracing', function() {
|
| */
|
| name: undefined,
|
|
|
| + /**
|
| + * @return {string} A concatenation of the parent id and the thread's
|
| + * tid. Can be used to uniquely identify a thread.
|
| + */
|
| + get ptid() {
|
| + return TimelineThread.getPTIDFromPidAndTid(this.tid, this.parent.pid);
|
| + },
|
| +
|
| getSubrow: function(i) {
|
| while (i >= this.subRows.length)
|
| this.subRows.push([]);
|
| return this.subRows[i];
|
| },
|
|
|
| - addNonNestedSlice: function(slice) {
|
| - for (var i = 0; i < this.nonNestedSubRows.length; i++) {
|
| - var currSubRow = this.nonNestedSubRows[i];
|
| - var lastSlice = currSubRow[currSubRow.length - 1];
|
| - if (slice.start >= lastSlice.start + lastSlice.duration) {
|
| - currSubRow.push(slice);
|
| - return;
|
| - }
|
| +
|
| + shiftSubRow_: function(subRow, amount) {
|
| + for (var tS = 0; tS < subRow.length; tS++) {
|
| + var slice = subRow[tS];
|
| + slice.start = (slice.start + amount);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Shifts all the timestamps inside this thread forward by the amount
|
| + * specified.
|
| + */
|
| + shiftTimestampsForward: function(amount) {
|
| + if (this.cpuSlices)
|
| + this.shiftSubRow_(this.cpuSlices, amount);
|
| +
|
| + for (var tSR = 0; tSR < this.subRows.length; tSR++) {
|
| + this.shiftSubRow_(this.subRows[tSR], amount);
|
| }
|
| - this.nonNestedSubRows.push([slice]);
|
| +
|
| + this.asyncSlices.shiftTimestampsForward(amount);
|
| },
|
|
|
| /**
|
| * Updates the minTimestamp and maxTimestamp fields based on the
|
| - * current slices and nonNestedSubRows attached to the thread.
|
| + * current objects associated with the thread.
|
| */
|
| updateBounds: function() {
|
| var values = [];
|
| @@ -109,10 +180,10 @@ cr.define('tracing', function() {
|
| values.push(slices[0].start);
|
| values.push(slices[slices.length - 1].end);
|
| }
|
| - for (var i = 0; i < this.nonNestedSubRows.length; ++i) {
|
| - slices = this.nonNestedSubRows[i];
|
| - values.push(slices[0].start);
|
| - values.push(slices[slices.length - 1].end);
|
| + if (this.asyncSlices.slices.length) {
|
| + this.asyncSlices.updateBounds();
|
| + values.push(this.asyncSlices.minTimestamp);
|
| + values.push(this.asyncSlices.maxTimestamp);
|
| }
|
| if (values.length) {
|
| this.minTimestamp = Math.min.apply(Math, values);
|
| @@ -134,7 +205,7 @@ cr.define('tracing', function() {
|
| /**
|
| * @return {String} User friendly details about this thread.
|
| */
|
| - get userFriendlyDetials() {
|
| + get userFriendlyDetails() {
|
| return 'pid: ' + this.parent.pid +
|
| ', tid: ' + this.tid +
|
| (this.name ? ', name: ' + this.name : '');
|
| @@ -191,6 +262,15 @@ cr.define('tracing', function() {
|
| },
|
|
|
| /**
|
| + * Shifts all the timestamps inside this counter forward by the amount
|
| + * specified.
|
| + */
|
| + shiftTimestampsForward: function(amount) {
|
| + for (var sI = 0; sI < this.timestamps.length; sI++)
|
| + this.timestamps[sI] = (this.timestamps[sI] + amount);
|
| + },
|
| +
|
| + /**
|
| * Updates the bounds for this counter based on the samples it contains.
|
| */
|
| updateBounds: function() {
|
| @@ -260,6 +340,17 @@ cr.define('tracing', function() {
|
| },
|
|
|
| /**
|
| + * Shifts all the timestamps inside this process forward by the amount
|
| + * specified.
|
| + */
|
| + shiftTimestampsForward: function(amount) {
|
| + for (var tid in this.threads)
|
| + this.threads[tid].shiftTimestampsForward(amount);
|
| + for (var id in this.counters)
|
| + this.counters[id].shiftTimestampsForward(amount);
|
| + },
|
| +
|
| + /**
|
| * @return {TimlineThread} The thread identified by tid on this process,
|
| * creating it if it doesn't exist.
|
| */
|
| @@ -315,6 +406,17 @@ cr.define('tracing', function() {
|
| },
|
|
|
| /**
|
| + * Shifts all the timestamps inside this CPU forward by the amount
|
| + * specified.
|
| + */
|
| + shiftTimestampsForward: function(amount) {
|
| + for (var sI = 0; sI < this.slices.length; sI++)
|
| + this.slices[sI].start = (this.slices[sI].start + amount);
|
| + for (var id in this.counters)
|
| + this.counters[id].shiftTimestampsForward(amount);
|
| + },
|
| +
|
| + /**
|
| * Updates the minTimestamp and maxTimestamp fields based on the
|
| * current slices attached to the cpu.
|
| */
|
| @@ -337,6 +439,145 @@ cr.define('tracing', function() {
|
| return x.cpuNumber - y.cpuNumber;
|
| };
|
|
|
| + /**
|
| + * A group of AsyncSlices.
|
| + * @constructor
|
| + */
|
| + function TimelineAsyncSliceGroup(name) {
|
| + this.name = name;
|
| + this.slices = [];
|
| + }
|
| +
|
| + TimelineAsyncSliceGroup.prototype = {
|
| + __proto__: Object.prototype,
|
| +
|
| + /**
|
| + * Helper function that pushes the provided slice onto the slices array.
|
| + */
|
| + push: function(slice) {
|
| + this.slices.push(slice);
|
| + },
|
| +
|
| + /**
|
| + * @return {Number} The number of slices in this group.
|
| + */
|
| + get length() {
|
| + return this.slices.length;
|
| + },
|
| +
|
| + /**
|
| + * Built automatically by rebuildSubRows().
|
| + */
|
| + subRows_: undefined,
|
| +
|
| + /**
|
| + * Updates the bounds for this group based on the slices it contains.
|
| + */
|
| + sortSlices_: function() {
|
| + this.slices.sort(function(x, y) {
|
| + return x.start - y.start;
|
| + });
|
| + },
|
| +
|
| + /**
|
| + * Shifts all the timestamps inside this group forward by the amount
|
| + * specified.
|
| + */
|
| + shiftTimestampsForward: function(amount) {
|
| + for (var sI = 0; sI < this.slices.length; sI++)
|
| + this.slices[sI].start = (this.slices[sI].start + amount);
|
| + },
|
| +
|
| + /**
|
| + * Updates the bounds for this group based on the slices it contains.
|
| + */
|
| + updateBounds: function() {
|
| + this.sortSlices_();
|
| + if (this.slices.length) {
|
| + this.minTimestamp = this.slices[0].start;
|
| + this.maxTimestamp = this.slices[this.slices.length - 1].end;
|
| + } else {
|
| + this.minTimestamp = undefined;
|
| + this.maxTimestamp = undefined;
|
| + }
|
| + this.subRows_ = undefined;
|
| + },
|
| +
|
| + get subRows() {
|
| + if (!this.subRows_)
|
| + this.rebuildSubRows_();
|
| + return this.subRows_;
|
| + },
|
| +
|
| + /**
|
| + * Breaks up the list of slices into N rows, each of which is a list of
|
| + * slices that are non overlapping.
|
| + *
|
| + * It uses a very simple approach: walk through the slices in sorted order
|
| + * by start time. For each slice, try to fit it in an existing subRow. If it
|
| + * doesn't fit in any subrow, make another subRow.
|
| + */
|
| + rebuildSubRows_: function() {
|
| + this.sortSlices_();
|
| + var subRows = [];
|
| + for (var i = 0; i < this.slices.length; i++) {
|
| + var slice = this.slices[i];
|
| +
|
| + var found = false;
|
| + for (var j = 0; j < subRows.length; j++) {
|
| + var subRow = subRows[j];
|
| + var lastSliceInSubRow = subRow[subRow.length - 1];
|
| + if (slice.start >= lastSliceInSubRow.end) {
|
| + found = true;
|
| + subRow.push(slice);
|
| + }
|
| + }
|
| + if (!found) {
|
| + subRows.push([slice]);
|
| + }
|
| + }
|
| + this.subRows_ = subRows;
|
| + },
|
| +
|
| + /**
|
| + * Breaks up this group into slices based on start thread.
|
| + *
|
| + * @return {Array} An array of TimelineAsyncSliceGroups where each group has
|
| + * slices that started on the same thread.
|
| + **/
|
| + computeSubGroups: function() {
|
| + var subGroupsByPTID = {};
|
| + for (var i = 0; i < this.slices.length; ++i) {
|
| + var slice = this.slices[i];
|
| + var slicePTID = slice.startThread.ptid;
|
| + if (!subGroupsByPTID[slicePTID])
|
| + subGroupsByPTID[slicePTID] = new TimelineAsyncSliceGroup(this.name);
|
| + subGroupsByPTID[slicePTID].slices.push(slice);
|
| + }
|
| + var groups = [];
|
| + for (var ptid in subGroupsByPTID) {
|
| + var group = subGroupsByPTID[ptid];
|
| + group.updateBounds();
|
| + groups.push(group);
|
| + }
|
| + return groups;
|
| + }
|
| +
|
| + };
|
| +
|
| + /**
|
| + * Comparison between counters that orders by pid, then name.
|
| + */
|
| + TimelineCounter.compare = function(x, y) {
|
| + if (x.parent.pid != y.parent.pid) {
|
| + return TimelineProcess.compare(x.parent, y.parent.pid);
|
| + }
|
| + var tmp = x.name.localeCompare(y.name);
|
| + if (tmp == 0)
|
| + return x.tid - y.tid;
|
| + return tmp;
|
| + };
|
| +
|
| // The color pallette is split in half, with the upper
|
| // half of the pallette being the "highlighted" verison
|
| // of the base color. So, color 7's highlighted form is
|
| @@ -487,6 +728,7 @@ cr.define('tracing', function() {
|
| this.cpus = {};
|
| this.processes = {};
|
| this.importErrors = [];
|
| + this.asyncSliceGroups = {};
|
|
|
| if (opt_eventData)
|
| this.importEvents(opt_eventData, opt_zeroAndBoost);
|
| @@ -519,7 +761,9 @@ cr.define('tracing', function() {
|
| TimelineModelEmptyImporter.prototype = {
|
| __proto__: Object.prototype,
|
|
|
| - importEvents : function() {
|
| + importEvents: function() {
|
| + },
|
| + finalizeImport: function() {
|
| }
|
| };
|
|
|
| @@ -579,7 +823,7 @@ cr.define('tracing', function() {
|
| for (var s = 0; s < thread.subRows.length; s++)
|
| hasNonEmptySubrow |= thread.subRows[s].length > 0;
|
|
|
| - if (hasNonEmptySubrow || thread.nonNestedSubRows.legnth)
|
| + if (hasNonEmptySubrow || thread.asyncSlices.length > 0)
|
| prunedThreads[tid] = thread;
|
| }
|
| process.threads = prunedThreads;
|
| @@ -638,38 +882,10 @@ cr.define('tracing', function() {
|
| if (this.minTimestamp === undefined)
|
| return;
|
| var timeBase = this.minTimestamp;
|
| - var threads = this.getAllThreads();
|
| - for (var tI = 0; tI < threads.length; tI++) {
|
| - var thread = threads[tI];
|
| - var shiftSubRow = function(subRow) {
|
| - for (var tS = 0; tS < subRow.length; tS++) {
|
| - var slice = subRow[tS];
|
| - slice.start = (slice.start - timeBase);
|
| - }
|
| - };
|
| -
|
| - if (thread.cpuSlices)
|
| - shiftSubRow(thread.cpuSlices);
|
| -
|
| - for (var tSR = 0; tSR < thread.subRows.length; tSR++) {
|
| - shiftSubRow(thread.subRows[tSR]);
|
| - }
|
| - for (var tSR = 0; tSR < thread.nonNestedSubRows.length; tSR++) {
|
| - shiftSubRow(thread.nonNestedSubRows[tSR]);
|
| - }
|
| - }
|
| - var counters = this.getAllCounters();
|
| - for (var tI = 0; tI < counters.length; tI++) {
|
| - var counter = counters[tI];
|
| - for (var sI = 0; sI < counter.timestamps.length; sI++)
|
| - counter.timestamps[sI] = (counter.timestamps[sI] - timeBase);
|
| - }
|
| - var cpus = this.getAllCpus();
|
| - for (var tI = 0; tI < cpus.length; tI++) {
|
| - var cpu = cpus[tI];
|
| - for (var sI = 0; sI < cpu.slices.length; sI++)
|
| - cpu.slices[sI].start = (cpu.slices[sI].start - timeBase);
|
| - }
|
| + for (var pid in this.processes)
|
| + this.processes[pid].shiftTimestampsForward(-timeBase);
|
| + for (var cpuNumber in this.cpus)
|
| + this.cpus[cpuNumber].shiftTimestampsForward(-timeBase);
|
| this.updateBounds();
|
| },
|
|
|
| @@ -732,6 +948,7 @@ cr.define('tracing', function() {
|
| * @param {Object} events Events to import.
|
| * @param {boolean} isChildImport True the eventData being imported is an
|
| * additional trace after the primary eventData.
|
| + * @return {TimelineModelImporter} The importer used for the eventData.
|
| */
|
| importOneTrace_: function(eventData, isAdditionalImport) {
|
| var importerConstructor;
|
| @@ -747,7 +964,7 @@ cr.define('tracing', function() {
|
| var importer = new importerConstructor(
|
| this, eventData, isAdditionalImport);
|
| importer.importEvents();
|
| - this.pruneEmptyThreads();
|
| + return importer;
|
| },
|
|
|
| /**
|
| @@ -772,12 +989,20 @@ cr.define('tracing', function() {
|
| if (opt_zeroAndBoost === undefined)
|
| opt_zeroAndBoost = true;
|
|
|
| - this.importOneTrace_(eventData, false);
|
| + activeImporters = [];
|
| + var importer = this.importOneTrace_(eventData, false);
|
| + activeImporters.push(importer);
|
| if (opt_additionalEventData) {
|
| for (var i = 0; i < opt_additionalEventData.length; ++i) {
|
| - this.importOneTrace_(opt_additionalEventData[i], true);
|
| + importer = this.importOneTrace_(opt_additionalEventData[i], true);
|
| + activeImporters.push(importer);
|
| }
|
| }
|
| + for (var i = 0; i < activeImporters.length; ++i)
|
| + activeImporters[i].finalizeImport();
|
| +
|
| + for (var i = 0; i < activeImporters.length; ++i)
|
| + this.pruneEmptyThreads();
|
|
|
| this.updateBounds();
|
|
|
| @@ -802,10 +1027,13 @@ cr.define('tracing', function() {
|
| getStringColorId: getStringColorId,
|
|
|
| TimelineSlice: TimelineSlice,
|
| + TimelineThreadSlice: TimelineThreadSlice,
|
| + TimelineAsyncSlice: TimelineAsyncSlice,
|
| TimelineThread: TimelineThread,
|
| TimelineCounter: TimelineCounter,
|
| TimelineProcess: TimelineProcess,
|
| TimelineCpu: TimelineCpu,
|
| + TimelineAsyncSliceGroup: TimelineAsyncSliceGroup,
|
| TimelineModel: TimelineModel
|
| };
|
|
|
|
|