Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

virtio_net: Add ethtool stats

The main purpose of this patch is adding a way of checking per-queue stats.
It's useful to debug performance problems on multiqueue environment.

$ ethtool -S ens10
NIC statistics:
rx_queue_0_packets: 2090408
rx_queue_0_bytes: 3164825094
rx_queue_1_packets: 2082531
rx_queue_1_bytes: 3152932314
tx_queue_0_packets: 2770841
tx_queue_0_bytes: 4194955474
tx_queue_1_packets: 3084697
tx_queue_1_bytes: 4670196372

This change converts existing per-cpu stats structure into per-queue one.
This should not impact on performance since each queue counter is not
updated concurrently by multiple cpus.

Performance numbers:
- Guest has 2 vcpus and 2 queues
- Guest runs netserver
- Host runs 100-flow super_netperf

Before After Diff
UDP_STREAM 18byte 86.22 87.00 +0.90%
UDP_STREAM 1472byte 4055.27 4042.18 -0.32%
TCP_STREAM 16956.32 16890.63 -0.39%
UDP_RR 178667.11 185862.70 +4.03%
TCP_RR 128473.04 124985.81 -2.71%

Signed-off-by: Toshiaki Makita <makita.toshiaki@lab.ntt.co.jp>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Toshiaki Makita and committed by
David S. Miller
d7dfc5cf d1b1110f

