This patch adds a new API that allows offload providers to expose free-form debug information. The information can be used for troubleshooting the offload providers state.
The new API is integrated into the existing 'ovs-appctl dpif/offload/show' command, which now displays this debug output when available. Support for this API has been implemented for all currently supported offload providers. Signed-off-by: Eelco Chaudron <echau...@redhat.com> --- include/openvswitch/json.h | 1 + lib/dpif-offload-dummy.c | 56 +++++++++++++++++++ lib/dpif-offload-provider.h | 9 +++ lib/dpif-offload-rte_flow.c | 53 ++++++++++++++++++ lib/dpif-offload-tc.c | 55 ++++++++++++++++++ lib/dpif-offload.c | 11 ++++ lib/dpif-offload.h | 2 + lib/json.c | 7 +++ ofproto/ofproto-dpif.c | 18 +++++- tests/ofproto-dpif.at | 96 ++++++++++++++++++++++++++++++-- tests/system-dpdk.at | 13 ++++- tests/system-offloads-traffic.at | 29 ++++++++-- 12 files changed, 335 insertions(+), 15 deletions(-) diff --git a/include/openvswitch/json.h b/include/openvswitch/json.h index 134890553..ea2c4da04 100644 --- a/include/openvswitch/json.h +++ b/include/openvswitch/json.h @@ -123,6 +123,7 @@ struct json *json_array_create_3(struct json *, struct json *, struct json *); bool json_array_contains_string(const struct json *, const char *); struct json *json_object_create(void); +bool json_object_is_empty(struct json *); void json_object_put(struct json *, const char *name, struct json *value); void json_object_put_nocopy(struct json *, char *name, struct json *value); void json_object_put_string(struct json *, diff --git a/lib/dpif-offload-dummy.c b/lib/dpif-offload-dummy.c index d4997891c..1bdb9d043 100644 --- a/lib/dpif-offload-dummy.c +++ b/lib/dpif-offload-dummy.c @@ -24,6 +24,8 @@ #include "netdev-provider.h" #include "util.h" +#include "openvswitch/json.h" + struct dpif_offload_dummy { struct dpif_offload offload; @@ -171,6 +173,59 @@ dpif_offload_dummy_set_config(struct dpif_offload *dpif_offload, } } +static bool +dpif_offload_dummy_get_port_debug_ds(struct dpif_offload_port_mgr_port *port, + void *aux) +{ + struct ds *ds = aux; + + ds_put_format(ds, " - %s: port_no: %u\n", netdev_get_name(port->netdev), + port->port_no); + + return false; +} + +static bool +dpif_offload_dummy_get_port_debug_json(struct dpif_offload_port_mgr_port *port, + void *aux) +{ + struct json *json_port = json_object_create(); + struct json *json = aux; + + json_object_put(json_port, "port_no", + json_integer_create(odp_to_u32(port->port_no))); + + json_object_put(json, netdev_get_name(port->netdev), json_port); + return false; +} + +static void +dpif_offload_dummy_get_debug(const struct dpif_offload *offload, struct ds *ds, + struct json *json) +{ + struct dpif_offload_dummy *offload_dummy; + + offload_dummy = dpif_offload_dummy_cast(offload); + + if (json) { + struct json *json_ports = json_object_create(); + + dpif_offload_port_mgr_traverse_ports( + offload_dummy->port_mgr, dpif_offload_dummy_get_port_debug_json, + json_ports); + + if (!json_object_is_empty(json_ports)) { + json_object_put(json, "ports", json_ports); + } else { + json_destroy(json_ports); + } + + } else if (ds) { + dpif_offload_port_mgr_traverse_ports( + offload_dummy->port_mgr, dpif_offload_dummy_get_port_debug_ds, ds); + } +} + static bool dpif_offload_dummy_can_offload(struct dpif_offload *dpif_offload OVS_UNUSED, struct netdev *netdev) @@ -187,6 +242,7 @@ dpif_offload_dummy_can_offload(struct dpif_offload *dpif_offload OVS_UNUSED, .open = dpif_offload_dummy_open, \ .close = dpif_offload_dummy_close, \ .set_config = dpif_offload_dummy_set_config, \ + .get_debug = dpif_offload_dummy_get_debug, \ .can_offload = dpif_offload_dummy_can_offload, \ .port_add = dpif_offload_dummy_port_add, \ .port_del = dpif_offload_dummy_port_del, \ diff --git a/lib/dpif-offload-provider.h b/lib/dpif-offload-provider.h index ce309a672..4dfdbeeae 100644 --- a/lib/dpif-offload-provider.h +++ b/lib/dpif-offload-provider.h @@ -93,6 +93,15 @@ struct dpif_offload_class { void (*set_config)(struct dpif_offload *, const struct smap *other_config); + /* Retrieve debug information from the offload provider in either string + * (ds) or JSON format. If both formats are requested, the provider may + * choose which one to return. Note that the actual format is unspecified, + * it's up to the provider to decide what to return. If 'ds' is supplied, + * it should be initialized, and might already contain data. The caller is + * responsible for freeing any returned 'ds' or 'json' pointers. */ + void (*get_debug)(const struct dpif_offload *offload, struct ds *ds, + struct json *json); + /* Verifies whether the offload provider supports offloading flows for the * given 'netdev'. Returns 'false' if the provider lacks the capabilities * to offload on this port, otherwise returns 'true'. */ diff --git a/lib/dpif-offload-rte_flow.c b/lib/dpif-offload-rte_flow.c index 01a7a0730..e4c44c57a 100644 --- a/lib/dpif-offload-rte_flow.c +++ b/lib/dpif-offload-rte_flow.c @@ -23,6 +23,7 @@ #include "netdev-vport.h" #include "util.h" +#include "openvswitch/json.h" #include "openvswitch/vlog.h" VLOG_DEFINE_THIS_MODULE(dpif_offload_rte_flow); @@ -187,6 +188,57 @@ dpif_offload_rte_set_config(struct dpif_offload *offload, } } +static bool +dpif_offload_rte_get_port_debug_ds(struct dpif_offload_port_mgr_port *port, + void *aux) +{ + struct ds *ds = aux; + + ds_put_format(ds, " - %s: port_no: %u\n", + netdev_get_name(port->netdev), port->port_no); + + return false; +} + +static bool +dpif_offload_rte_get_port_debug_json(struct dpif_offload_port_mgr_port *port, + void *aux) +{ + struct json *json_port = json_object_create(); + struct json *json = aux; + + json_object_put(json_port, "port_no", + json_integer_create(odp_to_u32(port->port_no))); + + json_object_put(json, netdev_get_name(port->netdev), json_port); + return false; +} + +static void +dpif_offload_rte_get_debug(const struct dpif_offload *offload, struct ds *ds, + struct json *json) +{ + struct dpif_offload_rte_flow *offload_rte = dpif_offload_rte_cast(offload); + + if (json) { + struct json *json_ports = json_object_create(); + + dpif_offload_port_mgr_traverse_ports( + offload_rte->port_mgr, dpif_offload_rte_get_port_debug_json, + json_ports); + + if (!json_object_is_empty(json_ports)) { + json_object_put(json, "ports", json_ports); + } else { + json_destroy(json_ports); + } + + } else if (ds) { + dpif_offload_port_mgr_traverse_ports( + offload_rte->port_mgr, dpif_offload_rte_get_port_debug_ds, ds); + } +} + static bool dpif_offload_rte_can_offload(struct dpif_offload *dpif_offload OVS_UNUSED, struct netdev *netdev) @@ -209,6 +261,7 @@ struct dpif_offload_class dpif_offload_rte_flow_class = { .open = dpif_offload_rte_open, .close = dpif_offload_rte_close, .set_config = dpif_offload_rte_set_config, + .get_debug = dpif_offload_rte_get_debug, .can_offload = dpif_offload_rte_can_offload, .port_add = dpif_offload_rte_port_add, .port_del = dpif_offload_rte_port_del, diff --git a/lib/dpif-offload-tc.c b/lib/dpif-offload-tc.c index c3090829b..66d52851a 100644 --- a/lib/dpif-offload-tc.c +++ b/lib/dpif-offload-tc.c @@ -24,6 +24,7 @@ #include "util.h" #include "tc.h" +#include "openvswitch/json.h" #include "openvswitch/vlog.h" VLOG_DEFINE_THIS_MODULE(dpif_offload_tc); @@ -169,6 +170,59 @@ dpif_offload_tc_set_config(struct dpif_offload *offload, } } +static bool +dpif_offload_tc_get_port_debug_ds(struct dpif_offload_port_mgr_port *port, + void *aux) +{ + struct ds *ds = aux; + + ds_put_format(ds, " - %s: port_no: %u, ifindex: %d\n", + netdev_get_name(port->netdev), port->port_no, port->ifindex); + + return false; +} + +static bool +dpif_offload_tc_get_port_debug_json(struct dpif_offload_port_mgr_port *port, + void *aux) +{ + struct json *json_port = json_object_create(); + struct json *json = aux; + + json_object_put(json_port, "port_no", + json_integer_create(odp_to_u32(port->port_no))); + json_object_put(json_port, "ifindex", json_integer_create(port->ifindex)); + + json_object_put(json, netdev_get_name(port->netdev), json_port); + return false; +} + +static void +dpif_offload_tc_get_debug(const struct dpif_offload *offload, struct ds *ds, + struct json *json) +{ + struct dpif_offload_tc *offload_tc = dpif_offload_tc_cast(offload); + + if (json) { + struct json *json_ports = json_object_create(); + + dpif_offload_port_mgr_traverse_ports( + offload_tc->port_mgr, dpif_offload_tc_get_port_debug_json, + json_ports); + + if (!json_object_is_empty(json_ports)) { + json_object_put(json, "ports", json_ports); + } else { + json_destroy(json_ports); + } + + } else if (ds) { + dpif_offload_port_mgr_traverse_ports(offload_tc->port_mgr, + dpif_offload_tc_get_port_debug_ds, + ds); + } +} + static bool dpif_offload_tc_can_offload(struct dpif_offload *dpif_offload OVS_UNUSED, struct netdev *netdev) @@ -190,6 +244,7 @@ struct dpif_offload_class dpif_offload_tc_class = { .open = dpif_offload_tc_open, .close = dpif_offload_tc_close, .set_config = dpif_offload_tc_set_config, + .get_debug = dpif_offload_tc_get_debug, .can_offload = dpif_offload_tc_can_offload, .port_add = dpif_offload_tc_port_add, .port_del = dpif_offload_tc_port_del, diff --git a/lib/dpif-offload.c b/lib/dpif-offload.c index 98bce266a..f18754f60 100644 --- a/lib/dpif-offload.c +++ b/lib/dpif-offload.c @@ -396,6 +396,17 @@ dpif_offload_class_type(const struct dpif_offload *offload) return offload->class->type; } +bool dpif_offload_get_debug(const struct dpif_offload *offload, + struct ds *ds, struct json *json) +{ + if (!offload->class->get_debug) { + return false; + } + + offload->class->get_debug(offload, ds, json); + return true; +} + bool dpif_offload_is_offload_enabled(void) { diff --git a/lib/dpif-offload.h b/lib/dpif-offload.h index 2720d2b5f..dcca10b4b 100644 --- a/lib/dpif-offload.h +++ b/lib/dpif-offload.h @@ -44,6 +44,8 @@ int dpif_offload_attach_providers(struct dpif *); void dpif_offload_detach_providers(struct dpif *); const char *dpif_offload_name(const struct dpif_offload *); const char *dpif_offload_class_type(const struct dpif_offload *); +bool dpif_offload_get_debug(const struct dpif_offload *, struct ds *, + struct json *); void dpif_offload_dump_start(struct dpif_offload_dump *, const struct dpif *); bool dpif_offload_dump_next(struct dpif_offload_dump *, struct dpif_offload **); diff --git a/lib/json.c b/lib/json.c index 23622ab36..23000f195 100644 --- a/lib/json.c +++ b/lib/json.c @@ -397,6 +397,13 @@ json_object_create(void) return json; } +bool +json_object_is_empty(struct json *json) +{ + return json && json->type == JSON_OBJECT + && shash_is_empty(json->object); +} + struct json * json_integer_create(long long int integer) { diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c index 29d6fa59d..62528fcc6 100644 --- a/ofproto/ofproto-dpif.c +++ b/ofproto/ofproto-dpif.c @@ -6713,6 +6713,7 @@ dpif_offload_show_backer_text(const struct dpif_backer *backer, struct ds *ds) DPIF_OFFLOAD_FOR_EACH (offload, &dump, backer->dpif) { ds_put_format(ds, " %s\n", dpif_offload_class_type(offload)); + dpif_offload_get_debug(offload, ds, NULL); } } @@ -6727,15 +6728,26 @@ dpif_offload_show_backer_json(struct json *backers, /* Add datapath as new JSON object using its name as key. */ json_object_put(backers, dpif_name(backer->dpif), json_backer); - /* Add provider to "providers" array using its name as key. */ - struct json *json_providers = json_array_create_empty(); + /* Add provider to "providers" object using its name as key. */ + struct json *json_providers = json_object_create(); + + /* Add provider to "priority" array using its name as key. */ + struct json *json_priority = json_array_create_empty(); /* Add offload provides as new JSON objects using its type as key. */ DPIF_OFFLOAD_FOR_EACH (offload, &dump, backer->dpif) { - json_array_add(json_providers, + struct json *debug_data = json_object_create(); + + json_array_add(json_priority, json_string_create(dpif_offload_class_type(offload))); + + dpif_offload_get_debug(offload, NULL, debug_data); + + json_object_put(json_providers, dpif_offload_class_type(offload), + debug_data); } + json_object_put(json_backer, "priority", json_priority); json_object_put(json_backer, "providers", json_providers); return json_backer; } diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at index 8711295be..775b7b823 100644 --- a/tests/ofproto-dpif.at +++ b/tests/ofproto-dpif.at @@ -20,6 +20,28 @@ check_dpflow_stats () { echo "n_flows=$flows n_buckets=$buckets" } +sort_dpif_offload_show () { + awk ' + /^ -/ { dashlines[[++n]] = $0; next } + { print } + END { + # asort(dashlines) is a GNU extension, so we need to do it + # manually here as ofproto-dpif is also executed on FreeBSD. + for (i = 1; i <= n; i++) { + for (j = i + 1; j <= n; j++) { + if (dashlines[[i]] > dashlines[[j]]) { + tmp = dashlines[[i]] + dashlines[[i]] = dashlines[[j]] + dashlines[[j]] = tmp + } + } + } + + for (i=1; i<=n; i++) print dashlines[[i]] + } + ' +} + m4_divert_pop([PREPARE_TESTS]) @@ -10075,20 +10097,31 @@ AT_SETUP([ofproto-dpif - offload - ovs-appctl dpif/offload/]) AT_KEYWORDS([dpif-offload]) OVS_VSWITCHD_START([add-br br1 -- set bridge br1 datapath-type=dummy]) -AT_CHECK([ovs-appctl dpif/offload/show], [0], [dnl +AT_CHECK([ovs-appctl dpif/offload/show | sort_dpif_offload_show], [0], [dnl Globally enabled: false Datapaths: dummy@ovs-dummy: dummy dummy_x + - br0: port_no: 100 + - br1: port_no: 101 ]) AT_CHECK([ovs-appctl --format json --pretty dpif/offload/show], [0], [dnl { "dummy@ovs-dummy": { - "providers": [[ + "priority": [[ "dummy", - "dummy_x"]]}, + "dummy_x"]], + "providers": { + "dummy": { + "ports": { + "br0": { + "port_no": 100}, + "br1": { + "port_no": 101}}}, + "dummy_x": { + }}}, "enabled": false} ]) @@ -10113,26 +10146,77 @@ AT_KEYWORDS([dpif-offload]) OVS_VSWITCHD_START([add-br br1 -- set bridge br1 datapath-type=dummy], [], [], [], [-- set Open_vSwitch . other_config:hw-offload-priority=dummy_x,dummy]) -AT_CHECK([ovs-appctl dpif/offload/show], [0], [dnl +AT_CHECK([ovs-appctl dpif/offload/show | sort_dpif_offload_show], [0], [dnl Globally enabled: false Datapaths: dummy@ovs-dummy: dummy_x dummy + - br0: port_no: 100 + - br1: port_no: 101 ]) AT_CHECK([ovs-appctl --format json --pretty dpif/offload/show], [0], [dnl { "dummy@ovs-dummy": { - "providers": [[ + "priority": [[ "dummy_x", - "dummy"]]}, + "dummy"]], + "providers": { + "dummy": { + }, + "dummy_x": { + "ports": { + "br0": { + "port_no": 100}, + "br1": { + "port_no": 101}}}}}, "enabled": false} ]) OVS_TRAFFIC_VSWITCHD_STOP AT_CLEANUP +AT_SETUP([ofproto-dpif - offload - port priority order]) +AT_KEYWORDS([dpif-offload]) +OVS_VSWITCHD_START([add-port br0 p1 -- \ + set Interface p1 type=dummy ofport_request=1 -- \ + set port p1 other_config:hw-offload-priority=dummy_x,dummy -- \ + add-port br0 p2 -- \ + set Interface p2 type=dummy ofport_request=2 -- \ + set port p2 other_config:hw-offload-priority=none -- \ + add-port br0 p3 -- \ + set Interface p3 type=dummy ofport_request=3 -- \ + set port p3 other_config:hw-offload-priority=dummy_x -- \ + add-port br0 p4 -- \ + set Interface p4 type=dummy ofport_request=4 -- \ + set port p4 other_config:hw-offload-priority=dummy]) + +AT_CHECK([ovs-appctl --format json --pretty dpif/offload/show], [0], [dnl +{ + "dummy@ovs-dummy": { + "priority": [[ + "dummy", + "dummy_x"]], + "providers": { + "dummy": { + "ports": { + "br0": { + "port_no": 100}, + "p4": { + "port_no": 4}}}, + "dummy_x": { + "ports": { + "p1": { + "port_no": 1}, + "p3": { + "port_no": 3}}}}}, + "enabled": false} +]) + +OVS_VSWITCHD_STOP +AT_CLEANUP + dnl ---------------------------------------------------------------------- AT_BANNER([ofproto-dpif -- megaflows]) diff --git a/tests/system-dpdk.at b/tests/system-dpdk.at index 336cfe696..9985da6be 100644 --- a/tests/system-dpdk.at +++ b/tests/system-dpdk.at @@ -876,20 +876,29 @@ AT_KEYWORDS([dpdk dpif-offload]) OVS_DPDK_PRE_CHECK() OVS_DPDK_START([--no-pci]) AT_CHECK([ovs-vsctl add-br br0 -- set bridge br0 datapath_type=netdev]) +AT_CHECK([ovs-vsctl add-port br0 p1 \ + -- set Interface p1 type=dpdk options:dpdk-devargs=net_null0,no-rx=1], + [], [stdout], [stderr]) AT_CHECK([ovs-appctl dpif/offload/show], [0], [dnl Globally enabled: false Datapaths: netdev@ovs-netdev: rte_flow + - p1: port_no: 2 ]) AT_CHECK([ovs-appctl --format json --pretty dpif/offload/show], [0], [dnl { "enabled": false, "netdev@ovs-netdev": { - "providers": [[ - "rte_flow"]]}} + "priority": [[ + "rte_flow"]], + "providers": { + "rte_flow": { + "ports": { + "p1": { + "port_no": 2}}}}}} ]) OVS_DPDK_STOP_VSWITCHD diff --git a/tests/system-offloads-traffic.at b/tests/system-offloads-traffic.at index 471c8fdb8..98403c449 100644 --- a/tests/system-offloads-traffic.at +++ b/tests/system-offloads-traffic.at @@ -1172,19 +1172,40 @@ AT_KEYWORDS([dpif-offload]) OVS_TRAFFIC_VSWITCHD_START([], [], [-- set Open_vSwitch . other_config:hw-offload=true]) -AT_CHECK([ovs-appctl dpif/offload/show], [0], [dnl +sort_dpif_offload_show () { + dnl Note: We do not use an m4 macro as it does not like the $0, or escaped + dnl variants and loops until it runs out of memory. + awk ' + /^ -/ { dashlines[[++n]] = $0; next } + { print } + END { + asort(dashlines) + for (i=1; i<=n; i++) print dashlines[[i]] + } + ' | sed -E 's/ifindex: [[0-9]]+/ifindex: 0/g' +} + +AT_CHECK([ovs-appctl dpif/offload/show | sort_dpif_offload_show], [0], [dnl Globally enabled: true Datapaths: system@ovs-system: tc + - br0: port_no: 1, ifindex: 0 ]) -AT_CHECK([ovs-appctl --format json --pretty dpif/offload/show], [0], [dnl +AT_CHECK([ovs-appctl --format json --pretty dpif/offload/show \ + | sed -E 's/"ifindex": [[0-9]]+/"ifindex": 0/g'], [0], [dnl { "enabled": true, "system@ovs-system": { - "providers": [[ - "tc"]]}} + "priority": [[ + "tc"]], + "providers": { + "tc": { + "ports": { + "br0": { + "ifindex": 0, + "port_no": 1}}}}}} ]) OVS_TRAFFIC_VSWITCHD_STOP -- 2.50.1 _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev