Skip to content

Commit ecf257a

Browse files
author
Siwei Zhang
committed
server: Add bar chart data provider with /tree endpoint
This commit uses the newly introduced data provider type to support "bar chart" views. It focuses on implementing the /tree endpoint, which is the first one invoked by the viewer. [Added] /tree endpoint for bar chart view
1 parent ea336de commit ecf257a

13 files changed

Lines changed: 870 additions & 28 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Ericsson and others
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+
12+
package org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.services;
13+
14+
import static org.junit.Assert.assertEquals;
15+
import static org.junit.Assert.assertFalse;
16+
import static org.junit.Assert.assertNotNull;
17+
import static org.junit.Assert.assertNull;
18+
import static org.junit.Assert.assertTrue;
19+
20+
import java.util.Arrays;
21+
import java.util.Collections;
22+
import java.util.HashMap;
23+
import java.util.List;
24+
import java.util.Map;
25+
26+
import javax.ws.rs.client.Entity;
27+
import javax.ws.rs.client.WebTarget;
28+
import javax.ws.rs.core.Response;
29+
30+
import org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.model.views.QueryParameters;
31+
import org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.services.DataProviderService;
32+
import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.AxisDomainStub;
33+
import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.EntryStub;
34+
import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.ExperimentModelStub;
35+
import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.TmfTreeModelWithAxisDescriptorsResponseStub;
36+
import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.TmfTreeModelWithAxisDescriptorsStub;
37+
import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.TmfXYAxisDescriptionStub;
38+
import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.utils.RestServerTest;
39+
import org.junit.Test;
40+
41+
/**
42+
* Test {@link DataProviderService} with bar chart endpoints.
43+
*
44+
* @author Siwei Zhang
45+
*/
46+
@SuppressWarnings({"null"})
47+
public class BarChartDataProviderServiceTest extends RestServerTest {
48+
private static final String DATA_PROVIDER_RESPONSE_FAILED_MSG = "There should be a positive response for the data provider";
49+
private static final String MODEL_NULL_MSG = "The model is null, maybe the analysis did not run long enough?";
50+
private static final int MAX_ITER = 40;
51+
private static final List<EntryStub> EXPECTED_ENTRIES = List.of(
52+
new EntryStub(Arrays.asList("ust"), 0, -1, true, null),
53+
new EntryStub(Arrays.asList("UNKNOWN_PID"), 1, 0, true, null));
54+
55+
/**
56+
* Ensure that an bar chart data provider exists and returns correct data.
57+
* It does not test the data itself, simply that the serialized fields are
58+
* the expected ones according to the protocol. Tested using bar chart
59+
* provider for call stack.
60+
*
61+
* @throws InterruptedException
62+
* Exception thrown while waiting to execute again
63+
*/
64+
@Test
65+
public void testBarChartDataProvider() throws InterruptedException {
66+
ExperimentModelStub exp = assertPostExperiment(sfContextSwitchesUstNotInitializedStub.getName(), sfContextSwitchesUstNotInitializedStub);
67+
68+
WebTarget callstackTree = getBarChartTreeEndpoint(exp.getUUID().toString(), CALL_STACK_BAR_CHART_DATAPROVIDER_ID);
69+
70+
// Test getting the tree endpoint with descriptors for bar chart
71+
Map<String, Object> parameters = new HashMap<>();
72+
parameters.put(REQUESTED_TIMES_KEY, List.of(0L, Long.MAX_VALUE));
73+
TmfTreeModelWithAxisDescriptorsResponseStub responseModel;
74+
try (Response tree = callstackTree.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) {
75+
assertEquals(DATA_PROVIDER_RESPONSE_FAILED_MSG, 200, tree.getStatus());
76+
responseModel = tree.readEntity(TmfTreeModelWithAxisDescriptorsResponseStub.class);
77+
assertNotNull(responseModel);
78+
}
79+
// Make sure the analysis ran enough and we have a model
80+
int iteration = 0;
81+
while (responseModel.isRunning() && responseModel.getModel() == null && iteration < MAX_ITER) {
82+
Thread.sleep(100);
83+
try (Response treeResponse = callstackTree.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) {
84+
assertEquals(DATA_PROVIDER_RESPONSE_FAILED_MSG, 200, treeResponse.getStatus());
85+
responseModel = treeResponse.readEntity(TmfTreeModelWithAxisDescriptorsResponseStub.class);
86+
assertNotNull(responseModel);
87+
iteration++;
88+
}
89+
}
90+
91+
// Validate model
92+
TmfTreeModelWithAxisDescriptorsStub model = responseModel.getModel();
93+
assertNotNull(MODEL_NULL_MSG + responseModel, model);
94+
95+
// Validate axis descriptions
96+
TmfXYAxisDescriptionStub xAxis = model.getXAxisDescription();
97+
TmfXYAxisDescriptionStub yAxis = model.getYAxisDescription();
98+
assertNotNull("X axis description should not be null", xAxis);
99+
assertNotNull("Y axis description should not be null", yAxis);
100+
List<EntryStub> entries = model.getEntries();
101+
assertFalse(entries.isEmpty());
102+
}
103+
104+
/**
105+
* Ensure that the inside data is correct for call stack bar chart data
106+
* provider for both tree end point and bars end point.
107+
*
108+
* @throws InterruptedException
109+
* Exception thrown while waiting to execute again
110+
*/
111+
@Test
112+
public void testCallStackBarChartDataProvider() throws InterruptedException {
113+
ExperimentModelStub exp = assertPostExperiment(sfContextSwitchesUstNotInitializedStub.getName(), sfContextSwitchesUstNotInitializedStub);
114+
115+
WebTarget callstackTree = getBarChartTreeEndpoint(exp.getUUID().toString(), CALL_STACK_BAR_CHART_DATAPROVIDER_ID);
116+
117+
/*
118+
* Test the data in the tree end point with descriptors.
119+
*/
120+
Map<String, Object> parameters = new HashMap<>();
121+
parameters.put(REQUESTED_TIMES_KEY, List.of(0L, Long.MAX_VALUE));
122+
TmfTreeModelWithAxisDescriptorsResponseStub responseModel;
123+
try (Response tree = callstackTree.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) {
124+
assertEquals(DATA_PROVIDER_RESPONSE_FAILED_MSG, 200, tree.getStatus());
125+
responseModel = tree.readEntity(TmfTreeModelWithAxisDescriptorsResponseStub.class);
126+
assertNotNull(responseModel);
127+
}
128+
// Make sure the analysis ran enough and we have a model
129+
int iteration = 0;
130+
while (responseModel.isRunning() && responseModel.getModel() == null && iteration < MAX_ITER) {
131+
Thread.sleep(100);
132+
try (Response treeResponse = callstackTree.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) {
133+
assertEquals(DATA_PROVIDER_RESPONSE_FAILED_MSG, 200, treeResponse.getStatus());
134+
responseModel = treeResponse.readEntity(TmfTreeModelWithAxisDescriptorsResponseStub.class);
135+
assertNotNull(responseModel);
136+
iteration++;
137+
}
138+
}
139+
140+
// Validate model
141+
TmfTreeModelWithAxisDescriptorsStub model = responseModel.getModel();
142+
assertNotNull(MODEL_NULL_MSG + responseModel, model);
143+
144+
// Validate axis descriptions (fully)
145+
TmfXYAxisDescriptionStub xAxis = model.getXAxisDescription();
146+
TmfXYAxisDescriptionStub yAxis = model.getYAxisDescription();
147+
148+
assertNotNull("X axis description should not be null", xAxis);
149+
assertNotNull("Y axis description should not be null", yAxis);
150+
151+
// X axis
152+
assertEquals("X axis label mismatch", "Execution Time", xAxis.getLabel());
153+
assertEquals("X axis unit mismatch", "ns", xAxis.getUnit());
154+
assertEquals("X axis data type mismatch", "DURATION", xAxis.getDataType());
155+
AxisDomainStub xDomain = xAxis.getAxisDomain();
156+
assertNotNull("X axis domain should not be null", xDomain);
157+
assertTrue("X axis domain should be TimeRange", xDomain instanceof AxisDomainStub.RangeStub);
158+
AxisDomainStub.RangeStub timeRange = (AxisDomainStub.RangeStub) xDomain;
159+
assertEquals("X axis start time mismatch", 1L, timeRange.getStart());
160+
assertEquals("X axis end time mismatch", 5978542746L, timeRange.getEnd());
161+
162+
// Y axis
163+
assertEquals("Y axis label mismatch", "Number of Executions", yAxis.getLabel());
164+
assertEquals("Y axis unit mismatch", "", yAxis.getUnit());
165+
assertEquals("Y axis data type mismatch", "NUMBER", yAxis.getDataType());
166+
AxisDomainStub yDomain = yAxis.getAxisDomain();
167+
assertNull("Y axis domain should be null", yDomain);
168+
169+
// Scope & Auto-expand level
170+
String scope = model.getScope();
171+
assertEquals("Scope mismatch", "DEFAULT", scope);
172+
int autoExpandLevel = model.getAutoExpandLevel();
173+
assertEquals("Auto-expand level mismatch", -1, autoExpandLevel);
174+
175+
// Entries
176+
List<EntryStub> actualEntries = model.getEntries();
177+
assertEquals("Entry count mismatch", EXPECTED_ENTRIES.size(), actualEntries.size());
178+
179+
for (int i = 0; i < EXPECTED_ENTRIES.size(); i++) {
180+
EntryStub expected = EXPECTED_ENTRIES.get(i);
181+
EntryStub actual = actualEntries.get(i);
182+
assertEquals("Entry ID mismatch at index " + i, expected.getId(), actual.getId());
183+
assertEquals("Parent ID mismatch at index " + i, expected.getParentId(), actual.getParentId());
184+
assertEquals("HasRowModel mismatch at index " + i, expected.hasRowModel(), actual.hasRowModel());
185+
assertEquals("Labels mismatch at index " + i, expected.getLabels(), actual.getLabels());
186+
assertEquals("Style mismatch at index " + i, expected.getStyle(), actual.getStyle());
187+
}
188+
}
189+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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.incubator.trace.server.jersey.rest.core.tests.stubs;
12+
13+
import java.io.Serializable;
14+
import java.util.List;
15+
import java.util.Objects;
16+
17+
import org.eclipse.jdt.annotation.Nullable;
18+
19+
import com.fasterxml.jackson.annotation.JsonCreator;
20+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
21+
import com.fasterxml.jackson.annotation.JsonProperty;
22+
23+
/**
24+
* A base class stub for ITmfTreeModel implementations. This class provides
25+
* the common fields and structure used by tree model representations, including
26+
* entries, scope, and auto-expand level.
27+
*
28+
* @param <T>
29+
* the entry type used in this tree model
30+
* @author Siwei Zhang
31+
* @since 10.2
32+
*/
33+
@JsonIgnoreProperties(ignoreUnknown = true)
34+
public abstract class AbstractTreeModelStub<T extends EntryStub> implements Serializable {
35+
36+
private static final long serialVersionUID = -7234567890123456789L;
37+
38+
@JsonProperty("entries")
39+
private final List<T> fEntries;
40+
41+
@JsonProperty("scope")
42+
private final @Nullable String fScope;
43+
44+
@JsonProperty("autoExpandLevel")
45+
private final int fAutoExpandLevel;
46+
47+
/**
48+
* Constructor
49+
*
50+
* @param entries
51+
* the tree entries
52+
* @param scope
53+
* optional scope identifier
54+
* @param autoExpandLevel
55+
* the auto-expand level
56+
*/
57+
@JsonCreator
58+
protected AbstractTreeModelStub(
59+
@JsonProperty("entries") List<T> entries,
60+
@JsonProperty("scope") @Nullable String scope,
61+
@JsonProperty("autoExpandLevel") @Nullable Integer autoExpandLevel) {
62+
fEntries = Objects.requireNonNull(entries, "The 'entries' JSON field was not set");
63+
fScope = scope;
64+
fAutoExpandLevel = (autoExpandLevel == null ? -1 : autoExpandLevel);
65+
}
66+
67+
/**
68+
* Get the entries of the tree model
69+
*
70+
* @return list of tree entries
71+
*/
72+
public List<T> getEntries() {
73+
return fEntries;
74+
}
75+
76+
/**
77+
* Get the optional scope associated with this tree model
78+
*
79+
* @return scope string or null
80+
*/
81+
public @Nullable String getScope() {
82+
return fScope;
83+
}
84+
85+
/**
86+
* Get the auto-expand level (e.g., for tree UI expansion)
87+
*
88+
* @return the auto-expand level
89+
*/
90+
public int getAutoExpandLevel() {
91+
return fAutoExpandLevel;
92+
}
93+
}

0 commit comments

Comments
 (0)