|
| 1 | +open Eio.Std |
| 2 | + |
| 3 | +module Read = Fxt.Read |
| 4 | +module Rings = Map.Make(Int) |
| 5 | + |
| 6 | +let ( ++ ) = Int64.add |
| 7 | +let ( -- ) = Int64.sub |
| 8 | + |
| 9 | +type ring_stats = { |
| 10 | + mutable last_event : int64; |
| 11 | + mutable gc_depth : int; |
| 12 | + mutable app_time : int64; |
| 13 | + mutable gc_time : int64; |
| 14 | +} |
| 15 | + |
| 16 | +type stats = { |
| 17 | + mutable rings : ring_stats Rings.t; |
| 18 | +} |
| 19 | + |
| 20 | +let pp_header f = |
| 21 | + Fmt.pf f "Ring GC/s App/s Total/s %%GC@," |
| 22 | + |
| 23 | +let pp_times f ~gc ~app = |
| 24 | + let gc = Int64.to_float gc /. 1e9 in |
| 25 | + let app = Int64.to_float app /. 1e9 in |
| 26 | + Fmt.pf f "%8.3f %8.3f %8.3f %8.2f" gc app (gc +. app) |
| 27 | + (100. *. (gc /. (gc +. app))) |
| 28 | + |
| 29 | +let pp_ring f (id, stats) = |
| 30 | + Fmt.pf f "%3d " id; pp_times f ~gc:stats.gc_time ~app:stats.app_time |
| 31 | + |
| 32 | +let pp_stats f { rings } = |
| 33 | + pp_header f; |
| 34 | + Fmt.(list ~sep:cut) pp_ring f (Rings.bindings rings); |
| 35 | + let gc_time = Rings.fold (fun _ ring acc -> acc ++ ring.gc_time) rings 0L in |
| 36 | + let app_time = Rings.fold (fun _ ring acc -> acc ++ ring.app_time) rings 0L in |
| 37 | + Fmt.pf f "@,@,All "; pp_times f ~gc:gc_time ~app:app_time |
| 38 | + |
| 39 | +let i64 = Int64.to_int |
| 40 | + |
| 41 | +let get_ring ~timestamp t ring = |
| 42 | + match Rings.find_opt ring t.rings with |
| 43 | + | Some x -> x |
| 44 | + | None -> |
| 45 | + let x = { last_event = timestamp; gc_time = 0L; app_time = 0L; gc_depth = 0 } in |
| 46 | + t.rings <- Rings.add ring x t.rings; |
| 47 | + x |
| 48 | + |
| 49 | +let ring_of_thread ~timestamp t (thread : Read.thread) = |
| 50 | + let id = i64 thread.tid in |
| 51 | + if id land 3 = 1 then ( |
| 52 | + let ring = id lsr 2 in |
| 53 | + Some (get_ring t ring ~timestamp) |
| 54 | + ) else None |
| 55 | + |
| 56 | +let process_event stats e = |
| 57 | + let { Read.Event.ty; timestamp; thread; category; name = _; args = _ } = e in |
| 58 | + match ring_of_thread stats thread ~timestamp with |
| 59 | + | None -> () |
| 60 | + | Some ring -> |
| 61 | + let used = timestamp -- ring.last_event in |
| 62 | + if ring.gc_depth = 0 then |
| 63 | + ring.app_time <- ring.app_time ++ used |
| 64 | + else |
| 65 | + ring.gc_time <- ring.gc_time ++ used; |
| 66 | + ring.last_event <- timestamp; |
| 67 | + match category, ty with |
| 68 | + | "gc", Duration_begin -> ring.gc_depth <- ring.gc_depth + 1 |
| 69 | + | "gc", Duration_end -> |
| 70 | + (* todo: we could notice if the event is a top-level one and |
| 71 | + reset the depth to 0 in that case, to cope with missed events. *) |
| 72 | + if ring.gc_depth > 0 then |
| 73 | + ring.gc_depth <- ring.gc_depth - 1 |
| 74 | + else |
| 75 | + traceln "Warning: unexpected GC end event." |
| 76 | + | _ -> () |
| 77 | + |
| 78 | +let analyse reader = |
| 79 | + let stats = { rings = Rings.empty } in |
| 80 | + Read.records reader |> Seq.iter (fun (r : Read.record) -> |
| 81 | + match r with |
| 82 | + | Event e -> process_event stats e |
| 83 | + | User _ -> () |
| 84 | + | Scheduling _ -> () |
| 85 | + | Metadata -> () |
| 86 | + | Kernel _ -> () |
| 87 | + | Unknown _ -> () |
| 88 | + ); |
| 89 | + stats |
| 90 | + |
| 91 | +let main out tracefiles = |
| 92 | + tracefiles |> List.iter (fun tracefile -> |
| 93 | + Eio.Path.with_open_in tracefile @@ |
| 94 | + Eio.Buf_read.parse_exn ~max_size:max_int @@ fun r -> |
| 95 | + let stats = analyse r in |
| 96 | + Fmt.pf out "@[<v>%s:@,@,%a@]@." (Eio.Path.native_exn tracefile) pp_stats stats; |
| 97 | + Fmt.pf out "@.Note: all times are wall-clock and so include time spent blocking.@."; |
| 98 | + ); |
| 99 | + Ok () |
0 commit comments