diff --git a/analyses/org.eclipse.tracecompass.incubator.dpdk.core/plugin.xml b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/plugin.xml index 560ab331..1d1767a9 100644 --- a/analyses/org.eclipse.tracecompass.incubator.dpdk.core/plugin.xml +++ b/analyses/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/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/poll/stats/analysis/DpdkPollStatsAnalysis.java b/analyses/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..f2f69632 --- /dev/null +++ b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/poll/stats/analysis/DpdkPollStatsAnalysis.java @@ -0,0 +1,260 @@ +/******************************************************************************* + * 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.math.BigDecimal; +import java.math.RoundingMode; +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.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.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.tracecompass.incubator.dpdk.core.trace.DpdkTrace; +import org.eclipse.tracecompass.incubator.internal.dpdk.core.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; + + /** + * Constructor + */ + public DpdkPollStatsAnalysis() { + super(Messages.getMessage(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() ? 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<@NonNull String, Map<@NonNull String, List>> 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 static TmfEventRequest createEventRequest(ITmfTrace trace, TmfTimeRange timeRange, Map<@NonNull String, Map<@NonNull String, List>> 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 static void processEvent(ITmfEvent event, Map<@NonNull String, Map<@NonNull String, List>> pollCountsMap) { + if (!event.getName().equals(DpdkEthdevEventLayout.eventEthdevRxBurstNonEmpty())) { + return; + } + + 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()); + String threadName = event.getContent().getFieldValue(String.class, DpdkEthdevEventLayout.fieldThreadName()); + + if (nbRxPkts == null || portId == null || queueId == null || threadName == null) { + return; + } + + // update the poll count from a queue perspective + String queueName = "P" + portId + "/Q" + queueId; //$NON-NLS-1$ //$NON-NLS-2$ + updatePollCountsMap(pollCountsMap, Messages.getMessage(Messages.EthdevPollStats_QueueLabel), queueName, nbRxPkts); + + // update the poll count from a thread perspective + updatePollCountsMap(pollCountsMap, Messages.getMessage(Messages.EthdevPollStats_ThreadLabel), threadName, nbRxPkts); + } + + private static void updatePollCountsMap(Map<@NonNull String, Map<@NonNull String, List>> pollCountsMap, @NonNull String aspectName, @NonNull String key, Integer nbRxPkts) { + Map<@NonNull String, List> 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 @NonNull List convertToLamiTables(TmfTimeRange timeRange, + Map<@NonNull String, Map<@NonNull String, List>> pollAspectCounts) { + List results = new ArrayList<>(); + for (Entry<@NonNull String, Map<@NonNull String, List>> entry : pollAspectCounts.entrySet()) { + + Map<@NonNull String, List> dataSet = entry.getValue(); + List entries = new ArrayList<>(); + + for (Entry<@NonNull String, List> element : dataSet.entrySet()) { + /* + * Calculate the number of successful polls, along with the + * minimum and maximum polls values + */ + int nbSuccessfulPolls = element.getValue().size(); + int minPollValue = Collections.min(element.getValue()); + int maxPollValue = Collections.max(element.getValue()); + + // calculate the mean and the standard deviation + double avgPollValue = element.getValue().stream().mapToInt(i -> i).average().orElse(0); + double sd = element.getValue().stream().mapToDouble(val -> Math.pow(val - avgPollValue, 2)).sum(); + double std = Math.sqrt(sd / element.getValue().size()); + + BigDecimal bd = new BigDecimal(std).setScale(2, RoundingMode.HALF_UP); + double rounded = bd.doubleValue(); + + List<@NonNull LamiData> data = Arrays.asList( + new LamiString(element.getKey()), + new LamiLongNumber((long) minPollValue), + new LamiLongNumber((long) maxPollValue), + new LamiLongNumber((long) avgPollValue), + new LamiDoubleNumber(rounded), + new LamiLongNumber((long) nbSuccessfulPolls)); + + entries.add(new LamiTableEntry(data)); + } + + List<@NonNull LamiTableEntryAspect> tableAspects = Arrays.asList(new LamiCategoryAspect(entry.getKey(), 0), + new LamiCountAspect(Messages.EthdevPollStats_MinimumValueLabel, 1), + new LamiCountAspect(Messages.EthdevPollStats_MaximumValueLabel, 2), + new LamiCountAspect(Messages.EthdevPollStats_AverageValueLabel, 3), + new LamiCountAspect(Messages.EthdevPollStats_StandardDeviationLabel, 4), + new LamiCountAspect(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(@NonNull String element) { + fElement = element; + } + + @Override + public @NonNull 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/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/poll/stats/analysis/Messages.java b/analyses/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..f7a2472a --- /dev/null +++ b/analyses/org.eclipse.tracecompass.incubator.dpdk.core/src/org/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/poll/stats/analysis/Messages.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * 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.NonNull; +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 @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/stats/analysis/messages.properties b/analyses/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/analyses/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