/*******************************************************************************
* Copyright (c) 2016 Ericsson
*
* 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.timing.core.callgraph;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.analysis.timing.core.segmentstore.IAnalysisProgressListener;
import org.eclipse.tracecompass.analysis.timing.core.segmentstore.ISegmentStoreProvider;
import org.eclipse.tracecompass.common.core.StreamUtils;
import org.eclipse.tracecompass.internal.analysis.timing.core.Activator;
import org.eclipse.tracecompass.segmentstore.core.ISegment;
import org.eclipse.tracecompass.segmentstore.core.ISegmentStore;
import org.eclipse.tracecompass.segmentstore.core.SegmentStoreFactory;
import org.eclipse.tracecompass.segmentstore.core.SegmentStoreFactory.SegmentStoreType;
import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
import org.eclipse.tracecompass.statesystem.core.exceptions.AttributeNotFoundException;
import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException;
import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException;
import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval;
import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue;
import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue.Type;
import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule;
import org.eclipse.tracecompass.tmf.core.analysis.TmfAbstractAnalysisModule;
import org.eclipse.tracecompass.tmf.core.callstack.CallStackAnalysis;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
/**
* Call stack analysis used to create a segment for each call function from an
* entry/exit event. It builds a segment tree from the state system. An example
* taken from the Fibonacci trace's callStack shows the structure of the segment
* tree given by this analysis:
*
* <pre>
* (Caller) main
* ↓↑
* (Callee) Fibonacci
* ↓↑ ↓↑
* Fibonacci Fibonacci
* ↓↑ ↓↑
* ... ...
* </pre>
*
* @author Sonia Farrah
*/
public abstract class CallGraphAnalysis extends TmfAbstractAnalysisModule implements ISegmentStoreProvider {
// ------------------------------------------------------------------------
// Attributes
// ------------------------------------------------------------------------
/**
* Segment store
*/
private final ISegmentStore<@NonNull ISegment> fStore;
/**
* Listeners
*/
private final ListenerList fListeners = new ListenerList(ListenerList.IDENTITY);
/**
* The Trace's root functions list
*/
private final List<ICalledFunction> fRootFunctions = new ArrayList<>();
/**
* The sub attributes of a certain thread
*/
private List<Integer> fCurrentQuarks = Collections.emptyList();
/**
* The List of thread nodes. Each thread has a virtual node having the root
* function as children
*/
private List<ThreadNode> fThreadNodes = new ArrayList<>();
/**
* Default constructor
*/
public CallGraphAnalysis() {
super();
fStore = SegmentStoreFactory.createSegmentStore(SegmentStoreType.Fast);
}
@Override
public @NonNull String getHelpText() {
String msg = Messages.CallGraphAnalysis_Description;
return (msg != null) ? msg : super.getHelpText();
}
@Override
public @NonNull String getHelpText(@NonNull ITmfTrace trace) {
return getHelpText();
}
@Override
public boolean canExecute(ITmfTrace trace) {
/*
* FIXME: change to !Iterables.isEmpty(getDependentAnalyses()) when
* analysis dependencies work better
*/
return true;
}
@Override
protected Iterable<IAnalysisModule> getDependentAnalyses() {
return TmfTraceManager.getTraceSet(getTrace()).stream()
.flatMap(trace -> StreamUtils.getStream(TmfTraceUtils.getAnalysisModulesOfClass(trace, CallStackAnalysis.class)))
.distinct().collect(Collectors.toList());
}
@Override
protected boolean executeAnalysis(@Nullable IProgressMonitor monitor) {
ITmfTrace trace = getTrace();
if (monitor == null || trace == null) {
return false;
}
Iterable<IAnalysisModule> dependentAnalyses = getDependentAnalyses();
for (IAnalysisModule module : dependentAnalyses) {
if (!(module instanceof CallStackAnalysis)) {
return false;
}
module.schedule();
}
// TODO:Look at updates while the state system's being built
dependentAnalyses.forEach((t) -> t.waitForCompletion(monitor));
for (IAnalysisModule module : dependentAnalyses) {
CallStackAnalysis callstackModule = (CallStackAnalysis) module;
String[] threadsPattern = callstackModule.getThreadsPattern();
String[] processesPattern = callstackModule.getProcessesPattern();
String[] callStackPath = callstackModule.getCallStackPath();
ITmfStateSystem ss = callstackModule.getStateSystem();
if (!iterateOverStateSystem(ss, threadsPattern, processesPattern, callStackPath, monitor)) {
return false;
}
}
monitor.worked(1);
monitor.done();
return true;
}
/**
* Iterate over the process of the state system,then iterate over the
* different threads of each process.
*
* @param ss
* The state system
* @param threadsPattern
* The threads pattern
* @param processesPattern
* The processes pattern
* @param callStackPath
* The call stack path
* @param monitor
* The monitor
* @return Boolean
*/
@VisibleForTesting
protected boolean iterateOverStateSystem(@Nullable ITmfStateSystem ss, String[] threadsPattern, String[] processesPattern, String[] callStackPath, IProgressMonitor monitor) {
if (ss == null) {
return false;
}
List<Integer> processQuarks = ss.getQuarks(processesPattern);
for (int processQuark : processQuarks) {
int processId = getProcessId(ss, processQuark, ss.getCurrentEndTime());
for (int threadQuark : ss.getQuarks(processQuark, threadsPattern)) {
if (!iterateOverQuark(ss, processId, threadQuark, callStackPath, monitor)) {
return false;
}
}
}
sendUpdate(fStore);
return true;
}
/**
* Iterate over functions with the same quark,search for their callees then
* add them to the segment store
*
* @param stateSystem
* The state system
* @param processId
* The process ID of the traced application
* @param threadQuark
* The thread quark
* @param subAttributePath
* sub-Attributes path
* @param monitor
* The monitor
* @return Boolean
*/
private boolean iterateOverQuark(ITmfStateSystem stateSystem, int processId, int threadQuark, String[] subAttributePath, IProgressMonitor monitor) {
String threadName = stateSystem.getAttributeName(threadQuark);
long threadId = -1;
ITmfStateInterval interval = null;
try {
interval = stateSystem.querySingleState(stateSystem.getStartTime(), threadQuark);
ITmfStateValue threadStateValue = interval.getStateValue();
if (threadStateValue.getType() == Type.LONG || threadStateValue.getType() == Type.INTEGER) {
threadId = threadStateValue.unboxLong();
} else {
try {
threadId = Long.parseLong(threadName);
} catch (NumberFormatException e) {
/* use default threadId */
}
}
} catch (StateSystemDisposedException error) {
Activator.getInstance().logError(Messages.QueringStateSystemError, error);
}
try {
long curTime = stateSystem.getStartTime();
long limit = stateSystem.getCurrentEndTime();
AbstractCalledFunction initSegment = CalledFunctionFactory.create(0, 0, 0, threadName, processId, null);
ThreadNode init = new ThreadNode(initSegment, 0, threadId);
while (curTime < limit) {
if (monitor.isCanceled()) {
return false;
}
int callStackQuark = stateSystem.getQuarkRelative(threadQuark, subAttributePath);
fCurrentQuarks = stateSystem.getSubAttributes(callStackQuark, false);
if (fCurrentQuarks.isEmpty()) {
return false;
}
final int depth = 0;
int quarkParent = fCurrentQuarks.get(depth);
interval = stateSystem.querySingleState(curTime, quarkParent);
ITmfStateValue stateValue = interval.getStateValue();
if (!stateValue.isNull()) {
long intervalStart = interval.getStartTime();
long intervalEnd = interval.getEndTime();
// Create the segment for the first call event.
AbstractCalledFunction rootFunction = CalledFunctionFactory.create(intervalStart, intervalEnd + 1, depth, stateValue, processId, null);
fRootFunctions.add(rootFunction);
AggregatedCalledFunction firstNode = new AggregatedCalledFunction(rootFunction, fCurrentQuarks.size());
if (!findChildren(rootFunction, depth, stateSystem, fCurrentQuarks.size() + fCurrentQuarks.get(depth), firstNode, processId, monitor)) {
return false;
}
init.addChild(rootFunction, firstNode);
}
curTime = interval.getEndTime() + 1;
}
fThreadNodes.add(init);
} catch (AttributeNotFoundException | StateSystemDisposedException | TimeRangeException e) {
Activator.getInstance().logError(Messages.QueringStateSystemError, e);
return false;
}
return true;
}
/**
* Find the functions called by a parent function in a call stack then add
* segments for each child, updating the self times of each node
* accordingly.
*
* @param node
* The segment of the stack call event(the parent) callStackQuark
* @param depth
* The depth of the parent function
* @param ss
* The quark of the segment parent ss The actual state system
* @param maxQuark
* The last quark in the state system
* @param aggregatedCalledFunction
* A node in the aggregation tree
* @param processId
* The process ID of the traced application
* @param monitor
* The progress monitor The progress monitor TODO: if stack size
* is an issue, convert to a stack instead of recursive function
*/
private boolean findChildren(AbstractCalledFunction node, int depth, ITmfStateSystem ss, int maxQuark, AggregatedCalledFunction aggregatedCalledFunction, int processId, IProgressMonitor monitor) {
fStore.add(node);
long curTime = node.getStart();
long limit = node.getEnd();
ITmfStateInterval interval = null;
while (curTime < limit) {
if (monitor.isCanceled()) {
return false;
}
try {
if (depth + 1 < fCurrentQuarks.size()) {
interval = ss.querySingleState(curTime, fCurrentQuarks.get(depth + 1));
} else {
return true;
}
} catch (StateSystemDisposedException e) {
Activator.getInstance().logError(Messages.QueringStateSystemError, e);
return false;
}
ITmfStateValue stateValue = interval.getStateValue();
if (!stateValue.isNull()) {
long intervalStart = interval.getStartTime();
long intervalEnd = interval.getEndTime();
if (intervalStart < node.getStart() || intervalEnd > limit) {
return true;
}
AbstractCalledFunction function = CalledFunctionFactory.create(intervalStart, intervalEnd + 1, node.getDepth() + 1, stateValue, processId, node);
AggregatedCalledFunction childNode = new AggregatedCalledFunction(function, aggregatedCalledFunction);
// Search for the children with the next quark.
findChildren(function, depth + 1, ss, maxQuark, childNode, processId, monitor);
node.addChild(function);
aggregatedCalledFunction.addChild(function, childNode);
}
curTime = interval.getEndTime() + 1;
}
return true;
}
@Override
public void addListener(@NonNull IAnalysisProgressListener listener) {
fListeners.add(listener);
}
@Override
public void removeListener(@NonNull IAnalysisProgressListener listener) {
fListeners.remove(listener);
}
@Override
protected void canceling() {
// Do nothing
}
@Override
public @Nullable ISegmentStore<@NonNull ISegment> getSegmentStore() {
return fStore;
}
/**
* Update listeners
*
* @param store
* The segment store
*/
protected void sendUpdate(final ISegmentStore<@NonNull ISegment> store) {
getListeners().forEach(listener -> listener.onComplete(this, store));
}
/**
* Get Listeners
*
* @return The listeners
*/
protected Iterable<IAnalysisProgressListener> getListeners() {
return Arrays.stream(fListeners.getListeners())
.filter(listener -> listener instanceof IAnalysisProgressListener)
.map(listener -> (IAnalysisProgressListener) listener)
.collect(Collectors.toList());
}
/**
* The functions of the first level
*
* @return Functions of the first level
*/
public List<ICalledFunction> getRootFunctions() {
return ImmutableList.copyOf(fRootFunctions);
}
/**
* List of thread nodes. Each thread has a virtual node having the root
* functions called as children.
*
* @return The thread nodes
*/
public List<ThreadNode> getThreadNodes() {
return ImmutableList.copyOf(fThreadNodes);
}
private static int getProcessId(ITmfStateSystem ss, int processQuark, long curTime) {
int processId = -1;
if (processQuark != ITmfStateSystem.ROOT_ATTRIBUTE) {
try {
ITmfStateInterval interval = ss.querySingleState(curTime, processQuark);
String processName = ss.getAttributeName(processQuark);
ITmfStateValue processStateValue = interval.getStateValue();
if (processStateValue.getType() == Type.INTEGER) {
processId = processStateValue.unboxInt();
} else {
try {
processId = Integer.parseInt(processName);
} catch (NumberFormatException e) {
/* use default processId */
}
}
} catch (StateSystemDisposedException e) {
// ignore
}
}
return processId;
}
}