/*
 * Copyright (c) 2010, 2012, 2013, 2014 Nicira, Inc.
 *
 * 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:
 *
 *     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.
 */

#include <config.h>
#undef NDEBUG
#include <assert.h>
#include <getopt.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include "flow.h"
#include "multipath.h"
#include "openvswitch/ofp-actions.h"
#include "ovstest.h"
#include "util.h"

static void
test_multipath_main(int argc, char *argv[])
{
    enum { MP_MAX_LINKS = 63 };
    struct ofpact_multipath mp;
    bool ok = true;
    char *error;
    int n;

    set_program_name(argv[0]);

    if (argc != 2) {
        ovs_fatal(0, "usage: %s multipath_action", program_name);
    }

    error = multipath_parse(&mp, argv[1]);
    if (error) {
        ovs_fatal(0, "%s", error);
    }

    for (n = 1; n <= MP_MAX_LINKS; n++) {
        enum { N_FLOWS = 65536 };
        double disruption, perfect, distribution;
        int histogram[MP_MAX_LINKS];
        double sum_dev2, stddev;
        int changed;
        int i;

        changed = 0;
        memset(histogram, 0, sizeof histogram);
        for (i = 0; i < N_FLOWS; i++) {
            int old_link, new_link;
            struct flow_wildcards wc;
            struct flow flow;

            flow_random_hash_fields(&flow);
            flow_wildcards_init_catchall(&wc);

            mp.max_link = n - 1;
            multipath_execute(&mp, &flow, &wc);
            old_link = flow.regs[0];

            mp.max_link = n;
            multipath_execute(&mp, &flow, &wc);
            new_link = flow.regs[0];

            assert(old_link >= 0 && old_link < n);
            assert(new_link >= 0 && new_link < n + 1);

            histogram[old_link]++;
            changed += old_link != new_link;
        }

        sum_dev2 = 0.0;
        for (i = 0; i < n; i++) {
            double mean = (double) N_FLOWS / n;
            double deviation = histogram[i] - mean;

            sum_dev2 += deviation * deviation;
        }
        stddev = sqrt(sum_dev2 / n);

        disruption = (double) changed / N_FLOWS;
        perfect = 1.0 / (n + 1);
        distribution = stddev / ((double) N_FLOWS / n);
        printf("%2d -> %2d: disruption=%.2f (perfect=%.2f); "
               "stddev/expected=%.4f\n",
               n, n + 1, disruption, perfect, distribution);

        switch (mp.algorithm) {
        case NX_MP_ALG_MODULO_N:
            if (disruption < (n < 2 ? .25 : .5)) {
                fprintf(stderr, "%d -> %d: disruption=%.2f < .5\n",
                        n, n + 1, disruption);
                ok = false;
            }
            break;

        case NX_MP_ALG_HASH_THRESHOLD:
            if (disruption < .48 || disruption > .52) {
                fprintf(stderr, "%d -> %d: disruption=%.2f not approximately "
                        ".5\n", n, n + 1, disruption);
                ok = false;
            }
            break;

        case NX_MP_ALG_ITER_HASH:
            if (!(n & (n - 1))) {
                break;
            }
            /* Fall through. */
        case NX_MP_ALG_HRW:
            if (fabs(disruption - perfect) >= .01) {
                fprintf(stderr, "%d -> %d: disruption=%.5f differs from "
                        "perfect=%.5f by more than .01\n",
                        n, n + 1, disruption, perfect);
                ok = false;
            }
            break;

        default:
            OVS_NOT_REACHED();
        }
    }

    exit(ok ? 0 : 1);
}

OVSTEST_REGISTER("test-multipath", test_multipath_main);
