From b295f06da496aa8d8c8f660d4f02946d25203861 Mon Sep 17 00:00:00 2001 From: zhenwei pi Date: Thu, 15 Oct 2020 19:21:14 +0800 Subject: [PATCH] util: support device stats collection for libvirt can retrieve traffic stats for emulated interfaces that are backed by tap or macvtap devices, but this information wasn't available for hostdev interfaces (those that are implemented by assigning an SR-IOV VF device to a guest using vfio): #virsh domifstat instance --interface=52:54:00:2d:b2:35 error: Failed to get interface stats instance 52:54:00:2d:b2:35 error: internal error: Interface name not provided For some SR-IOV VF devices this information is available via the netlink VFINFO_LIST request/response, and that is what this patch uses to implement stats retrieval for VF. Not that this is dependent on support in the PF driver - for example, the Mellanox ConnectX-4 Lx (mlx5) driver reports usable stats, while Intel 82599 (ixgbe) and 82576 (igb) just report all stats as 0. (this is the same result as "ip -s link show"). Signed-off-by: zhenwei pi Reviewed-by: Laine Stump --- src/libvirt_private.syms | 1 + src/qemu/qemu_driver.c | 14 +++++++ src/util/virnetdev.c | 82 +++++++++++++++++++++++++++++++++++++++- src/util/virnetdev.h | 4 ++ 4 files changed, 99 insertions(+), 2 deletions(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 52e9c6313f..ae543589f1 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2584,6 +2584,7 @@ virNetDevSetRootQDisc; virNetDevSetupControl; virNetDevSysfsFile; virNetDevValidateConfig; +virNetDevVFInterfaceStats; # util/virnetdevbandwidth.h diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index bb4a46be98..f547abd259 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -10191,6 +10191,20 @@ qemuDomainInterfaceStats(virDomainPtr dom, if (virDomainNetGetActualType(net) == VIR_DOMAIN_NET_TYPE_VHOSTUSER) { if (virNetDevOpenvswitchInterfaceStats(net->ifname, stats) < 0) goto cleanup; + } else if (virDomainNetGetActualType(net) == VIR_DOMAIN_NET_TYPE_HOSTDEV) { + virDomainHostdevDefPtr hostdev = virDomainNetGetActualHostdev(net); + virPCIDeviceAddressPtr vfAddr; + + if (!hostdev) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("hostdev interface missing hostdev data")); + goto cleanup; + } + + vfAddr = &hostdev->source.subsys.u.pci.addr; + if (virNetDevVFInterfaceStats(vfAddr, stats) < 0) + goto cleanup; + } else { if (virNetDevTapInterfaceStats(net->ifname, stats, !virDomainNetTypeSharesHostView(net)) < 0) diff --git a/src/util/virnetdev.c b/src/util/virnetdev.c index 5c7660dab4..f53e1751b3 100644 --- a/src/util/virnetdev.c +++ b/src/util/virnetdev.c @@ -1514,6 +1514,17 @@ static struct nla_policy ifla_vf_policy[IFLA_VF_MAX+1] = { .maxlen = sizeof(struct ifla_vf_mac) }, [IFLA_VF_VLAN] = { .type = NLA_UNSPEC, .maxlen = sizeof(struct ifla_vf_vlan) }, + [IFLA_VF_STATS] = { .type = NLA_NESTED }, +}; + + +static struct nla_policy ifla_vfstats_policy[IFLA_VF_STATS_MAX+1] = { + [IFLA_VF_STATS_RX_PACKETS] = { .type = NLA_U64 }, + [IFLA_VF_STATS_TX_PACKETS] = { .type = NLA_U64 }, + [IFLA_VF_STATS_RX_BYTES] = { .type = NLA_U64 }, + [IFLA_VF_STATS_TX_BYTES] = { .type = NLA_U64 }, + [IFLA_VF_STATS_BROADCAST] = { .type = NLA_U64 }, + [IFLA_VF_STATS_MULTICAST] = { .type = NLA_U64 }, }; @@ -1651,13 +1662,14 @@ virNetDevSetVfConfig(const char *ifname, int vf, static int virNetDevParseVfConfig(struct nlattr **tb, int32_t vf, virMacAddrPtr mac, - int *vlanid) + int *vlanid, virDomainInterfaceStatsPtr stats) { int rc = -1; struct ifla_vf_mac *vf_mac; struct ifla_vf_vlan *vf_vlan; struct nlattr *tb_vf_info = {NULL, }; struct nlattr *tb_vf[IFLA_VF_MAX+1]; + struct nlattr *tb_vf_stats[IFLA_VF_STATS_MAX+1]; int rem; if (!tb[IFLA_VFINFO_LIST]) { @@ -1693,6 +1705,26 @@ virNetDevParseVfConfig(struct nlattr **tb, int32_t vf, virMacAddrPtr mac, } } + if (stats && tb_vf[IFLA_VF_STATS] && tb_vf[IFLA_VF_MAC]) { + vf_mac = RTA_DATA(tb_vf[IFLA_VF_MAC]); + if (vf_mac && vf_mac->vf == vf) { + rc = nla_parse_nested(tb_vf_stats, IFLA_VF_STATS_MAX, + tb_vf[IFLA_VF_STATS], + ifla_vfstats_policy); + if (rc < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("error parsing IFLA_VF_STATS")); + return rc; + } + + stats->rx_bytes = nla_get_u64(tb_vf_stats[IFLA_VF_STATS_RX_BYTES]); + stats->tx_bytes = nla_get_u64(tb_vf_stats[IFLA_VF_STATS_TX_BYTES]); + stats->rx_packets = nla_get_u64(tb_vf_stats[IFLA_VF_STATS_RX_PACKETS]); + stats->tx_packets = nla_get_u64(tb_vf_stats[IFLA_VF_STATS_TX_PACKETS]); + rc = 0; + } + } + if (rc == 0) break; } @@ -1714,7 +1746,43 @@ virNetDevGetVfConfig(const char *ifname, int vf, virMacAddrPtr mac, if (virNetlinkDumpLink(ifname, ifindex, &nlData, tb, 0, 0) < 0) return -1; - return virNetDevParseVfConfig(tb, vf, mac, vlanid); + return virNetDevParseVfConfig(tb, vf, mac, vlanid, NULL); +} + + +/** + * virNetDevVFInterfaceStats: + * @vfAddr: PCI address of a VF + * @stats: returns stats of the VF interface + * + * Get the VF interface from kernel by netlink. + * Returns 0 on success, -1 on failure. + */ +int +virNetDevVFInterfaceStats(virPCIDeviceAddressPtr vfAddr, + virDomainInterfaceStatsPtr stats) +{ + g_autofree void *nlData = NULL; + struct nlattr *tb[IFLA_MAX + 1] = {NULL, }; + g_autofree char *vfSysfsPath = NULL; + g_autofree char *pfname = NULL; + int vf = -1; + + if (virPCIDeviceAddressGetSysfsFile(vfAddr, &vfSysfsPath) < 0) + return -1; + + if (!virPCIIsVirtualFunction(vfSysfsPath)) { + virReportError(VIR_ERR_INTERNAL_ERROR, _("'%s' is not a VF device"), vfSysfsPath); + return -1; + } + + if (virPCIGetVirtualFunctionInfo(vfSysfsPath, -1, &pfname, &vf) < 0) + return -1; + + if (virNetlinkDumpLink(pfname, -1, &nlData, tb, 0, 0) < 0) + return -1; + + return virNetDevParseVfConfig(tb, vf, NULL, NULL, stats); } @@ -2330,6 +2398,16 @@ virNetDevSetNetConfig(const char *linkdev G_GNUC_UNUSED, } +int +virNetDevVFInterfaceStats(virPCIDeviceAddressPtr vfAddr G_GNUC_UNUSED, + virDomainInterfaceStatsPtr stats G_GNUC_UNUSED) +{ + virReportSystemError(ENOSYS, "%s", + _("Unable to get VF net device stats on this platform")); + return -1; +} + + #endif /* defined(WITH_LIBNL) */ VIR_ENUM_IMPL(virNetDevIfState, diff --git a/src/util/virnetdev.h b/src/util/virnetdev.h index dfef49938f..53e606c61c 100644 --- a/src/util/virnetdev.h +++ b/src/util/virnetdev.h @@ -316,4 +316,8 @@ int virNetDevSetRootQDisc(const char *ifname, const char *qdisc) G_GNUC_NO_INLINE; +int virNetDevVFInterfaceStats(virPCIDeviceAddressPtr vfAddr, + virDomainInterfaceStatsPtr stats) +ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); + G_DEFINE_AUTOPTR_CLEANUP_FUNC(virNetDevRxFilter, virNetDevRxFilterFree);