blob: 548671b7e94bea108ad52b4d1b7844ce85bfd9e0 [file] [log] [blame]
/**********************************************************************
* Copyright (c) 2017 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.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.Messages;
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.core.model.AbstractTmfTraceDataProvider;
import org.eclipse.tracecompass.internal.tmf.core.model.TmfXyResponseFactory;
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.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.model.CommonStatusMessage;
import org.eclipse.tracecompass.tmf.core.model.YModel;
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.tree.ITmfTreeDataModel;
import org.eclipse.tracecompass.tmf.core.model.tree.TmfTreeDataModel;
import org.eclipse.tracecompass.tmf.core.model.tree.TmfTreeModel;
import org.eclipse.tracecompass.tmf.core.model.xy.ITmfTreeXYDataProvider;
import org.eclipse.tracecompass.tmf.core.model.xy.ITmfXyModel;
import org.eclipse.tracecompass.tmf.core.model.xy.IYModel;
import org.eclipse.tracecompass.tmf.core.response.ITmfResponse;
import org.eclipse.tracecompass.tmf.core.response.TmfModelResponse;
import org.eclipse.tracecompass.tmf.core.statesystem.ITmfAnalysisModuleWithStateSystems;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
import org.w3c.dom.Element;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
/**
* This data provider will return a XY model (wrapped in a response) based on a
* query filter. The model can be used afterwards by any viewer to draw charts.
* Model returned is for XML analysis
*
* @author Yonni Chen
* @since 2.3
*/
@NonNullByDefault
public class XmlXYDataProvider extends AbstractTmfTraceDataProvider
implements ITmfTreeXYDataProvider<ITmfTreeDataModel> {
private static final String TITLE = Objects.requireNonNull(Messages.XmlDataProvider_DefaultXYTitle);
/**
* Data provider ID
*/
public static final String ID = "org.eclipse.tracecompass.tmf.analysis.xml.core.module.XmlXYDataProvider"; //$NON-NLS-1$
private static final String SPLIT_STRING = "/"; //$NON-NLS-1$
private static final Pattern WILDCARD_PATTERN = Pattern.compile("\\*"); //$NON-NLS-1$
private static final AtomicLong ENTRY_IDS = new AtomicLong();
private final ReentrantReadWriteLock fLock = new ReentrantReadWriteLock(false);
/**
* Two way association between quarks and entry IDs, ensures that a single ID is
* reused per every quark, and finds the quarks to query for the XY models.
*/
private final BiMap<Long, Integer> fIdToQuark = HashBiMap.create();
private final BiMap<Integer, String> fQuarkToString = HashBiMap.create();
private final long fTraceId = ENTRY_IDS.getAndIncrement();
private @Nullable TmfModelResponse<TmfTreeModel<ITmfTreeDataModel>> fCached;
private static class XmlXYEntry implements IXmlStateSystemContainer {
private final ITmfAnalysisModuleWithStateSystems fStateSystemModule;
private final String fPath;
private final DisplayType fType;
private final AnalysisCompilationData fCompilationData;
public XmlXYEntry(ITmfAnalysisModuleWithStateSystems stateSystem, String path, Element entryElement, AnalysisCompilationData compilationData) {
fStateSystemModule = stateSystem;
fPath = path;
switch (entryElement.getAttribute(TmfXmlStrings.DISPLAY_TYPE)) {
case TmfXmlStrings.DISPLAY_TYPE_DELTA:
fType = DisplayType.DELTA;
break;
case TmfXmlStrings.DISPLAY_TYPE_ABSOLUTE:
default:
fType = DisplayType.ABSOLUTE;
break;
}
fCompilationData = compilationData;
}
@Override
public @Nullable String getAttributeValue(@Nullable String name) {
// Method must be overridden
return name;
}
@Override
public ITmfStateSystem getStateSystem() {
fStateSystemModule.waitForInitialization();
Iterator<ITmfStateSystem> stateSystems = fStateSystemModule.getStateSystems().iterator();
if (!stateSystems.hasNext()) {
throw new NullPointerException("Analysis " + fStateSystemModule.getId() + " has no state system"); //$NON-NLS-1$ //$NON-NLS-2$
}
return stateSystems.next();
}
public DisplayType getType() {
return fType;
}
public List<Integer> getQuarks() {
/* This is an attribute tree path and not a file path */
String[] paths = fPath.split(SPLIT_STRING);
/* Get the list of quarks to process with this path */
List<Integer> quarks = Collections.singletonList(IXmlStateSystemContainer.ROOT_QUARK);
//recursively find the paths
for (String path : paths) {
List<Integer> subQuarks = new ArrayList<>();
/* Replace * by .* to have a regex string */
String name = WILDCARD_PATTERN.matcher(path).replaceAll(".*"); //$NON-NLS-1$
for (int relativeQuark : quarks) {
subQuarks.addAll(getStateSystem().getSubAttributes(relativeQuark, false, name));
}
quarks = subQuarks;
}
return quarks;
}
@Override
public @NonNull AnalysisCompilationData getAnalysisCompilationData() {
return fCompilationData;
}
}
private enum DisplayType {
ABSOLUTE, DELTA
}
/** XML Model elements to use to create the series */
private final DataDrivenStateSystemPath fDisplay;
private final XmlXYEntry fXmlEntry;
private final @Nullable DataDrivenStateSystemPath fSeriesNameAttrib;
/**
* Constructor
*/
private XmlXYDataProvider(ITmfTrace trace, XmlXYEntry entry, DataDrivenStateSystemPath displayPath, @Nullable DataDrivenStateSystemPath seriesName) {
super(trace);
fXmlEntry = entry;
fDisplay = displayPath;
fSeriesNameAttrib = seriesName;
}
/**
* Create an instance of {@link XmlXYDataProvider}. Returns null if statesystem
* is null.
*
* @param trace
* A trace on which we are interested to fetch a model
* @param analysisIds
* A list of analysis ids used for retrieving Analysis objects
* @param entryElement
* An XML entry element
* @return A XmlDataProvider
*/
public static @Nullable XmlXYDataProvider create(ITmfTrace trace, Set<String> analysisIds, Element entryElement) {
ITmfAnalysisModuleWithStateSystems ss = getStateSystemFromAnalyses(analysisIds, trace);
if (ss == null) {
return null;
}
AnalysisCompilationData compilationData = new AnalysisCompilationData();
/*
* Initialize state attributes. There should be only one entry element for XY
* charts.
*/
String path = entryElement.hasAttribute(TmfXmlStrings.PATH) ? entryElement.getAttribute(TmfXmlStrings.PATH) : TmfXmlStrings.WILDCARD;
XmlXYEntry entry = new XmlXYEntry(ss, path, entryElement, compilationData);
/* Get the display element to use */
List<@NonNull Element> displayElements = TmfXmlUtils.getChildElements(entryElement, TmfXmlStrings.DISPLAY_ELEMENT);
if (displayElements.isEmpty()) {
return null;
}
Element displayElement = displayElements.get(0);
TmfXmlStateSystemPathCu display = TmfXmlStateSystemPathCu.compile(entry.getAnalysisCompilationData(), Collections.singletonList(displayElement));
if (display == null) {
return null;
}
/* Get the series name element to use */
List<Element> seriesNameElements = TmfXmlUtils.getChildElements(entryElement, TmfXmlStrings.NAME_ELEMENT);
DataDrivenStateSystemPath seriesName = null;
if (!seriesNameElements.isEmpty()) {
Element seriesNameElement = seriesNameElements.get(0);
TmfXmlStateSystemPathCu seriesNameCu = TmfXmlStateSystemPathCu.compile(entry.getAnalysisCompilationData(), Collections.singletonList(seriesNameElement));
if (seriesNameCu != null) {
seriesName = seriesNameCu.generate();
}
}
return new XmlXYDataProvider(trace, entry, display.generate(), seriesName);
}
@Override
public TmfModelResponse<ITmfXyModel> fetchXY(Map<String, Object> fetchParameters, @Nullable IProgressMonitor monitor) {
DataDrivenStateSystemPath display = fDisplay;
XmlXYEntry entry = fXmlEntry;
ITmfStateSystem ss = entry.getStateSystem();
TimeQueryFilter filter = FetchParametersUtils.createTimeQuery(fetchParameters);
if (filter == null) {
return TmfXyResponseFactory.createFailedResponse(CommonStatusMessage.INCORRECT_QUERY_PARAMETERS);
}
long[] xValues = filter.getTimesRequested();
Map<Integer, IYModel> map = initSeries(fetchParameters);
if (map.isEmpty()) {
return TmfXyResponseFactory.create(TITLE, xValues, Collections.emptyMap(), true);
}
long currentEnd = ss.getCurrentEndTime();
try {
for (int i = 0; i < xValues.length; i++) {
if (monitor != null && monitor.isCanceled()) {
return TmfXyResponseFactory.createCancelledResponse(CommonStatusMessage.TASK_CANCELLED);
}
long time = xValues[i];
if (time > currentEnd) {
break;
} else if (ss.getStartTime() <= time) {
List<@NonNull ITmfStateInterval> full = ss.queryFullState(time);
for (Entry<Integer, IYModel> series : map.entrySet()) {
int attributeQuark = display.getQuark(series.getKey(), entry);
if (attributeQuark >= 0 && attributeQuark < full.size()) {
Object value = full.get(attributeQuark).getValue();
series.getValue().getData()[i] = extractValue(value);
}
}
}
}
// Update the series value if delta is requested
for (Entry<Integer, IYModel> series : map.entrySet()) {
if (entry.getType().equals(DisplayType.DELTA)) {
getSeriesDelta(series.getValue().getData());
}
}
} catch (StateSystemDisposedException e) {
return TmfXyResponseFactory.createFailedResponse(e.getMessage());
}
boolean complete = ss.waitUntilBuilt(0) || filter.getEnd() <= currentEnd;
return TmfXyResponseFactory.create(TITLE, xValues, Maps.uniqueIndex(map.values(), yModel -> Long.toString(yModel.getId())), complete);
}
private Map<Integer, IYModel> initSeries(Map<String, Object> parameters) {
SelectionTimeQueryFilter filter = FetchParametersUtils.createSelectionTimeQuery(parameters);
if (filter == null) {
return Collections.emptyMap();
}
fLock.readLock().lock();
try {
Map<Integer, IYModel> map = new HashMap<>();
int length = filter.getTimesRequested().length;
for (Long id : filter.getSelectedItems()) {
Integer quark = fIdToQuark.get(id);
if (quark != null) {
String name = String.valueOf(fQuarkToString.get(quark));
map.put(quark, new YModel(id, name, new double[length]));
}
}
return map;
} finally {
fLock.readLock().unlock();
}
}
private static void getSeriesDelta(double[] data) {
double prevData = data[0];
data[0] = 0;
for (int i = 1; i < data.length; i++) {
double current = data[i];
// Update value by subtracting previous value
data[i] = current - prevData;
prevData = current;
}
data[0] = data[1];
}
private static double extractValue(@Nullable Object val) {
if (val instanceof Number) {
return ((Number) val).doubleValue();
}
return 0;
}
private static @Nullable ITmfAnalysisModuleWithStateSystems getStateSystemFromAnalyses(Set<String> analysisIds, ITmfTrace trace) {
@Nullable ITmfAnalysisModuleWithStateSystems stateSystemModule = null;
if (analysisIds.isEmpty()) {
stateSystemModule = Iterables.getFirst(TmfTraceUtils.getAnalysisModulesOfClass(trace, ITmfAnalysisModuleWithStateSystems.class), null);
} else {
for (String moduleId : analysisIds) {
ITmfAnalysisModuleWithStateSystems module = TmfTraceUtils.getAnalysisModuleOfClass(trace, ITmfAnalysisModuleWithStateSystems.class, moduleId);
if (module != null) {
stateSystemModule = module;
break;
}
}
}
if (stateSystemModule != null) {
stateSystemModule.schedule();
}
return stateSystemModule;
}
/**
* @since 2.4
*/
@Override
public TmfModelResponse<TmfTreeModel<ITmfTreeDataModel>> fetchTree(Map<String, Object> fetchParameters, @Nullable IProgressMonitor monitor) {
fLock.readLock().lock();
try {
if (fCached != null) {
return fCached;
}
} finally {
fLock.readLock().unlock();
}
ITmfStateSystem ss = fXmlEntry.getStateSystem();
DataDrivenStateSystemPath seriesNameAttrib = fSeriesNameAttrib;
boolean isComplete = ss.waitUntilBuilt(0);
// Get the quarks before the full states to ensure that the attributes will be present in the full state
List<Integer> quarks = fXmlEntry.getQuarks();
fLock.writeLock().lock();
try {
List<ITmfStateInterval> fullState = ss.queryFullState(ss.getCurrentEndTime());
ImmutableList.Builder<ITmfTreeDataModel> builder = ImmutableList.builder();
builder.add(new TmfTreeDataModel(fTraceId, -1, Collections.singletonList(getTrace().getName())));
for (int quark : quarks) {
String seriesName = ss.getAttributeName(quark);
if (seriesNameAttrib != null) {
// Use the value of the series name attribute
int seriesNameQuark = seriesNameAttrib.getQuark(quark, fXmlEntry);
Object value = fullState.get(seriesNameQuark).getValue();
if (value != null) {
seriesName = String.valueOf(value);
}
}
if (!seriesName.isEmpty()) {
String tempSeriesName = seriesName;
String uniqueName = fQuarkToString.computeIfAbsent(quark, q -> getUniqueNameFor(tempSeriesName));
// Check if an ID has already been created for this quark.
Long id = fIdToQuark.inverse().computeIfAbsent(quark, q -> ENTRY_IDS.getAndIncrement());
builder.add(new TmfTreeDataModel(id, fTraceId, Collections.singletonList(uniqueName)));
}
}
ImmutableList<ITmfTreeDataModel> list = builder.build();
if (isComplete) {
TmfModelResponse<TmfTreeModel<ITmfTreeDataModel>> tmfModelResponse = new TmfModelResponse<>(new TmfTreeModel<>(Collections.emptyList(), list), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
fCached = tmfModelResponse;
return tmfModelResponse;
}
return new TmfModelResponse<>(new TmfTreeModel<>(Collections.emptyList(), list), ITmfResponse.Status.RUNNING, CommonStatusMessage.RUNNING);
} catch (StateSystemDisposedException e) {
return new TmfModelResponse<>(null, ITmfResponse.Status.FAILED, CommonStatusMessage.STATE_SYSTEM_FAILED);
} finally {
fLock.writeLock().unlock();
}
}
private String getUniqueNameFor(String seriesName) {
Integer quark = fQuarkToString.inverse().get(seriesName);
int index = 1;
String newName = seriesName;
while (quark != null) {
newName = seriesName + '(' + index + ')';
quark = fQuarkToString.inverse().get(newName);
index++;
}
return newName;
}
/**
* @since 2.4
*/
@Override
public String getId() {
return ID;
}
@Deprecated
@Override
public TmfModelResponse<ITmfXyModel> fetchXY(TimeQueryFilter filter, @Nullable IProgressMonitor monitor) {
Map<String, Object> parameters = FetchParametersUtils.timeQueryToMap(filter);
return fetchXY(parameters, monitor);
}
@Deprecated
@Override
public TmfModelResponse<List<ITmfTreeDataModel>> fetchTree(TimeQueryFilter filter, @Nullable IProgressMonitor monitor) {
Map<String, Object> parameters = FetchParametersUtils.timeQueryToMap(filter);
TmfModelResponse<@NonNull TmfTreeModel<@NonNull ITmfTreeDataModel>> response = fetchTree(parameters, monitor);
TmfTreeModel<@NonNull ITmfTreeDataModel> model = response.getModel();
List<ITmfTreeDataModel> treeModel = null;
if (model != null) {
treeModel = model.getEntries();
}
return new TmfModelResponse<>(treeModel, response.getStatus(), response.getStatusMessage());
}
}