diff mbox series

[ovs-dev,1/5] ofp-print: Abbreviate lists of fields in table features output.

Message ID 20191204230611.331596-1-blp@ovn.org
State Accepted
Commit 95a5454c511057ae6f35c18f0adfce4d2dbe5410
Headers show
Series [ovs-dev,1/5] ofp-print: Abbreviate lists of fields in table features output. | expand

Commit Message

Ben Pfaff Dec. 4, 2019, 11:06 p.m. UTC
This makes the output both shorter and easier to read.

Signed-off-by: Ben Pfaff <blp@ovn.org>
---
 include/openvswitch/meta-flow.h |   3 +
 lib/meta-flow.c                 |  24 +++
 lib/ofp-table.c                 | 197 ++++++++++++++++++++++---
 tests/ofp-print.at              | 112 +-------------
 tests/ofproto.at                | 254 ++------------------------------
 5 files changed, 223 insertions(+), 367 deletions(-)

Comments

William Tu Dec. 12, 2019, 6:17 p.m. UTC | #1
On Wed, Dec 04, 2019 at 03:06:07PM -0800, Ben Pfaff wrote:
> This makes the output both shorter and easier to read.
> 
> Signed-off-by: Ben Pfaff <blp@ovn.org>

LGTM, I applied the series to master.
Thanks!
William
Ben Pfaff Dec. 13, 2019, 1:17 a.m. UTC | #2
On Thu, Dec 12, 2019 at 10:17:40AM -0800, William Tu wrote:
> On Wed, Dec 04, 2019 at 03:06:07PM -0800, Ben Pfaff wrote:
> > This makes the output both shorter and easier to read.
> > 
> > Signed-off-by: Ben Pfaff <blp@ovn.org>
> 
> LGTM, I applied the series to master.

OK.

This was actually a frustrating series for me because I was using it to
debug a test that kept failing, but then the test stopped failing, and
it wouldn't fail anymore even if I reverted the whole series.

I'm sure it'll come back though.
diff mbox series

Patch

