| /********************************************************************** |
| * Copyright (c) 2017 Ericsson |
| * |
| * 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.internal.analysis.timing.core.segmentstore; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.NoSuchElementException; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicLong; |
| import java.util.function.Predicate; |
| |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.jdt.annotation.NonNull; |
| import org.eclipse.jdt.annotation.Nullable; |
| import org.eclipse.tracecompass.analysis.timing.core.segmentstore.ISegmentStoreProvider; |
| import org.eclipse.tracecompass.internal.analysis.timing.core.Activator; |
| import org.eclipse.tracecompass.internal.tmf.core.model.AbstractTmfTraceDataProvider; |
| import org.eclipse.tracecompass.internal.tmf.core.model.TmfXyResponseFactory; |
| import org.eclipse.tracecompass.internal.tmf.core.model.filters.IRegexQuery; |
| import org.eclipse.tracecompass.internal.tmf.core.model.xy.TmfTreeXYCompositeDataProvider; |
| import org.eclipse.tracecompass.segmentstore.core.ISegment; |
| import org.eclipse.tracecompass.segmentstore.core.ISegmentStore; |
| import org.eclipse.tracecompass.segmentstore.core.SegmentComparators; |
| import org.eclipse.tracecompass.segmentstore.core.segment.interfaces.INamedSegment; |
| import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule; |
| import org.eclipse.tracecompass.tmf.core.model.CommonStatusMessage; |
| import org.eclipse.tracecompass.tmf.core.model.SeriesModel; |
| import org.eclipse.tracecompass.tmf.core.model.filters.SelectionTimeQueryFilter; |
| import org.eclipse.tracecompass.tmf.core.model.filters.TimeQueryFilter; |
| import org.eclipse.tracecompass.tmf.core.model.timegraph.IFilterProperty; |
| import org.eclipse.tracecompass.tmf.core.model.tree.ITmfTreeDataModel; |
| import org.eclipse.tracecompass.tmf.core.model.tree.ITmfTreeDataProvider; |
| import org.eclipse.tracecompass.tmf.core.model.tree.TmfTreeDataModel; |
| import org.eclipse.tracecompass.tmf.core.model.xy.ITmfTreeXYDataProvider; |
| import org.eclipse.tracecompass.tmf.core.model.xy.ITmfXyModel; |
| import org.eclipse.tracecompass.tmf.core.response.ITmfResponse; |
| import org.eclipse.tracecompass.tmf.core.response.TmfModelResponse; |
| import org.eclipse.tracecompass.tmf.core.segment.ISegmentAspect; |
| import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; |
| import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils; |
| |
| import com.google.common.collect.BiMap; |
| import com.google.common.collect.HashBiMap; |
| import com.google.common.collect.HashMultimap; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableList.Builder; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Maps; |
| import com.google.common.collect.Multimap; |
| import com.google.common.primitives.Doubles; |
| import com.google.common.primitives.Ints; |
| import com.google.common.primitives.Longs; |
| |
| /** |
| * This data provider will return a XY model (model wrapped in a response) based |
| * on a query filter. The model can be used afterwards by any viewer to draw |
| * charts. Model returned is for analysis using SegmentStore |
| * |
| * @author Yonni Chen |
| * @since 3.1 |
| */ |
| public class SegmentStoreScatterDataProvider extends AbstractTmfTraceDataProvider implements ITmfTreeXYDataProvider<TmfTreeDataModel> { |
| |
| /** |
| * Extension point ID. |
| * @since 4.0 |
| */ |
| public static final String ID = "org.eclipse.tracecompass.internal.analysis.timing.core.segmentstore.scatter.dataprovider"; //$NON-NLS-1$ |
| |
| private static final String DEFAULT_CATEGORY = "default"; //$NON-NLS-1$ |
| private static final AtomicLong ENTRY_ID = new AtomicLong(); |
| |
| private final ISegmentStoreProvider fProvider; |
| private final String fId; |
| |
| private final BiMap<Long, String> fIdToType = HashBiMap.create(); |
| private final long fTraceId = ENTRY_ID.getAndIncrement(); |
| |
| private static class CheckSegmentType implements Predicate<ISegment> { |
| |
| private final Set<String> fSelectedTypes; |
| private final String fPrefix; |
| |
| public CheckSegmentType(String prefix, Set<String> selectedTypes) { |
| fSelectedTypes = selectedTypes; |
| fPrefix = prefix; |
| } |
| |
| @Override |
| public boolean test(ISegment segment) { |
| if (!(segment instanceof INamedSegment)) { |
| return fSelectedTypes.contains(fPrefix + DEFAULT_CATEGORY); |
| } |
| return fSelectedTypes.contains(fPrefix + ((INamedSegment) segment).getName()); |
| } |
| } |
| |
| /** |
| * An iterator over a segment store that returns segments from a segment store |
| * only if they do not overlap |
| */ |
| private static class SegmentStoreIterator implements Iterator<@NonNull ISegment> { |
| |
| /* Last segment per category */ |
| private final Map<String, ISegment> fLasts = new HashMap<>(); |
| private @Nullable ISegment fNext = null; |
| private final Iterator<@NonNull ISegment> fIterator; |
| private final long fStartTime; |
| private final long fPixelSize; |
| |
| public SegmentStoreIterator(long startTime, Iterable<@NonNull ISegment> iterableToCompact, long pixelSize) { |
| fStartTime = startTime; |
| fIterator = Objects.requireNonNull(iterableToCompact.iterator()); |
| fPixelSize = Math.max(1, pixelSize); |
| } |
| |
| @Override |
| public @NonNull ISegment next() { |
| /* hasNext implies next != null */ |
| if (hasNext()) { |
| ISegment segment = Objects.requireNonNull(fNext); |
| fLasts.put(getSegmentName(segment), segment); |
| fNext = null; |
| return segment; |
| } |
| throw new NoSuchElementException(); |
| } |
| |
| @Override |
| public boolean hasNext() { |
| if (fLasts.isEmpty() && fNext == null) { |
| // iteration hasn't started yet. |
| if (fIterator.hasNext()) { |
| ISegment segment = fIterator.next(); |
| if (segment.getStart() >= fStartTime) { |
| fNext = segment; |
| } |
| } else { |
| return false; |
| } |
| } |
| |
| // clear warning in calling overlaps below. |
| while (fNext == null && fIterator.hasNext()) { |
| ISegment tmp = fIterator.next(); |
| ISegment last = fLasts.get(getSegmentName(tmp)); |
| if (tmp.getStart() >= fStartTime && !overlaps(last, tmp)) { |
| fNext = tmp; |
| } |
| } |
| return fNext != null; |
| } |
| |
| /* |
| * Returns whether two segments overlaps or not by comparing start/end of last |
| * and start/end of next. |
| */ |
| private boolean overlaps(@Nullable ISegment last, ISegment next) { |
| if (last == null) { |
| return false; |
| } |
| long timePerPix = fPixelSize; |
| final long start = last.getStart(); |
| final long pixelStart = fStartTime; |
| final long pixelDuration = start - pixelStart; |
| long startPixBoundL = pixelDuration / timePerPix * timePerPix + pixelStart; |
| long startPixBoundR = startPixBoundL + timePerPix; |
| final long currentStart = next.getStart(); |
| if (currentStart >= startPixBoundL && currentStart <= startPixBoundR) { |
| long length = last.getLength(); |
| long lengthNext = next.getLength(); |
| long lengthLow = length / timePerPix * timePerPix; |
| long lengthHigh = lengthLow + timePerPix; |
| return (lengthNext >= lengthLow && lengthNext <= lengthHigh); |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * Create an instance of {@link SegmentStoreScatterDataProvider} for a given |
| * analysis ID. Returns a null instance if the ISegmentStoreProvider is null. If |
| * the provider is an instance of {@link IAnalysisModule}, analysis is also |
| * scheduled. |
| * <p> |
| * If the trace has multiple analysis modules with the same secondary ID, |
| * <code>null</code> is returned so the caller can try to make a |
| * {@link TmfTreeXYCompositeDataProvider} for all the traces instead |
| * |
| * @param trace |
| * A trace on which we are interested to fetch a model |
| * @param secondaryId |
| * The ID of the analysis to use for this provider |
| * @return An instance of SegmentStoreDataProvider. Returns a null if the |
| * ISegmentStoreProvider is null. |
| * @since 4.0 |
| */ |
| public static @Nullable ITmfTreeDataProvider<? extends ITmfTreeDataModel> create(ITmfTrace trace, String secondaryId) { |
| // The trace can be an experiment, so we need to know if there are multiple analysis modules with the same ID |
| Iterable<ISegmentStoreProvider> modules = TmfTraceUtils.getAnalysisModulesOfClass(trace, ISegmentStoreProvider.class); |
| Iterable<ISegmentStoreProvider> filteredModules = Iterables.filter(modules, m -> ((IAnalysisModule) m).getId().equals(secondaryId)); |
| Iterator<ISegmentStoreProvider> iterator = filteredModules.iterator(); |
| if (iterator.hasNext()) { |
| ISegmentStoreProvider module = iterator.next(); |
| if (iterator.hasNext()) { |
| // More than one module, must be an experiment, return null so the factory can try with individual traces |
| return null; |
| } |
| ((IAnalysisModule) module).schedule(); |
| return new SegmentStoreScatterDataProvider(trace, module, secondaryId); |
| } |
| return null; |
| } |
| |
| /** |
| * Constructor |
| */ |
| private SegmentStoreScatterDataProvider(ITmfTrace trace, ISegmentStoreProvider provider, String secondaryId) { |
| super(trace); |
| fProvider = provider; |
| fId = ID + ':' + secondaryId; |
| } |
| |
| /** |
| * @since 4.0 |
| */ |
| @Override |
| public TmfModelResponse<List<TmfTreeDataModel>> fetchTree(TimeQueryFilter filter, @Nullable IProgressMonitor monitor) { |
| ISegmentStoreProvider provider = fProvider; |
| ISegmentStore<ISegment> segStore = provider.getSegmentStore(); |
| |
| if (segStore == null) { |
| return new TmfModelResponse<>(null, ITmfResponse.Status.FAILED, CommonStatusMessage.ANALYSIS_INITIALIZATION_FAILED); |
| } |
| long start = filter.getStart(); |
| long end = filter.getEnd(); |
| final Iterable<ISegment> intersectingElements = Iterables.filter(segStore.getIntersectingElements(start, end), s -> s.getStart() >= start); |
| |
| Set<String> segmentTypes = new HashSet<>(); |
| IAnalysisModule module = (provider instanceof IAnalysisModule) ? (IAnalysisModule) provider : null; |
| boolean complete = module == null ? true : module.isQueryable(filter.getEnd()); |
| |
| // Create the list of segment types that will each create a series |
| for (INamedSegment segment : Iterables.filter(intersectingElements, INamedSegment.class)) { |
| if (monitor != null && monitor.isCanceled()) { |
| return new TmfModelResponse<>(null, ITmfResponse.Status.CANCELLED, CommonStatusMessage.TASK_CANCELLED); |
| } |
| segmentTypes.add(segment.getName()); |
| } |
| |
| Builder<TmfTreeDataModel> nodes = new ImmutableList.Builder<>(); |
| nodes.add(new TmfTreeDataModel(fTraceId, -1, String.valueOf(getTrace().getName()))); |
| |
| // There are segments, but no type, probably not named segments, so just add a category |
| if (segmentTypes.isEmpty() && intersectingElements.iterator().hasNext()) { |
| long seriesId = getUniqueId(DEFAULT_CATEGORY); |
| nodes.add(new TmfTreeDataModel(seriesId, fTraceId, DEFAULT_CATEGORY)); |
| } |
| for (String seriesName : segmentTypes) { |
| long seriesId = getUniqueId(seriesName); |
| nodes.add(new TmfTreeDataModel(seriesId, fTraceId, seriesName)); |
| } |
| |
| return new TmfModelResponse<>(nodes.build(), complete ? ITmfResponse.Status.COMPLETED : ITmfResponse.Status.RUNNING, |
| complete ? CommonStatusMessage.COMPLETED: CommonStatusMessage.RUNNING); |
| } |
| |
| @Override |
| public TmfModelResponse<ITmfXyModel> fetchXY(TimeQueryFilter filter, @Nullable IProgressMonitor monitor) { |
| ISegmentStoreProvider provider = fProvider; |
| |
| // FIXME: There is no way to get the running status of a segment store analysis, |
| // so we need to wait for completion before going forward, to be sure the |
| // segment store is available. |
| if ((provider instanceof IAnalysisModule) && !((IAnalysisModule) provider).waitForCompletion()) { |
| return TmfXyResponseFactory.createFailedResponse(CommonStatusMessage.ANALYSIS_INITIALIZATION_FAILED); |
| } |
| |
| final ISegmentStore<ISegment> segStore = provider.getSegmentStore(); |
| if (segStore == null) { |
| return TmfXyResponseFactory.createFailedResponse(Objects.requireNonNull(Messages.SegmentStoreDataProvider_SegmentNotAvailable)); |
| } |
| |
| Map<@NonNull Integer, @NonNull Predicate<@NonNull Multimap<@NonNull String, @NonNull String>>> predicates = new HashMap<>(); |
| if (filter instanceof IRegexQuery) { |
| IRegexQuery regexFilter = (IRegexQuery) filter; |
| predicates.putAll(computeRegexPredicate(regexFilter)); |
| } |
| |
| long start = filter.getStart(); |
| long end = filter.getEnd(); |
| // The types in the tree do not contain the trace name for sake of readability, but |
| // the name of the series in XY model should be unique per trace |
| String prefix = getTrace().getName() + '/'; |
| Map<String, Series> types = initTypes(prefix, filter); |
| if (types.isEmpty()) { |
| // this would return an empty map even if we did the queries. |
| return TmfXyResponseFactory.create(Objects.requireNonNull(Messages.SegmentStoreScatterGraphViewer_title), Collections.emptyMap(), true); |
| } |
| long pixelSize = Math.max(1, (end - start) / filter.getTimesRequested().length); |
| final Iterable<ISegment> intersectingElements = Iterables.filter(segStore.getIntersectingElements(start, end, SegmentComparators.INTERVAL_START_COMPARATOR), (segment) -> { |
| CheckSegmentType cs = new CheckSegmentType(prefix, types.keySet()); |
| return cs.test(segment); |
| }); |
| final Iterable<ISegment> displayData = compactList(start, intersectingElements, pixelSize); |
| |
| IAnalysisModule module = (fProvider instanceof IAnalysisModule) ? (IAnalysisModule) fProvider : null; |
| boolean complete = module == null ? true : module.isQueryable(filter.getEnd()); |
| |
| // For each visible segments, add start time to x value and duration for y value |
| for (ISegment segment : displayData) { |
| if (monitor != null && monitor.isCanceled()) { |
| return TmfXyResponseFactory.createCancelledResponse(CommonStatusMessage.TASK_CANCELLED); |
| } |
| |
| String name = prefix + getSegmentName(segment); |
| Series thisSeries = types.get(name); |
| if (thisSeries == null) { |
| // This shouldn't be, log an error and continue |
| Activator.getInstance().logError("Series " + thisSeries + " should exist"); //$NON-NLS-1$//$NON-NLS-2$ |
| continue; |
| } |
| |
| addPoint(thisSeries, segment, predicates, monitor); |
| } |
| |
| return TmfXyResponseFactory.create(Objects.requireNonNull(Messages.SegmentStoreScatterGraphViewer_title), |
| Maps.transformValues(types, Series::build), complete); |
| } |
| |
| private static String getSegmentName(ISegment segment) { |
| return (segment instanceof INamedSegment) ? ((INamedSegment) segment).getName() : DEFAULT_CATEGORY; |
| } |
| |
| /** |
| * Filter the time graph state and add it to the state list |
| * |
| * @param stateList |
| * The timegraph state list |
| * @param timeGraphState |
| * The current timegraph state |
| * @param key |
| * The timegraph entry model id |
| * @param predicates |
| * The predicates used to filter the timegraph state. It is a map of |
| * predicate by property. The value of the property is an integer |
| * representing a bitmask associated to that property. The status of |
| * each property will be set for the timegraph state according to the |
| * associated predicate test result. |
| * @param monitor |
| * The progress monitor |
| */ |
| private void addPoint(Series series, ISegment segment, Map<Integer, Predicate<Multimap<String, String>>> predicates, @Nullable IProgressMonitor monitor) { |
| |
| if (!predicates.isEmpty()) { |
| |
| // Get the filter external input data |
| Multimap<@NonNull String, @NonNull String> input = getFilterInput(segment); |
| |
| // Test each predicates and set the status of the property associated to the |
| // predicate |
| int mask = 0; |
| for (Map.Entry<Integer, Predicate<Multimap<String, String>>> mapEntry : predicates.entrySet()) { |
| Predicate<Multimap<String, String>> value = Objects.requireNonNull(mapEntry.getValue()); |
| boolean status = value.test(input); |
| Integer property = Objects.requireNonNull(mapEntry.getKey()); |
| if (status && property != IFilterProperty.DIMMED) { |
| mask |= property; |
| } else if (!status && property == IFilterProperty.DIMMED) { |
| mask |= IFilterProperty.DIMMED; |
| } else if (!status && property == IFilterProperty.EXCLUDE) { |
| mask |= IFilterProperty.EXCLUDE; |
| } else if (status){ |
| mask |= property; |
| } |
| } |
| series.addPoint(segment.getStart(), segment.getLength(), mask); |
| } else { |
| series.addPoint(segment.getStart(), segment.getLength(), 0); |
| } |
| } |
| |
| private Multimap<String, String> getFilterInput(ISegment segment) { |
| Multimap<String, String> map = HashMultimap.create(); |
| for(ISegmentAspect aspect : fProvider.getSegmentAspects()) { |
| Object resolve = aspect.resolve(segment); |
| if (resolve != null) { |
| map.put(aspect.getName(), String.valueOf(resolve)); |
| } |
| } |
| return map; |
| } |
| |
| private static class Series { |
| private final long fId; |
| private final String fName; |
| private final List<Long> fXValues = new ArrayList<>(); |
| private final List<Double> fYValues = new ArrayList<>(); |
| private final List<Integer> fProperties = new ArrayList<>(); |
| |
| public Series(long id, String name) { |
| fId = id; |
| fName = name; |
| } |
| |
| public void addPoint(long x, double y, int properties) { |
| fXValues.add(x); |
| fYValues.add(y); |
| fProperties.add(properties); |
| } |
| |
| public SeriesModel build() { |
| return new SeriesModel(getId(), getName(), Longs.toArray(fXValues), Doubles.toArray(fYValues), Ints.toArray(fProperties)); |
| } |
| |
| private long getId() { |
| return fId; |
| } |
| |
| private String getName() { |
| return fName; |
| } |
| } |
| |
| private Map<String, Series> initTypes(String prefix, TimeQueryFilter filter) { |
| if (!(filter instanceof SelectionTimeQueryFilter)) { |
| return Collections.emptyMap(); |
| } |
| |
| Map<String, Series> segmentTypes = new HashMap<>(); |
| for (Long id : ((SelectionTimeQueryFilter) filter).getSelectedItems()) { |
| String string = fIdToType.get(id); |
| if (string == null) { |
| continue; |
| } |
| |
| String name = prefix + string; |
| segmentTypes.put(name, new Series(id, name)); |
| } |
| return segmentTypes; |
| } |
| |
| private long getUniqueId(String name) { |
| return fIdToType.inverse().computeIfAbsent(name, n -> ENTRY_ID.getAndIncrement()); |
| } |
| |
| private static Iterable<ISegment> compactList(final long startTime, final Iterable<@NonNull ISegment> iterableToCompact, long pixelSize) { |
| return () -> new SegmentStoreIterator(startTime, iterableToCompact, pixelSize); |
| } |
| |
| /** |
| * @since 4.0 |
| */ |
| @Override |
| public String getId() { |
| return fId; |
| } |
| |
| } |