netdev: Fix carrier status for down interfaces.
[sliver-openvswitch.git] / ofproto / ofproto.c
index 552c6e3..37678a4 100644 (file)
@@ -321,7 +321,7 @@ static const struct ofhooks default_ofhooks;
 static uint64_t pick_datapath_id(const struct ofproto *);
 static uint64_t pick_fallback_dpid(void);
 
-static void ofproto_expire(struct ofproto *);
+static int ofproto_expire(struct ofproto *);
 
 static void update_stats(struct ofproto *, struct rule *,
                          const struct odp_flow_stats *);
@@ -1151,9 +1151,9 @@ ofproto_run1(struct ofproto *p)
     }
 
     if (time_msec() >= p->next_expiration) {
+        int delay = ofproto_expire(p);
+        p->next_expiration = time_msec() + delay;
         COVERAGE_INC(ofproto_expiration);
-        ofproto_expire(p);
-        p->next_expiration = time_msec() + 1000;
     }
 
     if (p->netflow) {
@@ -1396,7 +1396,6 @@ make_ofport(const struct odp_port *odp_port)
     enum netdev_flags flags;
     struct ofport *ofport;
     struct netdev *netdev;
-    bool carrier;
     int error;
 
     memset(&netdev_options, 0, sizeof netdev_options);
@@ -1423,8 +1422,7 @@ make_ofport(const struct odp_port *odp_port)
     netdev_get_flags(netdev, &flags);
     ofport->opp.config = flags & NETDEV_UP ? 0 : OFPPC_PORT_DOWN;
 
-    netdev_get_carrier(netdev, &carrier);
-    ofport->opp.state = carrier ? 0 : OFPPS_LINK_DOWN;
+    ofport->opp.state = netdev_get_carrier(netdev) ? 0 : OFPPS_LINK_DOWN;
 
     netdev_get_features(netdev,
                         &ofport->opp.curr, &ofport->opp.advertised,
@@ -2064,6 +2062,17 @@ rule_create_subrule(struct ofproto *ofproto, struct rule *rule,
     return subrule;
 }
 
+/* Remove 'rule' from 'ofproto' and free up the associated memory:
+ *
+ *   - If 'rule' was installed in the datapath, uninstalls it and updates
+ *     'rule''s statistics (or its super-rule's statistics, if it is a
+ *     subrule), via rule_uninstall().
+ *
+ *   - Removes 'rule' from the classifier.
+ *
+ *   - If 'rule' is a super-rule that has subrules, revalidates (and possibly
+ *     uninstalls and destroys) its subrules, via rule_destroy().
+ */
 static void
 rule_remove(struct ofproto *ofproto, struct rule *rule)
 {
@@ -2201,6 +2210,14 @@ rule_account(struct ofproto *ofproto, struct rule *rule, uint64_t extra_bytes)
     }
 }
 
+/* 'rule' must be an exact-match rule in 'p'.
+ *
+ * If 'rule' is installed in the datapath, uninstalls it and updates's
+ * statistics.  If 'rule' is a subrule, the statistics that are updated are
+ * actually its super-rule's statistics; otherwise 'rule''s own statistics are
+ * updated.
+ *
+ * If 'rule' is not installed, this function has no effect. */
 static void
 rule_uninstall(struct ofproto *p, struct rule *rule)
 {
@@ -3007,17 +3024,6 @@ handle_desc_stats_request(struct ofproto *p, struct ofconn *ofconn,
     return 0;
 }
 
-static void
-count_subrules(struct cls_rule *cls_rule, void *n_subrules_)
-{
-    struct rule *rule = rule_from_cls_rule(cls_rule);
-    int *n_subrules = n_subrules_;
-
-    if (rule->super) {
-        (*n_subrules)++;
-    }
-}
-
 static int
 handle_table_stats_request(struct ofproto *p, struct ofconn *ofconn,
                            struct ofp_stats_request *request)
@@ -3026,12 +3032,17 @@ handle_table_stats_request(struct ofproto *p, struct ofconn *ofconn,
     struct ofpbuf *msg;
     struct odp_stats dpstats;
     int n_exact, n_subrules, n_wild;
+    struct rule *rule;
 
     msg = start_stats_reply(request, sizeof *ots * 2);
 
     /* Count rules of various kinds. */
     n_subrules = 0;
-    classifier_for_each(&p->cls, CLS_INC_EXACT, count_subrules, &n_subrules);
+    CLASSIFIER_FOR_EACH_EXACT_RULE (rule, struct rule, cr, &p->cls) {
+        if (rule->super) {
+            n_subrules++;
+        }
+    }
     n_exact = classifier_count_exact(&p->cls) - n_subrules;
     n_wild = classifier_count(&p->cls) - classifier_count_exact(&p->cls);
 
@@ -4185,16 +4196,20 @@ handle_odp_msg(struct ofproto *p, struct ofpbuf *packet)
 
 struct expire_cbdata {
     struct ofproto *ofproto;
+    int dp_max_idle;
 };
 
+static int ofproto_dp_max_idle(const struct ofproto *);
 static void ofproto_update_used(struct ofproto *);
 static void rule_expire(struct cls_rule *, void *cbdata);
 
 /* This function is called periodically by ofproto_run().  Its job is to
  * collect updates for the flows that have been installed into the datapath,
  * most importantly when they last were used, and then use that information to
- * expire flows that have not been used recently. */
-static void
+ * expire flows that have not been used recently.
+ *
+ * Returns the number of milliseconds after which it should be called again. */
+static int
 ofproto_expire(struct ofproto *ofproto)
 {
     struct expire_cbdata cbdata;
@@ -4202,9 +4217,14 @@ ofproto_expire(struct ofproto *ofproto)
     /* Update 'used' for each flow in the datapath. */
     ofproto_update_used(ofproto);
 
-    /* Expire idle flows. */
+    /* Expire idle flows.
+     *
+     * A wildcarded flow is idle only when all of its subrules have expired due
+     * to becoming idle, so iterate through the exact-match flows first. */
     cbdata.ofproto = ofproto;
-    classifier_for_each(&ofproto->cls, CLS_INC_ALL, rule_expire, &cbdata);
+    cbdata.dp_max_idle = ofproto_dp_max_idle(ofproto);
+    classifier_for_each(&ofproto->cls, CLS_INC_EXACT, rule_expire, &cbdata);
+    classifier_for_each(&ofproto->cls, CLS_INC_WILD, rule_expire, &cbdata);
 
     /* Let the hook know that we're at a stable point: all outstanding data
      * in existing flows has been accounted to the account_cb.  Thus, the
@@ -4213,6 +4233,8 @@ ofproto_expire(struct ofproto *ofproto)
     if (ofproto->ofhooks->account_checkpoint_cb) {
         ofproto->ofhooks->account_checkpoint_cb(ofproto->aux);
     }
+
+    return MIN(cbdata.dp_max_idle, 1000);
 }
 
 /* Update 'used' member of each flow currently installed into the datapath. */
@@ -4250,6 +4272,96 @@ ofproto_update_used(struct ofproto *p)
     free(flows);
 }
 
+/* Calculates and returns the number of milliseconds of idle time after which
+ * flows should expire from the datapath and we should fold their statistics
+ * into their parent rules in userspace. */
+static int
+ofproto_dp_max_idle(const struct ofproto *ofproto)
+{
+    /*
+     * Idle time histogram.
+     *
+     * Most of the time a switch has a relatively small number of flows.  When
+     * this is the case we might as well keep statistics for all of them in
+     * userspace and to cache them in the kernel datapath for performance as
+     * well.
+     *
+     * As the number of flows increases, the memory required to maintain
+     * statistics about them in userspace and in the kernel becomes
+     * significant.  However, with a large number of flows it is likely that
+     * only a few of them are "heavy hitters" that consume a large amount of
+     * bandwidth.  At this point, only heavy hitters are worth caching in the
+     * kernel and maintaining in userspaces; other flows we can discard.
+     *
+     * The technique used to compute the idle time is to build a histogram with
+     * N_BUCKETS bucket whose width is BUCKET_WIDTH msecs each.  Each flow that
+     * is installed in the kernel gets dropped in the appropriate bucket.
+     * After the histogram has been built, we compute the cutoff so that only
+     * the most-recently-used 1% of flows (but at least 1000 flows) are kept
+     * cached.  At least the most-recently-used bucket of flows is kept, so
+     * actually an arbitrary number of flows can be kept in any given
+     * expiration run (though the next run will delete most of those unless
+     * they receive additional data).
+     *
+     * This requires a second pass through the exact-match flows, in addition
+     * to the pass made by ofproto_update_used(), because the former function
+     * never looks at uninstallable flows.
+     */
+    enum { BUCKET_WIDTH = ROUND_UP(100, TIME_UPDATE_INTERVAL) };
+    enum { N_BUCKETS = 5000 / BUCKET_WIDTH };
+    int buckets[N_BUCKETS] = { 0 };
+    int total, bucket;
+    struct rule *rule;
+    long long int now;
+    int i;
+
+    total = classifier_count_exact(&ofproto->cls);
+    if (total <= 1000) {
+        return N_BUCKETS * BUCKET_WIDTH;
+    }
+
+    /* Build histogram. */
+    now = time_msec();
+    CLASSIFIER_FOR_EACH_EXACT_RULE (rule, struct rule, cr, &ofproto->cls) {
+        long long int idle = now - rule->used;
+        int bucket = (idle <= 0 ? 0
+                      : idle >= BUCKET_WIDTH * N_BUCKETS ? N_BUCKETS - 1
+                      : (unsigned int) idle / BUCKET_WIDTH);
+        buckets[bucket]++;
+    }
+
+    /* Find the first bucket whose flows should be expired. */
+    for (bucket = 0; bucket < N_BUCKETS; bucket++) {
+        if (buckets[bucket]) {
+            int subtotal = 0;
+            do {
+                subtotal += buckets[bucket++];
+            } while (bucket < N_BUCKETS && subtotal < MAX(1000, total / 100));
+            break;
+        }
+    }
+
+    if (VLOG_IS_DBG_ENABLED()) {
+        struct ds s;
+
+        ds_init(&s);
+        ds_put_cstr(&s, "keep");
+        for (i = 0; i < N_BUCKETS; i++) {
+            if (i == bucket) {
+                ds_put_cstr(&s, ", drop");
+            }
+            if (buckets[i]) {
+                ds_put_format(&s, " %d:%d", i * BUCKET_WIDTH, buckets[i]);
+            }
+        }
+        VLOG_INFO("%s: %s (msec:count)",
+                  dpif_name(ofproto->dpif), ds_cstr(&s));
+        ds_destroy(&s);
+    }
+
+    return bucket * BUCKET_WIDTH;
+}
+
 static void
 rule_active_timeout(struct ofproto *ofproto, struct rule *rule)
 {
@@ -4283,10 +4395,6 @@ rule_active_timeout(struct ofproto *ofproto, struct rule *rule)
         expired.used = rule->used;
 
         netflow_expire(ofproto->netflow, &rule->nf_flow, &expired);
-
-        /* Schedule us to send the accumulated records once we have
-         * collected all of them. */
-        poll_immediate_wake();
     }
 }
 
@@ -4318,16 +4426,24 @@ rule_expire(struct cls_rule *cls_rule, void *cbdata_)
     now = time_msec();
     if (now < expire) {
         /* 'rule' has not expired according to OpenFlow rules. */
-        if (rule->installed && now >= rule->used + 5000) {
-            /* This rule is idle, so uninstall it from the datapath. */
-            if (rule->super) {
-                rule_remove(ofproto, rule);
+        if (!rule->cr.wc.wildcards) {
+            if (now >= rule->used + cbdata->dp_max_idle) {
+                /* This rule is idle, so drop it to free up resources. */
+                if (rule->super) {
+                    /* It's not part of the OpenFlow flow table, so we can
+                     * delete it entirely and fold its statistics into its
+                     * super-rule. */
+                    rule_remove(ofproto, rule);
+                } else {
+                    /* It is part of the OpenFlow flow table, so we have to
+                     * keep the rule but we can at least uninstall it from the
+                     * datapath. */
+                    rule_uninstall(ofproto, rule);
+                }
             } else {
-                rule_uninstall(ofproto, rule);
+                /* Send NetFlow active timeout if appropriate. */
+                rule_active_timeout(cbdata->ofproto, rule);
             }
-        } else if (!rule->cr.wc.wildcards) {
-            /* Send NetFlow active timeout if appropriate. */
-            rule_active_timeout(cbdata->ofproto, rule);
         }
     } else {
         /* 'rule' has expired according to OpenFlow rules. */
@@ -4430,6 +4546,10 @@ send_flow_removed(struct ofproto *p, struct rule *rule,
     struct ofconn *prev;
     struct ofpbuf *buf = NULL;
 
+    if (!rule->send_flow_removed) {
+        return;
+    }
+
     /* We limit the maximum number of queued flow expirations it by accounting
      * them under the counter for replies.  That works because preventing
      * OpenFlow requests from being processed also prevents new flows from
@@ -4438,7 +4558,7 @@ send_flow_removed(struct ofproto *p, struct rule *rule,
 
     prev = NULL;
     LIST_FOR_EACH (ofconn, struct ofconn, node, &p->all_conns) {
-        if (rule->send_flow_removed && rconn_is_connected(ofconn->rconn)
+        if (rconn_is_connected(ofconn->rconn)
             && ofconn_receives_async_msgs(ofconn)) {
             if (prev) {
                 queue_tx(ofpbuf_clone(buf), prev, prev->reply_counter);