168 lines
4.6 KiB
OCaml
168 lines
4.6 KiB
OCaml
|
|
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 <number>] [-n <number>] 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
|