blob: cfca630e758ae175b186c93a52cc7863040ec80c [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.tmf.analysis.xml.core.module;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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.tmf.analysis.xml.core.Activator;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.compile.AnalysisCompilationData;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.compile.TmfXmlStateSystemPathCu;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.DataDrivenStateSystemPath;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.module.XmlTimeGraphEntryModel.Builder;
import org.eclipse.tracecompass.internal.tmf.core.model.AbstractTmfTraceDataProvider;
import org.eclipse.tracecompass.internal.tmf.core.model.filters.FetchParametersUtils;
import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException;
import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException;
import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval;
import org.eclipse.tracecompass.tmf.analysis.xml.core.module.TmfXmlStrings;
import org.eclipse.tracecompass.tmf.analysis.xml.core.module.TmfXmlUtils;
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.filters.SelectionTimeQueryFilter;
import org.eclipse.tracecompass.tmf.core.model.filters.TimeQueryFilter;
import org.eclipse.tracecompass.tmf.core.model.timegraph.ITimeGraphArrow;
import org.eclipse.tracecompass.tmf.core.model.timegraph.ITimeGraphDataProvider;
import org.eclipse.tracecompass.tmf.core.model.timegraph.ITimeGraphRowModel;
import org.eclipse.tracecompass.tmf.core.model.timegraph.ITimeGraphState;
import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphModel;
import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphRowModel;
import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphState;
import org.eclipse.tracecompass.tmf.core.model.tree.TmfTreeModel;
import org.eclipse.tracecompass.tmf.core.response.ITmfResponse;
import org.eclipse.tracecompass.tmf.core.response.ITmfResponse.Status;
import org.eclipse.tracecompass.tmf.core.response.TmfModelResponse;
import org.eclipse.tracecompass.tmf.core.statesystem.ITmfAnalysisModuleWithStateSystems;
import org.eclipse.tracecompass.tmf.core.statesystem.TmfStateSystemAnalysisModule;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
import org.eclipse.tracecompass.tmf.core.util.Pair;
import org.w3c.dom.Element;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.collect.Table;
/**
* This data provider will return time graph models (wrapped in a response)
* based on a query filter. The models can be used afterwards by any viewer to
* draw time graphs. Model returned is for XML analysis.
*
* @author Loic Prieur-Drevon
* @since 3.0
*/
public class XmlTimeGraphDataProvider extends AbstractTmfTraceDataProvider implements ITimeGraphDataProvider<@NonNull XmlTimeGraphEntryModel> {
/**
* Provider unique ID.
*/
public static final @NonNull String ID = "org.eclipse.tracecompass.tmf.analysis.xml.core.module.XmlTimeGraphDataProvider"; //$NON-NLS-1$
private static final AtomicLong sfAtomicId = new AtomicLong();
private static final @NonNull String SPLIT_STRING = "/"; //$NON-NLS-1$
private final List<ITmfStateSystem> fSs;
private final List<Element> fEntries;
/**
* Remember the unique mappings of state system and quark to entry ID.
*/
private final Table<ITmfStateSystem, Integer, Long> fBaseQuarkToId = HashBasedTable.create();
private final Map<Long, Pair<ITmfStateSystem, Integer>> fIDToDisplayQuark = new HashMap<>();
private final @NonNull AnalysisCompilationData fCompilationData;
/**
* {@link XmlTimeGraphDataProvider} create method
*
* @param trace
* the trace for which the provider will return data
* @param viewElement
* the XML view {@link Element}.
* @return the relevant {@link XmlTimeGraphDataProvider}
*/
public static XmlTimeGraphDataProvider create(@NonNull ITmfTrace trace, Element viewElement) {
Set<@NonNull String> analysisIds = TmfXmlUtils.getViewAnalysisIds(viewElement);
List<Element> entries = TmfXmlUtils.getChildElements(viewElement, TmfXmlStrings.ENTRY_ELEMENT);
Set<@NonNull ITmfAnalysisModuleWithStateSystems> stateSystemModules = new HashSet<>();
if (analysisIds.isEmpty()) {
/*
* No analysis specified, take all state system analysis modules
*/
Iterables.addAll(stateSystemModules, TmfTraceUtils.getAnalysisModulesOfClass(trace, ITmfAnalysisModuleWithStateSystems.class));
} else {
for (String moduleId : analysisIds) {
// Get the module for the current trace only. The caller will take care of
// generating composite providers with experiments
IAnalysisModule module = trace.getAnalysisModule(moduleId);
if (module instanceof ITmfAnalysisModuleWithStateSystems) {
stateSystemModules.add((ITmfAnalysisModuleWithStateSystems) module);
}
}
}
List<ITmfStateSystem> sss = new ArrayList<>();
for (ITmfAnalysisModuleWithStateSystems module : stateSystemModules) {
if (module.schedule().isOK() && module.waitForInitialization()) {
module.getStateSystems().forEach(sss::add);
}
}
return (sss.isEmpty() ? null : new XmlTimeGraphDataProvider(trace, sss, entries));
}
private XmlTimeGraphDataProvider(@NonNull ITmfTrace trace, List<ITmfStateSystem> stateSystems, List<Element> entries) {
super(trace);
fSs = stateSystems;
fEntries = entries;
fCompilationData = new AnalysisCompilationData();
}
@Override
public TmfModelResponse<TmfTreeModel<@NonNull XmlTimeGraphEntryModel>> fetchTree(Map<String, Object> fetchParameters, @Nullable IProgressMonitor monitor) {
@NonNull List<@NonNull XmlTimeGraphEntryModel> entryList = new ArrayList<>();
boolean isComplete = true;
String traceName = String.valueOf(getTrace().getName());
for (ITmfStateSystem ss : fSs) {
isComplete &= ss.waitUntilBuilt(0);
/* Don't query empty state system */
if (ss.getNbAttributes() > 0 && ss.getStartTime() != Long.MIN_VALUE) {
long start = ss.getStartTime();
long end = ss.getCurrentEndTime();
long id = fBaseQuarkToId.row(ss).computeIfAbsent(ITmfStateSystem.ROOT_ATTRIBUTE, s -> sfAtomicId.getAndIncrement());
Builder ssEntry = new Builder(id, -1, Collections.singletonList(traceName), start, end, null, ss, ITmfStateSystem.ROOT_ATTRIBUTE, fCompilationData);
entryList.add(ssEntry.build());
for (Element entry : fEntries) {
buildEntry(ss, entry, ssEntry, -1, StringUtils.EMPTY, end, entryList);
}
}
}
Status status = isComplete ? Status.COMPLETED : Status.RUNNING;
String msg = isComplete ? CommonStatusMessage.COMPLETED : CommonStatusMessage.RUNNING;
return new TmfModelResponse<>(new TmfTreeModel<>(Collections.emptyList(), entryList), status, msg);
}
private void buildEntry(ITmfStateSystem ssq, Element entryElement, @NonNull Builder parentEntry,
int prevBaseQuark, @NonNull String prevRegex, long currentEnd, List<XmlTimeGraphEntryModel> entryList) {
/* Get the attribute string to display */
String path = entryElement.getAttribute(TmfXmlStrings.PATH);
if (path.isEmpty()) {
path = TmfXmlStrings.WILDCARD;
}
/*
* Make sure the XML element has either a display attribute or entries,
* otherwise issue a warning
*/
List<Element> displayElements = TmfXmlUtils.getChildElements(entryElement, TmfXmlStrings.DISPLAY_ELEMENT);
List<Element> entryElements = TmfXmlUtils.getChildElements(entryElement, TmfXmlStrings.ENTRY_ELEMENT);
if (displayElements.isEmpty() && entryElements.isEmpty()) {
Activator.logWarning(String.format("XML view: entry for %s should have either a display element or entry elements", path)); //$NON-NLS-1$
return;
}
// Get the state system to use to populate those entries, by default, it
// is the same as the parent
String analysisId = entryElement.getAttribute(TmfXmlStrings.ANALYSIS_ID);
ITmfStateSystem parentSs = ssq;
ITmfStateSystem ss = parentSs;
int baseQuark = prevBaseQuark;
if (!analysisId.isEmpty()) {
ss = TmfStateSystemAnalysisModule.getStateSystem(getTrace(), analysisId);
baseQuark = ITmfStateSystem.ROOT_ATTRIBUTE;
if (ss == null) {
return;
}
}
// Replace any place holders in the path
Pattern pattern = Pattern.compile(prevRegex);
String attributePath = prevBaseQuark > 0 ? parentSs.getFullAttributePath(prevBaseQuark) : StringUtils.EMPTY;
Matcher matcher = pattern.matcher(attributePath);
if (matcher.find()) {
path = matcher.replaceFirst(path);
}
String regexName = path.replaceAll("\\*", "(.*)"); //$NON-NLS-1$//$NON-NLS-2$
/* Get the list of quarks to process with this path */
String[] paths = regexName.split(SPLIT_STRING);
int i = 0;
List<Integer> quarks = Collections.singletonList(baseQuark);
while (i < paths.length) {
List<Integer> subQuarks = new LinkedList<>();
/* Replace * by .* to have a regex string */
String name = paths[i];
for (int relativeQuark : quarks) {
subQuarks.addAll(ss.getSubAttributes(relativeQuark, false, name));
}
quarks = subQuarks;
i++;
}
/* Process each quark */
DataDrivenStateSystemPath displayPath = null;
Map<String, Builder> entryMap = new HashMap<>();
if (!displayElements.isEmpty()) {
Element displayElement = displayElements.get(0);
TmfXmlStateSystemPathCu displayCu = TmfXmlStateSystemPathCu.compile(parentEntry.getAnalysisCompilationData(), Collections.singletonList(displayElement));
if (displayCu != null) {
displayPath = displayCu.generate();
}
}
for (int quark : quarks) {
Builder currentEntry = parentEntry;
/* Process the current entry, if specified */
if (displayPath != null) {
currentEntry = processEntry(entryElement, displayPath, parentEntry, quark, ss, currentEnd);
entryMap.put(currentEntry.getXmlId(), currentEntry);
} else {
long id = fBaseQuarkToId.row(ss).computeIfAbsent(quark, s -> sfAtomicId.getAndIncrement());
currentEntry = new Builder(id, parentEntry.getId(),
Collections.singletonList(ss.getAttributeName(quark)), ss.getStartTime(), ss.getCurrentEndTime(), null, ss, quark, fCompilationData);
entryMap.put(currentEntry.getXmlId(), currentEntry);
}
/* Process the children entry of this entry */
for (Element subEntryEl : entryElements) {
String regex = prevRegex.isEmpty() ? regexName : prevRegex + '/' + regexName;
buildEntry(ss, subEntryEl, currentEntry, quark, regex, currentEnd, entryList);
}
}
// At this point, the parent has been set, so we can build the entries
buildTree(entryMap, parentEntry.getId());
for (Builder b : entryMap.values()) {
entryList.add(b.build());
}
}
private Builder processEntry(@NonNull Element entryElement, DataDrivenStateSystemPath displayPath,
@NonNull Builder parentEntry, int quark, ITmfStateSystem ss, long currentEnd) {
/*
* Get the start time and end time of this entry from the display attribute
*/
int displayQuark = displayPath.getQuark(quark, parentEntry);
long id = fBaseQuarkToId.row(ss).computeIfAbsent(quark, s -> sfAtomicId.getAndIncrement());
if (displayQuark < 0) {
return new Builder(id, parentEntry.getId(),
Collections.singletonList(String.format("Unknown display quark for %s", ss.getAttributeName(quark))), ss.getStartTime(), ss.getCurrentEndTime(), null, ss, quark, fCompilationData); //$NON-NLS-1$
}
fIDToDisplayQuark.put(id, new Pair<>(ss, displayQuark));
long entryStart = ss.getStartTime();
long entryEnd = currentEnd;
try {
ITmfStateInterval oneInterval = ss.querySingleState(entryStart, displayQuark);
/* The entry start is the first non-null interval */
while (oneInterval.getStateValue().isNull()) {
long ts = oneInterval.getEndTime() + 1;
if (ts > currentEnd) {
break;
}
oneInterval = ss.querySingleState(ts, displayQuark);
}
entryStart = oneInterval.getStartTime();
/* The entry end is the last non-null interval */
oneInterval = ss.querySingleState(entryEnd - 1, displayQuark);
while (oneInterval.getStateValue().isNull()) {
long ts = oneInterval.getStartTime() - 1;
if (ts < ss.getStartTime()) {
break;
}
oneInterval = ss.querySingleState(ts, displayQuark);
}
entryEnd = Math.min(oneInterval.getEndTime() + 1, currentEnd);
} catch (StateSystemDisposedException e) {
}
return new Builder(id, parentEntry.getId(), Collections.singletonList(ss.getAttributeName(quark)), entryStart, entryEnd, entryElement, ss, quark, fCompilationData);
}
/** Build a tree using getParentId() and getId() */
private static void buildTree(Map<String, Builder> entryMap, long parentId) {
for (Builder entry : entryMap.values()) {
boolean root = true;
if (!entry.getXmlParentId().isEmpty()) {
Builder parent = entryMap.get(entry.getXmlParentId());
/*
* Associate the parent entry only if their time overlap. A child entry may
* start before its parent, for example at the beginning of the trace if a
* parent has not yet appeared in the state system. We just want to make sure
* that the entry didn't start after the parent ended or ended before the parent
* started.
*/
if (parent != null &&
!(entry.getStartTime() > parent.getEndTime() ||
entry.getEndTime() < parent.getStartTime())) {
entry.setParentId(parent.getId());
root = false;
}
}
if (root) {
entry.setParentId(parentId);
}
}
}
@Override
public @NonNull String getId() {
return ID;
}
@Override
public @NonNull TmfModelResponse<@NonNull TimeGraphModel> fetchRowModel(@NonNull Map<@NonNull String, @NonNull Object> fetchParameters, @Nullable IProgressMonitor monitor) {
Table<ITmfStateSystem, Integer, Long> table = HashBasedTable.create();
// TODO server: Parameters validation should be handle separately. It
// can be either in the data provider itself or before calling it. It
// will avoid the creation of filters and the content of the map can be
// use directly.
SelectionTimeQueryFilter filter = FetchParametersUtils.createSelectionTimeQuery(fetchParameters);
if (filter == null) {
return new TmfModelResponse<>(null, ITmfResponse.Status.FAILED, CommonStatusMessage.INCORRECT_QUERY_PARAMETERS);
}
for (Long id : filter.getSelectedItems()) {
Pair<ITmfStateSystem, Integer> pair = fIDToDisplayQuark.get(id);
if (pair != null) {
table.put(pair.getFirst(), pair.getSecond(), id);
}
}
List<@NonNull ITimeGraphRowModel> allRows = new ArrayList<>();
try {
for (Entry<ITmfStateSystem, Map<Integer, Long>> ssEntry : table.rowMap().entrySet()) {
Collection<@NonNull ITimeGraphRowModel> rows = createRows(ssEntry.getKey(), ssEntry.getValue(), filter.getTimesRequested(), fetchParameters, monitor);
allRows.addAll(rows);
}
} catch (IndexOutOfBoundsException | TimeRangeException | StateSystemDisposedException e) {
return new TmfModelResponse<>(null, Status.FAILED, CommonStatusMessage.STATE_SYSTEM_FAILED);
}
return new TmfModelResponse<>(new TimeGraphModel(allRows), Status.COMPLETED, CommonStatusMessage.COMPLETED);
}
private @NonNull Collection<@NonNull ITimeGraphRowModel> createRows(ITmfStateSystem ss, Map<Integer, Long> idToDisplayQuark,
long[] timesRequested, @NonNull Map<@NonNull String, @NonNull Object> parameters, @Nullable IProgressMonitor monitor) throws StateSystemDisposedException {
Map<@NonNull Integer, @NonNull Predicate<@NonNull Multimap<@NonNull String, @NonNull Object>>> predicates = new HashMap<>();
Multimap<@NonNull Integer, @NonNull String> regexesMap = DataProviderParameterUtils.extractRegexFilter(parameters);
if (regexesMap != null) {
predicates.putAll(computeRegexPredicate(regexesMap));
}
long currentEndTime = ss.getCurrentEndTime();
Map<Integer, ITimeGraphRowModel> quarkToRow = new HashMap<>(idToDisplayQuark.size());
for (Entry<Integer, Long> entry : idToDisplayQuark.entrySet()) {
quarkToRow.put(entry.getKey(), new TimeGraphRowModel(entry.getValue(), new ArrayList<>()));
}
for (ITmfStateInterval interval : ss.query2D(idToDisplayQuark.keySet(), getTimes(ss, timesRequested))) {
if (monitor != null && monitor.isCanceled()) {
return Collections.emptyList();
}
ITimeGraphRowModel row = quarkToRow.get(interval.getAttribute());
if (row != null) {
List<@NonNull ITimeGraphState> states = row.getStates();
ITimeGraphState timeGraphState = getStateFromInterval(interval, currentEndTime);
applyFilterAndAddState(states, timeGraphState, row.getEntryID(), predicates, monitor);
}
}
for (ITimeGraphRowModel model : quarkToRow.values()) {
model.getStates().sort(Comparator.comparingLong(ITimeGraphState::getStartTime));
}
return quarkToRow.values();
}
private static @NonNull TimeGraphState getStateFromInterval(ITmfStateInterval statusInterval, long currentEndTime) {
long time = statusInterval.getStartTime();
long duration = Math.min(currentEndTime, statusInterval.getEndTime() + 1) - time;
Object o = statusInterval.getValue();
if (o instanceof Integer) {
return new TimeGraphState(time, duration, ((Integer) o).intValue(), String.valueOf(o));
} else if (o instanceof Long) {
long l = (long) o;
return new TimeGraphState(time, duration, (int) l, "0x" + Long.toHexString(l)); //$NON-NLS-1$
} else if (o instanceof String) {
return new TimeGraphState(time, duration, Integer.MIN_VALUE, (String) o);
} else if (o instanceof Double) {
return new TimeGraphState(time, duration, ((Double) o).intValue());
}
return new TimeGraphState(time, duration, Integer.MIN_VALUE);
}
private static @NonNull Set<@NonNull Long> getTimes(ITmfStateSystem key, long[] timesRequested) {
Set<@NonNull Long> times = new HashSet<>();
for (long t : timesRequested) {
if (key.getStartTime() <= t && t <= key.getCurrentEndTime()) {
times.add(t);
}
}
return times;
}
@Override
public @NonNull TmfModelResponse<@NonNull List<@NonNull ITimeGraphArrow>> fetchArrows(Map<String, Object> fetchParameters, @Nullable IProgressMonitor monitor) {
return new TmfModelResponse<>(null, Status.COMPLETED, CommonStatusMessage.COMPLETED);
}
@Override
public @NonNull TmfModelResponse<@NonNull Map<@NonNull String, @NonNull String>> fetchTooltip(Map<String, Object> fetchParameters, @Nullable IProgressMonitor monitor) {
return new TmfModelResponse<>(null, Status.COMPLETED, CommonStatusMessage.COMPLETED);
}
@Deprecated
@Override
public TmfModelResponse<List<XmlTimeGraphEntryModel>> fetchTree(@NonNull TimeQueryFilter filter, @Nullable IProgressMonitor monitor) {
@NonNull Map<@NonNull String, @NonNull Object> parameters = FetchParametersUtils.timeQueryToMap(filter);
TmfModelResponse<TmfTreeModel<@NonNull XmlTimeGraphEntryModel>> response = fetchTree(parameters, monitor);
TmfTreeModel<@NonNull XmlTimeGraphEntryModel> model = response.getModel();
List<XmlTimeGraphEntryModel> treeModel = null;
if (model != null) {
treeModel = model.getEntries();
}
return new TmfModelResponse<>(treeModel, response.getStatus(), response.getStatusMessage());
}
@Deprecated
@Override
public @NonNull TmfModelResponse<@NonNull List<@NonNull ITimeGraphRowModel>> fetchRowModel(@NonNull SelectionTimeQueryFilter filter, @Nullable IProgressMonitor monitor) {
@NonNull Map<@NonNull String, @NonNull Object> parameters = FetchParametersUtils.selectionTimeQueryToMap(filter);
TmfModelResponse<@NonNull TimeGraphModel> response = fetchRowModel(parameters, monitor);
TimeGraphModel model = response.getModel();
List<@NonNull ITimeGraphRowModel> rows = null;
if (model != null) {
rows = model.getRows();
}
return new TmfModelResponse<>(rows, response.getStatus(), response.getStatusMessage());
}
@Deprecated
@Override
public @NonNull TmfModelResponse<@NonNull List<@NonNull ITimeGraphArrow>> fetchArrows(@NonNull TimeQueryFilter filter, @Nullable IProgressMonitor monitor) {
Map<String, Object> parameters = FetchParametersUtils.timeQueryToMap(filter);
return fetchArrows(parameters, monitor);
}
@Deprecated
@Override
public @NonNull TmfModelResponse<@NonNull Map<@NonNull String, @NonNull String>> fetchTooltip(@NonNull SelectionTimeQueryFilter filter, @Nullable IProgressMonitor monitor) {
Map<String, Object> parameters = FetchParametersUtils.selectionTimeQueryToMap(filter);
return fetchTooltip(parameters, monitor);
}
}