summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichal Weglicki <michalx.weglicki@intel.com>2018-01-09 07:55:37 +0000
committerBen Pfaff <blp@ovn.org>2018-01-10 15:29:13 -0800
commit971f4b394c6e8480300494787fe919869ff3886c (patch)
tree8dfa01877f05fbc46ebcc85d3c62f3c06e820ad0
parentcd32509e4af4f9f7a002a6a5c137718f2173c538 (diff)
downloadopenvswitch-971f4b394c6e8480300494787fe919869ff3886c.tar.gz
netdev: Custom statistics.
- 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. 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> Signed-off-by: Ben Pfaff <blp@ovn.org>
-rw-r--r--Documentation/howto/dpdk.rst15
-rw-r--r--NEWS5
-rw-r--r--include/openflow/intel-ext.h28
-rw-r--r--include/openvswitch/netdev.h17
-rw-r--r--include/openvswitch/ofp-util.h1
-rw-r--r--lib/netdev-dpdk.c193
-rw-r--r--lib/netdev-dummy.c36
-rw-r--r--lib/netdev-linux.c1
-rw-r--r--lib/netdev-provider.h13
-rw-r--r--lib/netdev-vport.c1
-rw-r--r--lib/netdev.c27
-rw-r--r--lib/netdev.h2
-rw-r--r--lib/ofp-print.c20
-rw-r--r--lib/ofp-util.c106
-rw-r--r--lib/util.c13
-rw-r--r--lib/util.h2
-rw-r--r--ofproto/ofproto.c3
-rw-r--r--tests/ofproto.at2
-rw-r--r--vswitchd/bridge.c31
19 files changed, 498 insertions, 18 deletions
diff --git a/Documentation/howto/dpdk.rst b/Documentation/howto/dpdk.rst
index 2393c2ff7..587aaed5b 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 fd0e2d84e..8caf6bb56 100644
--- a/NEWS
+++ b/NEWS
@@ -32,6 +32,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.
* Configuring a controller, or unconfiguring all controllers, now deletes
diff --git a/include/openflow/intel-ext.h b/include/openflow/intel-ext.h
index 974e63e0d..3d7317147 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 50bafc3a6..e25c241f4 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 a9e57ed58..296078a2f 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 364f545c4..e32c7f678 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 40086a377..57da19c48 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 ccb6def6c..37143b8df 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 3d9f3365d..25bd671c1 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 f72025e5b..478ed90c4 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 b1b6da4a5..be05dc640 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 dc9d9a0f5..ff1b604b2 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 1376ef687..8a1f426ad 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,25 @@ 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, " ");
+ } else {
+ ds_put_char(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 47f30c771..597112e4f 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 296565631..a4d22df0c 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 1792356e5..b6639b8b8 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 139fcf96e..d42acd747 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -3890,8 +3890,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 540928f1d..ec1dc5161 100644
--- a/tests/ofproto.at
+++ b/tests/ofproto.at
@@ -111,6 +111,8 @@ 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 27ee50646..d80da1cdd 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,17 @@ 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 *));
+
/* 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 +2415,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