diff mbox

[API-NEXT,PATCHv2,1/5] linux-generic: sockets: implement pktio statistics counters

Message ID 1447839268-20423-2-git-send-email-maxim.uvarov@linaro.org
State Superseded
Headers show

Commit Message

Maxim Uvarov Nov. 18, 2015, 9:34 a.m. UTC
Signed-off-by: Maxim Uvarov <maxim.uvarov@linaro.org>
---
 platform/linux-generic/Makefile.am                 |   2 +
 .../linux-generic/include/odp_packet_io_internal.h |  11 ++
 platform/linux-generic/include/odp_packet_socket.h |   6 +
 platform/linux-generic/odp_packet_io.c             |  53 +++++++
 platform/linux-generic/pktio/ethtool.c             | 160 +++++++++++++++++++++
 platform/linux-generic/pktio/socket.c              |  84 +++++++++++
 platform/linux-generic/pktio/socket_mmap.c         |  19 +++
 platform/linux-generic/pktio/sysfs.c               |  71 +++++++++
 8 files changed, 406 insertions(+)
 create mode 100644 platform/linux-generic/pktio/ethtool.c
 create mode 100644 platform/linux-generic/pktio/sysfs.c

Comments

Stuart Haslam Nov. 18, 2015, 2:24 p.m. UTC | #1
On Wed, Nov 18, 2015 at 12:34:24PM +0300, Maxim Uvarov wrote:
> Signed-off-by: Maxim Uvarov <maxim.uvarov@linaro.org>
> ---
>  platform/linux-generic/Makefile.am                 |   2 +
>  .../linux-generic/include/odp_packet_io_internal.h |  11 ++
>  platform/linux-generic/include/odp_packet_socket.h |   6 +
>  platform/linux-generic/odp_packet_io.c             |  53 +++++++
>  platform/linux-generic/pktio/ethtool.c             | 160 +++++++++++++++++++++
>  platform/linux-generic/pktio/socket.c              |  84 +++++++++++
>  platform/linux-generic/pktio/socket_mmap.c         |  19 +++
>  platform/linux-generic/pktio/sysfs.c               |  71 +++++++++
>  8 files changed, 406 insertions(+)
>  create mode 100644 platform/linux-generic/pktio/ethtool.c
>  create mode 100644 platform/linux-generic/pktio/sysfs.c
> 
> diff --git a/platform/linux-generic/Makefile.am b/platform/linux-generic/Makefile.am
> index 610c79c..71b030e 100644
> --- a/platform/linux-generic/Makefile.am
> +++ b/platform/linux-generic/Makefile.am
> @@ -165,11 +165,13 @@ __LIB__libodp_la_SOURCES = \
>  			   odp_packet.c \
>  			   odp_packet_flags.c \
>  			   odp_packet_io.c \
> +			   pktio/ethtool.c \
>  			   pktio/io_ops.c \
>  			   pktio/loop.c \
>  			   pktio/netmap.c \
>  			   pktio/socket.c \
>  			   pktio/socket_mmap.c \
> +			   pktio/sysfs.c \
>  			   odp_pool.c \
>  			   odp_queue.c \
>  			   odp_rwlock.c \
> diff --git a/platform/linux-generic/include/odp_packet_io_internal.h b/platform/linux-generic/include/odp_packet_io_internal.h
> index 1a1118c..6566978 100644
> --- a/platform/linux-generic/include/odp_packet_io_internal.h
> +++ b/platform/linux-generic/include/odp_packet_io_internal.h
> @@ -84,6 +84,9 @@ struct pktio_entry {
>  		STATE_STOP
>  	} state;
>  	classifier_t cls;		/**< classifier linked with this pktio*/
> +	odp_pktio_stats_t stats;	/**< statistic counters for pktio */
> +	int use_ethtool;		/**< 1 - use ethtool,
> +					     0 - sysfs for statistics */
>  	char name[PKTIO_NAME_LEN];	/**< name of pktio provided to
>  					   pktio_open() */
>  	odp_pktio_param_t param;
> @@ -107,6 +110,8 @@ typedef struct pktio_if_ops {
>  	int (*close)(pktio_entry_t *pktio_entry);
>  	int (*start)(pktio_entry_t *pktio_entry);
>  	int (*stop)(pktio_entry_t *pktio_entry);
> +	int (*stats)(pktio_entry_t *pktio_entry, odp_pktio_stats_t *stats);
> +	int (*stats_reset)(pktio_entry_t *pktio_entry);
>  	int (*recv)(pktio_entry_t *pktio_entry, odp_packet_t pkt_table[],
>  		    unsigned len);
>  	int (*send)(pktio_entry_t *pktio_entry, odp_packet_t pkt_table[],
> @@ -159,6 +164,12 @@ extern const pktio_if_ops_t pcap_pktio_ops;
>  #endif
>  extern const pktio_if_ops_t * const pktio_if_ops[];
>  
> +int sysfs_stats(pktio_entry_t *pktio_entry,
> +		odp_pktio_stats_t *stats);
> +int sock_stats_reset(pktio_entry_t *pktio_entry);
> +int sock_stats(pktio_entry_t *pktio_entry,
> +	       odp_pktio_stats_t *stats);
> +
>  #ifdef __cplusplus
>  }
>  #endif
> diff --git a/platform/linux-generic/include/odp_packet_socket.h b/platform/linux-generic/include/odp_packet_socket.h
> index a5e0eb3..0836e3c 100644
> --- a/platform/linux-generic/include/odp_packet_socket.h
> +++ b/platform/linux-generic/include/odp_packet_socket.h
> @@ -18,6 +18,7 @@
>  #include <odp/debug.h>
>  #include <odp/pool.h>
>  #include <odp/packet.h>
> +#include <odp/packet_io.h>
>  
>  #include <linux/version.h>
>  
> @@ -114,4 +115,9 @@ int promisc_mode_set_fd(int fd, const char *name, int enable);
>   */
>  int promisc_mode_get_fd(int fd, const char *name);
>  
> +/**
> + * Get ethtool statistics of a packet socket
> + */
> +int ethtool_stats_get_fd(int fd, const char *name, odp_pktio_stats_t *stats);
> +
>  #endif
> diff --git a/platform/linux-generic/odp_packet_io.c b/platform/linux-generic/odp_packet_io.c
> index 3ef400f..ba97629 100644
> --- a/platform/linux-generic/odp_packet_io.c
> +++ b/platform/linux-generic/odp_packet_io.c
> @@ -862,3 +862,56 @@ void odp_pktio_print(odp_pktio_t id)
>  
>  	ODP_PRINT("\n%s\n", str);
>  }
> +
> +int odp_pktio_stats(odp_pktio_t pktio,
> +		    odp_pktio_stats_t *stats)
> +{
> +	pktio_entry_t *entry;
> +	int ret = -1;
> +
> +	entry = get_pktio_entry(pktio);
> +	if (entry == NULL) {
> +		ODP_DBG("pktio entry %d does not exist\n", pktio);
> +		return -1;
> +	}
> +
> +	lock_entry(entry);
> +
> +	if (odp_unlikely(is_free(entry))) {
> +		unlock_entry(entry);
> +		ODP_DBG("already freed pktio\n");
> +		return -1;
> +	}
> +
> +	if (entry->s.ops->stats)
> +		ret = entry->s.ops->stats(entry, stats);
> +	unlock_entry(entry);
> +
> +	return ret;
> +}
> +
> +int odp_pktio_stats_reset(odp_pktio_t pktio)
> +{
> +	pktio_entry_t *entry;
> +	int ret = -1;
> +
> +	entry = get_pktio_entry(pktio);
> +	if (entry == NULL) {
> +		ODP_DBG("pktio entry %d does not exist\n", pktio);
> +		return -1;
> +	}
> +
> +	lock_entry(entry);
> +
> +	if (odp_unlikely(is_free(entry))) {
> +		unlock_entry(entry);
> +		ODP_DBG("already freed pktio\n");
> +		return -1;
> +	}
> +
> +	if (entry->s.ops->stats)
> +		ret = entry->s.ops->stats_reset(entry);
> +	unlock_entry(entry);
> +
> +	return ret;
> +}
> diff --git a/platform/linux-generic/pktio/ethtool.c b/platform/linux-generic/pktio/ethtool.c
> new file mode 100644
> index 0000000..b689015
> --- /dev/null
> +++ b/platform/linux-generic/pktio/ethtool.c
> @@ -0,0 +1,160 @@
> +/* Copyright (c) 2015, Linaro Limited
> + * All rights reserved.
> + *
> + * SPDX-License-Identifier:     BSD-3-Clause
> + */
> +
> +#include <sys/ioctl.h>
> +#include <netinet/in.h>
> +#include <linux/sockios.h>
> +#include <linux/if.h>
> +#include <linux/ethtool.h>
> +#include <errno.h>
> +#include <net/if.h>
> +
> +#include <odp.h>
> +#include <odp_packet_socket.h>
> +#include <odp_debug_internal.h>
> +
> +static struct ethtool_gstrings *get_stringset(int fd, struct ifreq *ifr)
> +{
> +	struct {
> +		struct ethtool_sset_info hdr;
> +		uint32_t buf[1];
> +	} sset_info;
> +	struct ethtool_drvinfo drvinfo;
> +	uint32_t len;
> +	struct ethtool_gstrings *strings;
> +	ptrdiff_t drvinfo_offset = offsetof(struct ethtool_drvinfo, n_stats);
> +
> +	sset_info.hdr.cmd = ETHTOOL_GSSET_INFO;
> +	sset_info.hdr.reserved = 0;
> +	sset_info.hdr.sset_mask = 1ULL << ETH_SS_STATS;
> +	ifr->ifr_data =  &sset_info;
> +	if (ioctl(fd, SIOCETHTOOL, ifr) == 0) {
> +		len = sset_info.hdr.sset_mask ? sset_info.hdr.data[0] : 0;
> +	} else if (errno == EOPNOTSUPP && drvinfo_offset != 0) {
> +		/* Fallback for old kernel versions */
> +		drvinfo.cmd = ETHTOOL_GDRVINFO;
> +		ifr->ifr_data = &drvinfo;
> +		if (ioctl(fd, SIOCETHTOOL, ifr)) {
> +			__odp_errno = errno;
> +			ODP_ERR("Cannot get stats information\n");
> +			return NULL;
> +		}
> +		len = *(uint32_t *)((char *)&drvinfo + drvinfo_offset);
> +	} else {
> +		__odp_errno = errno;
> +		return NULL;
> +	}
> +
> +	if (!len) {
> +		ODP_ERR("len is zero");
> +		return NULL;
> +	}
> +
> +	strings = calloc(1, sizeof(*strings) + len * ETH_GSTRING_LEN);
> +	if (!strings) {
> +		ODP_ERR("alloc failed\n");
> +		return NULL;
> +	}
> +
> +	strings->cmd = ETHTOOL_GSTRINGS;
> +	strings->string_set = ETH_SS_STATS;
> +	strings->len = len;
> +	ifr->ifr_data = strings;
> +	if (ioctl(fd, SIOCETHTOOL, ifr)) {
> +		__odp_errno = errno;
> +		ODP_ERR("Cannot get stats information\n");
> +		free(strings);
> +		return NULL;
> +	}
> +
> +	return strings;
> +}
> +
> +static int ethtool_stats(int fd, struct ifreq *ifr, odp_pktio_stats_t *stats)
> +{
> +	struct ethtool_gstrings *strings;
> +	struct ethtool_stats *estats;
> +	unsigned int n_stats, i;
> +	int err;
> +	int cnts;
> +
> +	strings = get_stringset(fd, ifr);
> +	if (!strings)
> +		return -1;
> +
> +	n_stats = strings->len;
> +	if (n_stats < 1) {
> +		ODP_ERR("no stats available\n");
> +		free(strings);
> +		return -1;
> +	}
> +
> +	estats = calloc(1, n_stats * sizeof(uint64_t) +
> +			sizeof(struct ethtool_stats));
> +	if (!estats) {
> +		free(strings);
> +		return -1;
> +	}
> +
> +	estats->cmd = ETHTOOL_GSTATS;
> +	estats->n_stats = n_stats;
> +	ifr->ifr_data = stats;
> +	err = ioctl(fd, SIOCETHTOOL, ifr);
> +	if (err < 0) {
> +		__odp_errno = errno;
> +		free(strings);
> +		free(estats);
> +		return -1;
> +	}
> +
> +	cnts = 0;
> +	for (i = 0; i < n_stats; i++) {
> +		char *cnt = (char *)&strings->data[i * ETH_GSTRING_LEN];
> +		uint64_t val = estats->data[i];
> +
> +		if (!strcmp(cnt, "rx_octets")) {
> +			stats->in_octets = val;
> +			cnts++;
> +		} else if (!strcmp(cnt, "rx_ucast_packets")) {
> +			stats->in_ucast_pkts = val;
> +			cnts++;

This won't work on all devices, ixgbe for example doesn't have this
string. As per my email on Friday it looks like the strings used in
ethtool aren't standardised:

https://lists.linaro.org/pipermail/lng-odp/2015-November/017351.html

Also whether they're updated while in netmap mode seems to be somewhat
device specific. So we still don't have a universally working solution
for netmap.

I think the best way around this for now is just to ensure that the
validation test doesn't fail if an implementation reports 0 for all
counters, since the spec allows that. We then may or may not get stats
from netmap, depending on the device used.

> +		} else if (!strcmp(cnt, "rx_discards")) {
> +			stats->in_discards = val;
> +			cnts++;
> +		} else if (!strcmp(cnt, "rx_errors")) {
> +			stats->in_errors = val;
> +			cnts++;
> +		} else if (!strcmp(cnt, "tx_octets")) {
> +			stats->out_octets = val;
> +			cnts++;
> +		} else if (!strcmp(cnt, "tx_ucast_packets")) {
> +			stats->out_ucast_pkts = val;
> +			cnts++;
> +		} else if (!strcmp(cnt, "tx_discards")) {
> +			stats->out_discards = val;
> +			cnts++;
> +		} else if (!strcmp(cnt, "tx_errors")) {
> +			stats->out_errors = val;
> +			cnts++;
> +		}
> +	}
> +
> +	free(strings);
> +	free(estats);
> +
> +	if (cnts < 8)
> +		return -1;

This shouldn't be treated as a failure.

> +
> +	return 0;
> +}
> +
> +int ethtool_stats_get_fd(int fd, const char *name, odp_pktio_stats_t *stats)
> +{
> +	struct ifreq ifr;
> +
> +	snprintf(ifr.ifr_name, IF_NAMESIZE, "%s", name);
> +	return ethtool_stats(fd, &ifr, stats);
> +}
> diff --git a/platform/linux-generic/pktio/socket.c b/platform/linux-generic/pktio/socket.c
> index 5f5e0ae..9f896a9 100644
> --- a/platform/linux-generic/pktio/socket.c
> +++ b/platform/linux-generic/pktio/socket.c
> @@ -209,6 +209,7 @@ static int sock_setup_pkt(pktio_entry_t *pktio_entry, const char *netdev,
>  	struct ifreq ethreq;
>  	struct sockaddr_ll sa_ll;
>  	pkt_sock_t *pkt_sock = &pktio_entry->s.pkt_sock;
> +	odp_pktio_stats_t cur_stats;
>  
>  	/* Init pktio entry */
>  	memset(pkt_sock, 0, sizeof(*pkt_sock));
> @@ -254,6 +255,22 @@ static int sock_setup_pkt(pktio_entry_t *pktio_entry, const char *netdev,
>  		goto error;
>  	}
>  
> +	err = ethtool_stats_get_fd(pktio_entry->s.pkt_sock.sockfd,
> +				   pktio_entry->s.name,
> +				   &cur_stats);
> +	if (err != 0) {
> +		err = sysfs_stats(pktio_entry, &cur_stats);
> +		if (err != 0)
> +			ODP_ABORT("statistic counters are not reachable\n");
> +		pktio_entry->s.use_ethtool = 0;
> +	} else {
> +		pktio_entry->s.use_ethtool = 1;
> +	}
> +
> +	err = sock_stats_reset(pktio_entry);
> +	if (err != 0)
> +		goto error;
> +
>  	return 0;
>  
>  error:
> @@ -467,6 +484,71 @@ static int sock_promisc_mode_get(pktio_entry_t *pktio_entry)
>  				   pktio_entry->s.name);
>  }
>  
> +int sock_stats(pktio_entry_t *pktio_entry,
> +	       odp_pktio_stats_t *stats)
> +{
> +	odp_pktio_stats_t cur_stats;
> +	int ret;
> +
> +	memset(&cur_stats, 0, sizeof(odp_pktio_stats_t));
> +	if (pktio_entry->s.use_ethtool) {
> +		ret =  ethtool_stats_get_fd(pktio_entry->s.pkt_sock.sockfd,
> +					    pktio_entry->s.name,
> +					    &cur_stats);
> +	} else {
> +		ret = sysfs_stats(pktio_entry, &cur_stats);
> +	}
> +	if (ret)
> +		ODP_ABORT("getting statistics error\n");
> +
> +	stats->in_octets = cur_stats.in_octets -
> +				pktio_entry->s.stats.in_octets;
> +	stats->in_ucast_pkts = cur_stats.in_ucast_pkts -
> +				pktio_entry->s.stats.in_ucast_pkts;
> +	stats->in_discards = cur_stats.in_discards -
> +				pktio_entry->s.stats.in_discards;
> +	stats->in_errors = cur_stats.in_errors -
> +				pktio_entry->s.stats.in_errors;
> +	stats->in_unknown_protos = cur_stats.in_unknown_protos -
> +				pktio_entry->s.stats.in_unknown_protos;
> +
> +	stats->out_octets = cur_stats.out_octets -
> +				pktio_entry->s.stats.out_octets;
> +	stats->out_ucast_pkts = cur_stats.out_ucast_pkts -
> +				pktio_entry->s.stats.out_ucast_pkts;
> +	stats->out_discards = cur_stats.out_discards -
> +				pktio_entry->s.stats.out_discards;
> +	stats->out_errors = cur_stats.out_errors -
> +				pktio_entry->s.stats.out_errors;
> +
> +	return 0;
> +}
> +
> +int sock_stats_reset(pktio_entry_t *pktio_entry)
> +{
> +	int err = 0;
> +	odp_pktio_stats_t cur_stats;
> +
> +	memset(&cur_stats, 0, sizeof(odp_pktio_stats_t));
> +
> +	if (pktio_entry->s.use_ethtool) {
> +		err = ethtool_stats_get_fd(pktio_entry->s.pkt_sock.sockfd,
> +					   pktio_entry->s.name,
> +					   &cur_stats);
> +	} else {
> +		err = sysfs_stats(pktio_entry, &cur_stats);
> +	}
> +
> +	if (err != 0) {
> +		ODP_ERR("stats error\n");
> +	} else {
> +		memcpy(&pktio_entry->s.stats, &cur_stats,
> +		       sizeof(odp_pktio_stats_t));
> +	}
> +
> +	return err;
> +}
> +
>  const pktio_if_ops_t sock_mmsg_pktio_ops = {
>  	.init = NULL,
>  	.term = NULL,
> @@ -474,6 +556,8 @@ const pktio_if_ops_t sock_mmsg_pktio_ops = {
>  	.close = sock_close,
>  	.start = NULL,
>  	.stop = NULL,
> +	.stats = sock_stats,
> +	.stats_reset = sock_stats_reset,
>  	.recv = sock_mmsg_recv,
>  	.send = sock_mmsg_send,
>  	.mtu_get = sock_mtu_get,
> diff --git a/platform/linux-generic/pktio/socket_mmap.c b/platform/linux-generic/pktio/socket_mmap.c
> index 79ff82d..06bdad6 100644
> --- a/platform/linux-generic/pktio/socket_mmap.c
> +++ b/platform/linux-generic/pktio/socket_mmap.c
> @@ -408,6 +408,7 @@ static int sock_mmap_open(odp_pktio_t id ODP_UNUSED,
>  {
>  	int if_idx;
>  	int ret = 0;
> +	odp_pktio_stats_t cur_stats;
>  
>  	if (getenv("ODP_PKTIO_DISABLE_SOCKET_MMAP"))
>  		return -1;
> @@ -467,6 +468,22 @@ static int sock_mmap_open(odp_pktio_t id ODP_UNUSED,
>  			goto error;
>  	}
>  
> +	ret = ethtool_stats_get_fd(pktio_entry->s.pkt_sock.sockfd,
> +				   pktio_entry->s.name,
> +				   &cur_stats);
> +	if (ret != 0) {
> +		ret = sysfs_stats(pktio_entry, &cur_stats);
> +		if (ret != 0)
> +			ODP_ABORT("statistic counters are not reachable\n");
> +		pktio_entry->s.use_ethtool = 0;
> +	} else {
> +		pktio_entry->s.use_ethtool = 1;
> +	}
> +
> +	ret = sock_stats_reset(pktio_entry);
> +	if (ret != 0)
> +		goto error;
> +
>  	return 0;
>  
>  error:
> @@ -525,6 +542,8 @@ const pktio_if_ops_t sock_mmap_pktio_ops = {
>  	.close = sock_mmap_close,
>  	.start = NULL,
>  	.stop = NULL,
> +	.stats = sock_stats,
> +	.stats_reset = sock_stats_reset,
>  	.recv = sock_mmap_recv,
>  	.send = sock_mmap_send,
>  	.mtu_get = sock_mmap_mtu_get,
> diff --git a/platform/linux-generic/pktio/sysfs.c b/platform/linux-generic/pktio/sysfs.c
> new file mode 100644
> index 0000000..afa5bfb
> --- /dev/null
> +++ b/platform/linux-generic/pktio/sysfs.c
> @@ -0,0 +1,71 @@
> +/* Copyright (c) 2015, Linaro Limited
> + * All rights reserved.
> + *
> + * SPDX-License-Identifier:     BSD-3-Clause
> + */
> +
> +#include <odp.h>
> +#include <odp_packet_io_internal.h>
> +#include <stdio.h>
> +
> +static int sysfs_get_val(const char *fname, uint64_t *val)
> +{
> +	FILE  *file;
> +	char str[128];
> +	uint64_t ret = -1;

Should be int

> +
> +	file = fopen(fname, "rt");
> +	if (file == NULL) {
> +		/* File not found */

Only if errno == ENOENT, some (maybe all?) other failures should be
reported as failures.

> +		return 0;
> +	}
> +
> +	if (fgets(str, sizeof(str), file) != NULL)
> +		ret = sscanf(str, "%" SCNx64, val);
> +
> +	(void)fclose(file);
> +
> +	if (ret != 1) {
> +		ODP_ERR("read %s\n", fname);
> +		return -1;
> +	}
> +
> +	return 0;
> +}
> +
> +int sysfs_stats(pktio_entry_t *pktio_entry,
> +		odp_pktio_stats_t *stats)
> +{
> +	char fname[256];
> +	const char *dev = pktio_entry->s.name;
> +	int ret = 0;
> +
> +	sprintf(fname, "/sys/class/net/%s/statistics/rx_bytes", dev);
> +	ret -= sysfs_get_val(fname, &stats->in_octets);
> +
> +	sprintf(fname, "/sys/class/net/%s/statistics/rx_packets", dev);
> +	ret -= sysfs_get_val(fname, &stats->in_ucast_pkts);
> +
> +	sprintf(fname, "/sys/class/net/%s/statistics/rx_droppped", dev);
> +	ret -= sysfs_get_val(fname, &stats->in_discards);
> +
> +	sprintf(fname, "/sys/class/net/%s/statistics/rx_errors", dev);
> +	ret -= sysfs_get_val(fname, &stats->in_errors);
> +
> +	/* stats->in_unknown_protos is not supported in sysfs */
> +
> +	sprintf(fname, "/sys/class/net/%s/statistics/tx_bytes", dev);
> +	ret -= sysfs_get_val(fname, &stats->out_octets);
> +
> +	sprintf(fname, "/sys/class/net/%s/statistics/tx_packets", dev);
> +	ret -= sysfs_get_val(fname, &stats->out_ucast_pkts);
> +
> +	sprintf(fname, "/sys/class/net/%s/statistics/tx_dropped", dev);
> +	ret -= sysfs_get_val(fname, &stats->out_discards);
> +
> +	sprintf(fname, "/sys/class/net/%s/statistics/tx_errors", dev);
> +	ret -= sysfs_get_val(fname, &stats->out_errors);
> +
> +	return ret;
> +}
> +
> -- 
> 1.9.1
>
Stuart Haslam Nov. 18, 2015, 3 p.m. UTC | #2
On Wed, Nov 18, 2015 at 12:34:24PM +0300, Maxim Uvarov wrote:
> Signed-off-by: Maxim Uvarov <maxim.uvarov@linaro.org>
> ---
>  platform/linux-generic/Makefile.am                 |   2 +
>  .../linux-generic/include/odp_packet_io_internal.h |  11 ++
>  platform/linux-generic/include/odp_packet_socket.h |   6 +
>  platform/linux-generic/odp_packet_io.c             |  53 +++++++
>  platform/linux-generic/pktio/ethtool.c             | 160 +++++++++++++++++++++
>  platform/linux-generic/pktio/socket.c              |  84 +++++++++++
>  platform/linux-generic/pktio/socket_mmap.c         |  19 +++
>  platform/linux-generic/pktio/sysfs.c               |  71 +++++++++
>  8 files changed, 406 insertions(+)
>  create mode 100644 platform/linux-generic/pktio/ethtool.c
>  create mode 100644 platform/linux-generic/pktio/sysfs.c
> 
> diff --git a/platform/linux-generic/Makefile.am b/platform/linux-generic/Makefile.am
> index 610c79c..71b030e 100644
> --- a/platform/linux-generic/Makefile.am
> +++ b/platform/linux-generic/Makefile.am
> @@ -165,11 +165,13 @@ __LIB__libodp_la_SOURCES = \
>  			   odp_packet.c \
>  			   odp_packet_flags.c \
>  			   odp_packet_io.c \
> +			   pktio/ethtool.c \
>  			   pktio/io_ops.c \
>  			   pktio/loop.c \
>  			   pktio/netmap.c \
>  			   pktio/socket.c \
>  			   pktio/socket_mmap.c \
> +			   pktio/sysfs.c \
>  			   odp_pool.c \
>  			   odp_queue.c \
>  			   odp_rwlock.c \
> diff --git a/platform/linux-generic/include/odp_packet_io_internal.h b/platform/linux-generic/include/odp_packet_io_internal.h
> index 1a1118c..6566978 100644
> --- a/platform/linux-generic/include/odp_packet_io_internal.h
> +++ b/platform/linux-generic/include/odp_packet_io_internal.h
> @@ -84,6 +84,9 @@ struct pktio_entry {
>  		STATE_STOP
>  	} state;
>  	classifier_t cls;		/**< classifier linked with this pktio*/
> +	odp_pktio_stats_t stats;	/**< statistic counters for pktio */
> +	int use_ethtool;		/**< 1 - use ethtool,
> +					     0 - sysfs for statistics */
>  	char name[PKTIO_NAME_LEN];	/**< name of pktio provided to
>  					   pktio_open() */
>  	odp_pktio_param_t param;
> @@ -107,6 +110,8 @@ typedef struct pktio_if_ops {
>  	int (*close)(pktio_entry_t *pktio_entry);
>  	int (*start)(pktio_entry_t *pktio_entry);
>  	int (*stop)(pktio_entry_t *pktio_entry);
> +	int (*stats)(pktio_entry_t *pktio_entry, odp_pktio_stats_t *stats);
> +	int (*stats_reset)(pktio_entry_t *pktio_entry);
>  	int (*recv)(pktio_entry_t *pktio_entry, odp_packet_t pkt_table[],
>  		    unsigned len);
>  	int (*send)(pktio_entry_t *pktio_entry, odp_packet_t pkt_table[],
> @@ -159,6 +164,12 @@ extern const pktio_if_ops_t pcap_pktio_ops;
>  #endif
>  extern const pktio_if_ops_t * const pktio_if_ops[];
>  
> +int sysfs_stats(pktio_entry_t *pktio_entry,
> +		odp_pktio_stats_t *stats);
> +int sock_stats_reset(pktio_entry_t *pktio_entry);
> +int sock_stats(pktio_entry_t *pktio_entry,
> +	       odp_pktio_stats_t *stats);
> +
>  #ifdef __cplusplus
>  }
>  #endif
> diff --git a/platform/linux-generic/include/odp_packet_socket.h b/platform/linux-generic/include/odp_packet_socket.h
> index a5e0eb3..0836e3c 100644
> --- a/platform/linux-generic/include/odp_packet_socket.h
> +++ b/platform/linux-generic/include/odp_packet_socket.h
> @@ -18,6 +18,7 @@
>  #include <odp/debug.h>
>  #include <odp/pool.h>
>  #include <odp/packet.h>
> +#include <odp/packet_io.h>
>  
>  #include <linux/version.h>
>  
> @@ -114,4 +115,9 @@ int promisc_mode_set_fd(int fd, const char *name, int enable);
>   */
>  int promisc_mode_get_fd(int fd, const char *name);
>  
> +/**
> + * Get ethtool statistics of a packet socket
> + */
> +int ethtool_stats_get_fd(int fd, const char *name, odp_pktio_stats_t *stats);
> +
>  #endif
> diff --git a/platform/linux-generic/odp_packet_io.c b/platform/linux-generic/odp_packet_io.c
> index 3ef400f..ba97629 100644
> --- a/platform/linux-generic/odp_packet_io.c
> +++ b/platform/linux-generic/odp_packet_io.c
> @@ -862,3 +862,56 @@ void odp_pktio_print(odp_pktio_t id)
>  
>  	ODP_PRINT("\n%s\n", str);
>  }
> +
> +int odp_pktio_stats(odp_pktio_t pktio,
> +		    odp_pktio_stats_t *stats)
> +{
> +	pktio_entry_t *entry;
> +	int ret = -1;
> +
> +	entry = get_pktio_entry(pktio);
> +	if (entry == NULL) {
> +		ODP_DBG("pktio entry %d does not exist\n", pktio);
> +		return -1;
> +	}
> +
> +	lock_entry(entry);
> +
> +	if (odp_unlikely(is_free(entry))) {
> +		unlock_entry(entry);
> +		ODP_DBG("already freed pktio\n");
> +		return -1;
> +	}
> +
> +	if (entry->s.ops->stats)
> +		ret = entry->s.ops->stats(entry, stats);
> +	unlock_entry(entry);
> +
> +	return ret;
> +}
> +
> +int odp_pktio_stats_reset(odp_pktio_t pktio)
> +{
> +	pktio_entry_t *entry;
> +	int ret = -1;
> +
> +	entry = get_pktio_entry(pktio);
> +	if (entry == NULL) {
> +		ODP_DBG("pktio entry %d does not exist\n", pktio);
> +		return -1;
> +	}
> +
> +	lock_entry(entry);
> +
> +	if (odp_unlikely(is_free(entry))) {
> +		unlock_entry(entry);
> +		ODP_DBG("already freed pktio\n");
> +		return -1;
> +	}
> +
> +	if (entry->s.ops->stats)
> +		ret = entry->s.ops->stats_reset(entry);
> +	unlock_entry(entry);
> +
> +	return ret;
> +}
> diff --git a/platform/linux-generic/pktio/ethtool.c b/platform/linux-generic/pktio/ethtool.c
> new file mode 100644
> index 0000000..b689015
> --- /dev/null
> +++ b/platform/linux-generic/pktio/ethtool.c
> @@ -0,0 +1,160 @@
> +/* Copyright (c) 2015, Linaro Limited
> + * All rights reserved.
> + *
> + * SPDX-License-Identifier:     BSD-3-Clause
> + */
> +
> +#include <sys/ioctl.h>
> +#include <netinet/in.h>
> +#include <linux/sockios.h>
> +#include <linux/if.h>
> +#include <linux/ethtool.h>
> +#include <errno.h>
> +#include <net/if.h>
> +
> +#include <odp.h>
> +#include <odp_packet_socket.h>
> +#include <odp_debug_internal.h>
> +
> +static struct ethtool_gstrings *get_stringset(int fd, struct ifreq *ifr)
> +{
> +	struct {
> +		struct ethtool_sset_info hdr;
> +		uint32_t buf[1];
> +	} sset_info;
> +	struct ethtool_drvinfo drvinfo;
> +	uint32_t len;
> +	struct ethtool_gstrings *strings;
> +	ptrdiff_t drvinfo_offset = offsetof(struct ethtool_drvinfo, n_stats);
> +
> +	sset_info.hdr.cmd = ETHTOOL_GSSET_INFO;
> +	sset_info.hdr.reserved = 0;
> +	sset_info.hdr.sset_mask = 1ULL << ETH_SS_STATS;
> +	ifr->ifr_data =  &sset_info;
> +	if (ioctl(fd, SIOCETHTOOL, ifr) == 0) {
> +		len = sset_info.hdr.sset_mask ? sset_info.hdr.data[0] : 0;
> +	} else if (errno == EOPNOTSUPP && drvinfo_offset != 0) {
> +		/* Fallback for old kernel versions */
> +		drvinfo.cmd = ETHTOOL_GDRVINFO;
> +		ifr->ifr_data = &drvinfo;
> +		if (ioctl(fd, SIOCETHTOOL, ifr)) {
> +			__odp_errno = errno;
> +			ODP_ERR("Cannot get stats information\n");
> +			return NULL;
> +		}
> +		len = *(uint32_t *)((char *)&drvinfo + drvinfo_offset);
> +	} else {
> +		__odp_errno = errno;
> +		return NULL;
> +	}
> +
> +	if (!len) {
> +		ODP_ERR("len is zero");
> +		return NULL;
> +	}
> +
> +	strings = calloc(1, sizeof(*strings) + len * ETH_GSTRING_LEN);
> +	if (!strings) {
> +		ODP_ERR("alloc failed\n");
> +		return NULL;
> +	}
> +
> +	strings->cmd = ETHTOOL_GSTRINGS;
> +	strings->string_set = ETH_SS_STATS;
> +	strings->len = len;
> +	ifr->ifr_data = strings;
> +	if (ioctl(fd, SIOCETHTOOL, ifr)) {
> +		__odp_errno = errno;
> +		ODP_ERR("Cannot get stats information\n");
> +		free(strings);
> +		return NULL;
> +	}
> +
> +	return strings;
> +}
> +
> +static int ethtool_stats(int fd, struct ifreq *ifr, odp_pktio_stats_t *stats)
> +{
> +	struct ethtool_gstrings *strings;
> +	struct ethtool_stats *estats;
> +	unsigned int n_stats, i;
> +	int err;
> +	int cnts;
> +
> +	strings = get_stringset(fd, ifr);
> +	if (!strings)
> +		return -1;
> +
> +	n_stats = strings->len;
> +	if (n_stats < 1) {
> +		ODP_ERR("no stats available\n");
> +		free(strings);
> +		return -1;
> +	}
> +
> +	estats = calloc(1, n_stats * sizeof(uint64_t) +
> +			sizeof(struct ethtool_stats));
> +	if (!estats) {
> +		free(strings);
> +		return -1;
> +	}
> +
> +	estats->cmd = ETHTOOL_GSTATS;
> +	estats->n_stats = n_stats;
> +	ifr->ifr_data = stats;

Should be estats
Maxim Uvarov Nov. 23, 2015, 1:47 p.m. UTC | #3
On 11/18/2015 17:24, Stuart Haslam wrote:
> On Wed, Nov 18, 2015 at 12:34:24PM +0300, Maxim Uvarov wrote:
>> Signed-off-by: Maxim Uvarov <maxim.uvarov@linaro.org>
>> ---
>>   platform/linux-generic/Makefile.am                 |   2 +
>>   .../linux-generic/include/odp_packet_io_internal.h |  11 ++
>>   platform/linux-generic/include/odp_packet_socket.h |   6 +
>>   platform/linux-generic/odp_packet_io.c             |  53 +++++++
>>   platform/linux-generic/pktio/ethtool.c             | 160 +++++++++++++++++++++
>>   platform/linux-generic/pktio/socket.c              |  84 +++++++++++
>>   platform/linux-generic/pktio/socket_mmap.c         |  19 +++
>>   platform/linux-generic/pktio/sysfs.c               |  71 +++++++++
>>   8 files changed, 406 insertions(+)
>>   create mode 100644 platform/linux-generic/pktio/ethtool.c
>>   create mode 100644 platform/linux-generic/pktio/sysfs.c
>>
>> diff --git a/platform/linux-generic/Makefile.am b/platform/linux-generic/Makefile.am
>> index 610c79c..71b030e 100644
>> --- a/platform/linux-generic/Makefile.am
>> +++ b/platform/linux-generic/Makefile.am
>> @@ -165,11 +165,13 @@ __LIB__libodp_la_SOURCES = \
>>   			   odp_packet.c \
>>   			   odp_packet_flags.c \
>>   			   odp_packet_io.c \
>> +			   pktio/ethtool.c \
>>   			   pktio/io_ops.c \
>>   			   pktio/loop.c \
>>   			   pktio/netmap.c \
>>   			   pktio/socket.c \
>>   			   pktio/socket_mmap.c \
>> +			   pktio/sysfs.c \
>>   			   odp_pool.c \
>>   			   odp_queue.c \
>>   			   odp_rwlock.c \
>> diff --git a/platform/linux-generic/include/odp_packet_io_internal.h b/platform/linux-generic/include/odp_packet_io_internal.h
>> index 1a1118c..6566978 100644
>> --- a/platform/linux-generic/include/odp_packet_io_internal.h
>> +++ b/platform/linux-generic/include/odp_packet_io_internal.h
>> @@ -84,6 +84,9 @@ struct pktio_entry {
>>   		STATE_STOP
>>   	} state;
>>   	classifier_t cls;		/**< classifier linked with this pktio*/
>> +	odp_pktio_stats_t stats;	/**< statistic counters for pktio */
>> +	int use_ethtool;		/**< 1 - use ethtool,
>> +					     0 - sysfs for statistics */
>>   	char name[PKTIO_NAME_LEN];	/**< name of pktio provided to
>>   					   pktio_open() */
>>   	odp_pktio_param_t param;
>> @@ -107,6 +110,8 @@ typedef struct pktio_if_ops {
>>   	int (*close)(pktio_entry_t *pktio_entry);
>>   	int (*start)(pktio_entry_t *pktio_entry);
>>   	int (*stop)(pktio_entry_t *pktio_entry);
>> +	int (*stats)(pktio_entry_t *pktio_entry, odp_pktio_stats_t *stats);
>> +	int (*stats_reset)(pktio_entry_t *pktio_entry);
>>   	int (*recv)(pktio_entry_t *pktio_entry, odp_packet_t pkt_table[],
>>   		    unsigned len);
>>   	int (*send)(pktio_entry_t *pktio_entry, odp_packet_t pkt_table[],
>> @@ -159,6 +164,12 @@ extern const pktio_if_ops_t pcap_pktio_ops;
>>   #endif
>>   extern const pktio_if_ops_t * const pktio_if_ops[];
>>   
>> +int sysfs_stats(pktio_entry_t *pktio_entry,
>> +		odp_pktio_stats_t *stats);
>> +int sock_stats_reset(pktio_entry_t *pktio_entry);
>> +int sock_stats(pktio_entry_t *pktio_entry,
>> +	       odp_pktio_stats_t *stats);
>> +
>>   #ifdef __cplusplus
>>   }
>>   #endif
>> diff --git a/platform/linux-generic/include/odp_packet_socket.h b/platform/linux-generic/include/odp_packet_socket.h
>> index a5e0eb3..0836e3c 100644
>> --- a/platform/linux-generic/include/odp_packet_socket.h
>> +++ b/platform/linux-generic/include/odp_packet_socket.h
>> @@ -18,6 +18,7 @@
>>   #include <odp/debug.h>
>>   #include <odp/pool.h>
>>   #include <odp/packet.h>
>> +#include <odp/packet_io.h>
>>   
>>   #include <linux/version.h>
>>   
>> @@ -114,4 +115,9 @@ int promisc_mode_set_fd(int fd, const char *name, int enable);
>>    */
>>   int promisc_mode_get_fd(int fd, const char *name);
>>   
>> +/**
>> + * Get ethtool statistics of a packet socket
>> + */
>> +int ethtool_stats_get_fd(int fd, const char *name, odp_pktio_stats_t *stats);
>> +
>>   #endif
>> diff --git a/platform/linux-generic/odp_packet_io.c b/platform/linux-generic/odp_packet_io.c
>> index 3ef400f..ba97629 100644
>> --- a/platform/linux-generic/odp_packet_io.c
>> +++ b/platform/linux-generic/odp_packet_io.c
>> @@ -862,3 +862,56 @@ void odp_pktio_print(odp_pktio_t id)
>>   
>>   	ODP_PRINT("\n%s\n", str);
>>   }
>> +
>> +int odp_pktio_stats(odp_pktio_t pktio,
>> +		    odp_pktio_stats_t *stats)
>> +{
>> +	pktio_entry_t *entry;
>> +	int ret = -1;
>> +
>> +	entry = get_pktio_entry(pktio);
>> +	if (entry == NULL) {
>> +		ODP_DBG("pktio entry %d does not exist\n", pktio);
>> +		return -1;
>> +	}
>> +
>> +	lock_entry(entry);
>> +
>> +	if (odp_unlikely(is_free(entry))) {
>> +		unlock_entry(entry);
>> +		ODP_DBG("already freed pktio\n");
>> +		return -1;
>> +	}
>> +
>> +	if (entry->s.ops->stats)
>> +		ret = entry->s.ops->stats(entry, stats);
>> +	unlock_entry(entry);
>> +
>> +	return ret;
>> +}
>> +
>> +int odp_pktio_stats_reset(odp_pktio_t pktio)
>> +{
>> +	pktio_entry_t *entry;
>> +	int ret = -1;
>> +
>> +	entry = get_pktio_entry(pktio);
>> +	if (entry == NULL) {
>> +		ODP_DBG("pktio entry %d does not exist\n", pktio);
>> +		return -1;
>> +	}
>> +
>> +	lock_entry(entry);
>> +
>> +	if (odp_unlikely(is_free(entry))) {
>> +		unlock_entry(entry);
>> +		ODP_DBG("already freed pktio\n");
>> +		return -1;
>> +	}
>> +
>> +	if (entry->s.ops->stats)
>> +		ret = entry->s.ops->stats_reset(entry);
>> +	unlock_entry(entry);
>> +
>> +	return ret;
>> +}
>> diff --git a/platform/linux-generic/pktio/ethtool.c b/platform/linux-generic/pktio/ethtool.c
>> new file mode 100644
>> index 0000000..b689015
>> --- /dev/null
>> +++ b/platform/linux-generic/pktio/ethtool.c
>> @@ -0,0 +1,160 @@
>> +/* Copyright (c) 2015, Linaro Limited
>> + * All rights reserved.
>> + *
>> + * SPDX-License-Identifier:     BSD-3-Clause
>> + */
>> +
>> +#include <sys/ioctl.h>
>> +#include <netinet/in.h>
>> +#include <linux/sockios.h>
>> +#include <linux/if.h>
>> +#include <linux/ethtool.h>
>> +#include <errno.h>
>> +#include <net/if.h>
>> +
>> +#include <odp.h>
>> +#include <odp_packet_socket.h>
>> +#include <odp_debug_internal.h>
>> +
>> +static struct ethtool_gstrings *get_stringset(int fd, struct ifreq *ifr)
>> +{
>> +	struct {
>> +		struct ethtool_sset_info hdr;
>> +		uint32_t buf[1];
>> +	} sset_info;
>> +	struct ethtool_drvinfo drvinfo;
>> +	uint32_t len;
>> +	struct ethtool_gstrings *strings;
>> +	ptrdiff_t drvinfo_offset = offsetof(struct ethtool_drvinfo, n_stats);
>> +
>> +	sset_info.hdr.cmd = ETHTOOL_GSSET_INFO;
>> +	sset_info.hdr.reserved = 0;
>> +	sset_info.hdr.sset_mask = 1ULL << ETH_SS_STATS;
>> +	ifr->ifr_data =  &sset_info;
>> +	if (ioctl(fd, SIOCETHTOOL, ifr) == 0) {
>> +		len = sset_info.hdr.sset_mask ? sset_info.hdr.data[0] : 0;
>> +	} else if (errno == EOPNOTSUPP && drvinfo_offset != 0) {
>> +		/* Fallback for old kernel versions */
>> +		drvinfo.cmd = ETHTOOL_GDRVINFO;
>> +		ifr->ifr_data = &drvinfo;
>> +		if (ioctl(fd, SIOCETHTOOL, ifr)) {
>> +			__odp_errno = errno;
>> +			ODP_ERR("Cannot get stats information\n");
>> +			return NULL;
>> +		}
>> +		len = *(uint32_t *)((char *)&drvinfo + drvinfo_offset);
>> +	} else {
>> +		__odp_errno = errno;
>> +		return NULL;
>> +	}
>> +
>> +	if (!len) {
>> +		ODP_ERR("len is zero");
>> +		return NULL;
>> +	}
>> +
>> +	strings = calloc(1, sizeof(*strings) + len * ETH_GSTRING_LEN);
>> +	if (!strings) {
>> +		ODP_ERR("alloc failed\n");
>> +		return NULL;
>> +	}
>> +
>> +	strings->cmd = ETHTOOL_GSTRINGS;
>> +	strings->string_set = ETH_SS_STATS;
>> +	strings->len = len;
>> +	ifr->ifr_data = strings;
>> +	if (ioctl(fd, SIOCETHTOOL, ifr)) {
>> +		__odp_errno = errno;
>> +		ODP_ERR("Cannot get stats information\n");
>> +		free(strings);
>> +		return NULL;
>> +	}
>> +
>> +	return strings;
>> +}
>> +
>> +static int ethtool_stats(int fd, struct ifreq *ifr, odp_pktio_stats_t *stats)
>> +{
>> +	struct ethtool_gstrings *strings;
>> +	struct ethtool_stats *estats;
>> +	unsigned int n_stats, i;
>> +	int err;
>> +	int cnts;
>> +
>> +	strings = get_stringset(fd, ifr);
>> +	if (!strings)
>> +		return -1;
>> +
>> +	n_stats = strings->len;
>> +	if (n_stats < 1) {
>> +		ODP_ERR("no stats available\n");
>> +		free(strings);
>> +		return -1;
>> +	}
>> +
>> +	estats = calloc(1, n_stats * sizeof(uint64_t) +
>> +			sizeof(struct ethtool_stats));
>> +	if (!estats) {
>> +		free(strings);
>> +		return -1;
>> +	}
>> +
>> +	estats->cmd = ETHTOOL_GSTATS;
>> +	estats->n_stats = n_stats;
>> +	ifr->ifr_data = stats;
>> +	err = ioctl(fd, SIOCETHTOOL, ifr);
>> +	if (err < 0) {
>> +		__odp_errno = errno;
>> +		free(strings);
>> +		free(estats);
>> +		return -1;
>> +	}
>> +
>> +	cnts = 0;
>> +	for (i = 0; i < n_stats; i++) {
>> +		char *cnt = (char *)&strings->data[i * ETH_GSTRING_LEN];
>> +		uint64_t val = estats->data[i];
>> +
>> +		if (!strcmp(cnt, "rx_octets")) {
>> +			stats->in_octets = val;
>> +			cnts++;
>> +		} else if (!strcmp(cnt, "rx_ucast_packets")) {
>> +			stats->in_ucast_pkts = val;
>> +			cnts++;
> This won't work on all devices, ixgbe for example doesn't have this
> string. As per my email on Friday it looks like the strings used in
> ethtool aren't standardised:
>
> https://lists.linaro.org/pipermail/lng-odp/2015-November/017351.html
>
> Also whether they're updated while in netmap mode seems to be somewhat
> device specific. So we still don't have a universally working solution
> for netmap.
>
> I think the best way around this for now is just to ensure that the
> validation test doesn't fail if an implementation reports 0 for all
> counters, since the spec allows that. We then may or may not get stats
> from netmap, depending on the device used.

yes, for netmap there is no standard way. So let's  try ethtool,
then sysfs. If  it will be needed then we will add more string names
to ethtool parsing. I will make test to allow not supported stats now,
then I think we can add pktio caps if stats are supported or not.

>> +		} else if (!strcmp(cnt, "rx_discards")) {
>> +			stats->in_discards = val;
>> +			cnts++;
>> +		} else if (!strcmp(cnt, "rx_errors")) {
>> +			stats->in_errors = val;
>> +			cnts++;
>> +		} else if (!strcmp(cnt, "tx_octets")) {
>> +			stats->out_octets = val;
>> +			cnts++;
>> +		} else if (!strcmp(cnt, "tx_ucast_packets")) {
>> +			stats->out_ucast_pkts = val;
>> +			cnts++;
>> +		} else if (!strcmp(cnt, "tx_discards")) {
>> +			stats->out_discards = val;
>> +			cnts++;
>> +		} else if (!strcmp(cnt, "tx_errors")) {
>> +			stats->out_errors = val;
>> +			cnts++;
>> +		}
>> +	}
>> +
>> +	free(strings);
>> +	free(estats);
>> +
>> +	if (cnts < 8)
>> +		return -1;
> This shouldn't be treated as a failure.
That is important check to detect if ethtool is supported or we should 
fallback to ethtool.
One of the example is strings named differently. In case of netmap we do 
not switch to sysfs,
but in case of other drivers we do.

>> +
>> +	return 0;
>> +}
>> +
>> +int ethtool_stats_get_fd(int fd, const char *name, odp_pktio_stats_t *stats)
>> +{
>> +	struct ifreq ifr;
>> +
>> +	snprintf(ifr.ifr_name, IF_NAMESIZE, "%s", name);
>> +	return ethtool_stats(fd, &ifr, stats);
>> +}
>> diff --git a/platform/linux-generic/pktio/socket.c b/platform/linux-generic/pktio/socket.c
>> index 5f5e0ae..9f896a9 100644
>> --- a/platform/linux-generic/pktio/socket.c
>> +++ b/platform/linux-generic/pktio/socket.c
>> @@ -209,6 +209,7 @@ static int sock_setup_pkt(pktio_entry_t *pktio_entry, const char *netdev,
>>   	struct ifreq ethreq;
>>   	struct sockaddr_ll sa_ll;
>>   	pkt_sock_t *pkt_sock = &pktio_entry->s.pkt_sock;
>> +	odp_pktio_stats_t cur_stats;
>>   
>>   	/* Init pktio entry */
>>   	memset(pkt_sock, 0, sizeof(*pkt_sock));
>> @@ -254,6 +255,22 @@ static int sock_setup_pkt(pktio_entry_t *pktio_entry, const char *netdev,
>>   		goto error;
>>   	}
>>   
>> +	err = ethtool_stats_get_fd(pktio_entry->s.pkt_sock.sockfd,
>> +				   pktio_entry->s.name,
>> +				   &cur_stats);
>> +	if (err != 0) {
>> +		err = sysfs_stats(pktio_entry, &cur_stats);
>> +		if (err != 0)
>> +			ODP_ABORT("statistic counters are not reachable\n");
>> +		pktio_entry->s.use_ethtool = 0;
>> +	} else {
>> +		pktio_entry->s.use_ethtool = 1;
>> +	}
>> +
>> +	err = sock_stats_reset(pktio_entry);
>> +	if (err != 0)
>> +		goto error;
>> +
>>   	return 0;
>>   
>>   error:
>> @@ -467,6 +484,71 @@ static int sock_promisc_mode_get(pktio_entry_t *pktio_entry)
>>   				   pktio_entry->s.name);
>>   }
>>   
>> +int sock_stats(pktio_entry_t *pktio_entry,
>> +	       odp_pktio_stats_t *stats)
>> +{
>> +	odp_pktio_stats_t cur_stats;
>> +	int ret;
>> +
>> +	memset(&cur_stats, 0, sizeof(odp_pktio_stats_t));
>> +	if (pktio_entry->s.use_ethtool) {
>> +		ret =  ethtool_stats_get_fd(pktio_entry->s.pkt_sock.sockfd,
>> +					    pktio_entry->s.name,
>> +					    &cur_stats);
>> +	} else {
>> +		ret = sysfs_stats(pktio_entry, &cur_stats);
>> +	}
>> +	if (ret)
>> +		ODP_ABORT("getting statistics error\n");
>> +
>> +	stats->in_octets = cur_stats.in_octets -
>> +				pktio_entry->s.stats.in_octets;
>> +	stats->in_ucast_pkts = cur_stats.in_ucast_pkts -
>> +				pktio_entry->s.stats.in_ucast_pkts;
>> +	stats->in_discards = cur_stats.in_discards -
>> +				pktio_entry->s.stats.in_discards;
>> +	stats->in_errors = cur_stats.in_errors -
>> +				pktio_entry->s.stats.in_errors;
>> +	stats->in_unknown_protos = cur_stats.in_unknown_protos -
>> +				pktio_entry->s.stats.in_unknown_protos;
>> +
>> +	stats->out_octets = cur_stats.out_octets -
>> +				pktio_entry->s.stats.out_octets;
>> +	stats->out_ucast_pkts = cur_stats.out_ucast_pkts -
>> +				pktio_entry->s.stats.out_ucast_pkts;
>> +	stats->out_discards = cur_stats.out_discards -
>> +				pktio_entry->s.stats.out_discards;
>> +	stats->out_errors = cur_stats.out_errors -
>> +				pktio_entry->s.stats.out_errors;
>> +
>> +	return 0;
>> +}
>> +
>> +int sock_stats_reset(pktio_entry_t *pktio_entry)
>> +{
>> +	int err = 0;
>> +	odp_pktio_stats_t cur_stats;
>> +
>> +	memset(&cur_stats, 0, sizeof(odp_pktio_stats_t));
>> +
>> +	if (pktio_entry->s.use_ethtool) {
>> +		err = ethtool_stats_get_fd(pktio_entry->s.pkt_sock.sockfd,
>> +					   pktio_entry->s.name,
>> +					   &cur_stats);
>> +	} else {
>> +		err = sysfs_stats(pktio_entry, &cur_stats);
>> +	}
>> +
>> +	if (err != 0) {
>> +		ODP_ERR("stats error\n");
>> +	} else {
>> +		memcpy(&pktio_entry->s.stats, &cur_stats,
>> +		       sizeof(odp_pktio_stats_t));
>> +	}
>> +
>> +	return err;
>> +}
>> +
>>   const pktio_if_ops_t sock_mmsg_pktio_ops = {
>>   	.init = NULL,
>>   	.term = NULL,
>> @@ -474,6 +556,8 @@ const pktio_if_ops_t sock_mmsg_pktio_ops = {
>>   	.close = sock_close,
>>   	.start = NULL,
>>   	.stop = NULL,
>> +	.stats = sock_stats,
>> +	.stats_reset = sock_stats_reset,
>>   	.recv = sock_mmsg_recv,
>>   	.send = sock_mmsg_send,
>>   	.mtu_get = sock_mtu_get,
>> diff --git a/platform/linux-generic/pktio/socket_mmap.c b/platform/linux-generic/pktio/socket_mmap.c
>> index 79ff82d..06bdad6 100644
>> --- a/platform/linux-generic/pktio/socket_mmap.c
>> +++ b/platform/linux-generic/pktio/socket_mmap.c
>> @@ -408,6 +408,7 @@ static int sock_mmap_open(odp_pktio_t id ODP_UNUSED,
>>   {
>>   	int if_idx;
>>   	int ret = 0;
>> +	odp_pktio_stats_t cur_stats;
>>   
>>   	if (getenv("ODP_PKTIO_DISABLE_SOCKET_MMAP"))
>>   		return -1;
>> @@ -467,6 +468,22 @@ static int sock_mmap_open(odp_pktio_t id ODP_UNUSED,
>>   			goto error;
>>   	}
>>   
>> +	ret = ethtool_stats_get_fd(pktio_entry->s.pkt_sock.sockfd,
>> +				   pktio_entry->s.name,
>> +				   &cur_stats);
>> +	if (ret != 0) {
>> +		ret = sysfs_stats(pktio_entry, &cur_stats);
>> +		if (ret != 0)
>> +			ODP_ABORT("statistic counters are not reachable\n");
>> +		pktio_entry->s.use_ethtool = 0;
>> +	} else {
>> +		pktio_entry->s.use_ethtool = 1;
>> +	}
>> +
>> +	ret = sock_stats_reset(pktio_entry);
>> +	if (ret != 0)
>> +		goto error;
>> +
>>   	return 0;
>>   
>>   error:
>> @@ -525,6 +542,8 @@ const pktio_if_ops_t sock_mmap_pktio_ops = {
>>   	.close = sock_mmap_close,
>>   	.start = NULL,
>>   	.stop = NULL,
>> +	.stats = sock_stats,
>> +	.stats_reset = sock_stats_reset,
>>   	.recv = sock_mmap_recv,
>>   	.send = sock_mmap_send,
>>   	.mtu_get = sock_mmap_mtu_get,
>> diff --git a/platform/linux-generic/pktio/sysfs.c b/platform/linux-generic/pktio/sysfs.c
>> new file mode 100644
>> index 0000000..afa5bfb
>> --- /dev/null
>> +++ b/platform/linux-generic/pktio/sysfs.c
>> @@ -0,0 +1,71 @@
>> +/* Copyright (c) 2015, Linaro Limited
>> + * All rights reserved.
>> + *
>> + * SPDX-License-Identifier:     BSD-3-Clause
>> + */
>> +
>> +#include <odp.h>
>> +#include <odp_packet_io_internal.h>
>> +#include <stdio.h>
>> +
>> +static int sysfs_get_val(const char *fname, uint64_t *val)
>> +{
>> +	FILE  *file;
>> +	char str[128];
>> +	uint64_t ret = -1;
> Should be int

yes, I always change it but always forgot to ammend that chunk for some 
reason :)

>> +
>> +	file = fopen(fname, "rt");
>> +	if (file == NULL) {
>> +		/* File not found */
> Only if errno == ENOENT, some (maybe all?) other failures should be
> reported as failures.
>
>> +		return 0;
>> +	}
>> +
>> +	if (fgets(str, sizeof(str), file) != NULL)
>> +		ret = sscanf(str, "%" SCNx64, val);
>> +
>> +	(void)fclose(file);
>> +
>> +	if (ret != 1) {
>> +		ODP_ERR("read %s\n", fname);
>> +		return -1;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +int sysfs_stats(pktio_entry_t *pktio_entry,
>> +		odp_pktio_stats_t *stats)
>> +{
>> +	char fname[256];
>> +	const char *dev = pktio_entry->s.name;
>> +	int ret = 0;
>> +
>> +	sprintf(fname, "/sys/class/net/%s/statistics/rx_bytes", dev);
>> +	ret -= sysfs_get_val(fname, &stats->in_octets);
>> +
>> +	sprintf(fname, "/sys/class/net/%s/statistics/rx_packets", dev);
>> +	ret -= sysfs_get_val(fname, &stats->in_ucast_pkts);
>> +
>> +	sprintf(fname, "/sys/class/net/%s/statistics/rx_droppped", dev);
>> +	ret -= sysfs_get_val(fname, &stats->in_discards);
>> +
>> +	sprintf(fname, "/sys/class/net/%s/statistics/rx_errors", dev);
>> +	ret -= sysfs_get_val(fname, &stats->in_errors);
>> +
>> +	/* stats->in_unknown_protos is not supported in sysfs */
>> +
>> +	sprintf(fname, "/sys/class/net/%s/statistics/tx_bytes", dev);
>> +	ret -= sysfs_get_val(fname, &stats->out_octets);
>> +
>> +	sprintf(fname, "/sys/class/net/%s/statistics/tx_packets", dev);
>> +	ret -= sysfs_get_val(fname, &stats->out_ucast_pkts);
>> +
>> +	sprintf(fname, "/sys/class/net/%s/statistics/tx_dropped", dev);
>> +	ret -= sysfs_get_val(fname, &stats->out_discards);
>> +
>> +	sprintf(fname, "/sys/class/net/%s/statistics/tx_errors", dev);
>> +	ret -= sysfs_get_val(fname, &stats->out_errors);
>> +
>> +	return ret;
>> +}
>> +
>> -- 
>> 1.9.1
>>
diff mbox

Patch

diff --git a/platform/linux-generic/Makefile.am b/platform/linux-generic/Makefile.am
index 610c79c..71b030e 100644
--- a/platform/linux-generic/Makefile.am
+++ b/platform/linux-generic/Makefile.am
@@ -165,11 +165,13 @@  __LIB__libodp_la_SOURCES = \
 			   odp_packet.c \
 			   odp_packet_flags.c \
 			   odp_packet_io.c \
+			   pktio/ethtool.c \
 			   pktio/io_ops.c \
 			   pktio/loop.c \
 			   pktio/netmap.c \
 			   pktio/socket.c \
 			   pktio/socket_mmap.c \
+			   pktio/sysfs.c \
 			   odp_pool.c \
 			   odp_queue.c \
 			   odp_rwlock.c \
diff --git a/platform/linux-generic/include/odp_packet_io_internal.h b/platform/linux-generic/include/odp_packet_io_internal.h
index 1a1118c..6566978 100644
--- a/platform/linux-generic/include/odp_packet_io_internal.h
+++ b/platform/linux-generic/include/odp_packet_io_internal.h
@@ -84,6 +84,9 @@  struct pktio_entry {
 		STATE_STOP
 	} state;
 	classifier_t cls;		/**< classifier linked with this pktio*/
+	odp_pktio_stats_t stats;	/**< statistic counters for pktio */
+	int use_ethtool;		/**< 1 - use ethtool,
+					     0 - sysfs for statistics */
 	char name[PKTIO_NAME_LEN];	/**< name of pktio provided to
 					   pktio_open() */
 	odp_pktio_param_t param;
@@ -107,6 +110,8 @@  typedef struct pktio_if_ops {
 	int (*close)(pktio_entry_t *pktio_entry);
 	int (*start)(pktio_entry_t *pktio_entry);
 	int (*stop)(pktio_entry_t *pktio_entry);
+	int (*stats)(pktio_entry_t *pktio_entry, odp_pktio_stats_t *stats);
+	int (*stats_reset)(pktio_entry_t *pktio_entry);
 	int (*recv)(pktio_entry_t *pktio_entry, odp_packet_t pkt_table[],
 		    unsigned len);
 	int (*send)(pktio_entry_t *pktio_entry, odp_packet_t pkt_table[],
@@ -159,6 +164,12 @@  extern const pktio_if_ops_t pcap_pktio_ops;
 #endif
 extern const pktio_if_ops_t * const pktio_if_ops[];
 
+int sysfs_stats(pktio_entry_t *pktio_entry,
+		odp_pktio_stats_t *stats);
+int sock_stats_reset(pktio_entry_t *pktio_entry);
+int sock_stats(pktio_entry_t *pktio_entry,
+	       odp_pktio_stats_t *stats);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/platform/linux-generic/include/odp_packet_socket.h b/platform/linux-generic/include/odp_packet_socket.h
index a5e0eb3..0836e3c 100644
--- a/platform/linux-generic/include/odp_packet_socket.h
+++ b/platform/linux-generic/include/odp_packet_socket.h
@@ -18,6 +18,7 @@ 
 #include <odp/debug.h>
 #include <odp/pool.h>
 #include <odp/packet.h>
+#include <odp/packet_io.h>
 
 #include <linux/version.h>
 
@@ -114,4 +115,9 @@  int promisc_mode_set_fd(int fd, const char *name, int enable);
  */
 int promisc_mode_get_fd(int fd, const char *name);
 
+/**
+ * Get ethtool statistics of a packet socket
+ */
+int ethtool_stats_get_fd(int fd, const char *name, odp_pktio_stats_t *stats);
+
 #endif
diff --git a/platform/linux-generic/odp_packet_io.c b/platform/linux-generic/odp_packet_io.c
index 3ef400f..ba97629 100644
--- a/platform/linux-generic/odp_packet_io.c
+++ b/platform/linux-generic/odp_packet_io.c
@@ -862,3 +862,56 @@  void odp_pktio_print(odp_pktio_t id)
 
 	ODP_PRINT("\n%s\n", str);
 }
+
+int odp_pktio_stats(odp_pktio_t pktio,
+		    odp_pktio_stats_t *stats)
+{
+	pktio_entry_t *entry;
+	int ret = -1;
+
+	entry = get_pktio_entry(pktio);
+	if (entry == NULL) {
+		ODP_DBG("pktio entry %d does not exist\n", pktio);
+		return -1;
+	}
+
+	lock_entry(entry);
+
+	if (odp_unlikely(is_free(entry))) {
+		unlock_entry(entry);
+		ODP_DBG("already freed pktio\n");
+		return -1;
+	}
+
+	if (entry->s.ops->stats)
+		ret = entry->s.ops->stats(entry, stats);
+	unlock_entry(entry);
+
+	return ret;
+}
+
+int odp_pktio_stats_reset(odp_pktio_t pktio)
+{
+	pktio_entry_t *entry;
+	int ret = -1;
+
+	entry = get_pktio_entry(pktio);
+	if (entry == NULL) {
+		ODP_DBG("pktio entry %d does not exist\n", pktio);
+		return -1;
+	}
+
+	lock_entry(entry);
+
+	if (odp_unlikely(is_free(entry))) {
+		unlock_entry(entry);
+		ODP_DBG("already freed pktio\n");
+		return -1;
+	}
+
+	if (entry->s.ops->stats)
+		ret = entry->s.ops->stats_reset(entry);
+	unlock_entry(entry);
+
+	return ret;
+}
diff --git a/platform/linux-generic/pktio/ethtool.c b/platform/linux-generic/pktio/ethtool.c
new file mode 100644
index 0000000..b689015
--- /dev/null
+++ b/platform/linux-generic/pktio/ethtool.c
@@ -0,0 +1,160 @@ 
+/* Copyright (c) 2015, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier:     BSD-3-Clause
+ */
+
+#include <sys/ioctl.h>
+#include <netinet/in.h>
+#include <linux/sockios.h>
+#include <linux/if.h>
+#include <linux/ethtool.h>
+#include <errno.h>
+#include <net/if.h>
+
+#include <odp.h>
+#include <odp_packet_socket.h>
+#include <odp_debug_internal.h>
+
+static struct ethtool_gstrings *get_stringset(int fd, struct ifreq *ifr)
+{
+	struct {
+		struct ethtool_sset_info hdr;
+		uint32_t buf[1];
+	} sset_info;
+	struct ethtool_drvinfo drvinfo;
+	uint32_t len;
+	struct ethtool_gstrings *strings;
+	ptrdiff_t drvinfo_offset = offsetof(struct ethtool_drvinfo, n_stats);
+
+	sset_info.hdr.cmd = ETHTOOL_GSSET_INFO;
+	sset_info.hdr.reserved = 0;
+	sset_info.hdr.sset_mask = 1ULL << ETH_SS_STATS;
+	ifr->ifr_data =  &sset_info;
+	if (ioctl(fd, SIOCETHTOOL, ifr) == 0) {
+		len = sset_info.hdr.sset_mask ? sset_info.hdr.data[0] : 0;
+	} else if (errno == EOPNOTSUPP && drvinfo_offset != 0) {
+		/* Fallback for old kernel versions */
+		drvinfo.cmd = ETHTOOL_GDRVINFO;
+		ifr->ifr_data = &drvinfo;
+		if (ioctl(fd, SIOCETHTOOL, ifr)) {
+			__odp_errno = errno;
+			ODP_ERR("Cannot get stats information\n");
+			return NULL;
+		}
+		len = *(uint32_t *)((char *)&drvinfo + drvinfo_offset);
+	} else {
+		__odp_errno = errno;
+		return NULL;
+	}
+
+	if (!len) {
+		ODP_ERR("len is zero");
+		return NULL;
+	}
+
+	strings = calloc(1, sizeof(*strings) + len * ETH_GSTRING_LEN);
+	if (!strings) {
+		ODP_ERR("alloc failed\n");
+		return NULL;
+	}
+
+	strings->cmd = ETHTOOL_GSTRINGS;
+	strings->string_set = ETH_SS_STATS;
+	strings->len = len;
+	ifr->ifr_data = strings;
+	if (ioctl(fd, SIOCETHTOOL, ifr)) {
+		__odp_errno = errno;
+		ODP_ERR("Cannot get stats information\n");
+		free(strings);
+		return NULL;
+	}
+
+	return strings;
+}
+
+static int ethtool_stats(int fd, struct ifreq *ifr, odp_pktio_stats_t *stats)
+{
+	struct ethtool_gstrings *strings;
+	struct ethtool_stats *estats;
+	unsigned int n_stats, i;
+	int err;
+	int cnts;
+
+	strings = get_stringset(fd, ifr);
+	if (!strings)
+		return -1;
+
+	n_stats = strings->len;
+	if (n_stats < 1) {
+		ODP_ERR("no stats available\n");
+		free(strings);
+		return -1;
+	}
+
+	estats = calloc(1, n_stats * sizeof(uint64_t) +
+			sizeof(struct ethtool_stats));
+	if (!estats) {
+		free(strings);
+		return -1;
+	}
+
+	estats->cmd = ETHTOOL_GSTATS;
+	estats->n_stats = n_stats;
+	ifr->ifr_data = stats;
+	err = ioctl(fd, SIOCETHTOOL, ifr);
+	if (err < 0) {
+		__odp_errno = errno;
+		free(strings);
+		free(estats);
+		return -1;
+	}
+
+	cnts = 0;
+	for (i = 0; i < n_stats; i++) {
+		char *cnt = (char *)&strings->data[i * ETH_GSTRING_LEN];
+		uint64_t val = estats->data[i];
+
+		if (!strcmp(cnt, "rx_octets")) {
+			stats->in_octets = val;
+			cnts++;
+		} else if (!strcmp(cnt, "rx_ucast_packets")) {
+			stats->in_ucast_pkts = val;
+			cnts++;
+		} else if (!strcmp(cnt, "rx_discards")) {
+			stats->in_discards = val;
+			cnts++;
+		} else if (!strcmp(cnt, "rx_errors")) {
+			stats->in_errors = val;
+			cnts++;
+		} else if (!strcmp(cnt, "tx_octets")) {
+			stats->out_octets = val;
+			cnts++;
+		} else if (!strcmp(cnt, "tx_ucast_packets")) {
+			stats->out_ucast_pkts = val;
+			cnts++;
+		} else if (!strcmp(cnt, "tx_discards")) {
+			stats->out_discards = val;
+			cnts++;
+		} else if (!strcmp(cnt, "tx_errors")) {
+			stats->out_errors = val;
+			cnts++;
+		}
+	}
+
+	free(strings);
+	free(estats);
+
+	if (cnts < 8)
+		return -1;
+
+	return 0;
+}
+
+int ethtool_stats_get_fd(int fd, const char *name, odp_pktio_stats_t *stats)
+{
+	struct ifreq ifr;
+
+	snprintf(ifr.ifr_name, IF_NAMESIZE, "%s", name);
+	return ethtool_stats(fd, &ifr, stats);
+}
diff --git a/platform/linux-generic/pktio/socket.c b/platform/linux-generic/pktio/socket.c
index 5f5e0ae..9f896a9 100644
--- a/platform/linux-generic/pktio/socket.c
+++ b/platform/linux-generic/pktio/socket.c
@@ -209,6 +209,7 @@  static int sock_setup_pkt(pktio_entry_t *pktio_entry, const char *netdev,
 	struct ifreq ethreq;
 	struct sockaddr_ll sa_ll;
 	pkt_sock_t *pkt_sock = &pktio_entry->s.pkt_sock;
+	odp_pktio_stats_t cur_stats;
 
 	/* Init pktio entry */
 	memset(pkt_sock, 0, sizeof(*pkt_sock));
@@ -254,6 +255,22 @@  static int sock_setup_pkt(pktio_entry_t *pktio_entry, const char *netdev,
 		goto error;
 	}
 
+	err = ethtool_stats_get_fd(pktio_entry->s.pkt_sock.sockfd,
+				   pktio_entry->s.name,
+				   &cur_stats);
+	if (err != 0) {
+		err = sysfs_stats(pktio_entry, &cur_stats);
+		if (err != 0)
+			ODP_ABORT("statistic counters are not reachable\n");
+		pktio_entry->s.use_ethtool = 0;
+	} else {
+		pktio_entry->s.use_ethtool = 1;
+	}
+
+	err = sock_stats_reset(pktio_entry);
+	if (err != 0)
+		goto error;
+
 	return 0;
 
 error:
@@ -467,6 +484,71 @@  static int sock_promisc_mode_get(pktio_entry_t *pktio_entry)
 				   pktio_entry->s.name);
 }
 
+int sock_stats(pktio_entry_t *pktio_entry,
+	       odp_pktio_stats_t *stats)
+{
+	odp_pktio_stats_t cur_stats;
+	int ret;
+
+	memset(&cur_stats, 0, sizeof(odp_pktio_stats_t));
+	if (pktio_entry->s.use_ethtool) {
+		ret =  ethtool_stats_get_fd(pktio_entry->s.pkt_sock.sockfd,
+					    pktio_entry->s.name,
+					    &cur_stats);
+	} else {
+		ret = sysfs_stats(pktio_entry, &cur_stats);
+	}
+	if (ret)
+		ODP_ABORT("getting statistics error\n");
+
+	stats->in_octets = cur_stats.in_octets -
+				pktio_entry->s.stats.in_octets;
+	stats->in_ucast_pkts = cur_stats.in_ucast_pkts -
+				pktio_entry->s.stats.in_ucast_pkts;
+	stats->in_discards = cur_stats.in_discards -
+				pktio_entry->s.stats.in_discards;
+	stats->in_errors = cur_stats.in_errors -
+				pktio_entry->s.stats.in_errors;
+	stats->in_unknown_protos = cur_stats.in_unknown_protos -
+				pktio_entry->s.stats.in_unknown_protos;
+
+	stats->out_octets = cur_stats.out_octets -
+				pktio_entry->s.stats.out_octets;
+	stats->out_ucast_pkts = cur_stats.out_ucast_pkts -
+				pktio_entry->s.stats.out_ucast_pkts;
+	stats->out_discards = cur_stats.out_discards -
+				pktio_entry->s.stats.out_discards;
+	stats->out_errors = cur_stats.out_errors -
+				pktio_entry->s.stats.out_errors;
+
+	return 0;
+}
+
+int sock_stats_reset(pktio_entry_t *pktio_entry)
+{
+	int err = 0;
+	odp_pktio_stats_t cur_stats;
+
+	memset(&cur_stats, 0, sizeof(odp_pktio_stats_t));
+
+	if (pktio_entry->s.use_ethtool) {
+		err = ethtool_stats_get_fd(pktio_entry->s.pkt_sock.sockfd,
+					   pktio_entry->s.name,
+					   &cur_stats);
+	} else {
+		err = sysfs_stats(pktio_entry, &cur_stats);
+	}
+
+	if (err != 0) {
+		ODP_ERR("stats error\n");
+	} else {
+		memcpy(&pktio_entry->s.stats, &cur_stats,
+		       sizeof(odp_pktio_stats_t));
+	}
+
+	return err;
+}
+
 const pktio_if_ops_t sock_mmsg_pktio_ops = {
 	.init = NULL,
 	.term = NULL,
@@ -474,6 +556,8 @@  const pktio_if_ops_t sock_mmsg_pktio_ops = {
 	.close = sock_close,
 	.start = NULL,
 	.stop = NULL,
+	.stats = sock_stats,
+	.stats_reset = sock_stats_reset,
 	.recv = sock_mmsg_recv,
 	.send = sock_mmsg_send,
 	.mtu_get = sock_mtu_get,
diff --git a/platform/linux-generic/pktio/socket_mmap.c b/platform/linux-generic/pktio/socket_mmap.c
index 79ff82d..06bdad6 100644
--- a/platform/linux-generic/pktio/socket_mmap.c
+++ b/platform/linux-generic/pktio/socket_mmap.c
@@ -408,6 +408,7 @@  static int sock_mmap_open(odp_pktio_t id ODP_UNUSED,
 {
 	int if_idx;
 	int ret = 0;
+	odp_pktio_stats_t cur_stats;
 
 	if (getenv("ODP_PKTIO_DISABLE_SOCKET_MMAP"))
 		return -1;
@@ -467,6 +468,22 @@  static int sock_mmap_open(odp_pktio_t id ODP_UNUSED,
 			goto error;
 	}
 
+	ret = ethtool_stats_get_fd(pktio_entry->s.pkt_sock.sockfd,
+				   pktio_entry->s.name,
+				   &cur_stats);
+	if (ret != 0) {
+		ret = sysfs_stats(pktio_entry, &cur_stats);
+		if (ret != 0)
+			ODP_ABORT("statistic counters are not reachable\n");
+		pktio_entry->s.use_ethtool = 0;
+	} else {
+		pktio_entry->s.use_ethtool = 1;
+	}
+
+	ret = sock_stats_reset(pktio_entry);
+	if (ret != 0)
+		goto error;
+
 	return 0;
 
 error:
@@ -525,6 +542,8 @@  const pktio_if_ops_t sock_mmap_pktio_ops = {
 	.close = sock_mmap_close,
 	.start = NULL,
 	.stop = NULL,
+	.stats = sock_stats,
+	.stats_reset = sock_stats_reset,
 	.recv = sock_mmap_recv,
 	.send = sock_mmap_send,
 	.mtu_get = sock_mmap_mtu_get,
diff --git a/platform/linux-generic/pktio/sysfs.c b/platform/linux-generic/pktio/sysfs.c
new file mode 100644
index 0000000..afa5bfb
--- /dev/null
+++ b/platform/linux-generic/pktio/sysfs.c
@@ -0,0 +1,71 @@ 
+/* Copyright (c) 2015, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier:     BSD-3-Clause
+ */
+
+#include <odp.h>
+#include <odp_packet_io_internal.h>
+#include <stdio.h>
+
+static int sysfs_get_val(const char *fname, uint64_t *val)
+{
+	FILE  *file;
+	char str[128];
+	uint64_t ret = -1;
+
+	file = fopen(fname, "rt");
+	if (file == NULL) {
+		/* File not found */
+		return 0;
+	}
+
+	if (fgets(str, sizeof(str), file) != NULL)
+		ret = sscanf(str, "%" SCNx64, val);
+
+	(void)fclose(file);
+
+	if (ret != 1) {
+		ODP_ERR("read %s\n", fname);
+		return -1;
+	}
+
+	return 0;
+}
+
+int sysfs_stats(pktio_entry_t *pktio_entry,
+		odp_pktio_stats_t *stats)
+{
+	char fname[256];
+	const char *dev = pktio_entry->s.name;
+	int ret = 0;
+
+	sprintf(fname, "/sys/class/net/%s/statistics/rx_bytes", dev);
+	ret -= sysfs_get_val(fname, &stats->in_octets);
+
+	sprintf(fname, "/sys/class/net/%s/statistics/rx_packets", dev);
+	ret -= sysfs_get_val(fname, &stats->in_ucast_pkts);
+
+	sprintf(fname, "/sys/class/net/%s/statistics/rx_droppped", dev);
+	ret -= sysfs_get_val(fname, &stats->in_discards);
+
+	sprintf(fname, "/sys/class/net/%s/statistics/rx_errors", dev);
+	ret -= sysfs_get_val(fname, &stats->in_errors);
+
+	/* stats->in_unknown_protos is not supported in sysfs */
+
+	sprintf(fname, "/sys/class/net/%s/statistics/tx_bytes", dev);
+	ret -= sysfs_get_val(fname, &stats->out_octets);
+
+	sprintf(fname, "/sys/class/net/%s/statistics/tx_packets", dev);
+	ret -= sysfs_get_val(fname, &stats->out_ucast_pkts);
+
+	sprintf(fname, "/sys/class/net/%s/statistics/tx_dropped", dev);
+	ret -= sysfs_get_val(fname, &stats->out_discards);
+
+	sprintf(fname, "/sys/class/net/%s/statistics/tx_errors", dev);
+	ret -= sysfs_get_val(fname, &stats->out_errors);
+
+	return ret;
+}
+