From patchwork Tue Jan 9 07:55:37 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Weglicki, MichalX" X-Patchwork-Id: 857283 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=openvswitch.org (client-ip=140.211.169.12; helo=mail.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Received: from mail.linuxfoundation.org (mail.linuxfoundation.org [140.211.169.12]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3zG4cX6jzLz9s7v for ; Tue, 9 Jan 2018 19:12:12 +1100 (AEDT) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id 69174F34; Tue, 9 Jan 2018 08:12:09 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@mail.linuxfoundation.org Received: from smtp1.linuxfoundation.org (smtp1.linux-foundation.org [172.17.192.35]) by mail.linuxfoundation.org (Postfix) with ESMTPS id AA8E2F01 for ; Tue, 9 Jan 2018 08:12:07 +0000 (UTC) X-Greylist: domain auto-whitelisted by SQLgrey-1.7.6 Received: from mga07.intel.com (mga07.intel.com [134.134.136.100]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 7C098A3 for ; Tue, 9 Jan 2018 08:12:05 +0000 (UTC) X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from orsmga007.jf.intel.com ([10.7.209.58]) by orsmga105.jf.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 09 Jan 2018 00:12:04 -0800 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.46,334,1511856000"; d="scan'208";a="8954370" Received: from irvmail001.ir.intel.com ([163.33.26.43]) by orsmga007.jf.intel.com with ESMTP; 09 Jan 2018 00:12:03 -0800 Received: from silpixa00389819.ir.intel.com (silpixa00389819.ir.intel.com [10.237.219.152]) by irvmail001.ir.intel.com (8.14.3/8.13.6/MailSET/Hub) with ESMTP id w098C22t013356; Tue, 9 Jan 2018 08:12:02 GMT From: Michal Weglicki To: dev@openvswitch.org Date: Tue, 9 Jan 2018 07:55:37 +0000 Message-Id: <1515484537-45766-1-git-send-email-michalx.weglicki@intel.com> X-Mailer: git-send-email 1.8.3.1 X-Spam-Status: No, score=-4.2 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_MED, T_RP_MATCHES_RCVD autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on smtp1.linux-foundation.org Subject: [ovs-dev] [PATCH v4] netdev: Custom statistics. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: ovs-dev-bounces@openvswitch.org Errors-To: ovs-dev-bounces@openvswitch.org - 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 Signed-off-by: Ben Pfaff Signed-off-by: Michal Weglicki --- 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(-) 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: +`__. 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