Source file flame_graph_panel.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
open! Core
open Bonsai_web
open Vdom_keyboard
open Memtrace_viewer_common

module Graph = struct
  type t =
    { focus : Data.Fragment.t
    ; trie : Data.Fragment_trie.t
    ; call_sites : Data.Call_sites.t
    ; flame_tree : Data.Fragment.Oriented.t list
    ; icicle_tree : Data.Fragment.Oriented.t list
    ; focus_seq : Data.Fragment.Iterator.t option
    }

  module Node = struct
    type t =
      | Tree of Data.Fragment.Oriented.t
      | Focus of Data.Fragment.Iterator.t

    let location = function
      | Tree tree -> Data.Fragment.Oriented.first tree
      | Focus seq -> Data.Fragment.Iterator.location seq
    ;;

    let type_ ~graph:_ t : Flame_graph_view.Node_type.t =
      match location t with
      | Allocation_site _ -> Allocation_site
      | Function _ -> Function
      | Allocator | Toplevel | Dummy -> assert false
    ;;

    let label ~graph:_ t =
      match location t with
      | Allocation_site alloc_site -> Data.Allocation_site.short_name alloc_site
      | loc -> Data.Location.defname loc
    ;;

    let entry ~graph = function
      | Tree tree -> Data.Fragment.entry (Data.Fragment.Oriented.fragment tree)
      | Focus _ -> Data.Fragment.entry graph.focus
    ;;

    let allocations ~graph t = Data.Entry.allocations (entry ~graph t)
    let size ~graph t = allocations ~graph t |> Byte_units.bytes_float

    let details ~graph t =
      let loc = location t in
      let entry = entry ~graph t in
      let call_sites = Data.Call_sites.for_location graph.call_sites loc in
      String.concat
        [ Data.Location.full_name loc ~call_sites:(Some call_sites)
        ; ": "
        ; Data.Entry.allocations_string entry
        ; " ("
        ; Data.Entry.percentage_string entry
        ; ")"
        ]
    ;;
  end

  module Tree = struct
    type t = Data.Fragment.Oriented.t

    let node t = Node.Tree t

    let children ~graph:_ t =
      let children = Data.Fragment.Oriented.one_frame_extensions t in
      List.filter_map children ~f:(fun (loc, fragment) ->
        if Data.Location.is_special loc then None else Some fragment)
    ;;

    let parent ~graph t =
      let%bind.Option t = Data.Fragment.Oriented.retract t in
      let fragment = Data.Fragment.Oriented.fragment t in
      if Data.Fragment.same fragment graph.focus then None else Some t
    ;;

    let same (t1 : t) (t2 : t) =
      let fragment1 = Data.Fragment.Oriented.fragment t1 in
      let fragment2 = Data.Fragment.Oriented.fragment t2 in
      Data.Fragment.same fragment1 fragment2
    ;;

    let allocations t =
      let fragment = Data.Fragment.Oriented.fragment t in
      let entry = Data.Fragment.entry fragment in
      Data.Entry.allocations entry
    ;;
  end

  module Sequence = struct
    type t = Data.Fragment.Iterator.t

    let node t = Node.Focus t

    let label ~graph t =
      (* This assumes that we're labeling the entire sequence, which is to say,
         this is the initial node in the sequence. *)
      let fragment = Data.Fragment.Iterator.suffix t in
      let allocs = Data.Entry.allocations (Data.Fragment.entry fragment) in
      let total_allocs = Data.Fragment_trie.total_allocations graph.trie in
      let percentage = Byte_units.(allocs // total_allocs) *. 100. in
      [ allocs |> Byte_units.Short.to_string; sprintf "%.1f%%" percentage ]
    ;;

    let next ~graph:_ seq =
      let%bind.Option next = Data.Fragment.Iterator.next seq in
      if Data.Location.is_special (Data.Fragment.Iterator.location next)
      then None
      else Some next
    ;;

    let prev ~graph:_ seq =
      let%bind.Option prev = Data.Fragment.Iterator.prev seq in
      if Data.Location.is_special (Data.Fragment.Iterator.location prev)
      then None
      else Some prev
    ;;

    let same t1 t2 =
      let prefix1 = Data.Fragment.Iterator.prefix t1 in
      let prefix2 = Data.Fragment.Iterator.prefix t2 in
      Data.Fragment.same prefix1 prefix2
    ;;
  end

  let create ~trie ~call_sites ~focus =
    let flame_tree =
      if Data.Fragment.is_empty focus
      then []
      else (
        let orient = Orientation.Callees in
        let tree = Data.Fragment.oriented focus ~orient in
        Tree.children ~graph:() tree)
    in
    let icicle_tree =
      if Data.Fragment.is_empty focus
      then []
      else (
        let orient = Orientation.Callers in
        let tree = Data.Fragment.oriented focus ~orient in
        Tree.children ~graph:() tree)
    in
    let focus_seq =
      let%bind.Option seq = Data.Fragment.iterator_start focus in
      let rec loop seq =
        if Data.Location.is_special (Data.Fragment.Iterator.location seq)
        then (
          match Data.Fragment.Iterator.next seq with
          | None -> None
          | Some seq -> loop seq)
        else Some seq
      in
      loop seq
    in
    { trie; call_sites; focus; flame_tree; icicle_tree; focus_seq }
  ;;

  let flame_tree { flame_tree; _ } = flame_tree
  let icicle_tree { icicle_tree; _ } = icicle_tree
  let focus { focus_seq; _ } = focus_seq

  let size { flame_tree; icicle_tree; focus_seq; focus; _ } =
    let allocations =
      match focus_seq with
      | Some _ ->
        let entry = Data.Fragment.entry focus in
        Data.Entry.allocations entry
      | None ->
        let flame_allocations =
          List.fold_left
            ~init:Byte_units.zero
            ~f:(fun acc tree -> Byte_units.(acc + Tree.allocations tree))
            flame_tree
        in
        let icicle_allocations =
          List.fold_left
            ~init:Byte_units.zero
            ~f:(fun acc tree -> Byte_units.(acc + Tree.allocations tree))
            icicle_tree
        in
        Byte_units.max flame_allocations icicle_allocations
    in
    Byte_units.bytes_float allocations
  ;;
end

module Flame_graph = Flame_graph_view.Make (Graph)

module Selection = struct
  type t =
    | Flame of { fragment : Data.Fragment.t }
    | Icicle of { fragment : Data.Fragment.t }
    | Focus of
        { callers_fragment : Data.Fragment.t
        ; callees_fragment : Data.Fragment.t
        }

  let of_selector (selector : Flame_graph.Selector.t) : t =
    match selector with
    | Flame tree ->
      let fragment = Data.Fragment.Oriented.fragment tree in
      Flame { fragment }
    | Icicle tree ->
      let fragment = Data.Fragment.Oriented.fragment tree in
      Icicle { fragment }
    | Focus seq ->
      let callers_fragment = Data.Fragment.Iterator.suffix seq in
      let callees_fragment = Data.Fragment.Iterator.prefix seq in
      Focus { callers_fragment; callees_fragment }
  ;;
end

module Default_selection = App_state.Default_selection

module State = struct
  type t =
    | Flame of { backtrace : Data.Backtrace.t }
    | Icicle of { backtrace : Data.Backtrace.Reversed.t }
    | Focus of { trace : Data.Fragment.Iterator.Trace.t }
  [@@deriving sexp, equal]

  let to_selector ~trie : t -> Flame_graph.Selector.t option = function
    | Flame { backtrace } ->
      let%map.Option fragment = Data.Fragment_trie.find trie backtrace in
      let orient = Orientation.Callees in
      let tree = Data.Fragment.oriented fragment ~orient in
      Flame_graph.Selector.Flame tree
    | Icicle { backtrace } ->
      let%map.Option fragment = Data.Fragment_trie.find_rev trie backtrace in
      let orient = Orientation.Callers in
      let tree = Data.Fragment.oriented fragment ~orient in
      Flame_graph.Selector.Icicle tree
    | Focus { trace } ->
      let%map.Option seq = Data.Fragment_trie.find_iterator trie trace in
      Flame_graph.Selector.Focus seq
  ;;

  let of_selector : Flame_graph.Selector.t -> t = function
    | Flame tree ->
      let fragment = Data.Fragment.Oriented.fragment tree in
      let backtrace = Data.Fragment.backtrace fragment in
      Flame { backtrace }
    | Icicle tree ->
      let fragment = Data.Fragment.Oriented.fragment tree in
      let backtrace = Data.Fragment.backtrace_rev fragment in
      Icicle { backtrace }
    | Focus seq ->
      let trace = Data.Fragment.Iterator.trace seq in
      Focus { trace }
  ;;

  let default ~state ~default_selection ~focus =
    match state with
    | None -> None
    | Some _ ->
      (match default_selection with
       | Default_selection.No_selection -> None
       | Default_selection.First_caller ->
         let%map.Option seq = Data.Fragment.iterator_start focus in
         let trace = Data.Fragment.Iterator.trace seq in
         Focus { trace }
       | Default_selection.First_callee ->
         let%map.Option seq = Data.Fragment.iterator_end focus in
         let trace = Data.Fragment.Iterator.trace seq in
         Focus { trace })
  ;;
end

let commands ~selection ~focus ~set_focus ~keep_selection_in_view
  : Flame_graph_view.Commands.t
  =
  let module Command = Flame_graph_view.Command in
  match selection with
  | None -> Flame_graph_view.Commands.all_disabled (* it won't be used anyway *)
  | Some selection ->
    let set_focus fragment ~default_selection =
      Effect.Many [ set_focus fragment ~default_selection; keep_selection_in_view ]
    in
    let extend_focus_to : Flame_graph_view.Command.t =
      match (selection : Selection.t) with
      | Flame { fragment } ->
        Enabled
          (set_focus fragment ~default_selection:App_state.Default_selection.First_callee)
      | Icicle { fragment } ->
        Enabled (set_focus fragment ~default_selection:First_caller)
      | Focus _ -> Disabled
    in
    let retract_callers_from_focus : Command.t =
      match selection with
      | Focus { callers_fragment; _ } when not (Data.Fragment.same callers_fragment focus)
        -> Enabled (set_focus callers_fragment ~default_selection:First_caller)
      | Focus _ | Flame _ | Icicle _ -> Disabled
    in
    let retract_callees_from_focus : Command.t =
      match selection with
      | Focus { callees_fragment; _ } when not (Data.Fragment.same callees_fragment focus)
        -> Enabled (set_focus callees_fragment ~default_selection:First_callee)
      | Focus _ | Flame _ | Icicle _ -> Disabled
    in
    { extend_focus_to; retract_callers_from_focus; retract_callees_from_focus }
;;

type t =
  { view : Vdom.Node.t
  ; key_handler : Keyboard_event_handler.t
  ; selection : Selection.t option
  ; reset_selection :
      Data.Fragment.t -> default_selection:Default_selection.t -> unit Ui_effect.t
  ; scroll_focus_into_view : unit Ui_effect.t
  }

let component ~trie ~call_sites ~focus ~set_focus ~activate =
  let open Bonsai.Let_syntax in
  let%sub state, set_state = Bonsai.state_opt () ~equal:[%equal: State.t] in
  let%sub selection =
    return
      (let%map state = state
       and trie = trie in
       Option.bind ~f:(State.to_selector ~trie) state)
  in
  (* We need an effect that scrolls the selected node into view _after_ the next
     display, since it's not until then that it will be off screen. *)
  let%sub scroll_selection_into_view_after_display =
    After_next_display.component (Value.return Flame_graph.scroll_selection_into_view)
  in
  let%sub scroll_focus_into_view_after_display =
    After_next_display.component (Value.return Flame_graph.scroll_focus_into_view)
  in
  let%sub flame_graph =
    let set_selection =
      let%map set_state = set_state in
      fun selector -> set_state (Some (State.of_selector selector))
    in
    let activate =
      let%map activate = activate
      and set_state = set_state in
      fun selector ->
        Ui_effect.Many
          [ set_state (Some (State.of_selector selector))
          ; activate (Selection.of_selector selector)
          ]
    in
    let%sub graph : Graph.t Bonsai.Computation.t =
      let%arr focus = focus
      and trie = trie
      and call_sites = call_sites in
      Graph.create ~focus ~trie ~call_sites
    in
    let commands =
      let%map selection = selection
      and focus = focus
      and set_focus = set_focus
      and keep_selection_in_view = scroll_selection_into_view_after_display in
      let selection = selection |> Option.map ~f:Selection.of_selector in
      commands ~selection ~focus ~set_focus ~keep_selection_in_view
    in
    Flame_graph.component
      graph
      ~selection
      ~select:set_selection
      ~navigate_to:set_selection
      ~activate
      ~commands
  in
  let selection =
    let%map selection = selection in
    Option.map ~f:Selection.of_selector selection
  in
  return
    (let%map flame_graph = flame_graph
     and state = state
     and set_state = set_state
     and selection = selection
     and scroll_focus_into_view = scroll_focus_into_view_after_display in
     let view = flame_graph.view in
     let key_handler = flame_graph.key_handler in
     let reset_selection focus ~default_selection =
       let new_state = State.default ~state ~default_selection ~focus in
       set_state new_state
     in
     { view; key_handler; selection; reset_selection; scroll_focus_into_view })
;;