blob: efdbd2c7089fe8fda0b5ddbab21adcefa8052cdb [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2021 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
*******************************************************************************/
package org.eclipse.tracecompass.internal.tmf.core.annotations;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.commons.lang3.math.Fraction;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.internal.provisional.tmf.core.model.annotations.Annotation;
import org.eclipse.tracecompass.internal.provisional.tmf.core.model.annotations.AnnotationCategoriesModel;
import org.eclipse.tracecompass.internal.provisional.tmf.core.model.annotations.AnnotationModel;
import org.eclipse.tracecompass.internal.provisional.tmf.core.model.annotations.IOutputAnnotationProvider;
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.SubMarker;
import org.eclipse.tracecompass.tmf.core.dataprovider.DataProviderParameterUtils;
import org.eclipse.tracecompass.tmf.core.markers.ITimeReference;
import org.eclipse.tracecompass.tmf.core.markers.TimeReference;
import org.eclipse.tracecompass.tmf.core.model.OutputElementStyle;
import org.eclipse.tracecompass.tmf.core.model.StyleProperties;
import org.eclipse.tracecompass.tmf.core.presentation.RGBAColor;
import org.eclipse.tracecompass.tmf.core.response.ITmfResponse.Status;
import org.eclipse.tracecompass.tmf.core.response.TmfModelResponse;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.RangeSet;
/**
* Data provider for Periodic Markers (frames)
*
* @author Matthew Khouzam
* @since 6.3
*/
public class PeriodicAnnotationProvider implements IOutputAnnotationProvider {
private static final TmfModelResponse<AnnotationModel> EMPTY_MODEL_RESPONSE = new TmfModelResponse<>(new AnnotationModel(Collections.emptyMap()), Status.COMPLETED, ""); //$NON-NLS-1$
private final String fCategory;
private final double fPeriod;
private final long fPeriodInteger;
private @Nullable Fraction fPeriodFraction;
private final long fRollover;
private final RGBAColor fColor1;
private final @Nullable RGBAColor fColor2;
private ITimeReference fReference;
/**
* TODO: remove later
*/
private final Marker fMarker;
/**
* Creates a data provider for periodic markers. They alternate in color and
* can have sub markers.
*
* @param category
* the category marker, to reference submarkers and such
* @param index
* the index (number) of the reference frame
* @param time
* the time of the reference frame frame - the time of the frame refered by index.
* @param period
* the period of the markers, in ns
* @param rollover
* the rollover for the index. If it is 100, the indexes will
* increment 98->99->0-> ...
* @param color1
* the even color
* @param color2
* the odd color
*/
public PeriodicAnnotationProvider(Marker category, long index, long time, double period, long rollover, RGBAColor color1, @Nullable RGBAColor color2) {
fCategory = String.valueOf(category.getName());
fMarker = category;
fReference = new TimeReference(time, index);
fColor1 = color1;
fColor2 = color2;
fPeriod = period;
fPeriodInteger = (long) period;
try {
fPeriodFraction = Fraction.getFraction(fPeriod - fPeriodInteger);
} catch (ArithmeticException e) {
/* can't convert to fraction, use floating-point arithmetic */
fPeriodFraction = null;
}
fRollover = rollover;
}
/**
* Creates a data provider for periodic markers. They alternate in color and
* can have sub markers.
*
* @param category
* the category Name
* @param index
* the index (number) of the starting frame
* @param time
* the time of the starting frame
* @param period
* the period of the markers, in ns
* @param rollover
* the rollover for the index. If it is 100, the indexes will
* increment 98->99->0-> ...
* @param color1
* the even color
* @param color2
* the odd color
*/
public PeriodicAnnotationProvider(String category, long index, long time, double period, long rollover, RGBAColor color1, @Nullable RGBAColor color2) {
this(new Marker(category, "black") {}, index, time, period, rollover, color1, color2); //$NON-NLS-1$
}
/*
* Adjust to a reference that is closer to the start time, to avoid rounding
* errors in floating point calculations with large numbers.
*/
private ITimeReference adjustReference(ITimeReference baseReference, long time) {
long offsetIndex = (long) ((time - baseReference.getTime()) / fPeriod);
long offsetTime = 0;
Fraction fraction = fPeriodFraction;
if (fraction != null) {
/*
* If period = int num/den, find an offset index that is an exact
* multiple of den and calculate index * period = (index * int) +
* (index / den * num), all exact calculations.
*/
offsetIndex = offsetIndex - offsetIndex % fraction.getDenominator();
offsetTime = offsetIndex * fPeriodInteger + offsetIndex / fraction.getDenominator() * fraction.getNumerator();
} else {
/*
* Couldn't compute fractional part as fraction, use simple
* multiplication but with possible rounding error.
*/
offsetTime = Math.round(offsetIndex * fPeriod);
}
return new TimeReference(baseReference.getTime() + offsetTime, baseReference.getIndex() + offsetIndex);
}
@Override
public @NonNull TmfModelResponse<@NonNull AnnotationCategoriesModel> fetchAnnotationCategories(@NonNull Map<@NonNull String, @NonNull Object> fetchParameters, @Nullable IProgressMonitor monitor) {
List<@NonNull String> categories = new ArrayList<>();
categories.add(fCategory);
getCategories(categories, getMarker());
/*
* We can have different sub-markers of the same category, we need them
* to be distinct
*/
categories = categories.stream().distinct().collect(Collectors.toList());
return new TmfModelResponse<>(
new AnnotationCategoriesModel(categories), Status.COMPLETED, ""); //$NON-NLS-1$
}
private void getCategories(List<@NonNull String> categories, Marker marker) {
for (SubMarker sm : marker.getSubMarkers()) {
categories.add(String.valueOf(sm.getName()));
getCategories(categories, sm);
}
}
@Override
public @NonNull TmfModelResponse<@NonNull AnnotationModel> fetchAnnotations(@NonNull Map<@NonNull String, @NonNull Object> fetchParameters, @Nullable IProgressMonitor monitor) {
List<Long> times = DataProviderParameterUtils.extractTimeRequested(fetchParameters);
if (times == null) {
return EMPTY_MODEL_RESPONSE;
}
int size = times.size() - 1;
long startTime = times.get(0);
long endTime = times.get(size);
long resolution = (endTime - startTime) / (size);
if (startTime > endTime) {
return EMPTY_MODEL_RESPONSE;
}
long step = Math.max(Math.round(fPeriod), resolution);
OutputElementStyle[] styles = new OutputElementStyle[2];
styles[0] = generateOutputElementStyle(fColor1);
RGBAColor color2 = fColor2;
styles[1] = color2 == null ? styles[0] : generateOutputElementStyle(color2);
Collection<Annotation> annotations = new ArrayList<>();
/* Subtract 1.5 periods to ensure previous marker is included */
long time = startTime - Math.max(Math.round(1.5 * fPeriod), resolution);
ITimeReference reference = adjustReference(fReference, time);
Annotation annotation = null;
while (true) {
long index = Math.round((time - reference.getTime()) / fPeriod) + reference.getIndex();
long markerTime = Math.round((index - reference.getIndex()) * fPeriod) + reference.getTime();
long duration = (fColor2 == null) ? 0 : Math.round((index + 1 - reference.getIndex()) * fPeriod) + reference.getTime() - markerTime;
long labelIndex = index;
if (fRollover != 0) {
labelIndex %= fRollover;
if (labelIndex < 0) {
labelIndex += fRollover;
}
}
/* Add previous marker if current is visible */
if ((markerTime >= startTime || markerTime + duration > startTime) && annotation != null) {
annotations.add(annotation);
}
if (isApplicable(labelIndex)) {
OutputElementStyle style = Objects.requireNonNull((index % 2) == 0 ? styles[0] : styles[1]);
annotation = new Annotation(markerTime, duration, -1, String.valueOf(labelIndex), style);
} else {
annotation = null;
}
if (markerTime > endTime || (monitor != null && monitor.isCanceled())) {
if (annotation != null && isApplicable(labelIndex)) {
/* The next marker out of range is included */
annotations.add(annotation);
}
break;
}
time += step;
}
Map<String, Collection<Annotation>> model = new HashMap<>();
model.put(fCategory, annotations);
return new TmfModelResponse<>(new AnnotationModel(model), Status.COMPLETED, ""); //$NON-NLS-1$
}
private static OutputElementStyle generateOutputElementStyle(RGBAColor color) {
String colorString = color.toString().substring(0, 7);
return new OutputElementStyle(null,
ImmutableMap.of(StyleProperties.STYLE_NAME, colorString,
StyleProperties.COLOR, colorString,
StyleProperties.OPACITY, (float) (color.getAlpha() / 255.0)));
}
/**
* Period
*
* @return period in ns
*/
public double getPeriod() {
return fPeriod;
}
/**
* Get the marker model
*
* @return the marker that this annotation provider is modeled on
*/
public Marker getMarker() {
return fMarker;
}
/**
* Get the base index (starting index) for a marker
*
* @return the base index
*/
public long getBaseIndex() {
Marker marker = getMarker();
if (marker instanceof PeriodicMarker) {
return ((PeriodicMarker) marker).getOffset();
}
return 0;
}
/**
* Is this index applicable?
*
* @param index
* the index to look at
* @return true if it applies
*/
public boolean isApplicable(long index) {
Marker marker = getMarker();
if (marker instanceof PeriodicMarker) {
PeriodicMarker periodicMarker = (PeriodicMarker) marker;
RangeSet<@Nullable Long> indexRange = periodicMarker.getIndexRange();
if (indexRange != null) {
return indexRange.contains(periodicMarker.getOffset() + index);
}
}
return true;
}
}