type t = V : (unit -> 'a) -> t

let stabilize_garbage_collector () =
  let rec go limit last_heap_live_words =
    if limit <= 0 then failwith "Unable to stabilize the number of live words in the heap" ;
    Gc.compact () ;
    let stat = Gc.stat () in
    if stat.Gc.live_words <> last_heap_live_words
    then go (pred limit) stat.Gc.live_words in
  go 10 0

let runnable f i =
  for _ = 1 to i do ignore @@ Sys.opaque_identity (f ()) done [@@inline]

let tmp = Bytes.create 40

let reset () = Bytes.fill tmp 0 40 ' '

let print ppf (n, m) =
  let l = n * 40 / m in
  Bytes.fill tmp 0 l '#' ;
  Fmt.pf ppf "[%s] %d%%%!" (Bytes.unsafe_to_string tmp) (n * 100 / m)

let samples = 750

let run t =
  let idx = ref 0 in
  let run = ref 0 in
  let (V fn) = t in

  let m = Array.create_float (samples * 2) in

  reset () ;
  Fmt.pr "%a" print (0, samples) ;

  while !idx < samples do
    let current_run = !run in
    let current_idx = !idx in

    (* XXX(dinosaure): GC and prints can put noise on our samples.
       - GC is not predictable
       - prints can add a latency on I/O *)
    (* Fmt.pr "\r%a" print (current_idx, samples) ; *)
    (* if current_run = 0 then stabilize_garbage_collector () ; *)

    let time_0 = Clock.now () in

    runnable fn current_run ;

    let time_1 = Clock.now () in

    m.((current_idx * 2) + 0) <- float_of_int current_run ;
    m.((current_idx * 2) + 1) <- Int64.to_float (Int64.sub time_1 time_0) ;

    let next =
      (max : int -> int -> int) (int_of_float (float_of_int current_run *. 1.01)) (succ current_run) in
    run := next ; incr idx
  done ;

  Fmt.pr "\r%a\n%!" print (samples, samples) ;

  Array.init samples (fun i -> [| m.((i * 2) + 0); m.((i * 2) + 1) |])

