Skip to content

Commit d2f9125

Browse files
committed
dpdk: add packet distribution statistics analysis
Introduce packet distribution statistics analysis for PMD threads in the ethdev library. This analysis calculates various statistics related to the distribution of packets retrieved in a single rte_eth_rx_burst() call, on a per-thread and per-queue basis. The computed statistics include the minimum, maximum, average number of packets retrieved, as well as the standard deviation. Signed-off-by: Adel Belkhiri <adel.belkhiri@gmail.com>
1 parent 1a11873 commit d2f9125

File tree

4 files changed

+334
-0
lines changed

4 files changed

+334
-0
lines changed

analyses/org.eclipse.tracecompass.incubator.dpdk.core/plugin.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@
3737
class="org.eclipse.tracecompass.incubator.internal.dpdk.core.ethdev.poll.distribution.analysis.DpdkPollDistributionAnalysis"
3838
id="org.eclipse.tracecompass.incubator.dpdk.core.ethdev.poll.distribution">
3939
</analysis>
40+
<analysis
41+
class="org.eclipse.tracecompass.incubator.internal.dpdk.core.ethdev.poll.stats.analysis.DpdkPollStatsAnalysis"
42+
id="org.eclipse.tracecompass.incubator.dpdk.core.ethdev.poll.stats">
43+
</analysis>
4044
</extension>
4145
<extension
4246
point="org.eclipse.tracecompass.tmf.core.dataprovider">
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 École Polytechnique de Montréal
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License 2.0 which
6+
* accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*******************************************************************************/
11+
12+
package org.eclipse.tracecompass.incubator.internal.dpdk.core.ethdev.poll.stats.analysis;
13+
14+
import java.math.BigDecimal;
15+
import java.math.RoundingMode;
16+
import java.text.NumberFormat;
17+
import java.util.ArrayList;
18+
import java.util.Arrays;
19+
import java.util.Collections;
20+
import java.util.HashMap;
21+
import java.util.List;
22+
import java.util.Map;
23+
import java.util.Map.Entry;
24+
import java.util.concurrent.atomic.AtomicLong;
25+
26+
import org.eclipse.core.runtime.CoreException;
27+
import org.eclipse.core.runtime.IProgressMonitor;
28+
import org.eclipse.core.runtime.SubMonitor;
29+
import org.eclipse.jdt.annotation.NonNull;
30+
import org.eclipse.jdt.annotation.Nullable;
31+
import org.eclipse.tracecompass.incubator.dpdk.core.trace.DpdkTrace;
32+
import org.eclipse.tracecompass.incubator.internal.dpdk.core.analysis.DpdkEthdevEventLayout;
33+
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiGenericAspect;
34+
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiTableEntryAspect;
35+
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiAnalysis;
36+
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiResultTable;
37+
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableClass;
38+
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry;
39+
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiData;
40+
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiDoubleNumber;
41+
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiLongNumber;
42+
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiTimeRange;
43+
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiTimestamp;
44+
import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
45+
import org.eclipse.tracecompass.tmf.core.request.ITmfEventRequest.ExecutionType;
46+
import org.eclipse.tracecompass.tmf.core.request.TmfEventRequest;
47+
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
48+
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
49+
50+
/**
51+
* The DPDK Polls Statistics Analysis is an on-demand analysis that computes
52+
* statistics related to the polling of receive queues of Ethernet ports by PMD
53+
* (Poll-Mode Driver) threads, through calls to `rte_eth_rx_burst()`. The
54+
* statistics include, per queue and per thread, the minimum, maximum, average,
55+
* and standard deviation of the number of packets retrieved in a single call to
56+
* the `rte_eth_rx_burst()` API function.
57+
*
58+
* @author Adel Belkhiri
59+
*/
60+
public class DpdkPollStatsAnalysis extends LamiAnalysis {
61+
62+
private static final long PROGRESS_INTERVAL = (1 << 10) - 1L;
63+
private static final int MEMORY_SANITY_LIMIT = 40000;
64+
65+
/**
66+
* Constructor
67+
*/
68+
public DpdkPollStatsAnalysis() {
69+
super(Messages.getMessage(Messages.EthdevPollStats_AnalysisName), false, trace -> true, Collections.emptyList());
70+
}
71+
72+
@Override
73+
protected synchronized void initialize() {
74+
// do nothing
75+
}
76+
77+
@Override
78+
public boolean canExecute(ITmfTrace trace) {
79+
if (trace instanceof DpdkTrace) {
80+
return ((DpdkTrace) trace).validate(null, trace.getPath()).isOK() ? true : false;
81+
}
82+
return false;
83+
}
84+
85+
private static int workRemaining(ITmfTrace trace) {
86+
return (int) Math.min(trace.getNbEvents() / (PROGRESS_INTERVAL + 1), Integer.MAX_VALUE);
87+
}
88+
89+
@Override
90+
public List<LamiResultTable> execute(ITmfTrace trace, @Nullable TmfTimeRange timeRange, String extraParamsString, IProgressMonitor monitor) throws CoreException {
91+
AtomicLong done = new AtomicLong();
92+
Map<@NonNull String, Map<@NonNull String, List<Integer>>> pollCountMap = new HashMap<>();
93+
TmfTimeRange adjustedTimeRange = timeRange == null ? TmfTimeRange.ETERNITY : timeRange;
94+
SubMonitor subMonitor = SubMonitor.convert(monitor, Messages.EthdevPollStats_AnalysisName, workRemaining(trace));
95+
96+
// create and send the event request
97+
TmfEventRequest eventRequest = createEventRequest(trace, adjustedTimeRange,
98+
pollCountMap, subMonitor, done);
99+
trace.sendRequest(eventRequest);
100+
101+
// convert the results to LAMI tables
102+
try {
103+
eventRequest.waitForCompletion();
104+
return convertToLamiTables(adjustedTimeRange, pollCountMap);
105+
} catch (InterruptedException e) {
106+
Thread.currentThread().interrupt();
107+
return Collections.emptyList();
108+
}
109+
}
110+
111+
private static TmfEventRequest createEventRequest(ITmfTrace trace, TmfTimeRange timeRange, Map<@NonNull String, Map<@NonNull String, List<Integer>>> pollAspectCounts, SubMonitor monitor, AtomicLong nbProcessevents) {
112+
return new TmfEventRequest(ITmfEvent.class, timeRange, 0, Integer.MAX_VALUE, ExecutionType.BACKGROUND) {
113+
@Override
114+
public void handleData(ITmfEvent event) {
115+
if (monitor.isCanceled()) {
116+
cancel();
117+
return;
118+
}
119+
120+
// process events to compute RX polls statistics
121+
processEvent(event, pollAspectCounts);
122+
123+
if ((nbProcessevents.incrementAndGet() & PROGRESS_INTERVAL) == 0) {
124+
monitor.setWorkRemaining(workRemaining(trace));
125+
monitor.worked(1);
126+
monitor.setTaskName(String.format("Dpdk Polls Statistics Analysis (%s events processed)", //$NON-NLS-1$
127+
NumberFormat.getInstance().format(nbProcessevents.get())));
128+
}
129+
}
130+
};
131+
}
132+
133+
private static void processEvent(ITmfEvent event, Map<@NonNull String, Map<@NonNull String, List<Integer>>> pollCountsMap) {
134+
if (!event.getName().equals(DpdkEthdevEventLayout.eventEthdevRxBurstNonEmpty())) {
135+
return;
136+
}
137+
138+
Integer nbRxPkts = event.getContent().getFieldValue(Integer.class, DpdkEthdevEventLayout.fieldNbRxPkts());
139+
Integer portId = event.getContent().getFieldValue(Integer.class, DpdkEthdevEventLayout.fieldPortId());
140+
Integer queueId = event.getContent().getFieldValue(Integer.class, DpdkEthdevEventLayout.fieldQueueId());
141+
String threadName = event.getContent().getFieldValue(String.class, DpdkEthdevEventLayout.fieldThreadName());
142+
143+
if (nbRxPkts == null || portId == null || queueId == null || threadName == null) {
144+
return;
145+
}
146+
147+
// update the poll count from a queue perspective
148+
String queueName = "P" + portId + "/Q" + queueId; //$NON-NLS-1$ //$NON-NLS-2$
149+
updatePollCountsMap(pollCountsMap, Messages.getMessage(Messages.EthdevPollStats_QueueLabel), queueName, nbRxPkts);
150+
151+
// update the poll count from a thread perspective
152+
updatePollCountsMap(pollCountsMap, Messages.getMessage(Messages.EthdevPollStats_ThreadLabel), threadName, nbRxPkts);
153+
}
154+
155+
private static void updatePollCountsMap(Map<@NonNull String, Map<@NonNull String, List<Integer>>> pollCountsMap, @NonNull String aspectName, @NonNull String key, Integer nbRxPkts) {
156+
Map<@NonNull String, List<Integer>> dataSet = pollCountsMap.computeIfAbsent(aspectName, unused -> new HashMap<>());
157+
if (dataSet.size() < MEMORY_SANITY_LIMIT) {
158+
List<Integer> data = dataSet.computeIfAbsent(key, unused -> new ArrayList<>());
159+
data.add(nbRxPkts);
160+
}
161+
}
162+
163+
private @NonNull List<LamiResultTable> convertToLamiTables(TmfTimeRange timeRange,
164+
Map<@NonNull String, Map<@NonNull String, List<Integer>>> pollAspectCounts) {
165+
List<LamiResultTable> results = new ArrayList<>();
166+
for (Entry<@NonNull String, Map<@NonNull String, List<Integer>>> entry : pollAspectCounts.entrySet()) {
167+
168+
Map<@NonNull String, List<Integer>> dataSet = entry.getValue();
169+
List<LamiTableEntry> entries = new ArrayList<>();
170+
171+
for (Entry<@NonNull String, List<Integer>> element : dataSet.entrySet()) {
172+
/*
173+
* Calculate the number of successful polls, along with the
174+
* minimum and maximum polls values
175+
*/
176+
int nbSuccessfulPolls = element.getValue().size();
177+
int minPollValue = Collections.min(element.getValue());
178+
int maxPollValue = Collections.max(element.getValue());
179+
180+
// calculate the mean and the standard deviation
181+
double avgPollValue = element.getValue().stream().mapToInt(i -> i).average().orElse(0);
182+
double sd = element.getValue().stream().mapToDouble(val -> Math.pow(val - avgPollValue, 2)).sum();
183+
double std = Math.sqrt(sd / element.getValue().size());
184+
185+
BigDecimal bd = new BigDecimal(std).setScale(2, RoundingMode.HALF_UP);
186+
double rounded = bd.doubleValue();
187+
188+
List<@NonNull LamiData> data = Arrays.asList(
189+
new LamiString(element.getKey()),
190+
new LamiLongNumber((long) minPollValue),
191+
new LamiLongNumber((long) maxPollValue),
192+
new LamiLongNumber((long) avgPollValue),
193+
new LamiDoubleNumber(rounded),
194+
new LamiLongNumber((long) nbSuccessfulPolls));
195+
196+
entries.add(new LamiTableEntry(data));
197+
}
198+
199+
List<@NonNull LamiTableEntryAspect> tableAspects = Arrays.asList(new LamiCategoryAspect(entry.getKey(), 0),
200+
new LamiCountAspect(Messages.EthdevPollStats_MinimumValueLabel, 1),
201+
new LamiCountAspect(Messages.EthdevPollStats_MaximumValueLabel, 2),
202+
new LamiCountAspect(Messages.EthdevPollStats_AverageValueLabel, 3),
203+
new LamiCountAspect(Messages.EthdevPollStats_StandardDeviationLabel, 4),
204+
new LamiCountAspect(Messages.EthdevPollStats_CountLabel, 5));
205+
LamiTableClass tableClass = new LamiTableClass(entry.getKey(), entry.getKey(), tableAspects, Collections.emptySet());
206+
LamiResultTable lrt = new LamiResultTable(createTimeRange(timeRange), tableClass, entries);
207+
results.add(lrt);
208+
}
209+
return results;
210+
}
211+
212+
/**
213+
* Todo, move to LAMI
214+
*/
215+
private static LamiTimeRange createTimeRange(TmfTimeRange timeRange) {
216+
return new LamiTimeRange(new LamiTimestamp(timeRange.getStartTime().toNanos()), new LamiTimestamp(timeRange.getEndTime().toNanos()));
217+
}
218+
219+
/**
220+
* Todo, move to LAMI
221+
*/
222+
private final class LamiString extends LamiData {
223+
private final String fElement;
224+
225+
private LamiString(@NonNull String element) {
226+
fElement = element;
227+
}
228+
229+
@Override
230+
public @NonNull String toString() {
231+
return fElement;
232+
}
233+
}
234+
235+
/**
236+
* Count aspect, generic
237+
*
238+
* TODO: move to LAMI
239+
*
240+
*/
241+
private final class LamiCountAspect extends LamiGenericAspect {
242+
243+
private LamiCountAspect(String name, int column) {
244+
super(name, null, column, true, false);
245+
}
246+
}
247+
248+
/**
249+
* Category aspect, generic
250+
*
251+
* TODO: move to LAMI
252+
*
253+
*/
254+
private final class LamiCategoryAspect extends LamiGenericAspect {
255+
256+
private LamiCategoryAspect(String name, int column) {
257+
super(name, null, column, false, false);
258+
}
259+
}
260+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 École Polytechnique de Montréal
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License 2.0 which
6+
* accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*******************************************************************************/
11+
12+
package org.eclipse.tracecompass.incubator.internal.dpdk.core.ethdev.poll.stats.analysis;
13+
14+
import org.eclipse.jdt.annotation.NonNull;
15+
import org.eclipse.jdt.annotation.Nullable;
16+
import org.eclipse.osgi.util.NLS;
17+
18+
/**
19+
* Messages for the {@link DpdkPollStatsAnalysis} on-demand analysis
20+
*
21+
* @author Adel Belkhiri
22+
*/
23+
@SuppressWarnings("javadoc")
24+
public class Messages extends NLS {
25+
private static final String BUNDLE_NAME = "org.eclipse.tracecompass.incubator.internal.dpdk.core.ethdev.poll.stats.analysis.messages"; //$NON-NLS-1$
26+
27+
public static @Nullable String EthdevPollStats_AnalysisName;
28+
public static @Nullable String EthdevPollStats_QueueLabel;
29+
public static @Nullable String EthdevPollStats_ThreadLabel;
30+
31+
public static @Nullable String EthdevPollStats_MinimumValueLabel;
32+
public static @Nullable String EthdevPollStats_MaximumValueLabel;
33+
public static @Nullable String EthdevPollStats_AverageValueLabel;
34+
public static @Nullable String EthdevPollStats_StandardDeviationLabel;
35+
public static @Nullable String EthdevPollStats_CountLabel;
36+
37+
static @NonNull String getMessage(@Nullable String msg) {
38+
if (msg == null) {
39+
return ""; //$NON-NLS-1$
40+
}
41+
return msg;
42+
}
43+
44+
static {
45+
// initialize resource bundle
46+
NLS.initializeMessages(BUNDLE_NAME, Messages.class);
47+
}
48+
49+
private Messages() {
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
###############################################################################
2+
# Copyright (c) 2024 École Polytechnique de Montréal
3+
#
4+
# All rights reserved. This program and the accompanying materials
5+
# are made available under the terms of the Eclipse Public License 2.0
6+
# which accompanies this distribution, and is available at
7+
# https://www.eclipse.org/legal/epl-2.0
8+
#
9+
# SPDX-License-Identifier: EPL-2.0
10+
###############################################################################
11+
12+
EthdevPollStats_AnalysisName=DPDK Polls Statistics (ethdev)
13+
EthdevPollStats_QueueLabel=Port Queue
14+
EthdevPollStats_ThreadLabel=PMD Thread
15+
EthdevPollStats_MinimumValueLabel=Minimum Value
16+
EthdevPollStats_MaximumValueLabel=Maximum Value
17+
EthdevPollStats_AverageValueLabel=Average Value
18+
EthdevPollStats_StandardDeviationLabel=Standard Deviation
19+
EthdevPollStats_CountLabel=Count

0 commit comments

Comments
 (0)