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/ui/base/draw_helpers.html"> 9<link rel="import" href="/tracing/ui/base/ui.html"> 10<link rel="import" href="/tracing/ui/tracks/alert_track.html"> 11<link rel="import" href="/tracing/ui/tracks/container_track.html"> 12<link rel="import" href="/tracing/ui/tracks/device_track.html"> 13<link rel="import" href="/tracing/ui/tracks/global_memory_dump_track.html"> 14<link rel="import" href="/tracing/ui/tracks/highlighter.html"> 15<link rel="import" href="/tracing/ui/tracks/interaction_track.html"> 16<link rel="import" href="/tracing/ui/tracks/kernel_track.html"> 17<link rel="import" href="/tracing/ui/tracks/process_track.html"> 18 19<style> 20.model-track { 21 -webkit-box-flex: 1; 22} 23</style> 24 25<script> 26'use strict'; 27 28tr.exportTo('tr.ui.tracks', function() { 29 var SelectionState = tr.model.SelectionState; 30 var EventPresenter = tr.ui.b.EventPresenter; 31 32 /** 33 * Visualizes a Model by building ProcessTracks and CpuTracks. 34 * @constructor 35 */ 36 var ModelTrack = tr.ui.b.define('model-track', tr.ui.tracks.ContainerTrack); 37 38 39 ModelTrack.prototype = { 40 41 __proto__: tr.ui.tracks.ContainerTrack.prototype, 42 43 decorate: function(viewport) { 44 tr.ui.tracks.ContainerTrack.prototype.decorate.call(this, viewport); 45 this.classList.add('model-track'); 46 47 var typeInfos = tr.ui.tracks.Highlighter.getAllRegisteredTypeInfos(); 48 this.highlighters_ = typeInfos.map( 49 function(typeInfo) { 50 return new typeInfo.constructor(viewport); 51 }); 52 53 this.upperMode_ = false; 54 this.annotationViews_ = []; 55 }, 56 57 // upperMode is true if the track is being used on the ruler. 58 get upperMode() { 59 return this.upperMode_; 60 }, 61 62 set upperMode(upperMode) { 63 this.upperMode_ = upperMode; 64 this.updateContents_(); 65 }, 66 67 detach: function() { 68 tr.ui.tracks.ContainerTrack.prototype.detach.call(this); 69 }, 70 71 get model() { 72 return this.model_; 73 }, 74 75 set model(model) { 76 this.model_ = model; 77 this.updateContents_(); 78 79 this.model_.addEventListener('annotationChange', 80 this.updateAnnotations_.bind(this)); 81 }, 82 83 get hasVisibleContent() { 84 return this.children.length > 0; 85 }, 86 87 updateContents_: function() { 88 this.textContent = ''; 89 if (!this.model_) 90 return; 91 92 if (this.upperMode_) 93 this.updateContentsForUpperMode_(); 94 else 95 this.updateContentsForLowerMode_(); 96 }, 97 98 updateContentsForUpperMode_: function() { 99 }, 100 101 updateContentsForLowerMode_: function() { 102 if (this.model_.userModel.expectations.length) { 103 var mrt = new tr.ui.tracks.InteractionTrack(this.viewport_); 104 mrt.model = this.model_; 105 this.appendChild(mrt); 106 } 107 108 if (this.model_.alerts.length) { 109 var at = new tr.ui.tracks.AlertTrack(this.viewport_); 110 at.alerts = this.model_.alerts; 111 this.appendChild(at); 112 } 113 114 if (this.model_.globalMemoryDumps.length) { 115 var gmdt = new tr.ui.tracks.GlobalMemoryDumpTrack(this.viewport_); 116 gmdt.memoryDumps = this.model_.globalMemoryDumps; 117 this.appendChild(gmdt); 118 } 119 120 this.appendDeviceTrack_(); 121 this.appendKernelTrack_(); 122 123 // Get a sorted list of processes. 124 var processes = this.model_.getAllProcesses(); 125 processes.sort(tr.model.Process.compare); 126 127 for (var i = 0; i < processes.length; ++i) { 128 var process = processes[i]; 129 130 var track = new tr.ui.tracks.ProcessTrack(this.viewport); 131 track.process = process; 132 if (!track.hasVisibleContent) 133 continue; 134 135 this.appendChild(track); 136 } 137 this.viewport_.rebuildEventToTrackMap(); 138 this.viewport_.rebuildContainerToTrackMap(); 139 140 for (var i = 0; i < this.highlighters_.length; i++) { 141 this.highlighters_[i].processModel(this.model_); 142 } 143 144 this.updateAnnotations_(); 145 }, 146 147 updateAnnotations_: function() { 148 this.annotationViews_ = []; 149 var annotations = this.model_.getAllAnnotations(); 150 for (var i = 0; i < annotations.length; i++) { 151 this.annotationViews_.push( 152 annotations[i].getOrCreateView(this.viewport_)); 153 } 154 this.invalidateDrawingContainer(); 155 }, 156 157 addEventsToTrackMap: function(eventToTrackMap) { 158 if (!this.model_) 159 return; 160 161 var tracks = this.children; 162 for (var i = 0; i < tracks.length; ++i) 163 tracks[i].addEventsToTrackMap(eventToTrackMap); 164 165 if (this.instantEvents === undefined) 166 return; 167 168 var vp = this.viewport_; 169 this.instantEvents.forEach(function(ev) { 170 eventToTrackMap.addEvent(ev, this); 171 }.bind(this)); 172 }, 173 174 appendDeviceTrack_: function() { 175 var device = this.model.device; 176 var track = new tr.ui.tracks.DeviceTrack(this.viewport); 177 track.device = this.model.device; 178 if (!track.hasVisibleContent) 179 return; 180 this.appendChild(track); 181 }, 182 183 appendKernelTrack_: function() { 184 var kernel = this.model.kernel; 185 var track = new tr.ui.tracks.KernelTrack(this.viewport); 186 track.kernel = this.model.kernel; 187 if (!track.hasVisibleContent) 188 return; 189 this.appendChild(track); 190 }, 191 192 drawTrack: function(type) { 193 var ctx = this.context(); 194 if (!this.model_) 195 return; 196 197 var pixelRatio = window.devicePixelRatio || 1; 198 var bounds = this.getBoundingClientRect(); 199 var canvasBounds = ctx.canvas.getBoundingClientRect(); 200 201 ctx.save(); 202 ctx.translate(0, pixelRatio * (bounds.top - canvasBounds.top)); 203 204 var dt = this.viewport.currentDisplayTransform; 205 var viewLWorld = dt.xViewToWorld(0); 206 var viewRWorld = dt.xViewToWorld(bounds.width * pixelRatio); 207 208 switch (type) { 209 case tr.ui.tracks.DrawType.GRID: 210 this.viewport.drawMajorMarkLines(ctx); 211 // The model is the only thing that draws grid lines. 212 ctx.restore(); 213 return; 214 215 case tr.ui.tracks.DrawType.FLOW_ARROWS: 216 if (this.model_.flowIntervalTree.size === 0) { 217 ctx.restore(); 218 return; 219 } 220 221 this.drawFlowArrows_(viewLWorld, viewRWorld); 222 ctx.restore(); 223 return; 224 225 case tr.ui.tracks.DrawType.INSTANT_EVENT: 226 if (!this.model_.instantEvents || 227 this.model_.instantEvents.length === 0) 228 break; 229 230 tr.ui.b.drawInstantSlicesAsLines( 231 ctx, 232 this.viewport.currentDisplayTransform, 233 viewLWorld, 234 viewRWorld, 235 bounds.height, 236 this.model_.instantEvents, 237 4); 238 239 break; 240 241 case tr.ui.tracks.DrawType.MARKERS: 242 if (!this.viewport.interestRange.isEmpty) { 243 this.viewport.interestRange.draw(ctx, viewLWorld, viewRWorld); 244 this.viewport.interestRange.drawIndicators( 245 ctx, viewLWorld, viewRWorld); 246 } 247 ctx.restore(); 248 return; 249 250 case tr.ui.tracks.DrawType.HIGHLIGHTS: 251 for (var i = 0; i < this.highlighters_.length; i++) { 252 this.highlighters_[i].drawHighlight(ctx, dt, viewLWorld, viewRWorld, 253 bounds.height); 254 } 255 ctx.restore(); 256 return; 257 258 case tr.ui.tracks.DrawType.ANNOTATIONS: 259 for (var i = 0; i < this.annotationViews_.length; i++) { 260 this.annotationViews_[i].draw(ctx); 261 } 262 ctx.restore(); 263 return; 264 } 265 ctx.restore(); 266 267 tr.ui.tracks.ContainerTrack.prototype.drawTrack.call(this, type); 268 }, 269 270 drawFlowArrows_: function(viewLWorld, viewRWorld) { 271 var ctx = this.context(); 272 var dt = this.viewport.currentDisplayTransform; 273 dt.applyTransformToCanvas(ctx); 274 275 var pixWidth = dt.xViewVectorToWorld(1); 276 277 ctx.strokeStyle = 'rgba(0, 0, 0, 0.4)'; 278 ctx.fillStyle = 'rgba(0, 0, 0, 0.4)'; 279 ctx.lineWidth = pixWidth > 1.0 ? 1 : pixWidth; 280 281 var events = 282 this.model_.flowIntervalTree.findIntersection(viewLWorld, viewRWorld); 283 284 // When not showing flow events, show only highlighted/selected ones. 285 var onlyHighlighted = !this.viewport.showFlowEvents; 286 var canvasBounds = ctx.canvas.getBoundingClientRect(); 287 for (var i = 0; i < events.length; ++i) { 288 if (onlyHighlighted && 289 events[i].selectionState !== SelectionState.SELECTED && 290 events[i].selectionState !== SelectionState.HIGHLIGHTED) 291 continue; 292 this.drawFlowArrow_(ctx, events[i], canvasBounds, pixWidth); 293 } 294 }, 295 296 drawFlowArrow_: function(ctx, flowEvent, 297 canvasBounds, pixWidth) { 298 var pixelRatio = window.devicePixelRatio || 1; 299 300 var startTrack = this.viewport.trackForEvent(flowEvent.startSlice); 301 var endTrack = this.viewport.trackForEvent(flowEvent.endSlice); 302 303 // TODO(nduca): Figure out how to draw flow arrows even when 304 // processes are collapsed, bug #931. 305 if (startTrack === undefined || endTrack === undefined) 306 return; 307 308 var startBounds = startTrack.getBoundingClientRect(); 309 var endBounds = endTrack.getBoundingClientRect(); 310 311 if (flowEvent.selectionState == SelectionState.SELECTED) { 312 ctx.shadowBlur = 1; 313 ctx.shadowColor = 'red'; 314 ctx.shadowOffsety = 2; 315 ctx.strokeStyle = 'red'; 316 } else if (flowEvent.selectionState == SelectionState.HIGHLIGHTED) { 317 ctx.shadowBlur = 1; 318 ctx.shadowColor = 'red'; 319 ctx.shadowOffsety = 2; 320 ctx.strokeStyle = 'red'; 321 } else if (flowEvent.selectionState == SelectionState.DIMMED) { 322 ctx.shadowBlur = 0; 323 ctx.shadowOffsetX = 0; 324 ctx.strokeStyle = 'rgba(0, 0, 0, 0.2)'; 325 } else { 326 var hasBoost = false; 327 var startSlice = flowEvent.startSlice; 328 hasBoost |= startSlice.selectionState === SelectionState.SELECTED; 329 hasBoost |= startSlice.selectionState === SelectionState.HIGHLIGHTED; 330 var endSlice = flowEvent.endSlice; 331 hasBoost |= endSlice.selectionState === SelectionState.SELECTED; 332 hasBoost |= endSlice.selectionState === SelectionState.HIGHLIGHTED; 333 if (hasBoost) { 334 ctx.shadowBlur = 1; 335 ctx.shadowColor = 'rgba(255, 0, 0, 0.4)'; 336 ctx.shadowOffsety = 2; 337 ctx.strokeStyle = 'rgba(255, 0, 0, 0.4)'; 338 } else { 339 ctx.shadowBlur = 0; 340 ctx.shadowOffsetX = 0; 341 ctx.strokeStyle = 'rgba(0, 0, 0, 0.4)'; 342 } 343 } 344 345 var startSize = startBounds.left + startBounds.top + 346 startBounds.bottom + startBounds.right; 347 var endSize = endBounds.left + endBounds.top + 348 endBounds.bottom + endBounds.right; 349 // Nothing to do if both ends of the track are collapsed. 350 if (startSize === 0 && endSize === 0) 351 return; 352 353 var startY = this.calculateTrackY_(startTrack, canvasBounds); 354 var endY = this.calculateTrackY_(endTrack, canvasBounds); 355 356 var pixelStartY = pixelRatio * startY; 357 var pixelEndY = pixelRatio * endY; 358 var half = (flowEvent.end - flowEvent.start) / 2; 359 360 ctx.beginPath(); 361 ctx.moveTo(flowEvent.start, pixelStartY); 362 ctx.bezierCurveTo( 363 flowEvent.start + half, pixelStartY, 364 flowEvent.start + half, pixelEndY, 365 flowEvent.end, pixelEndY); 366 ctx.stroke(); 367 368 var arrowWidth = 5 * pixWidth * pixelRatio; 369 var distance = flowEvent.end - flowEvent.start; 370 if (distance <= (2 * arrowWidth)) 371 return; 372 373 var tipX = flowEvent.end; 374 var tipY = pixelEndY; 375 var arrowHeight = (endBounds.height / 4) * pixelRatio; 376 tr.ui.b.drawTriangle(ctx, 377 tipX, tipY, 378 tipX - arrowWidth, tipY - arrowHeight, 379 tipX - arrowWidth, tipY + arrowHeight); 380 ctx.fill(); 381 }, 382 383 calculateTrackY_: function(track, canvasBounds) { 384 var bounds = track.getBoundingClientRect(); 385 var size = bounds.left + bounds.top + bounds.bottom + bounds.right; 386 if (size === 0) 387 return this.calculateTrackY_(track.parentNode, canvasBounds); 388 389 return bounds.top - canvasBounds.top + (bounds.height / 2); 390 }, 391 392 addIntersectingEventsInRangeToSelectionInWorldSpace: function( 393 loWX, hiWX, viewPixWidthWorld, selection) { 394 function onPickHit(instantEvent) { 395 selection.push(instantEvent); 396 } 397 var instantEventWidth = 3 * viewPixWidthWorld; 398 tr.b.iterateOverIntersectingIntervals(this.model_.instantEvents, 399 function(x) { return x.start; }, 400 function(x) { return x.duration + instantEventWidth; }, 401 loWX, hiWX, 402 onPickHit.bind(this)); 403 404 tr.ui.tracks.ContainerTrack.prototype. 405 addIntersectingEventsInRangeToSelectionInWorldSpace. 406 apply(this, arguments); 407 }, 408 409 addClosestEventToSelection: function(worldX, worldMaxDist, loY, hiY, 410 selection) { 411 this.addClosestInstantEventToSelection(this.model_.instantEvents, 412 worldX, worldMaxDist, selection); 413 tr.ui.tracks.ContainerTrack.prototype.addClosestEventToSelection. 414 apply(this, arguments); 415 } 416 }; 417 418 return { 419 ModelTrack: ModelTrack 420 }; 421}); 422</script> 423