Skip to content
LinkState
Go back

Microbursts, NAPI Budget, and Fast-Path Tradeoffs

Introduction to NAPI Budget Tuning and XDP

Overview of NAPI and XDP

The Linux networking stack uses NAPI (New API) to transition from interrupt‑driven to polling‑driven packet reception when the NIC reports a high rate of incoming frames. When an interrupt occurs, the kernel disables further interrupts for that queue and enters a NAPI poll loop, which repeatedly calls the driver’s poll method until either the budget is exhausted or no more packets are available. The two key knobs that govern this loop are:

XDP (eXpress Data Path) attaches a BPF program at the very earliest point in the receive path, right after the NIC DMA places a packet into the descriptor ring but before the kernel allocates an skb. XDP programs can drop, redirect, or pass the packet up the stack with virtually no per‑packet overhead beyond the BPF instruction count.

Importance of Benchmarking under Microburst Traffic

Microbursts are short, intense spikes of packet arrival that can exceed the average line rate by an order of magnitude for a few microseconds to milliseconds. They stress the transient buffering capacity of NIC rings, the NAPI polling mechanism, and any inline BPF hooks. Understanding how NAPI budget, busy‑poll, and hook placement interact under such traffic is essential for:

A repeatable benchmark that measures ring occupancy, softirq time, backlog growth, and loss onset under controlled microburst patterns provides the evidence needed to tune these knobs safely.


NAPI Budget Tuning

Understanding NAPI Budget Parameters

ParameterLocationUnitDefaultMeaning
net.core.netdev_budget/proc/sys/net/core/netdev_budgetpackets300Max packets processed per NAPI poll cycle.
net.core.netdev_budget_usecs/proc/sys/net/core/netdev_budget_usecsµs2000Max time a NAPI poll may run before yielding.
net.dev_weight (per‑queue)/sys/class/net/<dev>/queues/rx-*/weightpacketsinherited from netdev_budgetAllows per‑queue override (rarely used).

When the NIC raises an interrupt, the kernel disables further interrupts and enters the NAPI loop. The loop continues until either:

If the budget is too low, the kernel will exit NAPI early, re‑enable interrupts, and potentially incur many interrupts per second, increasing softirq overhead. If the budget is too high, a single NAPI poll may monopolize the CPU, delaying other softirqs or tasks and increasing latency.

Impact of NAPI Budget on Network Performance

Under microburst traffic, the NIC ring can fill faster than the application can drain it. A larger NAPI budget allows the kernel to pull more packets per poll, reducing the chance that the ring overflows before the next poll. However, if the application’s receive path (e.g., socket queue) is the bottleneck, pulling more packets merely increases backlog in the kernel’s skb queues, which may manifest as increased rx_drops or rx_over_errors.

Code Examples for NAPI Budget Tuning

# View current settings
sysctl net.core.netdev_budget
sysctl net.core.netdev_budget_usecs

# Temporarily increase budget to 1024 packets and 5000 µs
sysctl -w net.core.netdev_budget=1024
sysctl -w net.core.netdev_budget_usecs=5000

# Make persistent across reboots (Debian/Ubuntu)
echo "net.core.netdev_budget=1024" >> /etc/sysctl.d/99-napi.conf
echo "net.core.netdev_budget_usecs=5000" >> /etc/sysctl.d/99-napi.conf
sysctl --system

# Per‑queue override (if NIC supports multiple rx queues)
for q in /sys/class/net/eth0/queues/rx-*; do
    echo 512 > $q/weight   # sets netdev_budget for that queue
done

When testing, it is useful to capture the number of NAPI polls executed and the average packets per poll:

# Show NAPI statistics (requires kernel >= 4.14)
cat /proc/net/softnet_stat
# Columns: processed, dropped, time_squeeze, ... (see Documentation/networking/softnet-stat.txt)

Busy-Poll Choices and Their Impact

Busy-Poll Mechanism Overview

Busy‑poll is an optimization that allows a socket (or the entire NIC) to poll for packets in a tight loop instead of relying on interrupts or NAPI. When enabled, the kernel repeatedly invokes the driver’s poll method in a loop, consuming CPU cycles but potentially reducing latency and avoiding softirq context switches.

Key sysctls:

When busy_poll > 0, the kernel will spin for up to that many microseconds before giving up and either blocking (if the socket is blocking) or returning EAGAIN. The spin occurs in softirq context, so it adds to the softirq CPU utilization measured in /proc/stat.

Effects of Busy-Poll on CPU Utilization and Packet Processing