diff --git a/include/openvswitch/meta-flow.h b/include/openvswitch/meta-flow.h
index b7fd6cb3f0fb..1f81d830e70f 100644
--- a/include/openvswitch/meta-flow.h
+++ b/include/openvswitch/meta-flow.h
@@ -1943,6 +1943,9 @@  struct mf_bitmap {
 
 bool mf_bitmap_is_superset(const struct mf_bitmap *super,
                            const struct mf_bitmap *sub);
+struct mf_bitmap mf_bitmap_and(struct mf_bitmap, struct mf_bitmap);
+struct mf_bitmap mf_bitmap_or(struct mf_bitmap, struct mf_bitmap);
+struct mf_bitmap mf_bitmap_not(struct mf_bitmap);
 
 /* Use this macro as CASE_MFF_REGS: in a switch statement to choose all of the
  * MFF_REGn cases. */
diff --git a/lib/meta-flow.c b/lib/meta-flow.c
index e3274b97f335..8b62e6d96835 100644
--- a/lib/meta-flow.c
+++ b/lib/meta-flow.c
@@ -3581,3 +3581,27 @@  mf_bitmap_is_superset(const struct mf_bitmap *super,
 {
     return bitmap_is_superset(super->bm, sub->bm, MFF_N_IDS);
 }
+
+/* Returns the bitwise-and of 'a' and 'b'. */
+struct mf_bitmap
+mf_bitmap_and(struct mf_bitmap a, struct mf_bitmap b)
+{
+    bitmap_and(a.bm, b.bm, MFF_N_IDS);
+    return a;
+}
+
+/* Returns the bitwise-or of 'a' and 'b'. */
+struct mf_bitmap
+mf_bitmap_or(struct mf_bitmap a, struct mf_bitmap b)
+{
+    bitmap_or(a.bm, b.bm, MFF_N_IDS);
+    return a;
+}
+
+/* Returns the bitwise-not of 'x'. */
+struct mf_bitmap
+mf_bitmap_not(struct mf_bitmap x)
+{
+    bitmap_not(x.bm, MFF_N_IDS);
+    return x;
+}
diff --git a/lib/ofp-table.c b/lib/ofp-table.c
index d2014f087509..6ed6c5e3b274 100644
--- a/lib/ofp-table.c
+++ b/lib/ofp-table.c
@@ -1328,6 +1328,163 @@  parse_ofp_table_mod(struct ofputil_table_mod *tm, const char **namep,
     return NULL;
 }
 
+/* Returns true if 's' consists of only ASCII digits (and at least one). */
+static bool
+is_all_digits(const char *s)
+{
+    return s[0] && s[strspn(s, "0123456789")] == '\0';
+}
+
+/* Returns true if 'a' and 'b' are the same except 'b' ends in a number one
+ * larger than 'a', for example, "reg0" and "reg1" */
+static bool
+are_names_sequential(const char *a, const char *b)
+{
+    /* Skip common prefix. */
+    for (; *a == *b; a++, b++) {
+        if (!*a) {
+            /* 'a' and 'b' are the same.  Weird, but not sequential. */
+            return false;
+        }
+    }
+
+    return (is_all_digits(a)
+            && is_all_digits(b)
+            && strlen(a) < 10
+            && strlen(b) < 10
+            && atoi(a) + 1 == atoi(b));
+}
+
+/* Returns the number of sequential names at the start of the 'n' strings in
+ * 'ids'.  Returns at least 1 (if 'n' > 0). */
+static size_t
+count_sequential_suffix_run(const char *ids[], size_t n)
+{
+    for (size_t i = 1; ; i++) {
+        if (i >= n || !are_names_sequential(ids[i - 1], ids[i])) {
+            /* "x0...x1" is worse than "x0 x1", so suppress it. */
+            return i == 2 ? 1 : i;
+        }
+    }
+}
+
+/* Counts the length of the longest common prefix (that ends in "_") between
+ * strings 'a' and 'b'.  Returns 0 if they have no common prefix. */
+static size_t
+count_common_prefix(const char *a, const char *b)
+{
+    size_t retval = 0;
+    for (size_t i = 0; ; i++) {
+        if (a[i] != b[i] || !a[i]) {
+            return retval;
+        } else if (a[i] == '_') {
+            retval = i + 1;
+        }
+    }
+}
+
+/* Returns the number of strings in the longest run of strings with a common
+ * prefix (that ends in "_") at the beginning of the 'n' strings in 'ids'.
+ * This is at least 1, if 'n' > 0.  All the strings are already known to have a
+ * common prefix of length 'prefix_len', so that's not of interest; only an
+ * additional common prefix is interesting.
+ *
+ * If this returns 'n' > 1, then '*extra_prefix_lenp' receives the length of
+ * the additional common prefix.  Otherwise '*extra_prefix_lenp' receives 0. */
+static size_t
+count_common_prefix_run(const char *ids[], size_t n,
+                        size_t prefix_len, size_t *extra_prefix_lenp)
+{
+    *extra_prefix_lenp = 0;
+    if (n < 2) {
+        return n;
+    }
+
+    size_t extra_prefix_len = count_common_prefix(ids[0] + prefix_len,
+                                                  ids[1] + prefix_len);
+    if (!extra_prefix_len) {
+        return 1;
+    }
+
+    size_t i = 2;
+    while (i < n) {
+        size_t next = count_common_prefix(ids[0] + prefix_len,
+                                          ids[i] + prefix_len);
+        if (!next) {
+            break;
+        } else if (next < extra_prefix_len) {
+            next = extra_prefix_len;
+        }
+        i++;
+    }
+    *extra_prefix_lenp = extra_prefix_len;
+    return i;
+}
+
+/* Appends the 'n' names in 'ids' to 's', omitting the first 'prefix_len' bytes
+ * of each name (which should all be the same), separating them from each other
+ * with spaces.
+ *
+ * Two kinds of abbreviation are implemented:
+ *
+ *     - Common prefixes: "eth_src eth_dst eth_type" => "eth_{src,dst,type}".
+ *
+ *     - Sequential suffixes: "reg0 reg1 reg2 reg3" => "reg0...reg3".
+ */
+static void
+print_names(struct ds *s, const char *ids[], size_t n, size_t prefix_len)
+{
+    int group = 0;
+    while (n > 0) {
+        if (group++) {
+            ds_put_char(s, prefix_len ? ',' : ' ');
+        }
+
+        /* Count the prefix and suffix runs at the beginning of 'ids'.  As of
+         * this writing we don't have any sequentially numbered fields whose
+         * names contain "_", so we should only have one or the other at a
+         * time.  However, if we end up with something like "a_0 a_1 a_2"
+         * someday, we want to render it as a_0...a_2, not as a_{0...2}, so
+         * given equal suffix and prefix runs, prefer the suffix. */
+        size_t extra_prefix_len;
+        size_t prefix_run = count_common_prefix_run(ids, n, prefix_len,
+                                                    &extra_prefix_len);
+        size_t suffix_run = count_sequential_suffix_run(ids, n);
+        size_t run = MAX(prefix_run, suffix_run);
+        if (suffix_run >= prefix_run) {
+            ds_put_format(s, "%s", ids[0] + prefix_len);
+            if (run > 1) {
+                ds_put_format(s, "...%s", ids[run - 1] + prefix_len);
+            }
+        } else {
+            ds_put_format(s, "%.*s{", (int) extra_prefix_len,
+                          ids[0] + prefix_len);
+            print_names(s, ids, run, prefix_len + extra_prefix_len);
+            ds_put_char(s, '}');
+        }
+
+        ids += run;
+        n -= run;
+    }
+}
+
+static void
+print_mf_bitmap(struct ds *s, const struct mf_bitmap *mfb)
+{
+    const char *ids[MFF_N_IDS];
+    size_t n = 0;
+
+    int i;
+    BITMAP_FOR_EACH_1 (i, MFF_N_IDS, mfb->bm) {
+        ids[n++] = mf_from_id(i)->name;
+    }
+
+    if (n > 0) {
+        ds_put_char(s, ' ');
+        print_names(s, ids, n, 0);
+    }
+}
+
 static void
 print_table_action_features(struct ds *s,
                             const struct ofputil_table_action_features *taf)
@@ -1339,12 +1496,8 @@  print_table_action_features(struct ds *s,
     }
 
     if (!bitmap_is_all_zeros(taf->set_fields.bm, MFF_N_IDS)) {
-        int i;
-
         ds_put_cstr(s, "        supported on Set-Field:");
-        BITMAP_FOR_EACH_1 (i, MFF_N_IDS, taf->set_fields.bm) {
-            ds_put_format(s, " %s", mf_from_id(i)->name);
-        }
+        print_mf_bitmap(s, &taf->set_fields);
         ds_put_char(s, '\n');
     }
 }
@@ -1404,10 +1557,10 @@  print_table_instruction_features(
                     } else {
                         ds_put_format(s, "%d", i);
                     }
-                    ds_put_char(s, ',');
+                    ds_put_char(s, ' ');
                 }
             }
