open Fiber let pr fmt = Format.printf fmt let epr fmt = Format.eprintf fmt external now : unit -> (int64[@unboxed]) = "b_mclock" "n_mclock" [@@noalloc] let reporter pid ppf = let report src level ~over k msgf = let k _ = over () ; k () in let with_metadata header _tags k ppf fmt = Format.kfprintf k ppf ("[%06d]%a[%a]: " ^^ fmt ^^ "\n%!") pid Logs_fmt.pp_header (level, header) Fmt.(styled `Magenta string) (Logs.Src.name src) in msgf @@ fun ?header ?tags fmt -> with_metadata header tags k ppf fmt in { Logs.report } let run uri = let open Lwt.Infix in let t0 = now () in Simple_client.run uri >>= fun _ -> let t1 = now () in Lwt.return (Int64.sub t1 t0) let run_client uri = run_process (fun () -> let () = Mirage_crypto_rng_unix.use_default () in Lwt_main.run (run uri)) let const x _ = x let clients ~n uri = parallel_map (List.init n (const uri)) ~f:run_client >>| Array.of_list let count ~p arr = let res = ref 0 in Array.iter (fun x -> if p x then incr res) arr ; !res let fold ~f a v = Array.fold_left f a v let map ~f a = Array.map f a let is_ok = function Ok _ -> true | _ -> false let is_error = function Error _ -> true | _ -> false let get_ok = function Ok v -> v | Error _ -> assert false let to_sec x = let x = Int64.to_float x in x /. 1e9 let ( <.> ) f g x = f (g x) let histogram res = let tbl = Hashtbl.create 0x100 in Array.iter (fun v -> let v = Float.round (to_sec v *. 1e2) in try let n = Hashtbl.find tbl v in Hashtbl.replace tbl v (succ n) with _ -> Hashtbl.add tbl v 1) res ; let res = Hashtbl.fold (fun k v a -> (k *. 1e-2, v) :: a) tbl [] in List.sort (fun (a, _) (b, _) -> Float.compare a b) res let utf8_chars = (* Characters: space @ [0x258F .. 0x2589] *) [| " "; "▏"; "▎"; "▍"; "▌"; "▋"; "▊"; "▉"; "█" |] let utf_num = Array.length utf8_chars - 1 (* (c) CraigFe *) let show_bar width ppf proportion = let bar_width = let width = width () in width - 2 in let squaresf = Float.of_int bar_width *. proportion in let squares = Float.to_int squaresf in let filled = min squares bar_width in let not_filled = bar_width - filled - 1 in Format.pp_print_string ppf "│" ; for _ = 1 to filled do Format.pp_print_string ppf utf8_chars.(utf_num) done ; (if filled <> bar_width then let () = let chunks = Float.to_int (squaresf *. Float.of_int utf_num) in let index = chunks - (filled * utf_num) in if index < utf_num then Format.pp_print_string ppf utf8_chars.(index) in for _ = 1 to not_filled do Format.pp_print_string ppf utf8_chars.(0) done) ; Format.pp_print_string ppf "│" let exit_failure = 1 let exit_success = 0 let show res = let er = count ~p:is_error res in if er > 0 then ( pr "Got %d error(s).\n%!" er ; exit exit_failure) else let res = map ~f:get_ok res in let total = fold ~f:Int64.add 0L res in let max = fold ~f:max 0L res in let min = fold ~f:min max res in let avg = Int64.div total (Int64.of_int (Array.length res)) in pr "Total: %2.03fs\n%!" (to_sec total) ; pr "Slowest: %2.03fs\n%!" (to_sec max) ; pr "Fastest: %2.03fs\n%!" (to_sec min) ; pr "Average: %2.03fs\n%!" (to_sec avg) ; let histogram = histogram res in let width () = 40 in let max = Float.of_int (List.fold_left (fun a (_, v) -> v + a) 0 histogram) in pr "\n%!" ; pr "Response time histogram:\n%!" ; List.iter (fun (k, v) -> let v = Int64.of_int v in let p = Int64.to_float v /. max in pr "%0.3f [%03Ld]\t%a\n%!" k v (show_bar width) p) histogram ; exit exit_success let concurrency = ref 50 let number = ref 200 let uri = ref None let anonymous_argument v = match !uri with | None -> ( try uri := Some (Uri.of_string v) with _ -> Format.eprintf "Invalid uri: %S.\n%!" v ; exit exit_failure) | Some _ -> () let spec = [ ( "-c", Arg.Set_int concurrency, "Number of workers to run concurrently. Total number of requests cannot \ be smaller than the concurrency level. Default is 50." ); ("-n", Arg.Set_int number, "Number of requests to run. Default is 200."); ] let usage = Format.asprintf "%s [-c ] [-n ] uri" Sys.argv.(0) let () = Arg.parse spec anonymous_argument usage ; match !uri with | Some uri -> Fiber.set_concurrency !concurrency ; (* Lwt_preemptive.init !concurrency !concurrency ignore ; *) let res = Fiber.run (clients ~n:!number uri) in show res | None -> Format.eprintf "%s\n%!" usage ; exit exit_failure