/*
 * bench_frame       : boilerplate for mpi program
 * measure(iter)     : measures `tf_dur` for $(iter) iterations
 * run_stat(N, var)  : run N measurements and obtain (avg, std) in sum1, sum2
 * warm_up(iter, dur): repeat until measurements (iter, dur) stabilize
 * report_latency(msgsize, MULTIPLICITY) : print a line of latency result
 */

subcode: bench_frame
    $include stdio
    $include stdlib
    $(if:HAS_MTEST)
        $include mpitest.h
    $(else)
        $include mpi

    $function main
        $(if:HAS_MTEST)
            MTest_Init(NULL, NULL);
        $(else)
            MPI_Init(NULL, NULL);

        $global grank, gsize: int
        MPI_Comm_rank(MPI_COMM_WORLD, &grank);
        MPI_Comm_size(MPI_COMM_WORLD, &gsize);

        $call check_launch

        MPI_Comm comm = MPI_COMM_WORLD;

        $my void *buf
        $(if:HAS_MTEST)
            $call mtest_malloc, MAX_BUFSIZE
        $(else)
            buf = malloc(MAX_BUFSIZE)
        $if !buf
            printf("! Failed to allocate buffer (size=%d)\n", MAX_BUFSIZE)
            return 1

        $(if:IS_RMA)
            $global MPI_Win win
            $if grank == 0
                MPI_Win_create(NULL, 0, 1, MPI_INFO_NULL, MPI_COMM_WORLD, &win)
            $else
                MPI_Win_create(buf, MAX_BUFSIZE, 1, MPI_INFO_NULL, MPI_COMM_WORLD, &win)

        $if grank == 0
            printf("TEST $(_pagename):\n")
            $call @report_header
        $call main
        $if grank == 0
            printf("\n")

        $(if:IS_RMA)
            MPI_Win_free(&win);

        $(if:HAS_MTEST)
            MTest_Finalize(0);
        $(else)
            MPI_Finalize();
    # -----
    subcode: check_launch
        $(if:MIN_PROCS)
            $if gsize < $(MIN_PROCS)
                printf("! Test $(_pagename) requires $(MIN_PROCS) processes !\n");
                return 1
        $(if:MEM_TYPES=sendrecv)
            $if gsize % 2 == 1
                printf("! Test $(_pagename) requires even number of processes to form even/odd pairs !\n");
            $global gsrc, gdst: int
            $if grank % 2 == 0
                gsrc = grank
                gdst = grank + 1
            $else
                gsrc = grank - 1
                gdst = grank

macros:
    use_double: 1

macros:
    MAX_BUFSIZE: 5000000  # 5 MB

#----------------------------------------
subcode: _autoload
    $register_prefix(comm) MPI_Comm
    $define MAX_BUFSIZE $(MAX_BUFSIZE)
    $define NUM_REPEAT 20

subcode: foreach_size
    $for int size = 0; size < $(MAX_BUFSIZE); size = (size==0)?1:size*2
        $(set:MSG_SIZE=size)
        BLOCK

# measure tf_dur over iter
subcode: measure(iter)
    tf_start = MPI_Wtime()
    $for 0:$(iter)
        BLOCK
    tf_dur = MPI_Wtime() - tf_start

# repeat N times and calc avg in sum1 and std in sum2
subcode: run_stat(N, var)
    $my double sum1=0, double sum2=0
    $for 0:$(N)
        BLOCK
        sum1 += $(var)
        sum2 += $(var) * $(var)
    sum1 /= $(N)
    sum2 /= $(N)
    $(if:RUN_STAT_VARIANCE)
        sum2 = (sum2 - sum1 * sum1)
    $(else)
        sum2 = sqrt(sum2 - sum1 * sum1)

# repeat until dur stabilize and iter adjusted to last minimum of 1ms
subcode: warm_up(iter, dur)
    # minimum iteration to fill the duration to 1 ms
    $(if:!WARM_UP_NUM_BEST)
        $(set:NUM_BEST=10)
    $(else)
        $(set:NUM_BEST=$(WARM_UP_NUM_BEST))
    $(if:!MIN_ITER)
        $(set:MIN_ITER=(int) ($(iter) * 0.001 / $(dur)))
    $(iter) = 2
    $my double last_dur = 1.0
    $my int num_best = 0
    $while num_best < $(NUM_BEST)
        BLOCK
        $my int min_iter = $(MIN_ITER)
        $if $(iter) < 10000 && $(iter) < min_iter
            $(iter) = min_iter
            num_best = 0
            continue
        # check that t_dur is no longer monotonically decreasing
        $if $(dur) > last_dur
            num_best++
        last_dur = $(dur)

subcode: header_latency
    printf("%12s %10s(us) %6s(us) %12s(MB/s)\n", "msgsize", "latency", "sigma", "bandwidth")

subcode: report_latency(MSGSIZE, MULTIPLICITY)
    $my tf_latency, tf_sigma, tf_bw
    tf_latency = sum1 / ($(MULTIPLICITY)) * 1e6
    tf_sigma = sum2 / ($(MULTIPLICITY)) * 1e6
    tf_bw = $(MSGSIZE) / tf_latency
    printf("%12d %10.3f     %6.3f     %12.3f\n", $(MSGSIZE), tf_latency, tf_sigma, tf_bw)

