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 SliceGroup class. 9 */ 10base.require('range'); 11base.require('model.slice'); 12base.require('color_scheme'); 13base.require('filter'); 14 15base.exportTo('tracing.model', function() { 16 var Slice = tracing.model.Slice; 17 18 /** 19 * A group of Slices, plus code to create them from B/E events, as 20 * well as arrange them into subRows. 21 * 22 * Do not mutate the slices array directly. Modify it only by 23 * SliceGroup mutation methods. 24 * 25 * @constructor 26 * @param {function(new:Slice, category, title, colorId, start, args)} 27 * opt_sliceConstructor The constructor to use when creating slices. 28 */ 29 function SliceGroup(opt_sliceConstructor) { 30 var sliceConstructor = opt_sliceConstructor || Slice; 31 this.sliceConstructor = sliceConstructor; 32 33 this.openPartialSlices_ = []; 34 35 this.slices = []; 36 this.bounds = new base.Range(); 37 } 38 39 SliceGroup.prototype = { 40 __proto__: Object.prototype, 41 42 /** 43 * @return {Number} The number of slices in this group. 44 */ 45 get length() { 46 return this.slices.length; 47 }, 48 49 /** 50 * Helper function that pushes the provided slice onto the slices array. 51 * @param {Slice} slice The slice to be added to the slices array. 52 */ 53 pushSlice: function(slice) { 54 this.slices.push(slice); 55 return slice; 56 }, 57 58 /** 59 * Helper function that pushes the provided slice onto the slices array. 60 * @param {Array.<Slice>} slices An array of slices to be added. 61 */ 62 pushSlices: function(slices) { 63 this.slices.push.apply(this.slices, slices); 64 }, 65 66 /** 67 * Opens a new slice in the group's slices. 68 * 69 * Calls to beginSlice and 70 * endSlice must be made with non-monotonically-decreasing timestamps. 71 * 72 * @param {String} title Title of the slice to add. 73 * @param {Number} ts The timetsamp of the slice, in milliseconds. 74 * @param {Object.<string, Object>} opt_args Arguments associated with 75 * the slice. 76 */ 77 beginSlice: function(category, title, ts, opt_args) { 78 if (this.openPartialSlices_.length) { 79 var prevSlice = this.openPartialSlices_[ 80 this.openPartialSlices_.length - 1]; 81 if (ts < prevSlice.start) 82 throw new Error('Slices must be added in increasing timestamp order'); 83 } 84 85 var colorId = tracing.getStringColorId(title); 86 var slice = new this.sliceConstructor(category, title, colorId, ts, 87 opt_args ? opt_args : {}); 88 this.openPartialSlices_.push(slice); 89 90 return slice; 91 }, 92 93 isTimestampValidForBeginOrEnd: function(ts) { 94 if (!this.openPartialSlices_.length) 95 return true; 96 var top = this.openPartialSlices_[this.openPartialSlices_.length - 1]; 97 return ts >= top.start; 98 }, 99 100 /** 101 * @return {Number} The number of beginSlices for which an endSlice has not 102 * been issued. 103 */ 104 get openSliceCount() { 105 return this.openPartialSlices_.length; 106 }, 107 108 /** 109 * Ends the last begun slice in this group and pushes it onto the slice 110 * array. 111 * 112 * @param {Number} ts Timestamp when the slice ended. 113 * @return {Slice} slice. 114 */ 115 endSlice: function(ts) { 116 if (!this.openSliceCount) 117 throw new Error('endSlice called without an open slice'); 118 119 var slice = this.openPartialSlices_[this.openSliceCount - 1]; 120 this.openPartialSlices_.splice(this.openSliceCount - 1, 1); 121 if (ts < slice.start) 122 throw new Error('Slice ' + slice.title + 123 ' end time is before its start.'); 124 125 slice.duration = ts - slice.start; 126 this.pushSlice(slice); 127 128 return slice; 129 }, 130 131 /** 132 * Closes any open slices. 133 * @param {Number} opt_maxTimestamp The end time to use for the closed 134 * slices. If not provided, 135 * the max timestamp for this slice is provided. 136 */ 137 autoCloseOpenSlices: function(opt_maxTimestamp) { 138 if (!opt_maxTimestamp) { 139 this.updateBounds(); 140 opt_maxTimestamp = this.bounds.max; 141 } 142 while (this.openSliceCount > 0) { 143 var slice = this.endSlice(opt_maxTimestamp); 144 slice.didNotFinish = true; 145 } 146 }, 147 148 /** 149 * Shifts all the timestamps inside this group forward by the amount 150 * specified. 151 */ 152 shiftTimestampsForward: function(amount) { 153 for (var sI = 0; sI < this.slices.length; sI++) { 154 var slice = this.slices[sI]; 155 slice.start = (slice.start + amount); 156 } 157 for (var sI = 0; sI < this.openPartialSlices_.length; sI++) { 158 var slice = this.openPartialSlices_[i]; 159 slice.start = (slice.start + amount); 160 } 161 }, 162 163 /** 164 * Updates the bounds for this group based on the slices it contains. 165 */ 166 updateBounds: function() { 167 this.bounds.reset(); 168 for (var i = 0; i < this.slices.length; i++) { 169 this.bounds.addValue(this.slices[i].start); 170 this.bounds.addValue(this.slices[i].end); 171 } 172 173 if (this.openPartialSlices_.length) { 174 this.bounds.addValue(this.openPartialSlices_[0].start); 175 this.bounds.addValue( 176 this.openPartialSlices_[this.openPartialSlices_.length - 1].start); 177 } 178 }, 179 180 copySlice: function(slice) { 181 var newSlice = new this.sliceConstructor(slice.category, slice.title, 182 slice.colorId, slice.start, 183 slice.args, slice.duration); 184 newSlice.didNotFinish = slice.didNotFinish; 185 return newSlice; 186 } 187 }; 188 189 /** 190 * Merge two slice groups. 191 * 192 * If the two groups do not nest properly some of the slices of groupB will 193 * be split to accomodate the improper nesting. This is done to accomodate 194 * combined kernel and userland call stacks on Android. Because userland 195 * tracing is done by writing to the trace_marker file, the kernel calls 196 * that get invoked as part of that write may not be properly nested with 197 * the userland call trace. For example the following sequence may occur: 198 * 199 * kernel enter sys_write (the write to trace_marker) 200 * user enter some_function 201 * kernel exit sys_write 202 * ... 203 * kernel enter sys_write (the write to trace_marker) 204 * user exit some_function 205 * kernel exit sys_write 206 * 207 * This is handled by splitting the sys_write call into two slices as 208 * follows: 209 * 210 * | sys_write | some_function | sys_write (cont.) | 211 * | sys_write (cont.) | | sys_write | 212 * 213 * The colorId of both parts of the split slices are kept the same, and the 214 * " (cont.)" suffix is appended to the later parts of a split slice. 215 * 216 * The two input SliceGroups are not modified by this, and the merged 217 * SliceGroup will contain a copy of each of the input groups' slices (those 218 * copies may be split). 219 */ 220 SliceGroup.merge = function(groupA, groupB) { 221 // This is implemented by traversing the two slice groups in reverse 222 // order. The slices in each group are sorted by ascending end-time, so 223 // we must do the traversal from back to front in order to maintain the 224 // sorting. 225 // 226 // We traverse the two groups simultaneously, merging as we go. At each 227 // iteration we choose the group from which to take the next slice based 228 // on which group's next slice has the greater end-time. During this 229 // traversal we maintain a stack of currently "open" slices for each input 230 // group. A slice is considered "open" from the time it gets reached in 231 // our input group traversal to the time we reach an slice in this 232 // traversal with an end-time before the start time of the "open" slice. 233 // 234 // Each time a slice from groupA is opened or closed (events corresponding 235 // to the end-time and start-time of the input slice, respectively) we 236 // split all of the currently open slices from groupB. 237 238 if (groupA.openPartialSlices_.length > 0) { 239 throw new Error('groupA has open partial slices'); 240 } 241 if (groupB.openPartialSlices_.length > 0) { 242 throw new Error('groupB has open partial slices'); 243 } 244 245 var result = new SliceGroup(); 246 247 var slicesA = groupA.slices; 248 var slicesB = groupB.slices; 249 var idxA = slicesA.length - 1; 250 var idxB = slicesB.length - 1; 251 var openA = []; 252 var openB = []; 253 254 var splitOpenSlices = function(when) { 255 for (var i = 0; i < openB.length; i++) { 256 var oldSlice = openB[i]; 257 var oldEnd = oldSlice.end; 258 if (when < oldSlice.start || oldEnd < when) { 259 throw new Error('slice should not be split'); 260 } 261 262 var newSlice = result.copySlice(oldSlice); 263 oldSlice.start = when; 264 oldSlice.duration = oldEnd - when; 265 oldSlice.title += ' (cont.)'; 266 newSlice.duration = when - newSlice.start; 267 openB[i] = newSlice; 268 result.pushSlice(newSlice); 269 } 270 } 271 272 var closeOpenSlices = function(upTo) { 273 while (openA.length > 0 || openB.length > 0) { 274 var nextA = openA[openA.length - 1]; 275 var nextB = openB[openB.length - 1]; 276 var startA = nextA && nextA.start; 277 var startB = nextB && nextB.start; 278 279 if ((startA === undefined || startA < upTo) && 280 (startB === undefined || startB < upTo)) { 281 return; 282 } 283 284 if (startB === undefined || startA > startB) { 285 splitOpenSlices(startA); 286 openA.pop(); 287 } else { 288 openB.pop(); 289 } 290 } 291 } 292 293 while (idxA >= 0 || idxB >= 0) { 294 var sA = slicesA[idxA]; 295 var sB = slicesB[idxB]; 296 var nextSlice, isFromB; 297 298 if (sA === undefined || (sB !== undefined && sA.end < sB.end)) { 299 nextSlice = result.copySlice(sB); 300 isFromB = true; 301 idxB--; 302 } else { 303 nextSlice = result.copySlice(sA); 304 isFromB = false; 305 idxA--; 306 } 307 308 closeOpenSlices(nextSlice.end); 309 310 result.pushSlice(nextSlice); 311 312 if (isFromB) { 313 openB.push(nextSlice); 314 } else { 315 splitOpenSlices(nextSlice.end); 316 openA.push(nextSlice); 317 } 318 } 319 320 closeOpenSlices(); 321 322 result.slices.reverse(); 323 324 return result; 325 }; 326 327 return { 328 SliceGroup: SliceGroup 329 }; 330}); 331