-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
dpdk: add busyness analysis for ethdev PMD threads
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
1 parent
9ff5ded
commit 759d826
Showing
17 changed files
with
916 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
...rg/eclipse/tracecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/Attributes.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"; | ||
} |
79 changes: 79 additions & 0 deletions
79
...racecompass/incubator/internal/dpdk/core/ethdev/spin/analysis/DpdkEthdevEventHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
154 changes: 154 additions & 0 deletions
154
...mpass/incubator/internal/dpdk/core/ethdev/spin/analysis/DpdkEthdevSpinAnalysisModule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
Oops, something went wrong.