+141 -50
+141 -50
drivers/net/virtio_net.c
··· 66 66 VIRTIO_NET_F_GUEST_UFO 67 67 }; 68 68 69 - struct virtnet_stats { 70 - struct u64_stats_sync tx_syncp; 71 - struct u64_stats_sync rx_syncp; 72 - u64 tx_bytes; 73 - u64 tx_packets; 74 - 75 - u64 rx_bytes; 76 - u64 rx_packets; 69 + struct virtnet_stat_desc { 70 + char desc[ETH_GSTRING_LEN]; 71 + size_t offset; 77 72 }; 73 + 74 + struct virtnet_sq_stats { 75 + struct u64_stats_sync syncp; 76 + u64 packets; 77 + u64 bytes; 78 + }; 79 + 80 + struct virtnet_rq_stats { 81 + struct u64_stats_sync syncp; 82 + u64 packets; 83 + u64 bytes; 84 + }; 85 + 86 + #define VIRTNET_SQ_STAT(m) offsetof(struct virtnet_sq_stats, m) 87 + #define VIRTNET_RQ_STAT(m) offsetof(struct virtnet_rq_stats, m) 88 + 89 + static const struct virtnet_stat_desc virtnet_sq_stats_desc[] = { 90 + { "packets", VIRTNET_SQ_STAT(packets) }, 91 + { "bytes", VIRTNET_SQ_STAT(bytes) }, 92 + }; 93 + 94 + static const struct virtnet_stat_desc virtnet_rq_stats_desc[] = { 95 + { "packets", VIRTNET_RQ_STAT(packets) }, 96 + { "bytes", VIRTNET_RQ_STAT(bytes) }, 97 + }; 98 + 99 + #define VIRTNET_SQ_STATS_LEN ARRAY_SIZE(virtnet_sq_stats_desc) 100 + #define VIRTNET_RQ_STATS_LEN ARRAY_SIZE(virtnet_rq_stats_desc) 78 101 79 102 /* Internal representation of a send virtqueue */ 80 103 struct send_queue { ··· 110 87 /* Name of the send queue: output.$index */ 111 88 char name[40]; 112 89 90 + struct virtnet_sq_stats stats; 91 + 113 92 struct napi_struct napi; 114 93 }; 115 94 ··· 123 98 struct napi_struct napi; 124 99 125 100 struct bpf_prog __rcu *xdp_prog; 101 + 102 + struct virtnet_rq_stats stats; 126 103 127 104 /* Chain pages by the private ptr. */ 128 105 struct page *pages; ··· 178 151 179 152 /* Packet virtio header size */ 180 153 u8 hdr_len; 181 - 182 - /* Active statistics */ 183 - struct virtnet_stats __percpu *stats; 184 154 185 155 /* Work struct for refilling if we run low on memory. */ 186 156 struct delayed_work refill; ··· 1151 1127 struct virtnet_info *vi = rq->vq->vdev->priv; 1152 1128 unsigned int len, received = 0, bytes = 0; 1153 1129 void *buf; 1154 - struct virtnet_stats *stats = this_cpu_ptr(vi->stats); 1155 1130 1156 1131 if (!vi->big_packets || vi->mergeable_rx_bufs) { 1157 1132 void *ctx; ··· 1173 1150 schedule_delayed_work(&vi->refill, 0); 1174 1151 } 1175 1152 1176 - u64_stats_update_begin(&stats->rx_syncp); 1177 - stats->rx_bytes += bytes; 1178 - stats->rx_packets += received; 1179 - u64_stats_update_end(&stats->rx_syncp); 1153 + u64_stats_update_begin(&rq->stats.syncp); 1154 + rq->stats.bytes += bytes; 1155 + rq->stats.packets += received; 1156 + u64_stats_update_end(&rq->stats.syncp); 1180 1157 1181 1158 return received; 1182 1159 } ··· 1185 1162 { 1186 1163 struct sk_buff *skb; 1187 1164 unsigned int len; 1188 - struct virtnet_info *vi = sq->vq->vdev->priv; 1189 - struct virtnet_stats *stats = this_cpu_ptr(vi->stats); 1190 1165 unsigned int packets = 0; 1191 1166 unsigned int bytes = 0; 1192 1167 ··· 1203 1182 if (!packets) 1204 1183 return; 1205 1184 1206 - u64_stats_update_begin(&stats->tx_syncp); 1207 - stats->tx_bytes += bytes; 1208 - stats->tx_packets += packets; 1209 - u64_stats_update_end(&stats->tx_syncp); 1185 + u64_stats_update_begin(&sq->stats.syncp); 1186 + sq->stats.bytes += bytes; 1187 + sq->stats.packets += packets; 1188 + u64_stats_update_end(&sq->stats.syncp); 1210 1189 } 1211 1190 1212 1191 static void virtnet_poll_cleantx(struct receive_queue *rq) ··· 1495 1474 struct rtnl_link_stats64 *tot) 1496 1475 { 1497 1476 struct virtnet_info *vi = netdev_priv(dev); 1498 - int cpu; 1499 1477 unsigned int start; 1478 + int i; 1500 1479 1501 - for_each_possible_cpu(cpu) { 1502 - struct virtnet_stats *stats = per_cpu_ptr(vi->stats, cpu); 1480 + for (i = 0; i < vi->max_queue_pairs; i++) { 1503 1481 u64 tpackets, tbytes, rpackets, rbytes; 1482 + struct receive_queue *rq = &vi->rq[i]; 1483 + struct send_queue *sq = &vi->sq[i]; 1504 1484 1505 1485 do { 1506 - start = u64_stats_fetch_begin_irq(&stats->tx_syncp); 1507 - tpackets = stats->tx_packets; 1508 - tbytes = stats->tx_bytes; 1509 - } while (u64_stats_fetch_retry_irq(&stats->tx_syncp, start)); 1486 + start = u64_stats_fetch_begin_irq(&sq->stats.syncp); 1487 + tpackets = sq->stats.packets; 1488 + tbytes = sq->stats.bytes; 1489 + } while (u64_stats_fetch_retry_irq(&sq->stats.syncp, start)); 1510 1490 1511 1491 do { 1512 - start = u64_stats_fetch_begin_irq(&stats->rx_syncp); 1513 - rpackets = stats->rx_packets; 1514 - rbytes = stats->rx_bytes; 1515 - } while (u64_stats_fetch_retry_irq(&stats->rx_syncp, start)); 1492 + start = u64_stats_fetch_begin_irq(&rq->stats.syncp); 1493 + rpackets = rq->stats.packets; 1494 + rbytes = rq->stats.bytes; 1495 + } while (u64_stats_fetch_retry_irq(&rq->stats.syncp, start)); 1516 1496 1517 1497 tot->rx_packets += rpackets; 1518 1498 tot->tx_packets += tpackets; ··· 1851 1829 return err; 1852 1830 } 1853 1831 1832 + static void virtnet_get_strings(struct net_device *dev, u32 stringset, u8 *data) 1833 + { 1834 + struct virtnet_info *vi = netdev_priv(dev); 1835 + char *p = (char *)data; 1836 + unsigned int i, j; 1837 + 1838 + switch (stringset) { 1839 + case ETH_SS_STATS: 1840 + for (i = 0; i < vi->curr_queue_pairs; i++) { 1841 + for (j = 0; j < VIRTNET_RQ_STATS_LEN; j++) { 1842 + snprintf(p, ETH_GSTRING_LEN, "rx_queue_%u_%s", 1843 + i, virtnet_rq_stats_desc[j].desc); 1844 + p += ETH_GSTRING_LEN; 1845 + } 1846 + } 1847 + 1848 + for (i = 0; i < vi->curr_queue_pairs; i++) { 1849 + for (j = 0; j < VIRTNET_SQ_STATS_LEN; j++) { 1850 + snprintf(p, ETH_GSTRING_LEN, "tx_queue_%u_%s", 1851 + i, virtnet_sq_stats_desc[j].desc); 1852 + p += ETH_GSTRING_LEN; 1853 + } 1854 + } 1855 + break; 1856 + } 1857 + } 1858 + 1859 + static int virtnet_get_sset_count(struct net_device *dev, int sset) 1860 + { 1861 + struct virtnet_info *vi = netdev_priv(dev); 1862 + 1863 + switch (sset) { 1864 + case ETH_SS_STATS: 1865 + return vi->curr_queue_pairs * (VIRTNET_RQ_STATS_LEN + 1866 + VIRTNET_SQ_STATS_LEN); 1867 + default: 1868 + return -EOPNOTSUPP; 1869 + } 1870 + } 1871 + 1872 + static void virtnet_get_ethtool_stats(struct net_device *dev, 1873 + struct ethtool_stats *stats, u64 *data) 1874 + { 1875 + struct virtnet_info *vi = netdev_priv(dev); 1876 + unsigned int idx = 0, start, i, j; 1877 + const u8 *stats_base; 1878 + size_t offset; 1879 + 1880 + for (i = 0; i < vi->curr_queue_pairs; i++) { 1881 + struct receive_queue *rq = &vi->rq[i]; 1882 + 1883 + stats_base = (u8 *)&rq->stats; 1884 + do { 1885 + start = u64_stats_fetch_begin_irq(&rq->stats.syncp); 1886 + for (j = 0; j < VIRTNET_RQ_STATS_LEN; j++) { 1887 + offset = virtnet_rq_stats_desc[j].offset; 1888 + data[idx + j] = *(u64 *)(stats_base + offset); 1889 + } 1890 + } while (u64_stats_fetch_retry_irq(&rq->stats.syncp, start)); 1891 + idx += VIRTNET_RQ_STATS_LEN; 1892 + } 1893 + 1894 + for (i = 0; i < vi->curr_queue_pairs; i++) { 1895 + struct send_queue *sq = &vi->sq[i]; 1896 + 1897 + stats_base = (u8 *)&sq->stats; 1898 + do { 1899 + start = u64_stats_fetch_begin_irq(&sq->stats.syncp); 1900 + for (j = 0; j < VIRTNET_SQ_STATS_LEN; j++) { 1901 + offset = virtnet_sq_stats_desc[j].offset; 1902 + data[idx + j] = *(u64 *)(stats_base + offset); 1903 + } 1904 + } while (u64_stats_fetch_retry_irq(&sq->stats.syncp, start)); 1905 + idx += VIRTNET_SQ_STATS_LEN; 1906 + } 1907 + } 1908 + 1854 1909 static void virtnet_get_channels(struct net_device *dev, 1855 1910 struct ethtool_channels *channels) 1856 1911 { ··· 2027 1928 .get_drvinfo = virtnet_get_drvinfo, 2028 1929 .get_link = ethtool_op_get_link, 2029 1930 .get_ringparam = virtnet_get_ringparam, 1931 + .get_strings = virtnet_get_strings, 1932 + .get_sset_count = virtnet_get_sset_count, 1933 + .get_ethtool_stats = virtnet_get_ethtool_stats, 2030 1934 .set_channels = virtnet_set_channels, 2031 1935 .get_channels = virtnet_get_channels, 2032 1936 .get_ts_info = ethtool_op_get_ts_info, ··· 2522 2420 sg_init_table(vi->rq[i].sg, ARRAY_SIZE(vi->rq[i].sg)); 2523 2421 ewma_pkt_len_init(&vi->rq[i].mrg_avg_pkt_len); 2524 2422 sg_init_table(vi->sq[i].sg, ARRAY_SIZE(vi->sq[i].sg)); 2423 + 2424 + u64_stats_init(&vi->rq[i].stats.syncp); 2425 + u64_stats_init(&vi->sq[i].stats.syncp); 2525 2426 } 2526 2427 2527 2428 return 0; ··· 2649 2544 2650 2545 static int virtnet_probe(struct virtio_device *vdev) 2651 2546 { 2652 - int i, err; 2547 + int i, err = -ENOMEM; 2653 2548 struct net_device *dev; 2654 2549 struct virtnet_info *vi; 2655 2550 u16 max_queue_pairs; ··· 2726 2621 vi->dev = dev; 2727 2622 vi->vdev = vdev; 2728 2623 vdev->priv = vi; 2729 - vi->stats = alloc_percpu(struct virtnet_stats); 2730 - err = -ENOMEM; 2731 - if (vi->stats == NULL) 2732 - goto free; 2733 - 2734 - for_each_possible_cpu(i) { 2735 - struct virtnet_stats *virtnet_stats; 2736 - virtnet_stats = per_cpu_ptr(vi->stats, i); 2737 - u64_stats_init(&virtnet_stats->tx_syncp); 2738 - u64_stats_init(&virtnet_stats->rx_syncp); 2739 - } 2740 2624 2741 2625 INIT_WORK(&vi->config_work, virtnet_config_changed_work); 2742 2626 ··· 2762 2668 */ 2763 2669 dev_err(&vdev->dev, "device MTU appears to have changed " 2764 2670 "it is now %d < %d", mtu, dev->min_mtu); 2765 - goto free_stats; 2671 + goto free; 2766 2672 } 2767 2673 2768 2674 dev->mtu = mtu; ··· 2786 2692 /* Allocate/initialize the rx/tx queues, and invoke find_vqs */ 2787 2693 err = init_vqs(vi); 2788 2694 if (err) 2789 - goto free_stats; 2695 + goto free; 2790 2696 2791 2697 #ifdef CONFIG_SYSFS 2792 2698 if (vi->mergeable_rx_bufs) ··· 2841 2747 cancel_delayed_work_sync(&vi->refill); 2842 2748 free_receive_page_frags(vi); 2843 2749 virtnet_del_vqs(vi); 2844 - free_stats: 2845 - free_percpu(vi->stats); 2846 2750 free: 2847 2751 free_netdev(dev); 2848 2752 return err; ··· 2873 2781 2874 2782 remove_vq_common(vi); 2875 2783 2876 - free_percpu(vi->stats); 2877 2784 free_netdev(vi->dev); 2878 2785 } 2879 2786