tmf: support for searching previous event in TmfEventTableDataProvider
- Use table_search_direction (NEXT/PREVIOUS) to search next or previous
- Use isFiltered parameter for getting next event when the parameter
table_search_direction is not passed. This behavior is deprecated and
will be removed.
- Only return first matching event when searching next/previous
[Added] support for searching previous in TmfEventTableDataProvider
Change-Id: Ib2fd0fd75278571002102412d67c68810970d4d0
Signed-off-by: Bernd Hufmann <Bernd.Hufmann@ericsson.com>
Reviewed-on: https://git.eclipse.org/r/c/tracecompass/org.eclipse.tracecompass/+/183056
Tested-by: Trace Compass Bot <tracecompass-bot@eclipse.org>
Tested-by: Patrick Tasse <patrick.tasse@gmail.com>
Reviewed-by: Patrick Tasse <patrick.tasse@gmail.com>
diff --git a/tmf/org.eclipse.tracecompass.tmf.core.tests/src/org/eclipse/tracecompass/tmf/core/tests/event/TmfEventTableDataProviderTest.java b/tmf/org.eclipse.tracecompass.tmf.core.tests/src/org/eclipse/tracecompass/tmf/core/tests/event/TmfEventTableDataProviderTest.java
index a9f5fa4..b696676 100644
--- a/tmf/org.eclipse.tracecompass.tmf.core.tests/src/org/eclipse/tracecompass/tmf/core/tests/event/TmfEventTableDataProviderTest.java
+++ b/tmf/org.eclipse.tracecompass.tmf.core.tests/src/org/eclipse/tracecompass/tmf/core/tests/event/TmfEventTableDataProviderTest.java
@@ -27,6 +27,7 @@
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.tracecompass.internal.provisional.tmf.core.model.events.TmfEventTableColumnDataModel;
import org.eclipse.tracecompass.internal.provisional.tmf.core.model.events.TmfEventTableDataProvider;
+import org.eclipse.tracecompass.internal.provisional.tmf.core.model.events.TmfEventTableDataProvider.Direction;
import org.eclipse.tracecompass.internal.provisional.tmf.core.model.events.TmfEventTableFilterModel;
import org.eclipse.tracecompass.internal.provisional.tmf.core.model.filters.EventTableQueryFilter;
import org.eclipse.tracecompass.internal.provisional.tmf.core.model.filters.VirtualTableQueryFilter;
@@ -36,7 +37,6 @@
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.filters.FetchParametersUtils;
-import org.eclipse.tracecompass.tmf.core.dataprovider.DataProviderParameterUtils;
import org.eclipse.tracecompass.tmf.core.exceptions.TmfTraceException;
import org.eclipse.tracecompass.tmf.core.model.CoreFilterProperty;
import org.eclipse.tracecompass.tmf.core.model.filters.TimeQueryFilter;
@@ -321,8 +321,45 @@
}
/**
+ * Given a start index, count and a list of desired columns, we check model returned by
+ * the data provider. We also apply a search filter on a single column, where no event matches
+ * the search expression.
+ */
+ @Test
+ public void testDataProviderWithSearchNoMatch() {
+ Long eventTypeColumnId = fColumns.get(EVENT_TYPE_COLUMN_NAME);
+ Long timestampColumnId = fColumns.get(TIMESTAMP_COLUMN_NAME);
+
+ assertNotNull(timestampColumnId);
+ assertNotNull(eventTypeColumnId);
+
+ // Query for the index for the first matching event
+ VirtualTableQueryFilter queryFilter = new EventTableQueryFilter(Arrays.asList(eventTypeColumnId, timestampColumnId), 0, 1, null);
+ Map<String, Object> parameters = FetchParametersUtils.virtualTableQueryToMap(queryFilter);
+
+ Map<Long, String> searchExpressions = new HashMap<>();
+ searchExpressions.put(eventTypeColumnId, "Does not exits");
+
+ parameters.put(TmfEventTableDataProvider.TABLE_SEARCH_EXPRESSION_KEY, searchExpressions);
+ parameters.put(TmfEventTableDataProvider.TABLE_SEARCH_DIRECTION_KEY, Direction.NEXT.name());
+
+ List<Long> expectedColumnsId = Arrays.asList(eventTypeColumnId, timestampColumnId);
+ TmfTimestampFormat.getDefaulTimeFormat().format(TmfTimestamp.fromMillis(2).toNanos());
+ List<EventTableLine> expectedData = Collections.emptyList();
+
+ TmfModelResponse<ITmfVirtualTableModel<EventTableLine>> response = fProvider.fetchLines(parameters, null);
+ ITmfVirtualTableModel<EventTableLine> currentModel = response.getModel();
+ currentModel = response.getModel();
+ assertNotNull(currentModel);
+ assertTrue(currentModel.getLines().isEmpty());
+ TmfVirtualTableModel<@NonNull EventTableLine> expectedModel = new TmfVirtualTableModel<>(expectedColumnsId, expectedData, 0, 10000);
+ assertEquals(expectedModel, currentModel);
+ }
+
+ /**
* Given a start index, count and a list of desired columns, we check model
- * returned by the data provider. We also apply a search filter on a single column.
+ * returned by the data provider. We also apply a search filter on a single
+ * column to search NEXT from the start index.
*/
@Test
public void testDataProviderWithSimpleSingleColumnsSearch() {
@@ -340,7 +377,7 @@
searchExpressions.put(eventTypeColumnId, "Type-2");
parameters.put(TmfEventTableDataProvider.TABLE_SEARCH_EXPRESSION_KEY, searchExpressions);
- parameters.put(DataProviderParameterUtils.FILTERED_KEY, true);
+ parameters.put(TmfEventTableDataProvider.TABLE_SEARCH_DIRECTION_KEY, Direction.NEXT.name());
List<Long> expectedColumnsId = Arrays.asList(eventTypeColumnId, timestampColumnId);
TmfTimestampFormat.getDefaulTimeFormat().format(TmfTimestamp.fromMillis(2).toNanos());
@@ -351,7 +388,7 @@
TmfModelResponse<ITmfVirtualTableModel<EventTableLine>> response = fProvider.fetchLines(parameters, null);
ITmfVirtualTableModel<EventTableLine> currentModel = response.getModel();
- TmfVirtualTableModel<@NonNull EventTableLine> expectedModel = new TmfVirtualTableModel<>(expectedColumnsId, expectedData, 0, 10000);
+ TmfVirtualTableModel<@NonNull EventTableLine> expectedModel = new TmfVirtualTableModel<>(expectedColumnsId, expectedData, 2, 10000);
assertEquals(expectedModel, currentModel);
// Query for events with search filter active. Matching lines will be tagged for highlighting
@@ -377,6 +414,127 @@
/**
* Given a start index, count and a list of desired columns, we check model
+ * returned by the data provider. We also apply a search filter on a single
+ * column to search NEXT from the start index. N number of events from
+ * the first matched event will be returned by the data provider.
+ */
+ @Test
+ public void testDataProviderWithGetDataFromSearchForwardMatch() {
+ Long eventTypeColumnId = fColumns.get(EVENT_TYPE_COLUMN_NAME);
+ Long timestampColumnId = fColumns.get(TIMESTAMP_COLUMN_NAME);
+
+ assertNotNull(timestampColumnId);
+ assertNotNull(eventTypeColumnId);
+
+ // Query for the index for the first matching event
+ int nbEventsRequested = 3;
+ VirtualTableQueryFilter queryFilter = new EventTableQueryFilter(Arrays.asList(eventTypeColumnId, timestampColumnId), 0, nbEventsRequested, null);
+ Map<String, Object> parameters = FetchParametersUtils.virtualTableQueryToMap(queryFilter);
+
+ Map<Long, String> searchExpressions = new HashMap<>();
+ searchExpressions.put(eventTypeColumnId, "Type-2");
+
+ parameters.put(TmfEventTableDataProvider.TABLE_SEARCH_EXPRESSION_KEY, searchExpressions);
+ parameters.put(TmfEventTableDataProvider.TABLE_SEARCH_DIRECTION_KEY, Direction.NEXT.name());
+
+ List<Long> expectedColumnsId = Arrays.asList(eventTypeColumnId, timestampColumnId);
+ TmfTimestampFormat.getDefaulTimeFormat().format(TmfTimestamp.fromMillis(2).toNanos());
+ List<EventTableLine> expectedData = Arrays.asList(
+ new EventTableLine(Arrays.asList(new VirtualTableCell("Type-2"), new VirtualTableCell(lineTimestamp(3))), 2, TmfTimestamp.fromMillis(3), 2, 0),
+ new EventTableLine(Arrays.asList(new VirtualTableCell("Type-3"), new VirtualTableCell(lineTimestamp(4))), 3, TmfTimestamp.fromMillis(4), 3, 0),
+ new EventTableLine(Arrays.asList(new VirtualTableCell("Type-4"), new VirtualTableCell(lineTimestamp(5))), 4, TmfTimestamp.fromMillis(5), 4, 0));
+ expectedData.get(0).setActiveProperties(CoreFilterProperty.HIGHLIGHT);
+
+ TmfModelResponse<ITmfVirtualTableModel<EventTableLine>> response = fProvider.fetchLines(parameters, null);
+ ITmfVirtualTableModel<EventTableLine> currentModel = response.getModel();
+
+ TmfVirtualTableModel<@NonNull EventTableLine> expectedModel = new TmfVirtualTableModel<>(expectedColumnsId, expectedData, 2, 10000);
+ assertNotNull(currentModel);
+ assertEquals(nbEventsRequested, currentModel.getLines().size());
+ assertEquals(expectedModel, currentModel);
+ }
+
+ /**
+ * Given a start index, count and a list of desired columns, we check model
+ * returned by the data provider. We also apply a search filter on a single column
+ * with search direction PREVIOUS.
+ */
+ @Test
+ public void testDataProviderWithSimpleSingleColumnsIndexBackwardsSearch() {
+ Long eventTypeColumnId = fColumns.get(EVENT_TYPE_COLUMN_NAME);
+ Long timestampColumnId = fColumns.get(TIMESTAMP_COLUMN_NAME);
+
+ assertNotNull(timestampColumnId);
+ assertNotNull(eventTypeColumnId);
+
+ // Query for the index for the first matching event backwards, starting at index 10
+ VirtualTableQueryFilter queryFilter = new EventTableQueryFilter(Arrays.asList(eventTypeColumnId, timestampColumnId), 10, 1, null);
+ Map<String, Object> parameters = FetchParametersUtils.virtualTableQueryToMap(queryFilter);
+
+ Map<Long, String> searchExpressions = new HashMap<>();
+ searchExpressions.put(eventTypeColumnId, "Type-2");
+
+ parameters.put(TmfEventTableDataProvider.TABLE_SEARCH_EXPRESSION_KEY, searchExpressions);
+ parameters.put(TmfEventTableDataProvider.TABLE_SEARCH_DIRECTION_KEY, Direction.PREVIOUS.name());
+
+ List<Long> expectedColumnsId = Arrays.asList(eventTypeColumnId, timestampColumnId);
+ TmfTimestampFormat.getDefaulTimeFormat().format(TmfTimestamp.fromMillis(2).toNanos());
+ List<EventTableLine> expectedData = Arrays.asList(
+ new EventTableLine(Arrays.asList(new VirtualTableCell("Type-2"), new VirtualTableCell(lineTimestamp(10))), 9, TmfTimestamp.fromMillis(10), 9, 0));
+ expectedData.get(0).setActiveProperties(CoreFilterProperty.HIGHLIGHT);
+
+ TmfModelResponse<ITmfVirtualTableModel<EventTableLine>> response = fProvider.fetchLines(parameters, null);
+ ITmfVirtualTableModel<EventTableLine> currentModel = response.getModel();
+
+ TmfVirtualTableModel<@NonNull EventTableLine> expectedModel = new TmfVirtualTableModel<>(expectedColumnsId, expectedData, 9, 10000);
+ assertEquals(expectedModel, currentModel);
+ }
+
+ /**
+ * Given a start index, count and a list of desired columns, we check model
+ * returned by the data provider. We also apply a search filter on a single
+ * column to search PREVIOUS from the start index. N number of events from
+ * the first matched event will be returned by the data provider.
+ */
+ @Test
+ public void testDataProviderWithGetDataFromSearchBackwardsMatch() {
+ Long eventTypeColumnId = fColumns.get(EVENT_TYPE_COLUMN_NAME);
+ Long timestampColumnId = fColumns.get(TIMESTAMP_COLUMN_NAME);
+
+ assertNotNull(timestampColumnId);
+ assertNotNull(eventTypeColumnId);
+
+ // Query for the 3 events starting from the the first matching event backwards, starting at index 10
+ int nbEventsRequested = 3;
+ VirtualTableQueryFilter queryFilter = new EventTableQueryFilter(Arrays.asList(eventTypeColumnId, timestampColumnId), 10, nbEventsRequested, null);
+ Map<String, Object> parameters = FetchParametersUtils.virtualTableQueryToMap(queryFilter);
+
+ Map<Long, String> searchExpressions = new HashMap<>();
+ searchExpressions.put(eventTypeColumnId, "Type-2");
+
+ parameters.put(TmfEventTableDataProvider.TABLE_SEARCH_EXPRESSION_KEY, searchExpressions);
+ parameters.put(TmfEventTableDataProvider.TABLE_SEARCH_DIRECTION_KEY, Direction.PREVIOUS.name());
+
+ List<Long> expectedColumnsId = Arrays.asList(eventTypeColumnId, timestampColumnId);
+ TmfTimestampFormat.getDefaulTimeFormat().format(TmfTimestamp.fromMillis(2).toNanos());
+ List<EventTableLine> expectedData = Arrays.asList(
+ new EventTableLine(Arrays.asList(new VirtualTableCell("Type-2"), new VirtualTableCell(lineTimestamp(10))), 9, TmfTimestamp.fromMillis(10), 9, 0),
+ new EventTableLine(Arrays.asList(new VirtualTableCell("Type-3"), new VirtualTableCell(lineTimestamp(11))), 10, TmfTimestamp.fromMillis(11), 10, 0),
+ new EventTableLine(Arrays.asList(new VirtualTableCell("Type-4"), new VirtualTableCell(lineTimestamp(12))), 11, TmfTimestamp.fromMillis(12), 11, 0));
+ expectedData.get(0).setActiveProperties(CoreFilterProperty.HIGHLIGHT);
+
+ TmfModelResponse<ITmfVirtualTableModel<EventTableLine>> response = fProvider.fetchLines(parameters, null);
+ ITmfVirtualTableModel<EventTableLine> currentModel = response.getModel();
+
+ TmfVirtualTableModel<@NonNull EventTableLine> expectedModel = new TmfVirtualTableModel<>(expectedColumnsId, expectedData, 9, 10000);
+ assertNotNull(currentModel);
+ assertEquals(nbEventsRequested, currentModel.getLines().size());
+ assertEquals(expectedModel, currentModel);
+ }
+
+
+ /**
+ * Given a start index, count and a list of desired columns, we check model
* returned by the data provider. We also apply a search filter on multiple columns
*/
@Test
@@ -396,7 +554,7 @@
searchExpressions.put(timestampColumnId, "\\d*4\\d*s*");
parameters.put(TmfEventTableDataProvider.TABLE_SEARCH_EXPRESSION_KEY, searchExpressions);
- parameters.put(DataProviderParameterUtils.FILTERED_KEY, true);
+ parameters.put(TmfEventTableDataProvider.TABLE_SEARCH_DIRECTION_KEY, Direction.NEXT.name());
List<Long> expectedColumnsId = new ArrayList<>(fColumns.values());
TmfTimestampFormat.getDefaulTimeFormat().format(TmfTimestamp.fromMillis(2).toNanos());
@@ -407,7 +565,7 @@
TmfModelResponse<ITmfVirtualTableModel<EventTableLine>> response = fProvider.fetchLines(parameters, null);
ITmfVirtualTableModel<EventTableLine> currentModel = response.getModel();
- TmfVirtualTableModel<@NonNull EventTableLine> expectedModel = new TmfVirtualTableModel<>(expectedColumnsId, expectedData, 0, 10000);
+ TmfVirtualTableModel<@NonNull EventTableLine> expectedModel = new TmfVirtualTableModel<>(expectedColumnsId, expectedData, 3, 10000);
assertEquals(expectedModel, currentModel);
// Query for events with search filter active. Matching lines will be tagged for highlighting
diff --git a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/internal/provisional/tmf/core/model/events/TmfEventTableDataProvider.java b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/internal/provisional/tmf/core/model/events/TmfEventTableDataProvider.java
index e312079..bd0ac89 100644
--- a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/internal/provisional/tmf/core/model/events/TmfEventTableDataProvider.java
+++ b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/internal/provisional/tmf/core/model/events/TmfEventTableDataProvider.java
@@ -18,13 +18,16 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
+import java.util.Optional;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Predicate;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.common.core.NonNullUtils;
import org.eclipse.tracecompass.internal.provisional.tmf.core.model.filters.VirtualTableQueryFilter;
import org.eclipse.tracecompass.internal.provisional.tmf.core.model.table.EventTableLine;
import org.eclipse.tracecompass.internal.provisional.tmf.core.model.table.ITmfFilterModel;
@@ -37,6 +40,8 @@
import org.eclipse.tracecompass.internal.tmf.core.model.filters.FetchParametersUtils;
import org.eclipse.tracecompass.tmf.core.dataprovider.DataProviderParameterUtils;
import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
+import org.eclipse.tracecompass.tmf.core.event.ITmfEventField;
+import org.eclipse.tracecompass.tmf.core.event.ITmfEventType;
import org.eclipse.tracecompass.tmf.core.event.aspect.ITmfEventAspect;
import org.eclipse.tracecompass.tmf.core.event.aspect.TmfBaseAspects;
import org.eclipse.tracecompass.tmf.core.filter.FilterManager;
@@ -53,6 +58,7 @@
import org.eclipse.tracecompass.tmf.core.request.TmfEventRequest;
import org.eclipse.tracecompass.tmf.core.response.ITmfResponse;
import org.eclipse.tracecompass.tmf.core.response.TmfModelResponse;
+import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
import org.eclipse.tracecompass.tmf.core.trace.ITmfContext;
@@ -62,6 +68,7 @@
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
/**
* This data provider will return a virtual table model (wrapped in a response)
@@ -77,10 +84,9 @@
*/
public static final String TABLE_SEARCH_EXPRESSION_KEY = "table_search_expressions"; //$NON-NLS-1$
/**
- * Key for table search to get index of search
+ * Key for table search direction (forward or backward)
*/
- public static final String TABLE_SEARCH_INDEX_KEY = "table_search_index"; //$NON-NLS-1$
-
+ public static final String TABLE_SEARCH_DIRECTION_KEY = "table_search_direction"; //$NON-NLS-1$
/**
* Key for table filters
*/
@@ -94,9 +100,11 @@
private @Nullable ITmfFilter fFilter;
/**
- * Maps used for the optimization of filtered query. TODO: Since these map can
- * take a lot of memory, one solution would be to replace these maps by two
- * lists and use binary search instead of TreeMap floor
+ * Maps used for the optimization of filtered query.
+ *
+ * TODO: Since these map can take a lot of memory, one solution would be to
+ * replace these maps by two lists and use binary search instead of TreeMap
+ * floor.
*/
private TreeMap<Long, Long> fIndexToRankMap = new TreeMap<>();
private TreeMap<Long, Long> fRankToIndexMap = new TreeMap<>();
@@ -126,6 +134,17 @@
private static final int INDEX_STORING_INTERVAL = 1000;
/**
+ * Direction of search, navigation etc.
+ * @since 7.1
+ */
+ public enum Direction {
+ /** Search next */
+ NEXT,
+ /** Search previous*/
+ PREVIOUS
+ }
+
+ /**
* Constructor
*
* @param trace
@@ -180,24 +199,73 @@
@Nullable TmfCollapseFilter collapseFilter = extractCollapseFilter(fetchParameters);
Map<Long, ITmfEventAspect<?>> aspects = getAspectsFromColumnsId(queryFilter.getColumnsId());
- Boolean isFiltered = DataProviderParameterUtils.extractIsFiltered(fetchParameters);
- boolean isIndexRequest = isFiltered != null && isFiltered;
-
if (aspects.isEmpty()) {
- return new TmfModelResponse<>(new TmfVirtualTableModel<EventTableLine>(Collections.emptyList(), Collections.emptyList(), queryFilter.getIndex(), 0), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
+ return new TmfModelResponse<>(new TmfVirtualTableModel<>(Collections.emptyList(), Collections.emptyList(), queryFilter.getIndex(), 0), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
}
List<Long> columnsIds = new ArrayList<>(aspects.keySet());
if (getTrace().getNbEvents() == 0) {
return new TmfModelResponse<>(new TmfVirtualTableModel<>(columnsIds, Collections.emptyList(), queryFilter.getIndex(), 0), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
}
- boolean forwardSearch = queryFilter.getCount() >= 0;
+ /*
+ * Search for the next or previous event starting from the given event index
+ */
+ Object directionValue = fetchParameters.get(TABLE_SEARCH_DIRECTION_KEY);
+ /////////////////////////////////////
+ // TODO remove isFiltered when Theia front-end is updated to use TABLE_SEARCH_DIRECTION_KEY instead
+ Boolean isFiltered = DataProviderParameterUtils.extractIsFiltered(fetchParameters);
+ boolean isIndexRequest = isFiltered != null && isFiltered;
+ if (isIndexRequest && directionValue == null) {
+ directionValue = Direction.NEXT.name();
+ }
+ /////////////////////////////////////
+
+ if (searchFilter != null && directionValue != null) {
+ Direction direction = directionValue.equals(Direction.PREVIOUS.name()) ? Direction.PREVIOUS : Direction.NEXT;
+ @Nullable WrappedEvent event = null;
+ Predicate<@NonNull ITmfEvent> predicate;
+ if (filter == null) {
+ predicate = searchFilter::matches;
+ } else {
+ predicate = e -> (filter.matches(e) && searchFilter.matches(e));
+ }
+ if (direction == Direction.NEXT) {
+ event = getNextWrappedEventMatching(getTrace(), Math.abs(queryFilter.getIndex()), predicate, monitor);
+ } else if (direction == Direction.PREVIOUS) {
+ event = getPreviousWrappedEventMatching(getTrace(), Math.abs(queryFilter.getIndex()), predicate, monitor);
+ }
+ List<EventTableLine> lines = new ArrayList<>();
+ long rank = queryFilter.getIndex();
+ if (event != null) {
+ rank = event.getRank();
+ // create new queryFilter with updated start rank to get number of events starting from first matching event
+ queryFilter = new VirtualTableQueryFilter(queryFilter.getColumnsId(), rank, queryFilter.getCount());
+ lines.add(buildEventTableLine(aspects, event.getOriginalEvent(), rank, rank, true));
+ }
+ if ((queryFilter.getCount() == 1) || (event == null)) {
+ /**
+ * If no event was found or the number of requested events is one
+ * reply here since all required data for the reply is available.
+ */
+ TmfVirtualTableModel<EventTableLine> model = new TmfVirtualTableModel<>(columnsIds, lines, rank, getTrace().getNbEvents());
+ return new TmfModelResponse<>(model, ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
+ }
+ }
+
+ /*
+ * Collect queryFilter.getCount() number of events from start rank
+ */
+
+ /*
+ * TODO implement upper limit of queryFilter.getCount() to avoid running out of memory.
+ * TBD if the check and should be handled here or in the calling methods.
+ */
TableEventRequest request;
if (filter != null) {
- request = filteredTableRequest(Math.abs(queryFilter.getCount()), queryFilter.getIndex(), aspects, filter, searchFilter, forwardSearch, collapseFilter, monitor);
+ request = filteredTableRequest(Math.abs(queryFilter.getCount()), queryFilter.getIndex(), aspects, filter, searchFilter, collapseFilter, monitor);
} else {
- request = tableRequest(Math.abs(queryFilter.getCount()), queryFilter.getIndex(), aspects, searchFilter, forwardSearch, collapseFilter, isIndexRequest, monitor);
+ request = tableRequest(Math.abs(queryFilter.getCount()), queryFilter.getIndex(), aspects, searchFilter, collapseFilter, monitor);
request.setEventCount(getTrace().getNbEvents());
}
@@ -205,13 +273,11 @@
try {
request.waitForCompletion();
} catch (InterruptedException e) {
- return new TmfModelResponse<>(null, ITmfResponse.Status.FAILED, e.getMessage());
+ return new TmfModelResponse<>(null, ITmfResponse.Status.FAILED, NonNullUtils.nullToEmptyString(e.getMessage()));
}
-
if (request.isCancelled()) {
return new TmfModelResponse<>(null, ITmfResponse.Status.CANCELLED, CommonStatusMessage.TASK_CANCELLED);
}
-
TmfVirtualTableModel<EventTableLine> model = new TmfVirtualTableModel<>(columnsIds, request.getEventLines(), queryFilter.getIndex(), request.getCurrentCount());
return new TmfModelResponse<>(model, ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
}
@@ -293,7 +359,7 @@
try {
request.waitForCompletion();
} catch (InterruptedException e) {
- return new TmfModelResponse<>(null, ITmfResponse.Status.FAILED, e.getMessage());
+ return new TmfModelResponse<>(null, ITmfResponse.Status.FAILED, NonNullUtils.nullToEmptyString(e.getMessage()));
}
return new TmfModelResponse<>(foundIndex, ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
@@ -325,12 +391,31 @@
}
}
+ /**
+ * Create a table event request that fills a list of {@link EventTableLine}
+ * based on the query parameters and based on a filter.
+ *
+ * @param queryCount
+ * number of requested events
+ * @param queryIndex
+ * start rank to start looking for requested events
+ * @param aspects
+ * Aspects to resolve
+ * @param filter
+ * The filter to apply
+ * @param searchFilter
+ * Search filter used to tag event lines
+ * @param collapseFilter
+ * The collapse filter to apply
+ * @param monitor
+ * a progress monitor
+ * @return a {@link TableEventRequest} to fill a list of {@link EventTableLine}
+ */
private TableEventRequest filteredTableRequest(int queryCount,
long queryIndex,
Map<Long, ITmfEventAspect<?>> aspects,
ITmfFilter filter,
@Nullable ITmfFilter searchFilter,
- boolean forwardSearch,
@Nullable ITmfFilter collapseFilter,
@Nullable IProgressMonitor monitor) {
@@ -354,15 +439,13 @@
List<EventTableLine> events = getEventLines();
if (filter.matches(event) && (collapseFilter == null || collapseFilter.matches(event))) {
- if (searchFilter == null || searchFilter.matches(event)) {
- if (events.size() < queryCount && queryIndex <= currentIndex) {
- // FIXME: searching while filtering
- events.add(buildEventTableLine(aspects, event, currentIndex, rank, false));
- }
- if (currentIndex % INDEX_STORING_INTERVAL == 0) {
- fIndexToRankMap.put(currentIndex, rank);
- fRankToIndexMap.put(rank, currentIndex);
- }
+ boolean matches = searchFilter != null && searchFilter.matches(event);
+ if (events.size() < queryCount && queryIndex <= currentIndex) {
+ events.add(buildEventTableLine(aspects, event, currentIndex, rank, matches));
+ }
+ if (currentIndex % INDEX_STORING_INTERVAL == 0) {
+ fIndexToRankMap.put(currentIndex, rank);
+ fRankToIndexMap.put(rank, currentIndex);
}
currentIndex++;
incrementCount();
@@ -375,10 +458,15 @@
events.set(lastIndex, new EventTableLine(prevLine.getCells(), prevLine.getIndex(), prevLine.getTimestamp(), prevLine.getRank(), prevRepeatCount++));
}
- if (searchFilter != null && ((!forwardSearch && getCurrentCount() == queryCount) || events.size() == queryCount)) {
- done();
- return;
- }
+ /*
+ * FIXME: Right now the whole trace is read to determine the number of matched events.
+ * In case of search filter being present, it only starts counting from the first
+ * matched event and the total number of filtered event is incorrect.
+ *
+ * However, the number of matched events should only be done once per filter and
+ * after the filter changed. Implement way to determine the number of matched events.
+ */
+
rank++;
}
@@ -393,13 +481,29 @@
};
}
+ /**
+ * Create a table event request that fills a list of {@link EventTableLine}
+ * based on the query parameters.
+ *
+ * @param queryCount
+ * number of requested events
+ * @param queryIndex
+ * start rank to start looking for requested events
+ * @param aspects
+ * Aspects to resolve
+ * @param searchFilter
+ * Search filter used to tag event lines
+ * @param collapseFilter
+ * The collapse filter to apply
+ * @param monitor
+ * a progress monitor
+ * @return a {@link TableEventRequest} to fill a list of {@link EventTableLine}
+ */
private TableEventRequest tableRequest(int queryCount,
long queryIndex,
Map<Long, ITmfEventAspect<?>> aspects,
@Nullable ITmfFilter searchFilter,
- boolean forwardSearch,
@Nullable ITmfFilter collapseFilter,
- boolean isIndexRequest,
@Nullable IProgressMonitor monitor) {
return new TableEventRequest(queryIndex) {
@@ -415,11 +519,11 @@
List<EventTableLine> events = getEventLines();
boolean matches = searchFilter != null && searchFilter.matches(event);
- if ((!isIndexRequest || matches) && (collapseFilter == null || collapseFilter.matches(event))) {
+ if (collapseFilter == null || collapseFilter.matches(event)) {
if (events.size() < queryCount) {
events.add(buildEventTableLine(aspects, event, rank, rank, matches));
}
- } else if (collapseFilter != null && !events.isEmpty()) {
+ } else if (!events.isEmpty()) {
// If a collapse filter is present, we need to update the last event we have in
// our list to increment the repeat count value.
int lastIndex = events.size() - 1;
@@ -428,7 +532,7 @@
events.set(lastIndex, new EventTableLine(prevLine.getCells(), prevLine.getIndex(), prevLine.getTimestamp(), prevLine.getRank(), prevRepeatCount++));
}
- if ((searchFilter != null && !forwardSearch && getNbRead() == queryCount) || events.size() == queryCount) {
+ if ((getNbRead() == queryCount) || events.size() == queryCount) {
done();
return;
}
@@ -471,6 +575,7 @@
* Filter to apply
*/
private void applyFilter(ITmfFilter filter) {
+ // TODO verify if this works correctly
if (!filter.equals(fFilter)) {
fFilter = filter;
fIndexToRankMap.clear();
@@ -548,7 +653,7 @@
}
@SuppressWarnings("unchecked")
- private @Nullable static ITmfFilter extractSearchFilter(Map<String, Object> fetchParameters) {
+ private static @Nullable ITmfFilter extractSearchFilter(Map<String, Object> fetchParameters) {
Object searchFilterObject = fetchParameters.get(TABLE_SEARCH_EXPRESSION_KEY);
if (searchFilterObject instanceof Map<?, ?>) {
return extractSimpleSearchFilter((Map<?, String>) searchFilterObject);
@@ -556,7 +661,7 @@
return null;
}
- private @Nullable static ITmfFilter extractSimpleSearchFilter (Map<?, String> searchMap) {
+ private static @Nullable ITmfFilter extractSimpleSearchFilter (Map<?, String> searchMap) {
if (searchMap.isEmpty()) {
return null;
}
@@ -670,4 +775,270 @@
}
return commonTraceType != null;
}
+
+
+ /**
+ * Retrieve from a trace the next event, from a starting rank, matching the
+ * given predicate.
+ *
+ * Code inspired from TmfTraceUtils implementation, however this will return
+ * a {@link WrappedEvent} with the correct event rank.
+ *
+ * @param trace
+ * The trace
+ * @param startRank
+ * The rank of the event at which to start searching. Use
+ * <code>0</code> to search from the start of the trace.
+ * @param predicate
+ * The predicate to test events against
+ * @param monitor
+ * Optional progress monitor that can be used to cancel the operation
+ * @return The first {@link WrappedEvent} matching the predicate, or null if the end of the
+ * trace was reached and no event was found
+ */
+ private static @Nullable WrappedEvent getNextWrappedEventMatching(ITmfTrace trace, long startRank,
+ Predicate<ITmfEvent> predicate, @Nullable IProgressMonitor monitor) {
+ if (monitor != null && monitor.isCanceled()) {
+ return null;
+ }
+
+ /*
+ * startRank + 1 because we do not want to include the start event itself in the
+ * search
+ */
+ EventMatchingRequest req = new EventMatchingRequest(startRank + 1, predicate, monitor);
+ trace.sendRequest(req);
+ try {
+ req.waitForCompletion();
+ } catch (InterruptedException e) {
+ return null;
+ }
+
+ return req.getFoundEvent();
+ }
+
+ /**
+ * Retrieve from a trace the previous event, from a given rank, matching the
+ * given predicate.
+ *
+ * Code inspired from TmfTraceUtils implementation, however this will return
+ * a {@link WrappedEvent} with the correct event rank.
+ *
+ * @param trace
+ * The trace
+ * @param startRank
+ * The rank of the event at which to start searching backwards.
+ * @param predicate
+ * The predicate to test events against
+ * @param monitor
+ * Optional progress monitor that can be used to cancel the operation
+ * @return The first {@link WrappedEvent} found matching the predicate, or null if the
+ * beginning of the trace was reached and no event was found
+ */
+ private static @Nullable WrappedEvent getPreviousWrappedEventMatching(ITmfTrace trace, long startRank,
+ Predicate<ITmfEvent> predicate, @Nullable IProgressMonitor monitor) {
+ if (monitor != null && monitor.isCanceled()) {
+ return null;
+ }
+ /*
+ * We are going to do a series of queries matching the trace's cache size in
+ * length (which should minimize on-disk seeks), then iterate on the found
+ * events in reverse order until we find a match.
+ */
+ int step = trace.getCacheSize();
+
+ /*
+ * If we are close to the beginning of the trace, make sure we only look for the
+ * events before the startRank.
+ */
+ if (startRank < step) {
+ step = (int) startRank;
+ }
+
+ long currentRank = startRank;
+ try {
+ while (currentRank > 0) {
+ currentRank = Math.max(currentRank - step, 0);
+
+ List<WrappedEvent> list = new ArrayList<>(step);
+ ArrayFillingRequest req = new ArrayFillingRequest(currentRank, step, list, monitor);
+ trace.sendRequest(req);
+
+ /* Check periodically if the job was cancelled */
+ req.waitForCompletion();
+
+ Optional<WrappedEvent> matchingEvent = Lists.reverse(list).stream()
+ .filter(e -> predicate.test(e.getOriginalEvent()))
+ .findFirst();
+
+ if (matchingEvent.isPresent()) {
+ /* We found an event matching, return it! */
+ return matchingEvent.get();
+ }
+ /* Keep searching, next loop */
+
+ }
+ } catch (InterruptedException e) {
+ return null;
+ }
+
+ /*
+ * We searched up to the beginning of the trace and didn't find anything.
+ */
+ return null;
+
+ }
+
+ /**
+ * Event request looking for an event matching a Predicate.
+ */
+ private static class EventMatchingRequest extends TmfEventRequest {
+
+ private final Predicate<ITmfEvent> fPredicate;
+ private final @Nullable IProgressMonitor fMonitor;
+
+ private @Nullable WrappedEvent fFoundEvent = null;
+
+ /**
+ * Basic constructor, will query the trace until the end.
+ *
+ * @param startRank
+ * The rank at which to start, use 0 for the beginning
+ * @param predicate
+ * The predicate to test against each event
+ * @param monitor
+ * an optional progress monitor
+ */
+ public EventMatchingRequest(long startRank, Predicate<ITmfEvent> predicate, @Nullable IProgressMonitor monitor) {
+ super(ITmfEvent.class, startRank, ALL_DATA, ExecutionType.FOREGROUND);
+ fPredicate = predicate;
+ fMonitor = monitor;
+ }
+
+ /**
+ * Basic constructor, will query the trace the limit is reached.
+ *
+ * @param startRank
+ * The rank at which to start, use 0 for the beginning
+ * @param limit
+ * The limit on the number of events
+ * @param predicate
+ * The predicate to test against each event
+ * @param monitor
+ * an optional progress monitor
+ */
+ public EventMatchingRequest(long startRank, int limit, Predicate<ITmfEvent> predicate, @Nullable IProgressMonitor monitor) {
+ super(ITmfEvent.class, startRank, limit, ExecutionType.FOREGROUND);
+ fPredicate = predicate;
+ fMonitor = monitor;
+ }
+
+ public @Nullable WrappedEvent getFoundEvent() {
+ return fFoundEvent;
+ }
+
+ @Override
+ public void handleData(ITmfEvent event) {
+ super.handleData(event);
+ if (fPredicate.test(event)) {
+ fFoundEvent = new WrappedEvent(event, getNbRead() + getIndex() - 1);
+ done();
+ }
+ if (fMonitor != null && fMonitor.isCanceled()) {
+ cancel();
+ }
+ }
+ }
+
+ /**
+ * Event request that simply puts all returned events into a list passed in
+ * parameter.
+ */
+ private static class ArrayFillingRequest extends TmfEventRequest {
+
+ private final List<WrappedEvent> fList;
+ private final @Nullable IProgressMonitor fMonitor;
+
+ public ArrayFillingRequest(long startRank, int limit, List<WrappedEvent> listToFill, @Nullable IProgressMonitor monitor) {
+ super(ITmfEvent.class, startRank, limit, ExecutionType.FOREGROUND);
+ fList = listToFill;
+ fMonitor = monitor;
+ }
+
+ @Override
+ public void handleData(ITmfEvent event) {
+ super.handleData(event);
+ fList.add(new WrappedEvent(event, getIndex() + getNbRead() - 1));
+ if (fMonitor != null && fMonitor.isCanceled()) {
+ cancel();
+ }
+ }
+ }
+
+ /**
+ * A TMF Events wrapper class wrapping a ITmfEvent and the corresponding, valid rank.
+ */
+ private static class WrappedEvent implements ITmfEvent {
+ /**
+ * Event reference.
+ */
+ ITmfEvent fEvent;
+ /**
+ * Events rank.
+ */
+ long fRank;
+
+ /**
+ * Constructor for new cached events.
+ *
+ * @param tmfEvent
+ * The original trace event
+ * @param rank
+ * The rank of this event in the trace
+ */
+ public WrappedEvent (ITmfEvent tmfEvent, long rank) {
+ this.fEvent = tmfEvent;
+ this.fRank = rank;
+ }
+
+ public ITmfEvent getOriginalEvent() {
+ return fEvent;
+ }
+
+ @Override
+ public @Nullable <T> T getAdapter(@Nullable Class<T> adapterType) {
+ return fEvent.getAdapter(adapterType);
+ }
+
+ @Override
+ public ITmfTrace getTrace() {
+ return fEvent.getTrace();
+ }
+
+ @Override
+ public long getRank() {
+ return fRank;
+ }
+
+ @Override
+ public String getName() {
+ return fEvent.getName();
+ }
+
+ @Override
+ public ITmfTimestamp getTimestamp() {
+ return fEvent.getTimestamp();
+ }
+
+ @Override
+ public @Nullable ITmfEventType getType() {
+ return fEvent.getType();
+ }
+
+ @Override
+ public @Nullable ITmfEventField getContent() {
+ return fEvent.getContent();
+ }
+ }
+
}
\ No newline at end of file