2009-07-08 13:19:16 -07:00
|
|
|
|
/*
|
2016-08-10 14:58:51 -07:00
|
|
|
|
* Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc.
|
2009-07-08 13:19:16 -07:00
|
|
|
|
*
|
2009-06-15 15:11:30 -07:00
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
|
* You may obtain a copy of the License at:
|
2009-07-08 13:19:16 -07:00
|
|
|
|
*
|
2009-06-15 15:11:30 -07:00
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
*
|
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
|
* limitations under the License.
|
2009-07-08 13:19:16 -07:00
|
|
|
|
*/
|
|
|
|
|
|
2020-06-17 14:22:47 -07:00
|
|
|
|
/* Tests for classifier, written with knowledge of and to advantage of the
|
|
|
|
|
* classifier's internal structure.
|
2009-07-08 13:19:16 -07:00
|
|
|
|
*
|
|
|
|
|
* With very few exceptions, these tests obtain complete coverage of every
|
|
|
|
|
* basic block and every branch in the classifier implementation, e.g. a clean
|
|
|
|
|
* report from "gcov -b". (Covering the exceptions would require finding
|
|
|
|
|
* collisions in the hash function used for flow data, etc.)
|
|
|
|
|
*
|
|
|
|
|
* This test should receive a clean report from "valgrind --leak-check=full":
|
|
|
|
|
* it frees every heap block that it allocates.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <config.h>
|
2014-10-24 13:22:24 -07:00
|
|
|
|
#undef NDEBUG
|
2014-10-29 11:34:40 -07:00
|
|
|
|
#include "classifier.h"
|
2014-10-24 13:22:24 -07:00
|
|
|
|
#include <assert.h>
|
2009-07-08 13:19:16 -07:00
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <limits.h>
|
2010-10-28 17:13:18 -07:00
|
|
|
|
#include "byte-order.h"
|
2014-10-24 13:22:24 -07:00
|
|
|
|
#include "classifier-private.h"
|
2010-05-26 12:48:32 -07:00
|
|
|
|
#include "command-line.h"
|
test-classifier: Add benchmark.
Add a benchmark command for classifier lookup performance testing.
Running the test-classifier without arguments of with "--help" will
print the following usage:
usage: ovstest test-classifier benchmark <n_rules> <n_priorities> <n_subtables> <n_threads> <n_lookups>
where:
<n_rules> - The number of rules to install for lookups. More rules
makes misses less likely.
<n_priorities> - How many different priorities to use. Using only 1
priority will force lookups to continue through all
subtables.
<n_subtables> - Number of subtables to use. Normally a classifier has
rules with different kinds of masks, resulting in
multiple subtables (one per mask). However, in some
special cases a table may consist of only one kind of
rules, so there will be only one subtable.
<n_threads> - How many lookup threads to use. Using one thread should
give less variance accross runs, but classifier
scaling can be tested with multiple threads.
<n_lookups> - How many lookups each thread should perform.
For testing the classifier is filled with <n_rules> rules using
<n_subtables> different mask patterns and <n_priorities> different
priorities. A random set of lookup flows are created, and <n_threads>
lookup threads are spawned to perform <n_lookups> lookups each. The
count of hits and misses, as well as the overall execution time is
reported.
Example run:
$ tests/ovstest test-classifier benchmark 1000 1 30 1 3800000
Benchmarking with:
1000 rules with 1 priorities in 30 tables, 1 threads doing 3800000 lookups each
Without wildcards:
hits: 461520, misses: 3338480
classifier lookups: 386 ms, 9844559 lookups/sec
With wildcards:
hits: 461520, misses: 3338480
classifier lookups: 866 ms, 4387990 lookups/sec
Signed-off-by: Jarno Rajahalme <jrajahalme@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
2015-08-21 12:49:46 -07:00
|
|
|
|
#include "fatal-signal.h"
|
2009-07-08 13:19:16 -07:00
|
|
|
|
#include "flow.h"
|
2025-05-16 23:25:17 +02:00
|
|
|
|
#include "openvswitch/vlog.h"
|
2014-10-29 11:34:40 -07:00
|
|
|
|
#include "ovstest.h"
|
test-classifier: Add benchmark.
Add a benchmark command for classifier lookup performance testing.
Running the test-classifier without arguments of with "--help" will
print the following usage:
usage: ovstest test-classifier benchmark <n_rules> <n_priorities> <n_subtables> <n_threads> <n_lookups>
where:
<n_rules> - The number of rules to install for lookups. More rules
makes misses less likely.
<n_priorities> - How many different priorities to use. Using only 1
priority will force lookups to continue through all
subtables.
<n_subtables> - Number of subtables to use. Normally a classifier has
rules with different kinds of masks, resulting in
multiple subtables (one per mask). However, in some
special cases a table may consist of only one kind of
rules, so there will be only one subtable.
<n_threads> - How many lookup threads to use. Using one thread should
give less variance accross runs, but classifier
scaling can be tested with multiple threads.
<n_lookups> - How many lookups each thread should perform.
For testing the classifier is filled with <n_rules> rules using
<n_subtables> different mask patterns and <n_priorities> different
priorities. A random set of lookup flows are created, and <n_threads>
lookup threads are spawned to perform <n_lookups> lookups each. The
count of hits and misses, as well as the overall execution time is
reported.
Example run:
$ tests/ovstest test-classifier benchmark 1000 1 30 1 3800000
Benchmarking with:
1000 rules with 1 priorities in 30 tables, 1 threads doing 3800000 lookups each
Without wildcards:
hits: 461520, misses: 3338480
classifier lookups: 386 ms, 9844559 lookups/sec
With wildcards:
hits: 461520, misses: 3338480
classifier lookups: 866 ms, 4387990 lookups/sec
Signed-off-by: Jarno Rajahalme <jrajahalme@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
2015-08-21 12:49:46 -07:00
|
|
|
|
#include "ovs-atomic.h"
|
|
|
|
|
#include "ovs-thread.h"
|
2009-07-08 13:19:16 -07:00
|
|
|
|
#include "packets.h"
|
2013-06-25 09:22:11 -07:00
|
|
|
|
#include "random.h"
|
test-classifier: Add benchmark.
Add a benchmark command for classifier lookup performance testing.
Running the test-classifier without arguments of with "--help" will
print the following usage:
usage: ovstest test-classifier benchmark <n_rules> <n_priorities> <n_subtables> <n_threads> <n_lookups>
where:
<n_rules> - The number of rules to install for lookups. More rules
makes misses less likely.
<n_priorities> - How many different priorities to use. Using only 1
priority will force lookups to continue through all
subtables.
<n_subtables> - Number of subtables to use. Normally a classifier has
rules with different kinds of masks, resulting in
multiple subtables (one per mask). However, in some
special cases a table may consist of only one kind of
rules, so there will be only one subtable.
<n_threads> - How many lookup threads to use. Using one thread should
give less variance accross runs, but classifier
scaling can be tested with multiple threads.
<n_lookups> - How many lookups each thread should perform.
For testing the classifier is filled with <n_rules> rules using
<n_subtables> different mask patterns and <n_priorities> different
priorities. A random set of lookup flows are created, and <n_threads>
lookup threads are spawned to perform <n_lookups> lookups each. The
count of hits and misses, as well as the overall execution time is
reported.
Example run:
$ tests/ovstest test-classifier benchmark 1000 1 30 1 3800000
Benchmarking with:
1000 rules with 1 priorities in 30 tables, 1 threads doing 3800000 lookups each
Without wildcards:
hits: 461520, misses: 3338480
classifier lookups: 386 ms, 9844559 lookups/sec
With wildcards:
hits: 461520, misses: 3338480
classifier lookups: 866 ms, 4387990 lookups/sec
Signed-off-by: Jarno Rajahalme <jrajahalme@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
2015-08-21 12:49:46 -07:00
|
|
|
|
#include "timeval.h"
|
2010-10-28 13:20:23 -07:00
|
|
|
|
#include "unaligned.h"
|
2014-10-24 13:22:24 -07:00
|
|
|
|
#include "util.h"
|
2014-04-29 15:50:38 -07:00
|
|
|
|
|
2015-06-11 15:53:43 -07:00
|
|
|
|
static bool versioned = false;
|
|
|
|
|
|
2010-11-03 11:00:58 -07:00
|
|
|
|
/* Fields in a rule. */
|
2015-12-10 17:58:13 -08:00
|
|
|
|
#define CLS_FIELDS \
|
|
|
|
|
/* struct flow all-caps */ \
|
|
|
|
|
/* member name name */ \
|
|
|
|
|
/* ----------- -------- */ \
|
|
|
|
|
CLS_FIELD(tunnel.tun_id, TUN_ID) \
|
|
|
|
|
CLS_FIELD(metadata, METADATA) \
|
|
|
|
|
CLS_FIELD(nw_src, NW_SRC) \
|
|
|
|
|
CLS_FIELD(nw_dst, NW_DST) \
|
|
|
|
|
CLS_FIELD(in_port.ofp_port, IN_PORT) \
|
2017-03-01 17:47:59 -05:00
|
|
|
|
CLS_FIELD(vlans[0].tci, VLAN_TCI) \
|
2015-12-10 17:58:13 -08:00
|
|
|
|
CLS_FIELD(dl_type, DL_TYPE) \
|
|
|
|
|
CLS_FIELD(tp_src, TP_SRC) \
|
|
|
|
|
CLS_FIELD(tp_dst, TP_DST) \
|
|
|
|
|
CLS_FIELD(dl_src, DL_SRC) \
|
|
|
|
|
CLS_FIELD(dl_dst, DL_DST) \
|
|
|
|
|
CLS_FIELD(nw_proto, NW_PROTO) \
|
|
|
|
|
CLS_FIELD(nw_tos, NW_DSCP)
|
2010-11-03 11:00:58 -07:00
|
|
|
|
|
|
|
|
|
/* Field indexes.
|
|
|
|
|
*
|
|
|
|
|
* (These are also indexed into struct classifier's 'tables' array.) */
|
|
|
|
|
enum {
|
2012-08-03 13:27:15 -07:00
|
|
|
|
#define CLS_FIELD(MEMBER, NAME) CLS_F_IDX_##NAME,
|
2010-11-03 11:00:58 -07:00
|
|
|
|
CLS_FIELDS
|
|
|
|
|
#undef CLS_FIELD
|
|
|
|
|
CLS_N_FIELDS
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* Field information. */
|
|
|
|
|
struct cls_field {
|
|
|
|
|
int ofs; /* Offset in struct flow. */
|
|
|
|
|
int len; /* Length in bytes. */
|
|
|
|
|
const char *name; /* Name (for debugging). */
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const struct cls_field cls_fields[CLS_N_FIELDS] = {
|
2012-08-03 13:27:15 -07:00
|
|
|
|
#define CLS_FIELD(MEMBER, NAME) \
|
2010-11-03 11:00:58 -07:00
|
|
|
|
{ offsetof(struct flow, MEMBER), \
|
|
|
|
|
sizeof ((struct flow *)0)->MEMBER, \
|
|
|
|
|
#NAME },
|
|
|
|
|
CLS_FIELDS
|
|
|
|
|
#undef CLS_FIELD
|
|
|
|
|
};
|
|
|
|
|
|
2009-07-08 13:19:16 -07:00
|
|
|
|
struct test_rule {
|
2015-06-11 15:53:43 -07:00
|
|
|
|
struct ovs_list list_node;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
int aux; /* Auxiliary data. */
|
|
|
|
|
struct cls_rule cls_rule; /* Classifier rule data. */
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static struct test_rule *
|
|
|
|
|
test_rule_from_cls_rule(const struct cls_rule *rule)
|
|
|
|
|
{
|
|
|
|
|
return rule ? CONTAINER_OF(rule, struct test_rule, cls_rule) : NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2012-12-21 14:59:35 -08:00
|
|
|
|
static void
|
|
|
|
|
test_rule_destroy(struct test_rule *rule)
|
|
|
|
|
{
|
|
|
|
|
if (rule) {
|
|
|
|
|
cls_rule_destroy(&rule->cls_rule);
|
|
|
|
|
free(rule);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-06 11:45:54 -07:00
|
|
|
|
static struct test_rule *make_rule(int wc_fields, int priority, int value_pat);
|
2012-08-20 11:29:43 -07:00
|
|
|
|
static void free_rule(struct test_rule *);
|
|
|
|
|
static struct test_rule *clone_rule(const struct test_rule *);
|
|
|
|
|
|
2009-07-08 13:19:16 -07:00
|
|
|
|
/* Trivial (linear) classifier. */
|
|
|
|
|
struct tcls {
|
|
|
|
|
size_t n_rules;
|
|
|
|
|
size_t allocated_rules;
|
|
|
|
|
struct test_rule **rules;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
tcls_init(struct tcls *tcls)
|
|
|
|
|
{
|
|
|
|
|
tcls->n_rules = 0;
|
|
|
|
|
tcls->allocated_rules = 0;
|
|
|
|
|
tcls->rules = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
tcls_destroy(struct tcls *tcls)
|
|
|
|
|
{
|
|
|
|
|
if (tcls) {
|
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < tcls->n_rules; i++) {
|
2012-12-21 14:59:35 -08:00
|
|
|
|
test_rule_destroy(tcls->rules[i]);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
free(tcls->rules);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
tcls_is_empty(const struct tcls *tcls)
|
|
|
|
|
{
|
|
|
|
|
return tcls->n_rules == 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct test_rule *
|
|
|
|
|
tcls_insert(struct tcls *tcls, const struct test_rule *rule)
|
|
|
|
|
{
|
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < tcls->n_rules; i++) {
|
|
|
|
|
const struct cls_rule *pos = &tcls->rules[i]->cls_rule;
|
2010-11-08 16:35:34 -08:00
|
|
|
|
if (cls_rule_equal(pos, &rule->cls_rule)) {
|
|
|
|
|
/* Exact match. */
|
2014-10-29 09:59:57 -07:00
|
|
|
|
ovsrcu_postpone(free_rule, tcls->rules[i]);
|
2012-08-20 11:29:43 -07:00
|
|
|
|
tcls->rules[i] = clone_rule(rule);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
return tcls->rules[i];
|
2010-03-19 13:54:36 -04:00
|
|
|
|
} else if (pos->priority < rule->cls_rule.priority) {
|
2009-07-08 13:19:16 -07:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (tcls->n_rules >= tcls->allocated_rules) {
|
|
|
|
|
tcls->rules = x2nrealloc(tcls->rules, &tcls->allocated_rules,
|
|
|
|
|
sizeof *tcls->rules);
|
|
|
|
|
}
|
|
|
|
|
if (i != tcls->n_rules) {
|
|
|
|
|
memmove(&tcls->rules[i + 1], &tcls->rules[i],
|
|
|
|
|
sizeof *tcls->rules * (tcls->n_rules - i));
|
|
|
|
|
}
|
2012-08-20 11:29:43 -07:00
|
|
|
|
tcls->rules[i] = clone_rule(rule);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
tcls->n_rules++;
|
|
|
|
|
return tcls->rules[i];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
tcls_remove(struct tcls *cls, const struct test_rule *rule)
|
|
|
|
|
{
|
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < cls->n_rules; i++) {
|
|
|
|
|
struct test_rule *pos = cls->rules[i];
|
|
|
|
|
if (pos == rule) {
|
2012-12-21 14:59:35 -08:00
|
|
|
|
test_rule_destroy(pos);
|
|
|
|
|
|
2009-07-08 13:19:16 -07:00
|
|
|
|
memmove(&cls->rules[i], &cls->rules[i + 1],
|
|
|
|
|
sizeof *cls->rules * (cls->n_rules - i - 1));
|
2012-12-21 14:59:35 -08:00
|
|
|
|
|
2009-07-08 13:19:16 -07:00
|
|
|
|
cls->n_rules--;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-12-17 10:32:12 -08:00
|
|
|
|
OVS_NOT_REACHED();
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
2012-09-04 12:43:53 -07:00
|
|
|
|
match(const struct cls_rule *wild_, const struct flow *fixed)
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
2012-09-04 12:43:53 -07:00
|
|
|
|
struct match wild;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
int f_idx;
|
|
|
|
|
|
2012-09-04 12:43:53 -07:00
|
|
|
|
minimatch_expand(&wild_->match, &wild);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
for (f_idx = 0; f_idx < CLS_N_FIELDS; f_idx++) {
|
2010-11-10 14:39:54 -08:00
|
|
|
|
bool eq;
|
|
|
|
|
|
2012-08-03 13:27:15 -07:00
|
|
|
|
if (f_idx == CLS_F_IDX_NW_SRC) {
|
2012-09-04 12:43:53 -07:00
|
|
|
|
eq = !((fixed->nw_src ^ wild.flow.nw_src)
|
|
|
|
|
& wild.wc.masks.nw_src);
|
2010-11-10 14:39:54 -08:00
|
|
|
|
} else if (f_idx == CLS_F_IDX_NW_DST) {
|
2012-09-04 12:43:53 -07:00
|
|
|
|
eq = !((fixed->nw_dst ^ wild.flow.nw_dst)
|
|
|
|
|
& wild.wc.masks.nw_dst);
|
2012-01-27 17:16:05 -08:00
|
|
|
|
} else if (f_idx == CLS_F_IDX_TP_SRC) {
|
2012-09-04 12:43:53 -07:00
|
|
|
|
eq = !((fixed->tp_src ^ wild.flow.tp_src)
|
|
|
|
|
& wild.wc.masks.tp_src);
|
2012-01-27 17:16:05 -08:00
|
|
|
|
} else if (f_idx == CLS_F_IDX_TP_DST) {
|
2012-09-04 12:43:53 -07:00
|
|
|
|
eq = !((fixed->tp_dst ^ wild.flow.tp_dst)
|
|
|
|
|
& wild.wc.masks.tp_dst);
|
2012-05-29 00:38:21 +12:00
|
|
|
|
} else if (f_idx == CLS_F_IDX_DL_SRC) {
|
2012-09-04 12:43:53 -07:00
|
|
|
|
eq = eth_addr_equal_except(fixed->dl_src, wild.flow.dl_src,
|
|
|
|
|
wild.wc.masks.dl_src);
|
2012-05-29 00:38:21 +12:00
|
|
|
|
} else if (f_idx == CLS_F_IDX_DL_DST) {
|
2012-09-04 12:43:53 -07:00
|
|
|
|
eq = eth_addr_equal_except(fixed->dl_dst, wild.flow.dl_dst,
|
|
|
|
|
wild.wc.masks.dl_dst);
|
2010-11-23 10:06:28 -08:00
|
|
|
|
} else if (f_idx == CLS_F_IDX_VLAN_TCI) {
|
2017-03-01 17:47:59 -05:00
|
|
|
|
eq = !((fixed->vlans[0].tci ^ wild.flow.vlans[0].tci)
|
|
|
|
|
& wild.wc.masks.vlans[0].tci);
|
2011-01-20 15:29:00 -08:00
|
|
|
|
} else if (f_idx == CLS_F_IDX_TUN_ID) {
|
2012-09-13 20:11:08 -07:00
|
|
|
|
eq = !((fixed->tunnel.tun_id ^ wild.flow.tunnel.tun_id)
|
|
|
|
|
& wild.wc.masks.tunnel.tun_id);
|
2012-07-12 00:01:11 +12:00
|
|
|
|
} else if (f_idx == CLS_F_IDX_METADATA) {
|
2012-09-04 12:43:53 -07:00
|
|
|
|
eq = !((fixed->metadata ^ wild.flow.metadata)
|
|
|
|
|
& wild.wc.masks.metadata);
|
2011-11-09 23:39:16 -08:00
|
|
|
|
} else if (f_idx == CLS_F_IDX_NW_DSCP) {
|
2012-09-04 12:43:53 -07:00
|
|
|
|
eq = !((fixed->nw_tos ^ wild.flow.nw_tos) &
|
|
|
|
|
(wild.wc.masks.nw_tos & IP_DSCP_MASK));
|
2012-06-18 14:12:52 -07:00
|
|
|
|
} else if (f_idx == CLS_F_IDX_NW_PROTO) {
|
2012-09-04 12:43:53 -07:00
|
|
|
|
eq = !((fixed->nw_proto ^ wild.flow.nw_proto)
|
|
|
|
|
& wild.wc.masks.nw_proto);
|
2012-06-18 13:33:13 -07:00
|
|
|
|
} else if (f_idx == CLS_F_IDX_DL_TYPE) {
|
2012-09-04 12:43:53 -07:00
|
|
|
|
eq = !((fixed->dl_type ^ wild.flow.dl_type)
|
|
|
|
|
& wild.wc.masks.dl_type);
|
2012-08-03 13:27:15 -07:00
|
|
|
|
} else if (f_idx == CLS_F_IDX_IN_PORT) {
|
2013-06-19 16:58:44 -07:00
|
|
|
|
eq = !((fixed->in_port.ofp_port
|
|
|
|
|
^ wild.flow.in_port.ofp_port)
|
|
|
|
|
& wild.wc.masks.in_port.ofp_port);
|
2010-11-10 14:39:54 -08:00
|
|
|
|
} else {
|
2013-12-17 10:32:12 -08:00
|
|
|
|
OVS_NOT_REACHED();
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
|
2010-11-10 14:39:54 -08:00
|
|
|
|
if (!eq) {
|
|
|
|
|
return false;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct cls_rule *
|
2010-10-28 13:26:31 -07:00
|
|
|
|
tcls_lookup(const struct tcls *cls, const struct flow *flow)
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < cls->n_rules; i++) {
|
|
|
|
|
struct test_rule *pos = cls->rules[i];
|
2010-10-28 13:26:31 -07:00
|
|
|
|
if (match(&pos->cls_rule, flow)) {
|
2009-07-08 13:19:16 -07:00
|
|
|
|
return &pos->cls_rule;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2010-10-28 13:26:31 -07:00
|
|
|
|
tcls_delete_matches(struct tcls *cls, const struct cls_rule *target)
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < cls->n_rules; ) {
|
|
|
|
|
struct test_rule *pos = cls->rules[i];
|
2015-07-15 13:17:01 -07:00
|
|
|
|
if (!minimask_has_extra(pos->cls_rule.match.mask,
|
|
|
|
|
target->match.mask)) {
|
2012-09-04 12:43:53 -07:00
|
|
|
|
struct flow flow;
|
|
|
|
|
|
2015-07-15 13:17:01 -07:00
|
|
|
|
miniflow_expand(pos->cls_rule.match.flow, &flow);
|
2012-09-04 12:43:53 -07:00
|
|
|
|
if (match(target, &flow)) {
|
|
|
|
|
tcls_remove(cls, pos);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
2012-09-04 12:43:53 -07:00
|
|
|
|
i++;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-28 13:26:31 -07:00
|
|
|
|
static ovs_be32 nw_src_values[] = { CONSTANT_HTONL(0xc0a80001),
|
2010-05-11 16:44:03 -07:00
|
|
|
|
CONSTANT_HTONL(0xc0a04455) };
|
2010-10-28 13:26:31 -07:00
|
|
|
|
static ovs_be32 nw_dst_values[] = { CONSTANT_HTONL(0xc0a80002),
|
2010-05-11 16:44:03 -07:00
|
|
|
|
CONSTANT_HTONL(0xc0a04455) };
|
2010-12-10 10:42:42 -08:00
|
|
|
|
static ovs_be64 tun_id_values[] = {
|
|
|
|
|
0,
|
|
|
|
|
CONSTANT_HTONLL(UINT64_C(0xfedcba9876543210)) };
|
2012-07-12 00:01:11 +12:00
|
|
|
|
static ovs_be64 metadata_values[] = {
|
|
|
|
|
0,
|
|
|
|
|
CONSTANT_HTONLL(UINT64_C(0xfedcba9876543210)) };
|
2013-06-19 16:58:44 -07:00
|
|
|
|
static ofp_port_t in_port_values[] = { OFP_PORT_C(1), OFPP_LOCAL };
|
2010-11-23 10:06:28 -08:00
|
|
|
|
static ovs_be16 vlan_tci_values[] = { CONSTANT_HTONS(101), CONSTANT_HTONS(0) };
|
2010-10-28 13:26:31 -07:00
|
|
|
|
static ovs_be16 dl_type_values[]
|
2010-05-11 16:44:03 -07:00
|
|
|
|
= { CONSTANT_HTONS(ETH_TYPE_IP), CONSTANT_HTONS(ETH_TYPE_ARP) };
|
2010-10-28 13:26:31 -07:00
|
|
|
|
static ovs_be16 tp_src_values[] = { CONSTANT_HTONS(49362),
|
2010-05-11 16:44:03 -07:00
|
|
|
|
CONSTANT_HTONS(80) };
|
2010-10-28 13:26:31 -07:00
|
|
|
|
static ovs_be16 tp_dst_values[] = { CONSTANT_HTONS(6667), CONSTANT_HTONS(22) };
|
2015-08-28 14:55:11 -07:00
|
|
|
|
static struct eth_addr dl_src_values[] = {
|
2017-11-28 15:32:24 -08:00
|
|
|
|
ETH_ADDR_C(00,02,e3,0f,80,a4),
|
|
|
|
|
ETH_ADDR_C(5e,33,7f,5f,1e,99)
|
|
|
|
|
};
|
2015-08-28 14:55:11 -07:00
|
|
|
|
static struct eth_addr dl_dst_values[] = {
|
2017-11-28 15:32:24 -08:00
|
|
|
|
ETH_ADDR_C(4a,27,71,ae,64,c1),
|
|
|
|
|
ETH_ADDR_C(ff,ff,ff,ff,ff,ff)
|
|
|
|
|
};
|
2011-02-02 11:33:20 -08:00
|
|
|
|
static uint8_t nw_proto_values[] = { IPPROTO_TCP, IPPROTO_ICMP };
|
2011-11-09 23:39:16 -08:00
|
|
|
|
static uint8_t nw_dscp_values[] = { 48, 0 };
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
|
|
|
|
static void *values[CLS_N_FIELDS][2];
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
init_values(void)
|
|
|
|
|
{
|
2010-04-12 11:49:16 -04:00
|
|
|
|
values[CLS_F_IDX_TUN_ID][0] = &tun_id_values[0];
|
|
|
|
|
values[CLS_F_IDX_TUN_ID][1] = &tun_id_values[1];
|
|
|
|
|
|
2012-07-12 00:01:11 +12:00
|
|
|
|
values[CLS_F_IDX_METADATA][0] = &metadata_values[0];
|
|
|
|
|
values[CLS_F_IDX_METADATA][1] = &metadata_values[1];
|
|
|
|
|
|
2009-07-08 13:19:16 -07:00
|
|
|
|
values[CLS_F_IDX_IN_PORT][0] = &in_port_values[0];
|
|
|
|
|
values[CLS_F_IDX_IN_PORT][1] = &in_port_values[1];
|
|
|
|
|
|
2010-11-23 10:06:28 -08:00
|
|
|
|
values[CLS_F_IDX_VLAN_TCI][0] = &vlan_tci_values[0];
|
|
|
|
|
values[CLS_F_IDX_VLAN_TCI][1] = &vlan_tci_values[1];
|
2009-11-11 14:59:49 -08:00
|
|
|
|
|
2015-08-28 14:55:11 -07:00
|
|
|
|
values[CLS_F_IDX_DL_SRC][0] = &dl_src_values[0];
|
|
|
|
|
values[CLS_F_IDX_DL_SRC][1] = &dl_src_values[1];
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2015-08-28 14:55:11 -07:00
|
|
|
|
values[CLS_F_IDX_DL_DST][0] = &dl_dst_values[0];
|
|
|
|
|
values[CLS_F_IDX_DL_DST][1] = &dl_dst_values[1];
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
|
|
|
|
values[CLS_F_IDX_DL_TYPE][0] = &dl_type_values[0];
|
|
|
|
|
values[CLS_F_IDX_DL_TYPE][1] = &dl_type_values[1];
|
|
|
|
|
|
|
|
|
|
values[CLS_F_IDX_NW_SRC][0] = &nw_src_values[0];
|
|
|
|
|
values[CLS_F_IDX_NW_SRC][1] = &nw_src_values[1];
|
|
|
|
|
|
|
|
|
|
values[CLS_F_IDX_NW_DST][0] = &nw_dst_values[0];
|
|
|
|
|
values[CLS_F_IDX_NW_DST][1] = &nw_dst_values[1];
|
|
|
|
|
|
|
|
|
|
values[CLS_F_IDX_NW_PROTO][0] = &nw_proto_values[0];
|
|
|
|
|
values[CLS_F_IDX_NW_PROTO][1] = &nw_proto_values[1];
|
|
|
|
|
|
2011-11-09 23:39:16 -08:00
|
|
|
|
values[CLS_F_IDX_NW_DSCP][0] = &nw_dscp_values[0];
|
|
|
|
|
values[CLS_F_IDX_NW_DSCP][1] = &nw_dscp_values[1];
|
2010-01-21 17:34:05 -08:00
|
|
|
|
|
2009-07-08 13:19:16 -07:00
|
|
|
|
values[CLS_F_IDX_TP_SRC][0] = &tp_src_values[0];
|
|
|
|
|
values[CLS_F_IDX_TP_SRC][1] = &tp_src_values[1];
|
|
|
|
|
|
|
|
|
|
values[CLS_F_IDX_TP_DST][0] = &tp_dst_values[0];
|
|
|
|
|
values[CLS_F_IDX_TP_DST][1] = &tp_dst_values[1];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define N_NW_SRC_VALUES ARRAY_SIZE(nw_src_values)
|
|
|
|
|
#define N_NW_DST_VALUES ARRAY_SIZE(nw_dst_values)
|
2010-04-12 11:49:16 -04:00
|
|
|
|
#define N_TUN_ID_VALUES ARRAY_SIZE(tun_id_values)
|
2012-07-12 00:01:11 +12:00
|
|
|
|
#define N_METADATA_VALUES ARRAY_SIZE(metadata_values)
|
2009-07-08 13:19:16 -07:00
|
|
|
|
#define N_IN_PORT_VALUES ARRAY_SIZE(in_port_values)
|
2010-11-23 10:06:28 -08:00
|
|
|
|
#define N_VLAN_TCI_VALUES ARRAY_SIZE(vlan_tci_values)
|
2009-07-08 13:19:16 -07:00
|
|
|
|
#define N_DL_TYPE_VALUES ARRAY_SIZE(dl_type_values)
|
|
|
|
|
#define N_TP_SRC_VALUES ARRAY_SIZE(tp_src_values)
|
|
|
|
|
#define N_TP_DST_VALUES ARRAY_SIZE(tp_dst_values)
|
|
|
|
|
#define N_DL_SRC_VALUES ARRAY_SIZE(dl_src_values)
|
|
|
|
|
#define N_DL_DST_VALUES ARRAY_SIZE(dl_dst_values)
|
|
|
|
|
#define N_NW_PROTO_VALUES ARRAY_SIZE(nw_proto_values)
|
2011-11-09 23:39:16 -08:00
|
|
|
|
#define N_NW_DSCP_VALUES ARRAY_SIZE(nw_dscp_values)
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
|
|
|
|
#define N_FLOW_VALUES (N_NW_SRC_VALUES * \
|
|
|
|
|
N_NW_DST_VALUES * \
|
2010-04-12 11:49:16 -04:00
|
|
|
|
N_TUN_ID_VALUES * \
|
2009-07-08 13:19:16 -07:00
|
|
|
|
N_IN_PORT_VALUES * \
|
2010-11-23 10:06:28 -08:00
|
|
|
|
N_VLAN_TCI_VALUES * \
|
2009-07-08 13:19:16 -07:00
|
|
|
|
N_DL_TYPE_VALUES * \
|
|
|
|
|
N_TP_SRC_VALUES * \
|
|
|
|
|
N_TP_DST_VALUES * \
|
|
|
|
|
N_DL_SRC_VALUES * \
|
|
|
|
|
N_DL_DST_VALUES * \
|
2010-01-21 17:34:05 -08:00
|
|
|
|
N_NW_PROTO_VALUES * \
|
2011-11-09 23:39:16 -08:00
|
|
|
|
N_NW_DSCP_VALUES)
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
|
|
|
|
static unsigned int
|
|
|
|
|
get_value(unsigned int *x, unsigned n_values)
|
|
|
|
|
{
|
|
|
|
|
unsigned int rem = *x % n_values;
|
|
|
|
|
*x /= n_values;
|
|
|
|
|
return rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2015-06-11 15:53:43 -07:00
|
|
|
|
compare_classifiers(struct classifier *cls, size_t n_invisible_rules,
|
2016-07-29 16:52:01 -07:00
|
|
|
|
ovs_version_t version, struct tcls *tcls)
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
2010-05-26 15:24:13 -07:00
|
|
|
|
static const int confidence = 500;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
unsigned int i;
|
|
|
|
|
|
2015-06-11 15:53:43 -07:00
|
|
|
|
assert(classifier_count(cls) == tcls->n_rules + n_invisible_rules);
|
2010-05-26 15:24:13 -07:00
|
|
|
|
for (i = 0; i < confidence; i++) {
|
2014-11-06 14:55:29 -08:00
|
|
|
|
const struct cls_rule *cr0, *cr1, *cr2;
|
2010-09-03 11:30:02 -07:00
|
|
|
|
struct flow flow;
|
2013-11-19 17:31:29 -08:00
|
|
|
|
struct flow_wildcards wc;
|
classifier: Fix race for prefix tree configuration.
The thread fence in the classifier is supposed to ensure that when the
subtable->trie_plen is updated, the actual prefix tree is ready to be
used. On the write side in trie_init(), the fence is between the
tree configuration and the 'trie_plen' update. On the reader's side
however, the fence is at the beginning of the classifier_lookup__(),
and both reads of the 'trie_plen' and the accesses to the tree itself
are happening afterwards. And since both types of the reads are on
the same side of the fence, the fence is kind of pointless and doesn't
guarantee any memory ordering. So, readers can be accessing partially
initialized prefix trees.
Another problem with the configuration is that cls->n_tries is updated
without any synchronization as well. The comment on the fence says
that it also synchronizes for the cls->n_tries, but that doesn't make
a lot of sense. In practice, cls->n_tries is read multiple times
throughout the classifier_lookup__() and each of these reads may give
a different value if there is a concurrent update, causing the reader
to access trees that are not initialized or in the middle of being
destroyed, leading to OVS crashes while the user updates the flow
table prefixes.
First thing that needs to be fixed here is to only read cls->n_tries
once to avoid obvious crashes with access to uninitialized trie_ctx[]
entries.
The second thing is that we need a proper memory synchronization that
will guarantee that our prefix trees are fully initialized when
readers access them. In the current logic we would need to issue
a thread fence after every read of a subtable->trie_plen value, i.e.,
we'd need a fence per subtable lookup. This would be very expensive
and wasteful, considering the prefix tree configuration normally
happens only once somewhere at startup.
What we can do instead is to convert cls->n_tries into atomic and use
it as a synchronization point:
Writer (classifier_set_prefix_fields):
1. Before making any changes, set cls->n_tries to zero. Relaxed
memory order can be used here, because we'll have a full memory
barrier at the next step.
2. ovsrcu_synchronize() to wait for all threads to stop using tries.
3. Update tries while nobody is using them.
4. Set cls->n_tries to a new value with memory_order_release.
Reader (classifier_lookup):
1. Read the cls->n_tries with the memory_order_acquire.
2. Use that once read value throughout.
RCU in this scenario will ensure that every thread no longer uses the
prefix trees when we're about to change them. The acquire-release
semantics on the cls->n_tries just saves us from calling the
ovsrcu_synchronize() the second time once we're done with the whole
reconfiguration. We're just updating the number and making all the
previous changes visible on CPUs that acquire it.
Alternative solution might be to go full RCU and make the array of
trees itself RCU-protected. This way we would not need to do any
extra RCU synchronization or managing the memory ordering. However,
that would mean having multiple layers of RCU with trees and rules
in them potentially surviving multiple grace periods, which I would
like to avoid, if possible.
Previous code was also trying to be smart and not disable prefix tree
lookups for prefixes that are not changing. We're sacrificing this
functionality in the name of simpler code. Attempt to make that work
would either require a full conversion to RCU or a per-subtable
synchronization. Lookups can be done without the prefix match
optimizations for a brief period of time. This doesn't affect
correctness of the resulted datapath flows.
In the actual implementation instead of dropping cls->n_tries to zero
at step one, we keep the access to the first N tries that are not
going to change by setting the cls->n_tries to the index of the first
trie that will be updated. So, we'll not be disabling all the prefix
match optimizations completely.
There was an attempt to solve this problem already in commit:
a6117059904b ("classifier: Prevent tries vs n_tries race leading to NULL dereference.")
But it was focused on one particular crash and didn't take into account
a wider issue with the memory ordering on these trees in general. The
changes made in that commit are mostly reverted as not needed anymore.
Fixes: f358a2cb2e54 ("lib/classifier: RCUify prefix trie code.")
Reported-at: https://mail.openvswitch.org/pipermail/ovs-dev/2025-April/422765.html
Reported-by: Numan Siddique <numans@ovn.org>
Acked-by: Eelco Chaudron <echaudro@redhat.com>
Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
2025-05-16 23:25:16 +02:00
|
|
|
|
uint32_t n_tries;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
unsigned int x;
|
|
|
|
|
|
2013-11-19 17:31:29 -08:00
|
|
|
|
flow_wildcards_init_catchall(&wc);
|
2013-06-25 09:22:11 -07:00
|
|
|
|
x = random_range(N_FLOW_VALUES);
|
2012-06-18 15:09:49 -07:00
|
|
|
|
memset(&flow, 0, sizeof flow);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
flow.nw_src = nw_src_values[get_value(&x, N_NW_SRC_VALUES)];
|
|
|
|
|
flow.nw_dst = nw_dst_values[get_value(&x, N_NW_DST_VALUES)];
|
2012-09-13 20:11:08 -07:00
|
|
|
|
flow.tunnel.tun_id = tun_id_values[get_value(&x, N_TUN_ID_VALUES)];
|
2012-07-12 00:01:11 +12:00
|
|
|
|
flow.metadata = metadata_values[get_value(&x, N_METADATA_VALUES)];
|
2013-06-19 16:58:44 -07:00
|
|
|
|
flow.in_port.ofp_port = in_port_values[get_value(&x,
|
|
|
|
|
N_IN_PORT_VALUES)];
|
2017-03-01 17:47:59 -05:00
|
|
|
|
flow.vlans[0].tci = vlan_tci_values[get_value(&x, N_VLAN_TCI_VALUES)];
|
2009-07-08 13:19:16 -07:00
|
|
|
|
flow.dl_type = dl_type_values[get_value(&x, N_DL_TYPE_VALUES)];
|
|
|
|
|
flow.tp_src = tp_src_values[get_value(&x, N_TP_SRC_VALUES)];
|
|
|
|
|
flow.tp_dst = tp_dst_values[get_value(&x, N_TP_DST_VALUES)];
|
2015-08-28 14:55:11 -07:00
|
|
|
|
flow.dl_src = dl_src_values[get_value(&x, N_DL_SRC_VALUES)];
|
|
|
|
|
flow.dl_dst = dl_dst_values[get_value(&x, N_DL_DST_VALUES)];
|
2009-07-08 13:19:16 -07:00
|
|
|
|
flow.nw_proto = nw_proto_values[get_value(&x, N_NW_PROTO_VALUES)];
|
2011-11-09 23:39:16 -08:00
|
|
|
|
flow.nw_tos = nw_dscp_values[get_value(&x, N_NW_DSCP_VALUES)];
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2014-06-23 10:13:41 -07:00
|
|
|
|
/* This assertion is here to suppress a GCC 4.9 array-bounds warning */
|
classifier: Fix race for prefix tree configuration.
The thread fence in the classifier is supposed to ensure that when the
subtable->trie_plen is updated, the actual prefix tree is ready to be
used. On the write side in trie_init(), the fence is between the
tree configuration and the 'trie_plen' update. On the reader's side
however, the fence is at the beginning of the classifier_lookup__(),
and both reads of the 'trie_plen' and the accesses to the tree itself
are happening afterwards. And since both types of the reads are on
the same side of the fence, the fence is kind of pointless and doesn't
guarantee any memory ordering. So, readers can be accessing partially
initialized prefix trees.
Another problem with the configuration is that cls->n_tries is updated
without any synchronization as well. The comment on the fence says
that it also synchronizes for the cls->n_tries, but that doesn't make
a lot of sense. In practice, cls->n_tries is read multiple times
throughout the classifier_lookup__() and each of these reads may give
a different value if there is a concurrent update, causing the reader
to access trees that are not initialized or in the middle of being
destroyed, leading to OVS crashes while the user updates the flow
table prefixes.
First thing that needs to be fixed here is to only read cls->n_tries
once to avoid obvious crashes with access to uninitialized trie_ctx[]
entries.
The second thing is that we need a proper memory synchronization that
will guarantee that our prefix trees are fully initialized when
readers access them. In the current logic we would need to issue
a thread fence after every read of a subtable->trie_plen value, i.e.,
we'd need a fence per subtable lookup. This would be very expensive
and wasteful, considering the prefix tree configuration normally
happens only once somewhere at startup.
What we can do instead is to convert cls->n_tries into atomic and use
it as a synchronization point:
Writer (classifier_set_prefix_fields):
1. Before making any changes, set cls->n_tries to zero. Relaxed
memory order can be used here, because we'll have a full memory
barrier at the next step.
2. ovsrcu_synchronize() to wait for all threads to stop using tries.
3. Update tries while nobody is using them.
4. Set cls->n_tries to a new value with memory_order_release.
Reader (classifier_lookup):
1. Read the cls->n_tries with the memory_order_acquire.
2. Use that once read value throughout.
RCU in this scenario will ensure that every thread no longer uses the
prefix trees when we're about to change them. The acquire-release
semantics on the cls->n_tries just saves us from calling the
ovsrcu_synchronize() the second time once we're done with the whole
reconfiguration. We're just updating the number and making all the
previous changes visible on CPUs that acquire it.
Alternative solution might be to go full RCU and make the array of
trees itself RCU-protected. This way we would not need to do any
extra RCU synchronization or managing the memory ordering. However,
that would mean having multiple layers of RCU with trees and rules
in them potentially surviving multiple grace periods, which I would
like to avoid, if possible.
Previous code was also trying to be smart and not disable prefix tree
lookups for prefixes that are not changing. We're sacrificing this
functionality in the name of simpler code. Attempt to make that work
would either require a full conversion to RCU or a per-subtable
synchronization. Lookups can be done without the prefix match
optimizations for a brief period of time. This doesn't affect
correctness of the resulted datapath flows.
In the actual implementation instead of dropping cls->n_tries to zero
at step one, we keep the access to the first N tries that are not
going to change by setting the cls->n_tries to the index of the first
trie that will be updated. So, we'll not be disabling all the prefix
match optimizations completely.
There was an attempt to solve this problem already in commit:
a6117059904b ("classifier: Prevent tries vs n_tries race leading to NULL dereference.")
But it was focused on one particular crash and didn't take into account
a wider issue with the memory ordering on these trees in general. The
changes made in that commit are mostly reverted as not needed anymore.
Fixes: f358a2cb2e54 ("lib/classifier: RCUify prefix trie code.")
Reported-at: https://mail.openvswitch.org/pipermail/ovs-dev/2025-April/422765.html
Reported-by: Numan Siddique <numans@ovn.org>
Acked-by: Eelco Chaudron <echaudro@redhat.com>
Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
2025-05-16 23:25:16 +02:00
|
|
|
|
atomic_read_relaxed(&cls->n_tries, &n_tries);
|
|
|
|
|
ovs_assert(n_tries <= CLS_MAX_TRIES);
|
2014-06-23 10:13:41 -07:00
|
|
|
|
|
2023-11-15 18:47:33 +09:00
|
|
|
|
cr0 = classifier_lookup(cls, version, &flow, &wc, NULL);
|
2010-10-28 13:26:31 -07:00
|
|
|
|
cr1 = tcls_lookup(tcls, &flow);
|
|
|
|
|
assert((cr0 == NULL) == (cr1 == NULL));
|
|
|
|
|
if (cr0 != NULL) {
|
|
|
|
|
const struct test_rule *tr0 = test_rule_from_cls_rule(cr0);
|
|
|
|
|
const struct test_rule *tr1 = test_rule_from_cls_rule(cr1);
|
|
|
|
|
|
2010-11-08 16:35:34 -08:00
|
|
|
|
assert(cls_rule_equal(cr0, cr1));
|
2010-10-28 13:26:31 -07:00
|
|
|
|
assert(tr0->aux == tr1->aux);
|
2015-06-11 15:53:43 -07:00
|
|
|
|
|
|
|
|
|
/* Make sure the rule should have been visible. */
|
2016-04-17 08:51:21 -07:00
|
|
|
|
assert(cls_rule_visible_in_version(cr0, version));
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
2023-11-15 18:47:33 +09:00
|
|
|
|
cr2 = classifier_lookup(cls, version, &flow, NULL, NULL);
|
2013-11-19 17:31:29 -08:00
|
|
|
|
assert(cr2 == cr0);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
destroy_classifier(struct classifier *cls)
|
|
|
|
|
{
|
2014-07-21 21:00:04 -07:00
|
|
|
|
struct test_rule *rule;
|
2010-10-28 16:18:20 -07:00
|
|
|
|
|
2014-11-13 11:54:31 -08:00
|
|
|
|
classifier_defer(cls);
|
2014-11-13 11:54:31 -08:00
|
|
|
|
CLS_FOR_EACH (rule, cls_rule, cls) {
|
2018-01-30 13:00:31 -08:00
|
|
|
|
classifier_remove_assert(cls, &rule->cls_rule);
|
|
|
|
|
ovsrcu_postpone(free_rule, rule);
|
2010-10-28 16:18:20 -07:00
|
|
|
|
}
|
2009-07-08 13:19:16 -07:00
|
|
|
|
classifier_destroy(cls);
|
|
|
|
|
}
|
|
|
|
|
|
2014-06-26 07:41:25 -07:00
|
|
|
|
static void
|
2016-08-10 14:58:51 -07:00
|
|
|
|
pvector_verify(const struct pvector *pvec)
|
2014-06-26 07:41:25 -07:00
|
|
|
|
{
|
|
|
|
|
void *ptr OVS_UNUSED;
|
2014-10-30 11:40:07 -07:00
|
|
|
|
int prev_priority = INT_MAX;
|
2014-06-26 07:41:25 -07:00
|
|
|
|
|
2016-08-10 14:58:51 -07:00
|
|
|
|
PVECTOR_FOR_EACH (ptr, pvec) {
|
2014-10-30 11:40:07 -07:00
|
|
|
|
int priority = cursor__.vector[cursor__.entry_idx].priority;
|
2014-06-26 07:41:25 -07:00
|
|
|
|
if (priority > prev_priority) {
|
2014-10-24 13:22:24 -07:00
|
|
|
|
ovs_abort(0, "Priority vector is out of order (%u > %u)",
|
|
|
|
|
priority, prev_priority);
|
2014-06-26 07:41:25 -07:00
|
|
|
|
}
|
|
|
|
|
prev_priority = priority;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-11 02:29:08 -07:00
|
|
|
|
static unsigned int
|
|
|
|
|
trie_verify(const rcu_trie_ptr *trie, unsigned int ofs, unsigned int n_bits)
|
|
|
|
|
{
|
|
|
|
|
const struct trie_node *node = ovsrcu_get(struct trie_node *, trie);
|
|
|
|
|
|
|
|
|
|
if (node) {
|
|
|
|
|
assert(node->n_rules == 0 || node->n_bits > 0);
|
|
|
|
|
ofs += node->n_bits;
|
|
|
|
|
assert((ofs > 0 || (ofs == 0 && node->n_bits == 0)) && ofs <= n_bits);
|
|
|
|
|
|
|
|
|
|
return node->n_rules
|
|
|
|
|
+ trie_verify(&node->edges[0], ofs, n_bits)
|
|
|
|
|
+ trie_verify(&node->edges[1], ofs, n_bits);
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2014-07-18 02:24:26 -07:00
|
|
|
|
verify_tries(struct classifier *cls)
|
2014-11-14 15:58:09 -08:00
|
|
|
|
OVS_NO_THREAD_SAFETY_ANALYSIS
|
2014-07-11 02:29:08 -07:00
|
|
|
|
{
|
2025-05-16 23:25:15 +02:00
|
|
|
|
unsigned int n_rules;
|
classifier: Fix race for prefix tree configuration.
The thread fence in the classifier is supposed to ensure that when the
subtable->trie_plen is updated, the actual prefix tree is ready to be
used. On the write side in trie_init(), the fence is between the
tree configuration and the 'trie_plen' update. On the reader's side
however, the fence is at the beginning of the classifier_lookup__(),
and both reads of the 'trie_plen' and the accesses to the tree itself
are happening afterwards. And since both types of the reads are on
the same side of the fence, the fence is kind of pointless and doesn't
guarantee any memory ordering. So, readers can be accessing partially
initialized prefix trees.
Another problem with the configuration is that cls->n_tries is updated
without any synchronization as well. The comment on the fence says
that it also synchronizes for the cls->n_tries, but that doesn't make
a lot of sense. In practice, cls->n_tries is read multiple times
throughout the classifier_lookup__() and each of these reads may give
a different value if there is a concurrent update, causing the reader
to access trees that are not initialized or in the middle of being
destroyed, leading to OVS crashes while the user updates the flow
table prefixes.
First thing that needs to be fixed here is to only read cls->n_tries
once to avoid obvious crashes with access to uninitialized trie_ctx[]
entries.
The second thing is that we need a proper memory synchronization that
will guarantee that our prefix trees are fully initialized when
readers access them. In the current logic we would need to issue
a thread fence after every read of a subtable->trie_plen value, i.e.,
we'd need a fence per subtable lookup. This would be very expensive
and wasteful, considering the prefix tree configuration normally
happens only once somewhere at startup.
What we can do instead is to convert cls->n_tries into atomic and use
it as a synchronization point:
Writer (classifier_set_prefix_fields):
1. Before making any changes, set cls->n_tries to zero. Relaxed
memory order can be used here, because we'll have a full memory
barrier at the next step.
2. ovsrcu_synchronize() to wait for all threads to stop using tries.
3. Update tries while nobody is using them.
4. Set cls->n_tries to a new value with memory_order_release.
Reader (classifier_lookup):
1. Read the cls->n_tries with the memory_order_acquire.
2. Use that once read value throughout.
RCU in this scenario will ensure that every thread no longer uses the
prefix trees when we're about to change them. The acquire-release
semantics on the cls->n_tries just saves us from calling the
ovsrcu_synchronize() the second time once we're done with the whole
reconfiguration. We're just updating the number and making all the
previous changes visible on CPUs that acquire it.
Alternative solution might be to go full RCU and make the array of
trees itself RCU-protected. This way we would not need to do any
extra RCU synchronization or managing the memory ordering. However,
that would mean having multiple layers of RCU with trees and rules
in them potentially surviving multiple grace periods, which I would
like to avoid, if possible.
Previous code was also trying to be smart and not disable prefix tree
lookups for prefixes that are not changing. We're sacrificing this
functionality in the name of simpler code. Attempt to make that work
would either require a full conversion to RCU or a per-subtable
synchronization. Lookups can be done without the prefix match
optimizations for a brief period of time. This doesn't affect
correctness of the resulted datapath flows.
In the actual implementation instead of dropping cls->n_tries to zero
at step one, we keep the access to the first N tries that are not
going to change by setting the cls->n_tries to the index of the first
trie that will be updated. So, we'll not be disabling all the prefix
match optimizations completely.
There was an attempt to solve this problem already in commit:
a6117059904b ("classifier: Prevent tries vs n_tries race leading to NULL dereference.")
But it was focused on one particular crash and didn't take into account
a wider issue with the memory ordering on these trees in general. The
changes made in that commit are mostly reverted as not needed anymore.
Fixes: f358a2cb2e54 ("lib/classifier: RCUify prefix trie code.")
Reported-at: https://mail.openvswitch.org/pipermail/ovs-dev/2025-April/422765.html
Reported-by: Numan Siddique <numans@ovn.org>
Acked-by: Eelco Chaudron <echaudro@redhat.com>
Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
2025-05-16 23:25:16 +02:00
|
|
|
|
uint32_t i, n_tries;
|
2014-07-11 02:29:08 -07:00
|
|
|
|
|
classifier: Fix race for prefix tree configuration.
The thread fence in the classifier is supposed to ensure that when the
subtable->trie_plen is updated, the actual prefix tree is ready to be
used. On the write side in trie_init(), the fence is between the
tree configuration and the 'trie_plen' update. On the reader's side
however, the fence is at the beginning of the classifier_lookup__(),
and both reads of the 'trie_plen' and the accesses to the tree itself
are happening afterwards. And since both types of the reads are on
the same side of the fence, the fence is kind of pointless and doesn't
guarantee any memory ordering. So, readers can be accessing partially
initialized prefix trees.
Another problem with the configuration is that cls->n_tries is updated
without any synchronization as well. The comment on the fence says
that it also synchronizes for the cls->n_tries, but that doesn't make
a lot of sense. In practice, cls->n_tries is read multiple times
throughout the classifier_lookup__() and each of these reads may give
a different value if there is a concurrent update, causing the reader
to access trees that are not initialized or in the middle of being
destroyed, leading to OVS crashes while the user updates the flow
table prefixes.
First thing that needs to be fixed here is to only read cls->n_tries
once to avoid obvious crashes with access to uninitialized trie_ctx[]
entries.
The second thing is that we need a proper memory synchronization that
will guarantee that our prefix trees are fully initialized when
readers access them. In the current logic we would need to issue
a thread fence after every read of a subtable->trie_plen value, i.e.,
we'd need a fence per subtable lookup. This would be very expensive
and wasteful, considering the prefix tree configuration normally
happens only once somewhere at startup.
What we can do instead is to convert cls->n_tries into atomic and use
it as a synchronization point:
Writer (classifier_set_prefix_fields):
1. Before making any changes, set cls->n_tries to zero. Relaxed
memory order can be used here, because we'll have a full memory
barrier at the next step.
2. ovsrcu_synchronize() to wait for all threads to stop using tries.
3. Update tries while nobody is using them.
4. Set cls->n_tries to a new value with memory_order_release.
Reader (classifier_lookup):
1. Read the cls->n_tries with the memory_order_acquire.
2. Use that once read value throughout.
RCU in this scenario will ensure that every thread no longer uses the
prefix trees when we're about to change them. The acquire-release
semantics on the cls->n_tries just saves us from calling the
ovsrcu_synchronize() the second time once we're done with the whole
reconfiguration. We're just updating the number and making all the
previous changes visible on CPUs that acquire it.
Alternative solution might be to go full RCU and make the array of
trees itself RCU-protected. This way we would not need to do any
extra RCU synchronization or managing the memory ordering. However,
that would mean having multiple layers of RCU with trees and rules
in them potentially surviving multiple grace periods, which I would
like to avoid, if possible.
Previous code was also trying to be smart and not disable prefix tree
lookups for prefixes that are not changing. We're sacrificing this
functionality in the name of simpler code. Attempt to make that work
would either require a full conversion to RCU or a per-subtable
synchronization. Lookups can be done without the prefix match
optimizations for a brief period of time. This doesn't affect
correctness of the resulted datapath flows.
In the actual implementation instead of dropping cls->n_tries to zero
at step one, we keep the access to the first N tries that are not
going to change by setting the cls->n_tries to the index of the first
trie that will be updated. So, we'll not be disabling all the prefix
match optimizations completely.
There was an attempt to solve this problem already in commit:
a6117059904b ("classifier: Prevent tries vs n_tries race leading to NULL dereference.")
But it was focused on one particular crash and didn't take into account
a wider issue with the memory ordering on these trees in general. The
changes made in that commit are mostly reverted as not needed anymore.
Fixes: f358a2cb2e54 ("lib/classifier: RCUify prefix trie code.")
Reported-at: https://mail.openvswitch.org/pipermail/ovs-dev/2025-April/422765.html
Reported-by: Numan Siddique <numans@ovn.org>
Acked-by: Eelco Chaudron <echaudro@redhat.com>
Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
2025-05-16 23:25:16 +02:00
|
|
|
|
atomic_read_explicit(&cls->n_tries, &n_tries, memory_order_acquire);
|
|
|
|
|
for (i = 0; i < n_tries; i++) {
|
|
|
|
|
n_rules = trie_verify(&cls->tries[i].root, 0,
|
|
|
|
|
cls->tries[i].field->n_bits);
|
2025-05-16 23:25:15 +02:00
|
|
|
|
assert(n_rules <= cls->n_rules);
|
2014-07-11 02:29:08 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2009-07-08 13:19:16 -07:00
|
|
|
|
static void
|
2013-07-11 14:19:11 -07:00
|
|
|
|
check_tables(const struct classifier *cls, int n_tables, int n_rules,
|
2016-07-29 16:52:01 -07:00
|
|
|
|
int n_dups, int n_invisible, ovs_version_t version)
|
2014-11-14 15:58:09 -08:00
|
|
|
|
OVS_NO_THREAD_SAFETY_ANALYSIS
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
2013-10-29 16:39:52 -07:00
|
|
|
|
const struct cls_subtable *table;
|
2010-11-19 16:41:02 -08:00
|
|
|
|
struct test_rule *test_rule;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
int found_tables = 0;
|
2015-06-11 15:53:43 -07:00
|
|
|
|
int found_tables_with_visible_rules = 0;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
int found_rules = 0;
|
2010-11-03 11:00:58 -07:00
|
|
|
|
int found_dups = 0;
|
2015-06-11 15:53:43 -07:00
|
|
|
|
int found_invisible = 0;
|
|
|
|
|
int found_visible_but_removable = 0;
|
2010-11-19 16:41:02 -08:00
|
|
|
|
int found_rules2 = 0;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2016-08-10 14:58:51 -07:00
|
|
|
|
pvector_verify(&cls->subtables);
|
2014-07-18 02:24:26 -07:00
|
|
|
|
CMAP_FOR_EACH (table, cmap_node, &cls->subtables_map) {
|
2014-04-29 15:50:38 -07:00
|
|
|
|
const struct cls_match *head;
|
2014-10-30 11:40:07 -07:00
|
|
|
|
int max_priority = INT_MIN;
|
2013-02-08 00:06:22 +02:00
|
|
|
|
unsigned int max_count = 0;
|
2014-06-26 07:41:25 -07:00
|
|
|
|
bool found = false;
|
2015-06-11 15:53:43 -07:00
|
|
|
|
bool found_visible_rules = false;
|
2014-06-26 07:41:25 -07:00
|
|
|
|
const struct cls_subtable *iter;
|
|
|
|
|
|
|
|
|
|
/* Locate the subtable from 'subtables'. */
|
2016-08-10 14:58:51 -07:00
|
|
|
|
PVECTOR_FOR_EACH (iter, &cls->subtables) {
|
2014-06-26 07:41:25 -07:00
|
|
|
|
if (iter == table) {
|
|
|
|
|
if (found) {
|
2014-10-24 13:22:24 -07:00
|
|
|
|
ovs_abort(0, "Subtable %p duplicated in 'subtables'.",
|
|
|
|
|
table);
|
2014-06-26 07:41:25 -07:00
|
|
|
|
}
|
|
|
|
|
found = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!found) {
|
2014-10-24 13:22:24 -07:00
|
|
|
|
ovs_abort(0, "Subtable %p not found from 'subtables'.", table);
|
2014-06-26 07:41:25 -07:00
|
|
|
|
}
|
2010-11-03 11:00:58 -07:00
|
|
|
|
|
2014-07-11 02:29:07 -07:00
|
|
|
|
assert(!cmap_is_empty(&table->rules));
|
2014-07-11 02:29:08 -07:00
|
|
|
|
assert(trie_verify(&table->ports_trie, 0, table->ports_mask_len)
|
2014-11-14 14:47:03 -08:00
|
|
|
|
== (table->ports_mask_len ? cmap_count(&table->rules) : 0));
|
2014-07-11 02:29:08 -07:00
|
|
|
|
|
2009-07-08 13:19:16 -07:00
|
|
|
|
found_tables++;
|
2015-06-11 15:53:43 -07:00
|
|
|
|
|
2014-07-11 02:29:07 -07:00
|
|
|
|
CMAP_FOR_EACH (head, cmap_node, &table->rules) {
|
2014-10-30 11:40:07 -07:00
|
|
|
|
int prev_priority = INT_MAX;
|
2016-07-29 16:52:01 -07:00
|
|
|
|
ovs_version_t prev_version = 0;
|
2015-06-11 15:53:43 -07:00
|
|
|
|
const struct cls_match *rule, *prev;
|
|
|
|
|
bool found_visible_rules_in_list = false;
|
|
|
|
|
|
|
|
|
|
assert(head->priority <= table->max_priority);
|
2010-11-03 11:00:58 -07:00
|
|
|
|
|
2013-02-08 00:06:22 +02:00
|
|
|
|
if (head->priority > max_priority) {
|
|
|
|
|
max_priority = head->priority;
|
2015-06-11 15:53:43 -07:00
|
|
|
|
max_count = 0;
|
2013-02-08 00:06:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
2015-06-11 15:53:43 -07:00
|
|
|
|
FOR_EACH_RULE_IN_LIST_PROTECTED(rule, prev, head) {
|
2016-07-29 16:52:01 -07:00
|
|
|
|
ovs_version_t rule_version;
|
2015-06-11 15:53:43 -07:00
|
|
|
|
const struct cls_rule *found_rule;
|
|
|
|
|
|
|
|
|
|
/* Priority may not increase. */
|
|
|
|
|
assert(rule->priority <= prev_priority);
|
|
|
|
|
|
|
|
|
|
if (rule->priority == max_priority) {
|
|
|
|
|
++max_count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Count invisible rules and visible duplicates. */
|
|
|
|
|
if (!cls_match_visible_in_version(rule, version)) {
|
|
|
|
|
found_invisible++;
|
|
|
|
|
} else {
|
|
|
|
|
if (cls_match_is_eventually_invisible(rule)) {
|
|
|
|
|
found_visible_but_removable++;
|
|
|
|
|
}
|
|
|
|
|
if (found_visible_rules_in_list) {
|
|
|
|
|
found_dups++;
|
|
|
|
|
}
|
|
|
|
|
found_visible_rules_in_list = true;
|
|
|
|
|
found_visible_rules = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Rule must be visible in the version it was inserted. */
|
2016-07-29 16:52:01 -07:00
|
|
|
|
rule_version = rule->versions.add_version;
|
2015-06-11 15:53:43 -07:00
|
|
|
|
assert(cls_match_visible_in_version(rule, rule_version));
|
|
|
|
|
|
|
|
|
|
/* We should always find the latest version of the rule,
|
|
|
|
|
* unless all rules have been marked for removal.
|
|
|
|
|
* Later versions must always be later in the list. */
|
2015-07-06 11:45:54 -07:00
|
|
|
|
found_rule = classifier_find_rule_exactly(cls, rule->cls_rule,
|
|
|
|
|
rule_version);
|
2015-06-11 15:53:43 -07:00
|
|
|
|
if (found_rule && found_rule != rule->cls_rule) {
|
2016-04-17 08:51:21 -07:00
|
|
|
|
struct cls_match *cls_match;
|
|
|
|
|
cls_match = get_cls_match_protected(found_rule);
|
2015-06-11 15:53:43 -07:00
|
|
|
|
|
|
|
|
|
assert(found_rule->priority == rule->priority);
|
|
|
|
|
|
|
|
|
|
/* Found rule may not have a lower version. */
|
2016-07-29 16:52:01 -07:00
|
|
|
|
assert(cls_match->versions.add_version >= rule_version);
|
2015-06-11 15:53:43 -07:00
|
|
|
|
|
|
|
|
|
/* This rule must not be visible in the found rule's
|
|
|
|
|
* version. */
|
2015-07-06 11:45:54 -07:00
|
|
|
|
assert(!cls_match_visible_in_version(
|
2016-07-29 16:52:01 -07:00
|
|
|
|
rule, cls_match->versions.add_version));
|
2015-06-11 15:53:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (rule->priority == prev_priority) {
|
|
|
|
|
/* Exact duplicate rule may not have a lower version. */
|
|
|
|
|
assert(rule_version >= prev_version);
|
|
|
|
|
|
|
|
|
|
/* Previous rule must not be visible in rule's version. */
|
|
|
|
|
assert(!cls_match_visible_in_version(prev, rule_version));
|
|
|
|
|
}
|
2013-02-08 00:06:22 +02:00
|
|
|
|
|
2010-11-03 11:00:58 -07:00
|
|
|
|
prev_priority = rule->priority;
|
2015-06-11 15:53:43 -07:00
|
|
|
|
prev_version = rule_version;
|
2010-11-03 11:00:58 -07:00
|
|
|
|
found_rules++;
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-06-11 15:53:43 -07:00
|
|
|
|
|
|
|
|
|
if (found_visible_rules) {
|
|
|
|
|
found_tables_with_visible_rules++;
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-08 00:06:22 +02:00
|
|
|
|
assert(table->max_priority == max_priority);
|
|
|
|
|
assert(table->max_count == max_count);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
|
2014-07-18 02:24:26 -07:00
|
|
|
|
assert(found_tables == cmap_count(&cls->subtables_map));
|
2016-08-10 14:58:51 -07:00
|
|
|
|
assert(found_tables == pvector_count(&cls->subtables));
|
2015-06-11 15:53:43 -07:00
|
|
|
|
assert(n_tables == -1 || n_tables == found_tables_with_visible_rules);
|
|
|
|
|
assert(n_rules == -1 || found_rules == n_rules + found_invisible);
|
2010-11-03 11:00:58 -07:00
|
|
|
|
assert(n_dups == -1 || found_dups == n_dups);
|
2015-06-11 15:53:43 -07:00
|
|
|
|
assert(found_invisible == n_invisible);
|
2010-11-19 16:41:02 -08:00
|
|
|
|
|
2014-07-11 02:29:07 -07:00
|
|
|
|
CLS_FOR_EACH (test_rule, cls_rule, cls) {
|
2010-11-19 16:41:02 -08:00
|
|
|
|
found_rules2++;
|
|
|
|
|
}
|
2015-06-11 15:53:43 -07:00
|
|
|
|
/* Iteration does not see removable rules. */
|
|
|
|
|
assert(found_rules
|
|
|
|
|
== found_rules2 + found_visible_but_removable + found_invisible);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct test_rule *
|
2015-07-06 11:45:54 -07:00
|
|
|
|
make_rule(int wc_fields, int priority, int value_pat)
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
|
|
|
|
const struct cls_field *f;
|
|
|
|
|
struct test_rule *rule;
|
2012-08-07 15:28:18 -07:00
|
|
|
|
struct match match;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2012-08-07 15:28:18 -07:00
|
|
|
|
match_init_catchall(&match);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
for (f = &cls_fields[0]; f < &cls_fields[CLS_N_FIELDS]; f++) {
|
|
|
|
|
int f_idx = f - cls_fields;
|
2010-11-10 14:39:54 -08:00
|
|
|
|
int value_idx = (value_pat & (1u << f_idx)) != 0;
|
2012-08-07 15:28:18 -07:00
|
|
|
|
memcpy((char *) &match.flow + f->ofs,
|
2010-11-10 14:39:54 -08:00
|
|
|
|
values[f_idx][value_idx], f->len);
|
|
|
|
|
|
2012-08-03 13:27:15 -07:00
|
|
|
|
if (f_idx == CLS_F_IDX_NW_SRC) {
|
2013-06-27 15:27:15 -07:00
|
|
|
|
match.wc.masks.nw_src = OVS_BE32_MAX;
|
2010-11-10 14:39:54 -08:00
|
|
|
|
} else if (f_idx == CLS_F_IDX_NW_DST) {
|
2013-06-27 15:27:15 -07:00
|
|
|
|
match.wc.masks.nw_dst = OVS_BE32_MAX;
|
2012-01-27 17:16:05 -08:00
|
|
|
|
} else if (f_idx == CLS_F_IDX_TP_SRC) {
|
2013-06-27 15:27:15 -07:00
|
|
|
|
match.wc.masks.tp_src = OVS_BE16_MAX;
|
2012-01-27 17:16:05 -08:00
|
|
|
|
} else if (f_idx == CLS_F_IDX_TP_DST) {
|
2013-06-27 15:27:15 -07:00
|
|
|
|
match.wc.masks.tp_dst = OVS_BE16_MAX;
|
2012-05-29 00:38:21 +12:00
|
|
|
|
} else if (f_idx == CLS_F_IDX_DL_SRC) {
|
2015-08-28 14:55:11 -07:00
|
|
|
|
WC_MASK_FIELD(&match.wc, dl_src);
|
2012-05-29 00:38:21 +12:00
|
|
|
|
} else if (f_idx == CLS_F_IDX_DL_DST) {
|
2015-08-28 14:55:11 -07:00
|
|
|
|
WC_MASK_FIELD(&match.wc, dl_dst);
|
2010-11-23 10:06:28 -08:00
|
|
|
|
} else if (f_idx == CLS_F_IDX_VLAN_TCI) {
|
2017-03-01 17:47:59 -05:00
|
|
|
|
match.wc.masks.vlans[0].tci = OVS_BE16_MAX;
|
2011-01-20 15:29:00 -08:00
|
|
|
|
} else if (f_idx == CLS_F_IDX_TUN_ID) {
|
2013-06-27 15:27:15 -07:00
|
|
|
|
match.wc.masks.tunnel.tun_id = OVS_BE64_MAX;
|
2012-07-12 00:01:11 +12:00
|
|
|
|
} else if (f_idx == CLS_F_IDX_METADATA) {
|
2013-06-27 15:27:15 -07:00
|
|
|
|
match.wc.masks.metadata = OVS_BE64_MAX;
|
2012-06-18 14:11:13 -07:00
|
|
|
|
} else if (f_idx == CLS_F_IDX_NW_DSCP) {
|
2012-08-07 15:28:18 -07:00
|
|
|
|
match.wc.masks.nw_tos |= IP_DSCP_MASK;
|
2012-06-18 14:12:52 -07:00
|
|
|
|
} else if (f_idx == CLS_F_IDX_NW_PROTO) {
|
2012-08-07 15:28:18 -07:00
|
|
|
|
match.wc.masks.nw_proto = UINT8_MAX;
|
2012-06-18 13:33:13 -07:00
|
|
|
|
} else if (f_idx == CLS_F_IDX_DL_TYPE) {
|
2013-06-27 15:27:15 -07:00
|
|
|
|
match.wc.masks.dl_type = OVS_BE16_MAX;
|
2012-08-03 13:27:15 -07:00
|
|
|
|
} else if (f_idx == CLS_F_IDX_IN_PORT) {
|
2013-06-20 15:10:04 -07:00
|
|
|
|
match.wc.masks.in_port.ofp_port = u16_to_ofp(UINT16_MAX);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
} else {
|
2013-12-17 10:32:12 -08:00
|
|
|
|
OVS_NOT_REACHED();
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
2012-08-07 15:28:18 -07:00
|
|
|
|
|
|
|
|
|
rule = xzalloc(sizeof *rule);
|
2014-10-31 14:14:56 -07:00
|
|
|
|
cls_rule_init(&rule->cls_rule, &match, wc_fields
|
2015-06-11 15:53:43 -07:00
|
|
|
|
? (priority == INT_MIN ? priority + 1 :
|
|
|
|
|
priority == INT_MAX ? priority - 1 : priority)
|
2015-07-06 11:45:54 -07:00
|
|
|
|
: 0);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
return rule;
|
|
|
|
|
}
|
|
|
|
|
|
2012-08-20 11:29:43 -07:00
|
|
|
|
static struct test_rule *
|
|
|
|
|
clone_rule(const struct test_rule *src)
|
|
|
|
|
{
|
|
|
|
|
struct test_rule *dst;
|
|
|
|
|
|
|
|
|
|
dst = xmalloc(sizeof *dst);
|
|
|
|
|
dst->aux = src->aux;
|
|
|
|
|
cls_rule_clone(&dst->cls_rule, &src->cls_rule);
|
|
|
|
|
return dst;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
free_rule(struct test_rule *rule)
|
|
|
|
|
{
|
|
|
|
|
cls_rule_destroy(&rule->cls_rule);
|
|
|
|
|
free(rule);
|
|
|
|
|
}
|
|
|
|
|
|
2009-07-08 13:19:16 -07:00
|
|
|
|
static void
|
2014-10-30 11:40:07 -07:00
|
|
|
|
shuffle(int *p, size_t n)
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
|
|
|
|
for (; n > 1; n--, p++) {
|
2014-10-30 11:40:07 -07:00
|
|
|
|
int *q = &p[random_range(n)];
|
|
|
|
|
int tmp = *p;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
*p = *q;
|
|
|
|
|
*q = tmp;
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-09-04 12:43:53 -07:00
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
shuffle_u32s(uint32_t *p, size_t n)
|
|
|
|
|
{
|
|
|
|
|
for (; n > 1; n--, p++) {
|
2013-06-25 09:22:11 -07:00
|
|
|
|
uint32_t *q = &p[random_range(n)];
|
2012-09-04 12:43:53 -07:00
|
|
|
|
uint32_t tmp = *p;
|
|
|
|
|
*p = *q;
|
|
|
|
|
*q = tmp;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-05-16 23:25:17 +02:00
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
shuffle_fields(enum mf_field_id *p, size_t n)
|
|
|
|
|
{
|
|
|
|
|
for (; n > 1; n--, p++) {
|
|
|
|
|
enum mf_field_id *q = &p[random_range(n)];
|
|
|
|
|
enum mf_field_id tmp = *p;
|
|
|
|
|
*p = *q;
|
|
|
|
|
*q = tmp;
|
|
|
|
|
}
|
|
|
|
|
}
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2012-09-04 12:43:53 -07:00
|
|
|
|
/* Classifier tests. */
|
|
|
|
|
|
2025-05-16 23:25:17 +02:00
|
|
|
|
static enum mf_field_id trie_fields[4] = {
|
|
|
|
|
MFF_IPV4_DST, MFF_IPV4_SRC, MFF_IPV6_DST, MFF_IPV6_SRC,
|
2013-12-11 11:07:01 -08:00
|
|
|
|
};
|
|
|
|
|
|
2014-07-11 02:29:08 -07:00
|
|
|
|
static void
|
|
|
|
|
set_prefix_fields(struct classifier *cls)
|
|
|
|
|
{
|
|
|
|
|
verify_tries(cls);
|
|
|
|
|
classifier_set_prefix_fields(cls, trie_fields, ARRAY_SIZE(trie_fields));
|
|
|
|
|
verify_tries(cls);
|
|
|
|
|
}
|
|
|
|
|
|
2009-07-08 13:19:16 -07:00
|
|
|
|
/* Tests an empty classifier. */
|
|
|
|
|
static void
|
2015-03-17 10:35:26 -04:00
|
|
|
|
test_empty(struct ovs_cmdl_context *ctx OVS_UNUSED)
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
|
|
|
|
struct classifier cls;
|
|
|
|
|
struct tcls tcls;
|
|
|
|
|
|
2015-01-06 11:10:42 -08:00
|
|
|
|
classifier_init(&cls, flow_segment_u64s);
|
2014-07-11 02:29:08 -07:00
|
|
|
|
set_prefix_fields(&cls);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
tcls_init(&tcls);
|
|
|
|
|
assert(classifier_is_empty(&cls));
|
|
|
|
|
assert(tcls_is_empty(&tcls));
|
2016-07-29 16:52:01 -07:00
|
|
|
|
compare_classifiers(&cls, 0, OVS_VERSION_MIN, &tcls);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
classifier_destroy(&cls);
|
|
|
|
|
tcls_destroy(&tcls);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Destroys a null classifier. */
|
|
|
|
|
static void
|
2015-03-17 10:35:26 -04:00
|
|
|
|
test_destroy_null(struct ovs_cmdl_context *ctx OVS_UNUSED)
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
|
|
|
|
classifier_destroy(NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Tests classification with one rule at a time. */
|
|
|
|
|
static void
|
2015-03-17 10:35:26 -04:00
|
|
|
|
test_single_rule(struct ovs_cmdl_context *ctx OVS_UNUSED)
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
|
|
|
|
unsigned int wc_fields; /* Hilarious. */
|
|
|
|
|
|
|
|
|
|
for (wc_fields = 0; wc_fields < (1u << CLS_N_FIELDS); wc_fields++) {
|
|
|
|
|
struct classifier cls;
|
|
|
|
|
struct test_rule *rule, *tcls_rule;
|
|
|
|
|
struct tcls tcls;
|
|
|
|
|
|
|
|
|
|
rule = make_rule(wc_fields,
|
2015-07-06 11:45:54 -07:00
|
|
|
|
hash_bytes(&wc_fields, sizeof wc_fields, 0), 0);
|
2015-01-06 11:10:42 -08:00
|
|
|
|
classifier_init(&cls, flow_segment_u64s);
|
2014-07-11 02:29:08 -07:00
|
|
|
|
set_prefix_fields(&cls);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
tcls_init(&tcls);
|
|
|
|
|
tcls_rule = tcls_insert(&tcls, rule);
|
2015-06-11 15:53:43 -07:00
|
|
|
|
|
2016-07-29 16:52:01 -07:00
|
|
|
|
classifier_insert(&cls, &rule->cls_rule, OVS_VERSION_MIN, NULL, 0);
|
|
|
|
|
compare_classifiers(&cls, 0, OVS_VERSION_MIN, &tcls);
|
|
|
|
|
check_tables(&cls, 1, 1, 0, 0, OVS_VERSION_MIN);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2018-01-30 13:00:31 -08:00
|
|
|
|
classifier_remove_assert(&cls, &rule->cls_rule);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
tcls_remove(&tcls, tcls_rule);
|
|
|
|
|
assert(classifier_is_empty(&cls));
|
|
|
|
|
assert(tcls_is_empty(&tcls));
|
2016-07-29 16:52:01 -07:00
|
|
|
|
compare_classifiers(&cls, 0, OVS_VERSION_MIN, &tcls);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2014-10-29 09:59:57 -07:00
|
|
|
|
ovsrcu_postpone(free_rule, rule);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
classifier_destroy(&cls);
|
|
|
|
|
tcls_destroy(&tcls);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Tests replacing one rule by another. */
|
|
|
|
|
static void
|
2015-03-17 10:35:26 -04:00
|
|
|
|
test_rule_replacement(struct ovs_cmdl_context *ctx OVS_UNUSED)
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
|
|
|
|
unsigned int wc_fields;
|
|
|
|
|
|
|
|
|
|
for (wc_fields = 0; wc_fields < (1u << CLS_N_FIELDS); wc_fields++) {
|
|
|
|
|
struct classifier cls;
|
2010-02-10 10:55:45 -08:00
|
|
|
|
struct test_rule *rule1;
|
|
|
|
|
struct test_rule *rule2;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
struct tcls tcls;
|
|
|
|
|
|
2015-07-06 11:45:54 -07:00
|
|
|
|
rule1 = make_rule(wc_fields, OFP_DEFAULT_PRIORITY, UINT_MAX);
|
|
|
|
|
rule2 = make_rule(wc_fields, OFP_DEFAULT_PRIORITY, UINT_MAX);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
rule2->aux += 5;
|
|
|
|
|
rule2->aux += 5;
|
|
|
|
|
|
2015-01-06 11:10:42 -08:00
|
|
|
|
classifier_init(&cls, flow_segment_u64s);
|
2014-07-11 02:29:08 -07:00
|
|
|
|
set_prefix_fields(&cls);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
tcls_init(&tcls);
|
2010-02-10 10:55:45 -08:00
|
|
|
|
tcls_insert(&tcls, rule1);
|
2016-07-29 16:52:01 -07:00
|
|
|
|
classifier_insert(&cls, &rule1->cls_rule, OVS_VERSION_MIN, NULL, 0);
|
|
|
|
|
compare_classifiers(&cls, 0, OVS_VERSION_MIN, &tcls);
|
|
|
|
|
check_tables(&cls, 1, 1, 0, 0, OVS_VERSION_MIN);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
tcls_destroy(&tcls);
|
|
|
|
|
|
|
|
|
|
tcls_init(&tcls);
|
2010-02-10 10:55:45 -08:00
|
|
|
|
tcls_insert(&tcls, rule2);
|
2014-07-11 02:29:07 -07:00
|
|
|
|
|
2009-07-08 13:19:16 -07:00
|
|
|
|
assert(test_rule_from_cls_rule(
|
2016-07-29 16:52:01 -07:00
|
|
|
|
classifier_replace(&cls, &rule2->cls_rule, OVS_VERSION_MIN,
|
2015-01-11 13:25:24 -08:00
|
|
|
|
NULL, 0)) == rule1);
|
2014-10-29 09:59:57 -07:00
|
|
|
|
ovsrcu_postpone(free_rule, rule1);
|
2016-07-29 16:52:01 -07:00
|
|
|
|
compare_classifiers(&cls, 0, OVS_VERSION_MIN, &tcls);
|
|
|
|
|
check_tables(&cls, 1, 1, 0, 0, OVS_VERSION_MIN);
|
2014-11-13 11:54:31 -08:00
|
|
|
|
classifier_defer(&cls);
|
2018-01-30 13:00:31 -08:00
|
|
|
|
classifier_remove_assert(&cls, &rule2->cls_rule);
|
2014-07-11 02:29:07 -07:00
|
|
|
|
|
|
|
|
|
tcls_destroy(&tcls);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
destroy_classifier(&cls);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
2010-11-03 11:00:58 -07:00
|
|
|
|
factorial(int n_items)
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
2010-11-03 11:00:58 -07:00
|
|
|
|
int n, i;
|
|
|
|
|
|
|
|
|
|
n = 1;
|
|
|
|
|
for (i = 2; i <= n_items; i++) {
|
|
|
|
|
n *= i;
|
|
|
|
|
}
|
|
|
|
|
return n;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
|
2010-11-03 11:00:58 -07:00
|
|
|
|
static void
|
|
|
|
|
swap(int *a, int *b)
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
2010-11-03 11:00:58 -07:00
|
|
|
|
int tmp = *a;
|
|
|
|
|
*a = *b;
|
|
|
|
|
*b = tmp;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2010-11-03 11:00:58 -07:00
|
|
|
|
reverse(int *a, int n)
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
2010-11-03 11:00:58 -07:00
|
|
|
|
int i;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2010-11-03 11:00:58 -07:00
|
|
|
|
for (i = 0; i < n / 2; i++) {
|
|
|
|
|
int j = n - (i + 1);
|
|
|
|
|
swap(&a[i], &a[j]);
|
|
|
|
|
}
|
|
|
|
|
}
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2010-11-03 11:00:58 -07:00
|
|
|
|
static bool
|
|
|
|
|
next_permutation(int *a, int n)
|
|
|
|
|
{
|
|
|
|
|
int k;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2010-11-03 11:00:58 -07:00
|
|
|
|
for (k = n - 2; k >= 0; k--) {
|
|
|
|
|
if (a[k] < a[k + 1]) {
|
|
|
|
|
int l;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2010-11-03 11:00:58 -07:00
|
|
|
|
for (l = n - 1; ; l--) {
|
|
|
|
|
if (a[l] > a[k]) {
|
|
|
|
|
swap(&a[k], &a[l]);
|
|
|
|
|
reverse(a + (k + 1), n - (k + 1));
|
|
|
|
|
return true;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2010-11-03 11:00:58 -07:00
|
|
|
|
return false;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
|
2010-11-03 11:00:58 -07:00
|
|
|
|
/* Tests classification with rules that have the same matching criteria. */
|
2009-07-08 13:19:16 -07:00
|
|
|
|
static void
|
2015-03-17 10:35:26 -04:00
|
|
|
|
test_many_rules_in_one_list (struct ovs_cmdl_context *ctx OVS_UNUSED)
|
2010-11-03 11:00:58 -07:00
|
|
|
|
{
|
|
|
|
|
enum { N_RULES = 3 };
|
|
|
|
|
int n_pris;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2010-11-03 11:00:58 -07:00
|
|
|
|
for (n_pris = N_RULES; n_pris >= 1; n_pris--) {
|
|
|
|
|
int ops[N_RULES * 2];
|
|
|
|
|
int pris[N_RULES];
|
|
|
|
|
int n_permutations;
|
|
|
|
|
int i;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2010-11-03 11:00:58 -07:00
|
|
|
|
pris[0] = 0;
|
|
|
|
|
for (i = 1; i < N_RULES; i++) {
|
|
|
|
|
pris[i] = pris[i - 1] + (n_pris > i);
|
|
|
|
|
}
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2010-11-03 11:00:58 -07:00
|
|
|
|
for (i = 0; i < N_RULES * 2; i++) {
|
|
|
|
|
ops[i] = i / 2;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
|
2010-11-03 11:00:58 -07:00
|
|
|
|
n_permutations = 0;
|
|
|
|
|
do {
|
|
|
|
|
struct test_rule *rules[N_RULES];
|
|
|
|
|
struct test_rule *tcls_rules[N_RULES];
|
|
|
|
|
int pri_rules[N_RULES];
|
|
|
|
|
struct classifier cls;
|
|
|
|
|
struct tcls tcls;
|
2016-07-29 16:52:01 -07:00
|
|
|
|
ovs_version_t version = OVS_VERSION_MIN;
|
2015-06-11 15:53:43 -07:00
|
|
|
|
size_t n_invisible_rules = 0;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2010-11-03 11:00:58 -07:00
|
|
|
|
n_permutations++;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2010-11-03 11:00:58 -07:00
|
|
|
|
for (i = 0; i < N_RULES; i++) {
|
2015-07-06 11:45:54 -07:00
|
|
|
|
rules[i] = make_rule(456, pris[i], 0);
|
2010-11-03 11:00:58 -07:00
|
|
|
|
tcls_rules[i] = NULL;
|
|
|
|
|
pri_rules[i] = -1;
|
|
|
|
|
}
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2015-01-06 11:10:42 -08:00
|
|
|
|
classifier_init(&cls, flow_segment_u64s);
|
2014-07-11 02:29:08 -07:00
|
|
|
|
set_prefix_fields(&cls);
|
2010-11-03 11:00:58 -07:00
|
|
|
|
tcls_init(&tcls);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2010-11-03 11:00:58 -07:00
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ops); i++) {
|
2015-06-11 15:53:43 -07:00
|
|
|
|
struct test_rule *displaced_rule = NULL;
|
|
|
|
|
struct cls_rule *removable_rule = NULL;
|
2010-11-03 11:00:58 -07:00
|
|
|
|
int j = ops[i];
|
|
|
|
|
int m, n;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2010-11-03 11:00:58 -07:00
|
|
|
|
if (!tcls_rules[j]) {
|
|
|
|
|
tcls_rules[j] = tcls_insert(&tcls, rules[j]);
|
2015-06-11 15:53:43 -07:00
|
|
|
|
if (versioned) {
|
|
|
|
|
/* Insert the new rule in the next version. */
|
2015-07-06 11:45:54 -07:00
|
|
|
|
++version;
|
2015-06-11 15:53:43 -07:00
|
|
|
|
|
|
|
|
|
displaced_rule = test_rule_from_cls_rule(
|
|
|
|
|
classifier_find_rule_exactly(&cls,
|
2015-07-06 11:45:54 -07:00
|
|
|
|
&rules[j]->cls_rule,
|
|
|
|
|
version));
|
2015-06-11 15:53:43 -07:00
|
|
|
|
if (displaced_rule) {
|
|
|
|
|
/* Mark the old rule for removal after the current
|
|
|
|
|
* version. */
|
|
|
|
|
cls_rule_make_invisible_in_version(
|
2015-06-12 16:12:56 -07:00
|
|
|
|
&displaced_rule->cls_rule, version);
|
2015-06-11 15:53:43 -07:00
|
|
|
|
n_invisible_rules++;
|
|
|
|
|
removable_rule = &displaced_rule->cls_rule;
|
|
|
|
|
}
|
2015-07-06 11:45:54 -07:00
|
|
|
|
classifier_insert(&cls, &rules[j]->cls_rule, version,
|
|
|
|
|
NULL, 0);
|
2015-06-11 15:53:43 -07:00
|
|
|
|
} else {
|
|
|
|
|
displaced_rule = test_rule_from_cls_rule(
|
|
|
|
|
classifier_replace(&cls, &rules[j]->cls_rule,
|
2015-07-06 11:45:54 -07:00
|
|
|
|
version, NULL, 0));
|
2015-06-11 15:53:43 -07:00
|
|
|
|
}
|
2010-11-03 11:00:58 -07:00
|
|
|
|
if (pri_rules[pris[j]] >= 0) {
|
|
|
|
|
int k = pri_rules[pris[j]];
|
|
|
|
|
assert(displaced_rule != NULL);
|
|
|
|
|
assert(displaced_rule != rules[j]);
|
|
|
|
|
assert(pris[j] == displaced_rule->cls_rule.priority);
|
|
|
|
|
tcls_rules[k] = NULL;
|
|
|
|
|
} else {
|
|
|
|
|
assert(displaced_rule == NULL);
|
|
|
|
|
}
|
|
|
|
|
pri_rules[pris[j]] = j;
|
|
|
|
|
} else {
|
2015-06-11 15:53:43 -07:00
|
|
|
|
if (versioned) {
|
|
|
|
|
/* Mark the rule for removal after the current
|
|
|
|
|
* version. */
|
|
|
|
|
++version;
|
2015-07-06 11:45:54 -07:00
|
|
|
|
cls_rule_make_invisible_in_version(
|
|
|
|
|
&rules[j]->cls_rule, version);
|
2015-06-11 15:53:43 -07:00
|
|
|
|
n_invisible_rules++;
|
|
|
|
|
removable_rule = &rules[j]->cls_rule;
|
|
|
|
|
} else {
|
2018-01-30 13:00:31 -08:00
|
|
|
|
classifier_remove_assert(&cls, &rules[j]->cls_rule);
|
2015-06-11 15:53:43 -07:00
|
|
|
|
}
|
2010-11-03 11:00:58 -07:00
|
|
|
|
tcls_remove(&tcls, tcls_rules[j]);
|
|
|
|
|
tcls_rules[j] = NULL;
|
|
|
|
|
pri_rules[pris[j]] = -1;
|
|
|
|
|
}
|
2015-06-11 15:53:43 -07:00
|
|
|
|
compare_classifiers(&cls, n_invisible_rules, version, &tcls);
|
2010-11-03 11:00:58 -07:00
|
|
|
|
n = 0;
|
|
|
|
|
for (m = 0; m < N_RULES; m++) {
|
|
|
|
|
n += tcls_rules[m] != NULL;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
2015-06-11 15:53:43 -07:00
|
|
|
|
check_tables(&cls, n > 0, n, n - 1, n_invisible_rules,
|
|
|
|
|
version);
|
|
|
|
|
|
|
|
|
|
if (versioned && removable_rule) {
|
2016-04-17 08:51:21 -07:00
|
|
|
|
struct cls_match *cls_match =
|
|
|
|
|
get_cls_match_protected(removable_rule);
|
|
|
|
|
|
2015-06-11 15:53:43 -07:00
|
|
|
|
/* Removable rule is no longer visible. */
|
2016-04-17 08:51:21 -07:00
|
|
|
|
assert(cls_match);
|
|
|
|
|
assert(!cls_match_visible_in_version(cls_match, version));
|
2018-01-30 13:00:31 -08:00
|
|
|
|
classifier_remove_assert(&cls, removable_rule);
|
2015-06-11 15:53:43 -07:00
|
|
|
|
n_invisible_rules--;
|
|
|
|
|
}
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
2010-11-03 11:00:58 -07:00
|
|
|
|
|
2014-11-13 11:54:31 -08:00
|
|
|
|
classifier_defer(&cls);
|
2010-11-03 11:00:58 -07:00
|
|
|
|
for (i = 0; i < N_RULES; i++) {
|
2014-10-29 09:59:57 -07:00
|
|
|
|
if (classifier_remove(&cls, &rules[i]->cls_rule)) {
|
|
|
|
|
ovsrcu_postpone(free_rule, rules[i]);
|
2014-04-29 15:50:38 -07:00
|
|
|
|
}
|
2010-11-03 11:00:58 -07:00
|
|
|
|
}
|
2014-04-29 15:50:38 -07:00
|
|
|
|
classifier_destroy(&cls);
|
|
|
|
|
tcls_destroy(&tcls);
|
2010-11-03 11:00:58 -07:00
|
|
|
|
} while (next_permutation(ops, ARRAY_SIZE(ops)));
|
|
|
|
|
assert(n_permutations == (factorial(N_RULES * 2) >> N_RULES));
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2010-11-03 11:00:58 -07:00
|
|
|
|
static int
|
|
|
|
|
count_ones(unsigned long int x)
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
2010-11-03 11:00:58 -07:00
|
|
|
|
int n = 0;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2010-11-03 11:00:58 -07:00
|
|
|
|
while (x) {
|
2012-07-20 11:45:33 -07:00
|
|
|
|
x = zero_rightmost_1bit(x);
|
2010-11-03 11:00:58 -07:00
|
|
|
|
n++;
|
|
|
|
|
}
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2010-11-03 11:00:58 -07:00
|
|
|
|
return n;
|
|
|
|
|
}
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2010-11-03 11:00:58 -07:00
|
|
|
|
static bool
|
|
|
|
|
array_contains(int *array, int n, int value)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2010-11-03 11:00:58 -07:00
|
|
|
|
for (i = 0; i < n; i++) {
|
|
|
|
|
if (array[i] == value) {
|
|
|
|
|
return true;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
2010-11-03 11:00:58 -07:00
|
|
|
|
|
|
|
|
|
return false;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
|
2010-11-03 11:00:58 -07:00
|
|
|
|
/* Tests classification with two rules at a time that fall into the same
|
|
|
|
|
* table but different lists. */
|
2009-07-08 13:19:16 -07:00
|
|
|
|
static void
|
2015-03-17 10:35:26 -04:00
|
|
|
|
test_many_rules_in_one_table(struct ovs_cmdl_context *ctx OVS_UNUSED)
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
2010-11-03 11:00:58 -07:00
|
|
|
|
int iteration;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2010-11-03 11:00:58 -07:00
|
|
|
|
for (iteration = 0; iteration < 50; iteration++) {
|
|
|
|
|
enum { N_RULES = 20 };
|
|
|
|
|
struct test_rule *rules[N_RULES];
|
|
|
|
|
struct test_rule *tcls_rules[N_RULES];
|
|
|
|
|
struct classifier cls;
|
|
|
|
|
struct tcls tcls;
|
2016-07-29 16:52:01 -07:00
|
|
|
|
ovs_version_t version = OVS_VERSION_MIN;
|
2015-06-11 15:53:43 -07:00
|
|
|
|
size_t n_invisible_rules = 0;
|
2010-11-03 11:00:58 -07:00
|
|
|
|
int value_pats[N_RULES];
|
|
|
|
|
int value_mask;
|
|
|
|
|
int wcf;
|
|
|
|
|
int i;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2010-11-03 11:00:58 -07:00
|
|
|
|
do {
|
2013-06-25 09:22:11 -07:00
|
|
|
|
wcf = random_uint32() & ((1u << CLS_N_FIELDS) - 1);
|
2010-11-03 11:00:58 -07:00
|
|
|
|
value_mask = ~wcf & ((1u << CLS_N_FIELDS) - 1);
|
|
|
|
|
} while ((1 << count_ones(value_mask)) < N_RULES);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2015-01-06 11:10:42 -08:00
|
|
|
|
classifier_init(&cls, flow_segment_u64s);
|
2014-07-11 02:29:08 -07:00
|
|
|
|
set_prefix_fields(&cls);
|
2010-11-03 11:00:58 -07:00
|
|
|
|
tcls_init(&tcls);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2010-11-03 11:00:58 -07:00
|
|
|
|
for (i = 0; i < N_RULES; i++) {
|
2014-10-30 11:40:07 -07:00
|
|
|
|
int priority = random_range(INT_MAX);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2010-11-03 11:00:58 -07:00
|
|
|
|
do {
|
2013-06-25 09:22:11 -07:00
|
|
|
|
value_pats[i] = random_uint32() & value_mask;
|
2010-11-03 11:00:58 -07:00
|
|
|
|
} while (array_contains(value_pats, i, value_pats[i]));
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2015-06-11 15:53:43 -07:00
|
|
|
|
++version;
|
2015-07-06 11:45:54 -07:00
|
|
|
|
rules[i] = make_rule(wcf, priority, value_pats[i]);
|
2010-11-03 11:00:58 -07:00
|
|
|
|
tcls_rules[i] = tcls_insert(&tcls, rules[i]);
|
2014-07-11 02:29:07 -07:00
|
|
|
|
|
2015-07-06 11:45:54 -07:00
|
|
|
|
classifier_insert(&cls, &rules[i]->cls_rule, version, NULL, 0);
|
2015-06-11 15:53:43 -07:00
|
|
|
|
compare_classifiers(&cls, n_invisible_rules, version, &tcls);
|
2010-11-03 11:00:58 -07:00
|
|
|
|
|
2015-06-11 15:53:43 -07:00
|
|
|
|
check_tables(&cls, 1, i + 1, 0, n_invisible_rules, version);
|
2010-11-03 11:00:58 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < N_RULES; i++) {
|
|
|
|
|
tcls_remove(&tcls, tcls_rules[i]);
|
2015-06-11 15:53:43 -07:00
|
|
|
|
if (versioned) {
|
|
|
|
|
/* Mark the rule for removal after the current version. */
|
|
|
|
|
++version;
|
2015-07-06 11:45:54 -07:00
|
|
|
|
cls_rule_make_invisible_in_version(&rules[i]->cls_rule,
|
|
|
|
|
version);
|
2015-06-11 15:53:43 -07:00
|
|
|
|
n_invisible_rules++;
|
|
|
|
|
} else {
|
2018-01-30 13:00:31 -08:00
|
|
|
|
classifier_remove_assert(&cls, &rules[i]->cls_rule);
|
2015-06-11 15:53:43 -07:00
|
|
|
|
}
|
|
|
|
|
compare_classifiers(&cls, n_invisible_rules, version, &tcls);
|
|
|
|
|
check_tables(&cls, i < N_RULES - 1, N_RULES - (i + 1), 0,
|
|
|
|
|
n_invisible_rules, version);
|
|
|
|
|
if (!versioned) {
|
|
|
|
|
ovsrcu_postpone(free_rule, rules[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
2010-11-03 11:00:58 -07:00
|
|
|
|
|
2015-06-11 15:53:43 -07:00
|
|
|
|
if (versioned) {
|
|
|
|
|
for (i = 0; i < N_RULES; i++) {
|
2018-01-30 13:00:31 -08:00
|
|
|
|
classifier_remove_assert(&cls, &rules[i]->cls_rule);
|
2015-06-11 15:53:43 -07:00
|
|
|
|
n_invisible_rules--;
|
|
|
|
|
|
|
|
|
|
compare_classifiers(&cls, n_invisible_rules, version, &tcls);
|
|
|
|
|
check_tables(&cls, 0, 0, 0, n_invisible_rules, version);
|
|
|
|
|
ovsrcu_postpone(free_rule, rules[i]);
|
|
|
|
|
}
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
2010-11-03 11:00:58 -07:00
|
|
|
|
|
|
|
|
|
classifier_destroy(&cls);
|
|
|
|
|
tcls_destroy(&tcls);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2010-11-03 11:00:58 -07:00
|
|
|
|
/* Tests classification with many rules at a time that fall into random lists
|
|
|
|
|
* in 'n' tables. */
|
2009-07-08 13:19:16 -07:00
|
|
|
|
static void
|
2010-11-03 11:00:58 -07:00
|
|
|
|
test_many_rules_in_n_tables(int n_tables)
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
|
|
|
|
enum { MAX_RULES = 50 };
|
2010-11-03 11:00:58 -07:00
|
|
|
|
int wcfs[10];
|
2009-07-08 13:19:16 -07:00
|
|
|
|
int iteration;
|
2010-11-03 11:00:58 -07:00
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
assert(n_tables < 10);
|
|
|
|
|
for (i = 0; i < n_tables; i++) {
|
|
|
|
|
do {
|
2013-06-25 09:22:11 -07:00
|
|
|
|
wcfs[i] = random_uint32() & ((1u << CLS_N_FIELDS) - 1);
|
2010-11-03 11:00:58 -07:00
|
|
|
|
} while (array_contains(wcfs, i, wcfs[i]));
|
|
|
|
|
}
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
|
|
|
|
for (iteration = 0; iteration < 30; iteration++) {
|
2014-10-30 11:40:07 -07:00
|
|
|
|
int priorities[MAX_RULES];
|
2009-07-08 13:19:16 -07:00
|
|
|
|
struct classifier cls;
|
|
|
|
|
struct tcls tcls;
|
2016-07-29 16:52:01 -07:00
|
|
|
|
ovs_version_t version = OVS_VERSION_MIN;
|
2015-06-11 15:53:43 -07:00
|
|
|
|
size_t n_invisible_rules = 0;
|
|
|
|
|
struct ovs_list list = OVS_LIST_INITIALIZER(&list);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2013-06-25 09:22:11 -07:00
|
|
|
|
random_set_seed(iteration + 1);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
for (i = 0; i < MAX_RULES; i++) {
|
2014-10-30 11:40:07 -07:00
|
|
|
|
priorities[i] = (i * 129) & INT_MAX;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
shuffle(priorities, ARRAY_SIZE(priorities));
|
|
|
|
|
|
2015-01-06 11:10:42 -08:00
|
|
|
|
classifier_init(&cls, flow_segment_u64s);
|
2014-07-11 02:29:08 -07:00
|
|
|
|
set_prefix_fields(&cls);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
tcls_init(&tcls);
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < MAX_RULES; i++) {
|
|
|
|
|
struct test_rule *rule;
|
2014-10-30 11:40:07 -07:00
|
|
|
|
int priority = priorities[i];
|
2013-06-25 09:22:11 -07:00
|
|
|
|
int wcf = wcfs[random_range(n_tables)];
|
|
|
|
|
int value_pat = random_uint32() & ((1u << CLS_N_FIELDS) - 1);
|
2015-07-06 11:45:54 -07:00
|
|
|
|
rule = make_rule(wcf, priority, value_pat);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
tcls_insert(&tcls, rule);
|
2015-07-06 11:45:54 -07:00
|
|
|
|
classifier_insert(&cls, &rule->cls_rule, version, NULL, 0);
|
2015-06-11 15:53:43 -07:00
|
|
|
|
compare_classifiers(&cls, n_invisible_rules, version, &tcls);
|
|
|
|
|
check_tables(&cls, -1, i + 1, -1, n_invisible_rules, version);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
|
2015-06-11 15:53:43 -07:00
|
|
|
|
while (classifier_count(&cls) - n_invisible_rules > 0) {
|
2010-10-28 16:18:20 -07:00
|
|
|
|
struct test_rule *target;
|
2014-07-21 21:00:04 -07:00
|
|
|
|
struct test_rule *rule;
|
2015-06-11 15:53:43 -07:00
|
|
|
|
size_t n_removable_rules = 0;
|
2010-10-28 16:18:20 -07:00
|
|
|
|
|
2013-06-25 09:22:11 -07:00
|
|
|
|
target = clone_rule(tcls.rules[random_range(tcls.n_rules)]);
|
2010-10-28 16:18:20 -07:00
|
|
|
|
|
2015-07-06 11:45:54 -07:00
|
|
|
|
CLS_FOR_EACH_TARGET (rule, cls_rule, &cls, &target->cls_rule,
|
|
|
|
|
version) {
|
2015-06-11 15:53:43 -07:00
|
|
|
|
if (versioned) {
|
|
|
|
|
/* Mark the rule for removal after the current version. */
|
|
|
|
|
cls_rule_make_invisible_in_version(&rule->cls_rule,
|
2015-06-12 16:12:56 -07:00
|
|
|
|
version + 1);
|
2015-06-11 15:53:43 -07:00
|
|
|
|
n_removable_rules++;
|
|
|
|
|
compare_classifiers(&cls, n_invisible_rules, version,
|
|
|
|
|
&tcls);
|
|
|
|
|
check_tables(&cls, -1, -1, -1, n_invisible_rules, version);
|
|
|
|
|
|
2016-03-25 14:10:22 -07:00
|
|
|
|
ovs_list_push_back(&list, &rule->list_node);
|
2015-06-11 15:53:43 -07:00
|
|
|
|
} else if (classifier_remove(&cls, &rule->cls_rule)) {
|
2014-10-29 09:59:57 -07:00
|
|
|
|
ovsrcu_postpone(free_rule, rule);
|
|
|
|
|
}
|
2010-10-28 16:18:20 -07:00
|
|
|
|
}
|
2014-07-11 02:29:07 -07:00
|
|
|
|
|
2015-06-11 15:53:43 -07:00
|
|
|
|
++version;
|
|
|
|
|
n_invisible_rules += n_removable_rules;
|
|
|
|
|
|
2010-10-28 16:18:20 -07:00
|
|
|
|
tcls_delete_matches(&tcls, &target->cls_rule);
|
2012-08-20 11:29:43 -07:00
|
|
|
|
free_rule(target);
|
2015-06-11 15:53:43 -07:00
|
|
|
|
|
|
|
|
|
compare_classifiers(&cls, n_invisible_rules, version, &tcls);
|
|
|
|
|
check_tables(&cls, -1, -1, -1, n_invisible_rules, version);
|
|
|
|
|
}
|
|
|
|
|
if (versioned) {
|
|
|
|
|
struct test_rule *rule;
|
|
|
|
|
|
|
|
|
|
/* Remove rules that are no longer visible. */
|
|
|
|
|
LIST_FOR_EACH_POP (rule, list_node, &list) {
|
2018-01-30 13:00:31 -08:00
|
|
|
|
classifier_remove_assert(&cls, &rule->cls_rule);
|
2015-06-11 15:53:43 -07:00
|
|
|
|
n_invisible_rules--;
|
|
|
|
|
|
|
|
|
|
compare_classifiers(&cls, n_invisible_rules, version,
|
|
|
|
|
&tcls);
|
|
|
|
|
check_tables(&cls, -1, -1, -1, n_invisible_rules, version);
|
|
|
|
|
}
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
destroy_classifier(&cls);
|
|
|
|
|
tcls_destroy(&tcls);
|
|
|
|
|
}
|
|
|
|
|
}
|
2010-11-03 11:00:58 -07:00
|
|
|
|
|
|
|
|
|
static void
|
2015-03-17 10:35:26 -04:00
|
|
|
|
test_many_rules_in_two_tables(struct ovs_cmdl_context *ctx OVS_UNUSED)
|
2010-11-03 11:00:58 -07:00
|
|
|
|
{
|
|
|
|
|
test_many_rules_in_n_tables(2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2015-03-17 10:35:26 -04:00
|
|
|
|
test_many_rules_in_five_tables(struct ovs_cmdl_context *ctx OVS_UNUSED)
|
2010-11-03 11:00:58 -07:00
|
|
|
|
{
|
|
|
|
|
test_many_rules_in_n_tables(5);
|
|
|
|
|
}
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
test-classifier: Add benchmark.
Add a benchmark command for classifier lookup performance testing.
Running the test-classifier without arguments of with "--help" will
print the following usage:
usage: ovstest test-classifier benchmark <n_rules> <n_priorities> <n_subtables> <n_threads> <n_lookups>
where:
<n_rules> - The number of rules to install for lookups. More rules
makes misses less likely.
<n_priorities> - How many different priorities to use. Using only 1
priority will force lookups to continue through all
subtables.
<n_subtables> - Number of subtables to use. Normally a classifier has
rules with different kinds of masks, resulting in
multiple subtables (one per mask). However, in some
special cases a table may consist of only one kind of
rules, so there will be only one subtable.
<n_threads> - How many lookup threads to use. Using one thread should
give less variance accross runs, but classifier
scaling can be tested with multiple threads.
<n_lookups> - How many lookups each thread should perform.
For testing the classifier is filled with <n_rules> rules using
<n_subtables> different mask patterns and <n_priorities> different
priorities. A random set of lookup flows are created, and <n_threads>
lookup threads are spawned to perform <n_lookups> lookups each. The
count of hits and misses, as well as the overall execution time is
reported.
Example run:
$ tests/ovstest test-classifier benchmark 1000 1 30 1 3800000
Benchmarking with:
1000 rules with 1 priorities in 30 tables, 1 threads doing 3800000 lookups each
Without wildcards:
hits: 461520, misses: 3338480
classifier lookups: 386 ms, 9844559 lookups/sec
With wildcards:
hits: 461520, misses: 3338480
classifier lookups: 866 ms, 4387990 lookups/sec
Signed-off-by: Jarno Rajahalme <jrajahalme@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
2015-08-21 12:49:46 -07:00
|
|
|
|
/* Classifier benchmarks. */
|
|
|
|
|
|
|
|
|
|
static int n_rules; /* Number of rules to insert. */
|
|
|
|
|
static int n_priorities; /* Number of priorities to use. */
|
|
|
|
|
static int n_tables; /* Number of subtables. */
|
|
|
|
|
static int n_threads; /* Number of threads to search and mutate. */
|
|
|
|
|
static int n_lookups; /* Number of lookups each thread performs. */
|
|
|
|
|
|
2025-05-16 23:25:17 +02:00
|
|
|
|
static void benchmark(bool use_wc, bool stress_prefixes);
|
test-classifier: Add benchmark.
Add a benchmark command for classifier lookup performance testing.
Running the test-classifier without arguments of with "--help" will
print the following usage:
usage: ovstest test-classifier benchmark <n_rules> <n_priorities> <n_subtables> <n_threads> <n_lookups>
where:
<n_rules> - The number of rules to install for lookups. More rules
makes misses less likely.
<n_priorities> - How many different priorities to use. Using only 1
priority will force lookups to continue through all
subtables.
<n_subtables> - Number of subtables to use. Normally a classifier has
rules with different kinds of masks, resulting in
multiple subtables (one per mask). However, in some
special cases a table may consist of only one kind of
rules, so there will be only one subtable.
<n_threads> - How many lookup threads to use. Using one thread should
give less variance accross runs, but classifier
scaling can be tested with multiple threads.
<n_lookups> - How many lookups each thread should perform.
For testing the classifier is filled with <n_rules> rules using
<n_subtables> different mask patterns and <n_priorities> different
priorities. A random set of lookup flows are created, and <n_threads>
lookup threads are spawned to perform <n_lookups> lookups each. The
count of hits and misses, as well as the overall execution time is
reported.
Example run:
$ tests/ovstest test-classifier benchmark 1000 1 30 1 3800000
Benchmarking with:
1000 rules with 1 priorities in 30 tables, 1 threads doing 3800000 lookups each
Without wildcards:
hits: 461520, misses: 3338480
classifier lookups: 386 ms, 9844559 lookups/sec
With wildcards:
hits: 461520, misses: 3338480
classifier lookups: 866 ms, 4387990 lookups/sec
Signed-off-by: Jarno Rajahalme <jrajahalme@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
2015-08-21 12:49:46 -07:00
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
elapsed(const struct timeval *start)
|
|
|
|
|
{
|
|
|
|
|
struct timeval end;
|
|
|
|
|
|
|
|
|
|
xgettimeofday(&end);
|
|
|
|
|
return timeval_to_msec(&end) - timeval_to_msec(start);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
run_benchmarks(struct ovs_cmdl_context *ctx)
|
|
|
|
|
{
|
|
|
|
|
if (ctx->argc < 5
|
|
|
|
|
|| (ctx->argc > 1 && !strcmp(ctx->argv[1], "--help"))) {
|
|
|
|
|
printf(
|
|
|
|
|
"usage: ovstest %s benchmark <n_rules> <n_priorities> <n_subtables> <n_threads> <n_lookups>\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"where:\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"<n_rules> - The number of rules to install for lookups. More rules\n"
|
|
|
|
|
" makes misses less likely.\n"
|
|
|
|
|
"<n_priorities> - How many different priorities to use. Using only 1\n"
|
|
|
|
|
" priority will force lookups to continue through all\n"
|
|
|
|
|
" subtables.\n"
|
|
|
|
|
"<n_subtables> - Number of subtables to use. Normally a classifier has\n"
|
|
|
|
|
" rules with different kinds of masks, resulting in\n"
|
|
|
|
|
" multiple subtables (one per mask). However, in some\n"
|
|
|
|
|
" special cases a table may consist of only one kind of\n"
|
|
|
|
|
" rules, so there will be only one subtable.\n"
|
|
|
|
|
"<n_threads> - How many lookup threads to use. Using one thread should\n"
|
|
|
|
|
" give less variance accross runs, but classifier\n"
|
|
|
|
|
" scaling can be tested with multiple threads.\n"
|
|
|
|
|
"<n_lookups> - How many lookups each thread should perform.\n"
|
|
|
|
|
"\n", program_name);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
n_rules = strtol(ctx->argv[1], NULL, 10);
|
|
|
|
|
n_priorities = strtol(ctx->argv[2], NULL, 10);
|
|
|
|
|
n_tables = strtol(ctx->argv[3], NULL, 10);
|
|
|
|
|
n_threads = strtol(ctx->argv[4], NULL, 10);
|
|
|
|
|
n_lookups = strtol(ctx->argv[5], NULL, 10);
|
|
|
|
|
|
|
|
|
|
printf("\nBenchmarking with:\n"
|
|
|
|
|
"%d rules with %d priorities in %d tables, "
|
|
|
|
|
"%d threads doing %d lookups each\n",
|
|
|
|
|
n_rules, n_priorities, n_tables, n_threads, n_lookups);
|
|
|
|
|
|
|
|
|
|
puts("\nWithout wildcards: \n");
|
2025-05-16 23:25:17 +02:00
|
|
|
|
benchmark(false, false);
|
test-classifier: Add benchmark.
Add a benchmark command for classifier lookup performance testing.
Running the test-classifier without arguments of with "--help" will
print the following usage:
usage: ovstest test-classifier benchmark <n_rules> <n_priorities> <n_subtables> <n_threads> <n_lookups>
where:
<n_rules> - The number of rules to install for lookups. More rules
makes misses less likely.
<n_priorities> - How many different priorities to use. Using only 1
priority will force lookups to continue through all
subtables.
<n_subtables> - Number of subtables to use. Normally a classifier has
rules with different kinds of masks, resulting in
multiple subtables (one per mask). However, in some
special cases a table may consist of only one kind of
rules, so there will be only one subtable.
<n_threads> - How many lookup threads to use. Using one thread should
give less variance accross runs, but classifier
scaling can be tested with multiple threads.
<n_lookups> - How many lookups each thread should perform.
For testing the classifier is filled with <n_rules> rules using
<n_subtables> different mask patterns and <n_priorities> different
priorities. A random set of lookup flows are created, and <n_threads>
lookup threads are spawned to perform <n_lookups> lookups each. The
count of hits and misses, as well as the overall execution time is
reported.
Example run:
$ tests/ovstest test-classifier benchmark 1000 1 30 1 3800000
Benchmarking with:
1000 rules with 1 priorities in 30 tables, 1 threads doing 3800000 lookups each
Without wildcards:
hits: 461520, misses: 3338480
classifier lookups: 386 ms, 9844559 lookups/sec
With wildcards:
hits: 461520, misses: 3338480
classifier lookups: 866 ms, 4387990 lookups/sec
Signed-off-by: Jarno Rajahalme <jrajahalme@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
2015-08-21 12:49:46 -07:00
|
|
|
|
puts("\nWith wildcards: \n");
|
2025-05-16 23:25:17 +02:00
|
|
|
|
benchmark(true, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
run_prefix_stress(struct ovs_cmdl_context *ctx OVS_UNUSED)
|
|
|
|
|
{
|
|
|
|
|
vlog_set_levels(NULL, VLF_ANY_DESTINATION, VLL_OFF);
|
|
|
|
|
vlog_set_levels(NULL, VLF_CONSOLE, VLL_WARN);
|
|
|
|
|
|
|
|
|
|
n_rules = 10000;
|
|
|
|
|
n_priorities = 2;
|
|
|
|
|
n_tables = 30;
|
|
|
|
|
n_threads = 2;
|
|
|
|
|
n_lookups = 2000000;
|
|
|
|
|
|
|
|
|
|
printf("\nStress testing prefixes with:\n"
|
|
|
|
|
"%d rules with %d priorities in %d tables, "
|
|
|
|
|
"%d threads doing %d lookups each\n",
|
|
|
|
|
n_rules, n_priorities, n_tables, n_threads, n_lookups);
|
|
|
|
|
|
|
|
|
|
benchmark(true, true);
|
test-classifier: Add benchmark.
Add a benchmark command for classifier lookup performance testing.
Running the test-classifier without arguments of with "--help" will
print the following usage:
usage: ovstest test-classifier benchmark <n_rules> <n_priorities> <n_subtables> <n_threads> <n_lookups>
where:
<n_rules> - The number of rules to install for lookups. More rules
makes misses less likely.
<n_priorities> - How many different priorities to use. Using only 1
priority will force lookups to continue through all
subtables.
<n_subtables> - Number of subtables to use. Normally a classifier has
rules with different kinds of masks, resulting in
multiple subtables (one per mask). However, in some
special cases a table may consist of only one kind of
rules, so there will be only one subtable.
<n_threads> - How many lookup threads to use. Using one thread should
give less variance accross runs, but classifier
scaling can be tested with multiple threads.
<n_lookups> - How many lookups each thread should perform.
For testing the classifier is filled with <n_rules> rules using
<n_subtables> different mask patterns and <n_priorities> different
priorities. A random set of lookup flows are created, and <n_threads>
lookup threads are spawned to perform <n_lookups> lookups each. The
count of hits and misses, as well as the overall execution time is
reported.
Example run:
$ tests/ovstest test-classifier benchmark 1000 1 30 1 3800000
Benchmarking with:
1000 rules with 1 priorities in 30 tables, 1 threads doing 3800000 lookups each
Without wildcards:
hits: 461520, misses: 3338480
classifier lookups: 386 ms, 9844559 lookups/sec
With wildcards:
hits: 461520, misses: 3338480
classifier lookups: 866 ms, 4387990 lookups/sec
Signed-off-by: Jarno Rajahalme <jrajahalme@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
2015-08-21 12:49:46 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct cls_aux {
|
|
|
|
|
const struct classifier *cls;
|
|
|
|
|
size_t n_lookup_flows;
|
|
|
|
|
struct flow *lookup_flows;
|
|
|
|
|
bool use_wc;
|
2025-05-16 23:25:17 +02:00
|
|
|
|
bool quiesce;
|
test-classifier: Add benchmark.
Add a benchmark command for classifier lookup performance testing.
Running the test-classifier without arguments of with "--help" will
print the following usage:
usage: ovstest test-classifier benchmark <n_rules> <n_priorities> <n_subtables> <n_threads> <n_lookups>
where:
<n_rules> - The number of rules to install for lookups. More rules
makes misses less likely.
<n_priorities> - How many different priorities to use. Using only 1
priority will force lookups to continue through all
subtables.
<n_subtables> - Number of subtables to use. Normally a classifier has
rules with different kinds of masks, resulting in
multiple subtables (one per mask). However, in some
special cases a table may consist of only one kind of
rules, so there will be only one subtable.
<n_threads> - How many lookup threads to use. Using one thread should
give less variance accross runs, but classifier
scaling can be tested with multiple threads.
<n_lookups> - How many lookups each thread should perform.
For testing the classifier is filled with <n_rules> rules using
<n_subtables> different mask patterns and <n_priorities> different
priorities. A random set of lookup flows are created, and <n_threads>
lookup threads are spawned to perform <n_lookups> lookups each. The
count of hits and misses, as well as the overall execution time is
reported.
Example run:
$ tests/ovstest test-classifier benchmark 1000 1 30 1 3800000
Benchmarking with:
1000 rules with 1 priorities in 30 tables, 1 threads doing 3800000 lookups each
Without wildcards:
hits: 461520, misses: 3338480
classifier lookups: 386 ms, 9844559 lookups/sec
With wildcards:
hits: 461520, misses: 3338480
classifier lookups: 866 ms, 4387990 lookups/sec
Signed-off-by: Jarno Rajahalme <jrajahalme@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
2015-08-21 12:49:46 -07:00
|
|
|
|
atomic_int hits;
|
|
|
|
|
atomic_int misses;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void *
|
|
|
|
|
lookup_classifier(void *aux_)
|
|
|
|
|
{
|
|
|
|
|
struct cls_aux *aux = aux_;
|
2016-07-29 16:52:01 -07:00
|
|
|
|
ovs_version_t version = OVS_VERSION_MIN;
|
test-classifier: Add benchmark.
Add a benchmark command for classifier lookup performance testing.
Running the test-classifier without arguments of with "--help" will
print the following usage:
usage: ovstest test-classifier benchmark <n_rules> <n_priorities> <n_subtables> <n_threads> <n_lookups>
where:
<n_rules> - The number of rules to install for lookups. More rules
makes misses less likely.
<n_priorities> - How many different priorities to use. Using only 1
priority will force lookups to continue through all
subtables.
<n_subtables> - Number of subtables to use. Normally a classifier has
rules with different kinds of masks, resulting in
multiple subtables (one per mask). However, in some
special cases a table may consist of only one kind of
rules, so there will be only one subtable.
<n_threads> - How many lookup threads to use. Using one thread should
give less variance accross runs, but classifier
scaling can be tested with multiple threads.
<n_lookups> - How many lookups each thread should perform.
For testing the classifier is filled with <n_rules> rules using
<n_subtables> different mask patterns and <n_priorities> different
priorities. A random set of lookup flows are created, and <n_threads>
lookup threads are spawned to perform <n_lookups> lookups each. The
count of hits and misses, as well as the overall execution time is
reported.
Example run:
$ tests/ovstest test-classifier benchmark 1000 1 30 1 3800000
Benchmarking with:
1000 rules with 1 priorities in 30 tables, 1 threads doing 3800000 lookups each
Without wildcards:
hits: 461520, misses: 3338480
classifier lookups: 386 ms, 9844559 lookups/sec
With wildcards:
hits: 461520, misses: 3338480
classifier lookups: 866 ms, 4387990 lookups/sec
Signed-off-by: Jarno Rajahalme <jrajahalme@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
2015-08-21 12:49:46 -07:00
|
|
|
|
int hits = 0, old_hits;
|
|
|
|
|
int misses = 0, old_misses;
|
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
|
|
random_set_seed(1);
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < n_lookups; i++) {
|
|
|
|
|
const struct cls_rule *cr;
|
|
|
|
|
struct flow_wildcards wc;
|
|
|
|
|
unsigned int x;
|
|
|
|
|
|
|
|
|
|
x = random_range(aux->n_lookup_flows);
|
|
|
|
|
|
|
|
|
|
if (aux->use_wc) {
|
|
|
|
|
flow_wildcards_init_catchall(&wc);
|
|
|
|
|
cr = classifier_lookup(aux->cls, version, &aux->lookup_flows[x],
|
2023-11-15 18:47:33 +09:00
|
|
|
|
&wc, NULL);
|
test-classifier: Add benchmark.
Add a benchmark command for classifier lookup performance testing.
Running the test-classifier without arguments of with "--help" will
print the following usage:
usage: ovstest test-classifier benchmark <n_rules> <n_priorities> <n_subtables> <n_threads> <n_lookups>
where:
<n_rules> - The number of rules to install for lookups. More rules
makes misses less likely.
<n_priorities> - How many different priorities to use. Using only 1
priority will force lookups to continue through all
subtables.
<n_subtables> - Number of subtables to use. Normally a classifier has
rules with different kinds of masks, resulting in
multiple subtables (one per mask). However, in some
special cases a table may consist of only one kind of
rules, so there will be only one subtable.
<n_threads> - How many lookup threads to use. Using one thread should
give less variance accross runs, but classifier
scaling can be tested with multiple threads.
<n_lookups> - How many lookups each thread should perform.
For testing the classifier is filled with <n_rules> rules using
<n_subtables> different mask patterns and <n_priorities> different
priorities. A random set of lookup flows are created, and <n_threads>
lookup threads are spawned to perform <n_lookups> lookups each. The
count of hits and misses, as well as the overall execution time is
reported.
Example run:
$ tests/ovstest test-classifier benchmark 1000 1 30 1 3800000
Benchmarking with:
1000 rules with 1 priorities in 30 tables, 1 threads doing 3800000 lookups each
Without wildcards:
hits: 461520, misses: 3338480
classifier lookups: 386 ms, 9844559 lookups/sec
With wildcards:
hits: 461520, misses: 3338480
classifier lookups: 866 ms, 4387990 lookups/sec
Signed-off-by: Jarno Rajahalme <jrajahalme@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
2015-08-21 12:49:46 -07:00
|
|
|
|
} else {
|
|
|
|
|
cr = classifier_lookup(aux->cls, version, &aux->lookup_flows[x],
|
2023-11-15 18:47:33 +09:00
|
|
|
|
NULL, NULL);
|
test-classifier: Add benchmark.
Add a benchmark command for classifier lookup performance testing.
Running the test-classifier without arguments of with "--help" will
print the following usage:
usage: ovstest test-classifier benchmark <n_rules> <n_priorities> <n_subtables> <n_threads> <n_lookups>
where:
<n_rules> - The number of rules to install for lookups. More rules
makes misses less likely.
<n_priorities> - How many different priorities to use. Using only 1
priority will force lookups to continue through all
subtables.
<n_subtables> - Number of subtables to use. Normally a classifier has
rules with different kinds of masks, resulting in
multiple subtables (one per mask). However, in some
special cases a table may consist of only one kind of
rules, so there will be only one subtable.
<n_threads> - How many lookup threads to use. Using one thread should
give less variance accross runs, but classifier
scaling can be tested with multiple threads.
<n_lookups> - How many lookups each thread should perform.
For testing the classifier is filled with <n_rules> rules using
<n_subtables> different mask patterns and <n_priorities> different
priorities. A random set of lookup flows are created, and <n_threads>
lookup threads are spawned to perform <n_lookups> lookups each. The
count of hits and misses, as well as the overall execution time is
reported.
Example run:
$ tests/ovstest test-classifier benchmark 1000 1 30 1 3800000
Benchmarking with:
1000 rules with 1 priorities in 30 tables, 1 threads doing 3800000 lookups each
Without wildcards:
hits: 461520, misses: 3338480
classifier lookups: 386 ms, 9844559 lookups/sec
With wildcards:
hits: 461520, misses: 3338480
classifier lookups: 866 ms, 4387990 lookups/sec
Signed-off-by: Jarno Rajahalme <jrajahalme@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
2015-08-21 12:49:46 -07:00
|
|
|
|
}
|
|
|
|
|
if (cr) {
|
|
|
|
|
hits++;
|
|
|
|
|
} else {
|
|
|
|
|
misses++;
|
|
|
|
|
}
|
2025-05-16 23:25:17 +02:00
|
|
|
|
if (aux->quiesce) {
|
|
|
|
|
ovsrcu_quiesce();
|
|
|
|
|
}
|
test-classifier: Add benchmark.
Add a benchmark command for classifier lookup performance testing.
Running the test-classifier without arguments of with "--help" will
print the following usage:
usage: ovstest test-classifier benchmark <n_rules> <n_priorities> <n_subtables> <n_threads> <n_lookups>
where:
<n_rules> - The number of rules to install for lookups. More rules
makes misses less likely.
<n_priorities> - How many different priorities to use. Using only 1
priority will force lookups to continue through all
subtables.
<n_subtables> - Number of subtables to use. Normally a classifier has
rules with different kinds of masks, resulting in
multiple subtables (one per mask). However, in some
special cases a table may consist of only one kind of
rules, so there will be only one subtable.
<n_threads> - How many lookup threads to use. Using one thread should
give less variance accross runs, but classifier
scaling can be tested with multiple threads.
<n_lookups> - How many lookups each thread should perform.
For testing the classifier is filled with <n_rules> rules using
<n_subtables> different mask patterns and <n_priorities> different
priorities. A random set of lookup flows are created, and <n_threads>
lookup threads are spawned to perform <n_lookups> lookups each. The
count of hits and misses, as well as the overall execution time is
reported.
Example run:
$ tests/ovstest test-classifier benchmark 1000 1 30 1 3800000
Benchmarking with:
1000 rules with 1 priorities in 30 tables, 1 threads doing 3800000 lookups each
Without wildcards:
hits: 461520, misses: 3338480
classifier lookups: 386 ms, 9844559 lookups/sec
With wildcards:
hits: 461520, misses: 3338480
classifier lookups: 866 ms, 4387990 lookups/sec
Signed-off-by: Jarno Rajahalme <jrajahalme@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
2015-08-21 12:49:46 -07:00
|
|
|
|
}
|
|
|
|
|
atomic_add(&aux->hits, hits, &old_hits);
|
|
|
|
|
atomic_add(&aux->misses, misses, &old_misses);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-16 23:25:17 +02:00
|
|
|
|
struct prefix_aux {
|
|
|
|
|
struct classifier *cls;
|
|
|
|
|
atomic_bool running;
|
|
|
|
|
size_t n_updates;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void *
|
|
|
|
|
update_prefixes(void *aux_)
|
|
|
|
|
{
|
|
|
|
|
struct prefix_aux *aux = aux_;
|
|
|
|
|
size_t n, n_updates = 0;
|
|
|
|
|
bool running = true;
|
|
|
|
|
|
|
|
|
|
random_set_seed(1);
|
|
|
|
|
|
|
|
|
|
while (running) {
|
|
|
|
|
n_updates++;
|
|
|
|
|
|
|
|
|
|
shuffle_fields(trie_fields, ARRAY_SIZE(trie_fields));
|
|
|
|
|
n = random_range(ARRAY_SIZE(trie_fields) + 1);
|
|
|
|
|
classifier_set_prefix_fields(aux->cls, trie_fields, n);
|
|
|
|
|
verify_tries(aux->cls);
|
|
|
|
|
|
|
|
|
|
atomic_read_relaxed(&aux->running, &running);
|
|
|
|
|
}
|
|
|
|
|
aux->n_updates = n_updates;
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
test-classifier: Add benchmark.
Add a benchmark command for classifier lookup performance testing.
Running the test-classifier without arguments of with "--help" will
print the following usage:
usage: ovstest test-classifier benchmark <n_rules> <n_priorities> <n_subtables> <n_threads> <n_lookups>
where:
<n_rules> - The number of rules to install for lookups. More rules
makes misses less likely.
<n_priorities> - How many different priorities to use. Using only 1
priority will force lookups to continue through all
subtables.
<n_subtables> - Number of subtables to use. Normally a classifier has
rules with different kinds of masks, resulting in
multiple subtables (one per mask). However, in some
special cases a table may consist of only one kind of
rules, so there will be only one subtable.
<n_threads> - How many lookup threads to use. Using one thread should
give less variance accross runs, but classifier
scaling can be tested with multiple threads.
<n_lookups> - How many lookups each thread should perform.
For testing the classifier is filled with <n_rules> rules using
<n_subtables> different mask patterns and <n_priorities> different
priorities. A random set of lookup flows are created, and <n_threads>
lookup threads are spawned to perform <n_lookups> lookups each. The
count of hits and misses, as well as the overall execution time is
reported.
Example run:
$ tests/ovstest test-classifier benchmark 1000 1 30 1 3800000
Benchmarking with:
1000 rules with 1 priorities in 30 tables, 1 threads doing 3800000 lookups each
Without wildcards:
hits: 461520, misses: 3338480
classifier lookups: 386 ms, 9844559 lookups/sec
With wildcards:
hits: 461520, misses: 3338480
classifier lookups: 866 ms, 4387990 lookups/sec
Signed-off-by: Jarno Rajahalme <jrajahalme@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
2015-08-21 12:49:46 -07:00
|
|
|
|
/* Benchmark classification. */
|
|
|
|
|
static void
|
2025-05-16 23:25:17 +02:00
|
|
|
|
benchmark(bool use_wc, bool stress_prefixes)
|
test-classifier: Add benchmark.
Add a benchmark command for classifier lookup performance testing.
Running the test-classifier without arguments of with "--help" will
print the following usage:
usage: ovstest test-classifier benchmark <n_rules> <n_priorities> <n_subtables> <n_threads> <n_lookups>
where:
<n_rules> - The number of rules to install for lookups. More rules
makes misses less likely.
<n_priorities> - How many different priorities to use. Using only 1
priority will force lookups to continue through all
subtables.
<n_subtables> - Number of subtables to use. Normally a classifier has
rules with different kinds of masks, resulting in
multiple subtables (one per mask). However, in some
special cases a table may consist of only one kind of
rules, so there will be only one subtable.
<n_threads> - How many lookup threads to use. Using one thread should
give less variance accross runs, but classifier
scaling can be tested with multiple threads.
<n_lookups> - How many lookups each thread should perform.
For testing the classifier is filled with <n_rules> rules using
<n_subtables> different mask patterns and <n_priorities> different
priorities. A random set of lookup flows are created, and <n_threads>
lookup threads are spawned to perform <n_lookups> lookups each. The
count of hits and misses, as well as the overall execution time is
reported.
Example run:
$ tests/ovstest test-classifier benchmark 1000 1 30 1 3800000
Benchmarking with:
1000 rules with 1 priorities in 30 tables, 1 threads doing 3800000 lookups each
Without wildcards:
hits: 461520, misses: 3338480
classifier lookups: 386 ms, 9844559 lookups/sec
With wildcards:
hits: 461520, misses: 3338480
classifier lookups: 866 ms, 4387990 lookups/sec
Signed-off-by: Jarno Rajahalme <jrajahalme@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
2015-08-21 12:49:46 -07:00
|
|
|
|
{
|
|
|
|
|
struct classifier cls;
|
2016-07-29 16:52:01 -07:00
|
|
|
|
ovs_version_t version = OVS_VERSION_MIN;
|
test-classifier: Add benchmark.
Add a benchmark command for classifier lookup performance testing.
Running the test-classifier without arguments of with "--help" will
print the following usage:
usage: ovstest test-classifier benchmark <n_rules> <n_priorities> <n_subtables> <n_threads> <n_lookups>
where:
<n_rules> - The number of rules to install for lookups. More rules
makes misses less likely.
<n_priorities> - How many different priorities to use. Using only 1
priority will force lookups to continue through all
subtables.
<n_subtables> - Number of subtables to use. Normally a classifier has
rules with different kinds of masks, resulting in
multiple subtables (one per mask). However, in some
special cases a table may consist of only one kind of
rules, so there will be only one subtable.
<n_threads> - How many lookup threads to use. Using one thread should
give less variance accross runs, but classifier
scaling can be tested with multiple threads.
<n_lookups> - How many lookups each thread should perform.
For testing the classifier is filled with <n_rules> rules using
<n_subtables> different mask patterns and <n_priorities> different
priorities. A random set of lookup flows are created, and <n_threads>
lookup threads are spawned to perform <n_lookups> lookups each. The
count of hits and misses, as well as the overall execution time is
reported.
Example run:
$ tests/ovstest test-classifier benchmark 1000 1 30 1 3800000
Benchmarking with:
1000 rules with 1 priorities in 30 tables, 1 threads doing 3800000 lookups each
Without wildcards:
hits: 461520, misses: 3338480
classifier lookups: 386 ms, 9844559 lookups/sec
With wildcards:
hits: 461520, misses: 3338480
classifier lookups: 866 ms, 4387990 lookups/sec
Signed-off-by: Jarno Rajahalme <jrajahalme@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
2015-08-21 12:49:46 -07:00
|
|
|
|
struct cls_aux aux;
|
|
|
|
|
int *wcfs = xmalloc(n_tables * sizeof *wcfs);
|
|
|
|
|
int *priorities = xmalloc(n_priorities * sizeof *priorities);
|
|
|
|
|
struct timeval start;
|
|
|
|
|
pthread_t *threads;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
fatal_signal_init();
|
|
|
|
|
|
|
|
|
|
random_set_seed(1);
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < n_tables; i++) {
|
|
|
|
|
do {
|
|
|
|
|
wcfs[i] = random_uint32() & ((1u << CLS_N_FIELDS) - 1);
|
|
|
|
|
} while (array_contains(wcfs, i, wcfs[i]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < n_priorities; i++) {
|
|
|
|
|
priorities[i] = (i * 129) & INT_MAX;
|
|
|
|
|
}
|
|
|
|
|
shuffle(priorities, n_priorities);
|
|
|
|
|
|
|
|
|
|
classifier_init(&cls, flow_segment_u64s);
|
|
|
|
|
set_prefix_fields(&cls);
|
|
|
|
|
|
|
|
|
|
/* Create lookup flows. */
|
|
|
|
|
aux.use_wc = use_wc;
|
2025-05-16 23:25:17 +02:00
|
|
|
|
aux.quiesce = stress_prefixes;
|
test-classifier: Add benchmark.
Add a benchmark command for classifier lookup performance testing.
Running the test-classifier without arguments of with "--help" will
print the following usage:
usage: ovstest test-classifier benchmark <n_rules> <n_priorities> <n_subtables> <n_threads> <n_lookups>
where:
<n_rules> - The number of rules to install for lookups. More rules
makes misses less likely.
<n_priorities> - How many different priorities to use. Using only 1
priority will force lookups to continue through all
subtables.
<n_subtables> - Number of subtables to use. Normally a classifier has
rules with different kinds of masks, resulting in
multiple subtables (one per mask). However, in some
special cases a table may consist of only one kind of
rules, so there will be only one subtable.
<n_threads> - How many lookup threads to use. Using one thread should
give less variance accross runs, but classifier
scaling can be tested with multiple threads.
<n_lookups> - How many lookups each thread should perform.
For testing the classifier is filled with <n_rules> rules using
<n_subtables> different mask patterns and <n_priorities> different
priorities. A random set of lookup flows are created, and <n_threads>
lookup threads are spawned to perform <n_lookups> lookups each. The
count of hits and misses, as well as the overall execution time is
reported.
Example run:
$ tests/ovstest test-classifier benchmark 1000 1 30 1 3800000
Benchmarking with:
1000 rules with 1 priorities in 30 tables, 1 threads doing 3800000 lookups each
Without wildcards:
hits: 461520, misses: 3338480
classifier lookups: 386 ms, 9844559 lookups/sec
With wildcards:
hits: 461520, misses: 3338480
classifier lookups: 866 ms, 4387990 lookups/sec
Signed-off-by: Jarno Rajahalme <jrajahalme@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
2015-08-21 12:49:46 -07:00
|
|
|
|
aux.cls = &cls;
|
|
|
|
|
aux.n_lookup_flows = 2 * N_FLOW_VALUES;
|
|
|
|
|
aux.lookup_flows = xzalloc(aux.n_lookup_flows * sizeof *aux.lookup_flows);
|
|
|
|
|
for (i = 0; i < aux.n_lookup_flows; i++) {
|
|
|
|
|
struct flow *flow = &aux.lookup_flows[i];
|
|
|
|
|
unsigned int x;
|
|
|
|
|
|
|
|
|
|
x = random_range(N_FLOW_VALUES);
|
|
|
|
|
flow->nw_src = nw_src_values[get_value(&x, N_NW_SRC_VALUES)];
|
|
|
|
|
flow->nw_dst = nw_dst_values[get_value(&x, N_NW_DST_VALUES)];
|
|
|
|
|
flow->tunnel.tun_id = tun_id_values[get_value(&x, N_TUN_ID_VALUES)];
|
|
|
|
|
flow->metadata = metadata_values[get_value(&x, N_METADATA_VALUES)];
|
|
|
|
|
flow->in_port.ofp_port = in_port_values[get_value(&x,
|
|
|
|
|
N_IN_PORT_VALUES)];
|
2017-03-01 17:47:59 -05:00
|
|
|
|
flow->vlans[0].tci = vlan_tci_values[get_value(&x, N_VLAN_TCI_VALUES)];
|
test-classifier: Add benchmark.
Add a benchmark command for classifier lookup performance testing.
Running the test-classifier without arguments of with "--help" will
print the following usage:
usage: ovstest test-classifier benchmark <n_rules> <n_priorities> <n_subtables> <n_threads> <n_lookups>
where:
<n_rules> - The number of rules to install for lookups. More rules
makes misses less likely.
<n_priorities> - How many different priorities to use. Using only 1
priority will force lookups to continue through all
subtables.
<n_subtables> - Number of subtables to use. Normally a classifier has
rules with different kinds of masks, resulting in
multiple subtables (one per mask). However, in some
special cases a table may consist of only one kind of
rules, so there will be only one subtable.
<n_threads> - How many lookup threads to use. Using one thread should
give less variance accross runs, but classifier
scaling can be tested with multiple threads.
<n_lookups> - How many lookups each thread should perform.
For testing the classifier is filled with <n_rules> rules using
<n_subtables> different mask patterns and <n_priorities> different
priorities. A random set of lookup flows are created, and <n_threads>
lookup threads are spawned to perform <n_lookups> lookups each. The
count of hits and misses, as well as the overall execution time is
reported.
Example run:
$ tests/ovstest test-classifier benchmark 1000 1 30 1 3800000
Benchmarking with:
1000 rules with 1 priorities in 30 tables, 1 threads doing 3800000 lookups each
Without wildcards:
hits: 461520, misses: 3338480
classifier lookups: 386 ms, 9844559 lookups/sec
With wildcards:
hits: 461520, misses: 3338480
classifier lookups: 866 ms, 4387990 lookups/sec
Signed-off-by: Jarno Rajahalme <jrajahalme@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
2015-08-21 12:49:46 -07:00
|
|
|
|
flow->dl_type = dl_type_values[get_value(&x, N_DL_TYPE_VALUES)];
|
|
|
|
|
flow->tp_src = tp_src_values[get_value(&x, N_TP_SRC_VALUES)];
|
|
|
|
|
flow->tp_dst = tp_dst_values[get_value(&x, N_TP_DST_VALUES)];
|
2015-08-28 14:55:11 -07:00
|
|
|
|
flow->dl_src = dl_src_values[get_value(&x, N_DL_SRC_VALUES)];
|
|
|
|
|
flow->dl_dst = dl_dst_values[get_value(&x, N_DL_DST_VALUES)];
|
test-classifier: Add benchmark.
Add a benchmark command for classifier lookup performance testing.
Running the test-classifier without arguments of with "--help" will
print the following usage:
usage: ovstest test-classifier benchmark <n_rules> <n_priorities> <n_subtables> <n_threads> <n_lookups>
where:
<n_rules> - The number of rules to install for lookups. More rules
makes misses less likely.
<n_priorities> - How many different priorities to use. Using only 1
priority will force lookups to continue through all
subtables.
<n_subtables> - Number of subtables to use. Normally a classifier has
rules with different kinds of masks, resulting in
multiple subtables (one per mask). However, in some
special cases a table may consist of only one kind of
rules, so there will be only one subtable.
<n_threads> - How many lookup threads to use. Using one thread should
give less variance accross runs, but classifier
scaling can be tested with multiple threads.
<n_lookups> - How many lookups each thread should perform.
For testing the classifier is filled with <n_rules> rules using
<n_subtables> different mask patterns and <n_priorities> different
priorities. A random set of lookup flows are created, and <n_threads>
lookup threads are spawned to perform <n_lookups> lookups each. The
count of hits and misses, as well as the overall execution time is
reported.
Example run:
$ tests/ovstest test-classifier benchmark 1000 1 30 1 3800000
Benchmarking with:
1000 rules with 1 priorities in 30 tables, 1 threads doing 3800000 lookups each
Without wildcards:
hits: 461520, misses: 3338480
classifier lookups: 386 ms, 9844559 lookups/sec
With wildcards:
hits: 461520, misses: 3338480
classifier lookups: 866 ms, 4387990 lookups/sec
Signed-off-by: Jarno Rajahalme <jrajahalme@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
2015-08-21 12:49:46 -07:00
|
|
|
|
flow->nw_proto = nw_proto_values[get_value(&x, N_NW_PROTO_VALUES)];
|
|
|
|
|
flow->nw_tos = nw_dscp_values[get_value(&x, N_NW_DSCP_VALUES)];
|
|
|
|
|
}
|
|
|
|
|
atomic_init(&aux.hits, 0);
|
|
|
|
|
atomic_init(&aux.misses, 0);
|
|
|
|
|
|
|
|
|
|
/* Rule insertion. */
|
|
|
|
|
for (i = 0; i < n_rules; i++) {
|
|
|
|
|
struct test_rule *rule;
|
|
|
|
|
const struct cls_rule *old_cr;
|
|
|
|
|
|
|
|
|
|
int priority = priorities[random_range(n_priorities)];
|
|
|
|
|
int wcf = wcfs[random_range(n_tables)];
|
|
|
|
|
int value_pat = random_uint32() & ((1u << CLS_N_FIELDS) - 1);
|
|
|
|
|
|
|
|
|
|
rule = make_rule(wcf, priority, value_pat);
|
|
|
|
|
old_cr = classifier_find_rule_exactly(&cls, &rule->cls_rule, version);
|
|
|
|
|
if (!old_cr) {
|
|
|
|
|
classifier_insert(&cls, &rule->cls_rule, version, NULL, 0);
|
|
|
|
|
} else {
|
|
|
|
|
free_rule(rule);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-16 23:25:17 +02:00
|
|
|
|
pthread_t prefix_thread;
|
|
|
|
|
struct prefix_aux paux;
|
|
|
|
|
|
|
|
|
|
if (stress_prefixes) {
|
|
|
|
|
paux.cls = &cls;
|
|
|
|
|
paux.n_updates = 0;
|
|
|
|
|
atomic_init(&paux.running, true);
|
|
|
|
|
|
|
|
|
|
prefix_thread = ovs_thread_create("prefixes", update_prefixes, &paux);
|
|
|
|
|
ovsrcu_quiesce_start();
|
|
|
|
|
}
|
|
|
|
|
|
test-classifier: Add benchmark.
Add a benchmark command for classifier lookup performance testing.
Running the test-classifier without arguments of with "--help" will
print the following usage:
usage: ovstest test-classifier benchmark <n_rules> <n_priorities> <n_subtables> <n_threads> <n_lookups>
where:
<n_rules> - The number of rules to install for lookups. More rules
makes misses less likely.
<n_priorities> - How many different priorities to use. Using only 1
priority will force lookups to continue through all
subtables.
<n_subtables> - Number of subtables to use. Normally a classifier has
rules with different kinds of masks, resulting in
multiple subtables (one per mask). However, in some
special cases a table may consist of only one kind of
rules, so there will be only one subtable.
<n_threads> - How many lookup threads to use. Using one thread should
give less variance accross runs, but classifier
scaling can be tested with multiple threads.
<n_lookups> - How many lookups each thread should perform.
For testing the classifier is filled with <n_rules> rules using
<n_subtables> different mask patterns and <n_priorities> different
priorities. A random set of lookup flows are created, and <n_threads>
lookup threads are spawned to perform <n_lookups> lookups each. The
count of hits and misses, as well as the overall execution time is
reported.
Example run:
$ tests/ovstest test-classifier benchmark 1000 1 30 1 3800000
Benchmarking with:
1000 rules with 1 priorities in 30 tables, 1 threads doing 3800000 lookups each
Without wildcards:
hits: 461520, misses: 3338480
classifier lookups: 386 ms, 9844559 lookups/sec
With wildcards:
hits: 461520, misses: 3338480
classifier lookups: 866 ms, 4387990 lookups/sec
Signed-off-by: Jarno Rajahalme <jrajahalme@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
2015-08-21 12:49:46 -07:00
|
|
|
|
/* Lookup. */
|
|
|
|
|
xgettimeofday(&start);
|
|
|
|
|
threads = xmalloc(n_threads * sizeof *threads);
|
|
|
|
|
for (i = 0; i < n_threads; i++) {
|
|
|
|
|
threads[i] = ovs_thread_create("lookups", lookup_classifier, &aux);
|
|
|
|
|
}
|
|
|
|
|
for (i = 0; i < n_threads; i++) {
|
|
|
|
|
xpthread_join(threads[i], NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int elapsed_msec = elapsed(&start);
|
|
|
|
|
|
|
|
|
|
free(threads);
|
|
|
|
|
|
2025-05-16 23:25:17 +02:00
|
|
|
|
if (stress_prefixes) {
|
|
|
|
|
atomic_store_relaxed(&paux.running, false);
|
|
|
|
|
xpthread_join(prefix_thread, NULL);
|
|
|
|
|
printf("Prefixes updated %"PRIuSIZE" times.\n", paux.n_updates);
|
|
|
|
|
ovsrcu_quiesce_end();
|
|
|
|
|
}
|
|
|
|
|
|
test-classifier: Add benchmark.
Add a benchmark command for classifier lookup performance testing.
Running the test-classifier without arguments of with "--help" will
print the following usage:
usage: ovstest test-classifier benchmark <n_rules> <n_priorities> <n_subtables> <n_threads> <n_lookups>
where:
<n_rules> - The number of rules to install for lookups. More rules
makes misses less likely.
<n_priorities> - How many different priorities to use. Using only 1
priority will force lookups to continue through all
subtables.
<n_subtables> - Number of subtables to use. Normally a classifier has
rules with different kinds of masks, resulting in
multiple subtables (one per mask). However, in some
special cases a table may consist of only one kind of
rules, so there will be only one subtable.
<n_threads> - How many lookup threads to use. Using one thread should
give less variance accross runs, but classifier
scaling can be tested with multiple threads.
<n_lookups> - How many lookups each thread should perform.
For testing the classifier is filled with <n_rules> rules using
<n_subtables> different mask patterns and <n_priorities> different
priorities. A random set of lookup flows are created, and <n_threads>
lookup threads are spawned to perform <n_lookups> lookups each. The
count of hits and misses, as well as the overall execution time is
reported.
Example run:
$ tests/ovstest test-classifier benchmark 1000 1 30 1 3800000
Benchmarking with:
1000 rules with 1 priorities in 30 tables, 1 threads doing 3800000 lookups each
Without wildcards:
hits: 461520, misses: 3338480
classifier lookups: 386 ms, 9844559 lookups/sec
With wildcards:
hits: 461520, misses: 3338480
classifier lookups: 866 ms, 4387990 lookups/sec
Signed-off-by: Jarno Rajahalme <jrajahalme@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
2015-08-21 12:49:46 -07:00
|
|
|
|
int hits, misses;
|
|
|
|
|
atomic_read(&aux.hits, &hits);
|
|
|
|
|
atomic_read(&aux.misses, &misses);
|
|
|
|
|
printf("hits: %d, misses: %d\n", hits, misses);
|
|
|
|
|
|
|
|
|
|
printf("classifier lookups: %5d ms, %"PRId64" lookups/sec\n",
|
|
|
|
|
elapsed_msec,
|
|
|
|
|
(((uint64_t)hits + misses) * 1000) / elapsed_msec);
|
|
|
|
|
|
|
|
|
|
destroy_classifier(&cls);
|
|
|
|
|
free(aux.lookup_flows);
|
|
|
|
|
free(priorities);
|
|
|
|
|
free(wcfs);
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-04 12:43:53 -07:00
|
|
|
|
/* Miniflow tests. */
|
|
|
|
|
|
|
|
|
|
static uint32_t
|
|
|
|
|
random_value(void)
|
|
|
|
|
{
|
2018-02-27 17:34:14 -08:00
|
|
|
|
static const uint32_t values_[] =
|
2012-09-04 12:43:53 -07:00
|
|
|
|
{ 0xffffffff, 0xaaaaaaaa, 0x55555555, 0x80000000,
|
|
|
|
|
0x00000001, 0xface0000, 0x00d00d1e, 0xdeadbeef };
|
|
|
|
|
|
2018-02-27 17:34:14 -08:00
|
|
|
|
return values_[random_range(ARRAY_SIZE(values_))];
|
2012-09-04 12:43:53 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
choose(unsigned int n, unsigned int *idxp)
|
|
|
|
|
{
|
|
|
|
|
if (*idxp < n) {
|
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
*idxp -= n;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-06 11:10:42 -08:00
|
|
|
|
#define FLOW_U32S (FLOW_U64S * 2)
|
|
|
|
|
|
2012-09-04 12:43:53 -07:00
|
|
|
|
static bool
|
|
|
|
|
init_consecutive_values(int n_consecutive, struct flow *flow,
|
|
|
|
|
unsigned int *idxp)
|
|
|
|
|
{
|
|
|
|
|
uint32_t *flow_u32 = (uint32_t *) flow;
|
|
|
|
|
|
|
|
|
|
if (choose(FLOW_U32S - n_consecutive + 1, idxp)) {
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < n_consecutive; i++) {
|
|
|
|
|
flow_u32[*idxp + i] = random_value();
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
next_random_flow(struct flow *flow, unsigned int idx)
|
|
|
|
|
{
|
|
|
|
|
uint32_t *flow_u32 = (uint32_t *) flow;
|
|
|
|
|
|
|
|
|
|
memset(flow, 0, sizeof *flow);
|
|
|
|
|
|
|
|
|
|
/* Empty flow. */
|
|
|
|
|
if (choose(1, &idx)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* All flows with a small number of consecutive nonzero values. */
|
2017-08-02 15:03:06 -07:00
|
|
|
|
for (int i = 1; i <= 4; i++) {
|
2012-09-04 12:43:53 -07:00
|
|
|
|
if (init_consecutive_values(i, flow, &idx)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* All flows with a large number of consecutive nonzero values. */
|
2017-08-02 15:03:06 -07:00
|
|
|
|
for (int i = FLOW_U32S - 4; i <= FLOW_U32S; i++) {
|
2012-09-04 12:43:53 -07:00
|
|
|
|
if (init_consecutive_values(i, flow, &idx)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* All flows with exactly two nonconsecutive nonzero values. */
|
|
|
|
|
if (choose((FLOW_U32S - 1) * (FLOW_U32S - 2) / 2, &idx)) {
|
|
|
|
|
int ofs1;
|
|
|
|
|
|
|
|
|
|
for (ofs1 = 0; ofs1 < FLOW_U32S - 2; ofs1++) {
|
|
|
|
|
int ofs2;
|
|
|
|
|
|
|
|
|
|
for (ofs2 = ofs1 + 2; ofs2 < FLOW_U32S; ofs2++) {
|
|
|
|
|
if (choose(1, &idx)) {
|
|
|
|
|
flow_u32[ofs1] = random_value();
|
|
|
|
|
flow_u32[ofs2] = random_value();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-12-17 10:32:12 -08:00
|
|
|
|
OVS_NOT_REACHED();
|
2012-09-04 12:43:53 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 16 randomly chosen flows with N >= 3 nonzero values. */
|
|
|
|
|
if (choose(16 * (FLOW_U32S - 4), &idx)) {
|
|
|
|
|
int n = idx / 16 + 3;
|
|
|
|
|
|
2017-08-02 15:03:06 -07:00
|
|
|
|
for (int i = 0; i < n; i++) {
|
2012-09-04 12:43:53 -07:00
|
|
|
|
flow_u32[i] = random_value();
|
|
|
|
|
}
|
|
|
|
|
shuffle_u32s(flow_u32, FLOW_U32S);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
any_random_flow(struct flow *flow)
|
|
|
|
|
{
|
|
|
|
|
static unsigned int max;
|
|
|
|
|
if (!max) {
|
|
|
|
|
while (next_random_flow(flow, max)) {
|
|
|
|
|
max++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
next_random_flow(flow, random_range(max));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
toggle_masked_flow_bits(struct flow *flow, const struct flow_wildcards *mask)
|
|
|
|
|
{
|
|
|
|
|
const uint32_t *mask_u32 = (const uint32_t *) &mask->masks;
|
|
|
|
|
uint32_t *flow_u32 = (uint32_t *) flow;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < FLOW_U32S; i++) {
|
|
|
|
|
if (mask_u32[i] != 0) {
|
|
|
|
|
uint32_t bit;
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
bit = 1u << random_range(32);
|
|
|
|
|
} while (!(bit & mask_u32[i]));
|
|
|
|
|
flow_u32[i] ^= bit;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wildcard_extra_bits(struct flow_wildcards *mask)
|
|
|
|
|
{
|
|
|
|
|
uint32_t *mask_u32 = (uint32_t *) &mask->masks;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < FLOW_U32S; i++) {
|
|
|
|
|
if (mask_u32[i] != 0) {
|
|
|
|
|
uint32_t bit;
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
bit = 1u << random_range(32);
|
|
|
|
|
} while (!(bit & mask_u32[i]));
|
|
|
|
|
mask_u32[i] &= ~bit;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-15 13:17:01 -07:00
|
|
|
|
/* Returns a copy of 'src'. The caller must eventually free the returned
|
|
|
|
|
* miniflow with free(). */
|
|
|
|
|
static struct miniflow *
|
|
|
|
|
miniflow_clone__(const struct miniflow *src)
|
|
|
|
|
{
|
|
|
|
|
struct miniflow *dst;
|
|
|
|
|
size_t data_size;
|
|
|
|
|
|
|
|
|
|
data_size = miniflow_alloc(&dst, 1, src);
|
|
|
|
|
miniflow_clone(dst, src, data_size / sizeof(uint64_t));
|
|
|
|
|
return dst;
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-12 16:00:49 -07:00
|
|
|
|
/* Returns a hash value for 'flow', given 'basis'. */
|
|
|
|
|
static inline uint32_t
|
|
|
|
|
miniflow_hash__(const struct miniflow *flow, uint32_t basis)
|
|
|
|
|
{
|
2015-08-25 13:55:03 -07:00
|
|
|
|
const uint64_t *p = miniflow_get_values(flow);
|
|
|
|
|
size_t n_values = miniflow_n_values(flow);
|
|
|
|
|
struct flowmap hash_map = FLOWMAP_EMPTY_INITIALIZER;
|
2015-08-12 16:00:49 -07:00
|
|
|
|
uint32_t hash = basis;
|
2015-08-25 13:55:03 -07:00
|
|
|
|
size_t idx;
|
2015-08-12 16:00:49 -07:00
|
|
|
|
|
2015-08-25 13:55:03 -07:00
|
|
|
|
FLOWMAP_FOR_EACH_INDEX(idx, flow->map) {
|
|
|
|
|
uint64_t value = *p++;
|
|
|
|
|
|
|
|
|
|
if (value) {
|
|
|
|
|
hash = hash_add64(hash, value);
|
|
|
|
|
flowmap_set(&hash_map, idx, 1);
|
2015-08-12 16:00:49 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
2015-08-25 13:55:03 -07:00
|
|
|
|
map_t map;
|
|
|
|
|
FLOWMAP_FOR_EACH_MAP (map, hash_map) {
|
|
|
|
|
hash = hash_add64(hash, map);
|
2015-08-12 16:00:49 -07:00
|
|
|
|
}
|
|
|
|
|
|
2015-08-25 13:55:03 -07:00
|
|
|
|
return hash_finish(hash, n_values);
|
2015-08-12 16:00:49 -07:00
|
|
|
|
}
|
|
|
|
|
|
2012-09-04 12:43:53 -07:00
|
|
|
|
static void
|
2015-03-17 10:35:26 -04:00
|
|
|
|
test_miniflow(struct ovs_cmdl_context *ctx OVS_UNUSED)
|
2012-09-04 12:43:53 -07:00
|
|
|
|
{
|
|
|
|
|
struct flow flow;
|
|
|
|
|
unsigned int idx;
|
|
|
|
|
|
|
|
|
|
random_set_seed(0xb3faca38);
|
|
|
|
|
for (idx = 0; next_random_flow(&flow, idx); idx++) {
|
2015-01-06 11:10:42 -08:00
|
|
|
|
const uint64_t *flow_u64 = (const uint64_t *) &flow;
|
2015-07-15 13:17:01 -07:00
|
|
|
|
struct miniflow *miniflow, *miniflow2, *miniflow3;
|
2012-09-04 12:43:53 -07:00
|
|
|
|
struct flow flow2, flow3;
|
|
|
|
|
struct flow_wildcards mask;
|
2015-07-15 13:17:01 -07:00
|
|
|
|
struct minimask *minimask;
|
2012-09-04 12:43:53 -07:00
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
/* Convert flow to miniflow. */
|
2015-07-15 13:17:01 -07:00
|
|
|
|
miniflow = miniflow_create(&flow);
|
2012-09-04 12:43:53 -07:00
|
|
|
|
|
|
|
|
|
/* Check that the flow equals its miniflow. */
|
2017-03-01 17:47:59 -05:00
|
|
|
|
for (i = 0; i < FLOW_MAX_VLAN_HEADERS; i++) {
|
|
|
|
|
assert(miniflow_get_vid(miniflow, i) ==
|
|
|
|
|
vlan_tci_to_vid(flow.vlans[i].tci));
|
|
|
|
|
}
|
2015-01-06 11:10:42 -08:00
|
|
|
|
for (i = 0; i < FLOW_U64S; i++) {
|
2015-07-15 13:17:01 -07:00
|
|
|
|
assert(miniflow_get(miniflow, i) == flow_u64[i]);
|
2012-09-04 12:43:53 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Check that the miniflow equals itself. */
|
2015-07-15 13:17:01 -07:00
|
|
|
|
assert(miniflow_equal(miniflow, miniflow));
|
2012-09-04 12:43:53 -07:00
|
|
|
|
|
|
|
|
|
/* Convert miniflow back to flow and verify that it's the same. */
|
2015-07-15 13:17:01 -07:00
|
|
|
|
miniflow_expand(miniflow, &flow2);
|
2012-09-04 12:43:53 -07:00
|
|
|
|
assert(flow_equal(&flow, &flow2));
|
|
|
|
|
|
|
|
|
|
/* Check that copying a miniflow works properly. */
|
2015-07-15 13:17:01 -07:00
|
|
|
|
miniflow2 = miniflow_clone__(miniflow);
|
2015-07-15 13:17:01 -07:00
|
|
|
|
assert(miniflow_equal(miniflow, miniflow2));
|
2015-08-12 16:00:49 -07:00
|
|
|
|
assert(miniflow_hash__(miniflow, 0) == miniflow_hash__(miniflow2, 0));
|
2015-07-15 13:17:01 -07:00
|
|
|
|
miniflow_expand(miniflow2, &flow3);
|
2012-09-04 12:43:53 -07:00
|
|
|
|
assert(flow_equal(&flow, &flow3));
|
|
|
|
|
|
|
|
|
|
/* Check that masked matches work as expected for identical flows and
|
|
|
|
|
* miniflows. */
|
|
|
|
|
do {
|
|
|
|
|
next_random_flow(&mask.masks, 1);
|
|
|
|
|
} while (flow_wildcards_is_catchall(&mask));
|
2015-07-15 13:17:01 -07:00
|
|
|
|
minimask = minimask_create(&mask);
|
|
|
|
|
assert(minimask_is_catchall(minimask)
|
2012-09-04 12:43:53 -07:00
|
|
|
|
== flow_wildcards_is_catchall(&mask));
|
2015-07-15 13:17:01 -07:00
|
|
|
|
assert(miniflow_equal_in_minimask(miniflow, miniflow2, minimask));
|
|
|
|
|
assert(miniflow_equal_flow_in_minimask(miniflow, &flow2, minimask));
|
|
|
|
|
assert(miniflow_hash_in_minimask(miniflow, minimask, 0x12345678) ==
|
|
|
|
|
flow_hash_in_minimask(&flow, minimask, 0x12345678));
|
2015-08-12 16:00:49 -07:00
|
|
|
|
assert(minimask_hash(minimask, 0) ==
|
|
|
|
|
miniflow_hash__(&minimask->masks, 0));
|
2012-09-04 12:43:53 -07:00
|
|
|
|
|
|
|
|
|
/* Check that masked matches work as expected for differing flows and
|
|
|
|
|
* miniflows. */
|
|
|
|
|
toggle_masked_flow_bits(&flow2, &mask);
|
2015-07-15 13:17:01 -07:00
|
|
|
|
assert(!miniflow_equal_flow_in_minimask(miniflow, &flow2, minimask));
|
|
|
|
|
miniflow3 = miniflow_create(&flow2);
|
|
|
|
|
assert(!miniflow_equal_in_minimask(miniflow, miniflow3, minimask));
|
2012-09-04 12:43:53 -07:00
|
|
|
|
|
|
|
|
|
/* Clean up. */
|
2015-07-15 13:17:01 -07:00
|
|
|
|
free(miniflow);
|
|
|
|
|
free(miniflow2);
|
|
|
|
|
free(miniflow3);
|
|
|
|
|
free(minimask);
|
2012-09-04 12:43:53 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2015-03-17 10:35:26 -04:00
|
|
|
|
test_minimask_has_extra(struct ovs_cmdl_context *ctx OVS_UNUSED)
|
2012-09-04 12:43:53 -07:00
|
|
|
|
{
|
|
|
|
|
struct flow_wildcards catchall;
|
2015-07-15 13:17:01 -07:00
|
|
|
|
struct minimask *minicatchall;
|
2012-09-04 12:43:53 -07:00
|
|
|
|
struct flow flow;
|
|
|
|
|
unsigned int idx;
|
|
|
|
|
|
|
|
|
|
flow_wildcards_init_catchall(&catchall);
|
2015-07-15 13:17:01 -07:00
|
|
|
|
minicatchall = minimask_create(&catchall);
|
|
|
|
|
assert(minimask_is_catchall(minicatchall));
|
2012-09-04 12:43:53 -07:00
|
|
|
|
|
|
|
|
|
random_set_seed(0x2ec7905b);
|
|
|
|
|
for (idx = 0; next_random_flow(&flow, idx); idx++) {
|
|
|
|
|
struct flow_wildcards mask;
|
2015-07-15 13:17:01 -07:00
|
|
|
|
struct minimask *minimask;
|
2012-09-04 12:43:53 -07:00
|
|
|
|
|
|
|
|
|
mask.masks = flow;
|
2015-07-15 13:17:01 -07:00
|
|
|
|
minimask = minimask_create(&mask);
|
|
|
|
|
assert(!minimask_has_extra(minimask, minimask));
|
|
|
|
|
assert(minimask_has_extra(minicatchall, minimask)
|
|
|
|
|
== !minimask_is_catchall(minimask));
|
|
|
|
|
if (!minimask_is_catchall(minimask)) {
|
|
|
|
|
struct minimask *minimask2;
|
2012-09-04 12:43:53 -07:00
|
|
|
|
|
|
|
|
|
wildcard_extra_bits(&mask);
|
2015-07-15 13:17:01 -07:00
|
|
|
|
minimask2 = minimask_create(&mask);
|
|
|
|
|
assert(minimask_has_extra(minimask2, minimask));
|
|
|
|
|
assert(!minimask_has_extra(minimask, minimask2));
|
|
|
|
|
free(minimask2);
|
2012-09-04 12:43:53 -07:00
|
|
|
|
}
|
|
|
|
|
|
2015-07-15 13:17:01 -07:00
|
|
|
|
free(minimask);
|
2012-09-04 12:43:53 -07:00
|
|
|
|
}
|
2012-12-21 14:59:35 -08:00
|
|
|
|
|
2015-07-15 13:17:01 -07:00
|
|
|
|
free(minicatchall);
|
2012-09-04 12:43:53 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2015-03-17 10:35:26 -04:00
|
|
|
|
test_minimask_combine(struct ovs_cmdl_context *ctx OVS_UNUSED)
|
2012-09-04 12:43:53 -07:00
|
|
|
|
{
|
|
|
|
|
struct flow_wildcards catchall;
|
2015-07-15 13:17:01 -07:00
|
|
|
|
struct minimask *minicatchall;
|
2012-09-04 12:43:53 -07:00
|
|
|
|
struct flow flow;
|
|
|
|
|
unsigned int idx;
|
|
|
|
|
|
|
|
|
|
flow_wildcards_init_catchall(&catchall);
|
2015-07-15 13:17:01 -07:00
|
|
|
|
minicatchall = minimask_create(&catchall);
|
|
|
|
|
assert(minimask_is_catchall(minicatchall));
|
2012-09-04 12:43:53 -07:00
|
|
|
|
|
|
|
|
|
random_set_seed(0x181bf0cd);
|
|
|
|
|
for (idx = 0; next_random_flow(&flow, idx); idx++) {
|
2015-07-15 13:17:01 -07:00
|
|
|
|
struct minimask *minimask, *minimask2;
|
2012-09-04 12:43:53 -07:00
|
|
|
|
struct flow_wildcards mask, mask2, combined, combined2;
|
2015-07-15 13:17:01 -07:00
|
|
|
|
struct {
|
|
|
|
|
struct minimask minicombined;
|
|
|
|
|
uint64_t storage[FLOW_U64S];
|
|
|
|
|
} m;
|
2012-09-04 12:43:53 -07:00
|
|
|
|
struct flow flow2;
|
|
|
|
|
|
|
|
|
|
mask.masks = flow;
|
2015-07-15 13:17:01 -07:00
|
|
|
|
minimask = minimask_create(&mask);
|
2012-09-04 12:43:53 -07:00
|
|
|
|
|
2015-07-15 13:17:01 -07:00
|
|
|
|
minimask_combine(&m.minicombined, minimask, minicatchall, m.storage);
|
|
|
|
|
assert(minimask_is_catchall(&m.minicombined));
|
2012-09-04 12:43:53 -07:00
|
|
|
|
|
|
|
|
|
any_random_flow(&flow2);
|
|
|
|
|
mask2.masks = flow2;
|
2015-07-15 13:17:01 -07:00
|
|
|
|
minimask2 = minimask_create(&mask2);
|
2012-09-04 12:43:53 -07:00
|
|
|
|
|
2015-07-15 13:17:01 -07:00
|
|
|
|
minimask_combine(&m.minicombined, minimask, minimask2, m.storage);
|
2013-06-10 22:48:58 -07:00
|
|
|
|
flow_wildcards_and(&combined, &mask, &mask2);
|
2015-07-15 13:17:01 -07:00
|
|
|
|
minimask_expand(&m.minicombined, &combined2);
|
2012-09-04 12:43:53 -07:00
|
|
|
|
assert(flow_wildcards_equal(&combined, &combined2));
|
|
|
|
|
|
2015-07-15 13:17:01 -07:00
|
|
|
|
free(minimask);
|
|
|
|
|
free(minimask2);
|
2012-09-04 12:43:53 -07:00
|
|
|
|
}
|
2012-12-21 14:59:35 -08:00
|
|
|
|
|
2015-07-15 13:17:01 -07:00
|
|
|
|
free(minicatchall);
|
2012-09-04 12:43:53 -07:00
|
|
|
|
}
|
|
|
|
|
|
test-classifier: Add benchmark.
Add a benchmark command for classifier lookup performance testing.
Running the test-classifier without arguments of with "--help" will
print the following usage:
usage: ovstest test-classifier benchmark <n_rules> <n_priorities> <n_subtables> <n_threads> <n_lookups>
where:
<n_rules> - The number of rules to install for lookups. More rules
makes misses less likely.
<n_priorities> - How many different priorities to use. Using only 1
priority will force lookups to continue through all
subtables.
<n_subtables> - Number of subtables to use. Normally a classifier has
rules with different kinds of masks, resulting in
multiple subtables (one per mask). However, in some
special cases a table may consist of only one kind of
rules, so there will be only one subtable.
<n_threads> - How many lookup threads to use. Using one thread should
give less variance accross runs, but classifier
scaling can be tested with multiple threads.
<n_lookups> - How many lookups each thread should perform.
For testing the classifier is filled with <n_rules> rules using
<n_subtables> different mask patterns and <n_priorities> different
priorities. A random set of lookup flows are created, and <n_threads>
lookup threads are spawned to perform <n_lookups> lookups each. The
count of hits and misses, as well as the overall execution time is
reported.
Example run:
$ tests/ovstest test-classifier benchmark 1000 1 30 1 3800000
Benchmarking with:
1000 rules with 1 priorities in 30 tables, 1 threads doing 3800000 lookups each
Without wildcards:
hits: 461520, misses: 3338480
classifier lookups: 386 ms, 9844559 lookups/sec
With wildcards:
hits: 461520, misses: 3338480
classifier lookups: 866 ms, 4387990 lookups/sec
Signed-off-by: Jarno Rajahalme <jrajahalme@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
2015-08-21 12:49:46 -07:00
|
|
|
|
|
|
|
|
|
static void help(struct ovs_cmdl_context *ctx);
|
|
|
|
|
|
2015-03-16 12:01:55 -04:00
|
|
|
|
static const struct ovs_cmdl_command commands[] = {
|
2012-09-04 12:43:53 -07:00
|
|
|
|
/* Classifier tests. */
|
2016-08-15 18:47:29 +00:00
|
|
|
|
{"empty", NULL, 0, 0, test_empty, OVS_RO },
|
|
|
|
|
{"destroy-null", NULL, 0, 0, test_destroy_null, OVS_RO },
|
|
|
|
|
{"single-rule", NULL, 0, 0, test_single_rule, OVS_RO },
|
|
|
|
|
{"rule-replacement", NULL, 0, 0, test_rule_replacement, OVS_RO },
|
|
|
|
|
{"many-rules-in-one-list", NULL, 0, 1, test_many_rules_in_one_list, OVS_RO },
|
|
|
|
|
{"many-rules-in-one-table", NULL, 0, 1, test_many_rules_in_one_table, OVS_RO },
|
|
|
|
|
{"many-rules-in-two-tables", NULL, 0, 0, test_many_rules_in_two_tables, OVS_RO },
|
|
|
|
|
{"many-rules-in-five-tables", NULL, 0, 0, test_many_rules_in_five_tables, OVS_RO },
|
|
|
|
|
{"benchmark", NULL, 0, 5, run_benchmarks, OVS_RO },
|
2025-05-16 23:25:17 +02:00
|
|
|
|
{"stress-prefixes", NULL, 0, 0, run_prefix_stress, OVS_RO },
|
2012-09-04 12:43:53 -07:00
|
|
|
|
|
|
|
|
|
/* Miniflow and minimask tests. */
|
2016-08-15 18:47:29 +00:00
|
|
|
|
{"miniflow", NULL, 0, 0, test_miniflow, OVS_RO },
|
|
|
|
|
{"minimask_has_extra", NULL, 0, 0, test_minimask_has_extra, OVS_RO },
|
|
|
|
|
{"minimask_combine", NULL, 0, 0, test_minimask_combine, OVS_RO },
|
2012-09-04 12:43:53 -07:00
|
|
|
|
|
2016-08-15 18:47:29 +00:00
|
|
|
|
{"--help", NULL, 0, 0, help, OVS_RO },
|
|
|
|
|
{NULL, NULL, 0, 0, NULL, OVS_RO },
|
2010-05-26 12:48:32 -07:00
|
|
|
|
};
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
test-classifier: Add benchmark.
Add a benchmark command for classifier lookup performance testing.
Running the test-classifier without arguments of with "--help" will
print the following usage:
usage: ovstest test-classifier benchmark <n_rules> <n_priorities> <n_subtables> <n_threads> <n_lookups>
where:
<n_rules> - The number of rules to install for lookups. More rules
makes misses less likely.
<n_priorities> - How many different priorities to use. Using only 1
priority will force lookups to continue through all
subtables.
<n_subtables> - Number of subtables to use. Normally a classifier has
rules with different kinds of masks, resulting in
multiple subtables (one per mask). However, in some
special cases a table may consist of only one kind of
rules, so there will be only one subtable.
<n_threads> - How many lookup threads to use. Using one thread should
give less variance accross runs, but classifier
scaling can be tested with multiple threads.
<n_lookups> - How many lookups each thread should perform.
For testing the classifier is filled with <n_rules> rules using
<n_subtables> different mask patterns and <n_priorities> different
priorities. A random set of lookup flows are created, and <n_threads>
lookup threads are spawned to perform <n_lookups> lookups each. The
count of hits and misses, as well as the overall execution time is
reported.
Example run:
$ tests/ovstest test-classifier benchmark 1000 1 30 1 3800000
Benchmarking with:
1000 rules with 1 priorities in 30 tables, 1 threads doing 3800000 lookups each
Without wildcards:
hits: 461520, misses: 3338480
classifier lookups: 386 ms, 9844559 lookups/sec
With wildcards:
hits: 461520, misses: 3338480
classifier lookups: 866 ms, 4387990 lookups/sec
Signed-off-by: Jarno Rajahalme <jrajahalme@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
2015-08-21 12:49:46 -07:00
|
|
|
|
static void
|
|
|
|
|
help(struct ovs_cmdl_context *ctx OVS_UNUSED)
|
|
|
|
|
{
|
|
|
|
|
const struct ovs_cmdl_command *p;
|
|
|
|
|
struct ds test_names = DS_EMPTY_INITIALIZER;
|
|
|
|
|
const int linesize = 80;
|
|
|
|
|
|
|
|
|
|
printf("usage: ovstest %s TEST [TESTARGS]\n"
|
|
|
|
|
"where TEST is one of the following:\n\n",
|
|
|
|
|
program_name);
|
|
|
|
|
|
|
|
|
|
for (p = commands; p->name != NULL; p++) {
|
|
|
|
|
if (*p->name != '-') { /* Skip internal commands */
|
|
|
|
|
if (test_names.length > 1
|
|
|
|
|
&& test_names.length + strlen(p->name) + 1 >= linesize) {
|
|
|
|
|
test_names.length -= 1;
|
|
|
|
|
printf ("%s\n", ds_cstr(&test_names));
|
|
|
|
|
ds_clear(&test_names);
|
|
|
|
|
}
|
|
|
|
|
ds_put_format(&test_names, "%s, ", p->name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (test_names.length > 2) {
|
|
|
|
|
test_names.length -= 2;
|
|
|
|
|
printf("%s\n", ds_cstr(&test_names));
|
|
|
|
|
}
|
|
|
|
|
ds_destroy(&test_names);
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-01 00:47:01 -07:00
|
|
|
|
static void
|
|
|
|
|
test_classifier_main(int argc, char *argv[])
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
2015-03-17 10:35:26 -04:00
|
|
|
|
struct ovs_cmdl_context ctx = {
|
|
|
|
|
.argc = argc - 1,
|
|
|
|
|
.argv = argv + 1,
|
|
|
|
|
};
|
2010-11-03 11:00:58 -07:00
|
|
|
|
set_program_name(argv[0]);
|
2015-06-11 15:53:43 -07:00
|
|
|
|
|
|
|
|
|
if (argc > 1 && !strcmp(argv[1], "--versioned")) {
|
|
|
|
|
versioned = true;
|
|
|
|
|
ctx.argc--;
|
|
|
|
|
ctx.argv++;
|
|
|
|
|
}
|
|
|
|
|
|
2009-07-08 13:19:16 -07:00
|
|
|
|
init_values();
|
2015-03-17 10:35:26 -04:00
|
|
|
|
ovs_cmdl_run_command(&ctx, commands);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
2014-04-01 00:47:01 -07:00
|
|
|
|
|
|
|
|
|
OVSTEST_REGISTER("test-classifier", test_classifier_main);
|