1<!DOCTYPE html> 2<!-- 3Copyright (c) 2013 The Chromium Authors. All rights reserved. 4Use of this source code is governed by a BSD-style license that can be 5found in the LICENSE file. 6--> 7 8<link rel="import" href="/tracing/base/color_scheme.html"> 9<link rel="import" href="/tracing/base/guid.html"> 10<link rel="import" href="/tracing/base/sorted_array_utils.html"> 11<link rel="import" href="/tracing/core/filter.html"> 12<link rel="import" href="/tracing/model/event_container.html"> 13 14<script> 15'use strict'; 16 17/** 18 * @fileoverview Provides the SliceGroup class. 19 */ 20tr.exportTo('tr.model', function() { 21 var ColorScheme = tr.b.ColorScheme; 22 var Slice = tr.model.Slice; 23 24 function getSliceLo(s) { 25 return s.start; 26 } 27 28 function getSliceHi(s) { 29 return s.end; 30 } 31 32 /** 33 * A group of Slices, plus code to create them from B/E events, as 34 * well as arrange them into subRows. 35 * 36 * Do not mutate the slices array directly. Modify it only by 37 * SliceGroup mutation methods. 38 * 39 * @constructor 40 * @param {function(new:Slice, category, title, colorId, start, args)=} 41 * opt_sliceConstructor The constructor to use when creating slices. 42 * @extends {tr.model.EventContainer} 43 */ 44 function SliceGroup(parentContainer, opt_sliceConstructor, opt_name) { 45 tr.model.EventContainer.call(this); 46 47 this.parentContainer_ = parentContainer; 48 49 var sliceConstructor = opt_sliceConstructor || Slice; 50 this.sliceConstructor = sliceConstructor; 51 52 this.openPartialSlices_ = []; 53 54 this.slices = []; 55 this.topLevelSlices = []; 56 this.haveTopLevelSlicesBeenBuilt = false; 57 this.name_ = opt_name; 58 59 if (this.model === undefined) 60 throw new Error('SliceGroup must have model defined.'); 61 } 62 63 SliceGroup.prototype = { 64 __proto__: tr.model.EventContainer.prototype, 65 66 get parentContainer() { 67 return this.parentContainer_; 68 }, 69 70 get model() { 71 return this.parentContainer_.model; 72 }, 73 74 get stableId() { 75 return this.parentContainer_.stableId + '.SliceGroup'; 76 }, 77 78 getSettingsKey: function() { 79 if (!this.name_) 80 return undefined; 81 var parentKey = this.parentContainer_.getSettingsKey(); 82 if (!parentKey) 83 return undefined; 84 return parentKey + '.' + this.name; 85 }, 86 87 /** 88 * @return {Number} The number of slices in this group. 89 */ 90 get length() { 91 return this.slices.length; 92 }, 93 94 /** 95 * Helper function that pushes the provided slice onto the slices array. 96 * @param {Slice} slice The slice to be added to the slices array. 97 */ 98 pushSlice: function(slice) { 99 this.haveTopLevelSlicesBeenBuilt = false; 100 slice.parentContainer = this.parentContainer_; 101 this.slices.push(slice); 102 return slice; 103 }, 104 105 /** 106 * Helper function that pushes the provided slices onto the slices array. 107 * @param {Array.<Slice>} slices An array of slices to be added. 108 */ 109 pushSlices: function(slices) { 110 this.haveTopLevelSlicesBeenBuilt = false; 111 slices.forEach(function(slice) { 112 slice.parentContainer = this.parentContainer_; 113 this.slices.push(slice); 114 }, this); 115 }, 116 117 /** 118 * Opens a new slice in the group's slices. 119 * 120 * Calls to beginSlice and 121 * endSlice must be made with non-monotonically-decreasing timestamps. 122 * 123 * @param {String} category Category name of the slice to add. 124 * @param {String} title Title of the slice to add. 125 * @param {Number} ts The timetsamp of the slice, in milliseconds. 126 * @param {Object.<string, Object>=} opt_args Arguments associated with 127 * the slice. 128 * @param {Number=} opt_colorId The color of the slice, defined by 129 * its palette id (see base/color_scheme.html). 130 */ 131 beginSlice: function(category, title, ts, opt_args, opt_tts, 132 opt_argsStripped, opt_colorId) { 133 if (this.openPartialSlices_.length) { 134 var prevSlice = this.openPartialSlices_[ 135 this.openPartialSlices_.length - 1]; 136 if (ts < prevSlice.start) 137 throw new Error('Slices must be added in increasing timestamp order'); 138 } 139 140 var colorId = opt_colorId || 141 ColorScheme.getColorIdForGeneralPurposeString(title); 142 var slice = new this.sliceConstructor(category, title, colorId, ts, 143 opt_args ? opt_args : {}, null, 144 opt_tts, undefined, 145 opt_argsStripped); 146 this.openPartialSlices_.push(slice); 147 slice.didNotFinish = true; 148 this.pushSlice(slice); 149 150 return slice; 151 }, 152 153 isTimestampValidForBeginOrEnd: function(ts) { 154 if (!this.openPartialSlices_.length) 155 return true; 156 var top = this.openPartialSlices_[this.openPartialSlices_.length - 1]; 157 return ts >= top.start; 158 }, 159 160 /** 161 * @return {Number} The number of beginSlices for which an endSlice has not 162 * been issued. 163 */ 164 get openSliceCount() { 165 return this.openPartialSlices_.length; 166 }, 167 168 get mostRecentlyOpenedPartialSlice() { 169 if (!this.openPartialSlices_.length) 170 return undefined; 171 return this.openPartialSlices_[this.openPartialSlices_.length - 1]; 172 }, 173 174 /** 175 * Ends the last begun slice in this group and pushes it onto the slice 176 * array. 177 * 178 * @param {Number} ts Timestamp when the slice ended 179 * @param {Number=} opt_colorId The color of the slice, defined by 180 * its palette id (see base/color_scheme.html). 181 * @return {Slice} slice. 182 */ 183 endSlice: function(ts, opt_tts, opt_colorId) { 184 if (!this.openSliceCount) 185 throw new Error('endSlice called without an open slice'); 186 187 var slice = this.openPartialSlices_[this.openSliceCount - 1]; 188 this.openPartialSlices_.splice(this.openSliceCount - 1, 1); 189 if (ts < slice.start) 190 throw new Error('Slice ' + slice.title + 191 ' end time is before its start.'); 192 193 slice.duration = ts - slice.start; 194 slice.didNotFinish = false; 195 slice.colorId = opt_colorId || slice.colorId; 196 197 if (opt_tts && slice.cpuStart !== undefined) 198 slice.cpuDuration = opt_tts - slice.cpuStart; 199 200 return slice; 201 }, 202 203 /** 204 * Push a complete event as a Slice into the slice list. 205 * The timestamp can be in any order. 206 * 207 * @param {String} category Category name of the slice to add. 208 * @param {String} title Title of the slice to add. 209 * @param {Number} ts The timetsamp of the slice, in milliseconds. 210 * @param {Number} duration The duration of the slice, in milliseconds. 211 * @param {Object.<string, Object>=} opt_args Arguments associated with 212 * the slice. 213 * @param {Number=} opt_colorId The color of the slice, as defined by 214 * its palette id (see base/color_scheme.html). 215 */ 216 pushCompleteSlice: function(category, title, ts, duration, tts, 217 cpuDuration, opt_args, opt_argsStripped, 218 opt_colorId, opt_bind_id) { 219 var colorId = opt_colorId || 220 ColorScheme.getColorIdForGeneralPurposeString(title); 221 var slice = new this.sliceConstructor(category, title, colorId, ts, 222 opt_args ? opt_args : {}, 223 duration, tts, cpuDuration, 224 opt_argsStripped, opt_bind_id); 225 if (duration === undefined) 226 slice.didNotFinish = true; 227 this.pushSlice(slice); 228 return slice; 229 }, 230 231 /** 232 * Closes any open slices. 233 * @param {Number=} opt_maxTimestamp The end time to use for the closed 234 * slices. If not provided, 235 * the max timestamp for this slice is provided. 236 */ 237 autoCloseOpenSlices: function() { 238 this.updateBounds(); 239 var maxTimestamp = this.bounds.max; 240 for (var sI = 0; sI < this.slices.length; sI++) { 241 var slice = this.slices[sI]; 242 if (slice.didNotFinish) 243 slice.duration = maxTimestamp - slice.start; 244 } 245 this.openPartialSlices_ = []; 246 }, 247 248 /** 249 * Shifts all the timestamps inside this group forward by the amount 250 * specified. 251 */ 252 shiftTimestampsForward: function(amount) { 253 for (var sI = 0; sI < this.slices.length; sI++) { 254 var slice = this.slices[sI]; 255 slice.start = (slice.start + amount); 256 } 257 }, 258 259 /** 260 * Updates the bounds for this group based on the slices it contains. 261 */ 262 updateBounds: function() { 263 this.bounds.reset(); 264 for (var i = 0; i < this.slices.length; i++) { 265 this.bounds.addValue(this.slices[i].start); 266 this.bounds.addValue(this.slices[i].end); 267 } 268 }, 269 270 copySlice: function(slice) { 271 var newSlice = new this.sliceConstructor(slice.category, slice.title, 272 slice.colorId, slice.start, 273 slice.args, slice.duration, slice.cpuStart, slice.cpuDuration); 274 newSlice.didNotFinish = slice.didNotFinish; 275 return newSlice; 276 }, 277 278 iterateAllEventsInThisContainer: function(eventTypePredicate, 279 callback, opt_this) { 280 if (eventTypePredicate.call(opt_this, this.sliceConstructor)) 281 this.slices.forEach(callback, opt_this); 282 }, 283 284 iterateAllChildEventContainers: function(callback, opt_this) { 285 }, 286 287 getSlicesOfName: function(title) { 288 var slices = []; 289 for (var i = 0; i < this.slices.length; i++) { 290 if (this.slices[i].title == title) { 291 slices.push(this.slices[i]); 292 } 293 } 294 return slices; 295 }, 296 297 iterSlicesInTimeRange: function(callback, start, end) { 298 var ret = []; 299 tr.b.iterateOverIntersectingIntervals( 300 this.topLevelSlices, 301 function(s) { return s.start; }, 302 function(s) { return s.duration; }, 303 start, 304 end, 305 function(topLevelSlice) { 306 callback(topLevelSlice); 307 topLevelSlice.iterateAllDescendents(callback); 308 }); 309 return ret; 310 }, 311 312 findFirstSlice: function() { 313 if (!this.haveTopLevelSlicesBeenBuilt) 314 throw new Error('Nope'); 315 if (0 === this.slices.length) 316 return undefined; 317 return this.slices[0]; 318 }, 319 320 findSliceAtTs: function(ts) { 321 if (!this.haveTopLevelSlicesBeenBuilt) 322 throw new Error('Nope'); 323 var i = tr.b.findIndexInSortedClosedIntervals( 324 this.topLevelSlices, 325 getSliceLo, getSliceHi, 326 ts); 327 if (i == -1 || i == this.topLevelSlices.length) 328 return undefined; 329 330 var curSlice = this.topLevelSlices[i]; 331 332 // Now recurse on slice looking for subSlice of given ts. 333 while (true) { 334 var i = tr.b.findIndexInSortedClosedIntervals( 335 curSlice.subSlices, 336 getSliceLo, getSliceHi, 337 ts); 338 if (i == -1 || i == curSlice.subSlices.length) 339 return curSlice; 340 curSlice = curSlice.subSlices[i]; 341 } 342 }, 343 344 findNextSliceAfter: function(ts, refGuid) { 345 var i = tr.b.findLowIndexInSortedArray( 346 this.slices, getSliceLo, ts); 347 if (i === this.slices.length) 348 return undefined; 349 for (; i < this.slices.length; i++) { 350 var slice = this.slices[i]; 351 if (slice.start > ts) 352 return slice; 353 if (slice.guid <= refGuid) 354 continue; 355 return slice; 356 } 357 return undefined; 358 }, 359 360 /** 361 * Construct subSlices for this group. 362 * Populate the group topLevelSlices, parent slices get a subSlices[], 363 * a selfThreadTime and a selfTime, child slices get a parentSlice 364 * reference. 365 */ 366 createSubSlices: function() { 367 this.haveTopLevelSlicesBeenBuilt = true; 368 this.createSubSlicesImpl_(); 369 if (this.parentContainer.timeSlices) 370 this.addCpuTimeToSubslices_(this.parentContainer.timeSlices); 371 this.slices.forEach(function(slice) { 372 var selfTime = slice.duration; 373 for (var i = 0; i < slice.subSlices.length; i++) 374 selfTime -= slice.subSlices[i].duration; 375 slice.selfTime = selfTime; 376 377 if (slice.cpuDuration === undefined) 378 return; 379 380 var cpuSelfTime = slice.cpuDuration; 381 for (var i = 0; i < slice.subSlices.length; i++) { 382 if (slice.subSlices[i].cpuDuration !== undefined) 383 cpuSelfTime -= slice.subSlices[i].cpuDuration; 384 } 385 slice.cpuSelfTime = cpuSelfTime; 386 }); 387 }, 388 createSubSlicesImpl_: function() { 389 var precisionUnit = this.model.intrinsicTimeUnit; 390 391 function addSliceIfBounds(root, child) { 392 // Because we know that the start time of child is >= the start time 393 // of all other slices seen so far, we can just check the last slice 394 // of each row for bounding. 395 if (root.bounds(child, precisionUnit)) { 396 if (root.subSlices && root.subSlices.length > 0) { 397 if (addSliceIfBounds(root.subSlices[root.subSlices.length - 1], 398 child)) 399 return true; 400 } 401 child.parentSlice = root; 402 if (root.subSlices === undefined) 403 root.subSlices = []; 404 root.subSlices.push(child); 405 return true; 406 } 407 return false; 408 } 409 410 if (!this.slices.length) 411 return; 412 413 var ops = []; 414 for (var i = 0; i < this.slices.length; i++) { 415 if (this.slices[i].subSlices) 416 this.slices[i].subSlices.splice(0, 417 this.slices[i].subSlices.length); 418 ops.push(i); 419 } 420 421 var originalSlices = this.slices; 422 ops.sort(function(ix, iy) { 423 var x = originalSlices[ix]; 424 var y = originalSlices[iy]; 425 if (x.start != y.start) 426 return x.start - y.start; 427 428 // Elements get inserted into the slices array in order of when the 429 // slices start. Because slices must be properly nested, we break 430 // start-time ties by assuming that the elements appearing earlier 431 // in the slices array (and thus ending earlier) start earlier. 432 return ix - iy; 433 }); 434 435 var slices = new Array(this.slices.length); 436 for (var i = 0; i < ops.length; i++) { 437 slices[i] = originalSlices[ops[i]]; 438 } 439 440 // Actually build the subrows. 441 var rootSlice = slices[0]; 442 this.topLevelSlices = []; 443 this.topLevelSlices.push(rootSlice); 444 rootSlice.isTopLevel = true; 445 for (var i = 1; i < slices.length; i++) { 446 var slice = slices[i]; 447 if (!addSliceIfBounds(rootSlice, slice)) { 448 rootSlice = slice; 449 rootSlice.isTopLevel = true; 450 this.topLevelSlices.push(rootSlice); 451 } 452 } 453 454 // Keep the slices in sorted form. 455 this.slices = slices; 456 }, 457 addCpuTimeToSubslices_: function(timeSlices) { 458 var SCHEDULING_STATE = tr.model.SCHEDULING_STATE; 459 var sliceIdx = 0; 460 timeSlices.forEach(function(timeSlice) { 461 if (timeSlice.schedulingState == SCHEDULING_STATE.RUNNING) { 462 while (sliceIdx < this.topLevelSlices.length) { 463 if (this.addCpuTimeToSubslice_(this.topLevelSlices[sliceIdx], 464 timeSlice)) { 465 // The current top-level slice and children are fully 466 // accounted for, proceed to next top-level slice. 467 sliceIdx++; 468 } else { 469 // The current top-level runs beyond the time slice, break out 470 // so we can potentially add more time slices to it 471 break; 472 } 473 } 474 } 475 }, this); 476 }, 477 /* Add run-time of this timeSlice to the passed in slice 478 * and all of it's children (recursively). 479 * Returns whether the slice ends before or at the end of the 480 * time slice, signaling we are done with this slice. 481 */ 482 addCpuTimeToSubslice_: function(slice, timeSlice) { 483 // Make sure they overlap 484 if (slice.start > timeSlice.end || slice.end < timeSlice.start) 485 return slice.end <= timeSlice.end; 486 487 // Compute actual overlap 488 var duration = timeSlice.duration; 489 if (slice.start > timeSlice.start) 490 duration -= slice.start - timeSlice.start; 491 if (timeSlice.end > slice.end) 492 duration -= timeSlice.end - slice.end; 493 494 if (slice.cpuDuration) { 495 slice.cpuDuration += duration; 496 } else { 497 slice.cpuDuration = duration; 498 } 499 500 for (var i = 0; i < slice.subSlices.length; i++) { 501 this.addCpuTimeToSubslice_(slice.subSlices[i], timeSlice); 502 } 503 504 return slice.end <= timeSlice.end; 505 } 506 }; 507 508 /** 509 * Merge two slice groups. 510 * 511 * If the two groups do not nest properly some of the slices of groupB will 512 * be split to accomodate the improper nesting. This is done to accomodate 513 * combined kernel and userland call stacks on Android. Because userland 514 * tracing is done by writing to the trace_marker file, the kernel calls 515 * that get invoked as part of that write may not be properly nested with 516 * the userland call trace. For example the following sequence may occur: 517 * 518 * kernel enter sys_write (the write to trace_marker) 519 * user enter some_function 520 * kernel exit sys_write 521 * ... 522 * kernel enter sys_write (the write to trace_marker) 523 * user exit some_function 524 * kernel exit sys_write 525 * 526 * This is handled by splitting the sys_write call into two slices as 527 * follows: 528 * 529 * | sys_write | some_function | sys_write (cont.) | 530 * | sys_write (cont.) | | sys_write | 531 * 532 * The colorId of both parts of the split slices are kept the same, and the 533 * " (cont.)" suffix is appended to the later parts of a split slice. 534 * 535 * The two input SliceGroups are not modified by this, and the merged 536 * SliceGroup will contain a copy of each of the input groups' slices (those 537 * copies may be split). 538 */ 539 SliceGroup.merge = function(groupA, groupB) { 540 // This is implemented by traversing the two slice groups in reverse 541 // order. The slices in each group are sorted by ascending end-time, so 542 // we must do the traversal from back to front in order to maintain the 543 // sorting. 544 // 545 // We traverse the two groups simultaneously, merging as we go. At each 546 // iteration we choose the group from which to take the next slice based 547 // on which group's next slice has the greater end-time. During this 548 // traversal we maintain a stack of currently "open" slices for each input 549 // group. A slice is considered "open" from the time it gets reached in 550 // our input group traversal to the time we reach an slice in this 551 // traversal with an end-time before the start time of the "open" slice. 552 // 553 // Each time a slice from groupA is opened or closed (events corresponding 554 // to the end-time and start-time of the input slice, respectively) we 555 // split all of the currently open slices from groupB. 556 557 if (groupA.openPartialSlices_.length > 0) 558 throw new Error('groupA has open partial slices'); 559 560 if (groupB.openPartialSlices_.length > 0) 561 throw new Error('groupB has open partial slices'); 562 563 if (groupA.parentContainer != groupB.parentContainer) 564 throw new Error('Different parent threads. Cannot merge'); 565 566 if (groupA.sliceConstructor != groupB.sliceConstructor) 567 throw new Error('Different slice constructors. Cannot merge'); 568 569 var result = new SliceGroup(groupA.parentContainer, 570 groupA.sliceConstructor, 571 groupA.name_); 572 573 var slicesA = groupA.slices; 574 var slicesB = groupB.slices; 575 var idxA = 0; 576 var idxB = 0; 577 var openA = []; 578 var openB = []; 579 580 var splitOpenSlices = function(when) { 581 for (var i = 0; i < openB.length; i++) { 582 var oldSlice = openB[i]; 583 var oldEnd = oldSlice.end; 584 if (when < oldSlice.start || oldEnd < when) { 585 throw new Error('slice should not be split'); 586 } 587 588 var newSlice = result.copySlice(oldSlice); 589 newSlice.start = when; 590 newSlice.duration = oldEnd - when; 591 if (newSlice.title.indexOf(' (cont.)') == -1) 592 newSlice.title += ' (cont.)'; 593 oldSlice.duration = when - oldSlice.start; 594 openB[i] = newSlice; 595 result.pushSlice(newSlice); 596 } 597 }; 598 599 var closeOpenSlices = function(upTo) { 600 while (openA.length > 0 || openB.length > 0) { 601 var nextA = openA[openA.length - 1]; 602 var nextB = openB[openB.length - 1]; 603 var endA = nextA && nextA.end; 604 var endB = nextB && nextB.end; 605 606 if ((endA === undefined || endA > upTo) && 607 (endB === undefined || endB > upTo)) { 608 return; 609 } 610 611 if (endB === undefined || endA < endB) { 612 splitOpenSlices(endA); 613 openA.pop(); 614 } else { 615 openB.pop(); 616 } 617 } 618 }; 619 620 while (idxA < slicesA.length || idxB < slicesB.length) { 621 var sA = slicesA[idxA]; 622 var sB = slicesB[idxB]; 623 var nextSlice, isFromB; 624 625 if (sA === undefined || (sB !== undefined && sA.start > sB.start)) { 626 nextSlice = result.copySlice(sB); 627 isFromB = true; 628 idxB++; 629 } else { 630 nextSlice = result.copySlice(sA); 631 isFromB = false; 632 idxA++; 633 } 634 635 closeOpenSlices(nextSlice.start); 636 637 result.pushSlice(nextSlice); 638 639 if (isFromB) { 640 openB.push(nextSlice); 641 } else { 642 splitOpenSlices(nextSlice.start); 643 openA.push(nextSlice); 644 } 645 } 646 647 closeOpenSlices(); 648 649 return result; 650 }; 651 652 return { 653 SliceGroup: SliceGroup 654 }; 655}); 656</script> 657