175 lines
4.5 KiB
OCaml
175 lines
4.5 KiB
OCaml
let src = Logs.Src.create "builder-viz" ~doc:"Builder_viz"
|
|
module Log = (val Logs.src_log src : Logs.LOG)
|
|
|
|
open Rresult
|
|
|
|
let read_file file =
|
|
try
|
|
let fh = open_in file in
|
|
try
|
|
let content = really_input_string fh (in_channel_length fh) in
|
|
close_in_noerr fh ;
|
|
content
|
|
with _ ->
|
|
close_in_noerr fh;
|
|
invalid_arg ("Error reading file: " ^ file)
|
|
with _ -> invalid_arg ("Error opening file " ^ file)
|
|
|
|
let print_treemap_html elf_path elf_size =
|
|
let open Modulectomy in
|
|
let infos =
|
|
elf_path
|
|
|> Elf.get
|
|
|> Result.map_error (fun _ -> R.msg "Invalid ELF file")
|
|
|> R.failwith_error_msg
|
|
in
|
|
let info, excluded_minors =
|
|
let size, info =
|
|
infos
|
|
|> Info.import
|
|
|> Info.diff_size_tree
|
|
in
|
|
(*> Note: this heuristic fails if one has all subtrees of equal size*)
|
|
let node_big_enough subtree =
|
|
match Info.(subtree.T.value.size) with
|
|
| None -> true
|
|
| Some subtree_size ->
|
|
let pct = Int64.(to_float subtree_size /. to_float size) in
|
|
pct > 0.004
|
|
in
|
|
info
|
|
|> Info.prefix_filename
|
|
|> Info.cut 2
|
|
|> Info.partition_subtrees node_big_enough
|
|
in
|
|
let scale_chunks =
|
|
let excluded_minors_size =
|
|
excluded_minors
|
|
|> List.map Info.compute_area
|
|
|> List.fold_left Int64.add 0L
|
|
in
|
|
[
|
|
"Smaller excluded entries", excluded_minors_size
|
|
]
|
|
in
|
|
let override_css = {|
|
|
.treemap-module {
|
|
fill: rgb(60, 60, 87);
|
|
}
|
|
.treemap-functor > text, .treemap-module > text {
|
|
fill: bisque;
|
|
}
|
|
|}
|
|
in
|
|
info
|
|
|> Treemap.of_tree
|
|
|> Treemap.to_html_with_scale
|
|
~binary_size:elf_size
|
|
~scale_chunks
|
|
~override_css
|
|
|> Tyxml.Html.pp () Format.std_formatter
|
|
(* |> Treemap.svg
|
|
* |> Fmt.to_to_string (Tyxml.Svg.pp ()) *)
|
|
|
|
let print_dependencies_html file =
|
|
let module G = Opam_graph in
|
|
let switch = read_file file in
|
|
let data = OpamFile.SwitchExport.read_from_string switch in
|
|
let graph = G.Ui.dependencies ~transitive:false data in
|
|
let sharing_stats =
|
|
data
|
|
|> G.dependencies ~transitive:false
|
|
|> G.calc_sharing_stats in
|
|
let override_css = {|
|
|
.deps-svg-wrap {
|
|
background: rgb(60, 60, 87);
|
|
}
|
|
|}
|
|
in
|
|
let html = G.Render.Html.of_assoc ~override_css ~sharing_stats graph in
|
|
Format.printf "%a" G.Render.Html.pp html
|
|
|
|
module Cmd_aux = struct
|
|
|
|
module Arg_aux = struct
|
|
|
|
let elf_path =
|
|
let doc = "The file-path of the debug-ELF to be analyzed" in
|
|
Cmdliner.Arg.(
|
|
required &
|
|
pos 0 (some file) None &
|
|
info ~doc ~docv:"DEBUG_ELF_PATH" []
|
|
)
|
|
|
|
let elf_size =
|
|
let doc = "The file-size of the stripped ELF file in bytes" in
|
|
Cmdliner.Arg.(
|
|
required &
|
|
pos 1 (some int) None &
|
|
info ~doc ~docv:"STRIPPED_ELF_SIZE" []
|
|
)
|
|
|
|
let opam_switch_path =
|
|
let doc = "The Opam-switch export file of the package to be analyzed" in
|
|
Cmdliner.Arg.(
|
|
required &
|
|
pos 0 (some file) None &
|
|
info ~doc ~docv:"SWITCH_EXPORT_PATH" []
|
|
)
|
|
|
|
end
|
|
|
|
module Aux = struct
|
|
|
|
let help man_format cmds = function
|
|
| None -> `Help (man_format, None)
|
|
| Some cmd ->
|
|
if List.mem cmd cmds
|
|
then `Help (man_format, Some cmd)
|
|
else `Error (true, "Unknown command: " ^ cmd)
|
|
|
|
end
|
|
|
|
open Cmdliner
|
|
|
|
let treemap =
|
|
let doc = "Dump treemap SVG and CSS wrapped in HTML" in
|
|
let term = Term.(const print_treemap_html $ Arg_aux.elf_path $ Arg_aux.elf_size) in
|
|
let info = Cmd.info ~doc "treemap" in
|
|
Cmd.v info term
|
|
|
|
let dependencies =
|
|
let doc = "Dump opam dependencies SVG and CSS wrapped in HTML" in
|
|
let term = Term.(const print_dependencies_html $ Arg_aux.opam_switch_path) in
|
|
let info = Cmd.info ~doc "dependencies" in
|
|
Cmd.v info term
|
|
|
|
let help =
|
|
let topic =
|
|
let doc = "Command to get help on" in
|
|
Cmdliner.Arg.(value & pos 0 (some string) None & info ~doc ~docv:"COMMAND" [])
|
|
in
|
|
let doc = "Builder database help" in
|
|
let term = Term.(ret (const Aux.help $ Arg.man_format $ choice_names $ topic)) in
|
|
let info = Cmd.info ~doc "help" in
|
|
Cmd.v info term
|
|
|
|
let default_info, default_cmd =
|
|
let doc = "Builder database command" in
|
|
let term = Term.(ret (const Aux.help $ Arg.man_format $ choice_names $ const None)) in
|
|
let info = Cmd.info ~doc "builder-viz" in
|
|
info, term
|
|
|
|
end
|
|
|
|
let () =
|
|
let open Cmdliner in
|
|
Cmd.group
|
|
~default:Cmd_aux.default_cmd Cmd_aux.default_info
|
|
[
|
|
Cmd_aux.help;
|
|
Cmd_aux.treemap;
|
|
Cmd_aux.dependencies;
|
|
]
|
|
|> Cmd.eval
|
|
|> exit
|