(* * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. *) module Tbl = CCHashtbl.Make (String) let read filename = let tbl = Tbl.create 64 in List.iter (Sexp.load_sexps filename) ~f:(fun sexp -> let {Report.name; entry} = Report.t_of_sexp sexp in match (Tbl.find_opt tbl name, entry) with | None, ProcessTimes (etime, ptimes) -> Tbl.replace tbl name ([(etime, ptimes)], [], []) | None, GcStats gc -> Tbl.replace tbl name ([], [gc], []) | None, Status status -> Tbl.replace tbl name ([], [], [status]) | Some (times, gcs, statuses), ProcessTimes (etime, ptimes) -> Tbl.replace tbl name ((etime, ptimes) :: times, gcs, statuses) | Some (times, gcs, statuses), GcStats gc -> Tbl.replace tbl name (times, gc :: gcs, statuses) | Some (times, gc, statuses), Status status -> Tbl.replace tbl name (times, gc, status :: statuses) ) ; tbl type times = {etime: float; utime: float; stime: float} type ('t, 'g) row = { name: string ; times: 't ; times_deltas: 't ; gcs: 'g ; gcs_deltas: 'g ; status: Report.status list ; status_deltas: Report.status list option } let times_of_raw (etime, ptimes) = let {Unix.tms_utime; tms_cutime; tms_stime; tms_cstime} = ptimes in let utime = tms_utime +. tms_cutime in let stime = tms_stime +. tms_cstime in let etime = etime in {etime; utime; stime} let add_time base_times row ptimes = let tustimes = times_of_raw ptimes in let times = tustimes :: row.times in let times_deltas = Option.fold base_times ~init:row.times_deltas ~f:(fun times_deltas {etime= btt; utime= but; stime= bst} -> let {etime= tt; utime= ut; stime= st} = tustimes in {etime= tt -. btt; utime= ut -. but; stime= st -. bst} :: times_deltas ) in {row with times; times_deltas} let add_times base_times times row = if List.is_empty times then {row with times_deltas= Option.to_list base_times} else List.fold ~f:(add_time base_times) ~init:row times let add_gc base_gcs row gc = let gcs = gc :: row.gcs in let gcs_deltas = Option.fold base_gcs ~init:row.gcs_deltas ~f:(fun gcs_deltas bgc -> Report. { allocated= gc.allocated -. bgc.allocated ; promoted= gc.promoted -. bgc.promoted ; peak_size= gc.peak_size -. bgc.peak_size } :: gcs_deltas ) in {row with gcs; gcs_deltas} let add_gcs base_gcs gcs row = if List.is_empty gcs then {row with gcs_deltas= Option.to_list base_gcs} else List.fold ~f:(add_gc base_gcs) ~init:row gcs let add_status base_status row status = if List.mem ~equal:Report.equal_status row.status status then row else match base_status with | Some base_status when not (List.mem ~equal:Report.equal_status base_status status) -> { row with status= status :: row.status ; status_deltas= Some (base_status @ Option.value row.status_deltas ~default:[]) } | _ -> {row with status= status :: row.status} let add_statuses base_status statuses row = List.fold ~f:(add_status base_status) ~init:row statuses let ave_floats flts = assert (not (Iter.is_empty flts)) ; let min, max, sum, num = Iter.fold flts ~init:(Float.infinity, Float.neg_infinity, 0., 0) ~f:(fun (min, max, sum, num) flt -> (Float.min min flt, Float.max max flt, sum +. flt, num + 1) ) in if num >= 5 then (sum -. min -. max) /. Float.of_int (num - 2) else sum /. Float.of_int num let combine name b_result c_result = let base_times, base_gcs, base_status = match b_result with | Some (times, gcs, statuses) -> let times = if List.is_empty times then None else let etimes, utimes, stimes, cutimes, cstimes = List.fold times ~init: ( Iter.empty , Iter.empty , Iter.empty , Iter.empty , Iter.empty ) ~f:(fun (etimes, utimes, stimes, cutimes, cstimes) ( etime , {Unix.tms_utime; tms_cutime; tms_stime; tms_cstime} ) -> ( Iter.cons etime etimes , Iter.cons tms_utime utimes , Iter.cons tms_stime stimes , Iter.cons tms_cutime cutimes , Iter.cons tms_cstime cstimes ) ) in Some (times_of_raw ( ave_floats etimes , { tms_utime= ave_floats utimes ; tms_stime= ave_floats stimes ; tms_cutime= ave_floats cutimes ; tms_cstime= ave_floats cstimes } )) in let gcs = if List.is_empty gcs then None else let allocs, promos, peaks = List.fold gcs ~init:(Iter.empty, Iter.empty, Iter.empty) ~f:(fun (allocs, promos, peaks) {Report.allocated; promoted; peak_size} -> ( Iter.cons allocated allocs , Iter.cons promoted promos , Iter.cons peak_size peaks ) ) in Some Report. { allocated= ave_floats allocs ; promoted= ave_floats promos ; peak_size= ave_floats peaks } in let status = Some (List.dedup_and_sort ~compare:Report.compare_status statuses) in (times, gcs, status) | None -> (None, None, None) in let row = { name ; times= [] ; times_deltas= [] ; gcs= [] ; gcs_deltas= [] ; status= [] ; status_deltas= None } in match c_result with | None -> let times_deltas = Option.to_list base_times in let gcs_deltas = Option.to_list base_gcs in let status_deltas = base_status in {row with times_deltas; gcs_deltas; status_deltas} | Some (c_times, c_gcs, c_statuses) -> row |> add_times base_times c_times |> add_gcs base_gcs c_gcs |> add_statuses base_status c_statuses type ranges = { max_time: float ; pct_time: float ; max_alloc: float ; pct_alloc: float ; max_promo: float ; pct_promo: float ; max_peak: float ; pct_peak: float } let ranges rows = let init = { max_time= 0. ; pct_time= 0. ; max_alloc= 0. ; pct_alloc= 0. ; max_promo= 0. ; pct_promo= 0. ; max_peak= 0. ; pct_peak= 0. } in Iter.fold rows ~init ~f:(fun acc {times; times_deltas; gcs; gcs_deltas} -> Option.fold times_deltas ~init:acc ~f:(fun acc deltas -> let max_time = Float.max acc.max_time (Float.abs deltas.etime) in let pct_time = Option.fold times ~init:acc.pct_time ~f:(fun pct_time times -> let pct = 100. *. deltas.etime /. times.etime in Float.max pct_time (Float.abs pct) ) in {acc with max_time; pct_time} ) |> fun init -> Option.fold gcs_deltas ~init ~f:(fun acc deltas -> let max_alloc = Float.max acc.max_alloc deltas.Report.allocated in let pct_alloc = Option.fold gcs ~init:acc.pct_alloc ~f:(fun pct_alloc gcs -> let pct = 100. *. deltas.Report.allocated /. gcs.Report.allocated in Float.max pct_alloc (Float.abs pct) ) in let max_promo = Float.max acc.max_promo deltas.Report.promoted in let pct_promo = Option.fold gcs ~init:acc.pct_promo ~f:(fun pct_promo gcs -> let pct = 100. *. deltas.Report.promoted /. gcs.Report.promoted in Float.max pct_promo (Float.abs pct) ) in let max_peak = Float.max acc.max_peak deltas.Report.peak_size in let pct_peak = Option.fold gcs ~init:acc.pct_peak ~f:(fun pct_peak gcs -> let pct = 100. *. deltas.Report.peak_size /. gcs.Report.peak_size in Float.max pct_peak (Float.abs pct) ) in { acc with max_alloc ; pct_alloc ; max_promo ; pct_promo ; max_peak ; pct_peak } ) ) let color max dat = (* linear interpolation mapping -1 to green, 0 to lace, and 1 to red *) let green = (133., 153., 0.) in let lace = (253., 246., 227.) in let red = (220., 50., 47.) in let gradient x = let scale x (r0, g0, b0) (r1, g1, b1) = let scale1 x c0 c1 = (x *. (c1 -. c0)) +. c0 in (scale1 x r0 r1, scale1 x g0 g1, scale1 x b0 b1) in let x = Float.max (-1.) (Float.min x 1.) in if Float.is_negative x then scale (-.x) lace green else scale x lace red in let rgb_to_hex (r, g, b) = let to_int x = Int.min 255 (Int.max 0 (Float.to_int x)) in Printf.sprintf "#%2x%2x%2x" (to_int r) (to_int g) (to_int b) in let rat = dat /. max in if Float.is_nan rat then rgb_to_hex lace else rgb_to_hex (gradient rat) let write_html ranges rows chan = let pf fmt = Printf.fprintf chan fmt in pf "
Test | elapsed (sec) |
Δ |
Δ%% |
user (sec) |
Δ |
Δ%% |
system (sec) |
Δ |
Δ%% |
alloc (MB) |
Δ |
Δ%% |
promo (MB) |
Δ |
Δ%% |
peak (MB) |
Δ |
Δ%% |
Status | Δ |
%12.3f | \n" t in let nondelta ppf t = Printf.fprintf ppf "%12.3f | \n" t in let delta max pct t ppf d = let r = 100. *. d /. t in Printf.fprintf ppf "%12.3f | \n\%12.2f%% | \n" (color max d) d (color pct r) r in let timed = delta ranges.max_time ranges.pct_time in let allocd = delta ranges.max_alloc ranges.pct_alloc in let promod = delta ranges.max_promo ranges.pct_promo in let peakd = delta ranges.max_peak ranges.pct_peak in let pf_status ppf s = let status_to_string = Format.asprintf "%a" Report.pp_status in Printf.fprintf ppf "%s" (String.prefix (status_to_string s) 50) in let stat ppf = function | [] -> Printf.fprintf ppf "\n" | ss -> List.iter ss ~f:(fun s -> match (s : Report.status) with | Safe _ | Unsafe _ | Ok -> Printf.fprintf ppf " | %a | \n" pf_status s | _ -> Printf.fprintf ppf "%a | \n" pf_status s ) in let statd ppf = function | None | Some [] -> Printf.fprintf ppf "\n" | Some ss -> Printf.fprintf ppf " | " ; List.iter ss ~f:(fun s -> Printf.fprintf ppf "%a" pf_status s) ; Printf.fprintf ppf " | \n" in pf "|||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
%s | " name ; ( match (times, times_deltas) with | ( Some {etime; utime; stime} , Some {etime= etime_delta; utime= utime_delta; stime= stime_delta} ) -> pf "%a%a%a%a%a%a" time etime (timed etime) etime_delta time utime (timed utime) utime_delta time stime (timed stime) stime_delta | Some {etime; utime; stime}, None -> pf "%a\n\ %a | \n\ %a | \n" time etime time utime time stime | None, Some {etime; utime; stime} -> pf " | %a | \n\ | %a | \n\ | %a | \n" nondelta etime nondelta utime nondelta stime | None, None -> pf " | \n\ | \n\ | \n" ) ; ( match (gcs, gcs_deltas) with | ( Some {Report.allocated; promoted; peak_size} , Some { allocated= allocated_delta ; promoted= promoted_delta ; peak_size= peak_size_delta } ) -> pf "%a%a%a%a%a%a" time allocated (allocd allocated) allocated_delta time promoted (promod promoted) promoted_delta time peak_size (peakd peak_size) peak_size_delta | Some {allocated; promoted; peak_size}, None -> pf "%a | \n\ %a | \n\ %a | \n" time allocated time promoted time peak_size | None, Some {allocated; promoted; peak_size} -> pf " | %a | \n\ | %a | \n\ | %a | \n" nondelta allocated nondelta promoted nondelta peak_size | None, None -> pf " | \n\ | \n\ | \n" ) ; pf "%a%a" stat status statd status_deltas ; pf " |