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 base.require('base.sorted_array_utils'); 8 base.require('tracing.tracks.container_track'); 9 base.require('ui'); 10 11 base.exportTo('tracing.tracks', function() { 12 13 /** 14 * A track that displays a SliceGroup. 15 * @constructor 16 * @extends {ContainerTrack} 17 */ 18 19 var SliceGroupTrack = ui.define( 20 'slice-group-track', tracing.tracks.ContainerTrack); 21 22 SliceGroupTrack.prototype = { 23 24 __proto__: tracing.tracks.ContainerTrack.prototype, 25 26 decorate: function(viewport) { 27 tracing.tracks.ContainerTrack.prototype.decorate.call(this, viewport); 28 this.classList.add('slice-group-track'); 29 this.tooltip_ = ''; 30 this.heading_ = ''; 31 }, 32 33 get group() { 34 return this.group_; 35 }, 36 37 set group(g) { 38 this.group_ = g; 39 this.updateContents_(); 40 }, 41 42 get heading() { 43 return this.heading_; 44 }, 45 46 set heading(h) { 47 this.heading_ = h; 48 this.updateContents_(); 49 }, 50 51 get tooltip() { 52 return this.tooltip_; 53 }, 54 55 set tooltip(t) { 56 this.tooltip_ = t; 57 this.updateContents_(); 58 }, 59 60 set decorateHit(f) { 61 this.decorateHit_ = f; 62 this.updateContents_(); 63 }, 64 65 addSliceTrack_: function(slices) { 66 var track = new tracing.tracks.SliceTrack(this.viewport); 67 track.slices = slices; 68 track.decorateHit = this.decorateHit_; 69 track.categoryFilter_ = this.categoryFilter; 70 this.appendChild(track); 71 return track; 72 }, 73 74 get subRows() { 75 return base.asArray(this.children).map(function(sliceTrack) { 76 return sliceTrack.slices; 77 }); 78 }, 79 80 get hasVisibleContent() { 81 return this.children.length > 0; 82 }, 83 84 updateContents_: function() { 85 if (!this.group_) { 86 this.updateHeadingAndTooltip_(); 87 return; 88 } 89 90 var slices = tracing.filterSliceArray(this.categoryFilter, 91 this.group_.slices); 92 if (this.areArrayContentsSame_(this.filteredSlices_, slices)) { 93 this.updateHeadingAndTooltip_(); 94 return; 95 } 96 97 this.filteredSlices_ = slices; 98 99 this.detach(); 100 if (!slices.length) 101 return; 102 var subRows = this.buildSubRows_(slices); 103 for (var srI = 0; srI < subRows.length; srI++) { 104 var subRow = subRows[srI]; 105 if (!subRow.length) 106 continue; 107 this.addSliceTrack_(subRow); 108 } 109 this.updateHeadingAndTooltip_(); 110 }, 111 112 updateHeadingAndTooltip_: function() { 113 if (!this.firstChild) 114 return; 115 this.firstChild.heading = this.heading_; 116 this.firstChild.tooltip = this.tooltip_; 117 }, 118 119 /** 120 * Breaks up the list of slices into N rows, each of which is a list of 121 * slices that are non overlapping. 122 */ 123 buildSubRows_: function(slices) { 124 // This function works by walking through slices by start time. 125 // 126 // The basic idea here is to insert each slice as deep into the subrow 127 // list as it can go such that every subSlice is fully contained by its 128 // parent slice. 129 // 130 // Visually, if we start with this: 131 // 0: [ a ] 132 // 1: [ b ] 133 // 2: [c][d] 134 // 135 // To place this slice: 136 // [e] 137 // We first check row 2's last item, [d]. [e] wont fit into [d] (they dont 138 // even intersect). So we go to row 1. That gives us [b], and [d] wont fit 139 // into that either. So, we go to row 0 and its last slice, [a]. That can 140 // completely contain [e], so that means we should add [e] as a subchild 141 // of [a]. That puts it on row 1, yielding: 142 // 0: [ a ] 143 // 1: [ b ][e] 144 // 2: [c][d] 145 // 146 // If we then get this slice: 147 // [f] 148 // We do the same deepest-to-shallowest walk of the subrows trying to fit 149 // it. This time, it doesn't fit in any open slice. So, we simply append 150 // it to row 0: 151 // 0: [ a ] [f] 152 // 1: [ b ][e] 153 // 2: [c][d] 154 if (!slices.length) 155 return []; 156 157 var ops = []; 158 for (var i = 0; i < slices.length; i++) { 159 if (slices[i].subSlices) 160 slices[i].subSlices.splice(0, 161 slices[i].subSlices.length); 162 ops.push(i); 163 } 164 165 ops.sort(function(ix, iy) { 166 var x = slices[ix]; 167 var y = slices[iy]; 168 if (x.start != y.start) 169 return x.start - y.start; 170 171 // Elements get inserted into the slices array in order of when the 172 // slices end. Because slices must be properly nested, we break 173 // start-time ties by assuming that the elements appearing earlier in 174 // the slices array (and thus ending earlier) start later. 175 return iy - ix; 176 }); 177 178 var subRows = [[]]; 179 this.badSlices_ = []; // TODO(simonjam): Connect this again. 180 181 for (var i = 0; i < ops.length; i++) { 182 var op = ops[i]; 183 var slice = slices[op]; 184 185 // Try to fit the slice into the existing subrows. 186 var inserted = false; 187 for (var j = subRows.length - 1; j >= 0; j--) { 188 if (subRows[j].length == 0) 189 continue; 190 191 var insertedSlice = subRows[j][subRows[j].length - 1]; 192 if (slice.start < insertedSlice.start) { 193 this.badSlices_.push(slice); 194 inserted = true; 195 } 196 if (slice.start >= insertedSlice.start && 197 slice.end <= insertedSlice.end) { 198 // Insert it into subRow j + 1. 199 while (subRows.length <= j + 1) 200 subRows.push([]); 201 subRows[j + 1].push(slice); 202 if (insertedSlice.subSlices) 203 insertedSlice.subSlices.push(slice); 204 inserted = true; 205 break; 206 } 207 } 208 if (inserted) 209 continue; 210 211 // Append it to subRow[0] as a root. 212 subRows[0].push(slice); 213 } 214 215 return subRows; 216 }, 217 218 areArrayContentsSame_: function(a, b) { 219 if (!a || !b) 220 return false; 221 if (!a.length || !b.length) 222 return false; 223 if (a.length != b.length) 224 return false; 225 for (var i = 0; i < a.length; ++i) { 226 if (a[i] != b[i]) 227 return false; 228 } 229 return true; 230 } 231 }; 232 233 return { 234 SliceGroupTrack: SliceGroupTrack 235 }; 236 }); 237