blob: 361fd74f0213072950020cda8ac46706869d7504 [file] [log] [blame]
<!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>