diff --git a/analyses/org.eclipse.tracecompass.incubator.dpdk.core/META-INF/MANIFEST.MF b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/META-INF/MANIFEST.MF
index 7eef4387..c6952c0f 100644
--- a/analyses/org.eclipse.tracecompass.incubator.dpdk.core/META-INF/MANIFEST.MF
+++ b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/META-INF/MANIFEST.MF
@@ -15,8 +15,10 @@ Require-Bundle: org.eclipse.ui,
org.eclipse.tracecompass.tmf.core,
org.eclipse.tracecompass.tmf.ctf.core,
org.eclipse.tracecompass.analysis.os.linux.core,
- org.eclipse.jdt.annotation;bundle-version="2.2.400"
+ org.eclipse.jdt.annotation;bundle-version="2.2.400",
+ com.google.guava
Export-Package: org.eclipse.tracecompass.incubator.dpdk.core.trace,
+ org.eclipse.tracecompass.incubator.internal.dpdk.core.ethdev.spin.analysis,
org.eclipse.tracecompass.incubator.internal.dpdk.core.ethdev.throughput.analysis,
org.eclipse.tracecompass.incubator.internal.dpdk.core.lcore.analysis;x-friends:="org.eclipse.tracecompass.incubator.dpdk.core.tests"
Automatic-Module-Name: org.eclipse.tracecompass.incubator.dpdk.core
diff --git a/analyses/org.eclipse.tracecompass.incubator.dpdk.core/plugin.xml b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/plugin.xml
index 28d16bc6..694e32c5 100644
--- a/analyses/org.eclipse.tracecompass.incubator.dpdk.core/plugin.xml
+++ b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/plugin.xml
@@ -21,6 +21,15 @@
class="org.eclipse.tracecompass.incubator.dpdk.core.trace.DpdkTrace">
+
+
+
+
@@ -36,6 +45,10 @@
class="org.eclipse.tracecompass.incubator.internal.dpdk.core.ethdev.throughput.analysis.DpdkEthdevThroughputPpsDataProviderFactory"
id="org.eclipse.tracecompass.incubator.dpdk.ethdev.throughput.pps.dataprovider">
+
+
diff --git a/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/Attributes.java b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/Attributes.java
new file mode 100644
index 00000000..c5352b99
--- /dev/null
+++ b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/Attributes.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2024 É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
+ * 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.dpdk.core.ethdev.spin.analysis;
+
+/**
+ * This interface defines all the attribute names used in the state system.
+ *
+ * @author Adel Belkhiri
+ */
+@SuppressWarnings({ "nls" })
+public interface Attributes {
+
+ /* First-level attributes */
+
+ /** Root attribute for DPDK Ethdev Nics */
+ String POLL_THREADS = "Threads";
+ /** Reception Queues */
+ /** */
+ String SPIN_STATUS = "Spin";
+ /** */
+ String ACTIVE_STATUS = "Active";
+}
diff --git a/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/DpdkEthdevEventHandler.java b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/DpdkEthdevEventHandler.java
new file mode 100644
index 00000000..99338162
--- /dev/null
+++ b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/DpdkEthdevEventHandler.java
@@ -0,0 +1,79 @@
+/**********************************************************************
+ * Copyright (c) 2024 É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
+ * 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.dpdk.core.ethdev.spin.analysis;
+
+import java.util.Objects;
+
+import org.eclipse.tracecompass.incubator.internal.dpdk.core.Activator;
+import org.eclipse.tracecompass.incubator.internal.dpdk.core.analysis.DpdkEthdevEventLayout;
+import org.eclipse.tracecompass.incubator.internal.dpdk.core.analysis.IDpdkEventHandler;
+import org.eclipse.tracecompass.statesystem.core.ITmfStateSystemBuilder;
+import org.eclipse.tracecompass.statesystem.core.StateSystemBuilderUtils;
+import org.eclipse.tracecompass.statesystem.core.exceptions.StateValueTypeException;
+import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
+
+/**
+ * Event handler for the DPDK events required for the
+ * {@link DpdkEthdevSpinAnalysisModule} analysis
+ *
+ * @author Adel Belkhiri
+ */
+public class DpdkEthdevEventHandler implements IDpdkEventHandler {
+
+ private static final String POLL_THREADS = Objects.requireNonNull(Attributes.POLL_THREADS);
+
+ DpdkEthdevEventHandler() {
+ // Nothing here
+ }
+
+ /**
+ * Update the count of received or transmitted packets on the state system
+ *
+ * @param ssb
+ * state system builder
+ * @param queueQuark
+ * quark of the the Ethernet device queue
+ * @param nbPkts
+ * number of packets received or transmitted
+ * @param ts
+ * time to use for state change
+ */
+ public void updateCounts(ITmfStateSystemBuilder ssb, int queueQuark, Integer nbPkts, long ts) {
+ if (nbPkts <= 0) {
+ return;
+ }
+ try {
+ StateSystemBuilderUtils.incrementAttributeLong(ssb, ts, queueQuark, nbPkts);
+ } catch (StateValueTypeException e) {
+ Activator.getInstance().logWarning("Problem accessing the state of a NIC queue (Quark = " + String.valueOf(queueQuark) + ")", e); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+
+ @Override
+ public void handleEvent(ITmfStateSystemBuilder ssb, ITmfEvent event) {
+ long ts = event.getTimestamp().getValue();
+ String eventName = event.getName();
+
+ Integer portId = event.getContent().getFieldValue(Integer.class, DpdkEthdevEventLayout.fieldPortId());
+ Integer queueId = event.getContent().getFieldValue(Integer.class, DpdkEthdevEventLayout.fieldQueueId());
+ String threadName = event.getContent().getFieldValue(String.class, DpdkEthdevEventLayout.fieldThreadName());
+ Integer cpuId = event.getContent().getFieldValue(Integer.class, DpdkEthdevEventLayout.fieldCpuId());
+
+ int threadQuark = ssb.getQuarkAbsoluteAndAdd(POLL_THREADS, threadName + "/" + cpuId); //$NON-NLS-1$
+ int queueQark = ssb.getQuarkRelativeAndAdd(threadQuark, "P" + Objects.requireNonNull(portId).toString() + "/Q" + Objects.requireNonNull(queueId).toString()); //$NON-NLS-1$ //$NON-NLS-2$
+
+ if (eventName.equals(DpdkEthdevEventLayout.eventEthdevRxBurstEmpty())) {
+ ssb.modifyAttribute(ts, Attributes.SPIN_STATUS, queueQark);
+ } else if (eventName.equals(DpdkEthdevEventLayout.eventEthdevRxBurstNonEmpty())) {
+ ssb.modifyAttribute(ts, Attributes.ACTIVE_STATUS, queueQark);
+ }
+ }
+}
diff --git a/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/DpdkEthdevSpinAnalysisModule.java b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/DpdkEthdevSpinAnalysisModule.java
new file mode 100644
index 00000000..64828ee8
--- /dev/null
+++ b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/DpdkEthdevSpinAnalysisModule.java
@@ -0,0 +1,154 @@
+/**********************************************************************
+ * Copyright (c) 2024 É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
+ * 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.dpdk.core.ethdev.spin.analysis;
+
+import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.tracecompass.incubator.internal.dpdk.core.Activator;
+import org.eclipse.tracecompass.incubator.internal.dpdk.core.analysis.DpdkEthdevEventLayout;
+import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
+import org.eclipse.tracecompass.statesystem.core.exceptions.AttributeNotFoundException;
+import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException;
+import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException;
+import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval;
+import org.eclipse.tracecompass.tmf.core.analysis.requirements.TmfAbstractAnalysisRequirement;
+import org.eclipse.tracecompass.tmf.core.analysis.requirements.TmfAbstractAnalysisRequirement.PriorityLevel;
+import org.eclipse.tracecompass.tmf.core.analysis.requirements.TmfAnalysisEventRequirement;
+import org.eclipse.tracecompass.tmf.core.statesystem.ITmfStateProvider;
+import org.eclipse.tracecompass.tmf.core.statesystem.TmfStateSystemAnalysisModule;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.eclipse.tracecompass.tmf.core.trace.TmfTrace;
+import org.eclipse.tracecompass.tmf.core.util.Pair;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * This analysis provides an estimation for the percentage of time a PMD thread
+ * was doing a real work (e.g., fetching and processing packets). It is based on
+ * two events: the "lib.ethdev.rx.burst.empty" event denotes an empty poll, and
+ * the "lib.ethdev.rx.burst.nonempty" event denotes a successful poll, where one
+ * or many packets are retrieved.
+ *
+ * @author Adel Belkhiri
+ */
+public class DpdkEthdevSpinAnalysisModule extends TmfStateSystemAnalysisModule {
+
+ /** The ID of this analysis module */
+ public static final String ID = "org.eclipse.tracecompass.incubator.internal.dpdk.core.ethdev.spin.analysis"; //$NON-NLS-1$
+
+ private final TmfAbstractAnalysisRequirement REQUIREMENT = new TmfAnalysisEventRequirement(ImmutableList.of(
+ DpdkEthdevEventLayout.eventEthdevRxBurstEmpty(), DpdkEthdevEventLayout.eventEthdevRxBurstNonEmpty()), PriorityLevel.AT_LEAST_ONE);
+
+ @Override
+ protected ITmfStateProvider createStateProvider() {
+ ITmfTrace trace = checkNotNull(getTrace());
+
+ if (trace instanceof TmfTrace) {
+ return new DpdkEthdevSpinStateProvider(trace, ID);
+ }
+
+ throw new IllegalStateException();
+ }
+
+ @Override
+ public Iterable getAnalysisRequirements() {
+ return Collections.singleton(REQUIREMENT);
+ }
+
+ /**
+ * Calculates thread usage within a specific time interval
+ *
+ * @param threads
+ * Identifiers of the target threads
+ * @param start
+ * Start timestamp
+ * @param end
+ * End timestamp
+ * @return A map of Thread names -> time spent in empty or active iterations
+ */
+ public Map> getThreadUsageInRange(Set<@NonNull Integer> threads, long start, long end) {
+ Map> map = new HashMap<>();
+
+ ITmfTrace trace = getTrace();
+ ITmfStateSystem threadSs = getStateSystem();
+ if (trace == null || threadSs == null) {
+ return map;
+ }
+
+ long startTime = Math.max(start, threadSs.getStartTime());
+ long endTime = Math.min(end, threadSs.getCurrentEndTime());
+ if (endTime < startTime) {
+ return map;
+ }
+
+ try {
+ int threadsNode = threadSs.getQuarkAbsolute(Attributes.POLL_THREADS);
+ for (int threadNode : threadSs.getSubAttributes(threadsNode, false)) {
+ if (!threads.contains(threadNode)) {
+ continue;
+ }
+
+ String curThreadName = threadSs.getAttributeName(threadNode);
+ long countActive = 0;
+ long countSpin = 0;
+ for (int queueNode : threadSs.getSubAttributes(threadNode, false)) {
+ long ts = startTime;
+ do {
+ ITmfStateInterval stateInterval = threadSs.querySingleState(ts, queueNode);
+ Object stateValue = stateInterval.getStateValue().unboxValue();
+ long stateStart = stateInterval.getStartTime();
+ long stateEnd = stateInterval.getEndTime();
+
+ if (stateValue != null) {
+ String threadState = (String) stateValue;
+ if (threadState.equals(Attributes.ACTIVE_STATUS)) {
+ countActive += interpolateCount(startTime, endTime, stateEnd, stateStart);
+ } else if (threadState.equals(Attributes.SPIN_STATUS)) {
+ countSpin += interpolateCount(startTime, endTime, stateEnd, stateStart);
+ }
+ }
+ ts = Math.min(stateEnd, endTime) + 1;
+ } while (ts < endTime);
+ }
+
+ map.put(curThreadName, new Pair<>(countActive, countSpin));
+ }
+
+ } catch (TimeRangeException | AttributeNotFoundException | StateSystemDisposedException e) {
+ Activator.getInstance().logError(e.getMessage());
+ }
+
+ return map;
+ }
+
+ private static long interpolateCount(long startTime, long endTime, long spinningEnd, long spinningStart) {
+
+ long count = spinningEnd - spinningStart;
+
+ /* sanity check */
+ if (count > 0) {
+ if (startTime > spinningStart) {
+ count -= (startTime - spinningStart);
+ }
+
+ if (endTime < spinningEnd) {
+ count -= (spinningEnd - endTime);
+ }
+ }
+ return count;
+ }
+}
diff --git a/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/DpdkEthdevSpinDataProvider.java b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/DpdkEthdevSpinDataProvider.java
new file mode 100644
index 00000000..06e50670
--- /dev/null
+++ b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/DpdkEthdevSpinDataProvider.java
@@ -0,0 +1,240 @@
+/**********************************************************************
+ * Copyright (c) 2024 É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
+ * 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.dpdk.core.ethdev.spin.analysis;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.incubator.internal.dpdk.core.Activator;
+import org.eclipse.tracecompass.internal.tmf.core.model.filters.FetchParametersUtils;
+import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
+import org.eclipse.tracecompass.statesystem.core.exceptions.AttributeNotFoundException;
+import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException;
+import org.eclipse.tracecompass.tmf.core.model.CommonStatusMessage;
+import org.eclipse.tracecompass.tmf.core.model.IOutputStyleProvider;
+import org.eclipse.tracecompass.tmf.core.model.OutputElementStyle;
+import org.eclipse.tracecompass.tmf.core.model.OutputStyleModel;
+import org.eclipse.tracecompass.tmf.core.model.StyleProperties;
+import org.eclipse.tracecompass.tmf.core.model.YModel;
+import org.eclipse.tracecompass.tmf.core.model.filters.SelectionTimeQueryFilter;
+import org.eclipse.tracecompass.tmf.core.model.tree.TmfTreeDataModel;
+import org.eclipse.tracecompass.tmf.core.model.tree.TmfTreeModel;
+import org.eclipse.tracecompass.tmf.core.model.xy.AbstractTreeCommonXDataProvider;
+import org.eclipse.tracecompass.tmf.core.model.xy.IYModel;
+import org.eclipse.tracecompass.tmf.core.response.ITmfResponse;
+import org.eclipse.tracecompass.tmf.core.response.TmfModelResponse;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
+import org.eclipse.tracecompass.tmf.core.util.Pair;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * This data provider will return a XY model, showing the percentage of
+ * occupancy of a PMD thread. The model also shows how much time the thread
+ * spends processing packets versus being idle over a period.
+ *
+ * @author Adel Belkhiri
+ */
+public class DpdkEthdevSpinDataProvider extends AbstractTreeCommonXDataProvider
+ implements IOutputStyleProvider {
+
+ /**
+ * Extension point ID.
+ */
+ public static final String ID = "org.eclipse.tracecompass.incubator.internal.dpdk.core.ethdev.spin.dataprovider"; //$NON-NLS-1$
+
+ /**
+ * Title used to create XY models for the
+ * {@link DpdkEthdevSpinDataProvider}.
+ */
+ protected static final String PROVIDER_TITLE = Objects.requireNonNull("DPDK Threads Effective CPU Usage"); //$NON-NLS-1$
+
+ private static final String BASE_STYLE = "base"; //$NON-NLS-1$
+
+ private static final String THREADS_LABEL = Objects.requireNonNull(Messages.DpdkEthdevSpin_DataProvider_Threads);
+
+ private static final Map STATE_MAP;
+
+ static {
+ // Create the base style
+ ImmutableMap.Builder builder = new ImmutableMap.Builder<>();
+ builder.put(BASE_STYLE, new OutputElementStyle(null, ImmutableMap.of(StyleProperties.SERIES_TYPE, StyleProperties.SeriesType.SCATTER, StyleProperties.WIDTH, 1.0f)));
+ STATE_MAP = builder.build();
+ }
+
+ /**
+ * Create an instance of {@link DpdkEthdevSpinDataProvider}. Returns a null
+ * instance if the analysis module is not found.
+ *
+ * @param trace
+ * A trace on which we are interested to fetch a model
+ * @return a {@link DpdkEthdevSpinDataProvider} instance. If analysis module
+ * is not found, it returns null
+ */
+ public static @Nullable DpdkEthdevSpinDataProvider create(ITmfTrace trace) {
+ DpdkEthdevSpinAnalysisModule module = TmfTraceUtils.getAnalysisModuleOfClass(trace, DpdkEthdevSpinAnalysisModule.class, DpdkEthdevSpinAnalysisModule.ID);
+ if (module != null) {
+ module.schedule();
+ return new DpdkEthdevSpinDataProvider(trace, module);
+ }
+ return null;
+ }
+
+ /**
+ * Constructor
+ */
+ private DpdkEthdevSpinDataProvider(ITmfTrace trace, DpdkEthdevSpinAnalysisModule module) {
+ super(trace, module);
+ }
+
+ @Override
+ public String getId() {
+ return ID;
+ }
+
+ @Override
+ protected TmfTreeModel getTree(ITmfStateSystem ss, Map parameters, @Nullable IProgressMonitor monitor) {
+ List nodes = new ArrayList<>();
+ long rootId = getId(ITmfStateSystem.ROOT_ATTRIBUTE);
+ nodes.add(new TmfTreeDataModel(rootId, -1, Collections.singletonList(Objects.requireNonNull(getTrace().getName())), false, null));
+
+ try {
+ int threadsQuark = ss.getQuarkAbsolute(Attributes.POLL_THREADS);
+ long threadsId = getId(threadsQuark);
+ nodes.add(new TmfTreeDataModel(threadsId, rootId, Collections.singletonList(THREADS_LABEL), false, null));
+
+ for (Integer threadQuark : ss.getQuarks(Attributes.POLL_THREADS, "*")) { //$NON-NLS-1$
+ String threadName = ss.getAttributeName(threadQuark);
+ long threadId = getId(threadQuark);
+ nodes.add(new TmfTreeDataModel(threadId, threadsId, Collections.singletonList(threadName), false, null));
+ }
+ } catch (AttributeNotFoundException e) {
+ Activator.getInstance().logError("Error getting the root attribute of " + Attributes.POLL_THREADS); //$NON-NLS-1$
+ }
+ return new TmfTreeModel<>(Collections.emptyList(), nodes);
+ }
+
+ private static long getInitialPrevTime(SelectionTimeQueryFilter filter) {
+ /*
+ * Subtract from start time the same interval as the interval from start
+ * time to next time, ignoring duplicates in the times requested.
+ */
+ long startTime = filter.getStart();
+ for (long time : filter.getTimesRequested()) {
+ if (time > startTime) {
+ return startTime - (time - startTime);
+ }
+ }
+ return startTime;
+ }
+
+ @Override
+ protected @Nullable Collection getYSeriesModels(ITmfStateSystem ss, Map fetchParameters, @Nullable IProgressMonitor monitor) throws StateSystemDisposedException {
+ SelectionTimeQueryFilter filter = FetchParametersUtils.createSelectionTimeQuery(fetchParameters);
+ if (filter == null) {
+ return null;
+ }
+
+ if (getSelectedEntries(filter).isEmpty()) {
+ return null;
+ }
+
+ Set selectedThreads = new HashSet<>();
+
+ long[] xValues = filter.getTimesRequested();
+ Map selectedThreadValues = new HashMap<>();
+
+ for (Entry entry : getSelectedEntries(filter).entrySet()) {
+ int quark = Objects.requireNonNull(entry.getValue());
+
+ if ((quark == ITmfStateSystem.ROOT_ATTRIBUTE) ||
+ (ss.getParentAttributeQuark(quark) == ITmfStateSystem.ROOT_ATTRIBUTE)) {
+ continue;
+ }
+ selectedThreads.add(quark);
+ String name = ss.getAttributeName(quark);
+ selectedThreadValues.put(name, new YModel(entry.getKey(), getTrace().getName() + '/' + name, new double[xValues.length]));
+ }
+
+ long prevTime = Math.max(getInitialPrevTime(filter), ss.getStartTime());
+ long currentEnd = ss.getCurrentEndTime();
+
+ for (int i = 0; i < xValues.length; i++) {
+ long time = xValues[i];
+ if (time < ss.getStartTime() || time > currentEnd) {
+ prevTime = time;
+ continue;
+ }
+ if (prevTime < time) {
+ Map> threadUsageMap = getAnalysisModule().getThreadUsageInRange(selectedThreads, prevTime, time);
+ for (Entry> entry : threadUsageMap.entrySet()) {
+ IYModel values = selectedThreadValues.get(entry.getKey()/*
+ * threadName
+ */);
+ if (values != null) {
+ long countActive = Objects.requireNonNull(entry.getValue()).getFirst();
+ long countSpin = Objects.requireNonNull(entry.getValue()).getSecond();
+ values.getData()[i] = getPercentageValue(countActive, countSpin);
+ }
+ }
+ } else if (i > 0) {
+ /* In case of duplicate time xValue copy previous yValues */
+ for (IYModel values : selectedThreadValues.values()) {
+ values.getData()[i] = values.getData()[i - 1];
+ }
+ }
+ prevTime = time;
+ if (monitor != null && monitor.isCanceled()) {
+ return null;
+ }
+ }
+
+ ImmutableList.Builder ySeries = ImmutableList.builder();
+ for (IYModel entry : selectedThreadValues.values()) {
+ ySeries.add(entry);
+ }
+
+ return ySeries.build();
+ }
+
+ private static double getPercentageValue(long countActive, long countSpin) {
+ double value = (countActive + countSpin == 0) ? 0.0 : (double) countActive * 100 / (countActive + countSpin);
+ return value;
+ }
+
+ @Override
+ protected boolean isCacheable() {
+ return true;
+ }
+
+ @Override
+ protected String getTitle() {
+ return PROVIDER_TITLE;
+ }
+
+ @Override
+ public TmfModelResponse fetchStyle(Map fetchParameters, @Nullable IProgressMonitor monitor) {
+ return new TmfModelResponse<>(new OutputStyleModel(STATE_MAP), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
+ }
+}
diff --git a/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/DpdkEthdevSpinDataProviderFactory.java b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/DpdkEthdevSpinDataProviderFactory.java
new file mode 100644
index 00000000..2550304f
--- /dev/null
+++ b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/DpdkEthdevSpinDataProviderFactory.java
@@ -0,0 +1,55 @@
+/**********************************************************************
+ * Copyright (c) 2024 É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
+ * 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.dpdk.core.ethdev.spin.analysis;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.tmf.core.dataprovider.IDataProviderDescriptor;
+import org.eclipse.tracecompass.tmf.core.dataprovider.IDataProviderDescriptor.ProviderType;
+import org.eclipse.tracecompass.tmf.core.dataprovider.IDataProviderFactory;
+import org.eclipse.tracecompass.tmf.core.model.DataProviderDescriptor;
+import org.eclipse.tracecompass.tmf.core.model.tree.ITmfTreeDataModel;
+import org.eclipse.tracecompass.tmf.core.model.xy.ITmfTreeXYDataProvider;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
+
+/**
+ * Factory to create instances of the {@link DpdkEthdevSpinDataProvider}.
+ *
+ * @author Adel Belkhiri
+ */
+public class DpdkEthdevSpinDataProviderFactory implements IDataProviderFactory {
+ private static final IDataProviderDescriptor DESCRIPTOR = new DataProviderDescriptor.Builder()
+ .setId(DpdkEthdevSpinDataProvider.ID)
+ .setName("Dpdk Ethernet RX Spins") //$NON-NLS-1$
+ .setDescription("XY chart showing a rough estimate of PMD threads busyness based on the number of empy and full Rx spins") //$NON-NLS-1$
+ .setProviderType(ProviderType.TREE_TIME_XY)
+ .build();
+
+ @Override
+ public @Nullable ITmfTreeXYDataProvider extends ITmfTreeDataModel> createProvider(ITmfTrace trace) {
+ DpdkEthdevSpinAnalysisModule module = TmfTraceUtils.getAnalysisModuleOfClass(trace, DpdkEthdevSpinAnalysisModule.class, DpdkEthdevSpinAnalysisModule.ID);
+ if (module == null) {
+ return null;
+ }
+ module.schedule();
+ return DpdkEthdevSpinDataProvider.create(trace);
+ }
+
+ @Override
+ public Collection getDescriptors(ITmfTrace trace) {
+ DpdkEthdevSpinAnalysisModule module = TmfTraceUtils.getAnalysisModuleOfClass(trace, DpdkEthdevSpinAnalysisModule.class, DpdkEthdevSpinAnalysisModule.ID);
+ return module != null ? Collections.singletonList(DESCRIPTOR) : Collections.emptyList();
+ }
+
+}
diff --git a/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/DpdkEthdevSpinStateProvider.java b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/DpdkEthdevSpinStateProvider.java
new file mode 100644
index 00000000..6392ad6f
--- /dev/null
+++ b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/DpdkEthdevSpinStateProvider.java
@@ -0,0 +1,78 @@
+/**********************************************************************
+ * Copyright (c) 2024 É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
+ * 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.dpdk.core.ethdev.spin.analysis;
+
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.incubator.internal.dpdk.core.analysis.AbstractDpdkStateProvider;
+import org.eclipse.tracecompass.incubator.internal.dpdk.core.analysis.DpdkEthdevEventLayout;
+import org.eclipse.tracecompass.incubator.internal.dpdk.core.analysis.IDpdkEventHandler;
+import org.eclipse.tracecompass.tmf.core.statesystem.ITmfStateProvider;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * State provider for the {@link DpdkEthdevSpinAnalysisModule} analysis
+ *
+ * @author Adel Belkhiri
+ */
+public class DpdkEthdevSpinStateProvider extends AbstractDpdkStateProvider {
+
+ private static final int VERSION = 1;
+
+ /** Map events needed for this analysis with their handler functions */
+ private @Nullable Map fEventNames;
+
+ /**
+ * Constructor
+ *
+ * @param trace
+ * trace
+ * @param id
+ * id
+ */
+ protected DpdkEthdevSpinStateProvider(ITmfTrace trace, String id) {
+ super(trace, id);
+ }
+
+ /**
+ * Get the version of this state provider
+ */
+ @Override
+ public int getVersion() {
+ return VERSION;
+ }
+
+ /**
+ * Get a new instance
+ */
+ @Override
+ public ITmfStateProvider getNewInstance() {
+ return new DpdkEthdevSpinStateProvider(this.getTrace(), DpdkEthdevSpinAnalysisModule.ID);
+ }
+
+ @Override
+ protected @Nullable IDpdkEventHandler getEventHandler(String eventName) {
+ if (fEventNames == null) {
+ ImmutableMap.Builder builder = ImmutableMap.builder();
+ IDpdkEventHandler ethdevEventHandler = new DpdkEthdevEventHandler();
+ builder.put(DpdkEthdevEventLayout.eventEthdevRxBurstEmpty(), ethdevEventHandler);
+ builder.put(DpdkEthdevEventLayout.eventEthdevRxBurstNonEmpty(), ethdevEventHandler);
+ fEventNames = builder.build();
+ }
+ if (fEventNames != null) {
+ return fEventNames.get(eventName);
+ }
+ return null;
+ }
+}
diff --git a/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/Messages.java b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/Messages.java
new file mode 100644
index 00000000..96d733c6
--- /dev/null
+++ b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/Messages.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2024 É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
+ * 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.dpdk.core.ethdev.spin.analysis;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Messages for {@link DpdkEthdevSpinDataProvider}
+ *
+ * @author Adel Belkhiri
+ */
+@SuppressWarnings("javadoc")
+public class Messages extends NLS {
+ private static final String BUNDLE_NAME = "org.eclipse.tracecompass.incubator.internal.dpdk.core.ethdev.spin.analysis.messages"; //$NON-NLS-1$
+
+ public static @Nullable String DpdkEthdevSpin_DataProvider_Threads;
+ public static @Nullable String DpdkEthdevSpin_DataProvider_YAxis;
+ static {
+ // initialize resource bundle
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ private Messages() {
+ }
+}
diff --git a/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/messages.properties b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/messages.properties
new file mode 100644
index 00000000..5e0387dc
--- /dev/null
+++ b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/messages.properties
@@ -0,0 +1,13 @@
+###############################################################################
+# Copyright (c) 2024 É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 accompanies this distribution, and is available at
+# https://www.eclipse.org/legal/epl-2.0
+#
+# SPDX-License-Identifier: EPL-2.0
+###############################################################################
+
+DpdkEthdevSpin_DataProvider_Threads=Threads
+DpdkEthdevSpin_DataProvider_YAxis=Count
diff --git a/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/package-info.java b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/package-info.java
new file mode 100644
index 00000000..6054bdc5
--- /dev/null
+++ b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/package-info.java
@@ -0,0 +1,13 @@
+/**********************************************************************
+ * Copyright (c) 2024 É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
+ * accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ **********************************************************************/
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.eclipse.tracecompass.incubator.internal.dpdk.core.ethdev.spin.analysis;
+
diff --git a/analyses/org.eclipse.tracecompass.incubator.dpdk.ui/plugin.xml b/analyses/org.eclipse.tracecompass.incubator.dpdk.ui/plugin.xml
index 535db5d2..bda54a8a 100644
--- a/analyses/org.eclipse.tracecompass.incubator.dpdk.ui/plugin.xml
+++ b/analyses/org.eclipse.tracecompass.incubator.dpdk.ui/plugin.xml
@@ -25,6 +25,13 @@
class="org.eclipse.tracecompass.incubator.internal.dpdk.core.ethdev.throughput.analysis.DpdkEthdevThroughputAnalysisModule">
+
@@ -54,6 +61,13 @@
name="Ethernet Throughput View (BPS)"
restorable="true">
+
+
diff --git a/analyses/org.eclipse.tracecompass.incubator.dpdk.ui/src/org/eclipse/tracecompass/incubator/internal/dpdk/ui/ethdev/spin/Messages.java b/analyses/org.eclipse.tracecompass.incubator.dpdk.ui/src/org/eclipse/tracecompass/incubator/internal/dpdk/ui/ethdev/spin/Messages.java
new file mode 100644
index 00000000..ec3b7ec7
--- /dev/null
+++ b/analyses/org.eclipse.tracecompass.incubator.dpdk.ui/src/org/eclipse/tracecompass/incubator/internal/dpdk/ui/ethdev/spin/Messages.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2024 É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
+ * 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.dpdk.ui.ethdev.spin;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Messages for the {@link ThreadSpinStatisticsViewer} view
+ *
+ * @author Adel Belkhiri
+ */
+public class Messages extends NLS {
+ private static final String BUNDLE_NAME = "org.eclipse.tracecompass.incubator.internal.dpdk.ui.ethdev.spin.messages"; //$NON-NLS-1$
+ /** Title of the view */
+ public static @Nullable String EthdevThreadSpinStatsView_Title;
+ /** Title of the viewer */
+ public static @Nullable String EthdevThreadSpinStatsViewer_Title;
+ /** X axis caption */
+ public static @Nullable String EthdevThreadSpinStatsViewer_XAxis;
+ /** Y axis caption */
+ public static @Nullable String EthdevThreadSpinStatsViewer_YAxis;
+ /** column */
+ public static @Nullable String EthdevThreadSpinStatsTreeViewer_ThreadName;
+ /** Legend Column*/
+ public static @Nullable String EthdevThreadSpinStatsTreeViewer_Legend;
+ static {
+ // initialize resource bundle
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ private Messages() {
+ }
+}
diff --git a/analyses/org.eclipse.tracecompass.incubator.dpdk.ui/src/org/eclipse/tracecompass/incubator/internal/dpdk/ui/ethdev/spin/ThreadSpinStatisticsView.java b/analyses/org.eclipse.tracecompass.incubator.dpdk.ui/src/org/eclipse/tracecompass/incubator/internal/dpdk/ui/ethdev/spin/ThreadSpinStatisticsView.java
new file mode 100644
index 00000000..6d64ade8
--- /dev/null
+++ b/analyses/org.eclipse.tracecompass.incubator.dpdk.ui/src/org/eclipse/tracecompass/incubator/internal/dpdk/ui/ethdev/spin/ThreadSpinStatisticsView.java
@@ -0,0 +1,69 @@
+/**********************************************************************
+ * Copyright (c) 2024 É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
+ * 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.dpdk.ui.ethdev.spin;
+
+import java.util.Comparator;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.tracecompass.incubator.internal.dpdk.core.ethdev.spin.analysis.DpdkEthdevSpinDataProvider;
+import org.eclipse.tracecompass.tmf.ui.viewers.TmfViewer;
+import org.eclipse.tracecompass.tmf.ui.viewers.tree.AbstractSelectTreeViewer2;
+import org.eclipse.tracecompass.tmf.ui.viewers.tree.ITmfTreeColumnDataProvider;
+import org.eclipse.tracecompass.tmf.ui.viewers.tree.TmfGenericTreeEntry;
+import org.eclipse.tracecompass.tmf.ui.viewers.tree.TmfTreeColumnData;
+import org.eclipse.tracecompass.tmf.ui.viewers.xychart.TmfXYChartViewer;
+import org.eclipse.tracecompass.tmf.ui.viewers.xychart.linechart.TmfXYChartSettings;
+import org.eclipse.tracecompass.tmf.ui.views.xychart.TmfChartView;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Showing the PMD thread real occupancy across the trace duration
+ *
+ * @author Adel Belkhiri
+ */
+public class ThreadSpinStatisticsView extends TmfChartView {
+
+ /**
+ * Identifier of this view
+ */
+ public static final String ID = "org.eclipse.tracecompass.incubator.internal.dpdk.ui.ethdev.spin.statistics.view"; //$NON-NLS-1$
+ private static final double RESOLUTION = 0.4;
+
+ /**
+ * Default constructor
+ */
+ public ThreadSpinStatisticsView() {
+ super(Messages.EthdevThreadSpinStatsView_Title);
+ }
+
+ @Override
+ protected TmfXYChartViewer createChartViewer(Composite parent) {
+ TmfXYChartSettings settings = new TmfXYChartSettings(Messages.EthdevThreadSpinStatsViewer_Title, Messages.EthdevThreadSpinStatsViewer_XAxis, Messages.EthdevThreadSpinStatsViewer_YAxis, RESOLUTION);
+ return new ThreadSpinStatisticsViewer(parent, settings, DpdkEthdevSpinDataProvider.ID);
+ }
+
+ @Override
+ protected @NonNull TmfViewer createLeftChildViewer(@Nullable Composite parent) {
+ return new AbstractSelectTreeViewer2(parent, 1, DpdkEthdevSpinDataProvider.ID) {
+ @Override
+ protected ITmfTreeColumnDataProvider getColumnDataProvider() {
+ return () -> {
+ return ImmutableList.of(
+ createColumn(Messages.EthdevThreadSpinStatsTreeViewer_ThreadName, Comparator.comparing(TmfGenericTreeEntry::getName)),
+ new TmfTreeColumnData(Messages.EthdevThreadSpinStatsTreeViewer_Legend));
+ };
+ }
+ };
+ }
+}
diff --git a/analyses/org.eclipse.tracecompass.incubator.dpdk.ui/src/org/eclipse/tracecompass/incubator/internal/dpdk/ui/ethdev/spin/ThreadSpinStatisticsViewer.java b/analyses/org.eclipse.tracecompass.incubator.dpdk.ui/src/org/eclipse/tracecompass/incubator/internal/dpdk/ui/ethdev/spin/ThreadSpinStatisticsViewer.java
new file mode 100644
index 00000000..493373f0
--- /dev/null
+++ b/analyses/org.eclipse.tracecompass.incubator.dpdk.ui/src/org/eclipse/tracecompass/incubator/internal/dpdk/ui/ethdev/spin/ThreadSpinStatisticsViewer.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2024 É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
+ * 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.dpdk.ui.ethdev.spin;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.tracecompass.tmf.core.model.OutputElementStyle;
+import org.eclipse.tracecompass.tmf.core.model.StyleProperties;
+import org.eclipse.tracecompass.tmf.ui.viewers.xychart.linechart.TmfFilteredXYChartViewer;
+import org.eclipse.tracecompass.tmf.ui.viewers.xychart.linechart.TmfXYChartSettings;
+
+/**
+ * Viewer for the {@link ThreadSpinStatisticsView} view
+ *
+ * @author Adel Belkhiri
+ */
+public class ThreadSpinStatisticsViewer extends TmfFilteredXYChartViewer {
+
+ private static final int DEFAULT_SERIES_WIDTH = 1;
+
+ /**
+ * Constructor
+ *
+ * @param parent
+ * Parent composite
+ * @param settings
+ * Chart settings
+ * @param providerId
+ * Data provider ID
+ */
+ public ThreadSpinStatisticsViewer(Composite parent, TmfXYChartSettings settings, String providerId) {
+ super(parent, settings, providerId);
+ }
+
+ @Override
+ public @NonNull OutputElementStyle getSeriesStyle(@NonNull Long seriesId) {
+ return getPresentationProvider().getSeriesStyle(seriesId, StyleProperties.SeriesType.LINE, DEFAULT_SERIES_WIDTH);
+ }
+
+}
diff --git a/analyses/org.eclipse.tracecompass.incubator.dpdk.ui/src/org/eclipse/tracecompass/incubator/internal/dpdk/ui/ethdev/spin/messages.properties b/analyses/org.eclipse.tracecompass.incubator.dpdk.ui/src/org/eclipse/tracecompass/incubator/internal/dpdk/ui/ethdev/spin/messages.properties
new file mode 100644
index 00000000..b5e4a033
--- /dev/null
+++ b/analyses/org.eclipse.tracecompass.incubator.dpdk.ui/src/org/eclipse/tracecompass/incubator/internal/dpdk/ui/ethdev/spin/messages.properties
@@ -0,0 +1,16 @@
+###############################################################################
+# Copyright (c) 2024 É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 accompanies this distribution, and is available at
+# https://www.eclipse.org/legal/epl-2.0/
+#
+# SPDX-License-Identifier: EPL-2.0
+###############################################################################
+EthdevThreadSpinStatsView_Title=PMD Effective Busyness View
+EthdevThreadSpinStatsViewer_Title=PMD Effective Busyness View
+EthdevThreadSpinStatsViewer_XAxis=Time
+EthdevThreadSpinStatsViewer_YAxis=% BUSY
+EthdevThreadSpinStatsTreeViewer_ThreadName=Threads
+EthdevThreadSpinStatsTreeViewer_Legend=Legend
diff --git a/analyses/org.eclipse.tracecompass.incubator.dpdk.ui/src/org/eclipse/tracecompass/incubator/internal/dpdk/ui/ethdev/spin/package-info.java b/analyses/org.eclipse.tracecompass.incubator.dpdk.ui/src/org/eclipse/tracecompass/incubator/internal/dpdk/ui/ethdev/spin/package-info.java
new file mode 100644
index 00000000..79b47420
--- /dev/null
+++ b/analyses/org.eclipse.tracecompass.incubator.dpdk.ui/src/org/eclipse/tracecompass/incubator/internal/dpdk/ui/ethdev/spin/package-info.java
@@ -0,0 +1,11 @@
+/**********************************************************************
+ * Copyright (c) 2024 É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
+ * 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.dpdk.ui.ethdev.spin;