[ovs-dev,v4] netdev: Custom statistics.

Message ID 1515484537-45766-1-git-send-email-michalx.weglicki@intel.com
State Accepted
Headers show
Series
  • [ovs-dev,v4] netdev: Custom statistics.
Related show

Commit Message

Weglicki, MichalX Jan. 9, 2018, 7:55 a.m.
- New get_custom_stats interface function is added to netdev. It
  allows particular netdev implementation to expose custom
  counters in dictionary format (counter name/counter value).
- New statistics are retrieved using experimenter code and
  are printed as a result to ofctl dump-ports.
- New counters are available for OpenFlow 1.4+.
- New statistics are printed to output via ofctl only if those
  are present in reply message.
- New statistics definition is added to include/openflow/intel-ext.h.
- Custom statistics are implemented only for dpdk-physical
  port type.
- DPDK-physical implementation uses xstats to collect statistics.
  Only dropped and error counters are exposed.

v1->v2:
- Buffer overrun check in parse_intel_port_custom_property.
- ofputil_append_ofp14_port_stats uses "postappend" instead
  of "reserve" during message creation.
- NEWS update.
- DPDK documentation update.
- Compilation and sparse warnings corrections.
v2->v3:
- Netdev statistics and custom statistics are inserted into
  ovsdb at the same time (in single transaction).
- ofproto_class function has been removed, direct call is
  used instead.
- Current "dump ports" test case has been adjusted to check
  also custom counters.
- Some other minor corrections.
v3->v4:
- netdev-dpdk: "management" counters are added to custom
  statistics.

Co-authored-by: Ben Pfaff <blp@ovn.org>
Signed-off-by: Ben Pfaff <blp@ovn.org>
Signed-off-by: Michal Weglicki <michalx.weglicki@intel.com>
---
 Documentation/howto/dpdk.rst   |  15 ++--
 NEWS                           |   5 ++
 include/openflow/intel-ext.h   |  28 ++++++
 include/openvswitch/netdev.h   |  17 ++++
 include/openvswitch/ofp-util.h |   1 +
 lib/netdev-dpdk.c              | 193 ++++++++++++++++++++++++++++++++++++++++-
 lib/netdev-dummy.c             |  36 ++++++++
 lib/netdev-linux.c             |   1 +
 lib/netdev-provider.h          |  13 +++
 lib/netdev-vport.c             |   1 +
 lib/netdev.c                   |  27 ++++++
 lib/netdev.h                   |   2 +
 lib/ofp-print.c                |  18 ++++
 lib/ofp-util.c                 | 106 ++++++++++++++++++++--
 lib/util.c                     |  13 +++
 lib/util.h                     |   2 +
 ofproto/ofproto.c              |   3 +
 tests/ofproto.at               |   6 +-
 vswitchd/bridge.c              |  36 ++++++--
 19 files changed, 503 insertions(+), 20 deletions(-)

Comments

Ben Pfaff Jan. 10, 2018, 11:29 p.m. | #1
On Tue, Jan 09, 2018 at 07:55:37AM +0000, Michal Weglicki wrote:
> - New get_custom_stats interface function is added to netdev. It
>   allows particular netdev implementation to expose custom
>   counters in dictionary format (counter name/counter value).
> - New statistics are retrieved using experimenter code and
>   are printed as a result to ofctl dump-ports.
> - New counters are available for OpenFlow 1.4+.
> - New statistics are printed to output via ofctl only if those
>   are present in reply message.
> - New statistics definition is added to include/openflow/intel-ext.h.
> - Custom statistics are implemented only for dpdk-physical
>   port type.
> - DPDK-physical implementation uses xstats to collect statistics.
>   Only dropped and error counters are exposed.
> 
> v1->v2:
> - Buffer overrun check in parse_intel_port_custom_property.
> - ofputil_append_ofp14_port_stats uses "postappend" instead
>   of "reserve" during message creation.
> - NEWS update.
> - DPDK documentation update.
> - Compilation and sparse warnings corrections.
> v2->v3:
> - Netdev statistics and custom statistics are inserted into
>   ovsdb at the same time (in single transaction).
> - ofproto_class function has been removed, direct call is
>   used instead.
> - Current "dump ports" test case has been adjusted to check
>   also custom counters.
> - Some other minor corrections.
> v3->v4:
> - netdev-dpdk: "management" counters are added to custom
>   statistics.
> 
> Co-authored-by: Ben Pfaff <blp@ovn.org>
> Signed-off-by: Ben Pfaff <blp@ovn.org>
> Signed-off-by: Michal Weglicki <michalx.weglicki@intel.com>

Thanks for the patch!

I noticed some oddities in the test and figured out that what was going
on was some white space at the end of lines, so I fixed up the code to
avoid white space at the end of lines and the test to match.  I also
deleted some code that checked for a NULL return value from xmalloc(),
which can't happen.  I'm appending what I folded in.

With those changes, I applied this to master.

Thanks again!

--8<--------------------------cut here-------------------------->8--

