* [PATCH v3] virtio_net: add rx-alloc-fail counter
@ 2026-09-03 2:28 Longjun Tang
2026-09-07 20:31 ` netdev-bot+sashiko
2026-09-08 11:10 ` patchwork-bot+netdevbpf
0 siblings, 2 replies; 3+ messages in thread
From: Longjun Tang @ 2026-09-03 2:28 UTC (permalink / raw)
To: kuba, mst; +Cc: jasowangio, xuanzhuo, virtualization, netdev, tanglongjun
From: Longjun Tang <tanglongjun@kylinos.cn>
Count the cases where the driver fails to allocate a buffer or an skb,
such as when try_fill_recv() fails with ENOMEM, and report them via the
rx-alloc-fail qstat.
Plumb alloc-fail into virtnet_get_base_stats() to keep this new stat
reported consistently at both queue and device scope, matching how
packets, bytes, stop and wake are handled.
This counter does not cover every allocation failure in all RX paths.
It is intentionally limited to the common skb and RX buffer allocation
paths to keep its meaning clear.
$ ./tools/net/ynl/ynltool/ynltool qstats show --json scope queue
[{"ifname":"ens3",
"ifindex":2,
"queue-type":"rx",
"queue-id":0,
"rx":{"packets":3722,"bytes":374806,"alloc-fail":0}},
{"ifname":"ens3",
"ifindex":2,
"queue-type":"tx",
"queue-id":0,
"tx":{"packets":622,"bytes":77495,"stop":0,"wake":0}}]
Signed-off-by: Longjun Tang <tanglongjun@kylinos.cn>
---
change since v2:
- update commit msg: this counter does't cover all allocation failure cases.
- v2: https://lore.kernel.org/virtualization/20260824112050.02623327@kernel.org/T/#mcde23203f0dc3d3a64756e517d3f588c52723ad4
drivers/net/virtio_net.c | 33 +++++++++++++++++++++++++--------
1 file changed, 25 insertions(+), 8 deletions(-)
diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c
index e34c52d059d3..360cc3435e90 100644
--- a/drivers/net/virtio_net.c
+++ b/drivers/net/virtio_net.c
@@ -125,6 +125,7 @@ struct virtnet_rq_stats {
u64_stats_t packets;
u64_stats_t bytes;
u64_stats_t drops;
+ u64_stats_t alloc_fail;
u64_stats_t xdp_packets;
u64_stats_t xdp_tx;
u64_stats_t xdp_redirects;
@@ -173,8 +174,9 @@ static const struct virtnet_stat_desc virtnet_sq_stats_desc_qstat[] = {
};
static const struct virtnet_stat_desc virtnet_rq_stats_desc_qstat[] = {
- VIRTNET_RQ_STAT_QSTAT("packets", packets),
- VIRTNET_RQ_STAT_QSTAT("bytes", bytes),
+ VIRTNET_RQ_STAT_QSTAT("packets", packets),
+ VIRTNET_RQ_STAT_QSTAT("bytes", bytes),
+ VIRTNET_RQ_STAT_QSTAT("alloc_fail", alloc_fail),
};
#define VIRTNET_STATS_DESC_CQ(name) \
@@ -1917,8 +1919,10 @@ static struct sk_buff *receive_small_xdp(struct net_device *dev,
}
skb = virtnet_build_skb(buf, buflen, xdp.data - buf, len);
- if (unlikely(!skb))
+ if (unlikely(!skb)) {
+ u64_stats_inc(&stats->alloc_fail);
goto err;
+ }
if (metasize)
skb_metadata_set(skb, metasize);
@@ -1985,6 +1989,7 @@ static struct sk_buff *receive_small(struct net_device *dev,
return skb;
}
+ u64_stats_inc(&stats->alloc_fail);
err:
u64_stats_inc(&stats->drops);
page_pool_put_page(rq->page_pool, page, -1, true);
@@ -2016,8 +2021,10 @@ static struct sk_buff *receive_big(struct net_device *dev,
skb = page_to_skb(vi, rq, page, 0, len, PAGE_SIZE, 0);
u64_stats_add(&stats->bytes, len - vi->hdr_len);
- if (unlikely(!skb))
+ if (unlikely(!skb)) {
+ u64_stats_inc(&stats->alloc_fail);
goto err;
+ }
return skb;
@@ -2298,8 +2305,10 @@ static struct sk_buff *receive_mergeable_xdp(struct net_device *dev,
switch (act) {
case XDP_PASS:
head_skb = build_skb_from_xdp_buff(dev, vi, &xdp, xdp_frags_truesz);
- if (unlikely(!head_skb))
+ if (unlikely(!head_skb)) {
+ u64_stats_inc(&stats->alloc_fail);
break;
+ }
skb_mark_for_recycle(head_skb);
return head_skb;
@@ -2414,8 +2423,10 @@ static struct sk_buff *receive_mergeable(struct net_device *dev,
head_skb = page_to_skb(vi, rq, page, offset, len, truesize, headroom);
curr_skb = head_skb;
- if (unlikely(!curr_skb))
+ if (unlikely(!curr_skb)) {
+ u64_stats_inc(&stats->alloc_fail);
goto err_skb;
+ }
skb_mark_for_recycle(head_skb);
while (--num_buf) {
@@ -2444,8 +2455,10 @@ static struct sk_buff *receive_mergeable(struct net_device *dev,
truesize = mergeable_ctx_to_truesize(ctx);
curr_skb = virtnet_skb_append_frag(rq, head_skb, curr_skb, page,
buf, len, truesize);
- if (!curr_skb)
+ if (!curr_skb) {
+ u64_stats_inc(&stats->alloc_fail);
goto err_skb;
+ }
}
ewma_pkt_len_add(&rq->mrg_avg_pkt_len, head_skb->len);
@@ -2928,12 +2941,14 @@ static int virtnet_receive(struct receive_queue *rq, int budget,
u64_stats_set(&stats.packets, packets);
if (rq->vq->num_free > min((unsigned int)budget, virtqueue_get_vring_size(rq->vq)) / 2) {
- if (!try_fill_recv(vi, rq, GFP_ATOMIC))
+ if (!try_fill_recv(vi, rq, GFP_ATOMIC)) {
/* We need to retry refilling in the next NAPI poll so
* we must return budget to make sure the NAPI is
* repolled.
*/
packets = budget;
+ u64_stats_inc(&stats.alloc_fail);
+ }
}
u64_stats_update_begin(&rq->stats.syncp);
@@ -2948,6 +2963,7 @@ static int virtnet_receive(struct receive_queue *rq, int budget,
u64_stats_add(&rq->stats.packets, u64_stats_read(&stats.packets));
u64_stats_add(&rq->stats.bytes, u64_stats_read(&stats.bytes));
+ u64_stats_add(&rq->stats.alloc_fail, u64_stats_read(&stats.alloc_fail));
u64_stats_update_end(&rq->stats.syncp);
@@ -5666,6 +5682,7 @@ static void virtnet_get_base_stats(struct net_device *dev,
*/
rx->bytes = 0;
rx->packets = 0;
+ rx->alloc_fail = 0;
if (vi->device_stats_cap & VIRTIO_NET_STATS_TYPE_RX_BASIC) {
rx->hw_drops = 0;
--
2.25.1
^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: [PATCH v3] virtio_net: add rx-alloc-fail counter
2026-09-03 2:28 [PATCH v3] virtio_net: add rx-alloc-fail counter Longjun Tang
@ 2026-09-07 20:31 ` netdev-bot+sashiko
2026-09-08 11:10 ` patchwork-bot+netdevbpf
1 sibling, 0 replies; 3+ messages in thread
From: netdev-bot+sashiko @ 2026-09-07 20:31 UTC (permalink / raw)
To: lange_tang
Cc: kuba, mst, jasowangio, xuanzhuo, virtualization, netdev,
tanglongjun
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 1 (1) · Low: 0
- [Medium] The patch advertises the standardized `rx-alloc-fail` qstat
for every RX queue (`virtnet_rq_stats_desc_qstat[]` at…
Pre-existing issues:
- [Medium] The netdev qstats callbacks `virtnet_get_queue_stats_rx()` /
`virtnet_get_queue_stats_tx()` (drivers/net/virtio_net.c:5648-5672)…
--
virtio_net: add rx-alloc-fail counter
This adds a per-receive-queue alloc_fail counter to struct
virtnet_rq_stats, increments it on skb allocation failures in the
receive_small/receive_big/receive_mergeable paths and on try_fill_recv()
returning failure from virtnet_receive(), and exports it through the
rx-alloc-fail qstat including virtnet_get_base_stats().
A couple of questions below about the coverage of the new counter and
about how it is read back.
> diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c
> index e34c52d059d3..360cc3435e90 100644
> --- a/drivers/net/virtio_net.c
> +++ b/drivers/net/virtio_net.c
[ ... ]
> @@ -173,8 +174,9 @@ static const struct virtnet_stat_desc virtnet_sq_stats_desc_qstat[] = {
> };
>
> static const struct virtnet_stat_desc virtnet_rq_stats_desc_qstat[] = {
> - VIRTNET_RQ_STAT_QSTAT("packets", packets),
> - VIRTNET_RQ_STAT_QSTAT("bytes", bytes),
> + VIRTNET_RQ_STAT_QSTAT("packets", packets),
> + VIRTNET_RQ_STAT_QSTAT("bytes", bytes),
> + VIRTNET_RQ_STAT_QSTAT("alloc_fail", alloc_fail),
> };
[Severity: Medium]
This descriptor is used by virtnet_fill_stats_qstat() for every RX queue,
so rx-alloc-fail becomes an always-present value for all queue types,
including XSK-bound ones. Documentation/netlink/specs/netdev.yaml defines
it as "Number of times skb or buffer allocation failed on the Rx
datapath". Can the counter read 0 while the driver is in fact dropping
frames because allocations failed?
The AF_XDP receive path performs the same kind of skb and fragment
allocations that this patch now counts, but is not instrumented:
drivers/net/virtio_net.c:xsk_construct_skb() {
skb = napi_alloc_skb(&rq->napi, size);
if (unlikely(!skb)) {
xsk_buff_free(xdp);
return NULL;
}
...
}
drivers/net/virtio_net.c:xsk_append_merge_buffer() {
...
buf = napi_alloc_frag(len);
if (!buf) {
xsk_buff_free(xdp);
goto err;
}
...
if (!curr_skb) {
put_page(page);
goto err;
}
...
}
The XDP copy paths also turn RX buffer allocation failures into
xdp_drops/drops only:
drivers/net/virtio_net.c:xdp_linearize_page() {
page = page_pool_alloc_pages(rq->page_pool, GFP_ATOMIC);
if (!page)
return NULL;
...
}
drivers/net/virtio_net.c:mergeable_xdp_get_buf() {
...
xdp_page = page_pool_alloc_pages(rq->page_pool, GFP_ATOMIC);
if (!xdp_page)
return NULL;
...
}
Both NULL returns are routed to err_xdp by receive_small_xdp() and
receive_mergeable_xdp() without touching alloc_fail.
Finally, the refill sites outside NAPI drop the try_fill_recv() return
value entirely, so an -ENOMEM there is not visible at queue or device
scope:
drivers/net/virtio_net.c:virtnet_open() {
...
try_fill_recv(vi, &vi->rq[i], GFP_KERNEL);
...
}
drivers/net/virtio_net.c:virtnet_rx_resume() {
...
if (refill)
try_fill_recv(vi, rq, GFP_KERNEL);
...
}
The changelog says:
This counter does not cover every allocation failure in all RX paths.
It is intentionally limited to the common skb and RX buffer allocation
paths to keep its meaning clear.
Since the napi_alloc_skb() and virtnet_skb_append_frag() failures in the
XSK path look like exactly those common skb paths, could they be counted
too, so that the same failure is not counted in one variant and dropped
in the other? The caveat only exists in the changelog, so a userspace
consumer cannot tell "no allocation failures" apart from "this path is
not instrumented".
[ ... ]
> @@ -2928,12 +2941,14 @@ static int virtnet_receive(struct receive_queue *rq, int budget,
>
> u64_stats_set(&stats.packets, packets);
> if (rq->vq->num_free > min((unsigned int)budget, virtqueue_get_vring_size(rq->vq)) / 2) {
> - if (!try_fill_recv(vi, rq, GFP_ATOMIC))
> + if (!try_fill_recv(vi, rq, GFP_ATOMIC)) {
> /* We need to retry refilling in the next NAPI poll so
> * we must return budget to make sure the NAPI is
> * repolled.
> */
> packets = budget;
> + u64_stats_inc(&stats.alloc_fail);
> + }
> }
>
> u64_stats_update_begin(&rq->stats.syncp);
> @@ -2948,6 +2963,7 @@ static int virtnet_receive(struct receive_queue *rq, int budget,
>
> u64_stats_add(&rq->stats.packets, u64_stats_read(&stats.packets));
> u64_stats_add(&rq->stats.bytes, u64_stats_read(&stats.bytes));
> + u64_stats_add(&rq->stats.alloc_fail, u64_stats_read(&stats.alloc_fail));
>
> u64_stats_update_end(&rq->stats.syncp);
[Severity: Medium]
This isn't a bug introduced by this patch, but the qstat read side has no
seqcount protection, and the new alloc_fail field is now routed through
it as well.
The writer here runs in softirq inside
u64_stats_update_begin(&rq->stats.syncp) ...
u64_stats_update_end(&rq->stats.syncp), while the reader does a bare
read:
drivers/net/virtio_net.c:virtnet_get_queue_stats_rx() {
virtnet_stats_ctx_init(vi, &ctx, (void *)stats, true);
virtnet_get_hw_stats(vi, &ctx, i * 2);
virtnet_fill_stats(vi, i * 2, &ctx, (void *)&rq->stats, true, 0);
}
drivers/net/virtio_net.c:virtnet_fill_stats_qstat() {
...
v_stat = (const u64_stats_t *)(base + desc[i].offset);
ctx->data[offset] = u64_stats_read(v_stat);
...
}
The ethtool reader in the same file does use the retry protocol:
drivers/net/virtio_net.c:virtnet_get_ethtool_stats() {
...
do {
start = u64_stats_fetch_begin(&rq->stats.syncp);
virtnet_fill_stats(vi, i * 2, &ctx, stats_base, true, 0);
} while (u64_stats_fetch_retry(&rq->stats.syncp, start));
...
}
On CONFIG_64BIT=n, can a concurrent qstats dump observe a torn 64-bit
value for packets, bytes or the new alloc_fail? Would it make sense to
wrap the qstat callbacks in u64_stats_fetch_begin() /
u64_stats_fetch_retry() the same way?
> @@ -5666,6 +5682,7 @@ static void virtnet_get_base_stats(struct net_device *dev,
> */
> rx->bytes = 0;
> rx->packets = 0;
> + rx->alloc_fail = 0;
>
> if (vi->device_stats_cap & VIRTIO_NET_STATS_TYPE_RX_BASIC) {
> rx->hw_drops = 0;
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260903022848.33595-1-lange_tang%40163.com
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH v3] virtio_net: add rx-alloc-fail counter
2026-09-03 2:28 [PATCH v3] virtio_net: add rx-alloc-fail counter Longjun Tang
2026-09-07 20:31 ` netdev-bot+sashiko
@ 2026-09-08 11:10 ` patchwork-bot+netdevbpf
1 sibling, 0 replies; 3+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-09-08 11:10 UTC (permalink / raw)
To: Longjun Tang
Cc: kuba, mst, jasowangio, xuanzhuo, virtualization, netdev,
tanglongjun
Hello:
This patch was applied to netdev/net-next.git (main)
by Paolo Abeni <pabeni@redhat.com>:
On Thu, 3 Sep 2026 10:28:48 +0800 you wrote:
> From: Longjun Tang <tanglongjun@kylinos.cn>
>
> Count the cases where the driver fails to allocate a buffer or an skb,
> such as when try_fill_recv() fails with ENOMEM, and report them via the
> rx-alloc-fail qstat.
>
> Plumb alloc-fail into virtnet_get_base_stats() to keep this new stat
> reported consistently at both queue and device scope, matching how
> packets, bytes, stop and wake are handled.
>
> [...]
Here is the summary with links:
- [v3] virtio_net: add rx-alloc-fail counter
https://git.kernel.org/netdev/net-next/c/6a41ab891fe8
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-09-08 11:11 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-03 2:28 [PATCH v3] virtio_net: add rx-alloc-fail counter Longjun Tang
2026-09-07 20:31 ` netdev-bot+sashiko
2026-09-08 11:10 ` patchwork-bot+netdevbpf
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox