1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
8 * @fileoverview Provides the SliceGroup class.
10 base.require('base.range');
11 base.require('tracing.trace_model.slice');
12 base.require('tracing.color_scheme');
13 base.require('tracing.filter');
15 base.exportTo('tracing.trace_model', function() {
16 var Slice = tracing.trace_model.Slice;
19 * A group of Slices, plus code to create them from B/E events, as
20 * well as arrange them into subRows.
22 * Do not mutate the slices array directly. Modify it only by
23 * SliceGroup mutation methods.
26 * @param {function(new:Slice, category, title, colorId, start, args)=}
27 * opt_sliceConstructor The constructor to use when creating slices.
29 function SliceGroup(opt_sliceConstructor) {
30 var sliceConstructor = opt_sliceConstructor || Slice;
31 this.sliceConstructor = sliceConstructor;
33 this.openPartialSlices_ = [];
36 this.bounds = new base.Range();
37 this.topLevelSlices = [];
40 SliceGroup.prototype = {
41 __proto__: Object.prototype,
44 * @return {Number} The number of slices in this group.
47 return this.slices.length;
51 * Helper function that pushes the provided slice onto the slices array.
52 * @param {Slice} slice The slice to be added to the slices array.
54 pushSlice: function(slice) {
55 this.slices.push(slice);
60 * Helper function that pushes the provided slice onto the slices array.
61 * @param {Array.<Slice>} slices An array of slices to be added.
63 pushSlices: function(slices) {
64 this.slices.push.apply(this.slices, slices);
68 * Push an instant event into the slice list.
69 * @param {tracing.trace_model.InstantEvent} instantEvent The instantEvent.
71 pushInstantEvent: function(instantEvent) {
72 this.slices.push(instantEvent);
76 * Opens a new slice in the group's slices.
78 * Calls to beginSlice and
79 * endSlice must be made with non-monotonically-decreasing timestamps.
81 * @param {String} category Category name of the slice to add.
82 * @param {String} title Title of the slice to add.
83 * @param {Number} ts The timetsamp of the slice, in milliseconds.
84 * @param {Object.<string, Object>=} opt_args Arguments associated with
87 beginSlice: function(category, title, ts, opt_args, opt_tts) {
88 if (this.openPartialSlices_.length) {
89 var prevSlice = this.openPartialSlices_[
90 this.openPartialSlices_.length - 1];
91 if (ts < prevSlice.start)
92 throw new Error('Slices must be added in increasing timestamp order');
95 var colorId = tracing.getStringColorId(title);
96 var slice = new this.sliceConstructor(category, title, colorId, ts,
97 opt_args ? opt_args : {}, null,
99 this.openPartialSlices_.push(slice);
100 slice.didNotFinish = true;
101 this.pushSlice(slice);
106 isTimestampValidForBeginOrEnd: function(ts) {
107 if (!this.openPartialSlices_.length)
109 var top = this.openPartialSlices_[this.openPartialSlices_.length - 1];
110 return ts >= top.start;
114 * @return {Number} The number of beginSlices for which an endSlice has not
117 get openSliceCount() {
118 return this.openPartialSlices_.length;
121 get mostRecentlyOpenedPartialSlice() {
122 if (!this.openPartialSlices_.length)
124 return this.openPartialSlices_[this.openPartialSlices_.length - 1];
128 * Ends the last begun slice in this group and pushes it onto the slice
131 * @param {Number} ts Timestamp when the slice ended.
132 * @return {Slice} slice.
134 endSlice: function(ts, opt_tts) {
135 if (!this.openSliceCount)
136 throw new Error('endSlice called without an open slice');
138 var slice = this.openPartialSlices_[this.openSliceCount - 1];
139 this.openPartialSlices_.splice(this.openSliceCount - 1, 1);
140 if (ts < slice.start)
141 throw new Error('Slice ' + slice.title +
142 ' end time is before its start.');
144 slice.duration = ts - slice.start;
145 slice.didNotFinish = false;
147 if (opt_tts && slice.threadStart)
148 slice.threadTime = opt_tts - slice.threadStart;
154 * Push a complete event as a Slice into the slice list.
155 * The timestamp can be in any order.
157 * @param {String} category Category name of the slice to add.
158 * @param {String} title Title of the slice to add.
159 * @param {Number} ts The timetsamp of the slice, in milliseconds.
160 * @param {Number} duration The duration of the slice, in milliseconds.
161 * @param {Object.<string, Object>=} opt_args Arguments associated with
164 pushCompleteSlice: function(category, title, ts, duration, opt_args) {
165 var colorId = tracing.getStringColorId(title);
166 var slice = new this.sliceConstructor(category, title, colorId, ts,
167 opt_args ? opt_args : {},
169 if (duration === undefined)
170 slice.didNotFinish = true;
171 this.pushSlice(slice);
176 * Closes any open slices.
177 * @param {Number=} opt_maxTimestamp The end time to use for the closed
178 * slices. If not provided,
179 * the max timestamp for this slice is provided.
181 autoCloseOpenSlices: function(opt_maxTimestamp) {
182 if (!opt_maxTimestamp) {
184 opt_maxTimestamp = this.bounds.max;
186 for (var sI = 0; sI < this.slices.length; sI++) {
187 var slice = this.slices[sI];
188 if (slice.didNotFinish)
189 slice.duration = opt_maxTimestamp - slice.start;
191 this.openPartialSlices_ = [];
195 * Shifts all the timestamps inside this group forward by the amount
198 shiftTimestampsForward: function(amount) {
199 for (var sI = 0; sI < this.slices.length; sI++) {
200 var slice = this.slices[sI];
201 slice.start = (slice.start + amount);
206 * Updates the bounds for this group based on the slices it contains.
208 updateBounds: function() {
210 for (var i = 0; i < this.slices.length; i++) {
211 this.bounds.addValue(this.slices[i].start);
212 this.bounds.addValue(this.slices[i].end);
216 copySlice: function(slice) {
217 var newSlice = new this.sliceConstructor(slice.category, slice.title,
218 slice.colorId, slice.start,
219 slice.args, slice.duration);
220 newSlice.didNotFinish = slice.didNotFinish;
224 iterateAllEvents: function(callback) {
225 this.slices.forEach(callback);
229 * Construct subSlices for this group.
230 * Populate the group topLevelSlices, parent slices get a subSlices[]
231 * and a selfTime, child slices get a parentSlice reference.
233 createSubSlices: function() {
234 function addSliceIfBounds(root, child) {
235 // Because we know that the start time of child is >= the start time
236 // of all other slices seen so far, we can just check the last slice
237 // of each row for bounding.
238 if (root.bounds(child)) {
239 if (root.subSlices && root.subSlices.length > 0) {
240 if (addSliceIfBounds(root.subSlices[root.subSlices.length - 1],
245 root.selfTime = root.duration;
246 child.parentSlice = root;
249 root.subSlices.push(child);
250 root.selfTime -= child.duration;
256 if (!this.slices.length)
260 for (var i = 0; i < this.slices.length; i++) {
261 if (this.slices[i].subSlices)
262 this.slices[i].subSlices.splice(0,
263 this.slices[i].subSlices.length);
267 var groupSlices = this.slices;
268 ops.sort(function(ix, iy) {
269 var x = groupSlices[ix];
270 var y = groupSlices[iy];
271 if (x.start != y.start)
272 return x.start - y.start;
274 // Elements get inserted into the slices array in order of when the
275 // slices start. Because slices must be properly nested, we break
276 // start-time ties by assuming that the elements appearing earlier
277 // in the slices array (and thus ending earlier) start earlier.
281 var rootSlice = this.slices[ops[0]];
282 this.topLevelSlices = [];
283 this.topLevelSlices.push(rootSlice);
284 for (var i = 1; i < ops.length; i++) {
285 var slice = this.slices[ops[i]];
286 if (!addSliceIfBounds(rootSlice, slice)) {
288 this.topLevelSlices.push(rootSlice);
295 * Merge two slice groups.
297 * If the two groups do not nest properly some of the slices of groupB will
298 * be split to accomodate the improper nesting. This is done to accomodate
299 * combined kernel and userland call stacks on Android. Because userland
300 * tracing is done by writing to the trace_marker file, the kernel calls
301 * that get invoked as part of that write may not be properly nested with
302 * the userland call trace. For example the following sequence may occur:
304 * kernel enter sys_write (the write to trace_marker)
305 * user enter some_function
306 * kernel exit sys_write
308 * kernel enter sys_write (the write to trace_marker)
309 * user exit some_function
310 * kernel exit sys_write
312 * This is handled by splitting the sys_write call into two slices as
315 * | sys_write | some_function | sys_write (cont.) |
316 * | sys_write (cont.) | | sys_write |
318 * The colorId of both parts of the split slices are kept the same, and the
319 * " (cont.)" suffix is appended to the later parts of a split slice.
321 * The two input SliceGroups are not modified by this, and the merged
322 * SliceGroup will contain a copy of each of the input groups' slices (those
323 * copies may be split).
325 SliceGroup.merge = function(groupA, groupB) {
326 // This is implemented by traversing the two slice groups in reverse
327 // order. The slices in each group are sorted by ascending end-time, so
328 // we must do the traversal from back to front in order to maintain the
331 // We traverse the two groups simultaneously, merging as we go. At each
332 // iteration we choose the group from which to take the next slice based
333 // on which group's next slice has the greater end-time. During this
334 // traversal we maintain a stack of currently "open" slices for each input
335 // group. A slice is considered "open" from the time it gets reached in
336 // our input group traversal to the time we reach an slice in this
337 // traversal with an end-time before the start time of the "open" slice.
339 // Each time a slice from groupA is opened or closed (events corresponding
340 // to the end-time and start-time of the input slice, respectively) we
341 // split all of the currently open slices from groupB.
343 if (groupA.openPartialSlices_.length > 0) {
344 throw new Error('groupA has open partial slices');
346 if (groupB.openPartialSlices_.length > 0) {
347 throw new Error('groupB has open partial slices');
350 var result = new SliceGroup();
352 var slicesA = groupA.slices;
353 var slicesB = groupB.slices;
359 var splitOpenSlices = function(when) {
360 for (var i = 0; i < openB.length; i++) {
361 var oldSlice = openB[i];
362 var oldEnd = oldSlice.end;
363 if (when < oldSlice.start || oldEnd < when) {
364 throw new Error('slice should not be split');
367 var newSlice = result.copySlice(oldSlice);
368 newSlice.start = when;
369 newSlice.duration = oldEnd - when;
370 if (newSlice.title.indexOf(' (cont.)') == -1)
371 newSlice.title += ' (cont.)';
372 oldSlice.duration = when - oldSlice.start;
374 result.pushSlice(newSlice);
378 var closeOpenSlices = function(upTo) {
379 while (openA.length > 0 || openB.length > 0) {
380 var nextA = openA[openA.length - 1];
381 var nextB = openB[openB.length - 1];
382 var endA = nextA && nextA.end;
383 var endB = nextB && nextB.end;
385 if ((endA === undefined || endA > upTo) &&
386 (endB === undefined || endB > upTo)) {
390 if (endB === undefined || endA < endB) {
391 splitOpenSlices(endA);
399 while (idxA < slicesA.length || idxB < slicesB.length) {
400 var sA = slicesA[idxA];
401 var sB = slicesB[idxB];
402 var nextSlice, isFromB;
404 if (sA === undefined || (sB !== undefined && sA.start > sB.start)) {
405 nextSlice = result.copySlice(sB);
409 nextSlice = result.copySlice(sA);
414 closeOpenSlices(nextSlice.start);
416 result.pushSlice(nextSlice);
419 openB.push(nextSlice);
421 splitOpenSlices(nextSlice.start);
422 openA.push(nextSlice);
432 SliceGroup: SliceGroup