diff --git a/lib/ofp-print.c b/lib/ofp-print.c
index 6c64cd267bc0..8a1f426ade36 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -1960,8 +1960,10 @@ ofp_print_ofpst_port_reply(struct ds *string, const struct ofp_header *oh,
                     if (i % 3 == 0) {
                         ds_put_cstr(string, "\n");
                         ds_put_cstr(string, "                      ");
+                    } else {
+                        ds_put_char(string, ' ');
                     }
-                    ds_put_format(string, "%s=%"PRIu64", ",
+                    ds_put_format(string, "%s=%"PRIu64",",
                                   ps.custom_stats.counters[i].name,
                                   ps.custom_stats.counters[i].value);
                 }
diff --git a/tests/ofproto.at b/tests/ofproto.at
index c5eb5f654d57..ec1dc5161309 100644
--- a/tests/ofproto.at
+++ b/tests/ofproto.at
@@ -105,14 +105,14 @@ AT_CLEANUP
 AT_SETUP([ofproto - port stats - (OpenFlow 1.4)])
 OVS_VSWITCHD_START
 AT_CHECK([ovs-ofctl -O OpenFlow14 -vwarn dump-ports br0], [0], [stdout])
-AT_CHECK_UNQUOTED([strip_xids < stdout | sed 's/duration=[[0-9.]]*s/duration=?s/'],
+AT_CHECK([strip_xids < stdout | sed 's/duration=[[0-9.]]*s/duration=?s/'],
   [0], [dnl
-$(echo 'OFPST_PORT reply (OF1.4): 1 ports
+OFPST_PORT reply (OF1.4): 1 ports
   port LOCAL: rx pkts=0, bytes=0, drop=?, errs=?, frame=?, over=?, crc=?
            tx pkts=0, bytes=0, drop=?, errs=?, coll=?
            duration=?s
            CUSTOM Statistics
-                      rx_custom_packets_1=0, rx_custom_packets_2=0, ')
+                      rx_custom_packets_1=0, rx_custom_packets_2=0,
 ])
 OVS_VSWITCHD_STOP
 AT_CLEANUP
diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c
index 28c96c1cb64d..d80da1cdd6e9 100644
--- a/vswitchd/bridge.c
+++ b/vswitchd/bridge.c
@@ -2401,11 +2401,6 @@ iface_refresh_stats(struct iface *iface)
     int64_t *values = xmalloc(counters_size * sizeof(int64_t));
     const char **keys = xmalloc(counters_size * sizeof(char *));
 
-    if ((keys == NULL) || (values == NULL)) {
-        VLOG_ERR("Can't allocate memory to get statistics!");
-        return;
-    }
-
     /* Intentionally ignore return value, since errors will set 'stats' to
      * all-1s, and we will deal with that correctly below. */
     netdev_get_stats(iface->netdev, &stats);

Patch

diff --git a/Documentation/howto/dpdk.rst b/Documentation/howto/dpdk.rst
index 2393c2f..587aaed 100644
--- a/Documentation/howto/dpdk.rst
+++ b/Documentation/howto/dpdk.rst
@@ -311,12 +311,16 @@  performance of non-tunnel traffic, specifically for smaller size packet.
 
 .. _extended-statistics:
 
-Extended Statistics
--------------------
+Extended & Custom Statistics
+----------------------------
 
 DPDK Extended Statistics API allows PMD to expose unique set of statistics.
 The Extended statistics are implemented and supported only for DPDK physical
-and vHost ports.
+and vHost ports. Custom statistics are dynamic set of counters which can
+vary depenend on a driver. Those statistics are implemented
+for DPDK physical ports and contain all "dropped", "error" and "management"
+counters from XSTATS. XSTATS counters list can be found here:
+<https://wiki.opnfv.org/display/fastpath/Collectd+Metrics+and+Events>`__.
 
 To enable statistics, you have to enable OpenFlow 1.4 support for OVS.
 Configure bridge br0 to support OpenFlow version 1.4::
@@ -333,8 +337,9 @@  Query the port statistics by explicitly specifying -O OpenFlow14 option::
 
     $ ovs-ofctl -O OpenFlow14 dump-ports br0
 
-Note: vHost ports supports only partial statistics. RX packet size based
-counter are only supported and doesn't include TX packet size counters.
+Note about "Extended Statistics": vHost ports supports only partial
+statistics. RX packet size based counter are only supported and
+doesn't include TX packet size counters.
 
 .. _port-hotplug:
 
diff --git a/NEWS b/NEWS
index a7f2def..9d95434 100644
--- a/NEWS
+++ b/NEWS
@@ -29,6 +29,11 @@  Post-v2.8.0
      * Add support for vHost IOMMU
      * New debug appctl command 'netdev-dpdk/get-mempool-info'.
      * All the netdev-dpdk appctl commands described in ovs-vswitchd man page.
+     * Custom statistics:
+        - DPDK physical ports now return custom set of "dropped", "error" and
+          "management" statistics.
+        - ovs-ofctl dump-ports command now prints new of set custom statistics
+          if available (for OpenFlow 1.4+).
    - vswitchd:
      * Datapath IDs may now be specified as 0x1 (etc.) instead of 16 digits.
 
diff --git a/include/openflow/intel-ext.h b/include/openflow/intel-ext.h
index 974e63e..3d73171 100644
--- a/include/openflow/intel-ext.h
+++ b/include/openflow/intel-ext.h
@@ -27,9 +27,11 @@ 
 
 enum intel_port_stats_subtype {
     INTEL_PORT_STATS_RFC2819 = 1,
+    INTEL_PORT_STATS_CUSTOM
 };
 
 #define INTEL_PORT_STATS_RFC2819_SIZE 184
+#define INTEL_PORT_STATS_CUSTOM_SIZE 16
 
 /* Struct implements custom property type based on
  * 'ofp_prop_experimenter'. */
@@ -70,4 +72,30 @@  struct intel_port_stats_rfc2819 {
 OFP_ASSERT(sizeof (struct intel_port_stats_rfc2819) ==
            INTEL_PORT_STATS_RFC2819_SIZE);
 
+/* Structure implements custom property type based on
+ * 'ofp_prop_experimenter'. It contains custom
+ * statistics in dictionary format */
+struct intel_port_custom_stats {
+    ovs_be16 type;              /* OFPPSPT14_EXPERIMENTER. */
+    ovs_be16 length;            /* Length in bytes of this property excluding
+                                 * trailing padding. */
+    ovs_be32 experimenter;      /* INTEL_VENDOR_ID. */
+    ovs_be32 exp_type;          /* INTEL_PORT_STATS_*. */
+
+    uint8_t pad[2];
+    ovs_be16 stats_array_size;  /* number of counters. */
+
+    /* Followed by:
+     *   - Exactly 'stats_array_size' array elements of
+     *     dynamic structure which contains:
+     *     - "NAME SIZE" - counter name size (number of characters)
+     *     - "COUNTER NAME" - Exact number of characters
+     *       defined by "NAME SIZE".
+     *     - "COUNTER VALUE" -  ovs_be64 counter value,
+     *   - Zero or more bytes to fill out the
+     *     overall length in header.length. */
+};
+OFP_ASSERT(sizeof(struct intel_port_custom_stats) ==
+                                  INTEL_PORT_STATS_CUSTOM_SIZE);
+
 #endif /* openflow/intel-ext.h */
diff --git a/include/openvswitch/netdev.h b/include/openvswitch/netdev.h
index 50bafc3..e25c241 100644
--- a/include/openvswitch/netdev.h
+++ b/include/openvswitch/netdev.h
@@ -27,6 +27,9 @@  extern "C" {
 
 struct netdev;
 
+/* Maximum name length for custom statistics counters */
+#define NETDEV_CUSTOM_STATS_NAME_SIZE 64
+
 /* Network device statistics.
  *
  * Values of unsupported statistics are set to all-1-bits (UINT64_MAX). */
@@ -85,6 +88,18 @@  struct netdev_stats {
     uint64_t rx_jabber_errors;
 };
 
+/* Structure representation of custom statistics counter */
+struct netdev_custom_counter {
+    uint64_t value;
+    char name[NETDEV_CUSTOM_STATS_NAME_SIZE];
+};
+
+/* Structure representation of custom statistics */
+struct netdev_custom_stats {
+    uint16_t size;
+    struct netdev_custom_counter *counters;
+};
+
 /* Features. */
 enum netdev_features {
     NETDEV_F_10MB_HD =    1 << 0,  /* 10 Mb half-duplex rate support. */
@@ -115,6 +130,8 @@  uint64_t netdev_features_to_bps(enum netdev_features features,
 bool netdev_features_is_full_duplex(enum netdev_features features);
 int netdev_set_advertisements(struct netdev *, enum netdev_features advertise);
 
+void netdev_free_custom_stats_counters(struct netdev_custom_stats *);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/include/openvswitch/ofp-util.h b/include/openvswitch/ofp-util.h
index a9e57ed..296078a 100644
--- a/include/openvswitch/ofp-util.h
+++ b/include/openvswitch/ofp-util.h
@@ -1174,6 +1174,7 @@  bool ofputil_parse_key_value(char **stringp, char **keyp, char **valuep);
 struct ofputil_port_stats {
     ofp_port_t port_no;
     struct netdev_stats stats;
+    struct netdev_custom_stats custom_stats;
     uint32_t duration_sec;      /* UINT32_MAX if unknown. */
     uint32_t duration_nsec;
 };
diff --git a/lib/netdev-dpdk.c b/lib/netdev-dpdk.c
index 364f545..e32c7f6 100644
--- a/lib/netdev-dpdk.c
+++ b/lib/netdev-dpdk.c
@@ -434,6 +434,14 @@  struct netdev_dpdk {
          * from the enum set 'dpdk_hw_ol_features' */
         uint32_t hw_ol_features;
     );
+
+    PADDED_MEMBERS(CACHE_LINE_SIZE,
+        /* Names of all XSTATS counters */
+        struct rte_eth_xstat_name *rte_xstats_names;
+        int rte_xstats_names_size;
+        int rte_xstats_ids_size;
+        uint64_t *rte_xstats_ids;
+    );
 };
 
 struct netdev_rxq_dpdk {
@@ -916,6 +924,12 @@  common_construct(struct netdev *netdev, dpdk_port_t port_no,
 
     netdev_request_reconfigure(netdev);
 
+    dev->rte_xstats_names = NULL;
+    dev->rte_xstats_names_size = 0;
+
+    dev->rte_xstats_ids = NULL;
+    dev->rte_xstats_ids_size = 0;
+
     return 0;
 }
 
@@ -1154,6 +1168,126 @@  netdev_dpdk_dealloc(struct netdev *netdev)
     rte_free(dev);
 }
 
+static void
+netdev_dpdk_clear_xstats(struct netdev_dpdk *dev) OVS_REQUIRES(dev->mutex)
+{
+    /* If statistics are already allocated, we have to
+     * reconfigure, as port_id could have been changed. */
+    if (dev->rte_xstats_names) {
+        free(dev->rte_xstats_names);
+        dev->rte_xstats_names = NULL;
+        dev->rte_xstats_names_size = 0;
+    }
+    if (dev->rte_xstats_ids) {
+        free(dev->rte_xstats_ids);
+        dev->rte_xstats_ids = NULL;
+        dev->rte_xstats_ids_size = 0;
+    }
+}
+
+static const char*
+netdev_dpdk_get_xstat_name(struct netdev_dpdk *dev, uint64_t id)
+{
+    if (id >= dev->rte_xstats_names_size) {
+        return "UNKNOWN";
+    }
+    return dev->rte_xstats_names[id].name;
+}
+
+static bool
+netdev_dpdk_configure_xstats(struct netdev_dpdk *dev)
+    OVS_REQUIRES(dev->mutex)
+{
+    int rte_xstats_len;
+    bool ret;
+    struct rte_eth_xstat *rte_xstats;
+    uint64_t id;
+    int xstats_no;
+    const char *name;
+
+    /* Retrieving all XSTATS names. If something will go wrong
+     * or amount of counters will be equal 0, rte_xstats_names
+     * buffer will be marked as NULL, and any further xstats
+     * query won't be performed (e.g. during netdev_dpdk_get_stats
+     * execution). */
+
+    ret = false;
+    rte_xstats = NULL;
+
+    if (dev->rte_xstats_names == NULL || dev->rte_xstats_ids == NULL) {
+        dev->rte_xstats_names_size =
+                rte_eth_xstats_get_names(dev->port_id, NULL, 0);
+
+        if (dev->rte_xstats_names_size < 0) {
+            VLOG_WARN("Cannot get XSTATS for port: %"PRIu8, dev->port_id);
+            dev->rte_xstats_names_size = 0;
+        } else {
+            /* Reserve memory for xstats names and values */
+            dev->rte_xstats_names = xcalloc(dev->rte_xstats_names_size,
+                                            sizeof *dev->rte_xstats_names);
+
+            if (dev->rte_xstats_names) {
+                /* Retreive xstats names */
+                rte_xstats_len =
+                        rte_eth_xstats_get_names(dev->port_id,
+                                                 dev->rte_xstats_names,
+                                                 dev->rte_xstats_names_size);
+
+                if (rte_xstats_len < 0) {
+                    VLOG_WARN("Cannot get XSTATS names for port: %"PRIu8,
+                               dev->port_id);
+                    goto out;
+                } else if (rte_xstats_len != dev->rte_xstats_names_size) {
+                    VLOG_WARN("XSTATS size doesn't match for port: %"PRIu8,
+                              dev->port_id);
+                    goto out;
+                }
+
+                dev->rte_xstats_ids = xcalloc(dev->rte_xstats_names_size,
+                                              sizeof(uint64_t));
+
+                /* We have to calculate number of counters */
+                rte_xstats = xmalloc(rte_xstats_len * sizeof *rte_xstats);
+                memset(rte_xstats, 0xff, sizeof *rte_xstats * rte_xstats_len);
+
+                /* Retreive xstats values */
+                if (rte_eth_xstats_get(dev->port_id, rte_xstats,
+                                       rte_xstats_len) > 0) {
+                    dev->rte_xstats_ids_size = 0;
+                    xstats_no = 0;
+                    for (uint32_t i = 0; i < rte_xstats_len; i++) {
+                        id = rte_xstats[i].id;
+                        name = netdev_dpdk_get_xstat_name(dev, id);
+                        /* We need to filter out everything except
+                         * dropped, error and management counters */
+                        if (string_ends_with(name, "_errors") ||
+                            strstr(name, "_management_") ||
+                            string_ends_with(name, "_dropped")) {
+
+                            dev->rte_xstats_ids[xstats_no] = id;
+                            xstats_no++;
+                        }
+                    }
+                    dev->rte_xstats_ids_size = xstats_no;
+                    ret = true;
+                } else {
+                    VLOG_WARN("Can't get XSTATS IDs for port: %"PRIu8,
+                              dev->port_id);
+                }
+            }
+        }
+    } else {
+        /* Already configured */
+        ret = true;
+    }
+
+out:
+    if (!ret) {
+        netdev_dpdk_clear_xstats(dev);
+    }
+    return ret;
+}
+
 static int
 netdev_dpdk_get_config(const struct netdev *netdev, struct smap *args)
 {
@@ -1325,6 +1459,7 @@  netdev_dpdk_set_config(struct netdev *netdev, const struct smap *args,
                     dev->devargs = xstrdup(new_devargs);
                     dev->port_id = new_port_id;
                     netdev_request_reconfigure(&dev->up);
+                    netdev_dpdk_clear_xstats(dev);
                     err = 0;
                 }
             }
@@ -2182,6 +2317,56 @@  out:
 }
 
 static int
+netdev_dpdk_get_custom_stats(const struct netdev *netdev,
+                             struct netdev_custom_stats *custom_stats)
+{
+
+    uint32_t i;
+    struct netdev_dpdk *dev = netdev_dpdk_cast(netdev);
+    int rte_xstats_ret;
+
+    ovs_mutex_lock(&dev->mutex);
+
+    if (netdev_dpdk_configure_xstats(dev)) {
+        uint64_t *values = xcalloc(dev->rte_xstats_ids_size,
+                                   sizeof(uint64_t));
+
+        rte_xstats_ret =
+                rte_eth_xstats_get_by_id(dev->port_id, dev->rte_xstats_ids,
+                                         values, dev->rte_xstats_ids_size);
+
+        if (rte_xstats_ret > 0 &&
+            rte_xstats_ret <= dev->rte_xstats_ids_size) {
+
+            custom_stats->size = rte_xstats_ret;
+            custom_stats->counters =
+                    (struct netdev_custom_counter *) xcalloc(rte_xstats_ret,
+                            sizeof(struct netdev_custom_counter));
+
+            for (i = 0; i < rte_xstats_ret; i++) {
+                ovs_strlcpy(custom_stats->counters[i].name,
+                            netdev_dpdk_get_xstat_name(dev,
+                                                       dev->rte_xstats_ids[i]),
+                            NETDEV_CUSTOM_STATS_NAME_SIZE);
+                custom_stats->counters[i].value = values[i];
+            }
+        } else {
+            VLOG_WARN("Cannot get XSTATS values for port: %"PRIu8,
+                      dev->port_id);
+            custom_stats->counters = NULL;
+            custom_stats->size = 0;
+            /* Let's clear statistics cache, so it will be
+             * reconfigured */
+            netdev_dpdk_clear_xstats(dev);
+        }
+    }
+
+    ovs_mutex_unlock(&dev->mutex);
+
+    return 0;
+}
+
+static int
 netdev_dpdk_get_features(const struct netdev *netdev,
                          enum netdev_features *current,
                          enum netdev_features *advertised,
@@ -3391,7 +3576,8 @@  unlock:
 
 #define NETDEV_DPDK_CLASS(NAME, INIT, CONSTRUCT, DESTRUCT,    \
                           SET_CONFIG, SET_TX_MULTIQ, SEND,    \
-                          GET_CARRIER, GET_STATS,             \
+                          GET_CARRIER, GET_STATS,			  \
+                          GET_CUSTOM_STATS,					  \
                           GET_FEATURES, GET_STATUS,           \
                           RECONFIGURE, RXQ_RECV)              \
 {                                                             \
@@ -3426,6 +3612,7 @@  unlock:
     netdev_dpdk_get_carrier_resets,                           \
     netdev_dpdk_set_miimon,                                   \
     GET_STATS,                                                \
+    GET_CUSTOM_STATS,										  \
     GET_FEATURES,                                             \
     NULL,                       /* set_advertisements */      \
     NULL,                       /* get_pt_mode */             \
@@ -3475,6 +3662,7 @@  static const struct netdev_class dpdk_class =
         netdev_dpdk_eth_send,
         netdev_dpdk_get_carrier,
         netdev_dpdk_get_stats,
+        netdev_dpdk_get_custom_stats,
         netdev_dpdk_get_features,
         netdev_dpdk_get_status,
         netdev_dpdk_reconfigure,
@@ -3491,6 +3679,7 @@  static const struct netdev_class dpdk_ring_class =
         netdev_dpdk_ring_send,
         netdev_dpdk_get_carrier,
         netdev_dpdk_get_stats,
+        netdev_dpdk_get_custom_stats,
         netdev_dpdk_get_features,
         netdev_dpdk_get_status,
         netdev_dpdk_reconfigure,
@@ -3509,6 +3698,7 @@  static const struct netdev_class dpdk_vhost_class =
         netdev_dpdk_vhost_get_stats,
         NULL,
         NULL,
+        NULL,
         netdev_dpdk_vhost_reconfigure,
         netdev_dpdk_vhost_rxq_recv);
 static const struct netdev_class dpdk_vhost_client_class =
@@ -3524,6 +3714,7 @@  static const struct netdev_class dpdk_vhost_client_class =
         netdev_dpdk_vhost_get_stats,
         NULL,
         NULL,
+        NULL,
         netdev_dpdk_vhost_client_reconfigure,
         netdev_dpdk_vhost_rxq_recv);
 
diff --git a/lib/netdev-dummy.c b/lib/netdev-dummy.c
index 40086a3..57da19c 100644
--- a/lib/netdev-dummy.c
+++ b/lib/netdev-dummy.c
@@ -46,6 +46,8 @@ 
 
 VLOG_DEFINE_THIS_MODULE(netdev_dummy);
 
+#define C_STATS_SIZE 2
+
 struct reconnect;
 
 struct dummy_packet_stream {
@@ -109,6 +111,7 @@  struct netdev_dummy {
     struct eth_addr hwaddr OVS_GUARDED;
     int mtu OVS_GUARDED;
     struct netdev_stats stats OVS_GUARDED;
+    struct netdev_custom_counter custom_stats[C_STATS_SIZE] OVS_GUARDED;
     enum netdev_flags flags OVS_GUARDED;
     int ifindex OVS_GUARDED;
     int numa_id OVS_GUARDED;
@@ -686,6 +689,13 @@  netdev_dummy_construct(struct netdev *netdev_)
     netdev->requested_n_txq = netdev_->n_txq;
     netdev->numa_id = 0;
 
+    memset(&netdev->custom_stats, 0, sizeof(netdev->custom_stats));
+
+    ovs_strlcpy(netdev->custom_stats[0].name,
+                "rx_custom_packets_1", NETDEV_CUSTOM_STATS_NAME_SIZE);
+    ovs_strlcpy(netdev->custom_stats[1].name,
+                "rx_custom_packets_2", NETDEV_CUSTOM_STATS_NAME_SIZE);
+
     dummy_packet_conn_init(&netdev->conn);
 
     ovs_list_init(&netdev->rxes);
@@ -1021,6 +1031,8 @@  netdev_dummy_rxq_recv(struct netdev_rxq *rxq_, struct dp_packet_batch *batch)
     ovs_mutex_lock(&netdev->mutex);
     netdev->stats.rx_packets++;
     netdev->stats.rx_bytes += dp_packet_size(packet);
+    netdev->custom_stats[0].value++;
+    netdev->custom_stats[1].value++;
     ovs_mutex_unlock(&netdev->mutex);
 
     batch->packets[0] = packet;
@@ -1215,6 +1227,29 @@  netdev_dummy_get_stats(const struct netdev *netdev, struct netdev_stats *stats)
 }
 
 static int
+netdev_dummy_get_custom_stats(const struct netdev *netdev,
+                             struct netdev_custom_stats *custom_stats)
+{
+    int i;
+
+    struct netdev_dummy *dev = netdev_dummy_cast(netdev);
+
+    custom_stats->size = 2;
+    custom_stats->counters =
+            (struct netdev_custom_counter *) xcalloc(C_STATS_SIZE,
+                    sizeof(struct netdev_custom_counter));
+
+    for (i = 0 ; i < C_STATS_SIZE ; i++) {
+        custom_stats->counters[i].value = dev->custom_stats[i].value;
+        ovs_strlcpy(custom_stats->counters[i].name,
+                    dev->custom_stats[i].name,
+                    NETDEV_CUSTOM_STATS_NAME_SIZE);
+    }
+
+    return 0;
+}
+
+static int
 netdev_dummy_get_queue(const struct netdev *netdev OVS_UNUSED,
                        unsigned int queue_id, struct smap *details OVS_UNUSED)
 {
@@ -1383,6 +1418,7 @@  netdev_dummy_update_flags(struct netdev *netdev_,
     NULL,                       /* get_carrier_resets */        \
     NULL,                       /* get_miimon */                \
     netdev_dummy_get_stats,                                     \
+    netdev_dummy_get_custom_stats,                              \
                                                                 \
     NULL,                       /* get_features */              \
     NULL,                       /* set_advertisements */        \
diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c
index ccb6def..37143b8 100644
--- a/lib/netdev-linux.c
+++ b/lib/netdev-linux.c
@@ -2853,6 +2853,7 @@  netdev_linux_update_flags(struct netdev *netdev_, enum netdev_flags off,
     netdev_linux_get_carrier_resets,                            \
     netdev_linux_set_miimon_interval,                           \
     GET_STATS,                                                  \
+    NULL,														\
                                                                 \
     GET_FEATURES,                                               \
     netdev_linux_set_advertisements,                            \
diff --git a/lib/netdev-provider.h b/lib/netdev-provider.h
index 3d9f336..25bd671 100644
--- a/lib/netdev-provider.h
+++ b/lib/netdev-provider.h
@@ -458,6 +458,19 @@  struct netdev_class {
      * (UINT64_MAX). */
     int (*get_stats)(const struct netdev *netdev, struct netdev_stats *);
 
+    /* Retrieves current device custom stats for 'netdev' into 'custom_stats'.
+     *
+     * A network device should return only available statistics (if any).
+     * If there are not statistics available, empty array should be
+     * returned.
+     *
+     * The caller initializes 'custom_stats' before calling this function.
+     * The caller takes ownership over allocated array of counters inside
+     * structure netdev_custom_stats.
+     * */
+    int (*get_custom_stats)(const struct netdev *netdev,
+                            struct netdev_custom_stats *custom_stats);
+
     /* Stores the features supported by 'netdev' into each of '*current',
      * '*advertised', '*supported', and '*peer'.  Each value is a bitmap of
      * NETDEV_F_* bits.
diff --git a/lib/netdev-vport.c b/lib/netdev-vport.c
index f72025e..478ed90 100644
--- a/lib/netdev-vport.c
+++ b/lib/netdev-vport.c
@@ -907,6 +907,7 @@  netdev_vport_get_ifindex(const struct netdev *netdev_)
     NULL,                       /* get_carrier_resets */    \
     NULL,                       /* get_miimon */            \
     get_stats,                                              \
+    NULL,                       /* get_custom_stats */      \
                                                             \
     NULL,                       /* get_features */          \
     NULL,                       /* set_advertisements */    \
diff --git a/lib/netdev.c b/lib/netdev.c
index b1b6da4..be05dc6 100644
--- a/lib/netdev.c
+++ b/lib/netdev.c
@@ -1421,6 +1421,21 @@  netdev_get_stats(const struct netdev *netdev, struct netdev_stats *stats)
     return error;
 }
 
+/* Retrieves current device custom stats for 'netdev'. */
+int
+netdev_get_custom_stats(const struct netdev *netdev,
+                        struct netdev_custom_stats *custom_stats)
+{
+    int error;
+    memset(custom_stats, 0, sizeof *custom_stats);
+    error = (netdev->netdev_class->get_custom_stats
+             ? netdev->netdev_class->get_custom_stats(netdev, custom_stats)
+             : EOPNOTSUPP);
+
+    return error;
+}
+
+
 /* Attempts to set input rate limiting (policing) policy, such that up to
  * 'kbits_rate' kbps of traffic is accepted, with a maximum accumulative burst
  * size of 'kbits' kb. */
@@ -2376,6 +2391,18 @@  netdev_ports_flow_get(const struct dpif_class *dpif_class, struct match *match,
     return ENOENT;
 }
 
+void
+netdev_free_custom_stats_counters(struct netdev_custom_stats *custom_stats)
+{
+    if (custom_stats) {
+        if (custom_stats->counters) {
+            free(custom_stats->counters);
+            custom_stats->counters = NULL;
+            custom_stats->size = 0;
+        }
+    }
+}
+
 #ifdef __linux__
 static void
 netdev_ports_flow_init(void)
diff --git a/lib/netdev.h b/lib/netdev.h
index dc9d9a0..ff1b604 100644
--- a/lib/netdev.h
+++ b/lib/netdev.h
@@ -296,6 +296,8 @@  struct netdev *netdev_find_dev_by_in4(const struct in_addr *);
 
 /* Statistics. */
 int netdev_get_stats(const struct netdev *, struct netdev_stats *);
+int netdev_get_custom_stats(const struct netdev *,
+                            struct netdev_custom_stats *);
 
 /* Quality of service. */
 struct netdev_qos_capabilities {
diff --git a/lib/ofp-print.c b/lib/ofp-print.c
index 1376ef6..6c64cd2 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -1841,6 +1841,7 @@  ofp_print_ofpst_port_reply(struct ds *string, const struct ofp_header *oh,
                            const struct ofputil_port_map *port_map,
                            int verbosity)
 {
+    uint32_t i;
     ds_put_format(string, " %"PRIuSIZE" ports\n", ofputil_count_port_stats(oh));
     if (verbosity < 1) {
         return 0;
@@ -1950,6 +1951,23 @@  ofp_print_ofpst_port_reply(struct ds *string, const struct ofp_header *oh,
             ds_put_cstr(string, "\n");
             ds_destroy(&string_ext_stats);
         }
+
+        if (ps.custom_stats.size) {
+            ds_put_cstr(string, "           CUSTOM Statistics");
+            for (i = 0; i < ps.custom_stats.size; i++) {
+                /* 3 counters in the row */
+                if (ps.custom_stats.counters[i].name[0]) {
+                    if (i % 3 == 0) {
+                        ds_put_cstr(string, "\n");
+                        ds_put_cstr(string, "                      ");
+                    }
+                    ds_put_format(string, "%s=%"PRIu64", ",
+                                  ps.custom_stats.counters[i].name,
+                                  ps.custom_stats.counters[i].value);
+                }
+            }
+            ds_put_cstr(string, "\n");
+        }
     }
 }
 
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index 47f30c7..597112e 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -7999,15 +7999,18 @@  ofputil_append_ofp14_port_stats(const struct ofputil_port_stats *ops,
 {
     struct ofp14_port_stats_prop_ethernet *eth;
     struct intel_port_stats_rfc2819 *stats_rfc2819;
+    struct intel_port_custom_stats *stats_custom;
     struct ofp14_port_stats *ps14;
     struct ofpbuf *reply;
+    uint16_t i;
+    ovs_be64 counter_value;
+    size_t custom_stats_start, start_ofs;
 
-    reply = ofpmp_reserve(replies, sizeof *ps14 + sizeof *eth +
-                          sizeof *stats_rfc2819);
+    reply = ofpbuf_from_list(ovs_list_back(replies));
+    start_ofs = reply->size;
 
     ps14 = ofpbuf_put_uninit(reply, sizeof *ps14);
-    ps14->length = htons(sizeof *ps14 + sizeof *eth +
-                         sizeof *stats_rfc2819);
+
     memset(ps14->pad, 0, sizeof ps14->pad);
     ps14->port_no = ofputil_port_to_ofp11(ops->port_no);
     ps14->duration_sec = htonl(ops->duration_sec);
@@ -8027,10 +8030,10 @@  ofputil_append_ofp14_port_stats(const struct ofputil_port_stats *ops,
     eth->rx_crc_err = htonll(ops->stats.rx_crc_errors);
     eth->collisions = htonll(ops->stats.collisions);
 
-    uint64_t prop_type = OFPPROP_EXP(INTEL_VENDOR_ID,
+    uint64_t prop_type_stats = OFPPROP_EXP(INTEL_VENDOR_ID,
                                      INTEL_PORT_STATS_RFC2819);
 
-    stats_rfc2819 = ofpprop_put_zeros(reply, prop_type,
+    stats_rfc2819 = ofpprop_put_zeros(reply, prop_type_stats,
                                       sizeof *stats_rfc2819);
 
     memset(stats_rfc2819->pad, 0, sizeof stats_rfc2819->pad);
@@ -8076,6 +8079,38 @@  ofputil_append_ofp14_port_stats(const struct ofputil_port_stats *ops,
         htonll(ops->stats.rx_fragmented_errors);
     stats_rfc2819->rx_jabber_errors =
         htonll(ops->stats.rx_jabber_errors);
+
+    if (ops->custom_stats.counters && ops->custom_stats.size) {
+        custom_stats_start = reply->size;
+
+        uint64_t prop_type_custom = OFPPROP_EXP(INTEL_VENDOR_ID,
+                                                INTEL_PORT_STATS_CUSTOM);
+
+        stats_custom = ofpprop_put_zeros(reply, prop_type_custom,
+                                         sizeof *stats_custom);
+
+        stats_custom->stats_array_size = htons(ops->custom_stats.size);
+
+        for (i = 0; i < ops->custom_stats.size; i++) {
+            uint8_t counter_size = strlen(ops->custom_stats.counters[i].name);
+            /* Counter name size */
+            ofpbuf_put(reply, &counter_size, sizeof(counter_size));
+            /* Counter name */
+            ofpbuf_put(reply, ops->custom_stats.counters[i].name,
+                       counter_size);
+            /* Counter value */
+            counter_value = htonll(ops->custom_stats.counters[i].value);
+            ofpbuf_put(reply, &counter_value,
+                       sizeof(ops->custom_stats.counters[i].value));
+        }
+
+        ofpprop_end(reply, custom_stats_start);
+    }
+
+    ps14 = ofpbuf_at_assert(reply, start_ofs, sizeof *ps14);
+    ps14->length = htons(reply->size - start_ofs);
+
+    ofpmp_postappend(replies, start_ofs);
 }
 
 /* Encode a ports stat for 'ops' and append it to 'replies'. */
@@ -8239,6 +8274,56 @@  parse_intel_port_stats_rfc2819_property(const struct ofpbuf *payload,
 }
 
 static enum ofperr
+parse_intel_port_custom_property(const struct ofpbuf *payload,
+                                 struct ofputil_port_stats *ops)
+{
+    const struct intel_port_custom_stats *custom_stats = payload->data;
+
+    ops->custom_stats.size = ntohs(custom_stats->stats_array_size);
+
+    ops->custom_stats.counters = xcalloc(ops->custom_stats.size,
+                                         sizeof *ops->custom_stats.counters);
+
+    uint16_t msg_size = ntohs(custom_stats->length);
+    uint16_t current_len = sizeof *custom_stats;
+    uint8_t *current = (uint8_t *)payload->data + current_len;
+    uint8_t string_size = 0;
+    uint8_t value_size = 0;
+    ovs_be64 counter_value = 0;
+
+    for (int i = 0; i < ops->custom_stats.size; i++) {
+        current_len += string_size + value_size;
+        current += string_size + value_size;
+
+        value_size = sizeof(uint64_t);
+        /* Counter name size */
+        string_size = *current;
+
+        /* Buffer overrun check */
+        if (current_len + string_size + value_size > msg_size) {
+            VLOG_WARN_RL(&bad_ofmsg_rl, "Custom statistics buffer overrun! "
+                         "Further message parsing is aborted.");
+            break;
+        }
+
+        current++;
+        current_len++;
+
+        /* Counter name. */
+        struct netdev_custom_counter *c = &ops->custom_stats.counters[i];
+        size_t len = MIN(string_size, sizeof c->name - 1);
+        memcpy(c->name, current, len);
+        c->name[len] = '\0';
+        memcpy(&counter_value, current + string_size, value_size);
+
+        /* Counter value. */
+        c->value = ntohll(counter_value);
+    }
+
+    return 0;
+}
+
+static enum ofperr
 parse_intel_port_stats_property(const struct ofpbuf *payload,
                                 uint32_t exp_type,
                                 struct ofputil_port_stats *ops)
@@ -8249,6 +8334,9 @@  parse_intel_port_stats_property(const struct ofpbuf *payload,
     case INTEL_PORT_STATS_RFC2819:
         error = parse_intel_port_stats_rfc2819_property(payload, ops);
         break;
+    case INTEL_PORT_STATS_CUSTOM:
+        error = parse_intel_port_custom_property(payload, ops);
+        break;
     default:
         error = OFPERR_OFPBPC_BAD_EXP_TYPE;
         break;
@@ -8308,6 +8396,11 @@  ofputil_pull_ofp14_port_stats(struct ofputil_port_stats *ops,
                                                     INTEL_PORT_STATS_RFC2819,
                                                     ops);
             break;
+        case OFPPROP_EXP(INTEL_VENDOR_ID, INTEL_PORT_STATS_CUSTOM):
+            error = parse_intel_port_stats_property(&payload,
+                                                    INTEL_PORT_STATS_CUSTOM,
+                                                    ops);
+            break;
         default:
             error = OFPPROP_UNKNOWN(true, "port stats", type);
             break;
@@ -8354,6 +8447,7 @@  ofputil_decode_port_stats(struct ofputil_port_stats *ps, struct ofpbuf *msg)
     enum ofpraw raw;
 
     memset(&(ps->stats), 0xFF, sizeof (ps->stats));
+    memset(&(ps->custom_stats), 0, sizeof (ps->custom_stats));
 
     error = (msg->header ? ofpraw_decode(&raw, msg->header)
              : ofpraw_pull(&raw, msg));
diff --git a/lib/util.c b/lib/util.c
index 2965656..a4d22df 100644
--- a/lib/util.c
+++ b/lib/util.c
@@ -321,6 +321,19 @@  ovs_strzcpy(char *dst, const char *src, size_t size)
     }
 }
 
+/*
+ * Returns true if 'str' ends with given 'suffix'.
+ */
+int
+string_ends_with(const char *str, const char *suffix)
+{
+    int str_len = strlen(str);
+    int suffix_len = strlen(suffix);
+
+    return (str_len >= suffix_len) &&
+           (0 == strcmp(str + (str_len - suffix_len), suffix));
+}
+
 /* Prints 'format' on stderr, formatting it like printf() does.  If 'err_no' is
  * nonzero, then it is formatted with ovs_retval_to_string() and appended to
  * the message inside parentheses.  Then, terminates with abort().
diff --git a/lib/util.h b/lib/util.h
index 1792356..b6639b8 100644
--- a/lib/util.h
+++ b/lib/util.h
@@ -159,6 +159,8 @@  void free_cacheline(void *);
 void ovs_strlcpy(char *dst, const char *src, size_t size);
 void ovs_strzcpy(char *dst, const char *src, size_t size);
 
+int string_ends_with(const char *str, const char *suffix);
+
 /* The C standards say that neither the 'dst' nor 'src' argument to
  * memcpy() may be null, even if 'n' is zero.  This wrapper tolerates
  * the null case. */
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index 84eb18e..55bf4b5 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -3887,8 +3887,11 @@  append_port_stat(struct ofport *port, struct ovs_list *replies)
      * 'stats' to all-1s, which is correct for OpenFlow, and
      * netdev_get_stats() will log errors. */
     ofproto_port_get_stats(port, &ops.stats);
+    netdev_get_custom_stats(port->netdev, &ops.custom_stats);
 
     ofputil_append_port_stat(replies, &ops);
+
+    netdev_free_custom_stats_counters(&ops.custom_stats);
 }
 
 static void
diff --git a/tests/ofproto.at b/tests/ofproto.at
index 74a30cb..8d18a97 100644
--- a/tests/ofproto.at
+++ b/tests/ofproto.at
@@ -105,12 +105,14 @@  AT_CLEANUP
 AT_SETUP([ofproto - port stats - (OpenFlow 1.4)])
 OVS_VSWITCHD_START
 AT_CHECK([ovs-ofctl -O OpenFlow14 -vwarn dump-ports br0], [0], [stdout])
-AT_CHECK([strip_xids < stdout | sed 's/duration=[[0-9.]]*s/duration=?s/'],
+AT_CHECK_UNQUOTED([strip_xids < stdout | sed 's/duration=[[0-9.]]*s/duration=?s/'],
   [0], [dnl
-OFPST_PORT reply (OF1.4): 1 ports
+$(echo 'OFPST_PORT reply (OF1.4): 1 ports
   port LOCAL: rx pkts=0, bytes=0, drop=?, errs=?, frame=?, over=?, crc=?
            tx pkts=0, bytes=0, drop=?, errs=?, coll=?
            duration=?s
+           CUSTOM Statistics
+                      rx_custom_packets_1=0, rx_custom_packets_2=0, ')
 ])
 OVS_VSWITCHD_STOP
 AT_CLEANUP
diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c
index 27ee506..28c96c1 100644
--- a/vswitchd/bridge.c
+++ b/vswitchd/bridge.c
@@ -2347,6 +2347,11 @@  iface_refresh_cfm_stats(struct iface *iface)
 static void
 iface_refresh_stats(struct iface *iface)
 {
+    struct netdev_custom_stats custom_stats;
+    struct netdev_stats stats;
+    int n;
+    uint32_t i, counters_size;
+
 #define IFACE_STATS                             \
     IFACE_STAT(rx_packets,              "rx_packets")               \
     IFACE_STAT(tx_packets,              "tx_packets")               \
@@ -2385,16 +2390,22 @@  iface_refresh_stats(struct iface *iface)
 #define IFACE_STAT(MEMBER, NAME) + 1
     enum { N_IFACE_STATS = IFACE_STATS };
 #undef IFACE_STAT
-    int64_t values[N_IFACE_STATS];
-    const char *keys[N_IFACE_STATS];
-    int n;
-
-    struct netdev_stats stats;
 
     if (iface_is_synthetic(iface)) {
         return;
     }
 
+    netdev_get_custom_stats(iface->netdev, &custom_stats);
+
+    counters_size = custom_stats.size + N_IFACE_STATS;
+    int64_t *values = xmalloc(counters_size * sizeof(int64_t));
+    const char **keys = xmalloc(counters_size * sizeof(char *));
+
+    if ((keys == NULL) || (values == NULL)) {
+        VLOG_ERR("Can't allocate memory to get statistics!");
+        return;
+    }
+
     /* Intentionally ignore return value, since errors will set 'stats' to
      * all-1s, and we will deal with that correctly below. */
     netdev_get_stats(iface->netdev, &stats);
@@ -2409,10 +2420,23 @@  iface_refresh_stats(struct iface *iface)
     }
     IFACE_STATS;
 #undef IFACE_STAT
-    ovs_assert(n <= N_IFACE_STATS);
+
+    /* Copy custom statistics into keys[] and values[]. */
+    if (custom_stats.size && custom_stats.counters) {
+        for (i = 0 ; i < custom_stats.size ; i++) {
+            values[n] = custom_stats.counters[i].value;
+            keys[n] = custom_stats.counters[i].name;
+            n++;
+        }
+    }
+
+    ovs_assert(n <= counters_size);
 
     ovsrec_interface_set_statistics(iface->cfg, keys, values, n);
 #undef IFACE_STATS
+
+    free(values);
+    free(keys);
 }
 
 static void