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