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