Skip to content

Commit

Permalink
weighted tree: Add thresholds to differential palette
Browse files Browse the repository at this point in the history
This allows to specify a minimum a maximum percentage value between
which there will be a gradation of the colors (negative or positive).
Values below min are considered equal, and above max are shown darkest.

Change-Id: I51a867e7c9ddc68798245855f3056214dfb75f1c
Signed-off-by: Geneviève Bastien <gbastien+lttng@versatic.net>
Reviewed-on: https://git.eclipse.org/r/154442
Tested-by: Trace Compass Bot <tracecompass-bot@eclipse.org>
Reviewed-by: Matthew Khouzam <matthew.khouzam@ericsson.com>
Tested-by: Matthew Khouzam <matthew.khouzam@ericsson.com>
  • Loading branch information
tahini committed Jan 22, 2020
1 parent d7f424e commit 7c56fb0
Show file tree
Hide file tree
Showing 4 changed files with 248 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*******************************************************************************
* 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.tests.weighted.diff;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.tracecompass.incubator.analysis.core.weighted.tree.WeightedTree;
import org.eclipse.tracecompass.incubator.analysis.core.weighted.tree.diff.DifferentialWeightedTree;
import org.eclipse.tracecompass.incubator.internal.analysis.core.weighted.tree.DifferentialPalette;
import org.eclipse.tracecompass.tmf.core.model.OutputElementStyle;
import org.eclipse.tracecompass.tmf.core.util.Pair;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

import com.google.common.collect.ImmutableList;