CLI Examples for Configuring Busy-Poll

# View current global busy-poll settings
sysctl net.core.busy_poll
sysctl net.core.busy_read

# Set a global busy-poll timeout of 50 µs (useful for low‑latency UDP recv)
sysctl -w net.core.busy_poll=50
sysctl -w net.core.busy_read=50

# Make persistent
echo "net.core.busy_poll=50" >> /etc/sysctl.d/99-busypoll.conf
echo "net.core.busy_read=50" >> /etc/sysctl.d/99-busypoll.conf
sysctl --system

# Per‑socket example (C snippet)
int val = 50; // 50 µs
setsockopt(sockfd, SOL_SOCKET, SO_BUSY_POLL, &val, sizeof(val));

# Verify effect via /proc/net/softnet_stat (look for "poll" column increase)
watch -n 0.1 cat /proc/net/softnet_stat

When benchmarking, capture the CPU time spent in softirq due to busy‑poll:

# Measure softirq time before and after enabling busy-poll
pidstat -u -p ALL 1  # look for %system (softirq) column
# Or use perf:
perf stat -e cycles,instructions,cpu-clock,task-clock -a sleep 10

Hook Placement Between XDP and tc

XDP and tc Overview

Hook Placement Strategies for Optimal Performance

PlacementWhen to useEffect on packet pathTypical cost per packet
XDP native (driver)Early drop, forwarding, or load‑balancing where you want to avoid skb allocation entirely.Packet never becomes an skb if XDP returns XDP_DROP or XDP_REDIRECT.~50–150 ns (depends on BPF instruction count).
XDP genericWhen you need XDP features but the NIC does not support native XDP, or you want to keep the driver unchanged.Packet is still turned into an skb before XDP runs.~150–300 ns (extra skb allocation + XDP).
tc (tc ingress)Complex processing that needs full skb metadata (e.g., L4 parsing, connection tracking).Runs after skb allocation, before IP stack.~200–500 ns (depends on BPF complexity).
tc egressOutbound shaping, marking, or load‑balancing after routing decisions.After routing, before NIC transmission.Similar to ingress tc.

Microburst considerations:

Code Snippets for Implementing XDP and tc Hooks

# 1. Load an XDP native program (assumes NIC supports it)
ip link set dev eth0 xdp obj /opt/xdp_drop.o sec xdp

# 2. Verify XDP attachment
ip -d link show eth0 | grep xdp
# Expected output: xdp ... id 123

# 3. Load a tc ingress BPF program (after XDP)
tc qdisc add dev eth0 clsact
tc filter add dev eth0 ingress bpf da obj /opt/tc_count.o sec tc

# 4. Verify tc filter
tc filter show dev eth0 ingress

# 5. Remove both when done
ip link set dev eth0 xdp off
tc qdisc del dev eth0 clsact

Example XDP drop program (C, compiled with clang -O2 -target bpf):

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

SEC("xdp")
int xdp_drop(struct xdp_md *ctx) {
    return XDP_DROP;   // drop every packet
}
char _license[] SEC("license") = "GPL";

Example tc ingress packet counter (C):

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

struct {
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __uint(max_entries, 1);
    __type(key, __u32);
    __type(value, __u64);
} pkt_cnt SEC(".maps");

SEC("tc")
int tc_count(struct __sk_buff *skb) {
    __u32 idx = 0;
    __u64 *val = bpf_map_lookup_elem(&pkt_cnt, &idx);
    if (val)
        __sync_fetch_and_add(val, 1);
    return TC_ACT_OK;
}
char _license[] SEC("license") = "GPL";

When benchmarking, you can toggle each hook independently to measure its contribution to CPU usage and packet loss.


Benchmarking Under Microburst Traffic

Microburst Traffic Generation Tools and Techniques

Measurement Methodology

  1. Configure the NIC – Set desired NAPI budget, busy‑poll timeout, and attach/detach XDP/tc hooks as needed.
  2. Warm‑up – Run a low‑rate background traffic flow to stabilize NIC rings and kernel structures.
  3. Inject microbursts – Use pktgen or MoonGen to generate bursts of configurable size (e.g., 10 µs, 100 µs, 1 ms) at a target line‑rate (e.g., 10 Gbps).
  4. Collect metrics – During each test run, sample:
    • Ring occupancy (ethtool -S eth0 shows rx_ring_* counters).
    • Softir

Share this post on:

Previous Post
Intended Attach Graph vs the Programs Actually Live
Next Post
VIP Failover Without Session Ownership