aboutsummaryrefslogtreecommitdiffstats
path: root/net/openvswitch
diff options
context:
space:
mode:
authorThadeu Lima de Souza Cascardo <cascardo@redhat.com>2016-09-15 18:11:53 -0400
committerDavid S. Miller <davem@davemloft.net>2016-09-18 22:14:01 -0400
commitdb74a3335e0f645e3139c80bcfc90feb01d8e304 (patch)
tree5adec60da4faa80b188cc0b45639224c0a197db4 /net/openvswitch
parent40773966ccf1985a1b2bb570a03cbeaf1cbd4e00 (diff)
openvswitch: use percpu flow stats
Instead of using flow stats per NUMA node, use it per CPU. When using megaflows, the stats lock can be a bottleneck in scalability. On a E5-2690 12-core system, usual throughput went from ~4Mpps to ~15Mpps when forwarding between two 40GbE ports with a single flow configured on the datapath. This has been tested on a system with possible CPUs 0-7,16-23. After module removal, there were no corruption on the slab cache. Signed-off-by: Thadeu Lima de Souza Cascardo <cascardo@redhat.com> Cc: pravin shelar <pshelar@ovn.org> Acked-by: Pravin B Shelar <pshelar@ovn.org> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/openvswitch')
-rw-r--r--net/openvswitch/flow.c42
-rw-r--r--net/openvswitch/flow.h4
-rw-r--r--net/openvswitch/flow_table.c26
3 files changed, 33 insertions, 39 deletions
diff --git a/net/openvswitch/flow.c b/net/openvswitch/flow.c
index 5b80612df182..0fa45439def1 100644
--- a/net/openvswitch/flow.c
+++ b/net/openvswitch/flow.c
@@ -29,6 +29,7 @@
29#include <linux/module.h> 29#include <linux/module.h>
30#include <linux/in.h> 30#include <linux/in.h>
31#include <linux/rcupdate.h> 31#include <linux/rcupdate.h>
32#include <linux/cpumask.h>
32#include <linux/if_arp.h> 33#include <linux/if_arp.h>
33#include <linux/ip.h> 34#include <linux/ip.h>
34#include <linux/ipv6.h> 35#include <linux/ipv6.h>
@@ -72,32 +73,33 @@ void ovs_flow_stats_update(struct sw_flow *flow, __be16 tcp_flags,
72{ 73{
73 struct flow_stats *stats; 74 struct flow_stats *stats;
74 int node = numa_node_id(); 75 int node = numa_node_id();
76 int cpu = smp_processor_id();
75 int len = skb->len + (skb_vlan_tag_present(skb) ? VLAN_HLEN : 0); 77 int len = skb->len + (skb_vlan_tag_present(skb) ? VLAN_HLEN : 0);
76 78
77 stats = rcu_dereference(flow->stats[node]); 79 stats = rcu_dereference(flow->stats[cpu]);
78 80
79 /* Check if already have node-specific stats. */ 81 /* Check if already have CPU-specific stats. */
80 if (likely(stats)) { 82 if (likely(stats)) {
81 spin_lock(&stats->lock); 83 spin_lock(&stats->lock);
82 /* Mark if we write on the pre-allocated stats. */ 84 /* Mark if we write on the pre-allocated stats. */
83 if (node == 0 && unlikely(flow->stats_last_writer != node)) 85 if (cpu == 0 && unlikely(flow->stats_last_writer != cpu))
84 flow->stats_last_writer = node; 86 flow->stats_last_writer = cpu;
85 } else { 87 } else {
86 stats = rcu_dereference(flow->stats[0]); /* Pre-allocated. */ 88 stats = rcu_dereference(flow->stats[0]); /* Pre-allocated. */
87 spin_lock(&stats->lock); 89 spin_lock(&stats->lock);
88 90
89 /* If the current NUMA-node is the only writer on the 91 /* If the current CPU is the only writer on the
90 * pre-allocated stats keep using them. 92 * pre-allocated stats keep using them.
91 */ 93 */
92 if (unlikely(flow->stats_last_writer != node)) { 94 if (unlikely(flow->stats_last_writer != cpu)) {
93 /* A previous locker may have already allocated the 95 /* A previous locker may have already allocated the
94 * stats, so we need to check again. If node-specific 96 * stats, so we need to check again. If CPU-specific
95 * stats were already allocated, we update the pre- 97 * stats were already allocated, we update the pre-
96 * allocated stats as we have already locked them. 98 * allocated stats as we have already locked them.
97 */ 99 */
98 if (likely(flow->stats_last_writer != NUMA_NO_NODE) 100 if (likely(flow->stats_last_writer != -1) &&
99 && likely(!rcu_access_pointer(flow->stats[node]))) { 101 likely(!rcu_access_pointer(flow->stats[cpu]))) {
100 /* Try to allocate node-specific stats. */ 102 /* Try to allocate CPU-specific stats. */
101 struct flow_stats *new_stats; 103 struct flow_stats *new_stats;
102 104
103 new_stats = 105 new_stats =
@@ -114,12 +116,12 @@ void ovs_flow_stats_update(struct sw_flow *flow, __be16 tcp_flags,
114 new_stats->tcp_flags = tcp_flags; 116 new_stats->tcp_flags = tcp_flags;
115 spin_lock_init(&new_stats->lock); 117 spin_lock_init(&new_stats->lock);
116 118
117 rcu_assign_pointer(flow->stats[node], 119 rcu_assign_pointer(flow->stats[cpu],
118 new_stats); 120 new_stats);
119 goto unlock; 121 goto unlock;
120 } 122 }
121 } 123 }
122 flow->stats_last_writer = node; 124 flow->stats_last_writer = cpu;
123 } 125 }
124 } 126 }
125 127
@@ -136,15 +138,15 @@ void ovs_flow_stats_get(const struct sw_flow *flow,
136 struct ovs_flow_stats *ovs_stats, 138 struct ovs_flow_stats *ovs_stats,
137 unsigned long *used, __be16 *tcp_flags) 139 unsigned long *used, __be16 *tcp_flags)
138{ 140{
139 int node; 141 int cpu;
140 142
141 *used = 0; 143 *used = 0;
142 *tcp_flags = 0; 144 *tcp_flags = 0;
143 memset(ovs_stats, 0, sizeof(*ovs_stats)); 145 memset(ovs_stats, 0, sizeof(*ovs_stats));
144 146
145 /* We open code this to make sure node 0 is always considered */ 147 /* We open code this to make sure cpu 0 is always considered */
146 for (node = 0; node < MAX_NUMNODES; node = next_node(node, node_possible_map)) { 148 for (cpu = 0; cpu < nr_cpu_ids; cpu = cpumask_next(cpu, cpu_possible_mask)) {
147 struct flow_stats *stats = rcu_dereference_ovsl(flow->stats[node]); 149 struct flow_stats *stats = rcu_dereference_ovsl(flow->stats[cpu]);
148 150
149 if (stats) { 151 if (stats) {
150 /* Local CPU may write on non-local stats, so we must 152 /* Local CPU may write on non-local stats, so we must
@@ -164,11 +166,11 @@ void ovs_flow_stats_get(const struct sw_flow *flow,
164/* Called with ovs_mutex. */ 166/* Called with ovs_mutex. */
165void ovs_flow_stats_clear(struct sw_flow *flow) 167void ovs_flow_stats_clear(struct sw_flow *flow)
166{ 168{
167 int node; 169 int cpu;
168 170
169 /* We open code this to make sure node 0 is always considered */ 171 /* We open code this to make sure cpu 0 is always considered */
170 for (node = 0; node < MAX_NUMNODES; node = next_node(node, node_possible_map)) { 172 for (cpu = 0; cpu < nr_cpu_ids; cpu = cpumask_next(cpu, cpu_possible_mask)) {
171 struct flow_stats *stats = ovsl_dereference(flow->stats[node]); 173 struct flow_stats *stats = ovsl_dereference(flow->stats[cpu]);
172 174
173 if (stats) { 175 if (stats) {
174 spin_lock_bh(&stats->lock); 176 spin_lock_bh(&stats->lock);
diff --git a/net/openvswitch/flow.h b/net/openvswitch/flow.h
index 156a3029c17b..ae783f5c6695 100644
--- a/net/openvswitch/flow.h
+++ b/net/openvswitch/flow.h
@@ -178,14 +178,14 @@ struct sw_flow {
178 struct hlist_node node[2]; 178 struct hlist_node node[2];
179 u32 hash; 179 u32 hash;
180 } flow_table, ufid_table; 180 } flow_table, ufid_table;
181 int stats_last_writer; /* NUMA-node id of the last writer on 181 int stats_last_writer; /* CPU id of the last writer on
182 * 'stats[0]'. 182 * 'stats[0]'.
183 */ 183 */
184 struct sw_flow_key key; 184 struct sw_flow_key key;
185 struct sw_flow_id id; 185 struct sw_flow_id id;
186 struct sw_flow_mask *mask; 186 struct sw_flow_mask *mask;
187 struct sw_flow_actions __rcu *sf_acts; 187 struct sw_flow_actions __rcu *sf_acts;
188 struct flow_stats __rcu *stats[]; /* One for each NUMA node. First one 188 struct flow_stats __rcu *stats[]; /* One for each CPU. First one
189 * is allocated at flow creation time, 189 * is allocated at flow creation time,
190 * the rest are allocated on demand 190 * the rest are allocated on demand
191 * while holding the 'stats[0].lock'. 191 * while holding the 'stats[0].lock'.
diff --git a/net/openvswitch/flow_table.c b/net/openvswitch/flow_table.c
index 957a3c31dbb0..ea7a8073fa02 100644
--- a/net/openvswitch/flow_table.c
+++ b/net/openvswitch/flow_table.c
@@ -32,6 +32,7 @@
32#include <linux/module.h> 32#include <linux/module.h>
33#include <linux/in.h> 33#include <linux/in.h>
34#include <linux/rcupdate.h> 34#include <linux/rcupdate.h>
35#include <linux/cpumask.h>
35#include <linux/if_arp.h> 36#include <linux/if_arp.h>
36#include <linux/ip.h> 37#include <linux/ip.h>
37#include <linux/ipv6.h> 38#include <linux/ipv6.h>
@@ -79,17 +80,12 @@ struct sw_flow *ovs_flow_alloc(void)
79{ 80{
80 struct sw_flow *flow; 81 struct sw_flow *flow;
81 struct flow_stats *stats; 82 struct flow_stats *stats;
82 int node;
83 83
84 flow = kmem_cache_alloc(flow_cache, GFP_KERNEL); 84 flow = kmem_cache_zalloc(flow_cache, GFP_KERNEL);
85 if (!flow) 85 if (!flow)
86 return ERR_PTR(-ENOMEM); 86 return ERR_PTR(-ENOMEM);
87 87
88 flow->sf_acts = NULL; 88 flow->stats_last_writer = -1;
89 flow->mask = NULL;
90 flow->id.unmasked_key = NULL;
91 flow->id.ufid_len = 0;
92 flow->stats_last_writer = NUMA_NO_NODE;
93 89
94 /* Initialize the default stat node. */ 90 /* Initialize the default stat node. */
95 stats = kmem_cache_alloc_node(flow_stats_cache, 91 stats = kmem_cache_alloc_node(flow_stats_cache,
@@ -102,10 +98,6 @@ struct sw_flow *ovs_flow_alloc(void)
102 98
103 RCU_INIT_POINTER(flow->stats[0], stats); 99 RCU_INIT_POINTER(flow->stats[0], stats);
104 100
105 for_each_node(node)
106 if (node != 0)
107