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