aboutsummaryrefslogtreecommitdiff
path: root/platform
diff options
context:
space:
mode:
authorMaxim Uvarov <maxim.uvarov@linaro.org>2016-01-22 15:45:36 +0300
committerMaxim Uvarov <maxim.uvarov@linaro.org>2016-02-08 20:45:23 +0300
commitdd3b06bfbe1a30ac3bfa271887fae22c0d128a8e (patch)
treef7ebd06b84836c99f404f2a04eed00e5212a9aa4 /platform
parent899ca204262ffac9c182b81577db1b2d1980eba1 (diff)
linux-generic: sockets: implement pktio statistics
Signed-off-by: Maxim Uvarov <maxim.uvarov@linaro.org> Reviewed-and-tested-by: Stuart Haslam <stuart.haslam@linaro.org>
Diffstat (limited to 'platform')
-rw-r--r--platform/linux-generic/Makefile.am2
-rw-r--r--platform/linux-generic/include/odp_packet_io_internal.h15
-rw-r--r--platform/linux-generic/include/odp_packet_socket.h5
-rw-r--r--platform/linux-generic/odp_packet_io.c53
-rw-r--r--platform/linux-generic/pktio/ethtool.c164
-rw-r--r--platform/linux-generic/pktio/pktio_common.c72
-rw-r--r--platform/linux-generic/pktio/socket.c51
-rw-r--r--platform/linux-generic/pktio/socket_mmap.c49
-rw-r--r--platform/linux-generic/pktio/sysfs.c76
9 files changed, 487 insertions, 0 deletions
diff --git a/platform/linux-generic/Makefile.am b/platform/linux-generic/Makefile.am
index 42f26cbd6..1f9475469 100644
--- a/platform/linux-generic/Makefile.am
+++ b/platform/linux-generic/Makefile.am
@@ -124,12 +124,14 @@ __LIB__libodp_la_SOURCES = \
odp_packet.c \
odp_packet_flags.c \
odp_packet_io.c \
+ pktio/ethtool.c \
pktio/io_ops.c \
pktio/pktio_common.c \
pktio/loop.c \
pktio/netmap.c \
pktio/socket.c \
pktio/socket_mmap.c \
+ pktio/sysfs.c \
pktio/tap.c \
odp_pool.c \
odp_queue.c \
diff --git a/platform/linux-generic/include/odp_packet_io_internal.h b/platform/linux-generic/include/odp_packet_io_internal.h
index dd71a6fce..66724cce6 100644
--- a/platform/linux-generic/include/odp_packet_io_internal.h
+++ b/platform/linux-generic/include/odp_packet_io_internal.h
@@ -92,6 +92,12 @@ struct pktio_entry {
STATE_STOP
} state;
classifier_t cls; /**< classifier linked with this pktio*/
+ odp_pktio_stats_t stats; /**< statistic counters for pktio */
+ enum {
+ STATS_SYSFS = 0,
+ STATS_ETHTOOL,
+ STATS_UNSUPPORTED
+ } stats_type;
char name[PKTIO_NAME_LEN]; /**< name of pktio provided to
pktio_open() */
odp_pktio_t id;
@@ -132,6 +138,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[],
@@ -222,6 +230,13 @@ extern const pktio_if_ops_t pcap_pktio_ops;
extern const pktio_if_ops_t tap_pktio_ops;
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_fd(pktio_entry_t *pktio_entry,
+ odp_pktio_stats_t *stats,
+ int fd);
+int sock_stats_reset_fd(pktio_entry_t *pktio_entry, int fd);
+
#ifdef __cplusplus
}
#endif
diff --git a/platform/linux-generic/include/odp_packet_socket.h b/platform/linux-generic/include/odp_packet_socket.h
index ec202dbad..a7797d1c0 100644
--- a/platform/linux-generic/include/odp_packet_socket.h
+++ b/platform/linux-generic/include/odp_packet_socket.h
@@ -168,4 +168,9 @@ int rss_conf_set_fd(int fd, const char *name,
*/
void rss_conf_print(const odp_pktin_hash_proto_t *hash_proto);
+/**
+ * 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 9f5afbddd..1fa611d28 100644
--- a/platform/linux-generic/odp_packet_io.c
+++ b/platform/linux-generic/odp_packet_io.c
@@ -983,6 +983,59 @@ int odp_pktio_capability(odp_pktio_t pktio, odp_pktio_capability_t *capa)
return single_capability(capa);
}
+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;
+}
+
int odp_pktio_input_queues_config(odp_pktio_t pktio,
const odp_pktio_input_queue_param_t *param)
{
diff --git a/platform/linux-generic/pktio/ethtool.c b/platform/linux-generic/pktio/ethtool.c
new file mode 100644
index 000000000..1f2943888
--- /dev/null
+++ b/platform/linux-generic/pktio/ethtool.c
@@ -0,0 +1,164 @@
+/* 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 *)(void *)((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 = estats;
+ 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);
+
+ /* Ethtool strings came from kernel driver. Name of that
+ * strings is not universal. Current function needs to be updated
+ * if your driver has different names for counters */
+ 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/pktio_common.c b/platform/linux-generic/pktio/pktio_common.c
index be9db330f..1adc5f2ea 100644
--- a/platform/linux-generic/pktio/pktio_common.c
+++ b/platform/linux-generic/pktio/pktio_common.c
@@ -51,3 +51,75 @@ int _odp_packet_cls_enq(pktio_entry_t *pktio_entry,
return 0;
}
+
+int sock_stats_reset_fd(pktio_entry_t *pktio_entry, int fd)
+{
+ int err = 0;
+ odp_pktio_stats_t cur_stats;
+
+ if (pktio_entry->s.stats_type == STATS_UNSUPPORTED) {
+ memset(&pktio_entry->s.stats, 0,
+ sizeof(odp_pktio_stats_t));
+ return 0;
+ }
+
+ memset(&cur_stats, 0, sizeof(odp_pktio_stats_t));
+
+ if (pktio_entry->s.stats_type == STATS_ETHTOOL) {
+ (void)ethtool_stats_get_fd(fd,
+ pktio_entry->s.name,
+ &cur_stats);
+ } else if (pktio_entry->s.stats_type == STATS_SYSFS) {
+ err = sysfs_stats(pktio_entry, &cur_stats);
+ if (err != 0)
+ ODP_ERR("stats error\n");
+ }
+
+ if (err == 0)
+ memcpy(&pktio_entry->s.stats, &cur_stats,
+ sizeof(odp_pktio_stats_t));
+
+ return err;
+}
+
+int sock_stats_fd(pktio_entry_t *pktio_entry,
+ odp_pktio_stats_t *stats,
+ int fd)
+{
+ odp_pktio_stats_t cur_stats;
+ int ret = 0;
+
+ if (pktio_entry->s.stats_type == STATS_UNSUPPORTED)
+ return 0;
+
+ memset(&cur_stats, 0, sizeof(odp_pktio_stats_t));
+ if (pktio_entry->s.stats_type == STATS_ETHTOOL) {
+ (void)ethtool_stats_get_fd(fd,
+ pktio_entry->s.name,
+ &cur_stats);
+ } else if (pktio_entry->s.stats_type == STATS_SYSFS) {
+ sysfs_stats(pktio_entry, &cur_stats);
+ }
+
+ 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 ret;
+}
diff --git a/platform/linux-generic/pktio/socket.c b/platform/linux-generic/pktio/socket.c
index 4e82ce9c3..27a2b6cba 100644
--- a/platform/linux-generic/pktio/socket.c
+++ b/platform/linux-generic/pktio/socket.c
@@ -46,6 +46,8 @@
#include <odp/helper/eth.h>
#include <odp/helper/ip.h>
+static int sock_stats_reset(pktio_entry_t *pktio_entry);
+
/** Provide a sendmmsg wrapper for systems with no libc or kernel support.
* As it is implemented as a weak symbol, it has zero effect on systems
* with both.
@@ -474,6 +476,7 @@ static int sock_setup_pkt(pktio_entry_t *pktio_entry, const char *netdev,
char shm_name[ODP_SHM_NAME_LEN];
pkt_sock_t *pkt_sock = &pktio_entry->s.pkt_sock;
uint8_t *addr;
+ odp_pktio_stats_t cur_stats;
/* Init pktio entry */
memset(pkt_sock, 0, sizeof(*pkt_sock));
@@ -532,6 +535,27 @@ static int sock_setup_pkt(pktio_entry_t *pktio_entry, const char *netdev,
ODP_ERR("bind(to IF): %s\n", strerror(errno));
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) {
+ pktio_entry->s.stats_type = STATS_UNSUPPORTED;
+ ODP_DBG("pktio: %s unsupported stats\n",
+ pktio_entry->s.name);
+ } else {
+ pktio_entry->s.stats_type = STATS_SYSFS;
+ }
+ } else {
+ pktio_entry->s.stats_type = STATS_ETHTOOL;
+ }
+
+ err = sock_stats_reset(pktio_entry);
+ if (err != 0)
+ goto error;
+
return 0;
error:
@@ -788,6 +812,31 @@ static int sock_link_status(pktio_entry_t *pktio_entry)
pktio_entry->s.name);
}
+static int sock_stats(pktio_entry_t *pktio_entry,
+ odp_pktio_stats_t *stats)
+{
+ if (pktio_entry->s.stats_type == STATS_UNSUPPORTED) {
+ memset(stats, 0, sizeof(*stats));
+ return 0;
+ }
+
+ return sock_stats_fd(pktio_entry,
+ stats,
+ pktio_entry->s.pkt_sock.sockfd);
+}
+
+static int sock_stats_reset(pktio_entry_t *pktio_entry)
+{
+ if (pktio_entry->s.stats_type == STATS_UNSUPPORTED) {
+ memset(&pktio_entry->s.stats, 0,
+ sizeof(odp_pktio_stats_t));
+ return 0;
+ }
+
+ return sock_stats_reset_fd(pktio_entry,
+ pktio_entry->s.pkt_sock.sockfd);
+}
+
const pktio_if_ops_t sock_mmsg_pktio_ops = {
.init = NULL,
.term = NULL,
@@ -795,6 +844,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 f19e031e6..5fb9b1cc3 100644
--- a/platform/linux-generic/pktio/socket_mmap.c
+++ b/platform/linux-generic/pktio/socket_mmap.c
@@ -444,6 +444,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;
@@ -503,6 +504,27 @@ static int sock_mmap_open(odp_pktio_t id ODP_UNUSED,
goto error;
}
+ ret = ethtool_stats_get_fd(pktio_entry->s.pkt_sock_mmap.sockfd,
+ pktio_entry->s.name,
+ &cur_stats);
+ if (ret != 0) {
+ ret = sysfs_stats(pktio_entry, &cur_stats);
+ if (ret != 0) {
+ pktio_entry->s.stats_type = STATS_UNSUPPORTED;
+ ODP_DBG("pktio: %s unsupported stats\n",
+ pktio_entry->s.name);
+ } else {
+ pktio_entry->s.stats_type = STATS_SYSFS;
+ }
+ } else {
+ pktio_entry->s.stats_type = STATS_ETHTOOL;
+ }
+
+ ret = sock_stats_reset_fd(pktio_entry,
+ pktio_entry->s.pkt_sock_mmap.sockfd);
+ if (ret != 0)
+ goto error;
+
return 0;
error:
@@ -559,6 +581,31 @@ static int sock_mmap_link_status(pktio_entry_t *pktio_entry)
pktio_entry->s.name);
}
+static int sock_mmap_stats(pktio_entry_t *pktio_entry,
+ odp_pktio_stats_t *stats)
+{
+ if (pktio_entry->s.stats_type == STATS_UNSUPPORTED) {
+ memset(stats, 0, sizeof(*stats));
+ return 0;
+ }
+
+ return sock_stats_fd(pktio_entry,
+ stats,
+ pktio_entry->s.pkt_sock_mmap.sockfd);
+}
+
+static int sock_mmap_stats_reset(pktio_entry_t *pktio_entry)
+{
+ if (pktio_entry->s.stats_type == STATS_UNSUPPORTED) {
+ memset(&pktio_entry->s.stats, 0,
+ sizeof(odp_pktio_stats_t));
+ return 0;
+ }
+
+ return sock_stats_reset_fd(pktio_entry,
+ pktio_entry->s.pkt_sock_mmap.sockfd);
+}
+
const pktio_if_ops_t sock_mmap_pktio_ops = {
.init = NULL,
.term = NULL,
@@ -566,6 +613,8 @@ const pktio_if_ops_t sock_mmap_pktio_ops = {
.close = sock_mmap_close,
.start = NULL,
.stop = NULL,
+ .stats = sock_mmap_stats,
+ .stats_reset = sock_mmap_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 000000000..4e5c02837
--- /dev/null
+++ b/platform/linux-generic/pktio/sysfs.c
@@ -0,0 +1,76 @@
+/* Copyright (c) 2015, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <odp.h>
+#include <odp_packet_io_internal.h>
+#include <errno.h>
+#include <string.h>
+
+static int sysfs_get_val(const char *fname, uint64_t *val)
+{
+ FILE *file;
+ char str[128];
+ int ret = -1;
+
+ file = fopen(fname, "rt");
+ if (file == NULL) {
+ __odp_errno = errno;
+ /* do not print debug err if sysfs is not supported by
+ * kernel driver.
+ */
+ if (errno != ENOENT)
+ ODP_ERR("fopen %s: %s\n", fname, strerror(errno));
+ 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;
+}