diff mbox series

[ovs-dev,v6,4/9] netdev-linux: Add functions to manipulate tc police action

Message ID 20220702031832.13282-5-jianbol@nvidia.com
State Superseded
Headers show
Series Add support for ovs metering with tc offload | expand

Checks

Context Check Description
ovsrobot/apply-robot fail apply and check: fail
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/intel-ovs-compilation success test: success

Commit Message

Jianbo Liu July 2, 2022, 3:18 a.m. UTC
Add helpers to add, delete and get stats of police action with
the specified index.

Signed-off-by: Jianbo Liu <jianbol@nvidia.com>
---
 lib/netdev-linux.c | 144 +++++++++++++++++++++++++++++++++++++++++++++
 lib/netdev-linux.h |   6 ++
 lib/tc.c           | 118 +++++++++++++++++++++++++------------
 lib/tc.h           |   7 +++
 4 files changed, 239 insertions(+), 36 deletions(-)

Comments

Eelco Chaudron July 7, 2022, 11:51 a.m. UTC | #1
On 2 Jul 2022, at 5:18, Jianbo Liu wrote:

> Add helpers to add, delete and get stats of police action with
> the specified index.
>
> Signed-off-by: Jianbo Liu <jianbol@nvidia.com>

Two some small comments below...

> ---
>  lib/netdev-linux.c | 144 +++++++++++++++++++++++++++++++++++++++++++++
>  lib/netdev-linux.h |   6 ++
>  lib/tc.c           | 118 +++++++++++++++++++++++++------------
>  lib/tc.h           |   7 +++
>  4 files changed, 239 insertions(+), 36 deletions(-)
>
> diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c
> index 039a99f49..24f0bceb8 100644
> --- a/lib/netdev-linux.c
> +++ b/lib/netdev-linux.c
> @@ -5667,6 +5667,150 @@ tc_add_policer(struct netdev *netdev, uint32_t kbits_rate,
>      return 0;
>  }
>
> +int
> +tc_add_policer_action(uint32_t index, uint32_t kbits_rate,
> +                      uint32_t kbits_burst, uint32_t pkts_rate,
> +                      uint32_t pkts_burst, bool update)
> +{
> +    struct tc_police tc_police;
> +    struct ofpbuf request;
> +    struct tcamsg *tcamsg;
> +    size_t offset;
> +    int flags;
> +    int error;
> +
> +    tc_policer_init(&tc_police, kbits_rate, kbits_burst);
> +    tc_police.index = index;
> +
> +    flags = (update ? NLM_F_REPLACE : NLM_F_EXCL) | NLM_F_CREATE;
> +    tcamsg = tc_make_action_request(RTM_NEWACTION, flags, &request);
> +    if (!tcamsg) {
> +        return ENODEV;
> +    }
> +
> +    offset = nl_msg_start_nested(&request, TCA_ACT_TAB);
> +    nl_msg_put_act_police(&request, &tc_police, pkts_rate, pkts_burst);
> +    nl_msg_end_nested(&request, offset);
> +
> +    error = tc_transact(&request, NULL);
> +    if (error) {
> +        VLOG_ERR_RL(&rl, "Failed to %s police action, err=%d",
> +                    update ? "update" : "add", error);
> +    }
> +
> +    return error;
> +}
> +
> +static int
> +tc_update_policer_action_stats(struct ofpbuf *msg,
> +                               struct ofputil_meter_stats *stats)
> +{
> +    struct ovs_flow_stats stats_dropped = {0};
> +    struct ovs_flow_stats stats_hw = {0};
> +    struct ovs_flow_stats stats_sw = {0};

Here you want to keep the initializer as {}, as setting it to {0} gets some compilers to only initialize the first element. See also the robot complaining about this.

> +    const struct nlattr *act = NULL;
> +    struct nlattr *prio;
> +    struct tcamsg *tca;
> +    int error;
> +
> +    if (!stats) {
> +        return 0;
> +    }
> +
> +    if (NLMSG_HDRLEN + sizeof *tca > msg->size) {
> +        VLOG_ERR_RL(&rl, "Failed to get action stats, size error");
> +        return EPROTO;
> +    }
> +
> +    tca = ofpbuf_at_assert(msg, NLMSG_HDRLEN, sizeof *tca);
> +    act = nl_attr_find(msg, NLMSG_HDRLEN + sizeof *tca, TCA_ACT_TAB);
> +    if (!act) {
> +        VLOG_ERR_RL(&rl, "Failed to get action stats, can't find attr");


A nit I missed before, but as you need to send an update rev anyway, please change attr to attribute in the error message, which is more user-friendly.

> +        return EPROTO;
> +    }
> +

<SNIP>
Ilya Maximets July 7, 2022, 12:02 p.m. UTC | #2
On 7/7/22 13:51, Eelco Chaudron wrote:
> On 2 Jul 2022, at 5:18, Jianbo Liu wrote:
>> +static int
>> +tc_update_policer_action_stats(struct ofpbuf *msg,
>> +                               struct ofputil_meter_stats *stats)
>> +{
>> +    struct ovs_flow_stats stats_dropped = {0};
>> +    struct ovs_flow_stats stats_hw = {0};
>> +    struct ovs_flow_stats stats_sw = {0};
> 
> Here you want to keep the initializer as {}, as setting it to {0} gets
> some compilers to only initialize the first element. See also the robot
> complaining about this.
I don't think empty braces is a valid initializer from the
standard C point of view.  We should use memset or a valid
designated initializer with at least one field specified.

OTOH, do we really need these to be initialized?
If the tc_parse_action_stats() call fails, we're not using
them, but if it succeeds, they should be initialized with
valid values by the function.

Best regards, Ilya Maximets.
Jianbo Liu July 8, 2022, 1:38 a.m. UTC | #3
On Thu, 2022-07-07 at 14:02 +0200, Ilya Maximets wrote:
> On 7/7/22 13:51, Eelco Chaudron wrote:
> > On 2 Jul 2022, at 5:18, Jianbo Liu wrote:
> > > +static int
> > > +tc_update_policer_action_stats(struct ofpbuf *msg,
> > > +                               struct ofputil_meter_stats
> > > *stats)
> > > +{
> > > +    struct ovs_flow_stats stats_dropped = {0};
> > > +    struct ovs_flow_stats stats_hw = {0};
> > > +    struct ovs_flow_stats stats_sw = {0};
> > 
> > Here you want to keep the initializer as {}, as setting it to {0}
> > gets
> > some compilers to only initialize the first element. See also the
> > robot
> > complaining about this.
> I don't think empty braces is a valid initializer from the
> standard C point of view.  We should use memset or a valid
> designated initializer with at least one field specified.
> 
> OTOH, do we really need these to be initialized?
> If the tc_parse_action_stats() call fails, we're not using
> them, but if it succeeds, they should be initialized with
> valid values by the function.
> 

In tc_parse_action_stats(), we parse stats by stats_policy. And
TCA_STATS_BASIC_HW and TCA_STATS_QUEUE are defined as optional in it.
From this logic, we should init stats_hw and stats_dropped (not need
for stats_sw), in case we can't parse them out of stats.

I used {} in v5, and {0} in v6. Both doesn't work. Maybe {{0},{0}}
could work. But I prefer to memset. Will use memset in v7.

Thanks!

> Best regards, Ilya Maximets.
Jianbo Liu July 8, 2022, 1:40 a.m. UTC | #4
On Thu, 2022-07-07 at 13:51 +0200, Eelco Chaudron wrote:
> On 2 Jul 2022, at 5:18, Jianbo Liu wrote:
> 
> 
> > +    tca = ofpbuf_at_assert(msg, NLMSG_HDRLEN, sizeof *tca);
> > +    act = nl_attr_find(msg, NLMSG_HDRLEN + sizeof *tca,
> > TCA_ACT_TAB);
> > +    if (!act) {
> > +        VLOG_ERR_RL(&rl, "Failed to get action stats, can't find
> > attr");
> 
> 
> A nit I missed before, but as you need to send an update rev anyway,
> please change attr to attribute in the error message, which is more
> user-friendly.
> 

OK, will change in next version. Thanks!

> > +        return EPROTO;
> > +    }
> > +
> 
> <SNIP>
>
Jianbo Liu July 8, 2022, 2:49 a.m. UTC | #5
On Fri, 2022-07-08 at 09:38 +0800, Jianbo Liu wrote:
> On Thu, 2022-07-07 at 14:02 +0200, Ilya Maximets wrote:
> > On 7/7/22 13:51, Eelco Chaudron wrote:
> > > On 2 Jul 2022, at 5:18, Jianbo Liu wrote:
> > > > +static int
> > > > +tc_update_policer_action_stats(struct ofpbuf *msg,
> > > > +                               struct ofputil_meter_stats
> > > > *stats)
> > > > +{
> > > > +    struct ovs_flow_stats stats_dropped = {0};
> > > > +    struct ovs_flow_stats stats_hw = {0};
> > > > +    struct ovs_flow_stats stats_sw = {0};
> > > 
> > > Here you want to keep the initializer as {}, as setting it to {0}
> > > gets
> > > some compilers to only initialize the first element. See also the
> > > robot
> > > complaining about this.
> > I don't think empty braces is a valid initializer from the
> > standard C point of view.  We should use memset or a valid
> > designated initializer with at least one field specified.
> > 
> > OTOH, do we really need these to be initialized?
> > If the tc_parse_action_stats() call fails, we're not using
> > them, but if it succeeds, they should be initialized with
> > valid values by the function.
> > 
> 
> In tc_parse_action_stats(), we parse stats by stats_policy. And
> TCA_STATS_BASIC_HW and TCA_STATS_QUEUE are defined as optional in it.
> From this logic, we should init stats_hw and stats_dropped (not need
> for stats_sw), in case we can't parse them out of stats.
> 

Looks stats_sw also need initialization. It may not set any value in
tc_parse_action_stats().

> I used {} in v5, and {0} in v6. Both doesn't work. Maybe {{0},{0}}
> could work. But I prefer to memset. Will use memset in v7.
> 
> Thanks!
> 
> > Best regards, Ilya Maximets.
>
diff mbox series

Patch

diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c
index 039a99f49..24f0bceb8 100644
--- a/lib/netdev-linux.c
+++ b/lib/netdev-linux.c
@@ -5667,6 +5667,150 @@  tc_add_policer(struct netdev *netdev, uint32_t kbits_rate,
     return 0;
 }
 
+int
+tc_add_policer_action(uint32_t index, uint32_t kbits_rate,
+                      uint32_t kbits_burst, uint32_t pkts_rate,
+                      uint32_t pkts_burst, bool update)
+{
+    struct tc_police tc_police;
+    struct ofpbuf request;
+    struct tcamsg *tcamsg;
+    size_t offset;
+    int flags;
+    int error;
+
+    tc_policer_init(&tc_police, kbits_rate, kbits_burst);
+    tc_police.index = index;
+
+    flags = (update ? NLM_F_REPLACE : NLM_F_EXCL) | NLM_F_CREATE;
+    tcamsg = tc_make_action_request(RTM_NEWACTION, flags, &request);
+    if (!tcamsg) {
+        return ENODEV;
+    }
+
+    offset = nl_msg_start_nested(&request, TCA_ACT_TAB);
+    nl_msg_put_act_police(&request, &tc_police, pkts_rate, pkts_burst);
+    nl_msg_end_nested(&request, offset);
+
+    error = tc_transact(&request, NULL);
+    if (error) {
+        VLOG_ERR_RL(&rl, "Failed to %s police action, err=%d",
+                    update ? "update" : "add", error);
+    }
+
+    return error;
+}
+
+static int
+tc_update_policer_action_stats(struct ofpbuf *msg,
+                               struct ofputil_meter_stats *stats)
+{
+    struct ovs_flow_stats stats_dropped = {0};
+    struct ovs_flow_stats stats_hw = {0};
+    struct ovs_flow_stats stats_sw = {0};
+    const struct nlattr *act = NULL;
+    struct nlattr *prio;
+    struct tcamsg *tca;
+    int error;
+
+    if (!stats) {
+        return 0;
+    }
+
+    if (NLMSG_HDRLEN + sizeof *tca > msg->size) {
+        VLOG_ERR_RL(&rl, "Failed to get action stats, size error");
+        return EPROTO;
+    }
+
+    tca = ofpbuf_at_assert(msg, NLMSG_HDRLEN, sizeof *tca);
+    act = nl_attr_find(msg, NLMSG_HDRLEN + sizeof *tca, TCA_ACT_TAB);
+    if (!act) {
+        VLOG_ERR_RL(&rl, "Failed to get action stats, can't find attr");
+        return EPROTO;
+    }
+
+    prio = (struct nlattr *) act + 1;
+    error = tc_parse_action_stats(prio, &stats_sw, &stats_hw, &stats_dropped);
+    if (!error) {
+        stats->packet_in_count +=
+            get_32aligned_u64(&stats_sw.n_packets);
+        stats->byte_in_count += get_32aligned_u64(&stats_sw.n_bytes);
+        stats->packet_in_count +=
+            get_32aligned_u64(&stats_hw.n_packets);
+        stats->byte_in_count += get_32aligned_u64(&stats_hw.n_bytes);
+        if (stats->n_bands >= 1) {
+            stats->bands[0].packet_count +=
+                get_32aligned_u64(&stats_dropped.n_packets);
+        }
+    }
+
+    return error;
+}
+
+int
+tc_get_policer_action(uint32_t index, struct ofputil_meter_stats *stats)
+{
+    struct ofpbuf *replyp = NULL;
+    struct ofpbuf request;
+    struct tcamsg *tcamsg;
+    size_t root_offset;
+    size_t prio_offset;
+    int error;
+
+    tcamsg = tc_make_action_request(RTM_GETACTION, 0, &request);
+    if (!tcamsg) {
+        return ENODEV;
+    }
+
+    root_offset = nl_msg_start_nested(&request, TCA_ACT_TAB);
+    prio_offset = nl_msg_start_nested(&request, 1);
+    nl_msg_put_string(&request, TCA_ACT_KIND, "police");
+    nl_msg_put_u32(&request, TCA_ACT_INDEX, index);
+    nl_msg_end_nested(&request, prio_offset);
+    nl_msg_end_nested(&request, root_offset);
+
+    error = tc_transact(&request, &replyp);
+    if (error) {
+        VLOG_ERR_RL(&rl, "Failed to dump police action (index: %u), err=%d",
+                    index, error);
+        return error;
+    }
+
+    return tc_update_policer_action_stats(replyp, stats);
+}
+
+int
+tc_del_policer_action(uint32_t index, struct ofputil_meter_stats *stats)
+{
+    struct ofpbuf *replyp = NULL;
+    struct ofpbuf request;
+    struct tcamsg *tcamsg;
+    size_t root_offset;
+    size_t prio_offset;
+    int error;
+
+    tcamsg = tc_make_action_request(RTM_DELACTION, NLM_F_ACK, &request);
+    if (!tcamsg) {
+        return ENODEV;
+    }
+
+    root_offset = nl_msg_start_nested(&request, TCA_ACT_TAB);
+    prio_offset = nl_msg_start_nested(&request, 1);
+    nl_msg_put_string(&request, TCA_ACT_KIND, "police");
+    nl_msg_put_u32(&request, TCA_ACT_INDEX, index);
+    nl_msg_end_nested(&request, prio_offset);
+    nl_msg_end_nested(&request, root_offset);
+
+    error = tc_transact(&request, &replyp);
+    if (error) {
+        VLOG_ERR_RL(&rl, "Failed to delete police action (index: %u), err=%d",
+                    index, error);
+        return error;
+    }
+
+    return tc_update_policer_action_stats(replyp, stats);
+}
+
 static void
 read_psched(void)
 {
diff --git a/lib/netdev-linux.h b/lib/netdev-linux.h
index e1e30f806..9a416ce50 100644
--- a/lib/netdev-linux.h
+++ b/lib/netdev-linux.h
@@ -19,6 +19,7 @@ 
 
 #include <stdint.h>
 #include <stdbool.h>
+#include "openvswitch/ofp-meter.h"
 
 /* These functions are Linux specific, so they should be used directly only by
  * Linux-specific code. */
@@ -28,5 +29,10 @@  struct netdev;
 int netdev_linux_ethtool_set_flag(struct netdev *netdev, uint32_t flag,
                                   const char *flag_name, bool enable);
 int linux_get_ifindex(const char *netdev_name);
+int tc_add_policer_action(uint32_t index, uint32_t kbits_rate,
+                          uint32_t kbits_burst, uint32_t pkts_rate,
+                          uint32_t pkts_burst, bool update);
+int tc_del_policer_action(uint32_t index, struct ofputil_meter_stats *stats);
+int tc_get_policer_action(uint32_t index, struct ofputil_meter_stats *stats);
 
 #endif /* netdev-linux.h */
diff --git a/lib/tc.c b/lib/tc.c
index bfd6512a1..bd107a588 100644
--- a/lib/tc.c
+++ b/lib/tc.c
@@ -199,6 +199,20 @@  tc_make_request(int ifindex, int type, unsigned int flags,
     return tcmsg;
 }
 
+struct tcamsg *
+tc_make_action_request(int type, unsigned int flags,
+                       struct ofpbuf *request)
+{
+    struct tcamsg *tcamsg;
+
+    ofpbuf_init(request, 512);
+    nl_msg_put_nlmsghdr(request, sizeof *tcamsg, type, NLM_F_REQUEST | flags);
+    tcamsg = ofpbuf_put_zeros(request, sizeof *tcamsg);
+    tcamsg->tca_family = AF_UNSPEC;
+
+    return tcamsg;
+}
+
 static void request_from_tcf_id(struct tcf_id *id, uint16_t eth_type,
                                 int type, unsigned int flags,
                                 struct ofpbuf *request)
@@ -1760,21 +1774,69 @@  static const struct nl_policy stats_policy[] = {
     [TCA_STATS_BASIC_HW] = { .type = NL_A_UNSPEC,
                              .min_len = sizeof(struct gnet_stats_basic),
                              .optional = true, },
+    [TCA_STATS_QUEUE] = { .type = NL_A_UNSPEC,
+                          .min_len = sizeof(struct gnet_stats_queue),
+                          .optional = true, },
 };
 
+static int
+nl_parse_action_stats(struct nlattr *act_stats,
+                      struct ovs_flow_stats *stats_sw,
+                      struct ovs_flow_stats *stats_hw,
+                      struct ovs_flow_stats *stats_dropped)
+{
+    struct nlattr *stats_attrs[ARRAY_SIZE(stats_policy)];
+    struct gnet_stats_basic bs_all, bs_sw, bs_hw;
+    const struct gnet_stats_queue *qs;
+
+    if (!nl_parse_nested(act_stats, stats_policy, stats_attrs,
+                         ARRAY_SIZE(stats_policy))) {
+        VLOG_ERR_RL(&error_rl, "Failed to parse action stats policy");
+        return EPROTO;
+    }
+
+    memcpy(&bs_all,
+           nl_attr_get_unspec(stats_attrs[TCA_STATS_BASIC], sizeof bs_all),
+           sizeof bs_all);
+    if (stats_attrs[TCA_STATS_BASIC_HW]) {
+        memcpy(&bs_hw, nl_attr_get_unspec(stats_attrs[TCA_STATS_BASIC_HW],
+                                          sizeof bs_hw),
+               sizeof bs_hw);
+
+        bs_sw.packets = bs_all.packets - bs_hw.packets;
+        bs_sw.bytes = bs_all.bytes - bs_hw.bytes;
+    } else {
+        bs_sw.packets = bs_all.packets;
+        bs_sw.bytes = bs_all.bytes;
+    }
+
+    if (bs_sw.packets > get_32aligned_u64(&stats_sw->n_packets)) {
+        put_32aligned_u64(&stats_sw->n_packets, bs_sw.packets);
+        put_32aligned_u64(&stats_sw->n_bytes, bs_sw.bytes);
+    }
+
+    if (stats_attrs[TCA_STATS_BASIC_HW]
+        && bs_hw.packets > get_32aligned_u64(&stats_hw->n_packets)) {
+        put_32aligned_u64(&stats_hw->n_packets, bs_hw.packets);
+        put_32aligned_u64(&stats_hw->n_bytes, bs_hw.bytes);
+    }
+
+    if (stats_dropped && stats_attrs[TCA_STATS_QUEUE]) {
+        qs = nl_attr_get_unspec(stats_attrs[TCA_STATS_QUEUE], sizeof *qs);
+        put_32aligned_u64(&stats_dropped->n_packets, qs->drops);
+    }
+
+    return 0;
+}
+
 static int
 nl_parse_single_action(struct nlattr *action, struct tc_flower *flower,
                        bool terse)
 {
     struct nlattr *act_options;
-    struct nlattr *act_stats;
     struct nlattr *act_cookie;
     const char *act_kind;
     struct nlattr *action_attrs[ARRAY_SIZE(act_policy)];
-    struct nlattr *stats_attrs[ARRAY_SIZE(stats_policy)];
-    struct ovs_flow_stats *stats_sw = &flower->stats_sw;
-    struct ovs_flow_stats *stats_hw = &flower->stats_hw;
-    struct gnet_stats_basic bs_all, bs_hw, bs_sw;
     int err = 0;
 
     if (!nl_parse_nested(action, act_policy, action_attrs,
@@ -1824,41 +1886,25 @@  nl_parse_single_action(struct nlattr *action, struct tc_flower *flower,
         flower->act_cookie.len = nl_attr_get_size(act_cookie);
     }
 
-    act_stats = action_attrs[TCA_ACT_STATS];
-
-    if (!nl_parse_nested(act_stats, stats_policy, stats_attrs,
-                         ARRAY_SIZE(stats_policy))) {
-        VLOG_ERR_RL(&error_rl, "failed to parse action stats policy");
-        return EPROTO;
-    }
-
-    memcpy(&bs_all,
-           nl_attr_get_unspec(stats_attrs[TCA_STATS_BASIC], sizeof bs_all),
-           sizeof bs_all);
-    if (stats_attrs[TCA_STATS_BASIC_HW]) {
-        memcpy(&bs_hw, nl_attr_get_unspec(stats_attrs[TCA_STATS_BASIC_HW],
-                                          sizeof bs_hw),
-               sizeof bs_hw);
-
-        bs_sw.packets = bs_all.packets - bs_hw.packets;
-        bs_sw.bytes = bs_all.bytes - bs_hw.bytes;
-    } else {
-        bs_sw.packets = bs_all.packets;
-        bs_sw.bytes = bs_all.bytes;
-    }
+    return nl_parse_action_stats(action_attrs[TCA_ACT_STATS],
+                                 &flower->stats_sw, &flower->stats_hw, NULL);
+}
 
-    if (bs_sw.packets > get_32aligned_u64(&stats_sw->n_packets)) {
-        put_32aligned_u64(&stats_sw->n_packets, bs_sw.packets);
-        put_32aligned_u64(&stats_sw->n_bytes, bs_sw.bytes);
-    }
+int
+tc_parse_action_stats(struct nlattr *action, struct ovs_flow_stats *stats_sw,
+                      struct ovs_flow_stats *stats_hw,
+                      struct ovs_flow_stats *stats_dropped)
+{
+    struct nlattr *action_attrs[ARRAY_SIZE(act_policy)];
 
-    if (stats_attrs[TCA_STATS_BASIC_HW]
-        && bs_hw.packets > get_32aligned_u64(&stats_hw->n_packets)) {
-        put_32aligned_u64(&stats_hw->n_packets, bs_hw.packets);
-        put_32aligned_u64(&stats_hw->n_bytes, bs_hw.bytes);
+    if (!nl_parse_nested(action, act_policy, action_attrs,
+                         ARRAY_SIZE(act_policy))) {
+        VLOG_ERR_RL(&error_rl, "Failed to parse single action options");
+        return EPROTO;
     }
 
-    return 0;
+    return nl_parse_action_stats(action_attrs[TCA_ACT_STATS], stats_sw,
+                                 stats_hw, stats_dropped);
 }
 
 #define TCA_ACT_MIN_PRIO 1
diff --git a/lib/tc.h b/lib/tc.h
index 751bec891..30cd2f7ff 100644
--- a/lib/tc.h
+++ b/lib/tc.h
@@ -23,6 +23,7 @@ 
 #include <linux/pkt_cls.h>
 #include <linux/pkt_sched.h>
 
+#include "netlink.h"
 #include "netlink-socket.h"
 #include "odp-netlink.h"
 #include "openvswitch/ofpbuf.h"
@@ -80,6 +81,8 @@  tc_get_minor(unsigned int handle)
 
 struct tcmsg *tc_make_request(int ifindex, int type,
                               unsigned int flags, struct ofpbuf *);
+struct tcamsg *tc_make_action_request(int type, unsigned int flags,
+                                      struct ofpbuf *request);
 int tc_transact(struct ofpbuf *request, struct ofpbuf **replyp);
 int tc_add_del_qdisc(int ifindex, bool add, uint32_t block_id,
                      enum tc_qdisc_hook hook);
@@ -375,5 +378,9 @@  int parse_netlink_to_tc_flower(struct ofpbuf *reply,
                                bool terse);
 int parse_netlink_to_tc_chain(struct ofpbuf *reply, uint32_t *chain);
 void tc_set_policy(const char *policy);
+int tc_parse_action_stats(struct nlattr *action,
+                          struct ovs_flow_stats *stats_sw,
+                          struct ovs_flow_stats *stats_hw,
+                          struct ovs_flow_stats *stats_dropped);
 
 #endif /* tc.h */