1 // Copyright (c) 2012 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. 4 5 'use strict'; 6 7 /** 8 * @fileoverview Provides the TimelineSliceGroup class. 9 */ 10 base.require('timeline_slice'); 11 base.require('timeline_color_scheme'); 12 base.require('timeline_filter'); 13 14 base.exportTo('tracing', function() { 15 var TimelineSlice = tracing.TimelineSlice; 16 17 /** 18 * A group of TimelineSlices, plus code to create them from B/E events, as 19 * well as arrange them into subRows. 20 * 21 * Do not mutate the slices array directly. Modify it only by 22 * TimelineSliceGroup mutation methods. 23 * 24 * @constructor 25 * @param {function(new:TimelineSlice, category, title, colorId, start, args)} 26 * opt_sliceConstructor The constructor to use when creating slices. 27 */ 28 function TimelineSliceGroup(opt_sliceConstructor) { 29 var sliceConstructor = opt_sliceConstructor || TimelineSlice; 30 this.sliceConstructor = sliceConstructor; 31 32 this.openPartialSlices_ = []; 33 34 this.slices = []; 35 } 36 37 TimelineSliceGroup.prototype = { 38 __proto__: Object.prototype, 39 40 /** 41 * Helper function that pushes the provided slice onto the slices array. 42 * @param {TimelineSlice} slice The slice to be added to the slices array. 43 */ 44 pushSlice: function(slice) { 45 this.slices.push(slice); 46 return slice; 47 }, 48 49 /** 50 * Helper function that pushes the provided slice onto the slices array. 51 * @param {Array.<TimelineSlice>} slices An array of slices to be added. 52 */ 53 pushSlices: function(slices) { 54 this.slices.push.apply(this.slices, slices); 55 }, 56 57 /** 58 * Opens a new slice in the group's slices. 59 * 60 * Calls to beginSlice and 61 * endSlice must be made with non-monotonically-decreasing timestamps. 62 * 63 * @param {String} title Title of the slice to add. 64 * @param {Number} ts The timetsamp of the slice, in milliseconds. 65 * @param {Object.<string, Object>} opt_args Arguments associated with 66 * the slice. 67 */ 68 beginSlice: function(category, title, ts, opt_args) { 69 if (this.openPartialSlices_.length) { 70 var prevSlice = this.openPartialSlices_[ 71 this.openPartialSlices_.length - 1]; 72 if (ts < prevSlice.start) 73 throw new Error('Slices must be added in increasing timestamp order'); 74 } 75 76 var colorId = tracing.getStringColorId(title); 77 var slice = new this.sliceConstructor(category, title, colorId, ts, 78 opt_args ? opt_args : {}); 79 this.openPartialSlices_.push(slice); 80 return slice; 81 }, 82 83 isTimestampValidForBeginOrEnd: function(ts) { 84 if (!this.openPartialSlices_.length) 85 return true; 86 var top = this.openPartialSlices_[this.openPartialSlices_.length - 1]; 87 return ts >= top.start; 88 }, 89 90 /** 91 * @return {Number} The number of beginSlices for which an endSlice has not 92 * been issued. 93 */ 94 get openSliceCount() { 95 return this.openPartialSlices_.length; 96 }, 97 98 /** 99 * Ends the last begun slice in this group and pushes it onto the slice 100 * array. 101 * 102 * @param {Number} ts Timestamp when the slice ended. 103 * @return {TimelineSlice} slice. 104 */ 105 endSlice: function(ts) { 106 if (!this.openSliceCount) 107 throw new Error('endSlice called without an open slice'); 108 var slice = this.openPartialSlices_[this.openSliceCount - 1]; 109 this.openPartialSlices_.splice(this.openSliceCount - 1, 1); 110 if (ts < slice.start) 111 throw new Error('Slice ' + slice.name + 112 ' end time is before its start.'); 113 114 slice.duration = ts - slice.start; 115 this.pushSlice(slice); 116 117 return slice; 118 }, 119 120 /** 121 * Closes any open slices. 122 * @param {Number} opt_maxTimestamp The end time to use for the closed 123 * slices. If not provided, 124 * the max timestamp for this slice is provided. 125 */ 126 autoCloseOpenSlices: function(opt_maxTimestamp) { 127 if (!opt_maxTimestamp) { 128 this.updateBounds(); 129 opt_maxTimestamp = this.maxTimestamp; 130 } 131 while (this.openSliceCount > 0) { 132 var slice = this.endSlice(opt_maxTimestamp); 133 slice.didNotFinish = true; 134 } 135 }, 136 137 /** 138 * Shifts all the timestamps inside this group forward by the amount 139 * specified. 140 */ 141 shiftTimestampsForward: function(amount) { 142 for (var sI = 0; sI < this.slices.length; sI++) { 143 var slice = this.slices[sI]; 144 slice.start = (slice.start + amount); 145 } 146 for (var sI = 0; sI < this.openPartialSlices_.length; sI++) { 147 var slice = this.openPartialSlices_[i]; 148 slice.start = (slice.start + amount); 149 } 150 }, 151 152 /** 153 * Updates the bounds for this group based on the slices it contains. 154 */ 155 updateBounds: function() { 156 var vals = []; 157 if (this.slices.length) { 158 var minTimestamp = Number.MAX_VALUE; 159 var maxTimestamp = -Number.MAX_VALUE; 160 for (var i = 0; i < this.slices.length; i++) { 161 if (this.slices[i].start < minTimestamp) 162 minTimestamp = this.slices[i].start; 163 if (this.slices[i].end > maxTimestamp) 164 maxTimestamp = this.slices[i].end; 165 } 166 vals.push(minTimestamp); 167 vals.push(maxTimestamp); 168 } 169 170 if (this.openPartialSlices_.length) { 171 vals.push(this.openPartialSlices_[0].start); 172 vals.push( 173 this.openPartialSlices_[this.openPartialSlices_.length - 1].start); 174 } 175 176 if (vals.length) { 177 this.minTimestamp = Math.min.apply(Math, vals); 178 this.maxTimestamp = Math.max.apply(Math, vals); 179 } else { 180 this.minTimestamp = undefined; 181 this.maxTimestamp = undefined; 182 } 183 } 184 }; 185 186 return { 187 TimelineSliceGroup: TimelineSliceGroup 188 }; 189 }); 190