dpdk: expose xstats in stats segment

Expose DPDK xstats in the stat segment.
Represented as a 2D array. Thread by sw_if_index.
Each counter has the same name as the corresponding xstats counter,
under /if/<driver-name>/<xstats-name>

Type: improvement
Change-Id: Icd34b46e2b4d708f1c9a7063d6afd4ced3dfa4f5
Signed-off-by: Ole Troan <otroan@employees.org>
diff --git a/src/plugins/dpdk/device/dpdk.h b/src/plugins/dpdk/device/dpdk.h
index 88a4d9f..692bbbc 100644
--- a/src/plugins/dpdk/device/dpdk.h
+++ b/src/plugins/dpdk/device/dpdk.h
@@ -131,6 +131,7 @@
   u32 interface_number_from_port_id : 1;
   u32 use_intel_phdr_cksum : 1;
   u32 int_unmaskable : 1;
+  vlib_simple_counter_main_t *xstats_counters;
 } dpdk_driver_t;
 
 dpdk_driver_t *dpdk_driver_find (const char *name, const char **desc);
diff --git a/src/plugins/dpdk/device/dpdk_priv.h b/src/plugins/dpdk/device/dpdk_priv.h
index cb7b185..0af4357 100644
--- a/src/plugins/dpdk/device/dpdk_priv.h
+++ b/src/plugins/dpdk/device/dpdk_priv.h
@@ -47,28 +47,35 @@
   xd->flags = val ? xd->flags | flag : xd->flags & ~flag;
 }
 
-static inline void
-dpdk_get_xstats (dpdk_device_t * xd)
-{
-  int len, ret;
+void dpdk_counters_xstats_init (dpdk_device_t *xd);
 
+static inline void
+dpdk_get_xstats (dpdk_device_t *xd, u32 thread_index)
+{
+  int ret;
+  int i;
+  int len = vec_len (xd->xstats);
   if (!(xd->flags & DPDK_DEVICE_FLAG_ADMIN_UP))
     return;
-
-  len = rte_eth_xstats_get (xd->port_id, NULL, 0);
-  if (len < 0)
+  if (xd->driver == 0)
     return;
 
-  vec_validate (xd->xstats, len - 1);
-
   ret = rte_eth_xstats_get (xd->port_id, xd->xstats, len);
-  if (ret < 0 || ret > len)
+  if (ret < 0 || ret > len || len != vec_len (xd->driver->xstats_counters))
     {
+      /* Failed, expand vector and try again on next time around the track. */
+      vec_validate (xd->xstats, ret - 1);
       vec_set_len (xd->xstats, 0);
+      dpdk_log_warn ("rte_eth_xstats_get(%d) failed: %d", xd->port_id, ret);
       return;
     }
+  vec_foreach_index (i, xd->xstats)
+    {
+      vlib_set_simple_counter (&xd->driver->xstats_counters[i], thread_index,
+			       xd->sw_if_index, xd->xstats[i].value);
+    }
 
-  vec_set_len (xd->xstats, len);
+  vec_set_len (xd->xstats, ret);
 }
 
 #define DPDK_UPDATE_COUNTER(vnm, tidx, xd, stat, cnt)                         \
@@ -107,7 +114,7 @@
   DPDK_UPDATE_COUNTER (vnm, thread_index, xd, ierrors,
 		       VNET_INTERFACE_COUNTER_RX_ERROR);
 
-  dpdk_get_xstats (xd);
+  dpdk_get_xstats (xd, thread_index);
 }
 
 #if RTE_VERSION < RTE_VERSION_NUM(21, 11, 0, 0)
diff --git a/src/plugins/dpdk/device/init.c b/src/plugins/dpdk/device/init.c
index e416efe..453d9cf 100644
--- a/src/plugins/dpdk/device/init.c
+++ b/src/plugins/dpdk/device/init.c
@@ -30,7 +30,7 @@
 #include <dpdk/cryptodev/cryptodev.h>
 #include <vlib/pci/pci.h>
 #include <vlib/vmbus/vmbus.h>
-
+#include <vlib/stats/stats.h>
 #include <rte_ring.h>
 #include <rte_vect.h>
 
@@ -226,6 +226,75 @@
   return &dm->conf->default_devconf;
 }
 
+/*
+ * Initialise or refresh the xstats counters for a device
+ */
+void
+dpdk_counters_xstats_init (dpdk_device_t *xd)
+{
+  int len, ret, i;
+  struct rte_eth_xstat_name *xstats_names = 0;
+  char *name;
+  dpdk_driver_t *dr = xd->driver;
+
+  /* Only support xstats for supported drivers */
+  if (!dr)
+    return;
+
+  len = rte_eth_xstats_get_names (xd->port_id, 0, 0);
+  if (len < 0)
+    {
+      dpdk_log_err ("[%u] rte_eth_xstats_get_names failed: %d", xd->port_id,
+		    len);
+      return;
+    }
+  /* Counters for this driver is already initialised */
+  if (vec_len (dr->xstats_counters) == len)
+    {
+      vec_foreach_index (i, dr->xstats_counters)
+	{
+	  vlib_validate_simple_counter (&dr->xstats_counters[i],
+					xd->sw_if_index);
+	  vlib_zero_simple_counter (&dr->xstats_counters[i], xd->sw_if_index);
+	}
+      return;
+    }
+
+  /* Same driver, different interface, different length of counter array. */
+  ASSERT (vec_len (dr->xstats_counters) == 0);
+
+  vec_validate (xstats_names, len - 1);
+
+  ret = rte_eth_xstats_get_names (xd->port_id, xstats_names, len);
+  if (ret >= 0 && ret <= len)
+    {
+      vec_validate (dr->xstats_counters, len - 1);
+      vec_foreach_index (i, xstats_names)
+	{
+	  name = (char *) format (0, "/if/%s/%s%c", dr->drivers->name,
+				  xstats_names[i].name, 0);
+
+	  /* There is a bug in the ENA driver where the xstats names are not
+	   * unique. */
+	  if (vlib_stats_find_entry_index (name) != STAT_SEGMENT_INDEX_INVALID)
+	    {
+	      vec_free (name);
+	      name = (char *) format (0, "/if/%s/%s_%d%c", dr->drivers->name,
+				      xstats_names[i].name, i, 0);
+	    }
+
+	  dr->xstats_counters[i].name = name;
+	  dr->xstats_counters[i].stat_segment_name = name;
+	  dr->xstats_counters[i].counters = 0;
+	  vlib_validate_simple_counter (&dr->xstats_counters[i],
+					xd->sw_if_index);
+	  vlib_zero_simple_counter (&dr->xstats_counters[i], xd->sw_if_index);
+	  vec_free (name);
+	}
+    }
+  vec_free (xstats_names);
+}
+
 static clib_error_t *
 dpdk_lib_init (dpdk_main_t * dm)
 {
@@ -532,6 +601,7 @@
       if (vec_len (xd->errors))
 	dpdk_log_err ("[%u] setup failed Errors:\n  %U", port_id,
 		      format_dpdk_device_errors, xd);
+      dpdk_counters_xstats_init (xd);
     }
 
   for (int i = 0; i < vec_len (dm->devices); i++)