blob: 5cb066785a1ecd7840012ad3cea36d9e6573bb5f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2017 Ericsson
*
* 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
*
* Contributors:
* Patrick Tasse - Initial API and implementation
*******************************************************************************/
package org.eclipse.tracecompass.internal.tmf.ui.markers;
import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.RGBA;
import org.eclipse.tracecompass.internal.tmf.core.markers.IMarkerConstants;
import org.eclipse.tracecompass.internal.tmf.core.markers.Marker;
import org.eclipse.tracecompass.internal.tmf.core.markers.Marker.PeriodicMarker;
import org.eclipse.tracecompass.internal.tmf.core.markers.MarkerSegment;
import org.eclipse.tracecompass.internal.tmf.core.markers.MarkerSet;
import org.eclipse.tracecompass.internal.tmf.core.markers.SubMarker;
import org.eclipse.tracecompass.internal.tmf.core.markers.SubMarker.SplitMarker;
import org.eclipse.tracecompass.internal.tmf.core.markers.SubMarker.WeightedMarker;
import org.eclipse.tracecompass.tmf.core.signal.TmfMarkerEventSourceUpdatedSignal;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
import org.eclipse.tracecompass.tmf.core.trace.AbstractTmfTraceAdapterFactory.IDisposableAdapter;
import org.eclipse.tracecompass.tmf.core.trace.ICyclesConverter;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.ui.colors.ColorUtils;
import org.eclipse.tracecompass.tmf.ui.markers.IMarkerReferenceProvider;
import org.eclipse.tracecompass.tmf.ui.markers.PeriodicMarkerEventSource;
import org.eclipse.tracecompass.tmf.ui.markers.PeriodicMarkerEventSource.Reference;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.IMarkerEvent;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.IMarkerEventSource;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.MarkerEvent;
import com.google.common.collect.Lists;
import com.google.common.collect.RangeSet;
/**
* Configurable marker event source.
*/
public class ConfigurableMarkerEventSource implements IMarkerEventSource, IDisposableAdapter {
private static final long NANO_PER_MILLI = 1000000L;
private static final long NANO_PER_MICRO = 1000L;
private static final int MIN_PERIOD = 5; // in units of resolution intervals
private static final int ALPHA = 10;
private static final String COLOR_REGEX = "#[A-Fa-f0-9]{6}"; //$NON-NLS-1$
private List<IConfigurableMarkerEventSource> fMarkerEventSources;
private Map<Marker, RGBA> fColors = new HashMap<>();
private final ITmfTrace fTrace;
/**
* Constructor
*
* @param trace
* the trace
*/
public ConfigurableMarkerEventSource(ITmfTrace trace) {
fMarkerEventSources = new ArrayList<>();
fTrace = trace;
TmfSignalManager.register(this);
}
@Override
public void dispose() {
TmfSignalManager.deregister(this);
}
/**
* Configure the marker source from the specified marker set
*
* @param markerSet
* the marker set, or null to clear the configuration
*/
public void configure(MarkerSet markerSet) {
fMarkerEventSources.clear();
if (markerSet != null) {
for (Marker marker : markerSet.getMarkers()) {
configure(marker);
}
}
}
private void configure(Marker marker) {
if (marker instanceof PeriodicMarker) {
PeriodicMarker periodicMarker = (PeriodicMarker) marker;
String referenceId = periodicMarker.getReferenceId();
Reference baseReference = null;
if (fTrace instanceof IAdaptable && !referenceId.isEmpty()) {
@Nullable IMarkerReferenceProvider adapter = ((IAdaptable) fTrace).getAdapter(IMarkerReferenceProvider.class);
if (adapter != null) {
baseReference = adapter.getReference(referenceId);
}
}
if (baseReference == null) {
baseReference = Reference.ZERO;
}
long rollover = periodicMarker.getRange().hasUpperBound() ? (periodicMarker.getRange().upperEndpoint() - periodicMarker.getRange().lowerEndpoint() + 1) : 0;
RGBA evenColor = getColor(periodicMarker);
RGBA oddColor = getOddColor(evenColor);
double period = convertToNanos(periodicMarker.getPeriod(), periodicMarker.getUnit());
Reference reference = new Reference(baseReference.getTime() + Math.round(convertToNanos(periodicMarker.getOffset(), periodicMarker.getUnit())), baseReference.getIndex());
ConfigurablePeriodicMarkerEventSource markerEventSource = new ConfigurablePeriodicMarkerEventSource(marker, checkNotNull(periodicMarker.getName()), reference, period, rollover, evenColor, oddColor, false, periodicMarker.getRange().lowerEndpoint(), checkNotNull(periodicMarker.getLabel()), periodicMarker.getIndexRange());
fMarkerEventSources.add(markerEventSource);
}
}
private double convertToNanos(double number, String unit) {
if (unit.equalsIgnoreCase(IMarkerConstants.MS)) {
return number * NANO_PER_MILLI;
} else if (unit.equalsIgnoreCase(IMarkerConstants.US)) {
return number * NANO_PER_MICRO;
} else if (unit.equalsIgnoreCase(IMarkerConstants.NS)) {
return number;
} else if (unit.equalsIgnoreCase(IMarkerConstants.CYCLES) &&
fTrace instanceof IAdaptable) {
ICyclesConverter adapter = ((IAdaptable) fTrace).getAdapter(ICyclesConverter.class);
if (adapter != null) {
return adapter.cyclesToNanos((long) number);
}
}
return number;
}
private @NonNull RGBA getColor(Marker marker) {
RGBA color = fColors.get(marker);
if (color == null) {
color = parseColor(marker.getColor());
fColors.put(marker, color);
}
return color;
}
private static @NonNull RGBA getOddColor(RGBA color) {
return new RGBA(color.rgb.red, color.rgb.green, color.rgb.blue, 0);
}
private static @NonNull RGBA parseColor(String color) {
RGB rgb = null;
if (color.matches(COLOR_REGEX)) {
rgb = ColorUtils.fromHexColor(color);
} else {
rgb = ColorUtils.fromX11Color(color);
if (rgb == null) {
rgb = new RGB(0, 0, 0);
}
}
return new RGBA(rgb.red, rgb.green, rgb.blue, ALPHA);
}
@Override
public List<String> getMarkerCategories() {
Set<String> categories = new LinkedHashSet<>();
for (IConfigurableMarkerEventSource source : fMarkerEventSources) {
categories.addAll(source.getMarkerCategories());
getSubMarkerCategories(categories, source.getSubMarkers());
}
return checkNotNull(Lists.newArrayList(categories));
}
private void getSubMarkerCategories(Set<String> categories, List<SubMarker> subMarkers) {
for (SubMarker subMarker : subMarkers) {
categories.add(subMarker.getName());
getSubMarkerCategories(categories, subMarker.getSubMarkers());
if (subMarker instanceof WeightedMarker) {
for (MarkerSegment segment : ((WeightedMarker) subMarker).getSegments()) {
getSubMarkerCategories(categories, segment.getSubMarkers());
}
}
}
}
@Override
public List<IMarkerEvent> getMarkerList(String category, long startTime, long endTime, long resolution, IProgressMonitor monitor) {
return getMarkerList(startTime, endTime, resolution, monitor).stream()
.filter((marker) -> marker.getCategory().equals(category))
.collect(Collectors.toList());
}
@Override
public List<IMarkerEvent> getMarkerList(long startTime, long endTime, long resolution, IProgressMonitor monitor) {
@NonNull List<@NonNull IMarkerEvent> markerList = new ArrayList<>();
for (IConfigurableMarkerEventSource source : fMarkerEventSources) {
long minDuration = resolution * MIN_PERIOD;
if (source.getMaxDuration() > minDuration) {
@NonNull List<@NonNull IMarkerEvent> list = source.getMarkerList(startTime, endTime, resolution, monitor);
for (IMarkerEvent markerEvent : list) {
for (SubMarker subMarker : source.getSubMarkers()) {
getSubMarkerList(subMarker, markerEvent, markerList, startTime, endTime, minDuration);
}
markerList.add(markerEvent);
}
}
}
markerList.sort(Comparator.comparingLong(marker -> marker.getTime()));
return markerList;
}
private void getSubMarkerList(SubMarker subMarker, IMarkerEvent markerEvent, @NonNull List<@NonNull IMarkerEvent> markerList, long startTime, long endTime, long minDuration) {
if (subMarker instanceof SplitMarker) {
getSubMarkerList((SplitMarker) subMarker, markerEvent, markerList, startTime, endTime, minDuration);
} else if (subMarker instanceof WeightedMarker) {
getSubMarkerList((WeightedMarker) subMarker, markerEvent, markerList, startTime, endTime, minDuration);
}
}
private void getSubMarkerList(SplitMarker splitMarker, IMarkerEvent markerEvent, @NonNull List<@NonNull IMarkerEvent> markerList, long startTime, long endTime, long minDuration) {
if (markerEvent.getTime() > endTime || markerEvent.getTime() + markerEvent.getDuration() < startTime) {
return;
}
long lower = splitMarker.getRange().lowerEndpoint();
long upper = splitMarker.getRange().upperEndpoint();
long segments = upper - lower + 1;
long start = markerEvent.getTime();
for (int i = 0; i < segments; i++) {
long end = markerEvent.getTime() + Math.round((double) (i + 1) / segments * markerEvent.getDuration());
long duration = end - start;
long labelIndex = lower + i;
if (end >= startTime && duration > minDuration && splitMarker.getIndexRange().contains(labelIndex)) {
RGBA color = (labelIndex & 1) == 0 ? getColor(splitMarker) : getOddColor(getColor(splitMarker));
IMarkerEvent subMarkerEvent = new MarkerEvent(null, start, end - start, splitMarker.getName(), color, String.format(splitMarker.getLabel(), labelIndex), false);
for (SubMarker subMarker : splitMarker.getSubMarkers()) {
getSubMarkerList(subMarker, subMarkerEvent, markerList, startTime, endTime, minDuration);
}
markerList.add(subMarkerEvent);
}
if (start >= endTime) {
break;
}
start = end;
}
}
private void getSubMarkerList(WeightedMarker weightedMarker, IMarkerEvent markerEvent, @NonNull List<@NonNull IMarkerEvent> markerList, long startTime, long endTime, long minDuration) {
if (markerEvent.getTime() > endTime || markerEvent.getTime() + markerEvent.getDuration() < startTime) {
return;
}
long start = markerEvent.getTime();
long length = 0;
for (int i = 0; i < weightedMarker.getSegments().size(); i++) {
MarkerSegment segment = weightedMarker.getSegments().get(i);
length += segment.getLength();
long end = markerEvent.getTime() + Math.round((length / (double) weightedMarker.getTotalLength()) * markerEvent.getDuration());
long duration = end - start;
if (end >= startTime && duration > minDuration && !segment.getColor().isEmpty()) {
RGBA color = getColor(segment);
IMarkerEvent subMarkerEvent = new MarkerEvent(null, start, end - start, weightedMarker.getName(), color, String.format(segment.getLabel(), i), false);
for (SubMarker subMarker : segment.getSubMarkers()) {
getSubMarkerList(subMarker, subMarkerEvent, markerList, startTime, endTime, minDuration);
}
for (SubMarker subMarker : weightedMarker.getSubMarkers()) {
getSubMarkerList(subMarker, subMarkerEvent, markerList, startTime, endTime, minDuration);
}
markerList.add(subMarkerEvent);
}
if (start >= endTime) {
break;
}
start = end;
}
}
private static interface IConfigurableMarkerEventSource extends IMarkerEventSource {
double getMaxDuration();
public List<SubMarker> getSubMarkers();
}
private class ConfigurablePeriodicMarkerEventSource extends PeriodicMarkerEventSource implements IConfigurableMarkerEventSource {
private final Marker fMarker;
private final long fStartIndex;
private final String fLabel;
private final RangeSet<Long> fIndexRange;
private final double fMaxDuration;
public ConfigurablePeriodicMarkerEventSource(Marker marker, @NonNull String category, @NonNull Reference reference, double period, long rollover, @NonNull RGBA evenColor, @NonNull RGBA oddColor, boolean foreground, long startIndex, @NonNull String label, RangeSet<Long> indexRange) {
super(category, reference, period, rollover, evenColor, oddColor, foreground);
fMarker = marker;
fStartIndex = startIndex;
fLabel = label;
fIndexRange = indexRange;
fMaxDuration = period;
}
@Override
public @NonNull String getMarkerLabel(long index) {
return checkNotNull(String.format(fLabel, fStartIndex + index));
}
@Override
public boolean isApplicable(long index) {
if (fIndexRange != null) {
return fIndexRange.contains(fStartIndex + index);
}
return true;
}
@Override
public double getMaxDuration() {
return fMaxDuration;
}
@Override
public List<SubMarker> getSubMarkers() {
return fMarker.getSubMarkers();
}
}
/**
* A marker event source has been updated
*
* @param signal
* the signal
*/
@TmfSignalHandler
public void markerEventSourceUpdated(final TmfMarkerEventSourceUpdatedSignal signal) {
configure(MarkerUtils.getDefaultMarkerSet());
}
}