Source file location_table.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
open! Core
open! Bonsai_web
open Memtrace_viewer_common
module Row = struct
type t =
{ fragment : Data.Fragment.t
; display : Data.Location.t list
; allocations : Byte_units.Stable.V2.t
}
module Id = Data.Backtrace
let compare (id1, t1) (id2, t2) =
match Byte_units.compare t2.allocations t1.allocations with
| 0 -> Data.Backtrace.compare id1 id2
| c -> c
;;
let id { fragment; _ } = Data.Fragment.backtrace fragment
end
module Table = struct
module Row = Row
module Col_id = struct
module T = struct
type t =
| Location
| Allocations
| Percentage
[@@deriving sexp, compare]
end
include T
include Comparable.Make (T)
end
include Bonsai_simple_table.Make (Row) (Col_id)
end
let view_location loc ~call_sites =
let call_sites = Data.Call_sites.for_location call_sites loc in
Vdom.Node.span
~attrs:[ Vdom.Attr.title (Data.Location.full_name loc ~call_sites:(Some call_sites)) ]
[ Vdom.Node.text extra; Location.format_dom loc ~call_sites:None ]
;;
let cell ?(attr = Vdom.Attr.empty) node = { Table.node; attrs = [ attr ] }
let attr_if cond attr = if cond then attr else Vdom.Attr.empty
let location_col ~call_sites ~standard_attrs =
let open Vdom in
let = cell (Node.text "Location") in
let render _ (row : Table.Row.t) =
let node =
match row.display with
| [] -> Node.div ~attrs:[ Attr.class_ "last-loc" ] [ Node.text "(empty)" ]
| first :: rest ->
(match List.rev rest with
| [] ->
Node.div
~attrs:[ Attr.class_ "last-loc" ]
[ view_location "" first ~call_sites ]
| last :: _ ->
Node.span
[ Node.div
~attrs:[ Attr.class_ "last-loc" ]
[ view_location "" last ~call_sites ]
; Node.div
~attrs:[ Attr.class_ "first-loc" ]
[ view_location "..." first ~call_sites ]
])
in
let attr = standard_attrs ~fragment:row.fragment in
cell node ~attr
in
let classes = [ "locations-column" ] in
let group = None in
Table.Column.create ~classes ~header ~render ~group ()
;;
let special_class ~fragment =
attr_if (Data.Fragment.is_trivial fragment) (Vdom.Attr.class_ "loc-special")
;;
let allocations_col ~standard_attrs =
let = cell (Vdom.Node.text "Allocs") in
let render _id ({ fragment; allocations; _ } : Table.Row.t) =
let node = Vdom.Node.text (Byte_units.Short.to_string allocations) in
let attr = Vdom.Attr.many [ special_class ~fragment; standard_attrs ~fragment ] in
cell ~attr node
in
let classes = [ "allocations-column" ] in
let group = None in
Table.Column.create ~classes ~header ~render ~group ()
;;
let percentage_col ~total_allocations ~standard_attrs =
let = cell Vdom.Node.none in
let render _id ({ fragment; allocations; _ } : Table.Row.t) =
let percentage = Byte_units.(allocations // total_allocations) *. 100. in
let node = Vdom.Node.textf "%.1f%%" percentage in
let attr = Vdom.Attr.many [ special_class ~fragment; standard_attrs ~fragment ] in
cell ~attr node
in
let classes = [ "percentage-column" ] in
let group = None in
Table.Column.create ~classes ~header ~render ~group ()
;;
let standard_attrs ~fragment ~focus ~set_focus ~on_click_row =
let focus_class =
attr_if
(Data.Fragment.same fragment focus)
(Vdom.Attr.class_ "location-table-fragment-in-focus")
in
let click_handler = Vdom.Attr.on_click (fun _ -> on_click_row fragment) in
let double_click_handler = Vdom.Attr.on_double_click (fun _ -> set_focus fragment) in
Vdom.Attr.many [ focus_class; click_handler; double_click_handler ]
;;
let cols ~total_allocations ~call_sites ~focus ~set_focus ~on_click_row =
let standard_attrs ~fragment =
standard_attrs ~fragment ~focus ~set_focus ~on_click_row
in
Table.Col_id.Map.of_alist_exn
[ Location, location_col ~call_sites ~standard_attrs
; Allocations, allocations_col ~standard_attrs
; Percentage, percentage_col ~total_allocations ~standard_attrs
]
;;
let key_handler ~set_focus ~table_key_handler ~selected_fragment =
let open Vdom_keyboard in
let key = Keystroke.create' in
match selected_fragment with
| Some selected_fragment ->
let set_focus_command : Keyboard_event_handler.Command.t =
let keys = [ key Space; key Enter ] in
let description = "Focus on selected" in
let group = None in
let handler _ =
Effect.Many
[ set_focus selected_fragment
;
Effect.Prevent_default
]
in
{ keys; description; group; handler }
in
Keyboard_event_handler.add_command_exn table_key_handler set_focus_command
| None ->
let key_handler =
Keyboard_event_handler.add_disabled_key_exn table_key_handler (key Space)
in
let key_handler =
Keyboard_event_handler.add_disabled_key_exn key_handler (key Enter)
in
key_handler
;;
type t =
{ view : Vdom.Node.t
; key_handler : Vdom_keyboard.Keyboard_event_handler.t
; selection : (Data.Backtrace.t * Data.Fragment.t) option
; set_selection : Data.Fragment.t option -> unit Vdom.Effect.t
; move_selection : [ `Prev | `Next ] -> unit Vdom.Effect.t
}
let component
~total_allocations
~call_sites
~rows
~presorted
~focus
~set_focus
~on_click_row
=
let open Bonsai.Let_syntax in
let%sub input : Table.Input.t Bonsai.Computation.t =
let%arr focus = focus
and set_focus = set_focus
and on_click_row = on_click_row
and total_allocations = total_allocations
and call_sites = call_sites
and row_specs = rows
and presorted = presorted in
let row_specs = List.map ~f:(fun row -> Row.id row, row) row_specs in
let row_specs =
if presorted then row_specs else List.sort ~compare:Row.compare row_specs
in
let row_ids_in_order = `These (List.map ~f:fst row_specs) in
let col_ids_in_order = [ Table.Col_id.Location; Allocations; Percentage ] in
let rows = Data.Backtrace.Map.of_alist_exn row_specs in
let cols = cols ~total_allocations ~call_sites ~focus ~set_focus ~on_click_row in
let table_attrs = [ Vdom.Attr.class_ "location-table" ] in
Table.Input.create ~rows ~cols ~row_ids_in_order ~col_ids_in_order ~table_attrs ()
in
let%sub table = Table.bonsai input in
return
(let%map { Table.Result.view
; view_for_testing = _
; key_handler = table_key_handler
; focus_row
; inject
}
=
table
and set_focus = set_focus in
let selection =
Option.map focus_row ~f:(fun (backtrace, { fragment; _ }) -> backtrace, fragment)
in
let set_selection selection =
inject (Set_focus_row (Option.map ~f:Data.Fragment.backtrace selection))
in
let move_selection dir = inject (Move_focus dir) in
let key_handler =
let selected_fragment = Option.map selection ~f:(fun (_, fragment) -> fragment) in
key_handler ~table_key_handler ~set_focus ~selected_fragment
in
{ view; key_handler; selection; set_selection; move_selection })
;;