/*******************************************************************************
* Copyright (c) 2015, 2016 École Polytechnique de Montréal
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.eclipse.tracecompass.internal.analysis.graph.ui.criticalpath.view;
import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.tracecompass.analysis.graph.core.base.IGraphWorker;
import org.eclipse.tracecompass.analysis.graph.core.base.TmfEdge;
import org.eclipse.tracecompass.analysis.graph.core.base.TmfEdge.EdgeType;
import org.eclipse.tracecompass.analysis.graph.core.base.TmfGraph;
import org.eclipse.tracecompass.analysis.graph.core.base.TmfVertex;
import org.eclipse.tracecompass.analysis.graph.core.building.TmfGraphBuilderModule;
import org.eclipse.tracecompass.analysis.graph.core.criticalpath.CriticalPathModule;
import org.eclipse.tracecompass.common.core.NonNullUtils;
import org.eclipse.tracecompass.internal.analysis.graph.core.base.TmfGraphStatistics;
import org.eclipse.tracecompass.internal.analysis.graph.core.base.TmfGraphVisitor;
import org.eclipse.tracecompass.internal.analysis.graph.ui.criticalpath.view.CriticalPathPresentationProvider.State;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
import org.eclipse.tracecompass.tmf.core.signal.TmfStartAnalysisSignal;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
import org.eclipse.tracecompass.tmf.ui.views.timegraph.AbstractTimeGraphView;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.ITimeGraphContentProvider;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ILinkEvent;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeEvent;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeGraphEntry;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.TimeEvent;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.TimeGraphEntry;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.TimeLinkEvent;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Iterables;
import com.google.common.collect.Table;
/**
* The Critical Path view
*
* @author Geneviève Bastien
* @author Francis Giraldeau
*/
public class CriticalPathView extends AbstractTimeGraphView {
// ------------------------------------------------------------------------
// Constants
// ------------------------------------------------------------------------
/** View ID */
public static final String ID = "org.eclipse.linuxtools.tmf.analysis.graph.ui.criticalpath.view.criticalpathview"; //$NON-NLS-1$
private static final double NANOINV = 0.000000001;
private static final String COLUMN_PROCESS = Messages.getMessage(Messages.CriticalFlowView_columnProcess);
private static final String COLUMN_ELAPSED = Messages.getMessage(Messages.CriticalFlowView_columnElapsed);
private static final String COLUMN_PERCENT = Messages.getMessage(Messages.CriticalFlowView_columnPercent);
private static final String[] COLUMN_NAMES = new String[] {
COLUMN_PROCESS,
COLUMN_ELAPSED,
COLUMN_PERCENT
};
private static final String[] FILTER_COLUMN_NAMES = new String[] {
COLUMN_PROCESS
};
private final Table<ITmfTrace, Object, List<ILinkEvent>> fLinks = HashBasedTable.create();
/** The trace to entry list hash map */
private final Table<ITmfTrace, Object, TmfGraphStatistics> fObjectStatistics = HashBasedTable.create();
private final CriticalPathContentProvider fContentProvider = new CriticalPathContentProvider();
private TmfGraphStatistics fStats = new TmfGraphStatistics();
private static final IGraphWorker DEFAULT_WORKER = new IGraphWorker() {
@Override
public String getHostId() {
return "default"; //$NON-NLS-1$
}
};
private class CriticalPathContentProvider implements ITimeGraphContentProvider {
private final class HorizontalLinksVisitor extends TmfGraphVisitor {
private final CriticalPathEntry fDefaultParent;
private final Map<String, CriticalPathEntry> fHostEntries;
private final TmfGraph fGraph;
private final ITmfTrace fTrace;
private final HashMap<Object, CriticalPathEntry> fRootList;
private HorizontalLinksVisitor(CriticalPathEntry defaultParent, Map<String, CriticalPathEntry> hostEntries, TmfGraph graph, ITmfTrace trace, HashMap<Object, CriticalPathEntry> rootList) {
fDefaultParent = defaultParent;
fHostEntries = hostEntries;
fGraph = graph;
fTrace = trace;
fRootList = rootList;
}
@Override
public void visitHead(TmfVertex node) {
/* TODO possible null pointer ? */
IGraphWorker owner = fGraph.getParentOf(node);
if (owner == null) {
return;
}
if (fRootList.containsKey(owner)) {
return;
}
TmfVertex first = fGraph.getHead(owner);
TmfVertex last = fGraph.getTail(owner);
if (first == null || last == null) {
return;
}
setStartTime(Math.min(getStartTime(), first.getTs()));
setEndTime(Math.max(getEndTime(), last.getTs()));
// create host entry
CriticalPathEntry parent = fDefaultParent;
String host = owner.getHostId();
if (!fHostEntries.containsKey(host)) {
fHostEntries.put(host, new CriticalPathEntry(host, fTrace, getStartTime(), getEndTime(), owner));
}
parent = checkNotNull(fHostEntries.get(host));
CriticalPathEntry entry = new CriticalPathEntry(NonNullUtils.nullToEmptyString(owner), fTrace, getStartTime(), getEndTime(), owner);
parent.addChild(entry);
fRootList.put(owner, entry);
}
@Override
public void visit(TmfEdge link, boolean horizontal) {
if (horizontal) {
Object parent = fGraph.getParentOf(link.getVertexFrom());
CriticalPathEntry entry = fRootList.get(parent);
TimeEvent ev = new TimeEvent(entry, link.getVertexFrom().getTs(), link.getDuration(),
getMatchingState(link.getType()).ordinal());
entry.addEvent(ev);
}
}
}
private final class VerticalLinksVisitor extends TmfGraphVisitor {
private final TmfGraph fGraph;
private final List<ILinkEvent> fGraphLinks;
private final Map<Object, CriticalPathEntry> fEntryMap;
private VerticalLinksVisitor(TmfGraph graph, List<ILinkEvent> graphLinks, Map<Object, CriticalPathEntry> entryMap) {
fGraph = graph;
fGraphLinks = graphLinks;
fEntryMap = entryMap;
}
@Override
public void visitHead(TmfVertex node) {
}
@Override
public void visit(TmfVertex node) {
}
@Override
public void visit(TmfEdge link, boolean horizontal) {
if (!horizontal) {
Object parentFrom = fGraph.getParentOf(link.getVertexFrom());
Object parentTo = fGraph.getParentOf(link.getVertexTo());
CriticalPathEntry entryFrom = fEntryMap.get(parentFrom);
CriticalPathEntry entryTo = fEntryMap.get(parentTo);
TimeLinkEvent lk = new TimeLinkEvent(entryFrom, entryTo, link.getVertexFrom().getTs(),
link.getVertexTo().getTs() - link.getVertexFrom().getTs(), getMatchingState(link.getType()).ordinal());
fGraphLinks.add(lk);
}
}
}
private class BuildThread extends Thread {
private final ITmfTrace fBuildTrace;
private final IProgressMonitor fMonitor;
public BuildThread(final ITmfTrace trace) {
super("Critical path view build"); //$NON-NLS-1$
fBuildTrace = trace;
fMonitor = new NullProgressMonitor();
}
@Override
public void run() {
try {
CriticalPathModule module = Iterables.<@Nullable CriticalPathModule> getFirst(
TmfTraceUtils.getAnalysisModulesOfClass(fBuildTrace, CriticalPathModule.class),
null);
if (module == null) {
return;
}
module.schedule();
if (module.waitForCompletion(fMonitor)) {
// Module is completed, set the start and end time of
// this view
setStartEndTime(module);
refresh();
}
} finally {
fSyncLock.lock();
fBuildThread = null;
fSyncLock.unlock();
}
}
public void cancel() {
fMonitor.setCanceled(true);
}
}
private final Lock fSyncLock = new ReentrantLock();
private final Map<Object, Map<Object, CriticalPathEntry>> workerMaps = new HashMap<>();
private final Map<Object, List<TimeGraphEntry>> workerEntries = new HashMap<>();
private @Nullable Object fCurrentObject;
private @Nullable BuildThread fBuildThread = null;
@Override
public ITimeGraphEntry[] getElements(@Nullable Object inputElement) {
ITimeGraphEntry[] ret = new ITimeGraphEntry[0];
if (inputElement instanceof List) {
List<?> list = (List<?>) inputElement;
if (!list.isEmpty()) {
Object first = list.get(0);
if (first instanceof CriticalPathBaseEntry) {
IGraphWorker worker = ((CriticalPathBaseEntry) first).getWorker();
ret = getWorkerEntries(worker);
}
}
}
return ret;
}
private ITimeGraphEntry[] getWorkerEntries(IGraphWorker worker) {
fCurrentObject = worker;
List<TimeGraphEntry> entries = workerEntries.get(worker);
ITmfTrace trace = getTrace();
if (entries == null) {
buildEntryList(worker);
entries = workerEntries.get(worker);
} else if (trace != null) {
// Get the statistics object for this worker
TmfGraphStatistics stats = fObjectStatistics.get(trace, worker);
if (stats == null) {
stats = new TmfGraphStatistics();
final TmfGraph graph = getGraph(trace);
if (graph != null) {
stats.computeGraphStatistics(graph, worker);
}
}
fStats = stats;
}
return (entries == null) ?
new ITimeGraphEntry[0] :
entries.toArray(new @NonNull ITimeGraphEntry[entries.size()]);
}
private void buildEntryList(IGraphWorker worker) {
final ITmfTrace trace = getTrace();
if (trace == null) {
return;
}
final TmfGraph graph = getGraph(trace);
if (graph == null) {
return;
}
final HashMap<Object, CriticalPathEntry> rootList = new HashMap<>();
fLinks.remove(trace, worker);
TmfVertex vertex = graph.getHead();
/* Calculate statistics */
fStats = new TmfGraphStatistics();
fStats.computeGraphStatistics(graph, worker);
fObjectStatistics.put(trace, worker, fStats);
// Hosts entries are parent of each worker entries
final Map<String, CriticalPathEntry> hostEntries = new HashMap<>();
/* create all interval entries and horizontal links */
final CriticalPathEntry defaultParent = new CriticalPathEntry("default", trace, getStartTime(), getEndTime(), DEFAULT_WORKER); //$NON-NLS-1$
graph.scanLineTraverse(vertex, new HorizontalLinksVisitor(defaultParent, hostEntries, graph, trace, rootList));
workerMaps.put(worker, rootList);
List<TimeGraphEntry> list = new ArrayList<>();
list.addAll(hostEntries.values());
if (defaultParent.hasChildren()) {
list.add(defaultParent);
}
workerEntries.put(worker, list);
}
private @Nullable TmfGraph getGraph(final ITmfTrace trace) {
CriticalPathModule module = Iterables.<@Nullable CriticalPathModule> getFirst(
TmfTraceUtils.getAnalysisModulesOfClass(trace, CriticalPathModule.class),
null);
if (module == null) {
throw new IllegalStateException("View requires an analysis module"); //$NON-NLS-1$
}
final TmfGraph graph = module.getCriticalPath();
return graph;
}
public @Nullable List<ILinkEvent> getLinkList(long startTime, long endTime) {
Object current = fCurrentObject;
if (current == null) {
return null;
}
final ITmfTrace trace = getTrace();
if (trace == null) {
return null;
}
/*
* Critical path typically has relatively few links, so we calculate
* and save them all, but just return those in range
*/
List<ILinkEvent> links = fLinks.get(trace, current);
if (links != null) {
return getLinksInRange(links, startTime, endTime);
}
CriticalPathModule module = Iterables.<@Nullable CriticalPathModule> getFirst(
TmfTraceUtils.getAnalysisModulesOfClass(trace, CriticalPathModule.class), null);
if (module == null) {
throw new IllegalStateException("View requires an analysis module"); //$NON-NLS-1$
}
final TmfGraph graph = module.getCriticalPath();
if (graph == null) {
return null;
}
final Map<Object, CriticalPathEntry> entryMap = workerMaps.get(current);
if (entryMap == null) {
return null;
}
TmfVertex vertex = graph.getHead();
final List<ILinkEvent> graphLinks = new ArrayList<>();
/* find vertical links */
graph.scanLineTraverse(vertex, new VerticalLinksVisitor(graph, graphLinks, entryMap));
fLinks.put(trace, current, graphLinks);
return getLinksInRange(graphLinks, startTime, endTime);
}
private List<ILinkEvent> getLinksInRange(List<ILinkEvent> allLinks, long startTime, long endTime) {
List<ILinkEvent> linksInRange = new ArrayList<>();
for (ILinkEvent link : allLinks) {
if (((link.getTime() >= startTime) && (link.getTime() <= endTime)) ||
((link.getTime() + link.getDuration() >= startTime) && (link.getTime() + link.getDuration() <= endTime))) {
linksInRange.add(link);
}
}
return linksInRange;
}
@Override
public void dispose() {
fSyncLock.lock();
try {
BuildThread buildThread = fBuildThread;
if (buildThread != null) {
buildThread.cancel();
}
} finally {
fSyncLock.unlock();
}
}
@Override
public void inputChanged(@Nullable Viewer viewer, @Nullable Object oldInput, @Nullable Object newInput) {
// The input has changed, the critical path will be re-computed,
// wait for the analysis to be finished, then call the refresh
// method of the view
if (!(newInput instanceof List)) {
return;
}
List<?> list = (List<?>) newInput;
if (list.isEmpty()) {
return;
}
final ITmfTrace trace = getTrace();
if (trace == null) {
return;
}
fSyncLock.lock();
try {
BuildThread buildThread = fBuildThread;
if (buildThread != null) {
buildThread.cancel();
}
buildThread = new BuildThread(trace);
buildThread.start();
fBuildThread = buildThread;
} finally {
fSyncLock.unlock();
}
}
@Override
public ITimeGraphEntry @Nullable [] getChildren(@Nullable Object parentElement) {
if (parentElement instanceof CriticalPathEntry) {
List<? extends ITimeGraphEntry> children = ((CriticalPathEntry) parentElement).getChildren();
return children.toArray(new TimeGraphEntry[children.size()]);
}
return null;
}
@Override
public @Nullable ITimeGraphEntry getParent(@Nullable Object element) {
if (element instanceof CriticalPathEntry) {
return ((CriticalPathEntry) element).getParent();
}
return null;
}
@Override
public boolean hasChildren(@Nullable Object element) {
if (element instanceof CriticalPathEntry) {
return ((CriticalPathEntry) element).hasChildren();
}
return false;
}
}
private class CriticalPathTreeLabelProvider extends TreeLabelProvider {
@Override
public String getColumnText(@Nullable Object element, int columnIndex) {
if (element == null) {
return StringUtils.EMPTY;
}
CriticalPathEntry entry = (CriticalPathEntry) element;
if (columnIndex == 0) {
return NonNullUtils.nullToEmptyString(entry.getName());
} else if (columnIndex == 1) {
Long sum = fStats.getSum(entry.getWorker());
String value = String.format("%.9f", sum * NANOINV); //$NON-NLS-1$
return NonNullUtils.nullToEmptyString(value);
} else if (columnIndex == 2) {
Double percent = fStats.getPercent(entry.getWorker());
String value = String.format("%.2f", percent * 100); //$NON-NLS-1$
return NonNullUtils.nullToEmptyString(value);
}
return StringUtils.EMPTY;
}
}
private class CriticalPathEntryComparator implements Comparator<ITimeGraphEntry> {
@Override
public int compare(@Nullable ITimeGraphEntry o1, @Nullable ITimeGraphEntry o2) {
int result = 0;
if ((o1 instanceof CriticalPathEntry) && (o2 instanceof CriticalPathEntry)) {
CriticalPathEntry entry1 = (CriticalPathEntry) o1;
CriticalPathEntry entry2 = (CriticalPathEntry) o2;
result = -1 * fStats.getSum(entry1.getWorker()).compareTo(fStats.getSum(entry2.getWorker()));
}
return result;
}
}
/**
* Constructor
*/
public CriticalPathView() {
super(ID, new CriticalPathPresentationProvider());
setTreeColumns(COLUMN_NAMES);
setFilterColumns(FILTER_COLUMN_NAMES);
setTreeLabelProvider(new CriticalPathTreeLabelProvider());
setTimeGraphContentProvider(fContentProvider);
setEntryComparator(new CriticalPathEntryComparator());
}
// ------------------------------------------------------------------------
// Internal
// ------------------------------------------------------------------------
private static State getMatchingState(EdgeType type) {
State state = State.UNKNOWN;
switch (type) {
case RUNNING:
state = State.RUNNING;
break;
case PREEMPTED:
state = State.PREEMPTED;
break;
case TIMER:
state = State.TIMER;
break;
case BLOCK_DEVICE:
state = State.BLOCK_DEVICE;
break;
case INTERRUPTED:
state = State.INTERRUPTED;
break;
case NETWORK:
state = State.NETWORK;
break;
case USER_INPUT:
state = State.USER_INPUT;
break;
case IPI:
state = State.IPI;
break;
case EPS:
case UNKNOWN:
case DEFAULT:
case BLOCKED:
break;
default:
break;
}
return state;
}
@Override
protected void buildEntryList(@NonNull ITmfTrace trace, @NonNull ITmfTrace parentTrace, @NonNull IProgressMonitor monitor) {
/* This class uses a content provider instead */
}
@Override
protected @Nullable List<ITimeEvent> getEventList(TimeGraphEntry entry,
long startTime, long endTime, long resolution,
IProgressMonitor monitor) {
/*
* The event list is built in the HorizontalLinksVisitor. This is called
* only from the zoom thread and only for the CriticalPathBaseEntry.
*/
return null;
}
@Override
protected @Nullable List<ILinkEvent> getLinkList(long startTime, long endTime, long resolution, IProgressMonitor monitor) {
return fContentProvider.getLinkList(startTime, endTime);
}
/**
* Signal handler for analysis started
*
* @param signal
* The signal
*/
@TmfSignalHandler
public void analysisStarted(TmfStartAnalysisSignal signal) {
if (!(signal.getAnalysisModule() instanceof CriticalPathModule)) {
return;
}
CriticalPathModule module = (CriticalPathModule) signal.getAnalysisModule();
Object obj = module.getParameter(CriticalPathModule.PARAM_WORKER);
if (obj == null) {
return;
}
if (!(obj instanceof IGraphWorker)) {
throw new IllegalStateException("Wrong type for critical path module parameter " + //$NON-NLS-1$
CriticalPathModule.PARAM_WORKER +
" expected IGraphWorker got " + //$NON-NLS-1$
obj.getClass().getSimpleName());
}
ITmfTrace trace = getTrace();
if (trace == null) {
throw new IllegalStateException("Trace is null"); //$NON-NLS-1$
}
IGraphWorker worker = (IGraphWorker) obj;
TimeGraphEntry tge = new CriticalPathBaseEntry(worker);
List<TimeGraphEntry> list = Collections.singletonList(tge);
putEntryList(trace, list);
refresh();
}
private void setStartEndTime(CriticalPathModule module) {
// Initialize the start/end time of the view to trace's times
ITmfTrace trace = getTrace();
if (trace == null) {
throw new IllegalStateException("The trace should not be null when we have a critical path to display"); //$NON-NLS-1$
}
long start = trace.getStartTime().toNanos();
long end = trace.getEndTime().toNanos();
// Set the start/end time of the view
Object paramGraph = module.getParameter(CriticalPathModule.PARAM_GRAPH);
if (paramGraph instanceof TmfGraphBuilderModule) {
TmfGraphBuilderModule graphModule = (TmfGraphBuilderModule) paramGraph;
TmfGraph graph = graphModule.getGraph();
if (graph == null) {
return;
}
TmfVertex head = graph.getHead();
if (head != null) {
start = Math.min(start, head.getTs());
for (IGraphWorker w : graph.getWorkers()) {
TmfVertex tail = graph.getTail(w);
if (tail != null) {
end = Math.max(end, tail.getTs());
}
}
}
}
setStartTime(start);
setEndTime(end);
}
}