Skip to content

Commit

Permalink
dpdk: add busyness analysis for ethdev PMD threads
Browse files Browse the repository at this point in the history
PMD threads continuously poll NIC queues, leading to constant 100% CPU usage.
This analysis provides a rough estimation of how busy PMD threads are with
actual work, by comparing the times they successfully retrieve packets from the
NIC queue versus the times they are merely spinning without processing any
packets.

Signed-off-by: Adel Belkhiri <[email protected]>
  • Loading branch information
adel-belkhiri committed Oct 15, 2024
1 parent 9ff5ded commit 759d826
Show file tree
Hide file tree
Showing 17 changed files with 916 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions analyses/org.eclipse.tracecompass.incubator.dpdk.core/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@
class="org.eclipse.tracecompass.incubator.dpdk.core.trace.DpdkTrace">
</tracetype>
</module>
<module
analysis_module="org.eclipse.tracecompass.incubator.internal.dpdk.core.ethdev.spin.analysis.DpdkEthdevSpinAnalysisModule"
id="org.eclipse.tracecompass.incubator.internal.dpdk.core.ethdev.spin.analysis"
name="DPDK Ethernet Spin Analysis">
<tracetype
applies="true"
class="org.eclipse.tracecompass.incubator.dpdk.core.trace.DpdkTrace">
</tracetype>
</module>
</extension>
<extension
point="org.eclipse.tracecompass.tmf.core.dataprovider">
Expand All @@ -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">
</dataProviderFactory>
<dataProviderFactory
class="org.eclipse.tracecompass.incubator.internal.dpdk.core.ethdev.spin.analysis.DpdkEthdevSpinDataProviderFactory"
id="org.eclipse.tracecompass.incubator.internal.dpdk.core.ethdev.spin.dataprovider">
</dataProviderFactory>
</extension>
<extension
point="org.eclipse.linuxtools.tmf.core.tracetype">
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<TmfAbstractAnalysisRequirement> 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<String, Pair<Long, Long>> getThreadUsageInRange(Set<@NonNull Integer> threads, long start, long end) {
Map<String, Pair<Long, Long>> 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;
}
}
Loading

0 comments on commit 759d826

Please sign in to comment.