Skip to content

Commit

Permalink
profiling: Add style provider to CallStackDataProvider
Browse files Browse the repository at this point in the history
Allow consumers of the TSP to know what color to assign.

Use common code for flame graph and flame chart presentation.

Add address to tooltip when available.

Improve tooltips on server side.

[Added] style provider to call stack data provider
[Changed] Begin work on making flame graph work with TSP
[Deprecated] CallStackPresentationProvider

Change-Id: I001780ed147ecc9470d757ffe5f80be5c81c6d4f
Signed-off-by: Matthew Khouzam <matthew.khouzam@ericsson.com>
Reviewed-on: https://git.eclipse.org/r/c/tracecompass/org.eclipse.tracecompass/+/184234
Tested-by: Patrick Tasse <patrick.tasse@gmail.com>
Tested-by: Trace Compass Bot <tracecompass-bot@eclipse.org>
Reviewed-by: Patrick Tasse <patrick.tasse@gmail.com>
  • Loading branch information
MatthewKhouzam committed Aug 26, 2021
1 parent 10a544c commit 2efecf8
Show file tree
Hide file tree
Showing 12 changed files with 383 additions and 48 deletions.
Expand Up @@ -3,7 +3,7 @@ Bundle-ManifestVersion: 2
Bundle-Name: %Bundle-Name
Bundle-Vendor: %Bundle-Vendor
Bundle-SymbolicName: org.eclipse.tracecompass.analysis.profiling.core;singleton:=true
Bundle-Version: 2.0.1.qualifier
Bundle-Version: 2.1.0.qualifier
Bundle-Localization: plugin
Bundle-Activator: org.eclipse.tracecompass.internal.analysis.profiling.core.Activator
Bundle-ActivationPolicy: lazy
Expand Down
@@ -0,0 +1,82 @@
/*******************************************************************************
* Copyright (c) 2018 É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 2.0 which
* accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/

package org.eclipse.tracecompass.analysis.profiling.core.base;

import java.util.Map;
import java.util.Objects;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.tracecompass.tmf.core.dataprovider.X11ColorUtils;
import org.eclipse.tracecompass.tmf.core.model.OutputElementStyle;
import org.eclipse.tracecompass.tmf.core.model.StyleProperties;
import org.eclipse.tracecompass.tmf.core.presentation.IPaletteProvider;
import org.eclipse.tracecompass.tmf.core.presentation.RGBAColor;
import org.eclipse.tracecompass.tmf.core.presentation.RotatingPaletteProvider;

import com.google.common.collect.ImmutableMap;

/**
* Class to manage the colors of the flame chart and flame graph views
*
* @author Geneviève Bastien
* @since 2.1
*/
public final class FlameDefaultPalette {

/**
* The state index for the multiple state
*/
private static final int NUM_COLORS = 360;

private static final @NonNull Map<@NonNull String, @NonNull OutputElementStyle> STYLES;

static {
IPaletteProvider palette = new RotatingPaletteProvider.Builder().setNbColors(NUM_COLORS).build();
int i = 0;
ImmutableMap.Builder<@NonNull String, @NonNull OutputElementStyle> builder = new ImmutableMap.Builder<>();
for (RGBAColor color : palette.get()) {
builder.put(String.valueOf(i), new OutputElementStyle(null, ImmutableMap.of(
StyleProperties.STYLE_NAME, String.valueOf(i),
StyleProperties.BACKGROUND_COLOR, X11ColorUtils.toHexColor(color.getRed(), color.getGreen(), color.getBlue()))));
i++;
}
STYLES = builder.build();
}

private FlameDefaultPalette() {
// Do nothing
}

/**
* Get the map of all styles provided by this palette. These are the base
* styles, mapping to the key for each style. Styles for object can then
* refer to those base styles as parents.
*
* @return The map of style name to full style description.
*/
public static @NonNull Map<@NonNull String, @NonNull OutputElementStyle> getStyles() {
return STYLES;
}

/**
* Get the style element for a given value
*
* @param callsite
* The value to get an element for
* @return The output style
*/
public static OutputElementStyle getStyleFor(Object callsite) {
int index = Math.abs(31 * callsite.hashCode()) % NUM_COLORS;
return Objects.requireNonNull(STYLES.get(String.valueOf(index)));
}

}
Expand Up @@ -26,7 +26,9 @@
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.analysis.profiling.core.base.FlameDefaultPalette;
import org.eclipse.tracecompass.analysis.profiling.core.callstack.CallStackAnalysis;
import org.eclipse.tracecompass.internal.analysis.profiling.core.Activator;
import org.eclipse.tracecompass.internal.tmf.core.analysis.callsite.CallsiteAnalysis;
import org.eclipse.tracecompass.internal.tmf.core.model.filters.FetchParametersUtils;
import org.eclipse.tracecompass.internal.tmf.core.model.timegraph.AbstractTimeGraphDataProvider;
Expand All @@ -37,6 +39,8 @@
import org.eclipse.tracecompass.tmf.core.dataprovider.DataProviderParameterUtils;
import org.eclipse.tracecompass.tmf.core.event.lookup.ITmfCallsite;
import org.eclipse.tracecompass.tmf.core.model.CommonStatusMessage;
import org.eclipse.tracecompass.tmf.core.model.IOutputStyleProvider;
import org.eclipse.tracecompass.tmf.core.model.OutputStyleModel;
import org.eclipse.tracecompass.tmf.core.model.filters.SelectionTimeQueryFilter;
import org.eclipse.tracecompass.tmf.core.model.timegraph.ITimeGraphArrow;
import org.eclipse.tracecompass.tmf.core.model.timegraph.ITimeGraphRowModel;
Expand All @@ -50,9 +54,9 @@
import org.eclipse.tracecompass.tmf.core.symbols.ISymbolProvider;
import org.eclipse.tracecompass.tmf.core.symbols.SymbolProviderManager;
import org.eclipse.tracecompass.tmf.core.symbols.SymbolProviderUtils;
import org.eclipse.tracecompass.tmf.core.symbols.TmfResolvedSymbol;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
import org.eclipse.tracecompass.tmf.core.util.Pair;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
Expand All @@ -66,8 +70,9 @@
*
* @author Loic Prieur-Drevon
*/
public class CallStackDataProvider extends AbstractTimeGraphDataProvider<@NonNull CallStackAnalysis, @NonNull CallStackEntryModel> {
public class CallStackDataProvider extends AbstractTimeGraphDataProvider<@NonNull CallStackAnalysis, @NonNull CallStackEntryModel> implements IOutputStyleProvider {

private static final String ADDRESS_FORMAT = "0x%x"; //$NON-NLS-1$
/**
* Extension point ID.
*/
Expand All @@ -78,15 +83,67 @@ public class CallStackDataProvider extends AbstractTimeGraphDataProvider<@NonNul

private final @NonNull Collection<@NonNull ISymbolProvider> fProviders = new ArrayList<>();

private final LoadingCache<Pair<Integer, ITmfStateInterval>, @Nullable String> fTimeEventNames = CacheBuilder.newBuilder()
private static final class TimePidNameValue {

private final long fTime;
private final int fPid;
private final Object fNameValue;

public TimePidNameValue(int pid, Object nameValue, long time) {
fTime = time;
fPid = pid;
fNameValue = nameValue;
}

/**
* @return the time
*/
public long getTime() {
return fTime;
}

/**
* @return the pid
*/
public int getPid() {
return fPid;
}

/**
* @return the nameValue
*/
public Object getNameValue() {
return fNameValue;
}

@Override
public int hashCode() {
return Objects.hash(fNameValue, fPid, fTime);
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
TimePidNameValue other = (TimePidNameValue) obj;
return Objects.equals(fNameValue, other.fNameValue) && fPid == other.fPid && fTime == other.fTime;
}
}

private final LoadingCache<TimePidNameValue, @Nullable String> fTimeEventNames = CacheBuilder.newBuilder()
.maximumSize(1000)
.build(new CacheLoader<Pair<Integer, ITmfStateInterval>, @Nullable String>() {
.build(new CacheLoader<TimePidNameValue, @Nullable String>() {
@Override
public @Nullable String load(Pair<Integer, ITmfStateInterval> pidInterval) {
Integer pid = pidInterval.getFirst();
ITmfStateInterval interval = pidInterval.getSecond();
public @Nullable String load(TimePidNameValue value) {
Object nameValue = value.getNameValue();

Object nameValue = interval.getValue();
Long address = null;
String name = null;
if (nameValue instanceof String) {
Expand All @@ -106,7 +163,7 @@ public class CallStackDataProvider extends AbstractTimeGraphDataProvider<@NonNul
}
if (address != null) {
synchronized (fProviders) {
name = SymbolProviderUtils.getSymbolText(fProviders, pid, interval.getStartTime(), address);
name = SymbolProviderUtils.getSymbolText(fProviders, value.getPid(), value.getTime(), address);
}
}
return name;
Expand Down Expand Up @@ -174,7 +231,8 @@ public CallStackDataProvider(@NonNull ITmfTrace trace, @NonNull CallStackAnalysi
}
String threadName = ss.getAttributeName(threadQuark);
/*
* Default to process/trace entry, overwrite if a thread entry exists.
* Default to process/trace entry, overwrite if a thread entry
* exists.
*/
long callStackParent = threadParentId;
if (threadQuark != processQuark) {
Expand Down Expand Up @@ -266,7 +324,7 @@ protected TimeGraphModel getRowModel(ITmfStateSystem ss, @NonNull Map<@NonNull S
predicates.putAll(computeRegexPredicate(regexesMap));
}

@NonNull List<@NonNull ITimeGraphRowModel> rows = new ArrayList<>();
List<@NonNull ITimeGraphRowModel> rows = new ArrayList<>();
for (Map.Entry<Long, Integer> entry : entries.entrySet()) {
if (subMonitor.isCanceled()) {
return null;
Expand All @@ -291,16 +349,13 @@ private ITimeGraphState createTimeGraphState(ITmfStateInterval interval) {
Object value = interval.getValue();
Integer pid = fQuarkToPid.get(interval.getAttribute());
if (value != null && pid != null) {
String name = fTimeEventNames.getUnchecked(new Pair<>(pid, interval));
return new TimeGraphState(startTime, duration, getColorValue(name, value), name);
String name = fTimeEventNames.getUnchecked(new TimePidNameValue(pid, interval.getValue(), interval.getStartTime()));
Object key = name == null ? value : name;
return new TimeGraphState(startTime, duration, name, FlameDefaultPalette.getStyleFor(key));
}
return new TimeGraphState(startTime, duration, Integer.MIN_VALUE);
}

private static int getColorValue(String name, Object value) {
return 31 * (name != null ? name.hashCode() : value.hashCode());
}

@Override
public @NonNull TmfModelResponse<@NonNull List<@NonNull ITimeGraphArrow>> fetchArrows(Map<String, Object> parameters, @Nullable IProgressMonitor monitor) {
return new TmfModelResponse<>(null, ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
Expand Down Expand Up @@ -345,8 +400,8 @@ public void resetFunctionNames(@Nullable IProgressMonitor monitor) {
* selection start time
* @param forward
* if going to next or previous
* @return the next / previous state encapsulated in a row if it exists, else
* null
* @return the next / previous state encapsulated in a row if it exists,
* else null
* @throws StateSystemDisposedException
*/
private static List<ITimeGraphRowModel> getFollowEvent(ITmfStateSystem ss, Entry<Long, Integer> entry, long time, boolean forward) throws StateSystemDisposedException {
Expand Down Expand Up @@ -378,14 +433,14 @@ private static List<ITimeGraphRowModel> getFollowEvent(ITmfStateSystem ss, Entry
List<@NonNull Long> times = DataProviderParameterUtils.extractTimeRequested(parameters);
if (selected != null && times != null) {
Map<@NonNull Long, @NonNull Integer> md = getSelectedEntries(selected);
ITmfTrace trace = getTrace();
for (Long time : times) {
for (Entry<@NonNull Long, @NonNull Integer> entry : md.entrySet()) {
Long result = analysis.resolveDeviceId(entry.getValue(), time);
if (result != null) {
String deviceId = String.valueOf(result);
String deviceType = analysis.resolveDeviceType(entry.getValue(), time);
tooltips.put(deviceType, deviceId);
ITmfTrace trace = getTrace();
Iterable<@NonNull CallsiteAnalysis> csas = TmfTraceUtils.getAnalysisModulesOfClass(trace, CallsiteAnalysis.class);
for (CallsiteAnalysis csa : csas) {
List<@NonNull ITmfCallsite> res = csa.getCallsites(String.valueOf(trace.getUUID()), deviceType, deviceId, time);
Expand All @@ -395,15 +450,45 @@ private static List<ITimeGraphRowModel> getFollowEvent(ITmfStateSystem ss, Entry
}
return new TmfModelResponse<>(tooltips, ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
}

ITmfStateSystem stateSystem = analysis.getStateSystem();
if (stateSystem != null) {
try {
Collection<@NonNull ISymbolProvider> symbolProviders = SymbolProviderManager.getInstance().getSymbolProviders(trace);
ITmfStateInterval interval = stateSystem.querySingleState(Objects.requireNonNull(time), Objects.requireNonNull(entry.getValue()));
Object value = interval.getValue();
if (value instanceof Number) {
long longValue = ((Number) value).longValue();
for (ISymbolProvider provider : symbolProviders) {
TmfResolvedSymbol symbol = provider.getSymbol(longValue);
if (symbol != null) {
tooltips.put(Messages.CallStackDataProvider_toolTipState, symbol.getSymbolName());
tooltips.put(Messages.CallStackDataProvider_toolTipAddress, String.format(ADDRESS_FORMAT, symbol.getBaseAddress()));
break;
}
}
tooltips.computeIfAbsent(Messages.CallStackDataProvider_toolTipState, unused -> String.format(ADDRESS_FORMAT, longValue));
} else {
tooltips.put(Messages.CallStackDataProvider_toolTipState, interval.getValueString());
}
} catch (StateSystemDisposedException e) {
Activator.getInstance().logError("State System Disposed", e); //$NON-NLS-1$
}
}

}
}
}
return new TmfModelResponse<>(Collections.emptyMap(), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
return new TmfModelResponse<>(tooltips, ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
}

@Override
protected boolean isCacheable() {
return true;
}

}
@Override
public @NonNull TmfModelResponse<@NonNull OutputStyleModel> fetchStyle(@NonNull Map<@NonNull String, @NonNull Object> fetchParameters, @Nullable IProgressMonitor monitor) {
return new TmfModelResponse<>(new OutputStyleModel(FlameDefaultPalette.getStyles()), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
}
}
Expand Up @@ -35,6 +35,10 @@ public class Messages extends NLS {
*/
public static @Nullable String CallStackStateProvider_IncoherentCallstack;

public static String CallStackDataProvider_toolTipAddress;

public static String CallStackDataProvider_toolTipState;

/**
* Name of the data provider shown to the user
*/
Expand Down
Expand Up @@ -14,5 +14,7 @@

CallStackStateProvider_UnmatchedPoppedValue=Function exit name in event ({0}) different from the expected one ({1}). You may have lost events in your trace.
CallStackStateProvider_IncoherentCallstack=Incoherent callstack found on ({0}) occasions
CallStackDataProvider_toolTipAddress=Address
CallStackDataProvider_toolTipState=State
CallStackDataProviderFactory_title=Flame Chart
CallStackDataProviderFactory_descriptionText=Show a call stack over time
Expand Up @@ -3,7 +3,7 @@ Bundle-ManifestVersion: 2
Bundle-Name: %Bundle-Name
Bundle-Vendor: %Bundle-Vendor
Bundle-SymbolicName: org.eclipse.tracecompass.analysis.profiling.ui;singleton:=true
Bundle-Version: 2.1.3.qualifier
Bundle-Version: 3.0.0.qualifier
Bundle-Localization: plugin
Bundle-Activator: org.eclipse.tracecompass.internal.analysis.profiling.ui.Activator
Bundle-ActivationPolicy: lazy
Expand Down
Expand Up @@ -49,7 +49,9 @@
* presentation provider.
*
* @author Patrick Tasse
* @deprecated use output styles in the data provider instead
*/
@Deprecated
public class CallStackPresentationProvider extends TimeGraphPresentationProvider {

/** Number of colors used for call stack events */
Expand Down

0 comments on commit 2efecf8

Please sign in to comment.