-            ds_chomp(s, ',');
+            ds_chomp(s, ' ');
             ds_put_char(s, '\n');
         }
     }
@@ -1429,6 +1582,21 @@  print_table_instruction_features(
     }
 }
 
+static void
+print_matches(struct ds *s, const struct ofputil_table_features *f,
+              bool mask, bool wc, const char *title)
+{
+    const struct mf_bitmap m = mask ? f->mask : mf_bitmap_not(f->mask);
+    const struct mf_bitmap w = wc ? f->wildcard : mf_bitmap_not(f->wildcard);
+    const struct mf_bitmap bm = mf_bitmap_and(f->match, mf_bitmap_and(m, w));
+
+    if (!bitmap_is_all_zeros(bm.bm, MFF_N_IDS)) {
+        ds_put_format(s, "      %s:", title);
+        print_mf_bitmap(s, &bm);
+        ds_put_char(s, '\n');
+    }
+}
+
 /* Compares bitmaps of next tables 'a' and 'b', for tables 'a_table_id' and
  * 'b_table_id', respectively.  Returns true if the bitmaps are equal.
  *
@@ -1636,18 +1804,9 @@  ofputil_table_features_format(
         } else {
             ds_put_cstr(s, "    matching:\n");
 
-            int i;
-            BITMAP_FOR_EACH_1 (i, MFF_N_IDS, features->match.bm) {
-                const struct mf_field *f = mf_from_id(i);
-                bool mask = bitmap_is_set(features->mask.bm, i);
-                bool wildcard = bitmap_is_set(features->wildcard.bm, i);
-
-                ds_put_format(s, "      %s: %s\n",
-                              f->name,
-                              (mask ? "arbitrary mask"
-                               : wildcard ? "exact match or wildcard"
-                               : "must exact match"));
-            }
+            print_matches(s, features, true, true, "arbitrary mask");
+            print_matches(s, features, false, true, "exact match or wildcard");
+            print_matches(s, features, false, false, "must exact match");
         }
     }
 }
diff --git a/tests/ofp-print.at b/tests/ofp-print.at
index fe8ccdf3077f..dd6410b11902 100644
--- a/tests/ofp-print.at
+++ b/tests/ofp-print.at
@@ -1621,18 +1621,7 @@  OFPST_TABLE reply (xid=0x1):
     active=11, lookup=0, matched=0
     max_entries=1048576
     matching:
-      in_port: exact match or wildcard
-      eth_src: exact match or wildcard
-      eth_dst: exact match or wildcard
-      eth_type: exact match or wildcard
-      vlan_vid: exact match or wildcard
-      vlan_pcp: exact match or wildcard
-      ip_src: exact match or wildcard
-      ip_dst: exact match or wildcard
-      nw_proto: exact match or wildcard
-      nw_tos: exact match or wildcard
-      tcp_src: exact match or wildcard
-      tcp_dst: exact match or wildcard
+      exact match or wildcard: in_port eth_{src,dst,type} vlan_{vid,pcp} ip_{src,dst} nw_{proto,tos} tcp_{src,dst}
 ])
 AT_CLEANUP
 
@@ -1644,45 +1633,11 @@  AT_KEYWORDS([ofp-print OFPT_STATS_REPLY])
     config=controller
     max_entries=1000000
     instructions (table miss and others):
-      instructions: write_metadata,goto_table
+      instructions: write_metadata goto_table
       Write-Actions and Apply-Actions features:
-        supported on Set-Field: metadata in_port_oxm eth_src eth_dst eth_type vlan_vid vlan_pcp mpls_label mpls_tc ip_src ip_dst ipv6_src ipv6_dst ipv6_label nw_proto ip_dscp nw_ecn arp_op arp_spa arp_tpa arp_sha arp_tha tcp_src tcp_dst udp_src udp_dst sctp_src sctp_dst icmp_type icmp_code icmpv6_type icmpv6_code nd_target nd_sll nd_tll
+        supported on Set-Field: metadata in_port_oxm eth_{src,dst,type} vlan_{vid,pcp} mpls_{label,tc} ip_{src,dst} ipv6_{src,dst,label} nw_proto ip_dscp nw_ecn arp_{op,spa,tpa,sha,tha} tcp_{src,dst} udp_{src,dst} sctp_{src,dst} icmp_{type,code} icmpv6_{type,code} nd_{target,sll,tll}
     matching:
-      metadata: exact match or wildcard
-      in_port_oxm: exact match or wildcard
-      eth_src: exact match or wildcard
-      eth_dst: exact match or wildcard
-      eth_type: exact match or wildcard
-      vlan_vid: exact match or wildcard
-      vlan_pcp: exact match or wildcard
-      mpls_label: exact match or wildcard
-      mpls_tc: exact match or wildcard
-      ip_src: exact match or wildcard
-      ip_dst: exact match or wildcard
-      ipv6_src: exact match or wildcard
-      ipv6_dst: exact match or wildcard
-      ipv6_label: exact match or wildcard
-      nw_proto: exact match or wildcard
-      ip_dscp: exact match or wildcard
-      nw_ecn: exact match or wildcard
-      arp_op: exact match or wildcard
-      arp_spa: exact match or wildcard
-      arp_tpa: exact match or wildcard
-      arp_sha: exact match or wildcard
-      arp_tha: exact match or wildcard
-      tcp_src: exact match or wildcard
-      tcp_dst: exact match or wildcard
-      udp_src: exact match or wildcard
-      udp_dst: exact match or wildcard
-      sctp_src: exact match or wildcard
-      sctp_dst: exact match or wildcard
-      icmp_type: exact match or wildcard
-      icmp_code: exact match or wildcard
-      icmpv6_type: exact match or wildcard
-      icmpv6_code: exact match or wildcard
-      nd_target: exact match or wildcard
-      nd_sll: exact match or wildcard
-      nd_tll: exact match or wildcard
+      exact match or wildcard: metadata in_port_oxm eth_{src,dst,type} vlan_{vid,pcp} mpls_{label,tc} ip_{src,dst} ipv6_{src,dst,label} nw_proto ip_dscp nw_ecn arp_{op,spa,tpa,sha,tha} tcp_{src,dst} udp_{src,dst} sctp_{src,dst} icmp_{type,code} icmpv6_{type,code} nd_{target,sll,tll}
 
   table 1 ("table1"):
     active=0, lookup=0, matched=0
@@ -2690,65 +2645,12 @@  f5 f6 f7 f8 f9 fa fb fc fd 00 00 00 00 00 00 00 \
     max_entries=1000000
     instructions (table miss and others):
       next tables: 1-253
-      instructions: apply_actions,clear_actions,write_actions,write_metadata,goto_table
+      instructions: apply_actions clear_actions write_actions write_metadata goto_table
       Write-Actions and Apply-Actions features:
         actions: output group set_field strip_vlan push_vlan mod_nw_ttl dec_ttl set_mpls_ttl dec_mpls_ttl push_mpls pop_mpls set_queue
-        supported on Set-Field: tun_id tun_src tun_dst metadata in_port in_port_oxm pkt_mark reg0 reg1 reg2 reg3 reg4 reg5 reg6 reg7 eth_src eth_dst vlan_tci vlan_vid vlan_pcp mpls_label mpls_tc ip_src ip_dst ipv6_src ipv6_dst nw_tos ip_dscp nw_ecn nw_ttl arp_op arp_spa arp_tpa arp_sha arp_tha tcp_src tcp_dst udp_src udp_dst sctp_src sctp_dst
+        supported on Set-Field: tun_{id,src,dst} metadata in_{port,port_oxm} pkt_mark reg0...reg7 eth_{src,dst} vlan_{tci,vid,pcp} mpls_{label,tc} ip_{src,dst} ipv6_{src,dst} nw_tos ip_dscp nw_{ecn,ttl} arp_{op,spa,tpa,sha,tha} tcp_{src,dst} udp_{src,dst} sctp_{src,dst}
     matching:
-      tun_id: exact match or wildcard
-      tun_src: exact match or wildcard
-      tun_dst: exact match or wildcard
-      metadata: exact match or wildcard
-      in_port: exact match or wildcard
-      in_port_oxm: exact match or wildcard
-      pkt_mark: exact match or wildcard
-      reg0: exact match or wildcard
-      reg1: exact match or wildcard
-      reg2: exact match or wildcard
-      reg3: exact match or wildcard
-      reg4: exact match or wildcard
-      reg5: exact match or wildcard
-      reg6: exact match or wildcard
-      reg7: exact match or wildcard
-      eth_src: exact match or wildcard
-      eth_dst: exact match or wildcard
-      eth_type: exact match or wildcard
-      vlan_tci: exact match or wildcard
-      vlan_vid: exact match or wildcard
-      vlan_pcp: exact match or wildcard
-      mpls_label: exact match or wildcard
-      mpls_tc: exact match or wildcard
-      mpls_bos: exact match or wildcard
-      ip_src: exact match or wildcard
-      ip_dst: exact match or wildcard
-      ipv6_src: exact match or wildcard
-      ipv6_dst: exact match or wildcard
-      ipv6_label: exact match or wildcard
-      nw_proto: exact match or wildcard
-      nw_tos: exact match or wildcard
-      ip_dscp: exact match or wildcard
-      nw_ecn: exact match or wildcard
-      nw_ttl: exact match or wildcard
-      ip_frag: exact match or wildcard
-      arp_op: exact match or wildcard
-      arp_spa: exact match or wildcard
-      arp_tpa: exact match or wildcard
-      arp_sha: exact match or wildcard
-      arp_tha: exact match or wildcard
-      tcp_src: exact match or wildcard
-      tcp_dst: exact match or wildcard
-      tcp_flags: exact match or wildcard
-      udp_src: exact match or wildcard
-      udp_dst: exact match or wildcard
-      sctp_src: exact match or wildcard
-      sctp_dst: exact match or wildcard
-      icmp_type: exact match or wildcard
-      icmp_code: exact match or wildcard
-      icmpv6_type: exact match or wildcard
-      icmpv6_code: exact match or wildcard
-      nd_target: exact match or wildcard
-      nd_sll: exact match or wildcard
-      nd_tll: exact match or wildcard
+      exact match or wildcard: tun_{id,src,dst} metadata in_{port,port_oxm} pkt_mark reg0...reg7 eth_{src,dst,type} vlan_{tci,vid,pcp} mpls_{label,tc,bos} ip_{src,dst} ipv6_{src,dst,label} nw_{proto,tos} ip_dscp nw_{ecn,ttl} ip_frag arp_{op,spa,tpa,sha,tha} tcp_{src,dst,flags} udp_{src,dst} sctp_{src,dst} icmp_{type,code} icmpv6_{type,code} nd_{target,sll,tll}
 ])
 AT_CLEANUP
 
diff --git a/tests/ofproto.at b/tests/ofproto.at
index 2436434ca9b7..d5a13d0cf3b7 100644
--- a/tests/ofproto.at
+++ b/tests/ofproto.at
@@ -2205,18 +2205,7 @@  head_table() {
     active=0, lookup=0, matched=0
     max_entries=1000000
     matching:
-      in_port: exact match or wildcard
-      eth_src: exact match or wildcard
-      eth_dst: exact match or wildcard
-      eth_type: exact match or wildcard
-      vlan_vid: exact match or wildcard
-      vlan_pcp: exact match or wildcard
-      ip_src: exact match or wildcard
-      ip_dst: exact match or wildcard
-      nw_proto: exact match or wildcard
-      nw_tos: exact match or wildcard
-      tcp_src: exact match or wildcard
-      tcp_dst: exact match or wildcard
+      exact match or wildcard: in_port eth_{src,dst,type} vlan_{vid,pcp} ip_{src,dst} nw_{proto,tos} tcp_{src,dst}
 
 ' "$1"
 }
@@ -2276,18 +2265,7 @@  head_table() {
     active=0, lookup=0, matched=0
     max_entries=1000000
     matching:
-      in_port: exact match or wildcard
-      eth_src: exact match or wildcard
-      eth_dst: exact match or wildcard
-      eth_type: exact match or wildcard
-      vlan_vid: exact match or wildcard
-      vlan_pcp: exact match or wildcard
-      ip_src: exact match or wildcard
-      ip_dst: exact match or wildcard
-      nw_proto: exact match or wildcard
-      nw_tos: exact match or wildcard
-      tcp_src: exact match or wildcard
-      tcp_dst: exact match or wildcard
+      exact match or wildcard: in_port eth_{src,dst,type} vlan_{vid,pcp} ip_{src,dst} nw_{proto,tos} tcp_{src,dst}
 
 '
 }
@@ -2307,46 +2285,12 @@  head_table() {
     config=controller
     max_entries=1000000
     instructions (table miss and others):
-      instructions: apply_actions,clear_actions,write_actions,write_metadata,goto_table
+      instructions: apply_actions clear_actions write_actions write_metadata goto_table
       Write-Actions and Apply-Actions features:
         actions: output group set_field strip_vlan push_vlan mod_nw_ttl dec_ttl set_mpls_ttl dec_mpls_ttl push_mpls pop_mpls set_queue
-        supported on Set-Field: metadata in_port_oxm eth_src eth_dst vlan_vid vlan_pcp mpls_label mpls_tc ip_src ip_dst ipv6_src ipv6_dst ipv6_label ip_dscp nw_ecn arp_op arp_spa arp_tpa arp_sha arp_tha tcp_src tcp_dst udp_src udp_dst sctp_src sctp_dst icmp_type icmp_code icmpv6_type icmpv6_code nd_target nd_sll nd_tll
+        supported on Set-Field: metadata in_port_oxm eth_{src,dst} vlan_{vid,pcp} mpls_{label,tc} ip_{src,dst} ipv6_{src,dst,label} ip_dscp nw_ecn arp_{op,spa,tpa,sha,tha} tcp_{src,dst} udp_{src,dst} sctp_{src,dst} icmp_{type,code} icmpv6_{type,code} nd_{target,sll,tll}
     matching:
-      metadata: exact match or wildcard
-      in_port_oxm: exact match or wildcard
-      eth_src: exact match or wildcard
-      eth_dst: exact match or wildcard
-      eth_type: exact match or wildcard
-      vlan_vid: exact match or wildcard
-      vlan_pcp: exact match or wildcard
-      mpls_label: exact match or wildcard
-      mpls_tc: exact match or wildcard
-      ip_src: exact match or wildcard
-      ip_dst: exact match or wildcard
-      ipv6_src: exact match or wildcard
-      ipv6_dst: exact match or wildcard
-      ipv6_label: exact match or wildcard
-      nw_proto: exact match or wildcard
-      ip_dscp: exact match or wildcard
-      nw_ecn: exact match or wildcard
-      arp_op: exact match or wildcard
-      arp_spa: exact match or wildcard
-      arp_tpa: exact match or wildcard
-      arp_sha: exact match or wildcard
-      arp_tha: exact match or wildcard
-      tcp_src: exact match or wildcard
-      tcp_dst: exact match or wildcard
-      udp_src: exact match or wildcard
-      udp_dst: exact match or wildcard
-      sctp_src: exact match or wildcard
-      sctp_dst: exact match or wildcard
-      icmp_type: exact match or wildcard
-      icmp_code: exact match or wildcard
-      icmpv6_type: exact match or wildcard
-      icmpv6_code: exact match or wildcard
-      nd_target: exact match or wildcard
-      nd_sll: exact match or wildcard
-      nd_tll: exact match or wildcard
+      exact match or wildcard: metadata in_port_oxm eth_{src,dst,type} vlan_{vid,pcp} mpls_{label,tc} ip_{src,dst} ipv6_{src,dst,label} nw_proto ip_dscp nw_ecn arp_{op,spa,tpa,sha,tha} tcp_{src,dst} udp_{src,dst} sctp_{src,dst} icmp_{type,code} icmpv6_{type,code} nd_{target,sll,tll}
 
 ' "$1"
 }
@@ -2357,7 +2301,7 @@  tail_table() {
     config=controller
     max_entries=1000000
     instructions (table miss and others):
-      instructions: apply_actions,clear_actions,write_actions,write_metadata
+      instructions: apply_actions clear_actions write_actions write_metadata
       (same actions)
     (same matching)
 '
@@ -2403,189 +2347,13 @@  head_table () {
     max_entries=1000000
     instructions (table miss and others):
       next tables: 1-253
-      instructions: meter,apply_actions,clear_actions,write_actions,write_metadata,goto_table
+      instructions: meter apply_actions clear_actions write_actions write_metadata goto_table
       Write-Actions and Apply-Actions features:
         actions: output group set_field strip_vlan push_vlan mod_nw_ttl dec_ttl set_mpls_ttl dec_mpls_ttl push_mpls pop_mpls set_queue
-        supported on Set-Field: tun_id tun_src tun_dst tun_ipv6_src tun_ipv6_dst tun_flags tun_gbp_id tun_gbp_flags tun_erspan_idx tun_erspan_ver tun_erspan_dir tun_erspan_hwid tun_metadata0 dnl
-tun_metadata1 tun_metadata2 tun_metadata3 tun_metadata4 tun_metadata5 tun_metadata6 tun_metadata7 tun_metadata8 tun_metadata9 tun_metadata10 tun_metadata11 tun_metadata12 tun_metadata13 tun_metadata14 tun_metadata15 tun_metadata16 tun_metadata17 tun_metadata18 tun_metadata19 tun_metadata20 tun_metadata21 tun_metadata22 tun_metadata23 tun_metadata24 tun_metadata25 tun_metadata26 tun_metadata27 tun_metadata28 tun_metadata29 tun_metadata30 tun_metadata31 tun_metadata32 tun_metadata33 tun_metadata34 tun_metadata35 tun_metadata36 tun_metadata37 tun_metadata38 tun_metadata39 tun_metadata40 tun_metadata41 tun_metadata42 tun_metadata43 tun_metadata44 tun_metadata45 tun_metadata46 tun_metadata47 tun_metadata48 tun_metadata49 tun_metadata50 tun_metadata51 tun_metadata52 tun_metadata53 tun_metadata54 tun_metadata55 tun_metadata56 tun_metadata57 tun_metadata58 tun_metadata59 tun_metadata60 tun_metadata61 tun_metadata62 tun_metadata63 dnl
-metadata in_port in_port_oxm pkt_mark ct_mark ct_label reg0 reg1 reg2 reg3 reg4 reg5 reg6 reg7 reg8 reg9 reg10 reg11 reg12 reg13 reg14 reg15 xreg0 xreg1 xreg2 xreg3 xreg4 xreg5 xreg6 xreg7 xxreg0 xxreg1 xxreg2 xxreg3 eth_src eth_dst vlan_tci vlan_vid vlan_pcp mpls_label mpls_tc mpls_ttl ip_src ip_dst ipv6_src ipv6_dst ipv6_label nw_tos ip_dscp nw_ecn nw_ttl arp_op arp_spa arp_tpa arp_sha arp_tha tcp_src tcp_dst udp_src udp_dst sctp_src sctp_dst icmp_type icmp_code icmpv6_type icmpv6_code nd_target nd_sll nd_tll nd_reserved nd_options_type nsh_flags nsh_spi nsh_si nsh_c1 nsh_c2 nsh_c3 nsh_c4 nsh_ttl
+        supported on Set-Field: tun_{id,src,dst,ipv6_{src,dst},flags,gbp_{id,flags},erspan_{idx,ver,dir,hwid},metadata0...metadata63} metadata in_{port,port_oxm} pkt_mark ct_{mark,label} reg0...reg15 xreg0...xreg7 xxreg0...xxreg3 eth_{src,dst} vlan_{tci,vid,pcp} mpls_{label,tc,ttl} ip_{src,dst} ipv6_{src,dst,label} nw_tos ip_dscp nw_{ecn,ttl} arp_{op,spa,tpa,sha,tha} tcp_{src,dst} udp_{src,dst} sctp_{src,dst} icmp_{type,code} icmpv6_{type,code} nd_{target,sll,tll,reserved,options_type} nsh_{flags,spi,si,c1...c4,ttl}
     matching:
-      dp_hash: arbitrary mask
-      recirc_id: exact match or wildcard
-      packet_type: exact match or wildcard
-      conj_id: exact match or wildcard
-      tun_id: arbitrary mask
-      tun_src: arbitrary mask
-      tun_dst: arbitrary mask
-      tun_ipv6_src: arbitrary mask
-      tun_ipv6_dst: arbitrary mask
-      tun_flags: arbitrary mask
-      tun_gbp_id: arbitrary mask
-      tun_gbp_flags: arbitrary mask
-      tun_erspan_idx: arbitrary mask
-      tun_erspan_ver: arbitrary mask
-      tun_erspan_dir: arbitrary mask
-      tun_erspan_hwid: arbitrary mask
-      tun_metadata0: arbitrary mask
-      tun_metadata1: arbitrary mask
-      tun_metadata2: arbitrary mask
-      tun_metadata3: arbitrary mask
-      tun_metadata4: arbitrary mask
-      tun_metadata5: arbitrary mask
-      tun_metadata6: arbitrary mask
-      tun_metadata7: arbitrary mask
-      tun_metadata8: arbitrary mask
-      tun_metadata9: arbitrary mask
-      tun_metadata10: arbitrary mask
-      tun_metadata11: arbitrary mask
-      tun_metadata12: arbitrary mask
-      tun_metadata13: arbitrary mask
-      tun_metadata14: arbitrary mask
-      tun_metadata15: arbitrary mask
-      tun_metadata16: arbitrary mask
-      tun_metadata17: arbitrary mask
-      tun_metadata18: arbitrary mask
-      tun_metadata19: arbitrary mask
-      tun_metadata20: arbitrary mask
-      tun_metadata21: arbitrary mask
-      tun_metadata22: arbitrary mask
-      tun_metadata23: arbitrary mask
-      tun_metadata24: arbitrary mask
-      tun_metadata25: arbitrary mask
-      tun_metadata26: arbitrary mask
-      tun_metadata27: arbitrary mask
-      tun_metadata28: arbitrary mask
-      tun_metadata29: arbitrary mask
-      tun_metadata30: arbitrary mask
-      tun_metadata31: arbitrary mask
-      tun_metadata32: arbitrary mask
-      tun_metadata33: arbitrary mask
-      tun_metadata34: arbitrary mask
-      tun_metadata35: arbitrary mask
-      tun_metadata36: arbitrary mask
-      tun_metadata37: arbitrary mask
-      tun_metadata38: arbitrary mask
-      tun_metadata39: arbitrary mask
-      tun_metadata40: arbitrary mask
-      tun_metadata41: arbitrary mask
-      tun_metadata42: arbitrary mask
-      tun_metadata43: arbitrary mask
-      tun_metadata44: arbitrary mask
-      tun_metadata45: arbitrary mask
-      tun_metadata46: arbitrary mask
-      tun_metadata47: arbitrary mask
-      tun_metadata48: arbitrary mask
-      tun_metadata49: arbitrary mask
-      tun_metadata50: arbitrary mask
-      tun_metadata51: arbitrary mask
-      tun_metadata52: arbitrary mask
-      tun_metadata53: arbitrary mask
-      tun_metadata54: arbitrary mask
-      tun_metadata55: arbitrary mask
-      tun_metadata56: arbitrary mask
-      tun_metadata57: arbitrary mask
-      tun_metadata58: arbitrary mask
-      tun_metadata59: arbitrary mask
-      tun_metadata60: arbitrary mask
-      tun_metadata61: arbitrary mask
-      tun_metadata62: arbitrary mask
-      tun_metadata63: arbitrary mask
-      metadata: arbitrary mask
-      in_port: exact match or wildcard
-      in_port_oxm: exact match or wildcard
-      actset_output: exact match or wildcard
-      pkt_mark: arbitrary mask
-      ct_state: arbitrary mask
-      ct_zone: exact match or wildcard
-      ct_mark: arbitrary mask
-      ct_label: arbitrary mask
-      ct_nw_proto: exact match or wildcard
-      ct_nw_src: arbitrary mask
-      ct_nw_dst: arbitrary mask
-      ct_ipv6_src: arbitrary mask
-      ct_ipv6_dst: arbitrary mask
-      ct_tp_src: arbitrary mask
-      ct_tp_dst: arbitrary mask
-      reg0: arbitrary mask
-      reg1: arbitrary mask
-      reg2: arbitrary mask
-      reg3: arbitrary mask
-      reg4: arbitrary mask
-      reg5: arbitrary mask
-      reg6: arbitrary mask
-      reg7: arbitrary mask
-      reg8: arbitrary mask
-      reg9: arbitrary mask
-      reg10: arbitrary mask
-      reg11: arbitrary mask
-      reg12: arbitrary mask
-      reg13: arbitrary mask
-      reg14: arbitrary mask
-      reg15: arbitrary mask
-      xreg0: arbitrary mask
-      xreg1: arbitrary mask
-      xreg2: arbitrary mask
-      xreg3: arbitrary mask
-      xreg4: arbitrary mask
-      xreg5: arbitrary mask
-      xreg6: arbitrary mask
-      xreg7: arbitrary mask
-      xxreg0: arbitrary mask
-      xxreg1: arbitrary mask
-      xxreg2: arbitrary mask
-      xxreg3: arbitrary mask
-      eth_src: arbitrary mask
-      eth_dst: arbitrary mask
-      eth_type: exact match or wildcard
-      vlan_tci: arbitrary mask
-      vlan_vid: arbitrary mask
-      vlan_pcp: exact match or wildcard
-      mpls_label: exact match or wildcard
-      mpls_tc: exact match or wildcard
-      mpls_bos: exact match or wildcard
-      mpls_ttl: exact match or wildcard
-      ip_src: arbitrary mask
-      ip_dst: arbitrary mask
-      ipv6_src: arbitrary mask
-      ipv6_dst: arbitrary mask
-      ipv6_label: arbitrary mask
-      nw_proto: exact match or wildcard
-      nw_tos: exact match or wildcard
-      ip_dscp: exact match or wildcard
-      nw_ecn: exact match or wildcard
-      nw_ttl: exact match or wildcard
-      ip_frag: arbitrary mask
-      arp_op: exact match or wildcard
-      arp_spa: arbitrary mask
-      arp_tpa: arbitrary mask
-      arp_sha: arbitrary mask
-      arp_tha: arbitrary mask
-      tcp_src: arbitrary mask
-      tcp_dst: arbitrary mask
-      tcp_flags: arbitrary mask
-      udp_src: arbitrary mask
-      udp_dst: arbitrary mask
-      sctp_src: arbitrary mask
-      sctp_dst: arbitrary mask
-      icmp_type: exact match or wildcard
-      icmp_code: exact match or wildcard
-      icmpv6_type: exact match or wildcard
-      icmpv6_code: exact match or wildcard
-      nd_target: arbitrary mask
-      nd_sll: arbitrary mask
-      nd_tll: arbitrary mask
-      nd_reserved: exact match or wildcard
-      nd_options_type: exact match or wildcard
-      nsh_flags: arbitrary mask
-      nsh_mdtype: exact match or wildcard
-      nsh_np: exact match or wildcard
-      nsh_spi: exact match or wildcard
-      nsh_si: exact match or wildcard
-      nsh_c1: arbitrary mask
-      nsh_c2: arbitrary mask
-      nsh_c3: arbitrary mask
-      nsh_c4: arbitrary mask
-      nsh_ttl: exact match or wildcard
+      arbitrary mask: dp_hash tun_{id,src,dst,ipv6_{src,dst},flags,gbp_{id,flags},erspan_{idx,ver,dir,hwid},metadata0...metadata63} metadata pkt_mark ct_{state,mark,label,nw_{src,dst},ipv6_{src,dst},tp_{src,dst}} reg0...reg15 xreg0...xreg7 xxreg0...xxreg3 eth_{src,dst} vlan_{tci,vid} ip_{src,dst} ipv6_{src,dst,label} ip_frag arp_{spa,tpa,sha,tha} tcp_{src,dst,flags} udp_{src,dst} sctp_{src,dst} nd_{target,sll,tll} nsh_{flags,c1...c4}
+      exact match or wildcard: recirc_id packet_type conj_id in_{port,port_oxm} actset_output ct_{zone,nw_proto} eth_type vlan_pcp mpls_{label,tc,bos,ttl} nw_{proto,tos} ip_dscp nw_{ecn,ttl} arp_op icmp_{type,code} icmpv6_{type,code} nd_{reserved,options_type} nsh_{mdtype,np,spi,si,ttl}
 
 ' "$1"
 }
@@ -2594,7 +2362,7 @@  echo '  table 253:
     metadata: match=0xffffffffffffffff write=0xffffffffffffffff
     max_entries=1000000
     instructions (table miss and others):
-      instructions: meter,apply_actions,clear_actions,write_actions,write_metadata
+      instructions: meter apply_actions clear_actions write_actions write_metadata
       (same actions)
     (same matching)
 '