Skip to content

Commit ef966b7

Browse files
author
Siwei Zhang
committed
tmf: Add bar chart data provider type with feature to fetch tree
This commit introduces a new data provider type to support "bar chart" views. It focuses on implementing the feature to fetch tree. CallStackAnalysis is used as the initial example input to demonstrate this functionality through a function density view. [Added] A new data provider type called ITmfBarChartDataProvider, only the feature to fetch tree is implemented in this commit. [Added] CallStackBarChartDataProvider, showcasing a function density view as an example implementation. Signed-off-by: Siwei Zhang <siwei.zhang@ericsson.com>
1 parent 980bc83 commit ef966b7

23 files changed

Lines changed: 1415 additions & 37 deletions

File tree

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.genericxy.TmfTreeGenericXYCompositeDataProvider;
28+
import org.eclipse.tracecompass.tmf.core.model.tree.ITmfTreeDataModel;
29+
import org.eclipse.tracecompass.tmf.core.model.tree.ITmfTreeDataProvider;
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 TmfTreeGenericXYCompositeDataProvider<>(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)