Skip to content

Commit a55a408

Browse files
Siwei ZhangPatrickTasse
authored andcommitted
tmf: Add generic xy data provider type
This commit introduces a new data provider type to support xy views with non-time x-axis. CallStackAnalysis is used as the initial example input to demonstrate this functionality through a function density view. [Added] A new data provider type called AbstractTreeGenericXYCommonXDataProvider. [Added] CallStackFunctionDensityDataProvider, showcasing a function density view as an example implementation. Signed-off-by: Siwei Zhang <[email protected]>
1 parent 720fb62 commit a55a408

File tree

20 files changed

+1174
-39
lines changed

20 files changed

+1174
-39
lines changed

analysis/org.eclipse.tracecompass.analysis.profiling.core/plugin.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
class="org.eclipse.tracecompass.internal.analysis.profiling.core.instrumented.FlameChartDataProviderFactory"
2020
id="org.eclipse.tracecompass.analysis.profiling.core.flamechart">
2121
</dataProviderFactory>
22+
<dataProviderFactory
23+
class="org.eclipse.tracecompass.internal.analysis.profiling.core.callstack.provider.CallStackFunctionDensityDataProviderFactory"
24+
id="org.eclipse.tracecompass.analysis.profiling.core.callstack.functiondensity.provider">
25+
</dataProviderFactory>
2226
</extension>
2327
<extension
2428
point="org.eclipse.linuxtools.tmf.core.analysis">
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
/**********************************************************************
2+
* Copyright (c) 2025 Ericsson
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License 2.0 which
6+
* accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
**********************************************************************/
11+
package org.eclipse.tracecompass.internal.analysis.profiling.core.callstack.provider;
12+
13+
import java.util.ArrayList;
14+
import java.util.Collection;
15+
import java.util.Collections;
16+
import java.util.HashMap;
17+
import java.util.List;
18+
import java.util.Map;
19+
import java.util.Map.Entry;
20+
import java.util.Objects;
21+
22+
import org.eclipse.core.runtime.IProgressMonitor;
23+
import org.eclipse.jdt.annotation.NonNull;
24+
import org.eclipse.jdt.annotation.Nullable;
25+
import org.eclipse.tracecompass.analysis.profiling.core.callstack.CallStackAnalysis;
26+
import org.eclipse.tracecompass.analysis.profiling.core.callstack.CallStackSeries;
27+
import org.eclipse.tracecompass.common.core.NonNullUtils;
28+
import org.eclipse.tracecompass.internal.analysis.profiling.core.callgraph.ICalledFunction;
29+
import org.eclipse.tracecompass.internal.tmf.core.model.filters.FetchParametersUtils;
30+
import org.eclipse.tracecompass.segmentstore.core.ISegment;
31+
import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
32+
import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException;
33+
import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval;
34+
import org.eclipse.tracecompass.tmf.core.dataprovider.DataType;
35+
import org.eclipse.tracecompass.tmf.core.model.IAxisDomain;
36+
import org.eclipse.tracecompass.tmf.core.model.ISampling;
37+
import org.eclipse.tracecompass.tmf.core.model.YModel;
38+
import org.eclipse.tracecompass.tmf.core.model.filters.SelectionTimeQueryFilter;
39+
import org.eclipse.tracecompass.tmf.core.model.genericxy.AbstractTreeGenericXYCommonXDataProvider;
40+
import org.eclipse.tracecompass.tmf.core.model.tree.ITmfTreeDataModel;
41+
import org.eclipse.tracecompass.tmf.core.model.tree.TmfTreeDataModel;
42+
import org.eclipse.tracecompass.tmf.core.model.tree.TmfTreeModel;
43+
import org.eclipse.tracecompass.tmf.core.model.xy.IYModel;
44+
import org.eclipse.tracecompass.tmf.core.model.xy.TmfXYAxisDescription;
45+
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
46+
import org.eclipse.tracecompass.tmf.core.util.Pair;
47+
48+
/**
49+
* Bar chart provider to show function duration distributions in the call stack.
50+
*
51+
* @author Siwei Zhang
52+
* @since 2.6
53+
*/
54+
public class CallStackFunctionDensityDataProvider extends AbstractTreeGenericXYCommonXDataProvider<@NonNull CallStackAnalysis, @NonNull ITmfTreeDataModel> {
55+
56+
/**
57+
* Provider id.
58+
*/
59+
public static final String ID = "org.eclipse.tracecompass.analysis.profiling.core.callstack.functiondensity.provider"; //$NON-NLS-1$
60+
61+
private static final String EXECUTION_TIME = "Execution Time"; //$NON-NLS-1$
62+
private static final String NUMBER_OF_EXECUTIONS = "Number of Executions"; //$NON-NLS-1$
63+
private static final String UNIT_NS = "ns"; //$NON-NLS-1$
64+
private static final String UNIT_EMPTY = ""; //$NON-NLS-1$
65+
private final Map<Integer, Long> fPidsToEntryIds = new HashMap<>();
66+
private static final int UNKNOWN_PID = -1;
67+
private static final TmfXYAxisDescription Y_AXIS_DESCRIPTION = new TmfXYAxisDescription(
68+
NUMBER_OF_EXECUTIONS, UNIT_EMPTY, DataType.NUMBER);
69+
70+
/**
71+
* Stores the state system end time when making the query and the maximum
72+
* execution time.
73+
*/
74+
private Pair<Long, Long> fEndTimeToMaxDuration = null;
75+
76+
/**
77+
* Constructor.
78+
*
79+
* @param trace
80+
* The trace associated with this data provider
81+
* @param analysisModule
82+
* The analysis module used to compute data
83+
*/
84+
public CallStackFunctionDensityDataProvider(@NonNull ITmfTrace trace, @NonNull CallStackAnalysis analysisModule) {
85+
super(trace, analysisModule);
86+
}
87+
88+
@Override
89+
public TmfXYAxisDescription getXAxisDescription() {
90+
long maxDuration = getMaxExecutionTime();
91+
if (maxDuration == -1) {
92+
return new TmfXYAxisDescription(
93+
EXECUTION_TIME, UNIT_NS, DataType.DURATION, new IAxisDomain.Range(-1, -1));
94+
}
95+
96+
return new TmfXYAxisDescription(
97+
EXECUTION_TIME, UNIT_NS, DataType.DURATION, new IAxisDomain.Range(0, maxDuration));
98+
}
99+
100+
/**
101+
* Find the maximum duration in the segment store.
102+
*/
103+
private long getMaxExecutionTime() {
104+
ITmfStateSystem ss = getAnalysisModule().getStateSystem();
105+
if (ss == null) {
106+
return -1;
107+
}
108+
109+
if (fEndTimeToMaxDuration != null && fEndTimeToMaxDuration.getFirst() == ss.getCurrentEndTime()) {
110+
return fEndTimeToMaxDuration.getSecond();
111+
}
112+
113+
114+
CallStackSeries callstackSeries = getAnalysisModule().getCallStackSeries();
115+
if (callstackSeries == null) {
116+
return -1;
117+
}
118+
119+
long maxDuration = -1;
120+
for (ISegment segment : callstackSeries.getIntersectingElements(ss.getStartTime(), ss.getCurrentEndTime())) {
121+
maxDuration = Math.max(segment.getLength(), maxDuration);
122+
}
123+
124+
fEndTimeToMaxDuration = new Pair<>(ss.getCurrentEndTime(), maxDuration);
125+
return maxDuration;
126+
}
127+
128+
@Override
129+
public String getId() {
130+
return ID;
131+
}
132+
133+
@Override
134+
protected boolean isCacheable() {
135+
return false;
136+
}
137+
138+
@Override
139+
protected TmfTreeModel<@NonNull ITmfTreeDataModel> getTree(@NonNull ITmfStateSystem ss, @NonNull Map<@NonNull String, @NonNull Object> fetchParameters, @Nullable IProgressMonitor monitor) throws StateSystemDisposedException {
140+
CallStackSeries series = getAnalysisModule().getCallStackSeries();
141+
if (series == null) {
142+
return new TmfTreeModel<>(Collections.emptyList(), Collections.emptyList());
143+
}
144+
145+
List<@NonNull ITmfTreeDataModel> entries = new ArrayList<>();
146+
long traceId = getId(ITmfStateSystem.ROOT_ATTRIBUTE);
147+
entries.add(new TmfTreeDataModel(traceId, -1, NonNullUtils.nullToEmptyString(getTrace().getName())));
148+
149+
List<@NonNull Integer> processQuarks = ss.getQuarks(getAnalysisModule().getProcessesPattern());
150+
long end = ss.getCurrentEndTime();
151+
List<@NonNull ITmfStateInterval> fullEnd = ss.queryFullState(end);
152+
for(@NonNull Integer processQuark : processQuarks) {
153+
int pid = UNKNOWN_PID;
154+
if (processQuark != ITmfStateSystem.ROOT_ATTRIBUTE) {
155+
String processName = ss.getAttributeName(processQuark);
156+
Object processValue = fullEnd.get(processQuark).getValue();
157+
pid = getThreadProcessId(processName, processValue);
158+
}
159+
long entryId = getId(processQuark);
160+
fPidsToEntryIds.put(pid, entryId);
161+
entries.add(new TmfTreeDataModel(entryId, traceId, getNameFromPID(pid)));
162+
}
163+
return new TmfTreeModel<>(Collections.emptyList(), entries);
164+
}
165+
166+
private static @NonNull String getNameFromPID(int pid) {
167+
return pid == UNKNOWN_PID ? "UNKNOWN_PID" : String.valueOf(pid); //$NON-NLS-1$
168+
}
169+
170+
private static int getThreadProcessId(String name, @Nullable Object value) {
171+
if (value instanceof Number) {
172+
return ((Number) value).intValue();
173+
}
174+
try {
175+
return Integer.parseInt(name);
176+
} catch (NumberFormatException e) {
177+
return UNKNOWN_PID;
178+
}
179+
}
180+
181+
@Override
182+
protected @Nullable Pair<@NonNull ISampling,@NonNull Collection<@NonNull IYModel>> getXAxisAndYSeriesModels(
183+
@NonNull ITmfStateSystem ss,
184+
@NonNull Map<@NonNull String, @NonNull Object> fetchParameters,
185+
@Nullable IProgressMonitor monitor) throws StateSystemDisposedException {
186+
187+
SelectionTimeQueryFilter filter = FetchParametersUtils.createSelectionTimeQueryWithSamples(fetchParameters);
188+
if (filter == null) {
189+
return null;
190+
}
191+
192+
CallStackSeries series = getAnalysisModule().getCallStackSeries();
193+
if (series == null) {
194+
return null;
195+
}
196+
197+
long sampleStart = 0;
198+
long sampleEnd = getMaxExecutionTime();
199+
int nbSamples = filter.getNumberOfSamples();
200+
ISampling.Ranges sampling = createEvenlyDistributedRanges(sampleStart, sampleEnd, nbSamples);
201+
if(sampling == null) {
202+
return null;
203+
}
204+
205+
List<ISampling.@NonNull Range<@NonNull Long>> ranges = sampling.ranges();
206+
Map<Integer, double[]> pidsToBins = new HashMap<>();
207+
Map<@NonNull Long, @NonNull Integer> selectedEntries = getSelectedEntries(filter);
208+
// Initialize bins only for selected entries
209+
for (Entry<Integer, Long> pidToEntryId : fPidsToEntryIds.entrySet()) {
210+
int pid = pidToEntryId.getKey();
211+
long entryId = pidToEntryId.getValue();
212+
if (selectedEntries.containsKey(entryId)) {
213+
pidsToBins.put(pid, new double[ranges.size()]);
214+
}
215+
}
216+
217+
// Count function durations falling in each bin
218+
long totalSpan = sampleEnd - sampleStart + 1;
219+
long step = totalSpan / nbSamples;
220+
for (ISegment segment : series.getIntersectingElements(filter.getStart(), filter.getEnd())) {
221+
if (segment instanceof ICalledFunction function) {
222+
int pid = function.getProcessId();
223+
double[] bins = pidsToBins.get(pid);
224+
if (bins == null) {
225+
continue;
226+
}
227+
long duration = segment.getLength();
228+
229+
if (step > 0 && duration >= sampleStart && duration <= sampleEnd) {
230+
int index = (int) ((duration - sampleStart) / step);
231+
if (index >= nbSamples) {
232+
index = nbSamples - 1;
233+
}
234+
bins[index]++;
235+
}
236+
}
237+
}
238+
239+
// Build final Y models
240+
List<@NonNull IYModel> yModels = new ArrayList<>();
241+
for (Entry<Integer, double[]> entry : pidsToBins.entrySet()) {
242+
int pid = entry.getKey();
243+
double[] bins = entry.getValue();
244+
long entryId = fPidsToEntryIds.getOrDefault(pid, -1L);
245+
yModels.add(new YModel(entryId, getNameFromPID(pid), bins, Y_AXIS_DESCRIPTION));
246+
}
247+
248+
return new Pair<>(sampling, yModels);
249+
}
250+
251+
private static ISampling.@Nullable Ranges createEvenlyDistributedRanges(long start, long end, int samples) {
252+
if (samples <= 0 || start >= end) {
253+
return null;
254+
}
255+
256+
List<ISampling.@NonNull Range<@NonNull Long>> ranges = new ArrayList<>(samples);
257+
long totalSpan = end - start;
258+
long step = totalSpan / samples;
259+
long remainder = totalSpan % samples;
260+
261+
long current = start;
262+
for (int i = 0; i < samples; i++) {
263+
long rangeStart = current;
264+
long rangeEnd = current + step - 1;
265+
if (remainder > 0) {
266+
rangeEnd += 1;
267+
remainder--;
268+
}
269+
if (rangeEnd >= end || i == samples - 1) {
270+
rangeEnd = end;
271+
}
272+
ranges.add(new ISampling.Range<>(rangeStart, rangeEnd));
273+
current = rangeEnd + 1;
274+
}
275+
276+
return new ISampling.Ranges(ranges);
277+
}
278+
279+
@Override
280+
protected String getTitle() {
281+
String title = Objects.requireNonNull(Messages.CallStackFunctionDensityDataProviderFactory_title);
282+
return title;
283+
}
284+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**********************************************************************
2+
* Copyright (c) 2025 Ericsson
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License 2.0 which
6+
* accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
**********************************************************************/
11+
package org.eclipse.tracecompass.internal.analysis.profiling.core.callstack.provider;
12+
13+
import java.util.ArrayList;
14+
import java.util.Collection;
15+
import java.util.Collections;
16+
import java.util.Iterator;
17+
import java.util.List;
18+
import java.util.Objects;
19+
20+
import org.eclipse.jdt.annotation.NonNull;
21+
import org.eclipse.jdt.annotation.Nullable;
22+
import org.eclipse.tracecompass.analysis.profiling.core.callstack.CallStackAnalysis;
23+
import org.eclipse.tracecompass.tmf.core.dataprovider.IDataProviderDescriptor;
24+
import org.eclipse.tracecompass.tmf.core.dataprovider.IDataProviderDescriptor.ProviderType;
25+
import org.eclipse.tracecompass.tmf.core.dataprovider.IDataProviderFactory;
26+
import org.eclipse.tracecompass.tmf.core.model.DataProviderDescriptor;
27+
import org.eclipse.tracecompass.tmf.core.model.tree.ITmfTreeDataModel;
28+
import org.eclipse.tracecompass.tmf.core.model.tree.ITmfTreeDataProvider;
29+
import org.eclipse.tracecompass.tmf.core.model.xy.TmfTreeXYCompositeDataProvider;
30+
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
31+
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
32+
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
33+
import org.eclipse.tracecompass.tmf.core.trace.experiment.TmfExperiment;
34+
35+
import com.google.common.collect.Iterables;
36+
37+
/**
38+
* {@link CallStackFunctionDensityDataProvider} factory.
39+
*
40+
* @author Siwei Zhang
41+
* @since 2.6
42+
*/
43+
public class CallStackFunctionDensityDataProviderFactory implements IDataProviderFactory {
44+
45+
private static final IDataProviderDescriptor DESCRIPTOR = new DataProviderDescriptor.Builder()
46+
.setId(CallStackFunctionDensityDataProvider.ID)
47+
.setName(Objects.requireNonNull(Messages.CallStackFunctionDensityDataProviderFactory_title))
48+
.setDescription(Objects.requireNonNull(Messages.CallStackFunctionDensityDataProviderFactory_descriptionText))
49+
.setProviderType(ProviderType.TREE_GENERIC_XY)
50+
.build();
51+
52+
@Override
53+
public @Nullable ITmfTreeDataProvider<? extends ITmfTreeDataModel> createProvider(ITmfTrace trace) {
54+
if (trace instanceof TmfExperiment) {
55+
@NonNull List<@NonNull CallStackFunctionDensityDataProvider> providers = new ArrayList<>();
56+
for (ITmfTrace child : TmfTraceManager.getTraceSet(trace)) {
57+
CallStackFunctionDensityDataProvider provider = createProviderLocal(child);
58+
if (provider != null) {
59+
providers.add(provider);
60+
}
61+
}
62+
if (providers.size() == 1) {
63+
return providers.get(0);
64+
}
65+
if (!providers.isEmpty()) {
66+
String title = Objects.requireNonNull(Messages.CallStackFunctionDensityDataProviderFactory_title);
67+
return new TmfTreeXYCompositeDataProvider<>(providers, title, CallStackFunctionDensityDataProvider.ID);
68+
}
69+
return null;
70+
}
71+
return createProviderLocal(trace);
72+
}
73+
74+
private static @Nullable CallStackFunctionDensityDataProvider createProviderLocal(@NonNull ITmfTrace trace) {
75+
Iterator<CallStackAnalysis> modules = TmfTraceUtils.getAnalysisModulesOfClass(trace, CallStackAnalysis.class).iterator();
76+
while (modules.hasNext()) {
77+
CallStackAnalysis first = modules.next();
78+
first.schedule();
79+
return new CallStackFunctionDensityDataProvider(trace, first);
80+
}
81+
return null;
82+
}
83+
84+
@Override
85+
public Collection<IDataProviderDescriptor> getDescriptors(@NonNull ITmfTrace trace) {
86+
Iterable<@NonNull CallStackAnalysis> modules = TmfTraceUtils.getAnalysisModulesOfClass(trace, CallStackAnalysis.class);
87+
return !Iterables.isEmpty(modules) ? Collections.singletonList(DESCRIPTOR) : Collections.emptyList();
88+
}
89+
}

0 commit comments

Comments
 (0)