This commit is contained in:
swrup 2025-11-11 02:07:51 +01:00
parent aa2ff7b2f0
commit 2f3113f55d
11742 changed files with 1223940 additions and 0 deletions

View file

@ -0,0 +1,275 @@
open Stdune
module Process = Dune_engine.Process
module Console = struct
include Dune_console
let printf fmt = printf ("[Bench] " ^^ fmt)
end
module Json = struct
include Chrome_trace.Json
include Dune_stats.Json
end
module Output = struct
type measurement =
[ `Int of int
| `Float of float
]
type bench =
{ name : string
; metrics : (string * [ measurement | `List of measurement list ] * string) list
}
let json_of_bench { name; metrics } : Json.t =
let metrics =
List.map metrics ~f:(fun (name, value, units) ->
let value =
match value with
| `Int i -> `Int i
| `Float f -> `Float f
| `List xs -> `List (xs :> Json.t list)
in
`Assoc [ "name", `String name; "value", value; "units", `String units ])
in
`Assoc [ "name", `String name; "metrics", `List metrics ]
;;
type t =
{ config : (string * Json.t) list
; version : int
; results : bench list
}
let to_json { config; version; results } : Json.t =
let assoc = [ "results", `List (List.map results ~f:json_of_bench) ] in
let assoc = ("version", `Int version) :: assoc in
let assoc =
match config with
| [] -> assoc
| _ :: _ -> ("config", `Assoc config) :: assoc
in
`Assoc assoc
;;
end
let git =
lazy
(let path = Env.get Env.initial "PATH" |> Option.value_exn |> Bin.parse_path in
Bin.which ~path "git" |> Option.value_exn)
;;
let dune = Path.of_string (Filename.concat Fpath.initial_cwd Sys.argv.(1))
let output_limit = Dune_engine.Execution_parameters.Action_output_limit.default
let make_stdout () = Process.Io.make_stdout ~output_on_success:Swallow ~output_limit
let make_stderr () = Process.Io.make_stderr ~output_on_success:Swallow ~output_limit
module Package = struct
type t =
{ org : string
; name : string
}
let uri { org; name } = sprintf "https://github.com/%s/%s" org name
let make org name = { org; name }
let clone t =
let stdout_to = make_stdout () in
let stderr_to = make_stderr () in
let stdin_from = Process.Io.(null In) in
Process.run
Strict
~display:Quiet
~stdout_to
~stderr_to
~stdin_from
(Lazy.force git)
[ "clone"; uri t ]
;;
end
let duniverse =
let pkg = Package.make in
[ pkg "ocaml-dune" "dune-bench" ]
;;
let prepare_workspace () =
Fiber.parallel_iter duniverse ~f:(fun (pkg : Package.t) ->
Fpath.rm_rf pkg.name;
Console.printf "cloning %s/%s" pkg.org pkg.name;
Fiber.finalize
(fun () -> Package.clone pkg)
~finally:(fun () ->
Fiber.return @@ Console.printf "finished cloning %s/%s" pkg.org pkg.name))
;;
let dune_build ~name ~sandbox =
let stdin_from = Process.(Io.null In) in
let stdout_to = make_stdout () in
let stderr_to = make_stderr () in
let gc_dump = Temp.create File ~prefix:"gc_stat" ~suffix:name in
let open Fiber.O in
(* Build with timings and gc stats *)
let+ times =
Process.run_with_times
Strict
dune
~display:Quiet
~stdin_from
~stdout_to
~stderr_to
([ "build"
; "@install"
; "--release"
; "--cache" (* explicitly disable cache *)
; "disabled"
; "--dump-gc-stats"
; Path.to_string gc_dump
]
@
match sandbox with
| `Yes -> [ "--sandbox"; "hardlink" ]
| `No -> [])
in
(* Read the gc stats from the dump file *)
Dune_lang.Parser.parse_string
~mode:Single
~fname:(Path.to_string gc_dump)
(Io.read_file gc_dump)
|> Dune_lang.Decoder.parse Dune_util.Gc.decode Univ_map.empty
|> Metrics.make times
;;
let run_bench ~sandbox =
let open Fiber.O in
let* clean = dune_build ~name:"clean" ~sandbox in
let+ zero =
let rec zero acc n =
if n = 0
then Fiber.return (List.rev acc)
else
let* time = dune_build ~name:("zero" ^ string_of_int n) ~sandbox in
zero (time :: acc) (pred n)
in
zero [] 5
in
clean, zero
;;
type ('float, 'int) bench_results =
{ size : int
; clean : ('float, 'int) Metrics.t
; zero : ('float, 'int) Metrics.t list
}
let tag_results { size; clean; zero } =
let tag data = Metrics.map ~f:(fun t -> `Float t) ~g:(fun t -> `Int t) data in
let list_tag data =
List.map data ~f:tag
|> Metrics.unzip
|> Metrics.map ~f:(fun x -> `List x) ~g:(fun x -> `List x)
in
`Int size, tag clean, list_tag zero
;;
(** Display all clean and null builds with a few exceptions:
- fragments - not consistent between builds
- stack_size - not very useful
- forced_collections - only available in OCaml >= 4.12 *)
let display_clean_and_zero_with_sandboxing
({ elapsed_time
; user_cpu_time
; system_cpu_time
; minor_words
; promoted_words
; major_words
; minor_collections
; major_collections
; heap_words
; heap_chunks
; live_words
; live_blocks
; free_words
; free_blocks
; largest_free
; fragments = _
; compactions
; top_heap_words
; stack_size = _
} :
_ Metrics.t)
(zero : _ Metrics.t)
=
let display what units clean zero =
{ Output.name = what
; metrics = [ "[Clean] " ^ what, clean, units; "[Null] " ^ what, zero, units ]
}
in
[ display "Build Time" "Seconds" elapsed_time zero.elapsed_time
; display "Minor Words" "Approx. Words" minor_words zero.minor_words
; display "Promoted Words" "Approx. Words" promoted_words zero.promoted_words
; display "Major Words" "Approx. Words" major_words zero.major_words
; display "Minor Collections" "Collections" minor_collections zero.minor_collections
; display "Major Collections" "Collections" major_collections zero.major_collections
; display "Heap Words" "Words" heap_words zero.heap_words
; display "Heap Chunks" "Chunks" heap_chunks zero.heap_chunks
; display "Live Words" "Words" live_words zero.live_words
; display "Live Blocks" "Blocks" live_blocks zero.live_blocks
; display "Free Words" "Words" free_words zero.free_words
; display "Free Blocks" "Blocks" free_blocks zero.free_blocks
; display "Largest Free" "Words" largest_free zero.largest_free
; display "Compactions" "Compactions" compactions zero.compactions
; display "Top Heap Words" "Words" top_heap_words zero.top_heap_words
; display "User CPU Time" "Seconds" user_cpu_time zero.user_cpu_time
; display "System CPU Time" "Seconds" system_cpu_time zero.system_cpu_time
]
;;
let format_results bench_results =
(* tagging data for json conversion *)
let size, clean, zero = tag_results bench_results in
(* bench results *)
[ { Output.name = "Misc"; metrics = [ "Size of _boot/dune.exe", size, "Bytes" ] } ]
@ display_clean_and_zero_with_sandboxing clean zero
;;
let () =
Dune_util.Log.init ~file:No_log_file ();
let dir = Temp.create Dir ~prefix:"dune" ~suffix:"bench" in
Sys.chdir (Path.to_string dir);
Path.as_external dir |> Option.value_exn |> Path.set_root;
Path.Build.set_build_dir (Path.Outside_build_dir.of_string "_build");
let module Scheduler = Dune_engine.Scheduler in
let config =
Dune_engine.Clflags.display := Quiet;
{ Scheduler.Config.concurrency = 10
; stats = None
; print_ctrl_c_warning = false
; watch_exclusions = []
}
in
let size =
let stat : Unix.stats = Path.stat_exn dune in
stat.st_size
in
let results =
Scheduler.Run.go config ~on_event:(fun _ _ -> ())
@@ fun () ->
let open Fiber.O in
(* Prepare the workspace *)
let* () = prepare_workspace () in
(* Build the clean and null builds *)
Console.printf "Building clean and null builds";
let+ clean, zero = run_bench ~sandbox:`No in
Console.printf "Finished building clean and null builds";
(* Return the bench results *)
format_results { size; clean; zero }
in
let version = 4 in
let output = { Output.config = []; version; results } in
print_string (Json.to_string (Output.to_json output));
flush stdout
;;