[PATCH v2] util: support PCI passthrough net device stats collection

Daniel P. Berrangé berrange at redhat.com
Wed Sep 23 09:56:31 UTC 2020


On Wed, Sep 23, 2020 at 10:32:54AM +0800, zhenwei pi wrote:
> Collect PCI passthrough net device stats from kernel by netlink
> API.
> 
> Currently, libvirt can not get PCI passthrough net device stats,
> run command:
>  #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
> 
> The PCI device(usually SR-IOV virtual function device) is detached
> while it's used in PCI passthrough mode. And we can not parse this
> device from /proc/net/dev any more.
> 
> In this patch, libvirt check net device is VF of not firstly, then
> query virNetDevVFInterfaceStats(new API).
> virNetDevVFInterfaceStats parses VFs info of all PFs, compares MAC
> address until the two MAC addresses match.
> '#ip -s link show' can get the same result. Instead of parsing the
> output result, implement this feature by libnl API.
> 
> Notice that this feature deponds on driver of PF.
> Test on Mellanox ConnectX-4 Lx, it works well.
> Also test on Intel Corporation 82599ES, it works, but only get 0.
> (ip-link command get the same result).
> 
> Signed-off-by: zhenwei pi <pizhenwei at bytedance.com>
> ---
>  src/libvirt_private.syms |   1 +
>  src/qemu/qemu_driver.c   |   3 ++
>  src/util/virnetdev.c     | 137 +++++++++++++++++++++++++++++++++++++++++++++++
>  src/util/virnetdev.h     |   5 ++
>  4 files changed, 146 insertions(+)
> 
> diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
> index bdbe3431b8..bcc40b8d69 100644
> --- a/src/libvirt_private.syms
> +++ b/src/libvirt_private.syms
> @@ -2585,6 +2585,7 @@ virNetDevSetRcvMulti;
>  virNetDevSetupControl;
>  virNetDevSysfsFile;
>  virNetDevValidateConfig;
> +virNetDevVFInterfaceStats;
>  
>  
>  # util/virnetdevbandwidth.h
> diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
> index ae715c01d7..f554010c40 100644
> --- a/src/qemu/qemu_driver.c
> +++ b/src/qemu/qemu_driver.c
> @@ -10196,6 +10196,9 @@ 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) {
> +        if (virNetDevVFInterfaceStats(&net->mac, 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 e1a4cc2bef..377f25aae7 100644
> --- a/src/util/virnetdev.c
> +++ b/src/util/virnetdev.c
> @@ -1489,6 +1489,7 @@ 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 },
>  };
>  
>  
> @@ -2265,6 +2266,132 @@ virNetDevSetNetConfig(const char *linkdev, int vf,
>      return 0;
>  }
>  
> +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 },
> +};
> +
> +static int
> +virNetDevParseVfStats(struct nlattr **tb, virMacAddrPtr mac,
> +                      virDomainInterfaceStatsPtr stats)
> +{
> +    int ret = -1, len;
> +    struct ifla_vf_mac *vf_lladdr;
> +    struct nlattr *nla, *t[IFLA_VF_MAX+1];
> +    struct nlattr *stb[IFLA_VF_STATS_MAX+1];
> +
> +    if (tb == NULL || mac == NULL || stats == NULL) {
> +        return -1;
> +    }
> +
> +    if (!tb[IFLA_VFINFO_LIST])
> +        return -1;
> +
> +    len = nla_len(tb[IFLA_VFINFO_LIST]);
> +
> +    for (nla = nla_data(tb[IFLA_VFINFO_LIST]); nla_ok(nla, len);
> +            nla = nla_next(nla, &len)) {
> +        ret = nla_parse(t, IFLA_VF_MAX, nla_data(nla), nla_len(nla),
> +                ifla_vf_policy);
> +        if (ret < 0)
> +            return -1;
> +
> +        if (t[IFLA_VF_MAC] == NULL) {
> +            continue;
> +        }
> +
> +        vf_lladdr = nla_data(t[IFLA_VF_MAC]);
> +        if (virMacAddrCmpRaw(mac, vf_lladdr->mac)) {
> +            continue;
> +        }
> +
> +        if (t[IFLA_VF_STATS]) {
> +            ret = nla_parse_nested(stb, IFLA_VF_STATS_MAX,
> +                    t[IFLA_VF_STATS],
> +                    ifla_vfstats_policy);
> +            if (ret < 0)
> +                return -1;
> +
> +            stats->rx_bytes = nla_get_u64(stb[IFLA_VF_STATS_RX_BYTES]);
> +            stats->tx_bytes = nla_get_u64(stb[IFLA_VF_STATS_TX_BYTES]);
> +            stats->rx_packets = nla_get_u64(stb[IFLA_VF_STATS_RX_PACKETS]);
> +            stats->tx_packets = nla_get_u64(stb[IFLA_VF_STATS_TX_PACKETS]);
> +        }
> +        return 0;
> +    }
> +
> +    return ret;
> +}
> +
> +/**
> + * virNetDevVFInterfaceStats:
> + * @mac: MAC address of the VF interface
> + * @stats: returns stats of the VF interface
> + *
> + * Get the VF interface from kernel by netlink.
> + * Returns 0 on success, -1 on failure.
> + */
> +int
> +virNetDevVFInterfaceStats(virMacAddrPtr mac,
> +                     virDomainInterfaceStatsPtr stats)
> +{
> +    FILE *fp;
> +    char line[256], *colon, *ifname;
> +    int rc = -1;
> +    void *nlData = NULL;
> +    struct nlattr *tb[IFLA_MAX + 1] = {NULL, };
> +    char *sysfsDevicePath = NULL;
> +
> +    fp = fopen("/proc/net/dev", "r");
> +    if (!fp) {
> +        virReportSystemError(errno, "%s",
> +                _("Could not open /proc/net/dev"));
> +        return -1;
> +    }
> +
> +    /* get all PCI net devices, and parse VFs list from netlink API.
> +     * compare MAC address, collect device stats if matching.
> +     */
> +    while (fgets(line, sizeof(line), fp)) {
> +        /* The line looks like:
> +         *   "   eth0:..."
> +         * Split it at the colon. and strip blank from head.
> +         */
> +        colon = strchr(line, ':');
> +        if (!colon)
> +            continue;
> +        *colon = '\0';
> +        ifname = line;
> +        while ((*ifname == ' ') && (ifname < colon))
> +            ifname++;

It seems we're only parsing /proc/net/dev  to get a list of
device names, ignoring the rest of the data in that file.
It would be simpler if we just iterate over /sys/class/net
surely, as the filename as the nic names meaning we don't
need to parse anything.

> +
> +        if (virNetDevSysfsFile(&sysfsDevicePath, ifname, "device") < 0)
> +            break;
> +
> +        if (virNetDevIsPCIDevice(sysfsDevicePath)) {
> +            rc = virNetlinkDumpLink(ifname, -1, &nlData, tb, 0, 0);
> +            if (rc < 0) {
> +                rc = -1;
> +                goto cleanup;
> +            }
> +
> +            rc = virNetDevParseVfStats(tb, mac, stats);
> +            VIR_FREE(nlData);
> +            if (rc == 0)
> +                goto cleanup;
> +        }
> +        VIR_FREE(sysfsDevicePath);
> +    }
> +
> + cleanup:
> +    VIR_FREE(sysfsDevicePath);
> +    VIR_FORCE_FCLOSE(fp);
> +    return rc;
> +}

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|




More information about the libvir-list mailing list