1<!DOCTYPE html> 2<!-- 3Copyright (c) 2015 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/range.html"> 9<link rel="import" href="/tracing/base/task.html"> 10<link rel="import" href="/tracing/model/event_set.html"> 11<link rel="import" href="/tracing/ui/analysis/analysis_link.html"> 12<link rel="import" href="/tracing/ui/analysis/flow_classifier.html"> 13<link rel="import" href="/tracing/ui/base/dom_helpers.html"> 14<link rel="import" href="/tracing/ui/base/table.html"> 15 16<polymer-element name="tr-ui-a-related-events"> 17 <template> 18 <style> 19 :host { 20 display: flex; 21 flex-direction: column; 22 } 23 #table { 24 flex: 1 1 auto; 25 align-self: stretch; 26 } 27 </style> 28 <tr-ui-b-table id="table"></tr-ui-b-table> 29 </template> 30 31 <script> 32 'use strict'; 33 34 Polymer({ 35 ready: function() { 36 this.eventGroups_ = []; 37 this.cancelFunctions_ = []; 38 39 this.$.table.tableColumns = [ 40 { 41 title: 'Event(s)', 42 value: function(row) { 43 var typeEl = document.createElement('span'); 44 typeEl.innerText = row.type; 45 if (row.tooltip) 46 typeEl.title = row.tooltip; 47 return typeEl; 48 }, 49 width: '150px' 50 }, 51 { 52 title: 'Link', 53 width: '100%', 54 value: function(row) { 55 var linkEl = document.createElement('tr-ui-a-analysis-link'); 56 if (row.name) 57 linkEl.setSelectionAndContent(row.selection, row.name); 58 else 59 linkEl.selection = row.selection; 60 return linkEl; 61 } 62 } 63 ]; 64 }, 65 66 hasRelatedEvents: function() { 67 return (this.eventGroups_ && this.eventGroups_.length > 0); 68 }, 69 70 setRelatedEvents: function(eventSet) { 71 this.cancelAllTasks_(); 72 this.eventGroups_ = []; 73 this.addConnectedFlows_(eventSet); 74 this.addConnectedEvents_(eventSet); 75 this.addOverlappingSamples_(eventSet); 76 this.updateContents_(); 77 }, 78 79 addConnectedFlows_: function(eventSet) { 80 var classifier = new tr.ui.analysis.FlowClassifier(); 81 eventSet.forEach(function(slice) { 82 if (slice.inFlowEvents) { 83 slice.inFlowEvents.forEach(function(flow) { 84 classifier.addInFlow(flow); 85 }); 86 } 87 if (slice.outFlowEvents) { 88 slice.outFlowEvents.forEach(function(flow) { 89 classifier.addOutFlow(flow); 90 }); 91 } 92 }); 93 if (!classifier.hasEvents()) 94 return; 95 96 var addToEventGroups = function(type, flowEvent) { 97 this.eventGroups_.push({ 98 type: type, 99 selection: new tr.model.EventSet(flowEvent), 100 name: flowEvent.title 101 }); 102 }; 103 104 classifier.inFlowEvents.forEach( 105 addToEventGroups.bind(this, 'Incoming flow')); 106 classifier.outFlowEvents.forEach( 107 addToEventGroups.bind(this, 'Outgoing flow')); 108 classifier.internalFlowEvents.forEach( 109 addToEventGroups.bind(this, 'Internal flow')); 110 }, 111 112 cancelAllTasks_: function() { 113 this.cancelFunctions_.forEach(function(cancelFunction) { 114 cancelFunction(); 115 }); 116 this.cancelFunctions_ = []; 117 }, 118 119 addConnectedEvents_: function(eventSet) { 120 this.cancelFunctions_.push(this.createEventsLinkIfNeeded_( 121 'Preceding events', 122 'Add all events that have led to the selected one(s), connected by ' + 123 'flow arrows or by call stack.', 124 eventSet, 125 function(event, events) { 126 this.addInFlowEvents_(event, events); 127 this.addAncestors_(event, events); 128 if (event.startSlice) 129 events.push(event.startSlice); 130 }.bind(this))); 131 this.cancelFunctions_.push(this.createEventsLinkIfNeeded_( 132 'Following events', 133 'Add all events that have been caused by the selected one(s), ' + 134 'connected by flow arrows or by call stack.', 135 eventSet, 136 function(event, events) { 137 this.addOutFlowEvents_(event, events); 138 this.addDescendents_(event, events); 139 if (event.endSlice) 140 events.push(event.endSlice); 141 }.bind(this))); 142 this.cancelFunctions_.push(this.createEventsLinkIfNeeded_( 143 'All connected events', 144 'Add all events connected to the selected one(s) by flow arrows or ' + 145 'by call stack.', 146 eventSet, 147 function(event, events) { 148 this.addInFlowEvents_(event, events); 149 this.addOutFlowEvents_(event, events); 150 this.addAncestors_(event, events); 151 this.addDescendents_(event, events); 152 if (event.startSlice) 153 events.push(event.startSlice); 154 if (event.endSlice) 155 events.push(event.endSlice); 156 }.bind(this))); 157 }, 158 159 createEventsLinkIfNeeded_: function(title, tooltip, events, addFunction) { 160 events = new tr.model.EventSet(events); 161 var lengthBefore = events.length; 162 var task; 163 var isCanceled = false; 164 function addEventsUntilTimeout(startingIndex) { 165 if (isCanceled) 166 return; 167 var startingTime = window.performance.now(); 168 while (startingIndex < events.length) { 169 addFunction(events[startingIndex], events); 170 startingIndex++; 171 // Let's grant ourselves a budget of 8ms. 172 if (window.performance.now() - startingTime > 8) { 173 var newTask = new tr.b.Task( 174 addEventsUntilTimeout.bind(this, startingIndex), this); 175 task.after(newTask); 176 task = newTask; 177 return; 178 } 179 } 180 // Went through all events, add the link. 181 if (lengthBefore === events.length) 182 return; 183 this.eventGroups_.push({ 184 type: title, 185 tooltip: tooltip, 186 selection: events 187 }); 188 this.updateContents_(); 189 }; 190 function cancelTask() { 191 isCanceled = true; 192 } 193 task = new tr.b.Task(addEventsUntilTimeout.bind(this, 0), this); 194 tr.b.Task.RunWhenIdle(task); 195 return cancelTask; 196 }, 197 198 addInFlowEvents_: function(event, eventSet) { 199 if (!event.inFlowEvents) 200 return; 201 event.inFlowEvents.forEach(function(e) { 202 eventSet.push(e); 203 }); 204 }, 205 206 addOutFlowEvents_: function(event, eventSet) { 207 if (!event.outFlowEvents) 208 return; 209 event.outFlowEvents.forEach(function(e) { 210 eventSet.push(e); 211 }); 212 }, 213 214 addAncestors_: function(event, eventSet) { 215 if (!event.iterateAllAncestors) 216 return; 217 event.iterateAllAncestors(function(e) { 218 eventSet.push(e); 219 }); 220 }, 221 222 addDescendents_: function(event, eventSet) { 223 if (!event.iterateAllDescendents) 224 return; 225 event.iterateAllDescendents(function(e) { 226 eventSet.push(e); 227 }); 228 }, 229 230 addOverlappingSamples_: function(eventSet) { 231 var samples = new tr.model.EventSet; 232 eventSet.forEach(function(slice) { 233 if (!slice.parentContainer || !slice.parentContainer.samples) 234 return; 235 var candidates = slice.parentContainer.samples; 236 var range = tr.b.Range.fromExplicitRange( 237 slice.start, slice.start + slice.duration); 238 var filteredSamples = range.filterArray( 239 candidates, function(value) {return value.start;}); 240 filteredSamples.forEach(function(sample) { 241 samples.push(sample); 242 }); 243 }.bind(this)); 244 if (samples.length > 0) { 245 this.eventGroups_.push({ 246 type: 'Overlapping samples', 247 tooltip: 'All samples overlapping the selected slice(s).', 248 selection: samples 249 }); 250 } 251 }, 252 253 updateContents_: function() { 254 var table = this.$.table; 255 if (this.eventGroups_ === undefined) 256 table.tableRows = []; 257 else 258 table.tableRows = this.eventGroups_.slice(); 259 table.rebuild(); 260 } 261 }); 262 </script> 263</polymer-element> 264