1(**************************************************************************) 2(* *) 3(* OCaml *) 4(* *) 5(* Mark Shinwell and Leo White, Jane Street Europe *) 6(* *) 7(* Copyright 2015--2016 Jane Street Group LLC *) 8(* *) 9(* All rights reserved. This file is distributed under the terms of *) 10(* the GNU Lesser General Public License version 2.1, with the *) 11(* special exception on linking described in the file LICENSE. *) 12(* *) 13(**************************************************************************) 14 15module Gc_stats : sig 16 type t 17 18 val minor_words : t -> int 19 val promoted_words : t -> int 20 val major_words : t -> int 21 val minor_collections : t -> int 22 val major_collections : t -> int 23 val heap_words : t -> int 24 val heap_chunks : t -> int 25 val compactions : t -> int 26 val top_heap_words : t -> int 27end = struct 28 type t = { 29 minor_words : int; 30 promoted_words : int; 31 major_words : int; 32 minor_collections : int; 33 major_collections : int; 34 heap_words : int; 35 heap_chunks : int; 36 compactions : int; 37 top_heap_words : int; 38 } 39 40 let minor_words t = t.minor_words 41 let promoted_words t = t.promoted_words 42 let major_words t = t.major_words 43 let minor_collections t = t.minor_collections 44 let major_collections t = t.major_collections 45 let heap_words t = t.heap_words 46 let heap_chunks t = t.heap_chunks 47 let compactions t = t.compactions 48 let top_heap_words t = t.top_heap_words 49end 50 51module Program_counter = struct 52 module OCaml = struct 53 type t = Int64.t 54 55 let to_int64 t = t 56 end 57 58 module Foreign = struct 59 type t = Int64.t 60 61 let to_int64 t = t 62 end 63end 64 65module Function_identifier = struct 66 type t = Int64.t 67 68 let to_int64 t = t 69end 70 71module Function_entry_point = struct 72 type t = Int64.t 73 74 let to_int64 t = t 75end 76 77module Int64_map = Map.Make (Int64) 78 79module Frame_table = struct 80 type raw = (Int64.t * (Printexc.Slot.t list)) list 81 82 type t = Printexc.Slot.t list Int64_map.t 83 84 let demarshal chn : t = 85 let raw : raw = Marshal.from_channel chn in 86 List.fold_left (fun map (pc, rev_location_list) -> 87 Int64_map.add pc (List.rev rev_location_list) map) 88 Int64_map.empty 89 raw 90 91 let find_exn = Int64_map.find 92end 93 94module Shape_table = struct 95 type part_of_shape = 96 | Direct_call of { call_site : Int64.t; callee : Int64.t; } 97 | Indirect_call of Int64.t 98 | Allocation_point of Int64.t 99 100 let _ = Direct_call { call_site = 0L; callee = 0L; } 101 let _ = Indirect_call 0L 102 let _ = Allocation_point 0L 103 104 let part_of_shape_size = function 105 | Direct_call _ 106 | Indirect_call _ -> 1 107 | Allocation_point _ -> 3 108 109 type raw = (Int64.t * (part_of_shape list)) list 110 111 type t = part_of_shape list Int64_map.t 112 113 let demarshal chn : t = 114 let raw : raw = Marshal.from_channel chn in 115 List.fold_left (fun map (key, data) -> Int64_map.add key data map) 116 Int64_map.empty 117 raw 118 119 let find_exn = Int64_map.find 120end 121 122module Annotation = struct 123 type t = int 124 125 let to_int t = t 126end 127 128module Trace = struct 129 type node 130 type ocaml_node 131 type foreign_node 132 type uninstrumented_node 133 134 type t = node option 135 136 (* This function unmarshals into malloc blocks, which mean that we 137 obtain a straightforward means of writing [compare] on [node]s. *) 138 external unmarshal : in_channel -> 'a 139 = "caml_spacetime_only_works_for_native_code" 140 "caml_spacetime_unmarshal_trie" 141 142 let unmarshal in_channel = 143 let trace = unmarshal in_channel in 144 if trace = () then 145 None 146 else 147 Some ((Obj.magic trace) : node) 148 149 let node_is_null (node : node) = 150 ((Obj.magic node) : unit) == () 151 152 let foreign_node_is_null (node : foreign_node) = 153 ((Obj.magic node) : unit) == () 154 155 external node_num_header_words : unit -> int 156 = "caml_spacetime_only_works_for_native_code" 157 "caml_spacetime_node_num_header_words" "noalloc" 158 159 let num_header_words = lazy (node_num_header_words ()) 160 161 module OCaml = struct 162 type field_iterator = { 163 node : ocaml_node; 164 offset : int; 165 part_of_shape : Shape_table.part_of_shape; 166 remaining_layout : Shape_table.part_of_shape list; 167 shape_table : Shape_table.t; 168 } 169 170 module Allocation_point = struct 171 type t = field_iterator 172 173 let program_counter t = 174 match t.part_of_shape with 175 | Shape_table.Allocation_point call_site -> call_site 176 | _ -> assert false 177 178 external annotation : ocaml_node -> int -> Annotation.t 179 = "caml_spacetime_only_works_for_native_code" 180 "caml_spacetime_ocaml_allocation_point_annotation" 181 "noalloc" 182 183 let annotation t = annotation t.node t.offset 184 185 external count : ocaml_node -> int -> int 186 = "caml_spacetime_only_works_for_native_code" 187 "caml_spacetime_ocaml_allocation_point_count" 188 "noalloc" 189 190 let num_words_including_headers t = count t.node t.offset 191 end 192 193 module Direct_call_point = struct 194 type _ t = field_iterator 195 196 let call_site t = 197 match t.part_of_shape with 198 | Shape_table.Direct_call { call_site; _ } -> call_site 199 | _ -> assert false 200 201 let callee t = 202 match t.part_of_shape with 203 | Shape_table.Direct_call { callee; _ } -> callee 204 | _ -> assert false 205 206 external callee_node : ocaml_node -> int -> 'target 207 = "caml_spacetime_only_works_for_native_code" 208 "caml_spacetime_ocaml_direct_call_point_callee_node" 209 210 let callee_node (type target) (t : target t) : target = 211 callee_node t.node t.offset 212 end 213 214 module Indirect_call_point = struct 215 type t = field_iterator 216 217 let call_site t = 218 match t.part_of_shape with 219 | Shape_table.Indirect_call call_site -> call_site 220 | _ -> assert false 221 222 module Callee = struct 223 (* CR-soon mshinwell: we should think about the names again. This is 224 a "c_node" but it isn't foreign. *) 225 type t = foreign_node 226 227 let is_null = foreign_node_is_null 228 229 (* CR-soon mshinwell: maybe rename ...c_node_call_site -> c_node_pc, 230 since it isn't a call site in this case. *) 231 external callee : t -> Function_entry_point.t 232 = "caml_spacetime_only_works_for_native_code" 233 "caml_spacetime_c_node_call_site" 234 235 (* This can return a node satisfying "is_null" in the case of an 236 uninitialised tail call point. See the comment in the C code. *) 237 external callee_node : t -> node 238 = "caml_spacetime_only_works_for_native_code" 239 "caml_spacetime_c_node_callee_node" "noalloc" 240 241 external next : t -> foreign_node 242 = "caml_spacetime_only_works_for_native_code" 243 "caml_spacetime_c_node_next" "noalloc" 244 245 let next t = 246 let next = next t in 247 if foreign_node_is_null next then None 248 else Some next 249 end 250 251 external callees : ocaml_node -> int -> Callee.t 252 = "caml_spacetime_only_works_for_native_code" 253 "caml_spacetime_ocaml_indirect_call_point_callees" 254 "noalloc" 255 256 let callees t = 257 let callees = callees t.node t.offset in 258 if Callee.is_null callees then None 259 else Some callees 260 end 261 262 module Field = struct 263 type t = field_iterator 264 265 type direct_call_point = 266 | To_ocaml of ocaml_node Direct_call_point.t 267 | To_foreign of foreign_node Direct_call_point.t 268 | To_uninstrumented of 269 uninstrumented_node Direct_call_point.t 270 271 type classification = 272 | Allocation of Allocation_point.t 273 | Direct_call of direct_call_point 274 | Indirect_call of Indirect_call_point.t 275 276 external classify_direct_call_point : ocaml_node -> int -> int 277 = "caml_spacetime_only_works_for_native_code" 278 "caml_spacetime_classify_direct_call_point" 279 "noalloc" 280 281 let classify t = 282 match t.part_of_shape with 283 | Shape_table.Direct_call callee -> 284 let direct_call_point = 285 match classify_direct_call_point t.node t.offset with 286 | 0 -> 287 (* We should never classify uninitialised call points here. *) 288 assert false 289 | 1 -> To_ocaml t 290 | 2 -> To_foreign t 291 | _ -> assert false 292 in 293 Direct_call direct_call_point 294 | Shape_table.Indirect_call _ -> Indirect_call t 295 | Shape_table.Allocation_point _ -> Allocation t 296 297 (* CR-soon mshinwell: change to "is_unused"? *) 298 let is_uninitialised t = 299 let offset_to_node_hole = 300 match t.part_of_shape with 301 | Shape_table.Direct_call _ -> Some 0 302 | Shape_table.Indirect_call _ -> Some 0 303 | Shape_table.Allocation_point _ -> None 304 in 305 match offset_to_node_hole with 306 | None -> false 307 | Some offset_to_node_hole -> 308 (* There are actually two cases: 309 1. A normal unused node hole, which says Val_unit; 310 2. An unused tail call point. This will contain a pointer to the 311 start of the current node, but it also has the bottom bit 312 set. *) 313 let offset = t.offset + offset_to_node_hole in 314 Obj.is_int (Obj.field (Obj.repr t.node) offset) 315 316 let rec next t = 317 match t.remaining_layout with 318 | [] -> None 319 | part_of_shape::remaining_layout -> 320 let size = Shape_table.part_of_shape_size t.part_of_shape in 321 let offset = t.offset + size in 322 assert (offset < Obj.size (Obj.repr t.node)); 323 let t = 324 { node = t.node; 325 offset; 326 part_of_shape; 327 remaining_layout; 328 shape_table = t.shape_table; 329 } 330 in 331 skip_uninitialised t 332 333 and skip_uninitialised t = 334 if not (is_uninitialised t) then Some t 335 else next t 336 end 337 338 module Node = struct 339 type t = ocaml_node 340 341 external function_identifier : t -> Function_identifier.t 342 = "caml_spacetime_only_works_for_native_code" 343 "caml_spacetime_ocaml_function_identifier" 344 345 external next_in_tail_call_chain : t -> t 346 = "caml_spacetime_only_works_for_native_code" 347 "caml_spacetime_ocaml_tail_chain" "noalloc" 348 349 external compare : t -> t -> int 350 = "caml_spacetime_only_works_for_native_code" 351 "caml_spacetime_compare_node" "noalloc" 352 353 let fields t ~shape_table = 354 match Shape_table.find_exn (function_identifier t) shape_table with 355 | exception Not_found -> None 356 | [] -> None 357 | part_of_shape::remaining_layout -> 358 let t = 359 { node = t; 360 offset = Lazy.force num_header_words; 361 part_of_shape; 362 remaining_layout; 363 shape_table; 364 } 365 in 366 Field.skip_uninitialised t 367 end 368 end 369 370 module Foreign = struct 371 module Node = struct 372 type t = foreign_node 373 374 external compare : t -> t -> int 375 = "caml_spacetime_only_works_for_native_code" 376 "caml_spacetime_compare_node" "noalloc" 377 378 let fields t = 379 if foreign_node_is_null t then None 380 else Some t 381 end 382 383 module Allocation_point = struct 384 type t = foreign_node 385 386 external program_counter : t -> Program_counter.Foreign.t 387 (* This is not a mistake; the same C function works. *) 388 = "caml_spacetime_only_works_for_native_code" 389 "caml_spacetime_c_node_call_site" 390 391 external annotation : t -> Annotation.t 392 = "caml_spacetime_only_works_for_native_code" 393 "caml_spacetime_c_node_profinfo" "noalloc" 394 395 external num_words_including_headers : t -> int 396 = "caml_spacetime_only_works_for_native_code" 397 "caml_spacetime_c_node_allocation_count" "noalloc" 398 end 399 400 module Call_point = struct 401 type t = foreign_node 402 403 external call_site : t -> Program_counter.Foreign.t 404 = "caml_spacetime_only_works_for_native_code" 405 "caml_spacetime_c_node_call_site" 406 407 (* May return a null node. See comment above and the C code. *) 408 external callee_node : t -> node 409 = "caml_spacetime_only_works_for_native_code" 410 "caml_spacetime_c_node_callee_node" "noalloc" 411 end 412 413 module Field = struct 414 type t = foreign_node 415 416 type classification = 417 | Allocation of Allocation_point.t 418 | Call of Call_point.t 419 420 external is_call : t -> bool 421 = "caml_spacetime_only_works_for_native_code" 422 "caml_spacetime_c_node_is_call" "noalloc" 423 424 let classify t = 425 if is_call t then Call t 426 else Allocation t 427 428 external next : t -> t 429 = "caml_spacetime_only_works_for_native_code" 430 "caml_spacetime_c_node_next" "noalloc" 431 432 let next t = 433 let next = next t in 434 if foreign_node_is_null next then None 435 else Some next 436 end 437 end 438 439 module Node = struct 440 module T = struct 441 type t = node 442 443 external compare : t -> t -> int 444 = "caml_spacetime_only_works_for_native_code" 445 "caml_spacetime_compare_node" "noalloc" 446 end 447 448 include T 449 450 type classification = 451 | OCaml of OCaml.Node.t 452 | Foreign of Foreign.Node.t 453 454 (* CR-soon lwhite: These functions should work in bytecode *) 455 external is_ocaml_node : t -> bool 456 = "caml_spacetime_only_works_for_native_code" 457 "caml_spacetime_is_ocaml_node" "noalloc" 458 459 let classify t = 460 if is_ocaml_node t then OCaml ((Obj.magic t) : ocaml_node) 461 else Foreign ((Obj.magic t) : foreign_node) 462 463 let of_ocaml_node (node : ocaml_node) : t = Obj.magic node 464 let of_foreign_node (node : foreign_node) : t = Obj.magic node 465 466 module Map = Map.Make (T) 467 module Set = Set.Make (T) 468 end 469 470 let root t = t 471end 472 473module Heap_snapshot = struct 474 475 module Entries = struct 476 type t = int array (* == "struct snapshot_entries" *) 477 478 let length t = 479 let length = Array.length t in 480 assert (length mod 3 = 0); 481 length / 3 482 483 let annotation t idx = t.(idx*3) 484 let num_blocks t idx = t.(idx*3 + 1) 485 let num_words_including_headers t idx = t.(idx*3 + 2) 486 end 487 488 type total_allocations = 489 | End 490 | Total of { 491 annotation : Annotation.t; 492 count : int; 493 next : total_allocations; 494 } 495 496 let (_ : total_allocations) = (* suppress compiler warning *) 497 Total { annotation = 0; count = 0; next = End; } 498 499 type t = { 500 timestamp : float; 501 gc_stats : Gc_stats.t; 502 entries : Entries.t; 503 words_scanned : int; 504 words_scanned_with_profinfo : int; 505 total_allocations : total_allocations; 506 } 507 508 type heap_snapshot = t 509 510 let timestamp t = t.timestamp 511 let gc_stats t = t.gc_stats 512 let entries t = t.entries 513 let words_scanned t = t.words_scanned 514 let words_scanned_with_profinfo t = t.words_scanned_with_profinfo 515 516 module Total_allocation = struct 517 type t = total_allocations (* [End] is forbidden *) 518 519 let annotation = function 520 | End -> assert false 521 | Total { annotation; _ } -> annotation 522 523 let num_words_including_headers = function 524 | End -> assert false 525 | Total { count; _ } -> count 526 527 let next = function 528 | End -> assert false 529 | Total { next = End; _ } -> None 530 | Total { next; _ } -> Some next 531 end 532 533 let total_allocations t = 534 match t.total_allocations with 535 | End -> None 536 | (Total _) as totals -> Some totals 537 538 module Event = struct 539 type t = { 540 event_name : string; 541 time : float; 542 } 543 544 let event_name t = t.event_name 545 let timestamp t = t.time 546 end 547 548 module Series = struct 549 type t = { 550 num_snapshots : int; 551 time_of_writer_close : float; 552 frame_table : Frame_table.t; 553 shape_table : Shape_table.t; 554 traces_by_thread : Trace.t array; 555 finaliser_traces_by_thread : Trace.t array; 556 snapshots : heap_snapshot array; 557 events : Event.t list; 558 } 559 560 let pathname_suffix_trace = "trace" 561 562 (* The order of these constructors must match the C code. *) 563 type what_comes_next = 564 | Snapshot 565 | Traces 566 | Event 567 568 (* Suppress compiler warning 37. *) 569 let _ : what_comes_next list = [Snapshot; Traces; Event;] 570 571 let rec read_snapshots_and_events chn snapshots events = 572 let next : what_comes_next = Marshal.from_channel chn in 573 match next with 574 | Snapshot -> 575 let snapshot : heap_snapshot = Marshal.from_channel chn in 576 read_snapshots_and_events chn (snapshot :: snapshots) events 577 | Event -> 578 let event_name : string = Marshal.from_channel chn in 579 let time : float = Marshal.from_channel chn in 580 let event = { Event. event_name; time; } in 581 read_snapshots_and_events chn snapshots (event :: events) 582 | Traces -> 583 (Array.of_list (List.rev snapshots)), List.rev events 584 585 let read ~path = 586 let chn = open_in path in 587 let magic_number : int = Marshal.from_channel chn in 588 let magic_number_base = magic_number land 0xffff_ffff in 589 let version_number = magic_number lsr 32 in 590 if magic_number_base <> 0xace00ace then begin 591 failwith "Raw_spacetime_lib: not a Spacetime profiling file" 592 end else begin 593 match version_number with 594 | 0 -> 595 let snapshots, events = read_snapshots_and_events chn [] [] in 596 let num_snapshots = Array.length snapshots in 597 let time_of_writer_close : float = Marshal.from_channel chn in 598 let frame_table = Frame_table.demarshal chn in 599 let shape_table = Shape_table.demarshal chn in 600 let num_threads : int = Marshal.from_channel chn in 601 let traces_by_thread = Array.init num_threads (fun _ -> None) in 602 let finaliser_traces_by_thread = 603 Array.init num_threads (fun _ -> None) 604 in 605 for thread = 0 to num_threads - 1 do 606 let trace : Trace.t = Trace.unmarshal chn in 607 let finaliser_trace : Trace.t = Trace.unmarshal chn in 608 traces_by_thread.(thread) <- trace; 609 finaliser_traces_by_thread.(thread) <- finaliser_trace 610 done; 611 close_in chn; 612 { num_snapshots; 613 time_of_writer_close; 614 frame_table; 615 shape_table; 616 traces_by_thread; 617 finaliser_traces_by_thread; 618 snapshots; 619 events; 620 } 621 | _ -> 622 failwith "Raw_spacetime_lib: unknown Spacetime profiling file \ 623 version number" 624 end 625 626 type trace_kind = Normal | Finaliser 627 628 let num_threads t = Array.length t.traces_by_thread 629 630 let trace t ~kind ~thread_index = 631 if thread_index < 0 || thread_index >= num_threads t then None 632 else 633 match kind with 634 | Normal -> Some t.traces_by_thread.(thread_index) 635 | Finaliser -> Some t.finaliser_traces_by_thread.(thread_index) 636 637 let num_snapshots t = t.num_snapshots 638 let snapshot t ~index = t.snapshots.(index) 639 let frame_table t = t.frame_table 640 let shape_table t = t.shape_table 641 let time_of_writer_close t = t.time_of_writer_close 642 let events t = t.events 643 end 644end 645