//! Benchmark the performance of the submission ring by timing tasks which submit and wait on noop requests.
use glommio::{LocalExecutorBuilder, Task};
use std::fmt;
use std::time::{Duration, Instant};

struct Bench {
    num_tasks: u32,
    num_events: u32,
}

const BENCH_RUNS: &'static [Bench] = &[
    Bench {
        num_tasks: 100,
        num_events: 10_000_000,
    },
    Bench {
        num_tasks: 1_000,
        num_events: 10_000_000,
    },
    Bench {
        num_tasks: 10_000,
        num_events: 10_000_000,
    },
    Bench {
        num_tasks: 100_000,
        num_events: 10_000_000,
    },
    Bench {
        num_tasks: 1_000_000,
        num_events: 10_000_000,
    },
];

struct Measurement {
    total_events: u32,
    total_tasks: u32,
    average_task_duration: Duration,
}

impl fmt::Display for Measurement {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            r"Completed submission of {} noop requests across {} tasks, {} per task.
            Average task duration: {:?}",
            self.total_events,
            self.total_tasks,
            self.total_events / self.total_tasks,
            self.average_task_duration
        )
    }
}

fn main() {
    let mut measurements = vec![];
    let num_bench_runs = 5;
    for bench in BENCH_RUNS {
        for _ in 0..num_bench_runs {
            let ex = LocalExecutorBuilder::new().pin_to_cpu(0).make().unwrap();
            let measurement = ex.run(run_bench_tasks(bench.num_tasks, bench.num_events));

            println!("{}", measurement);
            measurements.push(measurement);
        }

        let sum = measurements.iter().fold(Duration::from_secs(0), |acc, v| {
            acc + v.average_task_duration
        });
        let average = sum / num_bench_runs;
        println!(
            "Average task duration across {} runs: {:?}\n",
            num_bench_runs, average
        );
        measurements.clear();
    }
}

async fn run_bench_tasks(num_tasks: u32, num_events: u32) -> Measurement {
    let num_ops_per_task = num_events / num_tasks;

    let mut handles = vec![];
    for _ in 0..num_tasks {
        let handle = Task::local(async move {
            let start_time = Instant::now();
            let submitter = glommio::nop::NopSubmitter::new();
            for _ in 0..num_ops_per_task {
                submitter.run_nop().await.unwrap();
            }
            start_time.elapsed()
        });
        handles.push(handle)
    }

    let mut task_duration_sum = Duration::from_secs(0);
    for handle in handles {
        let task_duration = handle.await;
        task_duration_sum += task_duration;
    }
    let average_task_duration = task_duration_sum / num_tasks as u32;
    Measurement {
        total_events: num_events,
        total_tasks: num_tasks,
        average_task_duration,
    }
}
