diff --git a/analysis/org.eclipse.tracecompass.analysis.profiling.core/plugin.xml b/analysis/org.eclipse.tracecompass.analysis.profiling.core/plugin.xml index 53d4e6e03d..7ad8aaaa64 100644 --- a/analysis/org.eclipse.tracecompass.analysis.profiling.core/plugin.xml +++ b/analysis/org.eclipse.tracecompass.analysis.profiling.core/plugin.xml @@ -19,6 +19,10 @@ class="org.eclipse.tracecompass.internal.analysis.profiling.core.instrumented.FlameChartDataProviderFactory" id="org.eclipse.tracecompass.analysis.profiling.core.flamechart"> + + diff --git a/analysis/org.eclipse.tracecompass.analysis.profiling.core/src/org/eclipse/tracecompass/internal/analysis/profiling/core/callstack/provider/CallStackFunctionDensityDataProvider.java b/analysis/org.eclipse.tracecompass.analysis.profiling.core/src/org/eclipse/tracecompass/internal/analysis/profiling/core/callstack/provider/CallStackFunctionDensityDataProvider.java new file mode 100644 index 0000000000..ea30625dd2 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.profiling.core/src/org/eclipse/tracecompass/internal/analysis/profiling/core/callstack/provider/CallStackFunctionDensityDataProvider.java @@ -0,0 +1,284 @@ +/********************************************************************** + * Copyright (c) 2025 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.profiling.core.callstack.provider; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +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.profiling.core.callstack.CallStackAnalysis; +import org.eclipse.tracecompass.analysis.profiling.core.callstack.CallStackSeries; +import org.eclipse.tracecompass.common.core.NonNullUtils; +import org.eclipse.tracecompass.internal.analysis.profiling.core.callgraph.ICalledFunction; +import org.eclipse.tracecompass.internal.tmf.core.model.filters.FetchParametersUtils; +import org.eclipse.tracecompass.segmentstore.core.ISegment; +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.DataType; +import org.eclipse.tracecompass.tmf.core.model.IAxisDomain; +import org.eclipse.tracecompass.tmf.core.model.ISampling; +import org.eclipse.tracecompass.tmf.core.model.YModel; +import org.eclipse.tracecompass.tmf.core.model.filters.SelectionTimeQueryFilter; +import org.eclipse.tracecompass.tmf.core.model.genericxy.AbstractTreeGenericXYCommonXDataProvider; +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.IYModel; +import org.eclipse.tracecompass.tmf.core.model.xy.TmfXYAxisDescription; +import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; +import org.eclipse.tracecompass.tmf.core.util.Pair; + +/** + * Bar chart provider to show function duration distributions in the call stack. + * + * @author Siwei Zhang + * @since 2.6 + */ +public class CallStackFunctionDensityDataProvider extends AbstractTreeGenericXYCommonXDataProvider<@NonNull CallStackAnalysis, @NonNull ITmfTreeDataModel> { + + /** + * Provider id. + */ + public static final String ID = "org.eclipse.tracecompass.analysis.profiling.core.callstack.functiondensity.provider"; //$NON-NLS-1$ + + private static final String EXECUTION_TIME = "Execution Time"; //$NON-NLS-1$ + private static final String NUMBER_OF_EXECUTIONS = "Number of Executions"; //$NON-NLS-1$ + private static final String UNIT_NS = "ns"; //$NON-NLS-1$ + private static final String UNIT_EMPTY = ""; //$NON-NLS-1$ + private final Map fPidsToEntryIds = new HashMap<>(); + private static final int UNKNOWN_PID = -1; + private static final TmfXYAxisDescription Y_AXIS_DESCRIPTION = new TmfXYAxisDescription( + NUMBER_OF_EXECUTIONS, UNIT_EMPTY, DataType.NUMBER); + + /** + * Stores the state system end time when making the query and the maximum + * execution time. + */ + private Pair fEndTimeToMaxDuration = null; + + /** + * Constructor. + * + * @param trace + * The trace associated with this data provider + * @param analysisModule + * The analysis module used to compute data + */ + public CallStackFunctionDensityDataProvider(@NonNull ITmfTrace trace, @NonNull CallStackAnalysis analysisModule) { + super(trace, analysisModule); + } + + @Override + public TmfXYAxisDescription getXAxisDescription() { + long maxDuration = getMaxExecutionTime(); + if (maxDuration == -1) { + return new TmfXYAxisDescription( + EXECUTION_TIME, UNIT_NS, DataType.DURATION, new IAxisDomain.Range(-1, -1)); + } + + return new TmfXYAxisDescription( + EXECUTION_TIME, UNIT_NS, DataType.DURATION, new IAxisDomain.Range(0, maxDuration)); + } + + /** + * Find the maximum duration in the segment store. + */ + private long getMaxExecutionTime() { + ITmfStateSystem ss = getAnalysisModule().getStateSystem(); + if (ss == null) { + return -1; + } + + if (fEndTimeToMaxDuration != null && fEndTimeToMaxDuration.getFirst() == ss.getCurrentEndTime()) { + return fEndTimeToMaxDuration.getSecond(); + } + + + CallStackSeries callstackSeries = getAnalysisModule().getCallStackSeries(); + if (callstackSeries == null) { + return -1; + } + + long maxDuration = -1; + for (ISegment segment : callstackSeries.getIntersectingElements(ss.getStartTime(), ss.getCurrentEndTime())) { + maxDuration = Math.max(segment.getLength(), maxDuration); + } + + fEndTimeToMaxDuration = new Pair<>(ss.getCurrentEndTime(), maxDuration); + return maxDuration; + } + + @Override + public String getId() { + return ID; + } + + @Override + protected boolean isCacheable() { + return false; + } + + @Override + protected TmfTreeModel<@NonNull ITmfTreeDataModel> getTree(@NonNull ITmfStateSystem ss, @NonNull Map<@NonNull String, @NonNull Object> fetchParameters, @Nullable IProgressMonitor monitor) throws StateSystemDisposedException { + CallStackSeries series = getAnalysisModule().getCallStackSeries(); + if (series == null) { + return new TmfTreeModel<>(Collections.emptyList(), Collections.emptyList()); + } + + List<@NonNull ITmfTreeDataModel> entries = new ArrayList<>(); + long traceId = getId(ITmfStateSystem.ROOT_ATTRIBUTE); + entries.add(new TmfTreeDataModel(traceId, -1, NonNullUtils.nullToEmptyString(getTrace().getName()))); + + List<@NonNull Integer> processQuarks = ss.getQuarks(getAnalysisModule().getProcessesPattern()); + long end = ss.getCurrentEndTime(); + List<@NonNull ITmfStateInterval> fullEnd = ss.queryFullState(end); + for(@NonNull Integer processQuark : processQuarks) { + int pid = UNKNOWN_PID; + if (processQuark != ITmfStateSystem.ROOT_ATTRIBUTE) { + String processName = ss.getAttributeName(processQuark); + Object processValue = fullEnd.get(processQuark).getValue(); + pid = getThreadProcessId(processName, processValue); + } + long entryId = getId(processQuark); + fPidsToEntryIds.put(pid, entryId); + entries.add(new TmfTreeDataModel(entryId, traceId, getNameFromPID(pid))); + } + return new TmfTreeModel<>(Collections.emptyList(), entries); + } + + private static @NonNull String getNameFromPID(int pid) { + return pid == UNKNOWN_PID ? "UNKNOWN_PID" : String.valueOf(pid); //$NON-NLS-1$ + } + + private static int getThreadProcessId(String name, @Nullable Object value) { + if (value instanceof Number) { + return ((Number) value).intValue(); + } + try { + return Integer.parseInt(name); + } catch (NumberFormatException e) { + return UNKNOWN_PID; + } + } + + @Override + protected @Nullable Pair<@NonNull ISampling,@NonNull Collection<@NonNull IYModel>> getXAxisAndYSeriesModels( + @NonNull ITmfStateSystem ss, + @NonNull Map<@NonNull String, @NonNull Object> fetchParameters, + @Nullable IProgressMonitor monitor) throws StateSystemDisposedException { + + SelectionTimeQueryFilter filter = FetchParametersUtils.createSelectionTimeQueryWithSamples(fetchParameters); + if (filter == null) { + return null; + } + + CallStackSeries series = getAnalysisModule().getCallStackSeries(); + if (series == null) { + return null; + } + + long sampleStart = 0; + long sampleEnd = getMaxExecutionTime(); + int nbSamples = filter.getNumberOfSamples(); + ISampling.Ranges sampling = createEvenlyDistributedRanges(sampleStart, sampleEnd, nbSamples); + if(sampling == null) { + return null; + } + + List> ranges = sampling.ranges(); + Map pidsToBins = new HashMap<>(); + Map<@NonNull Long, @NonNull Integer> selectedEntries = getSelectedEntries(filter); + // Initialize bins only for selected entries + for (Entry pidToEntryId : fPidsToEntryIds.entrySet()) { + int pid = pidToEntryId.getKey(); + long entryId = pidToEntryId.getValue(); + if (selectedEntries.containsKey(entryId)) { + pidsToBins.put(pid, new double[ranges.size()]); + } + } + + // Count function durations falling in each bin + long totalSpan = sampleEnd - sampleStart + 1; + long step = totalSpan / nbSamples; + for (ISegment segment : series.getIntersectingElements(filter.getStart(), filter.getEnd())) { + if (segment instanceof ICalledFunction function) { + int pid = function.getProcessId(); + double[] bins = pidsToBins.get(pid); + if (bins == null) { + continue; + } + long duration = segment.getLength(); + + if (step > 0 && duration >= sampleStart && duration <= sampleEnd) { + int index = (int) ((duration - sampleStart) / step); + if (index >= nbSamples) { + index = nbSamples - 1; + } + bins[index]++; + } + } + } + + // Build final Y models + List<@NonNull IYModel> yModels = new ArrayList<>(); + for (Entry entry : pidsToBins.entrySet()) { + int pid = entry.getKey(); + double[] bins = entry.getValue(); + long entryId = fPidsToEntryIds.getOrDefault(pid, -1L); + yModels.add(new YModel(entryId, getNameFromPID(pid), bins, Y_AXIS_DESCRIPTION)); + } + + return new Pair<>(sampling, yModels); + } + + private static ISampling.@Nullable Ranges createEvenlyDistributedRanges(long start, long end, int samples) { + if (samples <= 0 || start >= end) { + return null; + } + + List> ranges = new ArrayList<>(samples); + long totalSpan = end - start; + long step = totalSpan / samples; + long remainder = totalSpan % samples; + + long current = start; + for (int i = 0; i < samples; i++) { + long rangeStart = current; + long rangeEnd = current + step - 1; + if (remainder > 0) { + rangeEnd += 1; + remainder--; + } + if (rangeEnd >= end || i == samples - 1) { + rangeEnd = end; + } + ranges.add(new ISampling.Range<>(rangeStart, rangeEnd)); + current = rangeEnd + 1; + } + + return new ISampling.Ranges(ranges); + } + + @Override + protected String getTitle() { + String title = Objects.requireNonNull(Messages.CallStackFunctionDensityDataProviderFactory_title); + return title; + } +} diff --git a/analysis/org.eclipse.tracecompass.analysis.profiling.core/src/org/eclipse/tracecompass/internal/analysis/profiling/core/callstack/provider/CallStackFunctionDensityDataProviderFactory.java b/analysis/org.eclipse.tracecompass.analysis.profiling.core/src/org/eclipse/tracecompass/internal/analysis/profiling/core/callstack/provider/CallStackFunctionDensityDataProviderFactory.java new file mode 100644 index 0000000000..9a62aca2bb --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.profiling.core/src/org/eclipse/tracecompass/internal/analysis/profiling/core/callstack/provider/CallStackFunctionDensityDataProviderFactory.java @@ -0,0 +1,89 @@ +/********************************************************************** + * Copyright (c) 2025 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.profiling.core.callstack.provider; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.tracecompass.analysis.profiling.core.callstack.CallStackAnalysis; +import org.eclipse.tracecompass.tmf.core.dataprovider.IDataProviderDescriptor; +import org.eclipse.tracecompass.tmf.core.dataprovider.IDataProviderDescriptor.ProviderType; +import org.eclipse.tracecompass.tmf.core.dataprovider.IDataProviderFactory; +import org.eclipse.tracecompass.tmf.core.model.DataProviderDescriptor; +import org.eclipse.tracecompass.tmf.core.model.tree.ITmfTreeDataModel; +import org.eclipse.tracecompass.tmf.core.model.tree.ITmfTreeDataProvider; +import org.eclipse.tracecompass.tmf.core.model.xy.TmfTreeXYCompositeDataProvider; +import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; +import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager; +import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils; +import org.eclipse.tracecompass.tmf.core.trace.experiment.TmfExperiment; + +import com.google.common.collect.Iterables; + +/** + * {@link CallStackFunctionDensityDataProvider} factory. + * + * @author Siwei Zhang + * @since 2.6 + */ +public class CallStackFunctionDensityDataProviderFactory implements IDataProviderFactory { + + private static final IDataProviderDescriptor DESCRIPTOR = new DataProviderDescriptor.Builder() + .setId(CallStackFunctionDensityDataProvider.ID) + .setName(Objects.requireNonNull(Messages.CallStackFunctionDensityDataProviderFactory_title)) + .setDescription(Objects.requireNonNull(Messages.CallStackFunctionDensityDataProviderFactory_descriptionText)) + .setProviderType(ProviderType.TREE_GENERIC_XY) + .build(); + + @Override + public @Nullable ITmfTreeDataProvider createProvider(ITmfTrace trace) { + if (trace instanceof TmfExperiment) { + @NonNull List<@NonNull CallStackFunctionDensityDataProvider> providers = new ArrayList<>(); + for (ITmfTrace child : TmfTraceManager.getTraceSet(trace)) { + CallStackFunctionDensityDataProvider provider = createProviderLocal(child); + if (provider != null) { + providers.add(provider); + } + } + if (providers.size() == 1) { + return providers.get(0); + } + if (!providers.isEmpty()) { + String title = Objects.requireNonNull(Messages.CallStackFunctionDensityDataProviderFactory_title); + return new TmfTreeXYCompositeDataProvider<>(providers, title, CallStackFunctionDensityDataProvider.ID); + } + return null; + } + return createProviderLocal(trace); + } + + private static @Nullable CallStackFunctionDensityDataProvider createProviderLocal(@NonNull ITmfTrace trace) { + Iterator modules = TmfTraceUtils.getAnalysisModulesOfClass(trace, CallStackAnalysis.class).iterator(); + while (modules.hasNext()) { + CallStackAnalysis first = modules.next(); + first.schedule(); + return new CallStackFunctionDensityDataProvider(trace, first); + } + return null; + } + + @Override + public Collection getDescriptors(@NonNull ITmfTrace trace) { + Iterable<@NonNull CallStackAnalysis> modules = TmfTraceUtils.getAnalysisModulesOfClass(trace, CallStackAnalysis.class); + return !Iterables.isEmpty(modules) ? Collections.singletonList(DESCRIPTOR) : Collections.emptyList(); + } +} diff --git a/analysis/org.eclipse.tracecompass.analysis.profiling.core/src/org/eclipse/tracecompass/internal/analysis/profiling/core/callstack/provider/Messages.java b/analysis/org.eclipse.tracecompass.analysis.profiling.core/src/org/eclipse/tracecompass/internal/analysis/profiling/core/callstack/provider/Messages.java index 3eacee2934..36841320d1 100644 --- a/analysis/org.eclipse.tracecompass.analysis.profiling.core/src/org/eclipse/tracecompass/internal/analysis/profiling/core/callstack/provider/Messages.java +++ b/analysis/org.eclipse.tracecompass.analysis.profiling.core/src/org/eclipse/tracecompass/internal/analysis/profiling/core/callstack/provider/Messages.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2013, 2014 Ericsson + * Copyright (c) 2013, 2025 Ericsson * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License 2.0 which @@ -48,6 +48,15 @@ public class Messages extends NLS { */ public static @Nullable String CallStackDataProviderFactory_descriptionText; + /** + * Name of the data provider shown to the user for function density view + */ + public static @Nullable String CallStackFunctionDensityDataProviderFactory_title; + /** + * Help text for the data descriptor for function density view + */ + public static @Nullable String CallStackFunctionDensityDataProviderFactory_descriptionText; + static { // initialize resource bundle NLS.initializeMessages(BUNDLE_NAME, Messages.class); diff --git a/analysis/org.eclipse.tracecompass.analysis.profiling.core/src/org/eclipse/tracecompass/internal/analysis/profiling/core/callstack/provider/messages.properties b/analysis/org.eclipse.tracecompass.analysis.profiling.core/src/org/eclipse/tracecompass/internal/analysis/profiling/core/callstack/provider/messages.properties index 2f2ae96a28..a8a5d7a748 100644 --- a/analysis/org.eclipse.tracecompass.analysis.profiling.core/src/org/eclipse/tracecompass/internal/analysis/profiling/core/callstack/provider/messages.properties +++ b/analysis/org.eclipse.tracecompass.analysis.profiling.core/src/org/eclipse/tracecompass/internal/analysis/profiling/core/callstack/provider/messages.properties @@ -1,5 +1,5 @@ ############################################################################### -# Copyright (c) 2013, 2014 Ericsson +# Copyright (c) 2013, 2025 Ericsson # # All rights reserved. This program and the accompanying materials # are made available under the terms of the Eclipse Public License 2.0 @@ -17,4 +17,6 @@ CallStackStateProvider_IncoherentCallstack=Incoherent callstack found on ({0}) o CallStackDataProvider_toolTipAddress=Address CallStackDataProvider_toolTipState=State CallStackDataProviderFactory_title=Flame Chart -CallStackDataProviderFactory_descriptionText=Show a call stack over time \ No newline at end of file +CallStackDataProviderFactory_descriptionText=Show a call stack over time +CallStackFunctionDensityDataProviderFactory_title=Function Density View +CallStackFunctionDensityDataProviderFactory_descriptionText=Shows the distribution of function execution time \ No newline at end of file diff --git a/releng/org.eclipse.tracecompass.integration.core.tests/src/org/eclipse/tracecompass/integration/core/tests/dataproviders/DataProviderManagerTest.java b/releng/org.eclipse.tracecompass.integration.core.tests/src/org/eclipse/tracecompass/integration/core/tests/dataproviders/DataProviderManagerTest.java index 2196fcf7c7..d81d02e8f2 100644 --- a/releng/org.eclipse.tracecompass.integration.core.tests/src/org/eclipse/tracecompass/integration/core/tests/dataproviders/DataProviderManagerTest.java +++ b/releng/org.eclipse.tracecompass.integration.core.tests/src/org/eclipse/tracecompass/integration/core/tests/dataproviders/DataProviderManagerTest.java @@ -273,6 +273,12 @@ public class DataProviderManagerTest { .setId("org.eclipse.tracecompass.internal.analysis.profiling.callstack.provider.CallStackDataProvider"); EXPECTED_UST_DP_DESCRIPTORS.add(builder.build()); builder = new DataProviderDescriptor.Builder(); + builder.setName("Function Density View") + .setDescription("Shows the distribution of function execution time") + .setProviderType(ProviderType.TREE_GENERIC_XY) + .setId("org.eclipse.tracecompass.analysis.profiling.core.callstack.functiondensity.provider"); + EXPECTED_UST_DP_DESCRIPTORS.add(builder.build()); + builder = new DataProviderDescriptor.Builder(); builder.setName("Function Duration Statistics") .setDescription("Show the function duration statistics") .setProviderType(ProviderType.DATA_TREE) diff --git a/tmf/org.eclipse.tracecompass.tmf.core/META-INF/MANIFEST.MF b/tmf/org.eclipse.tracecompass.tmf.core/META-INF/MANIFEST.MF index 1a9a3053a7..716cb1ba4b 100644 --- a/tmf/org.eclipse.tracecompass.tmf.core/META-INF/MANIFEST.MF +++ b/tmf/org.eclipse.tracecompass.tmf.core/META-INF/MANIFEST.MF @@ -108,6 +108,7 @@ Export-Package: org.eclipse.tracecompass.internal.provisional.tmf.core.model, org.eclipse.tracecompass.tmf.core.model, org.eclipse.tracecompass.tmf.core.model.annotations, org.eclipse.tracecompass.tmf.core.model.filters, + org.eclipse.tracecompass.tmf.core.model.genericxy, org.eclipse.tracecompass.tmf.core.model.timegraph, org.eclipse.tracecompass.tmf.core.model.tree, org.eclipse.tracecompass.tmf.core.model.xy, diff --git a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/internal/tmf/core/model/TmfXyResponseFactory.java b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/internal/tmf/core/model/TmfXyResponseFactory.java index 2b319c0960..550bb96ddb 100644 --- a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/internal/tmf/core/model/TmfXyResponseFactory.java +++ b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/internal/tmf/core/model/TmfXyResponseFactory.java @@ -1,5 +1,5 @@ /********************************************************************** - * Copyright (c) 2017 Ericsson + * Copyright (c) 2017, 2025 Ericsson * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License 2.0 which @@ -19,9 +19,11 @@ import org.apache.commons.lang3.StringUtils; import org.eclipse.tracecompass.tmf.core.model.CommonStatusMessage; +import org.eclipse.tracecompass.tmf.core.model.ISampling; import org.eclipse.tracecompass.tmf.core.model.SeriesModel.SeriesModelBuilder; import org.eclipse.tracecompass.tmf.core.model.TmfXyModel; import org.eclipse.tracecompass.tmf.core.model.xy.ISeriesModel; +import org.eclipse.tracecompass.tmf.core.model.xy.ISeriesModel.DisplayType; import org.eclipse.tracecompass.tmf.core.model.xy.ITmfXyModel; import org.eclipse.tracecompass.tmf.core.model.xy.IYModel; import org.eclipse.tracecompass.tmf.core.model.xy.TmfXYAxisDescription; @@ -60,7 +62,9 @@ private TmfXyResponseFactory() { * Tells whether the computed model is complete or partial * @return A {@link TmfModelResponse} with either a running status or a * completed status + * @deprecated Use {@link ISampling} for xValues instead. */ + @Deprecated(since = "10.2", forRemoval = true) public static TmfModelResponse create(String title, long[] xValues, Collection yModels, boolean isComplete) { List series = Lists.transform(new ArrayList<>(yModels), model -> { SeriesModelBuilder builder = new SeriesModelBuilder(model.getId(), model.getName(), xValues, model.getData()); @@ -78,6 +82,114 @@ public static TmfModelResponse create(String title, long[] xValues, return new TmfModelResponse<>(model, ITmfResponse.Status.RUNNING, Objects.requireNonNull(CommonStatusMessage.RUNNING)); } + /** + * Create a {@link TmfModelResponse} for values with common sampling values, + * with a either RUNNING or COMPLETED status. Model is not null, it's either + * partial or full. + * + * @param title + * Chart title + * @param samples + * The samples requested by the viewer + * @param yModels + * Collection of IYModel + * @param isComplete + * Tells whether the computed model is complete or partial + * @return A {@link TmfModelResponse} with either a running status or a + * completed status + */ + public static TmfModelResponse create(String title, ISampling samples, Collection yModels, boolean isComplete) { + List series = Lists.transform(new ArrayList<>(yModels), model -> { + SeriesModelBuilder builder = new SeriesModelBuilder(model.getId(), model.getName(), samples, model.getData()); + TmfXYAxisDescription yAxis = model.getYAxisDescription(); + if (yAxis != null) { + builder.yAxisDescription(yAxis); + } + return builder.build(); + }); + ITmfXyModel model = new TmfXyModel(title, series); + + if (isComplete) { + return new TmfModelResponse<>(model, ITmfResponse.Status.COMPLETED, Objects.requireNonNull(CommonStatusMessage.COMPLETED)); + } + return new TmfModelResponse<>(model, ITmfResponse.Status.RUNNING, Objects.requireNonNull(CommonStatusMessage.RUNNING)); + } + + /** + * Create a {@link TmfModelResponse} for values with common sampling values, + * with a either RUNNING or COMPLETED status. Model is not null, it's either + * partial or full. + * + * @param title + * Chart title + * @param samples + * The samples requested by the viewer + * @param yModels + * Collection of IYModel + * @param displayType + * The type of display, see {@link DisplayType} + * @param isComplete + * Tells whether the computed model is complete or partial + * @return A {@link TmfModelResponse} with either a running status or a + * completed status + */ + public static TmfModelResponse create(String title, ISampling samples, Collection yModels, DisplayType displayType, boolean isComplete) { + List series = Lists.transform(new ArrayList<>(yModels), model -> { + SeriesModelBuilder builder = new SeriesModelBuilder(model.getId(), model.getName(), samples, model.getData()); + TmfXYAxisDescription yAxis = model.getYAxisDescription(); + if (yAxis != null) { + builder.yAxisDescription(yAxis); + } + builder.seriesDisplayType(displayType); + return builder.build(); + }); + ITmfXyModel model = new TmfXyModel(title, series); + + if (isComplete) { + return new TmfModelResponse<>(model, ITmfResponse.Status.COMPLETED, Objects.requireNonNull(CommonStatusMessage.COMPLETED)); + } + return new TmfModelResponse<>(model, ITmfResponse.Status.RUNNING, Objects.requireNonNull(CommonStatusMessage.RUNNING)); + } + + /** + * Create a {@link TmfModelResponse} for values with common sampling values + * and x axis descriptions, with a either RUNNING or COMPLETED status. Model + * is not null, it's either partial or full. + * + * @param title + * Chart title + * @param samples + * The samples requested by the viewer + * @param yModels + * Collection of IYModel + * @param displayType + * The type of display, see {@link DisplayType} + * @param xAxisDescription + * The description for x axis + * @param isComplete + * Tells whether the computed model is complete or partial + * @return A {@link TmfModelResponse} with either a running status or a + * completed status + */ + public static TmfModelResponse create(String title, ISampling samples, Collection yModels, DisplayType displayType, TmfXYAxisDescription xAxisDescription, boolean isComplete) { + List series = Lists.transform(new ArrayList<>(yModels), model -> { + SeriesModelBuilder builder = new SeriesModelBuilder(model.getId(), model.getName(), samples, model.getData()); + builder.xAxisDescription(xAxisDescription); + TmfXYAxisDescription yAxis = model.getYAxisDescription(); + if (yAxis != null) { + builder.yAxisDescription(yAxis); + } + builder.seriesDisplayType(displayType); + return builder.build(); + }); + ITmfXyModel model = new TmfXyModel(title, series); + + if (isComplete) { + return new TmfModelResponse<>(model, ITmfResponse.Status.COMPLETED, Objects.requireNonNull(CommonStatusMessage.COMPLETED)); + } + return new TmfModelResponse<>(model, ITmfResponse.Status.RUNNING, Objects.requireNonNull(CommonStatusMessage.RUNNING)); + } + /** * Create a {@link TmfModelResponse} with a either RUNNING or COMPLETED status. * Model is not null, it's either partial or full. diff --git a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/internal/tmf/core/model/filters/FetchParametersUtils.java b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/internal/tmf/core/model/filters/FetchParametersUtils.java index dfe1254a8e..3f1ae9763b 100644 --- a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/internal/tmf/core/model/filters/FetchParametersUtils.java +++ b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/internal/tmf/core/model/filters/FetchParametersUtils.java @@ -1,5 +1,5 @@ /********************************************************************** - * Copyright (c) 2019 Ericsson + * Copyright (c) 2019, 2025 Ericsson * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License 2.0 which @@ -61,6 +61,23 @@ private FetchParametersUtils() { return (timeRequested == null || selectedItems == null) ? null : new SelectionTimeQueryFilter(timeRequested, selectedItems); } + /** + * Create a {@link SelectionTimeQueryFilter} with the given map of + * parameters with number of samples specified. + * + * @param parameters + * Map of parameters + * @return A {@link SelectionTimeQueryFilter} or null if the parameters are + * invalid + */ + public static @Nullable SelectionTimeQueryFilter createSelectionTimeQueryWithSamples(Map parameters) { + DataProviderParameterUtils.TimeRangeWithSamples timeRange = DataProviderParameterUtils.extractTimeRangeWithSamples(parameters); + List selectedItems = DataProviderParameterUtils.extractSelectedItems(parameters); + return (timeRange == null || selectedItems == null) + ? null + : new SelectionTimeQueryFilter(timeRange.start(), timeRange.end(), timeRange.nbSamples(), selectedItems); + } + /** * Create a {@link VirtualTableQueryFilter} with the given map of parameters * diff --git a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/dataprovider/DataProviderParameterUtils.java b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/dataprovider/DataProviderParameterUtils.java index df1597842d..7b6ba27d30 100644 --- a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/dataprovider/DataProviderParameterUtils.java +++ b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/dataprovider/DataProviderParameterUtils.java @@ -1,5 +1,5 @@ /********************************************************************** - * Copyright (c) 2019 Ericsson + * Copyright (c) 2019, 2025 Ericsson * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License 2.0 which @@ -41,6 +41,12 @@ public class DataProviderParameterUtils { */ public static final String REQUESTED_TIME_KEY = "requested_times"; //$NON-NLS-1$ + /** + * Time range requested with the numbers of samples key + * @since 10.1 + */ + public static final String REQUESTED_TIMERANGE_KEY = "requested_timerange"; //$NON-NLS-1$ + /** * Selected items key */ @@ -189,6 +195,38 @@ private DataProviderParameterUtils() { return extractLongList(parameters, REQUESTED_TIME_KEY); } + /** + * Represents a time range with a number of samples. + * + * @param start + * Start time + * @param end + * End time + * @param nbSamples + * Number of samples + * @since 10.1 + */ + public record TimeRangeWithSamples(long start, long end, int nbSamples) {} + + /** + * Extract a {@link TimeRangeWithSamples} from the parameters map. + * + * @param parameters + * The map of parameters + * @return A {@link TimeRangeWithSamples} object or {@code null} if the parameters are invalid + * @since 10.1 + */ + public static @Nullable TimeRangeWithSamples extractTimeRangeWithSamples(Map parameters) { + List timeRequested = extractLongList(parameters, REQUESTED_TIMERANGE_KEY); + if (timeRequested == null || timeRequested.size() < 3) { + return null; + } + return new TimeRangeWithSamples( + timeRequested.get(0), + timeRequested.get(1), + timeRequested.get(2).intValue()); + } + /** * Helper to extract selected items from a map of parameters * diff --git a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/dataprovider/IDataProviderDescriptor.java b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/dataprovider/IDataProviderDescriptor.java index f7da7c7859..080f701989 100644 --- a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/dataprovider/IDataProviderDescriptor.java +++ b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/dataprovider/IDataProviderDescriptor.java @@ -73,7 +73,12 @@ public enum ProviderType { * * @since 10.1 */ - GANTT_CHART + GANTT_CHART, + /** + * A provider for generic xy charts with a time-less x-axis + * @since 10.1 + */ + TREE_GENERIC_XY } /** diff --git a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/IAxisDomain.java b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/IAxisDomain.java new file mode 100644 index 0000000000..79acf31e71 --- /dev/null +++ b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/IAxisDomain.java @@ -0,0 +1,87 @@ +/********************************************************************** + * Copyright (c) 2025 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.tmf.core.model; + +import java.util.List; +import java.util.Objects; + +import org.eclipse.jdt.annotation.Nullable; + + +/** + * Shows the available values of X axis. + * + * @author Siwei Zhang + * @since 10.1 + */ +public sealed interface IAxisDomain permits IAxisDomain.Categorical, IAxisDomain.Range { + + /** + * Categorical axis domain (e.g., names or labels). + * + * @param categories + * the category labels for the X axis + */ + record Categorical(List categories) implements IAxisDomain { + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Categorical other)) { + return false; + } + return Objects.equals(categories, other.categories); + } + + @Override + public int hashCode() { + return Objects.hash(categories); + } + + @Override + public String toString() { + return "AxisDomain.Categorical{categories=" + categories + "}"; //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + /** + * Represents a range-based axis domain, such as one used for execution + * durations. + * + * @param start + * the start value of the range + * @param end + * the end value of the range + */ + record Range(long start, long end) implements IAxisDomain { + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Range other)) { + return false; + } + return start == other.start && end == other.end; + } + + @Override + public int hashCode() { + return Objects.hash(start, end); + } + + @Override + public String toString() { + return "AxisDomain.TimeRange{start=" + start + ", end=" + end + "}"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + } +} diff --git a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/ISampling.java b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/ISampling.java new file mode 100644 index 0000000000..f3e45833ad --- /dev/null +++ b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/ISampling.java @@ -0,0 +1,147 @@ +/********************************************************************** + * Copyright (c) 2025 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.tmf.core.model; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.tracecompass.common.core.NonNullUtils; + +/** + * Represents the sampling information of the X-axis. + * This allows both time-series (numeric) and categorical (string or range-based) sampling. + * + * @author Siwei Zhang + * @since 10.1 + */ +public sealed interface ISampling permits ISampling.Timestamps, ISampling.Categories, ISampling.Ranges { + + /** + * Returns the number of sampling points in this sampling definition. + * + * @return the number of sampling points + */ + int size(); + + /** + * Time-based sampling points. + * + * @param timestamps + * the X-axis sampling points as an array of timestamps + */ + record Timestamps(long[] timestamps) implements ISampling { + @Override + public int size() { + return timestamps.length; + } + + @Override + public boolean equals(@Nullable Object obj) { + return (this == obj) || (obj instanceof Timestamps other && Arrays.equals(this.timestamps, other.timestamps)); + } + + @Override + public int hashCode() { + return Arrays.hashCode(timestamps); + } + + @Override + public String toString() { + return NonNullUtils.nullToEmptyString(Arrays.toString(timestamps)); + } + } + + /** + * Categorical sampling points (e.g., labels like "Read", "Write", "Idle"). + * + * @param categories + * the X-axis categories + */ + record Categories(List categories) implements ISampling { + @Override + public int size() { + return categories.size(); + } + + @Override + public boolean equals(@Nullable Object obj) { + return (this == obj) || (obj instanceof Categories other && Objects.equals(this.categories, other.categories)); + } + + @Override + public int hashCode() { + return Objects.hash(categories); + } + + @Override + public String toString() { + return NonNullUtils.nullToEmptyString(categories.toString()); + } + } + + /** + * Range sampling points, representing intervals per bucket. + * + * @param ranges + * the ranges for each bucket on the X-axis + */ + record Ranges(List> ranges) implements ISampling { + @Override + public int size() { + return ranges.size(); + } + + @Override + public boolean equals(@Nullable Object obj) { + return (this == obj) || (obj instanceof Ranges other && Objects.equals(this.ranges, other.ranges)); + } + + @Override + public int hashCode() { + return Objects.hash(ranges); + } + + @Override + public String toString() { + return NonNullUtils.nullToEmptyString(ranges.toString()); + } + } + + /** + * Represents a closed interval [start, end] on a comparable type. + * + * @param + * the type of the range boundaries (must be comparable) + * @param start + * the start of the range (inclusive) + * @param end + * the end of the range (inclusive) + */ + public record Range>(T start, T end) { + @Override + public boolean equals(@Nullable Object obj) { + return (this == obj) || (obj instanceof Range other && + Objects.equals(start, other.start) && Objects.equals(end, other.end)); + } + + @Override + public int hashCode() { + return Objects.hash(start, end); + } + + @Override + public String toString() { + return "Range[" + start + ", " + end + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + } +} diff --git a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/SeriesModel.java b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/SeriesModel.java index 9c938875ff..ea6661e66d 100644 --- a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/SeriesModel.java +++ b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/SeriesModel.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 École Polytechnique de Montréal + * Copyright (c) 2017, 2025 École Polytechnique de Montréal * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License 2.0 which @@ -59,15 +59,15 @@ public class SeriesModel implements ISeriesModel { private final String fName; @SerializedName("xValues") - private final long[] fXValues; + private final ISampling fSampling; @SerializedName("yValues") private final double[] fYValues; - @SerializedName("xAxis") + @SerializedName("xValuesDescription") private final TmfXYAxisDescription fXAxis; - @SerializedName("yAxis") + @SerializedName("yValuesDescription") private final TmfXYAxisDescription fYAxis; @SerializedName("dataType") @@ -89,10 +89,28 @@ public class SeriesModel implements ISeriesModel { * The y values of this series * @since 4.2 */ + @Deprecated(since = "10.1", forRemoval = true) public SeriesModel(long id, String name, long[] xValues, double[] yValues) { this(id, name, xValues, yValues, new TmfXYAxisDescription(DEFAULT_XAXIS_NAME, DEFAULT_XAXIS_UNIT), new TmfXYAxisDescription(DEFAULT_YAXIS_NAME, DEFAULT_YAXIS_UNIT), DisplayType.LINE, new int[xValues.length]); } + /** + * Constructor + * + * @param id + * The unique ID of the associated entry + * @param name + * The name of the series + * @param xValues + * The x values of this series + * @param yValues + * The y values of this series + * @since 10.1 + */ + public SeriesModel(long id, String name, ISampling xValues, double[] yValues) { + this(id, name, xValues, yValues, new TmfXYAxisDescription(DEFAULT_XAXIS_NAME, DEFAULT_XAXIS_UNIT), new TmfXYAxisDescription(DEFAULT_YAXIS_NAME, DEFAULT_YAXIS_UNIT), DisplayType.LINE, new int[xValues.size()]); + } + /** * Constructor with axis description * @@ -113,11 +131,47 @@ public SeriesModel(long id, String name, long[] xValues, double[] yValues) { * @param properties * The properties values for this series. Some priority values * are available in {@link CoreFilterProperty} + * @deprecated Use {@link ISampling} for xValues instead. */ + @Deprecated(since = "10.1", forRemoval = true) private SeriesModel(long id, String name, long[] xValues, double[] yValues, TmfXYAxisDescription xAxis, TmfXYAxisDescription yAxis, DisplayType displayType, int[] properties) { fId = id; fName = name; - fXValues = xValues; + fSampling = new ISampling.Timestamps(xValues); + fYValues = yValues; + fXAxis = xAxis; + fYAxis = yAxis; + fDisplayType = displayType; + fProperties = properties; + } + + /** + * Constructor that accepts a {@link ISampling} instead of raw x-values. + * + * @param id + * The unique ID of the associated entry + * @param name + * The name of the series + * @param sampling + * The X-axis sampling (e.g., timestamps or categories) + * @param yValues + * The Y values of this series + * @param xAxis + * X Axis description + * @param yAxis + * Y Axis description + * @param displayType + * Display type + * @param properties + * The properties values for this series + * @since 10.1 + */ + public SeriesModel(long id, String name, ISampling sampling, double[] yValues, + TmfXYAxisDescription xAxis, TmfXYAxisDescription yAxis, + DisplayType displayType, int[] properties) { + fId = id; + fName = name; + fSampling = sampling; fYValues = yValues; fXAxis = xAxis; fYAxis = yAxis; @@ -152,7 +206,12 @@ public DisplayType getDisplayType() { @Override public long[] getXAxis() { - return fXValues; + return (fSampling instanceof ISampling.Timestamps ts) ? ts.timestamps() : new long[0]; + } + + @Override + public ISampling getSampling() { + return fSampling; } @Override @@ -179,7 +238,7 @@ public boolean equals(@Nullable Object obj) { SeriesModel other = (SeriesModel) obj; return fName.equals(other.getName()) && fId == other.getId() - && Arrays.equals(fXValues, other.getXAxis()) + && Objects.equals(fSampling, other.getSampling()) && Arrays.equals(fYValues, other.getData()) && fXAxis.equals(other.getXAxisDescription()) && fYAxis.equals(other.getYAxisDescription()); @@ -187,7 +246,7 @@ public boolean equals(@Nullable Object obj) { @Override public int hashCode() { - return Objects.hash(fId, fName, fXValues, fYValues, fXAxis, fYAxis); + return Objects.hash(fId, fName, fSampling, fYValues, fXAxis, fYAxis); } /** @@ -199,7 +258,7 @@ public int hashCode() { public static class SeriesModelBuilder { private final long id; private final String name; - private final long[] xValues; + private final ISampling sampling; private final double[] yValues; private @Nullable TmfXYAxisDescription xAxis; private @Nullable TmfXYAxisDescription yAxis; @@ -217,11 +276,30 @@ public static class SeriesModelBuilder { * The x values of this series * @param yValues * The y values of this series + * @deprecated Use {@link ISampling} for xValues instead. */ + @Deprecated(since = "10.1", forRemoval = true) public SeriesModelBuilder(long id, String name, long[] xValues, double[] yValues) { + this(id, name, new ISampling.Timestamps(xValues), yValues); + } + + /** + * Constructor using {@link ISampling}. + * + * @param id + * The unique ID of the associated entry + * @param name + * The name of the series + * @param sampling + * The sampling of X values + * @param yValues + * The y values of this series + * @since 10.1 + */ + public SeriesModelBuilder(long id, String name, ISampling sampling, double[] yValues) { this.id = id; this.name = name; - this.xValues = xValues; + this.sampling = sampling; this.yValues = yValues; } @@ -279,11 +357,11 @@ public SeriesModelBuilder setProperties(int[] properties) { * @return {@link SeriesModel} */ public SeriesModel build() { - return new SeriesModel(id, name, xValues, yValues, + return new SeriesModel(id, name, sampling, yValues, xAxis != null ? xAxis : new TmfXYAxisDescription(DEFAULT_XAXIS_NAME, DEFAULT_XAXIS_UNIT), yAxis != null ? yAxis : new TmfXYAxisDescription(DEFAULT_YAXIS_NAME, DEFAULT_YAXIS_UNIT), displayType != null ? displayType : DisplayType.LINE, - properties != null ? properties : new int[xValues.length]); + properties != null ? properties : new int[sampling.size()]); } } } diff --git a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/filters/TimeQueryFilter.java b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/filters/TimeQueryFilter.java index 9e2faa149c..0937096b1d 100644 --- a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/filters/TimeQueryFilter.java +++ b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/filters/TimeQueryFilter.java @@ -1,5 +1,5 @@ /********************************************************************** - * Copyright (c) 2017 Ericsson + * Copyright (c) 2017, 2025 Ericsson * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License 2.0 which @@ -13,6 +13,7 @@ import java.util.Arrays; import java.util.List; +import java.util.Objects; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.tracecompass.internal.tmf.core.Activator; @@ -33,7 +34,10 @@ */ public class TimeQueryFilter { - private final long[] fTimesRequested; + private long @Nullable[] fTimesRequested; + private long fStart; + private long fEnd; + private int fNbSamples; /** * Constructor. Given a start value, end value and n entries, this @@ -48,7 +52,9 @@ public class TimeQueryFilter { * The number of entries **/ public TimeQueryFilter(long start, long end, int n) { - fTimesRequested = splitRangeIntoEqualParts(start, end, n); + fStart = start; + fEnd = end; + fNbSamples = n; } /** @@ -58,6 +64,9 @@ public TimeQueryFilter(long start, long end, int n) { * sorted list of times to query. */ public TimeQueryFilter(List times) { + if (times.isEmpty()) { + throw new IllegalArgumentException("The requested times is empty"); //$NON-NLS-1$ + } if (!Ordering.natural().isOrdered(times)) { throw new IllegalArgumentException("List of times is not sorted"); //$NON-NLS-1$ } @@ -70,7 +79,10 @@ public TimeQueryFilter(List times) { * @return The array of requested times */ public long[] getTimesRequested() { - return fTimesRequested; + if (fTimesRequested == null) { + fTimesRequested = splitRangeIntoEqualParts(fStart, fEnd, fNbSamples); + } + return Objects.requireNonNull(fTimesRequested); } /** @@ -79,7 +91,10 @@ public long[] getTimesRequested() { * @return The first time */ public long getStart() { - return fTimesRequested[0]; + if (fTimesRequested != null) { + return fTimesRequested[0]; + } + return fStart; } /** @@ -88,7 +103,23 @@ public long getStart() { * @return The last time */ public long getEnd() { - return fTimesRequested[Integer.max(0, fTimesRequested.length - 1)]; + if (fTimesRequested != null) { + return fTimesRequested[Integer.max(0, Objects.requireNonNull(fTimesRequested).length - 1)]; + } + return fEnd; + } + + /** + * Gets the number of samples + * + * @return The the number of samples + * @since 10.1 + */ + public int getNumberOfSamples() { + if (fTimesRequested != null) { + return Objects.requireNonNull(fTimesRequested).length; + } + return fNbSamples; } /** diff --git a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/genericxy/AbstractTreeGenericXYCommonXDataProvider.java b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/genericxy/AbstractTreeGenericXYCommonXDataProvider.java new file mode 100644 index 0000000000..7721129d7c --- /dev/null +++ b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/genericxy/AbstractTreeGenericXYCommonXDataProvider.java @@ -0,0 +1,160 @@ +/********************************************************************** + * Copyright (c) 2025 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.tmf.core.model.genericxy; + +import org.eclipse.tracecompass.tmf.core.model.tree.AbstractTreeDataProvider; +import org.eclipse.tracecompass.tmf.core.model.tree.ITmfTreeDataModel; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.logging.Level; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jdt.annotation.Nullable; +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.exceptions.TimeRangeException; +import org.eclipse.tracecompass.tmf.core.model.CommonStatusMessage; +import org.eclipse.tracecompass.tmf.core.model.ISampling; +import org.eclipse.tracecompass.tmf.core.model.filters.SelectionTimeQueryFilter; +import org.eclipse.tracecompass.tmf.core.model.xy.ISeriesModel.DisplayType; +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.model.xy.TmfXYAxisDescription; +import org.eclipse.tracecompass.tmf.core.response.TmfModelResponse; +import org.eclipse.tracecompass.tmf.core.statesystem.TmfStateSystemAnalysisModule; +import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; +import org.eclipse.tracecompass.tmf.core.util.Pair; +import org.eclipse.tracecompass.traceeventlogger.LogUtils.FlowScopeLog; +import org.eclipse.tracecompass.traceeventlogger.LogUtils.FlowScopeLogBuilder; + +import com.google.common.collect.ImmutableList; + +/** + * Abstract base class for tree-based data providers support xy chart with + * non-time x-axis visualizations. Default visualization type is set to bars. + *

+ * This class extends {@link AbstractTreeDataProvider} to handle hierarchical + * tree data, while also implementing {@link ITmfGenericXYDataProvider} to + * supply generic xy chart data for views. It is meant to be sub-classed by + * concrete providers that use generic xy chart representations. + *

+ * + * @param + * The type of analysis module used, which must extend + * {@link TmfStateSystemAnalysisModule} + * @param + * The type of tree data model, which must implement + * {@link ITmfTreeDataModel} + * @author Siwei Zhang + * @since 10.1 + */ +public abstract class AbstractTreeGenericXYCommonXDataProvider + extends AbstractTreeDataProvider implements ITmfTreeXYDataProvider { + + /** + * Constructor + * + * @param trace + * The trace associated with this data provider + * @param analysisModule + * The analysis module used to compute data + */ + public AbstractTreeGenericXYCommonXDataProvider(ITmfTrace trace, A analysisModule) { + super(trace, analysisModule); + } + + @Override + public TmfModelResponse fetchXY(Map fetchParameters, @Nullable IProgressMonitor monitor) { + A module = getAnalysisModule(); + + SelectionTimeQueryFilter filter = FetchParametersUtils.createSelectionTimeQueryWithSamples(fetchParameters); + if (filter == null) { + return TmfXyResponseFactory.createFailedResponse(CommonStatusMessage.INCORRECT_QUERY_PARAMETERS); + } + + TmfModelResponse res = verifyParameters(module, filter, monitor); + if (res != null) { + return res; + } + + ITmfStateSystem ss = Objects.requireNonNull(module.getStateSystem(), + "Statesystem should have been verified by verifyParameters"); //$NON-NLS-1$ + long currentEnd = ss.getCurrentEndTime(); + boolean complete = ss.waitUntilBuilt(0) || filter.getEnd() <= currentEnd; + + try (FlowScopeLog scope = new FlowScopeLogBuilder(LOGGER, Level.FINE, "AbstractTreeGenericXYCommonXDataProvider#fetchXY") //$NON-NLS-1$ + .setCategory(getClass().getSimpleName()).build()) { + Pair> xAxisAndYSeriesModels = getXAxisAndYSeriesModels(ss, fetchParameters, monitor); + if (xAxisAndYSeriesModels == null) { + // getModels returns null if the query was cancelled. + return TmfXyResponseFactory.createCancelledResponse(CommonStatusMessage.TASK_CANCELLED); + } + return TmfXyResponseFactory.create( + getTitle(), + xAxisAndYSeriesModels.getFirst(), + ImmutableList.copyOf(xAxisAndYSeriesModels.getSecond()), + getDisplayType(), + getXAxisDescription(), + complete); + } catch (StateSystemDisposedException | TimeRangeException | IndexOutOfBoundsException e) { + return TmfXyResponseFactory.createFailedResponse(String.valueOf(e.getMessage())); + } + } + + /** + * Get the display type for series. + * + * @return Description for series + */ + protected DisplayType getDisplayType() { + return DisplayType.BAR; + } + + /** + * Get description for x axis. + * + * @return Description for x axis + */ + protected abstract TmfXYAxisDescription getXAxisDescription(); + + /** + * Abstract method to be implemented by the providers to return the x axis + * and height of bars. The child class should check the validity of values + * inside query parameters. + * + * @param ss + * the {@link TmfStateSystemAnalysisModule}'s + * {@link ITmfStateSystem} + * @param fetchParameters + * the query's filter + * @param monitor + * progress monitor + * @return a pair, the first element is x values, and the second element is + * Y models; null if the query was cancelled + * @throws StateSystemDisposedException + * if the state system was closed during the query or could not + * be queried. + */ + protected abstract @Nullable Pair> getXAxisAndYSeriesModels(ITmfStateSystem ss, + Map fetchParameters, @Nullable IProgressMonitor monitor) + throws StateSystemDisposedException; + + /** + * Getter for the title of this provider + * + * @return this provider's title + */ + protected abstract String getTitle(); +} diff --git a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/genericxy/package-info.java b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/genericxy/package-info.java new file mode 100644 index 0000000000..22cb8c9165 --- /dev/null +++ b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/genericxy/package-info.java @@ -0,0 +1,13 @@ +/******************************************************************************* + * Copyright (c) 2025 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 + *******************************************************************************/ + +@org.eclipse.jdt.annotation.NonNullByDefault +package org.eclipse.tracecompass.tmf.core.model.genericxy; diff --git a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/tree/AbstractTreeDataProvider.java b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/tree/AbstractTreeDataProvider.java index bb7af58c7d..4875c7cf99 100644 --- a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/tree/AbstractTreeDataProvider.java +++ b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/tree/AbstractTreeDataProvider.java @@ -203,15 +203,14 @@ protected long getEntryId(Object key) { } /** - * Get selected entries from the filter for this provider + * Get selected entries from the filter for this provider. * - * @param filter - * {@link SelectionTimeQueryFilter}. - * @return a map of the valid entries' ID from the filter to their respective - * quark + * @param selectedItems + * the collection of selected item IDs + * @return a map of the valid entries' ID to their respective quark */ - protected Map getSelectedEntries(SelectionTimeQueryFilter filter) { - return getSelectedEntries(filter.getSelectedItems()); + protected Map getSelectedEntries(SelectionTimeQueryFilter selectedItems) { + return getSelectedEntries(selectedItems.getSelectedItems()); } /** diff --git a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/xy/ISeriesModel.java b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/xy/ISeriesModel.java index 421934a7de..c53b560c44 100644 --- a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/xy/ISeriesModel.java +++ b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/xy/ISeriesModel.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 École Polytechnique de Montréal + * Copyright (c) 2017, 2025 École Polytechnique de Montréal * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License 2.0 which @@ -14,6 +14,7 @@ import org.eclipse.tracecompass.common.core.NonNullUtils; import org.eclipse.tracecompass.internal.tmf.core.model.xy.Messages; import org.eclipse.tracecompass.tmf.core.model.CoreFilterProperty; +import org.eclipse.tracecompass.tmf.core.model.ISampling; /** * This represents a model for a series in a XY chart. I should be used to @@ -34,13 +35,18 @@ public interface ISeriesModel { */ public enum DisplayType { /** - * Line + * Line (only for XY line charts) */ LINE, /** - * Scatter + * Scatter (only for XY line charts) */ - SCATTER + SCATTER, + /** + * Bars (only for generic XY charts) + * @since 10.1 + */ + BAR } /** @@ -91,9 +97,22 @@ default DisplayType getDisplayType() { * Get the X values * * @return The x values + * @deprecated Use {@link #getSampling()} instead for support of categorical axes. */ + @Deprecated(since = "10.1", forRemoval = true) long[] getXAxis(); + /** + * Sampling points for the X-axis, supporting both time-based and categorical domains. + * + * @return the X-axis sampling representation + * @since 10.1 + */ + default ISampling getSampling() { + long[] legacy = getXAxis(); + return new ISampling.Timestamps(legacy); + } + /** * Get the y values * diff --git a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/xy/TmfXYAxisDescription.java b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/xy/TmfXYAxisDescription.java index cc0fe27064..3394d8ae5f 100644 --- a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/xy/TmfXYAxisDescription.java +++ b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/model/xy/TmfXYAxisDescription.java @@ -1,5 +1,5 @@ /********************************************************************** - * Copyright (c) 2019 Ericsson + * Copyright (c) 2019, 2025 Ericsson * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License 2.0 which @@ -15,6 +15,7 @@ import org.eclipse.jdt.annotation.Nullable; import org.eclipse.tracecompass.tmf.core.dataprovider.DataType; +import org.eclipse.tracecompass.tmf.core.model.IAxisDomain; /** * Represent a XY Axis description @@ -26,6 +27,7 @@ public class TmfXYAxisDescription { private String fLabel; private String fUnit; private DataType fDataType; + @Nullable private IAxisDomain fAxisDomain; /** * Constructor @@ -57,6 +59,27 @@ public TmfXYAxisDescription(String label, String unit, DataType dataType) { fDataType = dataType; } + /** + * Constructor + * + * @param label + * Label for the axis + * @param unit + * Unit type + * @param dataType + * The type of data this series represents + * @param axisDomain + * The available values for this axis + * @since 10.1 + */ + public TmfXYAxisDescription(String label, String unit, DataType dataType, IAxisDomain axisDomain) { + super(); + fLabel = label; + fUnit = unit; + fDataType = dataType; + fAxisDomain = axisDomain; + } + /** * Get the axis label * @@ -85,6 +108,16 @@ public DataType getDataType() { return fDataType; } + /** + * Get the available values for this axis + * + * @return The available values for this axis + * @since 10.1 + */ + public @Nullable IAxisDomain getAxisDomain() { + return fAxisDomain; + } + @Override public boolean equals(@Nullable Object obj) { if (this == obj) { @@ -99,11 +132,12 @@ public boolean equals(@Nullable Object obj) { TmfXYAxisDescription other = (TmfXYAxisDescription) obj; return fLabel.equals(other.getLabel()) && fUnit.equals(other.getUnit()) - && Objects.equals(fDataType, other.fDataType); + && Objects.equals(fDataType, other.fDataType) + && Objects.equals(fAxisDomain, other.fAxisDomain); } @Override public int hashCode() { - return Objects.hash(fLabel, fUnit, fDataType); + return Objects.hash(fLabel, fUnit, fDataType, fAxisDomain); } }