Add information about time left before timeouts to flow dumps.
authorBen Pfaff <blp@nicira.com>
Tue, 7 Feb 2012 18:13:52 +0000 (10:13 -0800)
committerBen Pfaff <blp@nicira.com>
Tue, 7 Feb 2012 23:48:53 +0000 (15:48 -0800)
The "learn" action is useful for MAC learning, but until now there has been
no way to find out through OpenFlow how much time remains before a MAC
learning entry (a learned flow) expires.  This commit adds that ability.

Feature #7193.
Signed-off-by: Ben Pfaff <blp@nicira.com>
12 files changed:
NEWS
include/openflow/nicira-ext.h
lib/learning-switch.c
lib/ofp-print.c
lib/ofp-util.c
lib/ofp-util.h
ofproto/ofproto.c
tests/ofp-print.at
tests/ofproto-dpif.at
tests/ofproto-macros.at
utilities/ovs-ofctl.8.in
utilities/ovs-ofctl.c

diff --git a/NEWS b/NEWS
index 193cf1c..c153370 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -18,6 +18,8 @@ post-v1.5.0
       table, with configurable policy for evicting flows upon
       overflow.  See the Flow_Table table in ovs-vswitch.conf.db(5)
       for more information.
+    - OpenFlow:
+      - NXM flow dumps now include times elapsed toward idle and hard timeouts.
     - ofproto-provider interface:
         - "struct rule" has a new member "used" that ofproto implementations
           should maintain by updating with ofproto_rule_update_used().
index 16fa263..b0c8e4e 100644 (file)
@@ -108,7 +108,12 @@ enum nicira_type {
 
     /* Alternative PACKET_IN message formats. */
     NXT_SET_PACKET_IN_FORMAT = 16, /* Set Packet In format. */
-    NXT_PACKET_IN = 17             /* Nicira Packet In. */
+    NXT_PACKET_IN = 17,            /* Nicira Packet In. */
+
+    /* Are the idle_age and hard_age members in struct nx_flow_stats supported?
+     * If so, the switch does not reply to this message (which consists only of
+     * a "struct nicira_header").  If not, the switch sends an error reply. */
+    NXT_FLOW_AGE = 18,
 };
 
 /* Header for Nicira vendor stats request and reply messages. */
