[ovs-dev] RFC: A plan for stats.

Ethan Jackson ethan at nicira.com
Sat Sep 20 18:00:39 UTC 2014


I'm doing some performance testing which heavily stresses the OVS DPDK slow
path, and notice we spend a ridiculous amount of CPU time on the rule_dpif stats
mutex.  For my test case, by commenting out the mutex I literally doubled the
performance.  I'm in a bit of a rush, so as a short term hack, I simply copied
over the per-thread stats code which dpif-netdev uses, but it got me thinking of
a general plan for how we should handle stats in userspace which I'll outline
here:

To start, I think the ovsthread_stats construction needs to be pulled into it's
own library and made higher level.  Right now it's a general mechanism for
storing arbitrary per thread data.  I think we really want something which
implements an API something like "get_stats", "credit_stats", "clear_stats",
etc.  That could be used directly by both ofproto-dpif and dpif-netdev very
cleanly.  You'll see in the hacked together patch I've included below, it's
basically a direct copy of all the relevant dpif-netdev code.

That done, I think we can actually get rid of the flow stats mutex by using a
technique similar to what we do for cmaps.  We're guaranteed that only one
writer ever touches a particular bucket, so it should be sufficient to ensure
all of the data fits on a particular cache line, and that we update a sequence
number to notify readers of changes.

Thoughts?

Ethan


---
 ofproto/ofproto-dpif.c | 125 ++++++++++++++++++++++++++++++++-----------------
 1 file changed, 83 insertions(+), 42 deletions(-)

diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 6a59098..e9f4f16 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -25,10 +25,10 @@
 #include "bond.h"
 #include "bundle.h"
 #include "byte-order.h"
+#include "cfm.h"
 #include "connectivity.h"
 #include "connmgr.h"
 #include "coverage.h"
-#include "cfm.h"
 #include "dpif.h"
 #include "dynamic-string.h"
 #include "fail-open.h"
@@ -44,13 +44,13 @@
 #include "netdev.h"
 #include "netlink.h"
 #include "nx-match.h"
-#include "odp-util.h"
 #include "odp-execute.h"
-#include "ofp-util.h"
-#include "ofpbuf.h"
+#include "odp-util.h"
 #include "ofp-actions.h"
 #include "ofp-parse.h"
 #include "ofp-print.h"
+#include "ofp-util.h"
+#include "ofpbuf.h"
 #include "ofproto-dpif-ipfix.h"
 #include "ofproto-dpif-mirror.h"
 #include "ofproto-dpif-monitor.h"
@@ -58,6 +58,7 @@
 #include "ofproto-dpif-sflow.h"
 #include "ofproto-dpif-upcall.h"
 #include "ofproto-dpif-xlate.h"
+#include "ovs-thread.h"
 #include "poll-loop.h"
 #include "seq.h"
 #include "simap.h"
@@ -76,6 +77,13 @@ COVERAGE_DEFINE(packet_in_overflow);
 
 struct flow_miss;
 
