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