@@ -1770,7 +1775,27 @@ struct nx_flow_stats_request {
 OFP_ASSERT(sizeof(struct nx_flow_stats_request) == 32);
 
 /* Body for Nicira vendor stats reply of type NXST_FLOW (analogous to
- * OFPST_FLOW reply). */
+ * OFPST_FLOW reply).
+ *
+ * The values of 'idle_age' and 'hard_age' are only meaningful when talking to
+ * a switch that implements the NXT_FLOW_AGE extension.  Zero means that the
+ * true value is unknown, perhaps because hardware does not track the value.
+ * (Zero is also the value that one should ordinarily expect to see talking to
+ * a switch that does not implement NXT_FLOW_AGE, since those switches zero the
+ * padding bytes that these fields replaced.)  A nonzero value X represents X-1
+ * seconds.  A value of 65535 represents 65534 or more seconds.
+ *
+ * 'idle_age' is the number of seconds that the flow has been idle, that is,
+ * the number of seconds since a packet passed through the flow.  'hard_age' is
+ * the number of seconds since the flow was last modified (e.g. OFPFC_MODIFY or
+ * OFPFC_MODIFY_STRICT).  (The 'duration_*' fields are the elapsed time since
+ * the flow was added, regardless of subsequent modifications.)
+ *
+ * For a flow with an idle or hard timeout, 'idle_age' or 'hard_age',
+ * respectively, will ordinarily be smaller than the timeout, but flow
+ * expiration times are only approximate and so one must be prepared to
+ * tolerate expirations that occur somewhat early or late.
+ */
 struct nx_flow_stats {
     ovs_be16 length;          /* Length of this entry. */
     uint8_t table_id;         /* ID of table flow came from. */
@@ -1783,7 +1808,8 @@ struct nx_flow_stats {
     ovs_be16 idle_timeout;    /* Number of seconds idle before expiration. */
     ovs_be16 hard_timeout;    /* Number of seconds before expiration. */
     ovs_be16 match_len;       /* Length of nx_match. */
-    uint8_t pad2[4];          /* Align to 64 bits. */
+    ovs_be16 idle_age;        /* Seconds since last packet, plus one. */
+    ovs_be16 hard_age;        /* Seconds since last modification, plus one. */
     ovs_be64 cookie;          /* Opaque controller-issued identifier. */
     ovs_be64 packet_count;    /* Number of packets, UINT64_MAX if unknown. */
     ovs_be64 byte_count;      /* Number of bytes, UINT64_MAX if unknown. */
index 60cf731..922db54 100644 (file)
@@ -264,6 +264,7 @@ lswitch_process_packet(struct lswitch *sw, struct rconn *rconn,
     case OFPUTIL_NXT_PACKET_IN:
     case OFPUTIL_NXT_FLOW_MOD:
     case OFPUTIL_NXT_FLOW_REMOVED:
+    case OFPUTIL_NXT_FLOW_AGE:
     case OFPUTIL_NXST_FLOW_REQUEST:
     case OFPUTIL_NXST_AGGREGATE_REQUEST:
     case OFPUTIL_NXST_FLOW_REPLY:
index eb45480..5eb9f8a 100644 (file)
@@ -1022,7 +1022,7 @@ ofp_print_flow_stats_reply(struct ds *string, const struct ofp_header *oh)
         struct ofputil_flow_stats fs;
         int retval;
 
-        retval = ofputil_decode_flow_stats_reply(&fs, &b);
+        retval = ofputil_decode_flow_stats_reply(&fs, &b, true);
         if (retval) {
             if (retval != EOF) {
                 ds_put_cstr(string, " ***parse error***");
@@ -1044,6 +1044,12 @@ ofp_print_flow_stats_reply(struct ds *string, const struct ofp_header *oh)
         if (fs.hard_timeout != OFP_FLOW_PERMANENT) {
             ds_put_format(string, "hard_timeout=%"PRIu16",", fs.hard_timeout);
         }
+        if (fs.idle_age >= 0) {
+            ds_put_format(string, "idle_age=%d,", fs.idle_age);
+        }
+        if (fs.hard_age >= 0 && fs.hard_age != fs.duration_sec) {
+            ds_put_format(string, "hard_age=%d,", fs.hard_age);
+        }
 
         cls_rule_format(&fs.rule, string);
         if (string->string[string->length - 1] != ' ') {
@@ -1455,6 +1461,9 @@ ofp_to_string__(const struct ofp_header *oh,
 
         break;
 
+    case OFPUTIL_NXT_FLOW_AGE:
+        break;
+
     case OFPUTIL_NXST_AGGREGATE_REPLY:
         ofp_print_stats_reply(string, oh);
         ofp_print_nxst_aggregate_reply(string, msg);
index ef8c470..ed98251 100644 (file)
@@ -396,6 +396,10 @@ ofputil_decode_vendor(const struct ofp_header *oh, size_t length,
         { OFPUTIL_NXT_FLOW_MOD_TABLE_ID, OFP10_VERSION,
           NXT_FLOW_MOD_TABLE_ID, "NXT_FLOW_MOD_TABLE_ID",
           sizeof(struct nx_flow_mod_table_id), 0 },
+
+        { OFPUTIL_NXT_FLOW_AGE, OFP10_VERSION,
+          NXT_FLOW_AGE, "NXT_FLOW_AGE",
+          sizeof(struct nicira_header), 0 },
     };
 
     static const struct ofputil_msg_category nxt_category = {
@@ -1307,11 +1311,18 @@ ofputil_encode_flow_stats_request(const struct ofputil_flow_stats_request *fsr,
  * iterates through the replies.  The caller must initially leave 'msg''s layer
  * pointers null and not modify them between calls.
  *
+ * Most switches don't send the values needed to populate fs->idle_age and
+ * fs->hard_age, so those members will usually be set to 0.  If the switch from
+ * which 'msg' originated is known to implement NXT_FLOW_AGE, then pass
+ * 'flow_age_extension' as true so that the contents of 'msg' determine the
+ * 'idle_age' and 'hard_age' members in 'fs'.
+ *
  * Returns 0 if successful, EOF if no replies were left in this 'msg',
  * otherwise a positive errno value. */
 int
 ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *fs,
-                                struct ofpbuf *msg)
+                                struct ofpbuf *msg,
+                                bool flow_age_extension)
 {
     const struct ofputil_msg_type *type;
     int code;
@@ -1362,6 +1373,8 @@ ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *fs,
         fs->duration_nsec = ntohl(ofs->duration_nsec);
         fs->idle_timeout = ntohs(ofs->idle_timeout);
         fs->hard_timeout = ntohs(ofs->hard_timeout);
+        fs->idle_age = -1;
+        fs->hard_age = -1;
         fs->packet_count = ntohll(get_32aligned_be64(&ofs->packet_count));
         fs->byte_count = ntohll(get_32aligned_be64(&ofs->byte_count));
     } else if (code == OFPUTIL_NXST_FLOW_REPLY) {
@@ -1399,6 +1412,16 @@ ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *fs,
         fs->duration_nsec = ntohl(nfs->duration_nsec);
         fs->idle_timeout = ntohs(nfs->idle_timeout);
         fs->hard_timeout = ntohs(nfs->hard_timeout);
+        fs->idle_age = -1;
+        fs->hard_age = -1;
+        if (flow_age_extension) {
+            if (nfs->idle_age) {
+                fs->idle_age = ntohs(nfs->idle_age) - 1;
+            }
+            if (nfs->hard_age) {
+                fs->hard_age = ntohs(nfs->hard_age) - 1;
+            }
+        }
         fs->packet_count = ntohll(nfs->packet_count);
         fs->byte_count = ntohll(nfs->byte_count);
     } else {
@@ -1467,8 +1490,13 @@ ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *fs,
         nfs->priority = htons(fs->rule.priority);
         nfs->idle_timeout = htons(fs->idle_timeout);
         nfs->hard_timeout = htons(fs->hard_timeout);
+        nfs->idle_age = htons(fs->idle_age < 0 ? 0
+                              : fs->idle_age < UINT16_MAX ? fs->idle_age + 1
+                              : UINT16_MAX);
+        nfs->hard_age = htons(fs->hard_age < 0 ? 0
+                              : fs->hard_age < UINT16_MAX ? fs->hard_age + 1
+                              : UINT16_MAX);
         nfs->match_len = htons(nx_put_match(msg, &fs->rule, 0, 0));
-        memset(nfs->pad2, 0, sizeof nfs->pad2);
         nfs->cookie = fs->cookie;
         nfs->packet_count = htonll(fs->packet_count);
         nfs->byte_count = htonll(fs->byte_count);
index 422c14a..f9885a3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009, 2010, 2011 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -79,6 +79,7 @@ enum ofputil_msg_code {
     OFPUTIL_NXT_FLOW_REMOVED,
     OFPUTIL_NXT_SET_PACKET_IN_FORMAT,
     OFPUTIL_NXT_PACKET_IN,
+    OFPUTIL_NXT_FLOW_AGE,
 
     /* NXST_* stat requests. */
     OFPUTIL_NXST_FLOW_REQUEST,
@@ -183,6 +184,8 @@ struct ofputil_flow_stats {
     uint32_t duration_nsec;
     uint16_t idle_timeout;
     uint16_t hard_timeout;
+    int idle_age;               /* Seconds since last packet, -1 if unknown. */
+    int hard_age;               /* Seconds since last change, -1 if unknown. */
     uint64_t packet_count;      /* Packet count, UINT64_MAX if unknown. */
     uint64_t byte_count;        /* Byte count, UINT64_MAX if unknown. */
     union ofp_action *actions;
@@ -190,7 +193,8 @@ struct ofputil_flow_stats {
 };
 
 int ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *,
-                                    struct ofpbuf *msg);
+                                    struct ofpbuf *msg,
+                                    bool flow_age_extension);
 void ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *,
                                      struct list *replies);
 
index c35d440..473ae41 100644 (file)
@@ -2174,9 +2174,10 @@ handle_port_stats_request(struct ofconn *ofconn,
 }
 
 static void
-calc_flow_duration__(long long int start, uint32_t *sec, uint32_t *nsec)
+calc_flow_duration__(long long int start, long long int now,
+                     uint32_t *sec, uint32_t *nsec)
 {
-    long long int msecs = time_msec() - start;
+    long long int msecs = now - start;
     *sec = msecs / 1000;
     *nsec = (msecs % 1000) * (1000 * 1000);
 }
@@ -2337,6 +2338,16 @@ collect_rules_strict(struct ofproto *ofproto, uint8_t table_id,
     return 0;
 }
 
+/* Returns 'age_ms' (a duration in milliseconds), converted to seconds and
+ * forced into the range of a uint16_t. */
+static int
+age_secs(long long int age_ms)
+{
+    return (age_ms < 0 ? 0
+            : age_ms >= UINT16_MAX * 1000 ? UINT16_MAX
+            : (unsigned int) age_ms / 1000);
+}
+
 static enum ofperr
 handle_flow_stats_request(struct ofconn *ofconn,
                           const struct ofp_stats_msg *osm)
@@ -2362,15 +2373,18 @@ handle_flow_stats_request(struct ofconn *ofconn,
 
     ofputil_start_stats_reply(osm, &replies);
     LIST_FOR_EACH (rule, ofproto_node, &rules) {
+        long long int now = time_msec();
         struct ofputil_flow_stats fs;
 
         fs.rule = rule->cr;
         fs.cookie = rule->flow_cookie;
         fs.table_id = rule->table_id;
-        calc_flow_duration__(rule->created, &fs.duration_sec,
+        calc_flow_duration__(rule->created, now, &fs.duration_sec,
                              &fs.duration_nsec);
         fs.idle_timeout = rule->idle_timeout;
         fs.hard_timeout = rule->hard_timeout;
+        fs.idle_age = age_secs(now - rule->used);
+        fs.hard_age = age_secs(now - rule->modified);
         ofproto->ofproto_class->rule_get_stats(rule, &fs.packet_count,
                                                &fs.byte_count);
         fs.actions = rule->actions;
@@ -2930,7 +2944,8 @@ ofproto_rule_send_removed(struct rule *rule, uint8_t reason)
     fr.rule = rule->cr;
     fr.cookie = rule->flow_cookie;
     fr.reason = reason;
-    calc_flow_duration__(rule->created, &fr.duration_sec, &fr.duration_nsec);
+    calc_flow_duration__(rule->created, time_msec(),
+                         &fr.duration_sec, &fr.duration_nsec);
     fr.idle_timeout = rule->idle_timeout;
     rule->ofproto->ofproto_class->rule_get_stats(rule, &fr.packet_count,
                                                  &fr.byte_count);
@@ -3199,6 +3214,10 @@ handle_openflow__(struct ofconn *ofconn, const struct ofpbuf *msg)
     case OFPUTIL_NXT_FLOW_MOD:
         return handle_flow_mod(ofconn, oh);
 
+    case OFPUTIL_NXT_FLOW_AGE:
+        /* Nothing to do. */
+        return 0;
+
         /* Statistics requests. */
     case OFPUTIL_OFPST_DESC_REQUEST:
         return handle_desc_stats_request(ofconn, msg->data);
index 85562b6..2595900 100644 (file)
@@ -798,7 +798,7 @@ AT_CHECK([ovs-ofctl ofp-print "\
 a8 00 02 00 00 0c 01 06 00 00 12 02 09 e7 00 00 \
 14 02 00 00 00 00 00 00 00 00 00 08 00 01 00 00 \
 00 88 00 00 00 00 00 03 32 11 62 00 ff ff 00 05 \
-00 00 00 4c 00 00 00 00 00 00 00 00 00 00 00 00 \
+00 00 00 4c 00 03 00 00 00 00 00 00 00 00 00 00 \
 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 3c \
 00 00 00 02 00 03 00 00 02 06 50 54 00 00 00 06 \
 00 00 04 06 50 54 00 00 00 05 00 00 06 02 08 00 \
@@ -806,7 +806,7 @@ a8 00 02 00 00 0c 01 06 00 00 12 02 09 e7 00 00 \
 a8 00 01 00 00 10 04 c0 a8 00 02 00 00 0c 01 06 \
 00 00 12 02 09 e4 00 00 14 02 00 00 00 00 00 00 \
 00 00 00 08 00 01 00 00 00 88 00 00 00 00 00 02 \
-33 f9 aa 00 ff ff 00 05 00 00 00 4c 00 00 00 00 \
+33 f9 aa 00 ff ff 00 05 00 00 00 4c 00 05 00 00 \
 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 \
 00 00 00 00 00 00 00 3c 00 00 00 02 00 01 00 00 \
 02 06 50 54 00 00 00 05 00 00 04 06 50 54 00 00 \
@@ -815,7 +815,7 @@ a8 00 01 00 00 10 04 c0 a8 00 02 00 00 0c 01 06 \
 a8 00 01 00 00 0c 01 06 00 00 12 02 00 00 00 00 \
 14 02 09 e5 00 00 00 00 00 00 00 08 00 03 00 00 \
 00 88 00 00 00 00 00 04 2d 0f a5 00 ff ff 00 05 \
-00 00 00 4c 00 00 00 00 00 00 00 00 00 00 00 00 \
+00 00 00 4c 00 01 00 00 00 00 00 00 00 00 00 00 \
 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 3c \
 00 00 00 02 00 03 00 00 02 06 50 54 00 00 00 06 \
 00 00 04 06 50 54 00 00 00 05 00 00 06 02 08 00 \
@@ -823,7 +823,7 @@ a8 00 01 00 00 0c 01 06 00 00 12 02 00 00 00 00 \
 a8 00 01 00 00 10 04 c0 a8 00 02 00 00 0c 01 06 \
 00 00 12 02 09 e3 00 00 14 02 00 00 00 00 00 00 \
 00 00 00 08 00 01 00 00 00 88 00 00 00 00 00 02 \
-34 73 bc 00 ff ff 00 05 00 00 00 4c 00 00 00 00 \
+34 73 bc 00 ff ff 00 05 00 0a 00 4c 00 03 00 03 \
 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 \
 00 00 00 00 00 00 00 3c 00 00 00 02 00 03 00 00 \
 02 06 50 54 00 00 00 06 00 00 04 06 50 54 00 00 \
@@ -920,10 +920,10 @@ ff ff 00 18 00 00 23 20 00 07 00 1f 00 01 00 04 \
 "], [0],
 [[NXST_FLOW reply (xid=0x4):
  cookie=0x0, duration=1.048s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,priority=65535,tcp,in_port=3,vlan_tci=0x0000,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:06,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,tp_src=2535,tp_dst=0 actions=output:1
- cookie=0x0, duration=3.84s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,priority=65535,tcp,in_port=3,vlan_tci=0x0000,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:06,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,tp_src=2532,tp_dst=0 actions=output:1
- cookie=0x0, duration=2.872s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,priority=65535,tcp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:06,dl_dst=50:54:00:00:00:05,nw_src=192.168.0.2,nw_dst=192.168.0.1,nw_tos=0,tp_src=0,tp_dst=2533 actions=output:3
- cookie=0x0, duration=4.756s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,priority=65535,tcp,in_port=3,vlan_tci=0x0000,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:06,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,tp_src=2531,tp_dst=0 actions=output:1
- cookie=0x0, duration=2.88s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,priority=65535,tcp,in_port=3,vlan_tci=0x0000,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:06,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,tp_src=2533,tp_dst=0 actions=output:1
+ cookie=0x0, duration=3.84s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,idle_age=2,priority=65535,tcp,in_port=3,vlan_tci=0x0000,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:06,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,tp_src=2532,tp_dst=0 actions=output:1
+ cookie=0x0, duration=2.872s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,idle_age=4,priority=65535,tcp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:06,dl_dst=50:54:00:00:00:05,nw_src=192.168.0.2,nw_dst=192.168.0.1,nw_tos=0,tp_src=0,tp_dst=2533 actions=output:3
+ cookie=0x0, duration=4.756s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,idle_age=0,priority=65535,tcp,in_port=3,vlan_tci=0x0000,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:06,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,tp_src=2531,tp_dst=0 actions=output:1
+ cookie=0x0, duration=2.88s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,hard_timeout=10,idle_age=2,priority=65535,tcp,in_port=3,vlan_tci=0x0000,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:06,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,tp_src=2533,tp_dst=0 actions=output:1
  cookie=0x0, duration=5.672s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,priority=65535,tcp,in_port=3,vlan_tci=0x0000,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:06,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,tp_src=2530,tp_dst=0 actions=output:1
  cookie=0x0, duration=1.04s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,priority=65535,tcp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:06,dl_dst=50:54:00:00:00:05,nw_src=192.168.0.2,nw_dst=192.168.0.1,nw_tos=0,tp_src=0,tp_dst=2535 actions=output:3
  cookie=0x0, duration=1.952s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,priority=65535,tcp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:06,dl_dst=50:54:00:00:00:05,nw_src=192.168.0.2,nw_dst=192.168.0.1,nw_tos=0,tp_src=0,tp_dst=2534 actions=output:3
index 0f5b1ce..f5c1358 100644 (file)
@@ -1035,3 +1035,93 @@ echo $n_recs
 AT_CHECK([test $n_recs -ge 13])
 
 AT_CLEANUP
+
+AT_SETUP([idle_age and hard_age increase over time])
+OVS_VSWITCHD_START
+
+# get_ages DURATION HARD IDLE
+#
+# Fetch the flow duration, hard age, and idle age into the variables
+# whose names are given as arguments.  Rounds DURATION down to the
+# nearest integer.  If hard_age doesn't appear in the output, sets
+# HARD to "none".  If idle_age doesn't appear in the output, sets IDLE
+# to 0.
+get_ages () {
+    AT_CHECK([ovs-ofctl dump-flows br0], [0], [stdout])
+
+    duration=`sed -n 's/.*duration=\([[0-9]]*\)\(\.[[0-9]]*\)\{0,1\}s.*/\1/p' stdout`
+    AT_CHECK([[expr X"$duration" : 'X[0-9][0-9]*$']], [0], [ignore])
+    AS_VAR_COPY([$1], [duration])
+
+    hard=`sed -n 's/.*hard_age=\([[0-9]]*\),.*/\1/p' stdout`
+    if test X"$hard" = X; then
+        hard=none
+    else
+        AT_CHECK([[expr X"$hard" : 'X[0-9][0-9]*$']], [0], [ignore])
+    fi
+    AS_VAR_COPY([$2], [hard])
+
+    idle=`sed -n 's/.*idle_age=\([[0-9]]*\),.*/\1/p' stdout`
+    if test X"$idle" = X; then
+        idle=0
+    else
+        AT_CHECK([[expr X"$idle" : 'X[0-9][0-9]*$']], [0], [ignore])
+    fi
+    AS_VAR_COPY([$3], [idle])
+}
+
+# Add a flow and get its initial hard and idle age.
+AT_CHECK([ovs-ofctl add-flow br0 hard_timeout=199,idle_timeout=188,actions=drop])
+get_ages duration1 hard1 idle1
+
+# Warp time forward by 10 seconds, then modify the flow's actions.
+ovs-appctl time/warp 10000
+get_ages duration2 hard2 idle2
+AT_CHECK([ovs-ofctl mod-flows br0 actions=flood])
+
+# Warp time forward by 10 seconds.
+ovs-appctl time/warp 10000
+get_ages duration3 hard3 idle3
+
+# Warp time forward 10 more seconds, then pass some packets through the flow,
+# then warp forward a few more times because idle times are only updated
+# occasionally.
+ovs-appctl time/warp 10000
+ovs-appctl netdev-dummy/receive br0 'in_port(0),eth(src=50:54:00:00:00:07,dst=50:54:00:00:00:05),eth_type(0x0800),ipv4(src=192.168.0.2,dst=192.168.0.1,proto=6,tos=0,ttl=64,frag=no),tcp(src=80,dst=1234)'
+ovs-appctl time/warp 1000
+ovs-appctl time/warp 1000
+ovs-appctl time/warp 1000
+get_ages duration4 hard4 idle4
+
+printf "duration: %4s => %4s => %4s => %4s\n" $duration1 $duration2 $duration3 $duration4
+printf "hard_age: %4s => %4s => %4s => %4s\n" $hard1 $hard2 $hard3 $hard4
+printf "idle_age: %4s => %4s => %4s => %4s\n" $idle1 $idle2 $idle3 $idle4
+
+# Duration should increase steadily over time.
+AT_CHECK([test $duration1 -lt $duration2])
+AT_CHECK([test $duration2 -lt $duration3])
+AT_CHECK([test $duration3 -lt $duration4])
+
+# Hard age should be "none" initially because it's the same as flow_duration,
+# then it should increase.
+AT_CHECK([test $hard1 = none])
+AT_CHECK([test $hard2 = none])
+AT_CHECK([test $hard3 != none])
+AT_CHECK([test $hard4 != none])
+AT_CHECK([test $hard3 -lt $hard4])
+
+# Idle age should increase from 1 to 2 to 3, then decrease.
+AT_CHECK([test $idle1 -lt $idle2])
+AT_CHECK([test $idle2 -lt $idle3])
+AT_CHECK([test $idle3 -gt $idle4])
+
+# Check some invariant relationships.
+AT_CHECK([test $duration1 = $idle1])
+AT_CHECK([test $duration2 = $idle2])
+AT_CHECK([test $duration3 = $idle3])
+AT_CHECK([test $idle3 -gt $hard3])
+AT_CHECK([test $idle4 -lt $hard4])
+AT_CHECK([test $hard4 -lt $duration4])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
index 3656ecf..1184d26 100644 (file)
@@ -10,6 +10,8 @@ s/ cookie=0x0,//
 s/ table=0,//
 s/ n_packets=0,//
 s/ n_bytes=0,//
+s/idle_age=[0-9]*,//
+s/hard_age=[0-9]*,//
 '
 }]
 m4_divert_pop([PREPARE_TESTS])
index 1c1b709..d72d8ab 100644 (file)
@@ -1071,19 +1071,13 @@ If set, a matching flow must include an output action to \fIport\fR.
 .
 The \fBdump\-tables\fR and \fBdump\-aggregate\fR commands print information 
 about the entries in a datapath's tables.  Each line of output is a 
-unique flow entry, which begins with some common information:
+flow entry as described in \fBFlow Syntax\fR, above, plus some
+additional fields:
 .
-.IP \fBduration\fR
-The number of seconds the entry has been in the table.
-.
-.IP \fBtable\fR
-The table that contains the flow.  When a packet arrives, the switch 
-begins searching for an entry at the lowest numbered table.  Tables are 
-numbered as shown by the \fBdump\-tables\fR command.
-.
-.IP \fBpriority\fR
-The priority of the entry in relation to other entries within the same
-table.  A higher value will match before a lower one.
+.IP \fBduration=\fIsecs\fR
+The time, in seconds, that the entry has been in the table.
+\fIsecs\fR includes as much precision as the switch provides, possibly
+to nanosecond resolution.
 .
 .IP \fBn_packets\fR
 The number of packets that have matched the entry.
@@ -1092,9 +1086,23 @@ The number of packets that have matched the entry.
 The total number of bytes from packets that have matched the entry.
 .
 .PP
-The rest of the line consists of a description of the flow entry as 
-described in \fBFlow Syntax\fR, above.
-.
+The following additional fields are included only if the switch is
+Open vSwitch 1.6 or later and the NXM flow format is used to dump the
+flow (see the description of the \fB\-\-flow-format\fR option below).
+The values of these additional fields are approximations only and in
+particular \fBidle_age\fR will sometimes become nonzero even for busy
+flows.
+.
+.IP \fBhard_age=\fIsecs\fR
+The integer number of seconds since the flow was added or modified.
+\fBhard_age\fR is displayed only if it differs from the integer part
+of \fBduration\fR.  (This is separate from \fBduration\fR because
+\fBmod\-flows\fR restarts the \fBhard_timeout\fR timer without zeroing
+\fBduration\fR.)
+.
+.IP \fBidle_age=\fIsecs\fR
+The integer number of seconds that have passed without any packets
+passing through the flow.
 .
 .SH OPTIONS
 .TP
index 7df0a90..26bc1c3 100644 (file)
@@ -1354,7 +1354,7 @@ read_flows_from_switch(struct vconn *vconn, enum nx_flow_format flow_format,
                 struct ofputil_flow_stats fs;
                 int retval;
 
-                retval = ofputil_decode_flow_stats_reply(&fs, reply);
+                retval = ofputil_decode_flow_stats_reply(&fs, reply, false);
                 if (retval) {
                     if (retval != EOF) {
                         ovs_fatal(0, "parse error in reply");