+struct rule_dpif_stats {
+    struct ovs_mutex mutex;
+    uint64_t n_packets;
+    uint64_t n_bytes;
+    long long int used;
+};
+
 struct rule_dpif {
     struct rule up;
 
@@ -83,8 +91,7 @@ struct rule_dpif {
      *
      *   - Do include packets and bytes from datapath flows which have not
      *   recently been processed by a revalidator. */
-    struct ovs_mutex stats_mutex;
-    struct dpif_flow_stats stats OVS_GUARDED;
+    struct ovsthread_stats stats;
 
     /* If non-zero then the recirculation id that has
      * been allocated for use with this rule.
@@ -96,8 +103,7 @@ struct rule_dpif {
 /* RULE_CAST() depends on this. */
 BUILD_ASSERT_DECL(offsetof(struct rule_dpif, up) == 0);
 
-static void rule_get_stats(struct rule *, uint64_t *packets, uint64_t *bytes,
-                           long long int *used);
+static void rule_get_stats(struct rule_dpif *, struct dpif_flow_stats *);
 static struct rule_dpif *rule_dpif_cast(const struct rule *);
 static void rule_expire(struct rule_dpif *);
 
@@ -3425,13 +3431,10 @@ rule_expire(struct rule_dpif *rule)
     }
 
     if (reason < 0 && idle_timeout) {
-        long long int used;
+        struct dpif_flow_stats stats;
 
-        ovs_mutex_lock(&rule->stats_mutex);
-        used = rule->stats.used;
-        ovs_mutex_unlock(&rule->stats_mutex);
-
-        if (now > used + idle_timeout * 1000) {
+        rule_get_stats(rule, &stats);
+        if (now > stats.used + idle_timeout * 1000) {
             reason = OFPRR_IDLE_TIMEOUT;
         }
     }
@@ -3493,15 +3496,26 @@ ofproto_dpif_execute_actions(struct ofproto_dpif *ofproto,
     return error;
 }
 
+static void *
+rule_dpif_stats_new_cb(void)
+{
+    struct rule_dpif_stats *bucket = xzalloc_cacheline(sizeof *bucket);
+    ovs_mutex_init(&bucket->mutex);
+    return bucket;
+}
+
 void
 rule_dpif_credit_stats(struct rule_dpif *rule,
                        const struct dpif_flow_stats *stats)
 {
-    ovs_mutex_lock(&rule->stats_mutex);
-    rule->stats.n_packets += stats->n_packets;
-    rule->stats.n_bytes += stats->n_bytes;
-    rule->stats.used = MAX(rule->stats.used, stats->used);
-    ovs_mutex_unlock(&rule->stats_mutex);
+    struct rule_dpif_stats *bucket;
+
+    bucket = ovsthread_stats_bucket_get(&rule->stats, rule_dpif_stats_new_cb);
+    ovs_mutex_lock(&bucket->mutex);
+    bucket->n_packets += stats->n_packets;
+    bucket->n_bytes += stats->n_bytes;
+    bucket->used = MAX(bucket->used, stats->used);
+    ovs_mutex_unlock(&bucket->mutex);
 }
 
 ovs_be64
@@ -3814,16 +3828,9 @@ rule_dealloc(struct rule *rule_)
 }
 
 static enum ofperr
-rule_construct(struct rule *rule_)
-    OVS_NO_THREAD_SAFETY_ANALYSIS
+rule_construct(struct rule *rule)
 {
-    struct rule_dpif *rule = rule_dpif_cast(rule_);
-    ovs_mutex_init_adaptive(&rule->stats_mutex);
-    rule->stats.n_packets = 0;
-    rule->stats.n_bytes = 0;
-    rule->stats.used = rule->up.modified;
-    rule->recirc_id = 0;
-
+    ovsthread_stats_init(&rule_dpif_cast(rule)->stats);
     return 0;
 }
 
@@ -3848,8 +3855,15 @@ static void
 rule_destruct(struct rule *rule_)
 {
     struct rule_dpif *rule = rule_dpif_cast(rule_);
+    struct rule_dpif_stats *bucket;
+    size_t i;
+
+    OVSTHREAD_STATS_FOR_EACH_BUCKET (bucket, i, &rule->stats) {
+        ovs_mutex_destroy(&bucket->mutex);
+        free_cacheline(bucket);
+    }
+    ovsthread_stats_destroy(&rule->stats);
 
-    ovs_mutex_destroy(&rule->stats_mutex);
     if (rule->recirc_id) {
         struct ofproto_dpif *ofproto = ofproto_dpif_cast(rule->up.ofproto);
 
@@ -3857,17 +3871,39 @@ rule_destruct(struct rule *rule_)
     }
 }
 
+/* TODO make sure you uninit the stats. */
 static void
-rule_get_stats(struct rule *rule_, uint64_t *packets, uint64_t *bytes,
-               long long int *used)
+rule_get_stats(struct rule_dpif *rule, struct dpif_flow_stats *stats)
+    OVS_NO_THREAD_SAFETY_ANALYSIS
 {
-    struct rule_dpif *rule = rule_dpif_cast(rule_);
+    struct rule_dpif_stats *bucket;
+    size_t i;
 
-    ovs_mutex_lock(&rule->stats_mutex);
-    *packets = rule->stats.n_packets;
-    *bytes = rule->stats.n_bytes;
-    *used = rule->stats.used;
-    ovs_mutex_unlock(&rule->stats_mutex);
+    memset(stats, 0, sizeof *stats);
+    /* We're supposed to hold a mutex for rule->up.modified.  Not sure if
+     * that's really true as the only writer is probably ofproto?   At any rate
+     * perhaps it should have it's own mutex?  For now I'm not dealing with it.
+     * Add back the thread safety analysis when fixed. */
+    stats->used = rule->up.modified;
+    OVSTHREAD_STATS_FOR_EACH_BUCKET(bucket, i, &rule->stats) {
+        ovs_mutex_lock(&bucket->mutex);
+        stats->n_packets += bucket->n_packets;
+        stats->n_bytes += bucket->n_bytes;
+        stats->used = MAX(stats->used, bucket->used);
+        ovs_mutex_unlock(&bucket->mutex);
+    }
+}
+
+static void
+rule_get_stats__(struct rule *rule, uint64_t *packets, uint64_t *bytes,
+                 long long int *used)
+{
+    struct dpif_flow_stats stats;
+
+    rule_get_stats(rule_dpif_cast(rule), &stats);
+    *packets = stats.n_packets;
+    *bytes = stats.n_bytes;
+    *used = stats.used;
 }
 
 static void
@@ -3895,10 +3931,15 @@ rule_modify_actions(struct rule *rule_, bool reset_counters)
     struct rule_dpif *rule = rule_dpif_cast(rule_);
 
     if (reset_counters) {
-        ovs_mutex_lock(&rule->stats_mutex);
-        rule->stats.n_packets = 0;
-        rule->stats.n_bytes = 0;
-        ovs_mutex_unlock(&rule->stats_mutex);
+        struct rule_dpif_stats *bucket;
+        size_t i;
+
+        OVSTHREAD_STATS_FOR_EACH_BUCKET (bucket, i, &rule->stats) {
+            ovs_mutex_lock(&bucket->mutex);
+            bucket->n_packets = 0;
+            bucket->n_bytes = 0;
+            ovs_mutex_unlock(&bucket->mutex);
+        }
     }
 
     complete_operation(rule);
@@ -5398,7 +5439,7 @@ const struct ofproto_class ofproto_dpif_class = {
     rule_delete,
     rule_destruct,
     rule_dealloc,
-    rule_get_stats,
+    rule_get_stats__,
     rule_execute,
     NULL,                       /* rule_premodify_actions */
     rule_modify_actions,
-- 
1.8.1.2




More information about the dev mailing list