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