blob: a19c59691d1f60d08a54a34decc5391af7ff4b4b [file] [log] [blame]
/**********************************************************************
* Copyright (c) 2017, 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.analysis.counters.core;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NavigableSet;
import java.util.Objects;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.analysis.counters.core.CounterAnalysis;
import org.eclipse.tracecompass.internal.tmf.core.model.filters.FetchParametersUtils;
import org.eclipse.tracecompass.internal.tmf.core.model.xy.AbstractTreeCommonXDataProvider;
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.core.dataprovider.DataProviderParameterUtils;
import org.eclipse.tracecompass.tmf.core.model.YModel;
import org.eclipse.tracecompass.tmf.core.model.filters.SelectedCounterQueryFilter;
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.IYModel;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.experiment.TmfExperiment;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.TreeMultimap;
/**
* This data provider will return a XY model (model is wrapped in a response)
* based on a query filter. The model is used afterwards by any viewer to draw
* charts. Model returned is for Counters views
*
* @author Mikael Ferland
* @author Yonni Chen
* @since 1.1
*/
public class CounterDataProvider extends AbstractTreeCommonXDataProvider<CounterAnalysis, TmfTreeDataModel> {
/**
* This data provider's extension point ID
*/
public static final String ID = "org.eclipse.tracecompass.analysis.counters.core.CounterDataProvider"; //$NON-NLS-1$
/**
* Cumulative key to extract isCumulative from parameters map
*/
public static final String CUMULATIVE_COUNTER_KEY = "isCumulative"; //$NON-NLS-1$
/**
* Chart's title
*/
private static final String TITLE = Objects.requireNonNull(Messages.CounterDataProvider_ChartTitle);
/**
* Create an instance of {@link CounterDataProvider}. Returns a null instance if
* the analysis module is not found.
*
* @param trace
* A trace on which we are interested to fetch a model
* @param module
* A CounterAnalysis instance
* @return A {@link CounterDataProvider} instance. If analysis module is not
* found, it returns null
*/
public static @Nullable CounterDataProvider create(ITmfTrace trace, @Nullable CounterAnalysis module) {
if (trace instanceof TmfExperiment) {
throw new UnsupportedOperationException("This data providers does not support experiment"); //$NON-NLS-1$
}
if (module != null) {
module.schedule();
return new CounterDataProvider(trace, module);
}
return null;
}
/**
* Constructor
*/
private CounterDataProvider(ITmfTrace trace, CounterAnalysis analysis) {
super(trace, analysis);
}
/**
* @since 1.2
*/
@Override
protected TmfTreeModel<TmfTreeDataModel> getTree(ITmfStateSystem ss, Map<String, Object> parameters, @Nullable IProgressMonitor monitor) {
List<TmfTreeDataModel> entries = new ArrayList<>();
long rootId = getId(ITmfStateSystem.ROOT_ATTRIBUTE);
entries.add(new TmfTreeDataModel(rootId, -1, Collections.singletonList(getTrace().getName())));
addTreeViewerBranch(ss, rootId, Collections.singletonList(CounterAnalysis.GROUPED_COUNTER_ASPECTS_ATTRIB), entries);
addTreeViewerBranch(ss, rootId, Collections.singletonList(CounterAnalysis.UNGROUPED_COUNTER_ASPECTS_ATTRIB), entries);
return new TmfTreeModel<>(Collections.emptyList(), entries);
}
private void addTreeViewerBranch(ITmfStateSystem ss, long parentId, List<String> branchName, List<TmfTreeDataModel> entries) {
int quark = ss.optQuarkAbsolute(branchName.get(0));
if (quark != ITmfStateSystem.INVALID_ATTRIBUTE && !ss.getSubAttributes(quark, false).isEmpty()) {
long id = getId(quark);
TmfTreeDataModel branch = new TmfTreeDataModel(id, parentId, branchName);
entries.add(branch);
addTreeViewerEntries(ss, id, quark, entries);
}
}
/**
* Recursively add all child entries of a parent branch from the state system.
*/
private void addTreeViewerEntries(ITmfStateSystem ss, long parentId, int quark, List<TmfTreeDataModel> entries) {
for (int childQuark : ss.getSubAttributes(quark, false)) {
long id = getId(childQuark);
TmfTreeDataModel childBranch = new TmfTreeDataModel(id, parentId, Collections.singletonList(ss.getAttributeName(childQuark)));
entries.add(childBranch);
addTreeViewerEntries(ss, id, childQuark, entries);
}
}
/**
* @since 1.2
*/
@Override
protected @Nullable Map<String, IYModel> getYModels(ITmfStateSystem ss, Map<String, Object> fetchParameters,
@Nullable IProgressMonitor monitor) throws StateSystemDisposedException {
// Check if the parameters contain timeRequested and selectedItems
// isCumulative will be compute later
if (FetchParametersUtils.createSelectionTimeQuery(fetchParameters) != null) {
return internalFetch(ss, fetchParameters, monitor);
}
return Collections.emptyMap();
}
private @Nullable Map<String, IYModel> internalFetch(ITmfStateSystem ss, Map<String, Object> fetchParameters,
@Nullable IProgressMonitor monitor) throws StateSystemDisposedException {
SelectedCounterQueryFilter filter = createCounterQuery(fetchParameters);
if (filter == null) {
return null;
}
long stateSystemEndTime = ss.getCurrentEndTime();
Collection<Long> times = extractRequestedTimes(ss, filter, stateSystemEndTime);
Map<Long, Integer> entries = Maps.filterValues(getSelectedEntries(filter), q -> ss.getSubAttributes(q, false).isEmpty());
TreeMultimap<Integer, ITmfStateInterval> countersIntervals = TreeMultimap.create(Comparator.naturalOrder(),
Comparator.comparingLong(ITmfStateInterval::getStartTime));
Iterable<@NonNull ITmfStateInterval> query2d = ss.query2D(entries.values(), times);
for (ITmfStateInterval interval : query2d) {
if (monitor != null && monitor.isCanceled()) {
return null;
}
countersIntervals.put(interval.getAttribute(), interval);
}
ImmutableMap.Builder<String, IYModel> ySeries = ImmutableMap.builder();
for (Entry<Long, Integer> entry : entries.entrySet()) {
if (monitor != null && monitor.isCanceled()) {
return null;
}
int quark = entry.getValue();
double[] yValues = buildYValues(countersIntervals.get(quark), filter);
String seriesName = getTrace().getName() + '/' + ss.getFullAttributePath(quark);
ySeries.put(seriesName, new YModel(entry.getKey(), seriesName, yValues));
}
return ySeries.build();
}
private static @Nullable SelectedCounterQueryFilter createCounterQuery(Map<String, Object> parameters) {
List<Long> timeRequested = DataProviderParameterUtils.extractTimeRequested(parameters);
List<Long> selectedItems = DataProviderParameterUtils.extractSelectedItems(parameters);
if (timeRequested == null || selectedItems == null) {
return null;
}
Boolean isCumulativeParameter = DataProviderParameterUtils.extractBoolean(parameters, CUMULATIVE_COUNTER_KEY);
// If the cumulative parameter is not present in the parameters use
// "false" as default value
boolean isCumulative = isCumulativeParameter != null && isCumulativeParameter;
return new SelectedCounterQueryFilter(timeRequested, selectedItems, isCumulative);
}
/**
* Extracts an array of times that will be used for a 2D query. It extracts the
* times based on the state system bounds, the requested time of the query
* filter and the current end time
*
* @param ss
* The state system
* @param filter
* The query filter
* @param currentEndTime
* We want to make sure that current end time is consistent
* throughout the query even if the analysis progresses
* @return A collection of time
*/
private static Collection<Long> extractRequestedTimes(ITmfStateSystem ss, SelectedCounterQueryFilter filter, long currentEndTime) {
Collection<Long> times = getTimes(filter, ss.getStartTime(), currentEndTime);
long[] xValues = filter.getTimesRequested();
long queryStart = filter.getStart();
long stateSystemStartTime = ss.getStartTime();
/* We only need the previous time for differential mode */
if (!filter.isCumulative() && xValues.length > 1) {
/*
* For differential mode, we need to get the time before query start. To do so,
* we subtract to query start the delta between xValues[1] and query start
*/
long prevTime = Long.max(stateSystemStartTime, 2 * queryStart - xValues[1]);
if (prevTime <= currentEndTime) {
times.add(prevTime);
}
}
return times;
}
private static double[] buildYValues(NavigableSet<ITmfStateInterval> countersIntervals, SelectedCounterQueryFilter filter) {
long[] times = filter.getTimesRequested();
boolean isCumulative = filter.isCumulative();
double[] yValues = new double[times.length];
long prevValue = 0L;
if (!countersIntervals.isEmpty()) {
Object value = countersIntervals.first().getValue();
if (value instanceof Number) {
prevValue = ((Number) value).longValue();
}
}
int to = 0;
for (ITmfStateInterval interval : countersIntervals) {
int from = Arrays.binarySearch(times, interval.getStartTime());
from = (from >= 0) ? from : -1 - from;
Number value = (Number) interval.getValue();
long l = value != null ? value.longValue() : 0l;
if (isCumulative) {
/* Fill in all the time stamps that the interval overlaps */
to = Arrays.binarySearch(times, interval.getEndTime());
to = (to >= 0) ? to + 1 : -1 - to;
Arrays.fill(yValues, from, to, l);
} else {
yValues[from] = (l - prevValue);
}
prevValue = l;
}
/* Fill the time stamps after the state system, if any. */
if (isCumulative) {
Arrays.fill(yValues, to, yValues.length, prevValue);
}
return yValues;
}
@Override
public String getTitle() {
return TITLE;
}
@Override
public String getId() {
return ID;
}
/**
* @since 1.2
*/
@Override
protected boolean isCacheable() {
return true;
}
}