/**
* Test the {@link DifferentialPalette} class
*
* @author Geneviève Bastien
*/
@NonNullByDefault
@RunWith(Parameterized.class)
public class DifferentialPaletteTest {

private static final String NO_DIFF_STYLE = "equal";
private static final String LESS_STYLES = "less"; //$NON-NLS-1$
private static final String MORE_STYLES = "more"; //$NON-NLS-1$
private static final int MAX_VALUE = 5;

/**
* @return The arrays of parameters
*/
@Parameters(name = "{index}: {0}")
public static Iterable<Object[]> getParameters() {
return Arrays.asList(new Object[][] {
{ "Default Palette", DifferentialPalette.getInstance(), Collections.singletonList(0), ImmutableList.of(1, 0.04),
ImmutableList.of(new Pair<>(0.00001, 1), new Pair<>(0.009, 1), new Pair<>(0.01, 2), new Pair<>(0.015, 2), new Pair<>(0.03, 4), new Pair<>(0.030000000001, 4), new Pair<>(0.02999999999, 3)) },
{ "Palette With Threshold", DifferentialPalette.create(10, 100), ImmutableList.of(0, 0.01, 0.0999999, 0.05, 0.1), ImmutableList.of(1, 10),
ImmutableList.of(new Pair<>(0.10000000001, 1), new Pair<>(0.9, 4), new Pair<>(0.33, 2), new Pair<>(0.53, 2), new Pair<>(0.55, 3), new Pair<>(0.75, 3), new Pair<>(0.78, 4), new Pair<>(0.98, 4)) },
{ "Palette With Small Threshold", DifferentialPalette.create(0, 1), Collections.singletonList(0), ImmutableList.of(0.01, 0.1, 1),
ImmutableList.of(new Pair<>(0.0000000001, 1), new Pair<>(0.00999999999, 4), new Pair<>(0.0025, 2), new Pair<>(0.0049999999, 2), new Pair<>(0.00500001, 3), new Pair<>(0.0075, 4), new Pair<>(0.0078, 4)) },

});
}

private final String fTestName;
private final DifferentialPalette fPalette;
private final List<Number> fNoDiffValues;
private final List<Number> fMaxDiffValues;
private final List<Pair<Number, Integer>> fTestValues;

/**
* Constructor
*
* @param testName
* The name of the current test case
* @param palette
* The palette to test
* @param noDiffValues
* The values for which there should be no difference shown
* @param maxDiffValues
* The values for which there should be maximum difference heat
* shown
* @param testMap
* A map of value and their expected heat
*/
public DifferentialPaletteTest(String testName, DifferentialPalette palette, List<Number> noDiffValues, List<Number> maxDiffValues, List<Pair<Number, Integer>> testMap) {
fTestName = testName;
fPalette = palette;
fNoDiffValues = noDiffValues;
fMaxDiffValues = maxDiffValues;
fTestValues = testMap;
}

/**
* Test the differential palette with the default constructor values
*/
@Test
public void testDefaultDiffPalette() {
Map<String, OutputElementStyle> styles = fPalette.getStyles();

WeightedTree<String> original = new WeightedTree<>("test", 100);

// Test a difference of 0
for (Number diff : fNoDiffValues) {
DifferentialWeightedTree<String> diffTree = new DifferentialWeightedTree<>(original, original.getObject(), original.getWeight(), diff.doubleValue());
OutputElementStyle style = fPalette.getStyleFor(diffTree);
assertEquals(fTestName + ": No diff style " + diff, NO_DIFF_STYLE, style.getParentKey());
assertTrue(fTestName + ": Style present for " + diff, styles.containsKey(style.getParentKey()));
}

// Test the maximum difference above the threshold, negative and positive
for (Number diff : fMaxDiffValues) {
// Test the positive value
DifferentialWeightedTree<String> diffTree = new DifferentialWeightedTree<>(original, original.getObject(), original.getWeight(), Math.abs(diff.doubleValue()));
OutputElementStyle style = fPalette.getStyleFor(diffTree);
assertEquals(fTestName + ": Max diff positive " + diff, MORE_STYLES + MAX_VALUE, style.getParentKey());
assertTrue(fTestName + ": Style present " + diff, styles.containsKey(style.getParentKey()));

// Test the negative value
diffTree = new DifferentialWeightedTree<>(original, original.getObject(), original.getWeight(), -Math.abs(diff.doubleValue()));
style = fPalette.getStyleFor(diffTree);
assertEquals(fTestName + ": Max diff negative " + diff, LESS_STYLES + MAX_VALUE, style.getParentKey());
assertTrue(fTestName + ": Style present " + diff, styles.containsKey(style.getParentKey()));
}

// Test the various values in between the thresholds
for (Pair<Number, Integer> diffEntry : fTestValues) {
Number diff = diffEntry.getFirst();
Integer expectedHeat = diffEntry.getSecond();

// Test he positive value
DifferentialWeightedTree<String> diffTree = new DifferentialWeightedTree<>(original, original.getObject(), original.getWeight(), Math.abs(diff.doubleValue()));
OutputElementStyle style = fPalette.getStyleFor(diffTree);
assertEquals(fTestName + ": positive diff " + diff, MORE_STYLES + expectedHeat, style.getParentKey());
assertTrue(fTestName + ": Style present " + diff, styles.containsKey(style.getParentKey()));

// Test the negative value
diffTree = new DifferentialWeightedTree<>(original, original.getObject(), original.getWeight(), -Math.abs(diff.doubleValue()));
style = fPalette.getStyleFor(diffTree);
assertEquals(fTestName + ": negative diff " + diff, LESS_STYLES + expectedHeat, style.getParentKey());
assertTrue(fTestName + ": Style present " + diff, styles.containsKey(style.getParentKey()));
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ public class DifferentialWeightedTreeProvider<@NonNull N> implements IWeightedTr
private static final Format DIFFERENTIAL_FORMAT = new Format() {

/**
*
* UUID for this format
*/
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 9150811551603074986L;

@Override
public @Nullable StringBuffer format(@Nullable Object obj, @Nullable StringBuffer toAppendTo, @Nullable FieldPosition pos) {
Expand Down Expand Up @@ -76,6 +76,7 @@ public class DifferentialWeightedTreeProvider<@NonNull N> implements IWeightedTr

private final IWeightedTreeProvider<N, ?, WeightedTree<N>> fOriginalTree;
private final List<MetricType> fAdditionalMetrics = new ArrayList<>(WEIGHT_TYPES);
private @Nullable IDataPalette fPalette = null;

/**
* Constructor
Expand Down Expand Up @@ -103,6 +104,31 @@ public DifferentialWeightedTreeProvider(IWeightedTreeProvider<N, ?, WeightedTree
fAdditionalMetrics.addAll(fOriginalTree.getAdditionalMetrics());
}

/**
* Set the differential threshold for this provider, ie views will highlight
* the gradual heat of the differential value when the value is between min
* and max threshold values.
*
* If the 2 values are identical, the default palette will be used.
*
* @param minThreshold
* Minimal threshold (in %, typically between 0 and 100) of
* significance for the heat (absolute value). Any percentage
* below this value (whether positive or negative) will be
* considered as equal.
* @param maxThreshold
* Maximal threshold (in %, typically between 0 and 100) of
* significance for the heat (absolute value). Any percentage
* above this value (whether positive or negative) will be
* considered at maximum heat.
*/
public void setHeatThresholds(int minThreshold, int maxThreshold) {
if (minThreshold == maxThreshold) {
fPalette = DifferentialPalette.getInstance();
}
fPalette = DifferentialPalette.create(minThreshold, maxThreshold);
}

@Override
public String getTitle() {
return "Differential tree"; //$NON-NLS-1$
Expand Down Expand Up @@ -138,7 +164,8 @@ public IWeightedTreeSet<N, Object, DifferentialWeightedTree<N>> getTreeSet() {

@Override
public IDataPalette getPalette() {
return DifferentialPalette.getInstance();
IDataPalette palette = fPalette;
return palette == null ? DifferentialPalette.getInstance() : palette;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@

package org.eclipse.tracecompass.incubator.internal.analysis.core.weighted.tree;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.incubator.analysis.core.weighted.tree.IDataPalette;
import org.eclipse.tracecompass.incubator.analysis.core.weighted.tree.diff.DifferentialWeightedTree;
import org.eclipse.tracecompass.incubator.internal.analysis.core.Activator;
import org.eclipse.tracecompass.tmf.core.dataprovider.X11ColorUtils;
import org.eclipse.tracecompass.tmf.core.model.OutputElementStyle;
import org.eclipse.tracecompass.tmf.core.model.StyleProperties;
Expand All @@ -37,23 +40,23 @@ public class DifferentialPalette implements IDataPalette {
private static @Nullable DifferentialPalette fInstance = null;
private static final int NB_COLORS = 5;
private static final String NO_DIFF_STYLE = "equal"; //$NON-NLS-1$
private static final OutputElementStyle WHITE_STYLE;
private static final String LESS_STYLES = "less"; //$NON-NLS-1$
private static final String MORE_STYLES = "more"; //$NON-NLS-1$
private static final Map<String, OutputElementStyle> STYLES;
// Map of styles with the parent
private static final Map<String, OutputElementStyle> STYLE_MAP = Collections.synchronizedMap(new HashMap<>());

static {
ImmutableMap.Builder<String, OutputElementStyle> builder = new ImmutableMap.Builder<>();
// Almost white style for when there is no diff
WHITE_STYLE = new OutputElementStyle(null, ImmutableMap.of(
builder.put(NO_DIFF_STYLE, new OutputElementStyle(null, ImmutableMap.of(
StyleProperties.STYLE_NAME, "No diff", //$NON-NLS-1$
StyleProperties.BACKGROUND_COLOR, X11ColorUtils.toHexColor(200, 200, 200),
StyleProperties.OPACITY, 1.0f));
StyleProperties.OPACITY, 1.0f)));

// Create the green palette (for less)
IPaletteProvider palette = SequentialPaletteProvider.create(DefaultColorPaletteProvider.GREEN, NB_COLORS + 1);
int i = 0;
ImmutableMap.Builder<String, OutputElementStyle> builder = new ImmutableMap.Builder<>();
builder.put(NO_DIFF_STYLE, WHITE_STYLE);
for (RGBAColor color : palette.get()) {
if (i == 0) {
// Skip first color (white)
Expand Down Expand Up @@ -85,14 +88,38 @@ public class DifferentialPalette implements IDataPalette {
STYLES = builder.build();
}

private DifferentialPalette() {
// Private constructor
private final int fMinThreshold;
private final int fMaxThreshold;
private final double fHeatStep;

/**
* Creates a palette with thresholds from/to which to define the heat of the
* difference.
*
* @param minThreshold
* Minimal threshold (in %, typically between 0 and 100) of
* significance for the heat (absolute value). Any percentage
* below this value (whether positive or negative) will be
* considered as equal.
* @param maxThreshold
* Maximal threshold (in %, typically between 0 and 100) of
* significance for the heat (absolute value). Any percentage
* above this value (whether positive or negative) will be
* considered at maximum heat
* @return A differential palette with the given threshold
*/
public static DifferentialPalette create(int minThreshold, int maxThreshold) {
if (minThreshold == maxThreshold) {
Activator.getInstance().logWarning("Creating differential palette with wrong arguments: min threshold should be different from max threshold " + minThreshold); //$NON-NLS-1$
return new DifferentialPalette(minThreshold, minThreshold + 1);
}
return new DifferentialPalette(minThreshold, maxThreshold);
}

/**
* Get the instance of this palette
* Get the default instance of this palette
*
* @return The instance of the palette
* @return The default instance of the palette
*/
public static DifferentialPalette getInstance() {
DifferentialPalette instance = fInstance;
Expand All @@ -103,26 +130,42 @@ public static DifferentialPalette getInstance() {
return instance;
}

private DifferentialPalette() {
this(0, NB_COLORS - 1);
}

private DifferentialPalette(int minThreshold, int maxThreshold) {
fMinThreshold = Math.min(Math.abs(minThreshold), Math.abs(maxThreshold));
fMaxThreshold = Math.max(Math.abs(minThreshold), Math.abs(maxThreshold));
fHeatStep = (double) (fMaxThreshold - fMinThreshold) / (NB_COLORS - 1);
}

@Override
public OutputElementStyle getStyleFor(Object object) {
if (object instanceof DifferentialWeightedTree) {
DifferentialWeightedTree<?> tree = (DifferentialWeightedTree<?>) object;
double difference = tree.getDifference();
if (difference == Double.NaN) {
return STYLES.getOrDefault(MORE_STYLES + NB_COLORS, WHITE_STYLE);
return STYLE_MAP.computeIfAbsent(MORE_STYLES + NB_COLORS, styleStr -> new OutputElementStyle(styleStr));
}
if (difference == 0) {
return WHITE_STYLE;
double percent = (Math.abs(difference) * 100);
// Not a significant difference
if (percent <= fMinThreshold) {
return STYLE_MAP.computeIfAbsent(NO_DIFF_STYLE, styleStr -> new OutputElementStyle(styleStr));
}
if (difference < 0) {
// The heat will be between 1 and NB_COLORS
int diffHeat = Math.max(1, Math.min(NB_COLORS, (int) (Math.abs(difference) * 100)));
return STYLES.getOrDefault(LESS_STYLES + diffHeat, WHITE_STYLE);

// Find the heat for this value
int diffHeat = NB_COLORS;
if (percent < fMaxThreshold) {
// The heat must be at least 1
diffHeat = Math.min(NB_COLORS, Math.max(1, (int) (((percent - fMinThreshold) / fHeatStep)) + 1));
}
int diffHeat = Math.max(1, Math.min(NB_COLORS, (int) difference * 100));
return STYLES.getOrDefault(MORE_STYLES + diffHeat, WHITE_STYLE);

// Find the right style, more or less, depending on the difference sign
return STYLE_MAP.computeIfAbsent((difference < 0) ? LESS_STYLES + diffHeat : MORE_STYLES + diffHeat, styleStr -> new OutputElementStyle(styleStr));

}
return WHITE_STYLE;
return STYLE_MAP.computeIfAbsent(NO_DIFF_STYLE, styleStr -> new OutputElementStyle(styleStr));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

package org.eclipse.tracecompass.incubator.scripting.core.callstack;

import org.eclipse.ease.modules.ScriptParameter;
import org.eclipse.ease.modules.WrapToScript;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
Expand Down Expand Up @@ -109,6 +110,12 @@ public class CallStackScriptingModule {
* The base treeset for comparison
* @param second
* The treeset to compare to
* @param minSignificantValue
* The value as a percentage (between 0 and 100), under which is
* difference should not be highlighted
* @param maxSignificantValue
* The value as a percentage (between 0 and 100), above which the
* difference should be highlighted at the maximal value.
* @param <N>
* The type of data that goes in the trees
* @return The resulting differential weighted tree provider containing the
Expand All @@ -117,8 +124,13 @@ public class CallStackScriptingModule {
@WrapToScript
public <@NonNull N> @Nullable DifferentialWeightedTreeProvider<N> diffTreeSets(IWeightedTreeProvider<N, ?, WeightedTree<N>> provider,
IWeightedTreeSet<N, @NonNull ?, WeightedTree<N>> first,
IWeightedTreeSet<N, @NonNull ?, WeightedTree<N>> second) {
IWeightedTreeSet<N, @NonNull ?, WeightedTree<N>> second,
@ScriptParameter(defaultValue = "-1") int minSignificantValue,
@ScriptParameter(defaultValue = "-1") int maxSignificantValue) {
DifferentialWeightedTreeProvider<@NonNull N> diffTrees = WeightedTreeUtils.diffTreeSets(provider, first, second);
if (diffTrees != null && minSignificantValue >= 0) {
diffTrees.setHeatThresholds(minSignificantValue, maxSignificantValue);
}
return diffTrees;
}

Expand Down

0 comments on commit 7c56fb0

Please sign in to comment.