3 Copyright (c) 2013 The Chromium Authors. All rights reserved.
4 Use of this source code is governed by a BSD-style license that can be
5 found in the LICENSE file.
8 <link rel="import" href="/tracing/trace_model/slice.html">
9 <link rel="import" href="/tracing/color_scheme.html">
10 <link rel="import" href="/tracing/filter.html">
11 <link rel="import" href="/base/guid.html">
12 <link rel="import" href="/base/range.html">
18 * @fileoverview Provides the SliceGroup class.
20 tv.exportTo('tracing.trace_model', function() {
21 var Slice = tracing.trace_model.Slice;
24 * A group of Slices, plus code to create them from B/E events, as
25 * well as arrange them into subRows.
27 * Do not mutate the slices array directly. Modify it only by
28 * SliceGroup mutation methods.
31 * @param {function(new:Slice, category, title, colorId, start, args)=}
32 * opt_sliceConstructor The constructor to use when creating slices.
34 function SliceGroup(parentThread, opt_sliceConstructor, opt_name) {
35 this.guid_ = tv.GUID.allocate();
37 this.parentThread_ = parentThread;
39 var sliceConstructor = opt_sliceConstructor || Slice;
40 this.sliceConstructor = sliceConstructor;
42 this.openPartialSlices_ = [];
45 this.bounds = new tv.Range();
46 this.topLevelSlices = [];
47 this.name_ = opt_name;
50 SliceGroup.prototype = {
51 __proto__: Object.prototype,
58 return this.parentThrad_;
62 return this.parentThread_.parent.model;
65 getSettingsKey: function() {
68 var parentKey = this.parentThread_.getSettingsKey();
71 return parentKey + '.' + this.name;
75 * @return {Number} The number of slices in this group.
78 return this.slices.length;
82 * Helper function that pushes the provided slice onto the slices array.
83 * @param {Slice} slice The slice to be added to the slices array.
85 pushSlice: function(slice) {
86 this.slices.push(slice);
91 * Helper function that pushes the provided slice onto the slices array.
92 * @param {Array.<Slice>} slices An array of slices to be added.
94 pushSlices: function(slices) {
95 this.slices.push.apply(this.slices, slices);
99 * Push an instant event into the slice list.
100 * @param {tracing.trace_model.InstantEvent} instantEvent The instantEvent.
102 pushInstantEvent: function(instantEvent) {
103 this.slices.push(instantEvent);
107 * Opens a new slice in the group's slices.
109 * Calls to beginSlice and
110 * endSlice must be made with non-monotonically-decreasing timestamps.
112 * @param {String} category Category name of the slice to add.
113 * @param {String} title Title of the slice to add.
114 * @param {Number} ts The timetsamp of the slice, in milliseconds.
115 * @param {Object.<string, Object>=} opt_args Arguments associated with
118 beginSlice: function(category, title, ts, opt_args, opt_tts) {
119 if (this.openPartialSlices_.length) {
120 var prevSlice = this.openPartialSlices_[
121 this.openPartialSlices_.length - 1];
122 if (ts < prevSlice.start)
123 throw new Error('Slices must be added in increasing timestamp order');
126 var colorId = tv.ui.getStringColorId(title);
127 var slice = new this.sliceConstructor(category, title, colorId, ts,
128 opt_args ? opt_args : {}, null,
130 this.openPartialSlices_.push(slice);
131 slice.didNotFinish = true;
132 this.pushSlice(slice);
137 isTimestampValidForBeginOrEnd: function(ts) {
138 if (!this.openPartialSlices_.length)
140 var top = this.openPartialSlices_[this.openPartialSlices_.length - 1];
141 return ts >= top.start;
145 * @return {Number} The number of beginSlices for which an endSlice has not
148 get openSliceCount() {
149 return this.openPartialSlices_.length;
152 get mostRecentlyOpenedPartialSlice() {
153 if (!this.openPartialSlices_.length)
155 return this.openPartialSlices_[this.openPartialSlices_.length - 1];
159 * Ends the last begun slice in this group and pushes it onto the slice
162 * @param {Number} ts Timestamp when the slice ended.
163 * @return {Slice} slice.
165 endSlice: function(ts, opt_tts) {
166 if (!this.openSliceCount)
167 throw new Error('endSlice called without an open slice');
169 var slice = this.openPartialSlices_[this.openSliceCount - 1];
170 this.openPartialSlices_.splice(this.openSliceCount - 1, 1);
171 if (ts < slice.start)
172 throw new Error('Slice ' + slice.title +
173 ' end time is before its start.');
175 slice.duration = ts - slice.start;
176 slice.didNotFinish = false;
178 if (opt_tts && slice.cpuStart !== undefined)
179 slice.cpuDuration = opt_tts - slice.cpuStart;
185 * Push a complete event as a Slice into the slice list.
186 * The timestamp can be in any order.
188 * @param {String} category Category name of the slice to add.
189 * @param {String} title Title of the slice to add.
190 * @param {Number} ts The timetsamp of the slice, in milliseconds.
191 * @param {Number} duration The duration of the slice, in milliseconds.
192 * @param {Object.<string, Object>=} opt_args Arguments associated with
195 pushCompleteSlice: function(category, title, ts, duration, tts,
196 cpuDuration, opt_args) {
197 var colorId = tv.ui.getStringColorId(title);
198 var slice = new this.sliceConstructor(category, title, colorId, ts,
199 opt_args ? opt_args : {},
200 duration, tts, cpuDuration);
201 if (duration === undefined)
202 slice.didNotFinish = true;
203 this.pushSlice(slice);
208 * Closes any open slices.
209 * @param {Number=} opt_maxTimestamp The end time to use for the closed
210 * slices. If not provided,
211 * the max timestamp for this slice is provided.
213 autoCloseOpenSlices: function(opt_maxTimestamp) {
214 if (!opt_maxTimestamp) {
216 opt_maxTimestamp = this.bounds.max;
218 for (var sI = 0; sI < this.slices.length; sI++) {
219 var slice = this.slices[sI];
220 if (slice.didNotFinish)
221 slice.duration = opt_maxTimestamp - slice.start;
223 this.openPartialSlices_ = [];
227 * Shifts all the timestamps inside this group forward by the amount
230 shiftTimestampsForward: function(amount) {
231 for (var sI = 0; sI < this.slices.length; sI++) {
232 var slice = this.slices[sI];
233 slice.start = (slice.start + amount);
238 * Updates the bounds for this group based on the slices it contains.
240 updateBounds: function() {
242 for (var i = 0; i < this.slices.length; i++) {
243 this.bounds.addValue(this.slices[i].start);
244 this.bounds.addValue(this.slices[i].end);
248 copySlice: function(slice) {
249 var newSlice = new this.sliceConstructor(slice.category, slice.title,
250 slice.colorId, slice.start,
251 slice.args, slice.duration, slice.cpuStart, slice.cpuDuration);
252 newSlice.didNotFinish = slice.didNotFinish;
256 iterateAllEvents: function(callback, opt_this) {
257 this.slices.forEach(callback, opt_this);
265 * Construct subSlices for this group.
266 * Populate the group topLevelSlices, parent slices get a subSlices[],
267 * a selfThreadTime and a selfTime, child slices get a parentSlice
270 createSubSlices: function() {
271 function addSliceIfBounds(root, child) {
272 // Because we know that the start time of child is >= the start time
273 // of all other slices seen so far, we can just check the last slice
274 // of each row for bounding.
275 if (root.bounds(child)) {
276 if (root.subSlices && root.subSlices.length > 0) {
277 if (addSliceIfBounds(root.subSlices[root.subSlices.length - 1],
282 root.selfTime = root.duration;
283 if (!root.cpuSelfTime && root.cpuDuration)
284 root.cpuSelfTime = root.cpuDuration;
285 child.parentSlice = root;
288 root.subSlices.push(child);
289 root.selfTime -= child.duration;
290 if (child.cpuDuration)
291 root.cpuSelfTime -= child.cpuDuration;
297 if (!this.slices.length)
301 for (var i = 0; i < this.slices.length; i++) {
302 if (this.slices[i].subSlices)
303 this.slices[i].subSlices.splice(0,
304 this.slices[i].subSlices.length);
308 var groupSlices = this.slices;
309 ops.sort(function(ix, iy) {
310 var x = groupSlices[ix];
311 var y = groupSlices[iy];
312 if (x.start != y.start)
313 return x.start - y.start;
315 // Elements get inserted into the slices array in order of when the
316 // slices start. Because slices must be properly nested, we break
317 // start-time ties by assuming that the elements appearing earlier
318 // in the slices array (and thus ending earlier) start earlier.
322 var rootSlice = this.slices[ops[0]];
323 this.topLevelSlices = [];
324 this.topLevelSlices.push(rootSlice);
325 for (var i = 1; i < ops.length; i++) {
326 var slice = this.slices[ops[i]];
327 if (!addSliceIfBounds(rootSlice, slice)) {
329 this.topLevelSlices.push(rootSlice);
336 * Merge two slice groups.
338 * If the two groups do not nest properly some of the slices of groupB will
339 * be split to accomodate the improper nesting. This is done to accomodate
340 * combined kernel and userland call stacks on Android. Because userland
341 * tracing is done by writing to the trace_marker file, the kernel calls
342 * that get invoked as part of that write may not be properly nested with
343 * the userland call trace. For example the following sequence may occur:
345 * kernel enter sys_write (the write to trace_marker)
346 * user enter some_function
347 * kernel exit sys_write
349 * kernel enter sys_write (the write to trace_marker)
350 * user exit some_function
351 * kernel exit sys_write
353 * This is handled by splitting the sys_write call into two slices as
356 * | sys_write | some_function | sys_write (cont.) |
357 * | sys_write (cont.) | | sys_write |
359 * The colorId of both parts of the split slices are kept the same, and the
360 * " (cont.)" suffix is appended to the later parts of a split slice.
362 * The two input SliceGroups are not modified by this, and the merged
363 * SliceGroup will contain a copy of each of the input groups' slices (those
364 * copies may be split).
366 SliceGroup.merge = function(groupA, groupB) {
367 // This is implemented by traversing the two slice groups in reverse
368 // order. The slices in each group are sorted by ascending end-time, so
369 // we must do the traversal from back to front in order to maintain the
372 // We traverse the two groups simultaneously, merging as we go. At each
373 // iteration we choose the group from which to take the next slice based
374 // on which group's next slice has the greater end-time. During this
375 // traversal we maintain a stack of currently "open" slices for each input
376 // group. A slice is considered "open" from the time it gets reached in
377 // our input group traversal to the time we reach an slice in this
378 // traversal with an end-time before the start time of the "open" slice.
380 // Each time a slice from groupA is opened or closed (events corresponding
381 // to the end-time and start-time of the input slice, respectively) we
382 // split all of the currently open slices from groupB.
384 if (groupA.openPartialSlices_.length > 0) {
385 throw new Error('groupA has open partial slices');
387 if (groupB.openPartialSlices_.length > 0) {
388 throw new Error('groupB has open partial slices');
390 if (groupA.parentThread != groupB.parentThread)
391 throw new Error('Different parent threads. Cannot merge');
393 var result = new SliceGroup(groupA.parentThread);
395 var slicesA = groupA.slices;
396 var slicesB = groupB.slices;
402 var splitOpenSlices = function(when) {
403 for (var i = 0; i < openB.length; i++) {
404 var oldSlice = openB[i];
405 var oldEnd = oldSlice.end;
406 if (when < oldSlice.start || oldEnd < when) {
407 throw new Error('slice should not be split');
410 var newSlice = result.copySlice(oldSlice);
411 newSlice.start = when;
412 newSlice.duration = oldEnd - when;
413 if (newSlice.title.indexOf(' (cont.)') == -1)
414 newSlice.title += ' (cont.)';
415 oldSlice.duration = when - oldSlice.start;
417 result.pushSlice(newSlice);
421 var closeOpenSlices = function(upTo) {
422 while (openA.length > 0 || openB.length > 0) {
423 var nextA = openA[openA.length - 1];
424 var nextB = openB[openB.length - 1];
425 var endA = nextA && nextA.end;
426 var endB = nextB && nextB.end;
428 if ((endA === undefined || endA > upTo) &&
429 (endB === undefined || endB > upTo)) {
433 if (endB === undefined || endA < endB) {
434 splitOpenSlices(endA);
442 while (idxA < slicesA.length || idxB < slicesB.length) {
443 var sA = slicesA[idxA];
444 var sB = slicesB[idxB];
445 var nextSlice, isFromB;
447 if (sA === undefined || (sB !== undefined && sA.start > sB.start)) {
448 nextSlice = result.copySlice(sB);
452 nextSlice = result.copySlice(sA);
457 closeOpenSlices(nextSlice.start);
459 result.pushSlice(nextSlice);
462 openB.push(nextSlice);
464 splitOpenSlices(nextSlice.start);
465 openA.push(nextSlice);
475 SliceGroup: SliceGroup