diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/services/GenericXYDataProviderServiceTest.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/services/GenericXYDataProviderServiceTest.java new file mode 100644 index 000000000..7f8321452 --- /dev/null +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/services/GenericXYDataProviderServiceTest.java @@ -0,0 +1,282 @@ +/******************************************************************************* + * Copyright (c) 2025 Ericsson and others + * + * 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.incubator.trace.server.jersey.rest.core.tests.services; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.Response; + +import org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.model.views.QueryParameters; +import org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.services.DataProviderService; +import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.EntryStub; +import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.ExperimentModelStub; +import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.IAxisDomainStub; +import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.XyEntryModelStub; +import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.XyEntryStub; +import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.XyTreeOutputResponseStub; +import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.ISamplingStub; +import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.TmfXYAxisDescriptionStub; +import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.XyModelStub; +import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.XyOutputResponseStub; +import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.XySeriesStub; +import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.utils.RestServerTest; +import org.eclipse.tracecompass.internal.tmf.core.Activator; +import org.eclipse.tracecompass.tmf.core.model.StyleProperties; +import org.junit.Test; + +import com.google.common.collect.ImmutableMap; + +/** + * Test {@link DataProviderService} with generic xy endpoints with non-time x-axis. + * + * @author Siwei Zhang + */ +@SuppressWarnings({"null"}) +public class GenericXYDataProviderServiceTest extends RestServerTest { + private static final String DATA_PROVIDER_RESPONSE_FAILED_MSG = "There should be a positive response for the data provider"; + private static final String MODEL_NULL_MSG = "The model is null, maybe the analysis did not run long enough?"; + private static final String REQUESTED_ITEMS_KEY = "requested_items"; + private static final String REQUESTED_TIMERANGE_KEY = "requested_timerange"; + private static final String START = "start"; + private static final String END = "end"; + private static final String NB_SAMPLES = "nbSamples"; + private static final int MAX_ITER = 40; + private static final long TRACE_START_TIME = 1450193697034689597L; + private static final long TRACE_END_TIME = 1450193745774189602L; + private static final List EXPECTED_ENTRIES = List.of( + new XyEntryStub(Arrays.asList("ust"), 0, -1, true, null, true), + new XyEntryStub(Arrays.asList("UNKNOWN_PID"), 1, 0, true, null, false)); + + /** + * Ensure that a generic xy data provider exists and returns correct data. + * It does not test the data itself, simply that the serialized fields are + * the expected ones according to the protocol. Tested using generic xy + * provider for call stack. + * + * @throws InterruptedException + * Exception thrown while waiting to execute again + */ + @Test + public void testGenericXYDataProvider() throws InterruptedException { + ExperimentModelStub exp = assertPostExperiment(sfContextSwitchesUstNotInitializedStub.getName(), sfContextSwitchesUstNotInitializedStub); + + WebTarget callstackTree = getGenericXYTreeEndpoint(exp.getUUID().toString(), CALL_STACK_FUNCTION_DENSITY_DATAPROVIDER_ID); + + // Test getting the tree endpoint with descriptors + Map parameters = new HashMap<>(); + parameters.put(REQUESTED_TIMES_KEY, List.of(0L, Long.MAX_VALUE)); + XyTreeOutputResponseStub responseModel; + try (Response tree = callstackTree.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + assertEquals(DATA_PROVIDER_RESPONSE_FAILED_MSG, 200, tree.getStatus()); + responseModel = tree.readEntity(XyTreeOutputResponseStub.class); + assertNotNull(responseModel); + } + // Make sure the analysis ran enough and we have a model + int iteration = 0; + while (responseModel.isRunning() && responseModel.getModel() == null && iteration < MAX_ITER) { + Thread.sleep(100); + try (Response treeResponse = callstackTree.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + assertEquals(DATA_PROVIDER_RESPONSE_FAILED_MSG, 200, treeResponse.getStatus()); + responseModel = treeResponse.readEntity(XyTreeOutputResponseStub.class); + assertNotNull(responseModel); + iteration++; + } + } + + // Validate model + XyEntryModelStub model = responseModel.getModel(); + assertNotNull(MODEL_NULL_MSG + responseModel, model); + + List entries = model.getEntries(); + assertFalse(entries.isEmpty()); + + // Test getting the generic xy endpoint with descriptors for xy + WebTarget xyEnpoint = getGenericXYSeriesEndpoint(exp.getUUID().toString(), CALL_STACK_FUNCTION_DENSITY_DATAPROVIDER_ID); + parameters = new HashMap<>(); + List items = new ArrayList<>(); + for (EntryStub entry : entries) { + items.add(entry.getId()); + } + parameters.put(REQUESTED_ITEMS_KEY, items); + parameters.put(REQUESTED_TIMERANGE_KEY, ImmutableMap.of(START, TRACE_START_TIME, END, TRACE_END_TIME, NB_SAMPLES, 5)); + try (Response series = xyEnpoint.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + assertEquals(DATA_PROVIDER_RESPONSE_FAILED_MSG, 200, series.getStatus()); + XyOutputResponseStub xyModelResponse = series.readEntity(XyOutputResponseStub.class); + assertNotNull(xyModelResponse); + + XyModelStub xyModel = xyModelResponse.getModel(); + Set xySeries = xyModel.getSeries(); + assertFalse(xySeries.isEmpty()); + } + } + + /** + * Ensure that the inside data is correct for call stack function density + * data provider for both tree end point and xy end point. + * + * @throws InterruptedException + * Exception thrown while waiting to execute again + */ + @Test + public void testCallStackFunctionDensityDataProvider() throws InterruptedException { + ExperimentModelStub exp = assertPostExperiment(sfContextSwitchesUstNotInitializedStub.getName(), sfContextSwitchesUstNotInitializedStub); + + WebTarget callstackTree = getGenericXYTreeEndpoint(exp.getUUID().toString(), CALL_STACK_FUNCTION_DENSITY_DATAPROVIDER_ID); + + /* + * Test the data in the tree end point with descriptors. + */ + Map parameters = new HashMap<>(); + parameters.put(REQUESTED_TIMES_KEY, List.of(0L, Long.MAX_VALUE)); + XyTreeOutputResponseStub responseModel; + try (Response tree = callstackTree.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + assertEquals(DATA_PROVIDER_RESPONSE_FAILED_MSG, 200, tree.getStatus()); + responseModel = tree.readEntity(XyTreeOutputResponseStub.class); + assertNotNull(responseModel); + } + // Make sure the analysis ran enough and we have a model + int iteration = 0; + while (responseModel.isRunning() && responseModel.getModel() == null && iteration < MAX_ITER) { + Thread.sleep(100); + try (Response treeResponse = callstackTree.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + assertEquals(DATA_PROVIDER_RESPONSE_FAILED_MSG, 200, treeResponse.getStatus()); + responseModel = treeResponse.readEntity(XyTreeOutputResponseStub.class); + assertNotNull(responseModel); + iteration++; + } + } + + // Validate model + XyEntryModelStub model = responseModel.getModel(); + assertNotNull(MODEL_NULL_MSG + responseModel, model); + + int autoExpandLevel = model.getAutoExpandLevel(); + assertEquals("Auto-expand level mismatch", -1, autoExpandLevel); + + // Entries + List actualEntries = model.getEntries(); + assertEquals("Entry count mismatch", EXPECTED_ENTRIES.size(), actualEntries.size()); + + for (int i = 0; i < EXPECTED_ENTRIES.size(); i++) { + XyEntryStub expected = EXPECTED_ENTRIES.get(i); + XyEntryStub actual = actualEntries.get(i); + assertEquals("HasRowModel mismatch at index " + i, expected.hasRowModel(), actual.hasRowModel()); + assertEquals("Labels mismatch at index " + i, expected.getLabels(), actual.getLabels()); + assertEquals("Style mismatch at index " + i, expected.getStyle(), actual.getStyle()); + } + assertEquals("Parent-child id mismatch.", actualEntries.get(0).getId(), actualEntries.get(1).getParentId()); + + /* + * Test the data in xy end point. + */ + WebTarget xyEnpoint = getGenericXYSeriesEndpoint(exp.getUUID().toString(), CALL_STACK_FUNCTION_DENSITY_DATAPROVIDER_ID); + parameters = new HashMap<>(); + List items = new ArrayList<>(); + for (EntryStub entry : actualEntries) { + items.add(entry.getId()); + } + parameters.put(REQUESTED_ITEMS_KEY, items); + parameters.put(REQUESTED_TIMERANGE_KEY, ImmutableMap.of(START, TRACE_START_TIME, END, TRACE_END_TIME, NB_SAMPLES, 5)); + XyOutputResponseStub xyModelResponse; + try (Response series = xyEnpoint.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + assertEquals(DATA_PROVIDER_RESPONSE_FAILED_MSG, 200, series.getStatus()); + xyModelResponse = series.readEntity(XyOutputResponseStub.class); + } + assertNotNull(xyModelResponse); + // Make sure the analysis ran enough and we have a fully executed model + iteration = 0; + while (xyModelResponse.isRunning() && iteration < MAX_ITER) { + Thread.sleep(100); + try (Response response = xyEnpoint.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + Activator.logWarning("current status: " + xyModelResponse.isRunning()); + assertEquals(DATA_PROVIDER_RESPONSE_FAILED_MSG, 200, response.getStatus()); + xyModelResponse = response.readEntity(XyOutputResponseStub.class); + assertNotNull(xyModelResponse); + iteration++; + } + } + XyModelStub xyModel = xyModelResponse.getModel(); + Set xySeries = xyModel.getSeries(); + assertEquals("Number of series mismatch", 1, xySeries.size()); + XySeriesStub seriesStub = xySeries.iterator().next(); + + // Validate fields + assertEquals("Name mismatch", "UNKNOWN_PID", seriesStub.getName()); + + // Validate xValues + ISamplingStub xValues = seriesStub.getXValues(); + assertTrue("xValues should be a RangesStub", xValues instanceof ISamplingStub.RangesStub); + List expectedRanges = Arrays.asList( + new ISamplingStub.RangesStub.RangeStub(0L, 1195708549L), + new ISamplingStub.RangesStub.RangeStub(1195708550L, 2391417098L), + new ISamplingStub.RangesStub.RangeStub(2391417099L, 3587125647L), + new ISamplingStub.RangesStub.RangeStub(3587125648L, 4782834196L), + new ISamplingStub.RangesStub.RangeStub(4782834197L, 5978542746L) + ); + List actualRanges = ((ISamplingStub.RangesStub) xValues).getRanges(); + assertEquals("Range size mismatch", expectedRanges.size(), actualRanges.size()); + assertEquals("Range size mismatch", expectedRanges.size(), actualRanges.size()); + for (int i = 0; i < expectedRanges.size(); i++) { + assertEquals("Range mismatch at index " + i, expectedRanges.get(i), actualRanges.get(i)); + } + + // Validate yValues + List actualYValues = seriesStub.getYValues(); + List expectedYValues = Arrays.asList(1943.0, 1.0, 2.0, 1.0, 1.0); + assertEquals("Y values size mismatch", expectedYValues.size(), actualYValues.size()); + assertEquals("Y values size mismatch", expectedYValues.size(), actualYValues.size()); + for (int i = 0; i < expectedYValues.size(); i++) { + assertEquals("Y value mismatch at index " + i, expectedYValues.get(i), actualYValues.get(i), 0.000001); + } + + // Validate axis descriptions (fully) + TmfXYAxisDescriptionStub xAxis = seriesStub.getXAxisDescription(); + TmfXYAxisDescriptionStub yAxis = seriesStub.getYAxisDescription(); + + assertNotNull("X axis description should not be null", xAxis); + assertNotNull("Y axis description should not be null", yAxis); + + // X axis + assertEquals("X axis label mismatch", "Execution Time", xAxis.getLabel()); + assertEquals("X axis unit mismatch", "ns", xAxis.getUnit()); + assertEquals("X axis data type mismatch", "DURATION", xAxis.getDataType()); + IAxisDomainStub xDomain = xAxis.getAxisDomain(); + assertNotNull("X axis domain should not be null", xDomain); + assertTrue("X axis domain should be TimeRange", xDomain instanceof IAxisDomainStub.RangeStub); + + // Y axis + assertEquals("Y axis label mismatch", "Number of Executions", yAxis.getLabel()); + assertEquals("Y axis unit mismatch", "", yAxis.getUnit()); + assertEquals("Y axis data type mismatch", "NUMBER", yAxis.getDataType()); + IAxisDomainStub yDomain = yAxis.getAxisDomain(); + assertNull("Y axis domain should be null", yDomain); + + // Validate style + assertNotNull("Style should not be null", seriesStub.getStyle()); + assertEquals("Series type should be bar", "bar", + seriesStub.getStyle().getStyleValues().get(StyleProperties.SERIES_TYPE)); + } +} diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/IAxisDomainStub.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/IAxisDomainStub.java new file mode 100644 index 000000000..6cfc37dfe --- /dev/null +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/IAxisDomainStub.java @@ -0,0 +1,148 @@ +/********************************************************************** + * 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.incubator.trace.server.jersey.rest.core.tests.stubs; + +import java.io.Serializable; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import org.eclipse.jdt.annotation.Nullable; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +/** + * A stub interface for AxisDomain which can be either Categorical or TimeRange. + * Matches the trace server protocol schema for AxisDomain. + * + * @author Siwei Zhang + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = IAxisDomainStub.CategoricalStub.class, name = "categorical"), + @JsonSubTypes.Type(value = IAxisDomainStub.RangeStub.class, name = "timeRange") +}) +@JsonIgnoreProperties(ignoreUnknown = true) +public sealed interface IAxisDomainStub extends Serializable permits + IAxisDomainStub.CategoricalStub, + IAxisDomainStub.RangeStub { + + /** + * Stub for AxisDomain.Categorical + */ + final class CategoricalStub implements IAxisDomainStub { + + private static final long serialVersionUID = 2L; + + private final List fCategories; + + /** + * Constructor + * + * @param categories + * the set of category labels for the axis domain + */ + @JsonCreator + public CategoricalStub(@JsonProperty("categories") List categories) { + fCategories = categories == null ? Collections.emptyList() : categories; + } + + /** + * Get the categories for this categorical axis domain. + * + * @return the set of category labels + */ + public List getCategories() { + return fCategories; + } + + @Override + public boolean equals(@Nullable Object obj) { + return obj instanceof CategoricalStub other && + Objects.equals(fCategories, other.fCategories); + } + + @Override + public int hashCode() { + return Objects.hash(fCategories); + } + + @Override + public String toString() { + return "CategoricalStub{" + "categories=" + fCategories + '}'; + } + } + + /** + * Stub for AxisDomain.Range + */ + final class RangeStub implements IAxisDomainStub { + + private static final long serialVersionUID = 3L; + + private final long fStart; + private final long fEnd; + + /** + * Constructor + * + * @param start + * start of the time range + * @param end + * end of the time range + */ + @JsonCreator + public RangeStub(@JsonProperty("start") long start, + @JsonProperty("end") long end) { + fStart = start; + fEnd = end; + } + + /** + * Get the start of range. + * + * @return the start + */ + public long getStart() { + return fStart; + } + + /** + * Get the end of range. + * + * @return the end + */ + public long getEnd() { + return fEnd; + } + + @Override + public boolean equals(@Nullable Object obj) { + return obj instanceof RangeStub other && + fStart == other.fStart && + fEnd == other.fEnd; + } + + @Override + public int hashCode() { + return Objects.hash(fStart, fEnd); + } + + @Override + public String toString() { + return "RangeStub{" + "start=" + fStart + ", end=" + fEnd + '}'; + } + } +} diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/ISamplingStub.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/ISamplingStub.java new file mode 100644 index 000000000..feb4f8b75 --- /dev/null +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/ISamplingStub.java @@ -0,0 +1,195 @@ +/********************************************************************** + * 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.incubator.trace.server.jersey.rest.core.tests.stubs; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.tracecompass.tmf.core.model.ISampling; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +/** + * Stub version of {@link ISampling}. + * + * @author Siwei Zhang + */ +@JsonSerialize(using = SamplingStubSerializer.class) +@JsonDeserialize(using = SamplingStubDeserializer.class) +public sealed interface ISamplingStub extends Serializable permits + ISamplingStub.TimestampsStub, + ISamplingStub.CategoriesStub, + ISamplingStub.RangesStub { + + /** + * Get the number of sampling points + * + * @return number of points + */ + int size(); + + /** + * Timestamp-based sampling. + */ + final class TimestampsStub implements ISamplingStub { + private static final long serialVersionUID = -8242136490356720296L; + + private final long[] fTimestamps; + + public TimestampsStub(long[] timestamps) { + this.fTimestamps = Objects.requireNonNull(timestamps); + } + + public long[] getTimestamps() { + return fTimestamps; + } + + @Override + public int size() { + return fTimestamps.length; + } + + @Override + public boolean equals(@Nullable Object obj) { + return (this == obj) || (obj instanceof TimestampsStub other && + Arrays.equals(this.fTimestamps, other.fTimestamps)); + } + + @Override + public int hashCode() { + return Arrays.hashCode(fTimestamps); + } + + @Override + public String toString() { + return "Timestamps" + Arrays.toString(fTimestamps); //$NON-NLS-1$ + } + } + + /** + * Categorical sampling (e.g., names). + */ + final class CategoriesStub implements ISamplingStub { + private static final long serialVersionUID = 3751152643508688051L; + + private final List fCategories; + + public CategoriesStub(List categories) { + this.fCategories = Objects.requireNonNull(categories); + } + + public List getCategories() { + return fCategories; + } + + @Override + public int size() { + return fCategories.size(); + } + + @Override + public boolean equals(@Nullable Object obj) { + return (this == obj) || (obj instanceof CategoriesStub other && + Objects.equals(this.fCategories, other.fCategories)); + } + + @Override + public int hashCode() { + return Objects.hash(fCategories); + } + + @Override + public String toString() { + return "Categories" + fCategories.toString(); //$NON-NLS-1$ + } + } + + /** + * Range sampling, representing start-end pairs. + */ + final class RangesStub implements ISamplingStub { + private static final long serialVersionUID = 3434126540189939098L; + + private final List fRanges; + + public RangesStub(List ranges) { + this.fRanges = Objects.requireNonNull(ranges); + } + + public List getRanges() { + return fRanges; + } + + @Override + public int size() { + return fRanges.size(); + } + + @Override + public boolean equals(@Nullable Object obj) { + return (this == obj) || (obj instanceof RangesStub other && + Objects.equals(this.fRanges, other.fRanges)); + } + + @Override + public int hashCode() { + return Objects.hash(fRanges); + } + + @Override + public String toString() { + return "Ranges" + fRanges.toString(); //$NON-NLS-1$ + } + + /** + * Stub representing a range. + */ + public static final class RangeStub implements Serializable { + private static final long serialVersionUID = 1L; + + private final long fStart; + private final long fEnd; + + public RangeStub(long start, long end) { + this.fStart = start; + this.fEnd = end; + } + + public long getStart() { + return fStart; + } + + public long getEnd() { + return fEnd; + } + + @Override + public boolean equals(@Nullable Object obj) { + return (this == obj) || (obj instanceof RangeStub other && + fStart == other.fStart && fEnd == other.fEnd); + } + + @Override + public int hashCode() { + return Objects.hash(fStart, fEnd); + } + + @Override + public String toString() { + return "[" + fStart + ", " + fEnd + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + } + } +} diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/SamplingStubDeserializer.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/SamplingStubDeserializer.java new file mode 100644 index 000000000..831a7a4da --- /dev/null +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/SamplingStubDeserializer.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (c) 2025 Ericsson and others + * + * 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.incubator.trace.server.jersey.rest.core.tests.stubs; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +/** + * Deserializer for SamplingStub. + * + * @author Siwei Zhang + */ +public class SamplingStubDeserializer extends JsonDeserializer { + + @Override + public ISamplingStub deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonToken token = p.getCurrentToken(); + if (token != JsonToken.START_ARRAY) { + ctxt.reportInputMismatch(ISamplingStub.class, "Expected array for SamplingStub"); + } + + token = p.nextToken(); + if (token == JsonToken.VALUE_NUMBER_INT) { + List timestamps = new ArrayList<>(); + do { + timestamps.add(p.getLongValue()); + } while (p.nextToken() != JsonToken.END_ARRAY); + long[] ts = timestamps.stream().mapToLong(Long::longValue).toArray(); + return new ISamplingStub.TimestampsStub(ts); + + } else if (token == JsonToken.VALUE_STRING) { + List categories = new ArrayList<>(); + do { + categories.add(p.getText()); + } while (p.nextToken() != JsonToken.END_ARRAY); + return new ISamplingStub.CategoriesStub(categories); + + } else if (token == JsonToken.START_ARRAY) { + List ranges = new ArrayList<>(); + while (token != JsonToken.END_ARRAY) { + p.nextToken(); // start + long start = p.getLongValue(); + p.nextToken(); // end + long end = p.getLongValue(); + p.nextToken(); // end of inner array + ranges.add(new ISamplingStub.RangesStub.RangeStub(start, end)); + token = p.nextToken(); // next outer token + } + return new ISamplingStub.RangesStub(ranges); + } + + ctxt.reportInputMismatch(ISamplingStub.class, "Unrecognized structure for SamplingStub"); + return null; + } +} diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/SamplingStubSerializer.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/SamplingStubSerializer.java new file mode 100644 index 000000000..34f78d0af --- /dev/null +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/SamplingStubSerializer.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2025 Ericsson and others + * + * 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.incubator.trace.server.jersey.rest.core.tests.stubs; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +/** + * Serializer for SamplingStub. Matches the protocol format used in real + * {@link SamplingStubSerializer} + * + * @author Siwei Zhang + */ +public class SamplingStubSerializer extends StdSerializer { + + private static final long serialVersionUID = 1L; + + public SamplingStubSerializer() { + super(ISamplingStub.class); + } + + @Override + public void serialize(ISamplingStub value, JsonGenerator gen, SerializerProvider provider) throws IOException { + if (value instanceof ISamplingStub.TimestampsStub timestamps) { + gen.writeArray(timestamps.getTimestamps(), 0, timestamps.getTimestamps().length); + + } else if (value instanceof ISamplingStub.CategoriesStub categories) { + gen.writeStartArray(); + for (String category : categories.getCategories()) { + gen.writeString(category); + } + gen.writeEndArray(); + + } else if (value instanceof ISamplingStub.RangesStub ranges) { + gen.writeStartArray(); + for (ISamplingStub.RangesStub.RangeStub range : ranges.getRanges()) { + gen.writeStartArray(); + gen.writeNumber(range.getStart()); + gen.writeNumber(range.getEnd()); + gen.writeEndArray(); + } + gen.writeEndArray(); + + } else { + throw new IllegalArgumentException("Unknown SamplingStub type: " + value.getClass().getName()); + } + } +} diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/TmfXYAxisDescriptionStub.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/TmfXYAxisDescriptionStub.java new file mode 100644 index 000000000..1f86b3456 --- /dev/null +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/TmfXYAxisDescriptionStub.java @@ -0,0 +1,97 @@ +/********************************************************************** + * 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.incubator.trace.server.jersey.rest.core.tests.stubs; + +import java.io.Serializable; +import java.util.Objects; + +import org.eclipse.jdt.annotation.Nullable; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * A stub class for the XY axis description model. It matches the trace server + * protocol's TmfXYAxisDescription schema. + * + * @author Siwei Zhang + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class TmfXYAxisDescriptionStub implements Serializable { + + private static final long serialVersionUID = 7302486196351034579L; + + private final String fLabel; + private final String fUnit; + private final String fDataType; + private final @Nullable IAxisDomainStub fAxisDomain; + + /** + * {@link JsonCreator} Constructor for final fields + * + * @param label + * Label for the axis + * @param unit + * Unit of the axis + * @param dataType + * Type of the data (as a string) + * @param axisDomain + * Optional domain for the axis + */ + @JsonCreator + public TmfXYAxisDescriptionStub( + @JsonProperty("label") String label, + @JsonProperty("unit") String unit, + @JsonProperty("dataType") String dataType, + @JsonProperty("axisDomain") @Nullable IAxisDomainStub axisDomain) { + fLabel = Objects.requireNonNull(label, "The 'label' json field was not set"); + fUnit = Objects.requireNonNull(unit, "The 'unit' json field was not set"); + fDataType = Objects.requireNonNull(dataType, "The 'dataType' json field was not set"); + fAxisDomain = axisDomain; + } + + /** + * Get the axis label + * + * @return the label + */ + public String getLabel() { + return fLabel; + } + + /** + * Get the unit of the axis + * + * @return the unit + */ + public String getUnit() { + return fUnit; + } + + /** + * Get the data type of the axis + * + * @return the data type + */ + public String getDataType() { + return fDataType; + } + + /** + * Get the axis domain + * + * @return the axis domain, if any + */ + public @Nullable IAxisDomainStub getAxisDomain() { + return fAxisDomain; + } +} \ No newline at end of file diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/XySeriesStub.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/XySeriesStub.java index b38979c7e..7882362ef 100644 --- a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/XySeriesStub.java +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/XySeriesStub.java @@ -1,5 +1,5 @@ /********************************************************************** - * Copyright (c) 2020 École Polytechnique de Montréal + * Copyright (c) 2020, 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 @@ -29,9 +29,11 @@ public class XySeriesStub implements Serializable { private final String fName; private final int fId; - private final List fXValues; + private final ISamplingStub fSampling; private final List fYValues; private final OutputElementStyleStub fStyle; + private final TmfXYAxisDescriptionStub fXAxisDescription; + private final TmfXYAxisDescriptionStub fYAxisDescription; /** * {@link JsonCreator} Constructor for final fields @@ -46,18 +48,26 @@ public class XySeriesStub implements Serializable { * The values for the y axis of this series * @param style * The style for this series + * @param xAxisDescription + * The description for x axis + * @param yAxisDescription + * The description for y axis */ @JsonCreator public XySeriesStub(@JsonProperty("seriesName") String name, @JsonProperty("seriesId") Integer id, - @JsonProperty("xValues") List xValues, + @JsonProperty("xValues") ISamplingStub xValues, @JsonProperty("yValues") List yValues, - @JsonProperty("style") OutputElementStyleStub style) { + @JsonProperty("style") OutputElementStyleStub style, + @JsonProperty("xValuesDescription") TmfXYAxisDescriptionStub xAxisDescription, + @JsonProperty("yValuesDescription") TmfXYAxisDescriptionStub yAxisDescription) { fName = Objects.requireNonNull(name, "The 'seriesName' json field was not set"); fId = Objects.requireNonNull(id, "The 'seriesId' json field was not set"); - fXValues = Objects.requireNonNull(xValues, "The 'xValues' json field was not set"); + fSampling = Objects.requireNonNull(xValues, "The 'xValues' json field was not set"); fYValues = Objects.requireNonNull(yValues, "The 'yValues' json field was not set"); fStyle = Objects.requireNonNull(style, "The 'style' json field was not set"); + fXAxisDescription = xAxisDescription; + fYAxisDescription = yAxisDescription; } /** @@ -83,8 +93,8 @@ public int getId() { * * @return The values on the x axis */ - public List getXValues() { - return fXValues; + public ISamplingStub getXValues() { + return fSampling; } /** @@ -104,4 +114,22 @@ public List getYValues() { public OutputElementStyleStub getStyle() { return fStyle; } + + /** + * Get the description for x axis + * + * @return The description for x axis + */ + public TmfXYAxisDescriptionStub getXAxisDescription() { + return fXAxisDescription; + } + + /** + * Get the description for y axis + * + * @return The description for y axis + */ + public TmfXYAxisDescriptionStub getYAxisDescription() { + return fYAxisDescription; + } } diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/utils/RestServerTest.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/utils/RestServerTest.java index 37e4e3b4c..233ea8816 100644 --- a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/utils/RestServerTest.java +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/utils/RestServerTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018, 2024 Ericsson + * Copyright (c) 2018, 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 @@ -118,6 +118,11 @@ public abstract class RestServerTest { */ protected static final String CALL_STACK_DATAPROVIDER_ID = "org.eclipse.tracecompass.internal.analysis.profiling.callstack.provider.CallStackDataProvider"; + /** + * Callstack function density data provider ID + */ + protected static final String CALL_STACK_FUNCTION_DENSITY_DATAPROVIDER_ID = "org.eclipse.tracecompass.analysis.profiling.core.callstack.functiondensity.provider"; + /** * Requested times key */ @@ -171,6 +176,11 @@ public abstract class RestServerTest { */ public static final String XY_SERIES_PATH = "xy"; + /** + * Generic XY path segment + */ + public static final String GENERIC_XY_PATH = "genericXY"; + /** * States path segment */ @@ -598,6 +608,24 @@ public static WebTarget getXYTreeEndpoint(String expUUID, String dataProviderId) .path(TREE_PATH); } + /** + * Get the {@link WebTarget} for the generic xy tree endpoint. + * + * @param expUUID + * Experiment UUID + * @param dataProviderId + * Data provider ID + * @return The generic xy tree endpoint + */ + public static WebTarget getGenericXYTreeEndpoint(String expUUID, String dataProviderId) { + return getApplicationEndpoint().path(EXPERIMENTS) + .path(expUUID) + .path(OUTPUTS_PATH) + .path(GENERIC_XY_PATH) + .path(dataProviderId) + .path(TREE_PATH); + } + /** * Get the {@link WebTarget} for the XY series endpoint. * @@ -615,6 +643,25 @@ public static WebTarget getXYSeriesEndpoint(String expUUID, String dataProviderI .path(XY_SERIES_PATH); } + /** + * Get the {@link WebTarget} for the generic XY series end point with + * non-time x-axis. + * + * @param expUUID + * Experiment UUID + * @param dataProviderId + * Data provider ID + * @return The generic XY series endpoint + */ + public static WebTarget getGenericXYSeriesEndpoint(String expUUID, String dataProviderId) { + return getApplicationEndpoint().path(EXPERIMENTS) + .path(expUUID) + .path(OUTPUTS_PATH) + .path(GENERIC_XY_PATH) + .path(dataProviderId) + .path(XY_SERIES_PATH); + } + /** * Get the {@link WebTarget} for the configTypes endpoint of a given data provider. * diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/webapp/SamplingSerializerTest.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/webapp/SamplingSerializerTest.java new file mode 100644 index 000000000..2a6336554 --- /dev/null +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/webapp/SamplingSerializerTest.java @@ -0,0 +1,101 @@ +/******************************************************************************* + * 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.incubator.trace.server.jersey.rest.core.tests.webapp; + +import static org.junit.Assert.*; + +import java.util.List; + +import org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.webapp.SamplingSerializer; +import org.eclipse.tracecompass.tmf.core.model.ISampling; +import org.eclipse.tracecompass.tmf.core.model.ISampling.Categories; +import org.eclipse.tracecompass.tmf.core.model.ISampling.Range; +import org.eclipse.tracecompass.tmf.core.model.ISampling.Ranges; +import org.eclipse.tracecompass.tmf.core.model.ISampling.Timestamps; +import org.junit.Before; +import org.junit.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; + +/** + * Test suite for {@link SamplingSerializer} and {@link SamplingDeserializer}. + *

+ * Validates JSON round-trip behavior for different {@link ISampling} + * subtypes: {@link Timestamps}, {@link Categories}, and {@link Ranges}. + * + * @author Siwei Zhang + */ +public class SamplingSerializerTest { + + private ObjectMapper fMapper; + + /** + * Set up the {@link ObjectMapper} and register the custom serializer and + * deserializer for {@link ISampling}. + */ + @Before + public void setup() { + fMapper = new ObjectMapper(); + SimpleModule module = new SimpleModule(); + module.addSerializer(ISampling.class, new SamplingSerializer()); + fMapper.registerModule(module); + } + + /** + * Test round-trip serialization and deserialization for + * {@link ISampling.Timestamps}. The format is a flat array of @NonNull Longs. + * + * @throws JsonProcessingException + * if JSON processing fails + */ + @Test + public void testTimestampsRoundTrip() throws JsonProcessingException { + ISampling original = new Timestamps(new long[] { 1, 2, 3 }); + String json = fMapper.writeValueAsString(original); + assertEquals("[1,2,3]", json); + } + + /** + * Test round-trip serialization and deserialization for + * {@link ISampling.Categories}. The format is an array of strings. + * + * @throws JsonProcessingException + * if JSON processing fails + */ + @Test + public void testCategoriesRoundTrip() throws JsonProcessingException { + ISampling original = new Categories(List.of("Read", "Write", "Idle")); + String json = fMapper.writeValueAsString(original); + assertEquals("[\"Read\",\"Write\",\"Idle\"]", json); + } + + /** + * Test round-trip serialization and deserialization for + * {@link ISampling.Ranges}. The format is a 2D array of timestamp + * ranges, i.e., {@code [[start, end], ...]}. + * + * @throws JsonProcessingException + * if JSON processing fails + */ + @Test + public void testTimeRangesRoundTrip() throws JsonProcessingException { + ISampling original = new Ranges(List.of( + new Range<>(1L, 2L), + new Range<>(2L, 3L), + new Range<>(3L, 4L) + )); + String json = fMapper.writeValueAsString(original); + assertEquals("[[1,2],[2,3],[3,4]]", json); + } +} diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/webapp/SeriesModelSerializerTest.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/webapp/SeriesModelSerializerTest.java index aed3be7a9..b8685eaf8 100644 --- a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/webapp/SeriesModelSerializerTest.java +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/webapp/SeriesModelSerializerTest.java @@ -11,6 +11,7 @@ package org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.webapp; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -19,10 +20,13 @@ import java.util.List; import org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.webapp.OutputElementStyleSerializer; +import org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.webapp.SamplingSerializer; import org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.webapp.SeriesModelSerializer; import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.OutputElementStyleStub; +import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.ISamplingStub; import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.XySeriesStub; import org.eclipse.tracecompass.tmf.core.model.OutputElementStyle; +import org.eclipse.tracecompass.tmf.core.model.ISampling; import org.eclipse.tracecompass.tmf.core.model.SeriesModel.SeriesModelBuilder; import org.eclipse.tracecompass.tmf.core.model.StyleProperties; import org.eclipse.tracecompass.tmf.core.model.xy.ISeriesModel; @@ -41,7 +45,7 @@ public class SeriesModelSerializerTest extends AbstractSerializerTest { private static final long ID = 0; private static final String TITLE = "valid-styles"; - private static final long[] times = { 0, 1, 2, 3 }; + private static final ISampling times = new ISampling.Timestamps(new long[] { 0, 1, 2, 3 }); private static final double[] fValues = { 0.1, 0.2, 0.3, 0.4 }; /** @@ -62,6 +66,7 @@ public void testValidStyles() throws JsonProcessingException { SimpleModule module = new SimpleModule(); module.addSerializer(ISeriesModel.class, new SeriesModelSerializer()); module.addSerializer(OutputElementStyle.class, new OutputElementStyleSerializer()); + module.addSerializer(ISampling.class, new SamplingSerializer()); fMapper.registerModule(module); String json = fMapper.writeValueAsString(lineModel); @@ -69,11 +74,10 @@ public void testValidStyles() throws JsonProcessingException { assertNotNull(deserialized); assertEquals(TITLE, deserialized.getName()); assertEquals(ID, deserialized.getId()); - List xValues = deserialized.getXValues(); - assertTrue(times.length == xValues.size()); - for (int i = 0; i < times.length; i++) { - assertEquals(i, times[i], xValues.get(i)); - } + // the sampling is de-serialized into sampling-stub + assertTrue(deserialized.getXValues() instanceof ISamplingStub.TimestampsStub); + long[] actual = ((ISamplingStub.TimestampsStub) deserialized.getXValues()).getTimestamps(); + assertArrayEquals(new long[] {0, 1, 2, 3}, actual); List yValues = deserialized.getYValues(); assertTrue(fValues.length == yValues.size()); for (int i = 0; i < fValues.length; i++) { diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/AxisDomain.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/AxisDomain.java new file mode 100644 index 000000000..d46045597 --- /dev/null +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/AxisDomain.java @@ -0,0 +1,49 @@ +/********************************************************************** + * 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.incubator.internal.trace.server.jersey.rest.core.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; + +/** + * Represents the domain of values for a chart axis. + *

+ * The domain can either be categorical (e.g., discrete string labels) or a + * numeric range (e.g., time or duration values). + *

+ * This interface is used for OpenAPI schema generation and supports polymorphic + * serialization via {@code type} discriminator. + */ +@Schema(description = "Domain of values supported on a chart axis. Can be either categorical or numeric range.") +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type", visible = true) +@JsonSubTypes({ + @JsonSubTypes.Type(value = AxisDomainCategorical.class, name = "categorical"), + @JsonSubTypes.Type(value = AxisDomainTimeRange.class, name = "timeRange") +}) +public interface AxisDomain { + + /** + * Returns the type of axis domain. + *

+ * This is used as a discriminator to identify the specific subtype + * implementation (e.g., "categorical", "timeRange"). + * + * @return A string identifying the domain type + */ + @Schema(description = "Type of axis domain (e.g., 'categorical' or 'timeRange')", requiredMode = RequiredMode.REQUIRED) + @JsonProperty("type") + String getType(); +} diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/AxisDomainCategorical.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/AxisDomainCategorical.java new file mode 100644 index 000000000..9fde6778c --- /dev/null +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/AxisDomainCategorical.java @@ -0,0 +1,61 @@ +/********************************************************************** + * 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.incubator.internal.trace.server.jersey.rest.core.model; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNull; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; + +/** + * Represents a categorical domain of values for an axis, where values are + * predefined strings such as labels or states (e.g., "read", "write", "idle"). + *

+ * Used in Swagger schema generation for chart axis descriptions. + */ +public class AxisDomainCategorical implements AxisDomain { + + private final @NonNull Set categories; + + /** + * Constructor + * + * @param categories + * The set of category labels + */ + @JsonCreator + public AxisDomainCategorical( + @JsonProperty("categories") + @NonNull Set categories) { + this.categories = categories; + } + + @Override + @Schema(description = "Type of axis domain", requiredMode = RequiredMode.REQUIRED) + public String getType() { + return "categorical"; + } + + /** + * @return The set of category names + */ + @ArraySchema(arraySchema = @Schema(description = "List of category labels on the axis"), schema = @Schema(requiredMode = RequiredMode.REQUIRED)) + public @NonNull Set getCategories() { + return categories; + } +} diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/AxisDomainTimeRange.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/AxisDomainTimeRange.java new file mode 100644 index 000000000..aedeb4f9f --- /dev/null +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/AxisDomainTimeRange.java @@ -0,0 +1,68 @@ +/********************************************************************** + * 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.incubator.internal.trace.server.jersey.rest.core.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; + +/** + * Represents a range-based domain of values for an axis, typically numeric + * (e.g., execution durations, time intervals). + *

+ * Used in Swagger schema generation for chart axis descriptions. + */ +public class AxisDomainTimeRange implements AxisDomain { + + private final long start; + private final long end; + + /** + * Constructor + * + * @param start + * The minimum value of the axis domain + * @param end + * The maximum value of the axis domain + */ + @JsonCreator + public AxisDomainTimeRange( + @JsonProperty("start") long start, + @JsonProperty("end") long end) { + this.start = start; + this.end = end; + } + + @Override + @Schema(description = "Type of axis domain", requiredMode = RequiredMode.REQUIRED) + public String getType() { + return "timeRange"; + } + + /** + * @return The start of the domain range + */ + @Schema(description = "Start of the axis range", requiredMode = RequiredMode.REQUIRED) + public long getStart() { + return start; + } + + /** + * @return The end of the domain range + */ + @Schema(description = "End of the axis range", requiredMode = RequiredMode.REQUIRED) + public long getEnd() { + return end; + } +} diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/GenericTimeRange.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/GenericTimeRange.java new file mode 100644 index 000000000..dd4029251 --- /dev/null +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/GenericTimeRange.java @@ -0,0 +1,44 @@ +/********************************************************************** + * 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.incubator.internal.trace.server.jersey.rest.core.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; + +/** + * Contributes to the model used for TSP swagger-core annotations. + * + * This time range can optionally include a number of sampling points. The + * sampling values are not restricted to timestamps—they can represent other + * types of samples. + */ +@Schema(description = "A generic time range with optional sampling count. Sampling points may represent values other than timestamps.") +public interface GenericTimeRange { + + /** + * @return The inclusive start of the range. + */ + @Schema(description = "Start of the range", requiredMode = RequiredMode.REQUIRED) + long getStart(); + + /** + * @return The inclusive end of the range. + */ + @Schema(description = "End of the range", requiredMode = RequiredMode.REQUIRED) + long getEnd(); + + /** + * @return The number of samples to compute within the range. + */ + @Schema(description = "Optional number of samples (1–65536) to generate within the range", requiredMode = RequiredMode.NOT_REQUIRED) + int getNbSamples(); +} diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/GenericXYQueryParameters.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/GenericXYQueryParameters.java new file mode 100644 index 000000000..18ae414c4 --- /dev/null +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/GenericXYQueryParameters.java @@ -0,0 +1,53 @@ +/********************************************************************** + * 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.incubator.internal.trace.server.jersey.rest.core.model; + +import org.eclipse.jdt.annotation.NonNull; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; + +/** + * Contributes to the model used for TSP swagger-core annotations. + * + * Uses generic time range for requested time range, which allows sampling + * values are not restricted to timestamps. + */ +public interface GenericXYQueryParameters { + + /** + * @return The parameters. + */ + @NonNull + @Schema(requiredMode = RequiredMode.REQUIRED) + GenericXYRequestedParameters getParameters(); + + /** + * Property names below use underscores as per trace-server protocol. + */ + interface GenericXYRequestedParameters { + + @JsonProperty("requested_timerange") + @Schema(requiredMode = RequiredMode.REQUIRED) + GenericTimeRange getRequestedTimeRange(); + + @JsonProperty("requested_items") + @Schema(requiredMode = RequiredMode.REQUIRED) + int[] getRequestedItems(); + + @JsonProperty("filter_query_parameters") + @Schema(requiredMode = RequiredMode.NOT_REQUIRED) + RequestedFilterQueryParameters getFilterQueryParameters(); + } +} diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/Sampling.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/Sampling.java new file mode 100644 index 000000000..7978bd6f2 --- /dev/null +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/Sampling.java @@ -0,0 +1,88 @@ +/********************************************************************** + * 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.incubator.internal.trace.server.jersey.rest.core.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; + +import java.util.List; + +/** + * Describes alternative representations of sampling options. + * Used for OpenAPI generation. + */ +@Schema(oneOf = { + Sampling.TimestampSampling.class, + Sampling.CategorySampling.class, + Sampling.RangeSampling.class +}) +public interface Sampling { + + /** + * Sampling as list of timestamps. + */ + public static class TimestampSampling { + /** + * The sampling points as timestamp values. + */ + @Schema( + description = "Sampling as list of timestamps", + requiredMode = RequiredMode.REQUIRED + ) + public long[] sampling; + } + + /** + * Sampling as list of categories. + */ + public static class CategorySampling { + /** + * The sampling points as category names or labels. + */ + @Schema( + description = "Sampling as list of categories", + requiredMode = RequiredMode.REQUIRED + ) + public String[] sampling; + } + + /** + * Sampling as a list of [start, end] ranges. + */ + public static class RangeSampling { + /** + * The list of sampling ranges, each with a start and end value. + */ + @Schema( + description = "Sampling as list of [start, end] timestamp ranges", + requiredMode = RequiredMode.REQUIRED + ) + public List sampling; + } + + /** + * Represents a closed interval [start, end] for a sampling range. + */ + public static class StartEndRange { + /** + * Start timestamp of the range (inclusive). + */ + @Schema(description = "Start timestamp of the range", requiredMode = RequiredMode.REQUIRED) + public long start; + + /** + * End timestamp of the range (inclusive). + */ + @Schema(description = "End timestamp of the range", requiredMode = RequiredMode.REQUIRED) + public long end; + } +} diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/SeriesModel.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/SeriesModel.java index a1c565489..550831cf2 100644 --- a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/SeriesModel.java +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/SeriesModel.java @@ -42,8 +42,12 @@ public interface SeriesModel { * @return The X values. */ @JsonProperty("xValues") - @ArraySchema(arraySchema = @Schema(description = "Series' X values"), schema = @Schema(requiredMode = RequiredMode.REQUIRED)) - long[] getXValues(); + @Schema(description = "Sampling values", requiredMode = RequiredMode.REQUIRED, oneOf = { + Sampling.TimestampSampling.class, + Sampling.CategorySampling.class, + Sampling.RangeSampling.class + }) + Sampling getXValues(); /** * @return The Y values. @@ -52,6 +56,20 @@ public interface SeriesModel { @ArraySchema(arraySchema = @Schema(description = "Series' Y values"), schema = @Schema(requiredMode = RequiredMode.REQUIRED)) double[] getYValues(); + /** + * @return The X values' description. + */ + @JsonProperty("xValuesDescription") + @ArraySchema(arraySchema = @Schema(description = "Series' X axis description"), schema = @Schema(requiredMode = RequiredMode.REQUIRED)) + XYAxisDescription getXAxisDescription(); + + /** + * @return The Y values' description. + */ + @JsonProperty("yValuesDescription") + @ArraySchema(arraySchema = @Schema(description = "Series' Y axis description"), schema = @Schema(requiredMode = RequiredMode.REQUIRED)) + XYAxisDescription getYAxisDescription(); + /** * @return The series style. */ diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/XYAxisDescription.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/XYAxisDescription.java new file mode 100644 index 000000000..16fea0cd0 --- /dev/null +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/XYAxisDescription.java @@ -0,0 +1,56 @@ +/********************************************************************** + * 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.incubator.internal.trace.server.jersey.rest.core.model; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; + +/** + * Contributes to the model used for TSP swagger-core annotations. + * Represents an axis description for XY charts. + */ +@Schema(description = "Describes a single axis in an XY chart, including label, unit, data type, and optional domain.") +public interface XYAxisDescription { + + /** + * @return Axis label, e.g., "Time", "Duration", etc. + */ + @NonNull + @Schema(description = "Label for the axis", requiredMode = RequiredMode.REQUIRED) + String getLabel(); + + /** + * @return Unit string, such as "ns", "ms", or "" (empty if not applicable). + */ + @NonNull + @Schema(description = "Unit associated with this axis (e.g., ns, ms)", requiredMode = RequiredMode.REQUIRED) + String getUnit(); + + /** + * @return The data type of the axis values. + */ + @NonNull + @Schema(description = "The type of data this axis represents", requiredMode = RequiredMode.REQUIRED) + DataType getDataType(); + + /** + * @return Optional domain of values that this axis can take. + */ + @JsonProperty("axisDomain") + @Schema(description = "Optional domain of values that this axis supports", requiredMode = RequiredMode.NOT_REQUIRED) + @Nullable AxisDomain getAxisDomain(); +} diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/services/DataProviderService.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/services/DataProviderService.java index 8520cb3b0..a313d0442 100644 --- a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/services/DataProviderService.java +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/services/DataProviderService.java @@ -66,6 +66,7 @@ import static org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.services.EndpointConstants.TGR; import static org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.services.EndpointConstants.TIMERANGE; import static org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.services.EndpointConstants.TIMERANGE_EX; +import static org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.services.EndpointConstants.TIMERANGE_SAMPLING_EX; import static org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.services.EndpointConstants.TIMERANGE_EX_TREE; import static org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.services.EndpointConstants.TIMERANGE_TREE; import static org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.services.EndpointConstants.TIMES_EX_TT; @@ -73,6 +74,7 @@ import static org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.services.EndpointConstants.TREE_ENTRIES; import static org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.services.EndpointConstants.VTB; import static org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.services.EndpointConstants.X_Y; +import static org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.services.EndpointConstants.GXY; import java.util.ArrayList; import java.util.Collection; @@ -110,6 +112,7 @@ import org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.model.ArrowsQueryParameters; import org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.model.DataProvider; import org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.model.ErrorResponse; +import org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.model.GenericXYQueryParameters; import org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.model.LinesQueryParameters; import org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.model.MarkerSetsResponse; import org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.model.OptionalQueryParameters; @@ -416,6 +419,100 @@ public Response getXY( } } + /** + * Query the generic xy chart data provider for its tree structure. + * + * @param expUUID + * desired experiment UUID + * @param outputId + * Output ID for the data provider to query + * @param queryParameters + * Parameters used to request the tree and axis descriptions, as + * defined in {@link QueryParameters} + * @return an {@link GenericView} with the results + */ + @POST + @Path("/genericXY/{outputId}/tree") + @Tag(name = GXY) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Operation(summary = "API to get the tree for generic xy chart", description = TREE_ENTRIES, responses = { + @ApiResponse(responseCode = "200", description = "Returns a list of generic xy chart entries. " + + CONSISTENT_PARENT, content = @Content(schema = @Schema(implementation = XYTreeResponse.class))), + @ApiResponse(responseCode = "400", description = INVALID_PARAMETERS, content = @Content(schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse(responseCode = "404", description = PROVIDER_NOT_FOUND, content = @Content(schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse(responseCode = "405", description = NO_PROVIDER, content = @Content(schema = @Schema(implementation = ErrorResponse.class))) + }) + public Response getGenericXYChartTree( + @Parameter(description = EXP_UUID) @PathParam("expUUID") UUID expUUID, + @Parameter(description = OUTPUT_ID) @PathParam("outputId") String outputId, + @RequestBody(description = "Query parameters to fetch the generic XY tree. " + TIMERANGE_TREE, content = { + @Content(examples = @ExampleObject("{\"parameters\":{" + TIMERANGE_EX_TREE + + "}}"), schema = @Schema(implementation = TreeQueryParameters.class)) + }, required = true) QueryParameters queryParameters) { + return getTree(expUUID, outputId, queryParameters); + } + + /** + * Query the provider for the generic xy chart. + * + * @param expUUID + * {@link UUID} of the experiment to query + * @param outputId + * Output ID for the data provider to query + * @param queryParameters + * Parameters to fetch xy as described by {@link QueryParameters} + * @return an {@link GenericView} with the results + */ + @POST + @Path("/genericXY/{outputId}/xy") + @Tag(name = GXY) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Operation(summary = "API to get the xy model", description = "Unique endpoint for all xy models, " + + "ensures that the same template is followed for all endpoints.", responses = { + @ApiResponse(responseCode = "200", description = "Return the queried xy response", content = @Content(schema = @Schema(implementation = XYResponse.class))), + @ApiResponse(responseCode = "400", description = MISSING_PARAMETERS, content = @Content(schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse(responseCode = "404", description = PROVIDER_NOT_FOUND, content = @Content(schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse(responseCode = "405", description = NO_PROVIDER, content = @Content(schema = @Schema(implementation = ErrorResponse.class))) + }) + public Response getGenericXY( + @Parameter(description = EXP_UUID) @PathParam("expUUID") UUID expUUID, + @Parameter(description = OUTPUT_ID) @PathParam("outputId") String outputId, + @RequestBody(description = "Query parameters to fetch the xy model. " + TIMERANGE + " " + ITEMS_XY, content = { + @Content(examples = @ExampleObject("{\"parameters\":{" + TIMERANGE_SAMPLING_EX + "," + ITEMS_EX + + "}}"), schema = @Schema(implementation = GenericXYQueryParameters.class)) + }, required = true) QueryParameters queryParameters) { + + Response errorResponse = validateParameters(outputId, queryParameters); + if (errorResponse != null) { + return errorResponse; + } + try (FlowScopeLog scope = new FlowScopeLogBuilder(LOGGER, Level.FINE, "DataProviderService#fetchXY") //$NON-NLS-1$ + .setCategory(outputId).build()) { + TmfExperiment experiment = ExperimentManagerService.getExperimentByUUID(expUUID); + if (experiment == null) { + return ErrorResponseUtil.newErrorResponse(Status.NOT_FOUND, NO_SUCH_TRACE); + } + + ITmfTreeXYDataProvider<@NonNull ITmfTreeDataModel> provider = manager.getOrCreateDataProvider(experiment, + outputId, ITmfTreeXYDataProvider.class); + + if (provider == null) { + // The analysis cannot be run on this trace + return ErrorResponseUtil.newErrorResponse(Status.METHOD_NOT_ALLOWED, NO_PROVIDER); + } + + Map params = queryParameters.getParameters(); + String errorMessage = QueryParametersUtil.validateGenericXYQueryParameters(params); + if (errorMessage != null) { + return ErrorResponseUtil.newErrorResponse(Status.BAD_REQUEST, errorMessage); } + + TmfModelResponse<@NonNull ITmfXyModel> response = provider.fetchXY(params, null); + return Response.ok(response).build(); + } + } + /** * Query the provider for XY tooltip, currently not implemented * diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/services/EndpointConstants.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/services/EndpointConstants.java index 90b0f4f29..67053f25d 100644 --- a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/services/EndpointConstants.java +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/services/EndpointConstants.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2021, 2022 Ericsson + * Copyright (c) 2021, 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 @@ -97,6 +97,7 @@ public final class EndpointConstants { static final String DIA = "Diagnostic"; //$NON-NLS-1$ static final String DT = "Data Tree"; //$NON-NLS-1$ static final String EXP = "Experiments"; //$NON-NLS-1$ + static final String GXY = "Generic XY"; //$NON-NLS-1$ static final String IDF = "Identifier"; //$NON-NLS-1$ static final String OCG = "Output Configurations"; //$NON-NLS-1$ static final String STY = "Styles"; //$NON-NLS-1$ @@ -170,6 +171,7 @@ public final class EndpointConstants { static final String MARKER_CATEGORIES_EX = "\"" + REQUESTED_MARKER_CATEGORIES_KEY + "\": [\"category1\", \"category2\"]"; //$NON-NLS-1$ //$NON-NLS-2$ static final String MARKER_SET_EX = "\"" + REQUESTED_MARKER_SET_KEY + "\": \"markerSetId\","; //$NON-NLS-1$ //$NON-NLS-2$ static final String TIMERANGE_EX = "\"" + REQUESTED_TIMERANGE_KEY + "\": {\"start\": 111111111, \"end\": 222222222, \"nbTimes\": 1920}"; //$NON-NLS-1$ //$NON-NLS-2$ + static final String TIMERANGE_SAMPLING_EX = "\"" + REQUESTED_TIMERANGE_KEY + "\": {\"start\": 111111111, \"end\": 222222222, \"nbSamples\": 1920}"; //$NON-NLS-1$ //$NON-NLS-2$ static final String FILTER_QUERY_PARAMETERS_EX = "\"" + FILTER_QUERY_PARAMETERS_KEY + "\": {\"" + FILTER_QUERY_STRATEGY + "\": \"SAMPLED\", \"" + FILTER_EXPRESSIONS_MAP + "\": {\"1\":[\"openat\", \"duration>10ms\"]}}"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ static final String TIMERANGE_EX_TREE = "\"" + REQUESTED_TIMERANGE_KEY + "\": {\"start\": 111111111, \"end\": 222222222}"; //$NON-NLS-1$ //$NON-NLS-2$ static final String TIMES_EX_TT = "\"" + REQUESTED_TIME_KEY + "\": [111200000],"; //$NON-NLS-1$ //$NON-NLS-2$ diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/services/QueryParametersUtil.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/services/QueryParametersUtil.java index e13f7f8fc..a35ea4d62 100644 --- a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/services/QueryParametersUtil.java +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/services/QueryParametersUtil.java @@ -1,5 +1,5 @@ /********************************************************************** - * Copyright (c) 2022 Ericsson + * Copyright (c) 2022, 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 @@ -54,7 +54,7 @@ private interface ElementType { private static final String FILTER_EXPRESSIONS_MAP = "filter_expressions_map"; //$NON-NLS-1$ private static final String NAME = "name"; //$NON-NLS-1$ private static final String NBTIMES = "nbTimes"; //$NON-NLS-1$ - private static final String REQUESTED_TIMERANGE_KEY = "requested_timerange"; //$NON-NLS-1$ + private static final String NBSAMPLES = "nbSamples"; //$NON-NLS-1$ private static final String SEP = ": "; //$NON-NLS-1$ private static final String START = "start"; //$NON-NLS-1$ private static final String STRATEGY = "strategy"; //$NON-NLS-1$ @@ -255,6 +255,24 @@ public static String validateFilterQueryParameters(Map params) { return null; } + /** + * Validate generic XY chart query parameters. + * + * @param params + * the mutable map of query parameters + * @return an error message if validation fails, or null otherwise + */ + public static String validateGenericXYQueryParameters(Map params) { + String errorMessage; + if ((errorMessage = validateRequestedTimeRangeWithSamples(params)) != null) { + return errorMessage; + } + if ((errorMessage = validateRequestedItems(params, true)) != null) { + return errorMessage; + } + return null; + } + /** * Validate a string query parameter. * @@ -310,24 +328,28 @@ private static String validateStringList(String param, Map param * @return an error message if validation fails, or null otherwise */ private static String validateRequestedTimeRange(Map params, boolean required, boolean isTree) { - Object requestedTimeRange = params.get(REQUESTED_TIMERANGE_KEY); + Object requestedTimeRange = params.get(DataProviderParameterUtils.REQUESTED_TIMERANGE_KEY); Object requestedTimes = params.get(DataProviderParameterUtils.REQUESTED_TIME_KEY); if (required && requestedTimeRange == null && requestedTimes == null) { - return MISSING_PARAMETERS + SEP + REQUESTED_TIMERANGE_KEY; + return MISSING_PARAMETERS + SEP + DataProviderParameterUtils.REQUESTED_TIMERANGE_KEY; } if (requestedTimeRange != null) { /* Transform requested timerange to requested times array */ - requestedTimes = params.computeIfPresent(REQUESTED_TIMERANGE_KEY, (k, v) -> { + requestedTimes = params.computeIfPresent(DataProviderParameterUtils.REQUESTED_TIMERANGE_KEY, (k, v) -> { if (v instanceof Map) { Map map = (Map) v; Object startObj = map.get(START); Object endObj = map.get(END); - Object nbTimesObj = map.get(NBTIMES); if (!(startObj instanceof Number && endObj instanceof Number)) { return null; } long start = ((Number) startObj).longValue(); long end = ((Number) endObj).longValue(); + Object nbTimesObj = map.get(NBTIMES); + if (nbTimesObj == null) { + return Arrays.asList(start, end); + } + if (!(nbTimesObj instanceof Number)) { return Arrays.asList(start, end); } @@ -341,10 +363,10 @@ private static String validateRequestedTimeRange(Map params, boo return null; }); if (requestedTimes == null) { - return INVALID_PARAMETERS + SEP + REQUESTED_TIMERANGE_KEY; + return INVALID_PARAMETERS + SEP + DataProviderParameterUtils.REQUESTED_TIMERANGE_KEY; } params.put(DataProviderParameterUtils.REQUESTED_TIME_KEY, requestedTimes); - params.remove(REQUESTED_TIMERANGE_KEY); + params.remove(DataProviderParameterUtils.REQUESTED_TIMERANGE_KEY); } else if (requestedTimes != null) { List<@NonNull Long> timeRequested = DataProviderParameterUtils.extractTimeRequested(params); if ((timeRequested == null) || (isTree && timeRequested.size() == 1) || (!isTree && timeRequested.isEmpty())) { @@ -355,6 +377,56 @@ private static String validateRequestedTimeRange(Map params, boo return null; } + /** + * Validate and transform the requested_timerange query parameter with the + * number of samples specified is in a valid format. + * + * The transformed object is a list of long values in order: + * [start, end, nbSamples]. + * + * @param params + * the mutable map of query parameters + * @return an error message if validation fails, or null otherwise + */ + private static String validateRequestedTimeRangeWithSamples(Map params) { + Object requestedTimeRange = params.get(DataProviderParameterUtils.REQUESTED_TIMERANGE_KEY); + if (requestedTimeRange == null) { + return MISSING_PARAMETERS + SEP + DataProviderParameterUtils.REQUESTED_TIMERANGE_KEY; + } + + if (!(requestedTimeRange instanceof Map)) { + return INVALID_PARAMETERS + SEP + DataProviderParameterUtils.REQUESTED_TIMERANGE_KEY; + } + + Map map = (Map) requestedTimeRange; + Object startObj = map.get(START); + if (startObj == null) { + return MISSING_PARAMETERS + SEP + START; + } + if (!(startObj instanceof Number)) { + return INVALID_PARAMETERS + SEP + START; + } + Object endObj = map.get(END); + if (endObj == null) { + return MISSING_PARAMETERS + SEP + END; + } + if (!(endObj instanceof Number)) { + return INVALID_PARAMETERS + SEP + END; + } + Object nbSamplesObj = map.get(NBSAMPLES); + if (nbSamplesObj == null) { + return MISSING_PARAMETERS + SEP + NBSAMPLES; + } + if (!(nbSamplesObj instanceof Number)) { + return INVALID_PARAMETERS + SEP + NBSAMPLES; + } + + Object requestedTimes = Arrays.asList( + ((Number) startObj).longValue(), ((Number) endObj).longValue(), ((Number) nbSamplesObj).longValue()); + params.put(DataProviderParameterUtils.REQUESTED_TIMERANGE_KEY, requestedTimes); + return null; + } + /** * Validate and convert the requested_times query parameter. * diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/webapp/AxisDomainSerializer.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/webapp/AxisDomainSerializer.java new file mode 100644 index 000000000..44a041dc6 --- /dev/null +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/webapp/AxisDomainSerializer.java @@ -0,0 +1,67 @@ +/********************************************************************** + * 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.incubator.internal.trace.server.jersey.rest.core.webapp; + +import java.io.IOException; + +import org.eclipse.tracecompass.tmf.core.model.IAxisDomain; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +/** + * Custom Jackson serializer for {@link IAxisDomain}. + *

+ * This serializer outputs a JSON representation including the {@code "type"} + * discriminator and the appropriate fields depending on the concrete subtype. + *

+ * + * Example JSON output: + *
+ * {
+ *   "type": "categorical",
+ *   "categories": ["foo", "bar"]
+ * }
+ *
+ * {
+ *   "type": "timeRange",
+ *   "start": 0,
+ *   "end": 100
+ * }
+ * 
+ * + * @author Siwei Zhang + * @since 10.2 + */ +public class AxisDomainSerializer extends JsonSerializer { + + private static final String TYPE = "type"; //$NON-NLS-1$ + + @Override + public void serialize(IAxisDomain value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writeStartObject(); + + if (value instanceof IAxisDomain.Categorical categorical) { + gen.writeStringField(TYPE, "categorical"); //$NON-NLS-1$ + gen.writeObjectField("categories", categorical.categories()); //$NON-NLS-1$ + } else if (value instanceof IAxisDomain.Range timeRange) { + gen.writeStringField(TYPE, "timeRange"); //$NON-NLS-1$ + gen.writeNumberField("start", timeRange.start()); //$NON-NLS-1$ + gen.writeNumberField("end", timeRange.end()); //$NON-NLS-1$ + } else { + throw new IllegalArgumentException("Unsupported AxisDomain implementation: " + value.getClass()); //$NON-NLS-1$ + } + + gen.writeEndObject(); + } +} diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/webapp/JacksonObjectMapperProvider.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/webapp/JacksonObjectMapperProvider.java index 992836f04..7528022a4 100644 --- a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/webapp/JacksonObjectMapperProvider.java +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/webapp/JacksonObjectMapperProvider.java @@ -24,7 +24,9 @@ import org.eclipse.tracecompass.tmf.core.config.ITmfConfiguration; import org.eclipse.tracecompass.tmf.core.config.ITmfConfigurationSourceType; import org.eclipse.tracecompass.tmf.core.model.DataProviderDescriptor; +import org.eclipse.tracecompass.tmf.core.model.IAxisDomain; import org.eclipse.tracecompass.tmf.core.model.OutputElementStyle; +import org.eclipse.tracecompass.tmf.core.model.ISampling; import org.eclipse.tracecompass.tmf.core.model.annotations.Annotation; import org.eclipse.tracecompass.tmf.core.model.timegraph.ITimeGraphArrow; import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphEntryModel; @@ -62,6 +64,7 @@ public ObjectMapper getContext(Class type) { module.addSerializer(DataProviderDescriptor.class, new DataProviderDescriptorSerializer()); module.addSerializer(ITmfXyModel.class, new XYModelSerializer()); module.addSerializer(ISeriesModel.class, new SeriesModelSerializer()); + module.addSerializer(ISampling.class, new SamplingSerializer()); module.addSerializer(TimeGraphState.class, new TimeGraphStateSerializer()); module.addSerializer(ITimeGraphArrow.class, new TimeGraphArrowSerializer()); module.addSerializer(TimeGraphRowModel.class, new TimeGraphRowModelSerializer()); @@ -76,6 +79,7 @@ public ObjectMapper getContext(Class type) { module.addSerializer(ITmfConfiguration.class, new TmfConfigurationSerializer()); module.addSerializer(ITmfConfigurationSourceType.class, new TmfConfigurationSourceTypeSerializer()); module.addSerializer(ITmfConfigParamDescriptor.class, new TmfConfigParamDescriptorSerializer()); + module.addSerializer(IAxisDomain.class, new AxisDomainSerializer()); // create JsonProvider to provide custom ObjectMapper JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider(); diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/webapp/SamplingSerializer.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/webapp/SamplingSerializer.java new file mode 100644 index 000000000..2cd788cb0 --- /dev/null +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/webapp/SamplingSerializer.java @@ -0,0 +1,69 @@ +/********************************************************************** + * 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.incubator.internal.trace.server.jersey.rest.core.webapp; + +import java.io.IOException; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.tracecompass.tmf.core.model.ISampling; +import org.eclipse.tracecompass.tmf.core.model.ISampling.Categories; +import org.eclipse.tracecompass.tmf.core.model.ISampling.Range; +import org.eclipse.tracecompass.tmf.core.model.ISampling.Ranges; +import org.eclipse.tracecompass.tmf.core.model.ISampling.Timestamps; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +/** + * Custom serializer for all Sampling subtypes. + * - Timestamps → flat array: [1, 2, 3] + * - Categories → array of strings: ["Read", "Write"] + * - TimeRanges → array of arrays: [[1, 2], [2, 3]] + */ +public class SamplingSerializer extends StdSerializer { + + private static final long serialVersionUID = 1L; + + /** + * Constructor + */ + public SamplingSerializer() { + super(ISampling.class); + } + + @Override + public void serialize(ISampling value, JsonGenerator gen, SerializerProvider provider) throws IOException { + if (value instanceof Timestamps timestamps) { + gen.writeArray(timestamps.timestamps(), 0, timestamps.timestamps().length); + + } else if (value instanceof Categories categories) { + gen.writeStartArray(); + for (String category : categories.categories()) { + gen.writeString(category); + } + gen.writeEndArray(); + + } else if (value instanceof Ranges timeRanges) { + gen.writeStartArray(); + for (Range<@NonNull Long> range : timeRanges.ranges()) { + gen.writeStartArray(); + gen.writeNumber(range.start()); + gen.writeNumber(range.end()); + gen.writeEndArray(); + } + gen.writeEndArray(); + } else { + throw new IllegalArgumentException("Unknown Sampling type: " + value.getClass().getName()); //$NON-NLS-1$ + } + } +} diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/webapp/SeriesModelSerializer.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/webapp/SeriesModelSerializer.java index 47da4592d..b30e373ba 100644 --- a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/webapp/SeriesModelSerializer.java +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/webapp/SeriesModelSerializer.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 @@ -47,13 +47,15 @@ public void serialize(ISeriesModel value, JsonGenerator gen, SerializerProvider gen.writeStartObject(); gen.writeNumberField("seriesId", value.getId()); //$NON-NLS-1$ gen.writeStringField("seriesName", value.getName()); //$NON-NLS-1$ - gen.writeObjectField("xValues", value.getXAxis()); //$NON-NLS-1$ + gen.writeObjectField("xValues", value.getSampling()); //$NON-NLS-1$ gen.writeObjectField("yValues", value.getData()); //$NON-NLS-1$ // no-op trim below, null-related (unlikely case) warning otherwise- String type = value.getDisplayType().name().toLowerCase().trim(); OutputElementStyle style = new OutputElementStyle(null, ImmutableMap.of(StyleProperties.SERIES_TYPE, type)); gen.writeObjectField("style", style); //$NON-NLS-1$ + gen.writeObjectField("xValuesDescription", value.getXAxisDescription()); //$NON-NLS-1$ + gen.writeObjectField("yValuesDescription", value.getYAxisDescription()); //$NON-NLS-1$ gen.writeEndObject(); } } diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.product/traceserver.product b/trace-server/org.eclipse.tracecompass.incubator.trace.server.product/traceserver.product index d05fc8bc0..f20c75a24 100644 --- a/trace-server/org.eclipse.tracecompass.incubator.trace.server.product/traceserver.product +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.product/traceserver.product @@ -134,8 +134,8 @@ Java and all Java-based trademarks are trademarks of Oracle Corporation in the U - +