blob: 0113e40947c700bb9330b8e25652f9797b812ba3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2019 É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.incubator.analysis.core.weighted.tree;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
/**
* A Weighted Tree class to describe hierarchical data with a weight. This class
* is a concrete class to describe a simple weighted tree, but it is also meant
* to be extended to support other metrics associated with each tree, apart from
* the weight.
*
* Note that the weight is such that the sum of the weight of the children is
* smaller or equal to the weight of the parent. Failure to comply to this will
* result in undefined behaviors when viewing the results.
*
* Also, if a child is added to the weighted tree for an object that is already
* present in the children of this tree, their data will be merged.
*
* @author Geneviève Bastien
* @param <T>
* The type of objects in this tree
*/
public class WeightedTree<@NonNull T> implements Comparable<WeightedTree<T>> {
private final T fObject;
private final Map<Object, WeightedTree<T>> fChildren = new HashMap<>();
private @Nullable WeightedTree<T> fParent;
private long fWeight = 0;
/**
* Constructor
*
* @param symbol
* The symbol of the call site. It can eventually be resolved to
* a string using the symbol providers
*/
public WeightedTree(T symbol) {
this(symbol, 0);
}
/**
* Constructor
*
* @param symbol
* The symbol of the call site. It can eventually be resolved to
* a string using the symbol providers
* @param initialWeight
* The initial length of this object
*/
public WeightedTree(T symbol, long initialWeight) {
fObject = symbol;
fParent = null;
fWeight = initialWeight;
}
/**
* Copy constructor
*
* @param copy
* The call site to copy
*/
protected WeightedTree(WeightedTree<T> copy) {
fObject = copy.fObject;
for (WeightedTree<T> entry : copy.fChildren.values()) {
fChildren.put(entry.getObject(), entry.copyOf());
}
fParent = copy.fParent;
fWeight = copy.fWeight;
}
/**
* Get the aggregated value of this callsite. The units of this length will
* depend on the time of callstack. Typically, for sampled, it will be the
* number of times this symbol was hit, while for instrumented, it can be
* the total time spent in this callstack element
*
* @return The aggregated value of this callsite
*/
public long getWeight() {
return fWeight;
}
/**
* Make a copy of this callsite, with its statistics. Implementing classes
* should make sure they copy all fields of the callsite, including the
* statistics.
*
* @return A copy of this aggregated call site
*/
public WeightedTree<T> copyOf() {
return new WeightedTree<>(this);
}
/**
* Get the symbol associated with this callsite
*
* @return The symbol for this callsite
*/
public T getObject() {
return fObject;
}
/**
* Get the caller of this callsite (parent)
*
* @return The caller of this callsite
*/
protected @Nullable WeightedTree<T> getParent() {
return fParent;
}
/**
* Sets the parent of this tree
*
* @param parent
* The parent tree
*/
protected void setParent(WeightedTree<T> parent) {
fParent = parent;
}
/**
* Get the callees of this callsite, ie the functions called by this one
*
* @return A collection of callees' callsites
*/
public Collection<WeightedTree<T>> getChildren() {
return fChildren.values();
}
/**
* Add value to the length of this callsite
*
* @param weight
* the amount to add to the length
*/
public void addToWeight(long weight) {
fWeight += weight;
}
/**
* Add a callee to this callsite. If a callee for the same object already
* exists, the data for both callees will be merged.
*
* @param callee
* the call site of the callee
*/
public void addChild(WeightedTree<T> callee) {
WeightedTree<T> callsite = fChildren.get(callee.getObject());
if (callsite == null) {
callee.setParent(this);
fChildren.put(callee.getObject(), callee);
return;
}
callsite.merge(callee);
}
/**
* Merge a callsite's data with this one. This method will modify the
* current callsite.
*
* It will first call {@link #mergeData(WeightedTree)} that needs to be
* implemented for each implementation of this class.
*
* It will then merge the callees of both callsites by adding the other's
* callees to this one.
*
* @param other
* The call site to merge. It has to have the same symbol as the
* current callsite otherwise it will throw an
* {@link IllegalArgumentException}
*/
public final void merge(WeightedTree<T> other) {
if (!other.getObject().equals(getObject())) {
throw new IllegalArgumentException("AggregatedStackTraces: trying to merge stack traces of different symbols"); //$NON-NLS-1$
}
fWeight += other.fWeight;
mergeData(other);
mergeChildren(other);
}
/**
* Merge the data of two callsites. This should modify the current
* callsite's specific data. It is called by {@link #merge(WeightedTree)}
* and this method MUST NOT touch the callees of the callsites.
*
* @param other
* The call site to merge to this one
*/
protected void mergeData(WeightedTree<T> other) {
// Nothing to do in main class
}
/**
* Merge the children callsites
*
* @param other
* The call site to merge to this one
*/
private void mergeChildren(WeightedTree<T> other) {
for (WeightedTree<T> otherChildSite : other.fChildren.values()) {
T childSymbol = otherChildSite.getObject();
WeightedTree<T> childSite = fChildren.get(childSymbol);
if (childSite == null) {
fChildren.put(childSymbol, otherChildSite.copyOf());
} else {
// combine children
childSite.merge(otherChildSite);
}
}
}
/**
* Get the maximum depth under and including this aggregated callsite. A
* depth of 1 means there is one element under and including this element.
*
* @return The maximum depth under and including this aggregated call site.
* The minimal value for the depth is 1.
*/
public int getMaxDepth() {
int maxDepth = 0;
for (WeightedTree<T> child : getChildren()) {
maxDepth = Math.max(maxDepth, child.getMaxDepth());
}
return maxDepth + 1;
}
/**
* Get other children of this tree that are not its direct descendants. It
* can be used for instance to represent extra data, for example kernel
* statuses for a callstack.
* <p>
* A weighted tree provider will advertise those potential children data
* that come with this tree, and consumers can then call this method with
* the index of this extra type, if the tree has more than one extra data
* set
*
* @param index
* The index of this extra children set, as provided by the
* {@link IWeightedTreeProvider#getExtraDataSets()} method.
*
* @return The extra children sites
*/
public Collection<WeightedTree<@NonNull T>> getExtraDataTrees(int index) {
return Collections.emptyList();
}
@Override
public String toString() {
return "WeightedTreeNode: " + fObject; //$NON-NLS-1$
}
@Override
public int compareTo(WeightedTree<@NonNull T> o) {
return Long.compare(fWeight, o.fWeight);
}
}