numam-dpdk/drivers/net/mlx5/mlx5_stats.c
Adrien Mazarguil fc40db9973 net/mlx: control netdevices through ioctl only
Several control operations implemented by these PMDs affect netdevices
through sysfs, itself subject to file system permission checks enforced by
the kernel, which limits their use for most purposes to applications
running with root privileges.

Since performing the same operations through ioctl() requires fewer
capabilities (only CAP_NET_ADMIN) and given the remaining operations are
already implemented this way, this patch standardizes on ioctl() and gets
rid of redundant code.

Signed-off-by: Adrien Mazarguil <adrien.mazarguil@6wind.com>
Reviewed-by: Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>
2018-03-30 14:08:42 +02:00

509 lines
12 KiB
C

/* SPDX-License-Identifier: BSD-3-Clause
* Copyright 2015 6WIND S.A.
* Copyright 2015 Mellanox.
*/
#include <inttypes.h>
#include <linux/sockios.h>
#include <linux/ethtool.h>
#include <stdint.h>
#include <stdio.h>
#include <rte_ethdev_driver.h>
#include <rte_common.h>
#include <rte_malloc.h>
#include "mlx5.h"
#include "mlx5_rxtx.h"
#include "mlx5_defs.h"
struct mlx5_counter_ctrl {
/* Name of the counter. */
char dpdk_name[RTE_ETH_XSTATS_NAME_SIZE];
/* Name of the counter on the device table. */
char ctr_name[RTE_ETH_XSTATS_NAME_SIZE];
uint32_t ib:1; /**< Nonzero for IB counters. */
};
static const struct mlx5_counter_ctrl mlx5_counters_init[] = {
{
.dpdk_name = "rx_port_unicast_bytes",
.ctr_name = "rx_vport_unicast_bytes",
},
{
.dpdk_name = "rx_port_multicast_bytes",
.ctr_name = "rx_vport_multicast_bytes",
},
{
.dpdk_name = "rx_port_broadcast_bytes",
.ctr_name = "rx_vport_broadcast_bytes",
},
{
.dpdk_name = "rx_port_unicast_packets",
.ctr_name = "rx_vport_unicast_packets",
},
{
.dpdk_name = "rx_port_multicast_packets",
.ctr_name = "rx_vport_multicast_packets",
},
{
.dpdk_name = "rx_port_broadcast_packets",
.ctr_name = "rx_vport_broadcast_packets",
},
{
.dpdk_name = "tx_port_unicast_bytes",
.ctr_name = "tx_vport_unicast_bytes",
},
{
.dpdk_name = "tx_port_multicast_bytes",
.ctr_name = "tx_vport_multicast_bytes",
},
{
.dpdk_name = "tx_port_broadcast_bytes",
.ctr_name = "tx_vport_broadcast_bytes",
},
{
.dpdk_name = "tx_port_unicast_packets",
.ctr_name = "tx_vport_unicast_packets",
},
{
.dpdk_name = "tx_port_multicast_packets",
.ctr_name = "tx_vport_multicast_packets",
},
{
.dpdk_name = "tx_port_broadcast_packets",
.ctr_name = "tx_vport_broadcast_packets",
},
{
.dpdk_name = "rx_wqe_err",
.ctr_name = "rx_wqe_err",
},
{
.dpdk_name = "rx_crc_errors_phy",
.ctr_name = "rx_crc_errors_phy",
},
{
.dpdk_name = "rx_in_range_len_errors_phy",
.ctr_name = "rx_in_range_len_errors_phy",
},
{
.dpdk_name = "rx_symbol_err_phy",
.ctr_name = "rx_symbol_err_phy",
},
{
.dpdk_name = "tx_errors_phy",
.ctr_name = "tx_errors_phy",
},
{
.dpdk_name = "rx_out_of_buffer",
.ctr_name = "out_of_buffer",
.ib = 1,
},
{
.dpdk_name = "tx_packets_phy",
.ctr_name = "tx_packets_phy",
},
{
.dpdk_name = "rx_packets_phy",
.ctr_name = "rx_packets_phy",
},
{
.dpdk_name = "tx_bytes_phy",
.ctr_name = "tx_bytes_phy",
},
{
.dpdk_name = "rx_bytes_phy",
.ctr_name = "rx_bytes_phy",
},
};
static const unsigned int xstats_n = RTE_DIM(mlx5_counters_init);
/**
* Read device counters table.
*
* @param priv
* Pointer to private structure.
* @param[out] stats
* Counters table output buffer.
*
* @return
* 0 on success and stats is filled, negative on error.
*/
static int
priv_read_dev_counters(struct priv *priv, uint64_t *stats)
{
struct mlx5_xstats_ctrl *xstats_ctrl = &priv->xstats_ctrl;
unsigned int i;
struct ifreq ifr;
unsigned int stats_sz = xstats_ctrl->stats_n * sizeof(uint64_t);
unsigned char et_stat_buf[sizeof(struct ethtool_stats) + stats_sz];
struct ethtool_stats *et_stats = (struct ethtool_stats *)et_stat_buf;
et_stats->cmd = ETHTOOL_GSTATS;
et_stats->n_stats = xstats_ctrl->stats_n;
ifr.ifr_data = (caddr_t)et_stats;
if (priv_ifreq(priv, SIOCETHTOOL, &ifr) != 0) {
WARN("unable to read statistic values from device");
return -1;
}
for (i = 0; i != xstats_n; ++i) {
if (mlx5_counters_init[i].ib) {
FILE *file;
MKSTR(path, "%s/ports/1/hw_counters/%s",
priv->ibdev_path,
mlx5_counters_init[i].ctr_name);
file = fopen(path, "rb");
if (file) {
int n = fscanf(file, "%" SCNu64, &stats[i]);
fclose(file);
if (n != 1)
stats[i] = 0;
}
} else {
stats[i] = (uint64_t)
et_stats->data[xstats_ctrl->dev_table_idx[i]];
}
}
return 0;
}
/**
* Query the number of statistics provided by ETHTOOL.
*
* @param priv
* Pointer to private structure.
*
* @return
* Number of statistics on success, -1 on error.
*/
static int
priv_ethtool_get_stats_n(struct priv *priv) {
struct ethtool_drvinfo drvinfo;
struct ifreq ifr;
drvinfo.cmd = ETHTOOL_GDRVINFO;
ifr.ifr_data = (caddr_t)&drvinfo;
if (priv_ifreq(priv, SIOCETHTOOL, &ifr) != 0) {
WARN("unable to query number of statistics");
return -1;
}
return drvinfo.n_stats;
}
/**
* Init the structures to read device counters.
*
* @param priv
* Pointer to private structure.
*/
void
priv_xstats_init(struct priv *priv)
{
struct mlx5_xstats_ctrl *xstats_ctrl = &priv->xstats_ctrl;
unsigned int i;
unsigned int j;
struct ifreq ifr;
struct ethtool_gstrings *strings = NULL;
unsigned int dev_stats_n;
unsigned int str_sz;
dev_stats_n = priv_ethtool_get_stats_n(priv);
if (dev_stats_n < 1) {
WARN("no extended statistics available");
return;
}
xstats_ctrl->stats_n = dev_stats_n;
/* Allocate memory to grab stat names and values. */
str_sz = dev_stats_n * ETH_GSTRING_LEN;
strings = (struct ethtool_gstrings *)
rte_malloc("xstats_strings",
str_sz + sizeof(struct ethtool_gstrings), 0);
if (!strings) {
WARN("unable to allocate memory for xstats");
return;
}
strings->cmd = ETHTOOL_GSTRINGS;
strings->string_set = ETH_SS_STATS;
strings->len = dev_stats_n;
ifr.ifr_data = (caddr_t)strings;
if (priv_ifreq(priv, SIOCETHTOOL, &ifr) != 0) {
WARN("unable to get statistic names");
goto free;
}
for (j = 0; j != xstats_n; ++j)
xstats_ctrl->dev_table_idx[j] = dev_stats_n;
for (i = 0; i != dev_stats_n; ++i) {
const char *curr_string = (const char *)
&strings->data[i * ETH_GSTRING_LEN];
for (j = 0; j != xstats_n; ++j) {
if (!strcmp(mlx5_counters_init[j].ctr_name,
curr_string)) {
xstats_ctrl->dev_table_idx[j] = i;
break;
}
}
}
for (j = 0; j != xstats_n; ++j) {
if (mlx5_counters_init[j].ib)
continue;
if (xstats_ctrl->dev_table_idx[j] >= dev_stats_n) {
WARN("counter \"%s\" is not recognized",
mlx5_counters_init[j].dpdk_name);
goto free;
}
}
/* Copy to base at first time. */
assert(xstats_n <= MLX5_MAX_XSTATS);
priv_read_dev_counters(priv, xstats_ctrl->base);
free:
rte_free(strings);
}
/**
* Get device extended statistics.
*
* @param priv
* Pointer to private structure.
* @param[out] stats
* Pointer to rte extended stats table.
*
* @return
* Number of extended stats on success and stats is filled,
* negative on error.
*/
static int
priv_xstats_get(struct priv *priv, struct rte_eth_xstat *stats)
{
struct mlx5_xstats_ctrl *xstats_ctrl = &priv->xstats_ctrl;
unsigned int i;
unsigned int n = xstats_n;
uint64_t counters[n];
if (priv_read_dev_counters(priv, counters) < 0)
return -1;
for (i = 0; i != xstats_n; ++i) {
stats[i].id = i;
stats[i].value = (counters[i] - xstats_ctrl->base[i]);
}
return n;
}
/**
* Reset device extended statistics.
*
* @param priv
* Pointer to private structure.
*/
static void
priv_xstats_reset(struct priv *priv)
{
struct mlx5_xstats_ctrl *xstats_ctrl = &priv->xstats_ctrl;
unsigned int i;
unsigned int n = xstats_n;
uint64_t counters[n];
if (priv_read_dev_counters(priv, counters) < 0)
return;
for (i = 0; i != n; ++i)
xstats_ctrl->base[i] = counters[i];
}
/**
* DPDK callback to get device statistics.
*
* @param dev
* Pointer to Ethernet device structure.
* @param[out] stats
* Stats structure output buffer.
*/
int
mlx5_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *stats)
{
struct priv *priv = dev->data->dev_private;
struct rte_eth_stats tmp = {0};
unsigned int i;
unsigned int idx;
priv_lock(priv);
/* Add software counters. */
for (i = 0; (i != priv->rxqs_n); ++i) {
struct mlx5_rxq_data *rxq = (*priv->rxqs)[i];
if (rxq == NULL)
continue;
idx = rxq->stats.idx;
if (idx < RTE_ETHDEV_QUEUE_STAT_CNTRS) {
#ifdef MLX5_PMD_SOFT_COUNTERS
tmp.q_ipackets[idx] += rxq->stats.ipackets;
tmp.q_ibytes[idx] += rxq->stats.ibytes;
#endif
tmp.q_errors[idx] += (rxq->stats.idropped +
rxq->stats.rx_nombuf);
}
#ifdef MLX5_PMD_SOFT_COUNTERS
tmp.ipackets += rxq->stats.ipackets;
tmp.ibytes += rxq->stats.ibytes;
#endif
tmp.ierrors += rxq->stats.idropped;
tmp.rx_nombuf += rxq->stats.rx_nombuf;
}
for (i = 0; (i != priv->txqs_n); ++i) {
struct mlx5_txq_data *txq = (*priv->txqs)[i];
if (txq == NULL)
continue;
idx = txq->stats.idx;
if (idx < RTE_ETHDEV_QUEUE_STAT_CNTRS) {
#ifdef MLX5_PMD_SOFT_COUNTERS
tmp.q_opackets[idx] += txq->stats.opackets;
tmp.q_obytes[idx] += txq->stats.obytes;
#endif
tmp.q_errors[idx] += txq->stats.oerrors;
}
#ifdef MLX5_PMD_SOFT_COUNTERS
tmp.opackets += txq->stats.opackets;
tmp.obytes += txq->stats.obytes;
#endif
tmp.oerrors += txq->stats.oerrors;
}
#ifndef MLX5_PMD_SOFT_COUNTERS
/* FIXME: retrieve and add hardware counters. */
#endif
*stats = tmp;
priv_unlock(priv);
return 0;
}
/**
* DPDK callback to clear device statistics.
*
* @param dev
* Pointer to Ethernet device structure.
*/
void
mlx5_stats_reset(struct rte_eth_dev *dev)
{
struct priv *priv = dev->data->dev_private;
unsigned int i;
unsigned int idx;
priv_lock(priv);
for (i = 0; (i != priv->rxqs_n); ++i) {
if ((*priv->rxqs)[i] == NULL)
continue;
idx = (*priv->rxqs)[i]->stats.idx;
(*priv->rxqs)[i]->stats =
(struct mlx5_rxq_stats){ .idx = idx };
}
for (i = 0; (i != priv->txqs_n); ++i) {
if ((*priv->txqs)[i] == NULL)
continue;
idx = (*priv->txqs)[i]->stats.idx;
(*priv->txqs)[i]->stats =
(struct mlx5_txq_stats){ .idx = idx };
}
#ifndef MLX5_PMD_SOFT_COUNTERS
/* FIXME: reset hardware counters. */
#endif
priv_unlock(priv);
}
/**
* DPDK callback to get extended device statistics.
*
* @param dev
* Pointer to Ethernet device structure.
* @param[out] stats
* Stats table output buffer.
* @param n
* The size of the stats table.
*
* @return
* Number of xstats on success, negative on failure.
*/
int
mlx5_xstats_get(struct rte_eth_dev *dev,
struct rte_eth_xstat *stats, unsigned int n)
{
struct priv *priv = dev->data->dev_private;
int ret = xstats_n;
if (n >= xstats_n && stats) {
struct mlx5_xstats_ctrl *xstats_ctrl = &priv->xstats_ctrl;
int stats_n;
priv_lock(priv);
stats_n = priv_ethtool_get_stats_n(priv);
if (stats_n < 0) {
priv_unlock(priv);
return -1;
}
if (xstats_ctrl->stats_n != stats_n)
priv_xstats_init(priv);
ret = priv_xstats_get(priv, stats);
priv_unlock(priv);
}
return ret;
}
/**
* DPDK callback to clear device extended statistics.
*
* @param dev
* Pointer to Ethernet device structure.
*/
void
mlx5_xstats_reset(struct rte_eth_dev *dev)
{
struct priv *priv = dev->data->dev_private;
struct mlx5_xstats_ctrl *xstats_ctrl = &priv->xstats_ctrl;
int stats_n;
priv_lock(priv);
stats_n = priv_ethtool_get_stats_n(priv);
if (stats_n < 0)
goto unlock;
if (xstats_ctrl->stats_n != stats_n)
priv_xstats_init(priv);
priv_xstats_reset(priv);
unlock:
priv_unlock(priv);
}
/**
* DPDK callback to retrieve names of extended device statistics
*
* @param dev
* Pointer to Ethernet device structure.
* @param[out] xstats_names
* Buffer to insert names into.
* @param n
* Number of names.
*
* @return
* Number of xstats names.
*/
int
mlx5_xstats_get_names(struct rte_eth_dev *dev,
struct rte_eth_xstat_name *xstats_names, unsigned int n)
{
struct priv *priv = dev->data->dev_private;
unsigned int i;
if (n >= xstats_n && xstats_names) {
priv_lock(priv);
for (i = 0; i != xstats_n; ++i) {
strncpy(xstats_names[i].name,
mlx5_counters_init[i].dpdk_name,
RTE_ETH_XSTATS_NAME_SIZE);
xstats_names[i].name[RTE_ETH_XSTATS_NAME_SIZE - 1] = 0;
}
priv_unlock(priv);
}
return xstats_n;
}