util: support device stats collection for <interface type='hostdev'>

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 <pizhenwei@bytedance.com>
Reviewed-by: Laine Stump <laine@redhat.com>
This commit is contained in:
zhenwei pi 2020-10-15 19:21:14 +08:00 committed by Laine Stump
parent b79abf9c3c
commit b295f06da4
4 changed files with 99 additions and 2 deletions

View File

@ -2584,6 +2584,7 @@ virNetDevSetRootQDisc;
virNetDevSetupControl;
virNetDevSysfsFile;
virNetDevValidateConfig;
virNetDevVFInterfaceStats;
# util/virnetdevbandwidth.h

View File

@ -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)

View File

@ -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,

View File

@ -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);