root@ubuntu:~# time tcpblast -o -s 65536 -c 16k 192.168.2.1:9999 > /dev/null; grep virtio1 /proc/interrupts; cat /sys/devices/lguest/virtio1/vqstats

real    0m17.075s
user    0m2.880s
sys     0m14.190s
  2:     176256    lguest-level     virtio1
  3:      21300    lguest-level     virtio1
0: 195507 2946 0 0
1: 753674 753674 0 0
---
 drivers/virtio/virtio.c      |   16 ++++++++++++++++
 drivers/virtio/virtio_ring.c |   23 ++++++++++++++++++++++-
 include/linux/virtio_ring.h  |    1 +
 3 files changed, 39 insertions(+), 1 deletion(-)

diff --git a/drivers/virtio/virtio.c b/drivers/virtio/virtio.c
--- a/drivers/virtio/virtio.c
+++ b/drivers/virtio/virtio.c
@@ -1,6 +1,7 @@
 #include <linux/virtio.h>
 #include <linux/spinlock.h>
 #include <linux/virtio_config.h>
+#include <linux/virtio_ring.h>
 
 /* Unique numbering for virtio devices. */
 static unsigned int dev_index;
@@ -46,12 +47,27 @@ static ssize_t features_show(struct devi
 	len += sprintf(buf+len, "\n");
 	return len;
 }
+static ssize_t vqstats_show(struct device *_d,
+			    struct device_attribute *attr, char *buf)
+{
+	struct virtio_device *dev = container_of(_d,struct virtio_device,dev);
+	struct virtqueue *vq;
+	ssize_t len = 0;
+
+	list_for_each_entry(vq, &dev->vqs, list) {
+		len += sprintf(buf+len, "%s: ", vq->name);
+		len += vring_stats_show(vq, buf+len);
+		len += sprintf(buf+len, "\n");
+	}
+	return len;
+}
 static struct device_attribute virtio_dev_attrs[] = {
 	__ATTR_RO(device),
 	__ATTR_RO(vendor),
 	__ATTR_RO(status),
 	__ATTR_RO(modalias),
 	__ATTR_RO(features),
+	__ATTR_RO(vqstats),
 	__ATTR_NULL
 };
 
diff --git a/drivers/virtio/virtio_ring.c b/drivers/virtio/virtio_ring.c
--- a/drivers/virtio/virtio_ring.c
+++ b/drivers/virtio/virtio_ring.c
@@ -82,6 +82,8 @@ struct vring_virtqueue
 	unsigned int in_use;
 #endif
 
+	unsigned long addbuf_total, kick_total, kick_suppress, other_notify;
+
 	/* Tokens for callbacks. */
 	void *data[];
 };
@@ -161,14 +163,17 @@ static int vring_add_buf(struct virtqueu
 	BUG_ON(out + in > vq->vring.num);
 	BUG_ON(out + in == 0);
 
+	vq->addbuf_total++;
 	if (vq->num_free < out + in) {
 		pr_debug("Can't add buf len %i - avail = %i\n",
 			 out + in, vq->num_free);
 		/* FIXME: for historical reasons, we force a notify here if
 		 * there are outgoing parts to the buffer.  Presumably the
 		 * host should service the ring ASAP. */
-		if (out)
+		if (out) {
 			vq->notify(&vq->vq);
+			vq->other_notify++;
+		}
 		END_USE(vq);
 		return -ENOSPC;
 	}
@@ -219,6 +224,7 @@ static void vring_kick(struct virtqueue 
 	 * new available array entries. */
 	wmb();
 
+	vq->kick_total++;
 	vq->vring.avail->idx += vq->num_added;
 	vq->num_added = 0;
 
@@ -228,6 +234,8 @@ static void vring_kick(struct virtqueue 
 	if (!(vq->vring.used->flags & VRING_USED_F_NO_NOTIFY))
 		/* Prod other side to tell it about changes. */
 		vq->notify(&vq->vq);
+	else
+		vq->kick_suppress++;
 
 	END_USE(vq);
 }
@@ -389,6 +397,8 @@ struct virtqueue *vring_new_virtqueue(un
 #ifdef DEBUG
 	vq->in_use = false;
 #endif
+	vq->addbuf_total = vq->kick_total = vq->kick_suppress
+		= vq->other_notify = 0;
 
 	vq->indirect = virtio_has_feature(vdev, VIRTIO_RING_F_INDIRECT_DESC);
 
@@ -430,4 +440,15 @@ void vring_transport_features(struct vir
 }
 EXPORT_SYMBOL_GPL(vring_transport_features);
 
+ssize_t vring_stats_show(struct virtqueue *_vq, char *buf)
+{
+	struct vring_virtqueue *vq = to_vvq(_vq);
+
+	return sprintf(buf, "%lu %lu %lu %lu",
+		       vq->addbuf_total,
+		       vq->kick_total - vq->kick_suppress,
+		       vq->kick_suppress,
+		       vq->other_notify);
+}
+
 MODULE_LICENSE("GPL");
diff --git a/include/linux/virtio_ring.h b/include/linux/virtio_ring.h
--- a/include/linux/virtio_ring.h
+++ b/include/linux/virtio_ring.h
@@ -131,5 +131,6 @@ void vring_del_virtqueue(struct virtqueu
 void vring_transport_features(struct virtio_device *vdev);
 
 irqreturn_t vring_interrupt(int irq, void *_vq);
+ssize_t vring_stats_show(struct virtqueue *_vq, char *buf);
 #endif /* __KERNEL__ */
 #endif /* _LINUX_VIRTIO_RING_H */
