Weighted tree: Add the data structure
This adds and ITree<T> interface to convey a parent/child relationship
to any class. And a WeightedTree<T> class to extend for weighted tree,
ie a parent/child data structure where each element has a weight, that
can represent anything.
It also adds an IWeightedTreeProvider interface that analysis providing
a weighted tree can implement to indicate what the data represents.
[added] A weighted tree data structure to display as flame graphs/tree
Change-Id: I10a8b7adf5c9a8ee1436a5e054bfca9e9a20123d
Signed-off-by: Geneviève Bastien <gbastien+lttng@versatic.net>
Reviewed-on: https://git.eclipse.org/r/133216
Tested-by: CI Bot
Reviewed-by: Matthew Khouzam <matthew.khouzam@ericsson.com>
Tested-by: Matthew Khouzam <matthew.khouzam@ericsson.com>
diff --git a/callstack/org.eclipse.tracecompass.incubator.analysis.core/META-INF/MANIFEST.MF b/callstack/org.eclipse.tracecompass.incubator.analysis.core/META-INF/MANIFEST.MF
index 4923cd9..ca80ce5 100644
--- a/callstack/org.eclipse.tracecompass.incubator.analysis.core/META-INF/MANIFEST.MF
+++ b/callstack/org.eclipse.tracecompass.incubator.analysis.core/META-INF/MANIFEST.MF
@@ -21,7 +21,9 @@
Export-Package: org.eclipse.tracecompass.incubator.analysis.core.aspects,
org.eclipse.tracecompass.incubator.analysis.core.concepts,
org.eclipse.tracecompass.incubator.analysis.core.model,
+ org.eclipse.tracecompass.incubator.analysis.core.weighted.tree,
org.eclipse.tracecompass.incubator.internal.analysis.core;x-internal:=true,
+ org.eclipse.tracecompass.incubator.internal.analysis.core.aspects;x-internal:=true,
org.eclipse.tracecompass.incubator.internal.analysis.core.model;x-friends:="org.eclipse.tracecompass.incubator.analysis.core.tests,org.eclipse.tracecompass.incubator.callstack.core.tests"
Import-Package: com.google.common.collect
Automatic-Module-Name: org.eclipse.tracecompass.incubator.analysis.core
diff --git a/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/ITree.java b/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/ITree.java
new file mode 100644
index 0000000..11a2785
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/ITree.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * 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 org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * A basic interface for a tree structure, ie hierarchical data where each node
+ * can be linked to a specific object.
+ *
+ * This interface is meant to be used for consuming the tree structure, the
+ * implementations can add their own tree building methods.
+ *
+ * @author Geneviève Bastien
+ *
+ * @param <T>
+ * The type of objects contained in this tree
+ */
+public interface ITree<@NonNull T> {
+
+ /**
+ * Get the symbol associated with this callsite
+ *
+ * @return The symbol for this callsite
+ */
+ T getObject();
+
+ /**
+ * Get the caller of this callsite (parent)
+ *
+ * @return The caller of this callsite
+ */
+ @Nullable ITree<T> getParent();
+
+ /**
+ * Get the callees of this callsite, ie the functions called by this one
+ *
+ * @return A collection of callees' callsites
+ */
+ Collection<ITree<T>> getChildren();
+
+}
diff --git a/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/IWeightedTreeProvider.java b/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/IWeightedTreeProvider.java
new file mode 100644
index 0000000..4cee4a3
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/IWeightedTreeProvider.java
@@ -0,0 +1,281 @@
+/*******************************************************************************
+ * 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.text.FieldPosition;
+import java.text.Format;
+import java.text.ParsePosition;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.common.core.format.DecimalUnitFormat;
+import org.eclipse.tracecompass.common.core.format.SubSecondTimeWithUnitFormat;
+import org.eclipse.tracecompass.common.core.format.DataSizeWithUnitFormat;
+import org.eclipse.tracecompass.common.core.format.DataSpeedWithUnitFormat;
+import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
+
+/**
+ * An interface that classes and analyses providing weighted trees can
+ * implement. This interface allows to add extra information about the specific
+ * trees that it provides.
+ *
+ * The trees are associated with elements that are used to group them. The
+ * elements can implement the {@link ITree} class if there is a hierarchy in the
+ * groupings.
+ *
+ * @author Geneviève Bastien
+ *
+ * @param <N>
+ * The type of objects represented by each node in the tree
+ * @param <E>
+ * The type of elements used to group the trees
+ * @param <T>
+ * The type of the tree provided
+ */
+public interface IWeightedTreeProvider<@NonNull N, E, @NonNull T extends WeightedTree<N>> {
+
+ /**
+ * The type of data that a value represents. Mostly for numeric value, as
+ * the data type will help decide how to format the data to be displayed to
+ * the user
+ */
+ public enum DataType {
+ /**
+ * Data represent a decimal number
+ */
+ NUMBER(new DecimalUnitFormat()),
+ /**
+ * Data represent a time in nanoseconds, can be negative
+ */
+ NANOSECONDS(SubSecondTimeWithUnitFormat.getInstance()),
+ /**
+ * Data represent a binary size, in bytes
+ */
+ BYTES(DataSizeWithUnitFormat.getInstance()),
+ /**
+ * Data represent a binary speed, in bytes/second
+ */
+ BINARY_SPEED(DataSpeedWithUnitFormat.getInstance()),
+ /**
+ * Any other type of data. Metric that use this data type may use
+ * additional formatter.
+ */
+ OTHER(new Format() {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StringBuffer format(@Nullable Object obj, @Nullable StringBuffer toAppendTo, @Nullable FieldPosition pos) {
+ if (toAppendTo == null) {
+ return new StringBuffer(String.valueOf(obj));
+ }
+ return Objects.requireNonNull(toAppendTo.append(String.valueOf(obj)));
+ }
+
+ @Override
+ public @Nullable Object parseObject(@Nullable String source, @Nullable ParsePosition pos) {
+ return null;
+ }
+
+ });
+
+ private Format fFormatter;
+
+ private DataType(Format formatter) {
+ fFormatter = formatter;
+ }
+
+ /**
+ * Formats an object according to the specified formatter
+ *
+ * @param object
+ * The object to format
+ * @return The formatted string
+ */
+ public String format(Object object) {
+ return fFormatter.format(object);
+ }
+ }
+
+ /**
+ * This class associate a title to a data type for tree metrics
+ */
+ public static class MetricType {
+ private final String fTitle;
+ private final DataType fDataType;
+ private final @Nullable Format fFormatter;
+
+ /**
+ * Constructor
+ *
+ * @param title
+ * The title of this metric (a string meant for end users)
+ * @param dataType
+ * The type of data this metric represent
+ * @param format
+ * The formatter for this metric. If <code>null</code>,
+ * formatting will use the {@link DataType}'s default
+ * formatter
+ */
+ public MetricType(String title, DataType dataType, @Nullable Format format) {
+ fTitle = title;
+ fDataType = dataType;
+ fFormatter = format;
+ }
+
+ /**
+ * Get the title of this metric, for the user
+ *
+ * @return The title
+ */
+ public String getTitle() {
+ return fTitle;
+ }
+
+ /**
+ * Get the type of data of this metric
+ *
+ * @return The data type of the metric
+ */
+ public DataType getDataType() {
+ return fDataType;
+ }
+
+ /**
+ * Formats an object for this metric
+ *
+ * @param obj
+ * The object to format
+ * @return The formatted string
+ */
+ public String format(Object obj) {
+ if (fFormatter != null) {
+ return Objects.requireNonNull(fFormatter.format(obj));
+ }
+ return fDataType.format(obj);
+ }
+
+ }
+
+ /**
+ * The default metric type for the tree's weight
+ */
+ MetricType WEIGHT_TYPE = new MetricType("Weight", DataType.NUMBER, null); //$NON-NLS-1$
+
+ /**
+ * Get the trees provided by this analysis. This should return all the trees
+ * for the whole trace
+ *
+ * @param element
+ * The element for which to get the trees
+ * @return A collection of trees provided by this class
+ */
+ Collection<T> getTreesFor(E element);
+
+ /**
+ * Get the trees for a certain time range. If a provider does not support
+ * ranged trees, this method can just return an empty collection
+ *
+ * @param element
+ * The element for which to get the trees
+ * @param start
+ * The timestamp of the start of the range
+ * @param end
+ * The timestamp of the end of the range
+ * @return A collection of trees whose values span from start to end
+ */
+ default Collection<T> getTrees(E element, ITmfTimestamp start, ITmfTimestamp end) {
+ return Collections.emptySet();
+ }
+
+ /**
+ * Get the elements under which are the trees. It can be a single constant
+ * element if this provider does not have the concept of grouping the trees.
+ *
+ * @return The elements used to group the trees
+ */
+ Collection<E> getElements();
+
+ /**
+ * Get the metric type for the weight value. The default metric is called
+ * "Weight" and is a number
+ *
+ * @return The metric type for the weight value.
+ */
+ default MetricType getWeightType() {
+ return WEIGHT_TYPE;
+ }
+
+ /**
+ * Get a list of additional metrics that are provided by this tree.
+ *
+ * @return A list of metrics provided by the trees, in addition to the
+ * weight
+ */
+ default List<MetricType> getAdditionalMetrics() {
+ return Collections.emptyList();
+ }
+
+ /**
+ * Get an additional metric for a tree. The metric index corresponds to the
+ * position of the desired metric in the list of metric returned by the
+ * {@link #getAdditionalMetrics()} method and the return value should be of
+ * the proper data type
+ *
+ * @param object
+ * The tree object for which to get the metric
+ * @param metricIndex
+ * The index in the list of the metric metric to get
+ * @return The value of the metric for the tree in parameter
+ */
+ default Object getAdditionalMetric(T object, int metricIndex) {
+ throw new UnsupportedOperationException("If the tree provider has metric, it should implement this method, or it should not be called"); //$NON-NLS-1$
+ }
+
+ /**
+ * Return a list of additional data sets' titles. These sets will be available
+ * by calling {@link WeightedTree#getExtraDataTrees(int)} on the trees,
+ * where the index in the list is the parameter that the children set should
+ * match
+ *
+ * @return The title of each child set
+ */
+ default List<String> getExtraDataSets() {
+ return Collections.emptyList();
+ }
+
+ /**
+ * Get a user-facing text to identify a tree object. By default, it is the
+ * string representation of the object.
+ *
+ * @param tree
+ * The tree whose value to display
+ * @return A user-facing string to identify this node
+ */
+ default String toDisplayString(T tree) {
+ return String.valueOf(tree.getObject());
+ }
+
+ /**
+ * A title for this tree provider. This title will be visible by users and
+ * should describe what this tree provider's data represent.
+ *
+ * @return The title of this provider
+ */
+ String getTitle();
+
+}
diff --git a/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/WeightedTree.java b/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/WeightedTree.java
new file mode 100644
index 0000000..0113e40
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/WeightedTree.java
@@ -0,0 +1,272 @@
+/*******************************************************************************
+ * 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);
+ }
+
+}
diff --git a/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/package-info.java b/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/package-info.java
new file mode 100644
index 0000000..41e95ad
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/package-info.java
@@ -0,0 +1,16 @@
+/*******************************************************************************
+ * 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
+ *******************************************************************************/
+
+/**
+ * This package contains classes describing a weighted tree data structure, and
+ * interfaces to implement for analysis or components that provide weighted
+ * trees.
+ */
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.eclipse.tracecompass.incubator.analysis.core.weighted.tree;