| <!DOCTYPE html> |
| <!-- |
| Copyright (c) 2015 The Chromium Authors. All rights reserved. |
| Use of this source code is governed by a BSD-style license that can be |
| found in the LICENSE file. |
| --> |
| |
| <link rel="import" href="/tracing/base/range.html"> |
| <link rel="import" href="/tracing/base/task.html"> |
| <link rel="import" href="/tracing/model/event_set.html"> |
| <link rel="import" href="/tracing/ui/analysis/analysis_link.html"> |
| <link rel="import" href="/tracing/ui/analysis/flow_classifier.html"> |
| <link rel="import" href="/tracing/ui/base/dom_helpers.html"> |
| <link rel="import" href="/tracing/ui/base/table.html"> |
| |
| <dom-module id='tr-ui-a-related-events'> |
| <template> |
| <style> |
| :host { |
| display: flex; |
| flex-direction: column; |
| } |
| #table { |
| flex: 1 1 auto; |
| align-self: stretch; |
| } |
| </style> |
| <tr-ui-b-table id="table"></tr-ui-b-table> |
| </template> |
| </dom-module> |
| <script> |
| 'use strict'; |
| |
| function* getEventInFlowEvents(event) { |
| if (!event.inFlowEvents) |
| return; |
| yield * event.inFlowEvents; |
| } |
| |
| function* getEventOutFlowEvents(event) { |
| if (!event.outFlowEvents) |
| return; |
| yield * event.outFlowEvents; |
| } |
| |
| function* getEventAncestors(event) { |
| if (!event.enumerateAllAncestors) |
| return; |
| yield * event.enumerateAllAncestors(); |
| } |
| |
| function* getEventDescendents(event) { |
| if (!event.enumerateAllDescendents) |
| return; |
| yield * event.enumerateAllDescendents(); |
| } |
| |
| Polymer({ |
| is: 'tr-ui-a-related-events', |
| |
| ready: function() { |
| this.eventGroups_ = []; |
| this.cancelFunctions_ = []; |
| |
| this.$.table.tableColumns = [ |
| { |
| title: 'Event(s)', |
| value: function(row) { |
| var typeEl = document.createElement('span'); |
| typeEl.innerText = row.type; |
| if (row.tooltip) |
| typeEl.title = row.tooltip; |
| return typeEl; |
| }, |
| width: '150px' |
| }, |
| { |
| title: 'Link', |
| width: '100%', |
| value: function(row) { |
| var linkEl = document.createElement('tr-ui-a-analysis-link'); |
| if (row.name) |
| linkEl.setSelectionAndContent(row.selection, row.name); |
| else |
| linkEl.selection = row.selection; |
| return linkEl; |
| } |
| } |
| ]; |
| }, |
| |
| hasRelatedEvents: function() { |
| return (this.eventGroups_ && this.eventGroups_.length > 0); |
| }, |
| |
| setRelatedEvents: function(eventSet) { |
| this.cancelAllTasks_(); |
| this.eventGroups_ = []; |
| this.addRuntimeCallStats_(eventSet); |
| this.addV8Slices_(eventSet); |
| this.addConnectedFlows_(eventSet); |
| this.addConnectedEvents_(eventSet); |
| this.addOverlappingSamples_(eventSet); |
| this.updateContents_(); |
| }, |
| |
| addConnectedFlows_: function(eventSet) { |
| var classifier = new tr.ui.analysis.FlowClassifier(); |
| eventSet.forEach(function(slice) { |
| if (slice.inFlowEvents) { |
| slice.inFlowEvents.forEach(function(flow) { |
| classifier.addInFlow(flow); |
| }); |
| } |
| if (slice.outFlowEvents) { |
| slice.outFlowEvents.forEach(function(flow) { |
| classifier.addOutFlow(flow); |
| }); |
| } |
| }); |
| if (!classifier.hasEvents()) |
| return; |
| |
| var addToEventGroups = function(type, flowEvent) { |
| this.eventGroups_.push({ |
| type: type, |
| selection: new tr.model.EventSet(flowEvent), |
| name: flowEvent.title |
| }); |
| }; |
| |
| classifier.inFlowEvents.forEach( |
| addToEventGroups.bind(this, 'Incoming flow')); |
| classifier.outFlowEvents.forEach( |
| addToEventGroups.bind(this, 'Outgoing flow')); |
| classifier.internalFlowEvents.forEach( |
| addToEventGroups.bind(this, 'Internal flow')); |
| }, |
| |
| cancelAllTasks_: function() { |
| this.cancelFunctions_.forEach(function(cancelFunction) { |
| cancelFunction(); |
| }); |
| this.cancelFunctions_ = []; |
| }, |
| |
| addConnectedEvents_: function(eventSet) { |
| this.cancelFunctions_.push(this.createEventsLinkIfNeeded_( |
| 'Preceding events', |
| 'Add all events that have led to the selected one(s), connected by ' + |
| 'flow arrows or by call stack.', |
| eventSet, |
| function*(event) { |
| yield * getEventInFlowEvents(event); |
| yield * getEventAncestors(event); |
| if (event.startSlice) |
| yield event.startSlice; |
| }.bind(this))); |
| this.cancelFunctions_.push(this.createEventsLinkIfNeeded_( |
| 'Following events', |
| 'Add all events that have been caused by the selected one(s), ' + |
| 'connected by flow arrows or by call stack.', |
| eventSet, |
| function*(event) { |
| yield * getEventOutFlowEvents(event); |
| yield * getEventDescendents(event); |
| if (event.endSlice) |
| yield event.endSlice; |
| }.bind(this))); |
| this.cancelFunctions_.push(this.createEventsLinkIfNeeded_( |
| 'All connected events', |
| 'Add all events connected to the selected one(s) by flow arrows or ' + |
| 'by call stack.', |
| eventSet, |
| function*(event) { |
| yield * getEventInFlowEvents(event); |
| yield * getEventOutFlowEvents(event); |
| yield * getEventAncestors(event); |
| yield * getEventDescendents(event); |
| if (event.startSlice) |
| yield event.startSlice; |
| if (event.endSlice) |
| yield event.endSlice; |
| }.bind(this))); |
| }, |
| |
| createEventsLinkIfNeeded_: function(title, tooltip, events, connectedFn) { |
| events = new tr.model.EventSet(events); |
| var eventsToProcess = new Set(events); |
| // for (var event of events) |
| // eventsToProcess.add(event); |
| var wasChanged = false; |
| var task; |
| var isCanceled = false; |
| function addEventsUntilTimeout() { |
| if (isCanceled) |
| return; |
| // Let's grant ourselves a budget of 8 ms. If time runs out, then |
| // create another task to do the rest. |
| var timeout = window.performance.now() + 8; |
| // TODO(alexandermont): Don't check window.performance.now |
| // every iteration. |
| while (eventsToProcess.size > 0 && |
| window.performance.now() <= timeout) { |
| // Get the next event. |
| var nextEvent = tr.b.getFirstElement(eventsToProcess); |
| eventsToProcess.delete(nextEvent); |
| |
| // Add the connected events to the list. |
| for (var eventToAdd of connectedFn(nextEvent)) { |
| if (!events.contains(eventToAdd)) { |
| events.push(eventToAdd); |
| eventsToProcess.add(eventToAdd); |
| wasChanged = true; |
| } |
| } |
| } |
| if (eventsToProcess.size > 0) { |
| // There are still events to process, but we ran out of time. Post |
| // more work for later. |
| var newTask = new tr.b.Task( |
| addEventsUntilTimeout.bind(this), this); |
| task.after(newTask); |
| task = newTask; |
| return; |
| } |
| // Went through all events, add the link. |
| if (!wasChanged) |
| return; |
| this.eventGroups_.push({ |
| type: title, |
| tooltip: tooltip, |
| selection: events |
| }); |
| this.updateContents_(); |
| } |
| function cancelTask() { |
| isCanceled = true; |
| } |
| task = new tr.b.Task(addEventsUntilTimeout.bind(this), this); |
| tr.b.Task.RunWhenIdle(task); |
| return cancelTask; |
| }, |
| |
| addOverlappingSamples_: function(eventSet) { |
| var samples = new tr.model.EventSet; |
| for (var slice of eventSet) { |
| if (!slice.parentContainer || !slice.parentContainer.samples) |
| continue; |
| var candidates = slice.parentContainer.samples; |
| var range = tr.b.Range.fromExplicitRange( |
| slice.start, slice.start + slice.duration); |
| var filteredSamples = range.filterArray( |
| candidates, function(value) {return value.start;}); |
| for (var sample of filteredSamples) |
| samples.push(sample); |
| } |
| if (samples.length > 0) { |
| this.eventGroups_.push({ |
| type: 'Overlapping samples', |
| tooltip: 'All samples overlapping the selected slice(s).', |
| selection: samples |
| }); |
| } |
| }, |
| |
| addV8Slices_: function(eventSet) { |
| var v8Slices = new tr.model.EventSet; |
| for (var slice of eventSet) { |
| if (slice.category === 'v8') |
| v8Slices.push(slice); |
| } |
| if (v8Slices.length > 0) { |
| this.eventGroups_.push({ |
| type: 'V8 Slices', |
| tooltip: 'All V8 slices in the selected slice(s).', |
| selection: v8Slices |
| }); |
| } |
| }, |
| |
| addRuntimeCallStats_: function(eventSet) { |
| var slices = new tr.model.EventSet; |
| for (var slice of eventSet) { |
| if (slice.category === 'v8' && slice.runtimeCallStats) |
| slices.push(slice); |
| } |
| if (slices.length > 0) { |
| this.eventGroups_.push({ |
| type: 'Runtime call stats table', |
| // eslint-disable-next-line |
| tooltip: 'All V8 slices containing runtime call stats table in the selected slice(s).', |
| selection: slices |
| }); |
| } |
| }, |
| |
| updateContents_: function() { |
| var table = this.$.table; |
| if (this.eventGroups_ === undefined) |
| table.tableRows = []; |
| else |
| table.tableRows = this.eventGroups_.slice(); |
| table.rebuild(); |
| } |
| }); |
| </script> |