// SPDX-License-Identifier: BSD-2-Clause
/* Copyright (C) 2015 - 2021 Intel Corporation. */

#include <assert.h>
#include <iostream>
#include <stdio.h>
#include <vector>

#include "AllocatorFactory.hpp"
#include "CommandLine.hpp"
#include "Configuration.hpp"
#include "ConsoleLog.hpp"
#include "FunctionCallsPerformanceTask.h"
#include "Stats.hpp"
#include "StressIncreaseToMax.h"
#include "Task.hpp"
#include "TaskFactory.hpp"
#include "Tests.hpp"
#include "Thread.hpp"

/*
Command line description.
Syntax:
        key=value
Options:
        - 'test' - specify the test case. This option can be used with the
following values: 'calls', 'all' or 'self', where: 'calls' - function calls
performance test, 'all' - execute both above ('footprint' and 'calls') tests,
                'self' - execute self tests
                's1' - stress tests
                (perform allocations until the maximum amount of allocated
memory has been reached, than frees allocated memory. If the time interval has
not been exceed, than repeat the test),
        - 'operations' - the number of memory operations per thread
        - 'size_from' - lower bound for the random sizes of allocation
        - 'size_to' - upper bound for the random sizes of allocation
        - 'seed' - random seed
        - 'threads_num' - the number of threads per test case
        - 'time' - minimum execution time interval
        - 'kind' - the kind to test
        - 'csv_log' - if 'true' then log to csv file memory operations and
statistics
        - 'call' specify the allocation function call. This option can be used
with the following values: 'malloc' (default), 'calloc', 'realloc',
        - 'requested_memory_limit' test stops when the requested memory limit
has been reached
* - maximum of available memory in OS, or maximum memory based 'operations'
parameter Example:
1. Performance test:
./perf_tool test=all operations=1000 size_from=32 size_to=20480 seed=11 threads_num=200
2. Stress test
./perf_tool test=s1 time=120 kind=MEMKIND_HBW size_from=1048576 csv_log=true requested_memory_limit=1048576
*/

int main(int argc, char *argv[])
{
    unsigned mem_operations_num = 1000;
    size_t size_from = 32, size_to = 2048 * 1024;
    unsigned seed = 11;
    // should be at least one
    size_t threads_number = 10;

    CommandLine cmd_line(argc, argv);

    if ((argc >= 1) && cmd_line.is_option_set("test", "self")) {
        execute_self_tests();
        getchar();
    }

    cmd_line.parse_with_strtol("operations", mem_operations_num);
    cmd_line.parse_with_strtol("size_from", size_from);
    cmd_line.parse_with_strtol("size_to", size_to);
    cmd_line.parse_with_strtol("seed", seed);
    cmd_line.parse_with_strtol("threads_num", threads_number);

    bool is_csv_log_enabled = cmd_line.is_option_set("csv_log", "true");

    // Heap Manager initialization
    std::vector<AllocatorFactory::initialization_stat> stats =
        AllocatorFactory().initialization_test();

    if (!cmd_line.is_option_set("print_init_stats", "false")) {
        printf("\nInitialization overhead:\n");
        for (int i = 0; i < stats.size(); i++) {
            AllocatorFactory::initialization_stat stat = stats[i];
            printf(
                "%32s : time=%7.7f.s, ref_delta_time=%15f, node0=%10fMB, node1=%7.7fMB\n",
                AllocatorTypes::allocator_name(stat.allocator_type).c_str(),
                stat.total_time, stat.ref_delta_time, stat.memory_overhead[0],
                stat.memory_overhead[1]);
        }
    }

    // Stress test by repeatedly increasing memory (to maximum), until given
    // time interval has been exceed.
    if (cmd_line.is_option_set("test", "s1")) {
        printf("Stress test (StressIncreaseToMax) start. \n");

        if (!cmd_line.is_option_present("operations"))
            mem_operations_num = 1000000;

        unsigned time = 120; // Default time interval.
        cmd_line.parse_with_strtol("time", time);

        size_t requested_memory_limit = 1024 * 1024;
        cmd_line.parse_with_strtol("requested_memory_limit",
                                   requested_memory_limit);

        unsigned allocator = AllocatorTypes::MEMKIND_HBW;
        if (cmd_line.is_option_present("kind")) {
            // Enable memkind allocator and specify kind.
            allocator = AllocatorTypes::allocator_type(
                cmd_line.get_option_value("kind"));
        }
        TypesConf allocator_types;
        allocator_types.enable_type(allocator);

        TypesConf enable_func_calls;
        enable_func_calls.enable_type(FunctionCalls::MALLOC);

        TaskConf task_conf = {
            mem_operations_num,
            {mem_operations_num,
             size_from, // No random sizes.
             size_from},
            enable_func_calls,
            allocator_types,
            11,
            is_csv_log_enabled,
        };

        StressIncreaseToMax::execute_test_iterations(task_conf, time,
                                                     requested_memory_limit);
        return 0;
    }

    printf("\nTest configuration: \n");
    printf("\t memory operations per thread = %u \n", mem_operations_num);
    printf("\t seed = %d\n", seed);
    printf("\t number of threads = %zu\n", threads_number);
    printf("\t size from-to = %zu-%zu\n\n", size_from, size_to);

    assert(size_from <= size_to);

    TypesConf func_calls;
    func_calls.enable_type(FunctionCalls::FREE);

    if (cmd_line.is_option_present("call")) {
        // Enable heap manager function call.
        func_calls.enable_type(
            FunctionCalls::function_type(cmd_line.get_option_value("call")));
    } else {
        func_calls.enable_type(FunctionCalls::MALLOC);
    }

    TypesConf allocator_types;
    if (cmd_line.is_option_present("allocator")) {
        allocator_types.enable_type(AllocatorTypes::allocator_type(
            cmd_line.get_option_value("allocator")));
    } else {
        for (unsigned i = 0; i <= AllocatorTypes::MEMKIND_HBW_PREFERRED; i++) {
            allocator_types.enable_type(i);
        }
    }

    TaskConf conf = {
        mem_operations_num, // number memory operations
        {
            mem_operations_num, // number of memory operations
            size_from,          // min. size of single allocation
            size_to             // max. size of single allocatioion
        },
        func_calls,      // enable function calls
        allocator_types, // enable allocators
        seed,            // random seed
        is_csv_log_enabled,
    };

    // Function calls test
    if (cmd_line.is_option_set("test", "calls") ||
        cmd_line.is_option_set("test", "all")) {
        TaskFactory task_factory;
        std::vector<Thread *> threads;
        std::vector<Task *> tasks;

        for (int i = 0; i < threads_number; i++) {
            FunctionCallsPerformanceTask *task =
                static_cast<FunctionCallsPerformanceTask *>(
                    task_factory.create(conf));
            tasks.push_back(task);
            threads.push_back(new Thread(task));
            conf.seed += 1;
        }

        ThreadsManager threads_manager(threads);
        threads_manager.start();
        threads_manager.barrier();

        TimeStats stats;
        for (int i = 0; i < tasks.size(); i++) {
            stats += tasks[i]->get_results();
        }

        ConsoleLog::print_table(stats);
        ConsoleLog::print_requested_memory(stats, "func. calls test");

        threads_manager.release();
    }

    return 0;
}
