diff --git a/tracetypes/org.eclipse.tracecompass.incubator.dpdk.core/plugin.xml b/tracetypes/org.eclipse.tracecompass.incubator.dpdk.core/plugin.xml index 276c5b45..f2445ba4 100644 --- a/tracetypes/org.eclipse.tracecompass.incubator.dpdk.core/plugin.xml +++ b/tracetypes/org.eclipse.tracecompass.incubator.dpdk.core/plugin.xml @@ -37,6 +37,10 @@ class="org.eclipse.tracecompass.incubator.internal.dpdk.core.ethdev.poll.distribution.analysis.DpdkPollDistributionAnalysis" id="org.eclipse.tracecompass.incubator.dpdk.core.ethdev.poll.distribution"> + + diff --git a/tracetypes/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/poll/stats/analysis/DpdkPollStatsAnalysis.java b/tracetypes/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/poll/stats/analysis/DpdkPollStatsAnalysis.java new file mode 100644 index 00000000..6c4d6c46 --- /dev/null +++ b/tracetypes/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/poll/stats/analysis/DpdkPollStatsAnalysis.java @@ -0,0 +1,262 @@ +/******************************************************************************* + * 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.stats.analysis; + +import java.text.NumberFormat; +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.Map.Entry; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.tracecompass.incubator.dpdk.core.trace.DpdkTrace; +import org.eclipse.tracecompass.incubator.internal.dpdk.core.ethdev.analysis.DpdkEthdevEventLayout; +import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiGenericAspect; +import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiTableEntryAspect; +import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiAnalysis; +import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiResultTable; +import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableClass; +import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry; +import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiData; +import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiDoubleNumber; +import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiLongNumber; +import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiTimeRange; +import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiTimestamp; +import org.eclipse.tracecompass.tmf.core.event.ITmfEvent; +import org.eclipse.tracecompass.tmf.core.request.ITmfEventRequest.ExecutionType; +import org.eclipse.tracecompass.tmf.core.request.TmfEventRequest; +import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange; +import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; + +/** + * The DPDK Polls Statistics Analysis is an on-demand analysis that computes + * statistics related to the polling of receive queues of Ethernet ports by PMD + * (Poll-Mode Driver) threads, through calls to `rte_eth_rx_burst()`. The + * statistics include, per queue and per thread, the minimum, maximum, average, + * and standard deviation of the number of packets retrieved in a single call to + * the `rte_eth_rx_burst()` API function. + * + * @author Adel Belkhiri + */ +public class DpdkPollStatsAnalysis extends LamiAnalysis { + + private static final long PROGRESS_INTERVAL = (1 << 10) - 1L; + private static final int MEMORY_SANITY_LIMIT = 40000; + /* Events layout */ + private final DpdkEthdevEventLayout fLayout = new DpdkEthdevEventLayout(); + + /** + * Constructor + */ + public DpdkPollStatsAnalysis() { + super(Objects.requireNonNull(Messages.EthdevPollStats_AnalysisName), false, trace -> 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(); + } + 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>> pollCountMap = new HashMap<>(); + TmfTimeRange adjustedTimeRange = timeRange == null ? TmfTimeRange.ETERNITY : timeRange; + SubMonitor subMonitor = SubMonitor.convert(monitor, Messages.EthdevPollStats_AnalysisName, workRemaining(trace)); + + // Create and send the event request + TmfEventRequest eventRequest = createEventRequest(trace, adjustedTimeRange, + pollCountMap, subMonitor, done); + trace.sendRequest(eventRequest); + + // Convert the results to LAMI tables + try { + eventRequest.waitForCompletion(); + return convertToLamiTables(adjustedTimeRange, pollCountMap); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return Collections.emptyList(); + } + } + + private TmfEventRequest createEventRequest(ITmfTrace trace, TmfTimeRange timeRange, 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 statistics + processEvent(event, pollAspectCounts); + + if ((nbProcessevents.incrementAndGet() & PROGRESS_INTERVAL) == 0) { + monitor.setWorkRemaining(workRemaining(trace)); + monitor.worked(1); + monitor.setTaskName(String.format("Dpdk Polls Statistics Analysis (%s events processed)", //$NON-NLS-1$ + NumberFormat.getInstance().format(nbProcessevents.get()))); + } + } + }; + } + + private void processEvent(ITmfEvent event, Map>> pollCountsMap) { + if (!event.getName().equals(fLayout.eventEthdevRxBurstNonEmpty())) { + return; + } + + Integer nbRxPkts = event.getContent().getFieldValue(Integer.class, fLayout.fieldNbRxPkts()); + Integer portId = event.getContent().getFieldValue(Integer.class, fLayout.fieldPortId()); + Integer queueId = event.getContent().getFieldValue(Integer.class, fLayout.fieldQueueId()); + String threadName = event.getContent().getFieldValue(String.class, fLayout.fieldThreadName()); + + if (nbRxPkts == null || portId == null || queueId == null || threadName == null) { + return; + } + + // Update the poll count for queues + String queueName = "P" + portId + "/Q" + queueId; //$NON-NLS-1$ //$NON-NLS-2$ + updatePollCountsMap(pollCountsMap, Objects.requireNonNull(Messages.EthdevPollStats_QueueLabel), queueName, nbRxPkts); + + // Update the poll count for threads + updatePollCountsMap(pollCountsMap, Objects.requireNonNull(Messages.EthdevPollStats_ThreadLabel), threadName, nbRxPkts); + } + + private static void updatePollCountsMap(Map>> pollCountsMap, String aspectName, String key, Integer nbRxPkts) { + Map> dataSet = pollCountsMap.computeIfAbsent(aspectName, unused -> new HashMap<>()); + if (dataSet.size() < MEMORY_SANITY_LIMIT) { + List data = dataSet.computeIfAbsent(key, unused -> new ArrayList<>()); + data.add(nbRxPkts); + } + } + + private List convertToLamiTables(TmfTimeRange timeRange, + Map>> pollAspectCounts) { + List results = new ArrayList<>(); + for (Entry>> entry : pollAspectCounts.entrySet()) { + + Map> dataSet = Objects.requireNonNull(entry.getValue()); + List entries = new ArrayList<>(); + + for (Entry> element : dataSet.entrySet()) { + + List pollValues = Objects.requireNonNull(element.getValue()); + /* + * Calculate the number of successful polls, along with the + * minimum and maximum polls values + */ + int nbSuccessfulPolls = pollValues.size(); + int minPollValue = Collections.min(element.getValue()); + int maxPollValue = Collections.max(element.getValue()); + + /* + * Calculate the mean and the standard deviation + */ + double avgPollValue = pollValues.stream().mapToInt(i -> i).average().orElse(0); + double sd = pollValues.stream().mapToDouble(val -> Math.pow(val - avgPollValue, 2)).sum(); + double std = Math.sqrt(sd / pollValues.size()); + double stdRounded = Math.round(std * 100.0) / 100.0; + + List data = Arrays.asList( + new LamiString(element.getKey()), + new LamiLongNumber((long) minPollValue), + new LamiLongNumber((long) maxPollValue), + new LamiLongNumber((long) avgPollValue), + new LamiDoubleNumber(stdRounded), + new LamiLongNumber((long) nbSuccessfulPolls)); + + entries.add(new LamiTableEntry(data)); + } + + List tableAspects = Arrays.asList(new LamiCategoryAspect(entry.getKey(), 0), + new LamiCountAspect(Objects.requireNonNull(Messages.EthdevPollStats_MinimumValueLabel), 1), + new LamiCountAspect(Objects.requireNonNull(Messages.EthdevPollStats_MaximumValueLabel), 2), + new LamiCountAspect(Objects.requireNonNull(Messages.EthdevPollStats_AverageValueLabel), 3), + new LamiCountAspect(Objects.requireNonNull(Messages.EthdevPollStats_StandardDeviationLabel), 4), + new LamiCountAspect(Objects.requireNonNull(Messages.EthdevPollStats_CountLabel), 5)); + LamiTableClass tableClass = new LamiTableClass(entry.getKey(), entry.getKey(), tableAspects, Collections.emptySet()); + LamiResultTable lrt = new LamiResultTable(createTimeRange(timeRange), tableClass, entries); + results.add(lrt); + } + return results; + } + + /** + * TODO: move to LAMI + */ + private static LamiTimeRange createTimeRange(TmfTimeRange timeRange) { + return new LamiTimeRange(new LamiTimestamp(timeRange.getStartTime().toNanos()), new LamiTimestamp(timeRange.getEndTime().toNanos())); + } + + /** + * TODO: move to LAMI + */ + private final class LamiString extends LamiData { + private final String fElement; + + private LamiString(String element) { + fElement = element; + } + + @Override + public @Nullable String toString() { + return fElement; + } + } + + /** + * Count aspect, generic + * + * TODO: move to LAMI + * + */ + private final class LamiCountAspect extends LamiGenericAspect { + + private LamiCountAspect(String name, int column) { + super(name, null, column, true, false); + } + } + + /** + * Category aspect, generic + * + * TODO: move to LAMI + * + */ + private final class LamiCategoryAspect extends LamiGenericAspect { + + private LamiCategoryAspect(String name, int column) { + super(name, null, column, false, false); + } + } +} diff --git a/tracetypes/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/poll/stats/analysis/Messages.java b/tracetypes/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/poll/stats/analysis/Messages.java new file mode 100644 index 00000000..95665385 --- /dev/null +++ b/tracetypes/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/poll/stats/analysis/Messages.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * 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.stats.analysis; + +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.osgi.util.NLS; + +/** + * Messages for the {@link DpdkPollStatsAnalysis} 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.stats.analysis.messages"; //$NON-NLS-1$ + + public static @Nullable String EthdevPollStats_AnalysisName; + public static @Nullable String EthdevPollStats_QueueLabel; + public static @Nullable String EthdevPollStats_ThreadLabel; + + public static @Nullable String EthdevPollStats_MinimumValueLabel; + public static @Nullable String EthdevPollStats_MaximumValueLabel; + public static @Nullable String EthdevPollStats_AverageValueLabel; + public static @Nullable String EthdevPollStats_StandardDeviationLabel; + public static @Nullable String EthdevPollStats_CountLabel; + + static { + // initialize resource bundle + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } + + private Messages() { + // do nothing + } +} diff --git a/tracetypes/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/poll/stats/analysis/messages.properties b/tracetypes/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/poll/stats/analysis/messages.properties new file mode 100644 index 00000000..7b185ff9 --- /dev/null +++ b/tracetypes/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/poll/stats/analysis/messages.properties @@ -0,0 +1,19 @@ +############################################################################### +# 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 +############################################################################### + +EthdevPollStats_AnalysisName=DPDK Polls Statistics (ethdev) +EthdevPollStats_QueueLabel=Port Queue +EthdevPollStats_ThreadLabel=PMD Thread +EthdevPollStats_MinimumValueLabel=Minimum Value +EthdevPollStats_MaximumValueLabel=Maximum Value +EthdevPollStats_AverageValueLabel=Average Value +EthdevPollStats_StandardDeviationLabel=Standard Deviation +EthdevPollStats_CountLabel=Count diff --git a/tracetypes/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/poll/stats/analysis/package-info.java b/tracetypes/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/poll/stats/analysis/package-info.java new file mode 100644 index 00000000..70e721ea --- /dev/null +++ b/tracetypes/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/poll/stats/analysis/package-info.java @@ -0,0 +1,12 @@ +/********************************************************************** + * 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.poll.stats.analysis;