| /******************************************************************************* |
| * Copyright (c) 2022 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: |
| * Kyrollos Bekhet - Initial API and implementation |
| ******************************************************************************/ |
| |
| package org.eclipse.tracecompass.internal.analysis.timing.core.segmentstore; |
| |
| import java.text.DecimalFormat; |
| import java.text.Format; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Objects; |
| import java.util.concurrent.atomic.AtomicLong; |
| import java.util.function.Predicate; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import java.util.regex.Pattern; |
| |
| import org.apache.commons.lang3.StringUtils; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.jdt.annotation.Nullable; |
| import org.eclipse.tracecompass.analysis.timing.core.segmentstore.ISegmentStoreProvider; |
| import org.eclipse.tracecompass.common.core.NonNullUtils; |
| import org.eclipse.tracecompass.common.core.log.TraceCompassLog; |
| import org.eclipse.tracecompass.common.core.log.TraceCompassLogUtils; |
| import org.eclipse.tracecompass.common.core.log.TraceCompassLogUtils.FlowScopeLog; |
| import org.eclipse.tracecompass.common.core.log.TraceCompassLogUtils.FlowScopeLogBuilder; |
| import org.eclipse.tracecompass.internal.provisional.tmf.core.model.filters.VirtualTableQueryFilter; |
| import org.eclipse.tracecompass.internal.provisional.tmf.core.model.table.ITmfVirtualTableDataProvider; |
| import org.eclipse.tracecompass.internal.provisional.tmf.core.model.table.ITmfVirtualTableModel; |
| import org.eclipse.tracecompass.internal.provisional.tmf.core.model.table.TmfVirtualTableModel; |
| import org.eclipse.tracecompass.internal.provisional.tmf.core.model.table.VirtualTableCell; |
| import org.eclipse.tracecompass.internal.tmf.core.model.AbstractTmfTraceDataProvider; |
| import org.eclipse.tracecompass.internal.tmf.core.model.filters.FetchParametersUtils; |
| 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.tmf.core.TmfStrings; |
| import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule; |
| import org.eclipse.tracecompass.tmf.core.dataprovider.DataProviderParameterUtils; |
| import org.eclipse.tracecompass.tmf.core.model.CommonStatusMessage; |
| import org.eclipse.tracecompass.tmf.core.model.tree.TmfTreeDataModel; |
| import org.eclipse.tracecompass.tmf.core.model.CoreFilterProperty; |
| import org.eclipse.tracecompass.tmf.core.model.tree.TmfTreeModel; |
| 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.segment.SegmentDurationAspect; |
| import org.eclipse.tracecompass.tmf.core.segment.SegmentEndTimeAspect; |
| import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp; |
| import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; |
| |
| import com.google.common.collect.BiMap; |
| import com.google.common.collect.HashBiMap; |
| |
| /** |
| * This data provider will return a virtual table model (wrapped in a response) |
| * Based on a virtual table query filter. Model returned is for segment store |
| * tables to do analysis. |
| * |
| * @author: Kyrollos Bekhet |
| */ |
| |
| public class SegmentStoreTableDataProvider extends AbstractTmfTraceDataProvider implements ITmfVirtualTableDataProvider<TmfTreeDataModel, SegmentStoreTableLine> { |
| /** |
| * |
| * A simple class to create checkpoints to index the segments of a segment |
| * store |
| */ |
| private static class SegmentStoreIndex { |
| private long fCounter; |
| private long fStartTimestamp; |
| private long fLength; |
| |
| public SegmentStoreIndex(long startTimeStamp, long counter, long length) { |
| fStartTimestamp = startTimeStamp; |
| fCounter = counter; |
| fLength = length; |
| } |
| |
| public long getStartTimestamp() { |
| return fStartTimestamp; |
| } |
| |
| public long getCounter() { |
| return fCounter; |
| } |
| |
| public long getLength() { |
| return fLength; |
| } |
| } |
| |
| /** |
| * A predicate implementation that is used to evaluate if a segment is the |
| * first of the checkpoint. |
| * |
| * @author Kyrollos Bekhet. |
| * |
| */ |
| private static class SegmentPredicate implements Predicate<ISegment> { |
| private final long fStartTime; |
| private long fCount; |
| private long fLength; |
| private boolean fDurationComparator; |
| |
| public SegmentPredicate(SegmentStoreIndex segmentIndex, String aspectName) { |
| fStartTime = segmentIndex.getStartTimestamp(); |
| fCount = segmentIndex.getCounter(); |
| fLength = segmentIndex.getLength(); |
| fDurationComparator = aspectName.equals(SegmentDurationAspect.SEGMENT_DURATION_ASPECT.getName()); |
| } |
| |
| @Override |
| public boolean test(ISegment segment) { |
| if (isDurationValid(segment.getLength())) { |
| if (segment.getStart() > fStartTime) { |
| return true; |
| } |
| if (segment.getStart() == fStartTime) { |
| if (fCount == 0) { |
| return true; |
| } |
| fCount--; |
| } |
| } |
| return false; |
| } |
| |
| private boolean isDurationValid(long segmentLength) { |
| if (!fDurationComparator) { |
| return true; |
| } |
| if (segmentLength == fLength) { |
| return true; |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * A placeholder class to wrap a segment and its rank. |
| * |
| * @author Kyrollos Bekhet |
| * |
| */ |
| private static class WrappedSegment { |
| private ISegment fSegment; |
| private long fRank; |
| |
| public WrappedSegment(ISegment segment, long rank) { |
| fSegment = segment; |
| fRank = rank; |
| } |
| |
| public ISegment getOriginalSegment() { |
| return fSegment; |
| } |
| |
| public long getRank() { |
| return fRank; |
| } |
| } |
| |
| private enum Direction { |
| /** Search next */ |
| NEXT, |
| /** Search previous */ |
| PREVIOUS |
| } |
| |
| /** |
| * A wrapper class to encapsulate the indexes with the comparator that was |
| * used to create the indexes. |
| * |
| * @author Kyrollos Bekhet |
| * |
| */ |
| private class SegmentIndexesComparatorWrapper { |
| private List<SegmentStoreIndex> fIndexes; |
| private Comparator<ISegment> fComparator; |
| private String fAspectName; |
| |
| @SuppressWarnings("null") |
| public SegmentIndexesComparatorWrapper() { |
| fComparator = SegmentComparators.INTERVAL_START_COMPARATOR.thenComparing(Comparator.comparingLong(ISegment::getLength)); |
| fIndexes = new ArrayList<>(); |
| fAspectName = StringUtils.EMPTY; |
| } |
| |
| public SegmentIndexesComparatorWrapper(List<SegmentStoreIndex> indexes, Comparator<ISegment> comparator, String aspectName) { |
| fIndexes = indexes; |
| fComparator = comparator; |
| fAspectName = aspectName; |
| } |
| |
| public List<SegmentStoreIndex> getIndexes() { |
| return fIndexes; |
| } |
| |
| public Comparator<ISegment> getComparator() { |
| return fComparator; |
| } |
| |
| public String getAspectName() { |
| return fAspectName; |
| } |
| } |
| |
| /** |
| * The id of the data provider |
| */ |
| public static final String ID = "org.eclipse.tracecompass.analysis.timing.core.segmentstore.SegmentStoreTableDataProvider"; //$NON-NLS-1$ |
| private static final AtomicLong fAtomicLong = new AtomicLong(); |
| private static BiMap<ISegmentAspect, Long> fAspectToIdMap = HashBiMap.create(); |
| private static final Format FORMATTER = new DecimalFormat("###,###.##"); //$NON-NLS-1$ |
| private static final int STEP = 1000; |
| private static final Logger LOGGER = TraceCompassLog.getLogger(SegmentStoreTableDataProvider.class); |
| private static final String TABLE_SEARCH_EXPRESSION_KEY = "table_search_expressions"; //$NON-NLS-1$ |
| private static final String TABLE_SEARCH_DIRECTION_KEY = "table_search_direction"; //$NON-NLS-1$ |
| private static final String TABLE_COMPARATOR_EXPRESSION_KEY = "table_comparator_expression"; //$NON-NLS-1$ |
| |
| private Map<Long, SegmentIndexesComparatorWrapper> fAllIndexes; |
| private SegmentIndexesComparatorWrapper fDefaultWrapper; |
| private final String fId; |
| private @Nullable ISegmentStoreProvider fSegmentProvider = null; |
| private boolean fIsFirstAspect; |
| private final int fSegmentStoreSize; |
| |
| /** |
| * Constructor |
| * |
| * @param trace |
| * A trace on which we are interested to fetch a segment store |
| * table model. |
| * @param segmentProvider |
| * The segment provider that contains the data and from which the |
| * data will be fetched. |
| * @param analysisId |
| * The analysis identifier. |
| */ |
| public SegmentStoreTableDataProvider(ITmfTrace trace, ISegmentStoreProvider segmentProvider, String analysisId) { |
| super(trace); |
| TraceCompassLogUtils.traceObjectCreation(LOGGER, Level.FINE, this); |
| fId = analysisId; |
| fIsFirstAspect = true; |
| fAllIndexes = new HashMap<>(); |
| fDefaultWrapper = new SegmentIndexesComparatorWrapper(); |
| ISegmentStore<ISegment> segmentStore = segmentProvider.getSegmentStore(); |
| if (segmentStore == null && segmentProvider instanceof IAnalysisModule) { |
| ((IAnalysisModule) segmentProvider).schedule(); |
| ((IAnalysisModule) segmentProvider).waitForCompletion(); |
| segmentStore = segmentProvider.getSegmentStore(); |
| } |
| fSegmentProvider = segmentProvider; |
| fSegmentStoreSize = segmentStore != null? segmentStore.size() : 0; |
| } |
| |
| @Override |
| public void dispose() { |
| TraceCompassLogUtils.traceObjectDestruction(LOGGER, Level.FINE, this, 10); |
| } |
| |
| /** |
| * Build the indexes which will act like checkpoints for the data provider. |
| * |
| * @param id |
| * the id of the aspect in the {@link fAspectToIdMap}. |
| * @param comparator |
| * The comparator used to build the indexes array |
| * @param aspectName |
| * The name of the aspect. |
| */ |
| private void buildIndex(long id, Comparator<ISegment> comparator, String aspectName) { |
| if (fAllIndexes.containsKey(id)) { |
| return; |
| } |
| if (fSegmentProvider != null) { |
| ISegmentStore<ISegment> segStore = fSegmentProvider.getSegmentStore(); |
| if (segStore != null) { |
| synchronized (fAllIndexes) { |
| try (FlowScopeLog scope = new FlowScopeLogBuilder(LOGGER, Level.FINE, "SegmentStoreTableDataProvider#buildIndex.buildingIndexes").build()) { //$NON-NLS-1$ |
| TraceCompassLogUtils.traceObjectCreation(LOGGER, Level.FINE, fAllIndexes); |
| Iterable<ISegment> sortedSegmentStore = segStore.iterator(comparator); |
| List<SegmentStoreIndex> indexes = getIndexes(sortedSegmentStore); |
| if (fIsFirstAspect) { |
| fDefaultWrapper = new SegmentIndexesComparatorWrapper(indexes, comparator, aspectName); |
| fIsFirstAspect = false; |
| fAllIndexes.put(id, fDefaultWrapper); |
| } else { |
| fAllIndexes.put(id, new SegmentIndexesComparatorWrapper(indexes, comparator, aspectName)); |
| } |
| } catch (Exception ex) { |
| TraceCompassLogUtils.traceInstant(LOGGER, Level.SEVERE, "error build index", ex.getMessage()); //$NON-NLS-1$ |
| } finally { |
| TraceCompassLogUtils.traceObjectDestruction(LOGGER, Level.FINE, fAllIndexes); |
| } |
| } |
| } |
| } |
| } |
| |
| private static List<SegmentStoreIndex> getIndexes(Iterable<ISegment> segmentStore) { |
| long counter = 0; |
| long i = 0; |
| long previousTimestamp = Long.MAX_VALUE; |
| List<SegmentStoreIndex> indexes = new ArrayList<>(); |
| for (ISegment segment : segmentStore) { |
| if (segment.getStart() == previousTimestamp) { |
| counter++; |
| } else { |
| previousTimestamp = segment.getStart(); |
| counter = 0; |
| } |
| if (i % STEP == 0) { |
| indexes.add(new SegmentStoreIndex(segment.getStart(), counter, segment.getLength())); |
| } |
| i++; |
| } |
| return indexes; |
| } |
| |
| @Override |
| public String getId() { |
| return fId; |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| public TmfModelResponse<TmfTreeModel<TmfTreeDataModel>> fetchTree(Map<String, Object> fetchParameters, @Nullable IProgressMonitor monitor) { |
| List<TmfTreeDataModel> model = new ArrayList<>(); |
| for (final ISegmentAspect aspect : ISegmentStoreProvider.getBaseSegmentAspects()) { |
| synchronized (fAspectToIdMap) { |
| long id = fAspectToIdMap.computeIfAbsent(aspect, a -> fAtomicLong.getAndIncrement()); |
| Comparator<ISegment> comparator = (Comparator<ISegment>) aspect.getComparator(); |
| if (comparator != null && aspect.getName().equals(SegmentEndTimeAspect.SEGMENT_END_TIME_ASPECT.getName())) { |
| comparator = comparator.reversed(); |
| } |
| if (comparator != null) { |
| buildIndex(id, comparator, aspect.getName()); |
| } |
| model.add(new TmfTreeDataModel(id, -1, Collections.singletonList(aspect.getName()))); |
| } |
| } |
| if (fSegmentProvider != null) { |
| synchronized (fAspectToIdMap) { |
| for (final ISegmentAspect aspect : fSegmentProvider.getSegmentAspects()) { |
| long id = fAspectToIdMap.computeIfAbsent(aspect, a -> fAtomicLong.getAndIncrement()); |
| Comparator<ISegment> comparator = (Comparator<ISegment>) aspect.getComparator(); |
| if (comparator != null) { |
| buildIndex(id, comparator, aspect.getName()); |
| } |
| model.add(new TmfTreeDataModel(id, -1, Collections.singletonList(aspect.getName()))); |
| } |
| } |
| } |
| return new TmfModelResponse<>(new TmfTreeModel<>(Collections.emptyList(), model), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED); |
| } |
| |
| @Override |
| public TmfModelResponse<ITmfVirtualTableModel<SegmentStoreTableLine>> fetchLines(Map<String, Object> fetchParameters, @Nullable IProgressMonitor monitor) { |
| TraceCompassLogUtils.traceAsyncStart(LOGGER, Level.FINE, "SegmentStoreTableDataProvider#fetchLines", fId, 2); //$NON-NLS-1$ |
| fetchParameters.putIfAbsent(DataProviderParameterUtils.REQUESTED_COLUMN_IDS_KEY, Collections.emptyList()); |
| VirtualTableQueryFilter queryFilter = FetchParametersUtils.createVirtualTableQueryFilter(fetchParameters); |
| if (queryFilter == null) { |
| return new TmfModelResponse<>(null, ITmfResponse.Status.FAILED, CommonStatusMessage.INCORRECT_QUERY_PARAMETERS); |
| } |
| SegmentIndexesComparatorWrapper indexesComparatorWrapper = getIndexesComparatorOrDefault(fetchParameters); |
| Map<Long, ISegmentAspect> aspects = getAspectsFromColumnId(queryFilter.getColumnsId()); |
| if (aspects.isEmpty()) { |
| return new TmfModelResponse<>(new TmfVirtualTableModel<>(Collections.emptyList(), Collections.emptyList(), queryFilter.getIndex(), 0), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED); |
| } |
| List<Long> columnIds = new ArrayList<>(aspects.keySet()); |
| if (fSegmentProvider != null) { |
| ISegmentStore<ISegment> segStore = fSegmentProvider.getSegmentStore(); |
| if (segStore != null) { |
| if (segStore.isEmpty()) { |
| return new TmfModelResponse<>(new TmfVirtualTableModel<>(columnIds, Collections.emptyList(), queryFilter.getIndex(), 0), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED); |
| } |
| if (queryFilter.getIndex() >= fSegmentStoreSize) { |
| return new TmfModelResponse<>(null, ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED); |
| } |
| synchronized (fAllIndexes) { |
| try (FlowScopeLog scope = new FlowScopeLogBuilder(LOGGER, Level.FINE, "SegmentStoreTableDataProvider#fetchLines").build()) { //$NON-NLS-1$ |
| TraceCompassLogUtils.traceObjectCreation(LOGGER, Level.FINE, fAllIndexes); |
| return extractRequestedLines(queryFilter, fetchParameters, segStore, aspects, indexesComparatorWrapper); |
| } catch (Exception ex) { |
| TraceCompassLogUtils.traceInstant(LOGGER, Level.SEVERE, "error fetching lines ", ex.getMessage()); //$NON-NLS-1$ |
| } finally { |
| TraceCompassLogUtils.traceObjectDestruction(LOGGER, Level.FINE, fAllIndexes); |
| } |
| } |
| } |
| } |
| return new TmfModelResponse<>(null, ITmfResponse.Status.FAILED, CommonStatusMessage.INCORRECT_QUERY_PARAMETERS); |
| } |
| |
| private static TmfModelResponse<ITmfVirtualTableModel<SegmentStoreTableLine>> extractRequestedLines(VirtualTableQueryFilter queryFilter, Map<String, Object> fetchParameters, ISegmentStore<ISegment> segmentStore, Map<Long, ISegmentAspect> aspects, |
| SegmentIndexesComparatorWrapper indexesComparatorWrapper) { |
| VirtualTableQueryFilter localQueryFilter = queryFilter; |
| @Nullable Predicate<ISegment> searchFilter = generateFilter(fetchParameters); |
| List<Long> columnIds = new ArrayList<>(aspects.keySet()); |
| List<SegmentStoreTableLine> lines = new ArrayList<>(); |
| int startIndexRank = (int) (localQueryFilter.getIndex() / STEP); |
| int actualStartQueryIndex = (int) (localQueryFilter.getIndex() % STEP); |
| SegmentStoreIndex segIndex = indexesComparatorWrapper.getIndexes().get(startIndexRank); |
| long start = segIndex.getStartTimestamp(); |
| SegmentPredicate filter = new SegmentPredicate(segIndex, indexesComparatorWrapper.getAspectName()); |
| int endIndexRank = (int) ((localQueryFilter.getIndex() + localQueryFilter.getCount() + STEP - 1) / STEP); |
| long end = getEndTimestamp(endIndexRank, indexesComparatorWrapper); |
| /* |
| * Search for the next or previous segment starting from the given |
| * segment index |
| */ |
| Object directionValue = fetchParameters.get(TABLE_SEARCH_DIRECTION_KEY); |
| if (searchFilter != null && directionValue != null) { |
| Direction direction = directionValue.equals(Direction.PREVIOUS.name()) ? Direction.PREVIOUS : Direction.NEXT; |
| @Nullable WrappedSegment segment = null; |
| if (direction == Direction.NEXT) { |
| segment = getNextWrappedSegmentMatching(searchFilter, queryFilter.getIndex(), segmentStore, indexesComparatorWrapper); |
| } else { |
| segment = getPreviousWrappedSegmentMatching(searchFilter, queryFilter.getIndex(), segmentStore, indexesComparatorWrapper); |
| } |
| if (segment != null) { |
| lines.add(buildSegmentStoreTableLine(aspects, segment.getOriginalSegment(), segment.getRank(), searchFilter)); |
| localQueryFilter = new VirtualTableQueryFilter(queryFilter.getColumnsId(), segment.getRank(), queryFilter.getCount()); |
| long nextSegmentRank = segment.getRank() + 1; |
| startIndexRank = (int) (nextSegmentRank / STEP); |
| actualStartQueryIndex = (int) (nextSegmentRank % STEP); |
| segIndex = indexesComparatorWrapper.getIndexes().get(startIndexRank); |
| start = segIndex.getStartTimestamp(); |
| endIndexRank = (int) ((nextSegmentRank + localQueryFilter.getCount() + STEP - 1) / STEP); |
| end = getEndTimestamp(endIndexRank, indexesComparatorWrapper); |
| } |
| if ((queryFilter.getCount() == 1) || (segment == null)) { |
| return new TmfModelResponse<>(new TmfVirtualTableModel<>(columnIds, lines, localQueryFilter.getIndex(), segmentStore.size()), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED); |
| } |
| } |
| List<ISegment> newSegStore = segmentStore.getIntersectingElements(start, end, indexesComparatorWrapper.getComparator(), filter); |
| for (int i = actualStartQueryIndex; i < newSegStore.size(); i++) { |
| if (queryFilter.getCount() == lines.size()) { |
| break; |
| } |
| long lineNumber = localQueryFilter.getIndex() + lines.size(); |
| SegmentStoreTableLine newLine = buildSegmentStoreTableLine(aspects, newSegStore.get(i), lineNumber, searchFilter); |
| lines.add(newLine); |
| } |
| return new TmfModelResponse<>(new TmfVirtualTableModel<>(columnIds, lines, localQueryFilter.getIndex(), segmentStore.size()), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED); |
| } |
| |
| private SegmentIndexesComparatorWrapper getIndexesComparatorOrDefault(Map<String, Object> fetchParameters) { |
| @Nullable Long key = extractColumnId(fetchParameters.get(TABLE_COMPARATOR_EXPRESSION_KEY)); |
| return fAllIndexes.getOrDefault(key, fDefaultWrapper); |
| } |
| |
| /** |
| * Retrieve from a segment store the next segment starting from a given |
| * index, matching the given predicate. |
| * |
| * @param searchFilter |
| * The predicate to match |
| * @param startQueryIndex |
| * The index of the query |
| * @param segmentStore |
| * The segment store from where the segments will be fetched. |
| * |
| * @return A {@link WrappedSegment} that contains the matching next segment |
| * found after a given index. |
| */ |
| private static @Nullable WrappedSegment getNextWrappedSegmentMatching(Predicate<ISegment> searchFilter, long startQueryIndex, ISegmentStore<ISegment> segmentStore, SegmentIndexesComparatorWrapper indexesComparatorWrapper) { |
| int startTimeIndexRank = (int) (startQueryIndex / STEP); |
| int actualStartQueryIndex = (int) (startQueryIndex % STEP); |
| int endTimeIndexRank = startTimeIndexRank + 1; |
| List<SegmentStoreIndex> indexes = indexesComparatorWrapper.getIndexes(); |
| while (startTimeIndexRank < indexes.size()) { |
| SegmentStoreIndex segIndex = indexes.get(startTimeIndexRank); |
| SegmentPredicate filter = new SegmentPredicate(segIndex, indexesComparatorWrapper.getAspectName()); |
| long end = getEndTimestamp(endTimeIndexRank, indexesComparatorWrapper); |
| List<ISegment> segments = segmentStore.getIntersectingElements(segIndex.getStartTimestamp(), end, indexesComparatorWrapper.getComparator(), filter); |
| for (int i = actualStartQueryIndex; i < segments.size(); i++) { |
| ISegment segment = segments.get(i); |
| if (searchFilter.test(segment)) { |
| long rank = ((long) startTimeIndexRank * STEP) + i; |
| return new WrappedSegment(segment, rank); |
| } |
| } |
| actualStartQueryIndex = 0; |
| startTimeIndexRank++; |
| endTimeIndexRank++; |
| } |
| return null; |
| } |
| |
| /** |
| * Retrieve from a segment store the previous segment, from a given rank, |
| * matching the given predicate. |
| * |
| * @param searchFilter |
| * The predicate to match |
| * @param endQueryIndex |
| * The index of the query |
| * @param segmentStore |
| * The segment store from where the segments will be fetched |
| * |
| * @return A {@link WrappedSegment} that contains the matching previous |
| * segment found before a given index. |
| */ |
| |
| private static @Nullable WrappedSegment getPreviousWrappedSegmentMatching(Predicate<ISegment> searchFilter, long endQueryIndex, ISegmentStore<ISegment> segmentStore, SegmentIndexesComparatorWrapper indexesWrapper) { |
| int actualEndQueryIndex = (int) (endQueryIndex % STEP); |
| int startTimeIndexRank = (int) (endQueryIndex / STEP); |
| int endTimeIndexRank = startTimeIndexRank + 1; |
| while (endTimeIndexRank > 0) { |
| SegmentStoreIndex segIndex = indexesWrapper.getIndexes().get(startTimeIndexRank); |
| SegmentPredicate filter = new SegmentPredicate(segIndex, indexesWrapper.getAspectName()); |
| long end = getEndTimestamp(endTimeIndexRank, indexesWrapper); |
| List<ISegment> segments = segmentStore.getIntersectingElements(segIndex.getStartTimestamp(), end, indexesWrapper.getComparator(), filter); |
| for (int i = Math.min(segments.size() - 1, actualEndQueryIndex); i >= 0; i--) { |
| if (searchFilter.test(segments.get(i))) { |
| long rank = i + ((long) startTimeIndexRank * STEP); |
| return new WrappedSegment(segments.get(i), rank); |
| } |
| } |
| actualEndQueryIndex = STEP; |
| startTimeIndexRank--; |
| endTimeIndexRank--; |
| } |
| return null; |
| } |
| |
| /** |
| * Generates a predicate filter based on the search map found in the given |
| * query parameters |
| * |
| * @param fetchParameters |
| * The query parameters used to extract the search map |
| * |
| * @return A predicate based on the search map found in the fetch parameters |
| */ |
| private static @Nullable Predicate<ISegment> generateFilter(Map<String, Object> fetchParameters) { |
| @Nullable Map<Long, String> searchMap = extractSearchFilter(fetchParameters); |
| if (searchMap == null) { |
| return null; |
| } |
| Map<Long, Pattern> patterns = new HashMap<>(); |
| for (Entry<Long, String> searchEntry : searchMap.entrySet()) { |
| patterns.put(searchEntry.getKey(), Pattern.compile(searchEntry.getValue())); |
| } |
| Map<Long, ISegmentAspect> aspects = fAspectToIdMap.inverse(); |
| return segment -> { |
| for (Entry<Long, Pattern> patternEntry : patterns.entrySet()) { |
| Pattern pattern = Objects.requireNonNull(patternEntry.getValue()); |
| ISegmentAspect aspect = aspects.get(patternEntry.getKey()); |
| if (aspect != null && !pattern.matcher(formatResolvedAspect(aspect.resolve(segment), aspect.getName())).find()) { |
| return false; |
| } |
| } |
| return true; |
| }; |
| } |
| |
| @SuppressWarnings("unchecked") |
| private static @Nullable Map<Long, String> extractSearchFilter(Map<String, Object> fetchParameters) { |
| Object searchFilterObject = fetchParameters.get(TABLE_SEARCH_EXPRESSION_KEY); |
| if (searchFilterObject instanceof Map<?, ?>) { |
| return extractSimpleSearchFilter((Map<?, String>) searchFilterObject); |
| } |
| return null; |
| } |
| |
| private static @Nullable Map<Long, String> extractSimpleSearchFilter(Map<?, String> searchFilterObject) { |
| if (searchFilterObject.isEmpty()) { |
| return null; |
| } |
| Map<Long, String> searchMap = new HashMap<>(); |
| for (Entry<?, String> searchEntry : searchFilterObject.entrySet()) { |
| Long key = extractColumnId(searchEntry.getKey()); |
| if (key != null) { |
| searchMap.put(key, searchEntry.getValue()); |
| } |
| } |
| return searchMap; |
| } |
| |
| /** |
| * Extract the id of the column out of an object |
| * |
| * @param key |
| * The object that contains the id |
| * |
| * @return The column id |
| */ |
| private static @Nullable Long extractColumnId(@Nullable Object key) { |
| try { |
| if (key instanceof String && Pattern.compile("[-?\\d+\\.?\\d+]").matcher((String) key).matches()) { //$NON-NLS-1$ |
| return Long.valueOf((String) key); |
| } |
| if (key instanceof Long) { |
| return (Long) key; |
| } |
| if (key instanceof Integer) { |
| return Long.valueOf((Integer) key); |
| } |
| } catch (NumberFormatException e) { |
| // Do nothing |
| } |
| return null; |
| } |
| |
| /** |
| * Builds the table line. |
| * |
| * @param aspects |
| * The aspects to resolve. |
| * @param segment |
| * The segment that contains the data that will fill the line. |
| * @param lineNumber |
| * The line number that will be assigned to the line that will be |
| * built. |
| * @param searchFilter |
| * The predicate that applies the search to set the active |
| * property |
| * |
| * @return Returns a SegmentStoreTableLine after resolving the aspects of a |
| * given segment |
| */ |
| private static SegmentStoreTableLine buildSegmentStoreTableLine(Map<Long, ISegmentAspect> aspects, ISegment segment, long lineNumber, @Nullable Predicate<ISegment> searchFilter) { |
| List<VirtualTableCell> entry = new ArrayList<>(aspects.size()); |
| for (Entry<Long, ISegmentAspect> aspectEntry : aspects.entrySet()) { |
| ISegmentAspect aspect = Objects.requireNonNull(aspectEntry.getValue()); |
| Object aspectResolved = aspect.resolve(segment); |
| String cellContent = formatResolvedAspect(aspectResolved, aspect.getName()); |
| entry.add(new VirtualTableCell(cellContent)); |
| } |
| SegmentStoreTableLine tableLine = new SegmentStoreTableLine(entry, lineNumber); |
| if (searchFilter != null) { |
| tableLine.setActiveProperties(searchFilter.test(segment) ? CoreFilterProperty.HIGHLIGHT : 0); |
| } |
| return tableLine; |
| } |
| |
| /** |
| * Returns the desired {@link ISegmentAspect}. |
| * |
| * @param desiredColumns |
| * The list of desired column ids that we want to retrieve |
| * |
| * @return The list of {@link ISegmentAspect} that matches the desired |
| * columns ids |
| */ |
| private static Map<Long, ISegmentAspect> getAspectsFromColumnId(List<Long> desiredColumns) { |
| Map<Long, ISegmentAspect> aspects = new LinkedHashMap<>(); |
| if (!desiredColumns.isEmpty()) { |
| for (Long columnId : desiredColumns) { |
| ISegmentAspect aspect = fAspectToIdMap.inverse().get(columnId); |
| if (aspect != null) { |
| aspects.put(columnId, aspect); |
| } |
| } |
| return aspects; |
| } |
| return Objects.requireNonNull(fAspectToIdMap.inverse()); |
| } |
| |
| /** |
| * Formats the resolved aspect into a string. |
| * |
| * @param aspectResolved |
| * The object that results of the resolution of the segment. |
| * @param aspectName |
| * The name of the aspect used to identify the strategy of |
| * formatting. |
| * |
| * @return a literal string of the content of the aspect resolved object. |
| */ |
| private static String formatResolvedAspect(@Nullable Object aspectResolved, String aspectName) { |
| String aspectParsed; |
| if (aspectName.equals(TmfStrings.duration())) { |
| aspectParsed = NonNullUtils.nullToEmptyString(FORMATTER.format(aspectResolved)); |
| } else if (aspectName.equals(TmfStrings.startTime()) || aspectName.equals(TmfStrings.endTime())) { |
| aspectParsed = String.valueOf(TmfTimestamp.fromNanos((Long) Objects.requireNonNull(aspectResolved))); |
| |
| } else { |
| aspectParsed = aspectResolved == null ? StringUtils.EMPTY : String.valueOf(aspectResolved); |
| } |
| return aspectParsed; |
| } |
| |
| private static long getEndTimestamp(int position, SegmentIndexesComparatorWrapper indexComparatorWrapper) { |
| List<SegmentStoreIndex> indexes = indexComparatorWrapper.getIndexes(); |
| if (position >= indexes.size()) { |
| boolean isEndTimeComparatorUsed = indexComparatorWrapper.getAspectName().equals(SegmentEndTimeAspect.SEGMENT_END_TIME_ASPECT.getName()); |
| if (isEndTimeComparatorUsed) { |
| return 0; |
| } |
| return Long.MAX_VALUE; |
| } |
| return indexes.get(position).getStartTimestamp(); |
| } |
| |
| } |