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().
/* 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. */
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. */
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. */
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:
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***");
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] != ' ') {
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);
{ 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 = {
* 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;
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) {
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 {
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);
/*
- * 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.
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,
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;
};
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);
}
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);
}
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)
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;
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);
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);
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 \
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 \
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 \
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 \
"], [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
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
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])
.
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.
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
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");