blob: ecf8b82c6a1eee81690896903decda8525b1a7e3 [file] [log] [blame]
/**********************************************************************
* Copyright (c) 2018 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.provisional.tmf.core.model.events;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicLong;
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.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;
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.filter.TmfCollapseFilter;
import org.eclipse.tracecompass.internal.tmf.core.model.AbstractTmfTraceDataProvider;
import org.eclipse.tracecompass.internal.tmf.core.model.filters.FetchParametersUtils;
import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
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;
import org.eclipse.tracecompass.tmf.core.filter.ITmfFilter;
import org.eclipse.tracecompass.tmf.core.filter.model.ITmfFilterTreeNode;
import org.eclipse.tracecompass.tmf.core.filter.model.TmfFilterMatchesNode;
import org.eclipse.tracecompass.tmf.core.filter.model.TmfFilterNode;
import org.eclipse.tracecompass.tmf.core.filter.model.TmfFilterRootNode;
import org.eclipse.tracecompass.tmf.core.model.CommonStatusMessage;
import org.eclipse.tracecompass.tmf.core.model.filters.TimeQueryFilter;
import org.eclipse.tracecompass.tmf.core.model.tree.TmfTreeModel;
import org.eclipse.tracecompass.tmf.core.request.ITmfEventRequest;
import org.eclipse.tracecompass.tmf.core.request.ITmfEventRequest.ExecutionType;
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.TmfTimeRange;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
import org.eclipse.tracecompass.tmf.core.trace.ITmfContext;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.experiment.TmfExperiment;
import com.google.common.collect.ImmutableSet;
/**
* This data provider will return a virtual table model (wrapped in a response)
* based on a virtual table query filter. Model returned is for event table.
*
* @author Yonni Chen
* @since 4.0
*/
public class TmfEventTableDataProvider extends AbstractTmfTraceDataProvider implements ITmfVirtualTableDataProvider<TmfEventTableColumnDataModel, EventTableLine> {
/**
* Key for table search
*/
public static final String TABLE_SEARCH_KEY = "table_search"; //$NON-NLS-1$
/**
* Key for table filters
*/
public static final String TABLE_FILTERS_KEY = "table_filters"; //$NON-NLS-1$
/**
* Extension point ID.
*/
public static final String ID = "org.eclipse.tracecompass.internal.provisional.tmf.core.model.events.TmfEventTableDataProvider"; //$NON-NLS-1$
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
*/
private TreeMap<Long, Long> fIndexToRankMap = new TreeMap<>();
private TreeMap<Long, Long> fRankToIndexMap = new TreeMap<>();
/**
* Keep the filtered count for future request with the same filter. TODO: Remove
* this cache since it is not thread safe and replace it with a better cache
* with the two list of cached index/rank
*/
private long fFilteredCount = -1L;
/**
* Atomic Long so that every column has a unique ID.
*/
private static final AtomicLong fAtomicLong = new AtomicLong();
/**
* Two lists are used in this case instead of a BiHashMap from Guava to
* accommodate the fact that the version of Guava used in Neon doesn't
* guaranteed a key insertion order for BiHashMap.
*/
private static final Map<Long, ITmfEventAspect<?>> fIdToAspectMap = new LinkedHashMap<>();
private static final Map<ITmfEventAspect<?>, Long> fAspectToIdMap = new HashMap<>();
/**
* To optimize filtered request a map of index to rank is used to find the
* starting point of a request in the trace. This value is used to avoid storing
* too many entries in the map. The corresponding rank associated to an index is
* only store each time the index is a factor of this value. TODO: Can be
* improve in the future to have a constant number of point per trace to avoid
* getting a huge map
*/
private static final int INDEX_STORING_INTERVAL = 1000;
/**
* Constructor
*
* @param trace
* A trace on which we are interested to fetch an event table model
*/
public TmfEventTableDataProvider(ITmfTrace trace) {
super(trace);
}
@Override
public String getId() {
return ID;
}
@Deprecated
@Override
public TmfModelResponse<List<TmfEventTableColumnDataModel>> fetchTree(TimeQueryFilter filter, @Nullable IProgressMonitor monitor) {
Map<String, Object> parameters = FetchParametersUtils.timeQueryToMap(filter);
TmfModelResponse<TmfTreeModel<TmfEventTableColumnDataModel>> response = fetchTree(parameters, monitor);
TmfTreeModel<TmfEventTableColumnDataModel> model = response.getModel();
List<TmfEventTableColumnDataModel> treeModel = null;
if (model != null) {
treeModel = model.getEntries();
}
return new TmfModelResponse<>(treeModel, response.getStatus(), response.getStatusMessage());
}
@Override
public TmfModelResponse<TmfTreeModel<TmfEventTableColumnDataModel>> fetchTree(Map<String, Object> fetchParameters, @Nullable IProgressMonitor monitor) {
List<TmfEventTableColumnDataModel> model = new ArrayList<>();
for (ITmfEventAspect<?> aspect : getTraceAspects(getTrace())) {
synchronized (fAspectToIdMap) {
long id = fAspectToIdMap.computeIfAbsent(aspect, a -> fAtomicLong.getAndIncrement());
fIdToAspectMap.putIfAbsent(id, aspect);
model.add(new TmfEventTableColumnDataModel(id, -1, Collections.singletonList(aspect.getName()), aspect.getHelpText(), aspect.isHiddenByDefault()));
}
}
return new TmfModelResponse<>(new TmfTreeModel<>(Collections.emptyList(), model), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
}
@Override
public TmfModelResponse<ITmfVirtualTableModel<EventTableLine>> fetchLines(Map<String, Object> fetchParameters, @Nullable IProgressMonitor monitor) {
VirtualTableQueryFilter queryFilter = FetchParametersUtils.createVirtualTableQueryFilter(fetchParameters);
if (queryFilter == null) {
return new TmfModelResponse<>(null, ITmfResponse.Status.FAILED, CommonStatusMessage.INCORRECT_QUERY_PARAMETERS);
}
@Nullable ITmfFilter filter = extractFilter(fetchParameters);
@Nullable ITmfFilter searchFilter = extractSearchFilter(fetchParameters);
@Nullable TmfCollapseFilter collapseFilter = extractCollapseFilter(fetchParameters);
Map<Long, ITmfEventAspect<?>> aspects = getAspectsFromColumnsId(queryFilter.getColumnsId());
if (aspects.isEmpty()) {
return new TmfModelResponse<>(new TmfVirtualTableModel<EventTableLine>(Collections.emptyList(), Collections.emptyList(), queryFilter.getIndex(), 0), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
}
boolean forwardSearch = queryFilter.getCount() >= 0;
TableEventRequest request;
if (filter != null) {
request = filteredTableRequest(Math.abs(queryFilter.getCount()), queryFilter.getIndex(), aspects, filter, searchFilter, forwardSearch, collapseFilter, monitor);
} else {
request = tableRequest(Math.abs(queryFilter.getCount()), queryFilter.getIndex(), aspects, searchFilter, forwardSearch, collapseFilter, monitor);
request.setEventCount(getTrace().getNbEvents());
}
getTrace().sendRequest(request);
try {
request.waitForCompletion();
} catch (InterruptedException e) {
return new TmfModelResponse<>(null, ITmfResponse.Status.FAILED, e.getMessage());
}
if (request.isCancelled()) {
return new TmfModelResponse<>(null, ITmfResponse.Status.CANCELLED, CommonStatusMessage.TASK_CANCELLED);
}
List<Long> columnsIds = new ArrayList<>(aspects.keySet());
TmfVirtualTableModel<EventTableLine> model = new TmfVirtualTableModel<>(columnsIds, request.getEventLines(), queryFilter.getIndex(), request.getCurrentCount());
return new TmfModelResponse<>(model, ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
}
/**
* Find the index in the table of an event using the rank in the trace or
* the timestamp value. It will take any filter into consideration.
*
* @param fetchParameters
* Map of parameters that contain the filter applied to the
* table, if any. Everything else is ignored.
* @param traceRank
* Rank of the event in the trace
* @param timeBegin
* Timestamp of the event
* @param monitor
* Progress monitor
* @return Index in the table
*/
public TmfModelResponse<List<Long>> fetchIndex(Map<String, Object> fetchParameters, long traceRank, long timeBegin, @Nullable IProgressMonitor monitor) {
@Nullable ITmfFilter filter = extractFilter(fetchParameters);
long rank;
if (traceRank == -1) {
ITmfContext context = getTrace().seekEvent(TmfTimestamp.fromNanos(timeBegin));
rank = context.getRank();
} else {
rank = traceRank;
}
if (filter == null) {
return new TmfModelResponse<>(Collections.singletonList(rank), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
}
applyFilter(filter);
Entry<Long, Long> nearestEntry = fRankToIndexMap.floorEntry(rank);
long startingIndex = nearestEntry != null ? nearestEntry.getValue() : 0L;
long startingRank = nearestEntry != null ? nearestEntry.getKey() : 0L;
List<Long> foundIndex = new ArrayList<>();
TmfEventRequest request = new TmfEventRequest(ITmfEvent.class, TmfTimeRange.ETERNITY, startingRank, ITmfEventRequest.ALL_DATA, ExecutionType.FOREGROUND) {
private long currentIndex = startingIndex;
private long fRank = startingRank;
@Override
public void handleData(ITmfEvent event) {
super.handleData(event);
if (monitor != null && monitor.isCanceled()) {
cancel();
return;
}
if (fRank >= rank) {
foundIndex.add(currentIndex);
done();
return;
}
if (filter.matches(event)) {
currentIndex++;
}
fRank++;
}
};
getTrace().sendRequest(request);
try {
request.waitForCompletion();
} catch (InterruptedException e) {
return new TmfModelResponse<>(null, ITmfResponse.Status.FAILED, e.getMessage());
}
return new TmfModelResponse<>(foundIndex, ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
}
private abstract class TableEventRequest extends TmfEventRequest {
private long fEventCount = 0L;
private List<EventTableLine> fEventLines = new ArrayList<>();
public TableEventRequest(long startingRank) {
super(ITmfEvent.class, TmfTimeRange.ETERNITY, startingRank, ITmfEventRequest.ALL_DATA, ExecutionType.FOREGROUND);
}
public void incrementCount() {
fEventCount++;
}
public void setEventCount(long count) {
fEventCount = count;
}
public long getCurrentCount() {
return fEventCount;
}
public List<EventTableLine> getEventLines() {
return fEventLines;
}
}
private TableEventRequest filteredTableRequest(int queryCount,
long queryIndex,
Map<Long, ITmfEventAspect<?>> aspects,
ITmfFilter filter,
@Nullable ITmfFilter searchFilter,
boolean forwardSearch,
@Nullable ITmfFilter collapseFilter,
@Nullable IProgressMonitor monitor) {
applyFilter(filter);
Entry<Long, Long> nearestEntry = fIndexToRankMap.floorEntry(queryIndex);
long startingRank = nearestEntry != null ? nearestEntry.getValue() : 0L;
Long startingIndex = nearestEntry != null ? nearestEntry.getKey() : 0L;
return new TableEventRequest(startingRank) {
private long currentIndex = startingIndex;
private long rank = startingRank;
@Override
public void handleData(@NonNull ITmfEvent event) {
super.handleData(event);
if (monitor != null && monitor.isCanceled()) {
cancel();
return;
}
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) {
events.add(buildEventTableLine(aspects, event, currentIndex, rank));
}
if (currentIndex % INDEX_STORING_INTERVAL == 0) {
fIndexToRankMap.put(currentIndex, rank);
fRankToIndexMap.put(rank, currentIndex);
}
}
currentIndex++;
incrementCount();
} else if (collapseFilter != null && !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;
EventTableLine prevLine = events.get(lastIndex);
long prevRepeatCount = prevLine.getRepeatCount();
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;
}
rank++;
}
@Override
public long getCurrentCount() {
long currentCount = super.getCurrentCount();
if (fFilteredCount == -1L) {
fFilteredCount = currentCount;
}
return fFilteredCount;
}
};
}
private TableEventRequest tableRequest(int queryCount,
long queryIndex,
Map<Long, ITmfEventAspect<?>> aspects,
@Nullable ITmfFilter searchFilter,
boolean forwardSearch,
@Nullable ITmfFilter collapseFilter,
@Nullable IProgressMonitor monitor) {
return new TableEventRequest(queryIndex) {
private long rank = queryIndex;
@Override
public void handleData(ITmfEvent event) {
super.handleData(event);
if (monitor != null && monitor.isCanceled()) {
cancel();
return;
}
List<EventTableLine> events = getEventLines();
if ((searchFilter == null || searchFilter.matches(event)) && (collapseFilter == null || collapseFilter.matches(event))) {
if (events.size() < queryCount) {
events.add(buildEventTableLine(aspects, event, rank, rank));
}
} else if (collapseFilter != null && !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;
EventTableLine prevLine = events.get(lastIndex);
long prevRepeatCount = prevLine.getRepeatCount();
events.set(lastIndex, new EventTableLine(prevLine.getCells(), prevLine.getIndex(), prevLine.getTimestamp(), prevLine.getRank(), prevRepeatCount++));
}
if ((searchFilter != null && !forwardSearch && getNbRead() == queryCount) || events.size() == queryCount) {
done();
return;
}
rank++;
}
};
}
/**
* Build an event line using the given aspects.
*
* @param aspects
* Aspects to resolve
* @param event
* The event used to resolve the aspect
* @param lineIndex
* Line index
* @param lineRank
* rank in the trace
* @return A new event table line
*/
private static EventTableLine buildEventTableLine(Map<Long, ITmfEventAspect<?>> aspects, ITmfEvent event, long lineIndex, long lineRank) {
List<VirtualTableCell> entry = new ArrayList<>(aspects.size());
for (Entry<Long, ITmfEventAspect<?>> aspectEntry : aspects.entrySet()) {
Object aspectResolved = aspectEntry.getValue().resolve(event);
String cellContent = aspectResolved == null ? StringUtils.EMPTY : String.valueOf(aspectResolved);
entry.add(new VirtualTableCell(cellContent));
}
return new EventTableLine(entry, lineIndex, event.getTimestamp(), lineRank, 0);
}
/**
* Apply a filter to this provider that will be used for future request. If the
* current filter is null or not equal to the provided filter, the IndexToRank
* and RankToIndex maps are cleared
*
* @param filter
* Filter to apply
*/
private void applyFilter(ITmfFilter filter) {
if (!filter.equals(fFilter)) {
fFilter = filter;
fIndexToRankMap.clear();
fRankToIndexMap.clear();
fFilteredCount = -1L;
}
}
/**
* We want to resolve only the columns that will be shown. This limits resolve
* operation and model size. If list of desired columns is empty, we retrieve
* all columns by default
*
* @param desiredColumns
* The list of desired columns name that we want to
* @return The list of {@link ITmfEventAspect} that match the desired columns
* name
*/
private static Map<Long, ITmfEventAspect<?>> getAspectsFromColumnsId(List<Long> desiredColumns) {
Map<Long, ITmfEventAspect<?>> aspects = new LinkedHashMap<>();
if (!desiredColumns.isEmpty()) {
for (Long columnsId : desiredColumns) {
ITmfEventAspect<?> aspect = fIdToAspectMap.get(columnsId);
if (aspect != null) {
aspects.put(columnsId, aspect);
}
}
return aspects;
}
return fIdToAspectMap;
}
private static @Nullable ITmfFilter extractFilter(Map<String, Object> fetchParameters) {
Object filtersObject = fetchParameters.get(TABLE_FILTERS_KEY);
if (filtersObject instanceof ITmfFilterModel) {
ITmfFilterModel filters = (ITmfFilterModel) filtersObject;
Map<Long, String> filterMap = filters.getTableFilter();
List<String> presetFilter = filters.getPresetFilter();
if ((filterMap == null || filterMap.isEmpty()) && (presetFilter == null || presetFilter.isEmpty())) {
return null;
}
TmfFilterRootNode rootFilter = new TmfFilterRootNode();
if (filterMap != null && !filterMap.isEmpty()) {
for (Entry<Long, String> filterEntry : filterMap.entrySet()) {
TmfFilterMatchesNode filterNode = new TmfFilterMatchesNode(null);
ITmfEventAspect<?> aspect = fIdToAspectMap.get(filterEntry.getKey());
if (aspect == null) {
return null;
}
filterNode.setEventAspect(aspect);
filterNode.setRegex(filterEntry.getValue());
rootFilter.addChild(filterNode);
}
}
if (presetFilter != null && !presetFilter.isEmpty()) {
ITmfFilterTreeNode[] savedFilters = FilterManager.getSavedFilters();
for (ITmfFilterTreeNode filterNode : savedFilters) {
if (filterNode instanceof TmfFilterNode) {
for (String presetFilterName : presetFilter) {
if (((TmfFilterNode) filterNode).getFilterName().equals(presetFilterName)) {
rootFilter.addChild(filterNode);
}
}
}
}
}
return rootFilter;
}
return null;
}
private static @Nullable ITmfFilter extractSearchFilter(Map<String, Object> fetchParameters) {
Object searchFilterObject = fetchParameters.get(TABLE_SEARCH_KEY);
if (searchFilterObject instanceof Map<?, ?>) {
Map<Long, String> searchMap = (Map<Long, String>) searchFilterObject;
if (searchMap.isEmpty()) {
return null;
}
TmfFilterRootNode rootFilter = new TmfFilterRootNode();
for (Entry<Long, String> searchEntry : searchMap.entrySet()) {
TmfFilterMatchesNode searchNode = new TmfFilterMatchesNode(rootFilter);
ITmfEventAspect<?> aspect = fIdToAspectMap.get(searchEntry.getKey());
if (aspect == null) {
return null;
}
searchNode.setEventAspect(aspect);
searchNode.setRegex(searchEntry.getValue());
}
return rootFilter;
}
return null;
}
private static @Nullable TmfCollapseFilter extractCollapseFilter(Map<String, Object> fetchParameters) {
Object filtersObject = fetchParameters.get(TABLE_FILTERS_KEY);
if (filtersObject instanceof ITmfFilterModel) {
ITmfFilterModel filters = (ITmfFilterModel) filtersObject;
if (filters.isCollapseFilter()) {
return new TmfCollapseFilter();
}
return null;
}
return null;
}
private static Iterable<ITmfEventAspect<?>> getTraceAspects(ITmfTrace trace) {
if (trace instanceof TmfExperiment) {
return getExperimentAspects((TmfExperiment) trace);
}
return trace.getEventAspects();
}
/**
* Get the events table for an experiment. If all traces in the experiment are
* of the same type, use the same behavior as if it was one trace of that type.
*
* @param experiment
* the experiment
* @param parent
* the parent Composite
* @param cacheSize
* the event table cache size
* @return An event table of the appropriate type
*/
private static Iterable<ITmfEventAspect<?>> getExperimentAspects(TmfExperiment experiment) {
List<ITmfTrace> traces = experiment.getTraces();
ImmutableSet.Builder<ITmfEventAspect<?>> builder = new ImmutableSet.Builder<>();
/* For experiments, we'll add a "trace name" aspect/column */
builder.add(TmfBaseAspects.getTraceNameAspect());
if (hasCommonTraceType(experiment)) {
/*
* All the traces in this experiment are of the same type, let's just use the
* normal table for that type.
*/
builder.addAll(traces.get(0).getEventAspects());
} else {
/*
* There are different trace types in the experiment, so we are definitely using
* a TmfEventsTable. Aggregate the columns from all trace types.
*/
for (ITmfTrace trace : traces) {
builder.addAll(trace.getEventAspects());
}
}
return builder.build();
}
/**
* Check if an experiment contains traces of all the same type. If so, returns
* this type as a String. If not, returns null.
*
* @param experiment
* The experiment
* @return The common trace type if there is one, or 'null' if there are
* different types.
*/
private static boolean hasCommonTraceType(TmfExperiment experiment) {
@Nullable String commonTraceType = null;
for (ITmfTrace trace : experiment.getTraces()) {
String traceType = trace.getTraceTypeId();
if (commonTraceType != null && !commonTraceType.equals(traceType)) {
return false;
}
commonTraceType = traceType;
}
return commonTraceType != null;
}
}