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 c6952c0fb..e998a62b5 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
@@ -16,7 +16,8 @@ Require-Bundle: org.eclipse.ui,
org.eclipse.tracecompass.tmf.ctf.core,
org.eclipse.tracecompass.analysis.os.linux.core,
org.eclipse.jdt.annotation;bundle-version="2.2.400",
- com.google.guava
+ com.google.guava,
+ org.eclipse.tracecompass.analysis.lami.core
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,
diff --git a/analyses/org.eclipse.tracecompass.incubator.dpdk.core/plugin.xml b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/plugin.xml
index 694e32c56..560ab3317 100644
--- a/analyses/org.eclipse.tracecompass.incubator.dpdk.core/plugin.xml
+++ b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/plugin.xml
@@ -31,6 +31,13 @@
+
+
+
+
true, Collections.emptyList());
+ }
+
+ @Override
+ protected synchronized void initialize() {
+ // do nothing
+ }
+
+ @Override
+ public boolean canExecute(ITmfTrace trace) {
+ if (trace instanceof DpdkTrace) {
+ return ((DpdkTrace) trace).validate(null, trace.getPath()).isOK() ? true : false;
+ }
+ return false;
+ }
+
+ private static int workRemaining(ITmfTrace trace) {
+ return (int) Math.min(trace.getNbEvents() / (PROGRESS_INTERVAL + 1), Integer.MAX_VALUE);
+ }
+
+ @Override
+ public List execute(ITmfTrace trace, @Nullable TmfTimeRange timeRange, String extraParamsString, IProgressMonitor monitor) throws CoreException {
+ AtomicLong done = new AtomicLong();
+ Map> pollCountPerQueue = new TreeMap<>();
+ TmfTimeRange adjustedTimeRange = timeRange == null ? TmfTimeRange.ETERNITY : timeRange;
+ SubMonitor subMonitor = SubMonitor.convert(monitor, Messages.getMessage(Messages.EthdevPollDistribution_AnalysisName), workRemaining(trace));
+
+ /*
+ * Handle the filter in case the user indicates a specific port to
+ * process its events
+ */
+ TmfFilterMatchesNode filter = new TmfFilterMatchesNode(null);
+ filter.setEventAspect(new TmfContentFieldAspect(Messages.getMessage(Messages.EthdevPollDistribution_CountLabel), DpdkEthdevEventLayout.fieldPortId()));
+ filter.setRegex(extraParamsString);
+ Predicate filterPred = (event -> extraParamsString.isEmpty() || filter.matches(event));
+
+ // create and send the event request
+ TmfEventRequest eventRequest = createEventRequest(trace, adjustedTimeRange, filterPred,
+ pollCountPerQueue, subMonitor, done);
+ trace.sendRequest(eventRequest);
+
+ try {
+ eventRequest.waitForCompletion();
+ return convertToLamiTables(adjustedTimeRange, pollCountPerQueue);
+
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return Collections.emptyList();
+ }
+ }
+
+ private static TmfEventRequest createEventRequest(ITmfTrace trace, TmfTimeRange timeRange, Predicate filterPredicate, Map> pollAspectCounts, SubMonitor monitor, AtomicLong nbProcessevents) {
+ return new TmfEventRequest(ITmfEvent.class, timeRange, 0, Integer.MAX_VALUE, ExecutionType.BACKGROUND) {
+ @Override
+ public void handleData(ITmfEvent event) {
+ if (monitor.isCanceled()) {
+ cancel();
+ return;
+ }
+
+ // process events to compute RX polls distribution
+ processEvent(event, filterPredicate, pollAspectCounts);
+
+ if ((nbProcessevents.incrementAndGet() & PROGRESS_INTERVAL) == 0) {
+ monitor.setWorkRemaining(workRemaining(trace));
+ monitor.worked(1);
+ monitor.setTaskName(String.format("DPDK Polls Distribution Analysis (%s events processed)", //$NON-NLS-1$
+ NumberFormat.getInstance().format(nbProcessevents.get())));
+ }
+ }
+ };
+ }
+
+ private static void processEvent(ITmfEvent event, Predicate filterPredicate,
+ Map> pollAspectCounts) {
+
+ if (event.getName().equals(DpdkEthdevEventLayout.eventEthdevRxBurstNonEmpty())
+ && filterPredicate.test(event)) {
+ Integer nbRxPkts = event.getContent().getFieldValue(Integer.class, DpdkEthdevEventLayout.fieldNbRxPkts());
+ Integer portId = event.getContent().getFieldValue(Integer.class, DpdkEthdevEventLayout.fieldPortId());
+ Integer queueId = event.getContent().getFieldValue(Integer.class, DpdkEthdevEventLayout.fieldQueueId());
+
+ if (nbRxPkts != null && portId != null && queueId != null) {
+ String queueName = "P" + portId + "/Q" + queueId; //$NON-NLS-1$ //$NON-NLS-2$
+ Map dataSet = pollAspectCounts.computeIfAbsent(queueName, k -> new HashMap<>());
+ if (dataSet.size() < MEMORY_SANITY_LIMIT) {
+ dataSet.merge(nbRxPkts, 1L, Long::sum);
+ }
+ }
+ }
+ }
+
+ private @NonNull List convertToLamiTables(TmfTimeRange timeRange,
+ Map> pollCountPerQueue) {
+ List results = new ArrayList<>();
+ for (Map.Entry> entry : pollCountPerQueue.entrySet()) {
+ String queueName = entry.getKey();
+ Map dataSet = entry.getValue();
+
+ List tableEntries = dataSet.entrySet().stream()
+ .map(e -> new LamiTableEntry(Arrays.asList(
+ new LamiString(e.getKey().toString()),
+ new LamiLongNumber(e.getValue()))))
+ .collect(Collectors.toList());
+
+ List<@NonNull LamiTableEntryAspect> tableAspects = Arrays.asList(
+ new LamiCategoryAspect(Messages.EthdevPollDistribution_NumberOfPacketsLabel, 0),
+ new LamiCountAspect(Messages.EthdevPollDistribution_CountLabel, 1));
+
+ LamiTableClass tableClass = new LamiTableClass(queueName, queueName, tableAspects, Collections.emptySet());
+ results.add(new LamiResultTable(createTimeRange(timeRange), tableClass, tableEntries));
+ }
+ return results;
+ }
+
+ /**
+ * Count aspect, generic
+ *
+ */
+ private final class LamiCountAspect extends LamiGenericAspect {
+ private LamiCountAspect(String name, int column) {
+ super(name, null, column, true, false);
+ }
+ }
+
+ /**
+ * Category aspect, generic
+ *
+ */
+ private final class LamiCategoryAspect extends LamiGenericAspect {
+ private LamiCategoryAspect(String name, int column) {
+ super(name, null, column, false, false);
+ }
+ }
+
+ /**
+ * TODO: move to LAMI
+ */
+ private static LamiTimeRange createTimeRange(TmfTimeRange timeRange) {
+ return new LamiTimeRange(new LamiTimestamp(timeRange.getStartTime().toNanos()), new LamiTimestamp(timeRange.getEndTime().toNanos()));
+ }
+
+ /**
+ * TODO: LamiString in LAMI is private
+ */
+ private final class LamiString extends LamiData {
+ private final String fElement;
+
+ private LamiString(String element) {
+ fElement = element;
+ }
+
+ @Override
+ public @NonNull String toString() {
+ return fElement;
+ }
+ }
+}
diff --git a/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/poll/distribution/analysis/Messages.java b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/poll/distribution/analysis/Messages.java
new file mode 100644
index 000000000..4ea7eba01
--- /dev/null
+++ b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/poll/distribution/analysis/Messages.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * 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.poll.distribution.analysis;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Messages for the {@link DpdkPollDistributionAnalysis} on-demand analysis
+ *
+ * @author Adel Belkhiri
+ */
+@SuppressWarnings("javadoc")
+public class Messages extends NLS {
+ private static final String BUNDLE_NAME = "org.eclipse.tracecompass.incubator.internal.dpdk.core.ethdev.poll.distribution.analysis.messages"; //$NON-NLS-1$
+
+ public static @Nullable String EthdevPollDistribution_AnalysisName;
+ public static @Nullable String EthdevPollDistribution_NumberOfPacketsLabel;
+ public static @Nullable String EthdevPollDistribution_CountLabel;
+
+ static @NonNull String getMessage(@Nullable String msg) {
+ if (msg == null) {
+ return ""; //$NON-NLS-1$
+ }
+ return msg;
+ }
+
+ 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/poll/distribution/analysis/messages.properties b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/poll/distribution/analysis/messages.properties
new file mode 100644
index 000000000..a9f707ec5
--- /dev/null
+++ b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/poll/distribution/analysis/messages.properties
@@ -0,0 +1,14 @@
+###############################################################################
+# 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
+###############################################################################
+
+EthdevPollDistribution_AnalysisName=DPDK Polls Distribution (ethdev)
+EthdevPollDistribution_NumberOfPacketsLabel=Number of retrieved packets
+EthdevPollDistribution_CountLabel=Count